diff options
Diffstat (limited to 'src/sync/future/mod.rs')
-rw-r--r-- | src/sync/future/mod.rs | 555 |
1 files changed, 555 insertions, 0 deletions
diff --git a/src/sync/future/mod.rs b/src/sync/future/mod.rs new file mode 100644 index 0000000..e6fde7a --- /dev/null +++ b/src/sync/future/mod.rs @@ -0,0 +1,555 @@ +// 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. + +pub use self::fence_signal::{FenceSignalFuture, FenceSignalFutureBehavior}; +pub use self::join::JoinFuture; +pub use self::now::{now, NowFuture}; +pub use self::semaphore_signal::SemaphoreSignalFuture; +use crate::buffer::BufferAccess; +use crate::command_buffer::submit::SubmitAnyBuilder; +use crate::command_buffer::submit::SubmitBindSparseError; +use crate::command_buffer::submit::SubmitCommandBufferError; +use crate::command_buffer::submit::SubmitPresentError; +use crate::command_buffer::CommandBufferExecError; +use crate::command_buffer::CommandBufferExecFuture; +use crate::command_buffer::PrimaryCommandBuffer; +use crate::device::DeviceOwned; +use crate::device::Queue; +use crate::image::ImageAccess; +use crate::image::ImageLayout; +use crate::swapchain; +use crate::swapchain::PresentFuture; +use crate::swapchain::PresentRegion; +use crate::swapchain::Swapchain; +use crate::sync::AccessFlags; +use crate::sync::FenceWaitError; +use crate::sync::PipelineStages; +use crate::OomError; +use std::error; +use std::fmt; +use std::sync::Arc; + +mod fence_signal; +mod join; +mod now; +mod semaphore_signal; + +/// Represents an event that will happen on the GPU in the future. +/// +/// See the documentation of the `sync` module for explanations about futures. +// TODO: consider switching all methods to take `&mut self` for optimization purposes +pub unsafe trait GpuFuture: DeviceOwned { + /// If possible, checks whether the submission has finished. If so, gives up ownership of the + /// resources used by these submissions. + /// + /// It is highly recommended to call `cleanup_finished` from time to time. Doing so will + /// prevent memory usage from increasing over time, and will also destroy the locks on + /// resources used by the GPU. + fn cleanup_finished(&mut self); + + /// Builds a submission that, if submitted, makes sure that the event represented by this + /// `GpuFuture` will happen, and possibly contains extra elements (eg. a semaphore wait or an + /// event wait) that makes the dependency with subsequent operations work. + /// + /// It is the responsibility of the caller to ensure that the submission is going to be + /// submitted only once. However keep in mind that this function can perfectly be called + /// multiple times (as long as the returned object is only submitted once). + /// Also note that calling `flush()` on the future may change the value returned by + /// `build_submission()`. + /// + /// It is however the responsibility of the implementation to not return the same submission + /// from multiple different future objects. For example if you implement `GpuFuture` on + /// `Arc<Foo>` then `build_submission()` must always return `SubmitAnyBuilder::Empty`, + /// otherwise it would be possible for the user to clone the `Arc` and make the same + /// submission be submitted multiple times. + /// + /// It is also the responsibility of the implementation to ensure that it works if you call + /// `build_submission()` and submits the returned value without calling `flush()` first. In + /// other words, `build_submission()` should perform an implicit flush if necessary. + /// + /// Once the caller has submitted the submission and has determined that the GPU has finished + /// executing it, it should call `signal_finished`. Failure to do so will incur a large runtime + /// overhead, as the future will have to block to make sure that it is finished. + unsafe fn build_submission(&self) -> Result<SubmitAnyBuilder, FlushError>; + + /// Flushes the future and submits to the GPU the actions that will permit this future to + /// occur. + /// + /// The implementation must remember that it was flushed. If the function is called multiple + /// times, only the first time must result in a flush. + fn flush(&self) -> Result<(), FlushError>; + + /// Sets the future to its "complete" state, meaning that it can safely be destroyed. + /// + /// This must only be done if you called `build_submission()`, submitted the returned + /// submission, and determined that it was finished. + /// + /// The implementation must be aware that this function can be called multiple times on the + /// same future. + unsafe fn signal_finished(&self); + + /// Returns the queue that triggers the event. Returns `None` if unknown or irrelevant. + /// + /// If this function returns `None` and `queue_change_allowed` returns `false`, then a panic + /// is likely to occur if you use this future. This is only a problem if you implement + /// the `GpuFuture` trait yourself for a type outside of vulkano. + fn queue(&self) -> Option<Arc<Queue>>; + + /// Returns `true` if elements submitted after this future can be submitted to a different + /// queue than the other returned by `queue()`. + fn queue_change_allowed(&self) -> bool; + + /// Checks whether submitting something after this future grants access (exclusive or shared, + /// depending on the parameter) to the given buffer on the given queue. + /// + /// If the access is granted, returns the pipeline stage and access flags of the latest usage + /// of this resource, or `None` if irrelevant. + /// + /// > **Note**: Returning `Ok` means "access granted", while returning `Err` means + /// > "don't know". Therefore returning `Err` is never unsafe. + fn check_buffer_access( + &self, + buffer: &dyn BufferAccess, + exclusive: bool, + queue: &Queue, + ) -> Result<Option<(PipelineStages, AccessFlags)>, AccessCheckError>; + + /// Checks whether submitting something after this future grants access (exclusive or shared, + /// depending on the parameter) to the given image on the given queue. + /// + /// If the access is granted, returns the pipeline stage and access flags of the latest usage + /// of this resource, or `None` if irrelevant. + /// + /// Implementations must ensure that the image is in the given layout. However if the `layout` + /// is `Undefined` then the implementation should accept any actual layout. + /// + /// > **Note**: Returning `Ok` means "access granted", while returning `Err` means + /// > "don't know". Therefore returning `Err` is never unsafe. + /// + /// > **Note**: Keep in mind that changing the layout of an image also requires exclusive + /// > access. + fn check_image_access( + &self, + image: &dyn ImageAccess, + layout: ImageLayout, + exclusive: bool, + queue: &Queue, + ) -> Result<Option<(PipelineStages, AccessFlags)>, AccessCheckError>; + + /// Joins this future with another one, representing the moment when both events have happened. + // TODO: handle errors + fn join<F>(self, other: F) -> JoinFuture<Self, F> + where + Self: Sized, + F: GpuFuture, + { + join::join(self, other) + } + + /// Executes a command buffer after this future. + /// + /// > **Note**: This is just a shortcut function. The actual implementation is in the + /// > `CommandBuffer` trait. + #[inline] + fn then_execute<Cb>( + self, + queue: Arc<Queue>, + command_buffer: Cb, + ) -> Result<CommandBufferExecFuture<Self, Cb>, CommandBufferExecError> + where + Self: Sized, + Cb: PrimaryCommandBuffer + 'static, + { + command_buffer.execute_after(self, queue) + } + + /// Executes a command buffer after this future, on the same queue as the future. + /// + /// > **Note**: This is just a shortcut function. The actual implementation is in the + /// > `CommandBuffer` trait. + #[inline] + fn then_execute_same_queue<Cb>( + self, + command_buffer: Cb, + ) -> Result<CommandBufferExecFuture<Self, Cb>, CommandBufferExecError> + where + Self: Sized, + Cb: PrimaryCommandBuffer + 'static, + { + let queue = self.queue().unwrap().clone(); + command_buffer.execute_after(self, queue) + } + + /// Signals a semaphore after this future. Returns another future that represents the signal. + /// + /// Call this function when you want to execute some operations on a queue and want to see the + /// result on another queue. + #[inline] + fn then_signal_semaphore(self) -> SemaphoreSignalFuture<Self> + where + Self: Sized, + { + semaphore_signal::then_signal_semaphore(self) + } + + /// Signals a semaphore after this future and flushes it. Returns another future that + /// represents the moment when the semaphore is signalled. + /// + /// This is a just a shortcut for `then_signal_semaphore()` followed with `flush()`. + /// + /// When you want to execute some operations A on a queue and some operations B on another + /// queue that need to see the results of A, it can be a good idea to submit A as soon as + /// possible while you're preparing B. + /// + /// If you ran A and B on the same queue, you would have to decide between submitting A then + /// B, or A and B simultaneously. Both approaches have their trade-offs. But if A and B are + /// on two different queues, then you would need two submits anyway and it is always + /// advantageous to submit A as soon as possible. + #[inline] + fn then_signal_semaphore_and_flush(self) -> Result<SemaphoreSignalFuture<Self>, FlushError> + where + Self: Sized, + { + let f = self.then_signal_semaphore(); + f.flush()?; + Ok(f) + } + + /// Signals a fence after this future. Returns another future that represents the signal. + /// + /// > **Note**: More often than not you want to immediately flush the future after calling this + /// > function. If so, consider using `then_signal_fence_and_flush`. + #[inline] + fn then_signal_fence(self) -> FenceSignalFuture<Self> + where + Self: Sized, + { + fence_signal::then_signal_fence(self, FenceSignalFutureBehavior::Continue) + } + + /// Signals a fence after this future. Returns another future that represents the signal. + /// + /// This is a just a shortcut for `then_signal_fence()` followed with `flush()`. + #[inline] + fn then_signal_fence_and_flush(self) -> Result<FenceSignalFuture<Self>, FlushError> + where + Self: Sized, + { + let f = self.then_signal_fence(); + f.flush()?; + Ok(f) + } + + /// Presents a swapchain image after this future. + /// + /// You should only ever do this indirectly after a `SwapchainAcquireFuture` of the same image, + /// otherwise an error will occur when flushing. + /// + /// > **Note**: This is just a shortcut for the `Swapchain::present()` function. + #[inline] + fn then_swapchain_present<W>( + self, + queue: Arc<Queue>, + swapchain: Arc<Swapchain<W>>, + image_index: usize, + ) -> PresentFuture<Self, W> + where + Self: Sized, + { + swapchain::present(swapchain, self, queue, image_index) + } + + /// Same as `then_swapchain_present`, except it allows specifying a present region. + /// + /// > **Note**: This is just a shortcut for the `Swapchain::present_incremental()` function. + #[inline] + fn then_swapchain_present_incremental<W>( + self, + queue: Arc<Queue>, + swapchain: Arc<Swapchain<W>>, + image_index: usize, + present_region: PresentRegion, + ) -> PresentFuture<Self, W> + where + Self: Sized, + { + swapchain::present_incremental(swapchain, self, queue, image_index, present_region) + } + + /// Turn the current future into a `Box<dyn GpuFuture>`. + /// + /// This is a helper function that calls `Box::new(yourFuture) as Box<dyn GpuFuture>`. + fn boxed(self) -> Box<dyn GpuFuture> + where + Self: Sized + 'static, + { + Box::new(self) as _ + } +} + +unsafe impl<F: ?Sized> GpuFuture for Box<F> +where + F: GpuFuture, +{ + #[inline] + fn cleanup_finished(&mut self) { + (**self).cleanup_finished() + } + + #[inline] + unsafe fn build_submission(&self) -> Result<SubmitAnyBuilder, FlushError> { + (**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) + } +} + +/// Access to a resource was denied. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum AccessError { + /// Exclusive access is denied. + ExclusiveDenied, + + /// The resource is already in use, and there is no tracking of concurrent usages. + AlreadyInUse, + + UnexpectedImageLayout { + allowed: ImageLayout, + requested: ImageLayout, + }, + + /// Trying to use an image without transitioning it from the "undefined" or "preinitialized" + /// layouts first. + ImageNotInitialized { + /// The layout that was requested for the image. + requested: ImageLayout, + }, + + /// Trying to use a buffer that still contains garbage data. + BufferNotInitialized, + + /// Trying to use a swapchain image without depending on a corresponding acquire image future. + SwapchainImageAcquireOnly, +} + +impl error::Error for AccessError {} + +impl fmt::Display for AccessError { + #[inline] + fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { + write!( + fmt, + "{}", + match *self { + AccessError::ExclusiveDenied => "only shared access is allowed for this resource", + AccessError::AlreadyInUse => { + "the resource is already in use, and there is no tracking of concurrent usages" + } + AccessError::UnexpectedImageLayout { .. } => { + unimplemented!() // TODO: find a description + } + AccessError::ImageNotInitialized { .. } => { + "trying to use an image without transitioning it from the undefined or \ + preinitialized layouts first" + } + AccessError::BufferNotInitialized => { + "trying to use a buffer that still contains garbage data" + } + AccessError::SwapchainImageAcquireOnly => { + "trying to use a swapchain image without depending on a corresponding acquire \ + image future" + } + } + ) + } +} + +/// Error that can happen when checking whether we have access to a resource. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum AccessCheckError { + /// Access to the resource has been denied. + Denied(AccessError), + /// The resource is unknown, therefore we cannot possibly answer whether we have access or not. + Unknown, +} + +impl error::Error for AccessCheckError {} + +impl fmt::Display for AccessCheckError { + #[inline] + fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { + write!( + fmt, + "{}", + match *self { + AccessCheckError::Denied(_) => "access to the resource has been denied", + AccessCheckError::Unknown => "the resource is unknown", + } + ) + } +} + +impl From<AccessError> for AccessCheckError { + #[inline] + fn from(err: AccessError) -> AccessCheckError { + AccessCheckError::Denied(err) + } +} + +/// Error that can happen when creating a graphics pipeline. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum FlushError { + /// Access to a resource has been denied. + AccessError(AccessError), + + /// Not enough memory. + OomError(OomError), + + /// The connection to the device has been lost. + DeviceLost, + + /// The surface is no longer accessible and must be recreated. + SurfaceLost, + + /// The surface has changed in a way that makes the swapchain unusable. You must query the + /// surface's new properties and recreate a new swapchain if you want to continue drawing. + OutOfDate, + + /// The swapchain has lost or doesn't have fullscreen exclusivity possibly for + /// implementation-specific reasons outside of the application’s control. + FullscreenExclusiveLost, + + /// The flush operation needed to block, but the timeout has elapsed. + Timeout, +} + +impl error::Error for FlushError { + #[inline] + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + match *self { + FlushError::AccessError(ref err) => Some(err), + FlushError::OomError(ref err) => Some(err), + _ => None, + } + } +} + +impl fmt::Display for FlushError { + #[inline] + fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { + write!( + fmt, + "{}", + match *self { + FlushError::AccessError(_) => "access to a resource has been denied", + FlushError::OomError(_) => "not enough memory", + FlushError::DeviceLost => "the connection to the device has been lost", + FlushError::SurfaceLost => "the surface of this swapchain is no longer valid", + FlushError::OutOfDate => "the swapchain needs to be recreated", + FlushError::FullscreenExclusiveLost => { + "the swapchain no longer has fullscreen exclusivity" + } + FlushError::Timeout => { + "the flush operation needed to block, but the timeout has \ + elapsed" + } + } + ) + } +} + +impl From<AccessError> for FlushError { + #[inline] + fn from(err: AccessError) -> FlushError { + FlushError::AccessError(err) + } +} + +impl From<SubmitPresentError> for FlushError { + #[inline] + fn from(err: SubmitPresentError) -> FlushError { + match err { + SubmitPresentError::OomError(err) => FlushError::OomError(err), + SubmitPresentError::DeviceLost => FlushError::DeviceLost, + SubmitPresentError::SurfaceLost => FlushError::SurfaceLost, + SubmitPresentError::OutOfDate => FlushError::OutOfDate, + SubmitPresentError::FullscreenExclusiveLost => FlushError::FullscreenExclusiveLost, + } + } +} + +impl From<SubmitCommandBufferError> for FlushError { + #[inline] + fn from(err: SubmitCommandBufferError) -> FlushError { + match err { + SubmitCommandBufferError::OomError(err) => FlushError::OomError(err), + SubmitCommandBufferError::DeviceLost => FlushError::DeviceLost, + } + } +} + +impl From<SubmitBindSparseError> for FlushError { + #[inline] + fn from(err: SubmitBindSparseError) -> FlushError { + match err { + SubmitBindSparseError::OomError(err) => FlushError::OomError(err), + SubmitBindSparseError::DeviceLost => FlushError::DeviceLost, + } + } +} + +impl From<FenceWaitError> for FlushError { + #[inline] + fn from(err: FenceWaitError) -> FlushError { + match err { + FenceWaitError::OomError(err) => FlushError::OomError(err), + FenceWaitError::Timeout => FlushError::Timeout, + FenceWaitError::DeviceLostError => FlushError::DeviceLost, + } + } +} |