From eceaeaf39b7cd0159cc095006eade5c66838c985 Mon Sep 17 00:00:00 2001 From: Connor Fitzgerald Date: Tue, 14 Oct 2025 19:42:59 -0400 Subject: [PATCH] Factor out swapchain to abstracted interface --- wgpu-hal/src/vulkan/adapter.rs | 117 +-- wgpu-hal/src/vulkan/device.rs | 126 +--- wgpu-hal/src/vulkan/instance.rs | 210 +----- wgpu-hal/src/vulkan/mod.rs | 473 ++----------- wgpu-hal/src/vulkan/semaphore_list.rs | 140 +++- wgpu-hal/src/vulkan/swapchain/mod.rs | 107 +++ wgpu-hal/src/vulkan/swapchain/native.rs | 904 ++++++++++++++++++++++++ 7 files changed, 1219 insertions(+), 858 deletions(-) create mode 100644 wgpu-hal/src/vulkan/swapchain/mod.rs create mode 100644 wgpu-hal/src/vulkan/swapchain/native.rs diff --git a/wgpu-hal/src/vulkan/adapter.rs b/wgpu-hal/src/vulkan/adapter.rs index d55e8c54534..a01b1f7fe50 100644 --- a/wgpu-hal/src/vulkan/adapter.rs +++ b/wgpu-hal/src/vulkan/adapter.rs @@ -4,7 +4,9 @@ use core::{ffi::CStr, marker::PhantomData}; use ash::{ext, google, khr, vk}; use parking_lot::Mutex; -use super::conv; +use crate::vulkan::semaphore_list::SemaphoreList; + +use super::semaphore_list::SemaphoreListMode; fn depth_stencil_required_flags() -> vk::FormatFeatureFlags { vk::FormatFeatureFlags::SAMPLED_IMAGE | vk::FormatFeatureFlags::DEPTH_STENCIL_ATTACHMENT @@ -1990,8 +1992,6 @@ impl super::Adapter { } }); - let swapchain_fn = khr::swapchain::Device::new(&self.instance.raw, &raw_device); - // Note that VK_EXT_debug_utils is an instance extension (enabled at the instance // level) but contains a few functions that can be loaded directly on the Device for a // dispatch-table-less pointer. @@ -2271,11 +2271,10 @@ impl super::Adapter { let queue = super::Queue { raw: raw_queue, - swapchain_fn, device: Arc::clone(&shared), family_index, relay_semaphores: Mutex::new(relay_semaphores), - signal_semaphores: Default::default(), + signal_semaphores: Mutex::new(SemaphoreList::new(SemaphoreListMode::Signal)), }; let mem_allocator = { @@ -2597,113 +2596,7 @@ impl crate::Adapter for super::Adapter { &self, surface: &super::Surface, ) -> Option { - if !self.private_caps.can_present { - return None; - } - let queue_family_index = 0; //TODO - { - profiling::scope!("vkGetPhysicalDeviceSurfaceSupportKHR"); - match unsafe { - surface.functor.get_physical_device_surface_support( - self.raw, - queue_family_index, - surface.raw, - ) - } { - Ok(true) => (), - Ok(false) => return None, - Err(e) => { - log::error!("get_physical_device_surface_support: {e}"); - return None; - } - } - } - - let caps = { - profiling::scope!("vkGetPhysicalDeviceSurfaceCapabilitiesKHR"); - match unsafe { - surface - .functor - .get_physical_device_surface_capabilities(self.raw, surface.raw) - } { - Ok(caps) => caps, - Err(e) => { - log::error!("get_physical_device_surface_capabilities: {e}"); - return None; - } - } - }; - - // If image count is 0, the support number of images is unlimited. - let max_image_count = if caps.max_image_count == 0 { - !0 - } else { - caps.max_image_count - }; - - // `0xFFFFFFFF` indicates that the extent depends on the created swapchain. - let current_extent = if caps.current_extent.width != !0 && caps.current_extent.height != !0 - { - Some(wgt::Extent3d { - width: caps.current_extent.width, - height: caps.current_extent.height, - depth_or_array_layers: 1, - }) - } else { - None - }; - - let raw_present_modes = { - profiling::scope!("vkGetPhysicalDeviceSurfacePresentModesKHR"); - match unsafe { - surface - .functor - .get_physical_device_surface_present_modes(self.raw, surface.raw) - } { - Ok(present_modes) => present_modes, - Err(e) => { - log::error!("get_physical_device_surface_present_modes: {e}"); - // Per definition of `SurfaceCapabilities`, there must be at least one present mode. - return None; - } - } - }; - - let raw_surface_formats = { - profiling::scope!("vkGetPhysicalDeviceSurfaceFormatsKHR"); - match unsafe { - surface - .functor - .get_physical_device_surface_formats(self.raw, surface.raw) - } { - Ok(formats) => formats, - Err(e) => { - log::error!("get_physical_device_surface_formats: {e}"); - // Per definition of `SurfaceCapabilities`, there must be at least one present format. - return None; - } - } - }; - - let formats = raw_surface_formats - .into_iter() - .filter_map(conv::map_vk_surface_formats) - .collect(); - Some(crate::SurfaceCapabilities { - formats, - // TODO: Right now we're always trunkating the swap chain - // (presumably - we're actually setting the min image count which isn't necessarily the swap chain size) - // Instead, we should use extensions when available to wait in present. - // See https://github.com/gfx-rs/wgpu/issues/2869 - maximum_frame_latency: (caps.min_image_count - 1)..=(max_image_count - 1), // Note this can't underflow since both `min_image_count` is at least one and we already patched `max_image_count`. - current_extent, - usage: conv::map_vk_image_usage(caps.supported_usage_flags), - present_modes: raw_present_modes - .into_iter() - .flat_map(conv::map_vk_present_mode) - .collect(), - composite_alpha_modes: conv::map_vk_composite_alpha(caps.supported_composite_alpha), - }) + surface.inner.surface_capabilities(self) } unsafe fn get_presentation_timestamp(&self) -> wgt::PresentationTimestamp { diff --git a/wgpu-hal/src/vulkan/device.rs b/wgpu-hal/src/vulkan/device.rs index f34df0da36f..dbd30df1015 100644 --- a/wgpu-hal/src/vulkan/device.rs +++ b/wgpu-hal/src/vulkan/device.rs @@ -8,7 +8,7 @@ use core::{ }; use arrayvec::ArrayVec; -use ash::{ext, khr, vk}; +use ash::{ext, vk}; use hashbrown::hash_map::Entry; use parking_lot::Mutex; @@ -489,130 +489,6 @@ struct CompiledStage { } impl super::Device { - pub(super) unsafe fn create_swapchain( - &self, - surface: &super::Surface, - config: &crate::SurfaceConfiguration, - provided_old_swapchain: Option, - ) -> Result { - profiling::scope!("Device::create_swapchain"); - let functor = khr::swapchain::Device::new(&surface.instance.raw, &self.shared.raw); - - let old_swapchain = match provided_old_swapchain { - Some(osc) => osc.raw, - None => vk::SwapchainKHR::null(), - }; - - let color_space = if config.format == wgt::TextureFormat::Rgba16Float { - // Enable wide color gamut mode - // Vulkan swapchain for Android only supports DISPLAY_P3_NONLINEAR_EXT and EXTENDED_SRGB_LINEAR_EXT - vk::ColorSpaceKHR::EXTENDED_SRGB_LINEAR_EXT - } else { - vk::ColorSpaceKHR::SRGB_NONLINEAR - }; - - let original_format = self.shared.private_caps.map_texture_format(config.format); - let mut raw_flags = vk::SwapchainCreateFlagsKHR::empty(); - let mut raw_view_formats: Vec = vec![]; - if !config.view_formats.is_empty() { - raw_flags |= vk::SwapchainCreateFlagsKHR::MUTABLE_FORMAT; - raw_view_formats = config - .view_formats - .iter() - .map(|f| self.shared.private_caps.map_texture_format(*f)) - .collect(); - raw_view_formats.push(original_format); - } - - let mut info = vk::SwapchainCreateInfoKHR::default() - .flags(raw_flags) - .surface(surface.raw) - .min_image_count(config.maximum_frame_latency + 1) // TODO: https://github.com/gfx-rs/wgpu/issues/2869 - .image_format(original_format) - .image_color_space(color_space) - .image_extent(vk::Extent2D { - width: config.extent.width, - height: config.extent.height, - }) - .image_array_layers(config.extent.depth_or_array_layers) - .image_usage(conv::map_texture_usage(config.usage)) - .image_sharing_mode(vk::SharingMode::EXCLUSIVE) - .pre_transform(vk::SurfaceTransformFlagsKHR::IDENTITY) - .composite_alpha(conv::map_composite_alpha_mode(config.composite_alpha_mode)) - .present_mode(conv::map_present_mode(config.present_mode)) - .clipped(true) - .old_swapchain(old_swapchain); - - let mut format_list_info = vk::ImageFormatListCreateInfo::default(); - if !raw_view_formats.is_empty() { - format_list_info = format_list_info.view_formats(&raw_view_formats); - info = info.push_next(&mut format_list_info); - } - - let result = { - profiling::scope!("vkCreateSwapchainKHR"); - unsafe { functor.create_swapchain(&info, None) } - }; - - // doing this before bailing out with error - if old_swapchain != vk::SwapchainKHR::null() { - unsafe { functor.destroy_swapchain(old_swapchain, None) } - } - - let raw = match result { - Ok(swapchain) => swapchain, - Err(error) => { - return Err(match error { - vk::Result::ERROR_SURFACE_LOST_KHR - | vk::Result::ERROR_INITIALIZATION_FAILED => crate::SurfaceError::Lost, - vk::Result::ERROR_NATIVE_WINDOW_IN_USE_KHR => { - crate::SurfaceError::Other("Native window is in use") - } - // We don't use VK_EXT_image_compression_control - // VK_ERROR_COMPRESSION_EXHAUSTED_EXT - other => super::map_host_device_oom_and_lost_err(other).into(), - }); - } - }; - - let images = - unsafe { functor.get_swapchain_images(raw) }.map_err(super::map_host_device_oom_err)?; - - let fence = unsafe { - self.shared - .raw - .create_fence(&vk::FenceCreateInfo::default(), None) - .map_err(super::map_host_device_oom_err)? - }; - - // NOTE: It's important that we define the same number of acquire/present semaphores - // as we will need to index into them with the image index. - let acquire_semaphores = (0..=images.len()) - .map(|i| { - super::SwapchainAcquireSemaphore::new(&self.shared, i) - .map(Mutex::new) - .map(Arc::new) - }) - .collect::, _>>()?; - - let present_semaphores = (0..=images.len()) - .map(|i| Arc::new(Mutex::new(super::SwapchainPresentSemaphores::new(i)))) - .collect::>(); - - Ok(super::Swapchain { - raw, - functor, - device: Arc::clone(&self.shared), - images, - fence, - config: config.clone(), - acquire_semaphores, - next_acquire_index: 0, - present_semaphores, - next_present_time: None, - }) - } - /// # Safety /// /// - `vk_image` must be created respecting `desc` diff --git a/wgpu-hal/src/vulkan/instance.rs b/wgpu-hal/src/vulkan/instance.rs index 6cb8515dcee..6225371b042 100644 --- a/wgpu-hal/src/vulkan/instance.rs +++ b/wgpu-hal/src/vulkan/instance.rs @@ -2,6 +2,7 @@ use alloc::{borrow::ToOwned as _, boxed::Box, ffi::CString, string::String, sync use core::{ ffi::{c_void, CStr}, marker::PhantomData, + mem::ManuallyDrop, slice, str::FromStr, }; @@ -170,48 +171,6 @@ impl super::DebugUtilsCreateInfo { } } -impl super::Swapchain { - /// # Safety - /// - /// - The device must have been made idle before calling this function. - unsafe fn release_resources(mut self, device: &ash::Device) -> Self { - profiling::scope!("Swapchain::release_resources"); - { - profiling::scope!("vkDeviceWaitIdle"); - // We need to also wait until all presentation work is done. Because there is no way to portably wait until - // the presentation work is done, we are forced to wait until the device is idle. - let _ = unsafe { - device - .device_wait_idle() - .map_err(super::map_host_device_oom_and_lost_err) - }; - }; - - unsafe { device.destroy_fence(self.fence, None) } - - // We cannot take this by value, as the function returns `self`. - for semaphore in self.acquire_semaphores.drain(..) { - let arc_removed = Arc::into_inner(semaphore).expect( - "Trying to destroy a SurfaceAcquireSemaphores that is still in use by a SurfaceTexture", - ); - let mutex_removed = arc_removed.into_inner(); - - unsafe { mutex_removed.destroy(device) }; - } - - for semaphore in self.present_semaphores.drain(..) { - let arc_removed = Arc::into_inner(semaphore).expect( - "Trying to destroy a SurfacePresentSemaphores that is still in use by a SurfaceTexture", - ); - let mutex_removed = arc_removed.into_inner(); - - unsafe { mutex_removed.destroy(device) }; - } - - self - } -} - impl super::InstanceShared { pub fn entry(&self) -> &ash::Entry { &self.entry @@ -589,11 +548,11 @@ impl super::Instance { &self, surface: vk::SurfaceKHR, ) -> super::Surface { - let functor = khr::surface::Instance::new(&self.shared.entry, &self.shared.raw); + let native_surface = + crate::vulkan::swapchain::NativeSurface::from_vk_surface_khr(self, surface); + super::Surface { - raw: surface, - functor, - instance: Arc::clone(&self.shared), + inner: ManuallyDrop::new(Box::new(native_surface)), swapchain: RwLock::new(None), } } @@ -1027,7 +986,7 @@ impl crate::Instance for super::Instance { impl Drop for super::Surface { fn drop(&mut self) { - unsafe { self.functor.destroy_surface(self.raw, None) }; + unsafe { ManuallyDrop::take(&mut self.inner).delete_surface() }; } } @@ -1041,21 +1000,23 @@ impl crate::Surface for super::Surface { ) -> Result<(), crate::SurfaceError> { // SAFETY: `configure`'s contract guarantees there are no resources derived from the swapchain in use. let mut swap_chain = self.swapchain.write(); - let old = swap_chain - .take() - .map(|sc| unsafe { sc.release_resources(&device.shared.raw) }); - let swapchain = unsafe { device.create_swapchain(self, config, old)? }; + let mut old = swap_chain.take(); + if let Some(ref mut old) = old { + unsafe { old.release_resources(device) }; + } + + let swapchain = unsafe { self.inner.create_swapchain(device, config, old)? }; *swap_chain = Some(swapchain); Ok(()) } unsafe fn unconfigure(&self, device: &super::Device) { - if let Some(sc) = self.swapchain.write().take() { + if let Some(mut sc) = self.swapchain.write().take() { // SAFETY: `unconfigure`'s contract guarantees there are no resources derived from the swapchain in use. - let swapchain = unsafe { sc.release_resources(&device.shared.raw) }; - unsafe { swapchain.functor.destroy_swapchain(swapchain.raw, None) }; + unsafe { sc.release_resources(device) }; + unsafe { sc.delete_swapchain() }; } } @@ -1067,140 +1028,17 @@ impl crate::Surface for super::Surface { let mut swapchain = self.swapchain.write(); let swapchain = swapchain.as_mut().unwrap(); - let mut timeout_ns = match timeout { - Some(duration) => duration.as_nanos() as u64, - None => u64::MAX, - }; - - // AcquireNextImageKHR on Android (prior to Android 11) doesn't support timeouts - // and will also log verbose warnings if tying to use a timeout. - // - // Android 10 implementation for reference: - // https://android.googlesource.com/platform/frameworks/native/+/refs/tags/android-mainline-10.0.0_r13/vulkan/libvulkan/swapchain.cpp#1426 - // Android 11 implementation for reference: - // https://android.googlesource.com/platform/frameworks/native/+/refs/tags/android-mainline-11.0.0_r45/vulkan/libvulkan/swapchain.cpp#1438 - // - // Android 11 corresponds to an SDK_INT/ro.build.version.sdk of 30 - if cfg!(target_os = "android") && self.instance.android_sdk_version < 30 { - timeout_ns = u64::MAX; - } - - let acquire_semaphore_arc = swapchain.get_acquire_semaphore(); - // Nothing should be using this, so we don't block, but panic if we fail to lock. - let acquire_semaphore_guard = acquire_semaphore_arc - .try_lock() - .expect("Failed to lock a SwapchainSemaphores."); - - // Wait for all commands writing to the previously acquired image to - // complete. - // - // Almost all the steps in the usual acquire-draw-present flow are - // asynchronous: they get something started on the presentation engine - // or the GPU, but on the CPU, control returns immediately. Without some - // sort of intervention, the CPU could crank out frames much faster than - // the presentation engine can display them. - // - // This is the intervention: if any submissions drew on this image, and - // thus waited for `locked_swapchain_semaphores.acquire`, wait for all - // of them to finish, thus ensuring that it's okay to pass `acquire` to - // `vkAcquireNextImageKHR` again. - swapchain.device.wait_for_fence( - fence, - acquire_semaphore_guard.previously_used_submission_index, - timeout_ns, - )?; - - // will block if no image is available - let (index, suboptimal) = match unsafe { - profiling::scope!("vkAcquireNextImageKHR"); - swapchain.functor.acquire_next_image( - swapchain.raw, - timeout_ns, - acquire_semaphore_guard.acquire, - swapchain.fence, - ) - } { - // We treat `VK_SUBOPTIMAL_KHR` as `VK_SUCCESS` on Android. - // See the comment in `Queue::present`. - #[cfg(target_os = "android")] - Ok((index, _)) => (index, false), - #[cfg(not(target_os = "android"))] - Ok(pair) => pair, - Err(error) => { - return match error { - vk::Result::TIMEOUT => Ok(None), - vk::Result::NOT_READY | vk::Result::ERROR_OUT_OF_DATE_KHR => { - Err(crate::SurfaceError::Outdated) - } - vk::Result::ERROR_SURFACE_LOST_KHR => Err(crate::SurfaceError::Lost), - // We don't use VK_EXT_full_screen_exclusive - // VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT - other => Err(super::map_host_device_oom_and_lost_err(other).into()), - }; - } - }; + unsafe { swapchain.acquire(timeout, fence) } + } - // Wait for the image was acquired to be fully ready to be rendered too. - // - // This wait is very important on Windows to avoid bad frame pacing on - // Windows where the Vulkan driver is using a DXGI swapchain. See - // https://github.com/gfx-rs/wgpu/issues/8310 and - // https://github.com/gfx-rs/wgpu/issues/8354 for more details. - // - // On other platforms, this wait may serve to slightly decrease frame - // latency, depending on how the platform implements waiting within - // acquire. + unsafe fn discard_texture(&self, texture: super::SurfaceTexture) { unsafe { - swapchain - .device - .raw - .wait_for_fences(&[swapchain.fence], false, timeout_ns) - .map_err(super::map_host_device_oom_and_lost_err)?; - - swapchain - .device - .raw - .reset_fences(&[swapchain.fence]) - .map_err(super::map_host_device_oom_and_lost_err)?; - } - - drop(acquire_semaphore_guard); - // We only advance the surface semaphores if we successfully acquired an image, otherwise - // we should try to re-acquire using the same semaphores. - swapchain.advance_acquire_semaphore(); - - let present_semaphore_arc = swapchain.get_present_semaphores(index); - - // special case for Intel Vulkan returning bizarre values (ugh) - if swapchain.device.vendor_id == crate::auxil::db::intel::VENDOR && index > 0x100 { - return Err(crate::SurfaceError::Outdated); - } - - let identity = swapchain.device.texture_identity_factory.next(); - - let texture = super::SurfaceTexture { - index, - texture: super::Texture { - raw: swapchain.images[index as usize], - drop_guard: None, - block: None, - external_memory: None, - format: swapchain.config.format, - copy_size: crate::CopyExtent { - width: swapchain.config.extent.width, - height: swapchain.config.extent.height, - depth: 1, - }, - identity, - }, - acquire_semaphores: acquire_semaphore_arc, - present_semaphores: present_semaphore_arc, + self.swapchain + .write() + .as_mut() + .unwrap() + .discard_texture(texture) + .unwrap() }; - Ok(Some(crate::AcquiredSurfaceTexture { - texture, - suboptimal, - })) } - - unsafe fn discard_texture(&self, _texture: super::SurfaceTexture) {} } diff --git a/wgpu-hal/src/vulkan/mod.rs b/wgpu-hal/src/vulkan/mod.rs index 1cd12ea39d5..aed60c6a74a 100644 --- a/wgpu-hal/src/vulkan/mod.rs +++ b/wgpu-hal/src/vulkan/mod.rs @@ -32,11 +32,19 @@ mod drm; mod instance; mod sampler; mod semaphore_list; +mod swapchain; pub use adapter::PhysicalDeviceFeatures; use alloc::{boxed::Box, ffi::CString, sync::Arc, vec::Vec}; -use core::{borrow::Borrow, ffi::CStr, fmt, marker::PhantomData, mem, num::NonZeroU32}; +use core::{ + borrow::Borrow, + ffi::CStr, + fmt, + marker::PhantomData, + mem::{self, ManuallyDrop}, + num::NonZeroU32, +}; use arrayvec::ArrayVec; use ash::{ext, khr, vk}; @@ -49,6 +57,8 @@ use wgt::InternalCounter; use semaphore_list::SemaphoreList; +use crate::vulkan::semaphore_list::{SemaphoreListMode, SemaphoreType}; + const MAX_TOTAL_ATTACHMENTS: usize = crate::MAX_COLOR_ATTACHMENTS * 2 + 1; #[derive(Clone, Debug)] @@ -180,302 +190,36 @@ pub struct Instance { shared: Arc, } -/// Semaphore used to acquire a swapchain image. -#[derive(Debug)] -struct SwapchainAcquireSemaphore { - /// A semaphore that is signaled when this image is safe for us to modify. - /// - /// When [`vkAcquireNextImageKHR`] returns the index of the next swapchain - /// image that we should use, that image may actually still be in use by the - /// presentation engine, and is not yet safe to modify. However, that - /// function does accept a semaphore that it will signal when the image is - /// indeed safe to begin messing with. - /// - /// This semaphore is: - /// - /// - waited for by the first queue submission to operate on this image - /// since it was acquired, and - /// - /// - signaled by [`vkAcquireNextImageKHR`] when the acquired image is ready - /// for us to use. - /// - /// [`vkAcquireNextImageKHR`]: https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#vkAcquireNextImageKHR - acquire: vk::Semaphore, - - /// True if the next command submission operating on this image should wait - /// for [`acquire`]. - /// - /// We must wait for `acquire` before drawing to this swapchain image, but - /// because `wgpu-hal` queue submissions are always strongly ordered, only - /// the first submission that works with a swapchain image actually needs to - /// wait. We set this flag when this image is acquired, and clear it the - /// first time it's passed to [`Queue::submit`] as a surface texture. - /// - /// Additionally, semaphores can only be waited on once, so we need to ensure - /// that we only actually pass this semaphore to the first submission that - /// uses that image. - /// - /// [`acquire`]: SwapchainAcquireSemaphore::acquire - /// [`Queue::submit`]: crate::Queue::submit - should_wait_for_acquire: bool, - - /// The fence value of the last command submission that wrote to this image. - /// - /// The next time we try to acquire this image, we'll block until - /// this submission finishes, proving that [`acquire`] is ready to - /// pass to `vkAcquireNextImageKHR` again. - /// - /// [`acquire`]: SwapchainAcquireSemaphore::acquire - previously_used_submission_index: crate::FenceValue, -} - -impl SwapchainAcquireSemaphore { - fn new(device: &DeviceShared, index: usize) -> Result { - Ok(Self { - acquire: device - .new_binary_semaphore(&format!("SwapchainImageSemaphore: Index {index} acquire"))?, - should_wait_for_acquire: true, - previously_used_submission_index: 0, - }) - } - - /// Sets the fence value which the next acquire will wait for. This prevents - /// the semaphore from being used while the previous submission is still in flight. - fn set_used_fence_value(&mut self, value: crate::FenceValue) { - self.previously_used_submission_index = value; - } - - /// Return the semaphore that commands drawing to this image should wait for, if any. - /// - /// This only returns `Some` once per acquisition; see - /// [`SwapchainAcquireSemaphore::should_wait_for_acquire`] for details. - fn get_acquire_wait_semaphore(&mut self) -> Option { - if self.should_wait_for_acquire { - self.should_wait_for_acquire = false; - Some(self.acquire) - } else { - None - } - } - - /// Indicates the cpu-side usage of this semaphore has finished for the frame, - /// so reset internal state to be ready for the next frame. - fn end_semaphore_usage(&mut self) { - // Reset the acquire semaphore, so that the next time we acquire this - // image, we can wait for it again. - self.should_wait_for_acquire = true; - } - - unsafe fn destroy(&self, device: &ash::Device) { - unsafe { - device.destroy_semaphore(self.acquire, None); - } - } -} - -#[derive(Debug)] -struct SwapchainPresentSemaphores { - /// A pool of semaphores for ordering presentation after drawing. - /// - /// The first [`present_index`] semaphores in this vector are: - /// - /// - all waited on by the call to [`vkQueuePresentKHR`] that presents this - /// image, and - /// - /// - each signaled by some [`vkQueueSubmit`] queue submission that draws to - /// this image, when the submission finishes execution. - /// - /// This vector accumulates one semaphore per submission that writes to this - /// image. This is awkward, but hard to avoid: [`vkQueuePresentKHR`] - /// requires a semaphore to order it with respect to drawing commands, and - /// we can't attach new completion semaphores to a command submission after - /// it's been submitted. This means that, at submission time, we must create - /// the semaphore we might need if the caller's next action is to enqueue a - /// presentation of this image. - /// - /// An alternative strategy would be for presentation to enqueue an empty - /// submit, ordered relative to other submits in the usual way, and - /// signaling a single presentation semaphore. But we suspect that submits - /// are usually expensive enough, and semaphores usually cheap enough, that - /// performance-sensitive users will avoid making many submits, so that the - /// cost of accumulated semaphores will usually be less than the cost of an - /// additional submit. - /// - /// Only the first [`present_index`] semaphores in the vector are actually - /// going to be signalled by submitted commands, and need to be waited for - /// by the next present call. Any semaphores beyond that index were created - /// for prior presents and are simply being retained for recycling. - /// - /// [`present_index`]: SwapchainPresentSemaphores::present_index - /// [`vkQueuePresentKHR`]: https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#vkQueuePresentKHR - /// [`vkQueueSubmit`]: https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#vkQueueSubmit - present: Vec, - - /// The number of semaphores in [`present`] to be signalled for this submission. - /// - /// [`present`]: SwapchainPresentSemaphores::present - present_index: usize, - - /// Which image this semaphore set is used for. - frame_index: usize, -} - -impl SwapchainPresentSemaphores { - pub fn new(frame_index: usize) -> Self { - Self { - present: Vec::new(), - present_index: 0, - frame_index, - } - } - - /// Return the semaphore that the next submission that writes to this image should - /// signal when it's done. - /// - /// See [`SwapchainPresentSemaphores::present`] for details. - fn get_submit_signal_semaphore( - &mut self, - device: &DeviceShared, - ) -> Result { - // Try to recycle a semaphore we created for a previous presentation. - let sem = match self.present.get(self.present_index) { - Some(sem) => *sem, - None => { - let sem = device.new_binary_semaphore(&format!( - "SwapchainImageSemaphore: Image {} present semaphore {}", - self.frame_index, self.present_index - ))?; - self.present.push(sem); - sem - } - }; - - self.present_index += 1; - - Ok(sem) - } - - /// Indicates the cpu-side usage of this semaphore has finished for the frame, - /// so reset internal state to be ready for the next frame. - fn end_semaphore_usage(&mut self) { - // Reset the index to 0, so that the next time we get a semaphore, we - // start from the beginning of the list. - self.present_index = 0; - } - - /// Return the semaphores that a presentation of this image should wait on. - /// - /// Return a slice of semaphores that the call to [`vkQueueSubmit`] that - /// ends this image's acquisition should wait for. See - /// [`SwapchainPresentSemaphores::present`] for details. - /// - /// Reset `self` to be ready for the next acquisition cycle. - /// - /// [`vkQueueSubmit`]: https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#vkQueueSubmit - fn get_present_wait_semaphores(&mut self) -> Vec { - self.present[0..self.present_index].to_vec() - } - - unsafe fn destroy(&self, device: &ash::Device) { - unsafe { - for sem in &self.present { - device.destroy_semaphore(*sem, None); - } - } - } -} - -struct Swapchain { - raw: vk::SwapchainKHR, - functor: khr::swapchain::Device, - device: Arc, - images: Vec, - /// Fence used to wait on the acquired image. - fence: vk::Fence, - config: crate::SurfaceConfiguration, - - /// Semaphores used between image acquisition and the first submission - /// that uses that image. This is indexed using [`next_acquire_index`]. - /// - /// Because we need to provide this to [`vkAcquireNextImageKHR`], we haven't - /// received the swapchain image index for the frame yet, so we cannot use - /// that to index it. - /// - /// Before we pass this to [`vkAcquireNextImageKHR`], we ensure that we wait on - /// the submission indicated by [`previously_used_submission_index`]. This enusres - /// the semaphore is no longer in use before we use it. - /// - /// [`next_acquire_index`]: Swapchain::next_acquire_index - /// [`vkAcquireNextImageKHR`]: https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#vkAcquireNextImageKHR - /// [`previously_used_submission_index`]: SwapchainAcquireSemaphore::previously_used_submission_index - acquire_semaphores: Vec>>, - /// The index of the next acquire semaphore to use. - /// - /// This is incremented each time we acquire a new image, and wraps around - /// to 0 when it reaches the end of [`acquire_semaphores`]. - /// - /// [`acquire_semaphores`]: Swapchain::acquire_semaphores - next_acquire_index: usize, - - /// Semaphore sets used between all submissions that write to an image and - /// the presentation of that image. - /// - /// This is indexed by the swapchain image index returned by - /// [`vkAcquireNextImageKHR`]. - /// - /// We know it is safe to use these semaphores because use them - /// _after_ the acquire semaphore. Because the acquire semaphore - /// has been signaled, the previous presentation using that image - /// is known-finished, so this semaphore is no longer in use. - /// - /// [`vkAcquireNextImageKHR`]: https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#vkAcquireNextImageKHR - present_semaphores: Vec>>, - - /// The present timing information which will be set in the next call to [`present()`](crate::Queue::present()). - /// - /// # Safety - /// - /// This must only be set if [`wgt::Features::VULKAN_GOOGLE_DISPLAY_TIMING`] is enabled, and - /// so the VK_GOOGLE_display_timing extension is present. - next_present_time: Option, -} - -impl Swapchain { - /// Mark the current frame finished, advancing to the next acquire semaphore. - fn advance_acquire_semaphore(&mut self) { - let semaphore_count = self.acquire_semaphores.len(); - self.next_acquire_index = (self.next_acquire_index + 1) % semaphore_count; - } - - /// Get the next acquire semaphore that should be used with this swapchain. - fn get_acquire_semaphore(&self) -> Arc> { - self.acquire_semaphores[self.next_acquire_index].clone() - } - - /// Get the set of present semaphores that should be used with the given image index. - fn get_present_semaphores(&self, index: u32) -> Arc> { - self.present_semaphores[index as usize].clone() - } -} - pub struct Surface { - raw: vk::SurfaceKHR, - functor: khr::surface::Instance, - instance: Arc, - swapchain: RwLock>, + inner: ManuallyDrop>, + swapchain: RwLock>>, } impl Surface { - pub unsafe fn raw_handle(&self) -> vk::SurfaceKHR { - self.raw + /// Returns the raw Vulkan surface handle. + /// + /// Returns `None` if the surface is a DXGI surface. + pub unsafe fn raw_native_handle(&self) -> Option { + Some( + self.inner + .as_any() + .downcast_ref::()? + .as_raw(), + ) } /// Get the raw Vulkan swapchain associated with this surface. /// - /// Returns [`None`] if the surface is not configured. - pub fn raw_swapchain(&self) -> Option { + /// Returns [`None`] if the surface is not configured or if the swapchain + /// is a DXGI swapchain. + pub fn raw_native_swapchain(&self) -> Option { let read = self.swapchain.read(); - read.as_ref().map(|it| it.raw) + Some( + read.as_ref()? + .as_any() + .downcast_ref::()? + .as_raw(), + ) } /// Set the present timing information which will be used for the next [presentation](crate::Queue::present()) of this surface, @@ -491,28 +235,20 @@ impl Surface { /// # Panics /// /// - If the surface hasn't been configured. + /// - If the surface has been configured for a DXGI swapchain. /// - If the device doesn't [support present timing](wgt::Features::VULKAN_GOOGLE_DISPLAY_TIMING). /// /// [VK_GOOGLE_display_timing]: https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_GOOGLE_display_timing.html #[track_caller] pub fn set_next_present_time(&self, present_timing: vk::PresentTimeGOOGLE) { let mut swapchain = self.swapchain.write(); - let swapchain = swapchain + swapchain .as_mut() - .expect("Surface should have been configured"); - let features = wgt::Features::VULKAN_GOOGLE_DISPLAY_TIMING; - if swapchain.device.features.contains(features) { - swapchain.next_present_time = Some(present_timing); - } else { - // Ideally we'd use something like `device.required_features` here, but that's in `wgpu-core`, which we are a dependency of - panic!( - concat!( - "Tried to set display timing properties ", - "without the corresponding feature ({:?}) enabled." - ), - features - ); - } + .expect("Surface should have been configured") + .as_any_mut() + .downcast_mut::() + .expect("Surface should have a native Vulkan swapchain") + .set_next_present_time(present_timing); } } @@ -520,8 +256,7 @@ impl Surface { pub struct SurfaceTexture { index: u32, texture: Texture, - acquire_semaphores: Arc>, - present_semaphores: Arc>, + metadata: Box, } impl crate::DynSurfaceTexture for SurfaceTexture {} @@ -854,7 +589,6 @@ impl RelaySemaphores { pub struct Queue { raw: vk::Queue, - swapchain_fn: khr::swapchain::Device, device: Arc, family_index: u32, relay_semaphores: Mutex, @@ -1472,56 +1206,43 @@ impl crate::Queue for Queue { ) -> Result<(), crate::DeviceError> { let mut fence_raw = vk::Fence::null(); - let mut wait_stage_masks = Vec::new(); - let mut wait_semaphores = Vec::new(); - let mut signal_semaphores = SemaphoreList::default(); + let mut wait_semaphores = SemaphoreList::new(SemaphoreListMode::Wait); + let mut signal_semaphores = SemaphoreList::new(SemaphoreListMode::Signal); // Double check that the same swapchain image isn't being given to us multiple times, // as that will deadlock when we try to lock them all. debug_assert!( { let mut check = HashSet::with_capacity(surface_textures.len()); - // We compare the Arcs by pointer, as Eq isn't well defined for SurfaceSemaphores. + // We compare the Box by pointer, as Eq isn't well defined for SurfaceSemaphores. for st in surface_textures { - check.insert(Arc::as_ptr(&st.acquire_semaphores) as usize); - check.insert(Arc::as_ptr(&st.present_semaphores) as usize); + let ptr: *const () = <*const _>::cast(&*st.metadata); + check.insert(ptr as usize); } - check.len() == surface_textures.len() * 2 + check.len() == surface_textures.len() }, "More than one surface texture is being used from the same swapchain. This will cause a deadlock in release." ); let locked_swapchain_semaphores = surface_textures .iter() - .map(|st| { - let acquire = st - .acquire_semaphores - .try_lock() - .expect("Failed to lock surface acquire semaphore"); - let present = st - .present_semaphores - .try_lock() - .expect("Failed to lock surface present semaphore"); - - (acquire, present) - }) + .map(|st| st.metadata.get_semaphore_guard()) .collect::>(); - for (mut acquire_semaphore, mut present_semaphores) in locked_swapchain_semaphores { - acquire_semaphore.set_used_fence_value(signal_value); + for mut semaphores in locked_swapchain_semaphores { + semaphores.set_used_fence_value(signal_value); // If we're the first submission to operate on this image, wait on // its acquire semaphore, to make sure the presentation engine is // done with it. - if let Some(sem) = acquire_semaphore.get_acquire_wait_semaphore() { - wait_stage_masks.push(vk::PipelineStageFlags::TOP_OF_PIPE); - wait_semaphores.push(sem); + if let Some(sem) = semaphores.get_acquire_wait_semaphore() { + wait_semaphores.push_wait(sem, vk::PipelineStageFlags::TOP_OF_PIPE); } // Get a semaphore to signal when we're done writing to this surface // image. Presentation of this image will wait for this. - let signal_semaphore = present_semaphores.get_submit_signal_semaphore(&self.device)?; - signal_semaphores.push_binary(signal_semaphore); + let signal_semaphore = semaphores.get_submit_signal_semaphore(&self.device)?; + signal_semaphores.push_signal(signal_semaphore); } let mut guard = self.signal_semaphores.lock(); @@ -1534,17 +1255,19 @@ impl crate::Queue for Queue { let semaphore_state = self.relay_semaphores.lock().advance(&self.device)?; if let Some(sem) = semaphore_state.wait { - wait_stage_masks.push(vk::PipelineStageFlags::TOP_OF_PIPE); - wait_semaphores.push(sem); + wait_semaphores.push_wait( + SemaphoreType::Binary(sem), + vk::PipelineStageFlags::TOP_OF_PIPE, + ); } - signal_semaphores.push_binary(semaphore_state.signal); + signal_semaphores.push_signal(SemaphoreType::Binary(semaphore_state.signal)); // We need to signal our wgpu::Fence if we have one, this adds it to the signal list. signal_fence.maintain(&self.device.raw)?; match *signal_fence { Fence::TimelineSemaphore(raw) => { - signal_semaphores.push_timeline(raw, signal_value); + signal_semaphores.push_signal(SemaphoreType::Timeline(raw, signal_value)); } Fence::FencePool { ref mut active, @@ -1570,13 +1293,13 @@ impl crate::Queue for Queue { .collect::>(); let mut vk_info = vk::SubmitInfo::default().command_buffers(&vk_cmd_buffers); - - vk_info = vk_info - .wait_semaphores(&wait_semaphores) - .wait_dst_stage_mask(&wait_stage_masks); - let mut vk_timeline_info = mem::MaybeUninit::uninit(); - vk_info = signal_semaphores.add_to_submit(vk_info, &mut vk_timeline_info); + vk_info = SemaphoreList::add_to_submit( + &mut wait_semaphores, + &mut signal_semaphores, + vk_info, + &mut vk_timeline_info, + ); profiling::scope!("vkQueueSubmit"); unsafe { @@ -1594,68 +1317,8 @@ impl crate::Queue for Queue { texture: SurfaceTexture, ) -> Result<(), crate::SurfaceError> { let mut swapchain = surface.swapchain.write(); - let ssc = swapchain.as_mut().unwrap(); - let mut acquire_semaphore = texture.acquire_semaphores.lock(); - let mut present_semaphores = texture.present_semaphores.lock(); - - let wait_semaphores = present_semaphores.get_present_wait_semaphores(); - - // Reset the acquire and present semaphores internal state - // to be ready for the next frame. - // - // We do this before the actual call to present to ensure that - // even if this method errors and early outs, we have reset - // the state for next frame. - acquire_semaphore.end_semaphore_usage(); - present_semaphores.end_semaphore_usage(); - - drop(acquire_semaphore); - - let swapchains = [ssc.raw]; - let image_indices = [texture.index]; - let vk_info = vk::PresentInfoKHR::default() - .swapchains(&swapchains) - .image_indices(&image_indices) - .wait_semaphores(&wait_semaphores); - - let mut display_timing; - let present_times; - let vk_info = if let Some(present_time) = ssc.next_present_time.take() { - debug_assert!( - ssc.device - .features - .contains(wgt::Features::VULKAN_GOOGLE_DISPLAY_TIMING), - "`next_present_time` should only be set if `VULKAN_GOOGLE_DISPLAY_TIMING` is enabled" - ); - present_times = [present_time]; - display_timing = vk::PresentTimesInfoGOOGLE::default().times(&present_times); - // SAFETY: We know that VK_GOOGLE_display_timing is present because of the safety contract on `next_present_time`. - vk_info.push_next(&mut display_timing) - } else { - vk_info - }; - let suboptimal = { - profiling::scope!("vkQueuePresentKHR"); - unsafe { self.swapchain_fn.queue_present(self.raw, &vk_info) }.map_err(|error| { - match error { - vk::Result::ERROR_OUT_OF_DATE_KHR => crate::SurfaceError::Outdated, - vk::Result::ERROR_SURFACE_LOST_KHR => crate::SurfaceError::Lost, - // We don't use VK_EXT_full_screen_exclusive - // VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT - _ => map_host_device_oom_and_lost_err(error).into(), - } - })? - }; - if suboptimal { - // We treat `VK_SUBOPTIMAL_KHR` as `VK_SUCCESS` on Android. - // On Android 10+, libvulkan's `vkQueuePresentKHR` implementation returns `VK_SUBOPTIMAL_KHR` if not doing pre-rotation - // (i.e `VkSwapchainCreateInfoKHR::preTransform` not being equal to the current device orientation). - // This is always the case when the device orientation is anything other than the identity one, as we unconditionally use `VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR`. - #[cfg(not(target_os = "android"))] - log::warn!("Suboptimal present of frame {}", texture.index); - } - Ok(()) + unsafe { swapchain.as_mut().unwrap().present(self, texture) } } unsafe fn get_timestamp_period(&self) -> f32 { @@ -1671,9 +1334,9 @@ impl Queue { pub fn add_signal_semaphore(&self, semaphore: vk::Semaphore, semaphore_value: Option) { let mut guard = self.signal_semaphores.lock(); if let Some(value) = semaphore_value { - guard.push_timeline(semaphore, value); + guard.push_signal(SemaphoreType::Timeline(semaphore, value)); } else { - guard.push_binary(semaphore); + guard.push_signal(SemaphoreType::Binary(semaphore)); } } } diff --git a/wgpu-hal/src/vulkan/semaphore_list.rs b/wgpu-hal/src/vulkan/semaphore_list.rs index 5d3fe451497..91e0310087a 100644 --- a/wgpu-hal/src/vulkan/semaphore_list.rs +++ b/wgpu-hal/src/vulkan/semaphore_list.rs @@ -4,10 +4,17 @@ use alloc::vec::Vec; use ash::vk; use core::mem::MaybeUninit; -/// A list of Vulkan semaphores to signal. +#[derive(Debug, PartialEq)] +pub enum SemaphoreListMode { + Wait, + Signal, +} + +/// A list of Vulkan semaphores to wait for or signal. /// /// This represents a list of binary or timeline semaphores, together -/// with values for the timeline semaphores. +/// with values for the timeline semaphores, and stage masks, if these +/// are used for waiting. /// /// This type ensures that the array of semaphores to be signaled /// stays aligned with the array of values for timeline semaphores @@ -16,14 +23,17 @@ use core::mem::MaybeUninit; /// actually have. /// /// [`add_to_submit`]: SemaphoreList::add_to_submit -#[derive(Debug, Default)] +#[derive(Debug)] pub struct SemaphoreList { - /// Semaphores to signal. + /// Mode of the semaphore list. Used for validation. + mode: SemaphoreListMode, + + /// Semaphores to use. /// /// This can be a mix of binary and timeline semaphores. semaphores: Vec, - /// Values for timeline semaphores. + /// Values for the timeline semaphores. /// /// If no timeline semaphores are present in [`semaphores`], this /// is empty. If any timeline semaphores are present, then this @@ -33,9 +43,23 @@ pub struct SemaphoreList { /// /// [`semaphores`]: Self::semaphores values: Vec, + + /// Stage masks for wait semaphores. + /// + /// This is only used if `mode` is `Wait`. + pub stage_masks: Vec, } impl SemaphoreList { + pub fn new(mode: SemaphoreListMode) -> Self { + Self { + mode, + semaphores: Vec::new(), + values: Vec::new(), + stage_masks: Vec::new(), + } + } + pub fn is_empty(&self) -> bool { self.semaphores.is_empty() } @@ -50,43 +74,83 @@ impl SemaphoreList { /// list's values, and add it to `submit_info`s extension chain. /// /// Return the revised `submit_info` value. - pub fn add_to_submit<'i, 's: 'i>( - &'s self, - submit_info: vk::SubmitInfo<'i>, - timeline_info: &'i mut MaybeUninit>, - ) -> vk::SubmitInfo<'i> { - self.check(); - let mut submit_info = submit_info.signal_semaphores(&self.semaphores); - if !self.values.is_empty() { - let timeline_info = timeline_info.write( - vk::TimelineSemaphoreSubmitInfo::default().signal_semaphore_values(&self.values), - ); + pub fn add_to_submit<'info, 'semaphores: 'info>( + wait_semaphores: &'semaphores mut Self, + signal_semaphores: &'semaphores mut Self, + submit_info: vk::SubmitInfo<'info>, + timeline_info: &'info mut MaybeUninit>, + ) -> vk::SubmitInfo<'info> { + wait_semaphores.check(); + signal_semaphores.check(); + + assert!(matches!(wait_semaphores.mode, SemaphoreListMode::Wait)); + assert!(matches!(signal_semaphores.mode, SemaphoreListMode::Signal)); + + let timeline_info = timeline_info.write(vk::TimelineSemaphoreSubmitInfo::default()); + + let mut uses_timeline = false; + + if !wait_semaphores.values.is_empty() { + *timeline_info = timeline_info.wait_semaphore_values(&wait_semaphores.values); + uses_timeline = true; + } + + if !signal_semaphores.values.is_empty() { + *timeline_info = timeline_info.signal_semaphore_values(&signal_semaphores.values); + uses_timeline = true; + } + + let mut submit_info = submit_info + .wait_semaphores(&wait_semaphores.semaphores) + .wait_dst_stage_mask(&wait_semaphores.stage_masks) + .signal_semaphores(&signal_semaphores.semaphores); + + if uses_timeline { submit_info = submit_info.push_next(timeline_info); } + submit_info } - /// Add a binary semaphore to this list. - pub fn push_binary(&mut self, semaphore: vk::Semaphore) { - self.semaphores.push(semaphore); - // Push a dummy value if necessary. - if !self.values.is_empty() { - self.values.push(!0); - } - self.check(); + /// Add a semaphore to be signaled. Panics if this is a list of semaphores to wait. + pub fn push_signal(&mut self, semaphore: SemaphoreType) { + assert!(matches!(self.mode, SemaphoreListMode::Signal)); + self.push_inner(semaphore); + } + + /// Add a semaphore to be waited for. Panics if this is a list of semaphores to signal. + pub fn push_wait(&mut self, semaphore: SemaphoreType, stage: vk::PipelineStageFlags) { + assert!(matches!(self.mode, SemaphoreListMode::Wait)); + + self.stage_masks.push(stage); + self.push_inner(semaphore); } - /// Add a timeline semaphore to this list, to be signalled with - /// `value`. - pub fn push_timeline(&mut self, semaphore: vk::Semaphore, value: u64) { - self.pad_values(); - self.semaphores.push(semaphore); - self.values.push(value); + fn push_inner(&mut self, semaphore: SemaphoreType) { + match semaphore { + SemaphoreType::Binary(semaphore) => { + self.semaphores.push(semaphore); + // Push a dummy value if necessary. + if !self.values.is_empty() { + self.values.push(!0); + } + } + SemaphoreType::Timeline(semaphore, value) => { + // We may be the first timeline semaphore, ensure that the values + // array is filled with dummy values for existing binary semaphores. + self.pad_values(); + self.semaphores.push(semaphore); + self.values.push(value); + } + } + self.check(); } /// Append `other` to `self`, leaving `other` empty. pub fn append(&mut self, other: &mut Self) { + assert_eq!(self.mode, other.mode); + // If we're about to receive values, ensure we're aligned first. if !other.values.is_empty() { self.pad_values(); @@ -97,6 +161,7 @@ impl SemaphoreList { if !self.values.is_empty() { self.pad_values(); } + self.stage_masks.append(&mut other.stage_masks); self.check(); } @@ -111,5 +176,20 @@ impl SemaphoreList { #[track_caller] fn check(&self) { debug_assert!(self.values.is_empty() || self.values.len() == self.semaphores.len()); + match self.mode { + SemaphoreListMode::Wait => { + debug_assert!( + self.stage_masks.is_empty() || self.stage_masks.len() == self.semaphores.len() + ); + } + SemaphoreListMode::Signal => { + debug_assert!(self.stage_masks.is_empty()); + } + } } } + +pub enum SemaphoreType { + Binary(vk::Semaphore), + Timeline(vk::Semaphore, u64), +} diff --git a/wgpu-hal/src/vulkan/swapchain/mod.rs b/wgpu-hal/src/vulkan/swapchain/mod.rs new file mode 100644 index 00000000000..8019f292d16 --- /dev/null +++ b/wgpu-hal/src/vulkan/swapchain/mod.rs @@ -0,0 +1,107 @@ +use alloc::boxed::Box; +use core::{any::Any, fmt::Debug, time::Duration}; + +use crate::vulkan::{semaphore_list::SemaphoreType, DeviceShared}; + +pub(super) use native::*; + +mod native; + +pub(super) trait Surface: Send + Sync + 'static { + /// Deletes the surface and associated resources. + /// + /// The surface must not be in use when it is deleted. + unsafe fn delete_surface(self: Box); + + /// Returns the surface capabilities for the given adapter. + /// + /// Returns `None` if the surface is not compatible with the adapter. + fn surface_capabilities(&self, adapter: &super::Adapter) -> Option; + + /// Creates a swapchain for the surface with the given configuration. + /// + /// If this is not the first swapchain created for the surface, the old swapchain + /// must be provided. [`Swapchain::release_resources`] must be called on the old swapchain + /// before calling this method. + unsafe fn create_swapchain( + &self, + device: &super::Device, + config: &crate::SurfaceConfiguration, + provided_old_swapchain: Option>, + ) -> Result, crate::SurfaceError>; + + /// Allows downcasting to the concrete type. + fn as_any(&self) -> &dyn Any; +} + +pub(super) trait Swapchain: Send + Sync + 'static { + /// Releases all resources associated with the swapchain, without + /// destroying the swapchain itself. Must be called before calling + /// either [`Surface::create_swapchain`] or [`Swapchain::delete_swapchain`]. + /// + /// The swapchain must not be in use when this is called. + unsafe fn release_resources(&mut self, device: &super::Device); + + /// Deletes the swapchain. + /// + /// The swapchain must not be in use when it is deleted and + /// [`Swapchain::release_resources`] must have been called first. + unsafe fn delete_swapchain(self: Box); + + /// Acquires the next available surface texture for rendering. + /// + /// `timeout` specifies the maximum time to wait for an image to become available. + /// If `None` is specified, this function will wait indefinitely. + /// + /// Returns `Ok(None)` if the timeout elapsed before an image became available. + unsafe fn acquire( + &mut self, + timeout: Option, + fence: &super::Fence, + ) -> Result>, crate::SurfaceError>; + + /// Tries to discard the acquired texture without presenting it. + /// + /// In practice, this doesn't really work in the current implementations. + unsafe fn discard_texture( + &mut self, + texture: super::SurfaceTexture, + ) -> Result<(), crate::SurfaceError>; + + /// Presents the given surface texture using the queue. + unsafe fn present( + &mut self, + queue: &super::Queue, + texture: crate::vulkan::SurfaceTexture, + ) -> Result<(), crate::SurfaceError>; + + /// Allows downcasting to the concrete type. + fn as_any(&self) -> &dyn Any; + + /// Allows downcasting to the concrete type mutably. + fn as_any_mut(&mut self) -> &mut dyn Any; +} + +/// Swapchain specific metadata associated with a surface texture. +pub(super) trait SurfaceTextureMetadata: Debug + Send + Sync + 'static { + /// Returns a guard which can yield the semaphores needed for submission using this swapchain texture. + fn get_semaphore_guard(&self) -> Box; + + /// Allows downcasting to the concrete type. + fn as_any(&self) -> &dyn Any; +} + +/// Guard type for managing swapchain submission semaphores. +pub(super) trait SwapchainSubmissionSemaphoreGuard { + /// Sets the Fence value for this submission. + fn set_used_fence_value(&mut self, value: u64); + + /// Gets semaphores to wait on before doing GPU work for this swapchain texture. + fn get_acquire_wait_semaphore(&mut self) -> Option; + + /// Gets the semaphore to signal when GPU work for this swapchain texture is complete. + fn get_submit_signal_semaphore( + &mut self, + device: &DeviceShared, + ) -> Result; +} diff --git a/wgpu-hal/src/vulkan/swapchain/native.rs b/wgpu-hal/src/vulkan/swapchain/native.rs new file mode 100644 index 00000000000..ac8c07cd115 --- /dev/null +++ b/wgpu-hal/src/vulkan/swapchain/native.rs @@ -0,0 +1,904 @@ +//! Vulkan Surface and Swapchain implementation using native Vulkan surfaces. + +use alloc::{boxed::Box, sync::Arc, vec::Vec}; +use core::any::Any; + +use ash::{khr, vk}; +use parking_lot::{Mutex, MutexGuard}; + +use crate::vulkan::{ + conv, map_host_device_oom_and_lost_err, + semaphore_list::SemaphoreType, + swapchain::{Surface, SurfaceTextureMetadata, Swapchain, SwapchainSubmissionSemaphoreGuard}, + DeviceShared, InstanceShared, +}; + +pub(crate) struct NativeSurface { + raw: vk::SurfaceKHR, + functor: khr::surface::Instance, + instance: Arc, +} + +impl NativeSurface { + pub fn from_vk_surface_khr(instance: &crate::vulkan::Instance, raw: vk::SurfaceKHR) -> Self { + let functor = khr::surface::Instance::new(&instance.shared.entry, &instance.shared.raw); + Self { + raw, + functor, + instance: Arc::clone(&instance.shared), + } + } + + pub fn as_raw(&self) -> vk::SurfaceKHR { + self.raw + } +} + +impl Surface for NativeSurface { + unsafe fn delete_surface(self: Box) { + unsafe { + self.functor.destroy_surface(self.raw, None); + } + } + + fn surface_capabilities( + &self, + adapter: &crate::vulkan::Adapter, + ) -> Option { + if !adapter.private_caps.can_present { + return None; + } + let queue_family_index = 0; //TODO + { + profiling::scope!("vkGetPhysicalDeviceSurfaceSupportKHR"); + match unsafe { + self.functor.get_physical_device_surface_support( + adapter.raw, + queue_family_index, + self.raw, + ) + } { + Ok(true) => (), + Ok(false) => return None, + Err(e) => { + log::error!("get_physical_device_surface_support: {e}"); + return None; + } + } + } + + let caps = { + profiling::scope!("vkGetPhysicalDeviceSurfaceCapabilitiesKHR"); + match unsafe { + self.functor + .get_physical_device_surface_capabilities(adapter.raw, self.raw) + } { + Ok(caps) => caps, + Err(e) => { + log::error!("get_physical_device_surface_capabilities: {e}"); + return None; + } + } + }; + + // If image count is 0, the support number of images is unlimited. + let max_image_count = if caps.max_image_count == 0 { + !0 + } else { + caps.max_image_count + }; + + // `0xFFFFFFFF` indicates that the extent depends on the created swapchain. + let current_extent = if caps.current_extent.width != !0 && caps.current_extent.height != !0 + { + Some(wgt::Extent3d { + width: caps.current_extent.width, + height: caps.current_extent.height, + depth_or_array_layers: 1, + }) + } else { + None + }; + + let raw_present_modes = { + profiling::scope!("vkGetPhysicalDeviceSurfacePresentModesKHR"); + match unsafe { + self.functor + .get_physical_device_surface_present_modes(adapter.raw, self.raw) + } { + Ok(present_modes) => present_modes, + Err(e) => { + log::error!("get_physical_device_surface_present_modes: {e}"); + // Per definition of `SurfaceCapabilities`, there must be at least one present mode. + return None; + } + } + }; + + let raw_surface_formats = { + profiling::scope!("vkGetPhysicalDeviceSurfaceFormatsKHR"); + match unsafe { + self.functor + .get_physical_device_surface_formats(adapter.raw, self.raw) + } { + Ok(formats) => formats, + Err(e) => { + log::error!("get_physical_device_surface_formats: {e}"); + // Per definition of `SurfaceCapabilities`, there must be at least one present format. + return None; + } + } + }; + + let formats = raw_surface_formats + .into_iter() + .filter_map(conv::map_vk_surface_formats) + .collect(); + Some(crate::SurfaceCapabilities { + formats, + // TODO: Right now we're always truncating the swap chain + // (presumably - we're actually setting the min image count which isn't necessarily the swap chain size) + // Instead, we should use extensions when available to wait in present. + // See https://github.com/gfx-rs/wgpu/issues/2869 + maximum_frame_latency: (caps.min_image_count - 1)..=(max_image_count - 1), // Note this can't underflow since both `min_image_count` is at least one and we already patched `max_image_count`. + current_extent, + usage: conv::map_vk_image_usage(caps.supported_usage_flags), + present_modes: raw_present_modes + .into_iter() + .flat_map(conv::map_vk_present_mode) + .collect(), + composite_alpha_modes: conv::map_vk_composite_alpha(caps.supported_composite_alpha), + }) + } + + unsafe fn create_swapchain( + &self, + device: &crate::vulkan::Device, + config: &crate::SurfaceConfiguration, + provided_old_swapchain: Option>, + ) -> Result, crate::SurfaceError> { + profiling::scope!("Device::create_swapchain"); + let functor = khr::swapchain::Device::new(&self.instance.raw, &device.shared.raw); + + let old_swapchain = match provided_old_swapchain { + Some(osc) => osc.as_any().downcast_ref::().unwrap().raw, + None => vk::SwapchainKHR::null(), + }; + + let color_space = if config.format == wgt::TextureFormat::Rgba16Float { + // Enable wide color gamut mode + // Vulkan swapchain for Android only supports DISPLAY_P3_NONLINEAR_EXT and EXTENDED_SRGB_LINEAR_EXT + vk::ColorSpaceKHR::EXTENDED_SRGB_LINEAR_EXT + } else { + vk::ColorSpaceKHR::SRGB_NONLINEAR + }; + + let original_format = device.shared.private_caps.map_texture_format(config.format); + let mut raw_flags = vk::SwapchainCreateFlagsKHR::empty(); + let mut raw_view_formats: Vec = vec![]; + if !config.view_formats.is_empty() { + raw_flags |= vk::SwapchainCreateFlagsKHR::MUTABLE_FORMAT; + raw_view_formats = config + .view_formats + .iter() + .map(|f| device.shared.private_caps.map_texture_format(*f)) + .collect(); + raw_view_formats.push(original_format); + } + + let mut info = vk::SwapchainCreateInfoKHR::default() + .flags(raw_flags) + .surface(self.raw) + .min_image_count(config.maximum_frame_latency + 1) // TODO: https://github.com/gfx-rs/wgpu/issues/2869 + .image_format(original_format) + .image_color_space(color_space) + .image_extent(vk::Extent2D { + width: config.extent.width, + height: config.extent.height, + }) + .image_array_layers(config.extent.depth_or_array_layers) + .image_usage(conv::map_texture_usage(config.usage)) + .image_sharing_mode(vk::SharingMode::EXCLUSIVE) + .pre_transform(vk::SurfaceTransformFlagsKHR::IDENTITY) + .composite_alpha(conv::map_composite_alpha_mode(config.composite_alpha_mode)) + .present_mode(conv::map_present_mode(config.present_mode)) + .clipped(true) + .old_swapchain(old_swapchain); + + let mut format_list_info = vk::ImageFormatListCreateInfo::default(); + if !raw_view_formats.is_empty() { + format_list_info = format_list_info.view_formats(&raw_view_formats); + info = info.push_next(&mut format_list_info); + } + + let result = { + profiling::scope!("vkCreateSwapchainKHR"); + unsafe { functor.create_swapchain(&info, None) } + }; + + // doing this before bailing out with error + if old_swapchain != vk::SwapchainKHR::null() { + unsafe { functor.destroy_swapchain(old_swapchain, None) } + } + + let raw = match result { + Ok(swapchain) => swapchain, + Err(error) => { + return Err(match error { + vk::Result::ERROR_SURFACE_LOST_KHR + | vk::Result::ERROR_INITIALIZATION_FAILED => crate::SurfaceError::Lost, + vk::Result::ERROR_NATIVE_WINDOW_IN_USE_KHR => { + crate::SurfaceError::Other("Native window is in use") + } + // We don't use VK_EXT_image_compression_control + // VK_ERROR_COMPRESSION_EXHAUSTED_EXT + other => map_host_device_oom_and_lost_err(other).into(), + }); + } + }; + + let images = unsafe { functor.get_swapchain_images(raw) } + .map_err(crate::vulkan::map_host_device_oom_err)?; + + let fence = unsafe { + device + .shared + .raw + .create_fence(&vk::FenceCreateInfo::default(), None) + .map_err(crate::vulkan::map_host_device_oom_err)? + }; + + // NOTE: It's important that we define the same number of acquire/present semaphores + // as we will need to index into them with the image index. + let acquire_semaphores = (0..images.len()) + .map(|i| { + SwapchainAcquireSemaphore::new(&device.shared, i) + .map(Mutex::new) + .map(Arc::new) + }) + .collect::, _>>()?; + + let present_semaphores = (0..images.len()) + .map(|i| Arc::new(Mutex::new(SwapchainPresentSemaphores::new(i)))) + .collect::>(); + + Ok(Box::new(NativeSwapchain { + raw, + functor, + device: Arc::clone(&device.shared), + images, + fence, + config: config.clone(), + acquire_semaphores, + next_acquire_index: 0, + present_semaphores, + next_present_time: None, + })) + } + + fn as_any(&self) -> &dyn Any { + self + } +} + +pub(crate) struct NativeSwapchain { + raw: vk::SwapchainKHR, + functor: khr::swapchain::Device, + device: Arc, + images: Vec, + /// Fence used to wait on the acquired image. + fence: vk::Fence, + config: crate::SurfaceConfiguration, + + /// Semaphores used between image acquisition and the first submission + /// that uses that image. This is indexed using [`next_acquire_index`]. + /// + /// Because we need to provide this to [`vkAcquireNextImageKHR`], we haven't + /// received the swapchain image index for the frame yet, so we cannot use + /// that to index it. + /// + /// Before we pass this to [`vkAcquireNextImageKHR`], we ensure that we wait on + /// the submission indicated by [`previously_used_submission_index`]. This ensures + /// the semaphore is no longer in use before we use it. + /// + /// [`next_acquire_index`]: NativeSwapchain::next_acquire_index + /// [`vkAcquireNextImageKHR`]: https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#vkAcquireNextImageKHR + /// [`previously_used_submission_index`]: SwapchainAcquireSemaphore::previously_used_submission_index + acquire_semaphores: Vec>>, + /// The index of the next acquire semaphore to use. + /// + /// This is incremented each time we acquire a new image, and wraps around + /// to 0 when it reaches the end of [`acquire_semaphores`]. + /// + /// [`acquire_semaphores`]: NativeSwapchain::acquire_semaphores + next_acquire_index: usize, + + /// Semaphore sets used between all submissions that write to an image and + /// the presentation of that image. + /// + /// This is indexed by the swapchain image index returned by + /// [`vkAcquireNextImageKHR`]. + /// + /// We know it is safe to use these semaphores because use them + /// _after_ the acquire semaphore. Because the acquire semaphore + /// has been signaled, the previous presentation using that image + /// is known-finished, so this semaphore is no longer in use. + /// + /// [`vkAcquireNextImageKHR`]: https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#vkAcquireNextImageKHR + present_semaphores: Vec>>, + + /// The present timing information which will be set in the next call to [`present()`](crate::Queue::present()). + /// + /// # Safety + /// + /// This must only be set if [`wgt::Features::VULKAN_GOOGLE_DISPLAY_TIMING`] is enabled, and + /// so the VK_GOOGLE_display_timing extension is present. + next_present_time: Option, +} + +impl Swapchain for NativeSwapchain { + unsafe fn release_resources(&mut self, device: &crate::vulkan::Device) { + profiling::scope!("Swapchain::release_resources"); + { + profiling::scope!("vkDeviceWaitIdle"); + // We need to also wait until all presentation work is done. Because there is no way to portably wait until + // the presentation work is done, we are forced to wait until the device is idle. + let _ = unsafe { + device + .shared + .raw + .device_wait_idle() + .map_err(map_host_device_oom_and_lost_err) + }; + }; + + unsafe { device.shared.raw.destroy_fence(self.fence, None) } + + // We cannot take this by value, as the function returns `self`. + for semaphore in self.acquire_semaphores.drain(..) { + let arc_removed = Arc::into_inner(semaphore).expect( + "Trying to destroy a SwapchainAcquireSemaphore that is still in use by a SurfaceTexture", + ); + let mutex_removed = arc_removed.into_inner(); + + unsafe { mutex_removed.destroy(&device.shared.raw) }; + } + + for semaphore in self.present_semaphores.drain(..) { + let arc_removed = Arc::into_inner(semaphore).expect( + "Trying to destroy a SwapchainPresentSemaphores that is still in use by a SurfaceTexture", + ); + let mutex_removed = arc_removed.into_inner(); + + unsafe { mutex_removed.destroy(&device.shared.raw) }; + } + } + + unsafe fn delete_swapchain(self: Box) { + unsafe { self.functor.destroy_swapchain(self.raw, None) }; + } + + unsafe fn acquire( + &mut self, + timeout: Option, + fence: &crate::vulkan::Fence, + ) -> Result>, crate::SurfaceError> + { + let mut timeout_ns = match timeout { + Some(duration) => duration.as_nanos() as u64, + None => u64::MAX, + }; + + // AcquireNextImageKHR on Android (prior to Android 11) doesn't support timeouts + // and will also log verbose warnings if tying to use a timeout. + // + // Android 10 implementation for reference: + // https://android.googlesource.com/platform/frameworks/native/+/refs/tags/android-mainline-10.0.0_r13/vulkan/libvulkan/swapchain.cpp#1426 + // Android 11 implementation for reference: + // https://android.googlesource.com/platform/frameworks/native/+/refs/tags/android-mainline-11.0.0_r45/vulkan/libvulkan/swapchain.cpp#1438 + // + // Android 11 corresponds to an SDK_INT/ro.build.version.sdk of 30 + if cfg!(target_os = "android") && self.device.instance.android_sdk_version < 30 { + timeout_ns = u64::MAX; + } + + let acquire_semaphore_arc = self.get_acquire_semaphore(); + // Nothing should be using this, so we don't block, but panic if we fail to lock. + let acquire_semaphore_guard = acquire_semaphore_arc + .try_lock() + .expect("Failed to lock a SwapchainSemaphores."); + + // Wait for all commands writing to the previously acquired image to + // complete. + // + // Almost all the steps in the usual acquire-draw-present flow are + // asynchronous: they get something started on the presentation engine + // or the GPU, but on the CPU, control returns immediately. Without some + // sort of intervention, the CPU could crank out frames much faster than + // the presentation engine can display them. + // + // This is the intervention: if any submissions drew on this image, and + // thus waited for `locked_swapchain_semaphores.acquire`, wait for all + // of them to finish, thus ensuring that it's okay to pass `acquire` to + // `vkAcquireNextImageKHR` again. + self.device.wait_for_fence( + fence, + acquire_semaphore_guard.previously_used_submission_index, + timeout_ns, + )?; + + // will block if no image is available + let (index, suboptimal) = match unsafe { + profiling::scope!("vkAcquireNextImageKHR"); + self.functor.acquire_next_image( + self.raw, + timeout_ns, + acquire_semaphore_guard.acquire, + self.fence, + ) + } { + // We treat `VK_SUBOPTIMAL_KHR` as `VK_SUCCESS` on Android. + // See the comment in `Queue::present`. + #[cfg(target_os = "android")] + Ok((index, _)) => (index, false), + #[cfg(not(target_os = "android"))] + Ok(pair) => pair, + Err(error) => { + return match error { + vk::Result::TIMEOUT => Ok(None), + vk::Result::NOT_READY | vk::Result::ERROR_OUT_OF_DATE_KHR => { + Err(crate::SurfaceError::Outdated) + } + vk::Result::ERROR_SURFACE_LOST_KHR => Err(crate::SurfaceError::Lost), + // We don't use VK_EXT_full_screen_exclusive + // VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT + other => Err(map_host_device_oom_and_lost_err(other).into()), + }; + } + }; + + // Wait for the image was acquired to be fully ready to be rendered too. + // + // This wait is very important on Windows to avoid bad frame pacing on + // Windows where the Vulkan driver is using a DXGI swapchain. See + // https://github.com/gfx-rs/wgpu/issues/8310 and + // https://github.com/gfx-rs/wgpu/issues/8354 for more details. + // + // On other platforms, this wait may serve to slightly decrease frame + // latency, depending on how the platform implements waiting within + // acquire. + unsafe { + self.device + .raw + .wait_for_fences(&[self.fence], false, timeout_ns) + .map_err(map_host_device_oom_and_lost_err)?; + + self.device + .raw + .reset_fences(&[self.fence]) + .map_err(map_host_device_oom_and_lost_err)?; + } + + drop(acquire_semaphore_guard); + // We only advance the surface semaphores if we successfully acquired an image, otherwise + // we should try to re-acquire using the same semaphores. + self.advance_acquire_semaphore(); + + let present_semaphore_arc = self.get_present_semaphores(index); + + // special case for Intel Vulkan returning bizarre values (ugh) + if self.device.vendor_id == crate::auxil::db::intel::VENDOR && index > 0x100 { + return Err(crate::SurfaceError::Outdated); + } + + let identity = self.device.texture_identity_factory.next(); + + let texture = crate::vulkan::SurfaceTexture { + index, + texture: crate::vulkan::Texture { + raw: self.images[index as usize], + drop_guard: None, + block: None, + external_memory: None, + format: self.config.format, + copy_size: crate::CopyExtent { + width: self.config.extent.width, + height: self.config.extent.height, + depth: 1, + }, + identity, + }, + metadata: Box::new(NativeSurfaceTextureMetadata { + acquire_semaphores: acquire_semaphore_arc, + present_semaphores: present_semaphore_arc, + }), + }; + Ok(Some(crate::AcquiredSurfaceTexture { + texture, + suboptimal, + })) + } + + unsafe fn discard_texture( + &mut self, + _texture: crate::vulkan::SurfaceTexture, + ) -> Result<(), crate::SurfaceError> { + // TODO: Current implementation no-ops + Ok(()) + } + + unsafe fn present( + &mut self, + queue: &crate::vulkan::Queue, + texture: crate::vulkan::SurfaceTexture, + ) -> Result<(), crate::SurfaceError> { + let metadata = texture + .metadata + .as_any() + .downcast_ref::() + .unwrap(); + let mut acquire_semaphore = metadata.acquire_semaphores.lock(); + let mut present_semaphores = metadata.present_semaphores.lock(); + + let wait_semaphores = present_semaphores.get_present_wait_semaphores(); + + // Reset the acquire and present semaphores internal state + // to be ready for the next frame. + // + // We do this before the actual call to present to ensure that + // even if this method errors and early outs, we have reset + // the state for next frame. + acquire_semaphore.end_semaphore_usage(); + present_semaphores.end_semaphore_usage(); + + drop(acquire_semaphore); + + let swapchains = [self.raw]; + let image_indices = [texture.index]; + let vk_info = vk::PresentInfoKHR::default() + .swapchains(&swapchains) + .image_indices(&image_indices) + .wait_semaphores(&wait_semaphores); + + let mut display_timing; + let present_times; + let vk_info = if let Some(present_time) = self.next_present_time.take() { + debug_assert!( + self.device + .features + .contains(wgt::Features::VULKAN_GOOGLE_DISPLAY_TIMING), + "`next_present_time` should only be set if `VULKAN_GOOGLE_DISPLAY_TIMING` is enabled" + ); + present_times = [present_time]; + display_timing = vk::PresentTimesInfoGOOGLE::default().times(&present_times); + // SAFETY: We know that VK_GOOGLE_display_timing is present because of the safety contract on `next_present_time`. + vk_info.push_next(&mut display_timing) + } else { + vk_info + }; + + let suboptimal = { + profiling::scope!("vkQueuePresentKHR"); + unsafe { self.functor.queue_present(queue.raw, &vk_info) }.map_err(|error| { + match error { + vk::Result::ERROR_OUT_OF_DATE_KHR => crate::SurfaceError::Outdated, + vk::Result::ERROR_SURFACE_LOST_KHR => crate::SurfaceError::Lost, + // We don't use VK_EXT_full_screen_exclusive + // VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT + _ => map_host_device_oom_and_lost_err(error).into(), + } + })? + }; + if suboptimal { + // We treat `VK_SUBOPTIMAL_KHR` as `VK_SUCCESS` on Android. + // On Android 10+, libvulkan's `vkQueuePresentKHR` implementation returns `VK_SUBOPTIMAL_KHR` if not doing pre-rotation + // (i.e `VkSwapchainCreateInfoKHR::preTransform` not being equal to the current device orientation). + // This is always the case when the device orientation is anything other than the identity one, as we unconditionally use `VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR`. + #[cfg(not(target_os = "android"))] + log::warn!("Suboptimal present of frame {}", texture.index); + } + Ok(()) + } + + fn as_any(&self) -> &dyn Any { + self + } + + fn as_any_mut(&mut self) -> &mut dyn Any { + self + } +} + +impl NativeSwapchain { + pub(crate) fn as_raw(&self) -> vk::SwapchainKHR { + self.raw + } + + pub fn set_next_present_time(&mut self, present_timing: vk::PresentTimeGOOGLE) { + let features = wgt::Features::VULKAN_GOOGLE_DISPLAY_TIMING; + if self.device.features.contains(features) { + self.next_present_time = Some(present_timing); + } else { + // Ideally we'd use something like `device.required_features` here, but that's in `wgpu-core`, which we are a dependency of + panic!( + concat!( + "Tried to set display timing properties ", + "without the corresponding feature ({:?}) enabled." + ), + features + ); + } + } + + /// Mark the current frame finished, advancing to the next acquire semaphore. + fn advance_acquire_semaphore(&mut self) { + let semaphore_count = self.acquire_semaphores.len(); + self.next_acquire_index = (self.next_acquire_index + 1) % semaphore_count; + } + + /// Get the next acquire semaphore that should be used with this swapchain. + fn get_acquire_semaphore(&self) -> Arc> { + self.acquire_semaphores[self.next_acquire_index].clone() + } + + /// Get the set of present semaphores that should be used with the given image index. + fn get_present_semaphores(&self, index: u32) -> Arc> { + self.present_semaphores[index as usize].clone() + } +} + +/// Semaphore used to acquire a swapchain image. +#[derive(Debug)] +struct SwapchainAcquireSemaphore { + /// A semaphore that is signaled when this image is safe for us to modify. + /// + /// When [`vkAcquireNextImageKHR`] returns the index of the next swapchain + /// image that we should use, that image may actually still be in use by the + /// presentation engine, and is not yet safe to modify. However, that + /// function does accept a semaphore that it will signal when the image is + /// indeed safe to begin messing with. + /// + /// This semaphore is: + /// + /// - waited for by the first queue submission to operate on this image + /// since it was acquired, and + /// + /// - signaled by [`vkAcquireNextImageKHR`] when the acquired image is ready + /// for us to use. + /// + /// [`vkAcquireNextImageKHR`]: https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#vkAcquireNextImageKHR + acquire: vk::Semaphore, + + /// True if the next command submission operating on this image should wait + /// for [`acquire`]. + /// + /// We must wait for `acquire` before drawing to this swapchain image, but + /// because `wgpu-hal` queue submissions are always strongly ordered, only + /// the first submission that works with a swapchain image actually needs to + /// wait. We set this flag when this image is acquired, and clear it the + /// first time it's passed to [`Queue::submit`] as a surface texture. + /// + /// Additionally, semaphores can only be waited on once, so we need to ensure + /// that we only actually pass this semaphore to the first submission that + /// uses that image. + /// + /// [`acquire`]: SwapchainAcquireSemaphore::acquire + /// [`Queue::submit`]: crate::Queue::submit + should_wait_for_acquire: bool, + + /// The fence value of the last command submission that wrote to this image. + /// + /// The next time we try to acquire this image, we'll block until + /// this submission finishes, proving that [`acquire`] is ready to + /// pass to `vkAcquireNextImageKHR` again. + /// + /// [`acquire`]: SwapchainAcquireSemaphore::acquire + previously_used_submission_index: crate::FenceValue, +} + +impl SwapchainAcquireSemaphore { + fn new(device: &DeviceShared, index: usize) -> Result { + Ok(Self { + acquire: device + .new_binary_semaphore(&format!("SwapchainImageSemaphore: Index {index} acquire"))?, + should_wait_for_acquire: true, + previously_used_submission_index: 0, + }) + } + + /// Sets the fence value which the next acquire will wait for. This prevents + /// the semaphore from being used while the previous submission is still in flight. + fn set_used_fence_value(&mut self, value: crate::FenceValue) { + self.previously_used_submission_index = value; + } + + /// Return the semaphore that commands drawing to this image should wait for, if any. + /// + /// This only returns `Some` once per acquisition; see + /// [`SwapchainAcquireSemaphore::should_wait_for_acquire`] for details. + fn get_acquire_wait_semaphore(&mut self) -> Option { + if self.should_wait_for_acquire { + self.should_wait_for_acquire = false; + Some(self.acquire) + } else { + None + } + } + + /// Indicates the cpu-side usage of this semaphore has finished for the frame, + /// so reset internal state to be ready for the next frame. + fn end_semaphore_usage(&mut self) { + // Reset the acquire semaphore, so that the next time we acquire this + // image, we can wait for it again. + self.should_wait_for_acquire = true; + } + + unsafe fn destroy(&self, device: &ash::Device) { + unsafe { + device.destroy_semaphore(self.acquire, None); + } + } +} + +#[derive(Debug)] +struct SwapchainPresentSemaphores { + /// A pool of semaphores for ordering presentation after drawing. + /// + /// The first [`present_index`] semaphores in this vector are: + /// + /// - all waited on by the call to [`vkQueuePresentKHR`] that presents this + /// image, and + /// + /// - each signaled by some [`vkQueueSubmit`] queue submission that draws to + /// this image, when the submission finishes execution. + /// + /// This vector accumulates one semaphore per submission that writes to this + /// image. This is awkward, but hard to avoid: [`vkQueuePresentKHR`] + /// requires a semaphore to order it with respect to drawing commands, and + /// we can't attach new completion semaphores to a command submission after + /// it's been submitted. This means that, at submission time, we must create + /// the semaphore we might need if the caller's next action is to enqueue a + /// presentation of this image. + /// + /// An alternative strategy would be for presentation to enqueue an empty + /// submit, ordered relative to other submits in the usual way, and + /// signaling a single presentation semaphore. But we suspect that submits + /// are usually expensive enough, and semaphores usually cheap enough, that + /// performance-sensitive users will avoid making many submits, so that the + /// cost of accumulated semaphores will usually be less than the cost of an + /// additional submit. + /// + /// Only the first [`present_index`] semaphores in the vector are actually + /// going to be signalled by submitted commands, and need to be waited for + /// by the next present call. Any semaphores beyond that index were created + /// for prior presents and are simply being retained for recycling. + /// + /// [`present_index`]: SwapchainPresentSemaphores::present_index + /// [`vkQueuePresentKHR`]: https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#vkQueuePresentKHR + /// [`vkQueueSubmit`]: https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#vkQueueSubmit + present: Vec, + + /// The number of semaphores in [`present`] to be signalled for this submission. + /// + /// [`present`]: SwapchainPresentSemaphores::present + present_index: usize, + + /// Which image this semaphore set is used for. + frame_index: usize, +} + +impl SwapchainPresentSemaphores { + pub fn new(frame_index: usize) -> Self { + Self { + present: Vec::new(), + present_index: 0, + frame_index, + } + } + + /// Return the semaphore that the next submission that writes to this image should + /// signal when it's done. + /// + /// See [`SwapchainPresentSemaphores::present`] for details. + fn get_submit_signal_semaphore( + &mut self, + device: &DeviceShared, + ) -> Result { + // Try to recycle a semaphore we created for a previous presentation. + let sem = match self.present.get(self.present_index) { + Some(sem) => *sem, + None => { + let sem = device.new_binary_semaphore(&format!( + "SwapchainImageSemaphore: Image {} present semaphore {}", + self.frame_index, self.present_index + ))?; + self.present.push(sem); + sem + } + }; + + self.present_index += 1; + + Ok(sem) + } + + /// Indicates the cpu-side usage of this semaphore has finished for the frame, + /// so reset internal state to be ready for the next frame. + fn end_semaphore_usage(&mut self) { + // Reset the index to 0, so that the next time we get a semaphore, we + // start from the beginning of the list. + self.present_index = 0; + } + + /// Return the semaphores that a presentation of this image should wait on. + /// + /// Return a slice of semaphores that the call to [`vkQueueSubmit`] that + /// ends this image's acquisition should wait for. See + /// [`SwapchainPresentSemaphores::present`] for details. + /// + /// Reset `self` to be ready for the next acquisition cycle. + /// + /// [`vkQueueSubmit`]: https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#vkQueueSubmit + fn get_present_wait_semaphores(&mut self) -> Vec { + self.present[0..self.present_index].to_vec() + } + + unsafe fn destroy(&self, device: &ash::Device) { + unsafe { + for sem in &self.present { + device.destroy_semaphore(*sem, None); + } + } + } +} + +#[derive(Debug)] +struct NativeSurfaceTextureMetadata { + acquire_semaphores: Arc>, + present_semaphores: Arc>, +} + +impl SurfaceTextureMetadata for NativeSurfaceTextureMetadata { + fn get_semaphore_guard(&self) -> Box { + Box::new(NativeSwapchainSubmissionSemaphoreGuard { + acquire_semaphore_guard: self + .acquire_semaphores + .try_lock() + .expect("Failed to lock surface acquire semaphore"), + present_semaphores_guard: self + .present_semaphores + .try_lock() + .expect("Failed to lock surface present semaphores"), + }) + } + + fn as_any(&self) -> &dyn Any { + self + } +} + +struct NativeSwapchainSubmissionSemaphoreGuard<'a> { + acquire_semaphore_guard: MutexGuard<'a, SwapchainAcquireSemaphore>, + present_semaphores_guard: MutexGuard<'a, SwapchainPresentSemaphores>, +} + +impl<'a> SwapchainSubmissionSemaphoreGuard for NativeSwapchainSubmissionSemaphoreGuard<'a> { + fn set_used_fence_value(&mut self, value: u64) { + self.acquire_semaphore_guard.set_used_fence_value(value); + } + + fn get_acquire_wait_semaphore(&mut self) -> Option { + self.acquire_semaphore_guard + .get_acquire_wait_semaphore() + .map(SemaphoreType::Binary) + } + + fn get_submit_signal_semaphore( + &mut self, + device: &DeviceShared, + ) -> Result { + self.present_semaphores_guard + .get_submit_signal_semaphore(device) + .map(SemaphoreType::Binary) + } +}