| // 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) |
| } |
| } |