Skip to content

Commit 8ad9615

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 8ad9615

File tree

2 files changed

+176
-15
lines changed

2 files changed

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

2253
fn main() {
54+
let args = Args::parse();
55+
2356
App::new()
2457
.add_plugins((
2558
DefaultPlugins.set(WindowPlugin {
@@ -32,28 +65,47 @@ fn main() {
3265
FrameTimeDiagnosticsPlugin,
3366
LogDiagnosticsPlugin::default(),
3467
))
68+
.insert_resource(args)
3569
.add_systems(Startup, setup)
3670
.add_systems(Update, (move_camera, print_mesh_count))
3771
.run();
3872
}
3973

4074
fn setup(
4175
mut commands: Commands,
76+
args: Res<Args>,
4277
mut meshes: ResMut<Assets<Mesh>>,
43-
mut materials: ResMut<Assets<StandardMaterial>>,
78+
materials: ResMut<Assets<StandardMaterial>>,
79+
images: ResMut<Assets<Image>>,
4480
) {
4581
warn!(include_str!("warning_string.txt"));
4682

83+
let args = args.into_inner();
84+
let images = images.into_inner();
85+
let materials = materials.into_inner();
86+
4787
const WIDTH: usize = 200;
4888
const HEIGHT: usize = 200;
89+
4990
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-
});
5491

55-
match std::env::args().nth(1).as_deref() {
56-
Some("sphere") => {
92+
let material_textures = init_textures(args, images);
93+
94+
let material = materials.add(Color::PINK.into());
95+
let mut texture_rng = Xoshiro256StarStar::seed_from_u64(42);
96+
let texture_material = if args.material_texture_count == 1 {
97+
Some(materials.add(StandardMaterial {
98+
base_color: Color::WHITE,
99+
base_color_texture: get_texture(args, &mut texture_rng, &material_textures),
100+
..default()
101+
}))
102+
} else {
103+
None
104+
};
105+
106+
let mut color_rng = Xoshiro256StarStar::seed_from_u64(42);
107+
match args.layout {
108+
Layout::Sphere => {
57109
// NOTE: This pattern is good for testing performance of culling as it provides roughly
58110
// the same number of visible meshes regardless of the viewing angle.
59111
const N_POINTS: usize = WIDTH * HEIGHT * 4;
@@ -66,7 +118,15 @@ fn setup(
66118
let unit_sphere_p = spherical_polar_to_cartesian(spherical_polar_theta_phi);
67119
commands.spawn(PbrBundle {
68120
mesh: mesh.clone_weak(),
69-
material: material.clone_weak(),
121+
material: get_material(
122+
args,
123+
&material,
124+
texture_material.as_ref(),
125+
materials,
126+
&mut color_rng,
127+
&mut texture_rng,
128+
&material_textures,
129+
),
70130
transform: Transform::from_translation((radius * unit_sphere_p).as_vec3()),
71131
..default()
72132
});
@@ -87,13 +147,29 @@ fn setup(
87147
// cube
88148
commands.spawn(PbrBundle {
89149
mesh: mesh.clone_weak(),
90-
material: material.clone_weak(),
150+
material: get_material(
151+
args,
152+
&material,
153+
texture_material.as_ref(),
154+
materials,
155+
&mut color_rng,
156+
&mut texture_rng,
157+
&material_textures,
158+
),
91159
transform: Transform::from_xyz((x as f32) * 2.5, (y as f32) * 2.5, 0.0),
92160
..default()
93161
});
94162
commands.spawn(PbrBundle {
95163
mesh: mesh.clone_weak(),
96-
material: material.clone_weak(),
164+
material: get_material(
165+
args,
166+
&material,
167+
texture_material.as_ref(),
168+
materials,
169+
&mut color_rng,
170+
&mut texture_rng,
171+
&material_textures,
172+
),
97173
transform: Transform::from_xyz(
98174
(x as f32) * 2.5,
99175
HEIGHT as f32 * 2.5,
@@ -103,13 +179,29 @@ fn setup(
103179
});
104180
commands.spawn(PbrBundle {
105181
mesh: mesh.clone_weak(),
106-
material: material.clone_weak(),
182+
material: get_material(
183+
args,
184+
&material,
185+
texture_material.as_ref(),
186+
materials,
187+
&mut color_rng,
188+
&mut texture_rng,
189+
&material_textures,
190+
),
107191
transform: Transform::from_xyz((x as f32) * 2.5, 0.0, (y as f32) * 2.5),
108192
..default()
109193
});
110194
commands.spawn(PbrBundle {
111195
mesh: mesh.clone_weak(),
112-
material: material.clone_weak(),
196+
material: get_material(
197+
args,
198+
&material,
199+
texture_material.as_ref(),
200+
materials,
201+
&mut color_rng,
202+
&mut texture_rng,
203+
&material_textures,
204+
),
113205
transform: Transform::from_xyz(0.0, (x as f32) * 2.5, (y as f32) * 2.5),
114206
..default()
115207
});
@@ -139,6 +231,64 @@ fn setup(
139231
commands.spawn(DirectionalLightBundle { ..default() });
140232
}
141233

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

161311
// System for rotating the camera
162-
fn move_camera(time: Res<Time>, mut camera_query: Query<&mut Transform, With<Camera>>) {
312+
fn move_camera(
313+
time: Res<Time>,
314+
args: Res<Args>,
315+
mut camera_query: Query<&mut Transform, With<Camera>>,
316+
) {
163317
let mut camera_transform = camera_query.single_mut();
164-
let delta = time.delta_seconds() * 0.15;
318+
let delta = 0.15
319+
* if args.benchmark {
320+
1.0 / 60.0
321+
} else {
322+
time.delta_seconds()
323+
};
165324
camera_transform.rotate_z(delta);
166325
camera_transform.rotate_x(delta);
167326
}

0 commit comments

Comments
 (0)