1 // Copyright (c) 2021 The Vulkano developers
2 // Licensed under the Apache License, Version 2.0
3 // <LICENSE-APACHE or
4 // https://www.apache.org/licenses/LICENSE-2.0> or the MIT
5 // license <LICENSE-MIT or https://opensource.org/licenses/MIT>,
6 // at your option. All files in the project carrying such
7 // notice may not be copied, modified, or distributed except
8 // according to those terms.
9 
10 use super::{
11     spirv_grammar::{SpirvGrammar, SpirvKindEnumerant},
12     write_file, IndexMap, VkRegistryData,
13 };
14 use heck::ToSnakeCase;
15 use indexmap::map::Entry;
16 use once_cell::sync::Lazy;
17 use proc_macro2::{Ident, TokenStream};
18 use quote::{format_ident, quote};
19 use regex::Regex;
20 use vk_parse::SpirvExtOrCap;
21 
write(vk_data: &VkRegistryData, grammar: &SpirvGrammar)22 pub fn write(vk_data: &VkRegistryData, grammar: &SpirvGrammar) {
23     let grammar_enumerants = grammar
24         .operand_kinds
25         .iter()
26         .find(|operand_kind| operand_kind.kind == "Capability")
27         .unwrap()
28         .enumerants
29         .as_slice();
30     let spirv_capabilities_output = spirv_reqs_output(
31         &spirv_capabilities_members(&vk_data.spirv_capabilities, grammar_enumerants),
32         false,
33     );
34     let spirv_extensions_output =
35         spirv_reqs_output(&spirv_extensions_members(&vk_data.spirv_extensions), true);
36     write_file(
37         "spirv_reqs.rs",
38         format!(
39             "vk.xml header version {}.{}.{}",
40             vk_data.header_version.0, vk_data.header_version.1, vk_data.header_version.2
41         ),
42         quote! {
43             #spirv_capabilities_output
44             #spirv_extensions_output
45         },
46     );
47 }
48 
49 #[derive(Clone, Debug)]
50 struct SpirvReqsMember {
51     name: String,
52     enables: Vec<(Enable, String)>,
53 }
54 
55 #[derive(Clone, Debug, PartialEq)]
56 enum Enable {
57     Core((String, String)),
58     Extension(Ident),
59     Feature(Ident),
60     Property((Ident, PropertyValue)),
61 }
62 
63 #[derive(Clone, Debug, PartialEq)]
64 enum PropertyValue {
65     Bool,
66     BoolMember(Vec<Ident>),
67 }
68 
spirv_reqs_output(members: &[SpirvReqsMember], extension: bool) -> TokenStream69 fn spirv_reqs_output(members: &[SpirvReqsMember], extension: bool) -> TokenStream {
70     let items = members.iter().map(|SpirvReqsMember { name, enables }| {
71         let arm = if extension {
72             quote! { #name }
73         } else {
74             let name = format_ident!("{}", name);
75             quote! { Capability::#name }
76         };
77 
78         if enables.is_empty() {
79             quote! {
80                 #arm => (),
81             }
82         } else {
83             let enables_items = enables.iter().map(|(enable, _description)| match enable {
84                 Enable::Core((major, minor)) => {
85                     let version = format_ident!("V{}_{}", major, minor);
86                     quote! {
87                         device.api_version() >= Version::#version
88                     }
89                 }
90                 Enable::Extension(extension) => quote! {
91                     device.enabled_extensions().#extension
92                 },
93                 Enable::Feature(feature) => quote! {
94                     device.enabled_features().#feature
95                 },
96                 Enable::Property((name, value)) => {
97                     let access = match value {
98                         PropertyValue::Bool => quote! {},
99                         PropertyValue::BoolMember(member) => quote! {
100                             .map(|x| x.intersects(#(#member)::*))
101                         },
102                     };
103 
104                     quote! {
105                         device.physical_device().properties().#name #access .unwrap_or(false)
106                     }
107                 }
108             });
109 
110             let description_items = enables.iter().map(|(_enable, description)| description);
111 
112             quote! {
113                 #arm => {
114                     if !(#(#enables_items)||*) {
115                         return Err(ShaderSupportError::RequirementsNotMet(&[
116                             #(#description_items),*
117                         ]));
118                     }
119                 },
120             }
121         }
122     });
123 
124     if extension {
125         quote! {
126             fn check_spirv_extension(device: &Device, extension: &str) -> Result<(), ShaderSupportError> {
127                 match extension {
128                     #(#items)*
129                     _ => return Err(ShaderSupportError::NotSupportedByVulkan),
130                 }
131                 Ok(())
132             }
133         }
134     } else {
135         quote! {
136             fn check_spirv_capability(device: &Device, capability: Capability) -> Result<(), ShaderSupportError> {
137                 match capability {
138                     #(#items)*
139                     _ => return Err(ShaderSupportError::NotSupportedByVulkan),
140                 }
141                 Ok(())
142             }
143         }
144     }
145 }
146 
spirv_capabilities_members( capabilities: &[&SpirvExtOrCap], grammar_enumerants: &[SpirvKindEnumerant], ) -> Vec<SpirvReqsMember>147 fn spirv_capabilities_members(
148     capabilities: &[&SpirvExtOrCap],
149     grammar_enumerants: &[SpirvKindEnumerant],
150 ) -> Vec<SpirvReqsMember> {
151     let mut members: IndexMap<String, SpirvReqsMember> = IndexMap::default();
152 
153     for ext_or_cap in capabilities {
154         let mut enables: Vec<_> = ext_or_cap.enables.iter().filter_map(make_enable).collect();
155         enables.dedup();
156 
157         // Find the capability in the list of enumerants, then go backwards through the list to find
158         // the first enumerant with the same value.
159         let enumerant_pos = match grammar_enumerants
160             .iter()
161             .position(|enumerant| enumerant.enumerant == ext_or_cap.name)
162         {
163             Some(pos) => pos,
164             // This can happen if the grammar file is behind on the vk.xml file.
165             None => continue,
166         };
167         let enumerant_value = &grammar_enumerants[enumerant_pos].value;
168 
169         let name = if let Some(enumerant) = grammar_enumerants[..enumerant_pos]
170             .iter()
171             .rev()
172             .take_while(|enumerant| &enumerant.value == enumerant_value)
173             .last()
174         {
175             // Another enumerant was found with the same value, so this one is an alias.
176             &enumerant.enumerant
177         } else {
178             // No other enumerant was found, so this is its canonical name.
179             &ext_or_cap.name
180         };
181 
182         match members.entry(name.clone()) {
183             Entry::Occupied(entry) => {
184                 entry.into_mut().enables.extend(enables);
185             }
186             Entry::Vacant(entry) => {
187                 entry.insert(SpirvReqsMember {
188                     name: name.clone(),
189                     enables,
190                 });
191             }
192         }
193     }
194 
195     members.into_iter().map(|(_, v)| v).collect()
196 }
197 
spirv_extensions_members(extensions: &[&SpirvExtOrCap]) -> Vec<SpirvReqsMember>198 fn spirv_extensions_members(extensions: &[&SpirvExtOrCap]) -> Vec<SpirvReqsMember> {
199     extensions
200         .iter()
201         .map(|ext_or_cap| {
202             let enables: Vec<_> = ext_or_cap.enables.iter().filter_map(make_enable).collect();
203 
204             SpirvReqsMember {
205                 name: ext_or_cap.name.clone(),
206                 enables,
207             }
208         })
209         .collect()
210 }
211 
make_enable(enable: &vk_parse::Enable) -> Option<(Enable, String)>212 fn make_enable(enable: &vk_parse::Enable) -> Option<(Enable, String)> {
213     static VK_API_VERSION: Lazy<Regex> =
214         Lazy::new(|| Regex::new(r"^VK_(?:API_)?VERSION_(\d+)_(\d+)$").unwrap());
215     static BIT: Lazy<Regex> = Lazy::new(|| Regex::new(r"_BIT(?:_NV)?$").unwrap());
216 
217     if matches!(enable, vk_parse::Enable::Version(version) if version == "VK_VERSION_1_0") {
218         return None;
219     }
220 
221     Some(match enable {
222         vk_parse::Enable::Version(version) => {
223             let captures = VK_API_VERSION.captures(version).unwrap();
224             let major = captures.get(1).unwrap().as_str();
225             let minor = captures.get(1).unwrap().as_str();
226 
227             (
228                 Enable::Core((major.parse().unwrap(), minor.parse().unwrap())),
229                 format!("Vulkan API version {}.{}", major, minor),
230             )
231         }
232         vk_parse::Enable::Extension(extension) => {
233             let extension_name = extension.strip_prefix("VK_").unwrap().to_snake_case();
234 
235             (
236                 Enable::Extension(format_ident!("{}", extension_name)),
237                 format!("device extension `{}`", extension_name),
238             )
239         }
240         vk_parse::Enable::Feature(feature) => {
241             let feature_name = feature.feature.to_snake_case();
242 
243             (
244                 Enable::Feature(format_ident!("{}", feature_name)),
245                 format!("feature `{}`", feature_name),
246             )
247         }
248         vk_parse::Enable::Property(property) => {
249             let property_name = property.member.to_snake_case();
250 
251             let (value, description) = if property.value == "VK_TRUE" {
252                 (PropertyValue::Bool, format!("property `{}`", property_name))
253             } else if let Some(member) = property.value.strip_prefix("VK_SUBGROUP_FEATURE_") {
254                 let member = BIT.replace(member, "");
255                 (
256                     PropertyValue::BoolMember(
257                         ["crate", "device", "physical", "SubgroupFeatures", &member]
258                             .into_iter()
259                             .map(|s| format_ident!("{}", s))
260                             .collect(),
261                     ),
262                     format!("property `{}.{}`", property_name, member),
263                 )
264             } else {
265                 unimplemented!()
266             };
267 
268             (
269                 Enable::Property((format_ident!("{}", property_name), value)),
270                 description,
271             )
272         }
273         _ => unimplemented!(),
274     })
275 }
276