Skip to content

Commit e4bcc77

Browse files
committed
Enhance many_cubes stress test use cases
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 Xoshiro256StarStar for reproducible random numbers.
1 parent e8b3892 commit e4bcc77

File tree

2 files changed

+178
-19
lines changed

2 files changed

+178
-19
lines changed

Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -254,13 +254,15 @@ bevy_internal = { path = "crates/bevy_internal", version = "0.12.0-dev", default
254254

255255
[dev-dependencies]
256256
anyhow = "1.0.4"
257-
rand = "0.8.0"
257+
rand = { version = "0.8.0", features = ["small_rng"] }
258258
ron = "0.8.0"
259259
serde = { version = "1", features = ["derive"] }
260260
bytemuck = "1.7"
261261
# Needed to poll Task examples
262262
futures-lite = "1.11.3"
263263
crossbeam-channel = "0.5.0"
264+
clap = { version = "4.4.0", features = ["derive"] }
265+
rand_xoshiro = "0.6.0"
264266

265267
[[example]]
266268
name = "hello_world"

examples/stress_tests/many_cubes.rs

Lines changed: 175 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,10 @@
33
//! To measure performance realistically, be sure to run this in release mode.
44
//! `cargo run --example many_cubes --release`
55
//!
6-
//! By default, this arranges the meshes in a cubical pattern, where the number of visible meshes
7-
//! varies with the viewing angle. You can choose to run the demo with a spherical pattern that
6+
//! By default, this arranges the meshes in a spherical pattern that
87
//! distributes the meshes evenly.
98
//!
10-
//! To start the demo using the spherical layout run
11-
//! `cargo run --example many_cubes --release sphere`
9+
//! See `cargo run --example many_cubes --release -- --help` for more options.
1210
1311
use std::f64::consts::PI;
1412

@@ -18,8 +16,41 @@ use bevy::{
1816
prelude::*,
1917
window::{PresentMode, WindowPlugin},
2018
};
19+
use bevy_internal::render::render_resource::{Extent3d, TextureDimension, TextureFormat};
20+
use clap::{Parser, ValueEnum};
21+
use rand::{seq::SliceRandom, Rng, RngCore, SeedableRng};
22+
use rand_xoshiro::Xoshiro256StarStar;
23+
24+
#[derive(Parser, Resource)]
25+
#[command(author, version, about, long_about = None)]
26+
struct Args {
27+
/// How the cube instances should be positioned.
28+
#[arg(short, long, value_enum, default_value_t=Layout::Sphere)]
29+
layout: Layout,
30+
31+
/// Whether to step the camera animation by a fixed amount such that each frame is the same across runs.
32+
#[arg(short, long, default_value_t = false)]
33+
benchmark: bool,
34+
35+
/// Whether to vary the material data in each instance.
36+
#[arg(short, long, default_value_t = false)]
37+
vary_material_data: bool,
38+
39+
/// The number of different textures from which to randomly select the material base color. 0 means no textures.
40+
#[arg(short, long, default_value_t = 0)]
41+
material_texture_count: usize,
42+
}
43+
44+
#[derive(Default, Clone, ValueEnum)]
45+
enum Layout {
46+
Cube,
47+
#[default]
48+
Sphere,
49+
}
2150

2251
fn main() {
52+
let args = Args::parse();
53+
2354
App::new()
2455
.add_plugins((
2556
DefaultPlugins.set(WindowPlugin {
@@ -32,28 +63,47 @@ fn main() {
3263
FrameTimeDiagnosticsPlugin,
3364
LogDiagnosticsPlugin::default(),
3465
))
66+
.insert_resource(args)
3567
.add_systems(Startup, setup)
3668
.add_systems(Update, (move_camera, print_mesh_count))
3769
.run();
3870
}
3971

4072
fn setup(
4173
mut commands: Commands,
74+
args: Res<Args>,
4275
mut meshes: ResMut<Assets<Mesh>>,
43-
mut materials: ResMut<Assets<StandardMaterial>>,
76+
materials: ResMut<Assets<StandardMaterial>>,
77+
images: ResMut<Assets<Image>>,
4478
) {
4579
warn!(include_str!("warning_string.txt"));
4680

81+
let args = args.into_inner();
82+
let images = images.into_inner();
83+
let materials = materials.into_inner();
84+
4785
const WIDTH: usize = 200;
4886
const HEIGHT: usize = 200;
87+
4988
let mesh = meshes.add(Mesh::from(shape::Cube { size: 1.0 }));
50-
let material = materials.add(StandardMaterial {
51-
base_color: Color::PINK,
52-
..default()
53-
});
5489

55-
match std::env::args().nth(1).as_deref() {
56-
Some("sphere") => {
90+
let material_textures = init_textures(args, images);
91+
92+
let material = materials.add(Color::PINK.into());
93+
let mut texture_rng = Xoshiro256StarStar::seed_from_u64(42);
94+
let texture_material = if args.material_texture_count == 1 {
95+
Some(materials.add(StandardMaterial {
96+
base_color: Color::WHITE,
97+
base_color_texture: get_texture(args, &mut texture_rng, &material_textures),
98+
..default()
99+
}))
100+
} else {
101+
None
102+
};
103+
104+
let mut color_rng = Xoshiro256StarStar::seed_from_u64(42);
105+
match args.layout {
106+
Layout::Sphere => {
57107
// NOTE: This pattern is good for testing performance of culling as it provides roughly
58108
// the same number of visible meshes regardless of the viewing angle.
59109
const N_POINTS: usize = WIDTH * HEIGHT * 4;
@@ -66,7 +116,15 @@ fn setup(
66116
let unit_sphere_p = spherical_polar_to_cartesian(spherical_polar_theta_phi);
67117
commands.spawn(PbrBundle {
68118
mesh: mesh.clone_weak(),
69-
material: material.clone_weak(),
119+
material: get_material(
120+
args,
121+
&material,
122+
texture_material.as_ref(),
123+
materials,
124+
&mut color_rng,
125+
&mut texture_rng,
126+
&material_textures,
127+
),
70128
transform: Transform::from_translation((radius * unit_sphere_p).as_vec3()),
71129
..default()
72130
});
@@ -87,13 +145,29 @@ fn setup(
87145
// cube
88146
commands.spawn(PbrBundle {
89147
mesh: mesh.clone_weak(),
90-
material: material.clone_weak(),
148+
material: get_material(
149+
args,
150+
&material,
151+
texture_material.as_ref(),
152+
materials,
153+
&mut color_rng,
154+
&mut texture_rng,
155+
&material_textures,
156+
),
91157
transform: Transform::from_xyz((x as f32) * 2.5, (y as f32) * 2.5, 0.0),
92158
..default()
93159
});
94160
commands.spawn(PbrBundle {
95161
mesh: mesh.clone_weak(),
96-
material: material.clone_weak(),
162+
material: get_material(
163+
args,
164+
&material,
165+
texture_material.as_ref(),
166+
materials,
167+
&mut color_rng,
168+
&mut texture_rng,
169+
&material_textures,
170+
),
97171
transform: Transform::from_xyz(
98172
(x as f32) * 2.5,
99173
HEIGHT as f32 * 2.5,
@@ -103,13 +177,29 @@ fn setup(
103177
});
104178
commands.spawn(PbrBundle {
105179
mesh: mesh.clone_weak(),
106-
material: material.clone_weak(),
180+
material: get_material(
181+
args,
182+
&material,
183+
texture_material.as_ref(),
184+
materials,
185+
&mut color_rng,
186+
&mut texture_rng,
187+
&material_textures,
188+
),
107189
transform: Transform::from_xyz((x as f32) * 2.5, 0.0, (y as f32) * 2.5),
108190
..default()
109191
});
110192
commands.spawn(PbrBundle {
111193
mesh: mesh.clone_weak(),
112-
material: material.clone_weak(),
194+
material: get_material(
195+
args,
196+
&material,
197+
texture_material.as_ref(),
198+
materials,
199+
&mut color_rng,
200+
&mut texture_rng,
201+
&material_textures,
202+
),
113203
transform: Transform::from_xyz(0.0, (x as f32) * 2.5, (y as f32) * 2.5),
114204
..default()
115205
});
@@ -139,6 +229,64 @@ fn setup(
139229
commands.spawn(DirectionalLightBundle { ..default() });
140230
}
141231

232+
fn init_textures(args: &Args, images: &mut Assets<Image>) -> Vec<Handle<Image>> {
233+
let mut color_bytes: Vec<u8> = vec![0u8; args.material_texture_count * 4];
234+
Xoshiro256StarStar::seed_from_u64(42).fill_bytes(&mut color_bytes);
235+
color_bytes
236+
.chunks_mut(4)
237+
.map(|p| {
238+
p[3] = 255;
239+
images.add(Image::new_fill(
240+
Extent3d {
241+
width: 1,
242+
height: 1,
243+
depth_or_array_layers: 1,
244+
},
245+
TextureDimension::D2,
246+
p,
247+
TextureFormat::Rgba8UnormSrgb,
248+
))
249+
})
250+
.collect()
251+
}
252+
253+
fn get_texture<R: Rng + ?Sized>(
254+
args: &Args,
255+
texture_rng: &mut R,
256+
material_textures: &[Handle<Image>],
257+
) -> Option<Handle<Image>> {
258+
match args.material_texture_count {
259+
c if c == 0 => None,
260+
c if c == 1 => Some(material_textures[0].clone_weak()),
261+
_ => Some(material_textures.choose(texture_rng).unwrap().clone_weak()),
262+
}
263+
}
264+
265+
fn get_material<R: Rng + ?Sized>(
266+
args: &Args,
267+
material: &Handle<StandardMaterial>,
268+
texture_material: Option<&Handle<StandardMaterial>>,
269+
materials: &mut Assets<StandardMaterial>,
270+
color_rng: &mut R,
271+
texture_rng: &mut R,
272+
material_textures: &[Handle<Image>],
273+
) -> Handle<StandardMaterial> {
274+
match (args.vary_material_data, args.material_texture_count) {
275+
(false, 0) => material.clone_weak(),
276+
(false, 1) => texture_material.unwrap().clone_weak(),
277+
(false, _) => materials.add(StandardMaterial {
278+
base_color: Color::WHITE,
279+
base_color_texture: get_texture(args, texture_rng, material_textures),
280+
..default()
281+
}),
282+
(true, _) => materials.add(StandardMaterial {
283+
base_color: Color::rgb_u8(color_rng.gen(), color_rng.gen(), color_rng.gen()),
284+
base_color_texture: get_texture(args, texture_rng, material_textures),
285+
..default()
286+
}),
287+
}
288+
}
289+
142290
// NOTE: This epsilon value is apparently optimal for optimizing for the average
143291
// nearest-neighbor distance. See:
144292
// http://extremelearning.com.au/how-to-evenly-distribute-points-on-a-sphere-more-effectively-than-the-canonical-fibonacci-lattice/
@@ -159,9 +307,18 @@ fn spherical_polar_to_cartesian(p: DVec2) -> DVec3 {
159307
}
160308

161309
// System for rotating the camera
162-
fn move_camera(time: Res<Time>, mut camera_query: Query<&mut Transform, With<Camera>>) {
310+
fn move_camera(
311+
time: Res<Time>,
312+
args: Res<Args>,
313+
mut camera_query: Query<&mut Transform, With<Camera>>,
314+
) {
163315
let mut camera_transform = camera_query.single_mut();
164-
let delta = time.delta_seconds() * 0.15;
316+
let delta = 0.15
317+
* if args.benchmark {
318+
1.0 / 60.0
319+
} else {
320+
time.delta_seconds()
321+
};
165322
camera_transform.rotate_z(delta);
166323
camera_transform.rotate_x(delta);
167324
}

0 commit comments

Comments
 (0)