renderer: reflow preview tests; a frame whose only change is the grid re-wraps off the terminal lock
Merged
LA
1d ago+562+562+012345678901234567890123456789-32-32-01234567890123456789
Summary
cmux-ad m18 (T-REWRAP): in every frame of a key resize and of a divider drag, each terminal draws its text re-wrapped at that frame's grid, with no change to the terminal's grid or scrollback until the PTY owner's coalesced resize. The render-only re-wrap already exists (ReflowPreview, ghostty_surface_set_reflow_preview). This PR adds its tests, a cost bench and one cost fix. No C API change.
- Tests (
reflow preview:, now in the fork CI filter list):- each frame's grid re-wraps the drawn rows; the live terminal's grid, rows and history do not change;
- output between two frames shows in the next re-wrapped frame;
- the committed grid (the owner's resize, as
set_gridruns it) changes the live grid once, and the live terminal is then drawn with the rows the preview drew; - a frame whose only change is the grid copies nothing under the terminal lock (red at
7b596b8f9).
- Fix (
382219e94): the preview keeps the live terminal's last copy (base, not reflowed). A frame with a new grid and an unchanged live terminal (every frame of a drag) re-wraps a copy ofbasewith no lock held. The live terminal is copied under the lock only when it changed orbaselacks the history the new grid needs (historyNeedcovers at least twice the columns, so one copy serves a gesture). Before, every drag frame held the terminal lock for the copy, 0.43-0.55 ms p50 at 200x60, against the output parser and the main thread's lock users. - Bench:
ghostty-bench +reflow-preview(ReleaseFast; 200x60 with 2000 styled lines, or any size; 5 gestures of 160 frames, a new grid each frame; p50/p95/p99/max per frame, the copy and the reflow apart, and the copies under the lock).
Measurements (fork workflow, Blacksmith macOS, Apple M5 Max virtual)
Before this fix (run 41996574281), per frame:
- 200x60: p50 0.56-0.58 ms, p95 0.68-0.76 ms, p99 0.77-0.93 ms; of it the copy under the lock p50 0.43 ms, max 0.82 ms.
- 300x80, 20000 lines: p50 0.76 ms, p95 0.91 ms, p99 1.02 ms.
After: see the green run below (copies under the lock per gesture, and the per-frame times).
Testing
- Red
7b596b8f9: run 42018811302 (scratch/t-rewrap-cap-red). - Green
382219e94: run 42018819336 (scratch/t-rewrap-cap-green). - Earlier: the three behavior tests passed at
c84ba9477(run 41987588958). - The Debug unit-test binary is not a cost measurement (build.zig builds it Debug: 1.6 s per frame there), so the cost is measured only by the ReleaseFast bench.
Not in this PR
- The renderer QoS change (visible surfaces at user_interactive): the cause A/B did not hold (B was worse), so the resize owner decided it does not land (branch
feat/render-qos-visible, unmerged). - The coalesced PTY resize is app-side (cmux's PTY is in the daemon; a MANUAL_MIRROR surface has no PTY). Ghostty.app's rule (
termio/Thread.zig25 ms, first size starts the timer, the newest size goes when it fires) is the model for the app.