Reduce GPU usage by activating VRR optimization only during high-rate input (#45369)
Fixes #29073 This PR reduces unnecessary GPU usage by being more selective about when we present frames to prevent display underclocking (VRR optimization). ## Problem Previously, we would keep presenting frames for 1 second after *any* input event, regardless of whether it triggered a re-render. This caused unnecessary GPU work when the user was idle or during low-frequency interactions. ## Solution 1. **Only track input that triggers re-renders**: We now only record input timestamps when the input actually causes the window to become dirty, rather than on every input event. 2. **Rate-based activation**: The VRR optimization now only activates when input arrives at a high rate (≥ 60fps over the last 100ms). This means casual mouse movements or occasional keystrokes won't trigger continuous frame presentation. 3. **Sustained optimization**: Once high-rate input is detected (e.g., during scrolling or dragging), we sustain frame presentation for 1 second to prevent display underclocking, even if input briefly pauses. ## Implementation Added `InputRateTracker` which: - Tracks input timestamps in a 100ms sliding window - Activates when the window contains ≥ 6 events (60fps × 0.1s) - Extends a `sustain_until` timestamp by 1 second each time high rate is detected Release Notes: - Reduced GPU usage when idle by only presenting frames during bursts of high-frequency input.
This commit is contained in:
committed by
GitHub
parent
7427924405
commit
b603372f44
@@ -876,7 +876,9 @@ pub struct Window {
|
||||
active: Rc<Cell<bool>>,
|
||||
hovered: Rc<Cell<bool>>,
|
||||
pub(crate) needs_present: Rc<Cell<bool>>,
|
||||
pub(crate) last_input_timestamp: Rc<Cell<Instant>>,
|
||||
/// Tracks recent input event timestamps to determine if input is arriving at a high rate.
|
||||
/// Used to selectively enable VRR optimization only when input rate exceeds 60fps.
|
||||
pub(crate) input_rate_tracker: Rc<RefCell<InputRateTracker>>,
|
||||
last_input_modality: InputModality,
|
||||
pub(crate) refreshing: bool,
|
||||
pub(crate) activation_observers: SubscriberSet<(), AnyObserver>,
|
||||
@@ -897,6 +899,51 @@ struct ModifierState {
|
||||
saw_keystroke: bool,
|
||||
}
|
||||
|
||||
/// Tracks input event timestamps to determine if input is arriving at a high rate.
|
||||
/// Used for selective VRR (Variable Refresh Rate) optimization.
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct InputRateTracker {
|
||||
timestamps: Vec<Instant>,
|
||||
window: Duration,
|
||||
inputs_per_second: u32,
|
||||
sustain_until: Instant,
|
||||
sustain_duration: Duration,
|
||||
}
|
||||
|
||||
impl Default for InputRateTracker {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
timestamps: Vec::new(),
|
||||
window: Duration::from_millis(100),
|
||||
inputs_per_second: 60,
|
||||
sustain_until: Instant::now(),
|
||||
sustain_duration: Duration::from_secs(1),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl InputRateTracker {
|
||||
pub fn record_input(&mut self) {
|
||||
let now = Instant::now();
|
||||
self.timestamps.push(now);
|
||||
self.prune_old_timestamps(now);
|
||||
|
||||
let min_events = self.inputs_per_second as u128 * self.window.as_millis() / 1000;
|
||||
if self.timestamps.len() as u128 >= min_events {
|
||||
self.sustain_until = now + self.sustain_duration;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_high_rate(&self) -> bool {
|
||||
Instant::now() < self.sustain_until
|
||||
}
|
||||
|
||||
fn prune_old_timestamps(&mut self, now: Instant) {
|
||||
self.timestamps
|
||||
.retain(|&t| now.duration_since(t) <= self.window);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub(crate) enum DrawPhase {
|
||||
None,
|
||||
@@ -1047,7 +1094,7 @@ impl Window {
|
||||
let hovered = Rc::new(Cell::new(platform_window.is_hovered()));
|
||||
let needs_present = Rc::new(Cell::new(false));
|
||||
let next_frame_callbacks: Rc<RefCell<Vec<FrameCallback>>> = Default::default();
|
||||
let last_input_timestamp = Rc::new(Cell::new(Instant::now()));
|
||||
let input_rate_tracker = Rc::new(RefCell::new(InputRateTracker::default()));
|
||||
|
||||
platform_window
|
||||
.request_decorations(window_decorations.unwrap_or(WindowDecorations::Server));
|
||||
@@ -1075,7 +1122,7 @@ impl Window {
|
||||
let active = active.clone();
|
||||
let needs_present = needs_present.clone();
|
||||
let next_frame_callbacks = next_frame_callbacks.clone();
|
||||
let last_input_timestamp = last_input_timestamp.clone();
|
||||
let input_rate_tracker = input_rate_tracker.clone();
|
||||
move |request_frame_options| {
|
||||
let next_frame_callbacks = next_frame_callbacks.take();
|
||||
if !next_frame_callbacks.is_empty() {
|
||||
@@ -1088,12 +1135,12 @@ impl Window {
|
||||
.log_err();
|
||||
}
|
||||
|
||||
// Keep presenting the current scene for 1 extra second since the
|
||||
// last input to prevent the display from underclocking the refresh rate.
|
||||
// Keep presenting if input was recently arriving at a high rate (>= 60fps).
|
||||
// Once high-rate input is detected, we sustain presentation for 1 second
|
||||
// to prevent display underclocking during active input.
|
||||
let needs_present = request_frame_options.require_presentation
|
||||
|| needs_present.get()
|
||||
|| (active.get()
|
||||
&& last_input_timestamp.get().elapsed() < Duration::from_secs(1));
|
||||
|| (active.get() && input_rate_tracker.borrow_mut().is_high_rate());
|
||||
|
||||
if invalidator.is_dirty() || request_frame_options.force_render {
|
||||
measure("frame duration", || {
|
||||
@@ -1101,7 +1148,6 @@ impl Window {
|
||||
.update(&mut cx, |_, window, cx| {
|
||||
let arena_clear_needed = window.draw(cx);
|
||||
window.present();
|
||||
// drop the arena elements after present to reduce latency
|
||||
arena_clear_needed.clear();
|
||||
})
|
||||
.log_err();
|
||||
@@ -1299,7 +1345,7 @@ impl Window {
|
||||
active,
|
||||
hovered,
|
||||
needs_present,
|
||||
last_input_timestamp,
|
||||
input_rate_tracker,
|
||||
last_input_modality: InputModality::Mouse,
|
||||
refreshing: false,
|
||||
activation_observers: SubscriberSet::new(),
|
||||
@@ -3691,8 +3737,6 @@ impl Window {
|
||||
/// Dispatch a mouse or keyboard event on the window.
|
||||
#[profiling::function]
|
||||
pub fn dispatch_event(&mut self, event: PlatformInput, cx: &mut App) -> DispatchEventResult {
|
||||
self.last_input_timestamp.set(Instant::now());
|
||||
|
||||
// Track whether this input was keyboard-based for focus-visible styling
|
||||
self.last_input_modality = match &event {
|
||||
PlatformInput::KeyDown(_) | PlatformInput::ModifiersChanged(_) => {
|
||||
@@ -3793,6 +3837,10 @@ impl Window {
|
||||
self.dispatch_key_event(any_key_event, cx);
|
||||
}
|
||||
|
||||
if self.invalidator.is_dirty() {
|
||||
self.input_rate_tracker.borrow_mut().record_input();
|
||||
}
|
||||
|
||||
DispatchEventResult {
|
||||
propagate: cx.propagate_event,
|
||||
default_prevented: self.default_prevented,
|
||||
|
||||
Reference in New Issue
Block a user