From 36d52373a178d8eb5f929fb34e301ce8f3745ecf Mon Sep 17 00:00:00 2001 From: Samuel Onoja Date: Fri, 25 Sep 2026 05:49:52 +0100 Subject: [PATCH] feat: report blur rendering workload --- README.md | 3 ++- compositor/src/backends/direct.rs | 15 ++++++++---- compositor/src/metrics.rs | 30 ++++++++++++++++++++++++ compositor/src/winit.rs | 38 +++++++++++++++++++++++++------ 4 files changed, 74 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 16d3db0..099dcf4 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,8 @@ Set `RUST_LOG=ferese=debug` for detailed compositor logging. Set `FERESE_TRACE_PERFORMANCE=1` to emit five-second per-output summaries under the `ferese::render` tracing target. Summaries include rendered frames, damaged -pixels, average and longest render time, and DRM missed-deadline totals. +pixels, average and longest render time, DRM missed-deadline totals, blur-active +frames, blur-region count, and sampled blur pixels. The native-client soak runner and its nested/DRM procedure are documented in [docs/native-soak-testing.md](docs/native-soak-testing.md). diff --git a/compositor/src/backends/direct.rs b/compositor/src/backends/direct.rs index fb68190..66a9742 100644 --- a/compositor/src/backends/direct.rs +++ b/compositor/src/backends/direct.rs @@ -43,7 +43,9 @@ use crate::{ Ferese, config::{OutputModeRequest, OutputProfile, OutputSettings, OutputTransform}, metrics::RenderMetrics, - winit::{animated_window_elements, cursorless_window_elements, redraw_output}, + winit::{ + animated_window_elements, cursorless_window_elements, frame_effect_metrics, redraw_output, + }, }; pub struct DirectBackendState { @@ -444,6 +446,8 @@ fn render_output(state: &mut Ferese, node: DrmNode, crtc: crtc::Handle) { state.process_dmabuf_imports(&mut device.renderer); let (mut buffer, age) = output.surface.next_buffer()?; let elements = animated_window_elements(state, &mut device.renderer, &output.output); + let effects = + frame_effect_metrics(&elements, output.output.current_scale().fractional_scale()); let mut framebuffer = device.renderer.bind(&mut buffer)?; let result = output.damage_tracker.render_output( &mut device.renderer, @@ -512,9 +516,12 @@ fn render_output(state: &mut Ferese, node: DrmNode, crtc: crtc::Handle) { output .surface .queue_buffer(Some(result.sync), Some(damage.clone()), presentation)?; - output - .render_metrics - .record_frame(render_started.elapsed(), &damage, missed_deadlines); + output.render_metrics.record_frame( + render_started.elapsed(), + &damage, + missed_deadlines, + effects, + ); output.frame_pending = true; Ok(true) })(); diff --git a/compositor/src/metrics.rs b/compositor/src/metrics.rs index 844bcaf..dc00488 100644 --- a/compositor/src/metrics.rs +++ b/compositor/src/metrics.rs @@ -4,6 +4,12 @@ use smithay::utils::{Physical, Rectangle}; const REPORT_INTERVAL: Duration = Duration::from_secs(5); +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +pub(crate) struct FrameEffectMetrics { + pub(crate) blur_regions: u64, + pub(crate) blur_sample_pixels: u64, +} + #[derive(Debug)] pub(crate) struct RenderMetrics { enabled: bool, @@ -13,6 +19,9 @@ pub(crate) struct RenderMetrics { damaged_pixels: u64, total_render_time: Duration, longest_render_time: Duration, + blur_frames: u64, + blur_regions: u64, + blur_sample_pixels: u64, } impl RenderMetrics { @@ -33,6 +42,9 @@ impl RenderMetrics { damaged_pixels: 0, total_render_time: Duration::ZERO, longest_render_time: Duration::ZERO, + blur_frames: 0, + blur_regions: 0, + blur_sample_pixels: 0, } } @@ -41,6 +53,7 @@ impl RenderMetrics { render_time: Duration, damage: &[Rectangle], missed_deadlines: u64, + effects: FrameEffectMetrics, ) { if !self.enabled { return; @@ -50,6 +63,9 @@ impl RenderMetrics { self.damaged_pixels += damage.iter().map(rectangle_area).sum::(); self.total_render_time += render_time; self.longest_render_time = self.longest_render_time.max(render_time); + self.blur_frames += u64::from(effects.blur_regions > 0); + self.blur_regions += effects.blur_regions; + self.blur_sample_pixels += effects.blur_sample_pixels; let now = Instant::now(); let elapsed = now.saturating_duration_since(self.interval_started); @@ -71,6 +87,9 @@ impl RenderMetrics { average_render_us, longest_render_us = self.longest_render_time.as_micros(), missed_deadlines, + blur_frames = self.blur_frames, + blur_regions = self.blur_regions, + blur_sample_pixels = self.blur_sample_pixels, "render performance" ); @@ -79,6 +98,9 @@ impl RenderMetrics { self.damaged_pixels = 0; self.total_render_time = Duration::ZERO; self.longest_render_time = Duration::ZERO; + self.blur_frames = 0; + self.blur_regions = 0; + self.blur_sample_pixels = 0; } } @@ -99,6 +121,7 @@ mod tests { Duration::from_millis(2), &[Rectangle::new((0, 0).into(), (100, 50).into())], 0, + FrameEffectMetrics::default(), ); assert_eq!(metrics.rendered_frames, 0); @@ -116,11 +139,18 @@ mod tests { Rectangle::new((10, 10).into(), (20, 10).into()), ], 0, + FrameEffectMetrics { + blur_regions: 2, + blur_sample_pixels: 4_096, + }, ); assert_eq!(metrics.rendered_frames, 1); assert_eq!(metrics.damaged_pixels, 5_200); assert_eq!(metrics.total_render_time, Duration::from_millis(2)); assert_eq!(metrics.longest_render_time, Duration::from_millis(2)); + assert_eq!(metrics.blur_frames, 1); + assert_eq!(metrics.blur_regions, 2); + assert_eq!(metrics.blur_sample_pixels, 4_096); } } diff --git a/compositor/src/winit.rs b/compositor/src/winit.rs index 3570248..1daaa8d 100644 --- a/compositor/src/winit.rs +++ b/compositor/src/winit.rs @@ -50,7 +50,10 @@ use smithay::{ wayland::{compositor::with_states, presentation::Refresh}, }; -use crate::{Ferese, metrics::RenderMetrics}; +use crate::{ + Ferese, + metrics::{FrameEffectMetrics, RenderMetrics}, +}; type SurfaceRenderElement = CropRenderElement< RelocateRenderElement>>, @@ -64,7 +67,7 @@ type MemoryRenderElement = CropRenderElement< >; type PhysicalDamage = Vec>; -type DamageRenderResult = Result, Box>; +type DamageRenderResult = Result<(Option, FrameEffectMetrics), Box>; render_elements! { pub(crate) AnimatedWindowRenderElement<=GlesRenderer>; @@ -682,6 +685,10 @@ pub fn init(event_loop: &mut EventLoop, state: &mut Ferese) -> Result<() let (renderer, mut framebuffer) = backend.bind()?; state.process_dmabuf_imports(renderer); let elements = animated_window_elements(state, renderer, &output); + let effects = frame_effect_metrics( + &elements, + output.current_scale().fractional_scale(), + ); let result = damage_tracker.render_output( renderer, &mut framebuffer, @@ -714,12 +721,12 @@ pub fn init(event_loop: &mut EventLoop, state: &mut Ferese) -> Result<() .finish()?; } - Ok(result.damage.cloned()) + Ok((result.damage.cloned(), effects)) } })(); - let damage = match rendered { - Ok(Some(damage)) => damage, - Ok(None) => { + let (damage, effects) = match rendered { + Ok((Some(damage), effects)) => (damage, effects), + Ok((None, _)) => { state.space.refresh(); state.popups.cleanup(); layer_map_for_output(&output).cleanup(); @@ -740,7 +747,7 @@ pub fn init(event_loop: &mut EventLoop, state: &mut Ferese) -> Result<() state.loop_signal.stop(); return; } - render_metrics.record_frame(render_started.elapsed(), &damage, 0); + render_metrics.record_frame(render_started.elapsed(), &damage, 0, effects); let mut presentation = OutputPresentationFeedback::new(&output); state.space.elements().for_each(|window| { @@ -811,6 +818,23 @@ pub(crate) fn cursorless_window_elements( output_elements(state, renderer, output, false) } +pub(crate) fn frame_effect_metrics( + elements: &[AnimatedWindowRenderElement], + scale: f64, +) -> FrameEffectMetrics { + elements + .iter() + .fold(FrameEffectMetrics::default(), |mut metrics, element| { + if let AnimatedWindowRenderElement::Backdrop(backdrop) = element { + let geometry = backdrop.geometry(scale.into()); + metrics.blur_regions += 1; + metrics.blur_sample_pixels += u64::try_from(geometry.size.w.max(0)).unwrap_or(0) + * u64::try_from(geometry.size.h.max(0)).unwrap_or(0); + } + metrics + }) +} + fn output_elements( state: &mut Ferese, renderer: &mut GlesRenderer,