blob: 8ffa1298e4c5abb557610352824963f24426fbdf [file] [log] [blame]
// 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);
});
}
}
}