mirror of
https://github.com/bevyengine/bevy
synced 2024-11-26 22:50:19 +00:00
0166db33f7
# Objective #11431 and #11688 implemented meshing support for Bevy's new geometric primitives. The next step is to deprecate the shapes in `bevy_render::mesh::shape` and to later remove them completely for 0.14. ## Solution Deprecate the shapes and reduce code duplication by utilizing the primitive meshing API for the old shapes where possible. Note that some shapes have behavior that can't be exactly reproduced with the new primitives yet: - `Box` is more of an AABB with min/max extents - `Plane` supports a subdivision count - `Quad` has a `flipped` property These types have not been changed to utilize the new primitives yet. --- ## Changelog - Deprecated all shapes in `bevy_render::mesh::shape` - Changed all examples to use new primitives for meshing ## Migration Guide Bevy has previously used rendering-specific types like `UVSphere` and `Quad` for primitive mesh shapes. These have now been deprecated to use the geometric primitives newly introduced in version 0.13. Some examples: ```rust let before = meshes.add(shape::Box::new(5.0, 0.15, 5.0)); let after = meshes.add(Cuboid::new(5.0, 0.15, 5.0)); let before = meshes.add(shape::Quad::default()); let after = meshes.add(Rectangle::default()); let before = meshes.add(shape::Plane::from_size(5.0)); // The surface normal can now also be specified when using `new` let after = meshes.add(Plane3d::default().mesh().size(5.0, 5.0)); let before = meshes.add( Mesh::try_from(shape::Icosphere { radius: 0.5, subdivisions: 5, }) .unwrap(), ); let after = meshes.add(Sphere::new(0.5).mesh().ico(5).unwrap()); ```
317 lines
10 KiB
Rust
317 lines
10 KiB
Rust
//! Simple benchmark to test per-entity draw overhead.
|
|
//!
|
|
//! 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 spherical pattern that
|
|
//! distributes the meshes evenly.
|
|
//!
|
|
//! See `cargo run --example many_cubes --release -- --help` for more options.
|
|
|
|
use std::{f64::consts::PI, str::FromStr};
|
|
|
|
use argh::FromArgs;
|
|
use bevy::{
|
|
diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
|
|
math::{DVec2, DVec3},
|
|
prelude::*,
|
|
render::{
|
|
render_asset::RenderAssetUsages,
|
|
render_resource::{Extent3d, TextureDimension, TextureFormat},
|
|
},
|
|
window::{PresentMode, WindowPlugin, WindowResolution},
|
|
winit::{UpdateMode, WinitSettings},
|
|
};
|
|
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_per_instance: 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() {
|
|
// `from_env` panics on the web
|
|
#[cfg(not(target_arch = "wasm32"))]
|
|
let args: Args = argh::from_env();
|
|
#[cfg(target_arch = "wasm32")]
|
|
let args = Args::from_args(&[], &[]).unwrap();
|
|
|
|
App::new()
|
|
.add_plugins((
|
|
DefaultPlugins.set(WindowPlugin {
|
|
primary_window: Some(Window {
|
|
present_mode: PresentMode::AutoNoVsync,
|
|
resolution: WindowResolution::new(1920.0, 1080.0)
|
|
.with_scale_factor_override(1.0),
|
|
..default()
|
|
}),
|
|
..default()
|
|
}),
|
|
FrameTimeDiagnosticsPlugin,
|
|
LogDiagnosticsPlugin::default(),
|
|
))
|
|
.insert_resource(WinitSettings {
|
|
focused_mode: UpdateMode::Continuous,
|
|
unfocused_mode: UpdateMode::Continuous,
|
|
})
|
|
.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>>,
|
|
material_assets: ResMut<Assets<StandardMaterial>>,
|
|
images: ResMut<Assets<Image>>,
|
|
) {
|
|
warn!(include_str!("warning_string.txt"));
|
|
|
|
let args = args.into_inner();
|
|
let images = images.into_inner();
|
|
let material_assets = material_assets.into_inner();
|
|
|
|
let mesh = meshes.add(Cuboid::default());
|
|
|
|
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;
|
|
// NOTE: f64 is used to avoid precision issues that produce visual artifacts in the distribution
|
|
let radius = WIDTH as f64 * 2.5;
|
|
let golden_ratio = 0.5f64 * (1.0f64 + 5.0f64.sqrt());
|
|
for i in 0..N_POINTS {
|
|
let spherical_polar_theta_phi =
|
|
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(),
|
|
material: materials.choose(&mut material_rng).unwrap().clone(),
|
|
transform: Transform::from_translation((radius * unit_sphere_p).as_vec3()),
|
|
..default()
|
|
});
|
|
}
|
|
|
|
// camera
|
|
commands.spawn(Camera3dBundle::default());
|
|
}
|
|
_ => {
|
|
// NOTE: This pattern is good for demonstrating that frustum culling is working correctly
|
|
// as the number of visible meshes rises and falls depending on the viewing angle.
|
|
for x in 0..WIDTH {
|
|
for y in 0..HEIGHT {
|
|
// introduce spaces to break any kind of moiré pattern
|
|
if x % 10 == 0 || y % 10 == 0 {
|
|
continue;
|
|
}
|
|
// cube
|
|
commands.spawn(PbrBundle {
|
|
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(),
|
|
material: materials.choose(&mut material_rng).unwrap().clone(),
|
|
transform: Transform::from_xyz(
|
|
(x as f32) * 2.5,
|
|
HEIGHT as f32 * 2.5,
|
|
(y as f32) * 2.5,
|
|
),
|
|
..default()
|
|
});
|
|
commands.spawn(PbrBundle {
|
|
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(),
|
|
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()
|
|
});
|
|
}
|
|
}
|
|
// camera
|
|
commands.spawn(Camera3dBundle {
|
|
transform: Transform::from_xyz(WIDTH as f32, HEIGHT as f32, WIDTH as f32),
|
|
..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,
|
|
RenderAssetUsages::RENDER_WORLD,
|
|
))
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
fn init_materials(
|
|
args: &Args,
|
|
textures: &[Handle<Image>],
|
|
assets: &mut Assets<StandardMaterial>,
|
|
) -> Vec<Handle<StandardMaterial>> {
|
|
let capacity = if args.vary_per_instance {
|
|
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.first().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/
|
|
// for details.
|
|
const EPSILON: f64 = 0.36;
|
|
|
|
fn fibonacci_spiral_on_sphere(golden_ratio: f64, i: usize, n: usize) -> DVec2 {
|
|
DVec2::new(
|
|
PI * 2. * (i as f64 / golden_ratio),
|
|
(1.0 - 2.0 * (i as f64 + EPSILON) / (n as f64 - 1.0 + 2.0 * EPSILON)).acos(),
|
|
)
|
|
}
|
|
|
|
fn spherical_polar_to_cartesian(p: DVec2) -> DVec3 {
|
|
let (sin_theta, cos_theta) = p.x.sin_cos();
|
|
let (sin_phi, cos_phi) = p.y.sin_cos();
|
|
DVec3::new(cos_theta * sin_phi, sin_theta * sin_phi, cos_phi)
|
|
}
|
|
|
|
// System for rotating the 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 = 0.15
|
|
* if args.benchmark {
|
|
1.0 / 60.0
|
|
} else {
|
|
time.delta_seconds()
|
|
};
|
|
camera_transform.rotate_z(delta);
|
|
camera_transform.rotate_x(delta);
|
|
}
|
|
|
|
// System for printing the number of meshes on every tick of the timer
|
|
fn print_mesh_count(
|
|
time: Res<Time>,
|
|
mut timer: Local<PrintingTimer>,
|
|
sprites: Query<(&Handle<Mesh>, &ViewVisibility)>,
|
|
) {
|
|
timer.tick(time.delta());
|
|
|
|
if timer.just_finished() {
|
|
info!(
|
|
"Meshes: {} - Visible Meshes {}",
|
|
sprites.iter().len(),
|
|
sprites.iter().filter(|(_, vis)| vis.get()).count(),
|
|
);
|
|
}
|
|
}
|
|
|
|
#[derive(Deref, DerefMut)]
|
|
struct PrintingTimer(Timer);
|
|
|
|
impl Default for PrintingTimer {
|
|
fn default() -> Self {
|
|
Self(Timer::from_seconds(1.0, TimerMode::Repeating))
|
|
}
|
|
}
|