From 28a9000833c5b3e347f07dcc44b48edc185b5a21 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Tue, 22 Jul 2025 12:50:02 +0200 Subject: [PATCH] wayland/compositor: Add per-surface frame time estimation --- src/wayland/handlers/compositor.rs | 88 ++++++++++++++++++++++++++++-- 1 file changed, 84 insertions(+), 4 deletions(-) diff --git a/src/wayland/handlers/compositor.rs b/src/wayland/handlers/compositor.rs index 70f13711..8564b047 100644 --- a/src/wayland/handlers/compositor.rs +++ b/src/wayland/handlers/compositor.rs @@ -7,11 +7,12 @@ use smithay::{ delegate_compositor, desktop::{layer_map_for_output, LayerSurface, PopupKind, WindowSurfaceType}, reexports::wayland_server::{protocol::wl_surface::WlSurface, Client, Resource}, - utils::{Logical, Size, SERIAL_COUNTER}, + utils::{Clock, Logical, Monotonic, Size, Time, SERIAL_COUNTER}, wayland::{ compositor::{ - add_blocker, add_pre_commit_hook, with_states, BufferAssignment, CompositorClientState, - CompositorHandler, CompositorState, SurfaceAttributes, + add_blocker, add_post_commit_hook, add_pre_commit_hook, with_states, + with_surface_tree_downward, BufferAssignment, CompositorClientState, CompositorHandler, + CompositorState, SurfaceAttributes, SurfaceData, TraversalAction, }, dmabuf::get_dmabuf, drm_syncobj::DrmSyncobjCachedState, @@ -25,7 +26,7 @@ use smithay::{ }, xwayland::XWaylandClientData, }; -use std::sync::Mutex; +use std::{collections::VecDeque, sync::Mutex, time::Duration}; fn toplevel_ensure_initial_configure( toplevel: &ToplevelSurface, @@ -92,6 +93,61 @@ pub fn client_compositor_state(client: &Client) -> &CompositorClientState { panic!("Unknown client data type") } +#[derive(Debug)] +struct FrametimeData { + last_commit: Option>, + last_diffs: VecDeque, + estimation: Duration, +} + +impl Default for FrametimeData { + fn default() -> Self { + FrametimeData { + last_commit: None, + last_diffs: VecDeque::with_capacity(100), + estimation: Duration::MAX, + } + } +} + +pub fn frame_time_estimation(clock: &Clock, states: &SurfaceData) -> Option { + let data = states + .data_map + .get::>()? + .lock() + .unwrap(); + if let Some(ref last) = data.last_commit { + // if the time since the last commit is already higher than our estimation, + // there is no reason to not use that as a better "guess" + let diff = Time::elapsed(&last, clock.now()); + Some(diff.max(data.estimation)) + } else { + Some(data.estimation) + } +} + +pub fn recursive_frame_time_estimation( + clock: &Clock, + surface: &WlSurface, +) -> Option { + let mut overall_estimate = None; + with_surface_tree_downward( + surface, + (), + |_, _, _| TraversalAction::DoChildren(()), + |_, data, _| { + let surface_estimate = frame_time_estimation(clock, data); + overall_estimate = match (overall_estimate, surface_estimate) { + (x, None) => x, + (None, Some(estimate)) => Some(estimate), + (Some(a), Some(b)) => Some(a.min(b)), + }; + }, + |_, _, _| true, + ); + overall_estimate +} + impl CompositorHandler for State { fn compositor_state(&mut self) -> &mut CompositorState { &mut self.common.compositor_state @@ -161,6 +217,30 @@ impl CompositorHandler for State { } } }); + + add_post_commit_hook::(surface, |state, _dh, surface| { + let now = state.common.clock.now(); + with_states(surface, |states| { + let mut data = states + .data_map + .get_or_insert_threadsafe::, _>(Default::default) + .lock() + .unwrap(); + if let Some(ref last) = data.last_commit { + let diff = Time::elapsed(last, now); + data.last_diffs.push_back(diff); + if data.last_diffs.len() > 100 { + data.last_diffs.pop_front(); + } + data.estimation = data + .last_diffs + .iter() + .fold(Duration::ZERO, |acc, new| acc.saturating_add(*new)) + / (data.last_diffs.len() as u32); + } + data.last_commit = Some(now); + }); + }); } fn commit(&mut self, surface: &WlSurface) {