From 5b4b3d5f61c6939b14c3d7d38662f00fc9530f73 Mon Sep 17 00:00:00 2001 From: Irene Knapp Date: Mon, 6 Jul 2026 17:31:27 -0700 Subject: render a few frames at once Force-Push: yes Change-Id: Ibf09815b6d64c80242d623a1a5f387d6e7166f67 --- src/main.rs | 134 +++++++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 102 insertions(+), 32 deletions(-) (limited to 'src/main.rs') diff --git a/src/main.rs b/src/main.rs index a7d3410..3fe3f66 100644 --- a/src/main.rs +++ b/src/main.rs @@ -23,6 +23,10 @@ mod error; const VULKAN_FIRST_PORTABILITY_VERSION: Version = Version::new(1, 3, 216); +// TODO: use VK_KHR_swapchain_maintenance1 to put a fence on the presentation +// operation. doing that will remove the requirement that we have more +// simultaneous frames than images. +const N_SIMULTANEOUS_FRAMES: usize = 5; enum Acceptable { @@ -144,8 +148,25 @@ struct Surreality { command_pool: OnceCell, command_buffers: OnceCell>, - image_available_semaphore: OnceCell, - rendering_finished_semaphore: OnceCell, + image_available_semaphores: OnceCell>, + rendering_finished_semaphores: OnceCell>, + + // Okay, the lifetime management on the fences is really subtle. There is + // one fence for each frame, and frame_fences holds the authoritative + // reference to it. + // + // There is one entry in image_fences for each image. The number of images + // is not directly related to the number of frames; it will likely be + // larger, but may be smaller or the same. At the start of execution, the + // entries are all nulls. Each time an image is acquired from the swapchain, + // the corresponding entry in image_fences is overwritten with a duplicate + // of the frame fence. This happens during rendering of the frame, so the + // frame fence is in the "signaled" state. It will be reset right before + // submitting the queue, then signaled again when the submission completes. + frame_fences: OnceCell>, + image_fences: Vec, + + frame_index: usize, } impl Surreality { @@ -174,8 +195,11 @@ impl Surreality { framebuffers: OnceCell::new(), command_pool: OnceCell::new(), command_buffers: OnceCell::new(), - image_available_semaphore: OnceCell::new(), - rendering_finished_semaphore: OnceCell::new(), + image_available_semaphores: OnceCell::new(), + rendering_finished_semaphores: OnceCell::new(), + frame_fences: OnceCell::new(), + image_fences: Vec::new(), + frame_index: 0, } } @@ -224,8 +248,8 @@ impl Surreality { self.init_commands()?; } - if self.image_available_semaphore.get().is_none() { - self.init_semaphores()?; + if self.image_available_semaphores.get().is_none() { + self.init_concurrency()?; } Ok(()) @@ -990,21 +1014,40 @@ impl Surreality { } #[allow(unsafe_code)] - fn init_semaphores(&mut self) -> Result<()> { + fn init_concurrency(&mut self) -> Result<()> { let device = self.device.get().unwrap(); + let swapchain_images = self.swapchain_images.get().unwrap(); let semaphore_info = vk::SemaphoreCreateInfo::builder(); + let fence_info = vk::FenceCreateInfo::builder() + .flags(vk::FenceCreateFlags::SIGNALED); - let image_available_semaphore = unsafe { - device.create_semaphore(&semaphore_info, None) - }?; + let mut image_available_semaphores = Vec::new(); + let mut rendering_finished_semaphores = Vec::new(); + let mut frame_fences = Vec::new(); - let rendering_finished_semaphore = unsafe { - device.create_semaphore(&semaphore_info, None) - }?; + for _ in 0 .. N_SIMULTANEOUS_FRAMES { + image_available_semaphores.push(unsafe { + device.create_semaphore(&semaphore_info, None) + }?); + + rendering_finished_semaphores.push(unsafe { + device.create_semaphore(&semaphore_info, None) + }?); + + frame_fences.push(unsafe { + device.create_fence(&fence_info, None) + }?); + } - self.image_available_semaphore.set(image_available_semaphore).unwrap(); - self.rendering_finished_semaphore - .set(rendering_finished_semaphore).unwrap(); + self.image_available_semaphores + .set(image_available_semaphores).unwrap(); + self.rendering_finished_semaphores + .set(rendering_finished_semaphores).unwrap(); + self.frame_fences.set(frame_fences).unwrap(); + + for _ in 0 .. swapchain_images.len() { + self.image_fences.push(vk::Fence::null()); + } Ok(()) } @@ -1297,19 +1340,31 @@ impl Surreality { let presentation_queue = self.presentation_queue.get().unwrap(); let swapchain = self.swapchain.get().unwrap(); let command_buffers = self.command_buffers.get().unwrap(); + let frame_index = self.frame_index; let image_available_semaphore - = self.image_available_semaphore.get().unwrap(); + = self.image_available_semaphores.get().unwrap()[frame_index]; let rendering_finished_semaphore - = self.rendering_finished_semaphore.get().unwrap(); + = self.rendering_finished_semaphores.get() + .unwrap()[frame_index]; + let frame_fence = self.frame_fences.get().unwrap()[frame_index]; + + unsafe { device.wait_for_fences(&[frame_fence], true, u64::MAX) }?; let image_index = unsafe { device.acquire_next_image_khr(*swapchain, u64::MAX, - *image_available_semaphore, + image_available_semaphore, vk::Fence::null()) }?.0 as usize; - let first_semaphores = [*image_available_semaphore]; - let second_semaphores = [*rendering_finished_semaphore]; + let image_fence = self.image_fences[image_index]; + if !image_fence.is_null() { + unsafe { device.wait_for_fences(&[image_fence], true, u64::MAX) }?; + } + + self.image_fences[image_index] = frame_fence; + + let first_semaphores = [image_available_semaphore]; + let second_semaphores = [rendering_finished_semaphore]; let wait_stages = [vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT]; let command_buffers = [command_buffers[image_index]]; @@ -1318,10 +1373,11 @@ impl Surreality { .wait_dst_stage_mask(&wait_stages) .command_buffers(&command_buffers) .signal_semaphores(&second_semaphores); + unsafe { device.reset_fences(&[frame_fence]) }?; unsafe { device.queue_submit(*graphics_queue, &[submit_info], - vk::Fence::null()) + frame_fence) }?; let swapchains = [*swapchain]; @@ -1334,6 +1390,8 @@ impl Surreality { unsafe { device.queue_present_khr(*presentation_queue, &present_info) }?; + + self.frame_index = (frame_index + 1) % N_SIMULTANEOUS_FRAMES; } Ok(()) @@ -1343,20 +1401,34 @@ impl Surreality { impl Drop for Surreality { #[allow(unsafe_code)] fn drop(&mut self) { - if let Some(image_available_semaphore) - = self.image_available_semaphore.get() + if let Some(device) = self.device.get() { + unsafe { device.device_wait_idle() }.unwrap(); + } + + if let Some(image_available_semaphores) + = self.image_available_semaphores.get() && let Some(device) = self.device.get() { - unsafe { device.destroy_semaphore(*image_available_semaphore, None) }; + for semaphore in image_available_semaphores { + unsafe { device.destroy_semaphore(*semaphore, None) }; + } } - if let Some(rendering_finished_semaphore) - = self.rendering_finished_semaphore.get() + if let Some(rendering_finished_semaphores) + = self.rendering_finished_semaphores.get() && let Some(device) = self.device.get() { - unsafe { - device.destroy_semaphore(*rendering_finished_semaphore, None) - }; + for semaphore in rendering_finished_semaphores { + unsafe { device.destroy_semaphore(*semaphore, None) }; + } + } + + if let Some(frame_fences) = self.frame_fences.get() + && let Some(device) = self.device.get() + { + for fence in frame_fences { + unsafe { device.destroy_fence(*fence, None) }; + } } if let Some(command_pool) = self.command_pool.get() @@ -1446,8 +1518,6 @@ impl ApplicationHandler for Surreality { fn window_event(&mut self, event_loop: &ActiveEventLoop, window_id: WindowId, event: WindowEvent) { - println!("window event {:?}", event); - match event { WindowEvent::RedrawRequested => { if !event_loop.exiting() { -- cgit 1.4.1