ash: Add const STRUCTURE_TYPE to all Vulkan structures for matching with match_struct! macro (#614)

* ash: Add `const STRUCTURE_TYPE` to all Vulkan structures for matching with `match_struct!` macro

In Vulkan layers extracing a structure based on its `s_type` is a common
operation, but comparing against an enum value and subsequently casting
to the right type is verbose and error-prone.

By generating a `const STRUCTURE_TYPE` with the given value for every
Vulkan structure it becomes possible to implement a macro that abstracts
this logic away in a safer way.

* generator: Reuse `HasStructureType::STRUCTURE_TYPE` in `s_type` initializer
This commit is contained in:
Marijn Suijten 2022-09-19 12:39:54 +02:00 committed by GitHub
parent 6d0a29b638
commit 13fef40d48
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 3063 additions and 688 deletions

View file

@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added `VK_EXT_image_drm_format_modifier` device extension (#603)
- Update Vulkan-Headers to 1.3.219 (#605, #608, #619)
- Added `const STRUCTURE_TYPE` to all Vulkan structures for matching with `match_struct!` macro (#614)
- Added `VK_EXT_sample_locations` device extension (#616)
- Added `VK_NV_coverage_reduction_mode` device extension (#617)
- Added `VK_KHR_ray_tracing_maintenance1` device extension (#620)

View file

@ -72,6 +72,107 @@ impl<'r, T> RawPtr<T> for Option<&'r T> {
}
}
/// Given a mutable raw pointer to a type with an `s_type` member such as [`vk::BaseOutStructure`],
/// match on a set of Vulkan structures. The struct will be rebound to the given variable of the
/// type of the given Vulkan structure.
///
/// Note that all match bodies have to be enclosed by curly braces due to macro parsing limitations.
/// It is unfortunately not possible to write `x @ ash::vk::SomeStruct => one_line_expression(),`.
///
/// ```
/// let mut info = ash::vk::DeviceCreateInfo::default();
/// let info: *mut ash::vk::BaseOutStructure = <*mut _>::cast(&mut info);
/// unsafe {
/// ash::match_out_struct!(match info {
/// info @ ash::vk::DeviceQueueCreateInfo => {
/// dbg!(&info); // Unreachable
/// }
/// info @ ash::vk::DeviceCreateInfo => {
/// dbg!(&info);
/// }
/// })
/// }
/// ```
///
/// In addition this macro propagates implicit return values just like normal `match` blocks, as
/// long as a default value or expression is provided in the "any" match arm
/// (`_ => { some_value() }`). For the time being said arm must be wrapped in curly braces; an
/// expression like `_ => None` is not yet supported.
///
/// ```
/// # let mut info = ash::vk::DeviceCreateInfo::default();
/// # let info: *mut ash::vk::BaseOutStructure = <*mut _>::cast(&mut info);
/// let device_create_flags: Option<ash::vk::DeviceCreateFlags> = unsafe {
/// ash::match_out_struct!(match info {
/// info @ ash::vk::DeviceQueueCreateInfo => {
/// dbg!(&info); // Unreachable
/// Some(ash::vk::DeviceCreateFlags::empty())
/// }
/// info @ ash::vk::DeviceCreateInfo => {
/// dbg!(&info);
/// Some(info.flags)
/// }
/// _ => {
/// None
/// }
/// })
/// };
/// ```
#[macro_export]
macro_rules! match_out_struct {
(match $p:ident { $($bind:ident @ $ty:path => $body:block $(,)?)+ $(_ => $any:block $(,)?)? }) => {
match std::ptr::addr_of!((*$p).s_type).read() {
$(<$ty as $crate::vk::TaggedStructure>::STRUCTURE_TYPE => {
let $bind = $p
.cast::<$ty>()
.as_mut()
.unwrap();
$body
}),+
_ => { $($any)? }
}
};
}
/// Given an immutable raw pointer to a type with an `s_type` member such as [`vk::BaseInStructure`],
/// match on a set of Vulkan structures. The struct will be rebound to the given variable of the
/// type of the given Vulkan structure.
///
/// Note that all match bodies have to be enclosed by curly braces due to macro parsing limitations.
/// It is unfortunately not possible to write `x @ ash::vk::SomeStruct => one_line_expression(),`.
///
/// ```
/// let info = ash::vk::DeviceCreateInfo::default();
/// let info: *const ash::vk::BaseInStructure = <*const _>::cast(&info);
/// unsafe {
/// ash::match_in_struct!(match info {
/// info @ ash::vk::DeviceQueueCreateInfo => {
/// dbg!(&info); // Unreachable
/// }
/// info @ ash::vk::DeviceCreateInfo => {
/// dbg!(&info);
/// }
/// })
/// }
/// ```
///
/// See the [`match_out_struct!`] documentation for an example with implicit return values.
#[macro_export]
macro_rules! match_in_struct {
(match $p:ident { $($bind:ident @ $ty:path => $body:block $(,)?)+ $(_ => $any:block $(,)?)? }) => {
match std::ptr::addr_of!((*$p).s_type).read() {
$(<$ty as $crate::vk::TaggedStructure>::STRUCTURE_TYPE => {
let $bind = $p
.cast::<$ty>()
.as_ref()
.unwrap();
$body
}),+
_ => { $($any)? }
}
};
}
#[cfg(test)]
mod tests {
use super::vk;

File diff suppressed because it is too large Load diff

View file

@ -52,3 +52,10 @@ impl From<vk::Extent2D> for vk::Rect2D {
}
}
}
/// Structures implementing this trait are layout-compatible with [`vk::BaseInStructure`] and
/// [`vk::BaseOutStructure`]. Such structures have an `s_type` field indicating its type, which
/// must always match the value of [`TaggedStructure::STRUCTURE_TYPE`].
pub unsafe trait TaggedStructure {
const STRUCTURE_TYPE: vk::StructureType;
}

View file

@ -497,7 +497,6 @@ pub trait FieldExt {
/// array types (e.g. `[f32; 3]`) will be converted to pointer types (e.g. `&[f32; 3]`),
/// which is needed for `C` function parameters. Set to `false` for struct definitions.
fn type_tokens(&self, is_ffi_param: bool) -> TokenStream;
fn is_clone(&self) -> bool;
/// Whether this is C's `void` type (not to be mistaken with a void _pointer_!)
fn is_void(&self) -> bool;
@ -632,10 +631,6 @@ fn discard_outmost_delimiter(stream: TokenStream) -> TokenStream {
}
impl FieldExt for vkxml::Field {
fn is_clone(&self) -> bool {
true
}
fn param_ident(&self) -> Ident {
let name = self.name.as_deref().unwrap_or("field");
let name_corrected = match name {
@ -1429,15 +1424,9 @@ pub fn derive_default(struct_: &vkxml::Struct, has_lifetime: bool) -> Option<Tok
let default_fields = members.clone().map(|field| {
let param_ident = field.param_ident();
if is_structure_type(field) {
let ty = field
.type_enums
.as_ref()
.and_then(|ty| ty.split(',').next());
if let Some(variant) = ty {
let variant_ident = variant_ident("VkStructureType", variant);
if field.type_enums.is_some() {
quote! {
#param_ident: StructureType::#variant_ident
#param_ident: Self::STRUCTURE_TYPE
}
} else {
quote! {
@ -1567,6 +1556,13 @@ pub fn derive_setters(
.clone()
.find(|field| field.param_ident() == "p_next");
let structure_type_field = members
.clone()
.find(|field| field.param_ident() == "s_type");
// Must either have both, or none:
assert_eq!(next_field.is_some(), structure_type_field.is_some());
let nofilter_count_members = [
("VkPipelineViewportStateCreateInfo", "pViewports"),
("VkPipelineViewportStateCreateInfo", "pScissors"),
@ -1822,7 +1818,24 @@ pub fn derive_setters(
quote!(unsafe impl #extends for #name<'_> {})
});
let impl_structure_type_trait = structure_type_field.map(|s_type| {
let value = s_type
.type_enums
.as_deref()
.expect("s_type field must have a value in `vk.xml`");
assert!(!value.contains(','));
let value = variant_ident("VkStructureType", value);
quote! {
unsafe impl #lifetime TaggedStructure for #name #lifetime {
const STRUCTURE_TYPE: StructureType = StructureType::#value;
}
}
});
let q = quote! {
#impl_structure_type_trait
#(#impl_extend_trait)*
#next_trait