| // Copyright (c) 2021 The Vulkano developers |
| // Licensed under the Apache License, Version 2.0 |
| // <LICENSE-APACHE or |
| // https://www.apache.org/licenses/LICENSE-2.0> or the MIT |
| // license <LICENSE-MIT or https://opensource.org/licenses/MIT>, |
| // at your option. All files in the project carrying such |
| // notice may not be copied, modified, or distributed except |
| // according to those terms. |
| |
| //! 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<Item = &Capability> { |
| 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<Item = &str> { |
| 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<Item = (String, ExecutionModel, EntryPointInfo)> + '_ { |
| 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<Id, DescriptorBindingVariable>, |
| } |
| |
| // 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<Id, DescriptorBindingVariable>, |
| spirv: &Spirv, |
| stage: ShaderStage, |
| entry_point: Id, |
| ) -> HashMap<(u32, u32), DescriptorBindingRequirements> { |
| struct Context<'a> { |
| global: &'a HashMap<Id, DescriptorBindingVariable>, |
| spirv: &'a Spirv, |
| stage: ShaderStage, |
| inspected_functions: HashSet<Id>, |
| result: HashMap<Id, DescriptorBindingVariable>, |
| } |
| |
| impl<'a> Context<'a> { |
| fn instruction_chain<const N: usize>( |
| &mut self, |
| chain: [fn(&Spirv, Id) -> Option<Id>; N], |
| id: Id, |
| ) -> Option<(&mut DescriptorBindingVariable, Option<u32>)> { |
| 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<u32>)>, |
| ) -> 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<Id> { |
| match *spirv.id(id).instruction() { |
| Instruction::ImageTexelPointer { image, .. } => Some(image), |
| _ => None, |
| } |
| } |
| |
| fn inst_load(spirv: &Spirv, id: Id) -> Option<Id> { |
| match *spirv.id(id).instruction() { |
| Instruction::Load { pointer, .. } => Some(pointer), |
| _ => None, |
| } |
| } |
| |
| fn inst_sampled_image(spirv: &Spirv, id: Id) -> Option<Id> { |
| 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<PushConstantRange> { |
| 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<u32, SpecializationConstantRequirements> { |
| 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::<ash::vk::Bool32>() 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<DeviceSize> { |
| 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, |
| } |
| } |