diff --git a/examples/src/bin/triangle.rs b/examples/src/bin/triangle.rs index 4fd203dc..e11690fa 100644 --- a/examples/src/bin/triangle.rs +++ b/examples/src/bin/triangle.rs @@ -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 = Vec::new(); + let mut submissions: Vec> = 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 diff --git a/vulkano/src/buffer/cpu_access.rs b/vulkano/src/buffer/cpu_access.rs index 041c2c69..b1cfa5ae 100644 --- a/vulkano/src/buffer/cpu_access.rs +++ b/vulkano/src/buffer/cpu_access.rs @@ -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> 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, - // Necessary to make it compile. marker: PhantomData>, } -#[derive(Debug)] -struct LatestSubmission { - read_submissions: Mutex>>, - write_submission: Option>, // TODO: can use `Weak::new()` once it's stabilized -} - impl CpuAccessibleBuffer { /// Deprecated. Use `from_data` instead. #[deprecated] @@ -108,7 +103,7 @@ impl CpuAccessibleBuffer { // TODO: check whether that's true ^ { - let mut mapping = uninitialized.write(Duration::new(0, 0)).unwrap(); + let mut mapping = uninitialized.write().unwrap(); ptr::write::(&mut *mapping, data) } @@ -145,7 +140,7 @@ impl 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 CpuAccessibleBuffer { 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 CpuAccessibleBuffer 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, 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, TryLockError>> { + 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 CpuAccessibleBuffer 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, 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, TryLockError>> { + 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 Buffer for CpuAccessibleBuffer self.inner.key() } - /*fn transition(&self, states: &mut StatesManager, num_command: usize, _: usize, _: usize, - write: bool, stage: PipelineStages, access: AccessFlagBits) - -> Option - { - 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 - { - 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 TypedBuffer for CpuAccessibleBuffer @@ -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> { diff --git a/vulkano/src/buffer/device_local.rs b/vulkano/src/buffer/device_local.rs index a497b4af..d63d8bcc 100644 --- a/vulkano/src/buffer/device_local.rs +++ b/vulkano/src/buffer/device_local.rs @@ -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> 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, - // Necessary to make it compile. marker: PhantomData>, } -#[derive(Debug)] -struct LatestSubmission { - read_submissions: SmallVec<[Weak; 4]>, - write_submission: Option>, // TODO: can use `Weak::new()` once it's stabilized -} - impl DeviceLocalBuffer { /// Builds a new buffer. Only allowed for sized data. #[inline] @@ -137,10 +127,6 @@ impl DeviceLocalBuffer { 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 Buffer for DeviceLocalBuffer offset: 0, } } + + #[inline] + fn gpu_access(&self, exclusive_access: bool, queue: &Queue) -> bool { + false // FIXME: + } } unsafe impl TypedBuffer for DeviceLocalBuffer diff --git a/vulkano/src/buffer/immutable.rs b/vulkano/src/buffer/immutable.rs index ca336075..ef8804b4 100644 --- a/vulkano/src/buffer/immutable.rs +++ b/vulkano/src/buffer/immutable.rs @@ -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> where A: MemoryPoo // Queue families allowed to access this buffer. queue_families: SmallVec<[u32; 4]>, - latest_write_submission: Mutex>>, // TODO: can use `Weak::new()` once it's stabilized - started_reading: AtomicBool, marker: PhantomData>, @@ -134,7 +132,6 @@ impl ImmutableBuffer { 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 Buffer for ImmutableBuffer offset: 0, } } + + #[inline] + fn gpu_access(&self, exclusive_access: bool, queue: &Queue) -> bool { + false // FIXME: + } } unsafe impl TypedBuffer for ImmutableBuffer diff --git a/vulkano/src/buffer/slice.rs b/vulkano/src/buffer/slice.rs index abcc62e3..e3eaff70 100644 --- a/vulkano/src/buffer/slice.rs +++ b/vulkano/src/buffer/slice.rs @@ -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 Buffer for BufferSlice 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 TypedBuffer for BufferSlice where B: Buffer, T: 'static { diff --git a/vulkano/src/buffer/traits.rs b/vulkano/src/buffer/traits.rs index 9a9f8530..8a9232de 100644 --- a/vulkano/src/buffer/traits.rs +++ b/vulkano/src/buffer/traits.rs @@ -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 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 { diff --git a/vulkano/src/command_buffer/auto.rs b/vulkano/src/command_buffer/auto.rs index 8df84359..995e028b 100644 --- a/vulkano/src/command_buffer/auto.rs +++ b/vulkano/src/command_buffer/auto.rs @@ -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 CommandBufferBuild for AutoCommandBufferBuilder } } -unsafe impl Submit for AutoCommandBufferBuilder - where Cb: Submit, +unsafe impl CommandBuffer for AutoCommandBufferBuilder + where Cb: CommandBuffer, P: CommandPool { + type Pool = as CommandBuffer>::Pool; + #[inline] - unsafe fn append_submission<'a>(&'a self, base: SubmitBuilder<'a>, queue: &Arc) - -> Result, Box> - { - self.inner.append_submission(base, queue) + fn inner(&self) -> &UnsafeCommandBuffer { + self.inner.inner() } } diff --git a/vulkano/src/command_buffer/cb/buffered.rs b/vulkano/src/command_buffer/cb/buffered.rs index 3b77a6ad..1c7088f3 100644 --- a/vulkano/src/command_buffer/cb/buffered.rs +++ b/vulkano/src/command_buffer/cb/buffered.rs @@ -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 CommandBufferBuild for BufferedCommandsListLayer } } -unsafe impl Submit for BufferedCommandsListLayer where I: Submit { +unsafe impl CommandBuffer for BufferedCommandsListLayer where I: CommandBuffer { + type Pool = I::Pool; + #[inline] - unsafe fn append_submission<'a>(&'a self, base: SubmitBuilder<'a>, queue: &Arc) - -> Result, Box> - { - self.inner.as_ref().unwrap().append_submission(base, queue) + fn inner(&self) -> &UnsafeCommandBuffer { + self.inner.as_ref().unwrap().inner() } } diff --git a/vulkano/src/command_buffer/cb/commands_list.rs b/vulkano/src/command_buffer/cb/commands_list.rs index 22c5dd34..2d6f93bd 100644 --- a/vulkano/src/command_buffer/cb/commands_list.rs +++ b/vulkano/src/command_buffer/cb/commands_list.rs @@ -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 CommandsListLayer { // TODO: implement CommandBufferBuild -unsafe impl Submit for CommandsListLayer where I: Submit { +unsafe impl CommandBuffer for CommandsListLayer where I: CommandBuffer { + type Pool = I::Pool; + #[inline] - unsafe fn append_submission<'a>(&'a self, base: SubmitBuilder<'a>, queue: &Arc) - -> Result, Box> - { - self.inner.append_submission(base, queue) + fn inner(&self) -> &UnsafeCommandBuffer { + self.inner.inner() } } diff --git a/vulkano/src/command_buffer/cb/mod.rs b/vulkano/src/command_buffer/cb/mod.rs index 3628765d..9fa90d85 100644 --- a/vulkano/src/command_buffer/cb/mod.rs +++ b/vulkano/src/command_buffer/cb/mod.rs @@ -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; diff --git a/vulkano/src/command_buffer/cb/submit_sync.rs b/vulkano/src/command_buffer/cb/submit_sync.rs index 381eb402..eadb9ad3 100644 --- a/vulkano/src/command_buffer/cb/submit_sync.rs +++ b/vulkano/src/command_buffer/cb/submit_sync.rs @@ -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 { inner: I, } -unsafe impl Submit for SubmitSyncLayer where I: Submit { +unsafe impl CommandBuffer for SubmitSyncLayer where I: CommandBuffer { + type Pool = I::Pool; + #[inline] - unsafe fn append_submission<'a>(&'a self, base: SubmitBuilder<'a>, queue: &Arc) - -> Result, Box> - { - // FIXME: - self.inner.append_submission(base, queue) + fn inner(&self) -> &UnsafeCommandBuffer { + self.inner.inner() } } diff --git a/vulkano/src/command_buffer/cb/sys.rs b/vulkano/src/command_buffer/cb/sys.rs index 244caad5..494eb6ca 100644 --- a/vulkano/src/command_buffer/cb/sys.rs +++ b/vulkano/src/command_buffer/cb/sys.rs @@ -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

where P: CommandPool { already_submitted: AtomicBool, } -unsafe impl

Submit for UnsafeCommandBuffer

where P: CommandPool { +unsafe impl

CommandBuffer for UnsafeCommandBuffer

where P: CommandPool { + type Pool = P; + #[inline] - unsafe fn append_submission<'a>(&'a self, base: SubmitBuilder<'a>, _queue: &Arc) - -> Result, Box> - { - Ok(base.add_command_buffer(self)) + fn inner(&self) -> &UnsafeCommandBuffer

{ + self } } diff --git a/vulkano/src/command_buffer/mod.rs b/vulkano/src/command_buffer/mod.rs index ecef656c..12628d8a 100644 --- a/vulkano/src/command_buffer/mod.rs +++ b/vulkano/src/command_buffer/mod.rs @@ -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)] diff --git a/vulkano/src/command_buffer/submit.rs b/vulkano/src/command_buffer/submit.rs deleted file mode 100644 index 822a6306..00000000 --- a/vulkano/src/command_buffer/submit.rs +++ /dev/null @@ -1,533 +0,0 @@ -// Copyright (c) 2016 The vulkano developers -// Licensed under the Apache License, Version 2.0 -// or the MIT -// license , -// 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 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) -> Result> - 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) -> Result, Box> - 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(self, other: S) -> SubmitChain 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) - -> Result, Box>; -} - -unsafe impl Submit for T where T: Deref, T::Target: Submit { - #[inline] - unsafe fn append_submission<'a>(&'a self, base: SubmitBuilder<'a>, queue: &Arc) - -> Result, Box> - { - (**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; 8]>, - keep_alive_fences: SmallVec<[Arc; 2]>, - marker: PhantomData<&'a ()>, -} - -#[derive(Default)] -struct SubmitBuilderSubmit { - batches: SmallVec<[vk::SubmitInfo; 4]>, - fence: Option>, -} - -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) -> 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, 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

(self, command_buffer: &'a UnsafeCommandBuffer

) - -> SubmitBuilder<'a> - where P: CommandPool - { - self.add_command_buffer_raw(command_buffer.internal_object()) - } - - // TODO: remove in favor of `add_command_buffer`? - #[inline] - pub fn add_command_buffer_raw(mut self, command_buffer: vk::CommandBuffer) - -> SubmitBuilder<'a> - { - if self.submits.last().map(|b| b.fence.is_some()).unwrap_or(true) { - self.submits.push(Default::default()); - } - - { - 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) -> 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(submit: S, queue: &Arc) -> Result, Box> - 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 { - first: A, - second: B, -} - -unsafe impl Submit for SubmitChain where A: Submit, B: Submit { - #[inline] - unsafe fn append_submission<'a>(&'a self, base: SubmitBuilder<'a>, queue: &Arc) - -> Result, Box> - { - // 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 DeviceOwned for SubmitChain where A: DeviceOwned, B: DeviceOwned { - #[inline] - fn device(&self) -> &Arc { - 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> { - fence: FenceWithWaiting, // TODO: make optional - queue: Arc, - keep_alive_semaphores: SmallVec<[Arc; 8]>, - keep_alive_fences: SmallVec<[Arc; 2]>, - submit: S, -} - -struct FenceWithWaiting(Arc); -impl Drop for FenceWithWaiting { - fn drop(&mut self) { - self.0.wait(Duration::from_secs(10)).unwrap(); // TODO: handle some errors - } -} - -impl fmt::Debug for Submission { - #[inline] - fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { - // TODO: better impl? - write!(fmt, "") - } -} - -impl Submission { - /// 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 { - &self.queue - } -} - -impl Submission 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> } - unsafe impl CommandBuffer for Basic { - type Pool = Arc; - - fn inner(&self) -> &UnsafeCommandBuffer { &self.inner } - - unsafe fn on_submit(&self, _: &Arc, fence: F) - -> SubmitInfo - where F: FnOnce() -> Arc - { - SubmitInfo::empty() - } - } - - let (device, queue) = gfx_dev_and_queue!(); - - let pool = Device::standard_command_pool(&device, queue.family()); - let kind = Kind::Primary::>; - - let cb = UnsafeCommandBufferBuilder::new(pool, kind, Flags::OneTimeSubmit).unwrap(); - let cb = Basic { inner: cb.build().unwrap() }; - - let _s = cb.submit(&queue); - } -}*/ diff --git a/vulkano/src/command_buffer/submit/mod.rs b/vulkano/src/command_buffer/submit/mod.rs new file mode 100644 index 00000000..10503e96 --- /dev/null +++ b/vulkano/src/command_buffer/submit/mod.rs @@ -0,0 +1,40 @@ +// Copyright (c) 2016 The vulkano developers +// Licensed under the Apache License, Version 2.0 +// or the MIT +// license , +// 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, + } + } +} diff --git a/vulkano/src/command_buffer/submit/queue_present.rs b/vulkano/src/command_buffer/submit/queue_present.rs new file mode 100644 index 00000000..1e9edc28 --- /dev/null +++ b/vulkano/src/command_buffer/submit/queue_present.rs @@ -0,0 +1,176 @@ +// Copyright (c) 2017 The vulkano developers +// Licensed under the Apache License, Version 2.0 +// or the MIT +// license , +// 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 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); + } +} diff --git a/vulkano/src/command_buffer/submit/queue_submit.rs b/vulkano/src/command_buffer/submit/queue_submit.rs new file mode 100644 index 00000000..cfa9bc96 --- /dev/null +++ b/vulkano/src/command_buffer/submit/queue_submit.rs @@ -0,0 +1,175 @@ +// Copyright (c) 2017 The vulkano developers +// Licensed under the Apache License, Version 2.0 +// or the MIT +// license , +// 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::::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

(&mut self, command_buffer: &'a UnsafeCommandBuffer

) + 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 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) + } + } +} diff --git a/vulkano/src/command_buffer/submit/semaphores_wait.rs b/vulkano/src/command_buffer/submit/semaphores_wait.rs new file mode 100644 index 00000000..94e90826 --- /dev/null +++ b/vulkano/src/command_buffer/submit/semaphores_wait.rs @@ -0,0 +1,81 @@ +// Copyright (c) 2016 The vulkano developers +// Licensed under the Apache License, Version 2.0 +// or the MIT +// license , +// 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> 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> 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 + } + } +} diff --git a/vulkano/src/command_buffer/traits.rs b/vulkano/src/command_buffer/traits.rs new file mode 100644 index 00000000..1b5a13fc --- /dev/null +++ b/vulkano/src/command_buffer/traits.rs @@ -0,0 +1,214 @@ +// Copyright (c) 2016 The vulkano developers +// Licensed under the Apache License, Version 2.0 +// or the MIT +// license , +// 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; + + /// 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) -> CommandBufferExecFuture + 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(self, future: F, queue: Arc) -> CommandBufferExecFuture + 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 CommandBuffer for T where T: SafeDeref, T::Target: CommandBuffer { + type Pool = ::Pool; + + #[inline] + fn inner(&self) -> &UnsafeCommandBuffer { + (**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 where F: GpuFuture, Cb: CommandBuffer { + previous: F, + command_buffer: Cb, + queue: Arc, + // 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` and not an `AtomicBool`. + submitted: Mutex, + finished: AtomicBool, +} + +unsafe impl GpuFuture for CommandBufferExecFuture + where F: GpuFuture, Cb: CommandBuffer +{ + #[inline] + fn is_finished(&self) -> bool { + self.finished.load(Ordering::SeqCst) + } + + unsafe fn build_submission(&self) -> Result> { + 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> { + 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> { + 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 DeviceOwned for CommandBufferExecFuture + where F: GpuFuture, Cb: CommandBuffer +{ + #[inline] + fn device(&self) -> &Arc { + self.command_buffer.device() + } +} + +impl Drop for CommandBufferExecFuture 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(); + } + } + } +} diff --git a/vulkano/src/device.rs b/vulkano/src/device.rs index c2255986..54884ebe 100644 --- a/vulkano/src/device.rs +++ b/vulkano/src/device.rs @@ -553,7 +553,7 @@ impl From 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, @@ -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 { diff --git a/vulkano/src/image/attachment.rs b/vulkano/src/image/attachment.rs index cbf59a2c..1a891f97 100644 --- a/vulkano/src/image/attachment.rs +++ b/vulkano/src/image/attachment.rs @@ -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> 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, -} - -#[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>, // TODO: can use `Weak::new()` once it's stabilized } impl AttachmentImage { @@ -182,10 +168,6 @@ impl AttachmentImage { 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 Image for AttachmentImage 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 ImageClearValue for AttachmentImage diff --git a/vulkano/src/image/immutable.rs b/vulkano/src/image/immutable.rs index 32f0b7da..66f52c32 100644 --- a/vulkano/src/image/immutable.rs +++ b/vulkano/src/image/immutable.rs @@ -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> where A: MemoryPool { dimensions: Dimensions, memory: A::Alloc, format: F, - per_layer: SmallVec<[PerLayer; 1]>, -} - -#[derive(Debug)] -struct PerLayer { - latest_write_submission: Mutex>>, // TODO: can use `Weak::new()` once it's stabilized - started_reading: AtomicBool, } impl ImmutableImage { @@ -104,16 +94,6 @@ impl ImmutableImage { 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 Image for ImmutableImage 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 ImageContent

for ImmutableImage diff --git a/vulkano/src/image/storage.rs b/vulkano/src/image/storage.rs index 2154e7b9..89e36688 100644 --- a/vulkano/src/image/storage.rs +++ b/vulkano/src/image/storage.rs @@ -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> where A: MemoryPool { // Queue families allowed to access this image. queue_families: SmallVec<[u32; 4]>, - - // Additional info behind a mutex. - guarded: Mutex, -} - -#[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; 4]>, - - // The latest submission that writes to this image. - write_submission: Option>, // TODO: can use `Weak::new()` once it's stabilized } impl StorageImage { @@ -139,11 +122,6 @@ impl StorageImage { 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 Image for StorageImage 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 ImageClearValue for StorageImage diff --git a/vulkano/src/image/swapchain.rs b/vulkano/src/image/swapchain.rs index 52a22fcf..cb1d7357 100644 --- a/vulkano/src/image/swapchain.rs +++ b/vulkano/src/image/swapchain.rs @@ -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, id: u32, - guarded: Mutex, -} - -#[derive(Debug)] -struct Guarded { - present_layout: bool, - latest_submission: Option>, // 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<::ClearValue> for SwapchainImage diff --git a/vulkano/src/image/traits.rs b/vulkano/src/image/traits.rs index d38155bf..dcdbc7de 100644 --- a/vulkano/src/image/traits.rs +++ b/vulkano/src/image/traits.rs @@ -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 Image for T where T: SafeDeref, T::Target: Image { @@ -149,6 +160,11 @@ unsafe impl 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 diff --git a/vulkano/src/swapchain/mod.rs b/vulkano/src/swapchain/mod.rs index 494c0a6a..f9075bcd 100644 --- a/vulkano/src/swapchain/mod.rs +++ b/vulkano/src/swapchain/mod.rs @@ -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; diff --git a/vulkano/src/swapchain/swapchain.rs b/vulkano/src/swapchain/swapchain.rs index a1ae8b4c..5ec97403 100644 --- a/vulkano/src/swapchain/swapchain.rs +++ b/vulkano/src/swapchain/swapchain.rs @@ -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, 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>>, - - images_semaphores: Mutex>>>, - // 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>>, } 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::>(); - { - 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 { + // 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, index: usize) -> Result<(), PresentError> { - let vk = self.device.pointers(); + // TODO: use another API, since taking by Arc is meh + pub fn present(me: Arc, before: F, queue: Arc, index: usize) + -> PresentFuture + 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> { - 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) -> Option> { - 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, + 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> { + let mut sem = SubmitSemaphoresWaitBuilder::new(); + sem.add_wait_semaphore(&self.semaphore); + Ok(SubmitAnyBuilder::SemaphoresWait(sem)) + } + + #[inline] + fn flush(&self) -> Result<(), Box> { + 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> { + 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 { + 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 for AcquireError { + #[inline] + fn from(err: OomError) -> AcquireError { + AcquireError::OomError(err) + } +} + impl From for AcquireError { #[inline] fn from(err: Error) -> AcquireError { @@ -508,61 +577,110 @@ impl From 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

where P: GpuFuture { + previous: P, + queue: Arc, + swapchain: Arc, + image_id: u32, + finished: AtomicBool, } -impl error::Error for PresentError { +unsafe impl

GpuFuture for PresentFuture

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 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> { + 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> { + 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> { + 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

DeviceOwned for PresentFuture

where P: GpuFuture { + #[inline] + fn device(&self) -> &Arc { + self.queue.device() + } +} + +impl

Drop for PresentFuture

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(); + } } } } diff --git a/vulkano/src/sync/fence.rs b/vulkano/src/sync/fence.rs index 5874cd34..a1d445ae 100644 --- a/vulkano/src/sync/fence.rs +++ b/vulkano/src/sync/fence.rs @@ -46,40 +46,18 @@ pub struct Fence> where D: SafeDeref { } impl Fence where D: SafeDeref { - /// See the docs of new(). - #[inline] - pub fn raw(device: D) -> Result, 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> { - Arc::new(Fence::raw(device).unwrap()) + pub fn new(device: D) -> Result, OomError> { + Fence::new_impl(device, false) } /// See the docs of signaled(). #[inline] - pub fn signaled_raw(device: D) -> Result, OomError> { + pub fn signaled(device: D) -> Result, 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> { - Arc::new(Fence::signaled_raw(device).unwrap()) - } - fn new_impl(device: D, signaled: bool) -> Result, OomError> { let fence = unsafe { let infos = vk::FenceCreateInfo { @@ -317,7 +295,6 @@ impl From 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))); } } diff --git a/vulkano/src/sync/future.rs b/vulkano/src/sync/future.rs new file mode 100644 index 00000000..188e84a1 --- /dev/null +++ b/vulkano/src/sync/future.rs @@ -0,0 +1,611 @@ +// Copyright (c) 2016 The vulkano developers +// Licensed under the Apache License, Version 2.0 +// or the MIT +// license , +// 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>; + + /// 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>; + + /// 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>; + + /// 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(self, other: F) -> JoinFuture + 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(self, queue: Arc, command_buffer: Cb) + -> CommandBufferExecFuture + 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(self, command_buffer: Cb) -> CommandBufferExecFuture + 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 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 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, swapchain: Arc, + image_index: usize) -> PresentFuture + where Self: Sized + { + Swapchain::present(swapchain, self, queue, image_index) + } +} + +unsafe impl 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> { + (**self).build_submission() + } + + #[inline] + fn flush(&self) -> Result<(), Box> { + (**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> { + (**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, +} + +impl DummyFuture { + /// Builds a new dummy future. + #[inline] + pub fn new(device: Arc) -> DummyFuture { + DummyFuture { + device: device, + } + } +} + +unsafe impl GpuFuture for DummyFuture { + #[inline] + fn is_finished(&self) -> bool { + true + } + + #[inline] + unsafe fn build_submission(&self) -> Result> { + Ok(SubmitAnyBuilder::Empty) + } + + #[inline] + fn flush(&self) -> Result<(), Box> { + Ok(()) + } + + #[inline] + unsafe fn signal_finished(&self) { + } + + #[inline] + fn queue_change_allowed(&self) -> bool { + true + } + + #[inline] + fn queue(&self) -> Option<&Arc> { + 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 { + &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 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` and not an `AtomicBool`. + wait_submitted: Mutex, + finished: AtomicBool, +} + +unsafe impl GpuFuture for SemaphoreSignalFuture where F: GpuFuture { + #[inline] + fn is_finished(&self) -> bool { + self.finished.load(Ordering::SeqCst) + } + + #[inline] + unsafe fn build_submission(&self) -> Result> { + // 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> { + 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> { + 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 DeviceOwned for SemaphoreSignalFuture where F: GpuFuture { + #[inline] + fn device(&self) -> &Arc { + self.semaphore.device() + } +} + +impl Drop for SemaphoreSignalFuture 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 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` and not an `AtomicBool`. + flushed: Mutex, +} + +impl FenceSignalFuture 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 GpuFuture for FenceSignalFuture 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> { + try!(self.flush()); + self.fence.wait(Duration::from_secs(600)).unwrap(); // TODO: handle errors + Ok(SubmitAnyBuilder::Empty) + } + + #[inline] + fn flush(&self) -> Result<(), Box> { + 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> { + 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 DeviceOwned for FenceSignalFuture where F: GpuFuture { + #[inline] + fn device(&self) -> &Arc { + self.fence.device() + } +} + +impl Drop for FenceSignalFuture 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 { + first: A, + second: B, +} + +unsafe impl DeviceOwned for JoinFuture where A: DeviceOwned, B: DeviceOwned { + #[inline] + fn device(&self) -> &Arc { + let device = self.first.device(); + debug_assert_eq!(self.second.device().internal_object(), device.internal_object()); + device + } +} + +unsafe impl GpuFuture for JoinFuture 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> { + // 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> { + 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> { + 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 + } +} diff --git a/vulkano/src/sync/mod.rs b/vulkano/src/sync/mod.rs index 1dba8c14..85ab2f63 100644 --- a/vulkano/src/sync/mod.rs +++ b/vulkano/src/sync/mod.rs @@ -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. diff --git a/vulkano/src/sync/semaphore.rs b/vulkano/src/sync/semaphore.rs index 059b777f..a39425ab 100644 --- a/vulkano/src/sync/semaphore.rs +++ b/vulkano/src/sync/semaphore.rs @@ -31,9 +31,9 @@ pub struct Semaphore> where D: SafeDeref { } impl Semaphore where D: SafeDeref { - /// See the docs of new(). + /// Builds a new semaphore. #[inline] - pub fn raw(device: D) -> Result, OomError> { + pub fn new(device: D) -> Result, 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 Semaphore where D: SafeDeref { semaphore: semaphore, }) } - - /// Builds a new semaphore. - /// - /// # Panic - /// - /// - Panics if the device or host ran out of memory. - /// - #[inline] - pub fn new(device: D) -> Arc> { - Arc::new(Semaphore::raw(device).unwrap()) - } } unsafe impl DeviceOwned for Semaphore {