// Copyright (c) 2021 The Vulkano developers // Licensed under the Apache License, Version 2.0 // or the MIT // license , // 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, 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, planes: Vec, texels_per_block: u8, numeric_format_color: Option, numeric_format_depth: Option, numeric_format_stencil: Option, ycbcr_chroma_sampling: Option, type_std_array: Option, type_cgmath: Option, type_nalgebra: Option, } 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 { 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 { 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 { 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 { 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 { 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> { 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> { 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> { match self { #(#validate_device_items)* } Ok(()) } } impl TryFrom for Format { type Error = (); fn try_from(val: ash::vk::Format) -> Result { 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 { static BLOCK_EXTENT_REGEX: Lazy = 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::>(); 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() }