atagen

monolith

public
Star0
Watch0

Commit d5eca1c82213

tarball

atagen <boss@atagen.co>2026-10-16 14:04 UTC

5 files changed+239-59
render-vk: reuse fences and command buffers across frames

Creating an exportable fence and allocating a command buffer for every submission, then destroying both once the GPU finished, cost NVIDIA about 35 us per submit. Pooling them cuts the median submit from 87 to 52 us. The periodic multi-millisecond submit stall turned out to be every 1024th submission regardless, recorded in research/nvidia.md for M7.
Modified file: crates/render-vk/src/device.rs+11 additions
@@ -37,12 +37,22 @@ pub(crate) struct Device {
3737 pub push_descriptor: ash::khr::push_descriptor::Device,
3838 pub dmabuf: Option<DmabufExt>,
3939 pub name: String,
40+ /// Signalled fences kept for reuse, see `FenceInner`.
41+ pub spare_fences: Mutex<Vec<vk::Fence>>,
4042 }
4143
4244 impl Drop for Device {
4345 fn drop(&mut self) {
4446 unsafe {
4547 let _ = self.device.device_wait_idle();
48+ for f in self
49+ .spare_fences
50+ .get_mut()
51+ .unwrap_or_else(|e| e.into_inner())
52+ .drain(..)
53+ {
54+ self.device.destroy_fence(f, None);
55+ }
4656 self.device.destroy_device(None);
4757 self.instance.destroy_instance(None);
4858 }
@@ -126,6 +136,7 @@ impl Shared {
126136 push_descriptor,
127137 dmabuf,
128138 name,
139+ spare_fences: Mutex::new(Vec::new()),
129140 };
130141 Ok(Self {
131142 allocator: Mutex::new(allocator?),
Modified file: crates/render-vk/src/renderer.rs+37 additions−22 deletions
@@ -41,6 +41,8 @@ pub struct VulkanRenderer {
4141 dmabuf_targets: HashMap<WeakDmabuf, Arc<ImageInner>>,
4242 pipelines: Pipelines,
4343 pool: vk::CommandPool,
44+ /// Command buffers whose work finished, reset and reused by `begin`.
45+ spare_cmds: Vec<vk::CommandBuffer>,
4446 samplers: [vk::Sampler; 4],
4547 min_filter: TextureFilter,
4648 mag_filter: TextureFilter,
@@ -76,7 +78,10 @@ impl VulkanRenderer {
7678 d.create_command_pool(
7779 &vk::CommandPoolCreateInfo::default()
7880 .queue_family_index(shared.device.queue_family)
79- .flags(vk::CommandPoolCreateFlags::TRANSIENT),
81+ .flags(
82+ vk::CommandPoolCreateFlags::TRANSIENT
83+ | vk::CommandPoolCreateFlags::RESET_COMMAND_BUFFER,
84+ ),
8085 None,
8186 )?
8287 };
@@ -105,6 +110,7 @@ impl VulkanRenderer {
105110 dmabuf_targets: HashMap::new(),
106111 pipelines: Pipelines::new(&shared)?,
107112 pool,
113+ spare_cmds: Vec::new(),
108114 samplers,
109115 min_filter: TextureFilter::Linear,
110116 mag_filter: TextureFilter::Linear,
@@ -152,28 +158,33 @@ impl VulkanRenderer {
152158 }
153159
154160 fn reap(&mut self) {
155- let d = &self.shared.device.device;
156- let pool = self.pool;
161+ let spare = &mut self.spare_cmds;
157162 self.pending.retain(|p| {
158163 if !p.fence.signaled() {
159164 return true;
160165 }
161- unsafe { d.free_command_buffers(pool, &[p.cmd]) };
166+ spare.push(p.cmd);
162167 false
163168 });
164169 self.dmabuf_textures.retain(|k, _| !k.is_gone());
165170 self.dmabuf_targets.retain(|k, _| !k.is_gone());
166171 }
167172
168- fn begin(&self) -> Result<vk::CommandBuffer, Error> {
173+ fn begin(&mut self) -> Result<vk::CommandBuffer, Error> {
169174 let d = &self.shared.device.device;
170- let cmd = unsafe {
171- d.allocate_command_buffers(
172- &vk::CommandBufferAllocateInfo::default()
173- .command_pool(self.pool)
174- .level(vk::CommandBufferLevel::PRIMARY)
175- .command_buffer_count(1),
176- )?[0]
175+ let cmd = match self.spare_cmds.pop() {
176+ Some(cmd) => {
177+ unsafe { d.reset_command_buffer(cmd, vk::CommandBufferResetFlags::empty())? };
178+ cmd
179+ }
180+ None => unsafe {
181+ d.allocate_command_buffers(
182+ &vk::CommandBufferAllocateInfo::default()
183+ .command_pool(self.pool)
184+ .level(vk::CommandBufferLevel::PRIMARY)
185+ .command_buffer_count(1),
186+ )?[0]
187+ },
177188 };
178189 unsafe {
179190 d.begin_command_buffer(
@@ -188,8 +199,14 @@ impl VulkanRenderer {
188199 #[profiling::function]
189200 fn end_and_submit(&self, cmd: vk::CommandBuffer) -> Result<Arc<FenceInner>, Error> {
190201 let d = &self.shared.device.device;
191- unsafe { d.end_command_buffer(cmd)? };
192- let fence = FenceInner::new(&self.shared)?;
202+ {
203+ profiling::scope!("end_command_buffer");
204+ unsafe { d.end_command_buffer(cmd)? };
205+ }
206+ let fence = {
207+ profiling::scope!("create_fence");
208+ FenceInner::new(&self.shared)?
209+ };
193210 let cmds = [vk::CommandBufferSubmitInfo::default().command_buffer(cmd)];
194211 let submit = vk::SubmitInfo2::default().command_buffer_infos(&cmds);
195212 let queue = self
@@ -198,7 +215,10 @@ impl VulkanRenderer {
198215 .queue
199216 .lock()
200217 .unwrap_or_else(|e| e.into_inner());
201- unsafe { d.queue_submit2(*queue, &[submit], fence.fence())? };
218+ {
219+ profiling::scope!("queue_submit");
220+ unsafe { d.queue_submit2(*queue, &[submit], fence.fence())? };
221+ }
202222 Ok(fence)
203223 }
204224
@@ -215,12 +235,7 @@ impl VulkanRenderer {
215235 );
216236 let fence = self.end_and_submit(cmd)?;
217237 let res = fence.wait();
218- unsafe {
219- self.shared
220- .device
221- .device
222- .free_command_buffers(self.pool, &[cmd])
223- };
238+ self.spare_cmds.push(cmd);
224239 Ok(res?)
225240 }
226241
@@ -455,9 +470,9 @@ impl VulkanRenderer {
455470 height: h as u32,
456471 depth: 1,
457472 });
473+ let cmd = self.begin()?;
458474 let d = &self.shared.device.device;
459475 let family = self.shared.device.queue_family;
460- let cmd = self.begin()?;
461476 unsafe {
462477 acquire(d, cmd, family, image, vk::ImageLayout::TRANSFER_DST_OPTIMAL);
463478 d.cmd_copy_buffer_to_image(
Modified file: crates/render-vk/src/sync.rs+31 additions−2 deletions
@@ -30,7 +30,19 @@ impl FenceInner {
3030 if exportable {
3131 info = info.push_next(&mut export);
3232 }
33- let fence = unsafe { shared.device.device.create_fence(&info, None)? };
33+ let spare = shared
34+ .device
35+ .spare_fences
36+ .lock()
37+ .unwrap_or_else(|e| e.into_inner())
38+ .pop();
39+ let fence = match spare {
40+ Some(f) => {
41+ unsafe { shared.device.device.reset_fences(&[f])? };
42+ f
43+ }
44+ None => unsafe { shared.device.device.create_fence(&info, None)? },
45+ };
3446 Ok(Arc::new(Self {
3547 shared: shared.clone(),
3648 fence,
@@ -107,9 +119,26 @@ fn readable(fd: &OwnedFd, timeout: Option<&rustix::event::Timespec>) -> bool {
107119 }
108120 }
109121
122+/// Fences go back to a pool instead of being destroyed, which cut the median
123+/// queue submit on NVIDIA from 87 to 52 us. One still pending is destroyed.
110124 impl Drop for FenceInner {
111125 fn drop(&mut self) {
112- unsafe { self.shared.device.device.destroy_fence(self.fence, None) };
126+ let d = &self.shared.device.device;
127+ let state = self.state.get_mut().unwrap_or_else(|e| e.into_inner());
128+ let idle = match state {
129+ State::Pending => unsafe { d.get_fence_status(self.fence) }.unwrap_or(false),
130+ State::Exported(_) => true,
131+ };
132+ if idle {
133+ self.shared
134+ .device
135+ .spare_fences
136+ .lock()
137+ .unwrap_or_else(|e| e.into_inner())
138+ .push(self.fence);
139+ } else {
140+ unsafe { d.destroy_fence(self.fence, None) };
141+ }
113142 }
114143 }
115144
Modified file: monolith.log+147 additions−35 deletions
@@ -1,38 +1,150 @@
1- Compiling monolith v0.0.0 (/home/bolt/code/monolith/crates/monolith)
2- Finished `release` profile [optimized] target(s) in 12.52s
1+ Finished `release` profile [optimized] target(s) in 0.09s
32 Running `target/release/monolith run --backend drm , vkcube`
4-2026-10-16T13:53:37.943439Z INFO monolith::drm: primary GPU path=/dev/dri/card1
5-2026-10-16T13:53:38.355268Z INFO monolith::drm: output connected name="DP-2" w=2560 h=1440 refresh=165
6-2026-10-16T13:53:38.355369Z INFO monolith::ipc: control socket path=/run/user/1000/monolith-wayland-2.sock
7-2026-10-16T13:53:38.355385Z INFO monolith: monolith running socket="wayland-2" device="NVIDIA GeForce RTX 3060"
8-2026-10-16T13:53:38.355828Z INFO monolith::commands: spawned pid=358143 argv=[",", "vkcube"]
9-2026-10-16T13:53:38.383773Z DEBUG monolith::drm: slow frame output="DP-2" took_ns=27867019 scene_ns=3700 render_ns=8621537 render_cpu_ns=2390802 queue_ns=213233
10-2026-10-16T13:53:38.387370Z DEBUG monolith::drm: frame rate output="DP-2" presented=1 vblanks=8700649 budget_us=29867
3+2026-10-16T13:55:27.915038Z INFO monolith: logging spans slower than 1 ms
4+slow span input_seat>add_keyboard_with_context_flags>input_keyboard took_us=2087
5+slow span input_seat>add_keyboard_with_context_flags took_us=2132
6+slow span input_seat>add_keyboard took_us=2147
7+2026-10-16T13:55:29.224103Z INFO monolith::drm: primary GPU path=/dev/dri/card1
8+slow span drm_device>drm_atomic took_us=90722
9+slow span drm_device took_us=90767
10+slow span new>acquire>acquire>create_buffer>create_buffer_with_flags>create_buffer_with_flags took_us=1298
11+slow span new>acquire>acquire>create_buffer>create_buffer_with_flags took_us=1328
12+slow span new>acquire>acquire>create_buffer took_us=1336
13+slow span new>acquire>acquire took_us=1345
14+slow span new>acquire took_us=1353
15+slow span drm_device>drm_atomic>create_surface>drm_atomic>test_state took_us=15241
16+slow span new>test_state>test_state>test_state took_us=15264
17+slow span new>test_state>test_state took_us=15273
18+slow span new>test_state took_us=15281
19+slow span new took_us=17299
20+slow span drm_device>drm_atomic>create_surface>drm_atomic>test_state took_us=6765
21+slow span drm_compositor>render_frame>test_state_complete>test_state>test_state took_us=6782
22+slow span drm_compositor>render_frame>test_state_complete>test_state took_us=6790
23+slow span drm_compositor>render_frame>test_state_complete took_us=6799
24+slow span drm_compositor>render_frame>bind took_us=15573
25+slow span drm_compositor>render_frame took_us=22996
26+slow span render_frame took_us=23018
27+slow span drm_device>drm_atomic>create_surface>drm_atomic>commit took_us=99237
28+slow span commit>commit>commit took_us=99272
29+slow span commit>commit took_us=99282
30+slow span commit took_us=99297
31+2026-10-16T13:55:29.641815Z INFO monolith::drm: output connected name="DP-2" w=2560 h=1440 refresh=165
32+2026-10-16T13:55:29.641868Z INFO monolith::ipc: control socket path=/run/user/1000/monolith-wayland-2.sock
33+2026-10-16T13:55:29.641880Z INFO monolith: monolith running socket="wayland-2" device="NVIDIA GeForce RTX 3060"
34+2026-10-16T13:55:29.642294Z INFO monolith::commands: spawned pid=358809 argv=[",", "vkcube"]
35+slow span render_surface>import_texture>import_texture>upload_with took_us=15812
36+slow span render_surface>import_texture>import_texture took_us=26501
37+slow span render_surface>import_texture took_us=26511
38+slow span drm_compositor>render_frame>acquire>acquire>create_buffer>create_buffer_with_flags>create_buffer_with_flags took_us=1252
39+slow span drm_compositor>render_frame>acquire>acquire>create_buffer>create_buffer_with_flags took_us=1278
40+slow span drm_compositor>render_frame>acquire>acquire>create_buffer took_us=1295
41+slow span drm_compositor>render_frame>acquire>acquire took_us=1314
42+slow span drm_compositor>render_frame>acquire took_us=1330
43+slow span drm_compositor>render_frame>add_framebuffer>framebuffer_from_bo>framebuffer_from_bo_internal took_us=7774
44+slow span drm_compositor>render_frame>add_framebuffer>framebuffer_from_bo took_us=7821
45+slow span drm_compositor>render_frame>add_framebuffer took_us=7837
46+slow span drm_compositor>render_frame>bind took_us=13767
47+slow span drm_compositor>render_frame took_us=23309
48+slow span render_surface>render_frame took_us=23323
49+2026-10-16T13:55:29.692355Z DEBUG render_surface: monolith::drm: slow frame output="DP-2" took_ns=49995407 scene_ns=7600 render_ns=23337629 render_cpu_ns=2849132 queue_ns=125632
50+slow span render_surface took_us=50013
51+2026-10-16T13:55:29.697539Z DEBUG monolith::drm: frame rate output="DP-2" presented=1 vblanks=8719010 budget_us=51995
1152 Selected WSI platform: wayland
1253 Selected GPU 0: NVIDIA GeForce RTX 3060, type: DiscreteGpu, apiVersion: 4211017 (1.4.329), driverVersion: -1797652352 (83.416.128)
13-2026-10-16T13:53:39.387636Z DEBUG monolith::drm: frame rate output="DP-2" presented=10 vblanks=165 budget_us=2400
14-2026-10-16T13:53:40.387865Z DEBUG monolith::drm: frame rate output="DP-2" presented=90 vblanks=164 budget_us=2082
15-2026-10-16T13:53:40.442431Z DEBUG monolith::drm: frame missed its vblank output="DP-2" late_ns=6062851 budget_ns=2558414
16-2026-10-16T13:53:41.388127Z DEBUG monolith::drm: frame rate output="DP-2" presented=163 vblanks=165 budget_us=2697
17-2026-10-16T13:53:41.497723Z DEBUG monolith::drm: slow frame output="DP-2" took_ns=3177748 scene_ns=4680 render_ns=3092097 render_cpu_ns=387440 queue_ns=79371
18-2026-10-16T13:53:41.503321Z DEBUG monolith::drm: frame missed its vblank output="DP-2" late_ns=6052851 budget_ns=3227936
19-2026-10-16T13:53:42.388427Z DEBUG monolith::drm: frame rate output="DP-2" presented=164 vblanks=165 budget_us=3081
20-2026-10-16T13:53:43.255318Z DEBUG monolith::drm: frame missed its vblank output="DP-2" late_ns=6096851 budget_ns=3239058
21-2026-10-16T13:53:43.388643Z DEBUG monolith::drm: frame rate output="DP-2" presented=164 vblanks=164 budget_us=3216
22-2026-10-16T13:53:44.385342Z DEBUG monolith::input: bind cmd=Fullscreen
23-2026-10-16T13:53:44.388911Z DEBUG monolith::drm: frame rate output="DP-2" presented=165 vblanks=165 budget_us=3300
24-2026-10-16T13:53:44.619725Z DEBUG monolith::drm: slow frame output="DP-2" took_ns=41477278 scene_ns=13400 render_ns=41081802 render_cpu_ns=6374442 queue_ns=375246
25-2026-10-16T13:53:44.625342Z DEBUG monolith::drm: frame missed its vblank output="DP-2" late_ns=96996106 budget_ns=3200227
26-2026-10-16T13:53:45.389164Z DEBUG monolith::drm: frame rate output="DP-2" presented=131 vblanks=165 budget_us=3242
27-2026-10-16T13:53:46.389427Z DEBUG monolith::drm: frame rate output="DP-2" presented=165 vblanks=165 budget_us=3249
28-2026-10-16T13:53:47.389678Z DEBUG monolith::drm: frame rate output="DP-2" presented=165 vblanks=165 budget_us=3188
29-2026-10-16T13:53:48.389939Z DEBUG monolith::drm: frame rate output="DP-2" presented=165 vblanks=165 budget_us=3242
30-2026-10-16T13:53:49.390191Z DEBUG monolith::drm: frame rate output="DP-2" presented=165 vblanks=165 budget_us=3208
31-2026-10-16T13:53:50.390452Z DEBUG monolith::drm: frame rate output="DP-2" presented=165 vblanks=165 budget_us=3182
32-2026-10-16T13:53:51.390710Z DEBUG monolith::drm: frame rate output="DP-2" presented=165 vblanks=165 budget_us=3172
33-2026-10-16T13:53:52.391010Z DEBUG monolith::drm: frame rate output="DP-2" presented=165 vblanks=165 budget_us=3287
34-2026-10-16T13:53:53.391226Z DEBUG monolith::drm: frame rate output="DP-2" presented=165 vblanks=164 budget_us=3190
35-2026-10-16T13:53:54.391491Z DEBUG monolith::drm: frame rate output="DP-2" presented=165 vblanks=165 budget_us=3195
36-2026-10-16T13:53:55.391795Z DEBUG monolith::drm: frame rate output="DP-2" presented=165 vblanks=165 budget_us=3227
37-2026-10-16T13:53:56.392074Z DEBUG monolith::drm: frame rate output="DP-2" presented=165 vblanks=164 budget_us=3406
38-2026-10-16T13:53:56.817264Z DEBUG monolith::input: bind cmd=Quit
54+2026-10-16T13:55:30.697736Z DEBUG monolith::drm: frame rate output="DP-2" presented=19 vblanks=164 budget_us=2437
55+slow span import_dmabuf took_us=17856
56+slow span import_dmabuf took_us=27238
57+slow span import_dmabuf took_us=20155
58+2026-10-16T13:55:31.697991Z DEBUG monolith::drm: frame rate output="DP-2" presented=82 vblanks=164 budget_us=2191
59+2026-10-16T13:55:32.679444Z DEBUG monolith::input: bind cmd=Fullscreen
60+2026-10-16T13:55:32.698256Z DEBUG monolith::drm: frame rate output="DP-2" presented=164 vblanks=165 budget_us=2013
61+slow span import_dmabuf took_us=17700
62+slow span import_dmabuf took_us=24037
63+slow span import_dmabuf took_us=15677
64+slow span on_commit_buffer_handler>update_buffer took_us=6342
65+slow span on_commit_buffer_handler took_us=6395
66+slow span new>render_damage>render_output_internal took_us=24988
67+slow span new>render_damage>render_output>render_output_internal took_us=25040
68+slow span new>render_damage>render_output took_us=25071
69+slow span drm_compositor>render_frame>render_output took_us=25080
70+slow span drm_compositor>render_frame took_us=25146
71+slow span render_surface>render_frame took_us=25158
72+2026-10-16T13:55:32.913829Z DEBUG render_surface: monolith::drm: slow frame output="DP-2" took_ns=25435336 scene_ns=26330 render_ns=25175162 render_cpu_ns=5136493 queue_ns=222484
73+slow span render_surface took_us=25483
74+2026-10-16T13:55:32.922566Z DEBUG monolith::drm: frame missed its vblank output="DP-2" late_ns=96993106 budget_ns=2508765
75+2026-10-16T13:55:32.934693Z DEBUG monolith::drm: frame missed its vblank output="DP-2" late_ns=6064851 budget_ns=3011606
76+2026-10-16T13:55:32.971061Z DEBUG monolith::drm: frame missed its vblank output="DP-2" late_ns=6068851 budget_ns=3327399
77+slow span new>render_damage>render_output_internal>submit>end_and_submit took_us=5815
78+slow span new>render_damage>render_output_internal>submit took_us=5930
79+slow span new>render_damage>render_output_internal took_us=5971
80+slow span new>render_damage>render_output>render_output_internal took_us=5978
81+slow span new>render_damage>render_output took_us=6008
82+slow span drm_compositor>render_frame>render_output took_us=6017
83+slow span drm_compositor>render_frame took_us=6080
84+slow span render_surface>render_frame took_us=6090
85+2026-10-16T13:55:33.016548Z DEBUG render_surface: monolith::drm: slow frame output="DP-2" took_ns=6376037 scene_ns=33750 render_ns=6107272 render_cpu_ns=1178461 queue_ns=223444
86+slow span render_surface took_us=6418
87+2026-10-16T13:55:33.019676Z DEBUG monolith::drm: frame missed its vblank output="DP-2" late_ns=6090851 budget_ns=3416091
88+2026-10-16T13:55:33.698509Z DEBUG monolith::drm: frame rate output="DP-2" presented=128 vblanks=165 budget_us=3342
89+2026-10-16T13:55:34.698766Z DEBUG monolith::drm: frame rate output="DP-2" presented=165 vblanks=165 budget_us=3311
90+slow span new>render_damage>render_output_internal>submit>end_and_submit took_us=5602
91+slow span new>render_damage>render_output_internal>submit took_us=5690
92+slow span new>render_damage>render_output_internal took_us=5712
93+slow span new>render_damage>render_output>render_output_internal took_us=5720
94+slow span new>render_damage>render_output took_us=5733
95+slow span drm_compositor>render_frame>render_output took_us=5740
96+slow span drm_compositor>render_frame took_us=5787
97+slow span render_surface>render_frame took_us=5796
98+2026-10-16T13:55:34.768375Z DEBUG render_surface: monolith::drm: slow frame output="DP-2" took_ns=6245805 scene_ns=8490 render_ns=5808747 render_cpu_ns=1863972 queue_ns=424608
99+slow span render_surface took_us=6312
100+2026-10-16T13:55:34.771529Z DEBUG monolith::drm: frame missed its vblank output="DP-2" late_ns=6069851 budget_ns=3362830
101+2026-10-16T13:55:35.699025Z DEBUG monolith::drm: frame rate output="DP-2" presented=164 vblanks=165 budget_us=3472
102+2026-10-16T13:55:36.699286Z DEBUG monolith::drm: frame rate output="DP-2" presented=165 vblanks=165 budget_us=3415
103+slow span new>render_damage>render_output_internal>submit>end_and_submit took_us=3063
104+slow span new>render_damage>render_output_internal>submit took_us=3108
105+slow span new>render_damage>render_output_internal took_us=3123
106+slow span new>render_damage>render_output>render_output_internal took_us=3130
107+slow span new>render_damage>render_output took_us=3141
108+slow span drm_compositor>render_frame>render_output took_us=3149
109+slow span drm_compositor>render_frame took_us=3170
110+slow span render_surface>render_frame took_us=3179
111+2026-10-16T13:55:37.432701Z DEBUG render_surface: monolith::drm: slow frame output="DP-2" took_ns=3290440 scene_ns=7500 render_ns=3204169 render_cpu_ns=3270391 queue_ns=75401
112+slow span render_surface took_us=3306
113+2026-10-16T13:55:37.699539Z DEBUG monolith::drm: frame rate output="DP-2" presented=165 vblanks=165 budget_us=3298
114+2026-10-16T13:55:38.699792Z DEBUG monolith::drm: frame rate output="DP-2" presented=165 vblanks=165 budget_us=3330
115+2026-10-16T13:55:39.700055Z DEBUG monolith::drm: frame rate output="DP-2" presented=165 vblanks=165 budget_us=3345
116+2026-10-16T13:55:40.700318Z DEBUG monolith::drm: frame rate output="DP-2" presented=165 vblanks=165 budget_us=3478
117+2026-10-16T13:55:41.700567Z DEBUG monolith::drm: frame rate output="DP-2" presented=165 vblanks=165 budget_us=3266
118+2026-10-16T13:55:42.700827Z DEBUG monolith::drm: frame rate output="DP-2" presented=165 vblanks=165 budget_us=3578
119+slow span new>render_damage>render_output_internal>submit>end_and_submit took_us=3009
120+slow span new>render_damage>render_output_internal>submit took_us=3120
121+slow span new>render_damage>render_output_internal took_us=3142
122+slow span new>render_damage>render_output>render_output_internal took_us=3155
123+slow span new>render_damage>render_output took_us=3174
124+slow span drm_compositor>render_frame>render_output took_us=3188
125+slow span drm_compositor>render_frame took_us=3220
126+slow span render_surface>render_frame took_us=3235
127+2026-10-16T13:55:43.640465Z DEBUG render_surface: monolith::drm: slow frame output="DP-2" took_ns=3363021 scene_ns=9080 render_ns=3252660 render_cpu_ns=3339252 queue_ns=97881
128+slow span render_surface took_us=3384
129+2026-10-16T13:55:43.646539Z DEBUG monolith::drm: frame missed its vblank output="DP-2" late_ns=6066851 budget_ns=3369191
130+2026-10-16T13:55:43.701091Z DEBUG monolith::drm: frame rate output="DP-2" presented=164 vblanks=165 budget_us=3591
131+2026-10-16T13:55:44.701355Z DEBUG monolith::drm: frame rate output="DP-2" presented=165 vblanks=165 budget_us=3399
132+2026-10-16T13:55:45.701605Z DEBUG monolith::drm: frame rate output="DP-2" presented=165 vblanks=165 budget_us=3279
133+2026-10-16T13:55:46.701869Z DEBUG monolith::drm: frame rate output="DP-2" presented=165 vblanks=165 budget_us=3357
134+2026-10-16T13:55:47.702145Z DEBUG monolith::drm: frame rate output="DP-2" presented=165 vblanks=165 budget_us=3503
135+2026-10-16T13:55:48.702451Z DEBUG monolith::drm: frame rate output="DP-2" presented=165 vblanks=165 budget_us=3412
136+2026-10-16T13:55:49.702636Z DEBUG monolith::drm: frame rate output="DP-2" presented=165 vblanks=164 budget_us=3267
137+slow span new>render_damage>render_output_internal>submit>end_and_submit took_us=3504
138+slow span new>render_damage>render_output_internal>submit took_us=3561
139+slow span new>render_damage>render_output_internal took_us=3578
140+slow span new>render_damage>render_output>render_output_internal took_us=3585
141+slow span new>render_damage>render_output took_us=3598
142+slow span drm_compositor>render_frame>render_output took_us=3605
143+slow span drm_compositor>render_frame took_us=3628
144+slow span render_surface>render_frame took_us=3636
145+2026-10-16T13:55:49.854584Z DEBUG render_surface: monolith::drm: slow frame output="DP-2" took_ns=3769619 scene_ns=9481 render_ns=3647066 render_cpu_ns=3739753 queue_ns=109462
146+slow span render_surface took_us=3793
147+2026-10-16T13:55:49.860264Z DEBUG monolith::drm: frame missed its vblank output="DP-2" late_ns=6067851 budget_ns=3373150
148+2026-10-16T13:55:50.378283Z DEBUG monolith::input: bind cmd=Quit
149+slow span drm_device>drm_atomic>create_surface>drm_atomic took_us=67389
150+slow span drm_device>drm_atomic>create_surface>drm_atomic>clear_state took_us=67443
Added file: research/nvidia.md+13 additions
@@ -0,0 +1,13 @@
1+# NVIDIA driver behaviour measured on an RTX 3060
2+
3+Numbers from throwaway benchmarks against monolith's Vulkan renderer, October 2026.
4+
5+## Waiting on a pending VkFence sleeps in 10 ms steps
6+
7+`vkWaitForFences` on a fence whose work has not finished yet returned after a 10.06 ms median, for GPU work that finishes in under 200 us. Waiting 200 us before calling it gave a 2 us median. Exporting the fence as a sync_file and polling the fd wakes on completion, at a 15 us median and 213 us worst case. render-vk waits this way now.
8+
9+## Every 1024th queue submit stalls for milliseconds
10+
11+`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.
12+
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.