
Currently, our batchable binned items are stored in a hash table that maps bin key, which includes the batch set key, to a list of entities. Multidraw is handled by sorting the bin keys and accumulating adjacent bins that can be multidrawn together (i.e. have the same batch set key) into multidraw commands during `batch_and_prepare_binned_render_phase`. This is reasonably efficient right now, but it will complicate future work to retain indirect draw parameters from frame to frame. Consider what must happen when we have retained indirect draw parameters and the application adds a bin (i.e. a new mesh) that shares a batch set key with some pre-existing meshes. (That is, the new mesh can be multidrawn with the pre-existing meshes.) To be maximally efficient, our goal in that scenario will be to update *only* the indirect draw parameters for the batch set (i.e. multidraw command) containing the mesh that was added, while leaving the others alone. That means that we have to quickly locate all the bins that belong to the batch set being modified. In the existing code, we would have to sort the list of bin keys so that bins that can be multidrawn together become adjacent to one another in the list. Then we would have to do a binary search through the sorted list to find the location of the bin that was just added. Next, we would have to widen our search to adjacent indexes that contain the same batch set, doing expensive comparisons against the batch set key every time. Finally, we would reallocate the indirect draw parameters and update the stored pointers to the indirect draw parameters that the bins store. By contrast, it'd be dramatically simpler if we simply changed the way bins are stored to first map from batch set key (i.e. multidraw command) to the bins (i.e. meshes) within that batch set key, and then from each individual bin to the mesh instances. That way, the scenario above in which we add a new mesh will be simpler to handle. First, we will look up the batch set key corresponding to that mesh in the outer map to find an inner map corresponding to the single multidraw command that will draw that batch set. We will know how many meshes the multidraw command is going to draw by the size of that inner map. Then we simply need to reallocate the indirect draw parameters and update the pointers to those parameters within the bins as necessary. There will be no need to do any binary search or expensive batch set key comparison: only a single hash lookup and an iteration over the inner map to update the pointers. This patch implements the above technique. Because we don't have retained bins yet, this PR provides no performance benefits. However, it opens the door to maximally efficient updates when only a small number of meshes change from frame to frame. The main churn that this patch causes is that the *batch set key* (which uniquely specifies a multidraw command) and *bin key* (which uniquely specifies a mesh *within* that multidraw command) are now separate, instead of the batch set key being embedded *within* the bin key. In order to isolate potential regressions, I think that at least #16890, #16836, and #16825 should land before this PR does. ## Migration Guide * The *batch set key* is now separate from the *bin key* in `BinnedPhaseItem`. The batch set key is used to collect multidrawable meshes together. If you aren't using the multidraw feature, you can safely set the batch set key to `()`.
125 lines
3.6 KiB
Rust
125 lines
3.6 KiB
Rust
//! This example demonstrates how to use a storage buffer with `AsBindGroup` in a custom material.
|
|
use std::array;
|
|
|
|
use bevy::{
|
|
prelude::*,
|
|
reflect::TypePath,
|
|
render::{
|
|
render_resource::{AsBindGroup, ShaderRef},
|
|
storage::ShaderStorageBuffer,
|
|
},
|
|
};
|
|
|
|
const SHADER_ASSET_PATH: &str = "shaders/storage_buffer.wgsl";
|
|
|
|
fn main() {
|
|
App::new()
|
|
.add_plugins((DefaultPlugins, MaterialPlugin::<CustomMaterial>::default()))
|
|
.add_systems(Startup, setup)
|
|
.add_systems(Update, update)
|
|
.run();
|
|
}
|
|
|
|
/// set up a simple 3D scene
|
|
fn setup(
|
|
mut commands: Commands,
|
|
mut meshes: ResMut<Assets<Mesh>>,
|
|
mut buffers: ResMut<Assets<ShaderStorageBuffer>>,
|
|
mut materials: ResMut<Assets<CustomMaterial>>,
|
|
) {
|
|
// Example data for the storage buffer
|
|
let color_data: Vec<[f32; 4]> = vec![
|
|
[1.0, 0.0, 0.0, 1.0],
|
|
[0.0, 1.0, 0.0, 1.0],
|
|
[0.0, 0.0, 1.0, 1.0],
|
|
[1.0, 1.0, 0.0, 1.0],
|
|
[0.0, 1.0, 1.0, 1.0],
|
|
];
|
|
|
|
let colors = buffers.add(ShaderStorageBuffer::from(color_data));
|
|
|
|
// Create the custom material with the storage buffer
|
|
let material_handles: [Handle<CustomMaterial>; 5] = array::from_fn(|color_id| {
|
|
materials.add(CustomMaterial {
|
|
colors: colors.clone(),
|
|
color_id: color_id as u32,
|
|
})
|
|
});
|
|
|
|
commands.insert_resource(CustomMaterialHandles(material_handles.clone()));
|
|
|
|
// Spawn cubes with the custom material
|
|
let mut current_color_id = 0;
|
|
for i in -6..=6 {
|
|
for j in -3..=3 {
|
|
commands.spawn((
|
|
Mesh3d(meshes.add(Cuboid::from_size(Vec3::splat(0.3)))),
|
|
MeshMaterial3d(material_handles[current_color_id % 5].clone()),
|
|
Transform::from_xyz(i as f32, j as f32, 0.0),
|
|
));
|
|
current_color_id += 1;
|
|
}
|
|
}
|
|
|
|
// Camera
|
|
commands.spawn((
|
|
Camera3d::default(),
|
|
Transform::from_xyz(0.0, 0.0, 10.0).looking_at(Vec3::ZERO, Vec3::Y),
|
|
));
|
|
}
|
|
|
|
// Update the material color by time
|
|
fn update(
|
|
time: Res<Time>,
|
|
material_handles: Res<CustomMaterialHandles>,
|
|
mut materials: ResMut<Assets<CustomMaterial>>,
|
|
mut buffers: ResMut<Assets<ShaderStorageBuffer>>,
|
|
) {
|
|
// All materials use the same buffer, so we only need to update one of them.
|
|
// But we do need to at least mark the others as changed, so that Bevy will
|
|
// reupload their contents to the GPU.
|
|
for material in &material_handles.0[1..] {
|
|
materials.get_mut(material);
|
|
}
|
|
let material = materials.get_mut(&material_handles.0[0]).unwrap();
|
|
|
|
let buffer = buffers.get_mut(&material.colors).unwrap();
|
|
buffer.set_data(
|
|
(0..5)
|
|
.map(|i| {
|
|
let t = time.elapsed_secs() * 5.0;
|
|
[
|
|
ops::sin(t + i as f32) / 2.0 + 0.5,
|
|
ops::sin(t + i as f32 + 2.0) / 2.0 + 0.5,
|
|
ops::sin(t + i as f32 + 4.0) / 2.0 + 0.5,
|
|
1.0,
|
|
]
|
|
})
|
|
.collect::<Vec<[f32; 4]>>()
|
|
.as_slice(),
|
|
);
|
|
}
|
|
|
|
// Holds handles to the custom materials
|
|
#[derive(Resource)]
|
|
struct CustomMaterialHandles([Handle<CustomMaterial>; 5]);
|
|
|
|
// This struct defines the data that will be passed to your shader
|
|
#[derive(Asset, TypePath, AsBindGroup, Debug, Clone)]
|
|
struct CustomMaterial {
|
|
#[storage(0, read_only)]
|
|
colors: Handle<ShaderStorageBuffer>,
|
|
#[uniform(1)]
|
|
color_id: u32,
|
|
}
|
|
|
|
impl Material for CustomMaterial {
|
|
fn vertex_shader() -> ShaderRef {
|
|
SHADER_ASSET_PATH.into()
|
|
}
|
|
|
|
fn fragment_shader() -> ShaderRef {
|
|
SHADER_ASSET_PATH.into()
|
|
}
|
|
}
|