@next2d/builder 0.1.7 → 0.1.10
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/dist/cli.d.ts +59 -0
- package/dist/cli.js +141 -0
- package/dist/constants.d.ts +53 -0
- package/dist/constants.js +64 -0
- package/dist/context.d.ts +29 -0
- package/dist/context.js +19 -0
- package/dist/electron.d.ts +10 -0
- package/dist/electron.js +142 -0
- package/dist/index.js +18 -754
- package/dist/native.d.ts +27 -0
- package/dist/native.js +123 -0
- package/dist/utils.d.ts +27 -0
- package/dist/utils.js +40 -0
- package/dist/web.d.ts +18 -0
- package/dist/web.js +70 -0
- package/dist/xbox.d.ts +62 -0
- package/dist/xbox.js +426 -0
- package/package.json +12 -9
- package/templates/xbox/CMakeLists.txt +116 -11
- package/templates/xbox/README.md +34 -8
- package/templates/xbox/cmake/FetchDawn.cmake +47 -6
- package/templates/xbox/cmake/FindV8.cmake +15 -0
- package/templates/xbox/js/bootstrap.js +151 -28
- package/templates/xbox/js/selftest.js +740 -90
- package/templates/xbox/src/AssetLoader.cpp +115 -12
- package/templates/xbox/src/AssetLoader.h +4 -0
- package/templates/xbox/src/EmbeddedAssets.cpp +149 -0
- package/templates/xbox/src/EmbeddedAssets.h +60 -0
- package/templates/xbox/src/EventLoop.cpp +13 -1
- package/templates/xbox/src/EventLoop.h +12 -0
- package/templates/xbox/src/bindings/Audio.cpp +211 -31
- package/templates/xbox/src/bindings/Bindings.h +10 -0
- package/templates/xbox/src/bindings/Canvas.cpp +77 -2
- package/templates/xbox/src/bindings/Canvas2D.cpp +496 -43
- package/templates/xbox/src/bindings/Console.cpp +6 -1
- package/templates/xbox/src/bindings/DomShims.cpp +143 -0
- package/templates/xbox/src/bindings/EventTarget.cpp +11 -0
- package/templates/xbox/src/bindings/Fetch.cpp +2 -8
- package/templates/xbox/src/bindings/Image.cpp +243 -15
- package/templates/xbox/src/bindings/Polyfills.cpp +1226 -0
- package/templates/xbox/src/bindings/RasterCore.h +105 -1
- package/templates/xbox/src/bindings/Video.cpp +153 -10
- package/templates/xbox/src/bindings/webgpu/WebGPU.cpp +844 -18
- package/templates/xbox/src/bindings/webgpu/WebGPUCommon.h +7 -9
- package/templates/xbox/src/bindings/webgpu/WebGPUEnums.h +32 -0
- package/templates/xbox/src/gpu/DawnContext.cpp +245 -7
- package/templates/xbox/src/gpu/DawnContext.h +18 -0
- package/templates/xbox/src/main.cpp +459 -8
- package/templates/xbox/src/platform/AudioDecoder.cpp +121 -0
- package/templates/xbox/src/platform/AudioDecoder.h +18 -0
- package/templates/xbox/src/platform/AudioEngine.cpp +37 -6
- package/templates/xbox/src/platform/DecodeQueue.cpp +136 -0
- package/templates/xbox/src/platform/DecodeQueue.h +27 -0
- package/templates/xbox/src/platform/ImageDecoder.cpp +44 -0
- package/templates/xbox/src/platform/ImageDecoder.h +18 -0
- package/templates/xbox/src/platform/StbTextRasterizer.cpp +324 -0
- package/templates/xbox/src/platform/StbTextRasterizer.h +37 -0
- package/templates/xbox/src/platform/TextRasterizer.cpp +32 -0
- package/templates/xbox/src/platform/WicDecoder.cpp +19 -0
- package/templates/xbox/src/v8/CodeCache.h +223 -0
- package/templates/xbox/src/v8/V8Runtime.cpp +76 -11
- package/templates/xbox/src/v8/V8Util.h +79 -0
- package/templates/xbox/src/v8/WeakHandle.h +45 -0
- package/templates/xbox/src/worker/WorkerRuntime.cpp +432 -48
- package/templates/xbox/src/worker/WorkerRuntime.h +43 -13
- package/templates/xbox/tests/check_local.sh +7 -2
- package/templates/xbox/tests/gamecore_api_probe.cpp +172 -0
- package/templates/xbox/tests/raster_test.cpp +50 -0
- package/templates/xbox/tests/windows_platform_test.cpp +75 -3
- package/templates/xbox/third_party/LICENSES.md +14 -0
- package/templates/xbox/third_party/dr_mp3.h +5412 -0
- package/templates/xbox/third_party/dr_wav.h +9131 -0
- package/templates/xbox/third_party/stb_image.h +7988 -0
- package/templates/xbox/third_party/stb_truetype.h +5079 -0
- package/templates/xbox/third_party/stb_vorbis.c +5584 -0
|
@@ -6,23 +6,45 @@
|
|
|
6
6
|
#include <windowsx.h>
|
|
7
7
|
#include <XGameRuntime.h>
|
|
8
8
|
|
|
9
|
+
// winmm (timeBeginPeriod) と DbgHelp (StackWalk64) はコンソール (Game Core OS) に
|
|
10
|
+
// 存在しないデスクトップ専用 API。コンソールは全画面 VSync 固定でペーシング不要、
|
|
11
|
+
// クラッシュ解析はプラットフォーム側の仕組みに任せる。
|
|
12
|
+
#if !NEXT2D_XBOX_CONSOLE
|
|
13
|
+
#include <timeapi.h>
|
|
14
|
+
#include <DbgHelp.h>
|
|
15
|
+
#pragma comment(lib, "winmm.lib")
|
|
16
|
+
#endif
|
|
17
|
+
|
|
9
18
|
#include "HostContext.h"
|
|
10
19
|
#include "AssetLoader.h"
|
|
20
|
+
#include "EmbeddedAssets.h"
|
|
11
21
|
#include "EventLoop.h"
|
|
12
22
|
#include "gpu/DawnContext.h"
|
|
23
|
+
#include "platform/DecodeQueue.h"
|
|
13
24
|
#include "platform/GamepadManager.h"
|
|
14
25
|
#include "platform/AudioEngine.h"
|
|
26
|
+
#include "platform/StbTextRasterizer.h"
|
|
15
27
|
#include "worker/WorkerRuntime.h"
|
|
16
28
|
#include "bindings/Bindings.h"
|
|
17
29
|
#include "bindings/EventTarget.h"
|
|
18
30
|
#include "v8/V8Runtime.h"
|
|
19
31
|
|
|
32
|
+
#include "v8/V8Util.h"
|
|
33
|
+
|
|
34
|
+
#include <v8-profiler.h>
|
|
35
|
+
|
|
36
|
+
#include <algorithm>
|
|
20
37
|
#include <filesystem>
|
|
21
38
|
#include <fstream>
|
|
22
39
|
#include <functional>
|
|
23
40
|
#include <iostream>
|
|
41
|
+
#include <map>
|
|
24
42
|
#include <sstream>
|
|
25
43
|
#include <string>
|
|
44
|
+
#include <utility>
|
|
45
|
+
#include <vector>
|
|
46
|
+
#include <cstdio>
|
|
47
|
+
#include <cstring>
|
|
26
48
|
|
|
27
49
|
namespace fs = std::filesystem;
|
|
28
50
|
using namespace next2d;
|
|
@@ -30,6 +52,137 @@ using namespace next2d;
|
|
|
30
52
|
namespace {
|
|
31
53
|
|
|
32
54
|
bool g_running = true;
|
|
55
|
+
|
|
56
|
+
// --- --perf: フレーム統計 ---------------------------------------------------
|
|
57
|
+
// メインループの各区間の所要時間 (ms) を集計し、300 フレーム毎に 1 行で出力する。
|
|
58
|
+
// avg でボトルネックの区間を、max でヒッチ (スパイク) の在り処を特定する。
|
|
59
|
+
// GUI 実行では stderr が見えないため next2d-perf.log にも追記する。
|
|
60
|
+
struct PerfSection {
|
|
61
|
+
double sum = 0.0;
|
|
62
|
+
double max = 0.0;
|
|
63
|
+
void Add(double ms)
|
|
64
|
+
{
|
|
65
|
+
sum += ms;
|
|
66
|
+
if (ms > max) {
|
|
67
|
+
max = ms;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
struct PerfStats {
|
|
73
|
+
static constexpr int kWindow = 300; // 60fps で約 5 秒
|
|
74
|
+
PerfSection tasks; // 入力/音声/タイマー/マイクロタスク
|
|
75
|
+
PerfSection js; // メイン rAF (アプリロジック/描画コマンド生成)
|
|
76
|
+
PerfSection worker; // レンダラ worker (WebGPU コマンド発行 + submit)
|
|
77
|
+
PerfSection tick; // V8 プラットフォームタスク + Dawn Tick
|
|
78
|
+
PerfSection present; // Present (GPU/VSync 待ちを含む)
|
|
79
|
+
PerfSection busy; // 上記合計 (ペーシング Sleep を除く実働)
|
|
80
|
+
PerfSection frame; // フレーム間隔 (実効フレームレートの逆数)
|
|
81
|
+
int frames = 0;
|
|
82
|
+
|
|
83
|
+
void Flush()
|
|
84
|
+
{
|
|
85
|
+
if (frames == 0) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const auto avg = [this](const PerfSection& s) { return s.sum / frames; };
|
|
89
|
+
char line[320];
|
|
90
|
+
std::snprintf(line, sizeof(line),
|
|
91
|
+
"[perf] frame %.1fms (max %.1f) busy %.1fms | "
|
|
92
|
+
"tasks %.2f/%.1f js %.2f/%.1f worker %.2f/%.1f "
|
|
93
|
+
"tick %.2f/%.1f present %.2f/%.1f (avg/max, %d frames)",
|
|
94
|
+
avg(frame), frame.max, avg(busy),
|
|
95
|
+
avg(tasks), tasks.max, avg(js), js.max, avg(worker), worker.max,
|
|
96
|
+
avg(tick), tick.max, avg(present), present.max, frames);
|
|
97
|
+
std::cerr << line << std::endl;
|
|
98
|
+
std::ofstream ofs("next2d-perf.log", std::ios::app);
|
|
99
|
+
if (ofs) {
|
|
100
|
+
ofs << line << std::endl;
|
|
101
|
+
}
|
|
102
|
+
*this = PerfStats{};
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
// --profile: CPU プロファイル結果を self サンプル数の降順で next2d-cpuprofile.log へ。
|
|
107
|
+
// ノードツリーを再帰集計し「関数 (script:line)」毎の self 時間を出す。
|
|
108
|
+
constexpr unsigned kProfileIntervalUs = 500;
|
|
109
|
+
|
|
110
|
+
void DumpCpuProfileNode(const v8::CpuProfileNode* node,
|
|
111
|
+
std::map<std::string, unsigned>& self_hits)
|
|
112
|
+
{
|
|
113
|
+
const char* name = node->GetFunctionNameStr();
|
|
114
|
+
const char* script = node->GetScriptResourceNameStr();
|
|
115
|
+
std::string key = (name && *name) ? name : "(anonymous)";
|
|
116
|
+
if (script && *script) {
|
|
117
|
+
// pak 内キーの末尾だけで十分読める
|
|
118
|
+
std::string s(script);
|
|
119
|
+
const auto slash = s.find_last_of('/');
|
|
120
|
+
if (slash != std::string::npos) {
|
|
121
|
+
s = s.substr(slash + 1);
|
|
122
|
+
}
|
|
123
|
+
key += " (" + s + ":" + std::to_string(node->GetLineNumber()) + ")";
|
|
124
|
+
}
|
|
125
|
+
self_hits[key] += node->GetHitCount();
|
|
126
|
+
for (int i = 0; i < node->GetChildrenCount(); ++i) {
|
|
127
|
+
DumpCpuProfileNode(node->GetChild(i), self_hits);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
void WriteCpuProfile(v8::CpuProfile* profile, unsigned interval_us, const char* reason)
|
|
132
|
+
{
|
|
133
|
+
std::map<std::string, unsigned> self_hits;
|
|
134
|
+
DumpCpuProfileNode(profile->GetTopDownRoot(), self_hits);
|
|
135
|
+
|
|
136
|
+
unsigned total = 0;
|
|
137
|
+
for (const auto& [key, hits] : self_hits) {
|
|
138
|
+
total += hits;
|
|
139
|
+
}
|
|
140
|
+
std::vector<std::pair<std::string, unsigned>> sorted(self_hits.begin(), self_hits.end());
|
|
141
|
+
std::sort(sorted.begin(), sorted.end(),
|
|
142
|
+
[](const auto& a, const auto& b) { return a.second > b.second; });
|
|
143
|
+
|
|
144
|
+
std::ofstream ofs("next2d-cpuprofile.log", std::ios::app);
|
|
145
|
+
const double dur_ms =
|
|
146
|
+
static_cast<double>(profile->GetEndTime() - profile->GetStartTime()) / 1000.0;
|
|
147
|
+
char head[224];
|
|
148
|
+
std::snprintf(head, sizeof(head),
|
|
149
|
+
"[cpuprofile] %s: window %.1fs, %u samples (interval %uus), top self-time:",
|
|
150
|
+
reason, dur_ms / 1000.0, total, interval_us);
|
|
151
|
+
std::cerr << head << std::endl;
|
|
152
|
+
if (ofs) {
|
|
153
|
+
ofs << head << std::endl;
|
|
154
|
+
}
|
|
155
|
+
int rank = 0;
|
|
156
|
+
for (const auto& [key, hits] : sorted) {
|
|
157
|
+
if (hits == 0 || ++rank > 50) {
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
char line[512];
|
|
161
|
+
std::snprintf(line, sizeof(line), " %5.1f%% %8.1fms %s",
|
|
162
|
+
total ? 100.0 * hits / total : 0.0,
|
|
163
|
+
hits * (interval_us / 1000.0), key.c_str());
|
|
164
|
+
std::cerr << line << std::endl;
|
|
165
|
+
if (ofs) {
|
|
166
|
+
ofs << line << std::endl;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// 現在のプロファイル窓を書き出して新しい窓を開始する。
|
|
172
|
+
// 遷移スパイク (単発の長い rAF) 直後に呼ぶことで、スパイクを含む窓と含まない窓の
|
|
173
|
+
// 比較から犯人関数を分離する (セッション全体の集計では定常コストに埋もれるため)。
|
|
174
|
+
void DumpAndRestartProfile(v8::CpuProfiler* profiler, v8::Isolate* isolate,
|
|
175
|
+
const char* reason)
|
|
176
|
+
{
|
|
177
|
+
v8::HandleScope hs(isolate);
|
|
178
|
+
v8::CpuProfile* p = profiler->StopProfiling(
|
|
179
|
+
v8::String::NewFromUtf8Literal(isolate, "next2d"));
|
|
180
|
+
if (p) {
|
|
181
|
+
WriteCpuProfile(p, kProfileIntervalUs, reason);
|
|
182
|
+
p->Delete();
|
|
183
|
+
}
|
|
184
|
+
profiler->StartProfiling(v8::String::NewFromUtf8Literal(isolate, "next2d"));
|
|
185
|
+
}
|
|
33
186
|
DawnContext* g_dawn = nullptr;
|
|
34
187
|
HostContext* g_host = nullptr;
|
|
35
188
|
V8Runtime* g_runtime = nullptr;
|
|
@@ -190,6 +343,21 @@ HWND CreateHostWindow(HINSTANCE instance, int width, int height)
|
|
|
190
343
|
wc.lpszClassName = L"Next2DXboxHostWindow";
|
|
191
344
|
RegisterClassExW(&wc);
|
|
192
345
|
|
|
346
|
+
// ウィンドウが画面(作業領域)より大きいと画面外にはみ出し、提示が
|
|
347
|
+
// 見切れる/非等倍でスケールされて歪む (CI の低解像度ディスプレイで
|
|
348
|
+
// 1920x1080 ウィンドウ → 描画が縦長になっていた)。作業領域に収まるよう
|
|
349
|
+
// クランプする。コンソールは常に全画面のためクランプ不要
|
|
350
|
+
// (SystemParametersInfo は Game Core に無い)。
|
|
351
|
+
#if !NEXT2D_XBOX_CONSOLE
|
|
352
|
+
RECT wa = {};
|
|
353
|
+
if (SystemParametersInfoW(SPI_GETWORKAREA, 0, &wa, 0)) {
|
|
354
|
+
const int maxW = wa.right - wa.left;
|
|
355
|
+
const int maxH = wa.bottom - wa.top;
|
|
356
|
+
if (maxW > 0 && width > maxW) { width = maxW; }
|
|
357
|
+
if (maxH > 0 && height > maxH) { height = maxH; }
|
|
358
|
+
}
|
|
359
|
+
#endif
|
|
360
|
+
|
|
193
361
|
// コンソールでは全画面相当。PC(GDK) 検証ではウィンドウ。
|
|
194
362
|
const DWORD style = WS_OVERLAPPEDWINDOW | WS_VISIBLE;
|
|
195
363
|
return CreateWindowExW(
|
|
@@ -200,16 +368,133 @@ HWND CreateHostWindow(HINSTANCE instance, int width, int height)
|
|
|
200
368
|
|
|
201
369
|
} // namespace
|
|
202
370
|
|
|
371
|
+
// 未処理例外(segfault/AV 等)発生時にコールスタックを next2d-error.log へ書き出す。
|
|
372
|
+
// GUI 実行では stderr が見えないため、C++ クラッシュ地点を掴む唯一の手段。
|
|
373
|
+
// DbgHelp の StackWalk64 で例外コンテキストからフレームを辿り、可能なら
|
|
374
|
+
// シンボル名 (関数+行) とモジュールを添える (PDB があれば関数/行まで解決)。
|
|
375
|
+
// コンソールには DbgHelp が無いためデスクトップ専用 (クラッシュ解析は実機ツール側)。
|
|
376
|
+
#if !NEXT2D_XBOX_CONSOLE
|
|
377
|
+
static LONG WINAPI CrashHandler(EXCEPTION_POINTERS* ep)
|
|
378
|
+
{
|
|
379
|
+
std::ofstream ofs("next2d-error.log", std::ios::app);
|
|
380
|
+
if (!ofs || !ep || !ep->ExceptionRecord || !ep->ContextRecord) {
|
|
381
|
+
return EXCEPTION_EXECUTE_HANDLER;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
ofs << "\n[CRASH] code=0x" << std::hex
|
|
385
|
+
<< ep->ExceptionRecord->ExceptionCode
|
|
386
|
+
<< " addr=" << ep->ExceptionRecord->ExceptionAddress
|
|
387
|
+
<< std::dec << std::endl;
|
|
388
|
+
|
|
389
|
+
HANDLE process = GetCurrentProcess();
|
|
390
|
+
HANDLE thread = GetCurrentThread();
|
|
391
|
+
SymSetOptions(SYMOPT_UNDNAME | SYMOPT_DEFERRED_LOADS | SYMOPT_LOAD_LINES);
|
|
392
|
+
SymInitialize(process, nullptr, TRUE);
|
|
393
|
+
|
|
394
|
+
CONTEXT ctx = *ep->ContextRecord;
|
|
395
|
+
STACKFRAME64 frame = {};
|
|
396
|
+
DWORD machine = 0;
|
|
397
|
+
#if defined(_M_X64)
|
|
398
|
+
machine = IMAGE_FILE_MACHINE_AMD64;
|
|
399
|
+
frame.AddrPC.Offset = ctx.Rip;
|
|
400
|
+
frame.AddrFrame.Offset = ctx.Rbp;
|
|
401
|
+
frame.AddrStack.Offset = ctx.Rsp;
|
|
402
|
+
#elif defined(_M_ARM64)
|
|
403
|
+
machine = IMAGE_FILE_MACHINE_ARM64;
|
|
404
|
+
frame.AddrPC.Offset = ctx.Pc;
|
|
405
|
+
frame.AddrFrame.Offset = ctx.Fp;
|
|
406
|
+
frame.AddrStack.Offset = ctx.Sp;
|
|
407
|
+
#endif
|
|
408
|
+
frame.AddrPC.Mode = AddrModeFlat;
|
|
409
|
+
frame.AddrFrame.Mode = AddrModeFlat;
|
|
410
|
+
frame.AddrStack.Mode = AddrModeFlat;
|
|
411
|
+
|
|
412
|
+
char symbuf[sizeof(SYMBOL_INFO) + 256] = {};
|
|
413
|
+
SYMBOL_INFO* sym = reinterpret_cast<SYMBOL_INFO*>(symbuf);
|
|
414
|
+
sym->SizeOfStruct = sizeof(SYMBOL_INFO);
|
|
415
|
+
sym->MaxNameLen = 255;
|
|
416
|
+
|
|
417
|
+
for (int i = 0; i < 48; ++i) {
|
|
418
|
+
if (!StackWalk64(machine, process, thread, &frame, &ctx,
|
|
419
|
+
nullptr, SymFunctionTableAccess64, SymGetModuleBase64, nullptr)) {
|
|
420
|
+
break;
|
|
421
|
+
}
|
|
422
|
+
const DWORD64 pc = frame.AddrPC.Offset;
|
|
423
|
+
if (!pc) {
|
|
424
|
+
break;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
ofs << " #" << i << " 0x" << std::hex << pc << std::dec;
|
|
428
|
+
|
|
429
|
+
DWORD64 disp = 0;
|
|
430
|
+
if (SymFromAddr(process, pc, &disp, sym)) {
|
|
431
|
+
ofs << " " << sym->Name << "+0x" << std::hex << disp << std::dec;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
const DWORD64 modbase = SymGetModuleBase64(process, pc);
|
|
435
|
+
if (modbase) {
|
|
436
|
+
char modname[MAX_PATH] = {};
|
|
437
|
+
if (GetModuleFileNameA(reinterpret_cast<HMODULE>(modbase), modname, MAX_PATH)) {
|
|
438
|
+
const char* base = std::strrchr(modname, '\\');
|
|
439
|
+
ofs << " [" << (base ? base + 1 : modname) << "]";
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
IMAGEHLP_LINE64 line = {};
|
|
444
|
+
line.SizeOfStruct = sizeof(IMAGEHLP_LINE64);
|
|
445
|
+
DWORD line_disp = 0;
|
|
446
|
+
if (SymGetLineFromAddr64(process, pc, &line_disp, &line) && line.FileName) {
|
|
447
|
+
const char* fbase = std::strrchr(line.FileName, '\\');
|
|
448
|
+
ofs << " (" << (fbase ? fbase + 1 : line.FileName) << ":" << line.LineNumber << ")";
|
|
449
|
+
}
|
|
450
|
+
ofs << std::endl;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
ofs.flush();
|
|
454
|
+
SymCleanup(process);
|
|
455
|
+
return EXCEPTION_EXECUTE_HANDLER; // プロセスを終了させる (無限再入回避)
|
|
456
|
+
}
|
|
457
|
+
#endif // !NEXT2D_XBOX_CONSOLE
|
|
458
|
+
|
|
203
459
|
int WINAPI wWinMain(HINSTANCE instance, HINSTANCE, PWSTR cmd_line, int)
|
|
204
460
|
{
|
|
461
|
+
#if !NEXT2D_XBOX_CONSOLE
|
|
462
|
+
// 最初にクラッシュハンドラを設置し、以降の segfault 等でスタックを記録する。
|
|
463
|
+
SetUnhandledExceptionFilter(CrashHandler);
|
|
464
|
+
|
|
465
|
+
// Sleep() の分解能を 1ms に上げる (フレームペーシング用。既定 15.6ms では
|
|
466
|
+
// 60Hz を刻めない)。コンソールは全画面 VSync 提示でペーシングされるため不要。
|
|
467
|
+
timeBeginPeriod(1);
|
|
468
|
+
#endif
|
|
469
|
+
|
|
205
470
|
// --selftest: アプリの代わりに js/selftest.js を実行し、全バインディングを
|
|
206
471
|
// 実機/PC(GDK) 上で検証して終了する (テスト完了で自動終了)。
|
|
207
472
|
const bool selftest = cmd_line && wcsstr(cmd_line, L"--selftest") != nullptr;
|
|
208
473
|
|
|
209
|
-
//
|
|
210
|
-
|
|
474
|
+
// --perf: フレーム統計を 300 フレーム (約 5 秒) 毎に stderr と next2d-perf.log
|
|
475
|
+
// へ出力する。どの区間 (JS / レンダラ worker / GPU / Present) にフレーム時間を
|
|
476
|
+
// 使っているかを数値化し、以降の最適化 (worker 並列化等) の要否判断に使う。
|
|
477
|
+
const bool perf = cmd_line && wcsstr(cmd_line, L"--perf") != nullptr;
|
|
478
|
+
|
|
479
|
+
// --profile: V8 の sampling CPU プロファイラを起動から終了まで回し、
|
|
480
|
+
// 終了時に self 時間の上位関数を next2d-cpuprofile.log へ出力する。
|
|
481
|
+
// jitless でも動作し、GC は "(garbage collector)" として現れる。
|
|
482
|
+
// --perf で見つかった js 区間の遷移スパイク (~1.2s) の中身を特定する用途。
|
|
483
|
+
const bool profile = cmd_line && wcsstr(cmd_line, L"--profile") != nullptr;
|
|
484
|
+
|
|
485
|
+
// 1. GDK ランタイム初期化。
|
|
486
|
+
// デスクトップでは Gaming Services 未導入の環境 (CI ランナー等) でも
|
|
487
|
+
// 起動できるよう、失敗を警告に留めて続行する (本ホストは XUser 等を未使用)。
|
|
488
|
+
// コンソールでは必須のため失敗したら終了する。
|
|
489
|
+
bool xgame_initialized = SUCCEEDED(XGameRuntimeInitialize());
|
|
490
|
+
if (!xgame_initialized) {
|
|
491
|
+
#if NEXT2D_XBOX_CONSOLE
|
|
211
492
|
std::cerr << "XGameRuntimeInitialize failed" << std::endl;
|
|
212
493
|
return 1;
|
|
494
|
+
#else
|
|
495
|
+
std::cerr << "warning: XGameRuntimeInitialize failed (Gaming Services not installed?)"
|
|
496
|
+
<< " - continuing on desktop" << std::endl;
|
|
497
|
+
#endif
|
|
213
498
|
}
|
|
214
499
|
|
|
215
500
|
const fs::path exe_dir = ExeDir();
|
|
@@ -217,6 +502,51 @@ int WINAPI wWinMain(HINSTANCE instance, HINSTANCE, PWSTR cmd_line, int)
|
|
|
217
502
|
const fs::path bootstrap_js = exe_dir / "js" / "bootstrap.js";
|
|
218
503
|
const fs::path selftest_js = exe_dir / "js" / "selftest.js";
|
|
219
504
|
|
|
505
|
+
// 埋め込みアセット (exe 内 RCDATA "N2DASSETS") を初期化する。存在すれば
|
|
506
|
+
// assets/app 一式と js/bootstrap.js を exe 内から読み、隣接平文 JS は不要になる。
|
|
507
|
+
// 無ければ全読み込みはファイルへフォールバックする (開発ビルド)。
|
|
508
|
+
if (InitEmbeddedAssets()) {
|
|
509
|
+
SetEmbeddedAssetsRoot(assets_app.string());
|
|
510
|
+
std::cerr << "[Assets] using embedded pak" << std::endl;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// フォント登録 (stb_truetype 用)。コンソールにはシステムフォント/DirectWrite が
|
|
514
|
+
// 無いため、資材内の TTF/OTF を stbtext レジストリへ登録しておく
|
|
515
|
+
// (デスクトップは DirectWrite が主経路のため未使用でも無害)。
|
|
516
|
+
// 対象: 埋め込み pak 内の *.ttf/*.otf + exe 隣接 fonts/ (開発ビルド用)。
|
|
517
|
+
{
|
|
518
|
+
const auto register_font = [](const std::string& name,
|
|
519
|
+
std::vector<uint8_t> bytes) {
|
|
520
|
+
if (stbtext::RegisterFont(name, std::move(bytes))) {
|
|
521
|
+
std::cerr << "[Font] registered: " << name << std::endl;
|
|
522
|
+
}
|
|
523
|
+
};
|
|
524
|
+
const auto is_font_key = [](const std::string& key) {
|
|
525
|
+
const auto dot = key.find_last_of('.');
|
|
526
|
+
if (dot == std::string::npos) return false;
|
|
527
|
+
const std::string ext = key.substr(dot + 1);
|
|
528
|
+
return ext == "ttf" || ext == "otf" || ext == "TTF" || ext == "OTF";
|
|
529
|
+
};
|
|
530
|
+
ForEachEmbeddedAsset(
|
|
531
|
+
[&](const std::string& key, const std::vector<uint8_t>& data) {
|
|
532
|
+
if (is_font_key(key)) {
|
|
533
|
+
register_font(fs::path(key).stem().string(), data);
|
|
534
|
+
}
|
|
535
|
+
});
|
|
536
|
+
std::error_code fonts_ec;
|
|
537
|
+
for (const auto& entry :
|
|
538
|
+
fs::directory_iterator(exe_dir / "fonts", fonts_ec)) {
|
|
539
|
+
const std::string p = entry.path().string();
|
|
540
|
+
if (is_font_key(p)) {
|
|
541
|
+
std::ifstream ifs(entry.path(), std::ios::binary);
|
|
542
|
+
std::vector<uint8_t> bytes(
|
|
543
|
+
(std::istreambuf_iterator<char>(ifs)),
|
|
544
|
+
std::istreambuf_iterator<char>());
|
|
545
|
+
register_font(entry.path().stem().string(), std::move(bytes));
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
220
550
|
// 2. HostContext と論理ビューポート
|
|
221
551
|
HostContext host;
|
|
222
552
|
host.viewport_width = 1920;
|
|
@@ -230,6 +560,22 @@ int WINAPI wWinMain(HINSTANCE instance, HINSTANCE, PWSTR cmd_line, int)
|
|
|
230
560
|
return 1;
|
|
231
561
|
}
|
|
232
562
|
|
|
563
|
+
// ウィンドウは要求サイズより小さく作られ得る (作業領域クランプ / DPI /
|
|
564
|
+
// CI の低解像度ディスプレイ)。surface・canvas・screen は全て viewport_* から
|
|
565
|
+
// 派生するため、ここで「実クライアント矩形」を採用して一致させる。これを
|
|
566
|
+
// 怠ると surface(1920x1080) を実ウィンドウ(例 1024x768)へ非等倍提示して
|
|
567
|
+
// 画面が縦長に歪む。V8 バインディング設置(screen/innerWidth/dpr)と
|
|
568
|
+
// Dawn 初期化の前に確定させる必要がある。
|
|
569
|
+
{
|
|
570
|
+
RECT rc = {};
|
|
571
|
+
if (GetClientRect(hwnd, &rc) && rc.right > 0 && rc.bottom > 0) {
|
|
572
|
+
host.viewport_width = rc.right;
|
|
573
|
+
host.viewport_height = rc.bottom;
|
|
574
|
+
}
|
|
575
|
+
std::cerr << "[Win] client rect: " << host.viewport_width
|
|
576
|
+
<< "x" << host.viewport_height << std::endl;
|
|
577
|
+
}
|
|
578
|
+
|
|
233
579
|
// 4. V8 プロセス初期化 + Isolate/Context + バインディング
|
|
234
580
|
V8Runtime::InitializeProcess(nullptr);
|
|
235
581
|
V8Runtime runtime;
|
|
@@ -255,7 +601,11 @@ int WINAPI wWinMain(HINSTANCE instance, HINSTANCE, PWSTR cmd_line, int)
|
|
|
255
601
|
host.workers = &workers;
|
|
256
602
|
g_dawn = &dawn;
|
|
257
603
|
|
|
258
|
-
|
|
604
|
+
// GPU 無し環境 (CI 等) では Dawn 初期化に失敗し得るが、CPU 側の機能と
|
|
605
|
+
// selftest は動かせるため続行する (Present 等は内部でゲートされる)
|
|
606
|
+
if (!dawn.Initialize(hwnd, host.viewport_width, host.viewport_height)) {
|
|
607
|
+
std::cerr << "warning: Dawn initialization failed - continuing without GPU" << std::endl;
|
|
608
|
+
}
|
|
259
609
|
gamepad.Initialize();
|
|
260
610
|
audio.Initialize();
|
|
261
611
|
|
|
@@ -270,9 +620,29 @@ int WINAPI wWinMain(HINSTANCE instance, HINSTANCE, PWSTR cmd_line, int)
|
|
|
270
620
|
InstallWorker(runtime.isolate(), ctx->Global(), &host);
|
|
271
621
|
}
|
|
272
622
|
|
|
623
|
+
// --profile: 起動直後からサンプリング開始 (0.5ms 間隔)
|
|
624
|
+
v8::CpuProfiler* cpu_profiler = nullptr;
|
|
625
|
+
if (profile) {
|
|
626
|
+
v8::Isolate::Scope iso_scope(runtime.isolate());
|
|
627
|
+
v8::HandleScope hs(runtime.isolate());
|
|
628
|
+
cpu_profiler = v8::CpuProfiler::New(runtime.isolate());
|
|
629
|
+
cpu_profiler->SetSamplingInterval(kProfileIntervalUs);
|
|
630
|
+
cpu_profiler->StartProfiling(
|
|
631
|
+
v8::String::NewFromUtf8Literal(runtime.isolate(), "next2d"));
|
|
632
|
+
std::cerr << "[cpuprofile] profiling started" << std::endl;
|
|
633
|
+
}
|
|
634
|
+
|
|
273
635
|
// 6. bootstrap.js -> アプリ本体(ESM) の順で読み込み
|
|
274
636
|
{
|
|
275
|
-
|
|
637
|
+
// 埋め込み pak を優先し、無ければ隣接ファイルから読む host スクリプトローダ。
|
|
638
|
+
const auto load_host_script = [&](const std::string& key, const fs::path& file) {
|
|
639
|
+
if (const auto* embedded = GetEmbeddedAsset(key)) {
|
|
640
|
+
return std::string(embedded->begin(), embedded->end());
|
|
641
|
+
}
|
|
642
|
+
return ReadFile(file);
|
|
643
|
+
};
|
|
644
|
+
|
|
645
|
+
const std::string boot = load_host_script("js/bootstrap.js", bootstrap_js);
|
|
276
646
|
if (boot.empty()) {
|
|
277
647
|
std::cerr << "bootstrap.js not found: " << bootstrap_js << std::endl;
|
|
278
648
|
} else if (!runtime.RunScript(boot, "js/bootstrap.js")) {
|
|
@@ -280,7 +650,7 @@ int WINAPI wWinMain(HINSTANCE instance, HINSTANCE, PWSTR cmd_line, int)
|
|
|
280
650
|
}
|
|
281
651
|
|
|
282
652
|
if (selftest) {
|
|
283
|
-
const std::string script =
|
|
653
|
+
const std::string script = load_host_script("js/selftest.js", selftest_js);
|
|
284
654
|
if (script.empty()) {
|
|
285
655
|
std::cerr << "selftest.js not found: " << selftest_js << std::endl;
|
|
286
656
|
return 1;
|
|
@@ -300,9 +670,14 @@ int WINAPI wWinMain(HINSTANCE instance, HINSTANCE, PWSTR cmd_line, int)
|
|
|
300
670
|
}
|
|
301
671
|
|
|
302
672
|
// 7. ゲームループ
|
|
303
|
-
v8::Isolate::Scope isolate_scope(runtime.isolate());
|
|
304
673
|
MSG msg = {};
|
|
305
674
|
int exit_code = 0;
|
|
675
|
+
PerfStats perf_stats;
|
|
676
|
+
double perf_prev_frame = 0.0;
|
|
677
|
+
// Isolate::Scope はループ内に限定する。Enter されたままの isolate を
|
|
678
|
+
// Dispose すると V8 の fatal (Disposing the isolate that is entered) になる。
|
|
679
|
+
{
|
|
680
|
+
v8::Isolate::Scope isolate_scope(runtime.isolate());
|
|
306
681
|
while (g_running) {
|
|
307
682
|
// Win32 メッセージ
|
|
308
683
|
while (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) {
|
|
@@ -318,6 +693,17 @@ int WINAPI wWinMain(HINSTANCE instance, HINSTANCE, PWSTR cmd_line, int)
|
|
|
318
693
|
|
|
319
694
|
const double now = event_loop.Now();
|
|
320
695
|
|
|
696
|
+
// --perf: 直前の計測点からの経過を各区間へ記録する軽量マーカー
|
|
697
|
+
double perf_cursor = now;
|
|
698
|
+
const auto perf_mark = [&](PerfSection& section) {
|
|
699
|
+
if (!perf) {
|
|
700
|
+
return;
|
|
701
|
+
}
|
|
702
|
+
const double t = event_loop.Now();
|
|
703
|
+
section.Add(t - perf_cursor);
|
|
704
|
+
perf_cursor = t;
|
|
705
|
+
};
|
|
706
|
+
|
|
321
707
|
v8::HandleScope handle_scope(runtime.isolate());
|
|
322
708
|
v8::Local<v8::Context> ctx = runtime.context();
|
|
323
709
|
v8::Context::Scope context_scope(ctx);
|
|
@@ -328,24 +714,64 @@ int WINAPI wWinMain(HINSTANCE instance, HINSTANCE, PWSTR cmd_line, int)
|
|
|
328
714
|
// 音声: 再生終了した source へ "ended" を配送する
|
|
329
715
|
PumpAudioEvents(runtime.isolate());
|
|
330
716
|
|
|
717
|
+
// バックグラウンドデコード完了の反映 (画像 load イベント / 音声 Promise)
|
|
718
|
+
decodequeue::Pump();
|
|
719
|
+
|
|
331
720
|
// タイマー -> マイクロタスク
|
|
332
721
|
event_loop.PumpTimers();
|
|
333
722
|
runtime.PumpMicrotasks();
|
|
723
|
+
perf_mark(perf_stats.tasks);
|
|
334
724
|
|
|
335
725
|
// メインスレッドの requestAnimationFrame (アプリのロジック/描画コマンド生成)
|
|
726
|
+
const double profile_js_start = cpu_profiler ? event_loop.Now() : 0.0;
|
|
336
727
|
event_loop.RunAnimationFrame(now);
|
|
337
728
|
runtime.PumpMicrotasks();
|
|
729
|
+
perf_mark(perf_stats.js);
|
|
730
|
+
|
|
731
|
+
// --profile: 遷移スパイク (単発の長い rAF) を検出したら窓を切り出す
|
|
732
|
+
if (cpu_profiler) {
|
|
733
|
+
const double js_ms = event_loop.Now() - profile_js_start;
|
|
734
|
+
if (js_ms > 300.0) {
|
|
735
|
+
char reason[64];
|
|
736
|
+
std::snprintf(reason, sizeof(reason), "spike (js %.0fms)", js_ms);
|
|
737
|
+
DumpAndRestartProfile(cpu_profiler, runtime.isolate(), reason);
|
|
738
|
+
}
|
|
739
|
+
}
|
|
338
740
|
|
|
339
741
|
// Worker (レンダラ等) のメッセージ配送 + rAF。
|
|
340
742
|
// レンダラ worker はここで WebGPU コマンドを発行し submit する。
|
|
341
743
|
workers.Pump(now);
|
|
744
|
+
perf_mark(perf_stats.worker);
|
|
342
745
|
|
|
343
746
|
// V8 プラットフォームタスク + Dawn の非同期処理
|
|
344
747
|
runtime.PumpPlatformTasks();
|
|
345
748
|
dawn.Tick();
|
|
749
|
+
perf_mark(perf_stats.tick);
|
|
346
750
|
|
|
347
751
|
// 提示
|
|
348
752
|
dawn.Present();
|
|
753
|
+
perf_mark(perf_stats.present);
|
|
754
|
+
|
|
755
|
+
if (perf) {
|
|
756
|
+
perf_stats.busy.Add(perf_cursor - now);
|
|
757
|
+
if (perf_prev_frame > 0.0) {
|
|
758
|
+
perf_stats.frame.Add(now - perf_prev_frame);
|
|
759
|
+
}
|
|
760
|
+
perf_prev_frame = now;
|
|
761
|
+
if (++perf_stats.frames >= PerfStats::kWindow) {
|
|
762
|
+
perf_stats.Flush();
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
// フレームペーシング: ブラウザの rAF 同様 ~60Hz に揃える。
|
|
767
|
+
// Present はスキップ時にブロックしないため、これが無いとループが
|
|
768
|
+
// 数万回転/秒で空回りし、rAF 前提のゲーム/Tween のタイミングが崩れる。
|
|
769
|
+
{
|
|
770
|
+
const double frame_ms = event_loop.Now() - now;
|
|
771
|
+
if (frame_ms < 15.0) {
|
|
772
|
+
Sleep(static_cast<DWORD>(15.0 - frame_ms));
|
|
773
|
+
}
|
|
774
|
+
}
|
|
349
775
|
|
|
350
776
|
// selftest 完了検知: selftest.js が globalThis.__selftestExitCode を設定したら終了
|
|
351
777
|
if (selftest) {
|
|
@@ -359,10 +785,35 @@ int WINAPI wWinMain(HINSTANCE instance, HINSTANCE, PWSTR cmd_line, int)
|
|
|
359
785
|
}
|
|
360
786
|
}
|
|
361
787
|
|
|
362
|
-
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
// --profile: 終了時に集計して出力する (Isolate 破棄前に必ず行う)
|
|
791
|
+
if (cpu_profiler) {
|
|
792
|
+
v8::Isolate::Scope iso_scope(runtime.isolate());
|
|
793
|
+
v8::HandleScope hs(runtime.isolate());
|
|
794
|
+
v8::CpuProfile* p = cpu_profiler->StopProfiling(
|
|
795
|
+
v8::String::NewFromUtf8Literal(runtime.isolate(), "next2d"));
|
|
796
|
+
if (p) {
|
|
797
|
+
WriteCpuProfile(p, kProfileIntervalUs, "final");
|
|
798
|
+
p->Delete();
|
|
799
|
+
}
|
|
800
|
+
cpu_profiler->Dispose();
|
|
801
|
+
cpu_profiler = nullptr;
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
// 8. 後始末: v8::Global を保持するものは Isolate 破棄前に必ず明示解放する。
|
|
805
|
+
// (スタック変数はスコープ終了 = runtime.Dispose() の後に巻き戻されるため、
|
|
806
|
+
// デストラクタ任せにすると破棄済み Isolate への Global::Reset で fail-fast する)
|
|
807
|
+
decodequeue::Shutdown(); // デコードスレッド join + 保留 complete (v8::Global) の破棄
|
|
808
|
+
workers.Shutdown(); // WorkerInstance の Global<Context> / EventLoop
|
|
809
|
+
event_loop.Shutdown(); // main の setTimeout/rAF コールバック (Global<Function>)
|
|
810
|
+
ShutdownAudioEvents();
|
|
811
|
+
ShutdownWebGPU();
|
|
363
812
|
host.main_canvas.Reset();
|
|
364
813
|
runtime.Dispose();
|
|
365
814
|
V8Runtime::ShutdownProcess();
|
|
366
|
-
|
|
815
|
+
if (xgame_initialized) {
|
|
816
|
+
XGameRuntimeUninitialize();
|
|
817
|
+
}
|
|
367
818
|
return exit_code;
|
|
368
819
|
}
|