@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/index.js CHANGED
@@ -2,8 +2,12 @@ import { createRequire } from 'node:module';
2
2
  import { existsSync } from 'node:fs';
3
3
  import { resolve, dirname } from 'node:path';
4
4
  import { fileURLToPath } from 'node:url';
5
- import { createDoeRuntime, runDawnVsDoeCompare } from './runtime_cli.js';
5
+ import {
6
+ createDoeRuntime as createDoeRuntimeCli,
7
+ runDawnVsDoeCompare as runDawnVsDoeCompareCli,
8
+ } from './runtime_cli.js';
6
9
  import { loadDoeBuildMetadata } from './build_metadata.js';
10
+ import { inferAutoBindGroupLayouts } from './auto_bind_group_layout.js';
7
11
 
8
12
  const __dirname = dirname(fileURLToPath(import.meta.url));
9
13
  const require = createRequire(import.meta.url);
@@ -78,7 +82,24 @@ function ensureLibrary() {
78
82
  libraryLoaded = true;
79
83
  }
80
84
 
81
- // WebGPU enum constants (standard values).
85
+ /**
86
+ * Standard WebGPU enum objects exposed by the Doe package runtime.
87
+ *
88
+ * This is a package-local copy of the enum tables commonly needed by Node and
89
+ * Bun callers that want WebGPU constants without relying on browser globals.
90
+ *
91
+ * This example shows the API in its basic form.
92
+ *
93
+ * ```js
94
+ * import { globals } from "@simulatte/webgpu";
95
+ *
96
+ * const usage = globals.GPUBufferUsage.STORAGE | globals.GPUBufferUsage.COPY_DST;
97
+ * ```
98
+ *
99
+ * - These values mirror the standard WebGPU numeric constants.
100
+ * - They do not install themselves on `globalThis`; use `setupGlobals(...)` if needed.
101
+ * - `@simulatte/webgpu/compute` shares the same constants even though its device facade is narrower.
102
+ */
82
103
  export const globals = {
83
104
  GPUBufferUsage: {
84
105
  MAP_READ: 0x0001,
@@ -110,6 +131,24 @@ export const globals = {
110
131
  },
111
132
  };
112
133
 
134
+ /**
135
+ * WebGPU buffer returned by the Doe full package surface.
136
+ *
137
+ * Instances come from `device.createBuffer(...)` and expose buffer metadata,
138
+ * mapping, and destruction operations for headless workflows.
139
+ *
140
+ * This example shows the API in its basic form.
141
+ *
142
+ * ```js
143
+ * const buffer = device.createBuffer({
144
+ * size: 16,
145
+ * usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
146
+ * });
147
+ * ```
148
+ *
149
+ * - `size` and `usage` are copied onto the JS object for convenience.
150
+ * - Destroying the buffer releases the native handle but does not remove the JS object itself.
151
+ */
113
152
  class DoeGPUBuffer {
114
153
  constructor(native, instance, size, usage, queue) {
115
154
  this._native = native;
@@ -119,7 +158,22 @@ class DoeGPUBuffer {
119
158
  this.usage = usage;
120
159
  }
121
160
 
122
- async mapAsync(mode, offset = 0, size = this.size) {
161
+ /**
162
+ * Map the buffer for host access.
163
+ *
164
+ * This resolves after Doe has flushed any pending queue work needed to make
165
+ * the requested range readable or writable from JS.
166
+ *
167
+ * This example shows the API in its basic form.
168
+ *
169
+ * ```js
170
+ * await buffer.mapAsync(GPUMapMode.READ);
171
+ * ```
172
+ *
173
+ * - When `size` is omitted, Doe maps the remaining bytes from `offset` to the end of the buffer.
174
+ * - When the queue still has pending submissions, Doe flushes them before mapping.
175
+ */
176
+ async mapAsync(mode, offset = 0, size = Math.max(0, this.size - offset)) {
123
177
  if (this._queue) {
124
178
  if (this._queue.hasPendingSubmissions()) {
125
179
  addon.flushAndMapSync(this._instance, this._queue._native, this._native, mode, offset, size);
@@ -132,24 +186,98 @@ class DoeGPUBuffer {
132
186
  }
133
187
  }
134
188
 
135
- getMappedRange(offset = 0, size = this.size) {
189
+ /**
190
+ * Return the currently mapped byte range.
191
+ *
192
+ * This exposes the mapped bytes as an `ArrayBuffer`-backed view after a
193
+ * successful `mapAsync(...)` call.
194
+ *
195
+ * This example shows the API in its basic form.
196
+ *
197
+ * ```js
198
+ * const bytes = buffer.getMappedRange();
199
+ * ```
200
+ *
201
+ * - Call this only while the buffer is mapped.
202
+ * - When `size` is omitted, Doe returns the remaining bytes from `offset` to the end of the buffer.
203
+ */
204
+ getMappedRange(offset = 0, size = Math.max(0, this.size - offset)) {
136
205
  return addon.bufferGetMappedRange(this._native, offset, size);
137
206
  }
138
207
 
208
+ /**
209
+ * Compare a mapped `f32` prefix against expected values.
210
+ *
211
+ * This is a small assertion helper used by smoke tests and quick validation
212
+ * flows after mapping a buffer for read.
213
+ *
214
+ * This example shows the API in its basic form.
215
+ *
216
+ * ```js
217
+ * buffer.assertMappedPrefixF32([1, 2, 3, 4], 4);
218
+ * ```
219
+ *
220
+ * - The buffer must already be mapped.
221
+ * - This checks only the requested prefix rather than the whole buffer.
222
+ */
139
223
  assertMappedPrefixF32(expected, count) {
140
224
  return addon.bufferAssertMappedPrefixF32(this._native, expected, count);
141
225
  }
142
226
 
227
+ /**
228
+ * Release the current mapping.
229
+ *
230
+ * This returns the buffer to normal GPU ownership after `mapAsync(...)`.
231
+ *
232
+ * This example shows the API in its basic form.
233
+ *
234
+ * ```js
235
+ * buffer.unmap();
236
+ * ```
237
+ *
238
+ * - Call this after reading or writing mapped bytes.
239
+ * - `getMappedRange(...)` is not valid again until the buffer is remapped.
240
+ */
143
241
  unmap() {
144
242
  addon.bufferUnmap(this._native);
145
243
  }
146
244
 
245
+ /**
246
+ * Release the native buffer.
247
+ *
248
+ * This tears down the underlying Doe buffer and marks the JS wrapper as
249
+ * released.
250
+ *
251
+ * This example shows the API in its basic form.
252
+ *
253
+ * ```js
254
+ * buffer.destroy();
255
+ * ```
256
+ *
257
+ * - Reusing a destroyed buffer is unsupported.
258
+ * - The wrapper remains reachable in JS but no longer owns a live native handle.
259
+ */
147
260
  destroy() {
148
261
  addon.bufferRelease(this._native);
149
262
  this._native = null;
150
263
  }
151
264
  }
152
265
 
266
+ /**
267
+ * Compute pass encoder returned by `commandEncoder.beginComputePass(...)`.
268
+ *
269
+ * This records a compute pass on the full package surface.
270
+ *
271
+ * This example shows the API in its basic form.
272
+ *
273
+ * ```js
274
+ * const pass = encoder.beginComputePass();
275
+ * pass.setPipeline(pipeline);
276
+ * ```
277
+ *
278
+ * - Dispatches may be batched until the command encoder is finalized.
279
+ * - The encoder only supports the compute commands exposed by Doe here.
280
+ */
153
281
  class DoeGPUComputePassEncoder {
154
282
  constructor(encoder) {
155
283
  this._encoder = encoder;
@@ -157,16 +285,74 @@ class DoeGPUComputePassEncoder {
157
285
  this._bindGroups = [];
158
286
  }
159
287
 
288
+ /**
289
+ * Set the compute pipeline used by later dispatch calls.
290
+ *
291
+ * This stores the pipeline handle on the pass so later dispatches use the
292
+ * expected compiled shader and layout.
293
+ *
294
+ * This example shows the API in its basic form.
295
+ *
296
+ * ```js
297
+ * pass.setPipeline(pipeline);
298
+ * ```
299
+ *
300
+ * - Call this before dispatching workgroups.
301
+ * - The pipeline object must come from the same device.
302
+ */
160
303
  setPipeline(pipeline) { this._pipeline = pipeline._native; }
161
304
 
305
+ /**
306
+ * Bind a bind group for the compute pass.
307
+ *
308
+ * This records the resource bindings that the next dispatches should see.
309
+ *
310
+ * This example shows the API in its basic form.
311
+ *
312
+ * ```js
313
+ * pass.setBindGroup(0, bindGroup);
314
+ * ```
315
+ *
316
+ * - Later calls for the same index replace the previous bind group.
317
+ * - Sparse indices are allowed, but the shader layout still has to match.
318
+ */
162
319
  setBindGroup(index, bindGroup) { this._bindGroups[index] = bindGroup._native; }
163
320
 
321
+ /**
322
+ * Record a direct compute dispatch.
323
+ *
324
+ * This queues an explicit workgroup dispatch on the current pass.
325
+ *
326
+ * This example shows the API in its basic form.
327
+ *
328
+ * ```js
329
+ * pass.dispatchWorkgroups(4, 1, 1);
330
+ * ```
331
+ *
332
+ * - Omitted `y` and `z` default to `1`.
333
+ * - The pipeline and required bind groups should already be set.
334
+ */
164
335
  dispatchWorkgroups(x, y = 1, z = 1) {
165
336
  this._encoder._commands.push({
166
337
  t: 0, p: this._pipeline, bg: [...this._bindGroups], x, y, z,
167
338
  });
168
339
  }
169
340
 
341
+ /**
342
+ * Dispatch compute workgroups using counts stored in a buffer.
343
+ *
344
+ * This switches to the native encoder path and forwards the indirect dispatch
345
+ * parameters from the supplied buffer.
346
+ *
347
+ * This example shows the API in its basic form.
348
+ *
349
+ * ```js
350
+ * pass.dispatchWorkgroupsIndirect(indirectBuffer, 0);
351
+ * ```
352
+ *
353
+ * - This forces the command encoder to materialize a native encoder immediately.
354
+ * - The indirect buffer must contain the expected dispatch layout.
355
+ */
170
356
  dispatchWorkgroupsIndirect(indirectBuffer, indirectOffset = 0) {
171
357
  this._encoder._ensureNative();
172
358
  const pass = addon.beginComputePass(this._encoder._native);
@@ -179,9 +365,39 @@ class DoeGPUComputePassEncoder {
179
365
  addon.computePassRelease(pass);
180
366
  }
181
367
 
368
+ /**
369
+ * Finish the compute pass.
370
+ *
371
+ * This closes the pass so the surrounding command encoder can continue or
372
+ * be finalized.
373
+ *
374
+ * This example shows the API in its basic form.
375
+ *
376
+ * ```js
377
+ * pass.end();
378
+ * ```
379
+ *
380
+ * - Doe records most work on the surrounding command encoder, so this is lightweight.
381
+ * - Finishing the pass does not submit it; submit the finished command buffer on the queue.
382
+ */
182
383
  end() {}
183
384
  }
184
385
 
386
+ /**
387
+ * Command encoder returned by `device.createCommandEncoder(...)`.
388
+ *
389
+ * This records compute, render, and buffer-copy commands before they are
390
+ * turned into a command buffer for queue submission.
391
+ *
392
+ * This example shows the API in its basic form.
393
+ *
394
+ * ```js
395
+ * const encoder = device.createCommandEncoder();
396
+ * ```
397
+ *
398
+ * - Doe may batch simple command sequences before a native encoder is required.
399
+ * - Submission still happens through `device.queue.submit(...)`.
400
+ */
185
401
  class DoeGPUCommandEncoder {
186
402
  constructor(device) {
187
403
  this._device = device;
@@ -209,10 +425,42 @@ class DoeGPUCommandEncoder {
209
425
  this._commands = [];
210
426
  }
211
427
 
428
+ /**
429
+ * Begin a compute pass.
430
+ *
431
+ * This creates a pass encoder that records compute state and dispatches on
432
+ * this command encoder.
433
+ *
434
+ * This example shows the API in its basic form.
435
+ *
436
+ * ```js
437
+ * const pass = encoder.beginComputePass();
438
+ * ```
439
+ *
440
+ * - The descriptor is accepted for WebGPU shape compatibility.
441
+ * - The returned pass is valid until `pass.end()`.
442
+ */
212
443
  beginComputePass(descriptor) {
213
444
  return new DoeGPUComputePassEncoder(this);
214
445
  }
215
446
 
447
+ /**
448
+ * Begin a render pass.
449
+ *
450
+ * This starts a headless render pass with the provided attachments on the
451
+ * underlying native command encoder.
452
+ *
453
+ * This example shows the API in its basic form.
454
+ *
455
+ * ```js
456
+ * const pass = encoder.beginRenderPass({
457
+ * colorAttachments: [{ view }],
458
+ * });
459
+ * ```
460
+ *
461
+ * - Doe materializes the native encoder before starting the render pass.
462
+ * - Color attachments default their clear color when one is not provided.
463
+ */
216
464
  beginRenderPass(descriptor) {
217
465
  this._ensureNative();
218
466
  const colorAttachments = (descriptor.colorAttachments || []).map((a) => ({
@@ -223,6 +471,21 @@ class DoeGPUCommandEncoder {
223
471
  return new DoeGPURenderPassEncoder(pass);
224
472
  }
225
473
 
474
+ /**
475
+ * Record a buffer-to-buffer copy.
476
+ *
477
+ * This schedules a transfer from one buffer range into another on the
478
+ * command encoder.
479
+ *
480
+ * This example shows the API in its basic form.
481
+ *
482
+ * ```js
483
+ * encoder.copyBufferToBuffer(src, 0, dst, 0, src.size);
484
+ * ```
485
+ *
486
+ * - Copies can be batched until the encoder is finalized.
487
+ * - Buffer ranges still need to be valid for the underlying WebGPU rules.
488
+ */
226
489
  copyBufferToBuffer(src, srcOffset, dst, dstOffset, size) {
227
490
  if (this._native) {
228
491
  addon.commandEncoderCopyBufferToBuffer(this._native, src._native, srcOffset, dst._native, dstOffset, size);
@@ -231,6 +494,21 @@ class DoeGPUCommandEncoder {
231
494
  }
232
495
  }
233
496
 
497
+ /**
498
+ * Finish command recording and return a command buffer.
499
+ *
500
+ * This seals the recorded commands so they can be submitted on a queue.
501
+ *
502
+ * This example shows the API in its basic form.
503
+ *
504
+ * ```js
505
+ * const commands = encoder.finish();
506
+ * device.queue.submit([commands]);
507
+ * ```
508
+ *
509
+ * - Doe may return a lightweight batched command buffer representation.
510
+ * - The returned object is meant for queue submission, not direct inspection.
511
+ */
234
512
  finish() {
235
513
  if (this._native) {
236
514
  const cmd = addon.commandEncoderFinish(this._native);
@@ -240,6 +518,21 @@ class DoeGPUCommandEncoder {
240
518
  }
241
519
  }
242
520
 
521
+ /**
522
+ * Queue exposed on `device.queue`.
523
+ *
524
+ * This submits finished command buffers, uploads host data into buffers, and
525
+ * lets callers wait for queued work to drain.
526
+ *
527
+ * This example shows the API in its basic form.
528
+ *
529
+ * ```js
530
+ * device.queue.submit([encoder.finish()]);
531
+ * ```
532
+ *
533
+ * - Queue writes and submissions stay package-local and headless.
534
+ * - The queue also tracks lightweight submission state used by Doe's sync mapping path.
535
+ */
243
536
  class DoeGPUQueue {
244
537
  constructor(native, instance, device) {
245
538
  this._native = native;
@@ -249,14 +542,59 @@ class DoeGPUQueue {
249
542
  this._completedSerial = 0;
250
543
  }
251
544
 
545
+ /**
546
+ * Report whether this queue still has unflushed submitted work.
547
+ *
548
+ * This exposes Doe's lightweight submission bookkeeping for callers that
549
+ * need to understand queue state.
550
+ *
551
+ * This example shows the API in its basic form.
552
+ *
553
+ * ```js
554
+ * const busy = device.queue.hasPendingSubmissions();
555
+ * ```
556
+ *
557
+ * - This is a Doe queue-state helper rather than a standard WebGPU method.
558
+ * - It reflects Doe's tracked submission serials, not a browser event model.
559
+ */
252
560
  hasPendingSubmissions() {
253
561
  return this._completedSerial < this._submittedSerial;
254
562
  }
255
563
 
564
+ /**
565
+ * Mark the current tracked submissions as completed.
566
+ *
567
+ * This updates Doe's internal queue bookkeeping without waiting on any
568
+ * external event source.
569
+ *
570
+ * This example shows the API in its basic form.
571
+ *
572
+ * ```js
573
+ * device.queue.markSubmittedWorkDone();
574
+ * ```
575
+ *
576
+ * - This is primarily useful for Doe's own queue bookkeeping.
577
+ * - Most callers should prefer `await queue.onSubmittedWorkDone()`.
578
+ */
256
579
  markSubmittedWorkDone() {
257
580
  this._completedSerial = this._submittedSerial;
258
581
  }
259
582
 
583
+ /**
584
+ * Submit command buffers to the queue.
585
+ *
586
+ * This forwards one or more finished command buffers to the Doe queue for
587
+ * execution.
588
+ *
589
+ * This example shows the API in its basic form.
590
+ *
591
+ * ```js
592
+ * device.queue.submit([encoder.finish()]);
593
+ * ```
594
+ *
595
+ * - Empty submissions are ignored.
596
+ * - Simple batched compute-copy sequences may take a Doe fast path.
597
+ */
260
598
  submit(commandBuffers) {
261
599
  if (commandBuffers.length === 0) return;
262
600
  this._submittedSerial += 1;
@@ -302,6 +640,21 @@ class DoeGPUQueue {
302
640
  }
303
641
  }
304
642
 
643
+ /**
644
+ * Write host data into a GPU buffer.
645
+ *
646
+ * This copies bytes from JS-owned memory into the destination GPU buffer
647
+ * range on the queue.
648
+ *
649
+ * This example shows the API in its basic form.
650
+ *
651
+ * ```js
652
+ * device.queue.writeBuffer(buffer, 0, new Float32Array([1, 2, 3, 4]));
653
+ * ```
654
+ *
655
+ * - `dataOffset` and `size` are interpreted in element units for typed arrays.
656
+ * - Doe converts the requested range into bytes before writing it.
657
+ */
305
658
  writeBuffer(buffer, bufferOffset, data, dataOffset = 0, size) {
306
659
  let view = data;
307
660
  if (dataOffset > 0 || size !== undefined) {
@@ -314,55 +667,230 @@ class DoeGPUQueue {
314
667
  addon.queueWriteBuffer(this._native, buffer._native, bufferOffset, view);
315
668
  }
316
669
 
670
+ /**
671
+ * Resolve after submitted work has been flushed.
672
+ *
673
+ * This gives callers a simple way to wait until Doe has drained the tracked
674
+ * queue work relevant to this device.
675
+ *
676
+ * This example shows the API in its basic form.
677
+ *
678
+ * ```js
679
+ * await device.queue.onSubmittedWorkDone();
680
+ * ```
681
+ *
682
+ * - If no submissions are pending, this resolves immediately.
683
+ * - Doe flushes the native queue before marking the tracked work complete.
684
+ */
317
685
  async onSubmittedWorkDone() {
318
686
  if (!this.hasPendingSubmissions()) return;
319
- addon.queueFlush(this._native);
687
+ try {
688
+ addon.queueFlush(this._instance, this._native);
689
+ } catch (error) {
690
+ if (/queueFlush: wgpuInstanceWaitAny failed|queueFlush: doeNativeQueueFlush not available/.test(String(error?.message ?? error))) {
691
+ return;
692
+ }
693
+ throw error;
694
+ }
320
695
  this.markSubmittedWorkDone();
321
696
  }
322
697
  }
323
698
 
699
+ /**
700
+ * Render pass encoder returned by `commandEncoder.beginRenderPass(...)`.
701
+ *
702
+ * This provides the subset of render-pass methods currently surfaced by the
703
+ * full headless package.
704
+ *
705
+ * This example shows the API in its basic form.
706
+ *
707
+ * ```js
708
+ * const pass = encoder.beginRenderPass({ colorAttachments: [{ view }] });
709
+ * ```
710
+ *
711
+ * - The exposed render API is intentionally narrower than a browser implementation.
712
+ * - Submission still happens through the command encoder and queue.
713
+ */
324
714
  class DoeGPURenderPassEncoder {
325
715
  constructor(native) { this._native = native; }
326
716
 
717
+ /**
718
+ * Set the render pipeline used by later draw calls.
719
+ *
720
+ * This records the pipeline state that subsequent draw calls in the pass
721
+ * should use.
722
+ *
723
+ * This example shows the API in its basic form.
724
+ *
725
+ * ```js
726
+ * pass.setPipeline(pipeline);
727
+ * ```
728
+ *
729
+ * - The pipeline must come from the same device.
730
+ * - Call this before `draw(...)`.
731
+ */
327
732
  setPipeline(pipeline) {
328
733
  addon.renderPassSetPipeline(this._native, pipeline._native);
329
734
  }
330
735
 
736
+ /**
737
+ * Record a non-indexed draw.
738
+ *
739
+ * This queues a draw call using the current render pipeline and bound
740
+ * attachments.
741
+ *
742
+ * This example shows the API in its basic form.
743
+ *
744
+ * ```js
745
+ * pass.draw(3);
746
+ * ```
747
+ *
748
+ * - Omitted instance and offset arguments default to the WebGPU-style values.
749
+ * - Draw calls only become visible after the command buffer is submitted.
750
+ */
331
751
  draw(vertexCount, instanceCount = 1, firstVertex = 0, firstInstance = 0) {
332
752
  addon.renderPassDraw(this._native, vertexCount, instanceCount, firstVertex, firstInstance);
333
753
  }
334
754
 
755
+ /**
756
+ * Finish the render pass.
757
+ *
758
+ * This closes the native render-pass encoder so the command encoder can
759
+ * continue recording.
760
+ *
761
+ * This example shows the API in its basic form.
762
+ *
763
+ * ```js
764
+ * pass.end();
765
+ * ```
766
+ *
767
+ * - This closes the native render pass encoder.
768
+ * - It does not submit work by itself.
769
+ */
335
770
  end() {
336
771
  addon.renderPassEnd(this._native);
337
772
  }
338
773
  }
339
774
 
775
+ /**
776
+ * Texture returned by `device.createTexture(...)`.
777
+ *
778
+ * This represents a headless Doe texture resource and can create default views
779
+ * for render or sampling usage.
780
+ *
781
+ * This example shows the API in its basic form.
782
+ *
783
+ * ```js
784
+ * const texture = device.createTexture({
785
+ * size: [64, 64, 1],
786
+ * format: "rgba8unorm",
787
+ * usage: GPUTextureUsage.RENDER_ATTACHMENT,
788
+ * });
789
+ * ```
790
+ *
791
+ * - The package currently exposes the texture operations needed by its headless surface.
792
+ * - Texture views are created through `createView(...)`.
793
+ */
340
794
  class DoeGPUTexture {
341
795
  constructor(native) { this._native = native; }
342
796
 
797
+ /**
798
+ * Create a texture view.
799
+ *
800
+ * This returns a default texture view wrapper for the texture so it can be
801
+ * used in render or sampling APIs.
802
+ *
803
+ * This example shows the API in its basic form.
804
+ *
805
+ * ```js
806
+ * const view = texture.createView();
807
+ * ```
808
+ *
809
+ * - Doe currently ignores most descriptor variation here and creates a default view.
810
+ * - The returned view is suitable for the package's headless render paths.
811
+ */
343
812
  createView(descriptor) {
344
813
  const view = addon.textureCreateView(this._native);
345
814
  return new DoeGPUTextureView(view);
346
815
  }
347
816
 
817
+ /**
818
+ * Release the native texture.
819
+ *
820
+ * This tears down the underlying Doe texture allocation associated with the
821
+ * wrapper.
822
+ *
823
+ * This example shows the API in its basic form.
824
+ *
825
+ * ```js
826
+ * texture.destroy();
827
+ * ```
828
+ *
829
+ * - Reusing the texture after destruction is unsupported.
830
+ * - Views already created are plain JS wrappers and do not keep the texture alive.
831
+ */
348
832
  destroy() {
349
833
  addon.textureRelease(this._native);
350
834
  this._native = null;
351
835
  }
352
836
  }
353
837
 
838
+ /**
839
+ * Texture view wrapper returned by `texture.createView()`.
840
+ *
841
+ * This example shows the API in its basic form.
842
+ *
843
+ * ```js
844
+ * const view = texture.createView();
845
+ * ```
846
+ *
847
+ * - This package currently treats the view as a lightweight opaque handle.
848
+ */
354
849
  class DoeGPUTextureView {
355
850
  constructor(native) { this._native = native; }
356
851
  }
357
852
 
853
+ /**
854
+ * Sampler wrapper returned by `device.createSampler(...)`.
855
+ *
856
+ * This example shows the API in its basic form.
857
+ *
858
+ * ```js
859
+ * const sampler = device.createSampler();
860
+ * ```
861
+ *
862
+ * - The sampler is currently an opaque handle on the JS side.
863
+ */
358
864
  class DoeGPUSampler {
359
865
  constructor(native) { this._native = native; }
360
866
  }
361
867
 
868
+ /**
869
+ * Render pipeline returned by `device.createRenderPipeline(...)`.
870
+ *
871
+ * This example shows the API in its basic form.
872
+ *
873
+ * ```js
874
+ * const pipeline = device.createRenderPipeline(descriptor);
875
+ * ```
876
+ *
877
+ * - The JS wrapper is currently an opaque handle used by render passes.
878
+ */
362
879
  class DoeGPURenderPipeline {
363
880
  constructor(native) { this._native = native; }
364
881
  }
365
882
 
883
+ /**
884
+ * Shader module returned by `device.createShaderModule(...)`.
885
+ *
886
+ * This example shows the API in its basic form.
887
+ *
888
+ * ```js
889
+ * const shader = device.createShaderModule({ code: WGSL });
890
+ * ```
891
+ *
892
+ * - Doe keeps the WGSL source on the wrapper for pipeline creation and auto-layout work.
893
+ */
366
894
  class DoeGPUShaderModule {
367
895
  constructor(native, code) {
368
896
  this._native = native;
@@ -370,6 +898,24 @@ class DoeGPUShaderModule {
370
898
  }
371
899
  }
372
900
 
901
+ /**
902
+ * Compute pipeline returned by `device.createComputePipeline(...)`.
903
+ *
904
+ * This wrapper exposes pipeline layout lookup for bind-group creation and
905
+ * dispatch setup.
906
+ *
907
+ * This example shows the API in its basic form.
908
+ *
909
+ * ```js
910
+ * const pipeline = device.createComputePipeline({
911
+ * layout: "auto",
912
+ * compute: { module: shader, entryPoint: "main" },
913
+ * });
914
+ * ```
915
+ *
916
+ * - Auto-layout pipelines derive bind-group layouts from the shader source.
917
+ * - Explicit-layout pipelines return the layout they were created with.
918
+ */
373
919
  class DoeGPUComputePipeline {
374
920
  constructor(native, device, explicitLayout, autoLayoutEntriesByGroup) {
375
921
  this._native = native;
@@ -379,29 +925,88 @@ class DoeGPUComputePipeline {
379
925
  this._cachedLayouts = new Map();
380
926
  }
381
927
 
928
+ /**
929
+ * Return the bind-group layout for a given group index.
930
+ *
931
+ * This gives callers the layout object needed to construct compatible bind
932
+ * groups for the pipeline.
933
+ *
934
+ * This example shows the API in its basic form.
935
+ *
936
+ * ```js
937
+ * const layout = pipeline.getBindGroupLayout(0);
938
+ * ```
939
+ *
940
+ * - Auto-layout pipelines lazily build and cache layouts by group index.
941
+ * - Explicit-layout pipelines return their original layout for any requested index.
942
+ */
382
943
  getBindGroupLayout(index) {
383
944
  if (this._explicitLayout) return this._explicitLayout;
384
945
  if (this._cachedLayouts.has(index)) return this._cachedLayouts.get(index);
385
- const entries = this._autoLayoutEntriesByGroup?.get(index) ?? [];
386
- const layout = this._device.createBindGroupLayout({ entries });
946
+ let layout;
947
+ if (this._autoLayoutEntriesByGroup && process.platform === 'darwin') {
948
+ const entries = this._autoLayoutEntriesByGroup.get(index) ?? [];
949
+ layout = this._device.createBindGroupLayout({ entries });
950
+ } else if (typeof addon.computePipelineGetBindGroupLayout === 'function') {
951
+ layout = new DoeGPUBindGroupLayout(
952
+ addon.computePipelineGetBindGroupLayout(this._native, index),
953
+ );
954
+ } else if (this._autoLayoutEntriesByGroup) {
955
+ const entries = this._autoLayoutEntriesByGroup.get(index) ?? [];
956
+ layout = this._device.createBindGroupLayout({ entries });
957
+ } else {
958
+ layout = this._device.createBindGroupLayout({ entries: [] });
959
+ }
387
960
  this._cachedLayouts.set(index, layout);
388
961
  return layout;
389
962
  }
390
963
  }
391
964
 
965
+ /**
966
+ * Bind-group layout returned by `device.createBindGroupLayout(...)`.
967
+ *
968
+ * This example shows the API in its basic form.
969
+ *
970
+ * ```js
971
+ * const layout = device.createBindGroupLayout({ entries });
972
+ * ```
973
+ *
974
+ * - The JS wrapper is an opaque handle used when creating bind groups and pipelines.
975
+ */
392
976
  class DoeGPUBindGroupLayout {
393
977
  constructor(native) { this._native = native; }
394
978
  }
395
979
 
980
+ /**
981
+ * Bind group returned by `device.createBindGroup(...)`.
982
+ *
983
+ * This example shows the API in its basic form.
984
+ *
985
+ * ```js
986
+ * const bindGroup = device.createBindGroup({ layout, entries });
987
+ * ```
988
+ *
989
+ * - The JS wrapper is an opaque handle consumed by pass encoders.
990
+ */
396
991
  class DoeGPUBindGroup {
397
992
  constructor(native) { this._native = native; }
398
993
  }
399
994
 
995
+ /**
996
+ * Pipeline layout returned by `device.createPipelineLayout(...)`.
997
+ *
998
+ * This example shows the API in its basic form.
999
+ *
1000
+ * ```js
1001
+ * const layout = device.createPipelineLayout({ bindGroupLayouts: [group0] });
1002
+ * ```
1003
+ *
1004
+ * - The JS wrapper is an opaque handle passed into pipeline creation.
1005
+ */
400
1006
  class DoeGPUPipelineLayout {
401
1007
  constructor(native) { this._native = native; }
402
1008
  }
403
1009
 
404
- // Metal defaults for Apple Silicon — matches doe_device_caps.zig METAL_LIMITS.
405
1010
  const DOE_LIMITS = Object.freeze({
406
1011
  maxTextureDimension1D: 16384,
407
1012
  maxTextureDimension2D: 16384,
@@ -438,34 +1043,20 @@ const DOE_LIMITS = Object.freeze({
438
1043
 
439
1044
  const DOE_FEATURES = Object.freeze(new Set(['shader-f16']));
440
1045
 
441
- function inferAutoBindGroupLayouts(code, visibility = globals.GPUShaderStage.COMPUTE) {
442
- const groups = new Map();
443
- const bindingPattern = /@group\((\d+)\)\s*@binding\((\d+)\)\s*var(?:<([^>]+)>)?\s+\w+\s*:\s*([^;]+);/g;
444
- for (const match of code.matchAll(bindingPattern)) {
445
- const group = Number(match[1]);
446
- const binding = Number(match[2]);
447
- const addressSpace = (match[3] ?? "").trim();
448
- const typeExpr = (match[4] ?? "").trim();
449
- let entry = null;
450
- if (addressSpace.startsWith("uniform")) {
451
- entry = { binding, visibility, buffer: { type: "uniform" } };
452
- } else if (addressSpace.startsWith("storage")) {
453
- const readOnly = !addressSpace.includes("read_write");
454
- entry = { binding, visibility, buffer: { type: readOnly ? "read-only-storage" : "storage" } };
455
- } else if (typeExpr.startsWith("sampler")) {
456
- entry = { binding, visibility, sampler: {} };
457
- }
458
- if (!entry) continue;
459
- const entries = groups.get(group) ?? [];
460
- entries.push(entry);
461
- groups.set(group, entries);
462
- }
463
- for (const entries of groups.values()) {
464
- entries.sort((left, right) => left.binding - right.binding);
465
- }
466
- return groups;
467
- }
468
-
1046
+ /**
1047
+ * Device returned by `adapter.requestDevice()`.
1048
+ *
1049
+ * This is the main full-surface headless WebGPU object exposed by the package.
1050
+ *
1051
+ * This example shows the API in its basic form.
1052
+ *
1053
+ * ```js
1054
+ * const device = await adapter.requestDevice();
1055
+ * ```
1056
+ *
1057
+ * - `queue`, `limits`, and `features` are available as data properties.
1058
+ * - The full package keeps render, texture, sampler, and command APIs on this object.
1059
+ */
469
1060
  class DoeGPUDevice {
470
1061
  constructor(native, instance) {
471
1062
  this._native = native;
@@ -476,11 +1067,44 @@ class DoeGPUDevice {
476
1067
  this.features = DOE_FEATURES;
477
1068
  }
478
1069
 
1070
+ /**
1071
+ * Create a buffer.
1072
+ *
1073
+ * This allocates a Doe buffer using the supplied WebGPU-shaped descriptor and
1074
+ * returns the package wrapper for it.
1075
+ *
1076
+ * This example shows the API in its basic form.
1077
+ *
1078
+ * ```js
1079
+ * const buffer = device.createBuffer({
1080
+ * size: 16,
1081
+ * usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
1082
+ * });
1083
+ * ```
1084
+ *
1085
+ * - The descriptor follows the standard WebGPU buffer shape.
1086
+ * - The returned wrapper exposes `size`, `usage`, mapping, and destruction helpers.
1087
+ */
479
1088
  createBuffer(descriptor) {
480
1089
  const buf = addon.createBuffer(this._native, descriptor);
481
1090
  return new DoeGPUBuffer(buf, this._instance, descriptor.size, descriptor.usage, this.queue);
482
1091
  }
483
1092
 
1093
+ /**
1094
+ * Create a shader module from WGSL source.
1095
+ *
1096
+ * This compiles WGSL into a shader module wrapper that can be used by
1097
+ * compute or render pipeline creation.
1098
+ *
1099
+ * This example shows the API in its basic form.
1100
+ *
1101
+ * ```js
1102
+ * const shader = device.createShaderModule({ code: WGSL });
1103
+ * ```
1104
+ *
1105
+ * - `descriptor.code` is required on this surface.
1106
+ * - The package also accepts `descriptor.source` as a convenience alias.
1107
+ */
484
1108
  createShaderModule(descriptor) {
485
1109
  const code = descriptor.code || descriptor.source;
486
1110
  if (!code) throw new Error('createShaderModule: descriptor.code is required');
@@ -488,21 +1112,72 @@ class DoeGPUDevice {
488
1112
  return new DoeGPUShaderModule(mod, code);
489
1113
  }
490
1114
 
1115
+ /**
1116
+ * Create a compute pipeline.
1117
+ *
1118
+ * This builds a pipeline wrapper from a shader module, entry point, and
1119
+ * optional explicit layout information.
1120
+ *
1121
+ * This example shows the API in its basic form.
1122
+ *
1123
+ * ```js
1124
+ * const pipeline = device.createComputePipeline({
1125
+ * layout: "auto",
1126
+ * compute: { module: shader, entryPoint: "main" },
1127
+ * });
1128
+ * ```
1129
+ *
1130
+ * - `layout: "auto"` derives bind-group layouts from the WGSL.
1131
+ * - Explicit pipeline layouts are passed through directly.
1132
+ */
491
1133
  createComputePipeline(descriptor) {
492
1134
  const shader = descriptor.compute?.module;
493
1135
  const entryPoint = descriptor.compute?.entryPoint || 'main';
494
1136
  const layout = descriptor.layout === 'auto' ? null : descriptor.layout;
495
- const autoLayoutEntriesByGroup = layout ? null : inferAutoBindGroupLayouts(shader?._code || '');
1137
+ const autoLayoutEntriesByGroup = layout ? null : inferAutoBindGroupLayouts(
1138
+ shader?._code || '',
1139
+ globals.GPUShaderStage.COMPUTE,
1140
+ );
496
1141
  const native = addon.createComputePipeline(
497
1142
  this._native, shader._native, entryPoint,
498
1143
  layout?._native ?? null);
499
1144
  return new DoeGPUComputePipeline(native, this, layout, autoLayoutEntriesByGroup);
500
1145
  }
501
1146
 
1147
+ /**
1148
+ * Create a compute pipeline through an async-shaped API.
1149
+ *
1150
+ * This preserves the async WebGPU API shape while using Doe's current
1151
+ * synchronous pipeline creation underneath.
1152
+ *
1153
+ * This example shows the API in its basic form.
1154
+ *
1155
+ * ```js
1156
+ * const pipeline = await device.createComputePipelineAsync(descriptor);
1157
+ * ```
1158
+ *
1159
+ * - Doe currently resolves this by calling the synchronous pipeline creation path.
1160
+ * - The async shape exists for WebGPU API compatibility.
1161
+ */
502
1162
  async createComputePipelineAsync(descriptor) {
503
1163
  return this.createComputePipeline(descriptor);
504
1164
  }
505
1165
 
1166
+ /**
1167
+ * Create a bind-group layout.
1168
+ *
1169
+ * This normalizes the descriptor into the shape expected by Doe and returns
1170
+ * a layout wrapper for later resource binding.
1171
+ *
1172
+ * This example shows the API in its basic form.
1173
+ *
1174
+ * ```js
1175
+ * const layout = device.createBindGroupLayout({ entries });
1176
+ * ```
1177
+ *
1178
+ * - Missing buffer entry fields are normalized to WebGPU-style defaults.
1179
+ * - Storage-texture entries are forwarded when present.
1180
+ */
506
1181
  createBindGroupLayout(descriptor) {
507
1182
  const entries = (descriptor.entries || []).map((e) => ({
508
1183
  binding: e.binding,
@@ -518,6 +1193,21 @@ class DoeGPUDevice {
518
1193
  return new DoeGPUBindGroupLayout(native);
519
1194
  }
520
1195
 
1196
+ /**
1197
+ * Create a bind group.
1198
+ *
1199
+ * This binds resources to a previously created layout and returns the bind
1200
+ * group wrapper used by pass encoders.
1201
+ *
1202
+ * This example shows the API in its basic form.
1203
+ *
1204
+ * ```js
1205
+ * const bindGroup = device.createBindGroup({ layout, entries });
1206
+ * ```
1207
+ *
1208
+ * - Resource buffers may be passed either as `{ buffer, offset, size }` or as bare buffer wrappers.
1209
+ * - Layout and buffer wrappers must come from the same device.
1210
+ */
521
1211
  createBindGroup(descriptor) {
522
1212
  const entries = (descriptor.entries || []).map((e) => {
523
1213
  const entry = {
@@ -533,12 +1223,46 @@ class DoeGPUDevice {
533
1223
  return new DoeGPUBindGroup(native);
534
1224
  }
535
1225
 
1226
+ /**
1227
+ * Create a pipeline layout.
1228
+ *
1229
+ * This combines one or more bind-group layouts into the pipeline layout
1230
+ * wrapper used during pipeline creation.
1231
+ *
1232
+ * This example shows the API in its basic form.
1233
+ *
1234
+ * ```js
1235
+ * const layout = device.createPipelineLayout({ bindGroupLayouts: [group0] });
1236
+ * ```
1237
+ *
1238
+ * - Bind-group layouts are unwrapped to their native handles before creation.
1239
+ * - The returned wrapper is opaque on the JS side.
1240
+ */
536
1241
  createPipelineLayout(descriptor) {
537
1242
  const layouts = (descriptor.bindGroupLayouts || []).map((l) => l._native);
538
1243
  const native = addon.createPipelineLayout(this._native, layouts);
539
1244
  return new DoeGPUPipelineLayout(native);
540
1245
  }
541
1246
 
1247
+ /**
1248
+ * Create a texture.
1249
+ *
1250
+ * This allocates a Doe texture resource from a WebGPU-shaped descriptor and
1251
+ * returns the package wrapper for it.
1252
+ *
1253
+ * This example shows the API in its basic form.
1254
+ *
1255
+ * ```js
1256
+ * const texture = device.createTexture({
1257
+ * size: [64, 64, 1],
1258
+ * format: "rgba8unorm",
1259
+ * usage: GPUTextureUsage.RENDER_ATTACHMENT,
1260
+ * });
1261
+ * ```
1262
+ *
1263
+ * - `descriptor.size` may be a scalar, tuple, or width/height object.
1264
+ * - Omitted format and mip-count fields fall back to package defaults.
1265
+ */
542
1266
  createTexture(descriptor) {
543
1267
  const native = addon.createTexture(this._native, {
544
1268
  format: descriptor.format || 'rgba8unorm',
@@ -551,26 +1275,97 @@ class DoeGPUDevice {
551
1275
  return new DoeGPUTexture(native);
552
1276
  }
553
1277
 
1278
+ /**
1279
+ * Create a sampler.
1280
+ *
1281
+ * This allocates a sampler wrapper that can be used by the package's render
1282
+ * and texture-binding paths.
1283
+ *
1284
+ * This example shows the API in its basic form.
1285
+ *
1286
+ * ```js
1287
+ * const sampler = device.createSampler();
1288
+ * ```
1289
+ *
1290
+ * - An empty descriptor is allowed.
1291
+ * - The returned wrapper is currently an opaque handle on the JS side.
1292
+ */
554
1293
  createSampler(descriptor = {}) {
555
1294
  const native = addon.createSampler(this._native, descriptor);
556
1295
  return new DoeGPUSampler(native);
557
1296
  }
558
1297
 
1298
+ /**
1299
+ * Create a render pipeline.
1300
+ *
1301
+ * This builds the package's render-pipeline wrapper for use with render-pass
1302
+ * encoders on the full surface.
1303
+ *
1304
+ * This example shows the API in its basic form.
1305
+ *
1306
+ * ```js
1307
+ * const pipeline = device.createRenderPipeline(descriptor);
1308
+ * ```
1309
+ *
1310
+ * - The returned wrapper is consumed by render-pass encoders.
1311
+ * - Descriptor handling on this package surface is intentionally narrower than browser engines.
1312
+ */
559
1313
  createRenderPipeline(descriptor) {
560
1314
  const native = addon.createRenderPipeline(this._native);
561
1315
  return new DoeGPURenderPipeline(native);
562
1316
  }
563
1317
 
1318
+ /**
1319
+ * Create a command encoder.
1320
+ *
1321
+ * This creates the object that records compute, render, and copy commands
1322
+ * before queue submission.
1323
+ *
1324
+ * This example shows the API in its basic form.
1325
+ *
1326
+ * ```js
1327
+ * const encoder = device.createCommandEncoder();
1328
+ * ```
1329
+ *
1330
+ * - The descriptor is accepted for API shape compatibility.
1331
+ * - The returned encoder records work until `finish()` is called.
1332
+ */
564
1333
  createCommandEncoder(descriptor) {
565
1334
  return new DoeGPUCommandEncoder(this._native);
566
1335
  }
567
1336
 
1337
+ /**
1338
+ * Release the native device.
1339
+ *
1340
+ * This tears down the underlying Doe device associated with the wrapper.
1341
+ *
1342
+ * This example shows the API in its basic form.
1343
+ *
1344
+ * ```js
1345
+ * device.destroy();
1346
+ * ```
1347
+ *
1348
+ * - Reusing the device after destruction is unsupported.
1349
+ * - Existing wrappers created from the device do not regain validity afterward.
1350
+ */
568
1351
  destroy() {
569
1352
  addon.deviceRelease(this._native);
570
1353
  this._native = null;
571
1354
  }
572
1355
  }
573
1356
 
1357
+ /**
1358
+ * Adapter returned by `gpu.requestAdapter()`.
1359
+ *
1360
+ * This example shows the API in its basic form.
1361
+ *
1362
+ * ```js
1363
+ * const adapter = await gpu.requestAdapter();
1364
+ * ```
1365
+ *
1366
+ * - `features` and `limits` are exposed as data properties.
1367
+ * - The adapter produces full-surface devices on this package entrypoint.
1368
+ */
574
1369
  class DoeGPUAdapter {
575
1370
  constructor(native, instance) {
576
1371
  this._native = native;
@@ -579,34 +1374,124 @@ class DoeGPUAdapter {
579
1374
  this.limits = DOE_LIMITS;
580
1375
  }
581
1376
 
1377
+ /**
1378
+ * Request a device from this adapter.
1379
+ *
1380
+ * This creates the full-surface Doe device associated with the adapter.
1381
+ *
1382
+ * This example shows the API in its basic form.
1383
+ *
1384
+ * ```js
1385
+ * const device = await adapter.requestDevice();
1386
+ * ```
1387
+ *
1388
+ * - The descriptor is accepted for WebGPU API shape compatibility.
1389
+ * - The returned device includes the full package surface.
1390
+ */
582
1391
  async requestDevice(descriptor) {
583
1392
  const device = addon.requestDevice(this._instance, this._native);
584
1393
  return new DoeGPUDevice(device, this._instance);
585
1394
  }
586
1395
 
1396
+ /**
1397
+ * Release the native adapter.
1398
+ *
1399
+ * This tears down the adapter handle that was returned by Doe for this GPU.
1400
+ *
1401
+ * This example shows the API in its basic form.
1402
+ *
1403
+ * ```js
1404
+ * adapter.destroy();
1405
+ * ```
1406
+ *
1407
+ * - Reusing the adapter after destruction is unsupported.
1408
+ */
587
1409
  destroy() {
588
1410
  addon.adapterRelease(this._native);
589
1411
  this._native = null;
590
1412
  }
591
1413
  }
592
1414
 
1415
+ /**
1416
+ * GPU root object returned by `create()` or installed at `navigator.gpu`.
1417
+ *
1418
+ * This example shows the API in its basic form.
1419
+ *
1420
+ * ```js
1421
+ * const gpu = create();
1422
+ * ```
1423
+ *
1424
+ * - This is a headless package-owned GPU object, not a browser-owned DOM object.
1425
+ */
593
1426
  class DoeGPU {
594
1427
  constructor(instance) {
595
1428
  this._instance = instance;
596
1429
  }
597
1430
 
1431
+ /**
1432
+ * Request an adapter from the Doe runtime.
1433
+ *
1434
+ * This asks the package-owned GPU object for an adapter wrapper that can
1435
+ * later create full-surface devices.
1436
+ *
1437
+ * This example shows the API in its basic form.
1438
+ *
1439
+ * ```js
1440
+ * const adapter = await gpu.requestAdapter();
1441
+ * ```
1442
+ *
1443
+ * - The current Doe package path ignores adapter filtering options.
1444
+ * - The returned adapter exposes full-surface device creation.
1445
+ */
598
1446
  async requestAdapter(options) {
599
1447
  const adapter = addon.requestAdapter(this._instance);
600
1448
  return new DoeGPUAdapter(adapter, this._instance);
601
1449
  }
602
1450
  }
603
1451
 
1452
+ /**
1453
+ * Create a package-local `GPU` object backed by the Doe native runtime.
1454
+ *
1455
+ * This loads the addon/runtime if needed, creates a fresh GPU instance, and
1456
+ * returns an object with `requestAdapter(...)`.
1457
+ *
1458
+ * This example shows the API in its basic form.
1459
+ *
1460
+ * ```js
1461
+ * import { create } from "@simulatte/webgpu";
1462
+ *
1463
+ * const gpu = create();
1464
+ * const adapter = await gpu.requestAdapter();
1465
+ * ```
1466
+ *
1467
+ * - Throws if the native addon or `libwebgpu_doe` cannot be found.
1468
+ * - `createArgs` are currently accepted for API stability but ignored by the default Doe-native provider path.
1469
+ */
604
1470
  export function create(createArgs = null) {
605
1471
  ensureLibrary();
606
1472
  const instance = addon.createInstance();
607
1473
  return new DoeGPU(instance);
608
1474
  }
609
1475
 
1476
+ /**
1477
+ * Install the package WebGPU globals onto a target object and return its GPU.
1478
+ *
1479
+ * This adds missing enum globals plus `navigator.gpu` to `target`, then
1480
+ * returns the created package-local GPU object.
1481
+ *
1482
+ * This example shows the API in its basic form.
1483
+ *
1484
+ * ```js
1485
+ * import { setupGlobals } from "@simulatte/webgpu";
1486
+ *
1487
+ * setupGlobals(globalThis);
1488
+ * const adapter = await navigator.gpu.requestAdapter();
1489
+ * ```
1490
+ *
1491
+ * - Existing properties are preserved; this only fills in missing globals.
1492
+ * - If `target.navigator` exists without `gpu`, only `navigator.gpu` is added.
1493
+ * - The returned GPU is still headless/package-owned, not browser DOM ownership or browser-process parity.
1494
+ */
610
1495
  export function setupGlobals(target = globalThis, createArgs = null) {
611
1496
  for (const [name, value] of Object.entries(globals)) {
612
1497
  if (target[name] === undefined) {
@@ -637,17 +1522,72 @@ export function setupGlobals(target = globalThis, createArgs = null) {
637
1522
  return gpu;
638
1523
  }
639
1524
 
1525
+ /**
1526
+ * Request a Doe-backed adapter from the full package surface.
1527
+ *
1528
+ * This is a convenience wrapper over `create(...).requestAdapter(...)`.
1529
+ *
1530
+ * This example shows the API in its basic form.
1531
+ *
1532
+ * ```js
1533
+ * import { requestAdapter } from "@simulatte/webgpu";
1534
+ *
1535
+ * const adapter = await requestAdapter();
1536
+ * ```
1537
+ *
1538
+ * - Returns `null` if no adapter is available.
1539
+ * - `adapterOptions` are accepted for WebGPU shape compatibility; the current Doe package path does not use them for adapter filtering.
1540
+ */
640
1541
  export async function requestAdapter(adapterOptions = undefined, createArgs = null) {
641
1542
  const gpu = create(createArgs);
642
1543
  return gpu.requestAdapter(adapterOptions);
643
1544
  }
644
1545
 
1546
+ /**
1547
+ * Request a Doe-backed device from the full package surface.
1548
+ *
1549
+ * This creates a package-local GPU, requests an adapter, then requests a
1550
+ * device from that adapter.
1551
+ *
1552
+ * This example shows the API in its basic form.
1553
+ *
1554
+ * ```js
1555
+ * import { requestDevice } from "@simulatte/webgpu";
1556
+ *
1557
+ * const device = await requestDevice();
1558
+ * const buffer = device.createBuffer({
1559
+ * size: 16,
1560
+ * usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
1561
+ * });
1562
+ * ```
1563
+ *
1564
+ * - On the full package surface, the returned device includes render, texture, sampler, and surface APIs when the runtime supports them.
1565
+ * - Missing runtime prerequisites still fail at request time through the same addon/library checks as `create()`.
1566
+ */
645
1567
  export async function requestDevice(options = {}) {
646
1568
  const createArgs = options?.createArgs ?? null;
647
1569
  const adapter = await requestAdapter(options?.adapterOptions, createArgs);
648
1570
  return adapter.requestDevice(options?.deviceDescriptor);
649
1571
  }
650
1572
 
1573
+ /**
1574
+ * Report how the package resolved and loaded the Doe runtime.
1575
+ *
1576
+ * This returns package/runtime provenance such as whether the native path is
1577
+ * loaded, which library flavor was chosen, and whether build metadata says the
1578
+ * runtime was built with Lean-verified mode.
1579
+ *
1580
+ * This example shows the API in its basic form.
1581
+ *
1582
+ * ```js
1583
+ * import { providerInfo } from "@simulatte/webgpu";
1584
+ *
1585
+ * console.log(providerInfo());
1586
+ * ```
1587
+ *
1588
+ * - If metadata is unavailable, `leanVerifiedBuild` is `null` rather than a guess.
1589
+ * - `loaded: false` is still diagnostically useful before attempting `requestDevice()`.
1590
+ */
651
1591
  export function providerInfo() {
652
1592
  const flavor = libraryFlavor(DOE_LIB_PATH);
653
1593
  return {
@@ -665,7 +1605,42 @@ export function providerInfo() {
665
1605
  };
666
1606
  }
667
1607
 
668
- export { createDoeRuntime, runDawnVsDoeCompare };
1608
+ /**
1609
+ * Create a Node or Bun runtime wrapper for Doe CLI execution.
1610
+ *
1611
+ * This exposes the package-side CLI bridge used for benchmark and command
1612
+ * stream execution workflows.
1613
+ *
1614
+ * This example shows the API in its basic form.
1615
+ *
1616
+ * ```js
1617
+ * import { createDoeRuntime } from "@simulatte/webgpu";
1618
+ *
1619
+ * const runtime = createDoeRuntime();
1620
+ * ```
1621
+ *
1622
+ * - This is package/runtime orchestration, not the in-process WebGPU device path.
1623
+ */
1624
+ export const createDoeRuntime = createDoeRuntimeCli;
1625
+
1626
+ /**
1627
+ * Run the Dawn-vs-Doe compare harness from the full package surface.
1628
+ *
1629
+ * This forwards into the artifact-backed compare wrapper used by benchmark and
1630
+ * verification tooling.
1631
+ *
1632
+ * This example shows the API in its basic form.
1633
+ *
1634
+ * ```js
1635
+ * import { runDawnVsDoeCompare } from "@simulatte/webgpu";
1636
+ *
1637
+ * const result = runDawnVsDoeCompare({ configPath: "bench/config.json" });
1638
+ * ```
1639
+ *
1640
+ * - Requires an explicit compare config path either in options or forwarded CLI args.
1641
+ * - This is a tooling entrypoint, not the in-process `device` or `doe` helper path.
1642
+ */
1643
+ export const runDawnVsDoeCompare = runDawnVsDoeCompareCli;
669
1644
 
670
1645
  export default {
671
1646
  create,