//! This example provides a 2D benchmark. //! //! Usage: spawn more entities by clicking on the screen. use std::str::FromStr; use argh::FromArgs; use bevy::{ color::palettes::basic::*, diagnostic::{DiagnosticsStore, FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin}, prelude::*, render::{ render_asset::RenderAssetUsages, render_resource::{Extent3d, TextureDimension, TextureFormat}, }, sprite::AlphaMode2d, utils::Duration, window::{PresentMode, WindowResolution}, winit::{UpdateMode, WinitSettings}, }; use rand::{seq::SliceRandom, Rng, SeedableRng}; use rand_chacha::ChaCha8Rng; 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, /// the alpha mode used to spawn the sprites #[argh(option, default = "AlphaMode::Blend")] alpha_mode: AlphaMode, } #[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'" )), } } } #[derive(Default, Clone)] enum AlphaMode { Opaque, #[default] Blend, AlphaMask, } impl FromStr for AlphaMode { type Err = String; fn from_str(s: &str) -> Result { match s { "opaque" => Ok(Self::Opaque), "blend" => Ok(Self::Blend), "alpha_mask" => Ok(Self::AlphaMask), _ => Err(format!( "Unknown alpha mode: '{s}', valid modes: 'opaque', 'blend', 'alpha_mask'" )), } } } const FIXED_TIMESTEP: f32 = 0.2; 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 { 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(WinitSettings { focused_mode: UpdateMode::Continuous, unfocused_mode: UpdateMode::Continuous, }) .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, window: Single<&Window>, mut scheduled: ResMut, mut counter: ResMut, bird_resources: ResMut, ) { 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: Handle, color_rng: ChaCha8Rng, material_rng: ChaCha8Rng, velocity_rng: ChaCha8Rng, transform_rng: ChaCha8Rng, } #[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>, window: Single<&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(Rectangle::from_size(Vec2::splat(BIRD_TEXTURE_SIZE as f32))), // We're seeding the PRNG here to make this example deterministic for testing purposes. // This isn't strictly required in practical use unless you need your app to be deterministic. color_rng: ChaCha8Rng::seed_from_u64(42), material_rng: ChaCha8Rng::seed_from_u64(42), velocity_rng: ChaCha8Rng::seed_from_u64(42), transform_rng: ChaCha8Rng::seed_from_u64(42), }; let font = TextFont { font_size: 40.0, ..Default::default() }; commands.spawn(Camera2d); commands .spawn(( Node { position_type: PositionType::Absolute, padding: UiRect::all(Val::Px(5.0)), ..default() }, BackgroundColor(Color::BLACK.with_alpha(0.75)), GlobalZIndex(i32::MAX), )) .with_children(|p| { p.spawn((Text::default(), StatsText)).with_children(|p| { p.spawn(( TextSpan::new("Bird Count: "), font.clone(), TextColor(LIME.into()), )); p.spawn((TextSpan::new(""), font.clone(), TextColor(AQUA.into()))); p.spawn(( TextSpan::new("\nFPS (raw): "), font.clone(), TextColor(LIME.into()), )); p.spawn((TextSpan::new(""), font.clone(), TextColor(AQUA.into()))); p.spawn(( TextSpan::new("\nFPS (SMA): "), font.clone(), TextColor(LIME.into()), )); p.spawn((TextSpan::new(""), font.clone(), TextColor(AQUA.into()))); p.spawn(( TextSpan::new("\nFPS (EMA): "), font.clone(), TextColor(LIME.into()), )); p.spawn((TextSpan::new(""), font.clone(), TextColor(AQUA.into()))); }); }); 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, &window.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