From 8ddab3ec4c642b3a150bbf156abd014cb370abbc Mon Sep 17 00:00:00 2001 From: Lucien Greathouse Date: Sun, 17 Oct 2021 15:05:59 -0400 Subject: [PATCH] Rewrite and Fix Remaining Padding Bugs (#35) * Cleaner validation tests * Introduce memoffset * New tests using manual offset checking * yeah that test is broken too * Rename AsStdX::StdXType to AsStdX::Output for sanity * Rename test file * Revamp and merge test suites * Squish tests more, make wgpu portion not run by default * Rewrite Crevice from the ground up, suddenly passing all the tests * Add primitive test for mat3 * Add a bare mat4 test * Fix std430 derive and add it to tests * Use actual struct alignment instead of min alignment for trailing padding * Start testing (and fix) std430 * Add and disable tests to work around wgpu --- .github/workflows/ci.yml | 2 +- Cargo.toml | 5 +- crevice-derive/Cargo.toml | 7 +- crevice-derive/src/layout.rs | 483 +++++++++--------- crevice-derive/src/lib.rs | 6 +- crevice-tests/Cargo.toml | 20 + crevice-tests/src/gpu.rs | 268 ++++++++++ crevice-tests/src/lib.rs | 325 ++++++++++++ crevice-tests/src/util.rs | 143 ++++++ crevice-validation/Cargo.toml | 16 - crevice-validation/src/lib.rs | 250 --------- src/internal.rs | 21 +- src/mint.rs | 24 +- src/std140/dynamic_uniform.rs | 6 +- src/std140/sizer.rs | 4 +- src/std140/traits.rs | 12 +- src/std430/primitives.rs | 26 +- src/std430/sizer.rs | 4 +- src/std430/traits.rs | 12 +- .../std140__matrix_uniform_std140-2.snap | 9 - .../std140__matrix_uniform_std140.snap | 26 - .../std140__matrix_uniform_std430.snap | 26 - .../std140__more_than_16_alignment.snap | 18 - tests/snapshots/std140__padding_at_end.snap | 26 - tests/snapshots/std140__point_light.snap | 42 -- tests/snapshots/std140__primitive_f32.snap | 26 - ...lculations_for_differing_member_sizes.snap | 26 - .../snapshots/std140__using_vec3_padding.snap | 26 - tests/snapshots/std140__vec3.snap | 26 - ...l.snap => test__generate_struct_glsl.snap} | 2 +- tests/std140.rs | 229 --------- tests/test.rs | 50 ++ 32 files changed, 1125 insertions(+), 1041 deletions(-) create mode 100644 crevice-tests/Cargo.toml create mode 100644 crevice-tests/src/gpu.rs create mode 100644 crevice-tests/src/lib.rs create mode 100644 crevice-tests/src/util.rs delete mode 100644 crevice-validation/Cargo.toml delete mode 100644 crevice-validation/src/lib.rs delete mode 100644 tests/snapshots/std140__matrix_uniform_std140-2.snap delete mode 100644 tests/snapshots/std140__matrix_uniform_std140.snap delete mode 100644 tests/snapshots/std140__matrix_uniform_std430.snap delete mode 100644 tests/snapshots/std140__more_than_16_alignment.snap delete mode 100644 tests/snapshots/std140__padding_at_end.snap delete mode 100644 tests/snapshots/std140__point_light.snap delete mode 100644 tests/snapshots/std140__primitive_f32.snap delete mode 100644 tests/snapshots/std140__proper_offset_calculations_for_differing_member_sizes.snap delete mode 100644 tests/snapshots/std140__using_vec3_padding.snap delete mode 100644 tests/snapshots/std140__vec3.snap rename tests/snapshots/{std140__generate_struct_glsl.snap => test__generate_struct_glsl.snap} (79%) delete mode 100644 tests/std140.rs create mode 100644 tests/test.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 68d5748..ce8d136 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,4 +42,4 @@ jobs: - uses: actions/checkout@v2 - name: Run Validation Tests - run: cargo test --all --verbose \ No newline at end of file + run: cargo test --all-features --no-fail-fast --verbose \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 17318a9..ee6bdcd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,8 @@ default = ["std"] std = [] [workspace] -members = ["crevice-derive", "crevice-validation"] +members = ["crevice-derive", "crevice-tests"] +default-members = ["crevice-derive", "crevice-tests"] [dependencies] crevice-derive = { version = "0.7.1", path = "crevice-derive" } @@ -30,5 +31,3 @@ mint = "0.5.5" [dev-dependencies] cgmath = { version = "0.17.0", features = ["mint"] } insta = "0.16.1" -type-layout = { version = "0.2.0", features = ["serde1"] } -crevice-derive = { version = "0.7.1", path = "crevice-derive", features = ["test_type_layout"] } diff --git a/crevice-derive/Cargo.toml b/crevice-derive/Cargo.toml index 2f0b930..393c60f 100644 --- a/crevice-derive/Cargo.toml +++ b/crevice-derive/Cargo.toml @@ -10,9 +10,10 @@ repository = "https://github.com/LPGhatguy/crevice" license = "MIT OR Apache-2.0" [features] -# Feature used for testing; enables type_layout derive on types. -# Requires crate using derive to depend on type_layout as well. -test_type_layout = [] +default = [] + +# Enable methods that let you introspect into the generated structs. +debug-methods = [] [lib] proc-macro = true diff --git a/crevice-derive/src/layout.rs b/crevice-derive/src/layout.rs index 5d54124..a0d26fd 100644 --- a/crevice-derive/src/layout.rs +++ b/crevice-derive/src/layout.rs @@ -1,278 +1,281 @@ use proc_macro2::{Span, TokenStream}; use quote::{format_ident, quote}; -use syn::{parse_quote, Data, DeriveInput, Fields, Ident, Path}; - -pub struct EmitOptions { - /// The Rust-friendly name of the layout, like Std140. - pub layout_name: Ident, - - /// The minimum alignment for a struct in this layout. - pub min_struct_alignment: usize, +use syn::{parse_quote, Data, DeriveInput, Fields, Ident, Path, Type}; + +pub fn emit( + input: DeriveInput, + trait_name: &'static str, + mod_name: &'static str, + min_struct_alignment: usize, +) -> TokenStream { + let mod_name = Ident::new(mod_name, Span::call_site()); + let trait_name = Ident::new(trait_name, Span::call_site()); + + let mod_path: Path = parse_quote!(::crevice::#mod_name); + let trait_path: Path = parse_quote!(#mod_path::#trait_name); + + let as_trait_name = format_ident!("As{}", trait_name); + let as_trait_path: Path = parse_quote!(#mod_path::#as_trait_name); + let as_trait_method = format_ident!("as_{}", mod_name); + let from_trait_method = format_ident!("from_{}", mod_name); + + let visibility = input.vis; + let input_name = input.ident; + let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); + + let generated_name = format_ident!("{}{}", trait_name, input_name); + + // Crevice's derive only works on regular structs. We could potentially + // support transparent tuple structs in the future. + let fields: Vec<_> = match &input.data { + Data::Struct(data) => match &data.fields { + Fields::Named(fields) => fields.named.iter().collect(), + Fields::Unnamed(_) => panic!("Tuple structs are not supported"), + Fields::Unit => panic!("Unit structs are not supported"), + }, + Data::Enum(_) | Data::Union(_) => panic!("Only structs are supported"), + }; + + // Gives the layout-specific version of the given type. + let layout_version_of_ty = |ty: &Type| { + quote! { + <#ty as #as_trait_path>::Output + } + }; - /// The fully-qualified path to the Crevice module containing everything for - /// this layout. - pub mod_path: Path, + // Gives an expression returning the layout-specific alignment for the type. + let layout_alignment_of_ty = |ty: &Type| { + quote! { + <<#ty as #as_trait_path>::Output as #trait_path>::ALIGNMENT + } + }; - /// The fully-qualified path to the trait defining a type in this layout. - pub trait_path: Path, + // 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([ + #min_struct_alignment, + #(#field_alignments,)* + ]) + }; + + // Generate names for each padding calculation function. + let pad_fns: Vec<_> = (0..fields.len()) + .map(|index| format_ident!("_{}__{}Pad{}", input_name, trait_name, index)) + .collect(); + + // Computes the offset immediately AFTER the field with the given index. + // + // This function depends on the generated padding calculation functions to + // do correct alignment. Be careful not to cause recursion! + let offset_after_field = |target: usize| { + let mut output = vec![quote!(0usize)]; + + for index in 0..=target { + let field_ty = &fields[index].ty; + let layout_ty = layout_version_of_ty(field_ty); + + output.push(quote! { + + ::core::mem::size_of::<#layout_ty>() + }); + + // For every field except our target field, also add the generated + // padding. Padding occurs after each field, so it isn't included in + // this value. + if index < target { + let pad_fn = &pad_fns[index]; + output.push(quote! { + + #pad_fn() + }); + } + } - /// The fully-qualified path to the trait implemented for types that can be - /// converted into this layout, like AsStd140. - pub as_trait_path: Path, + output.into_iter().collect::() + }; + + let pad_fn_impls: TokenStream = fields + .iter() + .enumerate() + .map(|(index, prev_field)| { + let pad_fn = &pad_fns[index]; + + 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) + .map(|next_field| layout_alignment_of_ty(&next_field.ty)) + .unwrap_or(quote!(#struct_alignment)); + + quote! { + /// Tells how many bytes of padding have to be inserted after + /// the field with index #index. + #[allow(non_snake_case)] + const fn #pad_fn() -> usize { + // First up, calculate our offset into the struct so far. + // We'll use this value to figure out how far out of + // 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, + ); + + // Using everything we've got, compute our padding amount. + ::crevice::internal::align_offset(starting_offset, alignment) + } + } + }) + .collect(); + + let generated_struct_fields: TokenStream = fields + .iter() + .enumerate() + .map(|(index, field)| { + let field_name = field.ident.as_ref().unwrap(); + let field_ty = layout_version_of_ty(&field.ty); + let pad_field_name = format_ident!("_pad{}", index); + let pad_fn = &pad_fns[index]; + + quote! { + #field_name: #field_ty, + #pad_field_name: [u8; #pad_fn()], + } + }) + .collect(); - /// The name of the associated type contained in AsTrait. - pub as_trait_assoc: Ident, + let generated_struct_field_init: TokenStream = fields + .iter() + .map(|field| { + let field_name = field.ident.as_ref().unwrap(); - /// The name of the method used to convert from AsTrait to Trait. - pub as_trait_method: Ident, + quote! { + #field_name: self.#field_name.#as_trait_method(), + } + }) + .collect(); - // The name of the method used to convert from Trait to AsTrait. - pub from_trait_method: Ident, -} + let input_struct_field_init: TokenStream = fields + .iter() + .map(|field| { + let field_name = field.ident.as_ref().unwrap(); -impl EmitOptions { - pub fn new( - layout_name: &'static str, - mod_name: &'static str, - min_struct_alignment: usize, - ) -> Self { - let mod_name = Ident::new(mod_name, Span::call_site()); - let layout_name = Ident::new(layout_name, Span::call_site()); - - let mod_path = parse_quote!(::crevice::#mod_name); - let trait_path = parse_quote!(#mod_path::#layout_name); - - let as_trait_name = format_ident!("As{}", layout_name); - let as_trait_path = parse_quote!(#mod_path::#as_trait_name); - let as_trait_assoc = format_ident!("{}Type", layout_name); - let as_trait_method = format_ident!("as_{}", mod_name); - let from_trait_method = format_ident!("from_{}", mod_name); - - Self { - layout_name, - min_struct_alignment, - - mod_path, - trait_path, - as_trait_path, - as_trait_assoc, - as_trait_method, - from_trait_method, + quote! { + #field_name: #as_trait_path::#from_trait_method(input.#field_name), + } + }) + .collect(); + + let struct_definition = quote! { + #[derive(Debug, Clone, Copy)] + #[repr(C)] + #[allow(non_snake_case)] + #visibility struct #generated_name #ty_generics #where_clause { + #generated_struct_fields } - } + }; - pub fn emit(&self, input: DeriveInput) -> TokenStream { - let min_struct_alignment = self.min_struct_alignment; - let layout_name = &self.layout_name; - let mod_path = &self.mod_path; - let trait_path = &self.trait_path; - let as_trait_path = &self.as_trait_path; - let as_trait_assoc = &self.as_trait_assoc; - let as_trait_method = &self.as_trait_method; - let from_trait_method = &self.from_trait_method; - - let visibility = input.vis; - - let name = input.ident; - let generated_name = format_ident!("{}{}", layout_name, name); - - let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); - - let fields = match &input.data { - Data::Struct(data) => match &data.fields { - Fields::Named(fields) => fields, - Fields::Unnamed(_) => panic!("Tuple structs are not supported"), - Fields::Unit => panic!("Unit structs are not supported"), - }, - Data::Enum(_) | Data::Union(_) => panic!("Only structs are supported"), - }; - - // Generate the names we'll use for calculating alignment of each field. - // Each name will turn into a const fn that's invoked to compute the - // size of a padding array before each field. - let align_names: Vec<_> = fields - .named + let debug_methods = if cfg!(feature = "debug-methods") { + let debug_fields: TokenStream = fields .iter() .map(|field| { - format_ident!( - "_{}__{}__{}__align", - name, - field.ident.as_ref().unwrap(), - layout_name - ) - }) - .collect(); - - // Generate one function per field that is used to apply alignment - // padding. Each function invokes all previous functions to calculate - // the total offset into the struct for the current field, then aligns - // up to the nearest multiple of alignment. - let alignment_calculators: Vec<_> = fields - .named - .iter() - .enumerate() - .map(|(index, field)| { - let align_name = &align_names[index]; - - let offset_accumulation = - fields - .named - .iter() - .zip(&align_names) - .take(index) - .map(|(field, align_name)| { - let field_ty = &field.ty; - quote! { - offset += #align_name(); - offset += ::core::mem::size_of::<<#field_ty as #as_trait_path>::#as_trait_assoc>(); - } - }); - - let pad_at_end = index - .checked_sub(1) - .map_or(quote!{0usize}, |prev_index|{ - let field = &fields.named[prev_index]; - let field_ty = &field.ty; - quote! { - if <<#field_ty as #as_trait_path>::#as_trait_assoc as #mod_path::#layout_name>::PAD_AT_END { - <<#field_ty as #as_trait_path>::#as_trait_assoc as #mod_path::#layout_name>::ALIGNMENT - } - else { - 0usize - } - } - }); - - let field_ty = &field.ty; - - quote! { - #[allow(non_snake_case)] - pub const fn #align_name() -> usize { - let mut offset = 0; - #( #offset_accumulation )* - - ::crevice::internal::align_offset( - offset, - ::crevice::internal::max( - <<#field_ty as #as_trait_path>::#as_trait_assoc as #mod_path::#layout_name>::ALIGNMENT, - #pad_at_end - ) - ) - } - } - }) - .collect(); - - // Generate the struct fields that will be present in the generated - // struct. Each field in the original struct turns into two fields in - // the generated struct: - // - // * Alignment, a byte array whose size is computed from #align_name(). - // * Data, the layout-specific version of the original field. - let generated_fields: Vec<_> = fields - .named - .iter() - .zip(&align_names) - .map(|(field, align_name)| { - let field_ty = &field.ty; let field_name = field.ident.as_ref().unwrap(); + let field_ty = &field.ty; quote! { - #align_name: [u8; #align_name()], - #field_name: <#field_ty as #as_trait_path>::#as_trait_assoc, + fields.push(Field { + name: stringify!(#field_name), + size: ::core::mem::size_of::<#field_ty>(), + offset: (&zeroed.#field_name as *const _ as usize) + - (&zeroed as *const _ as usize), + }); } }) .collect(); - // Generate an initializer for each field in the original struct. - // Alignment fields are filled in with zeroes using struct update - // syntax. - let field_initializers: Vec<_> = fields - .named - .iter() - .map(|field| { - let field_name = field.ident.as_ref().unwrap(); + quote! { + impl #impl_generics #generated_name #ty_generics #where_clause { + fn debug_metrics() -> String { + let size = ::core::mem::size_of::(); + let align = ::ALIGNMENT; + + let zeroed: Self = ::crevice::internal::bytemuck::Zeroable::zeroed(); + + #[derive(Debug)] + struct Field { + name: &'static str, + offset: usize, + size: usize, + } + let mut fields = Vec::new(); - quote!(#field_name: self.#field_name.#as_trait_method()) - }) - .collect(); + #debug_fields - let field_unwrappers: Vec<_> = fields - .named - .iter() - .map(|field|{ - let field_name = field.ident.as_ref().unwrap(); - let field_ty = &field.ty; - quote!(#field_name: <#field_ty as #as_trait_path>::#from_trait_method(value.#field_name)) - }) - .collect(); - - // This fold builds up an expression that finds the maximum alignment out of - // all of the fields in the struct. For this struct: - // - // struct Foo { a: ty1, b: ty2 } - // - // ...we should generate an expression like this: - // - // max(ty2_align, max(ty1_align, min_align)) - let struct_alignment = fields.named.iter().fold( - quote!(#min_struct_alignment), - |last, field| { - let field_ty = &field.ty; + format!("Size {}, Align {}, fields: {:#?}", size, align, fields) + } - quote! { - ::crevice::internal::max( - <<#field_ty as #as_trait_path>::#as_trait_assoc as #trait_path>::ALIGNMENT, - #last, + fn debug_definitions() -> &'static str { + stringify!( + #struct_definition + #pad_fn_impls ) } - }, - ); - - // For testing purposes, we can optionally generate type layout - // information using the type-layout crate. - let type_layout_derive = if cfg!(feature = "test_type_layout") { - quote!(#[derive(::type_layout::TypeLayout)]) - } else { - quote!() - }; - - quote! { - #( #alignment_calculators )* - - #[derive(Debug, Clone, Copy)] - #type_layout_derive - #[repr(C)] - #[allow(non_snake_case)] - #visibility struct #generated_name #ty_generics #where_clause { - #( #generated_fields )* } + } + } else { + quote!() + }; - 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 {} + quote! { + #pad_fn_impls + #struct_definition - unsafe impl #impl_generics #mod_path::#layout_name for #generated_name #ty_generics #where_clause { - const ALIGNMENT: usize = #struct_alignment; - const PAD_AT_END: bool = true; - } + 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 { + const ALIGNMENT: usize = #struct_alignment; + const PAD_AT_END: bool = true; + } - impl #impl_generics #as_trait_path for #name #ty_generics #where_clause { - type #as_trait_assoc = #generated_name; + impl #impl_generics #as_trait_path for #input_name #ty_generics #where_clause { + type Output = #generated_name; - fn #as_trait_method(&self) -> Self::#as_trait_assoc { - Self::#as_trait_assoc { - #( #field_initializers, )* + fn #as_trait_method(&self) -> Self::Output { + Self::Output { + #generated_struct_field_init - ..::crevice::internal::bytemuck::Zeroable::zeroed() - } + ..::crevice::internal::bytemuck::Zeroable::zeroed() } + } - fn #from_trait_method(value: Self::#as_trait_assoc) -> Self { - Self { - #( #field_unwrappers, )* - } + fn #from_trait_method(input: Self::Output) -> Self { + Self { + #input_struct_field_init } } } + + #debug_methods } } diff --git a/crevice-derive/src/lib.rs b/crevice-derive/src/lib.rs index 9c36ddb..ab1012a 100644 --- a/crevice-derive/src/lib.rs +++ b/crevice-derive/src/lib.rs @@ -5,12 +5,10 @@ use proc_macro::TokenStream as CompilerTokenStream; use syn::{parse_macro_input, DeriveInput}; -use crate::layout::EmitOptions; - #[proc_macro_derive(AsStd140)] pub fn derive_as_std140(input: CompilerTokenStream) -> CompilerTokenStream { let input = parse_macro_input!(input as DeriveInput); - let expanded = EmitOptions::new("Std140", "std140", 16).emit(input); + let expanded = layout::emit(input, "Std140", "std140", 16); CompilerTokenStream::from(expanded) } @@ -18,7 +16,7 @@ pub fn derive_as_std140(input: CompilerTokenStream) -> CompilerTokenStream { #[proc_macro_derive(AsStd430)] pub fn derive_as_std430(input: CompilerTokenStream) -> CompilerTokenStream { let input = parse_macro_input!(input as DeriveInput); - let expanded = EmitOptions::new("Std430", "std430", 0).emit(input); + let expanded = layout::emit(input, "Std430", "std430", 0); CompilerTokenStream::from(expanded) } diff --git a/crevice-tests/Cargo.toml b/crevice-tests/Cargo.toml new file mode 100644 index 0000000..a6f8605 --- /dev/null +++ b/crevice-tests/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "crevice-tests" +version = "0.1.0" +edition = "2018" + +[features] +wgpu-validation = ["wgpu", "naga", "futures"] + +[dependencies] +crevice = { path = ".." } +crevice-derive = { path = "../crevice-derive", features = ["debug-methods"] } + +anyhow = "1.0.44" +bytemuck = "1.7.2" +memoffset = "0.6.4" +mint = "0.5.5" + +futures = { version = "0.3.17", features = ["executor"], optional = true } +naga = { version = "0.7.0", features = ["glsl-in", "wgsl-out"], optional = true } +wgpu = { version = "0.11.0", optional = true } diff --git a/crevice-tests/src/gpu.rs b/crevice-tests/src/gpu.rs new file mode 100644 index 0000000..7e0bec5 --- /dev/null +++ b/crevice-tests/src/gpu.rs @@ -0,0 +1,268 @@ +use std::borrow::Cow; +use std::fmt::Debug; +use std::marker::PhantomData; + +use crevice::glsl::{Glsl, GlslStruct}; +use crevice::std140::{AsStd140, Std140}; +use crevice::std430::{AsStd430, Std430}; +use futures::executor::block_on; +use wgpu::util::DeviceExt; + +const BASE_SHADER: &str = "#version 450 + +{struct_definition} + +layout({layout}, set = 0, binding = 0) readonly buffer INPUT { + {struct_name} in_data; +}; + +layout({layout}, set = 0, binding = 1) buffer OUTPUT { + {struct_name} out_data; +}; + +void main() { + out_data = in_data; +}"; + +pub fn test_round_trip_struct(value: T) { + let shader_std140 = glsl_shader_for_struct::("std140"); + let shader_std430 = glsl_shader_for_struct::("std430"); + + let context = Context::new(); + context.test_round_trip_std140(&shader_std140, &value); + context.test_round_trip_std430(&shader_std430, &value); +} + +pub fn test_round_trip_primitive(value: T) { + let shader_std140 = glsl_shader_for_primitive::("std140"); + let shader_std430 = glsl_shader_for_primitive::("std430"); + + let context = Context::new(); + context.test_round_trip_std140(&shader_std140, &value); + context.test_round_trip_std430(&shader_std430, &value); +} + +fn glsl_shader_for_struct(layout: &str) -> String { + BASE_SHADER + .replace("{struct_name}", T::NAME) + .replace("{struct_definition}", &T::glsl_definition()) + .replace("{layout}", layout) +} + +fn glsl_shader_for_primitive(layout: &str) -> String { + BASE_SHADER + .replace("{struct_name}", T::NAME) + .replace("{struct_definition}", "") + .replace("{layout}", layout) +} + +fn compile_glsl(glsl: &str) -> String { + match compile(glsl) { + Ok(shader) => shader, + Err(err) => { + eprintln!("Bad shader: {}", glsl); + panic!("{}", err); + } + } +} + +struct Context { + device: wgpu::Device, + queue: wgpu::Queue, + _phantom: PhantomData<*const T>, +} + +impl Context +where + T: Debug + PartialEq + AsStd140 + AsStd430 + Glsl, +{ + fn new() -> Self { + let (device, queue) = setup(); + Self { + device, + queue, + _phantom: PhantomData, + } + } + + fn test_round_trip_std140(&self, glsl_shader: &str, value: &T) { + let mut data = Vec::new(); + data.extend_from_slice(value.as_std140().as_bytes()); + + let wgsl_shader = compile_glsl(glsl_shader); + let bytes = self.round_trip(&wgsl_shader, &data); + + let std140 = bytemuck::from_bytes::<::Output>(&bytes); + let output = T::from_std140(*std140); + + if value != &output { + println!( + "std140 value did not round-trip through wgpu successfully.\n\ + Input: {:?}\n\ + Output: {:?}\n\n\ + GLSL shader:\n{}\n\n\ + WGSL shader:\n{}", + value, output, glsl_shader, wgsl_shader, + ); + + panic!("wgpu round-trip failure for {}", T::NAME); + } + } + + fn test_round_trip_std430(&self, glsl_shader: &str, value: &T) { + let mut data = Vec::new(); + data.extend_from_slice(value.as_std430().as_bytes()); + + let wgsl_shader = compile_glsl(glsl_shader); + let bytes = self.round_trip(&wgsl_shader, &data); + + let std430 = bytemuck::from_bytes::<::Output>(&bytes); + let output = T::from_std430(*std430); + + if value != &output { + println!( + "std430 value did not round-trip through wgpu successfully.\n\ + Input: {:?}\n\ + Output: {:?}\n\n\ + GLSL shader:\n{}\n\n\ + WGSL shader:\n{}", + value, output, glsl_shader, wgsl_shader, + ); + + panic!("wgpu round-trip failure for {}", T::NAME); + } + } + + fn round_trip(&self, shader: &str, data: &[u8]) -> Vec { + let input_buffer = self + .device + .create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Input Buffer"), + contents: &data, + usage: wgpu::BufferUsages::STORAGE + | wgpu::BufferUsages::COPY_DST + | wgpu::BufferUsages::COPY_SRC, + }); + + let output_gpu_buffer = self.device.create_buffer(&wgpu::BufferDescriptor { + label: Some("Output Buffer"), + size: data.len() as wgpu::BufferAddress, + usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC, + mapped_at_creation: false, + }); + + let output_cpu_buffer = self.device.create_buffer(&wgpu::BufferDescriptor { + label: Some("Output Buffer"), + size: data.len() as wgpu::BufferAddress, + usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST, + mapped_at_creation: false, + }); + + let cs_module = self + .device + .create_shader_module(&wgpu::ShaderModuleDescriptor { + label: None, + source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(shader)), + }); + + let compute_pipeline = + self.device + .create_compute_pipeline(&wgpu::ComputePipelineDescriptor { + label: None, + layout: None, + module: &cs_module, + entry_point: "main", + }); + + let bind_group_layout = compute_pipeline.get_bind_group_layout(0); + let bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor { + label: None, + layout: &bind_group_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: input_buffer.as_entire_binding(), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: output_gpu_buffer.as_entire_binding(), + }, + ], + }); + + let mut encoder = self + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); + + { + let mut cpass = + encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { label: None }); + cpass.set_pipeline(&compute_pipeline); + cpass.set_bind_group(0, &bind_group, &[]); + cpass.dispatch(1, 1, 1); + } + + encoder.copy_buffer_to_buffer( + &output_gpu_buffer, + 0, + &output_cpu_buffer, + 0, + data.len() as wgpu::BufferAddress, + ); + + self.queue.submit([encoder.finish()]); + + let output_slice = output_cpu_buffer.slice(..); + let output_future = output_slice.map_async(wgpu::MapMode::Read); + + self.device.poll(wgpu::Maintain::Wait); + block_on(output_future).unwrap(); + + let output = output_slice.get_mapped_range().to_vec(); + output_cpu_buffer.unmap(); + + output + } +} + +fn setup() -> (wgpu::Device, wgpu::Queue) { + let instance = wgpu::Instance::new(wgpu::Backends::all()); + let adapter = + block_on(instance.request_adapter(&wgpu::RequestAdapterOptions::default())).unwrap(); + + println!("Adapter info: {:#?}", adapter.get_info()); + + block_on(adapter.request_device( + &wgpu::DeviceDescriptor { + label: None, + features: wgpu::Features::empty(), + limits: wgpu::Limits::downlevel_defaults(), + }, + None, + )) + .unwrap() +} + +fn compile(glsl_source: &str) -> anyhow::Result { + let mut parser = naga::front::glsl::Parser::default(); + + let module = parser + .parse( + &naga::front::glsl::Options { + stage: naga::ShaderStage::Compute, + defines: Default::default(), + }, + glsl_source, + ) + .map_err(|err| anyhow::format_err!("{:?}", err))?; + + let info = naga::valid::Validator::new( + naga::valid::ValidationFlags::default(), + naga::valid::Capabilities::all(), + ) + .validate(&module)?; + + let wgsl = naga::back::wgsl::write_string(&module, &info)?; + + Ok(wgsl) +} diff --git a/crevice-tests/src/lib.rs b/crevice-tests/src/lib.rs new file mode 100644 index 0000000..340b162 --- /dev/null +++ b/crevice-tests/src/lib.rs @@ -0,0 +1,325 @@ +#![cfg(test)] + +#[cfg(feature = "wgpu-validation")] +mod gpu; + +#[cfg(feature = "wgpu-validation")] +use gpu::{test_round_trip_primitive, test_round_trip_struct}; + +#[cfg(not(feature = "wgpu-validation"))] +fn test_round_trip_struct(_value: T) {} + +#[cfg(not(feature = "wgpu-validation"))] +fn test_round_trip_primitive(_value: T) {} + +#[macro_use] +mod util; + +use crevice::glsl::GlslStruct; +use crevice::std140::AsStd140; +use crevice::std430::AsStd430; +use mint::{ColumnMatrix2, ColumnMatrix3, ColumnMatrix4, Vector2, Vector3, Vector4}; + +#[test] +fn two_f32() { + #[derive(Debug, PartialEq, AsStd140, AsStd430, GlslStruct)] + struct TwoF32 { + x: f32, + y: f32, + } + + assert_std140!((size = 16, align = 16) TwoF32 { + x: 0, + y: 4, + }); + + assert_std430!((size = 8, align = 4) TwoF32 { + x: 0, + y: 4, + }); + + test_round_trip_struct(TwoF32 { x: 5.0, y: 7.0 }); +} + +#[test] +fn vec2() { + #[derive(Debug, PartialEq, AsStd140, AsStd430, GlslStruct)] + struct UseVec2 { + one: Vector2, + } + + assert_std140!((size = 16, align = 16) UseVec2 { + one: 0, + }); + + test_round_trip_struct(UseVec2 { + one: [1.0, 2.0].into(), + }); +} + +#[test] +fn mat2_bare() { + type Mat2 = ColumnMatrix2; + + assert_std140!((size = 32, align = 16) Mat2 { + x: 0, + y: 16, + }); + + assert_std430!((size = 16, align = 8) Mat2 { + x: 0, + y: 8, + }); + + // Naga doesn't work with std140 mat2 values. + // https://github.com/gfx-rs/naga/issues/1400 + + // test_round_trip_primitive(Mat2 { + // x: [1.0, 2.0].into(), + // y: [3.0, 4.0].into(), + // }); +} + +#[test] +fn mat3_bare() { + type Mat3 = ColumnMatrix3; + + assert_std140!((size = 48, align = 16) Mat3 { + x: 0, + y: 16, + z: 32, + }); + + // Naga produces invalid HLSL for mat3 value. + // https://github.com/gfx-rs/naga/issues/1466 + + // test_round_trip_primitive(Mat3 { + // x: [1.0, 2.0, 3.0].into(), + // y: [4.0, 5.0, 6.0].into(), + // z: [7.0, 8.0, 9.0].into(), + // }); +} + +#[test] +fn mat4_bare() { + type Mat4 = ColumnMatrix4; + + assert_std140!((size = 64, align = 16) Mat4 { + x: 0, + y: 16, + z: 32, + w: 48, + }); + + test_round_trip_primitive(Mat4 { + x: [1.0, 2.0, 3.0, 4.0].into(), + y: [5.0, 6.0, 7.0, 8.0].into(), + z: [9.0, 10.0, 11.0, 12.0].into(), + w: [13.0, 14.0, 15.0, 16.0].into(), + }); +} + +#[test] +fn mat3() { + #[derive(Debug, PartialEq, AsStd140, AsStd430, GlslStruct)] + struct TestData { + one: ColumnMatrix3, + } + + // Naga produces invalid HLSL for mat3 value. + // https://github.com/gfx-rs/naga/issues/1466 + + // test_round_trip_struct(TestData { + // one: [[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]].into(), + // }); +} + +#[test] +fn dvec4() { + #[derive(Debug, PartialEq, AsStd140, AsStd430, GlslStruct)] + struct UsingDVec4 { + doubles: Vector4, + } + + assert_std140!((size = 32, align = 32) UsingDVec4 { + doubles: 0, + }); + + // Naga does not appear to support doubles. + // https://github.com/gfx-rs/naga/issues/1272 + + // test_round_trip_struct(UsingDVec4 { + // doubles: [1.0, 2.0, 3.0, 4.0].into(), + // }); +} + +#[test] +fn four_f64() { + #[derive(Debug, PartialEq, AsStd140, AsStd430, GlslStruct)] + struct FourF64 { + x: f64, + y: f64, + z: f64, + w: f64, + } + + assert_std140!((size = 32, align = 16) FourF64 { + x: 0, + y: 8, + z: 16, + w: 24, + }); + + // Naga does not appear to support doubles. + // https://github.com/gfx-rs/naga/issues/1272 + + // test_round_trip_struct(FourF64 { + // x: 5.0, + // y: 7.0, + // z: 9.0, + // w: 11.0, + // }); +} + +#[test] +fn two_vec3() { + #[derive(Debug, PartialEq, AsStd140, AsStd430, GlslStruct)] + struct TwoVec3 { + one: Vector3, + two: Vector3, + } + + print_std140!(TwoVec3); + print_std430!(TwoVec3); + + assert_std140!((size = 32, align = 16) TwoVec3 { + one: 0, + two: 16, + }); + + assert_std430!((size = 32, align = 16) TwoVec3 { + one: 0, + two: 16, + }); + + test_round_trip_struct(TwoVec3 { + one: [1.0, 2.0, 3.0].into(), + two: [4.0, 5.0, 6.0].into(), + }); +} + +#[test] +fn two_vec4() { + #[derive(Debug, PartialEq, AsStd140, AsStd430, GlslStruct)] + struct TwoVec4 { + one: Vector4, + two: Vector4, + } + + assert_std140!((size = 32, align = 16) TwoVec4 { + one: 0, + two: 16, + }); + + test_round_trip_struct(TwoVec4 { + one: [1.0, 2.0, 3.0, 4.0].into(), + two: [5.0, 6.0, 7.0, 8.0].into(), + }); +} + +#[test] +fn vec3_then_f32() { + #[derive(Debug, PartialEq, AsStd140, AsStd430, GlslStruct)] + struct Vec3ThenF32 { + one: Vector3, + two: f32, + } + + assert_std140!((size = 16, align = 16) Vec3ThenF32 { + one: 0, + two: 12, + }); + + test_round_trip_struct(Vec3ThenF32 { + one: [1.0, 2.0, 3.0].into(), + two: 4.0, + }); +} + +#[test] +fn mat3_padding() { + #[derive(Debug, PartialEq, AsStd140, AsStd430, GlslStruct)] + struct Mat3Padding { + // Three rows of 16 bytes (3x f32 + 4 bytes padding) + one: mint::ColumnMatrix3, + two: f32, + } + + assert_std140!((size = 64, align = 16) Mat3Padding { + one: 0, + two: 48, + }); + + // Naga produces invalid HLSL for mat3 value. + // https://github.com/gfx-rs/naga/issues/1466 + + // test_round_trip_struct(Mat3Padding { + // one: [[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]].into(), + // two: 10.0, + // }); +} + +#[test] +fn padding_after_struct() { + #[derive(AsStd140)] + struct TwoF32 { + x: f32, + } + + #[derive(AsStd140)] + struct PaddingAfterStruct { + base_value: TwoF32, + // There should be 8 bytes of padding inserted here. + small_field: f32, + } + + assert_std140!((size = 32, align = 16) PaddingAfterStruct { + base_value: 0, + small_field: 16, + }); +} + +#[test] +fn proper_offset_calculations_for_differing_member_sizes() { + #[derive(AsStd140)] + struct Foo { + x: f32, + } + + #[derive(AsStd140)] + struct Bar { + first: Foo, + second: Foo, + } + + #[derive(AsStd140)] + struct Outer { + leading: Bar, + trailing: Foo, + } + + // Offset Size Contents + // 0 4 Bar.leading.first.x + // 4 12 [padding] + // 16 4 Bar.leading.second.x + // 20 12 [padding] + // 32 4 Bar.trailing.x + // 36 12 [padding] + // + // Total size is 48. + + assert_std140!((size = 48, align = 16) Outer { + leading: 0, + trailing: 32, + }); +} diff --git a/crevice-tests/src/util.rs b/crevice-tests/src/util.rs new file mode 100644 index 0000000..203afce --- /dev/null +++ b/crevice-tests/src/util.rs @@ -0,0 +1,143 @@ +#[macro_export] +macro_rules! print_std140 { + ($type:ty) => { + println!( + "{}", + <$type as crevice::std140::AsStd140>::Output::debug_metrics() + ); + println!(); + println!(); + println!( + "{}", + <$type as crevice::std140::AsStd140>::Output::debug_definitions() + ); + }; +} + +#[macro_export] +macro_rules! print_std430 { + ($type:ty) => { + println!( + "{}", + <$type as crevice::std430::AsStd430>::Output::debug_metrics() + ); + println!(); + println!(); + println!( + "{}", + <$type as crevice::std430::AsStd430>::Output::debug_definitions() + ); + }; +} + +#[macro_export] +macro_rules! assert_std140 { + ((size = $size:literal, align = $align:literal) $struct:ident { + $( $field:ident: $offset:literal, )* + }) => {{ + type Target = <$struct as crevice::std140::AsStd140>::Output; + + let mut fail = false; + + let actual_size = std::mem::size_of::(); + if actual_size != $size { + fail = true; + println!( + "Invalid size for std140 struct {}\n\ + Expected: {}\n\ + Actual: {}\n", + stringify!($struct), + $size, + actual_size, + ); + } + + let actual_alignment = ::ALIGNMENT; + if actual_alignment != $align { + fail = true; + println!( + "Invalid alignment for std140 struct {}\n\ + Expected: {}\n\ + Actual: {}\n", + stringify!($struct), + $align, + actual_alignment, + ); + } + + $({ + let actual_offset = memoffset::offset_of!(Target, $field); + if actual_offset != $offset { + fail = true; + println!( + "Invalid offset for field {}\n\ + Expected: {}\n\ + Actual: {}\n", + stringify!($field), + $offset, + actual_offset, + ); + } + })* + + if fail { + panic!("Invalid std140 result for {}", stringify!($struct)); + } + }}; +} + +#[macro_export] +macro_rules! assert_std430 { + ((size = $size:literal, align = $align:literal) $struct:ident { + $( $field:ident: $offset:literal, )* + }) => {{ + type Target = <$struct as crevice::std430::AsStd430>::Output; + + let mut fail = false; + + let actual_size = std::mem::size_of::(); + if actual_size != $size { + fail = true; + println!( + "Invalid size for std430 struct {}\n\ + Expected: {}\n\ + Actual: {}\n", + stringify!($struct), + $size, + actual_size, + ); + } + + let actual_alignment = ::ALIGNMENT; + if actual_alignment != $align { + fail = true; + println!( + "Invalid alignment for std430 struct {}\n\ + Expected: {}\n\ + Actual: {}\n", + stringify!($struct), + $align, + actual_alignment, + ); + } + + $({ + let actual_offset = memoffset::offset_of!(Target, $field); + if actual_offset != $offset { + fail = true; + println!( + "Invalid offset for std430 field {}\n\ + Expected: {}\n\ + Actual: {}\n", + stringify!($field), + $offset, + actual_offset, + ); + } + })* + + if fail { + panic!("Invalid std430 result for {}", stringify!($struct)); + } + }}; +} diff --git a/crevice-validation/Cargo.toml b/crevice-validation/Cargo.toml deleted file mode 100644 index 40b58ce..0000000 --- a/crevice-validation/Cargo.toml +++ /dev/null @@ -1,16 +0,0 @@ -[package] -name = "crevice-validation" -version = "0.1.0" -edition = "2018" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -anyhow = "1.0.44" -crevice = { path = ".." } -wgpu = "0.11.0" -mint = "0.5.5" -naga = { version = "0.7.0", features = ["glsl-in", "wgsl-out"] } -futures = { version = "0.3.17", features = ["executor"] } -bytemuck = "1.7.2" -type-layout = { version = "0.2.0", features = ["serde1"] } diff --git a/crevice-validation/src/lib.rs b/crevice-validation/src/lib.rs deleted file mode 100644 index 30d0f1a..0000000 --- a/crevice-validation/src/lib.rs +++ /dev/null @@ -1,250 +0,0 @@ -#![cfg(test)] - -use std::borrow::Cow; - -use crevice::glsl::GlslStruct; -use crevice::std140::{AsStd140, Std140}; -use futures::executor::block_on; -use mint::{Vector2, Vector3, Vector4}; -use wgpu::util::DeviceExt; - -const BASE_SHADER: &str = " -#version 450 - -{struct_definition} - -layout({layout}, set = 0, binding = 0) readonly buffer INPUT { - TestData in_data; -}; - -layout({layout}, set = 0, binding = 1) buffer OUTPUT { - TestData out_data; -}; - -void main() { - out_data = in_data; -}"; - -macro_rules! roundtrip_through_glsl { - ($layout:ident - $ty:ident { - $( - $key:ident : $value:expr, - )+ - } - ) => { - let (device, queue) = setup(); - - let input = $ty { - $($key: $value,)+ - }; - - let struct_definition = <$ty as GlslStruct>::glsl_definition(); - - let shader = BASE_SHADER - .replace("{struct_definition}", &struct_definition) - .replace("{layout}", stringify!($layout)); - - let output = round_trip( - &device, - &queue, - &shader, - &input, - ); - - let expected = input.as_std140(); - - $(assert_eq!(output.$key, expected.$key);)+ - } -} - -#[test] -fn vec2() { - #[derive(AsStd140, GlslStruct)] - struct TestData { - two: Vector2, - } - - roundtrip_through_glsl! { - std140 - TestData { - two: Vector2 { x: 1.0, y: 2.0 }, - } - } -} - -#[test] -fn double_vec4() { - #[derive(AsStd140, GlslStruct)] - struct TestData { - one: Vector4, - two: Vector4, - } - - roundtrip_through_glsl! { - std140 - TestData { - one: Vector4 { x: 1.0, y: 2.0, z: 3.0, w: 4.0 }, - two: Vector4 { x: 5.0, y: 6.0, z: 7.0, w: 8.0 }, - } - } -} - -#[test] -fn double_vec3() { - #[derive(AsStd140, GlslStruct)] - struct TestData { - one: Vector3, - two: Vector3, - } - - roundtrip_through_glsl! { - std140 - TestData { - one: Vector3 { x: 1.0, y: 2.0, z: 3.0 }, - two: Vector3 { x: 4.0, y: 5.0, z: 6.0 }, - } - } -} - -fn setup() -> (wgpu::Device, wgpu::Queue) { - let instance = wgpu::Instance::new(wgpu::Backends::all()); - let adapter = - block_on(instance.request_adapter(&wgpu::RequestAdapterOptions::default())).unwrap(); - - block_on(adapter.request_device( - &wgpu::DeviceDescriptor { - label: None, - features: wgpu::Features::empty(), - limits: wgpu::Limits::downlevel_defaults(), - }, - None, - )) - .unwrap() -} - -fn round_trip( - device: &wgpu::Device, - queue: &wgpu::Queue, - glsl_shader: &str, - value: &T, -) -> ::Std140Type { - let shader = match compile(glsl_shader) { - Ok(shader) => shader, - Err(err) => { - eprintln!("Bad shader: {}", glsl_shader); - panic!("{}", err); - } - }; - - let mut data = Vec::new(); - data.extend_from_slice(value.as_std140().as_bytes()); - - let input_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some("Input Buffer"), - contents: &data, - usage: wgpu::BufferUsages::STORAGE - | wgpu::BufferUsages::COPY_DST - | wgpu::BufferUsages::COPY_SRC, - }); - - let output_gpu_buffer = device.create_buffer(&wgpu::BufferDescriptor { - label: Some("Output Buffer"), - size: data.len() as wgpu::BufferAddress, - usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC, - mapped_at_creation: false, - }); - - let output_cpu_buffer = device.create_buffer(&wgpu::BufferDescriptor { - label: Some("Output Buffer"), - size: data.len() as wgpu::BufferAddress, - usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST, - mapped_at_creation: false, - }); - - let cs_module = device.create_shader_module(&wgpu::ShaderModuleDescriptor { - label: None, - source: wgpu::ShaderSource::Wgsl(Cow::Owned(shader)), - }); - - let compute_pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor { - label: None, - layout: None, - module: &cs_module, - entry_point: "main", - }); - - let bind_group_layout = compute_pipeline.get_bind_group_layout(0); - let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { - label: None, - layout: &bind_group_layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: input_buffer.as_entire_binding(), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: output_gpu_buffer.as_entire_binding(), - }, - ], - }); - - let mut encoder = - device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); - - { - let mut cpass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { label: None }); - cpass.set_pipeline(&compute_pipeline); - cpass.set_bind_group(0, &bind_group, &[]); - cpass.dispatch(1, 1, 1); - } - - encoder.copy_buffer_to_buffer( - &output_gpu_buffer, - 0, - &output_cpu_buffer, - 0, - data.len() as wgpu::BufferAddress, - ); - - queue.submit([encoder.finish()]); - - let output_slice = output_cpu_buffer.slice(..); - let output_future = output_slice.map_async(wgpu::MapMode::Read); - - device.poll(wgpu::Maintain::Wait); - block_on(output_future).unwrap(); - - let output = output_slice.get_mapped_range(); - let result = bytemuck::from_bytes::<::Std140Type>(&output).clone(); - - drop(output); - output_cpu_buffer.unmap(); - - result -} - -fn compile(glsl_source: &str) -> anyhow::Result { - let mut parser = naga::front::glsl::Parser::default(); - - let module = parser - .parse( - &naga::front::glsl::Options { - stage: naga::ShaderStage::Compute, - defines: Default::default(), - }, - glsl_source, - ) - .map_err(|err| anyhow::format_err!("{:?}", err))?; - - let info = naga::valid::Validator::new( - naga::valid::ValidationFlags::default(), - naga::valid::Capabilities::all(), - ) - .validate(&module)?; - - let wgsl = naga::back::wgsl::write_string(&module, &info)?; - - Ok(wgsl) -} diff --git a/src/internal.rs b/src/internal.rs index cf5a22f..cd22972 100644 --- a/src/internal.rs +++ b/src/internal.rs @@ -3,9 +3,9 @@ pub use bytemuck; -/// Align the given struct offset up to the given alignment. +/// Gives the number of bytes needed to make `offset` be aligned to `alignment`. pub const fn align_offset(offset: usize, alignment: usize) -> usize { - if offset % alignment == 0 { + if alignment == 0 || offset % alignment == 0 { 0 } else { alignment - offset % alignment @@ -21,3 +21,20 @@ pub const fn max(a: usize, b: usize) -> usize { b } } + +/// Max of an array of `usize`. This function's implementation is funky because +/// we have no for loops! +pub const fn max_arr(input: [usize; N]) -> usize { + let mut max = 0; + let mut i = 0; + + while i < N { + if input[i] > max { + max = input[i]; + } + + i += 1; + } + + max +} diff --git a/src/mint.rs b/src/mint.rs index f255591..4b1cca7 100644 --- a/src/mint.rs +++ b/src/mint.rs @@ -8,9 +8,9 @@ macro_rules! mint_vectors { ( $( $mint_ty:ty, $glsl_name:ident, $std_name:ident, ( $($field:ident),* ), )* ) => { $( impl AsStd140 for $mint_ty { - type Std140Type = std140::$std_name; + type Output = std140::$std_name; - fn as_std140(&self) -> Self::Std140Type { + fn as_std140(&self) -> Self::Output { std140::$std_name { $( $field: self.$field, @@ -18,7 +18,7 @@ macro_rules! mint_vectors { } } - fn from_std140(value: Self::Std140Type) -> Self { + fn from_std140(value: Self::Output) -> Self { Self { $( $field: value.$field, @@ -28,9 +28,9 @@ macro_rules! mint_vectors { } impl AsStd430 for $mint_ty { - type Std430Type = std430::$std_name; + type Output = std430::$std_name; - fn as_std430(&self) -> Self::Std430Type { + fn as_std430(&self) -> Self::Output { std430::$std_name { $( $field: self.$field, @@ -38,7 +38,7 @@ macro_rules! mint_vectors { } } - fn from_std430(value: Self::Std430Type) -> Self { + fn from_std430(value: Self::Output) -> Self { Self { $( $field: value.$field, @@ -80,9 +80,9 @@ macro_rules! mint_matrices { ( $( $mint_ty:ty, $glsl_name:ident, $std_name:ident, ( $($field:ident),* ), )* ) => { $( impl AsStd140 for $mint_ty { - type Std140Type = std140::$std_name; + type Output = std140::$std_name; - fn as_std140(&self) -> Self::Std140Type { + fn as_std140(&self) -> Self::Output { std140::$std_name { $( $field: self.$field.as_std140(), @@ -91,7 +91,7 @@ macro_rules! mint_matrices { } } - fn from_std140(value: Self::Std140Type) -> Self { + fn from_std140(value: Self::Output) -> Self { Self { $( $field: <_ as AsStd140>::from_std140(value.$field), @@ -101,9 +101,9 @@ macro_rules! mint_matrices { } impl AsStd430 for $mint_ty { - type Std430Type = std430::$std_name; + type Output = std430::$std_name; - fn as_std430(&self) -> Self::Std430Type { + fn as_std430(&self) -> Self::Output { std430::$std_name { $( $field: self.$field.as_std430(), @@ -112,7 +112,7 @@ macro_rules! mint_matrices { } } - fn from_std430(value: Self::Std430Type) -> Self { + fn from_std430(value: Self::Output) -> Self { Self { $( $field: <_ as AsStd430>::from_std430(value.$field), diff --git a/src/std140/dynamic_uniform.rs b/src/std140/dynamic_uniform.rs index 9a66b63..8c071c2 100644 --- a/src/std140/dynamic_uniform.rs +++ b/src/std140/dynamic_uniform.rs @@ -10,13 +10,13 @@ use crate::std140::{AsStd140, Std140}; pub struct DynamicUniform(pub T); impl AsStd140 for DynamicUniform { - type Std140Type = DynamicUniformStd140<::Std140Type>; + type Output = DynamicUniformStd140<::Output>; - fn as_std140(&self) -> Self::Std140Type { + fn as_std140(&self) -> Self::Output { DynamicUniformStd140(self.0.as_std140()) } - fn from_std140(value: Self::Std140Type) -> Self { + fn from_std140(value: Self::Output) -> Self { DynamicUniform(::from_std140(value.0)) } } diff --git a/src/std140/sizer.rs b/src/std140/sizer.rs index 4d76ad6..ee5c134 100644 --- a/src/std140/sizer.rs +++ b/src/std140/sizer.rs @@ -61,8 +61,8 @@ impl Sizer { where T: AsStd140, { - let size = size_of::<::Std140Type>(); - let alignment = ::Std140Type::ALIGNMENT; + let size = size_of::<::Output>(); + let alignment = ::Output::ALIGNMENT; let padding = align_offset(self.offset, alignment); self.offset += padding; diff --git a/src/std140/traits.rs b/src/std140/traits.rs index ebfa0a0..d5a1382 100644 --- a/src/std140/traits.rs +++ b/src/std140/traits.rs @@ -78,26 +78,26 @@ write_to_gpu_buffer(camera_std140.as_bytes()); */ pub trait AsStd140 { /// The `std140` version of this value. - type Std140Type: Std140; + type Output: Std140; /// Convert this value into the `std140` version of itself. - fn as_std140(&self) -> Self::Std140Type; + fn as_std140(&self) -> Self::Output; /// Returns the size of the `std140` version of this type. Useful for /// pre-sizing buffers. fn std140_size_static() -> usize { - size_of::() + size_of::() } /// Converts from `std140` version of self to self. - fn from_std140(val: Self::Std140Type) -> Self; + fn from_std140(val: Self::Output) -> Self; } impl AsStd140 for T where T: Std140, { - type Std140Type = Self; + type Output = Self; fn as_std140(&self) -> Self { *self @@ -144,7 +144,7 @@ where } fn std140_size(&self) -> usize { - size_of::<::Std140Type>() + size_of::<::Output>() } } diff --git a/src/std430/primitives.rs b/src/std430/primitives.rs index 6c05998..a2e6ba2 100644 --- a/src/std430/primitives.rs +++ b/src/std430/primitives.rs @@ -52,17 +52,17 @@ vectors! { #[doc = "Corresponds to a GLSL `vec3` in std430 layout."] align(16) vec3 Vec3(x, y, z) #[doc = "Corresponds to a GLSL `vec4` in std430 layout."] align(16) vec4 Vec4(x, y, z, w) - #[doc = "Corresponds to a GLSL `ivec2` in std140 layout."] align(8) ivec2 IVec2(x, y) - #[doc = "Corresponds to a GLSL `ivec3` in std140 layout."] align(16) ivec3 IVec3(x, y, z) - #[doc = "Corresponds to a GLSL `ivec4` in std140 layout."] align(16) ivec4 IVec4(x, y, z, w) + #[doc = "Corresponds to a GLSL `ivec2` in std430 layout."] align(8) ivec2 IVec2(x, y) + #[doc = "Corresponds to a GLSL `ivec3` in std430 layout."] align(16) ivec3 IVec3(x, y, z) + #[doc = "Corresponds to a GLSL `ivec4` in std430 layout."] align(16) ivec4 IVec4(x, y, z, w) - #[doc = "Corresponds to a GLSL `uvec2` in std140 layout."] align(8) uvec2 UVec2(x, y) - #[doc = "Corresponds to a GLSL `uvec3` in std140 layout."] align(16) uvec3 UVec3(x, y, z) - #[doc = "Corresponds to a GLSL `uvec4` in std140 layout."] align(16) uvec4 UVec4(x, y, z, w) + #[doc = "Corresponds to a GLSL `uvec2` in std430 layout."] align(8) uvec2 UVec2(x, y) + #[doc = "Corresponds to a GLSL `uvec3` in std430 layout."] align(16) uvec3 UVec3(x, y, z) + #[doc = "Corresponds to a GLSL `uvec4` in std430 layout."] align(16) uvec4 UVec4(x, y, z, w) - #[doc = "Corresponds to a GLSL `bvec2` in std140 layout."] align(8) bvec2 BVec2(x, y) - #[doc = "Corresponds to a GLSL `bvec3` in std140 layout."] align(16) bvec3 BVec3(x, y, z) - #[doc = "Corresponds to a GLSL `bvec4` in std140 layout."] align(16) bvec4 BVec4(x, y, z, w) + #[doc = "Corresponds to a GLSL `bvec2` in std430 layout."] align(8) bvec2 BVec2(x, y) + #[doc = "Corresponds to a GLSL `bvec3` in std430 layout."] align(16) bvec3 BVec3(x, y, z) + #[doc = "Corresponds to a GLSL `bvec4` in std430 layout."] align(16) bvec4 BVec4(x, y, z, w) #[doc = "Corresponds to a GLSL `dvec2` in std430 layout."] align(16) dvec2 DVec2(x, y) #[doc = "Corresponds to a GLSL `dvec3` in std430 layout."] align(32) dvec3 DVec3(x, y, z) @@ -105,7 +105,7 @@ macro_rules! matrices { matrices! { #[doc = "Corresponds to a GLSL `mat2` in std430 layout."] - align(16) + align(8) mat2 Mat2 { x: Vec2, y: Vec2, @@ -115,8 +115,11 @@ matrices! { align(16) mat3 Mat3 { x: Vec3, + _pad_x: f32, y: Vec3, + _pad_y: f32, z: Vec3, + _pad_z: f32, } #[doc = "Corresponds to a GLSL `mat4` in std430 layout."] @@ -139,8 +142,11 @@ matrices! { align(32) dmat3 DMat3 { x: DVec3, + _pad_x: f64, y: DVec3, + _pad_y: f64, z: DVec3, + _pad_z: f64, } #[doc = "Corresponds to a GLSL `dmat3` in std430 layout."] diff --git a/src/std430/sizer.rs b/src/std430/sizer.rs index 139c69c..20b7b29 100644 --- a/src/std430/sizer.rs +++ b/src/std430/sizer.rs @@ -61,8 +61,8 @@ impl Sizer { where T: AsStd430, { - let size = size_of::<::Std430Type>(); - let alignment = ::Std430Type::ALIGNMENT; + let size = size_of::<::Output>(); + let alignment = ::Output::ALIGNMENT; let padding = align_offset(self.offset, alignment); self.offset += padding; diff --git a/src/std430/traits.rs b/src/std430/traits.rs index d466481..c5cbfa7 100644 --- a/src/std430/traits.rs +++ b/src/std430/traits.rs @@ -78,26 +78,26 @@ write_to_gpu_buffer(camera_std430.as_bytes()); */ pub trait AsStd430 { /// The `std430` version of this value. - type Std430Type: Std430; + type Output: Std430; /// Convert this value into the `std430` version of itself. - fn as_std430(&self) -> Self::Std430Type; + fn as_std430(&self) -> Self::Output; /// Returns the size of the `std430` version of this type. Useful for /// pre-sizing buffers. fn std430_size_static() -> usize { - size_of::() + size_of::() } /// Converts from `std430` version of self to self. - fn from_std430(value: Self::Std430Type) -> Self; + fn from_std430(value: Self::Output) -> Self; } impl AsStd430 for T where T: Std430, { - type Std430Type = Self; + type Output = Self; fn as_std430(&self) -> Self { *self @@ -144,7 +144,7 @@ where } fn std430_size(&self) -> usize { - size_of::<::Std430Type>() + size_of::<::Output>() } } diff --git a/tests/snapshots/std140__matrix_uniform_std140-2.snap b/tests/snapshots/std140__matrix_uniform_std140-2.snap deleted file mode 100644 index a45d2a7..0000000 --- a/tests/snapshots/std140__matrix_uniform_std140-2.snap +++ /dev/null @@ -1,9 +0,0 @@ ---- -source: tests/std140.rs -expression: "::Std140Type::glsl_definition()" - ---- -struct MatrixUniform { - mat3 e; - float f; -}; diff --git a/tests/snapshots/std140__matrix_uniform_std140.snap b/tests/snapshots/std140__matrix_uniform_std140.snap deleted file mode 100644 index 5de0841..0000000 --- a/tests/snapshots/std140__matrix_uniform_std140.snap +++ /dev/null @@ -1,26 +0,0 @@ ---- -source: tests/std140.rs -expression: "<::Std140Type as TypeLayout>::type_layout()" - ---- -name: Std140MatrixUniform -size: 52 -alignment: 4 -fields: - - Field: - name: _MatrixUniform__e__Std140__align - ty: "[u8 ; _MatrixUniform__e__Std140__align()]" - size: 0 - - Field: - name: e - ty: "< mint :: ColumnMatrix3 < f32 > as :: crevice :: std140 :: AsStd140 > ::\nStd140Type" - size: 48 - - Field: - name: _MatrixUniform__f__Std140__align - ty: "[u8 ; _MatrixUniform__f__Std140__align()]" - size: 0 - - Field: - name: f - ty: "< f32 as :: crevice :: std140 :: AsStd140 > :: Std140Type" - size: 4 - diff --git a/tests/snapshots/std140__matrix_uniform_std430.snap b/tests/snapshots/std140__matrix_uniform_std430.snap deleted file mode 100644 index 05273ca..0000000 --- a/tests/snapshots/std140__matrix_uniform_std430.snap +++ /dev/null @@ -1,26 +0,0 @@ ---- -source: tests/std140.rs -expression: "<::Std430Type as TypeLayout>::type_layout()" - ---- -name: Std430MatrixUniform -size: 52 -alignment: 4 -fields: - - Field: - name: _MatrixUniform__e__Std430__align - ty: "[u8 ; _MatrixUniform__e__Std430__align()]" - size: 0 - - Field: - name: e - ty: "< mint :: ColumnMatrix3 < f32 > as :: crevice :: std430 :: AsStd430 > ::\nStd430Type" - size: 36 - - Field: - name: _MatrixUniform__f__Std430__align - ty: "[u8 ; _MatrixUniform__f__Std430__align()]" - size: 12 - - Field: - name: f - ty: "< f32 as :: crevice :: std430 :: AsStd430 > :: Std430Type" - size: 4 - diff --git a/tests/snapshots/std140__more_than_16_alignment.snap b/tests/snapshots/std140__more_than_16_alignment.snap deleted file mode 100644 index e0892fd..0000000 --- a/tests/snapshots/std140__more_than_16_alignment.snap +++ /dev/null @@ -1,18 +0,0 @@ ---- -source: tests/std140.rs -expression: "<::Std140Type as TypeLayout>::type_layout()" - ---- -name: Std140MoreThan16Alignment -size: 32 -alignment: 8 -fields: - - Field: - name: _MoreThan16Alignment__doubles__Std140__align - ty: "[u8 ; _MoreThan16Alignment__doubles__Std140__align()]" - size: 0 - - Field: - name: doubles - ty: "< DVec4 as :: crevice :: std140 :: AsStd140 > :: Std140Type" - size: 32 - diff --git a/tests/snapshots/std140__padding_at_end.snap b/tests/snapshots/std140__padding_at_end.snap deleted file mode 100644 index fa296a0..0000000 --- a/tests/snapshots/std140__padding_at_end.snap +++ /dev/null @@ -1,26 +0,0 @@ ---- -source: tests/std140.rs -expression: "<::Std140Type as TypeLayout>::type_layout()" - ---- -name: Std140PaddingAtEnd -size: 20 -alignment: 4 -fields: - - Field: - name: _PaddingAtEnd__base_value__Std140__align - ty: "[u8 ; _PaddingAtEnd__base_value__Std140__align()]" - size: 0 - - Field: - name: base_value - ty: "< PrimitiveF32 as :: crevice :: std140 :: AsStd140 > :: Std140Type" - size: 8 - - Field: - name: _PaddingAtEnd__small_field__Std140__align - ty: "[u8 ; _PaddingAtEnd__small_field__Std140__align()]" - size: 8 - - Field: - name: small_field - ty: "< f32 as :: crevice :: std140 :: AsStd140 > :: Std140Type" - size: 4 - diff --git a/tests/snapshots/std140__point_light.snap b/tests/snapshots/std140__point_light.snap deleted file mode 100644 index 20454a9..0000000 --- a/tests/snapshots/std140__point_light.snap +++ /dev/null @@ -1,42 +0,0 @@ ---- -source: tests/std140.rs -expression: "<::Std140Type as TypeLayout>::type_layout()" - ---- -name: Std140PointLight -size: 48 -alignment: 4 -fields: - - Field: - name: _PointLight__position__Std140__align - ty: "[u8 ; _PointLight__position__Std140__align()]" - size: 0 - - Field: - name: position - ty: "< Vec3 as :: crevice :: std140 :: AsStd140 > :: Std140Type" - size: 12 - - Field: - name: _PointLight__diffuse__Std140__align - ty: "[u8 ; _PointLight__diffuse__Std140__align()]" - size: 4 - - Field: - name: diffuse - ty: "< Vec3 as :: crevice :: std140 :: AsStd140 > :: Std140Type" - size: 12 - - Field: - name: _PointLight__specular__Std140__align - ty: "[u8 ; _PointLight__specular__Std140__align()]" - size: 4 - - Field: - name: specular - ty: "< Vec3 as :: crevice :: std140 :: AsStd140 > :: Std140Type" - size: 12 - - Field: - name: _PointLight__brightness__Std140__align - ty: "[u8 ; _PointLight__brightness__Std140__align()]" - size: 0 - - Field: - name: brightness - ty: "< f32 as :: crevice :: std140 :: AsStd140 > :: Std140Type" - size: 4 - diff --git a/tests/snapshots/std140__primitive_f32.snap b/tests/snapshots/std140__primitive_f32.snap deleted file mode 100644 index cb44cc8..0000000 --- a/tests/snapshots/std140__primitive_f32.snap +++ /dev/null @@ -1,26 +0,0 @@ ---- -source: tests/std140.rs -expression: "<::Std140Type as TypeLayout>::type_layout()" - ---- -name: Std140PrimitiveF32 -size: 8 -alignment: 4 -fields: - - Field: - name: _PrimitiveF32__x__Std140__align - ty: "[u8 ; _PrimitiveF32__x__Std140__align()]" - size: 0 - - Field: - name: x - ty: "< f32 as :: crevice :: std140 :: AsStd140 > :: Std140Type" - size: 4 - - Field: - name: _PrimitiveF32__y__Std140__align - ty: "[u8 ; _PrimitiveF32__y__Std140__align()]" - size: 0 - - Field: - name: y - ty: "< f32 as :: crevice :: std140 :: AsStd140 > :: Std140Type" - size: 4 - diff --git a/tests/snapshots/std140__proper_offset_calculations_for_differing_member_sizes.snap b/tests/snapshots/std140__proper_offset_calculations_for_differing_member_sizes.snap deleted file mode 100644 index a44d3a0..0000000 --- a/tests/snapshots/std140__proper_offset_calculations_for_differing_member_sizes.snap +++ /dev/null @@ -1,26 +0,0 @@ ---- -source: tests/std140.rs -expression: "<::Std140Type as\n TypeLayout>::type_layout()" - ---- -name: Std140ProperlyChecksForUnderlyingTypeSize -size: 36 -alignment: 4 -fields: - - Field: - name: _ProperlyChecksForUnderlyingTypeSize__leading__Std140__align - ty: "[u8 ; _ProperlyChecksForUnderlyingTypeSize__leading__Std140__align()]" - size: 0 - - Field: - name: leading - ty: "< BaseSizeAndStdSizeAreDifferent as :: crevice :: std140 :: AsStd140 > ::\nStd140Type" - size: 20 - - Field: - name: _ProperlyChecksForUnderlyingTypeSize__trailing__Std140__align - ty: "[u8 ; _ProperlyChecksForUnderlyingTypeSize__trailing__Std140__align()]" - size: 12 - - Field: - name: trailing - ty: "< PaddedByStdButNotRust as :: crevice :: std140 :: AsStd140 > :: Std140Type" - size: 4 - diff --git a/tests/snapshots/std140__using_vec3_padding.snap b/tests/snapshots/std140__using_vec3_padding.snap deleted file mode 100644 index 49effc3..0000000 --- a/tests/snapshots/std140__using_vec3_padding.snap +++ /dev/null @@ -1,26 +0,0 @@ ---- -source: tests/std140.rs -expression: "<::Std140Type as TypeLayout>::type_layout()" - ---- -name: Std140UsingVec3Padding -size: 16 -alignment: 4 -fields: - - Field: - name: _UsingVec3Padding__pos__Std140__align - ty: "[u8 ; _UsingVec3Padding__pos__Std140__align()]" - size: 0 - - Field: - name: pos - ty: "< Vec3 as :: crevice :: std140 :: AsStd140 > :: Std140Type" - size: 12 - - Field: - name: _UsingVec3Padding__brightness__Std140__align - ty: "[u8 ; _UsingVec3Padding__brightness__Std140__align()]" - size: 0 - - Field: - name: brightness - ty: "< f32 as :: crevice :: std140 :: AsStd140 > :: Std140Type" - size: 4 - diff --git a/tests/snapshots/std140__vec3.snap b/tests/snapshots/std140__vec3.snap deleted file mode 100644 index 764e633..0000000 --- a/tests/snapshots/std140__vec3.snap +++ /dev/null @@ -1,26 +0,0 @@ ---- -source: tests/std140.rs -expression: "<::Std140Type as TypeLayout>::type_layout()" - ---- -name: Std140TestVec3 -size: 28 -alignment: 4 -fields: - - Field: - name: _TestVec3__pos__Std140__align - ty: "[u8 ; _TestVec3__pos__Std140__align()]" - size: 0 - - Field: - name: pos - ty: "< Vec3 as :: crevice :: std140 :: AsStd140 > :: Std140Type" - size: 12 - - Field: - name: _TestVec3__velocity__Std140__align - ty: "[u8 ; _TestVec3__velocity__Std140__align()]" - size: 4 - - Field: - name: velocity - ty: "< Vec3 as :: crevice :: std140 :: AsStd140 > :: Std140Type" - size: 12 - diff --git a/tests/snapshots/std140__generate_struct_glsl.snap b/tests/snapshots/test__generate_struct_glsl.snap similarity index 79% rename from tests/snapshots/std140__generate_struct_glsl.snap rename to tests/snapshots/test__generate_struct_glsl.snap index c124a49..42fc1f4 100644 --- a/tests/snapshots/std140__generate_struct_glsl.snap +++ b/tests/snapshots/test__generate_struct_glsl.snap @@ -1,5 +1,5 @@ --- -source: tests/std140.rs +source: tests/test.rs expression: "TestGlsl::glsl_definition()" --- diff --git a/tests/std140.rs b/tests/std140.rs deleted file mode 100644 index 36a3ceb..0000000 --- a/tests/std140.rs +++ /dev/null @@ -1,229 +0,0 @@ -use insta::assert_yaml_snapshot; -use type_layout::TypeLayout; - -use crevice::glsl::GlslStruct; -use crevice::std140::{AsStd140, DVec4, Std140, Vec3}; -use crevice::std430::AsStd430; - -#[derive(AsStd140)] -struct PrimitiveF32 { - x: f32, - y: f32, -} - -#[test] -fn primitive_f32() { - assert_yaml_snapshot!(<::Std140Type as TypeLayout>::type_layout()); - - assert_eq!(::Std140Type::ALIGNMENT, 16); - - let value = PrimitiveF32 { x: 1.0, y: 2.0 }; - let _value_std140 = value.as_std140(); -} - -#[derive(AsStd140)] -struct TestVec3 { - pos: Vec3, - velocity: Vec3, -} - -#[test] -fn test_vec3() { - assert_yaml_snapshot!(<::Std140Type as TypeLayout>::type_layout()); - - assert_eq!(::Std140Type::ALIGNMENT, 16); - - let value = TestVec3 { - pos: Vec3 { - x: 1.0, - y: 2.0, - z: 3.0, - }, - velocity: Vec3 { - x: 4.0, - y: 5.0, - z: 6.0, - }, - }; - let _value_std140 = value.as_std140(); -} - -#[derive(AsStd140)] -struct UsingVec3Padding { - pos: Vec3, - brightness: f32, -} - -#[test] -fn using_vec3_padding() { - assert_yaml_snapshot!( - <::Std140Type as TypeLayout>::type_layout() - ); - - assert_eq!(::Std140Type::ALIGNMENT, 16); - - let value = UsingVec3Padding { - pos: Vec3 { - x: 1.0, - y: 2.0, - z: 3.0, - }, - brightness: 4.0, - }; - let _value_std140 = value.as_std140(); -} - -#[derive(AsStd140)] -struct PointLight { - position: Vec3, - diffuse: Vec3, - specular: Vec3, - brightness: f32, -} - -#[test] -fn point_light() { - assert_yaml_snapshot!(<::Std140Type as TypeLayout>::type_layout()); - - assert_eq!(::Std140Type::ALIGNMENT, 16); - - let value = PointLight { - position: Vec3 { - x: 1.0, - y: 2.0, - z: 3.0, - }, - diffuse: Vec3 { - x: 1.0, - y: 2.0, - z: 3.0, - }, - specular: Vec3 { - x: 1.0, - y: 2.0, - z: 3.0, - }, - brightness: 4.0, - }; - let _value_std140 = value.as_std140(); -} - -#[derive(AsStd140)] -struct MoreThan16Alignment { - doubles: DVec4, -} - -#[test] -fn more_than_16_alignment() { - assert_yaml_snapshot!( - <::Std140Type as TypeLayout>::type_layout() - ); - - assert_eq!(::Std140Type::ALIGNMENT, 32); -} - -#[derive(AsStd140)] -struct PaddingAtEnd { - base_value: PrimitiveF32, - small_field: f32, -} - -#[test] -fn padding_at_end() { - assert_yaml_snapshot!(<::Std140Type as TypeLayout>::type_layout()); -} - -#[derive(AsStd140, AsStd430)] -struct MatrixUniform { - e: mint::ColumnMatrix3, - f: f32, -} - -#[test] -fn matrix_uniform_std140() { - assert_yaml_snapshot!(<::Std140Type as TypeLayout>::type_layout()); -} - -#[test] -fn matrix_uniform_std430() { - assert_yaml_snapshot!(<::Std430Type as TypeLayout>::type_layout()) -} - -/// Rust size: 4, align: 4 -/// Std140 size: 4, align: 16 -#[derive(AsStd140)] -struct PaddedByStdButNotRust { - x: f32, -} - -/// Rust size: 8, align: 4 -/// Std140 size: 20, align: 16 -#[derive(AsStd140)] -struct BaseSizeAndStdSizeAreDifferent { - first: PaddedByStdButNotRust, - second: PaddedByStdButNotRust, -} - -/// If checking for base struct size, produces layout: -/// (padding 0) (field 20) (padding 8) (field 4) -/// which does not properly align the second member. -#[derive(AsStd140)] -struct ProperlyChecksForUnderlyingTypeSize { - leading: BaseSizeAndStdSizeAreDifferent, - trailing: PaddedByStdButNotRust, -} - -#[test] -fn proper_offset_calculations_for_differing_member_sizes() { - assert_yaml_snapshot!( - <::Std140Type as TypeLayout>::type_layout( - ) - ) -} - -#[derive(AsStd140, Debug, PartialEq)] -struct ThereAndBackAgain { - view: mint::ColumnMatrix3, - origin: mint::Vector3, -} - -#[test] -fn there_and_back_again() { - let x = ThereAndBackAgain { - view: mint::ColumnMatrix3 { - x: mint::Vector3 { - x: 1.0, - y: 0.0, - z: 0.0, - }, - y: mint::Vector3 { - x: 0.0, - y: 1.0, - z: 0.0, - }, - z: mint::Vector3 { - x: 0.0, - y: 0.0, - z: 1.0, - }, - }, - origin: mint::Vector3 { - x: 0.0, - y: 1.0, - z: 2.0, - }, - }; - let x_as = x.as_std140(); - assert_eq!(::from_std140(x_as), x); -} - -#[test] -fn generate_struct_glsl() { - #[derive(GlslStruct)] - struct TestGlsl { - foo: mint::Vector3, - bar: mint::ColumnMatrix2, - } - - insta::assert_display_snapshot!(TestGlsl::glsl_definition()); -} diff --git a/tests/test.rs b/tests/test.rs new file mode 100644 index 0000000..669557d --- /dev/null +++ b/tests/test.rs @@ -0,0 +1,50 @@ +use crevice::glsl::GlslStruct; +use crevice::std140::AsStd140; + +#[test] +fn there_and_back_again() { + #[derive(AsStd140, Debug, PartialEq)] + struct ThereAndBackAgain { + view: mint::ColumnMatrix3, + origin: mint::Vector3, + } + + let x = ThereAndBackAgain { + view: mint::ColumnMatrix3 { + x: mint::Vector3 { + x: 1.0, + y: 0.0, + z: 0.0, + }, + y: mint::Vector3 { + x: 0.0, + y: 1.0, + z: 0.0, + }, + z: mint::Vector3 { + x: 0.0, + y: 0.0, + z: 1.0, + }, + }, + origin: mint::Vector3 { + x: 0.0, + y: 1.0, + z: 2.0, + }, + }; + let x_as = x.as_std140(); + assert_eq!(::from_std140(x_as), x); +} + +#[test] +fn generate_struct_glsl() { + #[allow(dead_code)] + #[derive(GlslStruct)] + struct TestGlsl { + foo: mint::Vector3, + bar: mint::ColumnMatrix2, + } + + insta::assert_display_snapshot!(TestGlsl::glsl_definition()); +}