//! This example provides a 3D benchmark. //! //! Usage: spawn more entities by clicking with the left mouse button. use core::time::Duration; use std::str::FromStr; use argh::FromArgs; use bevy::{ asset::RenderAssetUsages, color::palettes::basic::*, diagnostic::{DiagnosticsStore, FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin}, prelude::*, render::render_resource::{Extent3d, TextureDimension, TextureFormat}, window::{PresentMode, WindowResolution}, winit::WinitSettings, }; use chacha20::ChaCha8Rng; use rand::{seq::IndexedRandom, RngExt, SeedableRng}; const CUBES_PER_SECOND: u32 = 10000; const GRAVITY: f32 = -9.8; const MAX_VELOCITY: f32 = 10.; const CUBE_SCALE: f32 = 1.0; const CUBE_TEXTURE_SIZE: usize = 256; const HALF_CUBE_SIZE: f32 = CUBE_SCALE * 0.5; const VOLUME_WIDTH: usize = 50; const VOLUME_SIZE: Vec3 = Vec3::splat(VOLUME_WIDTH as f32); #[derive(Resource)] struct BevyCounter { pub count: usize, pub color: Color, } #[derive(Component)] struct Cube { velocity: Vec3, } #[derive(FromArgs, Resource)] /// `bevymark_3d` cube stress test struct Args { /// 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 cubes 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, /// the alpha mode used to spawn the cubes #[argh(option, default = "AlphaMode::Opaque")] alpha_mode: AlphaMode, } #[derive(Default, Clone)] enum AlphaMode { #[default] Opaque, 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 3D".into(), resolution: WindowResolution::new(1920, 1080).with_scale_factor_override(1.0), present_mode: PresentMode::AutoNoVsync, ..default() }), ..default() }), FrameTimeDiagnosticsPlugin::default(), LogDiagnosticsPlugin::default(), )) .insert_resource(WinitSettings::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 CubeScheduled { waves: usize, per_wave: usize, } fn scheduled_spawner( mut commands: Commands, args: Res, mut scheduled: ResMut, mut counter: ResMut, cube_resources: ResMut, ) { if scheduled.waves > 0 { let cube_resources = cube_resources.into_inner(); spawn_cubes( &mut commands, args.into_inner(), &mut counter, scheduled.per_wave, cube_resources, None, scheduled.waves - 1, ); scheduled.waves -= 1; } } #[derive(Resource)] struct CubeResources { _textures: Vec>, materials: Vec>, cube_mesh: Handle, color_rng: ChaCha8Rng, material_rng: ChaCha8Rng, velocity_rng: ChaCha8Rng, transform_rng: ChaCha8Rng, } #[derive(Component)] struct StatsText; fn setup( mut commands: Commands, args: Res, asset_server: Res, mut meshes: ResMut>, material_assets: ResMut>, images: ResMut>, 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 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 cube_resources = CubeResources { _textures: textures, materials, cube_mesh: meshes.add(Cuboid::from_size(Vec3::splat(CUBE_SCALE))), color_rng: ChaCha8Rng::seed_from_u64(42), material_rng: ChaCha8Rng::seed_from_u64(12), velocity_rng: ChaCha8Rng::seed_from_u64(97), transform_rng: ChaCha8Rng::seed_from_u64(26), }; let font = TextFont { font_size: FontSize::Px(40.0), ..Default::default() }; commands.spawn(( Camera3d::default(), Transform::from_translation(VOLUME_SIZE * 1.3).looking_at(Vec3::ZERO, Vec3::Y), )); commands.spawn(( DirectionalLight { illuminance: 10000.0, shadow_maps_enabled: false, ..default() }, Transform::from_xyz(1.0, 2.0, 3.0).looking_at(Vec3::ZERO, Vec3::Y), )); commands.spawn(( Node { position_type: PositionType::Absolute, padding: UiRect::all(px(5)), ..default() }, BackgroundColor(Color::BLACK.with_alpha(0.75)), GlobalZIndex(i32::MAX), children![( Text::default(), StatsText, children![ ( TextSpan::new("Cube Count: "), font.clone(), TextColor(LIME.into()), ), (TextSpan::new(""), font.clone(), TextColor(AQUA.into())), ( TextSpan::new("\nFPS (raw): "), font.clone(), TextColor(LIME.into()), ), (TextSpan::new(""), font.clone(), TextColor(AQUA.into())), ( TextSpan::new("\nFPS (SMA): "), font.clone(), TextColor(LIME.into()), ), (TextSpan::new(""), font.clone(), TextColor(AQUA.into())), ( TextSpan::new("\nFPS (EMA): "), font.clone(), TextColor(LIME.into()), ), (TextSpan::new(""), font.clone(), TextColor(AQUA.into())) ] )], )); let mut scheduled = CubeScheduled { per_wave: args.per_wave, waves: args.waves, }; if args.benchmark { let counter = counter.into_inner(); for wave in (0..scheduled.waves).rev() { spawn_cubes( &mut commands, args, counter, scheduled.per_wave, &mut cube_resources, Some(wave), wave, ); } scheduled.waves = 0; } commands.insert_resource(cube_resources); commands.insert_resource(scheduled); } fn mouse_handler( mut commands: Commands, args: Res, time: Res