aboutsummaryrefslogtreecommitdiff
path: root/src/sync/fence.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/sync/fence.rs')
-rw-r--r--src/sync/fence.rs501
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);
+ }
+}