diff --git a/packages/troika-three-text/src/SDFGenerator.js b/packages/troika-three-text/src/SDFGenerator.js index ddcf77cc..07ab2ef9 100644 --- a/packages/troika-three-text/src/SDFGenerator.js +++ b/packages/troika-three-text/src/SDFGenerator.js @@ -116,7 +116,22 @@ function generateSDF_JS_Worker(width, height, path, viewBox, distance, exponent, for (let i = 0; i < textureData.length; i++) { imageData[i * 4 + channel] = textureData[i] } - mainThreadGenerator.webglUtils.renderImageData(canvas, imageData, x, y, width, height, 1 << (3 - channel)) + // The atlas context may have been lost while the worker was generating (before the async + // webglcontextlost event has flipped atlas.contextLost), in which case this GL write would + // throw from deep inside webgl-sdf-generator as an unhandled rejection, one per in-flight + // glyph, and strand the typeset callback. Dropping the write is safe: the TextBuilder's + // webglcontextrestored handler regenerates every known glyph into the restored atlas. A null + // probe means context creation was refused under the same GPU pressure - equally benign to + // drop, and no restore event will ever fire for a context that never existed. The probe + // passes the generator's context attributes so a probe that succeeds can't stick a + // wrong-attribute context onto the atlas. Rethrow only when a healthy context proves the + // error was unrelated. + try { + mainThreadGenerator.webglUtils.renderImageData(canvas, imageData, x, y, width, height, 1 << (3 - channel)) + } catch (err) { + const gl = typeof canvas.getContext === 'function' ? canvas.getContext('webgl', { premultipliedAlpha: false, preserveDrawingBuffer: true, antialias: false, depth: false }) : null + if (gl && !gl.isContextLost()) throw err + } timing += now() - start // clean up workers after a while diff --git a/packages/troika-three-text/src/TextBuilder.js b/packages/troika-three-text/src/TextBuilder.js index c4876d2d..f8d0c8e2 100644 --- a/packages/troika-three-text/src/TextBuilder.js +++ b/packages/troika-three-text/src/TextBuilder.js @@ -317,7 +317,16 @@ function getTextRenderInfo(args, callback) { if (neededHeight > currentHeight) { // Since resizing the canvas clears its render buffer, it needs special handling to copy the old contents over console.info(`Increasing SDF texture size ${currentHeight}->${neededHeight}`) - resizeWebGLCanvasWithoutClearing(sdfCanvas, textureWidth, neededHeight) + // If the context was lost, the canvas dimensions are still applied before the GL copy-over + // throws; drop the copy in that case - the webglcontextrestored handler regenerates every + // glyph into the resized atlas. The dispose below must still run either way so the texture + // reallocates at the new canvas size. + try { + resizeWebGLCanvasWithoutClearing(sdfCanvas, textureWidth, neededHeight) + } catch (err) { + const gl = sdfCanvas.getContext('webgl', { premultipliedAlpha: false, preserveDrawingBuffer: true, antialias: false, depth: false }) + if (gl && !gl.isContextLost()) throw err + } // As of Three r136 textures cannot be resized once they're allocated on the GPU, we must dispose to reallocate it sdfTexture.dispose() }