Skip to content

Commit 46f5411

Browse files
Add TextureAtlas stress test based on many_sprites and sprite_sheet examples (#5087)
# Objective Intended to close #5073 ## Solution Adds a stress test that use TextureAtlas based on the existing many_sprites test using the animated sprite implementation from the sprite_sheet example. In order to satisfy the goals described in #5073 the animations are all slightly offset. Of note is that the original stress test was designed to test fullstrum culling. I kept this test similar as to facilitate easy comparisons between the use of TextureAtlas and without.
1 parent 49da4e7 commit 46f5411

File tree

3 files changed

+147
-0
lines changed

3 files changed

+147
-0
lines changed

Cargo.toml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1216,6 +1216,16 @@ description = "A heavy sprite rendering workload to benchmark your system with B
12161216
category = "Stress Tests"
12171217
wasm = true
12181218

1219+
[[example]]
1220+
name = "many_animated_sprites"
1221+
path = "examples/stress_tests/many_animated_sprites.rs"
1222+
1223+
[package.metadata.example.many_animated_sprites]
1224+
name = "Many Animated Sprites"
1225+
description = "Displays many animated sprites in a grid arragement with slight offsets to their animation timers. Used for performance testing."
1226+
category = "Stress Tests"
1227+
wasm = true
1228+
12191229
[[example]]
12201230
name = "many_cubes"
12211231
path = "examples/stress_tests/many_cubes.rs"

examples/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,7 @@ cargo run --release --example <example name>
276276
Example | Description
277277
--- | ---
278278
[Bevymark](../examples/stress_tests/bevymark.rs) | A heavy sprite rendering workload to benchmark your system with Bevy
279+
[Many Animated Sprites](../examples/stress_tests/many_animated_sprites.rs) | Displays many animated sprites in a grid arragement with slight offsets to their animation timers. Used for performance testing.
279280
[Many Cubes](../examples/stress_tests/many_cubes.rs) | Simple benchmark to test per-entity draw overhead. Run with the `sphere` argument to test frustum culling
280281
[Many Foxes](../examples/stress_tests/many_foxes.rs) | Loads an animated fox model and spawns lots of them. Good for testing skinned mesh performance. Takes an unsigned integer argument for the number of foxes to spawn. Defaults to 1000
281282
[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
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
//! Renders a lot of animated sprites to allow performance testing.
2+
//!
3+
//! It sets up many animated sprites in different sizes and rotations, and at different scales in the world,
4+
//! and moves the camera over them to see how well frustum culling works.
5+
//!
6+
//! To measure performance realistically, be sure to run this in release mode.
7+
//! `cargo run --example many_animated_sprites --release`
8+
9+
use std::time::Duration;
10+
11+
use bevy::{
12+
diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
13+
math::Quat,
14+
prelude::*,
15+
render::camera::Camera,
16+
};
17+
18+
use rand::Rng;
19+
20+
const CAMERA_SPEED: f32 = 1000.0;
21+
22+
fn main() {
23+
App::new()
24+
// Since this is also used as a benchmark, we want it to display performance data.
25+
.add_plugin(LogDiagnosticsPlugin::default())
26+
.add_plugin(FrameTimeDiagnosticsPlugin::default())
27+
.add_plugins(DefaultPlugins)
28+
.add_startup_system(setup)
29+
.add_system(animate_sprite)
30+
.add_system(print_sprite_count)
31+
.add_system(move_camera.after(print_sprite_count))
32+
.run();
33+
}
34+
35+
fn setup(
36+
mut commands: Commands,
37+
assets: Res<AssetServer>,
38+
mut texture_atlases: ResMut<Assets<TextureAtlas>>,
39+
) {
40+
let mut rng = rand::thread_rng();
41+
42+
let tile_size = Vec2::splat(64.0);
43+
let map_size = Vec2::splat(320.0);
44+
45+
let half_x = (map_size.x / 2.0) as i32;
46+
let half_y = (map_size.y / 2.0) as i32;
47+
48+
let texture_handle = assets.load("textures/rpg/chars/gabe/gabe-idle-run.png");
49+
let texture_atlas = TextureAtlas::from_grid(texture_handle, Vec2::new(24.0, 24.0), 7, 1);
50+
let texture_atlas_handle = texture_atlases.add(texture_atlas);
51+
52+
// Spawns the camera
53+
commands
54+
.spawn()
55+
.insert_bundle(Camera2dBundle::default())
56+
.insert(Transform::from_xyz(0.0, 0.0, 1000.0));
57+
58+
// Builds and spawns the sprites
59+
for y in -half_y..half_y {
60+
for x in -half_x..half_x {
61+
let position = Vec2::new(x as f32, y as f32);
62+
let translation = (position * tile_size).extend(rng.gen::<f32>());
63+
let rotation = Quat::from_rotation_z(rng.gen::<f32>());
64+
let scale = Vec3::splat(rng.gen::<f32>() * 2.0);
65+
let mut timer = Timer::from_seconds(0.1, true);
66+
timer.set_elapsed(Duration::from_secs_f32(rng.gen::<f32>()));
67+
68+
commands
69+
.spawn_bundle(SpriteSheetBundle {
70+
texture_atlas: texture_atlas_handle.clone(),
71+
transform: Transform {
72+
translation,
73+
rotation,
74+
scale,
75+
},
76+
sprite: TextureAtlasSprite {
77+
custom_size: Some(tile_size),
78+
..default()
79+
},
80+
..default()
81+
})
82+
.insert(AnimationTimer(timer));
83+
}
84+
}
85+
}
86+
87+
// System for rotating and translating the camera
88+
fn move_camera(time: Res<Time>, mut camera_query: Query<&mut Transform, With<Camera>>) {
89+
let mut camera_transform = camera_query.single_mut();
90+
camera_transform.rotate(Quat::from_rotation_z(time.delta_seconds() * 0.5));
91+
*camera_transform = *camera_transform
92+
* Transform::from_translation(Vec3::X * CAMERA_SPEED * time.delta_seconds());
93+
}
94+
95+
#[derive(Component, Deref, DerefMut)]
96+
struct AnimationTimer(Timer);
97+
98+
fn animate_sprite(
99+
time: Res<Time>,
100+
texture_atlases: Res<Assets<TextureAtlas>>,
101+
mut query: Query<(
102+
&mut AnimationTimer,
103+
&mut TextureAtlasSprite,
104+
&Handle<TextureAtlas>,
105+
)>,
106+
) {
107+
for (mut timer, mut sprite, texture_atlas_handle) in query.iter_mut() {
108+
timer.tick(time.delta());
109+
if timer.just_finished() {
110+
let texture_atlas = texture_atlases.get(texture_atlas_handle).unwrap();
111+
sprite.index = (sprite.index + 1) % texture_atlas.textures.len();
112+
}
113+
}
114+
}
115+
116+
#[derive(Deref, DerefMut)]
117+
struct PrintingTimer(Timer);
118+
119+
impl Default for PrintingTimer {
120+
fn default() -> Self {
121+
Self(Timer::from_seconds(1.0, true))
122+
}
123+
}
124+
125+
// System for printing the number of sprites on every tick of the timer
126+
fn print_sprite_count(
127+
time: Res<Time>,
128+
mut timer: Local<PrintingTimer>,
129+
sprites: Query<&TextureAtlasSprite>,
130+
) {
131+
timer.tick(time.delta());
132+
133+
if timer.just_finished() {
134+
info!("Sprites: {}", sprites.iter().count(),);
135+
}
136+
}

0 commit comments

Comments
 (0)