mirror of
https://github.com/bevyengine/bevy
synced 2024-11-22 12:43:34 +00:00
Enhance many_cubes stress test use cases (#9596)
# Objective - Make `many_cubes` suitable for testing various parts of the upcoming batching work. ## Solution - Use `argh` for CLI. - Default to the sphere layout as it is more useful for benchmarking. - Add a benchmark mode that advances the camera by a fixed step to render the same frames across runs. - Add an option to vary the material data per-instance. The color is randomized. - Add an option to generate a number of textures and randomly choose one per instance. - Use seeded `StdRng` for deterministic random numbers.
This commit is contained in:
parent
02b520b4e8
commit
40c6b3b91e
2 changed files with 150 additions and 40 deletions
|
@ -261,6 +261,7 @@ bytemuck = "1.7"
|
||||||
# Needed to poll Task examples
|
# Needed to poll Task examples
|
||||||
futures-lite = "1.11.3"
|
futures-lite = "1.11.3"
|
||||||
crossbeam-channel = "0.5.0"
|
crossbeam-channel = "0.5.0"
|
||||||
|
argh = "0.1.12"
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "hello_world"
|
name = "hello_world"
|
||||||
|
|
|
@ -3,23 +3,68 @@
|
||||||
//! To measure performance realistically, be sure to run this in release mode.
|
//! To measure performance realistically, be sure to run this in release mode.
|
||||||
//! `cargo run --example many_cubes --release`
|
//! `cargo run --example many_cubes --release`
|
||||||
//!
|
//!
|
||||||
//! By default, this arranges the meshes in a cubical pattern, where the number of visible meshes
|
//! By default, this arranges the meshes in a spherical pattern that
|
||||||
//! varies with the viewing angle. You can choose to run the demo with a spherical pattern that
|
|
||||||
//! distributes the meshes evenly.
|
//! distributes the meshes evenly.
|
||||||
//!
|
//!
|
||||||
//! To start the demo using the spherical layout run
|
//! See `cargo run --example many_cubes --release -- --help` for more options.
|
||||||
//! `cargo run --example many_cubes --release sphere`
|
|
||||||
|
|
||||||
use std::f64::consts::PI;
|
use std::{f64::consts::PI, str::FromStr};
|
||||||
|
|
||||||
|
use argh::FromArgs;
|
||||||
use bevy::{
|
use bevy::{
|
||||||
diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
|
diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
|
||||||
math::{DVec2, DVec3},
|
math::{DVec2, DVec3},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
|
render::render_resource::{Extent3d, TextureDimension, TextureFormat},
|
||||||
window::{PresentMode, WindowPlugin},
|
window::{PresentMode, WindowPlugin},
|
||||||
};
|
};
|
||||||
|
use rand::{rngs::StdRng, seq::SliceRandom, Rng, SeedableRng};
|
||||||
|
|
||||||
|
#[derive(FromArgs, Resource)]
|
||||||
|
/// `many_cubes` stress test
|
||||||
|
struct Args {
|
||||||
|
/// how the cube instances should be positioned.
|
||||||
|
#[argh(option, default = "Layout::Sphere")]
|
||||||
|
layout: Layout,
|
||||||
|
|
||||||
|
/// whether to step the camera animation by a fixed amount such that each frame is the same across runs.
|
||||||
|
#[argh(switch)]
|
||||||
|
benchmark: bool,
|
||||||
|
|
||||||
|
/// whether to vary the material data in each instance.
|
||||||
|
#[argh(switch)]
|
||||||
|
vary_material_data: bool,
|
||||||
|
|
||||||
|
/// the number of different textures from which to randomly select the material base color. 0 means no textures.
|
||||||
|
#[argh(option, default = "0")]
|
||||||
|
material_texture_count: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Clone)]
|
||||||
|
enum Layout {
|
||||||
|
Cube,
|
||||||
|
#[default]
|
||||||
|
Sphere,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Layout {
|
||||||
|
type Err = String;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
match s {
|
||||||
|
"cube" => Ok(Self::Cube),
|
||||||
|
"sphere" => Ok(Self::Sphere),
|
||||||
|
_ => Err(format!(
|
||||||
|
"Unknown layout value: '{}', valid options: 'cube', 'sphere'",
|
||||||
|
s
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
let args: Args = argh::from_env();
|
||||||
|
|
||||||
App::new()
|
App::new()
|
||||||
.add_plugins((
|
.add_plugins((
|
||||||
DefaultPlugins.set(WindowPlugin {
|
DefaultPlugins.set(WindowPlugin {
|
||||||
|
@ -32,28 +77,36 @@ fn main() {
|
||||||
FrameTimeDiagnosticsPlugin,
|
FrameTimeDiagnosticsPlugin,
|
||||||
LogDiagnosticsPlugin::default(),
|
LogDiagnosticsPlugin::default(),
|
||||||
))
|
))
|
||||||
|
.insert_resource(args)
|
||||||
.add_systems(Startup, setup)
|
.add_systems(Startup, setup)
|
||||||
.add_systems(Update, (move_camera, print_mesh_count))
|
.add_systems(Update, (move_camera, print_mesh_count))
|
||||||
.run();
|
.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const WIDTH: usize = 200;
|
||||||
|
const HEIGHT: usize = 200;
|
||||||
|
|
||||||
fn setup(
|
fn setup(
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
|
args: Res<Args>,
|
||||||
mut meshes: ResMut<Assets<Mesh>>,
|
mut meshes: ResMut<Assets<Mesh>>,
|
||||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
material_assets: ResMut<Assets<StandardMaterial>>,
|
||||||
|
images: ResMut<Assets<Image>>,
|
||||||
) {
|
) {
|
||||||
warn!(include_str!("warning_string.txt"));
|
warn!(include_str!("warning_string.txt"));
|
||||||
|
|
||||||
const WIDTH: usize = 200;
|
let args = args.into_inner();
|
||||||
const HEIGHT: usize = 200;
|
let images = images.into_inner();
|
||||||
let mesh = meshes.add(Mesh::from(shape::Cube { size: 1.0 }));
|
let material_assets = material_assets.into_inner();
|
||||||
let material = materials.add(StandardMaterial {
|
|
||||||
base_color: Color::PINK,
|
|
||||||
..default()
|
|
||||||
});
|
|
||||||
|
|
||||||
match std::env::args().nth(1).as_deref() {
|
let mesh = meshes.add(Mesh::from(shape::Cube { size: 1.0 }));
|
||||||
Some("sphere") => {
|
|
||||||
|
let material_textures = init_textures(args, images);
|
||||||
|
let materials = init_materials(args, &material_textures, material_assets);
|
||||||
|
|
||||||
|
let mut material_rng = StdRng::seed_from_u64(42);
|
||||||
|
match args.layout {
|
||||||
|
Layout::Sphere => {
|
||||||
// NOTE: This pattern is good for testing performance of culling as it provides roughly
|
// NOTE: This pattern is good for testing performance of culling as it provides roughly
|
||||||
// the same number of visible meshes regardless of the viewing angle.
|
// the same number of visible meshes regardless of the viewing angle.
|
||||||
const N_POINTS: usize = WIDTH * HEIGHT * 4;
|
const N_POINTS: usize = WIDTH * HEIGHT * 4;
|
||||||
|
@ -65,8 +118,8 @@ fn setup(
|
||||||
fibonacci_spiral_on_sphere(golden_ratio, i, N_POINTS);
|
fibonacci_spiral_on_sphere(golden_ratio, i, N_POINTS);
|
||||||
let unit_sphere_p = spherical_polar_to_cartesian(spherical_polar_theta_phi);
|
let unit_sphere_p = spherical_polar_to_cartesian(spherical_polar_theta_phi);
|
||||||
commands.spawn(PbrBundle {
|
commands.spawn(PbrBundle {
|
||||||
mesh: mesh.clone_weak(),
|
mesh: mesh.clone(),
|
||||||
material: material.clone_weak(),
|
material: materials.choose(&mut material_rng).unwrap().clone(),
|
||||||
transform: Transform::from_translation((radius * unit_sphere_p).as_vec3()),
|
transform: Transform::from_translation((radius * unit_sphere_p).as_vec3()),
|
||||||
..default()
|
..default()
|
||||||
});
|
});
|
||||||
|
@ -86,14 +139,14 @@ fn setup(
|
||||||
}
|
}
|
||||||
// cube
|
// cube
|
||||||
commands.spawn(PbrBundle {
|
commands.spawn(PbrBundle {
|
||||||
mesh: mesh.clone_weak(),
|
mesh: mesh.clone(),
|
||||||
material: material.clone_weak(),
|
material: materials.choose(&mut material_rng).unwrap().clone(),
|
||||||
transform: Transform::from_xyz((x as f32) * 2.5, (y as f32) * 2.5, 0.0),
|
transform: Transform::from_xyz((x as f32) * 2.5, (y as f32) * 2.5, 0.0),
|
||||||
..default()
|
..default()
|
||||||
});
|
});
|
||||||
commands.spawn(PbrBundle {
|
commands.spawn(PbrBundle {
|
||||||
mesh: mesh.clone_weak(),
|
mesh: mesh.clone(),
|
||||||
material: material.clone_weak(),
|
material: materials.choose(&mut material_rng).unwrap().clone(),
|
||||||
transform: Transform::from_xyz(
|
transform: Transform::from_xyz(
|
||||||
(x as f32) * 2.5,
|
(x as f32) * 2.5,
|
||||||
HEIGHT as f32 * 2.5,
|
HEIGHT as f32 * 2.5,
|
||||||
|
@ -102,14 +155,14 @@ fn setup(
|
||||||
..default()
|
..default()
|
||||||
});
|
});
|
||||||
commands.spawn(PbrBundle {
|
commands.spawn(PbrBundle {
|
||||||
mesh: mesh.clone_weak(),
|
mesh: mesh.clone(),
|
||||||
material: material.clone_weak(),
|
material: materials.choose(&mut material_rng).unwrap().clone(),
|
||||||
transform: Transform::from_xyz((x as f32) * 2.5, 0.0, (y as f32) * 2.5),
|
transform: Transform::from_xyz((x as f32) * 2.5, 0.0, (y as f32) * 2.5),
|
||||||
..default()
|
..default()
|
||||||
});
|
});
|
||||||
commands.spawn(PbrBundle {
|
commands.spawn(PbrBundle {
|
||||||
mesh: mesh.clone_weak(),
|
mesh: mesh.clone(),
|
||||||
material: material.clone_weak(),
|
material: materials.choose(&mut material_rng).unwrap().clone(),
|
||||||
transform: Transform::from_xyz(0.0, (x as f32) * 2.5, (y as f32) * 2.5),
|
transform: Transform::from_xyz(0.0, (x as f32) * 2.5, (y as f32) * 2.5),
|
||||||
..default()
|
..default()
|
||||||
});
|
});
|
||||||
|
@ -123,22 +176,69 @@ fn setup(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// add one cube, the only one with strong handles
|
|
||||||
// also serves as a reference point during rotation
|
|
||||||
commands.spawn(PbrBundle {
|
|
||||||
mesh,
|
|
||||||
material,
|
|
||||||
transform: Transform {
|
|
||||||
translation: Vec3::new(0.0, HEIGHT as f32 * 2.5, 0.0),
|
|
||||||
scale: Vec3::splat(5.0),
|
|
||||||
..default()
|
|
||||||
},
|
|
||||||
..default()
|
|
||||||
});
|
|
||||||
|
|
||||||
commands.spawn(DirectionalLightBundle { ..default() });
|
commands.spawn(DirectionalLightBundle { ..default() });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn init_textures(args: &Args, images: &mut Assets<Image>) -> Vec<Handle<Image>> {
|
||||||
|
let mut color_rng = StdRng::seed_from_u64(42);
|
||||||
|
let color_bytes: Vec<u8> = (0..(args.material_texture_count * 4))
|
||||||
|
.map(|i| if (i % 4) == 3 { 255 } else { color_rng.gen() })
|
||||||
|
.collect();
|
||||||
|
color_bytes
|
||||||
|
.chunks(4)
|
||||||
|
.map(|pixel| {
|
||||||
|
images.add(Image::new_fill(
|
||||||
|
Extent3d {
|
||||||
|
width: 1,
|
||||||
|
height: 1,
|
||||||
|
depth_or_array_layers: 1,
|
||||||
|
},
|
||||||
|
TextureDimension::D2,
|
||||||
|
pixel,
|
||||||
|
TextureFormat::Rgba8UnormSrgb,
|
||||||
|
))
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init_materials(
|
||||||
|
args: &Args,
|
||||||
|
textures: &[Handle<Image>],
|
||||||
|
assets: &mut Assets<StandardMaterial>,
|
||||||
|
) -> Vec<Handle<StandardMaterial>> {
|
||||||
|
let capacity = if args.vary_material_data {
|
||||||
|
match args.layout {
|
||||||
|
Layout::Cube => (WIDTH - WIDTH / 10) * (HEIGHT - HEIGHT / 10),
|
||||||
|
Layout::Sphere => WIDTH * HEIGHT * 4,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
args.material_texture_count
|
||||||
|
}
|
||||||
|
.max(1);
|
||||||
|
|
||||||
|
let mut materials = Vec::with_capacity(capacity);
|
||||||
|
materials.push(assets.add(StandardMaterial {
|
||||||
|
base_color: Color::WHITE,
|
||||||
|
base_color_texture: textures.get(0).cloned(),
|
||||||
|
..default()
|
||||||
|
}));
|
||||||
|
|
||||||
|
let mut color_rng = StdRng::seed_from_u64(42);
|
||||||
|
let mut texture_rng = StdRng::seed_from_u64(42);
|
||||||
|
materials.extend(
|
||||||
|
std::iter::repeat_with(|| {
|
||||||
|
assets.add(StandardMaterial {
|
||||||
|
base_color: Color::rgb_u8(color_rng.gen(), color_rng.gen(), color_rng.gen()),
|
||||||
|
base_color_texture: textures.choose(&mut texture_rng).cloned(),
|
||||||
|
..default()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.take(capacity - materials.len()),
|
||||||
|
);
|
||||||
|
|
||||||
|
materials
|
||||||
|
}
|
||||||
|
|
||||||
// NOTE: This epsilon value is apparently optimal for optimizing for the average
|
// NOTE: This epsilon value is apparently optimal for optimizing for the average
|
||||||
// nearest-neighbor distance. See:
|
// nearest-neighbor distance. See:
|
||||||
// http://extremelearning.com.au/how-to-evenly-distribute-points-on-a-sphere-more-effectively-than-the-canonical-fibonacci-lattice/
|
// http://extremelearning.com.au/how-to-evenly-distribute-points-on-a-sphere-more-effectively-than-the-canonical-fibonacci-lattice/
|
||||||
|
@ -159,9 +259,18 @@ fn spherical_polar_to_cartesian(p: DVec2) -> DVec3 {
|
||||||
}
|
}
|
||||||
|
|
||||||
// System for rotating the camera
|
// System for rotating the camera
|
||||||
fn move_camera(time: Res<Time>, mut camera_query: Query<&mut Transform, With<Camera>>) {
|
fn move_camera(
|
||||||
|
time: Res<Time>,
|
||||||
|
args: Res<Args>,
|
||||||
|
mut camera_query: Query<&mut Transform, With<Camera>>,
|
||||||
|
) {
|
||||||
let mut camera_transform = camera_query.single_mut();
|
let mut camera_transform = camera_query.single_mut();
|
||||||
let delta = time.delta_seconds() * 0.15;
|
let delta = 0.15
|
||||||
|
* if args.benchmark {
|
||||||
|
1.0 / 60.0
|
||||||
|
} else {
|
||||||
|
time.delta_seconds()
|
||||||
|
};
|
||||||
camera_transform.rotate_z(delta);
|
camera_transform.rotate_z(delta);
|
||||||
camera_transform.rotate_x(delta);
|
camera_transform.rotate_x(delta);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue