diff options
Diffstat (limited to 'src/command_buffer/traits.rs')
-rw-r--r-- | src/command_buffer/traits.rs | 528 |
1 files changed, 528 insertions, 0 deletions
diff --git a/src/command_buffer/traits.rs b/src/command_buffer/traits.rs new file mode 100644 index 0000000..5252073 --- /dev/null +++ b/src/command_buffer/traits.rs @@ -0,0 +1,528 @@ +// 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::buffer::BufferAccess; +use crate::command_buffer::submit::SubmitAnyBuilder; +use crate::command_buffer::submit::SubmitCommandBufferBuilder; +use crate::command_buffer::sys::UnsafeCommandBuffer; +use crate::command_buffer::CommandBufferInheritance; +use crate::command_buffer::ImageUninitializedSafe; +use crate::device::Device; +use crate::device::DeviceOwned; +use crate::device::Queue; +use crate::image::ImageAccess; +use crate::image::ImageLayout; +use crate::render_pass::FramebufferAbstract; +use crate::sync::now; +use crate::sync::AccessCheckError; +use crate::sync::AccessError; +use crate::sync::AccessFlags; +use crate::sync::FlushError; +use crate::sync::GpuFuture; +use crate::sync::NowFuture; +use crate::sync::PipelineMemoryAccess; +use crate::sync::PipelineStages; +use crate::SafeDeref; +use crate::VulkanObject; +use std::borrow::Cow; +use std::error; +use std::fmt; +use std::sync::atomic::AtomicBool; +use std::sync::atomic::Ordering; +use std::sync::Arc; +use std::sync::Mutex; + +pub unsafe trait PrimaryCommandBuffer: DeviceOwned { + /// Returns the underlying `UnsafeCommandBuffer` of this command buffer. + fn inner(&self) -> &UnsafeCommandBuffer; + + /// Checks whether this command buffer is allowed to be submitted after the `future` and on + /// the given queue, and if so locks it. + /// + /// If you call this function, then you should call `unlock` afterwards. + fn lock_submit( + &self, + future: &dyn GpuFuture, + queue: &Queue, + ) -> Result<(), CommandBufferExecError>; + + /// Unlocks the command buffer. Should be called once for each call to `lock_submit`. + /// + /// # Safety + /// + /// Must not be called if you haven't called `lock_submit` before. + unsafe fn unlock(&self); + + /// Executes this command buffer on a queue. + /// + /// This function returns an object that implements the `GpuFuture` trait. See the + /// documentation of the `sync` module for more information. + /// + /// The command buffer is not actually executed until you call `flush()` on the object. + /// You are encouraged to chain together as many futures as possible before calling `flush()`, + /// and call `.then_signal_future()` before doing so. Note however that once you called + /// `execute()` there is no way to cancel the execution, even if you didn't flush yet. + /// + /// > **Note**: In the future this function may return `-> impl GpuFuture` instead of a + /// > concrete type. + /// + /// > **Note**: This is just a shortcut for `execute_after(vulkano::sync::now(), queue)`. + /// + /// # Panic + /// + /// Panics if the device of the command buffer is not the same as the device of the future. + #[inline] + fn execute( + self, + queue: Arc<Queue>, + ) -> Result<CommandBufferExecFuture<NowFuture, Self>, CommandBufferExecError> + where + Self: Sized + 'static, + { + let device = queue.device().clone(); + self.execute_after(now(device), queue) + } + + /// Executes the command buffer after an existing future. + /// + /// This function returns an object that implements the `GpuFuture` trait. See the + /// documentation of the `sync` module for more information. + /// + /// The command buffer is not actually executed until you call `flush()` on the object. + /// You are encouraged to chain together as many futures as possible before calling `flush()`, + /// and call `.then_signal_future()` before doing so. Note however that once you called + /// `execute()` there is no way to cancel the execution, even if you didn't flush yet. + /// + /// > **Note**: In the future this function may return `-> impl GpuFuture` instead of a + /// > concrete type. + /// + /// This function requires the `'static` lifetime to be on the command buffer. This is because + /// this function returns a `CommandBufferExecFuture` whose job is to lock resources and keep + /// them alive while they are in use by the GPU. If `'static` wasn't required, you could call + /// `std::mem::forget` on that object and "unlock" these resources. For more information about + /// this problem, search the web for "rust thread scoped leakpocalypse". + /// + /// # Panic + /// + /// Panics if the device of the command buffer is not the same as the device of the future. + #[inline] + fn execute_after<F>( + self, + future: F, + queue: Arc<Queue>, + ) -> Result<CommandBufferExecFuture<F, Self>, CommandBufferExecError> + where + Self: Sized + 'static, + F: GpuFuture, + { + assert_eq!( + self.device().internal_object(), + future.device().internal_object() + ); + + if !future.queue_change_allowed() { + assert!(future.queue().unwrap().is_same(&queue)); + } + + self.lock_submit(&future, &queue)?; + + Ok(CommandBufferExecFuture { + previous: future, + command_buffer: self, + queue, + submitted: Mutex::new(false), + finished: AtomicBool::new(false), + }) + } + + fn check_buffer_access( + &self, + buffer: &dyn BufferAccess, + exclusive: bool, + queue: &Queue, + ) -> Result<Option<(PipelineStages, AccessFlags)>, AccessCheckError>; + + fn check_image_access( + &self, + image: &dyn ImageAccess, + layout: ImageLayout, + exclusive: bool, + queue: &Queue, + ) -> Result<Option<(PipelineStages, AccessFlags)>, AccessCheckError>; +} + +unsafe impl<T> PrimaryCommandBuffer for T +where + T: SafeDeref, + T::Target: PrimaryCommandBuffer, +{ + #[inline] + fn inner(&self) -> &UnsafeCommandBuffer { + (**self).inner() + } + + #[inline] + fn lock_submit( + &self, + future: &dyn GpuFuture, + queue: &Queue, + ) -> Result<(), CommandBufferExecError> { + (**self).lock_submit(future, queue) + } + + #[inline] + unsafe fn unlock(&self) { + (**self).unlock(); + } + + #[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) + } +} + +pub unsafe trait SecondaryCommandBuffer: DeviceOwned { + /// Returns the underlying `UnsafeCommandBuffer` of this command buffer. + fn inner(&self) -> &UnsafeCommandBuffer; + + /// Checks whether this command buffer is allowed to be recorded to a command buffer, + /// and if so locks it. + /// + /// If you call this function, then you should call `unlock` afterwards. + fn lock_record(&self) -> Result<(), CommandBufferExecError>; + + /// Unlocks the command buffer. Should be called once for each call to `lock_record`. + /// + /// # Safety + /// + /// Must not be called if you haven't called `lock_record` before. + unsafe fn unlock(&self); + + /// Returns a `CommandBufferInheritance` value describing the properties that the command + /// buffer inherits from its parent primary command buffer. + fn inheritance(&self) -> CommandBufferInheritance<&dyn FramebufferAbstract>; + + /// Returns the number of buffers accessed by this command buffer. + fn num_buffers(&self) -> usize; + + /// Returns the `index`th buffer of this command buffer, or `None` if out of range. + /// + /// The valid range is between 0 and `num_buffers()`. + fn buffer(&self, index: usize) -> Option<(&dyn BufferAccess, PipelineMemoryAccess)>; + + /// Returns the number of images accessed by this command buffer. + fn num_images(&self) -> usize; + + /// Returns the `index`th image of this command buffer, or `None` if out of range. + /// + /// The valid range is between 0 and `num_images()`. + fn image( + &self, + index: usize, + ) -> Option<( + &dyn ImageAccess, + PipelineMemoryAccess, + ImageLayout, + ImageLayout, + ImageUninitializedSafe, + )>; +} + +unsafe impl<T> SecondaryCommandBuffer for T +where + T: SafeDeref, + T::Target: SecondaryCommandBuffer, +{ + #[inline] + fn inner(&self) -> &UnsafeCommandBuffer { + (**self).inner() + } + + #[inline] + fn lock_record(&self) -> Result<(), CommandBufferExecError> { + (**self).lock_record() + } + + #[inline] + unsafe fn unlock(&self) { + (**self).unlock(); + } + + #[inline] + fn inheritance(&self) -> CommandBufferInheritance<&dyn FramebufferAbstract> { + (**self).inheritance() + } + + #[inline] + fn num_buffers(&self) -> usize { + (**self).num_buffers() + } + + #[inline] + fn buffer(&self, index: usize) -> Option<(&dyn BufferAccess, PipelineMemoryAccess)> { + (**self).buffer(index) + } + + #[inline] + fn num_images(&self) -> usize { + (**self).num_images() + } + + #[inline] + fn image( + &self, + index: usize, + ) -> Option<( + &dyn ImageAccess, + PipelineMemoryAccess, + ImageLayout, + ImageLayout, + ImageUninitializedSafe, + )> { + (**self).image(index) + } +} + +/// Represents a command buffer being executed by the GPU and the moment when the execution +/// finishes. +#[must_use = "Dropping this object will immediately block the thread until the GPU has finished processing the submission"] +pub struct CommandBufferExecFuture<F, Cb> +where + F: GpuFuture, + Cb: PrimaryCommandBuffer, +{ + previous: F, + command_buffer: Cb, + queue: Arc<Queue>, + // True if the command buffer has already been submitted. + // If flush is called multiple times, we want to block so that only one flushing is executed. + // Therefore we use a `Mutex<bool>` and not an `AtomicBool`. + submitted: Mutex<bool>, + finished: AtomicBool, +} + +unsafe impl<F, Cb> GpuFuture for CommandBufferExecFuture<F, Cb> +where + F: GpuFuture, + Cb: PrimaryCommandBuffer, +{ + #[inline] + fn cleanup_finished(&mut self) { + self.previous.cleanup_finished(); + } + + unsafe fn build_submission(&self) -> Result<SubmitAnyBuilder, FlushError> { + Ok(match self.previous.build_submission()? { + SubmitAnyBuilder::Empty => { + let mut builder = SubmitCommandBufferBuilder::new(); + builder.add_command_buffer(self.command_buffer.inner()); + SubmitAnyBuilder::CommandBuffer(builder) + } + SubmitAnyBuilder::SemaphoresWait(sem) => { + let mut builder: SubmitCommandBufferBuilder = sem.into(); + builder.add_command_buffer(self.command_buffer.inner()); + SubmitAnyBuilder::CommandBuffer(builder) + } + SubmitAnyBuilder::CommandBuffer(mut builder) => { + // FIXME: add pipeline barrier + builder.add_command_buffer(self.command_buffer.inner()); + SubmitAnyBuilder::CommandBuffer(builder) + } + SubmitAnyBuilder::QueuePresent(_) | SubmitAnyBuilder::BindSparse(_) => { + unimplemented!() // TODO: + /*present.submit(); // TODO: wrong + let mut builder = SubmitCommandBufferBuilder::new(); + builder.add_command_buffer(self.command_buffer.inner()); + SubmitAnyBuilder::CommandBuffer(builder)*/ + } + }) + } + + #[inline] + fn flush(&self) -> Result<(), FlushError> { + unsafe { + let mut submitted = self.submitted.lock().unwrap(); + if *submitted { + return Ok(()); + } + + let queue = self.queue.clone(); + + match self.build_submission()? { + SubmitAnyBuilder::Empty => {} + SubmitAnyBuilder::CommandBuffer(builder) => { + builder.submit(&queue)?; + } + _ => unreachable!(), + }; + + // Only write `true` here in order to try again next time if we failed to submit. + *submitted = true; + Ok(()) + } + } + + #[inline] + unsafe fn signal_finished(&self) { + if self.finished.swap(true, Ordering::SeqCst) == false { + self.command_buffer.unlock(); + } + + self.previous.signal_finished(); + } + + #[inline] + fn queue_change_allowed(&self) -> bool { + false + } + + #[inline] + fn queue(&self) -> Option<Arc<Queue>> { + Some(self.queue.clone()) + } + + #[inline] + fn check_buffer_access( + &self, + buffer: &dyn BufferAccess, + exclusive: bool, + queue: &Queue, + ) -> Result<Option<(PipelineStages, AccessFlags)>, AccessCheckError> { + match self + .command_buffer + .check_buffer_access(buffer, exclusive, queue) + { + Ok(v) => Ok(v), + Err(AccessCheckError::Denied(err)) => Err(AccessCheckError::Denied(err)), + Err(AccessCheckError::Unknown) => { + self.previous.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> { + match self + .command_buffer + .check_image_access(image, layout, exclusive, queue) + { + Ok(v) => Ok(v), + Err(AccessCheckError::Denied(err)) => Err(AccessCheckError::Denied(err)), + Err(AccessCheckError::Unknown) => self + .previous + .check_image_access(image, layout, exclusive, queue), + } + } +} + +unsafe impl<F, Cb> DeviceOwned for CommandBufferExecFuture<F, Cb> +where + F: GpuFuture, + Cb: PrimaryCommandBuffer, +{ + #[inline] + fn device(&self) -> &Arc<Device> { + self.command_buffer.device() + } +} + +impl<F, Cb> Drop for CommandBufferExecFuture<F, Cb> +where + F: GpuFuture, + Cb: PrimaryCommandBuffer, +{ + fn drop(&mut self) { + unsafe { + if !*self.finished.get_mut() { + // TODO: handle errors? + self.flush().unwrap(); + // Block until the queue finished. + self.queue.wait().unwrap(); + self.command_buffer.unlock(); + self.previous.signal_finished(); + } + } + } +} + +/// Error that can happen when attempting to execute a command buffer. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum CommandBufferExecError { + /// Access to a resource has been denied. + AccessError { + error: AccessError, + command_name: Cow<'static, str>, + command_param: Cow<'static, str>, + command_offset: usize, + }, + + /// The command buffer or one of the secondary command buffers it executes was created with the + /// "one time submit" flag, but has already been submitted it the past. + OneTimeSubmitAlreadySubmitted, + + /// The command buffer or one of the secondary command buffers it executes is already in use by + /// the GPU and was not created with the "concurrent" flag. + ExclusiveAlreadyInUse, + // TODO: missing entries (eg. wrong queue family, secondary command buffer) +} + +impl error::Error for CommandBufferExecError { + #[inline] + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + match *self { + CommandBufferExecError::AccessError { ref error, .. } => Some(error), + _ => None, + } + } +} + +impl fmt::Display for CommandBufferExecError { + #[inline] + fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { + write!( + fmt, + "{}", + match *self { + CommandBufferExecError::AccessError { .. } => + "access to a resource has been denied", + CommandBufferExecError::OneTimeSubmitAlreadySubmitted => { + "the command buffer or one of the secondary command buffers it executes was \ + created with the \"one time submit\" flag, but has already been submitted in \ + the past" + } + CommandBufferExecError::ExclusiveAlreadyInUse => { + "the command buffer or one of the secondary command buffers it executes is \ + already in use was not created with the \"concurrent\" flag" + } + } + ) + } +} |