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.
- package/coverage.md +34 -43
- package/demo.js +13 -5
- package/index.js +66 -27
- package/package.json +5 -3
- package/src/webgl2_context.js +2559 -2483
- package/src/webgl2_resources.js +91 -0
- package/src/webgpu_context.js +1291 -1098
- package/webgl2.debug.wasm +0 -0
- package/webgl2.wasm +0 -0
package/src/webgpu_context.js
CHANGED
|
@@ -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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
export const GPUMapMode = {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
export const GPUTextureUsage = {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
export const GPUShaderStage = {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
-
|
|
121
|
-
|
|
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
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
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
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
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
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
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
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
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
|
-
|
|
308
|
-
|
|
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
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
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
|
-
|
|
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
|
-
|
|
322
|
-
// throw new Error("Failed to create pipeline layout");
|
|
323
|
-
}
|
|
430
|
+
this.wasm.wasm_free(ptr, layouts.length * 4);
|
|
324
431
|
|
|
325
|
-
|
|
432
|
+
if (handle === 0) {
|
|
433
|
+
// throw new Error("Failed to create pipeline layout");
|
|
326
434
|
}
|
|
327
435
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
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
|
-
|
|
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
|
-
|
|
472
|
-
|
|
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
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
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
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
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
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
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
|
-
|
|
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
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
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
|
-
|
|
613
|
+
this.wasm.wasm_free(ptr, data.length * 4);
|
|
616
614
|
|
|
617
|
-
|
|
615
|
+
if (handle === 0) {
|
|
616
|
+
// Error already captured by Rust
|
|
618
617
|
}
|
|
619
618
|
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
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
|
-
|
|
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
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
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
|
-
|
|
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
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
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
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
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
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
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
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
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
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
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
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
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
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
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
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
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
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
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
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
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
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
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
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
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
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
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
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
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
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
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
|
-
|
|
1311
|
+
return new GPU(wasmModule, wasmMemory);
|
|
1140
1312
|
}
|
|
1141
1313
|
|
|
1142
1314
|
export class GPUCanvasContext {
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
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
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
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
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
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
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
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
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
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
|
-
|
|
1260
|
-
|
|
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
|
}
|