// Copyright (c) 2022 The vulkano developers // Licensed under the Apache License, Version 2.0 // or the MIT // license , // 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::{BufferContents, BufferUsage, Subbuffer}, command_buffer::{ allocator::CommandBufferAllocator, auto::RenderPassStateType, synced::{Command, SetOrPush, SyncCommandBufferBuilder}, sys::UnsafeCommandBufferBuilder, AutoCommandBufferBuilder, }, descriptor_set::{ check_descriptor_write, layout::DescriptorType, sys::UnsafeDescriptorSet, DescriptorBindingResources, DescriptorSetResources, DescriptorSetUpdateError, DescriptorSetWithOffsets, DescriptorSetsCollection, DescriptorWriteInfo, WriteDescriptorSet, }, device::{DeviceOwned, QueueFlags}, memory::{is_aligned, DeviceAlignment}, pipeline::{ graphics::{ input_assembly::{Index, IndexType}, render_pass::PipelineRenderPassType, vertex_input::VertexBuffersCollection, }, ComputePipeline, GraphicsPipeline, PipelineBindPoint, PipelineLayout, }, shader::ShaderStages, DeviceSize, RequirementNotMet, RequiresOneOf, VulkanObject, }; use parking_lot::Mutex; use smallvec::SmallVec; use std::{ cmp::min, error, fmt::{Display, Error as FmtError, Formatter}, mem::{size_of, size_of_val}, ptr, slice, sync::Arc, }; /// # Commands to bind or push state for pipeline execution commands. /// /// These commands require a queue with a pipeline type that uses the given state. impl AutoCommandBufferBuilder where A: CommandBufferAllocator, { /// Binds descriptor sets for future dispatch or draw calls. /// /// # Panics /// /// - Panics if the queue family of the command buffer does not support `pipeline_bind_point`. /// - Panics if the highest descriptor set slot being bound is not less than the number of sets /// in `pipeline_layout`. /// - Panics if `self` and any element of `descriptor_sets` do not belong to the same device. pub fn bind_descriptor_sets( &mut self, pipeline_bind_point: PipelineBindPoint, pipeline_layout: Arc, first_set: u32, descriptor_sets: S, ) -> &mut Self where S: DescriptorSetsCollection, { let descriptor_sets = descriptor_sets.into_vec(); self.validate_bind_descriptor_sets( pipeline_bind_point, &pipeline_layout, first_set, &descriptor_sets, ) .unwrap(); unsafe { let mut sets_binder = self.inner.bind_descriptor_sets(); for set in descriptor_sets.into_iter() { sets_binder.add(set); } sets_binder.submit(pipeline_bind_point, pipeline_layout, first_set); } self } fn validate_bind_descriptor_sets( &self, pipeline_bind_point: PipelineBindPoint, pipeline_layout: &PipelineLayout, first_set: u32, descriptor_sets: &[DescriptorSetWithOffsets], ) -> Result<(), BindPushError> { // VUID-vkCmdBindDescriptorSets-pipelineBindPoint-parameter pipeline_bind_point.validate_device(self.device())?; let queue_family_properties = self.queue_family_properties(); // VUID-vkCmdBindDescriptorSets-commandBuffer-cmdpool // VUID-vkCmdBindDescriptorSets-pipelineBindPoint-00361 match pipeline_bind_point { PipelineBindPoint::Compute => { if !queue_family_properties .queue_flags .intersects(QueueFlags::COMPUTE) { return Err(BindPushError::NotSupportedByQueueFamily); } } PipelineBindPoint::Graphics => { if !queue_family_properties .queue_flags .intersects(QueueFlags::GRAPHICS) { return Err(BindPushError::NotSupportedByQueueFamily); } } } // VUID-vkCmdBindDescriptorSets-firstSet-00360 if first_set + descriptor_sets.len() as u32 > pipeline_layout.set_layouts().len() as u32 { return Err(BindPushError::DescriptorSetOutOfRange { set_num: first_set + descriptor_sets.len() as u32, pipeline_layout_set_count: pipeline_layout.set_layouts().len() as u32, }); } let properties = self.device().physical_device().properties(); let uniform_alignment = properties.min_uniform_buffer_offset_alignment; let storage_alignment = properties.min_storage_buffer_offset_alignment; for (i, set) in descriptor_sets.iter().enumerate() { let set_num = first_set + i as u32; let (set, dynamic_offsets) = set.as_ref(); // VUID-vkCmdBindDescriptorSets-commonparent assert_eq!(self.device(), set.device()); let set_layout = set.layout(); let pipeline_set_layout = &pipeline_layout.set_layouts()[set_num as usize]; // VUID-vkCmdBindDescriptorSets-pDescriptorSets-00358 if !pipeline_set_layout.is_compatible_with(set_layout) { return Err(BindPushError::DescriptorSetNotCompatible { set_num }); } let mut dynamic_offsets_remaining = dynamic_offsets; let mut required_dynamic_offset_count = 0; for (&binding_num, binding) in set_layout.bindings() { let required_alignment = match binding.descriptor_type { DescriptorType::UniformBufferDynamic => uniform_alignment, DescriptorType::StorageBufferDynamic => storage_alignment, _ => continue, }; let count = if binding.variable_descriptor_count { set.variable_descriptor_count() } else { binding.descriptor_count } as usize; required_dynamic_offset_count += count; if !dynamic_offsets_remaining.is_empty() { let split_index = min(count, dynamic_offsets_remaining.len()); let dynamic_offsets = &dynamic_offsets_remaining[..split_index]; dynamic_offsets_remaining = &dynamic_offsets_remaining[split_index..]; let elements = match set.resources().binding(binding_num) { Some(DescriptorBindingResources::Buffer(elements)) => elements.as_slice(), _ => unreachable!(), }; for (index, (&offset, element)) in dynamic_offsets.iter().zip(elements).enumerate() { // VUID-vkCmdBindDescriptorSets-pDynamicOffsets-01971 // VUID-vkCmdBindDescriptorSets-pDynamicOffsets-01972 if !is_aligned(offset as DeviceSize, required_alignment) { return Err(BindPushError::DynamicOffsetNotAligned { set_num, binding_num, index: index as u32, offset, required_alignment, }); } if let Some((buffer, range)) = element { // VUID-vkCmdBindDescriptorSets-pDescriptorSets-01979 if offset as DeviceSize + range.end > buffer.size() { return Err(BindPushError::DynamicOffsetOutOfBufferBounds { set_num, binding_num, index: index as u32, offset, range_end: range.end, buffer_size: buffer.size(), }); } } } } } // VUID-vkCmdBindDescriptorSets-dynamicOffsetCount-00359 if dynamic_offsets.len() != required_dynamic_offset_count { return Err(BindPushError::DynamicOffsetCountMismatch { set_num, provided_count: dynamic_offsets.len(), required_count: required_dynamic_offset_count, }); } } Ok(()) } /// Binds an index buffer for future indexed draw calls. /// /// # Panics /// /// - Panics if the queue family of the command buffer does not support graphics operations. /// - Panics if `self` and `index_buffer` do not belong to the same device. /// - Panics if `index_buffer` does not have the [`BufferUsage::INDEX_BUFFER`] usage enabled. /// - If the index buffer contains `u8` indices, panics if the [`index_type_uint8`] feature is /// not enabled on the device. /// /// [`BufferUsage::INDEX_BUFFER`]: crate::buffer::BufferUsage::INDEX_BUFFER /// [`index_type_uint8`]: crate::device::Features::index_type_uint8 pub fn bind_index_buffer(&mut self, index_buffer: Subbuffer<[I]>) -> &mut Self { self.validate_bind_index_buffer(index_buffer.as_bytes(), I::ty()) .unwrap(); unsafe { self.inner .bind_index_buffer(index_buffer.into_bytes(), I::ty()); } self } fn validate_bind_index_buffer( &self, index_buffer: &Subbuffer<[u8]>, index_type: IndexType, ) -> Result<(), BindPushError> { let queue_family_properties = self.queue_family_properties(); // VUID-vkCmdBindIndexBuffer-commandBuffer-cmdpool if !queue_family_properties .queue_flags .intersects(QueueFlags::GRAPHICS) { return Err(BindPushError::NotSupportedByQueueFamily); } // VUID-vkCmdBindIndexBuffer-commonparent assert_eq!(self.device(), index_buffer.device()); // VUID-vkCmdBindIndexBuffer-buffer-00433 if !index_buffer .buffer() .usage() .intersects(BufferUsage::INDEX_BUFFER) { return Err(BindPushError::IndexBufferMissingUsage); } // VUID-vkCmdBindIndexBuffer-indexType-02765 if index_type == IndexType::U8 && !self.device().enabled_features().index_type_uint8 { return Err(BindPushError::RequirementNotMet { required_for: "`index_type` is `IndexType::U8`", requires_one_of: RequiresOneOf { features: &["index_type_uint8"], ..Default::default() }, }); } // TODO: // VUID-vkCmdBindIndexBuffer-offset-00432 Ok(()) } /// Binds a compute pipeline for future dispatch calls. /// /// # Panics /// /// - Panics if the queue family of the command buffer does not support compute operations. /// - Panics if `self` and `pipeline` do not belong to the same device. pub fn bind_pipeline_compute(&mut self, pipeline: Arc) -> &mut Self { self.validate_bind_pipeline_compute(&pipeline).unwrap(); unsafe { self.inner.bind_pipeline_compute(pipeline); } self } fn validate_bind_pipeline_compute( &self, pipeline: &ComputePipeline, ) -> Result<(), BindPushError> { let queue_family_properties = self.queue_family_properties(); // VUID-vkCmdBindPipeline-pipelineBindPoint-00777 if !queue_family_properties .queue_flags .intersects(QueueFlags::COMPUTE) { return Err(BindPushError::NotSupportedByQueueFamily); } // VUID-vkCmdBindPipeline-commonparent assert_eq!(self.device(), pipeline.device()); Ok(()) } /// Binds a graphics pipeline for future draw calls. /// /// # Panics /// /// - Panics if the queue family of the command buffer does not support graphics operations. /// - Panics if `self` and `pipeline` do not belong to the same device. pub fn bind_pipeline_graphics(&mut self, pipeline: Arc) -> &mut Self { self.validate_bind_pipeline_graphics(&pipeline).unwrap(); unsafe { self.inner.bind_pipeline_graphics(pipeline); } self } fn validate_bind_pipeline_graphics( &self, pipeline: &GraphicsPipeline, ) -> Result<(), BindPushError> { let queue_family_properties = self.queue_family_properties(); // VUID-vkCmdBindPipeline-pipelineBindPoint-00778 if !queue_family_properties .queue_flags .intersects(QueueFlags::GRAPHICS) { return Err(BindPushError::NotSupportedByQueueFamily); } // VUID-vkCmdBindPipeline-commonparent assert_eq!(self.device(), pipeline.device()); if let Some(last_pipeline) = self .render_pass_state .as_ref() .and_then(|render_pass_state| match &render_pass_state.render_pass { RenderPassStateType::BeginRendering(state) if state.pipeline_used => { self.state().pipeline_graphics() } _ => None, }) { if let ( PipelineRenderPassType::BeginRendering(pipeline_rendering_info), PipelineRenderPassType::BeginRendering(last_pipeline_rendering_info), ) = (pipeline.render_pass(), last_pipeline.render_pass()) { // VUID-vkCmdBindPipeline-pipeline-06195 // VUID-vkCmdBindPipeline-pipeline-06196 if pipeline_rendering_info.color_attachment_formats != last_pipeline_rendering_info.color_attachment_formats { return Err(BindPushError::PreviousPipelineColorAttachmentFormatMismatch); } // VUID-vkCmdBindPipeline-pipeline-06197 if pipeline_rendering_info.depth_attachment_format != last_pipeline_rendering_info.depth_attachment_format { return Err(BindPushError::PreviousPipelineDepthAttachmentFormatMismatch); } // VUID-vkCmdBindPipeline-pipeline-06194 if pipeline_rendering_info.stencil_attachment_format != last_pipeline_rendering_info.stencil_attachment_format { return Err(BindPushError::PreviousPipelineStencilAttachmentFormatMismatch); } } } // VUID-vkCmdBindPipeline-pipeline-00781 // TODO: Ok(()) } /// Binds vertex buffers for future draw calls. /// /// # Panics /// /// - Panics if the queue family of the command buffer does not support graphics operations. /// - Panics if the highest vertex buffer binding being bound is greater than the /// [`max_vertex_input_bindings`] device property. /// - Panics if `self` and any element of `vertex_buffers` do not belong to the same device. /// - Panics if any element of `vertex_buffers` does not have the /// [`BufferUsage::VERTEX_BUFFER`] usage enabled. /// /// [`max_vertex_input_bindings`]: crate::device::Properties::max_vertex_input_bindings /// [`BufferUsage::VERTEX_BUFFER`]: crate::buffer::BufferUsage::VERTEX_BUFFER pub fn bind_vertex_buffers( &mut self, first_binding: u32, vertex_buffers: impl VertexBuffersCollection, ) -> &mut Self { let vertex_buffers = vertex_buffers.into_vec(); self.validate_bind_vertex_buffers(first_binding, &vertex_buffers) .unwrap(); unsafe { let mut binder = self.inner.bind_vertex_buffers(); for vb in vertex_buffers.into_iter() { binder.add(vb); } binder.submit(first_binding); } self } fn validate_bind_vertex_buffers( &self, first_binding: u32, vertex_buffers: &[Subbuffer<[u8]>], ) -> Result<(), BindPushError> { let queue_family_properties = self.queue_family_properties(); // VUID-vkCmdBindVertexBuffers-commandBuffer-cmdpool if !queue_family_properties .queue_flags .intersects(QueueFlags::GRAPHICS) { return Err(BindPushError::NotSupportedByQueueFamily); } // VUID-vkCmdBindVertexBuffers-firstBinding-00624 // VUID-vkCmdBindVertexBuffers-firstBinding-00625 if first_binding + vertex_buffers.len() as u32 > self .device() .physical_device() .properties() .max_vertex_input_bindings { return Err(BindPushError::MaxVertexInputBindingsExceeded { _binding_count: first_binding + vertex_buffers.len() as u32, _max: self .device() .physical_device() .properties() .max_vertex_input_bindings, }); } for buffer in vertex_buffers { // VUID-vkCmdBindVertexBuffers-commonparent assert_eq!(self.device(), buffer.device()); // VUID-vkCmdBindVertexBuffers-pBuffers-00627 if !buffer .buffer() .usage() .intersects(BufferUsage::VERTEX_BUFFER) { return Err(BindPushError::VertexBufferMissingUsage); } } Ok(()) } /// Sets push constants for future dispatch or draw calls. /// /// # Panics /// /// - Panics if `offset` is not a multiple of 4. /// - Panics if the size of `push_constants` is not a multiple of 4. /// - Panics if any of the bytes in `push_constants` do not fall within any of the pipeline /// layout's push constant ranges. pub fn push_constants( &mut self, pipeline_layout: Arc, offset: u32, push_constants: Pc, ) -> &mut Self where Pc: BufferContents, { let size = size_of::() as u32; if size == 0 { return self; } // SAFETY: `&push_constants` is a valid pointer, and the size of the struct is `size`, // thus, getting a slice of the whole struct is safe if its not modified. let push_constants = unsafe { slice::from_raw_parts(&push_constants as *const Pc as *const u8, size as usize) }; self.validate_push_constants(&pipeline_layout, offset, push_constants) .unwrap(); let mut current_offset = offset; let mut remaining_size = size; for range in pipeline_layout .push_constant_ranges_disjoint() .iter() .skip_while(|range| range.offset + range.size <= offset) { // there is a gap between ranges, but the passed push_constants contains // some bytes in this gap, exit the loop and report error if range.offset > current_offset { break; } // push the minimum of the whole remaining data, and the part until the end of this range let push_size = remaining_size.min(range.offset + range.size - current_offset); let data_offset = (current_offset - offset) as usize; unsafe { self.inner.push_constants::<[u8]>( pipeline_layout.clone(), range.stages, current_offset, push_size, &push_constants[data_offset..(data_offset + push_size as usize)], ); } current_offset += push_size; remaining_size -= push_size; if remaining_size == 0 { break; } } debug_assert!(remaining_size == 0); self } fn validate_push_constants( &self, pipeline_layout: &PipelineLayout, offset: u32, push_constants: &[u8], ) -> Result<(), BindPushError> { if offset % 4 != 0 { return Err(BindPushError::PushConstantsOffsetNotAligned); } if push_constants.len() % 4 != 0 { return Err(BindPushError::PushConstantsSizeNotAligned); } let mut current_offset = offset; let mut remaining_size = push_constants.len() as u32; for range in pipeline_layout .push_constant_ranges_disjoint() .iter() .skip_while(|range| range.offset + range.size <= offset) { // there is a gap between ranges, but the passed push_constants contains // some bytes in this gap, exit the loop and report error if range.offset > current_offset { break; } // push the minimum of the whole remaining data, and the part until the end of this range let push_size = remaining_size.min(range.offset + range.size - current_offset); current_offset += push_size; remaining_size -= push_size; if remaining_size == 0 { break; } } if remaining_size != 0 { return Err(BindPushError::PushConstantsDataOutOfRange { offset: current_offset, }); } Ok(()) } /// Pushes descriptor data directly into the command buffer for future dispatch or draw calls. /// /// # Panics /// /// - Panics if the queue family of the command buffer does not support `pipeline_bind_point`. /// - Panics if the /// [`khr_push_descriptor`](crate::device::DeviceExtensions::khr_push_descriptor) /// extension is not enabled on the device. /// - Panics if `set_num` is not less than the number of sets in `pipeline_layout`. /// - Panics if an element of `descriptor_writes` is not compatible with `pipeline_layout`. pub fn push_descriptor_set( &mut self, pipeline_bind_point: PipelineBindPoint, pipeline_layout: Arc, set_num: u32, descriptor_writes: impl IntoIterator, ) -> &mut Self { let descriptor_writes: SmallVec<[_; 8]> = descriptor_writes.into_iter().collect(); self.validate_push_descriptor_set( pipeline_bind_point, &pipeline_layout, set_num, &descriptor_writes, ) .unwrap(); unsafe { self.inner.push_descriptor_set( pipeline_bind_point, pipeline_layout, set_num, descriptor_writes, ); } self } fn validate_push_descriptor_set( &self, pipeline_bind_point: PipelineBindPoint, pipeline_layout: &PipelineLayout, set_num: u32, descriptor_writes: &[WriteDescriptorSet], ) -> Result<(), BindPushError> { if !self.device().enabled_extensions().khr_push_descriptor { return Err(BindPushError::RequirementNotMet { required_for: "`AutoCommandBufferBuilder::push_descriptor_set`", requires_one_of: RequiresOneOf { device_extensions: &["khr_push_descriptor"], ..Default::default() }, }); } // VUID-vkCmdPushDescriptorSetKHR-pipelineBindPoint-parameter pipeline_bind_point.validate_device(self.device())?; let queue_family_properties = self.queue_family_properties(); // VUID-vkCmdPushDescriptorSetKHR-commandBuffer-cmdpool // VUID-vkCmdPushDescriptorSetKHR-pipelineBindPoint-00363 match pipeline_bind_point { PipelineBindPoint::Compute => { if !queue_family_properties .queue_flags .intersects(QueueFlags::COMPUTE) { return Err(BindPushError::NotSupportedByQueueFamily); } } PipelineBindPoint::Graphics => { if !queue_family_properties .queue_flags .intersects(QueueFlags::GRAPHICS) { return Err(BindPushError::NotSupportedByQueueFamily); } } } // VUID-vkCmdPushDescriptorSetKHR-commonparent assert_eq!(self.device(), pipeline_layout.device()); // VUID-vkCmdPushDescriptorSetKHR-set-00364 if set_num as usize > pipeline_layout.set_layouts().len() { return Err(BindPushError::DescriptorSetOutOfRange { set_num, pipeline_layout_set_count: pipeline_layout.set_layouts().len() as u32, }); } let descriptor_set_layout = &pipeline_layout.set_layouts()[set_num as usize]; // VUID-vkCmdPushDescriptorSetKHR-set-00365 if !descriptor_set_layout.push_descriptor() { return Err(BindPushError::DescriptorSetNotPush { set_num }); } for write in descriptor_writes { check_descriptor_write(write, descriptor_set_layout, 0)?; } Ok(()) } } impl SyncCommandBufferBuilder { /// Starts the process of binding descriptor sets. Returns an intermediate struct which can be /// used to add the sets. #[inline] pub fn bind_descriptor_sets(&mut self) -> SyncCommandBufferBuilderBindDescriptorSets<'_> { SyncCommandBufferBuilderBindDescriptorSets { builder: self, descriptor_sets: SmallVec::new(), } } /// Calls `vkCmdBindIndexBuffer` on the builder. #[inline] pub unsafe fn bind_index_buffer(&mut self, buffer: Subbuffer<[u8]>, index_type: IndexType) { struct Cmd { buffer: Subbuffer<[u8]>, index_type: IndexType, } impl Command for Cmd { fn name(&self) -> &'static str { "bind_index_buffer" } unsafe fn send(&self, out: &mut UnsafeCommandBufferBuilder) { out.bind_index_buffer(&self.buffer, self.index_type); } } self.current_state.index_buffer = Some((buffer.clone(), index_type)); self.commands.push(Box::new(Cmd { buffer, index_type })); } /// Calls `vkCmdBindPipeline` on the builder with a compute pipeline. #[inline] pub unsafe fn bind_pipeline_compute(&mut self, pipeline: Arc) { struct Cmd { pipeline: Arc, } impl Command for Cmd { fn name(&self) -> &'static str { "bind_pipeline_compute" } unsafe fn send(&self, out: &mut UnsafeCommandBufferBuilder) { out.bind_pipeline_compute(&self.pipeline); } } self.current_state.pipeline_compute = Some(pipeline.clone()); self.commands.push(Box::new(Cmd { pipeline })); } /// Calls `vkCmdBindPipeline` on the builder with a graphics pipeline. #[inline] pub unsafe fn bind_pipeline_graphics(&mut self, pipeline: Arc) { struct Cmd { pipeline: Arc, } impl Command for Cmd { fn name(&self) -> &'static str { "bind_pipeline_graphics" } unsafe fn send(&self, out: &mut UnsafeCommandBufferBuilder) { out.bind_pipeline_graphics(&self.pipeline); } } // Reset any states that are fixed in the new pipeline. The pipeline bind command will // overwrite these states. self.current_state.reset_dynamic_states( pipeline .dynamic_states() .filter(|(_, d)| !d) // not dynamic .map(|(s, _)| s), ); self.current_state.pipeline_graphics = Some(pipeline.clone()); self.commands.push(Box::new(Cmd { pipeline })); } /// Starts the process of binding vertex buffers. Returns an intermediate struct which can be /// used to add the buffers. #[inline] pub fn bind_vertex_buffers(&mut self) -> SyncCommandBufferBuilderBindVertexBuffer<'_> { SyncCommandBufferBuilderBindVertexBuffer { builder: self, inner: UnsafeCommandBufferBuilderBindVertexBuffer::new(), buffers: SmallVec::new(), } } /// Calls `vkCmdPushConstants` on the builder. pub unsafe fn push_constants( &mut self, pipeline_layout: Arc, stages: ShaderStages, offset: u32, size: u32, data: &D, ) where D: ?Sized + Send + Sync + 'static, { struct Cmd { pipeline_layout: Arc, stages: ShaderStages, offset: u32, size: u32, data: Box<[u8]>, } impl Command for Cmd { fn name(&self) -> &'static str { "push_constants" } unsafe fn send(&self, out: &mut UnsafeCommandBufferBuilder) { out.push_constants::<[u8]>( &self.pipeline_layout, self.stages, self.offset, self.size, &self.data, ); } } debug_assert!(size_of_val(data) >= size as usize); let mut out = Vec::with_capacity(size as usize); ptr::copy::( data as *const D as *const u8, out.as_mut_ptr(), size as usize, ); out.set_len(size as usize); self.commands.push(Box::new(Cmd { pipeline_layout: pipeline_layout.clone(), stages, offset, size, data: out.into(), })); // TODO: Push constant invalidations. // The Vulkan spec currently is unclear about this, so Vulkano currently just marks // push constants as set, and never unsets them. See: // https://github.com/KhronosGroup/Vulkan-Docs/issues/1485 // https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/2711 self.current_state .push_constants .insert(offset..offset + size); self.current_state.push_constants_pipeline_layout = Some(pipeline_layout); } /// Calls `vkCmdPushDescriptorSetKHR` on the builder. pub unsafe fn push_descriptor_set( &mut self, pipeline_bind_point: PipelineBindPoint, pipeline_layout: Arc, set_num: u32, descriptor_writes: impl IntoIterator, ) { struct Cmd { pipeline_bind_point: PipelineBindPoint, pipeline_layout: Arc, set_num: u32, descriptor_writes: SmallVec<[WriteDescriptorSet; 8]>, } impl Command for Cmd { fn name(&self) -> &'static str { "push_descriptor_set" } unsafe fn send(&self, out: &mut UnsafeCommandBufferBuilder) { out.push_descriptor_set( self.pipeline_bind_point, &self.pipeline_layout, self.set_num, &self.descriptor_writes, ); } } let descriptor_writes: SmallVec<[WriteDescriptorSet; 8]> = descriptor_writes.into_iter().collect(); let state = self.current_state.invalidate_descriptor_sets( pipeline_bind_point, pipeline_layout.clone(), set_num, 1, ); let layout = state.pipeline_layout.set_layouts()[set_num as usize].as_ref(); debug_assert!(layout.push_descriptor()); let set_resources = match state .descriptor_sets .entry(set_num) .or_insert_with(|| SetOrPush::Push(DescriptorSetResources::new(layout, 0))) { SetOrPush::Push(set_resources) => set_resources, _ => unreachable!(), }; for write in &descriptor_writes { set_resources.update(write); } self.commands.push(Box::new(Cmd { pipeline_bind_point, pipeline_layout, set_num, descriptor_writes, })); } } pub struct SyncCommandBufferBuilderBindDescriptorSets<'b> { builder: &'b mut SyncCommandBufferBuilder, descriptor_sets: SmallVec<[DescriptorSetWithOffsets; 12]>, } impl<'b> SyncCommandBufferBuilderBindDescriptorSets<'b> { /// Adds a descriptor set to the list. pub fn add(&mut self, descriptor_set: impl Into) { self.descriptor_sets.push(descriptor_set.into()); } #[inline] pub unsafe fn submit( self, pipeline_bind_point: PipelineBindPoint, pipeline_layout: Arc, first_set: u32, ) { if self.descriptor_sets.is_empty() { return; } struct Cmd { descriptor_sets: SmallVec<[DescriptorSetWithOffsets; 12]>, pipeline_bind_point: PipelineBindPoint, pipeline_layout: Arc, first_set: u32, } impl Command for Cmd { fn name(&self) -> &'static str { "bind_descriptor_sets" } unsafe fn send(&self, out: &mut UnsafeCommandBufferBuilder) { let descriptor_sets = self.descriptor_sets.iter().map(|x| x.as_ref().0.inner()); let dynamic_offsets = self .descriptor_sets .iter() .flat_map(|x| x.as_ref().1.iter().copied()); out.bind_descriptor_sets( self.pipeline_bind_point, &self.pipeline_layout, self.first_set, descriptor_sets, dynamic_offsets, ); } } let state = self.builder.current_state.invalidate_descriptor_sets( pipeline_bind_point, pipeline_layout.clone(), first_set, self.descriptor_sets.len() as u32, ); for (set_num, set) in self.descriptor_sets.iter().enumerate() { state .descriptor_sets .insert(first_set + set_num as u32, SetOrPush::Set(set.clone())); } self.builder.commands.push(Box::new(Cmd { descriptor_sets: self.descriptor_sets, pipeline_bind_point, pipeline_layout, first_set, })); } } /// Prototype for a `vkCmdBindVertexBuffers`. pub struct SyncCommandBufferBuilderBindVertexBuffer<'a> { builder: &'a mut SyncCommandBufferBuilder, inner: UnsafeCommandBufferBuilderBindVertexBuffer, buffers: SmallVec<[Subbuffer<[u8]>; 4]>, } impl<'a> SyncCommandBufferBuilderBindVertexBuffer<'a> { /// Adds a buffer to the list. #[inline] pub fn add(&mut self, buffer: Subbuffer<[u8]>) { self.inner.add(&buffer); self.buffers.push(buffer); } #[inline] pub unsafe fn submit(self, first_set: u32) { struct Cmd { first_set: u32, inner: Mutex>, _buffers: SmallVec<[Subbuffer<[u8]>; 4]>, } impl Command for Cmd { fn name(&self) -> &'static str { "bind_vertex_buffers" } unsafe fn send(&self, out: &mut UnsafeCommandBufferBuilder) { out.bind_vertex_buffers(self.first_set, self.inner.lock().take().unwrap()); } } for (i, buffer) in self.buffers.iter().enumerate() { self.builder .current_state .vertex_buffers .insert(first_set + i as u32, buffer.clone()); } self.builder.commands.push(Box::new(Cmd { first_set, inner: Mutex::new(Some(self.inner)), _buffers: self.buffers, })); } } impl UnsafeCommandBufferBuilder { /// 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>( &mut self, pipeline_bind_point: PipelineBindPoint, pipeline_layout: &PipelineLayout, first_set: u32, sets: impl IntoIterator, dynamic_offsets: impl IntoIterator, ) { let fns = self.device.fns(); let sets: SmallVec<[_; 12]> = sets.into_iter().map(|s| s.handle()).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_set + num_bindings <= pipeline_layout.set_layouts().len() as u32); (fns.v1_0.cmd_bind_descriptor_sets)( self.handle, pipeline_bind_point.into(), pipeline_layout.handle(), first_set, 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(&mut self, buffer: &Subbuffer<[u8]>, index_type: IndexType) { let fns = self.device.fns(); debug_assert!(buffer.offset() < buffer.buffer().size()); debug_assert!(buffer .buffer() .usage() .intersects(BufferUsage::INDEX_BUFFER)); (fns.v1_0.cmd_bind_index_buffer)( self.handle, buffer.buffer().handle(), buffer.offset(), index_type.into(), ); } /// Calls `vkCmdBindPipeline` on the builder with a compute pipeline. #[inline] pub unsafe fn bind_pipeline_compute(&mut self, pipeline: &ComputePipeline) { let fns = self.device.fns(); (fns.v1_0.cmd_bind_pipeline)( self.handle, ash::vk::PipelineBindPoint::COMPUTE, pipeline.handle(), ); } /// Calls `vkCmdBindPipeline` on the builder with a graphics pipeline. #[inline] pub unsafe fn bind_pipeline_graphics(&mut self, pipeline: &GraphicsPipeline) { let fns = self.device.fns(); (fns.v1_0.cmd_bind_pipeline)( self.handle, ash::vk::PipelineBindPoint::GRAPHICS, pipeline.handle(), ); } /// 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. // TODO: vkCmdBindVertexBuffers2EXT #[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 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)( self.handle, first_binding, num_bindings, params.raw_buffers.as_ptr(), params.offsets.as_ptr(), ); } /// Calls `vkCmdPushConstants` on the builder. pub unsafe fn push_constants( &mut self, pipeline_layout: &PipelineLayout, stages: ShaderStages, offset: u32, size: u32, data: &D, ) where D: BufferContents + ?Sized, { let fns = self.device.fns(); debug_assert!(!stages.is_empty()); debug_assert!(size > 0); debug_assert_eq!(size % 4, 0); debug_assert_eq!(offset % 4, 0); debug_assert!(size_of_val(data) >= size as usize); (fns.v1_0.cmd_push_constants)( self.handle, pipeline_layout.handle(), stages.into(), offset, size, data as *const _ as *const _, ); } /// Calls `vkCmdPushDescriptorSetKHR` on the builder. /// /// If the list is empty then the command is automatically ignored. pub unsafe fn push_descriptor_set<'a>( &mut self, pipeline_bind_point: PipelineBindPoint, pipeline_layout: &PipelineLayout, set_num: u32, descriptor_writes: impl IntoIterator, ) { debug_assert!(self.device.enabled_extensions().khr_push_descriptor); let (infos, mut writes): (SmallVec<[_; 8]>, SmallVec<[_; 8]>) = descriptor_writes .into_iter() .map(|write| { let binding = &pipeline_layout.set_layouts()[set_num as usize].bindings()[&write.binding()]; ( write.to_vulkan_info(binding.descriptor_type), write.to_vulkan(ash::vk::DescriptorSet::null(), binding.descriptor_type), ) }) .unzip(); if writes.is_empty() { return; } // Set the info pointers separately. for (info, write) in infos.iter().zip(writes.iter_mut()) { match info { DescriptorWriteInfo::Image(info) => { write.descriptor_count = info.len() as u32; write.p_image_info = info.as_ptr(); } DescriptorWriteInfo::Buffer(info) => { write.descriptor_count = info.len() as u32; write.p_buffer_info = info.as_ptr(); } DescriptorWriteInfo::BufferView(info) => { write.descriptor_count = info.len() as u32; write.p_texel_buffer_view = info.as_ptr(); } } debug_assert!(write.descriptor_count != 0); } let fns = self.device.fns(); (fns.khr_push_descriptor.cmd_push_descriptor_set_khr)( self.handle, pipeline_bind_point.into(), pipeline_layout.handle(), set_num, writes.len() as u32, writes.as_ptr(), ); } } /// Prototype for a `vkCmdBindVertexBuffers`. #[derive(Debug)] pub struct UnsafeCommandBufferBuilderBindVertexBuffer { // Raw handles of the buffers to bind. pub raw_buffers: SmallVec<[ash::vk::Buffer; 4]>, // Raw offsets of the buffers to bind. pub 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(&mut self, subbuffer: &Subbuffer<[u8]>) { debug_assert!(subbuffer .buffer() .usage() .intersects(BufferUsage::VERTEX_BUFFER)); self.raw_buffers.push(subbuffer.buffer().handle()); self.offsets.push(subbuffer.offset()); } } #[derive(Clone, Debug)] pub(in super::super) enum BindPushError { DescriptorSetUpdateError(DescriptorSetUpdateError), RequirementNotMet { required_for: &'static str, requires_one_of: RequiresOneOf, }, /// The element of `descriptor_sets` being bound to a slot is not compatible with the /// corresponding slot in `pipeline_layout`. DescriptorSetNotCompatible { set_num: u32, }, /// The descriptor set number being pushed is not defined for push descriptor sets in the /// pipeline layout. DescriptorSetNotPush { set_num: u32, }, /// The highest descriptor set slot being bound is greater than the number of sets in /// `pipeline_layout`. DescriptorSetOutOfRange { set_num: u32, pipeline_layout_set_count: u32, }, /// In an element of `descriptor_sets`, the number of provided dynamic offsets does not match /// the number required by the descriptor set. DynamicOffsetCountMismatch { set_num: u32, provided_count: usize, required_count: usize, }, /// In an element of `descriptor_sets`, a provided dynamic offset /// is not a multiple of the value of the [`min_uniform_buffer_offset_alignment`] or /// [`min_storage_buffer_offset_alignment`] property. /// /// min_uniform_buffer_offset_alignment: crate::device::Properties::min_uniform_buffer_offset_alignment /// min_storage_buffer_offset_alignment: crate::device::Properties::min_storage_buffer_offset_alignment DynamicOffsetNotAligned { set_num: u32, binding_num: u32, index: u32, offset: u32, required_alignment: DeviceAlignment, }, /// In an element of `descriptor_sets`, a provided dynamic offset, when added to the end of the /// buffer range bound to the descriptor set, is greater than the size of the buffer. DynamicOffsetOutOfBufferBounds { set_num: u32, binding_num: u32, index: u32, offset: u32, range_end: DeviceSize, buffer_size: DeviceSize, }, /// An index buffer is missing the `index_buffer` usage. IndexBufferMissingUsage, /// The `max_vertex_input_bindings` limit has been exceeded. MaxVertexInputBindingsExceeded { _binding_count: u32, _max: u32, }, /// The queue family doesn't allow this operation. NotSupportedByQueueFamily, /// The newly set pipeline has color attachment formats that do not match the /// previously used pipeline. PreviousPipelineColorAttachmentFormatMismatch, /// The newly set pipeline has a depth attachment format that does not match the /// previously used pipeline. PreviousPipelineDepthAttachmentFormatMismatch, /// The newly set pipeline has a stencil attachment format that does not match the /// previously used pipeline. PreviousPipelineStencilAttachmentFormatMismatch, /// The push constants data to be written at an offset is not included in any push constant /// range of the pipeline layout. PushConstantsDataOutOfRange { offset: u32, }, /// The push constants offset is not a multiple of 4. PushConstantsOffsetNotAligned, /// The push constants size is not a multiple of 4. PushConstantsSizeNotAligned, /// A vertex buffer is missing the `vertex_buffer` usage. VertexBufferMissingUsage, } impl error::Error for BindPushError { fn source(&self) -> Option<&(dyn error::Error + 'static)> { match self { BindPushError::DescriptorSetUpdateError(err) => Some(err), _ => None, } } } impl Display for BindPushError { fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> { match self { Self::RequirementNotMet { required_for, requires_one_of, } => write!( f, "a requirement was not met for: {}; requires one of: {}", required_for, requires_one_of, ), Self::DescriptorSetUpdateError(_) => write!(f, "a DescriptorSetUpdateError"), Self::DescriptorSetNotCompatible { set_num } => write!( f, "the element of `descriptor_sets` being bound to slot {} is not compatible with \ the corresponding slot in `pipeline_layout`", set_num, ), Self::DescriptorSetNotPush { set_num } => write!( f, "the descriptor set number being pushed ({}) is not defined for push descriptor \ sets in the pipeline layout", set_num, ), Self::DescriptorSetOutOfRange { set_num, pipeline_layout_set_count, } => write!( f, "the highest descriptor set slot being bound ({}) is greater than the number of \ sets in `pipeline_layout` ({})", set_num, pipeline_layout_set_count, ), Self::DynamicOffsetCountMismatch { set_num, provided_count, required_count, } => write!( f, "in the element of `descriptor_sets` being bound to slot {}, the number of \ provided dynamic offsets ({}) does not match the number required by the \ descriptor set ({})", set_num, provided_count, required_count, ), Self::DynamicOffsetNotAligned { set_num, binding_num, index, offset, required_alignment, } => write!( f, "in the element of `descriptor_sets` being bound to slot {}, the dynamic offset \ provided for binding {} index {} ({}) is not a multiple of the value of the \ `min_uniform_buffer_offset_alignment` or `min_storage_buffer_offset_alignment` \ property ({:?})", set_num, binding_num, index, offset, required_alignment, ), Self::DynamicOffsetOutOfBufferBounds { set_num, binding_num, index, offset, range_end, buffer_size, } => write!( f, "in the element of `descriptor_sets` being bound to slot {}, the dynamic offset \ provided for binding {} index {} ({}), when added to the end of the buffer range \ bound to the descriptor set ({}), is greater than the size of the buffer ({})", set_num, binding_num, index, offset, range_end, buffer_size, ), Self::IndexBufferMissingUsage => { write!(f, "an index buffer is missing the `index_buffer` usage") } Self::MaxVertexInputBindingsExceeded { .. } => { write!(f, "the `max_vertex_input_bindings` limit has been exceeded") } Self::NotSupportedByQueueFamily => { write!(f, "the queue family doesn't allow this operation") } Self::PreviousPipelineColorAttachmentFormatMismatch => write!( f, "the newly set pipeline has color attachment formats that do not match the \ previously used pipeline", ), Self::PreviousPipelineDepthAttachmentFormatMismatch => write!( f, "the newly set pipeline has a depth attachment format that does not match the \ previously used pipeline", ), Self::PreviousPipelineStencilAttachmentFormatMismatch => write!( f, "the newly set pipeline has a stencil attachment format that does not match the \ previously used pipeline", ), Self::PushConstantsDataOutOfRange { offset } => write!( f, "the push constants data to be written at offset {} is not included in any push \ constant range of the pipeline layout", offset, ), Self::PushConstantsOffsetNotAligned => { write!(f, "the push constants offset is not a multiple of 4") } Self::PushConstantsSizeNotAligned => { write!(f, "the push constants size is not a multiple of 4") } Self::VertexBufferMissingUsage => { write!(f, "a vertex buffer is missing the `vertex_buffer` usage") } } } } impl From for BindPushError { fn from(err: DescriptorSetUpdateError) -> Self { Self::DescriptorSetUpdateError(err) } } impl From for BindPushError { fn from(err: RequirementNotMet) -> Self { Self::RequirementNotMet { required_for: err.required_for, requires_one_of: err.requires_one_of, } } }