Skip to content

Commit 4511968

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 StdRng for deterministic random numbers.
1 parent c440de0 commit 4511968

File tree

2 files changed

+192
-19
lines changed

2 files changed

+192
-19
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,7 @@ bytemuck = "1.7"
261261
# Needed to poll Task examples
262262
futures-lite = "1.11.3"
263263
crossbeam-channel = "0.5.0"
264+
argh = "0.1.12"
264265

265266
[[example]]
266267
name = "hello_world"

examples/stress_tests/many_cubes.rs

Lines changed: 191 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,68 @@
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
13-
use std::f64::consts::PI;
11+
use std::{f64::consts::PI, str::FromStr};
1412

13+
use argh::FromArgs;
1514
use bevy::{
1615
diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
1716
math::{DVec2, DVec3},
1817
prelude::*,
18+
render::render_resource::{Extent3d, TextureDimension, TextureFormat},
1919
window::{PresentMode, WindowPlugin},
2020
};
21+
use rand::{rngs::StdRng, seq::SliceRandom, Rng, SeedableRng};
22+
23+
#[derive(FromArgs, Resource)]
24+
/// `many_cubes` stress test
25+
struct Args {
26+
/// how the cube instances should be positioned.
27+
#[argh(option, default = "Layout::Sphere")]
28+
layout: Layout,
29+
30+
/// whether to step the camera animation by a fixed amount such that each frame is the same across runs.
31+
#[argh(switch)]
32+
benchmark: bool,
33+
34+
/// whether to vary the material data in each instance.
35+
#[argh(switch)]
36+
vary_material_data: bool,
37+
38+
/// the number of different textures from which to randomly select the material base color. 0 means no textures.
39+
#[argh(option, default = "0")]
40+
material_texture_count: usize,
41+
}
42+
43+
#[derive(Default, Clone)]
44+
enum Layout {
45+
Cube,
46+
#[default]
47+
Sphere,
48+
}
49+
50+
impl FromStr for Layout {
51+
type Err = String;
52+
53+
fn from_str(s: &str) -> Result<Self, Self::Err> {
54+
match s {
55+
"cube" => Ok(Self::Cube),
56+
"sphere" => Ok(Self::Sphere),
57+
_ => Err(format!(
58+
"Unknown layout value: '{}', valid options: 'cube', 'sphere'",
59+
s
60+
)),
61+
}
62+
}
63+
}
2164

2265
fn main() {
66+
let args: Args = argh::from_env();
67+
2368
App::new()
2469
.add_plugins((
2570
DefaultPlugins.set(WindowPlugin {
@@ -32,28 +77,47 @@ fn main() {
3277
FrameTimeDiagnosticsPlugin,
3378
LogDiagnosticsPlugin::default(),
3479
))
80+
.insert_resource(args)
3581
.add_systems(Startup, setup)
3682
.add_systems(Update, (move_camera, print_mesh_count))
3783
.run();
3884
}
3985

4086
fn setup(
4187
mut commands: Commands,
88+
args: Res<Args>,
4289
mut meshes: ResMut<Assets<Mesh>>,
43-
mut materials: ResMut<Assets<StandardMaterial>>,
90+
materials: ResMut<Assets<StandardMaterial>>,
91+
images: ResMut<Assets<Image>>,
4492
) {
4593
warn!(include_str!("warning_string.txt"));
4694

95+
let args = args.into_inner();
96+
let images = images.into_inner();
97+
let materials = materials.into_inner();
98+
4799
const WIDTH: usize = 200;
48100
const HEIGHT: usize = 200;
101+
49102
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-
});
54103

55-
match std::env::args().nth(1).as_deref() {
56-
Some("sphere") => {
104+
let material_textures = init_textures(args, images);
105+
106+
let material = materials.add(Color::PINK.into());
107+
let mut texture_rng = StdRng::seed_from_u64(42);
108+
let texture_material = if args.material_texture_count == 1 {
109+
Some(materials.add(StandardMaterial {
110+
base_color: Color::WHITE,
111+
base_color_texture: get_texture(args, &mut texture_rng, &material_textures),
112+
..default()
113+
}))
114+
} else {
115+
None
116+
};
117+
118+
let mut color_rng = StdRng::seed_from_u64(42);
119+
match args.layout {
120+
Layout::Sphere => {
57121
// NOTE: This pattern is good for testing performance of culling as it provides roughly
58122
// the same number of visible meshes regardless of the viewing angle.
59123
const N_POINTS: usize = WIDTH * HEIGHT * 4;
@@ -66,7 +130,15 @@ fn setup(
66130
let unit_sphere_p = spherical_polar_to_cartesian(spherical_polar_theta_phi);
67131
commands.spawn(PbrBundle {
68132
mesh: mesh.clone_weak(),
69-
material: material.clone_weak(),
133+
material: get_material(
134+
args,
135+
&material,
136+
texture_material.as_ref(),
137+
materials,
138+
&mut color_rng,
139+
&mut texture_rng,
140+
&material_textures,
141+
),
70142
transform: Transform::from_translation((radius * unit_sphere_p).as_vec3()),
71143
..default()
72144
});
@@ -87,13 +159,29 @@ fn setup(
87159
// cube
88160
commands.spawn(PbrBundle {
89161
mesh: mesh.clone_weak(),
90-
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+
),
91171
transform: Transform::from_xyz((x as f32) * 2.5, (y as f32) * 2.5, 0.0),
92172
..default()
93173
});
94174
commands.spawn(PbrBundle {
95175
mesh: mesh.clone_weak(),
96-
material: material.clone_weak(),
176+
material: get_material(
177+
args,
178+
&material,
179+
texture_material.as_ref(),
180+
materials,
181+
&mut color_rng,
182+
&mut texture_rng,
183+
&material_textures,
184+
),
97185
transform: Transform::from_xyz(
98186
(x as f32) * 2.5,
99187
HEIGHT as f32 * 2.5,
@@ -103,13 +191,29 @@ fn setup(
103191
});
104192
commands.spawn(PbrBundle {
105193
mesh: mesh.clone_weak(),
106-
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+
),
107203
transform: Transform::from_xyz((x as f32) * 2.5, 0.0, (y as f32) * 2.5),
108204
..default()
109205
});
110206
commands.spawn(PbrBundle {
111207
mesh: mesh.clone_weak(),
112-
material: material.clone_weak(),
208+
material: get_material(
209+
args,
210+
&material,
211+
texture_material.as_ref(),
212+
materials,
213+
&mut color_rng,
214+
&mut texture_rng,
215+
&material_textures,
216+
),
113217
transform: Transform::from_xyz(0.0, (x as f32) * 2.5, (y as f32) * 2.5),
114218
..default()
115219
});
@@ -139,6 +243,65 @@ fn setup(
139243
commands.spawn(DirectionalLightBundle { ..default() });
140244
}
141245

246+
fn init_textures(args: &Args, images: &mut Assets<Image>) -> Vec<Handle<Image>> {
247+
let mut color_rng = StdRng::seed_from_u64(42);
248+
let color_bytes: Vec<u8> = (0..(args.material_texture_count * 4))
249+
.map(|i| if (i % 4) == 3 { 255 } else { color_rng.gen() })
250+
.collect();
251+
color_bytes
252+
.chunks(4)
253+
.map(|pixel| {
254+
images.add(Image::new_fill(
255+
Extent3d {
256+
width: 1,
257+
height: 1,
258+
depth_or_array_layers: 1,
259+
},
260+
TextureDimension::D2,
261+
pixel,
262+
TextureFormat::Rgba8UnormSrgb,
263+
))
264+
})
265+
.collect()
266+
}
267+
268+
fn get_texture<R: Rng>(
269+
args: &Args,
270+
texture_rng: &mut R,
271+
material_textures: &[Handle<Image>],
272+
) -> Option<Handle<Image>> {
273+
match args.material_texture_count {
274+
c if c == 0 => None,
275+
c if c == 1 => Some(material_textures[0].clone_weak()),
276+
_ => Some(material_textures.choose(texture_rng).unwrap().clone_weak()),
277+
}
278+
}
279+
280+
fn get_material<R: Rng>(
281+
args: &Args,
282+
material: &Handle<StandardMaterial>,
283+
texture_material: Option<&Handle<StandardMaterial>>,
284+
materials: &mut Assets<StandardMaterial>,
285+
color_rng: &mut R,
286+
texture_rng: &mut R,
287+
material_textures: &[Handle<Image>],
288+
) -> Handle<StandardMaterial> {
289+
match (args.vary_material_data, args.material_texture_count) {
290+
(false, 0) => material.clone_weak(),
291+
(false, 1) => texture_material.unwrap().clone(),
292+
(false, _) => materials.add(StandardMaterial {
293+
base_color: Color::WHITE,
294+
base_color_texture: get_texture(args, texture_rng, material_textures),
295+
..default()
296+
}),
297+
(true, _) => materials.add(StandardMaterial {
298+
base_color: Color::rgb_u8(color_rng.gen(), color_rng.gen(), color_rng.gen()),
299+
base_color_texture: get_texture(args, texture_rng, material_textures),
300+
..default()
301+
}),
302+
}
303+
}
304+
142305
// NOTE: This epsilon value is apparently optimal for optimizing for the average
143306
// nearest-neighbor distance. See:
144307
// http://extremelearning.com.au/how-to-evenly-distribute-points-on-a-sphere-more-effectively-than-the-canonical-fibonacci-lattice/
@@ -159,9 +322,18 @@ fn spherical_polar_to_cartesian(p: DVec2) -> DVec3 {
159322
}
160323

161324
// System for rotating the camera
162-
fn move_camera(time: Res<Time>, mut camera_query: Query<&mut Transform, With<Camera>>) {
325+
fn move_camera(
326+
time: Res<Time>,
327+
args: Res<Args>,
328+
mut camera_query: Query<&mut Transform, With<Camera>>,
329+
) {
163330
let mut camera_transform = camera_query.single_mut();
164-
let delta = time.delta_seconds() * 0.15;
331+
let delta = 0.15
332+
* if args.benchmark {
333+
1.0 / 60.0
334+
} else {
335+
time.delta_seconds()
336+
};
165337
camera_transform.rotate_z(delta);
166338
camera_transform.rotate_x(delta);
167339
}

0 commit comments

Comments
 (0)