blob: 4c49747493a09d22df5e0ae5584d464751200c3a [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 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![]
}
}