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:
@@ -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) {
|
||||||
|
|||||||
@@ -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. */
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user