diff --git a/examples/stress_tests/bevymark.rs b/examples/stress_tests/bevymark.rs index d9cbe76a89315..4e2670ecabd9d 100644 --- a/examples/stress_tests/bevymark.rs +++ b/examples/stress_tests/bevymark.rs @@ -2,18 +2,24 @@ //! //! Usage: spawn more entities by clicking on the screen. +use std::str::FromStr; + +use argh::FromArgs; use bevy::{ diagnostic::{DiagnosticsStore, FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin}, prelude::*, + render::render_resource::{Extent3d, TextureDimension, TextureFormat}, + sprite::{MaterialMesh2dBundle, Mesh2dHandle}, window::{PresentMode, WindowResolution}, }; -use rand::{rngs::StdRng, thread_rng, Rng, SeedableRng}; +use rand::{rngs::StdRng, seq::SliceRandom, Rng, SeedableRng}; const BIRDS_PER_SECOND: u32 = 10000; const GRAVITY: f32 = -9.8 * 100.0; const MAX_VELOCITY: f32 = 750.; const BIRD_SCALE: f32 = 0.15; -const HALF_BIRD_SIZE: f32 = 256. * BIRD_SCALE * 0.5; +const BIRD_TEXTURE_SIZE: usize = 256; +const HALF_BIRD_SIZE: f32 = BIRD_TEXTURE_SIZE as f32 * BIRD_SCALE * 0.5; #[derive(Resource)] struct BevyCounter { @@ -26,7 +32,62 @@ struct Bird { velocity: Vec3, } +#[derive(FromArgs, Resource)] +/// `bevymark` sprite / 2D mesh stress test +struct Args { + /// whether to use sprite or mesh2d + #[argh(option, default = "Mode::Sprite")] + mode: Mode, + + /// whether to step animations by a fixed amount such that each frame is the same across runs. + /// If spawning waves, all are spawned up-front to immediately start rendering at the heaviest + /// load. + #[argh(switch)] + benchmark: bool, + + /// how many birds to spawn per wave. + #[argh(option, default = "0")] + per_wave: usize, + + /// the number of waves to spawn. + #[argh(option, default = "0")] + waves: usize, + + /// 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 color. 0 means no textures. + #[argh(option, default = "1")] + material_texture_count: usize, +} + +#[derive(Default, Clone)] +enum Mode { + #[default] + Sprite, + Mesh2d, +} + +impl FromStr for Mode { + type Err = String; + + fn from_str(s: &str) -> Result { + match s { + "sprite" => Ok(Self::Sprite), + "mesh2d" => Ok(Self::Mesh2d), + _ => Err(format!( + "Unknown mode: '{s}', valid modes: 'sprite', 'mesh2d'" + )), + } + } +} + +const FIXED_TIMESTEP: f32 = 0.2; + fn main() { + let args: Args = argh::from_env(); + App::new() .add_plugins(( DefaultPlugins.set(WindowPlugin { @@ -41,6 +102,7 @@ fn main() { FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin::default(), )) + .insert_resource(args) .insert_resource(BevyCounter { count: 0, color: Color::WHITE, @@ -56,50 +118,93 @@ fn main() { counter_system, ), ) - .insert_resource(FixedTime::new_from_secs(0.2)) + .insert_resource(FixedTime::new_from_secs(FIXED_TIMESTEP)) .run(); } #[derive(Resource)] struct BirdScheduled { - wave: usize, + waves: usize, per_wave: usize, } fn scheduled_spawner( mut commands: Commands, + args: Res, windows: Query<&Window>, mut scheduled: ResMut, mut counter: ResMut, - bird_texture: Res, + bird_resources: ResMut, ) { let window = windows.single(); - if scheduled.wave > 0 { + if scheduled.waves > 0 { + let bird_resources = bird_resources.into_inner(); spawn_birds( &mut commands, + args.into_inner(), &window.resolution, &mut counter, scheduled.per_wave, - bird_texture.clone_weak(), + bird_resources, + None, + scheduled.waves - 1, ); - let mut rng = thread_rng(); - counter.color = Color::rgb_linear(rng.gen(), rng.gen(), rng.gen()); - scheduled.wave -= 1; + scheduled.waves -= 1; } } -#[derive(Resource, Deref)] -struct BirdTexture(Handle); +#[derive(Resource)] +struct BirdResources { + textures: Vec>, + materials: Vec>, + quad: Mesh2dHandle, + color_rng: StdRng, + material_rng: StdRng, + velocity_rng: StdRng, +} #[derive(Component)] struct StatsText; -fn setup(mut commands: Commands, asset_server: Res) { +#[allow(clippy::too_many_arguments)] +fn setup( + mut commands: Commands, + args: Res, + asset_server: Res, + mut meshes: ResMut>, + material_assets: ResMut>, + images: ResMut>, + windows: Query<&Window>, + counter: ResMut, +) { warn!(include_str!("warning_string.txt")); - let texture = asset_server.load("branding/icon.png"); + let args = args.into_inner(); + let images = images.into_inner(); + + let mut textures = Vec::with_capacity(args.material_texture_count.max(1)); + if matches!(args.mode, Mode::Sprite) || args.material_texture_count > 0 { + textures.push(asset_server.load("branding/icon.png")); + } + init_textures(&mut textures, args, images); + + let material_assets = material_assets.into_inner(); + let materials = init_materials(args, &textures, material_assets); + + let mut bird_resources = BirdResources { + textures, + materials, + quad: meshes + .add(Mesh::from(shape::Quad::new(Vec2::splat( + BIRD_TEXTURE_SIZE as f32, + )))) + .into(), + color_rng: StdRng::seed_from_u64(42), + material_rng: StdRng::seed_from_u64(42), + velocity_rng: StdRng::seed_from_u64(42), + }; let text_section = move |color, value: &str| { TextSection::new( @@ -113,51 +218,77 @@ fn setup(mut commands: Commands, asset_server: Res) { }; commands.spawn(Camera2dBundle::default()); - commands.spawn(( - TextBundle::from_sections([ - text_section(Color::GREEN, "Bird Count"), - text_section(Color::CYAN, ""), - text_section(Color::GREEN, "\nFPS (raw): "), - text_section(Color::CYAN, ""), - text_section(Color::GREEN, "\nFPS (SMA): "), - text_section(Color::CYAN, ""), - text_section(Color::GREEN, "\nFPS (EMA): "), - text_section(Color::CYAN, ""), - ]) - .with_style(Style { - position_type: PositionType::Absolute, - top: Val::Px(5.0), - left: Val::Px(5.0), + commands + .spawn(NodeBundle { + style: Style { + position_type: PositionType::Absolute, + padding: UiRect::all(Val::Px(5.0)), + ..default() + }, + z_index: ZIndex::Global(i32::MAX), + background_color: Color::BLACK.with_a(0.75).into(), ..default() - }), - StatsText, - )); - - commands.insert_resource(BirdTexture(texture)); - commands.insert_resource(BirdScheduled { - per_wave: std::env::args() - .nth(1) - .and_then(|arg| arg.parse::().ok()) - .unwrap_or_default(), - wave: std::env::args() - .nth(2) - .and_then(|arg| arg.parse::().ok()) - .unwrap_or(1), - }); + }) + .with_children(|c| { + c.spawn(( + TextBundle::from_sections([ + text_section(Color::GREEN, "Bird Count: "), + text_section(Color::CYAN, ""), + text_section(Color::GREEN, "\nFPS (raw): "), + text_section(Color::CYAN, ""), + text_section(Color::GREEN, "\nFPS (SMA): "), + text_section(Color::CYAN, ""), + text_section(Color::GREEN, "\nFPS (EMA): "), + text_section(Color::CYAN, ""), + ]), + StatsText, + )); + }); + + let mut scheduled = BirdScheduled { + per_wave: args.per_wave, + waves: args.waves, + }; + + if args.benchmark { + let counter = counter.into_inner(); + for wave in (0..scheduled.waves).rev() { + spawn_birds( + &mut commands, + args, + &windows.single().resolution, + counter, + scheduled.per_wave, + &mut bird_resources, + Some(wave), + wave, + ); + } + scheduled.waves = 0; + } + commands.insert_resource(bird_resources); + commands.insert_resource(scheduled); } +#[allow(clippy::too_many_arguments)] fn mouse_handler( mut commands: Commands, + args: Res, time: Res