Skip to content

Fix stale rendering after shared texture atlas page merges - #6042

Merged
anthonykim1 merged 6 commits into
xtermjs:masterfrom
anthonykim1:anthonykim1/webgl-shared-atlas-pages-version
Jul 13, 2026
Merged

anthonykim1 merged 6 commits into
xtermjs:masterfrom
anthonykim1:anthonykim1/webgl-shared-atlas-pages-version

Conversation

@anthonykim1

@anthonykim1 anthonykim1 commented Jul 9, 2026 •

Copy link
Copy Markdown
Member

Part of: #6038
Related: microsoft/vscode#322756

  • Add a focused Chromium WebGL regression test for terminals sharing one texture atlas.
  • Keep each terminal rendering correctly when another terminal sharing the same texture atlas triggers a page merge.
  • Replace the shared consume-once atlas invalidation signal with a monotonic pageLayoutVersion.
  • Track _lastSeenPageLayoutVersion per GlyphRenderer so every WebGL renderer independently refreshes cached glyph page mappings.
  • Reset the renderer's last-seen page layout version when its atlas is replaced.

TDD:

  • 780cbef3 fails the focused repro with rendered signature drift while terminal B's buffer text remains correct.
  • 9559005 passes the same focused Chromium WebGL integration test.

Verification:

  • npm run build
  • npm run esbuild
  • npm run test-integration -- --suite=addon-webgl --project=Chromium addons/addon-webgl/test/WebglSharedAtlasGarble.test.ts
  • npm run lint-changes

Inspirations from:

  • The full-model refresh behavior after atlas changes comes from WebglRenderer.renderRows.
  • The stale shared-state bug is in the old consume-once atlas invalidation contract: TextureAtlas.beginFrame, page merge invalidation, and oversized-page invalidation.
  • The renderer-side check preserves the existing GlyphRenderer.beginFrame call site while moving acknowledgement state per renderer: GlyphRenderer.beginFrame.

@anthonykim1 anthonykim1 self-assigned this Jul 9, 2026
@anthonykim1 anthonykim1 added this to the 7.0.0 milestone Jul 9, 2026
@anthonykim1
anthonykim1 requested review from Tyriar and jerch July 9, 2026 18:51
@anthonykim1
anthonykim1 marked this pull request as ready for review July 9, 2026 18:51
@anthonykim1
anthonykim1 merged commit b1aee19 into xtermjs:master Jul 13, 2026
12 checks passed
heudev added a commit to heudev/xterm.js that referenced this pull request Jul 16, 2026
TextureAtlas.clearTexture() wipes every page and both cache maps but never
bumped _pageLayoutVersion, so it silently broke the invalidation contract that
xtermjs#6042 introduced for shared atlases.

The atlas is shared by every terminal with an equal config (CharAtlasCache),
but WebglRenderer.clearTextureAtlas() only clears the *calling* renderer's
model. Every other owner keeps vertex data pointing into the rows that were
just wiped, and GlyphRenderer.beginFrame() has no way to notice, so those
terminals keep drawing from stale texture coords until something else happens
to invalidate them. Once the clearing terminal rasterizes new glyphs into the
freed rows, the siblings render whatever landed there instead of their own
text.

Embedders hit this whenever they force a redraw on one terminal while others
are alive; VS Code calls Terminal.clearTextureAtlas() on OS resume, which is
why a stale terminal there stays garbled even after opening a new one (the new
terminal re-acquires the same cached atlas) and only recovers once the whole
panel is closed and the last owner drops the atlas.

Bump _pageLayoutVersion on clear so sibling renderers rebuild their models on
their next frame, matching what page merges and evictions already do.

The added test fails on master (terminal B's row renders terminal A's flood
glyphs) and passes with the fix.
AviPeltz added a commit to ParadevAI/Paradev that referenced this pull request Jul 16, 2026
…ble fix (#5701)

* fix(desktop,web): bump @xterm/* to beta.289 line for shared-atlas garble fix

Terminals sharing a WebGL texture atlas (same font/theme/DPR — all of ours)
rendered garbled multicolor glyph fragments after another terminal triggered
an atlas page merge: the consume-once invalidation flag was eaten by the
first renderer to draw, leaving siblings with stale glyph->texture coords.

Fixed upstream in xtermjs/xterm.js#6042 (per-renderer pageLayoutVersion),
first shipped in xterm 6.1.0-beta.289 / addon-webgl 0.20.0-beta.288.

The versions published 2026-07-13, inside the 3-day minimumReleaseAge
window, so bunfig.toml gets a scoped, dated exclusion for @xterm/* only.

* fix(desktop): hold @xterm/addon-ligatures at beta.220 to unbreak vite build

Its beta.289 bundle inlines opentype.js 2.x, which imports
node:diagnostics_channel — vite externalizes node builtins in the renderer
build and fails on the named import. The atlas fix doesn't touch this addon.
nedtwigg added a commit to diffplug/xterm.js that referenced this pull request Jul 22, 2026
Upstream 8aab310..699f553 is entirely addon-webgl atlas work:

- xtermjs#6042 replaces the consume-once ITextureAtlas.beginFrame() flag with a
  monotonic pageLayoutVersion, so every renderer sharing an atlas learns
  about a page-layout change instead of only the first one to ask.
- xtermjs#6043 adds _evictAllPages() so the atlas can't grow past the renderer's
  texture capacity, plus a defensive clamp in GlyphRenderer.render.
- xtermjs#6056 adds atlas overflow/stress/shared-garble test coverage.

The merge is textually clean; the semantic follow-up to _evictAllPages is
in the next commit.
@neomparam neomparam mentioned this pull request Oct 7, 2026
6 of 9 tasks
onurravli added a commit to onurravli/voltran that referenced this pull request Oct 16, 2026
…tion

The WebGL addon shares one texture atlas between every pane with the
same font and theme. Once enough characters and colours fill it, it
merges pages, and 0.19.0 tells its GPU textures apart only by per-page
version counters that collide after a merge, so a pane keeps sampling a
stale texture. Characters drawn late, such as ş, ç, ğ and ı, came out
in the wrong shape after a while, and in a stress test whole lines
turned to noise.

xtermjs/xterm.js#6042 fixes it but is only released in the betas, so
@xterm/xterm and its five addons are pinned to the matching beta
versions. The daemon's headless terminal and serializer stay on the
stable releases.
jingkuja added a commit to jingkuja/tabby that referenced this pull request Oct 27, 2026
…y output

addon-webgl 0.19 tracks each atlas page's version with a per-page counter.
After a page merge shifts pages down a texture slot, the new page can carry
the same version as the slot's old one, so the GPU keeps sampling the stale
texture until a tab switch forces setAtlas(). Its model-clear flag also never
resets, so every later frame rebuilds the whole model.

Backport the upstream atlas fixes (xtermjs/xterm.js#6042, Eugeny#6043, Eugeny#6055) as a
patch-package patch covering both the readable src and the shipped CJS/ESM
entries:
- global, monotonically increasing page content versions
- per-renderer pageLayoutVersion tracking for merges and shared clears
- schedule a full next frame when the atlas changes mid-frame
- evict all pages when no four same-size pages can merge; bound texture binds

In Tabby, coalesce page-removal events into one deferred redraw per frame,
skipped for hidden panes and cancelled on detach, destroy and context loss.

Make tabby-terminal's patch-package fail loudly, and drop the stale
ansi-color 0.2.1 patch: the locked 0.2.2 already ships the same fix.
Add scripts/rebuild-webgl-addon.mjs to regenerate the patched entries and
`npm run test:webgl` for a real-GPU regression of both entries.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.

1 participant