Decouple UI updates from WebGL renderer

This commit is contained in:
2026-02-15 11:16:00 +00:00
parent 93d655bffb
commit 2666f6570c
2 changed files with 39 additions and 6 deletions
+4 -2
View File
@@ -1,5 +1,5 @@
import { Provider } from '@react-spectrum/s2';
import { lazy, Suspense, useRef, useState } from 'react';
import { lazy, Suspense, useDeferredValue, useRef, useState } from 'react';
import type { EffectCanvasHandle } from './components/canvas/effect';
import { Controls } from './components/controls';
import { ImageLoader } from './components/image-loader';
@@ -34,6 +34,8 @@ const App = () => {
const [activeTab, setActiveTab] = useState<ActiveTab>('mask');
const effectCanvasRef = useRef<EffectCanvasHandle>(null);
const deferredDrosteParams = useDeferredValue(drosteParams);
useZoomAnimation({
isAnimating: drosteParams.zoomAnimating,
isActive: activeTab === 'effect',
@@ -77,7 +79,7 @@ const App = () => {
ref={effectCanvasRef}
image={image}
curve={curve}
drosteParams={drosteParams}
drosteParams={deferredDrosteParams}
/>
</Suspense>
);
+35 -4
View File
@@ -1,6 +1,6 @@
import { type RefObject, useEffect, useRef } from 'react';
import { computeOptimalCenter, computeOptimalScale } from '../../math/bezier';
import type { BezierCurve, DrosteParams } from '../../types';
import type { BezierCurve, DrosteParams, Point } from '../../types';
import { DrosteRenderer } from '../../webgl/droste-renderer';
type RendererConfig = {
@@ -10,13 +10,23 @@ type RendererConfig = {
drosteParams: DrosteParams;
};
type RenderParams = {
center: Point;
scaleFactor: number;
drosteParams: DrosteParams;
};
export function useDrosteRenderer({ canvasRef, image, curve, drosteParams }: RendererConfig) {
const rendererRef = useRef<DrosteRenderer | null>(null);
const rafIdRef = useRef<number | null>(null);
const renderParamsRef = useRef<RenderParams | null>(null);
const center = computeOptimalCenter(curve, image.width, image.height);
const autoScale = computeOptimalScale(curve, center, image.width, image.height);
const scaleFactor = drosteParams.scaleOverride ?? autoScale;
renderParamsRef.current = { center, scaleFactor, drosteParams };
useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) return;
@@ -56,14 +66,35 @@ export function useDrosteRenderer({ canvasRef, image, curve, drosteParams }: Ren
}, [curve, center]);
useEffect(() => {
const renderer = rendererRef.current;
const canvas = canvasRef.current;
if (!renderer || !canvas) return;
if (!canvas) return;
canvas.width = image.width;
canvas.height = image.height;
}, [image.width, image.height, canvasRef]);
renderer.render(center, scaleFactor, drosteParams);
useEffect(() => {
const renderer = rendererRef.current;
if (!renderer) return;
if (rafIdRef.current !== null) {
cancelAnimationFrame(rafIdRef.current);
}
rafIdRef.current = requestAnimationFrame(() => {
rafIdRef.current = null;
const params = renderParamsRef.current;
if (params) {
renderer.render(params.center, params.scaleFactor, params.drosteParams);
}
});
return () => {
if (rafIdRef.current !== null) {
cancelAnimationFrame(rafIdRef.current);
rafIdRef.current = null;
}
};
}, [image, center, scaleFactor, curve, drosteParams, canvasRef]);
const download = () => {