atagen

monolith

public
Star0
Watch0

Commit 5cd954fce34b

tarball

atagen <boss@atagen.co>2026-10-16 22:46 UTC

4 files changed+83-1
render-vk: absorb the periodic NVIDIA submit stall after flips

NVIDIA stalls for milliseconds on every 1024th vkQueueSubmit2, which dropped a frame every 6.2 s at 165 Hz. Burning the stalling submission on empty command buffers right after a flip moves the stall into idle time. Moving submission to a thread instead would still have delayed whichever frame hit it.
Modified file: crates/monolith/src/drm.rs+6 additions
@@ -457,6 +457,12 @@ fn on_vblank(state: &mut State, crtc: crtc::Handle, meta: &mut Option<DrmEventMe
457457 tracing::warn!("frame was not submitted: {e}");
458458 }
459459 surface.queued = false;
460+ if let Err(e) = state.renderer.absorb_submit_stall() {
461+ tracing::warn!("absorbing a submit stall failed: {e}");
462+ }
463+ let Some(surface) = state.drm.as_mut().and_then(|d| d.surfaces.get_mut(&crtc)) else {
464+ return;
465+ };
460466 if let Some(DrmEventTime::Monotonic(t)) = meta.as_ref().map(|m| m.time) {
461467 let t = t.as_nanos() as u64;
462468 if let Some(target) = surface.target_ns.take() {
Modified file: crates/render-vk/Cargo.toml+1 additions
@@ -16,3 +16,4 @@ gpu-allocator = { version = "0.27", default-features = false, features = ["vulka
1616 profiling = "1"
1717 smithay.workspace = true
1818 thiserror = "2"
19+tracing = "0.1"
Modified file: crates/render-vk/src/renderer.rs+75 additions
@@ -23,10 +23,12 @@ use smithay::reexports::wayland_server::protocol::wl_buffer::WlBuffer;
2323 use smithay::utils::{Buffer, Physical, Rectangle, Size, Transform};
2424 use smithay::wayland::compositor::SurfaceData;
2525 use smithay::wayland::shm::{shm_format_to_fourcc, with_buffer_contents};
26+use std::cell::Cell;
2627 use std::collections::HashMap;
2728 use std::fmt;
2829 use std::marker::PhantomData;
2930 use std::sync::{Arc, Mutex};
31+use std::time::{Duration, Instant};
3032
3133 struct Pending {
3234 fence: Arc<FenceInner>,
@@ -50,8 +52,43 @@ pub struct VulkanRenderer {
5052 context: ContextId<VulkanTexture>,
5153 caps: Arc<DmabufCaps>,
5254 shared: Arc<Shared>,
55+ submissions: Cell<u64>,
56+ stall: Cell<StallPhase>,
5357 }
5458
59+/// Where in every 1024 submissions the driver stalls. A phase is trusted
60+/// once two stalls land on it.
61+#[derive(Clone, Copy, Default)]
62+struct StallPhase {
63+ phase: Option<u64>,
64+ seen: Option<u64>,
65+}
66+
67+impl StallPhase {
68+ fn saw(self, at: u64) -> Self {
69+ if self.seen == Some(at) {
70+ if self.phase != Some(at) {
71+ tracing::debug!(phase = at, "queue submissions stall periodically");
72+ }
73+ Self {
74+ phase: Some(at),
75+ seen: Some(at),
76+ }
77+ } else {
78+ Self {
79+ seen: Some(at),
80+ ..self
81+ }
82+ }
83+ }
84+}
85+
86+/// A submission this slow is a driver stall rather than work.
87+const STALL: Duration = Duration::from_micros(1500);
88+/// How many submissions early a stall is absorbed. Each empty one costs
89+/// tens of microseconds.
90+const ABSORB_AHEAD: u64 = 8;
91+
5592 impl fmt::Debug for VulkanRenderer {
5693 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
5794 f.debug_struct("VulkanRenderer")
@@ -111,6 +148,12 @@ impl VulkanRenderer {
111148 pipelines: Pipelines::new(&shared)?,
112149 pool,
113150 spare_cmds: Vec::new(),
151+ submissions: Cell::new(0),
152+ stall: Cell::new(StallPhase {
153+ // Measured on an RTX 3060, see research/nvidia.md.
154+ phase: shared.device.name.contains("NVIDIA").then_some(1015),
155+ seen: None,
156+ }),
114157 samplers,
115158 min_filter: TextureFilter::Linear,
116159 mag_filter: TextureFilter::Linear,
@@ -215,13 +258,45 @@ impl VulkanRenderer {
215258 .queue
216259 .lock()
217260 .unwrap_or_else(|e| e.into_inner());
261+ let began = Instant::now();
218262 {
219263 profiling::scope!("queue_submit");
220264 unsafe { d.queue_submit2(*queue, &[submit], fence.fence())? };
221265 }
266+ let n = self.submissions.get();
267+ self.submissions.set(n + 1);
268+ if began.elapsed() > STALL {
269+ self.stall.set(self.stall.get().saw(n % 1024));
270+ }
222271 Ok(fence)
223272 }
224273
274+ /// Spends an upcoming stalling submission on empty command buffers, so
275+ /// the stall lands now, in idle time, instead of in a frame. Call it
276+ /// right after a flip. NVIDIA stalls for milliseconds on every 1024th
277+ /// submission, see research/nvidia.md.
278+ pub fn absorb_submit_stall(&mut self) -> Result<(), Error> {
279+ self.reap();
280+ let Some(phase) = self.stall.get().phase else {
281+ return Ok(());
282+ };
283+ let ahead = (phase + 1024 - self.submissions.get() % 1024) % 1024;
284+ if ahead >= ABSORB_AHEAD {
285+ return Ok(());
286+ }
287+ for _ in 0..=ahead {
288+ let cmd = self.begin()?;
289+ let fence = self.end_and_submit(cmd)?;
290+ self.pending.push(Pending {
291+ fence,
292+ cmd,
293+ _keep: Vec::new(),
294+ _staging: None,
295+ });
296+ }
297+ Ok(())
298+ }
299+
225300 /// Records, submits and waits for a short command buffer.
226301 fn one_shot(
227302 &mut self,
Modified file: research/nvidia.md+1 additions−1 deletions
@@ -10,4 +10,4 @@ Numbers from throwaway benchmarks against monolith's Vulkan renderer, October 20
1010
1111 `vkQueueSubmit2` normally takes 30 to 90 us, but every 1024th call takes 2.5 to 15 ms, at the same submission indices in every run (1015, 2039, ...). This holds with or without exportable fences and with pooled fences and command buffers, so it is per submission, probably an internal ring wrapping. At 165 Hz it shows up as one dropped frame every 6.2 s.
1212
13-Open for M7. Either spend the 1024th submission on an empty submit at a moment with slack, or move submission to its own thread so the render path never waits on it.
13+A `vkQueueSubmit2` with no command buffers is a driver no-op that does not advance the count. A submit of an empty command buffer does advance it and takes the stall itself. In a 4200-frame run, absorbing the stall in idle time left zero stalled renders. render-vk now does this after every flip when the stalling submission is within 8, and it learns the phase on other drivers once two stalls land on the same one.