@next2d/builder 0.1.8 → 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 -790
- 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 +11 -9
- package/templates/xbox/CMakeLists.txt +47 -11
- package/templates/xbox/README.md +18 -0
- package/templates/xbox/js/bootstrap.js +120 -0
- 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 +0 -15
- package/templates/xbox/src/bindings/Audio.cpp +118 -22
- package/templates/xbox/src/bindings/Canvas.cpp +32 -2
- package/templates/xbox/src/bindings/Canvas2D.cpp +495 -39
- package/templates/xbox/src/bindings/Console.cpp +6 -1
- package/templates/xbox/src/bindings/DomShims.cpp +18 -2
- package/templates/xbox/src/bindings/EventTarget.cpp +9 -0
- package/templates/xbox/src/bindings/Image.cpp +83 -57
- package/templates/xbox/src/bindings/Polyfills.cpp +671 -236
- package/templates/xbox/src/bindings/RasterCore.h +105 -1
- package/templates/xbox/src/bindings/Video.cpp +57 -3
- package/templates/xbox/src/bindings/webgpu/WebGPU.cpp +29 -3
- package/templates/xbox/src/bindings/webgpu/WebGPUCommon.h +4 -1
- package/templates/xbox/src/bindings/webgpu/WebGPUEnums.h +32 -0
- package/templates/xbox/src/gpu/DawnContext.cpp +167 -16
- package/templates/xbox/src/main.cpp +385 -16
- 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 +28 -3
- 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 +34 -33
- package/templates/xbox/src/v8/V8Util.h +54 -4
- package/templates/xbox/src/worker/WorkerRuntime.cpp +165 -65
- package/templates/xbox/src/worker/WorkerRuntime.h +23 -3
- package/templates/xbox/tests/check_local.sh +5 -1
- 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
|
@@ -4,28 +4,47 @@
|
|
|
4
4
|
// bootstrap.js とアプリ本体(ESM)の読み込み -> ゲームループ、という流れ。
|
|
5
5
|
#include <Windows.h>
|
|
6
6
|
#include <windowsx.h>
|
|
7
|
-
#include <timeapi.h>
|
|
8
7
|
#include <XGameRuntime.h>
|
|
9
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>
|
|
10
15
|
#pragma comment(lib, "winmm.lib")
|
|
16
|
+
#endif
|
|
11
17
|
|
|
12
18
|
#include "HostContext.h"
|
|
13
19
|
#include "AssetLoader.h"
|
|
20
|
+
#include "EmbeddedAssets.h"
|
|
14
21
|
#include "EventLoop.h"
|
|
15
22
|
#include "gpu/DawnContext.h"
|
|
23
|
+
#include "platform/DecodeQueue.h"
|
|
16
24
|
#include "platform/GamepadManager.h"
|
|
17
25
|
#include "platform/AudioEngine.h"
|
|
26
|
+
#include "platform/StbTextRasterizer.h"
|
|
18
27
|
#include "worker/WorkerRuntime.h"
|
|
19
28
|
#include "bindings/Bindings.h"
|
|
20
29
|
#include "bindings/EventTarget.h"
|
|
21
30
|
#include "v8/V8Runtime.h"
|
|
22
31
|
|
|
32
|
+
#include "v8/V8Util.h"
|
|
33
|
+
|
|
34
|
+
#include <v8-profiler.h>
|
|
35
|
+
|
|
36
|
+
#include <algorithm>
|
|
23
37
|
#include <filesystem>
|
|
24
38
|
#include <fstream>
|
|
25
39
|
#include <functional>
|
|
26
40
|
#include <iostream>
|
|
41
|
+
#include <map>
|
|
27
42
|
#include <sstream>
|
|
28
43
|
#include <string>
|
|
44
|
+
#include <utility>
|
|
45
|
+
#include <vector>
|
|
46
|
+
#include <cstdio>
|
|
47
|
+
#include <cstring>
|
|
29
48
|
|
|
30
49
|
namespace fs = std::filesystem;
|
|
31
50
|
using namespace next2d;
|
|
@@ -33,6 +52,137 @@ using namespace next2d;
|
|
|
33
52
|
namespace {
|
|
34
53
|
|
|
35
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
|
+
}
|
|
36
186
|
DawnContext* g_dawn = nullptr;
|
|
37
187
|
HostContext* g_host = nullptr;
|
|
38
188
|
V8Runtime* g_runtime = nullptr;
|
|
@@ -196,7 +346,9 @@ HWND CreateHostWindow(HINSTANCE instance, int width, int height)
|
|
|
196
346
|
// ウィンドウが画面(作業領域)より大きいと画面外にはみ出し、提示が
|
|
197
347
|
// 見切れる/非等倍でスケールされて歪む (CI の低解像度ディスプレイで
|
|
198
348
|
// 1920x1080 ウィンドウ → 描画が縦長になっていた)。作業領域に収まるよう
|
|
199
|
-
//
|
|
349
|
+
// クランプする。コンソールは常に全画面のためクランプ不要
|
|
350
|
+
// (SystemParametersInfo は Game Core に無い)。
|
|
351
|
+
#if !NEXT2D_XBOX_CONSOLE
|
|
200
352
|
RECT wa = {};
|
|
201
353
|
if (SystemParametersInfoW(SPI_GETWORKAREA, 0, &wa, 0)) {
|
|
202
354
|
const int maxW = wa.right - wa.left;
|
|
@@ -204,6 +356,7 @@ HWND CreateHostWindow(HINSTANCE instance, int width, int height)
|
|
|
204
356
|
if (maxW > 0 && width > maxW) { width = maxW; }
|
|
205
357
|
if (maxH > 0 && height > maxH) { height = maxH; }
|
|
206
358
|
}
|
|
359
|
+
#endif
|
|
207
360
|
|
|
208
361
|
// コンソールでは全画面相当。PC(GDK) 検証ではウィンドウ。
|
|
209
362
|
const DWORD style = WS_OVERLAPPEDWINDOW | WS_VISIBLE;
|
|
@@ -215,16 +368,120 @@ HWND CreateHostWindow(HINSTANCE instance, int width, int height)
|
|
|
215
368
|
|
|
216
369
|
} // namespace
|
|
217
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
|
+
|
|
218
459
|
int WINAPI wWinMain(HINSTANCE instance, HINSTANCE, PWSTR cmd_line, int)
|
|
219
460
|
{
|
|
461
|
+
#if !NEXT2D_XBOX_CONSOLE
|
|
462
|
+
// 最初にクラッシュハンドラを設置し、以降の segfault 等でスタックを記録する。
|
|
463
|
+
SetUnhandledExceptionFilter(CrashHandler);
|
|
464
|
+
|
|
220
465
|
// Sleep() の分解能を 1ms に上げる (フレームペーシング用。既定 15.6ms では
|
|
221
|
-
// 60Hz を刻めない)
|
|
466
|
+
// 60Hz を刻めない)。コンソールは全画面 VSync 提示でペーシングされるため不要。
|
|
222
467
|
timeBeginPeriod(1);
|
|
468
|
+
#endif
|
|
223
469
|
|
|
224
470
|
// --selftest: アプリの代わりに js/selftest.js を実行し、全バインディングを
|
|
225
471
|
// 実機/PC(GDK) 上で検証して終了する (テスト完了で自動終了)。
|
|
226
472
|
const bool selftest = cmd_line && wcsstr(cmd_line, L"--selftest") != nullptr;
|
|
227
473
|
|
|
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
|
+
|
|
228
485
|
// 1. GDK ランタイム初期化。
|
|
229
486
|
// デスクトップでは Gaming Services 未導入の環境 (CI ランナー等) でも
|
|
230
487
|
// 起動できるよう、失敗を警告に留めて続行する (本ホストは XUser 等を未使用)。
|
|
@@ -245,6 +502,51 @@ int WINAPI wWinMain(HINSTANCE instance, HINSTANCE, PWSTR cmd_line, int)
|
|
|
245
502
|
const fs::path bootstrap_js = exe_dir / "js" / "bootstrap.js";
|
|
246
503
|
const fs::path selftest_js = exe_dir / "js" / "selftest.js";
|
|
247
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
|
+
|
|
248
550
|
// 2. HostContext と論理ビューポート
|
|
249
551
|
HostContext host;
|
|
250
552
|
host.viewport_width = 1920;
|
|
@@ -318,9 +620,29 @@ int WINAPI wWinMain(HINSTANCE instance, HINSTANCE, PWSTR cmd_line, int)
|
|
|
318
620
|
InstallWorker(runtime.isolate(), ctx->Global(), &host);
|
|
319
621
|
}
|
|
320
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
|
+
|
|
321
635
|
// 6. bootstrap.js -> アプリ本体(ESM) の順で読み込み
|
|
322
636
|
{
|
|
323
|
-
|
|
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);
|
|
324
646
|
if (boot.empty()) {
|
|
325
647
|
std::cerr << "bootstrap.js not found: " << bootstrap_js << std::endl;
|
|
326
648
|
} else if (!runtime.RunScript(boot, "js/bootstrap.js")) {
|
|
@@ -328,7 +650,7 @@ int WINAPI wWinMain(HINSTANCE instance, HINSTANCE, PWSTR cmd_line, int)
|
|
|
328
650
|
}
|
|
329
651
|
|
|
330
652
|
if (selftest) {
|
|
331
|
-
const std::string script =
|
|
653
|
+
const std::string script = load_host_script("js/selftest.js", selftest_js);
|
|
332
654
|
if (script.empty()) {
|
|
333
655
|
std::cerr << "selftest.js not found: " << selftest_js << std::endl;
|
|
334
656
|
return 1;
|
|
@@ -350,6 +672,8 @@ int WINAPI wWinMain(HINSTANCE instance, HINSTANCE, PWSTR cmd_line, int)
|
|
|
350
672
|
// 7. ゲームループ
|
|
351
673
|
MSG msg = {};
|
|
352
674
|
int exit_code = 0;
|
|
675
|
+
PerfStats perf_stats;
|
|
676
|
+
double perf_prev_frame = 0.0;
|
|
353
677
|
// Isolate::Scope はループ内に限定する。Enter されたままの isolate を
|
|
354
678
|
// Dispose すると V8 の fatal (Disposing the isolate that is entered) になる。
|
|
355
679
|
{
|
|
@@ -369,6 +693,17 @@ int WINAPI wWinMain(HINSTANCE instance, HINSTANCE, PWSTR cmd_line, int)
|
|
|
369
693
|
|
|
370
694
|
const double now = event_loop.Now();
|
|
371
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
|
+
|
|
372
707
|
v8::HandleScope handle_scope(runtime.isolate());
|
|
373
708
|
v8::Local<v8::Context> ctx = runtime.context();
|
|
374
709
|
v8::Context::Scope context_scope(ctx);
|
|
@@ -379,24 +714,54 @@ int WINAPI wWinMain(HINSTANCE instance, HINSTANCE, PWSTR cmd_line, int)
|
|
|
379
714
|
// 音声: 再生終了した source へ "ended" を配送する
|
|
380
715
|
PumpAudioEvents(runtime.isolate());
|
|
381
716
|
|
|
717
|
+
// バックグラウンドデコード完了の反映 (画像 load イベント / 音声 Promise)
|
|
718
|
+
decodequeue::Pump();
|
|
719
|
+
|
|
382
720
|
// タイマー -> マイクロタスク
|
|
383
721
|
event_loop.PumpTimers();
|
|
384
722
|
runtime.PumpMicrotasks();
|
|
723
|
+
perf_mark(perf_stats.tasks);
|
|
385
724
|
|
|
386
725
|
// メインスレッドの requestAnimationFrame (アプリのロジック/描画コマンド生成)
|
|
726
|
+
const double profile_js_start = cpu_profiler ? event_loop.Now() : 0.0;
|
|
387
727
|
event_loop.RunAnimationFrame(now);
|
|
388
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
|
+
}
|
|
389
740
|
|
|
390
741
|
// Worker (レンダラ等) のメッセージ配送 + rAF。
|
|
391
742
|
// レンダラ worker はここで WebGPU コマンドを発行し submit する。
|
|
392
743
|
workers.Pump(now);
|
|
744
|
+
perf_mark(perf_stats.worker);
|
|
393
745
|
|
|
394
746
|
// V8 プラットフォームタスク + Dawn の非同期処理
|
|
395
747
|
runtime.PumpPlatformTasks();
|
|
396
748
|
dawn.Tick();
|
|
749
|
+
perf_mark(perf_stats.tick);
|
|
397
750
|
|
|
398
751
|
// 提示
|
|
399
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
|
+
}
|
|
400
765
|
|
|
401
766
|
// フレームペーシング: ブラウザの rAF 同様 ~60Hz に揃える。
|
|
402
767
|
// Present はスキップ時にブロックしないため、これが無いとループが
|
|
@@ -408,17 +773,6 @@ int WINAPI wWinMain(HINSTANCE instance, HINSTANCE, PWSTR cmd_line, int)
|
|
|
408
773
|
}
|
|
409
774
|
}
|
|
410
775
|
|
|
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
|
-
|
|
422
776
|
// selftest 完了検知: selftest.js が globalThis.__selftestExitCode を設定したら終了
|
|
423
777
|
if (selftest) {
|
|
424
778
|
v8::Local<v8::Value> code;
|
|
@@ -433,9 +787,24 @@ int WINAPI wWinMain(HINSTANCE instance, HINSTANCE, PWSTR cmd_line, int)
|
|
|
433
787
|
|
|
434
788
|
}
|
|
435
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
|
+
|
|
436
804
|
// 8. 後始末: v8::Global を保持するものは Isolate 破棄前に必ず明示解放する。
|
|
437
805
|
// (スタック変数はスコープ終了 = runtime.Dispose() の後に巻き戻されるため、
|
|
438
806
|
// デストラクタ任せにすると破棄済み Isolate への Global::Reset で fail-fast する)
|
|
807
|
+
decodequeue::Shutdown(); // デコードスレッド join + 保留 complete (v8::Global) の破棄
|
|
439
808
|
workers.Shutdown(); // WorkerInstance の Global<Context> / EventLoop
|
|
440
809
|
event_loop.Shutdown(); // main の setTimeout/rAF コールバック (Global<Function>)
|
|
441
810
|
ShutdownAudioEvents();
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
#include "AudioDecoder.h"
|
|
2
|
+
|
|
3
|
+
// dr_mp3 / dr_wav / stb_vorbis: public domain のヘッダオンリーデコーダ (third_party/)。
|
|
4
|
+
// メモリ入力のみ使用する。実装は本 TU に集約する (ODR 一意)。
|
|
5
|
+
#define DR_MP3_IMPLEMENTATION
|
|
6
|
+
#define DR_MP3_NO_STDIO
|
|
7
|
+
#include "third_party/dr_mp3.h"
|
|
8
|
+
|
|
9
|
+
#define DR_WAV_IMPLEMENTATION
|
|
10
|
+
#define DR_WAV_NO_STDIO
|
|
11
|
+
#include "third_party/dr_wav.h"
|
|
12
|
+
|
|
13
|
+
// stb_vorbis は .c 配布のためここで取り込む (STB_VORBIS_HEADER_ONLY は使わない)。
|
|
14
|
+
#define STB_VORBIS_NO_STDIO
|
|
15
|
+
#define STB_VORBIS_NO_PUSHDATA_API
|
|
16
|
+
#include "third_party/stb_vorbis.c"
|
|
17
|
+
|
|
18
|
+
#include <cstring>
|
|
19
|
+
|
|
20
|
+
namespace next2d {
|
|
21
|
+
|
|
22
|
+
namespace {
|
|
23
|
+
|
|
24
|
+
bool DecodeMp3(const std::vector<uint8_t>& input, PcmBuffer& out)
|
|
25
|
+
{
|
|
26
|
+
drmp3_config config = {};
|
|
27
|
+
drmp3_uint64 total_frames = 0;
|
|
28
|
+
float* frames = drmp3_open_memory_and_read_pcm_frames_f32(
|
|
29
|
+
input.data(), input.size(), &config, &total_frames, nullptr);
|
|
30
|
+
if (!frames || total_frames == 0 || config.channels == 0) {
|
|
31
|
+
if (frames) {
|
|
32
|
+
drmp3_free(frames, nullptr);
|
|
33
|
+
}
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
out.channels = config.channels;
|
|
37
|
+
out.sample_rate = config.sampleRate;
|
|
38
|
+
out.samples.assign(frames, frames + total_frames * config.channels);
|
|
39
|
+
drmp3_free(frames, nullptr);
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
bool DecodeWav(const std::vector<uint8_t>& input, PcmBuffer& out)
|
|
44
|
+
{
|
|
45
|
+
unsigned int channels = 0, sample_rate = 0;
|
|
46
|
+
drwav_uint64 total_frames = 0;
|
|
47
|
+
float* frames = drwav_open_memory_and_read_pcm_frames_f32(
|
|
48
|
+
input.data(), input.size(), &channels, &sample_rate, &total_frames, nullptr);
|
|
49
|
+
if (!frames || total_frames == 0 || channels == 0) {
|
|
50
|
+
if (frames) {
|
|
51
|
+
drwav_free(frames, nullptr);
|
|
52
|
+
}
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
out.channels = channels;
|
|
56
|
+
out.sample_rate = sample_rate;
|
|
57
|
+
out.samples.assign(frames, frames + total_frames * channels);
|
|
58
|
+
drwav_free(frames, nullptr);
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
bool DecodeOgg(const std::vector<uint8_t>& input, PcmBuffer& out)
|
|
63
|
+
{
|
|
64
|
+
int err = 0;
|
|
65
|
+
stb_vorbis* vorbis = stb_vorbis_open_memory(
|
|
66
|
+
input.data(), static_cast<int>(input.size()), &err, nullptr);
|
|
67
|
+
if (!vorbis) {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
const stb_vorbis_info info = stb_vorbis_get_info(vorbis);
|
|
71
|
+
const unsigned int total_frames = stb_vorbis_stream_length_in_samples(vorbis);
|
|
72
|
+
if (info.channels <= 0 || total_frames == 0) {
|
|
73
|
+
stb_vorbis_close(vorbis);
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
out.channels = static_cast<uint32_t>(info.channels);
|
|
77
|
+
out.sample_rate = info.sample_rate;
|
|
78
|
+
out.samples.resize(static_cast<size_t>(total_frames) * info.channels);
|
|
79
|
+
const int read = stb_vorbis_get_samples_float_interleaved(
|
|
80
|
+
vorbis, info.channels, out.samples.data(),
|
|
81
|
+
static_cast<int>(out.samples.size()));
|
|
82
|
+
stb_vorbis_close(vorbis);
|
|
83
|
+
if (read <= 0) {
|
|
84
|
+
out.samples.clear();
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
out.samples.resize(static_cast<size_t>(read) * info.channels);
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
} // namespace
|
|
92
|
+
|
|
93
|
+
bool DecodeAudio(const std::vector<uint8_t>& input, PcmBuffer& out)
|
|
94
|
+
{
|
|
95
|
+
if (input.size() < 12) {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
const uint8_t* d = input.data();
|
|
99
|
+
|
|
100
|
+
// マジックナンバーで形式を判定して該当デコーダを先に試し、
|
|
101
|
+
// 外れたら残りをカスケード (誤判定・ヘッダ無し MP3 の保険)。
|
|
102
|
+
const bool looks_wav = std::memcmp(d, "RIFF", 4) == 0 && std::memcmp(d + 8, "WAVE", 4) == 0;
|
|
103
|
+
const bool looks_ogg = std::memcmp(d, "OggS", 4) == 0;
|
|
104
|
+
const bool looks_mp3 = std::memcmp(d, "ID3", 3) == 0
|
|
105
|
+
|| (d[0] == 0xFF && (d[1] & 0xE0) == 0xE0);
|
|
106
|
+
|
|
107
|
+
if (looks_wav && DecodeWav(input, out)) {
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
if (looks_ogg && DecodeOgg(input, out)) {
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
if (looks_mp3 && DecodeMp3(input, out)) {
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// 判定が外れた場合のカスケード
|
|
118
|
+
return DecodeWav(input, out) || DecodeOgg(input, out) || DecodeMp3(input, out);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
} // namespace next2d
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// 音声デコード (エンコード済みバイト列 -> float32 interleaved PCM)。
|
|
2
|
+
//
|
|
3
|
+
// 第一候補は dr_mp3 / dr_wav / stb_vorbis (MP3/WAV/OGG、全プラットフォーム共通、
|
|
4
|
+
// public domain)。コンソール (Game Core OS) にはデスクトップの Media Foundation が
|
|
5
|
+
// 無いため、MF 依存を第一経路から外して PC 検証と実機の挙動を一致させる。
|
|
6
|
+
// デスクトップのみ AAC 等の保険として AudioEngine::Decode 側に MF フォールバックが残る。
|
|
7
|
+
#pragma once
|
|
8
|
+
|
|
9
|
+
#include "AudioEngine.h" // PcmBuffer
|
|
10
|
+
|
|
11
|
+
#include <cstdint>
|
|
12
|
+
#include <vector>
|
|
13
|
+
|
|
14
|
+
namespace next2d {
|
|
15
|
+
|
|
16
|
+
bool DecodeAudio(const std::vector<uint8_t>& input, PcmBuffer& out);
|
|
17
|
+
|
|
18
|
+
} // namespace next2d
|