From aef5579f91874917bebd6c38f73e622bf8b3de18 Mon Sep 17 00:00:00 2001 From: Mistium <92952823+Mistium@users.noreply.github.com> Date: Tue, 6 Oct 2026 19:25:30 +0100 Subject: [PATCH] Skip drawing while the GL context reports it is lost The webglcontextlost event arrives as a separate task, so a frame can still be drawn after the browser has taken the context. If that frame creates the first wrapped texture, twgl's sampler check reads .constructor of the null sampler a lost context returns and throws. draw() now also asks the context itself. --- src/RenderWebGL.js | 4 ++-- test/unit/ContextLossTests.js | 17 ++++++++++++++++- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/RenderWebGL.js b/src/RenderWebGL.js index 633626748..acf2b8c7d 100644 --- a/src/RenderWebGL.js +++ b/src/RenderWebGL.js @@ -968,7 +968,7 @@ class RenderWebGL extends EventEmitter { * @returns {boolean} True while the WebGL context is lost and nothing can be drawn. */ get isContextLost () { - return this._contextLost; + return this._contextLost || this._gl.isContextLost(); } /** @@ -1011,7 +1011,7 @@ class RenderWebGL extends EventEmitter { } draw() { - if (!this.dirty || this._contextLost) { + if (!this.dirty || this.isContextLost) { return; } this.dirty = false; diff --git a/test/unit/ContextLossTests.js b/test/unit/ContextLossTests.js index ae7c48796..a0e73ae02 100644 --- a/test/unit/ContextLossTests.js +++ b/test/unit/ContextLossTests.js @@ -10,7 +10,9 @@ const fakeRenderer = () => { renderer._allDrawables = []; renderer._drawList = []; renderer._contextLost = false; - renderer._gl = {}; + renderer._gl = { + isContextLost: () => false + }; renderer.dirty = true; renderer._listeners = {}; renderer.emit = name => { @@ -39,6 +41,19 @@ test('losing the context stops drawing and asks the browser to restore it', t => t.end(); }); +test('a context the browser has lost is not drawn before the lost event arrives', t => { + const renderer = fakeRenderer(); + renderer._gl.isContextLost = () => true; + t.equal(renderer.isContextLost, true); + + renderer._doExitDrawRegion = () => { + throw new Error('draw must not touch GL while the context is lost'); + }; + t.doesNotThrow(() => renderer.draw()); + t.equal(renderer.dirty, true, 'the frame is still pending'); + t.end(); +}); + test('restoring the context rebuilds renderer resources and tells every skin', t => { const renderer = fakeRenderer(); renderer._contextLost = true;