diff --git a/src/backend/x11.rs b/src/backend/x11.rs index a6ae140e..4057ca63 100644 --- a/src/backend/x11.rs +++ b/src/backend/x11.rs @@ -36,6 +36,9 @@ use std::{ sync::{Arc, Mutex}, }; +#[cfg(feature = "debug")] +use crate::{rendering::debug_ui, state::Fps}; + pub struct X11State { allocator: Arc>>, _egl: EGLDisplay, @@ -112,6 +115,8 @@ impl X11State { surface, output: output.clone(), render: ping.clone(), + #[cfg(feature = "debug")] + fps: Fps::default(), _global, }); @@ -126,6 +131,8 @@ pub struct Surface { surface: X11Surface, output: Output, render: ping::Ping, + #[cfg(feature = "debug")] + fps: Fps, _global: GlobalDrop, } @@ -145,14 +152,7 @@ impl Surface { let size = space.output_geometry(&self.output).unwrap(); let scale = space.output_scale(&self.output).unwrap(); let frame = state.egui.state.run( - |ctx| { - egui::SidePanel::left("my_left_panel").show(ctx, |ui| { - ui.label(format!( - "cosmic-comp version {}", - std::env!("CARGO_PKG_VERSION") - )); - }); - }, + |ctx| debug_ui(ctx, &self.fps, true), size, size.to_f64().to_physical(scale).to_i32_round().size, scale, @@ -185,6 +185,10 @@ impl Surface { self.surface .submit() .with_context(|| "Failed to submit buffer for display")?; + #[cfg(feature = "debug")] + { + self.fps.tick(); + } } Ok(None) => { let _ = renderer.unbind(); diff --git a/src/main.rs b/src/main.rs index 7b47fd17..fa57538e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,6 +10,7 @@ use slog::Drain; pub mod backend; pub mod input; +pub mod rendering; pub mod shell; pub mod state; pub mod utils; diff --git a/src/rendering/debug.rs b/src/rendering/debug.rs index e69de29b..da5b3e9c 100644 --- a/src/rendering/debug.rs +++ b/src/rendering/debug.rs @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: GPL-3.0-only + +use crate::state::{Common, Fps}; +use egui::CtxRef; + +pub fn debug_ui(ctx: &CtxRef, fps: &Fps, primary: bool) { + egui::Area::new("main") + .anchor(egui::Align2::LEFT_TOP, (10.0, 10.0)) + .show(ctx, |ui| { + use egui::widgets::plot::{Bar, BarChart, HLine, Legend, Plot}; + + // Basics + + let label_res = ui.label(format!( + "cosmic-comp version {}", + std::env!("CARGO_PKG_VERSION") + )); + ui.set_max_width(label_res.rect.width()); + if let Some(hash) = std::option_env!("GIT_HASH").and_then(|x| x.get(0..8)) { + ui.label(hash); + } + ui.separator(); + + // FPS + + let (max, min, avg) = ( + fps.max().as_secs_f64(), + fps.min().as_secs_f64(), + fps.avg_fps(), + ); + let fps_chart = BarChart::new( + fps.frames + .iter() + .enumerate() + .map(|(i, duration)| { + let value = duration.as_secs_f64(); + let transformed = ((value - min) / (max - min) * 255.0).round() as u8; + Bar::new(i as f64, value + 1.0).fill(egui::Color32::from_rgb( + transformed, + 255 - transformed, + 0, + )) + }) + .collect(), + ) + .vertical(); + + Plot::new("FPS") + .legend(Legend::default()) + .view_aspect(33.0) + .include_x(0.0) + .include_x(100.0) + .include_y(0.0) + .include_y(max + 1.0) + .show_x(false) + .show(ui, |plot_ui| { + plot_ui.bar_chart(fps_chart); + plot_ui.hline( + HLine::new(avg) + .highlight() + .name(format!("AVG {}", avg.round() as i32)) + .color(egui::Color32::DARK_BLUE), + ); + }); + }); +} diff --git a/src/rendering/mod.rs b/src/rendering/mod.rs new file mode 100644 index 00000000..d92fc703 --- /dev/null +++ b/src/rendering/mod.rs @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: GPL-3.0-only + +#[cfg(feature = "debug")] +mod debug; +#[cfg(feature = "debug")] +pub use debug::*; diff --git a/src/state.rs b/src/state.rs index 46370bf0..d5a54088 100644 --- a/src/state.rs +++ b/src/state.rs @@ -14,7 +14,10 @@ use smithay::{ shm::init_shm_global, }, }; + use std::{cell::RefCell, rc::Rc, time::Instant}; +#[cfg(feature = "debug")] +use std::{collections::VecDeque, time::Duration}; pub struct State { pub common: Common, @@ -34,7 +37,6 @@ pub struct Common { pub start_time: Instant, pub should_stop: bool, - #[cfg(feature = "debug")] pub egui: Egui, } @@ -46,6 +48,21 @@ pub struct Egui { pub alpha: f32, } +#[cfg(feature = "debug")] +pub struct Fps { + pub frames: VecDeque, + pub last: Instant, +} + +impl Default for Fps { + fn default() -> Fps { + Fps { + frames: VecDeque::with_capacity(101), + last: Instant::now(), + } + } +} + pub enum BackendData { X11(X11State), // TODO @@ -102,3 +119,34 @@ impl State { } } } + +#[cfg(feature = "debug")] +impl Fps { + pub fn tick(&mut self) { + let next = Instant::now(); + let frame = next.duration_since(self.last); + + self.frames.push_back(frame); + if self.frames.len() > 100 { + self.frames.pop_front(); + } + self.last = next; + } + + pub fn max(&self) -> &Duration { + self.frames.iter().max().unwrap_or(&Duration::ZERO) + } + + pub fn min(&self) -> &Duration { + self.frames.iter().min().unwrap_or(&Duration::ZERO) + } + + pub fn avg(&self) -> Duration { + self.frames.iter().cloned().sum::() / (self.frames.len() as u32) + } + + pub fn avg_fps(&self) -> f64 { + let sum_secs = self.frames.iter().map(|d| d.as_secs_f64()).sum::(); + 1.0 / (sum_secs / self.frames.len() as f64) + } +}