This commit is contained in:
Pierre Krieger 2017-02-13 16:18:10 +01:00
parent 3125a73628
commit dd13c015cb
31 changed files with 1755 additions and 1071 deletions

View File

@ -37,8 +37,6 @@ use vulkano::command_buffer;
use vulkano::command_buffer::AutoCommandBufferBuilder;
use vulkano::command_buffer::CommandBufferBuilder;
use vulkano::command_buffer::DynamicState;
use vulkano::command_buffer::Submit;
use vulkano::command_buffer::Submission;
use vulkano::descriptor::pipeline_layout::PipelineLayout;
use vulkano::descriptor::pipeline_layout::EmptyPipelineDesc;
use vulkano::device::Device;
@ -57,6 +55,7 @@ use vulkano::pipeline::viewport::Viewport;
use vulkano::pipeline::viewport::Scissor;
use vulkano::swapchain::SurfaceTransform;
use vulkano::swapchain::Swapchain;
use vulkano::sync::GpuFuture;
use std::sync::Arc;
use std::time::Duration;
@ -343,15 +342,16 @@ fn main() {
// Initialization is finally finished!
// In the loop below we are going to submit commands to the GPU. Submitting a command produces
// a `Submission` object which holds the resources for as long as they are in use by the GPU.
// an object that implements the `GpuFuture` trait, which holds the resources for as long as
// they are in use by the GPU.
//
// Destroying a `Submission` blocks until the GPU is finished executing it. In order to avoid
// Destroying the `GpuFuture` blocks until the GPU is finished executing it. In order to avoid
// that, we store them in a `Vec` and clean them from time to time.
let mut submissions: Vec<Submission> = Vec::new();
let mut submissions: Vec<Box<GpuFuture>> = Vec::new();
loop {
// Clearing the old submissions by keeping alive only the ones whose destructor would block.
submissions.retain(|s| s.destroying_would_block());
// Clearing the old submissions by keeping alive only the ones which aren't finished.
submissions.retain(|s| !s.is_finished());
// Before we can draw on the output, we have to *acquire* an image from the swapchain. If
// no image is available (which happens if you submit draw commands too quickly), then the
@ -360,7 +360,7 @@ fn main() {
//
// This function can block if no image is available. The parameter is a timeout after
// which the function call will return an error.
let image_num = swapchain.acquire_next_image(Duration::new(1, 0)).unwrap();
let (image_num, future) = swapchain.acquire_next_image(Duration::new(1, 0)).unwrap();
// In order to draw, we have to build a *command buffer*. The command buffer object holds
// the list of commands that are going to be executed.
@ -396,16 +396,19 @@ fn main() {
// Finish building the command buffer by calling `build`.
.build();
// Now all we need to do is submit the command buffer to the queue.
submissions.push(command_buffer.submit(&queue).unwrap());
let future = future
.then_execute(queue.clone(), command_buffer)
// The color output is now expected to contain our triangle. But in order to show it on
// the screen, we have to *present* the image by calling `present`.
//
// This function does not actually present the image immediately. Instead it submits a
// present command at the end of the queue. This means that it will only be presented once
// the GPU has finished executing the command buffer that draws the triangle.
swapchain.present(&queue, image_num).unwrap();
// The color output is now expected to contain our triangle. But in order to show it on
// the screen, we have to *present* the image by calling `present`.
//
// This function does not actually present the image immediately. Instead it submits a
// present command at the end of the queue. This means that it will only be presented once
// the GPU has finished executing the command buffer that draws the triangle.
.then_swapchain_present(queue.clone(), swapchain.clone(), image_num)
.then_signal_fence();
future.flush().unwrap();
submissions.push(Box::new(future) as Box<_>);
// Note that in more complex programs it is likely that one of `acquire_next_image`,
// `command_buffer::submit`, or `present` will block for some time. This happens when the

View File

@ -26,6 +26,7 @@ use std::sync::Mutex;
use std::sync::RwLock;
use std::sync::RwLockReadGuard;
use std::sync::RwLockWriteGuard;
use std::sync::TryLockError;
use std::sync::Weak;
use std::time::Duration;
use smallvec::SmallVec;
@ -37,8 +38,8 @@ use buffer::sys::Usage;
use buffer::traits::Buffer;
use buffer::traits::BufferInner;
use buffer::traits::TypedBuffer;
use command_buffer::Submission;
use device::Device;
use device::Queue;
use instance::QueueFamily;
use memory::Content;
use memory::CpuAccess as MemCpuAccess;
@ -62,23 +63,17 @@ pub struct CpuAccessibleBuffer<T: ?Sized, A = Arc<StdMemoryPool>> where A: Memor
// The memory held by the buffer.
memory: A::Alloc,
// Access pattern of the buffer. Can be read-locked for a shared CPU access, or write-locked
// for either a write CPU access or a GPU access.
access: RwLock<()>,
// Queue families allowed to access this buffer.
queue_families: SmallVec<[u32; 4]>,
// Latest submission that uses this buffer.
// Also used to block any attempt to submit this buffer while it is accessed by the CPU.
latest_submission: RwLock<LatestSubmission>,
// Necessary to make it compile.
marker: PhantomData<Box<T>>,
}
#[derive(Debug)]
struct LatestSubmission {
read_submissions: Mutex<Vec<Weak<Submission>>>,
write_submission: Option<Weak<Submission>>, // TODO: can use `Weak::new()` once it's stabilized
}
impl<T> CpuAccessibleBuffer<T> {
/// Deprecated. Use `from_data` instead.
#[deprecated]
@ -108,7 +103,7 @@ impl<T> CpuAccessibleBuffer<T> {
// TODO: check whether that's true ^
{
let mut mapping = uninitialized.write(Duration::new(0, 0)).unwrap();
let mut mapping = uninitialized.write().unwrap();
ptr::write::<T>(&mut *mapping, data)
}
@ -145,7 +140,7 @@ impl<T> CpuAccessibleBuffer<[T]> {
// TODO: check whether that's true ^
{
let mut mapping = uninitialized.write(Duration::new(0, 0)).unwrap();
let mut mapping = uninitialized.write().unwrap();
for (i, o) in data.zip(mapping.iter_mut()) {
ptr::write(o, i);
@ -223,11 +218,8 @@ impl<T: ?Sized> CpuAccessibleBuffer<T> {
Ok(Arc::new(CpuAccessibleBuffer {
inner: buffer,
memory: mem,
access: RwLock::new(()),
queue_families: queue_families,
latest_submission: RwLock::new(LatestSubmission {
read_submissions: Mutex::new(vec![]),
write_submission: None,
}),
marker: PhantomData,
}))
}
@ -259,22 +251,16 @@ impl<T: ?Sized, A> CpuAccessibleBuffer<T, A> where T: Content + 'static, A: Memo
///
/// After this function successfully locks the buffer, any attempt to submit a command buffer
/// that uses it will block until you unlock it.
// TODO: remove timeout parameter since CPU-side locking can't use it
#[inline]
pub fn read(&self, timeout: Duration) -> Result<ReadLock<T>, FenceWaitError> {
let submission = self.latest_submission.read().unwrap();
// TODO: should that set the write_submission to None?
if let Some(submission) = submission.write_submission.clone().and_then(|s| s.upgrade()) {
try!(submission.wait(timeout));
}
pub fn read(&self) -> Result<ReadLock<T>, TryLockError<RwLockReadGuard<()>>> {
let lock = try!(self.access.try_read());
let offset = self.memory.offset();
let range = offset .. offset + self.inner.size();
Ok(ReadLock {
inner: unsafe { self.memory.mapped_memory().unwrap().read_write(range) },
lock: submission,
lock: lock,
})
}
@ -286,30 +272,16 @@ impl<T: ?Sized, A> CpuAccessibleBuffer<T, A> where T: Content + 'static, A: Memo
///
/// After this function successfully locks the buffer, any attempt to submit a command buffer
/// that uses it will block until you unlock it.
// TODO: remove timeout parameter since CPU-side locking can't use it
#[inline]
pub fn write(&self, timeout: Duration) -> Result<WriteLock<T>, FenceWaitError> {
let mut submission = self.latest_submission.write().unwrap();
{
let mut read_submissions = submission.read_submissions.get_mut().unwrap();
for submission in read_submissions.drain(..) {
if let Some(submission) = submission.upgrade() {
try!(submission.wait(timeout));
}
}
}
if let Some(submission) = submission.write_submission.take().and_then(|s| s.upgrade()) {
try!(submission.wait(timeout));
}
pub fn write(&self) -> Result<WriteLock<T>, TryLockError<RwLockWriteGuard<()>>> {
let lock = try!(self.access.try_write());
let offset = self.memory.offset();
let range = offset .. offset + self.inner.size();
Ok(WriteLock {
inner: unsafe { self.memory.mapped_memory().unwrap().read_write(range) },
lock: submission,
lock: lock,
})
}
}
@ -330,134 +302,10 @@ unsafe impl<T: ?Sized, A> Buffer for CpuAccessibleBuffer<T, A>
self.inner.key()
}
/*fn transition(&self, states: &mut StatesManager, num_command: usize, _: usize, _: usize,
write: bool, stage: PipelineStages, access: AccessFlagBits)
-> Option<TrackedBufferPipelineBarrierRequest>
{
debug_assert!(!stage.host);
debug_assert!(!access.host_read);
debug_assert!(!access.host_write);
// We don't know when the user is going to write to the buffer from the CPU, so we just
// assume that it's happened all the time.
let mut state = states.buffer_or(self.inner().buffer, 0, || CpuAccessibleBufferClState {
size: self.size(),
stages: PipelineStages { host: true, .. PipelineStages::none() },
access: AccessFlagBits { host_write: true, .. AccessFlagBits::none() },
first_stages: None,
write: true,
earliest_previous_transition: 0,
needs_flush_at_the_end: false,
});
if write {
// Write after read or write after write.
let new_state = CpuAccessibleBufferClState {
size: state.size,
stages: stage,
access: access,
first_stages: Some(state.first_stages.clone().unwrap_or(stage)),
write: true,
earliest_previous_transition: num_command,
needs_flush_at_the_end: true,
};
let barrier = TrackedBufferPipelineBarrierRequest {
after_command_num: state.earliest_previous_transition,
source_stage: state.stages,
destination_stages: stage,
by_region: true,
memory_barrier: if state.write {
Some(TrackedBufferPipelineMemoryBarrierRequest {
offset: 0,
size: state.size,
source_access: state.access,
destination_access: access,
})
} else {
None
},
};
*state = new_state;
Some(barrier)
} else if state.write {
// Read after write.
let new_state = CpuAccessibleBufferClState {
size: state.size,
stages: stage,
access: access,
first_stages: Some(state.first_stages.clone().unwrap_or(stage)),
write: false,
earliest_previous_transition: num_command,
needs_flush_at_the_end: state.needs_flush_at_the_end,
};
let barrier = TrackedBufferPipelineBarrierRequest {
after_command_num: state.earliest_previous_transition,
source_stage: state.stages,
destination_stages: stage,
by_region: true,
memory_barrier: Some(TrackedBufferPipelineMemoryBarrierRequest {
offset: 0,
size: state.size,
source_access: state.access,
destination_access: access,
}),
};
*state = new_state;
Some(barrier)
} else {
// Read after read.
*state = CpuAccessibleBufferClState {
size: state.size,
stages: state.stages | stage,
access: state.access | access,
first_stages: Some(state.first_stages.clone().unwrap_or(stage)),
write: false,
earliest_previous_transition: state.earliest_previous_transition,
needs_flush_at_the_end: state.needs_flush_at_the_end,
};
None
}
#[inline]
fn gpu_access(&self, exclusive_access: bool, queue: &Queue) -> bool {
true // FIXME:
}
fn finish(&self, in_s: &mut StatesManager, out: &mut StatesManager)
-> Option<TrackedBufferPipelineBarrierRequest>
{
let state: CpuAccessibleBufferClState = in_s.remove_buffer(self.inner().buffer, 0).unwrap();
let barrier = if state.needs_flush_at_the_end {
let barrier = TrackedBufferPipelineBarrierRequest {
after_command_num: state.earliest_previous_transition,
source_stage: state.stages,
destination_stages: PipelineStages { host: true, .. PipelineStages::none() },
by_region: true,
memory_barrier: Some(TrackedBufferPipelineMemoryBarrierRequest {
offset: 0,
size: state.size,
source_access: state.access,
destination_access: AccessFlagBits { host_read: true,
.. AccessFlagBits::none() },
}),
};
Some(barrier)
} else {
None
};
out.buffer_or(self.inner().buffer, 0, || CpuAccessibleBufferFinished {
first_stages: state.first_stages.unwrap_or(PipelineStages::none()),
write: state.needs_flush_at_the_end,
});
barrier
}*/
}
unsafe impl<T: ?Sized, A> TypedBuffer for CpuAccessibleBuffer<T, A>
@ -487,7 +335,7 @@ pub struct CpuAccessibleBufferFinished {
/// this buffer's content or tries to submit a GPU command that uses this buffer, it will block.
pub struct ReadLock<'a, T: ?Sized + 'a> {
inner: MemCpuAccess<'a, T>,
lock: RwLockReadGuard<'a, LatestSubmission>,
lock: RwLockReadGuard<'a, ()>,
}
impl<'a, T: ?Sized + 'a> ReadLock<'a, T> {
@ -518,7 +366,7 @@ impl<'a, T: ?Sized + 'a> Deref for ReadLock<'a, T> {
/// this buffer's content or tries to submit a GPU command that uses this buffer, it will block.
pub struct WriteLock<'a, T: ?Sized + 'a> {
inner: MemCpuAccess<'a, T>,
lock: RwLockWriteGuard<'a, LatestSubmission>,
lock: RwLockWriteGuard<'a, ()>,
}
impl<'a, T: ?Sized + 'a> WriteLock<'a, T> {

View File

@ -27,8 +27,8 @@ use buffer::sys::Usage;
use buffer::traits::Buffer;
use buffer::traits::BufferInner;
use buffer::traits::TypedBuffer;
use command_buffer::Submission;
use device::Device;
use device::Queue;
use instance::QueueFamily;
use memory::pool::AllocLayout;
use memory::pool::MemoryPool;
@ -50,20 +50,10 @@ pub struct DeviceLocalBuffer<T: ?Sized, A = Arc<StdMemoryPool>> where A: MemoryP
// Queue families allowed to access this buffer.
queue_families: SmallVec<[u32; 4]>,
// Latest submission that uses this buffer.
// Also used to block any attempt to submit this buffer while it is accessed by the CPU.
latest_submission: Mutex<LatestSubmission>,
// Necessary to make it compile.
marker: PhantomData<Box<T>>,
}
#[derive(Debug)]
struct LatestSubmission {
read_submissions: SmallVec<[Weak<Submission>; 4]>,
write_submission: Option<Weak<Submission>>, // TODO: can use `Weak::new()` once it's stabilized
}
impl<T> DeviceLocalBuffer<T> {
/// Builds a new buffer. Only allowed for sized data.
#[inline]
@ -137,10 +127,6 @@ impl<T: ?Sized> DeviceLocalBuffer<T> {
inner: buffer,
memory: mem,
queue_families: queue_families,
latest_submission: Mutex::new(LatestSubmission {
read_submissions: SmallVec::new(),
write_submission: None,
}),
marker: PhantomData,
}))
}
@ -173,6 +159,11 @@ unsafe impl<T: ?Sized, A> Buffer for DeviceLocalBuffer<T, A>
offset: 0,
}
}
#[inline]
fn gpu_access(&self, exclusive_access: bool, queue: &Queue) -> bool {
false // FIXME:
}
}
unsafe impl<T: ?Sized, A> TypedBuffer for DeviceLocalBuffer<T, A>

View File

@ -33,8 +33,8 @@ use buffer::sys::Usage;
use buffer::traits::Buffer;
use buffer::traits::BufferInner;
use buffer::traits::TypedBuffer;
use command_buffer::Submission;
use device::Device;
use device::Queue;
use instance::QueueFamily;
use memory::pool::AllocLayout;
use memory::pool::MemoryPool;
@ -54,8 +54,6 @@ pub struct ImmutableBuffer<T: ?Sized, A = Arc<StdMemoryPool>> where A: MemoryPoo
// Queue families allowed to access this buffer.
queue_families: SmallVec<[u32; 4]>,
latest_write_submission: Mutex<Option<Weak<Submission>>>, // TODO: can use `Weak::new()` once it's stabilized
started_reading: AtomicBool,
marker: PhantomData<Box<T>>,
@ -134,7 +132,6 @@ impl<T: ?Sized> ImmutableBuffer<T> {
inner: buffer,
memory: mem,
queue_families: queue_families,
latest_write_submission: Mutex::new(None),
started_reading: AtomicBool::new(false),
marker: PhantomData,
}))
@ -168,6 +165,11 @@ unsafe impl<T: ?Sized, A> Buffer for ImmutableBuffer<T, A>
offset: 0,
}
}
#[inline]
fn gpu_access(&self, exclusive_access: bool, queue: &Queue) -> bool {
false // FIXME:
}
}
unsafe impl<T: ?Sized, A> TypedBuffer for ImmutableBuffer<T, A>

View File

@ -14,6 +14,7 @@ use std::ops::Range;
use buffer::traits::Buffer;
use buffer::traits::BufferInner;
use buffer::traits::TypedBuffer;
use device::Queue;
/// A subpart of a buffer.
///
@ -172,6 +173,11 @@ unsafe impl<T: ?Sized, B> Buffer for BufferSlice<T, B> where B: Buffer {
debug_assert!(self_size + self_offset <= self.size);
self.resource.conflict_key(self_offset, self_size)
}
#[inline]
fn gpu_access(&self, exclusive_access: bool, queue: &Queue) -> bool {
self.resource.gpu_access(exclusive_access, queue)
}
}
unsafe impl<T: ?Sized, B> TypedBuffer for BufferSlice<T, B> where B: Buffer, T: 'static {

View File

@ -11,6 +11,7 @@ use std::ops::Range;
use buffer::BufferSlice;
use buffer::sys::UnsafeBuffer;
use device::Queue;
use image::Image;
use memory::Content;
@ -130,6 +131,16 @@ pub unsafe trait Buffer {
// FIXME: remove implementation
unimplemented!()
}
/// Returns true if the buffer can be given access on the given queue.
///
/// This function implementation should remember that it has been called and return `false` if
/// it gets called a second time.
///
/// The only way to know that the GPU has stopped accessing a queue is when the buffer object
/// gets destroyed. Therefore you are encouraged to use temporary objects or handles (similar
/// to a lock) in order to represent a GPU access.
fn gpu_access(&self, exclusive_access: bool, queue: &Queue) -> bool;
}
/// Inner information about a buffer.
@ -164,6 +175,11 @@ unsafe impl<T> Buffer for T where T: SafeDeref, T::Target: Buffer {
fn conflict_key(&self, self_offset: usize, self_size: usize) -> u64 {
(**self).conflict_key(self_offset, self_size)
}
#[inline]
fn gpu_access(&self, exclusive_access: bool, queue: &Queue) -> bool {
(**self).gpu_access(exclusive_access, queue)
}
}
pub unsafe trait TypedBuffer: Buffer {

View File

@ -7,21 +7,19 @@
// notice may not be copied, modified, or distributed except
// according to those terms.
use std::error::Error;
use std::sync::Arc;
use command_buffer::cb;
use command_buffer::cmd;
use command_buffer::cb::AddCommand;
use command_buffer::cb::CommandBufferBuild;
use command_buffer::cb::UnsafeCommandBuffer;
use command_buffer::CommandBuffer;
use command_buffer::CommandBufferBuilder;
use command_buffer::pool::CommandPool;
use command_buffer::pool::StandardCommandPool;
use command_buffer::Submit;
use command_buffer::SubmitBuilder;
use device::Device;
use device::DeviceOwned;
use device::Queue;
use instance::QueueFamily;
use OomError;
@ -68,15 +66,15 @@ unsafe impl<L, P, O> CommandBufferBuild for AutoCommandBufferBuilder<L, P>
}
}
unsafe impl<L, P> Submit for AutoCommandBufferBuilder<L, P>
where Cb<L, P>: Submit,
unsafe impl<L, P> CommandBuffer for AutoCommandBufferBuilder<L, P>
where Cb<L, P>: CommandBuffer,
P: CommandPool
{
type Pool = <Cb<L, P> as CommandBuffer>::Pool;
#[inline]
unsafe fn append_submission<'a>(&'a self, base: SubmitBuilder<'a>, queue: &Arc<Queue>)
-> Result<SubmitBuilder<'a>, Box<Error>>
{
self.inner.append_submission(base, queue)
fn inner(&self) -> &UnsafeCommandBuffer<Self::Pool> {
self.inner.inner()
}
}

View File

@ -13,11 +13,11 @@ use std::sync::Arc;
use command_buffer::cb::AddCommand;
use command_buffer::cb::CommandBufferBuild;
use command_buffer::cb::CommandsList;
use command_buffer::cb::UnsafeCommandBuffer;
use command_buffer::CommandBuffer;
use command_buffer::CommandBufferBuilder;
use command_buffer::CommandBufferBuilderBuffered;
use command_buffer::cmd;
use command_buffer::Submit;
use command_buffer::SubmitBuilder;
use device::Device;
use device::DeviceOwned;
use device::Queue;
@ -141,12 +141,12 @@ unsafe impl<I, L, O> CommandBufferBuild for BufferedCommandsListLayer<I, L>
}
}
unsafe impl<I, L> Submit for BufferedCommandsListLayer<I, L> where I: Submit {
unsafe impl<I, L> CommandBuffer for BufferedCommandsListLayer<I, L> where I: CommandBuffer {
type Pool = I::Pool;
#[inline]
unsafe fn append_submission<'a>(&'a self, base: SubmitBuilder<'a>, queue: &Arc<Queue>)
-> Result<SubmitBuilder<'a>, Box<Error>>
{
self.inner.as_ref().unwrap().append_submission(base, queue)
fn inner(&self) -> &UnsafeCommandBuffer<I::Pool> {
self.inner.as_ref().unwrap().inner()
}
}

View File

@ -11,10 +11,10 @@ use std::error::Error;
use std::sync::Arc;
use command_buffer::cb::AddCommand;
use command_buffer::cb::UnsafeCommandBuffer;
use command_buffer::cmd;
use command_buffer::CommandBuffer;
use command_buffer::CommandBufferBuilder;
use command_buffer::Submit;
use command_buffer::SubmitBuilder;
use device::Device;
use device::DeviceOwned;
use device::Queue;
@ -59,12 +59,12 @@ impl<I> CommandsListLayer<I, ()> {
// TODO: implement CommandBufferBuild
unsafe impl<I, L> Submit for CommandsListLayer<I, L> where I: Submit {
unsafe impl<I, L> CommandBuffer for CommandsListLayer<I, L> where I: CommandBuffer {
type Pool = I::Pool;
#[inline]
unsafe fn append_submission<'a>(&'a self, base: SubmitBuilder<'a>, queue: &Arc<Queue>)
-> Result<SubmitBuilder<'a>, Box<Error>>
{
self.inner.append_submission(base, queue)
fn inner(&self) -> &UnsafeCommandBuffer<I::Pool> {
self.inner.inner()
}
}

View File

@ -71,6 +71,9 @@
//! The `CommandsList` trait is implemented on any command buffer or command buffer builder that
//! exposes a list of commands. It is required by some of the layers.
use command_buffer::pool::CommandPool;
use device::DeviceOwned;
pub use self::auto_barriers::AutoPipelineBarriersLayer;
pub use self::buffered::BufferedCommandsListLayer;
pub use self::buffered::BufferedCommandsListLayerCommands;

View File

@ -12,10 +12,10 @@ use std::sync::Arc;
use command_buffer::cb::AddCommand;
use command_buffer::cb::CommandBufferBuild;
use command_buffer::cb::UnsafeCommandBuffer;
use command_buffer::CommandBuffer;
use command_buffer::CommandBufferBuilder;
use command_buffer::cmd;
use command_buffer::Submit;
use command_buffer::SubmitBuilder;
use device::Device;
use device::DeviceOwned;
use device::Queue;
@ -102,13 +102,12 @@ pub struct SubmitSyncLayer<I> {
inner: I,
}
unsafe impl<I> Submit for SubmitSyncLayer<I> where I: Submit {
unsafe impl<I> CommandBuffer for SubmitSyncLayer<I> where I: CommandBuffer {
type Pool = I::Pool;
#[inline]
unsafe fn append_submission<'a>(&'a self, base: SubmitBuilder<'a>, queue: &Arc<Queue>)
-> Result<SubmitBuilder<'a>, Box<Error>>
{
// FIXME:
self.inner.append_submission(base, queue)
fn inner(&self) -> &UnsafeCommandBuffer<I::Pool> {
self.inner.inner()
}
}

View File

@ -12,11 +12,10 @@ use std::ptr;
use std::sync::Arc;
use std::sync::atomic::AtomicBool;
use command_buffer::CommandBuffer;
use command_buffer::cb::CommandBufferBuild;
use command_buffer::pool::AllocatedCommandBuffer;
use command_buffer::pool::CommandPool;
use command_buffer::Submit;
use command_buffer::SubmitBuilder;
use device::Device;
use device::DeviceOwned;
use device::Queue;
@ -287,12 +286,12 @@ pub struct UnsafeCommandBuffer<P> where P: CommandPool {
already_submitted: AtomicBool,
}
unsafe impl<P> Submit for UnsafeCommandBuffer<P> where P: CommandPool {
unsafe impl<P> CommandBuffer for UnsafeCommandBuffer<P> where P: CommandPool {
type Pool = P;
#[inline]
unsafe fn append_submission<'a>(&'a self, base: SubmitBuilder<'a>, _queue: &Arc<Queue>)
-> Result<SubmitBuilder<'a>, Box<Error>>
{
Ok(base.add_command_buffer(self))
fn inner(&self) -> &UnsafeCommandBuffer<P> {
self
}
}

View File

@ -44,10 +44,8 @@
pub use self::auto::AutoCommandBufferBuilder;
pub use self::builder::CommandBufferBuilder;
pub use self::builder::CommandBufferBuilderBuffered;
pub use self::submit::Submission;
pub use self::submit::Submit;
pub use self::submit::SubmitBuilder;
pub use self::submit::SubmitChain;
pub use self::traits::CommandBuffer;
pub use self::traits::CommandBufferExecFuture;
use pipeline::viewport::Viewport;
use pipeline::viewport::Scissor;
@ -55,10 +53,11 @@ use pipeline::viewport::Scissor;
pub mod cb;
pub mod cmd;
pub mod pool;
pub mod submit;
mod auto;
mod builder;
mod submit;
mod traits;
#[repr(C)]
#[derive(Debug, Copy, Clone, PartialEq, Eq)]

View File

@ -1,533 +0,0 @@
// Copyright (c) 2016 The vulkano developers
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT
// license <LICENSE-MIT or http://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 std::error::Error;
use std::fmt;
use std::marker::PhantomData;
use std::ops::Deref;
use std::ptr;
use std::sync::Arc;
use std::time::Duration;
use smallvec::SmallVec;
use command_buffer::cb::UnsafeCommandBuffer;
use command_buffer::pool::CommandPool;
use device::Device;
use device::DeviceOwned;
use device::Queue;
use sync::Fence;
use sync::FenceWaitError;
use sync::PipelineStages;
use sync::Semaphore;
use check_errors;
use vk;
use VulkanObject;
use VulkanPointers;
use SynchronizedVulkanObject;
/// Trait for objects that can be submitted to the GPU.
// TODO: is Box<Error> appropriate? maybe something else?
pub unsafe trait Submit: DeviceOwned {
/// Submits the object to the queue.
///
/// Since submitting has a fixed overhead, you should try, if possible, to submit multiple
/// command buffers at once instead. To do so, you can use the `chain` method.
///
/// `s.submit(queue)` is a shortcut for `s.submit_precise(queue).boxed()`.
// TODO: add example
#[inline]
fn submit(self, queue: &Arc<Queue>) -> Result<Submission, Box<Error>>
where Self: Sized + 'static
{
Ok(try!(self.submit_precise(queue)).boxed())
}
/// Submits the object to the queue.
///
/// Since submitting has a fixed overhead, you should try, if possible, to submit multiple
/// command buffers at once instead. To do so, you can use the `chain` method.
///
/// Contrary to `submit`, this method preserves strong typing in the submission. This means
/// that it has a lower overhead but it is less convenient to store in a container.
// TODO: add example
#[inline]
fn submit_precise(self, queue: &Arc<Queue>) -> Result<Submission<Self>, Box<Error>>
where Self: Sized + 'static
{
submit(self, queue)
}
/// 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<S>(self, other: S) -> SubmitChain<Self, S> where Self: Sized, S: Submit {
assert_eq!(self.device().internal_object(),
other.device().internal_object());
SubmitChain { first: self, second: other }
}
/// 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
///
/// This function must only be called if there's actually a submission with the returned
/// parameters that follows.
///
/// This function is supposed to be called only by vulkano's internals. It is recommended
/// that you never call it.
///
/// # Safety for the implementation
///
/// TODO: To write.
unsafe fn append_submission<'a>(&'a self, base: SubmitBuilder<'a>, queue: &Arc<Queue>)
-> Result<SubmitBuilder<'a>, Box<Error>>;
}
unsafe impl<T> Submit for T where T: Deref, T::Target: Submit {
#[inline]
unsafe fn append_submission<'a>(&'a self, base: SubmitBuilder<'a>, queue: &Arc<Queue>)
-> Result<SubmitBuilder<'a>, Box<Error>>
{
(**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
// 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<Semaphore>; 8]>,
keep_alive_fences: SmallVec<[Arc<Fence>; 2]>,
marker: PhantomData<&'a ()>,
}
#[derive(Default)]
struct SubmitBuilderSubmit {
batches: SmallVec<[vk::SubmitInfo; 4]>,
fence: Option<Arc<Fence>>,
}
impl<'a> SubmitBuilder<'a> {
/// Builds a new empty `SubmitBuilder`.
#[inline]
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(),
keep_alive_fences: SmallVec::new(),
marker: PhantomData,
}
}
/// 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_signal(mut self, fence: Arc<Fence>) -> SubmitBuilder<'a> {
if self.submits.last().map(|b| b.fence.is_some()).unwrap_or(true) {
self.submits.push(Default::default());
}
{
let mut last = self.submits.last_mut().unwrap();
debug_assert!(last.fence.is_none());
self.keep_alive_fences.push(fence.clone());
last.fence = Some(fence);
}
self
}
/// 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<Semaphore>, stages: PipelineStages)
-> 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());
}
{
let mut submit = self.submits.last_mut().unwrap();
if submit.batches.last().map(|b| b.signalSemaphoreCount != 0 ||
b.commandBufferCount != 0)
.unwrap_or(true)
{
submit.batches.push(SubmitBuilder::empty_vk_submit_info());
}
submit.batches.last_mut().unwrap().waitSemaphoreCount += 1;
self.dest_stages_storage.push(stages.into());
self.semaphores_storage.push(semaphore.internal_object());
self.keep_alive_semaphores.push(semaphore);
}
self
}
/// 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<P>(self, command_buffer: &'a UnsafeCommandBuffer<P>)
-> 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());
}
{
let mut submit = self.submits.last_mut().unwrap();
if submit.batches.last().map(|b| b.signalSemaphoreCount != 0).unwrap_or(true) {
submit.batches.push(SubmitBuilder::empty_vk_submit_info());
}
self.command_buffers_storage.push(command_buffer);
submit.batches.last_mut().unwrap().commandBufferCount += 1;
}
self
}
/// 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<Semaphore>) -> 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());
}
{
let mut submit = self.submits.last_mut().unwrap();
if submit.batches.is_empty() {
submit.batches.push(SubmitBuilder::empty_vk_submit_info());
}
submit.batches.last_mut().unwrap().signalSemaphoreCount += 1;
self.semaphores_storage.push(semaphore.internal_object());
self.keep_alive_semaphores.push(semaphore);
}
self
}
#[inline]
fn empty_vk_submit_info() -> vk::SubmitInfo {
vk::SubmitInfo {
sType: vk::STRUCTURE_TYPE_SUBMIT_INFO,
pNext: ptr::null(),
waitSemaphoreCount: 0,
pWaitSemaphores: ptr::null(),
pWaitDstStageMask: ptr::null(),
commandBufferCount: 0,
pCommandBuffers: ptr::null(),
signalSemaphoreCount: 0,
pSignalSemaphores: ptr::null(),
}
}
}
// Implementation for `Submit::submit`.
fn submit<S>(submit: S, queue: &Arc<Queue>) -> Result<Submission<S>, Box<Error>>
where S: Submit + 'static
{
let last_fence;
let keep_alive_semaphores;
let keep_alive_fences;
unsafe {
let mut builder = try!(submit.append_submission(SubmitBuilder::new(), queue));
keep_alive_semaphores = builder.keep_alive_semaphores;
keep_alive_fences = builder.keep_alive_fences;
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
};
{
let vk = queue.device().pointers();
let queue = queue.internal_object_guard();
let mut next_semaphore = 0;
let mut next_wait_stage = 0;
let mut next_command_buffer = 0;
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);
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);
next_command_buffer += batch.commandBufferCount as isize;
batch.pSignalSemaphores = builder.semaphores_storage
.as_ptr().offset(next_semaphore);
next_semaphore += batch.signalSemaphoreCount as isize;
}
let fence = submit.fence.as_ref().map(|f| f.internal_object()).unwrap_or(0);
check_errors(vk.QueueSubmit(*queue, submit.batches.len() as u32,
submit.batches.as_ptr(), fence)).unwrap(); // TODO: handle errors (trickier than it looks)
}
debug_assert_eq!(next_semaphore as usize, builder.semaphores_storage.len());
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());
}
}
Ok(Submission {
queue: queue.clone(),
fence: FenceWithWaiting(last_fence),
keep_alive_semaphores: keep_alive_semaphores,
keep_alive_fences: keep_alive_fences,
submit: submit,
})
}
/// Chain of two `Submit` objects. See `Submit::chain`.
pub struct SubmitChain<A, B> {
first: A,
second: B,
}
unsafe impl<A, B> Submit for SubmitChain<A, B> where A: Submit, B: Submit {
#[inline]
unsafe fn append_submission<'a>(&'a self, base: SubmitBuilder<'a>, queue: &Arc<Queue>)
-> Result<SubmitBuilder<'a>, Box<Error>>
{
// FIXME: huge problem here ; if the second one returns an error, the first one has been
// called without any actual following submission
let builder = try!(self.first.append_submission(base, queue));
self.second.append_submission(builder, queue)
}
}
unsafe impl<A, B> DeviceOwned for SubmitChain<A, B> where A: DeviceOwned, B: DeviceOwned {
#[inline]
fn device(&self) -> &Arc<Device> {
debug_assert_eq!(self.first.device().internal_object(),
self.second.device().internal_object());
self.first.device()
}
}
/// 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`
/// object will block until the GPU is finished executing.
///
/// Whenever you submit a command buffer, you are encouraged to store the returned `Submission`
/// in a long-living container such as a `Vec`. From time to time, you can clean the obsolete
/// objects by checking whether `destroying_would_block()` returns false. For example, if you use
/// a `Vec` you can do `vec.retain(|s| s.destroying_would_block())`.
///
/// # Leak safety
///
/// One of the roles of the `Submission` object is to keep alive the objects used by the GPU during
/// the submission. However if the user calls `std::mem::forget` on the `Submission`, all borrows
/// are immediately free'd. This is known as *the leakpocalypse*.
///
/// In order to avoid this problem, only `'static` objects can be put in a `Submission`.
// TODO: ^ decide whether we allow to add an unsafe non-static constructor
#[must_use]
pub struct Submission<S: 'static = Box<Submit>> {
fence: FenceWithWaiting, // TODO: make optional
queue: Arc<Queue>,
keep_alive_semaphores: SmallVec<[Arc<Semaphore>; 8]>,
keep_alive_fences: SmallVec<[Arc<Fence>; 2]>,
submit: S,
}
struct FenceWithWaiting(Arc<Fence>);
impl Drop for FenceWithWaiting {
fn drop(&mut self) {
self.0.wait(Duration::from_secs(10)).unwrap(); // TODO: handle some errors
}
}
impl<S> fmt::Debug for Submission<S> {
#[inline]
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
// TODO: better impl?
write!(fmt, "<Vulkan submission>")
}
}
impl<S> Submission<S> {
/// Returns `true` if destroying this `Submission` object would block the CPU for some time.
#[inline]
pub fn destroying_would_block(&self) -> bool {
!self.finished()
}
/// Returns `true` if the GPU has finished executing this submission.
#[inline]
pub fn finished(&self) -> bool {
self.fence.0.ready().unwrap_or(false) // TODO: what to do in case of error?
}
/// Waits until the submission has finished.
#[inline]
pub fn wait(&self, timeout: Duration) -> Result<(), FenceWaitError> {
self.fence.0.wait(timeout)
}
/// Returns the queue the submission was submitted to.
#[inline]
pub fn queue(&self) -> &Arc<Queue> {
&self.queue
}
}
impl<S> Submission<S> where S: Submit + 'static {
/// Turns this submission into a boxed submission.
pub fn boxed(self) -> Submission {
Submission {
fence: self.fence,
queue: self.queue,
keep_alive_semaphores: self.keep_alive_semaphores,
keep_alive_fences: self.keep_alive_fences,
submit: Box::new(self.submit) as Box<_>,
}
}
}
/* TODO: restore
#[cfg(test)]
mod tests {
use std::iter;
use std::iter::Empty;
use std::sync::Arc;
use command_buffer::pool::StandardCommandPool;
use command_buffer::submit::CommandBuffer;
use command_buffer::submit::SubmitInfo;
use command_buffer::sys::Kind;
use command_buffer::sys::Flags;
use command_buffer::sys::PipelineBarrierBuilder;
use command_buffer::sys::UnsafeCommandBuffer;
use command_buffer::sys::UnsafeCommandBufferBuilder;
use device::Device;
use device::Queue;
use framebuffer::framebuffer::EmptyAttachmentsList;
use framebuffer::EmptySinglePassRenderPass;
use framebuffer::Framebuffer;
use sync::Fence;
use sync::PipelineStages;
use sync::Semaphore;
#[test]
fn basic_submit() {
struct Basic { inner: UnsafeCommandBuffer<Arc<StandardCommandPool>> }
unsafe impl CommandBuffer for Basic {
type Pool = Arc<StandardCommandPool>;
fn inner(&self) -> &UnsafeCommandBuffer<Self::Pool> { &self.inner }
unsafe fn on_submit<F>(&self, _: &Arc<Queue>, fence: F)
-> SubmitInfo<Self::SemaphoresWaitIterator,
Self::SemaphoresSignalIterator>
where F: FnOnce() -> Arc<Fence>
{
SubmitInfo::empty()
}
}
let (device, queue) = gfx_dev_and_queue!();
let pool = Device::standard_command_pool(&device, queue.family());
let kind = Kind::Primary::<EmptySinglePassRenderPass, Framebuffer<EmptySinglePassRenderPass, EmptyAttachmentsList>>;
let cb = UnsafeCommandBufferBuilder::new(pool, kind, Flags::OneTimeSubmit).unwrap();
let cb = Basic { inner: cb.build().unwrap() };
let _s = cb.submit(&queue);
}
}*/

View File

@ -0,0 +1,40 @@
// Copyright (c) 2016 The vulkano developers
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT
// license <LICENSE-MIT or http://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.
//! Low-level builders that allow submitting an operation to a queue.
pub use self::queue_present::SubmitPresentBuilder;
pub use self::queue_present::SubmitPresentError;
pub use self::queue_submit::SubmitCommandBufferBuilder;
pub use self::queue_submit::SubmitCommandBufferError;
pub use self::semaphores_wait::SubmitSemaphoresWaitBuilder;
mod queue_present;
mod queue_submit;
mod semaphores_wait;
/// Contains all the possible submission builders.
#[derive(Debug)]
pub enum SubmitAnyBuilder<'a> {
Empty,
SemaphoresWait(SubmitSemaphoresWaitBuilder<'a>),
CommandBuffer(SubmitCommandBufferBuilder<'a>),
QueuePresent(SubmitPresentBuilder<'a>),
}
impl<'a> SubmitAnyBuilder<'a> {
/// Returns true if equal to `SubmitAnyBuilder::Empty`.
#[inline]
pub fn is_empty(&self) -> bool {
match self {
&SubmitAnyBuilder::Empty => true,
_ => false,
}
}
}

View File

@ -0,0 +1,176 @@
// Copyright (c) 2017 The vulkano developers
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT
// license <LICENSE-MIT or http://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 std::error;
use std::fmt;
use std::marker::PhantomData;
use std::mem;
use std::ptr;
use smallvec::SmallVec;
use command_buffer::cb::UnsafeCommandBuffer;
use command_buffer::pool::CommandPool;
use device::Queue;
use swapchain::Swapchain;
use sync::Fence;
use sync::PipelineStages;
use sync::Semaphore;
use check_errors;
use vk;
use Error;
use OomError;
use VulkanObject;
use VulkanPointers;
use SynchronizedVulkanObject;
/// Prototype for a submission that presents a swapchain on the screen.
#[derive(Debug)]
pub struct SubmitPresentBuilder<'a> {
wait_semaphores: SmallVec<[vk::Semaphore; 8]>,
swapchains: SmallVec<[vk::SwapchainKHR; 4]>,
image_indices: SmallVec<[u32; 4]>,
marker: PhantomData<&'a ()>,
}
impl<'a> SubmitPresentBuilder<'a> {
/// Builds a new empty `SubmitPresentBuilder`.
#[inline]
pub fn new() -> SubmitPresentBuilder<'a> {
SubmitPresentBuilder {
wait_semaphores: SmallVec::new(),
swapchains: SmallVec::new(),
image_indices: SmallVec::new(),
marker: PhantomData,
}
}
/// Adds a semaphore to be waited upon before the presents are executed.
#[inline]
pub unsafe fn add_wait_semaphore(&mut self, semaphore: &'a Semaphore) {
self.wait_semaphores.push(semaphore.internal_object());
}
/// Adds an image of a swapchain to be presented.
#[inline]
pub unsafe fn add_swapchain(&mut self, swapchain: &'a Swapchain, image_num: u32) {
debug_assert!(image_num < swapchain.num_images());
self.swapchains.push(swapchain.internal_object());
self.image_indices.push(image_num);
}
/// Submits the command. Calls `vkQueuePresentKHR`.
///
/// # Panic
///
/// Panics if no swapchain image has been added to the builder.
pub fn submit(mut self, queue: &Queue) -> Result<(), SubmitPresentError> {
unsafe {
debug_assert_eq!(self.swapchains.len(), self.image_indices.len());
assert!(!self.swapchains.is_empty(),
"Tried to submit a present command without any swapchain");
let vk = queue.device().pointers();
let queue = queue.internal_object_guard();
let mut results = vec![mem::uninitialized(); self.swapchains.len()]; // TODO: alloca
let infos = vk::PresentInfoKHR {
sType: vk::STRUCTURE_TYPE_PRESENT_INFO_KHR,
pNext: ptr::null(),
waitSemaphoreCount: self.wait_semaphores.len() as u32,
pWaitSemaphores: self.wait_semaphores.as_ptr(),
swapchainCount: self.swapchains.len() as u32,
pSwapchains: self.swapchains.as_ptr(),
pImageIndices: self.image_indices.as_ptr(),
pResults: results.as_mut_ptr(),
};
try!(check_errors(vk.QueuePresentKHR(*queue, &infos)));
for result in results {
// TODO: AMD driver initially didn't write the results ; check that it's been fixed
//try!(check_errors(result));
}
Ok(())
}
}
}
/// Error that can happen when submitting the present prototype.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)]
pub enum SubmitPresentError {
/// Not enough memory.
OomError(OomError),
/// The connection to the device has been lost.
DeviceLost,
/// The surface is no longer accessible and must be recreated.
SurfaceLost,
/// The surface has changed in a way that makes the swapchain unusable. You must query the
/// surface's new properties and recreate a new swapchain if you want to continue drawing.
OutOfDate,
}
impl error::Error for SubmitPresentError {
#[inline]
fn description(&self) -> &str {
match *self {
SubmitPresentError::OomError(_) => "not enough memory",
SubmitPresentError::DeviceLost => "the connection to the device has been lost",
SubmitPresentError::SurfaceLost => "the surface of this swapchain is no longer valid",
SubmitPresentError::OutOfDate => "the swapchain needs to be recreated",
}
}
#[inline]
fn cause(&self) -> Option<&error::Error> {
match *self {
SubmitPresentError::OomError(ref err) => Some(err),
_ => None
}
}
}
impl fmt::Display for SubmitPresentError {
#[inline]
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(fmt, "{}", error::Error::description(self))
}
}
impl From<Error> for SubmitPresentError {
#[inline]
fn from(err: Error) -> SubmitPresentError {
match err {
err @ Error::OutOfHostMemory => SubmitPresentError::OomError(OomError::from(err)),
err @ Error::OutOfDeviceMemory => SubmitPresentError::OomError(OomError::from(err)),
Error::DeviceLost => SubmitPresentError::DeviceLost,
Error::SurfaceLost => SubmitPresentError::SurfaceLost,
Error::OutOfDate => SubmitPresentError::OutOfDate,
_ => panic!("unexpected error: {:?}", err)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[should_panic(expected = "Tried to submit a present command without any swapchain")]
fn no_swapchain_added() {
let (_, queue) = gfx_dev_and_queue!();
let _ = SubmitPresentBuilder::new().submit(&queue);
}
}

View File

@ -0,0 +1,175 @@
// Copyright (c) 2017 The vulkano developers
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT
// license <LICENSE-MIT or http://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 std::error;
use std::fmt;
use std::marker::PhantomData;
use std::ptr;
use smallvec::SmallVec;
use command_buffer::cb::UnsafeCommandBuffer;
use command_buffer::pool::CommandPool;
use device::Queue;
use sync::Fence;
use sync::PipelineStages;
use sync::Semaphore;
use check_errors;
use vk;
use Error;
use OomError;
use VulkanObject;
use VulkanPointers;
use SynchronizedVulkanObject;
/// Prototype for a submission that executes command buffers.
#[derive(Debug)]
pub struct SubmitCommandBufferBuilder<'a> {
wait_semaphores: SmallVec<[vk::Semaphore; 16]>,
dest_stages: SmallVec<[vk::PipelineStageFlags; 8]>,
signal_semaphores: SmallVec<[vk::Semaphore; 16]>,
command_buffers: SmallVec<[vk::CommandBuffer; 4]>,
fence: 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(),
dest_stages: SmallVec::new(),
signal_semaphores: SmallVec::new(),
command_buffers: SmallVec::new(),
fence: 0,
marker: PhantomData,
}
}
/// Returns true if this builder will signal a fence when submitted.
#[inline]
pub fn has_fence(&self) -> bool {
self.fence != 0
}
/// Adds an operation that signals a fence after this submission ends.
///
/// If a fence was previously set, it will no longer be signaled.
#[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.
#[inline]
pub unsafe fn add_wait_semaphore(&mut self, semaphore: &'a Semaphore, stages: PipelineStages) {
debug_assert!(Into::<vk::PipelineStageFlagBits>::into(stages) != 0);
self.wait_semaphores.push(semaphore.internal_object());
self.dest_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.
#[inline]
pub unsafe fn add_command_buffer<P>(&mut self, command_buffer: &'a UnsafeCommandBuffer<P>)
where P: CommandPool
{
self.command_buffers.push(command_buffer.internal_object());
}
/// Returns the number of semaphores to signal.
#[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.
#[inline]
pub unsafe fn add_signal_semaphore(&mut self, semaphore: &'a Semaphore) {
self.signal_semaphores.push(semaphore.internal_object());
}
/// Submits the command buffer.
pub fn submit(mut self, queue: &Queue) -> Result<(), SubmitCommandBufferError> {
unsafe {
let vk = queue.device().pointers();
let queue = queue.internal_object_guard();
debug_assert_eq!(self.wait_semaphores.len(), self.dest_stages.len());
let batch = vk::SubmitInfo {
sType: vk::STRUCTURE_TYPE_SUBMIT_INFO,
pNext: ptr::null(),
waitSemaphoreCount: self.wait_semaphores.len() as u32,
pWaitSemaphores: self.wait_semaphores.as_ptr(),
pWaitDstStageMask: self.dest_stages.as_ptr(),
commandBufferCount: self.command_buffers.len() as u32,
pCommandBuffers: self.command_buffers.as_ptr(),
signalSemaphoreCount: self.signal_semaphores.len() as u32,
pSignalSemaphores: self.signal_semaphores.as_ptr(),
};
try!(check_errors(vk.QueueSubmit(*queue, 1, &batch, self.fence)));
Ok(())
}
}
}
/// 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 description(&self) -> &str {
match *self {
SubmitCommandBufferError::OomError(_) => "not enough memory",
SubmitCommandBufferError::DeviceLost => "the connection to the device has been lost",
}
}
#[inline]
fn cause(&self) -> Option<&error::Error> {
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, "{}", error::Error::description(self))
}
}
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)
}
}
}

View File

@ -0,0 +1,81 @@
// Copyright (c) 2016 The vulkano developers
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT
// license <LICENSE-MIT or http://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 smallvec::SmallVec;
use command_buffer::submit::SubmitCommandBufferBuilder;
use command_buffer::submit::SubmitPresentBuilder;
use sync::PipelineStages;
use sync::Semaphore;
use vk;
use VulkanObject;
/// Prototype for a submission that waits on semaphores.
///
/// This prototype can't actually be submitted because it doesn't correspond to anything in Vulkan.
/// However you can convert it into another builder prototype through the `Into` trait.
#[derive(Debug)]
pub struct SubmitSemaphoresWaitBuilder<'a> {
semaphores: SmallVec<[&'a Semaphore; 8]>,
}
impl<'a> SubmitSemaphoresWaitBuilder<'a> {
/// Builds a new empty `SubmitSemaphoresWaitBuilder`.
#[inline]
pub fn new() -> SubmitSemaphoresWaitBuilder<'a> {
SubmitSemaphoresWaitBuilder {
semaphores: SmallVec::new(),
}
}
/// Adds an operation that waits on a semaphore.
///
/// The semaphore must be signaled by a previous submission.
#[inline]
pub unsafe fn add_wait_semaphore(&mut self, semaphore: &'a Semaphore) {
self.semaphores.push(semaphore);
}
/// Merges this builder with another builder.
#[inline]
pub fn merge(&mut self, mut other: SubmitSemaphoresWaitBuilder<'a>) {
self.semaphores.extend(other.semaphores.drain());
}
}
impl<'a> Into<SubmitCommandBufferBuilder<'a>> for SubmitSemaphoresWaitBuilder<'a> {
#[inline]
fn into(mut self) -> SubmitCommandBufferBuilder<'a> {
unsafe {
let mut builder = SubmitCommandBufferBuilder::new();
for sem in self.semaphores.drain() {
builder.add_wait_semaphore(sem, PipelineStages {
// TODO: correct stages ; hard
all_commands: true,
.. PipelineStages::none()
});
}
builder
}
}
}
impl<'a> Into<SubmitPresentBuilder<'a>> for SubmitSemaphoresWaitBuilder<'a> {
#[inline]
fn into(mut self) -> SubmitPresentBuilder<'a> {
unsafe {
let mut builder = SubmitPresentBuilder::new();
for sem in self.semaphores.drain() {
builder.add_wait_semaphore(sem);
}
builder
}
}
}

View File

@ -0,0 +1,214 @@
// Copyright (c) 2016 The vulkano developers
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT
// license <LICENSE-MIT or http://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 std::error;
use std::sync::Arc;
use std::sync::Mutex;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering;
use buffer::Buffer;
use command_buffer::cb::UnsafeCommandBuffer;
use command_buffer::pool::CommandPool;
use command_buffer::submit::SubmitAnyBuilder;
use command_buffer::submit::SubmitCommandBufferBuilder;
use device::Device;
use device::DeviceOwned;
use device::Queue;
use image::Image;
use sync::DummyFuture;
use sync::GpuFuture;
use SafeDeref;
use VulkanObject;
pub unsafe trait CommandBuffer: DeviceOwned {
/// The command pool of the command buffer.
type Pool: CommandPool;
/// Returns the underlying `UnsafeCommandBuffer` of this command buffer.
fn inner(&self) -> &UnsafeCommandBuffer<Self::Pool>;
/// Executes this command buffer on a 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>) -> CommandBufferExecFuture<DummyFuture, Self>
where Self: Sized
{
let device = queue.device().clone();
self.execute_after(DummyFuture::new(device), queue)
}
/// Executes the command buffer after an existing future.
///
/// # 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>) -> CommandBufferExecFuture<F, Self>
where Self: Sized, F: GpuFuture
{
assert_eq!(self.device().internal_object(), future.device().internal_object());
if !future.queue_change_allowed() {
assert!(future.queue().unwrap().is_same(&queue));
}
CommandBufferExecFuture {
previous: future,
command_buffer: self,
queue: queue,
submitted: Mutex::new(false),
finished: AtomicBool::new(false),
}
}
// FIXME: lots of other methods
}
unsafe impl<T> CommandBuffer for T where T: SafeDeref, T::Target: CommandBuffer {
type Pool = <T::Target as CommandBuffer>::Pool;
#[inline]
fn inner(&self) -> &UnsafeCommandBuffer<Self::Pool> {
(**self).inner()
}
}
/// 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: CommandBuffer {
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: CommandBuffer
{
#[inline]
fn is_finished(&self) -> bool {
self.finished.load(Ordering::SeqCst)
}
unsafe fn build_submission(&self) -> Result<SubmitAnyBuilder, Box<error::Error>> {
Ok(match try!(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(present) => {
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<(), Box<error::Error>> {
unsafe {
let mut submitted = self.submitted.lock().unwrap();
if *submitted {
return Ok(());
}
let queue = self.queue.clone();
match try!(self.build_submission()) {
SubmitAnyBuilder::Empty => {},
SubmitAnyBuilder::CommandBuffer(builder) => {
try!(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) {
self.finished.store(true, Ordering::SeqCst);
self.previous.signal_finished();
}
#[inline]
fn queue_change_allowed(&self) -> bool {
false
}
#[inline]
fn queue(&self) -> Option<&Arc<Queue>> {
debug_assert!(match self.previous.queue() {
None => true,
Some(q) => q.is_same(&self.queue)
});
Some(&self.queue)
}
#[inline]
fn check_buffer_access(&self, buffer: &Buffer, exclusive: bool, queue: &Queue) -> bool {
// FIXME: check the command buffer too
self.previous.check_buffer_access(buffer, exclusive, queue)
}
#[inline]
fn check_image_access(&self, image: &Image, exclusive: bool, queue: &Queue) -> bool {
// FIXME: check the command buffer too
self.previous.check_image_access(image, exclusive, queue)
}
}
unsafe impl<F, Cb> DeviceOwned for CommandBufferExecFuture<F, Cb>
where F: GpuFuture, Cb: CommandBuffer
{
#[inline]
fn device(&self) -> &Arc<Device> {
self.command_buffer.device()
}
}
impl<F, Cb> Drop for CommandBufferExecFuture<F, Cb> where F: GpuFuture, Cb: CommandBuffer {
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.previous.signal_finished();
}
}
}
}

View File

@ -553,7 +553,7 @@ impl From<Error> for DeviceCreationError {
}
/// Represents a queue where commands can be submitted.
// TODO: should use internal synchronization
// TODO: should use internal synchronization?
#[derive(Debug)]
pub struct Queue {
queue: Mutex<vk::Queue>,
@ -569,6 +569,14 @@ impl Queue {
&self.device
}
/// Returns true if this is the same queue as another one.
#[inline]
pub fn is_same(&self, other: &Queue) -> bool {
self.id == other.id &&
self.family == other.family &&
self.device.internal_object() == other.device.internal_object()
}
/// Returns the family this queue belongs to.
#[inline]
pub fn family(&self) -> QueueFamily {
@ -581,9 +589,11 @@ impl Queue {
self.id
}
/// See the docs of wait().
/// Waits until all work on this queue has finished.
///
/// Just like `Device::wait()`, you shouldn't have to call this function in a typical program.
#[inline]
pub fn wait_raw(&self) -> Result<(), OomError> {
pub fn wait(&self) -> Result<(), OomError> {
unsafe {
let vk = self.device.pointers();
let queue = self.queue.lock().unwrap();
@ -591,19 +601,6 @@ impl Queue {
Ok(())
}
}
/// Waits until all work on this queue has finished.
///
/// Just like `Device::wait()`, you shouldn't have to call this function.
///
/// # Panic
///
/// - Panics if the device or host ran out of memory.
///
#[inline]
pub fn wait(&self) {
self.wait_raw().unwrap();
}
}
unsafe impl SynchronizedVulkanObject for Queue {

View File

@ -9,11 +9,9 @@
use std::iter::Empty;
use std::sync::Arc;
use std::sync::Mutex;
use std::sync::Weak;
use command_buffer::Submission;
use device::Device;
use device::Queue;
use format::ClearValue;
use format::FormatDesc;
use format::FormatTy;
@ -81,18 +79,6 @@ pub struct AttachmentImage<F, A = Arc<StdMemoryPool>> where A: MemoryPool {
// Layout to use when the image is used as a framebuffer attachment.
// Must be either "depth-stencil optimal" or "color optimal".
attachment_layout: Layout,
// Additional info behind a mutex.
guarded: Mutex<Guarded>,
}
#[derive(Debug)]
struct Guarded {
// If false, the image is still in the undefined layout.
correct_layout: bool,
// The latest submission that used the image. Used for synchronization purposes.
latest_submission: Option<Weak<Submission>>, // TODO: can use `Weak::new()` once it's stabilized
}
impl<F> AttachmentImage<F> {
@ -182,10 +168,6 @@ impl<F> AttachmentImage<F> {
format: format,
attachment_layout: if is_depth { Layout::DepthStencilAttachmentOptimal }
else { Layout::ColorAttachmentOptimal },
guarded: Mutex::new(Guarded {
correct_layout: false,
latest_submission: None,
}),
}))
}
}
@ -209,6 +191,11 @@ unsafe impl<F, A> Image for AttachmentImage<F, A> where F: 'static + Send + Sync
fn conflict_key(&self, _: u32, _: u32, _: u32, _: u32) -> u64 {
self.image.key()
}
#[inline]
fn gpu_access(&self, exclusive_access: bool, queue: &Queue) -> bool {
false // FIXME:
}
}
unsafe impl<F, A> ImageClearValue<F::ClearValue> for AttachmentImage<F, A>

View File

@ -9,13 +9,10 @@
use std::iter::Empty;
use std::sync::Arc;
use std::sync::Mutex;
use std::sync::Weak;
use std::sync::atomic::AtomicBool;
use smallvec::SmallVec;
use command_buffer::Submission;
use device::Device;
use device::Queue;
use format::FormatDesc;
use image::Dimensions;
use image::sys::ImageCreationError;
@ -43,13 +40,6 @@ pub struct ImmutableImage<F, A = Arc<StdMemoryPool>> where A: MemoryPool {
dimensions: Dimensions,
memory: A::Alloc,
format: F,
per_layer: SmallVec<[PerLayer; 1]>,
}
#[derive(Debug)]
struct PerLayer {
latest_write_submission: Mutex<Option<Weak<Submission>>>, // TODO: can use `Weak::new()` once it's stabilized
started_reading: AtomicBool,
}
impl<F> ImmutableImage<F> {
@ -104,16 +94,6 @@ impl<F> ImmutableImage<F> {
memory: mem,
dimensions: dimensions,
format: format,
per_layer: {
let mut v = SmallVec::new();
for _ in 0 .. dimensions.array_layers_with_cube() {
v.push(PerLayer {
latest_write_submission: Mutex::new(None),
started_reading: AtomicBool::new(false),
});
}
v
},
}))
}
}
@ -136,6 +116,11 @@ unsafe impl<F, A> Image for ImmutableImage<F, A> where F: 'static + Send + Sync,
fn conflict_key(&self, _: u32, _: u32, _: u32, _: u32) -> u64 {
self.image.key()
}
#[inline]
fn gpu_access(&self, exclusive_access: bool, queue: &Queue) -> bool {
false // FIXME:
}
}
unsafe impl<P, F, A> ImageContent<P> for ImmutableImage<F, A>

View File

@ -9,12 +9,10 @@
use std::iter::Empty;
use std::sync::Arc;
use std::sync::Mutex;
use std::sync::Weak;
use smallvec::SmallVec;
use command_buffer::Submission;
use device::Device;
use device::Queue;
use format::ClearValue;
use format::FormatDesc;
use format::FormatTy;
@ -56,21 +54,6 @@ pub struct StorageImage<F, A = Arc<StdMemoryPool>> where A: MemoryPool {
// Queue families allowed to access this image.
queue_families: SmallVec<[u32; 4]>,
// Additional info behind a mutex.
guarded: Mutex<Guarded>,
}
#[derive(Debug)]
struct Guarded {
// If false, the image is still in the undefined layout.
correct_layout: bool,
// The latest submissions that read from this image.
read_submissions: SmallVec<[Weak<Submission>; 4]>,
// The latest submission that writes to this image.
write_submission: Option<Weak<Submission>>, // TODO: can use `Weak::new()` once it's stabilized
}
impl<F> StorageImage<F> {
@ -139,11 +122,6 @@ impl<F> StorageImage<F> {
dimensions: dimensions,
format: format,
queue_families: queue_families,
guarded: Mutex::new(Guarded {
correct_layout: false,
read_submissions: SmallVec::new(),
write_submission: None,
}),
}))
}
}
@ -166,6 +144,11 @@ unsafe impl<F, A> Image for StorageImage<F, A> where F: 'static + Send + Sync, A
fn conflict_key(&self, _: u32, _: u32, _: u32, _: u32) -> u64 {
self.image.key()
}
#[inline]
fn gpu_access(&self, exclusive_access: bool, queue: &Queue) -> bool {
false // FIXME:
}
}
unsafe impl<F, A> ImageClearValue<F::ClearValue> for StorageImage<F, A>

View File

@ -8,10 +8,8 @@
// according to those terms.
use std::sync::Arc;
use std::sync::Mutex;
use std::sync::Weak;
use command_buffer::Submission;
use device::Queue;
use format::ClearValue;
use format::Format;
use format::FormatDesc;
@ -50,13 +48,6 @@ pub struct SwapchainImage {
format: Format,
swapchain: Arc<Swapchain>,
id: u32,
guarded: Mutex<Guarded>,
}
#[derive(Debug)]
struct Guarded {
present_layout: bool,
latest_submission: Option<Weak<Submission>>, // TODO: can use `Weak::new()` once it's stabilized
}
impl SwapchainImage {
@ -74,10 +65,6 @@ impl SwapchainImage {
format: format,
swapchain: swapchain.clone(),
id: id,
guarded: Mutex::new(Guarded {
present_layout: false,
latest_submission: None,
}),
}))
}
@ -114,6 +101,12 @@ unsafe impl Image for SwapchainImage {
fn conflict_key(&self, _: u32, _: u32, _: u32, _: u32) -> u64 {
self.image.key()
}
#[inline]
fn gpu_access(&self, _: bool, _: &Queue) -> bool {
// Swapchain image are only accessible after being acquired.
false
}
}
unsafe impl ImageClearValue<<Format as FormatDesc>::ClearValue> for SwapchainImage

View File

@ -8,6 +8,7 @@
// according to those terms.
use buffer::Buffer;
use device::Queue;
use format::ClearValue;
use format::Format;
use format::PossibleFloatFormatDesc;
@ -135,6 +136,16 @@ pub unsafe trait Image {
/// verify whether they actually overlap.
fn conflict_key(&self, first_layer: u32, num_layers: u32, first_mipmap: u32, num_mipmaps: u32)
-> u64;
/// Returns true if the image can be given access on the given queue.
///
/// This function implementation should remember that it has been called and return `false` if
/// it gets called a second time.
///
/// The only way to know that the GPU has stopped accessing a queue is when the image object
/// gets destroyed. Therefore you are encouraged to use temporary objects or handles (similar
/// to a lock) in order to represent a GPU access.
fn gpu_access(&self, exclusive_access: bool, queue: &Queue) -> bool;
}
unsafe impl<T> Image for T where T: SafeDeref, T::Target: Image {
@ -149,6 +160,11 @@ unsafe impl<T> Image for T where T: SafeDeref, T::Target: Image {
{
(**self).conflict_key(first_layer, num_layers, first_mipmap, num_mipmaps)
}
#[inline]
fn gpu_access(&self, exclusive_access: bool, queue: &Queue) -> bool {
(**self).gpu_access(exclusive_access, queue)
}
}
/// Extension trait for images. Checks whether the value `T` can be used as a clear value for the

View File

@ -125,12 +125,14 @@
//! section), you can draw on it. This is done in three steps:
//!
//! - Call `Swapchain::acquire_next_image`. This function will return the index of the image
//! (within the list returned by `Swapchain::new`) that is available to draw.
//! (within the list returned by `Swapchain::new`) that is available to draw, plus a future
//! representing the moment when the GPU will gain access to that image.
//! - Draw on that image just like you would draw to any other image (see the documentation of
//! the `pipeline` module).
//! - Call `Swapchain::present` with the same index in order to tell the implementation that you
//! are finished drawing to the image and that it can queue a command to present the image on
//! the screen after the draw operations are finished.
//! the `pipeline` module). You need to chain the draw after the future that was returned by
//! `acquire_next_image`.
//! - Call `Swapchain::present` with the same index and by chaining the futures, in order to tell
//! the implementation that you are finished drawing to the image and that it can queue a
//! command to present the image on the screen after the draw operations are finished.
//!
//! TODO: add example here
//! loop {
@ -155,7 +157,7 @@
//! ```no_run
//! # use std::time::Duration;
//! use vulkano::swapchain::AcquireError;
//! use vulkano::swapchain::PresentError;
//! use vulkano::sync::GpuFuture;
//!
//! // let mut swapchain = Swapchain::new(...);
//! # let mut swapchain: (::std::sync::Arc<::vulkano::swapchain::Swapchain>, _) = unsafe { ::std::mem::uninitialized() };
@ -170,19 +172,20 @@
//!
//! let (ref swapchain, ref _images) = swapchain;
//!
//! let index = match swapchain.acquire_next_image(Duration::from_millis(500)) {
//! Ok(img) => img,
//! let (index, acq_future) = match swapchain.acquire_next_image(Duration::from_millis(500)) {
//! Ok(r) => r,
//! Err(AcquireError::OutOfDate) => { recreate_swapchain = true; continue; },
//! Err(err) => panic!("{:?}", err)
//! };
//!
//! // ...
//!
//! match swapchain.present(&queue, index) {
//! Ok(()) => (),
//! Err(PresentError::OutOfDate) => { recreate_swapchain = true; },
//! Err(err) => panic!("{:?}", err),
//! }
//! let final_future = acq_future
//! // .then_execute(...)
//! .then_swapchain_present(queue.clone(), swapchain.clone(), index)
//! .then_signal_fence();
//!
//! final_future.flush().unwrap(); // TODO: PresentError?
//! }
//! ```
//!
@ -201,9 +204,10 @@ pub use self::surface::SupportedCompositeAlpha;
pub use self::surface::SupportedCompositeAlphaIter;
pub use self::surface::ColorSpace;
pub use self::surface::SurfaceCreationError;
pub use self::swapchain::Swapchain;
pub use self::swapchain::AcquireError;
pub use self::swapchain::PresentError;
pub use self::swapchain::PresentFuture;
pub use self::swapchain::Swapchain;
pub use self::swapchain::SwapchainAcquireFuture;
pub mod display;
mod surface;

View File

@ -13,13 +13,22 @@ use std::mem;
use std::ptr;
use std::sync::Arc;
use std::sync::Mutex;
use std::sync::Weak;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering;
use std::time::Duration;
use buffer::Buffer;
use command_buffer::submit::SubmitAnyBuilder;
use command_buffer::submit::SubmitCommandBufferBuilder;
use command_buffer::submit::SubmitPresentBuilder;
use command_buffer::submit::SubmitSemaphoresWaitBuilder;
use device::Device;
use device::DeviceOwned;
use device::Queue;
use format::Format;
use format::FormatDesc;
use image::Image;
use image::ImageDimensions;
use image::sys::UnsafeImage;
use image::sys::Usage as ImageUsage;
@ -30,6 +39,8 @@ use swapchain::PresentMode;
use swapchain::Surface;
use swapchain::SurfaceTransform;
use swapchain::SurfaceSwapchainLock;
use sync::Fence;
use sync::GpuFuture;
use sync::Semaphore;
use sync::SharingMode;
@ -49,15 +60,6 @@ pub struct Swapchain {
surface: Arc<Surface>,
swapchain: vk::SwapchainKHR,
/// Pool of semaphores from which a semaphore is retrieved when acquiring an image.
///
/// We need to use a queue so that we don't use the same semaphore twice in a row. The length
/// of the queue is strictly superior to the number of images, in case the driver lets us
/// acquire an image before it is presented.
semaphores_pool: Mutex<Vec<Arc<Semaphore>>>,
images_semaphores: Mutex<Vec<Option<Arc<Semaphore>>>>,
// If true, that means we have used this swapchain to recreate a new swapchain. The current
// swapchain can no longer be used for anything except presenting already-acquired images.
//
@ -77,6 +79,9 @@ pub struct Swapchain {
alpha: CompositeAlpha,
mode: PresentMode,
clipped: bool,
// TODO: meh for Mutex
images: Mutex<Vec<Weak<SwapchainImage>>>,
}
impl Swapchain {
@ -214,8 +219,6 @@ impl Swapchain {
device: device.clone(),
surface: surface.clone(),
swapchain: swapchain,
semaphores_pool: Mutex::new(Vec::new()),
images_semaphores: Mutex::new(Vec::new()),
stale: Mutex::new(false),
num_images: num_images,
format: format,
@ -228,6 +231,7 @@ impl Swapchain {
alpha: alpha,
mode: mode,
clipped: clipped,
images: Mutex::new(Vec::new()), // Filled below.
});
let images = unsafe {
@ -250,30 +254,22 @@ impl Swapchain {
SwapchainImage::from_raw(unsafe_image, format, &swapchain, id as u32).unwrap() // TODO: propagate error
}).collect::<Vec<_>>();
{
let mut semaphores = swapchain.images_semaphores.lock().unwrap();
for _ in 0 .. images.len() {
semaphores.push(None);
}
}
for _ in 0 .. images.len() + 1 {
// TODO: check if this change is okay (maybe the Arc can be omitted?) - Mixthos
//swapchain.semaphores_pool.push(try!(Semaphore::new(device.clone())));
swapchain.semaphores_pool.lock().unwrap().push(Arc::new(try!(Semaphore::raw(device.clone()))));
}
*swapchain.images.lock().unwrap() = images.iter().map(|i| Arc::downgrade(i)).collect();
Ok((swapchain, images))
}
/// Tries to take ownership of an image in order to draw on it.
///
/// The function returns the index of the image in the array of images that was returned
/// when creating the swapchain.
/// when creating the swapchain, plus a future that represents the moment when the image will
/// become available from the GPU (which may not be *immediately*).
///
/// If you try to draw on an image without acquiring it first, the execution will block. (TODO
/// behavior may change).
pub fn acquire_next_image(&self, timeout: Duration) -> Result<usize, AcquireError> {
// TODO: has to make sure vkQueuePresent is called, because calling acquire_next_image many
// times in a row is an error
// TODO: swapchain must not have been replaced by being passed as the VkSwapchainCreateInfoKHR::oldSwapchain value to vkCreateSwapchainKHR
pub fn acquire_next_image(&self, timeout: Duration) -> Result<(usize, SwapchainAcquireFuture), AcquireError> {
unsafe {
let stale = self.stale.lock().unwrap();
if *stale {
@ -282,7 +278,7 @@ impl Swapchain {
let vk = self.device.pointers();
let semaphore = self.semaphores_pool.lock().unwrap().remove(0);
let semaphore = try!(Semaphore::new(self.device.clone()));
let timeout_ns = timeout.as_secs().saturating_mul(1_000_000_000)
.saturating_add(timeout.subsec_nanos() as u64);
@ -290,7 +286,7 @@ impl Swapchain {
let mut out = mem::uninitialized();
let r = try!(check_errors(vk.AcquireNextImageKHR(self.device.internal_object(),
self.swapchain, timeout_ns,
semaphore.internal_object(), 0, // TODO: timeout
semaphore.internal_object(), 0,
&mut out)));
let id = match r {
@ -301,10 +297,12 @@ impl Swapchain {
s => panic!("unexpected success value: {:?}", s)
};
let mut images_semaphores = self.images_semaphores.lock().unwrap();
images_semaphores[id] = Some(semaphore);
Ok(id)
Ok((id, SwapchainAcquireFuture {
semaphore: semaphore,
id: id,
image: self.images.lock().unwrap().get(id).unwrap().clone(),
finished: AtomicBool::new(false),
}))
}
}
@ -315,40 +313,26 @@ impl Swapchain {
///
/// The actual behavior depends on the present mode that you passed when creating the
/// swapchain.
pub fn present(&self, queue: &Arc<Queue>, index: usize) -> Result<(), PresentError> {
let vk = self.device.pointers();
// TODO: use another API, since taking by Arc is meh
pub fn present<F>(me: Arc<Self>, before: F, queue: Arc<Queue>, index: usize)
-> PresentFuture<F>
where F: GpuFuture
{
assert!(index < me.num_images as usize);
let wait_semaphore = {
let mut images_semaphores = self.images_semaphores.lock().unwrap();
images_semaphores[index].take().expect("Trying to present an image that was \
not acquired")
};
let swapchain_image = me.images.lock().unwrap().get(index).unwrap().upgrade().unwrap(); // TODO: return error instead
// Normally if `check_image_access` returns false we're supposed to call the `gpu_access`
// function on the image instead. But since we know that this method on `SwapchainImage`
// always returns false anyway (by design), we don't need to do it.
assert!(before.check_image_access(&swapchain_image, true, &queue)); // TODO: return error isntead
// FIXME: the semaphore may be destroyed ; need to return it
unsafe {
let mut result = mem::uninitialized();
let queue = queue.internal_object_guard();
let index = index as u32;
let infos = vk::PresentInfoKHR {
sType: vk::STRUCTURE_TYPE_PRESENT_INFO_KHR,
pNext: ptr::null(),
waitSemaphoreCount: 1,
pWaitSemaphores: &wait_semaphore.internal_object(),
swapchainCount: 1,
pSwapchains: &self.swapchain,
pImageIndices: &index,
pResults: &mut result,
};
try!(check_errors(vk.QueuePresentKHR(*queue, &infos)));
//try!(check_errors(result)); // TODO: AMD driver doesn't seem to write the result
PresentFuture {
previous: before,
queue: queue,
swapchain: me,
image_id: index as u32,
finished: AtomicBool::new(false),
}
self.semaphores_pool.lock().unwrap().push(wait_semaphore);
Ok(())
}
/// Returns the number of images of the swapchain.
@ -414,23 +398,14 @@ impl Swapchain {
pub fn clipped(&self) -> bool {
self.clipped
}
}
unsafe impl VulkanObject for Swapchain {
type Object = vk::SwapchainKHR;
/*/// Returns the semaphore that is going to be signalled when the image is going to be ready
/// to be drawn upon.
///
/// Returns `None` if the image was not acquired first, or was already presented.
// TODO: racy, as someone could present the image before using the semaphore
#[inline]
pub fn image_semaphore(&self, id: u32) -> Option<Arc<Semaphore>> {
let semaphores = self.images_semaphores.lock().unwrap();
semaphores[id as usize].as_ref().map(|s| s.clone())
}*/
// TODO: the design of this functions depends on https://github.com/KhronosGroup/Vulkan-Docs/issues/155
#[inline]
#[doc(hidden)]
pub fn image_semaphore(&self, id: u32, semaphore: Arc<Semaphore>) -> Option<Arc<Semaphore>> {
let mut semaphores = self.images_semaphores.lock().unwrap();
mem::replace(&mut semaphores[id as usize], Some(semaphore))
fn internal_object(&self) -> vk::SwapchainKHR {
self.swapchain
}
}
@ -445,6 +420,93 @@ impl Drop for Swapchain {
}
}
/// Represents the moment when the GPU will have access to a swapchain image.
#[must_use]
pub struct SwapchainAcquireFuture {
semaphore: Semaphore,
id: usize,
image: Weak<SwapchainImage>,
finished: AtomicBool,
}
impl SwapchainAcquireFuture {
/// Returns the index of the image in the list of images returned when creating the swapchain.
#[inline]
pub fn image_id(&self) -> usize {
self.id
}
}
unsafe impl GpuFuture for SwapchainAcquireFuture {
#[inline]
fn is_finished(&self) -> bool {
self.finished.load(Ordering::SeqCst)
}
#[inline]
unsafe fn build_submission(&self) -> Result<SubmitAnyBuilder, Box<error::Error>> {
let mut sem = SubmitSemaphoresWaitBuilder::new();
sem.add_wait_semaphore(&self.semaphore);
Ok(SubmitAnyBuilder::SemaphoresWait(sem))
}
#[inline]
fn flush(&self) -> Result<(), Box<error::Error>> {
Ok(())
}
#[inline]
unsafe fn signal_finished(&self) {
self.finished.store(true, Ordering::SeqCst);
}
#[inline]
fn queue_change_allowed(&self) -> bool {
true
}
#[inline]
fn queue(&self) -> Option<&Arc<Queue>> {
None
}
#[inline]
fn check_buffer_access(&self, buffer: &Buffer, exclusive: bool, queue: &Queue) -> bool {
false
}
#[inline]
fn check_image_access(&self, image: &Image, exclusive: bool, queue: &Queue) -> bool {
if let Some(sc_img) = self.image.upgrade() {
sc_img.inner().internal_object() == image.inner().internal_object()
} else {
false
}
}
}
unsafe impl DeviceOwned for SwapchainAcquireFuture {
#[inline]
fn device(&self) -> &Arc<Device> {
self.semaphore.device()
}
}
impl Drop for SwapchainAcquireFuture {
fn drop(&mut self) {
if !*self.finished.get_mut() {
panic!() // FIXME: what to do?
/*// TODO: handle errors?
let fence = Fence::new(self.device().clone()).unwrap();
let mut builder = SubmitCommandBufferBuilder::new();
builder.add_wait_semaphore(&self.semaphore);
builder.set_signal_fence(&fence);
builder.submit(... which queue ? ...).unwrap();
fence.wait(Duration::from_secs(600)).unwrap();*/
}
}
}
/// Error that can happen when calling `acquire_next_image`.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)]
@ -494,6 +556,13 @@ impl fmt::Display for AcquireError {
}
}
impl From<OomError> for AcquireError {
#[inline]
fn from(err: OomError) -> AcquireError {
AcquireError::OomError(err)
}
}
impl From<Error> for AcquireError {
#[inline]
fn from(err: Error) -> AcquireError {
@ -508,61 +577,110 @@ impl From<Error> for AcquireError {
}
}
/// Error that can happen when calling `acquire_next_image`.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)]
pub enum PresentError {
/// Not enough memory.
OomError(OomError),
/// The connection to the device has been lost.
DeviceLost,
/// The surface is no longer accessible and must be recreated.
SurfaceLost,
/// The surface has changed in a way that makes the swapchain unusable. You must query the
/// surface's new properties and recreate a new swapchain if you want to continue drawing.
OutOfDate,
/// Represents a swapchain image being presented on the screen.
#[must_use = "Dropping this object will immediately block the thread until the GPU has finished processing the submission"]
pub struct PresentFuture<P> where P: GpuFuture {
previous: P,
queue: Arc<Queue>,
swapchain: Arc<Swapchain>,
image_id: u32,
finished: AtomicBool,
}
impl error::Error for PresentError {
unsafe impl<P> GpuFuture for PresentFuture<P> where P: GpuFuture {
#[inline]
fn description(&self) -> &str {
match *self {
PresentError::OomError(_) => "not enough memory",
PresentError::DeviceLost => "the connection to the device has been lost",
PresentError::SurfaceLost => "the surface of this swapchain is no longer valid",
PresentError::OutOfDate => "the swapchain needs to be recreated",
}
fn is_finished(&self) -> bool {
self.finished.load(Ordering::SeqCst)
}
#[inline]
fn cause(&self) -> Option<&error::Error> {
match *self {
PresentError::OomError(ref err) => Some(err),
_ => None
}
}
}
impl fmt::Display for PresentError {
#[inline]
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(fmt, "{}", error::Error::description(self))
}
}
impl From<Error> for PresentError {
#[inline]
fn from(err: Error) -> PresentError {
match err {
err @ Error::OutOfHostMemory => PresentError::OomError(OomError::from(err)),
err @ Error::OutOfDeviceMemory => PresentError::OomError(OomError::from(err)),
Error::DeviceLost => PresentError::DeviceLost,
Error::SurfaceLost => PresentError::SurfaceLost,
Error::OutOfDate => PresentError::OutOfDate,
_ => panic!("unexpected error: {:?}", err)
unsafe fn build_submission(&self) -> Result<SubmitAnyBuilder, Box<error::Error>> {
let queue = self.previous.queue().map(|q| q.clone());
// TODO: if the swapchain image layout is not PRESENT, should add a transition command
// buffer
Ok(match try!(self.previous.build_submission()) {
SubmitAnyBuilder::Empty => {
let mut builder = SubmitPresentBuilder::new();
builder.add_swapchain(&self.swapchain, self.image_id);
SubmitAnyBuilder::QueuePresent(builder)
},
SubmitAnyBuilder::SemaphoresWait(sem) => {
let mut builder: SubmitPresentBuilder = sem.into();
builder.add_swapchain(&self.swapchain, self.image_id);
SubmitAnyBuilder::QueuePresent(builder)
},
SubmitAnyBuilder::CommandBuffer(mut cb) => {
try!(cb.submit(&queue.unwrap())); // FIXME: wrong because build_submission can be called multiple times
let mut builder = SubmitPresentBuilder::new();
builder.add_swapchain(&self.swapchain, self.image_id);
SubmitAnyBuilder::QueuePresent(builder)
},
SubmitAnyBuilder::QueuePresent(present) => {
unimplemented!() // TODO:
/*present.submit();
let mut builder = SubmitPresentBuilder::new();
builder.add_swapchain(self.command_buffer.inner(), self.image_id);
SubmitAnyBuilder::CommandBuffer(builder)*/
},
})
}
#[inline]
fn flush(&self) -> Result<(), Box<error::Error>> {
unimplemented!()
}
#[inline]
unsafe fn signal_finished(&self) {
self.finished.store(true, Ordering::SeqCst);
self.previous.signal_finished();
}
#[inline]
fn queue_change_allowed(&self) -> bool {
false
}
#[inline]
fn queue(&self) -> Option<&Arc<Queue>> {
debug_assert!(match self.previous.queue() {
None => true,
Some(q) => q.is_same(&self.queue)
});
Some(&self.queue)
}
#[inline]
fn check_buffer_access(&self, buffer: &Buffer, exclusive: bool, queue: &Queue) -> bool {
unimplemented!() // TODO: VK specs don't say whether it is legal to do that
}
#[inline]
fn check_image_access(&self, image: &Image, exclusive: bool, queue: &Queue) -> bool {
unimplemented!() // TODO: VK specs don't say whether it is legal to do that
}
}
unsafe impl<P> DeviceOwned for PresentFuture<P> where P: GpuFuture {
#[inline]
fn device(&self) -> &Arc<Device> {
self.queue.device()
}
}
impl<P> Drop for PresentFuture<P> where P: GpuFuture {
fn drop(&mut self) {
unsafe {
if !*self.finished.get_mut() {
// TODO: handle errors?
self.flush().unwrap();
// Block until the queue finished.
self.queue().unwrap().wait().unwrap();
self.previous.signal_finished();
}
}
}
}

View File

@ -46,40 +46,18 @@ pub struct Fence<D = Arc<Device>> where D: SafeDeref<Target = Device> {
}
impl<D> Fence<D> where D: SafeDeref<Target = Device> {
/// See the docs of new().
#[inline]
pub fn raw(device: D) -> Result<Fence<D>, OomError> {
Fence::new_impl(device, false)
}
/// Builds a new fence.
///
/// # Panic
///
/// - Panics if the device or host ran out of memory.
///
#[inline]
pub fn new(device: D) -> Arc<Fence<D>> {
Arc::new(Fence::raw(device).unwrap())
pub fn new(device: D) -> Result<Fence<D>, OomError> {
Fence::new_impl(device, false)
}
/// See the docs of signaled().
#[inline]
pub fn signaled_raw(device: D) -> Result<Fence<D>, OomError> {
pub fn signaled(device: D) -> Result<Fence<D>, OomError> {
Fence::new_impl(device, true)
}
/// Builds a new fence already in the "signaled" state.
///
/// # Panic
///
/// - Panics if the device or host ran out of memory.
///
#[inline]
pub fn signaled(device: D) -> Arc<Fence<D>> {
Arc::new(Fence::signaled_raw(device).unwrap())
}
fn new_impl(device: D, signaled: bool) -> Result<Fence<D>, OomError> {
let fence = unsafe {
let infos = vk::FenceCreateInfo {
@ -317,7 +295,6 @@ impl From<Error> for FenceWaitError {
#[cfg(test)]
mod tests {
use std::sync::Arc;
use std::time::Duration;
use sync::Fence;
@ -325,7 +302,7 @@ mod tests {
fn fence_create() {
let (device, _) = gfx_dev_and_queue!();
let fence = Fence::new(device.clone());
let fence = Fence::new(device.clone()).unwrap();
assert!(!fence.ready().unwrap());
}
@ -333,7 +310,7 @@ mod tests {
fn fence_create_signaled() {
let (device, _) = gfx_dev_and_queue!();
let fence = Fence::signaled(device.clone());
let fence = Fence::signaled(device.clone()).unwrap();
assert!(fence.ready().unwrap());
}
@ -341,7 +318,7 @@ mod tests {
fn fence_signaled_wait() {
let (device, _) = gfx_dev_and_queue!();
let fence = Fence::signaled(device.clone());
let fence = Fence::signaled(device.clone()).unwrap();
fence.wait(Duration::new(0, 10)).unwrap();
}
@ -349,8 +326,8 @@ mod tests {
fn fence_reset() {
let (device, _) = gfx_dev_and_queue!();
let mut fence = Fence::signaled(device.clone());
Arc::get_mut(&mut fence).unwrap().reset();
let mut fence = Fence::signaled(device.clone()).unwrap();
fence.reset();
assert!(!fence.ready().unwrap());
}
@ -360,22 +337,23 @@ mod tests {
let (device1, _) = gfx_dev_and_queue!();
let (device2, _) = gfx_dev_and_queue!();
let fence1 = Fence::signaled(device1.clone());
let fence2 = Fence::signaled(device2.clone());
let fence1 = Fence::signaled(device1.clone()).unwrap();
let fence2 = Fence::signaled(device2.clone()).unwrap();
let _ = Fence::multi_wait([&*fence1, &*fence2].iter().cloned(), Duration::new(0, 10));
let _ = Fence::multi_wait([&fence1, &fence2].iter().cloned(), Duration::new(0, 10));
}
#[test]
#[should_panic(expected = "Tried to reset multiple fences that didn't belong to the same device")]
fn multireset_different_devices() {
use std::iter::once;
let (device1, _) = gfx_dev_and_queue!();
let (device2, _) = gfx_dev_and_queue!();
let mut fence1 = Fence::signaled(device1.clone());
let mut fence2 = Fence::signaled(device2.clone());
let mut fence1 = Fence::signaled(device1.clone()).unwrap();
let mut fence2 = Fence::signaled(device2.clone()).unwrap();
let _ = Fence::multi_reset(Some(Arc::get_mut(&mut fence1).unwrap()).into_iter()
.chain(Some(Arc::get_mut(&mut fence2).unwrap()).into_iter()));
let _ = Fence::multi_reset(once(&mut fence1).chain(once(&mut fence2)));
}
}

611
vulkano/src/sync/future.rs Normal file
View File

@ -0,0 +1,611 @@
// Copyright (c) 2016 The vulkano developers
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT
// license <LICENSE-MIT or http://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 std::error::Error;
use std::sync::Arc;
use std::sync::Mutex;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering;
use std::time::Duration;
use buffer::Buffer;
use command_buffer::CommandBuffer;
use command_buffer::CommandBufferExecFuture;
use command_buffer::submit::SubmitAnyBuilder;
use command_buffer::submit::SubmitCommandBufferBuilder;
use command_buffer::submit::SubmitSemaphoresWaitBuilder;
use device::Device;
use device::DeviceOwned;
use device::Queue;
use image::Image;
use swapchain::Swapchain;
use swapchain::PresentFuture;
use sync::Fence;
use sync::FenceWaitError;
use sync::Semaphore;
use SafeDeref;
use VulkanObject;
/// Represents an event that will happen on the GPU in the future.
pub unsafe trait GpuFuture: DeviceOwned {
/// Returns `true` if the event happened on the GPU.
///
/// If this returns `false`, then the destuctor of this future will block until it is the case.
///
/// If you didn't call `flush()` yet, then this function will return `false`.
// TODO: what if user submits a cb without fence, calls flush, and then calls is_finished()
// expecting it to return true eventually?
fn is_finished(&self) -> bool;
/// Builds a submission that, if submitted, makes sure that the event represented by this
/// `GpuFuture` will happen, and possibly contains extra elements (eg. a semaphore wait or an
/// event wait) that makes the dependency with subsequent operations work.
///
/// It is the responsibility of the caller to ensure that the submission is going to be
/// submitted only once. However keep in mind that this function can perfectly be called
/// multiple times (as long as the returned object is only submitted once).
///
/// Once the caller has submitted the submission and has determined that the GPU has finished
/// executing it, it should call `signal_finished`. Failure to do so will incur a large runtime
/// overhead, as the future will have to block to make sure that it is finished.
// TODO: better error type
unsafe fn build_submission(&self) -> Result<SubmitAnyBuilder, Box<Error>>;
/// Flushes the future and submits to the GPU the actions that will permit this future to
/// occur.
///
/// The implementation must remember that it was flushed. If the function is called multiple
/// times, only the first time must result in a flush.
// TODO: better error type
fn flush(&self) -> Result<(), Box<Error>>;
/// Sets the future to its "complete" state, meaning that it can safely be destroyed.
///
/// This must only be done if you called `build_submission()`, submitted the returned
/// submission, and determined that it was finished.
unsafe fn signal_finished(&self);
/// Returns the queue that triggers the event. Returns `None` if unknown or irrelevant.
///
/// If this function returns `None` and `queue_change_allowed` returns `false`, then a panic
/// is likely to occur if you use this future. This is only a problem if you implement
/// the `GpuFuture` trait yourself for a type outside of vulkano.
fn queue(&self) -> Option<&Arc<Queue>>;
/// Returns `true` if elements submitted after this future can be submitted to a different
/// queue than the other returned by `queue()`.
fn queue_change_allowed(&self) -> bool;
/// Checks whether submitting something after this future grants access (exclusive or shared,
/// depending on the parameter) to the given buffer on the given queue.
fn check_buffer_access(&self, buffer: &Buffer, exclusive: bool, queue: &Queue) -> bool;
/// Checks whether submitting something after this future grants access (exclusive or shared,
/// depending on the parameter) to the given image on the given queue.
fn check_image_access(&self, image: &Image, exclusive: bool, queue: &Queue) -> bool;
/// Joins this future with another one, representing the moment when both events have happened.
fn join<F>(self, other: F) -> JoinFuture<Self, F>
where Self: Sized, F: GpuFuture
{
assert_eq!(self.device().internal_object(), other.device().internal_object());
if !self.queue_change_allowed() && !other.queue_change_allowed() {
assert!(self.queue().unwrap().is_same(other.queue().unwrap()));
}
JoinFuture {
first: self,
second: other,
}
}
/// Executes a command buffer after this future.
#[inline]
fn then_execute<Cb>(self, queue: Arc<Queue>, command_buffer: Cb)
-> CommandBufferExecFuture<Self, Cb>
where Self: Sized, Cb: CommandBuffer
{
command_buffer.execute_after(self, queue)
}
/// Executes a command buffer after this future, on the same queue as the future.
#[inline]
fn then_execute_same_queue<Cb>(self, command_buffer: Cb) -> CommandBufferExecFuture<Self, Cb>
where Self: Sized, Cb: CommandBuffer
{
let queue = self.queue().unwrap().clone();
command_buffer.execute_after(self, queue)
}
/// Signals a semaphore after this future. Returns another future that represents the signal.
#[inline]
fn then_signal_semaphore(self) -> SemaphoreSignalFuture<Self> where Self: Sized {
let device = self.device().clone();
assert!(self.queue().is_some()); // TODO: document
SemaphoreSignalFuture {
previous: self,
semaphore: Semaphore::new(device).unwrap(),
wait_submitted: Mutex::new(false),
finished: AtomicBool::new(false),
}
}
/// Signals a fence after this future. Returns another future that represents the signal.
#[inline]
fn then_signal_fence(self) -> FenceSignalFuture<Self> where Self: Sized {
let device = self.device().clone();
assert!(self.queue().is_some()); // TODO: document
FenceSignalFuture {
previous: self,
fence: Fence::new(device).unwrap(),
flushed: Mutex::new(false),
}
}
/// Presents a swapchain image after this future.
///
/// You should only ever do this indirectly after a `SwapchainAcquireFuture` of the same image,
/// otherwise an error will occur when flushing.
///
/// > **Note**: This is just a shortcut for the `Swapchain::present()` function.
#[inline]
fn then_swapchain_present(self, queue: Arc<Queue>, swapchain: Arc<Swapchain>,
image_index: usize) -> PresentFuture<Self>
where Self: Sized
{
Swapchain::present(swapchain, self, queue, image_index)
}
}
unsafe impl<T> GpuFuture for T where T: SafeDeref, T::Target: GpuFuture {
#[inline]
fn is_finished(&self) -> bool {
(**self).is_finished()
}
#[inline]
unsafe fn build_submission(&self) -> Result<SubmitAnyBuilder, Box<Error>> {
(**self).build_submission()
}
#[inline]
fn flush(&self) -> Result<(), Box<Error>> {
(**self).flush()
}
#[inline]
unsafe fn signal_finished(&self) {
(**self).signal_finished()
}
#[inline]
fn queue_change_allowed(&self) -> bool {
(**self).queue_change_allowed()
}
#[inline]
fn queue(&self) -> Option<&Arc<Queue>> {
(**self).queue()
}
#[inline]
fn check_buffer_access(&self, buffer: &Buffer, exclusive: bool, queue: &Queue) -> bool {
(**self).check_buffer_access(buffer, exclusive, queue)
}
#[inline]
fn check_image_access(&self, image: &Image, exclusive: bool, queue: &Queue) -> bool {
(**self).check_image_access(image, exclusive, queue)
}
}
/// A dummy future that represents "now".
#[must_use]
pub struct DummyFuture {
device: Arc<Device>,
}
impl DummyFuture {
/// Builds a new dummy future.
#[inline]
pub fn new(device: Arc<Device>) -> DummyFuture {
DummyFuture {
device: device,
}
}
}
unsafe impl GpuFuture for DummyFuture {
#[inline]
fn is_finished(&self) -> bool {
true
}
#[inline]
unsafe fn build_submission(&self) -> Result<SubmitAnyBuilder, Box<Error>> {
Ok(SubmitAnyBuilder::Empty)
}
#[inline]
fn flush(&self) -> Result<(), Box<Error>> {
Ok(())
}
#[inline]
unsafe fn signal_finished(&self) {
}
#[inline]
fn queue_change_allowed(&self) -> bool {
true
}
#[inline]
fn queue(&self) -> Option<&Arc<Queue>> {
None
}
#[inline]
fn check_buffer_access(&self, buffer: &Buffer, exclusive: bool, queue: &Queue) -> bool {
false
}
#[inline]
fn check_image_access(&self, image: &Image, exclusive: bool, queue: &Queue) -> bool {
false
}
}
unsafe impl DeviceOwned for DummyFuture {
#[inline]
fn device(&self) -> &Arc<Device> {
&self.device
}
}
/// Represents a semaphore being signaled after a previous event.
#[must_use = "Dropping this object will immediately block the thread until the GPU has finished processing the submission"]
pub struct SemaphoreSignalFuture<F> where F: GpuFuture {
previous: F,
semaphore: Semaphore,
// True if the signaling command 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`.
wait_submitted: Mutex<bool>,
finished: AtomicBool,
}
unsafe impl<F> GpuFuture for SemaphoreSignalFuture<F> where F: GpuFuture {
#[inline]
fn is_finished(&self) -> bool {
self.finished.load(Ordering::SeqCst)
}
#[inline]
unsafe fn build_submission(&self) -> Result<SubmitAnyBuilder, Box<Error>> {
// Flushing the signaling part, since it must always be submitted before the waiting part.
try!(self.flush());
let mut sem = SubmitSemaphoresWaitBuilder::new();
sem.add_wait_semaphore(&self.semaphore);
Ok(SubmitAnyBuilder::SemaphoresWait(sem))
}
fn flush(&self) -> Result<(), Box<Error>> {
unsafe {
let mut wait_submitted = self.wait_submitted.lock().unwrap();
if *wait_submitted {
return Ok(());
}
let queue = self.previous.queue().unwrap().clone();
match try!(self.previous.build_submission()) {
SubmitAnyBuilder::Empty => {
let mut builder = SubmitCommandBufferBuilder::new();
builder.add_signal_semaphore(&self.semaphore);
try!(builder.submit(&queue));
},
SubmitAnyBuilder::SemaphoresWait(sem) => {
let mut builder: SubmitCommandBufferBuilder = sem.into();
builder.add_signal_semaphore(&self.semaphore);
try!(builder.submit(&queue));
},
SubmitAnyBuilder::CommandBuffer(mut builder) => {
debug_assert_eq!(builder.num_signal_semaphores(), 0);
builder.add_signal_semaphore(&self.semaphore);
try!(builder.submit(&queue));
},
SubmitAnyBuilder::QueuePresent(present) => {
try!(present.submit(&queue));
let mut builder = SubmitCommandBufferBuilder::new();
builder.add_signal_semaphore(&self.semaphore);
try!(builder.submit(&queue)); // FIXME: problematic because if we return an error and flush() is called again, then we'll submit the present twice
},
};
// Only write `true` here in order to try again next time if an error occurs.
*wait_submitted = true;
Ok(())
}
}
#[inline]
unsafe fn signal_finished(&self) {
debug_assert!(*self.wait_submitted.lock().unwrap());
self.finished.store(true, Ordering::SeqCst);
self.previous.signal_finished();
}
#[inline]
fn queue_change_allowed(&self) -> bool {
true
}
#[inline]
fn queue(&self) -> Option<&Arc<Queue>> {
self.previous.queue()
}
#[inline]
fn check_buffer_access(&self, buffer: &Buffer, exclusive: bool, queue: &Queue) -> bool {
self.previous.check_buffer_access(buffer, exclusive, queue)
}
#[inline]
fn check_image_access(&self, image: &Image, exclusive: bool, queue: &Queue) -> bool {
self.previous.check_image_access(image, exclusive, queue)
}
}
unsafe impl<F> DeviceOwned for SemaphoreSignalFuture<F> where F: GpuFuture {
#[inline]
fn device(&self) -> &Arc<Device> {
self.semaphore.device()
}
}
impl<F> Drop for SemaphoreSignalFuture<F> where F: GpuFuture {
fn drop(&mut self) {
unsafe {
if !*self.finished.get_mut() {
// TODO: handle errors?
self.flush().unwrap();
// Block until the queue finished.
self.queue().unwrap().wait().unwrap();
self.previous.signal_finished();
}
}
}
}
/// Represents a fence being signaled after a previous event.
#[must_use = "Dropping this object will immediately block the thread until the GPU has finished processing the submission"]
pub struct FenceSignalFuture<F> where F: GpuFuture {
previous: F,
fence: Fence,
// True if the signaling command 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`.
flushed: Mutex<bool>,
}
impl<F> FenceSignalFuture<F> where F: GpuFuture {
/// Waits until the fence is signaled, or at least until the number of nanoseconds of the
/// timeout has elapsed.
pub fn wait(&self, timeout: Duration) -> Result<(), FenceWaitError> {
// FIXME: flush?
self.fence.wait(timeout)
}
}
unsafe impl<F> GpuFuture for FenceSignalFuture<F> where F: GpuFuture {
#[inline]
fn is_finished(&self) -> bool {
if !*self.flushed.lock().unwrap() {
return false;
}
self.fence.wait(Duration::from_secs(0)).is_ok()
}
#[inline]
unsafe fn build_submission(&self) -> Result<SubmitAnyBuilder, Box<Error>> {
try!(self.flush());
self.fence.wait(Duration::from_secs(600)).unwrap(); // TODO: handle errors
Ok(SubmitAnyBuilder::Empty)
}
#[inline]
fn flush(&self) -> Result<(), Box<Error>> {
unsafe {
let mut flushed = self.flushed.lock().unwrap();
if *flushed {
return Ok(());
}
let queue = self.previous.queue().unwrap().clone();
match try!(self.previous.build_submission()) {
SubmitAnyBuilder::Empty => {
let mut b = SubmitCommandBufferBuilder::new();
b.set_fence_signal(&self.fence);
try!(b.submit(&queue));
},
SubmitAnyBuilder::SemaphoresWait(sem) => {
let b: SubmitCommandBufferBuilder = sem.into();
debug_assert!(!b.has_fence());
try!(b.submit(&queue));
},
SubmitAnyBuilder::CommandBuffer(mut cb_builder) => {
debug_assert!(!cb_builder.has_fence());
cb_builder.set_fence_signal(&self.fence);
try!(cb_builder.submit(&queue));
},
SubmitAnyBuilder::QueuePresent(present) => {
try!(present.submit(&queue));
let mut b = SubmitCommandBufferBuilder::new();
b.set_fence_signal(&self.fence);
try!(b.submit(&queue)); // FIXME: problematic because if we return an error and flush() is called again, then we'll submit the present twice
},
};
// Only write `true` here in order to try again next time if an error occurs.
*flushed = true;
Ok(())
}
}
#[inline]
unsafe fn signal_finished(&self) {
debug_assert!(*self.flushed.lock().unwrap());
self.previous.signal_finished();
}
#[inline]
fn queue_change_allowed(&self) -> bool {
true
}
#[inline]
fn queue(&self) -> Option<&Arc<Queue>> {
self.previous.queue()
}
#[inline]
fn check_buffer_access(&self, buffer: &Buffer, exclusive: bool, queue: &Queue) -> bool {
self.previous.check_buffer_access(buffer, exclusive, queue)
}
#[inline]
fn check_image_access(&self, image: &Image, exclusive: bool, queue: &Queue) -> bool {
self.previous.check_image_access(image, exclusive, queue)
}
}
unsafe impl<F> DeviceOwned for FenceSignalFuture<F> where F: GpuFuture {
#[inline]
fn device(&self) -> &Arc<Device> {
self.fence.device()
}
}
impl<F> Drop for FenceSignalFuture<F> where F: GpuFuture {
fn drop(&mut self) {
self.flush().unwrap(); // TODO: handle error?
self.fence.wait(Duration::from_secs(600)).unwrap(); // TODO: handle some errors
unsafe {
self.previous.signal_finished();
}
}
}
/// Two futures joined into one.
#[must_use]
pub struct JoinFuture<A, B> {
first: A,
second: B,
}
unsafe impl<A, B> DeviceOwned for JoinFuture<A, B> where A: DeviceOwned, B: DeviceOwned {
#[inline]
fn device(&self) -> &Arc<Device> {
let device = self.first.device();
debug_assert_eq!(self.second.device().internal_object(), device.internal_object());
device
}
}
unsafe impl<A, B> GpuFuture for JoinFuture<A, B> where A: GpuFuture, B: GpuFuture {
#[inline]
fn is_finished(&self) -> bool {
self.first.is_finished() && self.second.is_finished()
}
#[inline]
fn flush(&self) -> Result<(), Box<Error>> {
// Since each future remembers whether it has been flushed, there's no safety issue here
// if we call this function multiple times.
try!(self.first.flush());
try!(self.second.flush());
Ok(())
}
#[inline]
unsafe fn build_submission(&self) -> Result<SubmitAnyBuilder, Box<Error>> {
let first = try!(self.first.build_submission());
let second = try!(self.second.build_submission());
Ok(match (first, second) {
(SubmitAnyBuilder::Empty, b) => b,
(a, SubmitAnyBuilder::Empty) => a,
(SubmitAnyBuilder::SemaphoresWait(mut a), SubmitAnyBuilder::SemaphoresWait(b)) => {
a.merge(b);
SubmitAnyBuilder::SemaphoresWait(a)
},
_ => unimplemented!()
})
}
#[inline]
unsafe fn signal_finished(&self) {
self.first.signal_finished();
self.second.signal_finished();
}
#[inline]
fn queue_change_allowed(&self) -> bool {
self.first.queue_change_allowed() && self.second.queue_change_allowed()
}
#[inline]
fn queue(&self) -> Option<&Arc<Queue>> {
match (self.first.queue(), self.second.queue()) {
(Some(q1), Some(q2)) => if q1.is_same(&q2) {
Some(q1)
} else if self.first.queue_change_allowed() {
Some(q2)
} else if self.second.queue_change_allowed() {
Some(q1)
} else {
None
},
(Some(q), None) => Some(q),
(None, Some(q)) => Some(q),
(None, None) => None,
}
}
#[inline]
fn check_buffer_access(&self, buffer: &Buffer, exclusive: bool, queue: &Queue) -> bool {
let first = self.first.check_buffer_access(buffer, exclusive, queue);
let second = self.second.check_buffer_access(buffer, exclusive, queue);
debug_assert!(!exclusive || !(first && second), "Two futures gave exclusive access to the \
same resource");
first || second
}
#[inline]
fn check_image_access(&self, image: &Image, exclusive: bool, queue: &Queue) -> bool {
let first = self.first.check_image_access(image, exclusive, queue);
let second = self.second.check_image_access(image, exclusive, queue);
debug_assert!(!exclusive || !(first && second), "Two futures gave exclusive access to the \
same resource");
first || second
}
}

View File

@ -27,10 +27,16 @@ use vk;
pub use self::event::Event;
pub use self::fence::Fence;
pub use self::fence::FenceWaitError;
pub use self::future::DummyFuture;
pub use self::future::GpuFuture;
pub use self::future::SemaphoreSignalFuture;
pub use self::future::FenceSignalFuture;
pub use self::future::JoinFuture;
pub use self::semaphore::Semaphore;
mod event;
mod fence;
mod future;
mod semaphore;
/// Base trait for objects that can be used as resources and must be synchronized.

View File

@ -31,9 +31,9 @@ pub struct Semaphore<D = Arc<Device>> where D: SafeDeref<Target = Device> {
}
impl<D> Semaphore<D> where D: SafeDeref<Target = Device> {
/// See the docs of new().
/// Builds a new semaphore.
#[inline]
pub fn raw(device: D) -> Result<Semaphore<D>, OomError> {
pub fn new(device: D) -> Result<Semaphore<D>, OomError> {
let semaphore = unsafe {
// since the creation is constant, we use a `static` instead of a struct on the stack
static mut INFOS: vk::SemaphoreCreateInfo = vk::SemaphoreCreateInfo {
@ -54,17 +54,6 @@ impl<D> Semaphore<D> where D: SafeDeref<Target = Device> {
semaphore: semaphore,
})
}
/// Builds a new semaphore.
///
/// # Panic
///
/// - Panics if the device or host ran out of memory.
///
#[inline]
pub fn new(device: D) -> Arc<Semaphore<D>> {
Arc::new(Semaphore::raw(device).unwrap())
}
}
unsafe impl DeviceOwned for Semaphore {