Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhance many_cubes stress test use cases #9596

Merged
merged 1 commit into from
Sep 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
185 changes: 147 additions & 38 deletions examples/stress_tests/many_cubes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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 args = args.into_inner();
let images = images.into_inner();
let material_assets = material_assets.into_inner();

let mesh = meshes.add(Mesh::from(shape::Cube { size: 1.0 }));
let material = materials.add(StandardMaterial {
base_color: Color::PINK,
..default()
});

match std::env::args().nth(1).as_deref() {
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
// the same number of visible meshes regardless of the viewing angle.
const N_POINTS: usize = WIDTH * HEIGHT * 4;
Expand All @@ -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()
});
Expand All @@ -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,
Expand All @@ -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()
});
Expand All @@ -123,20 +176,67 @@ 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()
},
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()
});
}));

commands.spawn(DirectionalLightBundle { ..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
Expand All @@ -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);
}
Expand Down