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.
This commit is contained in:
Agus Zubiaga authored and GitHub committed 2026-07-26 05:33:42 +00:00
1 parent 5d2df6acd5
commit 0fd17feb17
1 file changed
+6 -3
+6 -3
View File
@@ -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", || {