atagen

monolith

public
Star0
Watch0

Commit 8956d017e879

tarball

atagen <boss@atagen.co>2026-10-16 23:18 UTC

6 files changed+175-7
monolith: import dmabufs on a worker thread

A first dmabuf import takes 15 to 27 ms on NVIDIA and used to run on the main thread at buffer creation, dropping frames whenever a client allocated its swapchain. linux-dmabuf now answers once the worker is done, and a commit of a buffer still importing waits on a blocker instead of importing inline.
Modified file: crates/monolith/src/handlers.rs+4 additions−6 deletions
@@ -1,6 +1,5 @@
11 use crate::state::{ClientState, State};
22 use smithay::backend::allocator::dmabuf::Dmabuf;
3-use smithay::backend::renderer::ImportDma;
43 use smithay::backend::renderer::utils::{import_surface_tree, on_commit_buffer_handler};
54 use smithay::desktop::{PopupKind, Window};
65 use smithay::input::dnd::{DndGrabHandler, GrabType, Source};
@@ -127,11 +126,7 @@ impl DmabufHandler for State {
127126 dmabuf: Dmabuf,
128127 notifier: ImportNotifier,
129128 ) {
130- if self.renderer.import_dmabuf(&dmabuf, None).is_ok() {
131- let _ = notifier.successful::<State>();
132- } else {
133- notifier.failed();
134- }
129+ self.import_for(&dmabuf, notifier);
135130 }
136131 }
137132
@@ -300,6 +295,9 @@ fn hold_until_readable(state: &mut State, surface: &WlSurface) {
300295 let (Some(dmabuf), Some(client)) = (dmabuf, surface.client()) else {
301296 return;
302297 };
298+ if let Some(blocker) = state.import_blocker(&dmabuf, client.clone()) {
299+ add_blocker(surface, blocker);
300+ }
303301 if let Some(point) = acquire
304302 && let Ok((blocker, source)) = point.generate_blocker()
305303 {
Added file: crates/monolith/src/import.rs+133 additions
@@ -0,0 +1,133 @@
1+//! Dmabuf imports on a worker thread. A first import takes 15 to 27 ms on
2+//! NVIDIA, which on the main thread drops frames. Buffer creation answers
3+//! once the import is done, and commits of buffers still importing wait on
4+//! a blocker.
5+
6+use crate::state::State;
7+use monolith_render_vk::{DmabufImporter, VulkanTexture};
8+use smithay::backend::allocator::dmabuf::{Dmabuf, WeakDmabuf};
9+use smithay::reexports::calloop::LoopHandle;
10+use smithay::reexports::calloop::channel::{Event, channel};
11+use smithay::reexports::wayland_server::Client;
12+use smithay::wayland::compositor::{Blocker, BlockerState, CompositorHandler};
13+use smithay::wayland::dmabuf::ImportNotifier;
14+use std::collections::HashMap;
15+use std::sync::Arc;
16+use std::sync::atomic::{AtomicBool, Ordering};
17+use std::sync::mpsc;
18+
19+pub struct Imports {
20+ jobs: mpsc::Sender<Dmabuf>,
21+ waiting: HashMap<WeakDmabuf, Waiters>,
22+}
23+
24+#[derive(Default)]
25+struct Waiters {
26+ done: Arc<AtomicBool>,
27+ notifiers: Vec<ImportNotifier>,
28+ clients: Vec<Client>,
29+}
30+
31+pub struct ImportBlocker(Arc<AtomicBool>);
32+
33+impl Blocker for ImportBlocker {
34+ fn state(&self) -> BlockerState {
35+ if self.0.load(Ordering::Acquire) {
36+ BlockerState::Released
37+ } else {
38+ BlockerState::Pending
39+ }
40+ }
41+}
42+
43+type Done = (Dmabuf, Result<VulkanTexture, String>);
44+
45+impl Imports {
46+ pub fn new(
47+ handle: &LoopHandle<'static, State>,
48+ importer: DmabufImporter,
49+ ) -> std::io::Result<Self> {
50+ let (done, results) = channel::<Done>();
51+ let (jobs, queue) = mpsc::channel::<Dmabuf>();
52+ std::thread::Builder::new()
53+ .name("dmabuf-import".into())
54+ .spawn(move || {
55+ for buffer in queue {
56+ let result = importer.import(&buffer).map_err(|e| e.to_string());
57+ if done.send((buffer, result)).is_err() {
58+ break;
59+ }
60+ }
61+ })?;
62+ handle
63+ .insert_source(results, |event, _, state| {
64+ if let Event::Msg((buffer, result)) = event {
65+ state.imported(buffer, result);
66+ }
67+ })
68+ .map_err(|e| std::io::Error::other(e.to_string()))?;
69+ Ok(Self {
70+ jobs,
71+ waiting: HashMap::new(),
72+ })
73+ }
74+
75+ fn start(&mut self, buffer: &Dmabuf) -> &mut Waiters {
76+ let jobs = &self.jobs;
77+ self.waiting.entry(buffer.weak()).or_insert_with(|| {
78+ let _ = jobs.send(buffer.clone());
79+ Waiters::default()
80+ })
81+ }
82+}
83+
84+impl State {
85+ /// Answers a client's buffer creation once its dmabuf is imported.
86+ pub fn import_for(&mut self, buffer: &Dmabuf, notifier: ImportNotifier) {
87+ if self.renderer.has_dmabuf(buffer) {
88+ let _ = notifier.successful::<State>();
89+ return;
90+ }
91+ self.imports.start(buffer).notifiers.push(notifier);
92+ }
93+
94+ /// A blocker for a commit whose dmabuf the renderer has not imported yet.
95+ pub fn import_blocker(&mut self, buffer: &Dmabuf, client: Client) -> Option<ImportBlocker> {
96+ if self.renderer.has_dmabuf(buffer) {
97+ return None;
98+ }
99+ let waiters = self.imports.start(buffer);
100+ waiters.clients.push(client);
101+ Some(ImportBlocker(waiters.done.clone()))
102+ }
103+
104+ fn imported(&mut self, buffer: Dmabuf, result: Result<VulkanTexture, String>) {
105+ let Some(waiters) = self.imports.waiting.remove(&buffer.weak()) else {
106+ return;
107+ };
108+ let result = result.and_then(|t| {
109+ self.renderer
110+ .adopt_dmabuf(&buffer, t)
111+ .map_err(|e| e.to_string())
112+ });
113+ match result {
114+ Ok(()) => {
115+ for n in waiters.notifiers {
116+ let _ = n.successful::<State>();
117+ }
118+ }
119+ Err(e) => {
120+ tracing::warn!("dmabuf import failed: {e}");
121+ for n in waiters.notifiers {
122+ n.failed();
123+ }
124+ }
125+ }
126+ waiters.done.store(true, Ordering::Release);
127+ let dh = self.display.clone();
128+ for client in waiters.clients {
129+ self.client_compositor_state(&client)
130+ .blocker_cleared(self, &dh);
131+ }
132+ }
133+}
Modified file: crates/monolith/src/main.rs+1 additions
@@ -7,6 +7,7 @@ mod drm;
77 mod focus;
88 mod handlers;
99 mod idle;
10+mod import;
1011 mod input;
1112 mod ipc;
1213 mod launch;
Modified file: crates/monolith/src/state.rs+3 additions
@@ -97,6 +97,7 @@ pub struct State {
9797 pub xwayland: Option<crate::xwayland::Xwayland>,
9898 pub xwayland_shell_state: XWaylandShellState,
9999 pub captures: crate::capture::Captures,
100+ pub imports: crate::import::Imports,
100101 /// The render node clients get dmabufs for, known on bare metal.
101102 pub render_node: Option<smithay::backend::drm::DrmNode>,
102103 pub lock: crate::lock::Lock,
@@ -177,6 +178,7 @@ impl State {
177178 let binds = Binds::new(&config);
178179 let animation = config.animation.clone();
179180 let primary_selection_state = PrimarySelectionState::new::<State>(&dh);
181+ let imports = crate::import::Imports::new(&handle, renderer.dmabuf_importer())?;
180182 Ok(Self {
181183 start: Instant::now(),
182184 socket_name,
@@ -214,6 +216,7 @@ impl State {
214216 xwayland: None,
215217 xwayland_shell_state: XWaylandShellState::new::<State>(&dh),
216218 captures: Default::default(),
219+ imports,
217220 render_node: None,
218221 lock: Default::default(),
219222 inhibitors: HashSet::new(),
Modified file: crates/render-vk/src/lib.rs+1 additions−1 deletions
@@ -18,4 +18,4 @@ pub use dmabuf::VulkanAllocator;
1818 pub use error::Error;
1919 pub use frame::{Effect, Transition, VulkanFrame};
2020 pub use image::{VulkanImage, VulkanMapping, VulkanTarget, VulkanTexture};
21-pub use renderer::VulkanRenderer;
21+pub use renderer::{DmabufImporter, VulkanRenderer};
Modified file: crates/render-vk/src/renderer.rs+33 additions
@@ -975,6 +975,39 @@ impl ImportDma for VulkanRenderer {
975975
976976 impl ImportDmaWl for VulkanRenderer {}
977977
978+/// Imports dmabufs on another thread. The textures it makes belong to the
979+/// renderer it came from, which takes them back with `adopt_dmabuf`.
980+#[derive(Clone)]
981+pub struct DmabufImporter {
982+ shared: Arc<Shared>,
983+ caps: Arc<DmabufCaps>,
984+}
985+
986+impl DmabufImporter {
987+ pub fn import(&self, buffer: &Dmabuf) -> Result<VulkanTexture, Error> {
988+ dmabuf::import(&self.shared, &self.caps, buffer, SAMPLE_USAGE).map(VulkanTexture)
989+ }
990+}
991+
992+impl VulkanRenderer {
993+ pub fn dmabuf_importer(&self) -> DmabufImporter {
994+ DmabufImporter {
995+ shared: self.shared.clone(),
996+ caps: self.caps.clone(),
997+ }
998+ }
999+
1000+ pub fn has_dmabuf(&self, buffer: &Dmabuf) -> bool {
1001+ self.dmabuf_textures.contains_key(&buffer.weak())
1002+ }
1003+
1004+ pub fn adopt_dmabuf(&mut self, buffer: &Dmabuf, texture: VulkanTexture) -> Result<(), Error> {
1005+ self.own(&texture.0)?;
1006+ self.dmabuf_textures.insert(buffer.weak(), texture);
1007+ Ok(())
1008+ }
1009+}
1010+
9781011 impl ExportMem for VulkanRenderer {
9791012 type TextureMapping = VulkanMapping;