// 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, RequiresOneOf, VkRegistryData};
use heck::ToSnakeCase;
use once_cell::sync::Lazy;
use proc_macro2::{Ident, Literal, TokenStream};
use quote::{format_ident, quote};
use regex::Regex;
use std::iter;
use vk_parse::{
Enum, EnumSpec, Extension, ExtensionChild, Feature, Format, FormatChild, InterfaceItem,
};
pub fn write(vk_data: &VkRegistryData) {
write_file(
"formats.rs",
format!(
"vk.xml header version {}.{}.{}",
vk_data.header_version.0, vk_data.header_version.1, vk_data.header_version.2
),
formats_output(&formats_members(
&vk_data.formats,
&vk_data.features,
&vk_data.extensions,
)),
);
}
#[derive(Clone, Debug)]
struct FormatMember {
name: Ident,
ffi_name: Ident,
requires_all_of: Vec<RequiresOneOf>,
aspect_color: bool,
aspect_depth: bool,
aspect_stencil: bool,
aspect_plane0: bool,
aspect_plane1: bool,
aspect_plane2: bool,
block_extent: [u32; 3],
block_size: u64,
compatibility: Ident,
components: [u8; 4],
compression: Option<Ident>,
planes: Vec<Ident>,
texels_per_block: u8,
numeric_format_color: Option<Ident>,
numeric_format_depth: Option<Ident>,
numeric_format_stencil: Option<Ident>,
ycbcr_chroma_sampling: Option<Ident>,
type_std_array: Option<TokenStream>,
type_cgmath: Option<TokenStream>,
type_nalgebra: Option<TokenStream>,
}
fn formats_output(members: &[FormatMember]) -> TokenStream {
let enum_items = members.iter().map(|FormatMember { name, ffi_name, .. }| {
let default = (ffi_name == "UNDEFINED").then(|| quote! { #[default] });
quote! { #default #name = ash::vk::Format::#ffi_name.as_raw(), }
});
let aspects_items = members.iter().map(
|&FormatMember {
ref name,
aspect_color,
aspect_depth,
aspect_stencil,
aspect_plane0,
aspect_plane1,
aspect_plane2,
..
}| {
if aspect_color
|| aspect_depth
|| aspect_stencil
|| aspect_plane0
|| aspect_plane1
|| aspect_plane2
{
let aspect_items = [
aspect_color.then(|| quote! { crate::image::ImageAspects::COLOR }),
aspect_depth.then(|| quote! { crate::image::ImageAspects::DEPTH }),
aspect_stencil.then(|| quote! { crate::image::ImageAspects::STENCIL }),
aspect_plane0.then(|| quote! { crate::image::ImageAspects::PLANE_0 }),
aspect_plane1.then(|| quote! { crate::image::ImageAspects::PLANE_1 }),
aspect_plane2.then(|| quote! { crate::image::ImageAspects::PLANE_2 }),
]
.into_iter()
.flatten();
quote! {
Self::#name => #(#aspect_items)|*,
}
} else {
quote! {
Self::#name => crate::image::ImageAspects::empty(),
}
}
},
);
let block_extent_items = members.iter().filter_map(
|FormatMember {
name,
block_extent: [x, y, z],
..
}| {
if *x == 1 && *y == 1 && *z == 1 {
None
} else {
let x = Literal::u32_unsuffixed(*x);
let y = Literal::u32_unsuffixed(*y);
let z = Literal::u32_unsuffixed(*z);
Some(quote! { Self::#name => [#x, #y, #z], })
}
},
);
let block_size_items = members.iter().map(
|FormatMember {
name, block_size, ..
}| {
let block_size = Literal::u64_unsuffixed(*block_size);
quote! { Self::#name => #block_size, }
},
);
let compatibility_items = members.iter().map(
|FormatMember {
name,
compatibility,
..
}| {
quote! {
Self::#name => &FormatCompatibilityInner::#compatibility,
}
},
);
let components_items = members.iter().map(
|FormatMember {
name, components, ..
}| {
let components = components.iter().map(|c| Literal::u8_unsuffixed(*c));
quote! {
Self::#name => [#(#components),*],
}
},
);
let compression_items = members.iter().filter_map(
|FormatMember {
name, compression, ..
}| {
compression
.as_ref()
.map(|x| quote! { Self::#name => Some(CompressionType::#x), })
},
);
let planes_items = members
.iter()
.filter_map(|FormatMember { name, planes, .. }| {
if planes.is_empty() {
None
} else {
Some(quote! { Self::#name => &[#(Self::#planes),*], })
}
});
let texels_per_block_items = members
.iter()
.filter(
|&FormatMember {
texels_per_block, ..
}| (*texels_per_block != 1),
)
.map(
|FormatMember {
name,
texels_per_block,
..
}| {
let texels_per_block = Literal::u8_unsuffixed(*texels_per_block);
quote! { Self::#name => #texels_per_block, }
},
);
let numeric_format_color_items = members.iter().filter_map(
|FormatMember {
name,
numeric_format_color,
..
}| {
numeric_format_color
.as_ref()
.map(|ty| quote! { Self::#name => Some(NumericFormat::#ty), })
},
);
let numeric_format_depth_items = members.iter().filter_map(
|FormatMember {
name,
numeric_format_depth,
..
}| {
numeric_format_depth
.as_ref()
.map(|ty| quote! { Self::#name => Some(NumericFormat::#ty), })
},
);
let numeric_format_stencil_items = members.iter().filter_map(
|FormatMember {
name,
numeric_format_stencil,
..
}| {
numeric_format_stencil
.as_ref()
.map(|ty| quote! { Self::#name => Some(NumericFormat::#ty), })
},
);
let ycbcr_chroma_sampling_items = members.iter().filter_map(
|FormatMember {
name,
ycbcr_chroma_sampling,
..
}| {
ycbcr_chroma_sampling
.as_ref()
.map(|ty| quote! { Self::#name => Some(ChromaSampling::#ty), })
},
);
let try_from_items = members.iter().map(|FormatMember { name, ffi_name, .. }| {
quote! { ash::vk::Format::#ffi_name => Ok(Self::#name), }
});
let type_for_format_items = members.iter().filter_map(
|FormatMember {
name,
type_std_array,
..
}| {
type_std_array.as_ref().map(|ty| {
quote! { (#name) => { #ty }; }
})
},
);
let type_for_format_cgmath_items = members.iter().filter_map(
|FormatMember {
name,
type_std_array,
type_cgmath,
..
}| {
(type_cgmath.as_ref().or(type_std_array.as_ref())).map(|ty| {
quote! { (cgmath, #name) => { #ty }; }
})
},
);
let type_for_format_nalgebra_items = members.iter().filter_map(
|FormatMember {
name,
type_std_array,
type_nalgebra,
..
}| {
(type_nalgebra.as_ref().or(type_std_array.as_ref())).map(|ty| {
quote! { (nalgebra, #name) => { #ty }; }
})
},
);
let validate_device_items = members.iter().map(
|FormatMember {
name,
requires_all_of,
..
}| {
let requires_items = requires_all_of.iter().map(
|RequiresOneOf {
api_version,
device_extensions,
instance_extensions,
features: _,
}| {
let condition_items = (api_version.iter().map(|(major, minor)| {
let version = format_ident!("V{}_{}", major, minor);
quote! { device_api_version >= crate::Version::#version }
}))
.chain(device_extensions.iter().map(|ext_name| {
let ident = format_ident!("{}", ext_name);
quote! { device_extensions.#ident }
}))
.chain(instance_extensions.iter().map(|ext_name| {
let ident = format_ident!("{}", ext_name);
quote! { instance_extensions.#ident }
}));
let required_for = format!("is `Format::{}`", name);
let requires_one_of_items = (api_version.iter().map(|(major, minor)| {
let version = format_ident!("V{}_{}", major, minor);
quote! {
crate::RequiresAllOf(&[
crate::Requires::APIVersion(crate::Version::#version),
]),
}
}))
.chain(device_extensions.iter().map(|ext_name| {
quote! {
crate::RequiresAllOf(&[
crate::Requires::DeviceExtension(#ext_name),
]),
}
}))
.chain(instance_extensions.iter().map(|ext_name| {
quote! {
crate::RequiresAllOf(&[
crate::Requires::InstanceExtension(#ext_name),
]),
}
}));
quote! {
if !(#(#condition_items)||*) {
return Err(Box::new(crate::ValidationError {
problem: #required_for.into(),
requires_one_of: crate::RequiresOneOf(&[
#(#requires_one_of_items)*
]),
..Default::default()
}));
}
}
},
);
quote! {
Self::#name => {
#(#requires_items)*
}
}
},
);
quote! {
/// An enumeration of all the possible formats.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
#[repr(i32)]
#[allow(non_camel_case_types)]
#[non_exhaustive]
pub enum Format {
#(#enum_items)*
}
impl Format {
/// Returns the aspects that images of this format have.
pub fn aspects(self) -> ImageAspects {
match self {
#(#aspects_items)*
}
}
/// Returns the extent in texels (horizontally and vertically) of a single texel
/// block of this format. A texel block is a rectangle of pixels that is represented by
/// a single element of this format. It is also the minimum granularity of the extent of
/// an image; images must always have an extent that's a multiple of the block extent.
///
/// For normal formats, the block extent is [1, 1, 1], meaning that each element of the
/// format represents one texel. Block-compressed formats encode multiple texels into
/// a single element. The 422 and 420 YCbCr formats have a block extent of [2, 1, 1] and
/// [2, 2, 1] respectively, as the red and blue components are shared across multiple
/// texels.
pub fn block_extent(self) -> [u32; 3] {
match self {
#(#block_extent_items)*
_ => [1, 1, 1],
}
}
/// Returns the size in bytes of a single texel block of this format.
///
/// For regular formats, this is the size of a single texel, but for more specialized
/// formats this may be the size of multiple texels.
///
/// Multi-planar formats store the color components disjointly in memory, and therefore
/// the texels do not form a single texel block. The block size for such formats
/// indicates the sum of the block size of each plane.
pub fn block_size(self) -> DeviceSize {
match self {
#(#block_size_items)*
}
}
/// Returns the an opaque object representing the compatibility class of the format.
/// This can be used to determine whether two formats are compatible for the purposes
/// of certain Vulkan operations, such as image copying.
pub fn compatibility(self) -> FormatCompatibility {
FormatCompatibility(match self {
#(#compatibility_items)*
})
}
/// Returns the number of bits per texel block that each component (R, G, B, A) is
/// represented with. Components that are not present in the format have 0 bits.
///
/// For depth/stencil formats, the depth component is the first, stencil the second. For
/// multi-planar formats, this is the number of bits across all planes.
///
/// For block-compressed formats, the number of bits in individual components is not
/// well-defined, and the return value is merely binary: 1 indicates a component
/// that is present in the format, 0 indicates one that is absent.
pub fn components(self) -> [u8; 4] {
match self {
#(#components_items)*
}
}
/// Returns the block compression scheme used for this format, if any. Returns `None` if
/// the format does not use compression.
pub fn compression(self) -> Option<CompressionType> {
match self {
#(#compression_items)*
_ => None,
}
}
/// For multi-planar formats, returns a slice of length 2 or 3, containing the
/// equivalent regular format of each plane.
///
/// For non-planar formats, returns the empty slice.
pub fn planes(self) -> &'static [Self] {
match self {
#(#planes_items)*
_ => &[],
}
}
/// Returns the number of texels for a single texel block. For most formats, this is
/// the product of the `block_extent` elements, but for some it differs.
pub fn texels_per_block(self) -> u8 {
match self {
#(#texels_per_block_items)*
_ => 1,
}
}
/// Returns the numeric format of the color aspect of this format. Returns `None`
/// for depth/stencil formats.
pub fn numeric_format_color(self) -> Option<NumericFormat> {
match self {
#(#numeric_format_color_items)*
_ => None,
}
}
/// Returns the numeric format of the depth aspect of this format. Returns `None`
/// color and stencil-only formats.
pub fn numeric_format_depth(self) -> Option<NumericFormat> {
match self {
#(#numeric_format_depth_items)*
_ => None,
}
}
/// Returns the numeric format of the stencil aspect of this format. Returns `None`
/// for color and depth-only formats.
pub fn numeric_format_stencil(self) -> Option<NumericFormat> {
match self {
#(#numeric_format_stencil_items)*
_ => None,
}
}
/// For YCbCr (YUV) formats, returns the way in which the chroma components are
/// represented. Returns `None` for non-YCbCr formats.
///
/// If an image view is created for one of the formats for which this function returns
/// `Some`, with the `color` aspect selected, then the view and any samplers that sample
/// it must be created with an attached sampler YCbCr conversion object.
pub fn ycbcr_chroma_sampling(self) -> Option<ChromaSampling> {
match self {
#(#ycbcr_chroma_sampling_items)*
_ => None,
}
}
#[allow(dead_code)]
pub(crate) fn validate_device(
self,
#[allow(unused_variables)] device: &crate::device::Device,
) -> Result<(), Box<crate::ValidationError>> {
self.validate_device_raw(
device.api_version(),
device.enabled_features(),
device.enabled_extensions(),
device.instance().enabled_extensions(),
)
}
#[allow(dead_code)]
pub(crate) fn validate_physical_device(
self,
#[allow(unused_variables)] physical_device: &crate::device::physical::PhysicalDevice,
) -> Result<(), Box<crate::ValidationError>> {
self.validate_device_raw(
physical_device.api_version(),
physical_device.supported_features(),
physical_device.supported_extensions(),
physical_device.instance().enabled_extensions(),
)
}
#[allow(dead_code)]
pub(crate) fn validate_device_raw(
self,
#[allow(unused_variables)] device_api_version: crate::Version,
#[allow(unused_variables)] device_features: &crate::device::Features,
#[allow(unused_variables)] device_extensions: &crate::device::DeviceExtensions,
#[allow(unused_variables)] instance_extensions: &crate::instance::InstanceExtensions,
) -> Result<(), Box<crate::ValidationError>> {
match self {
#(#validate_device_items)*
}
Ok(())
}
}
impl TryFrom<ash::vk::Format> for Format {
type Error = ();
fn try_from(val: ash::vk::Format) -> Result<Format, ()> {
match val {
#(#try_from_items)*
_ => Err(()),
}
}
}
/// Converts a format enum identifier to a standard Rust type that is suitable for
/// representing the format in a buffer or image.
///
/// This macro returns one possible suitable representation, but there are usually other
/// possibilities for a given format. A compile error occurs for formats that have no
/// well-defined size (the `size` method returns `None`).
///
/// - For regular unpacked formats with one component, this returns a single floating point,
/// signed or unsigned integer with the appropriate number of bits. For formats with
/// multiple components, an array is returned.
/// - For packed formats, this returns an unsigned integer with the size of the packed
/// element. For multi-packed formats (such as `2PACK16`), an array is returned.
/// - For compressed formats, this returns `[u8; N]` where N is the size of a block.
///
/// Note: for 16-bit floating point values, you need to import the [`half::f16`] type.
///
/// # Examples
///
/// For arrays:
///
/// ```
/// # use vulkano::type_for_format;
/// let pixel: type_for_format!(R32G32B32A32_SFLOAT);
/// pixel = [1.0f32, 0.0, 0.0, 1.0];
/// ```
///
/// For [`cgmath`]:
///
/// ```
/// # use vulkano::type_for_format;
/// let pixel: type_for_format!(cgmath, R32G32B32A32_SFLOAT);
/// pixel = cgmath::Vector4::new(1.0f32, 0.0, 0.0, 1.0);
/// ```
///
/// For [`nalgebra`]:
///
/// ```
/// # use vulkano::type_for_format;
/// let pixel: type_for_format!(nalgebra, R32G32B32A32_SFLOAT);
/// pixel = nalgebra::vector![1.0f32, 0.0, 0.0, 1.0];
/// ```
///
/// [`cgmath`]: https://crates.io/crates/cgmath
/// [`nalgebra`]: https://crates.io/crates/nalgebra
#[macro_export]
macro_rules! type_for_format {
#(#type_for_format_items)*
#(#type_for_format_cgmath_items)*
#(#type_for_format_nalgebra_items)*
}
}
}
fn formats_members(
formats: &[&Format],
features: &IndexMap<&str, &Feature>,
extensions: &IndexMap<&str, &Extension>,
) -> Vec<FormatMember> {
static BLOCK_EXTENT_REGEX: Lazy<Regex> =
Lazy::new(|| Regex::new(r"^(\d+),(\d+),(\d+)$").unwrap());
iter::once(
FormatMember {
name: format_ident!("UNDEFINED"),
ffi_name: format_ident!("UNDEFINED"),
requires_all_of: Vec::new(),
aspect_color: false,
aspect_depth: false,
aspect_stencil: false,
aspect_plane0: false,
aspect_plane1: false,
aspect_plane2: false,
block_extent: [0; 3],
block_size: 0,
compatibility: format_ident!("Undefined"),
components: [0u8; 4],
compression: None,
planes: vec![],
texels_per_block: 0,
numeric_format_color: None,
numeric_format_depth: None,
numeric_format_stencil: None,
ycbcr_chroma_sampling: None,
type_std_array: None,
type_cgmath: None,
type_nalgebra: None,
}
).chain(
formats
.iter()
.map(|format| {
let vulkan_name = format.name.strip_prefix("VK_FORMAT_").unwrap();
let ffi_name = format_ident!("{}", vulkan_name.to_ascii_uppercase());
let mut parts = vulkan_name.split('_').collect::<Vec<_>>();
if ["EXT", "IMG"].contains(parts.last().unwrap()) {
parts.pop();
}
let name = format_ident!("{}", parts.join("_"));
let mut member = FormatMember {
name,
ffi_name,
requires_all_of: Vec::new(),
aspect_color: false,
aspect_depth: false,
aspect_stencil: false,
aspect_plane0: false,
aspect_plane1: false,
aspect_plane2: false,
block_extent: [1, 1, 1],
block_size: format.blockSize as u64,
compatibility: format_ident!(
"Class_{}",
format.class.replace('-', "").replace(' ', "_")
),
components: [0u8; 4],
compression: format
.compressed
.as_ref()
.map(|c| format_ident!("{}", c.replace(' ', "_"))),
planes: vec![],
texels_per_block: format.texelsPerBlock,
numeric_format_color: None,
numeric_format_depth: None,
numeric_format_stencil: None,
ycbcr_chroma_sampling: None,
type_std_array: None,
type_cgmath: None,
type_nalgebra: None,
};
for child in &format.children {
match child {
FormatChild::Component {
name,
bits,
numericFormat,
..
} => {
let bits = if bits == "compressed" {
1u8
} else {
bits.parse().unwrap()
};
let ty = format_ident!("{}", numericFormat);
match name.as_str() {
"R" => {
member.aspect_color = true;
member.components[0] += bits;
member.numeric_format_color = Some(ty);
}
"G" => {
member.aspect_color = true;
member.components[1] += bits;
member.numeric_format_color = Some(ty);
}
"B" => {
member.aspect_color = true;
member.components[2] += bits;
member.numeric_format_color = Some(ty);
}
"A" => {
member.aspect_color = true;
member.components[3] += bits;
member.numeric_format_color = Some(ty);
}
"D" => {
member.aspect_depth = true;
member.components[0] += bits;
member.numeric_format_depth = Some(ty);
}
"S" => {
member.aspect_stencil = true;
member.components[1] += bits;
member.numeric_format_stencil = Some(ty);
}
_ => {
panic!("Unknown component type {} on format {}", name, format.name)
}
}
}
FormatChild::Plane {
index, compatible, ..
} => {
match *index {
0 => member.aspect_plane0 = true,
1 => member.aspect_plane1 = true,
2 => member.aspect_plane2 = true,
_ => (),
}
assert_eq!(*index as usize, member.planes.len());
member.planes.push(format_ident!(
"{}",
compatible.strip_prefix("VK_FORMAT_").unwrap()
));
}
//FormatChild::SpirvImageFormat { name, .. } => (),
_ => (),
}
}
if let Some(block_extent) = format.blockExtent.as_ref() {
let captures = BLOCK_EXTENT_REGEX.captures(block_extent).unwrap();
member.block_extent = [
captures.get(1).unwrap().as_str().parse().unwrap(),
captures.get(2).unwrap().as_str().parse().unwrap(),
captures.get(3).unwrap().as_str().parse().unwrap(),
];
} else {
match format.chroma.as_deref() {
Some("420") => member.block_extent = [2, 2, 1],
Some("422") => member.block_extent = [2, 1, 1],
_ => (),
}
};
if let (Some(numeric_type), true) = (&member.numeric_format_color, member.planes.is_empty()) {
if format.compressed.is_some() {
member.type_std_array = Some({
let block_size = Literal::usize_unsuffixed(format.blockSize as usize);
quote! { [u8; #block_size] }
});
} else if let Some(pack_bits) = format.packed {
let pack_elements = format.blockSize * 8 / pack_bits;
let element_type = format_ident!("u{}", pack_bits);
member.type_std_array = Some(if pack_elements > 1 {
let elements = Literal::usize_unsuffixed(pack_elements as usize);
quote! { [#element_type; #elements] }
} else {
quote! { #element_type }
});
} else {
let prefix = match numeric_type.to_string().as_str() {
"SFLOAT" => "f",
"SINT" | "SNORM" | "SSCALED" => "i",
"UINT" | "UNORM" | "USCALED" | "SRGB" => "u",
_ => unreachable!(),
};
let bits = member.components[0];
let component_type = format_ident!("{}{}", prefix, bits);
let component_count = if member.components[1] == 2 * bits {
// 422 format with repeated G component
4
} else {
// Normal format
member
.components
.into_iter()
.filter(|&c| {
if c != 0 {
debug_assert!(c == bits);
true
} else {
false
}
})
.count()
};
if component_count > 1 {
let elements = Literal::usize_unsuffixed(component_count);
member.type_std_array = Some(quote! { [#component_type; #elements] });
// cgmath only has 1, 2, 3 and 4-component vector types.
// Fall back to arrays for anything else.
if matches!(component_count, 1..=4) {
let ty = format_ident!("{}", format!("Vector{}", component_count));
member.type_cgmath = Some(quote! { cgmath::#ty<#component_type> });
}
member.type_nalgebra = Some(quote! {
nalgebra::base::SVector<#component_type, #component_count>
});
} else {
member.type_std_array = Some(quote! { #component_type });
}
}
}
if let Some(chroma) = format.chroma.as_ref() {
member.ycbcr_chroma_sampling = Some(format_ident!("Mode{}", chroma));
}
debug_assert!(
!member.components.iter().all(|x| *x == 0),
"format {} has 0 components",
vulkan_name
);
for &feature in features.values() {
for child in &feature.children {
if let ExtensionChild::Require { items, .. } = child {
for item in items {
match item {
InterfaceItem::Enum(Enum {
name,
spec: EnumSpec::Offset { extends, .. },
..
}) if name == &format.name && extends == "VkFormat" => {
if let Some(version) = feature.name.strip_prefix("VK_VERSION_")
{
let (major, minor) = version.split_once('_').unwrap();
member.requires_all_of.push(RequiresOneOf {
api_version: Some((
major.parse().unwrap(),
minor.parse().unwrap(),
)),
..Default::default()
});
}
}
_ => (),
}
}
}
}
}
for &extension in extensions.values() {
for child in &extension.children {
if let ExtensionChild::Require { items, .. } = child {
for item in items {
if let InterfaceItem::Enum(en) = item {
if matches!(
en,
Enum {
name,
spec: EnumSpec::Offset { extends, .. },
..
} if name == &format.name && extends == "VkFormat")
|| matches!(
en,
Enum {
spec: EnumSpec::Alias { alias, extends, .. },
..
} if alias == &format.name && extends.as_deref() == Some("VkFormat"))
{
let extension_name =
extension.name.strip_prefix("VK_").unwrap().to_snake_case();
if member.requires_all_of.is_empty() {
member.requires_all_of.push(Default::default());
};
let requires_one_of = member.requires_all_of.first_mut().unwrap();
match extension.ext_type.as_deref() {
Some("device") => {
requires_one_of
.device_extensions
.push(extension_name);
}
Some("instance") => {
requires_one_of
.instance_extensions
.push(extension_name);
}
_ => (),
}
}
}
}
}
}
}
member
}))
.collect()
}