blob: 1212dbafb72c8123814d07fe2f4d94afa744cdc2 [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::{write_file, IndexMap, VkRegistryData};
use ahash::HashMap;
use heck::ToSnakeCase;
use proc_macro2::{Ident, TokenStream};
use quote::{format_ident, quote};
use regex::Regex;
use std::{collections::hash_map::Entry, fmt::Write as _};
use vk_parse::{Extension, Type, TypeMember, TypeMemberMarkup, TypeSpec};
pub fn write(vk_data: &VkRegistryData) {
let properties_output = properties_output(&properties_members(&vk_data.types));
let properties_ffi_output =
properties_ffi_output(&properties_ffi_members(&vk_data.types, &vk_data.extensions));
write_file(
"properties.rs",
format!(
"vk.xml header version {}.{}.{}",
vk_data.header_version.0, vk_data.header_version.1, vk_data.header_version.2
),
quote! {
#properties_output
#properties_ffi_output
},
);
}
#[derive(Clone, Debug)]
struct PropertiesMember {
name: Ident,
ty: TokenStream,
doc: String,
raw: String,
ffi_name: Ident,
ffi_members: Vec<(Ident, TokenStream)>,
optional: bool,
}
fn properties_output(members: &[PropertiesMember]) -> TokenStream {
let struct_items = members.iter().map(
|PropertiesMember {
name,
ty,
doc,
optional,
..
}| {
if *optional {
quote! {
#[doc = #doc]
pub #name: Option<#ty>,
}
} else {
quote! {
#[doc = #doc]
pub #name: #ty,
}
}
},
);
let default_items = members.iter().map(|PropertiesMember { name, .. }| {
quote! {
#name: Default::default(),
}
});
let from_items = members.iter().map(
|PropertiesMember {
name,
ty,
ffi_name,
ffi_members,
optional,
..
}| {
if *optional {
let ffi_members = ffi_members.iter().map(|(ffi_member, ffi_member_field)| {
quote! { properties_ffi.#ffi_member.map(|s| s #ffi_member_field .#ffi_name) }
});
quote! {
#name: [
#(#ffi_members),*
].into_iter().flatten().next().and_then(<#ty>::from_vulkan),
}
} else {
let ffi_members = ffi_members.iter().map(|(ffi_member, ffi_member_field)| {
quote! { properties_ffi.#ffi_member #ffi_member_field .#ffi_name }
});
quote! {
#name: [
#(#ffi_members),*
].into_iter().next().and_then(<#ty>::from_vulkan).unwrap(),
}
}
},
);
quote! {
/// Represents all the properties of a physical device.
///
/// Depending on the highest version of Vulkan supported by the physical device, and the
/// available extensions, not every property may be available. For that reason, some
/// properties are wrapped in an `Option`.
#[derive(Clone, Debug)]
pub struct Properties {
#(#struct_items)*
pub _ne: crate::NonExhaustive,
}
impl Default for Properties {
fn default() -> Self {
Properties {
#(#default_items)*
_ne: crate::NonExhaustive(()),
}
}
}
impl From<&PropertiesFfi> for Properties {
fn from(properties_ffi: &PropertiesFfi) -> Self {
Properties {
#(#from_items)*
_ne: crate::NonExhaustive(()),
}
}
}
}
}
fn properties_members(types: &HashMap<&str, (&Type, Vec<&str>)>) -> Vec<PropertiesMember> {
let mut properties = HashMap::default();
[
&types["VkPhysicalDeviceProperties"],
&types["VkPhysicalDeviceLimits"],
&types["VkPhysicalDeviceSparseProperties"],
]
.into_iter()
.chain(sorted_structs(types).into_iter())
.filter(|(ty, _)| {
let name = ty.name.as_deref();
name == Some("VkPhysicalDeviceProperties")
|| name == Some("VkPhysicalDeviceLimits")
|| name == Some("VkPhysicalDeviceSparseProperties")
|| ty.structextends.as_deref() == Some("VkPhysicalDeviceProperties2")
})
.for_each(|(ty, _)| {
let vulkan_ty_name = ty.name.as_ref().unwrap();
let (ty_name, optional) = if vulkan_ty_name == "VkPhysicalDeviceProperties" {
(
(format_ident!("properties_vulkan10"), quote! { .properties }),
false,
)
} else if vulkan_ty_name == "VkPhysicalDeviceLimits" {
(
(
format_ident!("properties_vulkan10"),
quote! { .properties.limits },
),
false,
)
} else if vulkan_ty_name == "VkPhysicalDeviceSparseProperties" {
(
(
format_ident!("properties_vulkan10"),
quote! { .properties.sparse_properties },
),
false,
)
} else {
(
(format_ident!("{}", ffi_member(vulkan_ty_name)), quote! {}),
true,
)
};
members(ty)
.into_iter()
.for_each(|Member { name, ty, len }| {
if ty == "VkPhysicalDeviceLimits" || ty == "VkPhysicalDeviceSparseProperties" {
return;
}
let vulkano_member = name.to_snake_case();
let vulkano_ty = match name {
"apiVersion" => quote! { Version },
"bufferImageGranularity"
| "minStorageBufferOffsetAlignment"
| "minTexelBufferOffsetAlignment"
| "minUniformBufferOffsetAlignment"
| "nonCoherentAtomSize"
| "optimalBufferCopyOffsetAlignment"
| "optimalBufferCopyRowPitchAlignment"
| "robustStorageBufferAccessSizeAlignment"
| "robustUniformBufferAccessSizeAlignment"
| "storageTexelBufferOffsetAlignmentBytes"
| "uniformTexelBufferOffsetAlignmentBytes" => {
quote! { DeviceAlignment }
}
_ => vulkano_type(ty, len),
};
match properties.entry(vulkano_member.clone()) {
Entry::Vacant(entry) => {
let mut member = PropertiesMember {
name: format_ident!("{}", vulkano_member),
ty: vulkano_ty,
doc: String::new(),
raw: name.to_owned(),
ffi_name: format_ident!("{}", vulkano_member),
ffi_members: vec![ty_name.clone()],
optional,
};
make_doc(&mut member, vulkan_ty_name);
entry.insert(member);
}
Entry::Occupied(entry) => {
entry.into_mut().ffi_members.push(ty_name.clone());
}
};
});
});
let mut names: Vec<_> = properties
.values()
.map(|prop| prop.name.to_string())
.collect();
names.sort_unstable();
names
.into_iter()
.map(|name| properties.remove(&name).unwrap())
.collect()
}
fn make_doc(prop: &mut PropertiesMember, vulkan_ty_name: &str) {
let writer = &mut prop.doc;
write!(
writer,
"- [Vulkan documentation](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/{}.html#limits-{})",
vulkan_ty_name,
prop.raw
)
.unwrap();
}
#[derive(Clone, Debug)]
struct PropertiesFfiMember {
name: Ident,
ty: Ident,
provided_by: Vec<TokenStream>,
conflicts: Vec<Ident>,
}
fn properties_ffi_output(members: &[PropertiesFfiMember]) -> TokenStream {
let struct_items = members.iter().map(|PropertiesFfiMember { name, ty, .. }| {
quote! { #name: Option<ash::vk::#ty>, }
});
let make_chain_items = members.iter().map(
|PropertiesFfiMember {
name,
provided_by,
conflicts,
..
}| {
quote! {
if [#(#provided_by),*].into_iter().any(|x| x) &&
[#(self.#conflicts.is_none()),*].into_iter().all(|x| x) {
self.#name = Some(Default::default());
let member = self.#name.as_mut().unwrap();
member.p_next = head.p_next;
head.p_next = member as *mut _ as _;
}
}
},
);
quote! {
#[derive(Default)]
pub(crate) struct PropertiesFfi {
properties_vulkan10: ash::vk::PhysicalDeviceProperties2KHR,
#(#struct_items)*
}
impl PropertiesFfi {
pub(crate) fn make_chain(
&mut self,
api_version: Version,
device_extensions: &DeviceExtensions,
instance_extensions: &InstanceExtensions,
) {
self.properties_vulkan10 = Default::default();
let head = &mut self.properties_vulkan10;
#(#make_chain_items)*
}
pub(crate) fn head_as_mut(&mut self) -> &mut ash::vk::PhysicalDeviceProperties2KHR {
&mut self.properties_vulkan10
}
}
}
}
fn properties_ffi_members<'a>(
types: &'a HashMap<&str, (&Type, Vec<&str>)>,
extensions: &IndexMap<&'a str, &Extension>,
) -> Vec<PropertiesFfiMember> {
let mut property_included_in: HashMap<&str, Vec<&str>> = HashMap::default();
sorted_structs(types)
.into_iter()
.map(|(ty, provided_by)| {
let ty_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_") {
let version = format_ident!("V{}", version);
quote! { api_version >= Version::#version }
} else {
let member = format_ident!(
"{}_extensions",
extensions[provided_by].ext_type.as_ref().unwrap().as_str()
);
let name = format_ident!(
"{}",
provided_by
.strip_prefix("VK_")
.unwrap()
.to_ascii_lowercase(),
);
quote! { #member.#name }
}
})
.collect();
let mut conflicts = vec![];
members(ty).into_iter().for_each(|Member { name, .. }| {
match property_included_in.entry(name) {
Entry::Vacant(entry) => {
entry.insert(vec![ty_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(ty_name);
}
}
});
PropertiesFfiMember {
name: format_ident!("{}", ffi_member(ty_name)),
ty: format_ident!("{}", ty_name.strip_prefix("Vk").unwrap()),
provided_by,
conflicts: conflicts
.into_iter()
.map(|s| format_ident!("{}", s))
.collect(),
}
})
.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_deref() == Some("VkPhysicalDeviceProperties2"))
.collect();
let regex = Regex::new(r"^VkPhysicalDeviceVulkan\d+Properties$").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().any(|s| s.starts_with("VK_KHR_")) {
i32::MAX - 2
} else if provided_by.iter().any(|s| s.starts_with("VK_EXT_")) {
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("_properties").unwrap();
format!("properties_{}{}", base, suffix)
}
struct Member<'a> {
name: &'a str,
ty: &'a str,
len: Option<&'a str>,
}
fn members(ty: &Type) -> Vec<Member> {
let regex = Regex::new(r"\[([A-Za-z0-9_]+)\]\s*$").unwrap();
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,
});
let ty = def.markup.iter().find_map(|markup| match markup {
TypeMemberMarkup::Type(ty) => Some(ty.as_str()),
_ => None,
});
let len = def
.markup
.iter()
.find_map(|markup| match markup {
TypeMemberMarkup::Enum(len) => Some(len.as_str()),
_ => None,
})
.or_else(|| {
regex
.captures(&def.code)
.and_then(|cap| cap.get(1))
.map(|m| m.as_str())
});
if name != Some("sType") && name != Some("pNext") {
return name.map(|name| Member {
name,
ty: ty.unwrap(),
len,
});
}
}
None
})
.collect()
} else {
vec![]
}
}
fn vulkano_type(ty: &str, len: Option<&str>) -> TokenStream {
if let Some(len) = len {
match ty {
"char" => quote! { String },
"uint8_t" if len == "VK_LUID_SIZE" => quote! { [u8; 8] },
"uint8_t" if len == "VK_UUID_SIZE" => quote! { [u8; 16] },
"uint32_t" if len == "2" => quote! { [u32; 2] },
"uint32_t" if len == "3" => quote! { [u32; 3] },
"float" if len == "2" => quote! { [f32; 2] },
_ => unimplemented!("{}[{}]", ty, len),
}
} else {
match ty {
"float" => quote! { f32 },
"int32_t" => quote! { i32 },
"int64_t" => quote! { i64 },
"size_t" => quote! { usize },
"uint8_t" => quote! { u8 },
"uint32_t" => quote! { u32 },
"uint64_t" => quote! { u64 },
"VkBool32" => quote! { bool },
"VkConformanceVersion" => quote! { ConformanceVersion },
"VkDeviceSize" => quote! { DeviceSize },
"VkDriverId" => quote! { DriverId },
"VkExtent2D" => quote! { [u32; 2] },
"VkMemoryDecompressionMethodFlagsNV" => quote! { MemoryDecompressionMethods },
"VkOpticalFlowGridSizeFlagsNV" => quote! { OpticalFlowGridSizes },
"VkPhysicalDeviceType" => quote! { PhysicalDeviceType },
"VkPipelineRobustnessBufferBehaviorEXT" => quote! { PipelineRobustnessBufferBehavior },
"VkPipelineRobustnessImageBehaviorEXT" => quote! { PipelineRobustnessImageBehavior },
"VkPointClippingBehavior" => quote! { PointClippingBehavior },
"VkQueueFlags" => quote! { QueueFlags },
"VkRayTracingInvocationReorderModeNV" => quote! { RayTracingInvocationReorderMode },
"VkResolveModeFlags" => quote! { ResolveModes },
"VkSampleCountFlags" => quote! { SampleCounts },
"VkSampleCountFlagBits" => quote! { SampleCount },
"VkShaderCorePropertiesFlagsAMD" => quote! { ShaderCoreProperties },
"VkShaderFloatControlsIndependence" => quote! { ShaderFloatControlsIndependence },
"VkShaderStageFlags" => quote! { ShaderStages },
"VkSubgroupFeatureFlags" => quote! { SubgroupFeatures },
_ => unimplemented!("{}", ty),
}
}
}