demo_vk/graphics/vulkan/frames_in_flight/
mod.rs

1use {
2    crate::{
3        graphics::vulkan::{
4            raii, AcquireImageStatus, PresentImageStatus, Swapchain,
5            VulkanContext,
6        },
7        unwrap_here,
8    },
9    anyhow::{Context, Result},
10    ash::vk::{self, Handle},
11    std::{ffi::CString, sync::Arc},
12};
13
14#[derive(Debug, Copy, Clone, PartialEq, Eq)]
15pub enum FrameStatus {
16    /// Indicates that the frame is started.
17    ///
18    /// The command buffer is still owned by the FramesInFlight and does not
19    /// need to be freed by the caller.
20    FrameStarted(Frame),
21
22    /// Indicates that the swapchain needs to be rebuilt.
23    SwapchainNeedsRebuild,
24}
25
26/// A Frame is guaranteed to be synchronized such that no two frames with the
27/// same frame_index can be in-flight on the GPU at the same time.
28#[derive(Debug, Copy, Clone, PartialEq, Eq)]
29pub struct Frame {
30    command_buffer: vk::CommandBuffer,
31    swapchain_image_index: u32,
32    frame_index: usize,
33    swapchain_image: vk::Image,
34    swapchain_image_view: vk::ImageView,
35}
36
37impl Frame {
38    pub fn command_buffer(&self) -> vk::CommandBuffer {
39        self.command_buffer
40    }
41
42    pub fn swapchain_image_index(&self) -> u32 {
43        self.swapchain_image_index
44    }
45
46    pub fn frame_index(&self) -> usize {
47        self.frame_index
48    }
49
50    pub fn swapchain_image(&self) -> vk::Image {
51        self.swapchain_image
52    }
53
54    pub fn swapchain_image_view(&self) -> vk::ImageView {
55        self.swapchain_image_view
56    }
57}
58
59/// Used to track the status of each [FrameSync] instance.
60#[derive(Debug, Eq, PartialEq, Copy, Clone)]
61enum FrameSyncStatus {
62    /// Indicates that the frame is currently being assembled. e.g. someone
63    /// called start_frame, acquired this frame, and has not yet called
64    /// present_frame.
65    Assembling,
66
67    /// Indicates that present_frame was called and the frame is in use by the
68    /// GPU.
69    Pending,
70}
71
72/// Per-frame synchronization primitives.
73#[derive(Debug)]
74struct FrameSync {
75    status: FrameSyncStatus,
76    swapchain_image_acquired: raii::Semaphore,
77    graphics_commands_complete: raii::Fence,
78    command_pool: raii::CommandPool,
79    command_buffer: vk::CommandBuffer,
80}
81
82/// The fundamental synchronization mechanism for an application with N "frames
83/// in flight" where frame K's graphics command buffer can be recorded while
84/// frames K+1, K+2, ... K+N are all in-progress rendering on the GPU.
85///
86/// For example, when there are 3 frames in flight, the sequence can be
87/// visualized like so:
88#[doc = simple_mermaid::mermaid!("./frames_in_flight_diagram.mmd")]
89///
90/// The application takes ownership of the Frame object when calling
91/// [Self::start_frame] and returns ownership when calling
92/// [Self::present_frame]. As such, any time a system needs to access a
93/// frame-specific resource, the best option is to accept an [Frame] borrow and
94/// use [Frame::frame_index] to select frame-specific resources. This is certain
95/// to be safe because the only time the application has a [Frame] instance is
96/// when that frame's previous commands have finished executing.
97#[derive(Debug)]
98pub struct FramesInFlight {
99    // Used to synchronize the calls to QueueSubmit and Present per swapchain
100    // image.
101    swapchain_image_present_semaphores: Vec<raii::Semaphore>,
102
103    frames: Vec<FrameSync>,
104    frame_index: usize,
105    cxt: Arc<VulkanContext>,
106}
107
108impl FramesInFlight {
109    /// Creates a new instance with `frame_count` frames.
110    pub fn new(
111        ctx: Arc<VulkanContext>,
112        swapchain_image_count: usize,
113        frame_count: usize,
114    ) -> Result<Self> {
115        // Create one semaphore per swapchain image
116        let mut swapchain_image_present_semaphores =
117            Vec::with_capacity(swapchain_image_count);
118        for i in 0..swapchain_image_count {
119            swapchain_image_present_semaphores.push(unwrap_here!(
120                format!("Create present semaphore for swapchain image [{}]", i),
121                raii::Semaphore::new(
122                    format!("Swapchain Image Present [{}]", i),
123                    ctx.device.clone(),
124                    &vk::SemaphoreCreateInfo::default(),
125                )
126            ));
127        }
128
129        // create the per-frame synchronization primitives
130        let mut frames = Vec::with_capacity(frame_count);
131        for index in 0..frame_count {
132            let command_pool = unwrap_here!(
133                format!("Create command pool for frame {}", index),
134                raii::CommandPool::new(
135                    format!("frame [{}]", index),
136                    ctx.device.clone(),
137                    &vk::CommandPoolCreateInfo {
138                        flags: vk::CommandPoolCreateFlags::TRANSIENT,
139                        queue_family_index: ctx.graphics_queue_family_index,
140                        ..Default::default()
141                    },
142                )
143            );
144            let command_buffer = unwrap_here!(
145                format!("Create command buffer for frame {}", index),
146                unsafe {
147                    ctx.allocate_command_buffers(
148                        &vk::CommandBufferAllocateInfo {
149                            command_pool: command_pool.raw,
150                            level: vk::CommandBufferLevel::PRIMARY,
151                            command_buffer_count: 1,
152                            ..Default::default()
153                        },
154                    )?
155                    .first()
156                    .copied()
157                    .context("Expected exactly one command buffer")
158                }
159            );
160            let buffer_name =
161                CString::new(format!("Frame [{}] Commands", index)).unwrap();
162            ctx.device
163                .set_debug_name(&vk::DebugUtilsObjectNameInfoEXT {
164                    object_type: vk::ObjectType::COMMAND_BUFFER,
165                    object_handle: command_buffer.as_raw(),
166                    p_object_name: buffer_name.as_ptr(),
167                    ..Default::default()
168                })?;
169            frames.push(FrameSync {
170                status: FrameSyncStatus::Pending,
171                swapchain_image_acquired: unwrap_here!(
172                    format!(
173                        "Create image acquired semaphore for frame {index}"
174                    ),
175                    raii::Semaphore::new(
176                        format!("image acquired - frame [{}]", index),
177                        ctx.device.clone(),
178                        &vk::SemaphoreCreateInfo::default(),
179                    )
180                ),
181                graphics_commands_complete: unwrap_here!(
182                    format!("Create graphics cmd fence for frame {index}"),
183                    raii::Fence::new(
184                        format!(
185                            "graphics commands complete - frame [{}]",
186                            index
187                        ),
188                        ctx.device.clone(),
189                        &vk::FenceCreateInfo {
190                            flags: vk::FenceCreateFlags::SIGNALED,
191                            ..Default::default()
192                        },
193                    )
194                ),
195                command_pool,
196                command_buffer,
197            });
198        }
199        Ok(Self {
200            swapchain_image_present_semaphores,
201            frames,
202            frame_index: 0,
203            cxt: ctx,
204        })
205    }
206
207    /// Rebuilds the swapchain semaphores.
208    ///
209    /// This should only be required after the swapchain is rebuilt.
210    ///
211    /// Rebuilding semaphores cannot be done until all images have finished,
212    /// which means waiting for a full pipeline stall.
213    pub fn rebuild_swapchain_semaphores(
214        &mut self,
215        ctx: &VulkanContext,
216        swapchain_image_count: usize,
217    ) -> Result<()> {
218        self.wait_for_all_frames_to_complete()?;
219
220        // Needed to ensure that all resources are finished being used before
221        // continuing
222        unsafe { ctx.device_wait_idle()? };
223
224        self.swapchain_image_present_semaphores.clear();
225        // Create one semaphore per swapchain image
226        for i in 0..swapchain_image_count {
227            self.swapchain_image_present_semaphores.push(unwrap_here!(
228                format!("Create present semaphore for swapchain image [{}]", i),
229                raii::Semaphore::new(
230                    format!("Swapchain Image Present [{}]", i),
231                    ctx.device.clone(),
232                    &vk::SemaphoreCreateInfo::default(),
233                )
234            ));
235        }
236
237        for (i, frame_sync) in self.frames.iter_mut().enumerate() {
238            frame_sync.swapchain_image_acquired = unwrap_here!(
239                format!("Rebuild frame {i} swapchain image acquired semaphore"),
240                raii::Semaphore::new(
241                    format!("Swapchain Image Present [{}]", i),
242                    ctx.device.clone(),
243                    &vk::SemaphoreCreateInfo::default(),
244                )
245            );
246        }
247
248        Ok(())
249    }
250
251    /// Get the total number of configured frames in flight.
252    pub fn frame_count(&self) -> usize {
253        self.frames.len()
254    }
255
256    /// Blocks until all submitted commands for all frames have completed.
257    ///
258    /// NOTE: Only waits on pending frames. If there's a frame mid-assembly,
259    ///       there's nothing to wait on. (and what's more, attempting to wait
260    ///       would never succeed until the frame is submitted)
261    pub fn wait_for_all_frames_to_complete(&self) -> Result<()> {
262        let fences = self
263            .frames
264            .iter()
265            .filter(|frame_sync| frame_sync.status == FrameSyncStatus::Pending)
266            .map(|frame_sync| frame_sync.graphics_commands_complete.raw)
267            .collect::<Vec<vk::Fence>>();
268        unsafe {
269            self.cxt
270                .wait_for_fences(&fences, true, u64::MAX)
271                .context("wait for all pending frames to complete")
272        }
273    }
274
275    /// Starts the next frame in flight.
276    ///
277    /// This method *can* block if all frames are in flight. It will block until
278    /// the next frame is available.
279    ///
280    /// # Returns
281    ///
282    /// A [FrameStatus] containing one of:
283    /// * A [Frame] that must be returned to [Self::present_frame]
284    /// * A flag indicating that the Swapchain needs to be rebuilt before the
285    ///   next frame.
286    pub fn start_frame(
287        &mut self,
288        swapchain: &Swapchain,
289    ) -> Result<FrameStatus> {
290        self.frame_index = (self.frame_index + 1) % self.frames.len();
291
292        let frame_sync = &mut self.frames[self.frame_index];
293
294        // Wait for the last frame's submission to complete, if its still
295        // running.
296        unwrap_here!("Wait for the previous submission to complete", unsafe {
297            self.cxt.wait_for_fences(
298                &[frame_sync.graphics_commands_complete.raw],
299                true,
300                u64::MAX,
301            )
302        });
303
304        // Acquire the next Swapchain image
305        let status = unwrap_here!(
306            "Acquire the next swapchain image",
307            swapchain.acquire_image(frame_sync.swapchain_image_acquired.raw)
308        );
309        let swapchain_image_index = match status {
310            AcquireImageStatus::ImageAcquired(index) => index,
311            _ => {
312                return Ok(FrameStatus::SwapchainNeedsRebuild);
313            }
314        };
315
316        // NOTE: only reset the frame's fence and command buffer _after_
317        // acquiring a swapchain image. The order matters because the frame
318        // will not be processed if the swapchain is out-of-date and needs
319        // to be reconstructed.
320
321        // Swapchain image is available, so reset the commands fence.
322        unwrap_here!("Reset the frame's fence", unsafe {
323            self.cxt
324                .reset_fences(&[frame_sync.graphics_commands_complete.raw])
325        });
326        // mark the frame as pending so nobody gets stuck waiting for it
327        frame_sync.status = FrameSyncStatus::Assembling;
328
329        // Start the Frame's command buffer.
330        unwrap_here!("Reset the frame's command pool", unsafe {
331            self.cxt.reset_command_pool(
332                frame_sync.command_pool.raw,
333                vk::CommandPoolResetFlags::empty(),
334            )
335        });
336        unwrap_here!("Begin the frame's command buffer", unsafe {
337            self.cxt.begin_command_buffer(
338                frame_sync.command_buffer,
339                &vk::CommandBufferBeginInfo {
340                    flags: vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT,
341                    ..Default::default()
342                },
343            )
344        });
345
346        Ok(FrameStatus::FrameStarted(Frame {
347            command_buffer: frame_sync.command_buffer,
348            swapchain_image_index,
349            frame_index: self.frame_index,
350            swapchain_image: swapchain.images()[swapchain_image_index as usize],
351            swapchain_image_view: swapchain.image_views()
352                [swapchain_image_index as usize]
353                .raw,
354        }))
355    }
356
357    /// Queues the [Frame]'s command buffer and swapchain presentation.
358    pub fn present_frame(
359        &mut self,
360        swapchain: &Swapchain,
361        frame: Frame,
362    ) -> Result<PresentImageStatus> {
363        let frame_sync = &mut self.frames[frame.frame_index()];
364        unwrap_here!("End the frame's command buffer", unsafe {
365            self.cxt.end_command_buffer(frame_sync.command_buffer)
366        });
367
368        let wait_stage = vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT;
369        unwrap_here!("Submit the frame's primary command buffer", unsafe {
370            self.cxt.queue_submit(
371                self.cxt.graphics_queue,
372                &[vk::SubmitInfo {
373                    wait_semaphore_count: 1,
374                    p_wait_semaphores: &frame_sync.swapchain_image_acquired.raw,
375                    p_wait_dst_stage_mask: &wait_stage,
376                    command_buffer_count: 1,
377                    p_command_buffers: &frame_sync.command_buffer,
378                    signal_semaphore_count: 1,
379                    p_signal_semaphores: &self
380                        .swapchain_image_present_semaphores
381                        [frame.swapchain_image_index as usize]
382                        .raw,
383                    ..Default::default()
384                }],
385                frame_sync.graphics_commands_complete.raw,
386            )
387        });
388        frame_sync.status = FrameSyncStatus::Pending;
389
390        swapchain.present_image(
391            self.swapchain_image_present_semaphores
392                [frame.swapchain_image_index as usize]
393                .raw,
394            frame.swapchain_image_index(),
395        )
396    }
397}
398
399impl Drop for FramesInFlight {
400    fn drop(&mut self) {
401        self.wait_for_all_frames_to_complete().unwrap();
402        unsafe {
403            self.cxt.device_wait_idle().unwrap();
404        }
405    }
406}