Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add many_materials stress test #11592

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2086,6 +2086,17 @@ description = "Simple benchmark to test rendering many point lights. Run with `W
category = "Stress Tests"
wasm = true

[[example]]
name = "many_materials"
path = "examples/stress_tests/many_materials.rs"
doc-scrape-examples = true

[package.metadata.example.many_materials]
name = "Many Lights"
description = "Benchmark to test rendering many animated materials"
category = "Stress Tests"
wasm = true

[[example]]
name = "many_sprites"
path = "examples/stress_tests/many_sprites.rs"
Expand Down
45 changes: 33 additions & 12 deletions examples/3d/3d_scene.rs
Original file line number Diff line number Diff line change
@@ -1,34 +1,55 @@
//! A simple 3D scene with light shining over a cube sitting on a plane.

use bevy::pbr::NotShadowCaster;
use bevy::prelude::*;

fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Startup, setup)
//.add_systems(Update, update)
.run();
}

#[derive(Component)]
struct Marker;

fn update(time: Res<Time>, mut commands: Commands, delete: Query<Entity, With<Marker>>) {
if let Some(delete) = delete.get_single().ok() {
if time.elapsed_seconds() > 5.0 {
commands.entity(delete).despawn();
}
}
}

/// set up a simple 3D scene
fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
// circular base
commands.spawn(PbrBundle {
mesh: meshes.add(shape::Circle::new(4.0)),
material: materials.add(Color::WHITE),
transform: Transform::from_rotation(Quat::from_rotation_x(-std::f32::consts::FRAC_PI_2)),
..default()
});
commands.spawn((
PbrBundle {
mesh: meshes.add(shape::Circle::new(4.0)),
material: materials.add(Color::WHITE),
transform: Transform::from_rotation(Quat::from_rotation_x(
-std::f32::consts::FRAC_PI_2,
)),
..default()
},
NotShadowCaster,
));
// cube
commands.spawn(PbrBundle {
mesh: meshes.add(shape::Cube { size: 1.0 }),
material: materials.add(Color::rgb_u8(124, 144, 255)),
transform: Transform::from_xyz(0.0, 0.5, 0.0),
..default()
});
commands.spawn((
PbrBundle {
mesh: meshes.add(shape::Cube { size: 1.0 }),
material: materials.add(Color::rgb_u8(124, 144, 255)),
transform: Transform::from_xyz(0.0, 0.5, 0.0),
..default()
},
Marker,
));
// light
commands.spawn(PointLightBundle {
point_light: PointLight {
Expand Down
1 change: 1 addition & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,7 @@ Example | Description
[Many Gizmos](../examples/stress_tests/many_gizmos.rs) | Test rendering of many gizmos
[Many Glyphs](../examples/stress_tests/many_glyphs.rs) | Simple benchmark to test text rendering.
[Many Lights](../examples/stress_tests/many_lights.rs) | Simple benchmark to test rendering many point lights. Run with `WGPU_SETTINGS_PRIO=webgl2` to restrict to uniform buffers and max 256 lights
[Many Lights](../examples/stress_tests/many_lights.rs) | Benchmark to test rendering many animated materials
[Many Sprites](../examples/stress_tests/many_sprites.rs) | Displays many sprites in a grid arrangement! Used for performance testing. Use `--colored` to enable color tinted sprites.
[Text Pipeline](../examples/stress_tests/text_pipeline.rs) | Text Pipeline benchmark
[Transform Hierarchy](../examples/stress_tests/transform_hierarchy.rs) | Various test cases for hierarchy and transform propagation performance
Expand Down
121 changes: 121 additions & 0 deletions examples/stress_tests/many_materials.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
//! Benchmark to test rendering many animated materials

use bevy::{
diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
prelude::*,
utils::HashSet,
window::{PresentMode, WindowPlugin, WindowResolution},
};
use std::f32::consts::PI;

#[derive(Component)]
struct Floor;

fn main() {
App::new()
.add_plugins((
DefaultPlugins.set(WindowPlugin {
primary_window: Some(Window {
resolution: WindowResolution::new(1920.0, 1080.0)
.with_scale_factor_override(1.0),
title: "many_materials".into(),
present_mode: PresentMode::AutoNoVsync,
..default()
}),
..default()
}),
FrameTimeDiagnosticsPlugin,
LogDiagnosticsPlugin::default(),
))
.add_systems(Startup, setup)
.add_systems(Update, (animate_materials, make_materials_unique))
.run();
}

fn setup(
mut commands: Commands,
asset_server: Res<AssetServer>,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
let n = 4;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is a stress test this should probably be configurable using argh. Something like bevymark.

Copy link
Contributor

@IceSentry IceSentry Jan 28, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe giving it a specific amount would be more useful though.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you're making it configurable with argh, please copy the cfg blocks from the other stress tests using it (eg. many_cubes) as argh::from_env() panics in wasm.


// Camera
commands.spawn(Camera3dBundle {
transform: Transform::from_xyz(n as f32 + 1.0, 1.0, n as f32 + 1.0)
.looking_at(Vec3::new(0.0, -0.5, 0.0), Vec3::Y),
..default()
});

// Plane
commands.spawn((
PbrBundle {
mesh: meshes.add(shape::Plane::from_size(50.0)),
material: materials.add(Color::rgb(0.3, 0.5, 0.3)),
..default()
},
Floor,
));

// Light
commands.spawn(DirectionalLightBundle {
transform: Transform::from_rotation(Quat::from_euler(EulerRot::ZYX, 0.0, 1.0, -PI / 4.)),
directional_light: DirectionalLight {
illuminance: 3000.0,
shadows_enabled: true,
..default()
},
..default()
});

// Helmets
let helmet = asset_server.load("models/FlightHelmet/FlightHelmet.gltf#Scene0");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

using the helmet instead of a pbrbundle means you're also adding hierarchy and complex meshes to the stress test

it also means we don't have direct control of the material and can't try easily if it's because one of the property of it

I would prefer to use a PbrBundle as it makes a more focused stress test, and it wouldn't need the make_materials_unique system

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I think I agree with this analysis. It would be better to be able to properly isolate the effects here.

for x in -n..=n {
for z in -n..=n {
commands.spawn(SceneBundle {
scene: helmet.clone(),
transform: Transform::from_translation(Vec3::new(x as f32, 0.0, z as f32)),
..default()
});
}
}
}

fn animate_materials(
material_handles: Query<&Handle<StandardMaterial>>,
time: Res<Time>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
for (i, material_handle) in material_handles.iter().enumerate() {
if let Some(material) = materials.get_mut(material_handle) {
let color = Color::hsl(
((i as f32 * 2.345 + time.elapsed_seconds_wrapped()) * 100.0) % 360.0,
1.0,
0.5,
);
material.base_color = color;
}
}
}

/// This is needed because by default assets are loaded with shared materials
/// But we want to animate every helmet independently of the others, so we must duplicate the materials
fn make_materials_unique(
mut material_handles: Query<&mut Handle<StandardMaterial>, Without<Floor>>,
mut materials: ResMut<Assets<StandardMaterial>>,
mut ran: Local<bool>,
) {
if *ran {
return;
}
let mut set = HashSet::new();
for mut material_handle in material_handles.iter_mut() {
if set.contains(&material_handle.id()) {
let material = materials.get(&*material_handle).unwrap().clone();
*material_handle = materials.add(material);
} else {
set.insert(material_handle.id());
}
*ran = true;
}
}
Loading