blob: 32b674e9fca2e46d32209cc8222f7dc5a54e2fdb [file] [log] [blame]
// Copyright (c) 2021 The Vulkano developers
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT
// license <LICENSE-MIT or https://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
use super::{
spirv_grammar::{SpirvGrammar, SpirvKindEnumerant},
write_file, IndexMap, VkRegistryData,
};
use heck::ToSnakeCase;
use indexmap::map::Entry;
use once_cell::sync::Lazy;
use proc_macro2::{Ident, TokenStream};
use quote::{format_ident, quote};
use regex::Regex;
use vk_parse::SpirvExtOrCap;
pub fn write(vk_data: &VkRegistryData, grammar: &SpirvGrammar) {
let grammar_enumerants = grammar
.operand_kinds
.iter()
.find(|operand_kind| operand_kind.kind == "Capability")
.unwrap()
.enumerants
.as_slice();
let spirv_capabilities_output = spirv_reqs_output(
&spirv_capabilities_members(&vk_data.spirv_capabilities, grammar_enumerants),
false,
);
let spirv_extensions_output =
spirv_reqs_output(&spirv_extensions_members(&vk_data.spirv_extensions), true);
write_file(
"spirv_reqs.rs",
format!(
"vk.xml header version {}.{}.{}",
vk_data.header_version.0, vk_data.header_version.1, vk_data.header_version.2
),
quote! {
#spirv_capabilities_output
#spirv_extensions_output
},
);
}
#[derive(Clone, Debug)]
struct SpirvReqsMember {
name: String,
enables: Vec<(Enable, String)>,
}
#[derive(Clone, Debug, PartialEq)]
enum Enable {
Core((String, String)),
Extension(Ident),
Feature(Ident),
Property((Ident, PropertyValue)),
}
#[derive(Clone, Debug, PartialEq)]
enum PropertyValue {
Bool,
BoolMember(Vec<Ident>),
}
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 }
};
if enables.is_empty() {
quote! {
#arm => (),
}
} else {
let enables_items = enables.iter().map(|(enable, _description)| match enable {
Enable::Core((major, minor)) => {
let version = format_ident!("V{}_{}", major, minor);
quote! {
device.api_version() >= 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)::*))
},
};
quote! {
device.physical_device().properties().#name #access .unwrap_or(false)
}
}
});
let description_items = enables.iter().map(|(_enable, description)| description);
quote! {
#arm => {
if !(#(#enables_items)||*) {
return Err(ShaderSupportError::RequirementsNotMet(&[
#(#description_items),*
]));
}
},
}
}
});
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 {
#(#items)*
_ => return Err(ShaderSupportError::NotSupportedByVulkan),
}
Ok(())
}
}
}
}
fn spirv_capabilities_members(
capabilities: &[&SpirvExtOrCap],
grammar_enumerants: &[SpirvKindEnumerant],
) -> Vec<SpirvReqsMember> {
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();
// Find the capability in the list of enumerants, then go backwards through the list to find
// the first enumerant with the same value.
let enumerant_pos = match grammar_enumerants
.iter()
.position(|enumerant| enumerant.enumerant == ext_or_cap.name)
{
Some(pos) => pos,
// This can happen if the grammar file is behind on the vk.xml file.
None => continue,
};
let enumerant_value = &grammar_enumerants[enumerant_pos].value;
let name = if let Some(enumerant) = grammar_enumerants[..enumerant_pos]
.iter()
.rev()
.take_while(|enumerant| &enumerant.value == enumerant_value)
.last()
{
// Another enumerant was found with the same value, so this one is an alias.
&enumerant.enumerant
} else {
// No other enumerant was found, so this is its canonical name.
&ext_or_cap.name
};
match members.entry(name.clone()) {
Entry::Occupied(entry) => {
entry.into_mut().enables.extend(enables);
}
Entry::Vacant(entry) => {
entry.insert(SpirvReqsMember {
name: name.clone(),
enables,
});
}
}
}
members.into_iter().map(|(_, v)| v).collect()
}
fn spirv_extensions_members(extensions: &[&SpirvExtOrCap]) -> Vec<SpirvReqsMember> {
extensions
.iter()
.map(|ext_or_cap| {
let enables: Vec<_> = ext_or_cap.enables.iter().filter_map(make_enable).collect();
SpirvReqsMember {
name: ext_or_cap.name.clone(),
enables,
}
})
.collect()
}
fn make_enable(enable: &vk_parse::Enable) -> Option<(Enable, String)> {
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;
}
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();
(
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();
(
Enable::Extension(format_ident!("{}", extension_name)),
format!("device extension `{}`", extension_name),
)
}
vk_parse::Enable::Feature(feature) => {
let feature_name = feature.feature.to_snake_case();
(
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!(),
})
}