// Copyright (c) 2016 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. //! A program that is run on the device. //! //! In Vulkan, shaders are grouped in *shader modules*. Each shader module is built from SPIR-V //! code and can contain one or more entry points. Note that for the moment the official //! GLSL-to-SPIR-V compiler does not support multiple entry points. //! //! The vulkano library can parse and introspect SPIR-V code, but it does not fully validate the //! code. You are encouraged to use the `vulkano-shaders` crate that will generate Rust code that //! wraps around vulkano's shaders API. //! //! # Shader interface //! //! Vulkan has specific rules for interfacing shaders with each other, and with other parts //! of a program. //! //! ## Endianness //! //! The Vulkan specification requires that a Vulkan implementation has runtime support for the //! types [`u8`], [`u16`], [`u32`], [`u64`] as well as their signed versions, as well as [`f32`] //! and [`f64`] on the host, and that the representation and endianness of these types matches //! those on the device. This means that if you have for example a `Subbuffer`, you can be //! sure that it is represented the same way on the host as it is on the device, and you don't need //! to worry about converting the endianness. //! //! ## Layout of data //! //! When buffers, push constants or other user-provided data are accessed in shaders, //! the shader expects the values inside to be laid out in a specific way. For every uniform buffer, //! storage buffer or push constant block, the SPIR-V specification requires the SPIR-V code to //! provide the `Offset` decoration for every member of a struct, indicating where it is placed //! relative to the start of the struct. If there are arrays or matrices among the variables, the //! SPIR-V code must also provide an `ArrayStride` or `MatrixStride` decoration for them, //! indicating the number of bytes between the start of each element in the array or column in the //! matrix. When providing data to shaders, you must make sure that your data is placed at the //! locations indicated within the SPIR-V code, or the shader will read the wrong data and produce //! nonsense. //! //! GLSL does not require you to give explicit offsets and/or strides to your variables (although //! it has the option to provide them if you wish). Instead, the shader compiler automatically //! assigns every variable an offset, increasing in the order you declare them in. //! To know the exact offsets that will be used, so that you can lay out your data appropriately, //! you must know the alignment rules that the shader compiler uses. The shader compiler will //! always give a variable the smallest offset that fits the alignment rules and doesn't overlap //! with the previous variable. The shader compiler uses default alignment rules depending on the //! type of block, but you can specify another layout by using the `layout` qualifier. //! //! ## Alignment rules //! //! The offset of each variable from the start of a block, matrix or array must be a //! multiple of a certain number, which is called its *alignment*. The stride of an array or matrix //! must likewise be a multiple of this number. An alignment is always a power-of-two value. //! Regardless of whether the offset/stride is provided manually in the compiled SPIR-V code, //! or assigned automatically by the shader compiler, all variable offsets/strides in a shader must //! follow these alignment rules. //! //! Three sets of [alignment rules] are supported by Vulkan. Each one has a GLSL qualifier that //! you can place in front of a block, to make the shader compiler use that layout for the block. //! If you don't provide this qualifier, it will use a default alignment. //! //! - **Scalar alignment** (GLSL qualifier: `layout(scalar)`, requires the //! [`GL_EXT_scalar_block_layout`] GLSL extension). This is the same as the C alignment, //! expressed in Rust with the //! [`#[repr(C)]`](https://doc.rust-lang.org/nomicon/other-reprs.html#reprc) attribute. //! The shader compiler does not use this alignment by default, so you must use the GLSL //! qualifier. You must also enable the [`scalar_block_layout`] feature in Vulkan. //! - **Base alignment**, also known as **std430** (GLSL qualifier: `layout(std430)`). //! The shader compiler uses this alignment by default for all shader data except uniform buffers. //! If you use the base alignment for a uniform buffer, you must also enable the //! [`uniform_buffer_standard_layout`] feature in Vulkan. //! - **Extended alignment**, also known as **std140** (GLSL qualifier: `layout(std140)`). //! The shader compiler uses this alignment by default for uniform buffers. //! //! Each alignment type is a subset of the ones above it, so if something adheres to the extended //! alignment rules, it also follows the rules for the base and scalar alignments. //! //! In all three of these alignment rules, a primitive/scalar value with a size of N bytes has an //! alignment of N, meaning that it must have an offset that is a multiple of its size, //! like in C or Rust. For example, a `float` (like a Rust `f32`) has a size of 4 bytes, //! and an alignment of 4. //! //! The differences between the alignment rules are in how compound types (vectors, matrices, //! arrays and structs) are expected to be laid out. For a compound type with an element whose //! alignment is N, the scalar alignment considers the alignment of the compound type to be also N. //! However, the base and extended alignments are stricter: //! //! | GLSL type | Scalar | Base | Extended | //! |-----------|-----------------|-----------------|--------------------------| //! | primitive | N | N | N | //! | `vec2` | N | N * 2 | N * 2 | //! | `vec3` | N | N * 4 | N * 4 | //! | `vec4` | N | N * 4 | N * 4 | //! | array | N | N | max(N, 16) | //! | `struct` | Nmax | Nmax | max(Nmax, 16) | //! //! In the base and extended alignment, the alignment of a vector is the size of the whole vector, //! rather than the size of its individual elements as is the case in the scalar alignment. //! But note that, because alignment must be a power of two, the alignment of `vec3` cannot be //! N * 3; it must be N * 4, the same alignment as `vec4`. This means that it is not possible to //! tightly pack multiple `vec3` values (e.g. in an array); there will always be empty padding //! between them. //! //! In both the scalar and base alignment, the alignment of arrays and their elements is equal to //! the alignment of the contained type. In the extended alignment, however, the alignment is //! always at least 16 (the size of a `vec4`). Therefore, the minimum stride of the array can be //! much greater than the element size. For example, in an array of `float`, the stride must be at //! least 16, even though a `float` itself is only 4 bytes in size. Every `float` element will be //! followed by at least 12 bytes of unused space. //! //! A matrix `matCxR` is considered equivalent to an array of column vectors `vecR[C]`. //! In the base and extended alignments, that means that if the matrix has 3 rows, there will be //! one element's worth of padding between the column vectors. In the extended alignment, //! the alignment is also at least 16, further increasing the amount of padding between the //! column vectors. //! //! The rules for `struct`s are similar to those of arrays. When the members of the struct have //! different alignment requirements, the alignment of the struct as a whole is the maximum //! of the alignments of its members. As with arrays, in the extended alignment, the alignment //! of a struct is at least 16. //! //! [alignment rules]: //! [`GL_EXT_scalar_block_layout`]: //! [`scalar_block_layout`]: crate::device::Features::scalar_block_layout //! [`uniform_buffer_standard_layout`]: crate::device::Features::uniform_buffer_standard_layout use crate::{ descriptor_set::layout::DescriptorType, device::{Device, DeviceOwned}, format::{Format, NumericType}, image::view::ImageViewType, macros::{impl_id_counter, vulkan_bitflags_enum}, pipeline::{graphics::input_assembly::PrimitiveTopology, layout::PushConstantRange}, shader::spirv::{Capability, Spirv, SpirvError}, sync::PipelineStages, DeviceSize, OomError, Version, VulkanError, VulkanObject, }; use ahash::{HashMap, HashSet}; use std::{ borrow::Cow, collections::hash_map::Entry, error::Error, ffi::{CStr, CString}, fmt::{Display, Error as FmtError, Formatter}, mem, mem::MaybeUninit, num::NonZeroU64, ptr, sync::Arc, }; pub mod reflect; pub mod spirv; use spirv::ExecutionModel; // Generated by build.rs include!(concat!(env!("OUT_DIR"), "/spirv_reqs.rs")); /// Contains SPIR-V code with one or more entry points. #[derive(Debug)] pub struct ShaderModule { handle: ash::vk::ShaderModule, device: Arc, id: NonZeroU64, entry_points: HashMap>, } impl ShaderModule { /// Builds a new shader module from SPIR-V 32-bit words. The shader code is parsed and the /// necessary information is extracted from it. /// /// # Safety /// /// - The SPIR-V code is not validated beyond the minimum needed to extract the information. #[inline] pub unsafe fn from_words( device: Arc, words: &[u32], ) -> Result, ShaderCreationError> { let spirv = Spirv::new(words)?; Self::from_words_with_data( device, words, spirv.version(), reflect::spirv_capabilities(&spirv), reflect::spirv_extensions(&spirv), reflect::entry_points(&spirv), ) } /// As `from_words`, but takes a slice of bytes. /// /// # Panics /// /// - Panics if the length of `bytes` is not a multiple of 4. #[inline] pub unsafe fn from_bytes( device: Arc, bytes: &[u8], ) -> Result, ShaderCreationError> { assert!((bytes.len() % 4) == 0); Self::from_words( device, std::slice::from_raw_parts( bytes.as_ptr() as *const _, bytes.len() / mem::size_of::(), ), ) } /// As `from_words`, but does not parse the code. Instead, you must provide the needed /// information yourself. This can be useful if you've already done parsing yourself and /// want to prevent Vulkano from doing it a second time. /// /// # Safety /// /// - The SPIR-V code is not validated at all. /// - The provided information must match what the SPIR-V code contains. pub unsafe fn from_words_with_data<'a>( device: Arc, words: &[u32], spirv_version: Version, spirv_capabilities: impl IntoIterator, spirv_extensions: impl IntoIterator, entry_points: impl IntoIterator, ) -> Result, ShaderCreationError> { if let Err(reason) = check_spirv_version(&device, spirv_version) { return Err(ShaderCreationError::SpirvVersionNotSupported { version: spirv_version, reason, }); } for &capability in spirv_capabilities { if let Err(reason) = check_spirv_capability(&device, capability) { return Err(ShaderCreationError::SpirvCapabilityNotSupported { capability, reason, }); } } for extension in spirv_extensions { if let Err(reason) = check_spirv_extension(&device, extension) { return Err(ShaderCreationError::SpirvExtensionNotSupported { extension: extension.to_owned(), reason, }); } } let handle = { let infos = ash::vk::ShaderModuleCreateInfo { flags: ash::vk::ShaderModuleCreateFlags::empty(), code_size: words.len() * mem::size_of::(), p_code: words.as_ptr(), ..Default::default() }; let fns = device.fns(); let mut output = MaybeUninit::uninit(); (fns.v1_0.create_shader_module)( device.handle(), &infos, ptr::null(), output.as_mut_ptr(), ) .result() .map_err(VulkanError::from)?; output.assume_init() }; let entries = entry_points.into_iter().collect::>(); let entry_points = entries .iter() .map(|(name, _, _)| name) .collect::>() .iter() .map(|name| { ( (*name).clone(), entries .iter() .filter_map(|(entry_name, entry_model, info)| { if &entry_name == name { Some((*entry_model, info.clone())) } else { None } }) .collect::>(), ) }) .collect(); Ok(Arc::new(ShaderModule { handle, device, id: Self::next_id(), entry_points, })) } /// As `from_words_with_data`, but takes a slice of bytes. /// /// # Panics /// /// - Panics if the length of `bytes` is not a multiple of 4. pub unsafe fn from_bytes_with_data<'a>( device: Arc, bytes: &[u8], spirv_version: Version, spirv_capabilities: impl IntoIterator, spirv_extensions: impl IntoIterator, entry_points: impl IntoIterator, ) -> Result, ShaderCreationError> { assert!((bytes.len() % 4) == 0); Self::from_words_with_data( device, std::slice::from_raw_parts( bytes.as_ptr() as *const _, bytes.len() / mem::size_of::(), ), spirv_version, spirv_capabilities, spirv_extensions, entry_points, ) } /// Returns information about the entry point with the provided name. Returns `None` if no entry /// point with that name exists in the shader module or if multiple entry points with the same /// name exist. #[inline] pub fn entry_point<'a>(&'a self, name: &str) -> Option> { self.entry_points.get(name).and_then(|infos| { if infos.len() == 1 { infos.iter().next().map(|(_, info)| EntryPoint { module: self, name: CString::new(name).unwrap(), info, }) } else { None } }) } /// Returns information about the entry point with the provided name and execution model. /// Returns `None` if no entry and execution model exists in the shader module. #[inline] pub fn entry_point_with_execution<'a>( &'a self, name: &str, execution: ExecutionModel, ) -> Option> { self.entry_points.get(name).and_then(|infos| { infos.get(&execution).map(|info| EntryPoint { module: self, name: CString::new(name).unwrap(), info, }) }) } } impl Drop for ShaderModule { #[inline] fn drop(&mut self) { unsafe { let fns = self.device.fns(); (fns.v1_0.destroy_shader_module)(self.device.handle(), self.handle, ptr::null()); } } } unsafe impl VulkanObject for ShaderModule { type Handle = ash::vk::ShaderModule; #[inline] fn handle(&self) -> Self::Handle { self.handle } } unsafe impl DeviceOwned for ShaderModule { #[inline] fn device(&self) -> &Arc { &self.device } } impl_id_counter!(ShaderModule); /// Error that can happen when creating a new shader module. #[derive(Clone, Debug)] pub enum ShaderCreationError { OomError(OomError), SpirvCapabilityNotSupported { capability: Capability, reason: ShaderSupportError, }, SpirvError(SpirvError), SpirvExtensionNotSupported { extension: String, reason: ShaderSupportError, }, SpirvVersionNotSupported { version: Version, reason: ShaderSupportError, }, } impl Error for ShaderCreationError { fn source(&self) -> Option<&(dyn Error + 'static)> { match self { Self::OomError(err) => Some(err), Self::SpirvCapabilityNotSupported { reason, .. } => Some(reason), Self::SpirvError(err) => Some(err), Self::SpirvExtensionNotSupported { reason, .. } => Some(reason), Self::SpirvVersionNotSupported { reason, .. } => Some(reason), } } } impl Display for ShaderCreationError { fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> { match self { Self::OomError(_) => write!(f, "not enough memory available"), Self::SpirvCapabilityNotSupported { capability, .. } => write!( f, "the SPIR-V capability {:?} enabled by the shader is not supported by the device", capability, ), Self::SpirvError(_) => write!(f, "the SPIR-V module could not be read"), Self::SpirvExtensionNotSupported { extension, .. } => write!( f, "the SPIR-V extension {} enabled by the shader is not supported by the device", extension, ), Self::SpirvVersionNotSupported { version, .. } => write!( f, "the shader uses SPIR-V version {}.{}, which is not supported by the device", version.major, version.minor, ), } } } impl From for ShaderCreationError { fn from(err: VulkanError) -> Self { Self::OomError(err.into()) } } impl From for ShaderCreationError { fn from(err: SpirvError) -> Self { Self::SpirvError(err) } } /// Error that can happen when checking whether a shader is supported by a device. #[derive(Clone, Copy, Debug)] pub enum ShaderSupportError { NotSupportedByVulkan, RequirementsNotMet(&'static [&'static str]), } impl Error for ShaderSupportError {} impl Display for ShaderSupportError { fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> { match self { Self::NotSupportedByVulkan => write!(f, "not supported by Vulkan"), Self::RequirementsNotMet(requirements) => write!( f, "at least one of the following must be available/enabled on the device: {}", requirements.join(", "), ), } } } /// The information associated with a single entry point in a shader. #[derive(Clone, Debug)] pub struct EntryPointInfo { pub execution: ShaderExecution, pub descriptor_binding_requirements: HashMap<(u32, u32), DescriptorBindingRequirements>, pub push_constant_requirements: Option, pub specialization_constant_requirements: HashMap, pub input_interface: ShaderInterface, pub output_interface: ShaderInterface, } /// Represents a shader entry point in a shader module. /// /// Can be obtained by calling [`entry_point`](ShaderModule::entry_point) on the shader module. #[derive(Clone, Debug)] pub struct EntryPoint<'a> { module: &'a ShaderModule, name: CString, info: &'a EntryPointInfo, } impl<'a> EntryPoint<'a> { /// Returns the module this entry point comes from. #[inline] pub fn module(&self) -> &'a ShaderModule { self.module } /// Returns the name of the entry point. #[inline] pub fn name(&self) -> &CStr { &self.name } /// Returns the execution model of the shader. #[inline] pub fn execution(&self) -> &ShaderExecution { &self.info.execution } /// Returns the descriptor binding requirements. #[inline] pub fn descriptor_binding_requirements( &self, ) -> impl ExactSizeIterator { self.info .descriptor_binding_requirements .iter() .map(|(k, v)| (*k, v)) } /// Returns the push constant requirements. #[inline] pub fn push_constant_requirements(&self) -> Option<&PushConstantRange> { self.info.push_constant_requirements.as_ref() } /// Returns the specialization constant requirements. #[inline] pub fn specialization_constant_requirements( &self, ) -> impl ExactSizeIterator { self.info .specialization_constant_requirements .iter() .map(|(k, v)| (*k, v)) } /// Returns the input attributes used by the shader stage. #[inline] pub fn input_interface(&self) -> &ShaderInterface { &self.info.input_interface } /// Returns the output attributes used by the shader stage. #[inline] pub fn output_interface(&self) -> &ShaderInterface { &self.info.output_interface } } /// The mode in which a shader executes. This includes both information about the shader type/stage, /// and additional data relevant to particular shader types. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum ShaderExecution { Vertex, TessellationControl, TessellationEvaluation, Geometry(GeometryShaderExecution), Fragment(FragmentShaderExecution), Compute, RayGeneration, AnyHit, ClosestHit, Miss, Intersection, Callable, Task, Mesh, SubpassShading, } /*#[derive(Clone, Copy, Debug)] pub struct TessellationShaderExecution { pub num_output_vertices: u32, pub point_mode: bool, pub subdivision: TessellationShaderSubdivision, } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum TessellationShaderSubdivision { Triangles, Quads, Isolines, }*/ /// The mode in which a geometry shader executes. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct GeometryShaderExecution { pub input: GeometryShaderInput, /*pub max_output_vertices: u32, pub num_invocations: u32, pub output: GeometryShaderOutput,*/ } /// The input primitive type that is expected by a geometry shader. #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum GeometryShaderInput { Points, Lines, LinesWithAdjacency, Triangles, TrianglesWithAdjacency, } impl GeometryShaderInput { /// Returns true if the given primitive topology can be used as input for this geometry shader. #[inline] pub fn is_compatible_with(self, topology: PrimitiveTopology) -> bool { match self { Self::Points => matches!(topology, PrimitiveTopology::PointList), Self::Lines => matches!( topology, PrimitiveTopology::LineList | PrimitiveTopology::LineStrip ), Self::LinesWithAdjacency => matches!( topology, PrimitiveTopology::LineListWithAdjacency | PrimitiveTopology::LineStripWithAdjacency ), Self::Triangles => matches!( topology, PrimitiveTopology::TriangleList | PrimitiveTopology::TriangleStrip | PrimitiveTopology::TriangleFan, ), Self::TrianglesWithAdjacency => matches!( topology, PrimitiveTopology::TriangleListWithAdjacency | PrimitiveTopology::TriangleStripWithAdjacency, ), } } } /*#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum GeometryShaderOutput { Points, LineStrip, TriangleStrip, }*/ /// The mode in which a fragment shader executes. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct FragmentShaderExecution { pub fragment_tests_stages: FragmentTestsStages, } /// The fragment tests stages that will be executed in a fragment shader. #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum FragmentTestsStages { Early, Late, EarlyAndLate, } /// The requirements imposed by a shader on a binding within a descriptor set layout, and on any /// resource that is bound to that binding. #[derive(Clone, Debug, Default)] pub struct DescriptorBindingRequirements { /// The descriptor types that are allowed. pub descriptor_types: Vec, /// The number of descriptors (array elements) that the shader requires. The descriptor set /// layout can declare more than this, but never less. /// /// `None` means that the shader declares this as a runtime-sized array, and could potentially /// access every array element provided in the descriptor set. pub descriptor_count: Option, /// The image format that is required for image views bound to this binding. If this is /// `None`, then any image format is allowed. pub image_format: Option, /// Whether image views bound to this binding must have multisampling enabled or disabled. pub image_multisampled: bool, /// The base scalar type required for the format of image views bound to this binding. /// This is `None` for non-image bindings. pub image_scalar_type: Option, /// The view type that is required for image views bound to this binding. /// This is `None` for non-image bindings. pub image_view_type: Option, /// The shader stages that the binding must be declared for. pub stages: ShaderStages, /// The requirements for individual descriptors within a binding. /// /// Keys with `Some` hold requirements for a specific descriptor index, if it is statically /// known in the shader (a constant). The key `None` holds requirements for indices that are /// not statically known, but determined only at runtime (calculated from an input variable). pub descriptors: HashMap, DescriptorRequirements>, } /// The requirements imposed by a shader on resources bound to a descriptor. #[derive(Clone, Debug, Default)] pub struct DescriptorRequirements { /// For buffers and images, which shader stages perform read operations. pub memory_read: ShaderStages, /// For buffers and images, which shader stages perform write operations. pub memory_write: ShaderStages, /// For sampler bindings, whether the shader performs depth comparison operations. pub sampler_compare: bool, /// For sampler bindings, whether the shader performs sampling operations that are not /// permitted with unnormalized coordinates. This includes sampling with `ImplicitLod`, /// `Dref` or `Proj` SPIR-V instructions or with an LOD bias or offset. pub sampler_no_unnormalized_coordinates: bool, /// For sampler bindings, whether the shader performs sampling operations that are not /// permitted with a sampler YCbCr conversion. This includes sampling with `Gather` SPIR-V /// instructions or with an offset. pub sampler_no_ycbcr_conversion: bool, /// For sampler bindings, the sampled image descriptors that are used in combination with this /// sampler. pub sampler_with_images: HashSet, /// For storage image bindings, whether the shader performs atomic operations. pub storage_image_atomic: bool, } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct DescriptorIdentifier { pub set: u32, pub binding: u32, pub index: u32, } impl DescriptorBindingRequirements { /// Merges `other` into `self`, so that `self` satisfies the requirements of both. /// An error is returned if the requirements conflict. #[inline] pub fn merge(&mut self, other: &Self) -> Result<(), DescriptorBindingRequirementsIncompatible> { let Self { descriptor_types, descriptor_count, image_format, image_multisampled, image_scalar_type, image_view_type, stages, descriptors, } = self; /* Checks */ if !descriptor_types .iter() .any(|ty| other.descriptor_types.contains(ty)) { return Err(DescriptorBindingRequirementsIncompatible::DescriptorType); } if let (Some(first), Some(second)) = (*image_format, other.image_format) { if first != second { return Err(DescriptorBindingRequirementsIncompatible::ImageFormat); } } if let (Some(first), Some(second)) = (*image_scalar_type, other.image_scalar_type) { if first != second { return Err(DescriptorBindingRequirementsIncompatible::ImageScalarType); } } if let (Some(first), Some(second)) = (*image_view_type, other.image_view_type) { if first != second { return Err(DescriptorBindingRequirementsIncompatible::ImageViewType); } } if *image_multisampled != other.image_multisampled { return Err(DescriptorBindingRequirementsIncompatible::ImageMultisampled); } /* Merge */ descriptor_types.retain(|ty| other.descriptor_types.contains(ty)); *descriptor_count = (*descriptor_count).max(other.descriptor_count); *image_format = image_format.or(other.image_format); *image_scalar_type = image_scalar_type.or(other.image_scalar_type); *image_view_type = image_view_type.or(other.image_view_type); *stages |= other.stages; for (&index, other) in &other.descriptors { match descriptors.entry(index) { Entry::Vacant(entry) => { entry.insert(other.clone()); } Entry::Occupied(entry) => { entry.into_mut().merge(other); } } } Ok(()) } } impl DescriptorRequirements { /// Merges `other` into `self`, so that `self` satisfies the requirements of both. #[inline] pub fn merge(&mut self, other: &Self) { let Self { memory_read, memory_write, sampler_compare, sampler_no_unnormalized_coordinates, sampler_no_ycbcr_conversion, sampler_with_images, storage_image_atomic, } = self; *memory_read |= other.memory_read; *memory_write |= other.memory_write; *sampler_compare |= other.sampler_compare; *sampler_no_unnormalized_coordinates |= other.sampler_no_unnormalized_coordinates; *sampler_no_ycbcr_conversion |= other.sampler_no_ycbcr_conversion; sampler_with_images.extend(&other.sampler_with_images); *storage_image_atomic |= other.storage_image_atomic; } } /// An error that can be returned when trying to create the intersection of two /// `DescriptorBindingRequirements` values. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum DescriptorBindingRequirementsIncompatible { /// The allowed descriptor types of the descriptors do not overlap. DescriptorType, /// The descriptors require different formats. ImageFormat, /// The descriptors require different scalar types. ImageScalarType, /// The multisampling requirements of the descriptors differ. ImageMultisampled, /// The descriptors require different image view types. ImageViewType, } impl Error for DescriptorBindingRequirementsIncompatible {} impl Display for DescriptorBindingRequirementsIncompatible { fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> { match self { DescriptorBindingRequirementsIncompatible::DescriptorType => write!( f, "the allowed descriptor types of the two descriptors do not overlap", ), DescriptorBindingRequirementsIncompatible::ImageFormat => { write!(f, "the descriptors require different formats",) } DescriptorBindingRequirementsIncompatible::ImageMultisampled => write!( f, "the multisampling requirements of the descriptors differ", ), DescriptorBindingRequirementsIncompatible::ImageScalarType => { write!(f, "the descriptors require different scalar types",) } DescriptorBindingRequirementsIncompatible::ImageViewType => { write!(f, "the descriptors require different image view types",) } } } } /// The requirements imposed by a shader on a specialization constant. #[derive(Clone, Copy, Debug)] pub struct SpecializationConstantRequirements { pub size: DeviceSize, } /// Trait for types that contain specialization data for shaders. /// /// Shader modules can contain what is called *specialization constants*. They are the same as /// constants except that their values can be defined when you create a compute pipeline or a /// graphics pipeline. Doing so is done by passing a type that implements the /// `SpecializationConstants` trait and that stores the values in question. The `descriptors()` /// method of this trait indicates how to grab them. /// /// Boolean specialization constants must be stored as 32bits integers, where `0` means `false` and /// any non-zero value means `true`. Integer and floating-point specialization constants are /// stored as their Rust equivalent. /// /// This trait is implemented on `()` for shaders that don't have any specialization constant. /// /// # Examples /// /// ```rust /// use vulkano::shader::SpecializationConstants; /// use vulkano::shader::SpecializationMapEntry; /// /// #[repr(C)] // `#[repr(C)]` guarantees that the struct has a specific layout /// struct MySpecConstants { /// my_integer_constant: i32, /// a_boolean: u32, /// floating_point: f32, /// } /// /// unsafe impl SpecializationConstants for MySpecConstants { /// fn descriptors() -> &'static [SpecializationMapEntry] { /// static DESCRIPTORS: [SpecializationMapEntry; 3] = [ /// SpecializationMapEntry { /// constant_id: 0, /// offset: 0, /// size: 4, /// }, /// SpecializationMapEntry { /// constant_id: 1, /// offset: 4, /// size: 4, /// }, /// SpecializationMapEntry { /// constant_id: 2, /// offset: 8, /// size: 4, /// }, /// ]; /// /// &DESCRIPTORS /// } /// } /// ``` /// /// # Safety /// /// - The `SpecializationMapEntry` returned must contain valid offsets and sizes. /// - The size of each `SpecializationMapEntry` must match the size of the corresponding constant /// (`4` for booleans). pub unsafe trait SpecializationConstants { /// Returns descriptors of the struct's layout. fn descriptors() -> &'static [SpecializationMapEntry]; } unsafe impl SpecializationConstants for () { #[inline] fn descriptors() -> &'static [SpecializationMapEntry] { &[] } } /// Describes an individual constant to set in the shader. Also a field in the struct. // Implementation note: has the same memory representation as a `VkSpecializationMapEntry`. #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[repr(C)] pub struct SpecializationMapEntry { /// Identifier of the constant in the shader that corresponds to this field. /// /// For SPIR-V, this must be the value of the `SpecId` decoration applied to the specialization /// constant. /// For GLSL, this must be the value of `N` in the `layout(constant_id = N)` attribute applied /// to a constant. pub constant_id: u32, /// Offset within the struct where the data can be found. pub offset: u32, /// Size of the data in bytes. Must match the size of the constant (`4` for booleans). pub size: usize, } impl From for ash::vk::SpecializationMapEntry { #[inline] fn from(val: SpecializationMapEntry) -> Self { Self { constant_id: val.constant_id, offset: val.offset, size: val.size, } } } /// Type that contains the definition of an interface between two shader stages, or between /// the outside and a shader stage. #[derive(Clone, Debug)] pub struct ShaderInterface { elements: Vec, } impl ShaderInterface { /// Constructs a new `ShaderInterface`. /// /// # Safety /// /// - Must only provide one entry per location. /// - The format of each element must not be larger than 128 bits. // TODO: 4x64 bit formats are possible, but they require special handling. // TODO: could this be made safe? #[inline] pub unsafe fn new_unchecked(elements: Vec) -> ShaderInterface { ShaderInterface { elements } } /// Creates a description of an empty shader interface. #[inline] pub const fn empty() -> ShaderInterface { ShaderInterface { elements: Vec::new(), } } /// Returns a slice containing the elements of the interface. #[inline] pub fn elements(&self) -> &[ShaderInterfaceEntry] { self.elements.as_ref() } /// Checks whether the interface is potentially compatible with another one. /// /// Returns `Ok` if the two interfaces are compatible. #[inline] pub fn matches(&self, other: &ShaderInterface) -> Result<(), ShaderInterfaceMismatchError> { if self.elements().len() != other.elements().len() { return Err(ShaderInterfaceMismatchError::ElementsCountMismatch { self_elements: self.elements().len() as u32, other_elements: other.elements().len() as u32, }); } for a in self.elements() { let location_range = a.location..a.location + a.ty.num_locations(); for loc in location_range { let b = match other .elements() .iter() .find(|e| loc >= e.location && loc < e.location + e.ty.num_locations()) { None => { return Err(ShaderInterfaceMismatchError::MissingElement { location: loc }) } Some(b) => b, }; if a.ty != b.ty { return Err(ShaderInterfaceMismatchError::TypeMismatch { location: loc, self_ty: a.ty, other_ty: b.ty, }); } // TODO: enforce this? /*match (a.name, b.name) { (Some(ref an), Some(ref bn)) => if an != bn { return false }, _ => () };*/ } } // Note: since we check that the number of elements is the same, we don't need to iterate // over b's elements. Ok(()) } } /// Entry of a shader interface definition. #[derive(Debug, Clone)] pub struct ShaderInterfaceEntry { /// The location slot that the variable starts at. pub location: u32, /// The component slot that the variable starts at. Must be in the range 0..=3. pub component: u32, /// Name of the element, or `None` if the name is unknown. pub name: Option>, /// The type of the variable. pub ty: ShaderInterfaceEntryType, } /// The type of a variable in a shader interface. #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct ShaderInterfaceEntryType { /// The base numeric type. pub base_type: ShaderScalarType, /// The number of vector components. Must be in the range 1..=4. pub num_components: u32, /// The number of array elements or matrix columns. pub num_elements: u32, /// Whether the base type is 64 bits wide. If true, each item of the base type takes up two /// component slots instead of one. pub is_64bit: bool, } impl ShaderInterfaceEntryType { pub(crate) fn num_locations(&self) -> u32 { assert!(!self.is_64bit); // TODO: implement self.num_elements } } /// The numeric base type of a shader variable. #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum ShaderScalarType { Float, Sint, Uint, } // https://registry.khronos.org/vulkan/specs/1.3-extensions/html/chap43.html#formats-numericformat impl From for ShaderScalarType { #[inline] fn from(val: NumericType) -> Self { match val { NumericType::SFLOAT => Self::Float, NumericType::UFLOAT => Self::Float, NumericType::SINT => Self::Sint, NumericType::UINT => Self::Uint, NumericType::SNORM => Self::Float, NumericType::UNORM => Self::Float, NumericType::SSCALED => Self::Float, NumericType::USCALED => Self::Float, NumericType::SRGB => Self::Float, } } } /// Error that can happen when the interface mismatches between two shader stages. #[derive(Clone, Debug, PartialEq, Eq)] pub enum ShaderInterfaceMismatchError { /// The number of elements is not the same between the two shader interfaces. ElementsCountMismatch { /// Number of elements in the first interface. self_elements: u32, /// Number of elements in the second interface. other_elements: u32, }, /// An element is missing from one of the interfaces. MissingElement { /// Location of the missing element. location: u32, }, /// The type of an element does not match. TypeMismatch { /// Location of the element that mismatches. location: u32, /// Type in the first interface. self_ty: ShaderInterfaceEntryType, /// Type in the second interface. other_ty: ShaderInterfaceEntryType, }, } impl Error for ShaderInterfaceMismatchError {} impl Display for ShaderInterfaceMismatchError { fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> { write!( f, "{}", match self { ShaderInterfaceMismatchError::ElementsCountMismatch { .. } => { "the number of elements mismatches" } ShaderInterfaceMismatchError::MissingElement { .. } => "an element is missing", ShaderInterfaceMismatchError::TypeMismatch { .. } => { "the type of an element does not match" } } ) } } vulkan_bitflags_enum! { #[non_exhaustive] /// A set of [`ShaderStage`] values. ShaderStages impl { /// Creates a `ShaderStages` struct with all graphics stages set to `true`. #[inline] pub const fn all_graphics() -> ShaderStages { ShaderStages::VERTEX .union(ShaderStages::TESSELLATION_CONTROL) .union(ShaderStages::TESSELLATION_EVALUATION) .union(ShaderStages::GEOMETRY) .union(ShaderStages::FRAGMENT) } }, /// A shader stage within a pipeline. ShaderStage, = ShaderStageFlags(u32); // TODO: document VERTEX, Vertex = VERTEX, // TODO: document TESSELLATION_CONTROL, TessellationControl = TESSELLATION_CONTROL, // TODO: document TESSELLATION_EVALUATION, TessellationEvaluation = TESSELLATION_EVALUATION, // TODO: document GEOMETRY, Geometry = GEOMETRY, // TODO: document FRAGMENT, Fragment = FRAGMENT, // TODO: document COMPUTE, Compute = COMPUTE, // TODO: document RAYGEN, Raygen = RAYGEN_KHR { device_extensions: [khr_ray_tracing_pipeline, nv_ray_tracing], }, // TODO: document ANY_HIT, AnyHit = ANY_HIT_KHR { device_extensions: [khr_ray_tracing_pipeline, nv_ray_tracing], }, // TODO: document CLOSEST_HIT, ClosestHit = CLOSEST_HIT_KHR { device_extensions: [khr_ray_tracing_pipeline, nv_ray_tracing], }, // TODO: document MISS, Miss = MISS_KHR { device_extensions: [khr_ray_tracing_pipeline, nv_ray_tracing], }, // TODO: document INTERSECTION, Intersection = INTERSECTION_KHR { device_extensions: [khr_ray_tracing_pipeline, nv_ray_tracing], }, // TODO: document CALLABLE, Callable = CALLABLE_KHR { device_extensions: [khr_ray_tracing_pipeline, nv_ray_tracing], }, // TODO: document TASK, Task = TASK_EXT { device_extensions: [ext_mesh_shader, nv_mesh_shader], }, // TODO: document MESH, Mesh = MESH_EXT { device_extensions: [ext_mesh_shader, nv_mesh_shader], }, // TODO: document SUBPASS_SHADING, SubpassShading = SUBPASS_SHADING_HUAWEI { device_extensions: [huawei_subpass_shading], }, } impl From for ShaderStage { #[inline] fn from(val: ShaderExecution) -> Self { match val { ShaderExecution::Vertex => Self::Vertex, ShaderExecution::TessellationControl => Self::TessellationControl, ShaderExecution::TessellationEvaluation => Self::TessellationEvaluation, ShaderExecution::Geometry(_) => Self::Geometry, ShaderExecution::Fragment(_) => Self::Fragment, ShaderExecution::Compute => Self::Compute, ShaderExecution::RayGeneration => Self::Raygen, ShaderExecution::AnyHit => Self::AnyHit, ShaderExecution::ClosestHit => Self::ClosestHit, ShaderExecution::Miss => Self::Miss, ShaderExecution::Intersection => Self::Intersection, ShaderExecution::Callable => Self::Callable, ShaderExecution::Task => Self::Task, ShaderExecution::Mesh => Self::Mesh, ShaderExecution::SubpassShading => Self::SubpassShading, } } } impl From for PipelineStages { #[inline] fn from(stages: ShaderStages) -> PipelineStages { let mut result = PipelineStages::empty(); if stages.intersects(ShaderStages::VERTEX) { result |= PipelineStages::VERTEX_SHADER } if stages.intersects(ShaderStages::TESSELLATION_CONTROL) { result |= PipelineStages::TESSELLATION_CONTROL_SHADER } if stages.intersects(ShaderStages::TESSELLATION_EVALUATION) { result |= PipelineStages::TESSELLATION_EVALUATION_SHADER } if stages.intersects(ShaderStages::GEOMETRY) { result |= PipelineStages::GEOMETRY_SHADER } if stages.intersects(ShaderStages::FRAGMENT) { result |= PipelineStages::FRAGMENT_SHADER } if stages.intersects(ShaderStages::COMPUTE) { result |= PipelineStages::COMPUTE_SHADER } if stages.intersects( ShaderStages::RAYGEN | ShaderStages::ANY_HIT | ShaderStages::CLOSEST_HIT | ShaderStages::MISS | ShaderStages::INTERSECTION | ShaderStages::CALLABLE, ) { result |= PipelineStages::RAY_TRACING_SHADER } if stages.intersects(ShaderStages::TASK) { result |= PipelineStages::TASK_SHADER; } if stages.intersects(ShaderStages::MESH) { result |= PipelineStages::MESH_SHADER; } if stages.intersects(ShaderStages::SUBPASS_SHADING) { result |= PipelineStages::SUBPASS_SHADING; } result } } fn check_spirv_version(device: &Device, mut version: Version) -> Result<(), ShaderSupportError> { version.patch = 0; // Ignore the patch version match version { Version::V1_0 => {} Version::V1_1 | Version::V1_2 | Version::V1_3 => { if !(device.api_version() >= Version::V1_1) { return Err(ShaderSupportError::RequirementsNotMet(&[ "Vulkan API version 1.1", ])); } } Version::V1_4 => { if !(device.api_version() >= Version::V1_2 || device.enabled_extensions().khr_spirv_1_4) { return Err(ShaderSupportError::RequirementsNotMet(&[ "Vulkan API version 1.2", "extension `khr_spirv_1_4`", ])); } } Version::V1_5 => { if !(device.api_version() >= Version::V1_2) { return Err(ShaderSupportError::RequirementsNotMet(&[ "Vulkan API version 1.2", ])); } } Version::V1_6 => { if !(device.api_version() >= Version::V1_3) { return Err(ShaderSupportError::RequirementsNotMet(&[ "Vulkan API version 1.3", ])); } } _ => return Err(ShaderSupportError::NotSupportedByVulkan), } Ok(()) }