@motion-core/motion-gpu 0.1.0 → 0.2.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 (63) hide show
  1. package/README.md +31 -11
  2. package/dist/advanced.d.ts +3 -11
  3. package/dist/advanced.js +3 -6
  4. package/dist/core/advanced.d.ts +6 -0
  5. package/dist/core/advanced.js +5 -0
  6. package/dist/core/current-value.d.ts +23 -0
  7. package/dist/core/current-value.js +33 -0
  8. package/dist/core/error-diagnostics.d.ts +1 -1
  9. package/dist/core/error-report.js +2 -2
  10. package/dist/{frame-context.d.ts → core/frame-registry.d.ts} +3 -17
  11. package/dist/{frame-context.js → core/frame-registry.js} +2 -37
  12. package/dist/core/index.d.ts +19 -0
  13. package/dist/core/index.js +12 -0
  14. package/dist/core/material-preprocess.d.ts +1 -1
  15. package/dist/core/material-preprocess.js +1 -1
  16. package/dist/core/material.d.ts +4 -4
  17. package/dist/core/material.js +3 -3
  18. package/dist/core/recompile-policy.d.ts +1 -1
  19. package/dist/core/render-graph.d.ts +1 -1
  20. package/dist/core/render-targets.d.ts +1 -1
  21. package/dist/core/render-targets.js +1 -1
  22. package/dist/core/renderer.d.ts +1 -1
  23. package/dist/core/renderer.js +6 -6
  24. package/dist/core/runtime-loop.d.ts +31 -0
  25. package/dist/core/runtime-loop.js +294 -0
  26. package/dist/{advanced-scheduler.d.ts → core/scheduler-helpers.d.ts} +6 -2
  27. package/dist/core/shader.d.ts +2 -2
  28. package/dist/core/shader.js +1 -1
  29. package/dist/core/texture-loader.d.ts +1 -1
  30. package/dist/core/textures.d.ts +1 -1
  31. package/dist/core/textures.js +1 -1
  32. package/dist/core/uniforms.d.ts +1 -1
  33. package/dist/index.d.ts +3 -14
  34. package/dist/index.js +3 -8
  35. package/dist/passes/BlitPass.d.ts +1 -1
  36. package/dist/passes/CopyPass.d.ts +1 -1
  37. package/dist/passes/CopyPass.js +1 -1
  38. package/dist/passes/ShaderPass.d.ts +1 -1
  39. package/dist/passes/index.d.ts +3 -3
  40. package/dist/passes/index.js +3 -3
  41. package/dist/svelte/FragCanvas.svelte +220 -0
  42. package/dist/{FragCanvas.svelte.d.ts → svelte/FragCanvas.svelte.d.ts} +3 -3
  43. package/dist/{MotionGPUErrorOverlay.svelte → svelte/MotionGPUErrorOverlay.svelte} +1 -1
  44. package/dist/{MotionGPUErrorOverlay.svelte.d.ts → svelte/MotionGPUErrorOverlay.svelte.d.ts} +1 -1
  45. package/dist/svelte/advanced.d.ts +11 -0
  46. package/dist/svelte/advanced.js +6 -0
  47. package/dist/svelte/frame-context.d.ts +14 -0
  48. package/dist/svelte/frame-context.js +32 -0
  49. package/dist/svelte/index.d.ts +15 -0
  50. package/dist/svelte/index.js +9 -0
  51. package/dist/{motiongpu-context.d.ts → svelte/motiongpu-context.d.ts} +5 -7
  52. package/dist/{use-motiongpu-user-context.d.ts → svelte/use-motiongpu-user-context.d.ts} +2 -2
  53. package/dist/{use-motiongpu-user-context.js → svelte/use-motiongpu-user-context.js} +1 -1
  54. package/dist/{use-texture.d.ts → svelte/use-texture.d.ts} +2 -2
  55. package/dist/{use-texture.js → svelte/use-texture.js} +2 -2
  56. package/package.json +25 -5
  57. package/dist/FragCanvas.svelte +0 -511
  58. package/dist/current-writable.d.ts +0 -31
  59. package/dist/current-writable.js +0 -27
  60. /package/dist/{advanced-scheduler.js → core/scheduler-helpers.js} +0 -0
  61. /package/dist/{Portal.svelte → svelte/Portal.svelte} +0 -0
  62. /package/dist/{Portal.svelte.d.ts → svelte/Portal.svelte.d.ts} +0 -0
  63. /package/dist/{motiongpu-context.js → svelte/motiongpu-context.js} +0 -0
@@ -0,0 +1,294 @@
1
+ import { resolveMaterial } from './material.js';
2
+ import { toMotionGPUErrorReport } from './error-report.js';
3
+ import { createRenderer } from './renderer.js';
4
+ import { buildRendererPipelineSignature } from './recompile-policy.js';
5
+ import { assertUniformValueForType } from './uniforms.js';
6
+ function getRendererRetryDelayMs(attempt) {
7
+ return Math.min(8000, 250 * 2 ** Math.max(0, attempt - 1));
8
+ }
9
+ export function createMotionGPURuntimeLoop(options) {
10
+ const { canvas: canvasElement, registry, size } = options;
11
+ let frameId = null;
12
+ let renderer = null;
13
+ let isDisposed = false;
14
+ let previousTime = performance.now() / 1000;
15
+ let activeRendererSignature = '';
16
+ let failedRendererSignature = null;
17
+ let failedRendererAttempts = 0;
18
+ let nextRendererRetryAt = 0;
19
+ let rendererRebuildPromise = null;
20
+ const runtimeUniforms = {};
21
+ const runtimeTextures = {};
22
+ let activeUniforms = {};
23
+ let activeTextures = {};
24
+ let uniformKeys = [];
25
+ let uniformKeySet = new Set();
26
+ let uniformTypes = new Map();
27
+ let textureKeys = [];
28
+ let textureKeySet = new Set();
29
+ let activeMaterialSignature = '';
30
+ let currentCssWidth = -1;
31
+ let currentCssHeight = -1;
32
+ const renderUniforms = {};
33
+ const renderTextures = {};
34
+ const canvasSize = { width: 0, height: 0 };
35
+ let shouldContinueAfterFrame = false;
36
+ const setError = (error, phase) => {
37
+ const report = toMotionGPUErrorReport(error, phase);
38
+ options.reportError(report);
39
+ options.getOnError()?.(report);
40
+ };
41
+ const clearError = () => {
42
+ options.reportError(null);
43
+ };
44
+ const scheduleFrame = () => {
45
+ if (isDisposed || frameId !== null) {
46
+ return;
47
+ }
48
+ frameId = requestAnimationFrame(renderFrame);
49
+ };
50
+ const requestFrame = () => {
51
+ scheduleFrame();
52
+ };
53
+ const invalidate = (token) => {
54
+ registry.invalidate(token);
55
+ requestFrame();
56
+ };
57
+ const advance = () => {
58
+ registry.advance();
59
+ requestFrame();
60
+ };
61
+ const resetRuntimeMaps = () => {
62
+ for (const key of Object.keys(runtimeUniforms)) {
63
+ if (!uniformKeySet.has(key)) {
64
+ Reflect.deleteProperty(runtimeUniforms, key);
65
+ }
66
+ }
67
+ for (const key of Object.keys(runtimeTextures)) {
68
+ if (!textureKeySet.has(key)) {
69
+ Reflect.deleteProperty(runtimeTextures, key);
70
+ }
71
+ }
72
+ };
73
+ const resetRenderPayloadMaps = () => {
74
+ for (const key of Object.keys(renderUniforms)) {
75
+ if (!uniformKeySet.has(key)) {
76
+ Reflect.deleteProperty(renderUniforms, key);
77
+ }
78
+ }
79
+ for (const key of Object.keys(renderTextures)) {
80
+ if (!textureKeySet.has(key)) {
81
+ Reflect.deleteProperty(renderTextures, key);
82
+ }
83
+ }
84
+ };
85
+ const syncMaterialRuntimeState = (materialState) => {
86
+ const signatureChanged = activeMaterialSignature !== materialState.signature;
87
+ const defaultsChanged = activeUniforms !== materialState.uniforms || activeTextures !== materialState.textures;
88
+ if (!signatureChanged && !defaultsChanged) {
89
+ return;
90
+ }
91
+ activeUniforms = materialState.uniforms;
92
+ activeTextures = materialState.textures;
93
+ if (!signatureChanged) {
94
+ return;
95
+ }
96
+ uniformKeys = materialState.uniformLayout.entries.map((entry) => entry.name);
97
+ uniformTypes = new Map(materialState.uniformLayout.entries.map((entry) => [entry.name, entry.type]));
98
+ textureKeys = materialState.textureKeys;
99
+ uniformKeySet = new Set(uniformKeys);
100
+ textureKeySet = new Set(textureKeys);
101
+ resetRuntimeMaps();
102
+ resetRenderPayloadMaps();
103
+ activeMaterialSignature = materialState.signature;
104
+ };
105
+ const resolveActiveMaterial = () => {
106
+ return resolveMaterial(options.getMaterial());
107
+ };
108
+ const setUniform = (name, value) => {
109
+ if (!uniformKeySet.has(name)) {
110
+ throw new Error(`Unknown uniform "${name}". Declare it in material.uniforms first.`);
111
+ }
112
+ const expectedType = uniformTypes.get(name);
113
+ if (!expectedType) {
114
+ throw new Error(`Unknown uniform type for "${name}"`);
115
+ }
116
+ assertUniformValueForType(expectedType, value);
117
+ runtimeUniforms[name] = value;
118
+ };
119
+ const setTexture = (name, value) => {
120
+ if (!textureKeySet.has(name)) {
121
+ throw new Error(`Unknown texture "${name}". Declare it in material.textures first.`);
122
+ }
123
+ runtimeTextures[name] = value;
124
+ };
125
+ const renderFrame = (timestamp) => {
126
+ frameId = null;
127
+ if (isDisposed) {
128
+ return;
129
+ }
130
+ let materialState;
131
+ try {
132
+ materialState = resolveActiveMaterial();
133
+ }
134
+ catch (error) {
135
+ setError(error, 'initialization');
136
+ scheduleFrame();
137
+ return;
138
+ }
139
+ shouldContinueAfterFrame = false;
140
+ const outputColorSpace = options.getOutputColorSpace();
141
+ const rendererSignature = buildRendererPipelineSignature({
142
+ materialSignature: materialState.signature,
143
+ outputColorSpace
144
+ });
145
+ syncMaterialRuntimeState(materialState);
146
+ if (failedRendererSignature && failedRendererSignature !== rendererSignature) {
147
+ failedRendererSignature = null;
148
+ failedRendererAttempts = 0;
149
+ nextRendererRetryAt = 0;
150
+ }
151
+ if (!renderer || activeRendererSignature !== rendererSignature) {
152
+ if (failedRendererSignature === rendererSignature &&
153
+ performance.now() < nextRendererRetryAt) {
154
+ scheduleFrame();
155
+ return;
156
+ }
157
+ if (!rendererRebuildPromise) {
158
+ rendererRebuildPromise = (async () => {
159
+ try {
160
+ const nextRenderer = await createRenderer({
161
+ canvas: canvasElement,
162
+ fragmentWgsl: materialState.fragmentWgsl,
163
+ fragmentLineMap: materialState.fragmentLineMap,
164
+ fragmentSource: materialState.fragmentSource,
165
+ includeSources: materialState.includeSources,
166
+ defineBlockSource: materialState.defineBlockSource,
167
+ materialSource: materialState.source,
168
+ uniformLayout: materialState.uniformLayout,
169
+ textureKeys: materialState.textureKeys,
170
+ textureDefinitions: materialState.textures,
171
+ getRenderTargets: options.getRenderTargets,
172
+ getPasses: options.getPasses,
173
+ outputColorSpace,
174
+ getClearColor: options.getClearColor,
175
+ getDpr: () => options.dpr.current,
176
+ adapterOptions: options.getAdapterOptions(),
177
+ deviceDescriptor: options.getDeviceDescriptor()
178
+ });
179
+ if (isDisposed) {
180
+ nextRenderer.destroy();
181
+ return;
182
+ }
183
+ renderer?.destroy();
184
+ renderer = nextRenderer;
185
+ activeRendererSignature = rendererSignature;
186
+ failedRendererSignature = null;
187
+ failedRendererAttempts = 0;
188
+ nextRendererRetryAt = 0;
189
+ clearError();
190
+ }
191
+ catch (error) {
192
+ failedRendererSignature = rendererSignature;
193
+ failedRendererAttempts += 1;
194
+ const retryDelayMs = getRendererRetryDelayMs(failedRendererAttempts);
195
+ nextRendererRetryAt = performance.now() + retryDelayMs;
196
+ setError(error, 'initialization');
197
+ }
198
+ finally {
199
+ rendererRebuildPromise = null;
200
+ scheduleFrame();
201
+ }
202
+ })();
203
+ }
204
+ return;
205
+ }
206
+ const time = timestamp / 1000;
207
+ const rawDelta = Math.max(0, time - previousTime);
208
+ const delta = Math.min(rawDelta, options.maxDelta.current);
209
+ previousTime = time;
210
+ const rect = canvasElement.getBoundingClientRect();
211
+ const width = Math.max(0, Math.floor(rect.width));
212
+ const height = Math.max(0, Math.floor(rect.height));
213
+ if (width !== currentCssWidth || height !== currentCssHeight) {
214
+ currentCssWidth = width;
215
+ currentCssHeight = height;
216
+ size.set({ width, height });
217
+ }
218
+ try {
219
+ registry.run({
220
+ time,
221
+ delta,
222
+ setUniform,
223
+ setTexture,
224
+ invalidate,
225
+ advance,
226
+ renderMode: registry.getRenderMode(),
227
+ autoRender: registry.getAutoRender(),
228
+ canvas: canvasElement
229
+ });
230
+ const shouldRenderFrame = registry.shouldRender();
231
+ shouldContinueAfterFrame =
232
+ registry.getRenderMode() === 'always' ||
233
+ (registry.getRenderMode() === 'on-demand' && shouldRenderFrame);
234
+ if (shouldRenderFrame) {
235
+ for (const key of uniformKeys) {
236
+ const runtimeValue = runtimeUniforms[key];
237
+ renderUniforms[key] =
238
+ runtimeValue === undefined ? activeUniforms[key] : runtimeValue;
239
+ }
240
+ for (const key of textureKeys) {
241
+ const runtimeValue = runtimeTextures[key];
242
+ renderTextures[key] =
243
+ runtimeValue === undefined ? (activeTextures[key]?.source ?? null) : runtimeValue;
244
+ }
245
+ canvasSize.width = width;
246
+ canvasSize.height = height;
247
+ renderer.render({
248
+ time,
249
+ delta,
250
+ renderMode: registry.getRenderMode(),
251
+ uniforms: renderUniforms,
252
+ textures: renderTextures,
253
+ canvasSize
254
+ });
255
+ }
256
+ clearError();
257
+ }
258
+ catch (error) {
259
+ setError(error, 'render');
260
+ }
261
+ finally {
262
+ registry.endFrame();
263
+ }
264
+ if (shouldContinueAfterFrame) {
265
+ scheduleFrame();
266
+ }
267
+ };
268
+ (async () => {
269
+ try {
270
+ const initialMaterial = resolveActiveMaterial();
271
+ syncMaterialRuntimeState(initialMaterial);
272
+ activeRendererSignature = '';
273
+ scheduleFrame();
274
+ }
275
+ catch (error) {
276
+ setError(error, 'initialization');
277
+ scheduleFrame();
278
+ }
279
+ })();
280
+ return {
281
+ requestFrame,
282
+ invalidate,
283
+ advance,
284
+ destroy: () => {
285
+ isDisposed = true;
286
+ if (frameId !== null) {
287
+ cancelAnimationFrame(frameId);
288
+ frameId = null;
289
+ }
290
+ renderer?.destroy();
291
+ registry.clear();
292
+ }
293
+ };
294
+ }
@@ -1,6 +1,10 @@
1
- import type { FrameProfilingSnapshot, FrameRunTimings, FrameScheduleSnapshot, MotionGPUScheduler } from './motiongpu-context';
1
+ import type { FrameProfilingSnapshot, FrameRegistry, FrameRunTimings, FrameScheduleSnapshot } from './frame-registry.js';
2
2
  /**
3
- * Named scheduler presets exposed from the advanced entrypoint.
3
+ * Public scheduler control surface shared by framework adapters.
4
+ */
5
+ export type MotionGPUScheduler = Pick<FrameRegistry, 'createStage' | 'getStage' | 'setDiagnosticsEnabled' | 'getDiagnosticsEnabled' | 'getLastRunTimings' | 'getSchedule' | 'setProfilingEnabled' | 'setProfilingWindow' | 'resetProfiling' | 'getProfilingEnabled' | 'getProfilingWindow' | 'getProfilingSnapshot'>;
6
+ /**
7
+ * Named scheduler presets exposed from advanced entrypoints.
4
8
  */
5
9
  export type SchedulerPreset = 'balanced' | 'debug' | 'performance';
6
10
  /**
@@ -1,5 +1,5 @@
1
- import type { MaterialLineMap, MaterialSourceLocation } from './material-preprocess';
2
- import type { UniformLayout } from './types';
1
+ import type { MaterialLineMap, MaterialSourceLocation } from './material-preprocess.js';
2
+ import type { UniformLayout } from './types.js';
3
3
  /**
4
4
  * 1-based map from generated WGSL lines to original material source lines.
5
5
  */
@@ -1,4 +1,4 @@
1
- import { assertUniformName } from './uniforms';
1
+ import { assertUniformName } from './uniforms.js';
2
2
  /**
3
3
  * Fallback uniform field used when no custom uniforms are provided.
4
4
  */
@@ -1,4 +1,4 @@
1
- import type { TextureUpdateMode } from './types';
1
+ import type { TextureUpdateMode } from './types.js';
2
2
  /**
3
3
  * Options controlling bitmap decode behavior.
4
4
  */
@@ -1,4 +1,4 @@
1
- import type { TextureData, TextureDefinition, TextureDefinitionMap, TextureUpdateMode, TextureValue } from './types';
1
+ import type { TextureData, TextureDefinition, TextureDefinitionMap, TextureUpdateMode, TextureValue } from './types.js';
2
2
  /**
3
3
  * Texture definition with defaults and normalized numeric limits applied.
4
4
  */
@@ -1,4 +1,4 @@
1
- import { assertUniformName } from './uniforms';
1
+ import { assertUniformName } from './uniforms.js';
2
2
  /**
3
3
  * Default sampling filter for textures when no explicit value is provided.
4
4
  */
@@ -1,4 +1,4 @@
1
- import type { UniformLayout, UniformMap, UniformType, UniformValue } from './types';
1
+ import type { UniformLayout, UniformMap, UniformType, UniformValue } from './types.js';
2
2
  /**
3
3
  * Asserts that a name can be safely used as a WGSL identifier.
4
4
  *
package/dist/index.d.ts CHANGED
@@ -1,17 +1,6 @@
1
1
  /**
2
- * Public MotionGPU package entrypoint.
2
+ * Root package entrypoint.
3
3
  *
4
- * Exposes the production-ready core API for fullscreen WGSL rendering workflows.
4
+ * Framework-agnostic core entrypoint.
5
5
  */
6
- export { default as FragCanvas } from './FragCanvas.svelte';
7
- export { defineMaterial } from './core/material';
8
- export { BlitPass, CopyPass, ShaderPass } from './passes';
9
- export { useMotionGPU } from './motiongpu-context';
10
- export { useFrame } from './frame-context';
11
- export { useTexture } from './use-texture';
12
- export type { FrameInvalidationToken, FrameState, OutputColorSpace, RenderPass, RenderPassContext, RenderPassFlags, RenderPassInputSlot, RenderPassOutputSlot, RenderMode, RenderTarget, RenderTargetDefinition, RenderTargetDefinitionMap, TextureData, TextureDefinition, TextureDefinitionMap, TextureUpdateMode, TextureMap, TextureSource, TextureValue, TypedUniform, UniformMat4Value, UniformMap, UniformType, UniformValue } from './core/types';
13
- export type { LoadedTexture, TextureDecodeOptions, TextureLoadOptions } from './core/texture-loader';
14
- export type { FragMaterial, FragMaterialInput, MaterialIncludes, MaterialDefineValue, MaterialDefines, TypedMaterialDefineValue } from './core/material';
15
- export type { MotionGPUContext } from './motiongpu-context';
16
- export type { UseFrameOptions, UseFrameResult } from './frame-context';
17
- export type { TextureUrlInput, UseTextureResult } from './use-texture';
6
+ export * from './core/index.js';
package/dist/index.js CHANGED
@@ -1,11 +1,6 @@
1
1
  /**
2
- * Public MotionGPU package entrypoint.
2
+ * Root package entrypoint.
3
3
  *
4
- * Exposes the production-ready core API for fullscreen WGSL rendering workflows.
4
+ * Framework-agnostic core entrypoint.
5
5
  */
6
- export { default as FragCanvas } from './FragCanvas.svelte';
7
- export { defineMaterial } from './core/material';
8
- export { BlitPass, CopyPass, ShaderPass } from './passes';
9
- export { useMotionGPU } from './motiongpu-context';
10
- export { useFrame } from './frame-context';
11
- export { useTexture } from './use-texture';
6
+ export * from './core/index.js';
@@ -1,4 +1,4 @@
1
- import type { RenderPass, RenderPassContext, RenderPassFlags, RenderPassInputSlot, RenderPassOutputSlot } from '../core/types';
1
+ import type { RenderPass, RenderPassContext, RenderPassFlags, RenderPassInputSlot, RenderPassOutputSlot } from '../core/types.js';
2
2
  export interface BlitPassOptions extends RenderPassFlags {
3
3
  enabled?: boolean;
4
4
  needsSwap?: boolean;
@@ -1,4 +1,4 @@
1
- import type { RenderPass, RenderPassContext, RenderPassFlags, RenderPassInputSlot, RenderPassOutputSlot } from '../core/types';
1
+ import type { RenderPass, RenderPassContext, RenderPassFlags, RenderPassInputSlot, RenderPassOutputSlot } from '../core/types.js';
2
2
  export interface CopyPassOptions extends RenderPassFlags {
3
3
  enabled?: boolean;
4
4
  needsSwap?: boolean;
@@ -1,4 +1,4 @@
1
- import { BlitPass } from './BlitPass';
1
+ import { BlitPass } from './BlitPass.js';
2
2
  /**
3
3
  * Texture copy pass with fullscreen-blit fallback.
4
4
  */
@@ -1,4 +1,4 @@
1
- import type { RenderPass, RenderPassContext, RenderPassFlags, RenderPassInputSlot, RenderPassOutputSlot } from '../core/types';
1
+ import type { RenderPass, RenderPassContext, RenderPassFlags, RenderPassInputSlot, RenderPassOutputSlot } from '../core/types.js';
2
2
  export interface ShaderPassOptions extends RenderPassFlags {
3
3
  fragment: string;
4
4
  enabled?: boolean;
@@ -1,3 +1,3 @@
1
- export { BlitPass, type BlitPassOptions } from './BlitPass';
2
- export { CopyPass, type CopyPassOptions } from './CopyPass';
3
- export { ShaderPass, type ShaderPassOptions } from './ShaderPass';
1
+ export { BlitPass, type BlitPassOptions } from './BlitPass.js';
2
+ export { CopyPass, type CopyPassOptions } from './CopyPass.js';
3
+ export { ShaderPass, type ShaderPassOptions } from './ShaderPass.js';
@@ -1,3 +1,3 @@
1
- export { BlitPass } from './BlitPass';
2
- export { CopyPass } from './CopyPass';
3
- export { ShaderPass } from './ShaderPass';
1
+ export { BlitPass } from './BlitPass.js';
2
+ export { CopyPass } from './CopyPass.js';
3
+ export { ShaderPass } from './ShaderPass.js';
@@ -0,0 +1,220 @@
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
+ FrameInvalidationToken,
11
+ OutputColorSpace,
12
+ RenderPass,
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?: RenderPass[];
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
+ class?: string;
35
+ style?: string;
36
+ children?: Snippet;
37
+ }
38
+
39
+ const initialDpr = typeof window === 'undefined' ? 1 : (window.devicePixelRatio ?? 1);
40
+
41
+ let {
42
+ material,
43
+ renderTargets = {},
44
+ passes = [],
45
+ clearColor = [0, 0, 0, 1],
46
+ outputColorSpace = 'srgb',
47
+ renderMode = 'always',
48
+ autoRender = true,
49
+ maxDelta = 0.1,
50
+ adapterOptions = undefined,
51
+ deviceDescriptor = undefined,
52
+ dpr = initialDpr,
53
+ showErrorOverlay = true,
54
+ errorRenderer = undefined,
55
+ onError = undefined,
56
+ class: className = '',
57
+ style = '',
58
+ children
59
+ }: Props = $props();
60
+
61
+ let canvas: HTMLCanvasElement | undefined;
62
+ let errorReport = $state<MotionGPUErrorReport | null>(null);
63
+
64
+ const bindCanvas = (node: HTMLCanvasElement) => {
65
+ canvas = node;
66
+ return {
67
+ destroy: () => {
68
+ if (canvas === node) {
69
+ canvas = undefined;
70
+ }
71
+ }
72
+ };
73
+ };
74
+
75
+ const registry = createFrameRegistry({ maxDelta: 0.1 });
76
+ provideFrameRegistry(registry);
77
+ let requestFrameSignal: (() => void) | null = null;
78
+ const requestFrame = (): void => {
79
+ requestFrameSignal?.();
80
+ };
81
+ const invalidateFrame = (token?: FrameInvalidationToken): void => {
82
+ registry.invalidate(token);
83
+ requestFrame();
84
+ };
85
+ const advanceFrame = (): void => {
86
+ registry.advance();
87
+ requestFrame();
88
+ };
89
+ const size = currentWritable({ width: 0, height: 0 });
90
+ const dprState = currentWritable(initialDpr, requestFrame);
91
+ const maxDeltaState = currentWritable<number>(0.1, (value) => {
92
+ registry.setMaxDelta(value);
93
+ requestFrame();
94
+ });
95
+ const renderModeState = currentWritable<RenderMode>('always', (value) => {
96
+ registry.setRenderMode(value);
97
+ requestFrame();
98
+ });
99
+ const autoRenderState = currentWritable<boolean>(true, (value) => {
100
+ registry.setAutoRender(value);
101
+ requestFrame();
102
+ });
103
+ const userState = currentWritable<Record<string | symbol, unknown>>({});
104
+
105
+ provideMotionGPUContext({
106
+ get canvas() {
107
+ return canvas;
108
+ },
109
+ size,
110
+ dpr: dprState,
111
+ maxDelta: maxDeltaState,
112
+ renderMode: renderModeState,
113
+ autoRender: autoRenderState,
114
+ user: userState,
115
+ invalidate: () => invalidateFrame(),
116
+ advance: advanceFrame,
117
+ scheduler: {
118
+ createStage: registry.createStage,
119
+ getStage: registry.getStage,
120
+ setDiagnosticsEnabled: registry.setDiagnosticsEnabled,
121
+ getDiagnosticsEnabled: registry.getDiagnosticsEnabled,
122
+ getLastRunTimings: registry.getLastRunTimings,
123
+ getSchedule: registry.getSchedule,
124
+ setProfilingEnabled: registry.setProfilingEnabled,
125
+ setProfilingWindow: registry.setProfilingWindow,
126
+ resetProfiling: registry.resetProfiling,
127
+ getProfilingEnabled: registry.getProfilingEnabled,
128
+ getProfilingWindow: registry.getProfilingWindow,
129
+ getProfilingSnapshot: registry.getProfilingSnapshot
130
+ }
131
+ });
132
+
133
+ $effect(() => {
134
+ renderModeState.set(renderMode);
135
+ requestFrame();
136
+ });
137
+
138
+ $effect(() => {
139
+ autoRenderState.set(autoRender);
140
+ requestFrame();
141
+ });
142
+
143
+ $effect(() => {
144
+ maxDeltaState.set(maxDelta);
145
+ requestFrame();
146
+ });
147
+
148
+ $effect(() => {
149
+ dprState.set(dpr);
150
+ requestFrame();
151
+ });
152
+
153
+ onMount(() => {
154
+ if (!canvas) {
155
+ const report = toMotionGPUErrorReport(
156
+ new Error('Canvas element is not available'),
157
+ 'initialization'
158
+ );
159
+ errorReport = report;
160
+ onError?.(report);
161
+ return () => registry.clear();
162
+ }
163
+
164
+ const runtimeLoop = createMotionGPURuntimeLoop({
165
+ canvas,
166
+ registry,
167
+ size,
168
+ dpr: dprState,
169
+ maxDelta: maxDeltaState,
170
+ getMaterial: () => material,
171
+ getRenderTargets: () => renderTargets,
172
+ getPasses: () => passes,
173
+ getClearColor: () => clearColor,
174
+ getOutputColorSpace: () => outputColorSpace,
175
+ getAdapterOptions: () => adapterOptions,
176
+ getDeviceDescriptor: () => deviceDescriptor,
177
+ getOnError: () => onError,
178
+ reportError: (report) => {
179
+ errorReport = report;
180
+ }
181
+ });
182
+ requestFrameSignal = runtimeLoop.requestFrame;
183
+
184
+ return () => {
185
+ requestFrameSignal = null;
186
+ runtimeLoop.destroy();
187
+ };
188
+ });
189
+ </script>
190
+
191
+ <div class="motiongpu-canvas-wrap">
192
+ <canvas use:bindCanvas class={className} {style}></canvas>
193
+ {#if showErrorOverlay && errorReport}
194
+ {#if errorRenderer}
195
+ {@render errorRenderer(errorReport)}
196
+ {:else}
197
+ <MotionGPUErrorOverlay report={errorReport} />
198
+ {/if}
199
+ {/if}
200
+ {@render children?.()}
201
+ </div>
202
+
203
+ <style>
204
+ .motiongpu-canvas-wrap {
205
+ position: relative;
206
+ width: 100%;
207
+ height: 100%;
208
+ min-width: 0;
209
+ min-height: 0;
210
+ overflow: hidden;
211
+ }
212
+
213
+ canvas {
214
+ position: absolute;
215
+ inset: 0;
216
+ display: block;
217
+ width: 100%;
218
+ height: 100%;
219
+ }
220
+ </style>
@@ -1,7 +1,7 @@
1
1
  import type { Snippet } from 'svelte';
2
- import { type FragMaterial } from './core/material';
3
- import { type MotionGPUErrorReport } from './core/error-report';
4
- import type { OutputColorSpace, RenderPass, RenderMode, RenderTargetDefinitionMap } from './core/types';
2
+ import type { FragMaterial } from '../core/material';
3
+ import { type MotionGPUErrorReport } from '../core/error-report';
4
+ import type { OutputColorSpace, RenderPass, RenderMode, RenderTargetDefinitionMap } from '../core/types';
5
5
  interface Props {
6
6
  material: FragMaterial;
7
7
  renderTargets?: RenderTargetDefinitionMap;
@@ -1,5 +1,5 @@
1
1
  <script lang="ts">
2
- import type { MotionGPUErrorReport } from './core/error-report';
2
+ import type { MotionGPUErrorReport } from '../core/error-report';
3
3
  import Portal from './Portal.svelte';
4
4
 
5
5
  interface Props {