diff --git a/Cargo.toml b/Cargo.toml
index 7c0b5b9a2..3c8dabb5d 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -3,5 +3,6 @@ members = [
"examples",
"ash",
"ash-window",
+ "ash-swapchain",
"generator",
]
diff --git a/ash-swapchain/Cargo.toml b/ash-swapchain/Cargo.toml
new file mode 100644
index 000000000..4a37763ea
--- /dev/null
+++ b/ash-swapchain/Cargo.toml
@@ -0,0 +1,11 @@
+[package]
+name = "ash-swapchain"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+ash = { path = "../ash", version = "0.34" }
+
+[dev-dependencies]
+winit = "0.26"
+ash-window = { path = "../ash-window", version = "0.8" }
diff --git a/ash-swapchain/README.md b/ash-swapchain/README.md
new file mode 100644
index 000000000..b009dc920
--- /dev/null
+++ b/ash-swapchain/README.md
@@ -0,0 +1,33 @@
+ash-swapchain
+=
+
+Synchronization for swapchain resize and presentation
+-
+
+[](https://crates.io/crates/ash-swapchain)
+[](https://docs.rs/ash-swapchain)
+[](https://github.com/MaikKlein/ash/actions?workflow=CI)
+[](LICENSE-MIT)
+[](LICENSE-APACHE)
+
+## Usage
+
+A `Swapchain` manages (re)creating a `vk::SwapchainKHR` associated with a particular
+`vk::SurfaceKHR`, and tracks the availability of resources associated with a particular *frame in
+flight*. `Swapchain::acquire` yields an `AcquiredFrame` which specifies which swapchain image to
+render to, which frame's resources are available for reuse, how to synchronize submitted work with
+frame and swapchain image availability, and whether previously exposed swapchain images have been
+invalidated.
+
+## License
+
+Licensed under either of
+
+* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
+* MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
+
+at your option.
+
+## Contribution
+
+Unless you explicitly state otherwise, any Contribution intentionally submitted for inclusion in this crate by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.
diff --git a/ash-swapchain/examples/demo.rs b/ash-swapchain/examples/demo.rs
new file mode 100644
index 000000000..9f6c89b9b
--- /dev/null
+++ b/ash-swapchain/examples/demo.rs
@@ -0,0 +1,343 @@
+use std::time::Instant;
+
+use ash::{extensions::khr, vk};
+use ash_swapchain::Swapchain;
+use winit::{
+ event::{Event, WindowEvent},
+ event_loop::{ControlFlow, EventLoop},
+ window::{Window, WindowBuilder},
+};
+
+fn main() {
+ let event_loop = EventLoop::new();
+ let window = WindowBuilder::new().build(&event_loop).unwrap();
+
+ let mut app = App::new(&window);
+
+ event_loop.run(move |event, _, control_flow| {
+ *control_flow = ControlFlow::Poll;
+ match event {
+ Event::WindowEvent {
+ event: WindowEvent::CloseRequested,
+ ..
+ } => *control_flow = ControlFlow::Exit,
+ Event::WindowEvent {
+ event: WindowEvent::Resized(size),
+ ..
+ } => {
+ app.resize(vk::Extent2D {
+ width: size.width,
+ height: size.height,
+ });
+ }
+ Event::MainEventsCleared => {
+ app.draw();
+ }
+ _ => (),
+ }
+ });
+}
+
+pub struct App {
+ _entry: ash::Entry,
+ instance: ash::Instance,
+ surface: vk::SurfaceKHR,
+ epoch: Instant,
+
+ functions: Functions,
+ swapchain: Swapchain,
+ queue: vk::Queue,
+
+ command_pool: vk::CommandPool,
+ frames: Vec,
+}
+
+impl App {
+ pub fn new(window: &Window) -> Self {
+ unsafe {
+ let entry = ash::Entry::new();
+ let exts = ash_window::enumerate_required_extensions(&window).unwrap();
+ let ext_ptrs = exts
+ .iter()
+ .map(|x| x.as_ptr() as *const _)
+ .collect::>();
+ let instance = entry
+ .create_instance(
+ &vk::InstanceCreateInfo::builder()
+ .application_info(&vk::ApplicationInfo {
+ api_version: vk::make_api_version(0, 1, 0, 0),
+ ..Default::default()
+ })
+ .enabled_extension_names(&ext_ptrs),
+ None,
+ )
+ .unwrap();
+ let surface_fn = khr::Surface::new(&entry, &instance);
+ let surface = ash_window::create_surface(&entry, &instance, &window, None).unwrap();
+
+ let (physical_device, queue_family_index) = instance
+ .enumerate_physical_devices()
+ .unwrap()
+ .into_iter()
+ .find_map(|dev| {
+ let (family, _) = instance
+ .get_physical_device_queue_family_properties(dev)
+ .into_iter()
+ .enumerate()
+ .find(|(_index, info)| {
+ info.queue_flags.contains(vk::QueueFlags::GRAPHICS)
+ })?;
+ let family = family as u32;
+ let supported = surface_fn
+ .get_physical_device_surface_support(dev, family, surface)
+ .unwrap();
+ if !supported {
+ return None;
+ }
+ Some((dev, family))
+ })
+ .unwrap();
+
+ let device = instance
+ .create_device(
+ physical_device,
+ &vk::DeviceCreateInfo::builder()
+ .enabled_extension_names(&[khr::Swapchain::name().as_ptr() as _])
+ .queue_create_infos(&[vk::DeviceQueueCreateInfo::builder()
+ .queue_family_index(queue_family_index)
+ .queue_priorities(&[1.0])
+ .build()]),
+ None,
+ )
+ .unwrap();
+ let swapchain_fn = khr::Swapchain::new(&instance, &device);
+ let queue = device.get_device_queue(queue_family_index, queue_family_index);
+
+ let size = window.inner_size();
+ let mut options = ash_swapchain::Options::default();
+ options.usage(vk::ImageUsageFlags::TRANSFER_DST); // Typically this would be left as the default, COLOR_ATTACHMENT
+ let swapchain = Swapchain::new(
+ &ash_swapchain::Functions {
+ device: &device,
+ swapchain: &swapchain_fn,
+ surface: &surface_fn,
+ },
+ options,
+ surface,
+ physical_device,
+ vk::Extent2D {
+ width: size.width,
+ height: size.height,
+ },
+ );
+
+ let command_pool = device
+ .create_command_pool(
+ &vk::CommandPoolCreateInfo::builder()
+ .flags(
+ vk::CommandPoolCreateFlags::TRANSIENT
+ | vk::CommandPoolCreateFlags::RESET_COMMAND_BUFFER,
+ )
+ .queue_family_index(queue_family_index),
+ None,
+ )
+ .unwrap();
+ let cmds = device
+ .allocate_command_buffers(
+ &vk::CommandBufferAllocateInfo::builder()
+ .command_pool(command_pool)
+ .level(vk::CommandBufferLevel::PRIMARY)
+ .command_buffer_count(swapchain.frames_in_flight() as u32),
+ )
+ .unwrap();
+ let frames = cmds
+ .into_iter()
+ .map(|cmd| Frame {
+ cmd,
+ complete: device
+ .create_semaphore(&vk::SemaphoreCreateInfo::default(), None)
+ .unwrap(),
+ })
+ .collect();
+
+ Self {
+ _entry: entry,
+ instance,
+ surface,
+ epoch: Instant::now(),
+
+ functions: Functions {
+ device,
+ swapchain: swapchain_fn,
+ surface: surface_fn,
+ },
+ swapchain,
+ queue,
+
+ command_pool,
+ frames,
+ }
+ }
+ }
+
+ fn resize(&mut self, size: vk::Extent2D) {
+ self.swapchain.update(size);
+ }
+
+ fn draw(&mut self) {
+ let device = &self.functions.device;
+ unsafe {
+ let acq = self
+ .swapchain
+ .acquire(&self.functions.ash_swapchain(), !0)
+ .unwrap();
+ let cmd = self.frames[acq.frame_index].cmd;
+ let swapchain_image = self.swapchain.images()[acq.image_index];
+ device
+ .begin_command_buffer(
+ cmd,
+ &vk::CommandBufferBeginInfo::builder()
+ .flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT),
+ )
+ .unwrap();
+
+ //
+ // Record commands to render to swapchain_image
+ //
+
+ // Typically this barrier would be implemented with a subpass dependency from EXTERNAL,
+ // with both pipeline stages set to COLOR_ATTACHMENT_OUTPUT so that it doesn't block
+ // work that doesn't write to the swapchain image. The source stage must overlap with
+ // the wait_dst_stage_mask passed to `queue_submit` below to ensure that the image
+ // transition doesn't happen until after the acquire semaphore is signaled.
+ device.cmd_pipeline_barrier(
+ cmd,
+ vk::PipelineStageFlags::TRANSFER,
+ vk::PipelineStageFlags::TRANSFER,
+ vk::DependencyFlags::default(),
+ &[],
+ &[],
+ &[vk::ImageMemoryBarrier::builder()
+ .dst_access_mask(vk::AccessFlags::TRANSFER_WRITE)
+ .src_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
+ .dst_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
+ .old_layout(vk::ImageLayout::UNDEFINED)
+ .new_layout(vk::ImageLayout::TRANSFER_DST_OPTIMAL)
+ .image(swapchain_image)
+ .subresource_range(vk::ImageSubresourceRange {
+ aspect_mask: vk::ImageAspectFlags::COLOR,
+ base_mip_level: 0,
+ level_count: 1,
+ base_array_layer: 0,
+ layer_count: 1,
+ })
+ .build()],
+ );
+ let t = (self.epoch.elapsed().as_secs_f32().sin() + 1.0) * 0.5;
+ device.cmd_clear_color_image(
+ cmd,
+ swapchain_image,
+ vk::ImageLayout::TRANSFER_DST_OPTIMAL,
+ &vk::ClearColorValue {
+ float32: [0.0, t, 0.0, 1.0],
+ },
+ &[vk::ImageSubresourceRange {
+ aspect_mask: vk::ImageAspectFlags::COLOR,
+ base_mip_level: 0,
+ level_count: 1,
+ base_array_layer: 0,
+ layer_count: 1,
+ }],
+ );
+ // Typically this barrier would be implemented with the implicit subpass dependency to
+ // EXTERNAL
+ device.cmd_pipeline_barrier(
+ cmd,
+ vk::PipelineStageFlags::TRANSFER,
+ vk::PipelineStageFlags::BOTTOM_OF_PIPE,
+ vk::DependencyFlags::default(),
+ &[],
+ &[],
+ &[vk::ImageMemoryBarrier::builder()
+ .src_access_mask(vk::AccessFlags::TRANSFER_WRITE)
+ .src_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
+ .dst_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
+ .old_layout(vk::ImageLayout::TRANSFER_DST_OPTIMAL)
+ .new_layout(vk::ImageLayout::PRESENT_SRC_KHR)
+ .image(swapchain_image)
+ .subresource_range(vk::ImageSubresourceRange {
+ aspect_mask: vk::ImageAspectFlags::COLOR,
+ base_mip_level: 0,
+ level_count: 1,
+ base_array_layer: 0,
+ layer_count: 1,
+ })
+ .build()],
+ );
+
+ //
+ // Submit commands and queue present
+ //
+
+ device.end_command_buffer(cmd).unwrap();
+ device
+ .queue_submit(
+ self.queue,
+ &[vk::SubmitInfo::builder()
+ .wait_semaphores(&[acq.ready])
+ .wait_dst_stage_mask(&[vk::PipelineStageFlags::TRANSFER])
+ .signal_semaphores(&[self.frames[acq.frame_index].complete])
+ .command_buffers(&[cmd])
+ .build()],
+ acq.complete,
+ )
+ .unwrap();
+ self.swapchain
+ .queue_present(
+ &self.functions.ash_swapchain(),
+ self.queue,
+ self.frames[acq.frame_index].complete,
+ acq.image_index,
+ )
+ .unwrap();
+ }
+ }
+}
+
+impl Drop for App {
+ fn drop(&mut self) {
+ unsafe {
+ let device = &self.functions.device;
+ let _ = device.device_wait_idle();
+ for frame in &self.frames {
+ device.destroy_semaphore(frame.complete, None);
+ }
+ device.destroy_command_pool(self.command_pool, None);
+ self.swapchain.destroy(&self.functions.ash_swapchain());
+ self.functions.surface.destroy_surface(self.surface, None);
+ device.destroy_device(None);
+ self.instance.destroy_instance(None);
+ }
+ }
+}
+
+struct Functions {
+ device: ash::Device,
+ surface: khr::Surface,
+ swapchain: khr::Swapchain,
+}
+
+impl Functions {
+ fn ash_swapchain(&self) -> ash_swapchain::Functions<'_> {
+ ash_swapchain::Functions {
+ device: &self.device,
+ swapchain: &self.swapchain,
+ surface: &self.surface,
+ }
+ }
+}
+
+struct Frame {
+ cmd: vk::CommandBuffer,
+ complete: vk::Semaphore,
+}
diff --git a/ash-swapchain/src/lib.rs b/ash-swapchain/src/lib.rs
new file mode 100644
index 000000000..b0c8f0be0
--- /dev/null
+++ b/ash-swapchain/src/lib.rs
@@ -0,0 +1,415 @@
+use std::{collections::VecDeque, mem};
+
+use ash::{extensions::khr, prelude::VkResult, vk, Device};
+
+/// Manages synchronizing and rebuilding a Vulkan swapchain
+pub struct Swapchain {
+ options: Options,
+
+ frames: Vec,
+ frame_index: usize,
+
+ surface: vk::SurfaceKHR,
+ physical_device: vk::PhysicalDevice,
+ handle: vk::SwapchainKHR,
+ generation: u64,
+ images: Vec,
+ extent: vk::Extent2D,
+ format: vk::SurfaceFormatKHR,
+ needs_rebuild: bool,
+
+ old_swapchains: VecDeque<(vk::SwapchainKHR, u64)>,
+}
+
+impl Swapchain {
+ /// Construct a new [`Swapchain`] for rendering at most `frames_in_flight` frames
+ /// concurrently. `extent` should be the current dimensions of `surface`.
+ pub fn new(
+ fp: &Functions<'_>,
+ options: Options,
+ surface: vk::SurfaceKHR,
+ physical_device: vk::PhysicalDevice,
+ extent: vk::Extent2D,
+ ) -> Self {
+ Self {
+ frames: (0..options.frames_in_flight)
+ .map(|_| unsafe {
+ Frame {
+ complete: fp
+ .device
+ .create_fence(
+ &vk::FenceCreateInfo::builder()
+ .flags(vk::FenceCreateFlags::SIGNALED),
+ None,
+ )
+ .unwrap(),
+ acquire: fp
+ .device
+ .create_semaphore(&vk::SemaphoreCreateInfo::default(), None)
+ .unwrap(),
+ generation: 0,
+ }
+ })
+ .collect(),
+ frame_index: 0,
+
+ surface,
+ physical_device,
+ handle: vk::SwapchainKHR::null(),
+ generation: 0,
+ images: Vec::new(),
+ extent,
+ format: vk::SurfaceFormatKHR::default(),
+ needs_rebuild: true,
+
+ old_swapchains: VecDeque::new(),
+
+ options,
+ }
+ }
+
+ /// Destroy all swapchain resources. Must not be called while any frames are still in flight on
+ /// the GPU.
+ ///
+ /// # Safety
+ ///
+ /// Access to images obtained from [`images`](Self::images) must be externally synchronized, and
+ /// the contents of `fp` must be associated with the same `vk::Device` as that passed to
+ /// [`new`](Self::new).
+ pub unsafe fn destroy(&mut self, fp: &Functions<'_>) {
+ for frame in &self.frames {
+ fp.device.destroy_fence(frame.complete, None);
+ fp.device.destroy_semaphore(frame.acquire, None);
+ }
+ if self.handle != vk::SwapchainKHR::null() {
+ fp.swapchain.destroy_swapchain(self.handle, None);
+ }
+ for &(swapchain, _) in &self.old_swapchains {
+ fp.swapchain.destroy_swapchain(swapchain, None);
+ }
+ }
+
+ /// Force the swapchain to be rebuilt on the next [`acquire`](Self::acquire) call, passing in
+ /// the surface's current size.
+ pub fn update(&mut self, extent: vk::Extent2D) {
+ self.extent = extent;
+ self.needs_rebuild = true;
+ }
+
+ /// Maximum number of frames that may be concurrently rendered
+ pub fn frames_in_flight(&self) -> usize {
+ self.frames.len()
+ }
+
+ /// Latest set of swapchain images, keyed by [`AcquiredFrame::image_index`]
+ pub fn images(&self) -> &[vk::Image] {
+ &self.images
+ }
+
+ /// Format of images in [`images`](Self::images), and the color space that will be used to
+ /// present them
+ pub fn format(&self) -> vk::SurfaceFormatKHR {
+ self.format
+ }
+
+ /// Dimensions of images in [`images`](Self::images)
+ pub fn extent(&self) -> vk::Extent2D {
+ self.extent
+ }
+
+ /// Acquire resources to render a frame
+ ///
+ /// Returns [`vk::Result::ERROR_OUT_OF_DATE_KHR`] if and only if the configured format or
+ /// present modes could not be satisfied.
+ ///
+ /// # Safety
+ ///
+ /// The contents of `fp` must be associated with the same `vk::Device` as that passed to
+ /// [`new`](Self::new).
+ pub unsafe fn acquire(
+ &mut self,
+ fp: &Functions<'_>,
+ timeout_ns: u64,
+ ) -> VkResult {
+ let frame_index = self.frame_index;
+ let next_frame_index = (self.frame_index + 1) % self.frames.len();
+ let frame = &self.frames[frame_index];
+ let acquire = frame.acquire;
+ fp.device
+ .wait_for_fences(&[frame.complete], true, timeout_ns)?;
+
+ // Destroy swapchains that are guaranteed not to be in use now that this frame has finished
+ while let Some(&(swapchain, generation)) = self.old_swapchains.front() {
+ if self.frames[next_frame_index].generation == generation {
+ break;
+ }
+ fp.swapchain.destroy_swapchain(swapchain, None);
+ self.old_swapchains.pop_front();
+ }
+
+ loop {
+ if !self.needs_rebuild {
+ match fp
+ .swapchain
+ .acquire_next_image(self.handle, !0, acquire, vk::Fence::null())
+ {
+ Ok((index, suboptimal)) => {
+ self.needs_rebuild = suboptimal;
+ let invalidate_images =
+ self.frames[frame_index].generation != self.generation;
+ self.frames[frame_index].generation = self.generation;
+ self.frame_index = next_frame_index;
+ fp.device
+ .reset_fences(&[self.frames[frame_index].complete])
+ .unwrap();
+ return Ok(AcquiredFrame {
+ image_index: index as usize,
+ frame_index,
+ ready: acquire,
+ complete: self.frames[frame_index].complete,
+ invalidate_images,
+ });
+ }
+ Err(vk::Result::ERROR_OUT_OF_DATE_KHR) => {}
+ Err(e) => return Err(e),
+ }
+ };
+ self.needs_rebuild = true;
+
+ // Rebuild swapchain
+ let surface_capabilities = fp
+ .surface
+ .get_physical_device_surface_capabilities(self.physical_device, self.surface)?;
+ self.extent = match surface_capabilities.current_extent.width {
+ // If Vulkan doesn't know, the windowing system probably does. Known to apply at
+ // least to Wayland.
+ std::u32::MAX => vk::Extent2D {
+ width: self.extent.width,
+ height: self.extent.height,
+ },
+ _ => surface_capabilities.current_extent,
+ };
+ let pre_transform = if surface_capabilities
+ .supported_transforms
+ .contains(vk::SurfaceTransformFlagsKHR::IDENTITY)
+ {
+ vk::SurfaceTransformFlagsKHR::IDENTITY
+ } else {
+ surface_capabilities.current_transform
+ };
+ let present_mode = fp
+ .surface
+ .get_physical_device_surface_present_modes(self.physical_device, self.surface)?
+ .iter()
+ .filter_map(|&mode| {
+ Some((
+ mode,
+ self.options
+ .present_mode_preference
+ .iter()
+ .position(|&pref| pref == mode)?,
+ ))
+ })
+ .min_by_key(|&(_, priority)| priority)
+ .map(|(mode, _)| mode)
+ .ok_or(vk::Result::ERROR_OUT_OF_DATE_KHR)?;
+
+ let desired_image_count =
+ (surface_capabilities.min_image_count + 1).max(self.frames.len() as u32);
+ let image_count = if surface_capabilities.max_image_count > 0 {
+ surface_capabilities
+ .max_image_count
+ .min(desired_image_count)
+ } else {
+ desired_image_count
+ };
+
+ self.format = fp
+ .surface
+ .get_physical_device_surface_formats(self.physical_device, self.surface)?
+ .iter()
+ .filter_map(|&format| {
+ Some((
+ format,
+ self.options
+ .format_preference
+ .iter()
+ .position(|&pref| pref == format)?,
+ ))
+ })
+ .min_by_key(|&(_, priority)| priority)
+ .map(|(mode, _)| mode)
+ .ok_or(vk::Result::ERROR_OUT_OF_DATE_KHR)?;
+
+ if self.handle != vk::SwapchainKHR::null() {
+ self.old_swapchains
+ .push_back((self.handle, self.generation));
+ }
+ let handle = fp.swapchain.create_swapchain(
+ &vk::SwapchainCreateInfoKHR::builder()
+ .surface(self.surface)
+ .min_image_count(image_count)
+ .image_color_space(self.format.color_space)
+ .image_format(self.format.format)
+ .image_extent(self.extent)
+ .image_usage(self.options.usage)
+ .image_sharing_mode(self.options.sharing_mode)
+ .pre_transform(pre_transform)
+ .composite_alpha(self.options.composite_alpha)
+ .present_mode(present_mode)
+ .clipped(true)
+ .image_array_layers(1)
+ .old_swapchain(mem::replace(&mut self.handle, vk::SwapchainKHR::null())),
+ None,
+ )?;
+ self.generation = self.generation.wrapping_add(1);
+ self.handle = handle;
+ self.images = fp.swapchain.get_swapchain_images(handle)?;
+ self.needs_rebuild = false;
+ }
+ }
+
+ /// Queue presentation of a previously acquired image
+ ///
+ /// # Safety
+ ///
+ /// In addition to the usual requirements of [`khr::Swapchain::queue_present`]:
+ ///
+ /// - The contents of `fp` must be associated with the same `vk::Device` as that passed to
+ /// [`new`](Self::new).
+ /// - `image_index` must have been obtained from an [`AcquiredFrame::image_index`] from a
+ /// previous [`acquire`](Self::acquire) call which has not yet been passed to queue_present
+ /// - A command buffer that will signal `render_complete` after finishing access to the
+ /// `image_index` element of [`images`](Self::images) must have been submitted
+ pub unsafe fn queue_present(
+ &mut self,
+ fp: &Functions<'_>,
+ queue: vk::Queue,
+ render_complete: vk::Semaphore,
+ image_index: usize,
+ ) -> VkResult<()> {
+ match fp.swapchain.queue_present(
+ queue,
+ &vk::PresentInfoKHR::builder()
+ .wait_semaphores(&[render_complete])
+ .swapchains(&[self.handle])
+ .image_indices(&[image_index as u32]),
+ ) {
+ Ok(false) => Ok(()),
+ Ok(true) | Err(vk::Result::ERROR_OUT_OF_DATE_KHR) => {
+ self.needs_rebuild = true;
+ Ok(())
+ }
+ Err(e) => Err(e),
+ }
+ }
+}
+
+/// Functions required by [`Swapchain`] methods
+pub struct Functions<'a> {
+ pub device: &'a Device,
+ pub swapchain: &'a khr::Swapchain,
+ pub surface: &'a khr::Surface,
+}
+
+/// [`Swapchain`] configuration
+#[derive(Debug, Clone)]
+pub struct Options {
+ frames_in_flight: usize,
+ format_preference: Vec,
+ present_mode_preference: Vec,
+ usage: vk::ImageUsageFlags,
+ sharing_mode: vk::SharingMode,
+ composite_alpha: vk::CompositeAlphaFlagsKHR,
+}
+
+impl Options {
+ pub fn new() -> Self {
+ Self::default()
+ }
+
+ /// Number of frames that may be concurrently worked on, including recording on the CPU. Defaults to 2.
+ pub fn frames_in_flight(&mut self, frames: usize) -> &mut Self {
+ self.frames_in_flight = frames;
+ self
+ }
+
+ /// Preference-ordered list of image formats and color spaces. Defaults to 8-bit sRGB.
+ pub fn format_preference(&mut self, formats: &[vk::SurfaceFormatKHR]) -> &mut Self {
+ self.format_preference = formats.into();
+ self
+ }
+
+ /// Preference-ordered list of presentation modes. Defaults to [`vk::PresentModeKHR::FIFO`].
+ pub fn present_mode_preference(&mut self, modes: &[vk::PresentModeKHR]) -> &mut Self {
+ self.present_mode_preference = modes.into();
+ self
+ }
+
+ /// Required swapchain image usage flags. Defaults to [`vk::ImageUsageFlags::COLOR_ATTACHMENT`].
+ pub fn usage(&mut self, usage: vk::ImageUsageFlags) -> &mut Self {
+ self.usage = usage;
+ self
+ }
+
+ /// Requires swapchain image sharing mode. Defaults to [`vk::SharingMode::EXCLUSIVE`].
+ pub fn sharing_mode(&mut self, mode: vk::SharingMode) -> &mut Self {
+ self.sharing_mode = mode;
+ self
+ }
+
+ /// Requires swapchain image composite alpha. Defaults to [`vk::CompositeAlphaFlagsKHR::OPAQUE`].
+ pub fn composite_alpha(&mut self, value: vk::CompositeAlphaFlagsKHR) -> &mut Self {
+ self.composite_alpha = value;
+ self
+ }
+}
+
+impl Default for Options {
+ fn default() -> Self {
+ Self {
+ frames_in_flight: 2,
+ format_preference: vec![
+ vk::SurfaceFormatKHR {
+ format: vk::Format::B8G8R8A8_SRGB,
+ color_space: vk::ColorSpaceKHR::SRGB_NONLINEAR,
+ },
+ vk::SurfaceFormatKHR {
+ format: vk::Format::R8G8B8A8_SRGB,
+ color_space: vk::ColorSpaceKHR::SRGB_NONLINEAR,
+ },
+ ],
+ present_mode_preference: vec![vk::PresentModeKHR::FIFO],
+ usage: vk::ImageUsageFlags::COLOR_ATTACHMENT,
+ sharing_mode: vk::SharingMode::EXCLUSIVE,
+ composite_alpha: vk::CompositeAlphaFlagsKHR::OPAQUE,
+ }
+ }
+}
+
+struct Frame {
+ complete: vk::Fence,
+ acquire: vk::Semaphore,
+ generation: u64,
+}
+
+/// Information necessary to render a frame, from [`Swapchain::acquire`]
+#[derive(Debug, Copy, Clone)]
+pub struct AcquiredFrame {
+ /// Index of the image to write to in [`Swapchain::images`]
+ pub image_index: usize,
+ /// Index of the frame in flight, for use tracking your own per-frame resources, which may be
+ /// accessed immediately after [`Swapchain::acquire`] returns
+ pub frame_index: usize,
+ /// Must be waited on before accessing the image associated with `image_index`
+ pub ready: vk::Semaphore,
+ /// Must be signaled when access to the image associated with `image_index` and any per-frame
+ /// resources associated with `frame_index` is complete
+ pub complete: vk::Fence,
+ /// Set whenever [`Swapchain::images`] has, and [`Swapchain::extent`] and [`Swapchain::format`]
+ /// may have, changed since the last [`Swapchain::acquire`] call. Use this to invalidate derived
+ /// resources like [`vk::ImageView`]s and [`vk::Framebuffer`]s, taking care not to destroy them
+ /// until at least [`Options::frames_in_flight`] new frames have been acquired, including this
+ /// one.
+ pub invalidate_images: bool,
+}