Skip to content

Apply winterm-ghostty lessons: output-pump coalescing, gated blink, kitty graphics protocol - #4

Merged
Marc-André Moreau (mamoreau-devolutions) merged 3 commits into
masterfrom
copilot/analyze-winterm-ghostty
Sep 5, 2026
Merged

Apply winterm-ghostty lessons: output-pump coalescing, gated blink, kitty graphics protocol#4
Marc-André Moreau (mamoreau-devolutions) merged 3 commits into
masterfrom
copilot/analyze-winterm-ghostty

Conversation

@mamoreau-devolutions

Copy link
Copy Markdown
Contributor

Summary

Analysis of winterm-ghostty (Windows Terminal shell + ghostty engine) for portable lessons, followed by implementation of the top findings — culminating in kitty graphics protocol support.

Performance & correctness lessons applied (commit 1)

  • Output-pump coalescing (their 4.2 → 30.9 MB/s lesson): TermControl no longer posts one UI dispatch per 16 KiB ConPTY read. Queued engine invalidations collapse into a single trailing drain (interlocked pending flag, re-queues on mid-drain arrivals); accessibility/scroll-mark/viewport/IME listener fan-out runs once per burst. Engine.Feed stays on the PTY thread to preserve CSI 6n response timing. New tools/Devolutions.Terminal.Bench harness (engine/control modes, frame-paced drains, medians): an 8 MiB burst drops from 514 UI drains to ~1 per frame.
  • Damage-gated cursor blink: the 530 ms blink timer only invalidates when pixels can change (focused pane, DECSET 12/25 blinking+visible cursor, effectively visible control); focus loss repaints once out of a mid-blink dark phase.
  • Correctness audits pinned by tests: paste vs WriteInput byte separation, ConPTY per-sequence write serialization under a live two-writer race, event-resolved key symbols (Ctrl+Shift+/ → ?, never the unshifted /), and a libghostty-vt stack-headroom stress test (adversarial corpus on a 256 KB thread — 4× under the host default; their full-libghostty host needed 16 MB).

Kitty graphics protocol (commit 2)

Implements APC G end to end:

  • Parser: APC string states in VtParser (ESC _ / C1 entry, ST-only termination, CAN/SUB abort, bounded), default IVtDispatch.ApcDispatch.
  • Core: KittyGraphicsDecoder — control keys, base64 + zlib, raw RGB/RGBA → RGBA32, format 100 retains codec bytes for the renderer. Engine: image store with independent budget, chunked assembly (m=), actions t/T/p/d/q, OK/error responses honoring quiet flags, cursor advance below image for a=T (C=1 opts out), deletes by all/id/placement, logical-line anchored overlays surviving scrollback/reflow. New KittyImages capability.
  • Renderer: placements size to c/r cells (aspect-preserving with one axis), in-cell pixel offsets, source crop, z-order (z<0 below text, z≥0 above).
  • Ghostty engine: deterministic image.kitty.unsupported diagnostic (deduped per engine lifetime).
  • Intentional gaps (documented): file/shared-memory media rejected without I/O (repo policy), animation frames and Unicode placeholders (ENOTSUP), cell/z/number-targeted deletes.

Validation

  • Full solution builds clean (0 warnings/errors).
  • All 13 test suites green: 880+ tests, including 19 new Core kitty tests, 6 renderer pixel tests (raw/PNG/cell-sizing/z-order/crop), Ghostty diagnostic test, and the perf/correctness pins from commit 1.
  • Bench harness verified: dotnet tools/Devolutions.Terminal.Bench/bin/Release/net10.0/Devolutions.Terminal.Bench.dll control.

Remaining analysis items (not in this PR)

Kitty graphics was priority 4. Still available from the analysis: DirectWrite MapCharacters locale-aware fallback, WGC latency probe + vttest matrix, winghostty UIA bridge.

Applies the winterm-ghostty throughput lessons to the Devolutions Terminal
output path:

- TermControl collapses queued engine invalidations into a single trailing
  UI-thread drain (interlocked pending flag) instead of one dispatcher post
  per 16 KiB ConPTY read; accessibility/scroll-mark/viewport/IME listener
  fan-out now runs once per burst. Engine.Feed stays on the PTY thread for
  CSI 6n response timing.
- Cursor blink timer is damage-gated: ticks only invalidate when pixels can
  change (focused pane, blinking + visible cursor per DECSET 12/25,
  effectively visible control); focus loss repaints once out of a mid-blink
  dark phase.
- New tools/Devolutions.Terminal.Bench harness (engine/control modes, 16 KiB
  chunks, frame-paced dispatcher drains, medians): an 8 MiB burst drops from
  514 UI drains to ~1 per frame.
- Correctness pins from the winterm-ghostty audit: paste vs WriteInput
  separation, ConPTY per-sequence write serialization under a two-writer
  race, event-resolved key symbols for shifted Ctrl keys (modifyOtherKeys
  and kitty associated text), and libghostty-vt stack headroom (survives an
  adversarial corpus on a 256 KB thread, 4x under the host default).
- Docs: renderer.md coalescing/blink contract, ghostty-engine.md stack
  note, parity-status.md measurement entry.
Adds APC G support end to end:

- VtParser: APC string states (ESC _ / C1 0x9F entry, ST-only termination,
  CAN/SUB abort, 1 MiB bound) with a default IVtDispatch.ApcDispatch member.
- Core: KittyGraphicsDecoder parses control keys (a/t/f/o/s/v/i/I/p/c/r/x/y/
  X/Y/w/h/z/q/C/m/d), validates, decodes base64 + zlib, and converts raw
  RGB/RGBA to RGBA32; format 100 retains codec bytes for the renderer (the
  OSC 1337 contract).
- TerminalEngine: image store keyed by image id with independent budget and
  oldest-first eviction, chunked transmission assembly, actions t/T/p/d/q,
  OK/error responses honoring quiet flags, cursor advance below the image
  for a=T (C=1 opts out; a=p never moves), deletes by all/id/placement with
  uppercase forms freeing data, and logical-line anchored overlays that
  survive scrollback/reflow. New KittyImages capability flag.
- Renderer: kitty placements decode via the existing image cache, size to
  c/r cells with aspect preservation, honor in-cell pixel offsets and source
  crops, and composite below text for z<0 or above text for z>=0.
- Ghostty engine: deterministic image.kitty.unsupported diagnostic, deduped
  per engine lifetime for chunked transmissions.
- Rejected without I/O per repository policy: file/temp-file/shared-memory
  media; animation frames and Unicode placeholders are ENOTSUP; cell/z/number
  deletes are silent no-ops.

Tests: 19 Core tests (decoder, engine, parser boundaries incl. split APC,
C1 entry, CAN cancel, BEL non-termination), 6 renderer pixel tests (raw,
PNG, cell sizing, z-order both directions, crop), Ghostty diagnostic test.
Docs: advanced-vt-protocols.md, parity-status.md.
Awaiting Task.Run let the headless dispatcher interleave drains mid-burst on
the macOS and Linux CI runners (55 drains instead of <= 2). Drive the producer
on a raw Thread and block the UI thread on Join so every chunk queues before
any drain runs, on every platform.
@mamoreau-devolutions
Marc-André Moreau (mamoreau-devolutions) merged commit 7b0d7cb into master Sep 5, 2026
24 checks passed
@mamoreau-devolutions
Marc-André Moreau (mamoreau-devolutions) deleted the copilot/analyze-winterm-ghostty branch September 5, 2026 02:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant