use super::{
VulkanCommandBuffer, VulkanCommandBufferPool, VulkanImage, VulkanKhrSurface, VulkanMemoryHelper,
VulkanSemaphorePair,
};
use crate::{
IVulkanKhrSurfacePlatformSurface, IVulkanPlatformGraphicsContext, VulkanException, VulkanExceptionExtensions,
VulkanResultExtensions,
};
use ash::vk;
use ferroui_base::PixelSize;
use std::cell::{Cell, RefCell};
use std::rc::Rc;
use std::thread;
use std::time::Duration;
pub(crate) struct VulkanDisplay {
context: Rc<dyn IVulkanPlatformGraphicsContext>,
semaphore_pair: VulkanSemaphorePair,
next_image: Cell<u32>,
surface: RefCell<Option<VulkanKhrSurface>>,
surface_format: Cell<vk::SurfaceFormatKHR>,
swapchain: Cell<vk::SwapchainKHR>,
swapchain_extent: Cell<vk::Extent2D>,
platform_surface: Rc<dyn IVulkanKhrSurfacePlatformSurface>,
swapchain_images: RefCell<Vec<vk::Image>>,
swapchain_image_views: RefCell<Vec<vk::ImageView>>,
command_buffer_pool: Rc<VulkanCommandBufferPool>,
size: Cell<PixelSize>,
parameters: Cell<SwapchainParameters>,
state: Cell<SwapchainState>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum SwapchainResult {
Success,
Suboptimal,
OutOfDate,
SurfaceLost,
Failed(vk::Result),
}
impl SwapchainResult {
pub fn of(result: vk::Result) -> Self {
match result {
vk::Result::SUCCESS => Self::Success,
vk::Result::SUBOPTIMAL_KHR => Self::Suboptimal,
vk::Result::ERROR_OUT_OF_DATE_KHR => Self::OutOfDate,
vk::Result::ERROR_SURFACE_LOST_KHR => Self::SurfaceLost,
other => Self::Failed(other),
}
}
fn severity(self) -> u8 {
match self {
Self::Success => 0,
Self::Suboptimal => 1,
Self::OutOfDate => 2,
Self::SurfaceLost => 3,
Self::Failed(_) => 4,
}
}
pub fn of_present(call_result: vk::Result, result: vk::Result) -> Self {
let (call, swapchain) = (Self::of(call_result), Self::of(result));
if call.severity() >= swapchain.severity() { call } else { swapchain }
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum SwapchainState {
Current,
Suboptimal,
OutOfDate,
}
impl SwapchainState {
pub fn after(self, result: SwapchainResult) -> Self {
match (self, result) {
(_, SwapchainResult::OutOfDate) | (Self::OutOfDate, _) => Self::OutOfDate,
(_, SwapchainResult::Suboptimal) | (Self::Suboptimal, _) => Self::Suboptimal,
_ => Self::Current,
}
}
pub fn needs_recreation(
self,
size_differs: bool,
parameters_changed: impl FnOnce() -> Result<bool, VulkanException>,
) -> Result<bool, VulkanException> {
Ok(match self {
_ if size_differs => true,
Self::OutOfDate => true,
Self::Suboptimal => parameters_changed()?,
Self::Current => false,
})
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) struct SwapchainParameters {
pub image_count: u32,
pub extent: vk::Extent2D,
pub present_mode: vk::PresentModeKHR,
pub pre_transform: vk::SurfaceTransformFlagsKHR,
pub composite_alpha: vk::CompositeAlphaFlagsKHR,
}
impl SwapchainParameters {
pub fn new(
capabilities: &vk::SurfaceCapabilitiesKHR,
modes: &[vk::PresentModeKHR],
surface_size: PixelSize,
) -> Self {
let mut image_count = capabilities.min_image_count.wrapping_add(1);
if capabilities.max_image_count > 0 && image_count > capabilities.max_image_count {
image_count = capabilities.max_image_count;
}
let supports_identity_transform =
capabilities.supported_transforms.contains(vk::SurfaceTransformFlagsKHR::IDENTITY);
let is_rotated = capabilities.current_transform.contains(vk::SurfaceTransformFlagsKHR::ROTATE_90)
|| capabilities.current_transform.contains(vk::SurfaceTransformFlagsKHR::ROTATE_270);
let extent = if capabilities.current_extent.width != u32::MAX {
capabilities.current_extent
} else {
let width = capabilities
.min_image_extent
.width
.max(capabilities.max_image_extent.width.min(surface_size.width as u32));
let height = capabilities
.min_image_extent
.height
.max(capabilities.max_image_extent.height.min(surface_size.height as u32));
vk::Extent2D { width, height }
};
let present_mode = if modes.contains(&vk::PresentModeKHR::MAILBOX) {
vk::PresentModeKHR::MAILBOX
} else if modes.contains(&vk::PresentModeKHR::FIFO) {
vk::PresentModeKHR::FIFO
} else {
vk::PresentModeKHR::IMMEDIATE
};
Self {
image_count,
extent,
present_mode,
pre_transform: if supports_identity_transform && is_rotated {
vk::SurfaceTransformFlagsKHR::IDENTITY
} else {
capabilities.current_transform
},
composite_alpha: if capabilities
.supported_composite_alpha
.contains(vk::CompositeAlphaFlagsKHR::PRE_MULTIPLIED)
{
vk::CompositeAlphaFlagsKHR::PRE_MULTIPLIED
} else {
vk::CompositeAlphaFlagsKHR::OPAQUE
},
}
}
}
impl VulkanDisplay {
fn new(
context: Rc<dyn IVulkanPlatformGraphicsContext>,
surface: VulkanKhrSurface,
swapchain: vk::SwapchainKHR,
parameters: SwapchainParameters,
platform_surface: Rc<dyn IVulkanKhrSurfacePlatformSurface>,
) -> Result<Self, VulkanException> {
let semaphore_pair = VulkanSemaphorePair::new(context.clone())?;
let command_buffer_pool = VulkanCommandBufferPool::new(context.clone(), false)?;
let display = Self {
context,
semaphore_pair,
next_image: Cell::new(0),
surface: RefCell::new(Some(surface)),
surface_format: Cell::new(vk::SurfaceFormatKHR::default()),
swapchain: Cell::new(swapchain),
swapchain_extent: Cell::new(parameters.extent),
platform_surface,
swapchain_images: RefCell::new(Vec::new()),
swapchain_image_views: RefCell::new(Vec::new()),
command_buffer_pool,
size: Cell::new(PixelSize::default()),
parameters: Cell::new(parameters),
state: Cell::new(SwapchainState::Current),
};
display.create_swapchain_images()?;
Ok(display)
}
pub fn command_buffer_pool(&self) -> &Rc<VulkanCommandBufferPool> {
&self.command_buffer_pool
}
pub fn size(&self) -> PixelSize {
self.size.get()
}
pub fn surface_format(&self) -> Result<vk::SurfaceFormatKHR, VulkanException> {
if self.surface_format.get().format == vk::Format::UNDEFINED {
if let Some(surface) = self.surface.borrow().as_ref() {
self.surface_format.set(surface.get_surface_format()?);
}
}
Ok(self.surface_format.get())
}
fn create_swapchain(
context: &Rc<dyn IVulkanPlatformGraphicsContext>,
surface: &VulkanKhrSurface,
old_display: Option<&VulkanDisplay>,
) -> Result<(vk::SwapchainKHR, SwapchainParameters), VulkanException> {
while !surface.can_surface_present()? {
thread::sleep(Duration::from_millis(16));
}
let capabilities = context
.instance_api()
.get_physical_device_surface_capabilities_khr(context.physical_device_handle(), surface.handle())
.throw_on_error("vkGetPhysicalDeviceSurfaceCapabilitiesKHR")?;
let modes = context
.instance_api()
.get_physical_device_surface_present_modes_khr(context.physical_device_handle(), surface.handle())
.throw_on_error("vkGetPhysicalDeviceSurfacePresentModesKHR")?;
let surface_format = surface.get_surface_format()?;
let parameters = SwapchainParameters::new(&capabilities, &modes, surface.size());
let swapchain_create_info = vk::SwapchainCreateInfoKHR::default()
.surface(surface.handle())
.min_image_count(parameters.image_count)
.image_format(surface_format.format)
.image_color_space(surface_format.color_space)
.image_extent(parameters.extent)
.image_usage(vk::ImageUsageFlags::COLOR_ATTACHMENT | vk::ImageUsageFlags::TRANSFER_DST)
.image_sharing_mode(vk::SharingMode::EXCLUSIVE)
.image_array_layers(1)
.pre_transform(parameters.pre_transform)
.composite_alpha(parameters.composite_alpha)
.present_mode(parameters.present_mode)
.clipped(true)
.old_swapchain(old_display.map_or(vk::SwapchainKHR::null(), |display| display.swapchain.get()));
let swapchain = context
.device_api()
.create_swapchain_khr(context.device_handle(), &swapchain_create_info)
.throw_on_error("vkCreateSwapchainKHR")?;
if let Some(old_display) = old_display {
old_display.destroy_swapchain();
}
Ok((swapchain, parameters))
}
fn parameters_changed(&self) -> Result<bool, VulkanException> {
let surface = self.surface.borrow();
let Some(surface) = surface.as_ref() else { return Ok(true) };
let capabilities = self
.context
.instance_api()
.get_physical_device_surface_capabilities_khr(self.context.physical_device_handle(), surface.handle())
.throw_on_error("vkGetPhysicalDeviceSurfaceCapabilitiesKHR")?;
let current = self.parameters.get();
Ok(SwapchainParameters::new(&capabilities, &[current.present_mode], surface.size()) != current)
}
fn destroy_swapchain(&self) {
if self.swapchain.get() != vk::SwapchainKHR::null() {
self.context.device_api().destroy_swapchain_khr(self.context.device_handle(), self.swapchain.get());
}
self.swapchain.set(vk::SwapchainKHR::null());
}
pub fn create_display(
context: &Rc<dyn IVulkanPlatformGraphicsContext>,
surface: Rc<dyn IVulkanKhrSurfacePlatformSurface>,
) -> Result<VulkanDisplay, VulkanException> {
let khr_surface = VulkanKhrSurface::new(context.clone(), surface.clone());
let (swapchain, parameters) = Self::create_swapchain(context, &khr_surface, None)?;
VulkanDisplay::new(context.clone(), khr_surface, swapchain, parameters, surface)
}
fn destroy_current_image_views(&self) {
let views = std::mem::take(&mut *self.swapchain_image_views.borrow_mut());
for image_view in views {
self.context.device_api().destroy_image_view(self.context.device_handle(), image_view);
}
}
fn create_swapchain_images(&self) -> Result<(), VulkanException> {
self.destroy_current_image_views();
let extent = self.swapchain_extent.get();
self.size.set(PixelSize::new(extent.width as i32, extent.height as i32));
let images = self
.context
.device_api()
.get_swapchain_images_khr(self.context.device_handle(), self.swapchain.get())
.throw_on_error("vkGetSwapchainImagesKHR")?;
let format = self.surface_format()?.format;
let mut views = Vec::with_capacity(images.len());
for image in &images {
views.push(self.create_swapchain_image_view(*image, format)?);
}
*self.swapchain_images.borrow_mut() = images;
*self.swapchain_image_views.borrow_mut() = views;
Ok(())
}
fn create_swapchain_image_view(
&self,
swapchain_image: vk::Image,
format: vk::Format,
) -> Result<vk::ImageView, VulkanException> {
let image_view_create_info = vk::ImageViewCreateInfo::default()
.subresource_range(vk::ImageSubresourceRange {
aspect_mask: vk::ImageAspectFlags::COLOR,
level_count: 1,
layer_count: 1,
..Default::default()
})
.format(format)
.image(swapchain_image)
.view_type(vk::ImageViewType::TYPE_2D);
self.context
.device_api()
.create_image_view(self.context.device_handle(), &image_view_create_info)
.throw_on_error("vkCreateImageView")
}
fn recreate_swapchain(&self) -> Result<(), VulkanException> {
if self.surface.borrow().is_none() {
return self.recreate_surface();
}
let _ = self.context.device_api().device_wait_idle(self.context.device_handle());
let (swapchain, parameters) = {
let surface = self.surface.borrow();
let surface = surface.as_ref().expect("the surface was checked above");
Self::create_swapchain(&self.context, surface, Some(self))?
};
self.swapchain.set(swapchain);
self.swapchain_extent.set(parameters.extent);
self.parameters.set(parameters);
self.state.set(SwapchainState::Current);
self.create_swapchain_images()
}
fn recreate_surface(&self) -> Result<(), VulkanException> {
if let Some(surface) = self.surface.borrow_mut().take() {
surface.dispose();
}
*self.surface.borrow_mut() =
Some(VulkanKhrSurface::new(self.context.clone(), self.platform_surface.clone()));
self.destroy_swapchain();
self.recreate_swapchain()
}
pub fn ensure_swapchain_available(&self) -> Result<bool, VulkanException> {
let surface_size = self.surface.borrow().as_ref().map(|surface| surface.size());
let state = self.state.replace(SwapchainState::Current);
if state.needs_recreation(Some(self.size.get()) != surface_size, || self.parameters_changed())? {
self.recreate_swapchain()?;
return Ok(true);
}
Ok(false)
}
pub fn start_presentation(&self) -> Result<Rc<VulkanCommandBuffer>, VulkanException> {
self.next_image.set(0);
loop {
let (acquire_result, next_image) = self.context.device_api().acquire_next_image_khr(
self.context.device_handle(),
self.swapchain.get(),
u64::MAX,
self.semaphore_pair.image_available_semaphore().handle(),
vk::Fence::null(),
);
self.next_image.set(next_image);
match SwapchainResult::of(acquire_result) {
SwapchainResult::Success => break,
SwapchainResult::Suboptimal => {
self.state.set(self.state.get().after(SwapchainResult::Suboptimal));
break;
}
SwapchainResult::OutOfDate => self.recreate_swapchain()?,
SwapchainResult::SurfaceLost => self.recreate_surface()?,
SwapchainResult::Failed(result) => result.throw_on_error("vkAcquireNextImageKHR")?,
}
}
let command_buffer = self.command_buffer_pool.create_command_buffer()?;
command_buffer.begin_recording()?;
VulkanMemoryHelper::transition_layout(
&*self.context,
&command_buffer,
self.current_image(),
vk::ImageLayout::UNDEFINED,
vk::AccessFlags::NONE,
vk::ImageLayout::TRANSFER_DST_OPTIMAL,
vk::AccessFlags::TRANSFER_WRITE,
1,
);
Ok(command_buffer)
}
fn current_image(&self) -> vk::Image {
self.swapchain_images.borrow()[self.next_image.get() as usize]
}
pub fn blit_image_to_current_image(&self, command_buffer: &VulkanCommandBuffer, image: &VulkanImage) {
VulkanMemoryHelper::transition_layout(
&*self.context,
command_buffer,
image.handle(),
image.current_layout(),
vk::AccessFlags::NONE,
vk::ImageLayout::TRANSFER_SRC_OPTIMAL,
vk::AccessFlags::TRANSFER_READ,
image.mip_levels(),
);
let size = self.size.get();
let src_blit_region = vk::ImageBlit {
src_offsets: [
vk::Offset3D::default(),
vk::Offset3D { x: image.size().width, y: image.size().height, z: 1 },
],
dst_offsets: [vk::Offset3D::default(), vk::Offset3D { x: size.width, y: size.height, z: 1 }],
src_subresource: vk::ImageSubresourceLayers {
aspect_mask: vk::ImageAspectFlags::COLOR,
layer_count: 1,
..Default::default()
},
dst_subresource: vk::ImageSubresourceLayers {
aspect_mask: vk::ImageAspectFlags::COLOR,
layer_count: 1,
..Default::default()
},
};
self.context.device_api().cmd_blit_image(
command_buffer.handle(),
image.handle(),
vk::ImageLayout::TRANSFER_SRC_OPTIMAL,
self.current_image(),
vk::ImageLayout::TRANSFER_DST_OPTIMAL,
&[src_blit_region],
vk::Filter::LINEAR,
);
VulkanMemoryHelper::transition_layout(
&*self.context,
command_buffer,
image.handle(),
vk::ImageLayout::TRANSFER_SRC_OPTIMAL,
vk::AccessFlags::TRANSFER_READ,
image.current_layout(),
vk::AccessFlags::NONE,
image.mip_levels(),
);
}
pub fn end_presentation(&self, command_buffer: &VulkanCommandBuffer) -> Result<(), VulkanException> {
VulkanMemoryHelper::transition_layout(
&*self.context,
command_buffer,
self.current_image(),
vk::ImageLayout::TRANSFER_DST_OPTIMAL,
vk::AccessFlags::NONE,
vk::ImageLayout::PRESENT_SRC_KHR,
vk::AccessFlags::NONE,
1,
);
command_buffer.submit_with(
&[self.semaphore_pair.image_available_semaphore()],
&[vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT],
&[self.semaphore_pair.render_finished_semaphore()],
None,
)?;
let (call_result, result) = self.context.device_api().vk_queue_present_khr(
self.context.main_queue_handle(),
self.semaphore_pair.render_finished_semaphore().handle(),
self.swapchain.get(),
self.next_image.get(),
);
match SwapchainResult::of_present(call_result, result) {
SwapchainResult::Success => Ok(()),
outcome @ (SwapchainResult::Suboptimal | SwapchainResult::OutOfDate) => {
self.state.set(self.state.get().after(outcome));
Ok(())
}
SwapchainResult::SurfaceLost => vk::Result::ERROR_SURFACE_LOST_KHR.throw_on_error("vkQueuePresentKHR"),
SwapchainResult::Failed(result) => result.throw_on_error("vkQueuePresentKHR"),
}
}
pub fn dispose(&self) {
let _ = self.context.device_api().device_wait_idle(self.context.device_handle());
self.semaphore_pair.dispose();
self.destroy_current_image_views();
self.destroy_swapchain();
self.command_buffer_pool.dispose();
if let Some(surface) = self.surface.borrow_mut().take() {
surface.dispose();
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn capabilities() -> vk::SurfaceCapabilitiesKHR {
vk::SurfaceCapabilitiesKHR {
min_image_count: 2,
max_image_count: 0,
current_extent: vk::Extent2D { width: 800, height: 600 },
min_image_extent: vk::Extent2D { width: 1, height: 1 },
max_image_extent: vk::Extent2D { width: 4096, height: 4096 },
max_image_array_layers: 1,
supported_transforms: vk::SurfaceTransformFlagsKHR::IDENTITY,
current_transform: vk::SurfaceTransformFlagsKHR::IDENTITY,
supported_composite_alpha: vk::CompositeAlphaFlagsKHR::OPAQUE,
supported_usage_flags: vk::ImageUsageFlags::COLOR_ATTACHMENT,
}
}
const FIFO: &[vk::PresentModeKHR] = &[vk::PresentModeKHR::FIFO];
#[test]
fn one_image_more_than_the_minimum_within_the_maximum() {
let mut caps = capabilities();
assert_eq!(SwapchainParameters::new(&caps, FIFO, PixelSize::new(1, 1)).image_count, 3);
caps.max_image_count = 8;
assert_eq!(SwapchainParameters::new(&caps, FIFO, PixelSize::new(1, 1)).image_count, 3);
caps.max_image_count = 2;
assert_eq!(SwapchainParameters::new(&caps, FIFO, PixelSize::new(1, 1)).image_count, 2);
}
#[test]
fn the_extent_of_the_surface_is_taken_when_it_has_one() {
let caps = capabilities();
let parameters = SwapchainParameters::new(&caps, FIFO, PixelSize::new(320, 200));
assert_eq!(parameters.extent, vk::Extent2D { width: 800, height: 600 });
}
#[test]
fn an_open_extent_is_the_size_of_the_platform_surface_within_the_limits() {
let mut caps = capabilities();
caps.current_extent = vk::Extent2D { width: u32::MAX, height: u32::MAX };
caps.min_image_extent = vk::Extent2D { width: 16, height: 16 };
caps.max_image_extent = vk::Extent2D { width: 1000, height: 500 };
let extent = |w, h| SwapchainParameters::new(&caps, FIFO, PixelSize::new(w, h)).extent;
assert_eq!(extent(320, 200), vk::Extent2D { width: 320, height: 200 });
assert_eq!(extent(4000, 3000), vk::Extent2D { width: 1000, height: 500 });
assert_eq!(extent(1, 2), vk::Extent2D { width: 16, height: 16 });
}
#[test]
fn mailbox_before_fifo_before_immediate() {
let caps = capabilities();
let mode = |modes: &[vk::PresentModeKHR]| SwapchainParameters::new(&caps, modes, PixelSize::new(1, 1)).present_mode;
assert_eq!(
mode(&[vk::PresentModeKHR::IMMEDIATE, vk::PresentModeKHR::FIFO, vk::PresentModeKHR::MAILBOX]),
vk::PresentModeKHR::MAILBOX
);
assert_eq!(mode(&[vk::PresentModeKHR::IMMEDIATE, vk::PresentModeKHR::FIFO]), vk::PresentModeKHR::FIFO);
assert_eq!(mode(&[vk::PresentModeKHR::FIFO_RELAXED]), vk::PresentModeKHR::IMMEDIATE);
assert_eq!(mode(&[]), vk::PresentModeKHR::IMMEDIATE);
}
#[test]
fn a_quarter_turn_is_left_to_the_compositor_when_the_surface_allows() {
let mut caps = capabilities();
caps.current_transform = vk::SurfaceTransformFlagsKHR::ROTATE_90;
caps.supported_transforms = vk::SurfaceTransformFlagsKHR::IDENTITY | vk::SurfaceTransformFlagsKHR::ROTATE_90;
let transform = |caps: &vk::SurfaceCapabilitiesKHR| {
SwapchainParameters::new(caps, FIFO, PixelSize::new(1, 1)).pre_transform
};
assert_eq!(transform(&caps), vk::SurfaceTransformFlagsKHR::IDENTITY);
caps.current_transform = vk::SurfaceTransformFlagsKHR::ROTATE_270;
assert_eq!(transform(&caps), vk::SurfaceTransformFlagsKHR::IDENTITY);
caps.current_transform = vk::SurfaceTransformFlagsKHR::ROTATE_180;
assert_eq!(transform(&caps), vk::SurfaceTransformFlagsKHR::ROTATE_180);
caps.current_transform = vk::SurfaceTransformFlagsKHR::ROTATE_90;
caps.supported_transforms = vk::SurfaceTransformFlagsKHR::ROTATE_90;
assert_eq!(transform(&caps), vk::SurfaceTransformFlagsKHR::ROTATE_90);
}
#[test]
fn the_results_of_the_presentation_engine_are_classified() {
assert_eq!(SwapchainResult::of(vk::Result::SUCCESS), SwapchainResult::Success);
assert_eq!(SwapchainResult::of(vk::Result::SUBOPTIMAL_KHR), SwapchainResult::Suboptimal);
assert_eq!(SwapchainResult::of(vk::Result::ERROR_OUT_OF_DATE_KHR), SwapchainResult::OutOfDate);
assert_eq!(SwapchainResult::of(vk::Result::ERROR_SURFACE_LOST_KHR), SwapchainResult::SurfaceLost);
assert_eq!(
SwapchainResult::of(vk::Result::ERROR_DEVICE_LOST),
SwapchainResult::Failed(vk::Result::ERROR_DEVICE_LOST)
);
assert_eq!(SwapchainResult::of(vk::Result::TIMEOUT), SwapchainResult::Failed(vk::Result::TIMEOUT));
}
#[test]
fn a_present_is_the_worse_of_its_two_results() {
let present = SwapchainResult::of_present;
assert_eq!(present(vk::Result::SUCCESS, vk::Result::SUCCESS), SwapchainResult::Success);
assert_eq!(present(vk::Result::SUBOPTIMAL_KHR, vk::Result::SUBOPTIMAL_KHR), SwapchainResult::Suboptimal);
assert_eq!(present(vk::Result::SUCCESS, vk::Result::SUBOPTIMAL_KHR), SwapchainResult::Suboptimal);
assert_eq!(
present(vk::Result::ERROR_OUT_OF_DATE_KHR, vk::Result::SUBOPTIMAL_KHR),
SwapchainResult::OutOfDate
);
assert_eq!(
present(vk::Result::ERROR_DEVICE_LOST, vk::Result::SUCCESS),
SwapchainResult::Failed(vk::Result::ERROR_DEVICE_LOST)
);
assert_eq!(
present(vk::Result::SUBOPTIMAL_KHR, vk::Result::ERROR_SURFACE_LOST_KHR),
SwapchainResult::SurfaceLost
);
}
#[test]
fn the_state_keeps_the_strongest_report_until_it_is_checked() {
use SwapchainState::*;
assert_eq!(Current.after(SwapchainResult::Success), Current);
assert_eq!(Current.after(SwapchainResult::Suboptimal), Suboptimal);
assert_eq!(Suboptimal.after(SwapchainResult::Success), Suboptimal);
assert_eq!(Suboptimal.after(SwapchainResult::OutOfDate), OutOfDate);
assert_eq!(OutOfDate.after(SwapchainResult::Suboptimal), OutOfDate);
}
#[test]
fn a_suboptimal_swap_chain_is_recreated_only_when_the_surface_asks_for_other_parameters() {
use SwapchainState::*;
let never = || -> Result<bool, VulkanException> { panic!("the parameters are not asked for") };
assert!(!Current.needs_recreation(false, never).unwrap());
assert!(Current.needs_recreation(true, never).unwrap());
assert!(OutOfDate.needs_recreation(false, never).unwrap());
assert!(Suboptimal.needs_recreation(true, never).unwrap());
assert!(!Suboptimal.needs_recreation(false, || Ok(false)).unwrap());
assert!(Suboptimal.needs_recreation(false, || Ok(true)).unwrap());
}
#[test]
fn a_rotated_surface_asks_for_the_same_parameters_again() {
let mut caps = capabilities();
caps.current_extent = vk::Extent2D { width: 2400, height: 1080 };
caps.current_transform = vk::SurfaceTransformFlagsKHR::ROTATE_90;
caps.supported_transforms = vk::SurfaceTransformFlagsKHR::IDENTITY | vk::SurfaceTransformFlagsKHR::ROTATE_90;
let created = SwapchainParameters::new(&caps, &[vk::PresentModeKHR::MAILBOX, vk::PresentModeKHR::FIFO], PixelSize::new(2400, 1080));
let again = SwapchainParameters::new(&caps, &[created.present_mode], PixelSize::new(2400, 1080));
assert_eq!(created, again);
caps.current_extent = vk::Extent2D { width: 1080, height: 2400 };
caps.current_transform = vk::SurfaceTransformFlagsKHR::IDENTITY;
assert_ne!(SwapchainParameters::new(&caps, &[created.present_mode], PixelSize::new(1080, 2400)), created);
}
#[test]
fn premultiplied_alpha_when_the_surface_composes_it() {
let mut caps = capabilities();
let alpha = |caps: &vk::SurfaceCapabilitiesKHR| {
SwapchainParameters::new(caps, FIFO, PixelSize::new(1, 1)).composite_alpha
};
assert_eq!(alpha(&caps), vk::CompositeAlphaFlagsKHR::OPAQUE);
caps.supported_composite_alpha = vk::CompositeAlphaFlagsKHR::OPAQUE | vk::CompositeAlphaFlagsKHR::PRE_MULTIPLIED;
assert_eq!(alpha(&caps), vk::CompositeAlphaFlagsKHR::PRE_MULTIPLIED);
caps.supported_composite_alpha = vk::CompositeAlphaFlagsKHR::INHERIT;
assert_eq!(alpha(&caps), vk::CompositeAlphaFlagsKHR::OPAQUE);
}
}