| // 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 heck::SnakeCase; |
| use indexmap::IndexMap; |
| use regex::Regex; |
| use std::{ |
| collections::{hash_map::Entry, HashMap}, |
| io::Write, |
| }; |
| use vk_parse::{Extension, Type, TypeMember, TypeMemberMarkup, TypeSpec}; |
| |
| // This is not included in vk.xml, so it's added here manually |
| fn requires_features(name: &str) -> &'static [&'static str] { |
| match name { |
| "sparseImageInt64Atomics" => &["shaderImageInt64Atomics"], |
| "sparseImageFloat32Atomics" => &["shaderImageFloat32Atomics"], |
| "sparseImageFloat32AtomicAdd" => &["shaderImageFloat32AtomicAdd"], |
| _ => &[], |
| } |
| } |
| |
| fn conflicts_features(name: &str) -> &'static [&'static str] { |
| match name { |
| "shadingRateImage" => &[ |
| "pipelineFragmentShadingRate", |
| "primitiveFragmentShadingRate", |
| "attachmentFragmentShadingRate", |
| ], |
| "fragmentDensityMap" => &[ |
| "pipelineFragmentShadingRate", |
| "primitiveFragmentShadingRate", |
| "attachmentFragmentShadingRate", |
| ], |
| "pipelineFragmentShadingRate" => &["shadingRateImage", "fragmentDensityMap"], |
| "primitiveFragmentShadingRate" => &["shadingRateImage", "fragmentDensityMap"], |
| "attachmentFragmentShadingRate" => &["shadingRateImage", "fragmentDensityMap"], |
| _ => &[], |
| } |
| } |
| |
| fn required_by_extensions(name: &str) -> &'static [&'static str] { |
| match name { |
| "shaderDrawParameters" => &["VK_KHR_shader_draw_parameters"], |
| "drawIndirectCount" => &["VK_KHR_draw_indirect_count"], |
| "samplerMirrorClampToEdge" => &["VK_KHR_sampler_mirror_clamp_to_edge"], |
| "descriptorIndexing" => &["VK_EXT_descriptor_indexing"], |
| "samplerFilterMinmax" => &["VK_EXT_sampler_filter_minmax"], |
| "shaderOutputViewportIndex" => &["VK_EXT_shader_viewport_index_layer"], |
| "shaderOutputLayer" => &["VK_EXT_shader_viewport_index_layer"], |
| _ => &[], |
| } |
| } |
| |
| pub fn write<W: Write>( |
| writer: &mut W, |
| types: &HashMap<&str, (&Type, Vec<&str>)>, |
| extensions: &IndexMap<&str, &Extension>, |
| ) { |
| write!(writer, "crate::device::features::features! {{").unwrap(); |
| |
| for feat in make_vulkano_features(types) { |
| write!(writer, "\n\t{} => {{", feat.member).unwrap(); |
| write_doc(writer, &feat); |
| write!(writer, "\n\t\tffi_name: {},", feat.ffi_name).unwrap(); |
| write!( |
| writer, |
| "\n\t\tffi_members: [{}],", |
| feat.ffi_members.join(", ") |
| ) |
| .unwrap(); |
| write!( |
| writer, |
| "\n\t\trequires_features: [{}],", |
| feat.requires_features.join(", ") |
| ) |
| .unwrap(); |
| write!( |
| writer, |
| "\n\t\tconflicts_features: [{}],", |
| feat.conflicts_features.join(", ") |
| ) |
| .unwrap(); |
| write!( |
| writer, |
| "\n\t\trequired_by_extensions: [{}],", |
| feat.required_by_extensions.join(", ") |
| ) |
| .unwrap(); |
| write!(writer, "\n\t}},").unwrap(); |
| } |
| |
| write!( |
| writer, |
| "\n}}\n\ncrate::device::features::features_ffi! {{\n\tapi_version,\n\tdevice_extensions,\n\tinstance_extensions," |
| ) |
| .unwrap(); |
| |
| for ffi in make_vulkano_features_ffi(types, extensions) { |
| write!(writer, "\n\t{} => {{", ffi.member).unwrap(); |
| write!(writer, "\n\t\tty: {},", ffi.ty).unwrap(); |
| write!( |
| writer, |
| "\n\t\tprovided_by: [{}],", |
| ffi.provided_by.join(", ") |
| ) |
| .unwrap(); |
| write!(writer, "\n\t\tconflicts: [{}],", ffi.conflicts.join(", ")).unwrap(); |
| write!(writer, "\n\t}},").unwrap(); |
| } |
| |
| write!(writer, "\n}}").unwrap(); |
| } |
| |
| #[derive(Clone, Debug)] |
| struct VulkanoFeature { |
| member: String, |
| vulkan_doc: String, |
| ffi_name: String, |
| ffi_members: Vec<String>, |
| requires_features: Vec<String>, |
| conflicts_features: Vec<String>, |
| required_by_extensions: Vec<String>, |
| } |
| |
| fn make_vulkano_features(types: &HashMap<&str, (&Type, Vec<&str>)>) -> Vec<VulkanoFeature> { |
| let mut features = HashMap::new(); |
| std::iter::once(&types["VkPhysicalDeviceFeatures"]) |
| .chain(sorted_structs(types).into_iter()) |
| .filter(|(ty, _)| { |
| ty.name.as_ref().map(|s| s.as_str()) == Some("VkPhysicalDeviceFeatures") |
| || ty.structextends.as_ref().map(|s| s.as_str()) |
| == Some("VkPhysicalDeviceFeatures2,VkDeviceCreateInfo") |
| }) |
| .for_each(|(ty, _)| { |
| let vulkan_ty_name = ty.name.as_ref().unwrap(); |
| |
| let ty_name = if vulkan_ty_name == "VkPhysicalDeviceFeatures" { |
| "features_vulkan10.features".to_owned() |
| } else { |
| ffi_member(vulkan_ty_name) |
| }; |
| |
| members(ty).into_iter().for_each(|name| { |
| let member = name.to_snake_case(); |
| match features.entry(member.clone()) { |
| Entry::Vacant(entry) => { |
| let requires_features = requires_features(name); |
| let conflicts_features = conflicts_features(name); |
| let required_by_extensions = required_by_extensions(name); |
| |
| entry.insert(VulkanoFeature { |
| member: member.clone(), |
| vulkan_doc: format!("{}.html#features-{}", vulkan_ty_name, name), |
| ffi_name: member, |
| ffi_members: vec![ty_name.to_owned()], |
| requires_features: requires_features |
| .into_iter() |
| .map(|&s| s.to_snake_case()) |
| .collect(), |
| conflicts_features: conflicts_features |
| .into_iter() |
| .map(|&s| s.to_snake_case()) |
| .collect(), |
| required_by_extensions: required_by_extensions |
| .iter() |
| .map(|vk_name| vk_name.strip_prefix("VK_").unwrap().to_snake_case()) |
| .collect(), |
| }); |
| } |
| Entry::Occupied(entry) => { |
| entry.into_mut().ffi_members.push(ty_name.to_owned()); |
| } |
| }; |
| }); |
| }); |
| |
| let mut names: Vec<_> = features.values().map(|feat| feat.member.clone()).collect(); |
| names.sort_unstable(); |
| names |
| .into_iter() |
| .map(|name| features.remove(&name).unwrap()) |
| .collect() |
| } |
| |
| fn write_doc<W>(writer: &mut W, feat: &VulkanoFeature) |
| where |
| W: Write, |
| { |
| write!(writer, "\n\t\tdoc: \"\n\t\t\t- [Vulkan documentation](https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/{})", feat.vulkan_doc).unwrap(); |
| |
| if !feat.requires_features.is_empty() { |
| let links: Vec<_> = feat |
| .requires_features |
| .iter() |
| .map(|ext| format!("[`{}`](crate::device::Features::{0})", ext)) |
| .collect(); |
| write!( |
| writer, |
| "\n\t\t\t- Requires feature{}: {}", |
| if feat.requires_features.len() > 1 { |
| "s" |
| } else { |
| "" |
| }, |
| links.join(", ") |
| ) |
| .unwrap(); |
| } |
| |
| if !feat.required_by_extensions.is_empty() { |
| let links: Vec<_> = feat |
| .required_by_extensions |
| .iter() |
| .map(|ext| format!("[`{}`](crate::device::DeviceExtensions::{0})", ext)) |
| .collect(); |
| write!( |
| writer, |
| "\n\t\t\t- Required by device extension{}: {}", |
| if feat.required_by_extensions.len() > 1 { |
| "s" |
| } else { |
| "" |
| }, |
| links.join(", ") |
| ) |
| .unwrap(); |
| } |
| |
| if !feat.conflicts_features.is_empty() { |
| let links: Vec<_> = feat |
| .conflicts_features |
| .iter() |
| .map(|ext| format!("[`{}`](crate::device::Features::{0})", ext)) |
| .collect(); |
| write!( |
| writer, |
| "\n\t\t\t- Conflicts with feature{}: {}", |
| if feat.conflicts_features.len() > 1 { |
| "s" |
| } else { |
| "" |
| }, |
| links.join(", ") |
| ) |
| .unwrap(); |
| } |
| |
| write!(writer, "\n\t\t\",").unwrap(); |
| } |
| |
| #[derive(Clone, Debug)] |
| struct VulkanoFeatureFfi { |
| member: String, |
| ty: String, |
| provided_by: Vec<String>, |
| conflicts: Vec<String>, |
| } |
| |
| fn make_vulkano_features_ffi<'a>( |
| types: &'a HashMap<&str, (&Type, Vec<&str>)>, |
| extensions: &IndexMap<&'a str, &Extension>, |
| ) -> Vec<VulkanoFeatureFfi> { |
| let mut feature_included_in: HashMap<&str, Vec<&str>> = HashMap::new(); |
| sorted_structs(types) |
| .into_iter() |
| .map(|(ty, provided_by)| { |
| let name = ty.name.as_ref().unwrap(); |
| let provided_by = provided_by |
| .iter() |
| .map(|provided_by| { |
| if let Some(version) = provided_by.strip_prefix("VK_VERSION_") { |
| format!("api_version >= crate::Version::V{}", version) |
| } else { |
| format!( |
| "{}_extensions.{}", |
| extensions[provided_by].ext_type.as_ref().unwrap().as_str(), |
| provided_by |
| .strip_prefix("VK_") |
| .unwrap() |
| .to_ascii_lowercase() |
| ) |
| } |
| }) |
| .collect(); |
| let mut conflicts = vec![]; |
| members(ty) |
| .into_iter() |
| .for_each(|member| match feature_included_in.entry(member) { |
| Entry::Vacant(entry) => { |
| entry.insert(vec![name]); |
| } |
| Entry::Occupied(entry) => { |
| let conflicters = entry.into_mut(); |
| conflicters.iter().for_each(|conflicter| { |
| let conflicter = ffi_member(conflicter); |
| if !conflicts.contains(&conflicter) { |
| conflicts.push(conflicter); |
| } |
| }); |
| conflicters.push(name); |
| } |
| }); |
| |
| VulkanoFeatureFfi { |
| member: ffi_member(name), |
| ty: name.strip_prefix("Vk").unwrap().to_owned(), |
| provided_by, |
| conflicts, |
| } |
| }) |
| .collect() |
| } |
| |
| fn sorted_structs<'a>( |
| types: &'a HashMap<&str, (&'a Type, Vec<&'a str>)>, |
| ) -> Vec<&'a (&'a Type, Vec<&'a str>)> { |
| let mut structs: Vec<_> = types |
| .values() |
| .filter(|(ty, _)| { |
| ty.structextends.as_ref().map(|s| s.as_str()) |
| == Some("VkPhysicalDeviceFeatures2,VkDeviceCreateInfo") |
| }) |
| .collect(); |
| let regex = Regex::new(r"^VkPhysicalDeviceVulkan\d+Features$").unwrap(); |
| structs.sort_unstable_by_key(|&(ty, provided_by)| { |
| let name = ty.name.as_ref().unwrap(); |
| ( |
| !regex.is_match(name), |
| if let Some(version) = provided_by |
| .iter() |
| .find_map(|s| s.strip_prefix("VK_VERSION_")) |
| { |
| let (major, minor) = version.split_once('_').unwrap(); |
| major.parse::<i32>().unwrap() << 22 | minor.parse::<i32>().unwrap() << 12 |
| } else if provided_by |
| .iter() |
| .find(|s| s.starts_with("VK_KHR_")) |
| .is_some() |
| { |
| i32::MAX - 2 |
| } else if provided_by |
| .iter() |
| .find(|s| s.starts_with("VK_EXT_")) |
| .is_some() |
| { |
| i32::MAX - 1 |
| } else { |
| i32::MAX |
| }, |
| name, |
| ) |
| }); |
| |
| structs |
| } |
| |
| fn ffi_member(ty_name: &str) -> String { |
| let ty_name = ty_name |
| .strip_prefix("VkPhysicalDevice") |
| .unwrap() |
| .to_snake_case(); |
| let (base, suffix) = ty_name.rsplit_once("_features").unwrap(); |
| format!("features_{}{}", base, suffix) |
| } |
| |
| fn members(ty: &Type) -> Vec<&str> { |
| if let TypeSpec::Members(members) = &ty.spec { |
| members |
| .iter() |
| .filter_map(|member| { |
| if let TypeMember::Definition(def) = member { |
| let name = def.markup.iter().find_map(|markup| match markup { |
| TypeMemberMarkup::Name(name) => Some(name.as_str()), |
| _ => None, |
| }); |
| if name != Some("sType") && name != Some("pNext") { |
| return name; |
| } |
| } |
| None |
| }) |
| .collect() |
| } else { |
| vec![] |
| } |
| } |