command trait

This commit is contained in:
janis 2026-04-11 23:25:33 +02:00
parent 3438dfde84
commit 15afc00e51
Signed by: janis
SSH key fingerprint: SHA256:bB1qbbqmDXZNT0KKD5c2Dfjg53JGhj7B3CFcLIzSqq8
3 changed files with 494 additions and 26 deletions

View file

@ -1,4 +1,19 @@
#![allow(dead_code)] #![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::*; use super::resources::*;
@ -12,11 +27,6 @@ pub struct CopyTextures {
pub dst: Write<TextureRegion>, pub dst: Write<TextureRegion>,
} }
pub struct ClearTexture {
pub dst: Write<TextureRegion>,
pub clear_value: [f32; 4],
}
pub struct CopyBufferToTexture { pub struct CopyBufferToTexture {
pub src: Read<BufferSlice>, pub src: Read<BufferSlice>,
pub dst: Write<TextureRegion>, pub dst: Write<TextureRegion>,
@ -31,20 +41,108 @@ pub struct Copy<T: Resource, U: Resource> {
pub src: Read<T>, pub src: Read<T>,
pub dst: Write<U>, pub dst: Write<U>,
} }
impl<T: Resource, U: Resource> Copy<T, U> {
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<BufferSlice, TextureRegion> {
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<TextureRegion, BufferSlice> {
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::<Self>().read() };
*cursor += std::mem::size_of::<Self>();
command.apply(recorder);
},
}
}
}
impl Command for Copy<TextureRegion, TextureRegion> {
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<BufferSlice, BufferSlice> {
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<TextureRegion>,
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 struct UpdateBuffer {
pub dst: Write<BufferSlice>, pub dst: Write<BufferSlice>,
pub data: Vec<u8>, pub data: Vec<u8>,
} }
pub struct UpdateTexture { impl Command for UpdateBuffer {
pub dst: Write<TextureRegion>, fn side_effects(&self, mut map: SideEffectMap) {
pub data: Vec<u8>, map.insert(
self.dst.id(),
ResourceAccess {
range: self.dst.into_access(),
access: vk::AccessFlags2::TRANSFER_WRITE,
},
);
} }
pub struct Update<T: Resource> { fn apply(self, _recorder: &mut CommandRecorder) {}
pub dst: Write<T>,
pub data: Vec<u8>,
} }
pub struct RenderPass { pub struct RenderPass {
@ -55,15 +153,98 @@ pub struct RenderPass {
pub layers: u32, 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); 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 struct BindVertexBuffers {
pub buffers: Vec<Read<BufferSlice>>, pub buffers: Vec<Read<BufferSlice>>,
} }
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<BufferSlice>, pub IndexFormat); pub struct BindIndexBuffer(pub Read<BufferSlice>, 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 struct BindDescriptorSets {
pub sets: Vec<DescriptorSet>, pub sets: Vec<DescriptorSet>,
} }
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 struct SetViewport {
pub x: f32, pub x: f32,
pub y: f32, pub y: f32,
@ -73,6 +254,14 @@ pub struct SetViewport {
pub max_depth: f32, 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 struct SetScissor {
pub x: u32, pub x: u32,
pub y: u32, pub y: u32,
@ -80,10 +269,26 @@ pub struct SetScissor {
pub height: u32, 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 struct PushConstants {
pub data: Box<[u8]>, 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 struct DrawData {
pub vertex_count: u32, pub vertex_count: u32,
pub instance_count: u32, pub instance_count: u32,
@ -121,19 +326,94 @@ pub struct Draw<T: IsDrawData> {
pub count_buffer: Option<Read<BufferSlice>>, pub count_buffer: Option<Read<BufferSlice>>,
} }
pub struct Dispatch; impl<T: IsDrawData> Command for Draw<T> {
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::<Self>().read() };
*cursor += std::mem::size_of::<Self>();
command.apply(recorder);
},
}
}
}
mod sealed { mod sealed {
pub trait IsDrawData {} pub trait IsDrawData {
fn side_effects(&self, _map: super::SideEffectMap) {}
}
pub trait InsideRenderPass {} pub trait InsideRenderPass {}
pub trait OutsideRenderPass {} pub trait OutsideRenderPass {}
} }
use ash::vk;
use sealed::*; use sealed::*;
impl IsDrawData for DrawData {} impl IsDrawData for DrawData {}
impl IsDrawData for DrawIndexedData {} impl IsDrawData for DrawIndexedData {}
impl IsDrawData for DrawIndirectData {} impl IsDrawData for DrawIndirectData {
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 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<T: Resource, U: Resource> OutsideRenderPass for Copy<T, U> {}
impl OutsideRenderPass for ClearTexture {}
impl OutsideRenderPass for UpdateBuffer {}
impl InsideRenderPass for BindPipeline {} impl InsideRenderPass for BindPipeline {}
impl OutsideRenderPass for BindPipeline {} impl OutsideRenderPass for BindPipeline {}

View file

@ -1,2 +1,71 @@
struct CommandRecorder; use std::{
struct RenderPassRecorder; 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<MaybeUninit<u8>>,
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<C: Command>(&mut self, command: C) {
struct Packed<C> {
meta: CommandMeta,
command: C,
}
let packed = Packed {
meta: CommandMeta {
apply_command: |recorder, command_ptr, cursor| {
*cursor += size_of::<C>();
let command = unsafe { command_ptr.cast::<C>().read_unaligned() };
C::apply(command, recorder);
},
},
command,
};
let len = self.command_bytes.len();
self.command_bytes.reserve(size_of::<Packed<C>>());
unsafe {
let bytes = self.command_bytes.as_mut_ptr().add(len);
bytes.cast::<Packed<C>>().write_unaligned(packed);
}
}
}

View file

@ -1,21 +1,139 @@
use ash::vk;
use crate::images::MipRange; 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 { mod impls {
use super::*; use super::*;
impl Resource for Buffer {} impl Resource for Buffer {
impl Resource for Texture {} fn id(&self) -> ResourceId {
impl Resource for BufferSlice {} self.0
impl Resource for TextureRegion {} }
}
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<T: AsResourceRange> AsResourceRange for Read<T> {
fn into_access(&self) -> ResourceRange {
self.0.into_access()
}
}
impl<T: AsResourceRange> AsResourceRange for Write<T> {
fn into_access(&self) -> ResourceRange {
self.0.into_access()
}
}
impl<T: AsResourceRange> AsResourceRange for ReadWrite<T> {
fn into_access(&self) -> ResourceRange {
self.0.into_access()
}
}
impl<T: Resource> Resource for Read<T> {
fn id(&self) -> ResourceId {
self.0.id()
}
}
impl<T: Resource> Resource for Write<T> {
fn id(&self) -> ResourceId {
self.0.id()
}
}
impl<T: Resource> Resource for ReadWrite<T> {
fn id(&self) -> ResourceId {
self.0.id()
}
}
} }
pub struct Pipeline; pub struct Pipeline;
pub struct DescriptorSet; pub struct DescriptorSet;
pub struct Buffer; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Texture; 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 struct BufferSlice {
pub buffer: Buffer, pub buffer: Buffer,
pub offset: u64, pub offset: u64,
@ -23,7 +141,8 @@ pub struct BufferSlice {
} }
pub struct TextureRegion { pub struct TextureRegion {
pub texture: Texture, pub texture: Texture,
pub mip_levels: MipRange, pub aspect: vk::ImageAspectFlags,
pub mip_level: u32,
pub array_layers: MipRange, pub array_layers: MipRange,
pub origin: (u32, u32, u32), pub origin: (u32, u32, u32),
pub extent: (u32, u32, u32), pub extent: (u32, u32, u32),