screen_13/driver/
swapchain.rs

1//! Native window presentation types.
2
3use {
4    super::{
5        DriverError, Surface,
6        device::Device,
7        image::{Image, ImageInfo},
8    },
9    ash::vk,
10    derive_builder::{Builder, UninitializedFieldError},
11    log::{debug, info, trace, warn},
12    std::{mem::replace, ops::Deref, slice, sync::Arc, thread::panicking},
13};
14
15// TODO: This needs to track completed command buffers and not constantly create semaphores
16
17/// Provides the ability to present rendering results to a [`Surface`].
18#[derive(Debug)]
19pub struct Swapchain {
20    device: Arc<Device>,
21    images: Box<[SwapchainImage]>,
22    info: SwapchainInfo,
23    old_swapchain: vk::SwapchainKHR,
24    suboptimal: bool,
25    surface: Surface,
26    swapchain: vk::SwapchainKHR,
27}
28
29impl Swapchain {
30    /// Prepares a [`vk::SwapchainKHR`] object which is lazily created after calling
31    /// [`acquire_next_image`][Self::acquire_next_image].
32    #[profiling::function]
33    pub fn new(
34        device: &Arc<Device>,
35        surface: Surface,
36        info: impl Into<SwapchainInfo>,
37    ) -> Result<Self, DriverError> {
38        let device = Arc::clone(device);
39        let info = info.into();
40
41        Ok(Swapchain {
42            device,
43            images: Default::default(),
44            info,
45            old_swapchain: vk::SwapchainKHR::null(),
46            suboptimal: true,
47            surface,
48            swapchain: vk::SwapchainKHR::null(),
49        })
50    }
51
52    /// Gets the next available swapchain image which should be rendered to and then presented using
53    /// [`present_image`][Self::present_image].
54    #[profiling::function]
55    pub fn acquire_next_image(
56        &mut self,
57        acquired: vk::Semaphore,
58    ) -> Result<SwapchainImage, SwapchainError> {
59        for _ in 0..2 {
60            if self.suboptimal {
61                self.recreate_swapchain().map_err(|err| {
62                    if matches!(err, DriverError::Unsupported) {
63                        SwapchainError::Suboptimal
64                    } else {
65                        SwapchainError::SurfaceLost
66                    }
67                })?;
68            }
69
70            let swapchain_ext = Device::expect_swapchain_ext(&self.device);
71
72            let image_idx = unsafe {
73                swapchain_ext.acquire_next_image(
74                    self.swapchain,
75                    u64::MAX,
76                    acquired,
77                    vk::Fence::null(),
78                )
79            }
80            .map(|(idx, suboptimal)| {
81                if suboptimal {
82                    debug!("acquired image is suboptimal");
83                }
84
85                self.suboptimal = suboptimal;
86
87                idx
88            });
89
90            match image_idx {
91                Ok(image_idx) => {
92                    let image_idx = image_idx as usize;
93
94                    assert!(image_idx < self.images.len());
95
96                    let image = unsafe { self.images.get_unchecked(image_idx) };
97                    let image = SwapchainImage::clone_swapchain(image);
98
99                    return Ok(replace(
100                        unsafe { self.images.get_unchecked_mut(image_idx) },
101                        image,
102                    ));
103                }
104                Err(err)
105                    if err == vk::Result::ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT
106                        || err == vk::Result::ERROR_OUT_OF_DATE_KHR
107                        || err == vk::Result::NOT_READY
108                        || err == vk::Result::TIMEOUT =>
109                {
110                    warn!("unable to acquire image: {err}");
111
112                    self.suboptimal = true;
113
114                    // Try again to see if we can acquire an image this frame
115                    // (Makes redraw during resize look slightly better)
116                    continue;
117                }
118                Err(err) if err == vk::Result::ERROR_DEVICE_LOST => {
119                    warn!("unable to acquire image: {err}");
120
121                    self.suboptimal = true;
122
123                    return Err(SwapchainError::DeviceLost);
124                }
125                Err(err) if err == vk::Result::ERROR_SURFACE_LOST_KHR => {
126                    warn!("unable to acquire image: {err}");
127
128                    self.suboptimal = true;
129
130                    return Err(SwapchainError::SurfaceLost);
131                }
132                Err(err) => {
133                    // Probably:
134                    // VK_ERROR_OUT_OF_HOST_MEMORY
135                    // VK_ERROR_OUT_OF_DEVICE_MEMORY
136
137                    // TODO: Maybe handle timeout in here
138
139                    warn!("unable to acquire image: {err}");
140
141                    return Err(SwapchainError::SurfaceLost);
142                }
143            }
144        }
145
146        Err(SwapchainError::Suboptimal)
147    }
148
149    fn clamp_desired_image_count(
150        desired_image_count: u32,
151        surface_capabilities: vk::SurfaceCapabilitiesKHR,
152    ) -> u32 {
153        let mut desired_image_count = desired_image_count.max(surface_capabilities.min_image_count);
154
155        if surface_capabilities.max_image_count != 0 {
156            desired_image_count = desired_image_count.min(surface_capabilities.max_image_count);
157        }
158
159        desired_image_count.min(u8::MAX as u32)
160    }
161
162    #[profiling::function]
163    fn destroy_swapchain(device: &Device, swapchain: &mut vk::SwapchainKHR) {
164        if *swapchain != vk::SwapchainKHR::null() {
165            // wait for device to be finished with swapchain before destroying it.
166            // This avoid crashes when resizing windows
167            #[cfg(target_os = "macos")]
168            if let Err(err) = unsafe { device.device_wait_idle() } {
169                warn!("device_wait_idle() failed: {err}");
170            }
171
172            let swapchain_ext = Device::expect_swapchain_ext(device);
173
174            unsafe {
175                swapchain_ext.destroy_swapchain(*swapchain, None);
176            }
177
178            *swapchain = vk::SwapchainKHR::null();
179        }
180    }
181
182    /// Gets information about this swapchain.
183    pub fn info(&self) -> SwapchainInfo {
184        self.info.clone()
185    }
186
187    /// Presents an image which has been previously acquired using
188    /// [`acquire_next_image`][Self::acquire_next_image].
189    #[profiling::function]
190    pub fn present_image(
191        &mut self,
192        image: SwapchainImage,
193        wait_semaphores: &[vk::Semaphore],
194        queue_family_index: u32,
195        queue_index: u32,
196    ) {
197        let queue_family_index = queue_family_index as usize;
198        let queue_index = queue_index as usize;
199
200        debug_assert!(
201            queue_family_index < self.device.physical_device.queue_families.len(),
202            "Queue family index must be within the range of the available queues created by the device."
203        );
204        debug_assert!(
205            queue_index
206                < self.device.physical_device.queue_families[queue_family_index].queue_count
207                    as usize,
208            "Queue index must be within the range of the available queues created by the device."
209        );
210
211        let present_info = vk::PresentInfoKHR::default()
212            .wait_semaphores(wait_semaphores)
213            .swapchains(slice::from_ref(&self.swapchain))
214            .image_indices(slice::from_ref(&image.image_idx));
215
216        let swapchain_ext = Device::expect_swapchain_ext(&self.device);
217
218        unsafe {
219            match swapchain_ext.queue_present(
220                self.device.queues[queue_family_index][queue_index],
221                &present_info,
222            ) {
223                Ok(_) => {
224                    Self::destroy_swapchain(&self.device, &mut self.old_swapchain);
225                }
226                Err(err)
227                    if err == vk::Result::ERROR_DEVICE_LOST
228                        || err == vk::Result::ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT
229                        || err == vk::Result::ERROR_OUT_OF_DATE_KHR
230                        || err == vk::Result::ERROR_SURFACE_LOST_KHR
231                        || err == vk::Result::SUBOPTIMAL_KHR =>
232                {
233                    // Handled in the next frame
234                    self.suboptimal = true;
235                }
236                Err(err) => {
237                    // Probably:
238                    // VK_ERROR_OUT_OF_HOST_MEMORY
239                    // VK_ERROR_OUT_OF_DEVICE_MEMORY
240                    warn!("{err}");
241                }
242            }
243        }
244
245        let image_idx = image.image_idx as usize;
246        self.images[image_idx] = image;
247    }
248
249    #[profiling::function]
250    fn recreate_swapchain(&mut self) -> Result<(), DriverError> {
251        Self::destroy_swapchain(&self.device, &mut self.old_swapchain);
252
253        let surface_caps = Surface::capabilities(&self.surface)?;
254        let present_modes = Surface::present_modes(&self.surface)?;
255
256        let desired_image_count =
257            Self::clamp_desired_image_count(self.info.desired_image_count, surface_caps);
258
259        let image_usage = self.supported_surface_usage(surface_caps.supported_usage_flags)?;
260
261        let (surface_width, surface_height) = match surface_caps.current_extent.width {
262            std::u32::MAX => (
263                // TODO: Maybe handle this case with aspect-correct clamping?
264                self.info.width.clamp(
265                    surface_caps.min_image_extent.width,
266                    surface_caps.max_image_extent.width,
267                ),
268                self.info.height.clamp(
269                    surface_caps.min_image_extent.height,
270                    surface_caps.max_image_extent.height,
271                ),
272            ),
273            _ => (
274                surface_caps.current_extent.width,
275                surface_caps.current_extent.height,
276            ),
277        };
278
279        if surface_width * surface_height == 0 {
280            return Err(DriverError::Unsupported);
281        }
282
283        let present_mode = self
284            .info
285            .present_modes
286            .iter()
287            .copied()
288            .find(|mode| present_modes.contains(mode))
289            .unwrap_or(vk::PresentModeKHR::FIFO);
290
291        let pre_transform = if surface_caps
292            .supported_transforms
293            .contains(vk::SurfaceTransformFlagsKHR::IDENTITY)
294        {
295            vk::SurfaceTransformFlagsKHR::IDENTITY
296        } else {
297            surface_caps.current_transform
298        };
299
300        let swapchain_ext = Device::expect_swapchain_ext(&self.device);
301        let swapchain_create_info = vk::SwapchainCreateInfoKHR::default()
302            .surface(*self.surface)
303            .min_image_count(desired_image_count)
304            .image_color_space(self.info.surface.color_space)
305            .image_format(self.info.surface.format)
306            .image_extent(vk::Extent2D {
307                width: surface_width,
308                height: surface_height,
309            })
310            .image_usage(image_usage)
311            .image_sharing_mode(vk::SharingMode::EXCLUSIVE)
312            .pre_transform(pre_transform)
313            .composite_alpha(vk::CompositeAlphaFlagsKHR::OPAQUE)
314            .present_mode(present_mode)
315            .clipped(true)
316            .old_swapchain(self.swapchain)
317            .image_array_layers(1);
318        let swapchain = unsafe { swapchain_ext.create_swapchain(&swapchain_create_info, None) }
319            .map_err(|err| {
320                warn!("{err}");
321
322                DriverError::Unsupported
323            })?;
324
325        let images =
326            unsafe { swapchain_ext.get_swapchain_images(swapchain) }.map_err(|err| match err {
327                vk::Result::INCOMPLETE => DriverError::InvalidData,
328                vk::Result::ERROR_OUT_OF_DEVICE_MEMORY | vk::Result::ERROR_OUT_OF_HOST_MEMORY => {
329                    DriverError::OutOfMemory
330                }
331                _ => DriverError::Unsupported,
332            })?;
333        let images = images
334            .into_iter()
335            .enumerate()
336            .map(|(image_idx, image)| {
337                let mut image = Image::from_raw(
338                    &self.device,
339                    image,
340                    ImageInfo::image_2d(
341                        surface_width,
342                        surface_height,
343                        self.info.surface.format,
344                        image_usage,
345                    ),
346                );
347
348                let image_idx = image_idx as u32;
349                image.name = Some(format!("swapchain{image_idx}"));
350
351                Ok(SwapchainImage {
352                    exec_idx: 0,
353                    image,
354                    image_idx,
355                })
356            })
357            .collect::<Result<Box<_>, _>>()?;
358
359        self.info.height = surface_height;
360        self.info.width = surface_width;
361        self.images = images;
362        self.old_swapchain = self.swapchain;
363        self.swapchain = swapchain;
364        self.suboptimal = false;
365
366        info!(
367            "swapchain {}x{} {present_mode:?}x{} {:?} {image_usage:#?}",
368            self.info.width,
369            self.info.height,
370            self.images.len(),
371            self.info.surface.format,
372        );
373
374        Ok(())
375    }
376
377    /// Sets information about this swapchain.
378    ///
379    /// Previously acquired swapchain images should be discarded after calling this function.
380    pub fn set_info(&mut self, info: impl Into<SwapchainInfo>) {
381        let info: SwapchainInfo = info.into();
382
383        if self.info != info {
384            // attempt to reducing flickering when resizing windows on mac
385            #[cfg(target_os = "macos")]
386            if let Err(err) = unsafe { self.device.device_wait_idle() } {
387                warn!("device_wait_idle() failed: {err}");
388            }
389
390            self.info = info;
391
392            trace!("info: {:?}", self.info);
393
394            self.suboptimal = true;
395        }
396    }
397
398    fn supported_surface_usage(
399        &mut self,
400        surface_capabilities: vk::ImageUsageFlags,
401    ) -> Result<vk::ImageUsageFlags, DriverError> {
402        let mut res = vk::ImageUsageFlags::empty();
403
404        for bit in 0..u32::BITS {
405            let usage = vk::ImageUsageFlags::from_raw((1 << bit) & surface_capabilities.as_raw());
406            if usage.is_empty() {
407                continue;
408            }
409
410            if Device::image_format_properties(
411                &self.device,
412                self.info.surface.format,
413                vk::ImageType::TYPE_2D,
414                vk::ImageTiling::OPTIMAL,
415                usage,
416                vk::ImageCreateFlags::empty(),
417            )
418            .inspect_err(|err| {
419                warn!(
420                    "unable to get image format properties: {:?} {:?} {err}",
421                    self.info.surface.format, usage
422                )
423            })?
424            .is_none()
425            {
426                continue;
427            }
428
429            res |= usage;
430        }
431
432        // On mesa the device will return this usage flag as supported even when the extension
433        // that is needed for an image to have this flag isn't enabled
434        res &= !vk::ImageUsageFlags::ATTACHMENT_FEEDBACK_LOOP_EXT;
435
436        Ok(res)
437    }
438}
439
440impl Drop for Swapchain {
441    #[profiling::function]
442    fn drop(&mut self) {
443        if panicking() {
444            return;
445        }
446
447        Self::destroy_swapchain(&self.device, &mut self.old_swapchain);
448        Self::destroy_swapchain(&self.device, &mut self.swapchain);
449    }
450}
451
452/// Describes the condition of a swapchain.
453#[derive(Clone, Copy, Debug, PartialEq)]
454pub enum SwapchainError {
455    /// This frame is lost but more may be acquired later.
456    DeviceLost,
457
458    /// This frame is not lost but there may be a delay while the next frame is recreated.
459    Suboptimal,
460
461    /// The surface was lost and must be recreated, which includes any operating system window.
462    SurfaceLost,
463}
464
465/// An opaque type representing a swapchain image.
466#[derive(Debug)]
467pub struct SwapchainImage {
468    pub(crate) exec_idx: usize,
469    image: Image,
470    image_idx: u32,
471}
472
473impl SwapchainImage {
474    pub(crate) fn clone_swapchain(this: &Self) -> Self {
475        let Self {
476            exec_idx,
477            image,
478            image_idx,
479        } = this;
480
481        Self {
482            exec_idx: *exec_idx,
483            image: Image::clone_swapchain(image),
484            image_idx: *image_idx,
485        }
486    }
487}
488
489impl Deref for SwapchainImage {
490    type Target = Image;
491
492    fn deref(&self) -> &Self::Target {
493        &self.image
494    }
495}
496
497/// Information used to create a [`Swapchain`] instance.
498#[derive(Builder, Clone, Debug, Eq, Hash, PartialEq)]
499#[builder(
500    build_fn(private, name = "fallible_build", error = "SwapchainInfoBuilderError"),
501    derive(Clone, Debug),
502    pattern = "owned"
503)]
504#[non_exhaustive]
505pub struct SwapchainInfo {
506    /// The desired, but not guaranteed, number of images that will be in the created swapchain.
507    ///
508    /// More images introduces more display lag, but smoother animation.
509    #[builder(default = "3")]
510    pub desired_image_count: u32,
511
512    /// The initial height of the surface.
513    pub height: u32,
514
515    /// The format and color space of the surface.
516    pub surface: vk::SurfaceFormatKHR,
517
518    /// `vk::PresentModeKHR` Determines timing and queueing with which frames are actually displayed to the user.
519    /// `present_modes` is a set of these modes ordered by preference. If the first mode is not available it will fall
520    /// back to the next, etc...    
521    ///
522    /// `vk::PresentModeKHR::FIFO` - Presentation frames are kept in a First-In-First-Out queue approximately 3 frames
523    /// long. Every vertical blanking period, the presentation engine will pop a frame off the queue to display. If
524    /// there is no frame to display, it will present the same frame again until the next vblank.
525    ///
526    /// When a present command is executed on the GPU, the presented image is added on the queue.
527    ///
528    /// * **Tearing:** No tearing will be observed.
529    /// * **Also known as**: "Vsync On"
530    ///
531    /// `vk::PresentModeKHR::FIFO_RELAXED` - Presentation frames are kept in a First-In-First-Out queue approximately 3
532    /// frames long. Every vertical blanking period, the presentation engine will pop a frame off the queue to display.
533    /// If there is no frame to display, it will present the same frame until there is a frame in the queue. The moment
534    /// there is a frame in the queue, it will immediately pop the frame off the queue.
535    ///
536    /// When a present command is executed on the GPU, the presented image is added on the queue.
537    ///
538    /// * **Tearing**:
539    ///   Tearing will be observed if frames last more than one vblank as the front buffer.
540    /// * **Also known as**: "Adaptive Vsync"
541    ///
542    /// `vk::PresentModeKHR::IMMEDIATE` - Presentation frames are not queued at all. The moment a present command is
543    /// executed on the GPU, the presented image is swapped onto the front buffer immediately.
544    ///
545    /// * **Tearing**: Tearing can be observed.
546    /// * **Also known as**: "Vsync Off"
547    ///
548    /// `vk::PresentModeKHR::MAILBOX` - Presentation frames are kept in a single-frame queue. Every vertical blanking
549    /// period, the presentation engine will pop a frame from the queue. If there is no frame to display, it will
550    /// present the same frame again until the next vblank.
551    ///
552    /// When a present command is executed on the GPU, the frame will be put into the queue.
553    /// If there was already a frame in the queue, the new frame will _replace_ the old frame
554    /// on the queue.
555    ///
556    /// * **Tearing**: No tearing will be observed.
557    /// * **Also known as**: "Fast Vsync"
558    #[builder(default = vec![vk::PresentModeKHR::FIFO_RELAXED, vk::PresentModeKHR::FIFO])]
559    pub present_modes: Vec<vk::PresentModeKHR>,
560
561    /// The initial width of the surface.
562    pub width: u32,
563}
564
565impl SwapchainInfo {
566    /// Specifies a default swapchain with the given `width`, `height` and `format` values.
567    #[inline(always)]
568    pub fn new(width: u32, height: u32, surface: vk::SurfaceFormatKHR) -> SwapchainInfo {
569        Self {
570            width,
571            height,
572            surface,
573            desired_image_count: 3,
574            present_modes: vec![vk::PresentModeKHR::FIFO_RELAXED, vk::PresentModeKHR::FIFO],
575        }
576    }
577
578    /// Converts a `SwapchainInfo` into a `SwapchainInfoBuilder`.
579    #[inline(always)]
580    pub fn to_builder(self) -> SwapchainInfoBuilder {
581        SwapchainInfoBuilder {
582            desired_image_count: Some(self.desired_image_count),
583            height: Some(self.height),
584            surface: Some(self.surface),
585            present_modes: Some(self.present_modes),
586            width: Some(self.width),
587        }
588    }
589}
590
591impl From<SwapchainInfoBuilder> for SwapchainInfo {
592    fn from(info: SwapchainInfoBuilder) -> Self {
593        info.build()
594    }
595}
596
597impl SwapchainInfoBuilder {
598    /// Builds a new `SwapchainInfo`.
599    ///
600    /// # Panics
601    ///
602    /// If any of the following values have not been set this function will panic:
603    ///
604    /// * `width`
605    /// * `height`
606    /// * `surface`
607    #[inline(always)]
608    pub fn build(self) -> SwapchainInfo {
609        match self.fallible_build() {
610            Err(SwapchainInfoBuilderError(err)) => panic!("{err}"),
611            Ok(info) => info,
612        }
613    }
614}
615
616#[derive(Debug)]
617struct SwapchainInfoBuilderError(UninitializedFieldError);
618
619impl From<UninitializedFieldError> for SwapchainInfoBuilderError {
620    fn from(err: UninitializedFieldError) -> Self {
621        Self(err)
622    }
623}
624
625#[cfg(test)]
626mod tests {
627    use super::*;
628
629    type Info = SwapchainInfo;
630    type Builder = SwapchainInfoBuilder;
631
632    #[test]
633    pub fn swapchain_info() {
634        let info = Info::new(20, 24, vk::SurfaceFormatKHR::default());
635        let builder = info.clone().to_builder().build();
636
637        assert_eq!(info, builder);
638    }
639
640    #[test]
641    pub fn swapchain_info_builder() {
642        let info = Info::new(23, 64, vk::SurfaceFormatKHR::default());
643        let builder = Builder::default()
644            .width(23)
645            .height(64)
646            .surface(vk::SurfaceFormatKHR::default())
647            .build();
648
649        assert_eq!(info, builder);
650    }
651
652    #[test]
653    #[should_panic(expected = "Field not initialized: height")]
654    pub fn swapchain_info_builder_uninit_height() {
655        Builder::default().build();
656    }
657
658    #[test]
659    #[should_panic(expected = "Field not initialized: surface")]
660    pub fn swapchain_info_builder_uninit_surface() {
661        Builder::default().height(42).build();
662    }
663
664    #[test]
665    #[should_panic(expected = "Field not initialized: width")]
666    pub fn swapchain_info_builder_uninit_width() {
667        Builder::default()
668            .height(42)
669            .surface(vk::SurfaceFormatKHR::default())
670            .build();
671    }
672}