From 3398f8f2b7324a658c6424b65d6a882624e43bdd Mon Sep 17 00:00:00 2001 From: Benjamin Saunders Date: Sun, 5 Dec 2021 14:08:59 -0800 Subject: [PATCH 1/2] Introduce ash-swapchain helper crate --- Cargo.toml | 1 + ash-swapchain/Cargo.toml | 11 + ash-swapchain/README.md | 33 +++ ash-swapchain/examples/demo.rs | 321 ++++++++++++++++++++++++++ ash-swapchain/src/lib.rs | 399 +++++++++++++++++++++++++++++++++ 5 files changed, 765 insertions(+) create mode 100644 ash-swapchain/Cargo.toml create mode 100644 ash-swapchain/README.md create mode 100644 ash-swapchain/examples/demo.rs create mode 100644 ash-swapchain/src/lib.rs 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 +- + +[![Crates.io Version](https://img.shields.io/crates/v/ash-window.svg)](https://crates.io/crates/ash-swapchain) +[![Documentation](https://docs.rs/ash-window/badge.svg)](https://docs.rs/ash-swapchain) +[![Build Status](https://github.com/MaikKlein/ash/workflows/CI/badge.svg)](https://github.com/MaikKlein/ash/actions?workflow=CI) +[![LICENSE](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE-MIT) +[![LICENSE](https://img.shields.io/badge/license-apache-blue.svg)](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..ca3c67cec --- /dev/null +++ b/ash-swapchain/examples/demo.rs @@ -0,0 +1,321 @@ +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_fn: khr::Surface, + surface: vk::SurfaceKHR, + epoch: Instant, + + device: ash::Device, + swapchain_fn: khr::Swapchain, + 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( + options, + surface, + physical_device, + &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_fn, + surface, + epoch: Instant::now(), + + device, + swapchain_fn, + swapchain, + queue, + + command_pool, + frames, + } + } + } + + fn resize(&mut self, size: vk::Extent2D) { + self.swapchain.update(size); + } + + fn draw(&mut self) { + unsafe { + let acq = self + .swapchain + .acquire(&self.device, &self.surface_fn, &self.swapchain_fn, !0) + .unwrap(); + let cmd = self.frames[acq.frame_index].cmd; + let swapchain_image = self.swapchain.images()[acq.image_index]; + self.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. + self.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; + self.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 + self.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 + // + + self.device.end_command_buffer(cmd).unwrap(); + self.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.swapchain_fn, + self.queue, + self.frames[acq.frame_index].complete, + acq.image_index, + ) + .unwrap(); + } + } +} + +impl Drop for App { + fn drop(&mut self) { + unsafe { + let _ = self.device.device_wait_idle(); + for frame in &self.frames { + self.device.destroy_semaphore(frame.complete, None); + } + self.device.destroy_command_pool(self.command_pool, None); + self.swapchain.destroy(&self.device, &self.swapchain_fn); + self.surface_fn.destroy_surface(self.surface, None); + self.device.destroy_device(None); + self.instance.destroy_instance(None); + } + } +} + +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..6d5f9e7a5 --- /dev/null +++ b/ash-swapchain/src/lib.rs @@ -0,0 +1,399 @@ +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( + options: Options, + surface: vk::SurfaceKHR, + physical_device: vk::PhysicalDevice, + device: &Device, + extent: vk::Extent2D, + ) -> Self { + Self { + frames: (0..options.frames_in_flight) + .map(|_| unsafe { + Frame { + complete: device + .create_fence( + &vk::FenceCreateInfo::builder() + .flags(vk::FenceCreateFlags::SIGNALED), + None, + ) + .unwrap(), + acquire: 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. + pub unsafe fn destroy(&mut self, device: &Device, swapchain_fn: &khr::Swapchain) { + for frame in &self.frames { + device.destroy_fence(frame.complete, None); + device.destroy_semaphore(frame.acquire, None); + } + if self.handle != vk::SwapchainKHR::null() { + swapchain_fn.destroy_swapchain(self.handle, None); + } + for &(swapchain, _) in &self.old_swapchains { + swapchain_fn.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 + /// + /// `surface_fn` and `swapchain_fn` must have been acquired from `device`, which must match the + /// `device` passed to [`new`](Self::new) + pub unsafe fn acquire( + &mut self, + device: &Device, + surface_fn: &khr::Surface, + swapchain_fn: &khr::Swapchain, + 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; + 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; + } + swapchain_fn.destroy_swapchain(swapchain, None); + self.old_swapchains.pop_front(); + } + + loop { + if !self.needs_rebuild { + match swapchain_fn.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; + 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 = surface_fn + .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 = surface_fn + .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 = surface_fn + .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 = swapchain_fn.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 = swapchain_fn.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`]: + /// + /// - `swapchain_fn` must be associated with the device `queue` and `render_complete` were + /// obtained from, which must match the device 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, + swapchain_fn: &khr::Swapchain, + queue: vk::Queue, + render_complete: vk::Semaphore, + image_index: usize, + ) -> VkResult<()> { + match swapchain_fn.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), + } + } +} + +/// [`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, +} From 971d4cfd646fb2b48122979f5f98c5a5eadf7825 Mon Sep 17 00:00:00 2001 From: Benjamin Saunders Date: Fri, 24 Dec 2021 11:26:35 -0800 Subject: [PATCH 2/2] Bundle up required function pointer tables for callsite concision --- ash-swapchain/examples/demo.rs | 64 +++++++++++++++++++++---------- ash-swapchain/src/lib.rs | 70 +++++++++++++++++++++------------- 2 files changed, 86 insertions(+), 48 deletions(-) diff --git a/ash-swapchain/examples/demo.rs b/ash-swapchain/examples/demo.rs index ca3c67cec..9f6c89b9b 100644 --- a/ash-swapchain/examples/demo.rs +++ b/ash-swapchain/examples/demo.rs @@ -41,12 +41,10 @@ fn main() { pub struct App { _entry: ash::Entry, instance: ash::Instance, - surface_fn: khr::Surface, surface: vk::SurfaceKHR, epoch: Instant, - device: ash::Device, - swapchain_fn: khr::Swapchain, + functions: Functions, swapchain: Swapchain, queue: vk::Queue, @@ -119,10 +117,14 @@ impl App { 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, - &device, vk::Extent2D { width: size.width, height: size.height, @@ -161,12 +163,14 @@ impl App { Self { _entry: entry, instance, - surface_fn, surface, epoch: Instant::now(), - device, - swapchain_fn, + functions: Functions { + device, + swapchain: swapchain_fn, + surface: surface_fn, + }, swapchain, queue, @@ -181,14 +185,15 @@ impl App { } fn draw(&mut self) { + let device = &self.functions.device; unsafe { let acq = self .swapchain - .acquire(&self.device, &self.surface_fn, &self.swapchain_fn, !0) + .acquire(&self.functions.ash_swapchain(), !0) .unwrap(); let cmd = self.frames[acq.frame_index].cmd; let swapchain_image = self.swapchain.images()[acq.image_index]; - self.device + device .begin_command_buffer( cmd, &vk::CommandBufferBeginInfo::builder() @@ -205,7 +210,7 @@ impl App { // 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. - self.device.cmd_pipeline_barrier( + device.cmd_pipeline_barrier( cmd, vk::PipelineStageFlags::TRANSFER, vk::PipelineStageFlags::TRANSFER, @@ -229,7 +234,7 @@ impl App { .build()], ); let t = (self.epoch.elapsed().as_secs_f32().sin() + 1.0) * 0.5; - self.device.cmd_clear_color_image( + device.cmd_clear_color_image( cmd, swapchain_image, vk::ImageLayout::TRANSFER_DST_OPTIMAL, @@ -246,7 +251,7 @@ impl App { ); // Typically this barrier would be implemented with the implicit subpass dependency to // EXTERNAL - self.device.cmd_pipeline_barrier( + device.cmd_pipeline_barrier( cmd, vk::PipelineStageFlags::TRANSFER, vk::PipelineStageFlags::BOTTOM_OF_PIPE, @@ -274,8 +279,8 @@ impl App { // Submit commands and queue present // - self.device.end_command_buffer(cmd).unwrap(); - self.device + device.end_command_buffer(cmd).unwrap(); + device .queue_submit( self.queue, &[vk::SubmitInfo::builder() @@ -289,7 +294,7 @@ impl App { .unwrap(); self.swapchain .queue_present( - &self.swapchain_fn, + &self.functions.ash_swapchain(), self.queue, self.frames[acq.frame_index].complete, acq.image_index, @@ -302,19 +307,36 @@ impl App { impl Drop for App { fn drop(&mut self) { unsafe { - let _ = self.device.device_wait_idle(); + let device = &self.functions.device; + let _ = device.device_wait_idle(); for frame in &self.frames { - self.device.destroy_semaphore(frame.complete, None); + device.destroy_semaphore(frame.complete, None); } - self.device.destroy_command_pool(self.command_pool, None); - self.swapchain.destroy(&self.device, &self.swapchain_fn); - self.surface_fn.destroy_surface(self.surface, None); - self.device.destroy_device(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 index 6d5f9e7a5..b0c8f0be0 100644 --- a/ash-swapchain/src/lib.rs +++ b/ash-swapchain/src/lib.rs @@ -25,24 +25,26 @@ 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, - device: &Device, extent: vk::Extent2D, ) -> Self { Self { frames: (0..options.frames_in_flight) .map(|_| unsafe { Frame { - complete: device + complete: fp + .device .create_fence( &vk::FenceCreateInfo::builder() .flags(vk::FenceCreateFlags::SIGNALED), None, ) .unwrap(), - acquire: device + acquire: fp + .device .create_semaphore(&vk::SemaphoreCreateInfo::default(), None) .unwrap(), generation: 0, @@ -71,17 +73,19 @@ impl Swapchain { /// /// # Safety /// - /// Access to images obtained from [`images`](Self::images) must be externally synchronized. - pub unsafe fn destroy(&mut self, device: &Device, swapchain_fn: &khr::Swapchain) { + /// 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 { - device.destroy_fence(frame.complete, None); - device.destroy_semaphore(frame.acquire, None); + fp.device.destroy_fence(frame.complete, None); + fp.device.destroy_semaphore(frame.acquire, None); } if self.handle != vk::SwapchainKHR::null() { - swapchain_fn.destroy_swapchain(self.handle, None); + fp.swapchain.destroy_swapchain(self.handle, None); } for &(swapchain, _) in &self.old_swapchains { - swapchain_fn.destroy_swapchain(swapchain, None); + fp.swapchain.destroy_swapchain(swapchain, None); } } @@ -120,40 +124,42 @@ impl Swapchain { /// /// # Safety /// - /// `surface_fn` and `swapchain_fn` must have been acquired from `device`, which must match the - /// `device` passed to [`new`](Self::new) + /// 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, - device: &Device, - surface_fn: &khr::Surface, - swapchain_fn: &khr::Swapchain, + 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; - device.wait_for_fences(&[frame.complete], true, timeout_ns)?; + 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; } - swapchain_fn.destroy_swapchain(swapchain, None); + fp.swapchain.destroy_swapchain(swapchain, None); self.old_swapchains.pop_front(); } loop { if !self.needs_rebuild { - match swapchain_fn.acquire_next_image(self.handle, !0, acquire, vk::Fence::null()) { + 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; - device + fp.device .reset_fences(&[self.frames[frame_index].complete]) .unwrap(); return Ok(AcquiredFrame { @@ -171,7 +177,8 @@ impl Swapchain { self.needs_rebuild = true; // Rebuild swapchain - let surface_capabilities = surface_fn + 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 @@ -190,7 +197,8 @@ impl Swapchain { } else { surface_capabilities.current_transform }; - let present_mode = surface_fn + let present_mode = fp + .surface .get_physical_device_surface_present_modes(self.physical_device, self.surface)? .iter() .filter_map(|&mode| { @@ -216,7 +224,8 @@ impl Swapchain { desired_image_count }; - self.format = surface_fn + self.format = fp + .surface .get_physical_device_surface_formats(self.physical_device, self.surface)? .iter() .filter_map(|&format| { @@ -236,7 +245,7 @@ impl Swapchain { self.old_swapchains .push_back((self.handle, self.generation)); } - let handle = swapchain_fn.create_swapchain( + let handle = fp.swapchain.create_swapchain( &vk::SwapchainCreateInfoKHR::builder() .surface(self.surface) .min_image_count(image_count) @@ -255,7 +264,7 @@ impl Swapchain { )?; self.generation = self.generation.wrapping_add(1); self.handle = handle; - self.images = swapchain_fn.get_swapchain_images(handle)?; + self.images = fp.swapchain.get_swapchain_images(handle)?; self.needs_rebuild = false; } } @@ -266,20 +275,20 @@ impl Swapchain { /// /// In addition to the usual requirements of [`khr::Swapchain::queue_present`]: /// - /// - `swapchain_fn` must be associated with the device `queue` and `render_complete` were - /// obtained from, which must match the device passed to [`new`](Self::new) + /// - 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, - swapchain_fn: &khr::Swapchain, + fp: &Functions<'_>, queue: vk::Queue, render_complete: vk::Semaphore, image_index: usize, ) -> VkResult<()> { - match swapchain_fn.queue_present( + match fp.swapchain.queue_present( queue, &vk::PresentInfoKHR::builder() .wait_semaphores(&[render_complete]) @@ -296,6 +305,13 @@ impl Swapchain { } } +/// 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 {