@forgeax/engine-rhi-webgpu 0.1.6 → 0.1.7
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 +9 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/errors.d.ts +2 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +32 -4
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/src/__tests__/rhi-webgpu.unit.test.ts +25 -2
- package/src/errors.ts +40 -2
- package/src/index.ts +8 -10
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.7",
|
|
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.7",
|
|
26
|
+
"@forgeax/engine-types": "0.1.7",
|
|
27
27
|
"@webgpu/types": "^0.1.71"
|
|
28
28
|
},
|
|
29
29
|
"forgeax": {
|
|
@@ -20,9 +20,9 @@ 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 { acquireCanvasContext, createShaderModule, requestDevice } from '../index';
|
|
25
|
+
import { acquireCanvasContext, createShaderModule, requestAdapter, requestDevice } from '../index';
|
|
26
26
|
import { createMockGpu, type MockCapture, makeShaderError } from './__mocks__/gpu-device';
|
|
27
27
|
|
|
28
28
|
{
|
|
@@ -1384,6 +1384,8 @@ import { createMockGpu, type MockCapture, makeShaderError } from './__mocks__/gp
|
|
|
1384
1384
|
}
|
|
1385
1385
|
|
|
1386
1386
|
describe('AC-10 — 4 error paths .code / .expected / .hint three-field assertions', () => {
|
|
1387
|
+
afterEach(() => vi.unstubAllGlobals());
|
|
1388
|
+
|
|
1387
1389
|
it('adapter null -> code=adapter-unavailable + three non-empty string fields', async () => {
|
|
1388
1390
|
const gpu = createMockGpu({ adapterNull: true });
|
|
1389
1391
|
const e = unwrapErr(await requestDevice({ gpu }));
|
|
@@ -1395,6 +1397,27 @@ import { createMockGpu, type MockCapture, makeShaderError } from './__mocks__/gp
|
|
|
1395
1397
|
expect(e.hint.length).toBeGreaterThan(0);
|
|
1396
1398
|
});
|
|
1397
1399
|
|
|
1400
|
+
it('requestAdapter throw keeps the original failure instead of claiming adapter-unavailable', async () => {
|
|
1401
|
+
vi.stubGlobal('navigator', {
|
|
1402
|
+
gpu: {
|
|
1403
|
+
requestAdapter: async () => {
|
|
1404
|
+
throw new DOMException('WebGPU permission policy denied access', 'SecurityError');
|
|
1405
|
+
},
|
|
1406
|
+
},
|
|
1407
|
+
});
|
|
1408
|
+
|
|
1409
|
+
const e = unwrapErr(await requestAdapter());
|
|
1410
|
+
expect(e.code).toBe('webgpu-runtime-error');
|
|
1411
|
+
expect(e.hint).toContain('wgpu/WebGL2 fallback');
|
|
1412
|
+
expect(e.detail).toEqual({
|
|
1413
|
+
error: {
|
|
1414
|
+
code: 'request-adapter-threw',
|
|
1415
|
+
name: 'SecurityError',
|
|
1416
|
+
message: 'WebGPU permission policy denied access',
|
|
1417
|
+
},
|
|
1418
|
+
});
|
|
1419
|
+
});
|
|
1420
|
+
|
|
1398
1421
|
it('feature not enabled -> code=feature-not-enabled + three non-empty string fields', async () => {
|
|
1399
1422
|
const gpu = createMockGpu({ requestDeviceFeatureNotEnabled: true });
|
|
1400
1423
|
const e = unwrapErr(await requestDevice({ gpu }));
|
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
|
|
|
@@ -398,19 +399,16 @@ export async function requestAdapter(
|
|
|
398
399
|
if (ambient === undefined || ambient === null) {
|
|
399
400
|
return adapterUnavailable();
|
|
400
401
|
}
|
|
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.
|
|
402
|
+
// `null` is the spec adapter-absence channel. A rejection is a different
|
|
403
|
+
// failure (permission policy, insecure context, browser/runtime fault, etc.)
|
|
404
|
+
// and must retain its cause instead of being rewritten as unsupported WebGPU.
|
|
405
|
+
// Both outcomes remain structured, so Runtime can still try its wgpu/WebGL2
|
|
406
|
+
// fallback without erasing the browser-native channel diagnosis.
|
|
409
407
|
let adapter: unknown;
|
|
410
408
|
try {
|
|
411
409
|
adapter = await ambient.requestAdapter(opts as GPURequestAdapterOptions | undefined);
|
|
412
|
-
} catch {
|
|
413
|
-
return
|
|
410
|
+
} catch (cause) {
|
|
411
|
+
return requestAdapterFailed(cause);
|
|
414
412
|
}
|
|
415
413
|
if (adapter === null) {
|
|
416
414
|
return adapterUnavailable();
|