// 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. //! Stage of a graphics pipeline. //! //! 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 does not provide any functionality that checks and introspects the SPIR-V //! code, therefore the whole shader-related API is unsafe. You are encouraged to use the //! `vulkano-shaders` crate that will generate Rust code that wraps around vulkano's shaders API. use crate::check_errors; use crate::descriptor_set::layout::DescriptorSetDesc; use crate::device::Device; use crate::format::Format; use crate::pipeline::input_assembly::PrimitiveTopology; use crate::pipeline::layout::PipelineLayoutPcRange; use crate::sync::PipelineStages; use crate::OomError; use crate::VulkanObject; use smallvec::SmallVec; use std::borrow::Cow; use std::error; use std::ffi::CStr; use std::fmt; use std::mem; use std::mem::MaybeUninit; use std::ops::BitOr; use std::ops::Range; use std::ptr; use std::sync::Arc; /// Contains SPIR-V code with one or more entry points. /// /// Note that it is advised to wrap around a `ShaderModule` with a struct that is different for /// each shader. #[derive(Debug)] pub struct ShaderModule { // The module. module: ash::vk::ShaderModule, // Pointer to the device. device: Arc, } impl ShaderModule { /// Builds a new shader module from SPIR-V bytes. /// /// # Safety /// /// - The SPIR-V code is not validated. /// - The SPIR-V code may require some features that are not enabled. This isn't checked by /// this function either. /// pub unsafe fn new(device: Arc, spirv: &[u8]) -> Result, OomError> { debug_assert!((spirv.len() % 4) == 0); Self::from_ptr(device, spirv.as_ptr() as *const _, spirv.len()) } /// Builds a new shader module from SPIR-V 32-bit words. /// /// # Safety /// /// - The SPIR-V code is not validated. /// - The SPIR-V code may require some features that are not enabled. This isn't checked by /// this function either. /// pub unsafe fn from_words( device: Arc, spirv: &[u32], ) -> Result, OomError> { Self::from_ptr(device, spirv.as_ptr(), spirv.len() * mem::size_of::()) } /// Builds a new shader module from SPIR-V. /// /// # Safety /// /// - The SPIR-V code is not validated. /// - The SPIR-V code may require some features that are not enabled. This isn't checked by /// this function either. /// unsafe fn from_ptr( device: Arc, spirv: *const u32, spirv_len: usize, ) -> Result, OomError> { let module = { let infos = ash::vk::ShaderModuleCreateInfo { flags: ash::vk::ShaderModuleCreateFlags::empty(), code_size: spirv_len, p_code: spirv, ..Default::default() }; let fns = device.fns(); let mut output = MaybeUninit::uninit(); check_errors(fns.v1_0.create_shader_module( device.internal_object(), &infos, ptr::null(), output.as_mut_ptr(), ))?; output.assume_init() }; Ok(Arc::new(ShaderModule { module: module, device: device, })) } /// Gets access to an entry point contained in this module. /// /// This is purely a *logical* operation. It returns a struct that *represents* the entry /// point but doesn't actually do anything. /// /// # Safety /// /// - The user must check that the entry point exists in the module, as this is not checked /// by Vulkan. /// - The input, output and layout must correctly describe the input, output and layout used /// by this stage. /// pub unsafe fn graphics_entry_point<'a, D>( &'a self, name: &'a CStr, descriptor_set_layout_descs: D, push_constant_range: Option, spec_constants: &'static [SpecializationMapEntry], input: ShaderInterface, output: ShaderInterface, ty: GraphicsShaderType, ) -> GraphicsEntryPoint<'a> where D: IntoIterator, { GraphicsEntryPoint { module: self, name, descriptor_set_layout_descs: descriptor_set_layout_descs.into_iter().collect(), push_constant_range, spec_constants, input, output, ty, } } /// Gets access to an entry point contained in this module. /// /// This is purely a *logical* operation. It returns a struct that *represents* the entry /// point but doesn't actually do anything. /// /// # Safety /// /// - The user must check that the entry point exists in the module, as this is not checked /// by Vulkan. /// - The layout must correctly describe the layout used by this stage. /// #[inline] pub unsafe fn compute_entry_point<'a, D>( &'a self, name: &'a CStr, descriptor_set_layout_descs: D, push_constant_range: Option, spec_constants: &'static [SpecializationMapEntry], ) -> ComputeEntryPoint<'a> where D: IntoIterator, { ComputeEntryPoint { module: self, name, descriptor_set_layout_descs: descriptor_set_layout_descs.into_iter().collect(), push_constant_range, spec_constants, } } } unsafe impl VulkanObject for ShaderModule { type Object = ash::vk::ShaderModule; #[inline] fn internal_object(&self) -> ash::vk::ShaderModule { self.module } } impl Drop for ShaderModule { #[inline] fn drop(&mut self) { unsafe { let fns = self.device.fns(); fns.v1_0 .destroy_shader_module(self.device.internal_object(), self.module, ptr::null()); } } } pub unsafe trait EntryPointAbstract { /// Returns the module this entry point comes from. fn module(&self) -> &ShaderModule; /// Returns the name of the entry point. fn name(&self) -> &CStr; /// Returns a description of the descriptor set layouts. fn descriptor_set_layout_descs(&self) -> &[DescriptorSetDesc]; /// Returns the push constant ranges. fn push_constant_range(&self) -> &Option; /// Returns the layout of the specialization constants. fn spec_constants(&self) -> &[SpecializationMapEntry]; } /// Represents a shader entry point in a shader module. /// /// Can be obtained by calling `entry_point()` on the shader module. #[derive(Clone, Debug)] pub struct GraphicsEntryPoint<'a> { module: &'a ShaderModule, name: &'a CStr, descriptor_set_layout_descs: SmallVec<[DescriptorSetDesc; 16]>, push_constant_range: Option, spec_constants: &'static [SpecializationMapEntry], input: ShaderInterface, output: ShaderInterface, ty: GraphicsShaderType, } impl<'a> GraphicsEntryPoint<'a> { /// Returns the input attributes used by the shader stage. #[inline] pub fn input(&self) -> &ShaderInterface { &self.input } /// Returns the output attributes used by the shader stage. #[inline] pub fn output(&self) -> &ShaderInterface { &self.output } /// Returns the type of shader. #[inline] pub fn ty(&self) -> GraphicsShaderType { self.ty } } unsafe impl<'a> EntryPointAbstract for GraphicsEntryPoint<'a> { #[inline] fn module(&self) -> &ShaderModule { self.module } #[inline] fn name(&self) -> &CStr { self.name } #[inline] fn descriptor_set_layout_descs(&self) -> &[DescriptorSetDesc] { &self.descriptor_set_layout_descs } #[inline] fn push_constant_range(&self) -> &Option { &self.push_constant_range } #[inline] fn spec_constants(&self) -> &[SpecializationMapEntry] { self.spec_constants } } #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum GraphicsShaderType { Vertex, TessellationControl, TessellationEvaluation, Geometry(GeometryShaderExecutionMode), Fragment, } /// Declares which type of primitives are expected by the geometry shader. #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum GeometryShaderExecutionMode { Points, Lines, LinesWithAdjacency, Triangles, TrianglesWithAdjacency, } impl GeometryShaderExecutionMode { /// Returns true if the given primitive topology can be used with this execution mode. #[inline] pub fn matches(&self, input: PrimitiveTopology) -> bool { match (*self, input) { (GeometryShaderExecutionMode::Points, PrimitiveTopology::PointList) => true, (GeometryShaderExecutionMode::Lines, PrimitiveTopology::LineList) => true, (GeometryShaderExecutionMode::Lines, PrimitiveTopology::LineStrip) => true, ( GeometryShaderExecutionMode::LinesWithAdjacency, PrimitiveTopology::LineListWithAdjacency, ) => true, ( GeometryShaderExecutionMode::LinesWithAdjacency, PrimitiveTopology::LineStripWithAdjacency, ) => true, (GeometryShaderExecutionMode::Triangles, PrimitiveTopology::TriangleList) => true, (GeometryShaderExecutionMode::Triangles, PrimitiveTopology::TriangleStrip) => true, (GeometryShaderExecutionMode::Triangles, PrimitiveTopology::TriangleFan) => true, ( GeometryShaderExecutionMode::TrianglesWithAdjacency, PrimitiveTopology::TriangleListWithAdjacency, ) => true, ( GeometryShaderExecutionMode::TrianglesWithAdjacency, PrimitiveTopology::TriangleStripWithAdjacency, ) => true, _ => false, } } } /// Represents the entry point of a compute shader in a shader module. /// /// Can be obtained by calling `compute_shader_entry_point()` on the shader module. #[derive(Debug, Clone)] pub struct ComputeEntryPoint<'a> { module: &'a ShaderModule, name: &'a CStr, descriptor_set_layout_descs: SmallVec<[DescriptorSetDesc; 16]>, push_constant_range: Option, spec_constants: &'static [SpecializationMapEntry], } unsafe impl<'a> EntryPointAbstract for ComputeEntryPoint<'a> { #[inline] fn module(&self) -> &ShaderModule { self.module } #[inline] fn name(&self) -> &CStr { self.name } #[inline] fn descriptor_set_layout_descs(&self) -> &[DescriptorSetDesc] { &self.descriptor_set_layout_descs } #[inline] fn push_constant_range(&self) -> &Option { &self.push_constant_range } #[inline] fn spec_constants(&self) -> &[SpecializationMapEntry] { self.spec_constants } } /// 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: could this be made safe? #[inline] pub unsafe fn new_unchecked(elements: Vec) -> ShaderInterface { ShaderInterface { elements } } /// Creates a description of an empty shader interface. 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. 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() { for loc in a.location.clone() { let b = match other .elements() .iter() .find(|e| loc >= e.location.start && loc < e.location.end) { None => { return Err(ShaderInterfaceMismatchError::MissingElement { location: loc }) } Some(b) => b, }; if a.format != b.format { return Err(ShaderInterfaceMismatchError::FormatMismatch { location: loc, self_format: a.format, other_format: b.format, }); } // 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 { /// Range of locations covered by the element. pub location: Range, /// Format of a each location of the element. pub format: Format, /// Name of the element, or `None` if the name is unknown. pub name: Option>, } /// 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 format of an element does not match. FormatMismatch { /// Location of the element that mismatches. location: u32, /// Format in the first interface. self_format: Format, /// Format in the second interface. other_format: Format, }, } impl error::Error for ShaderInterfaceMismatchError {} impl fmt::Display for ShaderInterfaceMismatchError { #[inline] fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { write!( fmt, "{}", match *self { ShaderInterfaceMismatchError::ElementsCountMismatch { .. } => { "the number of elements mismatches" } ShaderInterfaceMismatchError::MissingElement { .. } => "an element is missing", ShaderInterfaceMismatchError::FormatMismatch { .. } => { "the format of an element does not match" } } ) } } /// 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. /// /// Note that it is the shader module that chooses which type that implements /// `SpecializationConstants` it is possible to pass when creating the pipeline, through [the /// `EntryPointAbstract` trait](trait.EntryPointAbstract.html). Therefore there is generally no /// point to implement this trait yourself, unless you are also writing your own implementation of /// `EntryPointAbstract`. /// /// # Example /// /// ```rust /// use vulkano::pipeline::shader::SpecializationConstants; /// use vulkano::pipeline::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, } /// Describes a set of shader stages. // TODO: add example with BitOr #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub struct ShaderStages { pub vertex: bool, pub tessellation_control: bool, pub tessellation_evaluation: bool, pub geometry: bool, pub fragment: bool, pub compute: bool, } impl ShaderStages { /// Creates a `ShaderStages` struct will all stages set to `true`. // TODO: add example #[inline] pub const fn all() -> ShaderStages { ShaderStages { vertex: true, tessellation_control: true, tessellation_evaluation: true, geometry: true, fragment: true, compute: true, } } /// Creates a `ShaderStages` struct will all stages set to `false`. // TODO: add example #[inline] pub const fn none() -> ShaderStages { ShaderStages { vertex: false, tessellation_control: false, tessellation_evaluation: false, geometry: false, fragment: false, compute: false, } } /// Creates a `ShaderStages` struct with all graphics stages set to `true`. // TODO: add example #[inline] pub const fn all_graphics() -> ShaderStages { ShaderStages { vertex: true, tessellation_control: true, tessellation_evaluation: true, geometry: true, fragment: true, compute: false, } } /// Creates a `ShaderStages` struct with the compute stage set to `true`. // TODO: add example #[inline] pub const fn compute() -> ShaderStages { ShaderStages { vertex: false, tessellation_control: false, tessellation_evaluation: false, geometry: false, fragment: false, compute: true, } } /// Checks whether we have more stages enabled than `other`. // TODO: add example #[inline] pub const fn ensure_superset_of( &self, other: &ShaderStages, ) -> Result<(), ShaderStagesSupersetError> { if (self.vertex || !other.vertex) && (self.tessellation_control || !other.tessellation_control) && (self.tessellation_evaluation || !other.tessellation_evaluation) && (self.geometry || !other.geometry) && (self.fragment || !other.fragment) && (self.compute || !other.compute) { Ok(()) } else { Err(ShaderStagesSupersetError::NotSuperset) } } /// Checks whether any of the stages in `self` are also present in `other`. // TODO: add example #[inline] pub const fn intersects(&self, other: &ShaderStages) -> bool { (self.vertex && other.vertex) || (self.tessellation_control && other.tessellation_control) || (self.tessellation_evaluation && other.tessellation_evaluation) || (self.geometry && other.geometry) || (self.fragment && other.fragment) || (self.compute && other.compute) } } impl From for ash::vk::ShaderStageFlags { #[inline] fn from(val: ShaderStages) -> Self { let mut result = ash::vk::ShaderStageFlags::empty(); if val.vertex { result |= ash::vk::ShaderStageFlags::VERTEX; } if val.tessellation_control { result |= ash::vk::ShaderStageFlags::TESSELLATION_CONTROL; } if val.tessellation_evaluation { result |= ash::vk::ShaderStageFlags::TESSELLATION_EVALUATION; } if val.geometry { result |= ash::vk::ShaderStageFlags::GEOMETRY; } if val.fragment { result |= ash::vk::ShaderStageFlags::FRAGMENT; } if val.compute { result |= ash::vk::ShaderStageFlags::COMPUTE; } result } } impl From for ShaderStages { #[inline] fn from(val: ash::vk::ShaderStageFlags) -> Self { Self { vertex: val.intersects(ash::vk::ShaderStageFlags::VERTEX), tessellation_control: val.intersects(ash::vk::ShaderStageFlags::TESSELLATION_CONTROL), tessellation_evaluation: val .intersects(ash::vk::ShaderStageFlags::TESSELLATION_EVALUATION), geometry: val.intersects(ash::vk::ShaderStageFlags::GEOMETRY), fragment: val.intersects(ash::vk::ShaderStageFlags::FRAGMENT), compute: val.intersects(ash::vk::ShaderStageFlags::COMPUTE), } } } impl BitOr for ShaderStages { type Output = ShaderStages; #[inline] fn bitor(self, other: ShaderStages) -> ShaderStages { ShaderStages { vertex: self.vertex || other.vertex, tessellation_control: self.tessellation_control || other.tessellation_control, tessellation_evaluation: self.tessellation_evaluation || other.tessellation_evaluation, geometry: self.geometry || other.geometry, fragment: self.fragment || other.fragment, compute: self.compute || other.compute, } } } impl From for PipelineStages { #[inline] fn from(stages: ShaderStages) -> PipelineStages { PipelineStages { vertex_shader: stages.vertex, tessellation_control_shader: stages.tessellation_control, tessellation_evaluation_shader: stages.tessellation_evaluation, geometry_shader: stages.geometry, fragment_shader: stages.fragment, compute_shader: stages.compute, ..PipelineStages::none() } } } /// Error when checking that a `ShaderStages` object is a superset of another. #[derive(Debug, Clone)] pub enum ShaderStagesSupersetError { NotSuperset, } impl error::Error for ShaderStagesSupersetError {} impl fmt::Display for ShaderStagesSupersetError { #[inline] fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { write!( fmt, "{}", match *self { ShaderStagesSupersetError::NotSuperset => "shader stages not a superset", } ) } }