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
46162DawnContext::~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 (
0 commit comments