Last active
August 7, 2023 16:56
-
-
Save maximetinu/0e889e60ed3cef9fbabb7c61eaf41a1b to your computer and use it in GitHub Desktop.
Example benchmark for Bevy 0.11 to replicate a performance bottleneck when updating too many individual materials at runtime
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use bevy::{ | |
prelude::*, | |
reflect::{TypePath, TypeUuid}, | |
render::render_resource::{AsBindGroup, ShaderRef}, | |
sprite::*, | |
}; | |
use bevy_internal::{ | |
diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin}, | |
sprite::Material2d, | |
window::PresentMode, | |
}; | |
use rand::Rng; | |
const CAMERA_SPEED: f32 = 1000.0; | |
fn main() { | |
App::new() | |
.add_plugins(( | |
LogDiagnosticsPlugin::default(), | |
FrameTimeDiagnosticsPlugin, | |
DefaultPlugins.set(WindowPlugin { | |
primary_window: Some(Window { | |
present_mode: PresentMode::AutoNoVsync, | |
..default() | |
}), | |
..default() | |
}), | |
Material2dPlugin::<CustomMaterial2d>::default(), | |
)) | |
.add_systems(Startup, setup) | |
.add_systems(Update, (move_camera, mutate_materials)) | |
.run(); | |
} | |
fn random_color() -> Color { | |
let mut rng = rand::thread_rng(); | |
Color::rgba(rng.gen(), rng.gen(), rng.gen(), rng.gen()) | |
} | |
fn setup( | |
mut commands: Commands, | |
mut meshes: ResMut<Assets<Mesh>>, | |
mut materials: ResMut<Assets<CustomMaterial2d>>, | |
asset_server: Res<AssetServer>, | |
) { | |
let mut rng = rand::thread_rng(); | |
// Separation between the images | |
let tile_size = Vec2::splat(64.0); | |
// The amount of entities will be determined by this size | |
// 80 is 80 * 80 = 6400 entities | |
let map_size = Vec2::splat(80.0); | |
let half_x = (map_size.x / 2.0) as i32; | |
let half_y = (map_size.y / 2.0) as i32; | |
let sprite_handle = asset_server.load("branding/icon.png"); | |
// Can create the mesh handle beforehand | |
let mesh_handle = meshes.add(Mesh::from(shape::Quad::new(Vec2::ONE))); | |
// But can't do the same for the materials | |
// This would have a better performance, but in my real world scenario, I can't, | |
// because the quad size and material parameters are dynamic and only known at runtime | |
// let material_handle_beforehand = materials.add(CustomMaterial2d { | |
// color: random_color(), | |
// color_texture: Some(sprite_handle.clone()), | |
// }) | |
// Besides, I need to update the properties of the material of some of them dynamically, | |
// which I cannot know in advance. This is the reason for the bad performance | |
let mut sprites = vec![]; | |
for y in -half_y..half_y { | |
for x in -half_x..half_x { | |
let position = Vec2::new(x as f32, y as f32); | |
let translation = (position * tile_size).extend(rng.gen::<f32>()); | |
let rotation = Quat::from_rotation_z(rng.gen::<f32>()); | |
let scale = Vec3::splat(rng.gen::<f32>() * 2.0) * 256.0; | |
sprites.push(MaterialMesh2dBundle { | |
mesh: mesh_handle.clone().into(), | |
transform: Transform { | |
translation, | |
rotation, | |
scale, | |
}, | |
// material: material_handle_beforehand.clone(), | |
material: materials.add(CustomMaterial2d { | |
color: random_color(), | |
color_texture: Some(sprite_handle.clone()), | |
}), | |
..Default::default() | |
}); | |
} | |
} | |
commands.spawn_batch(sprites); | |
commands.spawn(Camera2dBundle::default()); | |
} | |
impl Material2d for CustomMaterial2d { | |
fn fragment_shader() -> ShaderRef { | |
"shaders/custom_material.wgsl".into() | |
} | |
} | |
#[derive(AsBindGroup, TypeUuid, TypePath, Debug, Clone)] | |
#[uuid = "b8f64602-437b-4ed4-a054-9595585fd74d"] | |
pub struct CustomMaterial2d { | |
#[uniform(0)] | |
color: Color, | |
#[texture(1)] | |
#[sampler(2)] | |
color_texture: Option<Handle<Image>>, | |
} | |
fn move_camera(time: Res<Time>, mut camera_query: Query<&mut Transform, With<Camera>>) { | |
let mut camera_transform = camera_query.single_mut(); | |
camera_transform.rotate_z(time.delta_seconds() * 0.5); | |
*camera_transform = *camera_transform | |
* Transform::from_translation(Vec3::X * CAMERA_SPEED * time.delta_seconds()); | |
} | |
// Simulate materials mutation at runtime | |
// This is another reason for bad performance. With no mutation, performance is still good. | |
fn mutate_materials( | |
mesh2d_materials: Query<&Handle<CustomMaterial2d>>, | |
mut mats: ResMut<Assets<CustomMaterial2d>>, | |
) { | |
for quad_mat in mesh2d_materials.iter() { | |
let mat = mats.get_mut(quad_mat).unwrap(); | |
mat.color = mat.color + Color::rgba(0.00001, 0.00001, 0.00001, 0.00001); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment