@motion-core/motion-gpu 0.4.0 → 0.4.2
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/dist/advanced.d.ts +1 -0
- package/dist/advanced.d.ts.map +1 -0
- package/dist/advanced.js +12 -6
- package/dist/core/advanced.d.ts +1 -0
- package/dist/core/advanced.d.ts.map +1 -0
- package/dist/core/advanced.js +12 -5
- package/dist/core/current-value.d.ts +1 -0
- package/dist/core/current-value.d.ts.map +1 -0
- package/dist/core/current-value.js +35 -34
- package/dist/core/current-value.js.map +1 -0
- package/dist/core/error-diagnostics.d.ts +1 -0
- package/dist/core/error-diagnostics.d.ts.map +1 -0
- package/dist/core/error-diagnostics.js +70 -137
- package/dist/core/error-diagnostics.js.map +1 -0
- package/dist/core/error-report.d.ts +1 -0
- package/dist/core/error-report.d.ts.map +1 -0
- package/dist/core/error-report.js +184 -233
- package/dist/core/error-report.js.map +1 -0
- package/dist/core/frame-registry.d.ts +1 -0
- package/dist/core/frame-registry.d.ts.map +1 -0
- package/dist/core/frame-registry.js +546 -662
- package/dist/core/frame-registry.js.map +1 -0
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +11 -12
- package/dist/core/material-preprocess.d.ts +1 -0
- package/dist/core/material-preprocess.d.ts.map +1 -0
- package/dist/core/material-preprocess.js +128 -151
- package/dist/core/material-preprocess.js.map +1 -0
- package/dist/core/material.d.ts +1 -0
- package/dist/core/material.d.ts.map +1 -0
- package/dist/core/material.js +263 -317
- package/dist/core/material.js.map +1 -0
- package/dist/core/recompile-policy.d.ts +1 -0
- package/dist/core/recompile-policy.d.ts.map +1 -0
- package/dist/core/recompile-policy.js +18 -13
- package/dist/core/recompile-policy.js.map +1 -0
- package/dist/core/render-graph.d.ts +1 -0
- package/dist/core/render-graph.d.ts.map +1 -0
- package/dist/core/render-graph.js +61 -68
- package/dist/core/render-graph.js.map +1 -0
- package/dist/core/render-targets.d.ts +2 -0
- package/dist/core/render-targets.d.ts.map +1 -0
- package/dist/core/render-targets.js +52 -53
- package/dist/core/render-targets.js.map +1 -0
- package/dist/core/renderer.d.ts +1 -0
- package/dist/core/renderer.d.ts.map +1 -0
- package/dist/core/renderer.js +942 -1081
- package/dist/core/renderer.js.map +1 -0
- package/dist/core/runtime-loop.d.ts +2 -0
- package/dist/core/runtime-loop.d.ts.map +1 -0
- package/dist/core/runtime-loop.js +305 -362
- package/dist/core/runtime-loop.js.map +1 -0
- package/dist/core/scheduler-helpers.d.ts +1 -0
- package/dist/core/scheduler-helpers.d.ts.map +1 -0
- package/dist/core/scheduler-helpers.js +52 -51
- package/dist/core/scheduler-helpers.js.map +1 -0
- package/dist/core/shader.d.ts +1 -0
- package/dist/core/shader.d.ts.map +1 -0
- package/dist/core/shader.js +92 -117
- package/dist/core/shader.js.map +1 -0
- package/dist/core/texture-loader.d.ts +1 -0
- package/dist/core/texture-loader.d.ts.map +1 -0
- package/dist/core/texture-loader.js +205 -273
- package/dist/core/texture-loader.js.map +1 -0
- package/dist/core/textures.d.ts +2 -0
- package/dist/core/textures.d.ts.map +1 -0
- package/dist/core/textures.js +106 -116
- package/dist/core/textures.js.map +1 -0
- package/dist/core/types.d.ts +2 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +0 -4
- package/dist/core/uniforms.d.ts +1 -0
- package/dist/core/uniforms.d.ts.map +1 -0
- package/dist/core/uniforms.js +170 -191
- package/dist/core/uniforms.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -6
- package/dist/passes/BlitPass.d.ts +1 -0
- package/dist/passes/BlitPass.d.ts.map +1 -0
- package/dist/passes/BlitPass.js +23 -18
- package/dist/passes/BlitPass.js.map +1 -0
- package/dist/passes/CopyPass.d.ts +2 -0
- package/dist/passes/CopyPass.d.ts.map +1 -0
- package/dist/passes/CopyPass.js +58 -52
- package/dist/passes/CopyPass.js.map +1 -0
- package/dist/passes/FullscreenPass.d.ts +2 -0
- package/dist/passes/FullscreenPass.d.ts.map +1 -0
- package/dist/passes/FullscreenPass.js +127 -130
- package/dist/passes/FullscreenPass.js.map +1 -0
- package/dist/passes/ShaderPass.d.ts +1 -0
- package/dist/passes/ShaderPass.d.ts.map +1 -0
- package/dist/passes/ShaderPass.js +40 -37
- package/dist/passes/ShaderPass.js.map +1 -0
- package/dist/passes/index.d.ts +1 -0
- package/dist/passes/index.d.ts.map +1 -0
- package/dist/passes/index.js +4 -3
- package/dist/react/FragCanvas.d.ts +2 -0
- package/dist/react/FragCanvas.d.ts.map +1 -0
- package/dist/react/FragCanvas.js +234 -211
- package/dist/react/FragCanvas.js.map +1 -0
- package/dist/react/MotionGPUErrorOverlay.d.ts +1 -0
- package/dist/react/MotionGPUErrorOverlay.d.ts.map +1 -0
- package/dist/react/MotionGPUErrorOverlay.js +384 -48
- package/dist/react/MotionGPUErrorOverlay.js.map +1 -0
- package/dist/react/Portal.d.ts +1 -0
- package/dist/react/Portal.d.ts.map +1 -0
- package/dist/react/Portal.js +18 -21
- package/dist/react/Portal.js.map +1 -0
- package/dist/react/advanced.d.ts +1 -0
- package/dist/react/advanced.d.ts.map +1 -0
- package/dist/react/advanced.js +12 -6
- package/dist/react/frame-context.d.ts +1 -0
- package/dist/react/frame-context.d.ts.map +1 -0
- package/dist/react/frame-context.js +88 -94
- package/dist/react/frame-context.js.map +1 -0
- package/dist/react/index.d.ts +1 -0
- package/dist/react/index.d.ts.map +1 -0
- package/dist/react/index.js +10 -9
- package/dist/react/motiongpu-context.d.ts +1 -0
- package/dist/react/motiongpu-context.d.ts.map +1 -0
- package/dist/react/motiongpu-context.js +18 -15
- package/dist/react/motiongpu-context.js.map +1 -0
- package/dist/react/use-motiongpu-user-context.d.ts +1 -0
- package/dist/react/use-motiongpu-user-context.d.ts.map +1 -0
- package/dist/react/use-motiongpu-user-context.js +83 -82
- package/dist/react/use-motiongpu-user-context.js.map +1 -0
- package/dist/react/use-texture.d.ts +1 -0
- package/dist/react/use-texture.d.ts.map +1 -0
- package/dist/react/use-texture.js +132 -152
- package/dist/react/use-texture.js.map +1 -0
- package/dist/svelte/FragCanvas.svelte.d.ts +2 -0
- package/dist/svelte/FragCanvas.svelte.d.ts.map +1 -0
- package/dist/svelte/MotionGPUErrorOverlay.svelte +17 -20
- package/dist/svelte/MotionGPUErrorOverlay.svelte.d.ts +1 -0
- package/dist/svelte/MotionGPUErrorOverlay.svelte.d.ts.map +1 -0
- package/dist/svelte/Portal.svelte.d.ts +1 -0
- package/dist/svelte/Portal.svelte.d.ts.map +1 -0
- package/dist/svelte/advanced.d.ts +1 -0
- package/dist/svelte/advanced.d.ts.map +1 -0
- package/dist/svelte/advanced.js +11 -6
- package/dist/svelte/frame-context.d.ts +1 -0
- package/dist/svelte/frame-context.d.ts.map +1 -0
- package/dist/svelte/frame-context.js +27 -27
- package/dist/svelte/frame-context.js.map +1 -0
- package/dist/svelte/index.d.ts +1 -0
- package/dist/svelte/index.d.ts.map +1 -0
- package/dist/svelte/index.js +10 -9
- package/dist/svelte/motiongpu-context.d.ts +1 -0
- package/dist/svelte/motiongpu-context.d.ts.map +1 -0
- package/dist/svelte/motiongpu-context.js +24 -21
- package/dist/svelte/motiongpu-context.js.map +1 -0
- package/dist/svelte/use-motiongpu-user-context.d.ts +1 -0
- package/dist/svelte/use-motiongpu-user-context.d.ts.map +1 -0
- package/dist/svelte/use-motiongpu-user-context.js +69 -70
- package/dist/svelte/use-motiongpu-user-context.js.map +1 -0
- package/dist/svelte/use-texture.d.ts +1 -0
- package/dist/svelte/use-texture.d.ts.map +1 -0
- package/dist/svelte/use-texture.js +125 -147
- package/dist/svelte/use-texture.js.map +1 -0
- package/package.json +15 -7
- package/src/lib/advanced.ts +6 -0
- package/src/lib/core/advanced.ts +12 -0
- package/src/lib/core/current-value.ts +64 -0
- package/src/lib/core/error-diagnostics.ts +236 -0
- package/src/lib/core/error-report.ts +406 -0
- package/src/lib/core/frame-registry.ts +1189 -0
- package/src/lib/core/index.ts +77 -0
- package/src/lib/core/material-preprocess.ts +284 -0
- package/src/lib/core/material.ts +667 -0
- package/src/lib/core/recompile-policy.ts +31 -0
- package/src/lib/core/render-graph.ts +143 -0
- package/src/lib/core/render-targets.ts +107 -0
- package/src/lib/core/renderer.ts +1547 -0
- package/src/lib/core/runtime-loop.ts +458 -0
- package/src/lib/core/scheduler-helpers.ts +136 -0
- package/src/lib/core/shader.ts +258 -0
- package/src/lib/core/texture-loader.ts +476 -0
- package/src/lib/core/textures.ts +235 -0
- package/src/lib/core/types.ts +582 -0
- package/src/lib/core/uniforms.ts +282 -0
- package/src/lib/index.ts +6 -0
- package/src/lib/passes/BlitPass.ts +54 -0
- package/src/lib/passes/CopyPass.ts +80 -0
- package/src/lib/passes/FullscreenPass.ts +173 -0
- package/src/lib/passes/ShaderPass.ts +88 -0
- package/src/lib/passes/index.ts +3 -0
- package/src/lib/react/MotionGPUErrorOverlay.tsx +392 -0
- package/src/lib/react/advanced.ts +36 -0
- package/src/lib/react/frame-context.ts +169 -0
- package/src/lib/react/index.ts +51 -0
- package/src/lib/react/motiongpu-context.ts +88 -0
- package/src/lib/react/use-motiongpu-user-context.ts +186 -0
- package/src/lib/react/use-texture.ts +233 -0
- package/src/lib/svelte/FragCanvas.svelte +249 -0
- package/src/lib/svelte/MotionGPUErrorOverlay.svelte +382 -0
- package/src/lib/svelte/Portal.svelte +31 -0
- package/src/lib/svelte/advanced.ts +32 -0
- package/src/lib/svelte/frame-context.ts +87 -0
- package/src/lib/svelte/index.ts +51 -0
- package/src/lib/svelte/motiongpu-context.ts +97 -0
- package/src/lib/svelte/use-motiongpu-user-context.ts +145 -0
- package/src/lib/svelte/use-texture.ts +232 -0
- package/dist/react/MotionGPUErrorOverlay.tsx +0 -129
- /package/{dist → src/lib}/react/FragCanvas.tsx +0 -0
- /package/{dist → src/lib}/react/Portal.tsx +0 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal subscribe contract used by MotionGPU core.
|
|
3
|
+
*/
|
|
4
|
+
export interface Subscribable<T> {
|
|
5
|
+
subscribe: (run: (value: T) => void) => () => void;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Readable value with synchronous access to the latest value.
|
|
10
|
+
*/
|
|
11
|
+
export interface CurrentReadable<T> extends Subscribable<T> {
|
|
12
|
+
readonly current: T;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Writable extension of {@link CurrentReadable}.
|
|
17
|
+
*/
|
|
18
|
+
export interface CurrentWritable<T> extends CurrentReadable<T> {
|
|
19
|
+
set: (value: T) => void;
|
|
20
|
+
update: (updater: (value: T) => T) => void;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Creates a writable value with immediate subscription semantics.
|
|
25
|
+
*/
|
|
26
|
+
export function createCurrentWritable<T>(
|
|
27
|
+
initialValue: T,
|
|
28
|
+
onChange?: (value: T) => void
|
|
29
|
+
): CurrentWritable<T> {
|
|
30
|
+
let current = initialValue;
|
|
31
|
+
const subscribers = new Set<(value: T) => void>();
|
|
32
|
+
|
|
33
|
+
const notify = (value: T): void => {
|
|
34
|
+
for (const run of subscribers) {
|
|
35
|
+
run(value);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const set = (value: T): void => {
|
|
40
|
+
if (Object.is(current, value)) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
current = value;
|
|
44
|
+
notify(value);
|
|
45
|
+
onChange?.(value);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
get current() {
|
|
50
|
+
return current;
|
|
51
|
+
},
|
|
52
|
+
subscribe(run) {
|
|
53
|
+
subscribers.add(run);
|
|
54
|
+
run(current);
|
|
55
|
+
return () => {
|
|
56
|
+
subscribers.delete(run);
|
|
57
|
+
};
|
|
58
|
+
},
|
|
59
|
+
set,
|
|
60
|
+
update(updater) {
|
|
61
|
+
set(updater(current));
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import type { MaterialSourceLocation } from './material-preprocess.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Source metadata for material declaration callsite.
|
|
5
|
+
*/
|
|
6
|
+
export interface MaterialSourceMetadata {
|
|
7
|
+
component?: string;
|
|
8
|
+
file?: string;
|
|
9
|
+
line?: number;
|
|
10
|
+
column?: number;
|
|
11
|
+
functionName?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* One WGSL compiler diagnostic enriched with source-location metadata.
|
|
16
|
+
*/
|
|
17
|
+
export interface ShaderCompilationDiagnostic {
|
|
18
|
+
generatedLine: number;
|
|
19
|
+
message: string;
|
|
20
|
+
linePos?: number;
|
|
21
|
+
lineLength?: number;
|
|
22
|
+
sourceLocation: MaterialSourceLocation | null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Runtime context snapshot captured for shader compilation diagnostics.
|
|
27
|
+
*/
|
|
28
|
+
export interface ShaderCompilationRuntimeContext {
|
|
29
|
+
materialSignature?: string;
|
|
30
|
+
passGraph?: {
|
|
31
|
+
passCount: number;
|
|
32
|
+
enabledPassCount: number;
|
|
33
|
+
inputs: string[];
|
|
34
|
+
outputs: string[];
|
|
35
|
+
};
|
|
36
|
+
activeRenderTargets: string[];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Structured payload attached to WGSL compilation errors.
|
|
41
|
+
*/
|
|
42
|
+
export interface ShaderCompilationDiagnosticsPayload {
|
|
43
|
+
kind: 'shader-compilation';
|
|
44
|
+
diagnostics: ShaderCompilationDiagnostic[];
|
|
45
|
+
fragmentSource: string;
|
|
46
|
+
includeSources: Record<string, string>;
|
|
47
|
+
defineBlockSource?: string;
|
|
48
|
+
materialSource: MaterialSourceMetadata | null;
|
|
49
|
+
runtimeContext?: ShaderCompilationRuntimeContext;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
type MotionGPUErrorWithDiagnostics = Error & {
|
|
53
|
+
motiongpuDiagnostics?: unknown;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
function isMaterialSourceMetadata(value: unknown): value is MaterialSourceMetadata {
|
|
57
|
+
if (value === null || typeof value !== 'object') {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const record = value as Record<string, unknown>;
|
|
62
|
+
if (record.component !== undefined && typeof record.component !== 'string') {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
if (record.file !== undefined && typeof record.file !== 'string') {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
if (record.functionName !== undefined && typeof record.functionName !== 'string') {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
if (record.line !== undefined && typeof record.line !== 'number') {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
if (record.column !== undefined && typeof record.column !== 'number') {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function isMaterialSourceLocation(value: unknown): value is MaterialSourceLocation | null {
|
|
82
|
+
if (value === null) {
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (typeof value !== 'object') {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const record = value as Record<string, unknown>;
|
|
91
|
+
const kind = record.kind;
|
|
92
|
+
if (kind !== 'fragment' && kind !== 'include' && kind !== 'define') {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return typeof record.line === 'number';
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function isShaderCompilationDiagnostic(value: unknown): value is ShaderCompilationDiagnostic {
|
|
100
|
+
if (value === null || typeof value !== 'object') {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const record = value as Record<string, unknown>;
|
|
105
|
+
if (typeof record.generatedLine !== 'number') {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
if (typeof record.message !== 'string') {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
if (record.linePos !== undefined && typeof record.linePos !== 'number') {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
if (record.lineLength !== undefined && typeof record.lineLength !== 'number') {
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
if (!isMaterialSourceLocation(record.sourceLocation)) {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function isStringArray(value: unknown): value is string[] {
|
|
125
|
+
return Array.isArray(value) && value.every((entry) => typeof entry === 'string');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function isShaderCompilationRuntimeContext(
|
|
129
|
+
value: unknown
|
|
130
|
+
): value is ShaderCompilationRuntimeContext {
|
|
131
|
+
if (value === null || typeof value !== 'object') {
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const record = value as Record<string, unknown>;
|
|
136
|
+
if (record.materialSignature !== undefined && typeof record.materialSignature !== 'string') {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
if (!isStringArray(record.activeRenderTargets)) {
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
const passGraph = record.passGraph;
|
|
143
|
+
if (passGraph === undefined) {
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
if (passGraph === null || typeof passGraph !== 'object') {
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const passGraphRecord = passGraph as Record<string, unknown>;
|
|
151
|
+
if (typeof passGraphRecord.passCount !== 'number') {
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
if (typeof passGraphRecord.enabledPassCount !== 'number') {
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
if (!isStringArray(passGraphRecord.inputs) || !isStringArray(passGraphRecord.outputs)) {
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Attaches structured diagnostics payload to an Error.
|
|
166
|
+
*/
|
|
167
|
+
export function attachShaderCompilationDiagnostics(
|
|
168
|
+
error: Error,
|
|
169
|
+
payload: ShaderCompilationDiagnosticsPayload
|
|
170
|
+
): Error {
|
|
171
|
+
(error as MotionGPUErrorWithDiagnostics).motiongpuDiagnostics = payload;
|
|
172
|
+
return error;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Extracts structured diagnostics payload from unknown error value.
|
|
177
|
+
*/
|
|
178
|
+
export function getShaderCompilationDiagnostics(
|
|
179
|
+
error: unknown
|
|
180
|
+
): ShaderCompilationDiagnosticsPayload | null {
|
|
181
|
+
if (!(error instanceof Error)) {
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const payload = (error as MotionGPUErrorWithDiagnostics).motiongpuDiagnostics;
|
|
186
|
+
if (payload === null || typeof payload !== 'object') {
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const record = payload as Record<string, unknown>;
|
|
191
|
+
if (record.kind !== 'shader-compilation') {
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
if (
|
|
195
|
+
!Array.isArray(record.diagnostics) ||
|
|
196
|
+
!record.diagnostics.every(isShaderCompilationDiagnostic)
|
|
197
|
+
) {
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
if (typeof record.fragmentSource !== 'string') {
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
if (record.defineBlockSource !== undefined && typeof record.defineBlockSource !== 'string') {
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
if (record.includeSources === null || typeof record.includeSources !== 'object') {
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
const includeSources = record.includeSources as Record<string, unknown>;
|
|
210
|
+
if (Object.values(includeSources).some((value) => typeof value !== 'string')) {
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
if (record.materialSource !== null && !isMaterialSourceMetadata(record.materialSource)) {
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
if (
|
|
217
|
+
record.runtimeContext !== undefined &&
|
|
218
|
+
!isShaderCompilationRuntimeContext(record.runtimeContext)
|
|
219
|
+
) {
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return {
|
|
224
|
+
kind: 'shader-compilation',
|
|
225
|
+
diagnostics: record.diagnostics as ShaderCompilationDiagnostic[],
|
|
226
|
+
fragmentSource: record.fragmentSource,
|
|
227
|
+
includeSources: includeSources as Record<string, string>,
|
|
228
|
+
...(record.defineBlockSource !== undefined
|
|
229
|
+
? { defineBlockSource: record.defineBlockSource as string }
|
|
230
|
+
: {}),
|
|
231
|
+
materialSource: (record.materialSource ?? null) as MaterialSourceMetadata | null,
|
|
232
|
+
...(record.runtimeContext !== undefined
|
|
233
|
+
? { runtimeContext: record.runtimeContext as ShaderCompilationRuntimeContext }
|
|
234
|
+
: {})
|
|
235
|
+
};
|
|
236
|
+
}
|
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getShaderCompilationDiagnostics,
|
|
3
|
+
type ShaderCompilationDiagnostic
|
|
4
|
+
} from './error-diagnostics.js';
|
|
5
|
+
import { formatShaderSourceLocation } from './shader.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Runtime phase in which an error occurred.
|
|
9
|
+
*/
|
|
10
|
+
export type MotionGPUErrorPhase = 'initialization' | 'render';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Stable machine-readable error category code.
|
|
14
|
+
*/
|
|
15
|
+
export type MotionGPUErrorCode =
|
|
16
|
+
| 'WEBGPU_UNAVAILABLE'
|
|
17
|
+
| 'WEBGPU_ADAPTER_UNAVAILABLE'
|
|
18
|
+
| 'WEBGPU_CONTEXT_UNAVAILABLE'
|
|
19
|
+
| 'WGSL_COMPILATION_FAILED'
|
|
20
|
+
| 'WEBGPU_DEVICE_LOST'
|
|
21
|
+
| 'WEBGPU_UNCAPTURED_ERROR'
|
|
22
|
+
| 'BIND_GROUP_MISMATCH'
|
|
23
|
+
| 'TEXTURE_USAGE_INVALID'
|
|
24
|
+
| 'TEXTURE_REQUEST_FAILED'
|
|
25
|
+
| 'TEXTURE_DECODE_UNAVAILABLE'
|
|
26
|
+
| 'TEXTURE_REQUEST_ABORTED'
|
|
27
|
+
| 'MOTIONGPU_RUNTIME_ERROR';
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Severity level for user-facing diagnostics.
|
|
31
|
+
*/
|
|
32
|
+
export type MotionGPUErrorSeverity = 'error' | 'fatal';
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* One source-code line displayed in diagnostics snippet.
|
|
36
|
+
*/
|
|
37
|
+
export interface MotionGPUErrorSourceLine {
|
|
38
|
+
number: number;
|
|
39
|
+
code: string;
|
|
40
|
+
highlight: boolean;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Structured source context displayed for shader compilation errors.
|
|
45
|
+
*/
|
|
46
|
+
export interface MotionGPUErrorSource {
|
|
47
|
+
component: string;
|
|
48
|
+
location: string;
|
|
49
|
+
line: number;
|
|
50
|
+
column?: number;
|
|
51
|
+
snippet: MotionGPUErrorSourceLine[];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Optional runtime context captured with diagnostics payload.
|
|
56
|
+
*/
|
|
57
|
+
export interface MotionGPUErrorContext {
|
|
58
|
+
materialSignature?: string;
|
|
59
|
+
passGraph?: {
|
|
60
|
+
passCount: number;
|
|
61
|
+
enabledPassCount: number;
|
|
62
|
+
inputs: string[];
|
|
63
|
+
outputs: string[];
|
|
64
|
+
};
|
|
65
|
+
activeRenderTargets: string[];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Structured error payload used by UI diagnostics.
|
|
70
|
+
*/
|
|
71
|
+
export interface MotionGPUErrorReport {
|
|
72
|
+
/**
|
|
73
|
+
* Stable machine-readable category code.
|
|
74
|
+
*/
|
|
75
|
+
code: MotionGPUErrorCode;
|
|
76
|
+
/**
|
|
77
|
+
* Severity level used by diagnostics UIs and telemetry.
|
|
78
|
+
*/
|
|
79
|
+
severity: MotionGPUErrorSeverity;
|
|
80
|
+
/**
|
|
81
|
+
* Whether runtime may recover without full renderer re-creation.
|
|
82
|
+
*/
|
|
83
|
+
recoverable: boolean;
|
|
84
|
+
/**
|
|
85
|
+
* Short category title.
|
|
86
|
+
*/
|
|
87
|
+
title: string;
|
|
88
|
+
/**
|
|
89
|
+
* Primary human-readable message.
|
|
90
|
+
*/
|
|
91
|
+
message: string;
|
|
92
|
+
/**
|
|
93
|
+
* Suggested remediation hint.
|
|
94
|
+
*/
|
|
95
|
+
hint: string;
|
|
96
|
+
/**
|
|
97
|
+
* Additional parsed details (for example WGSL line errors).
|
|
98
|
+
*/
|
|
99
|
+
details: string[];
|
|
100
|
+
/**
|
|
101
|
+
* Stack trace lines when available.
|
|
102
|
+
*/
|
|
103
|
+
stack: string[];
|
|
104
|
+
/**
|
|
105
|
+
* Original unmodified message.
|
|
106
|
+
*/
|
|
107
|
+
rawMessage: string;
|
|
108
|
+
/**
|
|
109
|
+
* Runtime phase where the error occurred.
|
|
110
|
+
*/
|
|
111
|
+
phase: MotionGPUErrorPhase;
|
|
112
|
+
/**
|
|
113
|
+
* Optional source context for shader-related diagnostics.
|
|
114
|
+
*/
|
|
115
|
+
source: MotionGPUErrorSource | null;
|
|
116
|
+
/**
|
|
117
|
+
* Optional runtime context snapshot (material/pass graph/render targets).
|
|
118
|
+
*/
|
|
119
|
+
context: MotionGPUErrorContext | null;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Splits multi-line values into trimmed non-empty lines.
|
|
124
|
+
*/
|
|
125
|
+
function splitLines(value: string): string[] {
|
|
126
|
+
return value
|
|
127
|
+
.split('\n')
|
|
128
|
+
.map((line) => line.trim())
|
|
129
|
+
.filter((line) => line.length > 0);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function toDisplayName(path: string): string {
|
|
133
|
+
const normalized = path.split(/[?#]/)[0] ?? path;
|
|
134
|
+
const chunks = normalized.split(/[\\/]/);
|
|
135
|
+
const last = chunks[chunks.length - 1];
|
|
136
|
+
return last && last.length > 0 ? last : path;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function toSnippet(source: string, line: number, radius = 3): MotionGPUErrorSourceLine[] {
|
|
140
|
+
const lines = source.replace(/\r\n?/g, '\n').split('\n');
|
|
141
|
+
if (lines.length === 0) {
|
|
142
|
+
return [];
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const targetLine = Math.min(Math.max(1, line), lines.length);
|
|
146
|
+
const start = Math.max(1, targetLine - radius);
|
|
147
|
+
const end = Math.min(lines.length, targetLine + radius);
|
|
148
|
+
const snippet: MotionGPUErrorSourceLine[] = [];
|
|
149
|
+
|
|
150
|
+
for (let index = start; index <= end; index += 1) {
|
|
151
|
+
snippet.push({
|
|
152
|
+
number: index,
|
|
153
|
+
code: lines[index - 1] ?? '',
|
|
154
|
+
highlight: index === targetLine
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return snippet;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function buildSourceFromDiagnostics(error: unknown): MotionGPUErrorSource | null {
|
|
162
|
+
const diagnostics = getShaderCompilationDiagnostics(error);
|
|
163
|
+
if (!diagnostics || diagnostics.diagnostics.length === 0) {
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const primary = diagnostics.diagnostics.find((entry) => entry.sourceLocation !== null);
|
|
168
|
+
if (!primary?.sourceLocation) {
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const location = primary.sourceLocation;
|
|
173
|
+
const column = primary.linePos && primary.linePos > 0 ? primary.linePos : undefined;
|
|
174
|
+
|
|
175
|
+
if (location.kind === 'fragment') {
|
|
176
|
+
const component =
|
|
177
|
+
diagnostics.materialSource?.component ??
|
|
178
|
+
(diagnostics.materialSource?.file
|
|
179
|
+
? toDisplayName(diagnostics.materialSource.file)
|
|
180
|
+
: 'User shader fragment');
|
|
181
|
+
const locationLabel = formatShaderSourceLocation(location) ?? `fragment line ${location.line}`;
|
|
182
|
+
return {
|
|
183
|
+
component,
|
|
184
|
+
location: `${component} (${locationLabel})`,
|
|
185
|
+
line: location.line,
|
|
186
|
+
...(column !== undefined ? { column } : {}),
|
|
187
|
+
snippet: toSnippet(diagnostics.fragmentSource, location.line)
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (location.kind === 'include') {
|
|
192
|
+
const includeName = location.include ?? 'unknown';
|
|
193
|
+
const includeSource = diagnostics.includeSources[includeName] ?? '';
|
|
194
|
+
const component = `#include <${includeName}>`;
|
|
195
|
+
const locationLabel = formatShaderSourceLocation(location) ?? `include <${includeName}>`;
|
|
196
|
+
return {
|
|
197
|
+
component,
|
|
198
|
+
location: `${component} (${locationLabel})`,
|
|
199
|
+
line: location.line,
|
|
200
|
+
...(column !== undefined ? { column } : {}),
|
|
201
|
+
snippet: toSnippet(includeSource, location.line)
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const defineName = location.define ?? 'unknown';
|
|
206
|
+
const defineLine = Math.max(1, location.line);
|
|
207
|
+
const component = `#define ${defineName}`;
|
|
208
|
+
const locationLabel =
|
|
209
|
+
formatShaderSourceLocation(location) ?? `define "${defineName}" line ${defineLine}`;
|
|
210
|
+
return {
|
|
211
|
+
component,
|
|
212
|
+
location: `${component} (${locationLabel})`,
|
|
213
|
+
line: defineLine,
|
|
214
|
+
...(column !== undefined ? { column } : {}),
|
|
215
|
+
snippet: toSnippet(diagnostics.defineBlockSource ?? '', defineLine, 2)
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function formatDiagnosticMessage(entry: ShaderCompilationDiagnostic): string {
|
|
220
|
+
const sourceLabel = formatShaderSourceLocation(entry.sourceLocation);
|
|
221
|
+
const generatedLineLabel =
|
|
222
|
+
entry.generatedLine > 0 ? `generated WGSL line ${entry.generatedLine}` : null;
|
|
223
|
+
const labels = [sourceLabel, generatedLineLabel].filter((value) => Boolean(value));
|
|
224
|
+
if (labels.length === 0) {
|
|
225
|
+
return entry.message;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return `[${labels.join(' | ')}] ${entry.message}`;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Maps known WebGPU/WGSL error patterns to a user-facing title and hint.
|
|
233
|
+
*/
|
|
234
|
+
function classifyErrorMessage(
|
|
235
|
+
message: string
|
|
236
|
+
): Pick<MotionGPUErrorReport, 'code' | 'severity' | 'recoverable' | 'title' | 'hint'> {
|
|
237
|
+
if (message.includes('WebGPU is not available in this browser')) {
|
|
238
|
+
return {
|
|
239
|
+
code: 'WEBGPU_UNAVAILABLE',
|
|
240
|
+
severity: 'fatal',
|
|
241
|
+
recoverable: false,
|
|
242
|
+
title: 'WebGPU unavailable',
|
|
243
|
+
hint: 'Use a browser with WebGPU enabled (latest Chrome/Edge/Safari TP) and secure context.'
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (message.includes('Unable to acquire WebGPU adapter')) {
|
|
248
|
+
return {
|
|
249
|
+
code: 'WEBGPU_ADAPTER_UNAVAILABLE',
|
|
250
|
+
severity: 'fatal',
|
|
251
|
+
recoverable: false,
|
|
252
|
+
title: 'WebGPU adapter unavailable',
|
|
253
|
+
hint: 'GPU adapter request failed. Check browser permissions, flags and device support.'
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (message.includes('Canvas does not support webgpu context')) {
|
|
258
|
+
return {
|
|
259
|
+
code: 'WEBGPU_CONTEXT_UNAVAILABLE',
|
|
260
|
+
severity: 'error',
|
|
261
|
+
recoverable: true,
|
|
262
|
+
title: 'Canvas cannot create WebGPU context',
|
|
263
|
+
hint: 'Make sure this canvas is attached to DOM and not using an unsupported context option.'
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (message.includes('WGSL compilation failed')) {
|
|
268
|
+
return {
|
|
269
|
+
code: 'WGSL_COMPILATION_FAILED',
|
|
270
|
+
severity: 'error',
|
|
271
|
+
recoverable: true,
|
|
272
|
+
title: 'WGSL compilation failed',
|
|
273
|
+
hint: 'Check WGSL line numbers below and verify struct/binding/function signatures.'
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (message.includes('WebGPU device lost') || message.includes('Device Lost')) {
|
|
278
|
+
return {
|
|
279
|
+
code: 'WEBGPU_DEVICE_LOST',
|
|
280
|
+
severity: 'fatal',
|
|
281
|
+
recoverable: false,
|
|
282
|
+
title: 'WebGPU device lost',
|
|
283
|
+
hint: 'GPU device/context was lost. Recreate the renderer and check OS/GPU stability.'
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (message.includes('WebGPU uncaptured error')) {
|
|
288
|
+
return {
|
|
289
|
+
code: 'WEBGPU_UNCAPTURED_ERROR',
|
|
290
|
+
severity: 'error',
|
|
291
|
+
recoverable: true,
|
|
292
|
+
title: 'WebGPU uncaptured error',
|
|
293
|
+
hint: 'A GPU command failed asynchronously. Review details and validate resource/state usage.'
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (message.includes('CreateBindGroup') || message.includes('bind group layout')) {
|
|
298
|
+
return {
|
|
299
|
+
code: 'BIND_GROUP_MISMATCH',
|
|
300
|
+
severity: 'error',
|
|
301
|
+
recoverable: true,
|
|
302
|
+
title: 'Bind group mismatch',
|
|
303
|
+
hint: 'Bindings in shader and runtime resources are out of sync. Verify uniforms/textures layout.'
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if (message.includes('Destination texture needs to have CopyDst')) {
|
|
308
|
+
return {
|
|
309
|
+
code: 'TEXTURE_USAGE_INVALID',
|
|
310
|
+
severity: 'error',
|
|
311
|
+
recoverable: true,
|
|
312
|
+
title: 'Invalid texture usage flags',
|
|
313
|
+
hint: 'Texture used as upload destination must include CopyDst (and often RenderAttachment).'
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (message.includes('Texture request failed')) {
|
|
318
|
+
return {
|
|
319
|
+
code: 'TEXTURE_REQUEST_FAILED',
|
|
320
|
+
severity: 'error',
|
|
321
|
+
recoverable: true,
|
|
322
|
+
title: 'Texture request failed',
|
|
323
|
+
hint: 'Verify texture URL, CORS policy and response status before retrying.'
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (message.includes('createImageBitmap is not available in this runtime')) {
|
|
328
|
+
return {
|
|
329
|
+
code: 'TEXTURE_DECODE_UNAVAILABLE',
|
|
330
|
+
severity: 'fatal',
|
|
331
|
+
recoverable: false,
|
|
332
|
+
title: 'Texture decode unavailable',
|
|
333
|
+
hint: 'Runtime lacks createImageBitmap support. Use a browser/runtime with image bitmap decoding.'
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if (message.toLowerCase().includes('texture request was aborted')) {
|
|
338
|
+
return {
|
|
339
|
+
code: 'TEXTURE_REQUEST_ABORTED',
|
|
340
|
+
severity: 'error',
|
|
341
|
+
recoverable: true,
|
|
342
|
+
title: 'Texture request aborted',
|
|
343
|
+
hint: 'Texture load was cancelled. Retry the request when source inputs stabilize.'
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
return {
|
|
348
|
+
code: 'MOTIONGPU_RUNTIME_ERROR',
|
|
349
|
+
severity: 'error',
|
|
350
|
+
recoverable: true,
|
|
351
|
+
title: 'MotionGPU render error',
|
|
352
|
+
hint: 'Review technical details below. If issue persists, isolate shader/uniform/texture changes.'
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Converts unknown errors to a consistent, display-ready error report.
|
|
358
|
+
*
|
|
359
|
+
* @param error - Unknown thrown value.
|
|
360
|
+
* @param phase - Phase during which error occurred.
|
|
361
|
+
* @returns Normalized error report.
|
|
362
|
+
*/
|
|
363
|
+
export function toMotionGPUErrorReport(
|
|
364
|
+
error: unknown,
|
|
365
|
+
phase: MotionGPUErrorPhase
|
|
366
|
+
): MotionGPUErrorReport {
|
|
367
|
+
const shaderDiagnostics = getShaderCompilationDiagnostics(error);
|
|
368
|
+
const rawMessage =
|
|
369
|
+
error instanceof Error
|
|
370
|
+
? error.message
|
|
371
|
+
: typeof error === 'string'
|
|
372
|
+
? error
|
|
373
|
+
: 'Unknown FragCanvas error';
|
|
374
|
+
const rawLines = splitLines(rawMessage);
|
|
375
|
+
const defaultMessage = rawLines[0] ?? rawMessage;
|
|
376
|
+
const defaultDetails = rawLines.slice(1);
|
|
377
|
+
const source = buildSourceFromDiagnostics(error);
|
|
378
|
+
const context = shaderDiagnostics?.runtimeContext ?? null;
|
|
379
|
+
const message =
|
|
380
|
+
shaderDiagnostics && shaderDiagnostics.diagnostics[0]
|
|
381
|
+
? formatDiagnosticMessage(shaderDiagnostics.diagnostics[0])
|
|
382
|
+
: defaultMessage;
|
|
383
|
+
const details = shaderDiagnostics
|
|
384
|
+
? shaderDiagnostics.diagnostics.slice(1).map((entry) => formatDiagnosticMessage(entry))
|
|
385
|
+
: defaultDetails;
|
|
386
|
+
const stack =
|
|
387
|
+
error instanceof Error && error.stack
|
|
388
|
+
? splitLines(error.stack).filter((line) => line !== message)
|
|
389
|
+
: [];
|
|
390
|
+
const classification = classifyErrorMessage(rawMessage);
|
|
391
|
+
|
|
392
|
+
return {
|
|
393
|
+
code: classification.code,
|
|
394
|
+
severity: classification.severity,
|
|
395
|
+
recoverable: classification.recoverable,
|
|
396
|
+
title: classification.title,
|
|
397
|
+
message,
|
|
398
|
+
hint: classification.hint,
|
|
399
|
+
details,
|
|
400
|
+
stack,
|
|
401
|
+
rawMessage,
|
|
402
|
+
phase,
|
|
403
|
+
source,
|
|
404
|
+
context
|
|
405
|
+
};
|
|
406
|
+
}
|