Commit 5cd954fce34b
tarball4 files changed+83-1
@@ -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() {@@ -16,3 +16,4 @@ gpu-allocator = { version = "0.27", default-features = false, features = ["vulka
1616
profiling = "1"1717
smithay.workspace = true1818
thiserror = "2"19
+tracing = "0.1"@@ -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 trusted60
+/// 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
+ ..self81
+ }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 costs89
+/// 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
.queue216259
.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, so275
+ /// the stall lands now, in idle time, instead of in a frame. Call it276
+ /// right after a flip. NVIDIA stalls for milliseconds on every 1024th277
+ /// 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,@@ -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.