pub use self::builder::{
CommandBufferState, SetOrPush, StencilOpStateDynamic, StencilStateDynamic,
SyncCommandBufferBuilder, SyncCommandBufferBuilderBindDescriptorSets,
SyncCommandBufferBuilderBindVertexBuffer, SyncCommandBufferBuilderError,
SyncCommandBufferBuilderExecuteCommands,
};
use super::{
sys::{UnsafeCommandBuffer, UnsafeCommandBufferBuilder},
CommandBufferExecError, ImageUninitializedSafe,
};
use crate::{
buffer::BufferAccess,
device::{Device, DeviceOwned, Queue},
image::{ImageAccess, ImageLayout},
sync::{
AccessCheckError, AccessError, AccessFlags, GpuFuture, PipelineMemoryAccess, PipelineStages,
},
};
use std::{borrow::Cow, collections::HashMap, ops::Range, sync::Arc};
mod builder;
pub struct SyncCommandBuffer {
inner: UnsafeCommandBuffer,
commands: Vec<Box<dyn Command>>,
barriers: Vec<usize>,
resources: HashMap<ResourceKey, ResourceFinalState>,
buffers: Vec<(Arc<dyn BufferAccess>, PipelineMemoryAccess)>,
images: Vec<(
Arc<dyn ImageAccess>,
PipelineMemoryAccess,
ImageLayout,
ImageLayout,
ImageUninitializedSafe,
)>,
}
impl SyncCommandBuffer {
pub fn lock_submit(
&self,
future: &dyn GpuFuture,
queue: &Queue,
) -> Result<(), CommandBufferExecError> {
let mut locked_resources = 0;
let mut ret_value = Ok(());
for state in self.resources.values() {
let resource_use = &state.resource_uses[0];
match &resource_use.resource {
KeyTy::Buffer(buffer) => {
let prev_err =
match future.check_buffer_access(buffer.as_ref(), state.exclusive, queue) {
Ok(_) => {
unsafe {
buffer.increase_gpu_lock();
}
locked_resources += 1;
continue;
}
Err(err) => err,
};
match (buffer.try_gpu_lock(state.exclusive, queue), prev_err) {
(Ok(_), _) => (),
(Err(err), AccessCheckError::Unknown)
| (_, AccessCheckError::Denied(err)) => {
ret_value = Err(CommandBufferExecError::AccessError {
error: err,
command_name: self.commands[resource_use.command_index]
.name()
.into(),
command_param: resource_use.name.clone(),
command_offset: resource_use.command_index,
});
break;
}
};
}
KeyTy::Image(image) => {
let prev_err = match future.check_image_access(
image.as_ref(),
state.initial_layout,
state.exclusive,
queue,
) {
Ok(_) => {
unsafe {
image.increase_gpu_lock();
}
locked_resources += 1;
continue;
}
Err(err) => err,
};
match (
image.try_gpu_lock(
state.exclusive,
state.image_uninitialized_safe.is_safe(),
state.initial_layout,
),
prev_err,
) {
(Ok(_), _) => (),
(Err(err), AccessCheckError::Unknown)
| (_, AccessCheckError::Denied(err)) => {
ret_value = Err(CommandBufferExecError::AccessError {
error: err,
command_name: self.commands[resource_use.command_index]
.name()
.into(),
command_param: resource_use.name.clone(),
command_offset: resource_use.command_index,
});
break;
}
};
}
}
locked_resources += 1;
}
if let Err(_) = ret_value {
for state in self.resources.values().take(locked_resources) {
let resource_use = &state.resource_uses[0];
match &resource_use.resource {
KeyTy::Buffer(buffer) => unsafe {
buffer.unlock();
},
KeyTy::Image(image) => {
let trans = if state.final_layout != state.initial_layout {
Some(state.final_layout)
} else {
None
};
unsafe {
image.unlock(trans);
}
}
}
}
}
ret_value
}
pub unsafe fn unlock(&self) {
for state in self.resources.values() {
let resource_use = &state.resource_uses[0];
match &resource_use.resource {
KeyTy::Buffer(buffer) => {
buffer.unlock();
}
KeyTy::Image(image) => {
let trans = if state.final_layout != state.initial_layout {
Some(state.final_layout)
} else {
None
};
image.unlock(trans);
}
}
}
}
#[inline]
pub fn check_buffer_access(
&self,
buffer: &dyn BufferAccess,
exclusive: bool,
queue: &Queue,
) -> Result<Option<(PipelineStages, AccessFlags)>, AccessCheckError> {
if let Some(value) = self.resources.get(&buffer.into()) {
if !value.exclusive && exclusive {
return Err(AccessCheckError::Unknown);
}
return Ok(Some((value.final_stages, value.final_access)));
}
Err(AccessCheckError::Unknown)
}
#[inline]
pub fn check_image_access(
&self,
image: &dyn ImageAccess,
layout: ImageLayout,
exclusive: bool,
queue: &Queue,
) -> Result<Option<(PipelineStages, AccessFlags)>, AccessCheckError> {
if let Some(value) = self.resources.get(&image.into()) {
if layout != ImageLayout::Undefined && value.final_layout != layout {
return Err(AccessCheckError::Denied(
AccessError::UnexpectedImageLayout {
allowed: value.final_layout,
requested: layout,
},
));
}
if !value.exclusive && exclusive {
return Err(AccessCheckError::Unknown);
}
return Ok(Some((value.final_stages, value.final_access)));
}
Err(AccessCheckError::Unknown)
}
#[inline]
pub fn num_buffers(&self) -> usize {
self.buffers.len()
}
#[inline]
pub fn buffer(&self, index: usize) -> Option<(&Arc<dyn BufferAccess>, PipelineMemoryAccess)> {
self.buffers
.get(index)
.map(|(buffer, memory)| (buffer, *memory))
}
#[inline]
pub fn num_images(&self) -> usize {
self.images.len()
}
#[inline]
pub fn image(
&self,
index: usize,
) -> Option<(
&Arc<dyn ImageAccess>,
PipelineMemoryAccess,
ImageLayout,
ImageLayout,
ImageUninitializedSafe,
)> {
self.images.get(index).map(
|(image, memory, start_layout, end_layout, image_uninitialized_safe)| {
(
image,
*memory,
*start_layout,
*end_layout,
*image_uninitialized_safe,
)
},
)
}
}
impl AsRef<UnsafeCommandBuffer> for SyncCommandBuffer {
#[inline]
fn as_ref(&self) -> &UnsafeCommandBuffer {
&self.inner
}
}
unsafe impl DeviceOwned for SyncCommandBuffer {
#[inline]
fn device(&self) -> &Arc<Device> {
self.inner.device()
}
}
#[derive(Debug, PartialEq, Eq, Hash)]
enum ResourceKey {
Buffer((u64, u64)),
Image(u64, Range<u32>, Range<u32>),
}
impl From<&dyn BufferAccess> for ResourceKey {
#[inline]
fn from(buffer: &dyn BufferAccess) -> Self {
Self::Buffer(buffer.conflict_key())
}
}
impl From<&dyn ImageAccess> for ResourceKey {
#[inline]
fn from(image: &dyn ImageAccess) -> Self {
Self::Image(
image.conflict_key(),
image.current_mip_levels_access(),
image.current_array_layers_access(),
)
}
}
#[derive(Clone)]
struct ResourceFinalState {
resource_uses: Vec<ResourceUse>,
final_stages: PipelineStages,
final_access: AccessFlags,
exclusive: bool,
initial_layout: ImageLayout,
final_layout: ImageLayout,
image_uninitialized_safe: ImageUninitializedSafe,
}
#[derive(Clone)]
struct ResourceUse {
command_index: usize,
resource: KeyTy,
name: Cow<'static, str>,
}
#[derive(Clone)]
enum KeyTy {
Buffer(Arc<dyn BufferAccess>),
Image(Arc<dyn ImageAccess>),
}
#[derive(Clone, Copy, Debug)]
struct ResourceLocation {
command_id: usize,
resource_index: usize,
}
trait Command: Send + Sync {
fn name(&self) -> &'static str;
unsafe fn send(&self, out: &mut UnsafeCommandBufferBuilder);
}
impl std::fmt::Debug for dyn Command {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.name())
}
}
#[cfg(test)]
mod tests {
use super::SyncCommandBufferBuilder;
use super::SyncCommandBufferBuilderError;
use crate::buffer::BufferUsage;
use crate::buffer::CpuAccessibleBuffer;
use crate::buffer::ImmutableBuffer;
use crate::command_buffer::pool::CommandPool;
use crate::command_buffer::pool::CommandPoolBuilderAlloc;
use crate::command_buffer::sys::CommandBufferBeginInfo;
use crate::command_buffer::AutoCommandBufferBuilder;
use crate::command_buffer::CommandBufferLevel;
use crate::command_buffer::CommandBufferUsage;
use crate::descriptor_set::layout::DescriptorSetLayout;
use crate::descriptor_set::layout::DescriptorSetLayoutBinding;
use crate::descriptor_set::layout::DescriptorSetLayoutCreateInfo;
use crate::descriptor_set::layout::DescriptorType;
use crate::descriptor_set::PersistentDescriptorSet;
use crate::descriptor_set::WriteDescriptorSet;
use crate::device::Device;
use crate::pipeline::layout::PipelineLayout;
use crate::pipeline::layout::PipelineLayoutCreateInfo;
use crate::pipeline::PipelineBindPoint;
use crate::sampler::Sampler;
use crate::sampler::SamplerCreateInfo;
use crate::shader::ShaderStages;
use crate::sync::GpuFuture;
use std::sync::Arc;
#[test]
fn basic_creation() {
unsafe {
let (device, queue) = gfx_dev_and_queue!();
let pool = Device::standard_command_pool(&device, queue.family());
let pool_builder_alloc = pool
.allocate(CommandBufferLevel::Primary, 1)
.unwrap()
.next()
.unwrap();
assert!(matches!(
SyncCommandBufferBuilder::new(
&pool_builder_alloc.inner(),
CommandBufferBeginInfo {
usage: CommandBufferUsage::MultipleSubmit,
..Default::default()
},
),
Ok(_)
));
}
}
#[test]
fn secondary_conflicting_writes() {
unsafe {
let (device, queue) = gfx_dev_and_queue!();
let (buf, future) = ImmutableBuffer::from_data(
0u32,
BufferUsage::transfer_destination(),
queue.clone(),
)
.unwrap();
future
.then_signal_fence_and_flush()
.unwrap()
.wait(None)
.unwrap();
let secondary = (0..2)
.map(|_| {
let mut builder = AutoCommandBufferBuilder::secondary_compute(
device.clone(),
queue.family(),
CommandBufferUsage::SimultaneousUse,
)
.unwrap();
builder.fill_buffer(buf.clone(), 42u32).unwrap();
Arc::new(builder.build().unwrap())
})
.collect::<Vec<_>>();
let pool = Device::standard_command_pool(&device, queue.family());
let allocs = pool
.allocate(CommandBufferLevel::Primary, 2)
.unwrap()
.collect::<Vec<_>>();
{
let mut builder = SyncCommandBufferBuilder::new(
allocs[0].inner(),
CommandBufferBeginInfo {
usage: CommandBufferUsage::SimultaneousUse,
..Default::default()
},
)
.unwrap();
secondary.iter().cloned().for_each(|secondary| {
let mut ec = builder.execute_commands();
ec.add(secondary);
ec.submit().unwrap();
});
let primary = builder.build().unwrap();
let names = primary
.commands
.iter()
.map(|c| c.name())
.collect::<Vec<_>>();
assert_eq!(&names, &["vkCmdExecuteCommands", "vkCmdExecuteCommands"]);
assert_eq!(&primary.barriers, &[0, 1]);
}
{
let mut builder = SyncCommandBufferBuilder::new(
allocs[1].inner(),
CommandBufferBeginInfo {
usage: CommandBufferUsage::SimultaneousUse,
..Default::default()
},
)
.unwrap();
let mut ec = builder.execute_commands();
secondary.into_iter().for_each(|secondary| {
ec.add(secondary);
});
assert!(matches!(
ec.submit(),
Err(SyncCommandBufferBuilderError::Conflict { .. })
));
}
}
}
#[test]
fn vertex_buffer_binding() {
unsafe {
let (device, queue) = gfx_dev_and_queue!();
let pool = Device::standard_command_pool(&device, queue.family());
let pool_builder_alloc = pool
.allocate(CommandBufferLevel::Primary, 1)
.unwrap()
.next()
.unwrap();
let mut sync = SyncCommandBufferBuilder::new(
&pool_builder_alloc.inner(),
CommandBufferBeginInfo {
usage: CommandBufferUsage::MultipleSubmit,
..Default::default()
},
)
.unwrap();
let buf =
CpuAccessibleBuffer::from_data(device, BufferUsage::all(), false, 0u32).unwrap();
let mut buf_builder = sync.bind_vertex_buffers();
buf_builder.add(buf);
buf_builder.submit(1);
assert!(sync.state().vertex_buffer(0).is_none());
assert!(sync.state().vertex_buffer(1).is_some());
assert!(sync.state().vertex_buffer(2).is_none());
}
}
#[test]
fn descriptor_set_binding() {
unsafe {
let (device, queue) = gfx_dev_and_queue!();
let pool = Device::standard_command_pool(&device, queue.family());
let pool_builder_alloc = pool
.allocate(CommandBufferLevel::Primary, 1)
.unwrap()
.next()
.unwrap();
let mut sync = SyncCommandBufferBuilder::new(
&pool_builder_alloc.inner(),
CommandBufferBeginInfo {
usage: CommandBufferUsage::MultipleSubmit,
..Default::default()
},
)
.unwrap();
let set_layout = DescriptorSetLayout::new(
device.clone(),
DescriptorSetLayoutCreateInfo {
bindings: [(
0,
DescriptorSetLayoutBinding {
stages: ShaderStages::all(),
..DescriptorSetLayoutBinding::descriptor_type(DescriptorType::Sampler)
},
)]
.into(),
..Default::default()
},
)
.unwrap();
let pipeline_layout = PipelineLayout::new(
device.clone(),
PipelineLayoutCreateInfo {
set_layouts: [set_layout.clone(), set_layout.clone()].into(),
..Default::default()
},
)
.unwrap();
let set = PersistentDescriptorSet::new(
set_layout.clone(),
[WriteDescriptorSet::sampler(
0,
Sampler::new(device.clone(), SamplerCreateInfo::simple_repeat_linear())
.unwrap(),
)],
)
.unwrap();
let mut set_builder = sync.bind_descriptor_sets();
set_builder.add(set.clone());
set_builder.submit(PipelineBindPoint::Graphics, pipeline_layout.clone(), 1);
assert!(sync
.state()
.descriptor_set(PipelineBindPoint::Compute, 0)
.is_none());
assert!(sync
.state()
.descriptor_set(PipelineBindPoint::Graphics, 0)
.is_none());
assert!(sync
.state()
.descriptor_set(PipelineBindPoint::Graphics, 1)
.is_some());
assert!(sync
.state()
.descriptor_set(PipelineBindPoint::Graphics, 2)
.is_none());
let mut set_builder = sync.bind_descriptor_sets();
set_builder.add(set);
set_builder.submit(PipelineBindPoint::Graphics, pipeline_layout, 0);
assert!(sync
.state()
.descriptor_set(PipelineBindPoint::Graphics, 0)
.is_some());
assert!(sync
.state()
.descriptor_set(PipelineBindPoint::Graphics, 1)
.is_some());
let pipeline_layout = PipelineLayout::new(
device.clone(),
PipelineLayoutCreateInfo {
set_layouts: [
DescriptorSetLayout::new(device.clone(), Default::default()).unwrap(),
set_layout.clone(),
]
.into(),
..Default::default()
},
)
.unwrap();
let set = PersistentDescriptorSet::new(
set_layout.clone(),
[WriteDescriptorSet::sampler(
0,
Sampler::new(device, SamplerCreateInfo::simple_repeat_linear()).unwrap(),
)],
)
.unwrap();
let mut set_builder = sync.bind_descriptor_sets();
set_builder.add(set);
set_builder.submit(PipelineBindPoint::Graphics, pipeline_layout, 1);
assert!(sync
.state()
.descriptor_set(PipelineBindPoint::Graphics, 0)
.is_none());
assert!(sync
.state()
.descriptor_set(PipelineBindPoint::Graphics, 1)
.is_some());
}
}
}