@motion-core/motion-gpu 0.4.1 → 0.5.0
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 +99 -0
- package/dist/advanced.d.ts +1 -0
- package/dist/advanced.d.ts.map +1 -0
- package/dist/advanced.js +14 -6
- package/dist/core/advanced.d.ts +1 -0
- package/dist/core/advanced.d.ts.map +1 -0
- package/dist/core/advanced.js +14 -5
- package/dist/core/compute-shader.d.ts +87 -0
- package/dist/core/compute-shader.d.ts.map +1 -0
- package/dist/core/compute-shader.js +205 -0
- package/dist/core/compute-shader.js.map +1 -0
- 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 +2 -1
- package/dist/core/error-report.d.ts.map +1 -0
- package/dist/core/error-report.js +247 -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 +6 -2
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +13 -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 +131 -152
- package/dist/core/material-preprocess.js.map +1 -0
- package/dist/core/material.d.ts +23 -6
- package/dist/core/material.d.ts.map +1 -0
- package/dist/core/material.js +290 -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 +8 -3
- package/dist/core/render-graph.d.ts.map +1 -0
- package/dist/core/render-graph.js +77 -68
- package/dist/core/render-graph.js.map +1 -0
- package/dist/core/render-targets.d.ts +1 -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 +1337 -1081
- package/dist/core/renderer.js.map +1 -0
- package/dist/core/runtime-loop.d.ts +3 -2
- package/dist/core/runtime-loop.d.ts.map +1 -0
- package/dist/core/runtime-loop.js +353 -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 +10 -1
- package/dist/core/shader.d.ts.map +1 -0
- package/dist/core/shader.js +109 -115
- package/dist/core/shader.js.map +1 -0
- package/dist/core/storage-buffers.d.ts +37 -0
- package/dist/core/storage-buffers.d.ts.map +1 -0
- package/dist/core/storage-buffers.js +95 -0
- package/dist/core/storage-buffers.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 +209 -273
- package/dist/core/texture-loader.js.map +1 -0
- package/dist/core/textures.d.ts +13 -0
- package/dist/core/textures.d.ts.map +1 -0
- package/dist/core/textures.js +111 -116
- package/dist/core/textures.js.map +1 -0
- package/dist/core/types.d.ts +147 -4
- 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 +13 -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/ComputePass.d.ts +83 -0
- package/dist/passes/ComputePass.d.ts.map +1 -0
- package/dist/passes/ComputePass.js +92 -0
- package/dist/passes/ComputePass.js.map +1 -0
- package/dist/passes/CopyPass.d.ts +1 -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 +1 -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/PingPongComputePass.d.ts +104 -0
- package/dist/passes/PingPongComputePass.d.ts.map +1 -0
- package/dist/passes/PingPongComputePass.js +132 -0
- package/dist/passes/PingPongComputePass.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 +41 -37
- package/dist/passes/ShaderPass.js.map +1 -0
- package/dist/passes/index.d.ts +3 -0
- package/dist/passes/index.d.ts.map +1 -0
- package/dist/passes/index.js +6 -3
- package/dist/react/FragCanvas.d.ts +3 -2
- 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 +200 -14
- 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 +14 -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 +6 -2
- package/dist/react/index.d.ts.map +1 -0
- package/dist/react/index.js +12 -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 +2 -2
- package/dist/svelte/FragCanvas.svelte.d.ts +3 -2
- package/dist/svelte/FragCanvas.svelte.d.ts.map +1 -0
- package/dist/svelte/MotionGPUErrorOverlay.svelte +137 -7
- 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 +13 -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 +6 -2
- package/dist/svelte/index.d.ts.map +1 -0
- package/dist/svelte/index.js +12 -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 +12 -7
- package/src/lib/advanced.ts +6 -0
- package/src/lib/core/advanced.ts +12 -0
- package/src/lib/core/compute-shader.ts +326 -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 +535 -0
- package/src/lib/core/frame-registry.ts +1190 -0
- package/src/lib/core/index.ts +94 -0
- package/src/lib/core/material-preprocess.ts +295 -0
- package/src/lib/core/material.ts +748 -0
- package/src/lib/core/recompile-policy.ts +31 -0
- package/src/lib/core/render-graph.ts +173 -0
- package/src/lib/core/render-targets.ts +107 -0
- package/src/lib/core/renderer.ts +2161 -0
- package/src/lib/core/runtime-loop.ts +537 -0
- package/src/lib/core/scheduler-helpers.ts +136 -0
- package/src/lib/core/shader.ts +301 -0
- package/src/lib/core/storage-buffers.ts +142 -0
- package/src/lib/core/texture-loader.ts +482 -0
- package/src/lib/core/textures.ts +257 -0
- package/src/lib/core/types.ts +743 -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/ComputePass.ts +136 -0
- package/src/lib/passes/CopyPass.ts +80 -0
- package/src/lib/passes/FullscreenPass.ts +173 -0
- package/src/lib/passes/PingPongComputePass.ts +180 -0
- package/src/lib/passes/ShaderPass.ts +89 -0
- package/src/lib/passes/index.ts +9 -0
- package/src/lib/react/FragCanvas.tsx +345 -0
- package/src/lib/react/MotionGPUErrorOverlay.tsx +524 -0
- package/src/lib/react/Portal.tsx +34 -0
- package/src/lib/react/advanced.ts +36 -0
- package/src/lib/react/frame-context.ts +169 -0
- package/src/lib/react/index.ts +68 -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 +512 -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 +68 -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
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
createCurrentWritable as currentWritable,
|
|
4
|
+
type CurrentReadable
|
|
5
|
+
} from '../core/current-value.js';
|
|
6
|
+
import {
|
|
7
|
+
isAbortError,
|
|
8
|
+
loadTexturesFromUrls,
|
|
9
|
+
type LoadedTexture,
|
|
10
|
+
type TextureLoadOptions
|
|
11
|
+
} from '../core/texture-loader.js';
|
|
12
|
+
import { toMotionGPUErrorReport, type MotionGPUErrorReport } from '../core/error-report.js';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Reactive state returned by `useTexture`.
|
|
16
|
+
*/
|
|
17
|
+
export interface UseTextureResult {
|
|
18
|
+
/**
|
|
19
|
+
* Loaded textures or `null` when unavailable/failed.
|
|
20
|
+
*/
|
|
21
|
+
textures: CurrentReadable<LoadedTexture[] | null>;
|
|
22
|
+
/**
|
|
23
|
+
* `true` while an active load request is running.
|
|
24
|
+
*/
|
|
25
|
+
loading: CurrentReadable<boolean>;
|
|
26
|
+
/**
|
|
27
|
+
* Last loading error.
|
|
28
|
+
*/
|
|
29
|
+
error: CurrentReadable<Error | null>;
|
|
30
|
+
/**
|
|
31
|
+
* Last loading error normalized to MotionGPU diagnostics report shape.
|
|
32
|
+
*/
|
|
33
|
+
errorReport: CurrentReadable<MotionGPUErrorReport | null>;
|
|
34
|
+
/**
|
|
35
|
+
* Reloads all textures using current URL input.
|
|
36
|
+
*/
|
|
37
|
+
reload: () => Promise<void>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Supported URL input variants for `useTexture`.
|
|
42
|
+
*/
|
|
43
|
+
export type TextureUrlInput = string[] | (() => string[]);
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Normalizes unknown thrown values to an `Error` instance.
|
|
47
|
+
*/
|
|
48
|
+
function toError(error: unknown): Error {
|
|
49
|
+
if (error instanceof Error) {
|
|
50
|
+
return error;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return new Error('Unknown texture loading error');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Releases GPU-side resources for a list of loaded textures.
|
|
58
|
+
*/
|
|
59
|
+
function disposeTextures(list: LoadedTexture[] | null): void {
|
|
60
|
+
for (const texture of list ?? []) {
|
|
61
|
+
texture.dispose();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
interface MergedAbortSignal {
|
|
66
|
+
signal: AbortSignal;
|
|
67
|
+
dispose: () => void;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function mergeAbortSignals(
|
|
71
|
+
primary: AbortSignal,
|
|
72
|
+
secondary: AbortSignal | undefined
|
|
73
|
+
): MergedAbortSignal {
|
|
74
|
+
if (!secondary) {
|
|
75
|
+
return {
|
|
76
|
+
signal: primary,
|
|
77
|
+
dispose: () => {}
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (typeof AbortSignal.any === 'function') {
|
|
82
|
+
return {
|
|
83
|
+
signal: AbortSignal.any([primary, secondary]),
|
|
84
|
+
dispose: () => {}
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const fallback = new AbortController();
|
|
89
|
+
let disposed = false;
|
|
90
|
+
const cleanup = (): void => {
|
|
91
|
+
if (disposed) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
disposed = true;
|
|
95
|
+
primary.removeEventListener('abort', abort);
|
|
96
|
+
secondary.removeEventListener('abort', abort);
|
|
97
|
+
};
|
|
98
|
+
const abort = (): void => fallback.abort();
|
|
99
|
+
|
|
100
|
+
primary.addEventListener('abort', abort, { once: true });
|
|
101
|
+
secondary.addEventListener('abort', abort, { once: true });
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
signal: fallback.signal,
|
|
105
|
+
dispose: cleanup
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Loads textures from URLs and exposes reactive loading/error state.
|
|
111
|
+
*
|
|
112
|
+
* @param urlInput - URLs array or lazy URL provider.
|
|
113
|
+
* @param options - Loader options passed to URL fetch/decode pipeline.
|
|
114
|
+
* @returns Reactive texture loading state with reload support.
|
|
115
|
+
*/
|
|
116
|
+
export function useTexture(
|
|
117
|
+
urlInput: TextureUrlInput,
|
|
118
|
+
options: TextureLoadOptions = {}
|
|
119
|
+
): UseTextureResult {
|
|
120
|
+
const texturesRef = useRef(currentWritable<LoadedTexture[] | null>(null));
|
|
121
|
+
const loadingRef = useRef(currentWritable(true));
|
|
122
|
+
const errorRef = useRef(currentWritable<Error | null>(null));
|
|
123
|
+
const errorReportRef = useRef(currentWritable<MotionGPUErrorReport | null>(null));
|
|
124
|
+
const activeControllerRef = useRef<AbortController | null>(null);
|
|
125
|
+
const runningLoadRef = useRef<Promise<void> | null>(null);
|
|
126
|
+
const reloadQueuedRef = useRef(false);
|
|
127
|
+
const requestVersionRef = useRef(0);
|
|
128
|
+
const disposedRef = useRef(false);
|
|
129
|
+
const optionsRef = useRef(options);
|
|
130
|
+
const urlInputRef = useRef(urlInput);
|
|
131
|
+
|
|
132
|
+
optionsRef.current = options;
|
|
133
|
+
urlInputRef.current = urlInput;
|
|
134
|
+
|
|
135
|
+
const getUrls = useCallback((): string[] => {
|
|
136
|
+
const currentInput = urlInputRef.current;
|
|
137
|
+
return typeof currentInput === 'function' ? currentInput() : currentInput;
|
|
138
|
+
}, []);
|
|
139
|
+
|
|
140
|
+
const executeLoad = useCallback(async (): Promise<void> => {
|
|
141
|
+
if (disposedRef.current) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const version = ++requestVersionRef.current;
|
|
146
|
+
const controller = new AbortController();
|
|
147
|
+
activeControllerRef.current = controller;
|
|
148
|
+
loadingRef.current.set(true);
|
|
149
|
+
errorRef.current.set(null);
|
|
150
|
+
errorReportRef.current.set(null);
|
|
151
|
+
|
|
152
|
+
const previous = texturesRef.current.current;
|
|
153
|
+
const mergedSignal = mergeAbortSignals(controller.signal, optionsRef.current.signal);
|
|
154
|
+
try {
|
|
155
|
+
const loaded = await loadTexturesFromUrls(getUrls(), {
|
|
156
|
+
...optionsRef.current,
|
|
157
|
+
signal: mergedSignal.signal
|
|
158
|
+
});
|
|
159
|
+
if (disposedRef.current || version !== requestVersionRef.current) {
|
|
160
|
+
disposeTextures(loaded);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
texturesRef.current.set(loaded);
|
|
165
|
+
disposeTextures(previous);
|
|
166
|
+
} catch (nextError) {
|
|
167
|
+
if (disposedRef.current || version !== requestVersionRef.current) {
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (isAbortError(nextError)) {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
disposeTextures(previous);
|
|
176
|
+
texturesRef.current.set(null);
|
|
177
|
+
const normalizedError = toError(nextError);
|
|
178
|
+
errorRef.current.set(normalizedError);
|
|
179
|
+
errorReportRef.current.set(toMotionGPUErrorReport(normalizedError, 'initialization'));
|
|
180
|
+
} finally {
|
|
181
|
+
if (!disposedRef.current && version === requestVersionRef.current) {
|
|
182
|
+
loadingRef.current.set(false);
|
|
183
|
+
}
|
|
184
|
+
if (activeControllerRef.current === controller) {
|
|
185
|
+
activeControllerRef.current = null;
|
|
186
|
+
}
|
|
187
|
+
mergedSignal.dispose();
|
|
188
|
+
}
|
|
189
|
+
}, [getUrls]);
|
|
190
|
+
|
|
191
|
+
const runLoadLoop = useCallback(async (): Promise<void> => {
|
|
192
|
+
do {
|
|
193
|
+
reloadQueuedRef.current = false;
|
|
194
|
+
await executeLoad();
|
|
195
|
+
} while (reloadQueuedRef.current && !disposedRef.current);
|
|
196
|
+
}, [executeLoad]);
|
|
197
|
+
|
|
198
|
+
const load = useCallback((): Promise<void> => {
|
|
199
|
+
activeControllerRef.current?.abort();
|
|
200
|
+
if (runningLoadRef.current) {
|
|
201
|
+
reloadQueuedRef.current = true;
|
|
202
|
+
return runningLoadRef.current;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const pending = runLoadLoop();
|
|
206
|
+
const trackedPending = pending.finally(() => {
|
|
207
|
+
if (runningLoadRef.current === trackedPending) {
|
|
208
|
+
runningLoadRef.current = null;
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
runningLoadRef.current = trackedPending;
|
|
212
|
+
return trackedPending;
|
|
213
|
+
}, [runLoadLoop]);
|
|
214
|
+
|
|
215
|
+
useEffect(() => {
|
|
216
|
+
void load();
|
|
217
|
+
|
|
218
|
+
return () => {
|
|
219
|
+
disposedRef.current = true;
|
|
220
|
+
requestVersionRef.current += 1;
|
|
221
|
+
activeControllerRef.current?.abort();
|
|
222
|
+
disposeTextures(texturesRef.current.current);
|
|
223
|
+
};
|
|
224
|
+
}, [load]);
|
|
225
|
+
|
|
226
|
+
return {
|
|
227
|
+
textures: texturesRef.current,
|
|
228
|
+
loading: loadingRef.current,
|
|
229
|
+
error: errorRef.current,
|
|
230
|
+
errorReport: errorReportRef.current,
|
|
231
|
+
reload: load
|
|
232
|
+
};
|
|
233
|
+
}
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { onMount } from 'svelte';
|
|
3
|
+
import type { Snippet } from 'svelte';
|
|
4
|
+
import type { FragMaterial } from '../core/material';
|
|
5
|
+
import { createCurrentWritable as currentWritable } from '../core/current-value';
|
|
6
|
+
import { toMotionGPUErrorReport, type MotionGPUErrorReport } from '../core/error-report';
|
|
7
|
+
import MotionGPUErrorOverlay from './MotionGPUErrorOverlay.svelte';
|
|
8
|
+
import { createMotionGPURuntimeLoop } from '../core/runtime-loop';
|
|
9
|
+
import type {
|
|
10
|
+
AnyPass,
|
|
11
|
+
FrameInvalidationToken,
|
|
12
|
+
OutputColorSpace,
|
|
13
|
+
RenderMode,
|
|
14
|
+
RenderTargetDefinitionMap
|
|
15
|
+
} from '../core/types';
|
|
16
|
+
import { provideMotionGPUContext } from './motiongpu-context';
|
|
17
|
+
import { createFrameRegistry, provideFrameRegistry } from './frame-context';
|
|
18
|
+
|
|
19
|
+
interface Props {
|
|
20
|
+
material: FragMaterial;
|
|
21
|
+
renderTargets?: RenderTargetDefinitionMap;
|
|
22
|
+
passes?: AnyPass[];
|
|
23
|
+
clearColor?: [number, number, number, number];
|
|
24
|
+
outputColorSpace?: OutputColorSpace;
|
|
25
|
+
renderMode?: RenderMode;
|
|
26
|
+
autoRender?: boolean;
|
|
27
|
+
maxDelta?: number;
|
|
28
|
+
adapterOptions?: GPURequestAdapterOptions;
|
|
29
|
+
deviceDescriptor?: GPUDeviceDescriptor;
|
|
30
|
+
dpr?: number;
|
|
31
|
+
showErrorOverlay?: boolean;
|
|
32
|
+
errorRenderer?: Snippet<[MotionGPUErrorReport]>;
|
|
33
|
+
onError?: (report: MotionGPUErrorReport) => void;
|
|
34
|
+
errorHistoryLimit?: number;
|
|
35
|
+
onErrorHistory?: (history: MotionGPUErrorReport[]) => void;
|
|
36
|
+
class?: string;
|
|
37
|
+
style?: string;
|
|
38
|
+
children?: Snippet;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const initialDpr = typeof window === 'undefined' ? 1 : (window.devicePixelRatio ?? 1);
|
|
42
|
+
|
|
43
|
+
let {
|
|
44
|
+
material,
|
|
45
|
+
renderTargets = {},
|
|
46
|
+
passes = [],
|
|
47
|
+
clearColor = [0, 0, 0, 1],
|
|
48
|
+
outputColorSpace = 'srgb',
|
|
49
|
+
renderMode = 'always',
|
|
50
|
+
autoRender = true,
|
|
51
|
+
maxDelta = 0.1,
|
|
52
|
+
adapterOptions = undefined,
|
|
53
|
+
deviceDescriptor = undefined,
|
|
54
|
+
dpr = initialDpr,
|
|
55
|
+
showErrorOverlay = true,
|
|
56
|
+
errorRenderer = undefined,
|
|
57
|
+
onError = undefined,
|
|
58
|
+
errorHistoryLimit = 0,
|
|
59
|
+
onErrorHistory = undefined,
|
|
60
|
+
class: className = '',
|
|
61
|
+
style = '',
|
|
62
|
+
children
|
|
63
|
+
}: Props = $props();
|
|
64
|
+
|
|
65
|
+
let canvas: HTMLCanvasElement | undefined;
|
|
66
|
+
let errorReport = $state<MotionGPUErrorReport | null>(null);
|
|
67
|
+
let errorHistory = $state<MotionGPUErrorReport[]>([]);
|
|
68
|
+
|
|
69
|
+
let normalizedErrorHistoryLimit = $derived.by(() => {
|
|
70
|
+
if (!Number.isFinite(errorHistoryLimit) || errorHistoryLimit <= 0) {
|
|
71
|
+
return 0;
|
|
72
|
+
}
|
|
73
|
+
return Math.floor(errorHistoryLimit);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const bindCanvas = (node: HTMLCanvasElement) => {
|
|
77
|
+
canvas = node;
|
|
78
|
+
return () => {
|
|
79
|
+
if (canvas === node) {
|
|
80
|
+
canvas = undefined;
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const registry = createFrameRegistry({ maxDelta: 0.1 });
|
|
86
|
+
provideFrameRegistry(registry);
|
|
87
|
+
let requestFrameSignal: (() => void) | null = null;
|
|
88
|
+
const requestFrame = (): void => {
|
|
89
|
+
requestFrameSignal?.();
|
|
90
|
+
};
|
|
91
|
+
const invalidateFrame = (token?: FrameInvalidationToken): void => {
|
|
92
|
+
registry.invalidate(token);
|
|
93
|
+
requestFrame();
|
|
94
|
+
};
|
|
95
|
+
const advanceFrame = (): void => {
|
|
96
|
+
registry.advance();
|
|
97
|
+
requestFrame();
|
|
98
|
+
};
|
|
99
|
+
const size = currentWritable({ width: 0, height: 0 });
|
|
100
|
+
const dprState = currentWritable(initialDpr, requestFrame);
|
|
101
|
+
const maxDeltaState = currentWritable<number>(0.1, (value) => {
|
|
102
|
+
registry.setMaxDelta(value);
|
|
103
|
+
requestFrame();
|
|
104
|
+
});
|
|
105
|
+
const renderModeState = currentWritable<RenderMode>('always', (value) => {
|
|
106
|
+
registry.setRenderMode(value);
|
|
107
|
+
requestFrame();
|
|
108
|
+
});
|
|
109
|
+
const autoRenderState = currentWritable<boolean>(true, (value) => {
|
|
110
|
+
registry.setAutoRender(value);
|
|
111
|
+
requestFrame();
|
|
112
|
+
});
|
|
113
|
+
const userState = currentWritable<Record<string | symbol, unknown>>({});
|
|
114
|
+
|
|
115
|
+
provideMotionGPUContext({
|
|
116
|
+
get canvas() {
|
|
117
|
+
return canvas;
|
|
118
|
+
},
|
|
119
|
+
size,
|
|
120
|
+
dpr: dprState,
|
|
121
|
+
maxDelta: maxDeltaState,
|
|
122
|
+
renderMode: renderModeState,
|
|
123
|
+
autoRender: autoRenderState,
|
|
124
|
+
user: userState,
|
|
125
|
+
invalidate: () => invalidateFrame(),
|
|
126
|
+
advance: advanceFrame,
|
|
127
|
+
scheduler: {
|
|
128
|
+
createStage: registry.createStage,
|
|
129
|
+
getStage: registry.getStage,
|
|
130
|
+
setDiagnosticsEnabled: registry.setDiagnosticsEnabled,
|
|
131
|
+
getDiagnosticsEnabled: registry.getDiagnosticsEnabled,
|
|
132
|
+
getLastRunTimings: registry.getLastRunTimings,
|
|
133
|
+
getSchedule: registry.getSchedule,
|
|
134
|
+
setProfilingEnabled: registry.setProfilingEnabled,
|
|
135
|
+
setProfilingWindow: registry.setProfilingWindow,
|
|
136
|
+
resetProfiling: registry.resetProfiling,
|
|
137
|
+
getProfilingEnabled: registry.getProfilingEnabled,
|
|
138
|
+
getProfilingWindow: registry.getProfilingWindow,
|
|
139
|
+
getProfilingSnapshot: registry.getProfilingSnapshot
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
$effect(() => {
|
|
144
|
+
renderModeState.set(renderMode);
|
|
145
|
+
autoRenderState.set(autoRender);
|
|
146
|
+
maxDeltaState.set(maxDelta);
|
|
147
|
+
dprState.set(dpr);
|
|
148
|
+
requestFrame();
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
$effect(() => {
|
|
152
|
+
const limit = normalizedErrorHistoryLimit;
|
|
153
|
+
if (limit <= 0) {
|
|
154
|
+
if (errorHistory.length === 0) {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
errorHistory = [];
|
|
158
|
+
onErrorHistory?.([]);
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (errorHistory.length <= limit) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const trimmed = errorHistory.slice(errorHistory.length - limit);
|
|
167
|
+
errorHistory = trimmed;
|
|
168
|
+
onErrorHistory?.(trimmed);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
onMount(() => {
|
|
172
|
+
if (!canvas) {
|
|
173
|
+
const report = toMotionGPUErrorReport(
|
|
174
|
+
new Error('Canvas element is not available'),
|
|
175
|
+
'initialization'
|
|
176
|
+
);
|
|
177
|
+
errorReport = report;
|
|
178
|
+
const historyLimit = normalizedErrorHistoryLimit;
|
|
179
|
+
if (historyLimit > 0) {
|
|
180
|
+
const nextHistory = [report].slice(-historyLimit);
|
|
181
|
+
errorHistory = nextHistory;
|
|
182
|
+
onErrorHistory?.(nextHistory);
|
|
183
|
+
}
|
|
184
|
+
onError?.(report);
|
|
185
|
+
return () => registry.clear();
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const runtimeLoop = createMotionGPURuntimeLoop({
|
|
189
|
+
canvas,
|
|
190
|
+
registry,
|
|
191
|
+
size,
|
|
192
|
+
dpr: dprState,
|
|
193
|
+
maxDelta: maxDeltaState,
|
|
194
|
+
getMaterial: () => material,
|
|
195
|
+
getRenderTargets: () => renderTargets,
|
|
196
|
+
getPasses: () => passes,
|
|
197
|
+
getClearColor: () => clearColor,
|
|
198
|
+
getOutputColorSpace: () => outputColorSpace,
|
|
199
|
+
getAdapterOptions: () => adapterOptions,
|
|
200
|
+
getDeviceDescriptor: () => deviceDescriptor,
|
|
201
|
+
getOnError: () => onError,
|
|
202
|
+
getErrorHistoryLimit: () => errorHistoryLimit,
|
|
203
|
+
getOnErrorHistory: () => onErrorHistory,
|
|
204
|
+
reportErrorHistory: (history) => {
|
|
205
|
+
errorHistory = history;
|
|
206
|
+
},
|
|
207
|
+
reportError: (report) => {
|
|
208
|
+
errorReport = report;
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
requestFrameSignal = runtimeLoop.requestFrame;
|
|
212
|
+
|
|
213
|
+
return () => {
|
|
214
|
+
requestFrameSignal = null;
|
|
215
|
+
runtimeLoop.destroy();
|
|
216
|
+
};
|
|
217
|
+
});
|
|
218
|
+
</script>
|
|
219
|
+
|
|
220
|
+
<div class="motiongpu-canvas-wrap">
|
|
221
|
+
<canvas {@attach bindCanvas} class={className} {style}></canvas>
|
|
222
|
+
{#if showErrorOverlay && errorReport}
|
|
223
|
+
{#if errorRenderer}
|
|
224
|
+
{@render errorRenderer(errorReport)}
|
|
225
|
+
{:else}
|
|
226
|
+
<MotionGPUErrorOverlay report={errorReport} />
|
|
227
|
+
{/if}
|
|
228
|
+
{/if}
|
|
229
|
+
{@render children?.()}
|
|
230
|
+
</div>
|
|
231
|
+
|
|
232
|
+
<style>
|
|
233
|
+
.motiongpu-canvas-wrap {
|
|
234
|
+
position: relative;
|
|
235
|
+
width: 100%;
|
|
236
|
+
height: 100%;
|
|
237
|
+
min-width: 0;
|
|
238
|
+
min-height: 0;
|
|
239
|
+
overflow: hidden;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
canvas {
|
|
243
|
+
position: absolute;
|
|
244
|
+
inset: 0;
|
|
245
|
+
display: block;
|
|
246
|
+
width: 100%;
|
|
247
|
+
height: 100%;
|
|
248
|
+
}
|
|
249
|
+
</style>
|