aboutsummaryrefslogtreecommitdiff
path: root/src/sync/future/fence_signal.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/sync/future/fence_signal.rs')
-rw-r--r--src/sync/future/fence_signal.rs516
1 files changed, 516 insertions, 0 deletions
diff --git a/src/sync/future/fence_signal.rs b/src/sync/future/fence_signal.rs
new file mode 100644
index 0000000..c2f5338
--- /dev/null
+++ b/src/sync/future/fence_signal.rs
@@ -0,0 +1,516 @@
+// Copyright (c) 2017 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 std::mem;
+use std::sync::Arc;
+use std::sync::Mutex;
+use std::sync::MutexGuard;
+use std::time::Duration;
+
+use crate::buffer::BufferAccess;
+use crate::command_buffer::submit::SubmitAnyBuilder;
+use crate::command_buffer::submit::SubmitCommandBufferBuilder;
+use crate::device::Device;
+use crate::device::DeviceOwned;
+use crate::device::Queue;
+use crate::image::ImageAccess;
+use crate::image::ImageLayout;
+use crate::sync::AccessCheckError;
+use crate::sync::AccessFlags;
+use crate::sync::Fence;
+use crate::sync::FlushError;
+use crate::sync::GpuFuture;
+use crate::sync::PipelineStages;
+
+/// Builds a new fence signal future.
+#[inline]
+pub fn then_signal_fence<F>(future: F, behavior: FenceSignalFutureBehavior) -> FenceSignalFuture<F>
+where
+ F: GpuFuture,
+{
+ let device = future.device().clone();
+
+ assert!(future.queue().is_some()); // TODO: document
+
+ let fence = Fence::from_pool(device.clone()).unwrap();
+ FenceSignalFuture {
+ device: device,
+ state: Mutex::new(FenceSignalFutureState::Pending(future, fence)),
+ behavior: behavior,
+ }
+}
+
+/// Describes the behavior of the future if you submit something after it.
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+pub enum FenceSignalFutureBehavior {
+ /// Continue execution on the same queue.
+ Continue,
+ /// Wait for the fence to be signalled before submitting any further operation.
+ Block {
+ /// How long to block the current thread.
+ timeout: Option<Duration>,
+ },
+}
+
+/// Represents a fence being signaled after a previous event.
+///
+/// Contrary to most other future types, it is possible to block the current thread until the event
+/// happens. This is done by calling the `wait()` function.
+///
+/// Also note that the `GpuFuture` trait is implemented on `Arc<FenceSignalFuture<_>>`.
+/// This means that you can put this future in an `Arc` and keep a copy of it somewhere in order
+/// to know when the execution reached that point.
+///
+/// ```
+/// use std::sync::Arc;
+/// use vulkano::sync::GpuFuture;
+///
+/// # let future: Box<GpuFuture> = return;
+/// // Assuming you have a chain of operations, like this:
+/// // let future = ...
+/// // .then_execute(foo)
+/// // .then_execute(bar)
+///
+/// // You can signal a fence at this point of the chain, and put the future in an `Arc`.
+/// let fence_signal = Arc::new(future.then_signal_fence());
+///
+/// // And then continue the chain:
+/// // fence_signal.clone()
+/// // .then_execute(baz)
+/// // .then_execute(qux)
+///
+/// // Later you can wait until you reach the point of `fence_signal`:
+/// fence_signal.wait(None).unwrap();
+/// ```
+#[must_use = "Dropping this object will immediately block the thread until the GPU has finished \
+ processing the submission"]
+pub struct FenceSignalFuture<F>
+where
+ F: GpuFuture,
+{
+ // Current state. See the docs of `FenceSignalFutureState`.
+ state: Mutex<FenceSignalFutureState<F>>,
+ // The device of the future.
+ device: Arc<Device>,
+ behavior: FenceSignalFutureBehavior,
+}
+
+// This future can be in three different states: pending (ie. newly-created), submitted (ie. the
+// command that submits the fence has been submitted), or cleaned (ie. the previous future has
+// been dropped).
+enum FenceSignalFutureState<F> {
+ // Newly-created. Not submitted yet.
+ Pending(F, Fence),
+
+ // Partially submitted to the queue. Only happens in situations where submitting requires two
+ // steps, and when the first step succeeded while the second step failed.
+ //
+ // Note that if there's ever a submit operation that needs three steps we will need to rework
+ // this code, as it was designed for two-step operations only.
+ PartiallyFlushed(F, Fence),
+
+ // Submitted to the queue.
+ Flushed(F, Fence),
+
+ // The submission is finished. The previous future and the fence have been cleaned.
+ Cleaned,
+
+ // A function panicked while the state was being modified. Should never happen.
+ Poisoned,
+}
+
+impl<F> FenceSignalFuture<F>
+where
+ F: GpuFuture,
+{
+ /// Blocks the current thread until the fence is signaled by the GPU. Performs a flush if
+ /// necessary.
+ ///
+ /// If `timeout` is `None`, then the wait is infinite. Otherwise the thread will unblock after
+ /// the specified timeout has elapsed and an error will be returned.
+ ///
+ /// If the wait is successful, this function also cleans any resource locked by previous
+ /// submissions.
+ pub fn wait(&self, timeout: Option<Duration>) -> Result<(), FlushError> {
+ let mut state = self.state.lock().unwrap();
+
+ self.flush_impl(&mut state)?;
+
+ match mem::replace(&mut *state, FenceSignalFutureState::Cleaned) {
+ FenceSignalFutureState::Flushed(previous, fence) => {
+ fence.wait(timeout)?;
+ unsafe {
+ previous.signal_finished();
+ }
+ Ok(())
+ }
+ FenceSignalFutureState::Cleaned => Ok(()),
+ _ => unreachable!(),
+ }
+ }
+}
+
+impl<F> FenceSignalFuture<F>
+where
+ F: GpuFuture,
+{
+ // Implementation of `cleanup_finished`, but takes a `&self` instead of a `&mut self`.
+ // This is an external function so that we can also call it from an `Arc<FenceSignalFuture>`.
+ #[inline]
+ fn cleanup_finished_impl(&self) {
+ let mut state = self.state.lock().unwrap();
+
+ match *state {
+ FenceSignalFutureState::Flushed(ref mut prev, ref fence) => {
+ match fence.wait(Some(Duration::from_secs(0))) {
+ Ok(()) => unsafe { prev.signal_finished() },
+ Err(_) => {
+ prev.cleanup_finished();
+ return;
+ }
+ }
+ }
+ FenceSignalFutureState::Pending(ref mut prev, _) => {
+ prev.cleanup_finished();
+ return;
+ }
+ FenceSignalFutureState::PartiallyFlushed(ref mut prev, _) => {
+ prev.cleanup_finished();
+ return;
+ }
+ _ => return,
+ };
+
+ // This code can only be reached if we're already flushed and waiting on the fence
+ // succeeded.
+ *state = FenceSignalFutureState::Cleaned;
+ }
+
+ // Implementation of `flush`. You must lock the state and pass the mutex guard here.
+ fn flush_impl(
+ &self,
+ state: &mut MutexGuard<FenceSignalFutureState<F>>,
+ ) -> Result<(), FlushError> {
+ unsafe {
+ // In this function we temporarily replace the current state with `Poisoned` at the
+ // beginning, and we take care to always put back a value into `state` before
+ // returning (even in case of error).
+ let old_state = mem::replace(&mut **state, FenceSignalFutureState::Poisoned);
+
+ let (previous, fence, partially_flushed) = match old_state {
+ FenceSignalFutureState::Pending(prev, fence) => (prev, fence, false),
+ FenceSignalFutureState::PartiallyFlushed(prev, fence) => (prev, fence, true),
+ other => {
+ // We were already flushed in the past, or we're already poisoned. Don't do
+ // anything.
+ **state = other;
+ return Ok(());
+ }
+ };
+
+ // TODO: meh for unwrap
+ let queue = previous.queue().unwrap().clone();
+
+ // There are three possible outcomes for the flush operation: success, partial success
+ // in which case `result` will contain `Err(OutcomeErr::Partial)`, or total failure
+ // in which case `result` will contain `Err(OutcomeErr::Full)`.
+ enum OutcomeErr<E> {
+ Partial(E),
+ Full(E),
+ }
+ let result = match previous.build_submission()? {
+ SubmitAnyBuilder::Empty => {
+ debug_assert!(!partially_flushed);
+ let mut b = SubmitCommandBufferBuilder::new();
+ b.set_fence_signal(&fence);
+ b.submit(&queue).map_err(|err| OutcomeErr::Full(err.into()))
+ }
+ SubmitAnyBuilder::SemaphoresWait(sem) => {
+ debug_assert!(!partially_flushed);
+ let b: SubmitCommandBufferBuilder = sem.into();
+ debug_assert!(!b.has_fence());
+ b.submit(&queue).map_err(|err| OutcomeErr::Full(err.into()))
+ }
+ SubmitAnyBuilder::CommandBuffer(mut cb_builder) => {
+ debug_assert!(!partially_flushed);
+ // The assert below could technically be a debug assertion as it is part of the
+ // safety contract of the trait. However it is easy to get this wrong if you
+ // write a custom implementation, and if so the consequences would be
+ // disastrous and hard to debug. Therefore we prefer to just use a regular
+ // assertion.
+ assert!(!cb_builder.has_fence());
+ cb_builder.set_fence_signal(&fence);
+ cb_builder
+ .submit(&queue)
+ .map_err(|err| OutcomeErr::Full(err.into()))
+ }
+ SubmitAnyBuilder::BindSparse(mut sparse) => {
+ debug_assert!(!partially_flushed);
+ // Same remark as `CommandBuffer`.
+ assert!(!sparse.has_fence());
+ sparse.set_fence_signal(&fence);
+ sparse
+ .submit(&queue)
+ .map_err(|err| OutcomeErr::Full(err.into()))
+ }
+ SubmitAnyBuilder::QueuePresent(present) => {
+ let intermediary_result = if partially_flushed {
+ Ok(())
+ } else {
+ present.submit(&queue)
+ };
+ match intermediary_result {
+ Ok(()) => {
+ let mut b = SubmitCommandBufferBuilder::new();
+ b.set_fence_signal(&fence);
+ b.submit(&queue)
+ .map_err(|err| OutcomeErr::Partial(err.into()))
+ }
+ Err(err) => Err(OutcomeErr::Full(err.into())),
+ }
+ }
+ };
+
+ // Restore the state before returning.
+ match result {
+ Ok(()) => {
+ **state = FenceSignalFutureState::Flushed(previous, fence);
+ Ok(())
+ }
+ Err(OutcomeErr::Partial(err)) => {
+ **state = FenceSignalFutureState::PartiallyFlushed(previous, fence);
+ Err(err)
+ }
+ Err(OutcomeErr::Full(err)) => {
+ **state = FenceSignalFutureState::Pending(previous, fence);
+ Err(err)
+ }
+ }
+ }
+ }
+}
+
+impl<F> FenceSignalFutureState<F> {
+ #[inline]
+ fn get_prev(&self) -> Option<&F> {
+ match *self {
+ FenceSignalFutureState::Pending(ref prev, _) => Some(prev),
+ FenceSignalFutureState::PartiallyFlushed(ref prev, _) => Some(prev),
+ FenceSignalFutureState::Flushed(ref prev, _) => Some(prev),
+ FenceSignalFutureState::Cleaned => None,
+ FenceSignalFutureState::Poisoned => None,
+ }
+ }
+}
+
+unsafe impl<F> GpuFuture for FenceSignalFuture<F>
+where
+ F: GpuFuture,
+{
+ #[inline]
+ fn cleanup_finished(&mut self) {
+ self.cleanup_finished_impl()
+ }
+
+ #[inline]
+ unsafe fn build_submission(&self) -> Result<SubmitAnyBuilder, FlushError> {
+ let mut state = self.state.lock().unwrap();
+ self.flush_impl(&mut state)?;
+
+ match *state {
+ FenceSignalFutureState::Flushed(_, ref fence) => match self.behavior {
+ FenceSignalFutureBehavior::Block { timeout } => {
+ fence.wait(timeout)?;
+ }
+ FenceSignalFutureBehavior::Continue => (),
+ },
+ FenceSignalFutureState::Cleaned | FenceSignalFutureState::Poisoned => (),
+ FenceSignalFutureState::Pending(_, _) => unreachable!(),
+ FenceSignalFutureState::PartiallyFlushed(_, _) => unreachable!(),
+ }
+
+ Ok(SubmitAnyBuilder::Empty)
+ }
+
+ #[inline]
+ fn flush(&self) -> Result<(), FlushError> {
+ let mut state = self.state.lock().unwrap();
+ self.flush_impl(&mut state)
+ }
+
+ #[inline]
+ unsafe fn signal_finished(&self) {
+ let state = self.state.lock().unwrap();
+ match *state {
+ FenceSignalFutureState::Flushed(ref prev, _) => {
+ prev.signal_finished();
+ }
+ FenceSignalFutureState::Cleaned | FenceSignalFutureState::Poisoned => (),
+ _ => unreachable!(),
+ }
+ }
+
+ #[inline]
+ fn queue_change_allowed(&self) -> bool {
+ match self.behavior {
+ FenceSignalFutureBehavior::Continue => {
+ let state = self.state.lock().unwrap();
+ if state.get_prev().is_some() {
+ false
+ } else {
+ true
+ }
+ }
+ FenceSignalFutureBehavior::Block { .. } => true,
+ }
+ }
+
+ #[inline]
+ fn queue(&self) -> Option<Arc<Queue>> {
+ let state = self.state.lock().unwrap();
+ if let Some(prev) = state.get_prev() {
+ prev.queue()
+ } else {
+ None
+ }
+ }
+
+ #[inline]
+ fn check_buffer_access(
+ &self,
+ buffer: &dyn BufferAccess,
+ exclusive: bool,
+ queue: &Queue,
+ ) -> Result<Option<(PipelineStages, AccessFlags)>, AccessCheckError> {
+ let state = self.state.lock().unwrap();
+ if let Some(previous) = state.get_prev() {
+ previous.check_buffer_access(buffer, exclusive, queue)
+ } else {
+ Err(AccessCheckError::Unknown)
+ }
+ }
+
+ #[inline]
+ fn check_image_access(
+ &self,
+ image: &dyn ImageAccess,
+ layout: ImageLayout,
+ exclusive: bool,
+ queue: &Queue,
+ ) -> Result<Option<(PipelineStages, AccessFlags)>, AccessCheckError> {
+ let state = self.state.lock().unwrap();
+ if let Some(previous) = state.get_prev() {
+ previous.check_image_access(image, layout, exclusive, queue)
+ } else {
+ Err(AccessCheckError::Unknown)
+ }
+ }
+}
+
+unsafe impl<F> DeviceOwned for FenceSignalFuture<F>
+where
+ F: GpuFuture,
+{
+ #[inline]
+ fn device(&self) -> &Arc<Device> {
+ &self.device
+ }
+}
+
+impl<F> Drop for FenceSignalFuture<F>
+where
+ F: GpuFuture,
+{
+ fn drop(&mut self) {
+ let mut state = self.state.lock().unwrap();
+
+ // We ignore any possible error while submitting for now. Problems are handled below.
+ let _ = self.flush_impl(&mut state);
+
+ match mem::replace(&mut *state, FenceSignalFutureState::Cleaned) {
+ FenceSignalFutureState::Flushed(previous, fence) => {
+ // This is a normal situation. Submitting worked.
+ // TODO: handle errors?
+ fence.wait(None).unwrap();
+ unsafe {
+ previous.signal_finished();
+ }
+ }
+ FenceSignalFutureState::Cleaned => {
+ // Also a normal situation. The user called `cleanup_finished()` before dropping.
+ }
+ FenceSignalFutureState::Poisoned => {
+ // The previous future was already dropped and blocked the current queue.
+ }
+ FenceSignalFutureState::Pending(_, _)
+ | FenceSignalFutureState::PartiallyFlushed(_, _) => {
+ // Flushing produced an error. There's nothing more we can do except drop the
+ // previous future and let it block the current queue.
+ }
+ }
+ }
+}
+
+unsafe impl<F> GpuFuture for Arc<FenceSignalFuture<F>>
+where
+ F: GpuFuture,
+{
+ #[inline]
+ fn cleanup_finished(&mut self) {
+ self.cleanup_finished_impl()
+ }
+
+ #[inline]
+ unsafe fn build_submission(&self) -> Result<SubmitAnyBuilder, FlushError> {
+ // Note that this is sound because we always return `SubmitAnyBuilder::Empty`. See the
+ // documentation of `build_submission`.
+ (**self).build_submission()
+ }
+
+ #[inline]
+ fn flush(&self) -> Result<(), FlushError> {
+ (**self).flush()
+ }
+
+ #[inline]
+ unsafe fn signal_finished(&self) {
+ (**self).signal_finished()
+ }
+
+ #[inline]
+ fn queue_change_allowed(&self) -> bool {
+ (**self).queue_change_allowed()
+ }
+
+ #[inline]
+ fn queue(&self) -> Option<Arc<Queue>> {
+ (**self).queue()
+ }
+
+ #[inline]
+ fn check_buffer_access(
+ &self,
+ buffer: &dyn BufferAccess,
+ exclusive: bool,
+ queue: &Queue,
+ ) -> Result<Option<(PipelineStages, AccessFlags)>, AccessCheckError> {
+ (**self).check_buffer_access(buffer, exclusive, queue)
+ }
+
+ #[inline]
+ fn check_image_access(
+ &self,
+ image: &dyn ImageAccess,
+ layout: ImageLayout,
+ exclusive: bool,
+ queue: &Queue,
+ ) -> Result<Option<(PipelineStages, AccessFlags)>, AccessCheckError> {
+ (**self).check_image_access(image, layout, exclusive, queue)
+ }
+}