erupt_bootstrap/
swapchain.rs

1//! Swapchain utils.
2//!
3//! Original implementation by [Ralith]
4//! (<https://github.com/MaikKlein/ash/pull/506>).
5//!
6//! [Ralith]: https://github.com/Ralith
7
8use erupt::{try_vk, utils::VulkanResult, vk, DeviceLoader, InstanceLoader, SmallVec, ObjectHandle};
9use std::{collections::VecDeque, mem};
10
11/// Manages synchronizing and rebuilding a Vulkan swapchain.
12pub struct Swapchain {
13    options: SwapchainOptions,
14
15    frames: Vec<Frame>,
16    frame_index: usize,
17
18    surface: vk::SurfaceKHR,
19    physical_device: vk::PhysicalDevice,
20    handle: vk::SwapchainKHR,
21    generation: u64,
22    images: SmallVec<vk::Image>,
23    extent: vk::Extent2D,
24    format: vk::SurfaceFormatKHR,
25    needs_rebuild: bool,
26
27    old_swapchains: VecDeque<(vk::SwapchainKHR, u64)>,
28}
29
30impl Swapchain {
31    /// Construct a new [`Swapchain`] for rendering at most `frames_in_flight` frames
32    /// concurrently. `extent` should be the current dimensions of `surface`.
33    pub fn new(
34        options: SwapchainOptions,
35        surface: vk::SurfaceKHR,
36        physical_device: vk::PhysicalDevice,
37        device: &DeviceLoader,
38        extent: vk::Extent2D,
39    ) -> Self {
40        Self {
41            frames: (0..options.frames_in_flight)
42                .map(|_| unsafe {
43                    Frame {
44                        complete: device
45                            .create_fence(
46                                &vk::FenceCreateInfoBuilder::new()
47                                    .flags(vk::FenceCreateFlags::SIGNALED),
48                                None,
49                            )
50                            .unwrap(),
51                        acquire: device
52                            .create_semaphore(&vk::SemaphoreCreateInfo::default(), None)
53                            .unwrap(),
54                        generation: 0,
55                    }
56                })
57                .collect(),
58            frame_index: 0,
59
60            surface,
61            physical_device,
62            handle: vk::SwapchainKHR::null(),
63            generation: 0,
64            images: SmallVec::new(),
65            extent,
66            format: vk::SurfaceFormatKHR::default(),
67            needs_rebuild: true,
68
69            old_swapchains: VecDeque::new(),
70
71            options,
72        }
73    }
74
75    /// Destroy all swapchain resources. Must not be called while any frames are still in flight on
76    /// the GPU.
77    ///
78    /// # Safety
79    ///
80    /// - `device` must match the `device` passed to [`Swapchain::new`].
81    /// - Access to images obtained from [`images`](Self::images) must be externally synchronized.
82    #[inline]
83    pub unsafe fn destroy(&mut self, device: &DeviceLoader) {
84        for frame in &self.frames {
85            device.destroy_fence(frame.complete, None);
86            device.destroy_semaphore(frame.acquire, None);
87        }
88
89        if self.handle != vk::SwapchainKHR::null() {
90            device.destroy_swapchain_khr(self.handle, None);
91        }
92
93        for &(swapchain, _) in &self.old_swapchains {
94            device.destroy_swapchain_khr(swapchain, None);
95        }
96    }
97
98    /// Force the swapchain to be rebuilt on the next [`acquire`](Self::acquire) call, passing in
99    /// the surface's current size.
100    #[inline]
101    pub fn update(&mut self, extent: vk::Extent2D) {
102        self.extent = extent;
103        self.needs_rebuild = true;
104    }
105
106    /// Maximum number of frames that may be concurrently rendered.
107    #[inline]
108    pub fn frames_in_flight(&self) -> usize {
109        self.frames.len()
110    }
111
112    /// Latest set of swapchain images, keyed by [`AcquiredFrame::image_index`].
113    #[inline]
114    pub fn images(&self) -> &[vk::Image] {
115        &self.images
116    }
117
118    /// Format of images in [`images`](Self::images), and the color space that will be used to
119    /// present them.
120    #[inline]
121    pub fn format(&self) -> vk::SurfaceFormatKHR {
122        self.format
123    }
124
125    /// Dimensions of images in [`images`](Self::images).
126    #[inline]
127    pub fn extent(&self) -> vk::Extent2D {
128        self.extent
129    }
130
131    /// Acquire resources to render a frame.
132    ///
133    /// Returns [`vk::Result::ERROR_OUT_OF_DATE_KHR`] if and only if the configured format or
134    /// present modes could not be satisfied.
135    ///
136    /// # Safety
137    ///
138    /// `device` must have been created from `instance` and must match the
139    /// `device` passed to [`Swapchain::new`].
140    pub unsafe fn acquire(
141        &mut self,
142        instance: &InstanceLoader,
143        device: &DeviceLoader,
144        timeout_ns: u64,
145    ) -> VulkanResult<AcquiredFrame> {
146        let frame_index = self.frame_index;
147        let next_frame_index = (self.frame_index + 1) % self.frames.len();
148        let frame = &self.frames[frame_index];
149        let acquire = frame.acquire;
150        try_vk!(device.wait_for_fences(&[frame.complete], true, timeout_ns));
151
152        // Destroy swapchains that are guaranteed not to be in use now that this frame has finished
153        while let Some(&(swapchain, generation)) = self.old_swapchains.front() {
154            if self.frames[next_frame_index].generation == generation {
155                break;
156            }
157            device.destroy_swapchain_khr(swapchain, None);
158            self.old_swapchains.pop_front();
159        }
160
161        loop {
162            if !self.needs_rebuild {
163                let acquire_next_image =
164                    device.acquire_next_image_khr(self.handle, !0, acquire, vk::Fence::null());
165                let suboptimal = acquire_next_image.raw == vk::Result::SUBOPTIMAL_KHR;
166                match acquire_next_image.result() {
167                    Ok(index) => {
168                        self.needs_rebuild = suboptimal;
169                        let invalidate_images =
170                            self.frames[frame_index].generation != self.generation;
171                        self.frames[frame_index].generation = self.generation;
172                        self.frame_index = next_frame_index;
173                        device
174                            .reset_fences(&[self.frames[frame_index].complete])
175                            .unwrap();
176                        return VulkanResult::new_ok(AcquiredFrame {
177                            image_index: index as usize,
178                            frame_index,
179                            ready: acquire,
180                            complete: self.frames[frame_index].complete,
181                            invalidate_images,
182                        });
183                    }
184                    Err(vk::Result::ERROR_OUT_OF_DATE_KHR) => {}
185                    Err(e) => return VulkanResult::new_err(e),
186                }
187            };
188            self.needs_rebuild = true;
189
190            // Rebuild swapchain
191            let surface_capabilities = try_vk!(instance
192                .get_physical_device_surface_capabilities_khr(self.physical_device, self.surface));
193
194            self.extent = match surface_capabilities.current_extent.width {
195                // If Vulkan doesn't know, the windowing system probably does. Known to apply at
196                // least to Wayland.
197                std::u32::MAX => vk::Extent2D {
198                    width: self.extent.width,
199                    height: self.extent.height,
200                },
201                _ => surface_capabilities.current_extent,
202            };
203
204            let pre_transform = if surface_capabilities
205                .supported_transforms
206                .contains(vk::SurfaceTransformFlagsKHR::IDENTITY_KHR)
207            {
208                vk::SurfaceTransformFlagBitsKHR::IDENTITY_KHR
209            } else {
210                surface_capabilities.current_transform
211            };
212
213            let present_modes = try_vk!(instance.get_physical_device_surface_present_modes_khr(
214                self.physical_device,
215                self.surface,
216                None
217            ));
218            let present_mode = match present_modes
219                .iter()
220                .filter_map(|&mode| {
221                    Some((
222                        mode,
223                        self.options
224                            .present_mode_preference
225                            .iter()
226                            .position(|&pref| pref == mode)?,
227                    ))
228                })
229                .min_by_key(|&(_, priority)| priority)
230            {
231                Some((mode, _)) => mode,
232                None => return VulkanResult::new_err(vk::Result::ERROR_OUT_OF_DATE_KHR),
233            };
234
235            let desired_image_count =
236                (surface_capabilities.min_image_count + 1).max(self.frames.len() as u32);
237            let image_count = if surface_capabilities.max_image_count > 0 {
238                surface_capabilities
239                    .max_image_count
240                    .min(desired_image_count)
241            } else {
242                desired_image_count
243            };
244
245            let surface_formats = try_vk!(instance.get_physical_device_surface_formats_khr(
246                self.physical_device,
247                self.surface,
248                None
249            ));
250            match surface_formats
251                .iter()
252                .filter_map(|&format| {
253                    Some((
254                        format,
255                        self.options
256                            .format_preference
257                            .iter()
258                            .position(|&pref| pref == format)?,
259                    ))
260                })
261                .min_by_key(|&(_, priority)| priority)
262            {
263                Some((format, _)) => self.format = format,
264                None => return VulkanResult::new_err(vk::Result::ERROR_OUT_OF_DATE_KHR),
265            };
266
267            if self.handle != vk::SwapchainKHR::null() {
268                self.old_swapchains
269                    .push_back((self.handle, self.generation));
270            }
271            let handle = try_vk!(device.create_swapchain_khr(
272                &vk::SwapchainCreateInfoKHRBuilder::new()
273                    .surface(self.surface)
274                    .min_image_count(image_count)
275                    .image_color_space(self.format.color_space)
276                    .image_format(self.format.format)
277                    .image_extent(self.extent)
278                    .image_usage(self.options.usage)
279                    .image_sharing_mode(self.options.sharing_mode)
280                    .pre_transform(pre_transform)
281                    .composite_alpha(self.options.composite_alpha)
282                    .present_mode(present_mode)
283                    .clipped(true)
284                    .image_array_layers(1)
285                    .old_swapchain(mem::replace(&mut self.handle, vk::SwapchainKHR::null())),
286                None,
287            ));
288            self.generation = self.generation.wrapping_add(1);
289            self.handle = handle;
290            self.images = try_vk!(device.get_swapchain_images_khr(handle, None));
291            self.needs_rebuild = false;
292        }
293    }
294
295    /// Queue presentation of a previously acquired image.
296    ///
297    /// # Safety
298    ///
299    /// In addition to the usual requirements of [`DeviceLoader::queue_present_khr`]:
300    ///
301    /// - `device` must match the `device` passed to [`Swapchain::new`].
302    /// - `image_index` must have been obtained from an [`AcquiredFrame::image_index`] from a
303    ///   previous [`acquire`](Self::acquire) call which has not yet been passed to queue_present.
304    /// - A command buffer that will signal `render_complete` after finishing access to the
305    ///   `image_index` element of [`images`](Self::images) must have been submitted.
306    #[inline]
307    pub unsafe fn queue_present(
308        &mut self,
309        device: &DeviceLoader,
310        queue: vk::Queue,
311        render_complete: vk::Semaphore,
312        image_index: usize,
313    ) -> VulkanResult<()> {
314        let queue_present = device.queue_present_khr(
315            queue,
316            &vk::PresentInfoKHRBuilder::new()
317                .wait_semaphores(&[render_complete])
318                .swapchains(&[self.handle])
319                .image_indices(&[image_index as u32]),
320        );
321
322        if let vk::Result::SUBOPTIMAL_KHR | vk::Result::ERROR_OUT_OF_DATE_KHR = queue_present.raw {
323            self.needs_rebuild = true;
324            VulkanResult::new_ok(())
325        } else {
326            queue_present
327        }
328    }
329}
330
331/// [`Swapchain`] configuration.
332#[derive(Debug, Clone)]
333pub struct SwapchainOptions {
334    frames_in_flight: usize,
335    format_preference: Vec<vk::SurfaceFormatKHR>,
336    present_mode_preference: Vec<vk::PresentModeKHR>,
337    usage: vk::ImageUsageFlags,
338    sharing_mode: vk::SharingMode,
339    composite_alpha: vk::CompositeAlphaFlagBitsKHR,
340}
341
342impl SwapchainOptions {
343    /// Uses the default values.
344    #[inline]
345    pub fn new() -> Self {
346        Self::default()
347    }
348
349    /// Number of frames that may be concurrently worked on, including recording on the CPU. Defaults to 2.
350    #[inline]
351    pub fn frames_in_flight(&mut self, frames: usize) -> &mut Self {
352        self.frames_in_flight = frames;
353        self
354    }
355
356    /// Preference-ordered list of image formats and color spaces. Defaults to 8-bit sRGB.
357    #[inline]
358    pub fn format_preference(&mut self, formats: &[vk::SurfaceFormatKHR]) -> &mut Self {
359        self.format_preference = formats.into();
360        self
361    }
362
363    /// Preference-ordered list of presentation modes. Defaults to [`vk::PresentModeKHR::FIFO_KHR`].
364    #[inline]
365    pub fn present_mode_preference(&mut self, modes: &[vk::PresentModeKHR]) -> &mut Self {
366        self.present_mode_preference = modes.into();
367        self
368    }
369
370    /// Required swapchain image usage flags. Defaults to [`vk::ImageUsageFlags::COLOR_ATTACHMENT`].
371    #[inline]
372    pub fn usage(&mut self, usage: vk::ImageUsageFlags) -> &mut Self {
373        self.usage = usage;
374        self
375    }
376
377    /// Requires swapchain image sharing mode. Defaults to [`vk::SharingMode::EXCLUSIVE`].
378    #[inline]
379    pub fn sharing_mode(&mut self, mode: vk::SharingMode) -> &mut Self {
380        self.sharing_mode = mode;
381        self
382    }
383
384    /// Requires swapchain image composite alpha. Defaults to [`vk::CompositeAlphaFlagBitsKHR::OPAQUE_KHR`].
385    #[inline]
386    pub fn composite_alpha(&mut self, value: vk::CompositeAlphaFlagBitsKHR) -> &mut Self {
387        self.composite_alpha = value;
388        self
389    }
390}
391
392impl Default for SwapchainOptions {
393    fn default() -> Self {
394        Self {
395            frames_in_flight: 2,
396            format_preference: vec![
397                vk::SurfaceFormatKHR {
398                    format: vk::Format::B8G8R8A8_SRGB,
399                    color_space: vk::ColorSpaceKHR::SRGB_NONLINEAR_KHR,
400                },
401                vk::SurfaceFormatKHR {
402                    format: vk::Format::R8G8B8A8_SRGB,
403                    color_space: vk::ColorSpaceKHR::SRGB_NONLINEAR_KHR,
404                },
405            ],
406            present_mode_preference: vec![vk::PresentModeKHR::FIFO_KHR],
407            usage: vk::ImageUsageFlags::COLOR_ATTACHMENT,
408            sharing_mode: vk::SharingMode::EXCLUSIVE,
409            composite_alpha: vk::CompositeAlphaFlagBitsKHR::OPAQUE_KHR,
410        }
411    }
412}
413
414struct Frame {
415    complete: vk::Fence,
416    acquire: vk::Semaphore,
417    generation: u64,
418}
419
420/// Information necessary to render a frame, from [`Swapchain::acquire`]
421#[derive(Debug, Copy, Clone)]
422pub struct AcquiredFrame {
423    /// Index of the image to write to in [`Swapchain::images`].
424    pub image_index: usize,
425    /// Index of the frame in flight, for use tracking your own per-frame resources, which may be
426    /// accessed immediately after [`Swapchain::acquire`] returns.
427    pub frame_index: usize,
428    /// Must be waited on before accessing the image associated with `image_index`.
429    pub ready: vk::Semaphore,
430    /// Must be signaled when access to the image associated with `image_index` and any per-frame
431    /// resources associated with `frame_index` is complete.
432    pub complete: vk::Fence,
433    /// Set whenever [`Swapchain::images`] has, and [`Swapchain::extent`] and [`Swapchain::format`]
434    /// may have, changed since the last [`Swapchain::acquire`] call. Use this to invalidate derived
435    /// resources like [`vk::ImageView`]s and [`vk::Framebuffer`]s, taking care not to destroy them
436    /// until at least [`SwapchainOptions::frames_in_flight`] new frames have been acquired, including this
437    /// one.
438    pub invalidate_images: bool,
439}