//! 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 { pub resource: T, pub access: vk::AccessFlags2, pub stage: vk::PipelineStageFlags2, pub layout: Option, } impl Command for ImportResource { 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, pub dst: TextureRegion, } pub struct CopyTextureToBuffer { pub src: TextureRegion, pub rows: Option, pub dst: BufferSlice, } pub struct CopyBuffersToTextures { pub src: BufferSlice, pub src_rows: Vec, pub dst: Texture, pub src_ranges: Vec<(TextureRegion, vk::Offset3D, vk::Extent3D)>, } pub struct Copy { pub src: T, pub dst: U, } fn copy_side_effects_inner(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 { 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::>(); 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 { 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 { 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 { 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, 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, pub data: Vec, } 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, pub depth_attachment: Option, pub stencil_attachment: Option, 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, 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::>(); let offsets = self .buffers .iter() .map(|buffer| buffer.range.offset) .collect::>(); 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, } 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, } 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 { pub data: T, pub vertex_buffers: Vec, pub index_buffer: Option<(BufferSlice, IndexFormat)>, } impl Command for Draw { 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 OutsideRenderPass for Copy {} 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 InsideRenderPass for Draw {} impl InsideRenderPass for T {}