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