@simulatte/webgpu 0.2.1 → 0.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +104 -0
- package/README.md +135 -108
- package/{API_CONTRACT.md → api-contract.md} +63 -3
- package/assets/fawn-icon-main-256.png +0 -0
- package/assets/package-surface-cube-snapshot.svg +14 -14
- package/compat-scope.md +46 -0
- package/headless-webgpu-comparison.md +3 -3
- package/layering-plan.md +259 -0
- package/native/doe_napi.c +110 -17
- package/package.json +28 -8
- 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/doe_napi.node +0 -0
- package/prebuilds/linux-x64/libwebgpu_dawn.so +0 -0
- package/prebuilds/linux-x64/libwebgpu_doe.so +0 -0
- package/prebuilds/linux-x64/metadata.json +26 -0
- package/scripts/generate-readme-assets.js +2 -2
- package/src/bun-ffi.js +3 -2
- package/src/bun.js +2 -2
- package/src/compute.d.ts +161 -0
- package/src/compute.js +277 -0
- package/src/doe.d.ts +84 -0
- package/src/doe.js +275 -0
- package/src/full.d.ts +112 -0
- package/src/full.js +10 -0
- package/src/index.js +114 -15
- package/src/node-runtime.js +2 -2
- package/src/node.js +2 -2
- package/src/runtime_cli.js +3 -1
- package/support-contracts.md +339 -0
- package/zig-source-inventory.md +468 -0
- package/COMPAT_SCOPE.md +0 -32
package/src/doe.js
ADDED
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
const DOE_GPU_BUFFER_USAGE = {
|
|
2
|
+
MAP_READ: 0x0001,
|
|
3
|
+
COPY_SRC: 0x0004,
|
|
4
|
+
COPY_DST: 0x0008,
|
|
5
|
+
UNIFORM: 0x0040,
|
|
6
|
+
STORAGE: 0x0080,
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const DOE_GPU_SHADER_STAGE = {
|
|
10
|
+
COMPUTE: 0x4,
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const DOE_GPU_MAP_MODE = {
|
|
14
|
+
READ: 0x0001,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const DOE_BUFFER_META = new WeakMap();
|
|
18
|
+
|
|
19
|
+
function resolve_buffer_usage_token(token) {
|
|
20
|
+
switch (token) {
|
|
21
|
+
case 'upload':
|
|
22
|
+
return DOE_GPU_BUFFER_USAGE.COPY_DST;
|
|
23
|
+
case 'readback':
|
|
24
|
+
return DOE_GPU_BUFFER_USAGE.COPY_SRC | DOE_GPU_BUFFER_USAGE.COPY_DST | DOE_GPU_BUFFER_USAGE.MAP_READ;
|
|
25
|
+
case 'uniform':
|
|
26
|
+
return DOE_GPU_BUFFER_USAGE.UNIFORM | DOE_GPU_BUFFER_USAGE.COPY_DST;
|
|
27
|
+
case 'storage-read':
|
|
28
|
+
return DOE_GPU_BUFFER_USAGE.STORAGE | DOE_GPU_BUFFER_USAGE.COPY_DST;
|
|
29
|
+
case 'storage-readwrite':
|
|
30
|
+
return DOE_GPU_BUFFER_USAGE.STORAGE | DOE_GPU_BUFFER_USAGE.COPY_DST | DOE_GPU_BUFFER_USAGE.COPY_SRC;
|
|
31
|
+
default:
|
|
32
|
+
throw new Error(`Unknown Doe buffer usage token: ${token}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function resolve_buffer_usage(usage) {
|
|
37
|
+
if (typeof usage === 'number') return usage;
|
|
38
|
+
if (typeof usage === 'string') return resolve_buffer_usage_token(usage);
|
|
39
|
+
if (Array.isArray(usage)) {
|
|
40
|
+
return usage.reduce((mask, token) => mask | resolve_buffer_usage_token(token), 0);
|
|
41
|
+
}
|
|
42
|
+
throw new Error('Doe buffer usage must be a number, string, or string array.');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function infer_binding_access_token(token) {
|
|
46
|
+
switch (token) {
|
|
47
|
+
case 'uniform':
|
|
48
|
+
return 'uniform';
|
|
49
|
+
case 'storage-read':
|
|
50
|
+
return 'storage-read';
|
|
51
|
+
case 'storage-readwrite':
|
|
52
|
+
return 'storage-readwrite';
|
|
53
|
+
default:
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function infer_binding_access(usage) {
|
|
59
|
+
if (typeof usage === 'number' || usage == null) return null;
|
|
60
|
+
const tokens = typeof usage === 'string'
|
|
61
|
+
? [usage]
|
|
62
|
+
: Array.isArray(usage)
|
|
63
|
+
? usage
|
|
64
|
+
: null;
|
|
65
|
+
if (!tokens) {
|
|
66
|
+
throw new Error('Doe buffer usage must be a number, string, or string array.');
|
|
67
|
+
}
|
|
68
|
+
const inferred = [...new Set(tokens.map(infer_binding_access_token).filter(Boolean))];
|
|
69
|
+
if (inferred.length > 1) {
|
|
70
|
+
throw new Error(`Doe buffer usage cannot imply multiple binding access modes: ${inferred.join(', ')}`);
|
|
71
|
+
}
|
|
72
|
+
return inferred[0] ?? null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function remember_buffer_usage(buffer, usage) {
|
|
76
|
+
DOE_BUFFER_META.set(buffer, {
|
|
77
|
+
binding_access: infer_binding_access(usage),
|
|
78
|
+
});
|
|
79
|
+
return buffer;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function inferred_binding_access_for_buffer(buffer) {
|
|
83
|
+
return DOE_BUFFER_META.get(buffer)?.binding_access ?? null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function normalize_workgroups(workgroups) {
|
|
87
|
+
if (typeof workgroups === 'number') {
|
|
88
|
+
return [workgroups, 1, 1];
|
|
89
|
+
}
|
|
90
|
+
if (Array.isArray(workgroups) && workgroups.length === 3) {
|
|
91
|
+
return workgroups;
|
|
92
|
+
}
|
|
93
|
+
throw new Error('Doe workgroups must be a number or a [x, y, z] tuple.');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function normalize_data_view(data) {
|
|
97
|
+
if (ArrayBuffer.isView(data)) {
|
|
98
|
+
return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
|
|
99
|
+
}
|
|
100
|
+
if (data instanceof ArrayBuffer) {
|
|
101
|
+
return new Uint8Array(data);
|
|
102
|
+
}
|
|
103
|
+
throw new Error('Doe buffer data must be an ArrayBuffer or ArrayBufferView.');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function normalize_binding(binding, index) {
|
|
107
|
+
const entry = binding && typeof binding === 'object' && 'buffer' in binding
|
|
108
|
+
? binding
|
|
109
|
+
: { buffer: binding };
|
|
110
|
+
const access = entry.access ?? inferred_binding_access_for_buffer(entry.buffer);
|
|
111
|
+
if (!access) {
|
|
112
|
+
throw new Error(
|
|
113
|
+
'Doe binding access is required for buffers without Doe helper usage metadata. ' +
|
|
114
|
+
'Pass { buffer, access } or create the buffer through doe.createBuffer* with a bindable usage token.'
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
return {
|
|
118
|
+
binding: index,
|
|
119
|
+
buffer: entry.buffer,
|
|
120
|
+
access,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function bind_group_layout_entry(binding) {
|
|
125
|
+
const buffer_type = binding.access === 'uniform'
|
|
126
|
+
? 'uniform'
|
|
127
|
+
: binding.access === 'storage-read'
|
|
128
|
+
? 'read-only-storage'
|
|
129
|
+
: 'storage';
|
|
130
|
+
return {
|
|
131
|
+
binding: binding.binding,
|
|
132
|
+
visibility: DOE_GPU_SHADER_STAGE.COMPUTE,
|
|
133
|
+
buffer: { type: buffer_type },
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function bind_group_entry(binding) {
|
|
138
|
+
return {
|
|
139
|
+
binding: binding.binding,
|
|
140
|
+
resource: { buffer: binding.buffer },
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
class DoeKernel {
|
|
145
|
+
constructor(device, pipeline, layout, entry_point) {
|
|
146
|
+
this.device = device;
|
|
147
|
+
this.pipeline = pipeline;
|
|
148
|
+
this.layout = layout;
|
|
149
|
+
this.entryPoint = entry_point;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async dispatch(options) {
|
|
153
|
+
const bindings = (options.bindings ?? []).map(normalize_binding);
|
|
154
|
+
const workgroups = normalize_workgroups(options.workgroups);
|
|
155
|
+
const bind_group = this.device.createBindGroup({
|
|
156
|
+
label: options.label ?? undefined,
|
|
157
|
+
layout: this.layout,
|
|
158
|
+
entries: bindings.map(bind_group_entry),
|
|
159
|
+
});
|
|
160
|
+
const encoder = this.device.createCommandEncoder({ label: options.label ?? undefined });
|
|
161
|
+
const pass = encoder.beginComputePass({ label: options.label ?? undefined });
|
|
162
|
+
pass.setPipeline(this.pipeline);
|
|
163
|
+
if (bindings.length > 0) {
|
|
164
|
+
pass.setBindGroup(0, bind_group);
|
|
165
|
+
}
|
|
166
|
+
pass.dispatchWorkgroups(workgroups[0], workgroups[1], workgroups[2]);
|
|
167
|
+
pass.end();
|
|
168
|
+
this.device.queue.submit([encoder.finish()]);
|
|
169
|
+
if (typeof this.device.queue.onSubmittedWorkDone === 'function') {
|
|
170
|
+
await this.device.queue.onSubmittedWorkDone();
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function create_bound_doe(device) {
|
|
176
|
+
return {
|
|
177
|
+
device,
|
|
178
|
+
createBuffer(options) {
|
|
179
|
+
return doe.createBuffer(device, options);
|
|
180
|
+
},
|
|
181
|
+
createBufferFromData(data, options = {}) {
|
|
182
|
+
return doe.createBufferFromData(device, data, options);
|
|
183
|
+
},
|
|
184
|
+
readBuffer(buffer, type, options = {}) {
|
|
185
|
+
return doe.readBuffer(device, buffer, type, options);
|
|
186
|
+
},
|
|
187
|
+
runCompute(options) {
|
|
188
|
+
return doe.runCompute(device, options);
|
|
189
|
+
},
|
|
190
|
+
compileCompute(options) {
|
|
191
|
+
return doe.compileCompute(device, options);
|
|
192
|
+
},
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function compile_compute(device, options) {
|
|
197
|
+
const bindings = (options.bindings ?? []).map(normalize_binding);
|
|
198
|
+
const shader = device.createShaderModule({ code: options.code });
|
|
199
|
+
const bind_group_layout = device.createBindGroupLayout({
|
|
200
|
+
entries: bindings.map(bind_group_layout_entry),
|
|
201
|
+
});
|
|
202
|
+
const pipeline_layout = device.createPipelineLayout({
|
|
203
|
+
bindGroupLayouts: [bind_group_layout],
|
|
204
|
+
});
|
|
205
|
+
const pipeline = device.createComputePipeline({
|
|
206
|
+
layout: pipeline_layout,
|
|
207
|
+
compute: {
|
|
208
|
+
module: shader,
|
|
209
|
+
entryPoint: options.entryPoint ?? 'main',
|
|
210
|
+
},
|
|
211
|
+
});
|
|
212
|
+
return new DoeKernel(device, pipeline, bind_group_layout, options.entryPoint ?? 'main');
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export const doe = {
|
|
216
|
+
bind(device) {
|
|
217
|
+
return create_bound_doe(device);
|
|
218
|
+
},
|
|
219
|
+
|
|
220
|
+
createBuffer(device, options) {
|
|
221
|
+
return remember_buffer_usage(device.createBuffer({
|
|
222
|
+
label: options.label ?? undefined,
|
|
223
|
+
size: options.size,
|
|
224
|
+
usage: resolve_buffer_usage(options.usage),
|
|
225
|
+
mappedAtCreation: options.mappedAtCreation ?? false,
|
|
226
|
+
}), options.usage);
|
|
227
|
+
},
|
|
228
|
+
|
|
229
|
+
createBufferFromData(device, data, options = {}) {
|
|
230
|
+
const view = normalize_data_view(data);
|
|
231
|
+
const usage = options.usage ?? 'storage-read';
|
|
232
|
+
const buffer = remember_buffer_usage(device.createBuffer({
|
|
233
|
+
label: options.label ?? undefined,
|
|
234
|
+
size: view.byteLength,
|
|
235
|
+
usage: resolve_buffer_usage(usage),
|
|
236
|
+
}), usage);
|
|
237
|
+
device.queue.writeBuffer(buffer, 0, view);
|
|
238
|
+
return buffer;
|
|
239
|
+
},
|
|
240
|
+
|
|
241
|
+
async readBuffer(device, buffer, type, options = {}) {
|
|
242
|
+
const offset = options.offset ?? 0;
|
|
243
|
+
const size = options.size ?? Math.max(0, (buffer.size ?? 0) - offset);
|
|
244
|
+
const staging = device.createBuffer({
|
|
245
|
+
label: options.label ?? undefined,
|
|
246
|
+
size,
|
|
247
|
+
usage: DOE_GPU_BUFFER_USAGE.COPY_DST | DOE_GPU_BUFFER_USAGE.MAP_READ,
|
|
248
|
+
});
|
|
249
|
+
const encoder = device.createCommandEncoder({ label: options.label ?? undefined });
|
|
250
|
+
encoder.copyBufferToBuffer(buffer, offset, staging, 0, size);
|
|
251
|
+
device.queue.submit([encoder.finish()]);
|
|
252
|
+
await staging.mapAsync(DOE_GPU_MAP_MODE.READ);
|
|
253
|
+
const copy = staging.getMappedRange().slice(0);
|
|
254
|
+
staging.unmap();
|
|
255
|
+
if (typeof staging.destroy === 'function') {
|
|
256
|
+
staging.destroy();
|
|
257
|
+
}
|
|
258
|
+
return new type(copy);
|
|
259
|
+
},
|
|
260
|
+
|
|
261
|
+
async runCompute(device, options) {
|
|
262
|
+
const kernel = compile_compute(device, options);
|
|
263
|
+
await kernel.dispatch({
|
|
264
|
+
bindings: options.bindings ?? [],
|
|
265
|
+
workgroups: options.workgroups,
|
|
266
|
+
label: options.label,
|
|
267
|
+
});
|
|
268
|
+
},
|
|
269
|
+
|
|
270
|
+
compileCompute(device, options) {
|
|
271
|
+
return compile_compute(device, options);
|
|
272
|
+
},
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
export default doe;
|
package/src/full.d.ts
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
BoundDoeNamespace,
|
|
3
|
+
DoeKernelDispatchOptions,
|
|
4
|
+
DoeNamespace,
|
|
5
|
+
DoeRunComputeOptions,
|
|
6
|
+
} from "./doe.js";
|
|
7
|
+
|
|
8
|
+
export interface ProviderInfo {
|
|
9
|
+
module: string;
|
|
10
|
+
loaded: boolean;
|
|
11
|
+
loadError: string;
|
|
12
|
+
defaultCreateArgs: string[];
|
|
13
|
+
doeNative: boolean;
|
|
14
|
+
libraryFlavor: string;
|
|
15
|
+
doeLibraryPath: string;
|
|
16
|
+
buildMetadataSource: string;
|
|
17
|
+
buildMetadataPath: string;
|
|
18
|
+
leanVerifiedBuild: boolean | null;
|
|
19
|
+
proofArtifactSha256: string | null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface DoeRuntimeRunResult {
|
|
23
|
+
ok: boolean;
|
|
24
|
+
exitCode: number;
|
|
25
|
+
stdout: string;
|
|
26
|
+
stderr: string;
|
|
27
|
+
signal: string | null;
|
|
28
|
+
command: string[];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface DoeRuntimeBenchResult extends DoeRuntimeRunResult {
|
|
32
|
+
traceJsonlPath: string | null;
|
|
33
|
+
traceMetaPath: string | null;
|
|
34
|
+
traceMeta: Record<string, unknown> | null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface DoeRuntimeBenchOptions {
|
|
38
|
+
commandsPath: string;
|
|
39
|
+
quirksPath?: string;
|
|
40
|
+
vendor?: string;
|
|
41
|
+
api?: string;
|
|
42
|
+
family?: string;
|
|
43
|
+
driver?: string;
|
|
44
|
+
traceJsonlPath?: string;
|
|
45
|
+
traceMetaPath?: string;
|
|
46
|
+
uploadBufferUsage?: string;
|
|
47
|
+
uploadSubmitEvery?: number;
|
|
48
|
+
queueWaitMode?: string;
|
|
49
|
+
queueSyncMode?: string;
|
|
50
|
+
extraArgs?: string[];
|
|
51
|
+
cwd?: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface DoeRuntime {
|
|
55
|
+
binPath: string;
|
|
56
|
+
libPath: string | null;
|
|
57
|
+
runRaw(args: string[], spawnOptions?: Record<string, unknown>): DoeRuntimeRunResult;
|
|
58
|
+
runBench(options: DoeRuntimeBenchOptions): DoeRuntimeBenchResult;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface RequestDeviceOptions {
|
|
62
|
+
adapterOptions?: GPURequestAdapterOptions;
|
|
63
|
+
deviceDescriptor?: GPUDeviceDescriptor;
|
|
64
|
+
createArgs?: string[] | null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface FullDoeRunComputeOptions extends DoeRunComputeOptions<GPUBuffer> {}
|
|
68
|
+
|
|
69
|
+
export interface FullDoeKernelDispatchOptions extends DoeKernelDispatchOptions<GPUBuffer> {}
|
|
70
|
+
|
|
71
|
+
export interface FullDoeKernel {
|
|
72
|
+
readonly device: GPUDevice;
|
|
73
|
+
readonly entryPoint: string;
|
|
74
|
+
dispatch(options: FullDoeKernelDispatchOptions): Promise<void>;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface FullBoundDoeNamespace
|
|
78
|
+
extends BoundDoeNamespace<GPUDevice, GPUBuffer, FullDoeKernel, FullDoeRunComputeOptions> {}
|
|
79
|
+
|
|
80
|
+
export interface FullDoeNamespace
|
|
81
|
+
extends DoeNamespace<GPUDevice, GPUBuffer, FullDoeKernel, FullBoundDoeNamespace, FullDoeRunComputeOptions> {}
|
|
82
|
+
|
|
83
|
+
export const globals: Record<string, unknown>;
|
|
84
|
+
export function create(createArgs?: string[] | null): GPU;
|
|
85
|
+
export function setupGlobals(target?: object, createArgs?: string[] | null): GPU;
|
|
86
|
+
export function requestAdapter(
|
|
87
|
+
adapterOptions?: GPURequestAdapterOptions,
|
|
88
|
+
createArgs?: string[] | null
|
|
89
|
+
): Promise<GPUAdapter | null>;
|
|
90
|
+
export function requestDevice(options?: RequestDeviceOptions): Promise<GPUDevice>;
|
|
91
|
+
export function providerInfo(): ProviderInfo;
|
|
92
|
+
export function createDoeRuntime(options?: {
|
|
93
|
+
binPath?: string;
|
|
94
|
+
libPath?: string;
|
|
95
|
+
}): DoeRuntime;
|
|
96
|
+
export function runDawnVsDoeCompare(options: Record<string, unknown>): DoeRuntimeRunResult;
|
|
97
|
+
|
|
98
|
+
export const doe: FullDoeNamespace;
|
|
99
|
+
|
|
100
|
+
declare const _default: {
|
|
101
|
+
create: typeof create;
|
|
102
|
+
globals: typeof globals;
|
|
103
|
+
setupGlobals: typeof setupGlobals;
|
|
104
|
+
requestAdapter: typeof requestAdapter;
|
|
105
|
+
requestDevice: typeof requestDevice;
|
|
106
|
+
providerInfo: typeof providerInfo;
|
|
107
|
+
createDoeRuntime: typeof createDoeRuntime;
|
|
108
|
+
runDawnVsDoeCompare: typeof runDawnVsDoeCompare;
|
|
109
|
+
doe: FullDoeNamespace;
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
export default _default;
|
package/src/full.js
ADDED
package/src/index.js
CHANGED
|
@@ -19,13 +19,13 @@ let libraryLoaded = false;
|
|
|
19
19
|
function loadAddon() {
|
|
20
20
|
const prebuildPath = resolve(__dirname, '..', 'prebuilds', `${process.platform}-${process.arch}`, 'doe_napi.node');
|
|
21
21
|
try {
|
|
22
|
-
return require(
|
|
22
|
+
return require('../build/Release/doe_napi.node');
|
|
23
23
|
} catch {
|
|
24
24
|
try {
|
|
25
|
-
return require('../build/
|
|
25
|
+
return require('../build/Debug/doe_napi.node');
|
|
26
26
|
} catch {
|
|
27
27
|
try {
|
|
28
|
-
return require(
|
|
28
|
+
return require(prebuildPath);
|
|
29
29
|
} catch {
|
|
30
30
|
return null;
|
|
31
31
|
}
|
|
@@ -71,7 +71,7 @@ function ensureLibrary() {
|
|
|
71
71
|
}
|
|
72
72
|
if (!DOE_LIB_PATH) {
|
|
73
73
|
throw new Error(
|
|
74
|
-
'@simulatte/webgpu: libwebgpu_doe not found. Build it with `cd
|
|
74
|
+
'@simulatte/webgpu: libwebgpu_doe not found. Build it with `cd zig && zig build dropin` or set DOE_WEBGPU_LIB.'
|
|
75
75
|
);
|
|
76
76
|
}
|
|
77
77
|
addon.loadLibrary(DOE_LIB_PATH);
|
|
@@ -120,14 +120,26 @@ class DoeGPUBuffer {
|
|
|
120
120
|
}
|
|
121
121
|
|
|
122
122
|
async mapAsync(mode, offset = 0, size = this.size) {
|
|
123
|
-
if (this._queue)
|
|
124
|
-
|
|
123
|
+
if (this._queue) {
|
|
124
|
+
if (this._queue.hasPendingSubmissions()) {
|
|
125
|
+
addon.flushAndMapSync(this._instance, this._queue._native, this._native, mode, offset, size);
|
|
126
|
+
this._queue.markSubmittedWorkDone();
|
|
127
|
+
} else {
|
|
128
|
+
addon.bufferMapSync(this._instance, this._native, mode, offset, size);
|
|
129
|
+
}
|
|
130
|
+
} else {
|
|
131
|
+
addon.bufferMapSync(this._instance, this._native, mode, offset, size);
|
|
132
|
+
}
|
|
125
133
|
}
|
|
126
134
|
|
|
127
135
|
getMappedRange(offset = 0, size = this.size) {
|
|
128
136
|
return addon.bufferGetMappedRange(this._native, offset, size);
|
|
129
137
|
}
|
|
130
138
|
|
|
139
|
+
assertMappedPrefixF32(expected, count) {
|
|
140
|
+
return addon.bufferAssertMappedPrefixF32(this._native, expected, count);
|
|
141
|
+
}
|
|
142
|
+
|
|
131
143
|
unmap() {
|
|
132
144
|
addon.bufferUnmap(this._native);
|
|
133
145
|
}
|
|
@@ -233,13 +245,57 @@ class DoeGPUQueue {
|
|
|
233
245
|
this._native = native;
|
|
234
246
|
this._instance = instance;
|
|
235
247
|
this._device = device;
|
|
248
|
+
this._submittedSerial = 0;
|
|
249
|
+
this._completedSerial = 0;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
hasPendingSubmissions() {
|
|
253
|
+
return this._completedSerial < this._submittedSerial;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
markSubmittedWorkDone() {
|
|
257
|
+
this._completedSerial = this._submittedSerial;
|
|
236
258
|
}
|
|
237
259
|
|
|
238
260
|
submit(commandBuffers) {
|
|
261
|
+
if (commandBuffers.length === 0) return;
|
|
262
|
+
this._submittedSerial += 1;
|
|
263
|
+
if (commandBuffers.length === 1 && commandBuffers[0]?._batched) {
|
|
264
|
+
const cmds = commandBuffers[0]._commands;
|
|
265
|
+
if (
|
|
266
|
+
cmds.length === 2
|
|
267
|
+
&& cmds[0]?.t === 0
|
|
268
|
+
&& cmds[1]?.t === 1
|
|
269
|
+
&& typeof addon.submitComputeDispatchCopy === 'function'
|
|
270
|
+
) {
|
|
271
|
+
addon.submitComputeDispatchCopy(
|
|
272
|
+
this._device,
|
|
273
|
+
this._native,
|
|
274
|
+
cmds[0].p,
|
|
275
|
+
cmds[0].bg,
|
|
276
|
+
cmds[0].x,
|
|
277
|
+
cmds[0].y,
|
|
278
|
+
cmds[0].z,
|
|
279
|
+
cmds[1].s,
|
|
280
|
+
cmds[1].so,
|
|
281
|
+
cmds[1].d,
|
|
282
|
+
cmds[1].do,
|
|
283
|
+
cmds[1].sz,
|
|
284
|
+
);
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
239
288
|
if (commandBuffers.length > 0 && commandBuffers.every((c) => c._batched)) {
|
|
240
289
|
const allCommands = [];
|
|
241
290
|
for (const cb of commandBuffers) allCommands.push(...cb._commands);
|
|
242
291
|
addon.submitBatched(this._device, this._native, allCommands);
|
|
292
|
+
if (
|
|
293
|
+
allCommands.length === 2
|
|
294
|
+
&& allCommands[0]?.t === 0
|
|
295
|
+
&& allCommands[1]?.t === 1
|
|
296
|
+
) {
|
|
297
|
+
this.markSubmittedWorkDone();
|
|
298
|
+
}
|
|
243
299
|
} else {
|
|
244
300
|
const natives = commandBuffers.map((c) => c._native);
|
|
245
301
|
addon.queueSubmit(this._native, natives);
|
|
@@ -259,8 +315,9 @@ class DoeGPUQueue {
|
|
|
259
315
|
}
|
|
260
316
|
|
|
261
317
|
async onSubmittedWorkDone() {
|
|
262
|
-
|
|
263
|
-
|
|
318
|
+
if (!this.hasPendingSubmissions()) return;
|
|
319
|
+
addon.queueFlush(this._native);
|
|
320
|
+
this.markSubmittedWorkDone();
|
|
264
321
|
}
|
|
265
322
|
}
|
|
266
323
|
|
|
@@ -307,15 +364,28 @@ class DoeGPURenderPipeline {
|
|
|
307
364
|
}
|
|
308
365
|
|
|
309
366
|
class DoeGPUShaderModule {
|
|
310
|
-
constructor(native) {
|
|
367
|
+
constructor(native, code) {
|
|
368
|
+
this._native = native;
|
|
369
|
+
this._code = code;
|
|
370
|
+
}
|
|
311
371
|
}
|
|
312
372
|
|
|
313
373
|
class DoeGPUComputePipeline {
|
|
314
|
-
constructor(native
|
|
374
|
+
constructor(native, device, explicitLayout, autoLayoutEntriesByGroup) {
|
|
375
|
+
this._native = native;
|
|
376
|
+
this._device = device;
|
|
377
|
+
this._explicitLayout = explicitLayout;
|
|
378
|
+
this._autoLayoutEntriesByGroup = autoLayoutEntriesByGroup;
|
|
379
|
+
this._cachedLayouts = new Map();
|
|
380
|
+
}
|
|
315
381
|
|
|
316
382
|
getBindGroupLayout(index) {
|
|
317
|
-
|
|
318
|
-
return
|
|
383
|
+
if (this._explicitLayout) return this._explicitLayout;
|
|
384
|
+
if (this._cachedLayouts.has(index)) return this._cachedLayouts.get(index);
|
|
385
|
+
const entries = this._autoLayoutEntriesByGroup?.get(index) ?? [];
|
|
386
|
+
const layout = this._device.createBindGroupLayout({ entries });
|
|
387
|
+
this._cachedLayouts.set(index, layout);
|
|
388
|
+
return layout;
|
|
319
389
|
}
|
|
320
390
|
}
|
|
321
391
|
|
|
@@ -368,6 +438,34 @@ const DOE_LIMITS = Object.freeze({
|
|
|
368
438
|
|
|
369
439
|
const DOE_FEATURES = Object.freeze(new Set(['shader-f16']));
|
|
370
440
|
|
|
441
|
+
function inferAutoBindGroupLayouts(code, visibility = globals.GPUShaderStage.COMPUTE) {
|
|
442
|
+
const groups = new Map();
|
|
443
|
+
const bindingPattern = /@group\((\d+)\)\s*@binding\((\d+)\)\s*var(?:<([^>]+)>)?\s+\w+\s*:\s*([^;]+);/g;
|
|
444
|
+
for (const match of code.matchAll(bindingPattern)) {
|
|
445
|
+
const group = Number(match[1]);
|
|
446
|
+
const binding = Number(match[2]);
|
|
447
|
+
const addressSpace = (match[3] ?? "").trim();
|
|
448
|
+
const typeExpr = (match[4] ?? "").trim();
|
|
449
|
+
let entry = null;
|
|
450
|
+
if (addressSpace.startsWith("uniform")) {
|
|
451
|
+
entry = { binding, visibility, buffer: { type: "uniform" } };
|
|
452
|
+
} else if (addressSpace.startsWith("storage")) {
|
|
453
|
+
const readOnly = !addressSpace.includes("read_write");
|
|
454
|
+
entry = { binding, visibility, buffer: { type: readOnly ? "read-only-storage" : "storage" } };
|
|
455
|
+
} else if (typeExpr.startsWith("sampler")) {
|
|
456
|
+
entry = { binding, visibility, sampler: {} };
|
|
457
|
+
}
|
|
458
|
+
if (!entry) continue;
|
|
459
|
+
const entries = groups.get(group) ?? [];
|
|
460
|
+
entries.push(entry);
|
|
461
|
+
groups.set(group, entries);
|
|
462
|
+
}
|
|
463
|
+
for (const entries of groups.values()) {
|
|
464
|
+
entries.sort((left, right) => left.binding - right.binding);
|
|
465
|
+
}
|
|
466
|
+
return groups;
|
|
467
|
+
}
|
|
468
|
+
|
|
371
469
|
class DoeGPUDevice {
|
|
372
470
|
constructor(native, instance) {
|
|
373
471
|
this._native = native;
|
|
@@ -380,24 +478,25 @@ class DoeGPUDevice {
|
|
|
380
478
|
|
|
381
479
|
createBuffer(descriptor) {
|
|
382
480
|
const buf = addon.createBuffer(this._native, descriptor);
|
|
383
|
-
return new DoeGPUBuffer(buf, this._instance, descriptor.size, descriptor.usage, this.queue
|
|
481
|
+
return new DoeGPUBuffer(buf, this._instance, descriptor.size, descriptor.usage, this.queue);
|
|
384
482
|
}
|
|
385
483
|
|
|
386
484
|
createShaderModule(descriptor) {
|
|
387
485
|
const code = descriptor.code || descriptor.source;
|
|
388
486
|
if (!code) throw new Error('createShaderModule: descriptor.code is required');
|
|
389
487
|
const mod = addon.createShaderModule(this._native, code);
|
|
390
|
-
return new DoeGPUShaderModule(mod);
|
|
488
|
+
return new DoeGPUShaderModule(mod, code);
|
|
391
489
|
}
|
|
392
490
|
|
|
393
491
|
createComputePipeline(descriptor) {
|
|
394
492
|
const shader = descriptor.compute?.module;
|
|
395
493
|
const entryPoint = descriptor.compute?.entryPoint || 'main';
|
|
396
494
|
const layout = descriptor.layout === 'auto' ? null : descriptor.layout;
|
|
495
|
+
const autoLayoutEntriesByGroup = layout ? null : inferAutoBindGroupLayouts(shader?._code || '');
|
|
397
496
|
const native = addon.createComputePipeline(
|
|
398
497
|
this._native, shader._native, entryPoint,
|
|
399
498
|
layout?._native ?? null);
|
|
400
|
-
return new DoeGPUComputePipeline(native);
|
|
499
|
+
return new DoeGPUComputePipeline(native, this, layout, autoLayoutEntriesByGroup);
|
|
401
500
|
}
|
|
402
501
|
|
|
403
502
|
async createComputePipelineAsync(descriptor) {
|
package/src/node-runtime.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export * from "./
|
|
2
|
-
export { default } from "./
|
|
1
|
+
export * from "./full.js";
|
|
2
|
+
export { default } from "./full.js";
|
package/src/node.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export * from "./
|
|
2
|
-
export { default } from "./
|
|
1
|
+
export * from "./full.js";
|
|
2
|
+
export { default } from "./full.js";
|
package/src/runtime_cli.js
CHANGED
|
@@ -158,7 +158,9 @@ export function createDoeRuntime(options = {}) {
|
|
|
158
158
|
require_existing_path("commandsPath", runOptions.commandsPath);
|
|
159
159
|
if (runOptions.quirksPath) require_existing_path("quirksPath", runOptions.quirksPath);
|
|
160
160
|
const args = build_bench_args(runOptions);
|
|
161
|
-
const result = runRaw(args
|
|
161
|
+
const result = runRaw(args, {
|
|
162
|
+
cwd: runOptions.cwd || WORKSPACE_ROOT,
|
|
163
|
+
});
|
|
162
164
|
const traceMeta = read_trace_meta(runOptions.traceMetaPath);
|
|
163
165
|
return {
|
|
164
166
|
...result,
|