mirror of
https://github.com/bevyengine/bevy
synced 2024-11-22 04:33:37 +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
|
||||
futures-lite = "1.11.3"
|
||||
crossbeam-channel = "0.5.0"
|
||||
argh = "0.1.12"
|
||||
|
||||
[[example]]
|
||||
name = "hello_world"
|
||||
|
|
|
@ -3,23 +3,68 @@
|
|||
//! To measure performance realistically, be sure to run this in release mode.
|
||||
//! `cargo run --example many_cubes --release`
|
||||
//!
|
||||
//! By default, this arranges the meshes in a cubical pattern, where the number of visible meshes
|
||||
//! varies with the viewing angle. You can choose to run the demo with a spherical pattern that
|
||||
//! By default, this arranges the meshes in a spherical pattern that
|
||||
//! distributes the meshes evenly.
|
||||
//!
|
||||
//! To start the demo using the spherical layout run
|
||||
//! `cargo run --example many_cubes --release sphere`
|
||||
//! See `cargo run --example many_cubes --release -- --help` for more options.
|
||||
|
||||
use std::f64::consts::PI;
|
||||
use std::{f64::consts::PI, str::FromStr};
|
||||
|
||||
use argh::FromArgs;
|
||||
use bevy::{
|
||||
diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
|
||||
math::{DVec2, DVec3},
|
||||
prelude::*,
|
||||
render::render_resource::{Extent3d, TextureDimension, TextureFormat},
|
||||
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() {
|
||||
let args: Args = argh::from_env();
|
||||
|
||||
App::new()
|
||||
.add_plugins((
|
||||
DefaultPlugins.set(WindowPlugin {
|
||||
|
@ -32,28 +77,36 @@ fn main() {
|
|||
FrameTimeDiagnosticsPlugin,
|
||||
LogDiagnosticsPlugin::default(),
|
||||
))
|
||||
.insert_resource(args)
|
||||
.add_systems(Startup, setup)
|
||||
.add_systems(Update, (move_camera, print_mesh_count))
|
||||
.run();
|
||||
}
|
||||
|
||||
const WIDTH: usize = 200;
|
||||
const HEIGHT: usize = 200;
|
||||
|
||||
fn setup(
|
||||
mut commands: Commands,
|
||||
args: Res<Args>,
|
||||
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"));
|
||||
|
||||
const WIDTH: usize = 200;
|
||||
const HEIGHT: usize = 200;
|
||||
let mesh = meshes.add(Mesh::from(shape::Cube { size: 1.0 }));
|
||||
let material = materials.add(StandardMaterial {
|
||||
base_color: Color::PINK,
|
||||
..default()
|
||||
});
|
||||
let args = args.into_inner();
|
||||
let images = images.into_inner();
|
||||
let material_assets = material_assets.into_inner();
|
||||
|
||||
match std::env::args().nth(1).as_deref() {
|
||||
Some("sphere") => {
|
||||
let mesh = meshes.add(Mesh::from(shape::Cube { size: 1.0 }));
|
||||
|
||||
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
|
||||
// the same number of visible meshes regardless of the viewing angle.
|
||||
const N_POINTS: usize = WIDTH * HEIGHT * 4;
|
||||
|
@ -65,8 +118,8 @@ fn setup(
|
|||
fibonacci_spiral_on_sphere(golden_ratio, i, N_POINTS);
|
||||
let unit_sphere_p = spherical_polar_to_cartesian(spherical_polar_theta_phi);
|
||||
commands.spawn(PbrBundle {
|
||||
mesh: mesh.clone_weak(),
|
||||
material: material.clone_weak(),
|
||||
mesh: mesh.clone(),
|
||||
material: materials.choose(&mut material_rng).unwrap().clone(),
|
||||
transform: Transform::from_translation((radius * unit_sphere_p).as_vec3()),
|
||||
..default()
|
||||
});
|
||||
|
@ -86,14 +139,14 @@ fn setup(
|
|||
}
|
||||
// cube
|
||||
commands.spawn(PbrBundle {
|
||||
mesh: mesh.clone_weak(),
|
||||
material: material.clone_weak(),
|
||||
mesh: mesh.clone(),
|
||||
material: materials.choose(&mut material_rng).unwrap().clone(),
|
||||
transform: Transform::from_xyz((x as f32) * 2.5, (y as f32) * 2.5, 0.0),
|
||||
..default()
|
||||
});
|
||||
commands.spawn(PbrBundle {
|
||||
mesh: mesh.clone_weak(),
|
||||
material: material.clone_weak(),
|
||||
mesh: mesh.clone(),
|
||||
material: materials.choose(&mut material_rng).unwrap().clone(),
|
||||
transform: Transform::from_xyz(
|
||||
(x as f32) * 2.5,
|
||||
HEIGHT as f32 * 2.5,
|
||||
|
@ -102,14 +155,14 @@ fn setup(
|
|||
..default()
|
||||
});
|
||||
commands.spawn(PbrBundle {
|
||||
mesh: mesh.clone_weak(),
|
||||
material: material.clone_weak(),
|
||||
mesh: mesh.clone(),
|
||||
material: materials.choose(&mut material_rng).unwrap().clone(),
|
||||
transform: Transform::from_xyz((x as f32) * 2.5, 0.0, (y as f32) * 2.5),
|
||||
..default()
|
||||
});
|
||||
commands.spawn(PbrBundle {
|
||||
mesh: mesh.clone_weak(),
|
||||
material: material.clone_weak(),
|
||||
mesh: mesh.clone(),
|
||||
material: materials.choose(&mut material_rng).unwrap().clone(),
|
||||
transform: Transform::from_xyz(0.0, (x as f32) * 2.5, (y as f32) * 2.5),
|
||||
..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() });
|
||||
}
|
||||
|
||||
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
|
||||
// nearest-neighbor distance. See:
|
||||
// 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
|
||||
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 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_x(delta);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue