atagen

monolith

public
Star0
Watch0

Commit df646f6621c6

tarball

atagen <boss@atagen.co>2026-10-19 09:49 UTC

9 files changed+199-6
fifo: add wp_fifo_v1 with barriers cleared on presentation

Mesa's FIFO present mode and Proton wait on wp_fifo_v1 for pacing, and without the global they fall back to frame callbacks alone. A render latches the barriers of the surfaces it shows and the output's vblank or the host frame callback clears them. A held barrier is cleared after 100 ms regardless, since hidden or covered surfaces are never latched and the protocol lets the compositor release them.
Modified file: crates/monolith/src/commit.rs+1 additions
@@ -104,6 +104,7 @@ impl CompositorHandler for State {
104104 }
105105 self.layer_commit(surface);
106106 self.send_scale_once(surface);
107+ crate::fifo::applied(self, surface);
107108 self.dirty.redraw = true;
108109 }
109110 }
Modified file: crates/monolith/src/drm.rs+11 additions
@@ -681,6 +681,14 @@ impl Drm {
681681 }
682682
683683 fn on_vblank(state: &mut State, crtc: crtc::Handle, meta: &mut Option<DrmEventMetadata>) {
684+ let presented = state
685+ .backend
686+ .drm()
687+ .and_then(|d| d.surfaces.get(&crtc))
688+ .map(|s| s.output.clone());
689+ if let Some(output) = presented {
690+ crate::fifo::presented(state, &output);
691+ }
684692 let Some(surface) = state
685693 .backend
686694 .drm_mut()
@@ -1118,6 +1126,9 @@ fn render_surface(state: &mut State, crtc: crtc::Handle, target: u64) -> bool {
11181126 }
11191127 surface.failures = if failed { surface.failures + 1 } else { 0 };
11201128 surface.redraw = animating || failed;
1129+ if !surface.queued {
1130+ crate::fifo::release_idle(&core.handle, &surface.output);
1131+ }
11211132 let elapsed = core.start.elapsed();
11221133 crate::frame::send_frame_callbacks(
11231134 wm,
Added file: crates/monolith/src/fifo.rs+165 additions
@@ -0,0 +1,165 @@
1+//! wp_fifo_v1 barriers. The first render that shows a commit holding a
2+//! barrier latches it on the output, and presenting that frame clears it.
3+
4+use crate::state::{State, now_ns};
5+use smithay::output::Output;
6+use smithay::reexports::calloop::timer::{TimeoutAction, Timer};
7+use smithay::reexports::calloop::{LoopHandle, RegistrationToken};
8+use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
9+use smithay::reexports::wayland_server::{Client, Resource, Weak};
10+use smithay::wayland::compositor::{Barrier, CompositorHandler, SurfaceData, with_states};
11+use smithay::wayland::fifo::FifoBarrierCachedState;
12+use std::sync::Mutex;
13+use std::time::Duration;
14+
15+/// How long a barrier may outlive its commit before it is cleared anyway. A
16+/// surface no render shows, hidden or covered or on a dark output, never
17+/// gets its barrier latched, and the protocol lets the compositor clear it
18+/// so the client keeps making progress.
19+const HELD_NS: u64 = 100_000_000;
20+const SWEEP: Duration = Duration::from_millis(50);
21+
22+#[derive(Default)]
23+pub struct Fifo {
24+ held: Vec<(Weak<WlSurface>, u64)>,
25+ sweeping: Option<RegistrationToken>,
26+}
27+
28+#[derive(Default)]
29+struct Latched(Mutex<Vec<(Barrier, Client)>>);
30+
31+/// The primary scanout choice for frame callbacks on `output`, which also
32+/// latches the barriers of every surface it visits.
33+pub fn scanout(
34+ output: &Output,
35+) -> impl FnMut(&WlSurface, &SurfaceData) -> Option<Output> + Copy + '_ {
36+ move |surface, states| {
37+ latch(output, surface, states);
38+ Some(output.clone())
39+ }
40+}
41+
42+fn latch(output: &Output, surface: &WlSurface, states: &SurfaceData) {
43+ if !states.cached_state.has::<FifoBarrierCachedState>() {
44+ return;
45+ }
46+ let barrier = states
47+ .cached_state
48+ .get::<FifoBarrierCachedState>()
49+ .current()
50+ .barrier
51+ .clone()
52+ .filter(|b| !b.is_signaled());
53+ let (Some(barrier), Some(client)) = (barrier, surface.client()) else {
54+ return;
55+ };
56+ let latched = output
57+ .user_data()
58+ .get_or_insert_threadsafe(Latched::default);
59+ let mut held = latched.0.lock().unwrap();
60+ held.retain(|(b, _)| !b.is_signaled());
61+ if !held.iter().any(|(b, _)| *b == barrier) {
62+ held.push((barrier, client));
63+ }
64+}
65+
66+/// Clears what the last frame on `output` latched.
67+pub fn presented(state: &mut State, output: &Output) {
68+ let Some(latched) = output.user_data().get::<Latched>() else {
69+ return;
70+ };
71+ let released = std::mem::take(&mut *latched.0.lock().unwrap());
72+ unblock(
73+ state,
74+ released.into_iter().map(|(barrier, client)| {
75+ barrier.signal();
76+ client
77+ }),
78+ );
79+}
80+
81+/// For a render that queued nothing, so no flip will report it presented.
82+pub fn release_idle(handle: &LoopHandle<'static, State>, output: &Output) {
83+ let output = output.clone();
84+ handle.insert_idle(move |state| presented(state, &output));
85+}
86+
87+/// Lets the commits of `clients` that were waiting on a signaled blocker apply.
88+pub fn unblock(state: &mut State, clients: impl IntoIterator<Item = Client>) {
89+ let dh = state.core.display.clone();
90+ let mut done = Vec::new();
91+ for client in clients {
92+ if done.contains(&client.id()) {
93+ continue;
94+ }
95+ done.push(client.id());
96+ state
97+ .client_compositor_state(&client)
98+ .blocker_cleared(state, &dh);
99+ }
100+}
101+
102+/// Starts the clock on a barrier a commit just made current.
103+pub fn applied(state: &mut State, surface: &WlSurface) {
104+ let held = with_states(surface, |s| {
105+ s.cached_state.has::<FifoBarrierCachedState>()
106+ && s.cached_state
107+ .get::<FifoBarrierCachedState>()
108+ .current()
109+ .barrier
110+ .as_ref()
111+ .is_some_and(|b| !b.is_signaled())
112+ });
113+ if !held {
114+ return;
115+ }
116+ let now = now_ns();
117+ let fifo = &mut state.services.fifo;
118+ match fifo.held.iter_mut().find(|(s, _)| s == surface) {
119+ Some((_, since)) => *since = now,
120+ None => fifo.held.push((surface.downgrade(), now)),
121+ }
122+ if fifo.sweeping.is_none() {
123+ match state
124+ .core
125+ .handle
126+ .insert_source(Timer::from_duration(SWEEP), |_, _, state| sweep(state))
127+ {
128+ Ok(token) => fifo.sweeping = Some(token),
129+ Err(e) => tracing::warn!("could not arm the fifo sweep: {e}"),
130+ }
131+ }
132+}
133+
134+fn sweep(state: &mut State) -> TimeoutAction {
135+ let now = now_ns();
136+ let mut clients = Vec::new();
137+ state.services.fifo.held.retain(|(weak, since)| {
138+ let Ok(surface) = weak.upgrade() else {
139+ return false;
140+ };
141+ with_states(&surface, |s| {
142+ let barrier = s
143+ .cached_state
144+ .get::<FifoBarrierCachedState>()
145+ .current()
146+ .barrier
147+ .clone();
148+ let Some(barrier) = barrier.filter(|b| !b.is_signaled()) else {
149+ return false;
150+ };
151+ if now.saturating_sub(*since) < HELD_NS {
152+ return true;
153+ }
154+ barrier.signal();
155+ clients.extend(surface.client());
156+ false
157+ })
158+ });
159+ unblock(state, clients);
160+ if state.services.fifo.held.is_empty() {
161+ state.services.fifo.sweeping = None;
162+ return TimeoutAction::Drop;
163+ }
164+ TimeoutAction::ToDuration(SWEEP)
165+}
Modified file: crates/monolith/src/frame.rs+6 additions−3 deletions
@@ -109,9 +109,12 @@ pub fn send_frame_callbacks(
109109 }
110110 let (windows, covered) = shown_windows(wm, o, output, now);
111111 for window in windows {
112- window.send_frame(output, elapsed, Some(Duration::ZERO), |_, _| {
113- Some(output.clone())
114- });
112+ window.send_frame(
113+ output,
114+ elapsed,
115+ Some(Duration::ZERO),
116+ crate::fifo::scanout(output),
117+ );
115118 }
116119 crate::layers::send_frames(output, elapsed, covered);
117120 if let Some(geo) = wm.space.output_geometry(output) {
Modified file: crates/monolith/src/globals.rs+3 additions
@@ -13,6 +13,7 @@ use smithay::utils::{ClockSource, Monotonic};
1313 use smithay::wayland::compositor::CompositorState;
1414 use smithay::wayland::cursor_shape::CursorShapeManagerState;
1515 use smithay::wayland::dmabuf::{DmabufFeedbackBuilder, DmabufGlobal, DmabufState};
16+use smithay::wayland::fifo::FifoManagerState;
1617 use smithay::wayland::foreign_toplevel_list::ForeignToplevelListState;
1718 use smithay::wayland::fractional_scale::FractionalScaleManagerState;
1819 use smithay::wayland::idle_inhibit::IdleInhibitManagerState;
@@ -76,6 +77,7 @@ pub struct Protocols {
7677 _presentation: PresentationState,
7778 _relative_pointer: RelativePointerManagerState,
7879 _pointer_constraints: PointerConstraintsState,
80+ _fifo: FifoManagerState,
7981 }
8082
8183 impl Protocols {
@@ -143,6 +145,7 @@ impl Protocols {
143145 _presentation: PresentationState::new::<State>(dh, Monotonic::ID as u32),
144146 _relative_pointer: RelativePointerManagerState::new::<State>(dh),
145147 _pointer_constraints: PointerConstraintsState::new::<State>(dh),
148+ _fifo: FifoManagerState::new::<State>(dh),
146149 }
147150 }
148151 }
Modified file: crates/monolith/src/layers.rs+6 additions−3 deletions
@@ -303,9 +303,12 @@ pub fn send_frames(output: &Output, elapsed: Duration, covered: bool) {
303303 if covered && l.layer() != Layer::Overlay {
304304 continue;
305305 }
306- l.send_frame(output, elapsed, Some(Duration::ZERO), |_, _| {
307- Some(output.clone())
308- });
306+ l.send_frame(
307+ output,
308+ elapsed,
309+ Some(Duration::ZERO),
310+ crate::fifo::scanout(output),
311+ );
309312 }
310313 }
311314
Modified file: crates/monolith/src/main.rs+1 additions
@@ -8,6 +8,7 @@ mod constraints;
88 mod cursor;
99 mod devices;
1010 mod drm;
11+mod fifo;
1112 mod focus;
1213 mod frame;
1314 mod globals;
Modified file: crates/monolith/src/nested/mod.rs+4 additions
@@ -329,6 +329,8 @@ pub fn render(state: &mut State) {
329329 host.surface.commit();
330330 host.frame_pending = true;
331331 let _ = host.conn.flush();
332+ } else {
333+ crate::fifo::release_idle(&core.handle, &host.output);
332334 }
333335 return;
334336 };
@@ -554,6 +556,8 @@ impl Dispatch<wl_callback::WlCallback, ()> for State {
554556 Kind::empty(),
555557 );
556558 }
559+ let output = host.output.clone();
560+ crate::fifo::presented(state, &output);
557561 }
558562 }
559563 }
Modified file: crates/monolith/src/state.rs+2 additions
@@ -100,6 +100,7 @@ pub struct Services {
100100 pub idle: crate::idle::Idle,
101101 pub power: crate::power::Power,
102102 pub commits: crate::stats::CommitStats,
103+ pub fifo: crate::fifo::Fifo,
103104 }
104105
105106 /// The config and what is derived from it, swapped whole on reload.
@@ -220,6 +221,7 @@ impl State {
220221 idle,
221222 power: Default::default(),
222223 commits: Default::default(),
224+ fifo: Default::default(),
223225 },
224226 })
225227 }