feat: report blur rendering workload

This commit is contained in:
Samuel Onoja committed 2026-09-25 05:49:52 +01:00
1 parent cbf7bf3539
commit 36d52373a1
4 files changed
+74 -12

No files matched your search

+2 -1
View File
@@ -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 Set `FERESE_TRACE_PERFORMANCE=1` to emit five-second per-output summaries under
the `ferese::render` tracing target. Summaries include rendered frames, damaged 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 The native-client soak runner and its nested/DRM procedure are documented in
[docs/native-soak-testing.md](docs/native-soak-testing.md). [docs/native-soak-testing.md](docs/native-soak-testing.md).
+11 -4
View File
@@ -43,7 +43,9 @@ use crate::{
Ferese, Ferese,
config::{OutputModeRequest, OutputProfile, OutputSettings, OutputTransform}, config::{OutputModeRequest, OutputProfile, OutputSettings, OutputTransform},
metrics::RenderMetrics, 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 { 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); state.process_dmabuf_imports(&mut device.renderer);
let (mut buffer, age) = output.surface.next_buffer()?; let (mut buffer, age) = output.surface.next_buffer()?;
let elements = animated_window_elements(state, &mut device.renderer, &output.output); 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 mut framebuffer = device.renderer.bind(&mut buffer)?;
let result = output.damage_tracker.render_output( let result = output.damage_tracker.render_output(
&mut device.renderer, &mut device.renderer,
@@ -512,9 +516,12 @@ fn render_output(state: &mut Ferese, node: DrmNode, crtc: crtc::Handle) {
output output
.surface .surface
.queue_buffer(Some(result.sync), Some(damage.clone()), presentation)?; .queue_buffer(Some(result.sync), Some(damage.clone()), presentation)?;
output output.render_metrics.record_frame(
.render_metrics render_started.elapsed(),
.record_frame(render_started.elapsed(), &damage, missed_deadlines); &damage,
missed_deadlines,
effects,
);
output.frame_pending = true; output.frame_pending = true;
Ok(true) Ok(true)
})(); })();
+30
View File
@@ -4,6 +4,12 @@ use smithay::utils::{Physical, Rectangle};
const REPORT_INTERVAL: Duration = Duration::from_secs(5); 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)] #[derive(Debug)]
pub(crate) struct RenderMetrics { pub(crate) struct RenderMetrics {
enabled: bool, enabled: bool,
@@ -13,6 +19,9 @@ pub(crate) struct RenderMetrics {
damaged_pixels: u64, damaged_pixels: u64,
total_render_time: Duration, total_render_time: Duration,
longest_render_time: Duration, longest_render_time: Duration,
blur_frames: u64,
blur_regions: u64,
blur_sample_pixels: u64,
} }
impl RenderMetrics { impl RenderMetrics {
@@ -33,6 +42,9 @@ impl RenderMetrics {
damaged_pixels: 0, damaged_pixels: 0,
total_render_time: Duration::ZERO, total_render_time: Duration::ZERO,
longest_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, render_time: Duration,
damage: &[Rectangle<i32, Physical>], damage: &[Rectangle<i32, Physical>],
missed_deadlines: u64, missed_deadlines: u64,
effects: FrameEffectMetrics,
) { ) {
if !self.enabled { if !self.enabled {
return; return;
@@ -50,6 +63,9 @@ impl RenderMetrics {
self.damaged_pixels += damage.iter().map(rectangle_area).sum::<u64>(); self.damaged_pixels += damage.iter().map(rectangle_area).sum::<u64>();
self.total_render_time += render_time; self.total_render_time += render_time;
self.longest_render_time = self.longest_render_time.max(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 now = Instant::now();
let elapsed = now.saturating_duration_since(self.interval_started); let elapsed = now.saturating_duration_since(self.interval_started);
@@ -71,6 +87,9 @@ impl RenderMetrics {
average_render_us, average_render_us,
longest_render_us = self.longest_render_time.as_micros(), longest_render_us = self.longest_render_time.as_micros(),
missed_deadlines, missed_deadlines,
blur_frames = self.blur_frames,
blur_regions = self.blur_regions,
blur_sample_pixels = self.blur_sample_pixels,
"render performance" "render performance"
); );
@@ -79,6 +98,9 @@ impl RenderMetrics {
self.damaged_pixels = 0; self.damaged_pixels = 0;
self.total_render_time = Duration::ZERO; self.total_render_time = Duration::ZERO;
self.longest_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), Duration::from_millis(2),
&[Rectangle::new((0, 0).into(), (100, 50).into())], &[Rectangle::new((0, 0).into(), (100, 50).into())],
0, 0,
FrameEffectMetrics::default(),
); );
assert_eq!(metrics.rendered_frames, 0); assert_eq!(metrics.rendered_frames, 0);
@@ -116,11 +139,18 @@ mod tests {
Rectangle::new((10, 10).into(), (20, 10).into()), Rectangle::new((10, 10).into(), (20, 10).into()),
], ],
0, 0,
FrameEffectMetrics {
blur_regions: 2,
blur_sample_pixels: 4_096,
},
); );
assert_eq!(metrics.rendered_frames, 1); assert_eq!(metrics.rendered_frames, 1);
assert_eq!(metrics.damaged_pixels, 5_200); assert_eq!(metrics.damaged_pixels, 5_200);
assert_eq!(metrics.total_render_time, Duration::from_millis(2)); assert_eq!(metrics.total_render_time, Duration::from_millis(2));
assert_eq!(metrics.longest_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);
} }
} }
+31 -7
View File
@@ -50,7 +50,10 @@ use smithay::{
wayland::{compositor::with_states, presentation::Refresh}, wayland::{compositor::with_states, presentation::Refresh},
}; };
use crate::{Ferese, metrics::RenderMetrics}; use crate::{
Ferese,
metrics::{FrameEffectMetrics, RenderMetrics},
};
type SurfaceRenderElement = CropRenderElement< type SurfaceRenderElement = CropRenderElement<
RelocateRenderElement<RescaleRenderElement<WaylandSurfaceRenderElement<GlesRenderer>>>, RelocateRenderElement<RescaleRenderElement<WaylandSurfaceRenderElement<GlesRenderer>>>,
@@ -64,7 +67,7 @@ type MemoryRenderElement = CropRenderElement<
>; >;
type PhysicalDamage = Vec<Rectangle<i32, Physical>>; type PhysicalDamage = Vec<Rectangle<i32, Physical>>;
type DamageRenderResult = Result<Option<PhysicalDamage>, Box<dyn Error>>; type DamageRenderResult = Result<(Option<PhysicalDamage>, FrameEffectMetrics), Box<dyn Error>>;
render_elements! { render_elements! {
pub(crate) AnimatedWindowRenderElement<=GlesRenderer>; pub(crate) AnimatedWindowRenderElement<=GlesRenderer>;
@@ -682,6 +685,10 @@ pub fn init(event_loop: &mut EventLoop<Ferese>, state: &mut Ferese) -> Result<()
let (renderer, mut framebuffer) = backend.bind()?; let (renderer, mut framebuffer) = backend.bind()?;
state.process_dmabuf_imports(renderer); state.process_dmabuf_imports(renderer);
let elements = animated_window_elements(state, renderer, &output); 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( let result = damage_tracker.render_output(
renderer, renderer,
&mut framebuffer, &mut framebuffer,
@@ -714,12 +721,12 @@ pub fn init(event_loop: &mut EventLoop<Ferese>, state: &mut Ferese) -> Result<()
.finish()?; .finish()?;
} }
Ok(result.damage.cloned()) Ok((result.damage.cloned(), effects))
} }
})(); })();
let damage = match rendered { let (damage, effects) = match rendered {
Ok(Some(damage)) => damage, Ok((Some(damage), effects)) => (damage, effects),
Ok(None) => { Ok((None, _)) => {
state.space.refresh(); state.space.refresh();
state.popups.cleanup(); state.popups.cleanup();
layer_map_for_output(&output).cleanup(); layer_map_for_output(&output).cleanup();
@@ -740,7 +747,7 @@ pub fn init(event_loop: &mut EventLoop<Ferese>, state: &mut Ferese) -> Result<()
state.loop_signal.stop(); state.loop_signal.stop();
return; 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); let mut presentation = OutputPresentationFeedback::new(&output);
state.space.elements().for_each(|window| { state.space.elements().for_each(|window| {
@@ -811,6 +818,23 @@ pub(crate) fn cursorless_window_elements(
output_elements(state, renderer, output, false) 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( fn output_elements(
state: &mut Ferese, state: &mut Ferese,
renderer: &mut GlesRenderer, renderer: &mut GlesRenderer,