diff options
Diffstat (limited to 'src/sync/fence.rs')
-rw-r--r-- | src/sync/fence.rs | 501 |
1 files changed, 501 insertions, 0 deletions
diff --git a/src/sync/fence.rs b/src/sync/fence.rs new file mode 100644 index 0000000..208573a --- /dev/null +++ b/src/sync/fence.rs @@ -0,0 +1,501 @@ +// Copyright (c) 2016 The vulkano developers +// Licensed under the Apache License, Version 2.0 +// <LICENSE-APACHE or +// https://www.apache.org/licenses/LICENSE-2.0> or the MIT +// license <LICENSE-MIT or https://opensource.org/licenses/MIT>, +// at your option. All files in the project carrying such +// 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; +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. +/// +/// 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. +#[derive(Debug)] +pub struct Fence<D = Arc<Device>> +where + D: SafeDeref<Target = Device>, +{ + fence: ash::vk::Fence, + + device: D, + + // 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, + + // 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, +} + +impl<D> Fence<D> +where + D: SafeDeref<Target = Device>, +{ + /// Takes a fence from the vulkano-provided fence pool. + /// If the pool is empty, a new fence will be allocated. + /// 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) => { + 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), + )?; + } + Ok(Fence { + fence: raw_fence, + device: device, + signaled: AtomicBool::new(false), + must_put_in_pool: true, + }) + } + None => { + // Pool is empty, alloc new fence + Fence::alloc_impl(device, false, true) + } + } + } + + /// Builds a new fence. + #[inline] + pub fn alloc(device: D) -> Result<Fence<D>, OomError> { + Fence::alloc_impl(device, false, false) + } + + /// Builds a new fence in signaled state. + #[inline] + pub fn alloc_signaled(device: D) -> Result<Fence<D>, OomError> { + Fence::alloc_impl(device, true, false) + } + + 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() + }, + ..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); + } + + let fns = self.device.fns(); + let result = check_errors( + fns.v1_0 + .get_fence_status(self.device.internal_object(), self.fence), + )?; + match result { + Success::Success => { + self.signaled.store(true, Ordering::Relaxed); + Ok(true) + } + Success::NotReady => Ok(false), + _ => unreachable!(), + } + } + } + + /// 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<Duration>) -> Result<(), FenceWaitError> { + unsafe { + if self.signaled.load(Ordering::Relaxed) { + return Ok(()); + } + + 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 + }; + + 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!(), + } + } + } + + /// Waits for multiple fences at once. + /// + /// # Panic + /// + /// 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; + + 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" + ), + }; + + if fence.signaled.load(Ordering::Relaxed) { + None + } else { + Some(fence.fence) + } + }) + .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 + }; + + let r = if let Some(device) = device { + unsafe { + 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, + timeout_ns, + ))? + } + } else { + return Ok(()); + }; + + match r { + Success::Success => Ok(()), + Success::Timeout => Err(FenceWaitError::Timeout), + _ => unreachable!(), + } + } + + /// Resets the fence. + // This function takes a `&mut self` because the Vulkan API requires that the fence be + // externally synchronized. + #[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(()) + } + } + + /// Resets multiple fences at once. + /// + /// # Panic + /// + /// - Panics if not all fences belong to the same device. + /// + 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 + .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" + ), + }; + + fence.signaled.store(false, Ordering::Relaxed); + fence.fence + }) + .collect(); + + 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(), + ))?; + } + } + Ok(()) + } +} + +unsafe impl DeviceOwned for Fence { + #[inline] + fn device(&self) -> &Arc<Device> { + &self.device + } +} + +unsafe impl<D> VulkanObject for Fence<D> +where + D: SafeDeref<Target = Device>, +{ + type Object = ash::vk::Fence; + + #[inline] + fn internal_object(&self) -> ash::vk::Fence { + self.fence + } +} + +impl<D> Drop for Fence<D> +where + D: SafeDeref<Target = Device>, +{ + #[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); + } else { + let fns = self.device.fns(); + fns.v1_0 + .destroy_fence(self.device.internal_object(), self.fence, ptr::null()); + } + } + } +} + +/// Error that can be returned when waiting on a fence. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum FenceWaitError { + /// Not enough memory to complete the wait. + OomError(OomError), + + /// The specified timeout wasn't long enough. + Timeout, + + /// The device has been lost. + DeviceLostError, +} + +impl error::Error for FenceWaitError { + #[inline] + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + match *self { + FenceWaitError::OomError(ref 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 From<Error> for FenceWaitError { + #[inline] + fn from(err: Error) -> FenceWaitError { + 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), + } + } +} + +#[cfg(test)] +mod tests { + use crate::sync::Fence; + use crate::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()); + } + + #[test] + fn fence_create_signaled() { + let (device, _) = gfx_dev_and_queue!(); + + let fence = Fence::alloc_signaled(device.clone()).unwrap(); + assert!(fence.ready().unwrap()); + } + + #[test] + fn fence_signaled_wait() { + let (device, _) = gfx_dev_and_queue!(); + + let fence = Fence::alloc_signaled(device.clone()).unwrap(); + fence.wait(Some(Duration::new(0, 10))).unwrap(); + } + + #[test] + fn fence_reset() { + let (device, _) = gfx_dev_and_queue!(); + + let mut fence = Fence::alloc_signaled(device.clone()).unwrap(); + fence.reset().unwrap(); + assert!(!fence.ready().unwrap()); + } + + #[test] + fn multiwait_different_devices() { + 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(); + + 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(); + + let _ = Fence::multi_reset(once(&mut fence1).chain(once(&mut fence2))); + } + ); + } + + #[test] + fn fence_pool() { + let (device, _) = gfx_dev_and_queue!(); + + assert_eq!(device.fence_pool().lock().unwrap().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().unwrap().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); + } +} |