From 290b7dd9ab9fffadc17715b2fdb2ec6333020b8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois?= Date: Fri, 12 Nov 2021 01:39:25 +0000 Subject: [PATCH] Update vendored Crevice to 0.8.0 + PR for arrays (#3059) # Objective - Update vendor crevice to have the latest update from crevice 0.8.0 - Using https://github.com/ElectronicRU/crevice/tree/arrays which has the changes to make arrays work ## Solution - Also updated glam and hexasphere to only have one version of glam - From the original PR, using crevice to write GLSL code containing arrays would probably not work but it's not something used by Bevy --- Cargo.toml | 2 +- crates/bevy_math/Cargo.toml | 2 +- crates/bevy_reflect/Cargo.toml | 2 +- crates/bevy_render/Cargo.toml | 2 +- crates/crevice/Cargo.toml | 20 +- crates/crevice/README.md | 76 +++- crates/crevice/README.tpl | 10 + crates/crevice/crevice-derive/Cargo.toml | 8 +- crates/crevice/crevice-derive/src/glsl.rs | 49 +++ crates/crevice/crevice-derive/src/layout.rs | 288 ++++++++++++++ crates/crevice/crevice-derive/src/lib.rs | 284 +------------- crates/crevice/crevice-tests/Cargo.toml | 20 + crates/crevice/crevice-tests/src/gpu.rs | 268 +++++++++++++ crates/crevice/crevice-tests/src/lib.rs | 366 ++++++++++++++++++ crates/crevice/crevice-tests/src/util.rs | 143 +++++++ crates/crevice/src/glam.rs | 103 ----- crates/crevice/src/glsl.rs | 93 +++++ crates/crevice/src/imp.rs | 10 + crates/crevice/src/imp/imp_cgmath.rs | 30 ++ crates/crevice/src/imp/imp_glam.rs | 24 ++ crates/crevice/src/imp/imp_mint.rs | 30 ++ crates/crevice/src/imp/imp_nalgebra.rs | 24 ++ crates/crevice/src/internal.rs | 21 +- crates/crevice/src/lib.rs | 74 ++-- crates/crevice/src/mint.rs | 130 ------- crates/crevice/src/std140/dynamic_uniform.rs | 6 +- crates/crevice/src/std140/primitives.rs | 61 +-- crates/crevice/src/std140/sizer.rs | 4 +- crates/crevice/src/std140/traits.rs | 55 +-- crates/crevice/src/std430/primitives.rs | 67 ++-- crates/crevice/src/std430/sizer.rs | 4 +- crates/crevice/src/std430/traits.rs | 53 +-- crates/crevice/src/util.rs | 97 +++++ .../test__generate_struct_array_glsl.snap | 8 + .../snapshots/test__generate_struct_glsl.snap | 9 + crates/crevice/tests/test.rs | 61 +++ pipelined/bevy_pbr2/Cargo.toml | 2 +- pipelined/bevy_render2/Cargo.toml | 4 +- .../src/render_resource/uniform_vec.rs | 4 +- 39 files changed, 1846 insertions(+), 668 deletions(-) create mode 100644 crates/crevice/crevice-derive/src/glsl.rs create mode 100644 crates/crevice/crevice-derive/src/layout.rs create mode 100644 crates/crevice/crevice-tests/Cargo.toml create mode 100644 crates/crevice/crevice-tests/src/gpu.rs create mode 100644 crates/crevice/crevice-tests/src/lib.rs create mode 100644 crates/crevice/crevice-tests/src/util.rs delete mode 100644 crates/crevice/src/glam.rs create mode 100644 crates/crevice/src/glsl.rs create mode 100644 crates/crevice/src/imp.rs create mode 100644 crates/crevice/src/imp/imp_cgmath.rs create mode 100644 crates/crevice/src/imp/imp_glam.rs create mode 100644 crates/crevice/src/imp/imp_mint.rs create mode 100644 crates/crevice/src/imp/imp_nalgebra.rs delete mode 100644 crates/crevice/src/mint.rs create mode 100644 crates/crevice/src/util.rs create mode 100644 crates/crevice/tests/snapshots/test__generate_struct_array_glsl.snap create mode 100644 crates/crevice/tests/snapshots/test__generate_struct_glsl.snap create mode 100644 crates/crevice/tests/test.rs diff --git a/Cargo.toml b/Cargo.toml index 68c8770fa6853..bdae6ba4401ed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -108,7 +108,7 @@ ron = "0.6.2" serde = { version = "1", features = ["derive"] } # Needed to poll Task examples futures-lite = "1.11.3" -crevice = {path = "crates/crevice"} +crevice = { path = "crates/crevice", version = "0.8.0", features = ["glam"] } [[example]] name = "hello_world" diff --git a/crates/bevy_math/Cargo.toml b/crates/bevy_math/Cargo.toml index d195c6f1e8339..eb8853c33510f 100644 --- a/crates/bevy_math/Cargo.toml +++ b/crates/bevy_math/Cargo.toml @@ -13,5 +13,5 @@ license = "MIT OR Apache-2.0" keywords = ["bevy"] [dependencies] -glam = { version = "0.15.1", features = ["serde", "bytemuck"] } +glam = { version = "0.20.0", features = ["serde", "bytemuck"] } bevy_reflect = { path = "../bevy_reflect", version = "0.5.0", features = ["bevy"] } diff --git a/crates/bevy_reflect/Cargo.toml b/crates/bevy_reflect/Cargo.toml index 9fb4c4ca1ff5b..d5540aa609f9a 100644 --- a/crates/bevy_reflect/Cargo.toml +++ b/crates/bevy_reflect/Cargo.toml @@ -28,7 +28,7 @@ parking_lot = "0.11.0" thiserror = "1.0" serde = "1" smallvec = { version = "1.6", features = ["serde", "union", "const_generics"], optional = true } -glam = { version = "0.15.1", features = ["serde"], optional = true } +glam = { version = "0.20.0", features = ["serde"], optional = true } [dev-dependencies] ron = "0.6.2" diff --git a/crates/bevy_render/Cargo.toml b/crates/bevy_render/Cargo.toml index 1752c34d68d52..c3ac5c6167dc0 100644 --- a/crates/bevy_render/Cargo.toml +++ b/crates/bevy_render/Cargo.toml @@ -32,7 +32,7 @@ downcast-rs = "1.2.0" thiserror = "1.0" anyhow = "1.0.4" hex = "0.4.2" -hexasphere = "4.0.0" +hexasphere = "6.0.0" parking_lot = "0.11.0" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] diff --git a/crates/crevice/Cargo.toml b/crates/crevice/Cargo.toml index f37325432f745..76d473d0c501c 100644 --- a/crates/crevice/Cargo.toml +++ b/crates/crevice/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "crevice" description = "Create GLSL-compatible versions of structs with explicitly-initialized padding" -version = "0.6.0" -edition = "2018" +version = "0.8.0" +edition = "2021" authors = ["Lucien Greathouse "] documentation = "https://docs.rs/crevice" homepage = "https://github.com/LPGhatguy/crevice" @@ -10,6 +10,7 @@ repository = "https://github.com/LPGhatguy/crevice" readme = "README.md" keywords = ["glsl", "std140", "std430"] license = "MIT OR Apache-2.0" +# resolver = "2" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -17,12 +18,19 @@ license = "MIT OR Apache-2.0" default = ["std"] std = [] +# [workspace] +# members = ["crevice-derive", "crevice-tests"] +# default-members = ["crevice-derive", "crevice-tests"] + [dependencies] -crevice-derive = { version = "0.6.0", path = "crevice-derive" } +crevice-derive = { version = "0.8.0", path = "crevice-derive" } bytemuck = "1.4.1" -mint = "0.5.5" -glam = "0.15.1" +mint = "0.5.8" + +cgmath = { version = "0.18.0", optional = true } +glam = { version = "0.20.0", features = ["mint"], optional = true } +nalgebra = { version = "0.29.0", features = ["mint"], optional = true } [dev-dependencies] -crevice-derive = { version = "0.6.0", path = "crevice-derive" } +insta = "0.16.1" diff --git a/crates/crevice/README.md b/crates/crevice/README.md index c8306125026a7..7e7070178e454 100644 --- a/crates/crevice/README.md +++ b/crates/crevice/README.md @@ -11,19 +11,34 @@ method to allow safely packing data into buffers for uploading. Generated structs also implement [`bytemuck::Zeroable`] and [`bytemuck::Pod`] for use with other libraries. -Crevice is similar to [`glsl-layout`][glsl-layout], but supports `mint` types -and explicitly initializes padding to remove one source of undefined behavior. +Crevice is similar to [`glsl-layout`][glsl-layout], but supports types from many +math crates, can generate GLSL source from structs, and explicitly initializes +padding to remove one source of undefined behavior. -Examples in this crate use cgmath, but any math crate that works with the mint -crate will also work. Some other crates include nalgebra, ultraviolet, glam, and -vek. +Crevice has support for many Rust math libraries via feature flags, and most +other math libraries by use of the mint crate. Crevice currently supports: + +* mint 0.5, enabled by default +* cgmath 0.18, using the `cgmath` feature +* nalgebra 0.29, using the `nalgebra` feature +* glam 0.19, using the `glam` feature + +PRs are welcome to add or update math libraries to Crevice. + +If your math library is not supported, it's possible to define structs using the +types from mint and convert your math library's types into mint types. This is +supported by most Rust math libraries. + +Your math library may require you to turn on a feature flag to get mint support. +For example, cgmath requires the "mint" feature to be enabled to allow +conversions to and from mint types. ## Examples ### Single Value -Uploading many types can be done by deriving `AsStd140` and using -[`as_std140`][std140::AsStd140::as_std140] and +Uploading many types can be done by deriving [`AsStd140`][std140::AsStd140] and +using [`as_std140`][std140::AsStd140::as_std140] and [`as_bytes`][std140::Std140::as_bytes] to turn the result into bytes. ```glsl @@ -36,8 +51,6 @@ uniform MAIN { ```rust use crevice::std140::{AsStd140, Std140}; -use cgmath::prelude::*; -use cgmath::{Matrix3, Vector3}; #[derive(AsStd140)] struct MainUniform { @@ -47,8 +60,12 @@ struct MainUniform { } let value = MainUniform { - orientation: Matrix3::identity().into(), - position: Vector3::new(1.0, 2.0, 3.0).into(), + orientation: [ + [1.0, 0.0, 0.0], + [0.0, 1.0, 0.0], + [0.0, 0.0, 1.0], + ].into(), + position: [1.0, 2.0, 3.0].into(), scale: 4.0, }; @@ -57,9 +74,10 @@ let value_std140 = value.as_std140(); upload_data_to_gpu(value_std140.as_bytes()); ``` -#### Sequential Types +### Sequential Types -More complicated data can be uploaded using the std140 `Writer` type. +More complicated data can be uploaded using the std140 +[`Writer`][std140::Writer] type. ```glsl struct PointLight { @@ -113,24 +131,40 @@ unmap_gpu_buffer(); ``` -### Minimum Supported Rust Version (MSRV) +## Features + +* `std` (default): Enables [`std::io::Write`]-based structs. +* `cgmath`: Enables support for types from cgmath. +* `nalgebra`: Enables support for types from nalgebra. +* `glam`: Enables support for types from glam. -Crevice supports Rust 1.46.0 and newer due to use of new `const fn` features. +## Minimum Supported Rust Version (MSRV) + +Crevice supports Rust 1.52.1 and newer due to use of new `const fn` features. [glsl-layout]: https://github.com/rustgd/glsl-layout -[Zeroable]: https://docs.rs/bytemuck/latest/bytemuck/trait.Zeroable.html -[Pod]: https://docs.rs/bytemuck/latest/bytemuck/trait.Pod.html -[TypeLayout]: https://docs.rs/type-layout/latest/type_layout/trait.TypeLayout.html + +[std140::AsStd140]: https://docs.rs/crevice/latest/crevice/std140/trait.AsStd140.html +[std140::AsStd140::as_std140]: https://docs.rs/crevice/latest/crevice/std140/trait.AsStd140.html#method.as_std140 +[std140::Std140::as_bytes]: https://docs.rs/crevice/latest/crevice/std140/trait.Std140.html#method.as_bytes +[std140::Writer]: https://docs.rs/crevice/latest/crevice/std140/struct.Writer.html + +[`std::io::Write`]: https://doc.rust-lang.org/stable/std/io/trait.Write.html + +[`bytemuck::Pod`]: https://docs.rs/bytemuck/latest/bytemuck/trait.Pod.html +[`bytemuck::Zeroable`]: https://docs.rs/bytemuck/latest/bytemuck/trait.Zeroable.html ## License Licensed under either of -* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0)) -* MIT license ([LICENSE-MIT](LICENSE-MIT) or [http://opensource.org/licenses/MIT](http://opensource.org/licenses/MIT)) +* Apache License, Version 2.0, ([LICENSE-APACHE](http://www.apache.org/licenses/LICENSE-2.0)) +* MIT license ([LICENSE-MIT](http://opensource.org/licenses/MIT)) at your option. ### Contribution -Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall +be dual licensed as above, without any additional terms or conditions. diff --git a/crates/crevice/README.tpl b/crates/crevice/README.tpl index 04934c443b16f..42ca95cfb80dc 100644 --- a/crates/crevice/README.tpl +++ b/crates/crevice/README.tpl @@ -2,6 +2,16 @@ {{readme}} +[std140::AsStd140]: https://docs.rs/crevice/latest/crevice/std140/trait.AsStd140.html +[std140::AsStd140::as_std140]: https://docs.rs/crevice/latest/crevice/std140/trait.AsStd140.html#method.as_std140 +[std140::Std140::as_bytes]: https://docs.rs/crevice/latest/crevice/std140/trait.Std140.html#method.as_bytes +[std140::Writer]: https://docs.rs/crevice/latest/crevice/std140/struct.Writer.html + +[`std::io::Write`]: https://doc.rust-lang.org/stable/std/io/trait.Write.html + +[`bytemuck::Pod`]: https://docs.rs/bytemuck/latest/bytemuck/trait.Pod.html +[`bytemuck::Zeroable`]: https://docs.rs/bytemuck/latest/bytemuck/trait.Zeroable.html + ## License Licensed under either of diff --git a/crates/crevice/crevice-derive/Cargo.toml b/crates/crevice/crevice-derive/Cargo.toml index 25d0726c4ceb8..1d64fbb6738fc 100644 --- a/crates/crevice/crevice-derive/Cargo.toml +++ b/crates/crevice/crevice-derive/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "crevice-derive" description = "Derive crate for the 'crevice' crate" -version = "0.6.0" +version = "0.8.0" edition = "2018" authors = ["Lucien Greathouse "] documentation = "https://docs.rs/crevice-derive" @@ -9,6 +9,12 @@ homepage = "https://github.com/LPGhatguy/crevice" repository = "https://github.com/LPGhatguy/crevice" license = "MIT OR Apache-2.0" +[features] +default = [] + +# Enable methods that let you introspect into the generated structs. +debug-methods = [] + [lib] proc-macro = true diff --git a/crates/crevice/crevice-derive/src/glsl.rs b/crates/crevice/crevice-derive/src/glsl.rs new file mode 100644 index 0000000000000..fd74f0cd91a91 --- /dev/null +++ b/crates/crevice/crevice-derive/src/glsl.rs @@ -0,0 +1,49 @@ +use proc_macro2::{Literal, TokenStream}; +use quote::quote; +use syn::{parse_quote, Data, DeriveInput, Fields, Path}; + +pub fn emit(input: DeriveInput) -> TokenStream { + 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"), + }; + + let base_trait_path: Path = parse_quote!(::crevice::glsl::Glsl); + let struct_trait_path: Path = parse_quote!(::crevice::glsl::GlslStruct); + + let name = input.ident; + let name_str = Literal::string(&name.to_string()); + + let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); + + let glsl_fields = fields.named.iter().map(|field| { + let field_ty = &field.ty; + let field_name_str = Literal::string(&field.ident.as_ref().unwrap().to_string()); + let field_as = quote! {<#field_ty as ::crevice::glsl::GlslArray>}; + + quote! { + s.push_str("\t"); + s.push_str(#field_as::NAME); + s.push_str(" "); + s.push_str(#field_name_str); + <#field_as::ArraySize as ::crevice::glsl::DimensionList>::push_to_string(s); + s.push_str(";\n"); + } + }); + + quote! { + unsafe impl #impl_generics #base_trait_path for #name #ty_generics #where_clause { + const NAME: &'static str = #name_str; + } + + unsafe impl #impl_generics #struct_trait_path for #name #ty_generics #where_clause { + fn enumerate_fields(s: &mut String) { + #( #glsl_fields )* + } + } + } +} diff --git a/crates/crevice/crevice-derive/src/layout.rs b/crates/crevice/crevice-derive/src/layout.rs new file mode 100644 index 0000000000000..0819a01cf2bfc --- /dev/null +++ b/crates/crevice/crevice-derive/src/layout.rs @@ -0,0 +1,288 @@ +use proc_macro2::{Span, TokenStream}; +use quote::{format_ident, quote}; +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 padded_name = format_ident!("{}Padded", trait_name); + let padded_path: Path = parse_quote!(#mod_path::#padded_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 + } + }; + + // 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 + } + }; + + // 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() + }); + } + } + + 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(); + + let generated_struct_field_init: TokenStream = fields + .iter() + .map(|field| { + let field_name = field.ident.as_ref().unwrap(); + + quote! { + #field_name: self.#field_name.#as_trait_method(), + } + }) + .collect(); + + let input_struct_field_init: TokenStream = fields + .iter() + .map(|field| { + let field_name = field.ident.as_ref().unwrap(); + + 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 + } + }; + + let debug_methods = if cfg!(feature = "debug-methods") { + let debug_fields: TokenStream = fields + .iter() + .map(|field| { + let field_name = field.ident.as_ref().unwrap(); + let field_ty = &field.ty; + + quote! { + 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(); + + 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(); + + #debug_fields + + format!("Size {}, Align {}, fields: {:#?}", size, align, fields) + } + + fn debug_definitions() -> &'static str { + stringify!( + #struct_definition + #pad_fn_impls + ) + } + } + } + } else { + quote!() + }; + + quote! { + #pad_fn_impls + #struct_definition + + 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; + type Padded = #padded_path(), + #struct_alignment + )}>; + } + + impl #impl_generics #as_trait_path for #input_name #ty_generics #where_clause { + type Output = #generated_name; + + fn #as_trait_method(&self) -> Self::Output { + Self::Output { + #generated_struct_field_init + + ..::crevice::internal::bytemuck::Zeroable::zeroed() + } + } + + fn #from_trait_method(input: Self::Output) -> Self { + Self { + #input_struct_field_init + } + } + } + + #debug_methods + } +} diff --git a/crates/crevice/crevice-derive/src/lib.rs b/crates/crevice/crevice-derive/src/lib.rs index 01d7376c4b321..ab1012a9092f9 100644 --- a/crates/crevice/crevice-derive/src/lib.rs +++ b/crates/crevice/crevice-derive/src/lib.rs @@ -1,13 +1,14 @@ +mod glsl; +mod layout; + use proc_macro::TokenStream as CompilerTokenStream; -use proc_macro2::{Span, TokenStream}; -use quote::{format_ident, quote}; -use syn::{parse_macro_input, parse_quote, Data, DeriveInput, Fields, Ident, Path}; +use syn::{parse_macro_input, DeriveInput}; #[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) } @@ -15,278 +16,15 @@ 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) } -struct EmitOptions { - /// The Rust-friendly name of the layout, like Std140. - layout_name: Ident, - - /// The minimum alignment for a struct in this layout. - min_struct_alignment: usize, - - /// The fully-qualified path to the Crevice module containing everything for - /// this layout. - mod_path: Path, - - /// The fully-qualified path to the trait defining a type in this layout. - trait_path: Path, - - /// The fully-qualified path to the trait implemented for types that can be - /// converted into this layout, like AsStd140. - as_trait_path: Path, - - /// The name of the associated type contained in AsTrait. - as_trait_assoc: Ident, - - /// The name of the method used to convert from AsTrait to Trait. - as_trait_method: Ident, - - // The name of the method used to convert from Trait to AsTrait. - from_trait_method: Ident, - - /// The name of the struct used for Padded type. - padded_name: Ident, -} - -impl EmitOptions { - 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); - - let padded_name = format_ident!("{}Padded", layout_name); - - Self { - layout_name, - min_struct_alignment, - - mod_path, - trait_path, - as_trait_path, - as_trait_assoc, - as_trait_method, - from_trait_method, - - padded_name, - } - } - - 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 padded_name = &self.padded_name; - - let visibility = input.vis; - - let name = input.ident; - let generated_name = format_ident!("{}{}", layout_name, name); - let alignment_mod_name = format_ident!("{}{}Alignment", 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 - .iter() - .map(|field| format_ident!("_{}_align", field.ident.as_ref().unwrap())) - .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! { - 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(); - - quote! { - #align_name: [u8; #alignment_mod_name::#align_name()], - #field_name: <#field_ty as #as_trait_path>::#as_trait_assoc, - } - }) - .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!(#field_name: self.#field_name.#as_trait_method()) - }) - .collect(); - - 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; - - quote! { - ::crevice::internal::max( - <<#field_ty as #as_trait_path>::#as_trait_assoc as #trait_path>::ALIGNMENT, - #last, - ) - } - }, - ); - - quote! { - #[allow(non_snake_case)] - mod #alignment_mod_name { - use super::*; - - #( #alignment_calculators )* - } - - #[derive(Debug, Clone, Copy)] - #[repr(C)] - #visibility struct #generated_name #ty_generics #where_clause { - #( #generated_fields )* - } - - 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::#layout_name for #generated_name #ty_generics #where_clause { - const ALIGNMENT: usize = #struct_alignment; - const PAD_AT_END: bool = true; - type Padded = #mod_path::#padded_name(), - #struct_alignment - )}>; - } - - impl #impl_generics #as_trait_path for #name #ty_generics #where_clause { - type #as_trait_assoc = #generated_name; - - fn #as_trait_method(&self) -> Self::#as_trait_assoc { - Self::#as_trait_assoc { - #( #field_initializers, )* - - ..::crevice::internal::bytemuck::Zeroable::zeroed() - } - } +#[proc_macro_derive(GlslStruct)] +pub fn derive_glsl_struct(input: CompilerTokenStream) -> CompilerTokenStream { + let input = parse_macro_input!(input as DeriveInput); + let expanded = glsl::emit(input); - fn #from_trait_method(value: Self::#as_trait_assoc) -> Self { - Self { - #( #field_unwrappers, )* - } - } - } - } - } + CompilerTokenStream::from(expanded) } diff --git a/crates/crevice/crevice-tests/Cargo.toml b/crates/crevice/crevice-tests/Cargo.toml new file mode 100644 index 0000000000000..a6f8605c5a2dc --- /dev/null +++ b/crates/crevice/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/crates/crevice/crevice-tests/src/gpu.rs b/crates/crevice/crevice-tests/src/gpu.rs new file mode 100644 index 0000000000000..517e8d7a46f98 --- /dev/null +++ b/crates/crevice/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(std::iter::once(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/crates/crevice/crevice-tests/src/lib.rs b/crates/crevice/crevice-tests/src/lib.rs new file mode 100644 index 0000000000000..da61b47fb47cf --- /dev/null +++ b/crates/crevice/crevice-tests/src/lib.rs @@ -0,0 +1,366 @@ +#![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, + }); +} + +#[test] +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] +fn array_strides_vec3() { + #[derive(Debug, PartialEq, AsStd140, AsStd430, GlslStruct)] + struct ArrayOfVector3 { + inner: [Vector3; 4], + } + + assert_std140!((size = 64, align = 16) ArrayOfVector3 { + inner: 0, + }); + + assert_std430!((size = 64, align = 16) ArrayOfVector3 { + inner: 0, + }); + + test_round_trip_struct(ArrayOfVector3 { + inner: [ + [0.0, 1.0, 2.0].into(), + [3.0, 4.0, 5.0].into(), + [6.0, 7.0, 8.0].into(), + [9.0, 10.0, 11.0].into(), + ], + }) +} diff --git a/crates/crevice/crevice-tests/src/util.rs b/crates/crevice/crevice-tests/src/util.rs new file mode 100644 index 0000000000000..203afced8e2e6 --- /dev/null +++ b/crates/crevice/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/crates/crevice/src/glam.rs b/crates/crevice/src/glam.rs deleted file mode 100644 index aa57061ffad06..0000000000000 --- a/crates/crevice/src/glam.rs +++ /dev/null @@ -1,103 +0,0 @@ -use bytemuck::Zeroable; - -use crate::std140::{self, AsStd140}; -use crate::std430::{self, AsStd430}; - -macro_rules! glam_vectors { - ( $( $glam_ty:ty, $std_name:ident, ( $($field:ident),* ), )* ) => { - $( - impl AsStd140 for $glam_ty { - type Std140Type = std140::$std_name; - - fn as_std140(&self) -> Self::Std140Type { - std140::$std_name { - $( - $field: self.$field, - )* - } - } - - fn from_std140(value: Self::Std140Type) -> Self { - Self::new($(value.$field,)*) - } - } - - impl AsStd430 for $glam_ty { - type Std430Type = std430::$std_name; - - fn as_std430(&self) -> Self::Std430Type { - std430::$std_name { - $( - $field: self.$field, - )* - } - } - - fn from_std430(value: Self::Std430Type) -> Self { - Self::new($(value.$field,)*) - } - } - )* - }; -} - -glam_vectors! { - glam::UVec4, UVec4, (x, y, z, w), - glam::Vec2, Vec2, (x, y), - glam::Vec3, Vec3, (x, y, z), - glam::Vec4, Vec4, (x, y, z, w), -} - -macro_rules! glam_matrices { - ( $( $glam_ty:ty, $std_name:ident, ( $($glam_field:ident),* ), ( $($field:ident),* ))* ) => { - $( - impl AsStd140 for $glam_ty { - type Std140Type = std140::$std_name; - - fn as_std140(&self) -> Self::Std140Type { - std140::$std_name { - $( - $field: self.$glam_field.as_std140(), - )* - ..Zeroable::zeroed() - } - } - - fn from_std140(value: Self::Std140Type) -> Self { - Self::from_cols( - $( - <_ as AsStd140>::from_std140(value.$field), - )* - ) - } - } - - impl AsStd430 for $glam_ty { - type Std430Type = std430::$std_name; - - fn as_std430(&self) -> Self::Std430Type { - std430::$std_name { - $( - $field: self.$glam_field.as_std430(), - )* - ..Zeroable::zeroed() - } - } - - fn from_std430(value: Self::Std430Type) -> Self { - Self::from_cols( - $( - <_ as AsStd430>::from_std430(value.$field), - )* - ) - } - } - )* - }; -} - -glam_matrices! { - glam::Mat2, Mat2, (x_axis, y_axis), (x, y) - glam::Mat3, Mat3, (x_axis, y_axis, z_axis), (x, y, z) - glam::Mat4, Mat4, (x_axis, y_axis, z_axis, w_axis), (x, y, z, w) -} diff --git a/crates/crevice/src/glsl.rs b/crates/crevice/src/glsl.rs new file mode 100644 index 0000000000000..3f879b6ab1798 --- /dev/null +++ b/crates/crevice/src/glsl.rs @@ -0,0 +1,93 @@ +//! Defines traits and types for generating GLSL code from Rust definitions. + +pub use crevice_derive::GlslStruct; +use std::marker::PhantomData; + +/// Type-level linked list of array dimensions +pub struct Dimension { + _marker: PhantomData, +} + +/// Type-level linked list terminator for array dimensions. +pub struct DimensionNil; + +/// Trait for type-level array dimensions. Probably shouldn't be implemented outside this crate. +pub unsafe trait DimensionList { + /// Write dimensions in square brackets to a string, list tail to list head. + fn push_to_string(s: &mut String); +} + +unsafe impl DimensionList for DimensionNil { + fn push_to_string(_: &mut String) {} +} + +unsafe impl DimensionList for Dimension { + fn push_to_string(s: &mut String) { + use std::fmt::Write; + A::push_to_string(s); + write!(s, "[{}]", N).unwrap(); + } +} + +/// Trait for types that have a GLSL equivalent. Useful for generating GLSL code +/// from Rust structs. +pub unsafe trait Glsl { + /// The name of this type in GLSL, like `vec2` or `mat4`. + const NAME: &'static str; +} + +/// Trait for types that can be represented as a struct in GLSL. +/// +/// This trait should not generally be implemented by hand, but can be derived. +pub unsafe trait GlslStruct: Glsl { + /// The fields contained in this struct. + fn enumerate_fields(s: &mut String); + + /// Generates GLSL code that represents this struct and its fields. + fn glsl_definition() -> String { + let mut output = String::new(); + output.push_str("struct "); + output.push_str(Self::NAME); + output.push_str(" {\n"); + + Self::enumerate_fields(&mut output); + + output.push_str("};"); + output + } +} + +/// Trait for types that are expressible as a GLSL type with (possibly zero) array dimensions. +pub unsafe trait GlslArray { + /// Base type name. + const NAME: &'static str; + /// Type-level linked list of array dimensions, ordered outer to inner. + type ArraySize: DimensionList; +} + +unsafe impl GlslArray for T { + const NAME: &'static str = ::NAME; + type ArraySize = DimensionNil; +} + +unsafe impl Glsl for f32 { + const NAME: &'static str = "float"; +} + +unsafe impl Glsl for f64 { + const NAME: &'static str = "double"; +} + +unsafe impl Glsl for i32 { + const NAME: &'static str = "int"; +} + +unsafe impl Glsl for u32 { + const NAME: &'static str = "uint"; +} + +unsafe impl GlslArray for [T; N] { + const NAME: &'static str = T::NAME; + + type ArraySize = Dimension; +} diff --git a/crates/crevice/src/imp.rs b/crates/crevice/src/imp.rs new file mode 100644 index 0000000000000..af49bd8915bbf --- /dev/null +++ b/crates/crevice/src/imp.rs @@ -0,0 +1,10 @@ +mod imp_mint; + +#[cfg(feature = "cgmath")] +mod imp_cgmath; + +#[cfg(feature = "glam")] +mod imp_glam; + +#[cfg(feature = "nalgebra")] +mod imp_nalgebra; diff --git a/crates/crevice/src/imp/imp_cgmath.rs b/crates/crevice/src/imp/imp_cgmath.rs new file mode 100644 index 0000000000000..79ee7e071cec2 --- /dev/null +++ b/crates/crevice/src/imp/imp_cgmath.rs @@ -0,0 +1,30 @@ +easy_impl! { + Vec2 cgmath::Vector2 { x, y }, + Vec3 cgmath::Vector3 { x, y, z }, + Vec4 cgmath::Vector4 { x, y, z, w }, + + IVec2 cgmath::Vector2 { x, y }, + IVec3 cgmath::Vector3 { x, y, z }, + IVec4 cgmath::Vector4 { x, y, z, w }, + + UVec2 cgmath::Vector2 { x, y }, + UVec3 cgmath::Vector3 { x, y, z }, + UVec4 cgmath::Vector4 { x, y, z, w }, + + // bool vectors are disabled due to https://github.com/LPGhatguy/crevice/issues/36 + // BVec2 cgmath::Vector2 { x, y }, + // BVec3 cgmath::Vector3 { x, y, z }, + // BVec4 cgmath::Vector4 { x, y, z, w }, + + DVec2 cgmath::Vector2 { x, y }, + DVec3 cgmath::Vector3 { x, y, z }, + DVec4 cgmath::Vector4 { x, y, z, w }, + + Mat2 cgmath::Matrix2 { x, y }, + Mat3 cgmath::Matrix3 { x, y, z }, + Mat4 cgmath::Matrix4 { x, y, z, w }, + + DMat2 cgmath::Matrix2 { x, y }, + DMat3 cgmath::Matrix3 { x, y, z }, + DMat4 cgmath::Matrix4 { x, y, z, w }, +} diff --git a/crates/crevice/src/imp/imp_glam.rs b/crates/crevice/src/imp/imp_glam.rs new file mode 100644 index 0000000000000..58ef711c27855 --- /dev/null +++ b/crates/crevice/src/imp/imp_glam.rs @@ -0,0 +1,24 @@ +minty_impl! { + mint::Vector2 => glam::Vec2, + mint::Vector3 => glam::Vec3, + mint::Vector4 => glam::Vec4, + mint::Vector2 => glam::IVec2, + mint::Vector3 => glam::IVec3, + mint::Vector4 => glam::IVec4, + mint::Vector2 => glam::UVec2, + mint::Vector3 => glam::UVec3, + mint::Vector4 => glam::UVec4, + // bool vectors are disabled due to https://github.com/LPGhatguy/crevice/issues/36 + // mint::Vector2 => glam::BVec2, + // mint::Vector3 => glam::BVec3, + // mint::Vector4 => glam::BVec4, + mint::Vector2 => glam::DVec2, + mint::Vector3 => glam::DVec3, + mint::Vector4 => glam::DVec4, + mint::ColumnMatrix2 => glam::Mat2, + mint::ColumnMatrix3 => glam::Mat3, + mint::ColumnMatrix4 => glam::Mat4, + mint::ColumnMatrix2 => glam::DMat2, + mint::ColumnMatrix3 => glam::DMat3, + mint::ColumnMatrix4 => glam::DMat4, +} diff --git a/crates/crevice/src/imp/imp_mint.rs b/crates/crevice/src/imp/imp_mint.rs new file mode 100644 index 0000000000000..056a181c2ca70 --- /dev/null +++ b/crates/crevice/src/imp/imp_mint.rs @@ -0,0 +1,30 @@ +easy_impl! { + Vec2 mint::Vector2 { x, y }, + Vec3 mint::Vector3 { x, y, z }, + Vec4 mint::Vector4 { x, y, z, w }, + + IVec2 mint::Vector2 { x, y }, + IVec3 mint::Vector3 { x, y, z }, + IVec4 mint::Vector4 { x, y, z, w }, + + UVec2 mint::Vector2 { x, y }, + UVec3 mint::Vector3 { x, y, z }, + UVec4 mint::Vector4 { x, y, z, w }, + + // bool vectors are disabled due to https://github.com/LPGhatguy/crevice/issues/36 + // BVec2 mint::Vector2 { x, y }, + // BVec3 mint::Vector3 { x, y, z }, + // BVec4 mint::Vector4 { x, y, z, w }, + + DVec2 mint::Vector2 { x, y }, + DVec3 mint::Vector3 { x, y, z }, + DVec4 mint::Vector4 { x, y, z, w }, + + Mat2 mint::ColumnMatrix2 { x, y }, + Mat3 mint::ColumnMatrix3 { x, y, z }, + Mat4 mint::ColumnMatrix4 { x, y, z, w }, + + DMat2 mint::ColumnMatrix2 { x, y }, + DMat3 mint::ColumnMatrix3 { x, y, z }, + DMat4 mint::ColumnMatrix4 { x, y, z, w }, +} diff --git a/crates/crevice/src/imp/imp_nalgebra.rs b/crates/crevice/src/imp/imp_nalgebra.rs new file mode 100644 index 0000000000000..3d1b89c0d315b --- /dev/null +++ b/crates/crevice/src/imp/imp_nalgebra.rs @@ -0,0 +1,24 @@ +minty_impl! { + mint::Vector2 => nalgebra::Vector2, + mint::Vector3 => nalgebra::Vector3, + mint::Vector4 => nalgebra::Vector4, + mint::Vector2 => nalgebra::Vector2, + mint::Vector3 => nalgebra::Vector3, + mint::Vector4 => nalgebra::Vector4, + mint::Vector2 => nalgebra::Vector2, + mint::Vector3 => nalgebra::Vector3, + mint::Vector4 => nalgebra::Vector4, + // bool vectors are disabled due to https://github.com/LPGhatguy/crevice/issues/36 + // mint::Vector2 => nalgebra::Vector2, + // mint::Vector3 => nalgebra::Vector3, + // mint::Vector4 => nalgebra::Vector4, + mint::Vector2 => nalgebra::Vector2, + mint::Vector3 => nalgebra::Vector3, + mint::Vector4 => nalgebra::Vector4, + mint::ColumnMatrix2 => nalgebra::Matrix2, + mint::ColumnMatrix3 => nalgebra::Matrix3, + mint::ColumnMatrix4 => nalgebra::Matrix4, + mint::ColumnMatrix2 => nalgebra::Matrix2, + mint::ColumnMatrix3 => nalgebra::Matrix3, + mint::ColumnMatrix4 => nalgebra::Matrix4, +} diff --git a/crates/crevice/src/internal.rs b/crates/crevice/src/internal.rs index cf5a22f368d4c..cd22972fb30aa 100644 --- a/crates/crevice/src/internal.rs +++ b/crates/crevice/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/crates/crevice/src/lib.rs b/crates/crevice/src/lib.rs index 16f0994474dfb..958d2c118b738 100644 --- a/crates/crevice/src/lib.rs +++ b/crates/crevice/src/lib.rs @@ -1,5 +1,9 @@ -#![allow(clippy::all)] - +#![allow( + clippy::new_without_default, + clippy::needless_update, + clippy::len_without_is_empty, + clippy::needless_range_loop +)] /*! [![GitHub CI Status](https://github.com/LPGhatguy/crevice/workflows/CI/badge.svg)](https://github.com/LPGhatguy/crevice/actions) [![crevice on crates.io](https://img.shields.io/crates/v/crevice.svg)](https://crates.io/crates/crevice) @@ -12,19 +16,34 @@ method to allow safely packing data into buffers for uploading. Generated structs also implement [`bytemuck::Zeroable`] and [`bytemuck::Pod`] for use with other libraries. -Crevice is similar to [`glsl-layout`][glsl-layout], but supports `mint` types -and explicitly initializes padding to remove one source of undefined behavior. +Crevice is similar to [`glsl-layout`][glsl-layout], but supports types from many +math crates, can generate GLSL source from structs, and explicitly initializes +padding to remove one source of undefined behavior. + +Crevice has support for many Rust math libraries via feature flags, and most +other math libraries by use of the mint crate. Crevice currently supports: + +* mint 0.5, enabled by default +* cgmath 0.18, using the `cgmath` feature +* nalgebra 0.29, using the `nalgebra` feature +* glam 0.19, using the `glam` feature + +PRs are welcome to add or update math libraries to Crevice. + +If your math library is not supported, it's possible to define structs using the +types from mint and convert your math library's types into mint types. This is +supported by most Rust math libraries. -Examples in this crate use cgmath, but any math crate that works with the mint -crate will also work. Some other crates include nalgebra, ultraviolet, glam, and -vek. +Your math library may require you to turn on a feature flag to get mint support. +For example, cgmath requires the "mint" feature to be enabled to allow +conversions to and from mint types. ## Examples ### Single Value -Uploading many types can be done by deriving `AsStd140` and using -[`as_std140`][std140::AsStd140::as_std140] and +Uploading many types can be done by deriving [`AsStd140`][std140::AsStd140] and +using [`as_std140`][std140::AsStd140::as_std140] and [`as_bytes`][std140::Std140::as_bytes] to turn the result into bytes. ```glsl @@ -35,10 +54,8 @@ uniform MAIN { } main; ``` -```skip +```rust use crevice::std140::{AsStd140, Std140}; -use cgmath::prelude::*; -use cgmath::{Matrix3, Vector3}; #[derive(AsStd140)] struct MainUniform { @@ -48,8 +65,12 @@ struct MainUniform { } let value = MainUniform { - orientation: Matrix3::identity().into(), - position: Vector3::new(1.0, 2.0, 3.0).into(), + orientation: [ + [1.0, 0.0, 0.0], + [0.0, 1.0, 0.0], + [0.0, 0.0, 1.0], + ].into(), + position: [1.0, 2.0, 3.0].into(), scale: 4.0, }; @@ -61,7 +82,8 @@ upload_data_to_gpu(value_std140.as_bytes()); ### Sequential Types -More complicated data can be uploaded using the std140 `Writer` type. +More complicated data can be uploaded using the std140 +[`Writer`][std140::Writer] type. ```glsl struct PointLight { @@ -76,7 +98,7 @@ buffer POINT_LIGHTS { } point_lights; ``` -```skip +```rust use crevice::std140::{self, AsStd140}; #[derive(AsStd140)] @@ -120,25 +142,31 @@ unmap_gpu_buffer(); # Ok::<(), std::io::Error>(()) ``` +## Features + +* `std` (default): Enables [`std::io::Write`]-based structs. +* `cgmath`: Enables support for types from cgmath. +* `nalgebra`: Enables support for types from nalgebra. +* `glam`: Enables support for types from glam. + ## Minimum Supported Rust Version (MSRV) -Crevice supports Rust 1.46.0 and newer due to use of new `const fn` features. +Crevice supports Rust 1.52.1 and newer due to use of new `const fn` features. [glsl-layout]: https://github.com/rustgd/glsl-layout -[Zeroable]: https://docs.rs/bytemuck/latest/bytemuck/trait.Zeroable.html -[Pod]: https://docs.rs/bytemuck/latest/bytemuck/trait.Pod.html -[TypeLayout]: https://docs.rs/type-layout/latest/type_layout/trait.TypeLayout.html */ #![deny(missing_docs)] #![cfg_attr(not(feature = "std"), no_std)] +#[macro_use] +mod util; + +pub mod glsl; pub mod std140; pub mod std430; #[doc(hidden)] pub mod internal; -mod mint; - -mod glam; +mod imp; diff --git a/crates/crevice/src/mint.rs b/crates/crevice/src/mint.rs deleted file mode 100644 index dbaf628e7ce7d..0000000000000 --- a/crates/crevice/src/mint.rs +++ /dev/null @@ -1,130 +0,0 @@ -use bytemuck::Zeroable; - -use crate::std140::{self, AsStd140}; -use crate::std430::{self, AsStd430}; - -macro_rules! mint_vectors { - ( $( $mint_ty:ty, $std_name:ident, ( $($field:ident),* ), )* ) => { - $( - impl AsStd140 for $mint_ty { - type Std140Type = std140::$std_name; - - fn as_std140(&self) -> Self::Std140Type { - std140::$std_name { - $( - $field: self.$field, - )* - } - } - - fn from_std140(value: Self::Std140Type) -> Self { - Self { - $( - $field: value.$field, - )* - } - } - } - - impl AsStd430 for $mint_ty { - type Std430Type = std430::$std_name; - - fn as_std430(&self) -> Self::Std430Type { - std430::$std_name { - $( - $field: self.$field, - )* - } - } - - fn from_std430(value: Self::Std430Type) -> Self { - Self { - $( - $field: value.$field, - )* - } - } - } - )* - }; -} - -mint_vectors! { - mint::Vector2, Vec2, (x, y), - mint::Vector3, Vec3, (x, y, z), - mint::Vector4, Vec4, (x, y, z, w), - - mint::Vector2, IVec2, (x, y), - mint::Vector3, IVec3, (x, y, z), - mint::Vector4, IVec4, (x, y, z, w), - - mint::Vector2, UVec2, (x, y), - mint::Vector3, UVec3, (x, y, z), - mint::Vector4, UVec4, (x, y, z, w), - - mint::Vector2, BVec2, (x, y), - mint::Vector3, BVec3, (x, y, z), - mint::Vector4, BVec4, (x, y, z, w), - - mint::Vector2, DVec2, (x, y), - mint::Vector3, DVec3, (x, y, z), - mint::Vector4, DVec4, (x, y, z, w), -} - -macro_rules! mint_matrices { - ( $( $mint_ty:ty, $std_name:ident, ( $($field:ident),* ), )* ) => { - $( - impl AsStd140 for $mint_ty { - type Std140Type = std140::$std_name; - - fn as_std140(&self) -> Self::Std140Type { - std140::$std_name { - $( - $field: self.$field.as_std140(), - )* - ..Zeroable::zeroed() - } - } - - fn from_std140(value: Self::Std140Type) -> Self { - Self { - $( - $field: <_ as AsStd140>::from_std140(value.$field), - )* - } - } - } - - impl AsStd430 for $mint_ty { - type Std430Type = std430::$std_name; - - fn as_std430(&self) -> Self::Std430Type { - std430::$std_name { - $( - $field: self.$field.as_std430(), - )* - ..Zeroable::zeroed() - } - } - - fn from_std430(value: Self::Std430Type) -> Self { - Self { - $( - $field: <_ as AsStd430>::from_std430(value.$field), - )* - } - } - } - )* - }; -} - -mint_matrices! { - mint::ColumnMatrix2, Mat2, (x, y), - mint::ColumnMatrix3, Mat3, (x, y, z), - mint::ColumnMatrix4, Mat4, (x, y, z, w), - - mint::ColumnMatrix2, DMat2, (x, y), - mint::ColumnMatrix3, DMat3, (x, y, z), - mint::ColumnMatrix4, DMat4, (x, y, z, w), -} diff --git a/crates/crevice/src/std140/dynamic_uniform.rs b/crates/crevice/src/std140/dynamic_uniform.rs index 9731ac165017c..262f8ea449842 100644 --- a/crates/crevice/src/std140/dynamic_uniform.rs +++ b/crates/crevice/src/std140/dynamic_uniform.rs @@ -11,13 +11,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/crates/crevice/src/std140/primitives.rs b/crates/crevice/src/std140/primitives.rs index ec142b6b420ce..34e161e3b7818 100644 --- a/crates/crevice/src/std140/primitives.rs +++ b/crates/crevice/src/std140/primitives.rs @@ -1,5 +1,6 @@ use bytemuck::{Pod, Zeroable}; +use crate::glsl::Glsl; use crate::std140::{Std140, Std140Padded}; use crate::internal::{align_offset, max}; @@ -28,13 +29,14 @@ unsafe impl Std140 for u32 { macro_rules! vectors { ( $( - #[$doc:meta] align($align:literal) $name:ident <$prim:ident> ($($field:ident),+) + #[$doc:meta] align($align:literal) $glsl_name:ident $name:ident <$prim:ident> ($($field:ident),+) )+ ) => { $( #[$doc] #[allow(missing_docs)] - #[derive(Debug, Clone, Copy)] + #[derive(Debug, Clone, Copy, PartialEq)] + #[repr(C)] pub struct $name { $(pub $field: $prim,)+ } @@ -46,30 +48,36 @@ macro_rules! vectors { const ALIGNMENT: usize = $align; type Padded = Std140Padded(), max(16, $align))}>; } + + unsafe impl Glsl for $name { + const NAME: &'static str = stringify!($glsl_name); + } )+ }; } vectors! { - #[doc = "Corresponds to a GLSL `vec2` in std140 layout."] align(8) Vec2(x, y) - #[doc = "Corresponds to a GLSL `vec3` in std140 layout."] align(16) Vec3(x, y, z) - #[doc = "Corresponds to a GLSL `vec4` in std140 layout."] align(16) Vec4(x, y, z, w) + #[doc = "Corresponds to a GLSL `vec2` in std140 layout."] align(8) vec2 Vec2(x, y) + #[doc = "Corresponds to a GLSL `vec3` in std140 layout."] align(16) vec3 Vec3(x, y, z) + #[doc = "Corresponds to a GLSL `vec4` in std140 layout."] align(16) vec4 Vec4(x, y, z, w) - #[doc = "Corresponds to a GLSL `ivec2` in std140 layout."] align(8) IVec2(x, y) - #[doc = "Corresponds to a GLSL `ivec3` in std140 layout."] align(16) IVec3(x, y, z) - #[doc = "Corresponds to a GLSL `ivec4` in std140 layout."] align(16) IVec4(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 `uvec2` in std140 layout."] align(8) UVec2(x, y) - #[doc = "Corresponds to a GLSL `uvec3` in std140 layout."] align(16) UVec3(x, y, z) - #[doc = "Corresponds to a GLSL `uvec4` in std140 layout."] align(16) UVec4(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 `bvec2` in std140 layout."] align(8) BVec2(x, y) - #[doc = "Corresponds to a GLSL `bvec3` in std140 layout."] align(16) BVec3(x, y, z) - #[doc = "Corresponds to a GLSL `bvec4` in std140 layout."] align(16) BVec4(x, y, z, w) + // bool vectors are disabled due to https://github.com/LPGhatguy/crevice/issues/36 - #[doc = "Corresponds to a GLSL `dvec2` in std140 layout."] align(16) DVec2(x, y) - #[doc = "Corresponds to a GLSL `dvec3` in std140 layout."] align(32) DVec3(x, y, z) - #[doc = "Corresponds to a GLSL `dvec4` in std140 layout."] align(32) DVec4(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 `dvec2` in std140 layout."] align(16) dvec2 DVec2(x, y) + #[doc = "Corresponds to a GLSL `dvec3` in std140 layout."] align(32) dvec3 DVec3(x, y, z) + #[doc = "Corresponds to a GLSL `dvec4` in std140 layout."] align(32) dvec4 DVec4(x, y, z, w) } macro_rules! matrices { @@ -77,7 +85,7 @@ macro_rules! matrices { $( #[$doc:meta] align($align:literal) - $name:ident { + $glsl_name:ident $name:ident { $($field:ident: $field_ty:ty,)+ } )+ @@ -86,6 +94,7 @@ macro_rules! matrices { #[$doc] #[allow(missing_docs)] #[derive(Debug, Clone, Copy)] + #[repr(C)] pub struct $name { $(pub $field: $field_ty,)+ } @@ -99,6 +108,10 @@ macro_rules! matrices { const PAD_AT_END: bool = true; type Padded = Std140Padded(), max(16, $align))}>; } + + unsafe impl Glsl for $name { + const NAME: &'static str = stringify!($glsl_name); + } )+ }; } @@ -106,7 +119,7 @@ macro_rules! matrices { matrices! { #[doc = "Corresponds to a GLSL `mat2` in std140 layout."] align(16) - Mat2 { + mat2 Mat2 { x: Vec2, _pad_x: [f32; 2], y: Vec2, @@ -115,7 +128,7 @@ matrices! { #[doc = "Corresponds to a GLSL `mat3` in std140 layout."] align(16) - Mat3 { + mat3 Mat3 { x: Vec3, _pad_x: f32, y: Vec3, @@ -126,7 +139,7 @@ matrices! { #[doc = "Corresponds to a GLSL `mat4` in std140 layout."] align(16) - Mat4 { + mat4 Mat4 { x: Vec4, y: Vec4, z: Vec4, @@ -135,14 +148,14 @@ matrices! { #[doc = "Corresponds to a GLSL `dmat2` in std140 layout."] align(16) - DMat2 { + dmat2 DMat2 { x: DVec2, y: DVec2, } #[doc = "Corresponds to a GLSL `dmat3` in std140 layout."] align(32) - DMat3 { + dmat3 DMat3 { x: DVec3, _pad_x: f64, y: DVec3, @@ -153,7 +166,7 @@ matrices! { #[doc = "Corresponds to a GLSL `dmat3` in std140 layout."] align(32) - DMat4 { + dmat4 DMat4 { x: DVec4, y: DVec4, z: DVec4, diff --git a/crates/crevice/src/std140/sizer.rs b/crates/crevice/src/std140/sizer.rs index 4d76ad6278997..ee5c134595daf 100644 --- a/crates/crevice/src/std140/sizer.rs +++ b/crates/crevice/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/crates/crevice/src/std140/traits.rs b/crates/crevice/src/std140/traits.rs index 6e0cf172fb8da..caa8fce6fdaa6 100644 --- a/crates/crevice/src/std140/traits.rs +++ b/crates/crevice/src/std140/traits.rs @@ -17,9 +17,10 @@ pub unsafe trait Std140: Copy + Zeroable + Pod { /// control and zero their padding bytes, making converting them to and from /// slices safe. const ALIGNMENT: usize; + /// Whether this type requires a padding at the end (ie, is a struct or an array /// of primitives). - /// See https://www.khronos.org/registry/OpenGL/specs/gl/glspec45.core.pdf#page=159 + /// See /// (rule 4 and 9) const PAD_AT_END: bool = false; /// Padded type (Std140Padded specialization) @@ -75,7 +76,7 @@ struct which contains only fields that also implement `AsStd140` can derive `AsStd140`. Types from the mint crate implement `AsStd140`, making them convenient for use -in uniform types. Most Rust geometry crates, like cgmath, nalgebra, and +in uniform types. Most Rust math crates, like cgmath, nalgebra, and ultraviolet support mint. ## Example @@ -87,9 +88,7 @@ uniform CAMERA { } camera; ``` -```skip -use cgmath::prelude::*; -use cgmath::{Matrix4, Deg, perspective}; +```no_run use crevice::std140::{AsStd140, Std140}; #[derive(AsStd140)] @@ -98,9 +97,12 @@ struct CameraUniform { projection: mint::ColumnMatrix4, } +let view: mint::ColumnMatrix4 = todo!("your math code here"); +let projection: mint::ColumnMatrix4 = todo!("your math code here"); + let camera = CameraUniform { - view: Matrix4::identity().into(), - projection: perspective(Deg(60.0), 16.0/9.0, 0.01, 100.0).into(), + view, + projection, }; # fn write_to_gpu_buffer(bytes: &[u8]) {} @@ -110,26 +112,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 @@ -178,29 +180,36 @@ where type Padded = Self; } +impl Std140Array { + fn uninit_array() -> [MaybeUninit; N] { + unsafe { MaybeUninit::uninit().assume_init() } + } + + fn from_uninit_array(a: [MaybeUninit; N]) -> Self { + unsafe { core::mem::transmute_copy(&a) } + } +} + impl AsStd140 for [T; N] where - ::Padded: Pod, + ::Padded: Pod, { - type Std140Type = Std140Array; - fn as_std140(&self) -> Self::Std140Type { - let mut res: [MaybeUninit<::Padded>; N] = - unsafe { MaybeUninit::uninit().assume_init() }; + type Output = Std140Array; + fn as_std140(&self) -> Self::Output { + let mut res = Self::Output::uninit_array(); for i in 0..N { res[i] = MaybeUninit::new(Std140Convertible::from_std140(self[i].as_std140())); } - unsafe { core::mem::transmute_copy(&res) } + Self::Output::from_uninit_array(res) } - fn from_std140(val: Self::Std140Type) -> Self { + fn from_std140(val: Self::Output) -> Self { let mut res: [MaybeUninit; N] = unsafe { MaybeUninit::uninit().assume_init() }; - for i in 0..N { - res[i] = MaybeUninit::new(AsStd140::from_std140(val.0[i].into_std140())); + res[i] = MaybeUninit::new(T::from_std140(Std140Convertible::into_std140(val.0[i]))); } - unsafe { core::mem::transmute_copy(&res) } } } @@ -241,7 +250,7 @@ where } fn std140_size(&self) -> usize { - size_of::<::Std140Type>() + size_of::<::Output>() } } diff --git a/crates/crevice/src/std430/primitives.rs b/crates/crevice/src/std430/primitives.rs index beb8b257c5da4..3348e82c7b2c6 100644 --- a/crates/crevice/src/std430/primitives.rs +++ b/crates/crevice/src/std430/primitives.rs @@ -1,5 +1,6 @@ use bytemuck::{Pod, Zeroable}; +use crate::glsl::Glsl; use crate::std430::{Std430, Std430Padded}; use crate::internal::align_offset; @@ -28,13 +29,14 @@ unsafe impl Std430 for u32 { macro_rules! vectors { ( $( - #[$doc:meta] align($align:literal) $name:ident <$prim:ident> ($($field:ident),+) + #[$doc:meta] align($align:literal) $glsl_name:ident $name:ident <$prim:ident> ($($field:ident),+) )+ ) => { $( #[$doc] #[allow(missing_docs)] #[derive(Debug, Clone, Copy)] + #[repr(C)] pub struct $name { $(pub $field: $prim,)+ } @@ -46,30 +48,36 @@ macro_rules! vectors { const ALIGNMENT: usize = $align; type Padded = Std430Padded(), $align)}>; } + + unsafe impl Glsl for $name { + const NAME: &'static str = stringify!($glsl_name); + } )+ }; } vectors! { - #[doc = "Corresponds to a GLSL `vec2` in std430 layout."] align(8) Vec2(x, y) - #[doc = "Corresponds to a GLSL `vec3` in std430 layout."] align(16) Vec3(x, y, z) - #[doc = "Corresponds to a GLSL `vec4` in std430 layout."] align(16) Vec4(x, y, z, w) + #[doc = "Corresponds to a GLSL `vec2` in std430 layout."] align(8) vec2 Vec2(x, y) + #[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 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 `ivec2` in std140 layout."] align(8) IVec2(x, y) - #[doc = "Corresponds to a GLSL `ivec3` in std140 layout."] align(16) IVec3(x, y, z) - #[doc = "Corresponds to a GLSL `ivec4` in std140 layout."] align(16) IVec4(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 `uvec2` in std140 layout."] align(8) UVec2(x, y) - #[doc = "Corresponds to a GLSL `uvec3` in std140 layout."] align(16) UVec3(x, y, z) - #[doc = "Corresponds to a GLSL `uvec4` in std140 layout."] align(16) UVec4(x, y, z, w) + // bool vectors are disabled due to https://github.com/LPGhatguy/crevice/issues/36 - #[doc = "Corresponds to a GLSL `bvec2` in std140 layout."] align(8) BVec2(x, y) - #[doc = "Corresponds to a GLSL `bvec3` in std140 layout."] align(16) BVec3(x, y, z) - #[doc = "Corresponds to a GLSL `bvec4` in std140 layout."] align(16) 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(x, y) - #[doc = "Corresponds to a GLSL `dvec3` in std430 layout."] align(32) DVec3(x, y, z) - #[doc = "Corresponds to a GLSL `dvec4` in std430 layout."] align(32) DVec4(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) + #[doc = "Corresponds to a GLSL `dvec4` in std430 layout."] align(32) dvec4 DVec4(x, y, z, w) } macro_rules! matrices { @@ -77,7 +85,7 @@ macro_rules! matrices { $( #[$doc:meta] align($align:literal) - $name:ident { + $glsl_name:ident $name:ident { $($field:ident: $field_ty:ty,)+ } )+ @@ -86,6 +94,7 @@ macro_rules! matrices { #[$doc] #[allow(missing_docs)] #[derive(Debug, Clone, Copy)] + #[repr(C)] pub struct $name { $(pub $field: $field_ty,)+ } @@ -99,29 +108,36 @@ macro_rules! matrices { const PAD_AT_END: bool = true; type Padded = Std430Padded(), $align)}>; } + + unsafe impl Glsl for $name { + const NAME: &'static str = stringify!($glsl_name); + } )+ }; } matrices! { #[doc = "Corresponds to a GLSL `mat2` in std430 layout."] - align(16) - Mat2 { + align(8) + mat2 Mat2 { x: Vec2, y: Vec2, } #[doc = "Corresponds to a GLSL `mat3` in std430 layout."] align(16) - Mat3 { + 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."] align(16) - Mat4 { + mat4 Mat4 { x: Vec4, y: Vec4, z: Vec4, @@ -130,22 +146,25 @@ matrices! { #[doc = "Corresponds to a GLSL `dmat2` in std430 layout."] align(16) - DMat2 { + dmat2 DMat2 { x: DVec2, y: DVec2, } #[doc = "Corresponds to a GLSL `dmat3` in std430 layout."] align(32) - DMat3 { + 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."] align(32) - DMat4 { + dmat4 DMat4 { x: DVec4, y: DVec4, z: DVec4, diff --git a/crates/crevice/src/std430/sizer.rs b/crates/crevice/src/std430/sizer.rs index 139c69cb78dd2..20b7b29e0bc5a 100644 --- a/crates/crevice/src/std430/sizer.rs +++ b/crates/crevice/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/crates/crevice/src/std430/traits.rs b/crates/crevice/src/std430/traits.rs index e53c2003f016e..6206ec7ac641f 100644 --- a/crates/crevice/src/std430/traits.rs +++ b/crates/crevice/src/std430/traits.rs @@ -17,9 +17,10 @@ pub unsafe trait Std430: Copy + Zeroable + Pod { /// control and zero their padding bytes, making converting them to and from /// slices safe. const ALIGNMENT: usize; + /// Whether this type requires a padding at the end (ie, is a struct or an array /// of primitives). - /// See https://www.khronos.org/registry/OpenGL/specs/gl/glspec45.core.pdf#page=159 + /// See /// (rule 4 and 9) const PAD_AT_END: bool = false; /// Padded type (Std430Padded specialization) @@ -87,9 +88,7 @@ uniform CAMERA { } camera; ``` -```skip -use cgmath::prelude::*; -use cgmath::{Matrix4, Deg, perspective}; +```no_run use crevice::std430::{AsStd430, Std430}; #[derive(AsStd430)] @@ -98,9 +97,12 @@ struct CameraUniform { projection: mint::ColumnMatrix4, } +let view: mint::ColumnMatrix4 = todo!("your math code here"); +let projection: mint::ColumnMatrix4 = todo!("your math code here"); + let camera = CameraUniform { - view: Matrix4::identity().into(), - projection: perspective(Deg(60.0), 16.0/9.0, 0.01, 100.0).into(), + view, + projection, }; # fn write_to_gpu_buffer(bytes: &[u8]) {} @@ -110,26 +112,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 @@ -178,29 +180,36 @@ where type Padded = Self; } +impl Std430Array { + fn uninit_array() -> [MaybeUninit; N] { + unsafe { MaybeUninit::uninit().assume_init() } + } + + fn from_uninit_array(a: [MaybeUninit; N]) -> Self { + unsafe { core::mem::transmute_copy(&a) } + } +} + impl AsStd430 for [T; N] where - ::Padded: Pod, + ::Padded: Pod, { - type Std430Type = Std430Array; - fn as_std430(&self) -> Self::Std430Type { - let mut res: [MaybeUninit<::Padded>; N] = - unsafe { MaybeUninit::uninit().assume_init() }; + type Output = Std430Array; + fn as_std430(&self) -> Self::Output { + let mut res = Self::Output::uninit_array(); for i in 0..N { res[i] = MaybeUninit::new(Std430Convertible::from_std430(self[i].as_std430())); } - unsafe { core::mem::transmute_copy(&res) } + Self::Output::from_uninit_array(res) } - fn from_std430(val: Self::Std430Type) -> Self { + fn from_std430(val: Self::Output) -> Self { let mut res: [MaybeUninit; N] = unsafe { MaybeUninit::uninit().assume_init() }; - for i in 0..N { - res[i] = MaybeUninit::new(AsStd430::from_std430(val.0[i].into_std430())); + res[i] = MaybeUninit::new(T::from_std430(val.0[i].into_std430())); } - unsafe { core::mem::transmute_copy(&res) } } } @@ -241,7 +250,7 @@ where } fn std430_size(&self) -> usize { - size_of::<::Std430Type>() + size_of::<::Output>() } } diff --git a/crates/crevice/src/util.rs b/crates/crevice/src/util.rs new file mode 100644 index 0000000000000..9c6c2a396450d --- /dev/null +++ b/crates/crevice/src/util.rs @@ -0,0 +1,97 @@ +#![allow(unused_macros)] + +macro_rules! easy_impl { + ( $( $std_name:ident $imp_ty:ty { $($field:ident),* }, )* ) => { + $( + impl crate::std140::AsStd140 for $imp_ty { + type Output = crate::std140::$std_name; + + #[inline] + fn as_std140(&self) -> Self::Output { + crate::std140::$std_name { + $( + $field: self.$field.as_std140(), + )* + ..bytemuck::Zeroable::zeroed() + } + } + + #[inline] + fn from_std140(value: Self::Output) -> Self { + Self { + $( + $field: <_ as crate::std140::AsStd140>::from_std140(value.$field), + )* + } + } + } + + impl crate::std430::AsStd430 for $imp_ty { + type Output = crate::std430::$std_name; + + #[inline] + fn as_std430(&self) -> Self::Output { + crate::std430::$std_name { + $( + $field: self.$field.as_std430(), + )* + ..bytemuck::Zeroable::zeroed() + } + } + + #[inline] + fn from_std430(value: Self::Output) -> Self { + Self { + $( + $field: <_ as crate::std430::AsStd430>::from_std430(value.$field), + )* + } + } + } + + unsafe impl crate::glsl::Glsl for $imp_ty { + const NAME: &'static str = crate::std140::$std_name::NAME; + } + )* + }; +} + +macro_rules! minty_impl { + ( $( $mint_ty:ty => $imp_ty:ty, )* ) => { + $( + impl crate::std140::AsStd140 for $imp_ty { + type Output = <$mint_ty as crate::std140::AsStd140>::Output; + + #[inline] + fn as_std140(&self) -> Self::Output { + let mint: $mint_ty = (*self).into(); + mint.as_std140() + } + + #[inline] + fn from_std140(value: Self::Output) -> Self { + <$mint_ty>::from_std140(value).into() + } + } + + impl crate::std430::AsStd430 for $imp_ty { + type Output = <$mint_ty as crate::std430::AsStd430>::Output; + + #[inline] + fn as_std430(&self) -> Self::Output { + let mint: $mint_ty = (*self).into(); + mint.as_std430() + } + + #[inline] + fn from_std430(value: Self::Output) -> Self { + <$mint_ty>::from_std430(value).into() + } + } + + unsafe impl crate::glsl::Glsl for $imp_ty { + const NAME: &'static str = <$mint_ty>::NAME; + } + )* + }; +} diff --git a/crates/crevice/tests/snapshots/test__generate_struct_array_glsl.snap b/crates/crevice/tests/snapshots/test__generate_struct_array_glsl.snap new file mode 100644 index 0000000000000..7829bd64ca141 --- /dev/null +++ b/crates/crevice/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/crates/crevice/tests/snapshots/test__generate_struct_glsl.snap b/crates/crevice/tests/snapshots/test__generate_struct_glsl.snap new file mode 100644 index 0000000000000..42fc1f4cd770e --- /dev/null +++ b/crates/crevice/tests/snapshots/test__generate_struct_glsl.snap @@ -0,0 +1,9 @@ +--- +source: tests/test.rs +expression: "TestGlsl::glsl_definition()" + +--- +struct TestGlsl { + vec3 foo; + mat2 bar; +}; diff --git a/crates/crevice/tests/test.rs b/crates/crevice/tests/test.rs new file mode 100644 index 0000000000000..f07786c827c63 --- /dev/null +++ b/crates/crevice/tests/test.rs @@ -0,0 +1,61 @@ +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()); +} + +#[test] +fn generate_struct_array_glsl() { + #[allow(dead_code)] + #[derive(GlslStruct)] + struct TestGlsl { + foo: [[mint::Vector3; 8]; 4], + } + + insta::assert_display_snapshot!(TestGlsl::glsl_definition()); +} diff --git a/pipelined/bevy_pbr2/Cargo.toml b/pipelined/bevy_pbr2/Cargo.toml index ea1a538c0095c..d4c238d6eed0c 100644 --- a/pipelined/bevy_pbr2/Cargo.toml +++ b/pipelined/bevy_pbr2/Cargo.toml @@ -29,5 +29,5 @@ bevy_utils = { path = "../../crates/bevy_utils", version = "0.5.0" } bitflags = "1.2" # direct dependency required for derive macro bytemuck = { version = "1", features = ["derive"] } -crevice = { path = "../../crates/crevice", version = "0.6.0" } +crevice = { path = "../../crates/crevice", version = "0.8.0", features = ["glam"] } wgpu = { version = "0.11.0", features = ["spirv"] } diff --git a/pipelined/bevy_render2/Cargo.toml b/pipelined/bevy_render2/Cargo.toml index 1544990fd2310..87b00c16afd20 100644 --- a/pipelined/bevy_render2/Cargo.toml +++ b/pipelined/bevy_render2/Cargo.toml @@ -40,10 +40,10 @@ thiserror = "1.0" futures-lite = "1.4.0" anyhow = "1.0" hex = "0.4.2" -hexasphere = "4.0" +hexasphere = "6.0.0" parking_lot = "0.11.0" regex = "1.5" -crevice = { path = "../../crates/crevice", version = "0.6.0" } +crevice = { path = "../../crates/crevice", version = "0.8.0", features = ["glam"] } [target.'cfg(target_arch = "wasm32")'.dependencies] wgpu = { version = "0.11.0", features = ["spirv", "webgl"] } diff --git a/pipelined/bevy_render2/src/render_resource/uniform_vec.rs b/pipelined/bevy_render2/src/render_resource/uniform_vec.rs index bfc50255203eb..73d9c2a535fca 100644 --- a/pipelined/bevy_render2/src/render_resource/uniform_vec.rs +++ b/pipelined/bevy_render2/src/render_resource/uniform_vec.rs @@ -21,8 +21,8 @@ impl Default for UniformVec { scratch: Vec::new(), uniform_buffer: None, capacity: 0, - item_size: (T::std140_size_static() + ::Std140Type::ALIGNMENT - 1) - & !(::Std140Type::ALIGNMENT - 1), + item_size: (T::std140_size_static() + ::Output::ALIGNMENT - 1) + & !(::Output::ALIGNMENT - 1), } } }