@motion-core/motion-gpu 0.1.0 → 0.3.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 (70) hide show
  1. package/README.md +37 -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 +36 -0
  8. package/dist/core/error-diagnostics.d.ts +15 -1
  9. package/dist/core/error-diagnostics.js +41 -1
  10. package/dist/core/error-report.d.ts +37 -0
  11. package/dist/core/error-report.js +62 -3
  12. package/dist/{frame-context.d.ts → core/frame-registry.d.ts} +3 -17
  13. package/dist/{frame-context.js → core/frame-registry.js} +2 -37
  14. package/dist/core/index.d.ts +19 -0
  15. package/dist/core/index.js +12 -0
  16. package/dist/core/material-preprocess.d.ts +1 -1
  17. package/dist/core/material-preprocess.js +1 -1
  18. package/dist/core/material.d.ts +4 -4
  19. package/dist/core/material.js +3 -3
  20. package/dist/core/recompile-policy.d.ts +1 -1
  21. package/dist/core/render-graph.d.ts +1 -1
  22. package/dist/core/render-targets.d.ts +1 -1
  23. package/dist/core/render-targets.js +1 -1
  24. package/dist/core/renderer.d.ts +11 -1
  25. package/dist/core/renderer.js +72 -10
  26. package/dist/core/runtime-loop.d.ts +34 -0
  27. package/dist/core/runtime-loop.js +365 -0
  28. package/dist/{advanced-scheduler.d.ts → core/scheduler-helpers.d.ts} +6 -2
  29. package/dist/core/shader.d.ts +2 -2
  30. package/dist/core/shader.js +1 -1
  31. package/dist/core/texture-loader.d.ts +1 -1
  32. package/dist/core/textures.d.ts +1 -1
  33. package/dist/core/textures.js +1 -1
  34. package/dist/core/types.d.ts +4 -0
  35. package/dist/core/uniforms.d.ts +1 -1
  36. package/dist/index.d.ts +3 -14
  37. package/dist/index.js +3 -8
  38. package/dist/passes/BlitPass.d.ts +6 -27
  39. package/dist/passes/BlitPass.js +10 -121
  40. package/dist/passes/CopyPass.d.ts +1 -1
  41. package/dist/passes/CopyPass.js +1 -1
  42. package/dist/passes/FullscreenPass.d.ts +37 -0
  43. package/dist/passes/FullscreenPass.js +131 -0
  44. package/dist/passes/ShaderPass.d.ts +6 -26
  45. package/dist/passes/ShaderPass.js +10 -121
  46. package/dist/passes/index.d.ts +3 -3
  47. package/dist/passes/index.js +3 -3
  48. package/dist/svelte/FragCanvas.svelte +263 -0
  49. package/dist/{FragCanvas.svelte.d.ts → svelte/FragCanvas.svelte.d.ts} +5 -3
  50. package/dist/{MotionGPUErrorOverlay.svelte → svelte/MotionGPUErrorOverlay.svelte} +11 -20
  51. package/dist/{MotionGPUErrorOverlay.svelte.d.ts → svelte/MotionGPUErrorOverlay.svelte.d.ts} +1 -1
  52. package/dist/svelte/advanced.d.ts +11 -0
  53. package/dist/svelte/advanced.js +6 -0
  54. package/dist/svelte/frame-context.d.ts +14 -0
  55. package/dist/svelte/frame-context.js +32 -0
  56. package/dist/svelte/index.d.ts +15 -0
  57. package/dist/svelte/index.js +9 -0
  58. package/dist/{motiongpu-context.d.ts → svelte/motiongpu-context.d.ts} +5 -7
  59. package/dist/{use-motiongpu-user-context.d.ts → svelte/use-motiongpu-user-context.d.ts} +2 -2
  60. package/dist/{use-motiongpu-user-context.js → svelte/use-motiongpu-user-context.js} +1 -1
  61. package/dist/{use-texture.d.ts → svelte/use-texture.d.ts} +7 -2
  62. package/dist/{use-texture.js → svelte/use-texture.js} +9 -3
  63. package/package.json +25 -5
  64. package/dist/FragCanvas.svelte +0 -511
  65. package/dist/current-writable.d.ts +0 -31
  66. package/dist/current-writable.js +0 -27
  67. /package/dist/{advanced-scheduler.js → core/scheduler-helpers.js} +0 -0
  68. /package/dist/{Portal.svelte → svelte/Portal.svelte} +0 -0
  69. /package/dist/{Portal.svelte.d.ts → svelte/Portal.svelte.d.ts} +0 -0
  70. /package/dist/{motiongpu-context.js → svelte/motiongpu-context.js} +0 -0
@@ -1,6 +1,6 @@
1
- import { normalizeTextureDefinition } from './textures';
2
- import { assertUniformName, assertUniformValueForType, inferUniformType, resolveUniformLayout } from './uniforms';
3
- import { normalizeDefines, normalizeIncludes, preprocessMaterialFragment, toDefineLine } from './material-preprocess';
1
+ import { normalizeTextureDefinition } from './textures.js';
2
+ import { assertUniformName, assertUniformValueForType, inferUniformType, resolveUniformLayout } from './uniforms.js';
3
+ import { normalizeDefines, normalizeIncludes, preprocessMaterialFragment, toDefineLine } from './material-preprocess.js';
4
4
  /**
5
5
  * Strict fragment contract used by MotionGPU.
6
6
  */
@@ -1,4 +1,4 @@
1
- import type { OutputColorSpace } from './types';
1
+ import type { OutputColorSpace } from './types.js';
2
2
  /**
3
3
  * Inputs that affect renderer pipeline compilation.
4
4
  */
@@ -1,4 +1,4 @@
1
- import type { RenderPass, RenderPassInputSlot, RenderPassOutputSlot } from './types';
1
+ import type { RenderPass, RenderPassInputSlot, RenderPassOutputSlot } from './types.js';
2
2
  /**
3
3
  * Resolved render-pass step with defaults applied.
4
4
  */
@@ -1,4 +1,4 @@
1
- import type { RenderTargetDefinitionMap } from './types';
1
+ import type { RenderTargetDefinitionMap } from './types.js';
2
2
  /**
3
3
  * Concrete render target configuration resolved for current canvas size.
4
4
  */
@@ -1,4 +1,4 @@
1
- import { assertUniformName } from './uniforms';
1
+ import { assertUniformName } from './uniforms.js';
2
2
  /**
3
3
  * Asserts positive finite numeric input for render target options.
4
4
  */
@@ -1,4 +1,14 @@
1
- import type { Renderer, RendererOptions } from './types';
1
+ import type { Renderer, RendererOptions } from './types.js';
2
+ /**
3
+ * Computes dirty float ranges between two uniform snapshots.
4
+ *
5
+ * Adjacent dirty ranges separated by a gap smaller than or equal to
6
+ * {@link DIRTY_RANGE_MERGE_GAP} are merged to reduce `writeBuffer` calls.
7
+ */
8
+ export declare function findDirtyFloatRanges(previous: Float32Array, next: Float32Array, mergeGapThreshold?: number): Array<{
9
+ start: number;
10
+ count: number;
11
+ }>;
2
12
  /**
3
13
  * Creates the WebGPU renderer used by `FragCanvas`.
4
14
  *
@@ -1,9 +1,9 @@
1
- import { buildRenderTargetSignature, resolveRenderTargetDefinitions } from './render-targets';
2
- import { planRenderGraph } from './render-graph';
3
- import { buildShaderSourceWithMap, formatShaderSourceLocation } from './shader';
4
- import { attachShaderCompilationDiagnostics } from './error-diagnostics';
5
- import { getTextureMipLevelCount, normalizeTextureDefinitions, resolveTextureUpdateMode, resolveTextureSize, toTextureData } from './textures';
6
- import { packUniformsInto } from './uniforms';
1
+ import { buildRenderTargetSignature, resolveRenderTargetDefinitions } from './render-targets.js';
2
+ import { planRenderGraph } from './render-graph.js';
3
+ import { buildShaderSourceWithMap, formatShaderSourceLocation } from './shader.js';
4
+ import { attachShaderCompilationDiagnostics } from './error-diagnostics.js';
5
+ import { getTextureMipLevelCount, normalizeTextureDefinitions, resolveTextureUpdateMode, resolveTextureSize, toTextureData } from './textures.js';
6
+ import { packUniformsInto } from './uniforms.js';
7
7
  /**
8
8
  * Binding index for frame uniforms (`time`, `delta`, `resolution`).
9
9
  */
@@ -78,9 +78,45 @@ async function assertCompilation(module, options) {
78
78
  ...(options?.defineBlockSource !== undefined
79
79
  ? { defineBlockSource: options.defineBlockSource }
80
80
  : {}),
81
- materialSource: options?.materialSource ?? null
81
+ materialSource: options?.materialSource ?? null,
82
+ ...(options?.runtimeContext !== undefined ? { runtimeContext: options.runtimeContext } : {})
82
83
  });
83
84
  }
85
+ function toSortedUniqueStrings(values) {
86
+ return Array.from(new Set(values)).sort((a, b) => a.localeCompare(b));
87
+ }
88
+ function buildPassGraphSnapshot(passes) {
89
+ const declaredPasses = passes ?? [];
90
+ let enabledPassCount = 0;
91
+ const inputs = [];
92
+ const outputs = [];
93
+ for (const pass of declaredPasses) {
94
+ if (pass.enabled === false) {
95
+ continue;
96
+ }
97
+ enabledPassCount += 1;
98
+ const needsSwap = pass.needsSwap ?? true;
99
+ const input = pass.input ?? 'source';
100
+ const output = pass.output ?? (needsSwap ? 'target' : 'source');
101
+ inputs.push(input);
102
+ outputs.push(output);
103
+ }
104
+ return {
105
+ passCount: declaredPasses.length,
106
+ enabledPassCount,
107
+ inputs: toSortedUniqueStrings(inputs),
108
+ outputs: toSortedUniqueStrings(outputs)
109
+ };
110
+ }
111
+ function buildShaderCompilationRuntimeContext(options) {
112
+ const passList = options.getPasses?.() ?? options.passes;
113
+ const renderTargetMap = options.getRenderTargets?.() ?? options.renderTargets;
114
+ return {
115
+ ...(options.materialSignature ? { materialSignature: options.materialSignature } : {}),
116
+ passGraph: buildPassGraphSnapshot(passList),
117
+ activeRenderTargets: Object.keys(renderTargetMap ?? {}).sort((a, b) => a.localeCompare(b))
118
+ };
119
+ }
84
120
  /**
85
121
  * Creates a 1x1 white fallback texture used before user textures become available.
86
122
  */
@@ -182,10 +218,19 @@ function createBindGroupLayoutEntries(textureBindings) {
182
218
  }
183
219
  return entries;
184
220
  }
221
+ /**
222
+ * Maximum gap (in floats) between two dirty ranges that triggers merge.
223
+ *
224
+ * Set to 4 (16 bytes) which covers one vec4f alignment slot.
225
+ */
226
+ const DIRTY_RANGE_MERGE_GAP = 4;
185
227
  /**
186
228
  * Computes dirty float ranges between two uniform snapshots.
229
+ *
230
+ * Adjacent dirty ranges separated by a gap smaller than or equal to
231
+ * {@link DIRTY_RANGE_MERGE_GAP} are merged to reduce `writeBuffer` calls.
187
232
  */
188
- function findDirtyFloatRanges(previous, next) {
233
+ export function findDirtyFloatRanges(previous, next, mergeGapThreshold = DIRTY_RANGE_MERGE_GAP) {
189
234
  const ranges = [];
190
235
  let start = -1;
191
236
  for (let index = 0; index < next.length; index += 1) {
@@ -203,7 +248,22 @@ function findDirtyFloatRanges(previous, next) {
203
248
  if (start !== -1) {
204
249
  ranges.push({ start, count: next.length - start });
205
250
  }
206
- return ranges;
251
+ if (ranges.length <= 1) {
252
+ return ranges;
253
+ }
254
+ const merged = [ranges[0]];
255
+ for (let index = 1; index < ranges.length; index += 1) {
256
+ const prev = merged[merged.length - 1];
257
+ const curr = ranges[index];
258
+ const gap = curr.start - (prev.start + prev.count);
259
+ if (gap <= mergeGapThreshold) {
260
+ prev.count = curr.start + curr.count - prev.start;
261
+ }
262
+ else {
263
+ merged.push(curr);
264
+ }
265
+ }
266
+ return merged;
207
267
  }
208
268
  /**
209
269
  * Determines whether shader output should perform linear-to-sRGB conversion.
@@ -339,6 +399,7 @@ export async function createRenderer(options) {
339
399
  };
340
400
  device.addEventListener('uncapturederror', handleUncapturedError);
341
401
  try {
402
+ const runtimeContext = buildShaderCompilationRuntimeContext(options);
342
403
  const convertLinearToSrgb = shouldConvertLinearToSrgb(options.outputColorSpace, format);
343
404
  const builtShader = buildShaderSourceWithMap(options.fragmentWgsl, options.uniformLayout, options.textureKeys, {
344
405
  convertLinearToSrgb,
@@ -352,7 +413,8 @@ export async function createRenderer(options) {
352
413
  ...(options.defineBlockSource !== undefined
353
414
  ? { defineBlockSource: options.defineBlockSource }
354
415
  : {}),
355
- materialSource: options.materialSource ?? null
416
+ materialSource: options.materialSource ?? null,
417
+ runtimeContext
356
418
  });
357
419
  const normalizedTextureDefinitions = normalizeTextureDefinitions(options.textureDefinitions, options.textureKeys);
358
420
  const textureBindings = options.textureKeys.map((key, index) => {
@@ -0,0 +1,34 @@
1
+ import type { CurrentReadable, CurrentWritable } from './current-value.js';
2
+ import { type FragMaterial } from './material.js';
3
+ import { type MotionGPUErrorReport } from './error-report.js';
4
+ import type { FrameRegistry } from './frame-registry.js';
5
+ import type { FrameInvalidationToken, OutputColorSpace, RenderPass, RenderTargetDefinitionMap } from './types.js';
6
+ export interface MotionGPURuntimeLoopOptions {
7
+ canvas: HTMLCanvasElement;
8
+ registry: FrameRegistry;
9
+ size: CurrentWritable<{
10
+ width: number;
11
+ height: number;
12
+ }>;
13
+ dpr: CurrentReadable<number>;
14
+ maxDelta: CurrentReadable<number>;
15
+ getMaterial: () => FragMaterial;
16
+ getRenderTargets: () => RenderTargetDefinitionMap;
17
+ getPasses: () => RenderPass[];
18
+ getClearColor: () => [number, number, number, number];
19
+ getOutputColorSpace: () => OutputColorSpace;
20
+ getAdapterOptions: () => GPURequestAdapterOptions | undefined;
21
+ getDeviceDescriptor: () => GPUDeviceDescriptor | undefined;
22
+ getOnError: () => ((report: MotionGPUErrorReport) => void) | undefined;
23
+ reportError: (report: MotionGPUErrorReport | null) => void;
24
+ getErrorHistoryLimit?: () => number | undefined;
25
+ getOnErrorHistory?: () => ((history: MotionGPUErrorReport[]) => void) | undefined;
26
+ reportErrorHistory?: (history: MotionGPUErrorReport[]) => void;
27
+ }
28
+ export interface MotionGPURuntimeLoop {
29
+ requestFrame: () => void;
30
+ invalidate: (token?: FrameInvalidationToken) => void;
31
+ advance: () => void;
32
+ destroy: () => void;
33
+ }
34
+ export declare function createMotionGPURuntimeLoop(options: MotionGPURuntimeLoopOptions): MotionGPURuntimeLoop;
@@ -0,0 +1,365 @@
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
+ let activeErrorKey = null;
37
+ let errorHistory = [];
38
+ const getHistoryLimit = () => {
39
+ const value = options.getErrorHistoryLimit?.() ?? 0;
40
+ if (!Number.isFinite(value) || value <= 0) {
41
+ return 0;
42
+ }
43
+ return Math.floor(value);
44
+ };
45
+ const publishErrorHistory = () => {
46
+ options.reportErrorHistory?.(errorHistory);
47
+ const onErrorHistory = options.getOnErrorHistory?.();
48
+ if (!onErrorHistory) {
49
+ return;
50
+ }
51
+ try {
52
+ onErrorHistory(errorHistory);
53
+ }
54
+ catch {
55
+ // User-provided error history handlers must not break runtime error recovery.
56
+ }
57
+ };
58
+ const syncErrorHistory = () => {
59
+ const limit = getHistoryLimit();
60
+ if (limit <= 0) {
61
+ if (errorHistory.length === 0) {
62
+ return;
63
+ }
64
+ errorHistory = [];
65
+ publishErrorHistory();
66
+ return;
67
+ }
68
+ if (errorHistory.length <= limit) {
69
+ return;
70
+ }
71
+ errorHistory = errorHistory.slice(errorHistory.length - limit);
72
+ publishErrorHistory();
73
+ };
74
+ const setError = (error, phase) => {
75
+ const report = toMotionGPUErrorReport(error, phase);
76
+ const reportKey = JSON.stringify({
77
+ phase: report.phase,
78
+ title: report.title,
79
+ message: report.message,
80
+ rawMessage: report.rawMessage
81
+ });
82
+ if (activeErrorKey === reportKey) {
83
+ return;
84
+ }
85
+ activeErrorKey = reportKey;
86
+ const historyLimit = getHistoryLimit();
87
+ if (historyLimit > 0) {
88
+ errorHistory = [...errorHistory, report];
89
+ if (errorHistory.length > historyLimit) {
90
+ errorHistory = errorHistory.slice(errorHistory.length - historyLimit);
91
+ }
92
+ publishErrorHistory();
93
+ }
94
+ options.reportError(report);
95
+ const onError = options.getOnError();
96
+ if (!onError) {
97
+ return;
98
+ }
99
+ try {
100
+ onError(report);
101
+ }
102
+ catch {
103
+ // User-provided error handlers must not break runtime error recovery.
104
+ }
105
+ };
106
+ const clearError = () => {
107
+ if (activeErrorKey === null) {
108
+ return;
109
+ }
110
+ activeErrorKey = null;
111
+ options.reportError(null);
112
+ };
113
+ const scheduleFrame = () => {
114
+ if (isDisposed || frameId !== null) {
115
+ return;
116
+ }
117
+ frameId = requestAnimationFrame(renderFrame);
118
+ };
119
+ const requestFrame = () => {
120
+ scheduleFrame();
121
+ };
122
+ const invalidate = (token) => {
123
+ registry.invalidate(token);
124
+ requestFrame();
125
+ };
126
+ const advance = () => {
127
+ registry.advance();
128
+ requestFrame();
129
+ };
130
+ const resetRuntimeMaps = () => {
131
+ for (const key of Object.keys(runtimeUniforms)) {
132
+ if (!uniformKeySet.has(key)) {
133
+ Reflect.deleteProperty(runtimeUniforms, key);
134
+ }
135
+ }
136
+ for (const key of Object.keys(runtimeTextures)) {
137
+ if (!textureKeySet.has(key)) {
138
+ Reflect.deleteProperty(runtimeTextures, key);
139
+ }
140
+ }
141
+ };
142
+ const resetRenderPayloadMaps = () => {
143
+ for (const key of Object.keys(renderUniforms)) {
144
+ if (!uniformKeySet.has(key)) {
145
+ Reflect.deleteProperty(renderUniforms, key);
146
+ }
147
+ }
148
+ for (const key of Object.keys(renderTextures)) {
149
+ if (!textureKeySet.has(key)) {
150
+ Reflect.deleteProperty(renderTextures, key);
151
+ }
152
+ }
153
+ };
154
+ const syncMaterialRuntimeState = (materialState) => {
155
+ const signatureChanged = activeMaterialSignature !== materialState.signature;
156
+ const defaultsChanged = activeUniforms !== materialState.uniforms || activeTextures !== materialState.textures;
157
+ if (!signatureChanged && !defaultsChanged) {
158
+ return;
159
+ }
160
+ activeUniforms = materialState.uniforms;
161
+ activeTextures = materialState.textures;
162
+ if (!signatureChanged) {
163
+ return;
164
+ }
165
+ uniformKeys = materialState.uniformLayout.entries.map((entry) => entry.name);
166
+ uniformTypes = new Map(materialState.uniformLayout.entries.map((entry) => [entry.name, entry.type]));
167
+ textureKeys = materialState.textureKeys;
168
+ uniformKeySet = new Set(uniformKeys);
169
+ textureKeySet = new Set(textureKeys);
170
+ resetRuntimeMaps();
171
+ resetRenderPayloadMaps();
172
+ activeMaterialSignature = materialState.signature;
173
+ };
174
+ const resolveActiveMaterial = () => {
175
+ return resolveMaterial(options.getMaterial());
176
+ };
177
+ const setUniform = (name, value) => {
178
+ if (!uniformKeySet.has(name)) {
179
+ throw new Error(`Unknown uniform "${name}". Declare it in material.uniforms first.`);
180
+ }
181
+ const expectedType = uniformTypes.get(name);
182
+ if (!expectedType) {
183
+ throw new Error(`Unknown uniform type for "${name}"`);
184
+ }
185
+ assertUniformValueForType(expectedType, value);
186
+ runtimeUniforms[name] = value;
187
+ };
188
+ const setTexture = (name, value) => {
189
+ if (!textureKeySet.has(name)) {
190
+ throw new Error(`Unknown texture "${name}". Declare it in material.textures first.`);
191
+ }
192
+ runtimeTextures[name] = value;
193
+ };
194
+ const renderFrame = (timestamp) => {
195
+ frameId = null;
196
+ if (isDisposed) {
197
+ return;
198
+ }
199
+ syncErrorHistory();
200
+ let materialState;
201
+ try {
202
+ materialState = resolveActiveMaterial();
203
+ }
204
+ catch (error) {
205
+ setError(error, 'initialization');
206
+ scheduleFrame();
207
+ return;
208
+ }
209
+ shouldContinueAfterFrame = false;
210
+ const outputColorSpace = options.getOutputColorSpace();
211
+ const rendererSignature = buildRendererPipelineSignature({
212
+ materialSignature: materialState.signature,
213
+ outputColorSpace
214
+ });
215
+ syncMaterialRuntimeState(materialState);
216
+ if (failedRendererSignature && failedRendererSignature !== rendererSignature) {
217
+ failedRendererSignature = null;
218
+ failedRendererAttempts = 0;
219
+ nextRendererRetryAt = 0;
220
+ }
221
+ if (!renderer || activeRendererSignature !== rendererSignature) {
222
+ if (failedRendererSignature === rendererSignature &&
223
+ performance.now() < nextRendererRetryAt) {
224
+ scheduleFrame();
225
+ return;
226
+ }
227
+ if (!rendererRebuildPromise) {
228
+ rendererRebuildPromise = (async () => {
229
+ try {
230
+ const nextRenderer = await createRenderer({
231
+ canvas: canvasElement,
232
+ fragmentWgsl: materialState.fragmentWgsl,
233
+ fragmentLineMap: materialState.fragmentLineMap,
234
+ fragmentSource: materialState.fragmentSource,
235
+ includeSources: materialState.includeSources,
236
+ defineBlockSource: materialState.defineBlockSource,
237
+ materialSource: materialState.source,
238
+ materialSignature: materialState.signature,
239
+ uniformLayout: materialState.uniformLayout,
240
+ textureKeys: materialState.textureKeys,
241
+ textureDefinitions: materialState.textures,
242
+ getRenderTargets: options.getRenderTargets,
243
+ getPasses: options.getPasses,
244
+ outputColorSpace,
245
+ getClearColor: options.getClearColor,
246
+ getDpr: () => options.dpr.current,
247
+ adapterOptions: options.getAdapterOptions(),
248
+ deviceDescriptor: options.getDeviceDescriptor()
249
+ });
250
+ if (isDisposed) {
251
+ nextRenderer.destroy();
252
+ return;
253
+ }
254
+ renderer?.destroy();
255
+ renderer = nextRenderer;
256
+ activeRendererSignature = rendererSignature;
257
+ failedRendererSignature = null;
258
+ failedRendererAttempts = 0;
259
+ nextRendererRetryAt = 0;
260
+ clearError();
261
+ }
262
+ catch (error) {
263
+ failedRendererSignature = rendererSignature;
264
+ failedRendererAttempts += 1;
265
+ const retryDelayMs = getRendererRetryDelayMs(failedRendererAttempts);
266
+ nextRendererRetryAt = performance.now() + retryDelayMs;
267
+ setError(error, 'initialization');
268
+ }
269
+ finally {
270
+ rendererRebuildPromise = null;
271
+ scheduleFrame();
272
+ }
273
+ })();
274
+ }
275
+ return;
276
+ }
277
+ const time = timestamp / 1000;
278
+ const rawDelta = Math.max(0, time - previousTime);
279
+ const delta = Math.min(rawDelta, options.maxDelta.current);
280
+ previousTime = time;
281
+ const rect = canvasElement.getBoundingClientRect();
282
+ const width = Math.max(0, Math.floor(rect.width));
283
+ const height = Math.max(0, Math.floor(rect.height));
284
+ if (width !== currentCssWidth || height !== currentCssHeight) {
285
+ currentCssWidth = width;
286
+ currentCssHeight = height;
287
+ size.set({ width, height });
288
+ }
289
+ try {
290
+ registry.run({
291
+ time,
292
+ delta,
293
+ setUniform,
294
+ setTexture,
295
+ invalidate,
296
+ advance,
297
+ renderMode: registry.getRenderMode(),
298
+ autoRender: registry.getAutoRender(),
299
+ canvas: canvasElement
300
+ });
301
+ const shouldRenderFrame = registry.shouldRender();
302
+ shouldContinueAfterFrame =
303
+ registry.getRenderMode() === 'always' ||
304
+ (registry.getRenderMode() === 'on-demand' && shouldRenderFrame);
305
+ if (shouldRenderFrame) {
306
+ for (const key of uniformKeys) {
307
+ const runtimeValue = runtimeUniforms[key];
308
+ renderUniforms[key] =
309
+ runtimeValue === undefined ? activeUniforms[key] : runtimeValue;
310
+ }
311
+ for (const key of textureKeys) {
312
+ const runtimeValue = runtimeTextures[key];
313
+ renderTextures[key] =
314
+ runtimeValue === undefined ? (activeTextures[key]?.source ?? null) : runtimeValue;
315
+ }
316
+ canvasSize.width = width;
317
+ canvasSize.height = height;
318
+ renderer.render({
319
+ time,
320
+ delta,
321
+ renderMode: registry.getRenderMode(),
322
+ uniforms: renderUniforms,
323
+ textures: renderTextures,
324
+ canvasSize
325
+ });
326
+ }
327
+ clearError();
328
+ }
329
+ catch (error) {
330
+ setError(error, 'render');
331
+ }
332
+ finally {
333
+ registry.endFrame();
334
+ }
335
+ if (shouldContinueAfterFrame) {
336
+ scheduleFrame();
337
+ }
338
+ };
339
+ (async () => {
340
+ try {
341
+ const initialMaterial = resolveActiveMaterial();
342
+ syncMaterialRuntimeState(initialMaterial);
343
+ activeRendererSignature = '';
344
+ scheduleFrame();
345
+ }
346
+ catch (error) {
347
+ setError(error, 'initialization');
348
+ scheduleFrame();
349
+ }
350
+ })();
351
+ return {
352
+ requestFrame,
353
+ invalidate,
354
+ advance,
355
+ destroy: () => {
356
+ isDisposed = true;
357
+ if (frameId !== null) {
358
+ cancelAnimationFrame(frameId);
359
+ frameId = null;
360
+ }
361
+ renderer?.destroy();
362
+ registry.clear();
363
+ }
364
+ };
365
+ }
@@ -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
  */
@@ -443,6 +443,10 @@ export interface RendererOptions {
443
443
  column?: number;
444
444
  functionName?: string;
445
445
  } | null;
446
+ /**
447
+ * Stable material signature captured during resolution.
448
+ */
449
+ materialSignature?: string;
446
450
  /**
447
451
  * Resolved uniform layout.
448
452
  */
@@ -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';