Skip to content

Commit 1ebec3a

Browse files
committed
#75 wip: xboxビルドテスト v67
1 parent 548b1c9 commit 1ebec3a

2 files changed

Lines changed: 210 additions & 1 deletion

File tree

templates/xbox/src/gpu/DawnContext.cpp

Lines changed: 125 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
#include "DawnContext.h"
22

33
#include <Windows.h>
4+
#include <chrono>
5+
#include <cstdint>
6+
#include <cstdio>
7+
#include <cstdlib>
8+
#include <filesystem>
49
#include <fstream>
510
#include <iostream>
611
#include <string>
@@ -41,6 +46,117 @@ void HandleUncapturedError(const wgpu::Device&, wgpu::ErrorType type, wgpu::Stri
4146
AppendGpuLog(line);
4247
}
4348

49+
// --- GPU Blob キャッシュ (シェーダ/パイプラインのコンパイル結果) -------------
50+
// D3D12 の HLSL コンパイル (FXC) はパイプライン初回生成時に走り、起動直後や
51+
// 新しいエフェクト初出時のフレームヒッチの原因になる。Dawn の BlobCache を
52+
// %LOCALAPPDATA%\Next2D\gpucache に永続化し、2 回目以降の起動で再利用する
53+
// (V8 バイトコードキャッシュの GPU 版)。
54+
// 呼び出し規約 (dawn/native/BlobCache.cpp LoadInternal で検証済み):
55+
// load は 2 段階 — まず value=nullptr でサイズ問い合わせ (戻り値 0 = miss)、
56+
// 次に確保済みバッファへの読み込み (戻り値がサイズと不一致なら miss 扱い)。
57+
// キーには Dawn がアダプタ/ドライバ情報を織り込むため、環境が変われば自然に
58+
// 別エントリになる。あらゆる失敗は 0 / 何もしない (graceful degrade)。
59+
60+
uint64_t Fnv1a(const void* data, size_t size, uint64_t hash = 1469598103934665603ull)
61+
{
62+
const auto* p = static_cast<const unsigned char*>(data);
63+
for (size_t i = 0; i < size; ++i) {
64+
hash ^= p[i];
65+
hash *= 1099511628211ull;
66+
}
67+
return hash;
68+
}
69+
70+
const std::filesystem::path& GpuCacheDir()
71+
{
72+
static const std::filesystem::path dir = []() -> std::filesystem::path {
73+
const char* base = std::getenv("LOCALAPPDATA");
74+
if (!base || !*base) {
75+
return {};
76+
}
77+
std::error_code ec;
78+
std::filesystem::path d = std::filesystem::path(base) / "Next2D" / "gpucache";
79+
std::filesystem::create_directories(d, ec);
80+
if (ec) {
81+
return {};
82+
}
83+
// ドライバ/Dawn 更新でキーが変わると旧 blob は孤児になる。
84+
// 30 日アクセスの無い .blob を掃除する (load ヒット時に touch する LRU)。
85+
const auto now = std::filesystem::file_time_type::clock::now();
86+
std::error_code iter_ec;
87+
for (const auto& entry : std::filesystem::directory_iterator(d, iter_ec)) {
88+
std::error_code e2;
89+
if (entry.path().extension() != ".blob") {
90+
continue;
91+
}
92+
const auto t = std::filesystem::last_write_time(entry.path(), e2);
93+
if (!e2 && now - t > std::chrono::hours(24 * 30)) {
94+
std::filesystem::remove(entry.path(), e2);
95+
}
96+
}
97+
return d;
98+
}();
99+
return dir;
100+
}
101+
102+
std::filesystem::path GpuCacheFileFor(const void* key, size_t key_size)
103+
{
104+
const std::filesystem::path& dir = GpuCacheDir();
105+
if (dir.empty()) {
106+
return {};
107+
}
108+
char buf[17] = {};
109+
std::snprintf(buf, sizeof(buf), "%016llx",
110+
static_cast<unsigned long long>(Fnv1a(key, key_size)));
111+
return dir / (std::string(buf) + ".blob");
112+
}
113+
114+
size_t LoadGpuCacheData(const void* key, size_t key_size,
115+
void* value, size_t value_size, void*)
116+
{
117+
const std::filesystem::path file = GpuCacheFileFor(key, key_size);
118+
if (file.empty()) {
119+
return 0;
120+
}
121+
std::error_code ec;
122+
const auto size = std::filesystem::file_size(file, ec);
123+
if (ec || size == 0) {
124+
return 0;
125+
}
126+
if (value == nullptr) {
127+
// 1 段階目: サイズ問い合わせ
128+
return static_cast<size_t>(size);
129+
}
130+
if (value_size < size) {
131+
return 0;
132+
}
133+
std::ifstream ifs(file, std::ios::binary);
134+
if (!ifs || !ifs.read(static_cast<char*>(value), static_cast<std::streamsize>(size))) {
135+
return 0;
136+
}
137+
// 使用時に touch して 30 日掃除を LRU として機能させる
138+
std::filesystem::last_write_time(
139+
file, std::filesystem::file_time_type::clock::now(), ec);
140+
return static_cast<size_t>(size);
141+
}
142+
143+
void StoreGpuCacheData(const void* key, size_t key_size,
144+
const void* value, size_t value_size, void*)
145+
{
146+
if (!value || value_size == 0) {
147+
return;
148+
}
149+
const std::filesystem::path file = GpuCacheFileFor(key, key_size);
150+
if (file.empty()) {
151+
return;
152+
}
153+
std::ofstream ofs(file, std::ios::binary | std::ios::trunc);
154+
if (ofs) {
155+
ofs.write(static_cast<const char*>(value),
156+
static_cast<std::streamsize>(value_size));
157+
}
158+
}
159+
44160
} // namespace
45161

46162
DawnContext::~DawnContext() = default;
@@ -117,6 +233,14 @@ bool DawnContext::AcquireDevice()
117233
device_desc.SetDeviceLostCallback(wgpu::CallbackMode::AllowProcessEvents, HandleDeviceLost);
118234
device_desc.SetUncapturedErrorCallback(HandleUncapturedError);
119235

236+
// GPU Blob キャッシュを接続する (シェーダコンパイル結果の永続化)。
237+
// 関数ポインタは Dawn が device 生成時に BlobCache へコピーするため、
238+
// この記述子自体は RequestDevice 完了までの生存で足りる。
239+
wgpu::DawnCacheDeviceDescriptor cache_desc = {};
240+
cache_desc.loadDataFunction = LoadGpuCacheData;
241+
cache_desc.storeDataFunction = StoreGpuCacheData;
242+
device_desc.nextInChain = &cache_desc;
243+
120244
#ifdef NDEBUG
121245
// Release では Dawn の API 検証 (skip_validation) と robustness (OOB クランプ) を
122246
// 無効化する。両方とも全描画コマンドのエンコードで毎フレーム CPU を消費する
@@ -128,7 +252,7 @@ bool DawnContext::AcquireDevice()
128252
wgpu::DawnTogglesDescriptor device_toggles = {};
129253
device_toggles.enabledToggles = kReleaseToggles;
130254
device_toggles.enabledToggleCount = 2;
131-
device_desc.nextInChain = &device_toggles;
255+
cache_desc.nextInChain = &device_toggles; // device -> cache -> toggles
132256
#endif
133257

134258
wgpu::Future device_future = adapter_.RequestDevice(

templates/xbox/src/main.cpp

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
#include <iostream>
3131
#include <sstream>
3232
#include <string>
33+
#include <cstdio>
3334
#include <cstring>
3435

3536
namespace fs = std::filesystem;
@@ -38,6 +39,56 @@ using namespace next2d;
3839
namespace {
3940

4041
bool g_running = true;
42+
43+
// --- --perf: フレーム統計 ---------------------------------------------------
44+
// メインループの各区間の所要時間 (ms) を集計し、300 フレーム毎に 1 行で出力する。
45+
// avg でボトルネックの区間を、max でヒッチ (スパイク) の在り処を特定する。
46+
// GUI 実行では stderr が見えないため next2d-perf.log にも追記する。
47+
struct PerfSection {
48+
double sum = 0.0;
49+
double max = 0.0;
50+
void Add(double ms)
51+
{
52+
sum += ms;
53+
if (ms > max) {
54+
max = ms;
55+
}
56+
}
57+
};
58+
59+
struct PerfStats {
60+
static constexpr int kWindow = 300; // 60fps で約 5 秒
61+
PerfSection tasks; // 入力/音声/タイマー/マイクロタスク
62+
PerfSection js; // メイン rAF (アプリロジック/描画コマンド生成)
63+
PerfSection worker; // レンダラ worker (WebGPU コマンド発行 + submit)
64+
PerfSection tick; // V8 プラットフォームタスク + Dawn Tick
65+
PerfSection present; // Present (GPU/VSync 待ちを含む)
66+
PerfSection busy; // 上記合計 (ペーシング Sleep を除く実働)
67+
PerfSection frame; // フレーム間隔 (実効フレームレートの逆数)
68+
int frames = 0;
69+
70+
void Flush()
71+
{
72+
if (frames == 0) {
73+
return;
74+
}
75+
const auto avg = [this](const PerfSection& s) { return s.sum / frames; };
76+
char line[320];
77+
std::snprintf(line, sizeof(line),
78+
"[perf] frame %.1fms (max %.1f) busy %.1fms | "
79+
"tasks %.2f/%.1f js %.2f/%.1f worker %.2f/%.1f "
80+
"tick %.2f/%.1f present %.2f/%.1f (avg/max, %d frames)",
81+
avg(frame), frame.max, avg(busy),
82+
avg(tasks), tasks.max, avg(js), js.max, avg(worker), worker.max,
83+
avg(tick), tick.max, avg(present), present.max, frames);
84+
std::cerr << line << std::endl;
85+
std::ofstream ofs("next2d-perf.log", std::ios::app);
86+
if (ofs) {
87+
ofs << line << std::endl;
88+
}
89+
*this = PerfStats{};
90+
}
91+
};
4192
DawnContext* g_dawn = nullptr;
4293
HostContext* g_host = nullptr;
4394
V8Runtime* g_runtime = nullptr;
@@ -318,6 +369,11 @@ int WINAPI wWinMain(HINSTANCE instance, HINSTANCE, PWSTR cmd_line, int)
318369
// 実機/PC(GDK) 上で検証して終了する (テスト完了で自動終了)。
319370
const bool selftest = cmd_line && wcsstr(cmd_line, L"--selftest") != nullptr;
320371

372+
// --perf: フレーム統計を 300 フレーム (約 5 秒) 毎に stderr と next2d-perf.log
373+
// へ出力する。どの区間 (JS / レンダラ worker / GPU / Present) にフレーム時間を
374+
// 使っているかを数値化し、以降の最適化 (worker 並列化等) の要否判断に使う。
375+
const bool perf = cmd_line && wcsstr(cmd_line, L"--perf") != nullptr;
376+
321377
// 1. GDK ランタイム初期化。
322378
// デスクトップでは Gaming Services 未導入の環境 (CI ランナー等) でも
323379
// 起動できるよう、失敗を警告に留めて続行する (本ホストは XUser 等を未使用)。
@@ -459,6 +515,8 @@ int WINAPI wWinMain(HINSTANCE instance, HINSTANCE, PWSTR cmd_line, int)
459515
// 7. ゲームループ
460516
MSG msg = {};
461517
int exit_code = 0;
518+
PerfStats perf_stats;
519+
double perf_prev_frame = 0.0;
462520
// Isolate::Scope はループ内に限定する。Enter されたままの isolate を
463521
// Dispose すると V8 の fatal (Disposing the isolate that is entered) になる。
464522
{
@@ -478,6 +536,17 @@ int WINAPI wWinMain(HINSTANCE instance, HINSTANCE, PWSTR cmd_line, int)
478536

479537
const double now = event_loop.Now();
480538

539+
// --perf: 直前の計測点からの経過を各区間へ記録する軽量マーカー
540+
double perf_cursor = now;
541+
const auto perf_mark = [&](PerfSection& section) {
542+
if (!perf) {
543+
return;
544+
}
545+
const double t = event_loop.Now();
546+
section.Add(t - perf_cursor);
547+
perf_cursor = t;
548+
};
549+
481550
v8::HandleScope handle_scope(runtime.isolate());
482551
v8::Local<v8::Context> ctx = runtime.context();
483552
v8::Context::Scope context_scope(ctx);
@@ -491,21 +560,37 @@ int WINAPI wWinMain(HINSTANCE instance, HINSTANCE, PWSTR cmd_line, int)
491560
// タイマー -> マイクロタスク
492561
event_loop.PumpTimers();
493562
runtime.PumpMicrotasks();
563+
perf_mark(perf_stats.tasks);
494564

495565
// メインスレッドの requestAnimationFrame (アプリのロジック/描画コマンド生成)
496566
event_loop.RunAnimationFrame(now);
497567
runtime.PumpMicrotasks();
568+
perf_mark(perf_stats.js);
498569

499570
// Worker (レンダラ等) のメッセージ配送 + rAF。
500571
// レンダラ worker はここで WebGPU コマンドを発行し submit する。
501572
workers.Pump(now);
573+
perf_mark(perf_stats.worker);
502574

503575
// V8 プラットフォームタスク + Dawn の非同期処理
504576
runtime.PumpPlatformTasks();
505577
dawn.Tick();
578+
perf_mark(perf_stats.tick);
506579

507580
// 提示
508581
dawn.Present();
582+
perf_mark(perf_stats.present);
583+
584+
if (perf) {
585+
perf_stats.busy.Add(perf_cursor - now);
586+
if (perf_prev_frame > 0.0) {
587+
perf_stats.frame.Add(now - perf_prev_frame);
588+
}
589+
perf_prev_frame = now;
590+
if (++perf_stats.frames >= PerfStats::kWindow) {
591+
perf_stats.Flush();
592+
}
593+
}
509594

510595
// フレームペーシング: ブラウザの rAF 同様 ~60Hz に揃える。
511596
// Present はスキップ時にブロックしないため、これが無いとループが

0 commit comments

Comments
 (0)