From 15afc00e514b916b0bda6e6e6bf6f7f098dd2576 Mon Sep 17 00:00:00 2001 From: janis Date: Sat, 11 Apr 2026 23:25:33 +0200 Subject: [PATCH] command trait --- crates/renderer/src/render_graph/commands.rs | 312 +++++++++++++++++- crates/renderer/src/render_graph/recorder.rs | 73 +++- crates/renderer/src/render_graph/resources.rs | 135 +++++++- 3 files changed, 494 insertions(+), 26 deletions(-) diff --git a/crates/renderer/src/render_graph/commands.rs b/crates/renderer/src/render_graph/commands.rs index 706a259..62d59a4 100644 --- a/crates/renderer/src/render_graph/commands.rs +++ b/crates/renderer/src/render_graph/commands.rs @@ -1,4 +1,19 @@ #![allow(dead_code)] +//! This module defines the commands that can be recorded in the render +//! graph. +//! Commands can be allocated, and then recorded into a command +//! buffer. +//! the lifecycle of a command is thus as follows: +//! 1. A command is constructed, with its parameters and resource dependencies. +//! 2. The command is inserted into a command list. At this stage, the command's side effects are recorded and barriers are automatically inserted as required. +//! 3. When the command list is recorded into a command buffer, the command's `apply` method is called, which translates the command into actual GPU commands using the provided `CommandRecorder`. +//! +//! Each command declares its side effects, such as what resources it +//! reads from or write to, and which pipeline stages it affects. Commands are +//! recorded sequentially, in the order they were inserted into a command +//! buffer. During command insertion + +use crate::render_graph::recorder::{CommandMeta, CommandRecorder, SideEffectMap}; use super::resources::*; @@ -12,11 +27,6 @@ pub struct CopyTextures { pub dst: Write, } -pub struct ClearTexture { - pub dst: Write, - pub clear_value: [f32; 4], -} - pub struct CopyBufferToTexture { pub src: Read, pub dst: Write, @@ -31,20 +41,108 @@ pub struct Copy { pub src: Read, pub dst: Write, } +impl Copy { + fn side_effects_inner(&self, mut map: SideEffectMap) { + map.insert( + self.src.id(), + ResourceAccess { + range: self.src.into_access(), + access: vk::AccessFlags2::TRANSFER_READ, + }, + ); + map.insert( + self.dst.id(), + ResourceAccess { + range: self.dst.into_access(), + access: vk::AccessFlags2::TRANSFER_WRITE, + }, + ); + } +} + +impl Command for Copy { + fn side_effects(&self, map: SideEffectMap) { + self.side_effects_inner(map) + } + + fn apply(self, _recorder: &mut CommandRecorder) { + // CopyBufferToTexture {src, dst}.apply(recorder) + } +} +impl Command for Copy { + fn side_effects(&self, map: SideEffectMap) { + self.side_effects_inner(map) + } + + fn apply(self, _recorder: &mut CommandRecorder) { + // CopyTextureToBuffer {src, dst}.apply(recorder) + } + + fn get_meta(&self) -> CommandMeta { + CommandMeta { + apply_command: |recorder, command_ptr, cursor| { + let command = unsafe { command_ptr.cast::().read() }; + *cursor += std::mem::size_of::(); + command.apply(recorder); + }, + } + } +} +impl Command for Copy { + fn side_effects(&self, map: SideEffectMap) { + self.side_effects_inner(map) + } + + fn apply(self, _recorder: &mut CommandRecorder) { + // CopyTextures {src, dst}.apply(recorder) + } +} +impl Command for Copy { + fn side_effects(&self, map: SideEffectMap) { + self.side_effects_inner(map) + } + + fn apply(self, _recorder: &mut CommandRecorder) { + // CopyBuffers {src, dst}.apply(recorder) + } +} + +pub struct ClearTexture { + pub dst: Write, + pub clear_value: [f32; 4], +} + +impl Command for ClearTexture { + fn side_effects(&self, mut map: SideEffectMap) { + map.insert( + self.dst.id(), + ResourceAccess { + range: self.dst.into_access(), + access: vk::AccessFlags2::TRANSFER_WRITE, + }, + ); + } + + fn apply(self, _recorder: &mut CommandRecorder) {} +} pub struct UpdateBuffer { pub dst: Write, pub data: Vec, } -pub struct UpdateTexture { - pub dst: Write, - pub data: Vec, -} +impl Command for UpdateBuffer { + fn side_effects(&self, mut map: SideEffectMap) { + map.insert( + self.dst.id(), + ResourceAccess { + range: self.dst.into_access(), + access: vk::AccessFlags2::TRANSFER_WRITE, + }, + ); + } -pub struct Update { - pub dst: Write, - pub data: Vec, + fn apply(self, _recorder: &mut CommandRecorder) {} } pub struct RenderPass { @@ -55,15 +153,98 @@ pub struct RenderPass { pub layers: u32, } +impl Command for RenderPass { + fn side_effects(&self, mut map: SideEffectMap) { + for attachment in &self.color_attachments { + map.insert( + attachment.id(), + ResourceAccess { + range: attachment.into_access(), + access: vk::AccessFlags2::COLOR_ATTACHMENT_WRITE, + }, + ); + } + if let Some(depth) = &self.depth_attachment { + map.insert( + depth.id(), + ResourceAccess { + range: depth.into_access(), + access: vk::AccessFlags2::DEPTH_STENCIL_ATTACHMENT_WRITE, + }, + ); + } + if let Some(stencil) = &self.stencil_attachment { + map.insert( + stencil.id(), + ResourceAccess { + range: stencil.into_access(), + access: vk::AccessFlags2::DEPTH_STENCIL_ATTACHMENT_WRITE, + }, + ); + } + } + + fn apply(self, _recorder: &mut CommandRecorder) {} +} + pub struct BindPipeline(pub Pipeline); + +impl Command for BindPipeline { + fn side_effects(&self, _map: SideEffectMap) { + // No resource access, but affects the pipeline state + } + + fn apply(self, _recorder: &mut CommandRecorder) {} +} + pub struct BindVertexBuffers { pub buffers: Vec>, } + +impl Command for BindVertexBuffers { + fn side_effects(&self, mut map: SideEffectMap) { + for buffer in &self.buffers { + map.insert( + buffer.id(), + ResourceAccess { + range: buffer.into_access(), + access: vk::AccessFlags2::VERTEX_ATTRIBUTE_READ, + }, + ); + } + } + + fn apply(self, _recorder: &mut CommandRecorder) {} +} + pub struct BindIndexBuffer(pub Read, pub IndexFormat); + +impl Command for BindIndexBuffer { + fn side_effects(&self, mut map: SideEffectMap) { + map.insert( + self.0.id(), + ResourceAccess { + range: self.0.into_access(), + access: vk::AccessFlags2::INDEX_READ, + }, + ); + } + + fn apply(self, _recorder: &mut CommandRecorder) {} +} + pub struct BindDescriptorSets { pub sets: Vec, } +impl Command for BindDescriptorSets { + fn side_effects(&self, _map: SideEffectMap) { + // No resource access, but affects the descriptor set state + } + + fn apply(self, _recorder: &mut CommandRecorder) {} +} + pub struct SetViewport { pub x: f32, pub y: f32, @@ -73,6 +254,14 @@ pub struct SetViewport { pub max_depth: f32, } +impl Command for SetViewport { + fn side_effects(&self, _map: SideEffectMap) { + // No resource access, but affects the viewport state + } + + fn apply(self, _recorder: &mut CommandRecorder) {} +} + pub struct SetScissor { pub x: u32, pub y: u32, @@ -80,10 +269,26 @@ pub struct SetScissor { pub height: u32, } +impl Command for SetScissor { + fn side_effects(&self, _map: SideEffectMap) { + // No resource access, but affects the scissor state + } + + fn apply(self, _recorder: &mut CommandRecorder) {} +} + pub struct PushConstants { pub data: Box<[u8]>, } +impl Command for PushConstants { + fn side_effects(&self, _map: SideEffectMap) { + // No resource access, but affects the push constant state + } + + fn apply(self, _recorder: &mut CommandRecorder) {} +} + pub struct DrawData { pub vertex_count: u32, pub instance_count: u32, @@ -121,19 +326,94 @@ pub struct Draw { pub count_buffer: Option>, } -pub struct Dispatch; +impl Command for Draw { + fn side_effects(&self, mut map: SideEffectMap) { + self.data.side_effects(map.reborrow()); + for vertex_buffer in &self.vertex_buffers { + map.insert( + vertex_buffer.id(), + ResourceAccess { + range: vertex_buffer.into_access(), + access: vk::AccessFlags2::VERTEX_ATTRIBUTE_READ, + }, + ); + } + if let Some((index_buffer, _)) = &self.index_buffer { + map.insert( + index_buffer.id(), + ResourceAccess { + range: index_buffer.into_access(), + access: vk::AccessFlags2::INDEX_READ, + }, + ); + } + if let Some(count_buffer) = &self.count_buffer { + map.insert( + count_buffer.id(), + ResourceAccess { + range: count_buffer.into_access(), + access: vk::AccessFlags2::INDIRECT_COMMAND_READ, + }, + ); + } + } + + fn apply(self, _recorder: &mut CommandRecorder) {} +} + +pub trait Command: Sized { + fn side_effects(&self, map: SideEffectMap); + fn apply(self, recorder: &mut CommandRecorder); + fn get_meta(&self) -> CommandMeta { + CommandMeta { + apply_command: |recorder, command_ptr, cursor| { + let command = unsafe { command_ptr.cast::().read() }; + *cursor += std::mem::size_of::(); + command.apply(recorder); + }, + } + } +} mod sealed { - pub trait IsDrawData {} + pub trait IsDrawData { + fn side_effects(&self, _map: super::SideEffectMap) {} + } pub trait InsideRenderPass {} pub trait OutsideRenderPass {} } +use ash::vk; use sealed::*; impl IsDrawData for DrawData {} impl IsDrawData for DrawIndexedData {} -impl IsDrawData for DrawIndirectData {} -impl IsDrawData for DrawIndexedIndirectData {} +impl IsDrawData for DrawIndirectData { + fn side_effects(&self, mut map: SideEffectMap) { + map.insert( + self.indirect_buffer.id(), + ResourceAccess { + range: self.indirect_buffer.into_access(), + access: vk::AccessFlags2::INDIRECT_COMMAND_READ, + }, + ); + } +} +impl IsDrawData for DrawIndexedIndirectData { + fn side_effects(&self, mut map: SideEffectMap) { + map.insert( + self.indirect_buffer.id(), + ResourceAccess { + range: self.indirect_buffer.into_access(), + access: vk::AccessFlags2::INDIRECT_COMMAND_READ, + }, + ); + } +} + +impl OutsideRenderPass for RenderPass {} +impl OutsideRenderPass for Copy {} +impl OutsideRenderPass for ClearTexture {} +impl OutsideRenderPass for UpdateBuffer {} impl InsideRenderPass for BindPipeline {} impl OutsideRenderPass for BindPipeline {} diff --git a/crates/renderer/src/render_graph/recorder.rs b/crates/renderer/src/render_graph/recorder.rs index 7e45b94..441e3e3 100644 --- a/crates/renderer/src/render_graph/recorder.rs +++ b/crates/renderer/src/render_graph/recorder.rs @@ -1,2 +1,71 @@ -struct CommandRecorder; -struct RenderPassRecorder; +use std::{ + collections::BTreeMap, + mem::{MaybeUninit, size_of}, + ptr::NonNull, +}; + +use crate::render_graph::{ + commands::Command, + resources::{ResourceAccess, ResourceId}, +}; + +pub struct CommandRecorder; +pub struct RenderPassRecorder; + +pub struct CommandMeta { + pub(crate) apply_command: unsafe fn(&mut CommandRecorder, NonNull<()>, cursor: &mut usize), +} + +pub struct SideEffectMap<'a>(&'a mut BTreeMap<(ResourceId, u32), ResourceAccess>, u32); + +impl<'a> SideEffectMap<'a> { + pub fn insert(&mut self, id: ResourceId, access: ResourceAccess) { + self.0.insert((id, self.1), access); + } + pub fn reborrow(&mut self) -> SideEffectMap<'_> { + SideEffectMap(self.0, self.1) + } +} + +pub struct CommandList { + command_bytes: Vec>, + 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>, +} + +impl CommandList { + pub fn new() -> Self { + Self { + command_bytes: Vec::new(), + cursor: 0, + side_effects: BTreeMap::new(), + } + } + + pub fn push(&mut self, command: C) { + struct Packed { + meta: CommandMeta, + command: C, + } + + let packed = Packed { + meta: CommandMeta { + apply_command: |recorder, command_ptr, cursor| { + *cursor += size_of::(); + let command = unsafe { command_ptr.cast::().read_unaligned() }; + C::apply(command, recorder); + }, + }, + command, + }; + + let len = self.command_bytes.len(); + self.command_bytes.reserve(size_of::>()); + unsafe { + let bytes = self.command_bytes.as_mut_ptr().add(len); + bytes.cast::>().write_unaligned(packed); + } + } +} diff --git a/crates/renderer/src/render_graph/resources.rs b/crates/renderer/src/render_graph/resources.rs index dd1c4fb..c1417cb 100644 --- a/crates/renderer/src/render_graph/resources.rs +++ b/crates/renderer/src/render_graph/resources.rs @@ -1,21 +1,139 @@ +use ash::vk; + use crate::images::MipRange; -pub trait Resource: Sized {} +pub trait Resource: Sized + AsResourceRange { + fn id(&self) -> ResourceId; +} + +pub trait AsResourceRange { + fn into_access(&self) -> ResourceRange; +} mod impls { use super::*; - impl Resource for Buffer {} - impl Resource for Texture {} - impl Resource for BufferSlice {} - impl Resource for TextureRegion {} + impl Resource for Buffer { + fn id(&self) -> ResourceId { + self.0 + } + } + impl Resource for Texture { + fn id(&self) -> ResourceId { + self.0 + } + } + impl Resource for BufferSlice { + fn id(&self) -> ResourceId { + self.buffer.id() + } + } + impl Resource for TextureRegion { + fn id(&self) -> ResourceId { + self.texture.id() + } + } + + impl AsResourceRange for BufferSlice { + fn into_access(&self) -> ResourceRange { + ResourceRange::Buffer { + offset: self.offset, + size: self.size, + } + } + } + + impl AsResourceRange for TextureRegion { + fn into_access(&self) -> ResourceRange { + ResourceRange::Texture { + aspect: self.aspect, + mip_level: self.mip_level, + array_layers: self.array_layers, + origin: self.origin, + extent: self.extent, + } + } + } + + impl AsResourceRange for Texture { + fn into_access(&self) -> ResourceRange { + ResourceRange::Texture { + aspect: vk::ImageAspectFlags::NONE, + mip_level: 0, + array_layers: MipRange::default(), + origin: (0, 0, 0), + extent: (u32::MAX, u32::MAX, u32::MAX), + } + } + } + + impl AsResourceRange for Buffer { + fn into_access(&self) -> ResourceRange { + ResourceRange::Buffer { + offset: 0, + size: u64::MAX, + } + } + } + impl AsResourceRange for Read { + fn into_access(&self) -> ResourceRange { + self.0.into_access() + } + } + impl AsResourceRange for Write { + fn into_access(&self) -> ResourceRange { + self.0.into_access() + } + } + impl AsResourceRange for ReadWrite { + fn into_access(&self) -> ResourceRange { + self.0.into_access() + } + } + impl Resource for Read { + fn id(&self) -> ResourceId { + self.0.id() + } + } + impl Resource for Write { + fn id(&self) -> ResourceId { + self.0.id() + } + } + impl Resource for ReadWrite { + fn id(&self) -> ResourceId { + self.0.id() + } + } } pub struct Pipeline; pub struct DescriptorSet; -pub struct Buffer; -pub struct Texture; +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct ResourceId(u32); + +pub struct ResourceAccess { + pub range: ResourceRange, + pub access: vk::AccessFlags2, +} + +pub enum ResourceRange { + Buffer { + offset: u64, + size: u64, + }, + Texture { + aspect: vk::ImageAspectFlags, + mip_level: u32, + array_layers: MipRange, + origin: (u32, u32, u32), + extent: (u32, u32, u32), + }, +} + +pub struct Buffer(ResourceId); +pub struct Texture(ResourceId); pub struct BufferSlice { pub buffer: Buffer, pub offset: u64, @@ -23,7 +141,8 @@ pub struct BufferSlice { } pub struct TextureRegion { pub texture: Texture, - pub mip_levels: MipRange, + pub aspect: vk::ImageAspectFlags, + pub mip_level: u32, pub array_layers: MipRange, pub origin: (u32, u32, u32), pub extent: (u32, u32, u32),