fix(refboard): prevent WebGL OOM on large scenes

- Skip thumbnail generation when scene has >50 items (extract.canvas
  on full viewport with many textures causes GPU OOM and context loss)
- Add WebGL context lost/restored handlers — auto-reload scene on recovery
- Reduce texture memory budget from 512MB to 256MB for GPU headroom
This commit is contained in:
Hiren Kangad
2026-03-10 10:45:14 +05:30
parent a2eea8f22f
commit 451ec339a5
3 changed files with 31 additions and 3 deletions
+25
View File
@@ -224,6 +224,24 @@ const PixiCanvas = forwardRef<PixiCanvasHandle, PixiCanvasProps>(
// Store observer for cleanup // Store observer for cleanup
(container as any).__pixiRO = ro; (container as any).__pixiRO = ro;
// -- WebGL context loss recovery ------------------------------------
const canvas = app.canvas as HTMLCanvasElement;
const onContextLost = (e: Event) => {
e.preventDefault(); // allows context to be restored
console.warn('[PixiCanvas] WebGL context lost — waiting for restore');
};
const onContextRestored = () => {
console.warn('[PixiCanvas] WebGL context restored — reloading scene');
// Re-load scene data so textures get re-uploaded to GPU
if (sceneRef.current && initialLoadDone.current) {
const data = sceneRef.current.serialize();
sceneRef.current.loadScene(data, false);
}
};
canvas.addEventListener('webglcontextlost', onContextLost);
canvas.addEventListener('webglcontextrestored', onContextRestored);
(container as any).__pixiContextHandlers = { onContextLost, onContextRestored, canvas };
// Signal that PixiJS is ready for scene loading // Signal that PixiJS is ready for scene loading
setPixiReady(true); setPixiReady(true);
@@ -254,6 +272,13 @@ const PixiCanvas = forwardRef<PixiCanvasHandle, PixiCanvasProps>(
delete (container as any).__pixiWheelHandler; delete (container as any).__pixiWheelHandler;
} }
const ctxHandlers = (container as any).__pixiContextHandlers;
if (ctxHandlers) {
ctxHandlers.canvas.removeEventListener('webglcontextlost', ctxHandlers.onContextLost);
ctxHandlers.canvas.removeEventListener('webglcontextrestored', ctxHandlers.onContextRestored);
delete (container as any).__pixiContextHandlers;
}
textures.clear(); textures.clear();
if (appRef.current) { if (appRef.current) {
+1 -1
View File
@@ -13,7 +13,7 @@ interface TextureEntry {
*/ */
export class TextureManager { export class TextureManager {
private cache = new Map<string, TextureEntry>(); private cache = new Map<string, TextureEntry>();
private budget = 512 * 1024 * 1024; // 512 MB private budget = 256 * 1024 * 1024; // 256 MB — keeps headroom for GPU ops
private currentUsage = 0; private currentUsage = 0;
/** Build the URL for a given asset. */ /** Build the URL for a given asset. */
+5 -2
View File
@@ -27,12 +27,15 @@ export function useSaveManager({ resolvedBoardId, isPublicView, canvasRef, setSa
try { try {
const state = JSON.stringify(scene.serialize()); const state = JSON.stringify(scene.serialize());
// Generate thumbnail from PixiJS renderer // Generate thumbnail — skip for large scenes to avoid GPU OOM
let thumbnail: string | undefined; let thumbnail: string | undefined;
try { try {
const app = canvasRef.current?.getApp(); const app = canvasRef.current?.getApp();
const viewport = canvasRef.current?.getViewport(); const viewport = canvasRef.current?.getViewport();
if (app?.renderer?.extract && viewport) { 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 fullCanvas = app.renderer.extract.canvas(viewport) as HTMLCanvasElement;
const thumbMax = 400; const thumbMax = 400;
const sw = fullCanvas.width; const sw = fullCanvas.width;