use std::sync::Arc;
use std::marker::PhantomData;
use crate::{Error, Result, runtime::SdlWindow};
use super::{
Device,
device::swapchain::{PresentationSwapchain, SwapchainConfig},
};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct PresentationCapabilities {
pub min_image_count: u32,
pub max_image_count: Option<u32>,
pub current_extent: Option<[u32; 2]>,
pub min_extent: [u32; 2],
pub max_extent: [u32; 2],
pub format_count: usize,
pub present_mode_count: usize,
pub supports_color_attachment: bool,
pub supports_transfer_dst: bool,
}
pub struct PresentationSurface<'window> {
_device: Arc<Device>,
loader: ash::khr::surface::Instance,
handle: ash::vk::SurfaceKHR,
graphics_present_supported: bool,
present_queue_family: Option<u32>,
_window: PhantomData<&'window SdlWindow>,
}
impl<'window> PresentationSurface<'window> {
pub(in crate::runtime) fn create(
device: &Arc<Device>,
window: &'window SdlWindow,
) -> Result<Self> {
let instance = device.instance();
let loader = ash::khr::surface::Instance::new(instance.entry(), instance.raw());
let handle = unsafe { window.window.vulkan_create_surface(instance.raw().handle()) }
.map_err(|source| Error::backend_failure("SDL3", "Vulkan surface creation", source))?;
let physical = device.physical();
let graphics_family = physical.graphics_queue_family;
let graphics_present_supported = graphics_family
.map(|family| unsafe {
loader.get_physical_device_surface_support(physical.handle, family, handle)
})
.transpose()
.map_err(|source| Error::backend_failure("Vulkan", "surface present-support query", source))?
.unwrap_or(false);
let present_queue_family = if graphics_present_supported {
graphics_family
} else {
let queue_families = unsafe {
instance
.raw()
.get_physical_device_queue_family_properties(physical.handle)
};
let mut found = None;
for (index, _props) in queue_families.iter().enumerate() {
let supported = unsafe {
loader.get_physical_device_surface_support(physical.handle, index as u32, handle)
}
.unwrap_or(false);
if supported {
found = Some(index as u32);
break;
}
}
found
};
Ok(Self {
_device: device.clone(),
loader,
handle,
graphics_present_supported,
present_queue_family,
_window: PhantomData,
})
}
pub(in crate::runtime) fn present_queue_family(&self) -> Option<u32> {
self.present_queue_family
}
pub const fn graphics_present_supported(&self) -> bool {
self.graphics_present_supported
}
pub(in crate::runtime) fn has_graphics(&self) -> bool {
self._device.has_graphics_queue()
}
pub(in crate::runtime) fn supports_swapchain(&self) -> bool {
self._device.supports_swapchain()
}
pub fn capabilities(&self) -> Result<PresentationCapabilities> {
let (raw, formats, modes) = self.raw_capabilities()?;
Ok(PresentationCapabilities {
min_image_count: raw.min_image_count,
max_image_count: (raw.max_image_count != 0).then_some(raw.max_image_count),
current_extent: (raw.current_extent.width != u32::MAX)
.then_some([raw.current_extent.width, raw.current_extent.height]),
min_extent: [raw.min_image_extent.width, raw.min_image_extent.height],
max_extent: [raw.max_image_extent.width, raw.max_image_extent.height],
format_count: formats.len(),
present_mode_count: modes.len(),
supports_color_attachment: raw
.supported_usage_flags
.contains(ash::vk::ImageUsageFlags::COLOR_ATTACHMENT),
supports_transfer_dst: raw
.supported_usage_flags
.contains(ash::vk::ImageUsageFlags::TRANSFER_DST),
})
}
pub fn swapchain_extent(&self, drawable_extent: [u32; 2]) -> Result<Option<[u32; 2]>> {
let (raw, _, _) = self.raw_capabilities()?;
if raw.current_extent.width != u32::MAX {
return Ok(Some([raw.current_extent.width, raw.current_extent.height]));
}
if drawable_extent.contains(&0) {
return Ok(None);
}
Ok(Some([
drawable_extent[0].clamp(raw.min_image_extent.width, raw.max_image_extent.width),
drawable_extent[1].clamp(raw.min_image_extent.height, raw.max_image_extent.height),
]))
}
pub(in crate::runtime) fn swapchain_config(
&self,
drawable_extent: [u32; 2],
) -> Result<Option<SwapchainConfig>> {
let (raw, formats, modes) = self.raw_capabilities()?;
let extent = if raw.current_extent.width != u32::MAX {
ash::vk::Extent2D::default()
.width(raw.current_extent.width)
.height(raw.current_extent.height)
} else {
if drawable_extent.contains(&0) {
return Ok(None);
}
ash::vk::Extent2D::default()
.width(drawable_extent[0].clamp(raw.min_image_extent.width, raw.max_image_extent.width))
.height(drawable_extent[1].clamp(raw.min_image_extent.height, raw.max_image_extent.height))
};
let format = formats
.iter()
.copied()
.find(|format| {
format.format == ash::vk::Format::B8G8R8A8_SRGB
&& format.color_space == ash::vk::ColorSpaceKHR::SRGB_NONLINEAR
})
.or_else(|| formats.first().copied())
.ok_or_else(|| Error::missing_capability("surface exposes no swapchain formats"))?;
if !modes.contains(&ash::vk::PresentModeKHR::FIFO) {
return Err(Error::missing_capability(
"surface does not expose mandatory FIFO presentation",
));
}
if !raw
.supported_usage_flags
.contains(ash::vk::ImageUsageFlags::COLOR_ATTACHMENT | ash::vk::ImageUsageFlags::TRANSFER_DST)
{
return Err(Error::missing_capability(
"surface swapchain images do not support color-attachment and transfer-destination usage",
));
}
let source_features = ash::vk::FormatFeatureFlags::BLIT_SRC
| ash::vk::FormatFeatureFlags::SAMPLED_IMAGE_FILTER_LINEAR;
if !self
._device
.optimal_format_supports(ash::vk::Format::R8G8B8A8_UNORM, source_features)
{
return Err(Error::missing_capability(
"R8G8B8A8_UNORM images do not support linear-filtered blit sources",
));
}
if !self
._device
.optimal_format_supports(format.format, ash::vk::FormatFeatureFlags::BLIT_DST)
{
return Err(Error::missing_capability(format!(
"selected swapchain format {:?} does not support blit destinations",
format.format
)));
}
let composite_alpha = [
ash::vk::CompositeAlphaFlagsKHR::OPAQUE,
ash::vk::CompositeAlphaFlagsKHR::PRE_MULTIPLIED,
ash::vk::CompositeAlphaFlagsKHR::POST_MULTIPLIED,
ash::vk::CompositeAlphaFlagsKHR::INHERIT,
]
.into_iter()
.find(|candidate| raw.supported_composite_alpha.contains(*candidate))
.ok_or_else(|| Error::missing_capability("surface exposes no composite-alpha mode"))?;
let image_count = raw
.min_image_count
.saturating_add(1)
.min(if raw.max_image_count == 0 {
u32::MAX
} else {
raw.max_image_count
});
Ok(Some(SwapchainConfig {
extent,
image_count,
format,
present_mode: ash::vk::PresentModeKHR::FIFO,
composite_alpha,
pre_transform: raw.current_transform,
}))
}
pub(in crate::runtime) fn create_swapchain(
&self,
drawable_extent: [u32; 2],
) -> Result<Option<PresentationSwapchain>> {
if self.present_queue_family.is_none() || !self.supports_swapchain() {
return Err(Error::missing_capability(
"selected device cannot create a presentation swapchain",
));
}
let Some(config) = self.swapchain_config(drawable_extent)? else {
return Ok(None);
};
PresentationSwapchain::create(
&self._device,
self.handle,
self.present_queue_family,
config,
)
.map(Some)
}
fn raw_capabilities(
&self,
) -> Result<(
ash::vk::SurfaceCapabilitiesKHR,
Vec<ash::vk::SurfaceFormatKHR>,
Vec<ash::vk::PresentModeKHR>,
)> {
let physical = self._device.physical();
let raw = unsafe {
self
.loader
.get_physical_device_surface_capabilities(physical.handle, self.handle)
}
.map_err(|source| Error::backend_failure("Vulkan", "surface-capability query", source))?;
let formats = unsafe {
self
.loader
.get_physical_device_surface_formats(physical.handle, self.handle)
}
.map_err(|source| Error::backend_failure("Vulkan", "surface-format query", source))?;
let modes = unsafe {
self
.loader
.get_physical_device_surface_present_modes(physical.handle, self.handle)
}
.map_err(|source| Error::backend_failure("Vulkan", "surface present-mode query", source))?;
Ok((raw, formats, modes))
}
}
impl Drop for PresentationSurface<'_> {
fn drop(&mut self) {
unsafe { self.loader.destroy_surface(self.handle, None) };
}
}