diff options
Diffstat (limited to 'src/command_buffer/commands/pipeline.rs')
-rw-r--r-- | src/command_buffer/commands/pipeline.rs | 2975 |
1 files changed, 2975 insertions, 0 deletions
diff --git a/src/command_buffer/commands/pipeline.rs b/src/command_buffer/commands/pipeline.rs new file mode 100644 index 0000000..27a162a --- /dev/null +++ b/src/command_buffer/commands/pipeline.rs @@ -0,0 +1,2975 @@ +// Copyright (c) 2022 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::{view::BufferView, BufferUsage, Subbuffer}, + command_buffer::{ + allocator::CommandBufferAllocator, + auto::{RenderPassState, RenderPassStateType}, + synced::{Command, Resource, SyncCommandBufferBuilder, SyncCommandBufferBuilderError}, + sys::UnsafeCommandBufferBuilder, + AutoCommandBufferBuilder, DispatchIndirectCommand, DrawIndexedIndirectCommand, + DrawIndirectCommand, ResourceInCommand, ResourceUseRef, SubpassContents, + }, + descriptor_set::{layout::DescriptorType, DescriptorBindingResources}, + device::{DeviceOwned, QueueFlags}, + format::{Format, FormatFeatures}, + image::{ + view::ImageViewType, ImageAccess, ImageAspects, ImageSubresourceRange, ImageViewAbstract, + SampleCount, + }, + pipeline::{ + graphics::{ + input_assembly::{PrimitiveTopology, PrimitiveTopologyClass}, + render_pass::PipelineRenderPassType, + vertex_input::VertexInputRate, + }, + DynamicState, GraphicsPipeline, PartialStateMode, Pipeline, PipelineLayout, + }, + sampler::{Sampler, SamplerImageViewIncompatibleError}, + shader::{DescriptorBindingRequirements, ShaderScalarType, ShaderStage}, + sync::{AccessFlags, PipelineMemoryAccess, PipelineStages}, + DeviceSize, RequiresOneOf, VulkanObject, +}; +use std::{ + cmp::min, + error::Error, + fmt::{Display, Error as FmtError, Formatter}, + mem::size_of, + ops::Range, + sync::Arc, +}; + +/// # Commands to execute a bound pipeline. +/// +/// Dispatch commands require a compute queue, draw commands require a graphics queue. +impl<L, A> AutoCommandBufferBuilder<L, A> +where + A: CommandBufferAllocator, +{ + /// Perform a single compute operation using a compute pipeline. + /// + /// A compute pipeline must have been bound using + /// [`bind_pipeline_compute`](Self::bind_pipeline_compute). Any resources used by the compute + /// pipeline, such as descriptor sets, must have been set beforehand. + pub fn dispatch( + &mut self, + group_counts: [u32; 3], + ) -> Result<&mut Self, PipelineExecutionError> { + self.validate_dispatch(group_counts)?; + + unsafe { + self.inner.dispatch(group_counts)?; + } + + Ok(self) + } + + fn validate_dispatch(&self, group_counts: [u32; 3]) -> Result<(), PipelineExecutionError> { + let queue_family_properties = self.queue_family_properties(); + + // VUID-vkCmdDispatch-commandBuffer-cmdpool + if !queue_family_properties + .queue_flags + .intersects(QueueFlags::COMPUTE) + { + return Err(PipelineExecutionError::NotSupportedByQueueFamily); + } + + // VUID-vkCmdDispatch-renderpass + if self.render_pass_state.is_some() { + return Err(PipelineExecutionError::ForbiddenInsideRenderPass); + } + + // VUID-vkCmdDispatch-None-02700 + let pipeline = match self.state().pipeline_compute() { + Some(x) => x.as_ref(), + None => return Err(PipelineExecutionError::PipelineNotBound), + }; + + self.validate_pipeline_descriptor_sets(pipeline)?; + self.validate_pipeline_push_constants(pipeline.layout())?; + + let max = self + .device() + .physical_device() + .properties() + .max_compute_work_group_count; + + // VUID-vkCmdDispatch-groupCountX-00386 + // VUID-vkCmdDispatch-groupCountY-00387 + // VUID-vkCmdDispatch-groupCountZ-00388 + if group_counts[0] > max[0] || group_counts[1] > max[1] || group_counts[2] > max[2] { + return Err(PipelineExecutionError::MaxComputeWorkGroupCountExceeded { + requested: group_counts, + max, + }); + } + + Ok(()) + } + + /// Perform multiple compute operations using a compute pipeline. One dispatch is performed for + /// each [`DispatchIndirectCommand`] struct in `indirect_buffer`. + /// + /// A compute pipeline must have been bound using + /// [`bind_pipeline_compute`](Self::bind_pipeline_compute). Any resources used by the compute + /// pipeline, such as descriptor sets, must have been set beforehand. + pub fn dispatch_indirect( + &mut self, + indirect_buffer: Subbuffer<[DispatchIndirectCommand]>, + ) -> Result<&mut Self, PipelineExecutionError> { + self.validate_dispatch_indirect(indirect_buffer.as_bytes())?; + + unsafe { + self.inner.dispatch_indirect(indirect_buffer)?; + } + + Ok(self) + } + + fn validate_dispatch_indirect( + &self, + indirect_buffer: &Subbuffer<[u8]>, + ) -> Result<(), PipelineExecutionError> { + let queue_family_properties = self.queue_family_properties(); + + // VUID-vkCmdDispatchIndirect-commandBuffer-cmdpool + if !queue_family_properties + .queue_flags + .intersects(QueueFlags::COMPUTE) + { + return Err(PipelineExecutionError::NotSupportedByQueueFamily); + } + + // VUID-vkCmdDispatchIndirect-renderpass + if self.render_pass_state.is_some() { + return Err(PipelineExecutionError::ForbiddenInsideRenderPass); + } + + // VUID-vkCmdDispatchIndirect-None-02700 + let pipeline = match self.state().pipeline_compute() { + Some(x) => x.as_ref(), + None => return Err(PipelineExecutionError::PipelineNotBound), + }; + + self.validate_pipeline_descriptor_sets(pipeline)?; + self.validate_pipeline_push_constants(pipeline.layout())?; + self.validate_indirect_buffer(indirect_buffer)?; + + Ok(()) + } + + /// Perform a single draw operation using a graphics pipeline. + /// + /// The parameters specify the first vertex and the number of vertices to draw, and the first + /// instance and number of instances. For non-instanced drawing, specify `instance_count` as 1 + /// and `first_instance` as 0. + /// + /// A graphics pipeline must have been bound using + /// [`bind_pipeline_graphics`](Self::bind_pipeline_graphics). Any resources used by the graphics + /// pipeline, such as descriptor sets, vertex buffers and dynamic state, must have been set + /// beforehand. If the bound graphics pipeline uses vertex buffers, then the provided vertex and + /// instance ranges must be in range of the bound vertex buffers. + pub fn draw( + &mut self, + vertex_count: u32, + instance_count: u32, + first_vertex: u32, + first_instance: u32, + ) -> Result<&mut Self, PipelineExecutionError> { + self.validate_draw(vertex_count, instance_count, first_vertex, first_instance)?; + + unsafe { + self.inner + .draw(vertex_count, instance_count, first_vertex, first_instance)?; + } + + if let RenderPassStateType::BeginRendering(state) = + &mut self.render_pass_state.as_mut().unwrap().render_pass + { + state.pipeline_used = true; + } + + Ok(self) + } + + fn validate_draw( + &self, + vertex_count: u32, + instance_count: u32, + first_vertex: u32, + first_instance: u32, + ) -> Result<(), PipelineExecutionError> { + // VUID-vkCmdDraw-renderpass + let render_pass_state = self + .render_pass_state + .as_ref() + .ok_or(PipelineExecutionError::ForbiddenOutsideRenderPass)?; + + // VUID-vkCmdDraw-None-02700 + let pipeline = match self.state().pipeline_graphics() { + Some(x) => x.as_ref(), + None => return Err(PipelineExecutionError::PipelineNotBound), + }; + + self.validate_pipeline_descriptor_sets(pipeline)?; + self.validate_pipeline_push_constants(pipeline.layout())?; + self.validate_pipeline_graphics_dynamic_state(pipeline)?; + self.validate_pipeline_graphics_render_pass(pipeline, render_pass_state)?; + self.validate_pipeline_graphics_vertex_buffers( + pipeline, + Some((first_vertex, vertex_count)), + Some((first_instance, instance_count)), + )?; + + Ok(()) + } + + /// Perform multiple draw operations using a graphics pipeline. + /// + /// One draw is performed for each [`DrawIndirectCommand`] struct in `indirect_buffer`. + /// The maximum number of draw commands in the buffer is limited by the + /// [`max_draw_indirect_count`](crate::device::Properties::max_draw_indirect_count) limit. + /// This limit is 1 unless the + /// [`multi_draw_indirect`](crate::device::Features::multi_draw_indirect) feature has been + /// enabled. + /// + /// A graphics pipeline must have been bound using + /// [`bind_pipeline_graphics`](Self::bind_pipeline_graphics). Any resources used by the graphics + /// pipeline, such as descriptor sets, vertex buffers and dynamic state, must have been set + /// beforehand. If the bound graphics pipeline uses vertex buffers, then the vertex and instance + /// ranges of each `DrawIndirectCommand` in the indirect buffer must be in range of the bound + /// vertex buffers. + pub fn draw_indirect( + &mut self, + indirect_buffer: Subbuffer<[DrawIndirectCommand]>, + ) -> Result<&mut Self, PipelineExecutionError> { + let draw_count = indirect_buffer.len() as u32; + let stride = size_of::<DrawIndirectCommand>() as u32; + self.validate_draw_indirect(indirect_buffer.as_bytes(), draw_count, stride)?; + + unsafe { + self.inner + .draw_indirect(indirect_buffer, draw_count, stride)?; + } + + if let RenderPassStateType::BeginRendering(state) = + &mut self.render_pass_state.as_mut().unwrap().render_pass + { + state.pipeline_used = true; + } + + Ok(self) + } + + fn validate_draw_indirect( + &self, + indirect_buffer: &Subbuffer<[u8]>, + draw_count: u32, + _stride: u32, + ) -> Result<(), PipelineExecutionError> { + // VUID-vkCmdDrawIndirect-renderpass + let render_pass_state = self + .render_pass_state + .as_ref() + .ok_or(PipelineExecutionError::ForbiddenOutsideRenderPass)?; + + // VUID-vkCmdDrawIndirect-None-02700 + let pipeline = match self.state().pipeline_graphics() { + Some(x) => x.as_ref(), + None => return Err(PipelineExecutionError::PipelineNotBound), + }; + + self.validate_pipeline_descriptor_sets(pipeline)?; + self.validate_pipeline_push_constants(pipeline.layout())?; + self.validate_pipeline_graphics_dynamic_state(pipeline)?; + self.validate_pipeline_graphics_render_pass(pipeline, render_pass_state)?; + self.validate_pipeline_graphics_vertex_buffers(pipeline, None, None)?; + + self.validate_indirect_buffer(indirect_buffer)?; + + // VUID-vkCmdDrawIndirect-drawCount-02718 + if draw_count > 1 && !self.device().enabled_features().multi_draw_indirect { + return Err(PipelineExecutionError::RequirementNotMet { + required_for: "`draw_count` is greater than `1`", + requires_one_of: RequiresOneOf { + features: &["multi_draw_indirect"], + ..Default::default() + }, + }); + } + + let max = self + .device() + .physical_device() + .properties() + .max_draw_indirect_count; + + // VUID-vkCmdDrawIndirect-drawCount-02719 + if draw_count > max { + return Err(PipelineExecutionError::MaxDrawIndirectCountExceeded { + provided: draw_count, + max, + }); + } + + Ok(()) + } + + /// Perform a single draw operation using a graphics pipeline, using an index buffer. + /// + /// The parameters specify the first index and the number of indices in the index buffer that + /// should be used, and the first instance and number of instances. For non-instanced drawing, + /// specify `instance_count` as 1 and `first_instance` as 0. The `vertex_offset` is a constant + /// value that should be added to each index in the index buffer to produce the final vertex + /// number to be used. + /// + /// An index buffer must have been bound using + /// [`bind_index_buffer`](Self::bind_index_buffer), and the provided index range must be in + /// range of the bound index buffer. + /// + /// A graphics pipeline must have been bound using + /// [`bind_pipeline_graphics`](Self::bind_pipeline_graphics). Any resources used by the graphics + /// pipeline, such as descriptor sets, vertex buffers and dynamic state, must have been set + /// beforehand. If the bound graphics pipeline uses vertex buffers, then the provided instance + /// range must be in range of the bound vertex buffers. The vertex indices in the index buffer + /// must be in range of the bound vertex buffers. + pub fn draw_indexed( + &mut self, + index_count: u32, + instance_count: u32, + first_index: u32, + vertex_offset: i32, + first_instance: u32, + ) -> Result<&mut Self, PipelineExecutionError> { + self.validate_draw_indexed( + index_count, + instance_count, + first_index, + vertex_offset, + first_instance, + )?; + + unsafe { + self.inner.draw_indexed( + index_count, + instance_count, + first_index, + vertex_offset, + first_instance, + )?; + } + + if let RenderPassStateType::BeginRendering(state) = + &mut self.render_pass_state.as_mut().unwrap().render_pass + { + state.pipeline_used = true; + } + + Ok(self) + } + + fn validate_draw_indexed( + &self, + index_count: u32, + instance_count: u32, + first_index: u32, + _vertex_offset: i32, + first_instance: u32, + ) -> Result<(), PipelineExecutionError> { + // TODO: how to handle an index out of range of the vertex buffers? + + // VUID-vkCmdDrawIndexed-renderpass + let render_pass_state = self + .render_pass_state + .as_ref() + .ok_or(PipelineExecutionError::ForbiddenOutsideRenderPass)?; + + // VUID-vkCmdDrawIndexed-None-02700 + let pipeline = match self.state().pipeline_graphics() { + Some(x) => x.as_ref(), + None => return Err(PipelineExecutionError::PipelineNotBound), + }; + + self.validate_pipeline_descriptor_sets(pipeline)?; + self.validate_pipeline_push_constants(pipeline.layout())?; + self.validate_pipeline_graphics_dynamic_state(pipeline)?; + self.validate_pipeline_graphics_render_pass(pipeline, render_pass_state)?; + self.validate_pipeline_graphics_vertex_buffers( + pipeline, + None, + Some((first_instance, instance_count)), + )?; + + self.validate_index_buffer(Some((first_index, index_count)))?; + + Ok(()) + } + + /// Perform multiple draw operations using a graphics pipeline, using an index buffer. + /// + /// One draw is performed for each [`DrawIndexedIndirectCommand`] struct in `indirect_buffer`. + /// The maximum number of draw commands in the buffer is limited by the + /// [`max_draw_indirect_count`](crate::device::Properties::max_draw_indirect_count) limit. + /// This limit is 1 unless the + /// [`multi_draw_indirect`](crate::device::Features::multi_draw_indirect) feature has been + /// enabled. + /// + /// An index buffer must have been bound using + /// [`bind_index_buffer`](Self::bind_index_buffer), and the index ranges of each + /// `DrawIndexedIndirectCommand` in the indirect buffer must be in range of the bound index + /// buffer. + /// + /// A graphics pipeline must have been bound using + /// [`bind_pipeline_graphics`](Self::bind_pipeline_graphics). Any resources used by the graphics + /// pipeline, such as descriptor sets, vertex buffers and dynamic state, must have been set + /// beforehand. If the bound graphics pipeline uses vertex buffers, then the instance ranges of + /// each `DrawIndexedIndirectCommand` in the indirect buffer must be in range of the bound + /// vertex buffers. + pub fn draw_indexed_indirect( + &mut self, + indirect_buffer: Subbuffer<[DrawIndexedIndirectCommand]>, + ) -> Result<&mut Self, PipelineExecutionError> { + let draw_count = indirect_buffer.len() as u32; + let stride = size_of::<DrawIndexedIndirectCommand>() as u32; + self.validate_draw_indexed_indirect(indirect_buffer.as_bytes(), draw_count, stride)?; + + unsafe { + self.inner + .draw_indexed_indirect(indirect_buffer, draw_count, stride)?; + } + + if let RenderPassStateType::BeginRendering(state) = + &mut self.render_pass_state.as_mut().unwrap().render_pass + { + state.pipeline_used = true; + } + + Ok(self) + } + + fn validate_draw_indexed_indirect( + &self, + indirect_buffer: &Subbuffer<[u8]>, + draw_count: u32, + _stride: u32, + ) -> Result<(), PipelineExecutionError> { + // VUID-vkCmdDrawIndexedIndirect-renderpass + let render_pass_state = self + .render_pass_state + .as_ref() + .ok_or(PipelineExecutionError::ForbiddenOutsideRenderPass)?; + + // VUID-vkCmdDrawIndexedIndirect-None-02700 + let pipeline = match self.state().pipeline_graphics() { + Some(x) => x.as_ref(), + None => return Err(PipelineExecutionError::PipelineNotBound), + }; + + self.validate_pipeline_descriptor_sets(pipeline)?; + self.validate_pipeline_push_constants(pipeline.layout())?; + self.validate_pipeline_graphics_dynamic_state(pipeline)?; + self.validate_pipeline_graphics_render_pass(pipeline, render_pass_state)?; + self.validate_pipeline_graphics_vertex_buffers(pipeline, None, None)?; + + self.validate_index_buffer(None)?; + self.validate_indirect_buffer(indirect_buffer)?; + + // VUID-vkCmdDrawIndexedIndirect-drawCount-02718 + if draw_count > 1 && !self.device().enabled_features().multi_draw_indirect { + return Err(PipelineExecutionError::RequirementNotMet { + required_for: "`draw_count` is greater than `1`", + requires_one_of: RequiresOneOf { + features: &["multi_draw_indirect"], + ..Default::default() + }, + }); + } + + let max = self + .device() + .physical_device() + .properties() + .max_draw_indirect_count; + + // VUID-vkCmdDrawIndexedIndirect-drawCount-02719 + if draw_count > max { + return Err(PipelineExecutionError::MaxDrawIndirectCountExceeded { + provided: draw_count, + max, + }); + } + + Ok(()) + } + + fn validate_index_buffer( + &self, + indices: Option<(u32, u32)>, + ) -> Result<(), PipelineExecutionError> { + let current_state = self.state(); + + // VUID? + let (index_buffer, index_type) = match current_state.index_buffer() { + Some(x) => x, + None => return Err(PipelineExecutionError::IndexBufferNotBound), + }; + + if let Some((first_index, index_count)) = indices { + let max_index_count = (index_buffer.size() / index_type.size()) as u32; + + // // VUID-vkCmdDrawIndexed-firstIndex-04932 + if first_index + index_count > max_index_count { + return Err(PipelineExecutionError::IndexBufferRangeOutOfBounds { + highest_index: first_index + index_count, + max_index_count, + }); + } + } + + Ok(()) + } + + fn validate_indirect_buffer( + &self, + buffer: &Subbuffer<[u8]>, + ) -> Result<(), PipelineExecutionError> { + // VUID-vkCmdDispatchIndirect-commonparent + assert_eq!(self.device(), buffer.device()); + + // VUID-vkCmdDispatchIndirect-buffer-02709 + if !buffer + .buffer() + .usage() + .intersects(BufferUsage::INDIRECT_BUFFER) + { + return Err(PipelineExecutionError::IndirectBufferMissingUsage); + } + + // VUID-vkCmdDispatchIndirect-offset-02710 + // TODO: + + Ok(()) + } + + fn validate_pipeline_descriptor_sets<Pl: Pipeline>( + &self, + pipeline: &Pl, + ) -> Result<(), PipelineExecutionError> { + fn validate_resources<T>( + set_num: u32, + binding_num: u32, + binding_reqs: &DescriptorBindingRequirements, + elements: &[Option<T>], + mut extra_check: impl FnMut(u32, &T) -> Result<(), DescriptorResourceInvalidError>, + ) -> Result<(), PipelineExecutionError> { + let elements_to_check = if let Some(descriptor_count) = binding_reqs.descriptor_count { + // The shader has a fixed-sized array, so it will never access more than + // the first `descriptor_count` elements. + elements.get(..descriptor_count as usize).ok_or({ + // There are less than `descriptor_count` elements in `elements` + PipelineExecutionError::DescriptorResourceInvalid { + set_num, + binding_num, + index: elements.len() as u32, + error: DescriptorResourceInvalidError::Missing, + } + })? + } else { + // The shader has a runtime-sized array, so any element could potentially + // be accessed. We must check them all. + elements + }; + + for (index, element) in elements_to_check.iter().enumerate() { + let index = index as u32; + + // VUID-vkCmdDispatch-None-02699 + let element = match element { + Some(x) => x, + None => { + return Err(PipelineExecutionError::DescriptorResourceInvalid { + set_num, + binding_num, + index, + error: DescriptorResourceInvalidError::Missing, + }) + } + }; + + if let Err(error) = extra_check(index, element) { + return Err(PipelineExecutionError::DescriptorResourceInvalid { + set_num, + binding_num, + index, + error, + }); + } + } + + Ok(()) + } + + if pipeline.num_used_descriptor_sets() == 0 { + return Ok(()); + } + + let current_state = self.state(); + + // VUID-vkCmdDispatch-None-02697 + let bindings_pipeline_layout = + match current_state.descriptor_sets_pipeline_layout(pipeline.bind_point()) { + Some(x) => x, + None => return Err(PipelineExecutionError::PipelineLayoutNotCompatible), + }; + + // VUID-vkCmdDispatch-None-02697 + if !pipeline.layout().is_compatible_with( + bindings_pipeline_layout, + pipeline.num_used_descriptor_sets(), + ) { + return Err(PipelineExecutionError::PipelineLayoutNotCompatible); + } + + for (&(set_num, binding_num), binding_reqs) in pipeline.descriptor_binding_requirements() { + let layout_binding = + &pipeline.layout().set_layouts()[set_num as usize].bindings()[&binding_num]; + + let check_buffer = + |_index: u32, (_buffer, _range): &(Subbuffer<[u8]>, Range<DeviceSize>)| Ok(()); + + let check_buffer_view = |index: u32, buffer_view: &Arc<BufferView>| { + for desc_reqs in (binding_reqs.descriptors.get(&Some(index)).into_iter()) + .chain(binding_reqs.descriptors.get(&None)) + { + if layout_binding.descriptor_type == DescriptorType::StorageTexelBuffer { + // VUID-vkCmdDispatch-OpTypeImage-06423 + if binding_reqs.image_format.is_none() + && !desc_reqs.memory_write.is_empty() + && !buffer_view + .format_features() + .intersects(FormatFeatures::STORAGE_WRITE_WITHOUT_FORMAT) + { + return Err(DescriptorResourceInvalidError::StorageWriteWithoutFormatNotSupported); + } + + // VUID-vkCmdDispatch-OpTypeImage-06424 + if binding_reqs.image_format.is_none() + && !desc_reqs.memory_read.is_empty() + && !buffer_view + .format_features() + .intersects(FormatFeatures::STORAGE_READ_WITHOUT_FORMAT) + { + return Err(DescriptorResourceInvalidError::StorageReadWithoutFormatNotSupported); + } + } + } + + Ok(()) + }; + + let check_image_view_common = |index: u32, image_view: &Arc<dyn ImageViewAbstract>| { + for desc_reqs in (binding_reqs.descriptors.get(&Some(index)).into_iter()) + .chain(binding_reqs.descriptors.get(&None)) + { + // VUID-vkCmdDispatch-None-02691 + if desc_reqs.storage_image_atomic + && !image_view + .format_features() + .intersects(FormatFeatures::STORAGE_IMAGE_ATOMIC) + { + return Err(DescriptorResourceInvalidError::StorageImageAtomicNotSupported); + } + + if layout_binding.descriptor_type == DescriptorType::StorageImage { + // VUID-vkCmdDispatch-OpTypeImage-06423 + if binding_reqs.image_format.is_none() + && !desc_reqs.memory_write.is_empty() + && !image_view + .format_features() + .intersects(FormatFeatures::STORAGE_WRITE_WITHOUT_FORMAT) + { + return Err( + DescriptorResourceInvalidError::StorageWriteWithoutFormatNotSupported, + ); + } + + // VUID-vkCmdDispatch-OpTypeImage-06424 + if binding_reqs.image_format.is_none() + && !desc_reqs.memory_read.is_empty() + && !image_view + .format_features() + .intersects(FormatFeatures::STORAGE_READ_WITHOUT_FORMAT) + { + return Err( + DescriptorResourceInvalidError::StorageReadWithoutFormatNotSupported, + ); + } + } + } + + /* + Instruction/Sampler/Image View Validation + https://registry.khronos.org/vulkan/specs/1.3-extensions/html/chap16.html#textures-input-validation + */ + + // The SPIR-V Image Format is not compatible with the image view’s format. + if let Some(format) = binding_reqs.image_format { + if image_view.format() != Some(format) { + return Err(DescriptorResourceInvalidError::ImageViewFormatMismatch { + required: format, + provided: image_view.format(), + }); + } + } + + // Rules for viewType + if let Some(image_view_type) = binding_reqs.image_view_type { + if image_view.view_type() != image_view_type { + return Err(DescriptorResourceInvalidError::ImageViewTypeMismatch { + required: image_view_type, + provided: image_view.view_type(), + }); + } + } + + // - If the image was created with VkImageCreateInfo::samples equal to + // VK_SAMPLE_COUNT_1_BIT, the instruction must have MS = 0. + // - If the image was created with VkImageCreateInfo::samples not equal to + // VK_SAMPLE_COUNT_1_BIT, the instruction must have MS = 1. + if binding_reqs.image_multisampled + != (image_view.image().samples() != SampleCount::Sample1) + { + return Err( + DescriptorResourceInvalidError::ImageViewMultisampledMismatch { + required: binding_reqs.image_multisampled, + provided: image_view.image().samples() != SampleCount::Sample1, + }, + ); + } + + // - If the Sampled Type of the OpTypeImage does not match the numeric format of the + // image, as shown in the SPIR-V Sampled Type column of the + // Interpretation of Numeric Format table. + // - If the signedness of any read or sample operation does not match the signedness of + // the image’s format. + if let Some(scalar_type) = binding_reqs.image_scalar_type { + let aspects = image_view.subresource_range().aspects; + let view_scalar_type = ShaderScalarType::from( + if aspects.intersects( + ImageAspects::COLOR + | ImageAspects::PLANE_0 + | ImageAspects::PLANE_1 + | ImageAspects::PLANE_2, + ) { + image_view.format().unwrap().type_color().unwrap() + } else if aspects.intersects(ImageAspects::DEPTH) { + image_view.format().unwrap().type_depth().unwrap() + } else if aspects.intersects(ImageAspects::STENCIL) { + image_view.format().unwrap().type_stencil().unwrap() + } else { + // Per `ImageViewBuilder::aspects` and + // VUID-VkDescriptorImageInfo-imageView-01976 + unreachable!() + }, + ); + + if scalar_type != view_scalar_type { + return Err( + DescriptorResourceInvalidError::ImageViewScalarTypeMismatch { + required: scalar_type, + provided: view_scalar_type, + }, + ); + } + } + + Ok(()) + }; + + let check_sampler_common = |index: u32, sampler: &Arc<Sampler>| { + for desc_reqs in (binding_reqs.descriptors.get(&Some(index)).into_iter()) + .chain(binding_reqs.descriptors.get(&None)) + { + // VUID-vkCmdDispatch-None-02703 + // VUID-vkCmdDispatch-None-02704 + if desc_reqs.sampler_no_unnormalized_coordinates + && sampler.unnormalized_coordinates() + { + return Err( + DescriptorResourceInvalidError::SamplerUnnormalizedCoordinatesNotAllowed, + ); + } + + // - OpImageFetch, OpImageSparseFetch, OpImage*Gather, and OpImageSparse*Gather must not + // be used with a sampler that enables sampler Y′CBCR conversion. + // - The ConstOffset and Offset operands must not be used with a sampler that enables + // sampler Y′CBCR conversion. + if desc_reqs.sampler_no_ycbcr_conversion + && sampler.sampler_ycbcr_conversion().is_some() + { + return Err( + DescriptorResourceInvalidError::SamplerYcbcrConversionNotAllowed, + ); + } + + /* + Instruction/Sampler/Image View Validation + https://registry.khronos.org/vulkan/specs/1.3-extensions/html/chap16.html#textures-input-validation + */ + + // - The SPIR-V instruction is one of the OpImage*Dref* instructions and the sampler + // compareEnable is VK_FALSE + // - The SPIR-V instruction is not one of the OpImage*Dref* instructions and the sampler + // compareEnable is VK_TRUE + if desc_reqs.sampler_compare != sampler.compare().is_some() { + return Err(DescriptorResourceInvalidError::SamplerCompareMismatch { + required: desc_reqs.sampler_compare, + provided: sampler.compare().is_some(), + }); + } + } + + Ok(()) + }; + + let check_image_view = |index: u32, image_view: &Arc<dyn ImageViewAbstract>| { + check_image_view_common(index, image_view)?; + + if let Some(sampler) = layout_binding.immutable_samplers.get(index as usize) { + check_sampler_common(index, sampler)?; + } + + Ok(()) + }; + + let check_image_view_sampler = + |index: u32, (image_view, sampler): &(Arc<dyn ImageViewAbstract>, Arc<Sampler>)| { + check_image_view_common(index, image_view)?; + check_sampler_common(index, sampler)?; + + Ok(()) + }; + + let check_sampler = |index: u32, sampler: &Arc<Sampler>| { + check_sampler_common(index, sampler)?; + + for desc_reqs in (binding_reqs.descriptors.get(&Some(index)).into_iter()) + .chain(binding_reqs.descriptors.get(&None)) + { + // Check sampler-image compatibility. Only done for separate samplers; + // combined image samplers are checked when updating the descriptor set. + + // If the image view isn't actually present in the resources, then just skip it. + // It will be caught later by check_resources. + let iter = desc_reqs.sampler_with_images.iter().filter_map(|id| { + current_state + .descriptor_set(pipeline.bind_point(), id.set) + .and_then(|set| set.resources().binding(id.binding)) + .and_then(|res| match res { + DescriptorBindingResources::ImageView(elements) => elements + .get(id.index as usize) + .and_then(|opt| opt.as_ref().map(|opt| (id, opt))), + _ => None, + }) + }); + + for (id, image_view) in iter { + if let Err(error) = sampler.check_can_sample(image_view.as_ref()) { + return Err( + DescriptorResourceInvalidError::SamplerImageViewIncompatible { + image_view_set_num: id.set, + image_view_binding_num: id.binding, + image_view_index: id.index, + error, + }, + ); + } + } + } + + Ok(()) + }; + + let check_none = |index: u32, _: &()| { + if let Some(sampler) = layout_binding.immutable_samplers.get(index as usize) { + check_sampler(index, sampler)?; + } + + Ok(()) + }; + + let set_resources = match current_state.descriptor_set(pipeline.bind_point(), set_num) { + Some(x) => x.resources(), + None => return Err(PipelineExecutionError::DescriptorSetNotBound { set_num }), + }; + + let binding_resources = set_resources.binding(binding_num).unwrap(); + + match binding_resources { + DescriptorBindingResources::None(elements) => { + validate_resources(set_num, binding_num, binding_reqs, elements, check_none)?; + } + DescriptorBindingResources::Buffer(elements) => { + validate_resources(set_num, binding_num, binding_reqs, elements, check_buffer)?; + } + DescriptorBindingResources::BufferView(elements) => { + validate_resources( + set_num, + binding_num, + binding_reqs, + elements, + check_buffer_view, + )?; + } + DescriptorBindingResources::ImageView(elements) => { + validate_resources( + set_num, + binding_num, + binding_reqs, + elements, + check_image_view, + )?; + } + DescriptorBindingResources::ImageViewSampler(elements) => { + validate_resources( + set_num, + binding_num, + binding_reqs, + elements, + check_image_view_sampler, + )?; + } + DescriptorBindingResources::Sampler(elements) => { + validate_resources( + set_num, + binding_num, + binding_reqs, + elements, + check_sampler, + )?; + } + } + } + + Ok(()) + } + + fn validate_pipeline_push_constants( + &self, + pipeline_layout: &PipelineLayout, + ) -> Result<(), PipelineExecutionError> { + if pipeline_layout.push_constant_ranges().is_empty() + || self.device().enabled_features().maintenance4 + { + return Ok(()); + } + + let current_state = self.state(); + + // VUID-vkCmdDispatch-maintenance4-06425 + let constants_pipeline_layout = match current_state.push_constants_pipeline_layout() { + Some(x) => x, + None => return Err(PipelineExecutionError::PushConstantsMissing), + }; + + // VUID-vkCmdDispatch-maintenance4-06425 + if pipeline_layout.handle() != constants_pipeline_layout.handle() + && pipeline_layout.push_constant_ranges() + != constants_pipeline_layout.push_constant_ranges() + { + return Err(PipelineExecutionError::PushConstantsNotCompatible); + } + + let set_bytes = current_state.push_constants(); + + // VUID-vkCmdDispatch-maintenance4-06425 + if !pipeline_layout + .push_constant_ranges() + .iter() + .all(|pc_range| set_bytes.contains(pc_range.offset..pc_range.offset + pc_range.size)) + { + return Err(PipelineExecutionError::PushConstantsMissing); + } + + Ok(()) + } + + fn validate_pipeline_graphics_dynamic_state( + &self, + pipeline: &GraphicsPipeline, + ) -> Result<(), PipelineExecutionError> { + let device = pipeline.device(); + let current_state = self.state(); + + // VUID-vkCmdDraw-commandBuffer-02701 + for dynamic_state in pipeline + .dynamic_states() + .filter(|(_, d)| *d) + .map(|(s, _)| s) + { + match dynamic_state { + DynamicState::BlendConstants => { + // VUID? + if current_state.blend_constants().is_none() { + return Err(PipelineExecutionError::DynamicStateNotSet { dynamic_state }); + } + } + DynamicState::ColorWriteEnable => { + // VUID-vkCmdDraw-attachmentCount-06667 + let enables = if let Some(enables) = current_state.color_write_enable() { + enables + } else { + return Err(PipelineExecutionError::DynamicStateNotSet { dynamic_state }); + }; + + // VUID-vkCmdDraw-attachmentCount-06667 + if enables.len() < pipeline.color_blend_state().unwrap().attachments.len() { + return Err( + PipelineExecutionError::DynamicColorWriteEnableNotEnoughValues { + color_write_enable_count: enables.len() as u32, + attachment_count: pipeline + .color_blend_state() + .unwrap() + .attachments + .len() as u32, + }, + ); + } + } + DynamicState::CullMode => { + // VUID? + if current_state.cull_mode().is_none() { + return Err(PipelineExecutionError::DynamicStateNotSet { dynamic_state }); + } + } + DynamicState::DepthBias => { + // VUID? + if current_state.depth_bias().is_none() { + return Err(PipelineExecutionError::DynamicStateNotSet { dynamic_state }); + } + } + DynamicState::DepthBiasEnable => { + // VUID-vkCmdDraw-None-04877 + if current_state.depth_bias_enable().is_none() { + return Err(PipelineExecutionError::DynamicStateNotSet { dynamic_state }); + } + } + DynamicState::DepthBounds => { + // VUID? + if current_state.depth_bounds().is_none() { + return Err(PipelineExecutionError::DynamicStateNotSet { dynamic_state }); + } + } + DynamicState::DepthBoundsTestEnable => { + // VUID? + if current_state.depth_bounds_test_enable().is_none() { + return Err(PipelineExecutionError::DynamicStateNotSet { dynamic_state }); + } + } + DynamicState::DepthCompareOp => { + // VUID? + if current_state.depth_compare_op().is_none() { + return Err(PipelineExecutionError::DynamicStateNotSet { dynamic_state }); + } + } + DynamicState::DepthTestEnable => { + // VUID? + if current_state.depth_test_enable().is_none() { + return Err(PipelineExecutionError::DynamicStateNotSet { dynamic_state }); + } + } + DynamicState::DepthWriteEnable => { + // VUID? + if current_state.depth_write_enable().is_none() { + return Err(PipelineExecutionError::DynamicStateNotSet { dynamic_state }); + } + + // TODO: Check if the depth buffer is writable + } + DynamicState::DiscardRectangle => { + let discard_rectangle_count = + match pipeline.discard_rectangle_state().unwrap().rectangles { + PartialStateMode::Dynamic(count) => count, + _ => unreachable!(), + }; + + for num in 0..discard_rectangle_count { + // VUID? + if current_state.discard_rectangle(num).is_none() { + return Err(PipelineExecutionError::DynamicStateNotSet { dynamic_state }); + } + } + } + DynamicState::ExclusiveScissor => todo!(), + DynamicState::FragmentShadingRate => todo!(), + DynamicState::FrontFace => { + // VUID? + if current_state.front_face().is_none() { + return Err(PipelineExecutionError::DynamicStateNotSet { dynamic_state }); + } + } + DynamicState::LineStipple => { + // VUID? + if current_state.line_stipple().is_none() { + return Err(PipelineExecutionError::DynamicStateNotSet { dynamic_state }); + } + } + DynamicState::LineWidth => { + // VUID? + if current_state.line_width().is_none() { + return Err(PipelineExecutionError::DynamicStateNotSet { dynamic_state }); + } + } + DynamicState::LogicOp => { + // VUID-vkCmdDraw-logicOp-04878 + if current_state.logic_op().is_none() { + return Err(PipelineExecutionError::DynamicStateNotSet { dynamic_state }); + } + } + DynamicState::PatchControlPoints => { + // VUID-vkCmdDraw-None-04875 + if current_state.patch_control_points().is_none() { + return Err(PipelineExecutionError::DynamicStateNotSet { dynamic_state }); + } + } + DynamicState::PrimitiveRestartEnable => { + // VUID-vkCmdDraw-None-04879 + let primitive_restart_enable = + if let Some(enable) = current_state.primitive_restart_enable() { + enable + } else { + return Err(PipelineExecutionError::DynamicStateNotSet { dynamic_state }); + }; + + if primitive_restart_enable { + let topology = match pipeline.input_assembly_state().topology { + PartialStateMode::Fixed(topology) => topology, + PartialStateMode::Dynamic(_) => { + if let Some(topology) = current_state.primitive_topology() { + topology + } else { + return Err(PipelineExecutionError::DynamicStateNotSet { + dynamic_state: DynamicState::PrimitiveTopology, + }); + } + } + }; + + match topology { + PrimitiveTopology::PointList + | PrimitiveTopology::LineList + | PrimitiveTopology::TriangleList + | PrimitiveTopology::LineListWithAdjacency + | PrimitiveTopology::TriangleListWithAdjacency => { + // VUID? + if !device.enabled_features().primitive_topology_list_restart { + return Err(PipelineExecutionError::RequirementNotMet { + required_for: "The bound pipeline sets \ + `DynamicState::PrimitiveRestartEnable` and the \ + current primitive topology is \ + `PrimitiveTopology::*List`", + requires_one_of: RequiresOneOf { + features: &["primitive_topology_list_restart"], + ..Default::default() + }, + }); + } + } + PrimitiveTopology::PatchList => { + // VUID? + if !device + .enabled_features() + .primitive_topology_patch_list_restart + { + return Err(PipelineExecutionError::RequirementNotMet { + required_for: "The bound pipeline sets \ + `DynamicState::PrimitiveRestartEnable` and the \ + current primitive topology is \ + `PrimitiveTopology::PatchList`", + requires_one_of: RequiresOneOf { + features: &["primitive_topology_patch_list_restart"], + ..Default::default() + }, + }); + } + } + _ => (), + } + } + } + DynamicState::PrimitiveTopology => { + // VUID-vkCmdDraw-primitiveTopology-03420 + let topology = if let Some(topology) = current_state.primitive_topology() { + topology + } else { + return Err(PipelineExecutionError::DynamicStateNotSet { dynamic_state }); + }; + + if pipeline.shader(ShaderStage::TessellationControl).is_some() { + // VUID? + if !matches!(topology, PrimitiveTopology::PatchList) { + return Err(PipelineExecutionError::DynamicPrimitiveTopologyInvalid { + topology, + }); + } + } else { + // VUID? + if matches!(topology, PrimitiveTopology::PatchList) { + return Err(PipelineExecutionError::DynamicPrimitiveTopologyInvalid { + topology, + }); + } + } + + let required_topology_class = match pipeline.input_assembly_state().topology { + PartialStateMode::Dynamic(topology_class) => topology_class, + _ => unreachable!(), + }; + + // VUID-vkCmdDraw-primitiveTopology-03420 + if topology.class() != required_topology_class { + return Err( + PipelineExecutionError::DynamicPrimitiveTopologyClassMismatch { + provided_class: topology.class(), + required_class: required_topology_class, + }, + ); + } + + // TODO: check that the topology matches the geometry shader + } + DynamicState::RasterizerDiscardEnable => { + // VUID-vkCmdDraw-None-04876 + if current_state.rasterizer_discard_enable().is_none() { + return Err(PipelineExecutionError::DynamicStateNotSet { dynamic_state }); + } + } + DynamicState::RayTracingPipelineStackSize => unreachable!( + "RayTracingPipelineStackSize dynamic state should not occur on a graphics pipeline" + ), + DynamicState::SampleLocations => todo!(), + DynamicState::Scissor => { + for num in 0..pipeline.viewport_state().unwrap().count().unwrap() { + // VUID? + if current_state.scissor(num).is_none() { + return Err(PipelineExecutionError::DynamicStateNotSet { dynamic_state }); + } + } + } + DynamicState::ScissorWithCount => { + // VUID-vkCmdDraw-scissorCount-03418 + // VUID-vkCmdDraw-viewportCount-03419 + let scissor_count = if let Some(scissors) = current_state.scissor_with_count() { + scissors.len() as u32 + } else { + return Err(PipelineExecutionError::DynamicStateNotSet { dynamic_state }); + }; + + // Check if the counts match, but only if the viewport count is fixed. + // If the viewport count is also dynamic, then the + // DynamicState::ViewportWithCount match arm will handle it. + if let Some(viewport_count) = pipeline.viewport_state().unwrap().count() { + // VUID-vkCmdDraw-scissorCount-03418 + if viewport_count != scissor_count { + return Err( + PipelineExecutionError::DynamicViewportScissorCountMismatch { + viewport_count, + scissor_count, + }, + ); + } + } + } + DynamicState::StencilCompareMask => { + let state = current_state.stencil_compare_mask(); + + // VUID? + if state.front.is_none() || state.back.is_none() { + return Err(PipelineExecutionError::DynamicStateNotSet { dynamic_state }); + } + } + DynamicState::StencilOp => { + let state = current_state.stencil_op(); + + // VUID? + if state.front.is_none() || state.back.is_none() { + return Err(PipelineExecutionError::DynamicStateNotSet { dynamic_state }); + } + } + DynamicState::StencilReference => { + let state = current_state.stencil_reference(); + + // VUID? + if state.front.is_none() || state.back.is_none() { + return Err(PipelineExecutionError::DynamicStateNotSet { dynamic_state }); + } + } + DynamicState::StencilTestEnable => { + // VUID? + if current_state.stencil_test_enable().is_none() { + return Err(PipelineExecutionError::DynamicStateNotSet { dynamic_state }); + } + + // TODO: Check if the stencil buffer is writable + } + DynamicState::StencilWriteMask => { + let state = current_state.stencil_write_mask(); + + // VUID? + if state.front.is_none() || state.back.is_none() { + return Err(PipelineExecutionError::DynamicStateNotSet { dynamic_state }); + } + } + DynamicState::VertexInput => todo!(), + DynamicState::VertexInputBindingStride => todo!(), + DynamicState::Viewport => { + for num in 0..pipeline.viewport_state().unwrap().count().unwrap() { + // VUID? + if current_state.viewport(num).is_none() { + return Err(PipelineExecutionError::DynamicStateNotSet { dynamic_state }); + } + } + } + DynamicState::ViewportCoarseSampleOrder => todo!(), + DynamicState::ViewportShadingRatePalette => todo!(), + DynamicState::ViewportWithCount => { + // VUID-vkCmdDraw-viewportCount-03417 + let viewport_count = if let Some(viewports) = current_state.viewport_with_count() { + viewports.len() as u32 + } else { + return Err(PipelineExecutionError::DynamicStateNotSet { dynamic_state }); + }; + + let scissor_count = if let Some(scissor_count) = + pipeline.viewport_state().unwrap().count() + { + // The scissor count is fixed. + scissor_count + } else { + // VUID-vkCmdDraw-viewportCount-03419 + // The scissor count is also dynamic. + if let Some(scissors) = current_state.scissor_with_count() { + scissors.len() as u32 + } else { + return Err(PipelineExecutionError::DynamicStateNotSet { dynamic_state }); + } + }; + + // VUID-vkCmdDraw-viewportCount-03417 + // VUID-vkCmdDraw-viewportCount-03419 + if viewport_count != scissor_count { + return Err( + PipelineExecutionError::DynamicViewportScissorCountMismatch { + viewport_count, + scissor_count, + }, + ); + } + + // TODO: VUID-vkCmdDrawIndexed-primitiveFragmentShadingRateWithMultipleViewports-04552 + // If the primitiveFragmentShadingRateWithMultipleViewports limit is not supported, + // the bound graphics pipeline was created with the + // VK_DYNAMIC_STATE_VIEWPORT_WITH_COUNT_EXT dynamic state enabled, and any of the + // shader stages of the bound graphics pipeline write to the PrimitiveShadingRateKHR + // built-in, then vkCmdSetViewportWithCountEXT must have been called in the current + // command buffer prior to this drawing command, and the viewportCount parameter of + // vkCmdSetViewportWithCountEXT must be 1 + } + DynamicState::ViewportWScaling => todo!(), + DynamicState::TessellationDomainOrigin => todo!(), + DynamicState::DepthClampEnable => todo!(), + DynamicState::PolygonMode => todo!(), + DynamicState::RasterizationSamples => todo!(), + DynamicState::SampleMask => todo!(), + DynamicState::AlphaToCoverageEnable => todo!(), + DynamicState::AlphaToOneEnable => todo!(), + DynamicState::LogicOpEnable => todo!(), + DynamicState::ColorBlendEnable => todo!(), + DynamicState::ColorBlendEquation => todo!(), + DynamicState::ColorWriteMask => todo!(), + DynamicState::RasterizationStream => todo!(), + DynamicState::ConservativeRasterizationMode => todo!(), + DynamicState::ExtraPrimitiveOverestimationSize => todo!(), + DynamicState::DepthClipEnable => todo!(), + DynamicState::SampleLocationsEnable => todo!(), + DynamicState::ColorBlendAdvanced => todo!(), + DynamicState::ProvokingVertexMode => todo!(), + DynamicState::LineRasterizationMode => todo!(), + DynamicState::LineStippleEnable => todo!(), + DynamicState::DepthClipNegativeOneToOne => todo!(), + DynamicState::ViewportWScalingEnable => todo!(), + DynamicState::ViewportSwizzle => todo!(), + DynamicState::CoverageToColorEnable => todo!(), + DynamicState::CoverageToColorLocation => todo!(), + DynamicState::CoverageModulationMode => todo!(), + DynamicState::CoverageModulationTableEnable => todo!(), + DynamicState::CoverageModulationTable => todo!(), + DynamicState::ShadingRateImageEnable => todo!(), + DynamicState::RepresentativeFragmentTestEnable => todo!(), + DynamicState::CoverageReductionMode => todo!(), + } + } + + Ok(()) + } + + fn validate_pipeline_graphics_render_pass( + &self, + pipeline: &GraphicsPipeline, + render_pass_state: &RenderPassState, + ) -> Result<(), PipelineExecutionError> { + // VUID? + if render_pass_state.contents != SubpassContents::Inline { + return Err(PipelineExecutionError::ForbiddenWithSubpassContents { + subpass_contents: render_pass_state.contents, + }); + } + + match (&render_pass_state.render_pass, pipeline.render_pass()) { + ( + RenderPassStateType::BeginRenderPass(state), + PipelineRenderPassType::BeginRenderPass(pipeline_subpass), + ) => { + // VUID-vkCmdDraw-renderPass-02684 + if !pipeline_subpass + .render_pass() + .is_compatible_with(state.subpass.render_pass()) + { + return Err(PipelineExecutionError::PipelineRenderPassNotCompatible); + } + + // VUID-vkCmdDraw-subpass-02685 + if pipeline_subpass.index() != state.subpass.index() { + return Err(PipelineExecutionError::PipelineSubpassMismatch { + pipeline: pipeline_subpass.index(), + current: state.subpass.index(), + }); + } + } + ( + RenderPassStateType::BeginRendering(current_rendering_info), + PipelineRenderPassType::BeginRendering(pipeline_rendering_info), + ) => { + // VUID-vkCmdDraw-viewMask-06178 + if pipeline_rendering_info.view_mask != render_pass_state.view_mask { + return Err(PipelineExecutionError::PipelineViewMaskMismatch { + pipeline_view_mask: pipeline_rendering_info.view_mask, + required_view_mask: render_pass_state.view_mask, + }); + } + + // VUID-vkCmdDraw-colorAttachmentCount-06179 + if pipeline_rendering_info.color_attachment_formats.len() + != current_rendering_info.color_attachment_formats.len() + { + return Err( + PipelineExecutionError::PipelineColorAttachmentCountMismatch { + pipeline_count: pipeline_rendering_info.color_attachment_formats.len() + as u32, + required_count: current_rendering_info.color_attachment_formats.len() + as u32, + }, + ); + } + + for (color_attachment_index, required_format, pipeline_format) in + current_rendering_info + .color_attachment_formats + .iter() + .zip( + pipeline_rendering_info + .color_attachment_formats + .iter() + .copied(), + ) + .enumerate() + .filter_map(|(i, (r, p))| r.map(|r| (i as u32, r, p))) + { + // VUID-vkCmdDraw-colorAttachmentCount-06180 + if Some(required_format) != pipeline_format { + return Err( + PipelineExecutionError::PipelineColorAttachmentFormatMismatch { + color_attachment_index, + pipeline_format, + required_format, + }, + ); + } + } + + if let Some((required_format, pipeline_format)) = current_rendering_info + .depth_attachment_format + .map(|r| (r, pipeline_rendering_info.depth_attachment_format)) + { + // VUID-vkCmdDraw-pDepthAttachment-06181 + if Some(required_format) != pipeline_format { + return Err( + PipelineExecutionError::PipelineDepthAttachmentFormatMismatch { + pipeline_format, + required_format, + }, + ); + } + } + + if let Some((required_format, pipeline_format)) = current_rendering_info + .stencil_attachment_format + .map(|r| (r, pipeline_rendering_info.stencil_attachment_format)) + { + // VUID-vkCmdDraw-pStencilAttachment-06182 + if Some(required_format) != pipeline_format { + return Err( + PipelineExecutionError::PipelineStencilAttachmentFormatMismatch { + pipeline_format, + required_format, + }, + ); + } + } + + // VUID-vkCmdDraw-imageView-06172 + // VUID-vkCmdDraw-imageView-06173 + // VUID-vkCmdDraw-imageView-06174 + // VUID-vkCmdDraw-imageView-06175 + // VUID-vkCmdDraw-imageView-06176 + // VUID-vkCmdDraw-imageView-06177 + // TODO: + } + _ => return Err(PipelineExecutionError::PipelineRenderPassTypeMismatch), + } + + // VUID-vkCmdDraw-None-02686 + // TODO: + + Ok(()) + } + + fn validate_pipeline_graphics_vertex_buffers( + &self, + pipeline: &GraphicsPipeline, + vertices: Option<(u32, u32)>, + instances: Option<(u32, u32)>, + ) -> Result<(), PipelineExecutionError> { + let vertex_input = pipeline.vertex_input_state(); + let mut vertices_in_buffers: Option<u64> = None; + let mut instances_in_buffers: Option<u64> = None; + let current_state = self.state(); + + for (&binding_num, binding_desc) in &vertex_input.bindings { + // VUID-vkCmdDraw-None-04007 + let vertex_buffer = match current_state.vertex_buffer(binding_num) { + Some(x) => x, + None => return Err(PipelineExecutionError::VertexBufferNotBound { binding_num }), + }; + + let mut num_elements = vertex_buffer.size() / binding_desc.stride as u64; + + match binding_desc.input_rate { + VertexInputRate::Vertex => { + vertices_in_buffers = Some(if let Some(x) = vertices_in_buffers { + min(x, num_elements) + } else { + num_elements + }); + } + VertexInputRate::Instance { divisor } => { + if divisor == 0 { + // A divisor of 0 means the same instance data is used for all instances, + // so we can draw any number of instances from a single element. + // The buffer must contain at least one element though. + if num_elements != 0 { + num_elements = u64::MAX; + } + } else { + // If divisor is e.g. 2, we use only half the amount of data from the source + // buffer, so the number of instances that can be drawn is twice as large. + num_elements = num_elements.saturating_mul(divisor as u64); + } + + instances_in_buffers = Some(if let Some(x) = instances_in_buffers { + min(x, num_elements) + } else { + num_elements + }); + } + }; + } + + if let Some((first_vertex, vertex_count)) = vertices { + let vertices_needed = first_vertex as u64 + vertex_count as u64; + + if let Some(vertices_in_buffers) = vertices_in_buffers { + // VUID-vkCmdDraw-None-02721 + if vertices_needed > vertices_in_buffers { + return Err(PipelineExecutionError::VertexBufferVertexRangeOutOfBounds { + vertices_needed, + vertices_in_buffers, + }); + } + } + } + + if let Some((first_instance, instance_count)) = instances { + let instances_needed = first_instance as u64 + instance_count as u64; + + if let Some(instances_in_buffers) = instances_in_buffers { + // VUID-vkCmdDraw-None-02721 + if instances_needed > instances_in_buffers { + return Err( + PipelineExecutionError::VertexBufferInstanceRangeOutOfBounds { + instances_needed, + instances_in_buffers, + }, + ); + } + } + + let view_mask = match pipeline.render_pass() { + PipelineRenderPassType::BeginRenderPass(subpass) => { + subpass.render_pass().views_used() + } + PipelineRenderPassType::BeginRendering(rendering_info) => rendering_info.view_mask, + }; + + if view_mask != 0 { + let max = pipeline + .device() + .physical_device() + .properties() + .max_multiview_instance_index + .unwrap_or(0); + + let highest_instance = instances_needed.saturating_sub(1); + + // VUID-vkCmdDraw-maxMultiviewInstanceIndex-02688 + if highest_instance > max as u64 { + return Err(PipelineExecutionError::MaxMultiviewInstanceIndexExceeded { + highest_instance, + max, + }); + } + } + } + + Ok(()) + } +} + +impl SyncCommandBufferBuilder { + /// Calls `vkCmdDispatch` on the builder. + #[inline] + pub unsafe fn dispatch( + &mut self, + group_counts: [u32; 3], + ) -> Result<(), SyncCommandBufferBuilderError> { + struct Cmd { + group_counts: [u32; 3], + } + + impl Command for Cmd { + fn name(&self) -> &'static str { + "dispatch" + } + + unsafe fn send(&self, out: &mut UnsafeCommandBufferBuilder) { + out.dispatch(self.group_counts); + } + } + + let command_index = self.commands.len(); + let command_name = "dispatch"; + let pipeline = self + .current_state + .pipeline_compute + .as_ref() + .unwrap() + .as_ref(); + + let mut resources = Vec::new(); + self.add_descriptor_sets(&mut resources, command_index, command_name, pipeline); + + for resource in &resources { + self.check_resource_conflicts(resource)?; + } + + self.commands.push(Box::new(Cmd { group_counts })); + + for resource in resources { + self.add_resource(resource); + } + + Ok(()) + } + + /// Calls `vkCmdDispatchIndirect` on the builder. + #[inline] + pub unsafe fn dispatch_indirect( + &mut self, + indirect_buffer: Subbuffer<[DispatchIndirectCommand]>, + ) -> Result<(), SyncCommandBufferBuilderError> { + struct Cmd { + indirect_buffer: Subbuffer<[DispatchIndirectCommand]>, + } + + impl Command for Cmd { + fn name(&self) -> &'static str { + "dispatch_indirect" + } + + unsafe fn send(&self, out: &mut UnsafeCommandBufferBuilder) { + out.dispatch_indirect(&self.indirect_buffer); + } + } + + let command_index = self.commands.len(); + let command_name = "dispatch_indirect"; + let pipeline = self + .current_state + .pipeline_compute + .as_ref() + .unwrap() + .as_ref(); + + let mut resources = Vec::new(); + self.add_descriptor_sets(&mut resources, command_index, command_name, pipeline); + self.add_indirect_buffer( + &mut resources, + command_index, + command_name, + indirect_buffer.as_bytes(), + ); + + for resource in &resources { + self.check_resource_conflicts(resource)?; + } + + self.commands.push(Box::new(Cmd { indirect_buffer })); + + for resource in resources { + self.add_resource(resource); + } + + Ok(()) + } + + /// Calls `vkCmdDraw` on the builder. + #[inline] + pub unsafe fn draw( + &mut self, + vertex_count: u32, + instance_count: u32, + first_vertex: u32, + first_instance: u32, + ) -> Result<(), SyncCommandBufferBuilderError> { + struct Cmd { + vertex_count: u32, + instance_count: u32, + first_vertex: u32, + first_instance: u32, + } + + impl Command for Cmd { + fn name(&self) -> &'static str { + "draw" + } + + unsafe fn send(&self, out: &mut UnsafeCommandBufferBuilder) { + out.draw( + self.vertex_count, + self.instance_count, + self.first_vertex, + self.first_instance, + ); + } + } + + let command_index = self.commands.len(); + let command_name = "draw"; + let pipeline = self + .current_state + .pipeline_graphics + .as_ref() + .unwrap() + .as_ref(); + + let mut resources = Vec::new(); + self.add_descriptor_sets(&mut resources, command_index, command_name, pipeline); + self.add_vertex_buffers(&mut resources, command_index, command_name, pipeline); + + for resource in &resources { + self.check_resource_conflicts(resource)?; + } + + self.commands.push(Box::new(Cmd { + vertex_count, + instance_count, + first_vertex, + first_instance, + })); + + for resource in resources { + self.add_resource(resource); + } + + Ok(()) + } + + /// 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, + ) -> Result<(), SyncCommandBufferBuilderError> { + struct Cmd { + index_count: u32, + instance_count: u32, + first_index: u32, + vertex_offset: i32, + first_instance: u32, + } + + impl Command for Cmd { + fn name(&self) -> &'static str { + "draw_indexed" + } + + unsafe fn send(&self, out: &mut UnsafeCommandBufferBuilder) { + out.draw_indexed( + self.index_count, + self.instance_count, + self.first_index, + self.vertex_offset, + self.first_instance, + ); + } + } + + let command_index = self.commands.len(); + let command_name = "draw_indexed"; + let pipeline = self + .current_state + .pipeline_graphics + .as_ref() + .unwrap() + .as_ref(); + + let mut resources = Vec::new(); + self.add_descriptor_sets(&mut resources, command_index, command_name, pipeline); + self.add_vertex_buffers(&mut resources, command_index, command_name, pipeline); + self.add_index_buffer(&mut resources, command_index, command_name); + + for resource in &resources { + self.check_resource_conflicts(resource)?; + } + + self.commands.push(Box::new(Cmd { + index_count, + instance_count, + first_index, + vertex_offset, + first_instance, + })); + + for resource in resources { + self.add_resource(resource); + } + + Ok(()) + } + + /// Calls `vkCmdDrawIndirect` on the builder. + #[inline] + pub unsafe fn draw_indirect( + &mut self, + indirect_buffer: Subbuffer<[DrawIndirectCommand]>, + draw_count: u32, + stride: u32, + ) -> Result<(), SyncCommandBufferBuilderError> { + struct Cmd { + indirect_buffer: Subbuffer<[DrawIndirectCommand]>, + draw_count: u32, + stride: u32, + } + + impl Command for Cmd { + fn name(&self) -> &'static str { + "draw_indirect" + } + + unsafe fn send(&self, out: &mut UnsafeCommandBufferBuilder) { + out.draw_indirect(&self.indirect_buffer, self.draw_count, self.stride); + } + } + + let command_index = self.commands.len(); + let command_name = "draw_indirect"; + let pipeline = self + .current_state + .pipeline_graphics + .as_ref() + .unwrap() + .as_ref(); + + let mut resources = Vec::new(); + self.add_descriptor_sets(&mut resources, command_index, command_name, pipeline); + self.add_vertex_buffers(&mut resources, command_index, command_name, pipeline); + self.add_indirect_buffer( + &mut resources, + command_index, + command_name, + indirect_buffer.as_bytes(), + ); + + for resource in &resources { + self.check_resource_conflicts(resource)?; + } + + self.commands.push(Box::new(Cmd { + indirect_buffer, + draw_count, + stride, + })); + + for resource in resources { + self.add_resource(resource); + } + + Ok(()) + } + + /// Calls `vkCmdDrawIndexedIndirect` on the builder. + #[inline] + pub unsafe fn draw_indexed_indirect( + &mut self, + indirect_buffer: Subbuffer<[DrawIndexedIndirectCommand]>, + draw_count: u32, + stride: u32, + ) -> Result<(), SyncCommandBufferBuilderError> { + struct Cmd { + indirect_buffer: Subbuffer<[DrawIndexedIndirectCommand]>, + draw_count: u32, + stride: u32, + } + + impl Command for Cmd { + fn name(&self) -> &'static str { + "draw_indexed_indirect" + } + + unsafe fn send(&self, out: &mut UnsafeCommandBufferBuilder) { + out.draw_indexed_indirect(&self.indirect_buffer, self.draw_count, self.stride); + } + } + + let command_index = self.commands.len(); + let command_name = "draw_indexed_indirect"; + let pipeline = self + .current_state + .pipeline_graphics + .as_ref() + .unwrap() + .as_ref(); + + let mut resources = Vec::new(); + self.add_descriptor_sets(&mut resources, command_index, command_name, pipeline); + self.add_vertex_buffers(&mut resources, command_index, command_name, pipeline); + self.add_index_buffer(&mut resources, command_index, command_name); + self.add_indirect_buffer( + &mut resources, + command_index, + command_name, + indirect_buffer.as_bytes(), + ); + + for resource in &resources { + self.check_resource_conflicts(resource)?; + } + + self.commands.push(Box::new(Cmd { + indirect_buffer, + draw_count, + stride, + })); + + for resource in resources { + self.add_resource(resource); + } + + Ok(()) + } + + fn add_descriptor_sets<Pl: Pipeline>( + &self, + resources: &mut Vec<(ResourceUseRef, Resource)>, + command_index: usize, + command_name: &'static str, + pipeline: &Pl, + ) { + let descriptor_sets_state = match self + .current_state + .descriptor_sets + .get(&pipeline.bind_point()) + { + Some(x) => x, + None => return, + }; + + for (&(set, binding), binding_reqs) in pipeline.descriptor_binding_requirements() { + // TODO: Can things be refactored so that the pipeline layout isn't needed at all? + let descriptor_type = descriptor_sets_state.pipeline_layout.set_layouts()[set as usize] + .bindings()[&binding] + .descriptor_type; + + let (access_read, access_write) = match descriptor_type { + DescriptorType::Sampler => continue, + DescriptorType::InputAttachment => { + // FIXME: This is tricky. Since we read from the input attachment + // and this input attachment is being written in an earlier pass, + // vulkano will think that it needs to put a pipeline barrier and will + // return a `Conflict` error. For now as a work-around we simply ignore + // input attachments. + continue; + } + DescriptorType::CombinedImageSampler + | DescriptorType::SampledImage + | DescriptorType::UniformTexelBuffer => (Some(AccessFlags::SHADER_READ), None), + DescriptorType::StorageImage + | DescriptorType::StorageTexelBuffer + | DescriptorType::StorageBuffer + | DescriptorType::StorageBufferDynamic => ( + Some(AccessFlags::SHADER_READ), + Some(AccessFlags::SHADER_WRITE), + ), + DescriptorType::UniformBuffer | DescriptorType::UniformBufferDynamic => { + (Some(AccessFlags::UNIFORM_READ), None) + } + }; + + let memory_iter = move |index: u32| { + let mut stages_read = PipelineStages::empty(); + let mut stages_write = PipelineStages::empty(); + + for desc_reqs in (binding_reqs.descriptors.get(&Some(index)).into_iter()) + .chain(binding_reqs.descriptors.get(&None)) + { + stages_read |= desc_reqs.memory_read.into(); + stages_write |= desc_reqs.memory_write.into(); + } + + let memory_read = (!stages_read.is_empty()).then(|| PipelineMemoryAccess { + stages: stages_read, + access: access_read.unwrap(), + exclusive: false, + }); + let memory_write = (!stages_write.is_empty()).then(|| PipelineMemoryAccess { + stages: stages_write, + access: access_write.unwrap(), + exclusive: true, + }); + + [memory_read, memory_write].into_iter().flatten() + }; + let buffer_resource = + |(index, buffer, range): (u32, Subbuffer<[u8]>, Range<DeviceSize>)| { + memory_iter(index).map(move |memory| { + ( + ResourceUseRef { + command_index, + command_name, + resource_in_command: ResourceInCommand::DescriptorSet { + set, + binding, + index, + }, + secondary_use_ref: None, + }, + Resource::Buffer { + buffer: buffer.clone(), + range: range.clone(), + memory, + }, + ) + }) + }; + let image_resource = |(index, image, subresource_range): ( + u32, + Arc<dyn ImageAccess>, + ImageSubresourceRange, + )| { + let layout = image + .descriptor_layouts() + .expect("descriptor_layouts must return Some when used in an image view") + .layout_for(descriptor_type); + + memory_iter(index).map(move |memory| { + ( + ResourceUseRef { + command_index, + command_name, + resource_in_command: ResourceInCommand::DescriptorSet { + set, + binding, + index, + }, + secondary_use_ref: None, + }, + Resource::Image { + image: image.clone(), + subresource_range: subresource_range.clone(), + memory, + start_layout: layout, + end_layout: layout, + }, + ) + }) + }; + + let descriptor_set_state = &descriptor_sets_state.descriptor_sets[&set]; + + match descriptor_set_state.resources().binding(binding).unwrap() { + DescriptorBindingResources::None(_) => continue, + DescriptorBindingResources::Buffer(elements) => { + if matches!( + descriptor_type, + DescriptorType::UniformBufferDynamic | DescriptorType::StorageBufferDynamic + ) { + let dynamic_offsets = descriptor_set_state.dynamic_offsets(); + resources.extend( + (elements.iter().enumerate()) + .filter_map(|(index, element)| { + element.as_ref().map(|(buffer, range)| { + let dynamic_offset = dynamic_offsets[index] as DeviceSize; + + ( + index as u32, + buffer.clone(), + dynamic_offset + range.start + ..dynamic_offset + range.end, + ) + }) + }) + .flat_map(buffer_resource), + ); + } else { + resources.extend( + (elements.iter().enumerate()) + .filter_map(|(index, element)| { + element.as_ref().map(|(buffer, range)| { + (index as u32, buffer.clone(), range.clone()) + }) + }) + .flat_map(buffer_resource), + ); + } + } + DescriptorBindingResources::BufferView(elements) => { + resources.extend( + (elements.iter().enumerate()) + .filter_map(|(index, element)| { + element.as_ref().map(|buffer_view| { + ( + index as u32, + buffer_view.buffer().clone(), + buffer_view.range(), + ) + }) + }) + .flat_map(buffer_resource), + ); + } + DescriptorBindingResources::ImageView(elements) => { + resources.extend( + (elements.iter().enumerate()) + .filter_map(|(index, element)| { + element.as_ref().map(|image_view| { + ( + index as u32, + image_view.image(), + image_view.subresource_range().clone(), + ) + }) + }) + .flat_map(image_resource), + ); + } + DescriptorBindingResources::ImageViewSampler(elements) => { + resources.extend( + (elements.iter().enumerate()) + .filter_map(|(index, element)| { + element.as_ref().map(|(image_view, _)| { + ( + index as u32, + image_view.image(), + image_view.subresource_range().clone(), + ) + }) + }) + .flat_map(image_resource), + ); + } + DescriptorBindingResources::Sampler(_) => (), + } + } + } + + fn add_vertex_buffers( + &self, + resources: &mut Vec<(ResourceUseRef, Resource)>, + command_index: usize, + command_name: &'static str, + pipeline: &GraphicsPipeline, + ) { + resources.extend( + pipeline + .vertex_input_state() + .bindings + .iter() + .map(|(&binding, _)| { + let vertex_buffer = &self.current_state.vertex_buffers[&binding]; + ( + ResourceUseRef { + command_index, + command_name, + resource_in_command: ResourceInCommand::VertexBuffer { binding }, + secondary_use_ref: None, + }, + Resource::Buffer { + buffer: vertex_buffer.clone(), + range: 0..vertex_buffer.size(), // TODO: + memory: PipelineMemoryAccess { + stages: PipelineStages::VERTEX_INPUT, + access: AccessFlags::VERTEX_ATTRIBUTE_READ, + exclusive: false, + }, + }, + ) + }), + ); + } + + fn add_index_buffer( + &self, + resources: &mut Vec<(ResourceUseRef, Resource)>, + command_index: usize, + command_name: &'static str, + ) { + let index_buffer = &self.current_state.index_buffer.as_ref().unwrap().0; + resources.push(( + ResourceUseRef { + command_index, + command_name, + resource_in_command: ResourceInCommand::IndexBuffer, + secondary_use_ref: None, + }, + Resource::Buffer { + buffer: index_buffer.clone(), + range: 0..index_buffer.size(), // TODO: + memory: PipelineMemoryAccess { + stages: PipelineStages::VERTEX_INPUT, + access: AccessFlags::INDEX_READ, + exclusive: false, + }, + }, + )); + } + + fn add_indirect_buffer( + &self, + resources: &mut Vec<(ResourceUseRef, Resource)>, + command_index: usize, + command_name: &'static str, + indirect_buffer: &Subbuffer<[u8]>, + ) { + resources.push(( + ResourceUseRef { + command_index, + command_name, + resource_in_command: ResourceInCommand::IndirectBuffer, + secondary_use_ref: None, + }, + Resource::Buffer { + buffer: indirect_buffer.clone(), + range: 0..indirect_buffer.size(), // TODO: + memory: PipelineMemoryAccess { + stages: PipelineStages::DRAW_INDIRECT, + access: AccessFlags::INDIRECT_COMMAND_READ, + exclusive: false, + }, + }, + )); + } +} + +impl UnsafeCommandBufferBuilder { + /// 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(); + (fns.v1_0.cmd_dispatch)( + self.handle, + group_counts[0], + group_counts[1], + group_counts[2], + ); + } + + /// Calls `vkCmdDispatchIndirect` on the builder. + #[inline] + pub unsafe fn dispatch_indirect(&mut self, buffer: &Subbuffer<[DispatchIndirectCommand]>) { + let fns = self.device.fns(); + + debug_assert!(buffer.offset() < buffer.buffer().size()); + debug_assert!(buffer + .buffer() + .usage() + .intersects(BufferUsage::INDIRECT_BUFFER)); + debug_assert_eq!(buffer.offset() % 4, 0); + + (fns.v1_0.cmd_dispatch_indirect)(self.handle, buffer.buffer().handle(), buffer.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(); + (fns.v1_0.cmd_draw)( + self.handle, + 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(); + (fns.v1_0.cmd_draw_indexed)( + self.handle, + index_count, + instance_count, + first_index, + vertex_offset, + first_instance, + ); + } + + /// Calls `vkCmdDrawIndirect` on the builder. + #[inline] + pub unsafe fn draw_indirect( + &mut self, + buffer: &Subbuffer<[DrawIndirectCommand]>, + draw_count: u32, + stride: u32, + ) { + let fns = self.device.fns(); + + debug_assert!( + draw_count == 0 + || ((stride % 4) == 0) + && stride as usize >= size_of::<ash::vk::DrawIndirectCommand>() + ); + + debug_assert!(buffer.offset() < buffer.buffer().size()); + debug_assert!(buffer + .buffer() + .usage() + .intersects(BufferUsage::INDIRECT_BUFFER)); + + (fns.v1_0.cmd_draw_indirect)( + self.handle, + buffer.buffer().handle(), + buffer.offset(), + draw_count, + stride, + ); + } + + /// Calls `vkCmdDrawIndexedIndirect` on the builder. + #[inline] + pub unsafe fn draw_indexed_indirect( + &mut self, + buffer: &Subbuffer<[DrawIndexedIndirectCommand]>, + draw_count: u32, + stride: u32, + ) { + let fns = self.device.fns(); + + debug_assert!(buffer.offset() < buffer.buffer().size()); + debug_assert!(buffer + .buffer() + .usage() + .intersects(BufferUsage::INDIRECT_BUFFER)); + + (fns.v1_0.cmd_draw_indexed_indirect)( + self.handle, + buffer.buffer().handle(), + buffer.offset(), + draw_count, + stride, + ); + } +} + +/// Error that can happen when recording a bound pipeline execution command. +#[derive(Debug, Clone)] +pub enum PipelineExecutionError { + SyncCommandBufferBuilderError(SyncCommandBufferBuilderError), + + RequirementNotMet { + required_for: &'static str, + requires_one_of: RequiresOneOf, + }, + + /// The resource bound to a descriptor set binding at a particular index is not compatible + /// with the requirements of the pipeline and shaders. + DescriptorResourceInvalid { + set_num: u32, + binding_num: u32, + index: u32, + error: DescriptorResourceInvalidError, + }, + + /// The pipeline layout requires a descriptor set bound to a set number, but none was bound. + DescriptorSetNotBound { + set_num: u32, + }, + + /// The bound pipeline uses a dynamic color write enable setting, but the number of provided + /// enable values is less than the number of attachments in the current render subpass. + DynamicColorWriteEnableNotEnoughValues { + color_write_enable_count: u32, + attachment_count: u32, + }, + + /// The bound pipeline uses a dynamic primitive topology, but the provided topology is of a + /// different topology class than what the pipeline requires. + DynamicPrimitiveTopologyClassMismatch { + provided_class: PrimitiveTopologyClass, + required_class: PrimitiveTopologyClass, + }, + + /// The bound pipeline uses a dynamic primitive topology, but the provided topology is not + /// compatible with the shader stages in the pipeline. + DynamicPrimitiveTopologyInvalid { + topology: PrimitiveTopology, + }, + + /// The pipeline requires a particular dynamic state, but this state was not set. + DynamicStateNotSet { + dynamic_state: DynamicState, + }, + + /// The bound pipeline uses a dynamic scissor and/or viewport count, but the scissor count + /// does not match the viewport count. + DynamicViewportScissorCountMismatch { + viewport_count: u32, + scissor_count: u32, + }, + + /// Operation forbidden inside a render pass. + ForbiddenInsideRenderPass, + + /// Operation forbidden outside a render pass. + ForbiddenOutsideRenderPass, + + /// Operation forbidden inside a render subpass with the specified contents. + ForbiddenWithSubpassContents { + subpass_contents: SubpassContents, + }, + + /// An indexed draw command was recorded, but no index buffer was bound. + IndexBufferNotBound, + + /// The highest index to be drawn exceeds the available number of indices in the bound index + /// buffer. + IndexBufferRangeOutOfBounds { + highest_index: u32, + max_index_count: u32, + }, + + /// The `indirect_buffer` usage was not enabled on the indirect buffer. + IndirectBufferMissingUsage, + + /// The `max_compute_work_group_count` limit has been exceeded. + MaxComputeWorkGroupCountExceeded { + requested: [u32; 3], + max: [u32; 3], + }, + + /// The `max_draw_indirect_count` limit has been exceeded. + MaxDrawIndirectCountExceeded { + provided: u32, + max: u32, + }, + + /// The `max_multiview_instance_index` limit has been exceeded. + MaxMultiviewInstanceIndexExceeded { + highest_instance: u64, + max: u32, + }, + + /// The queue family doesn't allow this operation. + NotSupportedByQueueFamily, + + /// The color attachment count in the bound pipeline does not match the count of the current + /// render pass. + PipelineColorAttachmentCountMismatch { + pipeline_count: u32, + required_count: u32, + }, + + /// The format of a color attachment in the bound pipeline does not match the format of the + /// corresponding color attachment in the current render pass. + PipelineColorAttachmentFormatMismatch { + color_attachment_index: u32, + pipeline_format: Option<Format>, + required_format: Format, + }, + + /// The format of the depth attachment in the bound pipeline does not match the format of the + /// depth attachment in the current render pass. + PipelineDepthAttachmentFormatMismatch { + pipeline_format: Option<Format>, + required_format: Format, + }, + + /// The bound pipeline is not compatible with the layout used to bind the descriptor sets. + PipelineLayoutNotCompatible, + + /// No pipeline was bound to the bind point used by the operation. + PipelineNotBound, + + /// The bound graphics pipeline uses a render pass that is not compatible with the currently + /// active render pass. + PipelineRenderPassNotCompatible, + + /// The bound graphics pipeline uses a render pass of a different type than the currently + /// active render pass. + PipelineRenderPassTypeMismatch, + + /// The bound graphics pipeline uses a render subpass index that doesn't match the currently + /// active subpass index. + PipelineSubpassMismatch { + pipeline: u32, + current: u32, + }, + + /// The format of the stencil attachment in the bound pipeline does not match the format of the + /// stencil attachment in the current render pass. + PipelineStencilAttachmentFormatMismatch { + pipeline_format: Option<Format>, + required_format: Format, + }, + + /// The view mask of the bound pipeline does not match the view mask of the current render pass. + PipelineViewMaskMismatch { + pipeline_view_mask: u32, + required_view_mask: u32, + }, + + /// The push constants are not compatible with the pipeline layout. + PushConstantsNotCompatible, + + /// Not all push constants used by the pipeline have been set. + PushConstantsMissing, + + /// The bound graphics pipeline requires a vertex buffer bound to a binding number, but none + /// was bound. + VertexBufferNotBound { + binding_num: u32, + }, + + /// The number of instances to be drawn exceeds the available number of indices in the + /// bound vertex buffers used by the pipeline. + VertexBufferInstanceRangeOutOfBounds { + instances_needed: u64, + instances_in_buffers: u64, + }, + + /// The number of vertices to be drawn exceeds the lowest available number of vertices in the + /// bound vertex buffers used by the pipeline. + VertexBufferVertexRangeOutOfBounds { + vertices_needed: u64, + vertices_in_buffers: u64, + }, +} + +impl Error for PipelineExecutionError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + match self { + Self::SyncCommandBufferBuilderError(err) => Some(err), + Self::DescriptorResourceInvalid { error, .. } => Some(error), + _ => None, + } + } +} + +impl Display for PipelineExecutionError { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> { + match self { + Self::SyncCommandBufferBuilderError(_) => write!(f, "a SyncCommandBufferBuilderError"), + Self::RequirementNotMet { + required_for, + requires_one_of, + } => write!( + f, + "a requirement was not met for: {}; requires one of: {}", + required_for, requires_one_of, + ), + Self::DescriptorResourceInvalid { + set_num, + binding_num, + index, + .. + } => write!( + f, + "the resource bound to descriptor set {} binding {} at index {} is not compatible \ + with the requirements of the pipeline and shaders", + set_num, binding_num, index, + ), + Self::DescriptorSetNotBound { set_num } => write!( + f, + "the pipeline layout requires a descriptor set bound to set number {}, but none \ + was bound", + set_num, + ), + Self::DynamicColorWriteEnableNotEnoughValues { + color_write_enable_count, + attachment_count, + } => write!( + f, + "the bound pipeline uses a dynamic color write enable setting, but the number of \ + provided enable values ({}) is less than the number of attachments in the current \ + render subpass ({})", + color_write_enable_count, attachment_count, + ), + Self::DynamicPrimitiveTopologyClassMismatch { + provided_class, + required_class, + } => write!( + f, + "The bound pipeline uses a dynamic primitive topology, but the provided topology \ + is of a different topology class ({:?}) than what the pipeline requires ({:?})", + provided_class, required_class, + ), + Self::DynamicPrimitiveTopologyInvalid { topology } => write!( + f, + "the bound pipeline uses a dynamic primitive topology, but the provided topology \ + ({:?}) is not compatible with the shader stages in the pipeline", + topology, + ), + Self::DynamicStateNotSet { dynamic_state } => write!( + f, + "the pipeline requires the dynamic state {:?}, but this state was not set", + dynamic_state, + ), + Self::DynamicViewportScissorCountMismatch { + viewport_count, + scissor_count, + } => write!( + f, + "the bound pipeline uses a dynamic scissor and/or viewport count, but the scissor \ + count ({}) does not match the viewport count ({})", + scissor_count, viewport_count, + ), + Self::ForbiddenInsideRenderPass => { + write!(f, "operation forbidden inside a render pass") + } + Self::ForbiddenOutsideRenderPass => { + write!(f, "operation forbidden outside a render pass") + } + Self::ForbiddenWithSubpassContents { subpass_contents } => write!( + f, + "operation forbidden inside a render subpass with contents {:?}", + subpass_contents, + ), + Self::IndexBufferNotBound => write!( + f, + "an indexed draw command was recorded, but no index buffer was bound", + ), + Self::IndexBufferRangeOutOfBounds { + highest_index, + max_index_count, + } => write!( + f, + "the highest index to be drawn ({}) exceeds the available number of indices in the \ + bound index buffer ({})", + highest_index, max_index_count, + ), + Self::IndirectBufferMissingUsage => write!( + f, + "the `indirect_buffer` usage was not enabled on the indirect buffer", + ), + Self::MaxComputeWorkGroupCountExceeded { .. } => write!( + f, + "the `max_compute_work_group_count` limit has been exceeded", + ), + Self::MaxDrawIndirectCountExceeded { .. } => { + write!(f, "the `max_draw_indirect_count` limit has been exceeded") + } + Self::MaxMultiviewInstanceIndexExceeded { .. } => write!( + f, + "the `max_multiview_instance_index` limit has been exceeded", + ), + Self::NotSupportedByQueueFamily => { + write!(f, "the queue family doesn't allow this operation") + } + Self::PipelineColorAttachmentCountMismatch { + pipeline_count, + required_count, + } => write!( + f, + "the color attachment count in the bound pipeline ({}) does not match the count of \ + the current render pass ({})", + pipeline_count, required_count, + ), + Self::PipelineColorAttachmentFormatMismatch { + color_attachment_index, + pipeline_format, + required_format, + } => write!( + f, + "the format of color attachment {} in the bound pipeline ({:?}) does not match the \ + format of the corresponding color attachment in the current render pass ({:?})", + color_attachment_index, pipeline_format, required_format, + ), + Self::PipelineDepthAttachmentFormatMismatch { + pipeline_format, + required_format, + } => write!( + f, + "the format of the depth attachment in the bound pipeline ({:?}) does not match \ + the format of the depth attachment in the current render pass ({:?})", + pipeline_format, required_format, + ), + Self::PipelineLayoutNotCompatible => write!( + f, + "the bound pipeline is not compatible with the layout used to bind the descriptor \ + sets", + ), + Self::PipelineNotBound => write!( + f, + "no pipeline was bound to the bind point used by the operation", + ), + Self::PipelineRenderPassNotCompatible => write!( + f, + "the bound graphics pipeline uses a render pass that is not compatible with the \ + currently active render pass", + ), + Self::PipelineRenderPassTypeMismatch => write!( + f, + "the bound graphics pipeline uses a render pass of a different type than the \ + currently active render pass", + ), + Self::PipelineSubpassMismatch { pipeline, current } => write!( + f, + "the bound graphics pipeline uses a render subpass index ({}) that doesn't match \ + the currently active subpass index ({})", + pipeline, current, + ), + Self::PipelineStencilAttachmentFormatMismatch { + pipeline_format, + required_format, + } => write!( + f, + "the format of the stencil attachment in the bound pipeline ({:?}) does not match \ + the format of the stencil attachment in the current render pass ({:?})", + pipeline_format, required_format, + ), + Self::PipelineViewMaskMismatch { + pipeline_view_mask, + required_view_mask, + } => write!( + f, + "the view mask of the bound pipeline ({}) does not match the view mask of the \ + current render pass ({})", + pipeline_view_mask, required_view_mask, + ), + Self::PushConstantsNotCompatible => write!( + f, + "the push constants are not compatible with the pipeline layout", + ), + Self::PushConstantsMissing => write!( + f, + "not all push constants used by the pipeline have been set", + ), + Self::VertexBufferNotBound { binding_num } => write!( + f, + "the bound graphics pipeline requires a vertex buffer bound to binding number {}, \ + but none was bound", + binding_num, + ), + Self::VertexBufferInstanceRangeOutOfBounds { + instances_needed, + instances_in_buffers, + } => write!( + f, + "the number of instances to be drawn ({}) exceeds the available number of \ + instances in the bound vertex buffers ({}) used by the pipeline", + instances_needed, instances_in_buffers, + ), + Self::VertexBufferVertexRangeOutOfBounds { + vertices_needed, + vertices_in_buffers, + } => write!( + f, + "the number of vertices to be drawn ({}) exceeds the available number of vertices \ + in the bound vertex buffers ({}) used by the pipeline", + vertices_needed, vertices_in_buffers, + ), + } + } +} + +impl From<SyncCommandBufferBuilderError> for PipelineExecutionError { + fn from(err: SyncCommandBufferBuilderError) -> Self { + Self::SyncCommandBufferBuilderError(err) + } +} + +#[derive(Clone, Copy, Debug)] +pub enum DescriptorResourceInvalidError { + ImageViewFormatMismatch { + required: Format, + provided: Option<Format>, + }, + ImageViewMultisampledMismatch { + required: bool, + provided: bool, + }, + ImageViewScalarTypeMismatch { + required: ShaderScalarType, + provided: ShaderScalarType, + }, + ImageViewTypeMismatch { + required: ImageViewType, + provided: ImageViewType, + }, + Missing, + SamplerCompareMismatch { + required: bool, + provided: bool, + }, + SamplerImageViewIncompatible { + image_view_set_num: u32, + image_view_binding_num: u32, + image_view_index: u32, + error: SamplerImageViewIncompatibleError, + }, + SamplerUnnormalizedCoordinatesNotAllowed, + SamplerYcbcrConversionNotAllowed, + StorageImageAtomicNotSupported, + StorageReadWithoutFormatNotSupported, + StorageWriteWithoutFormatNotSupported, +} + +impl Error for DescriptorResourceInvalidError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + match self { + Self::SamplerImageViewIncompatible { error, .. } => Some(error), + _ => None, + } + } +} + +impl Display for DescriptorResourceInvalidError { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> { + match self { + Self::ImageViewFormatMismatch { provided, required } => write!( + f, + "the format of the bound image view ({:?}) does not match what the pipeline \ + requires ({:?})", + provided, required, + ), + Self::ImageViewMultisampledMismatch { provided, required } => write!( + f, + "the multisampling of the bound image ({}) does not match what the pipeline \ + requires ({})", + provided, required, + ), + Self::ImageViewScalarTypeMismatch { provided, required } => write!( + f, + "the scalar type of the format and aspect of the bound image view ({:?}) does not \ + match what the pipeline requires ({:?})", + provided, required, + ), + Self::ImageViewTypeMismatch { provided, required } => write!( + f, + "the image view type of the bound image view ({:?}) does not match what the \ + pipeline requires ({:?})", + provided, required, + ), + Self::Missing => write!(f, "no resource was bound"), + Self::SamplerImageViewIncompatible { .. } => write!( + f, + "the bound sampler samples an image view that is not compatible with that sampler", + ), + Self::SamplerCompareMismatch { provided, required } => write!( + f, + "the depth comparison state of the bound sampler ({}) does not match what the \ + pipeline requires ({})", + provided, required, + ), + Self::SamplerUnnormalizedCoordinatesNotAllowed => write!( + f, + "the bound sampler is required to have unnormalized coordinates disabled", + ), + Self::SamplerYcbcrConversionNotAllowed => write!( + f, + "the bound sampler is required to have no attached sampler YCbCr conversion", + ), + Self::StorageImageAtomicNotSupported => write!( + f, + "the bound image view does not support the `storage_image_atomic` format feature", + ), + Self::StorageReadWithoutFormatNotSupported => write!( + f, + "the bound image view or buffer view does not support the \ + `storage_read_without_format` format feature", + ), + Self::StorageWriteWithoutFormatNotSupported => write!( + f, + "the bound image view or buffer view does not support the \ + `storage_write_without_format` format feature", + ), + } + } +} |