@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.
Files changed (75) 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 -754
  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 +12 -9
  19. package/templates/xbox/CMakeLists.txt +116 -11
  20. package/templates/xbox/README.md +34 -8
  21. package/templates/xbox/cmake/FetchDawn.cmake +47 -6
  22. package/templates/xbox/cmake/FindV8.cmake +15 -0
  23. package/templates/xbox/js/bootstrap.js +151 -28
  24. package/templates/xbox/js/selftest.js +740 -90
  25. package/templates/xbox/src/AssetLoader.cpp +115 -12
  26. package/templates/xbox/src/AssetLoader.h +4 -0
  27. package/templates/xbox/src/EmbeddedAssets.cpp +149 -0
  28. package/templates/xbox/src/EmbeddedAssets.h +60 -0
  29. package/templates/xbox/src/EventLoop.cpp +13 -1
  30. package/templates/xbox/src/EventLoop.h +12 -0
  31. package/templates/xbox/src/bindings/Audio.cpp +211 -31
  32. package/templates/xbox/src/bindings/Bindings.h +10 -0
  33. package/templates/xbox/src/bindings/Canvas.cpp +77 -2
  34. package/templates/xbox/src/bindings/Canvas2D.cpp +496 -43
  35. package/templates/xbox/src/bindings/Console.cpp +6 -1
  36. package/templates/xbox/src/bindings/DomShims.cpp +143 -0
  37. package/templates/xbox/src/bindings/EventTarget.cpp +11 -0
  38. package/templates/xbox/src/bindings/Fetch.cpp +2 -8
  39. package/templates/xbox/src/bindings/Image.cpp +243 -15
  40. package/templates/xbox/src/bindings/Polyfills.cpp +1226 -0
  41. package/templates/xbox/src/bindings/RasterCore.h +105 -1
  42. package/templates/xbox/src/bindings/Video.cpp +153 -10
  43. package/templates/xbox/src/bindings/webgpu/WebGPU.cpp +844 -18
  44. package/templates/xbox/src/bindings/webgpu/WebGPUCommon.h +7 -9
  45. package/templates/xbox/src/bindings/webgpu/WebGPUEnums.h +32 -0
  46. package/templates/xbox/src/gpu/DawnContext.cpp +245 -7
  47. package/templates/xbox/src/gpu/DawnContext.h +18 -0
  48. package/templates/xbox/src/main.cpp +459 -8
  49. package/templates/xbox/src/platform/AudioDecoder.cpp +121 -0
  50. package/templates/xbox/src/platform/AudioDecoder.h +18 -0
  51. package/templates/xbox/src/platform/AudioEngine.cpp +37 -6
  52. package/templates/xbox/src/platform/DecodeQueue.cpp +136 -0
  53. package/templates/xbox/src/platform/DecodeQueue.h +27 -0
  54. package/templates/xbox/src/platform/ImageDecoder.cpp +44 -0
  55. package/templates/xbox/src/platform/ImageDecoder.h +18 -0
  56. package/templates/xbox/src/platform/StbTextRasterizer.cpp +324 -0
  57. package/templates/xbox/src/platform/StbTextRasterizer.h +37 -0
  58. package/templates/xbox/src/platform/TextRasterizer.cpp +32 -0
  59. package/templates/xbox/src/platform/WicDecoder.cpp +19 -0
  60. package/templates/xbox/src/v8/CodeCache.h +223 -0
  61. package/templates/xbox/src/v8/V8Runtime.cpp +76 -11
  62. package/templates/xbox/src/v8/V8Util.h +79 -0
  63. package/templates/xbox/src/v8/WeakHandle.h +45 -0
  64. package/templates/xbox/src/worker/WorkerRuntime.cpp +432 -48
  65. package/templates/xbox/src/worker/WorkerRuntime.h +43 -13
  66. package/templates/xbox/tests/check_local.sh +7 -2
  67. package/templates/xbox/tests/gamecore_api_probe.cpp +172 -0
  68. package/templates/xbox/tests/raster_test.cpp +50 -0
  69. package/templates/xbox/tests/windows_platform_test.cpp +75 -3
  70. package/templates/xbox/third_party/LICENSES.md +14 -0
  71. package/templates/xbox/third_party/dr_mp3.h +5412 -0
  72. package/templates/xbox/third_party/dr_wav.h +9131 -0
  73. package/templates/xbox/third_party/stb_image.h +7988 -0
  74. package/templates/xbox/third_party/stb_truetype.h +5079 -0
  75. package/templates/xbox/third_party/stb_vorbis.c +5584 -0
@@ -8,7 +8,10 @@
8
8
  #include "gpu/DawnContext.h"
9
9
  #include "bindings/ImageSource.h"
10
10
 
11
+ #include "v8/V8Util.h"
12
+
11
13
  #include <cstring>
14
+ #include <iostream>
12
15
  #include <map>
13
16
  #include <vector>
14
17
 
@@ -24,7 +27,8 @@ struct Templates {
24
27
  v8::Global<v8::ObjectTemplate> adapter, device, queue, buffer, texture,
25
28
  textureView, sampler, shaderModule, bindGroupLayout, bindGroup,
26
29
  pipelineLayout, renderPipeline, commandEncoder, renderPass,
27
- commandBuffer, canvasContext;
30
+ commandBuffer, canvasContext, computePipeline, computePass,
31
+ querySet, renderBundle, renderBundleEncoder;
28
32
  };
29
33
 
30
34
  static std::map<v8::Isolate*, Templates> g_templates;
@@ -47,6 +51,9 @@ static v8::Local<v8::Object> WrapTexture(v8::Isolate*, wgpu::Texture);
47
51
  static v8::Local<v8::Object> WrapTextureView(v8::Isolate*, wgpu::TextureView);
48
52
  static v8::Local<v8::Object> WrapEncoder(v8::Isolate*, wgpu::CommandEncoder);
49
53
  static v8::Local<v8::Object> WrapPipeline(v8::Isolate*, wgpu::RenderPipeline);
54
+ static v8::Local<v8::Object> WrapComputePipeline(v8::Isolate*, wgpu::ComputePipeline);
55
+ static v8::Local<v8::Object> WrapQuerySet(v8::Isolate*, wgpu::QuerySet);
56
+ static v8::Local<v8::Object> WrapRenderBundle(v8::Isolate*, wgpu::RenderBundle);
50
57
 
51
58
  // ---------------------------------------------------------------------------
52
59
  // GPUBuffer
@@ -63,15 +70,25 @@ static void Buffer_GetMappedRange(const v8::FunctionCallbackInfo<v8::Value>& arg
63
70
  : (buffer.GetSize() - offset);
64
71
 
65
72
  void* ptr = buffer.GetMappedRange(offset, static_cast<size_t>(size));
66
- if (!ptr) {
73
+ if (ptr) {
74
+ // 書き込み可能マップ: マップ領域を指す非所有 ArrayBuffer (unmap まで有効)
75
+ std::unique_ptr<v8::BackingStore> store = v8::ArrayBuffer::NewBackingStore(
76
+ ptr, static_cast<size_t>(size),
77
+ [](void*, size_t, void*) {}, nullptr);
78
+ args.GetReturnValue().Set(v8::ArrayBuffer::New(isolate, std::move(store)));
79
+ return;
80
+ }
81
+
82
+ // MapMode::Read でマップされたバッファは非 const 版が nullptr を返す仕様。
83
+ // GetConstMappedRange から所有コピーを返す (読み戻し copyTextureToBuffer 等の経路)。
84
+ const void* cptr = buffer.GetConstMappedRange(offset, static_cast<size_t>(size));
85
+ if (!cptr) {
67
86
  args.GetReturnValue().Set(v8::ArrayBuffer::New(isolate, 0));
68
87
  return;
69
88
  }
70
- // マップ領域を指す非所有 ArrayBuffer (unmap まで有効)
71
- std::unique_ptr<v8::BackingStore> store = v8::ArrayBuffer::NewBackingStore(
72
- ptr, static_cast<size_t>(size),
73
- [](void*, size_t, void*) {}, nullptr);
74
- args.GetReturnValue().Set(v8::ArrayBuffer::New(isolate, std::move(store)));
89
+ v8::Local<v8::ArrayBuffer> ab = v8::ArrayBuffer::New(isolate, static_cast<size_t>(size));
90
+ memcpy(ab->GetBackingStore()->Data(), cptr, static_cast<size_t>(size));
91
+ args.GetReturnValue().Set(ab);
75
92
  }
76
93
 
77
94
  static void Buffer_Unmap(const v8::FunctionCallbackInfo<v8::Value>& args)
@@ -219,6 +236,55 @@ static wgpu::Extent3D ParseExtent(v8::Isolate* isolate, v8::Local<v8::Value> val
219
236
  return extent;
220
237
  }
221
238
 
239
+ // GPUOrigin3D は {x,y,z} オブジェクト or [x,y,z] 配列。ParseExtent とは
240
+ // キー名が違う (width/height ではなく x/y/z、既定は 1 ではなく 0) ため専用に扱う。
241
+ static wgpu::Origin3D ParseOrigin(v8::Isolate* isolate, v8::Local<v8::Value> value)
242
+ {
243
+ wgpu::Origin3D origin = {0, 0, 0};
244
+ if (value->IsArray()) {
245
+ auto arr = value.As<v8::Array>();
246
+ v8::Local<v8::Context> ctx = isolate->GetCurrentContext();
247
+ auto get = [&](uint32_t i) -> uint32_t {
248
+ v8::Local<v8::Value> v;
249
+ return (i < arr->Length() && arr->Get(ctx, i).ToLocal(&v) && v->IsNumber())
250
+ ? static_cast<uint32_t>(v.As<v8::Number>()->Value()) : 0;
251
+ };
252
+ origin.x = get(0);
253
+ origin.y = get(1);
254
+ origin.z = get(2);
255
+ } else if (value->IsObject()) {
256
+ auto o = value.As<v8::Object>();
257
+ origin.x = U32(isolate, o, "x", 0);
258
+ origin.y = U32(isolate, o, "y", 0);
259
+ origin.z = U32(isolate, o, "z", 0);
260
+ }
261
+ return origin;
262
+ }
263
+
264
+ static wgpu::ErrorFilter ToErrorFilter(std::string_view s)
265
+ {
266
+ if (s == "out-of-memory") return wgpu::ErrorFilter::OutOfMemory;
267
+ if (s == "internal") return wgpu::ErrorFilter::Internal;
268
+ return wgpu::ErrorFilter::Validation;
269
+ }
270
+
271
+ static wgpu::QueryType ToQueryType(std::string_view s)
272
+ {
273
+ if (s == "timestamp") return wgpu::QueryType::Timestamp;
274
+ return wgpu::QueryType::Occlusion;
275
+ }
276
+
277
+ // wgpu::ErrorType -> WebGPU の GPUError 種別文字列
278
+ static const char* ErrorTypeName(wgpu::ErrorType t)
279
+ {
280
+ switch (t) {
281
+ case wgpu::ErrorType::Validation: return "validation";
282
+ case wgpu::ErrorType::OutOfMemory: return "out-of-memory";
283
+ case wgpu::ErrorType::Internal: return "internal";
284
+ default: return "unknown";
285
+ }
286
+ }
287
+
222
288
  static void Queue_WriteTexture(const v8::FunctionCallbackInfo<v8::Value>& args)
223
289
  {
224
290
  v8::Isolate* isolate = args.GetIsolate();
@@ -229,6 +295,11 @@ static void Queue_WriteTexture(const v8::FunctionCallbackInfo<v8::Value>& args)
229
295
  wgpu::TexelCopyTextureInfo copy = {};
230
296
  copy.texture = Unwrap<wgpu::Texture>(Prop(isolate, dest, "texture").As<v8::Object>());
231
297
  copy.mipLevel = U32(isolate, dest, "mipLevel", 0);
298
+ // 宛先 origin (アトラスのセル位置など)。未指定なら (0,0,0)。
299
+ v8::Local<v8::Value> dst_origin = Prop(isolate, dest, "origin");
300
+ if (dst_origin->IsObject() || dst_origin->IsArray()) {
301
+ copy.origin = ParseOrigin(isolate, dst_origin);
302
+ }
232
303
 
233
304
  const void* data = nullptr;
234
305
  size_t length = 0;
@@ -313,6 +384,12 @@ static void Queue_Submit(const v8::FunctionCallbackInfo<v8::Value>& args)
313
384
  }
314
385
  }
315
386
  }
387
+ static uint64_t count = 0;
388
+ ++count;
389
+ if (count <= 10 || count % 300 == 0) {
390
+ std::cerr << "[GPU] queue.submit #" << count
391
+ << " (" << buffers.size() << " cmd)" << std::endl;
392
+ }
316
393
  queue.Submit(buffers.size(), buffers.data());
317
394
  }
318
395
 
@@ -460,6 +537,58 @@ static void Pass_End(const v8::FunctionCallbackInfo<v8::Value>& args)
460
537
  Unwrap<wgpu::RenderPassEncoder>(args.This()).End();
461
538
  }
462
539
 
540
+ static void Pass_BeginOcclusionQuery(const v8::FunctionCallbackInfo<v8::Value>& args)
541
+ {
542
+ auto& pass = Unwrap<wgpu::RenderPassEncoder>(args.This());
543
+ uint32_t index = args.Length() > 0 && args[0]->IsNumber()
544
+ ? static_cast<uint32_t>(args[0].As<v8::Number>()->Value()) : 0;
545
+ pass.BeginOcclusionQuery(index);
546
+ }
547
+
548
+ static void Pass_EndOcclusionQuery(const v8::FunctionCallbackInfo<v8::Value>& args)
549
+ {
550
+ Unwrap<wgpu::RenderPassEncoder>(args.This()).EndOcclusionQuery();
551
+ }
552
+
553
+ static void Pass_ExecuteBundles(const v8::FunctionCallbackInfo<v8::Value>& args)
554
+ {
555
+ v8::Isolate* isolate = args.GetIsolate();
556
+ auto& pass = Unwrap<wgpu::RenderPassEncoder>(args.This());
557
+ std::vector<wgpu::RenderBundle> bundles;
558
+ if (args.Length() > 0 && args[0]->IsArray()) {
559
+ auto arr = args[0].As<v8::Array>();
560
+ v8::Local<v8::Context> ctx = isolate->GetCurrentContext();
561
+ for (uint32_t i = 0; i < arr->Length(); ++i) {
562
+ v8::Local<v8::Value> v;
563
+ if (arr->Get(ctx, i).ToLocal(&v) && v->IsObject())
564
+ bundles.push_back(Unwrap<wgpu::RenderBundle>(v.As<v8::Object>()));
565
+ }
566
+ }
567
+ pass.ExecuteBundles(bundles.size(), bundles.data());
568
+ }
569
+
570
+ // pushDebugGroup / popDebugGroup / insertDebugMarker (render pass)
571
+ static void Pass_PushDebugGroup(const v8::FunctionCallbackInfo<v8::Value>& args)
572
+ {
573
+ v8::Isolate* isolate = args.GetIsolate();
574
+ std::string label = args.Length() > 0 ? v8util::ToStdString(isolate, args[0]) : "";
575
+ Unwrap<wgpu::RenderPassEncoder>(args.This()).PushDebugGroup(
576
+ wgpu::StringView(label.c_str(), label.size()));
577
+ }
578
+
579
+ static void Pass_PopDebugGroup(const v8::FunctionCallbackInfo<v8::Value>& args)
580
+ {
581
+ Unwrap<wgpu::RenderPassEncoder>(args.This()).PopDebugGroup();
582
+ }
583
+
584
+ static void Pass_InsertDebugMarker(const v8::FunctionCallbackInfo<v8::Value>& args)
585
+ {
586
+ v8::Isolate* isolate = args.GetIsolate();
587
+ std::string label = args.Length() > 0 ? v8util::ToStdString(isolate, args[0]) : "";
588
+ Unwrap<wgpu::RenderPassEncoder>(args.This()).InsertDebugMarker(
589
+ wgpu::StringView(label.c_str(), label.size()));
590
+ }
591
+
463
592
  static v8::Local<v8::Object> WrapRenderPass(v8::Isolate* isolate, wgpu::RenderPassEncoder pass)
464
593
  {
465
594
  v8::Local<v8::Object> obj = WrapWith(isolate, Tmpl(isolate).renderPass, std::move(pass));
@@ -474,10 +603,120 @@ static v8::Local<v8::Object> WrapRenderPass(v8::Isolate* isolate, wgpu::RenderPa
474
603
  SetMethod(isolate, obj, "draw", Pass_Draw);
475
604
  SetMethod(isolate, obj, "drawIndexed", Pass_DrawIndexed);
476
605
  SetMethod(isolate, obj, "drawIndirect", Pass_DrawIndirect);
606
+ SetMethod(isolate, obj, "beginOcclusionQuery", Pass_BeginOcclusionQuery);
607
+ SetMethod(isolate, obj, "endOcclusionQuery", Pass_EndOcclusionQuery);
608
+ SetMethod(isolate, obj, "executeBundles", Pass_ExecuteBundles);
609
+ SetMethod(isolate, obj, "pushDebugGroup", Pass_PushDebugGroup);
610
+ SetMethod(isolate, obj, "popDebugGroup", Pass_PopDebugGroup);
611
+ SetMethod(isolate, obj, "insertDebugMarker", Pass_InsertDebugMarker);
477
612
  SetMethod(isolate, obj, "end", Pass_End);
478
613
  return obj;
479
614
  }
480
615
 
616
+ // ---------------------------------------------------------------------------
617
+ // GPUComputePassEncoder
618
+ // compute シェーダ経路 (createComputePipeline / beginComputePass / dispatch)。
619
+ // Next2D の 2D ラスタライザは未使用だが、WebGPU の機能を特定用途に絞らず
620
+ // 全面的に利用可能にするため実装する。汎用 GPGPU / storage buffer 出力に対応。
621
+ // ---------------------------------------------------------------------------
622
+ static void CPass_SetPipeline(const v8::FunctionCallbackInfo<v8::Value>& args)
623
+ {
624
+ Unwrap<wgpu::ComputePassEncoder>(args.This()).SetPipeline(
625
+ Unwrap<wgpu::ComputePipeline>(args[0].As<v8::Object>()));
626
+ }
627
+
628
+ static void CPass_SetBindGroup(const v8::FunctionCallbackInfo<v8::Value>& args)
629
+ {
630
+ v8::Isolate* isolate = args.GetIsolate();
631
+ auto& pass = Unwrap<wgpu::ComputePassEncoder>(args.This());
632
+ uint32_t index = static_cast<uint32_t>(args[0].As<v8::Number>()->Value());
633
+ if (args.Length() < 2 || !args[1]->IsObject()) {
634
+ pass.SetBindGroup(index, nullptr);
635
+ return;
636
+ }
637
+ auto& group = Unwrap<wgpu::BindGroup>(args[1].As<v8::Object>());
638
+
639
+ // dynamicOffsets (render pass と同じく Array<number> / Uint32Array)
640
+ std::vector<uint32_t> offsets;
641
+ if (args.Length() > 2 && !args[2]->IsNullOrUndefined()) {
642
+ v8::Local<v8::Context> ctx = isolate->GetCurrentContext();
643
+ if (args[2]->IsArray()) {
644
+ auto arr = args[2].As<v8::Array>();
645
+ offsets.reserve(arr->Length());
646
+ for (uint32_t i = 0; i < arr->Length(); ++i) {
647
+ v8::Local<v8::Value> v;
648
+ if (arr->Get(ctx, i).ToLocal(&v) && v->IsNumber())
649
+ offsets.push_back(static_cast<uint32_t>(v.As<v8::Number>()->Value()));
650
+ }
651
+ } else if (args[2]->IsUint32Array()) {
652
+ auto ta = args[2].As<v8::Uint32Array>();
653
+ offsets.resize(ta->Length());
654
+ ta->CopyContents(offsets.data(), offsets.size() * sizeof(uint32_t));
655
+ }
656
+ }
657
+ pass.SetBindGroup(index, group,
658
+ static_cast<uint32_t>(offsets.size()),
659
+ offsets.empty() ? nullptr : offsets.data());
660
+ }
661
+
662
+ static void CPass_DispatchWorkgroups(const v8::FunctionCallbackInfo<v8::Value>& args)
663
+ {
664
+ auto& pass = Unwrap<wgpu::ComputePassEncoder>(args.This());
665
+ auto n = [&](int i, uint32_t fb) {
666
+ return (i < args.Length() && args[i]->IsNumber())
667
+ ? static_cast<uint32_t>(args[i].As<v8::Number>()->Value()) : fb; };
668
+ pass.DispatchWorkgroups(n(0, 1), n(1, 1), n(2, 1));
669
+ }
670
+
671
+ static void CPass_DispatchWorkgroupsIndirect(const v8::FunctionCallbackInfo<v8::Value>& args)
672
+ {
673
+ auto& pass = Unwrap<wgpu::ComputePassEncoder>(args.This());
674
+ auto& buffer = Unwrap<wgpu::Buffer>(args[0].As<v8::Object>());
675
+ uint64_t offset = args.Length() > 1 && args[1]->IsNumber()
676
+ ? static_cast<uint64_t>(args[1].As<v8::Number>()->Value()) : 0;
677
+ pass.DispatchWorkgroupsIndirect(buffer, offset);
678
+ }
679
+
680
+ static void CPass_End(const v8::FunctionCallbackInfo<v8::Value>& args)
681
+ {
682
+ Unwrap<wgpu::ComputePassEncoder>(args.This()).End();
683
+ }
684
+
685
+ static void CPass_PushDebugGroup(const v8::FunctionCallbackInfo<v8::Value>& args)
686
+ {
687
+ v8::Isolate* isolate = args.GetIsolate();
688
+ std::string label = args.Length() > 0 ? v8util::ToStdString(isolate, args[0]) : "";
689
+ Unwrap<wgpu::ComputePassEncoder>(args.This()).PushDebugGroup(
690
+ wgpu::StringView(label.c_str(), label.size()));
691
+ }
692
+
693
+ static void CPass_PopDebugGroup(const v8::FunctionCallbackInfo<v8::Value>& args)
694
+ {
695
+ Unwrap<wgpu::ComputePassEncoder>(args.This()).PopDebugGroup();
696
+ }
697
+
698
+ static void CPass_InsertDebugMarker(const v8::FunctionCallbackInfo<v8::Value>& args)
699
+ {
700
+ v8::Isolate* isolate = args.GetIsolate();
701
+ std::string label = args.Length() > 0 ? v8util::ToStdString(isolate, args[0]) : "";
702
+ Unwrap<wgpu::ComputePassEncoder>(args.This()).InsertDebugMarker(
703
+ wgpu::StringView(label.c_str(), label.size()));
704
+ }
705
+
706
+ static v8::Local<v8::Object> WrapComputePass(v8::Isolate* isolate, wgpu::ComputePassEncoder pass)
707
+ {
708
+ v8::Local<v8::Object> obj = WrapWith(isolate, Tmpl(isolate).computePass, std::move(pass));
709
+ SetMethod(isolate, obj, "setPipeline", CPass_SetPipeline);
710
+ SetMethod(isolate, obj, "setBindGroup", CPass_SetBindGroup);
711
+ SetMethod(isolate, obj, "dispatchWorkgroups", CPass_DispatchWorkgroups);
712
+ SetMethod(isolate, obj, "dispatchWorkgroupsIndirect", CPass_DispatchWorkgroupsIndirect);
713
+ SetMethod(isolate, obj, "pushDebugGroup", CPass_PushDebugGroup);
714
+ SetMethod(isolate, obj, "popDebugGroup", CPass_PopDebugGroup);
715
+ SetMethod(isolate, obj, "insertDebugMarker", CPass_InsertDebugMarker);
716
+ SetMethod(isolate, obj, "end", CPass_End);
717
+ return obj;
718
+ }
719
+
481
720
  // ---------------------------------------------------------------------------
482
721
  // GPUCommandEncoder
483
722
  // ---------------------------------------------------------------------------
@@ -501,6 +740,13 @@ static void Encoder_BeginRenderPass(const v8::FunctionCallbackInfo<v8::Value>& a
501
740
  att.loadOp = ToLoadOp(webgpu::Str(isolate, o, "loadOp"));
502
741
  att.storeOp = ToStoreOp(webgpu::Str(isolate, o, "storeOp"));
503
742
  att.depthSlice = wgpu::kDepthSliceUndefined;
743
+ // MSAA 解決先。player は全描画を MSAA テクスチャへ行い resolveTarget で
744
+ // 本体テクスチャに解決する。未対応だと描画結果がどこにも現れない (黒画面)。
745
+ v8::Local<v8::Value> rt = Prop(isolate, o, "resolveTarget");
746
+ if (rt->IsObject()) {
747
+ att.resolveTarget =
748
+ Unwrap<wgpu::TextureView>(rt.As<v8::Object>());
749
+ }
504
750
  v8::Local<v8::Value> cv = Prop(isolate, o, "clearValue");
505
751
  if (cv->IsArray()) {
506
752
  auto c = cv.As<v8::Array>();
@@ -544,10 +790,38 @@ static void Encoder_BeginRenderPass(const v8::FunctionCallbackInfo<v8::Value>& a
544
790
  pass_desc.depthStencilAttachment = &depth;
545
791
  }
546
792
 
793
+ // occlusionQuerySet: beginOcclusionQuery/endOcclusionQuery で被覆サンプル数を計測する
794
+ v8::Local<v8::Value> oqs = Prop(isolate, desc, "occlusionQuerySet");
795
+ if (oqs->IsObject()) {
796
+ pass_desc.occlusionQuerySet = Unwrap<wgpu::QuerySet>(oqs.As<v8::Object>());
797
+ }
798
+
799
+ // timestampWrites: { querySet, beginningOfPassWriteIndex?, endOfPassWriteIndex? }
800
+ // (timestamp-query フィーチャ有効時のみ有効。未有効なら player/検証側で未使用)
801
+ wgpu::PassTimestampWrites ts = {};
802
+ v8::Local<v8::Value> tsv = Prop(isolate, desc, "timestampWrites");
803
+ if (tsv->IsObject()) {
804
+ auto t = tsv.As<v8::Object>();
805
+ ts.querySet = Unwrap<wgpu::QuerySet>(Prop(isolate, t, "querySet").As<v8::Object>());
806
+ ts.beginningOfPassWriteIndex = U32(isolate, t, "beginningOfPassWriteIndex", wgpu::kQuerySetIndexUndefined);
807
+ ts.endOfPassWriteIndex = U32(isolate, t, "endOfPassWriteIndex", wgpu::kQuerySetIndexUndefined);
808
+ pass_desc.timestampWrites = &ts;
809
+ }
810
+
547
811
  args.GetReturnValue().Set(
548
812
  WrapRenderPass(isolate, encoder.BeginRenderPass(&pass_desc)));
549
813
  }
550
814
 
815
+ static void Encoder_BeginComputePass(const v8::FunctionCallbackInfo<v8::Value>& args)
816
+ {
817
+ v8::Isolate* isolate = args.GetIsolate();
818
+ auto& encoder = Unwrap<wgpu::CommandEncoder>(args.This());
819
+ // descriptor は任意 (timestampWrites 等は未使用)。既定で開始する。
820
+ wgpu::ComputePassDescriptor desc = {};
821
+ args.GetReturnValue().Set(
822
+ WrapComputePass(isolate, encoder.BeginComputePass(&desc)));
823
+ }
824
+
551
825
  static void Encoder_Finish(const v8::FunctionCallbackInfo<v8::Value>& args)
552
826
  {
553
827
  v8::Isolate* isolate = args.GetIsolate();
@@ -575,8 +849,7 @@ static wgpu::TexelCopyTextureInfo ParseTexelCopyTexture(v8::Isolate* isolate, v8
575
849
  info.mipLevel = U32(isolate, o, "mipLevel", 0);
576
850
  v8::Local<v8::Value> origin = Prop(isolate, o, "origin");
577
851
  if (origin->IsObject() || origin->IsArray()) {
578
- wgpu::Extent3D e = ParseExtent(isolate, origin);
579
- info.origin = { e.width, e.height, e.depthOrArrayLayers };
852
+ info.origin = ParseOrigin(isolate, origin);
580
853
  }
581
854
  return info;
582
855
  }
@@ -608,6 +881,187 @@ static void Encoder_CopyTextureToBuffer(const v8::FunctionCallbackInfo<v8::Value
608
881
  encoder.CopyTextureToBuffer(&src, &dst, &size);
609
882
  }
610
883
 
884
+ static void Encoder_ResolveQuerySet(const v8::FunctionCallbackInfo<v8::Value>& args)
885
+ {
886
+ v8::Isolate* isolate = args.GetIsolate();
887
+ auto& encoder = Unwrap<wgpu::CommandEncoder>(args.This());
888
+ auto& qs = Unwrap<wgpu::QuerySet>(args[0].As<v8::Object>());
889
+ uint32_t first = static_cast<uint32_t>(args[1].As<v8::Number>()->Value());
890
+ uint32_t count = static_cast<uint32_t>(args[2].As<v8::Number>()->Value());
891
+ auto& dst = Unwrap<wgpu::Buffer>(args[3].As<v8::Object>());
892
+ uint64_t dst_off = args.Length() > 4 && args[4]->IsNumber()
893
+ ? static_cast<uint64_t>(args[4].As<v8::Number>()->Value()) : 0;
894
+ (void) isolate;
895
+ encoder.ResolveQuerySet(qs, first, count, dst, dst_off);
896
+ }
897
+
898
+ static void Encoder_WriteTimestamp(const v8::FunctionCallbackInfo<v8::Value>& args)
899
+ {
900
+ auto& encoder = Unwrap<wgpu::CommandEncoder>(args.This());
901
+ auto& qs = Unwrap<wgpu::QuerySet>(args[0].As<v8::Object>());
902
+ uint32_t index = static_cast<uint32_t>(args[1].As<v8::Number>()->Value());
903
+ encoder.WriteTimestamp(qs, index);
904
+ }
905
+
906
+ static void Encoder_PushDebugGroup(const v8::FunctionCallbackInfo<v8::Value>& args)
907
+ {
908
+ v8::Isolate* isolate = args.GetIsolate();
909
+ std::string label = args.Length() > 0 ? v8util::ToStdString(isolate, args[0]) : "";
910
+ Unwrap<wgpu::CommandEncoder>(args.This()).PushDebugGroup(
911
+ wgpu::StringView(label.c_str(), label.size()));
912
+ }
913
+
914
+ static void Encoder_PopDebugGroup(const v8::FunctionCallbackInfo<v8::Value>& args)
915
+ {
916
+ Unwrap<wgpu::CommandEncoder>(args.This()).PopDebugGroup();
917
+ }
918
+
919
+ static void Encoder_InsertDebugMarker(const v8::FunctionCallbackInfo<v8::Value>& args)
920
+ {
921
+ v8::Isolate* isolate = args.GetIsolate();
922
+ std::string label = args.Length() > 0 ? v8util::ToStdString(isolate, args[0]) : "";
923
+ Unwrap<wgpu::CommandEncoder>(args.This()).InsertDebugMarker(
924
+ wgpu::StringView(label.c_str(), label.size()));
925
+ }
926
+
927
+ // ---------------------------------------------------------------------------
928
+ // GPURenderBundleEncoder (createRenderBundleEncoder -> finish -> GPURenderBundle)
929
+ // ---------------------------------------------------------------------------
930
+ static void RBEncoder_SetPipeline(const v8::FunctionCallbackInfo<v8::Value>& args)
931
+ {
932
+ Unwrap<wgpu::RenderBundleEncoder>(args.This()).SetPipeline(
933
+ Unwrap<wgpu::RenderPipeline>(args[0].As<v8::Object>()));
934
+ }
935
+
936
+ static void RBEncoder_SetBindGroup(const v8::FunctionCallbackInfo<v8::Value>& args)
937
+ {
938
+ v8::Isolate* isolate = args.GetIsolate();
939
+ auto& enc = Unwrap<wgpu::RenderBundleEncoder>(args.This());
940
+ uint32_t index = static_cast<uint32_t>(args[0].As<v8::Number>()->Value());
941
+ if (args.Length() < 2 || !args[1]->IsObject()) {
942
+ enc.SetBindGroup(index, nullptr);
943
+ return;
944
+ }
945
+ auto& group = Unwrap<wgpu::BindGroup>(args[1].As<v8::Object>());
946
+ std::vector<uint32_t> offsets;
947
+ if (args.Length() > 2 && !args[2]->IsNullOrUndefined()) {
948
+ v8::Local<v8::Context> ctx = isolate->GetCurrentContext();
949
+ if (args[2]->IsArray()) {
950
+ auto arr = args[2].As<v8::Array>();
951
+ offsets.reserve(arr->Length());
952
+ for (uint32_t i = 0; i < arr->Length(); ++i) {
953
+ v8::Local<v8::Value> v;
954
+ if (arr->Get(ctx, i).ToLocal(&v) && v->IsNumber())
955
+ offsets.push_back(static_cast<uint32_t>(v.As<v8::Number>()->Value()));
956
+ }
957
+ } else if (args[2]->IsUint32Array()) {
958
+ auto ta = args[2].As<v8::Uint32Array>();
959
+ offsets.resize(ta->Length());
960
+ ta->CopyContents(offsets.data(), offsets.size() * sizeof(uint32_t));
961
+ }
962
+ }
963
+ enc.SetBindGroup(index, group, static_cast<uint32_t>(offsets.size()),
964
+ offsets.empty() ? nullptr : offsets.data());
965
+ }
966
+
967
+ static void RBEncoder_SetVertexBuffer(const v8::FunctionCallbackInfo<v8::Value>& args)
968
+ {
969
+ auto& enc = Unwrap<wgpu::RenderBundleEncoder>(args.This());
970
+ uint32_t slot = static_cast<uint32_t>(args[0].As<v8::Number>()->Value());
971
+ auto& buffer = Unwrap<wgpu::Buffer>(args[1].As<v8::Object>());
972
+ uint64_t offset = args.Length() > 2 && args[2]->IsNumber()
973
+ ? static_cast<uint64_t>(args[2].As<v8::Number>()->Value()) : 0;
974
+ uint64_t size = args.Length() > 3 && args[3]->IsNumber()
975
+ ? static_cast<uint64_t>(args[3].As<v8::Number>()->Value()) : wgpu::kWholeSize;
976
+ enc.SetVertexBuffer(slot, buffer, offset, size);
977
+ }
978
+
979
+ static void RBEncoder_SetIndexBuffer(const v8::FunctionCallbackInfo<v8::Value>& args)
980
+ {
981
+ v8::Isolate* isolate = args.GetIsolate();
982
+ auto& enc = Unwrap<wgpu::RenderBundleEncoder>(args.This());
983
+ auto& buffer = Unwrap<wgpu::Buffer>(args[0].As<v8::Object>());
984
+ wgpu::IndexFormat format = ToIndexFormat(v8util::ToStdString(isolate, args[1]));
985
+ uint64_t offset = args.Length() > 2 && args[2]->IsNumber()
986
+ ? static_cast<uint64_t>(args[2].As<v8::Number>()->Value()) : 0;
987
+ uint64_t size = args.Length() > 3 && args[3]->IsNumber()
988
+ ? static_cast<uint64_t>(args[3].As<v8::Number>()->Value()) : wgpu::kWholeSize;
989
+ enc.SetIndexBuffer(buffer, format, offset, size);
990
+ }
991
+
992
+ static void RBEncoder_Draw(const v8::FunctionCallbackInfo<v8::Value>& args)
993
+ {
994
+ auto& enc = Unwrap<wgpu::RenderBundleEncoder>(args.This());
995
+ auto n = [&](int i, uint32_t fb) {
996
+ return (i < args.Length() && args[i]->IsNumber())
997
+ ? static_cast<uint32_t>(args[i].As<v8::Number>()->Value()) : fb; };
998
+ enc.Draw(n(0, 0), n(1, 1), n(2, 0), n(3, 0));
999
+ }
1000
+
1001
+ static void RBEncoder_DrawIndexed(const v8::FunctionCallbackInfo<v8::Value>& args)
1002
+ {
1003
+ auto& enc = Unwrap<wgpu::RenderBundleEncoder>(args.This());
1004
+ auto n = [&](int i, uint32_t fb) {
1005
+ return (i < args.Length() && args[i]->IsNumber())
1006
+ ? static_cast<uint32_t>(args[i].As<v8::Number>()->Value()) : fb; };
1007
+ enc.DrawIndexed(n(0, 0), n(1, 1), n(2, 0), n(3, 0), n(4, 0));
1008
+ }
1009
+
1010
+ static void RBEncoder_DrawIndirect(const v8::FunctionCallbackInfo<v8::Value>& args)
1011
+ {
1012
+ auto& enc = Unwrap<wgpu::RenderBundleEncoder>(args.This());
1013
+ auto& buffer = Unwrap<wgpu::Buffer>(args[0].As<v8::Object>());
1014
+ uint64_t offset = args.Length() > 1 && args[1]->IsNumber()
1015
+ ? static_cast<uint64_t>(args[1].As<v8::Number>()->Value()) : 0;
1016
+ enc.DrawIndirect(buffer, offset);
1017
+ }
1018
+
1019
+ static void RBEncoder_Finish(const v8::FunctionCallbackInfo<v8::Value>& args)
1020
+ {
1021
+ v8::Isolate* isolate = args.GetIsolate();
1022
+ auto& enc = Unwrap<wgpu::RenderBundleEncoder>(args.This());
1023
+ wgpu::RenderBundleDescriptor desc = {};
1024
+ args.GetReturnValue().Set(WrapRenderBundle(isolate, enc.Finish(&desc)));
1025
+ }
1026
+
1027
+ static v8::Local<v8::Object> WrapRenderBundleEncoder(v8::Isolate* isolate, wgpu::RenderBundleEncoder enc)
1028
+ {
1029
+ auto obj = WrapWith(isolate, Tmpl(isolate).renderBundleEncoder, std::move(enc));
1030
+ SetMethod(isolate, obj, "setPipeline", RBEncoder_SetPipeline);
1031
+ SetMethod(isolate, obj, "setBindGroup", RBEncoder_SetBindGroup);
1032
+ SetMethod(isolate, obj, "setVertexBuffer", RBEncoder_SetVertexBuffer);
1033
+ SetMethod(isolate, obj, "setIndexBuffer", RBEncoder_SetIndexBuffer);
1034
+ SetMethod(isolate, obj, "draw", RBEncoder_Draw);
1035
+ SetMethod(isolate, obj, "drawIndexed", RBEncoder_DrawIndexed);
1036
+ SetMethod(isolate, obj, "drawIndirect", RBEncoder_DrawIndirect);
1037
+ SetMethod(isolate, obj, "finish", RBEncoder_Finish);
1038
+ return obj;
1039
+ }
1040
+
1041
+ static v8::Local<v8::Object> WrapRenderBundle(v8::Isolate* isolate, wgpu::RenderBundle bundle)
1042
+ {
1043
+ return WrapWith(isolate, Tmpl(isolate).renderBundle, std::move(bundle));
1044
+ }
1045
+
1046
+ // ---------------------------------------------------------------------------
1047
+ // GPUQuerySet
1048
+ // ---------------------------------------------------------------------------
1049
+ static void QuerySet_Destroy(const v8::FunctionCallbackInfo<v8::Value>& args)
1050
+ {
1051
+ Unwrap<wgpu::QuerySet>(args.This()).Destroy();
1052
+ }
1053
+
1054
+ static v8::Local<v8::Object> WrapQuerySet(v8::Isolate* isolate, wgpu::QuerySet qs)
1055
+ {
1056
+ auto obj = WrapWith(isolate, Tmpl(isolate).querySet, std::move(qs));
1057
+ auto& q = Unwrap<wgpu::QuerySet>(obj);
1058
+ SetValue(isolate, obj, "count", v8::Integer::NewFromUnsigned(isolate, q.GetCount()));
1059
+ SetValue(isolate, obj, "type",
1060
+ Str(isolate, q.GetType() == wgpu::QueryType::Timestamp ? "timestamp" : "occlusion"));
1061
+ SetMethod(isolate, obj, "destroy", QuerySet_Destroy);
1062
+ return obj;
1063
+ }
1064
+
611
1065
  // ---------------------------------------------------------------------------
612
1066
  // GPUCanvasContext
613
1067
  // ---------------------------------------------------------------------------
@@ -617,6 +1071,11 @@ static void Ctx_Configure(const v8::FunctionCallbackInfo<v8::Value>& args)
617
1071
  DawnContext* gpu = HostContext::From(isolate)->gpu;
618
1072
  // device は既に DawnContext が保持。フォーマット/サイズのみ反映する。
619
1073
  if (args.Length() > 0 && args[0]->IsObject()) {
1074
+ static uint64_t count = 0;
1075
+ ++count;
1076
+ if (count <= 10 || count % 300 == 0) {
1077
+ std::cerr << "[GPU] context.configure #" << count << std::endl;
1078
+ }
620
1079
  // width/height は canvas サイズを使用 (configure の size は任意)
621
1080
  gpu->Configure(gpu->width(), gpu->height());
622
1081
  }
@@ -626,6 +1085,11 @@ static void Ctx_GetCurrentTexture(const v8::FunctionCallbackInfo<v8::Value>& arg
626
1085
  {
627
1086
  v8::Isolate* isolate = args.GetIsolate();
628
1087
  DawnContext* gpu = HostContext::From(isolate)->gpu;
1088
+ static uint64_t count = 0;
1089
+ ++count;
1090
+ if (count <= 10 || count % 300 == 0) {
1091
+ std::cerr << "[GPU] getCurrentTexture #" << count << std::endl;
1092
+ }
629
1093
  args.GetReturnValue().Set(WrapTexture(isolate, gpu->GetCurrentTexture()));
630
1094
  }
631
1095
 
@@ -749,9 +1213,13 @@ static void Device_CreateBindGroupLayout(const v8::FunctionCallbackInfo<v8::Valu
749
1213
  v8::Local<v8::Object> d = args[0].As<v8::Object>();
750
1214
 
751
1215
  std::vector<wgpu::BindGroupLayoutEntry> entries;
1216
+ // externalTexture の nextInChain 構造体は CreateBindGroupLayout 完了まで生存が必要。
1217
+ // reserve でベクタ再割当を防ぎ、back() のアドレスを安定させる。
1218
+ std::vector<wgpu::ExternalTextureBindingLayout> ext_layouts;
752
1219
  v8::Local<v8::Value> ev = Prop(isolate, d, "entries");
753
1220
  if (ev->IsArray()) {
754
1221
  auto arr = ev.As<v8::Array>();
1222
+ ext_layouts.reserve(arr->Length());
755
1223
  for (uint32_t i = 0; i < arr->Length(); ++i) {
756
1224
  v8::Local<v8::Value> v;
757
1225
  if (!arr->Get(ctx, i).ToLocal(&v) || !v->IsObject()) continue;
@@ -775,8 +1243,21 @@ static void Device_CreateBindGroupLayout(const v8::FunctionCallbackInfo<v8::Valu
775
1243
  ? ToTextureViewDimension(webgpu::Str(isolate, t, "viewDimension"))
776
1244
  : wgpu::TextureViewDimension::e2D;
777
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();
778
1260
  }
779
- // «EXTEND» storageTexture / externalTexture は必要に応じて追加
780
1261
  entries.push_back(entry);
781
1262
  }
782
1263
  }
@@ -881,17 +1362,67 @@ struct RenderPipelineScratch {
881
1362
  std::vector<wgpu::ColorTargetState> targets;
882
1363
  std::vector<wgpu::BlendState> blends;
883
1364
  std::string vs_entry, fs_entry;
1365
+ // pipeline-overridable constants (WGSL の override 定数)。key 文字列は
1366
+ // ConstantEntry.key が指すため CreateRenderPipeline 完了まで生存させる。
1367
+ std::vector<std::string> vs_const_keys, fs_const_keys;
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 = {};
884
1374
  };
885
1375
 
886
- static void Device_CreateRenderPipeline(const v8::FunctionCallbackInfo<v8::Value>& args)
1376
+ // { "KEY": number|bool, ... } を wgpu::ConstantEntry 配列へ変換する。
1377
+ // player は WGSL の override(GRADIENT_TYPE / SPREAD_MODE / yFlipSign / フィルタ種別など)
1378
+ // を constants で指定してパイプラインを特殊化する。ここを読まないと全パイプラインが
1379
+ // WGSL の既定値でコンパイルされ、放射状グラデが線形に、spread が pad に、
1380
+ // フィルタ種別が既定分岐に潰れる (エラーは出ない)。
1381
+ static void ParsePipelineConstants(
1382
+ v8::Isolate* isolate, v8::Local<v8::Object> stage,
1383
+ std::vector<std::string>& keys, std::vector<wgpu::ConstantEntry>& entries)
887
1384
  {
888
- v8::Isolate* isolate = args.GetIsolate();
889
1385
  v8::Local<v8::Context> ctx = isolate->GetCurrentContext();
890
- auto& device = Unwrap<wgpu::Device>(args.This());
891
- v8::Local<v8::Object> d = args[0].As<v8::Object>();
1386
+ v8::Local<v8::Value> cv = Prop(isolate, stage, "constants");
1387
+ if (!cv->IsObject()) {
1388
+ return;
1389
+ }
1390
+ auto co = cv.As<v8::Object>();
1391
+ v8::Local<v8::Array> names;
1392
+ if (!co->GetOwnPropertyNames(ctx).ToLocal(&names)) {
1393
+ return;
1394
+ }
1395
+ // 再確保で ConstantEntry.key の指す c_str が無効化されないよう上限で確保する。
1396
+ keys.reserve(keys.size() + names->Length());
1397
+ entries.reserve(entries.size() + names->Length());
1398
+ for (uint32_t i = 0; i < names->Length(); ++i) {
1399
+ v8::Local<v8::Value> k, val;
1400
+ if (!names->Get(ctx, i).ToLocal(&k)) continue;
1401
+ if (!co->Get(ctx, k).ToLocal(&val)) continue;
1402
+ double num;
1403
+ if (val->IsBoolean()) {
1404
+ num = val.As<v8::Boolean>()->Value() ? 1.0 : 0.0;
1405
+ } else if (val->IsNumber()) {
1406
+ num = val.As<v8::Number>()->Value();
1407
+ } else {
1408
+ continue;
1409
+ }
1410
+ v8::String::Utf8Value ks(isolate, k);
1411
+ if (!*ks) continue;
1412
+ keys.emplace_back(*ks, static_cast<size_t>(ks.length()));
1413
+ wgpu::ConstantEntry e = {};
1414
+ e.key = wgpu::StringView(keys.back().c_str(), keys.back().size());
1415
+ e.value = num;
1416
+ entries.push_back(e);
1417
+ }
1418
+ }
892
1419
 
893
- RenderPipelineScratch scratch;
894
- wgpu::RenderPipelineDescriptor desc = {};
1420
+ // ディスクリプタ構築 (sync/async 双方から使う。desc/scratch は呼び出し側が保持し、
1421
+ // CreateRenderPipeline[Async] が同期コピーするまで生存させる)。
1422
+ static void FillRenderPipelineDescriptor(v8::Isolate* isolate, v8::Local<v8::Object> d,
1423
+ RenderPipelineScratch& scratch, wgpu::RenderPipelineDescriptor& desc)
1424
+ {
1425
+ v8::Local<v8::Context> ctx = isolate->GetCurrentContext();
895
1426
 
896
1427
  // layout: 'auto' or GPUPipelineLayout
897
1428
  v8::Local<v8::Value> layout = Prop(isolate, d, "layout");
@@ -944,6 +1475,13 @@ static void Device_CreateRenderPipeline(const v8::FunctionCallbackInfo<v8::Value
944
1475
  desc.vertex.buffers = scratch.vertex_buffers.data();
945
1476
  }
946
1477
 
1478
+ // vertex.constants (override 定数)
1479
+ ParsePipelineConstants(isolate, vertex, scratch.vs_const_keys, scratch.vs_constants);
1480
+ if (!scratch.vs_constants.empty()) {
1481
+ desc.vertex.constantCount = scratch.vs_constants.size();
1482
+ desc.vertex.constants = scratch.vs_constants.data();
1483
+ }
1484
+
947
1485
  // primitive
948
1486
  v8::Local<v8::Value> prim = Prop(isolate, d, "primitive");
949
1487
  if (prim->IsObject()) {
@@ -958,7 +1496,8 @@ static void Device_CreateRenderPipeline(const v8::FunctionCallbackInfo<v8::Value
958
1496
  }
959
1497
 
960
1498
  // depthStencil (マスク処理は stencil8 + stencilFront/Back を使う)
961
- wgpu::DepthStencilState depth = {};
1499
+ // scratch.depth を参照 (アドレスを desc.depthStencil に格納するため寿命を延ばす)。
1500
+ wgpu::DepthStencilState& depth = scratch.depth;
962
1501
  v8::Local<v8::Value> dsv = Prop(isolate, d, "depthStencil");
963
1502
  if (dsv->IsObject()) {
964
1503
  auto ds = dsv.As<v8::Object>();
@@ -1008,10 +1547,18 @@ static void Device_CreateRenderPipeline(const v8::FunctionCallbackInfo<v8::Value
1008
1547
  if (ms->IsObject()) {
1009
1548
  auto m = ms.As<v8::Object>();
1010
1549
  desc.multisample.count = U32(isolate, m, "count", 1);
1550
+ // alphaToCoverageEnabled: フラグメント alpha を MSAA カバレッジへ変換する。
1551
+ // Next2D はベクタ図形の境界を塗り alpha に頼るため、これが無いと全図形の
1552
+ // アンチエイリアスが失われて縁がジャギる (静かな品質劣化)。
1553
+ desc.multisample.alphaToCoverageEnabled = Bool(isolate, m, "alphaToCoverageEnabled", false);
1554
+ if (HasProp(isolate, m, "mask")) {
1555
+ desc.multisample.mask = U32(isolate, m, "mask", 0xFFFFFFFF);
1556
+ }
1011
1557
  }
1012
1558
 
1013
1559
  // fragment
1014
- wgpu::FragmentState fragment = {};
1560
+ // scratch.fragment を参照 (アドレスを desc.fragment に格納するため寿命を延ばす)。
1561
+ wgpu::FragmentState& fragment = scratch.fragment;
1015
1562
  v8::Local<v8::Value> fv = Prop(isolate, d, "fragment");
1016
1563
  if (fv->IsObject()) {
1017
1564
  auto f = fv.As<v8::Object>();
@@ -1053,12 +1600,223 @@ static void Device_CreateRenderPipeline(const v8::FunctionCallbackInfo<v8::Value
1053
1600
  fragment.targetCount = scratch.targets.size();
1054
1601
  fragment.targets = scratch.targets.data();
1055
1602
  }
1603
+
1604
+ // fragment.constants (override 定数: GRADIENT_TYPE/SPREAD_MODE/フィルタ種別など)
1605
+ ParsePipelineConstants(isolate, f, scratch.fs_const_keys, scratch.fs_constants);
1606
+ if (!scratch.fs_constants.empty()) {
1607
+ fragment.constantCount = scratch.fs_constants.size();
1608
+ fragment.constants = scratch.fs_constants.data();
1609
+ }
1610
+
1056
1611
  desc.fragment = &fragment;
1057
1612
  }
1613
+ }
1058
1614
 
1615
+ static void Device_CreateRenderPipeline(const v8::FunctionCallbackInfo<v8::Value>& args)
1616
+ {
1617
+ v8::Isolate* isolate = args.GetIsolate();
1618
+ auto& device = Unwrap<wgpu::Device>(args.This());
1619
+ RenderPipelineScratch scratch;
1620
+ wgpu::RenderPipelineDescriptor desc = {};
1621
+ FillRenderPipelineDescriptor(isolate, args[0].As<v8::Object>(), scratch, desc);
1059
1622
  args.GetReturnValue().Set(WrapPipeline(isolate, device.CreateRenderPipeline(&desc)));
1060
1623
  }
1061
1624
 
1625
+ // ---------------------------------------------------------------------------
1626
+ // GPUDevice.createComputePipeline
1627
+ // { layout: 'auto'|GPUPipelineLayout, compute: {module, entryPoint?, constants?} }
1628
+ // ---------------------------------------------------------------------------
1629
+ // compute ディスクリプタ構築 (sync/async 共有)。entryPoint/constants の文字列寿命は
1630
+ // 呼び出し側が保持する keys/entry_strs で管理する。
1631
+ struct ComputePipelineScratch {
1632
+ std::string cs_entry;
1633
+ std::vector<std::string> cs_const_keys;
1634
+ std::vector<wgpu::ConstantEntry> cs_constants;
1635
+ };
1636
+
1637
+ static void FillComputePipelineDescriptor(v8::Isolate* isolate, v8::Local<v8::Object> d,
1638
+ ComputePipelineScratch& scratch, wgpu::ComputePipelineDescriptor& desc)
1639
+ {
1640
+ v8::Local<v8::Value> layout = Prop(isolate, d, "layout");
1641
+ if (layout->IsObject()) {
1642
+ desc.layout = Unwrap<wgpu::PipelineLayout>(layout.As<v8::Object>());
1643
+ }
1644
+ v8::Local<v8::Object> compute = Prop(isolate, d, "compute").As<v8::Object>();
1645
+ desc.compute.module = Unwrap<wgpu::ShaderModule>(Prop(isolate, compute, "module").As<v8::Object>());
1646
+ scratch.cs_entry = webgpu::Str(isolate, compute, "entryPoint");
1647
+ if (!scratch.cs_entry.empty()) {
1648
+ desc.compute.entryPoint = wgpu::StringView(scratch.cs_entry.c_str(), scratch.cs_entry.size());
1649
+ }
1650
+ ParsePipelineConstants(isolate, compute, scratch.cs_const_keys, scratch.cs_constants);
1651
+ if (!scratch.cs_constants.empty()) {
1652
+ desc.compute.constantCount = scratch.cs_constants.size();
1653
+ desc.compute.constants = scratch.cs_constants.data();
1654
+ }
1655
+ }
1656
+
1657
+ static void Device_CreateComputePipeline(const v8::FunctionCallbackInfo<v8::Value>& args)
1658
+ {
1659
+ v8::Isolate* isolate = args.GetIsolate();
1660
+ auto& device = Unwrap<wgpu::Device>(args.This());
1661
+ ComputePipelineScratch scratch;
1662
+ wgpu::ComputePipelineDescriptor desc = {};
1663
+ FillComputePipelineDescriptor(isolate, args[0].As<v8::Object>(), scratch, desc);
1664
+ args.GetReturnValue().Set(
1665
+ WrapComputePipeline(isolate, device.CreateComputePipeline(&desc)));
1666
+ }
1667
+
1668
+ // createRenderPipelineAsync / createComputePipelineAsync -> Promise<GPU*Pipeline>
1669
+ // Dawn C++ の Future 版 (CallbackMode::AllowProcessEvents で event loop が処理)。
1670
+ // ディスクリプタは呼び出し時に同期コピーされるため scratch はスタックで足りる。
1671
+ static void Device_CreateRenderPipelineAsync(const v8::FunctionCallbackInfo<v8::Value>& args)
1672
+ {
1673
+ v8::Isolate* isolate = args.GetIsolate();
1674
+ v8::Local<v8::Context> ctx = isolate->GetCurrentContext();
1675
+ auto& device = Unwrap<wgpu::Device>(args.This());
1676
+ RenderPipelineScratch scratch;
1677
+ wgpu::RenderPipelineDescriptor desc = {};
1678
+ FillRenderPipelineDescriptor(isolate, args[0].As<v8::Object>(), scratch, desc);
1679
+
1680
+ auto resolver = v8::Promise::Resolver::New(ctx).ToLocalChecked();
1681
+ auto* persistent = new v8::Global<v8::Promise::Resolver>(isolate, resolver);
1682
+ args.GetReturnValue().Set(resolver->GetPromise());
1683
+
1684
+ device.CreateRenderPipelineAsync(&desc, wgpu::CallbackMode::AllowProcessEvents,
1685
+ [isolate, persistent](wgpu::CreatePipelineAsyncStatus status,
1686
+ wgpu::RenderPipeline pipeline, wgpu::StringView) {
1687
+ v8::HandleScope scope(isolate);
1688
+ v8::Local<v8::Context> c = isolate->GetCurrentContext();
1689
+ auto r = persistent->Get(isolate);
1690
+ if (status == wgpu::CreatePipelineAsyncStatus::Success) {
1691
+ r->Resolve(c, WrapPipeline(isolate, pipeline)).Check();
1692
+ } else {
1693
+ r->Reject(c, v8::Exception::Error(
1694
+ v8util::Str(isolate, "createRenderPipelineAsync failed"))).Check();
1695
+ }
1696
+ persistent->Reset(); delete persistent;
1697
+ });
1698
+ }
1699
+
1700
+ static void Device_CreateComputePipelineAsync(const v8::FunctionCallbackInfo<v8::Value>& args)
1701
+ {
1702
+ v8::Isolate* isolate = args.GetIsolate();
1703
+ v8::Local<v8::Context> ctx = isolate->GetCurrentContext();
1704
+ auto& device = Unwrap<wgpu::Device>(args.This());
1705
+ ComputePipelineScratch scratch;
1706
+ wgpu::ComputePipelineDescriptor desc = {};
1707
+ FillComputePipelineDescriptor(isolate, args[0].As<v8::Object>(), scratch, desc);
1708
+
1709
+ auto resolver = v8::Promise::Resolver::New(ctx).ToLocalChecked();
1710
+ auto* persistent = new v8::Global<v8::Promise::Resolver>(isolate, resolver);
1711
+ args.GetReturnValue().Set(resolver->GetPromise());
1712
+
1713
+ device.CreateComputePipelineAsync(&desc, wgpu::CallbackMode::AllowProcessEvents,
1714
+ [isolate, persistent](wgpu::CreatePipelineAsyncStatus status,
1715
+ wgpu::ComputePipeline pipeline, wgpu::StringView) {
1716
+ v8::HandleScope scope(isolate);
1717
+ v8::Local<v8::Context> c = isolate->GetCurrentContext();
1718
+ auto r = persistent->Get(isolate);
1719
+ if (status == wgpu::CreatePipelineAsyncStatus::Success) {
1720
+ r->Resolve(c, WrapComputePipeline(isolate, pipeline)).Check();
1721
+ } else {
1722
+ r->Reject(c, v8::Exception::Error(
1723
+ v8util::Str(isolate, "createComputePipelineAsync failed"))).Check();
1724
+ }
1725
+ persistent->Reset(); delete persistent;
1726
+ });
1727
+ }
1728
+
1729
+ // createQuerySet({ type: 'occlusion'|'timestamp', count })
1730
+ static void Device_CreateQuerySet(const v8::FunctionCallbackInfo<v8::Value>& args)
1731
+ {
1732
+ v8::Isolate* isolate = args.GetIsolate();
1733
+ auto& device = Unwrap<wgpu::Device>(args.This());
1734
+ v8::Local<v8::Object> d = args[0].As<v8::Object>();
1735
+ wgpu::QuerySetDescriptor desc = {};
1736
+ desc.type = ToQueryType(webgpu::Str(isolate, d, "type"));
1737
+ desc.count = U32(isolate, d, "count", 0);
1738
+ args.GetReturnValue().Set(WrapQuerySet(isolate, device.CreateQuerySet(&desc)));
1739
+ }
1740
+
1741
+ // createRenderBundleEncoder({ colorFormats[], depthStencilFormat?, sampleCount?, ... })
1742
+ static void Device_CreateRenderBundleEncoder(const v8::FunctionCallbackInfo<v8::Value>& args)
1743
+ {
1744
+ v8::Isolate* isolate = args.GetIsolate();
1745
+ v8::Local<v8::Context> ctx = isolate->GetCurrentContext();
1746
+ auto& device = Unwrap<wgpu::Device>(args.This());
1747
+ v8::Local<v8::Object> d = args[0].As<v8::Object>();
1748
+
1749
+ std::vector<wgpu::TextureFormat> formats;
1750
+ v8::Local<v8::Value> cf = Prop(isolate, d, "colorFormats");
1751
+ if (cf->IsArray()) {
1752
+ auto arr = cf.As<v8::Array>();
1753
+ for (uint32_t i = 0; i < arr->Length(); ++i) {
1754
+ v8::Local<v8::Value> v;
1755
+ if (arr->Get(ctx, i).ToLocal(&v) && v->IsString()) {
1756
+ formats.push_back(ToTextureFormat(v8util::ToStdString(isolate, v)));
1757
+ } else {
1758
+ formats.push_back(wgpu::TextureFormat::Undefined); // ギャップ (null)
1759
+ }
1760
+ }
1761
+ }
1762
+
1763
+ wgpu::RenderBundleEncoderDescriptor desc = {};
1764
+ desc.colorFormatCount = formats.size();
1765
+ desc.colorFormats = formats.data();
1766
+ if (HasProp(isolate, d, "depthStencilFormat")) {
1767
+ desc.depthStencilFormat = ToTextureFormat(webgpu::Str(isolate, d, "depthStencilFormat"));
1768
+ }
1769
+ desc.sampleCount = U32(isolate, d, "sampleCount", 1);
1770
+ desc.depthReadOnly = Bool(isolate, d, "depthReadOnly", false);
1771
+ desc.stencilReadOnly = Bool(isolate, d, "stencilReadOnly", false);
1772
+
1773
+ args.GetReturnValue().Set(
1774
+ WrapRenderBundleEncoder(isolate, device.CreateRenderBundleEncoder(&desc)));
1775
+ }
1776
+
1777
+ // pushErrorScope(filter) / popErrorScope() -> Promise<GPUError|null>
1778
+ static void Device_PushErrorScope(const v8::FunctionCallbackInfo<v8::Value>& args)
1779
+ {
1780
+ v8::Isolate* isolate = args.GetIsolate();
1781
+ wgpu::ErrorFilter filter = ToErrorFilter(
1782
+ args.Length() > 0 ? v8util::ToStdString(isolate, args[0]) : "validation");
1783
+ Unwrap<wgpu::Device>(args.This()).PushErrorScope(filter);
1784
+ }
1785
+
1786
+ static void Device_PopErrorScope(const v8::FunctionCallbackInfo<v8::Value>& args)
1787
+ {
1788
+ v8::Isolate* isolate = args.GetIsolate();
1789
+ v8::Local<v8::Context> ctx = isolate->GetCurrentContext();
1790
+ auto& device = Unwrap<wgpu::Device>(args.This());
1791
+
1792
+ auto resolver = v8::Promise::Resolver::New(ctx).ToLocalChecked();
1793
+ auto* persistent = new v8::Global<v8::Promise::Resolver>(isolate, resolver);
1794
+ args.GetReturnValue().Set(resolver->GetPromise());
1795
+
1796
+ device.PopErrorScope(wgpu::CallbackMode::AllowProcessEvents,
1797
+ [isolate, persistent](wgpu::PopErrorScopeStatus, wgpu::ErrorType type, wgpu::StringView message) {
1798
+ v8::HandleScope scope(isolate);
1799
+ v8::Local<v8::Context> c = isolate->GetCurrentContext();
1800
+ auto r = persistent->Get(isolate);
1801
+ if (type == wgpu::ErrorType::NoError) {
1802
+ r->Resolve(c, v8::Null(isolate)).Check();
1803
+ } else {
1804
+ // GPUError 相当のオブジェクト ({ message, __errorType })
1805
+ v8::Local<v8::Object> err = v8::Object::New(isolate);
1806
+ std::string msg(message.data ? message.data : "", message.length);
1807
+ SetValue(isolate, err, "message", v8util::Str(isolate, msg.c_str()));
1808
+ SetValue(isolate, err, "__errorType", v8util::Str(isolate, ErrorTypeName(type)));
1809
+ r->Resolve(c, err).Check();
1810
+ }
1811
+ persistent->Reset(); delete persistent;
1812
+ });
1813
+ }
1814
+
1815
+ static void Device_Destroy(const v8::FunctionCallbackInfo<v8::Value>& args)
1816
+ {
1817
+ Unwrap<wgpu::Device>(args.This()).Destroy();
1818
+ }
1819
+
1062
1820
  static void Device_CreateCommandEncoder(const v8::FunctionCallbackInfo<v8::Value>& args)
1063
1821
  {
1064
1822
  v8::Isolate* isolate = args.GetIsolate();
@@ -1106,10 +1864,16 @@ static v8::Local<v8::Object> WrapEncoder(v8::Isolate* isolate, wgpu::CommandEnco
1106
1864
  {
1107
1865
  auto obj = WrapWith(isolate, Tmpl(isolate).commandEncoder, std::move(encoder));
1108
1866
  SetMethod(isolate, obj, "beginRenderPass", Encoder_BeginRenderPass);
1867
+ SetMethod(isolate, obj, "beginComputePass", Encoder_BeginComputePass);
1109
1868
  SetMethod(isolate, obj, "finish", Encoder_Finish);
1110
1869
  SetMethod(isolate, obj, "copyBufferToBuffer", Encoder_CopyBufferToBuffer);
1111
1870
  SetMethod(isolate, obj, "copyTextureToTexture", Encoder_CopyTextureToTexture);
1112
1871
  SetMethod(isolate, obj, "copyTextureToBuffer", Encoder_CopyTextureToBuffer);
1872
+ SetMethod(isolate, obj, "resolveQuerySet", Encoder_ResolveQuerySet);
1873
+ SetMethod(isolate, obj, "writeTimestamp", Encoder_WriteTimestamp);
1874
+ SetMethod(isolate, obj, "pushDebugGroup", Encoder_PushDebugGroup);
1875
+ SetMethod(isolate, obj, "popDebugGroup", Encoder_PopDebugGroup);
1876
+ SetMethod(isolate, obj, "insertDebugMarker", Encoder_InsertDebugMarker);
1113
1877
  return obj;
1114
1878
  }
1115
1879
 
@@ -1120,9 +1884,45 @@ static v8::Local<v8::Object> WrapPipeline(v8::Isolate* isolate, wgpu::RenderPipe
1120
1884
  return obj;
1121
1885
  }
1122
1886
 
1887
+ static void ComputePipeline_GetBindGroupLayout(const v8::FunctionCallbackInfo<v8::Value>& args)
1888
+ {
1889
+ v8::Isolate* isolate = args.GetIsolate();
1890
+ auto& pipeline = Unwrap<wgpu::ComputePipeline>(args.This());
1891
+ uint32_t index = args.Length() > 0 && args[0]->IsNumber()
1892
+ ? static_cast<uint32_t>(args[0].As<v8::Number>()->Value()) : 0;
1893
+ args.GetReturnValue().Set(
1894
+ WrapWith(isolate, Tmpl(isolate).bindGroupLayout, pipeline.GetBindGroupLayout(index)));
1895
+ }
1896
+
1897
+ static v8::Local<v8::Object> WrapComputePipeline(v8::Isolate* isolate, wgpu::ComputePipeline pipeline)
1898
+ {
1899
+ auto obj = WrapWith(isolate, Tmpl(isolate).computePipeline, std::move(pipeline));
1900
+ SetMethod(isolate, obj, "getBindGroupLayout", ComputePipeline_GetBindGroupLayout);
1901
+ return obj;
1902
+ }
1903
+
1123
1904
  // ---------------------------------------------------------------------------
1124
1905
  // GPUDevice / GPUQueue / GPUAdapter のラップ
1125
1906
  // ---------------------------------------------------------------------------
1907
+ static void Queue_OnSubmittedWorkDone(const v8::FunctionCallbackInfo<v8::Value>& args)
1908
+ {
1909
+ v8::Isolate* isolate = args.GetIsolate();
1910
+ v8::Local<v8::Context> ctx = isolate->GetCurrentContext();
1911
+ auto& queue = Unwrap<wgpu::Queue>(args.This());
1912
+
1913
+ auto resolver = v8::Promise::Resolver::New(ctx).ToLocalChecked();
1914
+ auto* persistent = new v8::Global<v8::Promise::Resolver>(isolate, resolver);
1915
+ args.GetReturnValue().Set(resolver->GetPromise());
1916
+
1917
+ queue.OnSubmittedWorkDone(wgpu::CallbackMode::AllowProcessEvents,
1918
+ [isolate, persistent](wgpu::QueueWorkDoneStatus, wgpu::StringView) {
1919
+ v8::HandleScope scope(isolate);
1920
+ v8::Local<v8::Context> c = isolate->GetCurrentContext();
1921
+ persistent->Get(isolate)->Resolve(c, v8::Undefined(isolate)).Check();
1922
+ persistent->Reset(); delete persistent;
1923
+ });
1924
+ }
1925
+
1126
1926
  static v8::Local<v8::Object> WrapQueue(v8::Isolate* isolate, wgpu::Queue queue)
1127
1927
  {
1128
1928
  auto obj = WrapWith(isolate, Tmpl(isolate).queue, std::move(queue));
@@ -1130,6 +1930,7 @@ static v8::Local<v8::Object> WrapQueue(v8::Isolate* isolate, wgpu::Queue queue)
1130
1930
  SetMethod(isolate, obj, "writeBuffer", Queue_WriteBuffer);
1131
1931
  SetMethod(isolate, obj, "writeTexture", Queue_WriteTexture);
1132
1932
  SetMethod(isolate, obj, "copyExternalImageToTexture", Queue_CopyExternalImageToTexture);
1933
+ SetMethod(isolate, obj, "onSubmittedWorkDone", Queue_OnSubmittedWorkDone);
1133
1934
  return obj;
1134
1935
  }
1135
1936
 
@@ -1144,10 +1945,26 @@ static v8::Local<v8::Object> WrapDevice(v8::Isolate* isolate, wgpu::Device devic
1144
1945
  SetMethod(isolate, obj, "createBindGroup", Device_CreateBindGroup);
1145
1946
  SetMethod(isolate, obj, "createPipelineLayout", Device_CreatePipelineLayout);
1146
1947
  SetMethod(isolate, obj, "createRenderPipeline", Device_CreateRenderPipeline);
1948
+ SetMethod(isolate, obj, "createComputePipeline", Device_CreateComputePipeline);
1949
+ SetMethod(isolate, obj, "createRenderPipelineAsync", Device_CreateRenderPipelineAsync);
1950
+ SetMethod(isolate, obj, "createComputePipelineAsync", Device_CreateComputePipelineAsync);
1147
1951
  SetMethod(isolate, obj, "createCommandEncoder", Device_CreateCommandEncoder);
1952
+ SetMethod(isolate, obj, "createQuerySet", Device_CreateQuerySet);
1953
+ SetMethod(isolate, obj, "createRenderBundleEncoder", Device_CreateRenderBundleEncoder);
1954
+ SetMethod(isolate, obj, "pushErrorScope", Device_PushErrorScope);
1955
+ SetMethod(isolate, obj, "popErrorScope", Device_PopErrorScope);
1956
+ SetMethod(isolate, obj, "destroy", Device_Destroy);
1148
1957
  SetValue(isolate, obj, "queue", WrapQueue(isolate, device.GetQueue()));
1149
1958
  SetValue(isolate, obj, "features", v8::Object::New(isolate));
1150
1959
 
1960
+ // device.lost: Promise<GPUDeviceLostInfo>。デバイス生存中は pending (spec 準拠)。
1961
+ // 実際のロストは Dawn の deviceLost コールバック経路 (DawnContext 側) に委ねる。
1962
+ {
1963
+ v8::Local<v8::Context> lctx = isolate->GetCurrentContext();
1964
+ auto lost = v8::Promise::Resolver::New(lctx).ToLocalChecked();
1965
+ SetValue(isolate, obj, "lost", lost->GetPromise());
1966
+ }
1967
+
1151
1968
  // limits: player は maxTextureDimension2D で描画最大サイズを決めるため実値を返す。
1152
1969
  // GetLimits の戻り値型は Dawn バージョン差があるため値でガードする(0 なら保証最小値)。
1153
1970
  v8::Local<v8::Object> limits = v8::Object::New(isolate);
@@ -1224,6 +2041,8 @@ static void InitTemplates(v8::Isolate* isolate)
1224
2041
  make(t.textureView); make(t.sampler); make(t.shaderModule); make(t.bindGroupLayout);
1225
2042
  make(t.bindGroup); make(t.pipelineLayout); make(t.renderPipeline); make(t.commandEncoder);
1226
2043
  make(t.renderPass); make(t.commandBuffer); make(t.canvasContext);
2044
+ make(t.computePipeline); make(t.computePass);
2045
+ make(t.querySet); make(t.renderBundle); make(t.renderBundleEncoder);
1227
2046
  }
1228
2047
 
1229
2048
  static void SetFlag(v8::Isolate* isolate, v8::Local<v8::Object> obj, const char* k, uint32_t v)
@@ -1234,6 +2053,13 @@ static void SetFlag(v8::Isolate* isolate, v8::Local<v8::Object> obj, const char*
1234
2053
  } // namespace webgpu
1235
2054
 
1236
2055
  // Bindings.h から呼ばれる公開関数
2056
+ // V8 破棄前に呼ぶ。テンプレートの v8::Global (static map) を解放する。
2057
+ // (放置すると static デストラクタが V8 破棄後に走りアクセス違反になる)
2058
+ void ShutdownWebGPU()
2059
+ {
2060
+ webgpu::g_templates.clear();
2061
+ }
2062
+
1237
2063
  void InstallWebGPU(v8::Isolate* isolate, v8::Local<v8::Object> global, HostContext* host)
1238
2064
  {
1239
2065
  using namespace next2d::webgpu;