@simulatte/webgpu 0.3.1 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +27 -12
- package/LICENSE +191 -0
- package/README.md +55 -41
- package/api-contract.md +67 -49
- package/architecture.md +317 -0
- package/assets/package-layers.svg +3 -3
- package/docs/doe-api-reference.html +1842 -0
- package/doe-api-design.md +237 -0
- package/examples/doe-api/README.md +19 -0
- package/examples/doe-api/buffers-readback.js +3 -2
- package/examples/{doe-routines/compute-once-like-input.js → doe-api/compute-one-shot-like-input.js} +1 -1
- package/examples/{doe-routines/compute-once-matmul.js → doe-api/compute-one-shot-matmul.js} +2 -2
- package/examples/{doe-routines/compute-once-multiple-inputs.js → doe-api/compute-one-shot-multiple-inputs.js} +1 -1
- package/examples/{doe-routines/compute-once.js → doe-api/compute-one-shot.js} +1 -1
- package/examples/doe-api/{compile-and-dispatch.js → kernel-create-and-dispatch.js} +4 -6
- package/examples/doe-api/{compute-dispatch.js → kernel-run.js} +4 -6
- package/headless-webgpu-comparison.md +3 -3
- package/jsdoc-style-guide.md +435 -0
- package/native/doe_napi.c +1481 -84
- package/package.json +18 -6
- package/prebuilds/darwin-arm64/doe_napi.node +0 -0
- package/prebuilds/darwin-arm64/libwebgpu_doe.dylib +0 -0
- package/prebuilds/darwin-arm64/metadata.json +5 -5
- package/prebuilds/linux-x64/metadata.json +1 -1
- package/scripts/generate-doe-api-docs.js +1607 -0
- package/scripts/generate-readme-assets.js +3 -3
- package/src/build_metadata.js +7 -4
- package/src/bun-ffi.js +1229 -474
- package/src/bun.js +5 -1
- package/src/compute.d.ts +16 -7
- package/src/compute.js +84 -53
- package/src/full.d.ts +16 -7
- package/src/full.js +12 -10
- package/src/index.js +679 -1324
- package/src/runtime_cli.js +17 -17
- package/src/shared/capabilities.js +144 -0
- package/src/shared/compiler-errors.js +78 -0
- package/src/shared/encoder-surface.js +295 -0
- package/src/shared/full-surface.js +514 -0
- package/src/shared/public-surface.js +82 -0
- package/src/shared/resource-lifecycle.js +120 -0
- package/src/shared/validation.js +495 -0
- package/src/webgpu_constants.js +30 -0
- package/support-contracts.md +2 -2
- package/compat-scope.md +0 -46
- package/layering-plan.md +0 -259
- package/src/auto_bind_group_layout.js +0 -32
- package/src/doe.d.ts +0 -184
- package/src/doe.js +0 -641
- package/zig-source-inventory.md +0 -468
|
@@ -0,0 +1,514 @@
|
|
|
1
|
+
import {
|
|
2
|
+
UINT32_MAX,
|
|
3
|
+
failValidation,
|
|
4
|
+
initResource,
|
|
5
|
+
assertObject,
|
|
6
|
+
assertArray,
|
|
7
|
+
assertNonEmptyString,
|
|
8
|
+
assertIntegerInRange,
|
|
9
|
+
assertLiveResource,
|
|
10
|
+
destroyResource,
|
|
11
|
+
} from './resource-lifecycle.js';
|
|
12
|
+
import {
|
|
13
|
+
assertBufferDescriptor,
|
|
14
|
+
assertTextureSize,
|
|
15
|
+
assertBindGroupResource,
|
|
16
|
+
normalizeTextureDimension,
|
|
17
|
+
normalizeBindGroupLayoutEntry,
|
|
18
|
+
} from './validation.js';
|
|
19
|
+
import {
|
|
20
|
+
shaderCheckFailure,
|
|
21
|
+
} from './compiler-errors.js';
|
|
22
|
+
|
|
23
|
+
function validateWriteBufferInput(data, dataOffset, size, path) {
|
|
24
|
+
assertIntegerInRange(dataOffset, path, 'dataOffset', { min: 0 });
|
|
25
|
+
if (
|
|
26
|
+
!ArrayBuffer.isView(data)
|
|
27
|
+
&& !(data instanceof ArrayBuffer)
|
|
28
|
+
&& !Buffer.isBuffer(data)
|
|
29
|
+
) {
|
|
30
|
+
failValidation(path, 'data must be a TypedArray, DataView, ArrayBuffer, or Buffer');
|
|
31
|
+
}
|
|
32
|
+
if (dataOffset === 0 && size === undefined) {
|
|
33
|
+
return data;
|
|
34
|
+
}
|
|
35
|
+
if (!ArrayBuffer.isView(data)) {
|
|
36
|
+
failValidation(path, 'dataOffset and size slicing require a TypedArray or DataView input');
|
|
37
|
+
}
|
|
38
|
+
if (size !== undefined) {
|
|
39
|
+
assertIntegerInRange(size, path, 'size', { min: 0 });
|
|
40
|
+
}
|
|
41
|
+
const elementSize = data.BYTES_PER_ELEMENT || 1;
|
|
42
|
+
const byteOffset = data.byteOffset + dataOffset * elementSize;
|
|
43
|
+
const byteLength = size !== undefined
|
|
44
|
+
? size * elementSize
|
|
45
|
+
: data.byteLength - dataOffset * elementSize;
|
|
46
|
+
return new Uint8Array(data.buffer, byteOffset, byteLength);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function createFullSurfaceClasses({
|
|
50
|
+
globals,
|
|
51
|
+
backend,
|
|
52
|
+
}) {
|
|
53
|
+
let classes = null;
|
|
54
|
+
|
|
55
|
+
class DoeGPUBuffer {
|
|
56
|
+
constructor(native, instance, size, usage, queue, owner) {
|
|
57
|
+
this._native = native;
|
|
58
|
+
this._instance = instance;
|
|
59
|
+
this._queue = queue;
|
|
60
|
+
this.size = size;
|
|
61
|
+
this.usage = usage;
|
|
62
|
+
initResource(this, 'GPUBuffer', owner);
|
|
63
|
+
if (backend.initBufferState) {
|
|
64
|
+
backend.initBufferState(this);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async mapAsync(mode, offset = 0, size = Math.max(0, this.size - offset)) {
|
|
69
|
+
const native = assertLiveResource(this, 'GPUBuffer.mapAsync', 'GPUBuffer');
|
|
70
|
+
assertIntegerInRange(mode, 'GPUBuffer.mapAsync', 'mode', { min: 0, max: UINT32_MAX });
|
|
71
|
+
assertIntegerInRange(offset, 'GPUBuffer.mapAsync', 'offset', { min: 0 });
|
|
72
|
+
assertIntegerInRange(size, 'GPUBuffer.mapAsync', 'size', { min: 0 });
|
|
73
|
+
if (offset + size > this.size) {
|
|
74
|
+
failValidation('GPUBuffer.mapAsync', `mapped range ${offset}+${size} exceeds buffer size ${this.size}`);
|
|
75
|
+
}
|
|
76
|
+
await backend.bufferMapAsync(this, native, mode, offset, size);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
getMappedRange(offset = 0, size = Math.max(0, this.size - offset)) {
|
|
80
|
+
const native = assertLiveResource(this, 'GPUBuffer.getMappedRange', 'GPUBuffer');
|
|
81
|
+
assertIntegerInRange(offset, 'GPUBuffer.getMappedRange', 'offset', { min: 0 });
|
|
82
|
+
assertIntegerInRange(size, 'GPUBuffer.getMappedRange', 'size', { min: 0 });
|
|
83
|
+
if (offset + size > this.size) {
|
|
84
|
+
failValidation('GPUBuffer.getMappedRange', `mapped range ${offset}+${size} exceeds buffer size ${this.size}`);
|
|
85
|
+
}
|
|
86
|
+
return backend.bufferGetMappedRange(this, native, offset, size);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
assertMappedPrefixF32(expected, count) {
|
|
90
|
+
const native = assertLiveResource(this, 'GPUBuffer.assertMappedPrefixF32', 'GPUBuffer');
|
|
91
|
+
assertIntegerInRange(count, 'GPUBuffer.assertMappedPrefixF32', 'count', { min: 0, max: UINT32_MAX });
|
|
92
|
+
if (Array.isArray(expected)) {
|
|
93
|
+
if (expected.length < count) {
|
|
94
|
+
failValidation('GPUBuffer.assertMappedPrefixF32', `expected array must contain at least ${count} values`);
|
|
95
|
+
}
|
|
96
|
+
const actual = new Float32Array(this.getMappedRange(0, count * Float32Array.BYTES_PER_ELEMENT));
|
|
97
|
+
for (let index = 0; index < count; index += 1) {
|
|
98
|
+
if (actual[index] !== expected[index]) {
|
|
99
|
+
failValidation(
|
|
100
|
+
'GPUBuffer.assertMappedPrefixF32',
|
|
101
|
+
`expected readback[${index}] === ${expected[index]}, got ${actual[index]}`,
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (typeof expected !== 'number') {
|
|
108
|
+
failValidation('GPUBuffer.assertMappedPrefixF32', 'expected must be a number or array of numbers');
|
|
109
|
+
}
|
|
110
|
+
if (typeof backend.bufferAssertMappedPrefixF32 === 'function') {
|
|
111
|
+
return backend.bufferAssertMappedPrefixF32(this, native, expected, count);
|
|
112
|
+
}
|
|
113
|
+
const actual = new Float32Array(this.getMappedRange(0, count * Float32Array.BYTES_PER_ELEMENT));
|
|
114
|
+
for (let index = 0; index < count; index += 1) {
|
|
115
|
+
if (actual[index] !== expected) {
|
|
116
|
+
failValidation(
|
|
117
|
+
'GPUBuffer.assertMappedPrefixF32',
|
|
118
|
+
`expected readback[${index}] === ${expected}, got ${actual[index]}`,
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
unmap() {
|
|
125
|
+
backend.bufferUnmap(assertLiveResource(this, 'GPUBuffer.unmap', 'GPUBuffer'), this);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
destroy() {
|
|
129
|
+
destroyResource(this, (native) => backend.bufferDestroy(native, this));
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
class DoeGPUQueue {
|
|
134
|
+
constructor(native, instance, device) {
|
|
135
|
+
this._native = native;
|
|
136
|
+
this._instance = instance;
|
|
137
|
+
this._device = device;
|
|
138
|
+
initResource(this, 'GPUQueue', device);
|
|
139
|
+
if (backend.initQueueState) {
|
|
140
|
+
backend.initQueueState(this);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
hasPendingSubmissions() {
|
|
145
|
+
assertLiveResource(this, 'GPUQueue.hasPendingSubmissions', 'GPUQueue');
|
|
146
|
+
return backend.queueHasPendingSubmissions(this);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
markSubmittedWorkDone() {
|
|
150
|
+
assertLiveResource(this, 'GPUQueue.markSubmittedWorkDone', 'GPUQueue');
|
|
151
|
+
backend.queueMarkSubmittedWorkDone(this);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
submit(commandBuffers) {
|
|
155
|
+
const native = assertLiveResource(this, 'GPUQueue.submit', 'GPUQueue');
|
|
156
|
+
const buffers = assertArray(commandBuffers, 'GPUQueue.submit', 'commandBuffers');
|
|
157
|
+
if (buffers.length === 0) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
return backend.queueSubmit(this, native, buffers);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
writeBuffer(buffer, bufferOffset, data, dataOffset = 0, size) {
|
|
164
|
+
const native = assertLiveResource(this, 'GPUQueue.writeBuffer', 'GPUQueue');
|
|
165
|
+
const bufferNative = assertLiveResource(buffer, 'GPUQueue.writeBuffer', 'GPUBuffer');
|
|
166
|
+
assertIntegerInRange(bufferOffset, 'GPUQueue.writeBuffer', 'bufferOffset', { min: 0 });
|
|
167
|
+
const view = validateWriteBufferInput(data, dataOffset, size, 'GPUQueue.writeBuffer');
|
|
168
|
+
return backend.queueWriteBuffer(this, native, bufferNative, bufferOffset, view);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async onSubmittedWorkDone() {
|
|
172
|
+
const native = assertLiveResource(this, 'GPUQueue.onSubmittedWorkDone', 'GPUQueue');
|
|
173
|
+
if (!this.hasPendingSubmissions()) {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
await backend.queueOnSubmittedWorkDone(this, native);
|
|
177
|
+
this.markSubmittedWorkDone();
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
class DoeGPUTexture {
|
|
182
|
+
constructor(native, owner) {
|
|
183
|
+
this._native = native;
|
|
184
|
+
initResource(this, 'GPUTexture', owner);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
createView(descriptor) {
|
|
188
|
+
const view = backend.textureCreateView(this, assertLiveResource(this, 'GPUTexture.createView', 'GPUTexture'), descriptor);
|
|
189
|
+
return new DoeGPUTextureView(view, this);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
destroy() {
|
|
193
|
+
destroyResource(this, (native) => backend.textureDestroy(native, this));
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
class DoeGPUTextureView {
|
|
198
|
+
constructor(native, owner) {
|
|
199
|
+
this._native = native;
|
|
200
|
+
initResource(this, 'GPUTextureView', owner);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
class DoeGPUSampler {
|
|
205
|
+
constructor(native, owner) {
|
|
206
|
+
this._native = native;
|
|
207
|
+
initResource(this, 'GPUSampler', owner);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
class DoeGPURenderPipeline {
|
|
212
|
+
constructor(native, owner) {
|
|
213
|
+
this._native = native;
|
|
214
|
+
initResource(this, 'GPURenderPipeline', owner);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
class DoeGPUShaderModule {
|
|
219
|
+
constructor(native, code, owner) {
|
|
220
|
+
this._native = native;
|
|
221
|
+
this._code = code;
|
|
222
|
+
initResource(this, 'GPUShaderModule', owner);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
destroy() {
|
|
226
|
+
if (typeof backend.shaderModuleDestroy !== 'function') {
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
destroyResource(this, (native) => backend.shaderModuleDestroy(native, this));
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
class DoeGPUComputePipeline {
|
|
234
|
+
constructor(native, device, explicitLayout, autoLayoutEntriesByGroup) {
|
|
235
|
+
this._native = native;
|
|
236
|
+
this._device = device;
|
|
237
|
+
this._explicitLayout = explicitLayout;
|
|
238
|
+
this._autoLayoutEntriesByGroup = autoLayoutEntriesByGroup;
|
|
239
|
+
this._cachedLayouts = new Map();
|
|
240
|
+
initResource(this, 'GPUComputePipeline', device);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
getBindGroupLayout(index) {
|
|
244
|
+
assertLiveResource(this, 'GPUComputePipeline.getBindGroupLayout', 'GPUComputePipeline');
|
|
245
|
+
assertIntegerInRange(index, 'GPUComputePipeline.getBindGroupLayout', 'index', { min: 0, max: UINT32_MAX });
|
|
246
|
+
if (this._explicitLayout) {
|
|
247
|
+
return this._explicitLayout;
|
|
248
|
+
}
|
|
249
|
+
if (this._cachedLayouts.has(index)) {
|
|
250
|
+
return this._cachedLayouts.get(index);
|
|
251
|
+
}
|
|
252
|
+
const layout = backend.computePipelineGetBindGroupLayout(this, index, classes);
|
|
253
|
+
this._cachedLayouts.set(index, layout);
|
|
254
|
+
return layout;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
class DoeGPUBindGroupLayout {
|
|
259
|
+
constructor(native, owner) {
|
|
260
|
+
this._native = native;
|
|
261
|
+
initResource(this, 'GPUBindGroupLayout', owner);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
class DoeGPUBindGroup {
|
|
266
|
+
constructor(native, owner) {
|
|
267
|
+
this._native = native;
|
|
268
|
+
initResource(this, 'GPUBindGroup', owner);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
class DoeGPUPipelineLayout {
|
|
273
|
+
constructor(native, owner) {
|
|
274
|
+
this._native = native;
|
|
275
|
+
initResource(this, 'GPUPipelineLayout', owner);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
class DoeGPUQuerySet {
|
|
280
|
+
constructor(native, type, count, owner) {
|
|
281
|
+
this._native = native;
|
|
282
|
+
this.type = type;
|
|
283
|
+
this.count = count;
|
|
284
|
+
initResource(this, 'GPUQuerySet', owner);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
destroy() {
|
|
288
|
+
destroyResource(this, (native) => backend.querySetDestroy(native));
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
class DoeGPUDevice {
|
|
293
|
+
constructor(native, instance, inheritedLimits = null, inheritedFeatures = null) {
|
|
294
|
+
this._native = native;
|
|
295
|
+
this._instance = instance;
|
|
296
|
+
initResource(this, 'GPUDevice');
|
|
297
|
+
this.queue = new DoeGPUQueue(backend.deviceGetQueue(native), instance, this);
|
|
298
|
+
this.limits = inheritedLimits ?? backend.deviceLimits(native);
|
|
299
|
+
this.features = inheritedFeatures ?? backend.deviceFeatures(native);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
createBuffer(descriptor) {
|
|
303
|
+
const validated = assertBufferDescriptor(descriptor, 'GPUDevice.createBuffer');
|
|
304
|
+
const native = backend.deviceCreateBuffer(this, validated);
|
|
305
|
+
const buffer = new DoeGPUBuffer(native, this._instance, validated.size, validated.usage, this.queue, this);
|
|
306
|
+
if (validated.mappedAtCreation && typeof backend.bufferMarkMappedAtCreation === 'function') {
|
|
307
|
+
backend.bufferMarkMappedAtCreation(buffer);
|
|
308
|
+
}
|
|
309
|
+
return buffer;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
createShaderModule(descriptor) {
|
|
313
|
+
const objectDescriptor = assertObject(descriptor, 'GPUDevice.createShaderModule', 'descriptor');
|
|
314
|
+
const code = objectDescriptor.code ?? objectDescriptor.source;
|
|
315
|
+
assertNonEmptyString(code, 'GPUDevice.createShaderModule', 'descriptor.code');
|
|
316
|
+
const preflight = backend.preflightShaderSource(code);
|
|
317
|
+
if (!preflight.ok) {
|
|
318
|
+
shaderCheckFailure('GPUDevice.createShaderModule', preflight);
|
|
319
|
+
}
|
|
320
|
+
const native = backend.deviceCreateShaderModule(this, code);
|
|
321
|
+
return new DoeGPUShaderModule(native, code, this);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
createComputePipeline(descriptor) {
|
|
325
|
+
const pipelineDescriptor = assertObject(descriptor, 'GPUDevice.createComputePipeline', 'descriptor');
|
|
326
|
+
const compute = assertObject(pipelineDescriptor.compute, 'GPUDevice.createComputePipeline', 'descriptor.compute');
|
|
327
|
+
const shader = compute.module;
|
|
328
|
+
const shaderNative = assertLiveResource(shader, 'GPUDevice.createComputePipeline', 'GPUShaderModule');
|
|
329
|
+
const entryPoint = compute.entryPoint ?? 'main';
|
|
330
|
+
assertNonEmptyString(entryPoint, 'GPUDevice.createComputePipeline', 'descriptor.compute.entryPoint');
|
|
331
|
+
const layout = pipelineDescriptor.layout === 'auto' || pipelineDescriptor.layout === undefined
|
|
332
|
+
? null
|
|
333
|
+
: pipelineDescriptor.layout;
|
|
334
|
+
if (layout !== null) {
|
|
335
|
+
assertLiveResource(layout, 'GPUDevice.createComputePipeline', 'GPUPipelineLayout');
|
|
336
|
+
}
|
|
337
|
+
const autoLayoutEntriesByGroup = layout
|
|
338
|
+
? null
|
|
339
|
+
: backend.requireAutoLayoutEntriesFromNative(
|
|
340
|
+
shader,
|
|
341
|
+
globals.GPUShaderStage.COMPUTE,
|
|
342
|
+
'GPUDevice.createComputePipeline',
|
|
343
|
+
);
|
|
344
|
+
const native = backend.deviceCreateComputePipeline(this, shaderNative, entryPoint, layout?._native ?? null);
|
|
345
|
+
return new DoeGPUComputePipeline(native, this, layout, autoLayoutEntriesByGroup);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
async createComputePipelineAsync(descriptor) {
|
|
349
|
+
return this.createComputePipeline(descriptor);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
createBindGroupLayout(descriptor) {
|
|
353
|
+
const layoutDescriptor = assertObject(descriptor, 'GPUDevice.createBindGroupLayout', 'descriptor');
|
|
354
|
+
const entries = assertArray(layoutDescriptor.entries ?? [], 'GPUDevice.createBindGroupLayout', 'descriptor.entries')
|
|
355
|
+
.map((entry, index) => normalizeBindGroupLayoutEntry(entry, index, 'GPUDevice.createBindGroupLayout'));
|
|
356
|
+
const native = backend.deviceCreateBindGroupLayout(this, entries);
|
|
357
|
+
return new DoeGPUBindGroupLayout(native, this);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
createBindGroup(descriptor) {
|
|
361
|
+
const bindGroupDescriptor = assertObject(descriptor, 'GPUDevice.createBindGroup', 'descriptor');
|
|
362
|
+
const layoutNative = assertLiveResource(bindGroupDescriptor.layout, 'GPUDevice.createBindGroup', 'GPUBindGroupLayout');
|
|
363
|
+
const entries = assertArray(bindGroupDescriptor.entries ?? [], 'GPUDevice.createBindGroup', 'descriptor.entries')
|
|
364
|
+
.map((entry, index) => {
|
|
365
|
+
const binding = assertObject(entry, 'GPUDevice.createBindGroup', `descriptor.entries[${index}]`);
|
|
366
|
+
const resource = assertBindGroupResource(binding.resource, 'GPUDevice.createBindGroup');
|
|
367
|
+
const normalized = {
|
|
368
|
+
binding: assertIntegerInRange(binding.binding, 'GPUDevice.createBindGroup', `descriptor.entries[${index}].binding`, { min: 0, max: UINT32_MAX }),
|
|
369
|
+
buffer: resource.buffer,
|
|
370
|
+
sampler: resource.sampler,
|
|
371
|
+
textureView: resource.textureView,
|
|
372
|
+
offset: resource.offset ?? 0,
|
|
373
|
+
};
|
|
374
|
+
if (resource.size !== undefined) {
|
|
375
|
+
normalized.size = resource.size;
|
|
376
|
+
}
|
|
377
|
+
return normalized;
|
|
378
|
+
});
|
|
379
|
+
const native = backend.deviceCreateBindGroup(this, layoutNative, entries);
|
|
380
|
+
return new DoeGPUBindGroup(native, this);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
createPipelineLayout(descriptor) {
|
|
384
|
+
const layoutDescriptor = assertObject(descriptor, 'GPUDevice.createPipelineLayout', 'descriptor');
|
|
385
|
+
const layouts = assertArray(layoutDescriptor.bindGroupLayouts ?? [], 'GPUDevice.createPipelineLayout', 'descriptor.bindGroupLayouts')
|
|
386
|
+
.map((layout, index) => assertLiveResource(layout, 'GPUDevice.createPipelineLayout', `descriptor.bindGroupLayouts[${index}]`));
|
|
387
|
+
const native = backend.deviceCreatePipelineLayout(this, layouts);
|
|
388
|
+
return new DoeGPUPipelineLayout(native, this);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
createTexture(descriptor) {
|
|
392
|
+
const textureDescriptor = assertObject(descriptor, 'GPUDevice.createTexture', 'descriptor');
|
|
393
|
+
const size = assertTextureSize(textureDescriptor.size, 'GPUDevice.createTexture');
|
|
394
|
+
const usage = assertIntegerInRange(textureDescriptor.usage, 'GPUDevice.createTexture', 'descriptor.usage', { min: 1 });
|
|
395
|
+
const dimension = normalizeTextureDimension(textureDescriptor.dimension, 'GPUDevice.createTexture');
|
|
396
|
+
const native = backend.deviceCreateTexture(this, {
|
|
397
|
+
...textureDescriptor,
|
|
398
|
+
dimension,
|
|
399
|
+
}, size, usage);
|
|
400
|
+
return new DoeGPUTexture(native, this);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
createSampler(descriptor = {}) {
|
|
404
|
+
assertObject(descriptor, 'GPUDevice.createSampler', 'descriptor');
|
|
405
|
+
const native = backend.deviceCreateSampler(this, descriptor);
|
|
406
|
+
return new DoeGPUSampler(native, this);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
createRenderPipeline(descriptor) {
|
|
410
|
+
const renderDescriptor = assertObject(descriptor, 'GPUDevice.createRenderPipeline', 'descriptor');
|
|
411
|
+
const vertex = assertObject(renderDescriptor.vertex, 'GPUDevice.createRenderPipeline', 'descriptor.vertex');
|
|
412
|
+
const fragment = assertObject(renderDescriptor.fragment, 'GPUDevice.createRenderPipeline', 'descriptor.fragment');
|
|
413
|
+
const vertexModule = assertLiveResource(vertex.module, 'GPUDevice.createRenderPipeline', 'GPUShaderModule');
|
|
414
|
+
const fragmentModule = assertLiveResource(fragment.module, 'GPUDevice.createRenderPipeline', 'GPUShaderModule');
|
|
415
|
+
const targets = assertArray(fragment.targets ?? [], 'GPUDevice.createRenderPipeline', 'descriptor.fragment.targets');
|
|
416
|
+
if (targets.length === 0) {
|
|
417
|
+
failValidation('GPUDevice.createRenderPipeline', 'descriptor.fragment.targets must contain at least one target');
|
|
418
|
+
}
|
|
419
|
+
const vertexBuffers = vertex.buffers === undefined
|
|
420
|
+
? []
|
|
421
|
+
: assertArray(vertex.buffers, 'GPUDevice.createRenderPipeline', 'descriptor.vertex.buffers');
|
|
422
|
+
const layout = renderDescriptor.layout && renderDescriptor.layout !== 'auto'
|
|
423
|
+
? assertLiveResource(renderDescriptor.layout, 'GPUDevice.createRenderPipeline', 'GPUPipelineLayout')
|
|
424
|
+
: null;
|
|
425
|
+
const native = backend.deviceCreateRenderPipeline(this, {
|
|
426
|
+
layout,
|
|
427
|
+
vertexModule,
|
|
428
|
+
vertexEntryPoint: vertex.entryPoint ?? 'main',
|
|
429
|
+
vertexBuffers,
|
|
430
|
+
fragmentModule,
|
|
431
|
+
fragmentEntryPoint: fragment.entryPoint ?? 'main',
|
|
432
|
+
colorFormat: assertNonEmptyString(targets[0].format, 'GPUDevice.createRenderPipeline', 'descriptor.fragment.targets[0].format'),
|
|
433
|
+
primitive: renderDescriptor.primitive ?? null,
|
|
434
|
+
depthStencil: renderDescriptor.depthStencil ?? null,
|
|
435
|
+
multisample: renderDescriptor.multisample ?? null,
|
|
436
|
+
});
|
|
437
|
+
return new DoeGPURenderPipeline(native, this);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
createQuerySet(descriptor) {
|
|
441
|
+
assertLiveResource(this, 'GPUDevice.createQuerySet', 'GPUDevice');
|
|
442
|
+
const queryDescriptor = assertObject(descriptor, 'GPUDevice.createQuerySet', 'descriptor');
|
|
443
|
+
if (queryDescriptor.type !== 'timestamp') {
|
|
444
|
+
failValidation('GPUDevice.createQuerySet', `unsupported query type "${queryDescriptor.type}"; only "timestamp" is supported`);
|
|
445
|
+
}
|
|
446
|
+
assertIntegerInRange(queryDescriptor.count, 'GPUDevice.createQuerySet', 'descriptor.count', { min: 1, max: UINT32_MAX });
|
|
447
|
+
const native = backend.deviceCreateQuerySet(this, queryDescriptor);
|
|
448
|
+
return new DoeGPUQuerySet(native, queryDescriptor.type, queryDescriptor.count, this);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
createCommandEncoder(descriptor) {
|
|
452
|
+
if (descriptor !== undefined) {
|
|
453
|
+
assertObject(descriptor, 'GPUDevice.createCommandEncoder', 'descriptor');
|
|
454
|
+
}
|
|
455
|
+
assertLiveResource(this, 'GPUDevice.createCommandEncoder', 'GPUDevice');
|
|
456
|
+
return backend.deviceCreateCommandEncoder(this, descriptor, classes);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
destroy() {
|
|
460
|
+
destroyResource(this, (native) => backend.deviceDestroy(native));
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
class DoeGPUAdapter {
|
|
465
|
+
constructor(native, instance) {
|
|
466
|
+
this._native = native;
|
|
467
|
+
this._instance = instance;
|
|
468
|
+
this.features = backend.adapterFeatures(native);
|
|
469
|
+
this.limits = backend.adapterLimits(native);
|
|
470
|
+
initResource(this, 'GPUAdapter');
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
async requestDevice(descriptor) {
|
|
474
|
+
return backend.adapterRequestDevice(this, descriptor, classes);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
destroy() {
|
|
478
|
+
destroyResource(this, (native) => backend.adapterDestroy(native));
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
class DoeGPU {
|
|
483
|
+
constructor(instance) {
|
|
484
|
+
this._instance = instance;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
async requestAdapter(options) {
|
|
488
|
+
return backend.gpuRequestAdapter(this, options, classes);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
classes = {
|
|
493
|
+
DoeGPUBuffer,
|
|
494
|
+
DoeGPUQueue,
|
|
495
|
+
DoeGPUTexture,
|
|
496
|
+
DoeGPUTextureView,
|
|
497
|
+
DoeGPUSampler,
|
|
498
|
+
DoeGPURenderPipeline,
|
|
499
|
+
DoeGPUShaderModule,
|
|
500
|
+
DoeGPUComputePipeline,
|
|
501
|
+
DoeGPUBindGroupLayout,
|
|
502
|
+
DoeGPUBindGroup,
|
|
503
|
+
DoeGPUPipelineLayout,
|
|
504
|
+
DoeGPUQuerySet,
|
|
505
|
+
DoeGPUDevice,
|
|
506
|
+
DoeGPUAdapter,
|
|
507
|
+
DoeGPU,
|
|
508
|
+
};
|
|
509
|
+
return classes;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
export {
|
|
513
|
+
createFullSurfaceClasses,
|
|
514
|
+
};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
function setupGlobalsOnTarget(target, gpu, globals) {
|
|
2
|
+
for (const [name, value] of Object.entries(globals)) {
|
|
3
|
+
if (target[name] === undefined) {
|
|
4
|
+
Object.defineProperty(target, name, {
|
|
5
|
+
value,
|
|
6
|
+
writable: true,
|
|
7
|
+
configurable: true,
|
|
8
|
+
enumerable: false,
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
if (typeof target.navigator === 'undefined') {
|
|
13
|
+
Object.defineProperty(target, 'navigator', {
|
|
14
|
+
value: { gpu },
|
|
15
|
+
writable: true,
|
|
16
|
+
configurable: true,
|
|
17
|
+
enumerable: false,
|
|
18
|
+
});
|
|
19
|
+
} else if (!target.navigator.gpu) {
|
|
20
|
+
Object.defineProperty(target.navigator, 'gpu', {
|
|
21
|
+
value: gpu,
|
|
22
|
+
writable: true,
|
|
23
|
+
configurable: true,
|
|
24
|
+
enumerable: false,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
return gpu;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function requestAdapterFromCreate(create, adapterOptions = undefined, createArgs = null) {
|
|
31
|
+
const gpu = create(createArgs);
|
|
32
|
+
return gpu.requestAdapter(adapterOptions);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function requestDeviceFromRequestAdapter(requestAdapter, options = {}) {
|
|
36
|
+
const createArgs = options?.createArgs ?? null;
|
|
37
|
+
const adapter = await requestAdapter(options?.adapterOptions, createArgs);
|
|
38
|
+
return adapter.requestDevice(options?.deviceDescriptor);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function buildProviderInfo({
|
|
42
|
+
moduleName = '@simulatte/webgpu',
|
|
43
|
+
loaded,
|
|
44
|
+
loadError,
|
|
45
|
+
defaultCreateArgs = [],
|
|
46
|
+
doeNative,
|
|
47
|
+
libraryFlavor,
|
|
48
|
+
doeLibraryPath,
|
|
49
|
+
buildMetadataSource,
|
|
50
|
+
buildMetadataPath,
|
|
51
|
+
leanVerifiedBuild,
|
|
52
|
+
proofArtifactSha256,
|
|
53
|
+
}) {
|
|
54
|
+
return {
|
|
55
|
+
module: moduleName,
|
|
56
|
+
loaded,
|
|
57
|
+
loadError,
|
|
58
|
+
defaultCreateArgs,
|
|
59
|
+
doeNative,
|
|
60
|
+
libraryFlavor,
|
|
61
|
+
doeLibraryPath,
|
|
62
|
+
buildMetadataSource,
|
|
63
|
+
buildMetadataPath,
|
|
64
|
+
leanVerifiedBuild,
|
|
65
|
+
proofArtifactSha256,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function libraryFlavor(libraryPath) {
|
|
70
|
+
if (!libraryPath) return 'missing';
|
|
71
|
+
if (/libwebgpu_doe\.(so|dylib|dll)$/.test(libraryPath)) return 'doe-dropin';
|
|
72
|
+
if (/lib(webgpu|webgpu_dawn|wgpu_native)\.(so|dylib|dll)/.test(libraryPath)) return 'delegate';
|
|
73
|
+
return 'unknown';
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export {
|
|
77
|
+
setupGlobalsOnTarget,
|
|
78
|
+
requestAdapterFromCreate,
|
|
79
|
+
requestDeviceFromRequestAdapter,
|
|
80
|
+
buildProviderInfo,
|
|
81
|
+
libraryFlavor,
|
|
82
|
+
};
|