From 20a03aad8789cef7d39d469cea968d80254b6dda Mon Sep 17 00:00:00 2001 From: Sameh Khamis Date: Thu, 24 Sep 2026 21:53:06 -0700 Subject: [PATCH] fix(render): release the atmosphere probe's WebGL context Lose the throwaway WebGL2 probe context right after the link probe instead of leaving it for GC; browsers (Safari especially) cap live contexts. Note that affected devices also lose globe-basemap fog, credit the changelog entry and move it to the top. Co-Authored-By: Claude Opus 5.5 (1M context) --- CHANGELOG.md | 27 ++++++++++++++------------- docs/CURRENT-STATE.md | 5 +++-- src/app/atmosphereCompat.js | 14 +++++++++++++- src/app/atmosphereCompat.test.mjs | 12 ++++++++++++ 4 files changed, 42 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a74edbbae..dceec6a9c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # Changelog +- Render on iPad and iPhone instead of stopping with "An error occurred while + rendering." Cesium's per-vertex model atmosphere binds shader `out` + parameters directly to varyings, which Apple's Metal/ANGLE backend cannot + link, so the program failed and the render loop was torn down. The stage is + now kept out of the pipeline on affected devices by clearing + `scene.fog.renderable`, which leaves `fog.enabled` — and the fog density that + drives 3D Tiles refinement — untouched. Detection is a WebGL2 link probe of + the same pattern, so a future driver fix restores the effect with no code + change, with iOS/iPadOS detection as a backstop. Sky atmosphere and the + ground-atmosphere fragment path route through locals and are unaffected; + affected devices lose distance fog on 3D tiles and on the globe basemaps. + `src/app/atmosphereCompat.test.mjs` pins the probe, the platform matrix and + the `renderable`-not-`enabled` choice (KnottyDyes, #705). + - Region scopes in voice analyst queries ("in the Gulf of Mexico", "over the Alps") work again in the dev server: the bundled Natural Earth and neighborhood packs are fetched as JSON in the browser @@ -196,19 +210,6 @@ Analyst records for loaded satellites, datacenters and dams, with explicit bound retain geometry; incident-link checks abort on disable or selection change, and the row includes a containment legend. -- Render on iPad and iPhone instead of stopping with "An error occurred while - rendering." Cesium's per-vertex model atmosphere binds shader `out` - parameters directly to varyings, which Apple's Metal/ANGLE backend cannot - link, so the program failed and the render loop was torn down. The stage is - now kept out of the pipeline on affected devices by clearing - `scene.fog.renderable`, which leaves `fog.enabled` — and the fog density that - drives 3D Tiles refinement — untouched. Detection is a WebGL2 link probe of - the same pattern, so a future driver fix restores the effect with no code - change, with iOS/iPadOS detection as a backstop. Sky atmosphere and the - ground-atmosphere fragment path route through locals and are unaffected. - `src/app/atmosphereCompat.test.mjs` pins the probe, the platform matrix and - the `renderable`-not-`enabled` choice. - - Remove the spurious scrollbars that appeared on both panel stacks at narrow widths (720px and below) as soon as a panel was expanded. The stacks scroll vertically there, and each panel's decorative glow, absolutely positioned diff --git a/docs/CURRENT-STATE.md b/docs/CURRENT-STATE.md index 92eccb9bd..348a25329 100644 --- a/docs/CURRENT-STATE.md +++ b/docs/CURRENT-STATE.md @@ -701,7 +701,6 @@ snapshots, lifecycle actions and row descriptors. Remount and teardown remove listeners and row subscriptions; obsolete completions do not repaint old rows. The clear control presents busy state while its existing action owns the transaction. - ## Model atmosphere on Apple Metal Cesium's per-vertex model atmosphere is kept out of the pipeline on devices @@ -712,13 +711,15 @@ attaches that stage only when `fog.enabled && fog.renderable`, so the viewer clears `scene.fog.renderable` and leaves `fog.enabled` — and the fog density that drives 3D Tiles screen-space-error scaling — intact. Sky atmosphere and the ground-atmosphere fragment path route through locals and are unaffected. +Affected devices lose distance fog on 3D tiles and on globe basemaps (Esri, +Bing, keyless), since the globe fog shader uses the same `renderable` flag. +The probe releases its throwaway WebGL2 context immediately. Detection is a WebGL2 link probe of the same out-parameter/varying pattern, so a future driver fix restores the effect with no code change; iOS/iPadOS platform detection is the backstop for when no probe context can be created. Applied during viewer construction, before any tile builds a draw command. - ## Map Source control ownership Map Source controls own chip listeners, source-state subscriptions and selection diff --git a/src/app/atmosphereCompat.js b/src/app/atmosphereCompat.js index 802f2b212..1bd23c8ea 100644 --- a/src/app/atmosphereCompat.js +++ b/src/app/atmosphereCompat.js @@ -154,7 +154,19 @@ export function shouldDisableModelAtmosphere({ } = {}) { const gl = createProbeContext(); if (gl) { - if (probeOutParamVaryingLinkFailure(gl)) return true; + let linkFails; + try { + linkFails = probeOutParamVaryingLinkFailure(gl); + } finally { + // Browsers cap live WebGL contexts (Safari especially); release the + // throwaway probe context now rather than waiting for GC. + try { + gl.getExtension?.('WEBGL_lose_context')?.loseContext(); + } catch { + // A context that cannot be released is left to GC. + } + } + if (linkFails) return true; // Probe ran and the driver accepted the pattern. Apple mobile is still // quarantined: the probe program is far simpler than Cesium's, and a // false negative there costs the entire scene. diff --git a/src/app/atmosphereCompat.test.mjs b/src/app/atmosphereCompat.test.mjs index 7f601512b..f1c1d43bf 100644 --- a/src/app/atmosphereCompat.test.mjs +++ b/src/app/atmosphereCompat.test.mjs @@ -207,3 +207,15 @@ test('tolerates a scene with no fog object', () => { assert.equal(applyModelAtmosphereWorkaround({}, {}), false); assert.equal(applyModelAtmosphereWorkaround(null, {}), false); }); + +test('releases the throwaway probe context after probing', () => { + let lost = 0; + const gl = fakeGl({ linkOk: true }); + gl.getExtension = (name) => + name === 'WEBGL_lose_context' ? { loseContext: () => (lost += 1) } : null; + shouldDisableModelAtmosphere({ + navigatorLike: LINUX_DESKTOP, + createProbeContext: () => gl, + }); + assert.equal(lost, 1, 'probe context must not linger until GC'); +});