gpui: Throttle framerate to 30 for unfocused windows (#52970)

Lukas Wirth created

This should reduce energy consumption when having agents running in
background windows, as the spinner that is being animated won't refresh
at the display framerate anymore.

Release Notes:

- N/A or Added/Fixed/Improved ...

Change summary

crates/gpui/src/window.rs | 25 +++++++++++++++++++------
1 file changed, 19 insertions(+), 6 deletions(-)

Detailed changes

crates/gpui/src/window.rs 🔗

@@ -1211,18 +1211,31 @@ impl Window {
                     .update(&mut cx, |_, _, cx| cx.thermal_state())
                     .log_err();
 
-                if thermal_state == Some(ThermalState::Serious)
-                    || thermal_state == Some(ThermalState::Critical)
+                // Throttle frame rate based on conditions:
+                // - Thermal pressure (Serious/Critical): cap to ~60fps
+                // - Inactive window (not focused): cap to ~30fps to save energy
+                let min_frame_interval = if !request_frame_options.force_render
+                    && !request_frame_options.require_presentation
+                    && next_frame_callbacks.borrow().is_empty()
                 {
-                    let now = Instant::now();
-                    let last_frame_time = last_frame_time.replace(Some(now));
+                    None
+                } else if !active.get() {
+                    Some(Duration::from_micros(33333))
+                } else if let Some(ThermalState::Critical | ThermalState::Serious) = thermal_state {
+                    Some(Duration::from_micros(16667))
+                } else {
+                    None
+                };
 
-                    if let Some(last_frame) = last_frame_time
-                        && now.duration_since(last_frame) < Duration::from_micros(16667)
+                let now = Instant::now();
+                if let Some(min_interval) = min_frame_interval {
+                    if let Some(last_frame) = last_frame_time.get()
+                        && now.duration_since(last_frame) < min_interval
                     {
                         return;
                     }
                 }
+                last_frame_time.set(Some(now));
 
                 let next_frame_callbacks = next_frame_callbacks.take();
                 if !next_frame_callbacks.is_empty() {