# 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>
233 lines
8.0 KiB
Rust
233 lines
8.0 KiB
Rust
//! Bind group layout related definitions for the mesh pipeline.
|
|
|
|
use bevy_render::{
|
|
mesh::morph::MAX_MORPH_WEIGHTS,
|
|
render_resource::{
|
|
BindGroup, BindGroupDescriptor, BindGroupLayout, BindGroupLayoutDescriptor,
|
|
BindingResource, Buffer, TextureView,
|
|
},
|
|
renderer::RenderDevice,
|
|
};
|
|
|
|
const MORPH_WEIGHT_SIZE: usize = std::mem::size_of::<f32>();
|
|
pub const MORPH_BUFFER_SIZE: usize = MAX_MORPH_WEIGHTS * MORPH_WEIGHT_SIZE;
|
|
|
|
/// Individual layout entries.
|
|
mod layout_entry {
|
|
use super::MORPH_BUFFER_SIZE;
|
|
use crate::render::mesh::JOINT_BUFFER_SIZE;
|
|
use crate::MeshUniform;
|
|
use bevy_render::{
|
|
render_resource::{
|
|
BindGroupLayoutEntry, BindingType, BufferBindingType, BufferSize, GpuArrayBuffer,
|
|
ShaderStages, TextureSampleType, TextureViewDimension,
|
|
},
|
|
renderer::RenderDevice,
|
|
};
|
|
|
|
fn buffer(binding: u32, size: u64, visibility: ShaderStages) -> BindGroupLayoutEntry {
|
|
BindGroupLayoutEntry {
|
|
binding,
|
|
visibility,
|
|
count: None,
|
|
ty: BindingType::Buffer {
|
|
ty: BufferBindingType::Uniform,
|
|
has_dynamic_offset: true,
|
|
min_binding_size: BufferSize::new(size),
|
|
},
|
|
}
|
|
}
|
|
pub(super) fn model(render_device: &RenderDevice, binding: u32) -> BindGroupLayoutEntry {
|
|
GpuArrayBuffer::<MeshUniform>::binding_layout(
|
|
binding,
|
|
ShaderStages::VERTEX_FRAGMENT,
|
|
render_device,
|
|
)
|
|
}
|
|
pub(super) fn skinning(binding: u32) -> BindGroupLayoutEntry {
|
|
buffer(binding, JOINT_BUFFER_SIZE as u64, ShaderStages::VERTEX)
|
|
}
|
|
pub(super) fn weights(binding: u32) -> BindGroupLayoutEntry {
|
|
buffer(binding, MORPH_BUFFER_SIZE as u64, ShaderStages::VERTEX)
|
|
}
|
|
pub(super) fn targets(binding: u32) -> BindGroupLayoutEntry {
|
|
BindGroupLayoutEntry {
|
|
binding,
|
|
visibility: ShaderStages::VERTEX,
|
|
ty: BindingType::Texture {
|
|
view_dimension: TextureViewDimension::D3,
|
|
sample_type: TextureSampleType::Float { filterable: false },
|
|
multisampled: false,
|
|
},
|
|
count: None,
|
|
}
|
|
}
|
|
}
|
|
/// Individual [`BindGroupEntry`](bevy_render::render_resource::BindGroupEntry)
|
|
/// for bind groups.
|
|
mod entry {
|
|
use super::MORPH_BUFFER_SIZE;
|
|
use crate::render::mesh::JOINT_BUFFER_SIZE;
|
|
use bevy_render::render_resource::{
|
|
BindGroupEntry, BindingResource, Buffer, BufferBinding, BufferSize, TextureView,
|
|
};
|
|
|
|
fn entry(binding: u32, size: u64, buffer: &Buffer) -> BindGroupEntry {
|
|
BindGroupEntry {
|
|
binding,
|
|
resource: BindingResource::Buffer(BufferBinding {
|
|
buffer,
|
|
offset: 0,
|
|
size: Some(BufferSize::new(size).unwrap()),
|
|
}),
|
|
}
|
|
}
|
|
pub(super) fn model(binding: u32, resource: BindingResource) -> BindGroupEntry {
|
|
BindGroupEntry { binding, resource }
|
|
}
|
|
pub(super) fn skinning(binding: u32, buffer: &Buffer) -> BindGroupEntry {
|
|
entry(binding, JOINT_BUFFER_SIZE as u64, buffer)
|
|
}
|
|
pub(super) fn weights(binding: u32, buffer: &Buffer) -> BindGroupEntry {
|
|
entry(binding, MORPH_BUFFER_SIZE as u64, buffer)
|
|
}
|
|
pub(super) fn targets(binding: u32, texture: &TextureView) -> BindGroupEntry {
|
|
BindGroupEntry {
|
|
binding,
|
|
resource: BindingResource::TextureView(texture),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// All possible [`BindGroupLayout`]s in bevy's default mesh shader (`mesh.wgsl`).
|
|
#[derive(Clone)]
|
|
pub struct MeshLayouts {
|
|
/// The mesh model uniform (transform) and nothing else.
|
|
pub model_only: BindGroupLayout,
|
|
|
|
/// Also includes the uniform for skinning
|
|
pub skinned: BindGroupLayout,
|
|
|
|
/// Also includes the uniform and [`MorphAttributes`] for morph targets.
|
|
///
|
|
/// [`MorphAttributes`]: bevy_render::mesh::morph::MorphAttributes
|
|
pub morphed: BindGroupLayout,
|
|
|
|
/// Also includes both uniforms for skinning and morph targets, also the
|
|
/// morph target [`MorphAttributes`] binding.
|
|
///
|
|
/// [`MorphAttributes`]: bevy_render::mesh::morph::MorphAttributes
|
|
pub morphed_skinned: BindGroupLayout,
|
|
}
|
|
|
|
impl MeshLayouts {
|
|
/// Prepare the layouts used by the default bevy [`Mesh`].
|
|
///
|
|
/// [`Mesh`]: bevy_render::prelude::Mesh
|
|
pub fn new(render_device: &RenderDevice) -> Self {
|
|
MeshLayouts {
|
|
model_only: Self::model_only_layout(render_device),
|
|
skinned: Self::skinned_layout(render_device),
|
|
morphed: Self::morphed_layout(render_device),
|
|
morphed_skinned: Self::morphed_skinned_layout(render_device),
|
|
}
|
|
}
|
|
|
|
// ---------- create individual BindGroupLayouts ----------
|
|
|
|
fn model_only_layout(render_device: &RenderDevice) -> BindGroupLayout {
|
|
render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
|
|
entries: &[layout_entry::model(render_device, 0)],
|
|
label: Some("mesh_layout"),
|
|
})
|
|
}
|
|
fn skinned_layout(render_device: &RenderDevice) -> BindGroupLayout {
|
|
render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
|
|
entries: &[
|
|
layout_entry::model(render_device, 0),
|
|
layout_entry::skinning(1),
|
|
],
|
|
label: Some("skinned_mesh_layout"),
|
|
})
|
|
}
|
|
fn morphed_layout(render_device: &RenderDevice) -> BindGroupLayout {
|
|
render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
|
|
entries: &[
|
|
layout_entry::model(render_device, 0),
|
|
layout_entry::weights(2),
|
|
layout_entry::targets(3),
|
|
],
|
|
label: Some("morphed_mesh_layout"),
|
|
})
|
|
}
|
|
fn morphed_skinned_layout(render_device: &RenderDevice) -> BindGroupLayout {
|
|
render_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
|
|
entries: &[
|
|
layout_entry::model(render_device, 0),
|
|
layout_entry::skinning(1),
|
|
layout_entry::weights(2),
|
|
layout_entry::targets(3),
|
|
],
|
|
label: Some("morphed_skinned_mesh_layout"),
|
|
})
|
|
}
|
|
|
|
// ---------- BindGroup methods ----------
|
|
|
|
pub fn model_only(&self, render_device: &RenderDevice, model: &BindingResource) -> BindGroup {
|
|
render_device.create_bind_group(&BindGroupDescriptor {
|
|
entries: &[entry::model(0, model.clone())],
|
|
layout: &self.model_only,
|
|
label: Some("model_only_mesh_bind_group"),
|
|
})
|
|
}
|
|
pub fn skinned(
|
|
&self,
|
|
render_device: &RenderDevice,
|
|
model: &BindingResource,
|
|
skin: &Buffer,
|
|
) -> BindGroup {
|
|
render_device.create_bind_group(&BindGroupDescriptor {
|
|
entries: &[entry::model(0, model.clone()), entry::skinning(1, skin)],
|
|
layout: &self.skinned,
|
|
label: Some("skinned_mesh_bind_group"),
|
|
})
|
|
}
|
|
pub fn morphed(
|
|
&self,
|
|
render_device: &RenderDevice,
|
|
model: &BindingResource,
|
|
weights: &Buffer,
|
|
targets: &TextureView,
|
|
) -> BindGroup {
|
|
render_device.create_bind_group(&BindGroupDescriptor {
|
|
entries: &[
|
|
entry::model(0, model.clone()),
|
|
entry::weights(2, weights),
|
|
entry::targets(3, targets),
|
|
],
|
|
layout: &self.morphed,
|
|
label: Some("morphed_mesh_bind_group"),
|
|
})
|
|
}
|
|
pub fn morphed_skinned(
|
|
&self,
|
|
render_device: &RenderDevice,
|
|
model: &BindingResource,
|
|
skin: &Buffer,
|
|
weights: &Buffer,
|
|
targets: &TextureView,
|
|
) -> BindGroup {
|
|
render_device.create_bind_group(&BindGroupDescriptor {
|
|
entries: &[
|
|
entry::model(0, model.clone()),
|
|
entry::skinning(1, skin),
|
|
entry::weights(2, weights),
|
|
entry::targets(3, targets),
|
|
],
|
|
layout: &self.morphed_skinned,
|
|
label: Some("morphed_skinned_mesh_bind_group"),
|
|
})
|
|
}
|
|
}
|