// 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::{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 AutoCommandBufferBuilder 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::() 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::() 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( &self, pipeline: &Pl, ) -> Result<(), PipelineExecutionError> { fn validate_resources( set_num: u32, binding_num: u32, binding_reqs: &DescriptorBindingRequirements, elements: &[Option], 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)| Ok(()); let check_buffer_view = |index: u32, buffer_view: &Arc| { 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| { 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| { 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| { 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, Arc)| { check_image_view_common(index, image_view)?; check_sampler_common(index, sampler)?; Ok(()) }; let check_sampler = |index: u32, sampler: &Arc| { 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 = None; let mut instances_in_buffers: Option = 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( &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)| { 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, 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::() ); 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, 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, 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, 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 for PipelineExecutionError { fn from(err: SyncCommandBufferBuilderError) -> Self { Self::SyncCommandBufferBuilderError(err) } } #[derive(Clone, Copy, Debug)] pub enum DescriptorResourceInvalidError { ImageViewFormatMismatch { required: Format, provided: Option, }, 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", ), } } }