perf(app): eliminate tab-switch stall with background refresh and instant reveal#24
Merged
Conversation
…redictor Groundwork for reusing cached pane instances across occlusion cycles: - cached_frame_matches_viewport: a cached frame is reusable only when the live viewport, the viewport the layout was built against, the current pane set (PaneId + rect), and the shared glyph atlas identity/eviction generation all still match, and the frame is not atlas-unstable. - invalidate_pane: clears a pane's row-cache key so its next rebuild is unconditionally full (reads every row from the live snapshot, independent of consumed row-dirty bits). - pane_rebuild_would_be_full: read-only predictor replicating rebuild_pane_cached's exact full-rebuild decision; the shared frame_invalidation_key_matches extraction keeps the two from drifting.
Measures the cost a busy tab pays on its first frame after a long occlusion (full viewport rebuild, cold vs warm glyph atlas) on a real GPU adapter; skips when none is available. Baseline on Apple Silicon at 200x60: ~93ms cold / ~38ms warm vs a ~16ms frame budget — the numbers motivating the background-refresh + instant-reveal design.
…tant reveal Switching to a tab with heavy output froze for a frame: while occluded, redraw() early-returns, so the pane render cache goes fully stale and the first frame after reveal rebuilds the whole viewport synchronously (93ms cold-atlas / 38ms warm-atlas at 200x60, per the baseline bench). - Throttled background cache refresh: dirty occluded windows refresh the pane cache and glyph atlas at most once per 250ms globally (one window per interval, least-recently-refreshed first), driven by pty output events plus a one-shot trailing wake-up per throttle window (fully idle again once the dirty backlog drains), without touching the 1x1 occluded swapchain. A refresh that could not reuse the row cache incrementally (e.g. the tab scrolled past a whole viewport) is skipped to keep the event-loop cost bounded; its panes are invalidated so the consumed row damage is deferred to the next full rebuild, never lost. - Instant reveal: the first redraw after Occluded(false) presents the cached instances (when the renderer-side guards allow reuse) and defers the now mostly-incremental rebuild to an immediately scheduled follow-up frame. The fast-path frame stashes its captured snapshots so the follow-up rebuilds against the exact damage it consumed, then schedules one more redraw to pick up output that landed in between. Re-occluding before the follow-up frame invalidates the stashed panes' row caches so the consumed damage is never lost. - NOA_TAB_SWITCH_TRACE=1 env-gated tracer logging the reveal-to-present breakdown and background refreshes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Switching to a tab with heavy output visibly froze for a frame: occluded windows stop redrawing, so the first frame after reveal paid a synchronous full-viewport rebuild (measured 93ms cold-atlas / 38ms warm at 200x60 vs a ~16ms frame budget — see the new headless bench).
Occluded(false)presents the cached instances and defers the (now mostly incremental) rebuild to an immediately scheduled follow-up frame, with strict reuse guards (live viewport, build-time viewport, pane set, atlas identity/eviction generation, atlas stability) and damage carry-forward on re-occlusion.Reviewable commit-by-commit: renderer guards → baseline bench → app wiring.
Test plan
cargo test --workspace(959 noa-app + 124 noa-render lib + GPU suites, executed on a real adapter),cargo clippyno new warnings,cargo fmt --checkcleanNOA_TAB_SWITCH_TRACE=1 cargo run -p noa— reveal lines show(fast-path, rows_rebuilt=0),bg-refreshlines show throttled background updates