@simulatte/webgpu 0.3.0 → 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 +37 -10
  2. package/LICENSE +191 -0
  3. package/README.md +62 -48
  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/src/doe.js DELETED
@@ -1,641 +0,0 @@
1
- const DOE_GPU_BUFFER_USAGE = {
2
- MAP_READ: 0x0001,
3
- COPY_SRC: 0x0004,
4
- COPY_DST: 0x0008,
5
- UNIFORM: 0x0040,
6
- STORAGE: 0x0080,
7
- };
8
-
9
- const DOE_GPU_SHADER_STAGE = {
10
- COMPUTE: 0x4,
11
- };
12
-
13
- const DOE_GPU_MAP_MODE = {
14
- READ: 0x0001,
15
- };
16
-
17
- const DOE_BUFFER_META = new WeakMap();
18
-
19
- function resolveBufferUsageToken(token, combined = false) {
20
- switch (token) {
21
- case 'upload':
22
- return DOE_GPU_BUFFER_USAGE.COPY_DST;
23
- case 'readback':
24
- return combined
25
- ? DOE_GPU_BUFFER_USAGE.COPY_SRC
26
- : DOE_GPU_BUFFER_USAGE.COPY_SRC | DOE_GPU_BUFFER_USAGE.COPY_DST | DOE_GPU_BUFFER_USAGE.MAP_READ;
27
- case 'uniform':
28
- return DOE_GPU_BUFFER_USAGE.UNIFORM | DOE_GPU_BUFFER_USAGE.COPY_DST;
29
- case 'storageRead':
30
- return DOE_GPU_BUFFER_USAGE.STORAGE | DOE_GPU_BUFFER_USAGE.COPY_DST;
31
- case 'storageReadWrite':
32
- return DOE_GPU_BUFFER_USAGE.STORAGE | DOE_GPU_BUFFER_USAGE.COPY_DST | DOE_GPU_BUFFER_USAGE.COPY_SRC;
33
- default:
34
- throw new Error(`Unknown Doe buffer usage token: ${token}`);
35
- }
36
- }
37
-
38
- function resolveBufferUsage(usage) {
39
- if (typeof usage === 'number') return usage;
40
- if (typeof usage === 'string') return resolveBufferUsageToken(usage);
41
- if (Array.isArray(usage)) {
42
- const combined = usage.length > 1;
43
- return usage.reduce((mask, token) => mask | resolveBufferUsageToken(token, combined), 0);
44
- }
45
- throw new Error('Doe buffer usage must be a number, string, or string array.');
46
- }
47
-
48
- function inferBindingAccessToken(token) {
49
- switch (token) {
50
- case 'uniform':
51
- return 'uniform';
52
- case 'storageRead':
53
- return 'storageRead';
54
- case 'storageReadWrite':
55
- return 'storageReadWrite';
56
- default:
57
- return null;
58
- }
59
- }
60
-
61
- function inferBindingAccess(usage) {
62
- if (typeof usage === 'number' || usage == null) return null;
63
- const tokens = typeof usage === 'string'
64
- ? [usage]
65
- : Array.isArray(usage)
66
- ? usage
67
- : null;
68
- if (!tokens) {
69
- throw new Error('Doe buffer usage must be a number, string, or string array.');
70
- }
71
- const inferred = [...new Set(tokens.map(inferBindingAccessToken).filter(Boolean))];
72
- if (inferred.length > 1) {
73
- throw new Error(`Doe buffer usage cannot imply multiple binding access modes: ${inferred.join(', ')}`);
74
- }
75
- return inferred[0] ?? null;
76
- }
77
-
78
- function rememberBufferUsage(buffer, usage) {
79
- DOE_BUFFER_META.set(buffer, {
80
- binding_access: inferBindingAccess(usage),
81
- });
82
- return buffer;
83
- }
84
-
85
- function inferredBindingAccessForBuffer(buffer) {
86
- return DOE_BUFFER_META.get(buffer)?.binding_access ?? null;
87
- }
88
-
89
- function normalizeWorkgroups(workgroups) {
90
- if (typeof workgroups === 'number') {
91
- return [workgroups, 1, 1];
92
- }
93
- if (Array.isArray(workgroups) && workgroups.length === 2) {
94
- return [workgroups[0], workgroups[1], 1];
95
- }
96
- if (Array.isArray(workgroups) && workgroups.length === 3) {
97
- return workgroups;
98
- }
99
- throw new Error('Doe workgroups must be a number, [x, y], or [x, y, z].');
100
- }
101
-
102
- function normalizeDataView(data) {
103
- if (ArrayBuffer.isView(data)) {
104
- return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
105
- }
106
- if (data instanceof ArrayBuffer) {
107
- return new Uint8Array(data);
108
- }
109
- throw new Error('Doe buffer data must be an ArrayBuffer or ArrayBufferView.');
110
- }
111
-
112
- function resolveBufferSize(source) {
113
- if (source && typeof source === 'object' && typeof source.size === 'number') {
114
- return source.size;
115
- }
116
- if (ArrayBuffer.isView(source)) {
117
- return source.byteLength;
118
- }
119
- if (source instanceof ArrayBuffer) {
120
- return source.byteLength;
121
- }
122
- throw new Error('Doe buffer-like source must expose a byte size or be ArrayBuffer-backed data.');
123
- }
124
-
125
- function normalizeBinding(binding, index) {
126
- const entry = binding && typeof binding === 'object' && 'buffer' in binding
127
- ? binding
128
- : { buffer: binding };
129
- const access = entry.access ?? inferredBindingAccessForBuffer(entry.buffer);
130
- if (!access) {
131
- throw new Error(
132
- 'Doe binding access is required for buffers without Doe helper usage metadata. ' +
133
- 'Pass { buffer, access } or create the buffer through doe.buffers.* with a bindable usage token.'
134
- );
135
- }
136
- return {
137
- binding: index,
138
- buffer: entry.buffer,
139
- access,
140
- };
141
- }
142
-
143
- function bindGroupLayoutEntry(binding) {
144
- const buffer_type = binding.access === 'uniform'
145
- ? 'uniform'
146
- : binding.access === 'storageRead'
147
- ? 'read-only-storage'
148
- : 'storage';
149
- return {
150
- binding: binding.binding,
151
- visibility: DOE_GPU_SHADER_STAGE.COMPUTE,
152
- buffer: { type: buffer_type },
153
- };
154
- }
155
-
156
- function bindGroupEntry(binding) {
157
- return {
158
- binding: binding.binding,
159
- resource: { buffer: binding.buffer },
160
- };
161
- }
162
-
163
- /**
164
- * Reusable compute kernel returned by `doe.compute.compile(...)`.
165
- *
166
- * This keeps the compiled pipeline and bind-group layout needed for repeated
167
- * dispatches of the same WGSL shape.
168
- *
169
- * - Instances are returned through the `Doe API` surface rather than exported directly.
170
- * - Dispatches still require bindings and workgroup counts for each run.
171
- */
172
- class DoeKernel {
173
- constructor(device, pipeline, layout, entryPoint) {
174
- this.device = device;
175
- this.pipeline = pipeline;
176
- this.layout = layout;
177
- this.entryPoint = entryPoint;
178
- }
179
-
180
- /**
181
- * Dispatch the compiled kernel once.
182
- *
183
- * - `workgroups` may be `number`, `[x, y]`, or `[x, y, z]`.
184
- * - Bare buffers without Doe helper metadata still require `{ buffer, access }`.
185
- */
186
- async dispatch(options) {
187
- const bindings = (options.bindings ?? []).map(normalizeBinding);
188
- const workgroups = normalizeWorkgroups(options.workgroups);
189
- const bindGroup = this.device.createBindGroup({
190
- label: options.label ?? undefined,
191
- layout: this.layout,
192
- entries: bindings.map(bindGroupEntry),
193
- });
194
- const encoder = this.device.createCommandEncoder({ label: options.label ?? undefined });
195
- const pass = encoder.beginComputePass({ label: options.label ?? undefined });
196
- pass.setPipeline(this.pipeline);
197
- if (bindings.length > 0) {
198
- pass.setBindGroup(0, bindGroup);
199
- }
200
- pass.dispatchWorkgroups(workgroups[0], workgroups[1], workgroups[2]);
201
- pass.end();
202
- this.device.queue.submit([encoder.finish()]);
203
- if (typeof this.device.queue.onSubmittedWorkDone === 'function') {
204
- await this.device.queue.onSubmittedWorkDone();
205
- }
206
- }
207
- }
208
-
209
- function compileCompute(device, options) {
210
- const bindings = (options.bindings ?? []).map(normalizeBinding);
211
- const shader = device.createShaderModule({ code: options.code });
212
- const bindGroupLayout = device.createBindGroupLayout({
213
- entries: bindings.map(bindGroupLayoutEntry),
214
- });
215
- const pipelineLayout = device.createPipelineLayout({
216
- bindGroupLayouts: [bindGroupLayout],
217
- });
218
- const pipeline = device.createComputePipeline({
219
- layout: pipelineLayout,
220
- compute: {
221
- module: shader,
222
- entryPoint: options.entryPoint ?? 'main',
223
- },
224
- });
225
- return new DoeKernel(device, pipeline, bindGroupLayout, options.entryPoint ?? 'main');
226
- }
227
-
228
- function createBuffer(device, options) {
229
- return rememberBufferUsage(device.createBuffer({
230
- label: options.label ?? undefined,
231
- size: options.size,
232
- usage: resolveBufferUsage(options.usage),
233
- mappedAtCreation: options.mappedAtCreation ?? false,
234
- }), options.usage);
235
- }
236
-
237
- function createBufferFromData(device, data, options = {}) {
238
- const view = normalizeDataView(data);
239
- const usage = options.usage ?? 'storageRead';
240
- const buffer = rememberBufferUsage(device.createBuffer({
241
- label: options.label ?? undefined,
242
- size: view.byteLength,
243
- usage: resolveBufferUsage(usage),
244
- }), usage);
245
- device.queue.writeBuffer(buffer, 0, view);
246
- return buffer;
247
- }
248
-
249
- function createBufferLike(device, source, options = {}) {
250
- return createBuffer(device, {
251
- ...options,
252
- size: options.size ?? resolveBufferSize(source),
253
- });
254
- }
255
-
256
- async function readBuffer(device, buffer, type, options = {}) {
257
- const offset = options.offset ?? 0;
258
- const size = options.size ?? Math.max(0, (buffer.size ?? 0) - offset);
259
- if (((buffer.usage ?? 0) & DOE_GPU_BUFFER_USAGE.MAP_READ) !== 0) {
260
- await buffer.mapAsync(DOE_GPU_MAP_MODE.READ, offset, size);
261
- const copy = buffer.getMappedRange(offset, size).slice(0);
262
- buffer.unmap();
263
- return new type(copy);
264
- }
265
- const staging = device.createBuffer({
266
- label: options.label ?? undefined,
267
- size,
268
- usage: DOE_GPU_BUFFER_USAGE.COPY_DST | DOE_GPU_BUFFER_USAGE.MAP_READ,
269
- });
270
- const encoder = device.createCommandEncoder({ label: options.label ?? undefined });
271
- encoder.copyBufferToBuffer(buffer, offset, staging, 0, size);
272
- device.queue.submit([encoder.finish()]);
273
- await staging.mapAsync(DOE_GPU_MAP_MODE.READ);
274
- const copy = staging.getMappedRange().slice(0);
275
- staging.unmap();
276
- if (typeof staging.destroy === 'function') {
277
- staging.destroy();
278
- }
279
- return new type(copy);
280
- }
281
-
282
- async function runCompute(device, options) {
283
- const kernel = compileCompute(device, options);
284
- await kernel.dispatch({
285
- bindings: options.bindings ?? [],
286
- workgroups: options.workgroups,
287
- label: options.label,
288
- });
289
- }
290
-
291
- function assertLayer3Usage(usage, path) {
292
- if (typeof usage === 'number') {
293
- throw new Error(`Doe ${path} does not accept raw numeric usage flags. Use Doe usage tokens on compute.once(...) or drop to gpu.buffers.*.`);
294
- }
295
- if (Array.isArray(usage) && usage.some((token) => typeof token === 'number')) {
296
- throw new Error(`Doe ${path} does not accept raw numeric usage flags. Use Doe usage tokens on compute.once(...) or drop to gpu.buffers.*.`);
297
- }
298
- }
299
-
300
- function normalizeOnceInput(device, input, index) {
301
- if (ArrayBuffer.isView(input) || input instanceof ArrayBuffer) {
302
- const buffer = createBufferFromData(device, input, {});
303
- return {
304
- binding: buffer,
305
- buffer,
306
- byte_length: resolveBufferSize(input),
307
- owned: true,
308
- };
309
- }
310
-
311
- if (input && typeof input === 'object' && 'data' in input) {
312
- assertLayer3Usage(input.usage, `compute.once input ${index} usage`);
313
- const buffer = createBufferFromData(device, input.data, {
314
- usage: input.usage ?? 'storageRead',
315
- label: input.label,
316
- });
317
- return {
318
- binding: input.access ? { buffer, access: input.access } : buffer,
319
- buffer,
320
- byte_length: resolveBufferSize(input.data),
321
- owned: true,
322
- };
323
- }
324
-
325
- if (input && typeof input === 'object' && 'buffer' in input) {
326
- return {
327
- binding: input,
328
- buffer: input.buffer,
329
- byte_length: resolveBufferSize(input.buffer),
330
- owned: false,
331
- };
332
- }
333
-
334
- if (input && typeof input === 'object' && typeof input.size === 'number') {
335
- return {
336
- binding: input,
337
- buffer: input,
338
- byte_length: input.size,
339
- owned: false,
340
- };
341
- }
342
-
343
- throw new Error(`Doe compute.once input ${index} must be data, a Doe input spec, or a buffer.`);
344
- }
345
-
346
- function normalizeOnceOutput(device, output, inputs) {
347
- if (!output || typeof output !== 'object') {
348
- throw new Error('Doe compute.once output is required.');
349
- }
350
- if (typeof output.type !== 'function') {
351
- throw new Error('Doe compute.once output.type must be a typed-array constructor.');
352
- }
353
-
354
- const fallback_input_index = inputs.length > 0 ? 0 : null;
355
- const like_input_index = output.likeInput ?? fallback_input_index;
356
- const size = output.size ?? (
357
- like_input_index != null && inputs[like_input_index]
358
- ? inputs[like_input_index].byte_length
359
- : null
360
- );
361
-
362
- if (!(size > 0)) {
363
- throw new Error('Doe compute.once output size must be provided or derived from likeInput.');
364
- }
365
-
366
- assertLayer3Usage(output.usage, 'compute.once output usage');
367
- const buffer = createBuffer(device, {
368
- size,
369
- usage: output.usage ?? 'storageReadWrite',
370
- label: output.label,
371
- });
372
- return {
373
- binding: output.access ? { buffer, access: output.access } : buffer,
374
- buffer,
375
- type: output.type,
376
- read_options: output.read ?? {},
377
- };
378
- }
379
-
380
- async function computeOnce(device, options) {
381
- const inputs = (options.inputs ?? []).map((input, index) => normalizeOnceInput(device, input, index));
382
- const output = normalizeOnceOutput(device, options.output, inputs);
383
- try {
384
- await runCompute(device, {
385
- code: options.code,
386
- entryPoint: options.entryPoint,
387
- bindings: [...inputs.map((input) => input.binding), output.binding],
388
- workgroups: options.workgroups,
389
- label: options.label,
390
- });
391
- return await readBuffer(device, output.buffer, output.type, output.read_options);
392
- } finally {
393
- if (typeof output.buffer.destroy === 'function') {
394
- output.buffer.destroy();
395
- }
396
- for (const input of inputs) {
397
- if (input.owned && typeof input.buffer.destroy === 'function') {
398
- input.buffer.destroy();
399
- }
400
- }
401
- }
402
- }
403
-
404
- function createBoundDoe(device) {
405
- return {
406
- device,
407
- buffers: {
408
- /**
409
- * Create a buffer with explicit size and Doe usage tokens.
410
- *
411
- * This is part of the `Doe API` surface over `device.createBuffer(...)`.
412
- * It accepts Doe usage tokens and remembers bindability metadata for
413
- * later Doe API calls.
414
- *
415
- * - Raw numeric usage flags are allowed here for explicit control.
416
- * - If you later pass a raw-usage buffer to `compute.run(...)`, you may still need `{ buffer, access }` because Doe can only infer access from Doe usage tokens, not arbitrary bitmasks.
417
- */
418
- create(options) {
419
- return createBuffer(device, options);
420
- },
421
- /**
422
- * Create a buffer from typed-array or ArrayBuffer data and upload it immediately.
423
- *
424
- * This allocates a buffer, writes the provided data into it, and
425
- * remembers Doe usage metadata for later helper inference.
426
- *
427
- * - Defaults to `storageRead` usage when none is provided.
428
- * - Raw numeric usage flags are allowed, but that may disable later access inference if the bitmask does not map cleanly to one Doe access mode.
429
- */
430
- fromData(data, options = {}) {
431
- return createBufferFromData(device, data, options);
432
- },
433
- /**
434
- * Create a buffer whose size is derived from another buffer or typed-array source.
435
- *
436
- * This copies the byte size from `source` unless an explicit
437
- * `options.size` is provided, which removes common `size: src.size`
438
- * boilerplate.
439
- *
440
- * - `source` may be a Doe buffer, a raw buffer exposing `.size`, a typed array, or an `ArrayBuffer`.
441
- * - If the source has no byte size, this throws instead of guessing.
442
- */
443
- like(source, options = {}) {
444
- return createBufferLike(device, source, options);
445
- },
446
- /**
447
- * Read a buffer back into a typed array.
448
- *
449
- * This copies the source buffer into a staging buffer, maps it for read,
450
- * and returns a new typed array instance created from the copied bytes.
451
- *
452
- * - `options.offset` and `options.size` let you read a subrange.
453
- * - The returned typed array constructor must accept a plain `ArrayBuffer`.
454
- */
455
- read(buffer, type, options = {}) {
456
- return readBuffer(device, buffer, type, options);
457
- },
458
- },
459
- compute: {
460
- /**
461
- * Compile and dispatch a one-off compute job.
462
- *
463
- * This builds a compute pipeline for the provided WGSL, dispatches it
464
- * once with the supplied bindings and workgroups, and waits for submitted
465
- * work to finish as part of the explicit `Doe API` surface.
466
- *
467
- * - `workgroups` may be `number`, `[x, y]`, or `[x, y, z]`.
468
- * - Bare buffers without Doe helper metadata require `{ buffer, access }`.
469
- * - This recompiles per call; use `compute.compile(...)` when reusing the kernel.
470
- */
471
- run(options) {
472
- return runCompute(device, options);
473
- },
474
- /**
475
- * Compile a reusable compute kernel.
476
- *
477
- * This creates the shader, bind-group layout, and compute pipeline once
478
- * and returns a kernel object with `.dispatch(...)`.
479
- *
480
- * - Binding access is inferred from the bindings passed at compile time.
481
- * - Reuse this path when you are dispatching the same WGSL shape repeatedly.
482
- */
483
- compile(options) {
484
- return compileCompute(device, options);
485
- },
486
- /**
487
- * Run a narrow Doe routines typed-array workflow.
488
- *
489
- * This is the first `Doe routines` path. It accepts typed-array or Doe input specs, allocates temporary
490
- * buffers, dispatches the compute job once, reads the output back, and
491
- * returns the requested typed array result.
492
- *
493
- * - This is intentionally opinionated: it rejects raw numeric WebGPU usage flags and expects Doe usage tokens when usage is specified.
494
- * - Output size defaults from `likeInput` or the first input when possible; if no size can be derived, it throws instead of guessing.
495
- * - Temporary buffers created internally are destroyed before the call returns.
496
- */
497
- once(options) {
498
- return computeOnce(device, options);
499
- },
500
- },
501
- };
502
- }
503
-
504
- /**
505
- * Build the shared Doe API / Doe routines namespace for a package surface.
506
- *
507
- * This creates the public `doe` object used by both `@simulatte/webgpu` and
508
- * `@simulatte/webgpu/compute` for the JS convenience surface layered over the
509
- * Doe runtime.
510
- *
511
- * - If no `requestDevice` implementation is supplied, `doe.requestDevice()` throws, but `doe.bind(device)` and the static helper groups still work.
512
- * - Both package surfaces share this helper shape; only the underlying raw device contract differs.
513
- */
514
- export function createDoeNamespace({ requestDevice } = {}) {
515
- return {
516
- /**
517
- * Request a device and return the bound Doe API helper object in one step.
518
- *
519
- * This calls the package-local `requestDevice(...)` implementation, then
520
- * wraps the resulting device into the `Doe API` surface.
521
- *
522
- * - Throws if this Doe namespace was created without a `requestDevice` implementation.
523
- * - The returned `gpu.device` is full-surface or compute-only depending on which package created the namespace.
524
- */
525
- async requestDevice(options = {}) {
526
- if (typeof requestDevice !== 'function') {
527
- throw new Error('Doe requestDevice() is unavailable in this context.');
528
- }
529
- return createBoundDoe(await requestDevice(options));
530
- },
531
-
532
- /**
533
- * Wrap an existing device in the Doe API surface.
534
- *
535
- * This turns a previously requested device into the same bound helper
536
- * object returned by `await doe.requestDevice()`.
537
- *
538
- * - Use this when you need the raw device first for non-helper setup.
539
- * - No async work happens here; it only wraps the device you already have.
540
- */
541
- bind(device) {
542
- return createBoundDoe(device);
543
- },
544
-
545
- buffers: {
546
- /**
547
- * Static Doe API buffer creation call for an explicit device.
548
- *
549
- * This lets callers use the Doe API buffer surface without first binding
550
- * a device into a `gpu` helper object.
551
- *
552
- * - This is the unbound form of `gpu.buffers.create(...)`.
553
- */
554
- create(device, options) {
555
- return createBuffer(device, options);
556
- },
557
- /**
558
- * Static data-upload helper for an explicit device.
559
- *
560
- * This provides the unbound form of the same upload flow exposed on
561
- * `gpu.buffers.fromData(...)`.
562
- *
563
- * - This is the unbound form of `gpu.buffers.fromData(...)`.
564
- */
565
- fromData(device, data, options = {}) {
566
- return createBufferFromData(device, data, options);
567
- },
568
- /**
569
- * Static size-copy helper for an explicit device.
570
- *
571
- * This keeps the `createBufferLike` convenience available when callers
572
- * are working with a raw device rather than a bound helper object.
573
- *
574
- * - This is the unbound form of `gpu.buffers.like(...)`.
575
- */
576
- like(device, source, options = {}) {
577
- return createBufferLike(device, source, options);
578
- },
579
- /**
580
- * Static readback helper for an explicit device.
581
- *
582
- * This exposes the same staging-copy readback path as
583
- * `gpu.buffers.read(...)` without requiring a bound helper.
584
- *
585
- * - This is the unbound form of `gpu.buffers.read(...)`.
586
- */
587
- read(device, buffer, type, options = {}) {
588
- return readBuffer(device, buffer, type, options);
589
- },
590
- },
591
-
592
- compute: {
593
- /**
594
- * Static compute dispatch helper for an explicit device.
595
- *
596
- * This gives raw-device callers the same one-off compute dispatch helper
597
- * that bound helpers expose on `gpu.compute.run(...)`.
598
- *
599
- * - This is the unbound form of `gpu.compute.run(...)`.
600
- */
601
- run(device, options) {
602
- return runCompute(device, options);
603
- },
604
- /**
605
- * Static reusable-kernel compiler for an explicit device.
606
- *
607
- * This exposes the reusable kernel path without requiring a previously
608
- * bound `gpu` helper object.
609
- *
610
- * - This is the unbound form of `gpu.compute.compile(...)`.
611
- */
612
- compile(device, options) {
613
- return compileCompute(device, options);
614
- },
615
- /**
616
- * Static Doe routines typed-array compute call for an explicit device.
617
- *
618
- * This keeps the narrow `Doe routines` `compute.once(...)` workflow available to
619
- * callers that are still holding a raw device.
620
- *
621
- * - This is the unbound form of `gpu.compute.once(...)`.
622
- */
623
- once(device, options) {
624
- return computeOnce(device, options);
625
- },
626
- },
627
- };
628
- }
629
-
630
- /**
631
- * Unbound Doe API / Doe routines namespace without a package-local `requestDevice(...)`.
632
- *
633
- * This export is primarily for internal composition and advanced consumers who
634
- * want the shared Doe API and Doe routines groups without choosing the full or compute package entry.
635
- *
636
- * - `doe.requestDevice()` throws here because no package-local request function is attached.
637
- * - Most package consumers should prefer the `doe` export from `@simulatte/webgpu` or `@simulatte/webgpu/compute`.
638
- */
639
- export const doe = createDoeNamespace();
640
-
641
- export default doe;