Skip to content

perf(terminal): use WebGL renderer and batched writes#2680

Merged
charlesvien merged 3 commits into
mainfrom
perf/terminal-webgl-batching
Jun 16, 2026
Merged

perf(terminal): use WebGL renderer and batched writes#2680
charlesvien merged 3 commits into
mainfrom
perf/terminal-webgl-batching

Conversation

@georgemunyoro

Copy link
Copy Markdown
Contributor

Problem

Under heavy output load, the terminal renderer could feel sluggish. We want smoother rendering by leveraging GPU-accelerated WebGL output, while still giving users an escape hatch in case of graphical glitches on their hardware.

Changes

  • WebGL rendering for the terminal

    • Added the @xterm/addon-webgl dependency to packages/ui.
    • TerminalManager now creates and manages a WebglAddon per terminal instance, tracking webglAddon, writeBuffer, and flushHandle on each TerminalInstance.
    • Introduced a setUseWebgl capability and an internal useWebgl flag (defaults to true) so rendering mode can be toggled at runtime.
    • Writes are now batched per frame via a write buffer/flush mechanism to reduce render churn.
  • GPU rendering setting

    • Added a terminalGpuRendering boolean to the settings store (defaults to true), with a setTerminalGpuRendering setter, and persisted it across sessions.
    • Added a "GPU rendering" toggle (Switch) in TerminalSettings, with an analytics event fired on change.
    • Terminal.tsx syncs the setting to terminalManager.setUseWebgl(...).
    • Cleaned up noBorder usage on the existing font setting rows now that the GPU row is the last item.
  • Native module fix (postinstall)

    • Restored the execute bit on node-pty's spawn-helper prebuilt binaries during postinstall, fixing posix_spawnp failed errors when opening a terminal session (pnpm drops the executable mode on extraction).

How did you test this?

Automatic notifications

  • Publish to changelog?
  • Alert Sales and Marketing teams?

Created with PostHog Code

The terminal ran on xterm's default DOM renderer and wrote every pty
chunk synchronously, so heavy output (build logs, large files) pinned
the renderer.

- Load the WebGL addon after term.open(), with an onContextLoss handler
  that disposes it so xterm falls back to the DOM renderer instead of
  rendering nothing, and a try/catch fallback when WebGL is unavailable.
- Coalesce pty output into one term.write() per animation frame; flush
  the buffer before serializing on detach and cancel the pending frame
  on destroy.

Also restore the execute bit on node-pty's spawn-helper in the Electron
postinstall: pnpm extracts the prebuilt binary as 0644, which makes
posix_spawnp fail ("posix_spawnp failed") the first time a terminal
session opens after a fresh install.
Adds a "GPU rendering" toggle to terminal settings (default on). When
disabled, TerminalManager disposes the WebGL addon so xterm falls back to
its DOM renderer; toggling applies live to open terminals. Persisted via
the settings store and synced into TerminalManager like theme and font.

Generated-By: PostHog Code
Task-Id: 177e4a61-ddc5-440a-8f9b-81d043f41335
@github-actions

github-actions Bot commented Jun 15, 2026

Copy link
Copy Markdown

React Doctor found no issues in the changed files. 🎉

Reviewed by React Doctor for commit f18f915.

@greptile-apps

greptile-apps Bot commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Comments Outside Diff (1)

  1. packages/ui/src/features/terminal/TerminalManager.ts, line 522-537 (link)

    P2 Redundant deferred save after detach

    flushWrite (line 524) calls scheduleSave, which arms a 500 ms setTimeout on the parked instance. detach then immediately serializes and emits stateChange (lines 526-531). 500 ms later the timer fires a second stateChange for the same parked terminal. The second emission is harmless (the parked SerializeAddon is still valid), but it's unnecessary work on every detach. Clearing the saveTimeout just before or just after the immediate emit would avoid the redundant scheduled save.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: packages/ui/src/features/terminal/TerminalManager.ts
    Line: 522-537
    
    Comment:
    **Redundant deferred save after `detach`**
    
    `flushWrite` (line 524) calls `scheduleSave`, which arms a 500 ms `setTimeout` on the parked instance. `detach` then immediately serializes and emits `stateChange` (lines 526-531). 500 ms later the timer fires a second `stateChange` for the same parked terminal. The second emission is harmless (the parked `SerializeAddon` is still valid), but it's unnecessary work on every detach. Clearing the `saveTimeout` just before or just after the immediate emit would avoid the redundant scheduled save.
    
    How can I resolve this? If you propose a fix, please make it concise.

    Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 1
packages/ui/src/features/terminal/TerminalManager.ts:522-537
**Redundant deferred save after `detach`**

`flushWrite` (line 524) calls `scheduleSave`, which arms a 500 ms `setTimeout` on the parked instance. `detach` then immediately serializes and emits `stateChange` (lines 526-531). 500 ms later the timer fires a second `stateChange` for the same parked terminal. The second emission is harmless (the parked `SerializeAddon` is still valid), but it's unnecessary work on every detach. Clearing the `saveTimeout` just before or just after the immediate emit would avoid the redundant scheduled save.

Reviews (1): Last reviewed commit: "feat(terminal): add setting to toggle GP..." | Re-trigger Greptile

Fix the writeData comment, which claimed term.write() was synchronous and
reflowed per IPC chunk; xterm buffers writes and RAF-debounces rendering, so
the batching actually trims per-call parse/write-buffer overhead. Also soften
"dramatically slower" and an unverified 0644 mode claim in postinstall.

Generated-By: PostHog Code
Task-Id: 177e4a61-ddc5-440a-8f9b-81d043f41335
@georgemunyoro georgemunyoro marked this pull request as ready for review June 15, 2026 16:46
@greptile-apps

greptile-apps Bot commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Reviews (2): Last reviewed commit: "docs(terminal): correct write-batching a..." | Re-trigger Greptile

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves terminal rendering performance under heavy output by enabling xterm’s WebGL renderer (with a user-facing toggle) and batching terminal writes, and it also patches a node-pty postinstall issue where spawn-helper can lose its executable bit under pnpm.

Changes:

  • Add @xterm/addon-webgl and load a WebglAddon after term.open() to enable GPU-accelerated rendering, with runtime toggling via terminalManager.setUseWebgl.
  • Batch writeData calls per animation frame to reduce xterm parse/write churn during high-volume output.
  • Persist a new terminalGpuRendering setting, expose it in UI settings, and restore node-pty spawn-helper executability in postinstall.

Reviewed changes

Copilot reviewed 6 out of 7 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
pnpm-lock.yaml Locks @xterm/addon-webgl and associated dependency updates.
packages/ui/src/features/terminal/TerminalManager.ts Adds WebGL addon lifecycle management and per-frame batched writes.
packages/ui/src/features/terminal/Terminal.tsx Syncs the persisted GPU-rendering setting into terminalManager.
packages/ui/src/features/settings/settingsStore.ts Adds/persists terminalGpuRendering setting and setter.
packages/ui/src/features/settings/sections/TerminalSettings.tsx Adds “GPU rendering” toggle UI and analytics event.
packages/ui/package.json Adds @xterm/addon-webgl dependency.
apps/code/scripts/postinstall.sh Restores +x on node-pty prebuilt spawn-helper binaries.
Files not reviewed (1)
  • pnpm-lock.yaml: Generated file

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +312 to +316
// Coalesce bursts of pty output into a single term.write() per animation
// frame instead of one call per IPC chunk, cutting the per-call parse and
// write-buffer overhead that piles up on the main thread under a heavy
// stream (build logs, cat-ing a file).
instance.writeBuffer += data;
Comment on lines +317 to +321
if (instance.flushHandle === null) {
instance.flushHandle = requestAnimationFrame(() => {
instance.flushHandle = null;
this.flushWrite(sessionId, instance);
});
Comment on lines 312 to 316
// Terminal
terminalFont: state.terminalFont,
terminalCustomFontFamily: state.terminalCustomFontFamily,
terminalGpuRendering: state.terminalGpuRendering,

@greptile-apps

greptile-apps Bot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Reviews (3): Last reviewed commit: "docs(terminal): correct write-batching a..." | Re-trigger Greptile

@charlesvien charlesvien merged commit 6c091ec into main Jun 16, 2026
28 checks passed
@charlesvien charlesvien deleted the perf/terminal-webgl-batching branch June 16, 2026 07:14
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.

3 participants