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 | |
| 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')
| -rw-r--r-- | src/backend/tty.rs | 16 | ||||
| -rw-r--r-- | src/niri.rs | 3 | ||||
| -rw-r--r-- | src/utils/mod.rs | 1 | ||||
| -rw-r--r-- | src/utils/vblank_throttle.rs | 71 |
4 files changed, 91 insertions, 0 deletions
diff --git a/src/backend/tty.rs b/src/backend/tty.rs index 891e8c79..fcae8087 100644 --- a/src/backend/tty.rs +++ b/src/backend/tty.rs @@ -1633,6 +1633,22 @@ impl Tty { presentation_time }; + if output_state + .vblank_throttle + .throttle(refresh_interval, time, move |state| { + let meta = DrmEventMetadata { + sequence: meta.sequence, + time: DrmEventTime::Monotonic(Duration::ZERO), + }; + + let tty = state.backend.tty(); + tty.on_vblank(&mut state.niri, node, crtc, meta); + }) + { + // Throttled. + return; + } + let redraw_needed = match mem::replace(&mut output_state.redraw_state, RedrawState::Idle) { RedrawState::WaitingForVBlank { redraw_needed } => redraw_needed, state @ (RedrawState::Idle diff --git a/src/niri.rs b/src/niri.rs index 423875d6..9520c0bd 100644 --- a/src/niri.rs +++ b/src/niri.rs @@ -170,6 +170,7 @@ use crate::ui::screen_transition::{self, ScreenTransition}; use crate::ui::screenshot_ui::{OutputScreenshot, ScreenshotUi, ScreenshotUiRenderElement}; use crate::utils::scale::{closest_representable_scale, guess_monitor_scale}; use crate::utils::spawning::{CHILD_DISPLAY, CHILD_ENV}; +use crate::utils::vblank_throttle::VBlankThrottle; use crate::utils::watcher::Watcher; use crate::utils::xwayland::satellite::Satellite; use crate::utils::{ @@ -459,6 +460,7 @@ pub struct OutputState { pub unfinished_animations_remain: bool, /// Last sequence received in a vblank event. pub last_drm_sequence: Option<u32>, + pub vblank_throttle: VBlankThrottle, /// Sequence for frame callback throttling. /// /// We want to send frame callbacks for each surface at most once per monitor refresh cycle. @@ -3076,6 +3078,7 @@ impl Niri { unfinished_animations_remain: false, frame_clock: FrameClock::new(refresh_interval, vrr), last_drm_sequence: None, + vblank_throttle: VBlankThrottle::new(self.event_loop.clone(), name.connector.clone()), frame_callback_sequence: 0, backdrop_buffer: SolidColorBuffer::new(size, backdrop_color), lock_render_state, 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 + } +} |
