Skip to main content

many_gradients/
many_gradients.rs

1//! Stress test demonstrating gradient performance improvements.
2//!
3//! This example creates many UI nodes with gradients to measure the performance
4//! impact of pre-converting colors to the target color space on the CPU.
5
6use argh::FromArgs;
7use bevy::{
8    color::palettes::css::*,
9    diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
10    math::ops::sin,
11    prelude::*,
12    ui::{
13        BackgroundGradient, ColorStop, Display, Gradient, InterpolationColorSpace, LinearGradient,
14        RepeatedGridTrack,
15    },
16    window::{PresentMode, WindowResolution},
17    winit::WinitSettings,
18};
19
20const COLS: usize = 30;
21
22#[derive(FromArgs, Resource, Debug)]
23/// Gradient stress test
24struct Args {
25    /// how many gradients per group (default: 900)
26    #[argh(option, default = "900")]
27    gradient_count: usize,
28
29    /// whether to animate gradients by changing colors
30    #[argh(switch)]
31    animate: bool,
32
33    /// use sRGB interpolation
34    #[argh(switch)]
35    srgb: bool,
36
37    /// use HSL interpolation
38    #[argh(switch)]
39    hsl: bool,
40}
41
42fn main() {
43    // `from_env` panics on the web
44    #[cfg(not(target_arch = "wasm32"))]
45    let args: Args = argh::from_env();
46    #[cfg(target_arch = "wasm32")]
47    let args = Args::from_args(&[], &[]).unwrap();
48
49    let total_gradients = args.gradient_count;
50
51    println!("Gradient stress test with {total_gradients} gradients");
52    println!(
53        "Color space: {}",
54        if args.srgb {
55            "sRGB"
56        } else if args.hsl {
57            "HSL"
58        } else {
59            "OkLab (default)"
60        }
61    );
62
63    App::new()
64        .add_plugins((
65            LogDiagnosticsPlugin::default(),
66            FrameTimeDiagnosticsPlugin::default(),
67            DefaultPlugins.set(WindowPlugin {
68                primary_window: Some(Window {
69                    title: "Gradient Stress Test".to_string(),
70                    resolution: WindowResolution::new(1920, 1080).with_scale_factor_override(1.0),
71                    present_mode: PresentMode::AutoNoVsync,
72                    ..default()
73                }),
74                ..default()
75            }),
76        ))
77        .insert_resource(WinitSettings::continuous())
78        .insert_resource(args)
79        .add_systems(Startup, setup)
80        .add_systems(Update, animate_gradients)
81        .run();
82}
83
84fn setup(mut commands: Commands, args: Res<Args>) {
85    warn!(include_str!("warning_string.txt"));
86
87    commands.spawn(Camera2d);
88
89    let rows_to_spawn = args.gradient_count.div_ceil(COLS);
90
91    // Create a grid of gradients
92    commands
93        .spawn(Node {
94            width: percent(100),
95            height: percent(100),
96            display: Display::Grid,
97            grid_template_columns: RepeatedGridTrack::flex(COLS as u16, 1.0),
98            grid_template_rows: RepeatedGridTrack::flex(rows_to_spawn as u16, 1.0),
99            ..default()
100        })
101        .with_children(|parent| {
102            for i in 0..args.gradient_count {
103                let angle = (i as f32 * 10.0) % 360.0;
104
105                let mut gradient = LinearGradient::new(
106                    angle,
107                    vec![
108                        ColorStop::new(RED, percent(0)),
109                        ColorStop::new(BLUE, percent(100)),
110                        ColorStop::new(GREEN, percent(20)),
111                        ColorStop::new(YELLOW, percent(40)),
112                        ColorStop::new(ORANGE, percent(60)),
113                        ColorStop::new(LIME, percent(80)),
114                        ColorStop::new(DARK_CYAN, percent(90)),
115                    ],
116                );
117
118                gradient.color_space = if args.srgb {
119                    InterpolationColorSpace::Srgba
120                } else if args.hsl {
121                    InterpolationColorSpace::Hsla
122                } else {
123                    InterpolationColorSpace::Oklaba
124                };
125
126                parent.spawn((
127                    Node {
128                        width: percent(100),
129                        height: percent(100),
130                        ..default()
131                    },
132                    BackgroundGradient(vec![Gradient::Linear(gradient)]),
133                    GradientNode { index: i },
134                ));
135            }
136        });
137}
138
139#[derive(Component)]
140struct GradientNode {
141    index: usize,
142}
143
144fn animate_gradients(
145    mut gradients: Query<(&mut BackgroundGradient, &GradientNode)>,
146    args: Res<Args>,
147    time: Res<Time>,
148) {
149    if !args.animate {
150        return;
151    }
152
153    let t = time.elapsed_secs();
154
155    for (mut bg_gradient, node) in &mut gradients {
156        let offset = node.index as f32 * 0.01;
157        let hue_shift = sin(t + offset) * 0.5 + 0.5;
158
159        if let Some(Gradient::Linear(gradient)) = bg_gradient.0.get_mut(0) {
160            let color1 = Color::hsl(hue_shift * 360.0, 1.0, 0.5);
161            let color2 = Color::hsl((hue_shift + 0.3) * 360.0 % 360.0, 1.0, 0.5);
162
163            gradient.stops = vec![
164                ColorStop::new(color1, percent(0)),
165                ColorStop::new(color2, percent(100)),
166                ColorStop::new(
167                    Color::hsl((hue_shift + 0.1) * 360.0 % 360.0, 1.0, 0.5),
168                    percent(20),
169                ),
170                ColorStop::new(
171                    Color::hsl((hue_shift + 0.15) * 360.0 % 360.0, 1.0, 0.5),
172                    percent(40),
173                ),
174                ColorStop::new(
175                    Color::hsl((hue_shift + 0.2) * 360.0 % 360.0, 1.0, 0.5),
176                    percent(60),
177                ),
178                ColorStop::new(
179                    Color::hsl((hue_shift + 0.25) * 360.0 % 360.0, 1.0, 0.5),
180                    percent(80),
181                ),
182                ColorStop::new(
183                    Color::hsl((hue_shift + 0.28) * 360.0 % 360.0, 1.0, 0.5),
184                    percent(90),
185                ),
186            ];
187        }
188    }
189}