From 0fd17feb1707dd91d417ce98a62ca6dbc9355ed0 Mon Sep 17 00:00:00 2001 From: Agus Zubiaga Date: Sun, 26 Jul 2026 02:33:42 -0300 Subject: [PATCH] gpui: Fix dropped frames when scrolling an unfocused window (#61600) Scroll events are delivered to the window under the pointer even when it isn't focused, but we cap inactive windows at ~30fps to save energy, so scrolling an unfocused window visibly drops frames. We now exempt high-rate input from the cap using the existing `InputRateTracker`: a trackpad scroll stream lifts it while events are arriving and for one second after the last burst, then the cap resumes on its own. ## Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content adheres to Zed's UI standards ([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) and [icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md) guidelines) - [ ] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable --- Release Notes: - Fixed dropped frames when scrolling unfocused windows. --- crates/gpui/src/window.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 9dd236260e..292126e6d8 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -1589,13 +1589,16 @@ impl Window { // Throttle frame rate based on conditions: // - Thermal pressure (Serious/Critical): cap to ~60fps - // - Inactive window (not focused): cap to ~30fps to save energy + // - Inactive window (not focused): cap to ~30fps to save energy, + // unless input is arriving at a high rate (e.g. scrolling an + // unfocused window, which is delivered to the window under + // the pointer). let min_frame_interval = if !force_render && !request_frame_options.require_presentation && next_frame_callbacks.borrow().is_empty() { None - } else if !active.get() { + } else if !active.get() && !input_rate_tracker.borrow_mut().is_high_rate() { Some(Duration::from_micros(33333)) } else if let Some(ThermalState::Critical | ThermalState::Serious) = thermal_state { Some(Duration::from_micros(16667)) @@ -1638,7 +1641,7 @@ impl Window { // to prevent display underclocking during active input. let needs_present = request_frame_options.require_presentation || needs_present.get() - || (active.get() && input_rate_tracker.borrow_mut().is_high_rate()); + || input_rate_tracker.borrow_mut().is_high_rate(); if invalidator.is_dirty() || force_render { measure("frame duration", || {