aboutsummaryrefslogtreecommitdiff
path: root/src/command_buffer/submit/queue_submit.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/command_buffer/submit/queue_submit.rs')
-rw-r--r--src/command_buffer/submit/queue_submit.rs359
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);
+ });
+ }
+ }
+}