synvael/crates/renderer/src/swapchain.rs

146 lines
5 KiB
Rust

// 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::Image>,
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<Vec<vk::ImageView>, 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)
}