From 7acda4d1fb5f19bfcef781c0bc1f868050d50560 Mon Sep 17 00:00:00 2001 From: janis Date: Sun, 12 Apr 2026 14:26:02 +0200 Subject: [PATCH] automatic barriers --- crates/renderer/src/images.rs | 258 +++++++++++++++- crates/renderer/src/render_graph/commands.rs | 81 +++-- crates/renderer/src/render_graph/recorder.rs | 183 +++++++++++- crates/renderer/src/render_graph/resources.rs | 281 ++++++++++++++++-- 4 files changed, 741 insertions(+), 62 deletions(-) diff --git a/crates/renderer/src/images.rs b/crates/renderer/src/images.rs index ec40427..7a870bc 100644 --- a/crates/renderer/src/images.rs +++ b/crates/renderer/src/images.rs @@ -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(&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 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 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 for vk::Extent3D { + fn from(extent: Extent) -> Self { + Self { + width: extent.width, + height: extent.height, + depth: extent.depth, + } + } +} + +impl From for (u32, u32, u32) { + fn from(extent: Extent) -> Self { + (extent.width, extent.height, extent.depth) + } +} + +impl From 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 for vk::Offset3D { + fn from(offset: Offset) -> Self { + Self { + x: offset.x, + y: offset.y, + z: offset.z, + } + } +} + +impl From for (i32, i32, i32) { + fn from(offset: Offset) -> Self { + (offset.x, offset.y, offset.z) + } +} + +impl From 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 { + 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; + + fn into_iter(self) -> Self::IntoIter { + core::range::Range { + start: self.start, + end: self.end, + } + .into_iter() + } } impl> From for MipRange { @@ -1206,3 +1442,15 @@ impl From 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, + } +} diff --git a/crates/renderer/src/render_graph/commands.rs b/crates/renderer/src/render_graph/commands.rs index 9087e3c..959fe5a 100644 --- a/crates/renderer/src/render_graph/commands.rs +++ b/crates/renderer/src/render_graph/commands.rs @@ -45,10 +45,16 @@ pub struct Copy { } impl Copy { 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 { 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 { .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 { .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::>(); @@ -148,9 +158,9 @@ impl Command for Copy { .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 { .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, 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 Command for Draw { 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, + ); } } diff --git a/crates/renderer/src/render_graph/recorder.rs b/crates/renderer/src/render_graph/recorder.rs index dc814ea..bb79ef3 100644 --- a/crates/renderer/src/render_graph/recorder.rs +++ b/crates/renderer/src/render_graph/recorder.rs @@ -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 { + Vec(Vec), + Single(T), +} + +impl VecOrSingle { + 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>, + resources: BTreeSet, +} + +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(&mut self, command: C) { + #[repr(C, packed)] struct Packed { 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::>().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> = + 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::(); + 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); + } + } + } } diff --git a/crates/renderer/src/render_graph/resources.rs b/crates/renderer/src/render_graph/resources.rs index 9352b92..c9892a8 100644 --- a/crates/renderer/src/render_graph/resources.rs +++ b/crates/renderer/src/render_graph/resources.rs @@ -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, + ); } 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, + ) { 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, + ) { 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, + ) { 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, + ) { 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, + ) { 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, + ) { 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, + ) { 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, + ) { 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, + ) { + self.0.side_effect(map, access, layout); } } impl Resource for Write { @@ -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, + ) { + self.0.side_effect(map, access, layout); } } impl Resource for ReadWrite { @@ -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, + ) { + 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 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 Deref for ReadWrite { &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, + ) +}