Skip to main content

vulkanalia_bootstrap/
swapchain.rs

1use crate::Device;
2use crate::Instance;
3use crate::device::QueueType;
4use crate::error::FormatError;
5use std::sync::atomic::{AtomicU64, Ordering};
6use std::sync::{Arc, Mutex};
7use vulkanalia::Version;
8use vulkanalia::vk;
9use vulkanalia::vk::DeviceV1_0;
10use vulkanalia::vk::HasBuilder;
11use vulkanalia::vk::KhrSurfaceExtensionInstanceCommands;
12use vulkanalia::vk::KhrSwapchainExtensionDeviceCommands;
13use vulkanalia::vk::{AllocationCallbacks, Handle, SwapchainKHR};
14
15#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq)]
16enum Priority {
17    Main,
18    Fallback,
19}
20
21#[derive(Debug, Clone)]
22struct Format {
23    inner: vk::SurfaceFormat2KHR,
24    priority: Priority,
25}
26
27#[derive(Debug, Clone)]
28struct PresentMode {
29    inner: vk::PresentModeKHR,
30    priority: Priority,
31}
32
33pub struct SwapchainBuilder {
34    instance: Arc<Instance>,
35    device: Arc<Device>,
36    allocation_callbacks: Option<AllocationCallbacks>,
37    desired_formats: Vec<Format>,
38    create_flags: vk::SwapchainCreateFlagsKHR,
39    desired_width: u32,
40    desired_height: u32,
41    array_layer_count: u32,
42    min_image_count: u32,
43    required_min_image_count: u32,
44    image_usage_flags: vk::ImageUsageFlags,
45    composite_alpha_flags_khr: vk::CompositeAlphaFlagsKHR,
46    desired_present_modes: Vec<PresentMode>,
47    pre_transform: vk::SurfaceTransformFlagsKHR,
48    clipped: bool,
49    old_swapchain: AtomicU64,
50    graphics_queue_index: usize,
51    present_queue_index: usize,
52}
53
54struct SurfaceFormatDetails {
55    capabilities: vk::SurfaceCapabilitiesKHR,
56    formats: Vec<vk::SurfaceFormatKHR>,
57    present_modes: Vec<vk::PresentModeKHR>,
58}
59
60fn query_surface_support_details(
61    phys_device: vk::PhysicalDevice,
62    instance: &vulkanalia::Instance,
63    surface: Option<vk::SurfaceKHR>,
64) -> crate::Result<SurfaceFormatDetails> {
65    let Some(surface) = surface else {
66        return Err(crate::SwapchainError::SurfaceHandleNotProvided.into());
67    };
68
69    let capabilities =
70        unsafe { instance.get_physical_device_surface_capabilities_khr(phys_device, surface) }?;
71    let formats =
72        unsafe { instance.get_physical_device_surface_formats_khr(phys_device, surface) }?;
73    let present_modes =
74        unsafe { instance.get_physical_device_surface_present_modes_khr(phys_device, surface) }?;
75
76    Ok(SurfaceFormatDetails {
77        capabilities,
78        formats,
79        present_modes,
80    })
81}
82
83fn default_formats<'a>() -> Vec<Format> {
84    vec![
85        Format {
86            inner: vk::SurfaceFormat2KHR {
87                surface_format: vk::SurfaceFormatKHR {
88                    format: vk::Format::B8G8R8A8_SRGB,
89                    color_space: vk::ColorSpaceKHR::SRGB_NONLINEAR,
90                    ..Default::default()
91                },
92                ..Default::default()
93            },
94            priority: Priority::Main,
95        },
96        Format {
97            inner: vk::SurfaceFormat2KHR {
98                surface_format: vk::SurfaceFormatKHR {
99                    format: vk::Format::R8G8B8_SRGB,
100                    color_space: vk::ColorSpaceKHR::SRGB_NONLINEAR,
101                    ..Default::default()
102                },
103                ..Default::default()
104            },
105            priority: Priority::Fallback,
106        },
107    ]
108}
109
110fn default_present_modes() -> Vec<PresentMode> {
111    vec![
112        PresentMode {
113            inner: vk::PresentModeKHR::MAILBOX,
114            priority: Priority::Main,
115        },
116        PresentMode {
117            inner: vk::PresentModeKHR::FIFO,
118            priority: Priority::Fallback,
119        },
120    ]
121}
122
123fn find_desired_surface_format(
124    available: &[vk::SurfaceFormatKHR],
125    desired: &mut [Format],
126) -> crate::Result<vk::SurfaceFormatKHR> {
127    if !desired.is_sorted_by_key(|f| f.priority.clone()) {
128        desired.sort_unstable_by_key(|f| f.priority.clone());
129    }
130
131    for desired in desired.iter() {
132        for available in available {
133            if desired.inner.surface_format.format == available.format
134                && desired.inner.surface_format.color_space == available.color_space
135            {
136                return Ok(desired.inner.surface_format);
137            }
138        }
139    }
140
141    Err(crate::SwapchainError::NoSuitableDesiredFormat(FormatError {
142        available: available.to_vec(),
143        desired: desired.iter().map(|d| d.inner.surface_format).collect(),
144    })
145    .into())
146}
147
148fn find_best_surface_format(
149    available: &[vk::SurfaceFormatKHR],
150    desired: &mut [Format],
151) -> vk::SurfaceFormatKHR {
152    find_desired_surface_format(available, desired).unwrap_or(available[0])
153}
154
155fn find_present_mode(
156    available: &[vk::PresentModeKHR],
157    desired: &mut [PresentMode],
158) -> vk::PresentModeKHR {
159    if !desired.is_sorted_by_key(|f| f.priority.clone()) {
160        desired.sort_unstable_by_key(|f| f.priority.clone());
161    }
162
163    for desired in desired {
164        for available in available {
165            if &desired.inner == available {
166                return *available;
167            }
168        }
169    }
170
171    vk::PresentModeKHR::FIFO
172}
173
174impl SwapchainBuilder {
175    fn find_extent(&self, capabilities: &vk::SurfaceCapabilitiesKHR) -> vk::Extent2D {
176        if capabilities.current_extent.width != u32::MAX {
177            capabilities.current_extent
178        } else {
179            let mut actual_extent = vk::Extent2D {
180                width: self.desired_width,
181                height: self.desired_height,
182            };
183
184            actual_extent.width = capabilities
185                .min_image_extent
186                .width
187                .max(capabilities.max_image_extent.width.min(actual_extent.width));
188            actual_extent.height = capabilities.min_image_extent.height.max(
189                capabilities
190                    .max_image_extent
191                    .height
192                    .min(actual_extent.height),
193            );
194
195            actual_extent
196        }
197    }
198
199    pub fn new(instance: Arc<Instance>, device: Arc<Device>) -> Self {
200        Self {
201            graphics_queue_index: device.get_queue(QueueType::Graphics).unwrap().0,
202            present_queue_index: device.get_queue(QueueType::Present).unwrap().0,
203            instance,
204            device,
205            allocation_callbacks: None,
206            desired_formats: Vec::with_capacity(4),
207            create_flags: vk::SwapchainCreateFlagsKHR::default(),
208            desired_width: 256,
209            desired_height: 256,
210            array_layer_count: 1,
211            min_image_count: 0,
212            required_min_image_count: 0,
213            image_usage_flags: vk::ImageUsageFlags::COLOR_ATTACHMENT,
214            pre_transform: vk::SurfaceTransformFlagsKHR::default(),
215            desired_present_modes: Vec::with_capacity(4),
216            composite_alpha_flags_khr: vk::CompositeAlphaFlagsKHR::OPAQUE,
217            clipped: true,
218            old_swapchain: Default::default(),
219        }
220    }
221
222    /// Add a preferred surface format to try when creating the swapchain.
223    /// Preferred formats are evaluated in the order they are added (main before fallback).
224    pub fn desired_format(mut self, format: vk::SurfaceFormat2KHR) -> Self {
225        self.desired_formats.push(Format {
226            inner: format,
227            priority: Priority::Main,
228        });
229        self
230    }
231
232    /// Set the desired width/height for the swapchain extent when the surface allows
233    /// an arbitrary size (i.e. current_extent.width == u32::MAX).
234    pub fn desired_size(mut self, size: vk::Extent2D) -> Self {
235        self.desired_width = size.width;
236        self.desired_height = size.height;
237        self
238    }
239
240    /// Add a fallback surface format to consider if preferred formats are not available.
241    pub fn fallback_format(mut self, format: vk::SurfaceFormat2KHR) -> Self {
242        self.desired_formats.push(Format {
243            inner: format,
244            priority: Priority::Fallback,
245        });
246        self
247    }
248
249    /// Use the default swapchain formats. This is done if no formats are provided.
250    ///
251    /// Default surface format is [
252    ///     [`vk::Format::B8G8R8A8_SRGB`],
253    ///     [`vk::ColorSpaceKHR::SRGB_NONLINEAR`]
254    /// ]
255    pub fn use_default_format_selection(mut self) -> Self {
256        self.desired_formats = default_formats();
257        self
258    }
259
260    /// Add a preferred present mode (e.g. MAILBOX, FIFO) to try when creating the swapchain.
261    pub fn desired_present_mode(mut self, present_mode: vk::PresentModeKHR) -> Self {
262        self.desired_present_modes.push(PresentMode {
263            inner: present_mode,
264            priority: Priority::Main,
265        });
266        self
267    }
268
269    /// Add a fallback present mode that will be used if preferred present modes are not present.
270    pub fn fallback_present_mode(mut self, present_mode: vk::PresentModeKHR) -> Self {
271        self.desired_present_modes.push(PresentMode {
272            inner: present_mode,
273            priority: Priority::Fallback,
274        });
275        self
276    }
277
278    pub fn use_default_present_modes(mut self) -> Self {
279        self.desired_present_modes = default_present_modes();
280        self
281    }
282
283    /// Sets the desired minimum image count for the swapchain.
284    /// Note that the presentation engine is always free to create more images than requested.
285    /// You may pass one of the values specified in the BufferMode enum, or any integer value.
286    /// For instance, if you pass DOUBLE_BUFFERING, the presentation engine is allowed to give you a double buffering setup,
287    /// triple buffering, or more. This is up to the drivers.
288    pub fn desired_min_image_count(mut self, min_image_count: u32) -> Self {
289        self.min_image_count = min_image_count;
290        self
291    }
292
293    /// Set whether the Vulkan implementation is allowed to discard rendering operations that
294    /// affect regions of the surface that are not visible. Default is true.
295    /// # Note:
296    /// Applications should use the default of true if:
297    /// - They do not expect to read back the content of presentable images before presenting them or after reacquiring them
298    /// - If their fragment shaders do not have any side effects that require them to run for all pixels in the presentable image.
299    pub fn clipped(mut self, clipped: bool) -> Self {
300        self.clipped = clipped;
301        self
302    }
303
304    pub fn create_flags(mut self, flags: vk::SwapchainCreateFlagsKHR) -> Self {
305        self.create_flags = flags;
306        self
307    }
308
309    /// Set the bitmask of the image usage for acquired swapchain images.
310    /// If the surface capabilities cannot allow it, building the swapchain will result in the `SwapchainError::required_usage_not_supported` error.
311    pub fn image_usage_flags(mut self, flags: vk::ImageUsageFlags) -> Self {
312        self.image_usage_flags = flags;
313        self
314    }
315
316    /// Add a image usage to the bitmask for acquired swapchain images.
317    pub fn add_image_usage_flags(mut self, flags: vk::ImageUsageFlags) -> Self {
318        self.image_usage_flags |= flags;
319        self
320    }
321
322    pub fn allocation_callbacks(mut self, allocation_callbacks: AllocationCallbacks) -> Self {
323        self.allocation_callbacks = Some(allocation_callbacks);
324        self
325    }
326
327    /// This method should be called with previously created [`Swapchain`].
328    ///
329    /// # Note:
330    /// This method will mark old swapchain and destroy it when creating a new one.
331    pub fn set_old_swapchain(&self, swapchain: Swapchain) {
332        if swapchain.destroy_image_views().is_err() {
333            #[cfg(feature = "enable_tracing")]
334            tracing::warn!("Could not destroy swapchain image views");
335            return;
336        };
337        self.old_swapchain
338            .store(swapchain.swapchain.as_raw(), Ordering::Relaxed);
339    }
340
341    pub fn build(&self) -> crate::Result<Swapchain> {
342        if self.instance.surface.is_none() {
343            return Err(crate::SwapchainError::SurfaceHandleNotProvided.into());
344        };
345
346        let mut desired_formats = self.desired_formats.clone();
347        if desired_formats.is_empty() {
348            desired_formats = default_formats();
349        };
350
351        let mut desired_present_modes = self.desired_present_modes.clone();
352        if desired_present_modes.is_empty() {
353            desired_present_modes = default_present_modes();
354        }
355
356        let surface_support = query_surface_support_details(
357            *self.device.physical_device().as_ref(),
358            &self.instance.instance,
359            self.instance.surface,
360        )?;
361
362        let mut image_count = self.min_image_count;
363        if image_count >= 1 {
364            if self.required_min_image_count < surface_support.capabilities.min_image_count {
365                return Err(crate::SwapchainError::RequiredMinImageCountTooLow.into());
366            }
367
368            image_count = surface_support.capabilities.min_image_count;
369        } else if image_count == 0 {
370            // We intentionally use minImageCount + 1 to maintain existing behavior,
371            // even if it typically results in triple buffering on most systems.
372            image_count = surface_support.capabilities.min_image_count + 1;
373        } else if image_count < surface_support.capabilities.min_image_count {
374            image_count = surface_support.capabilities.min_image_count
375        }
376
377        if surface_support.capabilities.max_image_count > 0
378            && image_count > surface_support.capabilities.max_image_count
379        {
380            image_count = surface_support.capabilities.max_image_count;
381        }
382
383        let surface_format =
384            find_best_surface_format(&surface_support.formats, &mut desired_formats);
385
386        let extent = self.find_extent(&surface_support.capabilities);
387
388        let mut image_array_layers = self.array_layer_count;
389        if surface_support.capabilities.max_image_array_layers < image_array_layers {
390            image_array_layers = surface_support.capabilities.max_image_array_layers;
391        }
392        if image_array_layers == 0 {
393            image_array_layers = 1;
394        }
395
396        let present_mode =
397            find_present_mode(&surface_support.present_modes, &mut desired_present_modes);
398
399        let is_unextended_present_mode =
400            matches!(
401                present_mode,
402                |vk::PresentModeKHR::IMMEDIATE| vk::PresentModeKHR::MAILBOX
403                    | vk::PresentModeKHR::FIFO
404                    | vk::PresentModeKHR::FIFO_RELAXED
405            );
406
407        if is_unextended_present_mode
408            && !surface_support
409                .capabilities
410                .supported_usage_flags
411                .contains(self.image_usage_flags)
412        {
413            return Err(crate::SwapchainError::RequiredUsageNotSupported.into());
414        };
415
416        let mut pre_transform = self.pre_transform;
417        if pre_transform == vk::SurfaceTransformFlagsKHR::default() {
418            pre_transform = surface_support.capabilities.current_transform;
419        }
420
421        let old_swapchain = self.old_swapchain.load(Ordering::Relaxed);
422
423        let mut swapchain_create_info = vk::SwapchainCreateInfoKHR::builder()
424            .flags(self.create_flags)
425            .surface(self.instance.surface.unwrap())
426            .min_image_count(image_count)
427            .image_format(surface_format.format)
428            .image_color_space(surface_format.color_space)
429            .image_extent(extent)
430            .image_array_layers(image_array_layers)
431            .image_usage(self.image_usage_flags)
432            .pre_transform(pre_transform)
433            .composite_alpha(self.composite_alpha_flags_khr)
434            .present_mode(present_mode)
435            .clipped(self.clipped)
436            .old_swapchain(SwapchainKHR::from_raw(old_swapchain));
437
438        let queue_family_indices = [
439            self.graphics_queue_index as _,
440            self.present_queue_index as _,
441        ];
442
443        if self.graphics_queue_index != self.present_queue_index {
444            swapchain_create_info.image_sharing_mode = vk::SharingMode::CONCURRENT;
445            swapchain_create_info =
446                swapchain_create_info.queue_family_indices(&queue_family_indices);
447        } else {
448            swapchain_create_info.image_sharing_mode = vk::SharingMode::EXCLUSIVE;
449        }
450
451        let swapchain = unsafe {
452            self.device
453                .create_swapchain_khr(&swapchain_create_info, self.allocation_callbacks.as_ref())
454        }
455        .map_err(|_| crate::SwapchainError::FailedCreateSwapchain)?;
456
457        if old_swapchain != 0 {
458            unsafe {
459                self.device.destroy_swapchain_khr(
460                    SwapchainKHR::from_raw(old_swapchain),
461                    self.allocation_callbacks.as_ref(),
462                )
463            }
464        }
465
466        Ok(Swapchain {
467            device: self.device.clone(),
468            swapchain,
469            extent,
470            image_format: surface_format.format,
471            image_usage_flags: self.image_usage_flags,
472            instance_version: self.instance.instance_version,
473            allocation_callbacks: self.allocation_callbacks,
474            image_views: Mutex::new(Vec::with_capacity(image_count as _)),
475        })
476    }
477}
478
479#[derive(Debug)]
480pub struct Swapchain {
481    device: Arc<Device>,
482    swapchain: vk::SwapchainKHR,
483    pub image_format: vk::Format,
484    pub extent: vk::Extent2D,
485    image_usage_flags: vk::ImageUsageFlags,
486    instance_version: Version,
487    allocation_callbacks: Option<AllocationCallbacks>,
488    image_views: Mutex<Vec<vk::ImageView>>,
489}
490
491impl Swapchain {
492    /// Retrieve the images currently owned by the swapchain.
493    pub fn get_images(&self) -> crate::Result<Vec<vk::Image>> {
494        let images = unsafe { self.device.get_swapchain_images_khr(self.swapchain) }?;
495
496        Ok(images)
497    }
498
499    /// Destroy any cached image views created for the swapchain and clear the cache.
500    pub fn destroy_image_views(&self) -> crate::Result<()> {
501        let mut image_views = self.image_views.lock().unwrap();
502
503        for image_view in image_views.drain(..) {
504            unsafe {
505                self.device
506                    .device()
507                    .destroy_image_view(image_view, self.allocation_callbacks.as_ref())
508            }
509        }
510
511        Ok(())
512    }
513
514    /// Create (or return cached) image views for each swapchain image.
515    /// The created views are cached for later destruction via `destroy_image_views`.
516    pub fn get_image_views(&self) -> crate::Result<Vec<vk::ImageView>> {
517        let images = self.get_images()?;
518
519        let mut desired_flags =
520            vk::ImageViewUsageCreateInfo::builder().usage(self.image_usage_flags);
521
522        let views: Vec<_> = images
523            .into_iter()
524            .map(|image| {
525                // Build the ImageViewCreateInfo using chaining so values are actually set.
526                let mut create_info = vk::ImageViewCreateInfo::builder();
527
528                if self.instance_version >= Version::V1_1_0 {
529                    create_info = create_info.push_next(&mut desired_flags);
530                }
531
532                let create_info = create_info
533                    .image(image)
534                    .view_type(vk::ImageViewType::_2D)
535                    .format(self.image_format)
536                    .components(vk::ComponentMapping::default())
537                    .subresource_range(
538                        vk::ImageSubresourceRange::builder()
539                            .aspect_mask(vk::ImageAspectFlags::COLOR)
540                            .level_count(1)
541                            .layer_count(1),
542                    );
543
544                unsafe {
545                    self.device
546                        .device()
547                        .create_image_view(&create_info, self.allocation_callbacks.as_ref())
548                }
549                .map_err(Into::into)
550            })
551            .collect::<crate::Result<_>>()?;
552
553        {
554            let mut image_views = self.image_views.lock().unwrap();
555            *image_views = views.clone();
556        }
557
558        Ok(views)
559    }
560
561    /// Destroy the swapchain handle. Image views should be destroyed separately
562    /// (e.g. via `Swapchain::destroy_image_views`) before destroying the swapchain.
563    pub fn destroy(&self) {
564        unsafe {
565            self.device
566                .destroy_swapchain_khr(self.swapchain, self.allocation_callbacks.as_ref())
567        };
568    }
569}
570
571impl AsRef<SwapchainKHR> for Swapchain {
572    fn as_ref(&self) -> &SwapchainKHR {
573        &self.swapchain
574    }
575}