vidya/crates/renderer/src/render_graph/commands.rs
2026-04-15 23:33:09 +02:00

668 lines
21 KiB
Rust

//! 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::{
device::DeviceOwned,
render_graph::recorder::{CommandRecorder, SideEffectMap},
};
use super::resources::*;
pub struct ImportResource<T: Resource> {
pub resource: T,
pub access: vk::AccessFlags2,
pub stage: vk::PipelineStageFlags2,
pub layout: Option<vk::ImageLayout>,
}
impl<T: Resource> Command for ImportResource<T> {
fn side_effects(&self, mut map: SideEffectMap) {
self.resource
.side_effect(map.reborrow().into_side_effect_map2(
self.stage,
self.access,
self.layout,
));
}
fn apply(self, _recorder: &mut CommandRecorder) {
// No actual GPU commands needed for import, as it's just a logical operation
}
}
pub struct CopyBuffers {
pub src: BufferSlice,
pub dst: BufferSlice,
}
pub struct CopyTextures {
pub src: TextureRegion,
pub dst: TextureRegion,
}
pub struct CopyBufferToTexture {
pub src: BufferSlice,
pub rows: Option<BufferRows>,
pub dst: TextureRegion,
}
pub struct CopyTextureToBuffer {
pub src: TextureRegion,
pub rows: Option<BufferRows>,
pub dst: BufferSlice,
}
pub struct CopyBuffersToTextures {
pub src: BufferSlice,
pub src_rows: Vec<BufferRows>,
pub dst: Texture,
pub src_ranges: Vec<(TextureRegion, vk::Offset3D, vk::Extent3D)>,
}
pub struct Copy<T: Resource, U: Resource> {
pub src: T,
pub dst: U,
}
fn copy_side_effects_inner<T, U>(src: &T, dst: &U, mut map: SideEffectMap)
where
T: Resource,
U: Resource,
{
src.side_effect(map.reborrow().into_side_effect_map2(
vk::PipelineStageFlags2::COPY,
vk::AccessFlags2::TRANSFER_READ,
Some(vk::ImageLayout::TRANSFER_SRC_OPTIMAL),
));
dst.side_effect(map.reborrow().into_side_effect_map2(
vk::PipelineStageFlags2::COPY,
vk::AccessFlags2::TRANSFER_WRITE,
Some(vk::ImageLayout::TRANSFER_DST_OPTIMAL),
));
}
impl Command for CopyBufferToTexture {
fn side_effects(&self, map: SideEffectMap) {
copy_side_effects_inner(&self.src, &self.dst, map)
}
fn apply(self, recorder: &mut CommandRecorder) {
let cmd = recorder.cmd_buffer();
let dev = &cmd.device().raw;
// it doesn't really make sense to copy the same data into multiple mip
// levels, considering the mip extents are different for each level.
debug_assert_eq!(self.dst.range.mip_levels.len(), 1);
let rows = self.rows.unwrap_or_default();
let regions = &[vk::BufferImageCopy2::default()
.buffer_offset(self.src.range.offset + rows.offset)
.buffer_row_length(rows.row_size)
.buffer_image_height(rows.row_count)
.image_offset(self.dst.offset)
.image_extent(self.dst.extent)
.image_subresource(
vk::ImageSubresourceLayers::default()
.aspect_mask(self.dst.range.aspect)
.mip_level(self.dst.range.mip_levels.start())
.base_array_layer(self.dst.range.array_layers.start())
.layer_count(self.dst.range.array_layers.len()),
)];
let info = vk::CopyBufferToImageInfo2::default()
.src_buffer(recorder.get_buffer_handle(self.src.id()))
.dst_image(recorder.get_image_handle(self.dst.id()))
.dst_image_layout(recorder.get_image_layout(self.dst.id()))
.regions(regions);
unsafe {
dev.cmd_copy_buffer_to_image2(cmd.raw(), &info);
}
}
}
impl Command for Copy<BufferRowSlices, TextureRegions> {
fn side_effects(&self, map: SideEffectMap) {
self.side_effects_inner(map)
}
fn apply(self, recorder: &mut CommandRecorder) {
let cmd = recorder.cmd_buffer();
let dev = &cmd.device().raw;
let regions = &self
.src
.rows
.iter()
.zip(self.dst.ranges.iter())
.map(|(src_row, dst_range)| {
vk::BufferImageCopy2::default()
.buffer_offset(src_row.offset)
.buffer_row_length(src_row.row_size)
.buffer_image_height(src_row.row_count)
.image_offset(dst_range.offset())
.image_extent(dst_range.extent())
.image_subresource(
vk::ImageSubresourceLayers::default()
.aspect_mask(dst_range.aspect)
.mip_level(dst_range.mip_levels.start())
.base_array_layer(dst_range.array_layers.start())
.layer_count(dst_range.array_layers.len()),
)
})
.collect::<Vec<_>>();
let info = vk::CopyBufferToImageInfo2::default()
.src_buffer(recorder.get_buffer_handle(self.src.id()))
.dst_image(recorder.get_image_handle(self.dst.id()))
.dst_image_layout(recorder.get_image_layout(self.dst.id()))
.regions(regions);
unsafe {
dev.cmd_copy_buffer_to_image2(cmd.raw(), &info);
}
}
}
impl Command for Copy<TextureRegion, BufferRowSlice> {
fn side_effects(&self, map: SideEffectMap) {
self.side_effects_inner(map)
}
fn apply(self, recorder: &mut CommandRecorder) {
let cmd = recorder.cmd_buffer();
let dev = &cmd.device().raw;
let regions = &[vk::BufferImageCopy2::default()
.buffer_offset(self.dst.row.offset)
.buffer_row_length(self.dst.row.row_size)
.buffer_image_height(self.dst.row.row_count)
.image_offset(self.src.range.offset())
.image_extent(self.src.range.extent())
.image_subresource(
vk::ImageSubresourceLayers::default()
.aspect_mask(self.src.range.aspect)
.mip_level(self.src.range.mip_levels.start())
.base_array_layer(self.src.range.array_layers.start())
.layer_count(self.src.range.array_layers.len()),
)];
let info = vk::CopyImageToBufferInfo2::default()
.dst_buffer(recorder.get_buffer_handle(self.dst.id()))
.src_image(recorder.get_image_handle(self.src.id()))
.src_image_layout(recorder.get_image_layout(self.src.id()))
.regions(regions);
unsafe {
dev.cmd_copy_image_to_buffer2(cmd.raw(), &info);
}
}
}
impl Command for Copy<TextureRegion, TextureRegion> {
fn side_effects(&self, map: SideEffectMap) {
self.side_effects_inner(map)
}
fn apply(self, recorder: &mut CommandRecorder) {
let cmd = recorder.cmd_buffer();
let dev = &cmd.device().raw;
let regions = &[vk::ImageCopy2::default()
.extent(self.src.range.extent())
.dst_offset(self.dst.range.offset())
.src_offset(self.src.range.offset())
.dst_subresource(
vk::ImageSubresourceLayers::default()
.aspect_mask(self.dst.range.aspect)
.mip_level(self.dst.range.mip_levels.start())
.base_array_layer(self.dst.range.array_layers.start())
.layer_count(self.dst.range.array_layers.len()),
)
.src_subresource(
vk::ImageSubresourceLayers::default()
.aspect_mask(self.src.range.aspect)
.mip_level(self.src.range.mip_levels.start())
.base_array_layer(self.src.range.array_layers.start())
.layer_count(self.src.range.array_layers.len()),
)];
let info = vk::CopyImageInfo2::default()
.dst_image(recorder.get_image_handle(self.dst.id()))
.dst_image_layout(recorder.get_image_layout(self.dst.id()))
.src_image(recorder.get_image_handle(self.src.id()))
.src_image_layout(recorder.get_image_layout(self.src.id()))
.regions(regions);
unsafe {
dev.cmd_copy_image2(cmd.raw(), &info);
}
}
}
impl Command for Copy<BufferSlice, BufferSlice> {
fn side_effects(&self, map: SideEffectMap) {
self.side_effects_inner(map)
}
fn apply(self, recorder: &mut CommandRecorder) {
let cmd = recorder.cmd_buffer();
let dev = &cmd.device().raw;
debug_assert_eq!(self.src.range.size, self.dst.range.size);
let regions = &[vk::BufferCopy2::default()
.dst_offset(self.dst.range.offset)
.src_offset(self.src.range.offset)
.size(self.src.range.size)];
let info = vk::CopyBufferInfo2::default()
.dst_buffer(recorder.get_buffer_handle(self.dst.id()))
.src_buffer(recorder.get_buffer_handle(self.src.id()))
.regions(regions);
unsafe {
dev.cmd_copy_buffer2(cmd.raw(), &info);
}
}
}
pub struct ClearTexture {
pub dst: Write<TextureRegion>,
pub clear_value: [f32; 4],
}
impl Command for ClearTexture {
fn side_effects(&self, mut map: SideEffectMap) {
self.dst.side_effect(map.reborrow().into_side_effect_map2(
vk::PipelineStageFlags2::TRANSFER,
vk::AccessFlags2::TRANSFER_WRITE,
Some(vk::ImageLayout::TRANSFER_DST_OPTIMAL),
));
}
fn apply(self, _recorder: &mut CommandRecorder) {}
}
pub struct UpdateBuffer {
pub dst: Write<BufferSlice>,
pub data: Vec<u8>,
}
impl Command for UpdateBuffer {
fn side_effects(&self, mut map: SideEffectMap) {
self.dst.side_effect(map.reborrow().into_side_effect_map2(
vk::PipelineStageFlags2::TRANSFER,
vk::AccessFlags2::TRANSFER_WRITE,
None,
));
}
fn apply(self, _recorder: &mut CommandRecorder) {}
}
pub struct BeginRendering {
pub color_attachments: Vec<TextureRegion>,
pub depth_attachment: Option<TextureRegion>,
pub stencil_attachment: Option<TextureRegion>,
pub area: (u32, u32),
pub layers: u32,
}
impl Command for BeginRendering {
fn side_effects(&self, mut map: SideEffectMap) {
for attachment in &self.color_attachments {
// The dependencies declared here need to be barrier'd before the
// render pass: the spec says pipelineBarrier inside of a render
// pass instance referencing framebuffer-local stages must contain
// only framebuffer-local stages.
// TODO: consider loadop and storeop?
attachment.side_effect(map.reborrow().into_side_effect_map2(
vk::PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT,
vk::AccessFlags2::COLOR_ATTACHMENT_READ,
Some(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL),
));
}
if let Some(depth) = &self.depth_attachment {
depth.side_effect(map.reborrow().into_side_effect_map2(
vk::PipelineStageFlags2::EARLY_FRAGMENT_TESTS,
vk::AccessFlags2::DEPTH_STENCIL_ATTACHMENT_READ,
Some(vk::ImageLayout::DEPTH_ATTACHMENT_OPTIMAL),
));
}
if let Some(stencil) = &self.stencil_attachment {
stencil.side_effect(map.reborrow().into_side_effect_map2(
vk::PipelineStageFlags2::EARLY_FRAGMENT_TESTS,
vk::AccessFlags2::DEPTH_STENCIL_ATTACHMENT_READ,
Some(vk::ImageLayout::STENCIL_ATTACHMENT_OPTIMAL),
));
}
}
fn apply(self, _recorder: &mut CommandRecorder) {}
}
pub struct EndRendering;
impl Command for EndRendering {
fn side_effects(&self, _map: SideEffectMap) {
// No resource access, but ends the render pass
}
fn apply(self, _recorder: &mut CommandRecorder) {}
}
pub struct BindPipeline(pub vk::Pipeline, pub vk::PipelineBindPoint);
impl Command for BindPipeline {
fn side_effects(&self, _map: SideEffectMap) {
// No resource access, but affects the pipeline state
}
fn apply(self, recorder: &mut CommandRecorder) {
let cmd = recorder.cmd_buffer();
let dev = &cmd.device().raw;
unsafe {
dev.cmd_bind_pipeline(cmd.raw(), self.1, self.0);
}
}
}
pub struct BindVertexBuffers {
pub buffers: Vec<BufferSlice>,
pub first_binding: u32,
}
impl Command for BindVertexBuffers {
fn side_effects(&self, mut map: SideEffectMap) {
for buffer in &self.buffers {
buffer.side_effect(map.reborrow().into_side_effect_map2(
vk::PipelineStageFlags2::VERTEX_ATTRIBUTE_INPUT,
vk::AccessFlags2::VERTEX_ATTRIBUTE_READ,
None,
));
}
}
fn apply(self, recorder: &mut CommandRecorder) {
let cmd = recorder.cmd_buffer();
let dev = &cmd.device().raw;
let buffers = self
.buffers
.iter()
.map(|buffer| recorder.get_buffer_handle(buffer.id()))
.collect::<Vec<_>>();
let offsets = self
.buffers
.iter()
.map(|buffer| buffer.range.offset)
.collect::<Vec<_>>();
unsafe {
dev.cmd_bind_vertex_buffers(cmd.raw(), self.first_binding, &buffers, &offsets);
}
}
}
pub struct BindIndexBuffer(pub BufferSlice, pub IndexFormat);
impl Command for BindIndexBuffer {
fn side_effects(&self, mut map: SideEffectMap) {
self.0.side_effect(map.reborrow().into_side_effect_map2(
vk::PipelineStageFlags2::INDEX_INPUT,
vk::AccessFlags2::INDEX_READ,
None,
));
}
fn apply(self, recorder: &mut CommandRecorder) {
let cmd = recorder.cmd_buffer();
let dev = &cmd.device().raw;
let buffer = recorder.get_buffer_handle(self.0.id());
let index_type = match self.1 {
IndexFormat::Uint8 => vk::IndexType::UINT8_KHR,
IndexFormat::Uint16 => vk::IndexType::UINT16,
IndexFormat::Uint32 => vk::IndexType::UINT32,
};
unsafe {
dev.cmd_bind_index_buffer(cmd.raw(), buffer, self.0.range.offset, index_type);
}
}
}
pub struct BindDescriptorSets {
pub first_set: u32,
pub pipeline_layout: vk::PipelineLayout,
pub bind_point: vk::PipelineBindPoint,
pub sets: Vec<vk::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) {
let cmd = recorder.cmd_buffer();
let dev = &cmd.device().raw;
unsafe {
dev.cmd_bind_descriptor_sets(
cmd.raw(),
self.bind_point,
self.pipeline_layout,
self.first_set,
&self.sets,
&[],
);
}
}
}
pub struct SetViewport {
pub x: f32,
pub y: f32,
pub width: f32,
pub height: f32,
pub min_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 x: u32,
pub y: u32,
pub width: 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 pipeline_layout: vk::PipelineLayout,
pub stage_flags: vk::ShaderStageFlags,
pub offset: u32,
pub data: Vec<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) {
let cmd = recorder.cmd_buffer();
let dev = &cmd.device().raw;
unsafe {
dev.cmd_push_constants(
cmd.raw(),
self.pipeline_layout,
self.stage_flags,
self.offset,
&self.data,
);
}
}
}
pub struct DrawData {
pub vertex_count: u32,
pub instance_count: u32,
pub first_vertex: u32,
pub first_instance: u32,
}
pub struct DrawIndexedData {
pub index_count: u32,
pub instance_count: u32,
pub first_index: u32,
pub vertex_offset: i32,
pub first_instance: u32,
}
pub struct DrawIndirectData {
pub indirect_buffer: BufferSlice,
pub count: u32,
pub stride: u32,
}
pub struct DrawIndexedIndirectData {
pub indirect_buffer: BufferSlice,
pub count: u32,
pub stride: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum IndexFormat {
Uint8,
Uint16,
Uint32,
}
pub struct Draw<T: IsDrawData> {
pub data: T,
pub vertex_buffers: Vec<BufferSlice>,
pub index_buffer: Option<(BufferSlice, IndexFormat)>,
}
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 {
vertex_buffer.side_effect(map.reborrow().into_side_effect_map2(
vk::PipelineStageFlags2::VERTEX_ATTRIBUTE_INPUT,
vk::AccessFlags2::VERTEX_ATTRIBUTE_READ,
None,
));
}
if let Some((index_buffer, _)) = &self.index_buffer {
index_buffer.side_effect(map.reborrow().into_side_effect_map2(
vk::PipelineStageFlags2::INDEX_INPUT,
vk::AccessFlags2::INDEX_READ,
None,
));
}
}
fn apply(self, _recorder: &mut CommandRecorder) {}
}
pub struct PipelineBarrier;
impl Command for PipelineBarrier {
fn side_effects(&self, _map: SideEffectMap) {}
fn apply(self, _recorder: &mut CommandRecorder) {
todo!()
}
}
pub trait Command: Sized {
fn side_effects(&self, map: SideEffectMap);
fn apply(self, recorder: &mut CommandRecorder);
}
mod sealed {
pub trait IsDrawData {
fn side_effects(&self, _map: super::SideEffectMap) {}
}
pub trait InsideRenderPass {}
pub trait OutsideRenderPass {}
}
use ash::vk;
pub(super) use sealed::*;
impl IsDrawData for DrawData {}
impl IsDrawData for DrawIndexedData {}
impl IsDrawData for DrawIndirectData {
fn side_effects(&self, mut map: SideEffectMap) {
self.indirect_buffer
.side_effect(map.reborrow().into_side_effect_map2(
vk::PipelineStageFlags2::DRAW_INDIRECT,
vk::AccessFlags2::INDIRECT_COMMAND_READ,
None,
));
}
}
impl IsDrawData for DrawIndexedIndirectData {
fn side_effects(&self, mut map: SideEffectMap) {
self.indirect_buffer
.side_effect(map.reborrow().into_side_effect_map2(
vk::PipelineStageFlags2::DRAW_INDIRECT,
vk::AccessFlags2::INDIRECT_COMMAND_READ,
None,
));
}
}
impl OutsideRenderPass for BeginRendering {}
impl InsideRenderPass for EndRendering {}
impl<T: Resource, U: Resource> OutsideRenderPass for Copy<T, U> {}
impl OutsideRenderPass for ClearTexture {}
impl OutsideRenderPass for UpdateBuffer {}
impl InsideRenderPass for BindPipeline {}
impl OutsideRenderPass for BindPipeline {}
impl InsideRenderPass for BindVertexBuffers {}
impl OutsideRenderPass for BindVertexBuffers {}
impl InsideRenderPass for BindIndexBuffer {}
impl OutsideRenderPass for BindIndexBuffer {}
impl InsideRenderPass for BindDescriptorSets {}
impl OutsideRenderPass for BindDescriptorSets {}
impl OutsideRenderPass for PushConstants {}
impl InsideRenderPass for SetViewport {}
impl InsideRenderPass for SetScissor {}
impl InsideRenderPass for PushConstants {}
impl<T: IsDrawData> InsideRenderPass for Draw<T> {}
impl<T: IsDrawData> InsideRenderPass for T {}