Skip to content

perf(desktop-gpui): repaint only the timeline and transport on hover - #2542

Merged
richiemcilroy merged 6 commits into
CapSoftware:mainfrom
notkainoa:perf/editor-section-invalidation
Oct 20, 2026
Merged

richiemcilroy merged 6 commits into
CapSoftware:mainfrom
notkainoa:perf/editor-section-invalidation

Conversation

@notkainoa

@notkainoa notkainoa commented Oct 14, 2026 •

Copy link
Copy Markdown

Hovering the paused timeline no longer rebuilds the whole editor on every mouse move. With the captions tab open, each move now costs about 2.5 ms instead of 11 ms, so the ghost playhead and hover preview keep up with a 120 Hz mouse.

Captions tab open, 120 Hz mouse, slow sweep:

Before After
Main-thread work per mouse move (p50) 10.9 ms 2.5–2.6 ms
Mouse moves handled (of 720) 360 676–678
Hover preview frames shown 360–361 670–679

The problem

Every editor section (header, toolbar, transport, sidebar, timeline) observes the editor. The hover handlers called cx.notify() on the editor, so every pointer move re-rendered the header, toolbar and sidebar too, even though only the timeline ghost playhead, the transport time and the canvas overlay read hover state.

What changed

  • timeline_hover, timeline_hover_leave and track_hover now notify only the timeline and transport views (invalidate_hover_chrome).
  • GPUI marks their ancestors dirty, so the editor window and its canvas overlay still re-render. Header, toolbar and sidebar are reused from their cache.
  • Temporary render counters confirmed header/toolbar/sidebar renders per sweep went from 692 to 0, with the timeline, transport and overlay unchanged.

All results

Sweep Sidebar tab Mouse Per move (p50), before → after Moves handled, before → after Preview frames, before → after
Slow (30 s) captions 120 Hz 10.9 → 2.5–2.6 ms 360 → 676–678 of 720 360–361 → 670–679
Slow (30 s) background 120 Hz 4.8–4.9 → 2.4–2.5 ms 720 → 713–720 719–721 → 711–715
Slow (30 s) captions 60 Hz 10.9–11.0 → 3.4–3.5 ms 360 → 360 360 → 359–360
Fast (3 s) captions 120 Hz 11.0 → 1.6 ms 360 → 720 of 720 0 → 0

Measured on current main (3403665), which includes #2557. On the older base this PR was first measured on, the slow sweeps came out lower after the fix (1.4–2.9 ms). The fast sweep, which shows no preview frames, is the same on both.

Known limits (out of scope)

  • Fast sweeps show 0 preview frames before and after. That's the cancel-on-newer-request starvation fixed in perf(editor): keep the preview updating while scrubbing #2535. With both changes, frames come back.
  • p95 stays around 11–12 ms on the slow captions sweep. Those are moves where the paused frame layout changes (for example entering a zoom segment), which still notifies the whole editor via frame_layout_requires_editor_invalidation.

Scope

  • Playing state is unchanged: timeline_hover and timeline_hover_leave keep their playing guards, track_hover keeps its drag guard, and the playhead still goes through invalidate_playback_chrome.
  • Row enter/leave (set_hovered_track) still notifies the whole editor. It fires once per row, not per move.

Checks

  • cargo test --release -p cap-desktop-gpui --bin cap-gpui editor_: 403 passed on current main.
  • cargo fmt is clean. cargo clippy -- -D warnings reports no warnings in the touched files (the existing failures are in the kernels crate).
Benchmark setup (CAP_GPUI_AUTO_HOVER_BENCH=1, new, apps/desktop-gpui/src/editor_hover_bench.rs)

The bench activates the window, sweeps the pointer across the paused timeline at a fixed speed, and for each move times the real hover handler plus one synchronous Window::draw. 1 s warmup, 6 s measured. Moves that arrive while the previous one is still running are coalesced, as the OS does, and the loop yields between moves so the preview pump can run. Apple M1, release build, reference-recording.cap from Cap's performance fixtures, 2–3 runs per row with before and after interleaved. "Before" is this branch with the three hover call sites put back to main's full invalidation. The hook does nothing unless the env var is set.

cd apps/desktop-gpui
CAP_GPUI_AUTO_EDITOR=<copy of reference-recording.cap> CAP_GPUI_AUTO_HOVER_BENCH=1 \
  CAP_GPUI_AUTO_SIDEBAR=captions CAP_GPUI_AUTO_HOVER_BENCH_HZ=120 \
  CAP_GPUI_AUTO_HOVER_BENCH_SWEEP_SECS=30 CAP_GPUI_NO_MENUS=1 CAP_GPUI_NO_TRAY=1 \
  cargo run --release

RetriggerView in GreptileConfidence Score: 5/5

The PR appears safe to merge; no actionable issues were found.

Summary

The PR narrows timeline hover redraws and adds an opt-in benchmark.

  • Paused timeline hover refreshes the timeline and transport without rebuilding other editor sections.
  • Developers can measure timeline hover redraw time with an opt-in benchmark.

Reviews (2) · Last reviewed commit: "perf(desktop-gpui): note that hover no l..." · Reviewed by Greptile

@vercel

vercel Bot commented Oct 14, 2026

Copy link
Copy Markdown
Contributor

Someone is attempting to deploy a commit to the Cap Software Inc Team on Vercel.

A member of the Team first needs to authorize it.

@notkainoa
notkainoa marked this pull request as ready for review October 14, 2026 16:57
@notkainoa
notkainoa force-pushed the perf/editor-section-invalidation branch from 43e1036 to 46fe21b Compare October 14, 2026 19:34
@richiemcilroy

Copy link
Copy Markdown
Member

@greptileai review

@richiemcilroy

Copy link
Copy Markdown
Member

Verified hands-on in the real editor before merging (M4 Max, release builds of this branch merged with current main, which now includes #2535, vs that same main). A local harness drove the real timeline_hover handler at 120 Hz across the paused timeline for 20 s, with local-only render counters on the root and each section view. CPU was sampled over 12 s of the sweep. Interleaved, 3 runs each, on cam60 (4K screen + camera).

main this PR
CPU while hovering, captions tab 130–135% 87–96%
CPU while hovering, default tab 94–97% 85–90%
Header / toolbar / sidebar renders per sweep (~2400 moves) ~2400 each 1111–1451 (only on paused frames whose layout changed)
Root, transport, timeline renders every move every move (unchanged)
Hover preview frames shown 1450–1457 1384–1457
Playback CPU (6 runs each, path untouched) 54.6–57.8% 53.5–57.7%
  • Everything that reads hover state still updates on every move. preview_time follows the pointer and clears on leave in every run.
  • Static check: preview_time, hovered_segment, scene_preview_time and split_preview are only read by the timeline, the transport, and the canvas overlay / speed popover. The parent renders the overlay and popover, and the parent still re-renders, so nothing in the cached header, toolbar or sidebar can go stale.
  • Before perf(editor): keep the preview updating while scrubbing #2535, a fast hover sweep on this branch sometimes starved the preview (one run showed 2 frames), as the PR's "Known limits" section says. With perf(editor): keep the preview updating while scrubbing #2535 now on main, frames are steady.
  • CI is green on the branch merged with current main.

@richiemcilroy
richiemcilroy merged commit 23bd622 into CapSoftware:main Oct 20, 2026
37 of 39 checks passed
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.

2 participants