//! This example provides a 2D benchmark. //! //! 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}, utils::Duration, window::{PresentMode, WindowResolution}, }; 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 BIRD_TEXTURE_SIZE: usize = 256; const HALF_BIRD_SIZE: f32 = BIRD_TEXTURE_SIZE as f32 * BIRD_SCALE * 0.5; #[derive(Resource)] struct BevyCounter { pub count: usize, pub color: Color, } #[derive(Component)] 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, /// generate z values in increasing order rather than randomly #[argh(switch)] ordered_z: bool, } #[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 { primary_window: Some(Window { title: "BevyMark".into(), resolution: WindowResolution::new(1920.0, 1080.0) .with_scale_factor_override(1.0), present_mode: PresentMode::AutoNoVsync, ..default() }), ..default() }), FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin::default(), )) .insert_resource(args) .insert_resource(BevyCounter { count: 0, color: Color::WHITE, }) .add_systems(Startup, setup) .add_systems(FixedUpdate, scheduled_spawner) .add_systems( Update, ( mouse_handler, movement_system, collision_system, counter_system, ), ) .insert_resource(Time::::from_duration(Duration::from_secs_f32( FIXED_TIMESTEP, ))) .run(); } #[derive(Resource)] struct BirdScheduled { waves: usize, per_wave: usize, } fn scheduled_spawner( mut commands: Commands, args: Res, windows: Query<&Window>, mut scheduled: ResMut, mut counter: ResMut, bird_resources: ResMut, ) { let window = windows.single(); 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_resources, None, scheduled.waves - 1, ); scheduled.waves -= 1; } } #[derive(Resource)] struct BirdResources { textures: Vec>, materials: Vec>, quad: Mesh2dHandle, color_rng: StdRng, material_rng: StdRng, velocity_rng: StdRng, transform_rng: StdRng, } #[derive(Component)] struct StatsText; #[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 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), transform_rng: StdRng::seed_from_u64(42), }; let text_section = move |color, value: &str| { TextSection::new( value, TextStyle { font_size: 40.0, color, ..default() }, ) }; commands.spawn(Camera2dBundle::default()); 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() }) .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