Skip to content

Fix webgl rendering corruption from atlas page merges#5883

Open
vsych wants to merge 1 commit into
xtermjs:masterfrom
vsych:fix-webgl-atlas-merge-stale-vertices
Open

Fix webgl rendering corruption from atlas page merges#5883
vsych wants to merge 1 commit into
xtermjs:masterfrom
vsych:fix-webgl-atlas-merge-stale-vertices

Conversation

@vsych
Copy link
Copy Markdown

@vsych vsych commented May 17, 2026

Two bugs in the webgl renderer cause the corruption from #5847.

1. Texture binding stale after a same-index page swap. GlyphRenderer.render only rebinds a texture unit on version mismatch, but after _createNewPage merges 4 pages into 1, the page object at the merged index swaps to a fresh mergedPage. With per-page counters the new page's version often coincided with the version we last bound at that index (the freshly-pushed mergedPage always landed on 1). The rebind was skipped and the texture kept the old page's canvas, so glyphs sampling that unit got garbage.

Make AtlasPage.version monotonic across all pages and the existing comparison detects a same-index swap, no extra bookkeeping required.

2. Stale texturePage in the vertex buffer after a mid-update merge. When a merge fires inside _updateModel, cached glyphs get a new texturePage. Cells already written in this pass (or carried over via the model-unchanged early-exit) still point at the pre-merge index. _requestClearModel was set on merge but beginFrame never reset it, so it stayed true forever; and renderRows only checked it before _updateModel, so a mid-pass merge went unhandled.

  • reset _requestClearModel when beginFrame reads it
  • after _updateModel, re-run a full update if a merge fired during the pass (capped at MERGE_RETRY_LIMIT)

Before:
issue
After:
image

Stress repro with the script from #5847 (full-screen rewrite, many colors, frequent merges):

Before this PR:

repro-master-no-fix

After this PR:

repro-with-fix

@vsych vsych force-pushed the fix-webgl-atlas-merge-stale-vertices branch from e7ccd52 to 15a2469 Compare May 17, 2026 08:45
@vsych vsych changed the title Fix webgl rendering corruption after mid-frame atlas page merge Fix webgl atlas merge leaving stale texture pages in vertex buffer May 17, 2026
@vsych vsych force-pushed the fix-webgl-atlas-merge-stale-vertices branch from 15a2469 to ae77634 Compare May 18, 2026 22:58
@vsych vsych changed the title Fix webgl atlas merge leaving stale texture pages in vertex buffer Fix webgl rendering corruption from atlas page merges May 18, 2026
@vsych vsych force-pushed the fix-webgl-atlas-merge-stale-vertices branch 2 times, most recently from 26430b0 to d9accc4 Compare May 18, 2026 23:24
@vsych vsych force-pushed the fix-webgl-atlas-merge-stale-vertices branch from d9accc4 to dc726a2 Compare May 19, 2026 00:30
hesnotsoharry added a commit to hesnotsoharry/Ouroboros that referenced this pull request May 19, 2026
Per ADR Decision 3 (RESOLVED 2026-05-18): keep WebGL, vendor upstream
fix via self-contained Node postinstall patcher (no new deps - avoids
the per-repo lockfile-sync dance).

Root cause (Phase C sonnet-diagnostician, high confidence):
- Upstream: xtermjs/xterm.js#5847 (OPEN 2026-04-27) - WebGL atlas
  page-merge corruption with allowTransparency:true + heavy streaming.
- Fix: xtermjs/xterm.js#5883 (OPEN 2026-05-17, NOT MERGED).
- Symptom in Ouroboros: ghost cursors in random positions during
  Claude TUI thinking; ghost in front of typing cursor.
- Match: same lib version (@xterm/addon-webgl 0.19.0), same config flag,
  same workload as the upstream repro.

Vendor patch shape:
- patches/addon-webgl-0.19.0.original.{mjs,js} - shipped 0.19.0 snapshot.
- patches/addon-webgl-0.19.0.patched.{mjs,js} - PR #5883 changes applied:
  (1) AtlasPage.version becomes monotonic global (5 increment sites);
  (2) TextureAtlas.beginFrame() resets _requestClearModel before return;
  (3) WebglRenderer.renderRows() adds bounded retry loop (max 3) after
  _updateModel to handle mid-update merges.
- tools/apply-patches.mjs - SHA-256 based; copies patched bundle if
  installed matches original. Idempotent. Soft-warns and exits 0 if
  bundle SHA matches neither (upstream update detected - patch needs
  re-mapping or removal). Wired into package.json postinstall chain.
- patches/README.md - removal flow (bump dep + delete patch files when
  upstream ships >= 0.19.1).

Companion changes:
- eslint.config.mjs - tools/**/*.mjs added to Node-globals block.
- Terminal/CLAUDE.md - gotcha entry pointing to the patch + removal.
- useTerminalSetup.lifecycle.ts - structural baseline trace from
  diagnostician (3 [xterm-init] log lines, kept per debug-before-fix.md).

Live verification by Cole REQUIRED before marking phase resolved -
ghost cursor disappearance can only be confirmed by running the IDE
against a real Claude TUI streaming workload.

Co-Authored-By: Claude Opus 4.7 (1M context) <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