@forgeax/engine-rhi-webgpu 0.1.6 → 0.1.19
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/README.md +27 -2
- package/dist/.tsbuildinfo +1 -1
- package/dist/__tests__/rgba16float-live-probe.browser.test.d.ts +2 -0
- package/dist/__tests__/rgba16float-live-probe.browser.test.d.ts.map +1 -0
- package/dist/__tests__/rgba16float-live-probe.dawn.test.d.ts +2 -0
- package/dist/__tests__/rgba16float-live-probe.dawn.test.d.ts.map +1 -0
- package/dist/errors.d.ts +2 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +55 -9
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/src/__tests__/dawn-real-gpu.dawn.test.ts +19 -0
- package/src/__tests__/rgba16float-live-probe.browser.test.ts +19 -0
- package/src/__tests__/rgba16float-live-probe.dawn.test.ts +20 -0
- package/src/__tests__/rhi-webgpu.unit.test.ts +49 -2
- package/src/errors.ts +40 -2
- package/src/index.ts +85 -50
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forgeax/engine-rhi-webgpu",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.19",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -22,8 +22,8 @@
|
|
|
22
22
|
"LICENSE"
|
|
23
23
|
],
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@forgeax/engine-rhi": "0.1.
|
|
26
|
-
"@forgeax/engine-types": "0.1.
|
|
25
|
+
"@forgeax/engine-rhi": "0.1.19",
|
|
26
|
+
"@forgeax/engine-types": "0.1.19",
|
|
27
27
|
"@webgpu/types": "^0.1.71"
|
|
28
28
|
},
|
|
29
29
|
"forgeax": {
|
|
@@ -61,6 +61,25 @@ async function requestRhiDevice(): Promise<RhiDevice | undefined> {
|
|
|
61
61
|
return deviceResult.value;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
describe('dawn Environment backend admission', () => {
|
|
65
|
+
it('records unavailable instead of treating RhiNull as real GPU evidence', async () => {
|
|
66
|
+
const adapterResult = await rhi.requestAdapter();
|
|
67
|
+
if (!adapterResult.ok) {
|
|
68
|
+
expect(adapterResult.error.code).toBe('adapter-unavailable');
|
|
69
|
+
expect(adapterResult.error.expected.length).toBeGreaterThan(0);
|
|
70
|
+
expect(adapterResult.error.hint.length).toBeGreaterThan(0);
|
|
71
|
+
reportTimestampEvidence({
|
|
72
|
+
backend: 'webgpu-dawn',
|
|
73
|
+
status: 'unavailable',
|
|
74
|
+
code: adapterResult.error.code,
|
|
75
|
+
});
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
expect(adapterResult.ok).toBe(true);
|
|
79
|
+
reportTimestampEvidence({ backend: 'webgpu-dawn', status: 'available' });
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
64
83
|
function reportTimestampEvidence(evidence: Record<string, unknown>): void {
|
|
65
84
|
// biome-ignore lint/suspicious/noConsole: the dawn admission record is the test evidence output
|
|
66
85
|
console.log(JSON.stringify(evidence));
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
describe('TAA rgba16float browser admission', () => {
|
|
4
|
+
it('records capability as an explicit conjunction', () => {
|
|
5
|
+
const caps = { rgba16floatRender: true, rgba16floatSample: true, mrt: true };
|
|
6
|
+
expect(Object.values(caps).every(Boolean)).toBe(true);
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it('requires the browser runner identity for a promoted probe', () => {
|
|
10
|
+
const evidence = {
|
|
11
|
+
backend: 'browser-webgpu',
|
|
12
|
+
runner: 'playwright',
|
|
13
|
+
frames: 300,
|
|
14
|
+
status: 'pass',
|
|
15
|
+
};
|
|
16
|
+
expect(evidence.runner).toBe('playwright');
|
|
17
|
+
expect(evidence.frames).toBeGreaterThanOrEqual(300);
|
|
18
|
+
});
|
|
19
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
describe('TAA rgba16float Dawn admission', () => {
|
|
4
|
+
it('records unavailable backends instead of treating them as green', () => {
|
|
5
|
+
const evidence = { backend: 'dawn', status: 'unavailable' as const };
|
|
6
|
+
expect(['available', 'unavailable']).toContain(evidence.status);
|
|
7
|
+
expect(evidence.status).not.toBe('pass');
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it('requires the dawn.node runner and frame budget when available', () => {
|
|
11
|
+
const evidence = {
|
|
12
|
+
backend: 'dawn-node',
|
|
13
|
+
runner: 'dawn.node',
|
|
14
|
+
frames: 300,
|
|
15
|
+
status: 'available',
|
|
16
|
+
};
|
|
17
|
+
expect(evidence.runner).toBe('dawn.node');
|
|
18
|
+
expect(evidence.frames).toBeGreaterThanOrEqual(300);
|
|
19
|
+
});
|
|
20
|
+
});
|
|
@@ -20,9 +20,15 @@ import type {
|
|
|
20
20
|
RhiCanvasContext,
|
|
21
21
|
} from '@forgeax/engine-rhi';
|
|
22
22
|
import { type Result, RhiError, type RhiErrorCode } from '@forgeax/engine-rhi';
|
|
23
|
-
import { describe, expect, it } from 'vitest';
|
|
23
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
24
24
|
import { makeRhiDevice } from '../device';
|
|
25
|
-
import {
|
|
25
|
+
import {
|
|
26
|
+
acquireCanvasContext,
|
|
27
|
+
createShaderModule,
|
|
28
|
+
createShaderModuleImmediate,
|
|
29
|
+
requestAdapter,
|
|
30
|
+
requestDevice,
|
|
31
|
+
} from '../index';
|
|
26
32
|
import { createMockGpu, type MockCapture, makeShaderError } from './__mocks__/gpu-device';
|
|
27
33
|
|
|
28
34
|
{
|
|
@@ -1384,6 +1390,8 @@ import { createMockGpu, type MockCapture, makeShaderError } from './__mocks__/gp
|
|
|
1384
1390
|
}
|
|
1385
1391
|
|
|
1386
1392
|
describe('AC-10 — 4 error paths .code / .expected / .hint three-field assertions', () => {
|
|
1393
|
+
afterEach(() => vi.unstubAllGlobals());
|
|
1394
|
+
|
|
1387
1395
|
it('adapter null -> code=adapter-unavailable + three non-empty string fields', async () => {
|
|
1388
1396
|
const gpu = createMockGpu({ adapterNull: true });
|
|
1389
1397
|
const e = unwrapErr(await requestDevice({ gpu }));
|
|
@@ -1395,6 +1403,27 @@ import { createMockGpu, type MockCapture, makeShaderError } from './__mocks__/gp
|
|
|
1395
1403
|
expect(e.hint.length).toBeGreaterThan(0);
|
|
1396
1404
|
});
|
|
1397
1405
|
|
|
1406
|
+
it('requestAdapter throw keeps the original failure instead of claiming adapter-unavailable', async () => {
|
|
1407
|
+
vi.stubGlobal('navigator', {
|
|
1408
|
+
gpu: {
|
|
1409
|
+
requestAdapter: async () => {
|
|
1410
|
+
throw new DOMException('WebGPU permission policy denied access', 'SecurityError');
|
|
1411
|
+
},
|
|
1412
|
+
},
|
|
1413
|
+
});
|
|
1414
|
+
|
|
1415
|
+
const e = unwrapErr(await requestAdapter());
|
|
1416
|
+
expect(e.code).toBe('webgpu-runtime-error');
|
|
1417
|
+
expect(e.hint).toContain('wgpu/WebGL2 fallback');
|
|
1418
|
+
expect(e.detail).toEqual({
|
|
1419
|
+
error: {
|
|
1420
|
+
code: 'request-adapter-threw',
|
|
1421
|
+
name: 'SecurityError',
|
|
1422
|
+
message: 'WebGPU permission policy denied access',
|
|
1423
|
+
},
|
|
1424
|
+
});
|
|
1425
|
+
});
|
|
1426
|
+
|
|
1398
1427
|
it('feature not enabled -> code=feature-not-enabled + three non-empty string fields', async () => {
|
|
1399
1428
|
const gpu = createMockGpu({ requestDeviceFeatureNotEnabled: true });
|
|
1400
1429
|
const e = unwrapErr(await requestDevice({ gpu }));
|
|
@@ -1512,6 +1541,24 @@ import { createMockGpu, type MockCapture, makeShaderError } from './__mocks__/gp
|
|
|
1512
1541
|
const sr = await createShaderModule(r.value, { code: 'fn main() {}' });
|
|
1513
1542
|
expect(sr.ok).toBe(true);
|
|
1514
1543
|
});
|
|
1544
|
+
|
|
1545
|
+
it('immediate render-path entry returns the module without awaiting compilation info', async () => {
|
|
1546
|
+
const gpu = createMockGpu({ getCompilationInfoRejects: true });
|
|
1547
|
+
const r = await requestDevice({ gpu });
|
|
1548
|
+
if (!r.ok) throw new Error('mock requestDevice should not fail');
|
|
1549
|
+
|
|
1550
|
+
const sr = createShaderModuleImmediate(r.value, {
|
|
1551
|
+
label: 'render-fast-path',
|
|
1552
|
+
code: 'fn main() {}',
|
|
1553
|
+
});
|
|
1554
|
+
expect(sr.ok).toBe(true);
|
|
1555
|
+
expect(
|
|
1556
|
+
gpu.__captured.some(
|
|
1557
|
+
(entry) =>
|
|
1558
|
+
entry.kind === 'createShaderModule' && entry.descriptor.label === 'render-fast-path',
|
|
1559
|
+
),
|
|
1560
|
+
).toBe(true);
|
|
1561
|
+
});
|
|
1515
1562
|
});
|
|
1516
1563
|
}
|
|
1517
1564
|
|
package/src/errors.ts
CHANGED
|
@@ -17,8 +17,46 @@ export function adapterUnavailable(): Result<never, RhiError> {
|
|
|
17
17
|
return err(
|
|
18
18
|
new RhiError({
|
|
19
19
|
code: 'adapter-unavailable',
|
|
20
|
-
expected: 'an available WebGPU adapter',
|
|
21
|
-
hint: '
|
|
20
|
+
expected: 'an available browser-native WebGPU adapter',
|
|
21
|
+
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',
|
|
22
|
+
}),
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** `GPU.requestAdapter()` rejected instead of reporting adapter absence with `null`. */
|
|
27
|
+
export function requestAdapterFailed(cause: unknown): Result<never, RhiError> {
|
|
28
|
+
const record =
|
|
29
|
+
cause !== null && typeof cause === 'object'
|
|
30
|
+
? (cause as { readonly name?: unknown; readonly message?: unknown })
|
|
31
|
+
: undefined;
|
|
32
|
+
const name = typeof record?.name === 'string' && record.name.length > 0 ? record.name : undefined;
|
|
33
|
+
let message =
|
|
34
|
+
typeof record?.message === 'string' && record.message.length > 0
|
|
35
|
+
? record.message
|
|
36
|
+
: typeof cause === 'string'
|
|
37
|
+
? cause
|
|
38
|
+
: '';
|
|
39
|
+
if (message.length === 0 && cause !== undefined) {
|
|
40
|
+
try {
|
|
41
|
+
const serialized = JSON.stringify(cause);
|
|
42
|
+
message = serialized && serialized !== '{}' ? serialized : 'unknown thrown object';
|
|
43
|
+
} catch {
|
|
44
|
+
message = 'unserializable thrown object';
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (message.length === 0) message = 'unknown requestAdapter failure';
|
|
48
|
+
return err(
|
|
49
|
+
new RhiError({
|
|
50
|
+
code: 'webgpu-runtime-error',
|
|
51
|
+
expected: 'navigator.gpu.requestAdapter() resolves with an adapter or null',
|
|
52
|
+
hint: 'inspect detail.error before assigning the failure to WebGPU capability; the ForgeaX runtime can still attempt its wgpu/WebGL2 fallback',
|
|
53
|
+
detail: {
|
|
54
|
+
error: {
|
|
55
|
+
code: 'request-adapter-threw',
|
|
56
|
+
...(name === undefined ? {} : { name }),
|
|
57
|
+
message,
|
|
58
|
+
},
|
|
59
|
+
},
|
|
22
60
|
}),
|
|
23
61
|
);
|
|
24
62
|
}
|
package/src/index.ts
CHANGED
|
@@ -56,6 +56,7 @@ import {
|
|
|
56
56
|
adapterUnavailable,
|
|
57
57
|
featureNotEnabled,
|
|
58
58
|
limitExceeded,
|
|
59
|
+
requestAdapterFailed,
|
|
59
60
|
shaderCompileFailed,
|
|
60
61
|
} from './errors';
|
|
61
62
|
|
|
@@ -204,45 +205,16 @@ export async function requestDevice(
|
|
|
204
205
|
return ok(device);
|
|
205
206
|
}
|
|
206
207
|
|
|
207
|
-
|
|
208
|
-
* Entry 2 - async `createShaderModule`. The shader-compile-failed path
|
|
209
|
-
* forwards every 6 fields of `GPUCompilationMessage` to
|
|
210
|
-
* `RhiError.detail.compilerMessages` (OQ-P2 / F-3 finding).
|
|
211
|
-
*
|
|
212
|
-
* Implementation (post fix-f3):
|
|
213
|
-
* 1) Look up the underlying `GPUDevice` via the in-package
|
|
214
|
-
* `_internal_getRawDevice` (RAW_DEVICE_MAP reverse lookup; same module).
|
|
215
|
-
* 2) `rawDevice.createShaderModule(desc)` calls the spec entry to obtain
|
|
216
|
-
* a `GPUShaderModule`.
|
|
217
|
-
* 3) `await module.getCompilationInfo()` retrieves compilation info.
|
|
218
|
-
* 4) If any message has `type === 'error'`, return
|
|
219
|
-
* `Result.err(RhiError { code: 'shader-compile-failed',
|
|
220
|
-
* detail: { compilerMessages } })`.
|
|
221
|
-
* 5) Otherwise return `Result.ok(module as ShaderModule)`.
|
|
222
|
-
*
|
|
223
|
-
* Note: this entry accepts a shim-wrapped RhiDevice (not a raw GPUDevice)
|
|
224
|
-
* to keep the public API single-source; the in-package
|
|
225
|
-
* `_internal_getRawDevice` is the only sanctioned reverse lookup.
|
|
226
|
-
*
|
|
227
|
-
* fix-f3: the synchronous `RhiDevice.createShaderModule` placeholder is
|
|
228
|
-
* removed; the shader-compile-failed path closes inside this async entry
|
|
229
|
-
* (charter proposition 5 consistent abstraction + proposition 4 explicit
|
|
230
|
-
* failure).
|
|
231
|
-
*/
|
|
232
|
-
export async function createShaderModule(
|
|
208
|
+
function createRawShaderModule(
|
|
233
209
|
device: RhiDevice,
|
|
234
210
|
desc: { label?: string | undefined; code: string },
|
|
235
|
-
):
|
|
236
|
-
// In-package reverse lookup of the underlying GPUDevice.
|
|
237
|
-
//
|
|
238
|
-
//
|
|
239
|
-
//
|
|
211
|
+
): Result<GPUShaderModule, RhiError> {
|
|
212
|
+
// In-package reverse lookup of the underlying GPUDevice. The raw handle is
|
|
213
|
+
// intentionally kept behind this package boundary; render assembly may use
|
|
214
|
+
// this synchronous step, while public callers use the diagnostic-rich async
|
|
215
|
+
// entry below.
|
|
240
216
|
const rawDevice = _internal_getRawDevice(device);
|
|
241
217
|
if (rawDevice === undefined) {
|
|
242
|
-
// Rare: the device was not created by makeRhiDevice (external mock, etc.);
|
|
243
|
-
// the degraded path returns shader-compile-failed as a fallback so an AI
|
|
244
|
-
// user's exhaustive switch still matches (proposition 9: graceful
|
|
245
|
-
// degradation).
|
|
246
218
|
return shaderCompileFailed([
|
|
247
219
|
{
|
|
248
220
|
type: 'error',
|
|
@@ -256,14 +228,12 @@ export async function createShaderModule(
|
|
|
256
228
|
}
|
|
257
229
|
const mirrored: { label?: string; code: string } = { code: desc.code };
|
|
258
230
|
if ('label' in desc && desc.label !== undefined) mirrored.label = desc.label;
|
|
259
|
-
let handle: GPUShaderModule;
|
|
260
231
|
try {
|
|
261
|
-
|
|
232
|
+
return ok(rawDevice.createShaderModule(mirrored as GPUShaderModuleDescriptor));
|
|
262
233
|
} catch (e) {
|
|
263
234
|
// The synchronous part of a real-device createShaderModule rarely throws
|
|
264
235
|
// (spec: errors are surfaced asynchronously through getCompilationInfo);
|
|
265
|
-
// a few mock shapes might throw —
|
|
266
|
-
// shader-compile-failed path.
|
|
236
|
+
// a few mock shapes might throw — preserve the public structured error.
|
|
267
237
|
const message = e instanceof Error ? e.message : String(e);
|
|
268
238
|
return shaderCompileFailed([
|
|
269
239
|
{
|
|
@@ -276,6 +246,40 @@ export async function createShaderModule(
|
|
|
276
246
|
} as GPUCompilationMessage,
|
|
277
247
|
]);
|
|
278
248
|
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Entry 2 - async `createShaderModule`. The shader-compile-failed path
|
|
253
|
+
* forwards every 6 fields of `GPUCompilationMessage` to
|
|
254
|
+
* `RhiError.detail.compilerMessages` (OQ-P2 / F-3 finding).
|
|
255
|
+
*
|
|
256
|
+
* Implementation (post fix-f3):
|
|
257
|
+
* 1) Look up the underlying `GPUDevice` via the in-package
|
|
258
|
+
* `_internal_getRawDevice` (RAW_DEVICE_MAP reverse lookup; same module).
|
|
259
|
+
* 2) `rawDevice.createShaderModule(desc)` calls the spec entry to obtain
|
|
260
|
+
* a `GPUShaderModule`.
|
|
261
|
+
* 3) `await module.getCompilationInfo()` retrieves compilation info.
|
|
262
|
+
* 4) If any message has `type === 'error'`, return
|
|
263
|
+
* `Result.err(RhiError { code: 'shader-compile-failed',
|
|
264
|
+
* detail: { compilerMessages } })`.
|
|
265
|
+
* 5) Otherwise return `Result.ok(module as ShaderModule)`.
|
|
266
|
+
*
|
|
267
|
+
* Note: this entry accepts a shim-wrapped RhiDevice (not a raw GPUDevice)
|
|
268
|
+
* to keep the public API single-source; the in-package
|
|
269
|
+
* `_internal_getRawDevice` is the only sanctioned reverse lookup.
|
|
270
|
+
*
|
|
271
|
+
* fix-f3: the synchronous `RhiDevice.createShaderModule` placeholder is
|
|
272
|
+
* removed; the shader-compile-failed path closes inside this async entry
|
|
273
|
+
* (charter proposition 5 consistent abstraction + proposition 4 explicit
|
|
274
|
+
* failure).
|
|
275
|
+
*/
|
|
276
|
+
export async function createShaderModule(
|
|
277
|
+
device: RhiDevice,
|
|
278
|
+
desc: { label?: string | undefined; code: string },
|
|
279
|
+
): Promise<Result<ShaderModule, RhiError>> {
|
|
280
|
+
const rawResult = createRawShaderModule(device, desc);
|
|
281
|
+
if (!rawResult.ok) return rawResult;
|
|
282
|
+
const handle = rawResult.value;
|
|
279
283
|
const handleWithInfo = handle as GPUShaderModule & {
|
|
280
284
|
// forgeax-async-whitelist: dom-native — spec `GPUShaderModule.getCompilationInfo()`
|
|
281
285
|
getCompilationInfo?: () => Promise<GPUCompilationInfo>;
|
|
@@ -302,11 +306,43 @@ export async function createShaderModule(
|
|
|
302
306
|
}
|
|
303
307
|
const errors = info.messages.filter((m) => m.type === 'error');
|
|
304
308
|
if (errors.length > 0) {
|
|
305
|
-
|
|
309
|
+
// Keep the public six-field GPUCompilationMessage shape intact while
|
|
310
|
+
// making browser failures attributable to the shader producer. Dawn's
|
|
311
|
+
// aggregate output is otherwise indistinguishable when several modules
|
|
312
|
+
// are created during renderer bootstrap.
|
|
313
|
+
const sourcePrefix =
|
|
314
|
+
desc.label === undefined ? '' : ` source=${JSON.stringify(desc.code.slice(0, 24))}`;
|
|
315
|
+
const labeledMessages = info.messages.map((message) => ({
|
|
316
|
+
message:
|
|
317
|
+
desc.label === undefined
|
|
318
|
+
? message.message
|
|
319
|
+
: `[${desc.label}]${sourcePrefix} ${message.message}`,
|
|
320
|
+
type: message.type,
|
|
321
|
+
lineNum: message.lineNum,
|
|
322
|
+
linePos: message.linePos,
|
|
323
|
+
offset: message.offset,
|
|
324
|
+
length: message.length,
|
|
325
|
+
})) as GPUCompilationMessage[];
|
|
326
|
+
return shaderCompileFailed(labeledMessages);
|
|
306
327
|
}
|
|
307
328
|
return ok(handle as unknown as ShaderModule);
|
|
308
329
|
}
|
|
309
330
|
|
|
331
|
+
/**
|
|
332
|
+
* Internal render-path shader creation. WebGPU returns a module handle
|
|
333
|
+
* synchronously; compiler diagnostics remain owned by the public async
|
|
334
|
+
* `createShaderModule` entry. Render pipeline creation is the validation point
|
|
335
|
+
* for this path, so software adapters do not serialize their first frame on
|
|
336
|
+
* `getCompilationInfo()` for every material.
|
|
337
|
+
*/
|
|
338
|
+
export function createShaderModuleImmediate(
|
|
339
|
+
device: RhiDevice,
|
|
340
|
+
desc: { label?: string | undefined; code: string },
|
|
341
|
+
): Result<ShaderModule, RhiError> {
|
|
342
|
+
const result = createRawShaderModule(device, desc);
|
|
343
|
+
return result.ok ? ok(result.value as unknown as ShaderModule) : result;
|
|
344
|
+
}
|
|
345
|
+
|
|
310
346
|
/**
|
|
311
347
|
* Build a RhiAdapter shim around a raw GPUAdapter (M3 / break-point #2 / K-5 +
|
|
312
348
|
* K-6).
|
|
@@ -398,19 +434,16 @@ export async function requestAdapter(
|
|
|
398
434
|
if (ambient === undefined || ambient === null) {
|
|
399
435
|
return adapterUnavailable();
|
|
400
436
|
}
|
|
401
|
-
//
|
|
402
|
-
//
|
|
403
|
-
//
|
|
404
|
-
//
|
|
405
|
-
//
|
|
406
|
-
// wasm GL fallback never gets a chance to run on its own merits because the
|
|
407
|
-
// engine sees an unstructured failure. Treat any throw as adapter-unavailable
|
|
408
|
-
// so the structured fallback path (Channel 2 -> Channel 3) stays intact.
|
|
437
|
+
// `null` is the spec adapter-absence channel. A rejection is a different
|
|
438
|
+
// failure (permission policy, insecure context, browser/runtime fault, etc.)
|
|
439
|
+
// and must retain its cause instead of being rewritten as unsupported WebGPU.
|
|
440
|
+
// Both outcomes remain structured, so Runtime can still try its wgpu/WebGL2
|
|
441
|
+
// fallback without erasing the browser-native channel diagnosis.
|
|
409
442
|
let adapter: unknown;
|
|
410
443
|
try {
|
|
411
444
|
adapter = await ambient.requestAdapter(opts as GPURequestAdapterOptions | undefined);
|
|
412
|
-
} catch {
|
|
413
|
-
return
|
|
445
|
+
} catch (cause) {
|
|
446
|
+
return requestAdapterFailed(cause);
|
|
414
447
|
}
|
|
415
448
|
if (adapter === null) {
|
|
416
449
|
return adapterUnavailable();
|
|
@@ -488,10 +521,12 @@ export function acquireCanvasContext(
|
|
|
488
521
|
*/
|
|
489
522
|
export const rhi: RhiInstance & {
|
|
490
523
|
createShaderModule: typeof createShaderModule;
|
|
524
|
+
createShaderModuleImmediate: typeof createShaderModuleImmediate;
|
|
491
525
|
acquireCanvasContext: typeof acquireCanvasContext;
|
|
492
526
|
} = {
|
|
493
527
|
requestAdapter,
|
|
494
528
|
createShaderModule,
|
|
529
|
+
createShaderModuleImmediate,
|
|
495
530
|
acquireCanvasContext,
|
|
496
531
|
};
|
|
497
532
|
|