@next2d/builder 0.1.6 → 0.1.8

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