diff options
| author | Ivan Molodetskikh <yalterz@gmail.com> | 2025-11-20 12:55:45 +0300 |
|---|---|---|
| committer | Ivan Molodetskikh <yalterz@gmail.com> | 2025-11-21 07:14:27 +0300 |
| commit | 9d522ed51e75d1253793f9f5ec42b8faf36e47e7 (patch) | |
| tree | 1cae16bfde4f02ea61eb64f31699f12cf55961be /src/utils | |
| parent | 8ef5cc2297c18063907ab1d4d690162b5a58e54d (diff) | |
| download | niri-9d522ed51e75d1253793f9f5ec42b8faf36e47e7.tar.gz niri-9d522ed51e75d1253793f9f5ec42b8faf36e47e7.tar.bz2 niri-9d522ed51e75d1253793f9f5ec42b8faf36e47e7.zip | |
tty: Throttle VBlanks on displays running faster than expected
Co-authored-by: Christian Meissl <meissl.christian@gmail.com>
Co-authored-by: Scott McKendry <39483124+scottmckendry@users.noreply.github.com>
Diffstat (limited to 'src/utils')
| -rw-r--r-- | src/utils/mod.rs | 1 | ||||
| -rw-r--r-- | src/utils/vblank_throttle.rs | 71 |
2 files changed, 72 insertions, 0 deletions
diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 9a32e60f..fdde46f8 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -38,6 +38,7 @@ pub mod scale; pub mod signals; pub mod spawning; pub mod transaction; +pub mod vblank_throttle; pub mod watcher; pub mod xwayland; diff --git a/src/utils/vblank_throttle.rs b/src/utils/vblank_throttle.rs new file mode 100644 index 00000000..ecf2053f --- /dev/null +++ b/src/utils/vblank_throttle.rs @@ -0,0 +1,71 @@ +//! VBlank throttling. +//! +//! Some buggy drivers deliver VBlanks way earlier than necessary. This helper throttles the VBlank +//! in such cases to avoid tearing and to get more consistent timings. + +use std::time::Duration; + +use calloop::timer::{TimeoutAction, Timer}; +use calloop::{LoopHandle, RegistrationToken}; + +use crate::niri::State; + +#[derive(Debug)] +pub struct VBlankThrottle { + event_loop: LoopHandle<'static, State>, + last_vblank_timestamp: Option<Duration>, + throttle_timer_token: Option<RegistrationToken>, + printed_warning: bool, + output_name: String, +} + +impl VBlankThrottle { + pub fn new(event_loop: LoopHandle<'static, State>, output_name: String) -> Self { + Self { + event_loop, + last_vblank_timestamp: None, + throttle_timer_token: None, + printed_warning: false, + output_name, + } + } + + pub fn throttle( + &mut self, + refresh_interval: Option<Duration>, + timestamp: Duration, + mut call_vblank: impl FnMut(&mut State) + 'static, + ) -> bool { + if let Some(token) = self.throttle_timer_token.take() { + self.event_loop.remove(token); + } + + if let Some((last, refresh)) = Option::zip(self.last_vblank_timestamp, refresh_interval) { + let passed = timestamp.saturating_sub(last); + if passed < refresh / 2 { + if !self.printed_warning { + self.printed_warning = true; + warn!( + "output {} running faster than expected, throttling vblanks: \ + expected refresh {refresh:?}, got vblank after {passed:?}", + self.output_name + ); + } + + let remaining = refresh - passed; + let token = self + .event_loop + .insert_source(Timer::from_duration(remaining), move |_, _, state| { + call_vblank(state); + TimeoutAction::Drop + }) + .unwrap(); + self.throttle_timer_token = Some(token); + return true; + } + } + + self.last_vblank_timestamp = Some(timestamp); + false + } +} |
