automatic barriers

This commit is contained in:
janis 2026-04-12 14:26:02 +02:00
parent 666e05bb8e
commit 7acda4d1fb
Signed by: janis
SSH key fingerprint: SHA256:bB1qbbqmDXZNT0KKD5c2Dfjg53JGhj7B3CFcLIzSqq8
4 changed files with 741 additions and 62 deletions

View file

@ -1,4 +1,9 @@
use std::{borrow::Cow, mem::ManuallyDrop, sync::Arc};
use std::{
borrow::Cow,
mem::ManuallyDrop,
ops::{Add, Sub},
sync::Arc,
};
use crate::{
device::{
@ -33,6 +38,22 @@ pub struct ImageDesc {
pub alloc_scheme: AllocationStrategy,
}
impl ImageDesc {
pub fn mip_level_range(&self) -> MipRange {
MipRange {
start: 0,
end: self.mip_levels,
}
}
pub fn layer_range(&self) -> MipRange {
MipRange {
start: 0,
end: self.array_layers,
}
}
}
impl std::hash::Hash for ImageDesc {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.flags.hash(state);
@ -469,9 +490,9 @@ impl Image {
vk::ImageSubresourceRange::default()
.aspect_mask(desc.aspect)
.base_mip_level(desc.mip_range.start)
.level_count(desc.mip_range.count())
.level_count(desc.mip_range.len())
.base_array_layer(desc.layer_range.start)
.layer_count(desc.layer_range.count()),
.layer_count(desc.layer_range.len()),
);
let device = self.image.device();
@ -585,6 +606,185 @@ impl ImageViewDesc {
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Extent {
pub width: u32,
pub height: u32,
pub depth: u32,
}
impl Extent {
pub fn as_offset(self) -> Offset {
Offset {
x: self.width as i32,
y: self.height as i32,
z: self.depth as i32,
}
}
pub fn from_offset(offset: Offset) -> Self {
Self {
width: offset.x as u32,
height: offset.y as u32,
depth: offset.z as u32,
}
}
pub fn min(self, other: Self) -> Self {
Self {
width: self.width.min(other.width),
height: self.height.min(other.height),
depth: self.depth.min(other.depth),
}
}
pub fn max(self, other: Self) -> Self {
Self {
width: self.width.max(other.width),
height: self.height.max(other.height),
depth: self.depth.max(other.depth),
}
}
}
impl Sub<Offset> for Extent {
type Output = Self;
fn sub(self, rhs: Offset) -> Self::Output {
Self {
width: self.width.saturating_sub(rhs.x as u32),
height: self.height.saturating_sub(rhs.y as u32),
depth: self.depth.saturating_sub(rhs.z as u32),
}
}
}
impl Add<Offset> for Extent {
type Output = Self;
fn add(self, rhs: Offset) -> Self::Output {
Self {
width: self.width.saturating_add(rhs.x as u32),
height: self.height.saturating_add(rhs.y as u32),
depth: self.depth.saturating_add(rhs.z as u32),
}
}
}
impl From<Extent> for vk::Extent3D {
fn from(extent: Extent) -> Self {
Self {
width: extent.width,
height: extent.height,
depth: extent.depth,
}
}
}
impl From<Extent> for (u32, u32, u32) {
fn from(extent: Extent) -> Self {
(extent.width, extent.height, extent.depth)
}
}
impl From<vk::Extent3D> for Extent {
fn from(extent: vk::Extent3D) -> Self {
Self {
width: extent.width,
height: extent.height,
depth: extent.depth,
}
}
}
impl From<(u32, u32, u32)> for Extent {
fn from((width, height, depth): (u32, u32, u32)) -> Self {
Self {
width,
height,
depth,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Offset {
pub x: i32,
pub y: i32,
pub z: i32,
}
impl Offset {
pub fn min(self, other: Self) -> Self {
Self {
x: self.x.min(other.x),
y: self.y.min(other.y),
z: self.z.min(other.z),
}
}
pub fn max(self, other: Self) -> Self {
Self {
x: self.x.max(other.x),
y: self.y.max(other.y),
z: self.z.max(other.z),
}
}
}
impl Add for Offset {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
Self {
x: self.x.saturating_add(rhs.x),
y: self.y.saturating_add(rhs.y),
z: self.z.saturating_add(rhs.z),
}
}
}
impl Sub for Offset {
type Output = Self;
fn sub(self, rhs: Self) -> Self::Output {
Self {
x: self.x.saturating_sub(rhs.x),
y: self.y.saturating_sub(rhs.y),
z: self.z.saturating_sub(rhs.z),
}
}
}
impl From<Offset> for vk::Offset3D {
fn from(offset: Offset) -> Self {
Self {
x: offset.x,
y: offset.y,
z: offset.z,
}
}
}
impl From<Offset> for (i32, i32, i32) {
fn from(offset: Offset) -> Self {
(offset.x, offset.y, offset.z)
}
}
impl From<vk::Offset3D> for Offset {
fn from(offset: vk::Offset3D) -> Self {
Self {
x: offset.x,
y: offset.y,
z: offset.z,
}
}
}
impl From<(i32, i32, i32)> for Offset {
fn from((x, y, z): (i32, i32, i32)) -> Self {
Self { x, y, z }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct MipRange {
start: u32,
@ -601,8 +801,8 @@ impl Default for MipRange {
}
impl MipRange {
pub fn count(&self) -> u32 {
self.end
pub fn len(&self) -> u32 {
self.end - self.start
}
pub fn start(&self) -> u32 {
@ -613,9 +813,45 @@ impl MipRange {
self.end
}
pub fn range(&self, other: &Self) -> Self {
Self {
start: self.start.max(other.start).min(self.end),
end: self.end.min(other.end).max(self.start),
}
}
pub fn iter(&self) -> core::range::RangeIter<u32> {
self.into_iter()
}
pub fn fits_in(&self, total_mips: u32) -> bool {
self.start < total_mips && (self.end == vk::REMAINING_MIP_LEVELS || self.end <= total_mips)
}
pub fn intersects(&self, other: &Self) -> bool {
self.start < other.end && self.end > other.start
}
pub fn union(&self, other: &Self) -> Self {
Self {
start: self.start.min(other.start),
end: self.end.max(other.end),
}
}
}
impl IntoIterator for MipRange {
type Item = u32;
type IntoIter = core::range::RangeIter<u32>;
fn into_iter(self) -> Self::IntoIter {
core::range::Range {
start: self.start,
end: self.end,
}
.into_iter()
}
}
impl<R: core::ops::RangeBounds<u32>> From<R> for MipRange {
@ -1206,3 +1442,15 @@ impl From<vk::Format> for FormatClass {
}
}
}
pub fn format_to_aspect_mask(format: vk::Format) -> vk::ImageAspectFlags {
use vk::Format as F;
match format {
F::D16_UNORM | F::X8_D24_UNORM_PACK32 | F::D32_SFLOAT => vk::ImageAspectFlags::DEPTH,
F::D16_UNORM_S8_UINT | F::D24_UNORM_S8_UINT | F::D32_SFLOAT_S8_UINT => {
vk::ImageAspectFlags::DEPTH | vk::ImageAspectFlags::STENCIL
}
F::S8_UINT => vk::ImageAspectFlags::STENCIL,
_ => vk::ImageAspectFlags::COLOR,
}
}

View file

@ -45,10 +45,16 @@ pub struct Copy<T: Resource, U: Resource> {
}
impl<T: Resource, U: Resource> Copy<T, U> {
fn side_effects_inner(&self, mut map: SideEffectMap) {
self.src
.side_effect(map.reborrow(), vk::AccessFlags2::TRANSFER_READ);
self.dst
.side_effect(map.reborrow(), vk::AccessFlags2::TRANSFER_WRITE);
self.src.side_effect(
map.reborrow(),
vk::AccessFlags2::TRANSFER_READ,
Some(vk::ImageLayout::TRANSFER_SRC_OPTIMAL),
);
self.dst.side_effect(
map.reborrow(),
vk::AccessFlags2::TRANSFER_WRITE,
Some(vk::ImageLayout::TRANSFER_DST_OPTIMAL),
);
}
}
@ -61,6 +67,10 @@ impl Command for Copy<BufferRowSlice, TextureRegion> {
let cmd = recorder.cmd_buffer();
let dev = &cmd.device().raw;
// it doesn't really make sense to copy the same data into multiple mip
// levels, considering the mip extents are different for each level.
debug_assert_eq!(self.dst.range.mip_levels.len(), 1);
let regions = &[vk::BufferImageCopy2::default()
.buffer_offset(self.src.row.offset)
.buffer_row_length(self.src.row.row_size)
@ -70,9 +80,9 @@ impl Command for Copy<BufferRowSlice, TextureRegion> {
.image_subresource(
vk::ImageSubresourceLayers::default()
.aspect_mask(self.dst.range.aspect)
.mip_level(self.dst.range.mip_level)
.mip_level(self.dst.range.mip_levels.start())
.base_array_layer(self.dst.range.array_layers.start())
.layer_count(self.dst.range.array_layers.count()),
.layer_count(self.dst.range.array_layers.len()),
)];
let info = vk::CopyBufferToImageInfo2::default()
@ -111,9 +121,9 @@ impl Command for Copy<BufferRowSlices, TextureRegions> {
.image_subresource(
vk::ImageSubresourceLayers::default()
.aspect_mask(dst_range.aspect)
.mip_level(dst_range.mip_level)
.mip_level(dst_range.mip_levels.start())
.base_array_layer(dst_range.array_layers.start())
.layer_count(dst_range.array_layers.count()),
.layer_count(dst_range.array_layers.len()),
)
})
.collect::<Vec<_>>();
@ -148,9 +158,9 @@ impl Command for Copy<TextureRegion, BufferRowSlice> {
.image_subresource(
vk::ImageSubresourceLayers::default()
.aspect_mask(self.src.range.aspect)
.mip_level(self.src.range.mip_level)
.mip_level(self.src.range.mip_levels.start())
.base_array_layer(self.src.range.array_layers.start())
.layer_count(self.src.range.array_layers.count()),
.layer_count(self.src.range.array_layers.len()),
)];
let info = vk::CopyImageToBufferInfo2::default()
@ -180,16 +190,16 @@ impl Command for Copy<TextureRegion, TextureRegion> {
.dst_subresource(
vk::ImageSubresourceLayers::default()
.aspect_mask(self.dst.range.aspect)
.mip_level(self.dst.range.mip_level)
.mip_level(self.dst.range.mip_levels.start())
.base_array_layer(self.dst.range.array_layers.start())
.layer_count(self.dst.range.array_layers.count()),
.layer_count(self.dst.range.array_layers.len()),
)
.src_subresource(
vk::ImageSubresourceLayers::default()
.aspect_mask(self.src.range.aspect)
.mip_level(self.src.range.mip_level)
.mip_level(self.src.range.mip_levels.start())
.base_array_layer(self.src.range.array_layers.start())
.layer_count(self.src.range.array_layers.count()),
.layer_count(self.src.range.array_layers.len()),
)];
let info = vk::CopyImageInfo2::default()
@ -238,8 +248,11 @@ pub struct ClearTexture {
impl Command for ClearTexture {
fn side_effects(&self, mut map: SideEffectMap) {
self.dst
.side_effect(map.reborrow(), vk::AccessFlags2::TRANSFER_WRITE);
self.dst.side_effect(
map.reborrow(),
vk::AccessFlags2::TRANSFER_WRITE,
Some(vk::ImageLayout::TRANSFER_DST_OPTIMAL),
);
}
fn apply(self, _recorder: &mut CommandRecorder) {}
@ -253,7 +266,7 @@ pub struct UpdateBuffer {
impl Command for UpdateBuffer {
fn side_effects(&self, mut map: SideEffectMap) {
self.dst
.side_effect(map.reborrow(), vk::AccessFlags2::TRANSFER_WRITE);
.side_effect(map.reborrow(), vk::AccessFlags2::TRANSFER_WRITE, None);
}
fn apply(self, _recorder: &mut CommandRecorder) {}
@ -270,18 +283,24 @@ pub struct BeginRendering {
impl Command for BeginRendering {
fn side_effects(&self, mut map: SideEffectMap) {
for attachment in &self.color_attachments {
attachment.side_effect(map.reborrow(), vk::AccessFlags2::COLOR_ATTACHMENT_WRITE);
attachment.side_effect(
map.reborrow(),
vk::AccessFlags2::COLOR_ATTACHMENT_WRITE,
Some(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL),
);
}
if let Some(depth) = &self.depth_attachment {
depth.side_effect(
map.reborrow(),
vk::AccessFlags2::DEPTH_STENCIL_ATTACHMENT_WRITE,
Some(vk::ImageLayout::DEPTH_ATTACHMENT_OPTIMAL),
);
}
if let Some(stencil) = &self.stencil_attachment {
stencil.side_effect(
map.reborrow(),
vk::AccessFlags2::DEPTH_STENCIL_ATTACHMENT_WRITE,
Some(vk::ImageLayout::STENCIL_ATTACHMENT_OPTIMAL),
);
}
}
@ -316,7 +335,11 @@ pub struct BindVertexBuffers {
impl Command for BindVertexBuffers {
fn side_effects(&self, mut map: SideEffectMap) {
for buffer in &self.buffers {
buffer.side_effect(map.reborrow(), vk::AccessFlags2::VERTEX_ATTRIBUTE_READ);
buffer.side_effect(
map.reborrow(),
vk::AccessFlags2::VERTEX_ATTRIBUTE_READ,
None,
);
}
}
@ -328,7 +351,7 @@ pub struct BindIndexBuffer(pub Read<BufferSlice>, pub IndexFormat);
impl Command for BindIndexBuffer {
fn side_effects(&self, mut map: SideEffectMap) {
self.0
.side_effect(map.reborrow(), vk::AccessFlags2::INDEX_READ);
.side_effect(map.reborrow(), vk::AccessFlags2::INDEX_READ, None);
}
fn apply(self, _recorder: &mut CommandRecorder) {}
@ -431,13 +454,25 @@ impl<T: IsDrawData> Command for Draw<T> {
fn side_effects(&self, mut map: SideEffectMap) {
self.data.side_effects(map.reborrow());
for vertex_buffer in &self.vertex_buffers {
vertex_buffer.side_effect(map.reborrow(), vk::AccessFlags2::VERTEX_ATTRIBUTE_READ);
vertex_buffer.side_effect(
map.reborrow(),
vk::AccessFlags2::VERTEX_ATTRIBUTE_READ,
None,
);
}
if let Some((index_buffer, _)) = &self.index_buffer {
index_buffer.side_effect(map.reborrow(), vk::AccessFlags2::VERTEX_ATTRIBUTE_READ);
index_buffer.side_effect(
map.reborrow(),
vk::AccessFlags2::VERTEX_ATTRIBUTE_READ,
None,
);
}
if let Some(count_buffer) = &self.count_buffer {
count_buffer.side_effect(map.reborrow(), vk::AccessFlags2::VERTEX_ATTRIBUTE_READ);
count_buffer.side_effect(
map.reborrow(),
vk::AccessFlags2::VERTEX_ATTRIBUTE_READ,
None,
);
}
}

View file

@ -1,5 +1,5 @@
use std::{
collections::BTreeMap,
collections::{BTreeMap, BTreeSet, HashMap},
mem::{MaybeUninit, size_of},
ptr::NonNull,
};
@ -8,9 +8,10 @@ use ash::vk;
use crate::{
commands::CommandBuffer,
images::ImageDesc,
render_graph::{
commands::{Command, InsideRenderPass, OutsideRenderPass},
resources::{ResourceAccess, ResourceId, TextureRegion, Write},
resources::{ResourceAccess, ResourceId, TextureRegion, Write, access_flag_is_read},
},
};
@ -25,6 +26,10 @@ impl CommandRecorder {
unimplemented!()
}
pub fn get_image_desc(&self, _id: ResourceId) -> &ImageDesc {
unimplemented!()
}
pub fn get_image_handle(&self, _id: ResourceId) -> vk::Image {
unimplemented!()
}
@ -42,12 +47,50 @@ struct CommandMeta {
apply_command: unsafe fn(&mut CommandRecorder, NonNull<()>, cursor: &mut usize),
}
pub struct SideEffectMap<'a>(&'a mut BTreeMap<(ResourceId, u32), ResourceAccess>, u32);
enum VecOrSingle<T> {
Vec(Vec<T>),
Single(T),
}
impl<T> VecOrSingle<T> {
fn push(&mut self, value: T)
where
T: Default,
{
match self {
VecOrSingle::Vec(vec) => vec.push(value),
VecOrSingle::Single(existing) => {
let existing = std::mem::take(existing);
*self = VecOrSingle::Vec(vec![existing, value]);
}
}
}
fn iter(&self) -> core::slice::Iter<'_, T> {
match self {
VecOrSingle::Vec(items) => items.as_slice().iter(),
VecOrSingle::Single(item) => core::slice::from_ref(item).iter(),
}
}
}
#[derive(Default)]
struct SideEffects {
map: BTreeMap<(ResourceId, u32), VecOrSingle<ResourceAccess>>,
resources: BTreeSet<ResourceId>,
}
pub struct SideEffectMap<'a>(&'a mut SideEffects, u32);
impl<'a> SideEffectMap<'a> {
pub fn insert(&mut self, id: ResourceId, access: ResourceAccess) {
self.0.insert((id, self.1), access);
self.0.resources.insert(id);
self.0
.map
.entry((id, self.1))
.and_modify(|entry| entry.push(access))
.or_insert_with(|| VecOrSingle::Single(access));
}
pub fn reborrow(&mut self) -> SideEffectMap<'_> {
SideEffectMap(self.0, self.1)
}
@ -58,7 +101,8 @@ pub struct CommandList {
cursor: usize,
// each command that accesses a resource adds an entry to this map (id, command_index) -> access
// during command recording, we fold accesses to the same resource as commands are recorded, and when a command reads a resource, we check for any previous writes to be made available/visible
side_effects: BTreeMap<(ResourceId, u32), ResourceAccess>,
side_effects: SideEffects,
num_commands: u32,
}
pub struct RenderPass<'a> {
@ -80,7 +124,8 @@ impl CommandList {
Self {
command_bytes: Vec::new(),
cursor: 0,
side_effects: BTreeMap::new(),
side_effects: SideEffects::default(),
num_commands: 0,
}
}
@ -107,11 +152,15 @@ impl CommandList {
}
fn push_inner<C: Command>(&mut self, command: C) {
#[repr(C, packed)]
struct Packed<C> {
meta: CommandMeta,
command: C,
}
command.side_effects(SideEffectMap(&mut self.side_effects, self.num_commands));
self.num_commands += 1;
let packed = Packed {
meta: CommandMeta {
apply_command: |recorder, command_ptr, cursor| {
@ -130,4 +179,126 @@ impl CommandList {
bytes.cast::<Packed<C>>().write_unaligned(packed);
}
}
fn fold_side_effects(&mut self) {
// Buffer accesses can be granularly tracked by their offset and size;
// Image accesses are tracked by their subresource range, which means all regions of the same mip/array layer are considered the same subresource for the purposes of ImageMemoryBarriers
// But, for simplicity sake, and since buffers can already be created
// with specific sizes from device memory ranges, and aliased as
// required, we can just fold all buffer accesses together and collapse
// the size and offsets to min([offset, ..]), max([offset + size, ..])
// for all accesses to the same buffer.
// Generally, we want to be as granular as possible with barriers when
// it makes sense, but reduce the number of calls to
// vkCmdPipelineBarrier.
// Image accesses cannot be folded if they differ in: layout, access type, access stage/mask.
struct SubresourceState {
command_index: u32,
}
struct Barrier {
resource_id: ResourceId,
command_index: u32,
from: ResourceAccess,
to: usize,
}
// map to keep track of the current state of each subresource
let mut subresource_state: BTreeMap<ResourceId, Vec<(ResourceAccess, SubresourceState)>> =
Default::default();
let mut barriers = Vec::new();
for id in &self.side_effects.resources {
// check if this subresource has been accessed before, and if we can
// fold this access with the previous one
let entry = subresource_state.entry(*id).or_default();
for (command_index, accesses) in self
.side_effects
.map
.range((*id, 0)..=(*id, u32::MAX))
.map(|((_id, command_index), accesses)| (command_index, accesses))
{
for access in accesses.iter() {
let mut access = *access;
// iterate over the previous accesses to this subresource:
// - if we find an access with the same polarity, attempt to
// fold; a barrier already exists if it is needed.
// - if we find an access with a different polarity, we need to
// insert a barrier before the current command index.
// - if we find need for a layout transition, we need to insert
// a barrier before the current command index.
let mut folded = false;
let len = entry.len();
for (_i, (sub, _state)) in &mut entry.iter_mut().enumerate().rev() {
if !folded && sub.try_fold(&access) {
access = *sub;
folded = true;
} else if sub.conflicts(&access) {
// if this access conflicts with a previous one, we need
// to insert a barrier before the current command index.
// if we have managed to fold this access with a
// previous one, a barrier already exists.
// we know that `from` is correct, as the first
// conflicting access must be of opposite polarity (or a
// layout transition) and no more accesses can be folded
// into it.
// we store the index of `to`, as more accesses may be
// folded into it, expanding mips/array layers.
barriers.push(Barrier {
resource_id: *id,
command_index: *command_index,
from: *sub,
to: len,
});
// we can stop iterating over previous accesses, as we
// can no longer fold into any accesses before this
// conflict.
break;
}
}
if !folded {
// if we couldn't fold this access with any previous one, add it as a new entry
entry.push((
access,
SubresourceState {
command_index: *command_index,
},
));
}
}
}
}
// construct barriers
for barrier in barriers {
// stuff
}
}
fn record_commands(&self, recorder: &mut CommandRecorder) {
let mut cursor = 0;
while cursor < self.command_bytes.len() {
unsafe {
let meta_ptr = self
.command_bytes
.as_ptr()
.add(cursor)
.cast::<CommandMeta>();
let meta = meta_ptr.read_unaligned();
let command_ptr = meta_ptr.add(1).cast::<()>().cast_mut();
(meta.apply_command)(recorder, NonNull::new_unchecked(command_ptr), &mut cursor);
}
}
}
}

View file

@ -2,11 +2,19 @@ use std::ops::Deref;
use ash::vk;
use crate::{images::MipRange, render_graph::recorder::SideEffectMap};
use crate::{
images::{Extent, MipRange, Offset},
render_graph::recorder::SideEffectMap,
};
pub trait Resource: Sized {
fn id(&self) -> ResourceId;
fn side_effect(&self, map: SideEffectMap, access: vk::AccessFlags2);
fn side_effect(
&self,
map: SideEffectMap,
access: vk::AccessFlags2,
layout: Option<vk::ImageLayout>,
);
}
mod impls {
@ -17,7 +25,12 @@ mod impls {
self.0
}
fn side_effect(&self, mut map: SideEffectMap, access: vk::AccessFlags2) {
fn side_effect(
&self,
mut map: SideEffectMap,
access: vk::AccessFlags2,
_layout: Option<vk::ImageLayout>,
) {
map.insert(
self.id(),
ResourceAccess {
@ -33,7 +46,12 @@ mod impls {
self.buffer.id()
}
fn side_effect(&self, mut map: SideEffectMap, access: vk::AccessFlags2) {
fn side_effect(
&self,
mut map: SideEffectMap,
access: vk::AccessFlags2,
_layout: Option<vk::ImageLayout>,
) {
map.insert(
self.id(),
ResourceAccess {
@ -48,7 +66,12 @@ mod impls {
self.buffer.id()
}
fn side_effect(&self, mut map: SideEffectMap, access: vk::AccessFlags2) {
fn side_effect(
&self,
mut map: SideEffectMap,
access: vk::AccessFlags2,
_layout: Option<vk::ImageLayout>,
) {
for range in &self.ranges {
map.insert(
self.id(),
@ -65,7 +88,12 @@ mod impls {
self.buffer.id()
}
fn side_effect(&self, mut map: SideEffectMap, access: vk::AccessFlags2) {
fn side_effect(
&self,
mut map: SideEffectMap,
access: vk::AccessFlags2,
_layout: Option<vk::ImageLayout>,
) {
map.insert(
self.id(),
ResourceAccess {
@ -84,7 +112,12 @@ mod impls {
self.buffer.id()
}
fn side_effect(&self, mut map: SideEffectMap, access: vk::AccessFlags2) {
fn side_effect(
&self,
mut map: SideEffectMap,
access: vk::AccessFlags2,
_layout: Option<vk::ImageLayout>,
) {
for row in &self.rows {
map.insert(
self.id(),
@ -106,11 +139,16 @@ mod impls {
self.0
}
fn side_effect(&self, mut map: SideEffectMap, access: vk::AccessFlags2) {
fn side_effect(
&self,
mut map: SideEffectMap,
access: vk::AccessFlags2,
layout: Option<vk::ImageLayout>,
) {
map.insert(
self.id(),
ResourceAccess {
range: TextureRange::full().into(),
range: (TextureRange::full(), layout.unwrap_or_default()).into(),
access,
},
);
@ -121,11 +159,16 @@ mod impls {
self.texture.id()
}
fn side_effect(&self, mut map: SideEffectMap, access: vk::AccessFlags2) {
fn side_effect(
&self,
mut map: SideEffectMap,
access: vk::AccessFlags2,
layout: Option<vk::ImageLayout>,
) {
map.insert(
self.id(),
ResourceAccess {
range: self.range.into(),
range: (self.range, layout.unwrap_or_default()).into(),
access,
},
);
@ -136,12 +179,17 @@ mod impls {
self.texture.id()
}
fn side_effect(&self, mut map: SideEffectMap, access: vk::AccessFlags2) {
fn side_effect(
&self,
mut map: SideEffectMap,
access: vk::AccessFlags2,
layout: Option<vk::ImageLayout>,
) {
for range in &self.ranges {
map.insert(
self.id(),
ResourceAccess {
range: (*range).into(),
range: (*range, layout.unwrap_or_default()).into(),
access,
},
);
@ -154,8 +202,13 @@ mod impls {
self.0.id()
}
fn side_effect(&self, map: SideEffectMap, access: vk::AccessFlags2) {
self.0.side_effect(map, access);
fn side_effect(
&self,
map: SideEffectMap,
access: vk::AccessFlags2,
layout: Option<vk::ImageLayout>,
) {
self.0.side_effect(map, access, layout);
}
}
impl<T: Resource> Resource for Write<T> {
@ -163,8 +216,13 @@ mod impls {
self.0.id()
}
fn side_effect(&self, map: SideEffectMap, access: vk::AccessFlags2) {
self.0.side_effect(map, access);
fn side_effect(
&self,
map: SideEffectMap,
access: vk::AccessFlags2,
layout: Option<vk::ImageLayout>,
) {
self.0.side_effect(map, access, layout);
}
}
impl<T: Resource> Resource for ReadWrite<T> {
@ -172,8 +230,13 @@ mod impls {
self.0.id()
}
fn side_effect(&self, map: SideEffectMap, access: vk::AccessFlags2) {
self.0.side_effect(map, access);
fn side_effect(
&self,
map: SideEffectMap,
access: vk::AccessFlags2,
layout: Option<vk::ImageLayout>,
) {
self.0.side_effect(map, access, layout);
}
}
}
@ -184,11 +247,131 @@ pub struct DescriptorSet;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct ResourceId(u32);
#[derive(Debug, Default, Clone, Copy)]
pub struct ResourceAccess {
pub range: ResourceRange,
pub access: vk::AccessFlags2,
}
impl ResourceAccess {
/// Attempts to fold another ResourceAccess into this one. Returns true if
/// successful, false if they are incompatible.
pub fn try_fold(&mut self, other: &Self) -> bool {
if self.access != other.access {
return false;
}
match (&mut self.range, &other.range) {
(ResourceRange::Buffer { .. }, ResourceRange::Texture { .. }) => false,
(ResourceRange::Texture { .. }, ResourceRange::Buffer { .. }) => false,
(
ResourceRange::Buffer { offset, size },
ResourceRange::Buffer {
offset: offset_b,
size: size_b,
},
) => {
// only fold if other lies within self
if *offset > *offset_b || *offset + *size < *offset_b + *size_b {
return false;
}
true
}
(
ResourceRange::Texture {
aspect,
mip_level,
array_layers,
layout,
..
},
ResourceRange::Texture {
aspect: aspect_b,
mip_level: mip_level_b,
array_layers: array_layers_b,
layout: layout_b,
..
},
) => {
if aspect != aspect_b
|| layout != layout_b
|| !mip_level.intersects(mip_level_b)
|| !array_layers.intersects(array_layers_b)
{
return false;
}
*mip_level = mip_level.union(mip_level_b);
*array_layers = array_layers.union(array_layers_b);
// let lower_left = Offset::from(*origin).min(Offset::from(*origin_b));
// let upper_right = (Offset::from(*origin) + Extent::from(*extent).as_offset())
// .max(Offset::from(*origin_b) + Extent::from(*extent_b).as_offset());
// *origin = lower_left.into();
// *extent = Extent::from_offset(upper_right - lower_left).into();
true
}
}
}
pub fn conflicts(&self, other: &Self) -> bool {
match (&self.range, &other.range) {
(ResourceRange::Buffer { .. }, ResourceRange::Texture { .. }) => false,
(ResourceRange::Texture { .. }, ResourceRange::Buffer { .. }) => false,
(
ResourceRange::Buffer { offset, size },
ResourceRange::Buffer {
offset: offset_b,
size: size_b,
},
) => {
// two reads don't conflict
// TODO: two writes may conflict if the user cares about the order of writes.
if access_flag_is_read(self.access) == access_flag_is_read(other.access) {
return false;
}
// must be a read/write conflict, check if subresources intersect
!(*offset > *offset_b + *size_b || *offset_b > *offset + *size)
}
(
ResourceRange::Texture {
aspect,
mip_level,
array_layers,
layout,
..
},
ResourceRange::Texture {
aspect: aspect_b,
mip_level: mip_level_b,
array_layers: array_layers_b,
layout: layout_b,
..
},
) => {
// layouts differing means we need a layout transition, which is a conflict
if layout != layout_b {
return true;
}
// two reads don't conflict
// TODO: two writes may conflict if the user cares about the order of writes.
if access_flag_is_read(self.access) == access_flag_is_read(other.access) {
return false;
}
// must be a read/write conflict, check if subresources intersect
aspect.intersects(*aspect_b)
&& (mip_level.intersects(mip_level_b)
|| array_layers.intersects(array_layers_b))
}
}
}
}
#[derive(Debug, Clone, Copy)]
pub enum ResourceRange {
Buffer {
offset: u64,
@ -196,14 +379,24 @@ pub enum ResourceRange {
},
Texture {
aspect: vk::ImageAspectFlags,
mip_level: u32,
mip_level: MipRange,
array_layers: MipRange,
origin: (i32, i32, i32),
extent: (u32, u32, u32),
layout: vk::ImageLayout,
},
}
#[derive(Debug, Clone, Copy)]
impl Default for ResourceRange {
fn default() -> Self {
ResourceRange::Buffer {
offset: 0,
size: u64::MAX,
}
}
}
#[derive(Debug, Default, Clone, Copy)]
pub struct BufferRange {
pub offset: u64,
pub size: u64,
@ -218,17 +411,17 @@ impl BufferRange {
}
}
#[derive(Debug, Clone, Copy)]
#[derive(Debug, Default, Clone, Copy)]
pub struct BufferRows {
pub offset: u64,
pub row_count: u32,
pub row_size: u32,
}
#[derive(Debug, Clone, Copy)]
#[derive(Debug, Default, Clone, Copy)]
pub struct TextureRange {
pub aspect: vk::ImageAspectFlags,
pub mip_level: u32,
pub mip_levels: MipRange,
pub array_layers: MipRange,
pub origin: (i32, i32, i32),
pub extent: (u32, u32, u32),
@ -251,14 +444,15 @@ impl TextureRange {
}
}
impl From<TextureRange> for ResourceRange {
fn from(value: TextureRange) -> Self {
impl From<(TextureRange, vk::ImageLayout)> for ResourceRange {
fn from((value, layout): (TextureRange, vk::ImageLayout)) -> Self {
ResourceRange::Texture {
aspect: value.aspect,
mip_level: value.mip_level,
mip_level: value.mip_levels,
array_layers: value.array_layers,
origin: value.origin,
extent: value.extent,
layout,
}
}
}
@ -278,7 +472,7 @@ impl TextureRange {
aspect: vk::ImageAspectFlags::COLOR
| vk::ImageAspectFlags::DEPTH
| vk::ImageAspectFlags::STENCIL,
mip_level: 0,
mip_levels: MipRange::default(),
array_layers: MipRange::default(),
origin: (0, 0, 0),
extent: (u32::MAX, u32::MAX, u32::MAX),
@ -346,3 +540,34 @@ impl<T> Deref for ReadWrite<T> {
&self.0
}
}
pub fn access_flag_is_read(access: vk::AccessFlags2) -> bool {
access.intersects(
vk::AccessFlags2::INDIRECT_COMMAND_READ
| vk::AccessFlags2::INDEX_READ
| vk::AccessFlags2::VERTEX_ATTRIBUTE_READ
| vk::AccessFlags2::UNIFORM_READ
| vk::AccessFlags2::INPUT_ATTACHMENT_READ
| vk::AccessFlags2::SHADER_READ
| vk::AccessFlags2::COLOR_ATTACHMENT_READ
| vk::AccessFlags2::DEPTH_STENCIL_ATTACHMENT_READ
| vk::AccessFlags2::TRANSFER_READ
| vk::AccessFlags2::HOST_READ
| vk::AccessFlags2::MEMORY_READ
| vk::AccessFlags2::SHADER_SAMPLED_READ
| vk::AccessFlags2::SHADER_STORAGE_READ
| vk::AccessFlags2::VIDEO_DECODE_READ_KHR
| vk::AccessFlags2::VIDEO_ENCODE_READ_KHR
| vk::AccessFlags2::MICROMAP_READ_EXT
| vk::AccessFlags2::ACCELERATION_STRUCTURE_READ_KHR
| vk::AccessFlags2::FRAGMENT_DENSITY_MAP_READ_EXT
| vk::AccessFlags2::FRAGMENT_SHADING_RATE_ATTACHMENT_READ_KHR
| vk::AccessFlags2::CONDITIONAL_RENDERING_READ_EXT
| vk::AccessFlags2::SHADER_BINDING_TABLE_READ_KHR
| vk::AccessFlags2::DESCRIPTOR_BUFFER_READ_EXT
| vk::AccessFlags2::COLOR_ATTACHMENT_READ_NONCOHERENT_EXT
| vk::AccessFlags2::COMMAND_PREPROCESS_READ_NV
| vk::AccessFlags2::TRANSFORM_FEEDBACK_COUNTER_READ_EXT
| vk::AccessFlags2::INVOCATION_MASK_READ_HUAWEI,
)
}