@motion-core/motion-gpu
Version:
Framework-agnostic WebGPU runtime for fullscreen WGSL shaders with explicit Svelte, React, and Vue adapter entrypoints.
241 lines (240 loc) • 7.4 kB
JavaScript
import { toMotionGPUErrorReport } from "../core/error-report.js";
import { createCurrentWritable } from "../core/current-value.js";
import { createFrameRegistry } from "../core/frame-registry.js";
import { createMotionGPURuntimeLoop } from "../core/runtime-loop.js";
import { MotionGPUReactContext } from "./motiongpu-context.js";
import { FrameRegistryReactContext } from "./frame-context.js";
import { MotionGPUErrorOverlay } from "./MotionGPUErrorOverlay.js";
import { useEffect, useRef, useState } from "react";
import { jsx, jsxs } from "react/jsx-runtime";
//#region src/lib/react/FragCanvas.tsx
function getInitialDpr() {
if (typeof window === "undefined") return 1;
return window.devicePixelRatio ?? 1;
}
function createRuntimeState(initialDpr) {
const registry = createFrameRegistry({ maxDelta: .1 });
const canvasRef = { current: void 0 };
const requestFrameSignalRef = { current: null };
const requestFrame = () => {
requestFrameSignalRef.current?.();
};
const invalidateFrame = () => {
registry.invalidate();
requestFrame();
};
const advanceFrame = () => {
registry.advance();
requestFrame();
};
const size = createCurrentWritable({
width: 0,
height: 0
});
const dprState = createCurrentWritable(initialDpr, requestFrame);
const maxDeltaState = createCurrentWritable(.1, (value) => {
registry.setMaxDelta(value);
requestFrame();
});
const renderModeState = createCurrentWritable("always", (value) => {
registry.setRenderMode(value);
requestFrame();
});
const autoRenderState = createCurrentWritable(true, (value) => {
registry.setAutoRender(value);
requestFrame();
});
return {
registry,
context: {
get canvas() {
return canvasRef.current;
},
size,
dpr: dprState,
maxDelta: maxDeltaState,
renderMode: renderModeState,
autoRender: autoRenderState,
user: createCurrentWritable({}),
invalidate: invalidateFrame,
advance: advanceFrame,
scheduler: {
createStage: registry.createStage,
getStage: registry.getStage,
setDiagnosticsEnabled: registry.setDiagnosticsEnabled,
getDiagnosticsEnabled: registry.getDiagnosticsEnabled,
getLastRunTimings: registry.getLastRunTimings,
getSchedule: registry.getSchedule,
setProfilingEnabled: registry.setProfilingEnabled,
setProfilingWindow: registry.setProfilingWindow,
resetProfiling: registry.resetProfiling,
getProfilingEnabled: registry.getProfilingEnabled,
getProfilingWindow: registry.getProfilingWindow,
getProfilingSnapshot: registry.getProfilingSnapshot
}
},
canvasRef,
size,
dprState,
maxDeltaState,
renderModeState,
autoRenderState,
requestFrameSignalRef,
requestFrame,
invalidateFrame,
advanceFrame
};
}
function getNormalizedErrorHistoryLimit(value) {
if (!Number.isFinite(value) || value <= 0) return 0;
return Math.floor(value);
}
function FragCanvas({ material, renderTargets = {}, passes = [], clearColor = [
0,
0,
0,
1
], color = void 0, renderMode = "always", autoRender = true, maxDelta = .1, adapterOptions = void 0, deviceDescriptor = void 0, dpr = getInitialDpr(), showErrorOverlay = true, errorRenderer, onError = void 0, errorHistoryLimit = 0, onErrorHistory = void 0, className = "", style, children }) {
const runtimeRef = useRef(null);
if (!runtimeRef.current) runtimeRef.current = createRuntimeState(getInitialDpr());
const runtime = runtimeRef.current;
const runtimePropsRef = useRef({
material,
renderTargets,
passes,
clearColor,
color,
adapterOptions,
deviceDescriptor,
onError,
errorHistoryLimit,
onErrorHistory
});
runtimePropsRef.current = {
material,
renderTargets,
passes,
clearColor,
color,
adapterOptions,
deviceDescriptor,
onError,
errorHistoryLimit,
onErrorHistory
};
const [errorReport, setErrorReport] = useState(null);
const [errorHistory, setErrorHistory] = useState([]);
useEffect(() => {
runtime.renderModeState.set(renderMode);
}, [renderMode, runtime]);
useEffect(() => {
runtime.autoRenderState.set(autoRender);
}, [autoRender, runtime]);
useEffect(() => {
runtime.maxDeltaState.set(maxDelta);
}, [maxDelta, runtime]);
useEffect(() => {
runtime.dprState.set(dpr);
}, [dpr, runtime]);
useEffect(() => {
const limit = getNormalizedErrorHistoryLimit(errorHistoryLimit);
if (limit <= 0) {
if (errorHistory.length === 0) return;
setErrorHistory([]);
onErrorHistory?.([]);
return;
}
if (errorHistory.length <= limit) return;
const trimmed = errorHistory.slice(errorHistory.length - limit);
setErrorHistory(trimmed);
onErrorHistory?.(trimmed);
}, [
errorHistory,
errorHistoryLimit,
onErrorHistory
]);
useEffect(() => {
const canvas = runtime.canvasRef.current;
if (!canvas) {
const report = toMotionGPUErrorReport(/* @__PURE__ */ new Error("Canvas element is not available"), "initialization");
setErrorReport(report);
const historyLimit = getNormalizedErrorHistoryLimit(runtimePropsRef.current.errorHistoryLimit);
if (historyLimit > 0) {
const nextHistory = [report].slice(-historyLimit);
setErrorHistory(nextHistory);
runtimePropsRef.current.onErrorHistory?.(nextHistory);
}
runtimePropsRef.current.onError?.(report);
return () => {
runtime.registry.clear();
};
}
const runtimeLoop = createMotionGPURuntimeLoop({
canvas,
registry: runtime.registry,
size: runtime.size,
dpr: runtime.dprState,
maxDelta: runtime.maxDeltaState,
getMaterial: () => runtimePropsRef.current.material,
getRenderTargets: () => runtimePropsRef.current.renderTargets,
getPasses: () => runtimePropsRef.current.passes,
getClearColor: () => runtimePropsRef.current.clearColor,
getColor: () => runtimePropsRef.current.color,
getAdapterOptions: () => runtimePropsRef.current.adapterOptions,
getDeviceDescriptor: () => runtimePropsRef.current.deviceDescriptor,
getOnError: () => runtimePropsRef.current.onError,
getErrorHistoryLimit: () => runtimePropsRef.current.errorHistoryLimit,
getOnErrorHistory: () => runtimePropsRef.current.onErrorHistory,
reportErrorHistory: (history) => {
setErrorHistory(history);
},
reportError: (report) => {
setErrorReport(report);
}
});
runtime.requestFrameSignalRef.current = runtimeLoop.requestFrame;
return () => {
runtime.requestFrameSignalRef.current = null;
runtimeLoop.destroy();
};
}, [runtime]);
const canvasStyle = {
position: "absolute",
inset: 0,
display: "block",
width: "100%",
height: "100%",
...style
};
return /* @__PURE__ */ jsx(FrameRegistryReactContext.Provider, {
value: runtime.registry,
children: /* @__PURE__ */ jsx(MotionGPUReactContext.Provider, {
value: runtime.context,
children: /* @__PURE__ */ jsxs("div", {
className: "motiongpu-canvas-wrap",
style: {
position: "relative",
width: "100%",
height: "100%",
minWidth: 0,
minHeight: 0,
overflow: "hidden"
},
children: [
/* @__PURE__ */ jsx("canvas", {
className,
style: canvasStyle,
ref: (node) => {
runtime.canvasRef.current = node ?? void 0;
}
}),
showErrorOverlay && errorReport ? errorRenderer ? errorRenderer(errorReport) : /* @__PURE__ */ jsx(MotionGPUErrorOverlay, { report: errorReport }) : null,
children
]
})
})
});
}
//#endregion
export { FragCanvas };
//# sourceMappingURL=FragCanvas.js.map