@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,537 @@
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
+ AnyPass,
14
+ FrameInvalidationToken,
15
+ OutputColorSpace,
16
+ PendingStorageWrite,
17
+ Renderer,
18
+ RenderTargetDefinitionMap,
19
+ StorageBufferDefinitionMap,
20
+ TextureMap,
21
+ TextureValue,
22
+ UniformType,
23
+ UniformValue
24
+ } from './types.js';
25
+
26
+ export interface MotionGPURuntimeLoopOptions {
27
+ canvas: HTMLCanvasElement;
28
+ registry: FrameRegistry;
29
+ size: CurrentWritable<{ width: number; height: number }>;
30
+ dpr: CurrentReadable<number>;
31
+ maxDelta: CurrentReadable<number>;
32
+ getMaterial: () => FragMaterial;
33
+ getRenderTargets: () => RenderTargetDefinitionMap;
34
+ getPasses: () => AnyPass[];
35
+ getClearColor: () => [number, number, number, number];
36
+ getOutputColorSpace: () => OutputColorSpace;
37
+ getAdapterOptions: () => GPURequestAdapterOptions | undefined;
38
+ getDeviceDescriptor: () => GPUDeviceDescriptor | undefined;
39
+ getOnError: () => ((report: MotionGPUErrorReport) => void) | undefined;
40
+ reportError: (report: MotionGPUErrorReport | null) => void;
41
+ getErrorHistoryLimit?: () => number | undefined;
42
+ getOnErrorHistory?: () => ((history: MotionGPUErrorReport[]) => void) | undefined;
43
+ reportErrorHistory?: (history: MotionGPUErrorReport[]) => void;
44
+ }
45
+
46
+ export interface MotionGPURuntimeLoop {
47
+ requestFrame: () => void;
48
+ invalidate: (token?: FrameInvalidationToken) => void;
49
+ advance: () => void;
50
+ destroy: () => void;
51
+ }
52
+
53
+ function getRendererRetryDelayMs(attempt: number): number {
54
+ return Math.min(8000, 250 * 2 ** Math.max(0, attempt - 1));
55
+ }
56
+
57
+ export function createMotionGPURuntimeLoop(
58
+ options: MotionGPURuntimeLoopOptions
59
+ ): MotionGPURuntimeLoop {
60
+ const { canvas: canvasElement, registry, size } = options;
61
+ let frameId: number | null = null;
62
+ let renderer: Renderer | null = null;
63
+ let isDisposed = false;
64
+ let previousTime = performance.now() / 1000;
65
+ let activeRendererSignature = '';
66
+ let failedRendererSignature: string | null = null;
67
+ let failedRendererAttempts = 0;
68
+ let nextRendererRetryAt = 0;
69
+ let rendererRebuildPromise: Promise<void> | null = null;
70
+
71
+ const runtimeUniforms: Record<string, UniformValue> = {};
72
+ const runtimeTextures: TextureMap = {};
73
+ let activeUniforms: Record<string, UniformValue> = {};
74
+ let activeTextures: Record<string, { source?: TextureValue }> = {};
75
+ let uniformKeys: string[] = [];
76
+ let uniformKeySet = new Set<string>();
77
+ let uniformTypes = new Map<string, UniformType>();
78
+ let textureKeys: string[] = [];
79
+ let textureKeySet = new Set<string>();
80
+ let activeMaterialSignature = '';
81
+ let currentCssWidth = -1;
82
+ let currentCssHeight = -1;
83
+ const renderUniforms: Record<string, UniformValue> = {};
84
+ const renderTextures: TextureMap = {};
85
+ const canvasSize = { width: 0, height: 0 };
86
+ let storageBufferKeys: string[] = [];
87
+ let storageBufferKeySet = new Set<string>();
88
+ let storageBufferDefinitions: StorageBufferDefinitionMap = {};
89
+ const pendingStorageWrites: PendingStorageWrite[] = [];
90
+ let shouldContinueAfterFrame = false;
91
+ let activeErrorKey: string | null = null;
92
+ let errorHistory: MotionGPUErrorReport[] = [];
93
+
94
+ const getHistoryLimit = (): number => {
95
+ const value = options.getErrorHistoryLimit?.() ?? 0;
96
+ if (!Number.isFinite(value) || value <= 0) {
97
+ return 0;
98
+ }
99
+
100
+ return Math.floor(value);
101
+ };
102
+
103
+ const publishErrorHistory = (): void => {
104
+ options.reportErrorHistory?.(errorHistory);
105
+ const onErrorHistory = options.getOnErrorHistory?.();
106
+ if (!onErrorHistory) {
107
+ return;
108
+ }
109
+
110
+ try {
111
+ onErrorHistory(errorHistory);
112
+ } catch {
113
+ // User-provided error history handlers must not break runtime error recovery.
114
+ }
115
+ };
116
+
117
+ const syncErrorHistory = (): void => {
118
+ const limit = getHistoryLimit();
119
+ if (limit <= 0) {
120
+ if (errorHistory.length === 0) {
121
+ return;
122
+ }
123
+ errorHistory = [];
124
+ publishErrorHistory();
125
+ return;
126
+ }
127
+
128
+ if (errorHistory.length <= limit) {
129
+ return;
130
+ }
131
+
132
+ errorHistory = errorHistory.slice(errorHistory.length - limit);
133
+ publishErrorHistory();
134
+ };
135
+
136
+ const setError = (error: unknown, phase: MotionGPUErrorPhase): void => {
137
+ const report = toMotionGPUErrorReport(error, phase);
138
+ const reportKey = JSON.stringify({
139
+ phase: report.phase,
140
+ title: report.title,
141
+ message: report.message,
142
+ rawMessage: report.rawMessage
143
+ });
144
+ if (activeErrorKey === reportKey) {
145
+ return;
146
+ }
147
+ activeErrorKey = reportKey;
148
+ const historyLimit = getHistoryLimit();
149
+ if (historyLimit > 0) {
150
+ errorHistory = [...errorHistory, report];
151
+ if (errorHistory.length > historyLimit) {
152
+ errorHistory = errorHistory.slice(errorHistory.length - historyLimit);
153
+ }
154
+ publishErrorHistory();
155
+ }
156
+ options.reportError(report);
157
+ const onError = options.getOnError();
158
+ if (!onError) {
159
+ return;
160
+ }
161
+
162
+ try {
163
+ onError(report);
164
+ } catch {
165
+ // User-provided error handlers must not break runtime error recovery.
166
+ }
167
+ };
168
+
169
+ const clearError = (): void => {
170
+ if (activeErrorKey === null) {
171
+ return;
172
+ }
173
+
174
+ activeErrorKey = null;
175
+ options.reportError(null);
176
+ };
177
+
178
+ const scheduleFrame = (): void => {
179
+ if (isDisposed || frameId !== null) {
180
+ return;
181
+ }
182
+
183
+ frameId = requestAnimationFrame(renderFrame);
184
+ };
185
+
186
+ const requestFrame = (): void => {
187
+ scheduleFrame();
188
+ };
189
+
190
+ const invalidate = (token?: FrameInvalidationToken): void => {
191
+ registry.invalidate(token);
192
+ requestFrame();
193
+ };
194
+
195
+ const advance = (): void => {
196
+ registry.advance();
197
+ requestFrame();
198
+ };
199
+
200
+ const resetRuntimeMaps = (): void => {
201
+ for (const key of Object.keys(runtimeUniforms)) {
202
+ if (!uniformKeySet.has(key)) {
203
+ Reflect.deleteProperty(runtimeUniforms, key);
204
+ }
205
+ }
206
+
207
+ for (const key of Object.keys(runtimeTextures)) {
208
+ if (!textureKeySet.has(key)) {
209
+ Reflect.deleteProperty(runtimeTextures, key);
210
+ }
211
+ }
212
+ };
213
+
214
+ const resetRenderPayloadMaps = (): void => {
215
+ for (const key of Object.keys(renderUniforms)) {
216
+ if (!uniformKeySet.has(key)) {
217
+ Reflect.deleteProperty(renderUniforms, key);
218
+ }
219
+ }
220
+
221
+ for (const key of Object.keys(renderTextures)) {
222
+ if (!textureKeySet.has(key)) {
223
+ Reflect.deleteProperty(renderTextures, key);
224
+ }
225
+ }
226
+ };
227
+
228
+ const syncMaterialRuntimeState = (materialState: ResolvedMaterial): void => {
229
+ const signatureChanged = activeMaterialSignature !== materialState.signature;
230
+ const defaultsChanged =
231
+ activeUniforms !== materialState.uniforms || activeTextures !== materialState.textures;
232
+
233
+ if (!signatureChanged && !defaultsChanged) {
234
+ return;
235
+ }
236
+
237
+ activeUniforms = materialState.uniforms;
238
+ activeTextures = materialState.textures;
239
+ if (!signatureChanged) {
240
+ return;
241
+ }
242
+
243
+ uniformKeys = materialState.uniformLayout.entries.map((entry) => entry.name);
244
+ uniformTypes = new Map(
245
+ materialState.uniformLayout.entries.map((entry) => [entry.name, entry.type])
246
+ );
247
+ textureKeys = materialState.textureKeys;
248
+ uniformKeySet = new Set(uniformKeys);
249
+ textureKeySet = new Set(textureKeys);
250
+ storageBufferKeys = materialState.storageBufferKeys;
251
+ storageBufferKeySet = new Set(storageBufferKeys);
252
+ storageBufferDefinitions = (options.getMaterial().storageBuffers ??
253
+ {}) as StorageBufferDefinitionMap;
254
+ resetRuntimeMaps();
255
+ resetRenderPayloadMaps();
256
+ activeMaterialSignature = materialState.signature;
257
+ };
258
+
259
+ const resolveActiveMaterial = (): ResolvedMaterial => {
260
+ return resolveMaterial(options.getMaterial());
261
+ };
262
+
263
+ const setUniform = (name: string, value: UniformValue): void => {
264
+ if (!uniformKeySet.has(name)) {
265
+ throw new Error(`Unknown uniform "${name}". Declare it in material.uniforms first.`);
266
+ }
267
+ const expectedType = uniformTypes.get(name);
268
+ if (!expectedType) {
269
+ throw new Error(`Unknown uniform type for "${name}"`);
270
+ }
271
+ assertUniformValueForType(expectedType, value);
272
+ runtimeUniforms[name] = value;
273
+ };
274
+
275
+ const setTexture = (name: string, value: TextureValue): void => {
276
+ if (!textureKeySet.has(name)) {
277
+ throw new Error(`Unknown texture "${name}". Declare it in material.textures first.`);
278
+ }
279
+ runtimeTextures[name] = value;
280
+ };
281
+
282
+ const writeStorageBuffer = (
283
+ name: string,
284
+ data: ArrayBufferView,
285
+ writeOptions?: { offset?: number }
286
+ ): void => {
287
+ if (!storageBufferKeySet.has(name)) {
288
+ throw new Error(
289
+ `Unknown storage buffer "${name}". Declare it in material.storageBuffers first.`
290
+ );
291
+ }
292
+ const definition = storageBufferDefinitions[name];
293
+ if (!definition) {
294
+ throw new Error(`Missing definition for storage buffer "${name}".`);
295
+ }
296
+ const offset = writeOptions?.offset ?? 0;
297
+ if (offset < 0 || offset + data.byteLength > definition.size) {
298
+ throw new Error(
299
+ `Storage buffer "${name}" write out of bounds: offset=${offset}, dataSize=${data.byteLength}, bufferSize=${definition.size}.`
300
+ );
301
+ }
302
+ pendingStorageWrites.push({ name, data, offset });
303
+ };
304
+
305
+ const readStorageBuffer = (name: string): Promise<ArrayBuffer> => {
306
+ if (!storageBufferKeySet.has(name)) {
307
+ throw new Error(
308
+ `Unknown storage buffer "${name}". Declare it in material.storageBuffers first.`
309
+ );
310
+ }
311
+ if (!renderer) {
312
+ return Promise.reject(
313
+ new Error(`Cannot read storage buffer "${name}": renderer not initialized.`)
314
+ );
315
+ }
316
+ const gpuBuffer = renderer.getStorageBuffer?.(name);
317
+ if (!gpuBuffer) {
318
+ return Promise.reject(new Error(`Storage buffer "${name}" not allocated on GPU.`));
319
+ }
320
+ const device = renderer.getDevice?.();
321
+ if (!device) {
322
+ return Promise.reject(new Error('Cannot read storage buffer: GPU device unavailable.'));
323
+ }
324
+ const definition = storageBufferDefinitions[name];
325
+ if (!definition) {
326
+ return Promise.reject(new Error(`Missing definition for storage buffer "${name}".`));
327
+ }
328
+ const stagingBuffer = device.createBuffer({
329
+ size: definition.size,
330
+ usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST
331
+ });
332
+ const commandEncoder = device.createCommandEncoder();
333
+ commandEncoder.copyBufferToBuffer(gpuBuffer, 0, stagingBuffer, 0, definition.size);
334
+ device.queue.submit([commandEncoder.finish()]);
335
+ return stagingBuffer.mapAsync(GPUMapMode.READ).then(() => {
336
+ const result = stagingBuffer.getMappedRange().slice(0);
337
+ stagingBuffer.unmap();
338
+ stagingBuffer.destroy();
339
+ return result;
340
+ });
341
+ };
342
+
343
+ const renderFrame = (timestamp: number): void => {
344
+ frameId = null;
345
+ if (isDisposed) {
346
+ return;
347
+ }
348
+ syncErrorHistory();
349
+
350
+ let materialState: ResolvedMaterial;
351
+ try {
352
+ materialState = resolveActiveMaterial();
353
+ } catch (error) {
354
+ setError(error, 'initialization');
355
+ scheduleFrame();
356
+ return;
357
+ }
358
+
359
+ shouldContinueAfterFrame = false;
360
+
361
+ const outputColorSpace = options.getOutputColorSpace();
362
+ const rendererSignature = buildRendererPipelineSignature({
363
+ materialSignature: materialState.signature,
364
+ outputColorSpace
365
+ });
366
+ syncMaterialRuntimeState(materialState);
367
+
368
+ if (failedRendererSignature && failedRendererSignature !== rendererSignature) {
369
+ failedRendererSignature = null;
370
+ failedRendererAttempts = 0;
371
+ nextRendererRetryAt = 0;
372
+ }
373
+
374
+ if (!renderer || activeRendererSignature !== rendererSignature) {
375
+ if (
376
+ failedRendererSignature === rendererSignature &&
377
+ performance.now() < nextRendererRetryAt
378
+ ) {
379
+ scheduleFrame();
380
+ return;
381
+ }
382
+
383
+ if (!rendererRebuildPromise) {
384
+ rendererRebuildPromise = (async () => {
385
+ try {
386
+ const nextRenderer = await createRenderer({
387
+ canvas: canvasElement,
388
+ fragmentWgsl: materialState.fragmentWgsl,
389
+ fragmentLineMap: materialState.fragmentLineMap,
390
+ fragmentSource: materialState.fragmentSource,
391
+ includeSources: materialState.includeSources,
392
+ defineBlockSource: materialState.defineBlockSource,
393
+ materialSource: materialState.source,
394
+ materialSignature: materialState.signature,
395
+ uniformLayout: materialState.uniformLayout,
396
+ textureKeys: materialState.textureKeys,
397
+ textureDefinitions: materialState.textures,
398
+ storageBufferKeys: materialState.storageBufferKeys,
399
+ storageBufferDefinitions,
400
+ storageTextureKeys: materialState.storageTextureKeys,
401
+ getRenderTargets: options.getRenderTargets,
402
+ getPasses: options.getPasses,
403
+ outputColorSpace,
404
+ getClearColor: options.getClearColor,
405
+ getDpr: () => options.dpr.current,
406
+ adapterOptions: options.getAdapterOptions(),
407
+ deviceDescriptor: options.getDeviceDescriptor()
408
+ });
409
+
410
+ if (isDisposed) {
411
+ nextRenderer.destroy();
412
+ return;
413
+ }
414
+
415
+ renderer?.destroy();
416
+ renderer = nextRenderer;
417
+ activeRendererSignature = rendererSignature;
418
+ failedRendererSignature = null;
419
+ failedRendererAttempts = 0;
420
+ nextRendererRetryAt = 0;
421
+ clearError();
422
+ } catch (error) {
423
+ failedRendererSignature = rendererSignature;
424
+ failedRendererAttempts += 1;
425
+ const retryDelayMs = getRendererRetryDelayMs(failedRendererAttempts);
426
+ nextRendererRetryAt = performance.now() + retryDelayMs;
427
+ setError(error, 'initialization');
428
+ } finally {
429
+ rendererRebuildPromise = null;
430
+ scheduleFrame();
431
+ }
432
+ })();
433
+ }
434
+
435
+ return;
436
+ }
437
+
438
+ const time = timestamp / 1000;
439
+ const rawDelta = Math.max(0, time - previousTime);
440
+ const delta = Math.min(rawDelta, options.maxDelta.current);
441
+ previousTime = time;
442
+ const rect = canvasElement.getBoundingClientRect();
443
+ const width = Math.max(0, Math.floor(rect.width));
444
+ const height = Math.max(0, Math.floor(rect.height));
445
+ if (width !== currentCssWidth || height !== currentCssHeight) {
446
+ currentCssWidth = width;
447
+ currentCssHeight = height;
448
+ size.set({ width, height });
449
+ }
450
+
451
+ try {
452
+ registry.run({
453
+ time,
454
+ delta,
455
+ setUniform,
456
+ setTexture,
457
+ writeStorageBuffer,
458
+ readStorageBuffer,
459
+ invalidate,
460
+ advance,
461
+ renderMode: registry.getRenderMode(),
462
+ autoRender: registry.getAutoRender(),
463
+ canvas: canvasElement
464
+ });
465
+
466
+ const shouldRenderFrame = registry.shouldRender();
467
+ shouldContinueAfterFrame =
468
+ registry.getRenderMode() === 'always' ||
469
+ (registry.getRenderMode() === 'on-demand' && shouldRenderFrame);
470
+
471
+ if (shouldRenderFrame) {
472
+ for (const key of uniformKeys) {
473
+ const runtimeValue = runtimeUniforms[key];
474
+ renderUniforms[key] =
475
+ runtimeValue === undefined ? (activeUniforms[key] as UniformValue) : runtimeValue;
476
+ }
477
+
478
+ for (const key of textureKeys) {
479
+ const runtimeValue = runtimeTextures[key];
480
+ renderTextures[key] =
481
+ runtimeValue === undefined ? (activeTextures[key]?.source ?? null) : runtimeValue;
482
+ }
483
+
484
+ canvasSize.width = width;
485
+ canvasSize.height = height;
486
+ renderer.render({
487
+ time,
488
+ delta,
489
+ renderMode: registry.getRenderMode(),
490
+ uniforms: renderUniforms,
491
+ textures: renderTextures,
492
+ canvasSize,
493
+ ...(pendingStorageWrites.length > 0
494
+ ? { pendingStorageWrites: pendingStorageWrites.splice(0) }
495
+ : {})
496
+ });
497
+ }
498
+
499
+ clearError();
500
+ } catch (error) {
501
+ setError(error, 'render');
502
+ } finally {
503
+ registry.endFrame();
504
+ }
505
+
506
+ if (shouldContinueAfterFrame) {
507
+ scheduleFrame();
508
+ }
509
+ };
510
+
511
+ (async () => {
512
+ try {
513
+ const initialMaterial = resolveActiveMaterial();
514
+ syncMaterialRuntimeState(initialMaterial);
515
+ activeRendererSignature = '';
516
+ scheduleFrame();
517
+ } catch (error) {
518
+ setError(error, 'initialization');
519
+ scheduleFrame();
520
+ }
521
+ })();
522
+
523
+ return {
524
+ requestFrame,
525
+ invalidate,
526
+ advance,
527
+ destroy: () => {
528
+ isDisposed = true;
529
+ if (frameId !== null) {
530
+ cancelAnimationFrame(frameId);
531
+ frameId = null;
532
+ }
533
+ renderer?.destroy();
534
+ registry.clear();
535
+ }
536
+ };
537
+ }
@@ -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
+ }