Read a parked globe's WebGL context from the one it was built with instead of asking its canvas during a switch

Asking the canvas again during the switch made the reuse depend on getContext at that moment: with new contexts refused (qa-strict-failure-matrix, cold Navara to Cesium) the rollback to the parked Navara threw and left no globe.
This commit is contained in:
German Khokhlov committed 2026-11-03 23:33:50 +04:00
1 parent 90d64e6916
commit 726e201314
2 files changed
+25 -8

No files matched your search

+9 -6
View File
@@ -9,11 +9,8 @@ export function createRendererCache() {
let entry = idle.get(kind); let entry = idle.get(kind);
idle.delete(kind); idle.delete(kind);
// loseContext() takes effect before its queued DOM event is delivered. // loseContext() takes effect before its queued DOM event is delivered.
// Check the existing context too, before resuming a parked render loop. // Check the context itself too, before resuming a parked render loop.
const canvas = entry?.scene.engine.host.canvas; if (entry && (entry.lost || entry.context?.isContextLost?.())) {
const context =
canvas?.getContext?.('webgl2') ?? canvas?.getContext?.('webgl');
if (entry && (entry.lost || context?.isContextLost())) {
entry.dispose(); entry.dispose();
entry = null; entry = null;
} }
@@ -39,7 +36,13 @@ export function createRendererCache() {
container, container,
defer: (fn) => cleanups.push(fn), defer: (fn) => cleanups.push(fn),
}); });
entry = { container, scene, dispose, lost: false }; // The context the globe draws with, read once while it is new: a
// switch reads its state and never asks the canvas again (a canvas
// asked for another context type would create one).
const canvas = scene.engine.host.canvas;
const context =
canvas.getContext?.('webgl2') ?? canvas.getContext?.('webgl') ?? null;
entry = { container, scene, dispose, lost: false, context };
scene.engine.host.canvas.addEventListener( scene.engine.host.canvas.addEventListener(
'webglcontextlost', 'webglcontextlost',
() => { () => {
+16 -2
View File
@@ -27,7 +27,11 @@ test('a parked renderer is reused, but a lost context is disposed before rebuild
defer(() => { defer(() => {
released += 1; released += 1;
}); });
const canvas = new EventTarget();
const context = { lost: false, isContextLost: () => context.lost };
canvas.getContext = (type) => (type === 'webgl2' ? context : null);
return { return {
context,
suspend() { suspend() {
this.running = false; this.running = false;
}, },
@@ -35,7 +39,7 @@ test('a parked renderer is reused, but a lost context is disposed before rebuild
this.running = true; this.running = true;
}, },
engine: { engine: {
host: { container, canvas: new EventTarget() }, host: { container, canvas },
render: { request() {} }, render: { request() {} },
}, },
}; };
@@ -49,13 +53,23 @@ test('a parked renderer is reused, but a lost context is disposed before rebuild
release(); release();
assert.equal(first.running, false); assert.equal(first.running, false);
assert.equal(first.engine.host.container.isConnected, false); assert.equal(first.engine.host.container.isConnected, false);
// A parked renderer whose canvas no longer hands out contexts is still
// reused when its own context is alive.
first.engine.host.canvas.getContext = () => {
throw new Error('getContext called during a switch');
};
assert.equal(await open(), first); assert.equal(await open(), first);
assert.equal(first.running, true); assert.equal(first.running, true);
assert.equal(first.engine.host.container.isConnected, true); assert.equal(first.engine.host.container.isConnected, true);
cache.retain('navara'); cache.retain('navara');
release(); release();
// The WebGL context is already lost, but its DOM event has not fired. // The WebGL context is already lost, but its DOM event has not fired.
first.engine.host.canvas.getContext = () => ({ isContextLost: () => true }); // The switch reads the context the globe was built with and never asks
// the canvas again (a QA or driver failure of getContext must not decide).
first.context.lost = true;
first.engine.host.canvas.getContext = () => {
throw new Error('getContext called during a switch');
};
const second = await open(); const second = await open();
assert.notEqual(second, first); assert.notEqual(second, first);
assert.equal(released, 1); assert.equal(released, 1);