demo_vk/graphics/vulkan/frames_in_flight/
mod.rs

1use {
2    crate::{
3        graphics::vulkan::{
4            raii, AcquireImageStatus, PresentImageStatus, Swapchain,
5            VulkanContext,
6        },
7        trace,
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(raii::Semaphore::new(
120                format!("Swapchain Image Present [{}]", i),
121                ctx.device.clone(),
122                &vk::SemaphoreCreateInfo::default(),
123            )?);
124        }
125
126        // create the per-frame synchronization primitives
127        let mut frames = Vec::with_capacity(frame_count);
128        for index in 0..frame_count {
129            let command_pool = raii::CommandPool::new(
130                format!("frame [{}]", index),
131                ctx.device.clone(),
132                &vk::CommandPoolCreateInfo {
133                    flags: vk::CommandPoolCreateFlags::TRANSIENT,
134                    queue_family_index: ctx.graphics_queue_family_index,
135                    ..Default::default()
136                },
137            )
138            .with_context(trace!(
139                "Error while creating command pool for frame {}",
140                index
141            ))?;
142            let command_buffer = unsafe {
143                ctx.allocate_command_buffers(&vk::CommandBufferAllocateInfo {
144                    command_pool: command_pool.raw,
145                    level: vk::CommandBufferLevel::PRIMARY,
146                    command_buffer_count: 1,
147                    ..Default::default()
148                })?[0]
149            };
150            let buffer_name =
151                CString::new(format!("Frame [{}] Commands", index)).unwrap();
152            ctx.device
153                .set_debug_name(&vk::DebugUtilsObjectNameInfoEXT {
154                    object_type: vk::ObjectType::COMMAND_BUFFER,
155                    object_handle: command_buffer.as_raw(),
156                    p_object_name: buffer_name.as_ptr(),
157                    ..Default::default()
158                })?;
159            frames.push(FrameSync {
160                status: FrameSyncStatus::Pending,
161                swapchain_image_acquired: raii::Semaphore::new(
162                    format!("image acquired - frame [{}]", index),
163                    ctx.device.clone(),
164                    &vk::SemaphoreCreateInfo::default(),
165                )
166                .with_context(trace!(
167                    "Error while creating semaphore for frame {}",
168                    index,
169                ))?,
170                graphics_commands_complete: raii::Fence::new(
171                    format!("graphics commands complete - frame [{}]", index),
172                    ctx.device.clone(),
173                    &vk::FenceCreateInfo {
174                        flags: vk::FenceCreateFlags::SIGNALED,
175                        ..Default::default()
176                    },
177                )
178                .with_context(trace!(
179                    "Error creating fence for frame {}",
180                    index
181                ))?,
182                command_pool,
183                command_buffer,
184            });
185        }
186        Ok(Self {
187            swapchain_image_present_semaphores,
188            frames,
189            frame_index: 0,
190            cxt: ctx,
191        })
192    }
193
194    /// Get the total number of configured frames in flight.
195    pub fn frame_count(&self) -> usize {
196        self.frames.len()
197    }
198
199    /// Blocks until all submitted commands for all frames have completed.
200    ///
201    /// NOTE: Only waits on pending frames. If there's a frame mid-assembly,
202    ///       there's nothing to wait on. (and what's more, attempting to wait
203    ///       would never succeed until the frame is submitted)
204    pub fn wait_for_all_frames_to_complete(&self) -> Result<()> {
205        let fences = self
206            .frames
207            .iter()
208            .filter(|frame_sync| frame_sync.status == FrameSyncStatus::Pending)
209            .map(|frame_sync| frame_sync.graphics_commands_complete.raw)
210            .collect::<Vec<vk::Fence>>();
211        unsafe {
212            self.cxt
213                .wait_for_fences(&fences, true, u64::MAX)
214                .with_context(trace!(
215                    "Error while waiting for all frames to finish rendering!"
216                ))?;
217        }
218        Ok(())
219    }
220
221    /// Starts the next frame in flight.
222    ///
223    /// This method *can* block if all frames are in flight. It will block until
224    /// the next frame is available.
225    ///
226    /// # Returns
227    ///
228    /// A [FrameStatus] containing one of:
229    /// * A [Frame] that must be returned to [Self::present_frame]
230    /// * A flag indicating that the Swapchain needs to be rebuilt before the
231    ///   next frame.
232    pub fn start_frame(
233        &mut self,
234        swapchain: &Swapchain,
235    ) -> Result<FrameStatus> {
236        self.frame_index = (self.frame_index + 1) % self.frames.len();
237
238        // Start the Frame
239        let frame_sync = &mut self.frames[self.frame_index];
240        unsafe {
241            // Wait for the last frame's submission to complete, if its still
242            // running.
243            self.cxt
244                .wait_for_fences(
245                    &[frame_sync.graphics_commands_complete.raw],
246                    true,
247                    u64::MAX,
248                )
249                .with_context(trace!(
250                    "Error while waiting for frame's commands to complete!"
251                ))?;
252            self.cxt
253                .reset_fences(&[frame_sync.graphics_commands_complete.raw])
254                .with_context(trace!(
255                    "Error while resetting the frame's fence!"
256                ))?;
257            // mark the frame as pending so nobody gets stuck waiting for it
258            frame_sync.status = FrameSyncStatus::Assembling;
259        };
260
261        // Acquire the next Swapchain image
262        let status = swapchain
263            .acquire_image(frame_sync.swapchain_image_acquired.raw)
264            .with_context(trace!(
265                "Error while acquiring swapchain image for frame!"
266            ))?;
267        let swapchain_image_index = match status {
268            AcquireImageStatus::ImageAcquired(index) => index,
269            _ => {
270                return Ok(FrameStatus::SwapchainNeedsRebuild);
271            }
272        };
273
274        // Start the Frame's command buffer.
275        unsafe {
276            self.cxt
277                .reset_command_pool(
278                    frame_sync.command_pool.raw,
279                    vk::CommandPoolResetFlags::empty(),
280                )
281                .with_context(trace!(
282                    "Error while resetting command buffer for frame!"
283                ))?;
284            self.cxt
285                .begin_command_buffer(
286                    frame_sync.command_buffer,
287                    &vk::CommandBufferBeginInfo {
288                        flags: vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT,
289                        ..Default::default()
290                    },
291                )
292                .with_context(trace!(
293                    "Error while beginning the frame's command buffer!"
294                ))?;
295        };
296
297        Ok(FrameStatus::FrameStarted(Frame {
298            command_buffer: frame_sync.command_buffer,
299            swapchain_image_index,
300            frame_index: self.frame_index,
301            swapchain_image: swapchain.images()[swapchain_image_index as usize],
302            swapchain_image_view: swapchain.image_views()
303                [swapchain_image_index as usize]
304                .raw,
305        }))
306    }
307
308    /// Queues the [Frame]'s command buffer and swapchain presentation.
309    pub fn present_frame(
310        &mut self,
311        swapchain: &Swapchain,
312        frame: Frame,
313    ) -> Result<PresentImageStatus> {
314        let frame_sync = &mut self.frames[frame.frame_index()];
315        unsafe {
316            self.cxt
317                .end_command_buffer(frame_sync.command_buffer)
318                .with_context(trace!(
319                    "Error while ending the command buffer!"
320                ))?;
321
322            let wait_stage = vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT;
323            self.cxt
324                .queue_submit(
325                    self.cxt.graphics_queue,
326                    &[vk::SubmitInfo {
327                        wait_semaphore_count: 1,
328                        p_wait_semaphores: &frame_sync
329                            .swapchain_image_acquired
330                            .raw,
331                        p_wait_dst_stage_mask: &wait_stage,
332                        command_buffer_count: 1,
333                        p_command_buffers: &frame_sync.command_buffer,
334                        signal_semaphore_count: 1,
335                        p_signal_semaphores: &self
336                            .swapchain_image_present_semaphores
337                            [frame.swapchain_image_index as usize]
338                            .raw,
339                        ..Default::default()
340                    }],
341                    frame_sync.graphics_commands_complete.raw,
342                )
343                .with_context(trace!(
344                    "Error while submitting frame commands!"
345                ))?;
346        }
347        frame_sync.status = FrameSyncStatus::Pending;
348
349        swapchain
350            .present_image(
351                self.swapchain_image_present_semaphores
352                    [frame.swapchain_image_index as usize]
353                    .raw,
354                frame.swapchain_image_index(),
355            )
356            .with_context(trace!("Error while presenting swapchain image!"))
357    }
358}
359
360impl Drop for FramesInFlight {
361    fn drop(&mut self) {
362        self.wait_for_all_frames_to_complete().unwrap();
363        unsafe {
364            self.cxt.device_wait_idle().unwrap();
365        }
366    }
367}