@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-ffi.js ADDED
@@ -0,0 +1,1057 @@
1
+ import { dlopen, FFIType, JSCallback, ptr as bunPtr, toArrayBuffer } from "bun:ffi";
2
+ import { existsSync } from "node:fs";
3
+ import { dirname, resolve } 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 PACKAGE_ROOT = resolve(__dirname, "..");
10
+
11
+ const CALLBACK_MODE_ALLOW_PROCESS_EVENTS = 2;
12
+ const REQUEST_ADAPTER_STATUS_SUCCESS = 1;
13
+ const REQUEST_DEVICE_STATUS_SUCCESS = 1;
14
+ const MAP_ASYNC_STATUS_SUCCESS = 1;
15
+ const STYPE_SHADER_SOURCE_WGSL = 0x00000002;
16
+ const PROCESS_EVENTS_TIMEOUT_NS = 5_000_000_000;
17
+
18
+ // Struct layout constants for 64-bit platforms (LP64 / LLP64).
19
+ const PTR_SIZE = 8;
20
+ const SIZE_T_SIZE = 8;
21
+
22
+ // WebGPU enum constants (standard values) — matches index.js.
23
+ export const globals = {
24
+ GPUBufferUsage: {
25
+ MAP_READ: 0x0001,
26
+ MAP_WRITE: 0x0002,
27
+ COPY_SRC: 0x0004,
28
+ COPY_DST: 0x0008,
29
+ INDEX: 0x0010,
30
+ VERTEX: 0x0020,
31
+ UNIFORM: 0x0040,
32
+ STORAGE: 0x0080,
33
+ INDIRECT: 0x0100,
34
+ QUERY_RESOLVE: 0x0200,
35
+ },
36
+ GPUShaderStage: {
37
+ VERTEX: 0x1,
38
+ FRAGMENT: 0x2,
39
+ COMPUTE: 0x4,
40
+ },
41
+ GPUMapMode: {
42
+ READ: 0x0001,
43
+ WRITE: 0x0002,
44
+ },
45
+ GPUTextureUsage: {
46
+ COPY_SRC: 0x01,
47
+ COPY_DST: 0x02,
48
+ TEXTURE_BINDING: 0x04,
49
+ STORAGE_BINDING: 0x08,
50
+ RENDER_ATTACHMENT: 0x10,
51
+ },
52
+ };
53
+
54
+ const DOE_LIMITS = Object.freeze({
55
+ maxTextureDimension1D: 16384,
56
+ maxTextureDimension2D: 16384,
57
+ maxTextureDimension3D: 2048,
58
+ maxTextureArrayLayers: 2048,
59
+ maxBindGroups: 4,
60
+ maxBindGroupsPlusVertexBuffers: 24,
61
+ maxBindingsPerBindGroup: 1000,
62
+ maxDynamicUniformBuffersPerPipelineLayout: 8,
63
+ maxDynamicStorageBuffersPerPipelineLayout: 4,
64
+ maxSampledTexturesPerShaderStage: 16,
65
+ maxSamplersPerShaderStage: 16,
66
+ maxStorageBuffersPerShaderStage: 8,
67
+ maxStorageTexturesPerShaderStage: 4,
68
+ maxUniformBuffersPerShaderStage: 12,
69
+ maxUniformBufferBindingSize: 65536,
70
+ maxStorageBufferBindingSize: 134217728,
71
+ minUniformBufferOffsetAlignment: 256,
72
+ minStorageBufferOffsetAlignment: 32,
73
+ maxVertexBuffers: 8,
74
+ maxBufferSize: 268435456,
75
+ maxVertexAttributes: 16,
76
+ maxVertexBufferArrayStride: 2048,
77
+ maxInterStageShaderVariables: 16,
78
+ maxColorAttachments: 8,
79
+ maxColorAttachmentBytesPerSample: 32,
80
+ maxComputeWorkgroupStorageSize: 32768,
81
+ maxComputeInvocationsPerWorkgroup: 1024,
82
+ maxComputeWorkgroupSizeX: 1024,
83
+ maxComputeWorkgroupSizeY: 1024,
84
+ maxComputeWorkgroupSizeZ: 64,
85
+ maxComputeWorkgroupsPerDimension: 65535,
86
+ });
87
+
88
+ const DOE_FEATURES = Object.freeze(new Set(["shader-f16"]));
89
+
90
+ // ---------------------------------------------------------------------------
91
+ // Library resolution
92
+ // ---------------------------------------------------------------------------
93
+
94
+ const LIB_EXT = { darwin: "dylib", linux: "so", win32: "dll" };
95
+
96
+ function resolveDoeLibraryPath() {
97
+ const ext = LIB_EXT[process.platform] ?? "so";
98
+ const candidates = [
99
+ process.env.DOE_WEBGPU_LIB,
100
+ resolve(PACKAGE_ROOT, "prebuilds", `${process.platform}-${process.arch}`, `libwebgpu_doe.${ext}`),
101
+ resolve(PACKAGE_ROOT, "..", "..", "zig", "zig-out", "lib", `libwebgpu_doe.${ext}`),
102
+ resolve(process.cwd(), "zig", "zig-out", "lib", `libwebgpu_doe.${ext}`),
103
+ ];
104
+ for (const c of candidates) {
105
+ if (c && existsSync(c)) return c;
106
+ }
107
+ return null;
108
+ }
109
+
110
+ const DOE_LIB_PATH = resolveDoeLibraryPath();
111
+ const DOE_LIBRARY_FLAVOR = libraryFlavor(DOE_LIB_PATH);
112
+ const DOE_BUILD_METADATA = loadDoeBuildMetadata({
113
+ packageRoot: PACKAGE_ROOT,
114
+ libraryPath: DOE_LIB_PATH ?? "",
115
+ });
116
+ let wgpu = null;
117
+
118
+ // ---------------------------------------------------------------------------
119
+ // FFI symbol bindings
120
+ // ---------------------------------------------------------------------------
121
+
122
+ function openLibrary(path) {
123
+ return dlopen(path, {
124
+ // Instance
125
+ wgpuCreateInstance: { args: [FFIType.ptr], returns: FFIType.ptr },
126
+ wgpuInstanceRelease: { args: [FFIType.ptr], returns: FFIType.void },
127
+ wgpuInstanceWaitAny: { args: [FFIType.ptr, FFIType.u64, FFIType.ptr, FFIType.u64], returns: FFIType.u32 },
128
+ wgpuInstanceProcessEvents: { args: [FFIType.ptr], returns: FFIType.void },
129
+
130
+ // Adapter/Device (flat helpers)
131
+ doeRequestAdapterFlat: { args: [FFIType.ptr, FFIType.ptr, FFIType.u32, FFIType.ptr, FFIType.ptr, FFIType.ptr], returns: FFIType.u64 },
132
+ doeRequestDeviceFlat: { args: [FFIType.ptr, FFIType.ptr, FFIType.u32, FFIType.ptr, FFIType.ptr, FFIType.ptr], returns: FFIType.u64 },
133
+ wgpuAdapterRelease: { args: [FFIType.ptr], returns: FFIType.void },
134
+ wgpuDeviceRelease: { args: [FFIType.ptr], returns: FFIType.void },
135
+ wgpuDeviceGetQueue: { args: [FFIType.ptr], returns: FFIType.ptr },
136
+
137
+ // Buffer
138
+ wgpuDeviceCreateBuffer: { args: [FFIType.ptr, FFIType.ptr], returns: FFIType.ptr },
139
+ wgpuBufferRelease: { args: [FFIType.ptr], returns: FFIType.void },
140
+ wgpuBufferUnmap: { args: [FFIType.ptr], returns: FFIType.void },
141
+ wgpuBufferGetConstMappedRange: { args: [FFIType.ptr, FFIType.u64, FFIType.u64], returns: FFIType.ptr },
142
+ wgpuBufferGetMappedRange: { args: [FFIType.ptr, FFIType.u64, FFIType.u64], returns: FFIType.ptr },
143
+ doeBufferMapAsyncFlat: { args: [FFIType.ptr, FFIType.u64, FFIType.u64, FFIType.u64, FFIType.u32, FFIType.ptr, FFIType.ptr, FFIType.ptr], returns: FFIType.u64 },
144
+ doeBufferMapSyncFlat: { args: [FFIType.ptr, FFIType.ptr, FFIType.u64, FFIType.u64, FFIType.u64], returns: FFIType.u32 },
145
+
146
+ // Queue
147
+ wgpuQueueSubmit: { args: [FFIType.ptr, FFIType.u64, FFIType.ptr], returns: FFIType.void },
148
+ wgpuQueueWriteBuffer: { args: [FFIType.ptr, FFIType.ptr, FFIType.u64, FFIType.ptr, FFIType.u64], returns: FFIType.void },
149
+ wgpuQueueRelease: { args: [FFIType.ptr], returns: FFIType.void },
150
+ doeQueueOnSubmittedWorkDoneFlat: { args: [FFIType.ptr, FFIType.u32, FFIType.ptr, FFIType.ptr, FFIType.ptr], returns: FFIType.u64 },
151
+
152
+ // Shader
153
+ wgpuDeviceCreateShaderModule: { args: [FFIType.ptr, FFIType.ptr], returns: FFIType.ptr },
154
+ wgpuShaderModuleRelease: { args: [FFIType.ptr], returns: FFIType.void },
155
+
156
+ // Compute pipeline
157
+ wgpuDeviceCreateComputePipeline: { args: [FFIType.ptr, FFIType.ptr], returns: FFIType.ptr },
158
+ wgpuComputePipelineRelease: { args: [FFIType.ptr], returns: FFIType.void },
159
+ wgpuComputePipelineGetBindGroupLayout: { args: [FFIType.ptr, FFIType.u32], returns: FFIType.ptr },
160
+
161
+ // Bind group layout / bind group / pipeline layout
162
+ wgpuDeviceCreateBindGroupLayout: { args: [FFIType.ptr, FFIType.ptr], returns: FFIType.ptr },
163
+ wgpuBindGroupLayoutRelease: { args: [FFIType.ptr], returns: FFIType.void },
164
+ wgpuDeviceCreateBindGroup: { args: [FFIType.ptr, FFIType.ptr], returns: FFIType.ptr },
165
+ wgpuBindGroupRelease: { args: [FFIType.ptr], returns: FFIType.void },
166
+ wgpuDeviceCreatePipelineLayout: { args: [FFIType.ptr, FFIType.ptr], returns: FFIType.ptr },
167
+ wgpuPipelineLayoutRelease: { args: [FFIType.ptr], returns: FFIType.void },
168
+
169
+ // Command encoder
170
+ wgpuDeviceCreateCommandEncoder: { args: [FFIType.ptr, FFIType.ptr], returns: FFIType.ptr },
171
+ wgpuCommandEncoderRelease: { args: [FFIType.ptr], returns: FFIType.void },
172
+ wgpuCommandEncoderBeginComputePass: { args: [FFIType.ptr, FFIType.ptr], returns: FFIType.ptr },
173
+ wgpuCommandEncoderCopyBufferToBuffer: { args: [FFIType.ptr, FFIType.ptr, FFIType.u64, FFIType.ptr, FFIType.u64, FFIType.u64], returns: FFIType.void },
174
+ wgpuCommandEncoderFinish: { args: [FFIType.ptr, FFIType.ptr], returns: FFIType.ptr },
175
+ wgpuCommandBufferRelease: { args: [FFIType.ptr], returns: FFIType.void },
176
+
177
+ // Compute pass
178
+ wgpuComputePassEncoderSetPipeline: { args: [FFIType.ptr, FFIType.ptr], returns: FFIType.void },
179
+ wgpuComputePassEncoderSetBindGroup: { args: [FFIType.ptr, FFIType.u32, FFIType.ptr, FFIType.u64, FFIType.ptr], returns: FFIType.void },
180
+ wgpuComputePassEncoderDispatchWorkgroups: { args: [FFIType.ptr, FFIType.u32, FFIType.u32, FFIType.u32], returns: FFIType.void },
181
+ wgpuComputePassEncoderDispatchWorkgroupsIndirect: { args: [FFIType.ptr, FFIType.ptr, FFIType.u64], returns: FFIType.void },
182
+ wgpuComputePassEncoderEnd: { args: [FFIType.ptr], returns: FFIType.void },
183
+ wgpuComputePassEncoderRelease: { args: [FFIType.ptr], returns: FFIType.void },
184
+
185
+ // Texture
186
+ wgpuDeviceCreateTexture: { args: [FFIType.ptr, FFIType.ptr], returns: FFIType.ptr },
187
+ wgpuTextureCreateView: { args: [FFIType.ptr, FFIType.ptr], returns: FFIType.ptr },
188
+ wgpuTextureRelease: { args: [FFIType.ptr], returns: FFIType.void },
189
+ wgpuTextureViewRelease: { args: [FFIType.ptr], returns: FFIType.void },
190
+
191
+ // Sampler
192
+ wgpuDeviceCreateSampler: { args: [FFIType.ptr, FFIType.ptr], returns: FFIType.ptr },
193
+ wgpuSamplerRelease: { args: [FFIType.ptr], returns: FFIType.void },
194
+
195
+ // Render pipeline
196
+ wgpuDeviceCreateRenderPipeline: { args: [FFIType.ptr, FFIType.ptr], returns: FFIType.ptr },
197
+ wgpuRenderPipelineRelease: { args: [FFIType.ptr], returns: FFIType.void },
198
+
199
+ // Render pass
200
+ wgpuCommandEncoderBeginRenderPass: { args: [FFIType.ptr, FFIType.ptr], returns: FFIType.ptr },
201
+ wgpuRenderPassEncoderSetPipeline: { args: [FFIType.ptr, FFIType.ptr], returns: FFIType.void },
202
+ wgpuRenderPassEncoderDraw: { args: [FFIType.ptr, FFIType.u32, FFIType.u32, FFIType.u32, FFIType.u32], returns: FFIType.void },
203
+ wgpuRenderPassEncoderEnd: { args: [FFIType.ptr], returns: FFIType.void },
204
+ wgpuRenderPassEncoderRelease: { args: [FFIType.ptr], returns: FFIType.void },
205
+ });
206
+ }
207
+
208
+ // ---------------------------------------------------------------------------
209
+ // Struct marshaling helpers
210
+ //
211
+ // All layouts assume 64-bit LP64/LLP64: ptr=8, size_t=8, u64=8, u32=4.
212
+ // WGPUStringView = { data: ptr@0, length: size_t@8 } = 16 bytes.
213
+ // ---------------------------------------------------------------------------
214
+
215
+ const encoder = new TextEncoder();
216
+
217
+ function writeStringView(view, offset, strBytes) {
218
+ if (strBytes) {
219
+ view.setBigUint64(offset, BigInt(bunPtr(strBytes)), true);
220
+ view.setBigUint64(offset + 8, BigInt(strBytes.length), true);
221
+ } else {
222
+ view.setBigUint64(offset, 0n, true);
223
+ view.setBigUint64(offset + 8, 0n, true);
224
+ }
225
+ }
226
+
227
+ function writePtr(view, offset, ptr) {
228
+ view.setBigUint64(offset, ptr ? BigInt(ptr) : 0n, true);
229
+ }
230
+
231
+ // WGPUBufferDescriptor: { nextInChain:ptr@0, label:sv@8, usage:u64@24, size:u64@32, mappedAtCreation:u32@40 } = 48
232
+ function buildBufferDescriptor(descriptor) {
233
+ const buf = new ArrayBuffer(48);
234
+ const v = new DataView(buf);
235
+ // nextInChain = null
236
+ writePtr(v, 0, null);
237
+ // label = empty
238
+ writeStringView(v, 8, null);
239
+ // usage
240
+ v.setBigUint64(24, BigInt(descriptor.usage || 0), true);
241
+ // size
242
+ v.setBigUint64(32, BigInt(descriptor.size || 0), true);
243
+ // mappedAtCreation
244
+ v.setUint32(40, descriptor.mappedAtCreation ? 1 : 0, true);
245
+ return new Uint8Array(buf);
246
+ }
247
+
248
+ // WGPUShaderSourceWGSL: { chain:{next:ptr@0, sType:u32@8, pad@12}, code:sv@16 } = 32
249
+ // WGPUShaderModuleDescriptor: { nextInChain:ptr@0, label:sv@8 } = 24
250
+ function buildShaderModuleDescriptor(code) {
251
+ const codeBytes = encoder.encode(code);
252
+
253
+ const wgslBuf = new ArrayBuffer(32);
254
+ const wgslView = new DataView(wgslBuf);
255
+ writePtr(wgslView, 0, null);
256
+ wgslView.setUint32(8, STYPE_SHADER_SOURCE_WGSL, true);
257
+ writeStringView(wgslView, 16, codeBytes);
258
+ const wgslArr = new Uint8Array(wgslBuf);
259
+
260
+ const descBuf = new ArrayBuffer(24);
261
+ const descView = new DataView(descBuf);
262
+ writePtr(descView, 0, bunPtr(wgslArr));
263
+ writeStringView(descView, 8, null);
264
+
265
+ return { desc: new Uint8Array(descBuf), _refs: [codeBytes, wgslArr] };
266
+ }
267
+
268
+ // WGPUComputePipelineDescriptor:
269
+ // { nextInChain:ptr@0, label:sv@8, layout:ptr@24, compute:WGPUProgrammableStageDescriptor@32 }
270
+ // WGPUProgrammableStageDescriptor: { nextInChain:ptr@0, module:ptr@8, entryPoint:sv@16, constantCount:size_t@32, constants:ptr@40 } = 48
271
+ // Total descriptor: 24 + 8 (layout) + 48 (compute) = 80
272
+ function buildComputePipelineDescriptor(shaderModulePtr, entryPoint, layoutPtr) {
273
+ const epBytes = encoder.encode(entryPoint);
274
+ const buf = new ArrayBuffer(80);
275
+ const v = new DataView(buf);
276
+ // nextInChain
277
+ writePtr(v, 0, null);
278
+ // label
279
+ writeStringView(v, 8, null);
280
+ // layout
281
+ writePtr(v, 24, layoutPtr);
282
+ // compute.nextInChain
283
+ writePtr(v, 32, null);
284
+ // compute.module
285
+ writePtr(v, 40, shaderModulePtr);
286
+ // compute.entryPoint
287
+ writeStringView(v, 48, epBytes);
288
+ // compute.constantCount
289
+ v.setBigUint64(64, 0n, true);
290
+ // compute.constants
291
+ writePtr(v, 72, null);
292
+ return { desc: new Uint8Array(buf), _refs: [epBytes] };
293
+ }
294
+
295
+ // WGPUBindGroupLayoutEntry: { nextInChain:ptr@0, binding:u32@8, visibility:u64@12(actually u32@12 + pad), ...complex }
296
+ // The full entry is large. We build a minimal version matching what doe_napi.c marshals.
297
+ // For simplicity, we build the entry array matching the C struct layout.
298
+ //
299
+ // WGPUBindGroupLayoutEntry (simplified layout for buffer-only bindings):
300
+ // { nextInChain:ptr@0, binding:u32@8, pad@12, visibility:u64@16,
301
+ // buffer:{nextInChain:ptr@24, type:u32@32, pad@36, hasDynamicOffset:u32@40, pad@44, minBindingSize:u64@48},
302
+ // sampler:{...@56}, texture:{...}, storageTexture:{...} }
303
+ // Full size per entry: 128 bytes (varies by ABI — we use a generous allocation)
304
+ //
305
+ // NOTE: The exact layout is ABI-dependent. We use the wgpu.h canonical layout.
306
+ // BindGroupLayoutEntry total = 136 bytes on 64-bit.
307
+ const BIND_GROUP_LAYOUT_ENTRY_SIZE = 120;
308
+
309
+ function buildBindGroupLayoutDescriptor(entries) {
310
+ const entryBufs = new Uint8Array(entries.length * BIND_GROUP_LAYOUT_ENTRY_SIZE);
311
+ const entryView = new DataView(entryBufs.buffer);
312
+
313
+ for (let i = 0; i < entries.length; i++) {
314
+ const e = entries[i];
315
+ const off = i * BIND_GROUP_LAYOUT_ENTRY_SIZE;
316
+ // nextInChain: ptr@0
317
+ writePtr(entryView, off + 0, null);
318
+ // binding: u32@8
319
+ entryView.setUint32(off + 8, e.binding, true);
320
+ // visibility: u64@16 (WGPUShaderStageFlags, after 4-byte pad at @12)
321
+ entryView.setBigUint64(off + 16, BigInt(e.visibility || 0), true);
322
+ // bindingArraySize: u32@24
323
+ entryView.setUint32(off + 24, 0, true);
324
+ // buffer sub-struct starts at @32:
325
+ // buffer.nextInChain: ptr@32
326
+ writePtr(entryView, off + 32, null);
327
+ if (e.buffer) {
328
+ // WGPUBufferBindingType: Undefined=1, Uniform=2, Storage=3, ReadOnlyStorage=4
329
+ const typeMap = { uniform: 2, storage: 3, "read-only-storage": 4 };
330
+ // buffer.type: u32@40
331
+ entryView.setUint32(off + 40, typeMap[e.buffer.type || "uniform"] || 2, true);
332
+ // buffer.hasDynamicOffset: u32@44
333
+ entryView.setUint32(off + 44, e.buffer.hasDynamicOffset ? 1 : 0, true);
334
+ // buffer.minBindingSize: u64@48
335
+ entryView.setBigUint64(off + 48, BigInt(e.buffer.minBindingSize || 0), true);
336
+ }
337
+ // sampler/texture/storageTexture sub-structs (@56..120) remain zeroed
338
+ }
339
+
340
+ // WGPUBindGroupLayoutDescriptor: { nextInChain:ptr@0, label:sv@8, entryCount:size_t@24, entries:ptr@32 } = 40
341
+ const descBuf = new ArrayBuffer(40);
342
+ const descView = new DataView(descBuf);
343
+ writePtr(descView, 0, null);
344
+ writeStringView(descView, 8, null);
345
+ descView.setBigUint64(24, BigInt(entries.length), true);
346
+ writePtr(descView, 32, entries.length > 0 ? bunPtr(entryBufs) : null);
347
+
348
+ return { desc: new Uint8Array(descBuf), _refs: [entryBufs] };
349
+ }
350
+
351
+ // WGPUBindGroupEntry: { nextInChain:ptr@0, binding:u32@8, pad@12, buffer:ptr@16, offset:u64@24, size:u64@32,
352
+ // sampler:ptr@40, textureView:ptr@48 } = 56
353
+ const BIND_GROUP_ENTRY_SIZE = 56;
354
+ const WHOLE_SIZE = 0xFFFFFFFFFFFFFFFFn;
355
+
356
+ function buildBindGroupDescriptor(layoutPtr, entries) {
357
+ const entryBufs = new Uint8Array(entries.length * BIND_GROUP_ENTRY_SIZE);
358
+ const entryView = new DataView(entryBufs.buffer);
359
+
360
+ for (let i = 0; i < entries.length; i++) {
361
+ const e = entries[i];
362
+ const off = i * BIND_GROUP_ENTRY_SIZE;
363
+ writePtr(entryView, off + 0, null);
364
+ entryView.setUint32(off + 8, e.binding, true);
365
+ const bufferPtr = e.resource?.buffer?._native ?? e.resource?._native ?? null;
366
+ writePtr(entryView, off + 16, bufferPtr);
367
+ entryView.setBigUint64(off + 24, BigInt(e.resource?.offset ?? 0), true);
368
+ entryView.setBigUint64(off + 32, e.resource?.size !== undefined ? BigInt(e.resource.size) : WHOLE_SIZE, true);
369
+ writePtr(entryView, off + 40, null); // sampler
370
+ writePtr(entryView, off + 48, null); // textureView
371
+ }
372
+
373
+ // WGPUBindGroupDescriptor: { nextInChain:ptr@0, label:sv@8, layout:ptr@24, entryCount:size_t@32, entries:ptr@40 } = 48
374
+ const descBuf = new ArrayBuffer(48);
375
+ const descView = new DataView(descBuf);
376
+ writePtr(descView, 0, null);
377
+ writeStringView(descView, 8, null);
378
+ writePtr(descView, 24, layoutPtr);
379
+ descView.setBigUint64(32, BigInt(entries.length), true);
380
+ writePtr(descView, 40, entries.length > 0 ? bunPtr(entryBufs) : null);
381
+
382
+ return { desc: new Uint8Array(descBuf), _refs: [entryBufs] };
383
+ }
384
+
385
+ // WGPUPipelineLayoutDescriptor: { nextInChain:ptr@0, label:sv@8, bindGroupLayoutCount:size_t@24,
386
+ // bindGroupLayouts:ptr@32, immediateSize:u32@40, pad@44 } = 48
387
+ function buildPipelineLayoutDescriptor(layouts) {
388
+ const ptrs = new BigUint64Array(layouts.length);
389
+ for (let i = 0; i < layouts.length; i++) {
390
+ ptrs[i] = BigInt(layouts[i]);
391
+ }
392
+
393
+ const descBuf = new ArrayBuffer(48);
394
+ const descView = new DataView(descBuf);
395
+ writePtr(descView, 0, null);
396
+ writeStringView(descView, 8, null);
397
+ descView.setBigUint64(24, BigInt(layouts.length), true);
398
+ writePtr(descView, 32, layouts.length > 0 ? bunPtr(ptrs) : null);
399
+ descView.setUint32(40, 0, true); // immediateSize
400
+
401
+ return { desc: new Uint8Array(descBuf), _refs: [ptrs] };
402
+ }
403
+
404
+ // WGPUTextureDescriptor: { nextInChain:ptr@0, label:sv@8, usage:u64@24, dimension:u32@32,
405
+ // size:{width:u32@36, height:u32@40, depthOrArrayLayers:u32@44}, format:u32@48,
406
+ // mipLevelCount:u32@52, sampleCount:u32@56, viewFormatCount:size_t@60(pad to 64), viewFormats:ptr@72 }
407
+ // Actual: nextInChain@0(8) label@8(16) usage@24(8) dimension@32(4) pad@36(4)
408
+ // size@40 {w:u32@40 h:u32@44 d:u32@48} pad@52(4) format@56(4) mipLevelCount@60(4) sampleCount@64(4)
409
+ // viewFormatCount@68(8) viewFormats@76(8) = 84 → round to 88
410
+ // NOTE: Exact layout depends on struct packing. Let me match the C definition carefully.
411
+ // From doe_napi.c:
412
+ // { nextInChain:ptr, label:WGPUStringView, usage:u64, dimension:u32, size:WGPUExtent3D, format:u32,
413
+ // mipLevelCount:u32, sampleCount:u32, viewFormatCount:size_t, viewFormats:ptr }
414
+ // WGPUExtent3D = { width:u32, height:u32, depthOrArrayLayers:u32 } = 12 bytes
415
+ //
416
+ // Layout (64-bit, packed):
417
+ // nextInChain: ptr@0 (8)
418
+ // label.data: ptr@8 (8)
419
+ // label.length: size_t@16 (8)
420
+ // usage: u64@24 (8)
421
+ // dimension: u32@32 (4)
422
+ // size.width: u32@36 (4) ← follows u32, natural alignment
423
+ // size.height: u32@40 (4)
424
+ // size.depthOrArrayLayers: u32@44 (4)
425
+ // format: u32@48 (4)
426
+ // mipLevelCount: u32@52 (4)
427
+ // sampleCount: u32@56 (4)
428
+ // pad: 4@60
429
+ // viewFormatCount: size_t@64 (8)
430
+ // viewFormats: ptr@72 (8)
431
+ // Total: 80
432
+ const TEXTURE_DESC_SIZE = 80;
433
+
434
+ const TEXTURE_FORMAT_MAP = {
435
+ rgba8unorm: 18, "rgba8unorm-srgb": 19, bgra8unorm: 23, "bgra8unorm-srgb": 24,
436
+ r32float: 33, rg32float: 43, rgba32float: 52, depth32float: 55,
437
+ };
438
+
439
+ function buildTextureDescriptor(descriptor) {
440
+ const buf = new ArrayBuffer(TEXTURE_DESC_SIZE);
441
+ const v = new DataView(buf);
442
+ writePtr(v, 0, null);
443
+ writeStringView(v, 8, null);
444
+ v.setBigUint64(24, BigInt(descriptor.usage || 0), true);
445
+ v.setUint32(32, 1, true); // dimension = 2D (WGPUTextureDimension_2D = 1... actually 0x00000002)
446
+ // WGPUTextureDimension: 1D=1, 2D=2, 3D=3 in standard. Let's use 2.
447
+ v.setUint32(32, 2, true);
448
+ const w = descriptor.size?.[0] ?? descriptor.size?.width ?? descriptor.size ?? 1;
449
+ const h = descriptor.size?.[1] ?? descriptor.size?.height ?? 1;
450
+ const d = descriptor.size?.[2] ?? descriptor.size?.depthOrArrayLayers ?? 1;
451
+ v.setUint32(36, w, true);
452
+ v.setUint32(40, h, true);
453
+ v.setUint32(44, d, true);
454
+ const fmt = descriptor.format || "rgba8unorm";
455
+ v.setUint32(48, TEXTURE_FORMAT_MAP[fmt] ?? 18, true);
456
+ v.setUint32(52, descriptor.mipLevelCount || 1, true);
457
+ v.setUint32(56, 1, true); // sampleCount
458
+ v.setBigUint64(64, 0n, true); // viewFormatCount
459
+ writePtr(v, 72, null); // viewFormats
460
+ return new Uint8Array(buf);
461
+ }
462
+
463
+ // WGPUSamplerDescriptor: { nextInChain:ptr@0, label:sv@8, addressModeU:u32@24, V:u32@28, W:u32@32,
464
+ // magFilter:u32@36, minFilter:u32@40, mipmapFilter:u32@44, lodMinClamp:f32@48, lodMaxClamp:f32@52,
465
+ // compare:u32@56, maxAnisotropy:u16@60 } = 64 (with padding)
466
+ const SAMPLER_DESC_SIZE = 64;
467
+
468
+ function buildSamplerDescriptor(descriptor) {
469
+ const buf = new ArrayBuffer(SAMPLER_DESC_SIZE);
470
+ const v = new DataView(buf);
471
+ writePtr(v, 0, null);
472
+ writeStringView(v, 8, null);
473
+ // defaults: clamp-to-edge=2, nearest=0
474
+ v.setUint32(24, 2, true); // addressModeU
475
+ v.setUint32(28, 2, true); // addressModeV
476
+ v.setUint32(32, 2, true); // addressModeW
477
+ v.setUint32(36, 0, true); // magFilter = nearest
478
+ v.setUint32(40, 0, true); // minFilter = nearest
479
+ v.setUint32(44, 0, true); // mipmapFilter = nearest
480
+ v.setFloat32(48, 0.0, true);
481
+ v.setFloat32(52, 32.0, true);
482
+ v.setUint32(56, 0, true); // compare = undefined
483
+ v.setUint16(60, 1, true); // maxAnisotropy
484
+ return new Uint8Array(buf);
485
+ }
486
+
487
+ // WGPURenderPassColorAttachment:
488
+ // { nextInChain:ptr@0, view:ptr@8, depthSlice:u32@16, pad@20, resolveTarget:ptr@24,
489
+ // loadOp:u32@32, storeOp:u32@36, clearValue:{r:f64@40, g:f64@48, b:f64@56, a:f64@64} } = 72
490
+ const RENDER_PASS_COLOR_ATTACHMENT_SIZE = 72;
491
+
492
+ // WGPURenderPassDescriptor:
493
+ // { nextInChain:ptr@0, label:sv@8, colorAttachmentCount:size_t@24, colorAttachments:ptr@32,
494
+ // depthStencilAttachment:ptr@40, occlusionQuerySet:ptr@48, timestampWrites:ptr@56 } = 64
495
+ function buildRenderPassDescriptor(descriptor) {
496
+ const colorAttachments = descriptor.colorAttachments || [];
497
+ const attBuf = new Uint8Array(colorAttachments.length * RENDER_PASS_COLOR_ATTACHMENT_SIZE);
498
+ const attView = new DataView(attBuf.buffer);
499
+
500
+ for (let i = 0; i < colorAttachments.length; i++) {
501
+ const a = colorAttachments[i];
502
+ const off = i * RENDER_PASS_COLOR_ATTACHMENT_SIZE;
503
+ writePtr(attView, off + 0, null);
504
+ writePtr(attView, off + 8, a.view._native);
505
+ attView.setUint32(off + 16, 0xFFFFFFFF, true); // depthSlice = WGPU_DEPTH_SLICE_UNDEFINED
506
+ writePtr(attView, off + 24, null); // resolveTarget
507
+ attView.setUint32(off + 32, 1, true); // loadOp = clear (1)
508
+ attView.setUint32(off + 36, 1, true); // storeOp = store (1)
509
+ const cv = a.clearValue || { r: 0, g: 0, b: 0, a: 1 };
510
+ attView.setFloat64(off + 40, cv.r ?? 0, true);
511
+ attView.setFloat64(off + 48, cv.g ?? 0, true);
512
+ attView.setFloat64(off + 56, cv.b ?? 0, true);
513
+ attView.setFloat64(off + 64, cv.a ?? 1, true);
514
+ }
515
+
516
+ const descBuf = new ArrayBuffer(64);
517
+ const descView = new DataView(descBuf);
518
+ writePtr(descView, 0, null);
519
+ writeStringView(descView, 8, null);
520
+ descView.setBigUint64(24, BigInt(colorAttachments.length), true);
521
+ writePtr(descView, 32, colorAttachments.length > 0 ? bunPtr(attBuf) : null);
522
+ writePtr(descView, 40, null); // depthStencilAttachment
523
+ writePtr(descView, 48, null); // occlusionQuerySet
524
+ writePtr(descView, 56, null); // timestampWrites
525
+
526
+ return { desc: new Uint8Array(descBuf), _refs: [attBuf] };
527
+ }
528
+
529
+ // ---------------------------------------------------------------------------
530
+ // Callback trampolines for async-to-sync bridging
531
+ //
532
+ // Uses CALLBACK_MODE_ALLOW_PROCESS_EVENTS + wgpuInstanceProcessEvents polling,
533
+ // matching the N-API addon strategy. wgpuInstanceWaitAny with timed waits is
534
+ // not supported on all backends (e.g. Vulkan/Dawn).
535
+ // ---------------------------------------------------------------------------
536
+
537
+ function processEventsUntilDone(instancePtr, isDone, timeoutNs = PROCESS_EVENTS_TIMEOUT_NS) {
538
+ const start = Number(process.hrtime.bigint());
539
+ while (!isDone()) {
540
+ wgpu.symbols.wgpuInstanceProcessEvents(instancePtr);
541
+ if (Number(process.hrtime.bigint()) - start >= timeoutNs) {
542
+ throw new Error("[fawn-webgpu] processEvents timeout");
543
+ }
544
+ }
545
+ }
546
+
547
+ function requestAdapterSync(instancePtr) {
548
+ let resolvedAdapter = null;
549
+ let resolvedStatus = null;
550
+ let done = false;
551
+ const cb = new JSCallback(
552
+ (status, adapter, _msgData, _msgLen, _ud1, _ud2) => {
553
+ resolvedStatus = status;
554
+ resolvedAdapter = adapter;
555
+ done = true;
556
+ },
557
+ { args: [FFIType.u32, FFIType.ptr, FFIType.ptr, FFIType.u64, FFIType.ptr, FFIType.ptr], returns: FFIType.void },
558
+ );
559
+ try {
560
+ const futureId = wgpu.symbols.doeRequestAdapterFlat(
561
+ instancePtr, null, CALLBACK_MODE_ALLOW_PROCESS_EVENTS, cb.ptr, null, null);
562
+ if (futureId === 0 || futureId === 0n) throw new Error("[fawn-webgpu] requestAdapter future unavailable");
563
+ processEventsUntilDone(instancePtr, () => done);
564
+ if (resolvedStatus !== REQUEST_ADAPTER_STATUS_SUCCESS || !resolvedAdapter) {
565
+ throw new Error(`[fawn-webgpu] requestAdapter failed (status=${resolvedStatus})`);
566
+ }
567
+ return resolvedAdapter;
568
+ } finally {
569
+ cb.close();
570
+ }
571
+ }
572
+
573
+ function requestDeviceSync(instancePtr, adapterPtr) {
574
+ let resolvedDevice = null;
575
+ let resolvedStatus = null;
576
+ let done = false;
577
+ const cb = new JSCallback(
578
+ (status, device, _msgData, _msgLen, _ud1, _ud2) => {
579
+ resolvedStatus = status;
580
+ resolvedDevice = device;
581
+ done = true;
582
+ },
583
+ { args: [FFIType.u32, FFIType.ptr, FFIType.ptr, FFIType.u64, FFIType.ptr, FFIType.ptr], returns: FFIType.void },
584
+ );
585
+ try {
586
+ const futureId = wgpu.symbols.doeRequestDeviceFlat(
587
+ adapterPtr, null, CALLBACK_MODE_ALLOW_PROCESS_EVENTS, cb.ptr, null, null);
588
+ if (futureId === 0 || futureId === 0n) throw new Error("[fawn-webgpu] requestDevice future unavailable");
589
+ processEventsUntilDone(instancePtr, () => done);
590
+ if (resolvedStatus !== REQUEST_DEVICE_STATUS_SUCCESS || !resolvedDevice) {
591
+ throw new Error(`[fawn-webgpu] requestDevice failed (status=${resolvedStatus})`);
592
+ }
593
+ return resolvedDevice;
594
+ } finally {
595
+ cb.close();
596
+ }
597
+ }
598
+
599
+ function bufferMapSync(instancePtr, bufferPtr, mode, offset, size) {
600
+ if (wgpu.symbols.doeBufferMapSyncFlat) {
601
+ const status = wgpu.symbols.doeBufferMapSyncFlat(
602
+ instancePtr, bufferPtr, BigInt(mode), BigInt(offset), BigInt(size));
603
+ if (status !== MAP_ASYNC_STATUS_SUCCESS) {
604
+ throw new Error(`[fawn-webgpu] bufferMapAsync failed (status=${status})`);
605
+ }
606
+ return;
607
+ }
608
+ let mapStatus = null;
609
+ let done = false;
610
+ const cb = new JSCallback(
611
+ (status, _msgData, _msgLen, _ud1, _ud2) => { mapStatus = status; done = true; },
612
+ { args: [FFIType.u32, FFIType.ptr, FFIType.u64, FFIType.ptr, FFIType.ptr], returns: FFIType.void },
613
+ );
614
+ try {
615
+ const futureId = wgpu.symbols.doeBufferMapAsyncFlat(
616
+ bufferPtr, BigInt(mode), BigInt(offset), BigInt(size),
617
+ CALLBACK_MODE_ALLOW_PROCESS_EVENTS, cb.ptr, null, null);
618
+ if (futureId === 0 || futureId === 0n) throw new Error("[fawn-webgpu] bufferMapAsync future unavailable");
619
+ processEventsUntilDone(instancePtr, () => done);
620
+ if (mapStatus !== MAP_ASYNC_STATUS_SUCCESS) {
621
+ throw new Error(`[fawn-webgpu] bufferMapAsync failed (status=${mapStatus})`);
622
+ }
623
+ } finally {
624
+ cb.close();
625
+ }
626
+ }
627
+
628
+ // ---------------------------------------------------------------------------
629
+ // WebGPU wrapper classes — matches index.js surface exactly
630
+ // ---------------------------------------------------------------------------
631
+
632
+ class DoeGPUBuffer {
633
+ constructor(native, instance, size, usage, queue) {
634
+ this._native = native;
635
+ this._instance = instance;
636
+ this._queue = queue;
637
+ this.size = size;
638
+ this.usage = usage;
639
+ }
640
+
641
+ async mapAsync(mode, offset = 0, size = this.size) {
642
+ bufferMapSync(this._instance, this._native, mode, offset, size);
643
+ this._mapMode = mode;
644
+ }
645
+
646
+ getMappedRange(offset = 0, size = this.size) {
647
+ const isWrite = (this._mapMode & 0x0002) !== 0;
648
+ if (isWrite) {
649
+ const dataPtr = wgpu.symbols.wgpuBufferGetMappedRange(this._native, BigInt(offset), BigInt(size));
650
+ if (!dataPtr) throw new Error("[fawn-webgpu] getMappedRange (write) returned NULL");
651
+ return toArrayBuffer(dataPtr, 0, size);
652
+ }
653
+ const dataPtr = wgpu.symbols.wgpuBufferGetConstMappedRange(this._native, BigInt(offset), BigInt(size));
654
+ if (!dataPtr) throw new Error("[fawn-webgpu] getMappedRange returned NULL");
655
+ if (DOE_LIBRARY_FLAVOR === "doe-dropin") {
656
+ return toArrayBuffer(dataPtr, 0, size);
657
+ }
658
+ const nativeView = toArrayBuffer(dataPtr, 0, size);
659
+ const copy = new ArrayBuffer(size);
660
+ new Uint8Array(copy).set(new Uint8Array(nativeView));
661
+ return copy;
662
+ }
663
+
664
+ unmap() {
665
+ wgpu.symbols.wgpuBufferUnmap(this._native);
666
+ this._mapMode = 0;
667
+ }
668
+
669
+ destroy() {
670
+ wgpu.symbols.wgpuBufferRelease(this._native);
671
+ this._native = null;
672
+ }
673
+ }
674
+
675
+ class DoeGPUComputePassEncoder {
676
+ constructor(native) { this._native = native; }
677
+
678
+ setPipeline(pipeline) {
679
+ wgpu.symbols.wgpuComputePassEncoderSetPipeline(this._native, pipeline._native);
680
+ }
681
+
682
+ setBindGroup(index, bindGroup) {
683
+ wgpu.symbols.wgpuComputePassEncoderSetBindGroup(this._native, index, bindGroup._native, BigInt(0), null);
684
+ }
685
+
686
+ dispatchWorkgroups(x, y = 1, z = 1) {
687
+ wgpu.symbols.wgpuComputePassEncoderDispatchWorkgroups(this._native, x, y, z);
688
+ }
689
+
690
+ dispatchWorkgroupsIndirect(indirectBuffer, indirectOffset = 0) {
691
+ wgpu.symbols.wgpuComputePassEncoderDispatchWorkgroupsIndirect(this._native, indirectBuffer._native, BigInt(indirectOffset));
692
+ }
693
+
694
+ end() {
695
+ wgpu.symbols.wgpuComputePassEncoderEnd(this._native);
696
+ }
697
+ }
698
+
699
+ class DoeGPUCommandEncoder {
700
+ constructor(native) { this._native = native; }
701
+
702
+ beginComputePass(_descriptor) {
703
+ const pass = wgpu.symbols.wgpuCommandEncoderBeginComputePass(this._native, null);
704
+ return new DoeGPUComputePassEncoder(pass);
705
+ }
706
+
707
+ beginRenderPass(descriptor) {
708
+ const { desc, _refs } = buildRenderPassDescriptor(descriptor);
709
+ const pass = wgpu.symbols.wgpuCommandEncoderBeginRenderPass(this._native, desc);
710
+ void _refs;
711
+ return new DoeGPURenderPassEncoder(pass);
712
+ }
713
+
714
+ copyBufferToBuffer(src, srcOffset, dst, dstOffset, size) {
715
+ wgpu.symbols.wgpuCommandEncoderCopyBufferToBuffer(
716
+ this._native, src._native, BigInt(srcOffset), dst._native, BigInt(dstOffset), BigInt(size));
717
+ }
718
+
719
+ finish() {
720
+ const cmd = wgpu.symbols.wgpuCommandEncoderFinish(this._native, null);
721
+ return { _native: cmd };
722
+ }
723
+ }
724
+
725
+ class DoeGPUQueue {
726
+ constructor(native, instance) {
727
+ this._native = native;
728
+ this._instance = instance;
729
+ }
730
+
731
+ submit(commandBuffers) {
732
+ const ptrs = new BigUint64Array(commandBuffers.length);
733
+ for (let i = 0; i < commandBuffers.length; i++) {
734
+ ptrs[i] = BigInt(commandBuffers[i]._native);
735
+ }
736
+ wgpu.symbols.wgpuQueueSubmit(this._native, BigInt(commandBuffers.length), ptrs);
737
+ }
738
+
739
+ writeBuffer(buffer, bufferOffset, data, dataOffset = 0, size) {
740
+ let view = data;
741
+ if (dataOffset > 0 || size !== undefined) {
742
+ const byteOffset = data.byteOffset + dataOffset * (data.BYTES_PER_ELEMENT || 1);
743
+ const byteLength = size !== undefined
744
+ ? size * (data.BYTES_PER_ELEMENT || 1)
745
+ : data.byteLength - dataOffset * (data.BYTES_PER_ELEMENT || 1);
746
+ view = new Uint8Array(data.buffer, byteOffset, byteLength);
747
+ }
748
+ wgpu.symbols.wgpuQueueWriteBuffer(this._native, buffer._native, BigInt(bufferOffset), view, BigInt(view.byteLength));
749
+ }
750
+
751
+ async onSubmittedWorkDone() {
752
+ // Match the Node provider contract: Doe submit commits synchronously,
753
+ // and mapAsync flushes when readback synchronization is required.
754
+ }
755
+ }
756
+
757
+ class DoeGPURenderPassEncoder {
758
+ constructor(native) { this._native = native; }
759
+
760
+ setPipeline(pipeline) {
761
+ wgpu.symbols.wgpuRenderPassEncoderSetPipeline(this._native, pipeline._native);
762
+ }
763
+
764
+ draw(vertexCount, instanceCount = 1, firstVertex = 0, firstInstance = 0) {
765
+ wgpu.symbols.wgpuRenderPassEncoderDraw(this._native, vertexCount, instanceCount, firstVertex, firstInstance);
766
+ }
767
+
768
+ end() {
769
+ wgpu.symbols.wgpuRenderPassEncoderEnd(this._native);
770
+ }
771
+ }
772
+
773
+ class DoeGPUTexture {
774
+ constructor(native) { this._native = native; }
775
+
776
+ createView(_descriptor) {
777
+ const view = wgpu.symbols.wgpuTextureCreateView(this._native, null);
778
+ return new DoeGPUTextureView(view);
779
+ }
780
+
781
+ destroy() {
782
+ wgpu.symbols.wgpuTextureRelease(this._native);
783
+ this._native = null;
784
+ }
785
+ }
786
+
787
+ class DoeGPUTextureView {
788
+ constructor(native) { this._native = native; }
789
+ }
790
+
791
+ class DoeGPUSampler {
792
+ constructor(native) { this._native = native; }
793
+ }
794
+
795
+ class DoeGPURenderPipeline {
796
+ constructor(native) { this._native = native; }
797
+ }
798
+
799
+ class DoeGPUShaderModule {
800
+ constructor(native) { this._native = native; }
801
+ }
802
+
803
+ class DoeGPUComputePipeline {
804
+ constructor(native) { this._native = native; }
805
+
806
+ getBindGroupLayout(index) {
807
+ const layout = wgpu.symbols.wgpuComputePipelineGetBindGroupLayout(this._native, index);
808
+ return new DoeGPUBindGroupLayout(layout);
809
+ }
810
+ }
811
+
812
+ class DoeGPUBindGroupLayout {
813
+ constructor(native) { this._native = native; }
814
+ }
815
+
816
+ class DoeGPUBindGroup {
817
+ constructor(native) { this._native = native; }
818
+ }
819
+
820
+ class DoeGPUPipelineLayout {
821
+ constructor(native) { this._native = native; }
822
+ }
823
+
824
+ class DoeGPUDevice {
825
+ constructor(native, instance) {
826
+ this._native = native;
827
+ this._instance = instance;
828
+ const q = wgpu.symbols.wgpuDeviceGetQueue(native);
829
+ this.queue = new DoeGPUQueue(q, instance);
830
+ this.limits = DOE_LIMITS;
831
+ this.features = DOE_FEATURES;
832
+ }
833
+
834
+ createBuffer(descriptor) {
835
+ const descBytes = buildBufferDescriptor(descriptor);
836
+ const buf = wgpu.symbols.wgpuDeviceCreateBuffer(this._native, descBytes);
837
+ return new DoeGPUBuffer(buf, this._instance, descriptor.size, descriptor.usage, this.queue._native);
838
+ }
839
+
840
+ createShaderModule(descriptor) {
841
+ const code = descriptor.code || descriptor.source;
842
+ if (!code) throw new Error("createShaderModule: descriptor.code is required");
843
+ const { desc, _refs } = buildShaderModuleDescriptor(code);
844
+ const mod = wgpu.symbols.wgpuDeviceCreateShaderModule(this._native, desc);
845
+ void _refs;
846
+ return new DoeGPUShaderModule(mod);
847
+ }
848
+
849
+ createComputePipeline(descriptor) {
850
+ const shader = descriptor.compute?.module;
851
+ const entryPoint = descriptor.compute?.entryPoint || "main";
852
+ const layout = descriptor.layout === "auto" ? null : descriptor.layout;
853
+ const { desc, _refs } = buildComputePipelineDescriptor(
854
+ shader._native, entryPoint, layout?._native ?? null);
855
+ const native = wgpu.symbols.wgpuDeviceCreateComputePipeline(this._native, desc);
856
+ void _refs;
857
+ return new DoeGPUComputePipeline(native);
858
+ }
859
+
860
+ async createComputePipelineAsync(descriptor) {
861
+ return this.createComputePipeline(descriptor);
862
+ }
863
+
864
+ createBindGroupLayout(descriptor) {
865
+ const entries = (descriptor.entries || []).map((e) => ({
866
+ binding: e.binding,
867
+ visibility: e.visibility,
868
+ buffer: e.buffer ? {
869
+ type: e.buffer.type || "uniform",
870
+ hasDynamicOffset: e.buffer.hasDynamicOffset || false,
871
+ minBindingSize: e.buffer.minBindingSize || 0,
872
+ } : undefined,
873
+ }));
874
+ const { desc, _refs } = buildBindGroupLayoutDescriptor(entries);
875
+ const native = wgpu.symbols.wgpuDeviceCreateBindGroupLayout(this._native, desc);
876
+ void _refs;
877
+ return new DoeGPUBindGroupLayout(native);
878
+ }
879
+
880
+ createBindGroup(descriptor) {
881
+ const { desc, _refs } = buildBindGroupDescriptor(descriptor.layout._native, descriptor.entries || []);
882
+ const native = wgpu.symbols.wgpuDeviceCreateBindGroup(this._native, desc);
883
+ void _refs;
884
+ return new DoeGPUBindGroup(native);
885
+ }
886
+
887
+ createPipelineLayout(descriptor) {
888
+ const layouts = (descriptor.bindGroupLayouts || []).map((l) => l._native);
889
+ const { desc, _refs } = buildPipelineLayoutDescriptor(layouts);
890
+ const native = wgpu.symbols.wgpuDeviceCreatePipelineLayout(this._native, desc);
891
+ void _refs;
892
+ return new DoeGPUPipelineLayout(native);
893
+ }
894
+
895
+ createTexture(descriptor) {
896
+ const descBytes = buildTextureDescriptor(descriptor);
897
+ const native = wgpu.symbols.wgpuDeviceCreateTexture(this._native, descBytes);
898
+ return new DoeGPUTexture(native);
899
+ }
900
+
901
+ createSampler(descriptor = {}) {
902
+ const descBytes = buildSamplerDescriptor(descriptor);
903
+ const native = wgpu.symbols.wgpuDeviceCreateSampler(this._native, descBytes);
904
+ return new DoeGPUSampler(native);
905
+ }
906
+
907
+ createRenderPipeline(_descriptor) {
908
+ // Stub: descriptor is not marshaled yet (matches Node N-API stub).
909
+ const native = wgpu.symbols.wgpuDeviceCreateRenderPipeline(this._native, null);
910
+ return new DoeGPURenderPipeline(native);
911
+ }
912
+
913
+ createCommandEncoder(_descriptor) {
914
+ const native = wgpu.symbols.wgpuDeviceCreateCommandEncoder(this._native, null);
915
+ return new DoeGPUCommandEncoder(native);
916
+ }
917
+
918
+ destroy() {
919
+ wgpu.symbols.wgpuDeviceRelease(this._native);
920
+ this._native = null;
921
+ }
922
+ }
923
+
924
+ class DoeGPUAdapter {
925
+ constructor(native, instance) {
926
+ this._native = native;
927
+ this._instance = instance;
928
+ this.features = DOE_FEATURES;
929
+ this.limits = DOE_LIMITS;
930
+ }
931
+
932
+ async requestDevice(_descriptor) {
933
+ const device = requestDeviceSync(this._instance, this._native);
934
+ return new DoeGPUDevice(device, this._instance);
935
+ }
936
+
937
+ destroy() {
938
+ wgpu.symbols.wgpuAdapterRelease(this._native);
939
+ this._native = null;
940
+ }
941
+ }
942
+
943
+ class DoeGPU {
944
+ constructor(instance) {
945
+ this._instance = instance;
946
+ }
947
+
948
+ async requestAdapter(_options) {
949
+ const adapter = requestAdapterSync(this._instance);
950
+ return new DoeGPUAdapter(adapter, this._instance);
951
+ }
952
+ }
953
+
954
+ // ---------------------------------------------------------------------------
955
+ // Library initialization
956
+ // ---------------------------------------------------------------------------
957
+
958
+ let libraryLoaded = false;
959
+
960
+ function ensureLibrary() {
961
+ if (libraryLoaded) return;
962
+ if (!DOE_LIB_PATH) {
963
+ throw new Error(
964
+ "@simulatte/webgpu: libwebgpu_doe not found. Build it with `cd fawn/zig && zig build dropin` or set DOE_WEBGPU_LIB."
965
+ );
966
+ }
967
+ wgpu = openLibrary(DOE_LIB_PATH);
968
+ libraryLoaded = true;
969
+ }
970
+
971
+ // ---------------------------------------------------------------------------
972
+ // Public API — matches index.js exports exactly
973
+ // ---------------------------------------------------------------------------
974
+
975
+ export function create(createArgs = null) {
976
+ ensureLibrary();
977
+ const instance = wgpu.symbols.wgpuCreateInstance(null);
978
+ return new DoeGPU(instance);
979
+ }
980
+
981
+ export function setupGlobals(target = globalThis, createArgs = null) {
982
+ for (const [name, value] of Object.entries(globals)) {
983
+ if (target[name] === undefined) {
984
+ Object.defineProperty(target, name, {
985
+ value,
986
+ writable: true,
987
+ configurable: true,
988
+ enumerable: false,
989
+ });
990
+ }
991
+ }
992
+ const gpu = create(createArgs);
993
+ if (typeof target.navigator === "undefined") {
994
+ Object.defineProperty(target, "navigator", {
995
+ value: { gpu },
996
+ writable: true,
997
+ configurable: true,
998
+ enumerable: false,
999
+ });
1000
+ } else if (!target.navigator.gpu) {
1001
+ Object.defineProperty(target.navigator, "gpu", {
1002
+ value: gpu,
1003
+ writable: true,
1004
+ configurable: true,
1005
+ enumerable: false,
1006
+ });
1007
+ }
1008
+ return gpu;
1009
+ }
1010
+
1011
+ export async function requestAdapter(adapterOptions = undefined, createArgs = null) {
1012
+ const gpu = create(createArgs);
1013
+ return gpu.requestAdapter(adapterOptions);
1014
+ }
1015
+
1016
+ export async function requestDevice(options = {}) {
1017
+ const createArgs = options?.createArgs ?? null;
1018
+ const adapter = await requestAdapter(options?.adapterOptions, createArgs);
1019
+ return adapter.requestDevice(options?.deviceDescriptor);
1020
+ }
1021
+
1022
+ function libraryFlavor(libraryPath) {
1023
+ if (!libraryPath) return "missing";
1024
+ if (/libwebgpu_doe\.(so|dylib|dll)$/.test(libraryPath)) return "doe-dropin";
1025
+ if (/lib(webgpu|webgpu_dawn|wgpu_native)\.(so|dylib|dll)/.test(libraryPath)) return "delegate";
1026
+ return "unknown";
1027
+ }
1028
+
1029
+ export function providerInfo() {
1030
+ const flavor = DOE_LIBRARY_FLAVOR;
1031
+ return {
1032
+ module: "@simulatte/webgpu",
1033
+ loaded: !!DOE_LIB_PATH,
1034
+ loadError: !DOE_LIB_PATH ? "libwebgpu_doe not found" : "",
1035
+ defaultCreateArgs: [],
1036
+ doeNative: flavor === "doe-dropin",
1037
+ libraryFlavor: flavor,
1038
+ doeLibraryPath: DOE_LIB_PATH ?? "",
1039
+ buildMetadataSource: DOE_BUILD_METADATA.source,
1040
+ buildMetadataPath: DOE_BUILD_METADATA.path,
1041
+ leanVerifiedBuild: DOE_BUILD_METADATA.leanVerifiedBuild,
1042
+ proofArtifactSha256: DOE_BUILD_METADATA.proofArtifactSha256,
1043
+ };
1044
+ }
1045
+
1046
+ export { createDoeRuntime, runDawnVsDoeCompare };
1047
+
1048
+ export default {
1049
+ create,
1050
+ globals,
1051
+ setupGlobals,
1052
+ requestAdapter,
1053
+ requestDevice,
1054
+ providerInfo,
1055
+ createDoeRuntime,
1056
+ runDawnVsDoeCompare,
1057
+ };