pub use self::{acquire_present::*, surface::*};
#[cfg(target_os = "ios")]
pub use surface::IOSMetalLayer;
mod acquire_present;
mod surface;
use crate::{
device::{Device, DeviceOwned},
format::Format,
image::{Image, ImageCreateFlags, ImageFormatInfo, ImageTiling, ImageType, ImageUsage},
instance::InstanceOwnedDebugWrapper,
macros::{impl_id_counter, vulkan_bitflags, vulkan_bitflags_enum, vulkan_enum},
sync::Sharing,
Requires, RequiresAllOf, RequiresOneOf, Validated, ValidationError, Version, VulkanError,
VulkanObject,
};
use parking_lot::Mutex;
use smallvec::SmallVec;
use std::{
fmt::Debug,
mem::MaybeUninit,
num::NonZeroU64,
ptr,
sync::{
atomic::{AtomicBool, AtomicU64, Ordering},
Arc,
},
};
#[derive(Debug)]
pub struct Swapchain {
handle: ash::vk::SwapchainKHR,
device: InstanceOwnedDebugWrapper<Arc<Device>>,
surface: InstanceOwnedDebugWrapper<Arc<Surface>>,
id: NonZeroU64,
flags: SwapchainCreateFlags,
min_image_count: u32,
image_format: Format,
image_view_formats: Vec<Format>,
image_color_space: ColorSpace,
image_extent: [u32; 2],
image_array_layers: u32,
image_usage: ImageUsage,
image_sharing: Sharing<SmallVec<[u32; 4]>>,
pre_transform: SurfaceTransform,
composite_alpha: CompositeAlpha,
present_mode: PresentMode,
present_modes: SmallVec<[PresentMode; PresentMode::COUNT]>,
clipped: bool,
scaling_behavior: Option<PresentScaling>,
present_gravity: Option<[PresentGravity; 2]>,
full_screen_exclusive: FullScreenExclusive,
win32_monitor: Option<Win32Monitor>,
prev_present_id: AtomicU64,
full_screen_exclusive_held: AtomicBool,
images: Vec<ImageEntry>,
is_retired: Mutex<bool>,
}
#[derive(Debug)]
struct ImageEntry {
handle: ash::vk::Image,
layout_initialized: AtomicBool,
}
impl Swapchain {
#[inline]
pub fn new(
device: Arc<Device>,
surface: Arc<Surface>,
create_info: SwapchainCreateInfo,
) -> Result<(Arc<Swapchain>, Vec<Arc<Image>>), Validated<VulkanError>> {
Self::validate_new_inner(&device, &surface, &create_info)?;
unsafe { Ok(Self::new_unchecked(device, surface, create_info)?) }
}
#[cfg_attr(not(feature = "document_unchecked"), doc(hidden))]
#[inline]
pub unsafe fn new_unchecked(
device: Arc<Device>,
surface: Arc<Surface>,
create_info: SwapchainCreateInfo,
) -> Result<(Arc<Swapchain>, Vec<Arc<Image>>), VulkanError> {
let (handle, image_handles) =
Self::new_inner_unchecked(&device, &surface, &create_info, None)?;
Self::from_handle(device, handle, image_handles, surface, create_info)
}
pub fn recreate(
self: &Arc<Self>,
create_info: SwapchainCreateInfo,
) -> Result<(Arc<Swapchain>, Vec<Arc<Image>>), Validated<VulkanError>> {
Self::validate_new_inner(&self.device, &self.surface, &create_info)?;
{
let mut is_retired = self.is_retired.lock();
if *is_retired {
return Err(Box::new(ValidationError {
context: "self".into(),
problem: "has already been used to recreate a swapchain".into(),
vuids: &["VUID-VkSwapchainCreateInfoKHR-oldSwapchain-01933"],
..Default::default()
})
.into());
} else {
*is_retired = true;
}
}
unsafe { Ok(self.recreate_unchecked(create_info)?) }
}
#[cfg_attr(not(feature = "document_unchecked"), doc(hidden))]
pub unsafe fn recreate_unchecked(
self: &Arc<Self>,
create_info: SwapchainCreateInfo,
) -> Result<(Arc<Swapchain>, Vec<Arc<Image>>), VulkanError> {
*self.is_retired.lock() = true;
let (handle, image_handles) = unsafe {
Self::new_inner_unchecked(&self.device, &self.surface, &create_info, Some(self))?
};
let (mut swapchain, swapchain_images) = Self::from_handle(
self.device.clone(),
handle,
image_handles,
self.surface.clone(),
create_info,
)?;
if self.full_screen_exclusive == FullScreenExclusive::ApplicationControlled {
Arc::get_mut(&mut swapchain)
.unwrap()
.full_screen_exclusive_held =
AtomicBool::new(self.full_screen_exclusive_held.load(Ordering::Relaxed))
};
Ok((swapchain, swapchain_images))
}
fn validate_new_inner(
device: &Device,
surface: &Surface,
create_info: &SwapchainCreateInfo,
) -> Result<(), Box<ValidationError>> {
if !device.enabled_extensions().khr_swapchain {
return Err(Box::new(ValidationError {
requires_one_of: RequiresOneOf(&[RequiresAllOf(&[Requires::DeviceExtension(
"khr_swapchain",
)])]),
..Default::default()
}));
}
create_info
.validate(device)
.map_err(|err| err.add_context("create_info"))?;
let &SwapchainCreateInfo {
flags: _,
min_image_count,
image_format,
image_view_formats: _,
image_color_space,
image_extent,
image_array_layers,
image_usage,
image_sharing: _,
pre_transform,
composite_alpha,
present_mode,
ref present_modes,
clipped: _,
scaling_behavior,
present_gravity,
full_screen_exclusive,
win32_monitor,
_ne: _,
} = create_info;
assert_eq!(device.instance(), surface.instance());
if !device
.active_queue_family_indices()
.iter()
.any(|&index| unsafe {
device
.physical_device()
.surface_support_unchecked(index, surface)
.unwrap_or_default()
})
{
return Err(Box::new(ValidationError {
context: "surface".into(),
problem: "is not supported by the physical device".into(),
vuids: &["VUID-VkSwapchainCreateInfoKHR-surface-01270"],
..Default::default()
}));
}
let surface_capabilities = unsafe {
device
.physical_device()
.surface_capabilities_unchecked(
surface,
SurfaceInfo {
present_mode: (device.enabled_extensions().ext_swapchain_maintenance1)
.then_some(present_mode),
full_screen_exclusive,
win32_monitor,
..Default::default()
},
)
.map_err(|_err| {
Box::new(ValidationError {
problem: "`PhysicalDevice::surface_capabilities` \
returned an error"
.into(),
..Default::default()
})
})?
};
let surface_formats = unsafe {
device
.physical_device()
.surface_formats_unchecked(
surface,
SurfaceInfo {
present_mode: (device.enabled_extensions().ext_swapchain_maintenance1)
.then_some(present_mode),
full_screen_exclusive,
win32_monitor,
..Default::default()
},
)
.map_err(|_err| {
Box::new(ValidationError {
problem: "`PhysicalDevice::surface_formats` \
returned an error"
.into(),
..Default::default()
})
})?
};
let surface_present_modes: SmallVec<[_; PresentMode::COUNT]> = unsafe {
device
.physical_device()
.surface_present_modes_unchecked(
surface,
SurfaceInfo {
full_screen_exclusive,
win32_monitor,
..Default::default()
},
)
.map_err(|_err| {
Box::new(ValidationError {
problem: "`PhysicalDevice::surface_present_modes` \
returned an error"
.into(),
..Default::default()
})
})?
.collect()
};
if surface_capabilities
.max_image_count
.map_or(false, |c| min_image_count > c)
{
return Err(Box::new(ValidationError {
problem: "`create_info.min_image_count` is greater than the `max_image_count` \
value of the capabilities of `surface`"
.into(),
vuids: &["VUID-VkSwapchainCreateInfoKHR-minImageCount-01272"],
..Default::default()
}));
}
if min_image_count < surface_capabilities.min_image_count {
return Err(Box::new(ValidationError {
problem: "`create_info.min_image_count` is less than the `min_image_count` \
value of the capabilities of `surface`"
.into(),
vuids: &["VUID-VkSwapchainCreateInfoKHR-presentMode-02839"],
..Default::default()
}));
}
if !surface_formats
.iter()
.any(|&fc| fc == (image_format, image_color_space))
{
return Err(Box::new(ValidationError {
problem: "the combination of `create_info.image_format` and \
`create_info.image_color_space` is not supported for `surface`"
.into(),
vuids: &["VUID-VkSwapchainCreateInfoKHR-imageFormat-01273"],
..Default::default()
}));
}
if image_array_layers > surface_capabilities.max_image_array_layers {
return Err(Box::new(ValidationError {
problem: "`create_info.image_array_layers` is greater than the \
`max_image_array_layers` value of the capabilities of `surface`"
.into(),
vuids: &["VUID-VkSwapchainCreateInfoKHR-imageArrayLayers-01275"],
..Default::default()
}));
}
if matches!(
present_mode,
PresentMode::Immediate
| PresentMode::Mailbox
| PresentMode::Fifo
| PresentMode::FifoRelaxed
) && !surface_capabilities
.supported_usage_flags
.contains(image_usage)
{
return Err(Box::new(ValidationError {
problem: "`create_info.present_mode` is `PresentMode::Immediate`, \
`PresentMode::Mailbox`, `PresentMode::Fifo` or `PresentMode::FifoRelaxed`, \
and `create_info.image_usage` contains flags that are not set in \
the `supported_usage_flags` value of the capabilities of `surface`"
.into(),
vuids: &["VUID-VkSwapchainCreateInfoKHR-presentMode-01427"],
..Default::default()
}));
}
if !surface_capabilities
.supported_transforms
.contains_enum(pre_transform)
{
return Err(Box::new(ValidationError {
problem: "`create_info.pre_transform` is not present in the \
`supported_transforms` value of the capabilities of `surface`"
.into(),
vuids: &["VUID-VkSwapchainCreateInfoKHR-preTransform-01279"],
..Default::default()
}));
}
if !surface_capabilities
.supported_composite_alpha
.contains_enum(composite_alpha)
{
return Err(Box::new(ValidationError {
problem: "`create_info.composite_alpha` is not present in the \
`supported_composite_alpha` value of the capabilities of `surface`"
.into(),
vuids: &["VUID-VkSwapchainCreateInfoKHR-compositeAlpha-01280"],
..Default::default()
}));
}
if !surface_present_modes.contains(&present_mode) {
return Err(Box::new(ValidationError {
problem: "`create_info.present_mode` is not supported for `surface`".into(),
vuids: &["VUID-VkSwapchainCreateInfoKHR-presentMode-01281"],
..Default::default()
}));
}
if present_modes.is_empty() {
if let Some(scaling_behavior) = scaling_behavior {
if !surface_capabilities
.supported_present_scaling
.contains_enum(scaling_behavior)
{
return Err(Box::new(ValidationError {
problem: "`create_info.scaling_behavior` is not present in the \
`supported_present_scaling` value of the \
capabilities of `surface`"
.into(),
vuids: &[
"VUID-VkSwapchainPresentScalingCreateInfoEXT-scalingBehavior-07770",
],
..Default::default()
}));
}
}
if let Some(present_gravity) = present_gravity {
for (axis_index, (present_gravity, supported_present_gravity)) in present_gravity
.into_iter()
.zip(surface_capabilities.supported_present_gravity)
.enumerate()
{
if !supported_present_gravity.contains_enum(present_gravity) {
return Err(Box::new(ValidationError {
problem: format!(
"`create_info.present_gravity[{0}]` is not present in the \
`supported_present_gravity[{0}]` value of the \
capabilities of `surface`",
axis_index,
)
.into(),
vuids: &[
"VUID-VkSwapchainPresentScalingCreateInfoEXT-presentGravityX-07772",
"VUID-VkSwapchainPresentScalingCreateInfoEXT-presentGravityY-07774",
],
..Default::default()
}));
}
}
}
} else {
for (index, &present_mode) in present_modes.iter().enumerate() {
if !surface_present_modes.contains(&present_mode) {
return Err(Box::new(ValidationError {
problem: format!(
"`create_info.present_modes[{}]` is not supported for `surface`",
index,
)
.into(),
vuids: &["VUID-VkSwapchainPresentModesCreateInfoEXT-None-07762"],
..Default::default()
}));
}
if !surface_capabilities
.compatible_present_modes
.contains(&present_mode)
{
return Err(Box::new(ValidationError {
problem: format!(
"`create_info.present_modes[{}]` is not present in the \
`compatible_present_modes` value of the \
capabilities of `surface`",
index,
)
.into(),
vuids: &["VUID-VkSwapchainPresentModesCreateInfoEXT-pPresentModes-07763"],
..Default::default()
}));
}
if scaling_behavior.is_some() || present_gravity.is_some() {
let surface_capabilities = unsafe {
device
.physical_device()
.surface_capabilities_unchecked(
surface,
SurfaceInfo {
present_mode: Some(present_mode),
full_screen_exclusive,
win32_monitor,
..Default::default()
},
)
.map_err(|_err| {
Box::new(ValidationError {
problem: "`PhysicalDevice::surface_capabilities` \
returned an error"
.into(),
..Default::default()
})
})?
};
if let Some(scaling_behavior) = scaling_behavior {
if !surface_capabilities
.supported_present_scaling
.contains_enum(scaling_behavior)
{
return Err(Box::new(ValidationError {
problem: format!(
"`create_info.scaling_behavior` is not present in the \
`supported_present_scaling` value of the \
capabilities of `surface` for \
`create_info.present_modes[{}]`",
index,
)
.into(),
vuids: &[
"VUID-VkSwapchainPresentScalingCreateInfoEXT-scalingBehavior-07771",
],
..Default::default()
}));
}
}
if let Some(present_gravity) = present_gravity {
for (axis_index, (present_gravity, supported_present_gravity)) in
present_gravity
.into_iter()
.zip(surface_capabilities.supported_present_gravity)
.enumerate()
{
if !supported_present_gravity.contains_enum(present_gravity) {
return Err(Box::new(ValidationError {
problem: format!(
"`create_info.present_gravity[{0}]` is not present in the \
`supported_present_gravity[{0}]` value of the \
capabilities of `surface` for \
`create_info.present_modes[{1}]`",
axis_index, index,
)
.into(),
vuids: &[
"VUID-VkSwapchainPresentScalingCreateInfoEXT-presentGravityX-07773",
"VUID-VkSwapchainPresentScalingCreateInfoEXT-presentGravityY-07775",
],
..Default::default()
}));
}
}
}
}
}
}
if scaling_behavior.is_some() {
if let Some(min_scaled_image_extent) = surface_capabilities.min_scaled_image_extent {
if image_extent[0] < min_scaled_image_extent[0]
|| image_extent[1] < min_scaled_image_extent[1]
{
return Err(Box::new(ValidationError {
problem: "`scaling_behavior` is `Some`, and an element of \
`create_info.image_extent` is less than the corresponding element \
of the `min_scaled_image_extent` value of the \
capabilities of `surface`"
.into(),
vuids: &["VUID-VkSwapchainCreateInfoKHR-pNext-07782"],
..Default::default()
}));
}
}
if let Some(max_scaled_image_extent) = surface_capabilities.max_scaled_image_extent {
if image_extent[0] > max_scaled_image_extent[0]
|| image_extent[1] > max_scaled_image_extent[1]
{
return Err(Box::new(ValidationError {
problem: "`scaling_behavior` is `Some`, and an element of \
`create_info.image_extent` is greater than the corresponding element \
of the `max_scaled_image_extent` value of the \
capabilities of `surface`"
.into(),
vuids: &["VUID-VkSwapchainCreateInfoKHR-pNext-07782"],
..Default::default()
}));
}
}
} else {
}
if surface.api() == SurfaceApi::Win32
&& full_screen_exclusive == FullScreenExclusive::ApplicationControlled
{
if win32_monitor.is_none() {
return Err(Box::new(ValidationError {
problem: "`surface` is a Win32 surface, and \
`create_info.full_screen_exclusive` is \
`FullScreenExclusive::ApplicationControlled`, but \
`create_info.win32_monitor` is `None`"
.into(),
vuids: &["VUID-VkSwapchainCreateInfoKHR-pNext-02679"],
..Default::default()
}));
}
} else {
if win32_monitor.is_some() {
return Err(Box::new(ValidationError {
problem: "`surface` is not a Win32 surface, or \
`create_info.full_screen_exclusive` is not \
`FullScreenExclusive::ApplicationControlled`, but \
`create_info.win32_monitor` is `Some`"
.into(),
..Default::default()
}));
}
}
Ok(())
}
unsafe fn new_inner_unchecked(
device: &Device,
surface: &Surface,
create_info: &SwapchainCreateInfo,
old_swapchain: Option<&Swapchain>,
) -> Result<(ash::vk::SwapchainKHR, Vec<ash::vk::Image>), VulkanError> {
let &SwapchainCreateInfo {
flags,
min_image_count,
image_format,
ref image_view_formats,
image_color_space,
image_extent,
image_array_layers,
image_usage,
ref image_sharing,
pre_transform,
composite_alpha,
present_mode,
ref present_modes,
clipped,
scaling_behavior,
present_gravity,
full_screen_exclusive,
win32_monitor,
_ne: _,
} = create_info;
let (image_sharing_mode_vk, queue_family_index_count_vk, p_queue_family_indices_vk) =
match image_sharing {
Sharing::Exclusive => (ash::vk::SharingMode::EXCLUSIVE, 0, ptr::null()),
Sharing::Concurrent(ref ids) => (
ash::vk::SharingMode::CONCURRENT,
ids.len() as u32,
ids.as_ptr(),
),
};
let mut create_info_vk = ash::vk::SwapchainCreateInfoKHR {
flags: flags.into(),
surface: surface.handle(),
min_image_count,
image_format: image_format.into(),
image_color_space: image_color_space.into(),
image_extent: ash::vk::Extent2D {
width: image_extent[0],
height: image_extent[1],
},
image_array_layers,
image_usage: image_usage.into(),
image_sharing_mode: image_sharing_mode_vk,
queue_family_index_count: queue_family_index_count_vk,
p_queue_family_indices: p_queue_family_indices_vk,
pre_transform: pre_transform.into(),
composite_alpha: composite_alpha.into(),
present_mode: present_mode.into(),
clipped: clipped as ash::vk::Bool32,
old_swapchain: old_swapchain.map_or(ash::vk::SwapchainKHR::null(), |os| os.handle),
..Default::default()
};
let mut format_list_info_vk = None;
let format_list_view_formats_vk: Vec<_>;
let mut full_screen_exclusive_info_vk = None;
let mut full_screen_exclusive_win32_info_vk = None;
let mut present_modes_info_vk = None;
let present_modes_vk: SmallVec<[ash::vk::PresentModeKHR; PresentMode::COUNT]>;
let mut present_scaling_info_vk = None;
if !image_view_formats.is_empty() {
format_list_view_formats_vk = image_view_formats
.iter()
.copied()
.map(ash::vk::Format::from)
.collect();
let next = format_list_info_vk.insert(ash::vk::ImageFormatListCreateInfo {
view_format_count: format_list_view_formats_vk.len() as u32,
p_view_formats: format_list_view_formats_vk.as_ptr(),
..Default::default()
});
next.p_next = create_info_vk.p_next;
create_info_vk.p_next = next as *const _ as *const _;
}
if full_screen_exclusive != FullScreenExclusive::Default {
let next =
full_screen_exclusive_info_vk.insert(ash::vk::SurfaceFullScreenExclusiveInfoEXT {
full_screen_exclusive: full_screen_exclusive.into(),
..Default::default()
});
next.p_next = create_info_vk.p_next as *mut _;
create_info_vk.p_next = next as *const _ as *const _;
}
if let Some(Win32Monitor(hmonitor)) = win32_monitor {
let next = full_screen_exclusive_win32_info_vk.insert(
ash::vk::SurfaceFullScreenExclusiveWin32InfoEXT {
hmonitor,
..Default::default()
},
);
next.p_next = create_info_vk.p_next as *mut _;
create_info_vk.p_next = next as *const _ as *const _;
}
if !present_modes.is_empty() {
present_modes_vk = present_modes.iter().copied().map(Into::into).collect();
let next = present_modes_info_vk.insert(ash::vk::SwapchainPresentModesCreateInfoEXT {
present_mode_count: present_modes_vk.len() as u32,
p_present_modes: present_modes_vk.as_ptr(),
..Default::default()
});
next.p_next = create_info_vk.p_next as *mut _;
create_info_vk.p_next = next as *const _ as *const _;
}
if scaling_behavior.is_some() || present_gravity.is_some() {
let [present_gravity_x, present_gravity_y] =
present_gravity.map_or_else(Default::default, |pg| pg.map(Into::into));
let next =
present_scaling_info_vk.insert(ash::vk::SwapchainPresentScalingCreateInfoEXT {
scaling_behavior: scaling_behavior.map_or_else(Default::default, Into::into),
present_gravity_x,
present_gravity_y,
..Default::default()
});
next.p_next = create_info_vk.p_next as *mut _;
create_info_vk.p_next = next as *const _ as *const _;
}
let fns = device.fns();
let handle = {
let mut output = MaybeUninit::uninit();
(fns.khr_swapchain.create_swapchain_khr)(
device.handle(),
&create_info_vk,
ptr::null(),
output.as_mut_ptr(),
)
.result()
.map_err(VulkanError::from)?;
output.assume_init()
};
let image_handles = loop {
let mut count = 0;
(fns.khr_swapchain.get_swapchain_images_khr)(
device.handle(),
handle,
&mut count,
ptr::null_mut(),
)
.result()
.map_err(VulkanError::from)?;
let mut images = Vec::with_capacity(count as usize);
let result = (fns.khr_swapchain.get_swapchain_images_khr)(
device.handle(),
handle,
&mut count,
images.as_mut_ptr(),
);
match result {
ash::vk::Result::SUCCESS => {
images.set_len(count as usize);
break images;
}
ash::vk::Result::INCOMPLETE => (),
err => return Err(VulkanError::from(err)),
}
};
Ok((handle, image_handles))
}
pub unsafe fn from_handle(
device: Arc<Device>,
handle: ash::vk::SwapchainKHR,
image_handles: impl IntoIterator<Item = ash::vk::Image>,
surface: Arc<Surface>,
create_info: SwapchainCreateInfo,
) -> Result<(Arc<Swapchain>, Vec<Arc<Image>>), VulkanError> {
let SwapchainCreateInfo {
flags,
min_image_count,
image_format,
image_view_formats,
image_color_space,
image_extent,
image_array_layers,
image_usage,
image_sharing,
pre_transform,
composite_alpha,
present_mode,
present_modes,
clipped,
scaling_behavior,
present_gravity,
full_screen_exclusive,
win32_monitor,
_ne: _,
} = create_info;
let swapchain = Arc::new(Swapchain {
handle,
device: InstanceOwnedDebugWrapper(device),
surface: InstanceOwnedDebugWrapper(surface),
id: Self::next_id(),
flags,
min_image_count,
image_format,
image_view_formats,
image_color_space,
image_extent,
image_array_layers,
image_usage,
image_sharing,
pre_transform,
composite_alpha,
present_mode,
present_modes,
clipped,
scaling_behavior,
present_gravity,
full_screen_exclusive,
win32_monitor,
prev_present_id: Default::default(),
full_screen_exclusive_held: AtomicBool::new(false),
images: image_handles
.into_iter()
.map(|handle| ImageEntry {
handle,
layout_initialized: AtomicBool::new(false),
})
.collect(),
is_retired: Mutex::new(false),
});
let swapchain_images = swapchain
.images
.iter()
.enumerate()
.map(|(image_index, entry)| unsafe {
Ok(Arc::new(Image::from_swapchain(
entry.handle,
swapchain.clone(),
image_index as u32,
)?))
})
.collect::<Result<_, VulkanError>>()?;
Ok((swapchain, swapchain_images))
}
#[inline]
pub fn create_info(&self) -> SwapchainCreateInfo {
SwapchainCreateInfo {
flags: self.flags,
min_image_count: self.min_image_count,
image_format: self.image_format,
image_view_formats: self.image_view_formats.clone(),
image_color_space: self.image_color_space,
image_extent: self.image_extent,
image_array_layers: self.image_array_layers,
image_usage: self.image_usage,
image_sharing: self.image_sharing.clone(),
pre_transform: self.pre_transform,
composite_alpha: self.composite_alpha,
present_mode: self.present_mode,
present_modes: self.present_modes.clone(),
clipped: self.clipped,
scaling_behavior: self.scaling_behavior,
present_gravity: self.present_gravity,
full_screen_exclusive: self.full_screen_exclusive,
win32_monitor: self.win32_monitor,
_ne: crate::NonExhaustive(()),
}
}
#[inline]
pub fn surface(&self) -> &Arc<Surface> {
&self.surface
}
#[inline]
pub fn flags(&self) -> SwapchainCreateFlags {
self.flags
}
#[inline]
pub fn index_of_image(&self, image: &Image) -> Option<u32> {
self.images
.iter()
.position(|entry| entry.handle == image.handle())
.map(|i| i as u32)
}
#[inline]
pub fn image_count(&self) -> u32 {
self.images.len() as u32
}
#[inline]
pub fn image_format(&self) -> Format {
self.image_format
}
#[inline]
pub fn image_view_formats(&self) -> &[Format] {
&self.image_view_formats
}
#[inline]
pub fn image_color_space(&self) -> ColorSpace {
self.image_color_space
}
#[inline]
pub fn image_extent(&self) -> [u32; 2] {
self.image_extent
}
#[inline]
pub fn image_array_layers(&self) -> u32 {
self.image_array_layers
}
#[inline]
pub fn image_usage(&self) -> ImageUsage {
self.image_usage
}
#[inline]
pub fn image_sharing(&self) -> &Sharing<SmallVec<[u32; 4]>> {
&self.image_sharing
}
#[inline]
pub(crate) unsafe fn full_screen_exclusive_held(&self) -> &AtomicBool {
&self.full_screen_exclusive_held
}
#[inline]
pub(crate) unsafe fn try_claim_present_id(&self, present_id: NonZeroU64) -> bool {
let present_id = u64::from(present_id);
self.prev_present_id.fetch_max(present_id, Ordering::SeqCst) < present_id
}
#[inline]
pub fn pre_transform(&self) -> SurfaceTransform {
self.pre_transform
}
#[inline]
pub fn composite_alpha(&self) -> CompositeAlpha {
self.composite_alpha
}
#[inline]
pub fn present_mode(&self) -> PresentMode {
self.present_mode
}
#[inline]
pub fn present_modes(&self) -> &[PresentMode] {
&self.present_modes
}
#[inline]
pub fn clipped(&self) -> bool {
self.clipped
}
#[inline]
pub fn scaling_behavior(&self) -> Option<PresentScaling> {
self.scaling_behavior
}
#[inline]
pub fn present_gravity(&self) -> Option<[PresentGravity; 2]> {
self.present_gravity
}
#[inline]
pub fn full_screen_exclusive(&self) -> FullScreenExclusive {
self.full_screen_exclusive
}
#[inline]
pub fn acquire_full_screen_exclusive_mode(&self) -> Result<(), Validated<VulkanError>> {
self.validate_acquire_full_screen_exclusive_mode()?;
unsafe { Ok(self.acquire_full_screen_exclusive_mode_unchecked()?) }
}
fn validate_acquire_full_screen_exclusive_mode(&self) -> Result<(), Box<ValidationError>> {
if *self.is_retired.lock() {
return Err(Box::new(ValidationError {
problem: "this swapchain is retired".into(),
vuids: &["VUID-vkAcquireFullScreenExclusiveModeEXT-swapchain-02674"],
..Default::default()
}));
}
if self.full_screen_exclusive != FullScreenExclusive::ApplicationControlled {
return Err(Box::new(ValidationError {
context: "self.full_screen_exclusive()".into(),
problem: "is not `FullScreenExclusive::ApplicationControlled`".into(),
vuids: &["VUID-vkAcquireFullScreenExclusiveModeEXT-swapchain-02675"],
..Default::default()
}));
}
if self
.full_screen_exclusive_held
.swap(true, Ordering::Acquire)
{
return Err(Box::new(ValidationError {
problem: "this swapchain already holds full-screen exclusive access".into(),
vuids: &["VUID-vkAcquireFullScreenExclusiveModeEXT-swapchain-02676"],
..Default::default()
}));
}
Ok(())
}
#[cfg_attr(not(feature = "document_unchecked"), doc(hidden))]
pub unsafe fn acquire_full_screen_exclusive_mode_unchecked(&self) -> Result<(), VulkanError> {
self.full_screen_exclusive_held
.store(true, Ordering::Relaxed);
let fns = self.device.fns();
(fns.ext_full_screen_exclusive
.acquire_full_screen_exclusive_mode_ext)(self.device.handle(), self.handle)
.result()
.map_err(VulkanError::from)?;
Ok(())
}
#[inline]
pub fn release_full_screen_exclusive_mode(&self) -> Result<(), Validated<VulkanError>> {
self.validate_release_full_screen_exclusive_mode()?;
unsafe { Ok(self.release_full_screen_exclusive_mode_unchecked()?) }
}
fn validate_release_full_screen_exclusive_mode(&self) -> Result<(), Box<ValidationError>> {
if *self.is_retired.lock() {
return Err(Box::new(ValidationError {
problem: "this swapchain is retired".into(),
vuids: &["VUID-vkReleaseFullScreenExclusiveModeEXT-swapchain-02677"],
..Default::default()
}));
}
if self.full_screen_exclusive != FullScreenExclusive::ApplicationControlled {
return Err(Box::new(ValidationError {
context: "self.full_screen_exclusive()".into(),
problem: "is not `FullScreenExclusive::ApplicationControlled`".into(),
vuids: &["VUID-vkReleaseFullScreenExclusiveModeEXT-swapchain-02678"],
..Default::default()
}));
}
Ok(())
}
#[cfg_attr(not(feature = "document_unchecked"), doc(hidden))]
pub unsafe fn release_full_screen_exclusive_mode_unchecked(&self) -> Result<(), VulkanError> {
self.full_screen_exclusive_held
.store(false, Ordering::Release);
let fns = self.device.fns();
(fns.ext_full_screen_exclusive
.release_full_screen_exclusive_mode_ext)(self.device.handle(), self.handle)
.result()
.map_err(VulkanError::from)?;
Ok(())
}
#[inline]
pub fn is_full_screen_exclusive(&self) -> bool {
if self.full_screen_exclusive != FullScreenExclusive::ApplicationControlled {
false
} else {
self.full_screen_exclusive_held.load(Ordering::SeqCst)
}
}
pub(crate) fn image_layout_initialized(&self, image_index: u32) {
let image_entry = self.images.get(image_index as usize);
if let Some(image_entry) = image_entry {
image_entry
.layout_initialized
.store(true, Ordering::Relaxed);
}
}
pub(crate) fn is_image_layout_initialized(&self, image_index: u32) -> bool {
let image_entry = self.images.get(image_index as usize);
if let Some(image_entry) = image_entry {
image_entry.layout_initialized.load(Ordering::Relaxed)
} else {
false
}
}
}
impl Drop for Swapchain {
#[inline]
fn drop(&mut self) {
unsafe {
let fns = self.device.fns();
(fns.khr_swapchain.destroy_swapchain_khr)(
self.device.handle(),
self.handle,
ptr::null(),
);
}
}
}
unsafe impl VulkanObject for Swapchain {
type Handle = ash::vk::SwapchainKHR;
#[inline]
fn handle(&self) -> Self::Handle {
self.handle
}
}
unsafe impl DeviceOwned for Swapchain {
#[inline]
fn device(&self) -> &Arc<Device> {
&self.device
}
}
impl_id_counter!(Swapchain);
#[derive(Clone, Debug)]
pub struct SwapchainCreateInfo {
pub flags: SwapchainCreateFlags,
pub min_image_count: u32,
pub image_format: Format,
pub image_view_formats: Vec<Format>,
pub image_color_space: ColorSpace,
pub image_extent: [u32; 2],
pub image_array_layers: u32,
pub image_usage: ImageUsage,
pub image_sharing: Sharing<SmallVec<[u32; 4]>>,
pub pre_transform: SurfaceTransform,
pub composite_alpha: CompositeAlpha,
pub present_mode: PresentMode,
pub present_modes: SmallVec<[PresentMode; PresentMode::COUNT]>,
pub clipped: bool,
pub scaling_behavior: Option<PresentScaling>,
pub present_gravity: Option<[PresentGravity; 2]>,
pub full_screen_exclusive: FullScreenExclusive,
pub win32_monitor: Option<Win32Monitor>,
pub _ne: crate::NonExhaustive,
}
impl Default for SwapchainCreateInfo {
#[inline]
fn default() -> Self {
Self {
flags: SwapchainCreateFlags::empty(),
min_image_count: 2,
image_format: Format::UNDEFINED,
image_view_formats: Vec::new(),
image_color_space: ColorSpace::SrgbNonLinear,
image_extent: [0, 0],
image_array_layers: 1,
image_usage: ImageUsage::empty(),
image_sharing: Sharing::Exclusive,
pre_transform: SurfaceTransform::Identity,
composite_alpha: CompositeAlpha::Opaque,
present_mode: PresentMode::Fifo,
present_modes: SmallVec::new(),
clipped: true,
scaling_behavior: None,
present_gravity: None,
full_screen_exclusive: FullScreenExclusive::Default,
win32_monitor: None,
_ne: crate::NonExhaustive(()),
}
}
}
impl SwapchainCreateInfo {
pub(crate) fn validate(&self, device: &Device) -> Result<(), Box<ValidationError>> {
let &Self {
flags,
min_image_count: _,
image_format,
ref image_view_formats,
image_color_space,
image_extent,
image_array_layers,
image_usage,
ref image_sharing,
pre_transform,
composite_alpha,
present_mode,
ref present_modes,
clipped: _,
scaling_behavior,
present_gravity,
full_screen_exclusive,
win32_monitor: _,
_ne: _,
} = self;
flags.validate_device(device).map_err(|err| {
err.add_context("flags")
.set_vuids(&["VUID-VkSwapchainCreateInfoKHR-flags-parameter"])
})?;
image_format.validate_device(device).map_err(|err| {
err.add_context("image_format")
.set_vuids(&["VUID-VkSwapchainCreateInfoKHR-imageFormat-parameter"])
})?;
image_color_space.validate_device(device).map_err(|err| {
err.add_context("image_color_space")
.set_vuids(&["VUID-VkSwapchainCreateInfoKHR-imageColorSpace-parameter"])
})?;
image_usage.validate_device(device).map_err(|err| {
err.add_context("image_usage")
.set_vuids(&["VUID-VkSwapchainCreateInfoKHR-imageUsage-parameter"])
})?;
if image_usage.is_empty() {
return Err(Box::new(ValidationError {
context: "image_usage".into(),
problem: "is empty".into(),
vuids: &["VUID-VkSwapchainCreateInfoKHR-imageUsage-requiredbitmask"],
..Default::default()
}));
}
pre_transform.validate_device(device).map_err(|err| {
err.add_context("pre_transform")
.set_vuids(&["VUID-VkSwapchainCreateInfoKHR-preTransform-parameter"])
})?;
composite_alpha.validate_device(device).map_err(|err| {
err.add_context("composite_alpha")
.set_vuids(&["VUID-VkSwapchainCreateInfoKHR-compositeAlpha-parameter"])
})?;
present_mode.validate_device(device).map_err(|err| {
err.add_context("present_mode")
.set_vuids(&["VUID-VkSwapchainCreateInfoKHR-presentMode-parameter"])
})?;
if image_extent.contains(&0) {
return Err(Box::new(ValidationError {
context: "image_extent".into(),
problem: "one or more elements are zero".into(),
vuids: &["VUID-VkSwapchainCreateInfoKHR-imageExtent-01689"],
..Default::default()
}));
}
if image_array_layers == 0 {
return Err(Box::new(ValidationError {
context: "image_array_layers".into(),
problem: "is zero".into(),
vuids: &["VUID-VkSwapchainCreateInfoKHR-imageArrayLayers-01275"],
..Default::default()
}));
}
match image_sharing {
Sharing::Exclusive => (),
Sharing::Concurrent(queue_family_indices) => {
if queue_family_indices.len() < 2 {
return Err(Box::new(ValidationError {
context: "image_sharing".into(),
problem: "is `Sharing::Concurrent`, and contains less than 2 \
queue family indices"
.into(),
vuids: &["VUID-VkSwapchainCreateInfoKHR-imageSharingMode-01278"],
..Default::default()
}));
}
let queue_family_count =
device.physical_device().queue_family_properties().len() as u32;
for (index, &queue_family_index) in queue_family_indices.iter().enumerate() {
if queue_family_indices[..index].contains(&queue_family_index) {
return Err(Box::new(ValidationError {
context: "queue_family_indices".into(),
problem: format!(
"the queue family index in the list at index {} is contained in \
the list more than once",
index,
)
.into(),
vuids: &["VUID-VkSwapchainCreateInfoKHR-imageSharingMode-01428"],
..Default::default()
}));
}
if queue_family_index >= queue_family_count {
return Err(Box::new(ValidationError {
context: format!("queue_family_indices[{}]", index).into(),
problem: "is not less than the number of queue families in the \
physical device"
.into(),
vuids: &["VUID-VkSwapchainCreateInfoKHR-imageSharingMode-01428"],
..Default::default()
}));
}
}
}
};
let image_format_properties = unsafe {
device
.physical_device()
.image_format_properties_unchecked(ImageFormatInfo {
format: image_format,
image_type: ImageType::Dim2d,
tiling: ImageTiling::Optimal,
usage: image_usage,
..Default::default()
})
.map_err(|_err| {
Box::new(ValidationError {
problem: "`PhysicalDevice::image_format_properties` \
returned an error"
.into(),
..Default::default()
})
})?
};
if image_format_properties.is_none() {
return Err(Box::new(ValidationError {
problem: "the combination of `image_format` and `image_usage` is not supported \
for images by the physical device"
.into(),
vuids: &["VUID-VkSwapchainCreateInfoKHR-imageFormat-01778"],
..Default::default()
}));
}
if flags.intersects(SwapchainCreateFlags::MUTABLE_FORMAT)
&& !image_view_formats.contains(&image_format)
{
return Err(Box::new(ValidationError {
problem: "`flags` contains `SwapchainCreateFlags::MUTABLE_FORMAT`, but \
`image_view_formats` does not contain `image_format`"
.into(),
vuids: &["VUID-VkSwapchainCreateInfoKHR-flags-03168"],
..Default::default()
}));
}
if !image_view_formats.is_empty() {
if !(device.api_version() >= Version::V1_2
|| device.enabled_extensions().khr_image_format_list)
{
return Err(Box::new(ValidationError {
context: "image_view_formats".into(),
problem: "is not empty".into(),
requires_one_of: RequiresOneOf(&[
RequiresAllOf(&[Requires::APIVersion(Version::V1_2)]),
RequiresAllOf(&[Requires::DeviceExtension("khr_image_format_list")]),
]),
..Default::default()
}));
}
if !flags.intersects(SwapchainCreateFlags::MUTABLE_FORMAT)
&& image_view_formats.len() != 1
{
return Err(Box::new(ValidationError {
problem: "`flags` does not contain `SwapchainCreateFlags::MUTABLE_FORMAT`, \
but `image_view_formats` contains more than one element"
.into(),
vuids: &["VUID-VkSwapchainCreateInfoKHR-flags-04100"],
..Default::default()
}));
}
for (index, &image_view_format) in image_view_formats.iter().enumerate() {
image_view_format.validate_device(device).map_err(|err| {
err.add_context(format!("image_view_formats[{}]", index))
.set_vuids(&["VUID-VkImageFormatListCreateInfo-pViewFormats-parameter"])
})?;
if image_view_format.compatibility() != image_format.compatibility() {
return Err(Box::new(ValidationError {
problem: format!(
"`image_view_formats[{}]` is not compatible with `image_format`",
index
)
.into(),
vuids: &["VUID-VkSwapchainCreateInfoKHR-pNext-04099"],
..Default::default()
}));
}
}
}
if !present_modes.is_empty() {
if !device.enabled_extensions().ext_swapchain_maintenance1 {
return Err(Box::new(ValidationError {
context: "present_modes".into(),
problem: "is not empty".into(),
requires_one_of: RequiresOneOf(&[RequiresAllOf(&[Requires::DeviceExtension(
"ext_swapchain_maintenance1",
)])]),
..Default::default()
}));
}
for (index, &present_mode) in present_modes.iter().enumerate() {
present_mode.validate_device(device).map_err(|err| {
err.add_context(format!("present_modes[{}]", index))
.set_vuids(&[
"VUID-VkSwapchainPresentModesCreateInfoEXT-pPresentModes-parameter",
])
})?;
}
if !present_modes.contains(&present_mode) {
return Err(Box::new(ValidationError {
problem: "`present_modes` is not empty, but does not contain `present_mode`"
.into(),
vuids: &["VUID-VkSwapchainPresentModesCreateInfoEXT-presentMode-07764"],
..Default::default()
}));
}
}
if let Some(scaling_behavior) = scaling_behavior {
if !device.enabled_extensions().ext_swapchain_maintenance1 {
return Err(Box::new(ValidationError {
context: "scaling_behavior".into(),
problem: "is `Some`".into(),
requires_one_of: RequiresOneOf(&[RequiresAllOf(&[Requires::DeviceExtension(
"ext_swapchain_maintenance1",
)])]),
..Default::default()
}));
}
scaling_behavior.validate_device(device).map_err(|err| {
err.add_context("scaling_behavior").set_vuids(&[
"VUID-VkSwapchainPresentScalingCreateInfoEXT-scalingBehavior-parameter",
])
})?;
}
if let Some(present_gravity) = present_gravity {
if !device.enabled_extensions().ext_swapchain_maintenance1 {
return Err(Box::new(ValidationError {
context: "present_gravity".into(),
problem: "is `Some`".into(),
requires_one_of: RequiresOneOf(&[RequiresAllOf(&[Requires::DeviceExtension(
"ext_swapchain_maintenance1",
)])]),
..Default::default()
}));
}
for (axis_index, present_gravity) in present_gravity.into_iter().enumerate() {
present_gravity.validate_device(device).map_err(|err| {
err.add_context(format!("present_gravity[{}]", axis_index))
.set_vuids(&[
"VUID-VkSwapchainPresentScalingCreateInfoEXT-presentGravityX-parameter",
"VUID-VkSwapchainPresentScalingCreateInfoEXT-presentGravityY-parameter",
])
})?;
}
}
if full_screen_exclusive != FullScreenExclusive::Default {
if !device.enabled_extensions().ext_full_screen_exclusive {
return Err(Box::new(ValidationError {
context: "full_screen_exclusive".into(),
problem: "is not `FullScreenExclusive::Default`".into(),
requires_one_of: RequiresOneOf(&[RequiresAllOf(&[Requires::DeviceExtension(
"ext_full_screen_exclusive",
)])]),
..Default::default()
}));
}
full_screen_exclusive
.validate_device(device)
.map_err(|err| {
err.add_context("full_screen_exclusive").set_vuids(&[
"VUID-VkSurfaceFullScreenExclusiveInfoEXT-fullScreenExclusive-parameter",
])
})?;
}
Ok(())
}
}
vulkan_bitflags! {
#[non_exhaustive]
SwapchainCreateFlags = SwapchainCreateFlagsKHR(u32);
MUTABLE_FORMAT = MUTABLE_FORMAT
RequiresOneOf([
RequiresAllOf([DeviceExtension(khr_swapchain_mutable_format)]),
]),
}
impl From<SwapchainCreateFlags> for ImageCreateFlags {
#[inline]
fn from(flags: SwapchainCreateFlags) -> Self {
let mut result = ImageCreateFlags::empty();
if flags.intersects(SwapchainCreateFlags::MUTABLE_FORMAT) {
result |= ImageCreateFlags::MUTABLE_FORMAT | ImageCreateFlags::EXTENDED_USAGE;
}
result
}
}
vulkan_bitflags_enum! {
#[non_exhaustive]
PresentScalingFlags,
PresentScaling,
= PresentScalingFlagsEXT(u32);
ONE_TO_ONE, OneToOne = ONE_TO_ONE,
ASPECT_RATIO_STRETCH, AspectRatioStretch = ASPECT_RATIO_STRETCH,
STRETCH, Stretch = STRETCH,
}
vulkan_bitflags_enum! {
#[non_exhaustive]
PresentGravityFlags,
PresentGravity,
= PresentGravityFlagsEXT(u32);
MIN, Min = MIN,
MAX, Max = MAX,
CENTERED, Centered = CENTERED,
}
vulkan_enum! {
#[non_exhaustive]
FullScreenExclusive = FullScreenExclusiveEXT(i32);
Default = DEFAULT,
Allowed = ALLOWED,
Disallowed = DISALLOWED,
ApplicationControlled = APPLICATION_CONTROLLED,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct Win32Monitor(pub(crate) ash::vk::HMONITOR);
impl Win32Monitor {
pub unsafe fn new<T>(hmonitor: *const T) -> Self {
Self(hmonitor as _)
}
}
unsafe impl Send for Win32Monitor {}
unsafe impl Sync for Win32Monitor {}