Commit ba17141501b4
tarball8 files changed+84-7
@@ -500,11 +500,14 @@ fn on_vblank(state: &mut State, crtc: crtc::Handle, meta: &mut Option<DrmEventMe
500500
surface.presented += 1;501501
let window = t.saturating_sub(surface.stats_since);502502
if window >= 1_000_000_000 {503
- let vblanks = window / surface.refresh_ns.max(1);503
+ // Under VRR the panel waits for each flip, so the nominal refresh504
+ // says nothing about how many vblanks there were.505
+ let vblanks = (!surface.vrr_on).then(|| window / surface.refresh_ns.max(1));504506
tracing::debug!(505507
output = surface.output.name(),506508
presented = surface.presented,507509
vblanks,510
+ vrr = surface.vrr_on,508511
budget_us = surface.budget.ns() / 1000,509512
"frame rate"510513
);@@ -715,7 +718,7 @@ fn render_surface(state: &mut State, crtc: crtc::Handle, target: u64) -> bool {
715718
}716719
surface.redraw = animating;717720
let elapsed = core.start.elapsed();718
- crate::frame::send_frame_callbacks(wm, lock, &surface.output, elapsed);721
+ crate::frame::send_frame_callbacks(wm, lock, surface.id, &surface.output, now, elapsed);719722
animating && !surface.queued720723
}721724
@@ -60,16 +60,35 @@ pub fn compose(
6060
elements6161
}6262
63
-/// Lets clients shown on `output` draw their next frame.64
-pub fn send_frame_callbacks(wm: &Wm, lock: &Lock, output: &Output, elapsed: Duration) {63
+/// Lets clients shown on `output` draw their next frame. Under a fullscreen64
+/// window at rest only it and the overlay layer are shown, and anything65
+/// else that kept drawing would hold the output, or a VRR panel, at full66
+/// refresh for frames nobody sees.67
+pub fn send_frame_callbacks(68
+ wm: &Wm,69
+ lock: &Lock,70
+ o: OutputId,71
+ output: &Output,72
+ now: u64,73
+ elapsed: Duration,74
+) {6575
if lock.active() {6676
crate::lock::send_frames(lock, output, elapsed);6777
return;6878
}79
+ let covering = wm80
+ .layout81
+ .arrange(o)82
+ .fullscreen83
+ .filter(|&w| wm.anim.gliding(w, now).is_none())84
+ .and_then(|w| wm.window(w));6985
for window in wm.space.elements_for_output(output) {86
+ if covering.is_some_and(|c| c != window) {87
+ continue;88
+ }7089
window.send_frame(output, elapsed, Some(Duration::ZERO), |_, _| {7190
Some(output.clone())7291
});7392
}74
- crate::layers::send_frames(output, elapsed);93
+ crate::layers::send_frames(output, elapsed, covering.is_some());7594
}@@ -83,6 +83,7 @@ impl CompositorHandler for State {
8383
if let Some(window) = window {8484
window.on_commit();8585
}86
+ self.count_commit(&root);8687
if let Some(id) = self.window_id(&root) {8788
self.refresh_identity(id);8889
if self.services.captures.wants_damage() {@@ -210,8 +210,13 @@ pub fn elements(out: &mut Vec<Draw>, renderer: &mut VulkanRenderer, output: &Out
210210
}211211
}212212
213
-pub fn send_frames(output: &Output, elapsed: Duration) {213
+/// With `covered`, a fullscreen window hides everything below the overlay214
+/// layer, so only overlay surfaces are asked to draw.215
+pub fn send_frames(output: &Output, elapsed: Duration, covered: bool) {214216
for l in layer_map_for_output(output).layers() {217
+ if covered && l.layer() != Layer::Overlay {218
+ continue;219
+ }215220
l.send_frame(output, elapsed, Some(Duration::ZERO), |_, _| {216221
Some(output.clone())217222
});@@ -19,6 +19,7 @@ mod lock;
1919
mod nested;2020
mod scene;2121
mod state;22
+mod stats;2223
mod toplevels;2324
mod wm;2425
mod xwayland;@@ -259,7 +259,7 @@ pub fn render(state: &mut State) {
259259
dirty.redraw = wm.anim.active(now);260260
lock.shown(&host.output);261261
let elapsed = core.start.elapsed();262
- crate::frame::send_frame_callbacks(wm, lock, &host.output, elapsed);262
+ crate::frame::send_frame_callbacks(wm, lock, host.output_id, &host.output, now, elapsed);263263
if damage.is_some() {264264
captures.output_damaged(&host.output);265265
}@@ -85,6 +85,7 @@ pub struct Services {
8585
pub launches: crate::launch::Launches,8686
pub ipc: crate::ipc::Server,8787
pub idle: crate::idle::Idle,88
+ pub commits: crate::stats::CommitStats,8889
}8990
9091
/// The config and what is derived from it, swapped whole on reload.@@ -183,6 +184,7 @@ impl State {
183184
launches: Default::default(),184185
ipc: Default::default(),185186
idle,187
+ commits: Default::default(),186188
},187189
})188190
}@@ -0,0 +1,46 @@
1
+//! Which clients commit most, logged once a second at debug level. Every2
+//! visible commit costs a frame, so this names whoever keeps an idle3
+//! desktop, or a VRR panel, at full refresh.4
+5
+use crate::state::{State, now_ns};6
+use smithay::desktop::{WindowSurfaceType, layer_map_for_output};7
+use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;8
+use std::collections::HashMap;9
+10
+#[derive(Default)]11
+pub struct CommitStats {12
+ counts: HashMap<String, u32>,13
+ since: u64,14
+}15
+16
+impl State {17
+ /// Counts a commit on `root` against the window or layer it belongs to.18
+ pub fn count_commit(&mut self, root: &WlSurface) {19
+ if !tracing::enabled!(tracing::Level::DEBUG) {20
+ return;21
+ }22
+ let label = match self.wm.id_of(root).and_then(|id| self.wm.get(id)) {23
+ Some(w) => w.app_id.clone(),24
+ None => self25
+ .wm26
+ .space27
+ .outputs()28
+ .find_map(|o| {29
+ let map = layer_map_for_output(o);30
+ let layer = map.layer_for_surface(root, WindowSurfaceType::TOPLEVEL)?;31
+ Some(format!("layer:{}", layer.namespace()))32
+ })33
+ .unwrap_or_else(|| "other".into()),34
+ };35
+ let stats = &mut self.services.commits;36
+ *stats.counts.entry(label).or_default() += 1;37
+ let now = now_ns();38
+ if now.saturating_sub(stats.since) >= 1_000_000_000 {39
+ let mut top: Vec<(String, u32)> = stats.counts.drain().collect();40
+ top.sort_by_key(|e| std::cmp::Reverse(e.1));41
+ top.truncate(3);42
+ tracing::debug!(?top, "commits per second");43
+ stats.since = now;44
+ }45
+ }46
+}