From d367b4d05b21cc4b4a7aaff6a2741a4697c8c486 Mon Sep 17 00:00:00 2001 From: janis Date: Sat, 24 Oct 2026 23:05:51 +0200 Subject: [PATCH] use renderer_resources --- crates/renderer_rendergraph/Cargo.toml | 2 + crates/renderer_rendergraph/src/graph.rs | 188 +++++++++++++++---- crates/renderer_rendergraph/src/recorder.rs | 5 +- crates/renderer_rendergraph/src/resources.rs | 34 ++-- 4 files changed, 174 insertions(+), 55 deletions(-) diff --git a/crates/renderer_rendergraph/Cargo.toml b/crates/renderer_rendergraph/Cargo.toml index 8471f90..e405adf 100644 --- a/crates/renderer_rendergraph/Cargo.toml +++ b/crates/renderer_rendergraph/Cargo.toml @@ -5,3 +5,5 @@ edition = "2024" [dependencies] ash.workspace = true +renderer_resources = { path = "../renderer_resources" } +ash-formats = { git = "https://github.com/janis-bhm/ash-formats", branch = "main" } diff --git a/crates/renderer_rendergraph/src/graph.rs b/crates/renderer_rendergraph/src/graph.rs index 608d6f3..bfe16cb 100644 --- a/crates/renderer_rendergraph/src/graph.rs +++ b/crates/renderer_rendergraph/src/graph.rs @@ -1,6 +1,13 @@ -use std::collections::{BTreeMap, BTreeSet}; +use std::{ + collections::{BTreeMap, BTreeSet, HashMap}, + sync::Arc, +}; use ash::vk; +use ash_formats::FormatExt; +use renderer_resources::{ + Device, buffers::Buffer as BufferResource, images::Image as ImageResource, +}; use crate::{ recorder::{CommandIndex, CommandList, CommandRecorder, RecordError, scope_range}, @@ -65,28 +72,34 @@ impl RenderGraph { id } - pub fn create_buffer(&mut self, desc: crate::resources::BufferDesc) -> Buffer { + pub fn create_buffer(&mut self, desc: renderer_resources::buffers::BufferDesc) -> Buffer { Buffer(self.add_resource(ResourceDesc::CreatedBuffer(desc))) } - pub fn create_texture(&mut self, desc: crate::resources::TextureDesc) -> Texture { + pub fn create_texture(&mut self, desc: renderer_resources::images::ImageDesc) -> Texture { Texture(self.add_resource(ResourceDesc::CreatedTexture(desc))) } pub fn import_buffer( &mut self, - handle: vk::Buffer, + resource: impl Into>, initial: crate::resources::ResourceState, ) -> Buffer { - Buffer(self.add_resource(ResourceDesc::ImportedBuffer { handle, initial })) + Buffer(self.add_resource(ResourceDesc::ImportedBuffer { + resource: resource.into(), + initial, + })) } pub fn import_texture( &mut self, - handle: vk::Image, + resource: impl Into>, initial: crate::resources::ResourceState, ) -> Texture { - Texture(self.add_resource(ResourceDesc::ImportedTexture { handle, initial })) + Texture(self.add_resource(ResourceDesc::ImportedTexture { + resource: resource.into(), + initial, + })) } pub fn push(&mut self, command: C) -> CommandIndex { @@ -144,6 +157,7 @@ impl RenderGraph { Ok(CompiledGraph { resources: self.resources, + resolved: HashMap::new(), commands: self.commands, order, barriers, @@ -160,10 +174,16 @@ impl Default for RenderGraph { pub struct CompiledGraph { resources: BTreeMap, + resolved: HashMap, commands: CommandList, order: Vec, barriers: Vec, - lifetimes: BTreeMap, + lifetimes: HashMap, +} + +enum ResolvedResource { + Buffer(Arc), + Image(Arc), } impl CompiledGraph { @@ -175,27 +195,64 @@ impl CompiledGraph { &self.barriers } - pub fn resource_lifetimes(&self) -> &BTreeMap { + pub fn resource_lifetimes(&self) -> &HashMap { &self.lifetimes } + /// Create the graph's transient resources. Compilation remains independent + /// of a Vulkan device, while command recording requires this resolution step + /// for any created resources used by commands. + pub fn resolve(&mut self, device: &Device) -> renderer_resources::Result<()> { + for (&id, desc) in &self.resources { + if self.resolved.contains_key(&id) { + continue; + } + let resource = match desc { + ResourceDesc::CreatedBuffer(desc) => ResolvedResource::Buffer(Arc::new( + BufferResource::new(device.clone(), desc.clone())?, + )), + ResourceDesc::CreatedTexture(desc) => ResolvedResource::Image(Arc::new( + ImageResource::new(device.clone(), desc.clone())?, + )), + ResourceDesc::ImportedBuffer { .. } | ResourceDesc::ImportedTexture { .. } => { + continue; + } + }; + self.resolved.insert(id, resource); + } + Ok(()) + } + /// Record the compiled order into a real Vulkan command buffer. Imported - /// handles are automatically bound. Created resources require a future - /// allocator integration and currently return a missing-handle error when - /// used by a command. + /// handles are automatically bound. Created resources must first be + /// resolved with a device. pub fn record_commands( &mut self, recorder: &mut CommandRecorder<'_>, ) -> Result<(), RecordError> { - for (&id, &desc) in &self.resources { + for (&id, desc) in &self.resources { + if !self.lifetimes.contains_key(&id) { + continue; + } match desc { - ResourceDesc::ImportedBuffer { handle, .. } => { - recorder.buffers.insert(id, handle); + ResourceDesc::ImportedBuffer { resource, .. } => { + recorder.buffers.insert(id, resource.raw()); } - ResourceDesc::ImportedTexture { handle, .. } => { - recorder.images.insert(id, handle); + ResourceDesc::ImportedTexture { resource, .. } => { + recorder.images.insert(id, resource.raw()); } - ResourceDesc::CreatedBuffer(_) | ResourceDesc::CreatedTexture(_) => {} + ResourceDesc::CreatedBuffer(_) => match self.resolved.get(&id) { + Some(ResolvedResource::Buffer(resource)) => { + recorder.buffers.insert(id, resource.raw()); + } + _ => return Err(RecordError::UnresolvedCreatedBuffer(id)), + }, + ResourceDesc::CreatedTexture(_) => match self.resolved.get(&id) { + Some(ResolvedResource::Image(resource)) => { + recorder.images.insert(id, resource.raw()); + } + _ => return Err(RecordError::UnresolvedCreatedImage(id)), + }, } } @@ -245,7 +302,7 @@ fn build_barriers( continue; } - let desc = resource_descs[¤t.resource]; + let desc = &resource_descs[¤t.resource]; let initial = desc.initial_state(); let (old_layout, new_layout) = layouts_from_state(initial, current.scope); let layout_transition = old_layout != new_layout && new_layout.is_some(); @@ -303,8 +360,8 @@ fn layouts_from_state( fn bind_resources( resources: &BTreeMap, accesses: &[Vec], -) -> BTreeMap { - let mut lifetimes = BTreeMap::new(); +) -> HashMap { + let mut lifetimes = HashMap::new(); for (command, command_accesses) in accesses.iter().enumerate() { for access in command_accesses { lifetimes @@ -328,12 +385,19 @@ fn bind_resources( ids.sort_by_key(|id| lifetimes[id].first_command); for id in ids { let lifetime = lifetimes.get_mut(&id).expect("lifetime key exists"); - let size = match resources[&id] { + let size = match &resources[&id] { ResourceDesc::CreatedBuffer(desc) => desc.size, - ResourceDesc::CreatedTexture(desc) => u64::from(desc.extent.width) - .saturating_mul(u64::from(desc.extent.height)) - .saturating_mul(u64::from(desc.extent.depth)) - .saturating_mul(4), + ResourceDesc::CreatedTexture(desc) => { + let texels_per_block = u64::from(desc.format.texels_per_block()).max(1); + let block_size = u64::from(desc.format.block_size()); + u64::from(desc.extent.width) + .saturating_mul(u64::from(desc.extent.height)) + .saturating_mul(u64::from(desc.extent.depth)) + .div_ceil(texels_per_block) + .saturating_mul(block_size) + .saturating_mul(u64::from(desc.array_layers)) + .saturating_mul(u64::from(desc.samples.as_raw().max(1))) + } ResourceDesc::ImportedBuffer { .. } | ResourceDesc::ImportedTexture { .. } => 0, }; if resources[&id].is_created() @@ -358,18 +422,23 @@ fn bind_resources( #[cfg(test)] mod tests { use super::*; - use crate::{commands::CopyBuffer, resources::BufferRange}; + use crate::{ + commands::{ClearTexture, CopyBuffer}, + resources::BufferRange, + }; #[test] fn copy_buffer_compiles_with_dependency_and_barrier() { let mut graph = RenderGraph::new(); - let src = graph.create_buffer(crate::resources::BufferDesc { + let src = graph.create_buffer(renderer_resources::buffers::BufferDesc { size: 64, usage: vk::BufferUsageFlags::TRANSFER_SRC, + ..Default::default() }); - let dst = graph.create_buffer(crate::resources::BufferDesc { + let dst = graph.create_buffer(renderer_resources::buffers::BufferDesc { size: 64, usage: vk::BufferUsageFlags::TRANSFER_DST, + ..Default::default() }); graph.push(CopyBuffer { src, @@ -393,17 +462,20 @@ mod tests { #[test] fn write_then_read_creates_order_and_barrier() { let mut graph = RenderGraph::new(); - let a = graph.create_buffer(crate::resources::BufferDesc { + let a = graph.create_buffer(renderer_resources::buffers::BufferDesc { size: 32, usage: vk::BufferUsageFlags::TRANSFER_SRC | vk::BufferUsageFlags::TRANSFER_DST, + ..Default::default() }); - let b = graph.create_buffer(crate::resources::BufferDesc { + let b = graph.create_buffer(renderer_resources::buffers::BufferDesc { size: 32, usage: vk::BufferUsageFlags::TRANSFER_DST, + ..Default::default() }); - let c = graph.create_buffer(crate::resources::BufferDesc { + let c = graph.create_buffer(renderer_resources::buffers::BufferDesc { size: 32, usage: vk::BufferUsageFlags::TRANSFER_DST, + ..Default::default() }); graph.push(CopyBuffer { src: a, @@ -439,4 +511,56 @@ mod tests { assert_eq!(compiled.barriers()[0].before_command, CommandIndex(1)); assert_eq!(compiled.barriers()[0].resources[0].id, b.id()); } + + #[test] + fn texture_aliasing_size_uses_format_block_size() { + let mut graph = RenderGraph::new(); + let texture = graph.create_texture(renderer_resources::images::ImageDesc { + format: vk::Format::R8G8B8A8_UNORM, + extent: vk::Extent3D { + width: 4, + height: 4, + depth: 1, + }, + usage: vk::ImageUsageFlags::TRANSFER_DST, + ..Default::default() + }); + let buffer = graph.create_buffer(renderer_resources::buffers::BufferDesc { + size: 8, + usage: vk::BufferUsageFlags::TRANSFER_DST, + ..Default::default() + }); + graph.push(ClearTexture { + texture, + range: vk::ImageSubresourceRange { + aspect_mask: vk::ImageAspectFlags::COLOR, + base_mip_level: 0, + level_count: 1, + base_array_layer: 0, + layer_count: 1, + }, + layout: vk::ImageLayout::TRANSFER_DST_OPTIMAL, + color: [0.0; 4], + }); + // Keep the buffer live in the same command, so its slot follows the image. + graph.commands.side_effects.insert( + CommandIndex(0), + buffer.id(), + SubresourceScope { + stage: vk::PipelineStageFlags2::CLEAR, + access: vk::AccessFlags2::TRANSFER_WRITE, + subresource: SubresourceScopeInner::Buffer(BufferRange { offset: 0, size: 8 }), + }, + ); + + let compiled = graph.compile().expect("graph compiles"); + assert_eq!( + compiled.resource_lifetimes()[&texture.id()].memory_offset, + 0 + ); + assert_eq!( + compiled.resource_lifetimes()[&buffer.id()].memory_offset, + 64 + ); + } } diff --git a/crates/renderer_rendergraph/src/recorder.rs b/crates/renderer_rendergraph/src/recorder.rs index a3dfeb5..1c0de16 100644 --- a/crates/renderer_rendergraph/src/recorder.rs +++ b/crates/renderer_rendergraph/src/recorder.rs @@ -121,10 +121,11 @@ impl Default for CommandList { pub enum RecordError { MissingBuffer(ResourceId), MissingImage(ResourceId), + UnresolvedCreatedBuffer(ResourceId), + UnresolvedCreatedImage(ResourceId), } -/// Vulkan recording context. Handle maps are populated for imported resources; -/// transient allocation is intentionally left to a later allocator integration. +/// Vulkan recording context. Handle maps are populated by the compiled graph. pub struct CommandRecorder<'a> { pub device: &'a ash::Device, pub command_buffer: vk::CommandBuffer, diff --git a/crates/renderer_rendergraph/src/resources.rs b/crates/renderer_rendergraph/src/resources.rs index 478af4f..2e77352 100644 --- a/crates/renderer_rendergraph/src/resources.rs +++ b/crates/renderer_rendergraph/src/resources.rs @@ -1,4 +1,7 @@ +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)] @@ -190,19 +193,6 @@ pub fn access_flag_is_write(access: vk::AccessFlags2) -> bool { ) } -#[derive(Debug, Clone, Copy)] -pub struct BufferDesc { - pub size: u64, - pub usage: vk::BufferUsageFlags, -} - -#[derive(Debug, Clone, Copy)] -pub struct TextureDesc { - pub format: vk::Format, - pub extent: vk::Extent3D, - pub usage: vk::ImageUsageFlags, -} - #[derive(Debug, Clone, Copy)] pub struct ResourceState { pub stage: vk::PipelineStageFlags2, @@ -220,29 +210,31 @@ impl Default for ResourceState { } } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone)] pub enum ResourceDesc { - CreatedBuffer(BufferDesc), - CreatedTexture(TextureDesc), + CreatedBuffer(renderer_resources::buffers::BufferDesc), + CreatedTexture(renderer_resources::images::ImageDesc), ImportedBuffer { - handle: vk::Buffer, + resource: Arc, initial: ResourceState, }, ImportedTexture { - handle: vk::Image, + resource: Arc, initial: ResourceState, }, } impl ResourceDesc { - pub fn initial_state(self) -> ResourceState { + pub fn initial_state(&self) -> ResourceState { match self { - Self::ImportedBuffer { initial, .. } | Self::ImportedTexture { initial, .. } => initial, + Self::ImportedBuffer { initial, .. } | Self::ImportedTexture { initial, .. } => { + *initial + } Self::CreatedBuffer(_) | Self::CreatedTexture(_) => ResourceState::default(), } } - pub fn is_created(self) -> bool { + pub fn is_created(&self) -> bool { matches!(self, Self::CreatedBuffer(_) | Self::CreatedTexture(_)) } }