Migrate to encase from crevice (#4339)

# Objective

- Unify buffer APIs
- Also see #4272

## Solution

- Replace vendored `crevice` with `encase`

---

## Changelog

Changed `StorageBuffer`
Added `DynamicStorageBuffer`
Replaced `UniformVec` with `UniformBuffer`
Replaced `DynamicUniformVec` with `DynamicUniformBuffer`

## Migration Guide

### `StorageBuffer`

removed `set_body()`, `values()`, `values_mut()`, `clear()`, `push()`, `append()`
added `set()`, `get()`, `get_mut()`

### `UniformVec` -> `UniformBuffer`

renamed `uniform_buffer()` to `buffer()`
removed `len()`, `is_empty()`, `capacity()`, `push()`, `reserve()`, `clear()`, `values()`
added `set()`, `get()`

### `DynamicUniformVec` -> `DynamicUniformBuffer`

renamed `uniform_buffer()` to `buffer()`
removed `capacity()`, `reserve()`


Co-authored-by: Carter Anderson <mcanders1@gmail.com>
This commit is contained in:
Teodor Tanasoaia 2022-05-18 21:09:21 +00:00
parent 1320818f96
commit 7cb4d3cb43
57 changed files with 553 additions and 4287 deletions

View file

@ -1,36 +0,0 @@
[package]
name = "bevy_crevice"
description = "Create GLSL-compatible versions of structs with explicitly-initialized padding (Bevy version)"
version = "0.8.0-dev"
edition = "2021"
authors = ["Lucien Greathouse <me@lpghatguy.com>"]
documentation = "https://docs.rs/crevice"
homepage = "https://github.com/LPGhatguy/crevice"
repository = "https://github.com/bevyengine/bevy"
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
[features]
default = ["std"]
std = []
# [workspace]
# members = ["crevice-derive", "crevice-tests"]
# default-members = ["crevice-derive", "crevice-tests"]
[dependencies]
bevy-crevice-derive = { version = "0.8.0-dev", path = "bevy-crevice-derive" }
bytemuck = "1.4.1"
mint = "0.5.8"
cgmath = { version = "0.18.0", optional = true }
glam = { version = "0.20.0", features = ["mint"], optional = true }
nalgebra = { version = "0.30.0", features = ["mint"], optional = true }
[dev-dependencies]
insta = "0.16.1"

View file

@ -1,201 +0,0 @@
i Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View file

@ -1,19 +0,0 @@
Copyright (c) 2020 Lucien Greathouse
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -1,181 +0,0 @@
# Bevy Crevice
This is a fork of [Crevice](https://crates.io/crates/crevice) for
[Bevy](https://bevyengine.org).
For use outside of Bevy, you should consider
using [Crevice](https://crates.io/crates/crevice) directly.
It was forked to allow better integration in Bevy:
* Easier derive macro usage, without needing to depend on `Crevice` directly.
* Use of unmerged features (as of the fork), like
[Array Support](https://github.com/LPGhatguy/crevice/pull/27/).
* Renaming of traits and macros to better match Bevy API.
## Crevice
Crevice creates GLSL-compatible versions of types through the power of derive
macros. Generated structures provide an [`as_bytes`][std140::Std140::as_bytes]
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 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.20, 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`][std140::AsStd140] and
using [`as_std140`][std140::AsStd140::as_std140] and
[`as_bytes`][std140::Std140::as_bytes] to turn the result into bytes.
```glsl
uniform MAIN {
mat3 orientation;
vec3 position;
float scale;
} main;
```
```rust
use bevy_crevice::std140::{AsStd140, Std140};
#[derive(AsStd140)]
struct MainUniform {
orientation: mint::ColumnMatrix3<f32>,
position: mint::Vector3<f32>,
scale: f32,
}
let value = MainUniform {
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,
};
let value_std140 = value.as_std140();
upload_data_to_gpu(value_std140.as_bytes());
```
### Sequential Types
More complicated data can be uploaded using the std140
[`Writer`][std140::Writer] type.
```glsl
struct PointLight {
vec3 position;
vec3 color;
float brightness;
};
buffer POINT_LIGHTS {
uint len;
PointLight[] lights;
} point_lights;
```
```rust
use bevy_crevice::std140::{self, AsStd140};
#[derive(AsStd140)]
struct PointLight {
position: mint::Vector3<f32>,
color: mint::Vector3<f32>,
brightness: f32,
}
let lights = vec![
PointLight {
position: [0.0, 1.0, 0.0].into(),
color: [1.0, 0.0, 0.0].into(),
brightness: 0.6,
},
PointLight {
position: [0.0, 4.0, 3.0].into(),
color: [1.0, 1.0, 1.0].into(),
brightness: 1.0,
},
];
let target_buffer = map_gpu_buffer_for_write();
let mut writer = std140::Writer::new(target_buffer);
let light_count = lights.len() as u32;
writer.write(&light_count)?;
// Crevice will automatically insert the required padding to align the
// PointLight structure correctly. In this case, there will be 12 bytes of
// padding between the length field and the light list.
writer.write(lights.as_slice())?;
unmap_gpu_buffer();
```
## 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.52.1 and newer due to use of new `const fn` features.
[glsl-layout]: https://github.com/rustgd/glsl-layout
[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](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.

View file

@ -1,25 +0,0 @@
# Crevice
{{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
* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
* MIT license ([LICENSE-MIT](LICENSE-MIT) or 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.

View file

@ -1,27 +0,0 @@
[package]
name = "bevy-crevice-derive"
description = "Derive crate for the 'crevice' crate (Bevy version)"
version = "0.8.0-dev"
edition = "2018"
authors = ["Lucien Greathouse <me@lpghatguy.com>"]
documentation = "https://docs.rs/crevice-derive"
homepage = "https://github.com/LPGhatguy/crevice"
repository = "https://github.com/bevyengine/bevy"
license = "MIT OR Apache-2.0"
[features]
default = []
# Enable methods that let you introspect into the generated structs.
debug-methods = []
[lib]
proc-macro = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
syn = "1.0.40"
quote = "1.0.7"
proc-macro2 = "1.0.21"
bevy_macro_utils = { path = "../../bevy_macro_utils", version = "0.8.0-dev" }

View file

@ -1,48 +0,0 @@
use bevy_macro_utils::get_named_struct_fields;
use proc_macro2::{Literal, TokenStream};
use quote::quote;
use syn::{parse_quote, DeriveInput, Path};
pub fn emit(input: DeriveInput) -> TokenStream {
let bevy_crevice_path = crate::bevy_crevice_path();
let fields = match get_named_struct_fields(&input.data) {
Ok(fields) => fields,
Err(e) => return e.into_compile_error(),
};
let base_trait_path: Path = parse_quote!(#bevy_crevice_path::glsl::Glsl);
let struct_trait_path: Path = parse_quote!(#bevy_crevice_path::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 #bevy_crevice_path::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 #bevy_crevice_path::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 )*
}
}
}
}

View file

@ -1,287 +0,0 @@
use bevy_macro_utils::get_named_struct_fields;
use proc_macro2::{Span, TokenStream};
use quote::{format_ident, quote};
use syn::{parse_quote, DeriveInput, Ident, Path, Type};
pub fn emit(
input: DeriveInput,
trait_name: &'static str,
mod_name: &'static str,
min_struct_alignment: usize,
) -> TokenStream {
let bevy_crevice_path = crate::bevy_crevice_path();
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!(#bevy_crevice_path::#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 get_named_struct_fields(&input.data) {
Ok(fields) => fields.named.iter().collect(),
Err(e) => return e.into_compile_error(),
};
// 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! {
#bevy_crevice_path::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::<TokenStream>()
};
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 = #bevy_crevice_path::internal::max(
#next_field_or_self_alignment,
min_alignment,
);
// Using everything we've got, compute our padding amount.
#bevy_crevice_path::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::<Self>();
let align = <Self as #trait_path>::ALIGNMENT;
let zeroed: Self = #bevy_crevice_path::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 #bevy_crevice_path::internal::bytemuck::Zeroable for #generated_name #ty_generics #where_clause {}
unsafe impl #impl_generics #bevy_crevice_path::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<Self, {#bevy_crevice_path::internal::align_offset(
::core::mem::size_of::<#generated_name>(),
#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
..#bevy_crevice_path::internal::bytemuck::Zeroable::zeroed()
}
}
fn #from_trait_method(input: Self::Output) -> Self {
Self {
#input_struct_field_init
}
}
}
#debug_methods
}
}

View file

@ -1,59 +0,0 @@
mod glsl;
mod layout;
use bevy_macro_utils::BevyManifest;
use proc_macro::TokenStream as CompilerTokenStream;
use syn::{parse_macro_input, DeriveInput, Path};
#[proc_macro_derive(AsStd140)]
pub fn derive_as_std140(input: CompilerTokenStream) -> CompilerTokenStream {
let input = parse_macro_input!(input as DeriveInput);
let expanded = layout::emit(input, "Std140", "std140", 16);
CompilerTokenStream::from(expanded)
}
#[proc_macro_derive(AsStd430)]
pub fn derive_as_std430(input: CompilerTokenStream) -> CompilerTokenStream {
let input = parse_macro_input!(input as DeriveInput);
let expanded = layout::emit(input, "Std430", "std430", 0);
CompilerTokenStream::from(expanded)
}
#[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);
CompilerTokenStream::from(expanded)
}
const BEVY: &str = "bevy";
const BEVY_CREVICE: &str = "bevy_crevice";
const BEVY_RENDER: &str = "bevy_render";
fn bevy_crevice_path() -> Path {
let bevy_manifest = BevyManifest::default();
bevy_manifest
.maybe_get_path(crate::BEVY)
.map(|bevy_path| {
let mut segments = bevy_path.segments;
segments.push(BevyManifest::parse_str("render"));
Path {
leading_colon: None,
segments,
}
})
.or_else(|| bevy_manifest.maybe_get_path(crate::BEVY_RENDER))
.map(|bevy_render_path| {
let mut segments = bevy_render_path.segments;
segments.push(BevyManifest::parse_str("render_resource"));
Path {
leading_colon: None,
segments,
}
})
.unwrap_or_else(|| bevy_manifest.get_path(crate::BEVY_CREVICE))
}

View file

@ -1,20 +0,0 @@
[package]
name = "crevice-tests"
version = "0.1.0"
edition = "2018"
[features]
wgpu-validation = ["wgpu", "naga", "futures"]
[dependencies]
bevy_crevice = { path = ".." }
bevy-crevice-derive = { path = "../bevy-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.8.0", features = ["glsl-in", "wgsl-out"], optional = true }
wgpu = { version = "0.12.0", optional = true }

View file

@ -1,268 +0,0 @@
use std::borrow::Cow;
use std::fmt::Debug;
use std::marker::PhantomData;
use bevy_crevice::glsl::{Glsl, GlslStruct};
use bevy_crevice::std140::{AsStd140, Std140};
use bevy_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<T: Debug + PartialEq + AsStd140 + AsStd430 + GlslStruct>(value: T) {
let shader_std140 = glsl_shader_for_struct::<T>("std140");
let shader_std430 = glsl_shader_for_struct::<T>("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<T: Debug + PartialEq + AsStd140 + AsStd430 + Glsl>(value: T) {
let shader_std140 = glsl_shader_for_primitive::<T>("std140");
let shader_std430 = glsl_shader_for_primitive::<T>("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<T: GlslStruct>(layout: &str) -> String {
BASE_SHADER
.replace("{struct_name}", T::NAME)
.replace("{struct_definition}", &T::glsl_definition())
.replace("{layout}", layout)
}
fn glsl_shader_for_primitive<T: Glsl>(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<T> {
device: wgpu::Device,
queue: wgpu::Queue,
_phantom: PhantomData<*const T>,
}
impl<T> Context<T>
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::<<T as AsStd140>::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::<<T as AsStd430>::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<u8> {
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<String> {
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)
}

View file

@ -1,366 +0,0 @@
#![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<T>(_value: T) {}
#[cfg(not(feature = "wgpu-validation"))]
fn test_round_trip_primitive<T>(_value: T) {}
#[macro_use]
mod util;
use bevy_crevice::glsl::GlslStruct;
use bevy_crevice::std140::AsStd140;
use bevy_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<f32>,
}
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<f32>;
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<f32>;
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<f32>;
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<f32>,
}
// 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<f64>,
}
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<f32>,
two: Vector3<f32>,
}
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<f32>,
two: Vector4<f32>,
}
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<f32>,
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<f32>,
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<f32>; 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(),
],
})
}

View file

@ -1,143 +0,0 @@
#[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::<Target>();
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 = <Target as crevice::std140::Std140>::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::<Target>();
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 = <Target as crevice::std430::Std430>::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));
}
}};
}

View file

@ -1,93 +0,0 @@
//! Defines traits and types for generating GLSL code from Rust definitions.
pub use bevy_crevice_derive::GlslStruct;
use std::marker::PhantomData;
/// Type-level linked list of array dimensions
pub struct Dimension<A, const N: usize> {
_marker: PhantomData<A>,
}
/// 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<A: DimensionList, const N: usize> DimensionList for Dimension<A, N> {
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<T: Glsl> GlslArray for T {
const NAME: &'static str = <T as Glsl>::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<T: GlslArray, const N: usize> GlslArray for [T; N] {
const NAME: &'static str = T::NAME;
type ArraySize = Dimension<T::ArraySize, N>;
}

View file

@ -1,10 +0,0 @@
mod imp_mint;
#[cfg(feature = "cgmath")]
mod imp_cgmath;
#[cfg(feature = "glam")]
mod imp_glam;
#[cfg(feature = "nalgebra")]
mod imp_nalgebra;

View file

@ -1,30 +0,0 @@
easy_impl! {
Vec2 cgmath::Vector2<f32> { x, y },
Vec3 cgmath::Vector3<f32> { x, y, z },
Vec4 cgmath::Vector4<f32> { x, y, z, w },
IVec2 cgmath::Vector2<i32> { x, y },
IVec3 cgmath::Vector3<i32> { x, y, z },
IVec4 cgmath::Vector4<i32> { x, y, z, w },
UVec2 cgmath::Vector2<u32> { x, y },
UVec3 cgmath::Vector3<u32> { x, y, z },
UVec4 cgmath::Vector4<u32> { x, y, z, w },
// bool vectors are disabled due to https://github.com/LPGhatguy/crevice/issues/36
// BVec2 cgmath::Vector2<bool> { x, y },
// BVec3 cgmath::Vector3<bool> { x, y, z },
// BVec4 cgmath::Vector4<bool> { x, y, z, w },
DVec2 cgmath::Vector2<f64> { x, y },
DVec3 cgmath::Vector3<f64> { x, y, z },
DVec4 cgmath::Vector4<f64> { x, y, z, w },
Mat2 cgmath::Matrix2<f32> { x, y },
Mat3 cgmath::Matrix3<f32> { x, y, z },
Mat4 cgmath::Matrix4<f32> { x, y, z, w },
DMat2 cgmath::Matrix2<f64> { x, y },
DMat3 cgmath::Matrix3<f64> { x, y, z },
DMat4 cgmath::Matrix4<f64> { x, y, z, w },
}

View file

@ -1,24 +0,0 @@
minty_impl! {
mint::Vector2<f32> => glam::Vec2,
mint::Vector3<f32> => glam::Vec3,
mint::Vector4<f32> => glam::Vec4,
mint::Vector2<i32> => glam::IVec2,
mint::Vector3<i32> => glam::IVec3,
mint::Vector4<i32> => glam::IVec4,
mint::Vector2<u32> => glam::UVec2,
mint::Vector3<u32> => glam::UVec3,
mint::Vector4<u32> => glam::UVec4,
// bool vectors are disabled due to https://github.com/LPGhatguy/crevice/issues/36
// mint::Vector2<bool> => glam::BVec2,
// mint::Vector3<bool> => glam::BVec3,
// mint::Vector4<bool> => glam::BVec4,
mint::Vector2<f64> => glam::DVec2,
mint::Vector3<f64> => glam::DVec3,
mint::Vector4<f64> => glam::DVec4,
mint::ColumnMatrix2<f32> => glam::Mat2,
mint::ColumnMatrix3<f32> => glam::Mat3,
mint::ColumnMatrix4<f32> => glam::Mat4,
mint::ColumnMatrix2<f64> => glam::DMat2,
mint::ColumnMatrix3<f64> => glam::DMat3,
mint::ColumnMatrix4<f64> => glam::DMat4,
}

View file

@ -1,30 +0,0 @@
easy_impl! {
Vec2 mint::Vector2<f32> { x, y },
Vec3 mint::Vector3<f32> { x, y, z },
Vec4 mint::Vector4<f32> { x, y, z, w },
IVec2 mint::Vector2<i32> { x, y },
IVec3 mint::Vector3<i32> { x, y, z },
IVec4 mint::Vector4<i32> { x, y, z, w },
UVec2 mint::Vector2<u32> { x, y },
UVec3 mint::Vector3<u32> { x, y, z },
UVec4 mint::Vector4<u32> { x, y, z, w },
// bool vectors are disabled due to https://github.com/LPGhatguy/crevice/issues/36
// BVec2 mint::Vector2<bool> { x, y },
// BVec3 mint::Vector3<bool> { x, y, z },
// BVec4 mint::Vector4<bool> { x, y, z, w },
DVec2 mint::Vector2<f64> { x, y },
DVec3 mint::Vector3<f64> { x, y, z },
DVec4 mint::Vector4<f64> { x, y, z, w },
Mat2 mint::ColumnMatrix2<f32> { x, y },
Mat3 mint::ColumnMatrix3<f32> { x, y, z },
Mat4 mint::ColumnMatrix4<f32> { x, y, z, w },
DMat2 mint::ColumnMatrix2<f64> { x, y },
DMat3 mint::ColumnMatrix3<f64> { x, y, z },
DMat4 mint::ColumnMatrix4<f64> { x, y, z, w },
}

View file

@ -1,24 +0,0 @@
minty_impl! {
mint::Vector2<f32> => nalgebra::Vector2<f32>,
mint::Vector3<f32> => nalgebra::Vector3<f32>,
mint::Vector4<f32> => nalgebra::Vector4<f32>,
mint::Vector2<i32> => nalgebra::Vector2<i32>,
mint::Vector3<i32> => nalgebra::Vector3<i32>,
mint::Vector4<i32> => nalgebra::Vector4<i32>,
mint::Vector2<u32> => nalgebra::Vector2<u32>,
mint::Vector3<u32> => nalgebra::Vector3<u32>,
mint::Vector4<u32> => nalgebra::Vector4<u32>,
// bool vectors are disabled due to https://github.com/LPGhatguy/crevice/issues/36
// mint::Vector2<bool> => nalgebra::Vector2<bool>,
// mint::Vector3<bool> => nalgebra::Vector3<bool>,
// mint::Vector4<bool> => nalgebra::Vector4<bool>,
mint::Vector2<f64> => nalgebra::Vector2<f64>,
mint::Vector3<f64> => nalgebra::Vector3<f64>,
mint::Vector4<f64> => nalgebra::Vector4<f64>,
mint::ColumnMatrix2<f32> => nalgebra::Matrix2<f32>,
mint::ColumnMatrix3<f32> => nalgebra::Matrix3<f32>,
mint::ColumnMatrix4<f32> => nalgebra::Matrix4<f32>,
mint::ColumnMatrix2<f64> => nalgebra::Matrix2<f64>,
mint::ColumnMatrix3<f64> => nalgebra::Matrix3<f64>,
mint::ColumnMatrix4<f64> => nalgebra::Matrix4<f64>,
}

View file

@ -1,40 +0,0 @@
//! This module is internal to crevice but used by its derive macro. No
//! guarantees are made about its contents.
pub use bytemuck;
/// Gives the number of bytes needed to make `offset` be aligned to `alignment`.
pub const fn align_offset(offset: usize, alignment: usize) -> usize {
if alignment == 0 || offset % alignment == 0 {
0
} else {
alignment - offset % alignment
}
}
/// Max of two `usize`. Implemented because the `max` method from `Ord` cannot
/// be used in const fns.
pub const fn max(a: usize, b: usize) -> usize {
if a > b {
a
} else {
b
}
}
/// Max of an array of `usize`. This function's implementation is funky because
/// we have no for loops!
pub const fn max_arr<const N: usize>(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
}

View file

@ -1,174 +0,0 @@
#![allow(
clippy::new_without_default,
clippy::needless_update,
clippy::len_without_is_empty,
clippy::needless_range_loop,
clippy::all,
clippy::doc_markdown
)]
/*!
[![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)
[![crevice docs](https://img.shields.io/badge/docs-docs.rs-orange.svg)](https://docs.rs/crevice)
Crevice creates GLSL-compatible versions of types through the power of derive
macros. Generated structures provide an [`as_bytes`][std140::Std140::as_bytes]
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 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.
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`][std140::AsStd140] and
using [`as_std140`][std140::AsStd140::as_std140] and
[`as_bytes`][std140::Std140::as_bytes] to turn the result into bytes.
```glsl
uniform MAIN {
mat3 orientation;
vec3 position;
float scale;
} main;
```
```rust
use bevy_crevice::std140::{AsStd140, Std140};
#[derive(AsStd140)]
struct MainUniform {
orientation: mint::ColumnMatrix3<f32>,
position: mint::Vector3<f32>,
scale: f32,
}
let value = MainUniform {
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,
};
let value_std140 = value.as_std140();
# fn upload_data_to_gpu(_value: &[u8]) {}
upload_data_to_gpu(value_std140.as_bytes());
```
### Sequential Types
More complicated data can be uploaded using the std140
[`Writer`][std140::Writer] type.
```glsl
struct PointLight {
vec3 position;
vec3 color;
float brightness;
};
buffer POINT_LIGHTS {
uint len;
PointLight[] lights;
} point_lights;
```
```rust
use bevy_crevice::std140::{self, AsStd140};
#[derive(AsStd140)]
struct PointLight {
position: mint::Vector3<f32>,
color: mint::Vector3<f32>,
brightness: f32,
}
let lights = vec![
PointLight {
position: [0.0, 1.0, 0.0].into(),
color: [1.0, 0.0, 0.0].into(),
brightness: 0.6,
},
PointLight {
position: [0.0, 4.0, 3.0].into(),
color: [1.0, 1.0, 1.0].into(),
brightness: 1.0,
},
];
# fn map_gpu_buffer_for_write() -> &'static mut [u8] {
# Box::leak(vec![0; 1024].into_boxed_slice())
# }
let target_buffer = map_gpu_buffer_for_write();
let mut writer = std140::Writer::new(target_buffer);
let light_count = lights.len() as u32;
writer.write(&light_count)?;
// Crevice will automatically insert the required padding to align the
// PointLight structure correctly. In this case, there will be 12 bytes of
// padding between the length field and the light list.
writer.write(lights.as_slice())?;
# fn unmap_gpu_buffer() {}
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.52.1 and newer due to use of new `const fn` features.
[glsl-layout]: https://github.com/rustgd/glsl-layout
*/
#![deny(missing_docs)]
#![cfg_attr(not(feature = "std"), no_std)]
#[macro_use]
mod util;
pub mod glsl;
pub mod std140;
pub mod std430;
#[doc(hidden)]
pub mod internal;
mod imp;

View file

@ -1,18 +0,0 @@
//! Defines traits and types for working with data adhering to GLSL's `std140`
//! layout specification.
mod dynamic_uniform;
mod primitives;
mod sizer;
mod traits;
#[cfg(feature = "std")]
mod writer;
pub use self::dynamic_uniform::*;
pub use self::primitives::*;
pub use self::sizer::*;
pub use self::traits::*;
#[cfg(feature = "std")]
pub use self::writer::*;
pub use bevy_crevice_derive::AsStd140;

View file

@ -1,68 +0,0 @@
use bytemuck::{Pod, Zeroable};
#[allow(unused_imports)]
use crate::internal::{align_offset, max};
use crate::std140::{AsStd140, Std140};
/// Wrapper type that aligns the inner type to at least 256 bytes.
///
/// This type is useful for ensuring correct alignment when creating dynamic
/// uniform buffers in APIs like WebGPU.
pub struct DynamicUniform<T>(pub T);
impl<T: AsStd140> AsStd140 for DynamicUniform<T> {
type Output = DynamicUniformStd140<<T as AsStd140>::Output>;
fn as_std140(&self) -> Self::Output {
DynamicUniformStd140(self.0.as_std140())
}
fn from_std140(value: Self::Output) -> Self {
DynamicUniform(<T as AsStd140>::from_std140(value.0))
}
}
/// std140 variant of [`DynamicUniform`].
#[derive(Clone, Copy)]
#[repr(transparent)]
pub struct DynamicUniformStd140<T>(T);
unsafe impl<T: Std140> Std140 for DynamicUniformStd140<T> {
const ALIGNMENT: usize = max(256, T::ALIGNMENT);
#[cfg(const_evaluatable_checked)]
type Padded = crate::std140::Std140Padded<
Self,
{ align_offset(core::mem::size_of::<T>(), max(256, T::ALIGNMENT)) },
>;
#[cfg(not(const_evaluatable_checked))]
type Padded = crate::std140::InvalidPadded;
}
unsafe impl<T: Zeroable> Zeroable for DynamicUniformStd140<T> {}
unsafe impl<T: Pod> Pod for DynamicUniformStd140<T> {}
#[cfg(test)]
mod test {
use super::*;
use crate::std140::{self, WriteStd140};
#[test]
fn size_is_unchanged() {
let dynamic_f32 = DynamicUniform(0.0f32);
assert_eq!(dynamic_f32.std140_size(), 0.0f32.std140_size());
}
#[test]
fn alignment_applies() {
let mut output = Vec::new();
let mut writer = std140::Writer::new(&mut output);
writer.write(&DynamicUniform(0.0f32)).unwrap();
assert_eq!(writer.len(), 4);
writer.write(&DynamicUniform(1.0f32)).unwrap();
assert_eq!(writer.len(), 260);
}
}

View file

@ -1,175 +0,0 @@
use bytemuck::{Pod, Zeroable};
use crate::glsl::Glsl;
use crate::std140::{Std140, Std140Padded};
use crate::internal::{align_offset, max};
use core::mem::size_of;
unsafe impl Std140 for f32 {
const ALIGNMENT: usize = 4;
type Padded = Std140Padded<Self, 12>;
}
unsafe impl Std140 for f64 {
const ALIGNMENT: usize = 8;
type Padded = Std140Padded<Self, 8>;
}
unsafe impl Std140 for i32 {
const ALIGNMENT: usize = 4;
type Padded = Std140Padded<Self, 12>;
}
unsafe impl Std140 for u32 {
const ALIGNMENT: usize = 4;
type Padded = Std140Padded<Self, 12>;
}
macro_rules! vectors {
(
$(
#[$doc:meta] align($align:literal) $glsl_name:ident $name:ident <$prim:ident> ($($field:ident),+)
)+
) => {
$(
#[$doc]
#[allow(missing_docs)]
#[derive(Debug, Clone, Copy, PartialEq)]
#[repr(C)]
pub struct $name {
$(pub $field: $prim,)+
}
unsafe impl Zeroable for $name {}
unsafe impl Pod for $name {}
unsafe impl Std140 for $name {
const ALIGNMENT: usize = $align;
type Padded = Std140Padded<Self, {align_offset(size_of::<$name>(), 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 Vec2<f32>(x, y)
#[doc = "Corresponds to a GLSL `vec3` in std140 layout."] align(16) vec3 Vec3<f32>(x, y, z)
#[doc = "Corresponds to a GLSL `vec4` in std140 layout."] align(16) vec4 Vec4<f32>(x, y, z, w)
#[doc = "Corresponds to a GLSL `ivec2` in std140 layout."] align(8) ivec2 IVec2<i32>(x, y)
#[doc = "Corresponds to a GLSL `ivec3` in std140 layout."] align(16) ivec3 IVec3<i32>(x, y, z)
#[doc = "Corresponds to a GLSL `ivec4` in std140 layout."] align(16) ivec4 IVec4<i32>(x, y, z, w)
#[doc = "Corresponds to a GLSL `uvec2` in std140 layout."] align(8) uvec2 UVec2<u32>(x, y)
#[doc = "Corresponds to a GLSL `uvec3` in std140 layout."] align(16) uvec3 UVec3<u32>(x, y, z)
#[doc = "Corresponds to a GLSL `uvec4` in std140 layout."] align(16) uvec4 UVec4<u32>(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 BVec2<bool>(x, y)
// #[doc = "Corresponds to a GLSL `bvec3` in std140 layout."] align(16) bvec3 BVec3<bool>(x, y, z)
// #[doc = "Corresponds to a GLSL `bvec4` in std140 layout."] align(16) bvec4 BVec4<bool>(x, y, z, w)
#[doc = "Corresponds to a GLSL `dvec2` in std140 layout."] align(16) dvec2 DVec2<f64>(x, y)
#[doc = "Corresponds to a GLSL `dvec3` in std140 layout."] align(32) dvec3 DVec3<f64>(x, y, z)
#[doc = "Corresponds to a GLSL `dvec4` in std140 layout."] align(32) dvec4 DVec4<f64>(x, y, z, w)
}
macro_rules! matrices {
(
$(
#[$doc:meta]
align($align:literal)
$glsl_name:ident $name:ident {
$($field:ident: $field_ty:ty,)+
}
)+
) => {
$(
#[$doc]
#[allow(missing_docs)]
#[derive(Debug, Clone, Copy)]
#[repr(C)]
pub struct $name {
$(pub $field: $field_ty,)+
}
unsafe impl Zeroable for $name {}
unsafe impl Pod for $name {}
unsafe impl Std140 for $name {
const ALIGNMENT: usize = $align;
/// Matrices are technically arrays of primitives, and as such require pad at end.
const PAD_AT_END: bool = true;
type Padded = Std140Padded<Self, {align_offset(size_of::<$name>(), max(16, $align))}>;
}
unsafe impl Glsl for $name {
const NAME: &'static str = stringify!($glsl_name);
}
)+
};
}
matrices! {
#[doc = "Corresponds to a GLSL `mat2` in std140 layout."]
align(16)
mat2 Mat2 {
x: Vec2,
_pad_x: [f32; 2],
y: Vec2,
_pad_y: [f32; 2],
}
#[doc = "Corresponds to a GLSL `mat3` in std140 layout."]
align(16)
mat3 Mat3 {
x: Vec3,
_pad_x: f32,
y: Vec3,
_pad_y: f32,
z: Vec3,
_pad_z: f32,
}
#[doc = "Corresponds to a GLSL `mat4` in std140 layout."]
align(16)
mat4 Mat4 {
x: Vec4,
y: Vec4,
z: Vec4,
w: Vec4,
}
#[doc = "Corresponds to a GLSL `dmat2` in std140 layout."]
align(16)
dmat2 DMat2 {
x: DVec2,
y: DVec2,
}
#[doc = "Corresponds to a GLSL `dmat3` in std140 layout."]
align(32)
dmat3 DMat3 {
x: DVec3,
_pad_x: f64,
y: DVec3,
_pad_y: f64,
z: DVec3,
_pad_z: f64,
}
#[doc = "Corresponds to a GLSL `dmat3` in std140 layout."]
align(32)
dmat4 DMat4 {
x: DVec4,
y: DVec4,
z: DVec4,
w: DVec4,
}
}

View file

@ -1,81 +0,0 @@
use core::mem::size_of;
use crate::internal::align_offset;
use crate::std140::{AsStd140, Std140};
/**
Type that computes the buffer size needed by a series of `std140` types laid
out.
This type works well well when paired with `Writer`, precomputing a buffer's
size to alleviate the need to dynamically re-allocate buffers.
## Example
```glsl
struct Frob {
vec3 size;
float frobiness;
}
buffer FROBS {
uint len;
Frob[] frobs;
} frobs;
```
```
use bevy_crevice::std140::{self, AsStd140};
#[derive(AsStd140)]
struct Frob {
size: mint::Vector3<f32>,
frobiness: f32,
}
// Many APIs require that buffers contain at least enough space for all
// fixed-size bindiongs to a buffer as well as one element of any arrays, if
// there are any.
let mut sizer = std140::Sizer::new();
sizer.add::<u32>();
sizer.add::<Frob>();
# fn create_buffer_with_size(size: usize) {}
let buffer = create_buffer_with_size(sizer.len());
# assert_eq!(sizer.len(), 32);
```
*/
pub struct Sizer {
offset: usize,
}
impl Sizer {
/// Create a new `Sizer`.
pub fn new() -> Self {
Self { offset: 0 }
}
/// Add a type's necessary padding and size to the `Sizer`. Returns the
/// offset into the buffer where that type would be written.
pub fn add<T>(&mut self) -> usize
where
T: AsStd140,
{
let size = size_of::<<T as AsStd140>::Output>();
let alignment = <T as AsStd140>::Output::ALIGNMENT;
let padding = align_offset(self.offset, alignment);
self.offset += padding;
let write_here = self.offset;
self.offset += size;
write_here
}
/// Returns the number of bytes required to contain all the types added to
/// the `Sizer`.
pub fn len(&self) -> usize {
self.offset
}
}

View file

@ -1,284 +0,0 @@
use core::mem::{size_of, MaybeUninit};
#[cfg(feature = "std")]
use std::io::{self, Write};
use bytemuck::{bytes_of, Pod, Zeroable};
#[cfg(feature = "std")]
use crate::std140::Writer;
/// Trait implemented for all `std140` primitives. Generally should not be
/// implemented outside this crate.
pub unsafe trait Std140: Copy + Zeroable + Pod {
/// The required alignment of the type. Must be a power of two.
///
/// This is distinct from the value returned by `std::mem::align_of` because
/// `AsStd140` structs do not use Rust's alignment. This enables them to
/// 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>
/// (rule 4 and 9)
const PAD_AT_END: bool = false;
/// Padded type (Std140Padded specialization)
/// The usual implementation is
/// type Padded = Std140Padded<Self, {align_offset(size_of::<Self>(), max(16, ALIGNMENT))}>;
type Padded: Std140Convertible<Self>;
/// Casts the type to a byte array. Implementors should not override this
/// method.
///
/// # Safety
/// This is always safe due to the requirements of [`bytemuck::Pod`] being a
/// prerequisite for this trait.
fn as_bytes(&self) -> &[u8] {
bytes_of(self)
}
}
/// Trait specifically for Std140::Padded, implements conversions between padded type and base type.
pub trait Std140Convertible<T: Std140>: Copy {
/// Convert from self to Std140
fn into_std140(self) -> T;
/// Convert from Std140 to self
fn from_std140(_: T) -> Self;
}
impl<T: Std140> Std140Convertible<T> for T {
fn into_std140(self) -> T {
self
}
fn from_std140(also_self: T) -> Self {
also_self
}
}
/// Unfortunately, we cannot easily derive padded representation for generic Std140 types.
/// For now, we'll just use this empty enum with no values.
#[derive(Copy, Clone)]
pub enum InvalidPadded {}
impl<T: Std140> Std140Convertible<T> for InvalidPadded {
fn into_std140(self) -> T {
unimplemented!()
}
fn from_std140(_: T) -> Self {
unimplemented!()
}
}
/**
Trait implemented for all types that can be turned into `std140` values.
*
This trait can often be `#[derive]`'d instead of manually implementing it. Any
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 math crates, like cgmath, nalgebra, and
ultraviolet support mint.
## Example
```glsl
uniform CAMERA {
mat4 view;
mat4 projection;
} camera;
```
```no_run
use bevy_crevice::std140::{AsStd140, Std140};
#[derive(AsStd140)]
struct CameraUniform {
view: mint::ColumnMatrix4<f32>,
projection: mint::ColumnMatrix4<f32>,
}
let view: mint::ColumnMatrix4<f32> = todo!("your math code here");
let projection: mint::ColumnMatrix4<f32> = todo!("your math code here");
let camera = CameraUniform {
view,
projection,
};
# fn write_to_gpu_buffer(bytes: &[u8]) {}
let camera_std140 = camera.as_std140();
write_to_gpu_buffer(camera_std140.as_bytes());
```
*/
pub trait AsStd140 {
/// The `std140` version of this value.
type Output: Std140;
/// Convert this value into the `std140` version of itself.
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::<Self::Output>()
}
/// Converts from `std140` version of self to self.
fn from_std140(val: Self::Output) -> Self;
}
impl<T> AsStd140 for T
where
T: Std140,
{
type Output = Self;
fn as_std140(&self) -> Self {
*self
}
fn from_std140(x: Self) -> Self {
x
}
}
#[doc(hidden)]
#[derive(Copy, Clone, Debug)]
pub struct Std140Padded<T: Std140, const PAD: usize> {
inner: T,
_padding: [u8; PAD],
}
unsafe impl<T: Std140, const PAD: usize> Zeroable for Std140Padded<T, PAD> {}
unsafe impl<T: Std140, const PAD: usize> Pod for Std140Padded<T, PAD> {}
impl<T: Std140, const PAD: usize> Std140Convertible<T> for Std140Padded<T, PAD> {
fn into_std140(self) -> T {
self.inner
}
fn from_std140(inner: T) -> Self {
Self {
inner,
_padding: [0u8; PAD],
}
}
}
#[doc(hidden)]
#[derive(Copy, Clone, Debug)]
#[repr(transparent)]
pub struct Std140Array<T: Std140, const N: usize>([T::Padded; N]);
unsafe impl<T: Std140, const N: usize> Zeroable for Std140Array<T, N> where T::Padded: Zeroable {}
unsafe impl<T: Std140, const N: usize> Pod for Std140Array<T, N> where T::Padded: Pod {}
unsafe impl<T: Std140, const N: usize> Std140 for Std140Array<T, N>
where
T::Padded: Pod,
{
const ALIGNMENT: usize = crate::internal::max(T::ALIGNMENT, 16);
type Padded = Self;
}
impl<T: Std140, const N: usize> Std140Array<T, N> {
fn uninit_array() -> [MaybeUninit<T::Padded>; N] {
unsafe { MaybeUninit::uninit().assume_init() }
}
fn from_uninit_array(a: [MaybeUninit<T::Padded>; N]) -> Self {
unsafe { core::mem::transmute_copy(&a) }
}
}
impl<T: AsStd140, const N: usize> AsStd140 for [T; N]
where
<T::Output as Std140>::Padded: Pod,
{
type Output = Std140Array<T::Output, N>;
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()));
}
Self::Output::from_uninit_array(res)
}
fn from_std140(val: Self::Output) -> Self {
let mut res: [MaybeUninit<T>; N] = unsafe { MaybeUninit::uninit().assume_init() };
for i in 0..N {
res[i] = MaybeUninit::new(T::from_std140(Std140Convertible::into_std140(val.0[i])));
}
unsafe { core::mem::transmute_copy(&res) }
}
}
/// Trait implemented for all types that can be written into a buffer as
/// `std140` bytes. This type is more general than [`AsStd140`]: all `AsStd140`
/// types implement `WriteStd140`, but not the other way around.
///
/// While `AsStd140` requires implementers to return a type that implements the
/// `Std140` trait, `WriteStd140` directly writes bytes using a [`Writer`]. This
/// makes `WriteStd140` usable for writing slices or other DSTs that could not
/// implement `AsStd140` without allocating new memory on the heap.
#[cfg(feature = "std")]
pub trait WriteStd140 {
/// Writes this value into the given [`Writer`] using `std140` layout rules.
///
/// Should return the offset of the first byte of this type, as returned by
/// the first call to [`Writer::write`].
fn write_std140<W: Write>(&self, writer: &mut Writer<W>) -> io::Result<usize>;
/// The space required to write this value using `std140` layout rules. This
/// does not include alignment padding that may be needed before or after
/// this type when written as part of a larger buffer.
fn std140_size(&self) -> usize {
let mut writer = Writer::new(io::sink());
self.write_std140(&mut writer).unwrap();
writer.len()
}
}
#[cfg(feature = "std")]
impl<T> WriteStd140 for T
where
T: AsStd140,
{
fn write_std140<W: Write>(&self, writer: &mut Writer<W>) -> io::Result<usize> {
writer.write_std140(&self.as_std140())
}
fn std140_size(&self) -> usize {
size_of::<<Self as AsStd140>::Output>()
}
}
#[cfg(feature = "std")]
impl<T> WriteStd140 for [T]
where
T: WriteStd140,
{
fn write_std140<W: Write>(&self, writer: &mut Writer<W>) -> io::Result<usize> {
// if no items are written, offset is current position of the writer
let mut offset = writer.len();
let mut iter = self.iter();
if let Some(item) = iter.next() {
offset = item.write_std140(writer)?;
}
for item in iter {
item.write_std140(writer)?;
}
Ok(offset)
}
fn std140_size(&self) -> usize {
let mut writer = Writer::new(io::sink());
self.write_std140(&mut writer).unwrap();
writer.len()
}
}

View file

@ -1,162 +0,0 @@
use std::io::{self, Write};
use std::mem::size_of;
use bytemuck::bytes_of;
use crate::internal::align_offset;
use crate::std140::{AsStd140, Std140, WriteStd140};
/**
Type that enables writing correctly aligned `std140` values to a buffer.
`Writer` is useful when many values need to be laid out in a row that cannot be
represented by a struct alone, like dynamically sized arrays or dynamically
laid-out values.
## Example
In this example, we'll write a length-prefixed list of lights to a buffer.
`std140::Writer` helps align correctly, even across multiple structs, which can
be tricky and error-prone otherwise.
```glsl
struct PointLight {
vec3 position;
vec3 color;
float brightness;
};
buffer POINT_LIGHTS {
uint len;
PointLight[] lights;
} point_lights;
```
```
use bevy_crevice::std140::{self, AsStd140};
#[derive(AsStd140)]
struct PointLight {
position: mint::Vector3<f32>,
color: mint::Vector3<f32>,
brightness: f32,
}
let lights = vec![
PointLight {
position: [0.0, 1.0, 0.0].into(),
color: [1.0, 0.0, 0.0].into(),
brightness: 0.6,
},
PointLight {
position: [0.0, 4.0, 3.0].into(),
color: [1.0, 1.0, 1.0].into(),
brightness: 1.0,
},
];
# fn map_gpu_buffer_for_write() -> &'static mut [u8] {
# Box::leak(vec![0; 1024].into_boxed_slice())
# }
let target_buffer = map_gpu_buffer_for_write();
let mut writer = std140::Writer::new(target_buffer);
let light_count = lights.len() as u32;
writer.write(&light_count)?;
// Crevice will automatically insert the required padding to align the
// PointLight structure correctly. In this case, there will be 12 bytes of
// padding between the length field and the light list.
writer.write(lights.as_slice())?;
# fn unmap_gpu_buffer() {}
unmap_gpu_buffer();
# Ok::<(), std::io::Error>(())
```
*/
pub struct Writer<W> {
writer: W,
offset: usize,
}
impl<W: Write> Writer<W> {
/// Create a new `Writer`, wrapping a buffer, file, or other type that
/// implements [`std::io::Write`].
pub fn new(writer: W) -> Self {
Self { writer, offset: 0 }
}
/// Write a new value to the underlying buffer, writing zeroed padding where
/// necessary.
///
/// Returns the offset into the buffer that the value was written to.
pub fn write<T>(&mut self, value: &T) -> io::Result<usize>
where
T: WriteStd140 + ?Sized,
{
value.write_std140(self)
}
/// Write an iterator of values to the underlying buffer.
///
/// Returns the offset into the buffer that the first value was written to.
/// If no values were written, returns the `len()`.
pub fn write_iter<I, T>(&mut self, iter: I) -> io::Result<usize>
where
I: IntoIterator<Item = T>,
T: WriteStd140,
{
let mut offset = self.offset;
let mut iter = iter.into_iter();
if let Some(item) = iter.next() {
offset = item.write_std140(self)?;
}
for item in iter {
item.write_std140(self)?;
}
Ok(offset)
}
/// Write an `Std140` type to the underlying buffer.
pub fn write_std140<T>(&mut self, value: &T) -> io::Result<usize>
where
T: Std140,
{
let padding = align_offset(self.offset, T::ALIGNMENT);
for _ in 0..padding {
self.writer.write_all(&[0])?;
}
self.offset += padding;
let value = value.as_std140();
self.writer.write_all(bytes_of(&value))?;
let write_here = self.offset;
self.offset += size_of::<T>();
Ok(write_here)
}
/// Write a slice of values to the underlying buffer.
#[deprecated(
since = "0.6.0",
note = "Use `write` instead -- it now works on slices."
)]
pub fn write_slice<T>(&mut self, slice: &[T]) -> io::Result<usize>
where
T: AsStd140,
{
self.write(slice)
}
/// Returns the amount of data written by this `Writer`.
pub fn len(&self) -> usize {
self.offset
}
}

View file

@ -1,16 +0,0 @@
//! Defines traits and types for working with data adhering to GLSL's `std140`
//! layout specification.
mod primitives;
mod sizer;
mod traits;
#[cfg(feature = "std")]
mod writer;
pub use self::primitives::*;
pub use self::sizer::*;
pub use self::traits::*;
#[cfg(feature = "std")]
pub use self::writer::*;
pub use bevy_crevice_derive::AsStd430;

View file

@ -1,173 +0,0 @@
use bytemuck::{Pod, Zeroable};
use crate::glsl::Glsl;
use crate::std430::{Std430, Std430Padded};
use crate::internal::align_offset;
use core::mem::size_of;
unsafe impl Std430 for f32 {
const ALIGNMENT: usize = 4;
type Padded = Self;
}
unsafe impl Std430 for f64 {
const ALIGNMENT: usize = 8;
type Padded = Self;
}
unsafe impl Std430 for i32 {
const ALIGNMENT: usize = 4;
type Padded = Self;
}
unsafe impl Std430 for u32 {
const ALIGNMENT: usize = 4;
type Padded = Self;
}
macro_rules! vectors {
(
$(
#[$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,)+
}
unsafe impl Zeroable for $name {}
unsafe impl Pod for $name {}
unsafe impl Std430 for $name {
const ALIGNMENT: usize = $align;
type Padded = Std430Padded<Self, {align_offset(size_of::<$name>(), $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 Vec2<f32>(x, y)
#[doc = "Corresponds to a GLSL `vec3` in std430 layout."] align(16) vec3 Vec3<f32>(x, y, z)
#[doc = "Corresponds to a GLSL `vec4` in std430 layout."] align(16) vec4 Vec4<f32>(x, y, z, w)
#[doc = "Corresponds to a GLSL `ivec2` in std430 layout."] align(8) ivec2 IVec2<i32>(x, y)
#[doc = "Corresponds to a GLSL `ivec3` in std430 layout."] align(16) ivec3 IVec3<i32>(x, y, z)
#[doc = "Corresponds to a GLSL `ivec4` in std430 layout."] align(16) ivec4 IVec4<i32>(x, y, z, w)
#[doc = "Corresponds to a GLSL `uvec2` in std430 layout."] align(8) uvec2 UVec2<u32>(x, y)
#[doc = "Corresponds to a GLSL `uvec3` in std430 layout."] align(16) uvec3 UVec3<u32>(x, y, z)
#[doc = "Corresponds to a GLSL `uvec4` in std430 layout."] align(16) uvec4 UVec4<u32>(x, y, z, w)
// bool vectors are disabled due to https://github.com/LPGhatguy/crevice/issues/36
// #[doc = "Corresponds to a GLSL `bvec2` in std430 layout."] align(8) bvec2 BVec2<bool>(x, y)
// #[doc = "Corresponds to a GLSL `bvec3` in std430 layout."] align(16) bvec3 BVec3<bool>(x, y, z)
// #[doc = "Corresponds to a GLSL `bvec4` in std430 layout."] align(16) bvec4 BVec4<bool>(x, y, z, w)
#[doc = "Corresponds to a GLSL `dvec2` in std430 layout."] align(16) dvec2 DVec2<f64>(x, y)
#[doc = "Corresponds to a GLSL `dvec3` in std430 layout."] align(32) dvec3 DVec3<f64>(x, y, z)
#[doc = "Corresponds to a GLSL `dvec4` in std430 layout."] align(32) dvec4 DVec4<f64>(x, y, z, w)
}
macro_rules! matrices {
(
$(
#[$doc:meta]
align($align:literal)
$glsl_name:ident $name:ident {
$($field:ident: $field_ty:ty,)+
}
)+
) => {
$(
#[$doc]
#[allow(missing_docs)]
#[derive(Debug, Clone, Copy)]
#[repr(C)]
pub struct $name {
$(pub $field: $field_ty,)+
}
unsafe impl Zeroable for $name {}
unsafe impl Pod for $name {}
unsafe impl Std430 for $name {
const ALIGNMENT: usize = $align;
/// Matrices are technically arrays of primitives, and as such require pad at end.
const PAD_AT_END: bool = true;
type Padded = Std430Padded<Self, {align_offset(size_of::<$name>(), $align)}>;
}
unsafe impl Glsl for $name {
const NAME: &'static str = stringify!($glsl_name);
}
)+
};
}
matrices! {
#[doc = "Corresponds to a GLSL `mat2` in std430 layout."]
align(8)
mat2 Mat2 {
x: Vec2,
y: Vec2,
}
#[doc = "Corresponds to a GLSL `mat3` in std430 layout."]
align(16)
mat3 Mat3 {
x: Vec3,
_pad_x: f32,
y: Vec3,
_pad_y: f32,
z: Vec3,
_pad_z: f32,
}
#[doc = "Corresponds to a GLSL `mat4` in std430 layout."]
align(16)
mat4 Mat4 {
x: Vec4,
y: Vec4,
z: Vec4,
w: Vec4,
}
#[doc = "Corresponds to a GLSL `dmat2` in std430 layout."]
align(16)
dmat2 DMat2 {
x: DVec2,
y: DVec2,
}
#[doc = "Corresponds to a GLSL `dmat3` in std430 layout."]
align(32)
dmat3 DMat3 {
x: DVec3,
_pad_x: f64,
y: DVec3,
_pad_y: f64,
z: DVec3,
_pad_z: f64,
}
#[doc = "Corresponds to a GLSL `dmat3` in std430 layout."]
align(32)
dmat4 DMat4 {
x: DVec4,
y: DVec4,
z: DVec4,
w: DVec4,
}
}

View file

@ -1,81 +0,0 @@
use core::mem::size_of;
use crate::internal::align_offset;
use crate::std430::{AsStd430, Std430};
/**
Type that computes the buffer size needed by a series of `std430` types laid
out.
This type works well well when paired with `Writer`, precomputing a buffer's
size to alleviate the need to dynamically re-allocate buffers.
## Example
```glsl
struct Frob {
vec3 size;
float frobiness;
}
buffer FROBS {
uint len;
Frob[] frobs;
} frobs;
```
```
use bevy_crevice::std430::{self, AsStd430};
#[derive(AsStd430)]
struct Frob {
size: mint::Vector3<f32>,
frobiness: f32,
}
// Many APIs require that buffers contain at least enough space for all
// fixed-size bindiongs to a buffer as well as one element of any arrays, if
// there are any.
let mut sizer = std430::Sizer::new();
sizer.add::<u32>();
sizer.add::<Frob>();
# fn create_buffer_with_size(size: usize) {}
let buffer = create_buffer_with_size(sizer.len());
# assert_eq!(sizer.len(), 32);
```
*/
pub struct Sizer {
offset: usize,
}
impl Sizer {
/// Create a new `Sizer`.
pub fn new() -> Self {
Self { offset: 0 }
}
/// Add a type's necessary padding and size to the `Sizer`. Returns the
/// offset into the buffer where that type would be written.
pub fn add<T>(&mut self) -> usize
where
T: AsStd430,
{
let size = size_of::<<T as AsStd430>::Output>();
let alignment = <T as AsStd430>::Output::ALIGNMENT;
let padding = align_offset(self.offset, alignment);
self.offset += padding;
let write_here = self.offset;
self.offset += size;
write_here
}
/// Returns the number of bytes required to contain all the types added to
/// the `Sizer`.
pub fn len(&self) -> usize {
self.offset
}
}

View file

@ -1,291 +0,0 @@
use core::mem::{size_of, MaybeUninit};
#[cfg(feature = "std")]
use std::io::{self, Write};
use bytemuck::{bytes_of, Pod, Zeroable};
#[cfg(feature = "std")]
use crate::std430::Writer;
/// Trait implemented for all `std430` primitives. Generally should not be
/// implemented outside this crate.
pub unsafe trait Std430: Copy + Zeroable + Pod {
/// The required alignment of the type. Must be a power of two.
///
/// This is distinct from the value returned by `std::mem::align_of` because
/// `AsStd430` structs do not use Rust's alignment. This enables them to
/// 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>
/// (rule 4 and 9)
const PAD_AT_END: bool = false;
/// Padded type (Std430Padded specialization)
/// The usual implementation is
/// type Padded = Std430Padded<Self, {align_offset(size_of::<Self>(), ALIGNMENT)}>;
type Padded: Std430Convertible<Self>;
/// Casts the type to a byte array. Implementors should not override this
/// method.
///
/// # Safety
/// This is always safe due to the requirements of [`bytemuck::Pod`] being a
/// prerequisite for this trait.
fn as_bytes(&self) -> &[u8] {
bytes_of(self)
}
}
unsafe impl Std430 for () {
const ALIGNMENT: usize = 0;
const PAD_AT_END: bool = false;
type Padded = ();
}
/// Trait specifically for Std430::Padded, implements conversions between padded type and base type.
pub trait Std430Convertible<T: Std430>: Copy {
/// Convert from self to Std430
fn into_std430(self) -> T;
/// Convert from Std430 to self
fn from_std430(_: T) -> Self;
}
impl<T: Std430> Std430Convertible<T> for T {
fn into_std430(self) -> T {
self
}
fn from_std430(also_self: T) -> Self {
also_self
}
}
/// Unfortunately, we cannot easily derive padded representation for generic Std140 types.
/// For now, we'll just use this empty enum with no values.
#[derive(Copy, Clone)]
pub enum InvalidPadded {}
impl<T: Std430> Std430Convertible<T> for InvalidPadded {
fn into_std430(self) -> T {
unimplemented!()
}
fn from_std430(_: T) -> Self {
unimplemented!()
}
}
/**
Trait implemented for all types that can be turned into `std430` values.
This trait can often be `#[derive]`'d instead of manually implementing it. Any
struct which contains only fields that also implement `AsStd430` can derive
`AsStd430`.
Types from the mint crate implement `AsStd430`, making them convenient for use
in uniform types. Most Rust geometry crates, like cgmath, nalgebra, and
ultraviolet support mint.
## Example
```glsl
uniform CAMERA {
mat4 view;
mat4 projection;
} camera;
```
```no_run
use bevy_crevice::std430::{AsStd430, Std430};
#[derive(AsStd430)]
struct CameraUniform {
view: mint::ColumnMatrix4<f32>,
projection: mint::ColumnMatrix4<f32>,
}
let view: mint::ColumnMatrix4<f32> = todo!("your math code here");
let projection: mint::ColumnMatrix4<f32> = todo!("your math code here");
let camera = CameraUniform {
view,
projection,
};
# fn write_to_gpu_buffer(bytes: &[u8]) {}
let camera_std430 = camera.as_std430();
write_to_gpu_buffer(camera_std430.as_bytes());
```
*/
pub trait AsStd430 {
/// The `std430` version of this value.
type Output: Std430;
/// Convert this value into the `std430` version of itself.
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::<Self::Output>()
}
/// Converts from `std430` version of self to self.
fn from_std430(value: Self::Output) -> Self;
}
impl<T> AsStd430 for T
where
T: Std430,
{
type Output = Self;
fn as_std430(&self) -> Self {
*self
}
fn from_std430(value: Self) -> Self {
value
}
}
#[doc(hidden)]
#[derive(Copy, Clone, Debug)]
pub struct Std430Padded<T: Std430, const PAD: usize> {
inner: T,
_padding: [u8; PAD],
}
unsafe impl<T: Std430, const PAD: usize> Zeroable for Std430Padded<T, PAD> {}
unsafe impl<T: Std430, const PAD: usize> Pod for Std430Padded<T, PAD> {}
impl<T: Std430, const PAD: usize> Std430Convertible<T> for Std430Padded<T, PAD> {
fn into_std430(self) -> T {
self.inner
}
fn from_std430(inner: T) -> Self {
Self {
inner,
_padding: [0u8; PAD],
}
}
}
#[doc(hidden)]
#[derive(Copy, Clone, Debug)]
#[repr(transparent)]
pub struct Std430Array<T: Std430, const N: usize>([T::Padded; N]);
unsafe impl<T: Std430, const N: usize> Zeroable for Std430Array<T, N> where T::Padded: Zeroable {}
unsafe impl<T: Std430, const N: usize> Pod for Std430Array<T, N> where T::Padded: Pod {}
unsafe impl<T: Std430, const N: usize> Std430 for Std430Array<T, N>
where
T::Padded: Pod,
{
const ALIGNMENT: usize = T::ALIGNMENT;
type Padded = Self;
}
impl<T: Std430, const N: usize> Std430Array<T, N> {
fn uninit_array() -> [MaybeUninit<T::Padded>; N] {
unsafe { MaybeUninit::uninit().assume_init() }
}
fn from_uninit_array(a: [MaybeUninit<T::Padded>; N]) -> Self {
unsafe { core::mem::transmute_copy(&a) }
}
}
impl<T: AsStd430, const N: usize> AsStd430 for [T; N]
where
<T::Output as Std430>::Padded: Pod,
{
type Output = Std430Array<T::Output, N>;
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()));
}
Self::Output::from_uninit_array(res)
}
fn from_std430(val: Self::Output) -> Self {
let mut res: [MaybeUninit<T>; N] = unsafe { MaybeUninit::uninit().assume_init() };
for i in 0..N {
res[i] = MaybeUninit::new(T::from_std430(val.0[i].into_std430()));
}
unsafe { core::mem::transmute_copy(&res) }
}
}
/// Trait implemented for all types that can be written into a buffer as
/// `std430` bytes. This type is more general than [`AsStd430`]: all `AsStd430`
/// types implement `WriteStd430`, but not the other way around.
///
/// While `AsStd430` requires implementers to return a type that implements the
/// `Std430` trait, `WriteStd430` directly writes bytes using a [`Writer`]. This
/// makes `WriteStd430` usable for writing slices or other DSTs that could not
/// implement `AsStd430` without allocating new memory on the heap.
#[cfg(feature = "std")]
pub trait WriteStd430 {
/// Writes this value into the given [`Writer`] using `std430` layout rules.
///
/// Should return the offset of the first byte of this type, as returned by
/// the first call to [`Writer::write`].
fn write_std430<W: Write>(&self, writer: &mut Writer<W>) -> io::Result<usize>;
/// The space required to write this value using `std430` layout rules. This
/// does not include alignment padding that may be needed before or after
/// this type when written as part of a larger buffer.
fn std430_size(&self) -> usize {
let mut writer = Writer::new(io::sink());
self.write_std430(&mut writer).unwrap();
writer.len()
}
}
#[cfg(feature = "std")]
impl<T> WriteStd430 for T
where
T: AsStd430,
{
fn write_std430<W: Write>(&self, writer: &mut Writer<W>) -> io::Result<usize> {
writer.write_std430(&self.as_std430())
}
fn std430_size(&self) -> usize {
size_of::<<Self as AsStd430>::Output>()
}
}
#[cfg(feature = "std")]
impl<T> WriteStd430 for [T]
where
T: WriteStd430,
{
fn write_std430<W: Write>(&self, writer: &mut Writer<W>) -> io::Result<usize> {
let mut offset = writer.len();
let mut iter = self.iter();
if let Some(item) = iter.next() {
offset = item.write_std430(writer)?;
}
for item in iter {
item.write_std430(writer)?;
}
Ok(offset)
}
fn std430_size(&self) -> usize {
let mut writer = Writer::new(io::sink());
self.write_std430(&mut writer).unwrap();
writer.len()
}
}

View file

@ -1,150 +0,0 @@
use std::io::{self, Write};
use std::mem::size_of;
use bytemuck::bytes_of;
use crate::internal::align_offset;
use crate::std430::{AsStd430, Std430, WriteStd430};
/**
Type that enables writing correctly aligned `std430` values to a buffer.
`Writer` is useful when many values need to be laid out in a row that cannot be
represented by a struct alone, like dynamically sized arrays or dynamically
laid-out values.
## Example
In this example, we'll write a length-prefixed list of lights to a buffer.
`std430::Writer` helps align correctly, even across multiple structs, which can
be tricky and error-prone otherwise.
```glsl
struct PointLight {
vec3 position;
vec3 color;
float brightness;
};
buffer POINT_LIGHTS {
uint len;
PointLight[] lights;
} point_lights;
```
```
use bevy_crevice::std430::{self, AsStd430};
#[derive(AsStd430)]
struct PointLight {
position: mint::Vector3<f32>,
color: mint::Vector3<f32>,
brightness: f32,
}
let lights = vec![
PointLight {
position: [0.0, 1.0, 0.0].into(),
color: [1.0, 0.0, 0.0].into(),
brightness: 0.6,
},
PointLight {
position: [0.0, 4.0, 3.0].into(),
color: [1.0, 1.0, 1.0].into(),
brightness: 1.0,
},
];
# fn map_gpu_buffer_for_write() -> &'static mut [u8] {
# Box::leak(vec![0; 1024].into_boxed_slice())
# }
let target_buffer = map_gpu_buffer_for_write();
let mut writer = std430::Writer::new(target_buffer);
let light_count = lights.len() as u32;
writer.write(&light_count)?;
// Crevice will automatically insert the required padding to align the
// PointLight structure correctly. In this case, there will be 12 bytes of
// padding between the length field and the light list.
writer.write(lights.as_slice())?;
# fn unmap_gpu_buffer() {}
unmap_gpu_buffer();
# Ok::<(), std::io::Error>(())
```
*/
pub struct Writer<W> {
writer: W,
offset: usize,
}
impl<W: Write> Writer<W> {
/// Create a new `Writer`, wrapping a buffer, file, or other type that
/// implements [`std::io::Write`].
pub fn new(writer: W) -> Self {
Self { writer, offset: 0 }
}
/// Write a new value to the underlying buffer, writing zeroed padding where
/// necessary.
///
/// Returns the offset into the buffer that the value was written to.
pub fn write<T>(&mut self, value: &T) -> io::Result<usize>
where
T: WriteStd430 + ?Sized,
{
value.write_std430(self)
}
/// Write an iterator of values to the underlying buffer.
///
/// Returns the offset into the buffer that the first value was written to.
/// If no values were written, returns the `len()`.
pub fn write_iter<I, T>(&mut self, iter: I) -> io::Result<usize>
where
I: IntoIterator<Item = T>,
T: WriteStd430,
{
let mut offset = self.offset;
let mut iter = iter.into_iter();
if let Some(item) = iter.next() {
offset = item.write_std430(self)?;
}
for item in iter {
item.write_std430(self)?;
}
Ok(offset)
}
/// Write an `Std430` type to the underlying buffer.
pub fn write_std430<T>(&mut self, value: &T) -> io::Result<usize>
where
T: Std430,
{
let padding = align_offset(self.offset, T::ALIGNMENT);
for _ in 0..padding {
self.writer.write_all(&[0])?;
}
self.offset += padding;
let value = value.as_std430();
self.writer.write_all(bytes_of(&value))?;
let write_here = self.offset;
self.offset += size_of::<T>();
Ok(write_here)
}
/// Returns the amount of data written by this `Writer`.
pub fn len(&self) -> usize {
self.offset
}
}

View file

@ -1,97 +0,0 @@
#![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;
}
)*
};
}

View file

@ -1,8 +0,0 @@
---
source: tests/test.rs
expression: "TestGlsl::glsl_definition()"
---
struct TestGlsl {
vec3 foo[8][4];
};

View file

@ -1,9 +0,0 @@
---
source: tests/test.rs
expression: "TestGlsl::glsl_definition()"
---
struct TestGlsl {
vec3 foo;
mat2 bar;
};

View file

@ -1,61 +0,0 @@
use bevy_crevice::glsl::GlslStruct;
use bevy_crevice::std140::AsStd140;
#[test]
fn there_and_back_again() {
#[derive(AsStd140, Debug, PartialEq)]
struct ThereAndBackAgain {
view: mint::ColumnMatrix3<f32>,
origin: mint::Vector3<f32>,
}
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!(<ThereAndBackAgain as AsStd140>::from_std140(x_as), x);
}
#[test]
fn generate_struct_glsl() {
#[allow(dead_code)]
#[derive(GlslStruct)]
struct TestGlsl {
foo: mint::Vector3<f32>,
bar: mint::ColumnMatrix2<f32>,
}
insta::assert_display_snapshot!(TestGlsl::glsl_definition());
}
#[test]
fn generate_struct_array_glsl() {
#[allow(dead_code)]
#[derive(GlslStruct)]
struct TestGlsl {
foo: [[mint::Vector3<f32>; 8]; 4],
}
insta::assert_display_snapshot!(TestGlsl::glsl_definition());
}

View file

@ -0,0 +1,16 @@
[package]
name = "bevy_encase_derive"
version = "0.8.0-dev"
edition = "2021"
description = "Bevy derive macro for encase"
homepage = "https://bevyengine.org"
repository = "https://github.com/bevyengine/bevy"
license = "MIT OR Apache-2.0"
keywords = ["bevy"]
[lib]
proc-macro = true
[dependencies]
bevy_macro_utils = { path = "../bevy_macro_utils", version = "0.8.0-dev" }
encase_derive_impl = "0.2"

View file

@ -0,0 +1,40 @@
use bevy_macro_utils::BevyManifest;
use encase_derive_impl::{implement, syn};
const BEVY: &str = "bevy";
const BEVY_RENDER: &str = "bevy_render";
const ENCASE: &str = "encase";
fn bevy_encase_path() -> syn::Path {
let bevy_manifest = BevyManifest::default();
bevy_manifest
.maybe_get_path(BEVY)
.map(|bevy_path| {
let mut segments = bevy_path.segments;
segments.push(BevyManifest::parse_str("render"));
syn::Path {
leading_colon: None,
segments,
}
})
.or_else(|| bevy_manifest.maybe_get_path(BEVY_RENDER))
.map(|bevy_render_path| {
let mut segments = bevy_render_path.segments;
segments.push(BevyManifest::parse_str("render_resource"));
syn::Path {
leading_colon: None,
segments,
}
})
.map(|path| {
let mut segments = path.segments;
segments.push(BevyManifest::parse_str(ENCASE));
syn::Path {
leading_colon: None,
segments,
}
})
.unwrap_or_else(|| bevy_manifest.get_path(ENCASE))
}
implement!(bevy_encase_path());

View file

@ -8,10 +8,7 @@ use bevy_render::{
mesh::MeshVertexBufferLayout,
prelude::Shader,
render_asset::{PrepareAssetError, RenderAsset, RenderAssets},
render_resource::{
std140::{AsStd140, Std140},
*,
},
render_resource::*,
renderer::RenderDevice,
texture::Image,
};
@ -140,7 +137,7 @@ bitflags::bitflags! {
}
/// The GPU representation of the uniform data of a [`StandardMaterial`].
#[derive(Clone, Default, AsStd140)]
#[derive(Clone, Default, ShaderType)]
pub struct StandardMaterialUniformData {
/// Doubles as diffuse albedo for non-metallic, specular for metallic and a mix for everything
/// in between.
@ -296,12 +293,15 @@ impl RenderAsset for StandardMaterial {
flags: flags.bits(),
alpha_cutoff,
};
let value_std140 = value.as_std140();
let byte_buffer = [0u8; StandardMaterialUniformData::SIZE.get() as usize];
let mut buffer = encase::UniformBuffer::new(byte_buffer);
buffer.write(&value).unwrap();
let buffer = render_device.create_buffer_with_data(&BufferInitDescriptor {
label: Some("pbr_standard_material_uniform_buffer"),
usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST,
contents: value_std140.as_bytes(),
contents: buffer.as_ref(),
});
let bind_group = render_device.create_bind_group(&BindGroupDescriptor {
entries: &[
@ -423,9 +423,7 @@ impl SpecializedMaterial for StandardMaterial {
ty: BindingType::Buffer {
ty: BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: BufferSize::new(
StandardMaterialUniformData::std140_size_static() as u64,
),
min_binding_size: Some(StandardMaterialUniformData::min_size()),
},
count: None,
},

View file

@ -21,7 +21,7 @@ use bevy_render::{
EntityRenderCommand, PhaseItem, RenderCommandResult, RenderPhase, SetItemPipeline,
TrackedRenderPass,
},
render_resource::{std140::AsStd140, std430::AsStd430, *},
render_resource::*,
renderer::{RenderContext, RenderDevice, RenderQueue},
texture::*,
view::{
@ -34,7 +34,7 @@ use bevy_utils::{
tracing::{error, warn},
HashMap,
};
use std::num::NonZeroU32;
use std::num::{NonZeroU32, NonZeroU64};
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemLabel)]
pub enum RenderLightSystems {
@ -78,8 +78,7 @@ pub struct ExtractedDirectionalLight {
pub type ExtractedDirectionalLightShadowMap = DirectionalLightShadowMap;
#[repr(C)]
#[derive(Copy, Clone, AsStd140, AsStd430, Default, Debug)]
#[derive(Copy, Clone, ShaderType, Default, Debug)]
pub struct GpuPointLight {
// The lower-right 2x2 values of the projection matrix 22 23 32 33
projection_lr: Vec4,
@ -90,13 +89,28 @@ pub struct GpuPointLight {
shadow_normal_bias: f32,
}
#[derive(ShaderType)]
pub struct GpuPointLightsUniform {
data: Box<[GpuPointLight; MAX_UNIFORM_BUFFER_POINT_LIGHTS]>,
}
impl Default for GpuPointLightsUniform {
fn default() -> Self {
Self {
data: Box::new([GpuPointLight::default(); MAX_UNIFORM_BUFFER_POINT_LIGHTS]),
}
}
}
#[derive(ShaderType, Default)]
pub struct GpuPointLightsStorage {
#[size(runtime)]
data: Vec<GpuPointLight>,
}
pub enum GpuPointLights {
Uniform {
buffer: UniformVec<[GpuPointLight; MAX_UNIFORM_BUFFER_POINT_LIGHTS]>,
},
Storage {
buffer: StorageBuffer<GpuPointLight>,
},
Uniform(UniformBuffer<GpuPointLightsUniform>),
Storage(StorageBuffer<GpuPointLightsStorage>),
}
impl GpuPointLights {
@ -108,66 +122,48 @@ impl GpuPointLights {
}
fn uniform() -> Self {
Self::Uniform {
buffer: UniformVec::default(),
}
Self::Uniform(UniformBuffer::default())
}
fn storage() -> Self {
Self::Storage {
buffer: StorageBuffer::default(),
}
Self::Storage(StorageBuffer::default())
}
fn clear(&mut self) {
fn set(&mut self, mut lights: Vec<GpuPointLight>) {
match self {
GpuPointLights::Uniform { buffer } => buffer.clear(),
GpuPointLights::Storage { buffer } => buffer.clear(),
}
}
fn push(&mut self, mut lights: Vec<GpuPointLight>) {
match self {
GpuPointLights::Uniform { buffer } => {
// NOTE: This iterator construction allows moving and padding with default
// values and is like this to avoid unnecessary cloning.
let gpu_point_lights = lights
.drain(..)
.chain(std::iter::repeat_with(GpuPointLight::default))
.take(MAX_UNIFORM_BUFFER_POINT_LIGHTS)
.collect::<Vec<_>>();
buffer.push(gpu_point_lights.try_into().unwrap());
GpuPointLights::Uniform(buffer) => {
let len = lights.len().min(MAX_UNIFORM_BUFFER_POINT_LIGHTS);
let src = &lights[..len];
let dst = &mut buffer.get_mut().data[..len];
dst.copy_from_slice(src);
}
GpuPointLights::Storage { buffer } => {
buffer.append(&mut lights);
GpuPointLights::Storage(buffer) => {
buffer.get_mut().data.clear();
buffer.get_mut().data.append(&mut lights);
}
}
}
fn write_buffer(&mut self, render_device: &RenderDevice, render_queue: &RenderQueue) {
match self {
GpuPointLights::Uniform { buffer } => buffer.write_buffer(render_device, render_queue),
GpuPointLights::Storage { buffer } => buffer.write_buffer(render_device, render_queue),
GpuPointLights::Uniform(buffer) => buffer.write_buffer(render_device, render_queue),
GpuPointLights::Storage(buffer) => buffer.write_buffer(render_device, render_queue),
}
}
pub fn binding(&self) -> Option<BindingResource> {
match self {
GpuPointLights::Uniform { buffer } => buffer.binding(),
GpuPointLights::Storage { buffer } => buffer.binding(),
GpuPointLights::Uniform(buffer) => buffer.binding(),
GpuPointLights::Storage(buffer) => buffer.binding(),
}
}
pub fn len(&self) -> usize {
match self {
GpuPointLights::Uniform { buffer } => buffer.len(),
GpuPointLights::Storage { buffer } => buffer.values().len(),
pub fn min_size(buffer_binding_type: BufferBindingType) -> NonZeroU64 {
match buffer_binding_type {
BufferBindingType::Storage { .. } => GpuPointLightsStorage::min_size(),
BufferBindingType::Uniform => GpuPointLightsUniform::min_size(),
}
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
// NOTE: These must match the bit flags in bevy_pbr2/src/render/pbr.frag!
@ -180,8 +176,7 @@ bitflags::bitflags! {
}
}
#[repr(C)]
#[derive(Copy, Clone, AsStd140, Default, Debug)]
#[derive(Copy, Clone, ShaderType, Default, Debug)]
pub struct GpuDirectionalLight {
view_projection: Mat4,
color: Vec4,
@ -201,10 +196,8 @@ bitflags::bitflags! {
}
}
#[repr(C)]
#[derive(Copy, Clone, Debug, AsStd140)]
#[derive(Copy, Clone, Debug, ShaderType)]
pub struct GpuLights {
// TODO: this comes first to work around a WGSL alignment issue. We need to solve this issue before releasing the renderer rework
directional_lights: [GpuDirectionalLight; MAX_DIRECTIONAL_LIGHTS],
ambient_color: Vec4,
// xyz are x/y/z cluster dimensions and w is the number of clusters
@ -245,7 +238,7 @@ impl FromWorld for ShadowPipeline {
ty: BindingType::Buffer {
ty: BufferBindingType::Uniform,
has_dynamic_offset: true,
min_binding_size: BufferSize::new(ViewUniform::std140_size_static() as u64),
min_binding_size: Some(ViewUniform::min_size()),
},
count: None,
},
@ -629,7 +622,7 @@ impl GlobalLightMeta {
#[derive(Default)]
pub struct LightMeta {
pub view_gpu_lights: DynamicUniformVec<GpuLights>,
pub view_gpu_lights: DynamicUniformBuffer<GpuLights>,
pub shadow_view_bind_group: Option<BindGroup>,
}
@ -688,7 +681,6 @@ pub fn prepare_lights(
.map(|CubeMapFace { target, up }| GlobalTransform::identity().looking_at(*target, *up))
.collect::<Vec<_>>();
global_light_meta.gpu_point_lights.clear();
global_light_meta.entity_to_index.clear();
let mut point_lights: Vec<_> = point_lights.iter().collect::<Vec<_>>();
@ -744,7 +736,8 @@ pub fn prepare_lights(
});
global_light_meta.entity_to_index.insert(entity, index);
}
global_light_meta.gpu_point_lights.push(gpu_point_lights);
global_light_meta.gpu_point_lights.set(gpu_point_lights);
global_light_meta
.gpu_point_lights
.write_buffer(&render_device, &render_queue);
@ -1027,16 +1020,58 @@ fn pack_offset_and_count(offset: usize, count: usize) -> u32 {
| (count as u32 & CLUSTER_COUNT_MASK)
}
#[derive(ShaderType)]
struct GpuClusterLightIndexListsUniform {
data: Box<[UVec4; ViewClusterBindings::MAX_UNIFORM_ITEMS]>,
}
// NOTE: Assert at compile time that GpuClusterLightIndexListsUniform
// fits within the maximum uniform buffer binding size
const _: () = assert!(GpuClusterLightIndexListsUniform::SIZE.get() <= 16384);
impl Default for GpuClusterLightIndexListsUniform {
fn default() -> Self {
Self {
data: Box::new([UVec4::ZERO; ViewClusterBindings::MAX_UNIFORM_ITEMS]),
}
}
}
#[derive(ShaderType)]
struct GpuClusterOffsetsAndCountsUniform {
data: Box<[UVec4; ViewClusterBindings::MAX_UNIFORM_ITEMS]>,
}
impl Default for GpuClusterOffsetsAndCountsUniform {
fn default() -> Self {
Self {
data: Box::new([UVec4::ZERO; ViewClusterBindings::MAX_UNIFORM_ITEMS]),
}
}
}
#[derive(ShaderType, Default)]
struct GpuClusterLightIndexListsStorage {
#[size(runtime)]
data: Vec<u32>,
}
#[derive(ShaderType, Default)]
struct GpuClusterOffsetsAndCountsStorage {
#[size(runtime)]
data: Vec<UVec2>,
}
enum ViewClusterBuffers {
Uniform {
// NOTE: UVec4 is because all arrays in Std140 layout have 16-byte alignment
cluster_light_index_lists: UniformVec<[UVec4; ViewClusterBindings::MAX_UNIFORM_ITEMS]>,
cluster_light_index_lists: UniformBuffer<GpuClusterLightIndexListsUniform>,
// NOTE: UVec4 is because all arrays in Std140 layout have 16-byte alignment
cluster_offsets_and_counts: UniformVec<[UVec4; ViewClusterBindings::MAX_UNIFORM_ITEMS]>,
cluster_offsets_and_counts: UniformBuffer<GpuClusterOffsetsAndCountsUniform>,
},
Storage {
cluster_light_index_lists: StorageBuffer<u32>,
cluster_offsets_and_counts: StorageBuffer<UVec2>,
cluster_light_index_lists: StorageBuffer<GpuClusterLightIndexListsStorage>,
cluster_offsets_and_counts: StorageBuffer<GpuClusterOffsetsAndCountsStorage>,
},
}
@ -1050,8 +1085,8 @@ impl ViewClusterBuffers {
fn uniform() -> Self {
ViewClusterBuffers::Uniform {
cluster_light_index_lists: UniformVec::default(),
cluster_offsets_and_counts: UniformVec::default(),
cluster_light_index_lists: UniformBuffer::default(),
cluster_offsets_and_counts: UniformBuffer::default(),
}
}
@ -1083,24 +1118,22 @@ impl ViewClusterBindings {
}
}
pub fn reserve_and_clear(&mut self) {
pub fn clear(&mut self) {
match &mut self.buffers {
ViewClusterBuffers::Uniform {
cluster_light_index_lists,
cluster_offsets_and_counts,
} => {
cluster_light_index_lists.clear();
cluster_light_index_lists.push([UVec4::ZERO; Self::MAX_UNIFORM_ITEMS]);
cluster_offsets_and_counts.clear();
cluster_offsets_and_counts.push([UVec4::ZERO; Self::MAX_UNIFORM_ITEMS]);
*cluster_light_index_lists.get_mut().data = [UVec4::ZERO; Self::MAX_UNIFORM_ITEMS];
*cluster_offsets_and_counts.get_mut().data = [UVec4::ZERO; Self::MAX_UNIFORM_ITEMS];
}
ViewClusterBuffers::Storage {
cluster_light_index_lists,
cluster_offsets_and_counts,
..
} => {
cluster_light_index_lists.clear();
cluster_offsets_and_counts.clear();
cluster_light_index_lists.get_mut().data.clear();
cluster_offsets_and_counts.get_mut().data.clear();
}
}
}
@ -1119,13 +1152,16 @@ impl ViewClusterBindings {
let component = self.n_offsets & ((1 << 2) - 1);
let packed = pack_offset_and_count(offset, count);
cluster_offsets_and_counts.get_mut(0)[array_index][component] = packed;
cluster_offsets_and_counts.get_mut().data[array_index][component] = packed;
}
ViewClusterBuffers::Storage {
cluster_offsets_and_counts,
..
} => {
cluster_offsets_and_counts.push(UVec2::new(offset as u32, count as u32));
cluster_offsets_and_counts
.get_mut()
.data
.push(UVec2::new(offset as u32, count as u32));
}
}
@ -1147,14 +1183,14 @@ impl ViewClusterBindings {
let sub_index = self.n_indices & ((1 << 2) - 1);
let index = index as u32 & POINT_LIGHT_INDEX_MASK;
cluster_light_index_lists.get_mut(0)[array_index][component] |=
cluster_light_index_lists.get_mut().data[array_index][component] |=
index << (8 * sub_index);
}
ViewClusterBuffers::Storage {
cluster_light_index_lists,
..
} => {
cluster_light_index_lists.push(index as u32);
cluster_light_index_lists.get_mut().data.push(index as u32);
}
}
@ -1205,6 +1241,24 @@ impl ViewClusterBindings {
} => cluster_offsets_and_counts.binding(),
}
}
pub fn min_size_cluster_light_index_lists(
buffer_binding_type: BufferBindingType,
) -> NonZeroU64 {
match buffer_binding_type {
BufferBindingType::Storage { .. } => GpuClusterLightIndexListsStorage::min_size(),
BufferBindingType::Uniform => GpuClusterLightIndexListsUniform::min_size(),
}
}
pub fn min_size_cluster_offsets_and_counts(
buffer_binding_type: BufferBindingType,
) -> NonZeroU64 {
match buffer_binding_type {
BufferBindingType::Storage { .. } => GpuClusterOffsetsAndCountsStorage::min_size(),
BufferBindingType::Uniform => GpuClusterOffsetsAndCountsUniform::min_size(),
}
}
}
pub fn prepare_clusters(
@ -1230,7 +1284,7 @@ pub fn prepare_clusters(
for (entity, cluster_config, extracted_clusters) in views.iter() {
let mut view_clusters_bindings =
ViewClusterBindings::new(mesh_pipeline.clustered_forward_buffer_binding_type);
view_clusters_bindings.reserve_and_clear();
view_clusters_bindings.clear();
let mut indices_full = false;

View file

@ -1,6 +1,6 @@
use crate::{
GlobalLightMeta, GpuLights, LightMeta, NotShadowCaster, NotShadowReceiver, ShadowPipeline,
ViewClusterBindings, ViewLightsUniformOffset, ViewShadowBindings,
GlobalLightMeta, GpuLights, GpuPointLights, LightMeta, NotShadowCaster, NotShadowReceiver,
ShadowPipeline, ViewClusterBindings, ViewLightsUniformOffset, ViewShadowBindings,
CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT,
};
use bevy_app::Plugin;
@ -19,7 +19,7 @@ use bevy_render::{
render_asset::RenderAssets,
render_component::{ComponentUniforms, DynamicUniformIndex, UniformComponentPlugin},
render_phase::{EntityRenderCommand, RenderCommandResult, TrackedRenderPass},
render_resource::{std140::AsStd140, *},
render_resource::*,
renderer::{RenderDevice, RenderQueue},
texture::{BevyDefault, GpuImage, Image, TextureFormatPixelInfo},
view::{ComputedVisibility, ViewUniform, ViewUniformOffset, ViewUniforms},
@ -76,7 +76,7 @@ impl Plugin for MeshRenderPlugin {
}
}
#[derive(Component, AsStd140, Clone)]
#[derive(Component, ShaderType, Clone)]
pub struct MeshUniform {
pub transform: Mat4,
pub inverse_transpose_model: Mat4,
@ -267,10 +267,7 @@ impl FromWorld for MeshPipeline {
let render_device = world.resource::<RenderDevice>();
let clustered_forward_buffer_binding_type = render_device
.get_supported_read_only_binding_type(CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT);
let cluster_min_binding_size = match clustered_forward_buffer_binding_type {
BufferBindingType::Storage { .. } => None,
BufferBindingType::Uniform => BufferSize::new(16384),
};
let view_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
entries: &[
// View
@ -280,7 +277,7 @@ impl FromWorld for MeshPipeline {
ty: BindingType::Buffer {
ty: BufferBindingType::Uniform,
has_dynamic_offset: true,
min_binding_size: BufferSize::new(ViewUniform::std140_size_static() as u64),
min_binding_size: Some(ViewUniform::min_size()),
},
count: None,
},
@ -291,7 +288,7 @@ impl FromWorld for MeshPipeline {
ty: BindingType::Buffer {
ty: BufferBindingType::Uniform,
has_dynamic_offset: true,
min_binding_size: BufferSize::new(GpuLights::std140_size_static() as u64),
min_binding_size: Some(GpuLights::min_size()),
},
count: None,
},
@ -344,10 +341,9 @@ impl FromWorld for MeshPipeline {
ty: BindingType::Buffer {
ty: clustered_forward_buffer_binding_type,
has_dynamic_offset: false,
// NOTE (when no storage buffers): Static size for uniform buffers.
// GpuPointLight has a padded size of 64 bytes, so 16384 / 64 = 256
// point lights max
min_binding_size: cluster_min_binding_size,
min_binding_size: Some(GpuPointLights::min_size(
clustered_forward_buffer_binding_type,
)),
},
count: None,
},
@ -358,9 +354,11 @@ impl FromWorld for MeshPipeline {
ty: BindingType::Buffer {
ty: clustered_forward_buffer_binding_type,
has_dynamic_offset: false,
// NOTE (when no storage buffers): With 256 point lights max, indices
// need 8 bits so use u8
min_binding_size: cluster_min_binding_size,
min_binding_size: Some(
ViewClusterBindings::min_size_cluster_light_index_lists(
clustered_forward_buffer_binding_type,
),
),
},
count: None,
},
@ -371,12 +369,11 @@ impl FromWorld for MeshPipeline {
ty: BindingType::Buffer {
ty: clustered_forward_buffer_binding_type,
has_dynamic_offset: false,
// NOTE (when no storage buffers): The offset needs to address 16384
// indices, which needs 14 bits. The count can be at most all 256 lights
// so 8 bits.
// NOTE: Pack the offset into the upper 19 bits and the count into the
// lower 13 bits.
min_binding_size: cluster_min_binding_size,
min_binding_size: Some(
ViewClusterBindings::min_size_cluster_offsets_and_counts(
clustered_forward_buffer_binding_type,
),
),
},
count: None,
},
@ -390,7 +387,7 @@ impl FromWorld for MeshPipeline {
ty: BindingType::Buffer {
ty: BufferBindingType::Uniform,
has_dynamic_offset: true,
min_binding_size: BufferSize::new(MeshUniform::std140_size_static() as u64),
min_binding_size: Some(MeshUniform::min_size()),
},
count: None,
};
@ -688,7 +685,7 @@ pub fn queue_mesh_bind_group(
skinned: None,
};
if let Some(skinned_joints_buffer) = skinned_mesh_uniform.buffer.uniform_buffer() {
if let Some(skinned_joints_buffer) = skinned_mesh_uniform.buffer.buffer() {
mesh_bind_group.skinned = Some(render_device.create_bind_group(&BindGroupDescriptor {
entries: &[
BindGroupEntry {
@ -712,9 +709,15 @@ pub fn queue_mesh_bind_group(
}
}
// NOTE: This is using BufferVec because it is using a trick to allow a fixed-size array
// in a uniform buffer to be used like a variable-sized array by only writing the valid data
// into the buffer, knowing the number of valid items starting from the dynamic offset, and
// ignoring the rest, whether they're valid for other dynamic offsets or not. This trick may
// be supported later in encase, and then we should make use of it.
#[derive(Default)]
pub struct SkinnedMeshUniform {
pub buffer: UniformVec<Mat4>,
pub buffer: BufferVec<Mat4>,
}
pub fn prepare_skinned_meshes(

View file

@ -31,9 +31,9 @@ webgl = ["wgpu/webgl"]
bevy_app = { path = "../bevy_app", version = "0.8.0-dev" }
bevy_asset = { path = "../bevy_asset", version = "0.8.0-dev" }
bevy_core = { path = "../bevy_core", version = "0.8.0-dev" }
bevy_crevice = { path = "../bevy_crevice", version = "0.8.0-dev", features = ["glam"] }
bevy_derive = { path = "../bevy_derive", version = "0.8.0-dev" }
bevy_ecs = { path = "../bevy_ecs", version = "0.8.0-dev" }
bevy_encase_derive = { path = "../bevy_encase_derive", version = "0.8.0-dev" }
bevy_math = { path = "../bevy_math", version = "0.8.0-dev" }
bevy_reflect = { path = "../bevy_reflect", version = "0.8.0-dev", features = ["bevy"] }
bevy_transform = { path = "../bevy_transform", version = "0.8.0-dev" }
@ -67,3 +67,4 @@ flate2 = { version = "1.0.22", optional = true }
ruzstd = { version = "0.2.4", optional = true }
# For transcoding of UASTC/ETC1S universal formats, and for .basis file support
basis-universal = { version = "0.2.0", optional = true }
encase = { version = "0.2", features = ["glam"] }

View file

@ -1,5 +1,5 @@
use crate::{
render_resource::{std140::AsStd140, DynamicUniformVec},
render_resource::{encase::internal::WriteInto, DynamicUniformBuffer, ShaderType},
renderer::{RenderDevice, RenderQueue},
view::ComputedVisibility,
RenderApp, RenderStage,
@ -58,7 +58,7 @@ impl<C> Default for UniformComponentPlugin<C> {
}
}
impl<C: Component + AsStd140 + Clone> Plugin for UniformComponentPlugin<C> {
impl<C: Component + ShaderType + WriteInto + Clone> Plugin for UniformComponentPlugin<C> {
fn build(&self, app: &mut App) {
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
render_app
@ -69,12 +69,12 @@ impl<C: Component + AsStd140 + Clone> Plugin for UniformComponentPlugin<C> {
}
/// Stores all uniforms of the component type.
pub struct ComponentUniforms<C: Component + AsStd140> {
uniforms: DynamicUniformVec<C>,
pub struct ComponentUniforms<C: Component + ShaderType> {
uniforms: DynamicUniformBuffer<C>,
}
impl<C: Component + AsStd140> Deref for ComponentUniforms<C> {
type Target = DynamicUniformVec<C>;
impl<C: Component + ShaderType> Deref for ComponentUniforms<C> {
type Target = DynamicUniformBuffer<C>;
#[inline]
fn deref(&self) -> &Self::Target {
@ -82,14 +82,14 @@ impl<C: Component + AsStd140> Deref for ComponentUniforms<C> {
}
}
impl<C: Component + AsStd140> ComponentUniforms<C> {
impl<C: Component + ShaderType> ComponentUniforms<C> {
#[inline]
pub fn uniforms(&self) -> &DynamicUniformVec<C> {
pub fn uniforms(&self) -> &DynamicUniformBuffer<C> {
&self.uniforms
}
}
impl<C: Component + AsStd140> Default for ComponentUniforms<C> {
impl<C: Component + ShaderType> Default for ComponentUniforms<C> {
fn default() -> Self {
Self {
uniforms: Default::default(),
@ -106,7 +106,7 @@ fn prepare_uniform_components<C: Component>(
mut component_uniforms: ResMut<ComponentUniforms<C>>,
components: Query<(Entity, &C)>,
) where
C: AsStd140 + Clone,
C: ShaderType + WriteInto + Clone,
{
component_uniforms.uniforms.clear();
let entities = components

View file

@ -8,7 +8,7 @@ mod pipeline_specializer;
mod shader;
mod storage_buffer;
mod texture;
mod uniform_vec;
mod uniform_buffer;
pub use bind_group::*;
pub use bind_group_layout::*;
@ -20,7 +20,7 @@ pub use pipeline_specializer::*;
pub use shader::*;
pub use storage_buffer::*;
pub use texture::*;
pub use uniform_vec::*;
pub use uniform_buffer::*;
// TODO: decide where re-exports should go
pub use wgpu::{
@ -44,6 +44,11 @@ pub use wgpu::{
VertexStepMode,
};
pub use bevy_crevice::*;
pub mod encase {
pub use bevy_encase_derive::ShaderType;
pub use encase::*;
}
pub use self::encase::{ShaderType, Size as ShaderSize};
pub use naga::ShaderStage;

View file

@ -1,42 +1,105 @@
use super::Buffer;
use crate::renderer::{RenderDevice, RenderQueue};
use bevy_crevice::std430::{self, AsStd430, Std430};
use bevy_utils::tracing::warn;
use std::num::NonZeroU64;
use wgpu::{BindingResource, BufferBinding, BufferDescriptor, BufferUsages};
use encase::{
internal::WriteInto, DynamicStorageBuffer as DynamicStorageBufferWrapper, ShaderType,
StorageBuffer as StorageBufferWrapper,
};
use wgpu::{util::BufferInitDescriptor, BindingResource, BufferBinding, BufferUsages};
/// A helper for a storage buffer binding with a body, or a variable-sized array, or both.
pub struct StorageBuffer<T: AsStd430, U: AsStd430 = ()> {
body: U,
values: Vec<T>,
scratch: Vec<u8>,
storage_buffer: Option<Buffer>,
pub struct StorageBuffer<T: ShaderType> {
value: T,
scratch: StorageBufferWrapper<Vec<u8>>,
buffer: Option<Buffer>,
capacity: usize,
}
impl<T: AsStd430, U: AsStd430 + Default> Default for StorageBuffer<T, U> {
/// Creates a new [`StorageBuffer`]
///
/// This does not immediately allocate system/video RAM buffers.
fn default() -> Self {
impl<T: ShaderType> From<T> for StorageBuffer<T> {
fn from(value: T) -> Self {
Self {
body: U::default(),
values: Vec::new(),
scratch: Vec::new(),
storage_buffer: None,
value,
scratch: StorageBufferWrapper::new(Vec::new()),
buffer: None,
capacity: 0,
}
}
}
impl<T: AsStd430, U: AsStd430> StorageBuffer<T, U> {
// NOTE: AsStd430::std430_size_static() uses size_of internally but trait functions cannot be
// marked as const functions
const BODY_SIZE: usize = std::mem::size_of::<U::Output>();
const ITEM_SIZE: usize = std::mem::size_of::<T::Output>();
impl<T: ShaderType + Default> Default for StorageBuffer<T> {
fn default() -> Self {
Self {
value: T::default(),
scratch: StorageBufferWrapper::new(Vec::new()),
buffer: None,
capacity: 0,
}
}
}
/// Gets the reference to the underlying buffer, if one has been allocated.
impl<T: ShaderType + WriteInto> StorageBuffer<T> {
#[inline]
pub fn buffer(&self) -> Option<&Buffer> {
self.storage_buffer.as_ref()
self.buffer.as_ref()
}
#[inline]
pub fn binding(&self) -> Option<BindingResource> {
Some(BindingResource::Buffer(
self.buffer()?.as_entire_buffer_binding(),
))
}
pub fn set(&mut self, value: T) {
self.value = value;
}
pub fn get(&self) -> &T {
&self.value
}
pub fn get_mut(&mut self) -> &mut T {
&mut self.value
}
pub fn write_buffer(&mut self, device: &RenderDevice, queue: &RenderQueue) {
self.scratch.write(&self.value).unwrap();
let size = self.scratch.as_ref().len();
if self.capacity < size {
self.buffer = Some(device.create_buffer_with_data(&BufferInitDescriptor {
label: None,
usage: BufferUsages::COPY_DST | BufferUsages::STORAGE,
contents: self.scratch.as_ref(),
}));
self.capacity = size;
} else if let Some(buffer) = &self.buffer {
queue.write_buffer(buffer, 0, self.scratch.as_ref());
}
}
}
pub struct DynamicStorageBuffer<T: ShaderType> {
values: Vec<T>,
scratch: DynamicStorageBufferWrapper<Vec<u8>>,
buffer: Option<Buffer>,
capacity: usize,
}
impl<T: ShaderType> Default for DynamicStorageBuffer<T> {
fn default() -> Self {
Self {
values: Vec::new(),
scratch: DynamicStorageBufferWrapper::new(Vec::new()),
buffer: None,
capacity: 0,
}
}
}
impl<T: ShaderType + WriteInto> DynamicStorageBuffer<T> {
#[inline]
pub fn buffer(&self) -> Option<&Buffer> {
self.buffer.as_ref()
}
#[inline]
@ -44,171 +107,47 @@ impl<T: AsStd430, U: AsStd430> StorageBuffer<T, U> {
Some(BindingResource::Buffer(BufferBinding {
buffer: self.buffer()?,
offset: 0,
size: Some(NonZeroU64::new((self.size()) as u64).unwrap()),
size: Some(T::min_size()),
}))
}
#[inline]
pub fn set_body(&mut self, body: U) {
self.body = body;
pub fn len(&self) -> usize {
self.values.len()
}
fn reserve_buffer(&mut self, device: &RenderDevice) -> bool {
let size = self.size();
if self.storage_buffer.is_none() || size > self.scratch.len() {
self.scratch.resize(size, 0);
self.storage_buffer = Some(device.create_buffer(&BufferDescriptor {
label: None,
size: size as wgpu::BufferAddress,
usage: BufferUsages::COPY_DST | BufferUsages::STORAGE,
mapped_at_creation: false,
}));
true
} else {
false
}
#[inline]
pub fn is_empty(&self) -> bool {
self.values.is_empty()
}
fn size(&self) -> usize {
let mut size = 0;
size += Self::BODY_SIZE;
if Self::ITEM_SIZE > 0 {
if size > 0 {
// Pad according to the array item type's alignment
size = (size + <U as AsStd430>::Output::ALIGNMENT - 1)
& !(<U as AsStd430>::Output::ALIGNMENT - 1);
}
// Variable size arrays must have at least 1 element
size += Self::ITEM_SIZE * self.values.len().max(1);
}
size
#[inline]
pub fn push(&mut self, value: T) -> u32 {
let offset = self.scratch.write(&value).unwrap() as u32;
self.values.push(value);
offset
}
#[inline]
pub fn write_buffer(&mut self, device: &RenderDevice, queue: &RenderQueue) {
self.reserve_buffer(device);
if let Some(storage_buffer) = &self.storage_buffer {
let range = 0..self.size();
let mut writer = std430::Writer::new(&mut self.scratch[range.clone()]);
let mut offset = 0;
// First write the struct body if there is one
if Self::BODY_SIZE > 0 {
if let Ok(new_offset) = writer.write(&self.body).map_err(|e| warn!("{:?}", e)) {
offset = new_offset;
}
}
if Self::ITEM_SIZE > 0 {
if self.values.is_empty() {
// Zero-out the padding and dummy array item in the case of the array being empty
for i in offset..self.size() {
self.scratch[i] = 0;
}
} else {
// Then write the array. Note that padding bytes may be added between the body
// and the array in order to align the array to the alignment requirements of its
// items
writer
.write(self.values.as_slice())
.map_err(|e| warn!("{:?}", e))
.ok();
}
}
queue.write_buffer(storage_buffer, 0, &self.scratch[range]);
let size = self.scratch.as_ref().len();
if self.capacity < size {
self.buffer = Some(device.create_buffer_with_data(&BufferInitDescriptor {
label: None,
usage: BufferUsages::COPY_DST | BufferUsages::STORAGE,
contents: self.scratch.as_ref(),
}));
self.capacity = size;
} else if let Some(buffer) = &self.buffer {
queue.write_buffer(buffer, 0, self.scratch.as_ref());
}
}
pub fn values(&self) -> &[T] {
&self.values
}
pub fn values_mut(&mut self) -> &mut [T] {
&mut self.values
}
#[inline]
pub fn clear(&mut self) {
self.values.clear();
}
#[inline]
pub fn push(&mut self, value: T) {
self.values.push(value);
}
#[inline]
pub fn append(&mut self, values: &mut Vec<T>) {
self.values.append(values);
}
}
#[cfg(test)]
mod tests {
use super::StorageBuffer;
use bevy_crevice::std430;
use bevy_crevice::std430::AsStd430;
use bevy_crevice::std430::Std430;
use bevy_math::Vec3;
use bevy_math::Vec4;
//Note:
//A Vec3 has 12 bytes and needs to be padded to 16 bytes, when converted to std430
//https://www.w3.org/TR/WGSL/#alignment-and-size
#[derive(AsStd430, Default)]
struct NotInherentlyAligned {
data: Vec3,
}
//Note:
//A Vec4 has 16 bytes and does not need to be padded to fit in std430
//https://www.w3.org/TR/WGSL/#alignment-and-size
#[derive(AsStd430)]
struct InherentlyAligned {
data: Vec4,
}
#[test]
fn storage_buffer_correctly_sized_nonaligned() {
let mut buffer: StorageBuffer<NotInherentlyAligned> = StorageBuffer::default();
buffer.push(NotInherentlyAligned { data: Vec3::ONE });
let actual_size = buffer.size();
let data = [NotInherentlyAligned { data: Vec3::ONE }].as_std430();
let data_as_bytes = data.as_bytes();
assert_eq!(actual_size, data_as_bytes.len());
}
#[test]
fn storage_buffer_correctly_sized_aligned() {
let mut buffer: StorageBuffer<InherentlyAligned> = StorageBuffer::default();
buffer.push(InherentlyAligned { data: Vec4::ONE });
let actual_size = buffer.size();
let data = [InherentlyAligned { data: Vec4::ONE }].as_std430();
let data_as_bytes = data.as_bytes();
assert_eq!(actual_size, data_as_bytes.len());
}
#[test]
fn storage_buffer_correctly_sized_item_and_body() {
let mut buffer: StorageBuffer<NotInherentlyAligned, NotInherentlyAligned> =
StorageBuffer::default();
buffer.push(NotInherentlyAligned { data: Vec3::ONE });
buffer.set_body(NotInherentlyAligned { data: Vec3::ONE });
let calculated_size = buffer.size();
//Emulate Write
let mut scratch = Vec::<u8>::new();
scratch.resize(calculated_size, 0);
let mut writer = std430::Writer::new(&mut scratch[0..calculated_size]);
writer
.write(&buffer.body)
.expect("Buffer has enough space to write the body.");
writer
.write(buffer.values.as_slice())
.expect("Buffer has enough space to write the values.");
self.scratch.as_mut().clear();
self.scratch.set_offset(0);
}
}

View file

@ -0,0 +1,150 @@
use crate::{
render_resource::Buffer,
renderer::{RenderDevice, RenderQueue},
};
use encase::{
internal::WriteInto, DynamicUniformBuffer as DynamicUniformBufferWrapper, ShaderType,
UniformBuffer as UniformBufferWrapper,
};
use wgpu::{util::BufferInitDescriptor, BindingResource, BufferBinding, BufferUsages};
pub struct UniformBuffer<T: ShaderType> {
value: T,
scratch: UniformBufferWrapper<Vec<u8>>,
buffer: Option<Buffer>,
}
impl<T: ShaderType> From<T> for UniformBuffer<T> {
fn from(value: T) -> Self {
Self {
value,
scratch: UniformBufferWrapper::new(Vec::new()),
buffer: None,
}
}
}
impl<T: ShaderType + Default> Default for UniformBuffer<T> {
fn default() -> Self {
Self {
value: T::default(),
scratch: UniformBufferWrapper::new(Vec::new()),
buffer: None,
}
}
}
impl<T: ShaderType + WriteInto> UniformBuffer<T> {
#[inline]
pub fn buffer(&self) -> Option<&Buffer> {
self.buffer.as_ref()
}
#[inline]
pub fn binding(&self) -> Option<BindingResource> {
Some(BindingResource::Buffer(
self.buffer()?.as_entire_buffer_binding(),
))
}
pub fn set(&mut self, value: T) {
self.value = value;
}
pub fn get(&self) -> &T {
&self.value
}
pub fn get_mut(&mut self) -> &mut T {
&mut self.value
}
pub fn write_buffer(&mut self, device: &RenderDevice, queue: &RenderQueue) {
self.scratch.write(&self.value).unwrap();
match &self.buffer {
Some(buffer) => queue.write_buffer(buffer, 0, self.scratch.as_ref()),
None => {
self.buffer = Some(device.create_buffer_with_data(&BufferInitDescriptor {
label: None,
usage: BufferUsages::COPY_DST | BufferUsages::UNIFORM,
contents: self.scratch.as_ref(),
}));
}
}
}
}
pub struct DynamicUniformBuffer<T: ShaderType> {
values: Vec<T>,
scratch: DynamicUniformBufferWrapper<Vec<u8>>,
buffer: Option<Buffer>,
capacity: usize,
}
impl<T: ShaderType> Default for DynamicUniformBuffer<T> {
fn default() -> Self {
Self {
values: Vec::new(),
scratch: DynamicUniformBufferWrapper::new(Vec::new()),
buffer: None,
capacity: 0,
}
}
}
impl<T: ShaderType + WriteInto> DynamicUniformBuffer<T> {
#[inline]
pub fn buffer(&self) -> Option<&Buffer> {
self.buffer.as_ref()
}
#[inline]
pub fn binding(&self) -> Option<BindingResource> {
Some(BindingResource::Buffer(BufferBinding {
buffer: self.buffer()?,
offset: 0,
size: Some(T::min_size()),
}))
}
#[inline]
pub fn len(&self) -> usize {
self.values.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.values.is_empty()
}
#[inline]
pub fn push(&mut self, value: T) -> u32 {
let offset = self.scratch.write(&value).unwrap() as u32;
self.values.push(value);
offset
}
#[inline]
pub fn write_buffer(&mut self, device: &RenderDevice, queue: &RenderQueue) {
let size = self.scratch.as_ref().len();
if self.capacity < size {
self.buffer = Some(device.create_buffer_with_data(&BufferInitDescriptor {
label: None,
usage: BufferUsages::COPY_DST | BufferUsages::UNIFORM,
contents: self.scratch.as_ref(),
}));
self.capacity = size;
} else if let Some(buffer) = &self.buffer {
queue.write_buffer(buffer, 0, self.scratch.as_ref());
}
}
#[inline]
pub fn clear(&mut self) {
self.values.clear();
self.scratch.as_mut().clear();
self.scratch.set_offset(0);
}
}

View file

@ -1,166 +0,0 @@
use crate::{
render_resource::std140::{self, AsStd140, DynamicUniform, Std140},
render_resource::Buffer,
renderer::{RenderDevice, RenderQueue},
};
use std::num::NonZeroU64;
use wgpu::{BindingResource, BufferBinding, BufferDescriptor, BufferUsages};
pub struct UniformVec<T: AsStd140> {
values: Vec<T>,
scratch: Vec<u8>,
uniform_buffer: Option<Buffer>,
capacity: usize,
item_size: usize,
}
impl<T: AsStd140> Default for UniformVec<T> {
fn default() -> Self {
Self {
values: Vec::new(),
scratch: Vec::new(),
uniform_buffer: None,
capacity: 0,
item_size: (T::std140_size_static() + <T as AsStd140>::Output::ALIGNMENT - 1)
& !(<T as AsStd140>::Output::ALIGNMENT - 1),
}
}
}
impl<T: AsStd140> UniformVec<T> {
#[inline]
pub fn uniform_buffer(&self) -> Option<&Buffer> {
self.uniform_buffer.as_ref()
}
#[inline]
pub fn binding(&self) -> Option<BindingResource> {
Some(BindingResource::Buffer(BufferBinding {
buffer: self.uniform_buffer()?,
offset: 0,
size: Some(NonZeroU64::new(self.item_size as u64).unwrap()),
}))
}
#[inline]
pub fn len(&self) -> usize {
self.values.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.values.is_empty()
}
#[inline]
pub fn capacity(&self) -> usize {
self.capacity
}
pub fn push(&mut self, value: T) -> usize {
let index = self.values.len();
self.values.push(value);
index
}
pub fn get_mut(&mut self, index: usize) -> &mut T {
&mut self.values[index]
}
pub fn reserve(&mut self, capacity: usize, device: &RenderDevice) -> bool {
if capacity > self.capacity {
self.capacity = capacity;
let size = self.item_size * capacity;
self.scratch.resize(size, 0);
self.uniform_buffer = Some(device.create_buffer(&BufferDescriptor {
label: None,
size: size as wgpu::BufferAddress,
usage: BufferUsages::COPY_DST | BufferUsages::UNIFORM,
mapped_at_creation: false,
}));
true
} else {
false
}
}
pub fn write_buffer(&mut self, device: &RenderDevice, queue: &RenderQueue) {
if self.values.is_empty() {
return;
}
self.reserve(self.values.len(), device);
if let Some(uniform_buffer) = &self.uniform_buffer {
let range = 0..self.item_size * self.values.len();
let mut writer = std140::Writer::new(&mut self.scratch[range.clone()]);
writer.write(self.values.as_slice()).unwrap();
queue.write_buffer(uniform_buffer, 0, &self.scratch[range]);
}
}
pub fn clear(&mut self) {
self.values.clear();
}
pub fn values(&self) -> &[T] {
&self.values
}
}
pub struct DynamicUniformVec<T: AsStd140> {
uniform_vec: UniformVec<DynamicUniform<T>>,
}
impl<T: AsStd140> Default for DynamicUniformVec<T> {
fn default() -> Self {
Self {
uniform_vec: Default::default(),
}
}
}
impl<T: AsStd140> DynamicUniformVec<T> {
#[inline]
pub fn uniform_buffer(&self) -> Option<&Buffer> {
self.uniform_vec.uniform_buffer()
}
#[inline]
pub fn binding(&self) -> Option<BindingResource> {
self.uniform_vec.binding()
}
#[inline]
pub fn len(&self) -> usize {
self.uniform_vec.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.uniform_vec.is_empty()
}
#[inline]
pub fn capacity(&self) -> usize {
self.uniform_vec.capacity()
}
#[inline]
pub fn push(&mut self, value: T) -> u32 {
(self.uniform_vec.push(DynamicUniform(value)) * self.uniform_vec.item_size) as u32
}
#[inline]
pub fn reserve(&mut self, capacity: usize, device: &RenderDevice) {
self.uniform_vec.reserve(capacity, device);
}
#[inline]
pub fn write_buffer(&mut self, device: &RenderDevice, queue: &RenderQueue) {
self.uniform_vec.write_buffer(device, queue);
}
#[inline]
pub fn clear(&mut self) {
self.uniform_vec.clear();
}
}

View file

@ -12,7 +12,7 @@ use crate::{
camera::ExtractedCamera,
prelude::Image,
render_asset::RenderAssets,
render_resource::{std140::AsStd140, DynamicUniformVec, Texture, TextureView},
render_resource::{DynamicUniformBuffer, ShaderType, Texture, TextureView},
renderer::{RenderDevice, RenderQueue},
texture::{BevyDefault, TextureCache},
RenderApp, RenderStage,
@ -83,7 +83,7 @@ pub struct ExtractedView {
pub height: u32,
}
#[derive(Clone, AsStd140)]
#[derive(Clone, ShaderType)]
pub struct ViewUniform {
view_proj: Mat4,
view: Mat4,
@ -96,7 +96,7 @@ pub struct ViewUniform {
#[derive(Default)]
pub struct ViewUniforms {
pub uniforms: DynamicUniformVec<ViewUniform>,
pub uniforms: DynamicUniformBuffer<ViewUniform>,
}
#[derive(Component)]

View file

@ -7,10 +7,7 @@ use bevy_render::{
color::Color,
prelude::Shader,
render_asset::{PrepareAssetError, RenderAsset, RenderAssets},
render_resource::{
std140::{AsStd140, Std140},
*,
},
render_resource::*,
renderer::RenderDevice,
texture::Image,
};
@ -92,7 +89,7 @@ bitflags::bitflags! {
}
/// The GPU representation of the uniform data of a [`ColorMaterial`].
#[derive(Clone, Default, AsStd140)]
#[derive(Clone, Default, ShaderType)]
pub struct ColorMaterialUniformData {
pub color: Vec4,
pub flags: u32,
@ -145,12 +142,15 @@ impl RenderAsset for ColorMaterial {
color: material.color.as_linear_rgba_f32().into(),
flags: flags.bits(),
};
let value_std140 = value.as_std140();
let byte_buffer = [0u8; ColorMaterialUniformData::SIZE.get() as usize];
let mut buffer = encase::UniformBuffer::new(byte_buffer);
buffer.write(&value).unwrap();
let buffer = render_device.create_buffer_with_data(&BufferInitDescriptor {
label: Some("color_material_uniform_buffer"),
usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST,
contents: value_std140.as_bytes(),
contents: buffer.as_ref(),
});
let bind_group = render_device.create_bind_group(&BindGroupDescriptor {
entries: &[
@ -201,9 +201,7 @@ impl Material2d for ColorMaterial {
ty: BindingType::Buffer {
ty: BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: BufferSize::new(
ColorMaterialUniformData::std140_size_static() as u64,
),
min_binding_size: Some(ColorMaterialUniformData::min_size()),
},
count: None,
},

View file

@ -11,7 +11,7 @@ use bevy_render::{
render_asset::RenderAssets,
render_component::{ComponentUniforms, DynamicUniformIndex, UniformComponentPlugin},
render_phase::{EntityRenderCommand, RenderCommandResult, TrackedRenderPass},
render_resource::{std140::AsStd140, *},
render_resource::*,
renderer::{RenderDevice, RenderQueue},
texture::{BevyDefault, GpuImage, Image, TextureFormatPixelInfo},
view::{ComputedVisibility, ExtractedView, ViewUniform, ViewUniformOffset, ViewUniforms},
@ -71,7 +71,7 @@ impl Plugin for Mesh2dRenderPlugin {
}
}
#[derive(Component, AsStd140, Clone)]
#[derive(Component, ShaderType, Clone)]
pub struct Mesh2dUniform {
pub transform: Mat4,
pub inverse_transpose_model: Mat4,
@ -134,7 +134,7 @@ impl FromWorld for Mesh2dPipeline {
ty: BindingType::Buffer {
ty: BufferBindingType::Uniform,
has_dynamic_offset: true,
min_binding_size: BufferSize::new(ViewUniform::std140_size_static() as u64),
min_binding_size: Some(ViewUniform::min_size()),
},
count: None,
},
@ -149,7 +149,7 @@ impl FromWorld for Mesh2dPipeline {
ty: BindingType::Buffer {
ty: BufferBindingType::Uniform,
has_dynamic_offset: true,
min_binding_size: BufferSize::new(Mesh2dUniform::std140_size_static() as u64),
min_binding_size: Some(Mesh2dUniform::min_size()),
},
count: None,
}],

View file

@ -19,7 +19,7 @@ use bevy_render::{
BatchedPhaseItem, DrawFunctions, EntityRenderCommand, RenderCommand, RenderCommandResult,
RenderPhase, SetItemPipeline, TrackedRenderPass,
},
render_resource::{std140::AsStd140, *},
render_resource::*,
renderer::{RenderDevice, RenderQueue},
texture::{BevyDefault, Image},
view::{Msaa, ViewUniform, ViewUniformOffset, ViewUniforms, Visibility},
@ -48,7 +48,7 @@ impl FromWorld for SpritePipeline {
ty: BindingType::Buffer {
ty: BufferBindingType::Uniform,
has_dynamic_offset: true,
min_binding_size: BufferSize::new(ViewUniform::std140_size_static() as u64),
min_binding_size: Some(ViewUniform::min_size()),
},
count: None,
}],

View file

@ -1,9 +1,6 @@
use bevy_ecs::prelude::*;
use bevy_render::{
render_resource::{std140::AsStd140, *},
renderer::RenderDevice,
texture::BevyDefault,
view::ViewUniform,
render_resource::*, renderer::RenderDevice, texture::BevyDefault, view::ViewUniform,
};
pub struct UiPipeline {
@ -23,7 +20,7 @@ impl FromWorld for UiPipeline {
ty: BindingType::Buffer {
ty: BufferBindingType::Uniform,
has_dynamic_offset: true,
min_binding_size: BufferSize::new(ViewUniform::std140_size_static() as u64),
min_binding_size: Some(ViewUniform::min_size()),
},
count: None,
}],

View file

@ -7,7 +7,6 @@ yanked = "deny"
notice = "deny"
ignore = [
"RUSTSEC-2020-0056", # from gilrs v0.8.1 - unmaintained - https://github.com/koute/stdweb/issues/403
"RUSTSEC-2020-0095", # from crevice dev dependency - unmaintained - https://github.com/johannhof/difference.rs/issues/45
]
[licenses]
@ -15,6 +14,7 @@ unlicensed = "deny"
copyleft = "deny"
allow = [
"MIT",
"MIT-0",
"Apache-2.0",
"BSD-3-Clause",
"ISC",

View file

@ -9,11 +9,10 @@ use bevy::{
mesh::{MeshVertexAttribute, MeshVertexBufferLayout},
render_asset::{PrepareAssetError, RenderAsset},
render_resource::{
std140::{AsStd140, Std140},
BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout,
BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingType, Buffer,
BufferBindingType, BufferInitDescriptor, BufferSize, BufferUsages,
RenderPipelineDescriptor, ShaderStages, SpecializedMeshPipelineError, VertexFormat,
BufferBindingType, BufferInitDescriptor, BufferUsages, RenderPipelineDescriptor,
ShaderSize, ShaderStages, ShaderType, SpecializedMeshPipelineError, VertexFormat,
},
renderer::RenderDevice,
},
@ -89,8 +88,13 @@ impl RenderAsset for CustomMaterial {
(render_device, material_pipeline): &mut SystemParamItem<Self::Param>,
) -> Result<Self::PreparedAsset, PrepareAssetError<Self::ExtractedAsset>> {
let color = Vec4::from_slice(&extracted_asset.color.as_linear_rgba_f32());
let byte_buffer = [0u8; Vec4::SIZE.get() as usize];
let mut buffer = bevy::render::render_resource::encase::UniformBuffer::new(byte_buffer);
buffer.write(&color).unwrap();
let buffer = render_device.create_buffer_with_data(&BufferInitDescriptor {
contents: color.as_std140().as_bytes(),
contents: buffer.as_ref(),
label: None,
usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST,
});
@ -130,7 +134,7 @@ impl Material for CustomMaterial {
ty: BindingType::Buffer {
ty: BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: BufferSize::new(Vec4::std140_size_static() as u64),
min_binding_size: Some(Vec4::min_size()),
},
count: None,
}],

View file

@ -8,10 +8,10 @@ use bevy::{
render::{
render_asset::{PrepareAssetError, RenderAsset},
render_resource::{
std140::{AsStd140, Std140},
BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout,
encase, BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout,
BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingType, Buffer,
BufferBindingType, BufferInitDescriptor, BufferSize, BufferUsages, ShaderStages,
BufferBindingType, BufferInitDescriptor, BufferUsages, ShaderSize, ShaderStages,
ShaderType,
},
renderer::RenderDevice,
},
@ -75,8 +75,13 @@ impl RenderAsset for CustomMaterial {
(render_device, material_pipeline): &mut SystemParamItem<Self::Param>,
) -> Result<Self::PreparedAsset, PrepareAssetError<Self::ExtractedAsset>> {
let color = Vec4::from_slice(&extracted_asset.color.as_linear_rgba_f32());
let byte_buffer = [0u8; Vec4::SIZE.get() as usize];
let mut buffer = encase::UniformBuffer::new(byte_buffer);
buffer.write(&color).unwrap();
let buffer = render_device.create_buffer_with_data(&BufferInitDescriptor {
contents: color.as_std140().as_bytes(),
contents: buffer.as_ref(),
label: None,
usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST,
});
@ -123,7 +128,7 @@ impl Material for CustomMaterial {
ty: BindingType::Buffer {
ty: BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: BufferSize::new(Vec4::std140_size_static() as u64),
min_binding_size: Some(Vec4::min_size()),
},
count: None,
}],

View file

@ -8,10 +8,7 @@ use bevy::{
render::{
mesh::MeshVertexBufferLayout,
render_asset::{PrepareAssetError, RenderAsset},
render_resource::{
std140::{AsStd140, Std140},
*,
},
render_resource::*,
renderer::RenderDevice,
},
};
@ -72,8 +69,13 @@ impl RenderAsset for CustomMaterial {
(render_device, material_pipeline): &mut SystemParamItem<Self::Param>,
) -> Result<Self::PreparedAsset, PrepareAssetError<Self::ExtractedAsset>> {
let color = Vec4::from_slice(&extracted_asset.color.as_linear_rgba_f32());
let byte_buffer = [0u8; Vec4::SIZE.get() as usize];
let mut buffer = encase::UniformBuffer::new(byte_buffer);
buffer.write(&color).unwrap();
let buffer = render_device.create_buffer_with_data(&BufferInitDescriptor {
contents: color.as_std140().as_bytes(),
contents: buffer.as_ref(),
label: None,
usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST,
});
@ -129,7 +131,7 @@ impl SpecializedMaterial for CustomMaterial {
ty: BindingType::Buffer {
ty: BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: BufferSize::new(Vec4::std140_size_static() as u64),
min_binding_size: Some(Vec4::min_size()),
},
count: None,
}],

View file

@ -20,8 +20,7 @@ crates=(
bevy_hierarchy
bevy_transform
bevy_window
bevy_crevice/bevy-crevice-derive
bevy_crevice
bevy_encase_derive
bevy_render
bevy_core_pipeline
bevy_input