webgl2 1.2.1 → 1.2.4

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.
@@ -25,6 +25,26 @@
25
25
  * }} GPUBufferDescriptor
26
26
  */
27
27
 
28
+ /**
29
+ * @typedef {{
30
+ * layout?: 'auto' | { layoutHandle: any },
31
+ * vertex: RenderPipelineShaderDescriptor,
32
+ * fragment: RenderPipelineShaderDescriptor
33
+ * }} RenderPipelineDescriptor
34
+ */
35
+
36
+ /**
37
+ * @typedef {{
38
+ * entryPoint: any,
39
+ * module: { moduleHandle: any },
40
+ * buffers?: {
41
+ * arrayStride: number,
42
+ * stepMode: string,
43
+ * attributes: { format: string, offset: number, shaderLocation: number }[]
44
+ * }[]
45
+ * }} RenderPipelineShaderDescriptor
46
+ */
47
+
28
48
  /**
29
49
  * WebGPU API implementation for WebAssembly
30
50
  *
@@ -33,1100 +53,1252 @@
33
53
  * and software rasterization of WebGPU workloads.
34
54
  */
35
55
 
36
- export const GPUBufferUsage = {
37
- MAP_READ: 0x0001,
38
- MAP_WRITE: 0x0002,
39
- COPY_SRC: 0x0004,
40
- COPY_DST: 0x0008,
41
- INDEX: 0x0010,
42
- VERTEX: 0x0020,
43
- UNIFORM: 0x0040,
44
- STORAGE: 0x0080,
45
- INDIRECT: 0x0100,
46
- QUERY_RESOLVE: 0x0200,
47
- };
48
-
49
- export const GPUMapMode = {
50
- READ: 0x0001,
51
- WRITE: 0x0002,
52
- };
53
-
54
- export const GPUTextureUsage = {
55
- COPY_SRC: 0x01,
56
- COPY_DST: 0x02,
57
- TEXTURE_BINDING: 0x04,
58
- STORAGE_BINDING: 0x08,
59
- RENDER_ATTACHMENT: 0x10,
60
- };
61
-
62
- export const GPUShaderStage = {
63
- VERTEX: 0x1,
64
- FRAGMENT: 0x2,
65
- COMPUTE: 0x4,
66
- };
56
+ export const GPUBufferUsage = /** @type {const} */({
57
+ MAP_READ: 0x0001,
58
+ MAP_WRITE: 0x0002,
59
+ COPY_SRC: 0x0004,
60
+ COPY_DST: 0x0008,
61
+ INDEX: 0x0010,
62
+ VERTEX: 0x0020,
63
+ UNIFORM: 0x0040,
64
+ STORAGE: 0x0080,
65
+ INDIRECT: 0x0100,
66
+ QUERY_RESOLVE: 0x0200,
67
+ });
68
+
69
+ export const GPUMapMode = /** @type {const} */({
70
+ READ: 0x0001,
71
+ WRITE: 0x0002,
72
+ });
73
+
74
+ export const GPUTextureUsage = /** @type {const} */({
75
+ COPY_SRC: 0x01,
76
+ COPY_DST: 0x02,
77
+ TEXTURE_BINDING: 0x04,
78
+ STORAGE_BINDING: 0x08,
79
+ RENDER_ATTACHMENT: 0x10,
80
+ });
81
+
82
+ export const GPUShaderStage = /** @type {const} */({
83
+ VERTEX: 0x1,
84
+ FRAGMENT: 0x2,
85
+ COMPUTE: 0x4,
86
+ });
67
87
 
68
88
  // Polyfill globals if missing (e.g. in Node.js)
69
89
  if (typeof globalThis !== 'undefined') {
70
- if (!globalThis.GPUBufferUsage) globalThis.GPUBufferUsage = GPUBufferUsage;
71
- if (!globalThis.GPUMapMode) globalThis.GPUMapMode = GPUMapMode;
72
- if (!globalThis.GPUTextureUsage) globalThis.GPUTextureUsage = GPUTextureUsage;
73
- if (!globalThis.GPUShaderStage) globalThis.GPUShaderStage = GPUShaderStage;
90
+ if (!globalThis.GPUBufferUsage) globalThis.GPUBufferUsage = GPUBufferUsage;
91
+ if (!globalThis.GPUMapMode) globalThis.GPUMapMode = GPUMapMode;
92
+ if (!globalThis.GPUTextureUsage) globalThis.GPUTextureUsage = GPUTextureUsage;
93
+ if (!globalThis.GPUShaderStage) globalThis.GPUShaderStage = GPUShaderStage;
74
94
  }
75
95
 
76
96
  const activeDevices = new Set();
77
97
 
78
98
  export class GPUUncapturedErrorEvent extends Event {
79
- constructor(type, eventInitDict) {
80
- super(type, eventInitDict);
81
- this.error = eventInitDict.error;
82
- }
99
+ constructor(type, eventInitDict) {
100
+ super(type, eventInitDict);
101
+ this.error = eventInitDict.error;
102
+ }
83
103
  }
84
104
 
105
+ const TEXTURE_FORMAT_MAP = /** @type {const} */({
106
+ 'r8unorm': 0,
107
+ 'r8snorm': 1,
108
+ 'r8uint': 2,
109
+ 'r8sint': 3,
110
+ 'r16float': 12,
111
+ 'rgba8unorm': 17,
112
+ 'rgba8unorm-srgb': 18,
113
+ 'bgra8unorm': 19,
114
+ 'bgra8unorm-srgb': 20,
115
+ 'rgba16float': 24,
116
+ 'r32float': 35,
117
+ 'depth32float': 38,
118
+ 'depth24plus': 39,
119
+ 'depth24plus-stencil8': 40,
120
+ });
121
+
122
+ // Map common features
123
+ const FEATURE_MAPPING = /** @type {const} */({
124
+ 'depth-clip-control': 0n,
125
+ 'depth32float-stencil8': 1n,
126
+ 'texture-compression-bc': 2n,
127
+ 'texture-compression-etc2': 3n,
128
+ 'texture-compression-astc': 4n,
129
+ 'indirect-first-instance': 5n,
130
+ 'shader-f16': 6n,
131
+ 'rg11b10ufloat-renderable': 7n,
132
+ 'bgra8unorm-storage': 8n,
133
+ 'float32-filterable': 9n,
134
+ 'float32-blendable': 10n,
135
+ 'clip-distances': 11n,
136
+ 'dual-source-blending': 12n,
137
+ });
138
+
139
+ const LIMIT_NAMES = /** @type {const} */([
140
+ "maxTextureDimension1D", "maxTextureDimension2D", "maxTextureDimension3D", "maxTextureArrayLayers",
141
+ "maxBindGroups", "maxBindGroupsPlusVertexBuffers", "maxBindingsPerBindGroup",
142
+ "maxDynamicUniformBuffersPerPipelineLayout", "maxDynamicStorageBuffersPerPipelineLayout",
143
+ "maxSampledTexturesPerShaderStage", "maxSamplersPerShaderStage", "maxStorageBuffersPerShaderStage",
144
+ "maxStorageTexturesPerShaderStage", "maxUniformBuffersPerShaderStage", "maxUniformBufferBindingSize",
145
+ "maxStorageBufferBindingSize", "maxVertexBuffers", "maxVertexAttributes", "maxVertexBufferArrayStride",
146
+ "maxImmediateSize", "minUniformBufferOffsetAlignment", "minStorageBufferOffsetAlignment",
147
+ "padding", "maxInterStageShaderVariables", "maxColorAttachments",
148
+ "maxColorAttachmentBytesPerSample", "maxComputeWorkgroupStorageSize", "maxComputeInvocationsPerWorkgroup",
149
+ "maxComputeWorkgroupSizeX", "maxComputeWorkgroupSizeY", "maxComputeWorkgroupSizeZ",
150
+ "maxComputeWorkgroupsPerDimension"
151
+ ]);
152
+
85
153
  /**
86
154
  * Wrapper around a WebAssembly-backed WebGPU implementation.
87
155
  */
88
156
  export class GPU {
89
157
 
90
- static dispatchUncapturedError(msg) {
91
- // In Node.js, Event might not be available globally or might behave differently.
92
- // But we are using 'node:test' which implies Node environment.
93
- // If Event is not defined, we might need a polyfill or just call the handler directly.
94
-
95
- const error = new GPUError(msg);
96
-
97
- for (const device of activeDevices) {
98
- if (typeof device.onuncapturederror === 'function') {
99
- device.onuncapturederror({ error });
100
- }
101
- // Also dispatch as event if supported
102
- if (typeof Event !== 'undefined') {
103
- const event = new GPUUncapturedErrorEvent('uncapturederror', { error });
104
- device.dispatchEvent(event);
105
- }
106
- }
158
+ static dispatchUncapturedError(msg) {
159
+ // In Node.js, Event might not be available globally or might behave differently.
160
+ // But we are using 'node:test' which implies Node environment.
161
+ // If Event is not defined, we might need a polyfill or just call the handler directly.
162
+
163
+ const error = new GPUError(msg);
164
+
165
+ for (const device of activeDevices) {
166
+ if (typeof device.onuncapturederror === 'function') {
167
+ device.onuncapturederror({ error });
168
+ }
169
+ // Also dispatch as event if supported
170
+ if (typeof Event !== 'undefined') {
171
+ const event = new GPUUncapturedErrorEvent('uncapturederror', { error });
172
+ device.dispatchEvent(event);
173
+ }
107
174
  }
108
-
109
- /**
110
- * @param {*} wasmModule - WebAssembly module exports implementing WebGPU.
111
- * @param {WebAssembly.Memory} wasmMemory - WebAssembly linear memory.
112
- */
113
- constructor(wasmModule, wasmMemory) {
114
- this.wasm = wasmModule;
115
- this.memory = wasmMemory;
175
+ }
176
+
177
+ /**
178
+ * @param {*} wasmModule - WebAssembly module exports implementing WebGPU.
179
+ * @param {WebAssembly.Memory} wasmMemory - WebAssembly linear memory.
180
+ */
181
+ constructor(wasmModule, wasmMemory) {
182
+ this.wasm = wasmModule;
183
+ this.memory = wasmMemory;
184
+ }
185
+
186
+ /**
187
+ * @returns {string}
188
+ */
189
+ getPreferredCanvasFormat() {
190
+ const format = this.wasm.wasm_webgpu_get_preferred_canvas_format();
191
+ for (const k in TEXTURE_FORMAT_MAP) {
192
+ if (TEXTURE_FORMAT_MAP[/** @type {keyof TEXTURE_FORMAT_MAP} */(k)] === format) return k;
193
+ }
194
+ return 'rgba8unorm';
195
+ }
196
+
197
+
198
+ /**
199
+ * Request a GPUAdapter
200
+ * @param {RequestAdapterOptions} options - Adapter request options
201
+ * @returns {Promise<GPUAdapter | null>}
202
+ */
203
+ async requestAdapter(options = {}) {
204
+ // Create a WebGPU context
205
+ const ctxHandle = this.wasm.wasm_webgpu_create_context();
206
+ if (!ctxHandle)
207
+ return null;
208
+
209
+ let powerPreference = 0; // None
210
+ if (options.powerPreference === 'low-power') {
211
+ powerPreference = 1;
212
+ } else if (options.powerPreference === 'high-performance') {
213
+ powerPreference = 2;
116
214
  }
117
215
 
118
-
119
- /**
120
- * Request a GPUAdapter
121
- * @param {RequestAdapterOptions} options - Adapter request options
122
- * @returns {Promise<GPUAdapter | null>}
123
- */
124
- async requestAdapter(options = {}) {
125
- // Create a WebGPU context
126
- const ctxHandle = this.wasm.wasm_webgpu_create_context();
127
- if (!ctxHandle)
128
- return null;
129
-
130
- let powerPreference = 0; // None
131
- if (options.powerPreference === 'low-power') {
132
- powerPreference = 1;
133
- } else if (options.powerPreference === 'high-performance') {
134
- powerPreference = 2;
135
- }
136
-
137
- const adapterHandle = this.wasm.wasm_webgpu_request_adapter(ctxHandle, powerPreference);
138
- if (adapterHandle === 0) {
139
- this.wasm.wasm_webgpu_destroy_context(ctxHandle);
140
- return null;
141
- }
142
-
143
- return new GPUAdapter(this.wasm, this.memory, ctxHandle, adapterHandle);
216
+ const adapterHandle = this.wasm.wasm_webgpu_request_adapter(ctxHandle, powerPreference);
217
+ if (adapterHandle === 0) {
218
+ this.wasm.wasm_webgpu_destroy_context(ctxHandle);
219
+ return null;
144
220
  }
221
+
222
+ return new GPUAdapter(this.wasm, this.memory, ctxHandle, adapterHandle);
223
+ }
145
224
  }
146
225
 
147
226
  export class GPUAdapter {
148
227
 
149
- /**
150
- * @param {*} wasmModule
151
- * @param {WebAssembly.Memory} wasmMemory
152
- * @param {number} ctxHandle
153
- * @param {number} adapterHandle
154
- */
155
- constructor(wasmModule, wasmMemory, ctxHandle, adapterHandle) {
156
- this.wasm = wasmModule;
157
- this.memory = wasmMemory;
158
- this.ctxHandle = ctxHandle;
159
- this.adapterHandle = adapterHandle;
160
- /** @type {Set<string>} */
161
- this.features = new Set();
162
- /** @type {Record<string, number>} */
163
- this.limits = {};
228
+ /**
229
+ * @param {*} wasmModule
230
+ * @param {WebAssembly.Memory} wasmMemory
231
+ * @param {number} ctxHandle
232
+ * @param {number} adapterHandle
233
+ */
234
+ constructor(wasmModule, wasmMemory, ctxHandle, adapterHandle) {
235
+ this.wasm = wasmModule;
236
+ this.memory = wasmMemory;
237
+ this.ctxHandle = ctxHandle;
238
+ this.adapterHandle = adapterHandle;
239
+
240
+ /** @type {Set<string>} */
241
+ this.features = new Set();
242
+ const featureBits = this.wasm.wasm_webgpu_get_adapter_features(this.ctxHandle, this.adapterHandle);
243
+ for (const [name, bit] of Object.entries(FEATURE_MAPPING)) {
244
+ if (featureBits & (1n << bit)) {
245
+ this.features.add(name);
246
+ }
164
247
  }
165
248
 
166
- /**
167
- * Request a GPUDevice
168
- * @param {Object} descriptor - Device descriptor
169
- * @returns {Promise<GPUDevice | null>}
170
- */
171
- async requestDevice(descriptor = {}) {
172
- const deviceHandle = this.wasm.wasm_webgpu_request_device(this.ctxHandle, this.adapterHandle);
173
- if (deviceHandle === 0) {
174
- return null;
175
- }
176
- return new GPUDevice(this.wasm, this.memory, this.ctxHandle, deviceHandle);
249
+ /** @type {Record<string, number>} */
250
+ this.limits = {};
251
+ const limitsSize = 36 * 4; // 34 u32s + 1 u64
252
+ const limitsPtr = this.wasm.wasm_alloc(limitsSize);
253
+ this.wasm.wasm_webgpu_get_adapter_limits(this.ctxHandle, this.adapterHandle, limitsPtr);
254
+ const limitsView = new DataView(this.memory.buffer, limitsPtr, limitsSize);
255
+
256
+ for (let i = 0; i < LIMIT_NAMES.length; i++) {
257
+ if (LIMIT_NAMES[i] !== "padding") {
258
+ this.limits[LIMIT_NAMES[i]] = limitsView.getUint32(i * 4, true);
259
+ }
177
260
  }
261
+ this.limits.maxBufferSize = Number(limitsView.getBigUint64(34 * 4, true));
262
+
263
+ this.wasm.wasm_free(limitsPtr, limitsSize);
264
+ }
265
+
266
+ /**
267
+ * Request a GPUDevice
268
+ * @param {Object} descriptor - Device descriptor
269
+ * @returns {Promise<GPUDevice | null>}
270
+ */
271
+ async requestDevice(descriptor = {}) {
272
+ const deviceHandle = this.wasm.wasm_webgpu_request_device(this.ctxHandle, this.adapterHandle);
273
+ if (deviceHandle === 0) {
274
+ return null;
275
+ }
276
+ return new GPUDevice(this.wasm, this.memory, this.ctxHandle, deviceHandle);
277
+ }
178
278
  }
179
279
 
180
280
  export class GPUDevice extends (typeof EventTarget !== 'undefined' ? EventTarget : Object) {
181
- /**
182
- * @param {*} wasmModule
183
- * @param {WebAssembly.Memory} wasmMemory
184
- * @param {number} ctxHandle
185
- * @param {number} deviceHandle
186
- */
187
- constructor(wasmModule, wasmMemory, ctxHandle, deviceHandle) {
188
- super();
189
- this.wasm = wasmModule;
190
- this.memory = wasmMemory;
191
- this.ctxHandle = ctxHandle;
192
- this.deviceHandle = deviceHandle;
193
- this.queue = new GPUQueue(wasmModule, wasmMemory, ctxHandle, deviceHandle);
194
- this._destroyed = false;
195
- activeDevices.add(this);
281
+ /**
282
+ * @param {*} wasmModule
283
+ * @param {WebAssembly.Memory} wasmMemory
284
+ * @param {number} ctxHandle
285
+ * @param {number} deviceHandle
286
+ */
287
+ constructor(wasmModule, wasmMemory, ctxHandle, deviceHandle) {
288
+ super();
289
+ this.wasm = wasmModule;
290
+ this.memory = wasmMemory;
291
+ this.ctxHandle = ctxHandle;
292
+ this.deviceHandle = deviceHandle;
293
+ this.queue = new GPUQueue(wasmModule, wasmMemory, ctxHandle, deviceHandle);
294
+ this._destroyed = false;
295
+ activeDevices.add(this);
296
+ }
297
+
298
+ pushErrorScope(filter) {
299
+ let filterCode = 0;
300
+ if (filter === 'validation') filterCode = 0;
301
+ else if (filter === 'out-of-memory') filterCode = 1;
302
+ else if (filter === 'internal') filterCode = 2;
303
+
304
+ this.wasm.wasm_webgpu_push_error_scope(filterCode);
305
+ }
306
+
307
+ async popErrorScope() {
308
+ const hasError = this.wasm.wasm_webgpu_pop_error_scope();
309
+ if (hasError) {
310
+ const ptr = this.wasm.wasm_get_webgpu_error_msg_ptr();
311
+ const msg = readString(this.memory, ptr);
312
+ const filter = this.wasm.wasm_get_webgpu_error_filter();
313
+
314
+ if (filter === 0) return new GPUValidationError(msg);
315
+ if (filter === 1) return new GPUOutOfMemoryError(msg);
316
+ if (filter === 2) return new GPUInternalError(msg);
317
+
318
+ return new GPUError(msg);
196
319
  }
197
-
198
- pushErrorScope(filter) {
199
- let filterCode = 0;
200
- if (filter === 'validation') filterCode = 0;
201
- else if (filter === 'out-of-memory') filterCode = 1;
202
- else if (filter === 'internal') filterCode = 2;
203
-
204
- this.wasm.wasm_webgpu_push_error_scope(filterCode);
320
+ return null;
321
+ }
322
+
323
+ /**
324
+ * Create a buffer
325
+ * @param {GPUBufferDescriptor} descriptor - Buffer descriptor
326
+ * @returns {GPUBuffer}
327
+ */
328
+ createBuffer(descriptor) {
329
+ const bufferHandle = this.wasm.wasm_webgpu_create_buffer(
330
+ this.ctxHandle,
331
+ this.deviceHandle,
332
+ BigInt(descriptor.size),
333
+ descriptor.usage,
334
+ descriptor.mappedAtCreation || false
335
+ );
336
+ if (bufferHandle === 0) {
337
+ // Error already captured by Rust (scope or uncaptured event)
205
338
  }
206
-
207
- async popErrorScope() {
208
- const hasError = this.wasm.wasm_webgpu_pop_error_scope();
209
- if (hasError) {
210
- const ptr = this.wasm.wasm_get_webgpu_error_msg_ptr();
211
- const msg = readString(this.memory, ptr);
212
- return new GPUError(msg);
213
- }
214
- return null;
215
- }
216
-
217
- /**
218
- * Create a buffer
219
- * @param {GPUBufferDescriptor} descriptor - Buffer descriptor
220
- * @returns {GPUBuffer}
221
- */
222
- createBuffer(descriptor) {
223
- const bufferHandle = this.wasm.wasm_webgpu_create_buffer(
224
- this.ctxHandle,
225
- this.deviceHandle,
226
- BigInt(descriptor.size),
227
- descriptor.usage,
228
- descriptor.mappedAtCreation || false
229
- );
230
- if (bufferHandle === 0) {
231
- // throw new Error("Failed to create buffer");
232
- }
233
- return new GPUBuffer(this.wasm, this.memory, this.ctxHandle, this.deviceHandle, bufferHandle, descriptor.size);
339
+ return new GPUBuffer(this.wasm, this.memory, this.ctxHandle, this.deviceHandle, bufferHandle, descriptor.size);
340
+ }
341
+
342
+ /**
343
+ * Create a texture
344
+ * @param {{
345
+ * size: { width: number, height: number, depthOrArrayLayers?: number },
346
+ * mipLevelCount?: number,
347
+ * sampleCount?: number,
348
+ * usage?: any
349
+ * }} descriptor - Texture descriptor
350
+ * @returns {GPUTexture}
351
+ */
352
+ createTexture(descriptor) {
353
+ let width, height, depthOrArrayLayers;
354
+ if (Array.isArray(descriptor.size)) {
355
+ width = descriptor.size[0];
356
+ height = descriptor.size[1] || 1;
357
+ depthOrArrayLayers = descriptor.size[2] || 1;
358
+ } else {
359
+ width = descriptor.size.width;
360
+ height = descriptor.size.height || 1;
361
+ depthOrArrayLayers = descriptor.size.depthOrArrayLayers || 1;
234
362
  }
235
363
 
236
- /**
237
- * Create a texture
238
- * @param {Object} descriptor - Texture descriptor
239
- * @returns {GPUTexture}
240
- */
241
- createTexture(descriptor) {
242
- let width, height, depthOrArrayLayers;
243
- if (Array.isArray(descriptor.size)) {
244
- width = descriptor.size[0];
245
- height = descriptor.size[1] || 1;
246
- depthOrArrayLayers = descriptor.size[2] || 1;
247
- } else {
248
- width = descriptor.size.width;
249
- height = descriptor.size.height || 1;
250
- depthOrArrayLayers = descriptor.size.depthOrArrayLayers || 1;
251
- }
252
-
253
- const textureHandle = this.wasm.wasm_webgpu_create_texture(
254
- this.ctxHandle,
255
- this.deviceHandle,
256
- width,
257
- height,
258
- depthOrArrayLayers,
259
- descriptor.mipLevelCount || 1,
260
- descriptor.sampleCount || 1,
261
- 1, // dimension: 2D (1)
262
- 0, // format (ignored by backend for now)
263
- descriptor.usage
264
- );
265
- if (textureHandle === 0) {
266
- // throw new Error("Failed to create texture");
267
- }
268
-
269
- // Create a normalized descriptor for the GPUTexture
270
- const normalizedDescriptor = Object.assign({}, descriptor, {
271
- size: { width, height, depthOrArrayLayers }
272
- });
273
-
274
- return new GPUTexture(this.wasm, this.memory, this.ctxHandle, this.deviceHandle, textureHandle, normalizedDescriptor);
364
+ const textureHandle = this.wasm.wasm_webgpu_create_texture(
365
+ this.ctxHandle,
366
+ this.deviceHandle,
367
+ width,
368
+ height,
369
+ depthOrArrayLayers,
370
+ descriptor.mipLevelCount || 1,
371
+ descriptor.sampleCount || 1,
372
+ 1, // dimension: 2D (1)
373
+ TEXTURE_FORMAT_MAP[/** @type {keyof TEXTURE_FORMAT_MAP} */(descriptor.format)] || 17, // default to rgba8unorm
374
+ descriptor.usage
375
+ );
376
+ if (textureHandle === 0) {
377
+ // Error already captured by Rust
275
378
  }
276
379
 
277
- /**
278
- * Create a shader module
279
- * @param {Object} descriptor - Shader module descriptor
280
- * @returns {GPUShaderModule}
281
- */
282
- createShaderModule(descriptor) {
283
- const code = descriptor.code;
284
- const encoder = new TextEncoder();
285
- const codeBytes = encoder.encode(code);
286
-
287
- const ptr = this.wasm.wasm_alloc(codeBytes.length);
288
- const heapU8 = new Uint8Array(this.memory.buffer, ptr, codeBytes.length);
289
- heapU8.set(codeBytes);
290
-
291
- const moduleHandle = this.wasm.wasm_webgpu_create_shader_module(
292
- this.ctxHandle,
293
- this.deviceHandle,
294
- ptr,
295
- codeBytes.length
296
- );
297
-
298
- this.wasm.wasm_free(ptr, codeBytes.length);
299
-
300
- if (moduleHandle === 0) {
301
- // throw new Error("Failed to create shader module");
302
- }
303
-
304
- return new GPUShaderModule(this.wasm, this.memory, this.ctxHandle, moduleHandle);
380
+ // Create a normalized descriptor for the GPUTexture
381
+ const normalizedDescriptor = Object.assign({}, descriptor, {
382
+ size: { width, height, depthOrArrayLayers }
383
+ });
384
+
385
+ return new GPUTexture(this.wasm, this.memory, this.ctxHandle, this.deviceHandle, textureHandle, normalizedDescriptor);
386
+ }
387
+
388
+ /**
389
+ * Create a shader module
390
+ * @param {{ code: string }} descriptor - Shader module descriptor
391
+ * @returns {GPUShaderModule}
392
+ */
393
+ createShaderModule(descriptor) {
394
+ const code = descriptor.code;
395
+ const encoder = new TextEncoder();
396
+ const codeBytes = encoder.encode(code);
397
+
398
+ const ptr = this.wasm.wasm_alloc(codeBytes.length);
399
+ const heapU8 = new Uint8Array(this.memory.buffer, ptr, codeBytes.length);
400
+ heapU8.set(codeBytes);
401
+
402
+ const moduleHandle = this.wasm.wasm_webgpu_create_shader_module(
403
+ this.ctxHandle,
404
+ this.deviceHandle,
405
+ ptr,
406
+ codeBytes.length
407
+ );
408
+
409
+ this.wasm.wasm_free(ptr, codeBytes.length);
410
+
411
+ if (moduleHandle === 0) {
412
+ // Error already captured by Rust
305
413
  }
306
414
 
307
- createPipelineLayout(descriptor) {
308
- const layouts = descriptor.bindGroupLayouts.map(l => l.layoutHandle);
309
- const ptr = this.wasm.wasm_alloc(layouts.length * 4);
310
- new Uint32Array(this.memory.buffer, ptr, layouts.length).set(new Uint32Array(layouts));
415
+ return new GPUShaderModule(this.wasm, this.memory, this.ctxHandle, moduleHandle);
416
+ }
311
417
 
312
- const handle = this.wasm.wasm_webgpu_create_pipeline_layout(
313
- this.ctxHandle,
314
- this.deviceHandle,
315
- ptr,
316
- layouts.length
317
- );
418
+ createPipelineLayout(descriptor) {
419
+ const layouts = descriptor.bindGroupLayouts.map(l => l.layoutHandle);
420
+ const ptr = this.wasm.wasm_alloc(layouts.length * 4);
421
+ new Uint32Array(this.memory.buffer, ptr, layouts.length).set(new Uint32Array(layouts));
318
422
 
319
- this.wasm.wasm_free(ptr, layouts.length * 4);
423
+ const handle = this.wasm.wasm_webgpu_create_pipeline_layout(
424
+ this.ctxHandle,
425
+ this.deviceHandle,
426
+ ptr,
427
+ layouts.length
428
+ );
320
429
 
321
- if (handle === 0) {
322
- // throw new Error("Failed to create pipeline layout");
323
- }
430
+ this.wasm.wasm_free(ptr, layouts.length * 4);
324
431
 
325
- return new GPUPipelineLayout(this.wasm, this.memory, this.ctxHandle, handle);
432
+ if (handle === 0) {
433
+ // throw new Error("Failed to create pipeline layout");
326
434
  }
327
435
 
328
- /**
329
- * Create a render pipeline
330
- * @param {Object} descriptor - Render pipeline descriptor
331
- * @returns {GPURenderPipeline}
332
- */
333
- createRenderPipeline(descriptor) {
334
- const vertexEntry = descriptor.vertex.entryPoint;
335
- const fragmentEntry = descriptor.fragment.entryPoint;
336
-
337
- const encoder = new TextEncoder();
338
- const vBytes = encoder.encode(vertexEntry);
339
- const fBytes = encoder.encode(fragmentEntry);
340
-
341
- const vPtr = this.wasm.wasm_alloc(vBytes.length);
342
- new Uint8Array(this.memory.buffer, vPtr, vBytes.length).set(vBytes);
343
-
344
- const fPtr = this.wasm.wasm_alloc(fBytes.length);
345
- new Uint8Array(this.memory.buffer, fPtr, fBytes.length).set(fBytes);
346
-
347
- // Encode vertex buffers
348
- // Format: [count, stride, stepMode, attrCount, format, offset, location, ...]
349
- const layoutData = [];
350
- const buffers = descriptor.vertex.buffers || [];
351
- layoutData.push(buffers.length);
352
-
353
- for (const buffer of buffers) {
354
- layoutData.push(buffer.arrayStride);
355
- layoutData.push(buffer.stepMode === 'instance' ? 1 : 0);
356
- layoutData.push(buffer.attributes.length);
357
- for (const attr of buffer.attributes) {
358
- // Map format string to enum if needed, or assume simple mapping for now
359
- // For this prototype, we'll assume the Rust side handles string parsing or we pass simple IDs
360
- // Let's pass format as a simple ID for now:
361
- // float32x3 = 0, float32x2 = 1, etc.
362
- // Actually, let's just pass the raw values and let Rust figure it out or use a simplified mapping
363
- let formatId = 0;
364
- if (attr.format === 'float32x3') formatId = 1;
365
- else if (attr.format === 'float32x2') formatId = 2;
366
- else if (attr.format === 'float32x4') formatId = 3;
367
-
368
- layoutData.push(formatId);
369
- layoutData.push(attr.offset);
370
- layoutData.push(attr.shaderLocation);
371
- }
372
- }
373
-
374
- const lPtr = this.wasm.wasm_alloc(layoutData.length * 4);
375
- new Uint32Array(this.memory.buffer, lPtr, layoutData.length).set(layoutData);
376
-
377
- let layoutHandle = 0;
378
- if (descriptor.layout && descriptor.layout !== 'auto') {
379
- layoutHandle = descriptor.layout.layoutHandle;
380
- }
381
-
382
- const primitiveTopology = {
383
- 'point-list': 1,
384
- 'line-list': 2,
385
- 'line-strip': 3,
386
- 'triangle-list': 4,
387
- 'triangle-strip': 5,
388
- }[descriptor.primitive?.topology || 'triangle-list'] || 4;
389
-
390
- const depthStencil = descriptor.depthStencil;
391
- const depthFormat = {
392
- 'depth32float': 1,
393
- 'depth24plus': 2,
394
- 'depth24plus-stencil8': 3,
395
- }[depthStencil?.format] || 0;
396
-
397
- const depthCompare = {
398
- 'never': 1,
399
- 'less': 2,
400
- 'equal': 3,
401
- 'less-equal': 4,
402
- 'greater': 5,
403
- 'not-equal': 6,
404
- 'greater-equal': 7,
405
- 'always': 8,
406
- }[depthStencil?.depthCompare || 'less'] || 2;
407
-
408
- const blendFactorMap = {
409
- 'zero': 0, 'one': 1, 'src': 2, 'one-minus-src': 3,
410
- 'src-alpha': 4, 'one-minus-src-alpha': 5,
411
- 'dst': 6, 'one-minus-dst': 7, 'dst-alpha': 8, 'one-minus-dst-alpha': 9,
412
- };
413
-
414
- const blendOpMap = {
415
- 'add': 0, 'subtract': 1, 'reverse-subtract': 2, 'min': 3, 'max': 4,
416
- };
417
-
418
- const fragmentTarget = descriptor.fragment.targets?.[0];
419
- const blend = fragmentTarget?.blend;
420
-
421
- const pipelineHandle = this.wasm.wasm_webgpu_create_render_pipeline(
422
- this.ctxHandle,
423
- this.deviceHandle,
424
- descriptor.vertex.module.moduleHandle,
425
- vPtr,
426
- vBytes.length,
427
- descriptor.fragment.module.moduleHandle,
428
- fPtr,
429
- fBytes.length,
430
- lPtr,
431
- layoutData.length,
432
- layoutHandle,
433
- primitiveTopology,
434
- depthFormat,
435
- depthStencil?.depthWriteEnabled ? 1 : 0,
436
- depthCompare,
437
- blend ? 1 : 0,
438
- blendFactorMap[blend?.color?.srcFactor] || 0,
439
- blendFactorMap[blend?.color?.dstFactor] || 0,
440
- blendOpMap[blend?.color?.operation] || 0,
441
- blendFactorMap[blend?.alpha?.srcFactor] || 0,
442
- blendFactorMap[blend?.alpha?.dstFactor] || 0,
443
- blendOpMap[blend?.alpha?.operation] || 0
444
- );
445
-
446
- this.wasm.wasm_free(vPtr, vBytes.length);
447
- this.wasm.wasm_free(fPtr, fBytes.length);
448
- this.wasm.wasm_free(lPtr, layoutData.length * 4);
449
-
450
- if (pipelineHandle === 0) {
451
- // throw new Error("Failed to create render pipeline");
452
- }
453
-
454
- return new GPURenderPipeline(this.wasm, this.memory, this.ctxHandle, pipelineHandle);
436
+ return new GPUPipelineLayout(this.wasm, this.memory, this.ctxHandle, handle);
437
+ }
438
+
439
+ /**
440
+ * Create a render pipeline
441
+ * @param {RenderPipelineDescriptor} descriptor - Render pipeline descriptor
442
+ * @returns {GPURenderPipeline}
443
+ */
444
+ createRenderPipeline(descriptor) {
445
+ const vertexEntry = descriptor.vertex.entryPoint;
446
+ const fragmentEntry = descriptor.fragment.entryPoint;
447
+
448
+ const encoder = new TextEncoder();
449
+ const vBytes = encoder.encode(vertexEntry);
450
+ const fBytes = encoder.encode(fragmentEntry);
451
+
452
+ const vPtr = this.wasm.wasm_alloc(vBytes.length);
453
+ new Uint8Array(this.memory.buffer, vPtr, vBytes.length).set(vBytes);
454
+
455
+ const fPtr = this.wasm.wasm_alloc(fBytes.length);
456
+ new Uint8Array(this.memory.buffer, fPtr, fBytes.length).set(fBytes);
457
+
458
+ // Encode vertex buffers
459
+ // Format: [count, stride, stepMode, attrCount, format, offset, location, ...]
460
+ const layoutData = [];
461
+ const buffers = descriptor.vertex.buffers || [];
462
+ layoutData.push(buffers.length);
463
+
464
+ for (const buffer of buffers) {
465
+ layoutData.push(buffer.arrayStride);
466
+ layoutData.push(buffer.stepMode === 'instance' ? 1 : 0);
467
+ layoutData.push(buffer.attributes.length);
468
+ for (const attr of buffer.attributes) {
469
+ // Map format string to enum if needed, or assume simple mapping for now
470
+ // For this prototype, we'll assume the Rust side handles string parsing or we pass simple IDs
471
+ // Let's pass format as a simple ID for now:
472
+ // float32x3 = 0, float32x2 = 1, etc.
473
+ // Actually, let's just pass the raw values and let Rust figure it out or use a simplified mapping
474
+ let formatId = 0;
475
+ if (attr.format === 'float32x3') formatId = 1;
476
+ else if (attr.format === 'float32x2') formatId = 2;
477
+ else if (attr.format === 'float32x4') formatId = 3;
478
+
479
+ layoutData.push(formatId);
480
+ layoutData.push(attr.offset);
481
+ layoutData.push(attr.shaderLocation);
482
+ }
455
483
  }
456
484
 
457
- /**
458
- * Create a command encoder
459
- * @param {Object} descriptor - Command encoder descriptor
460
- * @returns {GPUCommandEncoder}
461
- */
462
- createCommandEncoder(descriptor = {}) {
463
- const encoderHandle = this.wasm.wasm_webgpu_create_command_encoder(this.ctxHandle, this.deviceHandle);
464
- if (encoderHandle === 0) {
465
- // throw new Error("Failed to create command encoder");
466
- }
467
- return new GPUCommandEncoder(this.wasm, this.memory, this.ctxHandle, encoderHandle);
468
- }
485
+ const lPtr = this.wasm.wasm_alloc(layoutData.length * 4);
486
+ new Uint32Array(this.memory.buffer, lPtr, layoutData.length).set(layoutData);
469
487
 
470
- /**
471
- * Create a bind group layout
472
- * @param {Object} descriptor
473
- * @returns {GPUBindGroupLayout}
474
- */
475
- createBindGroupLayout(descriptor) {
476
- const entries = descriptor.entries || [];
477
- const data = [entries.length];
478
-
479
- for (const entry of entries) {
480
- data.push(entry.binding);
481
- data.push(entry.visibility);
482
-
483
- // Type: 0=Buffer, 1=Texture, 2=Sampler
484
- let typeId = 0;
485
- if (entry.buffer) typeId = 0;
486
- else if (entry.texture) typeId = 1;
487
- else if (entry.sampler) typeId = 2;
488
-
489
- data.push(typeId);
490
- }
491
-
492
- const ptr = this.wasm.wasm_alloc(data.length * 4);
493
- new Uint32Array(this.memory.buffer, ptr, data.length).set(data);
494
-
495
- const handle = this.wasm.wasm_webgpu_create_bind_group_layout(
496
- this.ctxHandle,
497
- this.deviceHandle,
498
- ptr,
499
- data.length
500
- );
501
-
502
- this.wasm.wasm_free(ptr, data.length * 4);
503
-
504
- if (handle === 0) {
505
- // throw new Error("Failed to create bind group layout");
506
- }
507
-
508
- return new GPUBindGroupLayout(this.wasm, this.memory, this.ctxHandle, handle);
488
+ let layoutHandle = 0;
489
+ if (descriptor.layout && descriptor.layout !== 'auto') {
490
+ layoutHandle = descriptor.layout.layoutHandle;
509
491
  }
510
492
 
511
- /**
512
- * Create a bind group
513
- * @param {Object} descriptor
514
- * @returns {GPUBindGroup}
515
- */
516
- createBindGroup(descriptor) {
517
- const entries = descriptor.entries || [];
518
- const data = [entries.length];
519
-
520
- for (const entry of entries) {
521
- data.push(entry.binding);
522
-
523
- // Resource Type: 0=Buffer, 1=TextureView, 2=Sampler
524
- let resType = 0;
525
- let resHandle = 0;
526
-
527
- if (entry.resource.buffer) {
528
- resType = 0;
529
- resHandle = entry.resource.buffer.bufferHandle;
530
- } else if (entry.resource instanceof GPUTextureView) {
531
- resType = 1;
532
- resHandle = entry.resource.viewHandle;
533
- } else if (entry.resource instanceof GPUSampler) {
534
- resType = 2;
535
- resHandle = entry.resource.samplerHandle;
536
- } else if (entry.resource.constructor.name === 'GPUSampler') {
537
- resType = 2;
538
- resHandle = entry.resource.samplerHandle;
539
- }
540
-
541
- data.push(resType);
542
- data.push(resHandle);
543
- }
544
-
545
- const ptr = this.wasm.wasm_alloc(data.length * 4);
546
- new Uint32Array(this.memory.buffer, ptr, data.length).set(data);
547
-
548
- const handle = this.wasm.wasm_webgpu_create_bind_group(
549
- this.ctxHandle,
550
- this.deviceHandle,
551
- descriptor.layout.layoutHandle,
552
- ptr,
553
- data.length
554
- );
555
-
556
- this.wasm.wasm_free(ptr, data.length * 4);
557
-
558
- if (handle === 0) {
559
- // throw new Error("Failed to create bind group");
560
- }
561
-
562
- return new GPUBindGroup(this.wasm, this.memory, this.ctxHandle, handle);
493
+ const primitiveTopology = {
494
+ 'point-list': 1,
495
+ 'line-list': 2,
496
+ 'line-strip': 3,
497
+ 'triangle-list': 4,
498
+ 'triangle-strip': 5,
499
+ }[descriptor.primitive?.topology || 'triangle-list'] || 4;
500
+
501
+ const depthStencil = descriptor.depthStencil;
502
+ const depthFormat = {
503
+ 'depth32float': 1,
504
+ 'depth24plus': 2,
505
+ 'depth24plus-stencil8': 3,
506
+ }[depthStencil?.format] || 0;
507
+
508
+ const depthCompare = {
509
+ 'never': 1,
510
+ 'less': 2,
511
+ 'equal': 3,
512
+ 'less-equal': 4,
513
+ 'greater': 5,
514
+ 'not-equal': 6,
515
+ 'greater-equal': 7,
516
+ 'always': 8,
517
+ }[depthStencil?.depthCompare || 'less'] || 2;
518
+
519
+ const blendFactorMap = {
520
+ 'zero': 0, 'one': 1, 'src': 2, 'one-minus-src': 3,
521
+ 'src-alpha': 4, 'one-minus-src-alpha': 5,
522
+ 'dst': 6, 'one-minus-dst': 7, 'dst-alpha': 8, 'one-minus-dst-alpha': 9,
523
+ };
524
+
525
+ const blendOpMap = {
526
+ 'add': 0, 'subtract': 1, 'reverse-subtract': 2, 'min': 3, 'max': 4,
527
+ };
528
+
529
+ const fragmentTarget = descriptor.fragment.targets?.[0];
530
+ const blend = fragmentTarget?.blend;
531
+
532
+ const pipelineHandle = this.wasm.wasm_webgpu_create_render_pipeline(
533
+ this.ctxHandle,
534
+ this.deviceHandle,
535
+ descriptor.vertex.module.moduleHandle,
536
+ vPtr,
537
+ vBytes.length,
538
+ descriptor.fragment.module.moduleHandle,
539
+ fPtr,
540
+ fBytes.length,
541
+ lPtr,
542
+ layoutData.length,
543
+ layoutHandle,
544
+ primitiveTopology,
545
+ depthFormat,
546
+ depthStencil?.depthWriteEnabled ? 1 : 0,
547
+ depthCompare,
548
+ blend ? 1 : 0,
549
+ blendFactorMap[blend?.color?.srcFactor] || 0,
550
+ blendFactorMap[blend?.color?.dstFactor] || 0,
551
+ blendOpMap[blend?.color?.operation] || 0,
552
+ blendFactorMap[blend?.alpha?.srcFactor] || 0,
553
+ blendFactorMap[blend?.alpha?.dstFactor] || 0,
554
+ blendOpMap[blend?.alpha?.operation] || 0
555
+ );
556
+
557
+ this.wasm.wasm_free(vPtr, vBytes.length);
558
+ this.wasm.wasm_free(fPtr, fBytes.length);
559
+ this.wasm.wasm_free(lPtr, layoutData.length * 4);
560
+
561
+ if (pipelineHandle === 0) {
562
+ // Error already captured by Rust
563
563
  }
564
564
 
565
- /**
566
- * Create a sampler
567
- * @param {Object} descriptor
568
- * @returns {GPUSampler}
569
- */
570
- createSampler(descriptor = {}) {
571
- const handle = this.wasm.wasm_webgpu_create_sampler(this.ctxHandle, this.deviceHandle);
572
- if (handle === 0) {
573
- // throw new Error("Failed to create sampler");
574
- }
575
- return new GPUSampler(this.wasm, this.memory, this.ctxHandle, handle);
565
+ return new GPURenderPipeline(this.wasm, this.memory, this.ctxHandle, pipelineHandle);
566
+ }
567
+
568
+ /**
569
+ * Create a command encoder
570
+ * @param {Object} descriptor - Command encoder descriptor
571
+ * @returns {GPUCommandEncoder}
572
+ */
573
+ createCommandEncoder(descriptor = {}) {
574
+ const encoderHandle = this.wasm.wasm_webgpu_create_command_encoder(this.ctxHandle, this.deviceHandle);
575
+ if (encoderHandle === 0) {
576
+ // Error already captured by Rust
576
577
  }
577
-
578
- /**
579
- * Destroy the device
580
- */
581
- destroy() {
582
- if (this._destroyed) return;
583
- activeDevices.delete(this);
584
- if (typeof this.wasm.wasm_webgpu_destroy_context === 'function') {
585
- this.wasm.wasm_webgpu_destroy_context(this.ctxHandle);
586
- }
587
- this._destroyed = true;
578
+ return new GPUCommandEncoder(this.wasm, this.memory, this.ctxHandle, encoderHandle);
579
+ }
580
+
581
+ /**
582
+ * Create a bind group layout
583
+ * @param {Object} descriptor
584
+ * @returns {GPUBindGroupLayout}
585
+ */
586
+ createBindGroupLayout(descriptor) {
587
+ const entries = descriptor.entries || [];
588
+ const data = [entries.length];
589
+
590
+ for (const entry of entries) {
591
+ data.push(entry.binding);
592
+ data.push(entry.visibility);
593
+
594
+ // Type: 0=Buffer, 1=Texture, 2=Sampler
595
+ let typeId = 0;
596
+ if (entry.buffer) typeId = 0;
597
+ else if (entry.texture) typeId = 1;
598
+ else if (entry.sampler) typeId = 2;
599
+
600
+ data.push(typeId);
588
601
  }
589
- }
590
602
 
591
- export class GPUQueue {
592
- /**
593
- * @param {*} wasmModule
594
- * @param {WebAssembly.Memory} wasmMemory
595
- * @param {number} ctxHandle
596
- * @param {number} queueHandle
597
- */
598
- constructor(wasmModule, wasmMemory, ctxHandle, queueHandle) {
599
- this.wasm = wasmModule;
600
- this.memory = wasmMemory;
601
- this.ctxHandle = ctxHandle;
602
- this.queueHandle = queueHandle;
603
- }
603
+ const ptr = this.wasm.wasm_alloc(data.length * 4);
604
+ new Uint32Array(this.memory.buffer, ptr, data.length).set(data);
604
605
 
605
- /**
606
- * Submit command buffers to the queue
607
- * @param {Array<GPUCommandBuffer>} commandBuffers
608
- */
609
- submit(commandBuffers) {
610
- const handles = new Uint32Array(commandBuffers.map(cb => cb.commandBufferHandle));
611
- const ptr = this.wasm.wasm_alloc(handles.byteLength);
612
- const heapU32 = new Uint32Array(this.memory.buffer, ptr, handles.length);
613
- heapU32.set(handles);
606
+ const handle = this.wasm.wasm_webgpu_create_bind_group_layout(
607
+ this.ctxHandle,
608
+ this.deviceHandle,
609
+ ptr,
610
+ data.length
611
+ );
614
612
 
615
- this.wasm.wasm_webgpu_queue_submit(this.ctxHandle, this.queueHandle, ptr, handles.length);
613
+ this.wasm.wasm_free(ptr, data.length * 4);
616
614
 
617
- this.wasm.wasm_free(ptr, handles.byteLength);
615
+ if (handle === 0) {
616
+ // Error already captured by Rust
618
617
  }
619
618
 
620
- /**
621
- * Write data to a buffer
622
- * @param {GPUBuffer} buffer
623
- * @param {number} bufferOffset
624
- * @param {ArrayBuffer|TypedArray} data
625
- * @param {number} dataOffset
626
- * @param {number} size
627
- */
628
- writeBuffer(buffer, bufferOffset, data, dataOffset = 0, size) {
629
- const srcData = data instanceof ArrayBuffer ? new Uint8Array(data) : new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
630
- const actualSize = size !== undefined ? size : srcData.byteLength - dataOffset;
631
- const subData = srcData.subarray(dataOffset, dataOffset + actualSize);
632
-
633
- const ptr = this.wasm.wasm_alloc(subData.byteLength);
634
- const heap = new Uint8Array(this.memory.buffer, ptr, subData.byteLength);
635
- heap.set(subData);
636
-
637
- this.wasm.wasm_webgpu_queue_write_buffer(
638
- this.ctxHandle,
639
- this.queueHandle,
640
- buffer.bufferHandle,
641
- BigInt(bufferOffset),
642
- ptr,
643
- subData.byteLength
644
- );
645
-
646
- this.wasm.wasm_free(ptr, subData.byteLength);
619
+ return new GPUBindGroupLayout(this.wasm, this.memory, this.ctxHandle, handle);
620
+ }
621
+
622
+ /**
623
+ * Create a bind group
624
+ * @param {Object} descriptor
625
+ * @returns {GPUBindGroup}
626
+ */
627
+ createBindGroup(descriptor) {
628
+ const entries = descriptor.entries || [];
629
+ const data = [entries.length];
630
+
631
+ for (const entry of entries) {
632
+ data.push(entry.binding);
633
+
634
+ // Resource Type: 0=Buffer, 1=TextureView, 2=Sampler
635
+ let resType = 0;
636
+ let resHandle = 0;
637
+
638
+ if (entry.resource.buffer) {
639
+ resType = 0;
640
+ resHandle = entry.resource.buffer.bufferHandle;
641
+ } else if (entry.resource instanceof GPUTextureView) {
642
+ resType = 1;
643
+ resHandle = entry.resource.viewHandle;
644
+ } else if (entry.resource instanceof GPUSampler) {
645
+ resType = 2;
646
+ resHandle = entry.resource.samplerHandle;
647
+ } else if (entry.resource.constructor.name === 'GPUSampler') {
648
+ resType = 2;
649
+ resHandle = entry.resource.samplerHandle;
650
+ }
651
+
652
+ data.push(resType);
653
+ data.push(resHandle);
647
654
  }
648
655
 
649
- /**
650
- * Write data to a texture
651
- * @param {Object} destination
652
- * @param {ArrayBuffer|TypedArray} data
653
- * @param {Object} dataLayout
654
- * @param {Object} size
655
- */
656
- writeTexture(destination, data, dataLayout, size) {
657
- const srcData = data instanceof ArrayBuffer ? new Uint8Array(data) : new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
658
- const subData = dataLayout.offset ? srcData.subarray(dataLayout.offset) : srcData;
659
-
660
- const ptr = this.wasm.wasm_alloc(subData.byteLength);
661
- const heap = new Uint8Array(this.memory.buffer, ptr, subData.byteLength);
662
- heap.set(subData);
663
-
664
- let width, height, depthOrArrayLayers;
665
- if (Array.isArray(size)) {
666
- width = size[0];
667
- height = size[1] || 1;
668
- depthOrArrayLayers = size[2] || 1;
669
- } else {
670
- width = size.width;
671
- height = size.height || 1;
672
- depthOrArrayLayers = size.depthOrArrayLayers || 1;
673
- }
674
-
675
- this.wasm.wasm_webgpu_queue_write_texture(
676
- this.ctxHandle,
677
- this.queueHandle,
678
- destination.texture.textureHandle,
679
- ptr,
680
- subData.byteLength,
681
- dataLayout.bytesPerRow || 0,
682
- dataLayout.rowsPerImage || 0,
683
- width,
684
- height,
685
- depthOrArrayLayers
686
- );
687
-
688
- this.wasm.wasm_free(ptr, subData.byteLength);
689
- }
690
- }
656
+ const ptr = this.wasm.wasm_alloc(data.length * 4);
657
+ new Uint32Array(this.memory.buffer, ptr, data.length).set(data);
691
658
 
692
- export class GPUBuffer {
693
- /**
694
- * @param {*} wasmModule
695
- * @param {WebAssembly.Memory} wasmMemory
696
- * @param {number} ctxHandle
697
- * @param {number} deviceHandle
698
- * @param {number} bufferHandle
699
- * @param {number} size
700
- */
701
- constructor(wasmModule, wasmMemory, ctxHandle, deviceHandle, bufferHandle, size) {
702
- this.wasm = wasmModule;
703
- this.memory = wasmMemory;
704
- this.ctxHandle = ctxHandle;
705
- this.deviceHandle = deviceHandle;
706
- this.bufferHandle = bufferHandle;
707
- this.size = size;
708
- }
659
+ const handle = this.wasm.wasm_webgpu_create_bind_group(
660
+ this.ctxHandle,
661
+ this.deviceHandle,
662
+ descriptor.layout.layoutHandle,
663
+ ptr,
664
+ data.length
665
+ );
666
+
667
+ this.wasm.wasm_free(ptr, data.length * 4);
709
668
 
710
- /**
711
- * Map buffer for reading/writing
712
- * @param {number} mode - Map mode
713
- * @param {number} offset
714
- * @param {number} size
715
- * @returns {Promise<void>}
716
- */
717
- async mapAsync(mode, offset = 0, size) {
718
- const result = this.wasm.wasm_webgpu_buffer_map_async(
719
- this.ctxHandle,
720
- this.deviceHandle,
721
- this.bufferHandle,
722
- mode,
723
- BigInt(offset),
724
- BigInt(size || this.size)
725
- );
726
- if (result !== 0) {
727
- throw new Error("Failed to map buffer");
728
- }
729
- return Promise.resolve();
669
+ if (handle === 0) {
670
+ // Error already captured by Rust
730
671
  }
731
672
 
732
- /**
733
- * Get mapped range
734
- * @param {number} offset
735
- * @param {number} size
736
- * @returns {Uint8Array}
737
- */
738
- getMappedRange(offset = 0, size) {
739
- const ptr = this.wasm.wasm_webgpu_buffer_get_mapped_range(
740
- this.ctxHandle,
741
- this.bufferHandle,
742
- BigInt(offset),
743
- BigInt(size || this.size)
744
- );
745
- if (ptr === 0) {
746
- throw new Error("Failed to get mapped range");
747
- }
748
- return new Uint8Array(this.memory.buffer, ptr, (size || this.size));
673
+ return new GPUBindGroup(this.wasm, this.memory, this.ctxHandle, handle);
674
+ }
675
+
676
+ /**
677
+ * Create a sampler
678
+ * @param {Object} descriptor
679
+ * @returns {GPUSampler}
680
+ */
681
+ createSampler(descriptor = {}) {
682
+ const addressModeMapping = {
683
+ 'clamp-to-edge': 0,
684
+ 'repeat': 1,
685
+ 'mirror-repeat': 2,
686
+ };
687
+ const filterMapping = {
688
+ 'nearest': 0,
689
+ 'linear': 1,
690
+ };
691
+ const compareMapping = {
692
+ 'never': 1,
693
+ 'less': 2,
694
+ 'equal': 3,
695
+ 'less-equal': 4,
696
+ 'greater': 5,
697
+ 'not-equal': 6,
698
+ 'greater-equal': 7,
699
+ 'always': 8,
700
+ };
701
+
702
+ const handle = this.wasm.wasm_webgpu_create_sampler(
703
+ this.ctxHandle,
704
+ this.deviceHandle,
705
+ addressModeMapping[descriptor.addressModeU] || 0,
706
+ addressModeMapping[descriptor.addressModeV] || 0,
707
+ addressModeMapping[descriptor.addressModeW] || 0,
708
+ filterMapping[descriptor.magFilter] || 0,
709
+ filterMapping[descriptor.minFilter] || 0,
710
+ filterMapping[descriptor.mipmapFilter] || 0,
711
+ descriptor.lodMinClamp || 0.0,
712
+ descriptor.lodMaxClamp || 32.0,
713
+ descriptor.compare ? compareMapping[descriptor.compare] || 0 : 0,
714
+ descriptor.maxAnisotropy || 1
715
+ );
716
+
717
+ if (handle === 0) {
718
+ // Error already captured by Rust
719
+ }
720
+ return new GPUSampler(this.wasm, this.memory, this.ctxHandle, handle);
721
+ }
722
+
723
+ /**
724
+ * Destroy the device
725
+ */
726
+ destroy() {
727
+ if (this._destroyed) return;
728
+ activeDevices.delete(this);
729
+ if (typeof this.wasm.wasm_webgpu_destroy_context === 'function') {
730
+ this.wasm.wasm_webgpu_destroy_context(this.ctxHandle);
749
731
  }
732
+ this._destroyed = true;
733
+ }
734
+ }
750
735
 
751
- /**
752
- * Unmap the buffer
753
- */
754
- unmap() {
755
- this.wasm.wasm_webgpu_buffer_unmap(this.ctxHandle, this.bufferHandle);
736
+ export class GPUQueue {
737
+ /**
738
+ * @param {*} wasmModule
739
+ * @param {WebAssembly.Memory} wasmMemory
740
+ * @param {number} ctxHandle
741
+ * @param {number} queueHandle
742
+ */
743
+ constructor(wasmModule, wasmMemory, ctxHandle, queueHandle) {
744
+ this.wasm = wasmModule;
745
+ this.memory = wasmMemory;
746
+ this.ctxHandle = ctxHandle;
747
+ this.queueHandle = queueHandle;
748
+ }
749
+
750
+ /**
751
+ * Submit command buffers to the queue
752
+ * @param {Array<GPUCommandBuffer>} commandBuffers
753
+ */
754
+ submit(commandBuffers) {
755
+ const handles = new Uint32Array(commandBuffers.map(cb => cb.commandBufferHandle));
756
+ const ptr = this.wasm.wasm_alloc(handles.byteLength);
757
+ const heapU32 = new Uint32Array(this.memory.buffer, ptr, handles.length);
758
+ heapU32.set(handles);
759
+
760
+ this.wasm.wasm_webgpu_queue_submit(this.ctxHandle, this.queueHandle, ptr, handles.length);
761
+
762
+ this.wasm.wasm_free(ptr, handles.byteLength);
763
+ }
764
+
765
+ /**
766
+ * Write data to a buffer
767
+ * @param {GPUBuffer} buffer
768
+ * @param {number} bufferOffset
769
+ * @param {ArrayBuffer|TypedArray} data
770
+ * @param {number} dataOffset
771
+ * @param {number} size
772
+ */
773
+ writeBuffer(buffer, bufferOffset, data, dataOffset = 0, size) {
774
+ const srcData = data instanceof ArrayBuffer ? new Uint8Array(data) : new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
775
+ const actualSize = size !== undefined ? size : srcData.byteLength - dataOffset;
776
+ const subData = srcData.subarray(dataOffset, dataOffset + actualSize);
777
+
778
+ const ptr = this.wasm.wasm_alloc(subData.byteLength);
779
+ const heap = new Uint8Array(this.memory.buffer, ptr, subData.byteLength);
780
+ heap.set(subData);
781
+
782
+ this.wasm.wasm_webgpu_queue_write_buffer(
783
+ this.ctxHandle,
784
+ this.queueHandle,
785
+ buffer.bufferHandle,
786
+ BigInt(bufferOffset),
787
+ ptr,
788
+ subData.byteLength
789
+ );
790
+
791
+ this.wasm.wasm_free(ptr, subData.byteLength);
792
+ }
793
+
794
+ /**
795
+ * Write data to a texture
796
+ * @param {Object} destination
797
+ * @param {ArrayBuffer|TypedArray} data
798
+ * @param {Object} dataLayout
799
+ * @param {Object} size
800
+ */
801
+ writeTexture(destination, data, dataLayout, size) {
802
+ const srcData = data instanceof ArrayBuffer ? new Uint8Array(data) : new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
803
+ const subData = dataLayout.offset ? srcData.subarray(dataLayout.offset) : srcData;
804
+
805
+ const ptr = this.wasm.wasm_alloc(subData.byteLength);
806
+ const heap = new Uint8Array(this.memory.buffer, ptr, subData.byteLength);
807
+ heap.set(subData);
808
+
809
+ let width, height, depthOrArrayLayers;
810
+ if (Array.isArray(size)) {
811
+ width = size[0];
812
+ height = size[1] || 1;
813
+ depthOrArrayLayers = size[2] || 1;
814
+ } else {
815
+ width = size.width;
816
+ height = size.height || 1;
817
+ depthOrArrayLayers = size.depthOrArrayLayers || 1;
756
818
  }
757
819
 
758
- /**
759
- * Destroy the buffer
760
- */
761
- destroy() {
762
- this.wasm.wasm_webgpu_buffer_destroy(this.ctxHandle, this.bufferHandle);
820
+ this.wasm.wasm_webgpu_queue_write_texture(
821
+ this.ctxHandle,
822
+ this.queueHandle,
823
+ destination.texture.textureHandle,
824
+ ptr,
825
+ subData.byteLength,
826
+ dataLayout.bytesPerRow || 0,
827
+ dataLayout.rowsPerImage || 0,
828
+ width,
829
+ height,
830
+ depthOrArrayLayers
831
+ );
832
+
833
+ this.wasm.wasm_free(ptr, subData.byteLength);
834
+ }
835
+ }
836
+
837
+ export class GPUBuffer {
838
+ /**
839
+ * @param {*} wasmModule
840
+ * @param {WebAssembly.Memory} wasmMemory
841
+ * @param {number} ctxHandle
842
+ * @param {number} deviceHandle
843
+ * @param {number} bufferHandle
844
+ * @param {number} size
845
+ */
846
+ constructor(wasmModule, wasmMemory, ctxHandle, deviceHandle, bufferHandle, size) {
847
+ this.wasm = wasmModule;
848
+ this.memory = wasmMemory;
849
+ this.ctxHandle = ctxHandle;
850
+ this.deviceHandle = deviceHandle;
851
+ this.bufferHandle = bufferHandle;
852
+ this.size = size;
853
+ }
854
+
855
+ /**
856
+ * Map buffer for reading/writing
857
+ * @param {number} mode - Map mode
858
+ * @param {number} offset
859
+ * @param {number} size
860
+ * @returns {Promise<void>}
861
+ */
862
+ async mapAsync(mode, offset = 0, size) {
863
+ const result = this.wasm.wasm_webgpu_buffer_map_async(
864
+ this.ctxHandle,
865
+ this.deviceHandle,
866
+ this.bufferHandle,
867
+ mode,
868
+ BigInt(offset),
869
+ BigInt(size || this.size)
870
+ );
871
+ if (result !== 0) {
872
+ throw new Error("Failed to map buffer");
763
873
  }
874
+ return Promise.resolve();
875
+ }
876
+
877
+ /**
878
+ * Get mapped range
879
+ * @param {number} offset
880
+ * @param {number} size
881
+ * @returns {Uint8Array}
882
+ */
883
+ getMappedRange(offset = 0, size) {
884
+ const ptr = this.wasm.wasm_webgpu_buffer_get_mapped_range(
885
+ this.ctxHandle,
886
+ this.bufferHandle,
887
+ BigInt(offset),
888
+ BigInt(size || this.size)
889
+ );
890
+ if (ptr === 0) {
891
+ throw new Error("Failed to get mapped range");
892
+ }
893
+ return new Uint8Array(this.memory.buffer, ptr, (size || this.size));
894
+ }
895
+
896
+ /**
897
+ * Unmap the buffer
898
+ */
899
+ unmap() {
900
+ this.wasm.wasm_webgpu_buffer_unmap(this.ctxHandle, this.bufferHandle);
901
+ }
902
+
903
+ /**
904
+ * Destroy the buffer
905
+ */
906
+ destroy() {
907
+ this.wasm.wasm_webgpu_buffer_destroy(this.ctxHandle, this.bufferHandle);
908
+ }
764
909
  }
765
910
 
766
911
  export class GPUShaderModule {
767
- /**
768
- * @param {*} wasmModule
769
- * @param {WebAssembly.Memory} wasmMemory
770
- * @param {number} ctxHandle
771
- * @param {number} moduleHandle
772
- */
773
- constructor(wasmModule, wasmMemory, ctxHandle, moduleHandle) {
774
- this.wasm = wasmModule;
775
- this.memory = wasmMemory;
776
- this.ctxHandle = ctxHandle;
777
- this.moduleHandle = moduleHandle;
778
- }
912
+ /**
913
+ * @param {*} wasmModule
914
+ * @param {WebAssembly.Memory} wasmMemory
915
+ * @param {number} ctxHandle
916
+ * @param {number} moduleHandle
917
+ */
918
+ constructor(wasmModule, wasmMemory, ctxHandle, moduleHandle) {
919
+ this.wasm = wasmModule;
920
+ this.memory = wasmMemory;
921
+ this.ctxHandle = ctxHandle;
922
+ this.moduleHandle = moduleHandle;
923
+ }
779
924
  }
780
925
 
781
926
  export class GPURenderPipeline {
782
- /**
783
- * @param {*} wasmModule
784
- * @param {WebAssembly.Memory} wasmMemory
785
- * @param {number} ctxHandle
786
- * @param {number} pipelineHandle
787
- */
788
- constructor(wasmModule, wasmMemory, ctxHandle, pipelineHandle) {
789
- this.wasm = wasmModule;
790
- this.memory = wasmMemory;
791
- this.ctxHandle = ctxHandle;
792
- this.pipelineHandle = pipelineHandle;
793
- }
927
+ /**
928
+ * @param {*} wasmModule
929
+ * @param {WebAssembly.Memory} wasmMemory
930
+ * @param {number} ctxHandle
931
+ * @param {number} pipelineHandle
932
+ */
933
+ constructor(wasmModule, wasmMemory, ctxHandle, pipelineHandle) {
934
+ this.wasm = wasmModule;
935
+ this.memory = wasmMemory;
936
+ this.ctxHandle = ctxHandle;
937
+ this.pipelineHandle = pipelineHandle;
938
+ }
939
+
940
+ /**
941
+ * @param {number} index
942
+ * @returns {GPUBindGroupLayout}
943
+ */
944
+ getBindGroupLayout(index) {
945
+ const layoutHandle = this.wasm.wasm_webgpu_pipeline_get_bind_group_layout(this.ctxHandle, this.pipelineHandle, index);
946
+ return new GPUBindGroupLayout(this.wasm, this.memory, this.ctxHandle, layoutHandle);
947
+ }
794
948
  }
795
949
 
796
950
  export class GPUCommandEncoder {
797
- /**
798
- * @param {*} wasmModule
799
- * @param {WebAssembly.Memory} wasmMemory
800
- * @param {number} ctxHandle
801
- * @param {number} encoderHandle
802
- */
803
- constructor(wasmModule, wasmMemory, ctxHandle, encoderHandle) {
804
- this.wasm = wasmModule;
805
- this.memory = wasmMemory;
806
- this.ctxHandle = ctxHandle;
807
- this.encoderHandle = encoderHandle;
808
- }
809
-
810
- /**
811
- * Copy data from one buffer to another
812
- * @param {GPUBuffer} source
813
- * @param {number} sourceOffset
814
- * @param {GPUBuffer} destination
815
- * @param {number} destinationOffset
816
- * @param {number} size
817
- */
818
- copyBufferToBuffer(source, sourceOffset, destination, destinationOffset, size) {
819
- this.wasm.wasm_webgpu_command_encoder_copy_buffer_to_buffer(
820
- this.ctxHandle,
821
- this.encoderHandle,
822
- source.bufferHandle,
823
- BigInt(sourceOffset),
824
- destination.bufferHandle,
825
- BigInt(destinationOffset),
826
- BigInt(size)
827
- );
828
- }
829
-
830
- /**
831
- * Copy texture to buffer
832
- * @param {Object} source
833
- * @param {Object} destination
834
- * @param {Object} size
835
- */
836
- copyTextureToBuffer(source, destination, size) {
837
- let width, height, depthOrArrayLayers;
838
- if (Array.isArray(size)) {
839
- width = size[0];
840
- height = size[1] || 1;
841
- depthOrArrayLayers = size[2] || 1;
842
- } else {
843
- width = size.width;
844
- height = size.height || 1;
845
- depthOrArrayLayers = size.depthOrArrayLayers || 1;
846
- }
847
-
848
- this.wasm.wasm_webgpu_command_encoder_copy_texture_to_buffer(
849
- this.ctxHandle,
850
- this.encoderHandle,
851
- source.texture.textureHandle,
852
- destination.buffer.bufferHandle,
853
- BigInt(destination.offset || 0),
854
- destination.bytesPerRow || 0,
855
- destination.rowsPerImage || 0,
856
- width,
857
- height,
858
- depthOrArrayLayers
859
- );
951
+ /**
952
+ * @param {*} wasmModule
953
+ * @param {WebAssembly.Memory} wasmMemory
954
+ * @param {number} ctxHandle
955
+ * @param {number} encoderHandle
956
+ */
957
+ constructor(wasmModule, wasmMemory, ctxHandle, encoderHandle) {
958
+ this.wasm = wasmModule;
959
+ this.memory = wasmMemory;
960
+ this.ctxHandle = ctxHandle;
961
+ this.encoderHandle = encoderHandle;
962
+ }
963
+
964
+ /**
965
+ * Copy data from one buffer to another
966
+ * @param {GPUBuffer} source
967
+ * @param {number} sourceOffset
968
+ * @param {GPUBuffer} destination
969
+ * @param {number} destinationOffset
970
+ * @param {number} size
971
+ */
972
+ copyBufferToBuffer(source, sourceOffset, destination, destinationOffset, size) {
973
+ this.wasm.wasm_webgpu_command_encoder_copy_buffer_to_buffer(
974
+ this.ctxHandle,
975
+ this.encoderHandle,
976
+ source.bufferHandle,
977
+ BigInt(sourceOffset),
978
+ destination.bufferHandle,
979
+ BigInt(destinationOffset),
980
+ BigInt(size)
981
+ );
982
+ }
983
+
984
+ /**
985
+ * Copy texture to buffer
986
+ * @param {Object} source
987
+ * @param {Object} destination
988
+ * @param {Object} size
989
+ */
990
+ copyTextureToBuffer(source, destination, size) {
991
+ let width, height, depthOrArrayLayers;
992
+ if (Array.isArray(size)) {
993
+ width = size[0];
994
+ height = size[1] || 1;
995
+ depthOrArrayLayers = size[2] || 1;
996
+ } else {
997
+ width = size.width;
998
+ height = size.height || 1;
999
+ depthOrArrayLayers = size.depthOrArrayLayers || 1;
860
1000
  }
861
1001
 
862
- /**
863
- * Begin a render pass
864
- * @param {Object} descriptor - Render pass descriptor
865
- * @returns {GPURenderPassEncoder}
866
- */
867
- beginRenderPass(descriptor) {
868
- return new GPURenderPassEncoder(this.wasm, this.memory, this.ctxHandle, this.encoderHandle, descriptor);
869
- }
870
-
871
- /**
872
- * Finish encoding and create a command buffer
873
- * @returns {GPUCommandBuffer}
874
- */
875
- finish() {
876
- const commandBufferHandle = this.wasm.wasm_webgpu_command_encoder_finish(this.ctxHandle, this.encoderHandle);
877
- if (commandBufferHandle === 0) {
878
- throw new Error("Failed to finish command encoder");
879
- }
880
- return new GPUCommandBuffer(this.wasm, this.memory, this.ctxHandle, commandBufferHandle);
1002
+ this.wasm.wasm_webgpu_command_encoder_copy_texture_to_buffer(
1003
+ this.ctxHandle,
1004
+ this.encoderHandle,
1005
+ source.texture.textureHandle,
1006
+ destination.buffer.bufferHandle,
1007
+ BigInt(destination.offset || 0),
1008
+ destination.bytesPerRow || 0,
1009
+ destination.rowsPerImage || 0,
1010
+ width,
1011
+ height,
1012
+ depthOrArrayLayers
1013
+ );
1014
+ }
1015
+
1016
+ /**
1017
+ * Begin a render pass
1018
+ * @param {Object} descriptor - Render pass descriptor
1019
+ * @returns {GPURenderPassEncoder}
1020
+ */
1021
+ beginRenderPass(descriptor) {
1022
+ const att = descriptor.colorAttachments[0];
1023
+ const loadOp = att.loadOp === 'clear' ? 1 : 0;
1024
+ const storeOp = att.storeOp === 'discard' ? 1 : 0;
1025
+ const clearColor = att.clearValue || { r: 0, g: 0, b: 0, a: 0 };
1026
+
1027
+ const passHandle = this.wasm.wasm_webgpu_command_encoder_begin_render_pass(
1028
+ this.ctxHandle,
1029
+ this.encoderHandle,
1030
+ att.view.viewHandle,
1031
+ loadOp,
1032
+ storeOp,
1033
+ clearColor.r,
1034
+ clearColor.g,
1035
+ clearColor.b,
1036
+ clearColor.a
1037
+ );
1038
+
1039
+ return new GPURenderPassEncoder(this.wasm, this.memory, this.ctxHandle, passHandle);
1040
+ }
1041
+
1042
+ /**
1043
+ * Finish encoding and create a command buffer
1044
+ * @returns {GPUCommandBuffer}
1045
+ */
1046
+ finish() {
1047
+ const commandBufferHandle = this.wasm.wasm_webgpu_command_encoder_finish(this.ctxHandle, this.encoderHandle);
1048
+ if (commandBufferHandle === 0) {
1049
+ throw new Error("Failed to finish command encoder");
881
1050
  }
1051
+ return new GPUCommandBuffer(this.wasm, this.memory, this.ctxHandle, commandBufferHandle);
1052
+ }
882
1053
  }
883
1054
 
884
1055
  export class GPURenderPassEncoder {
885
- /**
886
- * @param {*} wasmModule
887
- * @param {WebAssembly.Memory} wasmMemory
888
- * @param {number} ctxHandle
889
- * @param {number} encoderHandle
890
- * @param {Object} descriptor
891
- */
892
- constructor(wasmModule, wasmMemory, ctxHandle, encoderHandle, descriptor) {
893
- this.wasm = wasmModule;
894
- this.memory = wasmMemory;
895
- this.ctxHandle = ctxHandle;
896
- this.encoderHandle = encoderHandle;
897
- this.descriptor = descriptor;
898
- this.commands = [];
899
- }
900
-
901
- /**
902
- * Set the render pipeline
903
- * @param {GPURenderPipeline} pipeline
904
- */
905
- setPipeline(pipeline) {
906
- this.commands.push(1, pipeline.pipelineHandle);
907
- }
908
-
909
- /**
910
- * Set vertex buffer
911
- * @param {number} slot
912
- * @param {GPUBuffer} buffer
913
- * @param {number} offset
914
- * @param {number} size
915
- */
916
- setVertexBuffer(slot, buffer, offset = 0, size) {
917
- this.commands.push(2, slot, buffer.bufferHandle, offset, size || buffer.size);
918
- }
919
-
920
- /**
921
- * Set bind group
922
- * @param {number} index
923
- * @param {GPUBindGroup} bindGroup
924
- * @param {Array<number>} dynamicOffsets
925
- */
926
- setBindGroup(index, bindGroup, dynamicOffsets = []) {
927
- this.commands.push(4, index, bindGroup.bindGroupHandle);
928
- }
929
-
930
- /**
931
- * Set index buffer
932
- * @param {GPUBuffer} buffer
933
- * @param {string} indexFormat
934
- * @param {number} offset
935
- * @param {number} size
936
- */
937
- setIndexBuffer(buffer, indexFormat, offset = 0, size) {
938
- const formatId = indexFormat === 'uint32' ? 2 : 1;
939
- this.commands.push(5, buffer.bufferHandle, formatId, offset, size || (buffer.size - offset));
940
- }
941
-
942
- /**
943
- * Draw vertices
944
- * @param {number} vertexCount
945
- * @param {number} instanceCount
946
- * @param {number} firstVertex
947
- * @param {number} firstInstance
948
- */
949
- draw(vertexCount, instanceCount = 1, firstVertex = 0, firstInstance = 0) {
950
- this.commands.push(3, vertexCount, instanceCount, firstVertex, firstInstance);
951
- }
952
-
953
- /**
954
- * Draw indexed vertices
955
- * @param {number} indexCount
956
- * @param {number} instanceCount
957
- * @param {number} firstIndex
958
- * @param {number} baseVertex
959
- * @param {number} firstInstance
960
- */
961
- drawIndexed(indexCount, instanceCount = 1, firstIndex = 0, baseVertex = 0, firstInstance = 0) {
962
- this.commands.push(6, indexCount, instanceCount, firstIndex, baseVertex, firstInstance);
963
- }
964
-
965
- /**
966
- * Set viewport
967
- * @param {number} x
968
- * @param {number} y
969
- * @param {number} width
970
- * @param {number} height
971
- * @param {number} minDepth
972
- * @param {number} maxDepth
973
- */
974
- setViewport(x, y, width, height, minDepth, maxDepth) {
975
- // Use Float32Array to get bit representation of floats
976
- const f32 = new Float32Array(6);
977
- f32[0] = x;
978
- f32[1] = y;
979
- f32[2] = width;
980
- f32[3] = height;
981
- f32[4] = minDepth;
982
- f32[5] = maxDepth;
983
- const u32 = new Uint32Array(f32.buffer);
984
- this.commands.push(7, u32[0], u32[1], u32[2], u32[3], u32[4], u32[5]);
985
- }
986
-
987
- /**
988
- * Set scissor rectangle
989
- * @param {number} x
990
- * @param {number} y
991
- * @param {number} width
992
- * @param {number} height
993
- */
994
- setScissorRect(x, y, width, height) {
995
- this.commands.push(8, x, y, width, height);
996
- }
997
-
998
- /**
999
- * End the render pass
1000
- */
1001
- end() {
1002
- // Execute the render pass with all buffered commands
1003
- const att = this.descriptor.colorAttachments[0];
1004
- const loadOp = att.loadOp === 'clear' ? 1 : 0;
1005
- const storeOp = att.storeOp === 'discard' ? 1 : 0;
1006
- const clearColor = att.clearValue || { r: 0, g: 0, b: 0, a: 0 };
1007
-
1008
- const ptr = this.wasm.wasm_alloc(this.commands.length * 4);
1009
- const heapU32 = new Uint32Array(this.memory.buffer, ptr, this.commands.length);
1010
- heapU32.set(this.commands);
1011
-
1012
- this.wasm.wasm_webgpu_command_encoder_run_render_pass(
1013
- this.ctxHandle,
1014
- this.encoderHandle,
1015
- att.view.viewHandle,
1016
- loadOp,
1017
- storeOp,
1018
- clearColor.r,
1019
- clearColor.g,
1020
- clearColor.b,
1021
- clearColor.a,
1022
- ptr,
1023
- this.commands.length
1024
- );
1025
-
1026
- this.wasm.wasm_free(ptr, this.commands.length * 4);
1027
- }
1056
+ /**
1057
+ * @param {*} wasmModule
1058
+ * @param {WebAssembly.Memory} wasmMemory
1059
+ * @param {number} ctxHandle
1060
+ * @param {number} passHandle
1061
+ */
1062
+ constructor(wasmModule, wasmMemory, ctxHandle, passHandle) {
1063
+ this.wasm = wasmModule;
1064
+ this.memory = wasmMemory;
1065
+ this.ctxHandle = ctxHandle;
1066
+ this.passHandle = passHandle;
1067
+ }
1068
+
1069
+ /**
1070
+ * Set the render pipeline
1071
+ * @param {GPURenderPipeline} pipeline
1072
+ */
1073
+ setPipeline(pipeline) {
1074
+ this.wasm.wasm_webgpu_render_pass_set_pipeline(this.ctxHandle, this.passHandle, pipeline.pipelineHandle);
1075
+ }
1076
+
1077
+ /**
1078
+ * Set vertex buffer
1079
+ * @param {number} slot
1080
+ * @param {GPUBuffer} buffer
1081
+ * @param {number} offset
1082
+ * @param {number} size
1083
+ */
1084
+ setVertexBuffer(slot, buffer, offset = 0, size) {
1085
+ this.wasm.wasm_webgpu_render_pass_set_vertex_buffer(
1086
+ this.ctxHandle,
1087
+ this.passHandle,
1088
+ slot,
1089
+ buffer.bufferHandle,
1090
+ BigInt(offset),
1091
+ BigInt(size || buffer.size)
1092
+ );
1093
+ }
1094
+
1095
+ /**
1096
+ * Set bind group
1097
+ * @param {number} index
1098
+ * @param {GPUBindGroup} bindGroup
1099
+ * @param {Array<number>} dynamicOffsets
1100
+ */
1101
+ setBindGroup(index, bindGroup, dynamicOffsets = []) {
1102
+ this.wasm.wasm_webgpu_render_pass_set_bind_group(this.ctxHandle, this.passHandle, index, bindGroup.bindGroupHandle);
1103
+ }
1104
+
1105
+ /**
1106
+ * Set index buffer
1107
+ * @param {GPUBuffer} buffer
1108
+ * @param {string} indexFormat
1109
+ * @param {number} offset
1110
+ * @param {number} size
1111
+ */
1112
+ setIndexBuffer(buffer, indexFormat, offset = 0, size) {
1113
+ const formatId = indexFormat === 'uint32' ? 2 : 1;
1114
+ this.wasm.wasm_webgpu_render_pass_set_index_buffer(
1115
+ this.ctxHandle,
1116
+ this.passHandle,
1117
+ buffer.bufferHandle,
1118
+ formatId,
1119
+ BigInt(offset),
1120
+ BigInt(size || (buffer.size - offset))
1121
+ );
1122
+ }
1123
+
1124
+ /**
1125
+ * Draw vertices
1126
+ * @param {number} vertexCount
1127
+ * @param {number} instanceCount
1128
+ * @param {number} firstVertex
1129
+ * @param {number} firstInstance
1130
+ */
1131
+ draw(vertexCount, instanceCount = 1, firstVertex = 0, firstInstance = 0) {
1132
+ this.wasm.wasm_webgpu_render_pass_draw(this.ctxHandle, this.passHandle, vertexCount, instanceCount, firstVertex, firstInstance);
1133
+ }
1134
+
1135
+ /**
1136
+ * Draw indexed vertices
1137
+ * @param {number} indexCount
1138
+ * @param {number} instanceCount
1139
+ * @param {number} firstIndex
1140
+ * @param {number} baseVertex
1141
+ * @param {number} firstInstance
1142
+ */
1143
+ drawIndexed(indexCount, instanceCount = 1, firstIndex = 0, baseVertex = 0, firstInstance = 0) {
1144
+ this.wasm.wasm_webgpu_render_pass_draw_indexed(this.ctxHandle, this.passHandle, indexCount, instanceCount, firstIndex, baseVertex, firstInstance);
1145
+ }
1146
+
1147
+ /**
1148
+ * Set viewport
1149
+ * @param {number} x
1150
+ * @param {number} y
1151
+ * @param {number} width
1152
+ * @param {number} height
1153
+ * @param {number} minDepth
1154
+ * @param {number} maxDepth
1155
+ */
1156
+ setViewport(x, y, width, height, minDepth, maxDepth) {
1157
+ this.wasm.wasm_webgpu_render_pass_set_viewport(this.ctxHandle, this.passHandle, x, y, width, height, minDepth, maxDepth);
1158
+ }
1159
+
1160
+ /**
1161
+ * Set scissor rectangle
1162
+ * @param {number} x
1163
+ * @param {number} y
1164
+ * @param {number} width
1165
+ * @param {number} height
1166
+ */
1167
+ setScissorRect(x, y, width, height) {
1168
+ this.wasm.wasm_webgpu_render_pass_set_scissor_rect(this.ctxHandle, this.passHandle, x, y, width, height);
1169
+ }
1170
+
1171
+ /**
1172
+ * End the render pass
1173
+ */
1174
+ end() {
1175
+ this.wasm.wasm_webgpu_render_pass_end(this.ctxHandle, this.passHandle);
1176
+ }
1028
1177
  }
1029
1178
 
1030
1179
  export class GPUCommandBuffer {
1031
- /**
1032
- * @param {*} wasmModule
1033
- * @param {WebAssembly.Memory} wasmMemory
1034
- * @param {number} ctxHandle
1035
- * @param {number} commandBufferHandle
1036
- */
1037
- constructor(wasmModule, wasmMemory, ctxHandle, commandBufferHandle) {
1038
- this.wasm = wasmModule;
1039
- this.memory = wasmMemory;
1040
- this.ctxHandle = ctxHandle;
1041
- this.commandBufferHandle = commandBufferHandle;
1042
- }
1180
+ /**
1181
+ * @param {*} wasmModule
1182
+ * @param {WebAssembly.Memory} wasmMemory
1183
+ * @param {number} ctxHandle
1184
+ * @param {number} commandBufferHandle
1185
+ */
1186
+ constructor(wasmModule, wasmMemory, ctxHandle, commandBufferHandle) {
1187
+ this.wasm = wasmModule;
1188
+ this.memory = wasmMemory;
1189
+ this.ctxHandle = ctxHandle;
1190
+ this.commandBufferHandle = commandBufferHandle;
1191
+ }
1043
1192
  }
1044
1193
 
1045
1194
  export class GPUTexture {
1046
- /**
1047
- * @param {*} wasmModule
1048
- * @param {WebAssembly.Memory} wasmMemory
1049
- * @param {number} ctxHandle
1050
- * @param {number} deviceHandle
1051
- * @param {number} textureHandle
1052
- * @param {Object} descriptor
1053
- */
1054
- constructor(wasmModule, wasmMemory, ctxHandle, deviceHandle, textureHandle, descriptor) {
1055
- this.wasm = wasmModule;
1056
- this.memory = wasmMemory;
1057
- this.ctxHandle = ctxHandle;
1058
- this.deviceHandle = deviceHandle;
1059
- this.textureHandle = textureHandle;
1060
- this.width = descriptor.size.width;
1061
- this.height = descriptor.size.height;
1062
- this.depthOrArrayLayers = descriptor.size.depthOrArrayLayers || 1;
1063
- this.format = descriptor.format;
1064
- this.usage = descriptor.usage;
1195
+ /**
1196
+ * @param {*} wasmModule
1197
+ * @param {WebAssembly.Memory} wasmMemory
1198
+ * @param {number} ctxHandle
1199
+ * @param {number} deviceHandle
1200
+ * @param {number} textureHandle
1201
+ * @param {Object} descriptor
1202
+ */
1203
+ constructor(wasmModule, wasmMemory, ctxHandle, deviceHandle, textureHandle, descriptor) {
1204
+ this.wasm = wasmModule;
1205
+ this.memory = wasmMemory;
1206
+ this.ctxHandle = ctxHandle;
1207
+ this.deviceHandle = deviceHandle;
1208
+ this.textureHandle = textureHandle;
1209
+ this.width = descriptor.size.width;
1210
+ this.height = descriptor.size.height;
1211
+ this.depthOrArrayLayers = descriptor.size.depthOrArrayLayers || 1;
1212
+ this.format = descriptor.format;
1213
+ this.usage = descriptor.usage;
1214
+ }
1215
+
1216
+ /**
1217
+ * Create a view
1218
+ * @param {Object} descriptor
1219
+ * @returns {GPUTextureView}
1220
+ */
1221
+ createView(descriptor = {}) {
1222
+ const format = TEXTURE_FORMAT_MAP[descriptor.format] || 0; // 0 for default
1223
+ const dimensionMap = {
1224
+ '1d': 1,
1225
+ '2d': 2,
1226
+ '2d-array': 3,
1227
+ 'cube': 4,
1228
+ 'cube-array': 5,
1229
+ '3d': 6
1230
+ };
1231
+ const aspectMap = {
1232
+ 'all': 0,
1233
+ 'stencil-only': 1,
1234
+ 'depth-only': 2
1235
+ };
1236
+
1237
+ const viewHandle = this.wasm.wasm_webgpu_create_texture_view(
1238
+ this.ctxHandle,
1239
+ this.textureHandle,
1240
+ format,
1241
+ dimensionMap[descriptor.dimension] || 0,
1242
+ descriptor.baseMipLevel || 0,
1243
+ descriptor.mipLevelCount || 0,
1244
+ descriptor.baseArrayLayer || 0,
1245
+ descriptor.arrayLayerCount || 0,
1246
+ aspectMap[descriptor.aspect] || 0
1247
+ );
1248
+ if (viewHandle === 0) {
1249
+ // Error already captured by Rust
1065
1250
  }
1251
+ return new GPUTextureView(this.wasm, this.memory, this.ctxHandle, viewHandle, this);
1252
+ }
1066
1253
 
1067
- /**
1068
- * Create a view
1069
- * @param {Object} descriptor
1070
- * @returns {GPUTextureView}
1071
- */
1072
- createView(descriptor = {}) {
1073
- const viewHandle = this.wasm.wasm_webgpu_create_texture_view(
1074
- this.ctxHandle,
1075
- this.textureHandle
1076
- );
1077
- if (viewHandle === 0) {
1078
- // throw new Error("Failed to create texture view");
1079
- }
1080
- return new GPUTextureView(this.wasm, this.memory, this.ctxHandle, viewHandle, this);
1081
- }
1082
-
1083
- destroy() {
1084
- // TODO
1085
- }
1254
+ destroy() {
1255
+ this.wasm.wasm_webgpu_destroy_texture(this.ctxHandle, this.textureHandle);
1256
+ this.textureHandle = 0;
1257
+ }
1086
1258
  }
1087
1259
 
1088
1260
  export class GPUTextureView {
1089
- /**
1090
- * @param {*} wasmModule
1091
- * @param {WebAssembly.Memory} wasmMemory
1092
- * @param {number} ctxHandle
1093
- * @param {number} viewHandle
1094
- * @param {GPUTexture} texture
1095
- */
1096
- constructor(wasmModule, wasmMemory, ctxHandle, viewHandle, texture) {
1097
- this.wasm = wasmModule;
1098
- this.memory = wasmMemory;
1099
- this.ctxHandle = ctxHandle;
1100
- this.viewHandle = viewHandle;
1101
- this.texture = texture;
1102
- }
1261
+ /**
1262
+ * @param {*} wasmModule
1263
+ * @param {WebAssembly.Memory} wasmMemory
1264
+ * @param {number} ctxHandle
1265
+ * @param {number} viewHandle
1266
+ * @param {GPUTexture} texture
1267
+ */
1268
+ constructor(wasmModule, wasmMemory, ctxHandle, viewHandle, texture) {
1269
+ this.wasm = wasmModule;
1270
+ this.memory = wasmMemory;
1271
+ this.ctxHandle = ctxHandle;
1272
+ this.viewHandle = viewHandle;
1273
+ this.texture = texture;
1274
+ }
1103
1275
  }
1104
1276
 
1105
1277
  export class GPUBindGroupLayout {
1106
- constructor(wasmModule, wasmMemory, ctxHandle, layoutHandle) {
1107
- this.wasm = wasmModule;
1108
- this.memory = wasmMemory;
1109
- this.ctxHandle = ctxHandle;
1110
- this.layoutHandle = layoutHandle;
1111
- }
1278
+ constructor(wasmModule, wasmMemory, ctxHandle, layoutHandle) {
1279
+ this.wasm = wasmModule;
1280
+ this.memory = wasmMemory;
1281
+ this.ctxHandle = ctxHandle;
1282
+ this.layoutHandle = layoutHandle;
1283
+ }
1112
1284
  }
1113
1285
 
1114
1286
  export class GPUBindGroup {
1115
- constructor(wasmModule, wasmMemory, ctxHandle, bindGroupHandle) {
1116
- this.wasm = wasmModule;
1117
- this.memory = wasmMemory;
1118
- this.ctxHandle = ctxHandle;
1119
- this.bindGroupHandle = bindGroupHandle;
1120
- }
1287
+ constructor(wasmModule, wasmMemory, ctxHandle, bindGroupHandle) {
1288
+ this.wasm = wasmModule;
1289
+ this.memory = wasmMemory;
1290
+ this.ctxHandle = ctxHandle;
1291
+ this.bindGroupHandle = bindGroupHandle;
1292
+ }
1121
1293
  }
1122
1294
 
1123
1295
  export class GPUSampler {
1124
- constructor(wasmModule, wasmMemory, ctxHandle, samplerHandle) {
1125
- this.wasm = wasmModule;
1126
- this.memory = wasmMemory;
1127
- this.ctxHandle = ctxHandle;
1128
- this.samplerHandle = samplerHandle;
1129
- }
1296
+ constructor(wasmModule, wasmMemory, ctxHandle, samplerHandle) {
1297
+ this.wasm = wasmModule;
1298
+ this.memory = wasmMemory;
1299
+ this.ctxHandle = ctxHandle;
1300
+ this.samplerHandle = samplerHandle;
1301
+ }
1130
1302
  }
1131
1303
 
1132
1304
  /**
@@ -1136,127 +1308,148 @@ export class GPUSampler {
1136
1308
  * @returns {GPU}
1137
1309
  */
1138
1310
  export function createWebGPU(wasmModule, wasmMemory) {
1139
- return new GPU(wasmModule, wasmMemory);
1311
+ return new GPU(wasmModule, wasmMemory);
1140
1312
  }
1141
1313
 
1142
1314
  export class GPUCanvasContext {
1143
- /**
1144
- * @param {*} wasmModule
1145
- * @param {WebAssembly.Memory} wasmMemory
1146
- * @param {HTMLCanvasElement} canvas
1147
- */
1148
- constructor(wasmModule, wasmMemory, canvas) {
1149
- this.wasm = wasmModule;
1150
- this.memory = wasmMemory;
1151
- this.canvas = canvas;
1152
- this.device = null;
1153
- this.format = 'rgba8unorm';
1154
- this.usage = GPUTextureUsage.RENDER_ATTACHMENT;
1155
- this.width = canvas.width;
1156
- this.height = canvas.height;
1315
+ /**
1316
+ * @param {*} wasmModule
1317
+ * @param {WebAssembly.Memory} wasmMemory
1318
+ * @param {HTMLCanvasElement} canvas
1319
+ */
1320
+ constructor(wasmModule, wasmMemory, canvas) {
1321
+ this.wasm = wasmModule;
1322
+ this.memory = wasmMemory;
1323
+ this.canvas = canvas;
1324
+ this.device = null;
1325
+ this.format = 'rgba8unorm';
1326
+ this.usage = GPUTextureUsage.RENDER_ATTACHMENT;
1327
+ this.width = canvas.width;
1328
+ this.height = canvas.height;
1329
+ }
1330
+
1331
+ /**
1332
+ * Configure the context
1333
+ * @param {Object} descriptor
1334
+ */
1335
+ configure(descriptor) {
1336
+ this.device = descriptor.device;
1337
+ this.format = descriptor.format || 'rgba8unorm';
1338
+ this.usage = descriptor.usage || GPUTextureUsage.RENDER_ATTACHMENT;
1339
+ this.alphaMode = descriptor.alphaMode || 'opaque';
1340
+
1341
+ // Resize canvas internal buffer if needed
1342
+ if (this.canvas.width !== this.width || this.canvas.height !== this.height) {
1343
+ this.width = this.canvas.width;
1344
+ this.height = this.canvas.height;
1157
1345
  }
1158
-
1159
- /**
1160
- * Configure the context
1161
- * @param {Object} descriptor
1162
- */
1163
- configure(descriptor) {
1164
- this.device = descriptor.device;
1165
- this.format = descriptor.format || 'rgba8unorm';
1166
- this.usage = descriptor.usage || GPUTextureUsage.RENDER_ATTACHMENT;
1167
- this.alphaMode = descriptor.alphaMode || 'opaque';
1168
-
1169
- // Resize canvas internal buffer if needed
1170
- if (this.canvas.width !== this.width || this.canvas.height !== this.height) {
1171
- this.width = this.canvas.width;
1172
- this.height = this.canvas.height;
1173
- }
1346
+ }
1347
+
1348
+ unconfigure() {
1349
+ this.device = null;
1350
+ }
1351
+
1352
+ /**
1353
+ * Get the current texture to render into
1354
+ * @returns {GPUTexture}
1355
+ */
1356
+ getCurrentTexture() {
1357
+ if (!this.device) {
1358
+ throw new Error("Context not configured");
1174
1359
  }
1175
1360
 
1176
- unconfigure() {
1177
- this.device = null;
1178
- }
1179
-
1180
- /**
1181
- * Get the current texture to render into
1182
- * @returns {GPUTexture}
1183
- */
1184
- getCurrentTexture() {
1185
- if (!this.device) {
1186
- throw new Error("Context not configured");
1187
- }
1188
-
1189
- // Create a temporary texture that represents the canvas surface
1190
- // In a real implementation, this would be a managed swapchain texture.
1191
- // For SoftApi, we just create a regular texture that we will present later.
1192
- return this.device.createTexture({
1193
- size: { width: this.width, height: this.height, depthOrArrayLayers: 1 },
1194
- format: this.format,
1195
- usage: this.usage | GPUTextureUsage.COPY_SRC
1196
- });
1197
- }
1198
-
1199
- /**
1200
- * Present the current texture to the canvas
1201
- * This is a non-standard method for our Soft-GPU to bridge to the browser display.
1202
- * @param {GPUTexture} texture
1203
- */
1204
- present(texture) {
1205
- const ctx2d = this.canvas.getContext('2d');
1206
- if (!ctx2d) return;
1207
-
1208
- const width = texture.width;
1209
- const height = texture.height;
1210
-
1211
- // Use readPixels-like logic to get data from WASM
1212
- const len = width * height * 4;
1213
-
1214
- // We need a buffer to copy the texture to
1215
- const buffer = this.device.createBuffer({
1216
- size: len,
1217
- usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ
1218
- });
1219
-
1220
- const encoder = this.device.createCommandEncoder();
1221
- encoder.copyTextureToBuffer(
1222
- { texture: texture },
1223
- { buffer: buffer, bytesPerRow: width * 4 },
1224
- { width, height, depthOrArrayLayers: 1 }
1225
- );
1226
- this.device.queue.submit([encoder.finish()]);
1227
-
1228
- // Map and copy to canvas
1229
- buffer.mapAsync(GPUMapMode.READ).then(() => {
1230
- const data = buffer.getMappedRange();
1231
- const clamped = new Uint8ClampedArray(data.buffer, data.byteOffset, data.byteLength);
1232
- const imageData = new ImageData(clamped, width, height);
1233
- ctx2d.putImageData(imageData, 0, 0);
1234
- buffer.unmap();
1235
- buffer.destroy();
1236
- texture.destroy();
1237
- });
1238
- }
1361
+ // Create a temporary texture that represents the canvas surface
1362
+ // In a real implementation, this would be a managed swapchain texture.
1363
+ // For SoftApi, we just create a regular texture that we will present later.
1364
+ return this.device.createTexture({
1365
+ size: { width: this.width, height: this.height, depthOrArrayLayers: 1 },
1366
+ format: this.format,
1367
+ usage: this.usage | GPUTextureUsage.COPY_SRC
1368
+ });
1369
+ }
1370
+
1371
+ /**
1372
+ * Present the current texture to the canvas
1373
+ * This is a non-standard method for our Soft-GPU to bridge to the browser display.
1374
+ * @param {GPUTexture} texture
1375
+ */
1376
+ present(texture) {
1377
+ const ctx2d = this.canvas.getContext('2d');
1378
+ if (!ctx2d) return;
1379
+
1380
+ const width = texture.width;
1381
+ const height = texture.height;
1382
+
1383
+ // Use readPixels-like logic to get data from WASM
1384
+ const len = width * height * 4;
1385
+
1386
+ // We need a buffer to copy the texture to
1387
+ const buffer = this.device.createBuffer({
1388
+ size: len,
1389
+ usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ
1390
+ });
1391
+
1392
+ const encoder = this.device.createCommandEncoder();
1393
+ encoder.copyTextureToBuffer(
1394
+ { texture: texture },
1395
+ { buffer: buffer, bytesPerRow: width * 4 },
1396
+ { width, height, depthOrArrayLayers: 1 }
1397
+ );
1398
+ this.device.queue.submit([encoder.finish()]);
1399
+
1400
+ // Map and copy to canvas
1401
+ buffer.mapAsync(GPUMapMode.READ).then(() => {
1402
+ const data = buffer.getMappedRange();
1403
+ const clamped = new Uint8ClampedArray(data.buffer, data.byteOffset, data.byteLength);
1404
+ const imageData = new ImageData(clamped, width, height);
1405
+ ctx2d.putImageData(imageData, 0, 0);
1406
+ buffer.unmap();
1407
+ buffer.destroy();
1408
+ texture.destroy();
1409
+ });
1410
+ }
1239
1411
  }
1240
1412
 
1241
1413
  export class GPUPipelineLayout {
1242
- constructor(wasmModule, wasmMemory, ctxHandle, layoutHandle) {
1243
- this.wasm = wasmModule;
1244
- this.memory = wasmMemory;
1245
- this.ctxHandle = ctxHandle;
1246
- this.layoutHandle = layoutHandle;
1247
- }
1414
+ constructor(wasmModule, wasmMemory, ctxHandle, layoutHandle) {
1415
+ this.wasm = wasmModule;
1416
+ this.memory = wasmMemory;
1417
+ this.ctxHandle = ctxHandle;
1418
+ this.layoutHandle = layoutHandle;
1419
+ }
1248
1420
  }
1249
1421
 
1250
1422
  function readString(memory, ptr) {
1251
- if (!ptr) return null;
1252
- const view = new Uint8Array(memory.buffer);
1253
- let end = ptr;
1254
- while (view[end]) end++;
1255
- return new TextDecoder().decode(view.subarray(ptr, end));
1423
+ if (!ptr) return null;
1424
+ const view = new Uint8Array(memory.buffer);
1425
+ let end = ptr;
1426
+ while (view[end]) end++;
1427
+ return new TextDecoder().decode(view.subarray(ptr, end));
1256
1428
  }
1257
1429
 
1258
1430
  export class GPUError {
1259
- constructor(message) {
1260
- this.message = message;
1261
- }
1431
+ constructor(message) {
1432
+ this.message = message;
1433
+ }
1434
+ }
1435
+
1436
+ export class GPUValidationError extends GPUError {
1437
+ constructor(message) {
1438
+ super(message);
1439
+ this.name = 'GPUValidationError';
1440
+ }
1441
+ }
1442
+
1443
+ export class GPUInternalError extends GPUError {
1444
+ constructor(message) {
1445
+ super(message);
1446
+ this.name = 'GPUInternalError';
1447
+ }
1448
+ }
1449
+
1450
+ export class GPUOutOfMemoryError extends GPUError {
1451
+ constructor(message) {
1452
+ super(message);
1453
+ this.name = 'GPUOutOfMemoryError';
1454
+ }
1262
1455
  }