mirror of
https://github.com/vulkano-rs/vulkano.git
synced 2024-11-21 22:34:43 +00:00
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:
parent
3300f9095d
commit
17dbd1ac2c
@ -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()
|
||||
}
|
||||
|
@ -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 ),* ],
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<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 {
|
||||
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<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(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<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![];
|
||||
|
||||
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)
|
||||
}
|
||||
|
@ -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()
|
||||
};
|
||||
|
||||
|
@ -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()
|
||||
};
|
||||
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user