@simulatte/webgpu 0.2.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.
package/src/bun.js ADDED
@@ -0,0 +1,2 @@
1
+ export * from "./bun-ffi.js";
2
+ export { default } from "./bun-ffi.js";
package/src/index.js ADDED
@@ -0,0 +1,580 @@
1
+ import { createRequire } from 'node:module';
2
+ import { existsSync } from 'node:fs';
3
+ import { resolve, dirname } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { createDoeRuntime, runDawnVsDoeCompare } from './runtime_cli.js';
6
+ import { loadDoeBuildMetadata } from './build_metadata.js';
7
+
8
+ const __dirname = dirname(fileURLToPath(import.meta.url));
9
+ const require = createRequire(import.meta.url);
10
+
11
+ const addon = loadAddon();
12
+ const DOE_LIB_PATH = resolveDoeLibraryPath();
13
+ const DOE_BUILD_METADATA = loadDoeBuildMetadata({
14
+ packageRoot: resolve(__dirname, '..'),
15
+ libraryPath: DOE_LIB_PATH ?? '',
16
+ });
17
+ let libraryLoaded = false;
18
+
19
+ function loadAddon() {
20
+ const prebuildPath = resolve(__dirname, '..', 'prebuilds', `${process.platform}-${process.arch}`, 'doe_napi.node');
21
+ try {
22
+ return require(prebuildPath);
23
+ } catch {
24
+ try {
25
+ return require('../build/Release/doe_napi.node');
26
+ } catch {
27
+ try {
28
+ return require('../build/Debug/doe_napi.node');
29
+ } catch {
30
+ return null;
31
+ }
32
+ }
33
+ }
34
+ }
35
+
36
+ function resolveDoeLibraryPath() {
37
+ const ext = process.platform === 'darwin' ? 'dylib'
38
+ : process.platform === 'win32' ? 'dll' : 'so';
39
+
40
+ const candidates = [
41
+ process.env.DOE_WEBGPU_LIB,
42
+ process.env.FAWN_DOE_LIB,
43
+ resolve(__dirname, '..', 'prebuilds', `${process.platform}-${process.arch}`, `libwebgpu_doe.${ext}`),
44
+ resolve(__dirname, '..', '..', '..', 'zig', 'zig-out', 'lib', `libwebgpu_doe.${ext}`),
45
+ resolve(process.cwd(), 'zig', 'zig-out', 'lib', `libwebgpu_doe.${ext}`),
46
+ ];
47
+
48
+ for (const candidate of candidates) {
49
+ if (candidate && existsSync(candidate)) return candidate;
50
+ }
51
+ return null;
52
+ }
53
+
54
+ function libraryFlavor(libraryPath) {
55
+ if (!libraryPath) return 'missing';
56
+ if (libraryPath.endsWith('libwebgpu_doe.so') || libraryPath.endsWith('libwebgpu_doe.dylib') || libraryPath.endsWith('libwebgpu_doe.dll')) {
57
+ return 'doe-dropin';
58
+ }
59
+ if (libraryPath.endsWith('libwebgpu.so') || libraryPath.endsWith('libwebgpu.dylib') || libraryPath.endsWith('libwebgpu_dawn.so') || libraryPath.endsWith('libwgpu_native.so') || libraryPath.endsWith('libwgpu_native.so.0')) {
60
+ return 'delegate';
61
+ }
62
+ return 'unknown';
63
+ }
64
+
65
+ function ensureLibrary() {
66
+ if (libraryLoaded) return;
67
+ if (!addon) {
68
+ throw new Error(
69
+ '@simulatte/webgpu: Native addon not found. Run `npm run build:addon` or `npx node-gyp rebuild`.'
70
+ );
71
+ }
72
+ if (!DOE_LIB_PATH) {
73
+ throw new Error(
74
+ '@simulatte/webgpu: libwebgpu_doe not found. Build it with `cd fawn/zig && zig build dropin` or set DOE_WEBGPU_LIB.'
75
+ );
76
+ }
77
+ addon.loadLibrary(DOE_LIB_PATH);
78
+ libraryLoaded = true;
79
+ }
80
+
81
+ // WebGPU enum constants (standard values).
82
+ export const globals = {
83
+ GPUBufferUsage: {
84
+ MAP_READ: 0x0001,
85
+ MAP_WRITE: 0x0002,
86
+ COPY_SRC: 0x0004,
87
+ COPY_DST: 0x0008,
88
+ INDEX: 0x0010,
89
+ VERTEX: 0x0020,
90
+ UNIFORM: 0x0040,
91
+ STORAGE: 0x0080,
92
+ INDIRECT: 0x0100,
93
+ QUERY_RESOLVE: 0x0200,
94
+ },
95
+ GPUShaderStage: {
96
+ VERTEX: 0x1,
97
+ FRAGMENT: 0x2,
98
+ COMPUTE: 0x4,
99
+ },
100
+ GPUMapMode: {
101
+ READ: 0x0001,
102
+ WRITE: 0x0002,
103
+ },
104
+ GPUTextureUsage: {
105
+ COPY_SRC: 0x01,
106
+ COPY_DST: 0x02,
107
+ TEXTURE_BINDING: 0x04,
108
+ STORAGE_BINDING: 0x08,
109
+ RENDER_ATTACHMENT: 0x10,
110
+ },
111
+ };
112
+
113
+ class DoeGPUBuffer {
114
+ constructor(native, instance, size, usage, queue) {
115
+ this._native = native;
116
+ this._instance = instance;
117
+ this._queue = queue;
118
+ this.size = size;
119
+ this.usage = usage;
120
+ }
121
+
122
+ async mapAsync(mode, offset = 0, size = this.size) {
123
+ if (this._queue) addon.flushAndMapSync(this._instance, this._queue, this._native, mode, offset, size);
124
+ else addon.bufferMapSync(this._instance, this._native, mode, offset, size);
125
+ }
126
+
127
+ getMappedRange(offset = 0, size = this.size) {
128
+ return addon.bufferGetMappedRange(this._native, offset, size);
129
+ }
130
+
131
+ unmap() {
132
+ addon.bufferUnmap(this._native);
133
+ }
134
+
135
+ destroy() {
136
+ addon.bufferRelease(this._native);
137
+ this._native = null;
138
+ }
139
+ }
140
+
141
+ class DoeGPUComputePassEncoder {
142
+ constructor(encoder) {
143
+ this._encoder = encoder;
144
+ this._pipeline = null;
145
+ this._bindGroups = [];
146
+ }
147
+
148
+ setPipeline(pipeline) { this._pipeline = pipeline._native; }
149
+
150
+ setBindGroup(index, bindGroup) { this._bindGroups[index] = bindGroup._native; }
151
+
152
+ dispatchWorkgroups(x, y = 1, z = 1) {
153
+ this._encoder._commands.push({
154
+ t: 0, p: this._pipeline, bg: [...this._bindGroups], x, y, z,
155
+ });
156
+ }
157
+
158
+ dispatchWorkgroupsIndirect(indirectBuffer, indirectOffset = 0) {
159
+ this._encoder._ensureNative();
160
+ const pass = addon.beginComputePass(this._encoder._native);
161
+ addon.computePassSetPipeline(pass, this._pipeline);
162
+ for (let i = 0; i < this._bindGroups.length; i++) {
163
+ if (this._bindGroups[i]) addon.computePassSetBindGroup(pass, i, this._bindGroups[i]);
164
+ }
165
+ addon.computePassDispatchWorkgroupsIndirect(pass, indirectBuffer._native, indirectOffset);
166
+ addon.computePassEnd(pass);
167
+ addon.computePassRelease(pass);
168
+ }
169
+
170
+ end() {}
171
+ }
172
+
173
+ class DoeGPUCommandEncoder {
174
+ constructor(device) {
175
+ this._device = device;
176
+ this._commands = [];
177
+ this._native = null;
178
+ }
179
+
180
+ _ensureNative() {
181
+ if (this._native) return;
182
+ this._native = addon.createCommandEncoder(this._device);
183
+ for (const cmd of this._commands) {
184
+ if (cmd.t === 0) {
185
+ const pass = addon.beginComputePass(this._native);
186
+ addon.computePassSetPipeline(pass, cmd.p);
187
+ for (let i = 0; i < cmd.bg.length; i++) {
188
+ if (cmd.bg[i]) addon.computePassSetBindGroup(pass, i, cmd.bg[i]);
189
+ }
190
+ addon.computePassDispatchWorkgroups(pass, cmd.x, cmd.y, cmd.z);
191
+ addon.computePassEnd(pass);
192
+ addon.computePassRelease(pass);
193
+ } else if (cmd.t === 1) {
194
+ addon.commandEncoderCopyBufferToBuffer(this._native, cmd.s, cmd.so, cmd.d, cmd.do, cmd.sz);
195
+ }
196
+ }
197
+ this._commands = [];
198
+ }
199
+
200
+ beginComputePass(descriptor) {
201
+ return new DoeGPUComputePassEncoder(this);
202
+ }
203
+
204
+ beginRenderPass(descriptor) {
205
+ this._ensureNative();
206
+ const colorAttachments = (descriptor.colorAttachments || []).map((a) => ({
207
+ view: a.view._native,
208
+ clearValue: a.clearValue || { r: 0, g: 0, b: 0, a: 1 },
209
+ }));
210
+ const pass = addon.beginRenderPass(this._native, colorAttachments);
211
+ return new DoeGPURenderPassEncoder(pass);
212
+ }
213
+
214
+ copyBufferToBuffer(src, srcOffset, dst, dstOffset, size) {
215
+ if (this._native) {
216
+ addon.commandEncoderCopyBufferToBuffer(this._native, src._native, srcOffset, dst._native, dstOffset, size);
217
+ } else {
218
+ this._commands.push({ t: 1, s: src._native, so: srcOffset, d: dst._native, do: dstOffset, sz: size });
219
+ }
220
+ }
221
+
222
+ finish() {
223
+ if (this._native) {
224
+ const cmd = addon.commandEncoderFinish(this._native);
225
+ return { _native: cmd, _batched: false };
226
+ }
227
+ return { _commands: this._commands, _batched: true };
228
+ }
229
+ }
230
+
231
+ class DoeGPUQueue {
232
+ constructor(native, instance, device) {
233
+ this._native = native;
234
+ this._instance = instance;
235
+ this._device = device;
236
+ }
237
+
238
+ submit(commandBuffers) {
239
+ if (commandBuffers.length > 0 && commandBuffers.every((c) => c._batched)) {
240
+ const allCommands = [];
241
+ for (const cb of commandBuffers) allCommands.push(...cb._commands);
242
+ addon.submitBatched(this._device, this._native, allCommands);
243
+ } else {
244
+ const natives = commandBuffers.map((c) => c._native);
245
+ addon.queueSubmit(this._native, natives);
246
+ }
247
+ }
248
+
249
+ writeBuffer(buffer, bufferOffset, data, dataOffset = 0, size) {
250
+ let view = data;
251
+ if (dataOffset > 0 || size !== undefined) {
252
+ const byteOffset = data.byteOffset + dataOffset * (data.BYTES_PER_ELEMENT || 1);
253
+ const byteLength = size !== undefined
254
+ ? size * (data.BYTES_PER_ELEMENT || 1)
255
+ : data.byteLength - dataOffset * (data.BYTES_PER_ELEMENT || 1);
256
+ view = new Uint8Array(data.buffer, byteOffset, byteLength);
257
+ }
258
+ addon.queueWriteBuffer(this._native, buffer._native, bufferOffset, view);
259
+ }
260
+
261
+ async onSubmittedWorkDone() {
262
+ // No-op: Doe submit commits synchronously. GPU completion is ensured
263
+ // by mapAsync when data is actually needed.
264
+ }
265
+ }
266
+
267
+ class DoeGPURenderPassEncoder {
268
+ constructor(native) { this._native = native; }
269
+
270
+ setPipeline(pipeline) {
271
+ addon.renderPassSetPipeline(this._native, pipeline._native);
272
+ }
273
+
274
+ draw(vertexCount, instanceCount = 1, firstVertex = 0, firstInstance = 0) {
275
+ addon.renderPassDraw(this._native, vertexCount, instanceCount, firstVertex, firstInstance);
276
+ }
277
+
278
+ end() {
279
+ addon.renderPassEnd(this._native);
280
+ }
281
+ }
282
+
283
+ class DoeGPUTexture {
284
+ constructor(native) { this._native = native; }
285
+
286
+ createView(descriptor) {
287
+ const view = addon.textureCreateView(this._native);
288
+ return new DoeGPUTextureView(view);
289
+ }
290
+
291
+ destroy() {
292
+ addon.textureRelease(this._native);
293
+ this._native = null;
294
+ }
295
+ }
296
+
297
+ class DoeGPUTextureView {
298
+ constructor(native) { this._native = native; }
299
+ }
300
+
301
+ class DoeGPUSampler {
302
+ constructor(native) { this._native = native; }
303
+ }
304
+
305
+ class DoeGPURenderPipeline {
306
+ constructor(native) { this._native = native; }
307
+ }
308
+
309
+ class DoeGPUShaderModule {
310
+ constructor(native) { this._native = native; }
311
+ }
312
+
313
+ class DoeGPUComputePipeline {
314
+ constructor(native) { this._native = native; }
315
+
316
+ getBindGroupLayout(index) {
317
+ const layout = addon.computePipelineGetBindGroupLayout(this._native, index);
318
+ return new DoeGPUBindGroupLayout(layout);
319
+ }
320
+ }
321
+
322
+ class DoeGPUBindGroupLayout {
323
+ constructor(native) { this._native = native; }
324
+ }
325
+
326
+ class DoeGPUBindGroup {
327
+ constructor(native) { this._native = native; }
328
+ }
329
+
330
+ class DoeGPUPipelineLayout {
331
+ constructor(native) { this._native = native; }
332
+ }
333
+
334
+ // Metal defaults for Apple Silicon — matches doe_device_caps.zig METAL_LIMITS.
335
+ const DOE_LIMITS = Object.freeze({
336
+ maxTextureDimension1D: 16384,
337
+ maxTextureDimension2D: 16384,
338
+ maxTextureDimension3D: 2048,
339
+ maxTextureArrayLayers: 2048,
340
+ maxBindGroups: 4,
341
+ maxBindGroupsPlusVertexBuffers: 24,
342
+ maxBindingsPerBindGroup: 1000,
343
+ maxDynamicUniformBuffersPerPipelineLayout: 8,
344
+ maxDynamicStorageBuffersPerPipelineLayout: 4,
345
+ maxSampledTexturesPerShaderStage: 16,
346
+ maxSamplersPerShaderStage: 16,
347
+ maxStorageBuffersPerShaderStage: 8,
348
+ maxStorageTexturesPerShaderStage: 4,
349
+ maxUniformBuffersPerShaderStage: 12,
350
+ maxUniformBufferBindingSize: 65536,
351
+ maxStorageBufferBindingSize: 134217728,
352
+ minUniformBufferOffsetAlignment: 256,
353
+ minStorageBufferOffsetAlignment: 32,
354
+ maxVertexBuffers: 8,
355
+ maxBufferSize: 268435456,
356
+ maxVertexAttributes: 16,
357
+ maxVertexBufferArrayStride: 2048,
358
+ maxInterStageShaderVariables: 16,
359
+ maxColorAttachments: 8,
360
+ maxColorAttachmentBytesPerSample: 32,
361
+ maxComputeWorkgroupStorageSize: 32768,
362
+ maxComputeInvocationsPerWorkgroup: 1024,
363
+ maxComputeWorkgroupSizeX: 1024,
364
+ maxComputeWorkgroupSizeY: 1024,
365
+ maxComputeWorkgroupSizeZ: 64,
366
+ maxComputeWorkgroupsPerDimension: 65535,
367
+ });
368
+
369
+ const DOE_FEATURES = Object.freeze(new Set(['shader-f16']));
370
+
371
+ class DoeGPUDevice {
372
+ constructor(native, instance) {
373
+ this._native = native;
374
+ this._instance = instance;
375
+ const q = addon.deviceGetQueue(native);
376
+ this.queue = new DoeGPUQueue(q, instance, native);
377
+ this.limits = DOE_LIMITS;
378
+ this.features = DOE_FEATURES;
379
+ }
380
+
381
+ createBuffer(descriptor) {
382
+ const buf = addon.createBuffer(this._native, descriptor);
383
+ return new DoeGPUBuffer(buf, this._instance, descriptor.size, descriptor.usage, this.queue._native);
384
+ }
385
+
386
+ createShaderModule(descriptor) {
387
+ const code = descriptor.code || descriptor.source;
388
+ if (!code) throw new Error('createShaderModule: descriptor.code is required');
389
+ const mod = addon.createShaderModule(this._native, code);
390
+ return new DoeGPUShaderModule(mod);
391
+ }
392
+
393
+ createComputePipeline(descriptor) {
394
+ const shader = descriptor.compute?.module;
395
+ const entryPoint = descriptor.compute?.entryPoint || 'main';
396
+ const layout = descriptor.layout === 'auto' ? null : descriptor.layout;
397
+ const native = addon.createComputePipeline(
398
+ this._native, shader._native, entryPoint,
399
+ layout?._native ?? null);
400
+ return new DoeGPUComputePipeline(native);
401
+ }
402
+
403
+ async createComputePipelineAsync(descriptor) {
404
+ return this.createComputePipeline(descriptor);
405
+ }
406
+
407
+ createBindGroupLayout(descriptor) {
408
+ const entries = (descriptor.entries || []).map((e) => ({
409
+ binding: e.binding,
410
+ visibility: e.visibility,
411
+ buffer: e.buffer ? {
412
+ type: e.buffer.type || 'uniform',
413
+ hasDynamicOffset: e.buffer.hasDynamicOffset || false,
414
+ minBindingSize: e.buffer.minBindingSize || 0,
415
+ } : undefined,
416
+ storageTexture: e.storageTexture,
417
+ }));
418
+ const native = addon.createBindGroupLayout(this._native, entries);
419
+ return new DoeGPUBindGroupLayout(native);
420
+ }
421
+
422
+ createBindGroup(descriptor) {
423
+ const entries = (descriptor.entries || []).map((e) => {
424
+ const entry = {
425
+ binding: e.binding,
426
+ buffer: e.resource?.buffer?._native ?? e.resource?._native ?? null,
427
+ offset: e.resource?.offset ?? 0,
428
+ };
429
+ if (e.resource?.size !== undefined) entry.size = e.resource.size;
430
+ return entry;
431
+ });
432
+ const native = addon.createBindGroup(
433
+ this._native, descriptor.layout._native, entries);
434
+ return new DoeGPUBindGroup(native);
435
+ }
436
+
437
+ createPipelineLayout(descriptor) {
438
+ const layouts = (descriptor.bindGroupLayouts || []).map((l) => l._native);
439
+ const native = addon.createPipelineLayout(this._native, layouts);
440
+ return new DoeGPUPipelineLayout(native);
441
+ }
442
+
443
+ createTexture(descriptor) {
444
+ const native = addon.createTexture(this._native, {
445
+ format: descriptor.format || 'rgba8unorm',
446
+ width: descriptor.size?.[0] ?? descriptor.size?.width ?? descriptor.size ?? 1,
447
+ height: descriptor.size?.[1] ?? descriptor.size?.height ?? 1,
448
+ depthOrArrayLayers: descriptor.size?.[2] ?? descriptor.size?.depthOrArrayLayers ?? 1,
449
+ usage: descriptor.usage || 0,
450
+ mipLevelCount: descriptor.mipLevelCount || 1,
451
+ });
452
+ return new DoeGPUTexture(native);
453
+ }
454
+
455
+ createSampler(descriptor = {}) {
456
+ const native = addon.createSampler(this._native, descriptor);
457
+ return new DoeGPUSampler(native);
458
+ }
459
+
460
+ createRenderPipeline(descriptor) {
461
+ const native = addon.createRenderPipeline(this._native);
462
+ return new DoeGPURenderPipeline(native);
463
+ }
464
+
465
+ createCommandEncoder(descriptor) {
466
+ return new DoeGPUCommandEncoder(this._native);
467
+ }
468
+
469
+ destroy() {
470
+ addon.deviceRelease(this._native);
471
+ this._native = null;
472
+ }
473
+ }
474
+
475
+ class DoeGPUAdapter {
476
+ constructor(native, instance) {
477
+ this._native = native;
478
+ this._instance = instance;
479
+ this.features = DOE_FEATURES;
480
+ this.limits = DOE_LIMITS;
481
+ }
482
+
483
+ async requestDevice(descriptor) {
484
+ const device = addon.requestDevice(this._instance, this._native);
485
+ return new DoeGPUDevice(device, this._instance);
486
+ }
487
+
488
+ destroy() {
489
+ addon.adapterRelease(this._native);
490
+ this._native = null;
491
+ }
492
+ }
493
+
494
+ class DoeGPU {
495
+ constructor(instance) {
496
+ this._instance = instance;
497
+ }
498
+
499
+ async requestAdapter(options) {
500
+ const adapter = addon.requestAdapter(this._instance);
501
+ return new DoeGPUAdapter(adapter, this._instance);
502
+ }
503
+ }
504
+
505
+ export function create(createArgs = null) {
506
+ ensureLibrary();
507
+ const instance = addon.createInstance();
508
+ return new DoeGPU(instance);
509
+ }
510
+
511
+ export function setupGlobals(target = globalThis, createArgs = null) {
512
+ for (const [name, value] of Object.entries(globals)) {
513
+ if (target[name] === undefined) {
514
+ Object.defineProperty(target, name, {
515
+ value,
516
+ writable: true,
517
+ configurable: true,
518
+ enumerable: false,
519
+ });
520
+ }
521
+ }
522
+ const gpu = create(createArgs);
523
+ if (typeof target.navigator === 'undefined') {
524
+ Object.defineProperty(target, 'navigator', {
525
+ value: { gpu },
526
+ writable: true,
527
+ configurable: true,
528
+ enumerable: false,
529
+ });
530
+ } else if (!target.navigator.gpu) {
531
+ Object.defineProperty(target.navigator, 'gpu', {
532
+ value: gpu,
533
+ writable: true,
534
+ configurable: true,
535
+ enumerable: false,
536
+ });
537
+ }
538
+ return gpu;
539
+ }
540
+
541
+ export async function requestAdapter(adapterOptions = undefined, createArgs = null) {
542
+ const gpu = create(createArgs);
543
+ return gpu.requestAdapter(adapterOptions);
544
+ }
545
+
546
+ export async function requestDevice(options = {}) {
547
+ const createArgs = options?.createArgs ?? null;
548
+ const adapter = await requestAdapter(options?.adapterOptions, createArgs);
549
+ return adapter.requestDevice(options?.deviceDescriptor);
550
+ }
551
+
552
+ export function providerInfo() {
553
+ const flavor = libraryFlavor(DOE_LIB_PATH);
554
+ return {
555
+ module: '@simulatte/webgpu',
556
+ loaded: !!addon && !!DOE_LIB_PATH,
557
+ loadError: !addon ? 'native addon not found' : !DOE_LIB_PATH ? 'libwebgpu_doe not found' : '',
558
+ defaultCreateArgs: [],
559
+ doeNative: flavor === 'doe-dropin',
560
+ libraryFlavor: flavor,
561
+ doeLibraryPath: DOE_LIB_PATH ?? '',
562
+ buildMetadataSource: DOE_BUILD_METADATA.source,
563
+ buildMetadataPath: DOE_BUILD_METADATA.path,
564
+ leanVerifiedBuild: DOE_BUILD_METADATA.leanVerifiedBuild,
565
+ proofArtifactSha256: DOE_BUILD_METADATA.proofArtifactSha256,
566
+ };
567
+ }
568
+
569
+ export { createDoeRuntime, runDawnVsDoeCompare };
570
+
571
+ export default {
572
+ create,
573
+ globals,
574
+ setupGlobals,
575
+ requestAdapter,
576
+ requestDevice,
577
+ providerInfo,
578
+ createDoeRuntime,
579
+ runDawnVsDoeCompare,
580
+ };
@@ -0,0 +1,2 @@
1
+ export * from "./index.js";
2
+ export { default } from "./index.js";
package/src/node.js ADDED
@@ -0,0 +1,2 @@
1
+ export * from "./index.js";
2
+ export { default } from "./index.js";
@@ -0,0 +1 @@
1
+ export { createDoeRuntime, runDawnVsDoeCompare } from "./runtime_cli.js";