@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.
Files changed (33) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +133 -0
  3. package/dist/.tsbuildinfo +1 -0
  4. package/dist/__tests__/__mocks__/gpu-device.d.ts +226 -0
  5. package/dist/__tests__/__mocks__/gpu-device.d.ts.map +1 -0
  6. package/dist/__tests__/dawn-real-gpu.dawn.test.d.ts +2 -0
  7. package/dist/__tests__/dawn-real-gpu.dawn.test.d.ts.map +1 -0
  8. package/dist/__tests__/rhi-webgpu.unit.test.d.ts +2 -0
  9. package/dist/__tests__/rhi-webgpu.unit.test.d.ts.map +1 -0
  10. package/dist/device.d.ts +62 -0
  11. package/dist/device.d.ts.map +1 -0
  12. package/dist/errors.d.ts +49 -0
  13. package/dist/errors.d.ts.map +1 -0
  14. package/dist/index.d.ts +183 -0
  15. package/dist/index.d.ts.map +1 -0
  16. package/dist/index.mjs +1745 -0
  17. package/dist/index.mjs.map +1 -0
  18. package/dist/internal/__tests__/timestamp-query.unit.test.d.ts +2 -0
  19. package/dist/internal/__tests__/timestamp-query.unit.test.d.ts.map +1 -0
  20. package/dist/internal/error-translation.d.ts +16 -0
  21. package/dist/internal/error-translation.d.ts.map +1 -0
  22. package/dist/internal/timestamp-query.d.ts +15 -0
  23. package/dist/internal/timestamp-query.d.ts.map +1 -0
  24. package/package.json +58 -0
  25. package/src/__tests__/__mocks__/gpu-device.ts +555 -0
  26. package/src/__tests__/dawn-real-gpu.dawn.test.ts +1445 -0
  27. package/src/__tests__/rhi-webgpu.unit.test.ts +2398 -0
  28. package/src/device.ts +2102 -0
  29. package/src/errors.ts +183 -0
  30. package/src/index.ts +597 -0
  31. package/src/internal/__tests__/timestamp-query.unit.test.ts +88 -0
  32. package/src/internal/error-translation.ts +187 -0
  33. 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