Commit df646f6621c6
tarball9 files changed+199-6
@@ -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
}@@ -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 = state685
+ .backend686
+ .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) = state685693
.backend686694
.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,@@ -0,0 +1,165 @@
1
+//! wp_fifo_v1 barriers. The first render that shows a commit holding a2
+//! 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. A16
+/// surface no render shows, hidden or covered or on a dark output, never17
+/// gets its barrier latched, and the protocol lets the compositor clear it18
+/// 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 also32
+/// 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 = states47
+ .cached_state48
+ .get::<FifoBarrierCachedState>()49
+ .current()50
+ .barrier51
+ .clone()52
+ .filter(|b| !b.is_signaled());53
+ let (Some(barrier), Some(client)) = (barrier, surface.client()) else {54
+ return;55
+ };56
+ let latched = output57
+ .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
+ client77
+ }),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
+ state97
+ .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_state107
+ .get::<FifoBarrierCachedState>()108
+ .current()109
+ .barrier110
+ .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 state124
+ .core125
+ .handle126
+ .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 = s143
+ .cached_state144
+ .get::<FifoBarrierCachedState>()145
+ .current()146
+ .barrier147
+ .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
+ false157
+ })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
+}@@ -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) {@@ -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
}@@ -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
@@ -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;@@ -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
}@@ -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
}