summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorIrene Knapp <ireneista@irenes.space>2026-07-06 17:31:27 -0700
committerIrene Knapp <ireneista@irenes.space>2026-07-06 19:15:56 -0700
commit5b4b3d5f61c6939b14c3d7d38662f00fc9530f73 (patch)
tree2672d6dabcabff2a31af66f082490c6c6c0ea6e6 /src
parente2ff6de8bf004753b363d858e094ea4da33e3071 (diff)
render a few frames at once
Force-Push: yes
Change-Id: Ibf09815b6d64c80242d623a1a5f387d6e7166f67
Diffstat (limited to 'src')
-rw-r--r--src/main.rs134
1 files changed, 102 insertions, 32 deletions
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<T> {
@@ -144,8 +148,25 @@ struct Surreality {
   command_pool: OnceCell<vk::CommandPool>,
   command_buffers: OnceCell<Vec<vk::CommandBuffer>>,
 
-  image_available_semaphore: OnceCell<vk::Semaphore>,
-  rendering_finished_semaphore: OnceCell<vk::Semaphore>,
+  image_available_semaphores: OnceCell<Vec<vk::Semaphore>>,
+  rendering_finished_semaphores: OnceCell<Vec<vk::Semaphore>>,
+
+  //   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<Vec<vk::Fence>>,
+  image_fences: Vec<vk::Fence>,
+
+  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() {