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::{UpdateMode, 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    let args: Args = argh::from_env();
44    let total_gradients = args.gradient_count;
45
46    println!("Gradient stress test with {total_gradients} gradients");
47    println!(
48        "Color space: {}",
49        if args.srgb {
50            "sRGB"
51        } else if args.hsl {
52            "HSL"
53        } else {
54            "OkLab (default)"
55        }
56    );
57
58    App::new()
59        .add_plugins((
60            LogDiagnosticsPlugin::default(),
61            FrameTimeDiagnosticsPlugin::default(),
62            DefaultPlugins.set(WindowPlugin {
63                primary_window: Some(Window {
64                    title: "Gradient Stress Test".to_string(),
65                    resolution: WindowResolution::new(1920, 1080).with_scale_factor_override(1.0),
66                    present_mode: PresentMode::AutoNoVsync,
67                    ..default()
68                }),
69                ..default()
70            }),
71        ))
72        .insert_resource(WinitSettings {
73            focused_mode: UpdateMode::Continuous,
74            unfocused_mode: UpdateMode::Continuous,
75        })
76        .insert_resource(args)
77        .insert_resource(WinitSettings::continuous())
78        .add_systems(Startup, setup)
79        .add_systems(Update, animate_gradients)
80        .run();
81}
82
83fn setup(mut commands: Commands, args: Res<Args>) {
84    commands.spawn(Camera2d);
85
86    let rows_to_spawn = args.gradient_count.div_ceil(COLS);
87
88    // Create a grid of gradients
89    commands
90        .spawn(Node {
91            width: percent(100),
92            height: percent(100),
93            display: Display::Grid,
94            grid_template_columns: RepeatedGridTrack::flex(COLS as u16, 1.0),
95            grid_template_rows: RepeatedGridTrack::flex(rows_to_spawn as u16, 1.0),
96            ..default()
97        })
98        .with_children(|parent| {
99            for i in 0..args.gradient_count {
100                let angle = (i as f32 * 10.0) % 360.0;
101
102                let mut gradient = LinearGradient::new(
103                    angle,
104                    vec![
105                        ColorStop::new(RED, percent(0)),
106                        ColorStop::new(BLUE, percent(100)),
107                        ColorStop::new(GREEN, percent(20)),
108                        ColorStop::new(YELLOW, percent(40)),
109                        ColorStop::new(ORANGE, percent(60)),
110                        ColorStop::new(LIME, percent(80)),
111                        ColorStop::new(DARK_CYAN, percent(90)),
112                    ],
113                );
114
115                gradient.color_space = if args.srgb {
116                    InterpolationColorSpace::Srgba
117                } else if args.hsl {
118                    InterpolationColorSpace::Hsla
119                } else {
120                    InterpolationColorSpace::Oklaba
121                };
122
123                parent.spawn((
124                    Node {
125                        width: percent(100),
126                        height: percent(100),
127                        ..default()
128                    },
129                    BackgroundGradient(vec![Gradient::Linear(gradient)]),
130                    GradientNode { index: i },
131                ));
132            }
133        });
134}
135
136#[derive(Component)]
137struct GradientNode {
138    index: usize,
139}
140
141fn animate_gradients(
142    mut gradients: Query<(&mut BackgroundGradient, &GradientNode)>,
143    args: Res<Args>,
144    time: Res<Time>,
145) {
146    if !args.animate {
147        return;
148    }
149
150    let t = time.elapsed_secs();
151
152    for (mut bg_gradient, node) in &mut gradients {
153        let offset = node.index as f32 * 0.01;
154        let hue_shift = sin(t + offset) * 0.5 + 0.5;
155
156        if let Some(Gradient::Linear(gradient)) = bg_gradient.0.get_mut(0) {
157            let color1 = Color::hsl(hue_shift * 360.0, 1.0, 0.5);
158            let color2 = Color::hsl((hue_shift + 0.3) * 360.0 % 360.0, 1.0, 0.5);
159
160            gradient.stops = vec![
161                ColorStop::new(color1, percent(0)),
162                ColorStop::new(color2, percent(100)),
163                ColorStop::new(
164                    Color::hsl((hue_shift + 0.1) * 360.0 % 360.0, 1.0, 0.5),
165                    percent(20),
166                ),
167                ColorStop::new(
168                    Color::hsl((hue_shift + 0.15) * 360.0 % 360.0, 1.0, 0.5),
169                    percent(40),
170                ),
171                ColorStop::new(
172                    Color::hsl((hue_shift + 0.2) * 360.0 % 360.0, 1.0, 0.5),
173                    percent(60),
174                ),
175                ColorStop::new(
176                    Color::hsl((hue_shift + 0.25) * 360.0 % 360.0, 1.0, 0.5),
177                    percent(80),
178                ),
179                ColorStop::new(
180                    Color::hsl((hue_shift + 0.28) * 360.0 % 360.0, 1.0, 0.5),
181                    percent(90),
182                ),
183            ];
184        }
185    }
186}