@simulatte/webgpu 0.3.0 → 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 +37 -10
- package/LICENSE +191 -0
- package/README.md +62 -48
- 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
package/src/index.js
CHANGED
|
@@ -2,15 +2,66 @@ import { createRequire } from 'node:module';
|
|
|
2
2
|
import { existsSync } from 'node:fs';
|
|
3
3
|
import { resolve, dirname } from 'node:path';
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { globals } from './webgpu_constants.js';
|
|
5
6
|
import {
|
|
6
7
|
createDoeRuntime as createDoeRuntimeCli,
|
|
7
8
|
runDawnVsDoeCompare as runDawnVsDoeCompareCli,
|
|
8
9
|
} from './runtime_cli.js';
|
|
9
10
|
import { loadDoeBuildMetadata } from './build_metadata.js';
|
|
10
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
UINT32_MAX,
|
|
13
|
+
failValidation,
|
|
14
|
+
describeResourceLabel,
|
|
15
|
+
initResource,
|
|
16
|
+
assertObject,
|
|
17
|
+
assertArray,
|
|
18
|
+
assertBoolean,
|
|
19
|
+
assertNonEmptyString,
|
|
20
|
+
assertIntegerInRange,
|
|
21
|
+
assertOptionalIntegerInRange,
|
|
22
|
+
validatePositiveInteger,
|
|
23
|
+
assertLiveResource,
|
|
24
|
+
destroyResource,
|
|
25
|
+
} from './shared/resource-lifecycle.js';
|
|
26
|
+
import {
|
|
27
|
+
publishLimits,
|
|
28
|
+
publishFeatures,
|
|
29
|
+
} from './shared/capabilities.js';
|
|
30
|
+
import {
|
|
31
|
+
assertBufferDescriptor,
|
|
32
|
+
assertTextureSize,
|
|
33
|
+
assertBindGroupResource,
|
|
34
|
+
normalizeSamplerLayout,
|
|
35
|
+
normalizeTextureLayout,
|
|
36
|
+
normalizeStorageTextureLayout,
|
|
37
|
+
autoLayoutEntriesFromNativeBindings,
|
|
38
|
+
} from './shared/validation.js';
|
|
39
|
+
import {
|
|
40
|
+
setupGlobalsOnTarget,
|
|
41
|
+
requestAdapterFromCreate,
|
|
42
|
+
requestDeviceFromRequestAdapter,
|
|
43
|
+
buildProviderInfo,
|
|
44
|
+
libraryFlavor,
|
|
45
|
+
} from './shared/public-surface.js';
|
|
46
|
+
import {
|
|
47
|
+
shaderCheckFailure,
|
|
48
|
+
enrichNativeCompilerError,
|
|
49
|
+
compilerErrorFromMessage,
|
|
50
|
+
} from './shared/compiler-errors.js';
|
|
51
|
+
import {
|
|
52
|
+
createFullSurfaceClasses,
|
|
53
|
+
} from './shared/full-surface.js';
|
|
54
|
+
import {
|
|
55
|
+
createEncoderClasses,
|
|
56
|
+
} from './shared/encoder-surface.js';
|
|
11
57
|
|
|
12
58
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
13
59
|
const require = createRequire(import.meta.url);
|
|
60
|
+
const TEXTURE_DIMENSION_MAP = Object.freeze({
|
|
61
|
+
'1d': 1,
|
|
62
|
+
'2d': 2,
|
|
63
|
+
'3d': 3,
|
|
64
|
+
});
|
|
14
65
|
|
|
15
66
|
const addon = loadAddon();
|
|
16
67
|
const DOE_LIB_PATH = resolveDoeLibraryPath();
|
|
@@ -20,6 +71,9 @@ const DOE_BUILD_METADATA = loadDoeBuildMetadata({
|
|
|
20
71
|
});
|
|
21
72
|
let libraryLoaded = false;
|
|
22
73
|
|
|
74
|
+
export { globals, preflightShaderSource };
|
|
75
|
+
|
|
76
|
+
|
|
23
77
|
function loadAddon() {
|
|
24
78
|
const prebuildPath = resolve(__dirname, '..', 'prebuilds', `${process.platform}-${process.arch}`, 'doe_napi.node');
|
|
25
79
|
try {
|
|
@@ -44,8 +98,8 @@ function resolveDoeLibraryPath() {
|
|
|
44
98
|
const candidates = [
|
|
45
99
|
process.env.DOE_WEBGPU_LIB,
|
|
46
100
|
process.env.FAWN_DOE_LIB,
|
|
47
|
-
resolve(__dirname, '..', 'prebuilds', `${process.platform}-${process.arch}`, `libwebgpu_doe.${ext}`),
|
|
48
101
|
resolve(__dirname, '..', '..', '..', 'zig', 'zig-out', 'lib', `libwebgpu_doe.${ext}`),
|
|
102
|
+
resolve(__dirname, '..', 'prebuilds', `${process.platform}-${process.arch}`, `libwebgpu_doe.${ext}`),
|
|
49
103
|
resolve(process.cwd(), 'zig', 'zig-out', 'lib', `libwebgpu_doe.${ext}`),
|
|
50
104
|
];
|
|
51
105
|
|
|
@@ -55,17 +109,6 @@ function resolveDoeLibraryPath() {
|
|
|
55
109
|
return null;
|
|
56
110
|
}
|
|
57
111
|
|
|
58
|
-
function libraryFlavor(libraryPath) {
|
|
59
|
-
if (!libraryPath) return 'missing';
|
|
60
|
-
if (libraryPath.endsWith('libwebgpu_doe.so') || libraryPath.endsWith('libwebgpu_doe.dylib') || libraryPath.endsWith('libwebgpu_doe.dll')) {
|
|
61
|
-
return 'doe-dropin';
|
|
62
|
-
}
|
|
63
|
-
if (libraryPath.endsWith('libwebgpu.so') || libraryPath.endsWith('libwebgpu.dylib') || libraryPath.endsWith('libwebgpu_dawn.so') || libraryPath.endsWith('libwgpu_native.so') || libraryPath.endsWith('libwgpu_native.so.0')) {
|
|
64
|
-
return 'delegate';
|
|
65
|
-
}
|
|
66
|
-
return 'unknown';
|
|
67
|
-
}
|
|
68
|
-
|
|
69
112
|
function ensureLibrary() {
|
|
70
113
|
if (libraryLoaded) return;
|
|
71
114
|
if (!addon) {
|
|
@@ -82,524 +125,434 @@ function ensureLibrary() {
|
|
|
82
125
|
libraryLoaded = true;
|
|
83
126
|
}
|
|
84
127
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
* This is a package-local copy of the enum tables commonly needed by Node and
|
|
89
|
-
* Bun callers that want WebGPU constants without relying on browser globals.
|
|
90
|
-
*
|
|
91
|
-
* This example shows the API in its basic form.
|
|
92
|
-
*
|
|
93
|
-
* ```js
|
|
94
|
-
* import { globals } from "@simulatte/webgpu";
|
|
95
|
-
*
|
|
96
|
-
* const usage = globals.GPUBufferUsage.STORAGE | globals.GPUBufferUsage.COPY_DST;
|
|
97
|
-
* ```
|
|
98
|
-
*
|
|
99
|
-
* - These values mirror the standard WebGPU numeric constants.
|
|
100
|
-
* - They do not install themselves on `globalThis`; use `setupGlobals(...)` if needed.
|
|
101
|
-
* - `@simulatte/webgpu/compute` shares the same constants even though its device facade is narrower.
|
|
102
|
-
*/
|
|
103
|
-
export const globals = {
|
|
104
|
-
GPUBufferUsage: {
|
|
105
|
-
MAP_READ: 0x0001,
|
|
106
|
-
MAP_WRITE: 0x0002,
|
|
107
|
-
COPY_SRC: 0x0004,
|
|
108
|
-
COPY_DST: 0x0008,
|
|
109
|
-
INDEX: 0x0010,
|
|
110
|
-
VERTEX: 0x0020,
|
|
111
|
-
UNIFORM: 0x0040,
|
|
112
|
-
STORAGE: 0x0080,
|
|
113
|
-
INDIRECT: 0x0100,
|
|
114
|
-
QUERY_RESOLVE: 0x0200,
|
|
115
|
-
},
|
|
116
|
-
GPUShaderStage: {
|
|
117
|
-
VERTEX: 0x1,
|
|
118
|
-
FRAGMENT: 0x2,
|
|
119
|
-
COMPUTE: 0x4,
|
|
120
|
-
},
|
|
121
|
-
GPUMapMode: {
|
|
122
|
-
READ: 0x0001,
|
|
123
|
-
WRITE: 0x0002,
|
|
124
|
-
},
|
|
125
|
-
GPUTextureUsage: {
|
|
126
|
-
COPY_SRC: 0x01,
|
|
127
|
-
COPY_DST: 0x02,
|
|
128
|
-
TEXTURE_BINDING: 0x04,
|
|
129
|
-
STORAGE_BINDING: 0x08,
|
|
130
|
-
RENDER_ATTACHMENT: 0x10,
|
|
131
|
-
},
|
|
132
|
-
};
|
|
128
|
+
function validateBufferDescriptor(descriptor) {
|
|
129
|
+
return assertBufferDescriptor(descriptor, 'GPUDevice.createBuffer');
|
|
130
|
+
}
|
|
133
131
|
|
|
134
132
|
/**
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
* This example shows the API in its basic form.
|
|
141
|
-
*
|
|
142
|
-
* ```js
|
|
143
|
-
* const buffer = device.createBuffer({
|
|
144
|
-
* size: 16,
|
|
145
|
-
* usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
|
|
146
|
-
* });
|
|
147
|
-
* ```
|
|
148
|
-
*
|
|
149
|
-
* - `size` and `usage` are copied onto the JS object for convenience.
|
|
150
|
-
* - Destroying the buffer releases the native handle but does not remove the JS object itself.
|
|
133
|
+
* Read structured error fields from the native N-API addon's last-error ABI.
|
|
134
|
+
* Uses `addon.getLastErrorLine` / `addon.getLastErrorColumn` when available
|
|
135
|
+
* (requires native build that exports `doeNativeGetLastErrorLine/Column`).
|
|
136
|
+
* Returns null when the addon does not expose these functions.
|
|
151
137
|
*/
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
138
|
+
function readLastErrorFields() {
|
|
139
|
+
if (typeof addon?.getLastErrorStage !== 'function' && typeof addon?.getLastErrorKind !== 'function') {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
const stage = typeof addon?.getLastErrorStage === 'function' ? (addon.getLastErrorStage() ?? '') : '';
|
|
143
|
+
const kind = typeof addon?.getLastErrorKind === 'function' ? (addon.getLastErrorKind() ?? '') : '';
|
|
144
|
+
const line = typeof addon?.getLastErrorLine === 'function' ? Number(addon.getLastErrorLine()) : 0;
|
|
145
|
+
const column = typeof addon?.getLastErrorColumn === 'function' ? Number(addon.getLastErrorColumn()) : 0;
|
|
146
|
+
return {
|
|
147
|
+
stage: stage || undefined,
|
|
148
|
+
kind: kind || undefined,
|
|
149
|
+
line: line > 0 ? line : undefined,
|
|
150
|
+
column: column > 0 ? column : undefined,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
160
153
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
* This resolves after Doe has flushed any pending queue work needed to make
|
|
165
|
-
* the requested range readable or writable from JS.
|
|
166
|
-
*
|
|
167
|
-
* This example shows the API in its basic form.
|
|
168
|
-
*
|
|
169
|
-
* ```js
|
|
170
|
-
* await buffer.mapAsync(GPUMapMode.READ);
|
|
171
|
-
* ```
|
|
172
|
-
*
|
|
173
|
-
* - When `size` is omitted, Doe maps the remaining bytes from `offset` to the end of the buffer.
|
|
174
|
-
* - When the queue still has pending submissions, Doe flushes them before mapping.
|
|
175
|
-
*/
|
|
176
|
-
async mapAsync(mode, offset = 0, size = Math.max(0, this.size - offset)) {
|
|
177
|
-
if (this._queue) {
|
|
178
|
-
if (this._queue.hasPendingSubmissions()) {
|
|
179
|
-
addon.flushAndMapSync(this._instance, this._queue._native, this._native, mode, offset, size);
|
|
180
|
-
this._queue.markSubmittedWorkDone();
|
|
181
|
-
} else {
|
|
182
|
-
addon.bufferMapSync(this._instance, this._native, mode, offset, size);
|
|
183
|
-
}
|
|
184
|
-
} else {
|
|
185
|
-
addon.bufferMapSync(this._instance, this._native, mode, offset, size);
|
|
186
|
-
}
|
|
154
|
+
function adapterLimits(native) {
|
|
155
|
+
if (typeof addon?.adapterGetLimits !== 'function') {
|
|
156
|
+
return publishLimits(null);
|
|
187
157
|
}
|
|
158
|
+
return publishLimits(addon.adapterGetLimits(native));
|
|
159
|
+
}
|
|
188
160
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
* This exposes the mapped bytes as an `ArrayBuffer`-backed view after a
|
|
193
|
-
* successful `mapAsync(...)` call.
|
|
194
|
-
*
|
|
195
|
-
* This example shows the API in its basic form.
|
|
196
|
-
*
|
|
197
|
-
* ```js
|
|
198
|
-
* const bytes = buffer.getMappedRange();
|
|
199
|
-
* ```
|
|
200
|
-
*
|
|
201
|
-
* - Call this only while the buffer is mapped.
|
|
202
|
-
* - When `size` is omitted, Doe returns the remaining bytes from `offset` to the end of the buffer.
|
|
203
|
-
*/
|
|
204
|
-
getMappedRange(offset = 0, size = Math.max(0, this.size - offset)) {
|
|
205
|
-
return addon.bufferGetMappedRange(this._native, offset, size);
|
|
161
|
+
function deviceLimits(native) {
|
|
162
|
+
if (typeof addon?.deviceGetLimits !== 'function') {
|
|
163
|
+
return publishLimits(null);
|
|
206
164
|
}
|
|
165
|
+
return publishLimits(addon.deviceGetLimits(native));
|
|
166
|
+
}
|
|
207
167
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
*
|
|
216
|
-
* ```js
|
|
217
|
-
* buffer.assertMappedPrefixF32([1, 2, 3, 4], 4);
|
|
218
|
-
* ```
|
|
219
|
-
*
|
|
220
|
-
* - The buffer must already be mapped.
|
|
221
|
-
* - This checks only the requested prefix rather than the whole buffer.
|
|
222
|
-
*/
|
|
223
|
-
assertMappedPrefixF32(expected, count) {
|
|
224
|
-
return addon.bufferAssertMappedPrefixF32(this._native, expected, count);
|
|
225
|
-
}
|
|
168
|
+
function adapterFeatures(native) {
|
|
169
|
+
return publishFeatures(
|
|
170
|
+
typeof addon?.adapterHasFeature === 'function'
|
|
171
|
+
? (feature) => addon.adapterHasFeature(native, feature)
|
|
172
|
+
: null,
|
|
173
|
+
);
|
|
174
|
+
}
|
|
226
175
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
176
|
+
function deviceFeatures(native) {
|
|
177
|
+
return publishFeatures(
|
|
178
|
+
typeof addon?.deviceHasFeature === 'function'
|
|
179
|
+
? (feature) => addon.deviceHasFeature(native, feature)
|
|
180
|
+
: null,
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function preflightShaderSource(code) {
|
|
185
|
+
ensureLibrary();
|
|
186
|
+
if (typeof addon?.checkShaderSource === 'function') {
|
|
187
|
+
const result = addon.checkShaderSource(code);
|
|
188
|
+
if (result && typeof result === 'object') {
|
|
189
|
+
const out = {
|
|
190
|
+
ok: result.ok !== false,
|
|
191
|
+
stage: result.stage ?? '',
|
|
192
|
+
kind: result.kind ?? '',
|
|
193
|
+
message: result.message ?? '',
|
|
194
|
+
reasons: result.ok === false && result.message ? [result.message] : [],
|
|
195
|
+
};
|
|
196
|
+
if (typeof result.line === 'number' && result.line > 0) out.line = result.line;
|
|
197
|
+
if (typeof result.column === 'number' && result.column > 0) out.column = result.column;
|
|
198
|
+
return out;
|
|
199
|
+
}
|
|
243
200
|
}
|
|
201
|
+
return { ok: true, stage: '', kind: '', message: '', reasons: [] };
|
|
202
|
+
}
|
|
244
203
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
* This example shows the API in its basic form.
|
|
252
|
-
*
|
|
253
|
-
* ```js
|
|
254
|
-
* buffer.destroy();
|
|
255
|
-
* ```
|
|
256
|
-
*
|
|
257
|
-
* - Reusing a destroyed buffer is unsupported.
|
|
258
|
-
* - The wrapper remains reachable in JS but no longer owns a live native handle.
|
|
259
|
-
*/
|
|
260
|
-
destroy() {
|
|
261
|
-
addon.bufferRelease(this._native);
|
|
262
|
-
this._native = null;
|
|
204
|
+
function requireAutoLayoutEntriesFromNative(shaderNative, visibility, path) {
|
|
205
|
+
if (typeof addon?.shaderModuleGetBindings !== 'function') {
|
|
206
|
+
failValidation(
|
|
207
|
+
path,
|
|
208
|
+
'layout: "auto" requires native shader binding metadata on this package surface'
|
|
209
|
+
);
|
|
263
210
|
}
|
|
211
|
+
const bindings = addon.shaderModuleGetBindings(shaderNative);
|
|
212
|
+
if (!Array.isArray(bindings)) {
|
|
213
|
+
failValidation(
|
|
214
|
+
path,
|
|
215
|
+
'layout: "auto" could not read native shader binding metadata'
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
return autoLayoutEntriesFromNativeBindings(bindings, visibility);
|
|
264
219
|
}
|
|
265
220
|
|
|
221
|
+
|
|
266
222
|
/**
|
|
267
|
-
*
|
|
223
|
+
* Standard WebGPU enum objects exposed by the Doe package runtime.
|
|
268
224
|
*
|
|
269
|
-
*
|
|
225
|
+
* These package-local shared enum tables are commonly needed by Node and Bun
|
|
226
|
+
* callers that want WebGPU constants without relying on browser globals.
|
|
270
227
|
*
|
|
271
228
|
* This example shows the API in its basic form.
|
|
272
229
|
*
|
|
273
230
|
* ```js
|
|
274
|
-
*
|
|
275
|
-
*
|
|
231
|
+
* import { globals } from "@simulatte/webgpu";
|
|
232
|
+
*
|
|
233
|
+
* const usage = globals.GPUBufferUsage.STORAGE | globals.GPUBufferUsage.COPY_DST;
|
|
276
234
|
* ```
|
|
277
235
|
*
|
|
278
|
-
* -
|
|
279
|
-
* -
|
|
236
|
+
* - These values mirror the standard WebGPU numeric constants.
|
|
237
|
+
* - They do not install themselves on `globalThis`; use `setupGlobals(...)` if needed.
|
|
238
|
+
* - `@simulatte/webgpu/compute` shares the same constants even though its device facade is narrower.
|
|
280
239
|
*/
|
|
281
|
-
class DoeGPUComputePassEncoder {
|
|
282
|
-
constructor(encoder) {
|
|
283
|
-
this._encoder = encoder;
|
|
284
|
-
this._pipeline = null;
|
|
285
|
-
this._bindGroups = [];
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
/**
|
|
289
|
-
* Set the compute pipeline used by later dispatch calls.
|
|
290
|
-
*
|
|
291
|
-
* This stores the pipeline handle on the pass so later dispatches use the
|
|
292
|
-
* expected compiled shader and layout.
|
|
293
|
-
*
|
|
294
|
-
* This example shows the API in its basic form.
|
|
295
|
-
*
|
|
296
|
-
* ```js
|
|
297
|
-
* pass.setPipeline(pipeline);
|
|
298
|
-
* ```
|
|
299
|
-
*
|
|
300
|
-
* - Call this before dispatching workgroups.
|
|
301
|
-
* - The pipeline object must come from the same device.
|
|
302
|
-
*/
|
|
303
|
-
setPipeline(pipeline) { this._pipeline = pipeline._native; }
|
|
304
|
-
|
|
305
|
-
/**
|
|
306
|
-
* Bind a bind group for the compute pass.
|
|
307
|
-
*
|
|
308
|
-
* This records the resource bindings that the next dispatches should see.
|
|
309
|
-
*
|
|
310
|
-
* This example shows the API in its basic form.
|
|
311
|
-
*
|
|
312
|
-
* ```js
|
|
313
|
-
* pass.setBindGroup(0, bindGroup);
|
|
314
|
-
* ```
|
|
315
|
-
*
|
|
316
|
-
* - Later calls for the same index replace the previous bind group.
|
|
317
|
-
* - Sparse indices are allowed, but the shader layout still has to match.
|
|
318
|
-
*/
|
|
319
|
-
setBindGroup(index, bindGroup) { this._bindGroups[index] = bindGroup._native; }
|
|
320
|
-
|
|
321
|
-
/**
|
|
322
|
-
* Record a direct compute dispatch.
|
|
323
|
-
*
|
|
324
|
-
* This queues an explicit workgroup dispatch on the current pass.
|
|
325
|
-
*
|
|
326
|
-
* This example shows the API in its basic form.
|
|
327
|
-
*
|
|
328
|
-
* ```js
|
|
329
|
-
* pass.dispatchWorkgroups(4, 1, 1);
|
|
330
|
-
* ```
|
|
331
|
-
*
|
|
332
|
-
* - Omitted `y` and `z` default to `1`.
|
|
333
|
-
* - The pipeline and required bind groups should already be set.
|
|
334
|
-
*/
|
|
335
|
-
dispatchWorkgroups(x, y = 1, z = 1) {
|
|
336
|
-
this._encoder._commands.push({
|
|
337
|
-
t: 0, p: this._pipeline, bg: [...this._bindGroups], x, y, z,
|
|
338
|
-
});
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
/**
|
|
342
|
-
* Dispatch compute workgroups using counts stored in a buffer.
|
|
343
|
-
*
|
|
344
|
-
* This switches to the native encoder path and forwards the indirect dispatch
|
|
345
|
-
* parameters from the supplied buffer.
|
|
346
|
-
*
|
|
347
|
-
* This example shows the API in its basic form.
|
|
348
|
-
*
|
|
349
|
-
* ```js
|
|
350
|
-
* pass.dispatchWorkgroupsIndirect(indirectBuffer, 0);
|
|
351
|
-
* ```
|
|
352
|
-
*
|
|
353
|
-
* - This forces the command encoder to materialize a native encoder immediately.
|
|
354
|
-
* - The indirect buffer must contain the expected dispatch layout.
|
|
355
|
-
*/
|
|
356
|
-
dispatchWorkgroupsIndirect(indirectBuffer, indirectOffset = 0) {
|
|
357
|
-
this._encoder._ensureNative();
|
|
358
|
-
const pass = addon.beginComputePass(this._encoder._native);
|
|
359
|
-
addon.computePassSetPipeline(pass, this._pipeline);
|
|
360
|
-
for (let i = 0; i < this._bindGroups.length; i++) {
|
|
361
|
-
if (this._bindGroups[i]) addon.computePassSetBindGroup(pass, i, this._bindGroups[i]);
|
|
362
|
-
}
|
|
363
|
-
addon.computePassDispatchWorkgroupsIndirect(pass, indirectBuffer._native, indirectOffset);
|
|
364
|
-
addon.computePassEnd(pass);
|
|
365
|
-
addon.computePassRelease(pass);
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
/**
|
|
369
|
-
* Finish the compute pass.
|
|
370
|
-
*
|
|
371
|
-
* This closes the pass so the surrounding command encoder can continue or
|
|
372
|
-
* be finalized.
|
|
373
|
-
*
|
|
374
|
-
* This example shows the API in its basic form.
|
|
375
|
-
*
|
|
376
|
-
* ```js
|
|
377
|
-
* pass.end();
|
|
378
|
-
* ```
|
|
379
|
-
*
|
|
380
|
-
* - Doe records most work on the surrounding command encoder, so this is lightweight.
|
|
381
|
-
* - Finishing the pass does not submit it; submit the finished command buffer on the queue.
|
|
382
|
-
*/
|
|
383
|
-
end() {}
|
|
384
|
-
}
|
|
385
|
-
|
|
386
240
|
/**
|
|
387
|
-
*
|
|
241
|
+
* Compute pass encoder returned by `commandEncoder.beginComputePass(...)`.
|
|
388
242
|
*
|
|
389
|
-
* This records compute
|
|
390
|
-
* turned into a command buffer for queue submission.
|
|
243
|
+
* This records a compute pass on the full package surface.
|
|
391
244
|
*
|
|
392
245
|
* This example shows the API in its basic form.
|
|
393
246
|
*
|
|
394
247
|
* ```js
|
|
395
|
-
* const
|
|
248
|
+
* const pass = encoder.beginComputePass();
|
|
249
|
+
* pass.setPipeline(pipeline);
|
|
396
250
|
* ```
|
|
397
251
|
*
|
|
398
|
-
* -
|
|
399
|
-
* -
|
|
252
|
+
* - Dispatches may be batched until the command encoder is finalized.
|
|
253
|
+
* - The encoder only supports the compute commands exposed by Doe here.
|
|
400
254
|
*/
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
addon.computePassSetPipeline(pass, cmd.p);
|
|
415
|
-
for (let i = 0; i < cmd.bg.length; i++) {
|
|
416
|
-
if (cmd.bg[i]) addon.computePassSetBindGroup(pass, i, cmd.bg[i]);
|
|
255
|
+
function ensureNodeCommandEncoderNative(encoder) {
|
|
256
|
+
encoder._assertOpen('GPUCommandEncoder');
|
|
257
|
+
if (encoder._native) {
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
encoder._native = addon.createCommandEncoder(assertLiveResource(encoder._device, 'GPUCommandEncoder', 'GPUDevice'));
|
|
261
|
+
for (const cmd of encoder._commands) {
|
|
262
|
+
if (cmd.t === 0) {
|
|
263
|
+
const pass = addon.beginComputePass(encoder._native);
|
|
264
|
+
addon.computePassSetPipeline(pass, cmd.p);
|
|
265
|
+
for (let index = 0; index < cmd.bg.length; index += 1) {
|
|
266
|
+
if (cmd.bg[index]) {
|
|
267
|
+
addon.computePassSetBindGroup(pass, index, cmd.bg[index]);
|
|
417
268
|
}
|
|
418
|
-
addon.computePassDispatchWorkgroups(pass, cmd.x, cmd.y, cmd.z);
|
|
419
|
-
addon.computePassEnd(pass);
|
|
420
|
-
addon.computePassRelease(pass);
|
|
421
|
-
} else if (cmd.t === 1) {
|
|
422
|
-
addon.commandEncoderCopyBufferToBuffer(this._native, cmd.s, cmd.so, cmd.d, cmd.do, cmd.sz);
|
|
423
269
|
}
|
|
270
|
+
addon.computePassDispatchWorkgroups(pass, cmd.x, cmd.y, cmd.z);
|
|
271
|
+
addon.computePassEnd(pass);
|
|
272
|
+
addon.computePassRelease(pass);
|
|
273
|
+
} else if (cmd.t === 1) {
|
|
274
|
+
addon.commandEncoderCopyBufferToBuffer(encoder._native, cmd.s, cmd.so, cmd.d, cmd.do, cmd.sz);
|
|
424
275
|
}
|
|
425
|
-
this._commands = [];
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
/**
|
|
429
|
-
* Begin a compute pass.
|
|
430
|
-
*
|
|
431
|
-
* This creates a pass encoder that records compute state and dispatches on
|
|
432
|
-
* this command encoder.
|
|
433
|
-
*
|
|
434
|
-
* This example shows the API in its basic form.
|
|
435
|
-
*
|
|
436
|
-
* ```js
|
|
437
|
-
* const pass = encoder.beginComputePass();
|
|
438
|
-
* ```
|
|
439
|
-
*
|
|
440
|
-
* - The descriptor is accepted for WebGPU shape compatibility.
|
|
441
|
-
* - The returned pass is valid until `pass.end()`.
|
|
442
|
-
*/
|
|
443
|
-
beginComputePass(descriptor) {
|
|
444
|
-
return new DoeGPUComputePassEncoder(this);
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
/**
|
|
448
|
-
* Begin a render pass.
|
|
449
|
-
*
|
|
450
|
-
* This starts a headless render pass with the provided attachments on the
|
|
451
|
-
* underlying native command encoder.
|
|
452
|
-
*
|
|
453
|
-
* This example shows the API in its basic form.
|
|
454
|
-
*
|
|
455
|
-
* ```js
|
|
456
|
-
* const pass = encoder.beginRenderPass({
|
|
457
|
-
* colorAttachments: [{ view }],
|
|
458
|
-
* });
|
|
459
|
-
* ```
|
|
460
|
-
*
|
|
461
|
-
* - Doe materializes the native encoder before starting the render pass.
|
|
462
|
-
* - Color attachments default their clear color when one is not provided.
|
|
463
|
-
*/
|
|
464
|
-
beginRenderPass(descriptor) {
|
|
465
|
-
this._ensureNative();
|
|
466
|
-
const colorAttachments = (descriptor.colorAttachments || []).map((a) => ({
|
|
467
|
-
view: a.view._native,
|
|
468
|
-
clearValue: a.clearValue || { r: 0, g: 0, b: 0, a: 1 },
|
|
469
|
-
}));
|
|
470
|
-
const pass = addon.beginRenderPass(this._native, colorAttachments);
|
|
471
|
-
return new DoeGPURenderPassEncoder(pass);
|
|
472
276
|
}
|
|
277
|
+
encoder._commands = [];
|
|
278
|
+
}
|
|
473
279
|
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
* encoder.copyBufferToBuffer(src, 0, dst, 0, src.size);
|
|
484
|
-
* ```
|
|
485
|
-
*
|
|
486
|
-
* - Copies can be batched until the encoder is finalized.
|
|
487
|
-
* - Buffer ranges still need to be valid for the underlying WebGPU rules.
|
|
488
|
-
*/
|
|
489
|
-
copyBufferToBuffer(src, srcOffset, dst, dstOffset, size) {
|
|
490
|
-
if (this._native) {
|
|
491
|
-
addon.commandEncoderCopyBufferToBuffer(this._native, src._native, srcOffset, dst._native, dstOffset, size);
|
|
492
|
-
} else {
|
|
493
|
-
this._commands.push({ t: 1, s: src._native, so: srcOffset, d: dst._native, do: dstOffset, sz: size });
|
|
280
|
+
const nodeEncoderBackend = {
|
|
281
|
+
computePassInit(pass) {
|
|
282
|
+
pass._pipeline = null;
|
|
283
|
+
pass._bindGroups = [];
|
|
284
|
+
pass._ended = false;
|
|
285
|
+
},
|
|
286
|
+
computePassAssertOpen(pass, path) {
|
|
287
|
+
if (pass._ended) {
|
|
288
|
+
failValidation(path, 'compute pass is already ended');
|
|
494
289
|
}
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
/**
|
|
498
|
-
* Finish command recording and return a command buffer.
|
|
499
|
-
*
|
|
500
|
-
* This seals the recorded commands so they can be submitted on a queue.
|
|
501
|
-
*
|
|
502
|
-
* This example shows the API in its basic form.
|
|
503
|
-
*
|
|
504
|
-
* ```js
|
|
505
|
-
* const commands = encoder.finish();
|
|
506
|
-
* device.queue.submit([commands]);
|
|
507
|
-
* ```
|
|
508
|
-
*
|
|
509
|
-
* - Doe may return a lightweight batched command buffer representation.
|
|
510
|
-
* - The returned object is meant for queue submission, not direct inspection.
|
|
511
|
-
*/
|
|
512
|
-
finish() {
|
|
513
|
-
if (this._native) {
|
|
514
|
-
const cmd = addon.commandEncoderFinish(this._native);
|
|
515
|
-
return { _native: cmd, _batched: false };
|
|
290
|
+
if (pass._encoder._finished) {
|
|
291
|
+
failValidation(path, 'command encoder is already finished');
|
|
516
292
|
}
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
293
|
+
},
|
|
294
|
+
computePassSetPipeline(pass, pipelineNative) {
|
|
295
|
+
pass._pipeline = pipelineNative;
|
|
296
|
+
},
|
|
297
|
+
computePassSetBindGroup(pass, index, bindGroupNative) {
|
|
298
|
+
pass._bindGroups[index] = bindGroupNative;
|
|
299
|
+
},
|
|
300
|
+
computePassDispatchWorkgroups(pass, x, y, z) {
|
|
301
|
+
if (pass._pipeline == null) {
|
|
302
|
+
failValidation('GPUComputePassEncoder.dispatchWorkgroups', 'setPipeline() must be called before dispatch');
|
|
303
|
+
}
|
|
304
|
+
pass._encoder._commands.push({ t: 0, p: pass._pipeline, bg: [...pass._bindGroups], x, y, z });
|
|
305
|
+
},
|
|
306
|
+
computePassDispatchWorkgroupsIndirect(pass, indirectBufferNative, indirectOffset) {
|
|
307
|
+
if (pass._pipeline == null) {
|
|
308
|
+
failValidation('GPUComputePassEncoder.dispatchWorkgroupsIndirect', 'setPipeline() must be called before dispatch');
|
|
309
|
+
}
|
|
310
|
+
if (typeof addon.bufferReadIndirectCounts === 'function') {
|
|
311
|
+
const counts = addon.bufferReadIndirectCounts(indirectBufferNative, indirectOffset);
|
|
312
|
+
pass._encoder._commands.push({
|
|
313
|
+
t: 0,
|
|
314
|
+
p: pass._pipeline,
|
|
315
|
+
bg: [...pass._bindGroups],
|
|
316
|
+
x: counts.x,
|
|
317
|
+
y: counts.y,
|
|
318
|
+
z: counts.z,
|
|
319
|
+
});
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
ensureNodeCommandEncoderNative(pass._encoder);
|
|
323
|
+
const nativePass = addon.beginComputePass(pass._encoder._native);
|
|
324
|
+
addon.computePassSetPipeline(nativePass, pass._pipeline);
|
|
325
|
+
for (let index = 0; index < pass._bindGroups.length; index += 1) {
|
|
326
|
+
if (pass._bindGroups[index]) {
|
|
327
|
+
addon.computePassSetBindGroup(nativePass, index, pass._bindGroups[index]);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
addon.computePassDispatchWorkgroupsIndirect(nativePass, indirectBufferNative, indirectOffset);
|
|
331
|
+
addon.computePassEnd(nativePass);
|
|
332
|
+
addon.computePassRelease(nativePass);
|
|
333
|
+
},
|
|
334
|
+
computePassEnd(pass) {
|
|
335
|
+
pass._ended = true;
|
|
336
|
+
},
|
|
337
|
+
renderPassInit(pass, native) {
|
|
338
|
+
pass._native = native;
|
|
339
|
+
pass._ended = false;
|
|
340
|
+
},
|
|
341
|
+
renderPassAssertOpen(pass, path) {
|
|
342
|
+
if (pass._ended) {
|
|
343
|
+
failValidation(path, 'render pass is already ended');
|
|
344
|
+
}
|
|
345
|
+
if (pass._encoder._finished) {
|
|
346
|
+
failValidation(path, 'command encoder is already finished');
|
|
347
|
+
}
|
|
348
|
+
},
|
|
349
|
+
renderPassSetPipeline(pass, pipelineNative) {
|
|
350
|
+
addon.renderPassSetPipeline(
|
|
351
|
+
assertLiveResource(pass, 'GPURenderPassEncoder.setPipeline', 'GPURenderPassEncoder'),
|
|
352
|
+
pipelineNative,
|
|
353
|
+
);
|
|
354
|
+
},
|
|
355
|
+
renderPassSetBindGroup(pass, index, bindGroupNative) {
|
|
356
|
+
addon.renderPassSetBindGroup(
|
|
357
|
+
assertLiveResource(pass, 'GPURenderPassEncoder.setBindGroup', 'GPURenderPassEncoder'),
|
|
358
|
+
index,
|
|
359
|
+
bindGroupNative,
|
|
360
|
+
);
|
|
361
|
+
},
|
|
362
|
+
renderPassSetVertexBuffer(pass, slot, bufferNative, offset, size) {
|
|
363
|
+
addon.renderPassSetVertexBuffer(
|
|
364
|
+
assertLiveResource(pass, 'GPURenderPassEncoder.setVertexBuffer', 'GPURenderPassEncoder'),
|
|
365
|
+
slot,
|
|
366
|
+
bufferNative,
|
|
367
|
+
offset,
|
|
368
|
+
size ?? 0,
|
|
369
|
+
);
|
|
370
|
+
},
|
|
371
|
+
renderPassSetIndexBuffer(pass, bufferNative, format, offset, size) {
|
|
372
|
+
addon.renderPassSetIndexBuffer(
|
|
373
|
+
assertLiveResource(pass, 'GPURenderPassEncoder.setIndexBuffer', 'GPURenderPassEncoder'),
|
|
374
|
+
bufferNative,
|
|
375
|
+
format,
|
|
376
|
+
offset,
|
|
377
|
+
size ?? 0,
|
|
378
|
+
);
|
|
379
|
+
},
|
|
380
|
+
renderPassDraw(pass, vertexCount, instanceCount, firstVertex, firstInstance) {
|
|
381
|
+
addon.renderPassDraw(pass._native, vertexCount, instanceCount, firstVertex, firstInstance);
|
|
382
|
+
},
|
|
383
|
+
renderPassDrawIndexed(pass, indexCount, instanceCount, firstIndex, baseVertex, firstInstance) {
|
|
384
|
+
addon.renderPassDrawIndexed(pass._native, indexCount, instanceCount, firstIndex, baseVertex, firstInstance);
|
|
385
|
+
},
|
|
386
|
+
renderPassEnd(pass) {
|
|
387
|
+
addon.renderPassEnd(pass._native);
|
|
388
|
+
pass._ended = true;
|
|
389
|
+
},
|
|
390
|
+
commandEncoderInit(encoder) {
|
|
391
|
+
encoder._commands = [];
|
|
392
|
+
encoder._native = null;
|
|
393
|
+
encoder._finished = false;
|
|
394
|
+
},
|
|
395
|
+
commandEncoderAssertOpen(encoder, path) {
|
|
396
|
+
if (encoder._finished) {
|
|
397
|
+
failValidation(path, 'command encoder is already finished');
|
|
398
|
+
}
|
|
399
|
+
},
|
|
400
|
+
commandEncoderBeginComputePass(encoder, _descriptor, classes) {
|
|
401
|
+
return new classes.DoeGPUComputePassEncoder(null, encoder);
|
|
402
|
+
},
|
|
403
|
+
commandEncoderBeginRenderPass(encoder, passDescriptor, classes) {
|
|
404
|
+
const attachments = assertArray(passDescriptor.colorAttachments ?? [], 'GPUCommandEncoder.beginRenderPass', 'descriptor.colorAttachments');
|
|
405
|
+
if (attachments.length === 0) {
|
|
406
|
+
failValidation('GPUCommandEncoder.beginRenderPass', 'descriptor.colorAttachments must contain at least one attachment');
|
|
407
|
+
}
|
|
408
|
+
ensureNodeCommandEncoderNative(encoder);
|
|
409
|
+
const colorAttachments = attachments.map((attachment, index) => {
|
|
410
|
+
const entry = assertObject(attachment, 'GPUCommandEncoder.beginRenderPass', `descriptor.colorAttachments[${index}]`);
|
|
411
|
+
return {
|
|
412
|
+
view: assertLiveResource(entry.view, 'GPUCommandEncoder.beginRenderPass', 'GPUTextureView'),
|
|
413
|
+
clearValue: entry.clearValue || { r: 0, g: 0, b: 0, a: 1 },
|
|
414
|
+
};
|
|
415
|
+
});
|
|
416
|
+
let depthStencilAttachment = undefined;
|
|
417
|
+
if (passDescriptor.depthStencilAttachment !== undefined) {
|
|
418
|
+
const depthAttachment = assertObject(passDescriptor.depthStencilAttachment, 'GPUCommandEncoder.beginRenderPass', 'descriptor.depthStencilAttachment');
|
|
419
|
+
depthStencilAttachment = {
|
|
420
|
+
view: assertLiveResource(depthAttachment.view, 'GPUCommandEncoder.beginRenderPass', 'GPUTextureView'),
|
|
421
|
+
depthClearValue: depthAttachment.depthClearValue ?? 1,
|
|
422
|
+
depthReadOnly: depthAttachment.depthReadOnly ?? false,
|
|
423
|
+
stencilClearValue: depthAttachment.stencilClearValue ?? 0,
|
|
424
|
+
stencilReadOnly: depthAttachment.stencilReadOnly ?? false,
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
const pass = addon.beginRenderPass(encoder._native, {
|
|
428
|
+
colorAttachments,
|
|
429
|
+
depthStencilAttachment,
|
|
430
|
+
});
|
|
431
|
+
return new classes.DoeGPURenderPassEncoder(pass, encoder);
|
|
432
|
+
},
|
|
433
|
+
commandEncoderCopyBufferToBuffer(encoder, srcNative, srcOffset, dstNative, dstOffset, size) {
|
|
434
|
+
if (encoder._native) {
|
|
435
|
+
addon.commandEncoderCopyBufferToBuffer(encoder._native, srcNative, srcOffset, dstNative, dstOffset, size);
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
encoder._commands.push({ t: 1, s: srcNative, so: srcOffset, d: dstNative, do: dstOffset, sz: size });
|
|
439
|
+
},
|
|
440
|
+
commandEncoderWriteTimestamp(encoder, querySetNative, queryIndex) {
|
|
441
|
+
ensureNodeCommandEncoderNative(encoder);
|
|
442
|
+
addon.commandEncoderWriteTimestamp(encoder._native, querySetNative, queryIndex);
|
|
443
|
+
},
|
|
444
|
+
commandEncoderResolveQuerySet(encoder, querySetNative, firstQuery, queryCount, destinationNative, destinationOffset) {
|
|
445
|
+
ensureNodeCommandEncoderNative(encoder);
|
|
446
|
+
addon.commandEncoderResolveQuerySet(encoder._native, querySetNative, firstQuery, queryCount, destinationNative, destinationOffset);
|
|
447
|
+
},
|
|
448
|
+
commandEncoderCopyTextureToBuffer(encoder, source, destination, copySize) {
|
|
449
|
+
ensureNodeCommandEncoderNative(encoder);
|
|
450
|
+
addon.commandEncoderCopyTextureToBuffer(
|
|
451
|
+
encoder._native,
|
|
452
|
+
source.texture,
|
|
453
|
+
source.mipLevel ?? 0,
|
|
454
|
+
source.origin?.x ?? 0,
|
|
455
|
+
source.origin?.y ?? 0,
|
|
456
|
+
source.origin?.z ?? 0,
|
|
457
|
+
source.aspect ?? 1,
|
|
458
|
+
destination.buffer,
|
|
459
|
+
destination.offset ?? 0,
|
|
460
|
+
destination.bytesPerRow ?? 0,
|
|
461
|
+
destination.rowsPerImage ?? 0,
|
|
462
|
+
copySize.width,
|
|
463
|
+
copySize.height,
|
|
464
|
+
copySize.depthOrArrayLayers ?? 1,
|
|
465
|
+
);
|
|
466
|
+
},
|
|
467
|
+
commandEncoderFinish(encoder) {
|
|
468
|
+
ensureNodeCommandEncoderNative(encoder);
|
|
469
|
+
encoder._finished = true;
|
|
470
|
+
const cmd = addon.commandEncoderFinish(encoder._native);
|
|
471
|
+
encoder._native = null;
|
|
472
|
+
return { _native: cmd, _batched: false };
|
|
473
|
+
},
|
|
474
|
+
};
|
|
475
|
+
|
|
476
|
+
const {
|
|
477
|
+
DoeGPUComputePassEncoder,
|
|
478
|
+
DoeGPUCommandEncoder,
|
|
479
|
+
DoeGPURenderPassEncoder,
|
|
480
|
+
} = createEncoderClasses(nodeEncoderBackend);
|
|
520
481
|
|
|
521
482
|
/**
|
|
522
|
-
*
|
|
483
|
+
* Texture returned by `device.createTexture(...)`.
|
|
523
484
|
*
|
|
524
|
-
* This
|
|
525
|
-
*
|
|
485
|
+
* This represents a headless Doe texture resource and can create default views
|
|
486
|
+
* for render or sampling usage.
|
|
526
487
|
*
|
|
527
488
|
* This example shows the API in its basic form.
|
|
528
489
|
*
|
|
529
490
|
* ```js
|
|
530
|
-
* device.
|
|
491
|
+
* const texture = device.createTexture({
|
|
492
|
+
* size: [64, 64, 1],
|
|
493
|
+
* format: "rgba8unorm",
|
|
494
|
+
* usage: GPUTextureUsage.RENDER_ATTACHMENT,
|
|
495
|
+
* });
|
|
531
496
|
* ```
|
|
532
497
|
*
|
|
533
|
-
* -
|
|
534
|
-
* -
|
|
498
|
+
* - The package currently exposes the texture operations needed by its headless surface.
|
|
499
|
+
* - Texture views are created through `createView(...)`.
|
|
535
500
|
*/
|
|
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
|
-
}
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
* ```js
|
|
592
|
-
* device.queue.submit([encoder.finish()]);
|
|
593
|
-
* ```
|
|
594
|
-
*
|
|
595
|
-
* - Empty submissions are ignored.
|
|
596
|
-
* - Simple batched compute-copy sequences may take a Doe fast path.
|
|
597
|
-
*/
|
|
598
|
-
submit(commandBuffers) {
|
|
599
|
-
if (commandBuffers.length === 0) return;
|
|
600
|
-
this._submittedSerial += 1;
|
|
601
|
-
if (commandBuffers.length === 1 && commandBuffers[0]?._batched) {
|
|
602
|
-
const cmds = commandBuffers[0]._commands;
|
|
501
|
+
const fullSurfaceBackend = {
|
|
502
|
+
initBufferState(buffer) {
|
|
503
|
+
buffer._mapMode = 0;
|
|
504
|
+
},
|
|
505
|
+
bufferMarkMappedAtCreation(buffer) {
|
|
506
|
+
buffer._mapMode = globals.GPUMapMode.WRITE;
|
|
507
|
+
},
|
|
508
|
+
bufferMapAsync(wrapper, native, mode, offset, size) {
|
|
509
|
+
if (wrapper._queue) {
|
|
510
|
+
if (wrapper._queue.hasPendingSubmissions()) {
|
|
511
|
+
addon.flushAndMapSync(
|
|
512
|
+
wrapper._instance,
|
|
513
|
+
assertLiveResource(wrapper._queue, 'GPUBuffer.mapAsync', 'GPUQueue'),
|
|
514
|
+
native,
|
|
515
|
+
mode,
|
|
516
|
+
offset,
|
|
517
|
+
size,
|
|
518
|
+
);
|
|
519
|
+
wrapper._queue.markSubmittedWorkDone();
|
|
520
|
+
} else {
|
|
521
|
+
addon.bufferMapSync(wrapper._instance, native, mode, offset, size);
|
|
522
|
+
}
|
|
523
|
+
} else {
|
|
524
|
+
addon.bufferMapSync(wrapper._instance, native, mode, offset, size);
|
|
525
|
+
}
|
|
526
|
+
wrapper._mapMode = mode;
|
|
527
|
+
},
|
|
528
|
+
bufferGetMappedRange(wrapper, native, offset, size) {
|
|
529
|
+
return addon.bufferGetMappedRange(native, offset, size);
|
|
530
|
+
},
|
|
531
|
+
bufferAssertMappedPrefixF32(_wrapper, native, expected, count) {
|
|
532
|
+
return addon.bufferAssertMappedPrefixF32(native, expected, count);
|
|
533
|
+
},
|
|
534
|
+
bufferUnmap(native, wrapper) {
|
|
535
|
+
wrapper._mapMode = 0;
|
|
536
|
+
addon.bufferUnmap(native);
|
|
537
|
+
},
|
|
538
|
+
bufferDestroy(native) {
|
|
539
|
+
addon.bufferRelease(native);
|
|
540
|
+
},
|
|
541
|
+
initQueueState(queue) {
|
|
542
|
+
queue._submittedSerial = 0;
|
|
543
|
+
queue._completedSerial = 0;
|
|
544
|
+
},
|
|
545
|
+
queueHasPendingSubmissions(queue) {
|
|
546
|
+
return queue._completedSerial < queue._submittedSerial;
|
|
547
|
+
},
|
|
548
|
+
queueMarkSubmittedWorkDone(queue) {
|
|
549
|
+
queue._completedSerial = queue._submittedSerial;
|
|
550
|
+
},
|
|
551
|
+
queueSubmit(queue, queueNative, buffers) {
|
|
552
|
+
const deviceNative = assertLiveResource(queue._device, 'GPUQueue.submit', 'GPUDevice');
|
|
553
|
+
queue._submittedSerial += 1;
|
|
554
|
+
if (buffers.length === 1 && buffers[0]?._batched) {
|
|
555
|
+
const cmds = buffers[0]._commands;
|
|
603
556
|
if (
|
|
604
557
|
cmds.length === 2
|
|
605
558
|
&& cmds[0]?.t === 0
|
|
@@ -607,8 +560,8 @@ class DoeGPUQueue {
|
|
|
607
560
|
&& typeof addon.submitComputeDispatchCopy === 'function'
|
|
608
561
|
) {
|
|
609
562
|
addon.submitComputeDispatchCopy(
|
|
610
|
-
|
|
611
|
-
|
|
563
|
+
deviceNative,
|
|
564
|
+
queueNative,
|
|
612
565
|
cmds[0].p,
|
|
613
566
|
cmds[0].bg,
|
|
614
567
|
cmds[0].x,
|
|
@@ -623,831 +576,253 @@ class DoeGPUQueue {
|
|
|
623
576
|
return;
|
|
624
577
|
}
|
|
625
578
|
}
|
|
626
|
-
if (
|
|
579
|
+
if (buffers.every((commandBuffer) => commandBuffer?._batched && Array.isArray(commandBuffer._commands))) {
|
|
627
580
|
const allCommands = [];
|
|
628
|
-
for (const cb of
|
|
629
|
-
|
|
581
|
+
for (const cb of buffers) {
|
|
582
|
+
allCommands.push(...cb._commands);
|
|
583
|
+
}
|
|
584
|
+
addon.submitBatched(deviceNative, queueNative, allCommands);
|
|
630
585
|
if (
|
|
631
586
|
allCommands.length === 2
|
|
632
587
|
&& allCommands[0]?.t === 0
|
|
633
588
|
&& allCommands[1]?.t === 1
|
|
634
589
|
) {
|
|
635
|
-
|
|
590
|
+
queue.markSubmittedWorkDone();
|
|
636
591
|
}
|
|
637
|
-
|
|
638
|
-
const natives = commandBuffers.map((c) => c._native);
|
|
639
|
-
addon.queueSubmit(this._native, natives);
|
|
592
|
+
return;
|
|
640
593
|
}
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
* ```
|
|
654
|
-
*
|
|
655
|
-
* - `dataOffset` and `size` are interpreted in element units for typed arrays.
|
|
656
|
-
* - Doe converts the requested range into bytes before writing it.
|
|
657
|
-
*/
|
|
658
|
-
writeBuffer(buffer, bufferOffset, data, dataOffset = 0, size) {
|
|
659
|
-
let view = data;
|
|
660
|
-
if (dataOffset > 0 || size !== undefined) {
|
|
661
|
-
const byteOffset = data.byteOffset + dataOffset * (data.BYTES_PER_ELEMENT || 1);
|
|
662
|
-
const byteLength = size !== undefined
|
|
663
|
-
? size * (data.BYTES_PER_ELEMENT || 1)
|
|
664
|
-
: data.byteLength - dataOffset * (data.BYTES_PER_ELEMENT || 1);
|
|
665
|
-
view = new Uint8Array(data.buffer, byteOffset, byteLength);
|
|
666
|
-
}
|
|
667
|
-
addon.queueWriteBuffer(this._native, buffer._native, bufferOffset, view);
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
/**
|
|
671
|
-
* Resolve after submitted work has been flushed.
|
|
672
|
-
*
|
|
673
|
-
* This gives callers a simple way to wait until Doe has drained the tracked
|
|
674
|
-
* queue work relevant to this device.
|
|
675
|
-
*
|
|
676
|
-
* This example shows the API in its basic form.
|
|
677
|
-
*
|
|
678
|
-
* ```js
|
|
679
|
-
* await device.queue.onSubmittedWorkDone();
|
|
680
|
-
* ```
|
|
681
|
-
*
|
|
682
|
-
* - If no submissions are pending, this resolves immediately.
|
|
683
|
-
* - Doe flushes the native queue before marking the tracked work complete.
|
|
684
|
-
*/
|
|
685
|
-
async onSubmittedWorkDone() {
|
|
686
|
-
if (!this.hasPendingSubmissions()) return;
|
|
594
|
+
const natives = buffers.map((commandBuffer, index) => {
|
|
595
|
+
if (!commandBuffer || typeof commandBuffer !== 'object' || commandBuffer._native == null) {
|
|
596
|
+
failValidation('GPUQueue.submit', `commandBuffers[${index}] must be a finished command buffer`);
|
|
597
|
+
}
|
|
598
|
+
return commandBuffer._native;
|
|
599
|
+
});
|
|
600
|
+
addon.queueSubmit(queueNative, natives);
|
|
601
|
+
},
|
|
602
|
+
queueWriteBuffer(_queue, queueNative, bufferNative, bufferOffset, view) {
|
|
603
|
+
addon.queueWriteBuffer(queueNative, bufferNative, bufferOffset, view);
|
|
604
|
+
},
|
|
605
|
+
async queueOnSubmittedWorkDone(queue, queueNative) {
|
|
687
606
|
try {
|
|
688
|
-
addon.queueFlush(
|
|
607
|
+
addon.queueFlush(queue._instance, queueNative);
|
|
689
608
|
} catch (error) {
|
|
690
|
-
if (
|
|
609
|
+
if (error?.code === 'DOE_QUEUE_UNAVAILABLE') {
|
|
691
610
|
return;
|
|
692
611
|
}
|
|
693
612
|
throw error;
|
|
694
613
|
}
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
class DoeGPURenderPassEncoder {
|
|
715
|
-
constructor(native) { this._native = native; }
|
|
716
|
-
|
|
717
|
-
/**
|
|
718
|
-
* Set the render pipeline used by later draw calls.
|
|
719
|
-
*
|
|
720
|
-
* This records the pipeline state that subsequent draw calls in the pass
|
|
721
|
-
* should use.
|
|
722
|
-
*
|
|
723
|
-
* This example shows the API in its basic form.
|
|
724
|
-
*
|
|
725
|
-
* ```js
|
|
726
|
-
* pass.setPipeline(pipeline);
|
|
727
|
-
* ```
|
|
728
|
-
*
|
|
729
|
-
* - The pipeline must come from the same device.
|
|
730
|
-
* - Call this before `draw(...)`.
|
|
731
|
-
*/
|
|
732
|
-
setPipeline(pipeline) {
|
|
733
|
-
addon.renderPassSetPipeline(this._native, pipeline._native);
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
/**
|
|
737
|
-
* Record a non-indexed draw.
|
|
738
|
-
*
|
|
739
|
-
* This queues a draw call using the current render pipeline and bound
|
|
740
|
-
* attachments.
|
|
741
|
-
*
|
|
742
|
-
* This example shows the API in its basic form.
|
|
743
|
-
*
|
|
744
|
-
* ```js
|
|
745
|
-
* pass.draw(3);
|
|
746
|
-
* ```
|
|
747
|
-
*
|
|
748
|
-
* - Omitted instance and offset arguments default to the WebGPU-style values.
|
|
749
|
-
* - Draw calls only become visible after the command buffer is submitted.
|
|
750
|
-
*/
|
|
751
|
-
draw(vertexCount, instanceCount = 1, firstVertex = 0, firstInstance = 0) {
|
|
752
|
-
addon.renderPassDraw(this._native, vertexCount, instanceCount, firstVertex, firstInstance);
|
|
753
|
-
}
|
|
754
|
-
|
|
755
|
-
/**
|
|
756
|
-
* Finish the render pass.
|
|
757
|
-
*
|
|
758
|
-
* This closes the native render-pass encoder so the command encoder can
|
|
759
|
-
* continue recording.
|
|
760
|
-
*
|
|
761
|
-
* This example shows the API in its basic form.
|
|
762
|
-
*
|
|
763
|
-
* ```js
|
|
764
|
-
* pass.end();
|
|
765
|
-
* ```
|
|
766
|
-
*
|
|
767
|
-
* - This closes the native render pass encoder.
|
|
768
|
-
* - It does not submit work by itself.
|
|
769
|
-
*/
|
|
770
|
-
end() {
|
|
771
|
-
addon.renderPassEnd(this._native);
|
|
772
|
-
}
|
|
773
|
-
}
|
|
774
|
-
|
|
775
|
-
/**
|
|
776
|
-
* Texture returned by `device.createTexture(...)`.
|
|
777
|
-
*
|
|
778
|
-
* This represents a headless Doe texture resource and can create default views
|
|
779
|
-
* for render or sampling usage.
|
|
780
|
-
*
|
|
781
|
-
* This example shows the API in its basic form.
|
|
782
|
-
*
|
|
783
|
-
* ```js
|
|
784
|
-
* const texture = device.createTexture({
|
|
785
|
-
* size: [64, 64, 1],
|
|
786
|
-
* format: "rgba8unorm",
|
|
787
|
-
* usage: GPUTextureUsage.RENDER_ATTACHMENT,
|
|
788
|
-
* });
|
|
789
|
-
* ```
|
|
790
|
-
*
|
|
791
|
-
* - The package currently exposes the texture operations needed by its headless surface.
|
|
792
|
-
* - Texture views are created through `createView(...)`.
|
|
793
|
-
*/
|
|
794
|
-
class DoeGPUTexture {
|
|
795
|
-
constructor(native) { this._native = native; }
|
|
796
|
-
|
|
797
|
-
/**
|
|
798
|
-
* Create a texture view.
|
|
799
|
-
*
|
|
800
|
-
* This returns a default texture view wrapper for the texture so it can be
|
|
801
|
-
* used in render or sampling APIs.
|
|
802
|
-
*
|
|
803
|
-
* This example shows the API in its basic form.
|
|
804
|
-
*
|
|
805
|
-
* ```js
|
|
806
|
-
* const view = texture.createView();
|
|
807
|
-
* ```
|
|
808
|
-
*
|
|
809
|
-
* - Doe currently ignores most descriptor variation here and creates a default view.
|
|
810
|
-
* - The returned view is suitable for the package's headless render paths.
|
|
811
|
-
*/
|
|
812
|
-
createView(descriptor) {
|
|
813
|
-
const view = addon.textureCreateView(this._native);
|
|
814
|
-
return new DoeGPUTextureView(view);
|
|
815
|
-
}
|
|
816
|
-
|
|
817
|
-
/**
|
|
818
|
-
* Release the native texture.
|
|
819
|
-
*
|
|
820
|
-
* This tears down the underlying Doe texture allocation associated with the
|
|
821
|
-
* wrapper.
|
|
822
|
-
*
|
|
823
|
-
* This example shows the API in its basic form.
|
|
824
|
-
*
|
|
825
|
-
* ```js
|
|
826
|
-
* texture.destroy();
|
|
827
|
-
* ```
|
|
828
|
-
*
|
|
829
|
-
* - Reusing the texture after destruction is unsupported.
|
|
830
|
-
* - Views already created are plain JS wrappers and do not keep the texture alive.
|
|
831
|
-
*/
|
|
832
|
-
destroy() {
|
|
833
|
-
addon.textureRelease(this._native);
|
|
834
|
-
this._native = null;
|
|
835
|
-
}
|
|
836
|
-
}
|
|
837
|
-
|
|
838
|
-
/**
|
|
839
|
-
* Texture view wrapper returned by `texture.createView()`.
|
|
840
|
-
*
|
|
841
|
-
* This example shows the API in its basic form.
|
|
842
|
-
*
|
|
843
|
-
* ```js
|
|
844
|
-
* const view = texture.createView();
|
|
845
|
-
* ```
|
|
846
|
-
*
|
|
847
|
-
* - This package currently treats the view as a lightweight opaque handle.
|
|
848
|
-
*/
|
|
849
|
-
class DoeGPUTextureView {
|
|
850
|
-
constructor(native) { this._native = native; }
|
|
851
|
-
}
|
|
852
|
-
|
|
853
|
-
/**
|
|
854
|
-
* Sampler wrapper returned by `device.createSampler(...)`.
|
|
855
|
-
*
|
|
856
|
-
* This example shows the API in its basic form.
|
|
857
|
-
*
|
|
858
|
-
* ```js
|
|
859
|
-
* const sampler = device.createSampler();
|
|
860
|
-
* ```
|
|
861
|
-
*
|
|
862
|
-
* - The sampler is currently an opaque handle on the JS side.
|
|
863
|
-
*/
|
|
864
|
-
class DoeGPUSampler {
|
|
865
|
-
constructor(native) { this._native = native; }
|
|
866
|
-
}
|
|
867
|
-
|
|
868
|
-
/**
|
|
869
|
-
* Render pipeline returned by `device.createRenderPipeline(...)`.
|
|
870
|
-
*
|
|
871
|
-
* This example shows the API in its basic form.
|
|
872
|
-
*
|
|
873
|
-
* ```js
|
|
874
|
-
* const pipeline = device.createRenderPipeline(descriptor);
|
|
875
|
-
* ```
|
|
876
|
-
*
|
|
877
|
-
* - The JS wrapper is currently an opaque handle used by render passes.
|
|
878
|
-
*/
|
|
879
|
-
class DoeGPURenderPipeline {
|
|
880
|
-
constructor(native) { this._native = native; }
|
|
881
|
-
}
|
|
882
|
-
|
|
883
|
-
/**
|
|
884
|
-
* Shader module returned by `device.createShaderModule(...)`.
|
|
885
|
-
*
|
|
886
|
-
* This example shows the API in its basic form.
|
|
887
|
-
*
|
|
888
|
-
* ```js
|
|
889
|
-
* const shader = device.createShaderModule({ code: WGSL });
|
|
890
|
-
* ```
|
|
891
|
-
*
|
|
892
|
-
* - Doe keeps the WGSL source on the wrapper for pipeline creation and auto-layout work.
|
|
893
|
-
*/
|
|
894
|
-
class DoeGPUShaderModule {
|
|
895
|
-
constructor(native, code) {
|
|
896
|
-
this._native = native;
|
|
897
|
-
this._code = code;
|
|
898
|
-
}
|
|
899
|
-
}
|
|
900
|
-
|
|
901
|
-
/**
|
|
902
|
-
* Compute pipeline returned by `device.createComputePipeline(...)`.
|
|
903
|
-
*
|
|
904
|
-
* This wrapper exposes pipeline layout lookup for bind-group creation and
|
|
905
|
-
* dispatch setup.
|
|
906
|
-
*
|
|
907
|
-
* This example shows the API in its basic form.
|
|
908
|
-
*
|
|
909
|
-
* ```js
|
|
910
|
-
* const pipeline = device.createComputePipeline({
|
|
911
|
-
* layout: "auto",
|
|
912
|
-
* compute: { module: shader, entryPoint: "main" },
|
|
913
|
-
* });
|
|
914
|
-
* ```
|
|
915
|
-
*
|
|
916
|
-
* - Auto-layout pipelines derive bind-group layouts from the shader source.
|
|
917
|
-
* - Explicit-layout pipelines return the layout they were created with.
|
|
918
|
-
*/
|
|
919
|
-
class DoeGPUComputePipeline {
|
|
920
|
-
constructor(native, device, explicitLayout, autoLayoutEntriesByGroup) {
|
|
921
|
-
this._native = native;
|
|
922
|
-
this._device = device;
|
|
923
|
-
this._explicitLayout = explicitLayout;
|
|
924
|
-
this._autoLayoutEntriesByGroup = autoLayoutEntriesByGroup;
|
|
925
|
-
this._cachedLayouts = new Map();
|
|
926
|
-
}
|
|
927
|
-
|
|
928
|
-
/**
|
|
929
|
-
* Return the bind-group layout for a given group index.
|
|
930
|
-
*
|
|
931
|
-
* This gives callers the layout object needed to construct compatible bind
|
|
932
|
-
* groups for the pipeline.
|
|
933
|
-
*
|
|
934
|
-
* This example shows the API in its basic form.
|
|
935
|
-
*
|
|
936
|
-
* ```js
|
|
937
|
-
* const layout = pipeline.getBindGroupLayout(0);
|
|
938
|
-
* ```
|
|
939
|
-
*
|
|
940
|
-
* - Auto-layout pipelines lazily build and cache layouts by group index.
|
|
941
|
-
* - Explicit-layout pipelines return their original layout for any requested index.
|
|
942
|
-
*/
|
|
943
|
-
getBindGroupLayout(index) {
|
|
944
|
-
if (this._explicitLayout) return this._explicitLayout;
|
|
945
|
-
if (this._cachedLayouts.has(index)) return this._cachedLayouts.get(index);
|
|
946
|
-
let layout;
|
|
947
|
-
if (this._autoLayoutEntriesByGroup && process.platform === 'darwin') {
|
|
948
|
-
const entries = this._autoLayoutEntriesByGroup.get(index) ?? [];
|
|
949
|
-
layout = this._device.createBindGroupLayout({ entries });
|
|
950
|
-
} else if (typeof addon.computePipelineGetBindGroupLayout === 'function') {
|
|
951
|
-
layout = new DoeGPUBindGroupLayout(
|
|
952
|
-
addon.computePipelineGetBindGroupLayout(this._native, index),
|
|
614
|
+
},
|
|
615
|
+
textureCreateView(_texture, native) {
|
|
616
|
+
return addon.textureCreateView(native);
|
|
617
|
+
},
|
|
618
|
+
textureDestroy(native) {
|
|
619
|
+
addon.textureRelease(native);
|
|
620
|
+
},
|
|
621
|
+
shaderModuleDestroy(native) {
|
|
622
|
+
addon.shaderModuleRelease(native);
|
|
623
|
+
},
|
|
624
|
+
computePipelineGetBindGroupLayout(pipeline, index, classes) {
|
|
625
|
+
if (pipeline._autoLayoutEntriesByGroup && process.platform === 'darwin') {
|
|
626
|
+
const entries = pipeline._autoLayoutEntriesByGroup.get(index) ?? [];
|
|
627
|
+
return pipeline._device.createBindGroupLayout({ entries });
|
|
628
|
+
}
|
|
629
|
+
if (typeof addon.computePipelineGetBindGroupLayout === 'function') {
|
|
630
|
+
return new classes.DoeGPUBindGroupLayout(
|
|
631
|
+
addon.computePipelineGetBindGroupLayout(pipeline._native, index),
|
|
632
|
+
pipeline._device,
|
|
953
633
|
);
|
|
954
|
-
} else if (this._autoLayoutEntriesByGroup) {
|
|
955
|
-
const entries = this._autoLayoutEntriesByGroup.get(index) ?? [];
|
|
956
|
-
layout = this._device.createBindGroupLayout({ entries });
|
|
957
|
-
} else {
|
|
958
|
-
layout = this._device.createBindGroupLayout({ entries: [] });
|
|
959
634
|
}
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
}
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
class DoeGPUBindGroupLayout {
|
|
977
|
-
constructor(native) { this._native = native; }
|
|
978
|
-
}
|
|
979
|
-
|
|
980
|
-
/**
|
|
981
|
-
* Bind group returned by `device.createBindGroup(...)`.
|
|
982
|
-
*
|
|
983
|
-
* This example shows the API in its basic form.
|
|
984
|
-
*
|
|
985
|
-
* ```js
|
|
986
|
-
* const bindGroup = device.createBindGroup({ layout, entries });
|
|
987
|
-
* ```
|
|
988
|
-
*
|
|
989
|
-
* - The JS wrapper is an opaque handle consumed by pass encoders.
|
|
990
|
-
*/
|
|
991
|
-
class DoeGPUBindGroup {
|
|
992
|
-
constructor(native) { this._native = native; }
|
|
993
|
-
}
|
|
994
|
-
|
|
995
|
-
/**
|
|
996
|
-
* Pipeline layout returned by `device.createPipelineLayout(...)`.
|
|
997
|
-
*
|
|
998
|
-
* This example shows the API in its basic form.
|
|
999
|
-
*
|
|
1000
|
-
* ```js
|
|
1001
|
-
* const layout = device.createPipelineLayout({ bindGroupLayouts: [group0] });
|
|
1002
|
-
* ```
|
|
1003
|
-
*
|
|
1004
|
-
* - The JS wrapper is an opaque handle passed into pipeline creation.
|
|
1005
|
-
*/
|
|
1006
|
-
class DoeGPUPipelineLayout {
|
|
1007
|
-
constructor(native) { this._native = native; }
|
|
1008
|
-
}
|
|
1009
|
-
|
|
1010
|
-
const DOE_LIMITS = Object.freeze({
|
|
1011
|
-
maxTextureDimension1D: 16384,
|
|
1012
|
-
maxTextureDimension2D: 16384,
|
|
1013
|
-
maxTextureDimension3D: 2048,
|
|
1014
|
-
maxTextureArrayLayers: 2048,
|
|
1015
|
-
maxBindGroups: 4,
|
|
1016
|
-
maxBindGroupsPlusVertexBuffers: 24,
|
|
1017
|
-
maxBindingsPerBindGroup: 1000,
|
|
1018
|
-
maxDynamicUniformBuffersPerPipelineLayout: 8,
|
|
1019
|
-
maxDynamicStorageBuffersPerPipelineLayout: 4,
|
|
1020
|
-
maxSampledTexturesPerShaderStage: 16,
|
|
1021
|
-
maxSamplersPerShaderStage: 16,
|
|
1022
|
-
maxStorageBuffersPerShaderStage: 8,
|
|
1023
|
-
maxStorageTexturesPerShaderStage: 4,
|
|
1024
|
-
maxUniformBuffersPerShaderStage: 12,
|
|
1025
|
-
maxUniformBufferBindingSize: 65536,
|
|
1026
|
-
maxStorageBufferBindingSize: 134217728,
|
|
1027
|
-
minUniformBufferOffsetAlignment: 256,
|
|
1028
|
-
minStorageBufferOffsetAlignment: 32,
|
|
1029
|
-
maxVertexBuffers: 8,
|
|
1030
|
-
maxBufferSize: 268435456,
|
|
1031
|
-
maxVertexAttributes: 16,
|
|
1032
|
-
maxVertexBufferArrayStride: 2048,
|
|
1033
|
-
maxInterStageShaderVariables: 16,
|
|
1034
|
-
maxColorAttachments: 8,
|
|
1035
|
-
maxColorAttachmentBytesPerSample: 32,
|
|
1036
|
-
maxComputeWorkgroupStorageSize: 32768,
|
|
1037
|
-
maxComputeInvocationsPerWorkgroup: 1024,
|
|
1038
|
-
maxComputeWorkgroupSizeX: 1024,
|
|
1039
|
-
maxComputeWorkgroupSizeY: 1024,
|
|
1040
|
-
maxComputeWorkgroupSizeZ: 64,
|
|
1041
|
-
maxComputeWorkgroupsPerDimension: 65535,
|
|
1042
|
-
});
|
|
1043
|
-
|
|
1044
|
-
const DOE_FEATURES = Object.freeze(new Set(['shader-f16']));
|
|
1045
|
-
|
|
1046
|
-
/**
|
|
1047
|
-
* Device returned by `adapter.requestDevice()`.
|
|
1048
|
-
*
|
|
1049
|
-
* This is the main full-surface headless WebGPU object exposed by the package.
|
|
1050
|
-
*
|
|
1051
|
-
* This example shows the API in its basic form.
|
|
1052
|
-
*
|
|
1053
|
-
* ```js
|
|
1054
|
-
* const device = await adapter.requestDevice();
|
|
1055
|
-
* ```
|
|
1056
|
-
*
|
|
1057
|
-
* - `queue`, `limits`, and `features` are available as data properties.
|
|
1058
|
-
* - The full package keeps render, texture, sampler, and command APIs on this object.
|
|
1059
|
-
*/
|
|
1060
|
-
class DoeGPUDevice {
|
|
1061
|
-
constructor(native, instance) {
|
|
1062
|
-
this._native = native;
|
|
1063
|
-
this._instance = instance;
|
|
1064
|
-
const q = addon.deviceGetQueue(native);
|
|
1065
|
-
this.queue = new DoeGPUQueue(q, instance, native);
|
|
1066
|
-
this.limits = DOE_LIMITS;
|
|
1067
|
-
this.features = DOE_FEATURES;
|
|
1068
|
-
}
|
|
1069
|
-
|
|
1070
|
-
/**
|
|
1071
|
-
* Create a buffer.
|
|
1072
|
-
*
|
|
1073
|
-
* This allocates a Doe buffer using the supplied WebGPU-shaped descriptor and
|
|
1074
|
-
* returns the package wrapper for it.
|
|
1075
|
-
*
|
|
1076
|
-
* This example shows the API in its basic form.
|
|
1077
|
-
*
|
|
1078
|
-
* ```js
|
|
1079
|
-
* const buffer = device.createBuffer({
|
|
1080
|
-
* size: 16,
|
|
1081
|
-
* usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
|
|
1082
|
-
* });
|
|
1083
|
-
* ```
|
|
1084
|
-
*
|
|
1085
|
-
* - The descriptor follows the standard WebGPU buffer shape.
|
|
1086
|
-
* - The returned wrapper exposes `size`, `usage`, mapping, and destruction helpers.
|
|
1087
|
-
*/
|
|
1088
|
-
createBuffer(descriptor) {
|
|
1089
|
-
const buf = addon.createBuffer(this._native, descriptor);
|
|
1090
|
-
return new DoeGPUBuffer(buf, this._instance, descriptor.size, descriptor.usage, this.queue);
|
|
1091
|
-
}
|
|
1092
|
-
|
|
1093
|
-
/**
|
|
1094
|
-
* Create a shader module from WGSL source.
|
|
1095
|
-
*
|
|
1096
|
-
* This compiles WGSL into a shader module wrapper that can be used by
|
|
1097
|
-
* compute or render pipeline creation.
|
|
1098
|
-
*
|
|
1099
|
-
* This example shows the API in its basic form.
|
|
1100
|
-
*
|
|
1101
|
-
* ```js
|
|
1102
|
-
* const shader = device.createShaderModule({ code: WGSL });
|
|
1103
|
-
* ```
|
|
1104
|
-
*
|
|
1105
|
-
* - `descriptor.code` is required on this surface.
|
|
1106
|
-
* - The package also accepts `descriptor.source` as a convenience alias.
|
|
1107
|
-
*/
|
|
1108
|
-
createShaderModule(descriptor) {
|
|
1109
|
-
const code = descriptor.code || descriptor.source;
|
|
1110
|
-
if (!code) throw new Error('createShaderModule: descriptor.code is required');
|
|
1111
|
-
const mod = addon.createShaderModule(this._native, code);
|
|
1112
|
-
return new DoeGPUShaderModule(mod, code);
|
|
1113
|
-
}
|
|
1114
|
-
|
|
1115
|
-
/**
|
|
1116
|
-
* Create a compute pipeline.
|
|
1117
|
-
*
|
|
1118
|
-
* This builds a pipeline wrapper from a shader module, entry point, and
|
|
1119
|
-
* optional explicit layout information.
|
|
1120
|
-
*
|
|
1121
|
-
* This example shows the API in its basic form.
|
|
1122
|
-
*
|
|
1123
|
-
* ```js
|
|
1124
|
-
* const pipeline = device.createComputePipeline({
|
|
1125
|
-
* layout: "auto",
|
|
1126
|
-
* compute: { module: shader, entryPoint: "main" },
|
|
1127
|
-
* });
|
|
1128
|
-
* ```
|
|
1129
|
-
*
|
|
1130
|
-
* - `layout: "auto"` derives bind-group layouts from the WGSL.
|
|
1131
|
-
* - Explicit pipeline layouts are passed through directly.
|
|
1132
|
-
*/
|
|
1133
|
-
createComputePipeline(descriptor) {
|
|
1134
|
-
const shader = descriptor.compute?.module;
|
|
1135
|
-
const entryPoint = descriptor.compute?.entryPoint || 'main';
|
|
1136
|
-
const layout = descriptor.layout === 'auto' ? null : descriptor.layout;
|
|
1137
|
-
const autoLayoutEntriesByGroup = layout ? null : inferAutoBindGroupLayouts(
|
|
1138
|
-
shader?._code || '',
|
|
1139
|
-
globals.GPUShaderStage.COMPUTE,
|
|
635
|
+
if (pipeline._autoLayoutEntriesByGroup) {
|
|
636
|
+
const entries = pipeline._autoLayoutEntriesByGroup.get(index) ?? [];
|
|
637
|
+
return pipeline._device.createBindGroupLayout({ entries });
|
|
638
|
+
}
|
|
639
|
+
return pipeline._device.createBindGroupLayout({ entries: [] });
|
|
640
|
+
},
|
|
641
|
+
deviceLimits,
|
|
642
|
+
deviceFeatures,
|
|
643
|
+
adapterLimits,
|
|
644
|
+
adapterFeatures,
|
|
645
|
+
preflightShaderSource,
|
|
646
|
+
requireAutoLayoutEntriesFromNative(shader, visibility, path) {
|
|
647
|
+
return requireAutoLayoutEntriesFromNative(
|
|
648
|
+
assertLiveResource(shader, path, 'GPUShaderModule'),
|
|
649
|
+
visibility,
|
|
650
|
+
path,
|
|
1140
651
|
);
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
} : undefined,
|
|
1190
|
-
storageTexture: e.storageTexture,
|
|
1191
|
-
}));
|
|
1192
|
-
const native = addon.createBindGroupLayout(this._native, entries);
|
|
1193
|
-
return new DoeGPUBindGroupLayout(native);
|
|
1194
|
-
}
|
|
1195
|
-
|
|
1196
|
-
/**
|
|
1197
|
-
* Create a bind group.
|
|
1198
|
-
*
|
|
1199
|
-
* This binds resources to a previously created layout and returns the bind
|
|
1200
|
-
* group wrapper used by pass encoders.
|
|
1201
|
-
*
|
|
1202
|
-
* This example shows the API in its basic form.
|
|
1203
|
-
*
|
|
1204
|
-
* ```js
|
|
1205
|
-
* const bindGroup = device.createBindGroup({ layout, entries });
|
|
1206
|
-
* ```
|
|
1207
|
-
*
|
|
1208
|
-
* - Resource buffers may be passed either as `{ buffer, offset, size }` or as bare buffer wrappers.
|
|
1209
|
-
* - Layout and buffer wrappers must come from the same device.
|
|
1210
|
-
*/
|
|
1211
|
-
createBindGroup(descriptor) {
|
|
1212
|
-
const entries = (descriptor.entries || []).map((e) => {
|
|
1213
|
-
const entry = {
|
|
1214
|
-
binding: e.binding,
|
|
1215
|
-
buffer: e.resource?.buffer?._native ?? e.resource?._native ?? null,
|
|
1216
|
-
offset: e.resource?.offset ?? 0,
|
|
1217
|
-
};
|
|
1218
|
-
if (e.resource?.size !== undefined) entry.size = e.resource.size;
|
|
1219
|
-
return entry;
|
|
1220
|
-
});
|
|
1221
|
-
const native = addon.createBindGroup(
|
|
1222
|
-
this._native, descriptor.layout._native, entries);
|
|
1223
|
-
return new DoeGPUBindGroup(native);
|
|
1224
|
-
}
|
|
1225
|
-
|
|
1226
|
-
/**
|
|
1227
|
-
* Create a pipeline layout.
|
|
1228
|
-
*
|
|
1229
|
-
* This combines one or more bind-group layouts into the pipeline layout
|
|
1230
|
-
* wrapper used during pipeline creation.
|
|
1231
|
-
*
|
|
1232
|
-
* This example shows the API in its basic form.
|
|
1233
|
-
*
|
|
1234
|
-
* ```js
|
|
1235
|
-
* const layout = device.createPipelineLayout({ bindGroupLayouts: [group0] });
|
|
1236
|
-
* ```
|
|
1237
|
-
*
|
|
1238
|
-
* - Bind-group layouts are unwrapped to their native handles before creation.
|
|
1239
|
-
* - The returned wrapper is opaque on the JS side.
|
|
1240
|
-
*/
|
|
1241
|
-
createPipelineLayout(descriptor) {
|
|
1242
|
-
const layouts = (descriptor.bindGroupLayouts || []).map((l) => l._native);
|
|
1243
|
-
const native = addon.createPipelineLayout(this._native, layouts);
|
|
1244
|
-
return new DoeGPUPipelineLayout(native);
|
|
1245
|
-
}
|
|
1246
|
-
|
|
1247
|
-
/**
|
|
1248
|
-
* Create a texture.
|
|
1249
|
-
*
|
|
1250
|
-
* This allocates a Doe texture resource from a WebGPU-shaped descriptor and
|
|
1251
|
-
* returns the package wrapper for it.
|
|
1252
|
-
*
|
|
1253
|
-
* This example shows the API in its basic form.
|
|
1254
|
-
*
|
|
1255
|
-
* ```js
|
|
1256
|
-
* const texture = device.createTexture({
|
|
1257
|
-
* size: [64, 64, 1],
|
|
1258
|
-
* format: "rgba8unorm",
|
|
1259
|
-
* usage: GPUTextureUsage.RENDER_ATTACHMENT,
|
|
1260
|
-
* });
|
|
1261
|
-
* ```
|
|
1262
|
-
*
|
|
1263
|
-
* - `descriptor.size` may be a scalar, tuple, or width/height object.
|
|
1264
|
-
* - Omitted format and mip-count fields fall back to package defaults.
|
|
1265
|
-
*/
|
|
1266
|
-
createTexture(descriptor) {
|
|
1267
|
-
const native = addon.createTexture(this._native, {
|
|
1268
|
-
format: descriptor.format || 'rgba8unorm',
|
|
1269
|
-
width: descriptor.size?.[0] ?? descriptor.size?.width ?? descriptor.size ?? 1,
|
|
1270
|
-
height: descriptor.size?.[1] ?? descriptor.size?.height ?? 1,
|
|
1271
|
-
depthOrArrayLayers: descriptor.size?.[2] ?? descriptor.size?.depthOrArrayLayers ?? 1,
|
|
1272
|
-
usage: descriptor.usage || 0,
|
|
1273
|
-
mipLevelCount: descriptor.mipLevelCount || 1,
|
|
652
|
+
},
|
|
653
|
+
deviceGetQueue(native) {
|
|
654
|
+
return addon.deviceGetQueue(native);
|
|
655
|
+
},
|
|
656
|
+
deviceCreateBuffer(device, validated) {
|
|
657
|
+
return addon.createBuffer(assertLiveResource(device, 'GPUDevice.createBuffer', 'GPUDevice'), validated);
|
|
658
|
+
},
|
|
659
|
+
deviceCreateShaderModule(device, code) {
|
|
660
|
+
try {
|
|
661
|
+
return addon.createShaderModule(assertLiveResource(device, 'GPUDevice.createShaderModule', 'GPUDevice'), code);
|
|
662
|
+
} catch (error) {
|
|
663
|
+
throw enrichNativeCompilerError(error, 'GPUDevice.createShaderModule', readLastErrorFields());
|
|
664
|
+
}
|
|
665
|
+
},
|
|
666
|
+
deviceCreateComputePipeline(device, shaderNative, entryPoint, layoutNative) {
|
|
667
|
+
try {
|
|
668
|
+
return addon.createComputePipeline(
|
|
669
|
+
assertLiveResource(device, 'GPUDevice.createComputePipeline', 'GPUDevice'),
|
|
670
|
+
shaderNative,
|
|
671
|
+
entryPoint,
|
|
672
|
+
layoutNative,
|
|
673
|
+
);
|
|
674
|
+
} catch (error) {
|
|
675
|
+
throw enrichNativeCompilerError(error, 'GPUDevice.createComputePipeline', readLastErrorFields());
|
|
676
|
+
}
|
|
677
|
+
},
|
|
678
|
+
deviceCreateBindGroupLayout(device, entries) {
|
|
679
|
+
return addon.createBindGroupLayout(assertLiveResource(device, 'GPUDevice.createBindGroupLayout', 'GPUDevice'), entries);
|
|
680
|
+
},
|
|
681
|
+
deviceCreateBindGroup(device, layoutNative, entries) {
|
|
682
|
+
return addon.createBindGroup(
|
|
683
|
+
assertLiveResource(device, 'GPUDevice.createBindGroup', 'GPUDevice'),
|
|
684
|
+
layoutNative,
|
|
685
|
+
entries,
|
|
686
|
+
);
|
|
687
|
+
},
|
|
688
|
+
deviceCreatePipelineLayout(device, layouts) {
|
|
689
|
+
return addon.createPipelineLayout(assertLiveResource(device, 'GPUDevice.createPipelineLayout', 'GPUDevice'), layouts);
|
|
690
|
+
},
|
|
691
|
+
deviceCreateTexture(device, textureDescriptor, size, usage) {
|
|
692
|
+
return addon.createTexture(assertLiveResource(device, 'GPUDevice.createTexture', 'GPUDevice'), {
|
|
693
|
+
format: textureDescriptor.format || 'rgba8unorm',
|
|
694
|
+
width: size.width,
|
|
695
|
+
height: size.height,
|
|
696
|
+
depthOrArrayLayers: size.depthOrArrayLayers,
|
|
697
|
+
dimension: TEXTURE_DIMENSION_MAP[textureDescriptor.dimension ?? '2d'] ?? 2,
|
|
698
|
+
usage,
|
|
699
|
+
mipLevelCount: assertIntegerInRange(textureDescriptor.mipLevelCount ?? 1, 'GPUDevice.createTexture', 'descriptor.mipLevelCount', { min: 1, max: UINT32_MAX }),
|
|
1274
700
|
});
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
const
|
|
1315
|
-
return
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
* This creates the full-surface Doe device associated with the adapter.
|
|
1381
|
-
*
|
|
1382
|
-
* This example shows the API in its basic form.
|
|
1383
|
-
*
|
|
1384
|
-
* ```js
|
|
1385
|
-
* const device = await adapter.requestDevice();
|
|
1386
|
-
* ```
|
|
1387
|
-
*
|
|
1388
|
-
* - The descriptor is accepted for WebGPU API shape compatibility.
|
|
1389
|
-
* - The returned device includes the full package surface.
|
|
1390
|
-
*/
|
|
1391
|
-
async requestDevice(descriptor) {
|
|
1392
|
-
const device = addon.requestDevice(this._instance, this._native);
|
|
1393
|
-
return new DoeGPUDevice(device, this._instance);
|
|
1394
|
-
}
|
|
1395
|
-
|
|
1396
|
-
/**
|
|
1397
|
-
* Release the native adapter.
|
|
1398
|
-
*
|
|
1399
|
-
* This tears down the adapter handle that was returned by Doe for this GPU.
|
|
1400
|
-
*
|
|
1401
|
-
* This example shows the API in its basic form.
|
|
1402
|
-
*
|
|
1403
|
-
* ```js
|
|
1404
|
-
* adapter.destroy();
|
|
1405
|
-
* ```
|
|
1406
|
-
*
|
|
1407
|
-
* - Reusing the adapter after destruction is unsupported.
|
|
1408
|
-
*/
|
|
1409
|
-
destroy() {
|
|
1410
|
-
addon.adapterRelease(this._native);
|
|
1411
|
-
this._native = null;
|
|
1412
|
-
}
|
|
1413
|
-
}
|
|
1414
|
-
|
|
1415
|
-
/**
|
|
1416
|
-
* GPU root object returned by `create()` or installed at `navigator.gpu`.
|
|
1417
|
-
*
|
|
1418
|
-
* This example shows the API in its basic form.
|
|
1419
|
-
*
|
|
1420
|
-
* ```js
|
|
1421
|
-
* const gpu = create();
|
|
1422
|
-
* ```
|
|
1423
|
-
*
|
|
1424
|
-
* - This is a headless package-owned GPU object, not a browser-owned DOM object.
|
|
1425
|
-
*/
|
|
1426
|
-
class DoeGPU {
|
|
1427
|
-
constructor(instance) {
|
|
1428
|
-
this._instance = instance;
|
|
1429
|
-
}
|
|
701
|
+
},
|
|
702
|
+
deviceCreateSampler(device, descriptor) {
|
|
703
|
+
return addon.createSampler(assertLiveResource(device, 'GPUDevice.createSampler', 'GPUDevice'), descriptor);
|
|
704
|
+
},
|
|
705
|
+
deviceCreateRenderPipeline(device, descriptor) {
|
|
706
|
+
return addon.createRenderPipeline(
|
|
707
|
+
assertLiveResource(device, 'GPUDevice.createRenderPipeline', 'GPUDevice'),
|
|
708
|
+
{
|
|
709
|
+
layout: descriptor.layout,
|
|
710
|
+
vertex: {
|
|
711
|
+
module: descriptor.vertexModule,
|
|
712
|
+
entryPoint: descriptor.vertexEntryPoint,
|
|
713
|
+
buffers: descriptor.vertexBuffers ?? [],
|
|
714
|
+
},
|
|
715
|
+
fragment: {
|
|
716
|
+
module: descriptor.fragmentModule,
|
|
717
|
+
entryPoint: descriptor.fragmentEntryPoint,
|
|
718
|
+
targets: [{ format: descriptor.colorFormat }],
|
|
719
|
+
},
|
|
720
|
+
primitive: descriptor.primitive ? {
|
|
721
|
+
topology: descriptor.primitive.topology ?? 'triangle-list',
|
|
722
|
+
frontFace: descriptor.primitive.frontFace ?? 'ccw',
|
|
723
|
+
cullMode: descriptor.primitive.cullMode ?? 'none',
|
|
724
|
+
unclippedDepth: descriptor.primitive.unclippedDepth ?? false,
|
|
725
|
+
} : undefined,
|
|
726
|
+
multisample: descriptor.multisample ? {
|
|
727
|
+
count: descriptor.multisample.count ?? 1,
|
|
728
|
+
mask: descriptor.multisample.mask ?? 0xFFFF_FFFF,
|
|
729
|
+
alphaToCoverageEnabled: descriptor.multisample.alphaToCoverageEnabled ?? false,
|
|
730
|
+
} : undefined,
|
|
731
|
+
depthStencil: descriptor.depthStencil ? {
|
|
732
|
+
format: descriptor.depthStencil.format,
|
|
733
|
+
depthWriteEnabled: descriptor.depthStencil.depthWriteEnabled ?? false,
|
|
734
|
+
depthCompare: descriptor.depthStencil.depthCompare ?? 'always',
|
|
735
|
+
} : undefined,
|
|
736
|
+
},
|
|
737
|
+
);
|
|
738
|
+
},
|
|
739
|
+
deviceCreateQuerySet(device, descriptor) {
|
|
740
|
+
const QUERY_TYPE_TIMESTAMP = 2;
|
|
741
|
+
return addon.createQuerySet(
|
|
742
|
+
assertLiveResource(device, 'GPUDevice.createQuerySet', 'GPUDevice'),
|
|
743
|
+
QUERY_TYPE_TIMESTAMP,
|
|
744
|
+
descriptor.count,
|
|
745
|
+
);
|
|
746
|
+
},
|
|
747
|
+
querySetDestroy(native) {
|
|
748
|
+
addon.querySetDestroy(native);
|
|
749
|
+
},
|
|
750
|
+
deviceCreateCommandEncoder(device) {
|
|
751
|
+
return new DoeGPUCommandEncoder(null, device);
|
|
752
|
+
},
|
|
753
|
+
deviceDestroy(native) {
|
|
754
|
+
addon.deviceRelease(native);
|
|
755
|
+
},
|
|
756
|
+
adapterRequestDevice(adapter, _descriptor, classes) {
|
|
757
|
+
assertLiveResource(adapter, 'GPUAdapter.requestDevice', 'GPUAdapter');
|
|
758
|
+
const native = addon.requestDevice(adapter._instance, adapter._native);
|
|
759
|
+
const device = {
|
|
760
|
+
_destroyed: false,
|
|
761
|
+
_resourceLabel: 'GPUDevice',
|
|
762
|
+
_resourceOwner: null,
|
|
763
|
+
createBuffer: classes.DoeGPUDevice.prototype.createBuffer,
|
|
764
|
+
createShaderModule: classes.DoeGPUDevice.prototype.createShaderModule,
|
|
765
|
+
createComputePipeline: classes.DoeGPUDevice.prototype.createComputePipeline,
|
|
766
|
+
createComputePipelineAsync: classes.DoeGPUDevice.prototype.createComputePipelineAsync,
|
|
767
|
+
createBindGroupLayout: classes.DoeGPUDevice.prototype.createBindGroupLayout,
|
|
768
|
+
createBindGroup: classes.DoeGPUDevice.prototype.createBindGroup,
|
|
769
|
+
createPipelineLayout: classes.DoeGPUDevice.prototype.createPipelineLayout,
|
|
770
|
+
createTexture: classes.DoeGPUDevice.prototype.createTexture,
|
|
771
|
+
createSampler: classes.DoeGPUDevice.prototype.createSampler,
|
|
772
|
+
createRenderPipeline: classes.DoeGPUDevice.prototype.createRenderPipeline,
|
|
773
|
+
createQuerySet: classes.DoeGPUDevice.prototype.createQuerySet,
|
|
774
|
+
createCommandEncoder: classes.DoeGPUDevice.prototype.createCommandEncoder,
|
|
775
|
+
destroy: classes.DoeGPUDevice.prototype.destroy,
|
|
776
|
+
};
|
|
777
|
+
device._native = native;
|
|
778
|
+
device._instance = adapter._instance;
|
|
779
|
+
device.limits = deviceLimits(native);
|
|
780
|
+
device.features = deviceFeatures(native);
|
|
781
|
+
const queue = {
|
|
782
|
+
_destroyed: false,
|
|
783
|
+
_resourceLabel: 'GPUQueue',
|
|
784
|
+
_resourceOwner: device,
|
|
785
|
+
hasPendingSubmissions: classes.DoeGPUQueue.prototype.hasPendingSubmissions,
|
|
786
|
+
markSubmittedWorkDone: classes.DoeGPUQueue.prototype.markSubmittedWorkDone,
|
|
787
|
+
submit: classes.DoeGPUQueue.prototype.submit,
|
|
788
|
+
writeBuffer: classes.DoeGPUQueue.prototype.writeBuffer,
|
|
789
|
+
onSubmittedWorkDone: classes.DoeGPUQueue.prototype.onSubmittedWorkDone,
|
|
790
|
+
};
|
|
791
|
+
queue._native = addon.deviceGetQueue(native);
|
|
792
|
+
queue._instance = adapter._instance;
|
|
793
|
+
queue._device = device;
|
|
794
|
+
this.initQueueState(queue);
|
|
795
|
+
device.queue = queue;
|
|
796
|
+
return device;
|
|
797
|
+
},
|
|
798
|
+
adapterDestroy(native) {
|
|
799
|
+
addon.adapterRelease(native);
|
|
800
|
+
},
|
|
801
|
+
gpuRequestAdapter(gpu, _options, classes) {
|
|
802
|
+
const adapter = addon.requestAdapter(gpu._instance);
|
|
803
|
+
return new classes.DoeGPUAdapter(adapter, gpu._instance);
|
|
804
|
+
},
|
|
805
|
+
};
|
|
1430
806
|
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
}
|
|
807
|
+
const {
|
|
808
|
+
DoeGPUBuffer,
|
|
809
|
+
DoeGPUQueue,
|
|
810
|
+
DoeGPUTexture,
|
|
811
|
+
DoeGPUTextureView,
|
|
812
|
+
DoeGPUSampler,
|
|
813
|
+
DoeGPURenderPipeline,
|
|
814
|
+
DoeGPUShaderModule,
|
|
815
|
+
DoeGPUComputePipeline,
|
|
816
|
+
DoeGPUBindGroupLayout,
|
|
817
|
+
DoeGPUBindGroup,
|
|
818
|
+
DoeGPUPipelineLayout,
|
|
819
|
+
DoeGPUDevice,
|
|
820
|
+
DoeGPUAdapter,
|
|
821
|
+
DoeGPU,
|
|
822
|
+
} = createFullSurfaceClasses({
|
|
823
|
+
globals,
|
|
824
|
+
backend: fullSurfaceBackend,
|
|
825
|
+
});
|
|
1451
826
|
|
|
1452
827
|
/**
|
|
1453
828
|
* Create a package-local `GPU` object backed by the Doe native runtime.
|
|
@@ -1473,6 +848,15 @@ export function create(createArgs = null) {
|
|
|
1473
848
|
return new DoeGPU(instance);
|
|
1474
849
|
}
|
|
1475
850
|
|
|
851
|
+
export function setNativeTimeoutMs(timeoutMs) {
|
|
852
|
+
ensureLibrary();
|
|
853
|
+
validatePositiveInteger(timeoutMs, 'native timeout');
|
|
854
|
+
if (typeof addon.setTimeoutMs !== 'function') {
|
|
855
|
+
throw new Error('setNativeTimeoutMs is not supported by the loaded addon.');
|
|
856
|
+
}
|
|
857
|
+
addon.setTimeoutMs(timeoutMs);
|
|
858
|
+
}
|
|
859
|
+
|
|
1476
860
|
/**
|
|
1477
861
|
* Install the package WebGPU globals onto a target object and return its GPU.
|
|
1478
862
|
*
|
|
@@ -1493,33 +877,8 @@ export function create(createArgs = null) {
|
|
|
1493
877
|
* - The returned GPU is still headless/package-owned, not browser DOM ownership or browser-process parity.
|
|
1494
878
|
*/
|
|
1495
879
|
export function setupGlobals(target = globalThis, createArgs = null) {
|
|
1496
|
-
for (const [name, value] of Object.entries(globals)) {
|
|
1497
|
-
if (target[name] === undefined) {
|
|
1498
|
-
Object.defineProperty(target, name, {
|
|
1499
|
-
value,
|
|
1500
|
-
writable: true,
|
|
1501
|
-
configurable: true,
|
|
1502
|
-
enumerable: false,
|
|
1503
|
-
});
|
|
1504
|
-
}
|
|
1505
|
-
}
|
|
1506
880
|
const gpu = create(createArgs);
|
|
1507
|
-
|
|
1508
|
-
Object.defineProperty(target, 'navigator', {
|
|
1509
|
-
value: { gpu },
|
|
1510
|
-
writable: true,
|
|
1511
|
-
configurable: true,
|
|
1512
|
-
enumerable: false,
|
|
1513
|
-
});
|
|
1514
|
-
} else if (!target.navigator.gpu) {
|
|
1515
|
-
Object.defineProperty(target.navigator, 'gpu', {
|
|
1516
|
-
value: gpu,
|
|
1517
|
-
writable: true,
|
|
1518
|
-
configurable: true,
|
|
1519
|
-
enumerable: false,
|
|
1520
|
-
});
|
|
1521
|
-
}
|
|
1522
|
-
return gpu;
|
|
881
|
+
return setupGlobalsOnTarget(target, gpu, globals);
|
|
1523
882
|
}
|
|
1524
883
|
|
|
1525
884
|
/**
|
|
@@ -1539,8 +898,7 @@ export function setupGlobals(target = globalThis, createArgs = null) {
|
|
|
1539
898
|
* - `adapterOptions` are accepted for WebGPU shape compatibility; the current Doe package path does not use them for adapter filtering.
|
|
1540
899
|
*/
|
|
1541
900
|
export async function requestAdapter(adapterOptions = undefined, createArgs = null) {
|
|
1542
|
-
|
|
1543
|
-
return gpu.requestAdapter(adapterOptions);
|
|
901
|
+
return requestAdapterFromCreate(create, adapterOptions, createArgs);
|
|
1544
902
|
}
|
|
1545
903
|
|
|
1546
904
|
/**
|
|
@@ -1565,9 +923,7 @@ export async function requestAdapter(adapterOptions = undefined, createArgs = nu
|
|
|
1565
923
|
* - Missing runtime prerequisites still fail at request time through the same addon/library checks as `create()`.
|
|
1566
924
|
*/
|
|
1567
925
|
export async function requestDevice(options = {}) {
|
|
1568
|
-
|
|
1569
|
-
const adapter = await requestAdapter(options?.adapterOptions, createArgs);
|
|
1570
|
-
return adapter.requestDevice(options?.deviceDescriptor);
|
|
926
|
+
return requestDeviceFromRequestAdapter(requestAdapter, options);
|
|
1571
927
|
}
|
|
1572
928
|
|
|
1573
929
|
/**
|
|
@@ -1590,8 +946,7 @@ export async function requestDevice(options = {}) {
|
|
|
1590
946
|
*/
|
|
1591
947
|
export function providerInfo() {
|
|
1592
948
|
const flavor = libraryFlavor(DOE_LIB_PATH);
|
|
1593
|
-
return {
|
|
1594
|
-
module: '@simulatte/webgpu',
|
|
949
|
+
return buildProviderInfo({
|
|
1595
950
|
loaded: !!addon && !!DOE_LIB_PATH,
|
|
1596
951
|
loadError: !addon ? 'native addon not found' : !DOE_LIB_PATH ? 'libwebgpu_doe not found' : '',
|
|
1597
952
|
defaultCreateArgs: [],
|
|
@@ -1602,7 +957,7 @@ export function providerInfo() {
|
|
|
1602
957
|
buildMetadataPath: DOE_BUILD_METADATA.path,
|
|
1603
958
|
leanVerifiedBuild: DOE_BUILD_METADATA.leanVerifiedBuild,
|
|
1604
959
|
proofArtifactSha256: DOE_BUILD_METADATA.proofArtifactSha256,
|
|
1605
|
-
};
|
|
960
|
+
});
|
|
1606
961
|
}
|
|
1607
962
|
|
|
1608
963
|
/**
|