From 4470799247b4046dd7458bba4e2f011fe07b4411 Mon Sep 17 00:00:00 2001 From: Pierre Krieger Date: Sat, 8 Oct 2016 17:31:21 +0200 Subject: [PATCH] Various tweaks to submit --- vulkano/src/command_buffer/submit.rs | 163 ++++++++++++++++++++------- 1 file changed, 124 insertions(+), 39 deletions(-) diff --git a/vulkano/src/command_buffer/submit.rs b/vulkano/src/command_buffer/submit.rs index b3c70bb15..7f71a7c2b 100644 --- a/vulkano/src/command_buffer/submit.rs +++ b/vulkano/src/command_buffer/submit.rs @@ -8,11 +8,14 @@ // according to those terms. use std::fmt; +use std::marker::PhantomData; use std::ptr; use std::sync::Arc; use std::time::Duration; use smallvec::SmallVec; +use command_buffer::pool::CommandPool; +use command_buffer::sys::UnsafeCommandBuffer; use device::Device; use device::Queue; use sync::Fence; @@ -28,18 +31,27 @@ use SynchronizedVulkanObject; /// Trait for objects that can be submitted to the GPU. pub unsafe trait Submit { - /// Submits the command buffer. + /// Submits the object to the queue. /// /// Note that since submitting has a fixed overhead, you should try, if possible, to submit - /// multiple command buffers at once instead. - /// - /// This is a simple shortcut for creating a `Submit` object. + /// multiple command buffers at once instead. To do so, you can use the `chain` method. + // TODO: add example #[inline] fn submit(self, queue: &Arc) -> Submission where Self: Sized { submit(self, queue) } - /// Chains this `Submit` object with another one, so that both are submitted at once. + /// Consumes this object and another one to return a `SubmitChain` object that will submit both + /// at once. + /// + /// `self` will be executed first, and then `other` afterwards. + /// + /// # Panic + /// + /// - Panics if the two objects don't belong to the same `Device`. + /// + // TODO: add test for panic + // TODO: add example #[inline] fn chain(self, other: S) -> SubmitChain where Self: Sized, S: Submit { assert_eq!(self.device().internal_object(), @@ -50,8 +62,8 @@ pub unsafe trait Submit { /// Returns the device this object belongs to. fn device(&self) -> &Arc; - /// Called slightly before the object is submitted. The function must return a `SubmitBuilder` - /// containing the list of things to submit. + /// Called slightly before the object is submitted. The function must modify an existing + /// `SubmitBuilder` object to append the list of things to submit to it. /// /// # Safety for the caller /// @@ -63,8 +75,9 @@ pub unsafe trait Submit { /// /// # Safety for the implementation /// - /// To write. - unsafe fn append_submission(&self, base: SubmitBuilder, queue: &Arc) -> SubmitBuilder; + /// TODO: To write. + unsafe fn append_submission<'a>(&'a self, base: SubmitBuilder<'a>, queue: &Arc) + -> SubmitBuilder<'a>; } unsafe impl<'a, S> Submit for &'a S where S: Submit + 'a { @@ -74,7 +87,9 @@ unsafe impl<'a, S> Submit for &'a S where S: Submit + 'a { } #[inline] - unsafe fn append_submission(&self, base: SubmitBuilder, queue: &Arc) -> SubmitBuilder { + unsafe fn append_submission<'b>(&'b self, base: SubmitBuilder<'b>, queue: &Arc) + -> SubmitBuilder<'b> + { (**self).append_submission(base, queue) } } @@ -86,7 +101,9 @@ unsafe impl Submit for Box where S: Submit { } #[inline] - unsafe fn append_submission(&self, base: SubmitBuilder, queue: &Arc) -> SubmitBuilder { + unsafe fn append_submission<'a>(&'a self, base: SubmitBuilder<'a>, queue: &Arc) + -> SubmitBuilder<'a> + { (**self).append_submission(base, queue) } } @@ -98,20 +115,37 @@ unsafe impl Submit for Arc where S: Submit { } #[inline] - unsafe fn append_submission(&self, base: SubmitBuilder, queue: &Arc) -> SubmitBuilder { + unsafe fn append_submission<'a>(&'a self, base: SubmitBuilder<'a>, queue: &Arc) + -> SubmitBuilder<'a> + { (**self).append_submission(base, queue) } } /// Allows building a submission. +/// +/// This object contains a list of operations that the GPU should perform in order. You can add new +/// operations to the list with the various `add_*` methods. +/// +/// > **Note**: Command buffers submitted one after another are not executed in order. Instead they +/// > are only guarateed to *start* in the order they were added. The object that implements +/// > `Submit` should be aware of that fact and add appropriate pipeline barriers to the command +/// > buffers. +/// +/// # Safety +/// +/// While it is safe to build a `SubmitBuilder` from scratch, the only way to actually submit +/// something is through the `Submit` trait which is unsafe to implement. // TODO: can be optimized by storing all the semaphores in a single vec and all command buffers // in a single vec -pub struct SubmitBuilder { +// TODO: add sparse bindings and swapchain presents +pub struct SubmitBuilder<'a> { semaphores_storage: SmallVec<[vk::Semaphore; 16]>, dest_stages_storage: SmallVec<[vk::PipelineStageFlags; 8]>, command_buffers_storage: SmallVec<[vk::CommandBuffer; 4]>, submits: SmallVec<[SubmitBuilderSubmit; 2]>, keep_alive_semaphores: SmallVec<[Arc; 8]>, + marker: PhantomData<&'a ()>, } #[derive(Default)] @@ -120,25 +154,28 @@ struct SubmitBuilderSubmit { fence: Option>, } -impl SubmitBuilder { +impl<'a> SubmitBuilder<'a> { /// Builds a new empty `SubmitBuilder`. #[inline] - pub fn new() -> SubmitBuilder { + pub fn new() -> SubmitBuilder<'a> { SubmitBuilder { semaphores_storage: SmallVec::new(), dest_stages_storage: SmallVec::new(), command_buffers_storage: SmallVec::new(), submits: SmallVec::new(), keep_alive_semaphores: SmallVec::new(), + marker: PhantomData, } } - /// Adds a fence to signal. + /// Adds an operation that signals a fence. /// /// > **Note**: Due to the way the Vulkan API is designed, you are strongly encouraged to use /// > only one fence and signal at the very end of the submission. + /// + /// The fence is signalled after all previous operations of the `SubmitBuilder` are finished. #[inline] - pub fn add_fence(mut self, fence: Arc) -> SubmitBuilder { + pub fn add_fence_signal(mut self, fence: Arc) -> SubmitBuilder<'a> { if self.submits.last().map(|b| b.fence.is_some()).unwrap_or(true) { self.submits.push(Default::default()); } @@ -152,11 +189,21 @@ impl SubmitBuilder { self } - /// Adds a semaphore to wait upon. + /// Adds an operation that waits on a semaphore. + /// + /// Only the given `stages` of the command buffers added afterwards will wait upon + /// the semaphore. Other stages not included in `stages` can execute before waiting. + /// + /// The semaphore must be signalled by a previous submission. #[inline] pub fn add_wait_semaphore(mut self, semaphore: Arc, stages: PipelineStages) - -> SubmitBuilder + -> SubmitBuilder<'a> { + // TODO: check device of the semaphore with a debug_assert + // TODO: if stages contains tessellation or geometry stages, make sure the corresponding + // feature is active with a debug_assert + debug_assert!({ let f: vk::PipelineStageFlagBits = stages.into(); f != 0 }); + if self.submits.last().map(|b| b.fence.is_some()).unwrap_or(true) { self.submits.push(Default::default()); } @@ -179,10 +226,29 @@ impl SubmitBuilder { self } - // TODO: API shouldn't expose vk ; instead take a `&'a UnsafeCommandBuffer` where `'a` is a - // lifetime on `Submit::append_submission`. + /// Adds an operation that executes a command buffer. + /// + /// > **Note**: Command buffers submitted one after another are not executed in order. Instead + /// > they are only guarateed to *start* in the order they were added. The object that + /// > implements `Submit` should be aware of that fact and add appropriate pipeline barriers + /// > to the command buffers. + /// + /// Thanks to the lifetime requirement, the command buffer must outlive the `Submit` object + /// that builds this `SubmitBuilder`. Consequently keeping the `Submit` object alive is enough + /// to guarantee that the command buffer is kept alive as well. #[inline] - pub fn add_command_buffer(mut self, command_buffer: vk::CommandBuffer) -> SubmitBuilder { + pub fn add_command_buffer

(self, command_buffer: &'a UnsafeCommandBuffer

) + -> SubmitBuilder<'a> + where P: CommandPool + { + self.add_command_buffer_raw(command_buffer.internal_object()) + } + + // TODO: remove in favor of `add_command_buffer`? + #[inline] + pub fn add_command_buffer_raw(mut self, command_buffer: vk::CommandBuffer) + -> SubmitBuilder<'a> + { if self.submits.last().map(|b| b.fence.is_some()).unwrap_or(true) { self.submits.push(Default::default()); } @@ -200,9 +266,16 @@ impl SubmitBuilder { self } - /// Adds a semaphore to signal after all the previous elements have completed. + /// Adds an operation that signals a semaphore. + /// + /// The semaphore is signalled after all previous operations of the `SubmitBuilder` are + /// finished. + /// + /// The semaphore must be unsignalled. #[inline] - pub fn add_signal_semaphore(mut self, semaphore: Arc) -> SubmitBuilder { + pub fn add_signal_semaphore(mut self, semaphore: Arc) -> SubmitBuilder<'a> { + // TODO: check device of the semaphore with a debug_assert + if self.submits.last().map(|b| b.fence.is_some()).unwrap_or(true) { self.submits.push(Default::default()); } @@ -237,19 +310,24 @@ impl SubmitBuilder { } } -pub fn submit(submit: S, queue: &Arc) -> Submission +// Implementation for `Submit::submit`. +fn submit(submit: S, queue: &Arc) -> Submission where S: Submit { + let last_fence; + let keep_alive_semaphores; + unsafe { let mut builder = submit.append_submission(SubmitBuilder::new(), queue); + keep_alive_semaphores = builder.keep_alive_semaphores; - let last_fence = if let Some(last) = builder.submits.last_mut() { + last_fence = if let Some(last) = builder.submits.last_mut() { if last.fence.is_none() { last.fence = Some(Fence::new(submit.device().clone())); } last.fence.as_ref().unwrap().clone() - + } else { Fence::new(submit.device().clone()) // TODO: meh }; @@ -264,13 +342,17 @@ pub fn submit(submit: S, queue: &Arc) -> Submission for submit in builder.submits.iter_mut() { for batch in submit.batches.iter_mut() { - batch.pWaitSemaphores = builder.semaphores_storage.as_ptr().offset(next_semaphore); - batch.pWaitDstStageMask = builder.dest_stages_storage.as_ptr().offset(next_wait_stage); + batch.pWaitSemaphores = builder.semaphores_storage + .as_ptr().offset(next_semaphore); + batch.pWaitDstStageMask = builder.dest_stages_storage + .as_ptr().offset(next_wait_stage); next_semaphore += batch.waitSemaphoreCount as isize; next_wait_stage += batch.waitSemaphoreCount as isize; - batch.pCommandBuffers = builder.command_buffers_storage.as_ptr().offset(next_command_buffer); + batch.pCommandBuffers = builder.command_buffers_storage + .as_ptr().offset(next_command_buffer); next_command_buffer += batch.commandBufferCount as isize; - batch.pSignalSemaphores = builder.semaphores_storage.as_ptr().offset(next_semaphore); + batch.pSignalSemaphores = builder.semaphores_storage + .as_ptr().offset(next_semaphore); next_semaphore += batch.signalSemaphoreCount as isize; } @@ -284,16 +366,17 @@ pub fn submit(submit: S, queue: &Arc) -> Submission debug_assert_eq!(next_wait_stage as usize, builder.dest_stages_storage.len()); debug_assert_eq!(next_command_buffer as usize, builder.command_buffers_storage.len()); } + } - Submission { - queue: queue.clone(), - fence: last_fence, - keep_alive_semaphores: builder.keep_alive_semaphores, - submit: submit, - } + Submission { + queue: queue.clone(), + fence: last_fence, + keep_alive_semaphores: keep_alive_semaphores, + submit: submit, } } +/// Chain of two `Submit` objects. See `Submit::chain`. pub struct SubmitChain { first: A, second: B, @@ -309,13 +392,15 @@ unsafe impl Submit for SubmitChain where A: Submit, B: Submit { } #[inline] - unsafe fn append_submission(&self, base: SubmitBuilder, queue: &Arc) -> SubmitBuilder { + unsafe fn append_submission<'a>(&'a self, base: SubmitBuilder<'a>, queue: &Arc) + -> SubmitBuilder<'a> + { let builder = self.first.append_submission(base, queue); self.second.append_submission(builder, queue) } } -/// Returned when you submit one or multiple command buffers. +/// Returned when you submit something to a queue. /// /// This object holds the resources that are used by the GPU and that must be kept alive for at /// least as long as the GPU is executing the submission. Therefore destroying a `Submission`