diff --git a/src/backend/kms/mod.rs b/src/backend/kms/mod.rs index c64f984f..d8eb8856 100644 --- a/src/backend/kms/mod.rs +++ b/src/backend/kms/mod.rs @@ -381,6 +381,9 @@ impl State { let rescheduled_output = if let Some(device) = data.state.backend.kms().devices.get_mut(&drm_node) { if let Some(surface) = device.surfaces.get_mut(&crtc) { + #[cfg(feature = "debug")] + surface.fps.displayed(); + match surface.surface.as_mut().map(|x| x.frame_submitted()) { Some(Ok(feedback)) => { if let Some(mut feedback) = feedback.flatten() { diff --git a/src/backend/render/mod.rs b/src/backend/render/mod.rs index 02cd7fd8..c3f2e96e 100644 --- a/src/backend/render/mod.rs +++ b/src/backend/render/mod.rs @@ -252,12 +252,17 @@ where .map(Into::into), ); + #[cfg(feature = "debug")] + if let Some(fps) = fps.as_mut() { + fps.elements(); + } + renderer.bind(target).map_err(RenderError::Rendering)?; let res = damage_tracker.render_output(renderer, age, &elements, CLEAR_COLOR, None); #[cfg(feature = "debug")] if let Some(fps) = fps.as_mut() { - fps.end(); + fps.render(); } if let Some((source, buffers)) = screencopy { @@ -303,6 +308,10 @@ where } } } + #[cfg(feature = "debug")] + if let Some(fps) = fps.as_mut() { + fps.screencopy(); + } } res diff --git a/src/backend/winit.rs b/src/backend/winit.rs index 1790aef1..174dffb2 100644 --- a/src/backend/winit.rs +++ b/src/backend/winit.rs @@ -73,6 +73,8 @@ impl WinitState { self.backend .submit(damage.as_deref()) .with_context(|| "Failed to submit buffer for display")?; + #[cfg(feature = "debug")] + self.fps.displayed(); state.send_frames(&self.output, &states); if damage.is_some() { let mut output_presentation_feedback = diff --git a/src/backend/x11.rs b/src/backend/x11.rs index 7c9e903b..da5eb1fc 100644 --- a/src/backend/x11.rs +++ b/src/backend/x11.rs @@ -221,6 +221,8 @@ impl Surface { self.surface .submit() .with_context(|| "Failed to submit buffer for display")?; + #[cfg(feature = "debug")] + self.fps.displayed(); state.send_frames(&self.output, &states); if damage.is_some() { let mut output_presentation_feedback = diff --git a/src/debug.rs b/src/debug.rs index c8abf923..8e41261c 100644 --- a/src/debug.rs +++ b/src/debug.rs @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only use crate::state::{Common, Fps}; +use egui::Color32; use smithay::{ backend::{ drm::DrmNode, @@ -15,6 +16,11 @@ use smithay::{ utils::{IsAlive, Logical, Rectangle}, }; +pub const ELEMENTS_COLOR: Color32 = Color32::from_rgb(70, 198, 115); +pub const RENDER_COLOR: Color32 = Color32::from_rgb(29, 114, 58); +pub const SCREENCOPY_COLOR: Color32 = Color32::from_rgb(253, 178, 39); +pub const DISPLAY_COLOR: Color32 = Color32::from_rgb(41, 184, 209); + pub fn fps_ui( gpu: Option<&DrmNode>, state: &Common, @@ -31,43 +37,70 @@ pub fn fps_ui( fps.avg_frametime().as_secs_f64(), fps.avg_fps(), ); + let (max_disp, min_disp) = ( + fps.max_time_to_display().as_secs_f64(), + fps.min_time_to_display().as_secs_f64(), + ); + let amount = dbg!(avg_fps.round() as usize * 2); - let bars = fps + let ((bars_elements, bars_render), (bars_screencopy, bars_displayed)): ( + (Vec, Vec), + (Vec, Vec), + ) = fps .frames .iter() .rev() .take(amount) .rev() .enumerate() - .map(|(i, (_, d))| { - let value = d.as_secs_f64(); - let transformed = ((value - min) / (max - min) * 255.0).round() as u8; + .map(|(i, frame)| { + let elements_val = frame.duration_elements.as_secs_f64(); + let render_val = frame.duration_render.as_secs_f64(); + let screencopy_val = frame + .duration_screencopy + .as_ref() + .map(|val| val.as_secs_f64()) + .unwrap_or(0.0); + let displayed_val = frame.duration_displayed.as_secs_f64(); - Bar::new(i as f64, transformed as f64).fill(egui::Color32::from_rgb( - transformed, - 255 - transformed, - 0, - )) + let transformed_elements = + ((elements_val - min_disp) / (max_disp - min_disp) * 255.0).round() as u8; + let transformed_render = + ((render_val - min_disp) / (max_disp - min_disp) * 255.0).round() as u8; + let transformed_screencopy = + ((screencopy_val - min_disp) / (max_disp - min_disp) * 255.0).round() as u8; + let transformed_displayed = + ((displayed_val - min_disp) / (max_disp - min_disp) * 255.0).round() as u8; + ( + ( + Bar::new(i as f64, transformed_elements as f64).fill(ELEMENTS_COLOR), + Bar::new(i as f64, transformed_render as f64).fill(RENDER_COLOR), + ), + ( + Bar::new(i as f64, transformed_screencopy as f64).fill(SCREENCOPY_COLOR), + Bar::new(i as f64, transformed_displayed as f64).fill(DISPLAY_COLOR), + ), + ) }) - .collect::>(); + .unzip(); fps.state.render( |ctx| { egui::Area::new("main") .anchor(egui::Align2::LEFT_TOP, (10.0, 10.0)) .show(ctx, |ui| { - let label_res = ui.label(format!( + ui.label(format!( "cosmic-comp version {}", std::env!("CARGO_PKG_VERSION") )); if let Some(hash) = std::option_env!("GIT_HASH").and_then(|x| x.get(0..10)) { - ui.label(hash); + ui.label(format!(" :{hash}")); } if !state.egui.active { ui.label("Press Super+Escape for debug menu"); } else { - ui.set_max_width(400.0); + ui.set_max_width(300.0); ui.separator(); if let Some(gpu) = gpu { @@ -78,7 +111,16 @@ pub fn fps_ui( ui.label(egui::RichText::new(format!("avg: {:>7.6}", avg)).code()); ui.label(egui::RichText::new(format!("min: {:>7.6}", min)).code()); ui.label(egui::RichText::new(format!("max: {:>7.6}", max)).code()); - let fps_chart = BarChart::new(bars).vertical(); + let elements_chart = BarChart::new(bars_elements).vertical(); + let render_chart = BarChart::new(bars_render) + .stack_on(&[&elements_chart]) + .vertical(); + let screencopy_chart = BarChart::new(bars_screencopy) + .stack_on(&[&elements_chart, &render_chart]) + .vertical(); + let display_chart = BarChart::new(bars_displayed) + .stack_on(&[&elements_chart, &render_chart, &screencopy_chart]) + .vertical(); Plot::new("FPS") .legend(Legend::default()) @@ -89,14 +131,10 @@ pub fn fps_ui( .include_y(300) .show_x(false) .show(ui, |plot_ui| { - plot_ui.bar_chart(fps_chart); - /* - plot_ui.hline( - HLine::new(avg) - .highlight(true) - .color(egui::Color32::LIGHT_BLUE), - ); - */ + plot_ui.bar_chart(elements_chart); + plot_ui.bar_chart(render_chart); + plot_ui.bar_chart(screencopy_chart); + plot_ui.bar_chart(display_chart); }); } }); diff --git a/src/state.rs b/src/state.rs index 9b996f56..7adc3af9 100644 --- a/src/state.rs +++ b/src/state.rs @@ -103,29 +103,6 @@ pub struct Common { pub viewporter_state: ViewporterState, } -#[cfg(feature = "debug")] -pub struct Egui { - pub debug_state: smithay_egui::EguiState, - pub active: bool, - pub alpha: f32, -} - -#[cfg(feature = "debug")] -pub struct Fps { - pub state: smithay_egui::EguiState, - pub frames: VecDeque<(Instant, Duration)>, - pub start: Instant, -} - -#[cfg(feature = "debug")] -pub struct Frame { - start: Instant, - duration_elements: Duration, - duration_render: Duration, - duration_screencopy: Duration, - duration_displayed: Duration, -} - pub enum BackendData { X11(X11State), Winit(WinitState), @@ -455,48 +432,158 @@ impl Common { } #[cfg(feature = "debug")] -impl Fps { - const WINDOW_SIZE: usize = 1000; +pub struct Egui { + pub debug_state: smithay_egui::EguiState, + pub active: bool, + pub alpha: f32, +} - pub fn start(&mut self) { - self.start = Instant::now(); +#[cfg(feature = "debug")] +pub struct Fps { + pub state: smithay_egui::EguiState, + pending_frame: Option, + pub frames: VecDeque, +} + +#[cfg(feature = "debug")] +#[derive(Debug)] +struct PendingFrame { + start: Instant, + duration_elements: Option, + duration_render: Option, + duration_screencopy: Option, + duration_displayed: Option, +} + +#[cfg(feature = "debug")] +#[derive(Debug)] +pub struct Frame { + pub start: Instant, + pub duration_elements: Duration, + pub duration_render: Duration, + pub duration_screencopy: Option, + pub duration_displayed: Duration, +} + +impl Frame { + fn frame_time(&self) -> Duration { + self.duration_elements + + self.duration_render + + self.duration_screencopy.clone().unwrap_or(Duration::ZERO) } - pub fn end(&mut self) { - let frame_time = Instant::now().duration_since(self.start); + fn time_to_display(&self) -> Duration { + self.duration_elements + + self.duration_render + + self.duration_screencopy.clone().unwrap_or(Duration::ZERO) + + self.duration_displayed + } +} - self.frames.push_back((self.start, frame_time)); - if self.frames.len() > Fps::WINDOW_SIZE { - self.frames.pop_front(); +impl From for Frame { + fn from(pending: PendingFrame) -> Self { + Frame { + start: pending.start, + duration_elements: pending.duration_elements.unwrap_or(Duration::ZERO), + duration_render: pending.duration_render.unwrap_or(Duration::ZERO), + duration_screencopy: pending.duration_screencopy, + duration_displayed: pending.duration_displayed.unwrap_or(Duration::ZERO), + } + } +} + +#[cfg(feature = "debug")] +impl Fps { + const WINDOW_SIZE: usize = 360; + + pub fn start(&mut self) { + self.pending_frame = Some(PendingFrame { + start: Instant::now(), + duration_elements: None, + duration_render: None, + duration_screencopy: None, + duration_displayed: None, + }); + } + + pub fn elements(&mut self) { + if let Some(frame) = self.pending_frame.as_mut() { + frame.duration_elements = Some(Instant::now().duration_since(frame.start)); } } - pub fn max_frametime(&self) -> &Duration { - self.frames - .iter() - .map(|(_, f)| f) - .max() - .unwrap_or(&Duration::ZERO) + pub fn render(&mut self) { + if let Some(frame) = self.pending_frame.as_mut() { + frame.duration_render = Some( + Instant::now().duration_since(frame.start) + - frame.duration_elements.clone().unwrap_or(Duration::ZERO), + ); + } } - pub fn min_frametime(&self) -> &Duration { + pub fn screencopy(&mut self) { + if let Some(frame) = self.pending_frame.as_mut() { + frame.duration_screencopy = Some( + Instant::now().duration_since(frame.start) + - frame.duration_elements.clone().unwrap_or(Duration::ZERO) + - frame.duration_render.clone().unwrap_or(Duration::ZERO), + ); + } + } + + pub fn displayed(&mut self) { + if let Some(mut frame) = self.pending_frame.take() { + frame.duration_displayed = Some( + Instant::now().duration_since(frame.start) + - frame.duration_elements.clone().unwrap_or(Duration::ZERO) + - frame.duration_render.clone().unwrap_or(Duration::ZERO) + - frame.duration_screencopy.clone().unwrap_or(Duration::ZERO), + ); + + self.frames.push_back(frame.into()); + if self.frames.len() > Fps::WINDOW_SIZE { + self.frames.pop_front(); + } + } + } + + pub fn max_frametime(&self) -> Duration { self.frames .iter() - .map(|(_, f)| f) + .map(|f| f.frame_time()) + .max() + .unwrap_or(Duration::ZERO) + } + + pub fn min_frametime(&self) -> Duration { + self.frames + .iter() + .map(|f| f.frame_time()) .min() - .unwrap_or(&Duration::ZERO) + .unwrap_or(Duration::ZERO) + } + + pub fn max_time_to_display(&self) -> Duration { + self.frames + .iter() + .map(|f| f.time_to_display()) + .max() + .unwrap_or(Duration::ZERO) + } + + pub fn min_time_to_display(&self) -> Duration { + self.frames + .iter() + .map(|f| f.time_to_display()) + .min() + .unwrap_or(Duration::ZERO) } pub fn avg_frametime(&self) -> Duration { if self.frames.is_empty() { return Duration::ZERO; } - self.frames - .iter() - .map(|(_, f)| f) - .cloned() - .sum::() - / (self.frames.len() as u32) + self.frames.iter().map(|f| f.frame_time()).sum::() / (self.frames.len() as u32) } pub fn avg_fps(&self) -> f64 { @@ -504,7 +591,9 @@ impl Fps { return 0.0; } let secs = match (self.frames.front(), self.frames.back()) { - (Some((start, _)), Some((end, dur))) => end.duration_since(*start) + *dur, + (Some(Frame { start, .. }), Some(end_frame)) => { + end_frame.start.duration_since(*start) + end_frame.frame_time() + } _ => Duration::ZERO, } .as_secs_f64(); @@ -524,8 +613,8 @@ impl Default for Fps { state.context().set_visuals(visuals); state }, + pending_frame: None, frames: VecDeque::with_capacity(Fps::WINDOW_SIZE + 1), - start: Instant::now(), } } }