pub use self::builder::{
CommandBufferBuilderState, SetOrPush, StencilOpStateDynamic, StencilStateDynamic,
SyncCommandBufferBuilder, SyncCommandBufferBuilderBindDescriptorSets,
SyncCommandBufferBuilderBindVertexBuffer, SyncCommandBufferBuilderError,
SyncCommandBufferBuilderExecuteCommands,
};
use super::{
sys::{UnsafeCommandBuffer, UnsafeCommandBufferBuilder},
CommandBufferResourcesUsage,
};
use crate::{
buffer::{sys::Buffer, BufferAccess},
device::{Device, DeviceOwned, Queue},
image::{sys::Image, ImageAccess, ImageLayout, ImageSubresourceRange},
sync::{AccessCheckError, AccessError, AccessFlags, PipelineMemoryAccess, PipelineStages},
DeviceSize,
};
use ahash::HashMap;
use std::{
borrow::Cow,
fmt::{Debug, Error as FmtError, Formatter},
ops::Range,
sync::Arc,
};
mod builder;
pub struct SyncCommandBuffer {
inner: UnsafeCommandBuffer,
_commands: Vec<Box<dyn Command>>,
_barriers: Vec<usize>,
resources_usage: CommandBufferResourcesUsage,
buffer_indices: HashMap<Arc<Buffer>, usize>,
image_indices: HashMap<Arc<Image>, usize>,
buffers: Vec<(
Arc<dyn BufferAccess>,
Range<DeviceSize>,
PipelineMemoryAccess,
)>,
images: Vec<(
Arc<dyn ImageAccess>,
ImageSubresourceRange,
PipelineMemoryAccess,
ImageLayout,
ImageLayout,
)>,
}
impl SyncCommandBuffer {
#[inline]
pub(super) fn resources_usage(&self) -> &CommandBufferResourcesUsage {
&self.resources_usage
}
#[inline]
pub fn check_buffer_access(
&self,
buffer: &Buffer,
range: Range<DeviceSize>,
exclusive: bool,
_queue: &Queue,
) -> Result<Option<(PipelineStages, AccessFlags)>, AccessCheckError> {
let usage = match self.buffer_indices.get(buffer) {
Some(&index) => &self.resources_usage.buffers[index],
None => return Err(AccessCheckError::Unknown),
};
usage
.ranges
.range(&range)
.try_fold(
(PipelineStages::empty(), AccessFlags::empty()),
|(stages, access), (_range, range_usage)| {
if !range_usage.mutable && exclusive {
Err(AccessCheckError::Unknown)
} else {
Ok((
stages | range_usage.final_stages,
access | range_usage.final_access,
))
}
},
)
.map(Some)
}
#[inline]
pub fn check_image_access(
&self,
image: &Image,
range: Range<DeviceSize>,
exclusive: bool,
expected_layout: ImageLayout,
_queue: &Queue,
) -> Result<Option<(PipelineStages, AccessFlags)>, AccessCheckError> {
let usage = match self.image_indices.get(image) {
Some(&index) => &self.resources_usage.images[index],
None => return Err(AccessCheckError::Unknown),
};
usage
.ranges
.range(&range)
.try_fold(
(PipelineStages::empty(), AccessFlags::empty()),
|(stages, access), (_range, range_usage)| {
if expected_layout != ImageLayout::Undefined
&& range_usage.final_layout != expected_layout
{
return Err(AccessCheckError::Denied(
AccessError::UnexpectedImageLayout {
allowed: range_usage.final_layout,
requested: expected_layout,
},
));
}
if !range_usage.mutable && exclusive {
Err(AccessCheckError::Unknown)
} else {
Ok((
stages | range_usage.final_stages,
access | range_usage.final_access,
))
}
},
)
.map(Some)
}
#[inline]
pub fn num_buffers(&self) -> usize {
self.buffers.len()
}
#[inline]
pub fn buffer(
&self,
index: usize,
) -> Option<(
&Arc<dyn BufferAccess>,
Range<DeviceSize>,
PipelineMemoryAccess,
)> {
self.buffers
.get(index)
.map(|(buffer, range, memory)| (buffer, range.clone(), *memory))
}
#[inline]
pub fn num_images(&self) -> usize {
self.images.len()
}
#[inline]
pub fn image(
&self,
index: usize,
) -> Option<(
&Arc<dyn ImageAccess>,
&ImageSubresourceRange,
PipelineMemoryAccess,
ImageLayout,
ImageLayout,
)> {
self.images
.get(index)
.map(|(image, range, memory, start_layout, end_layout)| {
(image, range, *memory, *start_layout, *end_layout)
})
}
}
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(Clone, PartialEq, Eq)]
struct BufferFinalState {
resource_uses: Vec<BufferUse>,
final_stages: PipelineStages,
final_access: AccessFlags,
exclusive: bool,
}
#[derive(Clone, PartialEq, Eq)]
struct ImageFinalState {
resource_uses: Vec<ImageUse>,
final_stages: PipelineStages,
final_access: AccessFlags,
exclusive: bool,
initial_layout: ImageLayout,
final_layout: ImageLayout, }
#[derive(Clone, PartialEq, Eq)]
struct BufferUse {
command_index: usize,
name: Cow<'static, str>,
}
#[derive(Clone, PartialEq, Eq)]
struct ImageUse {
command_index: usize,
name: Cow<'static, str>,
}
#[derive(Clone)]
pub(super) enum Resource {
Buffer {
buffer: Arc<dyn BufferAccess>,
range: Range<DeviceSize>,
memory: PipelineMemoryAccess,
},
Image {
image: Arc<dyn ImageAccess>,
subresource_range: ImageSubresourceRange,
memory: PipelineMemoryAccess,
start_layout: ImageLayout,
end_layout: ImageLayout,
},
}
pub(super) trait Command: Send + Sync {
fn name(&self) -> &'static str;
unsafe fn send(&self, out: &mut UnsafeCommandBufferBuilder);
}
impl Debug for dyn Command {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> {
f.write_str(self.name())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
buffer::{BufferUsage, CpuAccessibleBuffer, DeviceLocalBuffer},
command_buffer::{
allocator::{
CommandBufferAllocator, CommandBufferBuilderAlloc, StandardCommandBufferAllocator,
},
sys::CommandBufferBeginInfo,
AutoCommandBufferBuilder, CommandBufferLevel, CommandBufferUsage, FillBufferInfo,
PrimaryCommandBufferAbstract,
},
descriptor_set::{
allocator::StandardDescriptorSetAllocator,
layout::{
DescriptorSetLayout, DescriptorSetLayoutBinding, DescriptorSetLayoutCreateInfo,
DescriptorType,
},
PersistentDescriptorSet, WriteDescriptorSet,
},
memory::allocator::StandardMemoryAllocator,
pipeline::{layout::PipelineLayoutCreateInfo, PipelineBindPoint, PipelineLayout},
sampler::{Sampler, SamplerCreateInfo},
shader::ShaderStages,
sync::GpuFuture,
};
#[test]
fn basic_creation() {
unsafe {
let (device, queue) = gfx_dev_and_queue!();
let allocator = StandardCommandBufferAllocator::new(device, Default::default());
let builder_alloc = allocator
.allocate(queue.queue_family_index(), CommandBufferLevel::Primary, 1)
.unwrap()
.next()
.unwrap();
SyncCommandBufferBuilder::new(
builder_alloc.inner(),
CommandBufferBeginInfo {
usage: CommandBufferUsage::MultipleSubmit,
..Default::default()
},
)
.unwrap();
}
}
#[test]
fn secondary_conflicting_writes() {
unsafe {
let (device, queue) = gfx_dev_and_queue!();
let cb_allocator =
StandardCommandBufferAllocator::new(device.clone(), Default::default());
let mut cbb = AutoCommandBufferBuilder::primary(
&cb_allocator,
queue.queue_family_index(),
CommandBufferUsage::OneTimeSubmit,
)
.unwrap();
let memory_allocator = StandardMemoryAllocator::new_default(device);
let buffer = DeviceLocalBuffer::from_data(
&memory_allocator,
0u32,
BufferUsage {
transfer_dst: true,
..BufferUsage::empty()
},
&mut cbb,
)
.unwrap();
cbb.build()
.unwrap()
.execute(queue.clone())
.unwrap()
.then_signal_fence_and_flush()
.unwrap()
.wait(None)
.unwrap();
let secondary = (0..2)
.map(|_| {
let mut builder = AutoCommandBufferBuilder::secondary(
&cb_allocator,
queue.queue_family_index(),
CommandBufferUsage::SimultaneousUse,
Default::default(),
)
.unwrap();
builder
.fill_buffer(FillBufferInfo {
data: 42u32,
..FillBufferInfo::dst_buffer(buffer.clone())
})
.unwrap();
Arc::new(builder.build().unwrap())
})
.collect::<Vec<_>>();
let allocs = cb_allocator
.allocate(queue.queue_family_index(), 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, &["execute_commands", "execute_commands"]);
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);
});
ec.submit().unwrap();
}
}
}
#[test]
fn vertex_buffer_binding() {
unsafe {
let (device, queue) = gfx_dev_and_queue!();
let cb_allocator =
StandardCommandBufferAllocator::new(device.clone(), Default::default());
let builder_alloc = cb_allocator
.allocate(queue.queue_family_index(), CommandBufferLevel::Primary, 1)
.unwrap()
.next()
.unwrap();
let mut sync = SyncCommandBufferBuilder::new(
builder_alloc.inner(),
CommandBufferBeginInfo {
usage: CommandBufferUsage::MultipleSubmit,
..Default::default()
},
)
.unwrap();
let memory_allocator = StandardMemoryAllocator::new_default(device);
let buf = CpuAccessibleBuffer::from_data(
&memory_allocator,
BufferUsage {
vertex_buffer: true,
..BufferUsage::empty()
},
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 cb_allocator =
StandardCommandBufferAllocator::new(device.clone(), Default::default());
let builder_alloc = cb_allocator
.allocate(queue.queue_family_index(), CommandBufferLevel::Primary, 1)
.unwrap()
.next()
.unwrap();
let mut sync = SyncCommandBufferBuilder::new(
builder_alloc.inner(),
CommandBufferBeginInfo {
usage: CommandBufferUsage::MultipleSubmit,
..Default::default()
},
)
.unwrap();
let set_layout = DescriptorSetLayout::new(
device.clone(),
DescriptorSetLayoutCreateInfo {
bindings: [(
0,
DescriptorSetLayoutBinding {
stages: ShaderStages::all_graphics(),
..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 ds_allocator = StandardDescriptorSetAllocator::new(device.clone());
let set = PersistentDescriptorSet::new(
&ds_allocator,
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(
&ds_allocator,
set_layout,
[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());
}
}
}