UNPKG

@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
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