Skip to content

Commit 73447b6

Browse files
ickshonperparrett
andauthored
many_buttons enhancements (#9712)
# Objective `many_buttons` enhancements: * use `argh` to manage the commandline arguments like the other stress tests * add an option to set the number of buttons * add a grid layout option * centre the grid properly * use viewport coords for the layout's style constraints * replace use of absolute positioning includes the changes from #9636 Displaying an image isn't actually about stress testing image rendering. Without a second texture (the first is used by the text) the entire grid will be drawn in a single batch. The extra texture used by the image forces the renderer to break up the batches at every button displaying an image, where it has to switch between the font atlas texture and the image texture. ## Solution <img width="401" alt="many_buttons_new" src="https://github.com/bevyengine/bevy/assets/27962798/82140c6d-d72c-4e4f-b9b6-dd204176e51d"> --- ## Changelog `many_buttons` stress test example enhancements: * uses `argh` to the manage the commandline arguments. * New commandline args: - `--help` display info & list all commandline options - `--buttons` set the number of buttons. - `--image-freq` set the frequency of buttons displaying images - `--grid` use a grid layout * style constraints are specified in viewport coords insead of percentage values * margins and nested bundles are used to construct the layout, instead of absolute positioning * the button grid centered in the window, the empty gap along the bottom and right is removed * an image is drawn as the background to every Nth button where N is set using the `--image-freq` commandline option. --------- Co-authored-by: Rob Parrett <robparrett@gmail.com>
1 parent c8f61c3 commit 73447b6

File tree

1 file changed

+155
-58
lines changed

1 file changed

+155
-58
lines changed

examples/stress_tests/many_buttons.rs

Lines changed: 155 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,48 @@
1-
//! This example shows what happens when there is a lot of buttons on screen.
2-
//!
3-
//! To start the demo without text run
4-
//! `cargo run --example many_buttons --release no-text`
5-
//!
6-
//! //! To start the demo without borders run
7-
//! `cargo run --example many_buttons --release no-borders`
8-
//!
9-
//| To do a full layout update each frame run
10-
//! `cargo run --example many_buttons --release recompute-layout`
11-
//!
12-
//! To recompute all text each frame run
13-
//! `cargo run --example many_buttons --release recompute-text`
14-
1+
/// General UI benchmark that stress tests layouting, text, interaction and rendering
2+
use argh::FromArgs;
153
use bevy::{
164
diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
175
prelude::*,
186
window::{PresentMode, WindowPlugin},
197
};
208

21-
// For a total of 110 * 110 = 12100 buttons with text
22-
const ROW_COLUMN_COUNT: usize = 110;
239
const FONT_SIZE: f32 = 7.0;
2410

11+
#[derive(FromArgs, Resource)]
12+
/// `many_buttons` general UI benchmark that stress tests layouting, text, interaction and rendering
13+
struct Args {
14+
/// whether to add text to each button
15+
#[argh(switch)]
16+
no_text: bool,
17+
18+
/// whether to add borders to each button
19+
#[argh(switch)]
20+
no_borders: bool,
21+
22+
/// whether to perform a full relayout each frame
23+
#[argh(switch)]
24+
relayout: bool,
25+
26+
/// whether to recompute all text each frame
27+
#[argh(switch)]
28+
recompute_text: bool,
29+
30+
/// how many buttons per row and column of the grid.
31+
#[argh(option, default = "110")]
32+
buttons: usize,
33+
34+
/// give every nth button an image
35+
#[argh(option, default = "4")]
36+
image_freq: usize,
37+
38+
/// use the grid layout model
39+
#[argh(switch)]
40+
grid: bool,
41+
}
42+
2543
/// This example shows what happens when there is a lot of buttons on screen.
2644
fn main() {
45+
let args: Args = argh::from_env();
2746
let mut app = App::new();
2847

2948
app.add_plugins((
@@ -37,22 +56,27 @@ fn main() {
3756
FrameTimeDiagnosticsPlugin,
3857
LogDiagnosticsPlugin::default(),
3958
))
40-
.add_systems(Startup, setup)
4159
.add_systems(Update, button_system);
4260

43-
if std::env::args().any(|arg| arg == "recompute-layout") {
44-
app.add_systems(Update, |mut ui_scale: ResMut<UiScale>| {
45-
ui_scale.set_changed();
61+
if args.grid {
62+
app.add_systems(Startup, setup_grid);
63+
} else {
64+
app.add_systems(Startup, setup_flex);
65+
}
66+
67+
if args.relayout {
68+
app.add_systems(Update, |mut style_query: Query<&mut Style>| {
69+
style_query.for_each_mut(|mut style| style.set_changed());
4670
});
4771
}
4872

49-
if std::env::args().any(|arg| arg == "recompute-text") {
73+
if args.recompute_text {
5074
app.add_systems(Update, |mut text_query: Query<&mut Text>| {
5175
text_query.for_each_mut(|mut text| text.set_changed());
5276
});
5377
}
5478

55-
app.run();
79+
app.insert_resource(args).run();
5680
}
5781

5882
#[derive(Component)]
@@ -64,50 +88,117 @@ fn button_system(
6488
Changed<Interaction>,
6589
>,
6690
) {
67-
for (interaction, mut material, IdleColor(idle_color)) in interaction_query.iter_mut() {
68-
if matches!(interaction, Interaction::Hovered) {
69-
*material = Color::ORANGE_RED.into();
70-
} else {
71-
*material = *idle_color;
72-
}
91+
for (interaction, mut button_color, IdleColor(idle_color)) in interaction_query.iter_mut() {
92+
*button_color = match interaction {
93+
Interaction::Hovered => Color::ORANGE_RED.into(),
94+
_ => *idle_color,
95+
};
7396
}
7497
}
7598

76-
fn setup(mut commands: Commands) {
99+
fn setup_flex(mut commands: Commands, asset_server: Res<AssetServer>, args: Res<Args>) {
100+
warn!(include_str!("warning_string.txt"));
101+
let image = if 0 < args.image_freq {
102+
Some(asset_server.load("branding/icon.png"))
103+
} else {
104+
None
105+
};
106+
107+
let buttons_f = args.buttons as f32;
108+
let border = if args.no_borders {
109+
UiRect::ZERO
110+
} else {
111+
UiRect::all(Val::VMin(0.05 * 90. / buttons_f))
112+
};
113+
114+
let as_rainbow = |i: usize| Color::hsl((i as f32 / buttons_f) * 360.0, 0.9, 0.8);
115+
commands.spawn(Camera2dBundle::default());
116+
commands
117+
.spawn(NodeBundle {
118+
style: Style {
119+
flex_direction: FlexDirection::Column,
120+
justify_content: JustifyContent::Center,
121+
align_items: AlignItems::Center,
122+
width: Val::Percent(100.),
123+
..default()
124+
},
125+
..default()
126+
})
127+
.with_children(|commands| {
128+
for column in 0..args.buttons {
129+
commands
130+
.spawn(NodeBundle::default())
131+
.with_children(|commands| {
132+
for row in 0..args.buttons {
133+
let color = as_rainbow(row % column.max(1)).into();
134+
let border_color = Color::WHITE.with_a(0.5).into();
135+
spawn_button(
136+
commands,
137+
color,
138+
buttons_f,
139+
column,
140+
row,
141+
!args.no_text,
142+
border,
143+
border_color,
144+
image
145+
.as_ref()
146+
.filter(|_| (column + row) % args.image_freq == 0)
147+
.cloned(),
148+
);
149+
}
150+
});
151+
}
152+
});
153+
}
154+
155+
fn setup_grid(mut commands: Commands, asset_server: Res<AssetServer>, args: Res<Args>) {
77156
warn!(include_str!("warning_string.txt"));
157+
let image = if 0 < args.image_freq {
158+
Some(asset_server.load("branding/icon.png"))
159+
} else {
160+
None
161+
};
162+
163+
let buttons_f = args.buttons as f32;
164+
let border = if args.no_borders {
165+
UiRect::ZERO
166+
} else {
167+
UiRect::all(Val::VMin(0.05 * 90. / buttons_f))
168+
};
78169

79-
let count = ROW_COLUMN_COUNT;
80-
let count_f = count as f32;
81-
let as_rainbow = |i: usize| Color::hsl((i as f32 / count_f) * 360.0, 0.9, 0.8);
170+
let as_rainbow = |i: usize| Color::hsl((i as f32 / buttons_f) * 360.0, 0.9, 0.8);
82171
commands.spawn(Camera2dBundle::default());
83172
commands
84173
.spawn(NodeBundle {
85174
style: Style {
175+
display: Display::Grid,
86176
width: Val::Percent(100.),
177+
height: Val::Percent(100.0),
178+
grid_template_columns: RepeatedGridTrack::flex(args.buttons as u16, 1.0),
179+
grid_template_rows: RepeatedGridTrack::flex(args.buttons as u16, 1.0),
87180
..default()
88181
},
89182
..default()
90183
})
91184
.with_children(|commands| {
92-
let spawn_text = std::env::args().all(|arg| arg != "no-text");
93-
let border = if std::env::args().all(|arg| arg != "no-borders") {
94-
UiRect::all(Val::Percent(10. / count_f))
95-
} else {
96-
UiRect::DEFAULT
97-
};
98-
for i in 0..count {
99-
for j in 0..count {
100-
let color = as_rainbow(j % i.max(1)).into();
101-
let border_color = as_rainbow(i % j.max(1)).into();
185+
for column in 0..args.buttons {
186+
for row in 0..args.buttons {
187+
let color = as_rainbow(row % column.max(1)).into();
188+
let border_color = Color::WHITE.with_a(0.5).into();
102189
spawn_button(
103190
commands,
104191
color,
105-
count_f,
106-
i,
107-
j,
108-
spawn_text,
192+
buttons_f,
193+
column,
194+
row,
195+
!args.no_text,
109196
border,
110197
border_color,
198+
image
199+
.as_ref()
200+
.filter(|_| (column + row) % args.image_freq == 0)
201+
.cloned(),
111202
);
112203
}
113204
}
@@ -118,23 +209,25 @@ fn setup(mut commands: Commands) {
118209
fn spawn_button(
119210
commands: &mut ChildBuilder,
120211
background_color: BackgroundColor,
121-
total: f32,
122-
i: usize,
123-
j: usize,
212+
buttons: f32,
213+
column: usize,
214+
row: usize,
124215
spawn_text: bool,
125216
border: UiRect,
126217
border_color: BorderColor,
218+
image: Option<Handle<Image>>,
127219
) {
128-
let width = 90.0 / total;
220+
let width = Val::Vw(90.0 / buttons);
221+
let height = Val::Vh(90.0 / buttons);
222+
let margin = UiRect::axes(width * 0.05, height * 0.05);
129223
let mut builder = commands.spawn((
130224
ButtonBundle {
131225
style: Style {
132-
width: Val::Percent(width),
133-
height: Val::Percent(width),
134-
bottom: Val::Percent(100.0 / total * i as f32),
135-
left: Val::Percent(100.0 / total * j as f32),
226+
width,
227+
height,
228+
margin,
136229
align_items: AlignItems::Center,
137-
position_type: PositionType::Absolute,
230+
justify_content: JustifyContent::Center,
138231
border,
139232
..default()
140233
},
@@ -145,10 +238,14 @@ fn spawn_button(
145238
IdleColor(background_color),
146239
));
147240

241+
if let Some(image) = image {
242+
builder.insert(UiImage::new(image));
243+
}
244+
148245
if spawn_text {
149-
builder.with_children(|commands| {
150-
commands.spawn(TextBundle::from_section(
151-
format!("{i}, {j}"),
246+
builder.with_children(|parent| {
247+
parent.spawn(TextBundle::from_section(
248+
format!("{column}, {row}"),
152249
TextStyle {
153250
font_size: FONT_SIZE,
154251
color: Color::rgb(0.2, 0.2, 0.2),

0 commit comments

Comments
 (0)