Skip to content

fix(terminal): prevent transparent WebGL flicker (#6491)#6597

Open
nwparker wants to merge 2 commits into
mainfrom
nwparker/fix-6491
Open

fix(terminal): prevent transparent WebGL flicker (#6491)#6597
nwparker wants to merge 2 commits into
mainfrom
nwparker/fix-6491

Conversation

@nwparker

@nwparker nwparker commented Jun 28, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #6491 — terminal backgrounds with opacity < 1 no longer flicker/step brightness on redraw.

Root cause: @xterm/addon-webgl draws Orca's rgba terminal background into an alpha WebGL canvas. Without clearing that canvas before redraw, the semi-transparent background is blended over the previous frame again, so repeated output updates visibly accumulate alpha.

Current fix:

  • Transparent panes keep WebGL/GPU rendering when the current xterm WebGL renderer internals are available.
  • Orca installs a guarded clear around xterm's WebGL renderRows path so the alpha canvas is cleared to transparent before xterm draws the latest model.
  • If that transparent-clear hook cannot be installed, Orca falls back to the DOM renderer for transparent panes rather than reintroducing flicker.
  • Full-opacity panes remain on the existing WebGL path, and opacity changes still apply live without remounting the pane.

Tradeoffs

The reported user-facing tradeoff is eliminated for Orca's current supported @xterm/addon-webgl version: transparent terminals no longer have to give up WebGL acceleration.

Honest implementation caveat: xterm exposes no public API for this transparent-frame clear, so the WebGL-preserving path uses a guarded private renderer shape. If a future xterm update changes that shape, the guard intentionally falls back to DOM for transparent panes to preserve correctness.

Testing

  • ./node_modules/.bin/vitest run --config config/vitest.config.ts src/renderer/src/lib/pane-manager/pane-webgl-transparent-clear.test.ts src/renderer/src/lib/pane-manager/pane-webgl-renderer.test.ts src/renderer/src/lib/pane-manager/pane-rendering-control.test.ts src/renderer/src/lib/pane-manager/pane-lifecycle.test.ts src/renderer/src/components/terminal-pane/terminal-cursor-inactive-style.test.ts — 38 passing
  • ./node_modules/.bin/vitest run --config config/vitest.config.ts src/renderer/src/lib/pane-manager — 268 passing
  • pnpm typecheck:web — clean
  • ./node_modules/.bin/oxlint <changed terminal/pane-manager files> --quiet — clean
  • git merge-tree origin/main HEAD — clean merge tree, no conflicts

Notes

The earlier PR evidence comment described the first implementation, where transparent panes switched to the DOM renderer. The latest commit changes that behavior: transparent panes now keep WebGL when the guarded clear hook installs, with DOM only as the compatibility fallback.

Made with Orca

xterm's WebGL renderer re-blends semi-transparent per-cell backgrounds
over the retained framebuffer on partial redraws, so when terminal
background opacity < 1 the background flickers/accumulates alpha on
every output update. The DOM renderer composites the full background
each frame and does not exhibit this.

Force the DOM renderer whenever background transparency is active and
restore WebGL automatically when opacity returns to 1. The gate lives in
shouldUseTerminalWebgl so it covers every existing WebGL attach path, and
applyTerminalAppearance flips the renderer live as the opacity slider
crosses 1 (no remount). An explicit GPU "on" setting yields to
transparency since the flicker is a correctness bug, not a perf choice.

Fixes #6491

Co-authored-by: Orca <help@stably.ai>
@coderabbitai

coderabbitai Bot commented Jun 28, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

@nwparker, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 24 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 391860d9-d72a-4297-b92e-761f40eb0f3f

📥 Commits

Reviewing files that changed from the base of the PR and between e51b2df and 96372d3.

📒 Files selected for processing (23)
  • src/renderer/src/components/terminal-pane/terminal-appearance.ts
  • src/renderer/src/components/terminal-pane/terminal-cursor-inactive-style.test.ts
  • src/renderer/src/lib/pane-manager/pane-container-listener-lifecycle.test.ts
  • src/renderer/src/lib/pane-manager/pane-dom-creation.ts
  • src/renderer/src/lib/pane-manager/pane-drag-reorder.test.ts
  • src/renderer/src/lib/pane-manager/pane-drop-no-op.test.ts
  • src/renderer/src/lib/pane-manager/pane-fit-resize-observer.test.ts
  • src/renderer/src/lib/pane-manager/pane-initial-fit-lifecycle.test.ts
  • src/renderer/src/lib/pane-manager/pane-lifecycle.test.ts
  • src/renderer/src/lib/pane-manager/pane-manager-types.ts
  • src/renderer/src/lib/pane-manager/pane-manager.ts
  • src/renderer/src/lib/pane-manager/pane-rendering-control.test.ts
  • src/renderer/src/lib/pane-manager/pane-rendering-control.ts
  • src/renderer/src/lib/pane-manager/pane-split-close.test.ts
  • src/renderer/src/lib/pane-manager/pane-split-scroll.test.ts
  • src/renderer/src/lib/pane-manager/pane-terminal-gpu-acceleration.test.ts
  • src/renderer/src/lib/pane-manager/pane-tree-ops.test.ts
  • src/renderer/src/lib/pane-manager/pane-tree-reparent-frame.test.ts
  • src/renderer/src/lib/pane-manager/pane-webgl-refresh-lifecycle.test.ts
  • src/renderer/src/lib/pane-manager/pane-webgl-renderer.test.ts
  • src/renderer/src/lib/pane-manager/pane-webgl-renderer.ts
  • src/renderer/src/lib/pane-manager/pane-webgl-transparent-clear.test.ts
  • src/renderer/src/lib/pane-manager/pane-webgl-transparent-clear.ts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@nwparker

Copy link
Copy Markdown
Contributor Author

Verification evidence (#6491)

Unit tests (vitest)

$ ./node_modules/.bin/vitest run --config config/vitest.config.ts \
    src/renderer/src/lib/pane-manager/pane-webgl-renderer.test.ts \
    src/renderer/src/lib/pane-manager/pane-rendering-control.test.ts

✓ shouldUseTerminalWebgl > forces DOM rendering under transparency even when GPU acceleration is forced on
✓ shouldUseTerminalWebgl > forces DOM rendering under transparency on the auto policy
✓ shouldUseTerminalWebgl > allows WebGL when transparency is off and GPU is forced on
✓ shouldUseTerminalWebgl > allows WebGL when transparency is off and auto policy permits it
✓ setPaneTerminalTransparency > tears down WebGL and switches to DOM when transparency is enabled
✓ setPaneTerminalTransparency > re-attaches WebGL when transparency is disabled and GPU is on
✓ setPaneTerminalTransparency > is a no-op when the transparency flag already matches

 Test Files  2 passed (2)
      Tests  7 passed (7)

Full src/renderer/src/lib/pane-manager/ suite: 261 passed. Lint (oxlint, changed files) clean; typecheck (tsgo -p config/tsconfig.tc.web.json) clean.

Live app validation (Electron over CDP, dev build of this branch)

Attached to a dev build of nwparker/fix-6491 (verified via window.api.app.getIdentity()devRepoRoot: /private/tmp/orca-bb/6491), opened a terminal pane, and inspected the live xterm renderer DOM directly (WebGL addon renders into unclassed <canvas> layers; DOM renderer produces .xterm-rows).

Setting Renderer DOM observed Renderer
terminalBackgroundOpacity = 1 (baseline) 2 render <canvas> layers, hasRowsDiv: false, rowDivs: 0 WebGL (no regression)
terminalBackgroundOpacity = 0.85 (transparency on) render canvases gone, hasRowsDiv: true, rowDivs: 61 DOM (flicker fixed)
0.85 → 1 (live toggle) render canvases back, hasRowsDiv: false, rowDivs: 0 WebGL re-attached, no remount

Raw eval output at opacity 0.85:

{"opacity":0.85,"canvasCount":1,"canvasInfo":["xterm-decoration-overview-ruler"],"hasRowsDiv":true,"rowDivs":61}

(the remaining xterm-decoration-overview-ruler canvas is the slim scrollbar, unrelated to the renderer.)

Before/after reasoning: Before this fix, shouldUseTerminalWebgl had no transparency gate, so WebGL stayed active at opacity < 1 and re-blended the changed rows' semi-transparent backgrounds over the persisted framebuffer each partial redraw, accumulating alpha (the flicker). After the fix, transparency forces the DOM renderer (confirmed by .xterm-rows replacing the WebGL canvas above), which composites the full background each frame — no accumulation, no flicker — and WebGL is restored the moment opacity returns to 1.

Screenshot of the terminal rendering cleanly at opacity 0.85 (DOM renderer) saved locally under the gitignored validation-screenshots/. The renderer-DOM state table above is the load-bearing evidence; the bug is a per-frame compositing artifact that a static screenshot cannot capture, so the canvas-vs-rows DOM inspection is the authoritative proof that the renderer switched.

@nwparker nwparker changed the title fix(terminal): disable WebGL renderer under background transparency (#6491) fix(terminal): prevent transparent WebGL flicker (#6491) Jun 30, 2026
@nwparker

Copy link
Copy Markdown
Contributor Author

Follow-up pushed in 96372d3.

What changed:

  • Transparent terminals now keep WebGL acceleration when the current @xterm/addon-webgl renderer shape is present.
  • Orca clears the WebGL alpha canvas before xterm's renderRows, preventing rgba background accumulation/flicker.
  • DOM rendering remains only as a guarded compatibility fallback if the transparent WebGL clear hook cannot be installed.
  • Fixed the previous failing terminal-cursor-inactive-style.test.ts manager mock.

Tradeoff status: the reported user-facing tradeoff is eliminated for the current supported xterm WebGL addon. The remaining honest caveat is implementation-level: xterm has no public transparent-frame clear API, so the WebGL-preserving path is guarded and falls back to DOM if a future xterm version changes the private renderer shape.

Validation:

  • Focused renderer/CI-failure suites: 5 files, 38 tests passing
  • Full src/renderer/src/lib/pane-manager suite: 32 files, 268 tests passing
  • pnpm typecheck:web: clean
  • Targeted oxlint on changed files: clean
  • git merge-tree origin/main HEAD: clean

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.

[Bug]: Terminal background flickers on every redraw when background transparency is enabled

1 participant