//! 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 std::marker::PhantomData; use crate::{ device::DeviceOwned, render_graph::recorder::{CommandRecorder, SideEffectMap}, }; use super::resources::*; pub struct ImportResource { pub resource: T, pub range: Option, pub access: vk::AccessFlags2, pub stage: vk::PipelineStageFlags2, pub layout: Option, } impl Command for ImportResource { fn side_effects(&self, mut map: SideEffectMap) { let range = self.range.unwrap_or_else(|| self.resource.full_range()); map.with(self.stage, self.access, self.layout) .insert_range(self.resource.id(), range); } fn apply(self, _recorder: &mut CommandRecorder) { // No actual GPU commands needed for import, as it's just a logical operation } } pub trait BufferImageBufferCopyRegion { fn regions(&self) -> &[BufferRows]; } impl BufferImageBufferCopyRegion for BufferRows { fn regions(&self) -> &[BufferRows] { core::slice::from_ref(self) } } impl> BufferImageBufferCopyRegion for T { fn regions(&self) -> &[BufferRows] { self.as_ref() } } pub trait ImageCopyRegion { fn regions(&self) -> &[TextureRegion]; } impl ImageCopyRegion for TextureRegion { fn regions(&self) -> &[TextureRegion] { core::slice::from_ref(self) } } impl]>> ImageCopyRegion for T { fn regions(&self) -> &[TextureRegion] { self.as_ref() } } pub trait BufferCopyRegion { fn regions(&self) -> &[BufferRange]; } impl BufferCopyRegion for BufferRange { fn regions(&self) -> &[BufferRange] { core::slice::from_ref(self) } } impl> BufferCopyRegion for T { fn regions(&self) -> &[BufferRange] { self.as_ref() } } pub struct CopyBuffers { pub src: Buffer, pub src_region: T, pub dst: Buffer, pub dst_region: T, } pub struct CopyTextures, R: IntoImageSubresourceRange> { pub src: Texture, pub src_region: T, pub dst: Texture, pub dst_region: T, _pd: PhantomData, } pub struct CopyBufferToTexture< T: BufferImageBufferCopyRegion, U: ImageCopyRegion, R: IntoImageSubresourceLayers, > { pub src: Buffer, pub src_region: T, pub dst: Texture, pub dst_region: U, _pd: PhantomData, } pub struct CopyTextureToBuffer< T: ImageCopyRegion, U: BufferImageBufferCopyRegion, R: IntoImageSubresourceLayers, > { pub src: Texture, pub src_region: T, pub dst: Buffer, pub dst_region: U, _pd: PhantomData, } impl Command for CopyBufferToTexture where T: BufferImageBufferCopyRegion, U: ImageCopyRegion, R: IntoImageSubresourceLayers, { fn side_effects(&self, mut map: SideEffectMap) { let block_size = self.dst.format().block_size() as u64; let texels_per_block = self.dst.format().texels_per_block() as u64; let src_id = self.src.id(); let dst_id = self.dst.id(); for (src_region, dst_region) in self .src_region .regions() .iter() .zip(self.dst_region.regions()) { let texel_count = if src_region.row_size != 0 { let texel_count = src_region.row_size as u64 * src_region.row_count as u64; debug_assert!( texel_count % texels_per_block == 0, "Buffer copy region must be a multiple of the block size" ); texel_count } else { dst_region.extent.width as u64 * dst_region.extent.height as u64 * dst_region.extent.depth as u64 }; let byte_size = texel_count.div_ceil(texels_per_block) * block_size; map.with( vk::PipelineStageFlags2::COPY, vk::AccessFlags2::TRANSFER_READ, Some(vk::ImageLayout::TRANSFER_SRC_OPTIMAL), ) .insert_range( src_id, BufferRange { offset: src_region.offset, size: byte_size, }, ); map.with( vk::PipelineStageFlags2::COPY, vk::AccessFlags2::TRANSFER_WRITE, Some(vk::ImageLayout::TRANSFER_DST_OPTIMAL), ) .insert_range(dst_id, dst_region.range.into_range()); } } fn apply(self, recorder: &mut CommandRecorder) { let cmd = recorder.cmd_buffer(); let dev = &cmd.device().raw; let regions = self .src_region .regions() .iter() .zip(self.dst_region.regions()) .map(|(src_region, dst_region)| { vk::BufferImageCopy2::default() .buffer_offset(src_region.offset) .buffer_row_length(src_region.row_size) .buffer_image_height(src_region.row_count) .image_offset(dst_region.offset) .image_extent(dst_region.extent) .image_subresource(dst_region.range.into_layers()) }) .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(vk::ImageLayout::TRANSFER_DST_OPTIMAL) .regions(®ions); unsafe { dev.cmd_copy_buffer_to_image2(cmd.raw(), &info); } } } impl Command for CopyTextureToBuffer where T: ImageCopyRegion, U: BufferImageBufferCopyRegion, R: IntoImageSubresourceLayers, { fn side_effects(&self, mut map: SideEffectMap) { let block_size = self.src.format().block_size() as u64; let texels_per_block = self.src.format().texels_per_block() as u64; let src_id = self.src.id(); let dst_id = self.dst.id(); for (src_region, dst_region) in self .src_region .regions() .iter() .zip(self.dst_region.regions()) { let texel_count = if dst_region.row_size != 0 { let texel_count = dst_region.row_size as u64 * dst_region.row_count as u64; debug_assert!( texel_count % texels_per_block == 0, "Buffer copy region must be a multiple of the block size" ); texel_count } else { src_region.extent.width as u64 * src_region.extent.height as u64 * src_region.extent.depth as u64 }; let byte_size = texel_count.div_ceil(texels_per_block) * block_size; map.with( vk::PipelineStageFlags2::COPY, vk::AccessFlags2::TRANSFER_READ, Some(vk::ImageLayout::TRANSFER_SRC_OPTIMAL), ) .insert_range(dst_id, src_region.range.into_range()); map.with( vk::PipelineStageFlags2::COPY, vk::AccessFlags2::TRANSFER_WRITE, Some(vk::ImageLayout::TRANSFER_DST_OPTIMAL), ) .insert_range( src_id, BufferRange { offset: dst_region.offset, size: byte_size, }, ); } } fn apply(self, recorder: &mut CommandRecorder) { let cmd = recorder.cmd_buffer(); let dev = &cmd.device().raw; let regions = self .src_region .regions() .iter() .zip(self.dst_region.regions()) .map(|(src_region, dst_region)| { vk::BufferImageCopy2::default() .buffer_offset(dst_region.offset) .buffer_row_length(dst_region.row_size) .buffer_image_height(dst_region.row_count) .image_offset(src_region.offset) .image_extent(src_region.extent) .image_subresource(src_region.range.into_layers()) }) .collect::>(); let info = vk::CopyImageToBufferInfo2::default() .dst_buffer(recorder.get_buffer_handle(self.src.id())) .src_image(recorder.get_image_handle(self.dst.id())) .src_image_layout(vk::ImageLayout::TRANSFER_SRC_OPTIMAL) .regions(®ions); unsafe { dev.cmd_copy_image_to_buffer2(cmd.raw(), &info); } } } impl Command for CopyBuffers where T: BufferCopyRegion, { fn side_effects(&self, mut map: SideEffectMap) { for region in self.src_region.regions() { map.with( vk::PipelineStageFlags2::COPY, vk::AccessFlags2::TRANSFER_READ, None, ) .insert_range(self.src.id(), *region); } for region in self.dst_region.regions() { map.with( vk::PipelineStageFlags2::COPY, vk::AccessFlags2::TRANSFER_WRITE, None, ) .insert_range(self.dst.id(), *region); } } fn apply(self, recorder: &mut CommandRecorder) { let cmd = recorder.cmd_buffer(); let dev = &cmd.device().raw; let regions = self .src_region .regions() .iter() .zip(self.dst_region.regions()) .map(|(src_region, dst_region)| { vk::BufferCopy2::default() .src_offset(src_region.offset) .dst_offset(dst_region.offset) .size(src_region.size) }) .collect::>(); let info = vk::CopyBufferInfo2::default() .dst_buffer(recorder.get_buffer_handle(self.dst.id())) .src_buffer(recorder.get_buffer_handle(self.src.id())) .regions(®ions); unsafe { dev.cmd_copy_buffer2(cmd.raw(), &info); } } } impl Command for CopyTextures where T: ImageCopyRegion, R: IntoImageSubresourceLayers, { fn side_effects(&self, mut map: SideEffectMap) { for region in self.src_region.regions() { map.with( vk::PipelineStageFlags2::COPY, vk::AccessFlags2::TRANSFER_READ, None, ) .insert_range(self.src.id(), region.range.into_range()); } for region in self.dst_region.regions() { map.with( vk::PipelineStageFlags2::COPY, vk::AccessFlags2::TRANSFER_WRITE, None, ) .insert_range(self.dst.id(), region.range.into_range()); } } fn apply(self, recorder: &mut CommandRecorder) { let cmd = recorder.cmd_buffer(); let dev = &cmd.device().raw; let regions = self .src_region .regions() .iter() .zip(self.dst_region.regions()) .map(|(src_region, dst_region)| { vk::ImageCopy2::default() .src_offset(src_region.offset) .src_subresource(src_region.range.into_layers()) .dst_offset(dst_region.offset) .dst_subresource(dst_region.range.into_layers()) .extent(src_region.extent) }) .collect::>(); let info = vk::CopyImageInfo2::default() .src_image(recorder.get_image_handle(self.src.id())) .src_image_layout(vk::ImageLayout::TRANSFER_SRC_OPTIMAL) .dst_image(recorder.get_image_handle(self.dst.id())) .dst_image_layout(vk::ImageLayout::TRANSFER_DST_OPTIMAL) .regions(®ions); unsafe { dev.cmd_copy_image2(cmd.raw(), &info); } } } pub struct ClearTexture { pub dst: Texture, pub dst_region: TextureRegion, pub clear_value: [f32; 4], } impl Command for ClearTexture { fn side_effects(&self, mut map: SideEffectMap) { map.with( vk::PipelineStageFlags2::CLEAR, vk::AccessFlags2::TRANSFER_WRITE, Some(vk::ImageLayout::TRANSFER_DST_OPTIMAL), ) .insert_range(self.dst.id(), self.dst_region.range.into_range()); } fn apply(self, recorder: &mut CommandRecorder) { let cmd = recorder.cmd_buffer(); let dev = &cmd.device().raw; let values = vk::ClearColorValue { float32: self.clear_value, }; unsafe { dev.cmd_clear_color_image( cmd.raw(), recorder.get_image_handle(self.dst.id()), vk::ImageLayout::TRANSFER_DST_OPTIMAL, &values, &[self.dst_region.range.into_range()], ); } } } pub struct UpdateBuffer { pub dst: Buffer, pub dst_region: BufferRange, pub data: Vec, } impl Command for UpdateBuffer { fn side_effects(&self, mut map: SideEffectMap) { map.with( vk::PipelineStageFlags2::TRANSFER, vk::AccessFlags2::TRANSFER_WRITE, None, ) .insert_range(self.dst.id(), self.dst_region); } fn apply(self, _recorder: &mut CommandRecorder) {} } pub struct RenderAttachment { pub texture: Texture, pub region: vk::ImageSubresourceRange, pub layout: vk::ImageLayout, pub load_op: vk::AttachmentLoadOp, pub store_op: vk::AttachmentStoreOp, } 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. map.with( vk::PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT, if attachment.load_op == vk::AttachmentLoadOp::LOAD { vk::AccessFlags2::COLOR_ATTACHMENT_READ } else { vk::AccessFlags2::empty() }, Some(attachment.layout), ) .insert_range(attachment.texture.id(), attachment.region); } if let Some(depth) = &self.depth_attachment { map.with( vk::PipelineStageFlags2::EARLY_FRAGMENT_TESTS, if depth.load_op == vk::AttachmentLoadOp::LOAD { vk::AccessFlags2::DEPTH_STENCIL_ATTACHMENT_READ } else { vk::AccessFlags2::empty() }, Some(depth.layout), ) .insert_range(depth.texture.id(), depth.region); } if let Some(stencil) = &self.stencil_attachment { map.with( vk::PipelineStageFlags2::EARLY_FRAGMENT_TESTS, if stencil.load_op == vk::AttachmentLoadOp::LOAD { vk::AccessFlags2::DEPTH_STENCIL_ATTACHMENT_READ } else { vk::AccessFlags2::empty() }, Some(stencil.layout), ) .insert_range(stencil.texture.id(), stencil.region); } } fn apply(self, _recorder: &mut CommandRecorder) {} } pub struct EndRendering; impl Command for EndRendering { fn side_effects(&self, _map: SideEffectMap) { // TODO: need the beginrendering struct to declare the store-op dependencies. } 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 { map.with( vk::PipelineStageFlags2::VERTEX_ATTRIBUTE_INPUT, vk::AccessFlags2::VERTEX_ATTRIBUTE_READ, None, ) .insert_range(buffer.id(), buffer.range); } } 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) { map.with( vk::PipelineStageFlags2::INDEX_INPUT, vk::AccessFlags2::INDEX_READ, None, ) .insert_range(self.0.id(), self.0.range); } 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, } impl Command for Draw { fn side_effects(&self, mut map: SideEffectMap) { self.data.side_effects(map.reborrow()); } 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; use ash_formats::FormatExt; pub(super) use sealed::*; impl IsDrawData for DrawData {} impl IsDrawData for DrawIndexedData {} impl IsDrawData for DrawIndirectData { fn side_effects(&self, mut map: SideEffectMap) { map.with( vk::PipelineStageFlags2::DRAW_INDIRECT, vk::AccessFlags2::INDIRECT_COMMAND_READ, None, ) .insert_range(self.indirect_buffer.id(), self.indirect_buffer.range); } } impl IsDrawData for DrawIndexedIndirectData { fn side_effects(&self, mut map: SideEffectMap) { map.with( vk::PipelineStageFlags2::DRAW_INDIRECT, vk::AccessFlags2::INDIRECT_COMMAND_READ, None, ) .insert_range(self.indirect_buffer.id(), self.indirect_buffer.range); } } impl OutsideRenderPass for BeginRendering {} impl InsideRenderPass for EndRendering {} 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 {}