@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.
Files changed (67) hide show
  1. package/dist/cli.d.ts +59 -0
  2. package/dist/cli.js +141 -0
  3. package/dist/constants.d.ts +53 -0
  4. package/dist/constants.js +64 -0
  5. package/dist/context.d.ts +29 -0
  6. package/dist/context.js +19 -0
  7. package/dist/electron.d.ts +10 -0
  8. package/dist/electron.js +142 -0
  9. package/dist/index.js +18 -790
  10. package/dist/native.d.ts +27 -0
  11. package/dist/native.js +123 -0
  12. package/dist/utils.d.ts +27 -0
  13. package/dist/utils.js +40 -0
  14. package/dist/web.d.ts +18 -0
  15. package/dist/web.js +70 -0
  16. package/dist/xbox.d.ts +62 -0
  17. package/dist/xbox.js +426 -0
  18. package/package.json +11 -9
  19. package/templates/xbox/CMakeLists.txt +47 -11
  20. package/templates/xbox/README.md +18 -0
  21. package/templates/xbox/js/bootstrap.js +120 -0
  22. package/templates/xbox/src/AssetLoader.cpp +115 -12
  23. package/templates/xbox/src/AssetLoader.h +4 -0
  24. package/templates/xbox/src/EmbeddedAssets.cpp +149 -0
  25. package/templates/xbox/src/EmbeddedAssets.h +60 -0
  26. package/templates/xbox/src/EventLoop.cpp +0 -15
  27. package/templates/xbox/src/bindings/Audio.cpp +118 -22
  28. package/templates/xbox/src/bindings/Canvas.cpp +32 -2
  29. package/templates/xbox/src/bindings/Canvas2D.cpp +495 -39
  30. package/templates/xbox/src/bindings/Console.cpp +6 -1
  31. package/templates/xbox/src/bindings/DomShims.cpp +18 -2
  32. package/templates/xbox/src/bindings/EventTarget.cpp +9 -0
  33. package/templates/xbox/src/bindings/Image.cpp +83 -57
  34. package/templates/xbox/src/bindings/Polyfills.cpp +671 -236
  35. package/templates/xbox/src/bindings/RasterCore.h +105 -1
  36. package/templates/xbox/src/bindings/Video.cpp +57 -3
  37. package/templates/xbox/src/bindings/webgpu/WebGPU.cpp +29 -3
  38. package/templates/xbox/src/bindings/webgpu/WebGPUCommon.h +4 -1
  39. package/templates/xbox/src/bindings/webgpu/WebGPUEnums.h +32 -0
  40. package/templates/xbox/src/gpu/DawnContext.cpp +167 -16
  41. package/templates/xbox/src/main.cpp +385 -16
  42. package/templates/xbox/src/platform/AudioDecoder.cpp +121 -0
  43. package/templates/xbox/src/platform/AudioDecoder.h +18 -0
  44. package/templates/xbox/src/platform/AudioEngine.cpp +28 -3
  45. package/templates/xbox/src/platform/DecodeQueue.cpp +136 -0
  46. package/templates/xbox/src/platform/DecodeQueue.h +27 -0
  47. package/templates/xbox/src/platform/ImageDecoder.cpp +44 -0
  48. package/templates/xbox/src/platform/ImageDecoder.h +18 -0
  49. package/templates/xbox/src/platform/StbTextRasterizer.cpp +324 -0
  50. package/templates/xbox/src/platform/StbTextRasterizer.h +37 -0
  51. package/templates/xbox/src/platform/TextRasterizer.cpp +32 -0
  52. package/templates/xbox/src/platform/WicDecoder.cpp +19 -0
  53. package/templates/xbox/src/v8/CodeCache.h +223 -0
  54. package/templates/xbox/src/v8/V8Runtime.cpp +34 -33
  55. package/templates/xbox/src/v8/V8Util.h +54 -4
  56. package/templates/xbox/src/worker/WorkerRuntime.cpp +165 -65
  57. package/templates/xbox/src/worker/WorkerRuntime.h +23 -3
  58. package/templates/xbox/tests/check_local.sh +5 -1
  59. package/templates/xbox/tests/gamecore_api_probe.cpp +172 -0
  60. package/templates/xbox/tests/raster_test.cpp +50 -0
  61. package/templates/xbox/tests/windows_platform_test.cpp +75 -3
  62. package/templates/xbox/third_party/LICENSES.md +14 -0
  63. package/templates/xbox/third_party/dr_mp3.h +5412 -0
  64. package/templates/xbox/third_party/dr_wav.h +9131 -0
  65. package/templates/xbox/third_party/stb_image.h +7988 -0
  66. package/templates/xbox/third_party/stb_truetype.h +5079 -0
  67. package/templates/xbox/third_party/stb_vorbis.c +5584 -0
@@ -7,6 +7,7 @@
7
7
  #include <cctype>
8
8
  #include <cmath>
9
9
  #include <cstdint>
10
+ #include <memory>
10
11
  #include <string>
11
12
  #include <utility>
12
13
  #include <vector>
@@ -26,10 +27,18 @@ struct SubPath {
26
27
 
27
28
  struct RGBA { uint8_t r=0,g=0,b=0,a=255; };
28
29
 
29
- // クリップ矩形 (デバイス座標, [x0,x1) x [y0,y1))。active=false なら無制限。
30
+ // クリップ領域 (デバイス座標)。
31
+ // active=false … 無制限
32
+ // active=true, mask=null … 矩形クリップ [x0,x1) x [y0,y1) (厳密)
33
+ // mask!=null … 任意形状クリップ。x0..y1 は外接矩形 (高速リジェクト用)、
34
+ // mask は surface と同寸のカバレッジ (1=内側/0=外側)。
35
+ // mask は shared_ptr のため save()/restore() の state コピーで安価に共有される
36
+ // (clip() は既存 mask を破壊せず新しい mask を割り当てるので復元が壊れない)。
30
37
  struct ClipRect {
31
38
  bool active = false;
32
39
  double x0 = 0, y0 = 0, x1 = 0, y1 = 0;
40
+ std::shared_ptr<const std::vector<uint8_t>> mask;
41
+ int mask_w = 0, mask_h = 0;
33
42
  };
34
43
 
35
44
  // RGBA8 のピクセルバッファ。
@@ -101,6 +110,9 @@ inline void Blend(Surface& s, const ClipRect& clip, int x, int y, const RGBA& co
101
110
  {
102
111
  if (x < 0 || y < 0 || x >= s.width || y >= s.height) return;
103
112
  if (clip.active && (x < clip.x0 || x >= clip.x1 || y < clip.y0 || y >= clip.y1)) return;
113
+ // 任意形状クリップ: マスクの外なら書き込まない。
114
+ if (clip.mask && (x >= clip.mask_w || y >= clip.mask_h ||
115
+ (*clip.mask)[static_cast<size_t>(y) * clip.mask_w + x] == 0)) return;
104
116
  size_t i = (static_cast<size_t>(y) * s.width + x) * 4;
105
117
  double a = (col.a / 255.0) * alpha;
106
118
  s.pixels[i+0] = static_cast<uint8_t>(col.r * a + s.pixels[i+0] * (1 - a));
@@ -215,6 +227,98 @@ inline void IntersectClip(ClipRect& clip, double nx0, double ny0, double nx1, do
215
227
  }
216
228
  }
217
229
 
230
+ // パスが単一の軸並行矩形か判定する (矩形なら外接矩形クリップで厳密に足りる)。
231
+ // beginPath→rect() は 4 or 5 点 (閉路点重複) の矩形サブパスになる。
232
+ inline bool IsAxisAlignedRect(const std::vector<SubPath>& path)
233
+ {
234
+ if (path.size() != 1) return false;
235
+ const std::vector<Point>& p = path[0].pts;
236
+ // 末尾が始点と重複する 5 点表現も許容する
237
+ size_t n = p.size();
238
+ if (n == 5 && std::abs(p[4].x - p[0].x) < 1e-6 && std::abs(p[4].y - p[0].y) < 1e-6) {
239
+ n = 4;
240
+ }
241
+ if (n != 4) return false;
242
+ // 各辺が水平または垂直であること
243
+ for (size_t i = 0; i < 4; ++i) {
244
+ const Point& a = p[i];
245
+ const Point& b = p[(i + 1) % 4];
246
+ const bool horizontal = std::abs(a.y - b.y) < 1e-6;
247
+ const bool vertical = std::abs(a.x - b.x) < 1e-6;
248
+ if (!horizontal && !vertical) return false;
249
+ }
250
+ return true;
251
+ }
252
+
253
+ // パスを nonzero winding でカバレッジマスク (surface 同寸, 1=内側) にラスタライズする。
254
+ // スキャンライン規約は FillPath と一致させる (塗りと同じ形にクリップされる)。
255
+ inline std::shared_ptr<std::vector<uint8_t>> RasterizePathMask(
256
+ const std::vector<SubPath>& path, int width, int height)
257
+ {
258
+ auto mask = std::make_shared<std::vector<uint8_t>>(
259
+ static_cast<size_t>(width) * height, 0);
260
+ if (width <= 0 || height <= 0) return mask;
261
+
262
+ double miny = 1e18, maxy = -1e18;
263
+ for (auto& sp : path) for (auto& p : sp.pts) { miny = std::min(miny,p.y); maxy = std::max(maxy,p.y); }
264
+ if (maxy < miny) return mask;
265
+ int y0 = std::max(0, static_cast<int>(std::floor(miny)));
266
+ int y1 = std::min(height - 1, static_cast<int>(std::ceil(maxy)));
267
+ for (int y = y0; y <= y1; ++y) {
268
+ double sy = y + 0.5;
269
+ std::vector<std::pair<double,int>> xs;
270
+ for (auto& sp : path) {
271
+ size_t n = sp.pts.size();
272
+ if (n < 2) continue;
273
+ for (size_t i = 0; i < n; ++i) {
274
+ Point a = sp.pts[i];
275
+ Point b = sp.pts[(i+1) % n];
276
+ if ((a.y <= sy && b.y > sy) || (b.y <= sy && a.y > sy)) {
277
+ double t = (sy - a.y) / (b.y - a.y);
278
+ xs.push_back({a.x + t * (b.x - a.x), a.y < b.y ? 1 : -1});
279
+ }
280
+ }
281
+ }
282
+ std::sort(xs.begin(), xs.end());
283
+ int wind = 0;
284
+ for (size_t i = 0; i + 1 < xs.size(); ++i) {
285
+ wind += xs[i].second;
286
+ if (wind != 0) {
287
+ int xa = std::max(0, static_cast<int>(std::floor(xs[i].first)));
288
+ int xb = std::min(width - 1, static_cast<int>(std::ceil(xs[i+1].first)));
289
+ for (int x = xa; x <= xb; ++x) {
290
+ (*mask)[static_cast<size_t>(y) * width + x] = 1;
291
+ }
292
+ }
293
+ }
294
+ }
295
+ return mask;
296
+ }
297
+
298
+ // 任意形状パスをクリップへ積集合で反映する (非矩形 clip() の実体)。
299
+ // 外接矩形 (高速リジェクト) も同時に反映し、既存 mask があれば AND を取って
300
+ // 新しい mask を割り当てる (save/restore 安全)。
301
+ inline void IntersectClipMask(ClipRect& clip, const std::vector<SubPath>& path,
302
+ int width, int height)
303
+ {
304
+ double minx, miny, maxx, maxy;
305
+ if (PathBounds(path, &minx, &miny, &maxx, &maxy)) {
306
+ IntersectClip(clip, std::floor(minx), std::floor(miny),
307
+ std::ceil(maxx), std::ceil(maxy));
308
+ }
309
+ auto next = RasterizePathMask(path, width, height);
310
+ if (clip.mask) {
311
+ const std::vector<uint8_t>& prev = *clip.mask;
312
+ const size_t n = std::min(next->size(), prev.size());
313
+ for (size_t i = 0; i < n; ++i) {
314
+ if (prev[i] == 0) { (*next)[i] = 0; }
315
+ }
316
+ }
317
+ clip.mask = next;
318
+ clip.mask_w = width;
319
+ clip.mask_h = height;
320
+ }
321
+
218
322
  // 3次ベジェを現在サブパスへフラット化して追記する。p0 は変換済みの直前点。
219
323
  inline void FlattenCubic(std::vector<Point>& out,
220
324
  Point p0, Point p1, Point p2, Point p3, int steps = 16)
@@ -15,15 +15,20 @@
15
15
  #include "v8/V8Util.h"
16
16
  #include "v8/WeakHandle.h"
17
17
 
18
+ #include <string>
19
+ #include <vector>
20
+
21
+ // Media Foundation はコンソール (Game Core OS) に無いデスクトップ専用 API。
22
+ // コンソールでは動画デコード未対応 (末尾の «CONSOLE» スタブ)。
23
+ // 実機での動画対応は devkit 段階の課題。
24
+ #if !NEXT2D_XBOX_CONSOLE
25
+
18
26
  #include <mfapi.h>
19
27
  #include <mfidl.h>
20
28
  #include <mfreadwrite.h>
21
29
  #include <shlwapi.h>
22
30
  #include <wrl/client.h>
23
31
 
24
- #include <string>
25
- #include <vector>
26
-
27
32
  #pragma comment(lib, "mfplat.lib")
28
33
  #pragma comment(lib, "mfreadwrite.lib")
29
34
  #pragma comment(lib, "mfuuid.lib")
@@ -322,3 +327,52 @@ v8::Local<v8::Object> CreateVideoElement(v8::Isolate* isolate, HostContext* /*ho
322
327
  }
323
328
 
324
329
  } // namespace next2d
330
+
331
+ #else // NEXT2D_XBOX_CONSOLE
332
+
333
+ // «CONSOLE» 動画デコード未対応スタブ。
334
+ // createElement('video') がクラッシュしないよう API 形状だけ提供する
335
+ // (フレーム取得は常に false、canPlayType は空 = 非対応を正しく通知)。
336
+ namespace next2d {
337
+
338
+ using v8util::Str;
339
+ using v8util::SetMethod;
340
+ using v8util::SetValue;
341
+
342
+ bool GetVideoFramePixels(v8::Isolate*, v8::Local<v8::Object>,
343
+ const uint8_t** out_rgba, uint32_t* out_width, uint32_t* out_height)
344
+ {
345
+ *out_rgba = nullptr;
346
+ *out_width = 0;
347
+ *out_height = 0;
348
+ return false;
349
+ }
350
+
351
+ v8::Local<v8::Object> CreateVideoElement(v8::Isolate* isolate, HostContext* /*host*/)
352
+ {
353
+ v8::Local<v8::Object> self = v8::Object::New(isolate);
354
+ SetValue(isolate, self, "__isVideoElement", v8::Boolean::New(isolate, true));
355
+ SetValue(isolate, self, "tagName", Str(isolate, "VIDEO"));
356
+ SetValue(isolate, self, "localName", Str(isolate, "video"));
357
+ SetValue(isolate, self, "videoWidth", v8::Integer::New(isolate, 0));
358
+ SetValue(isolate, self, "videoHeight", v8::Integer::New(isolate, 0));
359
+ SetValue(isolate, self, "paused", v8::Boolean::New(isolate, true));
360
+ SetValue(isolate, self, "ended", v8::Boolean::New(isolate, false));
361
+ InstallEventTarget(isolate, self);
362
+ SetMethod(isolate, self, "load", [](const v8::FunctionCallbackInfo<v8::Value>&) {});
363
+ SetMethod(isolate, self, "play", [](const v8::FunctionCallbackInfo<v8::Value>& a) {
364
+ v8::Isolate* iso = a.GetIsolate();
365
+ auto r = v8::Promise::Resolver::New(iso->GetCurrentContext()).ToLocalChecked();
366
+ r->Resolve(iso->GetCurrentContext(), v8::Undefined(iso)).Check();
367
+ a.GetReturnValue().Set(r->GetPromise());
368
+ });
369
+ SetMethod(isolate, self, "pause", [](const v8::FunctionCallbackInfo<v8::Value>&) {});
370
+ SetMethod(isolate, self, "canPlayType", [](const v8::FunctionCallbackInfo<v8::Value>& a) {
371
+ a.GetReturnValue().Set(Str(a.GetIsolate(), ""));
372
+ });
373
+ return self;
374
+ }
375
+
376
+ } // namespace next2d
377
+
378
+ #endif // !NEXT2D_XBOX_CONSOLE
@@ -8,6 +8,8 @@
8
8
  #include "gpu/DawnContext.h"
9
9
  #include "bindings/ImageSource.h"
10
10
 
11
+ #include "v8/V8Util.h"
12
+
11
13
  #include <cstring>
12
14
  #include <iostream>
13
15
  #include <map>
@@ -1211,9 +1213,13 @@ static void Device_CreateBindGroupLayout(const v8::FunctionCallbackInfo<v8::Valu
1211
1213
  v8::Local<v8::Object> d = args[0].As<v8::Object>();
1212
1214
 
1213
1215
  std::vector<wgpu::BindGroupLayoutEntry> entries;
1216
+ // externalTexture の nextInChain 構造体は CreateBindGroupLayout 完了まで生存が必要。
1217
+ // reserve でベクタ再割当を防ぎ、back() のアドレスを安定させる。
1218
+ std::vector<wgpu::ExternalTextureBindingLayout> ext_layouts;
1214
1219
  v8::Local<v8::Value> ev = Prop(isolate, d, "entries");
1215
1220
  if (ev->IsArray()) {
1216
1221
  auto arr = ev.As<v8::Array>();
1222
+ ext_layouts.reserve(arr->Length());
1217
1223
  for (uint32_t i = 0; i < arr->Length(); ++i) {
1218
1224
  v8::Local<v8::Value> v;
1219
1225
  if (!arr->Get(ctx, i).ToLocal(&v) || !v->IsObject()) continue;
@@ -1237,8 +1243,21 @@ static void Device_CreateBindGroupLayout(const v8::FunctionCallbackInfo<v8::Valu
1237
1243
  ? ToTextureViewDimension(webgpu::Str(isolate, t, "viewDimension"))
1238
1244
  : wgpu::TextureViewDimension::e2D;
1239
1245
  entry.texture.multisampled = Bool(isolate, t, "multisampled", false);
1246
+ } else if (HasProp(isolate, o, "storageTexture")) {
1247
+ auto st = Prop(isolate, o, "storageTexture").As<v8::Object>();
1248
+ entry.storageTexture.access = HasProp(isolate, st, "access")
1249
+ ? ToStorageTextureAccess(webgpu::Str(isolate, st, "access"))
1250
+ : wgpu::StorageTextureAccess::WriteOnly;
1251
+ entry.storageTexture.format = ToTextureFormat(webgpu::Str(isolate, st, "format"));
1252
+ entry.storageTexture.viewDimension = HasProp(isolate, st, "viewDimension")
1253
+ ? ToTextureViewDimension(webgpu::Str(isolate, st, "viewDimension"))
1254
+ : wgpu::TextureViewDimension::e2D;
1255
+ } else if (HasProp(isolate, o, "externalTexture")) {
1256
+ // GPUExternalTextureBindingLayout は空 (メンバ無し)。sType は
1257
+ // Dawn の C++ ラッパが既定で設定するため emplace してチェーンするだけでよい。
1258
+ ext_layouts.emplace_back();
1259
+ entry.nextInChain = &ext_layouts.back();
1240
1260
  }
1241
- // «EXTEND» storageTexture / externalTexture は必要に応じて追加
1242
1261
  entries.push_back(entry);
1243
1262
  }
1244
1263
  }
@@ -1347,6 +1366,11 @@ struct RenderPipelineScratch {
1347
1366
  // ConstantEntry.key が指すため CreateRenderPipeline 完了まで生存させる。
1348
1367
  std::vector<std::string> vs_const_keys, fs_const_keys;
1349
1368
  std::vector<wgpu::ConstantEntry> vs_constants, fs_constants;
1369
+ // desc.depthStencil / desc.fragment はこれらのアドレスを指す。ディスクリプタ構築を
1370
+ // ヘルパに切り出したため、ローカルだとヘルパ return でダングリング化する。scratch に
1371
+ // 保持して CreateRenderPipeline[Async] が同期コピーするまで生存させる。
1372
+ wgpu::DepthStencilState depth = {};
1373
+ wgpu::FragmentState fragment = {};
1350
1374
  };
1351
1375
 
1352
1376
  // { "KEY": number|bool, ... } を wgpu::ConstantEntry 配列へ変換する。
@@ -1472,7 +1496,8 @@ static void FillRenderPipelineDescriptor(v8::Isolate* isolate, v8::Local<v8::Obj
1472
1496
  }
1473
1497
 
1474
1498
  // depthStencil (マスク処理は stencil8 + stencilFront/Back を使う)
1475
- wgpu::DepthStencilState depth = {};
1499
+ // scratch.depth を参照 (アドレスを desc.depthStencil に格納するため寿命を延ばす)。
1500
+ wgpu::DepthStencilState& depth = scratch.depth;
1476
1501
  v8::Local<v8::Value> dsv = Prop(isolate, d, "depthStencil");
1477
1502
  if (dsv->IsObject()) {
1478
1503
  auto ds = dsv.As<v8::Object>();
@@ -1532,7 +1557,8 @@ static void FillRenderPipelineDescriptor(v8::Isolate* isolate, v8::Local<v8::Obj
1532
1557
  }
1533
1558
 
1534
1559
  // fragment
1535
- wgpu::FragmentState fragment = {};
1560
+ // scratch.fragment を参照 (アドレスを desc.fragment に格納するため寿命を延ばす)。
1561
+ wgpu::FragmentState& fragment = scratch.fragment;
1536
1562
  v8::Local<v8::Value> fv = Prop(isolate, d, "fragment");
1537
1563
  if (fv->IsObject()) {
1538
1564
  auto f = fv.As<v8::Object>();
@@ -61,12 +61,15 @@ inline v8::Local<v8::ObjectTemplate> HandleTemplate(v8::Isolate* isolate)
61
61
  }
62
62
 
63
63
  // --- JS オブジェクト読み取りヘルパー ------------------------------------
64
+ // プロパティ名はキャッシュ済み internalized string (v8util::PropName) を使う。
65
+ // ここは全ディスクリプタ解析の合流点で毎フレーム数千回通るため、名前 v8::String の
66
+ // 毎回生成 (ヒープ確保 + ハッシュ) を排除する効果が大きい。
64
67
  inline v8::Local<v8::Value> Prop(v8::Isolate* isolate, v8::Local<v8::Object> obj,
65
68
  const char* key)
66
69
  {
67
70
  v8::Local<v8::Context> ctx = isolate->GetCurrentContext();
68
71
  v8::Local<v8::Value> v;
69
- if (obj->Get(ctx, v8util::Str(isolate, key)).ToLocal(&v)) {
72
+ if (obj->Get(ctx, v8util::PropName(isolate, key)).ToLocal(&v)) {
70
73
  return v;
71
74
  }
72
75
  return v8::Undefined(isolate);
@@ -17,22 +17,54 @@ inline wgpu::TextureFormat ToTextureFormat(std::string_view s)
17
17
  if (s == "bgra8unorm") return F::BGRA8Unorm;
18
18
  if (s == "bgra8unorm-srgb") return F::BGRA8UnormSrgb;
19
19
  if (s == "rgba8snorm") return F::RGBA8Snorm;
20
+ if (s == "rgba8uint") return F::RGBA8Uint;
21
+ if (s == "rgba8sint") return F::RGBA8Sint;
20
22
  if (s == "r8unorm") return F::R8Unorm;
23
+ if (s == "r8snorm") return F::R8Snorm;
24
+ if (s == "r8uint") return F::R8Uint;
25
+ if (s == "r8sint") return F::R8Sint;
21
26
  if (s == "rg8unorm") return F::RG8Unorm;
27
+ if (s == "rg8snorm") return F::RG8Snorm;
28
+ if (s == "rg8uint") return F::RG8Uint;
29
+ if (s == "rg8sint") return F::RG8Sint;
30
+ if (s == "r16uint") return F::R16Uint;
31
+ if (s == "r16sint") return F::R16Sint;
22
32
  if (s == "r16float") return F::R16Float;
33
+ if (s == "rg16uint") return F::RG16Uint;
34
+ if (s == "rg16sint") return F::RG16Sint;
23
35
  if (s == "rg16float") return F::RG16Float;
36
+ if (s == "rgba16uint") return F::RGBA16Uint;
37
+ if (s == "rgba16sint") return F::RGBA16Sint;
24
38
  if (s == "rgba16float") return F::RGBA16Float;
39
+ if (s == "r32uint") return F::R32Uint;
40
+ if (s == "r32sint") return F::R32Sint;
25
41
  if (s == "r32float") return F::R32Float;
42
+ if (s == "rg32uint") return F::RG32Uint;
43
+ if (s == "rg32sint") return F::RG32Sint;
26
44
  if (s == "rg32float") return F::RG32Float;
45
+ if (s == "rgba32uint") return F::RGBA32Uint;
46
+ if (s == "rgba32sint") return F::RGBA32Sint;
27
47
  if (s == "rgba32float") return F::RGBA32Float;
48
+ if (s == "rgb10a2unorm") return F::RGB10A2Unorm;
49
+ if (s == "rg11b10ufloat") return F::RG11B10Ufloat;
28
50
  if (s == "depth16unorm") return F::Depth16Unorm;
29
51
  if (s == "depth24plus") return F::Depth24Plus;
30
52
  if (s == "depth24plus-stencil8") return F::Depth24PlusStencil8;
31
53
  if (s == "depth32float") return F::Depth32Float;
54
+ if (s == "depth32float-stencil8") return F::Depth32FloatStencil8;
32
55
  if (s == "stencil8") return F::Stencil8;
33
56
  return F::RGBA8Unorm;
34
57
  }
35
58
 
59
+ inline wgpu::StorageTextureAccess ToStorageTextureAccess(std::string_view s)
60
+ {
61
+ using A = wgpu::StorageTextureAccess;
62
+ if (s == "write-only") return A::WriteOnly;
63
+ if (s == "read-only") return A::ReadOnly;
64
+ if (s == "read-write") return A::ReadWrite;
65
+ return A::WriteOnly;
66
+ }
67
+
36
68
  inline wgpu::VertexFormat ToVertexFormat(std::string_view s)
37
69
  {
38
70
  using V = wgpu::VertexFormat;
@@ -1,22 +1,160 @@
1
1
  #include "DawnContext.h"
2
2
 
3
3
  #include <Windows.h>
4
+ #include <chrono>
5
+ #include <cstdint>
6
+ #include <cstdio>
7
+ #include <cstdlib>
8
+ #include <filesystem>
9
+ #include <fstream>
4
10
  #include <iostream>
11
+ #include <string>
5
12
 
6
13
  namespace next2d {
7
14
 
8
15
  namespace {
9
16
 
17
+ // GUI 実行では stderr が見えないため、GPU の致命的エラー/検証エラーを
18
+ // next2d-error.log にも残す。描画が黒くなる/表示されない等の原因(検証エラー・
19
+ // device lost・リソース枯渇)を掴む唯一の手段。氾濫防止に総数を制限する。
20
+ void AppendGpuLog(const std::string& line)
21
+ {
22
+ static int count = 0;
23
+ if (count >= 60) {
24
+ return;
25
+ }
26
+ ++count;
27
+ std::ofstream ofs("next2d-error.log", std::ios::app);
28
+ if (ofs) {
29
+ ofs << line << std::endl;
30
+ }
31
+ }
32
+
10
33
  void HandleDeviceLost(const wgpu::Device&, wgpu::DeviceLostReason reason, wgpu::StringView message)
11
34
  {
12
- std::cerr << "[Dawn] Device lost (" << static_cast<int>(reason) << "): "
13
- << std::string(message.data, message.length) << std::endl;
35
+ const std::string line = "[Dawn] Device lost (" + std::to_string(static_cast<int>(reason))
36
+ + "): " + std::string(message.data, message.length);
37
+ std::cerr << line << std::endl;
38
+ AppendGpuLog(line);
14
39
  }
15
40
 
16
41
  void HandleUncapturedError(const wgpu::Device&, wgpu::ErrorType type, wgpu::StringView message)
17
42
  {
18
- std::cerr << "[Dawn] Uncaptured error (" << static_cast<int>(type) << "): "
19
- << std::string(message.data, message.length) << std::endl;
43
+ const std::string line = "[Dawn] Uncaptured error (" + std::to_string(static_cast<int>(type))
44
+ + "): " + std::string(message.data, message.length);
45
+ std::cerr << line << std::endl;
46
+ AppendGpuLog(line);
47
+ }
48
+
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
+ }
20
158
  }
21
159
 
22
160
  } // namespace
@@ -95,6 +233,28 @@ bool DawnContext::AcquireDevice()
95
233
  device_desc.SetDeviceLostCallback(wgpu::CallbackMode::AllowProcessEvents, HandleDeviceLost);
96
234
  device_desc.SetUncapturedErrorCallback(HandleUncapturedError);
97
235
 
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
+
244
+ #ifdef NDEBUG
245
+ // Release では Dawn の API 検証 (skip_validation) と robustness (OOB クランプ) を
246
+ // 無効化する。両方とも全描画コマンドのエンコードで毎フレーム CPU を消費する
247
+ // 安全網で、検証済みタイトルの出荷ビルドでは Chrome も同様に外す定番最適化。
248
+ // Debug ビルドでは有効のままにして検証エラーを検出できるようにする
249
+ // (エラーは HandleUncapturedError -> next2d-error.log へ出る)。
250
+ // toggle 名は Dawn 本体 src/dawn/native/Toggles.cpp 定義 (いずれも ToggleStage::Device)。
251
+ static const char* kReleaseToggles[] = {"skip_validation", "disable_robustness"};
252
+ wgpu::DawnTogglesDescriptor device_toggles = {};
253
+ device_toggles.enabledToggles = kReleaseToggles;
254
+ device_toggles.enabledToggleCount = 2;
255
+ cache_desc.nextInChain = &device_toggles; // device -> cache -> toggles
256
+ #endif
257
+
98
258
  wgpu::Future device_future = adapter_.RequestDevice(
99
259
  &device_desc,
100
260
  wgpu::CallbackMode::AllowProcessEvents,
@@ -137,11 +297,10 @@ void DawnContext::Configure(uint32_t width, uint32_t height)
137
297
  wgpu::SurfaceConfiguration config = {};
138
298
  config.device = device_;
139
299
  config.format = format_;
300
+ // RenderAttachment のみ。CopySrc を足すと swapchain の flip 提示最適化が無効化され
301
+ // 提示ごとにコピーが挟まって重くなるため、描画確認が済んだ今は付けない
302
+ // (以前は DebugProbeSurface の黒画面調査用に付けていた)。
140
303
  config.usage = wgpu::TextureUsage::RenderAttachment;
141
- if (can_copy_src_) {
142
- // 黒画面デバッグ用の surface 読み戻し (DebugProbeSurface) を可能にする
143
- config.usage |= wgpu::TextureUsage::CopySrc;
144
- }
145
304
  config.width = width_;
146
305
  config.height = height_;
147
306
  config.presentMode = wgpu::PresentMode::Fifo;
@@ -231,17 +390,9 @@ void DawnContext::Present()
231
390
  // 取得なしで Present すると Dawn の検証エラーになる (描画しないフレームは提示しない)。
232
391
  if (surface_ && configured_ && frame_texture_acquired_) {
233
392
  ++present_count_;
234
- if (present_count_ <= 3 || present_count_ == 60 || present_count_ % 300 == 0) {
235
- DebugProbeSurface();
236
- }
237
393
  surface_.Present();
238
394
  } else {
239
395
  ++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
- }
245
396
  }
246
397
  frame_texture_acquired_ = false;
247
398
  current_texture_ = nullptr;