Path: blob/main/examples/large_scenes/caldera_hotel/src/main.rs
9356 views
// Press B for benchmark.1// Preferably after frame time is reading consistently, rust-analyzer has calmed down, and with locked gpu clocks.23use std::{f32::consts::PI, time::Instant};45use crate::light_consts::lux;6use argh::FromArgs;7use bevy::pbr::ContactShadows;8use bevy::{9anti_alias::taa::TemporalAntiAliasing,10camera::{11visibility::{NoCpuCulling, NoFrustumCulling},12Hdr,13},14camera_controller::free_camera::{FreeCamera, FreeCameraPlugin},15core_pipeline::prepass::{DeferredPrepass, DepthPrepass},16diagnostic::{DiagnosticsStore, FrameTimeDiagnosticsPlugin},17image::{ImageAddressMode, ImageSampler, ImageSamplerDescriptor},18light::{CascadeShadowConfig, CascadeShadowConfigBuilder},19pbr::{DefaultOpaqueRendererMethod, ScreenSpaceAmbientOcclusion},20post_process::bloom::Bloom,21prelude::*,22render::{23batching::NoAutomaticBatching,24occlusion_culling::OcclusionCulling,25render_resource::{26Extent3d, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages,27},28view::NoIndirectDrawing,29},30scene::SceneInstanceReady,31window::{PresentMode, WindowResolution},32winit::WinitSettings,33};3435#[derive(FromArgs, Resource, Clone)]36/// Config37pub struct Args {38/// disable bloom, AO, AA, shadows39#[argh(switch)]40minimal: bool,4142/// assign randomly generated materials to each unique mesh (mesh instances also share materials)43#[argh(switch)]44random_materials: bool,4546/// quantity of unique textures sets to randomly select from. (A texture set being: base_color, roughness)47#[argh(option, default = "0")]48texture_count: u32,4950/// quantity of hotel 01 models51#[argh(option, default = "1")]52count: u32,5354/// use deferred shading55#[argh(switch)]56deferred: bool,5758/// disable all frustum culling. Stresses queuing and batching as all mesh material entities in the scene are always drawn.59#[argh(switch)]60no_frustum_culling: bool,6162/// disable automatic batching. Skips batching resulting in heavy stress on render pass draw command encoding.63#[argh(switch)]64no_automatic_batching: bool,6566/// disable gpu occlusion culling for the camera67#[argh(switch)]68no_view_occlusion_culling: bool,6970/// disable gpu occlusion culling for the directional light71#[argh(switch)]72no_shadow_occlusion_culling: bool,7374/// disable indirect drawing.75#[argh(switch)]76no_indirect_drawing: bool,7778/// disable CPU culling.79#[argh(switch)]80no_cpu_culling: bool,8182/// spin the bistros and camera83#[argh(switch)]84spin: bool,8586/// don't show frame time87#[argh(switch)]88hide_frame_time: bool,89}9091pub fn main() {92let args: Args = argh::from_env();9394let mut app = App::new();9596app.init_resource::<CameraPositions>()97.init_resource::<FrameLowHigh>()98.insert_resource(args.clone())99.insert_resource(WinitSettings::continuous())100.add_plugins(DefaultPlugins.set(WindowPlugin {101primary_window: Some(Window {102present_mode: PresentMode::Immediate,103resolution: WindowResolution::new(1920, 1080).with_scale_factor_override(1.0),104..default()105}),106..default()107}))108.add_plugins((109FrameTimeDiagnosticsPlugin {110max_history_length: 1000,111..default()112},113FreeCameraPlugin,114))115.add_systems(Startup, setup)116.add_systems(Update, (input, spin, frame_time_system, benchmark).chain());117118if args.no_frustum_culling {119app.add_systems(Update, add_no_frustum_culling);120}121122if args.deferred {123app.insert_resource(DefaultOpaqueRendererMethod::deferred());124}125126app.run();127}128129#[derive(Component)]130pub struct Spin;131132#[derive(Component)]133struct FrameTimeText;134135#[derive(Component)]136pub struct PostProcScene;137138pub fn setup(139mut commands: Commands,140asset_server: Res<AssetServer>,141args: Res<Args>,142positions: Res<CameraPositions>,143) {144let hotel_01 = asset_server.load("hotel_01.glb#Scene0");145commands146.spawn((147SceneRoot(hotel_01.clone()),148Transform::from_scale(Vec3::splat(0.01)),149PostProcScene,150Spin,151))152.observe(assign_rng_materials);153154let mut count = 0;155if args.count > 1 {156let quantity = args.count - 1;157let side = (quantity as f32).sqrt().ceil() as i32 / 2;158'outer: for x in -side..=side {159for z in -side..=side {160if count >= quantity {161break 'outer;162}163if x == 0 && z == 0 {164continue;165}166commands.spawn((167SceneRoot(hotel_01.clone()),168Transform::from_xyz(x as f32 * 50.0, 0.0, z as f32 * 50.0)169.with_scale(Vec3::splat(0.01)),170Spin,171));172count += 1;173}174}175}176177// Sun178commands179.spawn((180Transform::from_rotation(Quat::from_euler(EulerRot::XYZ, PI * -0.35, PI * -0.13, 0.0)),181DirectionalLight {182color: Color::srgb(1.0, 0.87, 0.78),183illuminance: lux::FULL_DAYLIGHT,184shadow_maps_enabled: !args.minimal,185contact_shadows_enabled: !args.minimal,186shadow_depth_bias: 0.2,187shadow_normal_bias: 0.2,188..default()189},190CascadeShadowConfig::from(CascadeShadowConfigBuilder {191num_cascades: 3,192minimum_distance: 0.1,193maximum_distance: 80.0,194first_cascade_far_bound: 5.0,195overlap_proportion: 0.2,196}),197))198.insert_if(OcclusionCulling, || !args.no_shadow_occlusion_culling);199200// Camera201let mut cam = commands.spawn((202Msaa::Off,203Camera3d::default(),204Hdr,205positions[0],206Projection::Perspective(PerspectiveProjection {207fov: std::f32::consts::PI / 3.0,208near: 0.1,209far: 1000.0,210..Default::default()211}),212EnvironmentMapLight {213diffuse_map: asset_server.load("environment_maps/pisa_diffuse_rgb9e5_zstd.ktx2"),214specular_map: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"),215intensity: 1000.0,216..default()217},218ContactShadows::default(),219FreeCamera::default(),220Spin,221));222223cam.insert_if(DepthPrepass, || args.deferred)224.insert_if(DeferredPrepass, || args.deferred)225.insert_if(OcclusionCulling, || !args.no_view_occlusion_culling)226.insert_if(NoFrustumCulling, || args.no_frustum_culling)227.insert_if(NoAutomaticBatching, || args.no_automatic_batching)228.insert_if(NoIndirectDrawing, || args.no_indirect_drawing)229.insert_if(NoCpuCulling, || args.no_cpu_culling);230231if !args.minimal {232cam.insert((233Bloom {234intensity: 0.02,235..default()236},237TemporalAntiAliasing::default(),238))239.insert(ScreenSpaceAmbientOcclusion::default());240}241242if !args.hide_frame_time {243commands244.spawn((245Node {246left: px(1.5),247top: px(1.5),248..default()249},250GlobalZIndex(-1),251))252.with_children(|parent| {253parent.spawn((Text::new(""), TextColor(Color::BLACK), FrameTimeText));254});255commands.spawn(Node::default()).with_children(|parent| {256parent.spawn((Text::new(""), TextColor(Color::WHITE), FrameTimeText));257});258}259}260261// Go though each unique mesh and randomly generate a material.262// Each unique so instances are maintained.263#[allow(clippy::too_many_arguments)]264pub fn assign_rng_materials(265scene_ready: On<SceneInstanceReady>,266mut commands: Commands,267mut materials: ResMut<Assets<StandardMaterial>>,268mut images: ResMut<Assets<Image>>,269meshes: Res<Assets<Mesh>>,270mesh_instances: Query<(Entity, &Mesh3d)>,271args: Res<Args>,272asset_server: Res<AssetServer>,273scenes: Query<&SceneRoot>,274) {275if !args.random_materials {276return;277}278279let Ok(scene) = scenes.get(scene_ready.entity) else {280return;281};282283let scene_loaded = asset_server284.get_recursive_dependency_load_state(&scene.0)285.map(|state| state.is_loaded())286.unwrap_or(false);287288if !scene_loaded {289warn!("get_recursive_dependency_load_state not finished!");290}291292const MESH_INSTANCE_QTY: usize = 35689;293if MESH_INSTANCE_QTY != mesh_instances.iter().len() {294warn!(295"Mesh quantity appears incorrect. Expected: {}. Found: {}!",296MESH_INSTANCE_QTY,297mesh_instances.iter().len()298)299}300301let base_color_textures = (0..args.texture_count)302.map(|i| {303images.add(generate_random_compressed_texture_with_mipmaps(3042048, false, i,305))306})307.collect::<Vec<_>>();308let roughness_textures = (0..args.texture_count)309.map(|i| {310images.add(generate_random_compressed_texture_with_mipmaps(3112048,312false, // Using bc4 here seems to not work313i + 2048,314))315})316.collect::<Vec<_>>();317318for (i, (mesh_h, _mesh)) in meshes.iter().enumerate() {319let mut base_color_texture = None;320let mut roughness_texture = None;321322if !base_color_textures.is_empty() {323base_color_texture = Some(base_color_textures[i % base_color_textures.len()].clone());324}325if !roughness_textures.is_empty() {326roughness_texture = Some(roughness_textures[i % roughness_textures.len()].clone());327}328329let unique_material = materials.add(StandardMaterial {330base_color: Color::srgb(331hash_noise(i as u32, 0, 0),332hash_noise(i as u32, 0, 1),333hash_noise(i as u32, 0, 2),334),335base_color_texture,336metallic_roughness_texture: roughness_texture,337..default()338});339for (entity, mesh_instance_h) in mesh_instances.iter() {340if mesh_instance_h.id() == mesh_h {341commands342.entity(entity)343.insert(MeshMaterial3d::from(unique_material.clone()));344}345}346}347}348349fn generate_random_compressed_texture_with_mipmaps(size: u32, bc4: bool, seed: u32) -> Image {350let (bytes, mip_count) = calculate_bcn_image_size_with_mips(size, if bc4 { 8 } else { 16 });351let data = (0..bytes).map(|i| uhash(i, seed) as u8).collect::<Vec<_>>();352353Image {354texture_descriptor: TextureDescriptor {355label: None,356size: Extent3d {357width: size,358height: size,359..default()360},361dimension: TextureDimension::D2,362format: if bc4 {363TextureFormat::Bc4RUnorm364} else {365TextureFormat::Bc7RgbaUnormSrgb366},367mip_level_count: mip_count,368sample_count: 1,369usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST,370view_formats: &[],371},372sampler: ImageSampler::Descriptor(ImageSamplerDescriptor {373address_mode_u: ImageAddressMode::Repeat,374address_mode_v: ImageAddressMode::Repeat,375..default()376}),377378data: Some(data),379..Default::default()380}381}382383#[derive(Resource, Deref, DerefMut)]384pub struct CameraPositions([Transform; 3]);385386impl Default for CameraPositions {387fn default() -> Self {388Self([389Transform {390translation: Vec3::new(-20.147331, 16.818098, 42.806145),391rotation: Quat::from_array([-0.22917402, -0.34915298, -0.08848568, 0.9042908]),392scale: Vec3::ONE,393},394Transform {395translation: Vec3::new(1.6168646, 1.8304176, -5.846825),396rotation: Quat::from_array([-0.0007061247, -0.99179053, 0.12775362, -0.005481863]),397scale: Vec3::ONE,398},399Transform {400translation: Vec3::new(23.97184, 1.8938808, 30.568554),401rotation: Quat::from_array([-0.0013945175, 0.4685419, 0.00073959737, 0.8834399]),402scale: Vec3::ONE,403},404])405}406}407408fn input(409input: Res<ButtonInput<KeyCode>>,410mut camera: Query<&mut Transform, With<Camera>>,411positions: Res<CameraPositions>,412) {413let Ok(mut transform) = camera.single_mut() else {414return;415};416if input.just_pressed(KeyCode::KeyI) {417info!("{:?}", transform);418}419if input.just_pressed(KeyCode::Digit1) {420*transform = positions[0]421}422if input.just_pressed(KeyCode::Digit2) {423*transform = positions[1]424}425if input.just_pressed(KeyCode::Digit3) {426*transform = positions[2]427}428}429430fn spin(431camera: Single<Entity, With<Camera>>,432mut things_to_spin: Query<&mut Transform, With<Spin>>,433time: Res<Time>,434args: Res<Args>,435mut positions: ResMut<CameraPositions>,436) {437if args.spin {438let camera_position = things_to_spin.get(*camera).unwrap().translation;439let spin = |thing_to_spin: &mut Transform| {440thing_to_spin.rotate_around(camera_position, Quat::from_rotation_y(time.delta_secs()));441};442things_to_spin.iter_mut().for_each(|mut s| spin(s.as_mut())); // WHY443positions.iter_mut().for_each(spin);444}445}446447#[allow(clippy::too_many_arguments)]448fn benchmark(449input: Res<ButtonInput<KeyCode>>,450mut camera_transform: Single<&mut Transform, With<Camera>>,451materials: Res<Assets<StandardMaterial>>,452meshes: Res<Assets<Mesh>>,453has_std_mat: Query<&MeshMaterial3d<StandardMaterial>>,454has_mesh: Query<&Mesh3d>,455mut bench_started: Local<Option<Instant>>,456mut bench_frame: Local<u32>,457mut count_per_step: Local<u32>,458time: Res<Time>,459positions: Res<CameraPositions>,460mut low_high: ResMut<FrameLowHigh>,461) {462if input.just_pressed(KeyCode::KeyB) && bench_started.is_none() {463low_high.bench_reset();464*bench_started = Some(Instant::now());465*bench_frame = 0;466// Try to render for around 3s or at least 60 frames per step467*count_per_step = ((3.0 / time.delta_secs()) as u32).max(60);468println!(469"Starting Benchmark with {} frames per step",470*count_per_step471);472}473if bench_started.is_none() {474return;475}476if *bench_frame == 0 {477**camera_transform = positions[0]478} else if *bench_frame == *count_per_step {479**camera_transform = positions[1]480} else if *bench_frame == *count_per_step * 2 {481**camera_transform = positions[2]482} else if *bench_frame == *count_per_step * 3 {483let elapsed = bench_started.unwrap().elapsed().as_secs_f32();484println!(485"{:>7.2}ms Benchmark avg cpu frame time",486(elapsed / *bench_frame as f32) * 1000.0487);488let r = 1.0 / *bench_frame as f64;489println!("{:>7.2}ms avg 1% low", low_high.sum_one_percent_low * r);490println!("{:>7.2}ms avg 1% high", low_high.sum_one_percent_high * r);491println!(492"{:>7} Meshes\n{:>7} Mesh Instances\n{:>7} Materials\n{:>7} Material Instances",493meshes.len(),494has_mesh.iter().len(),495materials.len(),496has_std_mat.iter().len(),497);498*bench_started = None;499*bench_frame = 0;500**camera_transform = positions[0];501}502*bench_frame += 1;503low_high.bench_step();504}505506pub fn add_no_frustum_culling(507mut commands: Commands,508convert_query: Query<509Entity,510(511Without<NoFrustumCulling>,512With<MeshMaterial3d<StandardMaterial>>,513),514>,515) {516for entity in convert_query.iter() {517commands.entity(entity).insert(NoFrustumCulling);518}519}520521#[inline(always)]522pub fn uhash(a: u32, b: u32) -> u32 {523let mut x = (a.overflowing_mul(1597334673).0) ^ (b.overflowing_mul(3812015801).0);524// from https://nullprogram.com/blog/2018/07/31/525x = x ^ (x >> 16);526x = x.overflowing_mul(0x7feb352d).0;527x = x ^ (x >> 15);528x = x.overflowing_mul(0x846ca68b).0;529x = x ^ (x >> 16);530x531}532533#[inline(always)]534pub fn unormf(n: u32) -> f32 {535n as f32 * (1.0 / 0xffffffffu32 as f32)536}537538#[inline(always)]539pub fn hash_noise(x: u32, y: u32, z: u32) -> f32 {540let urnd = uhash(x, (y << 11) + z);541unormf(urnd)542}543544// BC7 block is 16 bytes, BC4 block is 8 bytes545fn calculate_bcn_image_size_with_mips(size: u32, block_size: u32) -> (u32, u32) {546let mut total_size = 0;547let mut mip_size = size;548let mut mip_count = 0;549while mip_size > 4 {550mip_count += 1;551let num_blocks = mip_size / 4; // Round up552let mip_level_size = num_blocks * num_blocks * block_size;553total_size += mip_level_size;554mip_size = (mip_size / 2).max(1);555}556(total_size, mip_count.max(1))557}558559#[derive(Resource, Default)]560struct FrameLowHigh {561one_percent_low: f64,562one_percent_high: f64,563sum_one_percent_low: f64,564sum_one_percent_high: f64,565}566567impl FrameLowHigh {568fn bench_reset(&mut self) {569self.sum_one_percent_high = 0.0;570self.sum_one_percent_low = 0.0;571}572fn bench_step(&mut self) {573self.sum_one_percent_high += self.one_percent_high;574self.sum_one_percent_low += self.one_percent_low;575}576}577578fn frame_time_system(579diagnostics: Res<DiagnosticsStore>,580mut text: Query<&mut Text, With<FrameTimeText>>,581mut measurements: Local<Vec<f64>>,582mut low_high: ResMut<FrameLowHigh>,583) {584if let Some(frame_time) = diagnostics.get(&FrameTimeDiagnosticsPlugin::FRAME_TIME) {585let mut string = format!(586"\n{:>7.2}ms ema\n{:>7.2}ms sma\n",587frame_time.smoothed().unwrap_or_default(),588frame_time.average().unwrap_or_default()589);590591if frame_time.history_len() >= 100 {592measurements.clear();593measurements.extend(frame_time.measurements().map(|t| t.value));594measurements.sort_by(|a, b| a.partial_cmp(b).unwrap());595let count = measurements.len() / 100;596low_high.one_percent_low = measurements.iter().take(count).sum::<f64>() / count as f64;597low_high.one_percent_high =598measurements.iter().rev().take(count).sum::<f64>() / count as f64;599600string.push_str(&format!(601"{:>7.2}ms 1% low\n{:>7.2}ms 1% high\n",602low_high.one_percent_low, low_high.one_percent_high603));604}605606for mut t in &mut text {607t.0 = string.clone();608}609};610}611612613