diff options
Diffstat (limited to 'src/command_buffer/submit/queue_submit.rs')
-rw-r--r-- | src/command_buffer/submit/queue_submit.rs | 359 |
1 files changed, 359 insertions, 0 deletions
diff --git a/src/command_buffer/submit/queue_submit.rs b/src/command_buffer/submit/queue_submit.rs new file mode 100644 index 0000000..8ffa129 --- /dev/null +++ b/src/command_buffer/submit/queue_submit.rs @@ -0,0 +1,359 @@ +// 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 crate::check_errors; +use crate::command_buffer::sys::UnsafeCommandBuffer; +use crate::device::Queue; +use crate::sync::Fence; +use crate::sync::PipelineStages; +use crate::sync::Semaphore; +use crate::Error; +use crate::OomError; +use crate::SynchronizedVulkanObject; +use crate::VulkanObject; +use smallvec::SmallVec; +use std::error; +use std::fmt; +use std::marker::PhantomData; + +/// Prototype for a submission that executes command buffers. +// TODO: example here +#[derive(Debug)] +pub struct SubmitCommandBufferBuilder<'a> { + wait_semaphores: SmallVec<[ash::vk::Semaphore; 16]>, + destination_stages: SmallVec<[ash::vk::PipelineStageFlags; 8]>, + signal_semaphores: SmallVec<[ash::vk::Semaphore; 16]>, + command_buffers: SmallVec<[ash::vk::CommandBuffer; 4]>, + fence: ash::vk::Fence, + marker: PhantomData<&'a ()>, +} + +impl<'a> SubmitCommandBufferBuilder<'a> { + /// Builds a new empty `SubmitCommandBufferBuilder`. + #[inline] + pub fn new() -> SubmitCommandBufferBuilder<'a> { + SubmitCommandBufferBuilder { + wait_semaphores: SmallVec::new(), + destination_stages: SmallVec::new(), + signal_semaphores: SmallVec::new(), + command_buffers: SmallVec::new(), + fence: ash::vk::Fence::null(), + marker: PhantomData, + } + } + + /// Returns true if this builder will signal a fence when submitted. + /// + /// # Example + /// + /// ``` + /// use vulkano::command_buffer::submit::SubmitCommandBufferBuilder; + /// use vulkano::sync::Fence; + /// # let device: std::sync::Arc<vulkano::device::Device> = return; + /// + /// unsafe { + /// let fence = Fence::from_pool(device.clone()).unwrap(); + /// + /// let mut builder = SubmitCommandBufferBuilder::new(); + /// assert!(!builder.has_fence()); + /// builder.set_fence_signal(&fence); + /// assert!(builder.has_fence()); + /// } + /// ``` + #[inline] + pub fn has_fence(&self) -> bool { + self.fence != ash::vk::Fence::null() + } + + /// Adds an operation that signals a fence after this submission ends. + /// + /// # Example + /// + /// ``` + /// use std::time::Duration; + /// use vulkano::command_buffer::submit::SubmitCommandBufferBuilder; + /// use vulkano::sync::Fence; + /// # let device: std::sync::Arc<vulkano::device::Device> = return; + /// # let queue: std::sync::Arc<vulkano::device::Queue> = return; + /// + /// unsafe { + /// let fence = Fence::from_pool(device.clone()).unwrap(); + /// + /// let mut builder = SubmitCommandBufferBuilder::new(); + /// builder.set_fence_signal(&fence); + /// + /// builder.submit(&queue).unwrap(); + /// + /// // We must not destroy the fence before it is signaled. + /// fence.wait(Some(Duration::from_secs(5))).unwrap(); + /// } + /// ``` + /// + /// # Safety + /// + /// - The fence must not be signaled at the time when you call `submit()`. + /// + /// - If you use the fence for multiple submissions, only one at a time must be executed by the + /// GPU. In other words, you must submit one, wait for the fence to be signaled, then reset + /// the fence, and then only submit the second. + /// + /// - If you submit this builder, the fence must be kept alive until it is signaled by the GPU. + /// Destroying the fence earlier is an undefined behavior. + /// + /// - The fence, command buffers, and semaphores must all belong to the same device. + /// + #[inline] + pub unsafe fn set_fence_signal(&mut self, fence: &'a Fence) { + self.fence = fence.internal_object(); + } + + /// Adds a semaphore to be waited upon before the command buffers are executed. + /// + /// 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. + /// + /// # Safety + /// + /// - The stages must be supported by the device. + /// + /// - If you submit this builder, the semaphore must be kept alive until you are guaranteed + /// that the GPU has at least started executing the command buffers. + /// + /// - If you submit this builder, no other queue must be waiting on these semaphores. In other + /// words, each semaphore signal can only correspond to one semaphore wait. + /// + /// - If you submit this builder, the semaphores must be signaled when the queue execution + /// reaches this submission, or there must be one or more submissions in queues that are + /// going to signal these semaphores. In other words, you must not block the queue with + /// semaphores that can't get signaled. + /// + /// - The fence, command buffers, and semaphores must all belong to the same device. + /// + #[inline] + pub unsafe fn add_wait_semaphore(&mut self, semaphore: &'a Semaphore, stages: PipelineStages) { + debug_assert!(!ash::vk::PipelineStageFlags::from(stages).is_empty()); + // TODO: debug assert that the device supports the stages + self.wait_semaphores.push(semaphore.internal_object()); + self.destination_stages.push(stages.into()); + } + + /// Adds a command buffer that is executed as part of this command. + /// + /// The command buffers are submitted in the order in which they are added. + /// + /// # Safety + /// + /// - If you submit this builder, the command buffer must be kept alive until you are + /// guaranteed that the GPU has finished executing it. + /// + /// - Any calls to vkCmdSetEvent, vkCmdResetEvent or vkCmdWaitEvents that have been recorded + /// into the command buffer must not reference any VkEvent that is referenced by any of + /// those commands that is pending execution on another queue. + /// TODO: rephrase ^ ? + /// + /// - The fence, command buffers, and semaphores must all belong to the same device. + /// + /// TODO: more here + /// + #[inline] + pub unsafe fn add_command_buffer(&mut self, command_buffer: &'a UnsafeCommandBuffer) { + self.command_buffers.push(command_buffer.internal_object()); + } + + /// Returns the number of semaphores to signal. + /// + /// In other words, this is the number of times `add_signal_semaphore` has been called. + #[inline] + pub fn num_signal_semaphores(&self) -> usize { + self.signal_semaphores.len() + } + + /// Adds a semaphore that is going to be signaled at the end of the submission. + /// + /// # Safety + /// + /// - If you submit this builder, the semaphore must be kept alive until you are guaranteed + /// that the GPU has finished executing this submission. + /// + /// - The semaphore must be in the unsignaled state when queue execution reaches this + /// submission. + /// + /// - The fence, command buffers, and semaphores must all belong to the same device. + /// + #[inline] + pub unsafe fn add_signal_semaphore(&mut self, semaphore: &'a Semaphore) { + self.signal_semaphores.push(semaphore.internal_object()); + } + + /// Submits the command buffer to the given queue. + /// + /// > **Note**: This is an expensive operation, so you may want to merge as many builders as + /// > possible together and avoid submitting them one by one. + /// + pub fn submit(self, queue: &Queue) -> Result<(), SubmitCommandBufferError> { + unsafe { + let fns = queue.device().fns(); + let queue = queue.internal_object_guard(); + + debug_assert_eq!(self.wait_semaphores.len(), self.destination_stages.len()); + + let batch = ash::vk::SubmitInfo { + wait_semaphore_count: self.wait_semaphores.len() as u32, + p_wait_semaphores: self.wait_semaphores.as_ptr(), + p_wait_dst_stage_mask: self.destination_stages.as_ptr(), + command_buffer_count: self.command_buffers.len() as u32, + p_command_buffers: self.command_buffers.as_ptr(), + signal_semaphore_count: self.signal_semaphores.len() as u32, + p_signal_semaphores: self.signal_semaphores.as_ptr(), + ..Default::default() + }; + + check_errors(fns.v1_0.queue_submit(*queue, 1, &batch, self.fence))?; + Ok(()) + } + } + + /// Merges this builder with another builder. + /// + /// # Panic + /// + /// Panics if both builders have a fence already set. + // TODO: create multiple batches instead + pub fn merge(mut self, other: Self) -> Self { + assert!( + self.fence == ash::vk::Fence::null() || other.fence == ash::vk::Fence::null(), + "Can't merge two queue submits that both have a fence" + ); + + self.wait_semaphores.extend(other.wait_semaphores); + self.destination_stages.extend(other.destination_stages); // TODO: meh? will be solved if we submit multiple batches + self.signal_semaphores.extend(other.signal_semaphores); + self.command_buffers.extend(other.command_buffers); + + if self.fence == ash::vk::Fence::null() { + self.fence = other.fence; + } + + self + } +} + +/// Error that can happen when submitting the prototype. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[repr(u32)] +pub enum SubmitCommandBufferError { + /// Not enough memory. + OomError(OomError), + + /// The connection to the device has been lost. + DeviceLost, +} + +impl error::Error for SubmitCommandBufferError { + #[inline] + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + match *self { + SubmitCommandBufferError::OomError(ref err) => Some(err), + _ => None, + } + } +} + +impl fmt::Display for SubmitCommandBufferError { + #[inline] + fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { + write!( + fmt, + "{}", + match *self { + SubmitCommandBufferError::OomError(_) => "not enough memory", + SubmitCommandBufferError::DeviceLost => + "the connection to the device has been lost", + } + ) + } +} + +impl From<Error> for SubmitCommandBufferError { + #[inline] + fn from(err: Error) -> SubmitCommandBufferError { + match err { + err @ Error::OutOfHostMemory => SubmitCommandBufferError::OomError(OomError::from(err)), + err @ Error::OutOfDeviceMemory => { + SubmitCommandBufferError::OomError(OomError::from(err)) + } + Error::DeviceLost => SubmitCommandBufferError::DeviceLost, + _ => panic!("unexpected error: {:?}", err), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::sync::Fence; + use std::time::Duration; + + #[test] + fn empty_submit() { + let (device, queue) = gfx_dev_and_queue!(); + let builder = SubmitCommandBufferBuilder::new(); + builder.submit(&queue).unwrap(); + } + + #[test] + fn signal_fence() { + unsafe { + let (device, queue) = gfx_dev_and_queue!(); + + let fence = Fence::alloc(device.clone()).unwrap(); + assert!(!fence.ready().unwrap()); + + let mut builder = SubmitCommandBufferBuilder::new(); + builder.set_fence_signal(&fence); + + builder.submit(&queue).unwrap(); + fence.wait(Some(Duration::from_secs(5))).unwrap(); + assert!(fence.ready().unwrap()); + } + } + + #[test] + fn has_fence() { + unsafe { + let (device, queue) = gfx_dev_and_queue!(); + + let fence = Fence::alloc(device.clone()).unwrap(); + + let mut builder = SubmitCommandBufferBuilder::new(); + assert!(!builder.has_fence()); + builder.set_fence_signal(&fence); + assert!(builder.has_fence()); + } + } + + #[test] + fn merge_both_have_fences() { + unsafe { + let (device, _) = gfx_dev_and_queue!(); + + let fence1 = Fence::alloc(device.clone()).unwrap(); + let fence2 = Fence::alloc(device.clone()).unwrap(); + + let mut builder1 = SubmitCommandBufferBuilder::new(); + builder1.set_fence_signal(&fence1); + let mut builder2 = SubmitCommandBufferBuilder::new(); + builder2.set_fence_signal(&fence2); + + assert_should_panic!("Can't merge two queue submits that both have a fence", { + let _ = builder1.merge(builder2); + }); + } + } +} |