aboutsummaryrefslogtreecommitdiff
path: root/src/sync/future/fence_signal.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/sync/future/fence_signal.rs')
-rw-r--r--src/sync/future/fence_signal.rs358
1 files changed, 231 insertions, 127 deletions
diff --git a/src/sync/future/fence_signal.rs b/src/sync/future/fence_signal.rs
index c2f5338..7f71d88 100644
--- a/src/sync/future/fence_signal.rs
+++ b/src/sync/future/fence_signal.rs
@@ -7,29 +7,33 @@
// notice may not be copied, modified, or distributed except
// according to those terms.
-use std::mem;
-use std::sync::Arc;
-use std::sync::Mutex;
-use std::sync::MutexGuard;
-use std::time::Duration;
-
-use crate::buffer::BufferAccess;
-use crate::command_buffer::submit::SubmitAnyBuilder;
-use crate::command_buffer::submit::SubmitCommandBufferBuilder;
-use crate::device::Device;
-use crate::device::DeviceOwned;
-use crate::device::Queue;
-use crate::image::ImageAccess;
-use crate::image::ImageLayout;
-use crate::sync::AccessCheckError;
-use crate::sync::AccessFlags;
-use crate::sync::Fence;
-use crate::sync::FlushError;
-use crate::sync::GpuFuture;
-use crate::sync::PipelineStages;
+use super::{AccessCheckError, FlushError, GpuFuture};
+use crate::{
+ buffer::Buffer,
+ command_buffer::{SemaphoreSubmitInfo, SubmitInfo},
+ device::{Device, DeviceOwned, Queue, QueueFlags},
+ image::{sys::Image, ImageLayout},
+ swapchain::Swapchain,
+ sync::{
+ fence::Fence,
+ future::{AccessError, SubmitAnyBuilder},
+ PipelineStages,
+ },
+ DeviceSize, OomError,
+};
+use parking_lot::{Mutex, MutexGuard};
+use std::{
+ future::Future,
+ mem::replace,
+ ops::Range,
+ pin::Pin,
+ sync::Arc,
+ task::{Context, Poll},
+ thread,
+ time::Duration,
+};
/// Builds a new fence signal future.
-#[inline]
pub fn then_signal_fence<F>(future: F, behavior: FenceSignalFutureBehavior) -> FenceSignalFuture<F>
where
F: GpuFuture,
@@ -38,11 +42,11 @@ where
assert!(future.queue().is_some()); // TODO: document
- let fence = Fence::from_pool(device.clone()).unwrap();
+ let fence = Arc::new(Fence::from_pool(device.clone()).unwrap());
FenceSignalFuture {
- device: device,
+ device,
state: Mutex::new(FenceSignalFutureState::Pending(future, fence)),
- behavior: behavior,
+ behavior,
}
}
@@ -52,6 +56,7 @@ pub enum FenceSignalFutureBehavior {
/// Continue execution on the same queue.
Continue,
/// Wait for the fence to be signalled before submitting any further operation.
+ #[allow(dead_code)] // TODO: why is this never constructed?
Block {
/// How long to block the current thread.
timeout: Option<Duration>,
@@ -63,6 +68,11 @@ pub enum FenceSignalFutureBehavior {
/// Contrary to most other future types, it is possible to block the current thread until the event
/// happens. This is done by calling the `wait()` function.
///
+/// This can also be done through Rust's Async system by simply `.await`ing this object. Note though
+/// that (due to the Vulkan API fence design) this will spin to check the fence, rather than
+/// blocking in the driver. Therefore if you have a long-running task, blocking may be less
+/// CPU intense (depending on the driver's implementation).
+///
/// Also note that the `GpuFuture` trait is implemented on `Arc<FenceSignalFuture<_>>`.
/// This means that you can put this future in an `Arc` and keep a copy of it somewhere in order
/// to know when the execution reached that point.
@@ -71,7 +81,7 @@ pub enum FenceSignalFutureBehavior {
/// use std::sync::Arc;
/// use vulkano::sync::GpuFuture;
///
-/// # let future: Box<GpuFuture> = return;
+/// # let future: Box<dyn GpuFuture> = return;
/// // Assuming you have a chain of operations, like this:
/// // let future = ...
/// // .then_execute(foo)
@@ -106,17 +116,17 @@ where
// been dropped).
enum FenceSignalFutureState<F> {
// Newly-created. Not submitted yet.
- Pending(F, Fence),
+ Pending(F, Arc<Fence>),
// Partially submitted to the queue. Only happens in situations where submitting requires two
// steps, and when the first step succeeded while the second step failed.
//
// Note that if there's ever a submit operation that needs three steps we will need to rework
// this code, as it was designed for two-step operations only.
- PartiallyFlushed(F, Fence),
+ PartiallyFlushed(F, Arc<Fence>),
// Submitted to the queue.
- Flushed(F, Fence),
+ Flushed(F, Arc<Fence>),
// The submission is finished. The previous future and the fence have been cleaned.
Cleaned,
@@ -129,6 +139,19 @@ impl<F> FenceSignalFuture<F>
where
F: GpuFuture,
{
+ /// Returns true if the fence is signaled by the GPU.
+ pub fn is_signaled(&self) -> Result<bool, OomError> {
+ let state = self.state.lock();
+
+ match &*state {
+ FenceSignalFutureState::Pending(_, fence)
+ | FenceSignalFutureState::PartiallyFlushed(_, fence)
+ | FenceSignalFutureState::Flushed(_, fence) => fence.is_signaled(),
+ FenceSignalFutureState::Cleaned => Ok(true),
+ FenceSignalFutureState::Poisoned => unreachable!(),
+ }
+ }
+
/// Blocks the current thread until the fence is signaled by the GPU. Performs a flush if
/// necessary.
///
@@ -138,11 +161,11 @@ where
/// If the wait is successful, this function also cleans any resource locked by previous
/// submissions.
pub fn wait(&self, timeout: Option<Duration>) -> Result<(), FlushError> {
- let mut state = self.state.lock().unwrap();
+ let mut state = self.state.lock();
self.flush_impl(&mut state)?;
- match mem::replace(&mut *state, FenceSignalFutureState::Cleaned) {
+ match replace(&mut *state, FenceSignalFutureState::Cleaned) {
FenceSignalFutureState::Flushed(previous, fence) => {
fence.wait(timeout)?;
unsafe {
@@ -162,48 +185,43 @@ where
{
// Implementation of `cleanup_finished`, but takes a `&self` instead of a `&mut self`.
// This is an external function so that we can also call it from an `Arc<FenceSignalFuture>`.
- #[inline]
fn cleanup_finished_impl(&self) {
- let mut state = self.state.lock().unwrap();
+ let mut state = self.state.lock();
match *state {
FenceSignalFutureState::Flushed(ref mut prev, ref fence) => {
match fence.wait(Some(Duration::from_secs(0))) {
- Ok(()) => unsafe { prev.signal_finished() },
+ Ok(()) => {
+ unsafe { prev.signal_finished() }
+ *state = FenceSignalFutureState::Cleaned;
+ }
Err(_) => {
prev.cleanup_finished();
- return;
}
}
}
FenceSignalFutureState::Pending(ref mut prev, _) => {
prev.cleanup_finished();
- return;
}
FenceSignalFutureState::PartiallyFlushed(ref mut prev, _) => {
prev.cleanup_finished();
- return;
}
- _ => return,
- };
-
- // This code can only be reached if we're already flushed and waiting on the fence
- // succeeded.
- *state = FenceSignalFutureState::Cleaned;
+ _ => (),
+ }
}
// Implementation of `flush`. You must lock the state and pass the mutex guard here.
fn flush_impl(
&self,
- state: &mut MutexGuard<FenceSignalFutureState<F>>,
+ state: &mut MutexGuard<'_, FenceSignalFutureState<F>>,
) -> Result<(), FlushError> {
unsafe {
// In this function we temporarily replace the current state with `Poisoned` at the
// beginning, and we take care to always put back a value into `state` before
// returning (even in case of error).
- let old_state = mem::replace(&mut **state, FenceSignalFutureState::Poisoned);
+ let old_state = replace(&mut **state, FenceSignalFutureState::Poisoned);
- let (previous, fence, partially_flushed) = match old_state {
+ let (previous, new_fence, partially_flushed) = match old_state {
FenceSignalFutureState::Pending(prev, fence) => (prev, fence, false),
FenceSignalFutureState::PartiallyFlushed(prev, fence) => (prev, fence, true),
other => {
@@ -215,7 +233,7 @@ where
};
// TODO: meh for unwrap
- let queue = previous.queue().unwrap().clone();
+ let queue = previous.queue().unwrap();
// There are three possible outcomes for the flush operation: success, partial success
// in which case `result` will contain `Err(OutcomeErr::Partial)`, or total failure
@@ -227,51 +245,107 @@ where
let result = match previous.build_submission()? {
SubmitAnyBuilder::Empty => {
debug_assert!(!partially_flushed);
- let mut b = SubmitCommandBufferBuilder::new();
- b.set_fence_signal(&fence);
- b.submit(&queue).map_err(|err| OutcomeErr::Full(err.into()))
+
+ queue
+ .with(|mut q| {
+ q.submit_unchecked([Default::default()], Some(new_fence.clone()))
+ })
+ .map_err(|err| OutcomeErr::Full(err.into()))
}
- SubmitAnyBuilder::SemaphoresWait(sem) => {
+ SubmitAnyBuilder::SemaphoresWait(semaphores) => {
debug_assert!(!partially_flushed);
- let b: SubmitCommandBufferBuilder = sem.into();
- debug_assert!(!b.has_fence());
- b.submit(&queue).map_err(|err| OutcomeErr::Full(err.into()))
+
+ queue
+ .with(|mut q| {
+ q.submit_unchecked(
+ [SubmitInfo {
+ wait_semaphores: semaphores
+ .into_iter()
+ .map(|semaphore| {
+ SemaphoreSubmitInfo {
+ // TODO: correct stages ; hard
+ stages: PipelineStages::ALL_COMMANDS,
+ ..SemaphoreSubmitInfo::semaphore(semaphore)
+ }
+ })
+ .collect(),
+ ..Default::default()
+ }],
+ None,
+ )
+ })
+ .map_err(|err| OutcomeErr::Full(err.into()))
}
- SubmitAnyBuilder::CommandBuffer(mut cb_builder) => {
+ SubmitAnyBuilder::CommandBuffer(submit_info, fence) => {
debug_assert!(!partially_flushed);
// The assert below could technically be a debug assertion as it is part of the
// safety contract of the trait. However it is easy to get this wrong if you
// write a custom implementation, and if so the consequences would be
// disastrous and hard to debug. Therefore we prefer to just use a regular
// assertion.
- assert!(!cb_builder.has_fence());
- cb_builder.set_fence_signal(&fence);
- cb_builder
- .submit(&queue)
- .map_err(|err| OutcomeErr::Full(err.into()))
+ assert!(fence.is_none());
+
+ queue
+ .with(|mut q| {
+ q.submit_with_future(
+ submit_info,
+ Some(new_fence.clone()),
+ &previous,
+ &queue,
+ )
+ })
+ .map_err(OutcomeErr::Full)
}
- SubmitAnyBuilder::BindSparse(mut sparse) => {
+ SubmitAnyBuilder::BindSparse(bind_infos, fence) => {
debug_assert!(!partially_flushed);
// Same remark as `CommandBuffer`.
- assert!(!sparse.has_fence());
- sparse.set_fence_signal(&fence);
- sparse
- .submit(&queue)
+ assert!(fence.is_none());
+ debug_assert!(queue.device().physical_device().queue_family_properties()
+ [queue.queue_family_index() as usize]
+ .queue_flags
+ .intersects(QueueFlags::SPARSE_BINDING));
+
+ queue
+ .with(|mut q| q.bind_sparse_unchecked(bind_infos, Some(new_fence.clone())))
.map_err(|err| OutcomeErr::Full(err.into()))
}
- SubmitAnyBuilder::QueuePresent(present) => {
+ SubmitAnyBuilder::QueuePresent(present_info) => {
let intermediary_result = if partially_flushed {
Ok(())
} else {
- present.submit(&queue)
+ // VUID-VkPresentIdKHR-presentIds-04999
+ for swapchain_info in &present_info.swapchain_infos {
+ if swapchain_info.present_id.map_or(false, |present_id| {
+ !swapchain_info.swapchain.try_claim_present_id(present_id)
+ }) {
+ return Err(FlushError::PresentIdLessThanOrEqual);
+ }
+
+ match previous.check_swapchain_image_acquired(
+ &swapchain_info.swapchain,
+ swapchain_info.image_index,
+ true,
+ ) {
+ Ok(_) => (),
+ Err(AccessCheckError::Unknown) => {
+ return Err(AccessError::SwapchainImageNotAcquired.into())
+ }
+ Err(AccessCheckError::Denied(e)) => return Err(e.into()),
+ }
+ }
+
+ queue
+ .with(|mut q| q.present_unchecked(present_info))?
+ .map(|r| r.map(|_| ()))
+ .fold(Ok(()), Result::and)
};
+
match intermediary_result {
- Ok(()) => {
- let mut b = SubmitCommandBufferBuilder::new();
- b.set_fence_signal(&fence);
- b.submit(&queue)
- .map_err(|err| OutcomeErr::Partial(err.into()))
- }
+ Ok(()) => queue
+ .with(|mut q| {
+ q.submit_unchecked([Default::default()], Some(new_fence.clone()))
+ })
+ .map_err(|err| OutcomeErr::Partial(err.into())),
Err(err) => Err(OutcomeErr::Full(err.into())),
}
}
@@ -280,15 +354,15 @@ where
// Restore the state before returning.
match result {
Ok(()) => {
- **state = FenceSignalFutureState::Flushed(previous, fence);
+ **state = FenceSignalFutureState::Flushed(previous, new_fence);
Ok(())
}
Err(OutcomeErr::Partial(err)) => {
- **state = FenceSignalFutureState::PartiallyFlushed(previous, fence);
+ **state = FenceSignalFutureState::PartiallyFlushed(previous, new_fence);
Err(err)
}
Err(OutcomeErr::Full(err)) => {
- **state = FenceSignalFutureState::Pending(previous, fence);
+ **state = FenceSignalFutureState::Pending(previous, new_fence);
Err(err)
}
}
@@ -296,13 +370,32 @@ where
}
}
+impl<F> Future for FenceSignalFuture<F>
+where
+ F: GpuFuture,
+{
+ type Output = Result<(), OomError>;
+
+ fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
+ // Implement through fence
+ let state = self.state.lock();
+
+ match &*state {
+ FenceSignalFutureState::Pending(_, fence)
+ | FenceSignalFutureState::PartiallyFlushed(_, fence)
+ | FenceSignalFutureState::Flushed(_, fence) => fence.poll_impl(cx),
+ FenceSignalFutureState::Cleaned => Poll::Ready(Ok(())),
+ FenceSignalFutureState::Poisoned => unreachable!(),
+ }
+ }
+}
+
impl<F> FenceSignalFutureState<F> {
- #[inline]
fn get_prev(&self) -> Option<&F> {
- match *self {
- FenceSignalFutureState::Pending(ref prev, _) => Some(prev),
- FenceSignalFutureState::PartiallyFlushed(ref prev, _) => Some(prev),
- FenceSignalFutureState::Flushed(ref prev, _) => Some(prev),
+ match self {
+ FenceSignalFutureState::Pending(prev, _) => Some(prev),
+ FenceSignalFutureState::PartiallyFlushed(prev, _) => Some(prev),
+ FenceSignalFutureState::Flushed(prev, _) => Some(prev),
FenceSignalFutureState::Cleaned => None,
FenceSignalFutureState::Poisoned => None,
}
@@ -313,18 +406,16 @@ unsafe impl<F> GpuFuture for FenceSignalFuture<F>
where
F: GpuFuture,
{
- #[inline]
fn cleanup_finished(&mut self) {
self.cleanup_finished_impl()
}
- #[inline]
unsafe fn build_submission(&self) -> Result<SubmitAnyBuilder, FlushError> {
- let mut state = self.state.lock().unwrap();
+ let mut state = self.state.lock();
self.flush_impl(&mut state)?;
- match *state {
- FenceSignalFutureState::Flushed(_, ref fence) => match self.behavior {
+ match &*state {
+ FenceSignalFutureState::Flushed(_, fence) => match self.behavior {
FenceSignalFutureBehavior::Block { timeout } => {
fence.wait(timeout)?;
}
@@ -338,15 +429,13 @@ where
Ok(SubmitAnyBuilder::Empty)
}
- #[inline]
fn flush(&self) -> Result<(), FlushError> {
- let mut state = self.state.lock().unwrap();
+ let mut state = self.state.lock();
self.flush_impl(&mut state)
}
- #[inline]
unsafe fn signal_finished(&self) {
- let state = self.state.lock().unwrap();
+ let state = self.state.lock();
match *state {
FenceSignalFutureState::Flushed(ref prev, _) => {
prev.signal_finished();
@@ -356,24 +445,18 @@ where
}
}
- #[inline]
fn queue_change_allowed(&self) -> bool {
match self.behavior {
FenceSignalFutureBehavior::Continue => {
- let state = self.state.lock().unwrap();
- if state.get_prev().is_some() {
- false
- } else {
- true
- }
+ let state = self.state.lock();
+ state.get_prev().is_none()
}
FenceSignalFutureBehavior::Block { .. } => true,
}
}
- #[inline]
fn queue(&self) -> Option<Arc<Queue>> {
- let state = self.state.lock().unwrap();
+ let state = self.state.lock();
if let Some(prev) = state.get_prev() {
prev.queue()
} else {
@@ -381,32 +464,46 @@ where
}
}
- #[inline]
fn check_buffer_access(
&self,
- buffer: &dyn BufferAccess,
+ buffer: &Buffer,
+ range: Range<DeviceSize>,
exclusive: bool,
queue: &Queue,
- ) -> Result<Option<(PipelineStages, AccessFlags)>, AccessCheckError> {
- let state = self.state.lock().unwrap();
+ ) -> Result<(), AccessCheckError> {
+ let state = self.state.lock();
if let Some(previous) = state.get_prev() {
- previous.check_buffer_access(buffer, exclusive, queue)
+ previous.check_buffer_access(buffer, range, exclusive, queue)
} else {
Err(AccessCheckError::Unknown)
}
}
- #[inline]
fn check_image_access(
&self,
- image: &dyn ImageAccess,
- layout: ImageLayout,
+ image: &Image,
+ range: Range<DeviceSize>,
exclusive: bool,
+ expected_layout: ImageLayout,
queue: &Queue,
- ) -> Result<Option<(PipelineStages, AccessFlags)>, AccessCheckError> {
- let state = self.state.lock().unwrap();
+ ) -> Result<(), AccessCheckError> {
+ let state = self.state.lock();
if let Some(previous) = state.get_prev() {
- previous.check_image_access(image, layout, exclusive, queue)
+ previous.check_image_access(image, range, exclusive, expected_layout, queue)
+ } else {
+ Err(AccessCheckError::Unknown)
+ }
+ }
+
+ #[inline]
+ fn check_swapchain_image_acquired(
+ &self,
+ swapchain: &Swapchain,
+ image_index: u32,
+ _before: bool,
+ ) -> Result<(), AccessCheckError> {
+ if let Some(previous) = self.state.lock().get_prev() {
+ previous.check_swapchain_image_acquired(swapchain, image_index, false)
} else {
Err(AccessCheckError::Unknown)
}
@@ -417,7 +514,6 @@ unsafe impl<F> DeviceOwned for FenceSignalFuture<F>
where
F: GpuFuture,
{
- #[inline]
fn device(&self) -> &Arc<Device> {
&self.device
}
@@ -428,12 +524,16 @@ where
F: GpuFuture,
{
fn drop(&mut self) {
- let mut state = self.state.lock().unwrap();
+ if thread::panicking() {
+ return;
+ }
+
+ let mut state = self.state.lock();
// We ignore any possible error while submitting for now. Problems are handled below.
let _ = self.flush_impl(&mut state);
- match mem::replace(&mut *state, FenceSignalFutureState::Cleaned) {
+ match replace(&mut *state, FenceSignalFutureState::Cleaned) {
FenceSignalFutureState::Flushed(previous, fence) => {
// This is a normal situation. Submitting worked.
// TODO: handle errors?
@@ -461,56 +561,60 @@ unsafe impl<F> GpuFuture for Arc<FenceSignalFuture<F>>
where
F: GpuFuture,
{
- #[inline]
fn cleanup_finished(&mut self) {
self.cleanup_finished_impl()
}
- #[inline]
unsafe fn build_submission(&self) -> Result<SubmitAnyBuilder, FlushError> {
// Note that this is sound because we always return `SubmitAnyBuilder::Empty`. See the
// documentation of `build_submission`.
(**self).build_submission()
}
- #[inline]
fn flush(&self) -> Result<(), FlushError> {
(**self).flush()
}
- #[inline]
unsafe fn signal_finished(&self) {
(**self).signal_finished()
}
- #[inline]
fn queue_change_allowed(&self) -> bool {
(**self).queue_change_allowed()
}
- #[inline]
fn queue(&self) -> Option<Arc<Queue>> {
(**self).queue()
}
- #[inline]
fn check_buffer_access(
&self,
- buffer: &dyn BufferAccess,
+ buffer: &Buffer,
+ range: Range<DeviceSize>,
exclusive: bool,
queue: &Queue,
- ) -> Result<Option<(PipelineStages, AccessFlags)>, AccessCheckError> {
- (**self).check_buffer_access(buffer, exclusive, queue)
+ ) -> Result<(), AccessCheckError> {
+ (**self).check_buffer_access(buffer, range, exclusive, queue)
}
- #[inline]
fn check_image_access(
&self,
- image: &dyn ImageAccess,
- layout: ImageLayout,
+ image: &Image,
+ range: Range<DeviceSize>,
exclusive: bool,
+ expected_layout: ImageLayout,
queue: &Queue,
- ) -> Result<Option<(PipelineStages, AccessFlags)>, AccessCheckError> {
- (**self).check_image_access(image, layout, exclusive, queue)
+ ) -> Result<(), AccessCheckError> {
+ (**self).check_image_access(image, range, exclusive, expected_layout, queue)
+ }
+
+ #[inline]
+ fn check_swapchain_image_acquired(
+ &self,
+ swapchain: &Swapchain,
+ image_index: u32,
+ before: bool,
+ ) -> Result<(), AccessCheckError> {
+ (**self).check_swapchain_image_acquired(swapchain, image_index, before)
}
}