diff options
Diffstat (limited to 'src/sync/fence.rs')
-rw-r--r-- | src/sync/fence.rs | 1851 |
1 files changed, 1548 insertions, 303 deletions
diff --git a/src/sync/fence.rs b/src/sync/fence.rs index 208573a..05968f2 100644 --- a/src/sync/fence.rs +++ b/src/sync/fence.rs @@ -7,148 +7,293 @@ // notice may not be copied, modified, or distributed except // according to those terms. -use crate::check_errors; -use crate::device::Device; -use crate::device::DeviceOwned; -use crate::Error; -use crate::OomError; -use crate::SafeDeref; -use crate::Success; -use crate::VulkanObject; +//! A fence provides synchronization between the device and the host, or between an external source +//! and the host. + +use crate::{ + device::{Device, DeviceOwned, Queue}, + macros::{impl_id_counter, vulkan_bitflags, vulkan_bitflags_enum}, + OomError, RequirementNotMet, RequiresOneOf, Version, VulkanError, VulkanObject, +}; +use parking_lot::{Mutex, MutexGuard}; use smallvec::SmallVec; -use std::error; -use std::fmt; -use std::mem::MaybeUninit; -use std::ptr; -use std::sync::atomic::AtomicBool; -use std::sync::atomic::Ordering; -use std::sync::Arc; -use std::time::Duration; - -/// A fence is used to know when a command buffer submission has finished its execution. +#[cfg(unix)] +use std::fs::File; +use std::{ + error::Error, + fmt::{Display, Error as FmtError, Formatter}, + future::Future, + mem::MaybeUninit, + num::NonZeroU64, + pin::Pin, + ptr, + sync::{Arc, Weak}, + task::{Context, Poll}, + time::Duration, +}; + +/// A two-state synchronization primitive that is signalled by the device and waited on by the host. +/// +/// # Queue-to-host synchronization +/// +/// The primary use of a fence is to know when execution of a queue has reached a particular point. +/// When adding a command to a queue, a fence can be provided with the command, to be signaled +/// when the operation finishes. You can check for a fence's current status by calling +/// `is_signaled`, `wait` or `await` on it. If the fence is found to be signaled, that means that +/// the queue has completed the operation that is associated with the fence, and all operations that +/// were submitted before it have been completed as well. +/// +/// When a queue command accesses a resource, it must be kept alive until the queue command has +/// finished executing, and you may not be allowed to perform certain other operations (or even any) +/// while the resource is in use. By calling `is_signaled`, `wait` or `await`, the queue will be +/// notified when the fence is signaled, so that all resources of the associated queue operation and +/// preceding operations can be released. /// -/// When a command buffer accesses a resource, you have to ensure that the CPU doesn't access -/// the same resource simultaneously (except for concurrent reads). Therefore in order to know -/// when the CPU can access a resource again, a fence has to be used. +/// Because of this, it is highly recommended to call `is_signaled`, `wait` or `await` on your fences. +/// Otherwise, the queue will hold onto resources indefinitely (using up memory) +/// and resource locks will not be released, which may cause errors when submitting future +/// queue operations. It is not strictly necessary to wait for *every* fence, as a fence +/// that was signaled later in the queue will automatically clean up resources associated with +/// earlier fences too. #[derive(Debug)] -pub struct Fence<D = Arc<Device>> -where - D: SafeDeref<Target = Device>, -{ - fence: ash::vk::Fence, - - device: D, +pub struct Fence { + handle: ash::vk::Fence, + device: Arc<Device>, + id: NonZeroU64, + must_put_in_pool: bool, - // If true, we know that the `Fence` is signaled. If false, we don't know. - // This variable exists so that we don't need to call `vkGetFenceStatus` or `vkWaitForFences` - // multiple times. - signaled: AtomicBool, + export_handle_types: ExternalFenceHandleTypes, - // Indicates whether this fence was taken from the fence pool. - // If true, will be put back into fence pool on drop. - must_put_in_pool: bool, + state: Mutex<FenceState>, } -impl<D> Fence<D> -where - D: SafeDeref<Target = Device>, -{ +impl Fence { + /// Creates a new `Fence`. + #[inline] + pub fn new(device: Arc<Device>, create_info: FenceCreateInfo) -> Result<Fence, FenceError> { + Self::validate_new(&device, &create_info)?; + + unsafe { Ok(Self::new_unchecked(device, create_info)?) } + } + + fn validate_new(device: &Device, create_info: &FenceCreateInfo) -> Result<(), FenceError> { + let &FenceCreateInfo { + signaled: _, + export_handle_types, + _ne: _, + } = create_info; + + if !export_handle_types.is_empty() { + if !(device.api_version() >= Version::V1_1 + || device.enabled_extensions().khr_external_fence) + { + return Err(FenceError::RequirementNotMet { + required_for: "`create_info.export_handle_types` is not empty", + requires_one_of: RequiresOneOf { + api_version: Some(Version::V1_1), + device_extensions: &["khr_external_fence"], + ..Default::default() + }, + }); + } + + // VUID-VkExportFenceCreateInfo-handleTypes-01446 + export_handle_types.validate_device(device)?; + + // VUID-VkExportFenceCreateInfo-handleTypes-01446 + for handle_type in export_handle_types.into_iter() { + let external_fence_properties = unsafe { + device + .physical_device() + .external_fence_properties_unchecked(ExternalFenceInfo::handle_type( + handle_type, + )) + }; + + if !external_fence_properties.exportable { + return Err(FenceError::HandleTypeNotExportable { handle_type }); + } + + if !external_fence_properties + .compatible_handle_types + .contains(export_handle_types) + { + return Err(FenceError::ExportHandleTypesNotCompatible); + } + } + } + + Ok(()) + } + + #[cfg_attr(not(feature = "document_unchecked"), doc(hidden))] + #[inline] + pub unsafe fn new_unchecked( + device: Arc<Device>, + create_info: FenceCreateInfo, + ) -> Result<Fence, VulkanError> { + let FenceCreateInfo { + signaled, + export_handle_types, + _ne: _, + } = create_info; + + let mut flags = ash::vk::FenceCreateFlags::empty(); + + if signaled { + flags |= ash::vk::FenceCreateFlags::SIGNALED; + } + + let mut create_info_vk = ash::vk::FenceCreateInfo { + flags, + ..Default::default() + }; + let mut export_fence_create_info_vk = None; + + if !export_handle_types.is_empty() { + let _ = export_fence_create_info_vk.insert(ash::vk::ExportFenceCreateInfo { + handle_types: export_handle_types.into(), + ..Default::default() + }); + } + + if let Some(info) = export_fence_create_info_vk.as_mut() { + info.p_next = create_info_vk.p_next; + create_info_vk.p_next = info as *const _ as *const _; + } + + let handle = { + let fns = device.fns(); + let mut output = MaybeUninit::uninit(); + (fns.v1_0.create_fence)( + device.handle(), + &create_info_vk, + ptr::null(), + output.as_mut_ptr(), + ) + .result() + .map_err(VulkanError::from)?; + + output.assume_init() + }; + + Ok(Fence { + handle, + device, + id: Self::next_id(), + must_put_in_pool: false, + export_handle_types, + state: Mutex::new(FenceState { + is_signaled: signaled, + ..Default::default() + }), + }) + } + /// Takes a fence from the vulkano-provided fence pool. - /// If the pool is empty, a new fence will be allocated. + /// If the pool is empty, a new fence will be created. /// Upon `drop`, the fence is put back into the pool. /// /// For most applications, using the fence pool should be preferred, /// in order to avoid creating new fences every frame. - pub fn from_pool(device: D) -> Result<Fence<D>, OomError> { - let maybe_raw_fence = device.fence_pool().lock().unwrap().pop(); - match maybe_raw_fence { - Some(raw_fence) => { + #[inline] + pub fn from_pool(device: Arc<Device>) -> Result<Fence, FenceError> { + let handle = device.fence_pool().lock().pop(); + let fence = match handle { + Some(handle) => { unsafe { // Make sure the fence isn't signaled let fns = device.fns(); - check_errors( - fns.v1_0 - .reset_fences(device.internal_object(), 1, &raw_fence), - )?; + (fns.v1_0.reset_fences)(device.handle(), 1, &handle) + .result() + .map_err(VulkanError::from)?; } - Ok(Fence { - fence: raw_fence, - device: device, - signaled: AtomicBool::new(false), + + Fence { + handle, + device, + id: Self::next_id(), must_put_in_pool: true, - }) + export_handle_types: ExternalFenceHandleTypes::empty(), + state: Mutex::new(Default::default()), + } } None => { // Pool is empty, alloc new fence - Fence::alloc_impl(device, false, true) + let mut fence = Fence::new(device, FenceCreateInfo::default())?; + fence.must_put_in_pool = true; + fence } - } - } + }; - /// Builds a new fence. - #[inline] - pub fn alloc(device: D) -> Result<Fence<D>, OomError> { - Fence::alloc_impl(device, false, false) + Ok(fence) } - /// Builds a new fence in signaled state. + /// Creates a new `Fence` from a raw object handle. + /// + /// # Safety + /// + /// - `handle` must be a valid Vulkan object handle created from `device`. + /// - `create_info` must match the info used to create the object. #[inline] - pub fn alloc_signaled(device: D) -> Result<Fence<D>, OomError> { - Fence::alloc_impl(device, true, false) - } + pub unsafe fn from_handle( + device: Arc<Device>, + handle: ash::vk::Fence, + create_info: FenceCreateInfo, + ) -> Fence { + let FenceCreateInfo { + signaled, + export_handle_types, + _ne: _, + } = create_info; - fn alloc_impl(device: D, signaled: bool, must_put_in_pool: bool) -> Result<Fence<D>, OomError> { - let fence = unsafe { - let infos = ash::vk::FenceCreateInfo { - flags: if signaled { - ash::vk::FenceCreateFlags::SIGNALED - } else { - ash::vk::FenceCreateFlags::empty() - }, + Fence { + handle, + device, + id: Self::next_id(), + must_put_in_pool: false, + export_handle_types, + state: Mutex::new(FenceState { + is_signaled: signaled, ..Default::default() - }; - - let fns = device.fns(); - let mut output = MaybeUninit::uninit(); - check_errors(fns.v1_0.create_fence( - device.internal_object(), - &infos, - ptr::null(), - output.as_mut_ptr(), - ))?; - output.assume_init() - }; - - Ok(Fence { - fence: fence, - device: device, - signaled: AtomicBool::new(signaled), - must_put_in_pool: must_put_in_pool, - }) + }), + } } /// Returns true if the fence is signaled. #[inline] - pub fn ready(&self) -> Result<bool, OomError> { - unsafe { - if self.signaled.load(Ordering::Relaxed) { - return Ok(true); + pub fn is_signaled(&self) -> Result<bool, OomError> { + let queue_to_signal = { + let mut state = self.state(); + + // If the fence is already signaled, or it's unsignaled but there's no queue that + // could signal it, return the currently known value. + if let Some(is_signaled) = state.is_signaled() { + return Ok(is_signaled); } - let fns = self.device.fns(); - let result = check_errors( - fns.v1_0 - .get_fence_status(self.device.internal_object(), self.fence), - )?; + // We must ask Vulkan for the state. + let result = unsafe { + let fns = self.device.fns(); + (fns.v1_0.get_fence_status)(self.device.handle(), self.handle) + }; + match result { - Success::Success => { - self.signaled.store(true, Ordering::Relaxed); - Ok(true) - } - Success::NotReady => Ok(false), - _ => unreachable!(), + ash::vk::Result::SUCCESS => unsafe { state.set_signaled() }, + ash::vk::Result::NOT_READY => return Ok(false), + err => return Err(VulkanError::from(err).into()), + } + }; + + // If we have a queue that we need to signal our status to, + // do so now after the state lock is dropped, to avoid deadlocks. + if let Some(queue) = queue_to_signal { + unsafe { + queue.with(|mut q| q.fence_signaled(self)); } } + + Ok(true) } /// Waits until the fence is signaled, or at least until the timeout duration has elapsed. @@ -156,281 +301,1356 @@ where /// Returns `Ok` if the fence is now signaled. Returns `Err` if the timeout was reached instead. /// /// If you pass a duration of 0, then the function will return without blocking. - pub fn wait(&self, timeout: Option<Duration>) -> Result<(), FenceWaitError> { - unsafe { - if self.signaled.load(Ordering::Relaxed) { + pub fn wait(&self, timeout: Option<Duration>) -> Result<(), FenceError> { + let queue_to_signal = { + let mut state = self.state.lock(); + + // If the fence is already signaled, we don't need to wait. + if state.is_signaled().unwrap_or(false) { return Ok(()); } - let timeout_ns = if let Some(timeout) = timeout { + let timeout_ns = timeout.map_or(u64::MAX, |timeout| { timeout .as_secs() .saturating_mul(1_000_000_000) .saturating_add(timeout.subsec_nanos() as u64) - } else { - u64::MAX + }); + + let result = unsafe { + let fns = self.device.fns(); + (fns.v1_0.wait_for_fences)( + self.device.handle(), + 1, + &self.handle, + ash::vk::TRUE, + timeout_ns, + ) }; - let fns = self.device.fns(); - let r = check_errors(fns.v1_0.wait_for_fences( - self.device.internal_object(), - 1, - &self.fence, - ash::vk::TRUE, - timeout_ns, - ))?; - - match r { - Success::Success => { - self.signaled.store(true, Ordering::Relaxed); - Ok(()) - } - Success::Timeout => Err(FenceWaitError::Timeout), - _ => unreachable!(), + match result { + ash::vk::Result::SUCCESS => unsafe { state.set_signaled() }, + ash::vk::Result::TIMEOUT => return Err(FenceError::Timeout), + err => return Err(VulkanError::from(err).into()), + } + }; + + // If we have a queue that we need to signal our status to, + // do so now after the state lock is dropped, to avoid deadlocks. + if let Some(queue) = queue_to_signal { + unsafe { + queue.with(|mut q| q.fence_signaled(self)); } } + + Ok(()) } /// Waits for multiple fences at once. /// - /// # Panic + /// # Panics /// - /// Panics if not all fences belong to the same device. - pub fn multi_wait<'a, I>(iter: I, timeout: Option<Duration>) -> Result<(), FenceWaitError> - where - I: IntoIterator<Item = &'a Fence<D>>, - D: 'a, - { - let mut device: Option<&Device> = None; + /// - Panics if not all fences belong to the same device. + pub fn multi_wait<'a>( + fences: impl IntoIterator<Item = &'a Fence>, + timeout: Option<Duration>, + ) -> Result<(), FenceError> { + let fences: SmallVec<[_; 8]> = fences.into_iter().collect(); + Self::validate_multi_wait(&fences, timeout)?; - let fences: SmallVec<[ash::vk::Fence; 8]> = iter - .into_iter() - .filter_map(|fence| { - match &mut device { - dev @ &mut None => *dev = Some(&*fence.device), - &mut Some(ref dev) - if &**dev as *const Device == &*fence.device as *const Device => {} - _ => panic!( - "Tried to wait for multiple fences that didn't belong to the \ - same device" - ), - }; + unsafe { Self::multi_wait_unchecked(fences, timeout) } + } + + fn validate_multi_wait( + fences: &[&Fence], + _timeout: Option<Duration>, + ) -> Result<(), FenceError> { + if fences.is_empty() { + return Ok(()); + } + + let device = &fences[0].device; + + for fence in fences { + // VUID-vkWaitForFences-pFences-parent + assert_eq!(device, &fence.device); + } + + Ok(()) + } + + #[cfg_attr(not(feature = "document_unchecked"), doc(hidden))] + pub unsafe fn multi_wait_unchecked<'a>( + fences: impl IntoIterator<Item = &'a Fence>, + timeout: Option<Duration>, + ) -> Result<(), FenceError> { + let queues_to_signal: SmallVec<[_; 8]> = { + let iter = fences.into_iter(); + let mut fences_vk: SmallVec<[_; 8]> = SmallVec::new(); + let mut fences: SmallVec<[_; 8]> = SmallVec::new(); + let mut states: SmallVec<[_; 8]> = SmallVec::new(); + + for fence in iter { + let state = fence.state.lock(); - if fence.signaled.load(Ordering::Relaxed) { - None - } else { - Some(fence.fence) + // Skip the fences that are already signaled. + if !state.is_signaled().unwrap_or(false) { + fences_vk.push(fence.handle); + fences.push(fence); + states.push(state); } - }) - .collect(); + } - let timeout_ns = if let Some(timeout) = timeout { - timeout - .as_secs() - .saturating_mul(1_000_000_000) - .saturating_add(timeout.subsec_nanos() as u64) - } else { - u64::MAX - }; + // VUID-vkWaitForFences-fenceCount-arraylength + // If there are no fences, or all the fences are signaled, we don't need to wait. + if fences_vk.is_empty() { + return Ok(()); + } - let r = if let Some(device) = device { - unsafe { + let device = &fences[0].device; + let timeout_ns = timeout.map_or(u64::MAX, |timeout| { + timeout + .as_secs() + .saturating_mul(1_000_000_000) + .saturating_add(timeout.subsec_nanos() as u64) + }); + + let result = { let fns = device.fns(); - check_errors(fns.v1_0.wait_for_fences( - device.internal_object(), - fences.len() as u32, - fences.as_ptr(), - ash::vk::TRUE, + (fns.v1_0.wait_for_fences)( + device.handle(), + fences_vk.len() as u32, + fences_vk.as_ptr(), + ash::vk::TRUE, // TODO: let the user choose false here? timeout_ns, - ))? + ) + }; + + match result { + ash::vk::Result::SUCCESS => fences + .into_iter() + .zip(&mut states) + .filter_map(|(fence, state)| state.set_signaled().map(|state| (state, fence))) + .collect(), + ash::vk::Result::TIMEOUT => return Err(FenceError::Timeout), + err => return Err(VulkanError::from(err).into()), } - } else { - return Ok(()); }; - match r { - Success::Success => Ok(()), - Success::Timeout => Err(FenceWaitError::Timeout), - _ => unreachable!(), + // If we have queues that we need to signal our status to, + // do so now after the state locks are dropped, to avoid deadlocks. + for (queue, fence) in queues_to_signal { + queue.with(|mut q| q.fence_signaled(fence)); } + + Ok(()) } /// Resets the fence. - // This function takes a `&mut self` because the Vulkan API requires that the fence be - // externally synchronized. + /// + /// The fence must not be in use by a queue operation. #[inline] - pub fn reset(&mut self) -> Result<(), OomError> { - unsafe { - let fns = self.device.fns(); - check_errors( - fns.v1_0 - .reset_fences(self.device.internal_object(), 1, &self.fence), - )?; - self.signaled.store(false, Ordering::Relaxed); - Ok(()) + pub fn reset(&self) -> Result<(), FenceError> { + let mut state = self.state.lock(); + self.validate_reset(&state)?; + + unsafe { Ok(self.reset_unchecked_locked(&mut state)?) } + } + + fn validate_reset(&self, state: &FenceState) -> Result<(), FenceError> { + // VUID-vkResetFences-pFences-01123 + if state.is_in_queue() { + return Err(FenceError::InQueue); } + + Ok(()) + } + + #[cfg_attr(not(feature = "document_unchecked"), doc(hidden))] + #[inline] + pub unsafe fn reset_unchecked(&self) -> Result<(), VulkanError> { + let mut state = self.state.lock(); + + self.reset_unchecked_locked(&mut state) + } + + unsafe fn reset_unchecked_locked(&self, state: &mut FenceState) -> Result<(), VulkanError> { + let fns = self.device.fns(); + (fns.v1_0.reset_fences)(self.device.handle(), 1, &self.handle) + .result() + .map_err(VulkanError::from)?; + + state.reset(); + + Ok(()) } /// Resets multiple fences at once. /// - /// # Panic + /// The fences must not be in use by a queue operation. /// - /// - Panics if not all fences belong to the same device. + /// # Panics /// - pub fn multi_reset<'a, I>(iter: I) -> Result<(), OomError> - where - I: IntoIterator<Item = &'a mut Fence<D>>, - D: 'a, - { - let mut device: Option<&Device> = None; - - let fences: SmallVec<[ash::vk::Fence; 8]> = iter + /// - Panics if not all fences belong to the same device. + pub fn multi_reset<'a>(fences: impl IntoIterator<Item = &'a Fence>) -> Result<(), FenceError> { + let (fences, mut states): (SmallVec<[_; 8]>, SmallVec<[_; 8]>) = fences .into_iter() .map(|fence| { - match &mut device { - dev @ &mut None => *dev = Some(&*fence.device), - &mut Some(ref dev) - if &**dev as *const Device == &*fence.device as *const Device => {} - _ => panic!( - "Tried to reset multiple fences that didn't belong to the same \ - device" - ), - }; + let state = fence.state.lock(); + (fence, state) + }) + .unzip(); + Self::validate_multi_reset(&fences, &states)?; + + unsafe { Ok(Self::multi_reset_unchecked_locked(&fences, &mut states)?) } + } + + fn validate_multi_reset( + fences: &[&Fence], + states: &[MutexGuard<'_, FenceState>], + ) -> Result<(), FenceError> { + if fences.is_empty() { + return Ok(()); + } + + let device = &fences[0].device; + + for (fence, state) in fences.iter().zip(states) { + // VUID-vkResetFences-pFences-parent + assert_eq!(device, &fence.device); + + // VUID-vkResetFences-pFences-01123 + if state.is_in_queue() { + return Err(FenceError::InQueue); + } + } + + Ok(()) + } - fence.signaled.store(false, Ordering::Relaxed); - fence.fence + #[cfg_attr(not(feature = "document_unchecked"), doc(hidden))] + pub unsafe fn multi_reset_unchecked<'a>( + fences: impl IntoIterator<Item = &'a Fence>, + ) -> Result<(), VulkanError> { + let (fences, mut states): (SmallVec<[_; 8]>, SmallVec<[_; 8]>) = fences + .into_iter() + .map(|fence| { + let state = fence.state.lock(); + (fence, state) }) - .collect(); + .unzip(); - if let Some(device) = device { - unsafe { - let fns = device.fns(); - check_errors(fns.v1_0.reset_fences( - device.internal_object(), - fences.len() as u32, - fences.as_ptr(), - ))?; + Self::multi_reset_unchecked_locked(&fences, &mut states) + } + + unsafe fn multi_reset_unchecked_locked( + fences: &[&Fence], + states: &mut [MutexGuard<'_, FenceState>], + ) -> Result<(), VulkanError> { + if fences.is_empty() { + return Ok(()); + } + + let device = &fences[0].device; + let fences_vk: SmallVec<[_; 8]> = fences.iter().map(|fence| fence.handle).collect(); + + let fns = device.fns(); + (fns.v1_0.reset_fences)(device.handle(), fences_vk.len() as u32, fences_vk.as_ptr()) + .result() + .map_err(VulkanError::from)?; + + for state in states { + state.reset(); + } + + Ok(()) + } + + /// Exports the fence into a POSIX file descriptor. The caller owns the returned `File`. + /// + /// The [`khr_external_fence_fd`](crate::device::DeviceExtensions::khr_external_fence_fd) + /// extension must be enabled on the device. + #[cfg(unix)] + #[inline] + pub fn export_fd(&self, handle_type: ExternalFenceHandleType) -> Result<File, FenceError> { + let mut state = self.state.lock(); + self.validate_export_fd(handle_type, &state)?; + + unsafe { Ok(self.export_fd_unchecked_locked(handle_type, &mut state)?) } + } + + #[cfg(unix)] + fn validate_export_fd( + &self, + handle_type: ExternalFenceHandleType, + state: &FenceState, + ) -> Result<(), FenceError> { + if !self.device.enabled_extensions().khr_external_fence_fd { + return Err(FenceError::RequirementNotMet { + required_for: "`Fence::export_fd`", + requires_one_of: RequiresOneOf { + device_extensions: &["khr_external_fence_fd"], + ..Default::default() + }, + }); + } + + // VUID-VkFenceGetFdInfoKHR-handleType-parameter + handle_type.validate_device(&self.device)?; + + // VUID-VkFenceGetFdInfoKHR-handleType-01453 + if !self.export_handle_types.intersects(handle_type.into()) { + return Err(FenceError::HandleTypeNotEnabled); + } + + // VUID-VkFenceGetFdInfoKHR-handleType-01454 + if handle_type.has_copy_transference() + && !(state.is_signaled().unwrap_or(false) || state.is_in_queue()) + { + return Err(FenceError::HandleTypeCopyNotSignaled); + } + + // VUID-VkFenceGetFdInfoKHR-fence-01455 + if let Some(imported_handle_type) = state.current_import { + match imported_handle_type { + ImportType::SwapchainAcquire => { + return Err(FenceError::ImportedForSwapchainAcquire) + } + ImportType::ExternalFence(imported_handle_type) => { + let external_fence_properties = unsafe { + self.device + .physical_device() + .external_fence_properties_unchecked(ExternalFenceInfo::handle_type( + handle_type, + )) + }; + + if !external_fence_properties + .export_from_imported_handle_types + .intersects(imported_handle_type.into()) + { + return Err(FenceError::ExportFromImportedNotSupported { + imported_handle_type, + }); + } + } } } + + // VUID-VkFenceGetFdInfoKHR-handleType-01456 + if !matches!( + handle_type, + ExternalFenceHandleType::OpaqueFd | ExternalFenceHandleType::SyncFd + ) { + return Err(FenceError::HandleTypeNotFd); + } + Ok(()) } -} -unsafe impl DeviceOwned for Fence { + #[cfg(unix)] + #[cfg_attr(not(feature = "document_unchecked"), doc(hidden))] #[inline] - fn device(&self) -> &Arc<Device> { - &self.device + pub unsafe fn export_fd_unchecked( + &self, + handle_type: ExternalFenceHandleType, + ) -> Result<File, VulkanError> { + let mut state = self.state.lock(); + self.export_fd_unchecked_locked(handle_type, &mut state) + } + + #[cfg(unix)] + unsafe fn export_fd_unchecked_locked( + &self, + handle_type: ExternalFenceHandleType, + state: &mut FenceState, + ) -> Result<File, VulkanError> { + use std::os::unix::io::FromRawFd; + + let info_vk = ash::vk::FenceGetFdInfoKHR { + fence: self.handle, + handle_type: handle_type.into(), + ..Default::default() + }; + + let mut output = MaybeUninit::uninit(); + let fns = self.device.fns(); + (fns.khr_external_fence_fd.get_fence_fd_khr)( + self.device.handle(), + &info_vk, + output.as_mut_ptr(), + ) + .result() + .map_err(VulkanError::from)?; + + state.export(handle_type); + + Ok(File::from_raw_fd(output.assume_init())) + } + + /// Exports the fence into a Win32 handle. + /// + /// The [`khr_external_fence_win32`](crate::device::DeviceExtensions::khr_external_fence_win32) + /// extension must be enabled on the device. + #[cfg(windows)] + #[inline] + pub fn export_win32_handle( + &self, + handle_type: ExternalFenceHandleType, + ) -> Result<*mut std::ffi::c_void, FenceError> { + let mut state = self.state.lock(); + self.validate_export_win32_handle(handle_type, &state)?; + + unsafe { Ok(self.export_win32_handle_unchecked_locked(handle_type, &mut state)?) } + } + + #[cfg(windows)] + fn validate_export_win32_handle( + &self, + handle_type: ExternalFenceHandleType, + state: &FenceState, + ) -> Result<(), FenceError> { + if !self.device.enabled_extensions().khr_external_fence_win32 { + return Err(FenceError::RequirementNotMet { + required_for: "`Fence::export_win32_handle`", + requires_one_of: RequiresOneOf { + device_extensions: &["khr_external_fence_win32"], + ..Default::default() + }, + }); + } + + // VUID-VkFenceGetWin32HandleInfoKHR-handleType-parameter + handle_type.validate_device(&self.device)?; + + // VUID-VkFenceGetWin32HandleInfoKHR-handleType-01448 + if !self.export_handle_types.intersects(handle_type.into()) { + return Err(FenceError::HandleTypeNotEnabled); + } + + // VUID-VkFenceGetWin32HandleInfoKHR-handleType-01449 + if matches!(handle_type, ExternalFenceHandleType::OpaqueWin32) + && state.is_exported(handle_type) + { + return Err(FenceError::AlreadyExported); + } + + // VUID-VkFenceGetWin32HandleInfoKHR-handleType-01451 + if handle_type.has_copy_transference() + && !(state.is_signaled().unwrap_or(false) || state.is_in_queue()) + { + return Err(FenceError::HandleTypeCopyNotSignaled); + } + + // VUID-VkFenceGetWin32HandleInfoKHR-fence-01450 + if let Some(imported_handle_type) = state.current_import { + match imported_handle_type { + ImportType::SwapchainAcquire => { + return Err(FenceError::ImportedForSwapchainAcquire) + } + ImportType::ExternalFence(imported_handle_type) => { + let external_fence_properties = unsafe { + self.device + .physical_device() + .external_fence_properties_unchecked(ExternalFenceInfo::handle_type( + handle_type, + )) + }; + + if !external_fence_properties + .export_from_imported_handle_types + .intersects(imported_handle_type.into()) + { + return Err(FenceError::ExportFromImportedNotSupported { + imported_handle_type, + }); + } + } + } + } + + // VUID-VkFenceGetWin32HandleInfoKHR-handleType-01452 + if !matches!( + handle_type, + ExternalFenceHandleType::OpaqueWin32 | ExternalFenceHandleType::OpaqueWin32Kmt + ) { + return Err(FenceError::HandleTypeNotWin32); + } + + Ok(()) + } + + #[cfg(windows)] + #[cfg_attr(not(feature = "document_unchecked"), doc(hidden))] + #[inline] + pub unsafe fn export_win32_handle_unchecked( + &self, + handle_type: ExternalFenceHandleType, + ) -> Result<*mut std::ffi::c_void, VulkanError> { + let mut state = self.state.lock(); + self.export_win32_handle_unchecked_locked(handle_type, &mut state) + } + + #[cfg(windows)] + unsafe fn export_win32_handle_unchecked_locked( + &self, + handle_type: ExternalFenceHandleType, + state: &mut FenceState, + ) -> Result<*mut std::ffi::c_void, VulkanError> { + let info_vk = ash::vk::FenceGetWin32HandleInfoKHR { + fence: self.handle, + handle_type: handle_type.into(), + ..Default::default() + }; + + let mut output = MaybeUninit::uninit(); + let fns = self.device.fns(); + (fns.khr_external_fence_win32.get_fence_win32_handle_khr)( + self.device.handle(), + &info_vk, + output.as_mut_ptr(), + ) + .result() + .map_err(VulkanError::from)?; + + state.export(handle_type); + + Ok(output.assume_init()) + } + + /// Imports a fence from a POSIX file descriptor. + /// + /// The [`khr_external_fence_fd`](crate::device::DeviceExtensions::khr_external_fence_fd) + /// extension must be enabled on the device. + /// + /// # Safety + /// + /// - If in `import_fence_fd_info`, `handle_type` is `ExternalHandleType::OpaqueFd`, + /// then `file` must represent a fence that was exported from Vulkan or a compatible API, + /// with a driver and device UUID equal to those of the device that owns `self`. + #[cfg(unix)] + #[inline] + pub unsafe fn import_fd( + &self, + import_fence_fd_info: ImportFenceFdInfo, + ) -> Result<(), FenceError> { + let mut state = self.state.lock(); + self.validate_import_fd(&import_fence_fd_info, &state)?; + + Ok(self.import_fd_unchecked_locked(import_fence_fd_info, &mut state)?) } -} -unsafe impl<D> VulkanObject for Fence<D> -where - D: SafeDeref<Target = Device>, -{ - type Object = ash::vk::Fence; + #[cfg(unix)] + fn validate_import_fd( + &self, + import_fence_fd_info: &ImportFenceFdInfo, + state: &FenceState, + ) -> Result<(), FenceError> { + if !self.device.enabled_extensions().khr_external_fence_fd { + return Err(FenceError::RequirementNotMet { + required_for: "`Fence::import_fd`", + requires_one_of: RequiresOneOf { + device_extensions: &["khr_external_fence_fd"], + ..Default::default() + }, + }); + } + + // VUID-vkImportFenceFdKHR-fence-01463 + if state.is_in_queue() { + return Err(FenceError::InQueue); + } + + let &ImportFenceFdInfo { + flags, + handle_type, + file: _, + _ne: _, + } = import_fence_fd_info; + + // VUID-VkImportFenceFdInfoKHR-flags-parameter + flags.validate_device(&self.device)?; + + // VUID-VkImportFenceFdInfoKHR-handleType-parameter + handle_type.validate_device(&self.device)?; + + // VUID-VkImportFenceFdInfoKHR-handleType-01464 + if !matches!( + handle_type, + ExternalFenceHandleType::OpaqueFd | ExternalFenceHandleType::SyncFd + ) { + return Err(FenceError::HandleTypeNotFd); + } + + // VUID-VkImportFenceFdInfoKHR-fd-01541 + // Can't validate, therefore unsafe + + // VUID-VkImportFenceFdInfoKHR-handleType-07306 + if handle_type.has_copy_transference() && !flags.intersects(FenceImportFlags::TEMPORARY) { + return Err(FenceError::HandletypeCopyNotTemporary); + } + Ok(()) + } + + #[cfg(unix)] + #[cfg_attr(not(feature = "document_unchecked"), doc(hidden))] #[inline] - fn internal_object(&self) -> ash::vk::Fence { - self.fence + pub unsafe fn import_fd_unchecked( + &self, + import_fence_fd_info: ImportFenceFdInfo, + ) -> Result<(), VulkanError> { + let mut state = self.state.lock(); + self.import_fd_unchecked_locked(import_fence_fd_info, &mut state) + } + + #[cfg(unix)] + unsafe fn import_fd_unchecked_locked( + &self, + import_fence_fd_info: ImportFenceFdInfo, + state: &mut FenceState, + ) -> Result<(), VulkanError> { + use std::os::unix::io::IntoRawFd; + + let ImportFenceFdInfo { + flags, + handle_type, + file, + _ne: _, + } = import_fence_fd_info; + + let info_vk = ash::vk::ImportFenceFdInfoKHR { + fence: self.handle, + flags: flags.into(), + handle_type: handle_type.into(), + fd: file.map_or(-1, |file| file.into_raw_fd()), + ..Default::default() + }; + + let fns = self.device.fns(); + (fns.khr_external_fence_fd.import_fence_fd_khr)(self.device.handle(), &info_vk) + .result() + .map_err(VulkanError::from)?; + + state.import(handle_type, flags.intersects(FenceImportFlags::TEMPORARY)); + + Ok(()) + } + + /// Imports a fence from a Win32 handle. + /// + /// The [`khr_external_fence_win32`](crate::device::DeviceExtensions::khr_external_fence_win32) + /// extension must be enabled on the device. + /// + /// # Safety + /// + /// - In `import_fence_win32_handle_info`, `handle` must represent a fence that was exported + /// from Vulkan or a compatible API, with a driver and device UUID equal to those of the + /// device that owns `self`. + #[cfg(windows)] + #[inline] + pub unsafe fn import_win32_handle( + &self, + import_fence_win32_handle_info: ImportFenceWin32HandleInfo, + ) -> Result<(), FenceError> { + let mut state = self.state.lock(); + self.validate_import_win32_handle(&import_fence_win32_handle_info, &state)?; + + Ok(self.import_win32_handle_unchecked_locked(import_fence_win32_handle_info, &mut state)?) + } + + #[cfg(windows)] + fn validate_import_win32_handle( + &self, + import_fence_win32_handle_info: &ImportFenceWin32HandleInfo, + state: &FenceState, + ) -> Result<(), FenceError> { + if !self.device.enabled_extensions().khr_external_fence_win32 { + return Err(FenceError::RequirementNotMet { + required_for: "`Fence::import_win32_handle`", + requires_one_of: RequiresOneOf { + device_extensions: &["khr_external_fence_win32"], + ..Default::default() + }, + }); + } + + // VUID-vkImportFenceWin32HandleKHR-fence-04448 + if state.is_in_queue() { + return Err(FenceError::InQueue); + } + + let &ImportFenceWin32HandleInfo { + flags, + handle_type, + handle: _, + _ne: _, + } = import_fence_win32_handle_info; + + // VUID-VkImportFenceWin32HandleInfoKHR-flags-parameter + flags.validate_device(&self.device)?; + + // VUID-VkImportFenceWin32HandleInfoKHR-handleType-01457 + handle_type.validate_device(&self.device)?; + + // VUID-VkImportFenceWin32HandleInfoKHR-handleType-01457 + if !matches!( + handle_type, + ExternalFenceHandleType::OpaqueWin32 | ExternalFenceHandleType::OpaqueWin32Kmt + ) { + return Err(FenceError::HandleTypeNotWin32); + } + + // VUID-VkImportFenceWin32HandleInfoKHR-handle-01539 + // Can't validate, therefore unsafe + + // VUID? + if handle_type.has_copy_transference() && !flags.intersects(FenceImportFlags::TEMPORARY) { + return Err(FenceError::HandletypeCopyNotTemporary); + } + + Ok(()) + } + + #[cfg(windows)] + #[cfg_attr(not(feature = "document_unchecked"), doc(hidden))] + #[inline] + pub unsafe fn import_win32_handle_unchecked( + &self, + import_fence_win32_handle_info: ImportFenceWin32HandleInfo, + ) -> Result<(), VulkanError> { + let mut state = self.state.lock(); + self.import_win32_handle_unchecked_locked(import_fence_win32_handle_info, &mut state) + } + + #[cfg(windows)] + unsafe fn import_win32_handle_unchecked_locked( + &self, + import_fence_win32_handle_info: ImportFenceWin32HandleInfo, + state: &mut FenceState, + ) -> Result<(), VulkanError> { + let ImportFenceWin32HandleInfo { + flags, + handle_type, + handle, + _ne: _, + } = import_fence_win32_handle_info; + + let info_vk = ash::vk::ImportFenceWin32HandleInfoKHR { + fence: self.handle, + flags: flags.into(), + handle_type: handle_type.into(), + handle, + name: ptr::null(), // TODO: support? + ..Default::default() + }; + + let fns = self.device.fns(); + (fns.khr_external_fence_win32.import_fence_win32_handle_khr)( + self.device.handle(), + &info_vk, + ) + .result() + .map_err(VulkanError::from)?; + + state.import(handle_type, flags.intersects(FenceImportFlags::TEMPORARY)); + + Ok(()) + } + + pub(crate) fn state(&self) -> MutexGuard<'_, FenceState> { + self.state.lock() + } + + // Shared by Fence and FenceSignalFuture + pub(crate) fn poll_impl(&self, cx: &mut Context<'_>) -> Poll<Result<(), OomError>> { + // Vulkan only allows polling of the fence status, so we have to use a spin future. + // This is still better than blocking in async applications, since a smart-enough async engine + // can choose to run some other tasks between probing this one. + + // Check if we are done without blocking + match self.is_signaled() { + Err(e) => return Poll::Ready(Err(e)), + Ok(signalled) => { + if signalled { + return Poll::Ready(Ok(())); + } + } + } + + // Otherwise spin + cx.waker().wake_by_ref(); + Poll::Pending } } -impl<D> Drop for Fence<D> -where - D: SafeDeref<Target = Device>, -{ +impl Drop for Fence { #[inline] fn drop(&mut self) { unsafe { if self.must_put_in_pool { - let raw_fence = self.fence; - self.device.fence_pool().lock().unwrap().push(raw_fence); + let raw_fence = self.handle; + self.device.fence_pool().lock().push(raw_fence); } else { let fns = self.device.fns(); - fns.v1_0 - .destroy_fence(self.device.internal_object(), self.fence, ptr::null()); + (fns.v1_0.destroy_fence)(self.device.handle(), self.handle, ptr::null()); } } } } -/// Error that can be returned when waiting on a fence. +impl Future for Fence { + type Output = Result<(), OomError>; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { + self.poll_impl(cx) + } +} + +unsafe impl VulkanObject for Fence { + type Handle = ash::vk::Fence; + + #[inline] + fn handle(&self) -> Self::Handle { + self.handle + } +} + +unsafe impl DeviceOwned for Fence { + #[inline] + fn device(&self) -> &Arc<Device> { + &self.device + } +} + +impl_id_counter!(Fence); + +#[derive(Debug, Default)] +pub(crate) struct FenceState { + is_signaled: bool, + pending_signal: Option<Weak<Queue>>, + + reference_exported: bool, + exported_handle_types: ExternalFenceHandleTypes, + current_import: Option<ImportType>, + permanent_import: Option<ExternalFenceHandleType>, +} + +impl FenceState { + /// If the fence is not in a queue and has no external references, returns the current status. + #[inline] + fn is_signaled(&self) -> Option<bool> { + // If either of these is true, we can't be certain of the status. + if self.is_in_queue() || self.has_external_reference() { + None + } else { + Some(self.is_signaled) + } + } + + #[inline] + fn is_in_queue(&self) -> bool { + self.pending_signal.is_some() + } + + /// Returns whether there are any potential external references to the fence payload. + /// That is, the fence has been exported by reference transference, or imported. + #[inline] + fn has_external_reference(&self) -> bool { + self.reference_exported || self.current_import.is_some() + } + + #[allow(dead_code)] + #[inline] + fn is_exported(&self, handle_type: ExternalFenceHandleType) -> bool { + self.exported_handle_types.intersects(handle_type.into()) + } + + #[inline] + pub(crate) unsafe fn add_queue_signal(&mut self, queue: &Arc<Queue>) { + self.pending_signal = Some(Arc::downgrade(queue)); + } + + /// Called when a fence first discovers that it is signaled. + /// Returns the queue that should be informed about it. + #[inline] + unsafe fn set_signaled(&mut self) -> Option<Arc<Queue>> { + self.is_signaled = true; + + // Fences with external references can't be used to determine queue completion. + if self.has_external_reference() { + self.pending_signal = None; + None + } else { + self.pending_signal.take().and_then(|queue| queue.upgrade()) + } + } + + /// Called when a queue is unlocking resources. + #[inline] + pub(crate) unsafe fn set_signal_finished(&mut self) { + self.is_signaled = true; + self.pending_signal = None; + } + + #[inline] + unsafe fn reset(&mut self) { + debug_assert!(!self.is_in_queue()); + self.current_import = self.permanent_import.map(Into::into); + self.is_signaled = false; + } + + #[allow(dead_code)] + #[inline] + unsafe fn export(&mut self, handle_type: ExternalFenceHandleType) { + self.exported_handle_types |= handle_type.into(); + + if handle_type.has_copy_transference() { + self.reset(); + } else { + self.reference_exported = true; + } + } + + #[allow(dead_code)] + #[inline] + unsafe fn import(&mut self, handle_type: ExternalFenceHandleType, temporary: bool) { + debug_assert!(!self.is_in_queue()); + self.current_import = Some(handle_type.into()); + + if !temporary { + self.permanent_import = Some(handle_type); + } + } + + #[inline] + pub(crate) unsafe fn import_swapchain_acquire(&mut self) { + debug_assert!(!self.is_in_queue()); + self.current_import = Some(ImportType::SwapchainAcquire); + } +} + +#[derive(Clone, Copy, Debug)] +enum ImportType { + SwapchainAcquire, + ExternalFence(ExternalFenceHandleType), +} + +impl From<ExternalFenceHandleType> for ImportType { + #[inline] + fn from(handle_type: ExternalFenceHandleType) -> Self { + Self::ExternalFence(handle_type) + } +} + +/// Parameters to create a new `Fence`. +#[derive(Clone, Debug)] +pub struct FenceCreateInfo { + /// Whether the fence should be created in the signaled state. + /// + /// The default value is `false`. + pub signaled: bool, + + /// The handle types that can be exported from the fence. + pub export_handle_types: ExternalFenceHandleTypes, + + pub _ne: crate::NonExhaustive, +} + +impl Default for FenceCreateInfo { + #[inline] + fn default() -> Self { + Self { + signaled: false, + export_handle_types: ExternalFenceHandleTypes::empty(), + _ne: crate::NonExhaustive(()), + } + } +} + +vulkan_bitflags_enum! { + #[non_exhaustive] + /// A set of [`ExternalFenceHandleType`] values. + ExternalFenceHandleTypes, + + /// The handle type used to export or import fences to/from an external source. + ExternalFenceHandleType impl { + /// Returns whether the given handle type has *copy transference* rather than *reference + /// transference*. + /// + /// Imports of handles with copy transference must always be temporary. Exports of such + /// handles must only occur if the fence is already signaled, or if there is a fence signal + /// operation pending in a queue. + #[inline] + pub fn has_copy_transference(self) -> bool { + // As defined by + // https://registry.khronos.org/vulkan/specs/1.3-extensions/html/chap7.html#synchronization-fence-handletypes-win32 + // https://registry.khronos.org/vulkan/specs/1.3-extensions/html/chap7.html#synchronization-fence-handletypes-fd + matches!(self, Self::SyncFd) + } + }, + + = ExternalFenceHandleTypeFlags(u32); + + /// A POSIX file descriptor handle that is only usable with Vulkan and compatible APIs. + /// + /// This handle type has *reference transference*. + OPAQUE_FD, OpaqueFd = OPAQUE_FD, + + /// A Windows NT handle that is only usable with Vulkan and compatible APIs. + /// + /// This handle type has *reference transference*. + OPAQUE_WIN32, OpaqueWin32 = OPAQUE_WIN32, + + /// A Windows global share handle that is only usable with Vulkan and compatible APIs. + /// + /// This handle type has *reference transference*. + OPAQUE_WIN32_KMT, OpaqueWin32Kmt = OPAQUE_WIN32_KMT, + + /// A POSIX file descriptor handle to a Linux Sync File or Android Fence object. + /// + /// This handle type has *copy transference*. + SYNC_FD, SyncFd = SYNC_FD, +} + +vulkan_bitflags! { + #[non_exhaustive] + + /// Additional parameters for a fence payload import. + FenceImportFlags = FenceImportFlags(u32); + + /// The fence payload will be imported only temporarily, regardless of the permanence of the + /// imported handle type. + TEMPORARY = TEMPORARY, +} + +#[cfg(unix)] +#[derive(Debug)] +pub struct ImportFenceFdInfo { + /// Additional parameters for the import operation. + /// + /// If `handle_type` has *copy transference*, this must include the `temporary` flag. + /// + /// The default value is [`FenceImportFlags::empty()`]. + pub flags: FenceImportFlags, + + /// The handle type of `file`. + /// + /// There is no default value. + pub handle_type: ExternalFenceHandleType, + + /// The file to import the fence from. + /// + /// If `handle_type` is `ExternalFenceHandleType::SyncFd`, then `file` can be `None`. + /// Instead of an imported file descriptor, a dummy file descriptor `-1` is used, + /// which represents a fence that is always signaled. + /// + /// The default value is `None`, which must be overridden if `handle_type` is not + /// `ExternalFenceHandleType::SyncFd`. + pub file: Option<File>, + + pub _ne: crate::NonExhaustive, +} + +#[cfg(unix)] +impl ImportFenceFdInfo { + /// Returns an `ImportFenceFdInfo` with the specified `handle_type`. + #[inline] + pub fn handle_type(handle_type: ExternalFenceHandleType) -> Self { + Self { + flags: FenceImportFlags::empty(), + handle_type, + file: None, + _ne: crate::NonExhaustive(()), + } + } +} + +#[cfg(windows)] +#[derive(Debug)] +pub struct ImportFenceWin32HandleInfo { + /// Additional parameters for the import operation. + /// + /// If `handle_type` has *copy transference*, this must include the `temporary` flag. + /// + /// The default value is [`FenceImportFlags::empty()`]. + pub flags: FenceImportFlags, + + /// The handle type of `handle`. + /// + /// There is no default value. + pub handle_type: ExternalFenceHandleType, + + /// The file to import the fence from. + /// + /// The default value is `null`, which must be overridden. + pub handle: *mut std::ffi::c_void, + + pub _ne: crate::NonExhaustive, +} + +#[cfg(windows)] +impl ImportFenceWin32HandleInfo { + /// Returns an `ImportFenceWin32HandleInfo` with the specified `handle_type`. + #[inline] + pub fn handle_type(handle_type: ExternalFenceHandleType) -> Self { + Self { + flags: FenceImportFlags::empty(), + handle_type, + handle: ptr::null_mut(), + _ne: crate::NonExhaustive(()), + } + } +} + +/// The fence configuration to query in +/// [`PhysicalDevice::external_fence_properties`](crate::device::physical::PhysicalDevice::external_fence_properties). +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct ExternalFenceInfo { + /// The external handle type that will be used with the fence. + pub handle_type: ExternalFenceHandleType, + + pub _ne: crate::NonExhaustive, +} + +impl ExternalFenceInfo { + /// Returns an `ExternalFenceInfo` with the specified `handle_type`. + #[inline] + pub fn handle_type(handle_type: ExternalFenceHandleType) -> Self { + Self { + handle_type, + _ne: crate::NonExhaustive(()), + } + } +} + +/// The properties for exporting or importing external handles, when a fence is created +/// with a specific configuration. +#[derive(Clone, Debug)] +#[non_exhaustive] +pub struct ExternalFenceProperties { + /// Whether a handle can be exported to an external source with the queried + /// external handle type. + pub exportable: bool, + + /// Whether a handle can be imported from an external source with the queried + /// external handle type. + pub importable: bool, + + /// Which external handle types can be re-exported after the queried external handle type has + /// been imported. + pub export_from_imported_handle_types: ExternalFenceHandleTypes, + + /// Which external handle types can be enabled along with the queried external handle type + /// when creating the fence. + pub compatible_handle_types: ExternalFenceHandleTypes, +} + #[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub enum FenceWaitError { - /// Not enough memory to complete the wait. +pub enum FenceError { + /// Not enough memory available. OomError(OomError), + /// The device has been lost. + DeviceLost, + /// The specified timeout wasn't long enough. Timeout, - /// The device has been lost. - DeviceLostError, + RequirementNotMet { + required_for: &'static str, + requires_one_of: RequiresOneOf, + }, + + /// The provided handle type does not permit more than one export, + /// and a handle of this type was already exported previously. + AlreadyExported, + + /// The provided handle type cannot be exported from the current import handle type. + ExportFromImportedNotSupported { + imported_handle_type: ExternalFenceHandleType, + }, + + /// One of the export handle types is not compatible with the other provided handles. + ExportHandleTypesNotCompatible, + + /// A handle type with copy transference was provided, but the fence is not signaled and there + /// is no pending queue operation that will signal it. + HandleTypeCopyNotSignaled, + + /// A handle type with copy transference was provided, + /// but the `temporary` import flag was not set. + HandletypeCopyNotTemporary, + + /// The provided export handle type was not set in `export_handle_types` when creating the + /// fence. + HandleTypeNotEnabled, + + /// Exporting is not supported for the provided handle type. + HandleTypeNotExportable { + handle_type: ExternalFenceHandleType, + }, + + /// The provided handle type is not a POSIX file descriptor handle. + HandleTypeNotFd, + + /// The provided handle type is not a Win32 handle. + HandleTypeNotWin32, + + /// The fence currently has a temporary import for a swapchain acquire operation. + ImportedForSwapchainAcquire, + + /// The fence is currently in use by a queue. + InQueue, } -impl error::Error for FenceWaitError { - #[inline] - fn source(&self) -> Option<&(dyn error::Error + 'static)> { - match *self { - FenceWaitError::OomError(ref err) => Some(err), +impl Error for FenceError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + match self { + Self::OomError(err) => Some(err), _ => None, } } } -impl fmt::Display for FenceWaitError { - #[inline] - fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { - write!( - fmt, - "{}", - match *self { - FenceWaitError::OomError(_) => "no memory available", - FenceWaitError::Timeout => "the timeout has been reached", - FenceWaitError::DeviceLostError => "the device was lost", +impl Display for FenceError { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> { + match self { + Self::OomError(_) => write!(f, "not enough memory available"), + Self::DeviceLost => write!(f, "the device was lost"), + Self::Timeout => write!(f, "the timeout has been reached"), + Self::RequirementNotMet { + required_for, + requires_one_of, + } => write!( + f, + "a requirement was not met for: {}; requires one of: {}", + required_for, requires_one_of, + ), + + Self::AlreadyExported => write!( + f, + "the provided handle type does not permit more than one export, and a handle of \ + this type was already exported previously", + ), + Self::ExportFromImportedNotSupported { + imported_handle_type, + } => write!( + f, + "the provided handle type cannot be exported from the current imported handle type \ + {:?}", + imported_handle_type, + ), + Self::ExportHandleTypesNotCompatible => write!( + f, + "one of the export handle types is not compatible with the other provided handles", + ), + Self::HandleTypeCopyNotSignaled => write!( + f, + "a handle type with copy transference was provided, but the fence is not signaled \ + and there is no pending queue operation that will signal it", + ), + Self::HandletypeCopyNotTemporary => write!( + f, + "a handle type with copy transference was provided, but the `temporary` \ + import flag was not set", + ), + Self::HandleTypeNotEnabled => write!( + f, + "the provided export handle type was not set in `export_handle_types` when \ + creating the fence", + ), + Self::HandleTypeNotExportable { handle_type } => write!( + f, + "exporting is not supported for handles of type {:?}", + handle_type, + ), + Self::HandleTypeNotFd => write!( + f, + "the provided handle type is not a POSIX file descriptor handle", + ), + Self::HandleTypeNotWin32 => { + write!(f, "the provided handle type is not a Win32 handle") } - ) + Self::ImportedForSwapchainAcquire => write!( + f, + "the fence currently has a temporary import for a swapchain acquire operation", + ), + Self::InQueue => write!(f, "the fence is currently in use by a queue"), + } } } -impl From<Error> for FenceWaitError { - #[inline] - fn from(err: Error) -> FenceWaitError { +impl From<VulkanError> for FenceError { + fn from(err: VulkanError) -> Self { match err { - Error::OutOfHostMemory => FenceWaitError::OomError(From::from(err)), - Error::OutOfDeviceMemory => FenceWaitError::OomError(From::from(err)), - Error::DeviceLost => FenceWaitError::DeviceLostError, - _ => panic!("Unexpected error value: {}", err as i32), + e @ VulkanError::OutOfHostMemory | e @ VulkanError::OutOfDeviceMemory => { + Self::OomError(e.into()) + } + VulkanError::DeviceLost => Self::DeviceLost, + _ => panic!("unexpected error: {:?}", err), + } + } +} + +impl From<OomError> for FenceError { + fn from(err: OomError) -> Self { + Self::OomError(err) + } +} + +impl From<RequirementNotMet> for FenceError { + fn from(err: RequirementNotMet) -> Self { + Self::RequirementNotMet { + required_for: err.required_for, + requires_one_of: err.requires_one_of, } } } #[cfg(test)] mod tests { - use crate::sync::Fence; - use crate::VulkanObject; + use crate::{ + sync::fence::{Fence, FenceCreateInfo}, + VulkanObject, + }; use std::time::Duration; #[test] fn fence_create() { let (device, _) = gfx_dev_and_queue!(); - let fence = Fence::alloc(device.clone()).unwrap(); - assert!(!fence.ready().unwrap()); + let fence = Fence::new(device, Default::default()).unwrap(); + assert!(!fence.is_signaled().unwrap()); } #[test] fn fence_create_signaled() { let (device, _) = gfx_dev_and_queue!(); - let fence = Fence::alloc_signaled(device.clone()).unwrap(); - assert!(fence.ready().unwrap()); + let fence = Fence::new( + device, + FenceCreateInfo { + signaled: true, + ..Default::default() + }, + ) + .unwrap(); + assert!(fence.is_signaled().unwrap()); } #[test] fn fence_signaled_wait() { let (device, _) = gfx_dev_and_queue!(); - let fence = Fence::alloc_signaled(device.clone()).unwrap(); + let fence = Fence::new( + device, + FenceCreateInfo { + signaled: true, + ..Default::default() + }, + ) + .unwrap(); fence.wait(Some(Duration::new(0, 10))).unwrap(); } @@ -438,9 +1658,16 @@ mod tests { fn fence_reset() { let (device, _) = gfx_dev_and_queue!(); - let mut fence = Fence::alloc_signaled(device.clone()).unwrap(); + let fence = Fence::new( + device, + FenceCreateInfo { + signaled: true, + ..Default::default() + }, + ) + .unwrap(); fence.reset().unwrap(); - assert!(!fence.ready().unwrap()); + assert!(!fence.is_signaled().unwrap()); } #[test] @@ -448,54 +1675,72 @@ mod tests { let (device1, _) = gfx_dev_and_queue!(); let (device2, _) = gfx_dev_and_queue!(); - assert_should_panic!( - "Tried to wait for multiple fences that didn't belong \ - to the same device", - { - let fence1 = Fence::alloc_signaled(device1.clone()).unwrap(); - let fence2 = Fence::alloc_signaled(device2.clone()).unwrap(); + assert_should_panic!({ + let fence1 = Fence::new( + device1.clone(), + FenceCreateInfo { + signaled: true, + ..Default::default() + }, + ) + .unwrap(); + let fence2 = Fence::new( + device2.clone(), + FenceCreateInfo { + signaled: true, + ..Default::default() + }, + ) + .unwrap(); - let _ = Fence::multi_wait( - [&fence1, &fence2].iter().cloned(), - Some(Duration::new(0, 10)), - ); - } - ); + let _ = Fence::multi_wait( + [&fence1, &fence2].iter().cloned(), + Some(Duration::new(0, 10)), + ); + }); } #[test] fn multireset_different_devices() { - use std::iter::once; - let (device1, _) = gfx_dev_and_queue!(); let (device2, _) = gfx_dev_and_queue!(); - assert_should_panic!( - "Tried to reset multiple fences that didn't belong \ - to the same device", - { - let mut fence1 = Fence::alloc_signaled(device1.clone()).unwrap(); - let mut fence2 = Fence::alloc_signaled(device2.clone()).unwrap(); + assert_should_panic!({ + let fence1 = Fence::new( + device1.clone(), + FenceCreateInfo { + signaled: true, + ..Default::default() + }, + ) + .unwrap(); + let fence2 = Fence::new( + device2.clone(), + FenceCreateInfo { + signaled: true, + ..Default::default() + }, + ) + .unwrap(); - let _ = Fence::multi_reset(once(&mut fence1).chain(once(&mut fence2))); - } - ); + let _ = Fence::multi_reset([&fence1, &fence2]); + }); } #[test] fn fence_pool() { let (device, _) = gfx_dev_and_queue!(); - assert_eq!(device.fence_pool().lock().unwrap().len(), 0); + assert_eq!(device.fence_pool().lock().len(), 0); let fence1_internal_obj = { let fence = Fence::from_pool(device.clone()).unwrap(); - assert_eq!(device.fence_pool().lock().unwrap().len(), 0); - fence.internal_object() + assert_eq!(device.fence_pool().lock().len(), 0); + fence.handle() }; - assert_eq!(device.fence_pool().lock().unwrap().len(), 1); + assert_eq!(device.fence_pool().lock().len(), 1); let fence2 = Fence::from_pool(device.clone()).unwrap(); - assert_eq!(device.fence_pool().lock().unwrap().len(), 0); - assert_eq!(fence2.internal_object(), fence1_internal_obj); + assert_eq!(device.fence_pool().lock().len(), 0); + assert_eq!(fence2.handle(), fence1_internal_obj); } } |