diff --git a/crates/renderer_rendergraph/src/commands.rs b/crates/renderer_rendergraph/src/commands.rs index af0e367..c598d78 100644 --- a/crates/renderer_rendergraph/src/commands.rs +++ b/crates/renderer_rendergraph/src/commands.rs @@ -3,7 +3,9 @@ use ash::vk; use crate::{ recorder::{CommandIndex, CommandRecorder, RecordError, RecordedCommand, SideEffectMap}, resources::{ - Buffer, BufferRange, ResourceId, SubresourceScope, SubresourceScopeInner, Texture, + Buffer, BufferRange, DescriptorResourceAccess, GraphDescriptorSet, IndexBinding, + IndexFormat, IndirectBinding, PushConstantData, ResourceId, SubresourceScope, + SubresourceScopeInner, Texture, VertexBinding, }, }; @@ -120,27 +122,205 @@ impl RecordedCommand for ClearTexture { } #[derive(Debug, Clone, Copy)] +pub enum DrawArgs { + Direct { + vertex_count: u32, + instance_count: u32, + first_vertex: u32, + first_instance: u32, + }, + Indexed { + index_count: u32, + instance_count: u32, + first_index: u32, + vertex_offset: i32, + first_instance: u32, + }, +} + +/// A self-contained graphics draw packet. Every graph resource consumed by +/// vertex/index/indirect input or descriptor sets is declared by this packet. +#[derive(Debug, Clone)] pub struct Draw { - pub vertex_count: u32, - pub instance_count: u32, - pub first_vertex: u32, - pub first_instance: u32, + pub args: DrawArgs, + /// Layout compatible with the bound pipeline, descriptor sets, and push constants. + pub pipeline_layout: vk::PipelineLayout, + pub vertices: Vec, + pub index: Option, + pub indirect: Option, + pub descriptor_sets: Vec, + pub push_constants: Vec, } impl Command for Draw { - fn side_effects(&self, _command_index: CommandIndex, _map: &mut SideEffectMap) {} + fn side_effects(&self, command_index: CommandIndex, map: &mut SideEffectMap) { + for vertex in &self.vertices { + map.insert( + command_index, + vertex.slice.buffer.id(), + access( + vk::PipelineStageFlags2::VERTEX_INPUT, + vk::AccessFlags2::VERTEX_ATTRIBUTE_READ, + SubresourceScopeInner::Buffer(vertex.slice.range), + ), + ); + } + if let Some(index) = self.index { + map.insert( + command_index, + index.slice.buffer.id(), + access( + vk::PipelineStageFlags2::INDEX_INPUT, + vk::AccessFlags2::INDEX_READ, + SubresourceScopeInner::Buffer(index.slice.range), + ), + ); + } + if let Some(indirect) = self.indirect { + map.insert( + command_index, + indirect.slice.buffer.id(), + access( + vk::PipelineStageFlags2::DRAW_INDIRECT, + vk::AccessFlags2::INDIRECT_COMMAND_READ, + SubresourceScopeInner::Buffer(indirect.slice.range), + ), + ); + } + for set in &self.descriptor_sets { + for declared in &set.accesses { + let scope = match *declared { + DescriptorResourceAccess::Buffer { + range, + stage, + access: mask, + .. + } => access(stage, mask, SubresourceScopeInner::Buffer(range)), + DescriptorResourceAccess::Texture { + range, + layout, + stage, + access: mask, + .. + } => access( + stage, + mask, + SubresourceScopeInner::Texture { range, layout }, + ), + }; + let resource = match *declared { + DescriptorResourceAccess::Buffer { buffer, .. } => buffer.id(), + DescriptorResourceAccess::Texture { texture, .. } => texture.id(), + }; + map.insert(command_index, resource, scope); + } + } + } } impl RecordedCommand for Draw { fn apply(&mut self, recorder: &mut CommandRecorder<'_>) -> Result<(), RecordError> { + for vertex in &self.vertices { + let buffers = [recorder.buffer(vertex.slice.buffer.id())?]; + let offsets = [vertex.slice.range.offset]; + unsafe { + recorder.device.cmd_bind_vertex_buffers( + recorder.command_buffer, + vertex.binding, + &buffers, + &offsets, + ); + } + } + if let Some(index) = self.index { + let index_type = match index.format { + IndexFormat::Uint8 => vk::IndexType::UINT8_KHR, + IndexFormat::Uint16 => vk::IndexType::UINT16, + IndexFormat::Uint32 => vk::IndexType::UINT32, + }; + unsafe { + recorder.device.cmd_bind_index_buffer( + recorder.command_buffer, + recorder.buffer(index.slice.buffer.id())?, + index.slice.range.offset, + index_type, + ); + } + } + for constants in &self.push_constants { + unsafe { + recorder.device.cmd_push_constants( + recorder.command_buffer, + self.pipeline_layout, + constants.stage_flags, + constants.offset, + &constants.data, + ); + } + } + let descriptor_sets: Vec<_> = self.descriptor_sets.iter().map(|set| set.raw).collect(); + if !descriptor_sets.is_empty() { + unsafe { + recorder.device.cmd_bind_descriptor_sets( + recorder.command_buffer, + vk::PipelineBindPoint::GRAPHICS, + self.pipeline_layout, + 0, + &descriptor_sets, + &[], + ); + } + } unsafe { - recorder.device.cmd_draw( - recorder.command_buffer, - self.vertex_count, - self.instance_count, - self.first_vertex, - self.first_instance, - ); + if let Some(indirect) = self.indirect { + let buffer = recorder.buffer(indirect.slice.buffer.id())?; + if matches!(self.args, DrawArgs::Indexed { .. }) { + recorder.device.cmd_draw_indexed_indirect( + recorder.command_buffer, + buffer, + indirect.slice.range.offset, + indirect.draw_count, + indirect.stride, + ); + } else { + recorder.device.cmd_draw_indirect( + recorder.command_buffer, + buffer, + indirect.slice.range.offset, + indirect.draw_count, + indirect.stride, + ); + } + } else { + match self.args { + DrawArgs::Direct { + vertex_count, + instance_count, + first_vertex, + first_instance, + } => recorder.device.cmd_draw( + recorder.command_buffer, + vertex_count, + instance_count, + first_vertex, + first_instance, + ), + DrawArgs::Indexed { + index_count, + instance_count, + first_index, + vertex_offset, + first_instance, + } => recorder.device.cmd_draw_indexed( + recorder.command_buffer, + index_count, + instance_count, + first_index, + vertex_offset, + first_instance, + ), + } + } } Ok(()) } diff --git a/crates/renderer_rendergraph/src/graph.rs b/crates/renderer_rendergraph/src/graph.rs index 34a9549..6e545e6 100644 --- a/crates/renderer_rendergraph/src/graph.rs +++ b/crates/renderer_rendergraph/src/graph.rs @@ -18,8 +18,9 @@ use renderer_device::QueueFamilyIndex; use crate::{ recorder::{CommandIndex, CommandList, CommandRecorder, RecordError, scope_range}, resources::{ - Buffer, GraphBufferDesc, GraphTextureDesc, ResourceDesc, ResourceId, Subresource, - SubresourceScope, SubresourceScopeInner, Texture, access_flag_is_write, + Buffer, DescriptorResourceAccess, GraphBufferDesc, GraphDescriptorSet, GraphTextureDesc, + ResourceDesc, ResourceId, Subresource, SubresourceScope, SubresourceScopeInner, Texture, + access_flag_is_write, }, }; @@ -244,6 +245,19 @@ impl RenderGraph { })) } + /// Associate graph-visible resource accesses with a raw Vulkan descriptor set. + /// + /// The caller is responsible for declaring every resource referenced by the + /// set accurately; compilation validates that each declared resource belongs + /// to this graph, but cannot inspect the raw descriptor contents. + pub fn track_descriptor_set( + &self, + raw: vk::DescriptorSet, + accesses: Vec, + ) -> GraphDescriptorSet { + GraphDescriptorSet { raw, accesses } + } + pub fn push(&mut self, command: C) -> CommandIndex { self.commands.push(command) } @@ -923,8 +937,11 @@ fn texture_size_estimate(desc: &GraphTextureDesc) -> u64 { mod tests { use super::*; use crate::{ - commands::{ClearTexture, CopyBuffer}, - resources::{BufferRange, GraphBufferDesc, GraphTextureDesc, ResourceDesc}, + commands::{ClearTexture, CopyBuffer, Draw, DrawArgs}, + resources::{ + BufferRange, BufferSlice, GraphBufferDesc, GraphTextureDesc, IndexBinding, IndexFormat, + IndirectBinding, ResourceDesc, VertexBinding, + }, }; #[test] @@ -1079,6 +1096,237 @@ mod tests { assert!(compiled.interference().interferes(src.id(), dst.id())); } + #[test] + fn draw_vertex_index_and_indirect_reads_follow_copies_and_derive_usage() { + let mut graph = RenderGraph::new(); + let source = graph.create_buffer(GraphBufferDesc { + size: 64, + ..Default::default() + }); + let vertex = graph.create_buffer(GraphBufferDesc { + size: 64, + ..Default::default() + }); + let index = graph.create_buffer(GraphBufferDesc { + size: 64, + ..Default::default() + }); + let indirect = graph.create_buffer(GraphBufferDesc { + size: 64, + ..Default::default() + }); + let range = BufferRange { + offset: 0, + size: 64, + }; + for dst in [vertex, index, indirect] { + graph.push(CopyBuffer { + src: source, + src_range: range, + dst, + dst_range: range, + }); + } + graph.push(Draw { + args: DrawArgs::Indexed { + index_count: 3, + instance_count: 1, + first_index: 0, + vertex_offset: 0, + first_instance: 0, + }, + pipeline_layout: vk::PipelineLayout::null(), + vertices: vec![VertexBinding { + binding: 0, + slice: BufferSlice { + buffer: vertex, + range, + }, + }], + index: Some(IndexBinding { + slice: BufferSlice { + buffer: index, + range, + }, + format: IndexFormat::Uint16, + }), + indirect: Some(IndirectBinding { + slice: BufferSlice { + buffer: indirect, + range, + }, + draw_count: 1, + stride: 20, + }), + descriptor_sets: Vec::new(), + push_constants: Vec::new(), + }); + + let accesses = graph.commands.side_effects.command_accesses(); + assert_eq!( + derive_buffer_usage( + &accesses_for_resource(vertex.id(), accesses), + vk::BufferUsageFlags::empty() + ), + vk::BufferUsageFlags::TRANSFER_DST | vk::BufferUsageFlags::VERTEX_BUFFER + ); + assert_eq!( + derive_buffer_usage( + &accesses_for_resource(index.id(), accesses), + vk::BufferUsageFlags::empty() + ), + vk::BufferUsageFlags::TRANSFER_DST | vk::BufferUsageFlags::INDEX_BUFFER + ); + assert_eq!( + derive_buffer_usage( + &accesses_for_resource(indirect.id(), accesses), + vk::BufferUsageFlags::empty() + ), + vk::BufferUsageFlags::TRANSFER_DST | vk::BufferUsageFlags::INDIRECT_BUFFER + ); + + let compiled = graph.compile().expect("graph compiles"); + assert_eq!(compiled.batches().len(), 2); + assert_eq!( + compiled.batches()[0], + [CommandIndex(0), CommandIndex(1), CommandIndex(2)] + ); + assert_eq!(compiled.batches()[1], [CommandIndex(3)]); + let draw_barrier = compiled + .barriers() + .iter() + .find(|barrier| barrier.before_command == CommandIndex(3)) + .expect("draw input barriers are generated"); + assert_eq!(draw_barrier.resources.len(), 3); + assert!( + draw_barrier + .resources + .iter() + .any(|resource| resource.id == vertex.id()) + ); + assert!( + draw_barrier + .resources + .iter() + .any(|resource| resource.id == index.id()) + ); + assert!( + draw_barrier + .resources + .iter() + .any(|resource| resource.id == indirect.id()) + ); + } + + #[test] + fn descriptor_set_declarations_create_buffer_and_texture_accesses() { + let mut graph = RenderGraph::new(); + let source = graph.create_buffer(GraphBufferDesc { + size: 32, + ..Default::default() + }); + let uniform = graph.create_buffer(GraphBufferDesc { + size: 32, + ..Default::default() + }); + let texture = graph.create_texture(GraphTextureDesc { + format: vk::Format::R8G8B8A8_UNORM, + ..Default::default() + }); + let buffer_range = BufferRange { + offset: 0, + size: 32, + }; + graph.push(CopyBuffer { + src: source, + src_range: buffer_range, + dst: uniform, + dst_range: buffer_range, + }); + graph.push(ClearTexture { + texture, + range: texture_range(vk::ImageAspectFlags::COLOR, 0, 1, 0, 1), + layout: vk::ImageLayout::TRANSFER_DST_OPTIMAL, + color: [0.0; 4], + }); + let set = graph.track_descriptor_set( + vk::DescriptorSet::null(), + vec![ + DescriptorResourceAccess::Buffer { + buffer: uniform, + range: buffer_range, + stage: vk::PipelineStageFlags2::VERTEX_SHADER, + access: vk::AccessFlags2::UNIFORM_READ, + }, + DescriptorResourceAccess::Texture { + texture, + range: texture_range(vk::ImageAspectFlags::COLOR, 0, 1, 0, 1), + layout: vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL, + stage: vk::PipelineStageFlags2::FRAGMENT_SHADER, + access: vk::AccessFlags2::SHADER_SAMPLED_READ, + }, + ], + ); + graph.push(Draw { + args: DrawArgs::Direct { + vertex_count: 3, + instance_count: 1, + first_vertex: 0, + first_instance: 0, + }, + pipeline_layout: vk::PipelineLayout::null(), + vertices: Vec::new(), + index: None, + indirect: None, + descriptor_sets: vec![set], + push_constants: Vec::new(), + }); + + let accesses = graph.commands.side_effects.command_accesses(); + assert!( + derive_buffer_usage( + &accesses_for_resource(uniform.id(), accesses), + vk::BufferUsageFlags::empty() + ) + .contains(vk::BufferUsageFlags::UNIFORM_BUFFER) + ); + assert!( + derive_image_usage( + &accesses_for_resource(texture.id(), accesses), + vk::ImageUsageFlags::empty() + ) + .contains(vk::ImageUsageFlags::SAMPLED) + ); + + let compiled = graph.compile().expect("graph compiles"); + let draw_barrier = compiled + .barriers() + .iter() + .find(|barrier| barrier.before_command == CommandIndex(2)) + .expect("descriptor accesses produce barriers"); + assert_eq!(draw_barrier.resources.len(), 2); + let texture_barrier = draw_barrier + .resources + .iter() + .find(|resource| resource.id == texture.id()) + .expect("sampled texture barrier is present"); + assert_eq!( + texture_barrier.new_layout, + Some(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL) + ); + assert_eq!( + texture_barrier.dst_access, + vk::AccessFlags2::SHADER_SAMPLED_READ + ); + assert!(compiled.resource_lifetimes().contains_key(&uniform.id())); + assert!(compiled.resource_lifetimes().contains_key(&texture.id())); + assert!( + compiled + .interference() + .interferes(uniform.id(), texture.id()) + ); + } + #[test] fn write_then_read_creates_order_and_barrier() { let mut graph = RenderGraph::new(); diff --git a/crates/renderer_rendergraph/src/lib.rs b/crates/renderer_rendergraph/src/lib.rs index b51deb6..525216a 100644 --- a/crates/renderer_rendergraph/src/lib.rs +++ b/crates/renderer_rendergraph/src/lib.rs @@ -8,4 +8,8 @@ pub mod resources; pub use graph::{CompiledGraph, GraphError, RenderGraph, ResourceInterference}; pub use recorder::{CommandIndex, CommandList, CommandRecorder, SideEffectMap}; -pub use resources::{Buffer, GraphBufferDesc, GraphTextureDesc, ResourceId, Texture}; +pub use resources::{ + Buffer, BufferRange, BufferSlice, DescriptorResourceAccess, GraphBufferDesc, + GraphDescriptorSet, GraphTextureDesc, IndexBinding, IndexFormat, IndirectBinding, + PushConstantData, ResourceId, Texture, VertexBinding, +}; diff --git a/crates/renderer_rendergraph/src/resources.rs b/crates/renderer_rendergraph/src/resources.rs index 6308b03..deb821d 100644 --- a/crates/renderer_rendergraph/src/resources.rs +++ b/crates/renderer_rendergraph/src/resources.rs @@ -130,6 +130,79 @@ impl BufferRange { } } +/// A graph buffer together with the byte range used by a command. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct BufferSlice { + pub buffer: Buffer, + pub range: BufferRange, +} + +/// A vertex-input binding. `binding` is the Vulkan vertex binding number. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct VertexBinding { + pub binding: u32, + pub slice: BufferSlice, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum IndexFormat { + Uint8, + Uint16, + Uint32, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct IndexBinding { + pub slice: BufferSlice, + pub format: IndexFormat, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct IndirectBinding { + pub slice: BufferSlice, + /// Number of draw commands in the indirect buffer. + pub draw_count: u32, + /// Stride in bytes between draw commands. Must satisfy Vulkan's alignment + /// and minimum-size requirements for the corresponding draw command. + pub stride: u32, +} + +#[derive(Debug, Clone, Copy)] +pub enum DescriptorResourceAccess { + Buffer { + buffer: Buffer, + range: BufferRange, + stage: vk::PipelineStageFlags2, + access: vk::AccessFlags2, + }, + Texture { + texture: Texture, + range: vk::ImageSubresourceRange, + layout: vk::ImageLayout, + stage: vk::PipelineStageFlags2, + access: vk::AccessFlags2, + }, +} + +/// A Vulkan descriptor set accompanied by graph-visible resource accesses. +/// +/// Callers must list every resource used by the set with its accurate range, +/// pipeline stage, access mask, and (for textures) image layout. The graph can +/// track these declarations, but cannot inspect or verify raw descriptor-set +/// contents. +#[derive(Debug, Clone)] +pub struct GraphDescriptorSet { + pub raw: vk::DescriptorSet, + pub accesses: Vec, +} + +#[derive(Debug, Clone)] +pub struct PushConstantData { + pub stage_flags: vk::ShaderStageFlags, + pub offset: u32, + pub data: Vec, +} + #[derive(Debug, Clone, Copy)] pub enum Subresource { Buffer(BufferRange),