@motion-core/motion-gpu 0.4.1 → 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.
Files changed (205) hide show
  1. package/dist/advanced.d.ts +1 -0
  2. package/dist/advanced.d.ts.map +1 -0
  3. package/dist/advanced.js +12 -6
  4. package/dist/core/advanced.d.ts +1 -0
  5. package/dist/core/advanced.d.ts.map +1 -0
  6. package/dist/core/advanced.js +12 -5
  7. package/dist/core/current-value.d.ts +1 -0
  8. package/dist/core/current-value.d.ts.map +1 -0
  9. package/dist/core/current-value.js +35 -34
  10. package/dist/core/current-value.js.map +1 -0
  11. package/dist/core/error-diagnostics.d.ts +1 -0
  12. package/dist/core/error-diagnostics.d.ts.map +1 -0
  13. package/dist/core/error-diagnostics.js +70 -137
  14. package/dist/core/error-diagnostics.js.map +1 -0
  15. package/dist/core/error-report.d.ts +1 -0
  16. package/dist/core/error-report.d.ts.map +1 -0
  17. package/dist/core/error-report.js +184 -233
  18. package/dist/core/error-report.js.map +1 -0
  19. package/dist/core/frame-registry.d.ts +1 -0
  20. package/dist/core/frame-registry.d.ts.map +1 -0
  21. package/dist/core/frame-registry.js +546 -662
  22. package/dist/core/frame-registry.js.map +1 -0
  23. package/dist/core/index.d.ts +1 -0
  24. package/dist/core/index.d.ts.map +1 -0
  25. package/dist/core/index.js +11 -12
  26. package/dist/core/material-preprocess.d.ts +1 -0
  27. package/dist/core/material-preprocess.d.ts.map +1 -0
  28. package/dist/core/material-preprocess.js +128 -151
  29. package/dist/core/material-preprocess.js.map +1 -0
  30. package/dist/core/material.d.ts +1 -0
  31. package/dist/core/material.d.ts.map +1 -0
  32. package/dist/core/material.js +263 -317
  33. package/dist/core/material.js.map +1 -0
  34. package/dist/core/recompile-policy.d.ts +1 -0
  35. package/dist/core/recompile-policy.d.ts.map +1 -0
  36. package/dist/core/recompile-policy.js +18 -13
  37. package/dist/core/recompile-policy.js.map +1 -0
  38. package/dist/core/render-graph.d.ts +1 -0
  39. package/dist/core/render-graph.d.ts.map +1 -0
  40. package/dist/core/render-graph.js +61 -68
  41. package/dist/core/render-graph.js.map +1 -0
  42. package/dist/core/render-targets.d.ts +1 -0
  43. package/dist/core/render-targets.d.ts.map +1 -0
  44. package/dist/core/render-targets.js +52 -53
  45. package/dist/core/render-targets.js.map +1 -0
  46. package/dist/core/renderer.d.ts +1 -0
  47. package/dist/core/renderer.d.ts.map +1 -0
  48. package/dist/core/renderer.js +942 -1081
  49. package/dist/core/renderer.js.map +1 -0
  50. package/dist/core/runtime-loop.d.ts +1 -0
  51. package/dist/core/runtime-loop.d.ts.map +1 -0
  52. package/dist/core/runtime-loop.js +305 -362
  53. package/dist/core/runtime-loop.js.map +1 -0
  54. package/dist/core/scheduler-helpers.d.ts +1 -0
  55. package/dist/core/scheduler-helpers.d.ts.map +1 -0
  56. package/dist/core/scheduler-helpers.js +52 -51
  57. package/dist/core/scheduler-helpers.js.map +1 -0
  58. package/dist/core/shader.d.ts +1 -0
  59. package/dist/core/shader.d.ts.map +1 -0
  60. package/dist/core/shader.js +92 -117
  61. package/dist/core/shader.js.map +1 -0
  62. package/dist/core/texture-loader.d.ts +1 -0
  63. package/dist/core/texture-loader.d.ts.map +1 -0
  64. package/dist/core/texture-loader.js +205 -273
  65. package/dist/core/texture-loader.js.map +1 -0
  66. package/dist/core/textures.d.ts +1 -0
  67. package/dist/core/textures.d.ts.map +1 -0
  68. package/dist/core/textures.js +106 -116
  69. package/dist/core/textures.js.map +1 -0
  70. package/dist/core/types.d.ts +1 -0
  71. package/dist/core/types.d.ts.map +1 -0
  72. package/dist/core/types.js +0 -4
  73. package/dist/core/uniforms.d.ts +1 -0
  74. package/dist/core/uniforms.d.ts.map +1 -0
  75. package/dist/core/uniforms.js +170 -191
  76. package/dist/core/uniforms.js.map +1 -0
  77. package/dist/index.d.ts +1 -0
  78. package/dist/index.d.ts.map +1 -0
  79. package/dist/index.js +11 -6
  80. package/dist/passes/BlitPass.d.ts +1 -0
  81. package/dist/passes/BlitPass.d.ts.map +1 -0
  82. package/dist/passes/BlitPass.js +23 -18
  83. package/dist/passes/BlitPass.js.map +1 -0
  84. package/dist/passes/CopyPass.d.ts +1 -0
  85. package/dist/passes/CopyPass.d.ts.map +1 -0
  86. package/dist/passes/CopyPass.js +58 -52
  87. package/dist/passes/CopyPass.js.map +1 -0
  88. package/dist/passes/FullscreenPass.d.ts +1 -0
  89. package/dist/passes/FullscreenPass.d.ts.map +1 -0
  90. package/dist/passes/FullscreenPass.js +127 -130
  91. package/dist/passes/FullscreenPass.js.map +1 -0
  92. package/dist/passes/ShaderPass.d.ts +1 -0
  93. package/dist/passes/ShaderPass.d.ts.map +1 -0
  94. package/dist/passes/ShaderPass.js +40 -37
  95. package/dist/passes/ShaderPass.js.map +1 -0
  96. package/dist/passes/index.d.ts +1 -0
  97. package/dist/passes/index.d.ts.map +1 -0
  98. package/dist/passes/index.js +4 -3
  99. package/dist/react/FragCanvas.d.ts +1 -0
  100. package/dist/react/FragCanvas.d.ts.map +1 -0
  101. package/dist/react/FragCanvas.js +234 -211
  102. package/dist/react/FragCanvas.js.map +1 -0
  103. package/dist/react/MotionGPUErrorOverlay.d.ts +1 -0
  104. package/dist/react/MotionGPUErrorOverlay.d.ts.map +1 -0
  105. package/dist/react/MotionGPUErrorOverlay.js +96 -13
  106. package/dist/react/MotionGPUErrorOverlay.js.map +1 -0
  107. package/dist/react/Portal.d.ts +1 -0
  108. package/dist/react/Portal.d.ts.map +1 -0
  109. package/dist/react/Portal.js +18 -21
  110. package/dist/react/Portal.js.map +1 -0
  111. package/dist/react/advanced.d.ts +1 -0
  112. package/dist/react/advanced.d.ts.map +1 -0
  113. package/dist/react/advanced.js +12 -6
  114. package/dist/react/frame-context.d.ts +1 -0
  115. package/dist/react/frame-context.d.ts.map +1 -0
  116. package/dist/react/frame-context.js +88 -94
  117. package/dist/react/frame-context.js.map +1 -0
  118. package/dist/react/index.d.ts +1 -0
  119. package/dist/react/index.d.ts.map +1 -0
  120. package/dist/react/index.js +10 -9
  121. package/dist/react/motiongpu-context.d.ts +1 -0
  122. package/dist/react/motiongpu-context.d.ts.map +1 -0
  123. package/dist/react/motiongpu-context.js +18 -15
  124. package/dist/react/motiongpu-context.js.map +1 -0
  125. package/dist/react/use-motiongpu-user-context.d.ts +1 -0
  126. package/dist/react/use-motiongpu-user-context.d.ts.map +1 -0
  127. package/dist/react/use-motiongpu-user-context.js +83 -82
  128. package/dist/react/use-motiongpu-user-context.js.map +1 -0
  129. package/dist/react/use-texture.d.ts +1 -0
  130. package/dist/react/use-texture.d.ts.map +1 -0
  131. package/dist/react/use-texture.js +132 -152
  132. package/dist/react/use-texture.js.map +1 -0
  133. package/dist/svelte/FragCanvas.svelte.d.ts +1 -0
  134. package/dist/svelte/FragCanvas.svelte.d.ts.map +1 -0
  135. package/dist/svelte/MotionGPUErrorOverlay.svelte.d.ts +1 -0
  136. package/dist/svelte/MotionGPUErrorOverlay.svelte.d.ts.map +1 -0
  137. package/dist/svelte/Portal.svelte.d.ts +1 -0
  138. package/dist/svelte/Portal.svelte.d.ts.map +1 -0
  139. package/dist/svelte/advanced.d.ts +1 -0
  140. package/dist/svelte/advanced.d.ts.map +1 -0
  141. package/dist/svelte/advanced.js +11 -6
  142. package/dist/svelte/frame-context.d.ts +1 -0
  143. package/dist/svelte/frame-context.d.ts.map +1 -0
  144. package/dist/svelte/frame-context.js +27 -27
  145. package/dist/svelte/frame-context.js.map +1 -0
  146. package/dist/svelte/index.d.ts +1 -0
  147. package/dist/svelte/index.d.ts.map +1 -0
  148. package/dist/svelte/index.js +10 -9
  149. package/dist/svelte/motiongpu-context.d.ts +1 -0
  150. package/dist/svelte/motiongpu-context.d.ts.map +1 -0
  151. package/dist/svelte/motiongpu-context.js +24 -21
  152. package/dist/svelte/motiongpu-context.js.map +1 -0
  153. package/dist/svelte/use-motiongpu-user-context.d.ts +1 -0
  154. package/dist/svelte/use-motiongpu-user-context.d.ts.map +1 -0
  155. package/dist/svelte/use-motiongpu-user-context.js +69 -70
  156. package/dist/svelte/use-motiongpu-user-context.js.map +1 -0
  157. package/dist/svelte/use-texture.d.ts +1 -0
  158. package/dist/svelte/use-texture.d.ts.map +1 -0
  159. package/dist/svelte/use-texture.js +125 -147
  160. package/dist/svelte/use-texture.js.map +1 -0
  161. package/package.json +12 -7
  162. package/src/lib/advanced.ts +6 -0
  163. package/src/lib/core/advanced.ts +12 -0
  164. package/src/lib/core/current-value.ts +64 -0
  165. package/src/lib/core/error-diagnostics.ts +236 -0
  166. package/src/lib/core/error-report.ts +406 -0
  167. package/src/lib/core/frame-registry.ts +1189 -0
  168. package/src/lib/core/index.ts +77 -0
  169. package/src/lib/core/material-preprocess.ts +284 -0
  170. package/src/lib/core/material.ts +667 -0
  171. package/src/lib/core/recompile-policy.ts +31 -0
  172. package/src/lib/core/render-graph.ts +143 -0
  173. package/src/lib/core/render-targets.ts +107 -0
  174. package/src/lib/core/renderer.ts +1547 -0
  175. package/src/lib/core/runtime-loop.ts +458 -0
  176. package/src/lib/core/scheduler-helpers.ts +136 -0
  177. package/src/lib/core/shader.ts +258 -0
  178. package/src/lib/core/texture-loader.ts +476 -0
  179. package/src/lib/core/textures.ts +235 -0
  180. package/src/lib/core/types.ts +582 -0
  181. package/src/lib/core/uniforms.ts +282 -0
  182. package/src/lib/index.ts +6 -0
  183. package/src/lib/passes/BlitPass.ts +54 -0
  184. package/src/lib/passes/CopyPass.ts +80 -0
  185. package/src/lib/passes/FullscreenPass.ts +173 -0
  186. package/src/lib/passes/ShaderPass.ts +88 -0
  187. package/src/lib/passes/index.ts +3 -0
  188. package/src/lib/react/FragCanvas.tsx +345 -0
  189. package/src/lib/react/MotionGPUErrorOverlay.tsx +392 -0
  190. package/src/lib/react/Portal.tsx +34 -0
  191. package/src/lib/react/advanced.ts +36 -0
  192. package/src/lib/react/frame-context.ts +169 -0
  193. package/src/lib/react/index.ts +51 -0
  194. package/src/lib/react/motiongpu-context.ts +88 -0
  195. package/src/lib/react/use-motiongpu-user-context.ts +186 -0
  196. package/src/lib/react/use-texture.ts +233 -0
  197. package/src/lib/svelte/FragCanvas.svelte +249 -0
  198. package/src/lib/svelte/MotionGPUErrorOverlay.svelte +382 -0
  199. package/src/lib/svelte/Portal.svelte +31 -0
  200. package/src/lib/svelte/advanced.ts +32 -0
  201. package/src/lib/svelte/frame-context.ts +87 -0
  202. package/src/lib/svelte/index.ts +51 -0
  203. package/src/lib/svelte/motiongpu-context.ts +97 -0
  204. package/src/lib/svelte/use-motiongpu-user-context.ts +145 -0
  205. package/src/lib/svelte/use-texture.ts +232 -0
@@ -0,0 +1,458 @@
1
+ import type { CurrentReadable, CurrentWritable } from './current-value.js';
2
+ import { resolveMaterial, type FragMaterial, type ResolvedMaterial } from './material.js';
3
+ import {
4
+ toMotionGPUErrorReport,
5
+ type MotionGPUErrorPhase,
6
+ type MotionGPUErrorReport
7
+ } from './error-report.js';
8
+ import { createRenderer } from './renderer.js';
9
+ import { buildRendererPipelineSignature } from './recompile-policy.js';
10
+ import { assertUniformValueForType } from './uniforms.js';
11
+ import type { FrameRegistry } from './frame-registry.js';
12
+ import type {
13
+ FrameInvalidationToken,
14
+ OutputColorSpace,
15
+ RenderPass,
16
+ Renderer,
17
+ RenderTargetDefinitionMap,
18
+ TextureMap,
19
+ TextureValue,
20
+ UniformType,
21
+ UniformValue
22
+ } from './types.js';
23
+
24
+ export interface MotionGPURuntimeLoopOptions {
25
+ canvas: HTMLCanvasElement;
26
+ registry: FrameRegistry;
27
+ size: CurrentWritable<{ width: number; height: number }>;
28
+ dpr: CurrentReadable<number>;
29
+ maxDelta: CurrentReadable<number>;
30
+ getMaterial: () => FragMaterial;
31
+ getRenderTargets: () => RenderTargetDefinitionMap;
32
+ getPasses: () => RenderPass[];
33
+ getClearColor: () => [number, number, number, number];
34
+ getOutputColorSpace: () => OutputColorSpace;
35
+ getAdapterOptions: () => GPURequestAdapterOptions | undefined;
36
+ getDeviceDescriptor: () => GPUDeviceDescriptor | undefined;
37
+ getOnError: () => ((report: MotionGPUErrorReport) => void) | undefined;
38
+ reportError: (report: MotionGPUErrorReport | null) => void;
39
+ getErrorHistoryLimit?: () => number | undefined;
40
+ getOnErrorHistory?: () => ((history: MotionGPUErrorReport[]) => void) | undefined;
41
+ reportErrorHistory?: (history: MotionGPUErrorReport[]) => void;
42
+ }
43
+
44
+ export interface MotionGPURuntimeLoop {
45
+ requestFrame: () => void;
46
+ invalidate: (token?: FrameInvalidationToken) => void;
47
+ advance: () => void;
48
+ destroy: () => void;
49
+ }
50
+
51
+ function getRendererRetryDelayMs(attempt: number): number {
52
+ return Math.min(8000, 250 * 2 ** Math.max(0, attempt - 1));
53
+ }
54
+
55
+ export function createMotionGPURuntimeLoop(
56
+ options: MotionGPURuntimeLoopOptions
57
+ ): MotionGPURuntimeLoop {
58
+ const { canvas: canvasElement, registry, size } = options;
59
+ let frameId: number | null = null;
60
+ let renderer: Renderer | null = null;
61
+ let isDisposed = false;
62
+ let previousTime = performance.now() / 1000;
63
+ let activeRendererSignature = '';
64
+ let failedRendererSignature: string | null = null;
65
+ let failedRendererAttempts = 0;
66
+ let nextRendererRetryAt = 0;
67
+ let rendererRebuildPromise: Promise<void> | null = null;
68
+
69
+ const runtimeUniforms: Record<string, UniformValue> = {};
70
+ const runtimeTextures: TextureMap = {};
71
+ let activeUniforms: Record<string, UniformValue> = {};
72
+ let activeTextures: Record<string, { source?: TextureValue }> = {};
73
+ let uniformKeys: string[] = [];
74
+ let uniformKeySet = new Set<string>();
75
+ let uniformTypes = new Map<string, UniformType>();
76
+ let textureKeys: string[] = [];
77
+ let textureKeySet = new Set<string>();
78
+ let activeMaterialSignature = '';
79
+ let currentCssWidth = -1;
80
+ let currentCssHeight = -1;
81
+ const renderUniforms: Record<string, UniformValue> = {};
82
+ const renderTextures: TextureMap = {};
83
+ const canvasSize = { width: 0, height: 0 };
84
+ let shouldContinueAfterFrame = false;
85
+ let activeErrorKey: string | null = null;
86
+ let errorHistory: MotionGPUErrorReport[] = [];
87
+
88
+ const getHistoryLimit = (): number => {
89
+ const value = options.getErrorHistoryLimit?.() ?? 0;
90
+ if (!Number.isFinite(value) || value <= 0) {
91
+ return 0;
92
+ }
93
+
94
+ return Math.floor(value);
95
+ };
96
+
97
+ const publishErrorHistory = (): void => {
98
+ options.reportErrorHistory?.(errorHistory);
99
+ const onErrorHistory = options.getOnErrorHistory?.();
100
+ if (!onErrorHistory) {
101
+ return;
102
+ }
103
+
104
+ try {
105
+ onErrorHistory(errorHistory);
106
+ } catch {
107
+ // User-provided error history handlers must not break runtime error recovery.
108
+ }
109
+ };
110
+
111
+ const syncErrorHistory = (): void => {
112
+ const limit = getHistoryLimit();
113
+ if (limit <= 0) {
114
+ if (errorHistory.length === 0) {
115
+ return;
116
+ }
117
+ errorHistory = [];
118
+ publishErrorHistory();
119
+ return;
120
+ }
121
+
122
+ if (errorHistory.length <= limit) {
123
+ return;
124
+ }
125
+
126
+ errorHistory = errorHistory.slice(errorHistory.length - limit);
127
+ publishErrorHistory();
128
+ };
129
+
130
+ const setError = (error: unknown, phase: MotionGPUErrorPhase): void => {
131
+ const report = toMotionGPUErrorReport(error, phase);
132
+ const reportKey = JSON.stringify({
133
+ phase: report.phase,
134
+ title: report.title,
135
+ message: report.message,
136
+ rawMessage: report.rawMessage
137
+ });
138
+ if (activeErrorKey === reportKey) {
139
+ return;
140
+ }
141
+ activeErrorKey = reportKey;
142
+ const historyLimit = getHistoryLimit();
143
+ if (historyLimit > 0) {
144
+ errorHistory = [...errorHistory, report];
145
+ if (errorHistory.length > historyLimit) {
146
+ errorHistory = errorHistory.slice(errorHistory.length - historyLimit);
147
+ }
148
+ publishErrorHistory();
149
+ }
150
+ options.reportError(report);
151
+ const onError = options.getOnError();
152
+ if (!onError) {
153
+ return;
154
+ }
155
+
156
+ try {
157
+ onError(report);
158
+ } catch {
159
+ // User-provided error handlers must not break runtime error recovery.
160
+ }
161
+ };
162
+
163
+ const clearError = (): void => {
164
+ if (activeErrorKey === null) {
165
+ return;
166
+ }
167
+
168
+ activeErrorKey = null;
169
+ options.reportError(null);
170
+ };
171
+
172
+ const scheduleFrame = (): void => {
173
+ if (isDisposed || frameId !== null) {
174
+ return;
175
+ }
176
+
177
+ frameId = requestAnimationFrame(renderFrame);
178
+ };
179
+
180
+ const requestFrame = (): void => {
181
+ scheduleFrame();
182
+ };
183
+
184
+ const invalidate = (token?: FrameInvalidationToken): void => {
185
+ registry.invalidate(token);
186
+ requestFrame();
187
+ };
188
+
189
+ const advance = (): void => {
190
+ registry.advance();
191
+ requestFrame();
192
+ };
193
+
194
+ const resetRuntimeMaps = (): void => {
195
+ for (const key of Object.keys(runtimeUniforms)) {
196
+ if (!uniformKeySet.has(key)) {
197
+ Reflect.deleteProperty(runtimeUniforms, key);
198
+ }
199
+ }
200
+
201
+ for (const key of Object.keys(runtimeTextures)) {
202
+ if (!textureKeySet.has(key)) {
203
+ Reflect.deleteProperty(runtimeTextures, key);
204
+ }
205
+ }
206
+ };
207
+
208
+ const resetRenderPayloadMaps = (): void => {
209
+ for (const key of Object.keys(renderUniforms)) {
210
+ if (!uniformKeySet.has(key)) {
211
+ Reflect.deleteProperty(renderUniforms, key);
212
+ }
213
+ }
214
+
215
+ for (const key of Object.keys(renderTextures)) {
216
+ if (!textureKeySet.has(key)) {
217
+ Reflect.deleteProperty(renderTextures, key);
218
+ }
219
+ }
220
+ };
221
+
222
+ const syncMaterialRuntimeState = (materialState: ResolvedMaterial): void => {
223
+ const signatureChanged = activeMaterialSignature !== materialState.signature;
224
+ const defaultsChanged =
225
+ activeUniforms !== materialState.uniforms || activeTextures !== materialState.textures;
226
+
227
+ if (!signatureChanged && !defaultsChanged) {
228
+ return;
229
+ }
230
+
231
+ activeUniforms = materialState.uniforms;
232
+ activeTextures = materialState.textures;
233
+ if (!signatureChanged) {
234
+ return;
235
+ }
236
+
237
+ uniformKeys = materialState.uniformLayout.entries.map((entry) => entry.name);
238
+ uniformTypes = new Map(
239
+ materialState.uniformLayout.entries.map((entry) => [entry.name, entry.type])
240
+ );
241
+ textureKeys = materialState.textureKeys;
242
+ uniformKeySet = new Set(uniformKeys);
243
+ textureKeySet = new Set(textureKeys);
244
+ resetRuntimeMaps();
245
+ resetRenderPayloadMaps();
246
+ activeMaterialSignature = materialState.signature;
247
+ };
248
+
249
+ const resolveActiveMaterial = (): ResolvedMaterial => {
250
+ return resolveMaterial(options.getMaterial());
251
+ };
252
+
253
+ const setUniform = (name: string, value: UniformValue): void => {
254
+ if (!uniformKeySet.has(name)) {
255
+ throw new Error(`Unknown uniform "${name}". Declare it in material.uniforms first.`);
256
+ }
257
+ const expectedType = uniformTypes.get(name);
258
+ if (!expectedType) {
259
+ throw new Error(`Unknown uniform type for "${name}"`);
260
+ }
261
+ assertUniformValueForType(expectedType, value);
262
+ runtimeUniforms[name] = value;
263
+ };
264
+
265
+ const setTexture = (name: string, value: TextureValue): void => {
266
+ if (!textureKeySet.has(name)) {
267
+ throw new Error(`Unknown texture "${name}". Declare it in material.textures first.`);
268
+ }
269
+ runtimeTextures[name] = value;
270
+ };
271
+
272
+ const renderFrame = (timestamp: number): void => {
273
+ frameId = null;
274
+ if (isDisposed) {
275
+ return;
276
+ }
277
+ syncErrorHistory();
278
+
279
+ let materialState: ResolvedMaterial;
280
+ try {
281
+ materialState = resolveActiveMaterial();
282
+ } catch (error) {
283
+ setError(error, 'initialization');
284
+ scheduleFrame();
285
+ return;
286
+ }
287
+
288
+ shouldContinueAfterFrame = false;
289
+
290
+ const outputColorSpace = options.getOutputColorSpace();
291
+ const rendererSignature = buildRendererPipelineSignature({
292
+ materialSignature: materialState.signature,
293
+ outputColorSpace
294
+ });
295
+ syncMaterialRuntimeState(materialState);
296
+
297
+ if (failedRendererSignature && failedRendererSignature !== rendererSignature) {
298
+ failedRendererSignature = null;
299
+ failedRendererAttempts = 0;
300
+ nextRendererRetryAt = 0;
301
+ }
302
+
303
+ if (!renderer || activeRendererSignature !== rendererSignature) {
304
+ if (
305
+ failedRendererSignature === rendererSignature &&
306
+ performance.now() < nextRendererRetryAt
307
+ ) {
308
+ scheduleFrame();
309
+ return;
310
+ }
311
+
312
+ if (!rendererRebuildPromise) {
313
+ rendererRebuildPromise = (async () => {
314
+ try {
315
+ const nextRenderer = await createRenderer({
316
+ canvas: canvasElement,
317
+ fragmentWgsl: materialState.fragmentWgsl,
318
+ fragmentLineMap: materialState.fragmentLineMap,
319
+ fragmentSource: materialState.fragmentSource,
320
+ includeSources: materialState.includeSources,
321
+ defineBlockSource: materialState.defineBlockSource,
322
+ materialSource: materialState.source,
323
+ materialSignature: materialState.signature,
324
+ uniformLayout: materialState.uniformLayout,
325
+ textureKeys: materialState.textureKeys,
326
+ textureDefinitions: materialState.textures,
327
+ getRenderTargets: options.getRenderTargets,
328
+ getPasses: options.getPasses,
329
+ outputColorSpace,
330
+ getClearColor: options.getClearColor,
331
+ getDpr: () => options.dpr.current,
332
+ adapterOptions: options.getAdapterOptions(),
333
+ deviceDescriptor: options.getDeviceDescriptor()
334
+ });
335
+
336
+ if (isDisposed) {
337
+ nextRenderer.destroy();
338
+ return;
339
+ }
340
+
341
+ renderer?.destroy();
342
+ renderer = nextRenderer;
343
+ activeRendererSignature = rendererSignature;
344
+ failedRendererSignature = null;
345
+ failedRendererAttempts = 0;
346
+ nextRendererRetryAt = 0;
347
+ clearError();
348
+ } catch (error) {
349
+ failedRendererSignature = rendererSignature;
350
+ failedRendererAttempts += 1;
351
+ const retryDelayMs = getRendererRetryDelayMs(failedRendererAttempts);
352
+ nextRendererRetryAt = performance.now() + retryDelayMs;
353
+ setError(error, 'initialization');
354
+ } finally {
355
+ rendererRebuildPromise = null;
356
+ scheduleFrame();
357
+ }
358
+ })();
359
+ }
360
+
361
+ return;
362
+ }
363
+
364
+ const time = timestamp / 1000;
365
+ const rawDelta = Math.max(0, time - previousTime);
366
+ const delta = Math.min(rawDelta, options.maxDelta.current);
367
+ previousTime = time;
368
+ const rect = canvasElement.getBoundingClientRect();
369
+ const width = Math.max(0, Math.floor(rect.width));
370
+ const height = Math.max(0, Math.floor(rect.height));
371
+ if (width !== currentCssWidth || height !== currentCssHeight) {
372
+ currentCssWidth = width;
373
+ currentCssHeight = height;
374
+ size.set({ width, height });
375
+ }
376
+
377
+ try {
378
+ registry.run({
379
+ time,
380
+ delta,
381
+ setUniform,
382
+ setTexture,
383
+ invalidate,
384
+ advance,
385
+ renderMode: registry.getRenderMode(),
386
+ autoRender: registry.getAutoRender(),
387
+ canvas: canvasElement
388
+ });
389
+
390
+ const shouldRenderFrame = registry.shouldRender();
391
+ shouldContinueAfterFrame =
392
+ registry.getRenderMode() === 'always' ||
393
+ (registry.getRenderMode() === 'on-demand' && shouldRenderFrame);
394
+
395
+ if (shouldRenderFrame) {
396
+ for (const key of uniformKeys) {
397
+ const runtimeValue = runtimeUniforms[key];
398
+ renderUniforms[key] =
399
+ runtimeValue === undefined ? (activeUniforms[key] as UniformValue) : runtimeValue;
400
+ }
401
+
402
+ for (const key of textureKeys) {
403
+ const runtimeValue = runtimeTextures[key];
404
+ renderTextures[key] =
405
+ runtimeValue === undefined ? (activeTextures[key]?.source ?? null) : runtimeValue;
406
+ }
407
+
408
+ canvasSize.width = width;
409
+ canvasSize.height = height;
410
+ renderer.render({
411
+ time,
412
+ delta,
413
+ renderMode: registry.getRenderMode(),
414
+ uniforms: renderUniforms,
415
+ textures: renderTextures,
416
+ canvasSize
417
+ });
418
+ }
419
+
420
+ clearError();
421
+ } catch (error) {
422
+ setError(error, 'render');
423
+ } finally {
424
+ registry.endFrame();
425
+ }
426
+
427
+ if (shouldContinueAfterFrame) {
428
+ scheduleFrame();
429
+ }
430
+ };
431
+
432
+ (async () => {
433
+ try {
434
+ const initialMaterial = resolveActiveMaterial();
435
+ syncMaterialRuntimeState(initialMaterial);
436
+ activeRendererSignature = '';
437
+ scheduleFrame();
438
+ } catch (error) {
439
+ setError(error, 'initialization');
440
+ scheduleFrame();
441
+ }
442
+ })();
443
+
444
+ return {
445
+ requestFrame,
446
+ invalidate,
447
+ advance,
448
+ destroy: () => {
449
+ isDisposed = true;
450
+ if (frameId !== null) {
451
+ cancelAnimationFrame(frameId);
452
+ frameId = null;
453
+ }
454
+ renderer?.destroy();
455
+ registry.clear();
456
+ }
457
+ };
458
+ }
@@ -0,0 +1,136 @@
1
+ import type {
2
+ FrameProfilingSnapshot,
3
+ FrameRegistry,
4
+ FrameRunTimings,
5
+ FrameScheduleSnapshot
6
+ } from './frame-registry.js';
7
+
8
+ /**
9
+ * Public scheduler control surface shared by framework adapters.
10
+ */
11
+ export type MotionGPUScheduler = Pick<
12
+ FrameRegistry,
13
+ | 'createStage'
14
+ | 'getStage'
15
+ | 'setDiagnosticsEnabled'
16
+ | 'getDiagnosticsEnabled'
17
+ | 'getLastRunTimings'
18
+ | 'getSchedule'
19
+ | 'setProfilingEnabled'
20
+ | 'setProfilingWindow'
21
+ | 'resetProfiling'
22
+ | 'getProfilingEnabled'
23
+ | 'getProfilingWindow'
24
+ | 'getProfilingSnapshot'
25
+ >;
26
+
27
+ /**
28
+ * Named scheduler presets exposed from advanced entrypoints.
29
+ */
30
+ export type SchedulerPreset = 'balanced' | 'debug' | 'performance';
31
+
32
+ /**
33
+ * Resolved scheduler timing configuration.
34
+ *
35
+ * Note: diagnostics and profiling currently share one internal toggle in the frame registry.
36
+ */
37
+ export interface SchedulerPresetConfig {
38
+ diagnosticsEnabled: boolean;
39
+ profilingEnabled: boolean;
40
+ profilingWindow: number;
41
+ }
42
+
43
+ /**
44
+ * Optional overrides applied on top of a named scheduler preset.
45
+ */
46
+ export interface ApplySchedulerPresetOptions {
47
+ diagnosticsEnabled?: boolean;
48
+ profilingEnabled?: boolean;
49
+ profilingWindow?: number;
50
+ }
51
+
52
+ /**
53
+ * Snapshot payload useful for scheduler diagnostics UIs and debug tooling.
54
+ */
55
+ export interface SchedulerDebugSnapshot {
56
+ diagnosticsEnabled: boolean;
57
+ profilingEnabled: boolean;
58
+ profilingWindow: number;
59
+ schedule: FrameScheduleSnapshot;
60
+ lastRunTimings: FrameRunTimings | null;
61
+ profilingSnapshot: FrameProfilingSnapshot | null;
62
+ }
63
+
64
+ const PRESET_CONFIG: Record<SchedulerPreset, SchedulerPresetConfig> = {
65
+ performance: {
66
+ diagnosticsEnabled: false,
67
+ profilingEnabled: false,
68
+ profilingWindow: 60
69
+ },
70
+ balanced: {
71
+ diagnosticsEnabled: true,
72
+ profilingEnabled: true,
73
+ profilingWindow: 120
74
+ },
75
+ debug: {
76
+ diagnosticsEnabled: true,
77
+ profilingEnabled: true,
78
+ profilingWindow: 240
79
+ }
80
+ };
81
+
82
+ function assertProfilingWindow(value: number): number {
83
+ if (!Number.isFinite(value) || value <= 0) {
84
+ throw new Error('profilingWindow must be a finite number greater than 0');
85
+ }
86
+
87
+ return Math.floor(value);
88
+ }
89
+
90
+ /**
91
+ * Applies a named scheduler preset to the runtime scheduler instance.
92
+ *
93
+ * Returns resolved values after overrides for easy logging/telemetry.
94
+ */
95
+ export function applySchedulerPreset(
96
+ scheduler: MotionGPUScheduler,
97
+ preset: SchedulerPreset,
98
+ options: ApplySchedulerPresetOptions = {}
99
+ ): SchedulerPresetConfig {
100
+ const base = PRESET_CONFIG[preset];
101
+ const diagnosticsEnabled = options.diagnosticsEnabled ?? base.diagnosticsEnabled;
102
+ const profilingEnabled = options.profilingEnabled ?? base.profilingEnabled;
103
+ if (diagnosticsEnabled !== profilingEnabled) {
104
+ throw new Error(
105
+ 'MotionGPU scheduler currently shares diagnostics/profiling state; both values must match'
106
+ );
107
+ }
108
+
109
+ const profilingWindow = assertProfilingWindow(options.profilingWindow ?? base.profilingWindow);
110
+
111
+ scheduler.setProfilingWindow(profilingWindow);
112
+ scheduler.setDiagnosticsEnabled(diagnosticsEnabled);
113
+ scheduler.setProfilingEnabled(profilingEnabled);
114
+
115
+ return {
116
+ diagnosticsEnabled,
117
+ profilingEnabled,
118
+ profilingWindow
119
+ };
120
+ }
121
+
122
+ /**
123
+ * Captures an aggregate scheduler diagnostics snapshot.
124
+ */
125
+ export function captureSchedulerDebugSnapshot(
126
+ scheduler: MotionGPUScheduler
127
+ ): SchedulerDebugSnapshot {
128
+ return {
129
+ diagnosticsEnabled: scheduler.getDiagnosticsEnabled(),
130
+ profilingEnabled: scheduler.getProfilingEnabled(),
131
+ profilingWindow: scheduler.getProfilingWindow(),
132
+ schedule: scheduler.getSchedule(),
133
+ lastRunTimings: scheduler.getLastRunTimings(),
134
+ profilingSnapshot: scheduler.getProfilingSnapshot()
135
+ };
136
+ }