From db25cc4b0bf4fe37fa6d96856e27e9bba7810f24 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Thu, 24 Apr 2025 18:57:13 +0200 Subject: [PATCH] kms/timings: Base `next_render_time` on time to submit --- src/backend/kms/surface/mod.rs | 2 +- src/backend/kms/surface/timings.rs | 58 +++++++++++++++++++++++++++--- 2 files changed, 54 insertions(+), 6 deletions(-) diff --git a/src/backend/kms/surface/mod.rs b/src/backend/kms/surface/mod.rs index f8b873f5..56b154dc 100644 --- a/src/backend/kms/surface/mod.rs +++ b/src/backend/kms/surface/mod.rs @@ -523,7 +523,7 @@ fn surface_thread( vrr_mode: AdaptiveSync::Disabled, state: QueueState::Idle, - timings: Timings::new(None, None, false), + timings: Timings::new(None, None, false, target_node), frame_callback_seq: 0, thread_sender, diff --git a/src/backend/kms/surface/timings.rs b/src/backend/kms/surface/timings.rs index 98efa7bd..eff7efa4 100644 --- a/src/backend/kms/surface/timings.rs +++ b/src/backend/kms/surface/timings.rs @@ -1,15 +1,19 @@ use std::{collections::VecDeque, num::NonZeroU64, time::Duration}; -use smithay::utils::{Clock, Monotonic, Time}; +use smithay::{ + backend::drm::DrmNode, + utils::{Clock, Monotonic, Time}, +}; use tracing::{debug, error}; -const FRAME_TIME_BUFFER: Duration = Duration::from_millis(1); -const FRAME_TIME_WINDOW: usize = 3; +const BASE_SAFETY_MARGIN: Duration = Duration::from_millis(3); +const SAMPLE_TIME_WINDOW: usize = 5; pub struct Timings { refresh_interval_ns: Option, min_refresh_interval_ns: Option, vrr: bool, + vendor: Option, pub pending_frame: Option, pub previous_frames: VecDeque, @@ -37,6 +41,10 @@ impl Frame { self.render_duration_elements + self.render_duration_draw } + fn submit_time(&self) -> Duration { + Time::elapsed(&self.render_start, self.presentation_submitted) + } + fn frame_time(&self) -> Duration { Time::elapsed(&self.render_start, self.presentation_presented) } @@ -49,6 +57,7 @@ impl Timings { refresh_interval: Option, min_interval: Option, vrr: bool, + node: DrmNode, ) -> Self { let refresh_interval_ns = if let Some(interval) = &refresh_interval { assert_eq!(interval.as_secs(), 0); @@ -64,10 +73,20 @@ impl Timings { None }; + let vendor = if let Ok(vendor) = std::fs::read_to_string(format!( + "/sys/class/drm/renderD{}/device/vendor", + node.minor() + )) { + u32::from_str_radix(&vendor.trim()[2..], 16).ok() + } else { + None + }; + Self { refresh_interval_ns, min_refresh_interval_ns, vrr, + vendor, pending_frame: None, previous_frames: VecDeque::new(), @@ -209,6 +228,22 @@ impl Timings { / (self.previous_frames.len() as u32) } + pub fn avg_submittime(&self, window: usize) -> Option { + if self.previous_frames.len() < window || window == 0 { + return None; + } + + Some( + self.previous_frames + .iter() + .rev() + .take(window) + .map(|f| f.submit_time()) + .try_fold(Duration::ZERO, |acc, x| acc.checked_add(x))? + / (window.min(self.previous_frames.len()) as u32), + ) + } + pub fn avg_frametime(&self, window: usize) -> Option { if self.previous_frames.len() < window || window == 0 { return None; @@ -308,15 +343,28 @@ impl Timings { } pub fn next_render_time(&self, clock: &Clock) -> Duration { + let Some(refresh_interval) = self.refresh_interval_ns else { + return Duration::ZERO; // we don't know what to expect, so render immediately. + }; + + const MIN_MARGIN: Duration = Duration::from_millis(3); + let baseline = MIN_MARGIN.max(Duration::from_nanos(refresh_interval.get() / 2)); + let estimated_presentation_time = self.next_presentation_time(clock); if estimated_presentation_time.is_zero() { return Duration::ZERO; } - let Some(avg_frametime) = self.avg_frametime(FRAME_TIME_WINDOW) else { + // HACK: Nvidia returns `page_flip`/`commit` early, so we have no information to optimize latency on submission. + if self.vendor == Some(0x10de) { return Duration::ZERO; + } + + let Some(avg_submittime) = self.avg_submittime(SAMPLE_TIME_WINDOW) else { + return estimated_presentation_time.saturating_sub(baseline + BASE_SAFETY_MARGIN); }; - estimated_presentation_time.saturating_sub(avg_frametime + FRAME_TIME_BUFFER) + let margin = avg_submittime + BASE_SAFETY_MARGIN; + estimated_presentation_time.saturating_sub(margin) } }