diff options
Diffstat (limited to 'src/command_buffer/sys.rs')
-rw-r--r-- | src/command_buffer/sys.rs | 1972 |
1 files changed, 1972 insertions, 0 deletions
diff --git a/src/command_buffer/sys.rs b/src/command_buffer/sys.rs new file mode 100644 index 0000000..0fb8b58 --- /dev/null +++ b/src/command_buffer/sys.rs @@ -0,0 +1,1972 @@ +// Copyright (c) 2016 The vulkano developers +// Licensed under the Apache License, Version 2.0 +// <LICENSE-APACHE or +// https://www.apache.org/licenses/LICENSE-2.0> or the MIT +// license <LICENSE-MIT or https://opensource.org/licenses/MIT>, +// at your option. All files in the project carrying such +// notice may not be copied, modified, or distributed except +// according to those terms. + +use crate::buffer::BufferAccess; +use crate::buffer::BufferInner; +use crate::buffer::TypedBufferAccess; +use crate::check_errors; +use crate::command_buffer::pool::UnsafeCommandPoolAlloc; +use crate::command_buffer::CommandBufferInheritance; +use crate::command_buffer::CommandBufferLevel; +use crate::command_buffer::CommandBufferUsage; +use crate::command_buffer::SecondaryCommandBuffer; +use crate::command_buffer::SubpassContents; +use crate::descriptor_set::sys::UnsafeDescriptorSet; +use crate::device::Device; +use crate::device::DeviceOwned; +use crate::format::ClearValue; +use crate::format::FormatTy; +use crate::image::ImageAccess; +use crate::image::ImageAspect; +use crate::image::ImageAspects; +use crate::image::ImageLayout; +use crate::image::SampleCount; +use crate::pipeline::depth_stencil::StencilFaces; +use crate::pipeline::input_assembly::IndexType; +use crate::pipeline::layout::PipelineLayout; +use crate::pipeline::shader::ShaderStages; +use crate::pipeline::viewport::Scissor; +use crate::pipeline::viewport::Viewport; +use crate::pipeline::ComputePipelineAbstract; +use crate::pipeline::GraphicsPipelineAbstract; +use crate::pipeline::PipelineBindPoint; +use crate::query::QueriesRange; +use crate::query::Query; +use crate::query::QueryControlFlags; +use crate::query::QueryResultElement; +use crate::query::QueryResultFlags; +use crate::render_pass::FramebufferAbstract; +use crate::sampler::Filter; +use crate::sync::AccessFlags; +use crate::sync::Event; +use crate::sync::PipelineStage; +use crate::sync::PipelineStages; +use crate::DeviceSize; +use crate::OomError; +use crate::VulkanObject; +use ash::vk::Handle; +use smallvec::SmallVec; +use std::ffi::CStr; +use std::fmt; +use std::mem; +use std::ops::Range; +use std::sync::Arc; + +/// Command buffer being built. +/// +/// You can add commands to an `UnsafeCommandBufferBuilder` by using the `AddCommand` trait. +/// The `AddCommand<&Cmd>` trait is implemented on the `UnsafeCommandBufferBuilder` for any `Cmd` +/// that is a raw Vulkan command. +/// +/// When you are finished adding commands, you can use the `CommandBufferBuild` trait to turn this +/// builder into an `UnsafeCommandBuffer`. +pub struct UnsafeCommandBufferBuilder { + command_buffer: ash::vk::CommandBuffer, + device: Arc<Device>, + usage: CommandBufferUsage, +} + +impl fmt::Debug for UnsafeCommandBufferBuilder { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "<Vulkan command buffer builder #{}>", + self.command_buffer.as_raw() + ) + } +} + +impl UnsafeCommandBufferBuilder { + /// Creates a new builder, for recording commands. + /// + /// # Safety + /// + /// - `pool_alloc` must outlive the returned builder and its created command buffer. + /// - `kind` must match how `pool_alloc` was created. + /// - All submitted commands must be valid and follow the requirements of the Vulkan specification. + /// - Any resources used by submitted commands must outlive the returned builder and its created command buffer. They must be protected against data races through manual synchronization. + /// + /// > **Note**: Some checks are still made with `debug_assert!`. Do not expect to be able to + /// > submit invalid commands. + pub unsafe fn new<F>( + pool_alloc: &UnsafeCommandPoolAlloc, + level: CommandBufferLevel<F>, + usage: CommandBufferUsage, + ) -> Result<UnsafeCommandBufferBuilder, OomError> + where + F: FramebufferAbstract, + { + let secondary = match level { + CommandBufferLevel::Primary => false, + CommandBufferLevel::Secondary(..) => true, + }; + + let device = pool_alloc.device().clone(); + let fns = device.fns(); + + let vk_flags = { + let a = ash::vk::CommandBufferUsageFlags::from(usage); + let b = match level { + CommandBufferLevel::Secondary(ref inheritance) + if inheritance.render_pass.is_some() => + { + ash::vk::CommandBufferUsageFlags::RENDER_PASS_CONTINUE + } + _ => ash::vk::CommandBufferUsageFlags::empty(), + }; + + a | b + }; + + let (rp, sp, fb) = match level { + CommandBufferLevel::Secondary(CommandBufferInheritance { + render_pass: Some(ref render_pass), + .. + }) => { + let rp = render_pass.subpass.render_pass().inner().internal_object(); + let sp = render_pass.subpass.index(); + let fb = match render_pass.framebuffer { + Some(ref fb) => { + // TODO: debug assert that the framebuffer is compatible with + // the render pass? + FramebufferAbstract::inner(fb).internal_object() + } + None => ash::vk::Framebuffer::null(), + }; + (rp, sp, fb) + } + _ => (ash::vk::RenderPass::null(), 0, ash::vk::Framebuffer::null()), + }; + + let (oqe, qf, ps) = match level { + CommandBufferLevel::Secondary(CommandBufferInheritance { + occlusion_query, + query_statistics_flags, + .. + }) => { + let ps: ash::vk::QueryPipelineStatisticFlags = query_statistics_flags.into(); + let (oqe, qf) = match occlusion_query { + Some(flags) => { + let qf = if flags.precise { + ash::vk::QueryControlFlags::PRECISE + } else { + ash::vk::QueryControlFlags::empty() + }; + (ash::vk::TRUE, qf) + } + None => (0, ash::vk::QueryControlFlags::empty()), + }; + + (oqe, qf, ps) + } + _ => ( + 0, + ash::vk::QueryControlFlags::empty(), + ash::vk::QueryPipelineStatisticFlags::empty(), + ), + }; + + let inheritance = ash::vk::CommandBufferInheritanceInfo { + render_pass: rp, + subpass: sp, + framebuffer: fb, + occlusion_query_enable: oqe, + query_flags: qf, + pipeline_statistics: ps, + ..Default::default() + }; + + let infos = ash::vk::CommandBufferBeginInfo { + flags: vk_flags, + p_inheritance_info: &inheritance, + ..Default::default() + }; + + check_errors( + fns.v1_0 + .begin_command_buffer(pool_alloc.internal_object(), &infos), + )?; + + Ok(UnsafeCommandBufferBuilder { + command_buffer: pool_alloc.internal_object(), + device: device.clone(), + usage, + }) + } + + /// Turns the builder into an actual command buffer. + #[inline] + pub fn build(self) -> Result<UnsafeCommandBuffer, OomError> { + unsafe { + let fns = self.device.fns(); + check_errors(fns.v1_0.end_command_buffer(self.command_buffer))?; + + Ok(UnsafeCommandBuffer { + command_buffer: self.command_buffer, + device: self.device.clone(), + usage: self.usage, + }) + } + } + + /// Calls `vkCmdBeginQuery` on the builder. + #[inline] + pub unsafe fn begin_query(&mut self, query: Query, flags: QueryControlFlags) { + let fns = self.device().fns(); + let cmd = self.internal_object(); + let flags = if flags.precise { + ash::vk::QueryControlFlags::PRECISE + } else { + ash::vk::QueryControlFlags::empty() + }; + fns.v1_0 + .cmd_begin_query(cmd, query.pool().internal_object(), query.index(), flags); + } + + /// Calls `vkCmdBeginRenderPass` on the builder. + #[inline] + pub unsafe fn begin_render_pass<F, I>( + &mut self, + framebuffer: &F, + subpass_contents: SubpassContents, + clear_values: I, + ) where + F: ?Sized + FramebufferAbstract, + I: IntoIterator<Item = ClearValue>, + { + let fns = self.device().fns(); + let cmd = self.internal_object(); + + // TODO: allow passing a different render pass + let raw_render_pass = framebuffer.render_pass().inner().internal_object(); + let raw_framebuffer = framebuffer.inner().internal_object(); + + let raw_clear_values: SmallVec<[_; 12]> = clear_values + .into_iter() + .map(|clear_value| match clear_value { + ClearValue::None => ash::vk::ClearValue { + color: ash::vk::ClearColorValue { float32: [0.0; 4] }, + }, + ClearValue::Float(val) => ash::vk::ClearValue { + color: ash::vk::ClearColorValue { float32: val }, + }, + ClearValue::Int(val) => ash::vk::ClearValue { + color: ash::vk::ClearColorValue { int32: val }, + }, + ClearValue::Uint(val) => ash::vk::ClearValue { + color: ash::vk::ClearColorValue { uint32: val }, + }, + ClearValue::Depth(val) => ash::vk::ClearValue { + depth_stencil: ash::vk::ClearDepthStencilValue { + depth: val, + stencil: 0, + }, + }, + ClearValue::Stencil(val) => ash::vk::ClearValue { + depth_stencil: ash::vk::ClearDepthStencilValue { + depth: 0.0, + stencil: val, + }, + }, + ClearValue::DepthStencil((depth, stencil)) => ash::vk::ClearValue { + depth_stencil: ash::vk::ClearDepthStencilValue { depth, stencil }, + }, + }) + .collect(); + + // TODO: allow customizing + let rect = [ + 0..framebuffer.dimensions()[0], + 0..framebuffer.dimensions()[1], + ]; + + let begin = ash::vk::RenderPassBeginInfo { + render_pass: raw_render_pass, + framebuffer: raw_framebuffer, + render_area: ash::vk::Rect2D { + offset: ash::vk::Offset2D { + x: rect[0].start as i32, + y: rect[1].start as i32, + }, + extent: ash::vk::Extent2D { + width: rect[0].end - rect[0].start, + height: rect[1].end - rect[1].start, + }, + }, + clear_value_count: raw_clear_values.len() as u32, + p_clear_values: raw_clear_values.as_ptr(), + ..Default::default() + }; + + fns.v1_0 + .cmd_begin_render_pass(cmd, &begin, subpass_contents.into()); + } + + /// Calls `vkCmdBindDescriptorSets` on the builder. + /// + /// Does nothing if the list of descriptor sets is empty, as it would be a no-op and isn't a + /// valid usage of the command anyway. + #[inline] + pub unsafe fn bind_descriptor_sets<'s, S, I>( + &mut self, + pipeline_bind_point: PipelineBindPoint, + pipeline_layout: &PipelineLayout, + first_binding: u32, + sets: S, + dynamic_offsets: I, + ) where + S: IntoIterator<Item = &'s UnsafeDescriptorSet>, + I: IntoIterator<Item = u32>, + { + let fns = self.device().fns(); + let cmd = self.internal_object(); + + let sets: SmallVec<[_; 12]> = sets.into_iter().map(|s| s.internal_object()).collect(); + if sets.is_empty() { + return; + } + let dynamic_offsets: SmallVec<[u32; 32]> = dynamic_offsets.into_iter().collect(); + + let num_bindings = sets.len() as u32; + debug_assert!( + first_binding + num_bindings <= pipeline_layout.descriptor_set_layouts().len() as u32 + ); + + fns.v1_0.cmd_bind_descriptor_sets( + cmd, + pipeline_bind_point.into(), + pipeline_layout.internal_object(), + first_binding, + num_bindings, + sets.as_ptr(), + dynamic_offsets.len() as u32, + dynamic_offsets.as_ptr(), + ); + } + + /// Calls `vkCmdBindIndexBuffer` on the builder. + #[inline] + pub unsafe fn bind_index_buffer<B>(&mut self, buffer: &B, index_ty: IndexType) + where + B: ?Sized + BufferAccess, + { + let fns = self.device().fns(); + let cmd = self.internal_object(); + + let inner = buffer.inner(); + debug_assert!(inner.offset < inner.buffer.size()); + debug_assert!(inner.buffer.usage().index_buffer); + + fns.v1_0.cmd_bind_index_buffer( + cmd, + inner.buffer.internal_object(), + inner.offset, + index_ty.into(), + ); + } + + /// Calls `vkCmdBindPipeline` on the builder with a compute pipeline. + #[inline] + pub unsafe fn bind_pipeline_compute<Cp>(&mut self, pipeline: &Cp) + where + Cp: ?Sized + ComputePipelineAbstract, + { + let fns = self.device().fns(); + let cmd = self.internal_object(); + fns.v1_0.cmd_bind_pipeline( + cmd, + ash::vk::PipelineBindPoint::COMPUTE, + pipeline.inner().internal_object(), + ); + } + + /// Calls `vkCmdBindPipeline` on the builder with a graphics pipeline. + #[inline] + pub unsafe fn bind_pipeline_graphics<Gp>(&mut self, pipeline: &Gp) + where + Gp: ?Sized + GraphicsPipelineAbstract, + { + let fns = self.device().fns(); + let cmd = self.internal_object(); + let inner = GraphicsPipelineAbstract::inner(pipeline).internal_object(); + fns.v1_0 + .cmd_bind_pipeline(cmd, ash::vk::PipelineBindPoint::GRAPHICS, inner); + } + + /// Calls `vkCmdBindVertexBuffers` on the builder. + /// + /// Does nothing if the list of buffers is empty, as it would be a no-op and isn't a valid + /// usage of the command anyway. + #[inline] + pub unsafe fn bind_vertex_buffers( + &mut self, + first_binding: u32, + params: UnsafeCommandBufferBuilderBindVertexBuffer, + ) { + debug_assert_eq!(params.raw_buffers.len(), params.offsets.len()); + + if params.raw_buffers.is_empty() { + return; + } + + let fns = self.device().fns(); + let cmd = self.internal_object(); + + let num_bindings = params.raw_buffers.len() as u32; + + debug_assert!({ + let max_bindings = self + .device() + .physical_device() + .properties() + .max_vertex_input_bindings; + first_binding + num_bindings <= max_bindings + }); + + fns.v1_0.cmd_bind_vertex_buffers( + cmd, + first_binding, + num_bindings, + params.raw_buffers.as_ptr(), + params.offsets.as_ptr(), + ); + } + + /// Calls `vkCmdCopyImage` on the builder. + /// + /// Does nothing if the list of regions is empty, as it would be a no-op and isn't a valid + /// usage of the command anyway. + #[inline] + pub unsafe fn copy_image<S, D, R>( + &mut self, + source: &S, + source_layout: ImageLayout, + destination: &D, + destination_layout: ImageLayout, + regions: R, + ) where + S: ?Sized + ImageAccess, + D: ?Sized + ImageAccess, + R: IntoIterator<Item = UnsafeCommandBufferBuilderImageCopy>, + { + // TODO: The correct check here is that the uncompressed element size of the source is + // equal to the compressed element size of the destination. + debug_assert!( + source.format().ty() == FormatTy::Compressed + || destination.format().ty() == FormatTy::Compressed + || source.format().size() == destination.format().size() + ); + + // Depth/Stencil formats are required to match exactly. + debug_assert!( + !matches!( + source.format().ty(), + FormatTy::Depth | FormatTy::Stencil | FormatTy::DepthStencil + ) || source.format() == destination.format() + ); + + debug_assert_eq!(source.samples(), destination.samples()); + let source = source.inner(); + debug_assert!(source.image.usage().transfer_source); + debug_assert!( + source_layout == ImageLayout::General + || source_layout == ImageLayout::TransferSrcOptimal + ); + + let destination = destination.inner(); + debug_assert!(destination.image.usage().transfer_destination); + debug_assert!( + destination_layout == ImageLayout::General + || destination_layout == ImageLayout::TransferDstOptimal + ); + + let regions: SmallVec<[_; 8]> = regions + .into_iter() + .filter_map(|copy| { + // TODO: not everything is checked here + debug_assert!( + copy.source_base_array_layer + copy.layer_count <= source.num_layers as u32 + ); + debug_assert!( + copy.destination_base_array_layer + copy.layer_count + <= destination.num_layers as u32 + ); + debug_assert!(copy.source_mip_level < destination.num_mipmap_levels as u32); + debug_assert!(copy.destination_mip_level < destination.num_mipmap_levels as u32); + + if copy.layer_count == 0 { + return None; + } + + Some(ash::vk::ImageCopy { + src_subresource: ash::vk::ImageSubresourceLayers { + aspect_mask: copy.aspects.into(), + mip_level: copy.source_mip_level, + base_array_layer: copy.source_base_array_layer + source.first_layer as u32, + layer_count: copy.layer_count, + }, + src_offset: ash::vk::Offset3D { + x: copy.source_offset[0], + y: copy.source_offset[1], + z: copy.source_offset[2], + }, + dst_subresource: ash::vk::ImageSubresourceLayers { + aspect_mask: copy.aspects.into(), + mip_level: copy.destination_mip_level, + base_array_layer: copy.destination_base_array_layer + + destination.first_layer as u32, + layer_count: copy.layer_count, + }, + dst_offset: ash::vk::Offset3D { + x: copy.destination_offset[0], + y: copy.destination_offset[1], + z: copy.destination_offset[2], + }, + extent: ash::vk::Extent3D { + width: copy.extent[0], + height: copy.extent[1], + depth: copy.extent[2], + }, + }) + }) + .collect(); + + if regions.is_empty() { + return; + } + + let fns = self.device().fns(); + let cmd = self.internal_object(); + fns.v1_0.cmd_copy_image( + cmd, + source.image.internal_object(), + source_layout.into(), + destination.image.internal_object(), + destination_layout.into(), + regions.len() as u32, + regions.as_ptr(), + ); + } + + /// Calls `vkCmdBlitImage` on the builder. + /// + /// Does nothing if the list of regions is empty, as it would be a no-op and isn't a valid + /// usage of the command anyway. + #[inline] + pub unsafe fn blit_image<S, D, R>( + &mut self, + source: &S, + source_layout: ImageLayout, + destination: &D, + destination_layout: ImageLayout, + regions: R, + filter: Filter, + ) where + S: ?Sized + ImageAccess, + D: ?Sized + ImageAccess, + R: IntoIterator<Item = UnsafeCommandBufferBuilderImageBlit>, + { + debug_assert!( + filter == Filter::Nearest + || !matches!( + source.format().ty(), + FormatTy::Depth | FormatTy::Stencil | FormatTy::DepthStencil + ) + ); + debug_assert!( + (source.format().ty() == FormatTy::Uint) + == (destination.format().ty() == FormatTy::Uint) + ); + debug_assert!( + (source.format().ty() == FormatTy::Sint) + == (destination.format().ty() == FormatTy::Sint) + ); + debug_assert!( + source.format() == destination.format() + || !matches!( + source.format().ty(), + FormatTy::Depth | FormatTy::Stencil | FormatTy::DepthStencil + ) + ); + + debug_assert_eq!(source.samples(), SampleCount::Sample1); + let source = source.inner(); + debug_assert!(source.image.format_features().blit_src); + debug_assert!(source.image.usage().transfer_source); + debug_assert!( + source_layout == ImageLayout::General + || source_layout == ImageLayout::TransferSrcOptimal + ); + + debug_assert_eq!(destination.samples(), SampleCount::Sample1); + let destination = destination.inner(); + debug_assert!(destination.image.format_features().blit_dst); + debug_assert!(destination.image.usage().transfer_destination); + debug_assert!( + destination_layout == ImageLayout::General + || destination_layout == ImageLayout::TransferDstOptimal + ); + + let regions: SmallVec<[_; 8]> = regions + .into_iter() + .filter_map(|blit| { + // TODO: not everything is checked here + debug_assert!( + blit.source_base_array_layer + blit.layer_count <= source.num_layers as u32 + ); + debug_assert!( + blit.destination_base_array_layer + blit.layer_count + <= destination.num_layers as u32 + ); + debug_assert!(blit.source_mip_level < destination.num_mipmap_levels as u32); + debug_assert!(blit.destination_mip_level < destination.num_mipmap_levels as u32); + + if blit.layer_count == 0 { + return None; + } + + Some(ash::vk::ImageBlit { + src_subresource: ash::vk::ImageSubresourceLayers { + aspect_mask: blit.aspects.into(), + mip_level: blit.source_mip_level, + base_array_layer: blit.source_base_array_layer + source.first_layer as u32, + layer_count: blit.layer_count, + }, + src_offsets: [ + ash::vk::Offset3D { + x: blit.source_top_left[0], + y: blit.source_top_left[1], + z: blit.source_top_left[2], + }, + ash::vk::Offset3D { + x: blit.source_bottom_right[0], + y: blit.source_bottom_right[1], + z: blit.source_bottom_right[2], + }, + ], + dst_subresource: ash::vk::ImageSubresourceLayers { + aspect_mask: blit.aspects.into(), + mip_level: blit.destination_mip_level, + base_array_layer: blit.destination_base_array_layer + + destination.first_layer as u32, + layer_count: blit.layer_count, + }, + dst_offsets: [ + ash::vk::Offset3D { + x: blit.destination_top_left[0], + y: blit.destination_top_left[1], + z: blit.destination_top_left[2], + }, + ash::vk::Offset3D { + x: blit.destination_bottom_right[0], + y: blit.destination_bottom_right[1], + z: blit.destination_bottom_right[2], + }, + ], + }) + }) + .collect(); + + if regions.is_empty() { + return; + } + + let fns = self.device().fns(); + let cmd = self.internal_object(); + fns.v1_0.cmd_blit_image( + cmd, + source.image.internal_object(), + source_layout.into(), + destination.image.internal_object(), + destination_layout.into(), + regions.len() as u32, + regions.as_ptr(), + filter.into(), + ); + } + + // TODO: missing structs + /*/// Calls `vkCmdClearAttachments` on the builder. + /// + /// Does nothing if the list of attachments or the list of rects is empty, as it would be a + /// no-op and isn't a valid usage of the command anyway. + #[inline] + pub unsafe fn clear_attachments<A, R>(&mut self, attachments: A, rects: R) + where A: IntoIterator<Item = >, + R: IntoIterator<Item = > + { + let attachments: SmallVec<[_; 16]> = attachments.map().collect(); + let rects: SmallVec<[_; 4]> = rects.map().collect(); + + if attachments.is_empty() || rects.is_empty() { + return; + } + + let fns = self.device().fns(); + let cmd = self.internal_object(); + fns.v1_0.CmdClearAttachments(cmd, attachments.len() as u32, attachments.as_ptr(), + rects.len() as u32, rects.as_ptr()); + }*/ + + /// Calls `vkCmdClearColorImage` on the builder. + /// + /// Does nothing if the list of regions is empty, as it would be a no-op and isn't a valid + /// usage of the command anyway. + // TODO: ClearValue could be more precise + pub unsafe fn clear_color_image<I, R>( + &mut self, + image: &I, + layout: ImageLayout, + color: ClearValue, + regions: R, + ) where + I: ?Sized + ImageAccess, + R: IntoIterator<Item = UnsafeCommandBufferBuilderColorImageClear>, + { + debug_assert!( + image.format().ty() == FormatTy::Float + || image.format().ty() == FormatTy::Uint + || image.format().ty() == FormatTy::Sint + ); + + let image = image.inner(); + debug_assert!(image.image.usage().transfer_destination); + debug_assert!(layout == ImageLayout::General || layout == ImageLayout::TransferDstOptimal); + + let color = match color { + ClearValue::Float(val) => ash::vk::ClearColorValue { float32: val }, + ClearValue::Int(val) => ash::vk::ClearColorValue { int32: val }, + ClearValue::Uint(val) => ash::vk::ClearColorValue { uint32: val }, + _ => ash::vk::ClearColorValue { float32: [0.0; 4] }, + }; + + let regions: SmallVec<[_; 8]> = regions + .into_iter() + .filter_map(|region| { + debug_assert!( + region.layer_count + region.base_array_layer <= image.num_layers as u32 + ); + debug_assert!( + region.level_count + region.base_mip_level <= image.num_mipmap_levels as u32 + ); + + if region.layer_count == 0 || region.level_count == 0 { + return None; + } + + Some(ash::vk::ImageSubresourceRange { + aspect_mask: ash::vk::ImageAspectFlags::COLOR, + base_mip_level: region.base_mip_level + image.first_mipmap_level as u32, + level_count: region.level_count, + base_array_layer: region.base_array_layer + image.first_layer as u32, + layer_count: region.layer_count, + }) + }) + .collect(); + + if regions.is_empty() { + return; + } + + let fns = self.device().fns(); + let cmd = self.internal_object(); + fns.v1_0.cmd_clear_color_image( + cmd, + image.image.internal_object(), + layout.into(), + &color, + regions.len() as u32, + regions.as_ptr(), + ); + } + + /// Calls `vkCmdCopyBuffer` on the builder. + /// + /// Does nothing if the list of regions is empty, as it would be a no-op and isn't a valid + /// usage of the command anyway. + #[inline] + pub unsafe fn copy_buffer<S, D, R>(&mut self, source: &S, destination: &D, regions: R) + where + S: ?Sized + BufferAccess, + D: ?Sized + BufferAccess, + R: IntoIterator<Item = (DeviceSize, DeviceSize, DeviceSize)>, + { + // TODO: debug assert that there's no overlap in the destinations? + + let source = source.inner(); + debug_assert!(source.offset < source.buffer.size()); + debug_assert!(source.buffer.usage().transfer_source); + + let destination = destination.inner(); + debug_assert!(destination.offset < destination.buffer.size()); + debug_assert!(destination.buffer.usage().transfer_destination); + + let regions: SmallVec<[_; 8]> = regions + .into_iter() + .map(|(sr, de, sz)| ash::vk::BufferCopy { + src_offset: sr + source.offset, + dst_offset: de + destination.offset, + size: sz, + }) + .collect(); + + if regions.is_empty() { + return; + } + + let fns = self.device().fns(); + let cmd = self.internal_object(); + fns.v1_0.cmd_copy_buffer( + cmd, + source.buffer.internal_object(), + destination.buffer.internal_object(), + regions.len() as u32, + regions.as_ptr(), + ); + } + + /// Calls `vkCmdCopyBufferToImage` on the builder. + /// + /// Does nothing if the list of regions is empty, as it would be a no-op and isn't a valid + /// usage of the command anyway. + #[inline] + pub unsafe fn copy_buffer_to_image<S, D, R>( + &mut self, + source: &S, + destination: &D, + destination_layout: ImageLayout, + regions: R, + ) where + S: ?Sized + BufferAccess, + D: ?Sized + ImageAccess, + R: IntoIterator<Item = UnsafeCommandBufferBuilderBufferImageCopy>, + { + let source = source.inner(); + debug_assert!(source.offset < source.buffer.size()); + debug_assert!(source.buffer.usage().transfer_source); + + debug_assert_eq!(destination.samples(), SampleCount::Sample1); + let destination = destination.inner(); + debug_assert!(destination.image.usage().transfer_destination); + debug_assert!( + destination_layout == ImageLayout::General + || destination_layout == ImageLayout::TransferDstOptimal + ); + + let regions: SmallVec<[_; 8]> = regions + .into_iter() + .map(|copy| { + debug_assert!(copy.image_layer_count <= destination.num_layers as u32); + debug_assert!(copy.image_mip_level < destination.num_mipmap_levels as u32); + + ash::vk::BufferImageCopy { + buffer_offset: source.offset + copy.buffer_offset, + buffer_row_length: copy.buffer_row_length, + buffer_image_height: copy.buffer_image_height, + image_subresource: ash::vk::ImageSubresourceLayers { + aspect_mask: copy.image_aspect.into(), + mip_level: copy.image_mip_level + destination.first_mipmap_level as u32, + base_array_layer: copy.image_base_array_layer + + destination.first_layer as u32, + layer_count: copy.image_layer_count, + }, + image_offset: ash::vk::Offset3D { + x: copy.image_offset[0], + y: copy.image_offset[1], + z: copy.image_offset[2], + }, + image_extent: ash::vk::Extent3D { + width: copy.image_extent[0], + height: copy.image_extent[1], + depth: copy.image_extent[2], + }, + } + }) + .collect(); + + if regions.is_empty() { + return; + } + + let fns = self.device().fns(); + let cmd = self.internal_object(); + fns.v1_0.cmd_copy_buffer_to_image( + cmd, + source.buffer.internal_object(), + destination.image.internal_object(), + destination_layout.into(), + regions.len() as u32, + regions.as_ptr(), + ); + } + + /// Calls `vkCmdCopyImageToBuffer` on the builder. + /// + /// Does nothing if the list of regions is empty, as it would be a no-op and isn't a valid + /// usage of the command anyway. + #[inline] + pub unsafe fn copy_image_to_buffer<S, D, R>( + &mut self, + source: &S, + source_layout: ImageLayout, + destination: &D, + regions: R, + ) where + S: ?Sized + ImageAccess, + D: ?Sized + BufferAccess, + R: IntoIterator<Item = UnsafeCommandBufferBuilderBufferImageCopy>, + { + debug_assert_eq!(source.samples(), SampleCount::Sample1); + let source = source.inner(); + debug_assert!(source.image.usage().transfer_source); + debug_assert!( + source_layout == ImageLayout::General + || source_layout == ImageLayout::TransferSrcOptimal + ); + + let destination = destination.inner(); + debug_assert!(destination.offset < destination.buffer.size()); + debug_assert!(destination.buffer.usage().transfer_destination); + + let regions: SmallVec<[_; 8]> = regions + .into_iter() + .map(|copy| { + debug_assert!(copy.image_layer_count <= source.num_layers as u32); + debug_assert!(copy.image_mip_level < source.num_mipmap_levels as u32); + + ash::vk::BufferImageCopy { + buffer_offset: destination.offset + copy.buffer_offset, + buffer_row_length: copy.buffer_row_length, + buffer_image_height: copy.buffer_image_height, + image_subresource: ash::vk::ImageSubresourceLayers { + aspect_mask: copy.image_aspect.into(), + mip_level: copy.image_mip_level + source.first_mipmap_level as u32, + base_array_layer: copy.image_base_array_layer + source.first_layer as u32, + layer_count: copy.image_layer_count, + }, + image_offset: ash::vk::Offset3D { + x: copy.image_offset[0], + y: copy.image_offset[1], + z: copy.image_offset[2], + }, + image_extent: ash::vk::Extent3D { + width: copy.image_extent[0], + height: copy.image_extent[1], + depth: copy.image_extent[2], + }, + } + }) + .collect(); + + if regions.is_empty() { + return; + } + + let fns = self.device().fns(); + let cmd = self.internal_object(); + fns.v1_0.cmd_copy_image_to_buffer( + cmd, + source.image.internal_object(), + source_layout.into(), + destination.buffer.internal_object(), + regions.len() as u32, + regions.as_ptr(), + ); + } + + /// Calls `vkCmdCopyQueryPoolResults` on the builder. + #[inline] + pub unsafe fn copy_query_pool_results<D, T>( + &mut self, + queries: QueriesRange, + destination: D, + stride: DeviceSize, + flags: QueryResultFlags, + ) where + D: BufferAccess + TypedBufferAccess<Content = [T]>, + T: QueryResultElement, + { + let destination = destination.inner(); + let range = queries.range(); + debug_assert!(destination.offset < destination.buffer.size()); + debug_assert!(destination.buffer.usage().transfer_destination); + debug_assert!(destination.offset % std::mem::size_of::<T>() as DeviceSize == 0); + debug_assert!(stride % std::mem::size_of::<T>() as DeviceSize == 0); + + let fns = self.device().fns(); + let cmd = self.internal_object(); + fns.v1_0.cmd_copy_query_pool_results( + cmd, + queries.pool().internal_object(), + range.start, + range.end - range.start, + destination.buffer.internal_object(), + destination.offset, + stride, + ash::vk::QueryResultFlags::from(flags) | T::FLAG, + ); + } + + /// Calls `vkCmdDispatch` on the builder. + #[inline] + pub unsafe fn dispatch(&mut self, group_counts: [u32; 3]) { + debug_assert!({ + let max_group_counts = self + .device() + .physical_device() + .properties() + .max_compute_work_group_count; + group_counts[0] <= max_group_counts[0] + && group_counts[1] <= max_group_counts[1] + && group_counts[2] <= max_group_counts[2] + }); + + let fns = self.device().fns(); + let cmd = self.internal_object(); + fns.v1_0 + .cmd_dispatch(cmd, group_counts[0], group_counts[1], group_counts[2]); + } + + /// Calls `vkCmdDispatchIndirect` on the builder. + #[inline] + pub unsafe fn dispatch_indirect<B>(&mut self, buffer: &B) + where + B: ?Sized + BufferAccess, + { + let fns = self.device().fns(); + let cmd = self.internal_object(); + + let inner = buffer.inner(); + debug_assert!(inner.offset < inner.buffer.size()); + debug_assert!(inner.buffer.usage().indirect_buffer); + debug_assert_eq!(inner.offset % 4, 0); + + fns.v1_0 + .cmd_dispatch_indirect(cmd, inner.buffer.internal_object(), inner.offset); + } + + /// Calls `vkCmdDraw` on the builder. + #[inline] + pub unsafe fn draw( + &mut self, + vertex_count: u32, + instance_count: u32, + first_vertex: u32, + first_instance: u32, + ) { + let fns = self.device().fns(); + let cmd = self.internal_object(); + fns.v1_0.cmd_draw( + cmd, + vertex_count, + instance_count, + first_vertex, + first_instance, + ); + } + + /// Calls `vkCmdDrawIndexed` on the builder. + #[inline] + pub unsafe fn draw_indexed( + &mut self, + index_count: u32, + instance_count: u32, + first_index: u32, + vertex_offset: i32, + first_instance: u32, + ) { + let fns = self.device().fns(); + let cmd = self.internal_object(); + fns.v1_0.cmd_draw_indexed( + cmd, + index_count, + instance_count, + first_index, + vertex_offset, + first_instance, + ); + } + + /// Calls `vkCmdDrawIndirect` on the builder. + #[inline] + pub unsafe fn draw_indirect<B>(&mut self, buffer: &B, draw_count: u32, stride: u32) + where + B: ?Sized + BufferAccess, + { + let fns = self.device().fns(); + let cmd = self.internal_object(); + + debug_assert!( + draw_count == 0 + || ((stride % 4) == 0) + && stride as usize >= mem::size_of::<ash::vk::DrawIndirectCommand>() + ); + + let inner = buffer.inner(); + debug_assert!(inner.offset < inner.buffer.size()); + debug_assert!(inner.buffer.usage().indirect_buffer); + + fns.v1_0.cmd_draw_indirect( + cmd, + inner.buffer.internal_object(), + inner.offset, + draw_count, + stride, + ); + } + + /// Calls `vkCmdDrawIndexedIndirect` on the builder. + #[inline] + pub unsafe fn draw_indexed_indirect<B>(&mut self, buffer: &B, draw_count: u32, stride: u32) + where + B: ?Sized + BufferAccess, + { + let fns = self.device().fns(); + let cmd = self.internal_object(); + + let inner = buffer.inner(); + debug_assert!(inner.offset < inner.buffer.size()); + debug_assert!(inner.buffer.usage().indirect_buffer); + + fns.v1_0.cmd_draw_indexed_indirect( + cmd, + inner.buffer.internal_object(), + inner.offset, + draw_count, + stride, + ); + } + + /// Calls `vkCmdEndQuery` on the builder. + #[inline] + pub unsafe fn end_query(&mut self, query: Query) { + let fns = self.device().fns(); + let cmd = self.internal_object(); + fns.v1_0 + .cmd_end_query(cmd, query.pool().internal_object(), query.index()); + } + + /// Calls `vkCmdEndRenderPass` on the builder. + #[inline] + pub unsafe fn end_render_pass(&mut self) { + let fns = self.device().fns(); + let cmd = self.internal_object(); + fns.v1_0.cmd_end_render_pass(cmd); + } + + /// Calls `vkCmdExecuteCommands` on the builder. + /// + /// Does nothing if the list of command buffers is empty, as it would be a no-op and isn't a + /// valid usage of the command anyway. + #[inline] + pub unsafe fn execute_commands(&mut self, cbs: UnsafeCommandBufferBuilderExecuteCommands) { + if cbs.raw_cbs.is_empty() { + return; + } + + let fns = self.device().fns(); + let cmd = self.internal_object(); + fns.v1_0 + .cmd_execute_commands(cmd, cbs.raw_cbs.len() as u32, cbs.raw_cbs.as_ptr()); + } + + /// Calls `vkCmdFillBuffer` on the builder. + #[inline] + pub unsafe fn fill_buffer<B>(&mut self, buffer: &B, data: u32) + where + B: ?Sized + BufferAccess, + { + let fns = self.device().fns(); + let cmd = self.internal_object(); + + let size = buffer.size(); + + let (buffer_handle, offset) = { + let BufferInner { + buffer: buffer_inner, + offset, + } = buffer.inner(); + debug_assert!(buffer_inner.usage().transfer_destination); + debug_assert_eq!(offset % 4, 0); + (buffer_inner.internal_object(), offset) + }; + + fns.v1_0 + .cmd_fill_buffer(cmd, buffer_handle, offset, size, data); + } + + /// Calls `vkCmdNextSubpass` on the builder. + #[inline] + pub unsafe fn next_subpass(&mut self, subpass_contents: SubpassContents) { + let fns = self.device().fns(); + let cmd = self.internal_object(); + fns.v1_0.cmd_next_subpass(cmd, subpass_contents.into()); + } + + #[inline] + pub unsafe fn pipeline_barrier(&mut self, command: &UnsafeCommandBufferBuilderPipelineBarrier) { + // If barrier is empty, don't do anything. + if command.src_stage_mask.is_empty() || command.dst_stage_mask.is_empty() { + debug_assert!(command.src_stage_mask.is_empty() && command.dst_stage_mask.is_empty()); + debug_assert!(command.memory_barriers.is_empty()); + debug_assert!(command.buffer_barriers.is_empty()); + debug_assert!(command.image_barriers.is_empty()); + return; + } + + let fns = self.device().fns(); + let cmd = self.internal_object(); + + debug_assert!(!command.src_stage_mask.is_empty()); + debug_assert!(!command.dst_stage_mask.is_empty()); + + fns.v1_0.cmd_pipeline_barrier( + cmd, + command.src_stage_mask, + command.dst_stage_mask, + command.dependency_flags, + command.memory_barriers.len() as u32, + command.memory_barriers.as_ptr(), + command.buffer_barriers.len() as u32, + command.buffer_barriers.as_ptr(), + command.image_barriers.len() as u32, + command.image_barriers.as_ptr(), + ); + } + + /// Calls `vkCmdPushConstants` on the builder. + #[inline] + pub unsafe fn push_constants<D>( + &mut self, + pipeline_layout: &PipelineLayout, + stages: ShaderStages, + offset: u32, + size: u32, + data: &D, + ) where + D: ?Sized, + { + let fns = self.device().fns(); + let cmd = self.internal_object(); + + debug_assert!(stages != ShaderStages::none()); + debug_assert!(size > 0); + debug_assert_eq!(size % 4, 0); + debug_assert_eq!(offset % 4, 0); + debug_assert!(mem::size_of_val(data) >= size as usize); + + fns.v1_0.cmd_push_constants( + cmd, + pipeline_layout.internal_object(), + stages.into(), + offset as u32, + size as u32, + data as *const D as *const _, + ); + } + + /// Calls `vkCmdResetEvent` on the builder. + #[inline] + pub unsafe fn reset_event(&mut self, event: &Event, stages: PipelineStages) { + let fns = self.device().fns(); + let cmd = self.internal_object(); + + debug_assert!(!stages.host); + debug_assert_ne!(stages, PipelineStages::none()); + + fns.v1_0 + .cmd_reset_event(cmd, event.internal_object(), stages.into()); + } + + /// Calls `vkCmdResetQueryPool` on the builder. + #[inline] + pub unsafe fn reset_query_pool(&mut self, queries: QueriesRange) { + let range = queries.range(); + let fns = self.device().fns(); + let cmd = self.internal_object(); + fns.v1_0.cmd_reset_query_pool( + cmd, + queries.pool().internal_object(), + range.start, + range.end - range.start, + ); + } + + /// Calls `vkCmdSetBlendConstants` on the builder. + #[inline] + pub unsafe fn set_blend_constants(&mut self, constants: [f32; 4]) { + let fns = self.device().fns(); + let cmd = self.internal_object(); + fns.v1_0.cmd_set_blend_constants(cmd, &constants); + } + + /// Calls `vkCmdSetDepthBias` on the builder. + #[inline] + pub unsafe fn set_depth_bias(&mut self, constant_factor: f32, clamp: f32, slope_factor: f32) { + let fns = self.device().fns(); + let cmd = self.internal_object(); + debug_assert!(clamp == 0.0 || self.device().enabled_features().depth_bias_clamp); + fns.v1_0 + .cmd_set_depth_bias(cmd, constant_factor, clamp, slope_factor); + } + + /// Calls `vkCmdSetDepthBounds` on the builder. + #[inline] + pub unsafe fn set_depth_bounds(&mut self, min: f32, max: f32) { + let fns = self.device().fns(); + let cmd = self.internal_object(); + debug_assert!(min >= 0.0 && min <= 1.0); + debug_assert!(max >= 0.0 && max <= 1.0); + fns.v1_0.cmd_set_depth_bounds(cmd, min, max); + } + + /// Calls `vkCmdSetEvent` on the builder. + #[inline] + pub unsafe fn set_event(&mut self, event: &Event, stages: PipelineStages) { + let fns = self.device().fns(); + let cmd = self.internal_object(); + + debug_assert!(!stages.host); + debug_assert_ne!(stages, PipelineStages::none()); + + fns.v1_0 + .cmd_set_event(cmd, event.internal_object(), stages.into()); + } + + /// Calls `vkCmdSetLineWidth` on the builder. + #[inline] + pub unsafe fn set_line_width(&mut self, line_width: f32) { + let fns = self.device().fns(); + let cmd = self.internal_object(); + debug_assert!(line_width == 1.0 || self.device().enabled_features().wide_lines); + fns.v1_0.cmd_set_line_width(cmd, line_width); + } + + /// Calls `vkCmdSetStencilCompareMask` on the builder. + #[inline] + pub unsafe fn set_stencil_compare_mask(&mut self, face_mask: StencilFaces, compare_mask: u32) { + let fns = self.device().fns(); + let cmd = self.internal_object(); + fns.v1_0 + .cmd_set_stencil_compare_mask(cmd, face_mask.into(), compare_mask); + } + + /// Calls `vkCmdSetStencilReference` on the builder. + #[inline] + pub unsafe fn set_stencil_reference(&mut self, face_mask: StencilFaces, reference: u32) { + let fns = self.device().fns(); + let cmd = self.internal_object(); + fns.v1_0 + .cmd_set_stencil_reference(cmd, face_mask.into(), reference); + } + + /// Calls `vkCmdSetStencilWriteMask` on the builder. + #[inline] + pub unsafe fn set_stencil_write_mask(&mut self, face_mask: StencilFaces, write_mask: u32) { + let fns = self.device().fns(); + let cmd = self.internal_object(); + fns.v1_0 + .cmd_set_stencil_write_mask(cmd, face_mask.into(), write_mask); + } + + /// Calls `vkCmdSetScissor` on the builder. + /// + /// If the list is empty then the command is automatically ignored. + #[inline] + pub unsafe fn set_scissor<I>(&mut self, first_scissor: u32, scissors: I) + where + I: IntoIterator<Item = Scissor>, + { + let scissors = scissors + .into_iter() + .map(|v| ash::vk::Rect2D::from(v.clone())) + .collect::<SmallVec<[_; 16]>>(); + if scissors.is_empty() { + return; + } + + debug_assert!(scissors.iter().all(|s| s.offset.x >= 0 && s.offset.y >= 0)); + debug_assert!(scissors.iter().all(|s| { + s.extent.width < i32::MAX as u32 + && s.extent.height < i32::MAX as u32 + && s.offset.x.checked_add(s.extent.width as i32).is_some() + && s.offset.y.checked_add(s.extent.height as i32).is_some() + })); + debug_assert!( + (first_scissor == 0 && scissors.len() == 1) + || self.device().enabled_features().multi_viewport + ); + debug_assert!({ + let max = self + .device() + .physical_device() + .properties() + .max_viewports; + first_scissor + scissors.len() as u32 <= max + }); + + let fns = self.device().fns(); + let cmd = self.internal_object(); + fns.v1_0 + .cmd_set_scissor(cmd, first_scissor, scissors.len() as u32, scissors.as_ptr()); + } + + /// Calls `vkCmdSetViewport` on the builder. + /// + /// If the list is empty then the command is automatically ignored. + #[inline] + pub unsafe fn set_viewport<I>(&mut self, first_viewport: u32, viewports: I) + where + I: IntoIterator<Item = Viewport>, + { + let viewports = viewports + .into_iter() + .map(|v| v.clone().into()) + .collect::<SmallVec<[_; 16]>>(); + if viewports.is_empty() { + return; + } + + debug_assert!( + (first_viewport == 0 && viewports.len() == 1) + || self.device().enabled_features().multi_viewport + ); + debug_assert!({ + let max = self + .device() + .physical_device() + .properties() + .max_viewports; + first_viewport + viewports.len() as u32 <= max + }); + + let fns = self.device().fns(); + let cmd = self.internal_object(); + fns.v1_0.cmd_set_viewport( + cmd, + first_viewport, + viewports.len() as u32, + viewports.as_ptr(), + ); + } + + /// Calls `vkCmdUpdateBuffer` on the builder. + #[inline] + pub unsafe fn update_buffer<B, D>(&mut self, buffer: &B, data: &D) + where + B: ?Sized + BufferAccess, + D: ?Sized, + { + let fns = self.device().fns(); + let cmd = self.internal_object(); + + let size = buffer.size(); + debug_assert_eq!(size % 4, 0); + debug_assert!(size <= 65536); + debug_assert!(size <= mem::size_of_val(data) as DeviceSize); + + let (buffer_handle, offset) = { + let BufferInner { + buffer: buffer_inner, + offset, + } = buffer.inner(); + debug_assert!(buffer_inner.usage().transfer_destination); + debug_assert_eq!(offset % 4, 0); + (buffer_inner.internal_object(), offset) + }; + + fns.v1_0.cmd_update_buffer( + cmd, + buffer_handle, + offset, + size, + data as *const D as *const _, + ); + } + + /// Calls `vkCmdWriteTimestamp` on the builder. + #[inline] + pub unsafe fn write_timestamp(&mut self, query: Query, stage: PipelineStage) { + let fns = self.device().fns(); + let cmd = self.internal_object(); + fns.v1_0.cmd_write_timestamp( + cmd, + stage.into(), + query.pool().internal_object(), + query.index(), + ); + } + + /// Calls `vkCmdBeginDebugUtilsLabelEXT` on the builder. + /// + /// # Safety + /// The command pool that this command buffer was allocated from must support graphics or + /// compute operations + #[inline] + pub unsafe fn debug_marker_begin(&mut self, name: &CStr, color: [f32; 4]) { + let fns = self.device().instance().fns(); + let cmd = self.internal_object(); + let info = ash::vk::DebugUtilsLabelEXT { + p_label_name: name.as_ptr(), + color, + ..Default::default() + }; + fns.ext_debug_utils + .cmd_begin_debug_utils_label_ext(cmd, &info); + } + + /// Calls `vkCmdEndDebugUtilsLabelEXT` on the builder. + /// + /// # Safety + /// There must be an outstanding `vkCmdBeginDebugUtilsLabelEXT` command prior to the + /// `vkQueueEndDebugUtilsLabelEXT` on the queue tha `CommandBuffer` is submitted to. + #[inline] + pub unsafe fn debug_marker_end(&mut self) { + let fns = self.device().instance().fns(); + let cmd = self.internal_object(); + fns.ext_debug_utils.cmd_end_debug_utils_label_ext(cmd); + } + + /// Calls `vkCmdInsertDebugUtilsLabelEXT` on the builder. + /// + /// # Safety + /// The command pool that this command buffer was allocated from must support graphics or + /// compute operations + #[inline] + pub unsafe fn debug_marker_insert(&mut self, name: &CStr, color: [f32; 4]) { + let fns = self.device().instance().fns(); + let cmd = self.internal_object(); + let info = ash::vk::DebugUtilsLabelEXT { + p_label_name: name.as_ptr(), + color, + ..Default::default() + }; + fns.ext_debug_utils + .cmd_insert_debug_utils_label_ext(cmd, &info); + } +} + +unsafe impl DeviceOwned for UnsafeCommandBufferBuilder { + #[inline] + fn device(&self) -> &Arc<Device> { + &self.device + } +} + +unsafe impl VulkanObject for UnsafeCommandBufferBuilder { + type Object = ash::vk::CommandBuffer; + + #[inline] + fn internal_object(&self) -> ash::vk::CommandBuffer { + self.command_buffer + } +} + +/// Prototype for a `vkCmdBindVertexBuffers`. +pub struct UnsafeCommandBufferBuilderBindVertexBuffer { + // Raw handles of the buffers to bind. + raw_buffers: SmallVec<[ash::vk::Buffer; 4]>, + // Raw offsets of the buffers to bind. + offsets: SmallVec<[DeviceSize; 4]>, +} + +impl UnsafeCommandBufferBuilderBindVertexBuffer { + /// Builds a new empty list. + #[inline] + pub fn new() -> UnsafeCommandBufferBuilderBindVertexBuffer { + UnsafeCommandBufferBuilderBindVertexBuffer { + raw_buffers: SmallVec::new(), + offsets: SmallVec::new(), + } + } + + /// Adds a buffer to the list. + #[inline] + pub fn add<B>(&mut self, buffer: &B) + where + B: ?Sized + BufferAccess, + { + let inner = buffer.inner(); + debug_assert!(inner.buffer.usage().vertex_buffer); + self.raw_buffers.push(inner.buffer.internal_object()); + self.offsets.push(inner.offset); + } +} + +/// Prototype for a `vkCmdExecuteCommands`. +pub struct UnsafeCommandBufferBuilderExecuteCommands { + // Raw handles of the command buffers to execute. + raw_cbs: SmallVec<[ash::vk::CommandBuffer; 4]>, +} + +impl UnsafeCommandBufferBuilderExecuteCommands { + /// Builds a new empty list. + #[inline] + pub fn new() -> UnsafeCommandBufferBuilderExecuteCommands { + UnsafeCommandBufferBuilderExecuteCommands { + raw_cbs: SmallVec::new(), + } + } + + /// Adds a command buffer to the list. + #[inline] + pub fn add<C>(&mut self, cb: &C) + where + C: ?Sized + SecondaryCommandBuffer, + { + // TODO: debug assert that it is a secondary command buffer? + self.raw_cbs.push(cb.inner().internal_object()); + } + + /// Adds a command buffer to the list. + #[inline] + pub unsafe fn add_raw(&mut self, cb: ash::vk::CommandBuffer) { + self.raw_cbs.push(cb); + } +} + +// TODO: move somewhere else? +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct UnsafeCommandBufferBuilderColorImageClear { + pub base_mip_level: u32, + pub level_count: u32, + pub base_array_layer: u32, + pub layer_count: u32, +} + +// TODO: move somewhere else? +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct UnsafeCommandBufferBuilderBufferImageCopy { + pub buffer_offset: DeviceSize, + pub buffer_row_length: u32, + pub buffer_image_height: u32, + pub image_aspect: ImageAspect, + pub image_mip_level: u32, + pub image_base_array_layer: u32, + pub image_layer_count: u32, + pub image_offset: [i32; 3], + pub image_extent: [u32; 3], +} + +// TODO: move somewhere else? +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct UnsafeCommandBufferBuilderImageCopy { + pub aspects: ImageAspects, + pub source_mip_level: u32, + pub destination_mip_level: u32, + pub source_base_array_layer: u32, + pub destination_base_array_layer: u32, + pub layer_count: u32, + pub source_offset: [i32; 3], + pub destination_offset: [i32; 3], + pub extent: [u32; 3], +} + +// TODO: move somewhere else? +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct UnsafeCommandBufferBuilderImageBlit { + pub aspects: ImageAspects, + pub source_mip_level: u32, + pub destination_mip_level: u32, + pub source_base_array_layer: u32, + pub destination_base_array_layer: u32, + pub layer_count: u32, + pub source_top_left: [i32; 3], + pub source_bottom_right: [i32; 3], + pub destination_top_left: [i32; 3], + pub destination_bottom_right: [i32; 3], +} + +/// Command that adds a pipeline barrier to a command buffer builder. +/// +/// A pipeline barrier is a low-level system-ish command that is often necessary for safety. By +/// default all commands that you add to a command buffer can potentially run simultaneously. +/// Adding a pipeline barrier separates commands before the barrier from commands after the barrier +/// and prevents them from running simultaneously. +/// +/// Please take a look at the Vulkan specifications for more information. Pipeline barriers are a +/// complex topic and explaining them in this documentation would be redundant. +/// +/// > **Note**: We use a builder-like API here so that users can pass multiple buffers or images of +/// > multiple different types. Doing so with a single function would be very tedious in terms of +/// > API. +pub struct UnsafeCommandBufferBuilderPipelineBarrier { + src_stage_mask: ash::vk::PipelineStageFlags, + dst_stage_mask: ash::vk::PipelineStageFlags, + dependency_flags: ash::vk::DependencyFlags, + memory_barriers: SmallVec<[ash::vk::MemoryBarrier; 2]>, + buffer_barriers: SmallVec<[ash::vk::BufferMemoryBarrier; 8]>, + image_barriers: SmallVec<[ash::vk::ImageMemoryBarrier; 8]>, +} + +impl UnsafeCommandBufferBuilderPipelineBarrier { + /// Creates a new empty pipeline barrier command. + #[inline] + pub fn new() -> UnsafeCommandBufferBuilderPipelineBarrier { + UnsafeCommandBufferBuilderPipelineBarrier { + src_stage_mask: ash::vk::PipelineStageFlags::empty(), + dst_stage_mask: ash::vk::PipelineStageFlags::empty(), + dependency_flags: ash::vk::DependencyFlags::BY_REGION, + memory_barriers: SmallVec::new(), + buffer_barriers: SmallVec::new(), + image_barriers: SmallVec::new(), + } + } + + /// Returns true if no barrier or execution dependency has been added yet. + #[inline] + pub fn is_empty(&self) -> bool { + self.src_stage_mask.is_empty() || self.dst_stage_mask.is_empty() + } + + /// Merges another pipeline builder into this one. + #[inline] + pub fn merge(&mut self, other: UnsafeCommandBufferBuilderPipelineBarrier) { + self.src_stage_mask |= other.src_stage_mask; + self.dst_stage_mask |= other.dst_stage_mask; + self.dependency_flags &= other.dependency_flags; + + self.memory_barriers + .extend(other.memory_barriers.into_iter()); + self.buffer_barriers + .extend(other.buffer_barriers.into_iter()); + self.image_barriers.extend(other.image_barriers.into_iter()); + } + + /// Adds an execution dependency. This means that all the stages in `source` of the previous + /// commands must finish before any of the stages in `destination` of the following commands can start. + /// + /// # Safety + /// + /// - If the pipeline stages include geometry or tessellation stages, then the corresponding + /// features must have been enabled in the device. + /// - There are certain rules regarding the pipeline barriers inside render passes. + /// + #[inline] + pub unsafe fn add_execution_dependency( + &mut self, + source: PipelineStages, + destination: PipelineStages, + by_region: bool, + ) { + if !by_region { + self.dependency_flags = ash::vk::DependencyFlags::empty(); + } + + debug_assert_ne!(source, PipelineStages::none()); + debug_assert_ne!(destination, PipelineStages::none()); + + self.src_stage_mask |= ash::vk::PipelineStageFlags::from(source); + self.dst_stage_mask |= ash::vk::PipelineStageFlags::from(destination); + } + + /// Adds a memory barrier. This means that all the memory writes by the given source stages + /// for the given source accesses must be visible by the given destination stages for the given + /// destination accesses. + /// + /// Also adds an execution dependency similar to `add_execution_dependency`. + /// + /// # Safety + /// + /// - Same as `add_execution_dependency`. + /// + pub unsafe fn add_memory_barrier( + &mut self, + source_stage: PipelineStages, + source_access: AccessFlags, + destination_stage: PipelineStages, + destination_access: AccessFlags, + by_region: bool, + ) { + debug_assert!(source_access.is_compatible_with(&source_stage)); + debug_assert!(destination_access.is_compatible_with(&destination_stage)); + + self.add_execution_dependency(source_stage, destination_stage, by_region); + + self.memory_barriers.push(ash::vk::MemoryBarrier { + src_access_mask: source_access.into(), + dst_access_mask: destination_access.into(), + ..Default::default() + }); + } + + /// Adds a buffer memory barrier. This means that all the memory writes to the given buffer by + /// the given source stages for the given source accesses must be visible by the given dest + /// stages for the given destination accesses. + /// + /// Also adds an execution dependency similar to `add_execution_dependency`. + /// + /// Also allows transferring buffer ownership between queues. + /// + /// # Safety + /// + /// - Same as `add_execution_dependency`. + /// - The buffer must be alive for at least as long as the command buffer to which this barrier + /// is added. + /// - Queue ownership transfers must be correct. + /// + pub unsafe fn add_buffer_memory_barrier<B>( + &mut self, + buffer: &B, + source_stage: PipelineStages, + source_access: AccessFlags, + destination_stage: PipelineStages, + destination_access: AccessFlags, + by_region: bool, + queue_transfer: Option<(u32, u32)>, + offset: DeviceSize, + size: DeviceSize, + ) where + B: ?Sized + BufferAccess, + { + debug_assert!(source_access.is_compatible_with(&source_stage)); + debug_assert!(destination_access.is_compatible_with(&destination_stage)); + + self.add_execution_dependency(source_stage, destination_stage, by_region); + + debug_assert!(size <= buffer.size()); + let BufferInner { + buffer, + offset: org_offset, + } = buffer.inner(); + let offset = offset + org_offset; + + let (src_queue, dest_queue) = if let Some((src_queue, dest_queue)) = queue_transfer { + (src_queue, dest_queue) + } else { + (ash::vk::QUEUE_FAMILY_IGNORED, ash::vk::QUEUE_FAMILY_IGNORED) + }; + + self.buffer_barriers.push(ash::vk::BufferMemoryBarrier { + src_access_mask: source_access.into(), + dst_access_mask: destination_access.into(), + src_queue_family_index: src_queue, + dst_queue_family_index: dest_queue, + buffer: buffer.internal_object(), + offset, + size, + ..Default::default() + }); + } + + /// Adds an image memory barrier. This is the equivalent of `add_buffer_memory_barrier` but + /// for images. + /// + /// In addition to transferring image ownership between queues, it also allows changing the + /// layout of images. + /// + /// Also adds an execution dependency similar to `add_execution_dependency`. + /// + /// # Safety + /// + /// - Same as `add_execution_dependency`. + /// - The buffer must be alive for at least as long as the command buffer to which this barrier + /// is added. + /// - Queue ownership transfers must be correct. + /// - Image layouts transfers must be correct. + /// - Access flags must be compatible with the image usage flags passed at image creation. + /// + pub unsafe fn add_image_memory_barrier<I>( + &mut self, + image: &I, + mipmaps: Range<u32>, + layers: Range<u32>, + source_stage: PipelineStages, + source_access: AccessFlags, + destination_stage: PipelineStages, + destination_access: AccessFlags, + by_region: bool, + queue_transfer: Option<(u32, u32)>, + current_layout: ImageLayout, + new_layout: ImageLayout, + ) where + I: ?Sized + ImageAccess, + { + debug_assert!(source_access.is_compatible_with(&source_stage)); + debug_assert!(destination_access.is_compatible_with(&destination_stage)); + + self.add_execution_dependency(source_stage, destination_stage, by_region); + + debug_assert_ne!(new_layout, ImageLayout::Undefined); + debug_assert_ne!(new_layout, ImageLayout::Preinitialized); + + debug_assert!(mipmaps.start < mipmaps.end); + debug_assert!(mipmaps.end <= image.mipmap_levels()); + debug_assert!(layers.start < layers.end); + debug_assert!(layers.end <= image.dimensions().array_layers()); + + let (src_queue, dest_queue) = if let Some((src_queue, dest_queue)) = queue_transfer { + (src_queue, dest_queue) + } else { + (ash::vk::QUEUE_FAMILY_IGNORED, ash::vk::QUEUE_FAMILY_IGNORED) + }; + + if image.format().ty() == FormatTy::Ycbcr { + unimplemented!(); + } + + // TODO: Let user choose + let aspects = image.format().aspects(); + let image = image.inner(); + + self.image_barriers.push(ash::vk::ImageMemoryBarrier { + src_access_mask: source_access.into(), + dst_access_mask: destination_access.into(), + old_layout: current_layout.into(), + new_layout: new_layout.into(), + src_queue_family_index: src_queue, + dst_queue_family_index: dest_queue, + image: image.image.internal_object(), + subresource_range: ash::vk::ImageSubresourceRange { + aspect_mask: aspects.into(), + base_mip_level: mipmaps.start + image.first_mipmap_level as u32, + level_count: mipmaps.end - mipmaps.start, + base_array_layer: layers.start + image.first_layer as u32, + layer_count: layers.end - layers.start, + }, + ..Default::default() + }); + } +} + +/// Command buffer that has been built. +/// +/// # Safety +/// +/// The command buffer must not outlive the command pool that it was created from, +/// nor the resources used by the recorded commands. +pub struct UnsafeCommandBuffer { + command_buffer: ash::vk::CommandBuffer, + device: Arc<Device>, + usage: CommandBufferUsage, +} + +impl UnsafeCommandBuffer { + #[inline] + pub fn usage(&self) -> CommandBufferUsage { + self.usage + } +} + +unsafe impl DeviceOwned for UnsafeCommandBuffer { + #[inline] + fn device(&self) -> &Arc<Device> { + &self.device + } +} + +unsafe impl VulkanObject for UnsafeCommandBuffer { + type Object = ash::vk::CommandBuffer; + + #[inline] + fn internal_object(&self) -> ash::vk::CommandBuffer { + self.command_buffer + } +} |