@forgeax/engine-rhi-webgpu 0.0.0-dev.8d955ade1c79
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/LICENSE +202 -0
- package/README.md +133 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/__tests__/__mocks__/gpu-device.d.ts +226 -0
- package/dist/__tests__/__mocks__/gpu-device.d.ts.map +1 -0
- package/dist/__tests__/dawn-real-gpu.dawn.test.d.ts +2 -0
- package/dist/__tests__/dawn-real-gpu.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/rhi-webgpu.unit.test.d.ts +2 -0
- package/dist/__tests__/rhi-webgpu.unit.test.d.ts.map +1 -0
- package/dist/device.d.ts +62 -0
- package/dist/device.d.ts.map +1 -0
- package/dist/errors.d.ts +49 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/index.d.ts +183 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.mjs +1745 -0
- package/dist/index.mjs.map +1 -0
- package/dist/internal/__tests__/timestamp-query.unit.test.d.ts +2 -0
- package/dist/internal/__tests__/timestamp-query.unit.test.d.ts.map +1 -0
- package/dist/internal/error-translation.d.ts +16 -0
- package/dist/internal/error-translation.d.ts.map +1 -0
- package/dist/internal/timestamp-query.d.ts +15 -0
- package/dist/internal/timestamp-query.d.ts.map +1 -0
- package/package.json +58 -0
- package/src/__tests__/__mocks__/gpu-device.ts +555 -0
- package/src/__tests__/dawn-real-gpu.dawn.test.ts +1445 -0
- package/src/__tests__/rhi-webgpu.unit.test.ts +2398 -0
- package/src/device.ts +2102 -0
- package/src/errors.ts +183 -0
- package/src/index.ts +597 -0
- package/src/internal/__tests__/timestamp-query.unit.test.ts +88 -0
- package/src/internal/error-translation.ts +187 -0
- package/src/internal/timestamp-query.ts +59 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,1745 @@
|
|
|
1
|
+
import { err, RhiError, ok } from '@forgeax/engine-rhi';
|
|
2
|
+
export { RhiError as RhiErrorClass, err, ok } from '@forgeax/engine-rhi';
|
|
3
|
+
|
|
4
|
+
// src/index.ts
|
|
5
|
+
function adapterUnavailable() {
|
|
6
|
+
return err(
|
|
7
|
+
new RhiError({
|
|
8
|
+
code: "adapter-unavailable",
|
|
9
|
+
expected: "an available browser-native WebGPU adapter",
|
|
10
|
+
hint: "this only reports the browser-native WebGPU channel; ForgeaX may continue through its wgpu/WebGL2 fallback, so do not conclude that the browser or machine is unsupported unless both backend causes fail"
|
|
11
|
+
})
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
function requestAdapterFailed(cause) {
|
|
15
|
+
const record = cause !== null && typeof cause === "object" ? cause : void 0;
|
|
16
|
+
const name = typeof record?.name === "string" && record.name.length > 0 ? record.name : void 0;
|
|
17
|
+
let message = typeof record?.message === "string" && record.message.length > 0 ? record.message : typeof cause === "string" ? cause : "";
|
|
18
|
+
if (message.length === 0 && cause !== void 0) {
|
|
19
|
+
try {
|
|
20
|
+
const serialized = JSON.stringify(cause);
|
|
21
|
+
message = serialized && serialized !== "{}" ? serialized : "unknown thrown object";
|
|
22
|
+
} catch {
|
|
23
|
+
message = "unserializable thrown object";
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
if (message.length === 0) message = "unknown requestAdapter failure";
|
|
27
|
+
return err(
|
|
28
|
+
new RhiError({
|
|
29
|
+
code: "webgpu-runtime-error",
|
|
30
|
+
expected: "navigator.gpu.requestAdapter() resolves with an adapter or null",
|
|
31
|
+
hint: "inspect detail.error before assigning the failure to WebGPU capability; the ForgeaX runtime can still attempt its wgpu/WebGL2 fallback",
|
|
32
|
+
detail: {
|
|
33
|
+
error: {
|
|
34
|
+
code: "request-adapter-threw",
|
|
35
|
+
...name === void 0 ? {} : { name },
|
|
36
|
+
message
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
})
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
function featureNotEnabled(featureName) {
|
|
43
|
+
const fname = "compute";
|
|
44
|
+
return err(
|
|
45
|
+
new RhiError({
|
|
46
|
+
code: "feature-not-enabled",
|
|
47
|
+
expected: `feature ${fname} to be enabled`,
|
|
48
|
+
hint: `verify device.features.${fname} before calling this entry point`
|
|
49
|
+
})
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
function limitExceeded(limitName) {
|
|
53
|
+
const lname = "maxBindGroups";
|
|
54
|
+
return err(
|
|
55
|
+
new RhiError({
|
|
56
|
+
code: "limit-exceeded",
|
|
57
|
+
expected: `${lname} to be within bounds`,
|
|
58
|
+
hint: `verify device.limits.${lname}`
|
|
59
|
+
})
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
function shaderCompileFailed(compilerMessages) {
|
|
63
|
+
const detail = { compilerMessages };
|
|
64
|
+
return err(
|
|
65
|
+
new RhiError({
|
|
66
|
+
code: "shader-compile-failed",
|
|
67
|
+
expected: "valid WGSL source",
|
|
68
|
+
hint: "inspect RhiError.detail.compilerMessages (each entry: { message, type, lineNum, linePos, offset, length } per WebGPU GPUCompilationMessage shape)",
|
|
69
|
+
detail
|
|
70
|
+
})
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
function commandEncoderFinished() {
|
|
74
|
+
return err(
|
|
75
|
+
new RhiError({
|
|
76
|
+
code: "command-encoder-finished",
|
|
77
|
+
expected: "command encoder must not be finished before recording new commands",
|
|
78
|
+
hint: "create a new command encoder via device.createCommandEncoder() for each frame; do not reuse a finished encoder"
|
|
79
|
+
})
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
function renderPassNotEnded() {
|
|
83
|
+
return err(
|
|
84
|
+
new RhiError({
|
|
85
|
+
code: "render-pass-not-ended",
|
|
86
|
+
expected: "previous render pass must be ended before beginning a new pass or finishing the encoder",
|
|
87
|
+
hint: "call pass.end() before beginRenderPass() or encoder.finish()"
|
|
88
|
+
})
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
function queueSubmitFailed(detailMessage) {
|
|
92
|
+
const baseHint = "check if any referenced buffer / pipeline / texture has been destroyed before submit";
|
|
93
|
+
const hint = detailMessage !== void 0 && detailMessage.length > 0 ? `${baseHint}; underlying GPU error: ${detailMessage}` : baseHint;
|
|
94
|
+
return err(
|
|
95
|
+
new RhiError({
|
|
96
|
+
code: "queue-submit-failed",
|
|
97
|
+
expected: "command buffer references must be valid at submit time (not destroyed; not from a different device)",
|
|
98
|
+
hint
|
|
99
|
+
})
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
function queueWriteBufferOutOfBounds(args) {
|
|
103
|
+
return err(
|
|
104
|
+
new RhiError({
|
|
105
|
+
code: "queue-write-buffer-out-of-bounds",
|
|
106
|
+
expected: "writeBuffer offset + data.byteLength must be <= buffer.size; offset must be 4-byte aligned",
|
|
107
|
+
hint: `verify offset alignment and bounds: offset (got ${args.offset}) + data.byteLength (got ${args.byteLength}) must be <= buffer.size (got ${args.bufferSize})`
|
|
108
|
+
})
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
function resolveTimestampQueries(args) {
|
|
112
|
+
try {
|
|
113
|
+
args.rawEncoder.resolveQuerySet(
|
|
114
|
+
args.rawQuerySet,
|
|
115
|
+
args.firstQuery,
|
|
116
|
+
args.queryCount,
|
|
117
|
+
args.rawDestination,
|
|
118
|
+
args.destinationOffset
|
|
119
|
+
);
|
|
120
|
+
return ok(void 0);
|
|
121
|
+
} catch (error) {
|
|
122
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
123
|
+
return err(
|
|
124
|
+
new RhiError({
|
|
125
|
+
code: "webgpu-runtime-error",
|
|
126
|
+
expected: "underlying GPUCommandEncoder.resolveQuerySet to succeed",
|
|
127
|
+
hint: `resolveQuerySet raised: ${message}`
|
|
128
|
+
})
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
function writeTimestamp(args) {
|
|
133
|
+
const encoderWithTimestamp = args.rawEncoder;
|
|
134
|
+
if (typeof encoderWithTimestamp.writeTimestamp !== "function") {
|
|
135
|
+
throw new RhiError({
|
|
136
|
+
code: "webgpu-runtime-error",
|
|
137
|
+
expected: "underlying GPUCommandEncoder.writeTimestamp to be callable",
|
|
138
|
+
hint: "timestamp-query is advertised but the raw encoder has no writeTimestamp method"
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
try {
|
|
142
|
+
encoderWithTimestamp.writeTimestamp(args.rawQuerySet, args.queryIndex);
|
|
143
|
+
} catch (error) {
|
|
144
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
145
|
+
throw new RhiError({
|
|
146
|
+
code: "webgpu-runtime-error",
|
|
147
|
+
expected: "underlying GPUCommandEncoder.writeTimestamp to succeed",
|
|
148
|
+
hint: `writeTimestamp raised: ${message}`
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// src/device.ts
|
|
154
|
+
function mirror(src, keys) {
|
|
155
|
+
const out = {};
|
|
156
|
+
for (const k of keys) {
|
|
157
|
+
if (k in src) {
|
|
158
|
+
out[k] = src[k];
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return out;
|
|
162
|
+
}
|
|
163
|
+
var BUFFER_KEYS = ["label", "size", "usage", "mappedAtCreation"];
|
|
164
|
+
var TEXTURE_KEYS = [
|
|
165
|
+
"label",
|
|
166
|
+
"size",
|
|
167
|
+
"mipLevelCount",
|
|
168
|
+
"sampleCount",
|
|
169
|
+
"dimension",
|
|
170
|
+
"format",
|
|
171
|
+
"usage",
|
|
172
|
+
"viewFormats",
|
|
173
|
+
"textureBindingViewDimension"
|
|
174
|
+
];
|
|
175
|
+
var SAMPLER_KEYS = [
|
|
176
|
+
"label",
|
|
177
|
+
"addressModeU",
|
|
178
|
+
"addressModeV",
|
|
179
|
+
"addressModeW",
|
|
180
|
+
"magFilter",
|
|
181
|
+
"minFilter",
|
|
182
|
+
"mipmapFilter",
|
|
183
|
+
"lodMinClamp",
|
|
184
|
+
"lodMaxClamp",
|
|
185
|
+
"compare",
|
|
186
|
+
"maxAnisotropy"
|
|
187
|
+
];
|
|
188
|
+
var BGL_KEYS = ["label", "entries"];
|
|
189
|
+
var PL_KEYS = ["label", "bindGroupLayouts"];
|
|
190
|
+
var ENC_KEYS = ["label"];
|
|
191
|
+
var TEXTURE_VIEW_KEYS = [
|
|
192
|
+
"label",
|
|
193
|
+
"format",
|
|
194
|
+
"dimension",
|
|
195
|
+
"usage",
|
|
196
|
+
"aspect",
|
|
197
|
+
"baseMipLevel",
|
|
198
|
+
"mipLevelCount",
|
|
199
|
+
"baseArrayLayer",
|
|
200
|
+
"arrayLayerCount"
|
|
201
|
+
];
|
|
202
|
+
var CP_KEYS = ["label", "layout", "compute"];
|
|
203
|
+
var QS_KEYS = ["label", "type", "count"];
|
|
204
|
+
var QUERY_SET_COUNT_LIMIT = 4096;
|
|
205
|
+
var RAW_DEVICE_MAP = /* @__PURE__ */ new WeakMap();
|
|
206
|
+
var BUFFER_RAW_MAP = /* @__PURE__ */ new WeakMap();
|
|
207
|
+
var TEXTURE_VIEW_RAW_MAP = /* @__PURE__ */ new WeakMap();
|
|
208
|
+
var ENCODER_STATE = /* @__PURE__ */ new WeakMap();
|
|
209
|
+
var PASS_STATE = /* @__PURE__ */ new WeakMap();
|
|
210
|
+
var COMMAND_BUFFER_RAW_MAP = /* @__PURE__ */ new WeakMap();
|
|
211
|
+
var TEXTURE_META_MAP = /* @__PURE__ */ new WeakMap();
|
|
212
|
+
var QUERY_SET_RAW_MAP = /* @__PURE__ */ new WeakMap();
|
|
213
|
+
var QUERY_SET_DESTROYED_MAP = /* @__PURE__ */ new WeakMap();
|
|
214
|
+
var BUFFER_META_MAP = /* @__PURE__ */ new WeakMap();
|
|
215
|
+
var BUFFER_USAGE_QUERY_RESOLVE = 512;
|
|
216
|
+
var QUERY_RESOLVE_ALIGNMENT = 256;
|
|
217
|
+
function _internal_getRawDevice(device) {
|
|
218
|
+
return RAW_DEVICE_MAP.get(device);
|
|
219
|
+
}
|
|
220
|
+
function probeRgba16floatRenderable(device) {
|
|
221
|
+
let tex;
|
|
222
|
+
try {
|
|
223
|
+
tex = device.createTexture({
|
|
224
|
+
label: "forgeax-caps-probe-rgba16float-renderable",
|
|
225
|
+
format: "rgba16float",
|
|
226
|
+
usage: 16,
|
|
227
|
+
// GPUTextureUsage.RENDER_ATTACHMENT
|
|
228
|
+
size: [1, 1, 1]
|
|
229
|
+
});
|
|
230
|
+
return true;
|
|
231
|
+
} catch {
|
|
232
|
+
return false;
|
|
233
|
+
} finally {
|
|
234
|
+
tex?.destroy?.();
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
function probeRg11b10ufloatRenderable(device, features) {
|
|
238
|
+
if (!features.has("rg11b10ufloat-renderable")) return false;
|
|
239
|
+
let tex;
|
|
240
|
+
try {
|
|
241
|
+
tex = device.createTexture({
|
|
242
|
+
label: "forgeax-caps-probe-rg11b10ufloat-renderable",
|
|
243
|
+
format: "rg11b10ufloat",
|
|
244
|
+
usage: 16,
|
|
245
|
+
// GPUTextureUsage.RENDER_ATTACHMENT
|
|
246
|
+
size: [1, 1, 1]
|
|
247
|
+
});
|
|
248
|
+
return true;
|
|
249
|
+
} catch {
|
|
250
|
+
return false;
|
|
251
|
+
} finally {
|
|
252
|
+
tex?.destroy?.();
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
function probeFloat32Filterable(device, features) {
|
|
256
|
+
if (!features.has("float32-filterable")) return false;
|
|
257
|
+
try {
|
|
258
|
+
device.createBindGroupLayout({
|
|
259
|
+
entries: [
|
|
260
|
+
{ binding: 0, visibility: 2, sampler: { type: "filtering" } },
|
|
261
|
+
// GPUShaderStage.FRAGMENT = 2
|
|
262
|
+
{ binding: 1, visibility: 2, texture: { sampleType: "float" } }
|
|
263
|
+
]
|
|
264
|
+
});
|
|
265
|
+
device.createSampler({ minFilter: "linear", magFilter: "linear" });
|
|
266
|
+
return true;
|
|
267
|
+
} catch {
|
|
268
|
+
return false;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
function deriveCaps(rawDevice, features, limits) {
|
|
272
|
+
const has = (name) => features.has(name);
|
|
273
|
+
const textureCompressionBc = has("texture-compression-bc");
|
|
274
|
+
const textureCompressionEtc2 = has("texture-compression-etc2");
|
|
275
|
+
const textureCompressionAstc = has("texture-compression-astc");
|
|
276
|
+
const hdrCaps = {
|
|
277
|
+
rgba16floatRenderable: probeRgba16floatRenderable(rawDevice),
|
|
278
|
+
rg11b10ufloatRenderable: probeRg11b10ufloatRenderable(rawDevice, features),
|
|
279
|
+
float32Filterable: probeFloat32Filterable(rawDevice, features)
|
|
280
|
+
};
|
|
281
|
+
return {
|
|
282
|
+
backendKind: "webgpu",
|
|
283
|
+
compute: true,
|
|
284
|
+
// WebGPU spec mandates compute-pipeline support.
|
|
285
|
+
timestampQuery: has("timestamp-query"),
|
|
286
|
+
timestampPeriodNanoseconds: has("timestamp-query") ? 1 : null,
|
|
287
|
+
indirectDrawing: true,
|
|
288
|
+
// WebGPU spec mandates drawIndirect / drawIndexedIndirect.
|
|
289
|
+
textureCompressionBc,
|
|
290
|
+
textureCompressionEtc2,
|
|
291
|
+
textureCompressionAstc,
|
|
292
|
+
multiDrawIndirect: false,
|
|
293
|
+
// wgpu native extension; unavailable on WebGPU browser path.
|
|
294
|
+
pushConstants: false,
|
|
295
|
+
// wgpu native extension; unavailable on WebGPU browser path.
|
|
296
|
+
textureBindingArray: false,
|
|
297
|
+
// wgpu native extension; unavailable on WebGPU browser path.
|
|
298
|
+
// 4 new fields (D-P3 / R-03 §3.1):
|
|
299
|
+
samplerAliasing: true,
|
|
300
|
+
// spec mandatory on browser backends.
|
|
301
|
+
firstInstanceIndirect: has("indirect-first-instance"),
|
|
302
|
+
storageBuffer: (limits.maxStorageBuffersPerShaderStage ?? 0) > 0,
|
|
303
|
+
storageTexture: (limits.maxStorageTexturesPerShaderStage ?? 0) > 0,
|
|
304
|
+
// HDR / filterable caps (feat-20260608 M1):
|
|
305
|
+
...hdrCaps,
|
|
306
|
+
maxColorAttachments: limits.maxColorAttachments ?? 4
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
function makeRenderPassEncoder(rawPass, encoder, occlusionQuerySet) {
|
|
310
|
+
const pass = {
|
|
311
|
+
setPipeline(pipeline) {
|
|
312
|
+
rawPass.setPipeline(pipeline);
|
|
313
|
+
},
|
|
314
|
+
setVertexBuffer(slot, buffer, offset, size) {
|
|
315
|
+
const rawBuf = BUFFER_RAW_MAP.get(buffer) ?? buffer;
|
|
316
|
+
rawPass.setVertexBuffer(slot, rawBuf, offset, size);
|
|
317
|
+
},
|
|
318
|
+
setIndexBuffer(buffer, format, offset, size) {
|
|
319
|
+
const rawBuf = BUFFER_RAW_MAP.get(buffer) ?? buffer;
|
|
320
|
+
rawPass.setIndexBuffer(rawBuf, format, offset, size);
|
|
321
|
+
},
|
|
322
|
+
setBindGroup(index, bindGroup, arg3, arg4, arg5) {
|
|
323
|
+
if (arg3 instanceof Uint32Array) {
|
|
324
|
+
rawPass.setBindGroup(
|
|
325
|
+
index,
|
|
326
|
+
bindGroup,
|
|
327
|
+
arg3,
|
|
328
|
+
arg4 ?? 0,
|
|
329
|
+
arg5 ?? arg3.length
|
|
330
|
+
);
|
|
331
|
+
} else if (arg3 === void 0) {
|
|
332
|
+
rawPass.setBindGroup(index, bindGroup);
|
|
333
|
+
} else {
|
|
334
|
+
rawPass.setBindGroup(index, bindGroup, arg3);
|
|
335
|
+
}
|
|
336
|
+
},
|
|
337
|
+
draw(vertexCount, instanceCount, firstVertex, firstInstance) {
|
|
338
|
+
rawPass.draw(vertexCount, instanceCount, firstVertex, firstInstance);
|
|
339
|
+
},
|
|
340
|
+
drawIndexed(indexCount, instanceCount, firstIndex, baseVertex, firstInstance) {
|
|
341
|
+
rawPass.drawIndexed(indexCount, instanceCount, firstIndex, baseVertex, firstInstance);
|
|
342
|
+
},
|
|
343
|
+
setViewport(x, y, w, h, minDepth, maxDepth) {
|
|
344
|
+
rawPass.setViewport(x, y, w, h, minDepth, maxDepth);
|
|
345
|
+
},
|
|
346
|
+
setScissorRect(x, y, w, h) {
|
|
347
|
+
rawPass.setScissorRect(x, y, w, h);
|
|
348
|
+
},
|
|
349
|
+
setBlendConstant(color) {
|
|
350
|
+
rawPass.setBlendConstant(color);
|
|
351
|
+
},
|
|
352
|
+
setStencilReference(reference) {
|
|
353
|
+
rawPass.setStencilReference(reference);
|
|
354
|
+
},
|
|
355
|
+
drawIndirect(indirectBuffer, indirectOffset) {
|
|
356
|
+
const rawBuf = BUFFER_RAW_MAP.get(indirectBuffer) ?? indirectBuffer;
|
|
357
|
+
rawPass.drawIndirect(rawBuf, indirectOffset);
|
|
358
|
+
},
|
|
359
|
+
drawIndexedIndirect(indirectBuffer, indirectOffset) {
|
|
360
|
+
const rawBuf = BUFFER_RAW_MAP.get(indirectBuffer) ?? indirectBuffer;
|
|
361
|
+
rawPass.drawIndexedIndirect(rawBuf, indirectOffset);
|
|
362
|
+
},
|
|
363
|
+
pushDebugGroup(groupLabel) {
|
|
364
|
+
rawPass.pushDebugGroup(groupLabel);
|
|
365
|
+
},
|
|
366
|
+
popDebugGroup() {
|
|
367
|
+
rawPass.popDebugGroup();
|
|
368
|
+
},
|
|
369
|
+
insertDebugMarker(markerLabel) {
|
|
370
|
+
rawPass.insertDebugMarker(markerLabel);
|
|
371
|
+
},
|
|
372
|
+
executeBundles(_bundles) {
|
|
373
|
+
return err(
|
|
374
|
+
new RhiError({
|
|
375
|
+
code: "rhi-not-available",
|
|
376
|
+
expected: "render bundle creation requires future closed loop",
|
|
377
|
+
hint: "see feat-future-rhi-render-bundle"
|
|
378
|
+
})
|
|
379
|
+
);
|
|
380
|
+
},
|
|
381
|
+
beginOcclusionQuery(queryIndex) {
|
|
382
|
+
const state = PASS_STATE.get(pass);
|
|
383
|
+
if (state === void 0) {
|
|
384
|
+
return err(
|
|
385
|
+
new RhiError({
|
|
386
|
+
code: "webgpu-runtime-error",
|
|
387
|
+
expected: "render pass state must exist",
|
|
388
|
+
hint: "beginOcclusionQuery called on an untracked render pass"
|
|
389
|
+
})
|
|
390
|
+
);
|
|
391
|
+
}
|
|
392
|
+
if (state.occlusionQuerySet === null) {
|
|
393
|
+
return err(
|
|
394
|
+
new RhiError({
|
|
395
|
+
code: "webgpu-runtime-error",
|
|
396
|
+
expected: "GPURenderPassDescriptor.occlusionQuerySet must be set",
|
|
397
|
+
hint: "pass occlusionQuerySet in RenderPassDescriptor before beginOcclusionQuery"
|
|
398
|
+
})
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
if (state.occlusionQueryActive) {
|
|
402
|
+
return err(
|
|
403
|
+
new RhiError({
|
|
404
|
+
code: "webgpu-runtime-error",
|
|
405
|
+
expected: "[[occlusion_query_active]] == false; pair beginOcclusionQuery / endOcclusionQuery",
|
|
406
|
+
hint: "call endOcclusionQuery() before beginOcclusionQuery() again; occlusion queries cannot nest (spec \xA7render-passes)"
|
|
407
|
+
})
|
|
408
|
+
);
|
|
409
|
+
}
|
|
410
|
+
const rawQs = QUERY_SET_RAW_MAP.get(state.occlusionQuerySet);
|
|
411
|
+
const qsCount = rawQs !== void 0 && typeof rawQs.count === "number" ? rawQs.count : Number.MAX_SAFE_INTEGER;
|
|
412
|
+
if (queryIndex < 0 || queryIndex >= qsCount) {
|
|
413
|
+
return err(
|
|
414
|
+
new RhiError({
|
|
415
|
+
code: "webgpu-runtime-error",
|
|
416
|
+
expected: "queryIndex < querySet.count",
|
|
417
|
+
hint: `got queryIndex=${queryIndex}; querySet.count=${qsCount}`
|
|
418
|
+
})
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
if (state.occlusionQueryWritten.has(queryIndex)) {
|
|
422
|
+
return err(
|
|
423
|
+
new RhiError({
|
|
424
|
+
code: "webgpu-runtime-error",
|
|
425
|
+
expected: "queryIndex must not have been written in this pass",
|
|
426
|
+
hint: `queryIndex=${queryIndex} was already written; cross-pass reuse on the same querySet is legal but in-pass reuse is not (spec \xA7queries)`
|
|
427
|
+
})
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
try {
|
|
431
|
+
rawPass.beginOcclusionQuery(queryIndex);
|
|
432
|
+
state.occlusionQueryActive = true;
|
|
433
|
+
state.occlusionQueryWritten.add(queryIndex);
|
|
434
|
+
return ok(void 0);
|
|
435
|
+
} catch (e) {
|
|
436
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
437
|
+
return err(
|
|
438
|
+
new RhiError({
|
|
439
|
+
code: "webgpu-runtime-error",
|
|
440
|
+
expected: "underlying GPURenderPassEncoder.beginOcclusionQuery to succeed",
|
|
441
|
+
hint: `beginOcclusionQuery raised: ${message}`
|
|
442
|
+
})
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
},
|
|
446
|
+
endOcclusionQuery() {
|
|
447
|
+
const state = PASS_STATE.get(pass);
|
|
448
|
+
if (state === void 0) {
|
|
449
|
+
return err(
|
|
450
|
+
new RhiError({
|
|
451
|
+
code: "webgpu-runtime-error",
|
|
452
|
+
expected: "render pass state must exist",
|
|
453
|
+
hint: "endOcclusionQuery called on an untracked render pass"
|
|
454
|
+
})
|
|
455
|
+
);
|
|
456
|
+
}
|
|
457
|
+
if (!state.occlusionQueryActive) {
|
|
458
|
+
return renderPassNotEnded();
|
|
459
|
+
}
|
|
460
|
+
try {
|
|
461
|
+
rawPass.endOcclusionQuery();
|
|
462
|
+
state.occlusionQueryActive = false;
|
|
463
|
+
return ok(void 0);
|
|
464
|
+
} catch (e) {
|
|
465
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
466
|
+
return err(
|
|
467
|
+
new RhiError({
|
|
468
|
+
code: "webgpu-runtime-error",
|
|
469
|
+
expected: "underlying GPURenderPassEncoder.endOcclusionQuery to succeed",
|
|
470
|
+
hint: `endOcclusionQuery raised: ${message}`
|
|
471
|
+
})
|
|
472
|
+
);
|
|
473
|
+
}
|
|
474
|
+
},
|
|
475
|
+
end() {
|
|
476
|
+
const state = PASS_STATE.get(pass);
|
|
477
|
+
if (state !== void 0) {
|
|
478
|
+
state.ended = true;
|
|
479
|
+
}
|
|
480
|
+
rawPass.end();
|
|
481
|
+
const encState = ENCODER_STATE.get(encoder);
|
|
482
|
+
if (encState !== void 0 && encState.activePass === pass) {
|
|
483
|
+
encState.activePass = null;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
};
|
|
487
|
+
PASS_STATE.set(pass, {
|
|
488
|
+
raw: rawPass,
|
|
489
|
+
ended: false,
|
|
490
|
+
encoder,
|
|
491
|
+
occlusionQuerySet,
|
|
492
|
+
occlusionQueryActive: false,
|
|
493
|
+
occlusionQueryWritten: /* @__PURE__ */ new Set()
|
|
494
|
+
});
|
|
495
|
+
return pass;
|
|
496
|
+
}
|
|
497
|
+
var ENCODER_FINISHED_ERROR_ARGS = {
|
|
498
|
+
code: "command-encoder-finished",
|
|
499
|
+
expected: "command encoder must not be finished before recording new commands",
|
|
500
|
+
hint: "create a new command encoder via device.createCommandEncoder() for each frame; do not reuse a finished encoder"
|
|
501
|
+
};
|
|
502
|
+
function throwIfFinished(state) {
|
|
503
|
+
if (state?.finished) {
|
|
504
|
+
throw new RhiError(ENCODER_FINISHED_ERROR_ARGS);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
function rawTextureView(view) {
|
|
508
|
+
return TEXTURE_VIEW_RAW_MAP.get(view) ?? view;
|
|
509
|
+
}
|
|
510
|
+
function mirrorRenderPassDescriptor(desc) {
|
|
511
|
+
const colorAttachments = [];
|
|
512
|
+
for (const attachment of desc.colorAttachments) {
|
|
513
|
+
if (attachment === null || attachment === void 0) {
|
|
514
|
+
colorAttachments.push(null);
|
|
515
|
+
continue;
|
|
516
|
+
}
|
|
517
|
+
if (attachment.loadOp === void 0 || attachment.storeOp === void 0) {
|
|
518
|
+
throw new TypeError("RHI render-pass color attachments require loadOp and storeOp");
|
|
519
|
+
}
|
|
520
|
+
colorAttachments.push({
|
|
521
|
+
view: rawTextureView(attachment.view),
|
|
522
|
+
...attachment.depthSlice === void 0 ? {} : { depthSlice: attachment.depthSlice },
|
|
523
|
+
...attachment.resolveTarget === void 0 ? {} : { resolveTarget: rawTextureView(attachment.resolveTarget) },
|
|
524
|
+
...attachment.clearValue === void 0 ? {} : { clearValue: attachment.clearValue },
|
|
525
|
+
loadOp: attachment.loadOp,
|
|
526
|
+
storeOp: attachment.storeOp
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
return {
|
|
530
|
+
...desc.label === void 0 ? {} : { label: desc.label },
|
|
531
|
+
colorAttachments,
|
|
532
|
+
...desc.depthStencilAttachment === void 0 ? {} : {
|
|
533
|
+
depthStencilAttachment: {
|
|
534
|
+
view: rawTextureView(desc.depthStencilAttachment.view),
|
|
535
|
+
...desc.depthStencilAttachment.depthClearValue === void 0 ? {} : { depthClearValue: desc.depthStencilAttachment.depthClearValue },
|
|
536
|
+
...desc.depthStencilAttachment.depthLoadOp === void 0 ? {} : { depthLoadOp: desc.depthStencilAttachment.depthLoadOp },
|
|
537
|
+
...desc.depthStencilAttachment.depthStoreOp === void 0 ? {} : { depthStoreOp: desc.depthStencilAttachment.depthStoreOp },
|
|
538
|
+
...desc.depthStencilAttachment.depthReadOnly === void 0 ? {} : { depthReadOnly: desc.depthStencilAttachment.depthReadOnly },
|
|
539
|
+
...desc.depthStencilAttachment.stencilClearValue === void 0 ? {} : { stencilClearValue: desc.depthStencilAttachment.stencilClearValue },
|
|
540
|
+
...desc.depthStencilAttachment.stencilLoadOp === void 0 ? {} : { stencilLoadOp: desc.depthStencilAttachment.stencilLoadOp },
|
|
541
|
+
...desc.depthStencilAttachment.stencilStoreOp === void 0 ? {} : { stencilStoreOp: desc.depthStencilAttachment.stencilStoreOp },
|
|
542
|
+
...desc.depthStencilAttachment.stencilReadOnly === void 0 ? {} : { stencilReadOnly: desc.depthStencilAttachment.stencilReadOnly }
|
|
543
|
+
}
|
|
544
|
+
},
|
|
545
|
+
...desc.occlusionQuerySet === void 0 ? {} : {
|
|
546
|
+
occlusionQuerySet: QUERY_SET_RAW_MAP.get(desc.occlusionQuerySet) ?? desc.occlusionQuerySet
|
|
547
|
+
},
|
|
548
|
+
...desc.timestampWrites === void 0 ? {} : {
|
|
549
|
+
timestampWrites: {
|
|
550
|
+
querySet: QUERY_SET_RAW_MAP.get(desc.timestampWrites.querySet) ?? desc.timestampWrites.querySet,
|
|
551
|
+
...desc.timestampWrites.beginningOfPassWriteIndex === void 0 ? {} : { beginningOfPassWriteIndex: desc.timestampWrites.beginningOfPassWriteIndex },
|
|
552
|
+
...desc.timestampWrites.endOfPassWriteIndex === void 0 ? {} : { endOfPassWriteIndex: desc.timestampWrites.endOfPassWriteIndex }
|
|
553
|
+
}
|
|
554
|
+
},
|
|
555
|
+
...desc.maxDrawCount === void 0 ? {} : { maxDrawCount: desc.maxDrawCount }
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
function mirrorRenderPipelineDescriptor(desc) {
|
|
559
|
+
const vertex = {
|
|
560
|
+
module: desc.vertex.module,
|
|
561
|
+
...desc.vertex.buffers === void 0 ? {} : { buffers: Array.from(desc.vertex.buffers) },
|
|
562
|
+
...desc.vertex.entryPoint === void 0 ? {} : { entryPoint: desc.vertex.entryPoint },
|
|
563
|
+
...desc.vertex.constants === void 0 ? {} : { constants: desc.vertex.constants }
|
|
564
|
+
};
|
|
565
|
+
const fragment = desc.fragment === void 0 ? void 0 : {
|
|
566
|
+
module: desc.fragment.module,
|
|
567
|
+
targets: Array.from(desc.fragment.targets),
|
|
568
|
+
...desc.fragment.entryPoint === void 0 ? {} : { entryPoint: desc.fragment.entryPoint },
|
|
569
|
+
...desc.fragment.constants === void 0 ? {} : { constants: desc.fragment.constants }
|
|
570
|
+
};
|
|
571
|
+
return {
|
|
572
|
+
...desc.label === void 0 ? {} : { label: desc.label },
|
|
573
|
+
layout: desc.layout === "auto" ? "auto" : desc.layout,
|
|
574
|
+
vertex,
|
|
575
|
+
...desc.primitive === void 0 ? {} : { primitive: desc.primitive },
|
|
576
|
+
...desc.depthStencil === void 0 ? {} : { depthStencil: desc.depthStencil },
|
|
577
|
+
...desc.multisample === void 0 ? {} : { multisample: desc.multisample },
|
|
578
|
+
...fragment === void 0 ? {} : { fragment }
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
function makeCommandEncoder(rawEncoder, caps, fireFeatureNotEnabled) {
|
|
582
|
+
function mirrorComputePassDescriptor(desc) {
|
|
583
|
+
if (desc === void 0) return void 0;
|
|
584
|
+
const out = mirror(desc, ["label"]);
|
|
585
|
+
if ("timestampWrites" in desc) {
|
|
586
|
+
const writes = desc.timestampWrites;
|
|
587
|
+
out.timestampWrites = writes === void 0 ? void 0 : {
|
|
588
|
+
querySet: QUERY_SET_RAW_MAP.get(writes.querySet) ?? writes.querySet,
|
|
589
|
+
...writes.beginningOfPassWriteIndex === void 0 ? {} : { beginningOfPassWriteIndex: writes.beginningOfPassWriteIndex },
|
|
590
|
+
...writes.endOfPassWriteIndex === void 0 ? {} : { endOfPassWriteIndex: writes.endOfPassWriteIndex }
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
return out;
|
|
594
|
+
}
|
|
595
|
+
const enc = {
|
|
596
|
+
beginRenderPass(desc) {
|
|
597
|
+
const state = ENCODER_STATE.get(enc);
|
|
598
|
+
throwIfFinished(state);
|
|
599
|
+
const rawPass = rawEncoder.beginRenderPass(mirrorRenderPassDescriptor(desc));
|
|
600
|
+
const occlusionQuerySet = desc.occlusionQuerySet ?? null;
|
|
601
|
+
const pass = makeRenderPassEncoder(rawPass, enc, occlusionQuerySet);
|
|
602
|
+
if (state !== void 0) state.activePass = pass;
|
|
603
|
+
return pass;
|
|
604
|
+
},
|
|
605
|
+
beginComputePass(desc) {
|
|
606
|
+
const state = ENCODER_STATE.get(enc);
|
|
607
|
+
throwIfFinished(state);
|
|
608
|
+
const rawDescriptor = mirrorComputePassDescriptor(desc);
|
|
609
|
+
const rawPass = rawDescriptor === void 0 ? rawEncoder.beginComputePass() : rawEncoder.beginComputePass(rawDescriptor);
|
|
610
|
+
const pass = {
|
|
611
|
+
setPipeline(pipeline) {
|
|
612
|
+
rawPass.setPipeline(pipeline);
|
|
613
|
+
},
|
|
614
|
+
setBindGroup(index, bindGroup, dynamicOffsets) {
|
|
615
|
+
if (dynamicOffsets === void 0) {
|
|
616
|
+
rawPass.setBindGroup(index, bindGroup);
|
|
617
|
+
} else {
|
|
618
|
+
rawPass.setBindGroup(index, bindGroup, dynamicOffsets);
|
|
619
|
+
}
|
|
620
|
+
},
|
|
621
|
+
dispatchWorkgroups(x, y, z) {
|
|
622
|
+
rawPass.dispatchWorkgroups(x, y, z);
|
|
623
|
+
},
|
|
624
|
+
dispatchWorkgroupsIndirect(indirectBuffer, indirectOffset) {
|
|
625
|
+
const rawBuffer = BUFFER_RAW_MAP.get(indirectBuffer) ?? indirectBuffer;
|
|
626
|
+
rawPass.dispatchWorkgroupsIndirect(rawBuffer, indirectOffset);
|
|
627
|
+
},
|
|
628
|
+
end() {
|
|
629
|
+
rawPass.end();
|
|
630
|
+
}
|
|
631
|
+
};
|
|
632
|
+
return pass;
|
|
633
|
+
},
|
|
634
|
+
copyBufferToBuffer(source, arg2, arg3, arg4, arg5) {
|
|
635
|
+
const state = ENCODER_STATE.get(enc);
|
|
636
|
+
throwIfFinished(state);
|
|
637
|
+
const rawSource = BUFFER_RAW_MAP.get(source) ?? source;
|
|
638
|
+
if (typeof arg2 === "number") {
|
|
639
|
+
const dst = arg3;
|
|
640
|
+
const rawDst = BUFFER_RAW_MAP.get(dst) ?? dst;
|
|
641
|
+
rawEncoder.copyBufferToBuffer(rawSource, arg2, rawDst, arg4 ?? 0, arg5 ?? 0);
|
|
642
|
+
} else {
|
|
643
|
+
const dst = arg2;
|
|
644
|
+
const rawDst = BUFFER_RAW_MAP.get(dst) ?? dst;
|
|
645
|
+
rawEncoder.copyBufferToBuffer(rawSource, rawDst, arg3);
|
|
646
|
+
}
|
|
647
|
+
},
|
|
648
|
+
copyBufferToTexture(source, destination, copySize) {
|
|
649
|
+
const state = ENCODER_STATE.get(enc);
|
|
650
|
+
throwIfFinished(state);
|
|
651
|
+
const rawSrc = {
|
|
652
|
+
...source,
|
|
653
|
+
buffer: BUFFER_RAW_MAP.get(source.buffer) ?? source.buffer
|
|
654
|
+
};
|
|
655
|
+
rawEncoder.copyBufferToTexture(rawSrc, destination, copySize);
|
|
656
|
+
},
|
|
657
|
+
copyTextureToBuffer(source, destination, copySize) {
|
|
658
|
+
const state = ENCODER_STATE.get(enc);
|
|
659
|
+
throwIfFinished(state);
|
|
660
|
+
const rawDst = {
|
|
661
|
+
...destination,
|
|
662
|
+
buffer: BUFFER_RAW_MAP.get(destination.buffer) ?? destination.buffer
|
|
663
|
+
};
|
|
664
|
+
rawEncoder.copyTextureToBuffer(source, rawDst, copySize);
|
|
665
|
+
},
|
|
666
|
+
copyTextureToTexture(source, destination, copySize) {
|
|
667
|
+
const state = ENCODER_STATE.get(enc);
|
|
668
|
+
throwIfFinished(state);
|
|
669
|
+
rawEncoder.copyTextureToTexture(source, destination, copySize);
|
|
670
|
+
},
|
|
671
|
+
clearBuffer(buffer, offset, size) {
|
|
672
|
+
const state = ENCODER_STATE.get(enc);
|
|
673
|
+
throwIfFinished(state);
|
|
674
|
+
const rawBuf = BUFFER_RAW_MAP.get(buffer) ?? buffer;
|
|
675
|
+
rawEncoder.clearBuffer(rawBuf, offset, size);
|
|
676
|
+
},
|
|
677
|
+
resolveQuerySet(querySet, firstQuery, queryCount, destination, destinationOffset) {
|
|
678
|
+
const state = ENCODER_STATE.get(enc);
|
|
679
|
+
if (state?.finished) {
|
|
680
|
+
return commandEncoderFinished();
|
|
681
|
+
}
|
|
682
|
+
if (destinationOffset % QUERY_RESOLVE_ALIGNMENT !== 0) {
|
|
683
|
+
return err(
|
|
684
|
+
new RhiError({
|
|
685
|
+
code: "webgpu-runtime-error",
|
|
686
|
+
expected: "destinationOffset % 256 == 0 (spec normative)",
|
|
687
|
+
hint: `got destinationOffset=${destinationOffset}; align to a multiple of 256 bytes (kQueryResolveAlignment)`
|
|
688
|
+
})
|
|
689
|
+
);
|
|
690
|
+
}
|
|
691
|
+
const dstMeta = BUFFER_META_MAP.get(destination);
|
|
692
|
+
if (dstMeta !== void 0 && (dstMeta.usage & BUFFER_USAGE_QUERY_RESOLVE) === 0) {
|
|
693
|
+
return err(
|
|
694
|
+
new RhiError({
|
|
695
|
+
code: "webgpu-runtime-error",
|
|
696
|
+
expected: "destination.usage must contain QUERY_RESOLVE",
|
|
697
|
+
hint: `got destination.usage=0x${dstMeta.usage.toString(16)}; create the buffer with GPUBufferUsage.QUERY_RESOLVE (0x200)`
|
|
698
|
+
})
|
|
699
|
+
);
|
|
700
|
+
}
|
|
701
|
+
const rawQs = QUERY_SET_RAW_MAP.get(querySet);
|
|
702
|
+
const qsCount = rawQs !== void 0 && typeof rawQs.count === "number" ? rawQs.count : Number.MAX_SAFE_INTEGER;
|
|
703
|
+
if (firstQuery < 0 || firstQuery + queryCount > qsCount) {
|
|
704
|
+
return err(
|
|
705
|
+
new RhiError({
|
|
706
|
+
code: "webgpu-runtime-error",
|
|
707
|
+
expected: "firstQuery + queryCount <= querySet.count",
|
|
708
|
+
hint: `got firstQuery=${firstQuery}, queryCount=${queryCount}; querySet.count=${qsCount}`
|
|
709
|
+
})
|
|
710
|
+
);
|
|
711
|
+
}
|
|
712
|
+
if (dstMeta !== void 0) {
|
|
713
|
+
const requiredBytes = destinationOffset + 8 * queryCount;
|
|
714
|
+
if (requiredBytes > dstMeta.size) {
|
|
715
|
+
return err(
|
|
716
|
+
new RhiError({
|
|
717
|
+
code: "webgpu-runtime-error",
|
|
718
|
+
expected: "destinationOffset + 8 * queryCount <= destination.size",
|
|
719
|
+
hint: `got destinationOffset=${destinationOffset}, queryCount=${queryCount} (8 * queryCount = ${8 * queryCount}); destination.size=${dstMeta.size}`
|
|
720
|
+
})
|
|
721
|
+
);
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
const rawQsHandle = QUERY_SET_RAW_MAP.get(querySet) ?? querySet;
|
|
725
|
+
const rawDstHandle = BUFFER_RAW_MAP.get(destination) ?? destination;
|
|
726
|
+
return resolveTimestampQueries({
|
|
727
|
+
rawEncoder,
|
|
728
|
+
rawQuerySet: rawQsHandle,
|
|
729
|
+
firstQuery,
|
|
730
|
+
queryCount,
|
|
731
|
+
rawDestination: rawDstHandle,
|
|
732
|
+
destinationOffset
|
|
733
|
+
});
|
|
734
|
+
},
|
|
735
|
+
pushDebugGroup(groupLabel) {
|
|
736
|
+
rawEncoder.pushDebugGroup(groupLabel);
|
|
737
|
+
},
|
|
738
|
+
popDebugGroup() {
|
|
739
|
+
rawEncoder.popDebugGroup();
|
|
740
|
+
},
|
|
741
|
+
insertDebugMarker(markerLabel) {
|
|
742
|
+
rawEncoder.insertDebugMarker(markerLabel);
|
|
743
|
+
},
|
|
744
|
+
writeTimestamp(querySet, queryIndex) {
|
|
745
|
+
const state = ENCODER_STATE.get(enc);
|
|
746
|
+
throwIfFinished(state);
|
|
747
|
+
if (caps.timestampQuery !== true) {
|
|
748
|
+
fireFeatureNotEnabled(
|
|
749
|
+
"timestamp-query",
|
|
750
|
+
"check device.caps.timestampQuery before calling writeTimestamp"
|
|
751
|
+
);
|
|
752
|
+
return;
|
|
753
|
+
}
|
|
754
|
+
const rawQs = QUERY_SET_RAW_MAP.get(querySet) ?? querySet;
|
|
755
|
+
writeTimestamp({ rawEncoder, rawQuerySet: rawQs, queryIndex });
|
|
756
|
+
},
|
|
757
|
+
finish() {
|
|
758
|
+
const state = ENCODER_STATE.get(enc);
|
|
759
|
+
if (state === void 0) {
|
|
760
|
+
return commandEncoderFinished();
|
|
761
|
+
}
|
|
762
|
+
if (state.finished) {
|
|
763
|
+
return commandEncoderFinished();
|
|
764
|
+
}
|
|
765
|
+
if (state.activePass !== null) {
|
|
766
|
+
const passState = PASS_STATE.get(state.activePass);
|
|
767
|
+
if (passState !== void 0 && !passState.ended) {
|
|
768
|
+
return renderPassNotEnded();
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
const rawCommandBuffer = rawEncoder.finish();
|
|
772
|
+
state.finished = true;
|
|
773
|
+
const cb = rawCommandBuffer;
|
|
774
|
+
COMMAND_BUFFER_RAW_MAP.set(cb, rawCommandBuffer);
|
|
775
|
+
return ok(cb);
|
|
776
|
+
}
|
|
777
|
+
};
|
|
778
|
+
ENCODER_STATE.set(enc, { raw: rawEncoder, finished: false, activePass: null });
|
|
779
|
+
return enc;
|
|
780
|
+
}
|
|
781
|
+
var MAP_MODE_READ = 1;
|
|
782
|
+
var MAP_MODE_WRITE = 2;
|
|
783
|
+
var BUFFER_USAGE_MAP_READ = 1;
|
|
784
|
+
var BUFFER_USAGE_MAP_WRITE = 2;
|
|
785
|
+
function rangeError(args) {
|
|
786
|
+
return err(
|
|
787
|
+
new RhiError({
|
|
788
|
+
code: "webgpu-runtime-error",
|
|
789
|
+
expected: args.expected,
|
|
790
|
+
hint: args.hint
|
|
791
|
+
})
|
|
792
|
+
);
|
|
793
|
+
}
|
|
794
|
+
function makeBufferWrapper(raw, size, usage) {
|
|
795
|
+
const initialState = typeof raw.mapState === "string" ? raw.mapState : "unmapped";
|
|
796
|
+
let mapState = initialState;
|
|
797
|
+
const wrapper = {
|
|
798
|
+
get mapState() {
|
|
799
|
+
const rs = raw.mapState;
|
|
800
|
+
if (typeof rs === "string") {
|
|
801
|
+
mapState = rs;
|
|
802
|
+
return rs;
|
|
803
|
+
}
|
|
804
|
+
return mapState;
|
|
805
|
+
},
|
|
806
|
+
async mapAsync(mode, offset, sizeArg) {
|
|
807
|
+
const cur = wrapper.mapState;
|
|
808
|
+
if (cur !== "unmapped") {
|
|
809
|
+
return rangeError({
|
|
810
|
+
expected: 'buffer.mapState === "unmapped" before mapAsync',
|
|
811
|
+
hint: `got mapState=${cur}; call buffer.unmap() before mapAsync, or wait for the previous mapAsync to settle`
|
|
812
|
+
});
|
|
813
|
+
}
|
|
814
|
+
const off = offset ?? 0;
|
|
815
|
+
const rangeSize = sizeArg === void 0 ? Math.max(0, size - off) : sizeArg;
|
|
816
|
+
if (off % 8 !== 0) {
|
|
817
|
+
return rangeError({
|
|
818
|
+
expected: "mapAsync offset % 8 == 0 (spec normative)",
|
|
819
|
+
hint: `got offset=${off}; align offset to 8 bytes`
|
|
820
|
+
});
|
|
821
|
+
}
|
|
822
|
+
if (rangeSize % 4 !== 0) {
|
|
823
|
+
return rangeError({
|
|
824
|
+
expected: "mapAsync rangeSize % 4 == 0 (spec normative)",
|
|
825
|
+
hint: `got rangeSize=${rangeSize}; align rangeSize to 4 bytes`
|
|
826
|
+
});
|
|
827
|
+
}
|
|
828
|
+
if (off + rangeSize > size) {
|
|
829
|
+
return rangeError({
|
|
830
|
+
expected: "mapAsync offset + rangeSize <= buffer.size",
|
|
831
|
+
hint: `got offset=${off}, rangeSize=${rangeSize}; buffer.size=${size}`
|
|
832
|
+
});
|
|
833
|
+
}
|
|
834
|
+
if ((mode & -4) !== 0) {
|
|
835
|
+
return rangeError({
|
|
836
|
+
expected: "mapAsync mode contains only READ or WRITE bits",
|
|
837
|
+
hint: `got mode=0x${mode.toString(16)}; pass GPUMapMode.READ (0x1) or GPUMapMode.WRITE (0x2)`
|
|
838
|
+
});
|
|
839
|
+
}
|
|
840
|
+
if (mode !== MAP_MODE_READ && mode !== MAP_MODE_WRITE) {
|
|
841
|
+
return rangeError({
|
|
842
|
+
expected: "mapAsync mode is exactly one of READ | WRITE (not both)",
|
|
843
|
+
hint: `got mode=0x${mode.toString(16)}; pass GPUMapMode.READ (0x1) or GPUMapMode.WRITE (0x2), not the OR-combined mask`
|
|
844
|
+
});
|
|
845
|
+
}
|
|
846
|
+
if ((mode & MAP_MODE_READ) !== 0 && (usage & BUFFER_USAGE_MAP_READ) === 0) {
|
|
847
|
+
return rangeError({
|
|
848
|
+
expected: "mapAsync mode READ requires buffer.usage to contain MAP_READ",
|
|
849
|
+
hint: `got mode=READ, buffer.usage=0x${usage.toString(16)}; create buffer with GPUBufferUsage.MAP_READ`
|
|
850
|
+
});
|
|
851
|
+
}
|
|
852
|
+
if ((mode & MAP_MODE_WRITE) !== 0 && (usage & BUFFER_USAGE_MAP_WRITE) === 0) {
|
|
853
|
+
return rangeError({
|
|
854
|
+
expected: "mapAsync mode WRITE requires buffer.usage to contain MAP_WRITE",
|
|
855
|
+
hint: `got mode=WRITE, buffer.usage=0x${usage.toString(16)}; create buffer with GPUBufferUsage.MAP_WRITE`
|
|
856
|
+
});
|
|
857
|
+
}
|
|
858
|
+
mapState = "pending";
|
|
859
|
+
try {
|
|
860
|
+
if (typeof raw.mapAsync === "function") {
|
|
861
|
+
if (sizeArg === void 0 && offset === void 0) {
|
|
862
|
+
await raw.mapAsync(mode);
|
|
863
|
+
} else if (sizeArg === void 0) {
|
|
864
|
+
await raw.mapAsync(mode, off);
|
|
865
|
+
} else {
|
|
866
|
+
await raw.mapAsync(mode, off, sizeArg);
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
mapState = "mapped";
|
|
870
|
+
return ok(wrapper);
|
|
871
|
+
} catch (e) {
|
|
872
|
+
mapState = "unmapped";
|
|
873
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
874
|
+
return rangeError({
|
|
875
|
+
expected: "underlying GPUBuffer.mapAsync to succeed",
|
|
876
|
+
hint: `mapAsync raised: ${message}`
|
|
877
|
+
});
|
|
878
|
+
}
|
|
879
|
+
},
|
|
880
|
+
getMappedRange(offset, sizeArg) {
|
|
881
|
+
const cur = wrapper.mapState;
|
|
882
|
+
if (cur !== "mapped") {
|
|
883
|
+
return rangeError({
|
|
884
|
+
expected: 'buffer.mapState === "mapped" before getMappedRange',
|
|
885
|
+
hint: "call buffer.mapAsync(MODE) and await it before getMappedRange"
|
|
886
|
+
});
|
|
887
|
+
}
|
|
888
|
+
try {
|
|
889
|
+
if (typeof raw.getMappedRange !== "function") {
|
|
890
|
+
return rangeError({
|
|
891
|
+
expected: "underlying GPUBuffer.getMappedRange to be available",
|
|
892
|
+
hint: "mock or driver does not expose getMappedRange; use a real GPUBuffer"
|
|
893
|
+
});
|
|
894
|
+
}
|
|
895
|
+
const view = sizeArg === void 0 ? offset === void 0 ? raw.getMappedRange() : raw.getMappedRange(offset) : raw.getMappedRange(offset ?? 0, sizeArg);
|
|
896
|
+
return ok(view);
|
|
897
|
+
} catch (e) {
|
|
898
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
899
|
+
return rangeError({
|
|
900
|
+
expected: "underlying GPUBuffer.getMappedRange to succeed",
|
|
901
|
+
hint: `getMappedRange raised: ${message}`
|
|
902
|
+
});
|
|
903
|
+
}
|
|
904
|
+
},
|
|
905
|
+
unmap() {
|
|
906
|
+
try {
|
|
907
|
+
if (typeof raw.unmap === "function") {
|
|
908
|
+
raw.unmap();
|
|
909
|
+
}
|
|
910
|
+
} catch {
|
|
911
|
+
}
|
|
912
|
+
mapState = "unmapped";
|
|
913
|
+
}
|
|
914
|
+
};
|
|
915
|
+
return wrapper;
|
|
916
|
+
}
|
|
917
|
+
function makeQueue(rawQueue) {
|
|
918
|
+
return {
|
|
919
|
+
writeBuffer(buffer, bufferOffset, data, dataOffset, size) {
|
|
920
|
+
const rawBuffer = BUFFER_RAW_MAP.get(buffer) ?? buffer;
|
|
921
|
+
const bufferSize = typeof rawBuffer.size === "number" ? rawBuffer.size : Number.MAX_SAFE_INTEGER;
|
|
922
|
+
if (bufferOffset % 4 !== 0) {
|
|
923
|
+
return queueWriteBufferOutOfBounds({
|
|
924
|
+
offset: bufferOffset,
|
|
925
|
+
byteLength: data instanceof ArrayBuffer ? data.byteLength : data.byteLength,
|
|
926
|
+
bufferSize
|
|
927
|
+
});
|
|
928
|
+
}
|
|
929
|
+
const dataByteLength = data instanceof ArrayBuffer ? data.byteLength : data.byteLength;
|
|
930
|
+
const writeStart = dataOffset ?? 0;
|
|
931
|
+
const writeSize = size ?? dataByteLength - writeStart;
|
|
932
|
+
if (bufferOffset + writeSize > bufferSize) {
|
|
933
|
+
return queueWriteBufferOutOfBounds({
|
|
934
|
+
offset: bufferOffset,
|
|
935
|
+
byteLength: writeSize,
|
|
936
|
+
bufferSize
|
|
937
|
+
});
|
|
938
|
+
}
|
|
939
|
+
try {
|
|
940
|
+
if (size !== void 0) {
|
|
941
|
+
rawQueue.writeBuffer(
|
|
942
|
+
rawBuffer,
|
|
943
|
+
bufferOffset,
|
|
944
|
+
data,
|
|
945
|
+
writeStart,
|
|
946
|
+
size
|
|
947
|
+
);
|
|
948
|
+
} else if (dataOffset !== void 0) {
|
|
949
|
+
rawQueue.writeBuffer(
|
|
950
|
+
rawBuffer,
|
|
951
|
+
bufferOffset,
|
|
952
|
+
data,
|
|
953
|
+
writeStart
|
|
954
|
+
);
|
|
955
|
+
} else {
|
|
956
|
+
rawQueue.writeBuffer(rawBuffer, bufferOffset, data);
|
|
957
|
+
}
|
|
958
|
+
return ok(void 0);
|
|
959
|
+
} catch (e) {
|
|
960
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
961
|
+
if (/out of (bounds|range)|exceed/i.test(message)) {
|
|
962
|
+
return queueWriteBufferOutOfBounds({
|
|
963
|
+
offset: bufferOffset,
|
|
964
|
+
byteLength: writeSize,
|
|
965
|
+
bufferSize
|
|
966
|
+
});
|
|
967
|
+
}
|
|
968
|
+
return queueSubmitFailed(message);
|
|
969
|
+
}
|
|
970
|
+
},
|
|
971
|
+
submit(commandBuffers) {
|
|
972
|
+
const rawList = [];
|
|
973
|
+
for (const cb of commandBuffers) {
|
|
974
|
+
const raw = COMMAND_BUFFER_RAW_MAP.get(cb);
|
|
975
|
+
if (raw !== void 0) {
|
|
976
|
+
rawList.push(raw);
|
|
977
|
+
} else {
|
|
978
|
+
rawList.push(cb);
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
try {
|
|
982
|
+
rawQueue.submit(rawList);
|
|
983
|
+
return ok(void 0);
|
|
984
|
+
} catch (e) {
|
|
985
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
986
|
+
return queueSubmitFailed(message);
|
|
987
|
+
}
|
|
988
|
+
},
|
|
989
|
+
writeTexture(destination, data, dataLayout, size) {
|
|
990
|
+
try {
|
|
991
|
+
const rawDestination = {
|
|
992
|
+
texture: destination.texture
|
|
993
|
+
};
|
|
994
|
+
if (destination.mipLevel !== void 0) rawDestination.mipLevel = destination.mipLevel;
|
|
995
|
+
if (destination.origin !== void 0) rawDestination.origin = destination.origin;
|
|
996
|
+
if (destination.aspect !== void 0) rawDestination.aspect = destination.aspect;
|
|
997
|
+
rawQueue.writeTexture(
|
|
998
|
+
rawDestination,
|
|
999
|
+
data,
|
|
1000
|
+
dataLayout,
|
|
1001
|
+
size
|
|
1002
|
+
);
|
|
1003
|
+
return ok(void 0);
|
|
1004
|
+
} catch (e) {
|
|
1005
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
1006
|
+
return err(
|
|
1007
|
+
new RhiError({
|
|
1008
|
+
code: "webgpu-runtime-error",
|
|
1009
|
+
expected: "underlying GPUQueue.writeTexture to succeed",
|
|
1010
|
+
hint: `writeTexture raised: ${message}`
|
|
1011
|
+
})
|
|
1012
|
+
);
|
|
1013
|
+
}
|
|
1014
|
+
},
|
|
1015
|
+
copyExternalImageToTexture(source, destination, copySize) {
|
|
1016
|
+
try {
|
|
1017
|
+
rawQueue.copyExternalImageToTexture(
|
|
1018
|
+
source,
|
|
1019
|
+
{
|
|
1020
|
+
texture: destination.texture,
|
|
1021
|
+
...destination.mipLevel === void 0 ? {} : { mipLevel: destination.mipLevel },
|
|
1022
|
+
...destination.origin === void 0 ? {} : { origin: destination.origin },
|
|
1023
|
+
...destination.aspect === void 0 ? {} : { aspect: destination.aspect },
|
|
1024
|
+
...destination.colorSpace === void 0 ? {} : { colorSpace: destination.colorSpace },
|
|
1025
|
+
...destination.premultipliedAlpha === void 0 ? {} : { premultipliedAlpha: destination.premultipliedAlpha }
|
|
1026
|
+
},
|
|
1027
|
+
copySize
|
|
1028
|
+
);
|
|
1029
|
+
return ok(void 0);
|
|
1030
|
+
} catch (e) {
|
|
1031
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
1032
|
+
return err(
|
|
1033
|
+
new RhiError({
|
|
1034
|
+
code: "webgpu-runtime-error",
|
|
1035
|
+
expected: "underlying GPUQueue.copyExternalImageToTexture to succeed",
|
|
1036
|
+
hint: `copyExternalImageToTexture raised: ${message}`
|
|
1037
|
+
})
|
|
1038
|
+
);
|
|
1039
|
+
}
|
|
1040
|
+
},
|
|
1041
|
+
// forgeax-async-whitelist: dom-native — spec `GPUQueue.onSubmittedWorkDone()` Promise passthrough
|
|
1042
|
+
onSubmittedWorkDone() {
|
|
1043
|
+
return rawQueue.onSubmittedWorkDone();
|
|
1044
|
+
}
|
|
1045
|
+
};
|
|
1046
|
+
}
|
|
1047
|
+
function makeRhiDevice(rawDevice) {
|
|
1048
|
+
const caps = deriveCaps(rawDevice, rawDevice.features, rawDevice.limits);
|
|
1049
|
+
const features = rawDevice.features;
|
|
1050
|
+
const limits = rawDevice.limits;
|
|
1051
|
+
const queue = makeQueue(rawDevice.queue);
|
|
1052
|
+
const device = {
|
|
1053
|
+
caps,
|
|
1054
|
+
features,
|
|
1055
|
+
limits,
|
|
1056
|
+
queue,
|
|
1057
|
+
lost: rawDevice.lost,
|
|
1058
|
+
createBuffer(desc) {
|
|
1059
|
+
const out = rawDevice.createBuffer(
|
|
1060
|
+
mirror(desc, BUFFER_KEYS)
|
|
1061
|
+
);
|
|
1062
|
+
const sizeField = typeof desc.size === "number" ? desc.size : 0;
|
|
1063
|
+
const usageField = desc.usage ?? 0;
|
|
1064
|
+
const handle = makeBufferWrapper(out, sizeField, usageField);
|
|
1065
|
+
BUFFER_RAW_MAP.set(handle, out);
|
|
1066
|
+
BUFFER_META_MAP.set(handle, {
|
|
1067
|
+
size: sizeField,
|
|
1068
|
+
usage: usageField,
|
|
1069
|
+
destroyed: false
|
|
1070
|
+
});
|
|
1071
|
+
return ok(handle);
|
|
1072
|
+
},
|
|
1073
|
+
createTexture(desc) {
|
|
1074
|
+
const out = rawDevice.createTexture(
|
|
1075
|
+
mirror(desc, TEXTURE_KEYS)
|
|
1076
|
+
);
|
|
1077
|
+
const handle = out;
|
|
1078
|
+
const viewFormats = desc.viewFormats === void 0 ? [] : Array.from(desc.viewFormats);
|
|
1079
|
+
TEXTURE_META_MAP.set(handle, {
|
|
1080
|
+
format: desc.format,
|
|
1081
|
+
usage: desc.usage,
|
|
1082
|
+
viewFormats,
|
|
1083
|
+
destroyed: false
|
|
1084
|
+
});
|
|
1085
|
+
return ok(handle);
|
|
1086
|
+
},
|
|
1087
|
+
destroyBuffer(buf) {
|
|
1088
|
+
const meta = BUFFER_META_MAP.get(buf);
|
|
1089
|
+
if (meta?.destroyed) {
|
|
1090
|
+
return err(
|
|
1091
|
+
new RhiError({
|
|
1092
|
+
code: "destroy-after-destroy",
|
|
1093
|
+
expected: "GPU buffer handle has not been destroyed yet",
|
|
1094
|
+
hint: "object already destroyed; track lifecycle in caller or check isDestroyed before re-destroy"
|
|
1095
|
+
})
|
|
1096
|
+
);
|
|
1097
|
+
}
|
|
1098
|
+
const rawBuf = BUFFER_RAW_MAP.get(buf);
|
|
1099
|
+
try {
|
|
1100
|
+
if (rawBuf !== void 0 && typeof rawBuf.destroy === "function") {
|
|
1101
|
+
rawBuf.destroy();
|
|
1102
|
+
}
|
|
1103
|
+
} catch (e) {
|
|
1104
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
1105
|
+
return err(
|
|
1106
|
+
new RhiError({
|
|
1107
|
+
code: "webgpu-runtime-error",
|
|
1108
|
+
expected: "underlying GPUBuffer.destroy() to succeed",
|
|
1109
|
+
hint: `destroy raised: ${message}`
|
|
1110
|
+
})
|
|
1111
|
+
);
|
|
1112
|
+
}
|
|
1113
|
+
if (meta !== void 0) meta.destroyed = true;
|
|
1114
|
+
return ok(void 0);
|
|
1115
|
+
},
|
|
1116
|
+
destroyTexture(tex) {
|
|
1117
|
+
const meta = TEXTURE_META_MAP.get(tex);
|
|
1118
|
+
if (meta?.destroyed) {
|
|
1119
|
+
return err(
|
|
1120
|
+
new RhiError({
|
|
1121
|
+
code: "destroy-after-destroy",
|
|
1122
|
+
expected: "GPU texture handle has not been destroyed yet",
|
|
1123
|
+
hint: "object already destroyed; track lifecycle in caller or check isDestroyed before re-destroy"
|
|
1124
|
+
})
|
|
1125
|
+
);
|
|
1126
|
+
}
|
|
1127
|
+
const rawTex = tex;
|
|
1128
|
+
try {
|
|
1129
|
+
if (typeof rawTex.destroy === "function") {
|
|
1130
|
+
rawTex.destroy();
|
|
1131
|
+
}
|
|
1132
|
+
} catch (e) {
|
|
1133
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
1134
|
+
return err(
|
|
1135
|
+
new RhiError({
|
|
1136
|
+
code: "webgpu-runtime-error",
|
|
1137
|
+
expected: "underlying GPUTexture.destroy() to succeed",
|
|
1138
|
+
hint: `destroy raised: ${message}`
|
|
1139
|
+
})
|
|
1140
|
+
);
|
|
1141
|
+
}
|
|
1142
|
+
if (meta !== void 0) meta.destroyed = true;
|
|
1143
|
+
return ok(void 0);
|
|
1144
|
+
},
|
|
1145
|
+
createTextureView(texture, desc) {
|
|
1146
|
+
const meta = TEXTURE_META_MAP.get(texture);
|
|
1147
|
+
if (meta !== void 0) {
|
|
1148
|
+
const fmt = desc.format;
|
|
1149
|
+
if (fmt !== void 0 && fmt !== meta.format && !meta.viewFormats.includes(fmt)) {
|
|
1150
|
+
return err(
|
|
1151
|
+
new RhiError({
|
|
1152
|
+
code: "webgpu-runtime-error",
|
|
1153
|
+
expected: "createTextureView format must be the source texture format or one of source.viewFormats",
|
|
1154
|
+
hint: `got format='${fmt}'; source.format='${meta.format}'; source.viewFormats=[${meta.viewFormats.join(", ")}]`
|
|
1155
|
+
})
|
|
1156
|
+
);
|
|
1157
|
+
}
|
|
1158
|
+
const reqUsage = desc.usage;
|
|
1159
|
+
if (reqUsage !== void 0 && reqUsage !== 0 && (reqUsage & ~meta.usage) !== 0) {
|
|
1160
|
+
return err(
|
|
1161
|
+
new RhiError({
|
|
1162
|
+
code: "webgpu-runtime-error",
|
|
1163
|
+
expected: "createTextureView usage must be a subset of source.usage",
|
|
1164
|
+
hint: `got usage=0x${reqUsage.toString(16)}; source.usage=0x${meta.usage.toString(16)}`
|
|
1165
|
+
})
|
|
1166
|
+
);
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
const rawTexture = texture;
|
|
1170
|
+
try {
|
|
1171
|
+
const rawView = rawTexture.createView(
|
|
1172
|
+
mirror(desc, TEXTURE_VIEW_KEYS)
|
|
1173
|
+
);
|
|
1174
|
+
const handle = rawView;
|
|
1175
|
+
TEXTURE_VIEW_RAW_MAP.set(handle, rawView);
|
|
1176
|
+
return ok(handle);
|
|
1177
|
+
} catch (e) {
|
|
1178
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
1179
|
+
return err(
|
|
1180
|
+
new RhiError({
|
|
1181
|
+
code: "webgpu-runtime-error",
|
|
1182
|
+
expected: "underlying GPUTexture.createView to succeed",
|
|
1183
|
+
hint: `createView raised: ${message}`
|
|
1184
|
+
})
|
|
1185
|
+
);
|
|
1186
|
+
}
|
|
1187
|
+
},
|
|
1188
|
+
createSampler(desc) {
|
|
1189
|
+
if (desc === void 0) {
|
|
1190
|
+
const out2 = rawDevice.createSampler();
|
|
1191
|
+
return ok(out2);
|
|
1192
|
+
}
|
|
1193
|
+
const out = rawDevice.createSampler(
|
|
1194
|
+
mirror(desc, SAMPLER_KEYS)
|
|
1195
|
+
);
|
|
1196
|
+
return ok(out);
|
|
1197
|
+
},
|
|
1198
|
+
createBindGroupLayout(desc) {
|
|
1199
|
+
const out = rawDevice.createBindGroupLayout(
|
|
1200
|
+
mirror(desc, BGL_KEYS)
|
|
1201
|
+
);
|
|
1202
|
+
return ok(out);
|
|
1203
|
+
},
|
|
1204
|
+
createBindGroup(desc) {
|
|
1205
|
+
const mirrored = {
|
|
1206
|
+
layout: desc.layout,
|
|
1207
|
+
entries: []
|
|
1208
|
+
};
|
|
1209
|
+
if ("label" in desc && desc.label !== void 0) mirrored.label = desc.label;
|
|
1210
|
+
for (const entry of desc.entries) {
|
|
1211
|
+
const resource = entry.resource;
|
|
1212
|
+
switch (resource.kind) {
|
|
1213
|
+
case "sampler": {
|
|
1214
|
+
mirrored.entries.push({
|
|
1215
|
+
binding: entry.binding,
|
|
1216
|
+
resource: resource.value
|
|
1217
|
+
});
|
|
1218
|
+
break;
|
|
1219
|
+
}
|
|
1220
|
+
case "buffer": {
|
|
1221
|
+
const { buffer, offset, size } = resource.value;
|
|
1222
|
+
const rawBuf = BUFFER_RAW_MAP.get(buffer) ?? buffer;
|
|
1223
|
+
const bufferBinding = { buffer: rawBuf };
|
|
1224
|
+
if (offset !== void 0) bufferBinding.offset = offset;
|
|
1225
|
+
if (size !== void 0) bufferBinding.size = size;
|
|
1226
|
+
mirrored.entries.push({ binding: entry.binding, resource: bufferBinding });
|
|
1227
|
+
break;
|
|
1228
|
+
}
|
|
1229
|
+
case "textureView": {
|
|
1230
|
+
mirrored.entries.push({
|
|
1231
|
+
binding: entry.binding,
|
|
1232
|
+
resource: resource.value
|
|
1233
|
+
});
|
|
1234
|
+
break;
|
|
1235
|
+
}
|
|
1236
|
+
case "externalTexture": {
|
|
1237
|
+
mirrored.entries.push({
|
|
1238
|
+
binding: entry.binding,
|
|
1239
|
+
resource: resource.value
|
|
1240
|
+
});
|
|
1241
|
+
break;
|
|
1242
|
+
}
|
|
1243
|
+
default: {
|
|
1244
|
+
throw new Error(`rhi-webgpu: unreachable RhiBindingResource kind in createBindGroup`);
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
const out = rawDevice.createBindGroup(mirrored);
|
|
1249
|
+
return ok(out);
|
|
1250
|
+
},
|
|
1251
|
+
createPipelineLayout(desc) {
|
|
1252
|
+
const out = rawDevice.createPipelineLayout(
|
|
1253
|
+
mirror(desc, PL_KEYS)
|
|
1254
|
+
);
|
|
1255
|
+
return ok(out);
|
|
1256
|
+
},
|
|
1257
|
+
createRenderPipeline(desc) {
|
|
1258
|
+
try {
|
|
1259
|
+
const out = rawDevice.createRenderPipeline(mirrorRenderPipelineDescriptor(desc));
|
|
1260
|
+
return ok(out);
|
|
1261
|
+
} catch (e) {
|
|
1262
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
1263
|
+
if (/compile|shader|wgsl/i.test(message)) {
|
|
1264
|
+
return err(
|
|
1265
|
+
new RhiError({
|
|
1266
|
+
code: "shader-compile-failed",
|
|
1267
|
+
expected: "render shader modules + entry points to be valid",
|
|
1268
|
+
hint: `compile error: ${message}`
|
|
1269
|
+
})
|
|
1270
|
+
);
|
|
1271
|
+
}
|
|
1272
|
+
return err(
|
|
1273
|
+
new RhiError({
|
|
1274
|
+
code: "webgpu-runtime-error",
|
|
1275
|
+
expected: "underlying GPUDevice.createRenderPipeline to succeed",
|
|
1276
|
+
hint: `createRenderPipeline raised: ${message}`
|
|
1277
|
+
})
|
|
1278
|
+
);
|
|
1279
|
+
}
|
|
1280
|
+
},
|
|
1281
|
+
createComputePipeline(desc) {
|
|
1282
|
+
if (caps.compute === false) {
|
|
1283
|
+
return err(
|
|
1284
|
+
new RhiError({
|
|
1285
|
+
code: "feature-not-enabled",
|
|
1286
|
+
expected: "caps.compute === true",
|
|
1287
|
+
hint: "check device.caps.compute before calling createComputePipeline"
|
|
1288
|
+
})
|
|
1289
|
+
);
|
|
1290
|
+
}
|
|
1291
|
+
try {
|
|
1292
|
+
const out = rawDevice.createComputePipeline(
|
|
1293
|
+
mirror(desc, CP_KEYS)
|
|
1294
|
+
);
|
|
1295
|
+
return ok(out);
|
|
1296
|
+
} catch (e) {
|
|
1297
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
1298
|
+
if (/compile|shader|wgsl/i.test(message)) {
|
|
1299
|
+
return err(
|
|
1300
|
+
new RhiError({
|
|
1301
|
+
code: "shader-compile-failed",
|
|
1302
|
+
expected: "compute shader module + entry point to be valid",
|
|
1303
|
+
hint: `compile error: ${message}`
|
|
1304
|
+
})
|
|
1305
|
+
);
|
|
1306
|
+
}
|
|
1307
|
+
return err(
|
|
1308
|
+
new RhiError({
|
|
1309
|
+
code: "webgpu-runtime-error",
|
|
1310
|
+
expected: "underlying GPUDevice.createComputePipeline to succeed",
|
|
1311
|
+
hint: `createComputePipeline raised: ${message}`
|
|
1312
|
+
})
|
|
1313
|
+
);
|
|
1314
|
+
}
|
|
1315
|
+
},
|
|
1316
|
+
createQuerySet(desc) {
|
|
1317
|
+
const count = desc.count;
|
|
1318
|
+
if (typeof count === "number" && count > QUERY_SET_COUNT_LIMIT) {
|
|
1319
|
+
return err(
|
|
1320
|
+
new RhiError({
|
|
1321
|
+
code: "limit-exceeded",
|
|
1322
|
+
expected: "count <= 4096 (spec normative)",
|
|
1323
|
+
hint: "create multiple QuerySet instances if more than 4096 queries needed"
|
|
1324
|
+
})
|
|
1325
|
+
);
|
|
1326
|
+
}
|
|
1327
|
+
if (desc.type === "timestamp" && caps.timestampQuery !== true) {
|
|
1328
|
+
return err(
|
|
1329
|
+
new RhiError({
|
|
1330
|
+
code: "feature-not-enabled",
|
|
1331
|
+
expected: "caps.timestampQuery === true (timestamp-query feature)",
|
|
1332
|
+
hint: "request the timestamp-query feature at requestDevice and check device.caps.timestampQuery before creating timestamp QuerySets"
|
|
1333
|
+
})
|
|
1334
|
+
);
|
|
1335
|
+
}
|
|
1336
|
+
try {
|
|
1337
|
+
const out = rawDevice.createQuerySet(
|
|
1338
|
+
mirror(desc, QS_KEYS)
|
|
1339
|
+
);
|
|
1340
|
+
const handle = out;
|
|
1341
|
+
QUERY_SET_RAW_MAP.set(handle, out);
|
|
1342
|
+
QUERY_SET_DESTROYED_MAP.set(handle, { destroyed: false });
|
|
1343
|
+
return ok(handle);
|
|
1344
|
+
} catch (e) {
|
|
1345
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
1346
|
+
return err(
|
|
1347
|
+
new RhiError({
|
|
1348
|
+
code: "webgpu-runtime-error",
|
|
1349
|
+
expected: "underlying GPUDevice.createQuerySet to succeed",
|
|
1350
|
+
hint: `createQuerySet raised: ${message}`
|
|
1351
|
+
})
|
|
1352
|
+
);
|
|
1353
|
+
}
|
|
1354
|
+
},
|
|
1355
|
+
destroyQuerySet(querySet) {
|
|
1356
|
+
const marker = QUERY_SET_DESTROYED_MAP.get(querySet);
|
|
1357
|
+
if (marker?.destroyed) {
|
|
1358
|
+
return err(
|
|
1359
|
+
new RhiError({
|
|
1360
|
+
code: "destroy-after-destroy",
|
|
1361
|
+
expected: "GPU query-set handle has not been destroyed yet",
|
|
1362
|
+
hint: "object already destroyed; release each timestamp QuerySet exactly once"
|
|
1363
|
+
})
|
|
1364
|
+
);
|
|
1365
|
+
}
|
|
1366
|
+
const rawQuery = QUERY_SET_RAW_MAP.get(querySet);
|
|
1367
|
+
try {
|
|
1368
|
+
if (rawQuery !== void 0 && typeof rawQuery.destroy === "function") rawQuery.destroy();
|
|
1369
|
+
} catch (e) {
|
|
1370
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
1371
|
+
return err(
|
|
1372
|
+
new RhiError({
|
|
1373
|
+
code: "webgpu-runtime-error",
|
|
1374
|
+
expected: "underlying GPUQuerySet.destroy() to succeed",
|
|
1375
|
+
hint: `destroy raised: ${message}`
|
|
1376
|
+
})
|
|
1377
|
+
);
|
|
1378
|
+
}
|
|
1379
|
+
if (marker !== void 0) marker.destroyed = true;
|
|
1380
|
+
return ok(void 0);
|
|
1381
|
+
},
|
|
1382
|
+
createCommandEncoder(desc) {
|
|
1383
|
+
const rawEnc = desc === void 0 ? rawDevice.createCommandEncoder() : rawDevice.createCommandEncoder(
|
|
1384
|
+
mirror(desc, ENC_KEYS)
|
|
1385
|
+
);
|
|
1386
|
+
const fireFeatureNotEnabled = (featureName, hint) => {
|
|
1387
|
+
console.error(
|
|
1388
|
+
`[RhiError feature-not-enabled] expected: device.features.has('${featureName}') === true; hint: ${hint}`
|
|
1389
|
+
);
|
|
1390
|
+
};
|
|
1391
|
+
return ok(makeCommandEncoder(rawEnc, caps, fireFeatureNotEnabled));
|
|
1392
|
+
}
|
|
1393
|
+
// fix-f3: synchronous createShaderModule placeholder removed; the
|
|
1394
|
+
// shader-compile-failed path lives in the top-level async factory
|
|
1395
|
+
// (see ../index.ts).
|
|
1396
|
+
};
|
|
1397
|
+
RAW_DEVICE_MAP.set(device, rawDevice);
|
|
1398
|
+
return { device, raw: rawDevice };
|
|
1399
|
+
}
|
|
1400
|
+
var SUPPORTED_CONTEXT_FORMATS = /* @__PURE__ */ new Set([
|
|
1401
|
+
"bgra8unorm",
|
|
1402
|
+
"rgba8unorm",
|
|
1403
|
+
"rgba16float"
|
|
1404
|
+
]);
|
|
1405
|
+
var CANVAS_CONFIG_KEYS = [
|
|
1406
|
+
"device",
|
|
1407
|
+
"format",
|
|
1408
|
+
"usage",
|
|
1409
|
+
"viewFormats",
|
|
1410
|
+
"colorSpace",
|
|
1411
|
+
"toneMapping",
|
|
1412
|
+
"alphaMode"
|
|
1413
|
+
];
|
|
1414
|
+
function makeCanvasContext(rawContext) {
|
|
1415
|
+
return {
|
|
1416
|
+
configure(desc) {
|
|
1417
|
+
const fmt = desc.format;
|
|
1418
|
+
if (typeof fmt === "string" && !SUPPORTED_CONTEXT_FORMATS.has(fmt)) {
|
|
1419
|
+
return err(
|
|
1420
|
+
new RhiError({
|
|
1421
|
+
code: "webgpu-runtime-error",
|
|
1422
|
+
expected: "one of bgra8unorm/rgba8unorm/rgba16float",
|
|
1423
|
+
hint: `got format='${fmt}'; canvas configuration cannot use srgb formats \u2014 use the non-srgb form (e.g. 'bgra8unorm') and put the srgb format in viewFormats, then createView with the srgb format`
|
|
1424
|
+
})
|
|
1425
|
+
);
|
|
1426
|
+
}
|
|
1427
|
+
try {
|
|
1428
|
+
const mirrored = mirror(
|
|
1429
|
+
desc,
|
|
1430
|
+
CANVAS_CONFIG_KEYS
|
|
1431
|
+
);
|
|
1432
|
+
if ("device" in mirrored) {
|
|
1433
|
+
const forgeaxDevice = desc.device;
|
|
1434
|
+
const rawDev = RAW_DEVICE_MAP.get(forgeaxDevice);
|
|
1435
|
+
if (rawDev === void 0) {
|
|
1436
|
+
return err(
|
|
1437
|
+
new RhiError({
|
|
1438
|
+
code: "rhi-not-available",
|
|
1439
|
+
expected: "CanvasConfiguration.device must be a RhiDevice produced by rhi.requestAdapter().requestDevice() (or the deprecated rhi.requestDevice factory)",
|
|
1440
|
+
hint: "pass the device returned by the forgeax rhi.requestAdapter() / rhi.requestDevice() entries; passing a foreign RhiDevice or a raw GPUDevice is rejected because the canvas-context spec requires the same raw GPUDevice that the forgeax shim wraps"
|
|
1441
|
+
})
|
|
1442
|
+
);
|
|
1443
|
+
}
|
|
1444
|
+
mirrored.device = rawDev;
|
|
1445
|
+
}
|
|
1446
|
+
rawContext.configure(mirrored);
|
|
1447
|
+
return ok(void 0);
|
|
1448
|
+
} catch (e) {
|
|
1449
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
1450
|
+
if (e instanceof Error && (e.name === "InvalidStateError" || /lost|destroyed/i.test(msg))) {
|
|
1451
|
+
return err(
|
|
1452
|
+
new RhiError({
|
|
1453
|
+
code: "rhi-not-available",
|
|
1454
|
+
expected: "CanvasConfiguration.device must be valid (not lost / destroyed)",
|
|
1455
|
+
hint: `configure raised: ${msg}`
|
|
1456
|
+
})
|
|
1457
|
+
);
|
|
1458
|
+
}
|
|
1459
|
+
return err(
|
|
1460
|
+
new RhiError({
|
|
1461
|
+
code: "webgpu-runtime-error",
|
|
1462
|
+
expected: "underlying GPUCanvasContext.configure to succeed",
|
|
1463
|
+
hint: `configure raised: ${msg}`
|
|
1464
|
+
})
|
|
1465
|
+
);
|
|
1466
|
+
}
|
|
1467
|
+
},
|
|
1468
|
+
unconfigure() {
|
|
1469
|
+
rawContext.unconfigure();
|
|
1470
|
+
},
|
|
1471
|
+
getConfiguration() {
|
|
1472
|
+
const conf = rawContext.getConfiguration();
|
|
1473
|
+
if (conf === null) return void 0;
|
|
1474
|
+
const out = {};
|
|
1475
|
+
for (const k of CANVAS_CONFIG_KEYS) {
|
|
1476
|
+
if (k in conf) {
|
|
1477
|
+
out[k] = conf[k];
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1480
|
+
return out;
|
|
1481
|
+
},
|
|
1482
|
+
getCurrentTexture() {
|
|
1483
|
+
try {
|
|
1484
|
+
const rawTex = rawContext.getCurrentTexture();
|
|
1485
|
+
return ok(rawTex);
|
|
1486
|
+
} catch (e) {
|
|
1487
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
1488
|
+
return err(
|
|
1489
|
+
new RhiError({
|
|
1490
|
+
code: "webgpu-runtime-error",
|
|
1491
|
+
expected: "GPUCanvasContext.getCurrentTexture to succeed (context configured)",
|
|
1492
|
+
hint: `getCurrentTexture raised: ${msg}`
|
|
1493
|
+
})
|
|
1494
|
+
);
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
};
|
|
1498
|
+
}
|
|
1499
|
+
function translateErrorEventToRhiError(event) {
|
|
1500
|
+
if (typeof event === "object" && event !== null && "reason" in event && typeof event.reason === "string") {
|
|
1501
|
+
const info = event;
|
|
1502
|
+
return err(
|
|
1503
|
+
new RhiError({
|
|
1504
|
+
code: "device-lost",
|
|
1505
|
+
expected: "device must remain alive (driver / browser must not destroy the GPUDevice)",
|
|
1506
|
+
hint: `device-lost reason: ${info.reason}; message: ${info.message ?? "<empty>"}`
|
|
1507
|
+
})
|
|
1508
|
+
);
|
|
1509
|
+
}
|
|
1510
|
+
if (typeof event === "object" && event !== null && "error" in event && typeof event.error === "object" && event.error !== null) {
|
|
1511
|
+
const error = event.error;
|
|
1512
|
+
const message = typeof error.message === "string" ? error.message : "<no message>";
|
|
1513
|
+
if (error.constructor.name === "GPUOutOfMemoryError") {
|
|
1514
|
+
return err(
|
|
1515
|
+
new RhiError({
|
|
1516
|
+
code: "oom",
|
|
1517
|
+
expected: "sufficient GPU memory to satisfy the allocation",
|
|
1518
|
+
hint: `GPU out-of-memory: ${message}`
|
|
1519
|
+
})
|
|
1520
|
+
);
|
|
1521
|
+
}
|
|
1522
|
+
if (error.constructor.name === "GPUInternalError") {
|
|
1523
|
+
return err(
|
|
1524
|
+
new RhiError({
|
|
1525
|
+
code: "internal-error",
|
|
1526
|
+
expected: "driver / browser must report a recognised validation error",
|
|
1527
|
+
hint: `GPU internal error: ${message}`
|
|
1528
|
+
})
|
|
1529
|
+
);
|
|
1530
|
+
}
|
|
1531
|
+
if (error.constructor.name === "GPUValidationError") {
|
|
1532
|
+
if (/shader|compile|wgsl/i.test(message)) {
|
|
1533
|
+
return err(
|
|
1534
|
+
new RhiError({
|
|
1535
|
+
code: "shader-compile-failed",
|
|
1536
|
+
expected: "valid WGSL source + matching pipeline layout",
|
|
1537
|
+
hint: `GPU validation: ${message}`
|
|
1538
|
+
})
|
|
1539
|
+
);
|
|
1540
|
+
}
|
|
1541
|
+
if (/size|alignment|out of bounds/i.test(message)) {
|
|
1542
|
+
return err(
|
|
1543
|
+
new RhiError({
|
|
1544
|
+
code: "queue-write-buffer-out-of-bounds",
|
|
1545
|
+
expected: "writeBuffer offset + data.byteLength must be within buffer.size",
|
|
1546
|
+
hint: `GPU validation: ${message}`
|
|
1547
|
+
})
|
|
1548
|
+
);
|
|
1549
|
+
}
|
|
1550
|
+
if (/encoder.*finished|finished encoder/i.test(message)) {
|
|
1551
|
+
return err(
|
|
1552
|
+
new RhiError({
|
|
1553
|
+
code: "command-encoder-finished",
|
|
1554
|
+
expected: "command encoder must not be finished before recording new commands",
|
|
1555
|
+
hint: `GPU validation: ${message}`
|
|
1556
|
+
})
|
|
1557
|
+
);
|
|
1558
|
+
}
|
|
1559
|
+
if (/render pass.*not ended|pass.*not ended/i.test(message)) {
|
|
1560
|
+
return err(
|
|
1561
|
+
new RhiError({
|
|
1562
|
+
code: "render-pass-not-ended",
|
|
1563
|
+
expected: "previous render pass must be ended before beginning a new pass",
|
|
1564
|
+
hint: `GPU validation: ${message}`
|
|
1565
|
+
})
|
|
1566
|
+
);
|
|
1567
|
+
}
|
|
1568
|
+
if (/submit/i.test(message)) {
|
|
1569
|
+
return err(
|
|
1570
|
+
new RhiError({
|
|
1571
|
+
code: "queue-submit-failed",
|
|
1572
|
+
expected: "command buffer references must be valid at submit time",
|
|
1573
|
+
hint: `GPU validation: ${message}`
|
|
1574
|
+
})
|
|
1575
|
+
);
|
|
1576
|
+
}
|
|
1577
|
+
return err(
|
|
1578
|
+
new RhiError({
|
|
1579
|
+
code: "limit-exceeded",
|
|
1580
|
+
expected: "descriptor field values within device limits",
|
|
1581
|
+
hint: `GPU validation: ${message}`
|
|
1582
|
+
})
|
|
1583
|
+
);
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
const repr = typeof event === "object" && event !== null && "toString" in event ? String(event) : "<unknown>";
|
|
1587
|
+
return err(
|
|
1588
|
+
new RhiError({
|
|
1589
|
+
code: "webgpu-runtime-error",
|
|
1590
|
+
expected: "spec-recognised GPUUncapturedErrorEvent or GPUDeviceLostInfo",
|
|
1591
|
+
hint: `unrecognised async-dispatch event: ${repr}`
|
|
1592
|
+
})
|
|
1593
|
+
);
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1596
|
+
// src/index.ts
|
|
1597
|
+
function classifyRequestDeviceError(e) {
|
|
1598
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
1599
|
+
if (/feature/i.test(msg)) return featureNotEnabled();
|
|
1600
|
+
if (/limit/i.test(msg)) return limitExceeded();
|
|
1601
|
+
return featureNotEnabled();
|
|
1602
|
+
}
|
|
1603
|
+
async function requestDevice(opts = {}) {
|
|
1604
|
+
const injected = opts.gpu;
|
|
1605
|
+
const ambient = typeof globalThis !== "undefined" ? globalThis.navigator?.gpu : void 0;
|
|
1606
|
+
const gpu = injected ?? ambient;
|
|
1607
|
+
if (gpu === void 0 || gpu === null) {
|
|
1608
|
+
return adapterUnavailable();
|
|
1609
|
+
}
|
|
1610
|
+
const adapter = await gpu.requestAdapter(opts.adapterOptions);
|
|
1611
|
+
if (adapter === null) {
|
|
1612
|
+
return adapterUnavailable();
|
|
1613
|
+
}
|
|
1614
|
+
let rawDevice;
|
|
1615
|
+
try {
|
|
1616
|
+
rawDevice = await adapter.requestDevice(opts.deviceDescriptor);
|
|
1617
|
+
} catch (e) {
|
|
1618
|
+
return classifyRequestDeviceError(e);
|
|
1619
|
+
}
|
|
1620
|
+
const { device } = makeRhiDevice(rawDevice);
|
|
1621
|
+
return ok(device);
|
|
1622
|
+
}
|
|
1623
|
+
async function createShaderModule(device, desc) {
|
|
1624
|
+
const rawDevice = _internal_getRawDevice(device);
|
|
1625
|
+
if (rawDevice === void 0) {
|
|
1626
|
+
return shaderCompileFailed([
|
|
1627
|
+
{
|
|
1628
|
+
type: "error",
|
|
1629
|
+
message: "rhi-webgpu: createShaderModule called with unregistered RhiDevice",
|
|
1630
|
+
lineNum: 0,
|
|
1631
|
+
linePos: 0,
|
|
1632
|
+
offset: 0,
|
|
1633
|
+
length: 0
|
|
1634
|
+
}
|
|
1635
|
+
]);
|
|
1636
|
+
}
|
|
1637
|
+
const mirrored = { code: desc.code };
|
|
1638
|
+
if ("label" in desc && desc.label !== void 0) mirrored.label = desc.label;
|
|
1639
|
+
let handle;
|
|
1640
|
+
try {
|
|
1641
|
+
handle = rawDevice.createShaderModule(mirrored);
|
|
1642
|
+
} catch (e) {
|
|
1643
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
1644
|
+
return shaderCompileFailed([
|
|
1645
|
+
{
|
|
1646
|
+
type: "error",
|
|
1647
|
+
message,
|
|
1648
|
+
lineNum: 0,
|
|
1649
|
+
linePos: 0,
|
|
1650
|
+
offset: 0,
|
|
1651
|
+
length: 0
|
|
1652
|
+
}
|
|
1653
|
+
]);
|
|
1654
|
+
}
|
|
1655
|
+
const handleWithInfo = handle;
|
|
1656
|
+
if (typeof handleWithInfo.getCompilationInfo !== "function") {
|
|
1657
|
+
return ok(handle);
|
|
1658
|
+
}
|
|
1659
|
+
let info;
|
|
1660
|
+
try {
|
|
1661
|
+
info = await handleWithInfo.getCompilationInfo();
|
|
1662
|
+
} catch {
|
|
1663
|
+
return ok(handle);
|
|
1664
|
+
}
|
|
1665
|
+
const errors = info.messages.filter((m) => m.type === "error");
|
|
1666
|
+
if (errors.length > 0) {
|
|
1667
|
+
return shaderCompileFailed(info.messages);
|
|
1668
|
+
}
|
|
1669
|
+
return ok(handle);
|
|
1670
|
+
}
|
|
1671
|
+
function makeRhiAdapter(rawAdapter) {
|
|
1672
|
+
const rawFeatures = rawAdapter.features;
|
|
1673
|
+
const features = rawFeatures !== void 0 && rawFeatures !== null ? new Set(rawFeatures) : /* @__PURE__ */ new Set();
|
|
1674
|
+
const limitsRaw = rawAdapter.limits ?? {};
|
|
1675
|
+
const limits = {};
|
|
1676
|
+
for (const key in limitsRaw) {
|
|
1677
|
+
const v = limitsRaw[key];
|
|
1678
|
+
if (typeof v === "number") {
|
|
1679
|
+
limits[key] = v;
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
return {
|
|
1683
|
+
features,
|
|
1684
|
+
limits,
|
|
1685
|
+
async requestDevice(opts) {
|
|
1686
|
+
let rawDevice;
|
|
1687
|
+
try {
|
|
1688
|
+
rawDevice = await rawAdapter.requestDevice(
|
|
1689
|
+
opts
|
|
1690
|
+
);
|
|
1691
|
+
} catch (e) {
|
|
1692
|
+
return classifyRequestDeviceError(e);
|
|
1693
|
+
}
|
|
1694
|
+
const { device } = makeRhiDevice(rawDevice);
|
|
1695
|
+
return ok(device);
|
|
1696
|
+
}
|
|
1697
|
+
};
|
|
1698
|
+
}
|
|
1699
|
+
async function requestAdapter(opts, _compatibleSurface) {
|
|
1700
|
+
const ambient = typeof globalThis !== "undefined" ? globalThis.navigator?.gpu : void 0;
|
|
1701
|
+
if (ambient === void 0 || ambient === null) {
|
|
1702
|
+
return adapterUnavailable();
|
|
1703
|
+
}
|
|
1704
|
+
let adapter;
|
|
1705
|
+
try {
|
|
1706
|
+
adapter = await ambient.requestAdapter(opts);
|
|
1707
|
+
} catch (cause) {
|
|
1708
|
+
return requestAdapterFailed(cause);
|
|
1709
|
+
}
|
|
1710
|
+
if (adapter === null) {
|
|
1711
|
+
return adapterUnavailable();
|
|
1712
|
+
}
|
|
1713
|
+
return ok(
|
|
1714
|
+
makeRhiAdapter(
|
|
1715
|
+
adapter
|
|
1716
|
+
)
|
|
1717
|
+
);
|
|
1718
|
+
}
|
|
1719
|
+
function acquireCanvasContext(canvas) {
|
|
1720
|
+
let rawContext;
|
|
1721
|
+
try {
|
|
1722
|
+
rawContext = canvas.getContext("webgpu");
|
|
1723
|
+
} catch {
|
|
1724
|
+
rawContext = null;
|
|
1725
|
+
}
|
|
1726
|
+
if (rawContext === null) {
|
|
1727
|
+
return err(
|
|
1728
|
+
new RhiError({
|
|
1729
|
+
code: "rhi-not-available",
|
|
1730
|
+
expected: 'canvas.getContext("webgpu") to return a non-null GPUCanvasContext',
|
|
1731
|
+
hint: 'canvas does not support WebGPU \u2014 pass an HTMLCanvasElement (or OffscreenCanvas) whose getContext("webgpu") returns a valid GPUCanvasContext'
|
|
1732
|
+
})
|
|
1733
|
+
);
|
|
1734
|
+
}
|
|
1735
|
+
return ok(makeCanvasContext(rawContext));
|
|
1736
|
+
}
|
|
1737
|
+
var rhi = {
|
|
1738
|
+
requestAdapter,
|
|
1739
|
+
createShaderModule,
|
|
1740
|
+
acquireCanvasContext
|
|
1741
|
+
};
|
|
1742
|
+
|
|
1743
|
+
export { _internal_getRawDevice, acquireCanvasContext, createShaderModule, requestAdapter, requestDevice, rhi, translateErrorEventToRhiError };
|
|
1744
|
+
//# sourceMappingURL=index.mjs.map
|
|
1745
|
+
//# sourceMappingURL=index.mjs.map
|