@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.
- package/README.md +37 -11
- package/dist/advanced.d.ts +3 -11
- package/dist/advanced.js +3 -6
- package/dist/core/advanced.d.ts +6 -0
- package/dist/core/advanced.js +5 -0
- package/dist/core/current-value.d.ts +23 -0
- package/dist/core/current-value.js +36 -0
- package/dist/core/error-diagnostics.d.ts +15 -1
- package/dist/core/error-diagnostics.js +41 -1
- package/dist/core/error-report.d.ts +37 -0
- package/dist/core/error-report.js +62 -3
- package/dist/{frame-context.d.ts → core/frame-registry.d.ts} +3 -17
- package/dist/{frame-context.js → core/frame-registry.js} +2 -37
- package/dist/core/index.d.ts +19 -0
- package/dist/core/index.js +12 -0
- package/dist/core/material-preprocess.d.ts +1 -1
- package/dist/core/material-preprocess.js +1 -1
- package/dist/core/material.d.ts +4 -4
- package/dist/core/material.js +3 -3
- package/dist/core/recompile-policy.d.ts +1 -1
- package/dist/core/render-graph.d.ts +1 -1
- package/dist/core/render-targets.d.ts +1 -1
- package/dist/core/render-targets.js +1 -1
- package/dist/core/renderer.d.ts +11 -1
- package/dist/core/renderer.js +72 -10
- package/dist/core/runtime-loop.d.ts +34 -0
- package/dist/core/runtime-loop.js +365 -0
- package/dist/{advanced-scheduler.d.ts → core/scheduler-helpers.d.ts} +6 -2
- package/dist/core/shader.d.ts +2 -2
- package/dist/core/shader.js +1 -1
- package/dist/core/texture-loader.d.ts +1 -1
- package/dist/core/textures.d.ts +1 -1
- package/dist/core/textures.js +1 -1
- package/dist/core/types.d.ts +4 -0
- package/dist/core/uniforms.d.ts +1 -1
- package/dist/index.d.ts +3 -14
- package/dist/index.js +3 -8
- package/dist/passes/BlitPass.d.ts +6 -27
- package/dist/passes/BlitPass.js +10 -121
- package/dist/passes/CopyPass.d.ts +1 -1
- package/dist/passes/CopyPass.js +1 -1
- package/dist/passes/FullscreenPass.d.ts +37 -0
- package/dist/passes/FullscreenPass.js +131 -0
- package/dist/passes/ShaderPass.d.ts +6 -26
- package/dist/passes/ShaderPass.js +10 -121
- package/dist/passes/index.d.ts +3 -3
- package/dist/passes/index.js +3 -3
- package/dist/svelte/FragCanvas.svelte +263 -0
- package/dist/{FragCanvas.svelte.d.ts → svelte/FragCanvas.svelte.d.ts} +5 -3
- package/dist/{MotionGPUErrorOverlay.svelte → svelte/MotionGPUErrorOverlay.svelte} +11 -20
- package/dist/{MotionGPUErrorOverlay.svelte.d.ts → svelte/MotionGPUErrorOverlay.svelte.d.ts} +1 -1
- package/dist/svelte/advanced.d.ts +11 -0
- package/dist/svelte/advanced.js +6 -0
- package/dist/svelte/frame-context.d.ts +14 -0
- package/dist/svelte/frame-context.js +32 -0
- package/dist/svelte/index.d.ts +15 -0
- package/dist/svelte/index.js +9 -0
- package/dist/{motiongpu-context.d.ts → svelte/motiongpu-context.d.ts} +5 -7
- package/dist/{use-motiongpu-user-context.d.ts → svelte/use-motiongpu-user-context.d.ts} +2 -2
- package/dist/{use-motiongpu-user-context.js → svelte/use-motiongpu-user-context.js} +1 -1
- package/dist/{use-texture.d.ts → svelte/use-texture.d.ts} +7 -2
- package/dist/{use-texture.js → svelte/use-texture.js} +9 -3
- package/package.json +25 -5
- package/dist/FragCanvas.svelte +0 -511
- package/dist/current-writable.d.ts +0 -31
- package/dist/current-writable.js +0 -27
- /package/dist/{advanced-scheduler.js → core/scheduler-helpers.js} +0 -0
- /package/dist/{Portal.svelte → svelte/Portal.svelte} +0 -0
- /package/dist/{Portal.svelte.d.ts → svelte/Portal.svelte.d.ts} +0 -0
- /package/dist/{motiongpu-context.js → svelte/motiongpu-context.js} +0 -0
package/dist/core/material.js
CHANGED
|
@@ -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
|
*/
|
package/dist/core/renderer.d.ts
CHANGED
|
@@ -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
|
*
|
package/dist/core/renderer.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
1
|
+
import type { FrameProfilingSnapshot, FrameRegistry, FrameRunTimings, FrameScheduleSnapshot } from './frame-registry.js';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
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
|
/**
|
package/dist/core/shader.d.ts
CHANGED
|
@@ -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
|
*/
|
package/dist/core/shader.js
CHANGED
package/dist/core/textures.d.ts
CHANGED
|
@@ -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
|
*/
|
package/dist/core/textures.js
CHANGED
package/dist/core/types.d.ts
CHANGED
|
@@ -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
|
*/
|
package/dist/core/uniforms.d.ts
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -1,17 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Root package entrypoint.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Framework-agnostic core entrypoint.
|
|
5
5
|
*/
|
|
6
|
-
export
|
|
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';
|