From 17dbd1ac2c7225c832b8fecccdf490568a0ba686 Mon Sep 17 00:00:00 2001 From: Rua Date: Mon, 24 Jul 2023 22:21:05 +0200 Subject: [PATCH] ValidationError-ify `ShaderModule` (#2268) * ValidationError-ify `ShaderModule` * Typo * Remove leftover comments * slice, not Iterator * Update vulkano/src/shader/mod.rs Co-authored-by: marc0246 <40955683+marc0246@users.noreply.github.com> * Update vulkano/src/shader/mod.rs Co-authored-by: marc0246 <40955683+marc0246@users.noreply.github.com> --------- Co-authored-by: marc0246 <40955683+marc0246@users.noreply.github.com> --- examples/src/bin/runtime-shader/main.rs | 53 +- vulkano-shaders/src/codegen.rs | 8 +- vulkano-shaders/src/lib.rs | 15 +- vulkano/autogen/extensions.rs | 37 +- vulkano/autogen/formats.rs | 3 +- vulkano/autogen/mod.rs | 54 +++ vulkano/autogen/spirv_reqs.rs | 369 ++++++++------ vulkano/src/pipeline/cache.rs | 16 +- vulkano/src/pipeline/compute.rs | 5 +- vulkano/src/shader/mod.rs | 612 +++++++++++------------- 10 files changed, 646 insertions(+), 526 deletions(-) diff --git a/examples/src/bin/runtime-shader/main.rs b/examples/src/bin/runtime-shader/main.rs index 5ca2454d..dc25098d 100644 --- a/examples/src/bin/runtime-shader/main.rs +++ b/examples/src/bin/runtime-shader/main.rs @@ -21,7 +21,7 @@ // // Vulkano uses shaderc to build your shaders internally. -use std::{fs::File, io::Read, sync::Arc}; +use std::{fs::File, io::Read, path::Path, sync::Arc}; use vulkano::{ buffer::{Buffer, BufferContents, BufferCreateInfo, BufferUsage}, command_buffer::{ @@ -49,7 +49,7 @@ use vulkano::{ GraphicsPipeline, PipelineLayout, PipelineShaderStageCreateInfo, }, render_pass::{Framebuffer, FramebufferCreateInfo, RenderPass, Subpass}, - shader::ShaderModule, + shader::{ShaderModule, ShaderModuleCreateInfo}, swapchain::{ acquire_next_image, AcquireError, Surface, Swapchain, SwapchainCreateInfo, SwapchainPresentInfo, @@ -178,26 +178,22 @@ fn main() { let graphics_pipeline = { let vs = { - let mut f = File::open("src/bin/runtime-shader/vert.spv").expect( - "can't find file `src/bin/runtime-shader/vert.spv`, this example needs to be run from \ - the root of the example crate", - ); - let mut v = vec![]; - f.read_to_end(&mut v).unwrap(); + let code = read_spirv_words_from_file("src/bin/runtime-shader/vert.spv"); // Create a ShaderModule on a device the same Shader::load does it. // NOTE: You will have to verify correctness of the data by yourself! - let module = unsafe { ShaderModule::from_bytes(device.clone(), &v).unwrap() }; + let module = unsafe { + ShaderModule::new(device.clone(), ShaderModuleCreateInfo::new(&code)).unwrap() + }; module.entry_point("main").unwrap() }; let fs = { - let mut f = File::open("src/bin/runtime-shader/frag.spv") - .expect("can't find file `src/bin/runtime-shader/frag.spv`"); - let mut v = vec![]; - f.read_to_end(&mut v).unwrap(); + let code = read_spirv_words_from_file("src/bin/runtime-shader/frag.spv"); - let module = unsafe { ShaderModule::from_bytes(device.clone(), &v).unwrap() }; + let module = unsafe { + ShaderModule::new(device.clone(), ShaderModuleCreateInfo::new(&code)).unwrap() + }; module.entry_point("main").unwrap() }; @@ -427,3 +423,32 @@ fn window_size_dependent_setup( }) .collect::>() } + +fn read_spirv_words_from_file(path: impl AsRef) -> Vec { + // Read the file. + let path = path.as_ref(); + let mut bytes = vec![]; + let mut file = File::open(path).unwrap_or_else(|err| { + panic!( + "can't open file `{}`: {}.\n\ + Note: this example needs to be run from the root of the example crate", + path.display(), + err, + ) + }); + file.read_to_end(&mut bytes).unwrap(); + + // Convert the bytes to words. + // SPIR-V is defined to be always little-endian, so this may need an endianness conversion. + assert!( + bytes.len() % 4 == 0, + "file `{}` does not contain a whole number of SPIR-V words", + path.display(), + ); + + // TODO: Use `slice::array_chunks` once it's stable. + bytes + .chunks_exact(4) + .map(|chunk| u32::from_le_bytes(chunk.try_into().unwrap())) + .collect() +} diff --git a/vulkano-shaders/src/codegen.rs b/vulkano-shaders/src/codegen.rs index 0b5cd120..9cf5a4f0 100644 --- a/vulkano-shaders/src/codegen.rs +++ b/vulkano-shaders/src/codegen.rs @@ -275,20 +275,20 @@ pub(super) fn reflect( device: ::std::sync::Arc<::vulkano::device::Device>, ) -> ::std::result::Result< ::std::sync::Arc<::vulkano::shader::ShaderModule>, - ::vulkano::shader::ShaderModuleCreationError, + ::vulkano::Validated<::vulkano::VulkanError>, > { let _bytes = ( #( #include_bytes ),* ); static WORDS: &[u32] = &[ #( #words ),* ]; unsafe { - ::vulkano::shader::ShaderModule::from_words_with_data( + ::vulkano::shader::ShaderModule::new_with_data( device, - WORDS, + ::vulkano::shader::ShaderModuleCreateInfo::new(&WORDS), + [ #( #entry_points ),* ], #spirv_version, [ #( #spirv_capabilities ),* ], [ #( #spirv_extensions ),* ], - [ #( #entry_points ),* ], ) } } diff --git a/vulkano-shaders/src/lib.rs b/vulkano-shaders/src/lib.rs index a1de9906..d87e9cbd 100644 --- a/vulkano-shaders/src/lib.rs +++ b/vulkano-shaders/src/lib.rs @@ -31,10 +31,10 @@ //! //! The macro generates the following items of interest: //! -//! - The `load` constructor. This function takes an `Arc`, calls -//! [`ShaderModule::from_words_with_data`] with the passed-in device and the shader data provided -//! via the macro, and returns `Result, ShaderModuleCreationError>`. -//! Before doing so, it loops through every capability instruction in the shader data, +//! - The `load` constructor. This function takes an `Arc`, constructs a +//! [`ShaderModule`] with the passed-in device and the shader data provided +//! via the macro, and returns `Result, Validated>`. +//! Before doing so, it checks every capability instruction in the shader data, //! verifying that the passed-in `Device` has the appropriate features enabled. //! - If the `shaders` option is used, then instead of one `load` constructor, there is one for //! each shader. They are named based on the provided names, `load_first`, `load_second` etc. @@ -50,8 +50,7 @@ //! ``` //! # fn main() {} //! # use std::sync::Arc; -//! # use vulkano::shader::{ShaderModuleCreationError, ShaderModule}; -//! # use vulkano::device::Device; +//! # use vulkano::{device::Device, shader::ShaderModule, Validated, VulkanError}; //! # //! # mod vs { //! # vulkano_shaders::shader!{ @@ -75,7 +74,7 @@ //! } //! //! impl Shaders { -//! pub fn load(device: Arc) -> Result { +//! pub fn load(device: Arc) -> Result> { //! Ok(Self { //! vs: vs::load(device)?, //! }) @@ -208,7 +207,7 @@ //! //! [`cargo-env-vars`]: https://doc.rust-lang.org/cargo/reference/environment-variables.html //! [cargo-expand]: https://github.com/dtolnay/cargo-expand -//! [`ShaderModule::from_words_with_data`]: vulkano::shader::ShaderModule::from_words_with_data +//! [`ShaderModule`]: vulkano::shader::ShaderModule //! [pipeline]: vulkano::pipeline //! [`set_target_env`]: shaderc::CompileOptions::set_target_env //! [`set_target_spirv`]: shaderc::CompileOptions::set_target_spirv diff --git a/vulkano/autogen/extensions.rs b/vulkano/autogen/extensions.rs index 31630571..fc6ce0e4 100644 --- a/vulkano/autogen/extensions.rs +++ b/vulkano/autogen/extensions.rs @@ -7,13 +7,13 @@ // notice may not be copied, modified, or distributed except // according to those terms. -use super::{write_file, IndexMap, VkRegistryData}; +use super::{write_file, IndexMap, RequiresOneOf, VkRegistryData}; use heck::ToSnakeCase; use once_cell::sync::Lazy; use proc_macro2::{Ident, Literal, TokenStream}; use quote::{format_ident, quote}; use regex::Regex; -use std::{cmp::min, fmt::Write as _, ops::BitOrAssign}; +use std::fmt::Write as _; use vk_parse::Extension; // This is not included in vk.xml, so it's added here manually @@ -49,35 +49,6 @@ struct ExtensionsMember { status: Option, } -#[derive(Clone, Debug, Default, PartialEq, Eq)] -pub struct RequiresOneOf { - pub api_version: Option<(u32, u32)>, - pub device_extensions: Vec, - pub instance_extensions: Vec, -} - -impl BitOrAssign<&Self> for RequiresOneOf { - fn bitor_assign(&mut self, rhs: &Self) { - self.api_version = match (self.api_version, rhs.api_version) { - (None, None) => None, - (None, Some(x)) | (Some(x), None) => Some(x), - (Some(lhs), Some(rhs)) => Some(min(lhs, rhs)), - }; - - for rhs_ext in &rhs.device_extensions { - if !self.device_extensions.contains(rhs_ext) { - self.device_extensions.push(rhs_ext.to_owned()); - } - } - - for rhs_ext in &rhs.instance_extensions { - if !self.instance_extensions.contains(rhs_ext) { - self.instance_extensions.push(rhs_ext.to_owned()); - } - } - } -} - #[derive(Clone, Debug)] enum ExtensionStatus { PromotedTo(Requires), @@ -129,6 +100,7 @@ fn device_extensions_output(members: &[ExtensionsMember]) -> TokenStream { api_version, device_extensions, instance_extensions, + features: _, }| { (device_extensions.is_empty() && (api_version.is_some() || !instance_extensions.is_empty())) @@ -204,6 +176,7 @@ fn device_extensions_output(members: &[ExtensionsMember]) -> TokenStream { api_version, device_extensions, instance_extensions: _, + features: _, }| { (!device_extensions.is_empty()).then(|| { let condition_items = api_version @@ -310,6 +283,7 @@ fn instance_extensions_output(members: &[ExtensionsMember]) -> TokenStream { api_version, device_extensions: _, instance_extensions, + features: _, }| { api_version.filter(|_| instance_extensions.is_empty()).map(|(major, minor)| { let version = format_ident!("V{}_{}", major, minor); @@ -363,6 +337,7 @@ fn instance_extensions_output(members: &[ExtensionsMember]) -> TokenStream { api_version, device_extensions: _, instance_extensions, + features: _, }| { (!instance_extensions.is_empty()).then(|| { let condition_items = api_version diff --git a/vulkano/autogen/formats.rs b/vulkano/autogen/formats.rs index d850b0de..b1bf7f1e 100644 --- a/vulkano/autogen/formats.rs +++ b/vulkano/autogen/formats.rs @@ -7,7 +7,7 @@ // notice may not be copied, modified, or distributed except // according to those terms. -use super::{extensions::RequiresOneOf, write_file, IndexMap, VkRegistryData}; +use super::{write_file, IndexMap, RequiresOneOf, VkRegistryData}; use heck::ToSnakeCase; use once_cell::sync::Lazy; use proc_macro2::{Ident, Literal, TokenStream}; @@ -271,6 +271,7 @@ fn formats_output(members: &[FormatMember]) -> TokenStream { api_version, device_extensions, instance_extensions, + features: _, }| { let condition_items = (api_version.iter().map(|(major, minor)| { let version = format_ident!("V{}_{}", major, minor); diff --git a/vulkano/autogen/mod.rs b/vulkano/autogen/mod.rs index fbe387f3..52aab061 100644 --- a/vulkano/autogen/mod.rs +++ b/vulkano/autogen/mod.rs @@ -12,10 +12,12 @@ use ahash::HashMap; use once_cell::sync::Lazy; use regex::Regex; use std::{ + cmp::min, env, fmt::Display, fs::File, io::{BufWriter, Write}, + ops::BitOrAssign, path::Path, process::Command, }; @@ -451,3 +453,55 @@ fn suffix_key(name: &str) -> u32 { 0 } } + +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct RequiresOneOf { + pub api_version: Option<(u32, u32)>, + pub device_extensions: Vec, + pub instance_extensions: Vec, + pub features: Vec, +} + +impl RequiresOneOf { + pub fn is_empty(&self) -> bool { + let Self { + api_version, + device_extensions, + instance_extensions, + features, + } = self; + + api_version.is_none() + && device_extensions.is_empty() + && instance_extensions.is_empty() + && features.is_empty() + } +} + +impl BitOrAssign<&Self> for RequiresOneOf { + fn bitor_assign(&mut self, rhs: &Self) { + self.api_version = match (self.api_version, rhs.api_version) { + (None, None) => None, + (None, Some(x)) | (Some(x), None) => Some(x), + (Some(lhs), Some(rhs)) => Some(min(lhs, rhs)), + }; + + for rhs in &rhs.device_extensions { + if !self.device_extensions.contains(rhs) { + self.device_extensions.push(rhs.to_owned()); + } + } + + for rhs in &rhs.instance_extensions { + if !self.instance_extensions.contains(rhs) { + self.instance_extensions.push(rhs.to_owned()); + } + } + + for rhs in &rhs.features { + if !self.features.contains(rhs) { + self.features.push(rhs.to_owned()); + } + } + } +} diff --git a/vulkano/autogen/spirv_reqs.rs b/vulkano/autogen/spirv_reqs.rs index 32b674e9..c7cfbf88 100644 --- a/vulkano/autogen/spirv_reqs.rs +++ b/vulkano/autogen/spirv_reqs.rs @@ -9,12 +9,12 @@ use super::{ spirv_grammar::{SpirvGrammar, SpirvKindEnumerant}, - write_file, IndexMap, VkRegistryData, + write_file, IndexMap, RequiresOneOf, VkRegistryData, }; use heck::ToSnakeCase; use indexmap::map::Entry; use once_cell::sync::Lazy; -use proc_macro2::{Ident, TokenStream}; +use proc_macro2::TokenStream; use quote::{format_ident, quote}; use regex::Regex; use vk_parse::SpirvExtOrCap; @@ -46,100 +46,199 @@ pub fn write(vk_data: &VkRegistryData, grammar: &SpirvGrammar) { ); } -#[derive(Clone, Debug)] struct SpirvReqsMember { name: String, - enables: Vec<(Enable, String)>, + requires_one_of: RequiresOneOf, + requires_properties: Vec, } -#[derive(Clone, Debug, PartialEq)] -enum Enable { - Core((String, String)), - Extension(Ident), - Feature(Ident), - Property((Ident, PropertyValue)), +struct RequiresProperty { + name: String, + value: PropertyValue, } -#[derive(Clone, Debug, PartialEq)] enum PropertyValue { Bool, - BoolMember(Vec), + FlagsIntersects { + path: TokenStream, + ty: String, + flag: String, + }, } -fn spirv_reqs_output(members: &[SpirvReqsMember], extension: bool) -> TokenStream { - let items = members.iter().map(|SpirvReqsMember { name, enables }| { - let arm = if extension { - quote! { #name } - } else { - let name = format_ident!("{}", name); - quote! { Capability::#name } - }; +fn spirv_reqs_output(members: &[SpirvReqsMember], is_extension: bool) -> TokenStream { + let (item_type, fn_def, not_supported_vuid, item_vuid) = if is_extension { + ( + "extension", + quote! { validate_spirv_extension(device: &Device, item: &str) }, + "VUID-VkShaderModuleCreateInfo-pCode-08739", + "VUID-VkShaderModuleCreateInfo-pCode-08740", + ) + } else { + ( + "capability", + quote! { validate_spirv_capability(device: &Device, item: Capability) }, + "VUID-VkShaderModuleCreateInfo-pCode-08741", + "VUID-VkShaderModuleCreateInfo-pCode-08742", + ) + }; - if enables.is_empty() { - quote! { - #arm => (), - } - } else { - let enables_items = enables.iter().map(|(enable, _description)| match enable { - Enable::Core((major, minor)) => { + let items = members.iter().map( + |SpirvReqsMember { + name, + requires_one_of, + requires_properties, + }| { + let arm = if is_extension { + quote! { #name } + } else { + let name = format_ident!("{}", name); + quote! { Capability::#name } + }; + + if !requires_one_of.is_empty() { + let &RequiresOneOf { + api_version, + ref device_extensions, + instance_extensions: _, + ref features, + } = requires_one_of; + + let condition_items = (api_version.iter().map(|version| { + let version = format_ident!("V{}_{}", version.0, version.1); + quote! { api_version >= crate::Version::#version } + })) + .chain(device_extensions.iter().map(|name| { + let ident = format_ident!("{}", name); + quote! { device_extensions.#ident } + })) + .chain(features.iter().map(|name| { + let ident = format_ident!("{}", name); + quote! { features.#ident } + })); + let requires_one_of_items = (api_version.iter().map(|(major, minor)| { let version = format_ident!("V{}_{}", major, minor); quote! { - device.api_version() >= Version::#version + crate::RequiresAllOf(&[ + crate::Requires::APIVersion(crate::Version::#version), + ]), } - } - Enable::Extension(extension) => quote! { - device.enabled_extensions().#extension - }, - Enable::Feature(feature) => quote! { - device.enabled_features().#feature - }, - Enable::Property((name, value)) => { - let access = match value { - PropertyValue::Bool => quote! {}, - PropertyValue::BoolMember(member) => quote! { - .map(|x| x.intersects(#(#member)::*)) - }, - }; - + })) + .chain(device_extensions.iter().map(|name| { quote! { - device.physical_device().properties().#name #access .unwrap_or(false) + crate::RequiresAllOf(&[ + crate::Requires::DeviceExtension(#name), + ]), } - } - }); - - let description_items = enables.iter().map(|(_enable, description)| description); - - quote! { - #arm => { - if !(#(#enables_items)||*) { - return Err(ShaderSupportError::RequirementsNotMet(&[ - #(#description_items),* - ])); + })) + .chain(features.iter().map(|name| { + quote! { + crate::RequiresAllOf(&[ + crate::Requires::Feature(#name), + ]), } - }, - } - } - }); + })); + let problem = format!("uses the SPIR-V {} `{}`", item_type, name); - if extension { - quote! { - fn check_spirv_extension(device: &Device, extension: &str) -> Result<(), ShaderSupportError> { - match extension { - #(#items)* - _ => return Err(ShaderSupportError::NotSupportedByVulkan), + quote! { + #arm => { + if !(#(#condition_items)||*) { + return Err(Box::new(crate::ValidationError { + problem: #problem.into(), + requires_one_of: crate::RequiresOneOf(&[ + #(#requires_one_of_items)* + ]), + vuids: &[#item_vuid], + ..Default::default() + })); + } + }, } - Ok(()) - } - } - } else { - quote! { - fn check_spirv_capability(device: &Device, capability: Capability) -> Result<(), ShaderSupportError> { - match capability { - #(#items)* - _ => return Err(ShaderSupportError::NotSupportedByVulkan), + } else if !requires_properties.is_empty() { + let condition_items = requires_properties.iter().map( + |RequiresProperty { name, value }| { + let name = format_ident!("{}", name); + let access = match value { + PropertyValue::Bool => quote! {}, + PropertyValue::FlagsIntersects { path, ty, flag } => { + let ty = format_ident!("{}", ty); + let flag = format_ident!("{}", flag); + quote! { + .map(|x| x.intersects(#path :: #ty :: #flag)) + } + } + }; + + quote! { + device.physical_device().properties().#name #access .unwrap_or(false) + } + }, + ); + let problem = { + let requirements_items: Vec<_> = requires_properties + .iter() + .map(|RequiresProperty { name, value }| match value { + PropertyValue::Bool => format!("`{}` must be `true`", name), + PropertyValue::FlagsIntersects { path: _, ty, flag } => { + format!("`{}` must contain `{}::{}`", name, ty, flag) + } + }) + .collect(); + + format!( + "uses the SPIR-V {} `{}`, but the device properties do not meet at \ + least one of the requirements ({})", + item_type, + name, + requirements_items.join(" or ") + ) + }; + + quote! { + #arm => { + if !(#(#condition_items)||*) { + return Err(Box::new(crate::ValidationError { + problem: #problem.into(), + vuids: &[#item_vuid], + ..Default::default() + })); + } + }, + } + } else { + quote! { + #arm => (), } - Ok(()) } + }, + ); + + let problem = format!( + "uses the SPIR-V {} `{{item:?}}`, which is not supported by Vulkan", + item_type, + ); + quote! { + fn #fn_def -> Result<(), Box> { + #[allow(unused_variables)] + let api_version = device.api_version(); + #[allow(unused_variables)] + let device_extensions = device.enabled_extensions(); + #[allow(unused_variables)] + let features = device.enabled_features(); + #[allow(unused_variables)] + let properties = device.physical_device().properties(); + + match item { + #(#items)* + _ => { + return Err(Box::new(crate::ValidationError { + problem: format!(#problem).into(), + vuids: &[#not_supported_vuid], + ..Default::default() + })); + } + } + Ok(()) } } } @@ -151,8 +250,7 @@ fn spirv_capabilities_members( let mut members: IndexMap = IndexMap::default(); for ext_or_cap in capabilities { - let mut enables: Vec<_> = ext_or_cap.enables.iter().filter_map(make_enable).collect(); - enables.dedup(); + let (requires_one_of, requires_properties) = make_requires(&ext_or_cap.enables); // Find the capability in the list of enumerants, then go backwards through the list to find // the first enumerant with the same value. @@ -181,12 +279,15 @@ fn spirv_capabilities_members( match members.entry(name.clone()) { Entry::Occupied(entry) => { - entry.into_mut().enables.extend(enables); + let member = entry.into_mut(); + member.requires_one_of |= &requires_one_of; + member.requires_properties.extend(requires_properties); } Entry::Vacant(entry) => { entry.insert(SpirvReqsMember { name: name.clone(), - enables, + requires_one_of, + requires_properties, }); } } @@ -199,77 +300,75 @@ fn spirv_extensions_members(extensions: &[&SpirvExtOrCap]) -> Vec = ext_or_cap.enables.iter().filter_map(make_enable).collect(); + let (requires_one_of, requires_properties) = make_requires(&ext_or_cap.enables); SpirvReqsMember { name: ext_or_cap.name.clone(), - enables, + requires_one_of, + requires_properties, } }) .collect() } -fn make_enable(enable: &vk_parse::Enable) -> Option<(Enable, String)> { +fn make_requires(enables: &[vk_parse::Enable]) -> (RequiresOneOf, Vec) { static VK_API_VERSION: Lazy = Lazy::new(|| Regex::new(r"^VK_(?:API_)?VERSION_(\d+)_(\d+)$").unwrap()); static BIT: Lazy = Lazy::new(|| Regex::new(r"_BIT(?:_NV)?$").unwrap()); - if matches!(enable, vk_parse::Enable::Version(version) if version == "VK_VERSION_1_0") { - return None; + let mut requires_one_of = RequiresOneOf::default(); + let mut requires_properties = vec![]; + + for enable in enables { + match enable { + vk_parse::Enable::Version(version) => { + if version != "VK_VERSION_1_0" { + let captures = VK_API_VERSION.captures(version).unwrap(); + let major = captures.get(1).unwrap().as_str(); + let minor = captures.get(1).unwrap().as_str(); + + requires_one_of.api_version = + Some((major.parse().unwrap(), minor.parse().unwrap())); + } + } + vk_parse::Enable::Extension(extension) => { + requires_one_of + .device_extensions + .push(extension.strip_prefix("VK_").unwrap().to_snake_case()); + } + vk_parse::Enable::Feature(feature) => { + requires_one_of + .features + .push(feature.feature.to_snake_case()); + } + vk_parse::Enable::Property(property) => { + let name = property.member.to_snake_case(); + + let value = if property.value == "VK_TRUE" { + PropertyValue::Bool + } else if let Some(member) = property.value.strip_prefix("VK_SUBGROUP_FEATURE_") { + PropertyValue::FlagsIntersects { + path: quote! { crate::device::physical }, + ty: "SubgroupFeatures".to_string(), + flag: BIT.replace(member, "").to_string(), + } + } else { + unimplemented!() + }; + + requires_properties.push(RequiresProperty { name, value }); + } + _ => unimplemented!(), + } } - Some(match enable { - vk_parse::Enable::Version(version) => { - let captures = VK_API_VERSION.captures(version).unwrap(); - let major = captures.get(1).unwrap().as_str(); - let minor = captures.get(1).unwrap().as_str(); + assert!(requires_one_of.is_empty() || requires_properties.is_empty()); - ( - Enable::Core((major.parse().unwrap(), minor.parse().unwrap())), - format!("Vulkan API version {}.{}", major, minor), - ) - } - vk_parse::Enable::Extension(extension) => { - let extension_name = extension.strip_prefix("VK_").unwrap().to_snake_case(); + requires_one_of.device_extensions.sort_unstable(); + requires_one_of.device_extensions.dedup(); - ( - Enable::Extension(format_ident!("{}", extension_name)), - format!("device extension `{}`", extension_name), - ) - } - vk_parse::Enable::Feature(feature) => { - let feature_name = feature.feature.to_snake_case(); + requires_one_of.features.sort_unstable(); + requires_one_of.features.dedup(); - ( - Enable::Feature(format_ident!("{}", feature_name)), - format!("feature `{}`", feature_name), - ) - } - vk_parse::Enable::Property(property) => { - let property_name = property.member.to_snake_case(); - - let (value, description) = if property.value == "VK_TRUE" { - (PropertyValue::Bool, format!("property `{}`", property_name)) - } else if let Some(member) = property.value.strip_prefix("VK_SUBGROUP_FEATURE_") { - let member = BIT.replace(member, ""); - ( - PropertyValue::BoolMember( - ["crate", "device", "physical", "SubgroupFeatures", &member] - .into_iter() - .map(|s| format_ident!("{}", s)) - .collect(), - ), - format!("property `{}.{}`", property_name, member), - ) - } else { - unimplemented!() - }; - - ( - Enable::Property((format_ident!("{}", property_name), value)), - description, - ) - } - _ => unimplemented!(), - }) + (requires_one_of, requires_properties) } diff --git a/vulkano/src/pipeline/cache.rs b/vulkano/src/pipeline/cache.rs index 0c4228ea..c3b78335 100644 --- a/vulkano/src/pipeline/cache.rs +++ b/vulkano/src/pipeline/cache.rs @@ -400,7 +400,7 @@ mod tests { layout::PipelineDescriptorSetLayoutCreateInfo, ComputePipeline, PipelineLayout, PipelineShaderStageCreateInfo, }, - shader::ShaderModule, + shader::{ShaderModule, ShaderModuleCreateInfo}, }; #[test] @@ -431,7 +431,8 @@ mod tests { 196611, 2, 450, 262149, 4, 1852399981, 0, 131091, 2, 196641, 3, 2, 327734, 2, 4, 0, 3, 131320, 5, 65789, 65592, ]; - let module = ShaderModule::from_words(device.clone(), &MODULE).unwrap(); + let module = + ShaderModule::new(device.clone(), ShaderModuleCreateInfo::new(&MODULE)).unwrap(); module.entry_point("main").unwrap() }; @@ -477,7 +478,9 @@ mod tests { 1, 196611, 2, 450, 262149, 4, 1852399981, 0, 131091, 2, 196641, 3, 2, 327734, 2, 4, 0, 3, 131320, 5, 65789, 65592, ]; - let module = ShaderModule::from_words(device.clone(), &MODULE).unwrap(); + let module = + ShaderModule::new(device.clone(), ShaderModuleCreateInfo::new(&MODULE)) + .unwrap(); module.entry_point("main").unwrap() }; @@ -518,7 +521,9 @@ mod tests { 327734, 2, 4, 0, 3, 131320, 5, 262203, 7, 8, 7, 327745, 13, 14, 11, 12, 262205, 6, 15, 14, 196670, 8, 15, 65789, 65592, ]; - let module = ShaderModule::from_words(device.clone(), &MODULE).unwrap(); + let module = + ShaderModule::new(device.clone(), ShaderModuleCreateInfo::new(&MODULE)) + .unwrap(); module.entry_point("main").unwrap() }; @@ -565,7 +570,8 @@ mod tests { 196611, 2, 450, 262149, 4, 1852399981, 0, 131091, 2, 196641, 3, 2, 327734, 2, 4, 0, 3, 131320, 5, 65789, 65592, ]; - let module = ShaderModule::from_words(device.clone(), &MODULE).unwrap(); + let module = + ShaderModule::new(device.clone(), ShaderModuleCreateInfo::new(&MODULE)).unwrap(); module.entry_point("main").unwrap() }; diff --git a/vulkano/src/pipeline/compute.rs b/vulkano/src/pipeline/compute.rs index fd830a6c..867bdd2f 100644 --- a/vulkano/src/pipeline/compute.rs +++ b/vulkano/src/pipeline/compute.rs @@ -394,7 +394,7 @@ mod tests { ComputePipeline, Pipeline, PipelineBindPoint, PipelineLayout, PipelineShaderStageCreateInfo, }, - shader::ShaderModule, + shader::{ShaderModule, ShaderModuleCreateInfo}, sync::{now, GpuFuture}, }; @@ -435,7 +435,8 @@ mod tests { 8, 9, 2, 262187, 6, 10, 0, 262194, 6, 11, 3735928559, 262176, 12, 2, 6, 327734, 2, 4, 0, 3, 131320, 5, 327745, 12, 13, 9, 10, 196670, 13, 11, 65789, 65592, ]; - let module = ShaderModule::from_words(device.clone(), &MODULE).unwrap(); + let module = + ShaderModule::new(device.clone(), ShaderModuleCreateInfo::new(&MODULE)).unwrap(); module.entry_point("main").unwrap() }; diff --git a/vulkano/src/shader/mod.rs b/vulkano/src/shader/mod.rs index 196d0264..15af22d8 100644 --- a/vulkano/src/shader/mod.rs +++ b/vulkano/src/shader/mod.rs @@ -139,16 +139,18 @@ use crate::{ instance::InstanceOwnedDebugWrapper, macros::{impl_id_counter, vulkan_bitflags_enum}, pipeline::{graphics::input_assembly::PrimitiveTopology, layout::PushConstantRange}, - shader::spirv::{Capability, Spirv, SpirvError}, + shader::spirv::{Capability, Spirv}, sync::PipelineStages, - OomError, Version, VulkanError, VulkanObject, + Requires, RequiresAllOf, RequiresOneOf, Validated, ValidationError, Version, VulkanError, + VulkanObject, }; use ahash::{HashMap, HashSet}; +use bytemuck::bytes_of; +use half::f16; +use spirv::ExecutionModel; use std::{ borrow::Cow, collections::hash_map::Entry, - error::Error, - fmt::{Display, Error as FmtError, Formatter}, mem::{align_of, discriminant, size_of, size_of_val, MaybeUninit}, num::NonZeroU64, ptr, @@ -158,10 +160,6 @@ use std::{ pub mod reflect; pub mod spirv; -use bytemuck::bytes_of; -use half::f16; -use spirv::ExecutionModel; - // Generated by build.rs include!(concat!(env!("OUT_DIR"), "/spirv_reqs.rs")); @@ -176,95 +174,95 @@ pub struct ShaderModule { } 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. + /// Creates a new shader module. /// /// # Safety /// - /// - The SPIR-V code is not validated beyond the minimum needed to extract the information. + /// - The SPIR-V code in `create_info.code` must be valid. #[inline] - pub unsafe fn from_words( + pub unsafe fn new( device: Arc, - words: &[u32], - ) -> Result, ShaderModuleCreationError> { - let spirv = Spirv::new(words)?; + create_info: ShaderModuleCreateInfo<'_>, + ) -> Result, Validated> { + let spirv = Spirv::new(create_info.code).map_err(|err| { + Box::new(ValidationError { + context: "create_info.code".into(), + problem: format!("error while parsing: {}", err).into(), + ..Default::default() + }) + })?; - Self::from_words_with_data( + Self::new_with_data( device, - words, + create_info, + reflect::entry_points(&spirv), 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 `bytes` is not aligned to 4. - /// - Panics if the length of `bytes` is not a multiple of 4. - #[inline] - pub unsafe fn from_bytes( + // This is public only for vulkano-shaders, do not use otherwise. + #[doc(hidden)] + pub unsafe fn new_with_data<'a>( device: Arc, - bytes: &[u8], - ) -> Result, ShaderModuleCreationError> { - assert!(bytes.as_ptr() as usize % align_of::() == 0); - assert!(bytes.len() % size_of::() == 0); - - Self::from_words( - device, - std::slice::from_raw_parts(bytes.as_ptr() as *const _, bytes.len() / 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], + create_info: ShaderModuleCreateInfo<'_>, + entry_points: impl IntoIterator, spirv_version: Version, spirv_capabilities: impl IntoIterator, spirv_extensions: impl IntoIterator, + ) -> Result, Validated> { + Self::validate_new( + &device, + &create_info, + spirv_version, + spirv_capabilities, + spirv_extensions, + )?; + + Ok(Self::new_with_data_unchecked( + device, + create_info, + entry_points, + )?) + } + + fn validate_new<'a>( + device: &Device, + create_info: &ShaderModuleCreateInfo<'_>, + spirv_version: Version, + spirv_capabilities: impl IntoIterator, + spirv_extensions: impl IntoIterator, + ) -> Result<(), Box> { + create_info + .validate(device, spirv_version, spirv_capabilities, spirv_extensions) + .map_err(|err| err.add_context("create_info"))?; + + Ok(()) + } + + #[cfg_attr(not(feature = "document_unchecked"), doc(hidden))] + pub unsafe fn new_unchecked( + device: Arc, + create_info: ShaderModuleCreateInfo<'_>, + ) -> Result, VulkanError> { + let spirv = Spirv::new(create_info.code).unwrap(); + + Self::new_with_data_unchecked(device, create_info, reflect::entry_points(&spirv)) + } + + unsafe fn new_with_data_unchecked( + device: Arc, + create_info: ShaderModuleCreateInfo<'_>, entry_points: impl IntoIterator, - ) -> Result, ShaderModuleCreationError> { - if let Err(reason) = check_spirv_version(&device, spirv_version) { - return Err(ShaderModuleCreationError::SpirvVersionNotSupported { - version: spirv_version, - reason, - }); - } - - for &capability in spirv_capabilities { - if let Err(reason) = check_spirv_capability(&device, capability) { - return Err(ShaderModuleCreationError::SpirvCapabilityNotSupported { - capability, - reason, - }); - } - } - - for extension in spirv_extensions { - if let Err(reason) = check_spirv_extension(&device, extension) { - return Err(ShaderModuleCreationError::SpirvExtensionNotSupported { - extension: extension.to_owned(), - reason, - }); - } - } + ) -> Result, VulkanError> { + let &ShaderModuleCreateInfo { code, _ne: _ } = &create_info; let handle = { let infos = ash::vk::ShaderModuleCreateInfo { flags: ash::vk::ShaderModuleCreateFlags::empty(), - code_size: size_of_val(words), - p_code: words.as_ptr(), + code_size: size_of_val(code), + p_code: code.as_ptr(), ..Default::default() }; @@ -281,6 +279,38 @@ impl ShaderModule { output.assume_init() }; + Ok(Self::from_handle_with_data( + device, + handle, + create_info, + entry_points, + )) + } + + /// Creates a new `ShaderModule` from a raw object handle. + /// + /// # Safety + /// + /// - `handle` must be a valid Vulkan object handle created from `device`. + /// - `create_info` must match the info used to create the object. + pub unsafe fn from_handle( + device: Arc, + handle: ash::vk::ShaderModule, + create_info: ShaderModuleCreateInfo<'_>, + ) -> Arc { + let spirv = Spirv::new(create_info.code).unwrap(); + + Self::from_handle_with_data(device, handle, create_info, reflect::entry_points(&spirv)) + } + + unsafe fn from_handle_with_data( + device: Arc, + handle: ash::vk::ShaderModule, + create_info: ShaderModuleCreateInfo<'_>, + entry_points: impl IntoIterator, + ) -> Arc { + let ShaderModuleCreateInfo { code: _, _ne: _ } = create_info; + let mut entry_point_map: HashMap> = HashMap::default(); let entry_point_infos: Vec<_> = entry_points @@ -295,39 +325,54 @@ impl ShaderModule { }) .collect(); - Ok(Arc::new(ShaderModule { + Arc::new(ShaderModule { handle, device: InstanceOwnedDebugWrapper(device), id: Self::next_id(), entry_point_map, entry_point_infos, - })) + }) } - /// As `from_words_with_data`, but takes a slice of bytes. + /// 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. + #[deprecated(since = "0.34.0", note = "use `new` instead")] + #[inline] + pub unsafe fn from_words( + device: Arc, + words: &[u32], + ) -> Result, Validated> { + Self::new(device, ShaderModuleCreateInfo::new(words)) + } + + /// As `from_words`, but takes a slice of bytes. /// /// # Panics /// /// - Panics if `bytes` is not aligned to 4. /// - Panics if the length of `bytes` is not a multiple of 4. - pub unsafe fn from_bytes_with_data<'a>( + #[deprecated( + since = "0.34.0", + note = "read little-endian words yourself, and then use `new` instead" + )] + #[inline] + pub unsafe fn from_bytes( device: Arc, bytes: &[u8], - spirv_version: Version, - spirv_capabilities: impl IntoIterator, - spirv_extensions: impl IntoIterator, - entry_points: impl IntoIterator, - ) -> Result, ShaderModuleCreationError> { + ) -> Result, Validated> { assert!(bytes.as_ptr() as usize % align_of::() == 0); assert!(bytes.len() % size_of::() == 0); - Self::from_words_with_data( + Self::new( device, - std::slice::from_raw_parts(bytes.as_ptr() as *const _, bytes.len() / size_of::()), - spirv_version, - spirv_capabilities, - spirv_extensions, - entry_points, + ShaderModuleCreateInfo::new(std::slice::from_raw_parts( + bytes.as_ptr() as *const _, + bytes.len() / size_of::(), + )), ) } @@ -393,6 +438,113 @@ unsafe impl DeviceOwned for ShaderModule { impl_id_counter!(ShaderModule); +pub struct ShaderModuleCreateInfo<'a> { + /// The SPIR-V code, in the form of 32-bit words. + /// + /// There is no default value. + pub code: &'a [u32], + + pub _ne: crate::NonExhaustive, +} + +impl<'a> ShaderModuleCreateInfo<'a> { + /// Returns a `ShaderModuleCreateInfo` with the specified `code`. + #[inline] + pub fn new(code: &'a [u32]) -> Self { + Self { + code, + _ne: crate::NonExhaustive(()), + } + } + + pub(crate) fn validate<'b>( + &self, + device: &Device, + mut spirv_version: Version, + spirv_capabilities: impl IntoIterator, + spirv_extensions: impl IntoIterator, + ) -> Result<(), Box> { + let &Self { code, _ne: _ } = self; + + if code.is_empty() { + return Err(Box::new(ValidationError { + context: "code".into(), + problem: "is empty".into(), + vuids: &["VUID-VkShaderModuleCreateInfo-codeSize-01085"], + ..Default::default() + })); + } + + { + spirv_version.patch = 0; // Ignore the patch version + + match spirv_version { + Version::V1_0 => None, + Version::V1_1 | Version::V1_2 | Version::V1_3 => { + (!(device.api_version() >= Version::V1_1)).then_some(RequiresOneOf(&[ + RequiresAllOf(&[Requires::APIVersion(Version::V1_1)]), + ])) + } + Version::V1_4 => (!(device.api_version() >= Version::V1_2 + || device.enabled_extensions().khr_spirv_1_4)) + .then_some(RequiresOneOf(&[ + RequiresAllOf(&[Requires::APIVersion(Version::V1_2)]), + RequiresAllOf(&[Requires::DeviceExtension("khr_spirv_1_4")]), + ])), + Version::V1_5 => { + (!(device.api_version() >= Version::V1_2)).then_some(RequiresOneOf(&[ + RequiresAllOf(&[Requires::APIVersion(Version::V1_2)]), + ])) + } + Version::V1_6 => { + (!(device.api_version() >= Version::V1_3)).then_some(RequiresOneOf(&[ + RequiresAllOf(&[Requires::APIVersion(Version::V1_3)]), + ])) + } + _ => { + return Err(Box::new(ValidationError { + context: "code".into(), + problem: format!( + "uses SPIR-V version {}.{}, which is not supported by Vulkan", + spirv_version.major, spirv_version.minor + ) + .into(), + // vuids? + ..Default::default() + })); + } + } + } + .map_or(Ok(()), |requires_one_of| { + Err(Box::new(ValidationError { + context: "code".into(), + problem: format!( + "uses SPIR-V version {}.{}", + spirv_version.major, spirv_version.minor + ) + .into(), + requires_one_of, + ..Default::default() + })) + })?; + + for &capability in spirv_capabilities { + validate_spirv_capability(device, capability).map_err(|err| err.add_context("code"))?; + } + + for extension in spirv_extensions { + validate_spirv_extension(device, extension).map_err(|err| err.add_context("code"))?; + } + + // VUID-VkShaderModuleCreateInfo-pCode-08736 + // VUID-VkShaderModuleCreateInfo-pCode-08737 + // VUID-VkShaderModuleCreateInfo-pCode-08738 + // Unsafe + + Ok(()) + } +} + /// The information associated with a single entry point in a shader. #[derive(Clone, Debug)] pub struct EntryPointInfo { @@ -636,7 +788,7 @@ 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> { + pub fn merge(&mut self, other: &Self) -> Result<(), Box> { let Self { descriptor_types, descriptor_count, @@ -654,29 +806,45 @@ impl DescriptorBindingRequirements { .iter() .any(|ty| other.descriptor_types.contains(ty)) { - return Err(DescriptorBindingRequirementsIncompatible::DescriptorType); + return Err(Box::new(ValidationError { + problem: "the allowed descriptor types of the two descriptors do not overlap" + .into(), + ..Default::default() + })); } if let (Some(first), Some(second)) = (*image_format, other.image_format) { if first != second { - return Err(DescriptorBindingRequirementsIncompatible::ImageFormat); + return Err(Box::new(ValidationError { + problem: "the descriptors require different formats".into(), + ..Default::default() + })); } } if let (Some(first), Some(second)) = (*image_scalar_type, other.image_scalar_type) { if first != second { - return Err(DescriptorBindingRequirementsIncompatible::ImageScalarType); + return Err(Box::new(ValidationError { + problem: "the descriptors require different scalar types".into(), + ..Default::default() + })); } } if let (Some(first), Some(second)) = (*image_view_type, other.image_view_type) { if first != second { - return Err(DescriptorBindingRequirementsIncompatible::ImageViewType); + return Err(Box::new(ValidationError { + problem: "the descriptors require different image view types".into(), + ..Default::default() + })); } } if *image_multisampled != other.image_multisampled { - return Err(DescriptorBindingRequirementsIncompatible::ImageMultisampled); + return Err(Box::new(ValidationError { + problem: "the multisampling requirements of the descriptors differ".into(), + ..Default::default() + })); } /* Merge */ @@ -896,12 +1064,12 @@ impl ShaderInterface { /// /// Returns `Ok` if the two interfaces are compatible. #[inline] - pub fn matches(&self, other: &ShaderInterface) -> Result<(), ShaderInterfaceMismatchError> { + pub fn matches(&self, other: &ShaderInterface) -> Result<(), Box> { 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, - }); + return Err(Box::new(ValidationError { + problem: "the number of elements in the shader interfaces are not equal".into(), + ..Default::default() + })); } for a in self.elements() { @@ -913,17 +1081,28 @@ impl ShaderInterface { .find(|e| loc >= e.location && loc < e.location + e.ty.num_locations()) { None => { - return Err(ShaderInterfaceMismatchError::MissingElement { location: loc }) + return Err(Box::new(ValidationError { + problem: format!( + "the second shader is missing an interface element at location {}", + loc + ) + .into(), + ..Default::default() + })); } Some(b) => b, }; if a.ty != b.ty { - return Err(ShaderInterfaceMismatchError::TypeMismatch { - location: loc, - self_ty: a.ty, - other_ty: b.ty, - }); + return Err(Box::new(ValidationError { + problem: format!( + "the interface element at location {} does not have the same type \ + in both shaders", + loc + ) + .into(), + ..Default::default() + })); } // TODO: enforce this? @@ -1187,222 +1366,3 @@ impl From for PipelineStages { 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(()) -} - -/// Error that can happen when creating a new shader module. -#[derive(Clone, Debug)] -pub enum ShaderModuleCreationError { - OomError(OomError), - SpirvCapabilityNotSupported { - capability: Capability, - reason: ShaderSupportError, - }, - SpirvError(SpirvError), - SpirvExtensionNotSupported { - extension: String, - reason: ShaderSupportError, - }, - SpirvVersionNotSupported { - version: Version, - reason: ShaderSupportError, - }, -} - -impl Error for ShaderModuleCreationError { - 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 ShaderModuleCreationError { - 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 ShaderModuleCreationError { - fn from(err: VulkanError) -> Self { - Self::OomError(err.into()) - } -} - -impl From for ShaderModuleCreationError { - 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(", "), - ), - } - } -} - -/// 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",) - } - } - } -} - -/// 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" - } - } - ) - } -}