From 655ca5e6c9a8b3b5abc1c93478c5719bce293632 Mon Sep 17 00:00:00 2001 From: Rua Date: Thu, 28 Dec 2023 19:44:22 +0100 Subject: [PATCH] Add support for the `ext_mesh_shader` extension (#2433) * Add support for the `ext_mesh_shader` extension * Helper function for queries * Documentation update * Add mesh primitives generated query * Derp * Support in vulkano-shaders too * More doc fixes * Better docs, explicitly saying when the values must be `Some` or `None`. * Update vulkano/src/pipeline/graphics/mod.rs Co-authored-by: marc0246 <40955683+marc0246@users.noreply.github.com> --------- Co-authored-by: marc0246 <40955683+marc0246@users.noreply.github.com> --- vulkano-shaders/src/lib.rs | 18 +- .../src/command_buffer/commands/pipeline.rs | 1099 +++++++++++++++-- vulkano/src/command_buffer/commands/query.rs | 16 + vulkano/src/command_buffer/mod.rs | 25 + vulkano/src/pipeline/graphics/mod.rs | 706 ++++++----- vulkano/src/pipeline/shader.rs | 28 +- vulkano/src/query.rs | 114 +- 7 files changed, 1538 insertions(+), 468 deletions(-) diff --git a/vulkano-shaders/src/lib.rs b/vulkano-shaders/src/lib.rs index 752407b6..6aed8bd5 100644 --- a/vulkano-shaders/src/lib.rs +++ b/vulkano-shaders/src/lib.rs @@ -92,10 +92,12 @@ //! of the following: //! //! - `vertex` -//! - `fragment` -//! - `geometry` //! - `tess_ctrl` //! - `tess_eval` +//! - `geometry` +//! - `task` +//! - `mesh` +//! - `fragment` //! - `compute` //! - `raygen` //! - `anyhit` @@ -421,10 +423,12 @@ impl Parse for MacroInput { output.0 = Some(match lit.value().as_str() { "vertex" => ShaderKind::Vertex, - "fragment" => ShaderKind::Fragment, - "geometry" => ShaderKind::Geometry, "tess_ctrl" => ShaderKind::TessControl, "tess_eval" => ShaderKind::TessEvaluation, + "geometry" => ShaderKind::Geometry, + "task" => ShaderKind::Task, + "mesh" => ShaderKind::Mesh, + "fragment" => ShaderKind::Fragment, "compute" => ShaderKind::Compute, "raygen" => ShaderKind::RayGeneration, "anyhit" => ShaderKind::AnyHit, @@ -434,9 +438,9 @@ impl Parse for MacroInput { "callable" => ShaderKind::Callable, ty => bail!( lit, - "expected `vertex`, `fragment`, `geometry`, `tess_ctrl`, `tess_eval`, \ - `compute`, `raygen`, `anyhit`, `closesthit`, `miss`, `intersection` or \ - `callable`, found `{ty}`", + "expected `vertex`, `tess_ctrl`, `tess_eval`, `geometry`, `task`, \ + `mesh`, `fragment` `compute`, `raygen`, `anyhit`, `closesthit`, \ + `miss`, `intersection` or `callable`, found `{ty}`", ), }); } diff --git a/vulkano/src/command_buffer/commands/pipeline.rs b/vulkano/src/command_buffer/commands/pipeline.rs index b105172f..1dc6945d 100644 --- a/vulkano/src/command_buffer/commands/pipeline.rs +++ b/vulkano/src/command_buffer/commands/pipeline.rs @@ -7,7 +7,7 @@ use crate::{ auto::{RenderPassState, RenderPassStateType, Resource, ResourceUseRef2}, sys::RawRecordingCommandBuffer, DispatchIndirectCommand, DrawIndexedIndirectCommand, DrawIndirectCommand, - RecordingCommandBuffer, ResourceInCommand, SubpassContents, + DrawMeshTasksIndirectCommand, RecordingCommandBuffer, ResourceInCommand, SubpassContents, }, descriptor_set::{ layout::DescriptorType, DescriptorBindingResources, DescriptorBufferInfo, @@ -24,7 +24,8 @@ use crate::{ }, DynamicState, GraphicsPipeline, Pipeline, PipelineLayout, }, - shader::{DescriptorBindingRequirements, DescriptorIdentifier, ShaderStage, ShaderStages}, + query::QueryType, + shader::{DescriptorBindingRequirements, DescriptorIdentifier, ShaderStages}, sync::{PipelineStageAccess, PipelineStageAccessFlags}, DeviceSize, Requires, RequiresAllOf, RequiresOneOf, ValidationError, Version, VulkanObject, }; @@ -41,6 +42,9 @@ macro_rules! vuids { VUIDType::DrawIndexed => &[$(concat!("VUID-vkCmdDrawIndexed-", $id)),+], VUIDType::DrawIndexedIndirect => &[$(concat!("VUID-vkCmdDrawIndexedIndirect-", $id)),+], VUIDType::DrawIndexedIndirectCount => &[$(concat!("VUID-vkCmdDrawIndexedIndirectCount-", $id)),+], + VUIDType::DrawMeshTasks => &[$(concat!("VUID-vkCmdDrawMeshTasksEXT-", $id)),+], + VUIDType::DrawMeshTasksIndirect => &[$(concat!("VUID-vkCmdDrawMeshTasksIndirectEXT-", $id)),+], + VUIDType::DrawMeshTasksIndirectCount => &[$(concat!("VUID-vkCmdDrawMeshTasksIndirectCountEXT-", $id)),+], } }; } @@ -203,13 +207,13 @@ impl RecordingCommandBuffer { self } - /// Perform a single draw operation using a graphics pipeline. + /// Perform a single draw operation using a primitive shading 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 + /// A primitive shading 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 @@ -266,9 +270,9 @@ impl RecordingCommandBuffer { const VUID_TYPE: VUIDType = VUIDType::Draw; self.validate_pipeline_descriptor_sets(VUID_TYPE, pipeline)?; self.validate_pipeline_push_constants(VUID_TYPE, pipeline.layout())?; + self.validate_pipeline_graphics_primitive_shading(VUID_TYPE, pipeline)?; self.validate_pipeline_graphics_dynamic_state(VUID_TYPE, pipeline)?; self.validate_pipeline_graphics_render_pass(VUID_TYPE, pipeline, render_pass_state)?; - self.validate_pipeline_graphics_vertex_buffers(VUID_TYPE, pipeline)?; let view_mask = match pipeline.subpass() { PipelineSubpassType::BeginRenderPass(subpass) => subpass.render_pass().views_used(), @@ -390,7 +394,7 @@ impl RecordingCommandBuffer { self } - /// Perform multiple draw operations using a graphics pipeline. + /// Perform multiple draw operations using a primitive shading 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 @@ -399,7 +403,7 @@ impl RecordingCommandBuffer { /// [`multi_draw_indirect`](Features::multi_draw_indirect) feature has been /// enabled. /// - /// A graphics pipeline must have been bound using + /// A primitive shading 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 @@ -455,9 +459,9 @@ impl RecordingCommandBuffer { const VUID_TYPE: VUIDType = VUIDType::DrawIndirect; self.validate_pipeline_descriptor_sets(VUID_TYPE, pipeline)?; self.validate_pipeline_push_constants(VUID_TYPE, pipeline.layout())?; + self.validate_pipeline_graphics_primitive_shading(VUID_TYPE, pipeline)?; self.validate_pipeline_graphics_dynamic_state(VUID_TYPE, pipeline)?; self.validate_pipeline_graphics_render_pass(VUID_TYPE, pipeline, render_pass_state)?; - self.validate_pipeline_graphics_vertex_buffers(VUID_TYPE, pipeline)?; Ok(()) } @@ -498,7 +502,7 @@ impl RecordingCommandBuffer { self } - /// Perform multiple draw operations using a graphics pipeline, + /// Perform multiple draw operations using a primitive shading graphics pipeline, /// reading the number of draw operations from a separate buffer. /// /// One draw is performed for each [`DrawIndirectCommand`] struct that is read from @@ -507,7 +511,7 @@ impl RecordingCommandBuffer { /// This number is limited by the /// [`max_draw_indirect_count`](Properties::max_draw_indirect_count) limit. /// - /// A graphics pipeline must have been bound using + /// A primitive shading 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 @@ -584,9 +588,9 @@ impl RecordingCommandBuffer { const VUID_TYPE: VUIDType = VUIDType::DrawIndirectCount; self.validate_pipeline_descriptor_sets(VUID_TYPE, pipeline)?; self.validate_pipeline_push_constants(VUID_TYPE, pipeline.layout())?; + self.validate_pipeline_graphics_primitive_shading(VUID_TYPE, pipeline)?; self.validate_pipeline_graphics_dynamic_state(VUID_TYPE, pipeline)?; self.validate_pipeline_graphics_render_pass(VUID_TYPE, pipeline, render_pass_state)?; - self.validate_pipeline_graphics_vertex_buffers(VUID_TYPE, pipeline)?; Ok(()) } @@ -634,7 +638,8 @@ impl RecordingCommandBuffer { self } - /// Perform a single draw operation using a graphics pipeline, using an index buffer. + /// Perform a single draw operation using a primitive shading 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, @@ -646,7 +651,7 @@ impl RecordingCommandBuffer { /// [`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 + /// A primitive shading 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 @@ -728,9 +733,9 @@ impl RecordingCommandBuffer { const VUID_TYPE: VUIDType = VUIDType::DrawIndexed; self.validate_pipeline_descriptor_sets(VUID_TYPE, pipeline)?; self.validate_pipeline_push_constants(VUID_TYPE, pipeline.layout())?; + self.validate_pipeline_graphics_primitive_shading(VUID_TYPE, pipeline)?; self.validate_pipeline_graphics_dynamic_state(VUID_TYPE, pipeline)?; self.validate_pipeline_graphics_render_pass(VUID_TYPE, pipeline, render_pass_state)?; - self.validate_pipeline_graphics_vertex_buffers(VUID_TYPE, pipeline)?; let index_buffer = self.builder_state.index_buffer.as_ref().ok_or_else(|| { Box::new(ValidationError { @@ -870,7 +875,8 @@ impl RecordingCommandBuffer { self } - /// Perform multiple draw operations using a graphics pipeline, using an index buffer. + /// Perform multiple draw operations using a primitive shading 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 @@ -884,7 +890,7 @@ impl RecordingCommandBuffer { /// `DrawIndexedIndirectCommand` in the indirect buffer must be in range of the bound index /// buffer. /// - /// A graphics pipeline must have been bound using + /// A primitive shading 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 @@ -940,9 +946,9 @@ impl RecordingCommandBuffer { const VUID_TYPE: VUIDType = VUIDType::DrawIndexedIndirect; self.validate_pipeline_descriptor_sets(VUID_TYPE, pipeline)?; self.validate_pipeline_push_constants(VUID_TYPE, pipeline.layout())?; + self.validate_pipeline_graphics_primitive_shading(VUID_TYPE, pipeline)?; self.validate_pipeline_graphics_dynamic_state(VUID_TYPE, pipeline)?; self.validate_pipeline_graphics_render_pass(VUID_TYPE, pipeline, render_pass_state)?; - self.validate_pipeline_graphics_vertex_buffers(VUID_TYPE, pipeline)?; let _index_buffer = self.builder_state.index_buffer.as_ref().ok_or_else(|| { Box::new(ValidationError { @@ -992,8 +998,8 @@ impl RecordingCommandBuffer { self } - /// Perform multiple draw operations using a graphics pipeline, using an index buffer, - /// and reading the number of draw operations from a separate buffer. + /// Perform multiple draw operations using a primitive shading graphics pipeline, + /// using an index buffer, and reading the number of draw operations from a separate buffer. /// /// One draw is performed for each [`DrawIndexedIndirectCommand`] struct that is read from /// `indirect_buffer`. The number of draws to perform is read from `count_buffer`, or @@ -1006,7 +1012,7 @@ impl RecordingCommandBuffer { /// `DrawIndexedIndirectCommand` in the indirect buffer must be in range of the bound index /// buffer. /// - /// A graphics pipeline must have been bound using + /// A primitive shading 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 @@ -1083,9 +1089,9 @@ impl RecordingCommandBuffer { const VUID_TYPE: VUIDType = VUIDType::DrawIndexedIndirectCount; self.validate_pipeline_descriptor_sets(VUID_TYPE, pipeline)?; self.validate_pipeline_push_constants(VUID_TYPE, pipeline.layout())?; + self.validate_pipeline_graphics_primitive_shading(VUID_TYPE, pipeline)?; self.validate_pipeline_graphics_dynamic_state(VUID_TYPE, pipeline)?; self.validate_pipeline_graphics_render_pass(VUID_TYPE, pipeline, render_pass_state)?; - self.validate_pipeline_graphics_vertex_buffers(VUID_TYPE, pipeline)?; let _index_buffer = self.builder_state.index_buffer.as_ref().ok_or_else(|| { Box::new(ValidationError { @@ -1142,6 +1148,448 @@ impl RecordingCommandBuffer { self } + /// Perform a single draw operation using a mesh shading graphics pipeline. + /// + /// A mesh shading 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 and dynamic state, must have been set beforehand. + /// + /// # Safety + /// + /// - The general [shader safety requirements](crate::shader#safety) apply. + pub unsafe fn draw_mesh_tasks( + &mut self, + group_counts: [u32; 3], + ) -> Result<&mut Self, Box> { + self.validate_draw_mesh_tasks(group_counts)?; + + unsafe { Ok(self.draw_mesh_tasks_unchecked(group_counts)) } + } + + fn validate_draw_mesh_tasks(&self, group_counts: [u32; 3]) -> Result<(), Box> { + self.inner.validate_draw_mesh_tasks(group_counts)?; + + let render_pass_state = self.builder_state.render_pass.as_ref().ok_or_else(|| { + Box::new(ValidationError { + problem: "a render pass instance is not active".into(), + vuids: &["VUID-vkCmdDrawMeshTasksEXT-renderpass"], + ..Default::default() + }) + })?; + + let pipeline = self + .builder_state + .pipeline_graphics + .as_ref() + .ok_or_else(|| { + Box::new(ValidationError { + problem: "no graphics pipeline is currently bound".into(), + vuids: &["VUID-vkCmdDrawMeshTasksEXT-None-08606"], + ..Default::default() + }) + })? + .as_ref(); + + const VUID_TYPE: VUIDType = VUIDType::DrawMeshTasks; + self.validate_pipeline_descriptor_sets(VUID_TYPE, pipeline)?; + self.validate_pipeline_push_constants(VUID_TYPE, pipeline.layout())?; + self.validate_pipeline_graphics_mesh_shading(VUID_TYPE, pipeline)?; + self.validate_pipeline_graphics_dynamic_state(VUID_TYPE, pipeline)?; + self.validate_pipeline_graphics_render_pass(VUID_TYPE, pipeline, render_pass_state)?; + + if pipeline.mesh_is_nv() { + return Err(Box::new(ValidationError { + problem: "the currently bound graphics pipeline uses NV mesh shaders instead of \ + EXT mesh shaders" + .into(), + vuids: &["VUID-vkCmdDrawMeshTasksEXT-MeshEXT-07087"], + ..Default::default() + })); + } + + let properties = self.device().physical_device().properties(); + let group_counts_product = group_counts.into_iter().try_fold(1, u32::checked_mul); + + if pipeline.shader_stages().intersects(ShaderStages::TASK) { + if group_counts[0] > properties.max_task_work_group_count.unwrap_or_default()[0] { + return Err(Box::new(ValidationError { + context: "group_counts[0]".into(), + problem: "is greater than the `max_task_work_group_count[0]` device limit" + .into(), + vuids: &["VUID-vkCmdDrawMeshTasksEXT-TaskEXT-07322"], + ..Default::default() + })); + } + + if group_counts[1] > properties.max_task_work_group_count.unwrap_or_default()[1] { + return Err(Box::new(ValidationError { + context: "group_counts[1]".into(), + problem: "is greater than the `max_task_work_group_count[1]` device limit" + .into(), + vuids: &["VUID-vkCmdDrawMeshTasksEXT-TaskEXT-07323"], + ..Default::default() + })); + } + + if group_counts[2] > properties.max_task_work_group_count.unwrap_or_default()[2] { + return Err(Box::new(ValidationError { + context: "group_counts[2]".into(), + problem: "is greater than the `max_task_work_group_count[2]` device limit" + .into(), + vuids: &["VUID-vkCmdDrawMeshTasksEXT-TaskEXT-07324"], + ..Default::default() + })); + } + + if group_counts_product.map_or(true, |size| { + size > properties + .max_task_work_group_total_count + .unwrap_or_default() + }) { + return Err(Box::new(ValidationError { + context: "group_counts".into(), + problem: "the product is greater than the `max_task_work_group_total_count` \ + device limit" + .into(), + vuids: &["VUID-vkCmdDrawMeshTasksEXT-TaskEXT-07325"], + ..Default::default() + })); + } + } else { + if group_counts[0] > properties.max_mesh_work_group_count.unwrap_or_default()[0] { + return Err(Box::new(ValidationError { + context: "group_counts[0]".into(), + problem: "is greater than the `max_mesh_work_group_count[0]` device limit" + .into(), + vuids: &["VUID-vkCmdDrawMeshTasksEXT-TaskEXT-07326"], + ..Default::default() + })); + } + + if group_counts[1] > properties.max_mesh_work_group_count.unwrap_or_default()[1] { + return Err(Box::new(ValidationError { + context: "group_counts[1]".into(), + problem: "is greater than the `max_mesh_work_group_count[1]` device limit" + .into(), + vuids: &["VUID-vkCmdDrawMeshTasksEXT-TaskEXT-07327"], + ..Default::default() + })); + } + + if group_counts[2] > properties.max_mesh_work_group_count.unwrap_or_default()[2] { + return Err(Box::new(ValidationError { + context: "group_counts[2]".into(), + problem: "is greater than the `max_mesh_work_group_count[2]` device limit" + .into(), + vuids: &["VUID-vkCmdDrawMeshTasksEXT-TaskEXT-07328"], + ..Default::default() + })); + } + + if group_counts_product.map_or(true, |size| { + size > properties + .max_mesh_work_group_total_count + .unwrap_or_default() + }) { + return Err(Box::new(ValidationError { + context: "group_counts".into(), + problem: "the product is greater than the `max_mesh_work_group_total_count` \ + device limit" + .into(), + vuids: &["VUID-vkCmdDrawMeshTasksEXT-TaskEXT-07329"], + ..Default::default() + })); + } + } + + Ok(()) + } + + #[cfg_attr(not(feature = "document_unchecked"), doc(hidden))] + pub unsafe fn draw_mesh_tasks_unchecked(&mut self, group_counts: [u32; 3]) -> &mut Self { + if let RenderPassStateType::BeginRendering(state) = + &mut self.builder_state.render_pass.as_mut().unwrap().render_pass + { + state.pipeline_used = true; + } + + let pipeline = self + .builder_state + .pipeline_graphics + .as_ref() + .unwrap() + .as_ref(); + + let mut used_resources = Vec::new(); + self.add_descriptor_sets_resources(&mut used_resources, pipeline); + + self.add_command( + "draw_mesh_tasks", + used_resources, + move |out: &mut RawRecordingCommandBuffer| { + out.draw_mesh_tasks_unchecked(group_counts); + }, + ); + + self + } + + /// Perform multiple draw operations using a mesh shading graphics pipeline. + /// + /// One draw is performed for each [`DrawMeshTasksIndirectCommand`] struct in `indirect_buffer`. + /// The maximum number of draw commands in the buffer is limited by the + /// [`max_draw_indirect_count`](Properties::max_draw_indirect_count) limit. + /// This limit is 1 unless the + /// [`multi_draw_indirect`](Features::multi_draw_indirect) feature has been + /// enabled. + /// + /// A mesh shading 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 and dynamic state, must have been set beforehand. + /// + /// # Safety + /// + /// - The general [shader safety requirements](crate::shader#safety) apply. + /// - The [safety requirements for + /// `DrawMeshTasksIndirectCommand`](DrawMeshTasksIndirectCommand#safety) apply. + pub unsafe fn draw_mesh_tasks_indirect( + &mut self, + indirect_buffer: Subbuffer<[DrawMeshTasksIndirectCommand]>, + ) -> Result<&mut Self, Box> { + let draw_count = indirect_buffer.len() as u32; + let stride = size_of::() as u32; + self.validate_draw_mesh_tasks_indirect(indirect_buffer.as_bytes(), draw_count, stride)?; + + unsafe { Ok(self.draw_mesh_tasks_indirect_unchecked(indirect_buffer, draw_count, stride)) } + } + + fn validate_draw_mesh_tasks_indirect( + &self, + indirect_buffer: &Subbuffer<[u8]>, + draw_count: u32, + stride: u32, + ) -> Result<(), Box> { + self.inner + .validate_draw_mesh_tasks_indirect(indirect_buffer, draw_count, stride)?; + + let render_pass_state = self.builder_state.render_pass.as_ref().ok_or_else(|| { + Box::new(ValidationError { + problem: "a render pass instance is not active".into(), + vuids: &["VUID-vkCmdDrawMeshTasksIndirectEXT-renderpass"], + ..Default::default() + }) + })?; + + let pipeline = self + .builder_state + .pipeline_graphics + .as_ref() + .ok_or_else(|| { + Box::new(ValidationError { + problem: "no graphics pipeline is currently bound".into(), + vuids: &["VUID-vkCmdDrawMeshTasksIndirectEXT-None-08606"], + ..Default::default() + }) + })? + .as_ref(); + + const VUID_TYPE: VUIDType = VUIDType::DrawMeshTasksIndirect; + self.validate_pipeline_descriptor_sets(VUID_TYPE, pipeline)?; + self.validate_pipeline_push_constants(VUID_TYPE, pipeline.layout())?; + self.validate_pipeline_graphics_mesh_shading(VUID_TYPE, pipeline)?; + self.validate_pipeline_graphics_dynamic_state(VUID_TYPE, pipeline)?; + self.validate_pipeline_graphics_render_pass(VUID_TYPE, pipeline, render_pass_state)?; + + if pipeline.mesh_is_nv() { + return Err(Box::new(ValidationError { + problem: "the currently bound graphics pipeline uses NV mesh shaders instead of \ + EXT mesh shaders" + .into(), + vuids: &["VUID-vkCmdDrawMeshTasksIndirectEXT-MeshEXT-07091"], + ..Default::default() + })); + } + + Ok(()) + } + + #[cfg_attr(not(feature = "document_unchecked"), doc(hidden))] + pub unsafe fn draw_mesh_tasks_indirect_unchecked( + &mut self, + indirect_buffer: Subbuffer<[DrawMeshTasksIndirectCommand]>, + draw_count: u32, + stride: u32, + ) -> &mut Self { + if let RenderPassStateType::BeginRendering(state) = + &mut self.builder_state.render_pass.as_mut().unwrap().render_pass + { + state.pipeline_used = true; + } + + let pipeline = self + .builder_state + .pipeline_graphics + .as_ref() + .unwrap() + .as_ref(); + + let mut used_resources = Vec::new(); + self.add_descriptor_sets_resources(&mut used_resources, pipeline); + self.add_indirect_buffer_resources(&mut used_resources, indirect_buffer.as_bytes()); + + self.add_command( + "draw_mesh_tasks_indirect", + used_resources, + move |out: &mut RawRecordingCommandBuffer| { + out.draw_mesh_tasks_indirect_unchecked(&indirect_buffer, draw_count, stride); + }, + ); + + self + } + + /// Perform multiple draw operations using a mesh shading graphics pipeline, + /// reading the number of draw operations from a separate buffer. + /// + /// One draw is performed for each [`DrawMeshTasksIndirectCommand`] struct that is read from + /// `indirect_buffer`. The number of draws to perform is read from `count_buffer`, or + /// specified by `max_draw_count`, whichever is lower. + /// This number is limited by the + /// [`max_draw_indirect_count`](Properties::max_draw_indirect_count) limit. + /// + /// A mesh shading 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 and dynamic state, must have been set beforehand. + /// + /// # Safety + /// + /// - The general [shader safety requirements](crate::shader#safety) apply. + /// - The [safety requirements for + /// `DrawMeshTasksIndirectCommand`](DrawMeshTasksIndirectCommand#safety) apply. + /// - The count stored in `count_buffer` must not be greater than the + /// [`max_draw_indirect_count`](Properties::max_draw_indirect_count) device limit. + /// - The count stored in `count_buffer` must fall within the range of `indirect_buffer`. + pub unsafe fn draw_mesh_tasks_indirect_count( + &mut self, + indirect_buffer: Subbuffer<[DrawMeshTasksIndirectCommand]>, + count_buffer: Subbuffer, + max_draw_count: u32, + ) -> Result<&mut Self, Box> { + let stride = size_of::() as u32; + self.validate_draw_mesh_tasks_indirect_count( + indirect_buffer.as_bytes(), + count_buffer.as_bytes(), + max_draw_count, + stride, + )?; + + unsafe { + Ok(self.draw_mesh_tasks_indirect_count_unchecked( + indirect_buffer, + count_buffer, + max_draw_count, + stride, + )) + } + } + + fn validate_draw_mesh_tasks_indirect_count( + &self, + indirect_buffer: &Subbuffer<[u8]>, + count_buffer: &Subbuffer<[u8]>, + max_draw_count: u32, + stride: u32, + ) -> Result<(), Box> { + self.inner.validate_draw_mesh_tasks_indirect_count( + indirect_buffer, + count_buffer, + max_draw_count, + stride, + )?; + + let render_pass_state = self.builder_state.render_pass.as_ref().ok_or_else(|| { + Box::new(ValidationError { + problem: "a render pass instance is not active".into(), + vuids: &["VUID-vkCmdDrawMeshTasksIndirectCountEXT-renderpass"], + ..Default::default() + }) + })?; + + let pipeline = self + .builder_state + .pipeline_graphics + .as_ref() + .ok_or_else(|| { + Box::new(ValidationError { + problem: "no graphics pipeline is currently bound".into(), + vuids: &["VUID-vkCmdDrawMeshTasksIndirectCountEXT-None-08606"], + ..Default::default() + }) + })? + .as_ref(); + + const VUID_TYPE: VUIDType = VUIDType::DrawMeshTasksIndirectCount; + self.validate_pipeline_descriptor_sets(VUID_TYPE, pipeline)?; + self.validate_pipeline_push_constants(VUID_TYPE, pipeline.layout())?; + self.validate_pipeline_graphics_mesh_shading(VUID_TYPE, pipeline)?; + self.validate_pipeline_graphics_dynamic_state(VUID_TYPE, pipeline)?; + self.validate_pipeline_graphics_render_pass(VUID_TYPE, pipeline, render_pass_state)?; + + if pipeline.mesh_is_nv() { + return Err(Box::new(ValidationError { + problem: "the currently bound graphics pipeline uses NV mesh shaders instead of \ + EXT mesh shaders" + .into(), + vuids: &["VUID-vkCmdDrawMeshTasksIndirectCountEXT-MeshEXT-07100"], + ..Default::default() + })); + } + + Ok(()) + } + + #[cfg_attr(not(feature = "document_unchecked"), doc(hidden))] + pub unsafe fn draw_mesh_tasks_indirect_count_unchecked( + &mut self, + indirect_buffer: Subbuffer<[DrawMeshTasksIndirectCommand]>, + count_buffer: Subbuffer, + max_draw_count: u32, + stride: u32, + ) -> &mut Self { + if let RenderPassStateType::BeginRendering(state) = + &mut self.builder_state.render_pass.as_mut().unwrap().render_pass + { + state.pipeline_used = true; + } + + let pipeline = self + .builder_state + .pipeline_graphics + .as_ref() + .unwrap() + .as_ref(); + + let mut used_resources = Vec::new(); + self.add_descriptor_sets_resources(&mut used_resources, pipeline); + self.add_indirect_buffer_resources(&mut used_resources, indirect_buffer.as_bytes()); + self.add_indirect_buffer_resources(&mut used_resources, count_buffer.as_bytes()); + + self.add_command( + "draw_mesh_tasks_indirect_count", + used_resources, + move |out: &mut RawRecordingCommandBuffer| { + out.draw_mesh_tasks_indirect_count_unchecked( + &indirect_buffer, + &count_buffer, + max_draw_count, + stride, + ); + }, + ); + + self + } + fn validate_pipeline_descriptor_sets( &self, vuid_type: VUIDType, @@ -1841,6 +2289,124 @@ impl RecordingCommandBuffer { Ok(()) } + fn validate_pipeline_graphics_primitive_shading( + &self, + vuid_type: VUIDType, + pipeline: &GraphicsPipeline, + ) -> Result<(), Box> { + if pipeline + .shader_stages() + .intersects(ShaderStages::MESH | ShaderStages::TASK) + { + return Err(Box::new(ValidationError { + problem: "the currently bound graphics pipeline uses mesh shading".into(), + vuids: vuids!(vuid_type, "stage-06481"), + ..Default::default() + })); + } + + if self + .builder_state + .queries + .contains_key(&ash::vk::QueryType::MESH_PRIMITIVES_GENERATED_EXT) + { + return Err(Box::new(ValidationError { + problem: "a `MeshPrimitivesGenerated` query is currently active".into(), + vuids: vuids!(vuid_type, "stage-07073"), + ..Default::default() + })); + } + + if let Some(query_state) = self + .builder_state + .queries + .get(&ash::vk::QueryType::PIPELINE_STATISTICS) + { + let &QueryType::PipelineStatistics(pipeline_statistics_flags) = + query_state.query_pool.query_type() + else { + unreachable!() + }; + + if pipeline_statistics_flags.is_mesh_shading_graphics() { + return Err(Box::new(ValidationError { + problem: "a pipeline statistics query is currently active, and its \ + pipeline statistics flags include statistics for mesh shading" + .into(), + vuids: vuids!(vuid_type, "stage-07073"), + ..Default::default() + })); + } + } + + let vertex_input_state = pipeline + .dynamic_state() + .contains(&DynamicState::VertexInput) + .then(|| self.builder_state.vertex_input.as_ref().unwrap()) + .unwrap_or_else(|| pipeline.vertex_input_state().unwrap()); + + for &binding_num in vertex_input_state.bindings.keys() { + if !self.builder_state.vertex_buffers.contains_key(&binding_num) { + return Err(Box::new(ValidationError { + problem: format!( + "the currently bound graphics pipeline uses \ + vertex buffer binding {0}, but \ + no vertex buffer is currently bound to binding {0}", + binding_num + ) + .into(), + vuids: vuids!(vuid_type, "None-04007"), + ..Default::default() + })); + } + } + + Ok(()) + } + + fn validate_pipeline_graphics_mesh_shading( + &self, + vuid_type: VUIDType, + pipeline: &GraphicsPipeline, + ) -> Result<(), Box> { + if pipeline.shader_stages().intersects( + ShaderStages::VERTEX + | ShaderStages::TESSELLATION_CONTROL + | ShaderStages::TESSELLATION_EVALUATION + | ShaderStages::GEOMETRY, + ) { + return Err(Box::new(ValidationError { + problem: "the currently bound graphics pipeline uses primitive shading".into(), + vuids: vuids!(vuid_type, "stage-06480"), + ..Default::default() + })); + } + + if let Some(query_state) = self + .builder_state + .queries + .get(&ash::vk::QueryType::PIPELINE_STATISTICS) + { + let &QueryType::PipelineStatistics(pipeline_statistics_flags) = + query_state.query_pool.query_type() + else { + unreachable!() + }; + + if pipeline_statistics_flags.is_primitive_shading_graphics() { + return Err(Box::new(ValidationError { + problem: "a pipeline statistics query is currently active, and its \ + pipeline statistics flags include statistics for primitive shading" + .into(), + vuids: vuids!(vuid_type, "pipelineStatistics-07076"), + ..Default::default() + })); + } + } + + Ok(()) + } + fn validate_pipeline_graphics_dynamic_state( &self, vuid_type: VUIDType, @@ -2166,8 +2732,10 @@ impl RecordingCommandBuffer { ..Default::default() })); } + } else if let Some(input_assembly_state) = pipeline.input_assembly_state() { + input_assembly_state.topology } else { - pipeline.input_assembly_state().topology + unreachable!("PrimitiveRestartEnable can only occur on primitive shading pipelines") }; match topology { @@ -2234,7 +2802,9 @@ impl RecordingCommandBuffer { })); }; - if pipeline.shader(ShaderStage::TessellationControl).is_some() { + if pipeline.shader_stages().intersects( + ShaderStages::TESSELLATION_CONTROL | ShaderStages::TESSELLATION_EVALUATION, + ) { if !matches!(topology, PrimitiveTopology::PatchList) { return Err(Box::new(ValidationError { problem: format!( @@ -2272,52 +2842,54 @@ impl RecordingCommandBuffer { .dynamic_primitive_topology_unrestricted .unwrap_or(false) { - let is_same_topology_class = matches!( - (topology, pipeline.input_assembly_state().topology), - (PrimitiveTopology::PointList, PrimitiveTopology::PointList) - | ( - PrimitiveTopology::LineList - | PrimitiveTopology::LineStrip - | PrimitiveTopology::LineListWithAdjacency - | PrimitiveTopology::LineStripWithAdjacency, - PrimitiveTopology::LineList - | PrimitiveTopology::LineStrip - | PrimitiveTopology::LineListWithAdjacency - | PrimitiveTopology::LineStripWithAdjacency, - ) - | ( - PrimitiveTopology::TriangleList - | PrimitiveTopology::TriangleStrip - | PrimitiveTopology::TriangleFan - | PrimitiveTopology::TriangleListWithAdjacency - | PrimitiveTopology::TriangleStripWithAdjacency, - PrimitiveTopology::TriangleList - | PrimitiveTopology::TriangleStrip - | PrimitiveTopology::TriangleFan - | PrimitiveTopology::TriangleListWithAdjacency - | PrimitiveTopology::TriangleStripWithAdjacency, - ) - | (PrimitiveTopology::PatchList, PrimitiveTopology::PatchList) - ); + if let Some(input_assembly_state) = pipeline.input_assembly_state() { + let is_same_topology_class = matches!( + (topology, input_assembly_state.topology), + (PrimitiveTopology::PointList, PrimitiveTopology::PointList) + | ( + PrimitiveTopology::LineList + | PrimitiveTopology::LineStrip + | PrimitiveTopology::LineListWithAdjacency + | PrimitiveTopology::LineStripWithAdjacency, + PrimitiveTopology::LineList + | PrimitiveTopology::LineStrip + | PrimitiveTopology::LineListWithAdjacency + | PrimitiveTopology::LineStripWithAdjacency, + ) + | ( + PrimitiveTopology::TriangleList + | PrimitiveTopology::TriangleStrip + | PrimitiveTopology::TriangleFan + | PrimitiveTopology::TriangleListWithAdjacency + | PrimitiveTopology::TriangleStripWithAdjacency, + PrimitiveTopology::TriangleList + | PrimitiveTopology::TriangleStrip + | PrimitiveTopology::TriangleFan + | PrimitiveTopology::TriangleListWithAdjacency + | PrimitiveTopology::TriangleStripWithAdjacency, + ) + | (PrimitiveTopology::PatchList, PrimitiveTopology::PatchList) + ); - if !is_same_topology_class { - return Err(Box::new(ValidationError { - problem: format!( - "the currently bound graphics pipeline requires the \ - `DynamicState::{:?}` dynamic state, and the \ - `dynamic_primitive_topology_unrestricted` device property is \ - `false`, but the dynamic primitive topology does not belong \ - to the same topology class as the topology that the \ - graphics pipeline was created with", - dynamic_state - ) - .into(), - vuids: vuids!( - vuid_type, - "dynamicPrimitiveTopologyUnrestricted-07500" - ), - ..Default::default() - })); + if !is_same_topology_class { + return Err(Box::new(ValidationError { + problem: format!( + "the currently bound graphics pipeline requires the \ + `DynamicState::{:?}` dynamic state, and the \ + `dynamic_primitive_topology_unrestricted` device property is \ + `false`, but the dynamic primitive topology does not belong \ + to the same topology class as the topology that the \ + graphics pipeline was created with", + dynamic_state + ) + .into(), + vuids: vuids!( + vuid_type, + "dynamicPrimitiveTopologyUnrestricted-07500" + ), + ..Default::default() + })); + } } } @@ -2811,36 +3383,6 @@ impl RecordingCommandBuffer { Ok(()) } - fn validate_pipeline_graphics_vertex_buffers( - &self, - vuid_type: VUIDType, - pipeline: &GraphicsPipeline, - ) -> Result<(), Box> { - let vertex_input_state = pipeline - .dynamic_state() - .contains(&DynamicState::VertexInput) - .then(|| self.builder_state.vertex_input.as_ref().unwrap()) - .unwrap_or_else(|| pipeline.vertex_input_state().unwrap()); - - for &binding_num in vertex_input_state.bindings.keys() { - if !self.builder_state.vertex_buffers.contains_key(&binding_num) { - return Err(Box::new(ValidationError { - problem: format!( - "the currently bound graphics pipeline uses \ - vertex buffer binding {0}, but \ - no vertex buffer is currently bound to binding {0}", - binding_num - ) - .into(), - vuids: vuids!(vuid_type, "None-04007"), - ..Default::default() - })); - } - } - - Ok(()) - } - fn add_descriptor_sets_resources( &self, used_resources: &mut Vec<(ResourceUseRef2, Resource)>, @@ -3967,6 +4509,364 @@ impl RawRecordingCommandBuffer { self } + + #[inline] + pub unsafe fn draw_mesh_tasks( + &mut self, + group_counts: [u32; 3], + ) -> Result<&mut Self, Box> { + self.validate_draw_mesh_tasks(group_counts)?; + + Ok(self.draw_mesh_tasks_unchecked(group_counts)) + } + + fn validate_draw_mesh_tasks( + &self, + _group_counts: [u32; 3], + ) -> Result<(), Box> { + if !self.device().enabled_extensions().ext_mesh_shader { + return Err(Box::new(ValidationError { + requires_one_of: RequiresOneOf(&[RequiresAllOf(&[Requires::DeviceExtension( + "ext_mesh_shader", + )])]), + ..Default::default() + })); + } + + if !self + .queue_family_properties() + .queue_flags + .intersects(QueueFlags::GRAPHICS) + { + return Err(Box::new(ValidationError { + problem: "the queue family of the command buffer does not support \ + graphics operations" + .into(), + vuids: &["VUID-vkCmdDrawMeshTasksEXT-commandBuffer-cmdpool"], + ..Default::default() + })); + } + + Ok(()) + } + + #[cfg_attr(not(feature = "document_unchecked"), doc(hidden))] + pub unsafe fn draw_mesh_tasks_unchecked(&mut self, group_counts: [u32; 3]) -> &mut Self { + let fns = self.device().fns(); + (fns.ext_mesh_shader.cmd_draw_mesh_tasks_ext)( + self.handle(), + group_counts[0], + group_counts[1], + group_counts[2], + ); + + self + } + + #[inline] + pub unsafe fn draw_mesh_tasks_indirect( + &mut self, + indirect_buffer: &Subbuffer<[DrawMeshTasksIndirectCommand]>, + draw_count: u32, + stride: u32, + ) -> Result<&mut Self, Box> { + self.validate_draw_mesh_tasks_indirect(indirect_buffer.as_bytes(), draw_count, stride)?; + + Ok(self.draw_mesh_tasks_indirect_unchecked(indirect_buffer, draw_count, stride)) + } + + fn validate_draw_mesh_tasks_indirect( + &self, + indirect_buffer: &Subbuffer<[u8]>, + draw_count: u32, + stride: u32, + ) -> Result<(), Box> { + if !self.device().enabled_extensions().ext_mesh_shader { + return Err(Box::new(ValidationError { + requires_one_of: RequiresOneOf(&[RequiresAllOf(&[Requires::DeviceExtension( + "ext_mesh_shader", + )])]), + ..Default::default() + })); + } + + if !self + .queue_family_properties() + .queue_flags + .intersects(QueueFlags::GRAPHICS) + { + return Err(Box::new(ValidationError { + problem: "the queue family of the command buffer does not support \ + graphics operations" + .into(), + vuids: &["VUID-vkCmdDrawMeshTasksIndirectEXT-commandBuffer-cmdpool"], + ..Default::default() + })); + } + + // VUID-vkCmdDrawMeshTasksIndirectEXT-commonparent + assert_eq!(self.device(), indirect_buffer.device()); + + if !indirect_buffer + .buffer() + .usage() + .intersects(BufferUsage::INDIRECT_BUFFER) + { + return Err(Box::new(ValidationError { + context: "indirect_buffer.usage()".into(), + problem: "does not contain `BufferUsage::INDIRECT_BUFFER`".into(), + vuids: &["VUID-vkCmdDrawMeshTasksIndirectEXT-buffer-02709"], + ..Default::default() + })); + } + + if size_of::() as DeviceSize > indirect_buffer.size() { + return Err(Box::new(ValidationError { + problem: "`size_of::()` is greater than \ + `indirect_buffer.size()`" + .into(), + vuids: &["VUID-vkCmdDispatchIndirect-offset-00407"], + ..Default::default() + })); + } + + if draw_count > 1 { + if !self.device().enabled_features().multi_draw_indirect { + return Err(Box::new(ValidationError { + context: "draw_count".into(), + problem: "is greater than 1".into(), + requires_one_of: RequiresOneOf(&[RequiresAllOf(&[Requires::Feature( + "multi_draw_indirect", + )])]), + vuids: &["VUID-vkCmdDrawMeshTasksIndirectEXT-drawCount-02718"], + })); + } + + if stride % 4 != 0 { + return Err(Box::new(ValidationError { + problem: "`draw_count` is greater than 1, but \ + `stride` is not a multiple of 4" + .into(), + vuids: &["VUID-vkCmdDrawMeshTasksIndirectEXT-drawCount-07088"], + ..Default::default() + })); + } + + if (stride as DeviceSize) < size_of::() as DeviceSize { + return Err(Box::new(ValidationError { + problem: "`draw_count` is greater than 1, but \ + `stride` is not greater than `size_of::()`" + .into(), + vuids: &["VUID-vkCmdDrawMeshTasksIndirectEXT-drawCount-07088"], + ..Default::default() + })); + } + + if stride as DeviceSize * (draw_count as DeviceSize - 1) + + size_of::() as DeviceSize + > indirect_buffer.size() + { + return Err(Box::new(ValidationError { + problem: "`draw_count` is greater than 1, but \ + `stride * (draw_count - 1) + size_of::()` \ + is greater than `indirect_buffer.size()`" + .into(), + vuids: &["VUID-vkCmdDrawMeshTasksIndirectEXT-drawCount-07090"], + ..Default::default() + })); + } + } else { + if size_of::() as DeviceSize > indirect_buffer.size() { + return Err(Box::new(ValidationError { + problem: "`draw_count` is 1, but `size_of::()` \ + is greater than `indirect_buffer.size()`" + .into(), + vuids: &["VUID-vkCmdDrawMeshTasksIndirectEXT-drawCount-07089"], + ..Default::default() + })); + } + } + + let properties = self.device().physical_device().properties(); + + if draw_count > properties.max_draw_indirect_count { + return Err(Box::new(ValidationError { + context: "draw_count".into(), + problem: "is greater than the `max_draw_indirect_count` limit".into(), + vuids: &["VUID-vkCmdDrawMeshTasksIndirectEXT-drawCount-02719"], + ..Default::default() + })); + } + + Ok(()) + } + + #[cfg_attr(not(feature = "document_unchecked"), doc(hidden))] + pub unsafe fn draw_mesh_tasks_indirect_unchecked( + &mut self, + indirect_buffer: &Subbuffer<[DrawMeshTasksIndirectCommand]>, + draw_count: u32, + stride: u32, + ) -> &mut Self { + let fns = self.device().fns(); + (fns.ext_mesh_shader.cmd_draw_mesh_tasks_indirect_ext)( + self.handle(), + indirect_buffer.buffer().handle(), + indirect_buffer.offset(), + draw_count, + stride, + ); + + self + } + + #[inline] + pub unsafe fn draw_mesh_tasks_indirect_count( + &mut self, + indirect_buffer: &Subbuffer<[DrawMeshTasksIndirectCommand]>, + count_buffer: &Subbuffer, + max_draw_count: u32, + stride: u32, + ) -> Result<&mut Self, Box> { + self.validate_draw_mesh_tasks_indirect_count( + indirect_buffer.as_bytes(), + count_buffer.as_bytes(), + max_draw_count, + stride, + )?; + + Ok(self.draw_mesh_tasks_indirect_count_unchecked( + indirect_buffer, + count_buffer, + max_draw_count, + stride, + )) + } + + fn validate_draw_mesh_tasks_indirect_count( + &self, + indirect_buffer: &Subbuffer<[u8]>, + count_buffer: &Subbuffer<[u8]>, + max_draw_count: u32, + stride: u32, + ) -> Result<(), Box> { + if !self.device().enabled_extensions().ext_mesh_shader { + return Err(Box::new(ValidationError { + requires_one_of: RequiresOneOf(&[RequiresAllOf(&[Requires::DeviceExtension( + "ext_mesh_shader", + )])]), + ..Default::default() + })); + } + + if !self.device().enabled_features().draw_indirect_count { + return Err(Box::new(ValidationError { + requires_one_of: RequiresOneOf(&[RequiresAllOf(&[Requires::Feature( + "draw_indirect_count", + )])]), + vuids: &["VUID-vkCmdDrawMeshTasksIndirectCountEXT-None-04445"], + ..Default::default() + })); + } + + if !self + .queue_family_properties() + .queue_flags + .intersects(QueueFlags::GRAPHICS) + { + return Err(Box::new(ValidationError { + problem: "the queue family of the command buffer does not support \ + graphics operations" + .into(), + vuids: &["VUID-vkCmdDrawMeshTasksIndirectCountEXT-commandBuffer-cmdpool"], + ..Default::default() + })); + } + + if !indirect_buffer + .buffer() + .usage() + .intersects(BufferUsage::INDIRECT_BUFFER) + { + return Err(Box::new(ValidationError { + context: "indirect_buffer.usage()".into(), + problem: "does not contain `BufferUsage::INDIRECT_BUFFER`".into(), + vuids: &["VUID-vkCmdDrawMeshTasksIndirectCountEXT-buffer-02709"], + ..Default::default() + })); + } + + if !count_buffer + .buffer() + .usage() + .intersects(BufferUsage::INDIRECT_BUFFER) + { + return Err(Box::new(ValidationError { + context: "count_buffer.usage()".into(), + problem: "does not contain `BufferUsage::INDIRECT_BUFFER`".into(), + vuids: &["VUID-vkCmdDrawMeshTasksIndirectCountEXT-countBuffer-02715"], + ..Default::default() + })); + } + + if stride % 4 != 0 { + return Err(Box::new(ValidationError { + context: "stride".into(), + problem: "is not a multiple of 4".into(), + vuids: &["VUID-vkCmdDrawMeshTasksIndirectCountEXT-stride-07096"], + ..Default::default() + })); + } + + if (stride as DeviceSize) < size_of::() as DeviceSize { + return Err(Box::new(ValidationError { + context: "stride".into(), + problem: "is not greater than `size_of::()`".into(), + vuids: &["VUID-vkCmdDrawMeshTasksIndirectCountEXT-stride-07096"], + ..Default::default() + })); + } + + if max_draw_count >= 1 + && stride as DeviceSize * (max_draw_count as DeviceSize - 1) + + size_of::() as DeviceSize + > indirect_buffer.size() + { + return Err(Box::new(ValidationError { + problem: "`max_draw_count` is equal to or greater than 1, but \ + `stride * (max_draw_count - 1) + size_of::()` \ + is greater than `indirect_buffer.size()`" + .into(), + vuids: &["VUID-vkCmdDrawMeshTasksIndirectCountEXT-maxDrawCount-07097"], + ..Default::default() + })); + } + + Ok(()) + } + + #[cfg_attr(not(feature = "document_unchecked"), doc(hidden))] + pub unsafe fn draw_mesh_tasks_indirect_count_unchecked( + &mut self, + indirect_buffer: &Subbuffer<[DrawMeshTasksIndirectCommand]>, + count_buffer: &Subbuffer, + max_draw_count: u32, + stride: u32, + ) -> &mut Self { + let fns = self.device().fns(); + + (fns.ext_mesh_shader.cmd_draw_mesh_tasks_indirect_count_ext)( + self.handle(), + indirect_buffer.buffer().handle(), + indirect_buffer.offset(), + count_buffer.buffer().handle(), + count_buffer.offset(), + max_draw_count, + stride, + ); + + self + } } #[derive(Clone, Copy)] @@ -3979,4 +4879,7 @@ enum VUIDType { DrawIndexed, DrawIndexedIndirect, DrawIndexedIndirectCount, + DrawMeshTasks, + DrawMeshTasksIndirect, + DrawMeshTasksIndirectCount, } diff --git a/vulkano/src/command_buffer/commands/query.rs b/vulkano/src/command_buffer/commands/query.rs index 44884d82..488fc06f 100644 --- a/vulkano/src/command_buffer/commands/query.rs +++ b/vulkano/src/command_buffer/commands/query.rs @@ -505,6 +505,22 @@ impl RawRecordingCommandBuffer { })); } } + QueryType::MeshPrimitivesGenerated => { + if !queue_family_properties + .queue_flags + .intersects(QueueFlags::GRAPHICS) + { + return Err(Box::new(ValidationError { + problem: "`query_pool.query_type()` is \ + `QueryType::MeshPrimitivesGenerated`, but \ + the queue family of the command buffer does not support \ + graphics operations" + .into(), + vuids: &["VUID-vkCmdBeginQuery-queryType-07070"], + ..Default::default() + })); + } + } QueryType::Timestamp | QueryType::AccelerationStructureCompactedSize | QueryType::AccelerationStructureSerializationSize diff --git a/vulkano/src/command_buffer/mod.rs b/vulkano/src/command_buffer/mod.rs index c10ed327..66c3bb53 100644 --- a/vulkano/src/command_buffer/mod.rs +++ b/vulkano/src/command_buffer/mod.rs @@ -172,6 +172,31 @@ pub struct DrawIndirectCommand { pub first_instance: u32, } +/// Used as buffer contents to provide input for the +/// [`RecordingCommandBuffer::draw_mesh_tasks_indirect`] command. +/// +/// # Safety +/// +/// - If the graphics pipeline **does not** include a task shader, then the +/// `group_count_x`, `group_count_y` and `group_count_z` values must not be greater than the +/// respective elements of the +/// [`max_mesh_work_group_count`](Properties::max_mesh_work_group_count) device limit, +/// and the product of these three values must not be greater than the +/// [`max_mesh_work_group_total_count`](Properties::max_mesh_work_group_total_count) device limit. +/// - If the graphics pipeline **does** include a task shader, then the +/// `group_count_x`, `group_count_y` and `group_count_z` values must not be greater than the +/// respective elements of the +/// [`max_task_work_group_count`](Properties::max_task_work_group_count) device limit, +/// and the product of these three values must not be greater than the +/// [`max_task_work_group_total_count`](Properties::max_task_work_group_total_count) device limit. +#[repr(C)] +#[derive(Clone, Copy, Debug, Default, Zeroable, Pod, PartialEq, Eq)] +pub struct DrawMeshTasksIndirectCommand { + pub group_count_x: u32, + pub group_count_y: u32, + pub group_count_z: u32, +} + /// Used as buffer contents to provide input for the /// [`RecordingCommandBuffer::draw_indexed_indirect`] command. /// diff --git a/vulkano/src/pipeline/graphics/mod.rs b/vulkano/src/pipeline/graphics/mod.rs index 83f7d6e2..b218aea2 100644 --- a/vulkano/src/pipeline/graphics/mod.rs +++ b/vulkano/src/pipeline/graphics/mod.rs @@ -2,11 +2,27 @@ //! //! Unlike a compute pipeline, which performs general-purpose work, a graphics pipeline is geared //! specifically towards doing graphical processing. To that end, it consists of several shaders, -//! with additional state and glue logic in between. +//! with additional state and glue logic in between, known as the pipeline's *state*. +//! The state often directly corresponds to one or more steps in the graphics pipeline. Each +//! state collection has a dedicated submodule. +//! +//! # Processing steps //! //! A graphics pipeline performs many separate steps, that execute more or less in sequence. //! Due to the parallel nature of a GPU, no strict ordering guarantees may exist. //! +//! Graphics pipelines come in two different forms: +//! - *Primitive shading* graphics pipelines, which contain a vertex shader, vertex input and +//! input assembly state, and optionally tessellation shaders and/or a geometry shader. +//! - *Mesh shading* graphics pipelines, which contain a mesh shader, and optionally a +//! task shader. +//! +//! These types differ in the operations that are performed in the first half of the pipeline, +//! but share a common second half. The type of a graphics pipeline is determined by whether +//! it contains a vertex or a mesh shader (it cannot contain both). +//! +//! ## Primitive shading +//! //! 1. Vertex input and assembly: vertex input data is read from data buffers and then assembled //! into primitives (points, lines, triangles etc.). //! 2. Vertex shader invocations: the vertex data of each primitive is fed as input to the vertex @@ -17,14 +33,31 @@ //! newly created vertices. //! 4. (Optional) Geometry shading: whole primitives are fed as input and processed into a new set //! of output primitives. -//! 5. Vertex post-processing, including: +//! +//! ## Mesh shading +//! +//! 1. (Optional) Task shader invocations: the task shader is run once for each workgroup in the +//! draw command. The task shader then spawns one or more mesh shader invocations. +//! 2. Mesh shader invocations: the mesh shader is run, either once each time it is spawned by a +//! task shader, or if there is no task shader, once for each workgroup in the draw command. +//! The mesh shader outputs a list of primitives (triangles etc). +//! +//! Mesh shading pipelines do not receive any vertex input; their input data is supplied entirely +//! from resources bound via descriptor sets, in combination with the x, y and z coordinates of +//! the current workgroup. +//! +//! ## Rasterization, fragment processing and output +//! +//! These steps are shared by all graphics pipeline types. +//! +//! 1. Vertex post-processing, including: //! - Clipping primitives to the view frustum and user-defined clipping planes. //! - Perspective division. //! - Viewport mapping. -//! 6. Rasterization: converting primitives into a two-dimensional representation. Primitives may be +//! 2. Rasterization: converting primitives into a two-dimensional representation. Primitives may be //! discarded depending on their orientation, and are then converted into a collection of //! fragments that are processed further. -//! 7. Fragment operations. These include invocations of the fragment shader, which generates the +//! 3. Fragment operations. These include invocations of the fragment shader, which generates the //! values to be written to the color attachment. Various testing and discarding operations can //! be performed both before and after the fragment shader ("early" and "late" fragment tests), //! including: @@ -34,13 +67,11 @@ //! - Depth bounds test //! - Stencil test //! - Depth test -//! 8. Color attachment output: the final pixel data is written to a framebuffer. Blending and +//! 4. Color attachment output: the final pixel data is written to a framebuffer. Blending and //! logical operations can be applied to combine incoming pixel data with data already present //! in the framebuffer. //! -//! A graphics pipeline contains many configuration options, which are grouped into collections of -//! "state". Often, these directly correspond to one or more steps in the graphics pipeline. Each -//! state collection has a dedicated submodule. +//! # Using a graphics pipeline //! //! Once a graphics pipeline has been created, you can execute it by first *binding* it in a command //! buffer, binding the necessary vertex buffers, binding any descriptor sets, setting push @@ -117,10 +148,9 @@ pub struct GraphicsPipeline { id: NonZeroU64, flags: PipelineCreateFlags, - // TODO: replace () with an object that describes the shaders in some way. - shaders: HashMap, + shader_stages: ShaderStages, vertex_input_state: Option, - input_assembly_state: InputAssemblyState, + input_assembly_state: Option, tessellation_state: Option, viewport_state: Option, rasterization_state: RasterizationState, @@ -137,6 +167,7 @@ pub struct GraphicsPipeline { num_used_descriptor_sets: u32, fixed_state: HashSet, fragment_tests_stages: Option, + mesh_is_nv: bool, // Note: this is only `Some` if `vertex_input_state` is `None`. required_vertex_inputs: Option>, } @@ -917,7 +948,8 @@ impl GraphicsPipeline { _ne: _, } = create_info; - let mut shaders = HashMap::default(); + let mut shader_stages = ShaderStages::empty(); + let mut mesh_is_nv = false; let mut descriptor_binding_requirements: HashMap< (u32, u32), DescriptorBindingRequirements, @@ -932,7 +964,7 @@ impl GraphicsPipeline { let entry_point_info = entry_point.info(); let stage = ShaderStage::from(entry_point_info.execution_model); - shaders.insert(stage, ()); + shader_stages |= stage.into(); let spirv = entry_point.module().spirv(); let entry_point_function = spirv.function(entry_point.id()); @@ -947,6 +979,7 @@ impl GraphicsPipeline { )); } } + ExecutionModel::MeshNV | ExecutionModel::TaskNV => mesh_is_nv = true, ExecutionModel::Fragment => { fragment_tests_stages = Some(FragmentTestsStages::Late); @@ -1055,9 +1088,9 @@ impl GraphicsPipeline { id: Self::next_id(), flags, - shaders, + shader_stages, vertex_input_state, - input_assembly_state: input_assembly_state.unwrap(), // Can be None if there's a mesh shader, but we don't support that yet + input_assembly_state, tessellation_state, viewport_state, rasterization_state: rasterization_state.unwrap(), // Can be None for pipeline libraries, but we don't support that yet @@ -1074,6 +1107,7 @@ impl GraphicsPipeline { num_used_descriptor_sets, fixed_state, fragment_tests_stages, + mesh_is_nv, required_vertex_inputs, }) } @@ -1090,16 +1124,15 @@ impl GraphicsPipeline { self.flags } - /// Returns information about a particular shader. - /// - /// `None` is returned if the pipeline does not contain this shader. - /// - /// Compatibility note: `()` is temporary, it will be replaced with something else in the - /// future. - // TODO: ^ implement and make this public + /// Returns the shader stages that this pipeline contains. #[inline] - pub(crate) fn shader(&self, stage: ShaderStage) -> Option<()> { - self.shaders.get(&stage).copied() + pub fn shader_stages(&self) -> ShaderStages { + self.shader_stages + } + + #[inline] + pub(crate) fn mesh_is_nv(&self) -> bool { + self.mesh_is_nv } /// Returns the vertex input state used to create this pipeline. @@ -1110,8 +1143,8 @@ impl GraphicsPipeline { /// Returns the input assembly state used to create this pipeline. #[inline] - pub fn input_assembly_state(&self) -> &InputAssemblyState { - &self.input_assembly_state + pub fn input_assembly_state(&self) -> Option<&InputAssemblyState> { + self.input_assembly_state.as_ref() } /// Returns the tessellation state used to create this pipeline. @@ -1251,35 +1284,39 @@ pub struct GraphicsPipelineCreateInfo { /// The shader stages to use. /// - /// A vertex shader must always be included. Other stages are optional. + /// Either a vertex shader or mesh shader must always be included. Other stages are optional. /// /// The default value is empty. pub stages: SmallVec<[PipelineShaderStageCreateInfo; 5]>, /// The vertex input state. /// - /// This state is always used, and must be provided. + /// This must be `Some` if `stages` contains a vertex shader. + /// It must be `None` otherwise. /// /// The default value is `None`. pub vertex_input_state: Option, /// The input assembly state. /// - /// This state is always used, and must be provided. + /// This must be `Some` if `stages` contains a vertex shader. + /// It must be `None` otherwise. /// /// The default value is `None`. pub input_assembly_state: Option, /// The tessellation state. /// - /// This state is used if `stages` contains tessellation shaders. + /// This must be `Some` if `stages` contains tessellation shaders. + /// It must be `None` otherwise. /// /// The default value is `None`. pub tessellation_state: Option, /// The viewport state. /// - /// This state is used if [rasterizer discarding] is not enabled. + /// This must be `Some` if [rasterizer discarding] is not enabled. + /// It must be `None` otherwise. /// /// The default value is `None`. /// @@ -1288,14 +1325,15 @@ pub struct GraphicsPipelineCreateInfo { /// The rasterization state. /// - /// This state is always used, and must be provided. + /// This must always be `Some`. /// /// The default value is `None`. pub rasterization_state: Option, /// The multisample state. /// - /// This state is used if [rasterizer discarding] is not enabled. + /// This must be `Some` if [rasterizer discarding] is not enabled. + /// It must be `None` otherwise. /// /// The default value is `None`. /// @@ -1304,8 +1342,9 @@ pub struct GraphicsPipelineCreateInfo { /// The depth/stencil state. /// - /// This state is used if `render_pass` has depth/stencil attachments, or if + /// This must be `Some` if `render_pass` has depth/stencil attachments, or if /// [rasterizer discarding] is enabled. + /// It must be `None` otherwise. /// /// The default value is `None`. /// @@ -1314,8 +1353,9 @@ pub struct GraphicsPipelineCreateInfo { /// The color blend state. /// - /// This state is used if `render_pass` has color attachments, and [rasterizer discarding] is + /// This must be `Some` if `render_pass` has color attachments, and [rasterizer discarding] is /// not enabled. + /// It must be `None` otherwise. /// /// The default value is `None`. /// @@ -1350,8 +1390,6 @@ pub struct GraphicsPipelineCreateInfo { /// The discard rectangle state. /// - /// This state is always used if it is provided. - /// /// The default value is `None`. pub discard_rectangle_state: Option, @@ -1448,17 +1486,17 @@ impl GraphicsPipelineCreateInfo { Gather shader stages */ - let mut stages_present = ShaderStages::empty(); - let mut vertex_stage = None; - let mut tessellation_control_stage = None; - let mut tessellation_evaluation_stage = None; - let mut geometry_stage = None; - let mut fragment_stage = None; + const PRIMITIVE_SHADING_STAGES: ShaderStages = ShaderStages::VERTEX + .union(ShaderStages::TESSELLATION_CONTROL) + .union(ShaderStages::TESSELLATION_CONTROL) + .union(ShaderStages::GEOMETRY); + const MESH_SHADING_STAGES: ShaderStages = ShaderStages::MESH.union(ShaderStages::TASK); - for (stage_index, stage) in stages.iter().enumerate() { - let entry_point_info = stage.entry_point.info(); - let stage_enum = ShaderStage::from(entry_point_info.execution_model); - let stage_flag = ShaderStages::from(stage_enum); + let mut stages_present = ShaderStages::empty(); + + for stage in stages { + let stage_flag = + ShaderStages::from(ShaderStage::from(stage.entry_point.info().execution_model)); if stages_present.intersects(stage_flag) { return Err(Box::new(ValidationError { @@ -1474,43 +1512,6 @@ impl GraphicsPipelineCreateInfo { })); } - const PRIMITIVE_SHADING_STAGES: ShaderStages = ShaderStages::VERTEX - .union(ShaderStages::TESSELLATION_CONTROL) - .union(ShaderStages::TESSELLATION_CONTROL) - .union(ShaderStages::GEOMETRY); - const MESH_SHADING_STAGES: ShaderStages = ShaderStages::MESH.union(ShaderStages::TASK); - - if stage_flag.intersects(PRIMITIVE_SHADING_STAGES) - && stages_present.intersects(MESH_SHADING_STAGES) - || stage_flag.intersects(MESH_SHADING_STAGES) - && stages_present.intersects(PRIMITIVE_SHADING_STAGES) - { - return Err(Box::new(ValidationError { - context: "stages".into(), - problem: "contains both primitive shading stages and mesh shading stages" - .into(), - vuids: &["VUID-VkGraphicsPipelineCreateInfo-pStages-02095"], - ..Default::default() - })); - } - - let stage_slot = match stage_enum { - ShaderStage::Vertex => &mut vertex_stage, - ShaderStage::TessellationControl => &mut tessellation_control_stage, - ShaderStage::TessellationEvaluation => &mut tessellation_evaluation_stage, - ShaderStage::Geometry => &mut geometry_stage, - ShaderStage::Fragment => &mut fragment_stage, - _ => { - return Err(Box::new(ValidationError { - context: format!("stages[{}]", stage_index).into(), - problem: "is not a pre-rasterization or fragment shader stage".into(), - vuids: &["VUID-VkGraphicsPipelineCreateInfo-pStages-06896"], - ..Default::default() - })); - } - }; - - *stage_slot = Some(stage); stages_present |= stage_flag; } @@ -1548,13 +1549,8 @@ impl GraphicsPipelineCreateInfo { _ => (), } - let need_vertex_input_state = need_pre_rasterization_shader_state - && stages.iter().any(|stage| { - matches!( - stage.entry_point.info().execution_model, - ExecutionModel::Vertex - ) - }); + let need_vertex_input_state = + need_pre_rasterization_shader_state && stages_present.intersects(ShaderStages::VERTEX); let need_fragment_shader_state = need_pre_rasterization_shader_state && (!rasterization_state .as_ref() @@ -1568,109 +1564,34 @@ impl GraphicsPipelineCreateInfo { .rasterizer_discard_enable || dynamic_state.contains(&DynamicState::RasterizerDiscardEnable)); - match (vertex_stage.is_some(), need_pre_rasterization_shader_state) { - (true, false) => { - return Err(Box::new(ValidationError { - problem: "the pipeline is not being created with \ - pre-rasterization shader state, but `stages` contains a \ - `ShaderStage::Vertex` stage" - .into(), - vuids: &["VUID-VkGraphicsPipelineCreateInfo-pStages-06895"], - ..Default::default() - })); - } - (false, true) => { + if need_pre_rasterization_shader_state { + if !stages_present.intersects(ShaderStages::VERTEX | ShaderStages::MESH) { return Err(Box::new(ValidationError { problem: "the pipeline is being created with \ pre-rasterization shader state, but `stages` does not contain a \ - `ShaderStage::Vertex` stage" + `ShaderStage::Vertex` or `ShaderStage::Mesh` stage" .into(), vuids: &["VUID-VkGraphicsPipelineCreateInfo-stage-02096"], ..Default::default() })); } - _ => (), - } - - match ( - tessellation_control_stage.is_some(), - need_pre_rasterization_shader_state, - ) { - (true, false) => { + } else { + if stages_present.intersects(PRIMITIVE_SHADING_STAGES | MESH_SHADING_STAGES) { return Err(Box::new(ValidationError { problem: "the pipeline is not being created with \ pre-rasterization shader state, but `stages` contains a \ - `ShaderStage::TessellationControl` stage" + pre-rasterization shader stage" .into(), vuids: &["VUID-VkGraphicsPipelineCreateInfo-pStages-06895"], ..Default::default() })); } - (false, true) => (), - _ => (), } match ( - tessellation_evaluation_stage.is_some(), - need_pre_rasterization_shader_state, + stages_present.intersects(ShaderStages::FRAGMENT), + need_fragment_shader_state, ) { - (true, false) => { - return Err(Box::new(ValidationError { - problem: "the pipeline is not being created with \ - pre-rasterization shader state, but `stages` contains a \ - `ShaderStage::TessellationEvaluation` stage" - .into(), - vuids: &["VUID-VkGraphicsPipelineCreateInfo-pStages-06895"], - ..Default::default() - })); - } - (false, true) => (), - _ => (), - } - - if stages_present.intersects(ShaderStages::TESSELLATION_CONTROL) - && !stages_present.intersects(ShaderStages::TESSELLATION_EVALUATION) - { - return Err(Box::new(ValidationError { - context: "stages".into(), - problem: "contains a `ShaderStage::TessellationControl` stage, but not a \ - `ShaderStage::TessellationEvaluation` stage" - .into(), - vuids: &["VUID-VkGraphicsPipelineCreateInfo-pStages-00729"], - ..Default::default() - })); - } else if stages_present.intersects(ShaderStages::TESSELLATION_EVALUATION) - && !stages_present.intersects(ShaderStages::TESSELLATION_CONTROL) - { - return Err(Box::new(ValidationError { - context: "stages".into(), - problem: "contains a `ShaderStage::TessellationEvaluation` stage, but not a \ - `ShaderStage::TessellationControl` stage" - .into(), - vuids: &["VUID-VkGraphicsPipelineCreateInfo-pStages-00730"], - ..Default::default() - })); - } - - match ( - geometry_stage.is_some(), - need_pre_rasterization_shader_state, - ) { - (true, false) => { - return Err(Box::new(ValidationError { - problem: "the pipeline is not being created with \ - pre-rasterization shader state, but `stages` contains a \ - `ShaderStage::Geometry` stage" - .into(), - vuids: &["VUID-VkGraphicsPipelineCreateInfo-pStages-06895"], - ..Default::default() - })); - } - (false, true) => (), - _ => (), - } - - match (fragment_stage.is_some(), need_fragment_shader_state) { (true, false) => { return Err(Box::new(ValidationError { problem: "the pipeline is not being created with \ @@ -1956,6 +1877,16 @@ impl GraphicsPipelineCreateInfo { Validate shader stages individually */ + let mut has_mesh_ext = false; + let mut has_mesh_nv = false; + let mut vertex_stage = None; + let mut tessellation_control_stage = None; + let mut tessellation_evaluation_stage = None; + let mut geometry_stage = None; + let mut task_stage = None; + let mut mesh_stage = None; + let mut fragment_stage = None; + for (stage_index, stage) in stages.iter().enumerate() { stage .validate(device) @@ -1969,6 +1900,37 @@ impl GraphicsPipelineCreateInfo { } = stage; let entry_point_info = entry_point.info(); + let execution_model = entry_point_info.execution_model; + + match execution_model { + ExecutionModel::TaskEXT | ExecutionModel::MeshEXT => { + has_mesh_ext = true; + } + ExecutionModel::TaskNV | ExecutionModel::MeshNV => { + has_mesh_nv = true; + } + _ => (), + } + + let stage_enum = ShaderStage::from(execution_model); + let stage_slot = match stage_enum { + ShaderStage::Vertex => &mut vertex_stage, + ShaderStage::TessellationControl => &mut tessellation_control_stage, + ShaderStage::TessellationEvaluation => &mut tessellation_evaluation_stage, + ShaderStage::Geometry => &mut geometry_stage, + ShaderStage::Task => &mut task_stage, + ShaderStage::Mesh => &mut mesh_stage, + ShaderStage::Fragment => &mut fragment_stage, + _ => { + return Err(Box::new(ValidationError { + context: format!("stages[{}]", stage_index).into(), + problem: "is not a pre-rasterization or fragment shader stage".into(), + vuids: &["VUID-VkGraphicsPipelineCreateInfo-pStages-06896"], + ..Default::default() + })); + } + }; + *stage_slot = Some(stage); layout .ensure_compatible_with_shader( @@ -1987,11 +1949,64 @@ impl GraphicsPipelineCreateInfo { })?; } - let ordered_stages: SmallVec<[_; 5]> = [ + if stages_present.intersects(PRIMITIVE_SHADING_STAGES) + && stages_present.intersects(MESH_SHADING_STAGES) + { + return Err(Box::new(ValidationError { + context: "stages".into(), + problem: "contains both primitive shading stages and mesh shading stages".into(), + vuids: &["VUID-VkGraphicsPipelineCreateInfo-pStages-02095"], + ..Default::default() + })); + } + + if stages_present.intersects(ShaderStages::TESSELLATION_CONTROL) + && !stages_present.intersects(ShaderStages::TESSELLATION_EVALUATION) + { + return Err(Box::new(ValidationError { + context: "stages".into(), + problem: "contains a `ShaderStage::TessellationControl` stage, but not a \ + `ShaderStage::TessellationEvaluation` stage" + .into(), + vuids: &["VUID-VkGraphicsPipelineCreateInfo-pStages-00729"], + ..Default::default() + })); + } else if stages_present.intersects(ShaderStages::TESSELLATION_EVALUATION) + && !stages_present.intersects(ShaderStages::TESSELLATION_CONTROL) + { + return Err(Box::new(ValidationError { + context: "stages".into(), + problem: "contains a `ShaderStage::TessellationEvaluation` stage, but not a \ + `ShaderStage::TessellationControl` stage" + .into(), + vuids: &["VUID-VkGraphicsPipelineCreateInfo-pStages-00730"], + ..Default::default() + })); + } + + if has_mesh_ext && has_mesh_nv { + return Err(Box::new(ValidationError { + context: "stages".into(), + problem: "contains mesh shader stages from both the EXT and the NV version".into(), + vuids: &["VUID-VkGraphicsPipelineCreateInfo-TaskNV-07063"], + ..Default::default() + })); + } + + // VUID-VkGraphicsPipelineCreateInfo-layout-01688 + // Checked at pipeline layout creation time. + + /* + Check compatibility between shader interfaces + */ + + let ordered_stages: SmallVec<[_; 7]> = [ vertex_stage, tessellation_control_stage, tessellation_evaluation_stage, geometry_stage, + task_stage, + mesh_stage, fragment_stage, ] .into_iter() @@ -2044,9 +2059,6 @@ impl GraphicsPipelineCreateInfo { })?; } - // VUID-VkGraphicsPipelineCreateInfo-layout-01688 - // Checked at pipeline layout creation time. - /* Validate states individually */ @@ -2061,9 +2073,6 @@ impl GraphicsPipelineCreateInfo { input_assembly_state .validate(device) .map_err(|err| err.add_context("input_assembly_state"))?; - - // TODO: - // VUID-VkGraphicsPipelineCreateInfo-topology-00737 } if let Some(tessellation_state) = tessellation_state { @@ -2076,7 +2085,133 @@ impl GraphicsPipelineCreateInfo { viewport_state .validate(device) .map_err(|err| err.add_context("viewport_state"))?; + } + if let Some(rasterization_state) = rasterization_state { + rasterization_state + .validate(device) + .map_err(|err| err.add_context("rasterization_state"))?; + } + + if let Some(multisample_state) = multisample_state { + multisample_state + .validate(device) + .map_err(|err| err.add_context("multisample_state"))?; + } + + if let Some(depth_stencil_state) = depth_stencil_state { + depth_stencil_state + .validate(device) + .map_err(|err| err.add_context("depth_stencil_state"))?; + } + + if let Some(color_blend_state) = color_blend_state { + color_blend_state + .validate(device) + .map_err(|err| err.add_context("color_blend_state"))?; + } + + if let Some(subpass) = subpass { + match subpass { + PipelineSubpassType::BeginRenderPass(subpass) => { + // VUID-VkGraphicsPipelineCreateInfo-commonparent + assert_eq!(device, subpass.render_pass().device().as_ref()); + } + PipelineSubpassType::BeginRendering(rendering_info) => { + if !device.enabled_features().dynamic_rendering { + return Err(Box::new(ValidationError { + context: "subpass".into(), + problem: "is `PipelineRenderPassType::BeginRendering`".into(), + requires_one_of: RequiresOneOf(&[RequiresAllOf(&[Requires::Feature( + "dynamic_rendering", + )])]), + vuids: &["VUID-VkGraphicsPipelineCreateInfo-dynamicRendering-06576"], + })); + } + + rendering_info + .validate(device) + .map_err(|err| err.add_context("subpass"))?; + } + } + } + + if let Some(discard_rectangle_state) = discard_rectangle_state { + if !device.enabled_extensions().ext_discard_rectangles { + return Err(Box::new(ValidationError { + context: "discard_rectangle_state".into(), + problem: "is `Some`".into(), + requires_one_of: RequiresOneOf(&[RequiresAllOf(&[Requires::DeviceExtension( + "ext_discard_rectangles", + )])]), + ..Default::default() + })); + } + + discard_rectangle_state + .validate(device) + .map_err(|err| err.add_context("discard_rectangle_state"))?; + } + + for dynamic_state in dynamic_state.iter().copied() { + dynamic_state.validate_device(device).map_err(|err| { + err.add_context("dynamic_state") + .set_vuids(&["VUID-VkPipelineDynamicStateCreateInfo-pDynamicStates-parameter"]) + })?; + } + + /* + Check dynamic states against other things + */ + + if stages_present.intersects(ShaderStages::MESH) { + if dynamic_state.contains(&DynamicState::PrimitiveTopology) { + return Err(Box::new(ValidationError { + problem: "`stages` includes a mesh shader, but `dynamic_state` contains \ + `DynamicState::PrimitiveTopology`" + .into(), + vuids: &["VUID-VkGraphicsPipelineCreateInfo-pDynamicStates-07065"], + ..Default::default() + })); + } + + if dynamic_state.contains(&DynamicState::PrimitiveRestartEnable) { + return Err(Box::new(ValidationError { + problem: "`stages` includes a mesh shader, but `dynamic_state` contains \ + `DynamicState::PrimitiveRestartEnable`" + .into(), + vuids: &["VUID-VkGraphicsPipelineCreateInfo-pDynamicStates-07066"], + ..Default::default() + })); + } + + if dynamic_state.contains(&DynamicState::PatchControlPoints) { + return Err(Box::new(ValidationError { + problem: "`stages` includes a mesh shader, but `dynamic_state` contains \ + `DynamicState::PatchControlPoints`" + .into(), + vuids: &["VUID-VkGraphicsPipelineCreateInfo-pDynamicStates-07066"], + ..Default::default() + })); + } + + if dynamic_state.contains(&DynamicState::VertexInput) { + return Err(Box::new(ValidationError { + problem: "`stages` includes a mesh shader, but `dynamic_state` contains \ + `DynamicState::VertexInput`" + .into(), + vuids: &["VUID-VkGraphicsPipelineCreateInfo-pDynamicStates-07067"], + ..Default::default() + })); + } + } + + if let Some(_input_assembly_state) = input_assembly_state { + // TODO: + // VUID-VkGraphicsPipelineCreateInfo-topology-00737 + } + + if let Some(viewport_state) = viewport_state { let ViewportState { ref viewports, ref scissors, @@ -2152,10 +2287,6 @@ impl GraphicsPipelineCreateInfo { } if let Some(rasterization_state) = rasterization_state { - rasterization_state - .validate(device) - .map_err(|err| err.add_context("rasterization_state"))?; - let &RasterizationState { depth_clamp_enable: _, rasterizer_discard_enable: _, @@ -2232,20 +2363,12 @@ impl GraphicsPipelineCreateInfo { // VUID-VkGraphicsPipelineCreateInfo-renderPass-06059 } - if let Some(multisample_state) = multisample_state { - multisample_state - .validate(device) - .map_err(|err| err.add_context("multisample_state"))?; - + if let Some(_multisample_state) = multisample_state { // TODO: // VUID-VkGraphicsPipelineCreateInfo-lineRasterizationMode-02766 } if let Some(depth_stencil_state) = depth_stencil_state { - depth_stencil_state - .validate(device) - .map_err(|err| err.add_context("depth_stencil_state"))?; - let &DepthStencilState { flags: _, ref depth, @@ -2287,137 +2410,6 @@ impl GraphicsPipelineCreateInfo { // VUID-VkGraphicsPipelineCreateInfo-renderPass-06040 } - if let Some(color_blend_state) = color_blend_state { - color_blend_state - .validate(device) - .map_err(|err| err.add_context("color_blend_state"))?; - } - - if let Some(subpass) = subpass { - match subpass { - PipelineSubpassType::BeginRenderPass(subpass) => { - // VUID-VkGraphicsPipelineCreateInfo-commonparent - assert_eq!(device, subpass.render_pass().device().as_ref()); - - if subpass.subpass_desc().view_mask != 0 { - if stages_present.intersects( - ShaderStages::TESSELLATION_CONTROL - | ShaderStages::TESSELLATION_EVALUATION, - ) && !device.enabled_features().multiview_tessellation_shader - { - return Err(Box::new(ValidationError { - problem: "`stages` contains tessellation shaders, and \ - `subpass` has a non-zero `view_mask`" - .into(), - requires_one_of: RequiresOneOf(&[RequiresAllOf(&[ - Requires::Feature("multiview_tessellation_shader"), - ])]), - vuids: &["VUID-VkGraphicsPipelineCreateInfo-renderPass-06047"], - ..Default::default() - })); - } - - if stages_present.intersects(ShaderStages::GEOMETRY) - && !device.enabled_features().multiview_geometry_shader - { - return Err(Box::new(ValidationError { - problem: "`stages` contains a geometry shader, and \ - `subpass` has a non-zero `view_mask`" - .into(), - requires_one_of: RequiresOneOf(&[RequiresAllOf(&[ - Requires::Feature("multiview_geometry_shader"), - ])]), - vuids: &["VUID-VkGraphicsPipelineCreateInfo-renderPass-06048"], - ..Default::default() - })); - } - } - } - PipelineSubpassType::BeginRendering(rendering_info) => { - if !device.enabled_features().dynamic_rendering { - return Err(Box::new(ValidationError { - context: "subpass".into(), - problem: "is `PipelineRenderPassType::BeginRendering`".into(), - requires_one_of: RequiresOneOf(&[RequiresAllOf(&[Requires::Feature( - "dynamic_rendering", - )])]), - vuids: &["VUID-VkGraphicsPipelineCreateInfo-dynamicRendering-06576"], - })); - } - - rendering_info - .validate(device) - .map_err(|err| err.add_context("subpass"))?; - - let &PipelineRenderingCreateInfo { - view_mask, - color_attachment_formats: _, - depth_attachment_format: _, - stencil_attachment_format: _, - _ne: _, - } = rendering_info; - - if view_mask != 0 { - if stages_present.intersects( - ShaderStages::TESSELLATION_CONTROL - | ShaderStages::TESSELLATION_EVALUATION, - ) && !device.enabled_features().multiview_tessellation_shader - { - return Err(Box::new(ValidationError { - problem: "`stages` contains tessellation shaders, and \ - `subpass.view_mask` is not 0" - .into(), - requires_one_of: RequiresOneOf(&[RequiresAllOf(&[ - Requires::Feature("multiview_tessellation_shader"), - ])]), - vuids: &["VUID-VkGraphicsPipelineCreateInfo-renderPass-06057"], - ..Default::default() - })); - } - - if stages_present.intersects(ShaderStages::GEOMETRY) - && !device.enabled_features().multiview_geometry_shader - { - return Err(Box::new(ValidationError { - problem: "`stages` contains a geometry shader, and \ - `subpass.view_mask` is not 0" - .into(), - requires_one_of: RequiresOneOf(&[RequiresAllOf(&[ - Requires::Feature("multiview_geometry_shader"), - ])]), - vuids: &["VUID-VkGraphicsPipelineCreateInfo-renderPass-06058"], - ..Default::default() - })); - } - } - } - } - } - - if let Some(discard_rectangle_state) = discard_rectangle_state { - if !device.enabled_extensions().ext_discard_rectangles { - return Err(Box::new(ValidationError { - context: "discard_rectangle_state".into(), - problem: "is `Some`".into(), - requires_one_of: RequiresOneOf(&[RequiresAllOf(&[Requires::DeviceExtension( - "ext_discard_rectangles", - )])]), - ..Default::default() - })); - } - - discard_rectangle_state - .validate(device) - .map_err(|err| err.add_context("discard_rectangle_state"))?; - } - - for dynamic_state in dynamic_state.iter().copied() { - dynamic_state.validate_device(device).map_err(|err| { - err.add_context("dynamic_state") - .set_vuids(&["VUID-VkPipelineDynamicStateCreateInfo-pDynamicStates-parameter"]) - })?; - } - /* Checks that rely on multiple pieces of state */ @@ -2704,6 +2696,70 @@ impl GraphicsPipelineCreateInfo { } } + if let Some(subpass) = subpass { + let view_mask = match subpass { + PipelineSubpassType::BeginRenderPass(subpass) => subpass.subpass_desc().view_mask, + PipelineSubpassType::BeginRendering(rendering_info) => rendering_info.view_mask, + }; + + if view_mask != 0 { + if stages_present.intersects( + ShaderStages::TESSELLATION_CONTROL | ShaderStages::TESSELLATION_EVALUATION, + ) && !device.enabled_features().multiview_tessellation_shader + { + return Err(Box::new(ValidationError { + problem: "`stages` contains tessellation shaders, and \ + `subpass` has a non-zero `view_mask`" + .into(), + requires_one_of: RequiresOneOf(&[RequiresAllOf(&[Requires::Feature( + "multiview_tessellation_shader", + )])]), + vuids: &[ + "VUID-VkGraphicsPipelineCreateInfo-renderPass-06047", + "VUID-VkGraphicsPipelineCreateInfo-renderPass-06057", + ], + ..Default::default() + })); + } + + if stages_present.intersects(ShaderStages::GEOMETRY) + && !device.enabled_features().multiview_geometry_shader + { + return Err(Box::new(ValidationError { + problem: "`stages` contains a geometry shader, and \ + `subpass` has a non-zero `view_mask`" + .into(), + requires_one_of: RequiresOneOf(&[RequiresAllOf(&[Requires::Feature( + "multiview_geometry_shader", + )])]), + vuids: &[ + "VUID-VkGraphicsPipelineCreateInfo-renderPass-06048", + "VUID-VkGraphicsPipelineCreateInfo-renderPass-06058", + ], + ..Default::default() + })); + } + + if stages_present.intersects(ShaderStages::MESH) + && !device.enabled_features().multiview_mesh_shader + { + return Err(Box::new(ValidationError { + problem: "`stages` contains a mesh shader, and \ + `subpass` has a non-zero `view_mask`" + .into(), + requires_one_of: RequiresOneOf(&[RequiresAllOf(&[Requires::Feature( + "multiview_mesh_shader", + )])]), + vuids: &[ + "VUID-VkGraphicsPipelineCreateInfo-renderPass-07064", + "VUID-VkGraphicsPipelineCreateInfo-renderPass-07720", + ], + ..Default::default() + })); + } + } + } + if let (Some(color_blend_state), Some(subpass)) = (color_blend_state, subpass) { let color_attachment_count = match subpass { PipelineSubpassType::BeginRenderPass(subpass) => { diff --git a/vulkano/src/pipeline/shader.rs b/vulkano/src/pipeline/shader.rs index 26f5229a..a2ba1267 100644 --- a/vulkano/src/pipeline/shader.rs +++ b/vulkano/src/pipeline/shader.rs @@ -74,8 +74,8 @@ impl PipelineShaderStageCreateInfo { .set_vuids(&["VUID-VkPipelineShaderStageCreateInfo-flags-parameter"]) })?; - let entry_point_info = entry_point.info(); - let stage_enum = ShaderStage::from(entry_point_info.execution_model); + let execution_model = entry_point.info().execution_model; + let stage_enum = ShaderStage::from(execution_model); stage_enum.validate_device(device).map_err(|err| { err.add_context("entry_point.info().execution") @@ -144,18 +144,6 @@ impl PipelineShaderStageCreateInfo { ShaderStage::Miss => (), ShaderStage::Intersection => (), ShaderStage::Callable => (), - ShaderStage::Task => { - if !device.enabled_features().task_shader { - return Err(Box::new(ValidationError { - context: "entry_point".into(), - problem: "specifies a `ShaderStage::Task` entry point".into(), - requires_one_of: RequiresOneOf(&[RequiresAllOf(&[Requires::Feature( - "task_shader", - )])]), - vuids: &["VUID-VkPipelineShaderStageCreateInfo-stage-02092"], - })); - } - } ShaderStage::Mesh => { if !device.enabled_features().mesh_shader { return Err(Box::new(ValidationError { @@ -168,6 +156,18 @@ impl PipelineShaderStageCreateInfo { })); } } + ShaderStage::Task => { + if !device.enabled_features().task_shader { + return Err(Box::new(ValidationError { + context: "entry_point".into(), + problem: "specifies a `ShaderStage::Task` entry point".into(), + requires_one_of: RequiresOneOf(&[RequiresAllOf(&[Requires::Feature( + "task_shader", + )])]), + vuids: &["VUID-VkPipelineShaderStageCreateInfo-stage-02092"], + })); + } + } ShaderStage::SubpassShading => (), } diff --git a/vulkano/src/query.rs b/vulkano/src/query.rs index 82e70998..bd3f78d8 100644 --- a/vulkano/src/query.rs +++ b/vulkano/src/query.rs @@ -44,6 +44,17 @@ impl QueryPool { unsafe { Ok(Self::new_unchecked(device, create_info)?) } } + fn validate_new( + device: &Device, + create_info: &QueryPoolCreateInfo, + ) -> Result<(), Box> { + create_info + .validate(device) + .map_err(|err| err.add_context("create_info"))?; + + Ok(()) + } + #[cfg_attr(not(feature = "document_unchecked"), doc(hidden))] pub unsafe fn new_unchecked( device: Arc, @@ -86,17 +97,6 @@ impl QueryPool { Ok(Self::from_handle(device, handle, create_info)) } - fn validate_new( - device: &Device, - create_info: &QueryPoolCreateInfo, - ) -> Result<(), Box> { - create_info - .validate(device) - .map_err(|err| err.add_context("create_info"))?; - - Ok(()) - } - /// Creates a new `QueryPool` from a raw object handle. /// /// # Safety @@ -247,7 +247,8 @@ impl QueryPool { | QueryType::AccelerationStructureCompactedSize | QueryType::AccelerationStructureSerializationSize | QueryType::AccelerationStructureSerializationBottomLevelPointers - | QueryType::AccelerationStructureSize => (), + | QueryType::AccelerationStructureSize + | QueryType::MeshPrimitivesGenerated => (), } Ok(()) @@ -373,6 +374,35 @@ impl QueryPoolCreateInfo { err.add_context("query_type.flags") .set_vuids(&["VUID-VkQueryPoolCreateInfo-queryType-00792"]) })?; + + if flags.intersects( + QueryPipelineStatisticFlags::TASK_SHADER_INVOCATIONS + | QueryPipelineStatisticFlags::MESH_SHADER_INVOCATIONS, + ) && !device.enabled_features().mesh_shader_queries + { + return Err(Box::new(ValidationError { + context: "query_type.flags".into(), + problem: "contains `TASK_SHADER_INVOCATIONS` or \ + `MESH_SHADER_INVOCATIONS`" + .into(), + requires_one_of: RequiresOneOf(&[RequiresAllOf(&[Requires::Feature( + "mesh_shader_queries", + )])]), + vuids: &["VUID-VkQueryPoolCreateInfo-meshShaderQueries-07069"], + })); + } + } + QueryType::MeshPrimitivesGenerated => { + if !device.enabled_features().mesh_shader_queries { + return Err(Box::new(ValidationError { + context: "query_type".into(), + problem: "is `QueryType::MeshPrimitivesGenerated`".into(), + requires_one_of: RequiresOneOf(&[RequiresAllOf(&[Requires::Feature( + "mesh_shader_queries", + )])]), + vuids: &["VUID-VkQueryPoolCreateInfo-meshShaderQueries-07068"], + })); + } } QueryType::Occlusion | QueryType::Timestamp @@ -461,6 +491,14 @@ pub enum QueryType { /// /// [`write_acceleration_structures_properties`]: crate::command_buffer::RecordingCommandBuffer::write_acceleration_structures_properties AccelerationStructureSize = ash::vk::QueryType::ACCELERATION_STRUCTURE_SIZE_KHR.as_raw(), + + /// Queries the number of primitives emitted from a mesh shader that reach the fragment shader. + /// + /// Used with the [`begin_query`] and [`end_query`] commands. + /// + /// [`begin_query`]: crate::command_buffer::RecordingCommandBuffer::begin_query + /// [`end_query`]: crate::command_buffer::RecordingCommandBuffer::end_query + MeshPrimitivesGenerated = ash::vk::QueryType::MESH_PRIMITIVES_GENERATED_EXT.as_raw(), } impl QueryType { @@ -485,7 +523,8 @@ impl QueryType { | Self::AccelerationStructureCompactedSize | Self::AccelerationStructureSerializationSize | Self::AccelerationStructureSerializationBottomLevelPointers - | Self::AccelerationStructureSize => 1, + | Self::AccelerationStructureSize + | Self::MeshPrimitivesGenerated => 1, Self::PipelineStatistics(flags) => flags.count() as DeviceSize, } } @@ -541,6 +580,17 @@ impl QueryType { })); } } + QueryType::MeshPrimitivesGenerated => { + if !device.enabled_extensions().ext_mesh_shader { + return Err(Box::new(ValidationError { + problem: "is `QueryType::MeshPrimitivesGenerated`".into(), + requires_one_of: RequiresOneOf(&[RequiresAllOf(&[ + Requires::DeviceExtension("ext_mesh_shader"), + ])]), + ..Default::default() + })); + } + } } Ok(()) @@ -566,6 +616,7 @@ impl From<&QueryType> for ash::vk::QueryType { QueryType::AccelerationStructureSize => { ash::vk::QueryType::ACCELERATION_STRUCTURE_SIZE_KHR } + QueryType::MeshPrimitivesGenerated => ash::vk::QueryType::MESH_PRIMITIVES_GENERATED_EXT, } } } @@ -596,6 +647,14 @@ vulkan_bitflags! { /// Returns `true` if `self` contains any flags referring to graphics operations. #[inline] pub const fn is_graphics(self) -> bool { + self.is_primitive_shading_graphics() || self.is_mesh_shading_graphics() || + self.intersects(QueryPipelineStatisticFlags::FRAGMENT_SHADER_INVOCATIONS) + } + + /// Returns `true` if `self` contains any flags referring to primitive shading graphics + /// operations. + #[inline] + pub const fn is_primitive_shading_graphics(self) -> bool { self.intersects( (QueryPipelineStatisticFlags::INPUT_ASSEMBLY_VERTICES) .union(QueryPipelineStatisticFlags::INPUT_ASSEMBLY_PRIMITIVES) @@ -604,11 +663,20 @@ vulkan_bitflags! { .union(QueryPipelineStatisticFlags::GEOMETRY_SHADER_PRIMITIVES) .union(QueryPipelineStatisticFlags::CLIPPING_INVOCATIONS) .union(QueryPipelineStatisticFlags::CLIPPING_PRIMITIVES) - .union(QueryPipelineStatisticFlags::FRAGMENT_SHADER_INVOCATIONS) .union(QueryPipelineStatisticFlags::TESSELLATION_CONTROL_SHADER_PATCHES) .union(QueryPipelineStatisticFlags::TESSELLATION_EVALUATION_SHADER_INVOCATIONS), ) } + + /// Returns `true` if `self` contains any flags referring to mesh shading graphics + /// operations. + #[inline] + pub const fn is_mesh_shading_graphics(self) -> bool { + self.intersects( + (QueryPipelineStatisticFlags::TASK_SHADER_INVOCATIONS) + .union(QueryPipelineStatisticFlags::MESH_SHADER_INVOCATIONS), + ) + } } = QueryPipelineStatisticFlags(u32); @@ -645,19 +713,17 @@ vulkan_bitflags! { /// Count the number of times a compute shader is invoked. COMPUTE_SHADER_INVOCATIONS = COMPUTE_SHADER_INVOCATIONS, - /* TODO: enable - // TODO: document - TASK_SHADER_INVOCATIONS = TASK_SHADER_INVOCATIONS_NV + /// Count the number of times a task shader is invoked. + TASK_SHADER_INVOCATIONS = TASK_SHADER_INVOCATIONS_EXT RequiresOneOf([ - RequiresAllOf([DeviceExtension(nv_mesh_shader)]), - ]),*/ + RequiresAllOf([DeviceExtension(ext_mesh_shader)]), + ]), - /* TODO: enable - // TODO: document - MESH_SHADER_INVOCATIONS = MESH_SHADER_INVOCATIONS_NV + /// Count the number of times a mesh shader is invoked. + MESH_SHADER_INVOCATIONS = MESH_SHADER_INVOCATIONS_EXT RequiresOneOf([ - RequiresAllOf([DeviceExtension(nv_mesh_shader)]), - ]),*/ + RequiresAllOf([DeviceExtension(ext_mesh_shader)]), + ]), } /// A trait for elements of buffers that can be used as a destination for query results.