diff --git a/vulkano/src/command_buffer/auto/builder.rs b/vulkano/src/command_buffer/auto/builder.rs index ddb62638..a0ba717b 100644 --- a/vulkano/src/command_buffer/auto/builder.rs +++ b/vulkano/src/command_buffer/auto/builder.rs @@ -24,6 +24,7 @@ use crate::{ input_assembly::PrimitiveTopology, rasterization::{CullMode, DepthBiasState, FrontFace, LineStipple}, subpass::PipelineRenderingCreateInfo, + vertex_input::VertexInputState, viewport::{Scissor, Viewport}, }, ComputePipeline, DynamicState, GraphicsPipeline, PipelineBindPoint, PipelineLayout, @@ -1318,6 +1319,7 @@ pub(in crate::command_buffer) struct CommandBufferBuilderState { pub(in crate::command_buffer) stencil_reference: StencilStateDynamic, pub(in crate::command_buffer) stencil_test_enable: Option, pub(in crate::command_buffer) stencil_write_mask: StencilStateDynamic, + pub(in crate::command_buffer) vertex_input: Option, pub(in crate::command_buffer) viewport: HashMap, pub(in crate::command_buffer) viewport_with_count: Option>, @@ -1369,7 +1371,7 @@ impl CommandBufferBuilderState { DynamicState::StencilReference => self.stencil_reference = Default::default(), DynamicState::StencilTestEnable => self.stencil_test_enable = None, DynamicState::StencilWriteMask => self.stencil_write_mask = Default::default(), - // DynamicState::VertexInput => todo!(), + DynamicState::VertexInput => self.vertex_input = None, // DynamicState::VertexInputBindingStride => todo!(), DynamicState::Viewport => self.viewport.clear(), // DynamicState::ViewportCoarseSampleOrder => todo!(), diff --git a/vulkano/src/command_buffer/commands/bind_push.rs b/vulkano/src/command_buffer/commands/bind_push.rs index 0658a941..37d61be6 100644 --- a/vulkano/src/command_buffer/commands/bind_push.rs +++ b/vulkano/src/command_buffer/commands/bind_push.rs @@ -13,10 +13,10 @@ use crate::{ graphics::vertex_input::VertexBuffersCollection, ComputePipeline, GraphicsPipeline, PipelineBindPoint, PipelineLayout, }, - DeviceSize, Requires, RequiresAllOf, RequiresOneOf, ValidationError, VulkanObject, + DeviceSize, Requires, RequiresAllOf, RequiresOneOf, ValidationError, Version, VulkanObject, }; use smallvec::SmallVec; -use std::{cmp::min, ffi::c_void, mem::size_of, sync::Arc}; +use std::{cmp::min, ffi::c_void, mem::size_of, ptr, sync::Arc}; /// # Commands to bind or push state for pipeline execution commands. /// @@ -954,19 +954,58 @@ impl UnsafeCommandBufferBuilder { return self; } - let (buffers_vk, offsets_vk): (SmallVec<[_; 2]>, SmallVec<[_; 2]>) = vertex_buffers - .iter() - .map(|buffer| (buffer.buffer().handle(), buffer.offset())) - .unzip(); + let device = self.device(); - let fns = self.device().fns(); - (fns.v1_0.cmd_bind_vertex_buffers)( - self.handle(), - first_binding, - buffers_vk.len() as u32, - buffers_vk.as_ptr(), - offsets_vk.as_ptr(), - ); + if device.api_version() >= Version::V1_3 + || device.enabled_extensions().ext_extended_dynamic_state + || device.enabled_extensions().ext_shader_object + { + let mut buffers_vk: SmallVec<[_; 2]> = SmallVec::with_capacity(vertex_buffers.len()); + let mut offsets_vk: SmallVec<[_; 2]> = SmallVec::with_capacity(vertex_buffers.len()); + let mut sizes_vk: SmallVec<[_; 2]> = SmallVec::with_capacity(vertex_buffers.len()); + + for buffer in vertex_buffers { + buffers_vk.push(buffer.buffer().handle()); + offsets_vk.push(buffer.offset()); + sizes_vk.push(buffer.size()); + } + + let fns = self.device().fns(); + let cmd_bind_vertex_buffers2 = if device.api_version() >= Version::V1_3 { + fns.v1_3.cmd_bind_vertex_buffers2 + } else if device.enabled_extensions().ext_extended_dynamic_state { + fns.ext_extended_dynamic_state.cmd_bind_vertex_buffers2_ext + } else { + fns.ext_shader_object.cmd_bind_vertex_buffers2_ext + }; + + cmd_bind_vertex_buffers2( + self.handle(), + first_binding, + buffers_vk.len() as u32, + buffers_vk.as_ptr(), + offsets_vk.as_ptr(), + sizes_vk.as_ptr(), + ptr::null(), + ) + } else { + let mut buffers_vk: SmallVec<[_; 2]> = SmallVec::with_capacity(vertex_buffers.len()); + let mut offsets_vk: SmallVec<[_; 2]> = SmallVec::with_capacity(vertex_buffers.len()); + + for buffer in vertex_buffers { + buffers_vk.push(buffer.buffer().handle()); + offsets_vk.push(buffer.offset()); + } + + let fns = self.device().fns(); + (fns.v1_0.cmd_bind_vertex_buffers)( + self.handle(), + first_binding, + buffers_vk.len() as u32, + buffers_vk.as_ptr(), + offsets_vk.as_ptr(), + ); + } self } diff --git a/vulkano/src/command_buffer/commands/dynamic_state.rs b/vulkano/src/command_buffer/commands/dynamic_state.rs index 74b0751e..e5a8c673 100644 --- a/vulkano/src/command_buffer/commands/dynamic_state.rs +++ b/vulkano/src/command_buffer/commands/dynamic_state.rs @@ -7,6 +7,10 @@ use crate::{ depth_stencil::{CompareOp, StencilFaces, StencilOp, StencilOps}, input_assembly::PrimitiveTopology, rasterization::{CullMode, DepthBiasState, FrontFace, LineStipple}, + vertex_input::{ + VertexInputAttributeDescription, VertexInputBindingDescription, VertexInputRate, + VertexInputState, + }, viewport::{Scissor, Viewport}, }, DynamicState, @@ -1069,6 +1073,46 @@ impl AutoCommandBufferBuilder { self } + /// Sets the dynamic vertex input for future draw calls. + #[inline] + pub fn set_vertex_input( + &mut self, + vertex_input_state: VertexInputState, + ) -> Result<&mut Self, Box> { + self.validate_set_vertex_input(&vertex_input_state)?; + + unsafe { Ok(self.set_vertex_input_unchecked(vertex_input_state)) } + } + + fn validate_set_vertex_input( + &self, + vertex_input_state: &VertexInputState, + ) -> Result<(), Box> { + self.inner.validate_set_vertex_input(vertex_input_state)?; + + self.validate_graphics_pipeline_fixed_state(DynamicState::VertexInput)?; + + Ok(()) + } + + #[cfg_attr(not(feature = "document_unchecked"), doc(hidden))] + pub unsafe fn set_vertex_input_unchecked( + &mut self, + vertex_input_state: VertexInputState, + ) -> &mut Self { + self.builder_state.vertex_input = Some(vertex_input_state.clone()); + + self.add_command( + "set_vertex_input", + Default::default(), + move |out: &mut UnsafeCommandBufferBuilder| { + out.set_vertex_input_unchecked(&vertex_input_state); + }, + ); + + self + } + /// Sets the dynamic viewports for future draw calls. pub fn set_viewport( &mut self, @@ -2837,6 +2881,121 @@ impl UnsafeCommandBufferBuilder { self } + #[inline] + pub unsafe fn set_vertex_input( + &mut self, + vertex_input_state: &VertexInputState, + ) -> Result<&mut Self, Box> { + self.validate_set_vertex_input(vertex_input_state)?; + + Ok(self.set_vertex_input_unchecked(vertex_input_state)) + } + + fn validate_set_vertex_input( + &self, + vertex_input_state: &VertexInputState, + ) -> Result<(), Box> { + if !(self.device().enabled_features().vertex_input_dynamic_state + || self.device().enabled_features().shader_object) + { + return Err(Box::new(ValidationError { + requires_one_of: RequiresOneOf(&[ + RequiresAllOf(&[Requires::Feature("vertex_input_dynamic_state")]), + RequiresAllOf(&[Requires::Feature("shader_object")]), + ]), + vuids: &["VUID-vkCmdSetVertexInputEXT-None-08546"], + ..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-vkCmdSetVertexInputEXT-commandBuffer-cmdpool"], + ..Default::default() + })); + } + + vertex_input_state + .validate(self.device()) + .map_err(|err| err.add_context("vertex_input_state"))?; + + Ok(()) + } + + #[cfg_attr(not(feature = "document_unchecked"), doc(hidden))] + pub unsafe fn set_vertex_input_unchecked( + &mut self, + vertex_input_state: &VertexInputState, + ) -> &mut Self { + let mut vertex_binding_descriptions_vk: SmallVec<[_; 8]> = SmallVec::new(); + let mut vertex_attribute_descriptions_vk: SmallVec<[_; 8]> = SmallVec::new(); + + let VertexInputState { + bindings, + attributes, + _ne: _, + } = vertex_input_state; + + vertex_binding_descriptions_vk.extend(bindings.iter().map(|(&binding, binding_desc)| { + let &VertexInputBindingDescription { + stride, + input_rate, + _ne: _, + } = binding_desc; + + let divisor = match input_rate { + // VUID-VkVertexInputBindingDescription2EXT-divisor-06227 + VertexInputRate::Vertex => 1, + VertexInputRate::Instance { divisor } => divisor, + }; + + ash::vk::VertexInputBindingDescription2EXT { + binding, + stride, + input_rate: input_rate.into(), + divisor, + ..Default::default() + } + })); + + vertex_attribute_descriptions_vk.extend(attributes.iter().map( + |(&location, attribute_desc)| { + let &VertexInputAttributeDescription { + binding, + format, + offset, + _ne: _, + } = attribute_desc; + + ash::vk::VertexInputAttributeDescription2EXT { + location, + binding, + format: format.into(), + offset, + ..Default::default() + } + }, + )); + + let fns = self.device().fns(); + (fns.ext_vertex_input_dynamic_state.cmd_set_vertex_input_ext)( + self.handle(), + vertex_binding_descriptions_vk.len() as u32, + vertex_binding_descriptions_vk.as_ptr(), + vertex_attribute_descriptions_vk.len() as u32, + vertex_attribute_descriptions_vk.as_ptr(), + ); + + self + } + #[inline] pub unsafe fn set_viewport( &mut self, diff --git a/vulkano/src/command_buffer/commands/pipeline.rs b/vulkano/src/command_buffer/commands/pipeline.rs index 91b6fc0d..0c7b6bab 100644 --- a/vulkano/src/command_buffer/commands/pipeline.rs +++ b/vulkano/src/command_buffer/commands/pipeline.rs @@ -16,8 +16,9 @@ use crate::{ image::{sampler::Sampler, view::ImageView, ImageAspects, ImageLayout, SampleCount}, pipeline::{ graphics::{ - input_assembly::PrimitiveTopology, subpass::PipelineSubpassType, - vertex_input::VertexInputRate, + input_assembly::PrimitiveTopology, + subpass::PipelineSubpassType, + vertex_input::{self, RequiredVertexInputsVUIDs, VertexInputRate}, }, DynamicState, GraphicsPipeline, Pipeline, PipelineLayout, }, @@ -270,7 +271,13 @@ impl AutoCommandBufferBuilder { } } - for (&binding_num, binding_desc) in &pipeline.vertex_input_state().bindings { + 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, binding_desc) in &vertex_input_state.bindings { let vertex_buffer = &self.builder_state.vertex_buffers[&binding_num]; // Per spec: @@ -604,7 +611,13 @@ impl AutoCommandBufferBuilder { } } - for (&binding_num, binding_desc) in &pipeline.vertex_input_state().bindings { + 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, binding_desc) in &vertex_input_state.bindings { let vertex_buffer = &self.builder_state.vertex_buffers[&binding_num]; // Per spec: @@ -2148,7 +2161,45 @@ impl AutoCommandBufferBuilder { })); } } - // DynamicState::VertexInput => todo!(), + DynamicState::VertexInput => { + if let Some(vertex_input_state) = &self.builder_state.vertex_input { + vertex_input::validate_required_vertex_inputs( + &vertex_input_state.attributes, + pipeline.required_vertex_inputs().unwrap(), + RequiredVertexInputsVUIDs { + not_present: vuids!(vuid_type, "Input-07939"), + numeric_type: vuids!(vuid_type, "Input-08734"), + requires32: vuids!(vuid_type, "format-08936"), + requires64: vuids!(vuid_type, "format-08937"), + requires_second_half: vuids!(vuid_type, "None-09203"), + }, + ) + .map_err(|mut err| { + err.problem = format!( + "the currently bound graphics pipeline requires the \ + `DynamicState::VertexInput` dynamic state, but \ + the dynamic vertex input does not meet the requirements of the \ + vertex shader in the pipeline: {}", + err.problem, + ) + .into(); + err + })?; + } else { + return Err(Box::new(ValidationError { + problem: format!( + "the currently bound graphics pipeline requires the \ + `DynamicState::{:?}` dynamic state, but \ + this state was either not set, or it was overwritten by a \ + more recent `bind_pipeline_graphics` command", + dynamic_state + ) + .into(), + vuids: vuids!(vuid_type, "None-04914"), + ..Default::default() + })); + } + } // DynamicState::VertexInputBindingStride => todo!(), DynamicState::Viewport => { let viewport_state = pipeline.viewport_state().unwrap(); @@ -2437,9 +2488,13 @@ impl AutoCommandBufferBuilder { vuid_type: VUIDType, pipeline: &GraphicsPipeline, ) -> Result<(), Box> { - let vertex_input = pipeline.vertex_input_state(); + 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.bindings.keys() { + 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!( @@ -2663,20 +2718,24 @@ impl AutoCommandBufferBuilder { used_resources: &mut Vec<(ResourceUseRef2, Resource)>, pipeline: &GraphicsPipeline, ) { - used_resources.extend(pipeline.vertex_input_state().bindings.iter().map( - |(&binding, _)| { - let vertex_buffer = &self.builder_state.vertex_buffers[&binding]; - ( - ResourceInCommand::VertexBuffer { binding }.into(), - Resource::Buffer { - buffer: vertex_buffer.clone(), - range: 0..vertex_buffer.size(), // TODO: - memory_access: - PipelineStageAccessFlags::VertexAttributeInput_VertexAttributeRead, - }, - ) - }, - )); + 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()); + + used_resources.extend(vertex_input_state.bindings.iter().map(|(&binding, _)| { + let vertex_buffer = &self.builder_state.vertex_buffers[&binding]; + ( + ResourceInCommand::VertexBuffer { binding }.into(), + Resource::Buffer { + buffer: vertex_buffer.clone(), + range: 0..vertex_buffer.size(), // TODO: + memory_access: + PipelineStageAccessFlags::VertexAttributeInput_VertexAttributeRead, + }, + ) + })); } fn add_index_buffer_resources(&self, used_resources: &mut Vec<(ResourceUseRef2, Resource)>) { diff --git a/vulkano/src/pipeline/graphics/mod.rs b/vulkano/src/pipeline/graphics/mod.rs index 8464533e..3d5526d2 100644 --- a/vulkano/src/pipeline/graphics/mod.rs +++ b/vulkano/src/pipeline/graphics/mod.rs @@ -56,7 +56,7 @@ use self::{ rasterization::RasterizationState, subpass::PipelineSubpassType, tessellation::TessellationState, - vertex_input::VertexInputState, + vertex_input::{RequiredVertexInputsVUIDs, VertexInputLocationRequirements, VertexInputState}, viewport::ViewportState, }; use super::{ @@ -75,7 +75,9 @@ use crate::{ rasterization::{CullMode, DepthBiasState}, subpass::PipelineRenderingCreateInfo, tessellation::TessellationDomainOrigin, - vertex_input::VertexInputRate, + vertex_input::{ + VertexInputAttributeDescription, VertexInputBindingDescription, VertexInputRate, + }, }, shader::{ spirv::{ExecutionMode, ExecutionModel, Instruction}, @@ -116,11 +118,7 @@ pub struct GraphicsPipeline { flags: PipelineCreateFlags, // TODO: replace () with an object that describes the shaders in some way. shaders: HashMap, - descriptor_binding_requirements: HashMap<(u32, u32), DescriptorBindingRequirements>, - num_used_descriptor_sets: u32, - fragment_tests_stages: Option, - - vertex_input_state: VertexInputState, + vertex_input_state: Option, input_assembly_state: InputAssemblyState, tessellation_state: Option, viewport_state: Option, @@ -134,7 +132,12 @@ pub struct GraphicsPipeline { discard_rectangle_state: Option, + descriptor_binding_requirements: HashMap<(u32, u32), DescriptorBindingRequirements>, + num_used_descriptor_sets: u32, fixed_state: HashSet, + fragment_tests_stages: Option, + // Note: this is only `Some` if `vertex_input_state` is `None`. + required_vertex_inputs: Option>, } impl GraphicsPipeline { @@ -305,19 +308,36 @@ impl GraphicsPipeline { } = vertex_input_state; vertex_binding_descriptions_vk.extend(bindings.iter().map( - |(&binding, binding_desc)| ash::vk::VertexInputBindingDescription { - binding, - stride: binding_desc.stride, - input_rate: binding_desc.input_rate.into(), + |(&binding, binding_desc)| { + let &VertexInputBindingDescription { + stride, + input_rate, + _ne: _, + } = binding_desc; + + ash::vk::VertexInputBindingDescription { + binding, + stride, + input_rate: input_rate.into(), + } }, )); vertex_attribute_descriptions_vk.extend(attributes.iter().map( - |(&location, attribute_desc)| ash::vk::VertexInputAttributeDescription { - location, - binding: attribute_desc.binding, - format: attribute_desc.format.into(), - offset: attribute_desc.offset, + |(&location, attribute_desc)| { + let &VertexInputAttributeDescription { + binding, + format, + offset, + _ne: _, + } = attribute_desc; + + ash::vk::VertexInputAttributeDescription { + location, + binding, + format: format.into(), + offset, + } }, )); @@ -902,6 +922,7 @@ impl GraphicsPipeline { DescriptorBindingRequirements, > = HashMap::default(); let mut fragment_tests_stages = None; + let mut required_vertex_inputs = None; for stage in &stages { let &PipelineShaderStageCreateInfo { @@ -915,22 +936,33 @@ impl GraphicsPipeline { let spirv = entry_point.module().spirv(); let entry_point_function = spirv.function(entry_point.id()); - if matches!(entry_point_info.execution_model, ExecutionModel::Fragment) { - fragment_tests_stages = Some(FragmentTestsStages::Late); + match entry_point_info.execution_model { + ExecutionModel::Vertex => { + if vertex_input_state.is_none() { + required_vertex_inputs = Some(vertex_input::required_vertex_inputs( + entry_point.module().spirv(), + entry_point.id(), + )); + } + } + ExecutionModel::Fragment => { + fragment_tests_stages = Some(FragmentTestsStages::Late); - for instruction in entry_point_function.execution_modes() { - if let Instruction::ExecutionMode { mode, .. } = *instruction { - match mode { - ExecutionMode::EarlyFragmentTests => { - fragment_tests_stages = Some(FragmentTestsStages::Early); + for instruction in entry_point_function.execution_modes() { + if let Instruction::ExecutionMode { mode, .. } = *instruction { + match mode { + ExecutionMode::EarlyFragmentTests => { + fragment_tests_stages = Some(FragmentTestsStages::Early); + } + ExecutionMode::EarlyAndLateFragmentTestsAMD => { + fragment_tests_stages = Some(FragmentTestsStages::EarlyAndLate); + } + _ => (), } - ExecutionMode::EarlyAndLateFragmentTestsAMD => { - fragment_tests_stages = Some(FragmentTestsStages::EarlyAndLate); - } - _ => (), } } } + _ => (), } for (&loc, reqs) in &entry_point_info.descriptor_binding_requirements { @@ -1022,11 +1054,7 @@ impl GraphicsPipeline { flags, shaders, - descriptor_binding_requirements, - num_used_descriptor_sets, - fragment_tests_stages, - - vertex_input_state: vertex_input_state.unwrap(), // Can be None if there's a mesh shader, but we don't support that yet + 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 tessellation_state, viewport_state, @@ -1040,7 +1068,11 @@ impl GraphicsPipeline { discard_rectangle_state, + descriptor_binding_requirements, + num_used_descriptor_sets, fixed_state, + fragment_tests_stages, + required_vertex_inputs, }) } @@ -1070,8 +1102,8 @@ impl GraphicsPipeline { /// Returns the vertex input state used to create this pipeline. #[inline] - pub fn vertex_input_state(&self) -> &VertexInputState { - &self.vertex_input_state + pub fn vertex_input_state(&self) -> Option<&VertexInputState> { + self.vertex_input_state.as_ref() } /// Returns the input assembly state used to create this pipeline. @@ -1145,6 +1177,14 @@ impl GraphicsPipeline { pub(crate) fn fixed_state(&self) -> &HashSet { &self.fixed_state } + + /// Returns the required vertex inputs. + #[inline] + pub(crate) fn required_vertex_inputs( + &self, + ) -> Option<&HashMap> { + self.required_vertex_inputs.as_ref() + } } impl Pipeline for GraphicsPipeline { @@ -1643,11 +1683,14 @@ impl GraphicsPipelineCreateInfo { _ => (), } - match (vertex_input_state.is_some(), need_vertex_input_state) { + match ( + vertex_input_state.is_some(), + need_vertex_input_state && !dynamic_state.contains(&DynamicState::VertexInput), + ) { (true, false) => { return Err(Box::new(ValidationError { - problem: "the pipeline is not being created with \ - vertex input state, but \ + problem: "the pipeline is not being created with vertex input state, or \ + `dynamic_state` includes `DynamicState::VertexInput`, but \ `vertex_input_state` is `Some`" .into(), ..Default::default() @@ -1655,8 +1698,8 @@ impl GraphicsPipelineCreateInfo { } (false, true) => { return Err(Box::new(ValidationError { - problem: "the pipeline is being created with \ - vertex input state, but \ + problem: "the pipeline is being created with vertex input state, and \ + `dynamic_state` does not include `DynamicState::VertexInput`, but \ `vertex_input_state` is `None`" .into(), vuids: &["VUID-VkGraphicsPipelineCreateInfo-pStages-02097"], @@ -2357,54 +2400,34 @@ impl GraphicsPipelineCreateInfo { */ if let (Some(vertex_stage), Some(vertex_input_state)) = (vertex_stage, vertex_input_state) { - for element in vertex_stage.entry_point.info().input_interface.elements() { - assert!(!element.ty.is_64bit); // TODO: implement - let location_range = - element.location..element.location + element.ty.num_locations(); + let required_vertex_inputs = vertex_input::required_vertex_inputs( + vertex_stage.entry_point.module().spirv(), + vertex_stage.entry_point.id(), + ); - for location in location_range { - let attribute_desc = match vertex_input_state.attributes.get(&location) { - Some(attribute_desc) => attribute_desc, - None => { - return Err(Box::new(ValidationError { - problem: format!( - "the vertex shader has an input variable with location {0}, but \ - `vertex_input_state.attributes` does not contain {0}", - location, - ) - .into(), - vuids: &["VUID-VkGraphicsPipelineCreateInfo-Input-07905"], - ..Default::default() - })); - } - }; - - // TODO: Check component assignments too. Multiple variables can occupy the - // same location but in different components. - - let shader_type = element.ty.base_type; - let attribute_type = attribute_desc - .format - .numeric_format_color() - .unwrap() - .numeric_type(); - - // VUID? - if shader_type != attribute_type { - return Err(Box::new(ValidationError { - problem: format!( - "`vertex_input_state.attributes[{}].format` has a different \ - numeric type than the vertex shader input variable with \ - location {0}", - location, - ) - .into(), - vuids: &["VUID-VkGraphicsPipelineCreateInfo-Input-07905"], - ..Default::default() - })); - } - } - } + vertex_input::validate_required_vertex_inputs( + &vertex_input_state.attributes, + &required_vertex_inputs, + RequiredVertexInputsVUIDs { + not_present: &["VUID-VkGraphicsPipelineCreateInfo-Input-07904"], + numeric_type: &["VUID-VkGraphicsPipelineCreateInfo-Input-08733"], + requires32: &["VUID-VkGraphicsPipelineCreateInfo-pVertexInputState-08929"], + requires64: &["VUID-VkGraphicsPipelineCreateInfo-pVertexInputState-08930"], + requires_second_half: &[ + "VUID-VkGraphicsPipelineCreateInfo-pVertexInputState-09198", + ], + }, + ) + .map_err(|mut err| { + err.problem = format!( + "{}: {}", + "`vertex_input_state` does not meet the requirements \ + of the vertex shader in `stages`", + err.problem, + ) + .into(); + err + })?; } if let (Some(_), Some(_)) = (tessellation_control_stage, tessellation_evaluation_stage) { diff --git a/vulkano/src/pipeline/graphics/vertex_input/definition.rs b/vulkano/src/pipeline/graphics/vertex_input/definition.rs index 56ec598d..c1ad461e 100644 --- a/vulkano/src/pipeline/graphics/vertex_input/definition.rs +++ b/vulkano/src/pipeline/graphics/vertex_input/definition.rs @@ -27,6 +27,7 @@ unsafe impl VertexDefinition for &[VertexBufferDescription] { VertexInputBindingDescription { stride: buffer.stride, input_rate: buffer.input_rate, + ..Default::default() }, ) }); @@ -91,6 +92,7 @@ unsafe impl VertexDefinition for &[VertexBufferDescription] { binding, format: infos.format, offset: offset as u32, + ..Default::default() }, )); offset += block_size; diff --git a/vulkano/src/pipeline/graphics/vertex_input/mod.rs b/vulkano/src/pipeline/graphics/vertex_input/mod.rs index 8765a1af..b478e90f 100644 --- a/vulkano/src/pipeline/graphics/vertex_input/mod.rs +++ b/vulkano/src/pipeline/graphics/vertex_input/mod.rs @@ -99,10 +99,15 @@ pub use self::{ }; use crate::{ device::Device, - format::{Format, FormatFeatures}, + format::{Format, FormatFeatures, NumericType}, + shader::{ + reflect::get_constant, + spirv::{Decoration, Id, Instruction, Spirv, StorageClass}, + }, DeviceSize, Requires, RequiresAllOf, RequiresOneOf, ValidationError, }; use ahash::HashMap; +use std::collections::hash_map::Entry; mod buffers; mod collection; @@ -185,15 +190,33 @@ impl VertexInputState { problem: "the length exceeds the `max_vertex_input_bindings` limit".into(), vuids: &[ "VUID-VkPipelineVertexInputStateCreateInfo-vertexBindingDescriptionCount-00613", + "VUID-vkCmdSetVertexInputEXT-vertexBindingDescriptionCount-04791", ], ..Default::default() })); } // VUID-VkPipelineVertexInputStateCreateInfo-pVertexBindingDescriptions-00616 + // VUID-vkCmdSetVertexInputEXT-pVertexBindingDescriptions-04794 // Ensured by HashMap. for (&binding, binding_desc) in bindings { + if binding >= properties.max_vertex_input_bindings { + return Err(Box::new(ValidationError { + context: format!("bindings[{}]", binding).into(), + problem: format!( + "the binding {} exceeds the `max_vertex_input_bindings` limit", + binding + ) + .into(), + vuids: &[ + "VUID-VkVertexInputBindingDescription-binding-00618", + "VUID-VkVertexInputBindingDescription2EXT-binding-04796", + ], + ..Default::default() + })); + } + binding_desc .validate(device) .map_err(|err| err.add_context(format!("bindings[{}]", binding)))?; @@ -205,12 +228,29 @@ impl VertexInputState { problem: "the length exceeds the `max_vertex_input_attributes` limit".into(), vuids: &[ "VUID-VkPipelineVertexInputStateCreateInfo-vertexAttributeDescriptionCount-00614", + "VUID-vkCmdSetVertexInputEXT-vertexAttributeDescriptionCount-04792", ], ..Default::default() })); } for (&location, attribute_desc) in attributes { + if location >= properties.max_vertex_input_attributes { + return Err(Box::new(ValidationError { + context: format!("attributes[{}]", location).into(), + problem: format!( + "the location {} exceeds the `max_vertex_input_attributes` limit", + location + ) + .into(), + vuids: &[ + "VUID-VkVertexInputAttributeDescription-location-00620", + "VUID-VkVertexInputAttributeDescription2EXT-location-06228", + ], + ..Default::default() + })); + } + attribute_desc .validate(device) .map_err(|err| err.add_context(format!("attributes[{}]", location)))?; @@ -219,21 +259,9 @@ impl VertexInputState { binding, format, offset, + _ne: _, } = attribute_desc; - if location > properties.max_vertex_input_attributes { - return Err(Box::new(ValidationError { - context: "attributes".into(), - problem: format!( - "the location {} exceeds the `max_vertex_input_attributes` limit", - location - ) - .into(), - vuids: &["VUID-VkVertexInputAttributeDescription-location-00620"], - ..Default::default() - })); - } - let binding_desc = bindings.get(&binding).ok_or_else(|| { Box::new(ValidationError { problem: format!( @@ -241,7 +269,10 @@ impl VertexInputState { binding ) .into(), - vuids: &["VUID-VkPipelineVertexInputStateCreateInfo-binding-00615"], + vuids: &[ + "VUID-VkPipelineVertexInputStateCreateInfo-binding-00615", + "VUID-vkCmdSetVertexInputEXT-binding-04793", + ], ..Default::default() }) })?; @@ -263,7 +294,10 @@ impl VertexInputState { requires_one_of: RequiresOneOf(&[RequiresAllOf(&[Requires::Feature( "vertex_attribute_access_beyond_stride", )])]), - vuids: &["VUID-VkVertexInputAttributeDescription-vertexAttributeAccessBeyondStride-04457"], + vuids: &[ + "VUID-VkVertexInputAttributeDescription-vertexAttributeAccessBeyondStride-04457", + "VUID-VkVertexInputAttributeDescription2EXT-vertexAttributeAccessBeyondStride-04806", + ], ..Default::default() })); } @@ -285,7 +319,10 @@ impl VertexInputState { location - 1, location, ) .into(), - vuids: &["VUID-VkPipelineVertexInputStateCreateInfo-pVertexAttributeDescriptions-00617"], + vuids: &[ + "VUID-VkPipelineVertexInputStateCreateInfo-pVertexAttributeDescriptions-00617", + "VUID-vkCmdSetVertexInputEXT-pVertexAttributeDescriptions-04795", + ], ..Default::default() })); } @@ -312,15 +349,36 @@ pub struct VertexInputBindingDescription { /// The number of bytes from the start of one element in the vertex buffer to the start of the /// next element. This can be simply the size of the data in each element, but larger strides /// are possible. + /// + /// The default value is `0`, which must be overridden. pub stride: u32, /// How often the vertex input should advance to the next element. + /// + /// The default value is [`VertexInputRate::Vertex`]. pub input_rate: VertexInputRate, + + pub _ne: crate::NonExhaustive, +} + +impl Default for VertexInputBindingDescription { + #[inline] + fn default() -> Self { + Self { + stride: 0, + input_rate: VertexInputRate::Vertex, + _ne: crate::NonExhaustive(()), + } + } } impl VertexInputBindingDescription { pub(crate) fn validate(&self, device: &Device) -> Result<(), Box> { - let &Self { stride, input_rate } = self; + let &Self { + stride, + input_rate, + _ne: _, + } = self; let properties = device.physical_device().properties(); @@ -328,7 +386,10 @@ impl VertexInputBindingDescription { return Err(Box::new(ValidationError { context: "stride".into(), problem: "exceeds the `max_vertex_input_binding_stride` limit".into(), - vuids: &["VUID-VkVertexInputBindingDescription-stride-00619"], + vuids: &[ + "VUID-VkVertexInputBindingDescription-stride-00619", + "VUID-VkVertexInputBindingDescription2EXT-stride-04797", + ], ..Default::default() })); } @@ -364,7 +425,10 @@ impl VertexInputBindingDescription { requires_one_of: RequiresOneOf(&[RequiresAllOf(&[Requires::Feature( "vertex_attribute_instance_rate_divisor", )])]), - vuids: &["VUID-VkVertexInputBindingDivisorDescriptionEXT-vertexAttributeInstanceRateDivisor-02229"], + vuids: &[ + "VUID-VkVertexInputBindingDivisorDescriptionEXT-vertexAttributeInstanceRateDivisor-02229", + "VUID-VkVertexInputBindingDescription2EXT-divisor-04799", + ], })); } @@ -380,7 +444,10 @@ impl VertexInputBindingDescription { requires_one_of: RequiresOneOf(&[RequiresAllOf(&[Requires::Feature( "vertex_attribute_instance_rate_zero_divisor", )])]), - vuids: &["VUID-VkVertexInputBindingDivisorDescriptionEXT-vertexAttributeInstanceRateZeroDivisor-02228"], + vuids: &[ + "VUID-VkVertexInputBindingDivisorDescriptionEXT-vertexAttributeInstanceRateZeroDivisor-02228", + "VUID-VkVertexInputBindingDescription2EXT-divisor-04798", + ], })); } @@ -390,7 +457,10 @@ impl VertexInputBindingDescription { problem: "is `VertexInputRate::Instance`, and \ its `divisor` value exceeds the `max_vertex_attrib_divisor` limit" .into(), - vuids: &["VUID-VkVertexInputBindingDivisorDescriptionEXT-divisor-01870"], + vuids: &[ + "VUID-VkVertexInputBindingDivisorDescriptionEXT-divisor-01870", + "VUID-VkVertexInputBindingDescription2EXT-divisor-06226", + ], ..Default::default() })); } @@ -406,9 +476,13 @@ impl VertexInputBindingDescription { #[derive(Clone, Copy, Debug)] pub struct VertexInputAttributeDescription { /// The vertex buffer binding number that this attribute should take its data from. + /// + /// The default value is `0`. pub binding: u32, /// The size and type of the vertex data. + /// + /// The default value is [`Format::UNDEFINED`], which must be overridden. pub format: Format, /// Number of bytes between the start of a vertex buffer element and the location of attribute. @@ -418,7 +492,23 @@ pub struct VertexInputAttributeDescription { /// `binding`, the /// [`vertex_attribute_access_beyond_stride`](crate::device::Features::vertex_attribute_access_beyond_stride) /// feature must be enabled on the device. + /// + /// The default value is `0`. pub offset: u32, + + pub _ne: crate::NonExhaustive, +} + +impl Default for VertexInputAttributeDescription { + #[inline] + fn default() -> Self { + Self { + binding: 0, + format: Format::UNDEFINED, + offset: 0, + _ne: crate::NonExhaustive(()), + } + } } impl VertexInputAttributeDescription { @@ -427,20 +517,26 @@ impl VertexInputAttributeDescription { binding, format, offset, + _ne: _, } = self; let properties = device.physical_device().properties(); format.validate_device(device).map_err(|err| { - err.add_context("format") - .set_vuids(&["VUID-VkVertexInputAttributeDescription-format-parameter"]) + err.add_context("format").set_vuids(&[ + "VUID-VkVertexInputAttributeDescription-format-parameter", + "VUID-VkVertexInputAttributeDescription2EXT-format-parameter", + ]) })?; if binding > properties.max_vertex_input_bindings { return Err(Box::new(ValidationError { context: "binding".into(), problem: "exceeds the `max_vertex_input_bindings` limit".into(), - vuids: &["VUID-VkVertexInputAttributeDescription-binding-00621"], + vuids: &[ + "VUID-VkVertexInputAttributeDescription-binding-00621", + "VUID-VkVertexInputAttributeDescription2EXT-binding-06229", + ], ..Default::default() })); } @@ -449,7 +545,10 @@ impl VertexInputAttributeDescription { return Err(Box::new(ValidationError { context: "offset".into(), problem: "exceeds the `max_vertex_input_attribute_offset` limit".into(), - vuids: &["VUID-VkVertexInputAttributeDescription-offset-00622"], + vuids: &[ + "VUID-VkVertexInputAttributeDescription-offset-00622", + "VUID-VkVertexInputAttributeDescription2EXT-offset-06230", + ], ..Default::default() })); } @@ -466,7 +565,10 @@ impl VertexInputAttributeDescription { context: "format".into(), problem: "the format features do not include `FormatFeatures::VERTEX_BUFFER`" .into(), - vuids: &["VUID-VkVertexInputAttributeDescription-format-00623"], + vuids: &[ + "VUID-VkVertexInputAttributeDescription-format-00623", + "VUID-VkVertexInputAttributeDescription2EXT-format-04805", + ], ..Default::default() })); } @@ -504,3 +606,402 @@ impl From for ash::vk::VertexInputRate { } } } + +#[derive(Clone, Copy, Debug)] +pub(crate) struct VertexInputLocationRequirements { + pub(crate) numeric_type: NumericType, + pub(crate) width: VertexInputLocationWidth, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub(crate) enum VertexInputLocationWidth { + /// The shader requires a 32-bit or smaller value at this location. + Requires32, + + /// The shader requires a 64-bit value at this location. + /// The boolean indicates whether the shader requires a format that fills the second half + /// of the location. + Requires64 { requires_second_half: bool }, +} + +pub(crate) fn required_vertex_inputs( + spirv: &Spirv, + entry_point_id: Id, +) -> HashMap { + let interface = match spirv.function(entry_point_id).entry_point() { + Some(Instruction::EntryPoint { interface, .. }) => interface, + _ => unreachable!(), + }; + + let mut required_vertex_inputs = HashMap::default(); + + for &variable_id in interface { + let variable_id_info = spirv.id(variable_id); + let pointer_type_id = match *variable_id_info.instruction() { + Instruction::Variable { + result_type_id, + storage_class: StorageClass::Input, + .. + } => result_type_id, + _ => continue, + }; + let pointer_type_id_info = spirv.id(pointer_type_id); + let type_id = match *pointer_type_id_info.instruction() { + Instruction::TypePointer { ty, .. } => ty, + _ => unreachable!(), + }; + + let mut variable_location = None; + let mut variable_component = 0; + + for instruction in variable_id_info.decorations() { + if let Instruction::Decorate { ref decoration, .. } = *instruction { + match *decoration { + Decoration::Location { location } => variable_location = Some(location), + Decoration::Component { component } => variable_component = component, + _ => (), + } + } + } + + if let Some(variable_location) = variable_location { + add_type_location( + &mut required_vertex_inputs, + spirv, + variable_location, + variable_component, + type_id, + ); + } else { + let block_type_id_info = spirv.id(type_id); + let member_types = match block_type_id_info.instruction() { + Instruction::TypeStruct { member_types, .. } => member_types, + _ => continue, + }; + + for (&type_id, member_info) in member_types.iter().zip(block_type_id_info.members()) { + let mut member_location = None; + let mut member_component = 0; + + for instruction in member_info.decorations() { + if let Instruction::MemberDecorate { ref decoration, .. } = *instruction { + match *decoration { + Decoration::Location { location } => member_location = Some(location), + Decoration::Component { component } => member_component = component, + _ => (), + } + } + } + + if let Some(member_location) = member_location { + add_type_location( + &mut required_vertex_inputs, + spirv, + member_location, + member_component, + type_id, + ); + } + } + } + } + + required_vertex_inputs +} + +fn add_type_location( + required_vertex_inputs: &mut HashMap, + spirv: &Spirv, + mut location: u32, + mut component: u32, + type_id: Id, +) -> (u32, u32) { + debug_assert!(component < 4); + + let mut add_scalar = |numeric_type: NumericType, width: u32| -> (u32, u32) { + if width > 32 { + debug_assert!(component & 1 == 0); + let half_index = component as usize / 2; + + match required_vertex_inputs.entry(location) { + Entry::Occupied(mut entry) => { + let requirements = entry.get_mut(); + debug_assert_eq!(requirements.numeric_type, numeric_type); + + match &mut requirements.width { + VertexInputLocationWidth::Requires32 => unreachable!(), + VertexInputLocationWidth::Requires64 { + requires_second_half, + } => { + if component == 2 { + debug_assert!(!*requires_second_half); + *requires_second_half = true; + } + } + } + } + Entry::Vacant(entry) => { + let mut required_halves = [false; 2]; + required_halves[half_index] = true; + entry.insert(VertexInputLocationRequirements { + numeric_type, + width: VertexInputLocationWidth::Requires64 { + requires_second_half: component == 2, + }, + }); + } + } + + (1, 2) + } else { + match required_vertex_inputs.entry(location) { + Entry::Occupied(entry) => { + let requirements = *entry.get(); + debug_assert_eq!(requirements.numeric_type, numeric_type); + debug_assert_eq!(requirements.width, VertexInputLocationWidth::Requires32); + } + Entry::Vacant(entry) => { + entry.insert(VertexInputLocationRequirements { + numeric_type, + width: VertexInputLocationWidth::Requires32, + }); + } + } + + (1, 1) + } + }; + + match *spirv.id(type_id).instruction() { + Instruction::TypeInt { + width, signedness, .. + } => { + let numeric_type = if signedness == 1 { + NumericType::Int + } else { + NumericType::Uint + }; + + add_scalar(numeric_type, width) + } + Instruction::TypeFloat { width, .. } => add_scalar(NumericType::Float, width), + Instruction::TypeVector { + component_type, + component_count, + .. + } => { + let mut total_locations_added = 1; + + for _ in 0..component_count { + // Overflow into next location + if component == 4 { + component = 0; + location += 1; + total_locations_added += 1; + } else { + debug_assert!(component < 4); + } + + let (_, components_added) = add_type_location( + required_vertex_inputs, + spirv, + location, + component, + component_type, + ); + component += components_added; + } + + (total_locations_added, 0) + } + Instruction::TypeMatrix { + column_type, + column_count, + .. + } => { + let mut total_locations_added = 0; + + for _ in 0..column_count { + let (locations_added, _) = add_type_location( + required_vertex_inputs, + spirv, + location, + component, + column_type, + ); + location += locations_added; + total_locations_added += locations_added; + } + + (total_locations_added, 0) + } + Instruction::TypeArray { + element_type, + length, + .. + } => { + let length = get_constant(spirv, length).unwrap(); + let mut total_locations_added = 0; + + for _ in 0..length { + let (locations_added, _) = add_type_location( + required_vertex_inputs, + spirv, + location, + component, + element_type, + ); + location += locations_added; + total_locations_added += locations_added; + } + + (total_locations_added, 0) + } + Instruction::TypeStruct { + ref member_types, .. + } => { + let mut total_locations_added = 0; + + for &member_type in member_types { + let (locations_added, _) = add_type_location( + required_vertex_inputs, + spirv, + location, + component, + member_type, + ); + location += locations_added; + total_locations_added += locations_added; + } + + (total_locations_added, 0) + } + _ => unimplemented!(), + } +} + +pub(crate) struct RequiredVertexInputsVUIDs { + pub(crate) not_present: &'static [&'static str], + pub(crate) numeric_type: &'static [&'static str], + pub(crate) requires32: &'static [&'static str], + pub(crate) requires64: &'static [&'static str], + pub(crate) requires_second_half: &'static [&'static str], +} + +pub(crate) fn validate_required_vertex_inputs( + attribute_descs: &HashMap, + required_vertex_inputs: &HashMap, + vuids: RequiredVertexInputsVUIDs, +) -> Result<(), Box> { + for (&location, location_info) in required_vertex_inputs { + let (is_previous, attribute_desc) = (attribute_descs.get(&location).map(|d| (false, d))) + .or_else(|| { + // If the previous location has at least three 64-bit components, + // then it extends into the current location, so try that instead. + location.checked_sub(1).and_then(|location| { + attribute_descs + .get(&location) + .filter(|attribute_desc| { + attribute_desc + .format + .components() + .starts_with(&[64, 64, 64]) + }) + .map(|d| (true, d)) + }) + }) + .ok_or_else(|| { + Box::new(ValidationError { + problem: format!( + "the vertex shader has an input variable with location {0}, but \ + the vertex input attributes do not contain {0}", + location, + ) + .into(), + vuids: vuids.not_present, + ..Default::default() + }) + })?; + + let attribute_numeric_type = attribute_desc + .format + .numeric_format_color() + .unwrap() + .numeric_type(); + + if attribute_numeric_type != location_info.numeric_type { + return Err(Box::new(ValidationError { + problem: format!( + "the numeric type of the format of vertex input attribute {0} ({1:?}) \ + does not equal the numeric type of the vertex shader input variable with \ + location {0} ({2:?})", + location, attribute_numeric_type, location_info.numeric_type, + ) + .into(), + vuids: vuids.numeric_type, + ..Default::default() + })); + } + + let attribute_components = attribute_desc.format.components(); + + // 64-bit in the shader must match with 64-bit in the attribute. + match location_info.width { + VertexInputLocationWidth::Requires32 => { + if attribute_components[0] > 32 { + return Err(Box::new(ValidationError { + problem: format!( + "the vertex shader input variable location {0} requires a non-64-bit \ + format, but the format of vertex input attribute {0} is 64-bit", + location, + ) + .into(), + vuids: vuids.requires32, + ..Default::default() + })); + } + } + VertexInputLocationWidth::Requires64 { + requires_second_half, + } => { + if attribute_components[0] <= 32 { + return Err(Box::new(ValidationError { + problem: format!( + "the vertex shader input variable location {0} requires a 64-bit \ + format, but the format of vertex input attribute {0} is not 64-bit", + location, + ) + .into(), + vuids: vuids.requires64, + ..Default::default() + })); + } + + // For 64-bit values, there are no default values for missing components. + // If the shader uses the 64-bit value in the second half of the location, then + // the attribute must provide it. + if requires_second_half { + let second_half_attribute_component = if is_previous { 3 } else { 1 }; + + if attribute_components[second_half_attribute_component] != 64 { + return Err(Box::new(ValidationError { + problem: format!( + "the vertex shader input variable location {0} requires a format \ + with at least {1} 64-bit components, but the format of \ + vertex input attribute {0} contains only {2} components", + location, + second_half_attribute_component + 1, + attribute_components.into_iter().filter(|&c| c != 0).count(), + ) + .into(), + vuids: vuids.requires_second_half, + ..Default::default() + })); + } + } + } + } + } + + Ok(()) +} diff --git a/vulkano/src/pipeline/mod.rs b/vulkano/src/pipeline/mod.rs index 7eb6ac90..0189417b 100644 --- a/vulkano/src/pipeline/mod.rs +++ b/vulkano/src/pipeline/mod.rs @@ -1251,12 +1251,15 @@ vulkan_enum! { RequiresAllOf([DeviceExtension(ext_line_rasterization)]), ]), - /* TODO: enable - // TODO: document + /// The `Option` variant of + /// [`GraphicsPipelineCreateInfo::vertex_input_state`](crate::pipeline::graphics::GraphicsPipelineCreateInfo::vertex_input_state). + /// + /// Set with + /// [`set_vertex_input`](crate::command_buffer::AutoCommandBufferBuilder::set_vertex_input). VertexInput = VERTEX_INPUT_EXT RequiresOneOf([ RequiresAllOf([DeviceExtension(ext_vertex_input_dynamic_state)]), - ]), */ + ]), /// The value of /// [`TessellationState::patch_control_points`](crate::pipeline::graphics::tessellation::TessellationState::patch_control_points). diff --git a/vulkano/src/shader/reflect.rs b/vulkano/src/shader/reflect.rs index 46f2971b..a610dc74 100644 --- a/vulkano/src/shader/reflect.rs +++ b/vulkano/src/shader/reflect.rs @@ -1444,6 +1444,17 @@ fn is_builtin(spirv: &Spirv, id: Id) -> bool { } } +pub(crate) fn get_constant(spirv: &Spirv, id: Id) -> Option { + match spirv.id(id).instruction() { + Instruction::Constant { value, .. } => match value.len() { + 1 => Some(value[0] as u64), + 2 => Some(value[0] as u64 | (value[1] as u64) << 32), + _ => panic!("constant {} is larger than 64 bits", id), + }, + _ => None, + } +} + #[cfg(test)] mod tests { use super::{HashMap, PushConstantRange, ShaderStages, Version};