@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.
Files changed (228) hide show
  1. package/README.md +99 -0
  2. package/dist/advanced.d.ts +1 -0
  3. package/dist/advanced.d.ts.map +1 -0
  4. package/dist/advanced.js +14 -6
  5. package/dist/core/advanced.d.ts +1 -0
  6. package/dist/core/advanced.d.ts.map +1 -0
  7. package/dist/core/advanced.js +14 -5
  8. package/dist/core/compute-shader.d.ts +87 -0
  9. package/dist/core/compute-shader.d.ts.map +1 -0
  10. package/dist/core/compute-shader.js +205 -0
  11. package/dist/core/compute-shader.js.map +1 -0
  12. package/dist/core/current-value.d.ts +1 -0
  13. package/dist/core/current-value.d.ts.map +1 -0
  14. package/dist/core/current-value.js +35 -34
  15. package/dist/core/current-value.js.map +1 -0
  16. package/dist/core/error-diagnostics.d.ts +1 -0
  17. package/dist/core/error-diagnostics.d.ts.map +1 -0
  18. package/dist/core/error-diagnostics.js +70 -137
  19. package/dist/core/error-diagnostics.js.map +1 -0
  20. package/dist/core/error-report.d.ts +2 -1
  21. package/dist/core/error-report.d.ts.map +1 -0
  22. package/dist/core/error-report.js +247 -233
  23. package/dist/core/error-report.js.map +1 -0
  24. package/dist/core/frame-registry.d.ts +1 -0
  25. package/dist/core/frame-registry.d.ts.map +1 -0
  26. package/dist/core/frame-registry.js +546 -662
  27. package/dist/core/frame-registry.js.map +1 -0
  28. package/dist/core/index.d.ts +6 -2
  29. package/dist/core/index.d.ts.map +1 -0
  30. package/dist/core/index.js +13 -12
  31. package/dist/core/material-preprocess.d.ts +1 -0
  32. package/dist/core/material-preprocess.d.ts.map +1 -0
  33. package/dist/core/material-preprocess.js +131 -152
  34. package/dist/core/material-preprocess.js.map +1 -0
  35. package/dist/core/material.d.ts +23 -6
  36. package/dist/core/material.d.ts.map +1 -0
  37. package/dist/core/material.js +290 -317
  38. package/dist/core/material.js.map +1 -0
  39. package/dist/core/recompile-policy.d.ts +1 -0
  40. package/dist/core/recompile-policy.d.ts.map +1 -0
  41. package/dist/core/recompile-policy.js +18 -13
  42. package/dist/core/recompile-policy.js.map +1 -0
  43. package/dist/core/render-graph.d.ts +8 -3
  44. package/dist/core/render-graph.d.ts.map +1 -0
  45. package/dist/core/render-graph.js +77 -68
  46. package/dist/core/render-graph.js.map +1 -0
  47. package/dist/core/render-targets.d.ts +1 -0
  48. package/dist/core/render-targets.d.ts.map +1 -0
  49. package/dist/core/render-targets.js +52 -53
  50. package/dist/core/render-targets.js.map +1 -0
  51. package/dist/core/renderer.d.ts +1 -0
  52. package/dist/core/renderer.d.ts.map +1 -0
  53. package/dist/core/renderer.js +1337 -1081
  54. package/dist/core/renderer.js.map +1 -0
  55. package/dist/core/runtime-loop.d.ts +3 -2
  56. package/dist/core/runtime-loop.d.ts.map +1 -0
  57. package/dist/core/runtime-loop.js +353 -362
  58. package/dist/core/runtime-loop.js.map +1 -0
  59. package/dist/core/scheduler-helpers.d.ts +1 -0
  60. package/dist/core/scheduler-helpers.d.ts.map +1 -0
  61. package/dist/core/scheduler-helpers.js +52 -51
  62. package/dist/core/scheduler-helpers.js.map +1 -0
  63. package/dist/core/shader.d.ts +10 -1
  64. package/dist/core/shader.d.ts.map +1 -0
  65. package/dist/core/shader.js +109 -115
  66. package/dist/core/shader.js.map +1 -0
  67. package/dist/core/storage-buffers.d.ts +37 -0
  68. package/dist/core/storage-buffers.d.ts.map +1 -0
  69. package/dist/core/storage-buffers.js +95 -0
  70. package/dist/core/storage-buffers.js.map +1 -0
  71. package/dist/core/texture-loader.d.ts +1 -0
  72. package/dist/core/texture-loader.d.ts.map +1 -0
  73. package/dist/core/texture-loader.js +209 -273
  74. package/dist/core/texture-loader.js.map +1 -0
  75. package/dist/core/textures.d.ts +13 -0
  76. package/dist/core/textures.d.ts.map +1 -0
  77. package/dist/core/textures.js +111 -116
  78. package/dist/core/textures.js.map +1 -0
  79. package/dist/core/types.d.ts +147 -4
  80. package/dist/core/types.d.ts.map +1 -0
  81. package/dist/core/types.js +0 -4
  82. package/dist/core/uniforms.d.ts +1 -0
  83. package/dist/core/uniforms.d.ts.map +1 -0
  84. package/dist/core/uniforms.js +170 -191
  85. package/dist/core/uniforms.js.map +1 -0
  86. package/dist/index.d.ts +1 -0
  87. package/dist/index.d.ts.map +1 -0
  88. package/dist/index.js +13 -6
  89. package/dist/passes/BlitPass.d.ts +1 -0
  90. package/dist/passes/BlitPass.d.ts.map +1 -0
  91. package/dist/passes/BlitPass.js +23 -18
  92. package/dist/passes/BlitPass.js.map +1 -0
  93. package/dist/passes/ComputePass.d.ts +83 -0
  94. package/dist/passes/ComputePass.d.ts.map +1 -0
  95. package/dist/passes/ComputePass.js +92 -0
  96. package/dist/passes/ComputePass.js.map +1 -0
  97. package/dist/passes/CopyPass.d.ts +1 -0
  98. package/dist/passes/CopyPass.d.ts.map +1 -0
  99. package/dist/passes/CopyPass.js +58 -52
  100. package/dist/passes/CopyPass.js.map +1 -0
  101. package/dist/passes/FullscreenPass.d.ts +1 -0
  102. package/dist/passes/FullscreenPass.d.ts.map +1 -0
  103. package/dist/passes/FullscreenPass.js +127 -130
  104. package/dist/passes/FullscreenPass.js.map +1 -0
  105. package/dist/passes/PingPongComputePass.d.ts +104 -0
  106. package/dist/passes/PingPongComputePass.d.ts.map +1 -0
  107. package/dist/passes/PingPongComputePass.js +132 -0
  108. package/dist/passes/PingPongComputePass.js.map +1 -0
  109. package/dist/passes/ShaderPass.d.ts +1 -0
  110. package/dist/passes/ShaderPass.d.ts.map +1 -0
  111. package/dist/passes/ShaderPass.js +41 -37
  112. package/dist/passes/ShaderPass.js.map +1 -0
  113. package/dist/passes/index.d.ts +3 -0
  114. package/dist/passes/index.d.ts.map +1 -0
  115. package/dist/passes/index.js +6 -3
  116. package/dist/react/FragCanvas.d.ts +3 -2
  117. package/dist/react/FragCanvas.d.ts.map +1 -0
  118. package/dist/react/FragCanvas.js +234 -211
  119. package/dist/react/FragCanvas.js.map +1 -0
  120. package/dist/react/MotionGPUErrorOverlay.d.ts +1 -0
  121. package/dist/react/MotionGPUErrorOverlay.d.ts.map +1 -0
  122. package/dist/react/MotionGPUErrorOverlay.js +200 -14
  123. package/dist/react/MotionGPUErrorOverlay.js.map +1 -0
  124. package/dist/react/Portal.d.ts +1 -0
  125. package/dist/react/Portal.d.ts.map +1 -0
  126. package/dist/react/Portal.js +18 -21
  127. package/dist/react/Portal.js.map +1 -0
  128. package/dist/react/advanced.d.ts +1 -0
  129. package/dist/react/advanced.d.ts.map +1 -0
  130. package/dist/react/advanced.js +14 -6
  131. package/dist/react/frame-context.d.ts +1 -0
  132. package/dist/react/frame-context.d.ts.map +1 -0
  133. package/dist/react/frame-context.js +88 -94
  134. package/dist/react/frame-context.js.map +1 -0
  135. package/dist/react/index.d.ts +6 -2
  136. package/dist/react/index.d.ts.map +1 -0
  137. package/dist/react/index.js +12 -9
  138. package/dist/react/motiongpu-context.d.ts +1 -0
  139. package/dist/react/motiongpu-context.d.ts.map +1 -0
  140. package/dist/react/motiongpu-context.js +18 -15
  141. package/dist/react/motiongpu-context.js.map +1 -0
  142. package/dist/react/use-motiongpu-user-context.d.ts +1 -0
  143. package/dist/react/use-motiongpu-user-context.d.ts.map +1 -0
  144. package/dist/react/use-motiongpu-user-context.js +83 -82
  145. package/dist/react/use-motiongpu-user-context.js.map +1 -0
  146. package/dist/react/use-texture.d.ts +1 -0
  147. package/dist/react/use-texture.d.ts.map +1 -0
  148. package/dist/react/use-texture.js +132 -152
  149. package/dist/react/use-texture.js.map +1 -0
  150. package/dist/svelte/FragCanvas.svelte +2 -2
  151. package/dist/svelte/FragCanvas.svelte.d.ts +3 -2
  152. package/dist/svelte/FragCanvas.svelte.d.ts.map +1 -0
  153. package/dist/svelte/MotionGPUErrorOverlay.svelte +137 -7
  154. package/dist/svelte/MotionGPUErrorOverlay.svelte.d.ts +1 -0
  155. package/dist/svelte/MotionGPUErrorOverlay.svelte.d.ts.map +1 -0
  156. package/dist/svelte/Portal.svelte.d.ts +1 -0
  157. package/dist/svelte/Portal.svelte.d.ts.map +1 -0
  158. package/dist/svelte/advanced.d.ts +1 -0
  159. package/dist/svelte/advanced.d.ts.map +1 -0
  160. package/dist/svelte/advanced.js +13 -6
  161. package/dist/svelte/frame-context.d.ts +1 -0
  162. package/dist/svelte/frame-context.d.ts.map +1 -0
  163. package/dist/svelte/frame-context.js +27 -27
  164. package/dist/svelte/frame-context.js.map +1 -0
  165. package/dist/svelte/index.d.ts +6 -2
  166. package/dist/svelte/index.d.ts.map +1 -0
  167. package/dist/svelte/index.js +12 -9
  168. package/dist/svelte/motiongpu-context.d.ts +1 -0
  169. package/dist/svelte/motiongpu-context.d.ts.map +1 -0
  170. package/dist/svelte/motiongpu-context.js +24 -21
  171. package/dist/svelte/motiongpu-context.js.map +1 -0
  172. package/dist/svelte/use-motiongpu-user-context.d.ts +1 -0
  173. package/dist/svelte/use-motiongpu-user-context.d.ts.map +1 -0
  174. package/dist/svelte/use-motiongpu-user-context.js +69 -70
  175. package/dist/svelte/use-motiongpu-user-context.js.map +1 -0
  176. package/dist/svelte/use-texture.d.ts +1 -0
  177. package/dist/svelte/use-texture.d.ts.map +1 -0
  178. package/dist/svelte/use-texture.js +125 -147
  179. package/dist/svelte/use-texture.js.map +1 -0
  180. package/package.json +12 -7
  181. package/src/lib/advanced.ts +6 -0
  182. package/src/lib/core/advanced.ts +12 -0
  183. package/src/lib/core/compute-shader.ts +326 -0
  184. package/src/lib/core/current-value.ts +64 -0
  185. package/src/lib/core/error-diagnostics.ts +236 -0
  186. package/src/lib/core/error-report.ts +535 -0
  187. package/src/lib/core/frame-registry.ts +1190 -0
  188. package/src/lib/core/index.ts +94 -0
  189. package/src/lib/core/material-preprocess.ts +295 -0
  190. package/src/lib/core/material.ts +748 -0
  191. package/src/lib/core/recompile-policy.ts +31 -0
  192. package/src/lib/core/render-graph.ts +173 -0
  193. package/src/lib/core/render-targets.ts +107 -0
  194. package/src/lib/core/renderer.ts +2161 -0
  195. package/src/lib/core/runtime-loop.ts +537 -0
  196. package/src/lib/core/scheduler-helpers.ts +136 -0
  197. package/src/lib/core/shader.ts +301 -0
  198. package/src/lib/core/storage-buffers.ts +142 -0
  199. package/src/lib/core/texture-loader.ts +482 -0
  200. package/src/lib/core/textures.ts +257 -0
  201. package/src/lib/core/types.ts +743 -0
  202. package/src/lib/core/uniforms.ts +282 -0
  203. package/src/lib/index.ts +6 -0
  204. package/src/lib/passes/BlitPass.ts +54 -0
  205. package/src/lib/passes/ComputePass.ts +136 -0
  206. package/src/lib/passes/CopyPass.ts +80 -0
  207. package/src/lib/passes/FullscreenPass.ts +173 -0
  208. package/src/lib/passes/PingPongComputePass.ts +180 -0
  209. package/src/lib/passes/ShaderPass.ts +89 -0
  210. package/src/lib/passes/index.ts +9 -0
  211. package/src/lib/react/FragCanvas.tsx +345 -0
  212. package/src/lib/react/MotionGPUErrorOverlay.tsx +524 -0
  213. package/src/lib/react/Portal.tsx +34 -0
  214. package/src/lib/react/advanced.ts +36 -0
  215. package/src/lib/react/frame-context.ts +169 -0
  216. package/src/lib/react/index.ts +68 -0
  217. package/src/lib/react/motiongpu-context.ts +88 -0
  218. package/src/lib/react/use-motiongpu-user-context.ts +186 -0
  219. package/src/lib/react/use-texture.ts +233 -0
  220. package/src/lib/svelte/FragCanvas.svelte +249 -0
  221. package/src/lib/svelte/MotionGPUErrorOverlay.svelte +512 -0
  222. package/src/lib/svelte/Portal.svelte +31 -0
  223. package/src/lib/svelte/advanced.ts +32 -0
  224. package/src/lib/svelte/frame-context.ts +87 -0
  225. package/src/lib/svelte/index.ts +68 -0
  226. package/src/lib/svelte/motiongpu-context.ts +97 -0
  227. package/src/lib/svelte/use-motiongpu-user-context.ts +145 -0
  228. 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>