atagen

monolith

public
Star0
Watch0

Commit 51675d61a5d0

tarball

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

1 file changed+31-3
state: submit idle uploads on a timer and trim the heap after flush_clients

Uploads queued for a frame stayed pinned in staging whenever none came, as with DPMS off, a VT switch or direct scanout. A 100 ms timer now submits them if no frame has. malloc_trim ran before flush_clients, so the few milliseconds it takes when it fires delayed client events.
Modified file: crates/monolith/src/state.rs+31 additions−3 deletions
@@ -15,6 +15,7 @@ use smithay::input::pointer::CursorImageStatus;
1515 use smithay::input::{Seat, SeatState};
1616 use smithay::reexports::calloop::LoopHandle;
1717 use smithay::reexports::calloop::generic::Generic;
18+use smithay::reexports::calloop::timer::{TimeoutAction, Timer};
1819 use smithay::reexports::calloop::{EventLoop, Interest, LoopSignal, Mode, PostAction};
1920 use smithay::reexports::wayland_server::backend::{ClientData, ClientId, DisconnectReason};
2021 use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
@@ -242,18 +243,41 @@ impl State {
242243 }
243244 self.wm.prune(now_ns());
244245 let _ = self.renderer.cleanup_texture_cache();
245- if TRIM_HEAP.swap(false, Ordering::Relaxed) {
246- trim_heap();
247- }
248246 self.confirm_lock();
249247 self.complete_captures();
250248 self.exit_if_gpu_lost();
249+ self.flush_idle_uploads();
251250 if std::mem::take(&mut self.dirty.snapshot) {
252251 crate::ipc::broadcast(self);
253252 self.publish_workspaces();
254253 self.publish_toplevels();
255254 }
256255 let _ = self.core.display.flush_clients();
256+ if TRIM_HEAP.swap(false, Ordering::Relaxed) {
257+ trim_heap();
258+ }
259+ }
260+
261+ /// Pixels staged for a frame sit in host-visible memory until one is
262+ /// drawn. With the outputs off, on another VT or scanning a client out
263+ /// directly none comes, so a timer submits them.
264+ fn flush_idle_uploads(&mut self) {
265+ if !self.renderer.has_queued_uploads() || UPLOAD_FLUSH_ARMED.swap(true, Ordering::Relaxed) {
266+ return;
267+ }
268+ let armed =
269+ self.core
270+ .handle
271+ .insert_source(Timer::from_duration(UPLOAD_IDLE), |_, _, state| {
272+ UPLOAD_FLUSH_ARMED.store(false, Ordering::Relaxed);
273+ if let Err(e) = state.renderer.flush_uploads() {
274+ tracing::debug!("cannot flush idle uploads: {e}");
275+ }
276+ TimeoutAction::Drop
277+ });
278+ if armed.is_err() {
279+ UPLOAD_FLUSH_ARMED.store(false, Ordering::Relaxed);
280+ }
257281 }
258282
259283 /// A lost or hung GPU cannot be recovered in place, so the loop stops the
@@ -581,6 +605,10 @@ impl ClientData for ClientState {
581605 }
582606 }
583607
608+const UPLOAD_IDLE: Duration = Duration::from_millis(100);
609+
610+static UPLOAD_FLUSH_ARMED: AtomicBool = AtomicBool::new(false);
611+
584612 /// Set at startup and whenever a client leaves, the two points that free a
585613 /// burst of heap.
586614 static TRIM_HEAP: AtomicBool = AtomicBool::new(true);