// Copyright (c) 2016 The vulkano developers // Licensed under the Apache License, Version 2.0 // or the MIT // license , // at your option. All files in the project carrying such // notice may not be copied, modified, or distributed except // according to those terms. //! 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; #[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. /// /// 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 { handle: ash::vk::Fence, device: Arc, id: NonZeroU64, must_put_in_pool: bool, export_handle_types: ExternalFenceHandleTypes, state: Mutex, } impl Fence { /// Creates a new `Fence`. #[inline] pub fn new(device: Arc, create_info: FenceCreateInfo) -> Result { 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, create_info: FenceCreateInfo, ) -> Result { 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 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. #[inline] pub fn from_pool(device: Arc) -> Result { 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(); (fns.v1_0.reset_fences)(device.handle(), 1, &handle) .result() .map_err(VulkanError::from)?; } 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 let mut fence = Fence::new(device, FenceCreateInfo::default())?; fence.must_put_in_pool = true; fence } }; Ok(fence) } /// 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 unsafe fn from_handle( device: Arc, handle: ash::vk::Fence, create_info: FenceCreateInfo, ) -> Fence { let FenceCreateInfo { signaled, export_handle_types, _ne: _, } = create_info; Fence { handle, device, id: Self::next_id(), must_put_in_pool: false, export_handle_types, state: Mutex::new(FenceState { is_signaled: signaled, ..Default::default() }), } } /// Returns true if the fence is signaled. #[inline] pub fn is_signaled(&self) -> Result { 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); } // 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 { 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. /// /// 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) -> 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 = timeout.map_or(u64::MAX, |timeout| { timeout .as_secs() .saturating_mul(1_000_000_000) .saturating_add(timeout.subsec_nanos() as u64) }); 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, ) }; 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. /// /// # Panics /// /// - Panics if not all fences belong to the same device. pub fn multi_wait<'a>( fences: impl IntoIterator, timeout: Option, ) -> Result<(), FenceError> { let fences: SmallVec<[_; 8]> = fences.into_iter().collect(); Self::validate_multi_wait(&fences, timeout)?; unsafe { Self::multi_wait_unchecked(fences, timeout) } } fn validate_multi_wait( fences: &[&Fence], _timeout: Option, ) -> 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, timeout: Option, ) -> 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(); // 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); } } // 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 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(); (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()), } }; // 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. /// /// The fence must not be in use by a queue operation. #[inline] 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. /// /// The fences must not be in use by a queue operation. /// /// # Panics /// /// - Panics if not all fences belong to the same device. pub fn multi_reset<'a>(fences: impl IntoIterator) -> Result<(), FenceError> { let (fences, mut states): (SmallVec<[_; 8]>, SmallVec<[_; 8]>) = fences .into_iter() .map(|fence| { 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(()) } #[cfg_attr(not(feature = "document_unchecked"), doc(hidden))] pub unsafe fn multi_reset_unchecked<'a>( fences: impl IntoIterator, ) -> Result<(), VulkanError> { let (fences, mut states): (SmallVec<[_; 8]>, SmallVec<[_; 8]>) = fences .into_iter() .map(|fence| { let state = fence.state.lock(); (fence, state) }) .unzip(); 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 { 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(()) } #[cfg(unix)] #[cfg_attr(not(feature = "document_unchecked"), doc(hidden))] #[inline] pub unsafe fn export_fd_unchecked( &self, handle_type: ExternalFenceHandleType, ) -> Result { 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 { 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)?) } #[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] 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> { // 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 Drop for Fence { #[inline] fn drop(&mut self) { unsafe { if self.must_put_in_pool { 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.handle(), self.handle, ptr::null()); } } } } impl Future for Fence { type Output = Result<(), OomError>; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 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 { &self.device } } impl_id_counter!(Fence); #[derive(Debug, Default)] pub(crate) struct FenceState { is_signaled: bool, pending_signal: Option>, reference_exported: bool, exported_handle_types: ExternalFenceHandleTypes, current_import: Option, permanent_import: Option, } 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 { // 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) { 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> { 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 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, 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 FenceError { /// Not enough memory available. OomError(OomError), /// The device has been lost. DeviceLost, /// The specified timeout wasn't long enough. Timeout, 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 for FenceError { fn source(&self) -> Option<&(dyn Error + 'static)> { match self { Self::OomError(err) => Some(err), _ => None, } } } 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 for FenceError { fn from(err: VulkanError) -> Self { match err { e @ VulkanError::OutOfHostMemory | e @ VulkanError::OutOfDeviceMemory => { Self::OomError(e.into()) } VulkanError::DeviceLost => Self::DeviceLost, _ => panic!("unexpected error: {:?}", err), } } } impl From for FenceError { fn from(err: OomError) -> Self { Self::OomError(err) } } impl From 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::{Fence, FenceCreateInfo}, VulkanObject, }; use std::time::Duration; #[test] fn fence_create() { let (device, _) = gfx_dev_and_queue!(); 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::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::new( device, FenceCreateInfo { signaled: true, ..Default::default() }, ) .unwrap(); fence.wait(Some(Duration::new(0, 10))).unwrap(); } #[test] fn fence_reset() { let (device, _) = gfx_dev_and_queue!(); let fence = Fence::new( device, FenceCreateInfo { signaled: true, ..Default::default() }, ) .unwrap(); fence.reset().unwrap(); assert!(!fence.is_signaled().unwrap()); } #[test] fn multiwait_different_devices() { let (device1, _) = gfx_dev_and_queue!(); let (device2, _) = gfx_dev_and_queue!(); 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)), ); }); } #[test] fn multireset_different_devices() { let (device1, _) = gfx_dev_and_queue!(); let (device2, _) = gfx_dev_and_queue!(); 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([&fence1, &fence2]); }); } #[test] fn fence_pool() { let (device, _) = gfx_dev_and_queue!(); 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().len(), 0); fence.handle() }; assert_eq!(device.fence_pool().lock().len(), 1); let fence2 = Fence::from_pool(device.clone()).unwrap(); assert_eq!(device.fence_pool().lock().len(), 0); assert_eq!(fence2.handle(), fence1_internal_obj); } }