// Copyright (c) 2021 The Vulkano developers // Licensed under the Apache License, Version 2.0 // or the MIT // license , // at your option. All files in the project carrying such // notice may not be copied, modified, or distributed except // according to those terms. //! Extraction of information from SPIR-V modules, that is needed by the rest of Vulkano. use super::{DescriptorBindingRequirements, FragmentShaderExecution, FragmentTestsStages}; use crate::{ descriptor_set::layout::DescriptorType, image::view::ImageViewType, pipeline::layout::PushConstantRange, shader::{ spirv::{ Capability, Decoration, Dim, ExecutionMode, ExecutionModel, Id, Instruction, Spirv, StorageClass, }, DescriptorIdentifier, DescriptorRequirements, EntryPointInfo, GeometryShaderExecution, GeometryShaderInput, ShaderExecution, ShaderInterface, ShaderInterfaceEntry, ShaderInterfaceEntryType, ShaderScalarType, ShaderStage, SpecializationConstantRequirements, }, DeviceSize, }; use ahash::{HashMap, HashSet}; use std::borrow::Cow; /// Returns an iterator of the capabilities used by `spirv`. #[inline] pub fn spirv_capabilities(spirv: &Spirv) -> impl Iterator { spirv .iter_capability() .filter_map(|instruction| match instruction { Instruction::Capability { capability } => Some(capability), _ => None, }) } /// Returns an iterator of the extensions used by `spirv`. #[inline] pub fn spirv_extensions(spirv: &Spirv) -> impl Iterator { spirv .iter_extension() .filter_map(|instruction| match instruction { Instruction::Extension { name } => Some(name.as_str()), _ => None, }) } /// Returns an iterator over all entry points in `spirv`, with information about the entry point. #[inline] pub fn entry_points( spirv: &Spirv, ) -> impl Iterator + '_ { let interface_variables = interface_variables(spirv); spirv.iter_entry_point().filter_map(move |instruction| { let (execution_model, function_id, entry_point_name, interface) = match instruction { Instruction::EntryPoint { execution_model, entry_point, name, interface, .. } => (*execution_model, *entry_point, name, interface), _ => return None, }; let execution = shader_execution(spirv, execution_model, function_id); let stage = ShaderStage::from(execution); let descriptor_binding_requirements = inspect_entry_point( &interface_variables.descriptor_binding, spirv, stage, function_id, ); let push_constant_requirements = push_constant_requirements(spirv, stage); let specialization_constant_requirements = specialization_constant_requirements(spirv); let input_interface = shader_interface( spirv, interface, StorageClass::Input, matches!( execution_model, ExecutionModel::TessellationControl | ExecutionModel::TessellationEvaluation | ExecutionModel::Geometry ), ); let output_interface = shader_interface( spirv, interface, StorageClass::Output, matches!(execution_model, ExecutionModel::TessellationControl), ); Some(( entry_point_name.clone(), execution_model, EntryPointInfo { execution, descriptor_binding_requirements, push_constant_requirements, specialization_constant_requirements, input_interface, output_interface, }, )) }) } /// Extracts the `ShaderExecution` for the entry point `function_id` from `spirv`. fn shader_execution( spirv: &Spirv, execution_model: ExecutionModel, function_id: Id, ) -> ShaderExecution { match execution_model { ExecutionModel::Vertex => ShaderExecution::Vertex, ExecutionModel::TessellationControl => ShaderExecution::TessellationControl, ExecutionModel::TessellationEvaluation => ShaderExecution::TessellationEvaluation, ExecutionModel::Geometry => { let mut input = None; for instruction in spirv.iter_execution_mode() { let mode = match instruction { Instruction::ExecutionMode { entry_point, mode, .. } if *entry_point == function_id => mode, _ => continue, }; match mode { ExecutionMode::InputPoints => { input = Some(GeometryShaderInput::Points); } ExecutionMode::InputLines => { input = Some(GeometryShaderInput::Lines); } ExecutionMode::InputLinesAdjacency => { input = Some(GeometryShaderInput::LinesWithAdjacency); } ExecutionMode::Triangles => { input = Some(GeometryShaderInput::Triangles); } ExecutionMode::InputTrianglesAdjacency => { input = Some(GeometryShaderInput::TrianglesWithAdjacency); } _ => (), } } ShaderExecution::Geometry(GeometryShaderExecution { input: input .expect("Geometry shader does not have an input primitive ExecutionMode"), }) } ExecutionModel::Fragment => { let mut fragment_tests_stages = FragmentTestsStages::Late; for instruction in spirv.iter_execution_mode() { let mode = match instruction { Instruction::ExecutionMode { entry_point, mode, .. } if *entry_point == function_id => mode, _ => continue, }; #[allow(clippy::single_match)] match mode { ExecutionMode::EarlyFragmentTests => { fragment_tests_stages = FragmentTestsStages::Early; } /*ExecutionMode::EarlyAndLateFragmentTestsAMD => { fragment_tests_stages = FragmentTestsStages::EarlyAndLate; }*/ _ => (), } } ShaderExecution::Fragment(FragmentShaderExecution { fragment_tests_stages, }) } ExecutionModel::GLCompute => ShaderExecution::Compute, ExecutionModel::RayGenerationKHR => ShaderExecution::RayGeneration, ExecutionModel::IntersectionKHR => ShaderExecution::Intersection, ExecutionModel::AnyHitKHR => ShaderExecution::AnyHit, ExecutionModel::ClosestHitKHR => ShaderExecution::ClosestHit, ExecutionModel::MissKHR => ShaderExecution::Miss, ExecutionModel::CallableKHR => ShaderExecution::Callable, ExecutionModel::TaskNV => ShaderExecution::Task, ExecutionModel::MeshNV => ShaderExecution::Mesh, ExecutionModel::Kernel => todo!(), } } #[derive(Clone, Debug, Default)] struct InterfaceVariables { descriptor_binding: HashMap, } // See also section 14.5.2 of the Vulkan specs: Descriptor Set Interface. #[derive(Clone, Debug)] struct DescriptorBindingVariable { set: u32, binding: u32, reqs: DescriptorBindingRequirements, } fn interface_variables(spirv: &Spirv) -> InterfaceVariables { let mut variables = InterfaceVariables::default(); for instruction in spirv.iter_global() { if let Instruction::Variable { result_id, result_type_id: _, storage_class, .. } = instruction { match storage_class { StorageClass::StorageBuffer | StorageClass::Uniform | StorageClass::UniformConstant => { variables.descriptor_binding.insert( *result_id, descriptor_binding_requirements_of(spirv, *result_id), ); } _ => (), } } } variables } fn inspect_entry_point( global: &HashMap, spirv: &Spirv, stage: ShaderStage, entry_point: Id, ) -> HashMap<(u32, u32), DescriptorBindingRequirements> { struct Context<'a> { global: &'a HashMap, spirv: &'a Spirv, stage: ShaderStage, inspected_functions: HashSet, result: HashMap, } impl<'a> Context<'a> { fn instruction_chain( &mut self, chain: [fn(&Spirv, Id) -> Option; N], id: Id, ) -> Option<(&mut DescriptorBindingVariable, Option)> { let id = chain .into_iter() .try_fold(id, |id, func| func(self.spirv, id))?; if let Some(variable) = self.global.get(&id) { // Variable was accessed without an access chain, return with index 0. let variable = self.result.entry(id).or_insert_with(|| variable.clone()); variable.reqs.stages = self.stage.into(); return Some((variable, Some(0))); } let (id, indexes) = match *self.spirv.id(id).instruction() { Instruction::AccessChain { base, ref indexes, .. } => (base, indexes), _ => return None, }; if let Some(variable) = self.global.get(&id) { // Variable was accessed with an access chain. // Retrieve index from instruction if it's a constant value. // TODO: handle a `None` index too? let index = match *self.spirv.id(*indexes.first().unwrap()).instruction() { Instruction::Constant { ref value, .. } => Some(value[0]), _ => None, }; let variable = self.result.entry(id).or_insert_with(|| variable.clone()); variable.reqs.stages = self.stage.into(); return Some((variable, index)); } None } fn inspect_entry_point_r(&mut self, function: Id) { fn desc_reqs( descriptor_variable: Option<(&mut DescriptorBindingVariable, Option)>, ) -> Option<&mut DescriptorRequirements> { descriptor_variable .map(|(variable, index)| variable.reqs.descriptors.entry(index).or_default()) } fn inst_image_texel_pointer(spirv: &Spirv, id: Id) -> Option { match *spirv.id(id).instruction() { Instruction::ImageTexelPointer { image, .. } => Some(image), _ => None, } } fn inst_load(spirv: &Spirv, id: Id) -> Option { match *spirv.id(id).instruction() { Instruction::Load { pointer, .. } => Some(pointer), _ => None, } } fn inst_sampled_image(spirv: &Spirv, id: Id) -> Option { match *spirv.id(id).instruction() { Instruction::SampledImage { sampler, .. } => Some(sampler), _ => Some(id), } } self.inspected_functions.insert(function); let mut in_function = false; for instruction in self.spirv.instructions() { if !in_function { match *instruction { Instruction::Function { result_id, .. } if result_id == function => { in_function = true; } _ => {} } } else { let stage = self.stage; match *instruction { Instruction::AtomicLoad { pointer, .. } => { // Storage buffer if let Some(desc_reqs) = desc_reqs(self.instruction_chain([], pointer)) { desc_reqs.memory_read = stage.into(); } // Storage image if let Some(desc_reqs) = desc_reqs( self.instruction_chain([inst_image_texel_pointer], pointer), ) { desc_reqs.memory_read = stage.into(); desc_reqs.storage_image_atomic = true; } } Instruction::AtomicStore { pointer, .. } => { // Storage buffer if let Some(desc_reqs) = desc_reqs(self.instruction_chain([], pointer)) { desc_reqs.memory_write = stage.into(); } // Storage image if let Some(desc_reqs) = desc_reqs( self.instruction_chain([inst_image_texel_pointer], pointer), ) { desc_reqs.memory_write = stage.into(); desc_reqs.storage_image_atomic = true; } } Instruction::AtomicExchange { pointer, .. } | Instruction::AtomicCompareExchange { pointer, .. } | Instruction::AtomicCompareExchangeWeak { pointer, .. } | Instruction::AtomicIIncrement { pointer, .. } | Instruction::AtomicIDecrement { pointer, .. } | Instruction::AtomicIAdd { pointer, .. } | Instruction::AtomicISub { pointer, .. } | Instruction::AtomicSMin { pointer, .. } | Instruction::AtomicUMin { pointer, .. } | Instruction::AtomicSMax { pointer, .. } | Instruction::AtomicUMax { pointer, .. } | Instruction::AtomicAnd { pointer, .. } | Instruction::AtomicOr { pointer, .. } | Instruction::AtomicXor { pointer, .. } | Instruction::AtomicFlagTestAndSet { pointer, .. } | Instruction::AtomicFlagClear { pointer, .. } | Instruction::AtomicFMinEXT { pointer, .. } | Instruction::AtomicFMaxEXT { pointer, .. } | Instruction::AtomicFAddEXT { pointer, .. } => { // Storage buffer if let Some(desc_reqs) = desc_reqs(self.instruction_chain([], pointer)) { desc_reqs.memory_read = stage.into(); desc_reqs.memory_write = stage.into(); } // Storage image if let Some(desc_reqs) = desc_reqs( self.instruction_chain([inst_image_texel_pointer], pointer), ) { desc_reqs.memory_read = stage.into(); desc_reqs.memory_write = stage.into(); desc_reqs.storage_image_atomic = true; } } Instruction::CopyMemory { target, source, .. } => { self.instruction_chain([], target); self.instruction_chain([], source); } Instruction::CopyObject { operand, .. } => { self.instruction_chain([], operand); } Instruction::ExtInst { ref operands, .. } => { // We don't know which extended instructions take pointers, // so we must interpret every operand as a pointer. for &operand in operands { self.instruction_chain([], operand); } } Instruction::FunctionCall { function, ref arguments, .. } => { // Rather than trying to figure out the type of each argument, we just // try all of them as pointers. for &argument in arguments { self.instruction_chain([], argument); } if !self.inspected_functions.contains(&function) { self.inspect_entry_point_r(function); } } Instruction::FunctionEnd => return, Instruction::ImageGather { sampled_image, image_operands, .. } | Instruction::ImageSparseGather { sampled_image, image_operands, .. } => { if let Some(desc_reqs) = desc_reqs(self.instruction_chain( [inst_sampled_image, inst_load], sampled_image, )) { desc_reqs.memory_read = stage.into(); desc_reqs.sampler_no_ycbcr_conversion = true; if image_operands.as_ref().map_or(false, |image_operands| { image_operands.bias.is_some() || image_operands.const_offset.is_some() || image_operands.offset.is_some() }) { desc_reqs.sampler_no_unnormalized_coordinates = true; } } } Instruction::ImageDrefGather { sampled_image, .. } | Instruction::ImageSparseDrefGather { sampled_image, .. } => { if let Some(desc_reqs) = desc_reqs(self.instruction_chain( [inst_sampled_image, inst_load], sampled_image, )) { desc_reqs.memory_read = stage.into(); desc_reqs.sampler_no_unnormalized_coordinates = true; desc_reqs.sampler_no_ycbcr_conversion = true; } } Instruction::ImageSampleImplicitLod { sampled_image, image_operands, .. } | Instruction::ImageSampleProjImplicitLod { sampled_image, image_operands, .. } | Instruction::ImageSparseSampleProjImplicitLod { sampled_image, image_operands, .. } | Instruction::ImageSparseSampleImplicitLod { sampled_image, image_operands, .. } => { if let Some(desc_reqs) = desc_reqs(self.instruction_chain( [inst_sampled_image, inst_load], sampled_image, )) { desc_reqs.memory_read = stage.into(); desc_reqs.sampler_no_unnormalized_coordinates = true; if image_operands.as_ref().map_or(false, |image_operands| { image_operands.const_offset.is_some() || image_operands.offset.is_some() }) { desc_reqs.sampler_no_ycbcr_conversion = true; } } } Instruction::ImageSampleProjExplicitLod { sampled_image, image_operands, .. } | Instruction::ImageSparseSampleProjExplicitLod { sampled_image, image_operands, .. } => { if let Some(desc_reqs) = desc_reqs(self.instruction_chain( [inst_sampled_image, inst_load], sampled_image, )) { desc_reqs.memory_read = stage.into(); desc_reqs.sampler_no_unnormalized_coordinates = true; if image_operands.const_offset.is_some() || image_operands.offset.is_some() { desc_reqs.sampler_no_ycbcr_conversion = true; } } } Instruction::ImageSampleDrefImplicitLod { sampled_image, image_operands, .. } | Instruction::ImageSampleProjDrefImplicitLod { sampled_image, image_operands, .. } | Instruction::ImageSparseSampleDrefImplicitLod { sampled_image, image_operands, .. } | Instruction::ImageSparseSampleProjDrefImplicitLod { sampled_image, image_operands, .. } => { if let Some(desc_reqs) = desc_reqs(self.instruction_chain( [inst_sampled_image, inst_load], sampled_image, )) { desc_reqs.memory_read = stage.into(); desc_reqs.sampler_no_unnormalized_coordinates = true; desc_reqs.sampler_compare = true; if image_operands.as_ref().map_or(false, |image_operands| { image_operands.const_offset.is_some() || image_operands.offset.is_some() }) { desc_reqs.sampler_no_ycbcr_conversion = true; } } } Instruction::ImageSampleDrefExplicitLod { sampled_image, image_operands, .. } | Instruction::ImageSampleProjDrefExplicitLod { sampled_image, image_operands, .. } | Instruction::ImageSparseSampleDrefExplicitLod { sampled_image, image_operands, .. } | Instruction::ImageSparseSampleProjDrefExplicitLod { sampled_image, image_operands, .. } => { if let Some(desc_reqs) = desc_reqs(self.instruction_chain( [inst_sampled_image, inst_load], sampled_image, )) { desc_reqs.memory_read = stage.into(); desc_reqs.sampler_no_unnormalized_coordinates = true; desc_reqs.sampler_compare = true; if image_operands.const_offset.is_some() || image_operands.offset.is_some() { desc_reqs.sampler_no_ycbcr_conversion = true; } } } Instruction::ImageSampleExplicitLod { sampled_image, image_operands, .. } | Instruction::ImageSparseSampleExplicitLod { sampled_image, image_operands, .. } => { if let Some(desc_reqs) = desc_reqs(self.instruction_chain( [inst_sampled_image, inst_load], sampled_image, )) { desc_reqs.memory_read = stage.into(); if image_operands.bias.is_some() || image_operands.const_offset.is_some() || image_operands.offset.is_some() { desc_reqs.sampler_no_unnormalized_coordinates = true; } if image_operands.const_offset.is_some() || image_operands.offset.is_some() { desc_reqs.sampler_no_ycbcr_conversion = true; } } } Instruction::ImageTexelPointer { image, .. } => { self.instruction_chain([], image); } Instruction::ImageRead { image, .. } => { if let Some(desc_reqs) = desc_reqs(self.instruction_chain([inst_load], image)) { desc_reqs.memory_read = stage.into(); } } Instruction::ImageWrite { image, .. } => { if let Some(desc_reqs) = desc_reqs(self.instruction_chain([inst_load], image)) { desc_reqs.memory_write = stage.into(); } } Instruction::Load { pointer, .. } => { if let Some((binding_variable, index)) = self.instruction_chain([], pointer) { // Only loads on buffers access memory directly. // Loads on images load the image object itself, but don't touch // the texels in memory yet. if binding_variable.reqs.descriptor_types.iter().any(|ty| { matches!( ty, DescriptorType::UniformBuffer | DescriptorType::UniformBufferDynamic | DescriptorType::StorageBuffer | DescriptorType::StorageBufferDynamic ) }) { if let Some(desc_reqs) = desc_reqs(Some((binding_variable, index))) { desc_reqs.memory_read = stage.into(); } } } } Instruction::SampledImage { image, sampler, .. } => { let identifier = match self.instruction_chain([inst_load], image) { Some((variable, Some(index))) => DescriptorIdentifier { set: variable.set, binding: variable.binding, index, }, _ => continue, }; if let Some(desc_reqs) = desc_reqs(self.instruction_chain([inst_load], sampler)) { desc_reqs.sampler_with_images.insert(identifier); } } Instruction::Store { pointer, .. } => { // This can only apply to buffers, right? if let Some(desc_reqs) = desc_reqs(self.instruction_chain([], pointer)) { desc_reqs.memory_write = stage.into(); } } _ => (), } } } } } let mut context = Context { global, spirv, stage, inspected_functions: HashSet::default(), result: HashMap::default(), }; context.inspect_entry_point_r(entry_point); context .result .into_values() .map(|variable| ((variable.set, variable.binding), variable.reqs)) .collect() } /// Returns a `DescriptorBindingRequirements` value for the pointed type. /// /// See also section 14.5.2 of the Vulkan specs: Descriptor Set Interface fn descriptor_binding_requirements_of(spirv: &Spirv, variable_id: Id) -> DescriptorBindingVariable { let variable_id_info = spirv.id(variable_id); let mut reqs = DescriptorBindingRequirements { descriptor_count: Some(1), ..Default::default() }; let (mut next_type_id, is_storage_buffer) = { let variable_type_id = match *variable_id_info.instruction() { Instruction::Variable { result_type_id, .. } => result_type_id, _ => panic!("Id {} is not a variable", variable_id), }; match *spirv.id(variable_type_id).instruction() { Instruction::TypePointer { ty, storage_class, .. } => (Some(ty), storage_class == StorageClass::StorageBuffer), _ => panic!( "Variable {} result_type_id does not refer to a TypePointer instruction", variable_id ), } }; while let Some(id) = next_type_id { let id_info = spirv.id(id); next_type_id = match *id_info.instruction() { Instruction::TypeStruct { .. } => { let decoration_block = id_info.iter_decoration().any(|instruction| { matches!( instruction, Instruction::Decorate { decoration: Decoration::Block, .. } ) }); let decoration_buffer_block = id_info.iter_decoration().any(|instruction| { matches!( instruction, Instruction::Decorate { decoration: Decoration::BufferBlock, .. } ) }); assert!( decoration_block ^ decoration_buffer_block, "Structs in shader interface are expected to be decorated with one of Block or \ BufferBlock", ); if decoration_buffer_block || decoration_block && is_storage_buffer { reqs.descriptor_types = vec![ DescriptorType::StorageBuffer, DescriptorType::StorageBufferDynamic, ]; } else { reqs.descriptor_types = vec![ DescriptorType::UniformBuffer, DescriptorType::UniformBufferDynamic, ]; }; None } Instruction::TypeImage { sampled_type, dim, arrayed, ms, sampled, image_format, .. } => { assert!( sampled != 0, "Vulkan requires that variables of type OpTypeImage have a Sampled operand of \ 1 or 2", ); reqs.image_format = image_format.into(); reqs.image_multisampled = ms != 0; reqs.image_scalar_type = Some(match *spirv.id(sampled_type).instruction() { Instruction::TypeInt { width, signedness, .. } => { assert!(width == 32); // TODO: 64-bit components match signedness { 0 => ShaderScalarType::Uint, 1 => ShaderScalarType::Sint, _ => unreachable!(), } } Instruction::TypeFloat { width, .. } => { assert!(width == 32); // TODO: 64-bit components ShaderScalarType::Float } _ => unreachable!(), }); match dim { Dim::SubpassData => { assert!( reqs.image_format.is_none(), "If Dim is SubpassData, Image Format must be Unknown", ); assert!(sampled == 2, "If Dim is SubpassData, Sampled must be 2"); assert!(arrayed == 0, "If Dim is SubpassData, Arrayed must be 0"); reqs.descriptor_types = vec![DescriptorType::InputAttachment]; } Dim::Buffer => { if sampled == 1 { reqs.descriptor_types = vec![DescriptorType::UniformTexelBuffer]; } else { reqs.descriptor_types = vec![DescriptorType::StorageTexelBuffer]; } } _ => { reqs.image_view_type = Some(match (dim, arrayed) { (Dim::Dim1D, 0) => ImageViewType::Dim1d, (Dim::Dim1D, 1) => ImageViewType::Dim1dArray, (Dim::Dim2D, 0) => ImageViewType::Dim2d, (Dim::Dim2D, 1) => ImageViewType::Dim2dArray, (Dim::Dim3D, 0) => ImageViewType::Dim3d, (Dim::Dim3D, 1) => { panic!("Vulkan doesn't support arrayed 3D textures") } (Dim::Cube, 0) => ImageViewType::Cube, (Dim::Cube, 1) => ImageViewType::CubeArray, (Dim::Rect, _) => { panic!("Vulkan doesn't support rectangle textures") } _ => unreachable!(), }); if reqs.descriptor_types.is_empty() { if sampled == 1 { reqs.descriptor_types = vec![DescriptorType::SampledImage]; } else { reqs.descriptor_types = vec![DescriptorType::StorageImage]; } } } } None } Instruction::TypeSampler { .. } => { reqs.descriptor_types = vec![DescriptorType::Sampler]; None } Instruction::TypeSampledImage { image_type, .. } => { reqs.descriptor_types = vec![DescriptorType::CombinedImageSampler]; Some(image_type) } Instruction::TypeArray { element_type, length, .. } => { let len = match spirv.id(length).instruction() { Instruction::Constant { value, .. } => { value.iter().rev().fold(0, |a, &b| (a << 32) | b as u64) } _ => panic!("failed to find array length"), }; if let Some(count) = reqs.descriptor_count.as_mut() { *count *= len as u32 } Some(element_type) } Instruction::TypeRuntimeArray { element_type, .. } => { reqs.descriptor_count = None; Some(element_type) } Instruction::TypeAccelerationStructureKHR { .. } => None, // FIXME temporary workaround _ => { let name = variable_id_info .iter_name() .find_map(|instruction| match *instruction { Instruction::Name { ref name, .. } => Some(name.as_str()), _ => None, }) .unwrap_or("__unnamed"); panic!( "Couldn't find relevant type for global variable `{}` (id {}, maybe \ unimplemented)", name, variable_id, ); } }; } DescriptorBindingVariable { set: variable_id_info .iter_decoration() .find_map(|instruction| match *instruction { Instruction::Decorate { decoration: Decoration::DescriptorSet { descriptor_set }, .. } => Some(descriptor_set), _ => None, }) .unwrap(), binding: variable_id_info .iter_decoration() .find_map(|instruction| match *instruction { Instruction::Decorate { decoration: Decoration::Binding { binding_point }, .. } => Some(binding_point), _ => None, }) .unwrap(), reqs, } } /// Extracts the `PushConstantRange` from `spirv`. fn push_constant_requirements(spirv: &Spirv, stage: ShaderStage) -> Option { spirv .iter_global() .find_map(|instruction| match *instruction { Instruction::TypePointer { ty, storage_class: StorageClass::PushConstant, .. } => { let id_info = spirv.id(ty); assert!(matches!( id_info.instruction(), Instruction::TypeStruct { .. } )); let start = offset_of_struct(spirv, ty); let end = size_of_type(spirv, ty).expect("Found runtime-sized push constants") as u32; Some(PushConstantRange { stages: stage.into(), offset: start, size: end - start, }) } _ => None, }) } /// Extracts the `SpecializationConstantRequirements` from `spirv`. fn specialization_constant_requirements( spirv: &Spirv, ) -> HashMap { spirv .iter_global() .filter_map(|instruction| { match *instruction { Instruction::SpecConstantTrue { result_type_id, result_id, } | Instruction::SpecConstantFalse { result_type_id, result_id, } | Instruction::SpecConstant { result_type_id, result_id, .. } | Instruction::SpecConstantComposite { result_type_id, result_id, .. } => spirv .id(result_id) .iter_decoration() .find_map(|instruction| match *instruction { Instruction::Decorate { decoration: Decoration::SpecId { specialization_constant_id, }, .. } => Some(specialization_constant_id), _ => None, }) .map(|constant_id| { let size = match *spirv.id(result_type_id).instruction() { Instruction::TypeBool { .. } => { // Translate bool to Bool32 std::mem::size_of::() as DeviceSize } _ => size_of_type(spirv, result_type_id) .expect("Found runtime-sized specialization constant"), }; (constant_id, SpecializationConstantRequirements { size }) }), _ => None, } }) .collect() } /// Extracts the `ShaderInterface` with the given storage class from `spirv`. fn shader_interface( spirv: &Spirv, interface: &[Id], filter_storage_class: StorageClass, ignore_first_array: bool, ) -> ShaderInterface { let elements: Vec<_> = interface .iter() .filter_map(|&id| { let (result_type_id, result_id) = match *spirv.id(id).instruction() { Instruction::Variable { result_type_id, result_id, storage_class, .. } if storage_class == filter_storage_class => (result_type_id, result_id), _ => return None, }; if is_builtin(spirv, result_id) { return None; } let id_info = spirv.id(result_id); let name = id_info .iter_name() .find_map(|instruction| match *instruction { Instruction::Name { ref name, .. } => Some(Cow::Owned(name.clone())), _ => None, }); let location = id_info .iter_decoration() .find_map(|instruction| match *instruction { Instruction::Decorate { decoration: Decoration::Location { location }, .. } => Some(location), _ => None, }) .unwrap_or_else(|| { panic!( "Input/output variable with id {} (name {:?}) is missing a location", result_id, name, ) }); let component = id_info .iter_decoration() .find_map(|instruction| match *instruction { Instruction::Decorate { decoration: Decoration::Component { component }, .. } => Some(component), _ => None, }) .unwrap_or(0); let ty = shader_interface_type_of(spirv, result_type_id, ignore_first_array); assert!(ty.num_elements >= 1); Some(ShaderInterfaceEntry { location, component, ty, name, }) }) .collect(); // Checking for overlapping elements. for (offset, element1) in elements.iter().enumerate() { for element2 in elements.iter().skip(offset + 1) { if element1.location == element2.location || (element1.location < element2.location && element1.location + element1.ty.num_locations() > element2.location) || (element2.location < element1.location && element2.location + element2.ty.num_locations() > element1.location) { panic!( "The locations of attributes `{:?}` ({}..{}) and `{:?}` ({}..{}) overlap", element1.name, element1.location, element1.location + element1.ty.num_locations(), element2.name, element2.location, element2.location + element2.ty.num_locations(), ); } } } ShaderInterface { elements } } /// Returns the size of a type, or `None` if its size cannot be determined. fn size_of_type(spirv: &Spirv, id: Id) -> Option { let id_info = spirv.id(id); match *id_info.instruction() { Instruction::TypeBool { .. } => { panic!("Can't put booleans in structs") } Instruction::TypeInt { width, .. } | Instruction::TypeFloat { width, .. } => { assert!(width % 8 == 0); Some(width as DeviceSize / 8) } Instruction::TypeVector { component_type, component_count, .. } => size_of_type(spirv, component_type) .map(|component_size| component_size * component_count as DeviceSize), Instruction::TypeMatrix { column_type, column_count, .. } => { // FIXME: row-major or column-major size_of_type(spirv, column_type) .map(|column_size| column_size * column_count as DeviceSize) } Instruction::TypeArray { length, .. } => { let stride = id_info .iter_decoration() .find_map(|instruction| match *instruction { Instruction::Decorate { decoration: Decoration::ArrayStride { array_stride }, .. } => Some(array_stride), _ => None, }) .unwrap(); let length = match spirv.id(length).instruction() { Instruction::Constant { value, .. } => Some( value .iter() .rev() .fold(0u64, |a, &b| (a << 32) | b as DeviceSize), ), _ => None, } .unwrap(); Some(stride as DeviceSize * length) } Instruction::TypeRuntimeArray { .. } => None, Instruction::TypeStruct { ref member_types, .. } => { let mut end_of_struct = 0; for (&member, member_info) in member_types.iter().zip(id_info.iter_members()) { // Built-ins have an unknown size. if member_info.iter_decoration().any(|instruction| { matches!( instruction, Instruction::MemberDecorate { decoration: Decoration::BuiltIn { .. }, .. } ) }) { return None; } // Some structs don't have `Offset` decorations, in the case they are used as local // variables only. Ignoring these. let offset = member_info .iter_decoration() .find_map(|instruction| match *instruction { Instruction::MemberDecorate { decoration: Decoration::Offset { byte_offset }, .. } => Some(byte_offset), _ => None, })?; let size = size_of_type(spirv, member)?; end_of_struct = end_of_struct.max(offset as DeviceSize + size); } Some(end_of_struct) } _ => panic!("Type {} not found", id), } } /// Returns the smallest offset of all members of a struct, or 0 if `id` is not a struct. fn offset_of_struct(spirv: &Spirv, id: Id) -> u32 { spirv .id(id) .iter_members() .filter_map(|member_info| { member_info .iter_decoration() .find_map(|instruction| match *instruction { Instruction::MemberDecorate { decoration: Decoration::Offset { byte_offset }, .. } => Some(byte_offset), _ => None, }) }) .min() .unwrap_or(0) } /// If `ignore_first_array` is true, the function expects the outermost instruction to be /// `OpTypeArray`. If it's the case, the OpTypeArray will be ignored. If not, the function will /// panic. fn shader_interface_type_of( spirv: &Spirv, id: Id, ignore_first_array: bool, ) -> ShaderInterfaceEntryType { match *spirv.id(id).instruction() { Instruction::TypeInt { width, signedness, .. } => { assert!(!ignore_first_array); ShaderInterfaceEntryType { base_type: match signedness { 0 => ShaderScalarType::Uint, 1 => ShaderScalarType::Sint, _ => unreachable!(), }, num_components: 1, num_elements: 1, is_64bit: match width { 8 | 16 | 32 => false, 64 => true, _ => unimplemented!(), }, } } Instruction::TypeFloat { width, .. } => { assert!(!ignore_first_array); ShaderInterfaceEntryType { base_type: ShaderScalarType::Float, num_components: 1, num_elements: 1, is_64bit: match width { 16 | 32 => false, 64 => true, _ => unimplemented!(), }, } } Instruction::TypeVector { component_type, component_count, .. } => { assert!(!ignore_first_array); ShaderInterfaceEntryType { num_components: component_count, ..shader_interface_type_of(spirv, component_type, false) } } Instruction::TypeMatrix { column_type, column_count, .. } => { assert!(!ignore_first_array); ShaderInterfaceEntryType { num_elements: column_count, ..shader_interface_type_of(spirv, column_type, false) } } Instruction::TypeArray { element_type, length, .. } => { if ignore_first_array { shader_interface_type_of(spirv, element_type, false) } else { let mut ty = shader_interface_type_of(spirv, element_type, false); let num_elements = spirv .instructions() .iter() .find_map(|instruction| match *instruction { Instruction::Constant { result_id, ref value, .. } if result_id == length => Some(value.clone()), _ => None, }) .expect("failed to find array length") .iter() .rev() .fold(0u64, |a, &b| (a << 32) | b as u64) as u32; ty.num_elements *= num_elements; ty } } Instruction::TypePointer { ty, .. } => { shader_interface_type_of(spirv, ty, ignore_first_array) } _ => panic!("Type {} not found or invalid", id), } } /// Returns true if a `BuiltIn` decorator is applied on an id. fn is_builtin(spirv: &Spirv, id: Id) -> bool { let id_info = spirv.id(id); if id_info.iter_decoration().any(|instruction| { matches!( instruction, Instruction::Decorate { decoration: Decoration::BuiltIn { .. }, .. } ) }) { return true; } if id_info .iter_members() .flat_map(|member_info| member_info.iter_decoration()) .any(|instruction| { matches!( instruction, Instruction::MemberDecorate { decoration: Decoration::BuiltIn { .. }, .. } ) }) { return true; } match id_info.instruction() { Instruction::Variable { result_type_id: ty, .. } | Instruction::TypeArray { element_type: ty, .. } | Instruction::TypeRuntimeArray { element_type: ty, .. } | Instruction::TypePointer { ty, .. } => is_builtin(spirv, *ty), Instruction::TypeStruct { member_types, .. } => { member_types.iter().any(|ty| is_builtin(spirv, *ty)) } _ => false, } }