@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
@@ -1,511 +0,0 @@
1
- <script lang="ts">
2
- import { onMount } from 'svelte';
3
- import type { Snippet } from 'svelte';
4
- import { resolveMaterial, type FragMaterial } from './core/material';
5
- import {
6
- toMotionGPUErrorReport,
7
- type MotionGPUErrorPhase,
8
- type MotionGPUErrorReport
9
- } from './core/error-report';
10
- import MotionGPUErrorOverlay from './MotionGPUErrorOverlay.svelte';
11
- import { currentWritable } from './current-writable';
12
- import { createRenderer } from './core/renderer';
13
- import { buildRendererPipelineSignature } from './core/recompile-policy';
14
- import type {
15
- FrameInvalidationToken,
16
- OutputColorSpace,
17
- RenderPass,
18
- RenderMode,
19
- Renderer,
20
- RenderTargetDefinitionMap,
21
- TextureMap,
22
- TextureValue,
23
- UniformType,
24
- UniformValue
25
- } from './core/types';
26
- import { provideMotionGPUContext } from './motiongpu-context';
27
- import { assertUniformValueForType } from './core/uniforms';
28
- import { createFrameRegistry, provideFrameRegistry } from './frame-context';
29
-
30
- interface Props {
31
- material: FragMaterial;
32
- renderTargets?: RenderTargetDefinitionMap;
33
- passes?: RenderPass[];
34
- clearColor?: [number, number, number, number];
35
- outputColorSpace?: OutputColorSpace;
36
- renderMode?: RenderMode;
37
- autoRender?: boolean;
38
- maxDelta?: number;
39
- adapterOptions?: GPURequestAdapterOptions;
40
- deviceDescriptor?: GPUDeviceDescriptor;
41
- dpr?: number;
42
- showErrorOverlay?: boolean;
43
- errorRenderer?: Snippet<[MotionGPUErrorReport]>;
44
- onError?: (report: MotionGPUErrorReport) => void;
45
- class?: string;
46
- style?: string;
47
- children?: Snippet;
48
- }
49
-
50
- const initialDpr = typeof window === 'undefined' ? 1 : (window.devicePixelRatio ?? 1);
51
-
52
- let {
53
- material,
54
- renderTargets = {},
55
- passes = [],
56
- clearColor = [0, 0, 0, 1],
57
- outputColorSpace = 'srgb',
58
- renderMode = 'always',
59
- autoRender = true,
60
- maxDelta = 0.1,
61
- adapterOptions = undefined,
62
- deviceDescriptor = undefined,
63
- dpr = initialDpr,
64
- showErrorOverlay = true,
65
- errorRenderer = undefined,
66
- onError = undefined,
67
- class: className = '',
68
- style = '',
69
- children
70
- }: Props = $props();
71
-
72
- let canvas: HTMLCanvasElement | undefined;
73
- let errorReport = $state<MotionGPUErrorReport | null>(null);
74
-
75
- const bindCanvas = (node: HTMLCanvasElement) => {
76
- canvas = node;
77
- return {
78
- destroy: () => {
79
- if (canvas === node) {
80
- canvas = undefined;
81
- }
82
- }
83
- };
84
- };
85
-
86
- const getRendererRetryDelayMs = (attempt: number): number => {
87
- return Math.min(8000, 250 * 2 ** Math.max(0, attempt - 1));
88
- };
89
-
90
- const registry = createFrameRegistry({ maxDelta: 0.1 });
91
- provideFrameRegistry(registry);
92
- let requestFrameSignal: (() => void) | null = null;
93
- const requestFrame = (): void => {
94
- requestFrameSignal?.();
95
- };
96
- const invalidateFrame = (token?: FrameInvalidationToken): void => {
97
- registry.invalidate(token);
98
- requestFrame();
99
- };
100
- const advanceFrame = (): void => {
101
- registry.advance();
102
- requestFrame();
103
- };
104
- const size = currentWritable({ width: 0, height: 0 });
105
- const dprState = currentWritable(initialDpr, requestFrame);
106
- const maxDeltaState = currentWritable<number>(0.1, (value) => {
107
- registry.setMaxDelta(value);
108
- requestFrame();
109
- });
110
- const renderModeState = currentWritable<RenderMode>('always', (value) => {
111
- registry.setRenderMode(value);
112
- requestFrame();
113
- });
114
- const autoRenderState = currentWritable<boolean>(true, (value) => {
115
- registry.setAutoRender(value);
116
- requestFrame();
117
- });
118
- const userState = currentWritable<Record<string | symbol, unknown>>({});
119
-
120
- provideMotionGPUContext({
121
- get canvas() {
122
- return canvas;
123
- },
124
- size,
125
- dpr: dprState,
126
- maxDelta: maxDeltaState,
127
- renderMode: renderModeState,
128
- autoRender: autoRenderState,
129
- user: userState,
130
- invalidate: () => invalidateFrame(),
131
- advance: advanceFrame,
132
- scheduler: {
133
- createStage: registry.createStage,
134
- getStage: registry.getStage,
135
- setDiagnosticsEnabled: registry.setDiagnosticsEnabled,
136
- getDiagnosticsEnabled: registry.getDiagnosticsEnabled,
137
- getLastRunTimings: registry.getLastRunTimings,
138
- getSchedule: registry.getSchedule,
139
- setProfilingEnabled: registry.setProfilingEnabled,
140
- setProfilingWindow: registry.setProfilingWindow,
141
- resetProfiling: registry.resetProfiling,
142
- getProfilingEnabled: registry.getProfilingEnabled,
143
- getProfilingWindow: registry.getProfilingWindow,
144
- getProfilingSnapshot: registry.getProfilingSnapshot
145
- }
146
- });
147
-
148
- $effect(() => {
149
- renderModeState.set(renderMode);
150
- requestFrame();
151
- });
152
-
153
- $effect(() => {
154
- autoRenderState.set(autoRender);
155
- requestFrame();
156
- });
157
-
158
- $effect(() => {
159
- maxDeltaState.set(maxDelta);
160
- requestFrame();
161
- });
162
-
163
- $effect(() => {
164
- dprState.set(dpr);
165
- requestFrame();
166
- });
167
-
168
- onMount(() => {
169
- const setError = (error: unknown, phase: MotionGPUErrorPhase): void => {
170
- const report = toMotionGPUErrorReport(error, phase);
171
- errorReport = report;
172
- onError?.(report);
173
- };
174
-
175
- const clearError = (): void => {
176
- errorReport = null;
177
- };
178
-
179
- if (!canvas) {
180
- setError(new Error('Canvas element is not available'), 'initialization');
181
- return () => registry.clear();
182
- }
183
-
184
- const canvasElement = canvas;
185
- let frameId: number | null = null;
186
- let renderer: Renderer | null = null;
187
- let isDisposed = false;
188
- let previousTime = performance.now() / 1000;
189
- let activeRendererSignature = '';
190
- let failedRendererSignature: string | null = null;
191
- let failedRendererAttempts = 0;
192
- let nextRendererRetryAt = 0;
193
- let rendererRebuildPromise: Promise<void> | null = null;
194
-
195
- const runtimeUniforms: Record<string, UniformValue> = {};
196
- const runtimeTextures: TextureMap = {};
197
- let activeUniforms: Record<string, UniformValue> = {};
198
- let activeTextures: Record<string, { source?: TextureValue }> = {};
199
- let uniformKeys: string[] = [];
200
- let uniformKeySet = new Set<string>();
201
- let uniformTypes = new Map<string, UniformType>();
202
- let textureKeys: string[] = [];
203
- let textureKeySet = new Set<string>();
204
- let activeMaterialSignature = '';
205
- let currentCssWidth = -1;
206
- let currentCssHeight = -1;
207
- const renderUniforms: Record<string, UniformValue> = {};
208
- const renderTextures: TextureMap = {};
209
- const canvasSize = { width: 0, height: 0 };
210
- let shouldContinueAfterFrame = false;
211
-
212
- const scheduleFrame = (): void => {
213
- if (isDisposed || frameId !== null) {
214
- return;
215
- }
216
-
217
- frameId = requestAnimationFrame(renderFrame);
218
- };
219
-
220
- requestFrameSignal = scheduleFrame;
221
-
222
- const resetRuntimeMaps = (): void => {
223
- for (const key of Object.keys(runtimeUniforms)) {
224
- if (!uniformKeySet.has(key)) {
225
- Reflect.deleteProperty(runtimeUniforms, key);
226
- }
227
- }
228
-
229
- for (const key of Object.keys(runtimeTextures)) {
230
- if (!textureKeySet.has(key)) {
231
- Reflect.deleteProperty(runtimeTextures, key);
232
- }
233
- }
234
- };
235
-
236
- const resetRenderPayloadMaps = (): void => {
237
- for (const key of Object.keys(renderUniforms)) {
238
- if (!uniformKeySet.has(key)) {
239
- Reflect.deleteProperty(renderUniforms, key);
240
- }
241
- }
242
-
243
- for (const key of Object.keys(renderTextures)) {
244
- if (!textureKeySet.has(key)) {
245
- Reflect.deleteProperty(renderTextures, key);
246
- }
247
- }
248
- };
249
-
250
- const syncMaterialRuntimeState = (materialState: ReturnType<typeof resolveMaterial>): void => {
251
- const signatureChanged = activeMaterialSignature !== materialState.signature;
252
- const defaultsChanged =
253
- activeUniforms !== materialState.uniforms || activeTextures !== materialState.textures;
254
-
255
- if (!signatureChanged && !defaultsChanged) {
256
- return;
257
- }
258
-
259
- activeUniforms = materialState.uniforms;
260
- activeTextures = materialState.textures;
261
- if (!signatureChanged) {
262
- return;
263
- }
264
-
265
- uniformKeys = materialState.uniformLayout.entries.map((entry) => entry.name);
266
- uniformTypes = new Map(
267
- materialState.uniformLayout.entries.map((entry) => [entry.name, entry.type])
268
- );
269
- textureKeys = materialState.textureKeys;
270
- uniformKeySet = new Set(uniformKeys);
271
- textureKeySet = new Set(textureKeys);
272
- resetRuntimeMaps();
273
- resetRenderPayloadMaps();
274
- activeMaterialSignature = materialState.signature;
275
- };
276
-
277
- const resolveActiveMaterial = () => {
278
- return resolveMaterial(material);
279
- };
280
-
281
- const setUniform = (name: string, value: UniformValue): void => {
282
- if (!uniformKeySet.has(name)) {
283
- throw new Error(`Unknown uniform "${name}". Declare it in material.uniforms first.`);
284
- }
285
- const expectedType = uniformTypes.get(name);
286
- if (!expectedType) {
287
- throw new Error(`Unknown uniform type for "${name}"`);
288
- }
289
- assertUniformValueForType(expectedType, value);
290
- runtimeUniforms[name] = value;
291
- };
292
-
293
- const setTexture = (name: string, value: TextureValue): void => {
294
- if (!textureKeySet.has(name)) {
295
- throw new Error(`Unknown texture "${name}". Declare it in material.textures first.`);
296
- }
297
- runtimeTextures[name] = value;
298
- };
299
-
300
- const renderFrame = (timestamp: number): void => {
301
- frameId = null;
302
- if (isDisposed) {
303
- return;
304
- }
305
-
306
- let materialState: ReturnType<typeof resolveActiveMaterial>;
307
- try {
308
- materialState = resolveActiveMaterial();
309
- } catch (error) {
310
- setError(error, 'initialization');
311
- scheduleFrame();
312
- return;
313
- }
314
-
315
- shouldContinueAfterFrame = false;
316
-
317
- const rendererSignature = buildRendererPipelineSignature({
318
- materialSignature: materialState.signature,
319
- outputColorSpace
320
- });
321
- syncMaterialRuntimeState(materialState);
322
-
323
- if (failedRendererSignature && failedRendererSignature !== rendererSignature) {
324
- failedRendererSignature = null;
325
- failedRendererAttempts = 0;
326
- nextRendererRetryAt = 0;
327
- }
328
-
329
- if (!renderer || activeRendererSignature !== rendererSignature) {
330
- if (
331
- failedRendererSignature === rendererSignature &&
332
- performance.now() < nextRendererRetryAt
333
- ) {
334
- scheduleFrame();
335
- return;
336
- }
337
-
338
- if (!rendererRebuildPromise) {
339
- rendererRebuildPromise = (async () => {
340
- try {
341
- const nextRenderer = await createRenderer({
342
- canvas: canvasElement,
343
- fragmentWgsl: materialState.fragmentWgsl,
344
- fragmentLineMap: materialState.fragmentLineMap,
345
- fragmentSource: materialState.fragmentSource,
346
- includeSources: materialState.includeSources,
347
- defineBlockSource: materialState.defineBlockSource,
348
- materialSource: materialState.source,
349
- uniformLayout: materialState.uniformLayout,
350
- textureKeys: materialState.textureKeys,
351
- textureDefinitions: materialState.textures,
352
- getRenderTargets: () => renderTargets,
353
- getPasses: () => passes,
354
- outputColorSpace,
355
- getClearColor: () => clearColor,
356
- getDpr: () => dprState.current,
357
- adapterOptions,
358
- deviceDescriptor
359
- });
360
-
361
- if (isDisposed) {
362
- nextRenderer.destroy();
363
- return;
364
- }
365
-
366
- renderer?.destroy();
367
- renderer = nextRenderer;
368
- activeRendererSignature = rendererSignature;
369
- failedRendererSignature = null;
370
- failedRendererAttempts = 0;
371
- nextRendererRetryAt = 0;
372
- clearError();
373
- } catch (error) {
374
- failedRendererSignature = rendererSignature;
375
- failedRendererAttempts += 1;
376
- const retryDelayMs = getRendererRetryDelayMs(failedRendererAttempts);
377
- nextRendererRetryAt = performance.now() + retryDelayMs;
378
- setError(error, 'initialization');
379
- } finally {
380
- rendererRebuildPromise = null;
381
- scheduleFrame();
382
- }
383
- })();
384
- }
385
-
386
- return;
387
- }
388
-
389
- const time = timestamp / 1000;
390
- const rawDelta = Math.max(0, time - previousTime);
391
- const delta = Math.min(rawDelta, maxDeltaState.current);
392
- previousTime = time;
393
- const rect = canvasElement.getBoundingClientRect();
394
- const width = Math.max(0, Math.floor(rect.width));
395
- const height = Math.max(0, Math.floor(rect.height));
396
- if (width !== currentCssWidth || height !== currentCssHeight) {
397
- currentCssWidth = width;
398
- currentCssHeight = height;
399
- size.set({ width, height });
400
- }
401
-
402
- try {
403
- registry.run({
404
- time,
405
- delta,
406
- setUniform,
407
- setTexture,
408
- invalidate: invalidateFrame,
409
- advance: advanceFrame,
410
- renderMode: registry.getRenderMode(),
411
- autoRender: registry.getAutoRender(),
412
- canvas: canvasElement
413
- });
414
-
415
- const shouldRenderFrame = registry.shouldRender();
416
- shouldContinueAfterFrame =
417
- registry.getRenderMode() === 'always' ||
418
- (registry.getRenderMode() === 'on-demand' && shouldRenderFrame);
419
-
420
- if (shouldRenderFrame) {
421
- for (const key of uniformKeys) {
422
- const runtimeValue = runtimeUniforms[key];
423
- renderUniforms[key] =
424
- runtimeValue === undefined ? (activeUniforms[key] as UniformValue) : runtimeValue;
425
- }
426
-
427
- for (const key of textureKeys) {
428
- const runtimeValue = runtimeTextures[key];
429
- renderTextures[key] =
430
- runtimeValue === undefined ? (activeTextures[key]?.source ?? null) : runtimeValue;
431
- }
432
-
433
- canvasSize.width = width;
434
- canvasSize.height = height;
435
- renderer.render({
436
- time,
437
- delta,
438
- renderMode: registry.getRenderMode(),
439
- uniforms: renderUniforms,
440
- textures: renderTextures,
441
- canvasSize
442
- });
443
- }
444
-
445
- clearError();
446
- } catch (error) {
447
- setError(error, 'render');
448
- } finally {
449
- registry.endFrame();
450
- }
451
-
452
- if (shouldContinueAfterFrame) {
453
- scheduleFrame();
454
- }
455
- };
456
-
457
- (async () => {
458
- try {
459
- const initialMaterial = resolveActiveMaterial();
460
- syncMaterialRuntimeState(initialMaterial);
461
- activeRendererSignature = '';
462
- scheduleFrame();
463
- } catch (error) {
464
- setError(error, 'initialization');
465
- scheduleFrame();
466
- }
467
- })();
468
-
469
- return () => {
470
- isDisposed = true;
471
- requestFrameSignal = null;
472
- if (frameId !== null) {
473
- cancelAnimationFrame(frameId);
474
- frameId = null;
475
- }
476
- renderer?.destroy();
477
- registry.clear();
478
- };
479
- });
480
- </script>
481
-
482
- <div class="motiongpu-canvas-wrap">
483
- <canvas use:bindCanvas class={className} {style}></canvas>
484
- {#if showErrorOverlay && errorReport}
485
- {#if errorRenderer}
486
- {@render errorRenderer(errorReport)}
487
- {:else}
488
- <MotionGPUErrorOverlay report={errorReport} />
489
- {/if}
490
- {/if}
491
- {@render children?.()}
492
- </div>
493
-
494
- <style>
495
- .motiongpu-canvas-wrap {
496
- position: relative;
497
- width: 100%;
498
- height: 100%;
499
- min-width: 0;
500
- min-height: 0;
501
- overflow: hidden;
502
- }
503
-
504
- canvas {
505
- position: absolute;
506
- inset: 0;
507
- display: block;
508
- width: 100%;
509
- height: 100%;
510
- }
511
- </style>
@@ -1,31 +0,0 @@
1
- import { type Readable } from 'svelte/store';
2
- /**
3
- * Readable store with synchronous access to the latest value.
4
- */
5
- export interface CurrentReadable<T> extends Readable<T> {
6
- /**
7
- * Latest store value.
8
- */
9
- readonly current: T;
10
- }
11
- /**
12
- * Writable extension of {@link CurrentReadable}.
13
- */
14
- export interface CurrentWritable<T> extends CurrentReadable<T> {
15
- /**
16
- * Sets next value.
17
- */
18
- set: (value: T) => void;
19
- /**
20
- * Updates value based on previous value.
21
- */
22
- update: (updater: (value: T) => T) => void;
23
- }
24
- /**
25
- * Creates a writable store that exposes the current value without subscription.
26
- *
27
- * @param initialValue - Initial store value.
28
- * @param onChange - Optional side-effect callback invoked on every `set`.
29
- * @returns Current-aware writable store.
30
- */
31
- export declare function currentWritable<T>(initialValue: T, onChange?: (value: T) => void): CurrentWritable<T>;
@@ -1,27 +0,0 @@
1
- import { writable } from 'svelte/store';
2
- /**
3
- * Creates a writable store that exposes the current value without subscription.
4
- *
5
- * @param initialValue - Initial store value.
6
- * @param onChange - Optional side-effect callback invoked on every `set`.
7
- * @returns Current-aware writable store.
8
- */
9
- export function currentWritable(initialValue, onChange) {
10
- let current = initialValue;
11
- const store = writable(initialValue);
12
- const set = (value) => {
13
- current = value;
14
- store.set(value);
15
- onChange?.(value);
16
- };
17
- return {
18
- get current() {
19
- return current;
20
- },
21
- subscribe: store.subscribe,
22
- set,
23
- update(updater) {
24
- set(updater(current));
25
- }
26
- };
27
- }
File without changes