@simulatte/webgpu 0.2.3 → 0.3.0

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 (44) hide show
  1. package/CHANGELOG.md +47 -4
  2. package/README.md +273 -235
  3. package/api-contract.md +163 -0
  4. package/assets/fawn-icon-main-256.png +0 -0
  5. package/assets/package-layers.svg +63 -0
  6. package/assets/package-surface-cube-snapshot.svg +7 -7
  7. package/{COMPAT_SCOPE.md → compat-scope.md} +1 -1
  8. package/examples/direct-webgpu/compute-dispatch.js +66 -0
  9. package/examples/direct-webgpu/explicit-bind-group.js +85 -0
  10. package/examples/direct-webgpu/request-device.js +10 -0
  11. package/examples/doe-api/buffers-readback.js +9 -0
  12. package/examples/doe-api/compile-and-dispatch.js +30 -0
  13. package/examples/doe-api/compute-dispatch.js +25 -0
  14. package/examples/doe-routines/compute-once-like-input.js +36 -0
  15. package/examples/doe-routines/compute-once-matmul.js +53 -0
  16. package/examples/doe-routines/compute-once-multiple-inputs.js +27 -0
  17. package/examples/doe-routines/compute-once.js +23 -0
  18. package/headless-webgpu-comparison.md +2 -2
  19. package/{LAYERING_PLAN.md → layering-plan.md} +10 -8
  20. package/native/doe_napi.c +102 -12
  21. package/package.json +26 -9
  22. package/prebuilds/darwin-arm64/doe_napi.node +0 -0
  23. package/prebuilds/darwin-arm64/libwebgpu_doe.dylib +0 -0
  24. package/prebuilds/darwin-arm64/metadata.json +6 -6
  25. package/prebuilds/linux-x64/doe_napi.node +0 -0
  26. package/prebuilds/linux-x64/libwebgpu_doe.so +0 -0
  27. package/prebuilds/linux-x64/metadata.json +5 -5
  28. package/scripts/generate-readme-assets.js +81 -8
  29. package/scripts/prebuild.js +23 -19
  30. package/src/auto_bind_group_layout.js +32 -0
  31. package/src/bun-ffi.js +93 -12
  32. package/src/bun.js +23 -2
  33. package/src/compute.d.ts +162 -0
  34. package/src/compute.js +915 -0
  35. package/src/doe.d.ts +184 -0
  36. package/src/doe.js +641 -0
  37. package/src/full.d.ts +119 -0
  38. package/src/full.js +35 -0
  39. package/src/index.js +1013 -38
  40. package/src/node-runtime.js +2 -2
  41. package/src/node.js +2 -2
  42. package/{SUPPORT_CONTRACTS.md → support-contracts.md} +27 -41
  43. package/{ZIG_SOURCE_INVENTORY.md → zig-source-inventory.md} +2 -2
  44. package/API_CONTRACT.md +0 -182
package/src/compute.js ADDED
@@ -0,0 +1,915 @@
1
+ import * as full from './index.js';
2
+ import { createDoeNamespace } from './doe.js';
3
+
4
+ function unwrap(value) {
5
+ return value && typeof value === 'object' && '_raw' in value ? value._raw : value;
6
+ }
7
+
8
+ function wrap_buffer(raw) {
9
+ return {
10
+ _raw: raw,
11
+ size: raw.size,
12
+ usage: raw.usage,
13
+ /**
14
+ * Map the wrapped compute-surface buffer for host access.
15
+ *
16
+ * This forwards the mapping request to the underlying Doe buffer while
17
+ * keeping the narrower compute facade shape.
18
+ *
19
+ * This example shows the API in its basic form.
20
+ *
21
+ * ```js
22
+ * await buffer.mapAsync(GPUMapMode.READ);
23
+ * ```
24
+ *
25
+ * - This forwards directly to the underlying Doe buffer.
26
+ * - The compute facade keeps the same mapping semantics as the full surface.
27
+ */
28
+ async mapAsync(mode, offset, size) {
29
+ return raw.mapAsync(mode, offset, size);
30
+ },
31
+ /**
32
+ * Return the currently mapped byte range.
33
+ *
34
+ * This exposes the mapped bytes from the wrapped buffer without changing
35
+ * the compute-only facade.
36
+ *
37
+ * This example shows the API in its basic form.
38
+ *
39
+ * ```js
40
+ * const bytes = buffer.getMappedRange();
41
+ * ```
42
+ *
43
+ * - Call this only while the buffer is mapped.
44
+ * - The returned bytes come from the wrapped full-surface buffer object.
45
+ */
46
+ getMappedRange(offset, size) {
47
+ return raw.getMappedRange(offset, size);
48
+ },
49
+ /**
50
+ * Compare a mapped `f32` prefix against expected values.
51
+ *
52
+ * This is a small validation helper that mirrors the underlying Doe buffer
53
+ * behavior on the compute surface.
54
+ *
55
+ * This example shows the API in its basic form.
56
+ *
57
+ * ```js
58
+ * buffer.assertMappedPrefixF32([1, 2, 3, 4], 4);
59
+ * ```
60
+ *
61
+ * - The buffer must already be mapped.
62
+ * - This helper is most useful in tests and smoke checks.
63
+ */
64
+ assertMappedPrefixF32(expected, count) {
65
+ return raw.assertMappedPrefixF32(expected, count);
66
+ },
67
+ /**
68
+ * Release the current mapping.
69
+ *
70
+ * This forwards unmapping to the wrapped buffer so the resource can return
71
+ * to normal GPU ownership.
72
+ *
73
+ * This example shows the API in its basic form.
74
+ *
75
+ * ```js
76
+ * buffer.unmap();
77
+ * ```
78
+ *
79
+ * - This forwards directly to the wrapped Doe buffer.
80
+ */
81
+ unmap() {
82
+ return raw.unmap();
83
+ },
84
+ /**
85
+ * Release the wrapped native buffer.
86
+ *
87
+ * This tears down the underlying Doe buffer owned by this facade object.
88
+ *
89
+ * This example shows the API in its basic form.
90
+ *
91
+ * ```js
92
+ * buffer.destroy();
93
+ * ```
94
+ *
95
+ * - Reusing the buffer after destruction is unsupported.
96
+ */
97
+ destroy() {
98
+ return raw.destroy();
99
+ },
100
+ };
101
+ }
102
+
103
+ function wrap_bind_group_layout(raw) {
104
+ return { _raw: raw };
105
+ }
106
+
107
+ function wrap_bind_group(raw) {
108
+ return { _raw: raw };
109
+ }
110
+
111
+ function wrap_pipeline_layout(raw) {
112
+ return { _raw: raw };
113
+ }
114
+
115
+ function wrap_compute_pipeline(raw) {
116
+ return {
117
+ _raw: raw,
118
+ /**
119
+ * Return the bind-group layout for a given group index.
120
+ *
121
+ * This forwards layout lookup to the underlying compute pipeline and wraps
122
+ * the result back into the compute facade.
123
+ *
124
+ * This example shows the API in its basic form.
125
+ *
126
+ * ```js
127
+ * const layout = pipeline.getBindGroupLayout(0);
128
+ * ```
129
+ *
130
+ * - This forwards to the underlying full-surface compute pipeline.
131
+ * - The returned layout is wrapped back into the compute facade.
132
+ */
133
+ getBindGroupLayout(index) {
134
+ return wrap_bind_group_layout(raw.getBindGroupLayout(index));
135
+ },
136
+ };
137
+ }
138
+
139
+ function wrap_compute_pass(raw) {
140
+ return {
141
+ _raw: raw,
142
+ /**
143
+ * Set the compute pipeline used by later dispatch calls.
144
+ *
145
+ * This records the pipeline state that the wrapped compute pass should use
146
+ * for subsequent dispatches.
147
+ *
148
+ * This example shows the API in its basic form.
149
+ *
150
+ * ```js
151
+ * pass.setPipeline(pipeline);
152
+ * ```
153
+ *
154
+ * - The pipeline must come from this compute facade or the same underlying device.
155
+ */
156
+ setPipeline(pipeline) {
157
+ return raw.setPipeline(unwrap(pipeline));
158
+ },
159
+ /**
160
+ * Bind a bind group for the compute pass.
161
+ *
162
+ * This records the resource bindings that the wrapped pass should expose to
163
+ * the shader.
164
+ *
165
+ * This example shows the API in its basic form.
166
+ *
167
+ * ```js
168
+ * pass.setBindGroup(0, bindGroup);
169
+ * ```
170
+ *
171
+ * - The bind group is unwrapped before forwarding to the underlying pass.
172
+ */
173
+ setBindGroup(index, bindGroup) {
174
+ return raw.setBindGroup(index, unwrap(bindGroup));
175
+ },
176
+ /**
177
+ * Record a direct compute dispatch.
178
+ *
179
+ * This forwards an explicit workgroup dispatch to the wrapped pass encoder.
180
+ *
181
+ * This example shows the API in its basic form.
182
+ *
183
+ * ```js
184
+ * pass.dispatchWorkgroups(4, 1, 1);
185
+ * ```
186
+ *
187
+ * - Omitted `y` and `z` default to `1`.
188
+ */
189
+ dispatchWorkgroups(x, y = 1, z = 1) {
190
+ return raw.dispatchWorkgroups(x, y, z);
191
+ },
192
+ /**
193
+ * Dispatch workgroups using counts stored in a buffer.
194
+ *
195
+ * This forwards an indirect dispatch after unwrapping the buffer passed
196
+ * through the compute facade.
197
+ *
198
+ * This example shows the API in its basic form.
199
+ *
200
+ * ```js
201
+ * pass.dispatchWorkgroupsIndirect(indirectBuffer, 0);
202
+ * ```
203
+ *
204
+ * - The indirect buffer is unwrapped before dispatch.
205
+ */
206
+ dispatchWorkgroupsIndirect(indirectBuffer, indirectOffset = 0) {
207
+ return raw.dispatchWorkgroupsIndirect(unwrap(indirectBuffer), indirectOffset);
208
+ },
209
+ /**
210
+ * Write a timestamp into a query set.
211
+ *
212
+ * This preserves the compute-facade API while forwarding timestamp writes
213
+ * only when the underlying runtime supports them.
214
+ *
215
+ * This example shows the API in its basic form.
216
+ *
217
+ * ```js
218
+ * pass.writeTimestamp(querySet, 0);
219
+ * ```
220
+ *
221
+ * - This throws when the underlying runtime does not expose timestamp query writes.
222
+ */
223
+ writeTimestamp(querySet, queryIndex) {
224
+ if (typeof raw.writeTimestamp !== 'function') {
225
+ throw new Error('timestamp query writes are unsupported on the compute surface');
226
+ }
227
+ return raw.writeTimestamp(unwrap(querySet), queryIndex);
228
+ },
229
+ /**
230
+ * Finish the compute pass.
231
+ *
232
+ * This closes the wrapped pass so the surrounding command encoder can be
233
+ * finalized or continue recording.
234
+ *
235
+ * This example shows the API in its basic form.
236
+ *
237
+ * ```js
238
+ * pass.end();
239
+ * ```
240
+ *
241
+ * - This closes the wrapped pass but does not submit work by itself.
242
+ */
243
+ end() {
244
+ return raw.end();
245
+ },
246
+ };
247
+ }
248
+
249
+ function wrap_command_encoder(raw) {
250
+ return {
251
+ _raw: raw,
252
+ /**
253
+ * Begin a compute pass on the compute facade.
254
+ *
255
+ * This creates a wrapped compute pass encoder that exposes only the
256
+ * compute-surface contract.
257
+ *
258
+ * This example shows the API in its basic form.
259
+ *
260
+ * ```js
261
+ * const pass = encoder.beginComputePass();
262
+ * ```
263
+ *
264
+ * - The returned pass is wrapped back into the compute facade.
265
+ */
266
+ beginComputePass(descriptor) {
267
+ return wrap_compute_pass(raw.beginComputePass(descriptor));
268
+ },
269
+ /**
270
+ * Record a buffer-to-buffer copy.
271
+ *
272
+ * This forwards the copy call after unwrapping the facade buffers to their
273
+ * underlying Doe handles.
274
+ *
275
+ * This example shows the API in its basic form.
276
+ *
277
+ * ```js
278
+ * encoder.copyBufferToBuffer(src, 0, dst, 0, src.size);
279
+ * ```
280
+ *
281
+ * - Source and destination buffers are unwrapped before forwarding.
282
+ */
283
+ copyBufferToBuffer(source, sourceOffset, target, targetOffset, size) {
284
+ return raw.copyBufferToBuffer(
285
+ unwrap(source),
286
+ sourceOffset,
287
+ unwrap(target),
288
+ targetOffset,
289
+ size,
290
+ );
291
+ },
292
+ /**
293
+ * Resolve a query set into a destination buffer.
294
+ *
295
+ * This keeps the query-resolution API available on the facade only when
296
+ * the underlying runtime exposes it.
297
+ *
298
+ * This example shows the API in its basic form.
299
+ *
300
+ * ```js
301
+ * encoder.resolveQuerySet(querySet, 0, 1, dst, 0);
302
+ * ```
303
+ *
304
+ * - This throws when query resolution is not supported by the underlying runtime.
305
+ */
306
+ resolveQuerySet(querySet, firstQuery, queryCount, destination, destinationOffset) {
307
+ if (typeof raw.resolveQuerySet !== 'function') {
308
+ throw new Error('query resolution is unsupported on the compute surface');
309
+ }
310
+ return raw.resolveQuerySet(
311
+ unwrap(querySet),
312
+ firstQuery,
313
+ queryCount,
314
+ unwrap(destination),
315
+ destinationOffset,
316
+ );
317
+ },
318
+ /**
319
+ * Finish command recording and return a wrapped command buffer.
320
+ *
321
+ * This seals the wrapped encoder so the resulting command buffer can be
322
+ * submitted through the compute-facade queue.
323
+ *
324
+ * This example shows the API in its basic form.
325
+ *
326
+ * ```js
327
+ * const commands = encoder.finish();
328
+ * queue.submit([commands]);
329
+ * ```
330
+ *
331
+ * - The command buffer is the same underlying object used by the full surface.
332
+ */
333
+ finish() {
334
+ return raw.finish();
335
+ },
336
+ };
337
+ }
338
+
339
+ function wrap_queue(raw) {
340
+ return {
341
+ _raw: raw,
342
+ /**
343
+ * Submit command buffers to the queue.
344
+ *
345
+ * This unwraps the supplied command buffers and forwards them to the
346
+ * underlying Doe queue.
347
+ *
348
+ * This example shows the API in its basic form.
349
+ *
350
+ * ```js
351
+ * queue.submit([encoder.finish()]);
352
+ * ```
353
+ *
354
+ * - Command buffers are unwrapped before forwarding.
355
+ */
356
+ submit(commandBuffers) {
357
+ return raw.submit(commandBuffers.map(unwrap));
358
+ },
359
+ /**
360
+ * Write host data into a GPU buffer.
361
+ *
362
+ * This forwards queue-side uploads after unwrapping the facade buffer.
363
+ *
364
+ * This example shows the API in its basic form.
365
+ *
366
+ * ```js
367
+ * queue.writeBuffer(buffer, 0, new Float32Array([1, 2, 3, 4]));
368
+ * ```
369
+ *
370
+ * - The wrapped buffer is unwrapped before forwarding to the underlying queue.
371
+ */
372
+ writeBuffer(buffer, bufferOffset, data, dataOffset, size) {
373
+ return raw.writeBuffer(unwrap(buffer), bufferOffset, data, dataOffset, size);
374
+ },
375
+ /**
376
+ * Resolve after submitted work has drained.
377
+ *
378
+ * This keeps the queue waiting API available on the facade while treating
379
+ * missing runtime support as an immediate resolution.
380
+ *
381
+ * This example shows the API in its basic form.
382
+ *
383
+ * ```js
384
+ * await queue.onSubmittedWorkDone();
385
+ * ```
386
+ *
387
+ * - If the underlying queue does not expose this method, the facade resolves immediately.
388
+ */
389
+ async onSubmittedWorkDone() {
390
+ if (typeof raw.onSubmittedWorkDone === 'function') {
391
+ return raw.onSubmittedWorkDone();
392
+ }
393
+ },
394
+ };
395
+ }
396
+
397
+ function wrap_query_set(raw) {
398
+ return {
399
+ _raw: raw,
400
+ /**
401
+ * Release the wrapped query set.
402
+ *
403
+ * This forwards destruction to the underlying query set handle returned by
404
+ * the full-surface runtime.
405
+ *
406
+ * This example shows the API in its basic form.
407
+ *
408
+ * ```js
409
+ * querySet.destroy();
410
+ * ```
411
+ *
412
+ * - Reusing the query set after destruction is unsupported.
413
+ */
414
+ destroy() {
415
+ return raw.destroy();
416
+ },
417
+ };
418
+ }
419
+
420
+ function wrap_device(raw) {
421
+ return {
422
+ _raw: raw,
423
+ queue: wrap_queue(raw.queue),
424
+ limits: raw.limits,
425
+ features: raw.features,
426
+ /**
427
+ * Create a buffer on the compute-only device facade.
428
+ *
429
+ * This forwards buffer creation to Doe and wraps the result back into the
430
+ * narrower compute-only surface.
431
+ *
432
+ * This example shows the API in its basic form.
433
+ *
434
+ * ```js
435
+ * const buffer = device.createBuffer({
436
+ * size: 16,
437
+ * usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
438
+ * });
439
+ * ```
440
+ *
441
+ * - The returned buffer is wrapped back into the compute facade.
442
+ */
443
+ createBuffer(descriptor) {
444
+ return wrap_buffer(raw.createBuffer(descriptor));
445
+ },
446
+ /**
447
+ * Create a shader module from WGSL source.
448
+ *
449
+ * This preserves the same shader-module behavior as the full package while
450
+ * keeping the compute-only device shape.
451
+ *
452
+ * This example shows the API in its basic form.
453
+ *
454
+ * ```js
455
+ * const shader = device.createShaderModule({ code: WGSL });
456
+ * ```
457
+ *
458
+ * - This forwards directly to the underlying Doe device.
459
+ */
460
+ createShaderModule(descriptor) {
461
+ return raw.createShaderModule(descriptor);
462
+ },
463
+ /**
464
+ * Create a compute pipeline.
465
+ *
466
+ * This builds the underlying Doe pipeline and wraps it back into the
467
+ * compute facade for later dispatch use.
468
+ *
469
+ * This example shows the API in its basic form.
470
+ *
471
+ * ```js
472
+ * const pipeline = device.createComputePipeline({
473
+ * layout: "auto",
474
+ * compute: { module: shader, entryPoint: "main" },
475
+ * });
476
+ * ```
477
+ *
478
+ * - The returned pipeline is wrapped back into the compute facade.
479
+ */
480
+ createComputePipeline(descriptor) {
481
+ const compute = descriptor.compute ?? {};
482
+ return wrap_compute_pipeline(raw.createComputePipeline({
483
+ ...descriptor,
484
+ layout: descriptor.layout === 'auto' ? 'auto' : unwrap(descriptor.layout),
485
+ compute: {
486
+ ...compute,
487
+ module: unwrap(compute.module),
488
+ },
489
+ }));
490
+ },
491
+ /**
492
+ * Create a compute pipeline through an async-shaped API.
493
+ *
494
+ * This preserves the async WebGPU shape while returning the wrapped
495
+ * compute-only pipeline object.
496
+ *
497
+ * This example shows the API in its basic form.
498
+ *
499
+ * ```js
500
+ * const pipeline = await device.createComputePipelineAsync(descriptor);
501
+ * ```
502
+ *
503
+ * - The returned pipeline is wrapped back into the compute facade.
504
+ */
505
+ async createComputePipelineAsync(descriptor) {
506
+ return wrap_compute_pipeline(await raw.createComputePipelineAsync(descriptor));
507
+ },
508
+ /**
509
+ * Create a bind-group layout.
510
+ *
511
+ * This forwards layout creation to Doe and returns the wrapped layout used
512
+ * by the compute facade.
513
+ *
514
+ * This example shows the API in its basic form.
515
+ *
516
+ * ```js
517
+ * const layout = device.createBindGroupLayout({ entries });
518
+ * ```
519
+ *
520
+ * - The returned layout is wrapped for compute-surface use.
521
+ */
522
+ createBindGroupLayout(descriptor) {
523
+ return wrap_bind_group_layout(raw.createBindGroupLayout(descriptor));
524
+ },
525
+ /**
526
+ * Create a bind group.
527
+ *
528
+ * This unwraps facade resources and creates the bind group on the
529
+ * underlying Doe device.
530
+ *
531
+ * This example shows the API in its basic form.
532
+ *
533
+ * ```js
534
+ * const bindGroup = device.createBindGroup({ layout, entries });
535
+ * ```
536
+ *
537
+ * - Wrapped layouts and buffers are unwrapped before forwarding.
538
+ */
539
+ createBindGroup(descriptor) {
540
+ const entries = (descriptor.entries ?? []).map((entry) => ({
541
+ ...entry,
542
+ resource: entry.resource && typeof entry.resource === 'object' && 'buffer' in entry.resource
543
+ ? { ...entry.resource, buffer: unwrap(entry.resource.buffer) }
544
+ : entry.resource,
545
+ }));
546
+ return wrap_bind_group(raw.createBindGroup({
547
+ ...descriptor,
548
+ layout: unwrap(descriptor.layout),
549
+ entries,
550
+ }));
551
+ },
552
+ /**
553
+ * Create a pipeline layout.
554
+ *
555
+ * This combines wrapped bind-group layouts into a pipeline layout on the
556
+ * underlying Doe device.
557
+ *
558
+ * This example shows the API in its basic form.
559
+ *
560
+ * ```js
561
+ * const layout = device.createPipelineLayout({ bindGroupLayouts: [group0] });
562
+ * ```
563
+ *
564
+ * - Wrapped bind-group layouts are unwrapped before creation.
565
+ */
566
+ createPipelineLayout(descriptor) {
567
+ return wrap_pipeline_layout(raw.createPipelineLayout({
568
+ ...descriptor,
569
+ bindGroupLayouts: (descriptor.bindGroupLayouts ?? []).map(unwrap),
570
+ }));
571
+ },
572
+ /**
573
+ * Create a command encoder.
574
+ *
575
+ * This returns the compute-facade wrapper around Doe's command encoder so
576
+ * callers stay inside the narrower device contract.
577
+ *
578
+ * This example shows the API in its basic form.
579
+ *
580
+ * ```js
581
+ * const encoder = device.createCommandEncoder();
582
+ * ```
583
+ *
584
+ * - The returned encoder is wrapped back into the compute facade.
585
+ */
586
+ createCommandEncoder(descriptor) {
587
+ return wrap_command_encoder(raw.createCommandEncoder(descriptor));
588
+ },
589
+ /**
590
+ * Create a query set.
591
+ *
592
+ * This forwards query-set creation when the underlying runtime supports it
593
+ * and otherwise fails explicitly.
594
+ *
595
+ * This example shows the API in its basic form.
596
+ *
597
+ * ```js
598
+ * const querySet = device.createQuerySet({ type: "timestamp", count: 2 });
599
+ * ```
600
+ *
601
+ * - This throws when query sets are unsupported by the underlying runtime.
602
+ */
603
+ createQuerySet(descriptor) {
604
+ if (typeof raw.createQuerySet !== 'function') {
605
+ throw new Error('query sets are unsupported on the compute surface');
606
+ }
607
+ return wrap_query_set(raw.createQuerySet(descriptor));
608
+ },
609
+ /**
610
+ * Release the wrapped device.
611
+ *
612
+ * This tears down the underlying Doe device associated with the compute
613
+ * facade wrapper.
614
+ *
615
+ * This example shows the API in its basic form.
616
+ *
617
+ * ```js
618
+ * device.destroy();
619
+ * ```
620
+ *
621
+ * - Reusing the device after destruction is unsupported.
622
+ */
623
+ destroy() {
624
+ return raw.destroy();
625
+ },
626
+ };
627
+ }
628
+
629
+ function wrap_adapter(raw) {
630
+ return {
631
+ _raw: raw,
632
+ features: raw.features,
633
+ limits: raw.limits,
634
+ /**
635
+ * Request a compute-only device facade from this adapter.
636
+ *
637
+ * This asks the underlying adapter for a Doe device and then narrows it to
638
+ * the compute-only JS surface.
639
+ *
640
+ * This example shows the API in its basic form.
641
+ *
642
+ * ```js
643
+ * const device = await adapter.requestDevice();
644
+ * ```
645
+ *
646
+ * - The wrapped device intentionally omits render and surface APIs.
647
+ */
648
+ async requestDevice(descriptor) {
649
+ return wrap_device(await raw.requestDevice(descriptor));
650
+ },
651
+ /**
652
+ * Release the wrapped adapter.
653
+ *
654
+ * This tears down the underlying Doe adapter associated with this facade.
655
+ *
656
+ * This example shows the API in its basic form.
657
+ *
658
+ * ```js
659
+ * adapter.destroy();
660
+ * ```
661
+ *
662
+ * - Reusing the adapter after destruction is unsupported.
663
+ */
664
+ destroy() {
665
+ return raw.destroy();
666
+ },
667
+ };
668
+ }
669
+
670
+ function wrap_gpu(raw) {
671
+ return {
672
+ _raw: raw,
673
+ /**
674
+ * Request a compute-only adapter facade.
675
+ *
676
+ * This asks the underlying GPU object for an adapter and wraps it into the
677
+ * compute-surface contract.
678
+ *
679
+ * This example shows the API in its basic form.
680
+ *
681
+ * ```js
682
+ * const adapter = await gpu.requestAdapter();
683
+ * ```
684
+ *
685
+ * - The wrapped adapter later produces compute-only devices.
686
+ */
687
+ async requestAdapter(options) {
688
+ return wrap_adapter(await raw.requestAdapter(options));
689
+ },
690
+ };
691
+ }
692
+
693
+ /**
694
+ * Standard WebGPU enum objects for the compute package surface.
695
+ *
696
+ * This exposes the same package-local enum tables as the full surface so
697
+ * compute-only consumers can build usage flags without browser globals.
698
+ *
699
+ * This example shows the API in its basic form.
700
+ *
701
+ * ```js
702
+ * import { globals } from "@simulatte/webgpu/compute";
703
+ *
704
+ * const usage = globals.GPUBufferUsage.STORAGE | globals.GPUBufferUsage.COPY_DST;
705
+ * ```
706
+ *
707
+ * - The enum values are shared with the full package.
708
+ * - The difference between package surfaces is the device facade, not the constants.
709
+ */
710
+ export const globals = full.globals;
711
+
712
+ /**
713
+ * Create a compute-only `GPU` facade backed by the Doe runtime.
714
+ *
715
+ * This wraps the full package GPU object and narrows the exposed adapter and
716
+ * device methods to the compute-focused contract.
717
+ *
718
+ * This example shows the API in its basic form.
719
+ *
720
+ * ```js
721
+ * import { create } from "@simulatte/webgpu/compute";
722
+ *
723
+ * const gpu = create();
724
+ * const adapter = await gpu.requestAdapter();
725
+ * ```
726
+ *
727
+ * - The underlying runtime is still Doe; this is a JS facade restriction, not a separate backend.
728
+ * - The returned device intentionally omits render, sampler, and surface methods.
729
+ */
730
+ export function create(createArgs = null) {
731
+ return wrap_gpu(full.create(createArgs));
732
+ }
733
+
734
+ /**
735
+ * Install compute-surface globals and `navigator.gpu` onto a target object.
736
+ *
737
+ * This adds missing enum globals and installs a compute-only GPU facade at
738
+ * `target.navigator.gpu`.
739
+ *
740
+ * This example shows the API in its basic form.
741
+ *
742
+ * ```js
743
+ * import { setupGlobals } from "@simulatte/webgpu/compute";
744
+ *
745
+ * setupGlobals(globalThis);
746
+ * const device = await navigator.gpu.requestAdapter().then((a) => a.requestDevice());
747
+ * ```
748
+ *
749
+ * - Existing globals are preserved.
750
+ * - The installed `navigator.gpu` still yields the compute-only facade, so render APIs remain intentionally absent.
751
+ */
752
+ export function setupGlobals(target = globalThis, createArgs = null) {
753
+ for (const [name, value] of Object.entries(globals)) {
754
+ if (target[name] === undefined) {
755
+ Object.defineProperty(target, name, {
756
+ value,
757
+ writable: true,
758
+ configurable: true,
759
+ enumerable: false,
760
+ });
761
+ }
762
+ }
763
+ const gpu = create(createArgs);
764
+ if (typeof target.navigator === 'undefined') {
765
+ Object.defineProperty(target, 'navigator', {
766
+ value: { gpu },
767
+ writable: true,
768
+ configurable: true,
769
+ enumerable: false,
770
+ });
771
+ } else if (!target.navigator.gpu) {
772
+ Object.defineProperty(target.navigator, 'gpu', {
773
+ value: gpu,
774
+ writable: true,
775
+ configurable: true,
776
+ enumerable: false,
777
+ });
778
+ }
779
+ return gpu;
780
+ }
781
+
782
+ /**
783
+ * Request a compute-surface adapter.
784
+ *
785
+ * This is a convenience wrapper over `create(...).requestAdapter(...)` for the
786
+ * compute package surface.
787
+ *
788
+ * This example shows the API in its basic form.
789
+ *
790
+ * ```js
791
+ * import { requestAdapter } from "@simulatte/webgpu/compute";
792
+ *
793
+ * const adapter = await requestAdapter();
794
+ * ```
795
+ *
796
+ * - Returns `null` if no adapter is available.
797
+ * - The adapter later produces a compute-only device facade.
798
+ */
799
+ export async function requestAdapter(adapterOptions = undefined, createArgs = null) {
800
+ return create(createArgs).requestAdapter(adapterOptions);
801
+ }
802
+
803
+ /**
804
+ * Request a compute-only device facade from the Doe runtime.
805
+ *
806
+ * This requests an adapter, then wraps the resulting device so only the
807
+ * compute-side JS surface is exposed.
808
+ *
809
+ * This example shows the API in its basic form.
810
+ *
811
+ * ```js
812
+ * import { requestDevice } from "@simulatte/webgpu/compute";
813
+ *
814
+ * const device = await requestDevice();
815
+ * console.log(typeof device.createRenderPipeline); // "undefined"
816
+ * ```
817
+ *
818
+ * - The facade hides render, sampler, and surface methods even if the underlying runtime has them.
819
+ * - Buffer and queue operations remain available for upload, dispatch, copy, and readback workflows.
820
+ */
821
+ export async function requestDevice(options = {}) {
822
+ const adapter = await requestAdapter(options?.adapterOptions, options?.createArgs ?? null);
823
+ return adapter.requestDevice(options?.deviceDescriptor);
824
+ }
825
+
826
+ /**
827
+ * Shared Doe API / Doe routines namespace for the compute package surface.
828
+ *
829
+ * This exposes `await doe.requestDevice()` for the one-line Doe API entry,
830
+ * `doe.bind(device)` when you already have a device, `doe.buffers.*` and
831
+ * `doe.compute.run(...)` / `doe.compute.compile(...)` for the `Doe API`
832
+ * surface, and `doe.compute.once(...)` for `Doe routines`.
833
+ *
834
+ * The exported `doe` object here is the JS convenience surface over the Doe
835
+ * runtime, not a separate runtime.
836
+ *
837
+ * This example shows the API in its basic form.
838
+ *
839
+ * ```js
840
+ * import { doe } from "@simulatte/webgpu/compute";
841
+ *
842
+ * const gpu = await doe.requestDevice();
843
+ * const src = gpu.buffers.fromData(new Float32Array([1, 2, 3, 4]));
844
+ * const dst = gpu.buffers.like(src, { usage: "storageReadWrite" });
845
+ * ```
846
+ *
847
+ * - This Doe API and Doe routines shape is shared with the full package surface; the difference is the raw device returned underneath.
848
+ * - `gpu.compute.once(...)` is intentionally narrow and rejects raw numeric usage flags; drop to `gpu.buffers.*` if you need explicit raw control.
849
+ */
850
+ export const doe = createDoeNamespace({
851
+ requestDevice,
852
+ });
853
+
854
+ /**
855
+ * Report how the compute package surface resolved the Doe runtime.
856
+ *
857
+ * This re-exports the same provenance report as the full package.
858
+ *
859
+ * This example shows the API in its basic form.
860
+ *
861
+ * ```js
862
+ * import { providerInfo } from "@simulatte/webgpu/compute";
863
+ *
864
+ * console.log(providerInfo().loaded);
865
+ * ```
866
+ *
867
+ * - The report describes the shared package/runtime load path, not the compute facade wrapper itself.
868
+ */
869
+ export const providerInfo = full.providerInfo;
870
+ /**
871
+ * Create a Node/Bun runtime wrapper for Doe CLI execution from the compute package.
872
+ *
873
+ * This re-exports the same runtime CLI helper as the full package for
874
+ * benchmark and command-stream execution workflows.
875
+ *
876
+ * This example shows the API in its basic form.
877
+ *
878
+ * ```js
879
+ * import { createDoeRuntime } from "@simulatte/webgpu/compute";
880
+ *
881
+ * const runtime = createDoeRuntime();
882
+ * ```
883
+ *
884
+ * - This is package/runtime orchestration, not the in-process compute facade.
885
+ */
886
+ export const createDoeRuntime = full.createDoeRuntime;
887
+ /**
888
+ * Run the Dawn-vs-Doe compare harness from the compute package surface.
889
+ *
890
+ * This re-exports the compare wrapper used for artifact-backed benchmark runs.
891
+ *
892
+ * This example shows the API in its basic form.
893
+ *
894
+ * ```js
895
+ * import { runDawnVsDoeCompare } from "@simulatte/webgpu/compute";
896
+ *
897
+ * const result = runDawnVsDoeCompare({ configPath: "bench/config.json" });
898
+ * ```
899
+ *
900
+ * - Requires an explicit compare config path either in options or forwarded CLI args.
901
+ * - This is a tooling entrypoint, not the in-process `doe.compute.*` helper path.
902
+ */
903
+ export const runDawnVsDoeCompare = full.runDawnVsDoeCompare;
904
+
905
+ export default {
906
+ create,
907
+ globals,
908
+ setupGlobals,
909
+ requestAdapter,
910
+ requestDevice,
911
+ providerInfo,
912
+ createDoeRuntime,
913
+ runDawnVsDoeCompare,
914
+ doe,
915
+ };