Skip to content

fix(webgl): release the GL context on dispose - #6069

Open
openwong2kim wants to merge 1 commit into
xtermjs:masterfrom
openwong2kim:fix/webgl-dispose-lose-context
Open

fix(webgl): release the GL context on dispose#6069
openwong2kim wants to merge 1 commit into
xtermjs:masterfrom
openwong2kim:fix/webgl-dispose-lose-context

Conversation

@openwong2kim

Copy link
Copy Markdown

Fixes #6068

What

Explicitly lose the WebGL2 context when the WebGL renderer is disposed, by
calling WEBGL_lose_context.loseContext() in the renderer's teardown:

this._canvas.parentElement?.removeChild(this._canvas);
removeTerminalFromCache(this._terminal);
// Detaching the canvas does not free the underlying WebGL2 context; it
// lingers until it is garbage collected. Browsers cap the number of live
// WebGL contexts per document (Chromium ~16), so in apps that create and
// dispose many terminals these orphaned contexts accumulate and the
// browser force-evicts the oldest LIVE context, blanking an active
// terminal. Explicitly lose the context so it is released immediately.
this._gl.getExtension('WEBGL_lose_context')?.loseContext();

Why

WebglAddon.dispose() detached the canvas but never released the underlying
WebGL2 context. It lingered until garbage collection, so every disposed addon
left a zombie context behind. Browsers cap the number of live WebGL contexts
per document (Chromium ~16, driver/build dependent); once an app creates and
disposes enough terminals to cross the cap, the browser force-evicts the
oldest context — which can belong to a terminal that is still on screen — and
that live terminal goes blank. See #6068 for a repro and full
analysis.

loseContext() is the standard, documented way to release a context
deterministically. getExtension('WEBGL_lose_context') is optional-chained so
a browser that does not expose the extension simply degrades to the previous
(leak-on-GC) behavior instead of throwing.

Tests

Added addons/addon-webgl/test/WebglContextRelease.test.ts, an integration test
that spies on WEBGL_lose_context.loseContext and asserts dispose() invokes
it exactly once. The cap-eviction itself is GPU/driver dependent and does not
reproduce under headless SwiftShader, so the test verifies the mechanism
directly rather than trying to force an eviction. Verified it fails without this
change (loseContext called 0 times) and passes with it.

  • npm run tsc — clean
  • eslint on the changed files — clean
  • New test passes on Chromium; the pre-existing shared-renderer color-comparison
    failures under headless SwiftShader are unaffected by this change (they fail
    identically on master).

WebglAddon.dispose() detached the canvas but never freed the underlying
WebGL2 context, so it lingered until garbage collection. Browsers cap the
number of live WebGL contexts per document (Chromium ~16); in apps that
create and dispose many terminals these orphaned contexts pile up and the
browser force-evicts the oldest LIVE context, blanking an active terminal.

Explicitly call WEBGL_lose_context.loseContext() in the renderer teardown so
the context is released immediately and the live count stays bounded.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

WebglAddon.dispose() does not release the underlying WebGL2 context

1 participant