perf: remove thumbnail generation from hot autosave path
extract.canvas(viewport) snapshots the entire viewport to a WebGL texture on the main thread. When viewport dimensions × devicePixelRatio exceed GPU limits, this causes texImage2D errors and ~1000ms stalls that block focus, typing, and scroll. Thumbnails can be generated on idle or server-side instead.
This commit is contained in:
@@ -12,7 +12,12 @@ interface SaveManagerOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Debounced save with thumbnail generation from PixiJS renderer.
|
* Debounced autosave — state only, no thumbnail.
|
||||||
|
*
|
||||||
|
* Thumbnail generation (extract.canvas on the full viewport) was removed
|
||||||
|
* from the hot save path because it blocks the main thread and GPU for
|
||||||
|
* hundreds of ms, causing focus/typing/wheel jank. Thumbnails should be
|
||||||
|
* generated on idle, on explicit export, or server-side.
|
||||||
*/
|
*/
|
||||||
export function useSaveManager({ resolvedBoardId, isPublicView, readOnly, canvasRef, setSaveStatus }: SaveManagerOptions) {
|
export function useSaveManager({ resolvedBoardId, isPublicView, readOnly, canvasRef, setSaveStatus }: SaveManagerOptions) {
|
||||||
const saveTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
const saveTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||||
@@ -27,38 +32,7 @@ export function useSaveManager({ resolvedBoardId, isPublicView, readOnly, canvas
|
|||||||
setSaveStatus('saving');
|
setSaveStatus('saving');
|
||||||
try {
|
try {
|
||||||
const state = JSON.stringify(scene.serialize());
|
const state = JSON.stringify(scene.serialize());
|
||||||
|
await saveCanvas(resolvedBoardId, state, undefined);
|
||||||
// Generate thumbnail — skip for large scenes to avoid GPU OOM
|
|
||||||
let thumbnail: string | undefined;
|
|
||||||
try {
|
|
||||||
const app = canvasRef.current?.getApp();
|
|
||||||
const viewport = canvasRef.current?.getViewport();
|
|
||||||
const itemCount = scene.getAllItems().length;
|
|
||||||
// Skip thumbnail extraction when >50 items — extract.canvas on
|
|
||||||
// the full viewport with many textures causes WebGL OOM.
|
|
||||||
if (app?.renderer?.extract && viewport && itemCount <= 50) {
|
|
||||||
const fullCanvas = app.renderer.extract.canvas(viewport) as HTMLCanvasElement;
|
|
||||||
const thumbMax = 400;
|
|
||||||
const sw = fullCanvas.width;
|
|
||||||
const sh = fullCanvas.height;
|
|
||||||
if (sw > 0 && sh > 0) {
|
|
||||||
const ratio = Math.min(thumbMax / sw, thumbMax / sh, 1);
|
|
||||||
const tw = Math.round(sw * ratio);
|
|
||||||
const th = Math.round(sh * ratio);
|
|
||||||
const offscreen = document.createElement('canvas');
|
|
||||||
offscreen.width = tw;
|
|
||||||
offscreen.height = th;
|
|
||||||
const ctx = offscreen.getContext('2d');
|
|
||||||
if (ctx) {
|
|
||||||
ctx.drawImage(fullCanvas, 0, 0, tw, th);
|
|
||||||
thumbnail = offscreen.toDataURL('image/webp', 0.6);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (thumbErr) {
|
|
||||||
console.warn('Thumbnail generation failed:', thumbErr);
|
|
||||||
}
|
|
||||||
await saveCanvas(resolvedBoardId, state, thumbnail);
|
|
||||||
setSaveStatus('saved');
|
setSaveStatus('saved');
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
if (err?.response?.status === 403) {
|
if (err?.response?.status === 403) {
|
||||||
|
|||||||
Reference in New Issue
Block a user