diff options
| author | Maze Winther <mazewinther@gmail.com> | 2026-04-22 16:48:37 +0200 |
|---|---|---|
| committer | Maze Winther <mazewinther@gmail.com> | 2026-04-22 16:48:37 +0200 |
| commit | cceb835a84c125990ad8e49784e01a7c52636bb3 (patch) | |
| tree | 8520cce9a203c72c7bcdc72ca138a36316a5a110 /apps/web/src/preview/components | |
| parent | f1b45b4328cff6a016720ce7b38b87ac2992dc2d (diff) | |
| download | opencut-cceb835a84c125990ad8e49784e01a7c52636bb3.tar.gz | |
perf: improve rendering performance by ~20x
Diffstat (limited to 'apps/web/src/preview/components')
| -rw-r--r-- | apps/web/src/preview/components/index.tsx | 100 |
1 files changed, 57 insertions, 43 deletions
diff --git a/apps/web/src/preview/components/index.tsx b/apps/web/src/preview/components/index.tsx index d3cb2a49..e8f5c572 100644 --- a/apps/web/src/preview/components/index.tsx +++ b/apps/web/src/preview/components/index.tsx @@ -132,7 +132,7 @@ function PreviewCanvas({ isVisible: boolean; }) => void; }) { - const canvasRef = useRef<HTMLCanvasElement>(null); + const canvasMountRef = useRef<HTMLDivElement>(null); const viewportRef = useRef<HTMLDivElement>(null); const lastFrameRef = useRef(-1); const lastSceneRef = useRef<RootNode | null>(null); @@ -158,35 +158,51 @@ function PreviewCanvas({ }); }, [nativeWidth, nativeHeight, activeProject.settings.fps]); + // Mount the compositor's output canvas directly into the preview. wgpu + // renders straight into this element, so there is no intermediate copy — + // the container div owns positioning/styling, the canvas itself fills it. + useEffect(() => { + const mount = canvasMountRef.current; + if (!mount) return; + const outputCanvas = renderer.getOutputCanvas(); + outputCanvas.style.display = "block"; + outputCanvas.style.width = "100%"; + outputCanvas.style.height = "100%"; + mount.appendChild(outputCanvas); + return () => { + if (outputCanvas.parentElement === mount) { + mount.removeChild(outputCanvas); + } + }; + }, [renderer]); + const render = useCallback(() => { - if (canvasRef.current && renderTree && !renderingRef.current) { - const renderTime = Math.min( - editor.playback.getCurrentTime(), - editor.timeline.getLastFrameTime(), - ); - const ticksPerFrame = Math.round( - (TICKS_PER_SECOND * renderer.fps.denominator) / renderer.fps.numerator, - ); - const frame = Math.floor(renderTime / ticksPerFrame); + if (!renderTree || renderingRef.current) return; - if ( - frame !== lastFrameRef.current || - renderTree !== lastSceneRef.current - ) { - renderingRef.current = true; - lastSceneRef.current = renderTree; - lastFrameRef.current = frame; - renderer - .renderToCanvas({ - node: renderTree, - time: renderTime, - targetCanvas: canvasRef.current, - }) - .then(() => { - renderingRef.current = false; - }); - } + const renderTime = Math.min( + editor.playback.getCurrentTime(), + editor.timeline.getLastFrameTime(), + ); + const ticksPerFrame = Math.round( + (TICKS_PER_SECOND * renderer.fps.denominator) / renderer.fps.numerator, + ); + const frame = Math.floor(renderTime / ticksPerFrame); + + if ( + frame === lastFrameRef.current && + renderTree === lastSceneRef.current + ) { + return; } + + renderingRef.current = true; + lastSceneRef.current = renderTree; + lastFrameRef.current = frame; + renderer + .render({ node: renderTree, time: renderTime }) + .then(() => { + renderingRef.current = false; + }); }, [renderer, renderTree, editor.playback, editor.timeline]); useRafLoop(render); @@ -286,22 +302,20 @@ function PreviewCanvas({ ref={viewportRef} className="relative flex size-full min-h-0 min-w-0 items-center justify-center overflow-hidden" > - <canvas - ref={canvasRef} - width={nativeWidth} - height={nativeHeight} - className="absolute block border" - style={{ - left: viewport.sceneLeft, - top: viewport.sceneTop, - width: viewport.sceneWidth, - height: viewport.sceneHeight, - background: - activeProject.settings.background.type === "blur" - ? "transparent" - : activeProject?.settings.background.color, - }} - /> + <div + ref={canvasMountRef} + className="absolute block border" + style={{ + left: viewport.sceneLeft, + top: viewport.sceneTop, + width: viewport.sceneWidth, + height: viewport.sceneHeight, + background: + activeProject.settings.background.type === "blur" + ? "transparent" + : activeProject?.settings.background.color, + }} + /> <PreviewOverlayLayer instances={overlayInstances} plane="under-interaction" |
