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>
This commit is contained in:
Rua 2023-07-24 22:21:05 +02:00 committed by GitHub
parent 3300f9095d
commit 17dbd1ac2c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 646 additions and 526 deletions

View File

@ -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::<Vec<_>>()
}
fn read_spirv_words_from_file(path: impl AsRef<Path>) -> Vec<u32> {
// 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()
}

View File

@ -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 ),* ],
)
}
}

View File

@ -31,10 +31,10 @@
//!
//! The macro generates the following items of interest:
//!
//! - The `load` constructor. This function takes an `Arc<Device>`, calls
//! [`ShaderModule::from_words_with_data`] with the passed-in device and the shader data provided
//! via the macro, and returns `Result<Arc<ShaderModule>, ShaderModuleCreationError>`.
//! Before doing so, it loops through every capability instruction in the shader data,
//! - The `load` constructor. This function takes an `Arc<Device>`, constructs a
//! [`ShaderModule`] with the passed-in device and the shader data provided
//! via the macro, and returns `Result<Arc<ShaderModule>, Validated<VulkanError>>`.
//! 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<Device>) -> Result<Self, ShaderModuleCreationError> {
//! pub fn load(device: Arc<Device>) -> Result<Self, Validated<VulkanError>> {
//! 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

View File

@ -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<ExtensionStatus>,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct RequiresOneOf {
pub api_version: Option<(u32, u32)>,
pub device_extensions: Vec<String>,
pub instance_extensions: Vec<String>,
}
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

View File

@ -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);

View File

@ -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<String>,
pub instance_extensions: Vec<String>,
pub features: Vec<String>,
}
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());
}
}
}
}

View File

@ -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,103 +46,202 @@ 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<RequiresProperty>,
}
#[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<Ident>),
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 {
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",
)
};
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 enables.is_empty() {
quote! {
#arm => (),
}
} else {
let enables_items = enables.iter().map(|(enable, _description)| match enable {
Enable::Core((major, minor)) => {
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),
]),
}
}))
.chain(device_extensions.iter().map(|name| {
quote! {
crate::RequiresAllOf(&[
crate::Requires::DeviceExtension(#name),
]),
}
}))
.chain(features.iter().map(|name| {
quote! {
crate::RequiresAllOf(&[
crate::Requires::Feature(#name),
]),
}
}));
let problem = format!("uses the SPIR-V {} `{}`", item_type, name);
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()
}));
}
Enable::Extension(extension) => quote! {
device.enabled_extensions().#extension
},
Enable::Feature(feature) => quote! {
device.enabled_features().#feature
},
Enable::Property((name, value)) => {
}
} 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::BoolMember(member) => quote! {
.map(|x| x.intersects(#(#member)::*))
},
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();
let description_items = enables.iter().map(|(_enable, description)| description);
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 !(#(#enables_items)||*) {
return Err(ShaderSupportError::RequirementsNotMet(&[
#(#description_items),*
]));
if !(#(#condition_items)||*) {
return Err(Box::new(crate::ValidationError {
problem: #problem.into(),
vuids: &[#item_vuid],
..Default::default()
}));
}
},
}
}
});
if extension {
quote! {
fn check_spirv_extension(device: &Device, extension: &str) -> Result<(), ShaderSupportError> {
match extension {
#(#items)*
_ => return Err(ShaderSupportError::NotSupportedByVulkan),
}
Ok(())
}
}
} else {
quote! {
fn check_spirv_capability(device: &Device, capability: Capability) -> Result<(), ShaderSupportError> {
match capability {
#arm => (),
}
}
},
);
let problem = format!(
"uses the SPIR-V {} `{{item:?}}`, which is not supported by Vulkan",
item_type,
);
quote! {
fn #fn_def -> Result<(), Box<ValidationError>> {
#[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(ShaderSupportError::NotSupportedByVulkan),
_ => {
return Err(Box::new(crate::ValidationError {
problem: format!(#problem).into(),
vuids: &[#not_supported_vuid],
..Default::default()
}));
}
}
Ok(())
}
}
}
}
fn spirv_capabilities_members(
capabilities: &[&SpirvExtOrCap],
@ -151,8 +250,7 @@ fn spirv_capabilities_members(
let mut members: IndexMap<String, SpirvReqsMember> = 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<SpirvReqsMembe
extensions
.iter()
.map(|ext_or_cap| {
let enables: 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<RequiresProperty>) {
static VK_API_VERSION: Lazy<Regex> =
Lazy::new(|| Regex::new(r"^VK_(?:API_)?VERSION_(\d+)_(\d+)$").unwrap());
static BIT: Lazy<Regex> = 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![];
Some(match enable {
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();
(
Enable::Core((major.parse().unwrap(), minor.parse().unwrap())),
format!("Vulkan API version {}.{}", major, minor),
)
requires_one_of.api_version =
Some((major.parse().unwrap(), minor.parse().unwrap()));
}
}
vk_parse::Enable::Extension(extension) => {
let extension_name = extension.strip_prefix("VK_").unwrap().to_snake_case();
(
Enable::Extension(format_ident!("{}", extension_name)),
format!("device extension `{}`", extension_name),
)
requires_one_of
.device_extensions
.push(extension.strip_prefix("VK_").unwrap().to_snake_case());
}
vk_parse::Enable::Feature(feature) => {
let feature_name = feature.feature.to_snake_case();
(
Enable::Feature(format_ident!("{}", feature_name)),
format!("feature `{}`", feature_name),
)
requires_one_of
.features
.push(feature.feature.to_snake_case());
}
vk_parse::Enable::Property(property) => {
let property_name = property.member.to_snake_case();
let name = property.member.to_snake_case();
let (value, description) = if property.value == "VK_TRUE" {
(PropertyValue::Bool, format!("property `{}`", property_name))
let value = if property.value == "VK_TRUE" {
PropertyValue::Bool
} 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),
)
PropertyValue::FlagsIntersects {
path: quote! { crate::device::physical },
ty: "SubgroupFeatures".to_string(),
flag: BIT.replace(member, "").to_string(),
}
} else {
unimplemented!()
};
(
Enable::Property((format_ident!("{}", property_name), value)),
description,
)
requires_properties.push(RequiresProperty { name, value });
}
_ => unimplemented!(),
})
}
}
assert!(requires_one_of.is_empty() || requires_properties.is_empty());
requires_one_of.device_extensions.sort_unstable();
requires_one_of.device_extensions.dedup();
requires_one_of.features.sort_unstable();
requires_one_of.features.dedup();
(requires_one_of, requires_properties)
}

View File

@ -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()
};

View File

@ -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()
};

View File

@ -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<Device>,
words: &[u32],
) -> Result<Arc<ShaderModule>, ShaderModuleCreationError> {
let spirv = Spirv::new(words)?;
create_info: ShaderModuleCreateInfo<'_>,
) -> Result<Arc<ShaderModule>, Validated<VulkanError>> {
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<Device>,
bytes: &[u8],
) -> Result<Arc<ShaderModule>, ShaderModuleCreationError> {
assert!(bytes.as_ptr() as usize % align_of::<u32>() == 0);
assert!(bytes.len() % size_of::<u32>() == 0);
Self::from_words(
device,
std::slice::from_raw_parts(bytes.as_ptr() as *const _, bytes.len() / size_of::<u32>()),
)
}
/// 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<Device>,
words: &[u32],
create_info: ShaderModuleCreateInfo<'_>,
entry_points: impl IntoIterator<Item = EntryPointInfo>,
spirv_version: Version,
spirv_capabilities: impl IntoIterator<Item = &'a Capability>,
spirv_extensions: impl IntoIterator<Item = &'a str>,
) -> Result<Arc<ShaderModule>, Validated<VulkanError>> {
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<Item = &'a Capability>,
spirv_extensions: impl IntoIterator<Item = &'a str>,
) -> Result<(), Box<ValidationError>> {
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<Device>,
create_info: ShaderModuleCreateInfo<'_>,
) -> Result<Arc<ShaderModule>, 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<Device>,
create_info: ShaderModuleCreateInfo<'_>,
entry_points: impl IntoIterator<Item = EntryPointInfo>,
) -> Result<Arc<ShaderModule>, 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<Arc<ShaderModule>, 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<Device>,
handle: ash::vk::ShaderModule,
create_info: ShaderModuleCreateInfo<'_>,
) -> Arc<ShaderModule> {
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<Device>,
handle: ash::vk::ShaderModule,
create_info: ShaderModuleCreateInfo<'_>,
entry_points: impl IntoIterator<Item = EntryPointInfo>,
) -> Arc<ShaderModule> {
let ShaderModuleCreateInfo { code: _, _ne: _ } = create_info;
let mut entry_point_map: HashMap<String, HashMap<ExecutionModel, usize>> =
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<Device>,
words: &[u32],
) -> Result<Arc<ShaderModule>, Validated<VulkanError>> {
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<Device>,
bytes: &[u8],
spirv_version: Version,
spirv_capabilities: impl IntoIterator<Item = &'a Capability>,
spirv_extensions: impl IntoIterator<Item = &'a str>,
entry_points: impl IntoIterator<Item = EntryPointInfo>,
) -> Result<Arc<ShaderModule>, ShaderModuleCreationError> {
) -> Result<Arc<ShaderModule>, Validated<VulkanError>> {
assert!(bytes.as_ptr() as usize % align_of::<u32>() == 0);
assert!(bytes.len() % size_of::<u32>() == 0);
Self::from_words_with_data(
Self::new(
device,
std::slice::from_raw_parts(bytes.as_ptr() as *const _, bytes.len() / size_of::<u32>()),
spirv_version,
spirv_capabilities,
spirv_extensions,
entry_points,
ShaderModuleCreateInfo::new(std::slice::from_raw_parts(
bytes.as_ptr() as *const _,
bytes.len() / size_of::<u32>(),
)),
)
}
@ -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<Item = &'b Capability>,
spirv_extensions: impl IntoIterator<Item = &'b str>,
) -> Result<(), Box<ValidationError>> {
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<ValidationError>> {
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<ValidationError>> {
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<ShaderStages> 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<VulkanError> for ShaderModuleCreationError {
fn from(err: VulkanError) -> Self {
Self::OomError(err.into())
}
}
impl From<SpirvError> 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"
}
}
)
}
}