vidya/crates/renderer_rendergraph/src/resources.rs
2026-10-24 23:05:51 +02:00

241 lines
7.3 KiB
Rust

use std::sync::Arc;
use ash::vk;
use renderer_resources::{buffers::Buffer as BufferResource, images::Image as ImageResource};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
pub struct ResourceId(pub u64);
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Buffer(pub ResourceId);
impl Buffer {
pub fn id(self) -> ResourceId {
self.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Texture(pub ResourceId);
impl Texture {
pub fn id(self) -> ResourceId {
self.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BufferRange {
pub offset: u64,
pub size: u64,
}
impl BufferRange {
pub const fn full() -> Self {
Self {
offset: 0,
size: vk::WHOLE_SIZE,
}
}
pub fn end(self) -> u64 {
if self.size == vk::WHOLE_SIZE {
u64::MAX
} else {
self.offset.saturating_add(self.size)
}
}
pub fn overlaps(self, other: Self) -> bool {
self.offset < other.end() && other.offset < self.end()
}
}
#[derive(Debug, Clone, Copy)]
pub enum Subresource {
Buffer(BufferRange),
Texture(vk::ImageSubresourceRange),
}
#[derive(Debug, Clone, Copy)]
pub enum SubresourceScopeInner {
Buffer(BufferRange),
Texture {
range: vk::ImageSubresourceRange,
layout: vk::ImageLayout,
},
}
#[derive(Debug, Clone, Copy)]
pub struct SubresourceScope {
pub access: vk::AccessFlags2,
pub stage: vk::PipelineStageFlags2,
pub subresource: SubresourceScopeInner,
}
impl SubresourceScope {
/// Fold identical ranges with compatible access polarity into one scope.
pub fn try_merge(&mut self, other: &Self) -> bool {
if access_flag_is_read(self.access) != access_flag_is_read(other.access)
|| access_flag_is_write(self.access) != access_flag_is_write(other.access)
{
return false;
}
let same_range = match (self.subresource, other.subresource) {
(SubresourceScopeInner::Buffer(a), SubresourceScopeInner::Buffer(b)) => a == b,
(
SubresourceScopeInner::Texture {
range: a,
layout: layout_a,
},
SubresourceScopeInner::Texture {
range: b,
layout: layout_b,
},
) => texture_ranges_equal(a, b) && layout_a == layout_b,
_ => false,
};
if same_range {
self.stage |= other.stage;
self.access |= other.access;
}
same_range
}
/// Whether accesses overlap and require ordering (a write or layout change).
pub fn conflicts(&self, other: &Self) -> bool {
let overlaps = match (self.subresource, other.subresource) {
(SubresourceScopeInner::Buffer(a), SubresourceScopeInner::Buffer(b)) => a.overlaps(b),
(
SubresourceScopeInner::Texture { range: a, .. },
SubresourceScopeInner::Texture { range: b, .. },
) => texture_ranges_overlap(a, b),
_ => false,
};
if !overlaps {
return false;
}
match (self.subresource, other.subresource) {
(
SubresourceScopeInner::Texture { layout: a, .. },
SubresourceScopeInner::Texture { layout: b, .. },
) if a != b => true,
_ => {
!(access_flag_is_read(self.access)
&& !access_flag_is_write(self.access)
&& access_flag_is_read(other.access)
&& !access_flag_is_write(other.access))
}
}
}
}
fn texture_ranges_equal(a: vk::ImageSubresourceRange, b: vk::ImageSubresourceRange) -> bool {
a.aspect_mask == b.aspect_mask
&& a.base_mip_level == b.base_mip_level
&& a.level_count == b.level_count
&& a.base_array_layer == b.base_array_layer
&& a.layer_count == b.layer_count
}
fn range_end(base: u32, count: u32) -> u64 {
if count == vk::REMAINING_MIP_LEVELS || count == vk::REMAINING_ARRAY_LAYERS {
u64::MAX
} else {
u64::from(base) + u64::from(count)
}
}
fn texture_ranges_overlap(a: vk::ImageSubresourceRange, b: vk::ImageSubresourceRange) -> bool {
a.aspect_mask.intersects(b.aspect_mask)
&& u64::from(a.base_mip_level) < range_end(b.base_mip_level, b.level_count)
&& u64::from(b.base_mip_level) < range_end(a.base_mip_level, a.level_count)
&& u64::from(a.base_array_layer) < range_end(b.base_array_layer, b.layer_count)
&& u64::from(b.base_array_layer) < range_end(a.base_array_layer, a.layer_count)
}
/// True when an access mask contains a Vulkan read access.
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::ACCELERATION_STRUCTURE_READ_KHR,
)
}
/// True when an access mask contains a Vulkan write access.
pub fn access_flag_is_write(access: vk::AccessFlags2) -> bool {
access.intersects(
vk::AccessFlags2::SHADER_WRITE
| vk::AccessFlags2::COLOR_ATTACHMENT_WRITE
| vk::AccessFlags2::DEPTH_STENCIL_ATTACHMENT_WRITE
| vk::AccessFlags2::TRANSFER_WRITE
| vk::AccessFlags2::HOST_WRITE
| vk::AccessFlags2::MEMORY_WRITE
| vk::AccessFlags2::SHADER_STORAGE_WRITE
| vk::AccessFlags2::ACCELERATION_STRUCTURE_WRITE_KHR,
)
}
#[derive(Debug, Clone, Copy)]
pub struct ResourceState {
pub stage: vk::PipelineStageFlags2,
pub access: vk::AccessFlags2,
pub layout: Option<vk::ImageLayout>,
}
impl Default for ResourceState {
fn default() -> Self {
Self {
stage: vk::PipelineStageFlags2::NONE,
access: vk::AccessFlags2::NONE,
layout: None,
}
}
}
#[derive(Debug, Clone)]
pub enum ResourceDesc {
CreatedBuffer(renderer_resources::buffers::BufferDesc),
CreatedTexture(renderer_resources::images::ImageDesc),
ImportedBuffer {
resource: Arc<BufferResource>,
initial: ResourceState,
},
ImportedTexture {
resource: Arc<ImageResource>,
initial: ResourceState,
},
}
impl ResourceDesc {
pub fn initial_state(&self) -> ResourceState {
match self {
Self::ImportedBuffer { initial, .. } | Self::ImportedTexture { initial, .. } => {
*initial
}
Self::CreatedBuffer(_) | Self::CreatedTexture(_) => ResourceState::default(),
}
}
pub fn is_created(&self) -> bool {
matches!(self, Self::CreatedBuffer(_) | Self::CreatedTexture(_))
}
}