// SPDX-License-Identifier: AGPL-3.0-only use crate::error::RendererError; use ash::{Device, Instance, khr, vk}; /// Presentation mode every swapchain is created with. /// /// `FIFO` is the only mode the specification guarantees to be supported, and it is vsync-locked, so presentation never tears. // TODO: select from the surface's supported modes once a vsync setting exists; `MAILBOX` is the low-latency alternative where available. pub const PRESENT_MODE: vk::PresentModeKHR = vk::PresentModeKHR::FIFO; /// Returns the Vulkan enum name of a presentation mode, for reporting. /// /// A mode outside the known set is reported as `"UNKNOWN"` rather than its numeric value, since the numeric value carries no meaning to a reader. #[must_use] pub const fn present_mode_name(mode: vk::PresentModeKHR) -> &'static str { match mode { vk::PresentModeKHR::IMMEDIATE => "IMMEDIATE", vk::PresentModeKHR::MAILBOX => "MAILBOX", vk::PresentModeKHR::FIFO => "FIFO", vk::PresentModeKHR::FIFO_RELAXED => "FIFO_RELAXED", _ => "UNKNOWN", } } /// Creates a swapchain and retrieves its images. /// /// # Errors /// /// Returns [`RendererError::VulkanError`] if a surface query fails or the swapchain and its images cannot be created. /// /// # Panics /// /// Panics if the driver reports zero surface formats, which the Vulkan specification forbids for a supported surface. pub fn create_swapchain( instance: &Instance, physical_device: vk::PhysicalDevice, device: &Device, surface_loader: &khr::surface::Instance, surface: vk::SurfaceKHR, width: u32, height: u32, ) -> Result< ( khr::swapchain::Device, vk::SwapchainKHR, Vec, vk::Format, vk::Extent2D, ), RendererError, > { let surface_capabilities = unsafe { surface_loader.get_physical_device_surface_capabilities(physical_device, surface)? }; let surface_formats = unsafe { surface_loader.get_physical_device_surface_formats(physical_device, surface)? }; let _surface_present_modes = unsafe { surface_loader.get_physical_device_surface_present_modes(physical_device, surface)? }; let format = surface_formats .iter() .find(|f| { f.format == vk::Format::B8G8R8A8_SRGB && f.color_space == vk::ColorSpaceKHR::SRGB_NONLINEAR }) .unwrap_or(&surface_formats[0]); let extent = if surface_capabilities.current_extent.width == u32::MAX { vk::Extent2D { width: width.clamp( surface_capabilities.min_image_extent.width, surface_capabilities.max_image_extent.width, ), height: height.clamp( surface_capabilities.min_image_extent.height, surface_capabilities.max_image_extent.height, ), } } else { surface_capabilities.current_extent }; let image_count = if surface_capabilities.max_image_count > 0 && surface_capabilities.min_image_count + 1 > surface_capabilities.max_image_count { surface_capabilities.max_image_count } else { surface_capabilities.min_image_count + 1 }; let swapchain_loader = khr::swapchain::Device::new(instance, device); let create_info = vk::SwapchainCreateInfoKHR::default() .surface(surface) .min_image_count(image_count) .image_format(format.format) .image_color_space(format.color_space) .image_extent(extent) .image_array_layers(1) .image_usage(vk::ImageUsageFlags::COLOR_ATTACHMENT) .image_sharing_mode(vk::SharingMode::EXCLUSIVE) .pre_transform(surface_capabilities.current_transform) .composite_alpha(vk::CompositeAlphaFlagsKHR::OPAQUE) .present_mode(PRESENT_MODE) .clipped(true); let swapchain = unsafe { swapchain_loader.create_swapchain(&create_info, None)? }; let images = unsafe { swapchain_loader.get_swapchain_images(swapchain)? }; Ok((swapchain_loader, swapchain, images, format.format, extent)) } /// Creates image views for the swapchain images. /// /// # Errors /// /// Returns [`RendererError::VulkanError`] if the device fails to create an image view. pub fn create_image_views( device: &Device, images: &[vk::Image], format: vk::Format, ) -> Result, RendererError> { let mut views = Vec::with_capacity(images.len()); for &image in images { let create_info = vk::ImageViewCreateInfo::default() .image(image) .view_type(vk::ImageViewType::TYPE_2D) .format(format) .subresource_range(vk::ImageSubresourceRange { aspect_mask: vk::ImageAspectFlags::COLOR, base_mip_level: 0, level_count: 1, base_array_layer: 0, layer_count: 1, }); let view = unsafe { device.create_image_view(&create_info, None)? }; views.push(view); } Ok(views) }