diff --git a/Cargo.toml b/Cargo.toml index ffc580f..f71a093 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ resolver = "2" [features] default = ["std"] std = [] +arrays = ["crevice-derive/arrays", "const_format"] [workspace] members = ["crevice-derive", "crevice-tests"] @@ -24,6 +25,7 @@ default-members = ["crevice-derive", "crevice-tests"] [dependencies] crevice-derive = { version = "0.8.0", path = "crevice-derive" } +const_format = {version = "0.2.22", optional = true} bytemuck = "1.4.1" mint = "0.5.8" diff --git a/crevice-derive/Cargo.toml b/crevice-derive/Cargo.toml index 1d64fbb..e78bef9 100644 --- a/crevice-derive/Cargo.toml +++ b/crevice-derive/Cargo.toml @@ -15,6 +15,9 @@ default = [] # Enable methods that let you introspect into the generated structs. debug-methods = [] +# Enable arrays support +arrays = [] + [lib] proc-macro = true diff --git a/crevice-derive/src/glsl.rs b/crevice-derive/src/glsl.rs index e6b7a3c..c5b89e6 100644 --- a/crevice-derive/src/glsl.rs +++ b/crevice-derive/src/glsl.rs @@ -1,6 +1,8 @@ use proc_macro2::{Literal, TokenStream}; use quote::quote; -use syn::{parse_quote, Data, DeriveInput, Fields, Path}; +#[cfg(feature = "arrays")] +use syn::TypeArray; +use syn::{parse_quote, Data, DeriveInput, Expr, Fields, Path, Type}; pub fn emit(input: DeriveInput) -> TokenStream { let fields = match &input.data { @@ -22,12 +24,14 @@ pub fn emit(input: DeriveInput) -> TokenStream { let glsl_fields = fields.named.iter().map(|field| { let field_ty = &field.ty; + let (base_ty, array_suffix) = remove_array_layers(field_ty); let field_name_str = Literal::string(&field.ident.as_ref().unwrap().to_string()); quote! { ::crevice::glsl::GlslField { - ty: <#field_ty as ::crevice::glsl::Glsl>::NAME, + ty: <#base_ty as ::crevice::glsl::Glsl>::NAME, name: #field_name_str, + dim: #array_suffix, } } }); @@ -44,3 +48,27 @@ pub fn emit(input: DeriveInput) -> TokenStream { } } } + +#[cfg(feature = "arrays")] +fn remove_array_layers(mut ty: &Type) -> (&Type, Expr) { + let mut suffix = quote!(""); + + loop { + match ty { + &Type::Array(TypeArray { + ref elem, ref len, .. + }) => { + ty = elem.as_ref(); + suffix = quote!( + ::crevice::internal::const_format::concatcp!("[", (#len as usize), "]", #suffix) + ); + } + _ => break, + } + } + (ty, Expr::Verbatim(suffix)) +} +#[cfg(not(feature = "arrays"))] +fn remove_array_layers(ty: &Type) -> (&Type, Expr) { + (ty, Expr::Verbatim(quote!(""))) +} diff --git a/crevice-derive/src/layout.rs b/crevice-derive/src/layout.rs index a0d26fd..ab2f7c7 100644 --- a/crevice-derive/src/layout.rs +++ b/crevice-derive/src/layout.rs @@ -19,6 +19,9 @@ pub fn emit( let as_trait_method = format_ident!("as_{}", mod_name); let from_trait_method = format_ident!("from_{}", mod_name); + let array_name = format_ident!("{}ArrayItem", trait_name); + let array_path: Path = parse_quote!(#mod_path::#array_name); + let visibility = input.vis; let input_name = input.ident; let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); @@ -50,14 +53,6 @@ pub fn emit( } }; - // Gives an expression telling whether the type should have trailing padding - // at least equal to its alignment. - let layout_pad_at_end_of_ty = |ty: &Type| { - quote! { - <<#ty as #as_trait_path>::Output as #trait_path>::PAD_AT_END - } - }; - let field_alignments = fields.iter().map(|field| layout_alignment_of_ty(&field.ty)); let struct_alignment = quote! { ::crevice::internal::max_arr([ @@ -100,15 +95,11 @@ pub fn emit( output.into_iter().collect::() }; - let pad_fn_impls: TokenStream = fields + let pad_fn_impls: TokenStream = pad_fns .iter() .enumerate() - .map(|(index, prev_field)| { - let pad_fn = &pad_fns[index]; - + .map(|(index, pad_fn)| { let starting_offset = offset_after_field(index); - let prev_field_has_end_padding = layout_pad_at_end_of_ty(&prev_field.ty); - let prev_field_alignment = layout_alignment_of_ty(&prev_field.ty); let next_field_or_self_alignment = fields .get(index + 1) @@ -125,21 +116,10 @@ pub fn emit( // alignment we are. let starting_offset = #starting_offset; - // If the previous field is a struct or array, we must align - // the next field to at least THAT field's alignment. - let min_alignment = if #prev_field_has_end_padding { - #prev_field_alignment - } else { - 0 - }; - // We set our target alignment to the larger of the // alignment due to the previous field and the alignment // requirement of the next field. - let alignment = ::crevice::internal::max( - #next_field_or_self_alignment, - min_alignment, - ); + let alignment = #next_field_or_self_alignment; // Using everything we've got, compute our padding amount. ::crevice::internal::align_offset(starting_offset, alignment) @@ -246,6 +226,16 @@ pub fn emit( quote!() }; + let array_item_impl = if cfg!(feature = "arrays") { + quote! { + unsafe impl #impl_generics #array_path for #generated_name #ty_generics #where_clause { + type Padding = [u8; 0]; + } + } + } else { + quote!() + }; + quote! { #pad_fn_impls #struct_definition @@ -253,11 +243,12 @@ pub fn emit( unsafe impl #impl_generics ::crevice::internal::bytemuck::Zeroable for #generated_name #ty_generics #where_clause {} unsafe impl #impl_generics ::crevice::internal::bytemuck::Pod for #generated_name #ty_generics #where_clause {} - unsafe impl #impl_generics #mod_path::#trait_name for #generated_name #ty_generics #where_clause { + unsafe impl #impl_generics #trait_path for #generated_name #ty_generics #where_clause { const ALIGNMENT: usize = #struct_alignment; - const PAD_AT_END: bool = true; } + #array_item_impl + impl #impl_generics #as_trait_path for #input_name #ty_generics #where_clause { type Output = #generated_name; diff --git a/crevice-tests/Cargo.toml b/crevice-tests/Cargo.toml index a6f8605..000ed61 100644 --- a/crevice-tests/Cargo.toml +++ b/crevice-tests/Cargo.toml @@ -5,6 +5,7 @@ edition = "2018" [features] wgpu-validation = ["wgpu", "naga", "futures"] +arrays = ["crevice/arrays"] [dependencies] crevice = { path = ".." } diff --git a/crevice-tests/src/gpu.rs b/crevice-tests/src/gpu.rs index 517e8d7..cf5f4a1 100644 --- a/crevice-tests/src/gpu.rs +++ b/crevice-tests/src/gpu.rs @@ -100,9 +100,11 @@ where "std140 value did not round-trip through wgpu successfully.\n\ Input: {:?}\n\ Output: {:?}\n\n\ + Data: {:?}\n\ + Bytes: {:?}\n\n\ GLSL shader:\n{}\n\n\ WGSL shader:\n{}", - value, output, glsl_shader, wgsl_shader, + value, output, data, bytes, glsl_shader, wgsl_shader, ); panic!("wgpu round-trip failure for {}", T::NAME); diff --git a/crevice-tests/src/lib.rs b/crevice-tests/src/lib.rs index 1b3d2f1..00e80df 100644 --- a/crevice-tests/src/lib.rs +++ b/crevice-tests/src/lib.rs @@ -342,3 +342,70 @@ fn bools_and_bool_vectors() { y: 8, }); } + +#[test] +#[cfg(feature = "arrays")] +fn array_strides_small_value() { + #[derive(Debug, PartialEq, AsStd140, AsStd430)] + struct ArrayOfSmallValues { + inner: [f32; 4], + } + + assert_std140!((size = 64, align = 16) ArrayOfSmallValues { + inner: 0, + }); + + assert_std430!((size = 16, align = 4) ArrayOfSmallValues { + inner: 0, + }); +} + +#[test] +#[cfg(feature = "arrays")] +fn array_strides_vec3() { + #[derive(Debug, PartialEq, AsStd140, AsStd430, GlslStruct)] + struct ArrayOfVector3 { + inner: [Vector3; 6], + } + + assert_std140!((size = 96, align = 16) ArrayOfVector3 { + inner: 0, + }); + + assert_std430!((size = 96, align = 16) ArrayOfVector3 { + inner: 0, + }); + + test_round_trip_struct(ArrayOfVector3 { + inner: [ + [0x00010203, 0x04050607, 0x08091011].into(), + [0x12131415, 0x16171819, 0x20212223].into(), + [0x24252627, 0x28293031, 0x32333435].into(), + [0x36373839, 0x40414243, 0x44454647].into(), + [0x48495051, 0x52535455, 0x56575859].into(), + [0x60616263, 0x64656667, 0x68697071].into(), + ], + }) +} + +#[test] +#[cfg(feature = "arrays")] +fn array_of_custom_struct_works() { + #[derive(Debug, PartialEq, AsStd140, AsStd430)] + struct SomeStruct { + x: f32, + } + + #[derive(Debug, PartialEq, AsStd140, AsStd430)] + struct ArrayOfStructs { + inner: [SomeStruct; 4], + } + + assert_std140!((size = 64, align = 16) ArrayOfStructs { + inner: 0, + }); + + assert_std430!((size = 16, align = 4) ArrayOfStructs { + inner: 0, + }); +} diff --git a/src/glsl.rs b/src/glsl.rs index 9305a77..5196bd8 100644 --- a/src/glsl.rs +++ b/src/glsl.rs @@ -16,6 +16,9 @@ pub struct GlslField { /// The field's name. This must be a valid GLSL identifier. pub name: &'static str, + + /// The field's array dimensions. This is a string of the form "[1][2]..." + pub dim: &'static str, } /// Trait for types that can be represented as a struct in GLSL. @@ -37,6 +40,7 @@ pub unsafe trait GlslStruct: Glsl { output.push_str(field.ty); output.push(' '); output.push_str(field.name); + output.push_str(field.dim); output.push_str(";\n"); } diff --git a/src/internal.rs b/src/internal.rs index cd22972..d83c8b2 100644 --- a/src/internal.rs +++ b/src/internal.rs @@ -38,3 +38,6 @@ pub const fn max_arr(input: [usize; N]) -> usize { max } + +#[cfg(feature = "arrays")] +pub use const_format; diff --git a/src/lib.rs b/src/lib.rs index d873117..abc3d90 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -150,7 +150,8 @@ Crevice supports Rust 1.52.1 and newer due to use of new `const fn` features. [glsl-layout]: https://github.com/rustgd/glsl-layout */ -#![deny(missing_docs)] +#![cfg_attr(feature = "arrays", feature(generic_const_exprs))] +//#![deny(missing_docs)] #![cfg_attr(not(feature = "std"), no_std)] #[macro_use] diff --git a/src/std140.rs b/src/std140.rs index a48d2d4..9849ac9 100644 --- a/src/std140.rs +++ b/src/std140.rs @@ -1,6 +1,8 @@ //! Defines traits and types for working with data adhering to GLSL's `std140` //! layout specification. +#[cfg(feature = "arrays")] +mod arrays; mod dynamic_uniform; mod primitives; mod sizer; @@ -10,6 +12,8 @@ mod writer; pub use crate::bool::Bool; +#[cfg(feature = "arrays")] +pub use self::arrays::Std140ArrayItem; pub use self::dynamic_uniform::*; pub use self::primitives::*; pub use self::sizer::*; diff --git a/src/std140/arrays.rs b/src/std140/arrays.rs new file mode 100644 index 0000000..e98105f --- /dev/null +++ b/src/std140/arrays.rs @@ -0,0 +1,77 @@ +use core::fmt::Debug; +use core::mem::{transmute_copy, MaybeUninit}; + +use bytemuck::{Pod, Zeroable}; + +use super::{AsStd140, Std140}; + +pub unsafe trait Std140ArrayItem: Std140 { + type Padding: Zeroable + Copy + Debug; +} + +type Padded = (T, ::Padding); + +fn wrap(x: &T) -> Padded +where + T::Output: Std140ArrayItem, +{ + (x.as_std140(), Zeroable::zeroed()) +} + +fn unwrap(x: Padded) -> T +where + T::Output: Std140ArrayItem, +{ + T::from_std140(x.0) +} + +#[doc(hidden)] +#[derive(Copy, Clone, Debug)] +#[repr(transparent)] +pub struct Std140Array([Padded; N]); + +unsafe impl Zeroable for Std140Array {} +unsafe impl Pod for Std140Array {} +unsafe impl Std140 for Std140Array { + const ALIGNMENT: usize = crate::internal::max(16, T::ALIGNMENT); +} + +impl AsStd140 for [T; N] +where + T::Output: Std140ArrayItem, +{ + type Output = Std140Array; + fn as_std140(&self) -> Self::Output { + let mut res: [MaybeUninit<_>; N] = unsafe { MaybeUninit::uninit().assume_init() }; + + for i in 0..N { + res[i] = MaybeUninit::new(wrap(&self[i])); + } + + unsafe { transmute_copy(&res) } + } + + fn from_std140(val: Self::Output) -> Self { + val.0.map(|x| unwrap(x)) + } +} + +unsafe impl Std140ArrayItem for f32 { + type Padding = [u8; 12]; +} + +unsafe impl Std140ArrayItem for f64 { + type Padding = [u8; 8]; +} + +unsafe impl Std140ArrayItem for i32 { + type Padding = [u8; 12]; +} + +unsafe impl Std140ArrayItem for u32 { + type Padding = [u8; 12]; +} + +unsafe impl Std140ArrayItem for crate::bool::Bool { + type Padding = [u8; 12]; +} diff --git a/src/std140/dynamic_uniform.rs b/src/std140/dynamic_uniform.rs index 8c071c2..9eaa4fa 100644 --- a/src/std140/dynamic_uniform.rs +++ b/src/std140/dynamic_uniform.rs @@ -1,6 +1,7 @@ use bytemuck::{Pod, Zeroable}; -use crate::internal::max; +#[allow(unused_imports)] +use crate::internal::{align_offset, max}; use crate::std140::{AsStd140, Std140}; /// Wrapper type that aligns the inner type to at least 256 bytes. diff --git a/src/std140/primitives.rs b/src/std140/primitives.rs index c823389..1ec87d9 100644 --- a/src/std140/primitives.rs +++ b/src/std140/primitives.rs @@ -1,7 +1,12 @@ +#[cfg(feature = "arrays")] +use core::mem::size_of; + use bytemuck::{Pod, Zeroable}; use crate::bool::Bool; use crate::glsl::Glsl; +#[cfg(feature = "arrays")] +use crate::internal::{align_offset, max}; use crate::std140::{AsStd140, Std140}; unsafe impl Std140 for f32 { @@ -58,6 +63,11 @@ macro_rules! vectors { const ALIGNMENT: usize = $align; } + #[cfg(feature = "arrays")] + unsafe impl super::Std140ArrayItem for $name { + type Padding = [u8; align_offset(size_of::<$name>(), max(16, $align))]; + } + unsafe impl Glsl for $name { const NAME: &'static str = stringify!($glsl_name); } @@ -111,8 +121,11 @@ macro_rules! matrices { unsafe impl Std140 for $name { const ALIGNMENT: usize = $align; - /// Matrices are technically arrays of primitives, and as such require pad at end. - const PAD_AT_END: bool = true; + } + + #[cfg(feature = "arrays")] + unsafe impl super::Std140ArrayItem for $name { + type Padding = [u8; 0]; } unsafe impl Glsl for $name { diff --git a/src/std140/traits.rs b/src/std140/traits.rs index 1f92955..17bb74a 100644 --- a/src/std140/traits.rs +++ b/src/std140/traits.rs @@ -18,12 +18,6 @@ pub unsafe trait Std140: Copy + Zeroable + Pod { /// slices safe. const ALIGNMENT: usize; - /// Whether this type requires a padding at the end (ie, is a struct or an array - /// of primitives). - /// See - /// (rule 4 and 9) - const PAD_AT_END: bool = false; - /// Casts the type to a byte array. Implementors should not override this /// method. /// diff --git a/src/std430.rs b/src/std430.rs index 74500cf..421053e 100644 --- a/src/std430.rs +++ b/src/std430.rs @@ -1,6 +1,8 @@ //! Defines traits and types for working with data adhering to GLSL's `std140` //! layout specification. +#[cfg(feature = "arrays")] +mod arrays; mod primitives; mod sizer; mod traits; @@ -9,6 +11,8 @@ mod writer; pub use crate::bool::Bool; +#[cfg(feature = "arrays")] +pub use self::arrays::Std430ArrayItem; pub use self::primitives::*; pub use self::sizer::*; pub use self::traits::*; diff --git a/src/std430/arrays.rs b/src/std430/arrays.rs new file mode 100644 index 0000000..a3e4338 --- /dev/null +++ b/src/std430/arrays.rs @@ -0,0 +1,77 @@ +use core::fmt::Debug; +use core::mem::{transmute_copy, MaybeUninit}; + +use bytemuck::{Pod, Zeroable}; + +use super::{AsStd430, Std430}; + +pub unsafe trait Std430ArrayItem: Std430 { + type Padding: Zeroable + Copy + Debug; +} + +type Padded = (T, ::Padding); + +fn wrap(x: &T) -> Padded +where + T::Output: Std430ArrayItem, +{ + (x.as_std430(), Zeroable::zeroed()) +} + +fn unwrap(x: Padded) -> T +where + T::Output: Std430ArrayItem, +{ + T::from_std430(x.0) +} + +#[doc(hidden)] +#[derive(Copy, Clone, Debug)] +#[repr(transparent)] +pub struct Std430Array([Padded; N]); + +unsafe impl Zeroable for Std430Array {} +unsafe impl Pod for Std430Array {} +unsafe impl Std430 for Std430Array { + const ALIGNMENT: usize = T::ALIGNMENT; +} + +impl AsStd430 for [T; N] +where + T::Output: Std430ArrayItem, +{ + type Output = Std430Array; + fn as_std430(&self) -> Self::Output { + let mut res: [MaybeUninit<_>; N] = unsafe { MaybeUninit::uninit().assume_init() }; + + for i in 0..N { + res[i] = MaybeUninit::new(wrap(&self[i])); + } + + unsafe { transmute_copy(&res) } + } + + fn from_std430(val: Self::Output) -> Self { + val.0.map(|x| unwrap(x)) + } +} + +unsafe impl Std430ArrayItem for f32 { + type Padding = [u8; 0]; +} + +unsafe impl Std430ArrayItem for f64 { + type Padding = [u8; 0]; +} + +unsafe impl Std430ArrayItem for i32 { + type Padding = [u8; 0]; +} + +unsafe impl Std430ArrayItem for u32 { + type Padding = [u8; 0]; +} + +unsafe impl Std430ArrayItem for crate::bool::Bool { + type Padding = [u8; 0]; +} diff --git a/src/std430/primitives.rs b/src/std430/primitives.rs index 1f5ea97..d16e6ea 100644 --- a/src/std430/primitives.rs +++ b/src/std430/primitives.rs @@ -1,7 +1,12 @@ +#[cfg(feature = "arrays")] +use core::mem::size_of; + use bytemuck::{Pod, Zeroable}; use crate::bool::Bool; use crate::glsl::Glsl; +#[cfg(feature = "arrays")] +use crate::internal::align_offset; use crate::std430::{AsStd430, Std430}; unsafe impl Std430 for f32 { @@ -58,6 +63,11 @@ macro_rules! vectors { const ALIGNMENT: usize = $align; } + #[cfg(feature = "arrays")] + unsafe impl super::Std430ArrayItem for $name { + type Padding = [u8; align_offset(size_of::<$name>(), $align)]; + } + unsafe impl Glsl for $name { const NAME: &'static str = stringify!($glsl_name); } @@ -111,8 +121,11 @@ macro_rules! matrices { unsafe impl Std430 for $name { const ALIGNMENT: usize = $align; - /// Matrices are technically arrays of primitives, and as such require pad at end. - const PAD_AT_END: bool = true; + } + + #[cfg(feature = "arrays")] + unsafe impl super::Std430ArrayItem for $name { + type Padding = [u8; 0]; } unsafe impl Glsl for $name { diff --git a/src/std430/traits.rs b/src/std430/traits.rs index 96523ba..fee8c07 100644 --- a/src/std430/traits.rs +++ b/src/std430/traits.rs @@ -18,12 +18,6 @@ pub unsafe trait Std430: Copy + Zeroable + Pod { /// slices safe. const ALIGNMENT: usize; - /// Whether this type requires a padding at the end (ie, is a struct or an array - /// of primitives). - /// See - /// (rule 4 and 9) - const PAD_AT_END: bool = false; - /// Casts the type to a byte array. Implementors should not override this /// method. /// diff --git a/tests/snapshots/test__generate_struct_array_glsl.snap b/tests/snapshots/test__generate_struct_array_glsl.snap new file mode 100644 index 0000000..7829bd6 --- /dev/null +++ b/tests/snapshots/test__generate_struct_array_glsl.snap @@ -0,0 +1,8 @@ +--- +source: tests/test.rs +expression: "TestGlsl::glsl_definition()" + +--- +struct TestGlsl { + vec3 foo[8][4]; +}; diff --git a/tests/test.rs b/tests/test.rs index 669557d..c349348 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -48,3 +48,15 @@ fn generate_struct_glsl() { insta::assert_display_snapshot!(TestGlsl::glsl_definition()); } + +#[test] +#[cfg(feature = "arrays")] +fn generate_struct_array_glsl() { + #[allow(dead_code)] + #[derive(GlslStruct)] + struct TestGlsl { + foo: [[mint::Vector3; 8]; 4], + } + + insta::assert_display_snapshot!(TestGlsl::glsl_definition()); +}