@next2d/builder 0.1.6 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -1
- package/dist/index.js +140 -36
- package/package.json +3 -2
- package/templates/xbox/CMakeLists.txt +74 -2
- package/templates/xbox/README.md +73 -12
- package/templates/xbox/cmake/FetchDawn.cmake +47 -6
- package/templates/xbox/cmake/FindV8.cmake +22 -1
- package/templates/xbox/js/bootstrap.js +31 -28
- package/templates/xbox/js/selftest.js +740 -90
- package/templates/xbox/src/EventLoop.cpp +28 -1
- package/templates/xbox/src/EventLoop.h +12 -0
- package/templates/xbox/src/bindings/Audio.cpp +99 -15
- package/templates/xbox/src/bindings/Bindings.h +10 -0
- package/templates/xbox/src/bindings/Canvas.cpp +45 -0
- package/templates/xbox/src/bindings/Canvas2D.cpp +2 -5
- package/templates/xbox/src/bindings/DomShims.cpp +127 -0
- package/templates/xbox/src/bindings/EventTarget.cpp +2 -0
- package/templates/xbox/src/bindings/Fetch.cpp +2 -8
- package/templates/xbox/src/bindings/Image.cpp +212 -10
- package/templates/xbox/src/bindings/Polyfills.cpp +791 -0
- package/templates/xbox/src/bindings/Video.cpp +96 -7
- package/templates/xbox/src/bindings/webgpu/WebGPU.cpp +815 -15
- package/templates/xbox/src/bindings/webgpu/WebGPUCommon.h +3 -8
- package/templates/xbox/src/gpu/DawnContext.cpp +90 -3
- package/templates/xbox/src/gpu/DawnContext.h +18 -0
- package/templates/xbox/src/main.cpp +88 -6
- package/templates/xbox/src/platform/AudioEngine.cpp +9 -3
- package/templates/xbox/src/v8/V8Runtime.cpp +69 -4
- package/templates/xbox/src/v8/V8Util.h +29 -0
- package/templates/xbox/src/v8/WeakHandle.h +45 -0
- package/templates/xbox/src/worker/WorkerRuntime.cpp +325 -41
- package/templates/xbox/src/worker/WorkerRuntime.h +20 -10
- package/templates/xbox/tests/check_local.sh +3 -2
- package/templates/xbox/tests/raster_test.bin +0 -0
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
#include <v8.h>
|
|
16
16
|
|
|
17
17
|
#include "v8/V8Util.h"
|
|
18
|
+
#include "v8/WeakHandle.h"
|
|
18
19
|
|
|
19
20
|
#include <memory>
|
|
20
21
|
#include <string>
|
|
@@ -27,13 +28,8 @@ struct Holder {
|
|
|
27
28
|
T handle;
|
|
28
29
|
};
|
|
29
30
|
|
|
30
|
-
template <typename T>
|
|
31
|
-
void ReleaseHolder(const v8::WeakCallbackInfo<Holder<T>>& info)
|
|
32
|
-
{
|
|
33
|
-
delete info.GetParameter();
|
|
34
|
-
}
|
|
35
|
-
|
|
36
31
|
// wgpu ハンドル T を、指定 ObjectTemplate から生成したオブジェクトにラップする。
|
|
32
|
+
// GC 時の解放は AttachWeak (第一パスで Global を Reset する規約に準拠) に委ねる。
|
|
37
33
|
template <typename T>
|
|
38
34
|
v8::Local<v8::Object> Wrap(v8::Isolate* isolate,
|
|
39
35
|
v8::Local<v8::ObjectTemplate> tmpl,
|
|
@@ -43,8 +39,7 @@ v8::Local<v8::Object> Wrap(v8::Isolate* isolate,
|
|
|
43
39
|
v8::Local<v8::Object> obj = tmpl->NewInstance(ctx).ToLocalChecked();
|
|
44
40
|
auto* holder = new Holder<T>{std::move(handle)};
|
|
45
41
|
obj->SetInternalField(0, v8::External::New(isolate, holder));
|
|
46
|
-
|
|
47
|
-
global->SetWeak(holder, ReleaseHolder<T>, v8::WeakCallbackType::kParameter);
|
|
42
|
+
v8util::AttachWeak(isolate, obj, holder);
|
|
48
43
|
return obj;
|
|
49
44
|
}
|
|
50
45
|
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
#include "DawnContext.h"
|
|
2
2
|
|
|
3
3
|
#include <Windows.h>
|
|
4
|
-
#include <dawn/native/DawnNative.h>
|
|
5
4
|
#include <iostream>
|
|
6
5
|
|
|
7
6
|
namespace next2d {
|
|
@@ -114,12 +113,13 @@ bool DawnContext::AcquireDevice()
|
|
|
114
113
|
return false;
|
|
115
114
|
}
|
|
116
115
|
|
|
117
|
-
// surface
|
|
116
|
+
// surface の優先フォーマットと CopySrc 可否 (デバッグ読み戻し用) を取得
|
|
118
117
|
wgpu::SurfaceCapabilities caps = {};
|
|
119
118
|
surface_.GetCapabilities(adapter_, &caps);
|
|
120
119
|
if (caps.formatCount > 0) {
|
|
121
120
|
format_ = caps.formats[0];
|
|
122
121
|
}
|
|
122
|
+
can_copy_src_ = (caps.usages & wgpu::TextureUsage::CopySrc) == wgpu::TextureUsage::CopySrc;
|
|
123
123
|
|
|
124
124
|
Configure(width_, height_);
|
|
125
125
|
return true;
|
|
@@ -138,26 +138,113 @@ void DawnContext::Configure(uint32_t width, uint32_t height)
|
|
|
138
138
|
config.device = device_;
|
|
139
139
|
config.format = format_;
|
|
140
140
|
config.usage = wgpu::TextureUsage::RenderAttachment;
|
|
141
|
+
if (can_copy_src_) {
|
|
142
|
+
// 黒画面デバッグ用の surface 読み戻し (DebugProbeSurface) を可能にする
|
|
143
|
+
config.usage |= wgpu::TextureUsage::CopySrc;
|
|
144
|
+
}
|
|
141
145
|
config.width = width_;
|
|
142
146
|
config.height = height_;
|
|
143
147
|
config.presentMode = wgpu::PresentMode::Fifo;
|
|
144
148
|
config.alphaMode = wgpu::CompositeAlphaMode::Opaque;
|
|
145
149
|
|
|
146
150
|
surface_.Configure(&config);
|
|
151
|
+
configured_ = true;
|
|
147
152
|
}
|
|
148
153
|
|
|
149
154
|
wgpu::Texture DawnContext::GetCurrentTexture()
|
|
150
155
|
{
|
|
156
|
+
if (!configured_) {
|
|
157
|
+
return nullptr;
|
|
158
|
+
}
|
|
151
159
|
wgpu::SurfaceTexture surface_texture = {};
|
|
152
160
|
surface_.GetCurrentTexture(&surface_texture);
|
|
161
|
+
frame_texture_acquired_ = true;
|
|
162
|
+
current_texture_ = surface_texture.texture;
|
|
153
163
|
return surface_texture.texture;
|
|
154
164
|
}
|
|
155
165
|
|
|
166
|
+
// 提示直前の surface 中央行を読み戻し、非黒ピクセル数をログする (黒画面調査用)。
|
|
167
|
+
// CI では目視できないため、これが「画面に何が出ているか」の唯一の証拠になる。
|
|
168
|
+
void DawnContext::DebugProbeSurface()
|
|
169
|
+
{
|
|
170
|
+
if (!can_copy_src_ || !current_texture_ || width_ == 0) {
|
|
171
|
+
std::cerr << "[GPU] surface probe unavailable (CopySrc="
|
|
172
|
+
<< (can_copy_src_ ? "yes" : "no") << ")" << std::endl;
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const uint32_t row_bytes = width_ * 4;
|
|
177
|
+
wgpu::BufferDescriptor buf_desc = {};
|
|
178
|
+
buf_desc.size = (row_bytes + 3) & ~3u;
|
|
179
|
+
buf_desc.usage = wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::MapRead;
|
|
180
|
+
wgpu::Buffer buffer = device_.CreateBuffer(&buf_desc);
|
|
181
|
+
|
|
182
|
+
wgpu::TexelCopyTextureInfo src = {};
|
|
183
|
+
src.texture = current_texture_;
|
|
184
|
+
src.origin = { 0, height_ / 2, 0 };
|
|
185
|
+
|
|
186
|
+
wgpu::TexelCopyBufferInfo dst = {};
|
|
187
|
+
dst.buffer = buffer;
|
|
188
|
+
dst.layout.offset = 0;
|
|
189
|
+
dst.layout.bytesPerRow = wgpu::kCopyStrideUndefined; // 1 行コピーでは不要
|
|
190
|
+
|
|
191
|
+
wgpu::Extent3D extent = { width_, 1, 1 };
|
|
192
|
+
|
|
193
|
+
wgpu::CommandEncoder encoder = device_.CreateCommandEncoder();
|
|
194
|
+
encoder.CopyTextureToBuffer(&src, &dst, &extent);
|
|
195
|
+
wgpu::CommandBuffer commands = encoder.Finish();
|
|
196
|
+
device_.GetQueue().Submit(1, &commands);
|
|
197
|
+
|
|
198
|
+
bool done = false;
|
|
199
|
+
wgpu::Future future = buffer.MapAsync(wgpu::MapMode::Read, 0, buf_desc.size,
|
|
200
|
+
wgpu::CallbackMode::AllowProcessEvents,
|
|
201
|
+
[&done](wgpu::MapAsyncStatus, wgpu::StringView) { done = true; });
|
|
202
|
+
// GPU 完了待ちは TimedWaitAny で行う (ProcessEvents の空スピンでは
|
|
203
|
+
// GPU がフレーム描画中のとき完了前にループ上限へ達する)
|
|
204
|
+
instance_.WaitAny(future, 2'000'000'000); // 2s
|
|
205
|
+
if (!done) {
|
|
206
|
+
std::cerr << "[GPU] surface probe: map timeout" << std::endl;
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const auto* px = static_cast<const uint8_t*>(buffer.GetConstMappedRange(0, buf_desc.size));
|
|
211
|
+
uint32_t non_black = 0;
|
|
212
|
+
for (uint32_t x = 0; px && x < width_; ++x) {
|
|
213
|
+
const uint8_t* p = px + static_cast<size_t>(x) * 4;
|
|
214
|
+
if (p[0] || p[1] || p[2]) {
|
|
215
|
+
++non_black;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
std::cerr << "[GPU] surface probe (present #" << present_count_
|
|
219
|
+
<< "): non-black " << non_black << "/" << width_ << " px";
|
|
220
|
+
if (px && width_ > 1) {
|
|
221
|
+
const uint8_t* c = px + static_cast<size_t>(width_ / 2) * 4;
|
|
222
|
+
std::cerr << ", center=(" << +c[0] << "," << +c[1] << "," << +c[2] << "," << +c[3] << ")";
|
|
223
|
+
}
|
|
224
|
+
std::cerr << std::endl;
|
|
225
|
+
buffer.Unmap();
|
|
226
|
+
}
|
|
227
|
+
|
|
156
228
|
void DawnContext::Present()
|
|
157
229
|
{
|
|
158
|
-
|
|
230
|
+
// 提示するのは「未構成でない」かつ「今フレーム GetCurrentTexture 済み」のときのみ。
|
|
231
|
+
// 取得なしで Present すると Dawn の検証エラーになる (描画しないフレームは提示しない)。
|
|
232
|
+
if (surface_ && configured_ && frame_texture_acquired_) {
|
|
233
|
+
++present_count_;
|
|
234
|
+
if (present_count_ <= 3 || present_count_ == 60 || present_count_ % 300 == 0) {
|
|
235
|
+
DebugProbeSurface();
|
|
236
|
+
}
|
|
159
237
|
surface_.Present();
|
|
238
|
+
} else {
|
|
239
|
+
++present_skipped_;
|
|
240
|
+
if (present_skipped_ <= 3 || present_skipped_ % 300 == 0) {
|
|
241
|
+
std::cerr << "[GPU] present skipped #" << present_skipped_
|
|
242
|
+
<< " (configured=" << configured_
|
|
243
|
+
<< " acquired=" << frame_texture_acquired_ << ")" << std::endl;
|
|
244
|
+
}
|
|
160
245
|
}
|
|
246
|
+
frame_texture_acquired_ = false;
|
|
247
|
+
current_texture_ = nullptr;
|
|
161
248
|
}
|
|
162
249
|
|
|
163
250
|
void DawnContext::Tick()
|
|
@@ -36,6 +36,9 @@ public:
|
|
|
36
36
|
// 提示する。
|
|
37
37
|
void Present();
|
|
38
38
|
|
|
39
|
+
// 提示直前の surface 中央行を読み戻して非黒ピクセル数をログする (黒画面調査用)。
|
|
40
|
+
void DebugProbeSurface();
|
|
41
|
+
|
|
39
42
|
// Dawn の非同期イベント(コールバック)を進める。毎フレーム呼ぶ。
|
|
40
43
|
void Tick();
|
|
41
44
|
|
|
@@ -59,6 +62,21 @@ private:
|
|
|
59
62
|
HWND hwnd_ = nullptr;
|
|
60
63
|
uint32_t width_ = 0;
|
|
61
64
|
uint32_t height_ = 0;
|
|
65
|
+
|
|
66
|
+
// surface が Configure 済みか。未構成のまま Present すると Dawn の
|
|
67
|
+
// 検証エラーが毎フレーム出るため、Present/GetCurrentTexture をゲートする。
|
|
68
|
+
bool configured_ = false;
|
|
69
|
+
|
|
70
|
+
// 今フレーム GetCurrentTexture が呼ばれたか。呼ばれていないフレームで
|
|
71
|
+
// Present すると Dawn の検証エラーになるため、Present をゲートする
|
|
72
|
+
// (アプリが描画しないフレームは提示しない)。
|
|
73
|
+
bool frame_texture_acquired_ = false;
|
|
74
|
+
|
|
75
|
+
// デバッグ用: surface の CopySrc 可否 / 今フレームのテクスチャ / 提示統計
|
|
76
|
+
bool can_copy_src_ = false;
|
|
77
|
+
wgpu::Texture current_texture_ = nullptr;
|
|
78
|
+
uint64_t present_count_ = 0;
|
|
79
|
+
uint64_t present_skipped_ = 0;
|
|
62
80
|
};
|
|
63
81
|
|
|
64
82
|
} // namespace next2d
|
|
@@ -4,8 +4,11 @@
|
|
|
4
4
|
// bootstrap.js とアプリ本体(ESM)の読み込み -> ゲームループ、という流れ。
|
|
5
5
|
#include <Windows.h>
|
|
6
6
|
#include <windowsx.h>
|
|
7
|
+
#include <timeapi.h>
|
|
7
8
|
#include <XGameRuntime.h>
|
|
8
9
|
|
|
10
|
+
#pragma comment(lib, "winmm.lib")
|
|
11
|
+
|
|
9
12
|
#include "HostContext.h"
|
|
10
13
|
#include "AssetLoader.h"
|
|
11
14
|
#include "EventLoop.h"
|
|
@@ -190,6 +193,18 @@ HWND CreateHostWindow(HINSTANCE instance, int width, int height)
|
|
|
190
193
|
wc.lpszClassName = L"Next2DXboxHostWindow";
|
|
191
194
|
RegisterClassExW(&wc);
|
|
192
195
|
|
|
196
|
+
// ウィンドウが画面(作業領域)より大きいと画面外にはみ出し、提示が
|
|
197
|
+
// 見切れる/非等倍でスケールされて歪む (CI の低解像度ディスプレイで
|
|
198
|
+
// 1920x1080 ウィンドウ → 描画が縦長になっていた)。作業領域に収まるよう
|
|
199
|
+
// クランプする。実機コンソールでは作業領域=全画面のため 1920x1080 のまま。
|
|
200
|
+
RECT wa = {};
|
|
201
|
+
if (SystemParametersInfoW(SPI_GETWORKAREA, 0, &wa, 0)) {
|
|
202
|
+
const int maxW = wa.right - wa.left;
|
|
203
|
+
const int maxH = wa.bottom - wa.top;
|
|
204
|
+
if (maxW > 0 && width > maxW) { width = maxW; }
|
|
205
|
+
if (maxH > 0 && height > maxH) { height = maxH; }
|
|
206
|
+
}
|
|
207
|
+
|
|
193
208
|
// コンソールでは全画面相当。PC(GDK) 検証ではウィンドウ。
|
|
194
209
|
const DWORD style = WS_OVERLAPPEDWINDOW | WS_VISIBLE;
|
|
195
210
|
return CreateWindowExW(
|
|
@@ -202,14 +217,27 @@ HWND CreateHostWindow(HINSTANCE instance, int width, int height)
|
|
|
202
217
|
|
|
203
218
|
int WINAPI wWinMain(HINSTANCE instance, HINSTANCE, PWSTR cmd_line, int)
|
|
204
219
|
{
|
|
220
|
+
// Sleep() の分解能を 1ms に上げる (フレームペーシング用。既定 15.6ms では
|
|
221
|
+
// 60Hz を刻めない)
|
|
222
|
+
timeBeginPeriod(1);
|
|
223
|
+
|
|
205
224
|
// --selftest: アプリの代わりに js/selftest.js を実行し、全バインディングを
|
|
206
225
|
// 実機/PC(GDK) 上で検証して終了する (テスト完了で自動終了)。
|
|
207
226
|
const bool selftest = cmd_line && wcsstr(cmd_line, L"--selftest") != nullptr;
|
|
208
227
|
|
|
209
|
-
// 1. GDK
|
|
210
|
-
|
|
228
|
+
// 1. GDK ランタイム初期化。
|
|
229
|
+
// デスクトップでは Gaming Services 未導入の環境 (CI ランナー等) でも
|
|
230
|
+
// 起動できるよう、失敗を警告に留めて続行する (本ホストは XUser 等を未使用)。
|
|
231
|
+
// コンソールでは必須のため失敗したら終了する。
|
|
232
|
+
bool xgame_initialized = SUCCEEDED(XGameRuntimeInitialize());
|
|
233
|
+
if (!xgame_initialized) {
|
|
234
|
+
#if NEXT2D_XBOX_CONSOLE
|
|
211
235
|
std::cerr << "XGameRuntimeInitialize failed" << std::endl;
|
|
212
236
|
return 1;
|
|
237
|
+
#else
|
|
238
|
+
std::cerr << "warning: XGameRuntimeInitialize failed (Gaming Services not installed?)"
|
|
239
|
+
<< " - continuing on desktop" << std::endl;
|
|
240
|
+
#endif
|
|
213
241
|
}
|
|
214
242
|
|
|
215
243
|
const fs::path exe_dir = ExeDir();
|
|
@@ -230,6 +258,22 @@ int WINAPI wWinMain(HINSTANCE instance, HINSTANCE, PWSTR cmd_line, int)
|
|
|
230
258
|
return 1;
|
|
231
259
|
}
|
|
232
260
|
|
|
261
|
+
// ウィンドウは要求サイズより小さく作られ得る (作業領域クランプ / DPI /
|
|
262
|
+
// CI の低解像度ディスプレイ)。surface・canvas・screen は全て viewport_* から
|
|
263
|
+
// 派生するため、ここで「実クライアント矩形」を採用して一致させる。これを
|
|
264
|
+
// 怠ると surface(1920x1080) を実ウィンドウ(例 1024x768)へ非等倍提示して
|
|
265
|
+
// 画面が縦長に歪む。V8 バインディング設置(screen/innerWidth/dpr)と
|
|
266
|
+
// Dawn 初期化の前に確定させる必要がある。
|
|
267
|
+
{
|
|
268
|
+
RECT rc = {};
|
|
269
|
+
if (GetClientRect(hwnd, &rc) && rc.right > 0 && rc.bottom > 0) {
|
|
270
|
+
host.viewport_width = rc.right;
|
|
271
|
+
host.viewport_height = rc.bottom;
|
|
272
|
+
}
|
|
273
|
+
std::cerr << "[Win] client rect: " << host.viewport_width
|
|
274
|
+
<< "x" << host.viewport_height << std::endl;
|
|
275
|
+
}
|
|
276
|
+
|
|
233
277
|
// 4. V8 プロセス初期化 + Isolate/Context + バインディング
|
|
234
278
|
V8Runtime::InitializeProcess(nullptr);
|
|
235
279
|
V8Runtime runtime;
|
|
@@ -255,7 +299,11 @@ int WINAPI wWinMain(HINSTANCE instance, HINSTANCE, PWSTR cmd_line, int)
|
|
|
255
299
|
host.workers = &workers;
|
|
256
300
|
g_dawn = &dawn;
|
|
257
301
|
|
|
258
|
-
|
|
302
|
+
// GPU 無し環境 (CI 等) では Dawn 初期化に失敗し得るが、CPU 側の機能と
|
|
303
|
+
// selftest は動かせるため続行する (Present 等は内部でゲートされる)
|
|
304
|
+
if (!dawn.Initialize(hwnd, host.viewport_width, host.viewport_height)) {
|
|
305
|
+
std::cerr << "warning: Dawn initialization failed - continuing without GPU" << std::endl;
|
|
306
|
+
}
|
|
259
307
|
gamepad.Initialize();
|
|
260
308
|
audio.Initialize();
|
|
261
309
|
|
|
@@ -300,9 +348,12 @@ int WINAPI wWinMain(HINSTANCE instance, HINSTANCE, PWSTR cmd_line, int)
|
|
|
300
348
|
}
|
|
301
349
|
|
|
302
350
|
// 7. ゲームループ
|
|
303
|
-
v8::Isolate::Scope isolate_scope(runtime.isolate());
|
|
304
351
|
MSG msg = {};
|
|
305
352
|
int exit_code = 0;
|
|
353
|
+
// Isolate::Scope はループ内に限定する。Enter されたままの isolate を
|
|
354
|
+
// Dispose すると V8 の fatal (Disposing the isolate that is entered) になる。
|
|
355
|
+
{
|
|
356
|
+
v8::Isolate::Scope isolate_scope(runtime.isolate());
|
|
306
357
|
while (g_running) {
|
|
307
358
|
// Win32 メッセージ
|
|
308
359
|
while (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) {
|
|
@@ -347,6 +398,27 @@ int WINAPI wWinMain(HINSTANCE instance, HINSTANCE, PWSTR cmd_line, int)
|
|
|
347
398
|
// 提示
|
|
348
399
|
dawn.Present();
|
|
349
400
|
|
|
401
|
+
// フレームペーシング: ブラウザの rAF 同様 ~60Hz に揃える。
|
|
402
|
+
// Present はスキップ時にブロックしないため、これが無いとループが
|
|
403
|
+
// 数万回転/秒で空回りし、rAF 前提のゲーム/Tween のタイミングが崩れる。
|
|
404
|
+
{
|
|
405
|
+
const double frame_ms = event_loop.Now() - now;
|
|
406
|
+
if (frame_ms < 15.0) {
|
|
407
|
+
Sleep(static_cast<DWORD>(15.0 - frame_ms));
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// 診断: ループ統計 (5 秒毎)
|
|
412
|
+
{
|
|
413
|
+
static uint64_t iteration = 0;
|
|
414
|
+
++iteration;
|
|
415
|
+
if (iteration % 300 == 0) {
|
|
416
|
+
std::cerr << "[Loop] it=" << iteration
|
|
417
|
+
<< " rAF=" << event_loop.PendingAnimationFrameCount()
|
|
418
|
+
<< " timers=" << event_loop.PendingTimerCount() << std::endl;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
350
422
|
// selftest 完了検知: selftest.js が globalThis.__selftestExitCode を設定したら終了
|
|
351
423
|
if (selftest) {
|
|
352
424
|
v8::Local<v8::Value> code;
|
|
@@ -359,10 +431,20 @@ int WINAPI wWinMain(HINSTANCE instance, HINSTANCE, PWSTR cmd_line, int)
|
|
|
359
431
|
}
|
|
360
432
|
}
|
|
361
433
|
|
|
362
|
-
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// 8. 後始末: v8::Global を保持するものは Isolate 破棄前に必ず明示解放する。
|
|
437
|
+
// (スタック変数はスコープ終了 = runtime.Dispose() の後に巻き戻されるため、
|
|
438
|
+
// デストラクタ任せにすると破棄済み Isolate への Global::Reset で fail-fast する)
|
|
439
|
+
workers.Shutdown(); // WorkerInstance の Global<Context> / EventLoop
|
|
440
|
+
event_loop.Shutdown(); // main の setTimeout/rAF コールバック (Global<Function>)
|
|
441
|
+
ShutdownAudioEvents();
|
|
442
|
+
ShutdownWebGPU();
|
|
363
443
|
host.main_canvas.Reset();
|
|
364
444
|
runtime.Dispose();
|
|
365
445
|
V8Runtime::ShutdownProcess();
|
|
366
|
-
|
|
446
|
+
if (xgame_initialized) {
|
|
447
|
+
XGameRuntimeUninitialize();
|
|
448
|
+
}
|
|
367
449
|
return exit_code;
|
|
368
450
|
}
|
|
@@ -41,6 +41,10 @@ AudioVoice::~AudioVoice()
|
|
|
41
41
|
|
|
42
42
|
void AudioVoice::Start(bool loop)
|
|
43
43
|
{
|
|
44
|
+
// 音声デバイスが無い環境 (CI 等) でも「再生開始→即終了」として扱えるよう、
|
|
45
|
+
// voice_ の有無に関わらず開始状態は記録する (IsFinished が ended 発火を導く)。
|
|
46
|
+
started_ = true;
|
|
47
|
+
looping_ = loop;
|
|
44
48
|
if (!voice_) {
|
|
45
49
|
return;
|
|
46
50
|
}
|
|
@@ -51,8 +55,6 @@ void AudioVoice::Start(bool loop)
|
|
|
51
55
|
buf.LoopCount = loop ? XAUDIO2_LOOP_INFINITE : 0;
|
|
52
56
|
voice_->SubmitSourceBuffer(&buf);
|
|
53
57
|
voice_->Start(0);
|
|
54
|
-
started_ = true;
|
|
55
|
-
looping_ = loop;
|
|
56
58
|
}
|
|
57
59
|
|
|
58
60
|
void AudioVoice::Stop()
|
|
@@ -66,9 +68,13 @@ void AudioVoice::Stop()
|
|
|
66
68
|
|
|
67
69
|
bool AudioVoice::IsFinished() const
|
|
68
70
|
{
|
|
69
|
-
if (!
|
|
71
|
+
if (!started_ || looping_) {
|
|
70
72
|
return false;
|
|
71
73
|
}
|
|
74
|
+
if (!voice_) {
|
|
75
|
+
// 実ボイスが無い (音声デバイス無し) 場合は開始と同時に終了扱い。
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
72
78
|
XAUDIO2_VOICE_STATE state = {};
|
|
73
79
|
voice_->GetState(&state);
|
|
74
80
|
return state.BuffersQueued == 0;
|
|
@@ -43,15 +43,26 @@ V8Runtime::~V8Runtime()
|
|
|
43
43
|
void V8Runtime::InitializeProcess(const char* exec_path)
|
|
44
44
|
{
|
|
45
45
|
// Xbox(GDK) / Nintendo Switch のリテール環境は動的コード生成(JIT)を禁止する。
|
|
46
|
-
//
|
|
47
|
-
// WebAssembly
|
|
48
|
-
|
|
46
|
+
// - JS: --jitless (Ignition インタプリタのみ・RWX ページ無し)
|
|
47
|
+
// - WebAssembly: --wasm-jitless (DrumBrake = V8 の wasm インタープリタ。
|
|
48
|
+
// prebuilt V8 r2 は v8_enable_drumbrake=true でビルドされている)
|
|
49
|
+
v8::V8::SetFlagsFromString("--jitless --wasm-jitless");
|
|
49
50
|
|
|
50
51
|
v8::V8::InitializeICUDefaultLocation(exec_path);
|
|
51
52
|
v8::V8::InitializeExternalStartupData(exec_path);
|
|
52
53
|
platform_ = v8::platform::NewDefaultPlatform();
|
|
53
54
|
v8::V8::InitializePlatform(platform_.get());
|
|
54
55
|
v8::V8::Initialize();
|
|
56
|
+
|
|
57
|
+
// WebAssembly (DrumBrake) はメモリ境界検査を guard page + trap handler で行うため、
|
|
58
|
+
// embedder が trap handler を有効化する必要がある (d8/Chrome も起動時に行う)。
|
|
59
|
+
// 未設定のまま wasm を実行するとインタープリタ初期化で fail-fast する。
|
|
60
|
+
// コンソール実機で VEH が使えない場合は、V8 を v8_drumbrake_bounds_checks=true
|
|
61
|
+
// (明示境界チェック) でビルドし直すこと。
|
|
62
|
+
if (!v8::V8::EnableWebAssemblyTrapHandler(true)) {
|
|
63
|
+
std::cerr << "warning: EnableWebAssemblyTrapHandler failed"
|
|
64
|
+
<< " - WebAssembly may not work" << std::endl;
|
|
65
|
+
}
|
|
55
66
|
}
|
|
56
67
|
|
|
57
68
|
void V8Runtime::ShutdownProcess()
|
|
@@ -75,6 +86,35 @@ bool V8Runtime::Initialize(HostContext* host)
|
|
|
75
86
|
// 例外時に未処理を潰さないよう、明示的に MicrotasksPolicy を制御する
|
|
76
87
|
isolate_->SetMicrotasksPolicy(v8::MicrotasksPolicy::kExplicit);
|
|
77
88
|
|
|
89
|
+
// 未処理の Promise 拒否を stderr へ報告する。framework の boot は async のため、
|
|
90
|
+
// この報告が無いと例外がどこにも出ずゲームが無音で停止する (実際に発生した)。
|
|
91
|
+
isolate_->SetPromiseRejectCallback([](v8::PromiseRejectMessage message) {
|
|
92
|
+
if (message.GetEvent() != v8::kPromiseRejectWithNoHandler) {
|
|
93
|
+
return; // 後からハンドラが付く分などは報告しない
|
|
94
|
+
}
|
|
95
|
+
v8::Isolate* isolate = v8::Isolate::GetCurrent();
|
|
96
|
+
v8::HandleScope hs(isolate);
|
|
97
|
+
v8::Local<v8::Value> value = message.GetValue();
|
|
98
|
+
std::string text = "(no value)";
|
|
99
|
+
std::string stack;
|
|
100
|
+
if (!value.IsEmpty()) {
|
|
101
|
+
text = v8util::ToStdString(isolate, value);
|
|
102
|
+
v8::Local<v8::Context> ctx = isolate->GetCurrentContext();
|
|
103
|
+
if (!ctx.IsEmpty() && value->IsObject()) {
|
|
104
|
+
v8::Local<v8::Value> s;
|
|
105
|
+
if (value.As<v8::Object>()
|
|
106
|
+
->Get(ctx, v8util::Str(isolate, "stack")).ToLocal(&s) &&
|
|
107
|
+
s->IsString()) {
|
|
108
|
+
stack = v8util::ToStdString(isolate, s);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
std::cerr << "[V8] Unhandled promise rejection: " << text << std::endl;
|
|
113
|
+
if (!stack.empty() && stack != text) {
|
|
114
|
+
std::cerr << stack << std::endl;
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
|
|
78
118
|
v8::Isolate::Scope isolate_scope(isolate_);
|
|
79
119
|
v8::HandleScope handle_scope(isolate_);
|
|
80
120
|
|
|
@@ -148,12 +188,37 @@ v8::MaybeLocal<v8::Module> V8Runtime::LoadModule(const std::string& path,
|
|
|
148
188
|
return cached->second.Get(isolate_);
|
|
149
189
|
}
|
|
150
190
|
|
|
151
|
-
|
|
191
|
+
std::string source = ReadTextFile(abs);
|
|
152
192
|
if (source.empty() && !fs::exists(abs)) {
|
|
153
193
|
v8util::ThrowTypeError(isolate_, "Module not found: " + abs);
|
|
154
194
|
return v8::MaybeLocal<v8::Module>();
|
|
155
195
|
}
|
|
156
196
|
|
|
197
|
+
// «診断» Tween(Job) の凍結調査: app.js の Job 更新コードへトレースを注入する。
|
|
198
|
+
// rAF が 2 本 (ticker + 1 Job) のまま描画が止まる現象の currentTime/duration の
|
|
199
|
+
// 実値を観測する。パターン不一致なら無変更 (ログのみ)。
|
|
200
|
+
if (source.find("(e.currentTime=(t-e.startTime)/1e3,") != std::string::npos) {
|
|
201
|
+
const auto patch = [&source](const std::string& from, const std::string& to) {
|
|
202
|
+
const auto pos = source.find(from);
|
|
203
|
+
if (pos != std::string::npos) {
|
|
204
|
+
source.replace(pos, from.size(), to);
|
|
205
|
+
} else {
|
|
206
|
+
std::cerr << "[V8] job trace patch not applied: "
|
|
207
|
+
<< from.substr(0, 40) << std::endl;
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
patch("(e.currentTime=(t-e.startTime)/1e3,",
|
|
211
|
+
"(e.currentTime=(t-e.startTime)/1e3,"
|
|
212
|
+
"((globalThis.__jt=(globalThis.__jt||0)+1)<=60||globalThis.__jt%600===0)&&"
|
|
213
|
+
"console.info(\"[jobtrace] #\"+globalThis.__jt,"
|
|
214
|
+
"\"ct=\"+e.currentTime.toFixed(3),\"dur=\"+e.duration,"
|
|
215
|
+
"\"t=\"+t.toFixed(1),\"st=\"+e.startTime.toFixed(1)),");
|
|
216
|
+
patch("e.currentTime>=e.duration?(",
|
|
217
|
+
"e.currentTime>=e.duration?("
|
|
218
|
+
"console.info(\"[jobtrace] COMPLETE dur=\"+e.duration),");
|
|
219
|
+
std::cerr << "[V8] job trace patch applied to " << abs << std::endl;
|
|
220
|
+
}
|
|
221
|
+
|
|
157
222
|
v8::Local<v8::String> src = v8util::Str(isolate_, source);
|
|
158
223
|
|
|
159
224
|
v8::ScriptOrigin origin(
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
#pragma once
|
|
3
3
|
|
|
4
4
|
#include <v8.h>
|
|
5
|
+
#include <iostream>
|
|
5
6
|
#include <string>
|
|
6
7
|
#include <string_view>
|
|
7
8
|
|
|
@@ -53,4 +54,32 @@ inline void ThrowTypeError(v8::Isolate* isolate, std::string_view message)
|
|
|
53
54
|
isolate->ThrowException(v8::Exception::TypeError(Str(isolate, message)));
|
|
54
55
|
}
|
|
55
56
|
|
|
57
|
+
// TryCatch が例外を保持していれば stderr へ報告する。
|
|
58
|
+
// rAF / タイマー / onmessage / イベントリスナーのコールバックは TryCatch で
|
|
59
|
+
// 保護されるが、報告しないと例外が無音で消えて原因究明が不可能になる
|
|
60
|
+
// (レンダラ worker が毎フレーム throw していても黒画面にしか見えない)。
|
|
61
|
+
inline void ReportCaught(v8::Isolate* isolate, v8::TryCatch* tc, const char* where)
|
|
62
|
+
{
|
|
63
|
+
if (!tc->HasCaught()) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
v8::HandleScope hs(isolate);
|
|
67
|
+
v8::Local<v8::Context> ctx = isolate->GetCurrentContext();
|
|
68
|
+
const std::string text = ToStdString(isolate, tc->Exception());
|
|
69
|
+
std::cerr << "[JS] exception in " << where << ": " << text << std::endl;
|
|
70
|
+
v8::Local<v8::Value> stack;
|
|
71
|
+
if (!ctx.IsEmpty() && tc->StackTrace(ctx).ToLocal(&stack)) {
|
|
72
|
+
const std::string s = ToStdString(isolate, stack);
|
|
73
|
+
if (!s.empty() && s != text) {
|
|
74
|
+
std::cerr << s << std::endl;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
v8::Local<v8::Message> message = tc->Message();
|
|
78
|
+
if (!message.IsEmpty() && !ctx.IsEmpty()) {
|
|
79
|
+
std::cerr << " at "
|
|
80
|
+
<< ToStdString(isolate, message->GetScriptOrigin().ResourceName())
|
|
81
|
+
<< ":" << message->GetLineNumber(ctx).FromMaybe(0) << std::endl;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
56
85
|
} // namespace next2d::v8util
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// SetWeak (GC 連動の解放) の正しい運用ヘルパー。
|
|
2
|
+
//
|
|
3
|
+
// V8 の規約では、第一パスの weak callback 内で対応する v8::Global を必ず
|
|
4
|
+
// Reset しなければならない。違反すると GC 時に fatal
|
|
5
|
+
// 「Check failed: Handle not reset in first callback.」で落ちる。
|
|
6
|
+
// payload と Global を束ねて所有し、GC で Reset + delete の両方を行う。
|
|
7
|
+
#pragma once
|
|
8
|
+
|
|
9
|
+
#include <v8.h>
|
|
10
|
+
|
|
11
|
+
namespace next2d::v8util {
|
|
12
|
+
|
|
13
|
+
template <typename T>
|
|
14
|
+
class WeakHandle {
|
|
15
|
+
public:
|
|
16
|
+
static void Attach(v8::Isolate* isolate, v8::Local<v8::Object> obj, T* payload)
|
|
17
|
+
{
|
|
18
|
+
auto* w = new WeakHandle<T>(payload);
|
|
19
|
+
w->handle_.Reset(isolate, obj);
|
|
20
|
+
w->handle_.SetWeak(w, &WeakHandle::OnGC, v8::WeakCallbackType::kParameter);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
private:
|
|
24
|
+
explicit WeakHandle(T* payload) : payload_(payload) {}
|
|
25
|
+
|
|
26
|
+
static void OnGC(const v8::WeakCallbackInfo<WeakHandle<T>>& info)
|
|
27
|
+
{
|
|
28
|
+
WeakHandle<T>* w = info.GetParameter();
|
|
29
|
+
w->handle_.Reset(); // 第一パスでの Reset (V8 の必須要件)
|
|
30
|
+
delete w->payload_;
|
|
31
|
+
delete w;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
v8::Global<v8::Object> handle_;
|
|
35
|
+
T* payload_;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// オブジェクトが GC されたときに `delete payload` する弱参照を張る。
|
|
39
|
+
template <typename T>
|
|
40
|
+
inline void AttachWeak(v8::Isolate* isolate, v8::Local<v8::Object> obj, T* payload)
|
|
41
|
+
{
|
|
42
|
+
WeakHandle<T>::Attach(isolate, obj, payload);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
} // namespace next2d::v8util
|