atagen

monolith

public
Star0
Watch0

Commit e4b186dffb77

tarball

atagen <boss@atagen.co>2026-10-16 13:08 UTC

3 files changed+83-2
monolith: hold dmabuf commits until their buffers are ready

Clients get linux-drm-syncobj on GPUs whose syncobjs signal eventfds, and a commit with an acquire point is held on the event loop until that point signals. Implicitly synced dmabuf commits are held until their pending writes finish. Either way the renderer never samples a buffer the client GPU is still writing, and never waits on it, which sidesteps NVIDIA lacking timeline semaphore export to syncobjs.
Modified file: crates/monolith/src/drm.rs+6 additions
@@ -30,6 +30,7 @@ use smithay::reexports::drm::control::{ModeTypeFlags, connector, crtc};
3030 use smithay::reexports::input::Libinput;
3131 use smithay::reexports::rustix::fs::OFlags;
3232 use smithay::utils::{DeviceFd, Point, Transform};
33+use smithay::wayland::drm_syncobj::{DrmSyncobjState, supports_syncobj_eventfd};
3334 use smithay_drm_extras::drm_scanner::{DrmScanEvent, DrmScanner};
3435 use std::collections::{HashMap, VecDeque};
3536 use std::path::{Path, PathBuf};
@@ -158,6 +159,11 @@ pub fn init(
158159 .get_driver()
159160 .map(|d| d.name().to_string_lossy().to_lowercase().contains("nvidia"))
160161 .unwrap_or(false);
162+ if supports_syncobj_eventfd(&fd) {
163+ state.syncobj = Some(DrmSyncobjState::new::<State>(&state.display, fd.clone()));
164+ } else {
165+ tracing::info!("no syncobj eventfd support, clients stay on implicit sync");
166+ }
161167 let gbm = GbmDevice::new(fd)?;
162168 let render_node = node
163169 .node_with_type(NodeType::Render)
Modified file: crates/monolith/src/handlers.rs+75 additions−2 deletions
@@ -6,6 +6,7 @@ use smithay::desktop::{PopupKind, Window};
66 use smithay::input::dnd::{DndGrabHandler, GrabType, Source};
77 use smithay::input::pointer::CursorImageStatus;
88 use smithay::input::{Seat, SeatHandler, SeatState};
9+use smithay::reexports::calloop::Interest;
910 use smithay::reexports::wayland_server::protocol::wl_buffer::WlBuffer;
1011 use smithay::reexports::wayland_protocols::xdg::decoration::zv1::server::zxdg_toplevel_decoration_v1::Mode as DecorationMode;
1112 use smithay::reexports::wayland_server::protocol::wl_output::WlOutput;
@@ -15,9 +16,14 @@ use smithay::reexports::wayland_server::{Client, Resource};
1516 use smithay::utils::Serial;
1617 use smithay::wayland::buffer::BufferHandler;
1718 use smithay::wayland::compositor::{
18- CompositorClientState, CompositorHandler, CompositorState, get_parent, is_sync_subsurface,
19+ BufferAssignment, CompositorClientState, CompositorHandler, CompositorState,
20+ SurfaceAttributes, add_blocker, add_pre_commit_hook, get_parent, is_sync_subsurface,
21+ with_states,
22+};
23+use smithay::wayland::drm_syncobj::{DrmSyncobjCachedState, DrmSyncobjHandler, DrmSyncobjState};
24+use smithay::wayland::dmabuf::{
25+ DmabufGlobal, DmabufHandler, DmabufState, ImportNotifier, get_dmabuf,
1926 };
20-use smithay::wayland::dmabuf::{DmabufGlobal, DmabufHandler, DmabufState, ImportNotifier};
2127 use smithay::wayland::output::OutputHandler;
2228 use smithay::wayland::selection::SelectionHandler;
2329 use smithay::wayland::selection::data_device::{
@@ -35,6 +41,12 @@ impl CompositorHandler for State {
3541 &mut self.compositor_state
3642 }
3743
44+ fn new_surface(&mut self, surface: &WlSurface) {
45+ add_pre_commit_hook::<Self, _>(surface, |state, _dh, surface| {
46+ hold_until_readable(state, surface);
47+ });
48+ }
49+
3850 fn client_compositor_state<'a>(&self, client: &'a Client) -> &'a CompositorClientState {
3951 &client
4052 .get_data::<ClientState>()
@@ -218,6 +230,67 @@ impl smithay::wayland::pointer_constraints::PointerConstraintsHandler for State
218230
219231 smithay::delegate_dispatch2!(State);
220232
233+impl DrmSyncobjHandler for State {
234+ fn drm_syncobj_state(&mut self) -> Option<&mut DrmSyncobjState> {
235+ self.syncobj.as_mut()
236+ }
237+}
238+
239+/// Holds a dmabuf commit until the buffer can be read without the renderer
240+/// stalling on the client's GPU work: until its explicit acquire point
241+/// signals, or for implicitly synced buffers until their writes finish.
242+fn hold_until_readable(state: &mut State, surface: &WlSurface) {
243+ let (acquire, dmabuf) = with_states(surface, |s| {
244+ let acquire = s
245+ .cached_state
246+ .get::<DrmSyncobjCachedState>()
247+ .pending()
248+ .acquire_point
249+ .clone();
250+ let dmabuf = match s
251+ .cached_state
252+ .get::<SurfaceAttributes>()
253+ .pending()
254+ .buffer
255+ .as_ref()
256+ {
257+ Some(BufferAssignment::NewBuffer(buffer)) => get_dmabuf(buffer).cloned().ok(),
258+ _ => None,
259+ };
260+ (acquire, dmabuf)
261+ });
262+ let (Some(dmabuf), Some(client)) = (dmabuf, surface.client()) else {
263+ return;
264+ };
265+ if let Some(point) = acquire
266+ && let Ok((blocker, source)) = point.generate_blocker()
267+ {
268+ let inserted = state.handle.insert_source(source, move |_, _, state| {
269+ let dh = state.display.clone();
270+ state
271+ .client_compositor_state(&client)
272+ .blocker_cleared(state, &dh);
273+ Ok(())
274+ });
275+ if inserted.is_ok() {
276+ add_blocker(surface, blocker);
277+ }
278+ return;
279+ }
280+ if let Ok((blocker, source)) = dmabuf.generate_blocker(Interest::READ) {
281+ let inserted = state.handle.insert_source(source, move |_, _, state| {
282+ let dh = state.display.clone();
283+ state
284+ .client_compositor_state(&client)
285+ .blocker_cleared(state, &dh);
286+ Ok(())
287+ });
288+ if inserted.is_ok() {
289+ add_blocker(surface, blocker);
290+ }
291+ }
292+}
293+
221294 impl XdgDecorationHandler for State {
222295 fn new_decoration(&mut self, toplevel: ToplevelSurface) {
223296 server_side(&toplevel);
Modified file: crates/monolith/src/state.rs+2 additions
@@ -46,6 +46,7 @@ pub struct State {
4646 pub popups: PopupManager,
4747 pub host: Option<Host>,
4848 pub drm: Option<crate::drm::Drm>,
49+ pub syncobj: Option<smithay::wayland::drm_syncobj::DrmSyncobjState>,
4950 pub cursor: crate::cursor::Cursor,
5051 pub cursor_status: CursorImageStatus,
5152 pub needs_redraw: bool,
@@ -145,6 +146,7 @@ impl State {
145146 popups: PopupManager::default(),
146147 host: None,
147148 drm: None,
149+ syncobj: None,
148150 cursor: crate::cursor::Cursor::load(),
149151 cursor_status: CursorImageStatus::default_named(),
150152 needs_redraw: true,