@simulatte/webgpu 0.3.1 → 0.3.2

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 (50) hide show
  1. package/CHANGELOG.md +27 -12
  2. package/LICENSE +191 -0
  3. package/README.md +55 -41
  4. package/api-contract.md +67 -49
  5. package/architecture.md +317 -0
  6. package/assets/package-layers.svg +3 -3
  7. package/docs/doe-api-reference.html +1842 -0
  8. package/doe-api-design.md +237 -0
  9. package/examples/doe-api/README.md +19 -0
  10. package/examples/doe-api/buffers-readback.js +3 -2
  11. package/examples/{doe-routines/compute-once-like-input.js → doe-api/compute-one-shot-like-input.js} +1 -1
  12. package/examples/{doe-routines/compute-once-matmul.js → doe-api/compute-one-shot-matmul.js} +2 -2
  13. package/examples/{doe-routines/compute-once-multiple-inputs.js → doe-api/compute-one-shot-multiple-inputs.js} +1 -1
  14. package/examples/{doe-routines/compute-once.js → doe-api/compute-one-shot.js} +1 -1
  15. package/examples/doe-api/{compile-and-dispatch.js → kernel-create-and-dispatch.js} +4 -6
  16. package/examples/doe-api/{compute-dispatch.js → kernel-run.js} +4 -6
  17. package/headless-webgpu-comparison.md +3 -3
  18. package/jsdoc-style-guide.md +435 -0
  19. package/native/doe_napi.c +1481 -84
  20. package/package.json +18 -6
  21. package/prebuilds/darwin-arm64/doe_napi.node +0 -0
  22. package/prebuilds/darwin-arm64/libwebgpu_doe.dylib +0 -0
  23. package/prebuilds/darwin-arm64/metadata.json +5 -5
  24. package/prebuilds/linux-x64/metadata.json +1 -1
  25. package/scripts/generate-doe-api-docs.js +1607 -0
  26. package/scripts/generate-readme-assets.js +3 -3
  27. package/src/build_metadata.js +7 -4
  28. package/src/bun-ffi.js +1229 -474
  29. package/src/bun.js +5 -1
  30. package/src/compute.d.ts +16 -7
  31. package/src/compute.js +84 -53
  32. package/src/full.d.ts +16 -7
  33. package/src/full.js +12 -10
  34. package/src/index.js +679 -1324
  35. package/src/runtime_cli.js +17 -17
  36. package/src/shared/capabilities.js +144 -0
  37. package/src/shared/compiler-errors.js +78 -0
  38. package/src/shared/encoder-surface.js +295 -0
  39. package/src/shared/full-surface.js +514 -0
  40. package/src/shared/public-surface.js +82 -0
  41. package/src/shared/resource-lifecycle.js +120 -0
  42. package/src/shared/validation.js +495 -0
  43. package/src/webgpu_constants.js +30 -0
  44. package/support-contracts.md +2 -2
  45. package/compat-scope.md +0 -46
  46. package/layering-plan.md +0 -259
  47. package/src/auto_bind_group_layout.js +0 -32
  48. package/src/doe.d.ts +0 -184
  49. package/src/doe.js +0 -641
  50. package/zig-source-inventory.md +0 -468
package/native/doe_napi.c CHANGED
@@ -62,11 +62,13 @@ typedef uint32_t WGPUBool;
62
62
  #define WGPU_WAIT_STATUS_SUCCESS 1
63
63
  #define WGPU_WAIT_STATUS_TIMED_OUT 2
64
64
  #define WGPU_WAIT_STATUS_ERROR 3
65
+ #define WGPU_STATUS_SUCCESS 1
65
66
  #define WGPU_MAP_ASYNC_STATUS_SUCCESS 1
66
67
  #define WGPU_REQUEST_STATUS_SUCCESS 1
67
68
  #define WGPU_CALLBACK_MODE_ALLOW_PROCESS_EVENTS 2
68
69
  #define DOE_DEFAULT_TIMEOUT_NS 2000000000ULL
69
70
  #define DOE_WAIT_SLICE_NS 1000ULL
71
+ #define DOE_ERROR_BUF_CAP 512
70
72
 
71
73
  typedef struct { uint64_t id; } WGPUFuture;
72
74
  typedef struct { const char* data; size_t length; } WGPUStringView;
@@ -187,6 +189,14 @@ typedef struct {
187
189
  uint32_t immediateSize;
188
190
  } WGPUPipelineLayoutDescriptor;
189
191
 
192
+ typedef struct {
193
+ uint32_t group;
194
+ uint32_t binding;
195
+ uint32_t kind;
196
+ uint32_t addr_space;
197
+ uint32_t access;
198
+ } DoeShaderBindingInfo;
199
+
190
200
  typedef struct {
191
201
  void* nextInChain;
192
202
  WGPUStringView label;
@@ -199,6 +209,30 @@ typedef struct {
199
209
  uint32_t depthOrArrayLayers;
200
210
  } WGPUExtent3D;
201
211
 
212
+ typedef struct {
213
+ uint32_t x;
214
+ uint32_t y;
215
+ uint32_t z;
216
+ } WGPUOrigin3D;
217
+
218
+ typedef struct {
219
+ uint64_t offset;
220
+ uint32_t bytesPerRow;
221
+ uint32_t rowsPerImage;
222
+ } WGPUTexelCopyBufferLayout;
223
+
224
+ typedef struct {
225
+ WGPUTexelCopyBufferLayout layout;
226
+ WGPUBuffer buffer;
227
+ } WGPUTexelCopyBufferInfo;
228
+
229
+ typedef struct {
230
+ WGPUTexture texture;
231
+ uint32_t mipLevel;
232
+ WGPUOrigin3D origin;
233
+ uint32_t aspect;
234
+ } WGPUTexelCopyTextureInfo;
235
+
202
236
  typedef struct {
203
237
  void* nextInChain;
204
238
  WGPUStringView label;
@@ -252,6 +286,19 @@ typedef struct {
252
286
  WGPUColor clearValue;
253
287
  } WGPURenderPassColorAttachment;
254
288
 
289
+ typedef struct {
290
+ void* nextInChain;
291
+ WGPUTextureView view;
292
+ uint32_t depthLoadOp;
293
+ uint32_t depthStoreOp;
294
+ float depthClearValue;
295
+ WGPUBool depthReadOnly;
296
+ uint32_t stencilLoadOp;
297
+ uint32_t stencilStoreOp;
298
+ uint32_t stencilClearValue;
299
+ WGPUBool stencilReadOnly;
300
+ } WGPURenderPassDepthStencilAttachment;
301
+
255
302
  typedef struct {
256
303
  void* nextInChain;
257
304
  WGPUStringView label;
@@ -262,6 +309,102 @@ typedef struct {
262
309
  void* timestampWrites;
263
310
  } WGPURenderPassDescriptor;
264
311
 
312
+ typedef struct {
313
+ void* nextInChain;
314
+ WGPUStringView key;
315
+ double value;
316
+ } WGPUConstantEntry;
317
+
318
+ typedef struct {
319
+ void* nextInChain;
320
+ WGPUShaderModule module;
321
+ WGPUStringView entryPoint;
322
+ size_t constantCount;
323
+ const WGPUConstantEntry* constants;
324
+ size_t bufferCount;
325
+ const void* buffers;
326
+ } WGPURenderVertexState;
327
+
328
+ typedef struct {
329
+ void* nextInChain;
330
+ uint32_t format;
331
+ uint64_t offset;
332
+ uint32_t shaderLocation;
333
+ } WGPURenderVertexAttribute;
334
+
335
+ typedef struct {
336
+ void* nextInChain;
337
+ uint32_t stepMode;
338
+ uint64_t arrayStride;
339
+ size_t attributeCount;
340
+ const WGPURenderVertexAttribute* attributes;
341
+ } WGPURenderVertexBufferLayout;
342
+
343
+ typedef struct {
344
+ void* nextInChain;
345
+ uint32_t format;
346
+ const void* blend;
347
+ uint64_t writeMask;
348
+ } WGPURenderColorTargetState;
349
+
350
+ typedef struct {
351
+ void* nextInChain;
352
+ WGPUShaderModule module;
353
+ WGPUStringView entryPoint;
354
+ size_t constantCount;
355
+ const WGPUConstantEntry* constants;
356
+ size_t targetCount;
357
+ const WGPURenderColorTargetState* targets;
358
+ } WGPURenderFragmentState;
359
+
360
+ typedef struct {
361
+ void* nextInChain;
362
+ uint32_t topology;
363
+ uint32_t stripIndexFormat;
364
+ uint32_t frontFace;
365
+ uint32_t cullMode;
366
+ WGPUBool unclippedDepth;
367
+ } WGPURenderPrimitiveState;
368
+
369
+ typedef struct {
370
+ void* nextInChain;
371
+ uint32_t count;
372
+ uint32_t mask;
373
+ WGPUBool alphaToCoverageEnabled;
374
+ } WGPURenderMultisampleState;
375
+
376
+ typedef struct {
377
+ uint32_t compare;
378
+ uint32_t failOp;
379
+ uint32_t depthFailOp;
380
+ uint32_t passOp;
381
+ } WGPURenderStencilFaceState;
382
+
383
+ typedef struct {
384
+ void* nextInChain;
385
+ uint32_t format;
386
+ uint32_t depthWriteEnabled;
387
+ uint32_t depthCompare;
388
+ WGPURenderStencilFaceState stencilFront;
389
+ WGPURenderStencilFaceState stencilBack;
390
+ uint32_t stencilReadMask;
391
+ uint32_t stencilWriteMask;
392
+ int32_t depthBias;
393
+ float depthBiasSlopeScale;
394
+ float depthBiasClamp;
395
+ } WGPURenderDepthStencilState;
396
+
397
+ typedef struct {
398
+ void* nextInChain;
399
+ WGPUStringView label;
400
+ WGPUPipelineLayout layout;
401
+ WGPURenderVertexState vertex;
402
+ WGPURenderPrimitiveState primitive;
403
+ const WGPURenderDepthStencilState* depthStencil;
404
+ WGPURenderMultisampleState multisample;
405
+ const WGPURenderFragmentState* fragment;
406
+ } WGPURenderPipelineDescriptor;
407
+
265
408
  typedef struct {
266
409
  void* nextInChain;
267
410
  uint32_t maxTextureDimension1D;
@@ -357,9 +500,11 @@ DECL_PFN(uint32_t, wgpuInstanceWaitAny, (WGPUInstance, size_t, WGPUFutureWaitInf
357
500
  DECL_PFN(void, wgpuInstanceProcessEvents, (WGPUInstance));
358
501
  DECL_PFN(void, wgpuAdapterRelease, (WGPUAdapter));
359
502
  DECL_PFN(WGPUBool, wgpuAdapterHasFeature, (WGPUAdapter, uint32_t));
503
+ DECL_PFN(uint32_t, wgpuAdapterGetLimits, (WGPUAdapter, void*));
360
504
  DECL_PFN(WGPUFuture, wgpuAdapterRequestDevice, (WGPUAdapter, const void*, WGPURequestDeviceCallbackInfo));
361
505
  DECL_PFN(void, wgpuDeviceRelease, (WGPUDevice));
362
506
  DECL_PFN(WGPUBool, wgpuDeviceHasFeature, (WGPUDevice, uint32_t));
507
+ DECL_PFN(uint32_t, wgpuDeviceGetLimits, (WGPUDevice, void*));
363
508
  DECL_PFN(WGPUQueue, wgpuDeviceGetQueue, (WGPUDevice));
364
509
  DECL_PFN(WGPUBuffer, wgpuDeviceCreateBuffer, (WGPUDevice, const WGPUBufferDescriptor*));
365
510
  DECL_PFN(WGPUShaderModule, wgpuDeviceCreateShaderModule, (WGPUDevice, const WGPUShaderModuleDescriptor*));
@@ -377,6 +522,8 @@ DECL_PFN(WGPUCommandEncoder, wgpuDeviceCreateCommandEncoder, (WGPUDevice, const
377
522
  DECL_PFN(void, wgpuCommandEncoderRelease, (WGPUCommandEncoder));
378
523
  DECL_PFN(WGPUComputePassEncoder, wgpuCommandEncoderBeginComputePass, (WGPUCommandEncoder, const WGPUComputePassDescriptor*));
379
524
  DECL_PFN(void, wgpuCommandEncoderCopyBufferToBuffer, (WGPUCommandEncoder, WGPUBuffer, uint64_t, WGPUBuffer, uint64_t, uint64_t));
525
+ DECL_PFN(void, wgpuCommandEncoderCopyTextureToBuffer, (WGPUCommandEncoder, const WGPUTexelCopyTextureInfo*, const WGPUTexelCopyBufferInfo*, const WGPUExtent3D*));
526
+ DECL_PFN(void, doeNativeCommandEncoderCopyTextureToBuffer, (WGPUCommandEncoder, WGPUTexture, uint32_t, WGPUBuffer, uint64_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t));
380
527
  DECL_PFN(WGPUCommandBuffer, wgpuCommandEncoderFinish, (WGPUCommandEncoder, const WGPUCommandBufferDescriptor*));
381
528
  DECL_PFN(void, wgpuComputePassEncoderSetPipeline, (WGPUComputePassEncoder, WGPUComputePipeline));
382
529
  DECL_PFN(void, wgpuComputePassEncoderSetBindGroup, (WGPUComputePassEncoder, uint32_t, WGPUBindGroup, size_t, const uint32_t*));
@@ -403,10 +550,25 @@ DECL_PFN(WGPURenderPipeline, wgpuDeviceCreateRenderPipeline, (WGPUDevice, const
403
550
  DECL_PFN(void, wgpuRenderPipelineRelease, (WGPURenderPipeline));
404
551
  DECL_PFN(WGPURenderPassEncoder, wgpuCommandEncoderBeginRenderPass, (WGPUCommandEncoder, const WGPURenderPassDescriptor*));
405
552
  DECL_PFN(void, wgpuRenderPassEncoderSetPipeline, (WGPURenderPassEncoder, WGPURenderPipeline));
553
+ DECL_PFN(void, wgpuRenderPassEncoderSetBindGroup, (WGPURenderPassEncoder, uint32_t, WGPUBindGroup, size_t, const uint32_t*));
554
+ DECL_PFN(void, wgpuRenderPassEncoderSetVertexBuffer, (WGPURenderPassEncoder, uint32_t, WGPUBuffer, uint64_t, uint64_t));
555
+ DECL_PFN(void, wgpuRenderPassEncoderSetIndexBuffer, (WGPURenderPassEncoder, WGPUBuffer, uint32_t, uint64_t, uint64_t));
406
556
  DECL_PFN(void, wgpuRenderPassEncoderDraw, (WGPURenderPassEncoder, uint32_t, uint32_t, uint32_t, uint32_t));
557
+ DECL_PFN(void, wgpuRenderPassEncoderDrawIndexed, (WGPURenderPassEncoder, uint32_t, uint32_t, uint32_t, int32_t, uint32_t));
407
558
  DECL_PFN(void, wgpuRenderPassEncoderEnd, (WGPURenderPassEncoder));
408
559
  DECL_PFN(void, wgpuRenderPassEncoderRelease, (WGPURenderPassEncoder));
409
- DECL_PFN(uint32_t, wgpuDeviceGetLimits, (WGPUDevice, void*));
560
+ DECL_PFN(uint32_t, doeNativeAdapterGetLimits, (WGPUAdapter, void*));
561
+ DECL_PFN(uint32_t, doeNativeDeviceGetLimits, (WGPUDevice, void*));
562
+ DECL_PFN(uint32_t, doeNativeAdapterHasFeature, (WGPUAdapter, uint32_t));
563
+ DECL_PFN(uint32_t, doeNativeDeviceHasFeature, (WGPUDevice, uint32_t));
564
+ DECL_PFN(size_t, doeNativeCopyLastErrorMessage, (char*, size_t));
565
+ DECL_PFN(size_t, doeNativeCopyLastErrorStage, (char*, size_t));
566
+ DECL_PFN(size_t, doeNativeCopyLastErrorKind, (char*, size_t));
567
+ DECL_PFN(uint32_t, doeNativeGetLastErrorLine, (void));
568
+ DECL_PFN(uint32_t, doeNativeGetLastErrorColumn, (void));
569
+ DECL_PFN(uint32_t, doeNativeCheckShaderSource, (const char*, size_t));
570
+ DECL_PFN(size_t, doeNativeShaderModuleGetBindings, (WGPUShaderModule, DoeShaderBindingInfo*, size_t));
571
+ DECL_PFN(WGPUFuture, doeNativeAdapterRequestDevice, (WGPUAdapter, const void*, WGPURequestDeviceCallbackInfo));
410
572
 
411
573
  /* Flat helpers are optional. When absent, the addon assembles the callback-info
412
574
  * structs directly and calls the standard WebGPU request entrypoints. */
@@ -414,6 +576,10 @@ DECL_PFN(WGPUFuture, doeRequestAdapterFlat, (WGPUInstance, const void*, uint32_t
414
576
  DECL_PFN(WGPUFuture, doeRequestDeviceFlat, (WGPUAdapter, const void*, uint32_t, WGPURequestDeviceCallback, void*, void*));
415
577
  DECL_PFN(void, doeNativeQueueFlush, (void*));
416
578
  DECL_PFN(void, doeNativeComputeDispatchFlush, (void*, void*, void**, uint32_t, uint32_t, uint32_t, uint32_t, void*, uint64_t, void*, uint64_t, uint64_t));
579
+ DECL_PFN(WGPUQuerySet, doeNativeDeviceCreateQuerySet, (WGPUDevice, uint32_t, uint32_t));
580
+ DECL_PFN(void, doeNativeCommandEncoderWriteTimestamp, (WGPUCommandEncoder, WGPUQuerySet, uint32_t));
581
+ DECL_PFN(void, doeNativeCommandEncoderResolveQuerySet, (WGPUCommandEncoder, WGPUQuerySet, uint32_t, uint32_t, WGPUBuffer, uint64_t));
582
+ DECL_PFN(void, doeNativeQuerySetDestroy, (WGPUQuerySet));
417
583
  typedef struct {
418
584
  void* nextInChain;
419
585
  uint32_t mode;
@@ -426,6 +592,25 @@ typedef WGPUFuture (*PFN_wgpuBufferMapAsync2)(WGPUBuffer, uint64_t, size_t, size
426
592
  static PFN_wgpuBufferMapAsync2 pfn_wgpuBufferMapAsync2 = NULL;
427
593
 
428
594
  static void* g_lib = NULL;
595
+ static uint64_t g_timeout_ns = DOE_DEFAULT_TIMEOUT_NS;
596
+
597
+ static uint64_t current_timeout_ns(void) {
598
+ return g_timeout_ns;
599
+ }
600
+
601
+ static void copy_library_error_message(char* out, size_t out_len) {
602
+ if (!out || out_len == 0) return;
603
+ out[0] = '\0';
604
+ if (!pfn_doeNativeCopyLastErrorMessage) return;
605
+ pfn_doeNativeCopyLastErrorMessage(out, out_len);
606
+ }
607
+
608
+ static void copy_library_error_meta(PFN_doeNativeCopyLastErrorMessage fn, char* out, size_t out_len) {
609
+ if (!out || out_len == 0) return;
610
+ out[0] = '\0';
611
+ if (!fn) return;
612
+ fn(out, out_len);
613
+ }
429
614
 
430
615
  static uint64_t monotonic_now_ns(void) {
431
616
  #ifdef _WIN32
@@ -463,12 +648,33 @@ static int process_events_until(WGPUInstance inst, volatile uint32_t* done, uint
463
648
  return 1;
464
649
  }
465
650
 
651
+ static void copy_string_view_message(WGPUStringView message, char* out, size_t out_len) {
652
+ if (!out || out_len == 0) return;
653
+ out[0] = '\0';
654
+ if (!message.data || message.length == 0) return;
655
+ size_t copy_len = message.length;
656
+ if (copy_len >= out_len) copy_len = out_len - 1;
657
+ memcpy(out, message.data, copy_len);
658
+ out[copy_len] = '\0';
659
+ }
660
+
661
+ static napi_value throw_status_error(napi_env env, const char* code, const char* prefix, uint32_t status, const char* detail) {
662
+ char msg[DOE_ERROR_BUF_CAP];
663
+ if (detail && detail[0] != '\0') {
664
+ snprintf(msg, sizeof(msg), "%s (status=%u, detail=%s)", prefix, status, detail);
665
+ } else {
666
+ snprintf(msg, sizeof(msg), "%s (status=%u)", prefix, status);
667
+ }
668
+ napi_throw_error(env, code, msg);
669
+ return NULL;
670
+ }
671
+
466
672
  /* ================================================================
467
673
  * N-API utility helpers
468
674
  * ================================================================ */
469
675
 
470
- #define NAPI_THROW(env, msg) do { napi_throw_error(env, NULL, msg); return NULL; } while(0)
471
- #define MAX_NAPI_ARGS 8
676
+ #define NAPI_THROW(env, msg) do { napi_throw_error(env, "DOE_ERROR", msg); return NULL; } while(0)
677
+ #define MAX_NAPI_ARGS 16
472
678
  #define NAPI_ASSERT_ARGC(env, info, n) \
473
679
  size_t _argc = (n); napi_value _args[MAX_NAPI_ARGS]; \
474
680
  if ((n) > MAX_NAPI_ARGS) NAPI_THROW(env, "too many args"); \
@@ -514,6 +720,20 @@ static int64_t get_int64_prop(napi_env env, napi_value obj, const char* key) {
514
720
  return out;
515
721
  }
516
722
 
723
+ static int64_t get_int64_value(napi_env env, napi_value value) {
724
+ int64_t out = 0;
725
+ napi_get_value_int64(env, value, &out);
726
+ return out;
727
+ }
728
+
729
+ static double get_double_prop(napi_env env, napi_value obj, const char* key) {
730
+ napi_value val;
731
+ napi_get_named_property(env, obj, key, &val);
732
+ double out = 0.0;
733
+ napi_get_value_double(env, val, &out);
734
+ return out;
735
+ }
736
+
517
737
  static bool get_bool_prop(napi_env env, napi_value obj, const char* key) {
518
738
  napi_value val;
519
739
  napi_get_named_property(env, obj, key, &val);
@@ -537,6 +757,16 @@ static napi_value get_prop(napi_env env, napi_value obj, const char* key) {
537
757
  return val;
538
758
  }
539
759
 
760
+ static char* dup_string_value(napi_env env, napi_value value, size_t* out_len) {
761
+ size_t len = 0;
762
+ napi_get_value_string_utf8(env, value, NULL, 0, &len);
763
+ char* out = (char*)malloc(len + 1);
764
+ if (!out) return NULL;
765
+ napi_get_value_string_utf8(env, value, out, len + 1, &len);
766
+ if (out_len) *out_len = len;
767
+ return out;
768
+ }
769
+
540
770
  static napi_valuetype prop_type(napi_env env, napi_value obj, const char* key) {
541
771
  napi_value val;
542
772
  napi_get_named_property(env, obj, key, &val);
@@ -563,6 +793,15 @@ static napi_value doe_load_library(napi_env env, napi_callback_info info) {
563
793
  free(path);
564
794
  if (!g_lib) NAPI_THROW(env, "Failed to load libwebgpu_doe");
565
795
 
796
+ const char* timeout_env = getenv("DOE_TIMEOUT_MS");
797
+ if (timeout_env && timeout_env[0] != '\0') {
798
+ char* end = NULL;
799
+ unsigned long parsed = strtoul(timeout_env, &end, 10);
800
+ if (end && *end == '\0') {
801
+ g_timeout_ns = (uint64_t)parsed * 1000000ULL;
802
+ }
803
+ }
804
+
566
805
  LOAD_SYM(wgpuCreateInstance);
567
806
  LOAD_SYM(wgpuInstanceRelease);
568
807
  LOAD_SYM(wgpuInstanceRequestAdapter);
@@ -570,6 +809,7 @@ static napi_value doe_load_library(napi_env env, napi_callback_info info) {
570
809
  LOAD_SYM(wgpuInstanceProcessEvents);
571
810
  LOAD_SYM(wgpuAdapterRelease);
572
811
  LOAD_SYM(wgpuAdapterHasFeature);
812
+ LOAD_SYM(wgpuAdapterGetLimits);
573
813
  LOAD_SYM(wgpuAdapterRequestDevice);
574
814
  LOAD_SYM(wgpuDeviceRelease);
575
815
  LOAD_SYM(wgpuDeviceHasFeature);
@@ -590,6 +830,8 @@ static napi_value doe_load_library(napi_env env, napi_callback_info info) {
590
830
  LOAD_SYM(wgpuCommandEncoderRelease);
591
831
  LOAD_SYM(wgpuCommandEncoderBeginComputePass);
592
832
  LOAD_SYM(wgpuCommandEncoderCopyBufferToBuffer);
833
+ LOAD_SYM(wgpuCommandEncoderCopyTextureToBuffer);
834
+ LOAD_SYM(doeNativeCommandEncoderCopyTextureToBuffer);
593
835
  LOAD_SYM(wgpuCommandEncoderFinish);
594
836
  LOAD_SYM(wgpuComputePassEncoderSetPipeline);
595
837
  LOAD_SYM(wgpuComputePassEncoderSetBindGroup);
@@ -616,15 +858,38 @@ static napi_value doe_load_library(napi_env env, napi_callback_info info) {
616
858
  LOAD_SYM(wgpuRenderPipelineRelease);
617
859
  LOAD_SYM(wgpuCommandEncoderBeginRenderPass);
618
860
  LOAD_SYM(wgpuRenderPassEncoderSetPipeline);
861
+ LOAD_SYM(wgpuRenderPassEncoderSetBindGroup);
862
+ LOAD_SYM(wgpuRenderPassEncoderSetVertexBuffer);
863
+ LOAD_SYM(wgpuRenderPassEncoderSetIndexBuffer);
619
864
  LOAD_SYM(wgpuRenderPassEncoderDraw);
865
+ LOAD_SYM(wgpuRenderPassEncoderDrawIndexed);
620
866
  LOAD_SYM(wgpuRenderPassEncoderEnd);
621
867
  LOAD_SYM(wgpuRenderPassEncoderRelease);
868
+ LOAD_SYM(wgpuAdapterGetLimits);
869
+ LOAD_SYM(wgpuAdapterHasFeature);
870
+ LOAD_SYM(wgpuDeviceHasFeature);
622
871
  LOAD_SYM(wgpuDeviceGetLimits);
872
+ pfn_doeNativeAdapterGetLimits = (PFN_doeNativeAdapterGetLimits)LIB_SYM(g_lib, "doeNativeAdapterGetLimits");
873
+ pfn_doeNativeDeviceGetLimits = (PFN_doeNativeDeviceGetLimits)LIB_SYM(g_lib, "doeNativeDeviceGetLimits");
874
+ pfn_doeNativeAdapterHasFeature = (PFN_doeNativeAdapterHasFeature)LIB_SYM(g_lib, "doeNativeAdapterHasFeature");
875
+ pfn_doeNativeDeviceHasFeature = (PFN_doeNativeDeviceHasFeature)LIB_SYM(g_lib, "doeNativeDeviceHasFeature");
876
+ pfn_doeNativeCopyLastErrorMessage = (PFN_doeNativeCopyLastErrorMessage)LIB_SYM(g_lib, "doeNativeCopyLastErrorMessage");
877
+ pfn_doeNativeCopyLastErrorStage = (PFN_doeNativeCopyLastErrorStage)LIB_SYM(g_lib, "doeNativeCopyLastErrorStage");
878
+ pfn_doeNativeCopyLastErrorKind = (PFN_doeNativeCopyLastErrorKind)LIB_SYM(g_lib, "doeNativeCopyLastErrorKind");
879
+ pfn_doeNativeGetLastErrorLine = (PFN_doeNativeGetLastErrorLine)LIB_SYM(g_lib, "doeNativeGetLastErrorLine");
880
+ pfn_doeNativeGetLastErrorColumn = (PFN_doeNativeGetLastErrorColumn)LIB_SYM(g_lib, "doeNativeGetLastErrorColumn");
881
+ pfn_doeNativeCheckShaderSource = (PFN_doeNativeCheckShaderSource)LIB_SYM(g_lib, "doeNativeCheckShaderSource");
882
+ pfn_doeNativeShaderModuleGetBindings = (PFN_doeNativeShaderModuleGetBindings)LIB_SYM(g_lib, "doeNativeShaderModuleGetBindings");
883
+ pfn_doeNativeAdapterRequestDevice = (PFN_doeNativeAdapterRequestDevice)LIB_SYM(g_lib, "doeNativeAdapterRequestDevice");
623
884
  pfn_doeRequestAdapterFlat = (PFN_doeRequestAdapterFlat)LIB_SYM(g_lib, "doeRequestAdapterFlat");
624
885
  pfn_doeRequestDeviceFlat = (PFN_doeRequestDeviceFlat)LIB_SYM(g_lib, "doeRequestDeviceFlat");
625
886
  pfn_wgpuBufferMapAsync2 = (PFN_wgpuBufferMapAsync2)LIB_SYM(g_lib, "wgpuBufferMapAsync");
626
887
  pfn_doeNativeQueueFlush = (PFN_doeNativeQueueFlush)LIB_SYM(g_lib, "doeNativeQueueFlush");
627
888
  pfn_doeNativeComputeDispatchFlush = (PFN_doeNativeComputeDispatchFlush)LIB_SYM(g_lib, "doeNativeComputeDispatchFlush");
889
+ pfn_doeNativeDeviceCreateQuerySet = (PFN_doeNativeDeviceCreateQuerySet)LIB_SYM(g_lib, "doeNativeDeviceCreateQuerySet");
890
+ pfn_doeNativeCommandEncoderWriteTimestamp = (PFN_doeNativeCommandEncoderWriteTimestamp)LIB_SYM(g_lib, "doeNativeCommandEncoderWriteTimestamp");
891
+ pfn_doeNativeCommandEncoderResolveQuerySet = (PFN_doeNativeCommandEncoderResolveQuerySet)LIB_SYM(g_lib, "doeNativeCommandEncoderResolveQuerySet");
892
+ pfn_doeNativeQuerySetDestroy = (PFN_doeNativeQuerySetDestroy)LIB_SYM(g_lib, "doeNativeQuerySetDestroy");
628
893
 
629
894
  /* Validate all critical function pointers were resolved. */
630
895
  if (!pfn_wgpuCreateInstance || !pfn_wgpuInstanceRelease || !pfn_wgpuInstanceRequestAdapter ||
@@ -674,14 +939,16 @@ typedef struct {
674
939
  uint32_t status;
675
940
  WGPUAdapter adapter;
676
941
  uint32_t done;
942
+ char message[DOE_ERROR_BUF_CAP];
677
943
  } AdapterRequestResult;
678
944
 
679
945
  static void adapter_callback(uint32_t status, WGPUAdapter adapter,
680
946
  WGPUStringView message, void* userdata1, void* userdata2) {
681
- (void)message; (void)userdata2;
947
+ (void)userdata2;
682
948
  AdapterRequestResult* r = (AdapterRequestResult*)userdata1;
683
949
  r->status = status;
684
950
  r->adapter = adapter;
951
+ copy_string_view_message(message, r->message, sizeof(r->message));
685
952
  r->done = 1;
686
953
  }
687
954
 
@@ -691,7 +958,7 @@ static napi_value doe_request_adapter(napi_env env, napi_callback_info info) {
691
958
  WGPUInstance inst = unwrap_ptr(env, _args[0]);
692
959
  if (!inst) NAPI_THROW(env, "Invalid instance");
693
960
 
694
- AdapterRequestResult result = {0, NULL, 0};
961
+ AdapterRequestResult result = {0};
695
962
  WGPUFuture future;
696
963
  if (pfn_doeRequestAdapterFlat) {
697
964
  future = pfn_doeRequestAdapterFlat(
@@ -707,9 +974,10 @@ static napi_value doe_request_adapter(napi_env env, napi_callback_info info) {
707
974
  future = pfn_wgpuInstanceRequestAdapter(inst, NULL, callback_info);
708
975
  }
709
976
  if (future.id == 0) NAPI_THROW(env, "requestAdapter future unavailable");
710
- if (!process_events_until(inst, &result.done, DOE_DEFAULT_TIMEOUT_NS) ||
711
- result.status != WGPU_REQUEST_STATUS_SUCCESS || !result.adapter)
712
- NAPI_THROW(env, "requestAdapter failed");
977
+ if (!process_events_until(inst, &result.done, current_timeout_ns()))
978
+ return throw_status_error(env, "DOE_REQUEST_ADAPTER_TIMEOUT", "requestAdapter timed out", result.status, result.message);
979
+ if (result.status != WGPU_REQUEST_STATUS_SUCCESS || !result.adapter)
980
+ return throw_status_error(env, "DOE_REQUEST_ADAPTER_ERROR", "requestAdapter failed", result.status, result.message);
713
981
 
714
982
  return wrap_ptr(env, result.adapter);
715
983
  }
@@ -729,14 +997,16 @@ typedef struct {
729
997
  uint32_t status;
730
998
  WGPUDevice device;
731
999
  uint32_t done;
1000
+ char message[DOE_ERROR_BUF_CAP];
732
1001
  } DeviceRequestResult;
733
1002
 
734
1003
  static void device_callback(uint32_t status, WGPUDevice device,
735
1004
  WGPUStringView message, void* userdata1, void* userdata2) {
736
- (void)message; (void)userdata2;
1005
+ (void)userdata2;
737
1006
  DeviceRequestResult* r = (DeviceRequestResult*)userdata1;
738
1007
  r->status = status;
739
1008
  r->device = device;
1009
+ copy_string_view_message(message, r->message, sizeof(r->message));
740
1010
  r->done = 1;
741
1011
  }
742
1012
 
@@ -747,25 +1017,22 @@ static napi_value doe_request_device(napi_env env, napi_callback_info info) {
747
1017
  WGPUAdapter adapter = unwrap_ptr(env, _args[1]);
748
1018
  if (!inst || !adapter) NAPI_THROW(env, "Invalid instance or adapter");
749
1019
 
750
- DeviceRequestResult result = {0, NULL, 0};
751
- WGPUFuture future;
752
- if (pfn_doeRequestDeviceFlat) {
753
- future = pfn_doeRequestDeviceFlat(
754
- adapter, NULL, WGPU_CALLBACK_MODE_ALLOW_PROCESS_EVENTS, device_callback, &result, NULL);
755
- } else {
756
- const WGPURequestDeviceCallbackInfo callback_info = {
757
- .nextInChain = NULL,
758
- .mode = WGPU_CALLBACK_MODE_ALLOW_PROCESS_EVENTS,
759
- .callback = device_callback,
760
- .userdata1 = &result,
761
- .userdata2 = NULL,
762
- };
763
- future = pfn_wgpuAdapterRequestDevice(adapter, NULL, callback_info);
764
- }
1020
+ DeviceRequestResult result = {0};
1021
+ const WGPURequestDeviceCallbackInfo callback_info = {
1022
+ .nextInChain = NULL,
1023
+ .mode = WGPU_CALLBACK_MODE_ALLOW_PROCESS_EVENTS,
1024
+ .callback = device_callback,
1025
+ .userdata1 = &result,
1026
+ .userdata2 = NULL,
1027
+ };
1028
+ WGPUFuture future = pfn_doeNativeAdapterRequestDevice
1029
+ ? pfn_doeNativeAdapterRequestDevice(adapter, NULL, callback_info)
1030
+ : pfn_wgpuAdapterRequestDevice(adapter, NULL, callback_info);
765
1031
  if (future.id == 0) NAPI_THROW(env, "requestDevice future unavailable");
766
- if (!process_events_until(inst, &result.done, DOE_DEFAULT_TIMEOUT_NS) ||
767
- result.status != WGPU_REQUEST_STATUS_SUCCESS || !result.device)
768
- NAPI_THROW(env, "requestDevice failed");
1032
+ if (!process_events_until(inst, &result.done, current_timeout_ns()))
1033
+ return throw_status_error(env, "DOE_REQUEST_DEVICE_TIMEOUT", "requestDevice timed out", result.status, result.message);
1034
+ if (result.status != WGPU_REQUEST_STATUS_SUCCESS || !result.device)
1035
+ return throw_status_error(env, "DOE_REQUEST_DEVICE_ERROR", "requestDevice failed", result.status, result.message);
769
1036
 
770
1037
  return wrap_ptr(env, result.device);
771
1038
  }
@@ -824,26 +1091,30 @@ static napi_value doe_buffer_unmap(napi_env env, napi_callback_info info) {
824
1091
  typedef struct {
825
1092
  uint32_t status;
826
1093
  uint32_t done;
1094
+ char message[DOE_ERROR_BUF_CAP];
827
1095
  } BufferMapResult;
828
1096
 
829
1097
  typedef struct {
830
1098
  uint32_t status;
831
1099
  uint32_t done;
1100
+ char message[DOE_ERROR_BUF_CAP];
832
1101
  } QueueWorkDoneResult;
833
1102
 
834
1103
  static void buffer_map_callback(uint32_t status, WGPUStringView message,
835
1104
  void* userdata1, void* userdata2) {
836
- (void)message; (void)userdata2;
1105
+ (void)userdata2;
837
1106
  BufferMapResult* r = (BufferMapResult*)userdata1;
838
1107
  r->status = status;
1108
+ copy_string_view_message(message, r->message, sizeof(r->message));
839
1109
  r->done = 1;
840
1110
  }
841
1111
 
842
1112
  static void queue_work_done_callback(uint32_t status, WGPUStringView message,
843
1113
  void* userdata1, void* userdata2) {
844
- (void)message; (void)userdata2;
1114
+ (void)userdata2;
845
1115
  QueueWorkDoneResult* r = (QueueWorkDoneResult*)userdata1;
846
1116
  r->status = status;
1117
+ copy_string_view_message(message, r->message, sizeof(r->message));
847
1118
  r->done = 1;
848
1119
  }
849
1120
 
@@ -853,13 +1124,14 @@ static napi_value doe_buffer_map_sync(napi_env env, napi_callback_info info) {
853
1124
  CHECK_LIB_LOADED(env);
854
1125
  WGPUInstance inst = unwrap_ptr(env, _args[0]);
855
1126
  WGPUBuffer buf = unwrap_ptr(env, _args[1]);
1127
+ if (!inst || !buf) NAPI_THROW(env, "bufferMapSync requires instance and buffer");
856
1128
  uint32_t mode;
857
1129
  napi_get_value_uint32(env, _args[2], &mode);
858
1130
  int64_t offset_i, size_i;
859
1131
  napi_get_value_int64(env, _args[3], &offset_i);
860
1132
  napi_get_value_int64(env, _args[4], &size_i);
861
1133
 
862
- BufferMapResult result = {0, 0};
1134
+ BufferMapResult result = {0};
863
1135
  WGPUBufferMapCallbackInfo cb_info = {
864
1136
  .nextInChain = NULL,
865
1137
  .mode = WGPU_CALLBACK_MODE_ALLOW_PROCESS_EVENTS,
@@ -871,9 +1143,10 @@ static napi_value doe_buffer_map_sync(napi_env env, napi_callback_info info) {
871
1143
  WGPUFuture future = pfn_wgpuBufferMapAsync2(buf, (uint64_t)mode,
872
1144
  (size_t)offset_i, (size_t)size_i, cb_info);
873
1145
  if (future.id == 0) NAPI_THROW(env, "bufferMapAsync future unavailable");
874
- if (!process_events_until(inst, &result.done, DOE_DEFAULT_TIMEOUT_NS) ||
875
- result.status != WGPU_MAP_ASYNC_STATUS_SUCCESS)
876
- NAPI_THROW(env, "bufferMapAsync failed");
1146
+ if (!process_events_until(inst, &result.done, current_timeout_ns()))
1147
+ return throw_status_error(env, "DOE_BUFFER_MAP_TIMEOUT", "bufferMapAsync timed out", result.status, result.message);
1148
+ if (result.status != WGPU_MAP_ASYNC_STATUS_SUCCESS)
1149
+ return throw_status_error(env, "DOE_BUFFER_MAP_ERROR", "bufferMapAsync failed", result.status, result.message);
877
1150
 
878
1151
  napi_value ok;
879
1152
  napi_get_boolean(env, true, &ok);
@@ -889,17 +1162,90 @@ static napi_value doe_buffer_get_mapped_range(napi_env env, napi_callback_info i
889
1162
  napi_get_value_int64(env, _args[1], &offset_i);
890
1163
  napi_get_value_int64(env, _args[2], &size_i);
891
1164
 
892
- const void* data = pfn_wgpuBufferGetConstMappedRange(buf, (size_t)offset_i, (size_t)size_i);
1165
+ void* data = pfn_wgpuBufferGetMappedRange(buf, (size_t)offset_i, (size_t)size_i);
1166
+ if (!data) {
1167
+ data = (void*)pfn_wgpuBufferGetConstMappedRange(buf, (size_t)offset_i, (size_t)size_i);
1168
+ }
893
1169
  if (!data) NAPI_THROW(env, "getMappedRange returned NULL");
894
1170
 
895
- /* Copy native data into a JS ArrayBuffer */
896
- void* ab_data = NULL;
897
1171
  napi_value ab;
898
- napi_create_arraybuffer(env, (size_t)size_i, &ab_data, &ab);
899
- memcpy(ab_data, data, (size_t)size_i);
1172
+ napi_create_external_arraybuffer(env, data, (size_t)size_i, NULL, NULL, &ab);
900
1173
  return ab;
901
1174
  }
902
1175
 
1176
+ static napi_value doe_buffer_write_mapped_range(napi_env env, napi_callback_info info) {
1177
+ NAPI_ASSERT_ARGC(env, info, 3);
1178
+ CHECK_LIB_LOADED(env);
1179
+ WGPUBuffer buf = unwrap_ptr(env, _args[0]);
1180
+ int64_t offset_i = 0;
1181
+ napi_get_value_int64(env, _args[1], &offset_i);
1182
+
1183
+ void* data = NULL;
1184
+ size_t byte_length = 0;
1185
+ bool is_typed_array = false;
1186
+ napi_is_typedarray(env, _args[2], &is_typed_array);
1187
+ if (is_typed_array) {
1188
+ napi_typedarray_type ta_type;
1189
+ size_t ta_length = 0;
1190
+ void* ta_data = NULL;
1191
+ napi_value ta_arraybuffer;
1192
+ size_t ta_byte_offset = 0;
1193
+ napi_get_typedarray_info(env, _args[2], &ta_type, &ta_length, &ta_data, &ta_arraybuffer, &ta_byte_offset);
1194
+ data = ta_data;
1195
+ switch (ta_type) {
1196
+ case napi_uint16_array: case napi_int16_array: byte_length = ta_length * 2; break;
1197
+ case napi_uint32_array: case napi_int32_array: case napi_float32_array: byte_length = ta_length * 4; break;
1198
+ case napi_float64_array: case napi_bigint64_array: case napi_biguint64_array: byte_length = ta_length * 8; break;
1199
+ default: byte_length = ta_length; break;
1200
+ }
1201
+ } else {
1202
+ bool is_ab = false;
1203
+ napi_is_arraybuffer(env, _args[2], &is_ab);
1204
+ if (is_ab) {
1205
+ napi_get_arraybuffer_info(env, _args[2], &data, &byte_length);
1206
+ } else {
1207
+ bool is_buffer = false;
1208
+ napi_is_buffer(env, _args[2], &is_buffer);
1209
+ if (is_buffer) {
1210
+ napi_get_buffer_info(env, _args[2], &data, &byte_length);
1211
+ } else {
1212
+ NAPI_THROW(env, "bufferWriteMappedRange: data must be TypedArray, ArrayBuffer, or Buffer");
1213
+ }
1214
+ }
1215
+ }
1216
+
1217
+ void* mapped = pfn_wgpuBufferGetMappedRange(buf, (size_t)offset_i, byte_length);
1218
+ if (!mapped) NAPI_THROW(env, "bufferWriteMappedRange: mapped range unavailable");
1219
+ memcpy(mapped, data, byte_length);
1220
+ return NULL;
1221
+ }
1222
+
1223
+ static napi_value doe_buffer_read_indirect_counts(napi_env env, napi_callback_info info) {
1224
+ NAPI_ASSERT_ARGC(env, info, 2);
1225
+ CHECK_LIB_LOADED(env);
1226
+ WGPUBuffer buf = unwrap_ptr(env, _args[0]);
1227
+ int64_t offset_i = 0;
1228
+ napi_get_value_int64(env, _args[1], &offset_i);
1229
+ if (!buf) NAPI_THROW(env, "bufferReadIndirectCounts requires buffer");
1230
+ if (offset_i < 0) NAPI_THROW(env, "bufferReadIndirectCounts offset must be non-negative");
1231
+
1232
+ const uint32_t* counts = (const uint32_t*)pfn_wgpuBufferGetConstMappedRange(buf, (size_t)offset_i, 3 * sizeof(uint32_t));
1233
+ if (!counts) NAPI_THROW(env, "bufferReadIndirectCounts: unable to read indirect data");
1234
+
1235
+ napi_value result;
1236
+ napi_create_object(env, &result);
1237
+ napi_value x;
1238
+ napi_value y;
1239
+ napi_value z;
1240
+ napi_create_uint32(env, counts[0], &x);
1241
+ napi_create_uint32(env, counts[1], &y);
1242
+ napi_create_uint32(env, counts[2], &z);
1243
+ napi_set_named_property(env, result, "x", x);
1244
+ napi_set_named_property(env, result, "y", y);
1245
+ napi_set_named_property(env, result, "z", z);
1246
+ return result;
1247
+ }
1248
+
903
1249
  /* bufferAssertMappedPrefixF32(buffer, expected, count) */
904
1250
  static napi_value doe_buffer_assert_mapped_prefix_f32(napi_env env, napi_callback_info info) {
905
1251
  NAPI_ASSERT_ARGC(env, info, 3);
@@ -951,10 +1297,100 @@ static napi_value doe_create_shader_module(napi_env env, napi_callback_info info
951
1297
 
952
1298
  WGPUShaderModule mod = pfn_wgpuDeviceCreateShaderModule(device, &desc);
953
1299
  free(code);
954
- if (!mod) NAPI_THROW(env, "createShaderModule failed (WGSL translation or compilation error — check stderr for details)");
1300
+ if (!mod) {
1301
+ char msg[DOE_ERROR_BUF_CAP];
1302
+ char stage[64];
1303
+ char kind[64];
1304
+ copy_library_error_message(msg, sizeof(msg));
1305
+ copy_library_error_meta(pfn_doeNativeCopyLastErrorStage, stage, sizeof(stage));
1306
+ copy_library_error_meta(pfn_doeNativeCopyLastErrorKind, kind, sizeof(kind));
1307
+ if (msg[0] != '\0') {
1308
+ char full_msg[DOE_ERROR_BUF_CAP];
1309
+ if (stage[0] != '\0' && kind[0] != '\0') {
1310
+ snprintf(full_msg, sizeof(full_msg), "[%s/%s] %s", stage, kind, msg);
1311
+ } else if (stage[0] != '\0') {
1312
+ snprintf(full_msg, sizeof(full_msg), "[%s] %s", stage, msg);
1313
+ } else {
1314
+ snprintf(full_msg, sizeof(full_msg), "%s", msg);
1315
+ }
1316
+ napi_throw_error(env, "DOE_SHADER_MODULE_ERROR", full_msg);
1317
+ } else {
1318
+ napi_throw_error(env, "DOE_SHADER_MODULE_ERROR", "createShaderModule failed (WGSL translation or compilation error)");
1319
+ }
1320
+ return NULL;
1321
+ }
955
1322
  return wrap_ptr(env, mod);
956
1323
  }
957
1324
 
1325
+ static napi_value doe_check_shader_source(napi_env env, napi_callback_info info) {
1326
+ NAPI_ASSERT_ARGC(env, info, 1);
1327
+ CHECK_LIB_LOADED(env);
1328
+ napi_valuetype value_type;
1329
+ if (napi_typeof(env, _args[0], &value_type) != napi_ok || value_type != napi_string) {
1330
+ NAPI_THROW(env, "checkShaderSource requires a WGSL source string");
1331
+ }
1332
+ napi_value result;
1333
+ napi_create_object(env, &result);
1334
+ if (!pfn_doeNativeCheckShaderSource) {
1335
+ napi_value ok;
1336
+ napi_get_boolean(env, true, &ok);
1337
+ napi_set_named_property(env, result, "ok", ok);
1338
+ return result;
1339
+ }
1340
+
1341
+ size_t code_len = 0;
1342
+ napi_get_value_string_utf8(env, _args[0], NULL, 0, &code_len);
1343
+ char* code = (char*)malloc(code_len + 1);
1344
+ if (!code) NAPI_THROW(env, "checkShaderSource: out of memory");
1345
+ napi_get_value_string_utf8(env, _args[0], code, code_len + 1, &code_len);
1346
+
1347
+ const uint32_t ok_status = pfn_doeNativeCheckShaderSource(code, code_len);
1348
+ free(code);
1349
+
1350
+ napi_value ok;
1351
+ napi_get_boolean(env, ok_status != 0, &ok);
1352
+ napi_set_named_property(env, result, "ok", ok);
1353
+ if (ok_status != 0) return result;
1354
+
1355
+ char message[DOE_ERROR_BUF_CAP];
1356
+ char stage[64];
1357
+ char kind[64];
1358
+ copy_library_error_message(message, sizeof(message));
1359
+ copy_library_error_meta(pfn_doeNativeCopyLastErrorStage, stage, sizeof(stage));
1360
+ copy_library_error_meta(pfn_doeNativeCopyLastErrorKind, kind, sizeof(kind));
1361
+
1362
+ napi_value message_val;
1363
+ napi_create_string_utf8(env, message, NAPI_AUTO_LENGTH, &message_val);
1364
+ napi_set_named_property(env, result, "message", message_val);
1365
+ if (stage[0] != '\0') {
1366
+ napi_value stage_val;
1367
+ napi_create_string_utf8(env, stage, NAPI_AUTO_LENGTH, &stage_val);
1368
+ napi_set_named_property(env, result, "stage", stage_val);
1369
+ }
1370
+ if (kind[0] != '\0') {
1371
+ napi_value kind_val;
1372
+ napi_create_string_utf8(env, kind, NAPI_AUTO_LENGTH, &kind_val);
1373
+ napi_set_named_property(env, result, "kind", kind_val);
1374
+ }
1375
+ if (pfn_doeNativeGetLastErrorLine) {
1376
+ uint32_t line = pfn_doeNativeGetLastErrorLine();
1377
+ if (line > 0) {
1378
+ napi_value line_val;
1379
+ napi_create_uint32(env, line, &line_val);
1380
+ napi_set_named_property(env, result, "line", line_val);
1381
+ }
1382
+ }
1383
+ if (pfn_doeNativeGetLastErrorColumn) {
1384
+ uint32_t col = pfn_doeNativeGetLastErrorColumn();
1385
+ if (col > 0) {
1386
+ napi_value col_val;
1387
+ napi_create_uint32(env, col, &col_val);
1388
+ napi_set_named_property(env, result, "column", col_val);
1389
+ }
1390
+ }
1391
+ return result;
1392
+ }
1393
+
958
1394
  static napi_value doe_shader_module_release(napi_env env, napi_callback_info info) {
959
1395
  NAPI_ASSERT_ARGC(env, info, 1);
960
1396
  void* mod = unwrap_ptr(env, _args[0]);
@@ -962,6 +1398,70 @@ static napi_value doe_shader_module_release(napi_env env, napi_callback_info inf
962
1398
  return NULL;
963
1399
  }
964
1400
 
1401
+ static const char* doe_binding_kind_name(uint32_t kind) {
1402
+ switch (kind) {
1403
+ case 0: return "buffer";
1404
+ case 1: return "sampler";
1405
+ case 2: return "texture";
1406
+ case 3: return "storage_texture";
1407
+ default: return "unknown";
1408
+ }
1409
+ }
1410
+
1411
+ static const char* doe_binding_space_name(uint32_t addr_space) {
1412
+ switch (addr_space) {
1413
+ case 0: return "function";
1414
+ case 1: return "private";
1415
+ case 2: return "workgroup";
1416
+ case 3: return "uniform";
1417
+ case 4: return "storage";
1418
+ case 5: return "handle";
1419
+ default: return "unknown";
1420
+ }
1421
+ }
1422
+
1423
+ static const char* doe_binding_access_name(uint32_t access) {
1424
+ switch (access) {
1425
+ case 0: return "read";
1426
+ case 1: return "write";
1427
+ case 2: return "read_write";
1428
+ default: return "unknown";
1429
+ }
1430
+ }
1431
+
1432
+ static napi_value doe_shader_module_get_bindings(napi_env env, napi_callback_info info) {
1433
+ NAPI_ASSERT_ARGC(env, info, 1);
1434
+ WGPUShaderModule shader_module = unwrap_ptr(env, _args[0]);
1435
+ if (!shader_module) NAPI_THROW(env, "shaderModuleGetBindings: null shader module");
1436
+ if (!pfn_doeNativeShaderModuleGetBindings) NAPI_THROW(env, "shaderModuleGetBindings: native binding metadata not available");
1437
+
1438
+ DoeShaderBindingInfo bindings[16];
1439
+ size_t count = pfn_doeNativeShaderModuleGetBindings(shader_module, bindings, 16);
1440
+
1441
+ napi_value array;
1442
+ napi_create_array_with_length(env, count, &array);
1443
+ for (size_t i = 0; i < count; i++) {
1444
+ napi_value entry;
1445
+ napi_create_object(env, &entry);
1446
+
1447
+ napi_value group, binding, kind, space, access;
1448
+ napi_create_uint32(env, bindings[i].group, &group);
1449
+ napi_create_uint32(env, bindings[i].binding, &binding);
1450
+ napi_create_string_utf8(env, doe_binding_kind_name(bindings[i].kind), NAPI_AUTO_LENGTH, &kind);
1451
+ napi_create_string_utf8(env, doe_binding_space_name(bindings[i].addr_space), NAPI_AUTO_LENGTH, &space);
1452
+ napi_create_string_utf8(env, doe_binding_access_name(bindings[i].access), NAPI_AUTO_LENGTH, &access);
1453
+
1454
+ napi_set_named_property(env, entry, "group", group);
1455
+ napi_set_named_property(env, entry, "binding", binding);
1456
+ napi_set_named_property(env, entry, "type", kind);
1457
+ napi_set_named_property(env, entry, "space", space);
1458
+ napi_set_named_property(env, entry, "access", access);
1459
+ napi_set_element(env, array, i, entry);
1460
+ }
1461
+
1462
+ return array;
1463
+ }
1464
+
965
1465
  /* ================================================================
966
1466
  * Compute Pipeline
967
1467
  * createComputePipeline(device, shaderModule, entryPoint, pipelineLayout?)
@@ -994,7 +1494,28 @@ static napi_value doe_create_compute_pipeline(napi_env env, napi_callback_info i
994
1494
 
995
1495
  WGPUComputePipeline pipeline = pfn_wgpuDeviceCreateComputePipeline(device, &desc);
996
1496
  free(ep);
997
- if (!pipeline) NAPI_THROW(env, "createComputePipeline failed (shader module invalid or entry point not found — check stderr for details)");
1497
+ if (!pipeline) {
1498
+ char msg[DOE_ERROR_BUF_CAP];
1499
+ char stage[64];
1500
+ char kind[64];
1501
+ copy_library_error_message(msg, sizeof(msg));
1502
+ copy_library_error_meta(pfn_doeNativeCopyLastErrorStage, stage, sizeof(stage));
1503
+ copy_library_error_meta(pfn_doeNativeCopyLastErrorKind, kind, sizeof(kind));
1504
+ if (msg[0] != '\0') {
1505
+ char full_msg[DOE_ERROR_BUF_CAP];
1506
+ if (stage[0] != '\0' && kind[0] != '\0') {
1507
+ snprintf(full_msg, sizeof(full_msg), "[%s/%s] %s", stage, kind, msg);
1508
+ } else if (stage[0] != '\0') {
1509
+ snprintf(full_msg, sizeof(full_msg), "[%s] %s", stage, msg);
1510
+ } else {
1511
+ snprintf(full_msg, sizeof(full_msg), "%s", msg);
1512
+ }
1513
+ napi_throw_error(env, "DOE_COMPUTE_PIPELINE_ERROR", full_msg);
1514
+ } else {
1515
+ napi_throw_error(env, "DOE_COMPUTE_PIPELINE_ERROR", "createComputePipeline failed");
1516
+ }
1517
+ return NULL;
1518
+ }
998
1519
  return wrap_ptr(env, pipeline);
999
1520
  }
1000
1521
 
@@ -1021,7 +1542,9 @@ static napi_value doe_compute_pipeline_get_bind_group_layout(napi_env env, napi_
1021
1542
  /* ================================================================
1022
1543
  * Bind Group Layout
1023
1544
  * createBindGroupLayout(device, entries[])
1024
- * Each entry: { binding, visibility, buffer?: { type }, storageTexture?: { ... } }
1545
+ * Each entry: { binding, visibility, buffer?: { type }, sampler?: { type },
1546
+ * texture?: { sampleType, viewDimension, multisampled },
1547
+ * storageTexture?: { access, format, viewDimension } }
1025
1548
  * ================================================================ */
1026
1549
 
1027
1550
  static uint32_t buffer_binding_type_from_string(napi_env env, napi_value val) {
@@ -1037,6 +1560,68 @@ static uint32_t buffer_binding_type_from_string(napi_env env, napi_value val) {
1037
1560
  return 0x00000001;
1038
1561
  }
1039
1562
 
1563
+ static uint32_t sampler_binding_type_from_string(napi_env env, napi_value val) {
1564
+ napi_valuetype vt;
1565
+ napi_typeof(env, val, &vt);
1566
+ if (vt != napi_string) return 0x00000001; /* Undefined */
1567
+ char buf[32] = {0};
1568
+ size_t len = 0;
1569
+ napi_get_value_string_utf8(env, val, buf, sizeof(buf), &len);
1570
+ if (strcmp(buf, "filtering") == 0) return 0x00000002;
1571
+ if (strcmp(buf, "non-filtering") == 0 || strcmp(buf, "non_filtering") == 0) return 0x00000003;
1572
+ if (strcmp(buf, "comparison") == 0) return 0x00000004;
1573
+ return 0x00000001;
1574
+ }
1575
+
1576
+ static uint32_t texture_sample_type_from_string(napi_env env, napi_value val) {
1577
+ napi_valuetype vt;
1578
+ napi_typeof(env, val, &vt);
1579
+ if (vt != napi_string) return 0x00000001; /* Undefined */
1580
+ char buf[64] = {0};
1581
+ size_t len = 0;
1582
+ napi_get_value_string_utf8(env, val, buf, sizeof(buf), &len);
1583
+ if (strcmp(buf, "float") == 0) return 0x00000002;
1584
+ if (strcmp(buf, "unfilterable-float") == 0 || strcmp(buf, "unfilterable_float") == 0) return 0x00000003;
1585
+ if (strcmp(buf, "depth") == 0) return 0x00000004;
1586
+ if (strcmp(buf, "sint") == 0) return 0x00000005;
1587
+ if (strcmp(buf, "uint") == 0) return 0x00000006;
1588
+ return 0x00000001;
1589
+ }
1590
+
1591
+ static uint32_t texture_view_dimension_from_string(napi_env env, napi_value val) {
1592
+ napi_valuetype vt;
1593
+ napi_typeof(env, val, &vt);
1594
+ if (vt != napi_string) return 0x00000000; /* Undefined */
1595
+ char buf[32] = {0};
1596
+ size_t len = 0;
1597
+ napi_get_value_string_utf8(env, val, buf, sizeof(buf), &len);
1598
+ if (strcmp(buf, "1d") == 0) return 0x00000001;
1599
+ if (strcmp(buf, "2d") == 0) return 0x00000002;
1600
+ if (strcmp(buf, "2d-array") == 0 || strcmp(buf, "2d_array") == 0) return 0x00000003;
1601
+ if (strcmp(buf, "cube") == 0) return 0x00000004;
1602
+ if (strcmp(buf, "cube-array") == 0 || strcmp(buf, "cube_array") == 0) return 0x00000005;
1603
+ if (strcmp(buf, "3d") == 0) return 0x00000006;
1604
+ return 0x00000000;
1605
+ }
1606
+
1607
+ static uint32_t storage_texture_access_from_string(napi_env env, napi_value val) {
1608
+ napi_valuetype vt;
1609
+ napi_typeof(env, val, &vt);
1610
+ if (vt != napi_string) return 0x00000001; /* Undefined */
1611
+ char buf[32] = {0};
1612
+ size_t len = 0;
1613
+ napi_get_value_string_utf8(env, val, buf, sizeof(buf), &len);
1614
+ if (strcmp(buf, "write-only") == 0 || strcmp(buf, "write_only") == 0) return 0x00000002;
1615
+ if (strcmp(buf, "read-only") == 0 || strcmp(buf, "read_only") == 0) return 0x00000003;
1616
+ if (strcmp(buf, "read-write") == 0 || strcmp(buf, "read_write") == 0) return 0x00000004;
1617
+ return 0x00000001;
1618
+ }
1619
+
1620
+ static uint32_t texture_format_from_string(napi_env env, napi_value val);
1621
+ static uint32_t primitive_topology_from_string(napi_env env, napi_value val);
1622
+ static uint32_t front_face_from_string(napi_env env, napi_value val);
1623
+ static uint32_t cull_mode_from_string(napi_env env, napi_value val);
1624
+
1040
1625
  static napi_value doe_create_bind_group_layout(napi_env env, napi_callback_info info) {
1041
1626
  NAPI_ASSERT_ARGC(env, info, 2);
1042
1627
  CHECK_LIB_LOADED(env);
@@ -1066,11 +1651,27 @@ static napi_value doe_create_bind_group_layout(napi_env env, napi_callback_info
1066
1651
  entries[i].buffer.minBindingSize = (uint64_t)get_int64_prop(env, buf_obj, "minBindingSize");
1067
1652
  }
1068
1653
 
1654
+ if (has_prop(env, elem, "sampler") && prop_type(env, elem, "sampler") == napi_object) {
1655
+ napi_value sampler_obj = get_prop(env, elem, "sampler");
1656
+ entries[i].sampler.type = sampler_binding_type_from_string(
1657
+ env, get_prop(env, sampler_obj, "type"));
1658
+ }
1659
+
1660
+ if (has_prop(env, elem, "texture") && prop_type(env, elem, "texture") == napi_object) {
1661
+ napi_value tex_obj = get_prop(env, elem, "texture");
1662
+ entries[i].texture.sampleType = texture_sample_type_from_string(
1663
+ env, get_prop(env, tex_obj, "sampleType"));
1664
+ entries[i].texture.viewDimension = texture_view_dimension_from_string(
1665
+ env, get_prop(env, tex_obj, "viewDimension"));
1666
+ if (has_prop(env, tex_obj, "multisampled"))
1667
+ entries[i].texture.multisampled = get_bool_prop(env, tex_obj, "multisampled") ? 1 : 0;
1668
+ }
1669
+
1069
1670
  if (has_prop(env, elem, "storageTexture") && prop_type(env, elem, "storageTexture") == napi_object) {
1070
1671
  napi_value st_obj = get_prop(env, elem, "storageTexture");
1071
- entries[i].storageTexture.access = get_uint32_prop(env, st_obj, "access");
1072
- entries[i].storageTexture.format = get_uint32_prop(env, st_obj, "format");
1073
- entries[i].storageTexture.viewDimension = get_uint32_prop(env, st_obj, "viewDimension");
1672
+ entries[i].storageTexture.access = storage_texture_access_from_string(env, get_prop(env, st_obj, "access"));
1673
+ entries[i].storageTexture.format = texture_format_from_string(env, get_prop(env, st_obj, "format"));
1674
+ entries[i].storageTexture.viewDimension = texture_view_dimension_from_string(env, get_prop(env, st_obj, "viewDimension"));
1074
1675
  }
1075
1676
  }
1076
1677
 
@@ -1097,7 +1698,7 @@ static napi_value doe_bind_group_layout_release(napi_env env, napi_callback_info
1097
1698
  /* ================================================================
1098
1699
  * Bind Group
1099
1700
  * createBindGroup(device, layout, entries[])
1100
- * Each entry: { binding, buffer, offset?, size? }
1701
+ * Each entry: { binding, buffer?, offset?, size?, sampler?, textureView? }
1101
1702
  * ================================================================ */
1102
1703
 
1103
1704
  static napi_value doe_create_bind_group(napi_env env, napi_callback_info info) {
@@ -1122,6 +1723,12 @@ static napi_value doe_create_bind_group(napi_env env, napi_callback_info info) {
1122
1723
  if (has_prop(env, elem, "buffer") && prop_type(env, elem, "buffer") == napi_external)
1123
1724
  entries[i].buffer = unwrap_ptr(env, get_prop(env, elem, "buffer"));
1124
1725
 
1726
+ if (has_prop(env, elem, "sampler") && prop_type(env, elem, "sampler") == napi_external)
1727
+ entries[i].sampler = unwrap_ptr(env, get_prop(env, elem, "sampler"));
1728
+
1729
+ if (has_prop(env, elem, "textureView") && prop_type(env, elem, "textureView") == napi_external)
1730
+ entries[i].textureView = unwrap_ptr(env, get_prop(env, elem, "textureView"));
1731
+
1125
1732
  if (has_prop(env, elem, "offset"))
1126
1733
  entries[i].offset = (uint64_t)get_int64_prop(env, elem, "offset");
1127
1734
 
@@ -1235,6 +1842,56 @@ static napi_value doe_command_encoder_copy_buffer_to_buffer(napi_env env, napi_c
1235
1842
  return NULL;
1236
1843
  }
1237
1844
 
1845
+ static napi_value doe_command_encoder_copy_texture_to_buffer(napi_env env, napi_callback_info info) {
1846
+ NAPI_ASSERT_ARGC(env, info, 14);
1847
+ CHECK_LIB_LOADED(env);
1848
+ WGPUCommandEncoder enc = unwrap_ptr(env, _args[0]);
1849
+ WGPUTexture src_texture = unwrap_ptr(env, _args[1]);
1850
+ if (!enc || !src_texture) NAPI_THROW(env, "commandEncoderCopyTextureToBuffer requires encoder and texture");
1851
+
1852
+ WGPUTexelCopyTextureInfo src;
1853
+ memset(&src, 0, sizeof(src));
1854
+ src.texture = src_texture;
1855
+ napi_get_value_uint32(env, _args[2], &src.mipLevel);
1856
+ napi_get_value_uint32(env, _args[3], &src.origin.x);
1857
+ napi_get_value_uint32(env, _args[4], &src.origin.y);
1858
+ napi_get_value_uint32(env, _args[5], &src.origin.z);
1859
+ napi_get_value_uint32(env, _args[6], &src.aspect);
1860
+
1861
+ WGPUTexelCopyBufferInfo dst;
1862
+ memset(&dst, 0, sizeof(dst));
1863
+ dst.buffer = unwrap_ptr(env, _args[7]);
1864
+ if (!dst.buffer) NAPI_THROW(env, "commandEncoderCopyTextureToBuffer requires destination buffer");
1865
+ int64_t dst_offset = 0;
1866
+ napi_get_value_int64(env, _args[8], &dst_offset);
1867
+ dst.layout.offset = (uint64_t)dst_offset;
1868
+ napi_get_value_uint32(env, _args[9], &dst.layout.bytesPerRow);
1869
+ napi_get_value_uint32(env, _args[10], &dst.layout.rowsPerImage);
1870
+
1871
+ WGPUExtent3D size;
1872
+ napi_get_value_uint32(env, _args[11], &size.width);
1873
+ napi_get_value_uint32(env, _args[12], &size.height);
1874
+ napi_get_value_uint32(env, _args[13], &size.depthOrArrayLayers);
1875
+
1876
+ if (pfn_doeNativeCommandEncoderCopyTextureToBuffer) {
1877
+ pfn_doeNativeCommandEncoderCopyTextureToBuffer(
1878
+ enc,
1879
+ src.texture,
1880
+ src.mipLevel,
1881
+ dst.buffer,
1882
+ dst.layout.offset,
1883
+ dst.layout.bytesPerRow,
1884
+ dst.layout.rowsPerImage,
1885
+ size.width,
1886
+ size.height,
1887
+ size.depthOrArrayLayers
1888
+ );
1889
+ } else {
1890
+ pfn_wgpuCommandEncoderCopyTextureToBuffer(enc, &src, &dst, &size);
1891
+ }
1892
+ return NULL;
1893
+ }
1894
+
1238
1895
  static napi_value doe_command_encoder_finish(napi_env env, napi_callback_info info) {
1239
1896
  NAPI_ASSERT_ARGC(env, info, 1);
1240
1897
  CHECK_LIB_LOADED(env);
@@ -1360,6 +2017,7 @@ static napi_value doe_queue_write_buffer(napi_env env, napi_callback_info info)
1360
2017
  CHECK_LIB_LOADED(env);
1361
2018
  WGPUQueue queue = unwrap_ptr(env, _args[0]);
1362
2019
  WGPUBuffer buf = unwrap_ptr(env, _args[1]);
2020
+ if (!queue || !buf) NAPI_THROW(env, "queueWriteBuffer requires queue and buffer");
1363
2021
  int64_t offset; napi_get_value_int64(env, _args[2], &offset);
1364
2022
 
1365
2023
  void* data = NULL;
@@ -1415,9 +2073,12 @@ static napi_value doe_queue_flush(napi_env env, napi_callback_info info) {
1415
2073
  pfn_doeNativeQueueFlush(queue);
1416
2074
  return NULL;
1417
2075
  }
1418
- if (!inst) NAPI_THROW(env, "queueFlush requires instance when doeNativeQueueFlush is unavailable");
2076
+ if (!inst) {
2077
+ napi_throw_error(env, "DOE_QUEUE_UNAVAILABLE", "queueFlush requires instance when doeNativeQueueFlush is unavailable");
2078
+ return NULL;
2079
+ }
1419
2080
 
1420
- QueueWorkDoneResult result = {0, 0};
2081
+ QueueWorkDoneResult result = {0};
1421
2082
  WGPUQueueWorkDoneCallbackInfo cb_info = {
1422
2083
  .nextInChain = NULL,
1423
2084
  .mode = WGPU_CALLBACK_MODE_WAIT_ANY_ONLY,
@@ -1441,19 +2102,20 @@ static napi_value doe_queue_flush(napi_env env, napi_callback_info info) {
1441
2102
  }
1442
2103
  } else if (wait_status == WGPU_WAIT_STATUS_TIMED_OUT) {
1443
2104
  pfn_wgpuInstanceProcessEvents(inst);
1444
- if (monotonic_now_ns() - start_ns >= DOE_DEFAULT_TIMEOUT_NS) {
1445
- NAPI_THROW(env, "queueFlush: queue wait timed out");
2105
+ if (monotonic_now_ns() - start_ns >= current_timeout_ns()) {
2106
+ napi_throw_error(env, "DOE_QUEUE_TIMEOUT", "queueFlush: queue wait timed out");
2107
+ return NULL;
1446
2108
  }
1447
2109
  wait_slice();
1448
2110
  } else if (wait_status == WGPU_WAIT_STATUS_ERROR) {
1449
- NAPI_THROW(env, "queueFlush: wgpuInstanceWaitAny failed");
2111
+ napi_throw_error(env, "DOE_QUEUE_UNAVAILABLE", "queueFlush: wgpuInstanceWaitAny failed");
2112
+ return NULL;
1450
2113
  } else {
1451
2114
  NAPI_THROW(env, "queueFlush: unsupported wait status");
1452
2115
  }
1453
2116
  }
1454
- if (result.status != WGPU_QUEUE_WORK_DONE_STATUS_SUCCESS) {
1455
- NAPI_THROW(env, "queueFlush: queue work did not complete");
1456
- }
2117
+ if (result.status != WGPU_QUEUE_WORK_DONE_STATUS_SUCCESS)
2118
+ return throw_status_error(env, "DOE_QUEUE_FLUSH_ERROR", "queueFlush: queue work did not complete", result.status, result.message);
1457
2119
  return NULL;
1458
2120
  }
1459
2121
 
@@ -1677,7 +2339,7 @@ static napi_value doe_flush_and_map_sync(napi_env env, napi_callback_info info)
1677
2339
  }
1678
2340
 
1679
2341
  /* Map the buffer synchronously via processEvents polling. */
1680
- BufferMapResult result = {0, 0};
2342
+ BufferMapResult result = {0};
1681
2343
  WGPUBufferMapCallbackInfo cb_info = {
1682
2344
  .nextInChain = NULL,
1683
2345
  .mode = WGPU_CALLBACK_MODE_ALLOW_PROCESS_EVENTS,
@@ -1688,9 +2350,10 @@ static napi_value doe_flush_and_map_sync(napi_env env, napi_callback_info info)
1688
2350
  WGPUFuture future = pfn_wgpuBufferMapAsync2(buf, (uint64_t)mode,
1689
2351
  (size_t)offset_i, (size_t)size_i, cb_info);
1690
2352
  if (future.id == 0) NAPI_THROW(env, "flushAndMapSync: bufferMapAsync future unavailable");
1691
- if (!process_events_until(inst, &result.done, DOE_DEFAULT_TIMEOUT_NS) ||
1692
- result.status != WGPU_MAP_ASYNC_STATUS_SUCCESS)
1693
- NAPI_THROW(env, "flushAndMapSync: bufferMapAsync failed");
2353
+ if (!process_events_until(inst, &result.done, current_timeout_ns()))
2354
+ return throw_status_error(env, "DOE_BUFFER_MAP_TIMEOUT", "flushAndMapSync: bufferMapAsync timed out", result.status, result.message);
2355
+ if (result.status != WGPU_MAP_ASYNC_STATUS_SUCCESS)
2356
+ return throw_status_error(env, "DOE_BUFFER_MAP_ERROR", "flushAndMapSync: bufferMapAsync failed", result.status, result.message);
1694
2357
 
1695
2358
  napi_value ok;
1696
2359
  napi_get_boolean(env, true, &ok);
@@ -1722,14 +2385,246 @@ static uint32_t texture_format_from_string(napi_env env, napi_value val) {
1722
2385
  char buf[32] = {0};
1723
2386
  size_t len = 0;
1724
2387
  napi_get_value_string_utf8(env, val, buf, sizeof(buf), &len);
1725
- if (strcmp(buf, "rgba8unorm") == 0) return 0x00000016;
1726
- if (strcmp(buf, "rgba8unorm-srgb") == 0) return 0x00000017;
1727
- if (strcmp(buf, "bgra8unorm") == 0) return 0x0000001B;
1728
- if (strcmp(buf, "bgra8unorm-srgb") == 0) return 0x0000001C;
1729
- if (strcmp(buf, "depth32float") == 0) return 0x00000030;
2388
+ if (strcmp(buf, "r8unorm") == 0) return 0x00000001;
2389
+ if (strcmp(buf, "r8snorm") == 0) return 0x00000002;
2390
+ if (strcmp(buf, "r8uint") == 0) return 0x00000003;
2391
+ if (strcmp(buf, "r8sint") == 0) return 0x00000004;
2392
+ if (strcmp(buf, "r16uint") == 0) return 0x00000007;
2393
+ if (strcmp(buf, "r16sint") == 0) return 0x00000008;
2394
+ if (strcmp(buf, "r16float") == 0) return 0x00000009;
2395
+ if (strcmp(buf, "rg8unorm") == 0) return 0x0000000A;
2396
+ if (strcmp(buf, "rg8snorm") == 0) return 0x0000000B;
2397
+ if (strcmp(buf, "rg8uint") == 0) return 0x0000000C;
2398
+ if (strcmp(buf, "rg8sint") == 0) return 0x0000000D;
2399
+ if (strcmp(buf, "r32float") == 0) return 0x0000000E;
2400
+ if (strcmp(buf, "r32uint") == 0) return 0x0000000F;
2401
+ if (strcmp(buf, "r32sint") == 0) return 0x00000010;
2402
+ if (strcmp(buf, "rg16uint") == 0) return 0x00000013;
2403
+ if (strcmp(buf, "rg16sint") == 0) return 0x00000014;
2404
+ if (strcmp(buf, "rg16float") == 0) return 0x00000015;
2405
+ if (strcmp(buf, "rgba8unorm") == 0) return 0x00000016;
2406
+ if (strcmp(buf, "rgba8unorm-srgb") == 0) return 0x00000017;
2407
+ if (strcmp(buf, "rgba8snorm") == 0) return 0x00000018;
2408
+ if (strcmp(buf, "rgba8uint") == 0) return 0x00000019;
2409
+ if (strcmp(buf, "rgba8sint") == 0) return 0x0000001A;
2410
+ if (strcmp(buf, "bgra8unorm") == 0) return 0x0000001B;
2411
+ if (strcmp(buf, "bgra8unorm-srgb") == 0) return 0x0000001C;
2412
+ if (strcmp(buf, "rgb10a2uint") == 0) return 0x0000001D;
2413
+ if (strcmp(buf, "rgb10a2unorm") == 0) return 0x0000001E;
2414
+ if (strcmp(buf, "rg11b10ufloat") == 0) return 0x0000001F;
2415
+ if (strcmp(buf, "rgb9e5ufloat") == 0) return 0x00000020;
2416
+ if (strcmp(buf, "rg32float") == 0) return 0x00000021;
2417
+ if (strcmp(buf, "rg32uint") == 0) return 0x00000022;
2418
+ if (strcmp(buf, "rg32sint") == 0) return 0x00000023;
2419
+ if (strcmp(buf, "rgba16uint") == 0) return 0x00000024;
2420
+ if (strcmp(buf, "rgba16sint") == 0) return 0x00000025;
2421
+ if (strcmp(buf, "rgba16float") == 0) return 0x00000026;
2422
+ if (strcmp(buf, "rgba32float") == 0) return 0x00000027;
2423
+ if (strcmp(buf, "rgba32uint") == 0) return 0x00000028;
2424
+ if (strcmp(buf, "rgba32sint") == 0) return 0x00000029;
2425
+ if (strcmp(buf, "stencil8") == 0) return 0x0000002C;
2426
+ if (strcmp(buf, "depth16unorm") == 0) return 0x0000002D;
2427
+ if (strcmp(buf, "depth24plus") == 0) return 0x0000002E;
2428
+ if (strcmp(buf, "depth24plus-stencil8") == 0) return 0x0000002F;
2429
+ if (strcmp(buf, "depth32float") == 0) return 0x00000030;
2430
+ if (strcmp(buf, "depth32float-stencil8") == 0) return 0x00000031;
2431
+ /* BC compressed formats */
2432
+ if (strcmp(buf, "bc1-rgba-unorm") == 0) return 0x00000032;
2433
+ if (strcmp(buf, "bc1-rgba-unorm-srgb") == 0) return 0x00000033;
2434
+ if (strcmp(buf, "bc2-rgba-unorm") == 0) return 0x00000034;
2435
+ if (strcmp(buf, "bc2-rgba-unorm-srgb") == 0) return 0x00000035;
2436
+ if (strcmp(buf, "bc3-rgba-unorm") == 0) return 0x00000036;
2437
+ if (strcmp(buf, "bc3-rgba-unorm-srgb") == 0) return 0x00000037;
2438
+ if (strcmp(buf, "bc4-r-unorm") == 0) return 0x00000038;
2439
+ if (strcmp(buf, "bc4-r-snorm") == 0) return 0x00000039;
2440
+ if (strcmp(buf, "bc5-rg-unorm") == 0) return 0x0000003A;
2441
+ if (strcmp(buf, "bc5-rg-snorm") == 0) return 0x0000003B;
2442
+ if (strcmp(buf, "bc6h-rgb-ufloat") == 0) return 0x0000003C;
2443
+ if (strcmp(buf, "bc6h-rgb-float") == 0) return 0x0000003D;
2444
+ if (strcmp(buf, "bc7-rgba-unorm") == 0) return 0x0000003E;
2445
+ if (strcmp(buf, "bc7-rgba-unorm-srgb") == 0) return 0x0000003F;
2446
+ /* ETC2/EAC compressed formats */
2447
+ if (strcmp(buf, "etc2-rgb8unorm") == 0) return 0x00000040;
2448
+ if (strcmp(buf, "etc2-rgb8unorm-srgb") == 0) return 0x00000041;
2449
+ if (strcmp(buf, "etc2-rgb8a1unorm") == 0) return 0x00000042;
2450
+ if (strcmp(buf, "etc2-rgb8a1unorm-srgb") == 0) return 0x00000043;
2451
+ if (strcmp(buf, "etc2-rgba8unorm") == 0) return 0x00000044;
2452
+ if (strcmp(buf, "etc2-rgba8unorm-srgb") == 0) return 0x00000045;
2453
+ if (strcmp(buf, "eac-r11unorm") == 0) return 0x00000046;
2454
+ if (strcmp(buf, "eac-r11snorm") == 0) return 0x00000047;
2455
+ if (strcmp(buf, "eac-rg11unorm") == 0) return 0x00000048;
2456
+ if (strcmp(buf, "eac-rg11snorm") == 0) return 0x00000049;
2457
+ /* ASTC compressed formats */
2458
+ if (strcmp(buf, "astc-4x4-unorm") == 0) return 0x0000004A;
2459
+ if (strcmp(buf, "astc-4x4-unorm-srgb") == 0) return 0x0000004B;
2460
+ if (strcmp(buf, "astc-5x4-unorm") == 0) return 0x0000004C;
2461
+ if (strcmp(buf, "astc-5x4-unorm-srgb") == 0) return 0x0000004D;
2462
+ if (strcmp(buf, "astc-5x5-unorm") == 0) return 0x0000004E;
2463
+ if (strcmp(buf, "astc-5x5-unorm-srgb") == 0) return 0x0000004F;
2464
+ if (strcmp(buf, "astc-6x5-unorm") == 0) return 0x00000050;
2465
+ if (strcmp(buf, "astc-6x5-unorm-srgb") == 0) return 0x00000051;
2466
+ if (strcmp(buf, "astc-6x6-unorm") == 0) return 0x00000052;
2467
+ if (strcmp(buf, "astc-6x6-unorm-srgb") == 0) return 0x00000053;
2468
+ if (strcmp(buf, "astc-8x5-unorm") == 0) return 0x00000054;
2469
+ if (strcmp(buf, "astc-8x5-unorm-srgb") == 0) return 0x00000055;
2470
+ if (strcmp(buf, "astc-8x6-unorm") == 0) return 0x00000056;
2471
+ if (strcmp(buf, "astc-8x6-unorm-srgb") == 0) return 0x00000057;
2472
+ if (strcmp(buf, "astc-8x8-unorm") == 0) return 0x00000058;
2473
+ if (strcmp(buf, "astc-8x8-unorm-srgb") == 0) return 0x00000059;
2474
+ if (strcmp(buf, "astc-10x5-unorm") == 0) return 0x0000005A;
2475
+ if (strcmp(buf, "astc-10x5-unorm-srgb") == 0) return 0x0000005B;
2476
+ if (strcmp(buf, "astc-10x6-unorm") == 0) return 0x0000005C;
2477
+ if (strcmp(buf, "astc-10x6-unorm-srgb") == 0) return 0x0000005D;
2478
+ if (strcmp(buf, "astc-10x8-unorm") == 0) return 0x0000005E;
2479
+ if (strcmp(buf, "astc-10x8-unorm-srgb") == 0) return 0x0000005F;
2480
+ if (strcmp(buf, "astc-10x10-unorm") == 0) return 0x00000060;
2481
+ if (strcmp(buf, "astc-10x10-unorm-srgb") == 0) return 0x00000061;
2482
+ if (strcmp(buf, "astc-12x10-unorm") == 0) return 0x00000062;
2483
+ if (strcmp(buf, "astc-12x10-unorm-srgb") == 0) return 0x00000063;
2484
+ if (strcmp(buf, "astc-12x12-unorm") == 0) return 0x00000064;
2485
+ if (strcmp(buf, "astc-12x12-unorm-srgb") == 0) return 0x00000065;
1730
2486
  return 0x00000016;
1731
2487
  }
1732
2488
 
2489
+ static uint32_t primitive_topology_from_string(napi_env env, napi_value val) {
2490
+ napi_valuetype vt;
2491
+ napi_typeof(env, val, &vt);
2492
+ if (vt == napi_number) {
2493
+ uint32_t out = 0;
2494
+ napi_get_value_uint32(env, val, &out);
2495
+ return out;
2496
+ }
2497
+ char buf[32] = {0};
2498
+ size_t len = 0;
2499
+ napi_get_value_string_utf8(env, val, buf, sizeof(buf), &len);
2500
+ if (strcmp(buf, "point-list") == 0) return 0x00000001;
2501
+ if (strcmp(buf, "line-list") == 0) return 0x00000002;
2502
+ if (strcmp(buf, "line-strip") == 0) return 0x00000003;
2503
+ if (strcmp(buf, "triangle-list") == 0) return 0x00000004;
2504
+ if (strcmp(buf, "triangle-strip") == 0) return 0x00000005;
2505
+ napi_throw_error(env, "DOE_ERROR", "Unsupported primitive topology");
2506
+ return 0;
2507
+ }
2508
+
2509
+ static uint32_t front_face_from_string(napi_env env, napi_value val) {
2510
+ napi_valuetype vt;
2511
+ napi_typeof(env, val, &vt);
2512
+ if (vt == napi_number) {
2513
+ uint32_t out = 0;
2514
+ napi_get_value_uint32(env, val, &out);
2515
+ return out;
2516
+ }
2517
+ char buf[16] = {0};
2518
+ size_t len = 0;
2519
+ napi_get_value_string_utf8(env, val, buf, sizeof(buf), &len);
2520
+ if (strcmp(buf, "ccw") == 0) return 0x00000001;
2521
+ if (strcmp(buf, "cw") == 0) return 0x00000002;
2522
+ napi_throw_error(env, "DOE_ERROR", "Unsupported frontFace");
2523
+ return 0;
2524
+ }
2525
+
2526
+ static uint32_t cull_mode_from_string(napi_env env, napi_value val) {
2527
+ napi_valuetype vt;
2528
+ napi_typeof(env, val, &vt);
2529
+ if (vt == napi_number) {
2530
+ uint32_t out = 0;
2531
+ napi_get_value_uint32(env, val, &out);
2532
+ return out;
2533
+ }
2534
+ char buf[16] = {0};
2535
+ size_t len = 0;
2536
+ napi_get_value_string_utf8(env, val, buf, sizeof(buf), &len);
2537
+ if (strcmp(buf, "none") == 0) return 0x00000001;
2538
+ if (strcmp(buf, "front") == 0) return 0x00000002;
2539
+ if (strcmp(buf, "back") == 0) return 0x00000003;
2540
+ napi_throw_error(env, "DOE_ERROR", "Unsupported cullMode");
2541
+ return 0;
2542
+ }
2543
+
2544
+ static uint32_t compare_func_from_value(napi_env env, napi_value val) {
2545
+ napi_valuetype vt;
2546
+ napi_typeof(env, val, &vt);
2547
+ if (vt == napi_number) {
2548
+ uint32_t out = 0;
2549
+ napi_get_value_uint32(env, val, &out);
2550
+ return out;
2551
+ }
2552
+ char buf[24] = {0};
2553
+ size_t len = 0;
2554
+ napi_get_value_string_utf8(env, val, buf, sizeof(buf), &len);
2555
+ if (strcmp(buf, "never") == 0) return 0x00000001;
2556
+ if (strcmp(buf, "less") == 0) return 0x00000002;
2557
+ if (strcmp(buf, "equal") == 0) return 0x00000003;
2558
+ if (strcmp(buf, "less-equal") == 0) return 0x00000004;
2559
+ if (strcmp(buf, "greater") == 0) return 0x00000005;
2560
+ if (strcmp(buf, "not-equal") == 0) return 0x00000006;
2561
+ if (strcmp(buf, "greater-equal") == 0) return 0x00000007;
2562
+ if (strcmp(buf, "always") == 0) return 0x00000008;
2563
+ napi_throw_error(env, "DOE_ERROR", "Unsupported compare function");
2564
+ return 0;
2565
+ }
2566
+
2567
+ static uint32_t vertex_step_mode_from_value(napi_env env, napi_value val) {
2568
+ napi_valuetype vt;
2569
+ napi_typeof(env, val, &vt);
2570
+ if (vt == napi_number) {
2571
+ uint32_t out = 0;
2572
+ napi_get_value_uint32(env, val, &out);
2573
+ return out;
2574
+ }
2575
+ char buf[24] = {0};
2576
+ size_t len = 0;
2577
+ napi_get_value_string_utf8(env, val, buf, sizeof(buf), &len);
2578
+ if (strcmp(buf, "vertex") == 0) return 0x00000001;
2579
+ if (strcmp(buf, "instance") == 0) return 0x00000002;
2580
+ napi_throw_error(env, "DOE_ERROR", "Unsupported vertex stepMode");
2581
+ return 0;
2582
+ }
2583
+
2584
+ static uint32_t vertex_format_from_value(napi_env env, napi_value val) {
2585
+ napi_valuetype vt;
2586
+ napi_typeof(env, val, &vt);
2587
+ if (vt == napi_number) {
2588
+ uint32_t out = 0;
2589
+ napi_get_value_uint32(env, val, &out);
2590
+ return out;
2591
+ }
2592
+ char buf[32] = {0};
2593
+ size_t len = 0;
2594
+ napi_get_value_string_utf8(env, val, buf, sizeof(buf), &len);
2595
+ if (strcmp(buf, "float32") == 0) return 0x00000019;
2596
+ if (strcmp(buf, "float32x2") == 0) return 0x0000001A;
2597
+ if (strcmp(buf, "float32x3") == 0) return 0x0000001B;
2598
+ if (strcmp(buf, "float32x4") == 0) return 0x0000001C;
2599
+ if (strcmp(buf, "uint32") == 0) return 0x00000021;
2600
+ if (strcmp(buf, "uint32x2") == 0) return 0x00000022;
2601
+ if (strcmp(buf, "uint32x3") == 0) return 0x00000023;
2602
+ if (strcmp(buf, "uint32x4") == 0) return 0x00000024;
2603
+ if (strcmp(buf, "sint32") == 0) return 0x00000025;
2604
+ if (strcmp(buf, "sint32x2") == 0) return 0x00000026;
2605
+ if (strcmp(buf, "sint32x3") == 0) return 0x00000027;
2606
+ if (strcmp(buf, "sint32x4") == 0) return 0x00000028;
2607
+ napi_throw_error(env, "DOE_ERROR", "Unsupported vertex format");
2608
+ return 0;
2609
+ }
2610
+
2611
+ static uint32_t index_format_from_value(napi_env env, napi_value val) {
2612
+ napi_valuetype vt;
2613
+ napi_typeof(env, val, &vt);
2614
+ if (vt == napi_number) {
2615
+ uint32_t out = 0;
2616
+ napi_get_value_uint32(env, val, &out);
2617
+ return out;
2618
+ }
2619
+ char buf[16] = {0};
2620
+ size_t len = 0;
2621
+ napi_get_value_string_utf8(env, val, buf, sizeof(buf), &len);
2622
+ if (strcmp(buf, "uint16") == 0) return 0x00000001;
2623
+ if (strcmp(buf, "uint32") == 0) return 0x00000002;
2624
+ napi_throw_error(env, "DOE_ERROR", "Unsupported index format");
2625
+ return 0;
2626
+ }
2627
+
1733
2628
  static napi_value doe_create_texture(napi_env env, napi_callback_info info) {
1734
2629
  NAPI_ASSERT_ARGC(env, info, 2);
1735
2630
  CHECK_LIB_LOADED(env);
@@ -1749,7 +2644,9 @@ static napi_value doe_create_texture(napi_env env, napi_callback_info info) {
1749
2644
  if (has_prop(env, _args[1], "mipLevelCount"))
1750
2645
  desc.mipLevelCount = get_uint32_prop(env, _args[1], "mipLevelCount");
1751
2646
  desc.sampleCount = 1;
1752
- desc.dimension = 1; /* 2D */
2647
+ desc.dimension = 2; /* WGPUTextureDimension_2D */
2648
+ if (has_prop(env, _args[1], "dimension"))
2649
+ desc.dimension = get_uint32_prop(env, _args[1], "dimension");
1753
2650
 
1754
2651
  WGPUTexture tex = pfn_wgpuDeviceCreateTexture(device, &desc);
1755
2652
  if (!tex) NAPI_THROW(env, "createTexture failed");
@@ -1853,16 +2750,246 @@ static napi_value doe_sampler_release(napi_env env, napi_callback_info info) {
1853
2750
  }
1854
2751
 
1855
2752
  /* ================================================================
1856
- * Render Pipeline (noop stub — uses built-in Metal shaders)
1857
- * createRenderPipeline(device)
2753
+ * Render Pipeline
2754
+ * createRenderPipeline(device, descriptor)
1858
2755
  * ================================================================ */
1859
2756
 
1860
2757
  static napi_value doe_create_render_pipeline(napi_env env, napi_callback_info info) {
1861
- NAPI_ASSERT_ARGC(env, info, 1);
2758
+ NAPI_ASSERT_ARGC(env, info, 2);
1862
2759
  CHECK_LIB_LOADED(env);
1863
2760
  WGPUDevice device = unwrap_ptr(env, _args[0]);
1864
2761
  if (!device) NAPI_THROW(env, "Invalid device");
1865
- WGPURenderPipeline rp = pfn_wgpuDeviceCreateRenderPipeline(device, NULL);
2762
+ napi_valuetype descriptor_type;
2763
+ napi_typeof(env, _args[1], &descriptor_type);
2764
+ if (descriptor_type != napi_object) NAPI_THROW(env, "createRenderPipeline requires a descriptor object");
2765
+
2766
+ if (prop_type(env, _args[1], "vertex") != napi_object) {
2767
+ NAPI_THROW(env, "createRenderPipeline requires descriptor.vertex");
2768
+ }
2769
+ if (prop_type(env, _args[1], "fragment") != napi_object) {
2770
+ NAPI_THROW(env, "createRenderPipeline requires descriptor.fragment");
2771
+ }
2772
+
2773
+ napi_value vertex = get_prop(env, _args[1], "vertex");
2774
+ napi_value fragment = get_prop(env, _args[1], "fragment");
2775
+
2776
+ napi_value targets = get_prop(env, fragment, "targets");
2777
+ bool is_targets_array = false;
2778
+ napi_is_array(env, targets, &is_targets_array);
2779
+ if (!is_targets_array) NAPI_THROW(env, "createRenderPipeline requires descriptor.fragment.targets");
2780
+ uint32_t target_count = 0;
2781
+ napi_get_array_length(env, targets, &target_count);
2782
+ if (target_count == 0) NAPI_THROW(env, "createRenderPipeline requires at least one fragment target");
2783
+ if (target_count > 1) NAPI_THROW(env, "createRenderPipeline currently supports one color target on this package surface");
2784
+
2785
+ napi_value vertex_module_value = get_prop(env, vertex, "module");
2786
+ WGPUShaderModule vertex_module = unwrap_ptr(env, vertex_module_value);
2787
+ if (!vertex_module) NAPI_THROW(env, "createRenderPipeline: descriptor.vertex.module must be a shader module");
2788
+ napi_value fragment_module_value = get_prop(env, fragment, "module");
2789
+ WGPUShaderModule fragment_module = unwrap_ptr(env, fragment_module_value);
2790
+ if (!fragment_module) NAPI_THROW(env, "createRenderPipeline: descriptor.fragment.module must be a shader module");
2791
+
2792
+ size_t vertex_entry_len = 0;
2793
+ size_t fragment_entry_len = 0;
2794
+ char* vertex_entry = has_prop(env, vertex, "entryPoint")
2795
+ ? dup_string_value(env, get_prop(env, vertex, "entryPoint"), &vertex_entry_len)
2796
+ : strdup("main");
2797
+ if (!has_prop(env, vertex, "entryPoint")) vertex_entry_len = 4;
2798
+ char* fragment_entry = has_prop(env, fragment, "entryPoint")
2799
+ ? dup_string_value(env, get_prop(env, fragment, "entryPoint"), &fragment_entry_len)
2800
+ : strdup("main");
2801
+ if (!has_prop(env, fragment, "entryPoint")) fragment_entry_len = 4;
2802
+ if (!vertex_entry || !fragment_entry) {
2803
+ free(vertex_entry);
2804
+ free(fragment_entry);
2805
+ NAPI_THROW(env, "createRenderPipeline: out of memory");
2806
+ }
2807
+
2808
+ WGPURenderVertexBufferLayout* vertex_buffers = NULL;
2809
+ WGPURenderVertexAttribute* vertex_attributes = NULL;
2810
+ WGPURenderDepthStencilState* depth_stencil = NULL;
2811
+ uint32_t vertex_buffer_count = 0;
2812
+
2813
+ if (has_prop(env, vertex, "buffers")) {
2814
+ napi_value buffers = get_prop(env, vertex, "buffers");
2815
+ bool is_array = false;
2816
+ napi_is_array(env, buffers, &is_array);
2817
+ if (!is_array) {
2818
+ free(vertex_entry);
2819
+ free(fragment_entry);
2820
+ NAPI_THROW(env, "createRenderPipeline: descriptor.vertex.buffers must be an array");
2821
+ }
2822
+ napi_get_array_length(env, buffers, &vertex_buffer_count);
2823
+ if (vertex_buffer_count > 0) {
2824
+ size_t total_attributes = 0;
2825
+ for (uint32_t i = 0; i < vertex_buffer_count; i++) {
2826
+ napi_value buffer_desc;
2827
+ napi_get_element(env, buffers, i, &buffer_desc);
2828
+ if (prop_type(env, buffer_desc, "attributes") == napi_object) {
2829
+ napi_value attrs = get_prop(env, buffer_desc, "attributes");
2830
+ bool attrs_is_array = false;
2831
+ napi_is_array(env, attrs, &attrs_is_array);
2832
+ if (!attrs_is_array) {
2833
+ free(vertex_entry);
2834
+ free(fragment_entry);
2835
+ NAPI_THROW(env, "createRenderPipeline: descriptor.vertex.buffers[*].attributes must be an array");
2836
+ }
2837
+ uint32_t attr_count = 0;
2838
+ napi_get_array_length(env, attrs, &attr_count);
2839
+ total_attributes += attr_count;
2840
+ }
2841
+ }
2842
+
2843
+ vertex_buffers = (WGPURenderVertexBufferLayout*)calloc(vertex_buffer_count, sizeof(WGPURenderVertexBufferLayout));
2844
+ if (!vertex_buffers) {
2845
+ free(vertex_entry);
2846
+ free(fragment_entry);
2847
+ NAPI_THROW(env, "createRenderPipeline: out of memory");
2848
+ }
2849
+ if (total_attributes > 0) {
2850
+ vertex_attributes = (WGPURenderVertexAttribute*)calloc(total_attributes, sizeof(WGPURenderVertexAttribute));
2851
+ if (!vertex_attributes) {
2852
+ free(vertex_buffers);
2853
+ free(vertex_entry);
2854
+ free(fragment_entry);
2855
+ NAPI_THROW(env, "createRenderPipeline: out of memory");
2856
+ }
2857
+ }
2858
+
2859
+ size_t attr_index = 0;
2860
+ for (uint32_t i = 0; i < vertex_buffer_count; i++) {
2861
+ napi_value buffer_desc;
2862
+ napi_get_element(env, buffers, i, &buffer_desc);
2863
+ vertex_buffers[i].nextInChain = NULL;
2864
+ vertex_buffers[i].stepMode = has_prop(env, buffer_desc, "stepMode")
2865
+ ? vertex_step_mode_from_value(env, get_prop(env, buffer_desc, "stepMode"))
2866
+ : 0x00000001;
2867
+ vertex_buffers[i].arrayStride = has_prop(env, buffer_desc, "arrayStride")
2868
+ ? (uint64_t)get_int64_prop(env, buffer_desc, "arrayStride")
2869
+ : 0;
2870
+ vertex_buffers[i].attributeCount = 0;
2871
+ vertex_buffers[i].attributes = NULL;
2872
+
2873
+ if (prop_type(env, buffer_desc, "attributes") == napi_object) {
2874
+ napi_value attrs = get_prop(env, buffer_desc, "attributes");
2875
+ uint32_t attr_count = 0;
2876
+ napi_get_array_length(env, attrs, &attr_count);
2877
+ vertex_buffers[i].attributeCount = attr_count;
2878
+ vertex_buffers[i].attributes = attr_count > 0 ? &vertex_attributes[attr_index] : NULL;
2879
+ for (uint32_t j = 0; j < attr_count; j++) {
2880
+ napi_value attr;
2881
+ napi_get_element(env, attrs, j, &attr);
2882
+ vertex_attributes[attr_index].nextInChain = NULL;
2883
+ vertex_attributes[attr_index].format = vertex_format_from_value(env, get_prop(env, attr, "format"));
2884
+ vertex_attributes[attr_index].offset = has_prop(env, attr, "offset")
2885
+ ? (uint64_t)get_int64_prop(env, attr, "offset")
2886
+ : 0;
2887
+ vertex_attributes[attr_index].shaderLocation = get_uint32_prop(env, attr, "shaderLocation");
2888
+ attr_index += 1;
2889
+ }
2890
+ }
2891
+ }
2892
+ }
2893
+ }
2894
+
2895
+ napi_value target0;
2896
+ napi_get_element(env, targets, 0, &target0);
2897
+
2898
+ WGPURenderColorTargetState color_target;
2899
+ memset(&color_target, 0, sizeof(color_target));
2900
+ color_target.nextInChain = NULL;
2901
+ color_target.format = texture_format_from_string(env, get_prop(env, target0, "format"));
2902
+ color_target.blend = NULL;
2903
+ color_target.writeMask = 0xF;
2904
+
2905
+ WGPURenderFragmentState fragment_state;
2906
+ memset(&fragment_state, 0, sizeof(fragment_state));
2907
+ fragment_state.nextInChain = NULL;
2908
+ fragment_state.module = fragment_module;
2909
+ fragment_state.entryPoint.data = fragment_entry;
2910
+ fragment_state.entryPoint.length = fragment_entry_len;
2911
+ fragment_state.constantCount = 0;
2912
+ fragment_state.constants = NULL;
2913
+ fragment_state.targetCount = 1;
2914
+ fragment_state.targets = &color_target;
2915
+
2916
+ WGPURenderPipelineDescriptor desc;
2917
+ memset(&desc, 0, sizeof(desc));
2918
+ desc.nextInChain = NULL;
2919
+ desc.label.data = NULL;
2920
+ desc.label.length = 0;
2921
+ desc.layout = has_prop(env, _args[1], "layout") && prop_type(env, _args[1], "layout") == napi_external
2922
+ ? unwrap_ptr(env, get_prop(env, _args[1], "layout"))
2923
+ : NULL;
2924
+ desc.vertex.nextInChain = NULL;
2925
+ desc.vertex.module = vertex_module;
2926
+ desc.vertex.entryPoint.data = vertex_entry;
2927
+ desc.vertex.entryPoint.length = vertex_entry_len;
2928
+ desc.vertex.constantCount = 0;
2929
+ desc.vertex.constants = NULL;
2930
+ desc.vertex.bufferCount = vertex_buffer_count;
2931
+ desc.vertex.buffers = vertex_buffers;
2932
+ desc.primitive.nextInChain = NULL;
2933
+ desc.primitive.topology = 0x00000004;
2934
+ desc.primitive.stripIndexFormat = 0;
2935
+ desc.primitive.frontFace = 0x00000001;
2936
+ desc.primitive.cullMode = 0x00000001;
2937
+ desc.primitive.unclippedDepth = 0;
2938
+ if (has_prop(env, _args[1], "primitive") && prop_type(env, _args[1], "primitive") == napi_object) {
2939
+ napi_value primitive = get_prop(env, _args[1], "primitive");
2940
+ if (has_prop(env, primitive, "topology"))
2941
+ desc.primitive.topology = primitive_topology_from_string(env, get_prop(env, primitive, "topology"));
2942
+ if (has_prop(env, primitive, "frontFace"))
2943
+ desc.primitive.frontFace = front_face_from_string(env, get_prop(env, primitive, "frontFace"));
2944
+ if (has_prop(env, primitive, "cullMode"))
2945
+ desc.primitive.cullMode = cull_mode_from_string(env, get_prop(env, primitive, "cullMode"));
2946
+ if (has_prop(env, primitive, "unclippedDepth"))
2947
+ desc.primitive.unclippedDepth = get_bool_prop(env, primitive, "unclippedDepth") ? 1 : 0;
2948
+ }
2949
+ desc.depthStencil = NULL;
2950
+ if (has_prop(env, _args[1], "depthStencil") && prop_type(env, _args[1], "depthStencil") == napi_object) {
2951
+ napi_value depth_obj = get_prop(env, _args[1], "depthStencil");
2952
+ depth_stencil = (WGPURenderDepthStencilState*)calloc(1, sizeof(WGPURenderDepthStencilState));
2953
+ if (!depth_stencil) {
2954
+ free(vertex_buffers);
2955
+ free(vertex_attributes);
2956
+ free(vertex_entry);
2957
+ free(fragment_entry);
2958
+ NAPI_THROW(env, "createRenderPipeline: out of memory");
2959
+ }
2960
+ depth_stencil->nextInChain = NULL;
2961
+ depth_stencil->format = texture_format_from_string(env, get_prop(env, depth_obj, "format"));
2962
+ depth_stencil->depthWriteEnabled = has_prop(env, depth_obj, "depthWriteEnabled")
2963
+ ? (get_bool_prop(env, depth_obj, "depthWriteEnabled") ? 1 : 0)
2964
+ : 0;
2965
+ depth_stencil->depthCompare = has_prop(env, depth_obj, "depthCompare")
2966
+ ? compare_func_from_value(env, get_prop(env, depth_obj, "depthCompare"))
2967
+ : 0x00000008;
2968
+ depth_stencil->stencilReadMask = 0xFFFFFFFFu;
2969
+ depth_stencil->stencilWriteMask = 0xFFFFFFFFu;
2970
+ desc.depthStencil = depth_stencil;
2971
+ }
2972
+ desc.multisample.nextInChain = NULL;
2973
+ desc.multisample.count = 1;
2974
+ desc.multisample.mask = 0xFFFFffffu;
2975
+ desc.multisample.alphaToCoverageEnabled = 0;
2976
+ if (has_prop(env, _args[1], "multisample") && prop_type(env, _args[1], "multisample") == napi_object) {
2977
+ napi_value multisample = get_prop(env, _args[1], "multisample");
2978
+ if (has_prop(env, multisample, "count"))
2979
+ desc.multisample.count = get_uint32_prop(env, multisample, "count");
2980
+ if (has_prop(env, multisample, "mask"))
2981
+ desc.multisample.mask = get_uint32_prop(env, multisample, "mask");
2982
+ if (has_prop(env, multisample, "alphaToCoverageEnabled"))
2983
+ desc.multisample.alphaToCoverageEnabled = get_bool_prop(env, multisample, "alphaToCoverageEnabled") ? 1 : 0;
2984
+ }
2985
+ desc.fragment = &fragment_state;
2986
+
2987
+ WGPURenderPipeline rp = pfn_wgpuDeviceCreateRenderPipeline(device, &desc);
2988
+ free(depth_stencil);
2989
+ free(vertex_attributes);
2990
+ free(vertex_buffers);
2991
+ free(vertex_entry);
2992
+ free(fragment_entry);
1866
2993
  if (!rp) NAPI_THROW(env, "createRenderPipeline failed");
1867
2994
  return wrap_ptr(env, rp);
1868
2995
  }
@@ -1876,7 +3003,7 @@ static napi_value doe_render_pipeline_release(napi_env env, napi_callback_info i
1876
3003
 
1877
3004
  /* ================================================================
1878
3005
  * Render Pass
1879
- * beginRenderPass(encoder, colorAttachments[])
3006
+ * beginRenderPass(encoder, descriptor)
1880
3007
  * ================================================================ */
1881
3008
 
1882
3009
  static napi_value doe_begin_render_pass(napi_env env, napi_callback_info info) {
@@ -1885,16 +3012,23 @@ static napi_value doe_begin_render_pass(napi_env env, napi_callback_info info) {
1885
3012
  WGPUCommandEncoder enc = unwrap_ptr(env, _args[0]);
1886
3013
  if (!enc) NAPI_THROW(env, "Invalid encoder");
1887
3014
 
1888
- /* _args[1] is array of color attachments */
3015
+ if (prop_type(env, _args[1], "colorAttachments") != napi_object) {
3016
+ NAPI_THROW(env, "beginRenderPass requires descriptor.colorAttachments");
3017
+ }
3018
+
3019
+ napi_value color_attachments = get_prop(env, _args[1], "colorAttachments");
1889
3020
  uint32_t att_count = 0;
1890
- napi_get_array_length(env, _args[1], &att_count);
3021
+ napi_get_array_length(env, color_attachments, &att_count);
1891
3022
  if (att_count == 0) NAPI_THROW(env, "beginRenderPass: need at least one color attachment");
1892
3023
 
1893
3024
  WGPURenderPassColorAttachment* atts = (WGPURenderPassColorAttachment*)calloc(
1894
3025
  att_count, sizeof(WGPURenderPassColorAttachment));
3026
+ WGPURenderPassDepthStencilAttachment depth_att;
3027
+ memset(&depth_att, 0, sizeof(depth_att));
3028
+ bool has_depth_att = false;
1895
3029
  for (uint32_t i = 0; i < att_count; i++) {
1896
3030
  napi_value elem;
1897
- napi_get_element(env, _args[1], i, &elem);
3031
+ napi_get_element(env, color_attachments, i, &elem);
1898
3032
  atts[i].view = unwrap_ptr(env, get_prop(env, elem, "view"));
1899
3033
  atts[i].loadOp = 1; /* clear */
1900
3034
  atts[i].storeOp = 1; /* store */
@@ -1914,10 +3048,34 @@ static napi_value doe_begin_render_pass(napi_env env, napi_callback_info info) {
1914
3048
  }
1915
3049
  }
1916
3050
 
3051
+ if (has_prop(env, _args[1], "depthStencilAttachment") && prop_type(env, _args[1], "depthStencilAttachment") == napi_object) {
3052
+ napi_value depth_obj = get_prop(env, _args[1], "depthStencilAttachment");
3053
+ depth_att.nextInChain = NULL;
3054
+ depth_att.view = unwrap_ptr(env, get_prop(env, depth_obj, "view"));
3055
+ depth_att.depthLoadOp = 1;
3056
+ depth_att.depthStoreOp = 1;
3057
+ depth_att.depthClearValue = has_prop(env, depth_obj, "depthClearValue")
3058
+ ? (float)get_double_prop(env, depth_obj, "depthClearValue")
3059
+ : 1.0f;
3060
+ depth_att.depthReadOnly = has_prop(env, depth_obj, "depthReadOnly")
3061
+ ? (get_bool_prop(env, depth_obj, "depthReadOnly") ? 1 : 0)
3062
+ : 0;
3063
+ depth_att.stencilLoadOp = 1;
3064
+ depth_att.stencilStoreOp = 1;
3065
+ depth_att.stencilClearValue = has_prop(env, depth_obj, "stencilClearValue")
3066
+ ? get_uint32_prop(env, depth_obj, "stencilClearValue")
3067
+ : 0;
3068
+ depth_att.stencilReadOnly = has_prop(env, depth_obj, "stencilReadOnly")
3069
+ ? (get_bool_prop(env, depth_obj, "stencilReadOnly") ? 1 : 0)
3070
+ : 0;
3071
+ has_depth_att = true;
3072
+ }
3073
+
1917
3074
  WGPURenderPassDescriptor desc;
1918
3075
  memset(&desc, 0, sizeof(desc));
1919
3076
  desc.colorAttachmentCount = att_count;
1920
3077
  desc.colorAttachments = atts;
3078
+ desc.depthStencilAttachment = has_depth_att ? &depth_att : NULL;
1921
3079
 
1922
3080
  WGPURenderPassEncoder pass = pfn_wgpuCommandEncoderBeginRenderPass(enc, &desc);
1923
3081
  free(atts);
@@ -1932,6 +3090,43 @@ static napi_value doe_render_pass_set_pipeline(napi_env env, napi_callback_info
1932
3090
  return NULL;
1933
3091
  }
1934
3092
 
3093
+ static napi_value doe_render_pass_set_bind_group(napi_env env, napi_callback_info info) {
3094
+ NAPI_ASSERT_ARGC(env, info, 3);
3095
+ WGPURenderPassEncoder pass = unwrap_ptr(env, _args[0]);
3096
+ uint32_t index = 0;
3097
+ napi_get_value_uint32(env, _args[1], &index);
3098
+ WGPUBindGroup bg = unwrap_ptr(env, _args[2]);
3099
+ pfn_wgpuRenderPassEncoderSetBindGroup(pass, index, bg, 0, NULL);
3100
+ return NULL;
3101
+ }
3102
+
3103
+ static napi_value doe_render_pass_set_vertex_buffer(napi_env env, napi_callback_info info) {
3104
+ NAPI_ASSERT_ARGC(env, info, 5);
3105
+ WGPURenderPassEncoder pass = unwrap_ptr(env, _args[0]);
3106
+ uint32_t slot = 0;
3107
+ uint64_t offset = 0;
3108
+ uint64_t size = 0;
3109
+ napi_get_value_uint32(env, _args[1], &slot);
3110
+ WGPUBuffer buffer = unwrap_ptr(env, _args[2]);
3111
+ offset = (uint64_t)get_int64_value(env, _args[3]);
3112
+ size = (uint64_t)get_int64_value(env, _args[4]);
3113
+ pfn_wgpuRenderPassEncoderSetVertexBuffer(pass, slot, buffer, offset, size);
3114
+ return NULL;
3115
+ }
3116
+
3117
+ static napi_value doe_render_pass_set_index_buffer(napi_env env, napi_callback_info info) {
3118
+ NAPI_ASSERT_ARGC(env, info, 5);
3119
+ WGPURenderPassEncoder pass = unwrap_ptr(env, _args[0]);
3120
+ WGPUBuffer buffer = unwrap_ptr(env, _args[1]);
3121
+ uint32_t format = index_format_from_value(env, _args[2]);
3122
+ uint64_t offset = 0;
3123
+ uint64_t size = 0;
3124
+ offset = (uint64_t)get_int64_value(env, _args[3]);
3125
+ size = (uint64_t)get_int64_value(env, _args[4]);
3126
+ pfn_wgpuRenderPassEncoderSetIndexBuffer(pass, buffer, format, offset, size);
3127
+ return NULL;
3128
+ }
3129
+
1935
3130
  /* renderPassDraw(pass, vertexCount, instanceCount, firstVertex, firstInstance) */
1936
3131
  static napi_value doe_render_pass_draw(napi_env env, napi_callback_info info) {
1937
3132
  NAPI_ASSERT_ARGC(env, info, 5);
@@ -1945,6 +3140,23 @@ static napi_value doe_render_pass_draw(napi_env env, napi_callback_info info) {
1945
3140
  return NULL;
1946
3141
  }
1947
3142
 
3143
+ static napi_value doe_render_pass_draw_indexed(napi_env env, napi_callback_info info) {
3144
+ NAPI_ASSERT_ARGC(env, info, 6);
3145
+ WGPURenderPassEncoder pass = unwrap_ptr(env, _args[0]);
3146
+ uint32_t index_count = 0;
3147
+ uint32_t instance_count = 0;
3148
+ uint32_t first_index = 0;
3149
+ int32_t base_vertex = 0;
3150
+ uint32_t first_instance = 0;
3151
+ napi_get_value_uint32(env, _args[1], &index_count);
3152
+ napi_get_value_uint32(env, _args[2], &instance_count);
3153
+ napi_get_value_uint32(env, _args[3], &first_index);
3154
+ napi_get_value_int32(env, _args[4], &base_vertex);
3155
+ napi_get_value_uint32(env, _args[5], &first_instance);
3156
+ pfn_wgpuRenderPassEncoderDrawIndexed(pass, index_count, instance_count, first_index, base_vertex, first_instance);
3157
+ return NULL;
3158
+ }
3159
+
1948
3160
  static napi_value doe_render_pass_end(napi_env env, napi_callback_info info) {
1949
3161
  NAPI_ASSERT_ARGC(env, info, 1);
1950
3162
  pfn_wgpuRenderPassEncoderEnd(unwrap_ptr(env, _args[0]));
@@ -1962,21 +3174,12 @@ static napi_value doe_render_pass_release(napi_env env, napi_callback_info info)
1962
3174
  * Device capabilities: limits, features
1963
3175
  * ================================================================ */
1964
3176
 
1965
- static napi_value doe_device_get_limits(napi_env env, napi_callback_info info) {
1966
- NAPI_ASSERT_ARGC(env, info, 1);
1967
- CHECK_LIB_LOADED(env);
1968
- WGPUDevice device = unwrap_ptr(env, _args[0]);
1969
- if (!device) NAPI_THROW(env, "deviceGetLimits: null device");
1970
-
1971
- WGPULimits limits;
1972
- memset(&limits, 0, sizeof(limits));
1973
- pfn_wgpuDeviceGetLimits(device, &limits);
1974
-
3177
+ static napi_value create_limits_object(napi_env env, const WGPULimits* limits) {
1975
3178
  napi_value obj;
1976
3179
  napi_create_object(env, &obj);
1977
3180
 
1978
- #define SET_U32(name) do { napi_value v; napi_create_uint32(env, limits.name, &v); napi_set_named_property(env, obj, #name, v); } while(0)
1979
- #define SET_U64(name) do { napi_value v; napi_create_double(env, (double)limits.name, &v); napi_set_named_property(env, obj, #name, v); } while(0)
3181
+ #define SET_U32(name) do { napi_value v; napi_create_uint32(env, limits->name, &v); napi_set_named_property(env, obj, #name, v); } while(0)
3182
+ #define SET_U64(name) do { napi_value v; napi_create_double(env, (double)limits->name, &v); napi_set_named_property(env, obj, #name, v); } while(0)
1980
3183
 
1981
3184
  SET_U32(maxTextureDimension1D);
1982
3185
  SET_U32(maxTextureDimension2D);
@@ -2016,18 +3219,192 @@ static napi_value doe_device_get_limits(napi_env env, napi_callback_info info) {
2016
3219
  return obj;
2017
3220
  }
2018
3221
 
3222
+ static napi_value doe_device_get_limits(napi_env env, napi_callback_info info) {
3223
+ NAPI_ASSERT_ARGC(env, info, 1);
3224
+ CHECK_LIB_LOADED(env);
3225
+ WGPUDevice device = unwrap_ptr(env, _args[0]);
3226
+ if (!device) NAPI_THROW(env, "deviceGetLimits: null device");
3227
+ uint32_t (*fn)(WGPUDevice, void*) = pfn_doeNativeDeviceGetLimits ? pfn_doeNativeDeviceGetLimits : pfn_wgpuDeviceGetLimits;
3228
+ if (!fn) {
3229
+ napi_value ret;
3230
+ napi_get_null(env, &ret);
3231
+ return ret;
3232
+ }
3233
+
3234
+ WGPULimits limits;
3235
+ memset(&limits, 0, sizeof(limits));
3236
+ uint32_t status = fn(device, &limits);
3237
+ if (status != WGPU_STATUS_SUCCESS) {
3238
+ napi_value ret;
3239
+ napi_get_null(env, &ret);
3240
+ return ret;
3241
+ }
3242
+
3243
+ return create_limits_object(env, &limits);
3244
+ }
3245
+
3246
+ static napi_value doe_adapter_get_limits(napi_env env, napi_callback_info info) {
3247
+ NAPI_ASSERT_ARGC(env, info, 1);
3248
+ CHECK_LIB_LOADED(env);
3249
+ WGPUAdapter adapter = unwrap_ptr(env, _args[0]);
3250
+ if (!adapter) NAPI_THROW(env, "adapterGetLimits: null adapter");
3251
+ uint32_t (*fn)(WGPUAdapter, void*) = pfn_doeNativeAdapterGetLimits ? pfn_doeNativeAdapterGetLimits : pfn_wgpuAdapterGetLimits;
3252
+ if (!fn) {
3253
+ napi_value ret;
3254
+ napi_get_null(env, &ret);
3255
+ return ret;
3256
+ }
3257
+
3258
+ WGPULimits limits;
3259
+ memset(&limits, 0, sizeof(limits));
3260
+ uint32_t status = fn(adapter, &limits);
3261
+ if (status != WGPU_STATUS_SUCCESS) {
3262
+ napi_value ret;
3263
+ napi_get_null(env, &ret);
3264
+ return ret;
3265
+ }
3266
+
3267
+ return create_limits_object(env, &limits);
3268
+ }
3269
+
3270
+ static napi_value doe_adapter_has_feature(napi_env env, napi_callback_info info) {
3271
+ NAPI_ASSERT_ARGC(env, info, 2);
3272
+ CHECK_LIB_LOADED(env);
3273
+ WGPUAdapter adapter = unwrap_ptr(env, _args[0]);
3274
+ uint32_t (*fn)(WGPUAdapter, uint32_t) = pfn_doeNativeAdapterHasFeature ? pfn_doeNativeAdapterHasFeature : pfn_wgpuAdapterHasFeature;
3275
+ if (!fn) {
3276
+ napi_value ret;
3277
+ napi_get_boolean(env, false, &ret);
3278
+ return ret;
3279
+ }
3280
+ uint32_t feature;
3281
+ napi_get_value_uint32(env, _args[1], &feature);
3282
+ uint32_t result = fn(adapter, feature);
3283
+ napi_value ret;
3284
+ napi_get_boolean(env, result != 0, &ret);
3285
+ return ret;
3286
+ }
3287
+
2019
3288
  static napi_value doe_device_has_feature(napi_env env, napi_callback_info info) {
2020
3289
  NAPI_ASSERT_ARGC(env, info, 2);
2021
3290
  CHECK_LIB_LOADED(env);
2022
3291
  WGPUDevice device = unwrap_ptr(env, _args[0]);
3292
+ uint32_t (*fn)(WGPUDevice, uint32_t) = pfn_doeNativeDeviceHasFeature ? pfn_doeNativeDeviceHasFeature : pfn_wgpuDeviceHasFeature;
3293
+ if (!fn) {
3294
+ napi_value ret;
3295
+ napi_get_boolean(env, false, &ret);
3296
+ return ret;
3297
+ }
2023
3298
  uint32_t feature;
2024
3299
  napi_get_value_uint32(env, _args[1], &feature);
2025
- uint32_t result = pfn_wgpuDeviceHasFeature(device, feature);
3300
+ uint32_t result = fn(device, feature);
2026
3301
  napi_value ret;
2027
3302
  napi_get_boolean(env, result != 0, &ret);
2028
3303
  return ret;
2029
3304
  }
2030
3305
 
3306
+ static napi_value doe_get_last_error_stage(napi_env env, napi_callback_info info) {
3307
+ (void)info;
3308
+ char buf[64];
3309
+ copy_library_error_meta(pfn_doeNativeCopyLastErrorStage, buf, sizeof(buf));
3310
+ if (buf[0] == '\0') return NULL;
3311
+ napi_value result;
3312
+ napi_create_string_utf8(env, buf, NAPI_AUTO_LENGTH, &result);
3313
+ return result;
3314
+ }
3315
+
3316
+ static napi_value doe_get_last_error_kind(napi_env env, napi_callback_info info) {
3317
+ (void)info;
3318
+ char buf[64];
3319
+ copy_library_error_meta(pfn_doeNativeCopyLastErrorKind, buf, sizeof(buf));
3320
+ if (buf[0] == '\0') return NULL;
3321
+ napi_value result;
3322
+ napi_create_string_utf8(env, buf, NAPI_AUTO_LENGTH, &result);
3323
+ return result;
3324
+ }
3325
+
3326
+ static napi_value doe_get_last_error_line(napi_env env, napi_callback_info info) {
3327
+ (void)info;
3328
+ if (!pfn_doeNativeGetLastErrorLine) return NULL;
3329
+ uint32_t line = pfn_doeNativeGetLastErrorLine();
3330
+ napi_value result;
3331
+ napi_create_uint32(env, line, &result);
3332
+ return result;
3333
+ }
3334
+
3335
+ static napi_value doe_get_last_error_column(napi_env env, napi_callback_info info) {
3336
+ (void)info;
3337
+ if (!pfn_doeNativeGetLastErrorColumn) return NULL;
3338
+ uint32_t col = pfn_doeNativeGetLastErrorColumn();
3339
+ napi_value result;
3340
+ napi_create_uint32(env, col, &result);
3341
+ return result;
3342
+ }
3343
+
3344
+ /* ================================================================
3345
+ * QuerySet (timestamp query)
3346
+ * ================================================================ */
3347
+
3348
+ static napi_value doe_create_query_set(napi_env env, napi_callback_info info) {
3349
+ NAPI_ASSERT_ARGC(env, info, 3);
3350
+ CHECK_LIB_LOADED(env);
3351
+ if (!pfn_doeNativeDeviceCreateQuerySet) NAPI_THROW(env, "doeNativeDeviceCreateQuerySet not available");
3352
+ WGPUDevice device = unwrap_ptr(env, _args[0]);
3353
+ if (!device) NAPI_THROW(env, "Invalid device");
3354
+ uint32_t query_type = 0;
3355
+ napi_get_value_uint32(env, _args[1], &query_type);
3356
+ uint32_t count = 0;
3357
+ napi_get_value_uint32(env, _args[2], &count);
3358
+ WGPUQuerySet qs = pfn_doeNativeDeviceCreateQuerySet(device, query_type, count);
3359
+ if (!qs) NAPI_THROW(env, "createQuerySet failed");
3360
+ return wrap_ptr(env, qs);
3361
+ }
3362
+
3363
+ static napi_value doe_command_encoder_write_timestamp(napi_env env, napi_callback_info info) {
3364
+ NAPI_ASSERT_ARGC(env, info, 3);
3365
+ CHECK_LIB_LOADED(env);
3366
+ if (!pfn_doeNativeCommandEncoderWriteTimestamp) NAPI_THROW(env, "doeNativeCommandEncoderWriteTimestamp not available");
3367
+ WGPUCommandEncoder enc = unwrap_ptr(env, _args[0]);
3368
+ WGPUQuerySet qs = unwrap_ptr(env, _args[1]);
3369
+ uint32_t query_index = 0;
3370
+ napi_get_value_uint32(env, _args[2], &query_index);
3371
+ pfn_doeNativeCommandEncoderWriteTimestamp(enc, qs, query_index);
3372
+ return NULL;
3373
+ }
3374
+
3375
+ static napi_value doe_command_encoder_resolve_query_set(napi_env env, napi_callback_info info) {
3376
+ NAPI_ASSERT_ARGC(env, info, 6);
3377
+ CHECK_LIB_LOADED(env);
3378
+ if (!pfn_doeNativeCommandEncoderResolveQuerySet) NAPI_THROW(env, "doeNativeCommandEncoderResolveQuerySet not available");
3379
+ WGPUCommandEncoder enc = unwrap_ptr(env, _args[0]);
3380
+ WGPUQuerySet qs = unwrap_ptr(env, _args[1]);
3381
+ uint32_t first_query = 0;
3382
+ napi_get_value_uint32(env, _args[2], &first_query);
3383
+ uint32_t query_count = 0;
3384
+ napi_get_value_uint32(env, _args[3], &query_count);
3385
+ WGPUBuffer dst = unwrap_ptr(env, _args[4]);
3386
+ int64_t dst_offset = 0;
3387
+ napi_get_value_int64(env, _args[5], &dst_offset);
3388
+ pfn_doeNativeCommandEncoderResolveQuerySet(enc, qs, first_query, query_count, dst, (uint64_t)dst_offset);
3389
+ return NULL;
3390
+ }
3391
+
3392
+ static napi_value doe_query_set_destroy(napi_env env, napi_callback_info info) {
3393
+ NAPI_ASSERT_ARGC(env, info, 1);
3394
+ if (!pfn_doeNativeQuerySetDestroy) return NULL;
3395
+ WGPUQuerySet qs = unwrap_ptr(env, _args[0]);
3396
+ if (qs) pfn_doeNativeQuerySetDestroy(qs);
3397
+ return NULL;
3398
+ }
3399
+
3400
+ static napi_value doe_set_timeout_ms(napi_env env, napi_callback_info info) {
3401
+ NAPI_ASSERT_ARGC(env, info, 1);
3402
+ uint32_t timeout_ms = 0;
3403
+ napi_get_value_uint32(env, _args[0], &timeout_ms);
3404
+ g_timeout_ns = (uint64_t)timeout_ms * 1000000ULL;
3405
+ return NULL;
3406
+ }
3407
+
2031
3408
  /* ================================================================
2032
3409
  * Module initialization
2033
3410
  * ================================================================ */
@@ -2049,9 +3426,13 @@ static napi_value doe_module_init(napi_env env, napi_value exports) {
2049
3426
  EXPORT_FN("bufferUnmap", doe_buffer_unmap),
2050
3427
  EXPORT_FN("bufferMapSync", doe_buffer_map_sync),
2051
3428
  EXPORT_FN("bufferGetMappedRange", doe_buffer_get_mapped_range),
3429
+ EXPORT_FN("bufferWriteMappedRange", doe_buffer_write_mapped_range),
3430
+ EXPORT_FN("bufferReadIndirectCounts", doe_buffer_read_indirect_counts),
2052
3431
  EXPORT_FN("bufferAssertMappedPrefixF32", doe_buffer_assert_mapped_prefix_f32),
3432
+ EXPORT_FN("checkShaderSource", doe_check_shader_source),
2053
3433
  EXPORT_FN("createShaderModule", doe_create_shader_module),
2054
3434
  EXPORT_FN("shaderModuleRelease", doe_shader_module_release),
3435
+ EXPORT_FN("shaderModuleGetBindings", doe_shader_module_get_bindings),
2055
3436
  EXPORT_FN("createComputePipeline", doe_create_compute_pipeline),
2056
3437
  EXPORT_FN("computePipelineRelease", doe_compute_pipeline_release),
2057
3438
  EXPORT_FN("computePipelineGetBindGroupLayout", doe_compute_pipeline_get_bind_group_layout),
@@ -2064,6 +3445,7 @@ static napi_value doe_module_init(napi_env env, napi_value exports) {
2064
3445
  EXPORT_FN("createCommandEncoder", doe_create_command_encoder),
2065
3446
  EXPORT_FN("commandEncoderRelease", doe_command_encoder_release),
2066
3447
  EXPORT_FN("commandEncoderCopyBufferToBuffer", doe_command_encoder_copy_buffer_to_buffer),
3448
+ EXPORT_FN("commandEncoderCopyTextureToBuffer", doe_command_encoder_copy_texture_to_buffer),
2067
3449
  EXPORT_FN("commandEncoderFinish", doe_command_encoder_finish),
2068
3450
  EXPORT_FN("commandBufferRelease", doe_command_buffer_release),
2069
3451
  EXPORT_FN("beginComputePass", doe_begin_compute_pass),
@@ -2090,11 +3472,26 @@ static napi_value doe_module_init(napi_env env, napi_value exports) {
2090
3472
  EXPORT_FN("renderPipelineRelease", doe_render_pipeline_release),
2091
3473
  EXPORT_FN("beginRenderPass", doe_begin_render_pass),
2092
3474
  EXPORT_FN("renderPassSetPipeline", doe_render_pass_set_pipeline),
3475
+ EXPORT_FN("renderPassSetBindGroup", doe_render_pass_set_bind_group),
3476
+ EXPORT_FN("renderPassSetVertexBuffer", doe_render_pass_set_vertex_buffer),
3477
+ EXPORT_FN("renderPassSetIndexBuffer", doe_render_pass_set_index_buffer),
2093
3478
  EXPORT_FN("renderPassDraw", doe_render_pass_draw),
3479
+ EXPORT_FN("renderPassDrawIndexed", doe_render_pass_draw_indexed),
2094
3480
  EXPORT_FN("renderPassEnd", doe_render_pass_end),
2095
3481
  EXPORT_FN("renderPassRelease", doe_render_pass_release),
3482
+ EXPORT_FN("adapterGetLimits", doe_adapter_get_limits),
3483
+ EXPORT_FN("adapterHasFeature", doe_adapter_has_feature),
2096
3484
  EXPORT_FN("deviceGetLimits", doe_device_get_limits),
2097
3485
  EXPORT_FN("deviceHasFeature", doe_device_has_feature),
3486
+ EXPORT_FN("createQuerySet", doe_create_query_set),
3487
+ EXPORT_FN("commandEncoderWriteTimestamp", doe_command_encoder_write_timestamp),
3488
+ EXPORT_FN("commandEncoderResolveQuerySet", doe_command_encoder_resolve_query_set),
3489
+ EXPORT_FN("querySetDestroy", doe_query_set_destroy),
3490
+ EXPORT_FN("setTimeoutMs", doe_set_timeout_ms),
3491
+ EXPORT_FN("getLastErrorStage", doe_get_last_error_stage),
3492
+ EXPORT_FN("getLastErrorKind", doe_get_last_error_kind),
3493
+ EXPORT_FN("getLastErrorLine", doe_get_last_error_line),
3494
+ EXPORT_FN("getLastErrorColumn", doe_get_last_error_column),
2098
3495
  };
2099
3496
 
2100
3497
  size_t count = sizeof(descriptors) / sizeof(descriptors[0]);