From 4cfeee71ccf6abb962b49a10844eb2f9bf9b9f13 Mon Sep 17 00:00:00 2001 From: ddidderr Date: Mon, 12 Oct 2026 23:25:38 +0200 Subject: [PATCH] docs: add TODO for smooth resize with large scrollback With a large scrollback-limit (e.g. 1 GiB), resizing the window or adding a split freezes the UI for a moment. Investigation showed the column reflow rewrites the entire scrollback synchronously while holding the terminal lock, which blocks the renderer and also the GTK main thread (focus, mouse and key paths lock the terminal). Window drags trigger a full reflow every 25 ms coalescing step, and the reflow inner loop scans all tracked pins per cell. The design is inherited from upstream Ghostty. TODO.md records the analysis and the options: a resize benchmark, reflow fast paths, removing main-thread terminal locking on hot paths, deferring history reflow until a drag settles, an incremental chunked reflow, and a config escape hatch, with a recommended order. No behaviour change. --- TODO.md | 98 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 TODO.md diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..061e0a3 --- /dev/null +++ b/TODO.md @@ -0,0 +1,98 @@ +# TODO + +## Smooth resize with very large scrollback + +**Symptom:** with a large `scrollback-limit` (e.g. 1 GiB) and a pane holding a +lot of history, resizing the window or adding a split freezes the UI for a +moment. + +### Why it freezes + +1. **Every resize rewrites the whole scrollback.** Any column change (window + drag, new split) runs `PageList::resize_cols` → `reflow_pages` + (`crates/ghostty-terminal/src/page_list.rs`), which rewrites every page cell + by cell into new pages. With ~1 GiB of history that is 100M+ cells and + roughly 2 GiB of memory traffic: a single synchronous O(scrollback) job. +2. **It holds the terminal lock the whole time.** `Termio::resize` + (`crates/ghostty-termio/src/termio.rs`) runs the reflow inside + `lock_terminal()`, so: + - the renderer thread can't build a frame and that pane stops updating; + - the GTK main thread blocks whenever it touches that terminal. Adding a + split changes focus, and `focus_callback` + (`crates/ghostty-core/src/surface/mod.rs`) does + `self.lock_terminal().flags.focused = …`. Mouse motion, the mouse cursor + shape and key handling also take the lock. So the whole window freezes, + not just the pane. +3. **A drag means many full reflows in a row.** Resizes are only coalesced + for 25 ms (`COALESCE_MIN_MS`, `crates/ghostty-termio/src/thread.rs`). Each + coalesced step reflows everything, so during a drag the IO thread holds the + lock almost all the time. +4. **The inner loop does extra work.** `reflow_row` and `remap_cell` scan + every tracked pin for every cell, which is O(cells × pins). Every cell also + goes through `write_cell`, even on plain short lines that would fit + unchanged. + +Upstream Zig Ghostty has the same design (including the per-cell pin scan in +`reflowRow`), so this was inherited faithfully, not introduced by the rewrite. + +**Key point:** making the reflow faster helps but can't get to "smooth at all +times". Even at memory bandwidth, rewriting ~1 GiB takes 100 ms or more, far +beyond a 16 ms frame. To truly never stall, the full reflow must not happen +under the lock in one go. + +### Options, from cheapest to most thorough + +- [ ] **0. Resize benchmark** in `ghostty-bench` (fill N GiB, resize the + columns) to get real numbers before and after each change. + +- [ ] **A. Speed up the reflow** (low risk, contained in `page_list.rs`) + - [ ] Skip the pin scan for pages with no tracked pins (nearly all + scrollback), or pre-sort pins per page. This removes the O(cells × pins) + factor. + - [ ] Fast path for rows that fit at the new width and aren't wrapped: copy + the whole row at once (cells plus style/grapheme/hyperlink refs) instead + of cell by cell. + - [ ] Page-level fast path: when shrinking, a page with no wrapped rows + whose content already fits keeps its storage and just gets a smaller + `size.cols`, with no copy. + - Likely several times faster, but only the benchmark will tell. Still + won't be frame-time at 1 GiB. + +- [ ] **B. Stop the GTK main thread from blocking on the terminal lock** + (moderate, independent of A) + - [ ] Hot main-thread paths shouldn't need the lock: the focus flag via an + IO message or atomic; the mouse-reporting mode and mouse shape cached in + atomics. + - Result: the window, other panes, split creation and dragging stay smooth. + Only the pane being reflowed briefly shows its last frame. + - Probably the biggest perceived improvement for the effort. + +- [ ] **C. Defer the history reflow until the drag settles** (moderate) + - During a drag, reflow only the active screen. Reflow history once the + size has been stable for about 100–200 ms. + - Avoids repeated full reflows mid-drag; one stall at the end remains unless + combined with D. + +- [ ] **D. Incremental, chunked reflow** (the real fix, biggest change) + - Reflow from the newest content toward the oldest, a few milliseconds of + work per lock hold, releasing the lock in between so the renderer and pty + keep running. + - Requires the page list to allow pages of different widths temporarily, so + the renderer, selection, search and scrollbar must cope with "not yet + reflowed" rows. If another resize arrives mid-way, the job just switches + to the new target width. + - Variant: only reflow the last N rows eagerly and do older history in the + background, or when it's scrolled into view. + +- [ ] **E. Config escape hatch** (trivial) + - A setting such as "reflow only the last N MB/rows of scrollback" and leave + older history unreflowed. Useful as a stopgap but changes behaviour. + +### Recommended order + +1. **B + A first.** They're mostly contained, low risk, and remove the + whole-window freeze plus most of the per-pane stall. +2. **Then C** for live drags. +3. **D only if the remaining single-pane hitch still matters.** It's the only + option that fully guarantees smoothness, but it's a real design change and + diverges from upstream.