@forgeax/engine-rhi-webgpu 0.0.0-dev.8d955ade1c79
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/README.md +133 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/__tests__/__mocks__/gpu-device.d.ts +226 -0
- package/dist/__tests__/__mocks__/gpu-device.d.ts.map +1 -0
- package/dist/__tests__/dawn-real-gpu.dawn.test.d.ts +2 -0
- package/dist/__tests__/dawn-real-gpu.dawn.test.d.ts.map +1 -0
- package/dist/__tests__/rhi-webgpu.unit.test.d.ts +2 -0
- package/dist/__tests__/rhi-webgpu.unit.test.d.ts.map +1 -0
- package/dist/device.d.ts +62 -0
- package/dist/device.d.ts.map +1 -0
- package/dist/errors.d.ts +49 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/index.d.ts +183 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.mjs +1745 -0
- package/dist/index.mjs.map +1 -0
- package/dist/internal/__tests__/timestamp-query.unit.test.d.ts +2 -0
- package/dist/internal/__tests__/timestamp-query.unit.test.d.ts.map +1 -0
- package/dist/internal/error-translation.d.ts +16 -0
- package/dist/internal/error-translation.d.ts.map +1 -0
- package/dist/internal/timestamp-query.d.ts +15 -0
- package/dist/internal/timestamp-query.d.ts.map +1 -0
- package/package.json +58 -0
- package/src/__tests__/__mocks__/gpu-device.ts +555 -0
- package/src/__tests__/dawn-real-gpu.dawn.test.ts +1445 -0
- package/src/__tests__/rhi-webgpu.unit.test.ts +2398 -0
- package/src/device.ts +2102 -0
- package/src/errors.ts +183 -0
- package/src/index.ts +597 -0
- package/src/internal/__tests__/timestamp-query.unit.test.ts +88 -0
- package/src/internal/error-translation.ts +187 -0
- package/src/internal/timestamp-query.ts +59 -0
package/src/errors.ts
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
// @forgeax/engine-rhi-webgpu/src/errors — RhiError factory functions.
|
|
2
|
+
//
|
|
3
|
+
// Source of truth for the .expected / .hint copy: plan-strategy 7.3 error-info
|
|
4
|
+
// table. After feat-20260508-rhi-surface-completion w7 (D-S3) the surface
|
|
5
|
+
// covers 8 factories: 4 device/shader paths (Round 1) + 4 command/queue paths
|
|
6
|
+
// (this closure: command-encoder-finished / render-pass-not-ended /
|
|
7
|
+
// queue-submit-failed / queue-write-buffer-out-of-bounds).
|
|
8
|
+
//
|
|
9
|
+
// Each .expected / .hint string aligns one-to-one with requirements boundary
|
|
10
|
+
// cases + plan-strategy 7.3 (charter proposition 3: machine-readable hint
|
|
11
|
+
// over prose).
|
|
12
|
+
|
|
13
|
+
import { err, type Result, RhiError, type RhiShaderCompileDetail } from '@forgeax/engine-rhi';
|
|
14
|
+
|
|
15
|
+
/** adapter null path (research F-5 single null channel). */
|
|
16
|
+
export function adapterUnavailable(): Result<never, RhiError> {
|
|
17
|
+
return err(
|
|
18
|
+
new RhiError({
|
|
19
|
+
code: 'adapter-unavailable',
|
|
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
|
+
},
|
|
60
|
+
}),
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** feature not enabled path (boundary cases / requirements). */
|
|
65
|
+
export function featureNotEnabled(featureName?: string | undefined): Result<never, RhiError> {
|
|
66
|
+
const fname = featureName ?? 'compute';
|
|
67
|
+
return err(
|
|
68
|
+
new RhiError({
|
|
69
|
+
code: 'feature-not-enabled',
|
|
70
|
+
expected: `feature ${fname} to be enabled`,
|
|
71
|
+
hint: `verify device.features.${fname} before calling this entry point`,
|
|
72
|
+
}),
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** limit exceeded path (boundary cases / requirements). */
|
|
77
|
+
export function limitExceeded(limitName?: string | undefined): Result<never, RhiError> {
|
|
78
|
+
const lname = limitName ?? 'maxBindGroups';
|
|
79
|
+
return err(
|
|
80
|
+
new RhiError({
|
|
81
|
+
code: 'limit-exceeded',
|
|
82
|
+
expected: `${lname} to be within bounds`,
|
|
83
|
+
hint: `verify device.limits.${lname}`,
|
|
84
|
+
}),
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** shader compile failed path + detail.compilerMessages forwarding (OQ-P2 6 fields). */
|
|
89
|
+
export function shaderCompileFailed(
|
|
90
|
+
compilerMessages: readonly GPUCompilationMessage[],
|
|
91
|
+
): Result<never, RhiError> {
|
|
92
|
+
const detail: RhiShaderCompileDetail = { compilerMessages };
|
|
93
|
+
return err(
|
|
94
|
+
new RhiError({
|
|
95
|
+
code: 'shader-compile-failed',
|
|
96
|
+
expected: 'valid WGSL source',
|
|
97
|
+
hint: 'inspect RhiError.detail.compilerMessages (each entry: { message, type, lineNum, linePos, offset, length } per WebGPU GPUCompilationMessage shape)',
|
|
98
|
+
detail,
|
|
99
|
+
}),
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Command encoder reused after finish() (W3C WebGPU 22 GPUCommandEncoder lifecycle).
|
|
105
|
+
*
|
|
106
|
+
* Trigger: encoder.beginRenderPass / copyXxx / finish called after a prior finish().
|
|
107
|
+
* Distinct from 'rhi-not-available': this is a real-path validation failure,
|
|
108
|
+
* not a placeholder for unimplemented surface (plan-strategy D-S3 template 1).
|
|
109
|
+
*/
|
|
110
|
+
export function commandEncoderFinished(): Result<never, RhiError> {
|
|
111
|
+
return err(
|
|
112
|
+
new RhiError({
|
|
113
|
+
code: 'command-encoder-finished',
|
|
114
|
+
expected: 'command encoder must not be finished before recording new commands',
|
|
115
|
+
hint: 'create a new command encoder via device.createCommandEncoder() for each frame; do not reuse a finished encoder',
|
|
116
|
+
}),
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Render pass not ended before next pass / finish (W3C WebGPU 22.7 Render pass).
|
|
122
|
+
*
|
|
123
|
+
* Trigger: encoder.beginRenderPass while previous pass active, or encoder.finish
|
|
124
|
+
* with active pass still recording (plan-strategy D-S3 template 2).
|
|
125
|
+
*/
|
|
126
|
+
export function renderPassNotEnded(): Result<never, RhiError> {
|
|
127
|
+
return err(
|
|
128
|
+
new RhiError({
|
|
129
|
+
code: 'render-pass-not-ended',
|
|
130
|
+
expected:
|
|
131
|
+
'previous render pass must be ended before beginning a new pass or finishing the encoder',
|
|
132
|
+
hint: 'call pass.end() before beginRenderPass() or encoder.finish()',
|
|
133
|
+
}),
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Queue.submit real-path failure (W3C WebGPU 23 Queue).
|
|
139
|
+
*
|
|
140
|
+
* Trigger: submit([cb]) with destroyed buffer/pipeline references, or GPU validation
|
|
141
|
+
* error fan-out via onuncapturederror. Explicitly distinct from 'rhi-not-available'
|
|
142
|
+
* (device-lost subclass) - submit-failed signals dynamic resource life-cycle issues
|
|
143
|
+
* the AI user can self-recover from (plan-strategy D-S3 template 3).
|
|
144
|
+
*/
|
|
145
|
+
export function queueSubmitFailed(detailMessage?: string | undefined): Result<never, RhiError> {
|
|
146
|
+
const baseHint =
|
|
147
|
+
'check if any referenced buffer / pipeline / texture has been destroyed before submit';
|
|
148
|
+
const hint =
|
|
149
|
+
detailMessage !== undefined && detailMessage.length > 0
|
|
150
|
+
? `${baseHint}; underlying GPU error: ${detailMessage}`
|
|
151
|
+
: baseHint;
|
|
152
|
+
return err(
|
|
153
|
+
new RhiError({
|
|
154
|
+
code: 'queue-submit-failed',
|
|
155
|
+
expected:
|
|
156
|
+
'command buffer references must be valid at submit time (not destroyed; not from a different device)',
|
|
157
|
+
hint,
|
|
158
|
+
}),
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Queue.writeBuffer offset/size out of bounds (W3C WebGPU 23.2 writeBuffer).
|
|
164
|
+
*
|
|
165
|
+
* Trigger: writeBuffer(buf, offset, data) where offset is not 4-byte aligned, or
|
|
166
|
+
* offset + data.byteLength exceeds buffer.size. Distinct from 'limit-exceeded'
|
|
167
|
+
* (static device.limits) - out-of-bounds is a dynamic per-buffer boundary
|
|
168
|
+
* (plan-strategy D-S3 template 4).
|
|
169
|
+
*/
|
|
170
|
+
export function queueWriteBufferOutOfBounds(args: {
|
|
171
|
+
offset: number;
|
|
172
|
+
byteLength: number;
|
|
173
|
+
bufferSize: number;
|
|
174
|
+
}): Result<never, RhiError> {
|
|
175
|
+
return err(
|
|
176
|
+
new RhiError({
|
|
177
|
+
code: 'queue-write-buffer-out-of-bounds',
|
|
178
|
+
expected:
|
|
179
|
+
'writeBuffer offset + data.byteLength must be <= buffer.size; offset must be 4-byte aligned',
|
|
180
|
+
hint: `verify offset alignment and bounds: offset (got ${args.offset}) + data.byteLength (got ${args.byteLength}) must be <= buffer.size (got ${args.bufferSize})`,
|
|
181
|
+
}),
|
|
182
|
+
);
|
|
183
|
+
}
|