atagen

monolith

public
Star0
Watch0

Commit ad1f04ba8cb2

tarball

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

2 files changed+108-27
render-vk: commit layouts only after the submit succeeds

acquire set the tracked layout while recording, so a failed vkQueueSubmit2 left images believed to be in layouts the GPU never reached. end_and_submit also dropped the command buffer from spare_cmds on every error. Layouts now go through a LayoutLog that finish commits, the command buffer returns to the spare list, and queued uploads are put back for the next attempt.
Modified file: crates/render-vk/src/barrier.rs+27 additions−3 deletions
@@ -1,5 +1,28 @@
11 use crate::image::{ImageInner, color_range};
22 use ash::vk;
3+use std::sync::Arc;
4+
5+/// Layout changes recorded into a command buffer that has not been submitted.
6+/// Images keep their old layout until `commit`, so a failed submit leaves the
7+/// tracking true to what the GPU has done.
8+#[derive(Default)]
9+pub(crate) struct LayoutLog(Vec<(Arc<ImageInner>, vk::ImageLayout)>);
10+
11+impl LayoutLog {
12+ fn current(&self, image: &Arc<ImageInner>) -> vk::ImageLayout {
13+ self.0
14+ .iter()
15+ .rev()
16+ .find(|(i, _)| Arc::ptr_eq(i, image))
17+ .map_or_else(|| image.layout(), |(_, layout)| *layout)
18+ }
19+
20+ pub fn commit(self) {
21+ for (image, layout) in self.0 {
22+ image.set_layout(layout);
23+ }
24+ }
25+}
326
427 /// A full barrier between all earlier and later work on the image, optionally
528 /// transferring ownership between queue families. Coarse, but correct for
@@ -39,8 +62,9 @@ pub(crate) unsafe fn acquire(
3962 d: &ash::Device,
4063 cmd: vk::CommandBuffer,
4164 family: u32,
42- image: &ImageInner,
65+ image: &Arc<ImageInner>,
4366 layout: vk::ImageLayout,
67+ log: &mut LayoutLog,
4468 ) {
4569 if image.external {
4670 unsafe {
@@ -55,7 +79,7 @@ pub(crate) unsafe fn acquire(
5579 )
5680 };
5781 } else {
58- let old = image.layout();
82+ let old = log.current(image);
5983 if old != layout {
6084 unsafe {
6185 barrier(
@@ -68,7 +92,7 @@ pub(crate) unsafe fn acquire(
6892 vk::QUEUE_FAMILY_IGNORED,
6993 )
7094 };
71- image.set_layout(layout);
95+ log.0.push((image.clone(), layout));
7296 }
7397 }
7498 }
Modified file: crates/render-vk/src/renderer/mod.rs+81 additions−24 deletions
@@ -5,7 +5,7 @@ use shm::stage_rows;
55 use stall::{STALL, StallPhase};
66
77 use crate::Error;
8-use crate::barrier::{acquire, release};
8+use crate::barrier::{LayoutLog, acquire, release};
99 use crate::device::Shared;
1010 use crate::dmabuf::{self, DmabufCaps, RENDER_USAGE, SAMPLE_USAGE, VulkanAllocator};
1111 use crate::format::{BYTES_PER_PIXEL, SUPPORTED};
@@ -49,6 +49,20 @@ struct Upload {
4949 copy: vk::BufferImageCopy,
5050 }
5151
52+impl Upload {
53+ fn bytes(&self) -> usize {
54+ let extent = self.copy.image_extent;
55+ extent.width as usize * extent.height as usize * BYTES_PER_PIXEL
56+ }
57+}
58+
59+/// What a command buffer holds besides its own commands. A failed submit
60+/// takes the uploads back and drops the layouts.
61+struct Recorded {
62+ uploads: Vec<Upload>,
63+ layouts: LayoutLog,
64+}
65+
5266 /// Queued staging memory past which uploads are submitted without waiting
5367 /// for a frame.
5468 const UPLOAD_BATCH_BYTES: usize = 64 * 1024 * 1024;
@@ -224,18 +238,31 @@ impl VulkanRenderer {
224238 )?[0]
225239 },
226240 };
227- unsafe {
241+ let began = unsafe {
228242 d.begin_command_buffer(
229243 cmd,
230244 &vk::CommandBufferBeginInfo::default()
231245 .flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT),
232- )?
246+ )
233247 };
248+ if let Err(e) = began {
249+ self.spare_cmds.push(cmd);
250+ return Err(e.into());
251+ }
234252 Ok(cmd)
235253 }
236254
255+ /// Ends and submits `cmd`. It goes back to the spare list if that fails.
256+ fn end_and_submit(&mut self, cmd: vk::CommandBuffer) -> Result<Arc<FenceInner>, Error> {
257+ let submitted = self.submit_cmd(cmd);
258+ if submitted.is_err() {
259+ self.spare_cmds.push(cmd);
260+ }
261+ submitted
262+ }
263+
237264 #[profiling::function]
238- fn end_and_submit(&self, cmd: vk::CommandBuffer) -> Result<Arc<FenceInner>, Error> {
265+ fn submit_cmd(&self, cmd: vk::CommandBuffer) -> Result<Arc<FenceInner>, Error> {
239266 let d = &self.shared.device.device;
240267 {
241268 profiling::scope!("end_command_buffer");
@@ -270,28 +297,39 @@ impl VulkanRenderer {
270297 /// go first, so `record` sees their pixels.
271298 fn one_shot(
272299 &mut self,
273- record: impl FnOnce(&ash::Device, vk::CommandBuffer, u32),
300+ record: impl FnOnce(&ash::Device, vk::CommandBuffer, u32, &mut LayoutLog),
274301 ) -> Result<(), Error> {
275302 let cmd = self.begin()?;
276- let uploads = self.record_uploads(cmd);
303+ let mut recorded = self.record_uploads(cmd);
277304 record(
278305 &self.shared.device.device,
279306 cmd,
280307 self.shared.device.queue_family,
308+ &mut recorded.layouts,
281309 );
282- let fence = self.finish(cmd, uploads, Vec::new())?;
310+ let fence = self.finish(cmd, recorded, Vec::new())?;
283311 Ok(fence.wait()?)
284312 }
285313
286- /// Submits `cmd`, which holds `uploads` and works on `keep`, and tracks
287- /// it until its fence signals.
314+ /// Submits `cmd`, which holds `recorded` and works on `keep`, and tracks
315+ /// it until its fence signals. On success the layouts it recorded take
316+ /// effect, on failure the uploads are queued again.
288317 fn finish(
289318 &mut self,
290319 cmd: vk::CommandBuffer,
291- uploads: Vec<Upload>,
320+ recorded: Recorded,
292321 mut keep: Vec<Arc<ImageInner>>,
293322 ) -> Result<Arc<FenceInner>, Error> {
294- let fence = self.end_and_submit(cmd)?;
323+ let Recorded { uploads, layouts } = recorded;
324+ let fence = match self.end_and_submit(cmd) {
325+ Ok(fence) => fence,
326+ Err(e) => {
327+ self.upload_bytes += uploads.iter().map(Upload::bytes).sum::<usize>();
328+ self.uploads.splice(0..0, uploads);
329+ return Err(e);
330+ }
331+ };
332+ layouts.commit();
295333 keep.extend(uploads.iter().map(|u| u.image.clone()));
296334 self.pending.push(Pending {
297335 fence: fence.clone(),
@@ -304,17 +342,25 @@ impl VulkanRenderer {
304342
305343 /// Records every queued upload into `cmd` and hands them back to keep
306344 /// alive until it finishes.
307- fn record_uploads(&mut self, cmd: vk::CommandBuffer) -> Vec<Upload> {
345+ fn record_uploads(&mut self, cmd: vk::CommandBuffer) -> Recorded {
308346 let uploads = std::mem::take(&mut self.uploads);
309347 self.upload_bytes = 0;
348+ let mut layouts = LayoutLog::default();
310349 if uploads.is_empty() {
311- return uploads;
350+ return Recorded { uploads, layouts };
312351 }
313352 let d = &self.shared.device.device;
314353 let family = self.shared.device.queue_family;
315354 unsafe {
316355 for u in &uploads {
317- acquire(d, cmd, family, &u.image, vk::ImageLayout::TRANSFER_DST_OPTIMAL);
356+ acquire(
357+ d,
358+ cmd,
359+ family,
360+ &u.image,
361+ vk::ImageLayout::TRANSFER_DST_OPTIMAL,
362+ &mut layouts,
363+ );
318364 }
319365 for (i, u) in uploads.iter().enumerate() {
320366 if uploads[..i].iter().any(|e| Arc::ptr_eq(&e.image, &u.image)) {
@@ -343,10 +389,11 @@ impl VulkanRenderer {
343389 family,
344390 &u.image,
345391 vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL,
392+ &mut layouts,
346393 );
347394 }
348395 }
349- uploads
396+ Recorded { uploads, layouts }
350397 }
351398
352399 /// Submits the queued uploads without waiting for a frame to carry them.
@@ -355,8 +402,8 @@ impl VulkanRenderer {
355402 return Ok(());
356403 }
357404 let cmd = self.begin()?;
358- let uploads = self.record_uploads(cmd);
359- self.finish(cmd, uploads, Vec::new()).map(drop)
405+ let recorded = self.record_uploads(cmd);
406+ self.finish(cmd, recorded, Vec::new()).map(drop)
360407 }
361408
362409 #[profiling::function]
@@ -385,7 +432,7 @@ impl VulkanRenderer {
385432 });
386433 }
387434 let cmd = self.begin()?;
388- let uploads = self.record_uploads(cmd);
435+ let mut recorded = self.record_uploads(cmd);
389436 let d = &self.shared.device.device;
390437 let family = self.shared.device.queue_family;
391438 let push = &self.shared.device.push_descriptor;
@@ -393,7 +440,14 @@ impl VulkanRenderer {
393440 let sampler = self.sampler();
394441 unsafe {
395442 for t in &sampled {
396- acquire(d, cmd, family, t, vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL);
443+ acquire(
444+ d,
445+ cmd,
446+ family,
447+ t,
448+ vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL,
449+ &mut recorded.layouts,
450+ );
397451 }
398452 acquire(
399453 d,
@@ -401,6 +455,7 @@ impl VulkanRenderer {
401455 family,
402456 &image,
403457 vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL,
458+ &mut recorded.layouts,
404459 );
405460 let attachments = [vk::RenderingAttachmentInfo::default()
406461 .image_view(image.view)
@@ -509,7 +564,7 @@ impl VulkanRenderer {
509564 }
510565 }
511566 sampled.push(image);
512- let fence = self.finish(cmd, uploads, sampled)?;
567+ let fence = self.finish(cmd, recorded, sampled)?;
513568 Ok(SyncPoint::from(VkFence(fence)))
514569 }
515570
@@ -585,12 +640,13 @@ impl VulkanRenderer {
585640 height: h as u32,
586641 depth: 1,
587642 });
588- self.uploads.push(Upload {
643+ let upload = Upload {
589644 image: image.clone(),
590645 staging,
591646 copy,
592- });
593- self.upload_bytes += w * h * BYTES_PER_PIXEL;
647+ };
648+ self.upload_bytes += upload.bytes();
649+ self.uploads.push(upload);
594650 if self.upload_bytes > UPLOAD_BATCH_BYTES {
595651 self.flush_uploads()?;
596652 }
@@ -636,13 +692,14 @@ impl VulkanRenderer {
636692 });
637693 let buffer = staging.buffer;
638694 let source = image.clone();
639- self.one_shot(|d, cmd, family| unsafe {
695+ self.one_shot(|d, cmd, family, layouts| unsafe {
640696 acquire(
641697 d,
642698 cmd,
643699 family,
644700 &source,
645701 vk::ImageLayout::TRANSFER_SRC_OPTIMAL,
702+ layouts,
646703 );
647704 d.cmd_copy_image_to_buffer(
648705 cmd,