Skip to content

Add a low-level fixed-cell terminal surface #42

Description

@monotykamary

Summary

Propose a low-level fixed-cell <terminal> host for applications that already own a PTY and VT parser. GPUIX would own only the final grid painter and transport boundary; shell lifecycle, escape parsing, scrollback, selection policy, and accessibility remain application concerns.

The goal is one retained host node rather than thousands of React text/run nodes per animated frame.

Motivation

Mostly bringing over all of the performance tricks I've done on localterm to the GPUIX world.

trimmed.mp4

Proposed API boundary

Portable path

A versioned frame prop carries metadata plus base64-encoded row-major cells. Each cell is a 16-byte little-endian record containing a glyph reference, foreground RGB, background RGB, flags, and reserved bytes. A side table stores multi-codepoint graphemes.

This path works through the existing React mutation transport and can be implemented by browser/WASM renderers.

Native direct path

An optional renderer capability avoids base64 and mutation JSON:

renderer.supportsNativeTerminal?.()
renderer.setTerminalFrame?.(elementId, JSON.stringify(metadata), cells)

The N-API boundary copies into a latest-frame mailbox. Multiple arrivals before paint coalesce, and React is not committed for animated output.

Painter

  • one retained terminal node;
  • nearest-sampled two-pixels-per-cell texture for backgrounds and block/quadrant/shade glyphs;
  • ordinary text remains GPUI-shaped and uses the monochrome glyph atlas;
  • shaping is cached independently of ANSI color;
  • wide cells, bold/italic/underline/strike, cursor, ligature control, and Nerd Font routing are preserved;
  • same-sized animated frames retain image identity and update the atlas tile in place;
  • when text/cursor geometry is unchanged on macOS, the existing scene can be presented without rebuilding React, layout, prepaint, or paint.

The image-update foundation is proposed in #40. Keeping Bun/PTY delivery healthy in a real window depends on the nonblocking embedded pump proposed in #39. GPUix 0.7's resolution of #36 already lets Tab and Shift+Tab reach the focused terminal normally; no terminal-specific focus prop is needed.

Measurements

On Apple Silicon with Bun 1.4.0 and a deterministic DEC 2026 truecolor producer:

  • fullscreen 1470×923 logical window, 185×53 grid: 58.7 PTY callbacks, service frames, and native stages per second, all 1:1;
  • direct frame projection p90: 0.15 ms;
  • N-API staging p90: 0.31 ms;
  • GPUI draw p90: 0.13 ms;
  • 100% stable-layer reuse after the initial scene;
  • zero React commits during animation.

The offscreen high-resolution probe measured approximately 5.31 ms median at 480×120 and 8.93 ms at 640×180. Timings are hardware-specific; the structural assertions are one node, latest-frame coalescing, no animated React commits, and one native stage per published frame.

Reference implementation

No PR is being opened, per the external-contributor policy.

The focused branch contains README documentation, a changeset, Rust unit coverage, and three React/native regressions for one-node painting, direct latest-frame coalescing, and updates without per-cell retained nodes.

Validation

  • Focused branch native suite: 225 passed
  • Focused branch React package build: passed
  • Full integration native suite: 227 passed
  • Full integration React suite: 377 passed
  • Downstream application suite: 319 passed
  • Real fullscreen telemetry described above

Would a specialized fixed-cell host fit GPUIX, or should this instead be expressed through the web-compatible canvas API discussed in #8? If the host is acceptable, is the versioned frame plus optional direct binary staging boundary appropriate?

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions