improve shader import model (#5703)
# Objective
operate on naga IR directly to improve handling of shader modules.
- give codespan reporting into imported modules
- allow glsl to be used from wgsl and vice-versa
the ultimate objective is to make it possible to
- provide user hooks for core shader functions (to modify light
behaviour within the standard pbr pipeline, for example)
- make automatic binding slot allocation possible
but ... since this is already big, adds some value and (i think) is at
feature parity with the existing code, i wanted to push this now.
## Solution
i made a crate called naga_oil (https://github.com/robtfm/naga_oil -
unpublished for now, could be part of bevy) which manages modules by
- building each module independantly to naga IR
- creating "header" files for each supported language, which are used to
build dependent modules/shaders
- make final shaders by combining the shader IR with the IR for imported
modules
then integrated this into bevy, replacing some of the existing shader
processing stuff. also reworked examples to reflect this.
## Migration Guide
shaders that don't use `#import` directives should work without changes.
the most notable user-facing difference is that imported
functions/variables/etc need to be qualified at point of use, and
there's no "leakage" of visible stuff into your shader scope from the
imports of your imports, so if you used things imported by your imports,
you now need to import them directly and qualify them.
the current strategy of including/'spreading' `mesh_vertex_output`
directly into a struct doesn't work any more, so these need to be
modified as per the examples (e.g. color_material.wgsl, or many others).
mesh data is assumed to be in bindgroup 2 by default, if mesh data is
bound into bindgroup 1 instead then the shader def `MESH_BINDGROUP_1`
needs to be added to the pipeline shader_defs.
2023-06-27 00:29:22 +00:00
|
|
|
#import bevy_pbr::mesh_bindings mesh
|
Reduce the size of MeshUniform to improve performance (#9416)
# Objective
- Significantly reduce the size of MeshUniform by only including
necessary data.
## Solution
Local to world, model transforms are affine. This means they only need a
4x3 matrix to represent them.
`MeshUniform` stores the current, and previous model transforms, and the
inverse transpose of the current model transform, all as 4x4 matrices.
Instead we can store the current, and previous model transforms as 4x3
matrices, and we only need the upper-left 3x3 part of the inverse
transpose of the current model transform. This change allows us to
reduce the serialized MeshUniform size from 208 bytes to 144 bytes,
which is over a 30% saving in data to serialize, and VRAM bandwidth and
space.
## Benchmarks
On an M1 Max, running `many_cubes -- sphere`, main is in yellow, this PR
is in red:
<img width="1484" alt="Screenshot 2023-08-11 at 02 36 43"
src="https://github.com/bevyengine/bevy/assets/302146/7d99c7b3-f2bb-4004-a8d0-4c00f755cb0d">
A reduction in frame time of ~14%.
---
## Changelog
- Changed: Redefined `MeshUniform` to improve performance by using 4x3
affine transforms and reconstructing 4x4 matrices in the shader. Helper
functions were added to `bevy_pbr::mesh_functions` to unpack the data.
`affine_to_square` converts the packed 4x3 in 3x4 matrix data to a 4x4
matrix. `mat2x4_f32_to_mat3x3` converts the 3x3 in mat2x4 + f32 matrix
data back into a 3x3.
## Migration Guide
Shader code before:
```
var model = mesh[instance_index].model;
```
Shader code after:
```
#import bevy_pbr::mesh_functions affine_to_square
var model = affine_to_square(mesh[instance_index].model);
```
2023-08-15 06:00:23 +00:00
|
|
|
#import bevy_pbr::mesh_functions get_model_matrix, mesh_position_local_to_clip
|
2023-09-11 19:14:15 +00:00
|
|
|
#import bevy_pbr::morph
|
2022-06-14 00:32:33 +00:00
|
|
|
|
|
|
|
#ifdef SKINNED
|
improve shader import model (#5703)
# Objective
operate on naga IR directly to improve handling of shader modules.
- give codespan reporting into imported modules
- allow glsl to be used from wgsl and vice-versa
the ultimate objective is to make it possible to
- provide user hooks for core shader functions (to modify light
behaviour within the standard pbr pipeline, for example)
- make automatic binding slot allocation possible
but ... since this is already big, adds some value and (i think) is at
feature parity with the existing code, i wanted to push this now.
## Solution
i made a crate called naga_oil (https://github.com/robtfm/naga_oil -
unpublished for now, could be part of bevy) which manages modules by
- building each module independantly to naga IR
- creating "header" files for each supported language, which are used to
build dependent modules/shaders
- make final shaders by combining the shader IR with the IR for imported
modules
then integrated this into bevy, replacing some of the existing shader
processing stuff. also reworked examples to reflect this.
## Migration Guide
shaders that don't use `#import` directives should work without changes.
the most notable user-facing difference is that imported
functions/variables/etc need to be qualified at point of use, and
there's no "leakage" of visible stuff into your shader scope from the
imports of your imports, so if you used things imported by your imports,
you now need to import them directly and qualify them.
the current strategy of including/'spreading' `mesh_vertex_output`
directly into a struct doesn't work any more, so these need to be
modified as per the examples (e.g. color_material.wgsl, or many others).
mesh data is assumed to be in bindgroup 2 by default, if mesh data is
bound into bindgroup 1 instead then the shader def `MESH_BINDGROUP_1`
needs to be added to the pipeline shader_defs.
2023-06-27 00:29:22 +00:00
|
|
|
#import bevy_pbr::skinning
|
2022-06-14 00:32:33 +00:00
|
|
|
#endif
|
|
|
|
|
2021-12-10 21:09:36 +00:00
|
|
|
struct Vertex {
|
Use GpuArrayBuffer for MeshUniform (#9254)
# Objective
- Reduce the number of rebindings to enable batching of draw commands
## Solution
- Use the new `GpuArrayBuffer` for `MeshUniform` data to store all
`MeshUniform` data in arrays within fewer bindings
- Sort opaque/alpha mask prepass, opaque/alpha mask main, and shadow
phases also by the batch per-object data binding dynamic offset to
improve performance on WebGL2.
---
## Changelog
- Changed: Per-object `MeshUniform` data is now managed by
`GpuArrayBuffer` as arrays in buffers that need to be indexed into.
## Migration Guide
Accessing the `model` member of an individual mesh object's shader
`Mesh` struct the old way where each `MeshUniform` was stored at its own
dynamic offset:
```rust
struct Vertex {
@location(0) position: vec3<f32>,
};
fn vertex(vertex: Vertex) -> VertexOutput {
var out: VertexOutput;
out.clip_position = mesh_position_local_to_clip(
mesh.model,
vec4<f32>(vertex.position, 1.0)
);
return out;
}
```
The new way where one needs to index into the array of `Mesh`es for the
batch:
```rust
struct Vertex {
@builtin(instance_index) instance_index: u32,
@location(0) position: vec3<f32>,
};
fn vertex(vertex: Vertex) -> VertexOutput {
var out: VertexOutput;
out.clip_position = mesh_position_local_to_clip(
mesh[vertex.instance_index].model,
vec4<f32>(vertex.position, 1.0)
);
return out;
}
```
Note that using the instance_index is the default way to pass the
per-object index into the shader, but if you wish to do custom rendering
approaches you can pass it in however you like.
---------
Co-authored-by: robtfm <50659922+robtfm@users.noreply.github.com>
Co-authored-by: Elabajaba <Elabajaba@users.noreply.github.com>
2023-07-30 13:17:08 +00:00
|
|
|
@builtin(instance_index) instance_index: u32,
|
2022-07-14 21:17:16 +00:00
|
|
|
@location(0) position: vec3<f32>,
|
2022-03-29 18:31:13 +00:00
|
|
|
#ifdef SKINNED
|
2023-09-11 19:14:15 +00:00
|
|
|
@location(5) joint_indexes: vec4<u32>,
|
|
|
|
@location(6) joint_weights: vec4<f32>,
|
|
|
|
#endif
|
|
|
|
#ifdef MORPH_TARGETS
|
|
|
|
@builtin(vertex_index) index: u32,
|
2022-03-29 18:31:13 +00:00
|
|
|
#endif
|
2021-12-10 21:09:36 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
struct VertexOutput {
|
2022-07-14 21:17:16 +00:00
|
|
|
@builtin(position) clip_position: vec4<f32>,
|
2021-12-10 21:09:36 +00:00
|
|
|
};
|
|
|
|
|
2023-09-11 19:14:15 +00:00
|
|
|
|
|
|
|
#ifdef MORPH_TARGETS
|
|
|
|
fn morph_vertex(vertex_in: Vertex) -> Vertex {
|
|
|
|
var vertex = vertex_in;
|
|
|
|
let weight_count = bevy_pbr::morph::layer_count();
|
|
|
|
for (var i: u32 = 0u; i < weight_count; i ++) {
|
|
|
|
let weight = bevy_pbr::morph::weight_at(i);
|
|
|
|
if weight == 0.0 {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
vertex.position += weight * bevy_pbr::morph::morph(vertex.index, bevy_pbr::morph::position_offset, i);
|
|
|
|
}
|
|
|
|
return vertex;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2022-07-14 21:17:16 +00:00
|
|
|
@vertex
|
2023-09-11 19:14:15 +00:00
|
|
|
fn vertex(vertex_no_morph: Vertex) -> VertexOutput {
|
|
|
|
|
|
|
|
#ifdef MORPH_TARGETS
|
|
|
|
var vertex = morph_vertex(vertex_no_morph);
|
|
|
|
#else
|
|
|
|
var vertex = vertex_no_morph;
|
|
|
|
#endif
|
|
|
|
|
2022-03-29 18:31:13 +00:00
|
|
|
#ifdef SKINNED
|
improve shader import model (#5703)
# Objective
operate on naga IR directly to improve handling of shader modules.
- give codespan reporting into imported modules
- allow glsl to be used from wgsl and vice-versa
the ultimate objective is to make it possible to
- provide user hooks for core shader functions (to modify light
behaviour within the standard pbr pipeline, for example)
- make automatic binding slot allocation possible
but ... since this is already big, adds some value and (i think) is at
feature parity with the existing code, i wanted to push this now.
## Solution
i made a crate called naga_oil (https://github.com/robtfm/naga_oil -
unpublished for now, could be part of bevy) which manages modules by
- building each module independantly to naga IR
- creating "header" files for each supported language, which are used to
build dependent modules/shaders
- make final shaders by combining the shader IR with the IR for imported
modules
then integrated this into bevy, replacing some of the existing shader
processing stuff. also reworked examples to reflect this.
## Migration Guide
shaders that don't use `#import` directives should work without changes.
the most notable user-facing difference is that imported
functions/variables/etc need to be qualified at point of use, and
there's no "leakage" of visible stuff into your shader scope from the
imports of your imports, so if you used things imported by your imports,
you now need to import them directly and qualify them.
the current strategy of including/'spreading' `mesh_vertex_output`
directly into a struct doesn't work any more, so these need to be
modified as per the examples (e.g. color_material.wgsl, or many others).
mesh data is assumed to be in bindgroup 2 by default, if mesh data is
bound into bindgroup 1 instead then the shader def `MESH_BINDGROUP_1`
needs to be added to the pipeline shader_defs.
2023-06-27 00:29:22 +00:00
|
|
|
let model = bevy_pbr::skinning::skin_model(vertex.joint_indexes, vertex.joint_weights);
|
2022-03-29 18:31:13 +00:00
|
|
|
#else
|
2023-10-04 18:29:29 +00:00
|
|
|
// Use vertex_no_morph.instance_index instead of vertex.instance_index to work around a wgpu dx12 bug.
|
|
|
|
// See https://github.com/gfx-rs/naga/issues/2416 .
|
|
|
|
let model = get_model_matrix(vertex_no_morph.instance_index);
|
2022-03-29 18:31:13 +00:00
|
|
|
#endif
|
2021-12-10 21:09:36 +00:00
|
|
|
|
|
|
|
var out: VertexOutput;
|
2022-06-14 00:32:33 +00:00
|
|
|
out.clip_position = mesh_position_local_to_clip(model, vec4<f32>(vertex.position, 1.0));
|
2021-12-10 21:09:36 +00:00
|
|
|
return out;
|
|
|
|
}
|
|
|
|
|
2022-07-14 21:17:16 +00:00
|
|
|
@fragment
|
|
|
|
fn fragment() -> @location(0) vec4<f32> {
|
2021-12-10 21:09:36 +00:00
|
|
|
return vec4<f32>(1.0, 1.0, 1.0, 1.0);
|
|
|
|
}
|