Preconvert colors before sending to shader (#20074)
# Objective - Fixes #20008 - Preconvert colors before sending them to the UI gradients shader for better performance ## Solution - Modified `prepare_gradient` in `gradient.rs` to convert colors from `LinearRgba` to `Srgba` on the CPU before sending to the GPU - Updated the gradient shader to remove per-pixel color space conversions since colors are now pre-converted - Added documentation to clarify that vertex colors are in sRGB space This optimization reduces the number of power operations per pixel from 3 to 1: - **Before**: Convert start color to sRGB, convert end color to sRGB, mix, convert back to linear (3 pow operations per pixel) - **After**: Colors pre-converted on CPU, mix in sRGB space, convert back to linear (1 pow operation per pixel) ## Testing - Verified that the UI gradient examples (`cargo run --example gradients`) compile and render correctly - The visual output should remain identical while performance improves, especially for large gradient areas - Changes maintain the same color interpolation behavior (mixing in sRGB space) To test: 1. Run `cargo run --example gradients` or `cargo run --example stacked_gradients` 2. Verify gradients render correctly --------- Co-authored-by: ickshonpe <david.curthoys@googlemail.com>
This commit is contained in:
parent
8e14d99fb9
commit
3fc49f00c1
11
Cargo.toml
11
Cargo.toml
@ -3068,6 +3068,17 @@ description = "Test rendering of many UI elements"
|
||||
category = "Stress Tests"
|
||||
wasm = true
|
||||
|
||||
[[example]]
|
||||
name = "many_gradients"
|
||||
path = "examples/stress_tests/many_gradients.rs"
|
||||
doc-scrape-examples = true
|
||||
|
||||
[package.metadata.example.many_gradients]
|
||||
name = "Many Gradients"
|
||||
description = "Stress test for gradient rendering performance"
|
||||
category = "Stress Tests"
|
||||
wasm = true
|
||||
|
||||
[[example]]
|
||||
name = "many_cameras_lights"
|
||||
path = "examples/stress_tests/many_cameras_lights.rs"
|
||||
|
@ -7,7 +7,7 @@ use core::{
|
||||
use super::shader_flags::BORDER_ALL;
|
||||
use crate::*;
|
||||
use bevy_asset::*;
|
||||
use bevy_color::{ColorToComponents, LinearRgba};
|
||||
use bevy_color::{ColorToComponents, Hsla, Hsva, LinearRgba, Oklaba, Oklcha, Srgba};
|
||||
use bevy_ecs::{
|
||||
prelude::Component,
|
||||
system::{
|
||||
@ -654,6 +654,44 @@ struct UiGradientVertex {
|
||||
hint: f32,
|
||||
}
|
||||
|
||||
fn convert_color_to_space(color: LinearRgba, space: InterpolationColorSpace) -> [f32; 4] {
|
||||
match space {
|
||||
InterpolationColorSpace::Oklaba => {
|
||||
let oklaba: Oklaba = color.into();
|
||||
[oklaba.lightness, oklaba.a, oklaba.b, oklaba.alpha]
|
||||
}
|
||||
InterpolationColorSpace::Oklcha | InterpolationColorSpace::OklchaLong => {
|
||||
let oklcha: Oklcha = color.into();
|
||||
[
|
||||
oklcha.lightness,
|
||||
oklcha.chroma,
|
||||
oklcha.hue.to_radians(),
|
||||
oklcha.alpha,
|
||||
]
|
||||
}
|
||||
InterpolationColorSpace::Srgba => {
|
||||
let srgba: Srgba = color.into();
|
||||
[srgba.red, srgba.green, srgba.blue, srgba.alpha]
|
||||
}
|
||||
InterpolationColorSpace::LinearRgba => color.to_f32_array(),
|
||||
InterpolationColorSpace::Hsla | InterpolationColorSpace::HslaLong => {
|
||||
let hsla: Hsla = color.into();
|
||||
// Normalize hue to 0..1 range for shader
|
||||
[
|
||||
hsla.hue / 360.0,
|
||||
hsla.saturation,
|
||||
hsla.lightness,
|
||||
hsla.alpha,
|
||||
]
|
||||
}
|
||||
InterpolationColorSpace::Hsva | InterpolationColorSpace::HsvaLong => {
|
||||
let hsva: Hsva = color.into();
|
||||
// Normalize hue to 0..1 range for shader
|
||||
[hsva.hue / 360.0, hsva.saturation, hsva.value, hsva.alpha]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn prepare_gradient(
|
||||
mut commands: Commands,
|
||||
render_device: Res<RenderDevice>,
|
||||
@ -804,8 +842,9 @@ pub fn prepare_gradient(
|
||||
continue;
|
||||
}
|
||||
}
|
||||
let start_color = start_stop.0.to_f32_array();
|
||||
let end_color = end_stop.0.to_f32_array();
|
||||
let start_color =
|
||||
convert_color_to_space(start_stop.0, gradient.color_space);
|
||||
let end_color = convert_color_to_space(end_stop.0, gradient.color_space);
|
||||
let mut stop_flags = flags;
|
||||
if 0. < start_stop.1
|
||||
&& (stop_index == gradient.stops_range.start || segment_count == 0)
|
||||
|
@ -114,26 +114,6 @@ fn fragment(in: GradientVertexOutput) -> @location(0) vec4<f32> {
|
||||
}
|
||||
}
|
||||
|
||||
// This function converts two linear rgba colors to srgba space, mixes them, and then converts the result back to linear rgb space.
|
||||
fn mix_linear_rgba_in_srgba_space(a: vec4<f32>, b: vec4<f32>, t: f32) -> vec4<f32> {
|
||||
let a_srgb = pow(a.rgb, vec3(1. / 2.2));
|
||||
let b_srgb = pow(b.rgb, vec3(1. / 2.2));
|
||||
let mixed_srgb = mix(a_srgb, b_srgb, t);
|
||||
return vec4(pow(mixed_srgb, vec3(2.2)), mix(a.a, b.a, t));
|
||||
}
|
||||
|
||||
fn linear_rgba_to_oklaba(c: vec4<f32>) -> vec4<f32> {
|
||||
let l = pow(0.41222146 * c.x + 0.53633255 * c.y + 0.051445995 * c.z, 1. / 3.);
|
||||
let m = pow(0.2119035 * c.x + 0.6806995 * c.y + 0.10739696 * c.z, 1. / 3.);
|
||||
let s = pow(0.08830246 * c.x + 0.28171885 * c.y + 0.6299787 * c.z, 1. / 3.);
|
||||
return vec4(
|
||||
0.21045426 * l + 0.7936178 * m - 0.004072047 * s,
|
||||
1.9779985 * l - 2.4285922 * m + 0.4505937 * s,
|
||||
0.025904037 * l + 0.78277177 * m - 0.80867577 * s,
|
||||
c.a
|
||||
);
|
||||
}
|
||||
|
||||
fn oklaba_to_linear_rgba(c: vec4<f32>) -> vec4<f32> {
|
||||
let l_ = c.x + 0.39633778 * c.y + 0.21580376 * c.z;
|
||||
let m_ = c.x - 0.105561346 * c.y - 0.06385417 * c.z;
|
||||
@ -149,33 +129,6 @@ fn oklaba_to_linear_rgba(c: vec4<f32>) -> vec4<f32> {
|
||||
);
|
||||
}
|
||||
|
||||
fn mix_linear_rgba_in_oklaba_space(a: vec4<f32>, b: vec4<f32>, t: f32) -> vec4<f32> {
|
||||
return oklaba_to_linear_rgba(mix(linear_rgba_to_oklaba(a), linear_rgba_to_oklaba(b), t));
|
||||
}
|
||||
|
||||
fn linear_rgba_to_hsla(c: vec4<f32>) -> vec4<f32> {
|
||||
let max = max(max(c.r, c.g), c.b);
|
||||
let min = min(min(c.r, c.g), c.b);
|
||||
let l = (max + min) * 0.5;
|
||||
if max == min {
|
||||
return vec4(0., 0., l, c.a);
|
||||
} else {
|
||||
let delta = max - min;
|
||||
let s = delta / (1. - abs(2. * l - 1.));
|
||||
var h = 0.;
|
||||
if max == c.r {
|
||||
h = ((c.g - c.b) / delta) % 6.;
|
||||
} else if max == c.g {
|
||||
h = ((c.b - c.r) / delta) + 2.;
|
||||
} else {
|
||||
h = ((c.r - c.g) / delta) + 4.;
|
||||
}
|
||||
h = h / 6.;
|
||||
return vec4<f32>(h, s, l, c.a);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn hsla_to_linear_rgba(hsl: vec4<f32>) -> vec4<f32> {
|
||||
let h = hsl.x;
|
||||
let s = hsl.y;
|
||||
@ -203,30 +156,6 @@ fn hsla_to_linear_rgba(hsl: vec4<f32>) -> vec4<f32> {
|
||||
return vec4<f32>(r + m, g + m, b + m, hsl.a);
|
||||
}
|
||||
|
||||
fn linear_rgba_to_hsva(c: vec4<f32>) -> vec4<f32> {
|
||||
let maxc = max(max(c.r, c.g), c.b);
|
||||
let minc = min(min(c.r, c.g), c.b);
|
||||
let delta = maxc - minc;
|
||||
var h: f32 = 0.0;
|
||||
var s: f32 = 0.0;
|
||||
let v: f32 = maxc;
|
||||
if delta != 0.0 {
|
||||
s = delta / maxc;
|
||||
if maxc == c.r {
|
||||
h = ((c.g - c.b) / delta) % 6.0;
|
||||
} else if maxc == c.g {
|
||||
h = ((c.b - c.r) / delta) + 2.0;
|
||||
} else {
|
||||
h = ((c.r - c.g) / delta) + 4.0;
|
||||
}
|
||||
h = h / 6.0;
|
||||
if h < 0.0 {
|
||||
h = h + 1.0;
|
||||
}
|
||||
}
|
||||
return vec4<f32>(h, s, v, c.a);
|
||||
}
|
||||
|
||||
fn hsva_to_linear_rgba(hsva: vec4<f32>) -> vec4<f32> {
|
||||
let h = hsva.x * 6.0;
|
||||
let s = hsva.y;
|
||||
@ -253,14 +182,6 @@ fn hsva_to_linear_rgba(hsva: vec4<f32>) -> vec4<f32> {
|
||||
return vec4<f32>(r + m, g + m, b + m, hsva.a);
|
||||
}
|
||||
|
||||
/// hue is left in radians and not converted to degrees
|
||||
fn linear_rgba_to_oklcha(c: vec4<f32>) -> vec4<f32> {
|
||||
let o = linear_rgba_to_oklaba(c);
|
||||
let chroma = sqrt(o.y * o.y + o.z * o.z);
|
||||
let hue = atan2(o.z, o.y);
|
||||
return vec4(o.x, chroma, rem_euclid(hue, TAU), o.a);
|
||||
}
|
||||
|
||||
fn oklcha_to_linear_rgba(c: vec4<f32>) -> vec4<f32> {
|
||||
let a = c.y * cos(c.z);
|
||||
let b = c.y * sin(c.z);
|
||||
@ -271,90 +192,6 @@ fn rem_euclid(a: f32, b: f32) -> f32 {
|
||||
return ((a % b) + b) % b;
|
||||
}
|
||||
|
||||
fn lerp_hue(a: f32, b: f32, t: f32) -> f32 {
|
||||
let diff = rem_euclid(b - a + PI, TAU) - PI;
|
||||
return rem_euclid(a + diff * t, TAU);
|
||||
}
|
||||
|
||||
fn lerp_hue_long(a: f32, b: f32, t: f32) -> f32 {
|
||||
let diff = rem_euclid(b - a + PI, TAU) - PI;
|
||||
return rem_euclid(a + (diff + select(TAU, -TAU, 0. < diff)) * t, TAU);
|
||||
}
|
||||
|
||||
fn mix_oklcha(a: vec4<f32>, b: vec4<f32>, t: f32) -> vec4<f32> {
|
||||
let ah = select(a.z, b.z, a.y == 0.);
|
||||
let bh = select(b.z, a.z, b.y == 0.);
|
||||
return vec4(
|
||||
mix(a.xy, b.xy, t),
|
||||
lerp_hue(ah, bh, t),
|
||||
mix(a.a, b.a, t)
|
||||
);
|
||||
}
|
||||
|
||||
fn mix_oklcha_long(a: vec4<f32>, b: vec4<f32>, t: f32) -> vec4<f32> {
|
||||
let ah = select(a.z, b.z, a.y == 0.);
|
||||
let bh = select(b.z, a.z, b.y == 0.);
|
||||
return vec4(
|
||||
mix(a.xy, b.xy, t),
|
||||
lerp_hue_long(ah, bh, t),
|
||||
mix(a.w, b.w, t)
|
||||
);
|
||||
}
|
||||
|
||||
fn mix_linear_rgba_in_oklcha_space(a: vec4<f32>, b: vec4<f32>, t: f32) -> vec4<f32> {
|
||||
return oklcha_to_linear_rgba(mix_oklcha(linear_rgba_to_oklcha(a), linear_rgba_to_oklcha(b), t));
|
||||
}
|
||||
|
||||
fn mix_linear_rgba_in_oklcha_space_long(a: vec4<f32>, b: vec4<f32>, t: f32) -> vec4<f32> {
|
||||
return oklcha_to_linear_rgba(mix_oklcha_long(linear_rgba_to_oklcha(a), linear_rgba_to_oklcha(b), t));
|
||||
}
|
||||
|
||||
fn mix_linear_rgba_in_hsva_space(a: vec4<f32>, b: vec4<f32>, t: f32) -> vec4<f32> {
|
||||
let ha = linear_rgba_to_hsva(a);
|
||||
let hb = linear_rgba_to_hsva(b);
|
||||
var h: f32;
|
||||
if ha.y == 0. {
|
||||
h = hb.x;
|
||||
} else if hb.y == 0. {
|
||||
h = ha.x;
|
||||
} else {
|
||||
h = lerp_hue(ha.x * TAU, hb.x * TAU, t) / TAU;
|
||||
}
|
||||
let s = mix(ha.y, hb.y, t);
|
||||
let v = mix(ha.z, hb.z, t);
|
||||
let a_alpha = mix(ha.a, hb.a, t);
|
||||
return hsva_to_linear_rgba(vec4<f32>(h, s, v, a_alpha));
|
||||
}
|
||||
|
||||
fn mix_linear_rgba_in_hsva_space_long(a: vec4<f32>, b: vec4<f32>, t: f32) -> vec4<f32> {
|
||||
let ha = linear_rgba_to_hsva(a);
|
||||
let hb = linear_rgba_to_hsva(b);
|
||||
let h = lerp_hue_long(ha.x * TAU, hb.x * TAU, t) / TAU;
|
||||
let s = mix(ha.y, hb.y, t);
|
||||
let v = mix(ha.z, hb.z, t);
|
||||
let a_alpha = mix(ha.a, hb.a, t);
|
||||
return hsva_to_linear_rgba(vec4<f32>(h, s, v, a_alpha));
|
||||
}
|
||||
|
||||
fn mix_linear_rgba_in_hsla_space(a: vec4<f32>, b: vec4<f32>, t: f32) -> vec4<f32> {
|
||||
let ha = linear_rgba_to_hsla(a);
|
||||
let hb = linear_rgba_to_hsla(b);
|
||||
let h = lerp_hue(ha.x * TAU, hb.x * TAU, t) / TAU;
|
||||
let s = mix(ha.y, hb.y, t);
|
||||
let l = mix(ha.z, hb.z, t);
|
||||
let a_alpha = mix(ha.a, hb.a, t);
|
||||
return hsla_to_linear_rgba(vec4<f32>(h, s, l, a_alpha));
|
||||
}
|
||||
|
||||
fn mix_linear_rgba_in_hsla_space_long(a: vec4<f32>, b: vec4<f32>, t: f32) -> vec4<f32> {
|
||||
let ha = linear_rgba_to_hsla(a);
|
||||
let hb = linear_rgba_to_hsla(b);
|
||||
let h = lerp_hue_long(ha.x * TAU, hb.x * TAU, t) / TAU;
|
||||
let s = mix(ha.y, hb.y, t);
|
||||
let l = mix(ha.z, hb.z, t);
|
||||
let a_alpha = mix(ha.a, hb.a, t);
|
||||
return hsla_to_linear_rgba(vec4<f32>(h, s, l, a_alpha));
|
||||
}
|
||||
|
||||
// These functions are used to calculate the distance in gradient space from the start of the gradient to the point.
|
||||
// The distance in gradient space is then used to interpolate between the start and end colors.
|
||||
@ -386,6 +223,105 @@ fn conic_distance(
|
||||
return (((angle - start) % TAU) + TAU) % TAU;
|
||||
}
|
||||
|
||||
fn mix_oklcha(a: vec4<f32>, b: vec4<f32>, t: f32) -> vec4<f32> {
|
||||
let hue_diff = b.z - a.z;
|
||||
var adjusted_hue = a.z;
|
||||
if abs(hue_diff) > PI {
|
||||
if hue_diff > 0.0 {
|
||||
adjusted_hue = a.z + (hue_diff - TAU) * t;
|
||||
} else {
|
||||
adjusted_hue = a.z + (hue_diff + TAU) * t;
|
||||
}
|
||||
} else {
|
||||
adjusted_hue = a.z + hue_diff * t;
|
||||
}
|
||||
return vec4(
|
||||
mix(a.x, b.x, t),
|
||||
mix(a.y, b.y, t),
|
||||
rem_euclid(adjusted_hue, TAU),
|
||||
mix(a.w, b.w, t)
|
||||
);
|
||||
}
|
||||
|
||||
fn mix_oklcha_long(a: vec4<f32>, b: vec4<f32>, t: f32) -> vec4<f32> {
|
||||
let hue_diff = b.z - a.z;
|
||||
var adjusted_hue = a.z;
|
||||
if abs(hue_diff) < PI {
|
||||
if hue_diff >= 0.0 {
|
||||
adjusted_hue = a.z + (hue_diff - TAU) * t;
|
||||
} else {
|
||||
adjusted_hue = a.z + (hue_diff + TAU) * t;
|
||||
}
|
||||
} else {
|
||||
adjusted_hue = a.z + hue_diff * t;
|
||||
}
|
||||
return vec4(
|
||||
mix(a.x, b.x, t),
|
||||
mix(a.y, b.y, t),
|
||||
rem_euclid(adjusted_hue, TAU),
|
||||
mix(a.w, b.w, t)
|
||||
);
|
||||
}
|
||||
|
||||
fn mix_hsla(a: vec4<f32>, b: vec4<f32>, t: f32) -> vec4<f32> {
|
||||
return vec4(
|
||||
fract(a.x + (fract(b.x - a.x + 0.5) - 0.5) * t),
|
||||
mix(a.y, b.y, t),
|
||||
mix(a.z, b.z, t),
|
||||
mix(a.w, b.w, t)
|
||||
);
|
||||
}
|
||||
|
||||
fn mix_hsla_long(a: vec4<f32>, b: vec4<f32>, t: f32) -> vec4<f32> {
|
||||
let d = fract(b.x - a.x + 0.5) - 0.5;
|
||||
return vec4(
|
||||
fract(a.x + (d + select(1., -1., 0. < d)) * t),
|
||||
mix(a.y, b.y, t),
|
||||
mix(a.z, b.z, t),
|
||||
mix(a.w, b.w, t)
|
||||
);
|
||||
}
|
||||
|
||||
fn mix_hsva(a: vec4<f32>, b: vec4<f32>, t: f32) -> vec4<f32> {
|
||||
let hue_diff = b.x - a.x;
|
||||
var adjusted_hue = a.x;
|
||||
if abs(hue_diff) > 0.5 {
|
||||
if hue_diff > 0.0 {
|
||||
adjusted_hue = a.x + (hue_diff - 1.0) * t;
|
||||
} else {
|
||||
adjusted_hue = a.x + (hue_diff + 1.0) * t;
|
||||
}
|
||||
} else {
|
||||
adjusted_hue = a.x + hue_diff * t;
|
||||
}
|
||||
return vec4(
|
||||
fract(adjusted_hue),
|
||||
mix(a.y, b.y, t),
|
||||
mix(a.z, b.z, t),
|
||||
mix(a.w, b.w, t)
|
||||
);
|
||||
}
|
||||
|
||||
fn mix_hsva_long(a: vec4<f32>, b: vec4<f32>, t: f32) -> vec4<f32> {
|
||||
let hue_diff = b.x - a.x;
|
||||
var adjusted_hue = a.x;
|
||||
if abs(hue_diff) < 0.5 {
|
||||
if hue_diff >= 0.0 {
|
||||
adjusted_hue = a.x + (hue_diff - 1.0) * t;
|
||||
} else {
|
||||
adjusted_hue = a.x + (hue_diff + 1.0) * t;
|
||||
}
|
||||
} else {
|
||||
adjusted_hue = a.x + hue_diff * t;
|
||||
}
|
||||
return vec4(
|
||||
fract(adjusted_hue),
|
||||
mix(a.y, b.y, t),
|
||||
mix(a.z, b.z, t),
|
||||
mix(a.w, b.w, t)
|
||||
);
|
||||
}
|
||||
|
||||
fn interpolate_gradient(
|
||||
distance: f32,
|
||||
start_color: vec4<f32>,
|
||||
@ -397,10 +333,10 @@ fn interpolate_gradient(
|
||||
) -> vec4<f32> {
|
||||
if start_distance == end_distance {
|
||||
if distance <= start_distance && enabled(flags, FILL_START) {
|
||||
return start_color;
|
||||
return convert_to_linear_rgba(start_color);
|
||||
}
|
||||
if start_distance <= distance && enabled(flags, FILL_END) {
|
||||
return end_color;
|
||||
return convert_to_linear_rgba(end_color);
|
||||
}
|
||||
return vec4(0.);
|
||||
}
|
||||
@ -409,14 +345,14 @@ fn interpolate_gradient(
|
||||
|
||||
if t < 0.0 {
|
||||
if enabled(flags, FILL_START) {
|
||||
return start_color;
|
||||
return convert_to_linear_rgba(start_color);
|
||||
}
|
||||
return vec4(0.0);
|
||||
}
|
||||
|
||||
if 1. < t {
|
||||
if enabled(flags, FILL_END) {
|
||||
return end_color;
|
||||
return convert_to_linear_rgba(end_color);
|
||||
}
|
||||
return vec4(0.0);
|
||||
}
|
||||
@ -426,24 +362,56 @@ fn interpolate_gradient(
|
||||
} else {
|
||||
t = 0.5 * (1 + (t - hint) / (1.0 - hint));
|
||||
}
|
||||
|
||||
#ifdef IN_SRGB
|
||||
return mix_linear_rgba_in_srgba_space(start_color, end_color, t);
|
||||
#else ifdef IN_OKLAB
|
||||
return mix_linear_rgba_in_oklaba_space(start_color, end_color, t);
|
||||
#else ifdef IN_OKLCH
|
||||
return mix_linear_rgba_in_oklcha_space(start_color, end_color, t);
|
||||
|
||||
return convert_to_linear_rgba(mix_colors(start_color, end_color, t));
|
||||
}
|
||||
|
||||
// Mix the colors, choosing the appropriate interpolation method for the given color space
|
||||
fn mix_colors(
|
||||
start_color: vec4<f32>,
|
||||
end_color: vec4<f32>,
|
||||
t: f32,
|
||||
) -> vec4<f32> {
|
||||
#ifdef IN_OKLCH
|
||||
return mix_oklcha(start_color, end_color, t);
|
||||
#else ifdef IN_OKLCH_LONG
|
||||
return mix_linear_rgba_in_oklcha_space_long(start_color, end_color, t);
|
||||
return mix_oklcha_long(start_color, end_color, t);
|
||||
#else ifdef IN_HSV
|
||||
return mix_linear_rgba_in_hsva_space(start_color, end_color, t);
|
||||
return mix_hsva(start_color, end_color, t);
|
||||
#else ifdef IN_HSV_LONG
|
||||
return mix_linear_rgba_in_hsva_space_long(start_color, end_color, t);
|
||||
return mix_hsva_long(start_color, end_color, t);
|
||||
#else ifdef IN_HSL
|
||||
return mix_linear_rgba_in_hsla_space(start_color, end_color, t);
|
||||
return mix_hsla(start_color, end_color, t);
|
||||
#else ifdef IN_HSL_LONG
|
||||
return mix_linear_rgba_in_hsla_space_long(start_color, end_color, t);
|
||||
return mix_hsla_long(start_color, end_color, t);
|
||||
#else
|
||||
// Just lerp in linear RGBA, OkLab and SRGBA spaces
|
||||
return mix(start_color, end_color, t);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Convert a color from the interpolation color space to linear rgba
|
||||
fn convert_to_linear_rgba(
|
||||
color: vec4<f32>,
|
||||
) -> vec4<f32> {
|
||||
#ifdef IN_OKLCH
|
||||
return oklcha_to_linear_rgba(color);
|
||||
#else ifdef IN_OKLCH_LONG
|
||||
return oklcha_to_linear_rgba(color);
|
||||
#else ifdef IN_HSV
|
||||
return hsva_to_linear_rgba(color);
|
||||
#else ifdef IN_HSV_LONG
|
||||
return hsva_to_linear_rgba(color);
|
||||
#else ifdef IN_HSL
|
||||
return hsla_to_linear_rgba(color);
|
||||
#else ifdef IN_HSL_LONG
|
||||
return hsla_to_linear_rgba(color);
|
||||
#else ifdef IN_OKLAB
|
||||
return oklaba_to_linear_rgba(color);
|
||||
#else ifdef IN_SRGB
|
||||
return vec4(pow(color.rgb, vec3(2.2)), color.a);
|
||||
#else
|
||||
// Color is already in linear rgba space
|
||||
return color;
|
||||
#endif
|
||||
}
|
||||
|
@ -510,6 +510,7 @@ Example | Description
|
||||
[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
|
||||
[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 Gradients](../examples/stress_tests/many_gradients.rs) | Stress test for gradient rendering performance
|
||||
[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 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.
|
||||
[Many Text2d](../examples/stress_tests/many_text2d.rs) | Displays many Text2d! Used for performance testing.
|
||||
|
180
examples/stress_tests/many_gradients.rs
Normal file
180
examples/stress_tests/many_gradients.rs
Normal file
@ -0,0 +1,180 @@
|
||||
//! Stress test demonstrating gradient performance improvements.
|
||||
//!
|
||||
//! This example creates many UI nodes with gradients to measure the performance
|
||||
//! impact of pre-converting colors to the target color space on the CPU.
|
||||
|
||||
use argh::FromArgs;
|
||||
use bevy::{
|
||||
color::palettes::css::*,
|
||||
diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
|
||||
math::ops::sin,
|
||||
prelude::*,
|
||||
ui::{
|
||||
BackgroundGradient, ColorStop, Display, Gradient, InterpolationColorSpace, LinearGradient,
|
||||
RepeatedGridTrack,
|
||||
},
|
||||
window::{PresentMode, WindowResolution},
|
||||
};
|
||||
|
||||
const COLS: usize = 30;
|
||||
|
||||
#[derive(FromArgs, Resource, Debug)]
|
||||
/// Gradient stress test
|
||||
struct Args {
|
||||
/// how many gradients per group (default: 900)
|
||||
#[argh(option, default = "900")]
|
||||
gradient_count: usize,
|
||||
|
||||
/// whether to animate gradients by changing colors
|
||||
#[argh(switch)]
|
||||
animate: bool,
|
||||
|
||||
/// use sRGB interpolation
|
||||
#[argh(switch)]
|
||||
srgb: bool,
|
||||
|
||||
/// use HSL interpolation
|
||||
#[argh(switch)]
|
||||
hsl: bool,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let args: Args = argh::from_env();
|
||||
let total_gradients = args.gradient_count;
|
||||
|
||||
println!("Gradient stress test with {total_gradients} gradients");
|
||||
println!(
|
||||
"Color space: {}",
|
||||
if args.srgb {
|
||||
"sRGB"
|
||||
} else if args.hsl {
|
||||
"HSL"
|
||||
} else {
|
||||
"OkLab (default)"
|
||||
}
|
||||
);
|
||||
|
||||
App::new()
|
||||
.add_plugins((
|
||||
LogDiagnosticsPlugin::default(),
|
||||
FrameTimeDiagnosticsPlugin::default(),
|
||||
DefaultPlugins.set(WindowPlugin {
|
||||
primary_window: Some(Window {
|
||||
title: "Gradient Stress Test".to_string(),
|
||||
resolution: WindowResolution::new(1920.0, 1080.0),
|
||||
present_mode: PresentMode::AutoNoVsync,
|
||||
..default()
|
||||
}),
|
||||
..default()
|
||||
}),
|
||||
))
|
||||
.insert_resource(args)
|
||||
.add_systems(Startup, setup)
|
||||
.add_systems(Update, animate_gradients)
|
||||
.run();
|
||||
}
|
||||
|
||||
fn setup(mut commands: Commands, args: Res<Args>) {
|
||||
commands.spawn(Camera2d);
|
||||
|
||||
let rows_to_spawn = args.gradient_count.div_ceil(COLS);
|
||||
|
||||
// Create a grid of gradients
|
||||
commands
|
||||
.spawn(Node {
|
||||
width: Val::Percent(100.0),
|
||||
height: Val::Percent(100.0),
|
||||
display: Display::Grid,
|
||||
grid_template_columns: RepeatedGridTrack::flex(COLS as u16, 1.0),
|
||||
grid_template_rows: RepeatedGridTrack::flex(rows_to_spawn as u16, 1.0),
|
||||
..default()
|
||||
})
|
||||
.with_children(|parent| {
|
||||
for i in 0..args.gradient_count {
|
||||
let angle = (i as f32 * 10.0) % 360.0;
|
||||
|
||||
let mut gradient = LinearGradient::new(
|
||||
angle,
|
||||
vec![
|
||||
ColorStop::new(RED, Val::Percent(0.0)),
|
||||
ColorStop::new(BLUE, Val::Percent(100.0)),
|
||||
ColorStop::new(GREEN, Val::Percent(20.0)),
|
||||
ColorStop::new(YELLOW, Val::Percent(40.0)),
|
||||
ColorStop::new(ORANGE, Val::Percent(60.0)),
|
||||
ColorStop::new(LIME, Val::Percent(80.0)),
|
||||
ColorStop::new(DARK_CYAN, Val::Percent(90.0)),
|
||||
],
|
||||
);
|
||||
|
||||
gradient.color_space = if args.srgb {
|
||||
InterpolationColorSpace::Srgba
|
||||
} else if args.hsl {
|
||||
InterpolationColorSpace::Hsla
|
||||
} else {
|
||||
InterpolationColorSpace::Oklaba
|
||||
};
|
||||
|
||||
parent.spawn((
|
||||
Node {
|
||||
width: Val::Percent(100.0),
|
||||
height: Val::Percent(100.0),
|
||||
..default()
|
||||
},
|
||||
BackgroundGradient(vec![Gradient::Linear(gradient)]),
|
||||
GradientNode { index: i },
|
||||
));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
struct GradientNode {
|
||||
index: usize,
|
||||
}
|
||||
|
||||
fn animate_gradients(
|
||||
mut gradients: Query<(&mut BackgroundGradient, &GradientNode)>,
|
||||
args: Res<Args>,
|
||||
time: Res<Time>,
|
||||
) {
|
||||
if !args.animate {
|
||||
return;
|
||||
}
|
||||
|
||||
let t = time.elapsed_secs();
|
||||
|
||||
for (mut bg_gradient, node) in &mut gradients {
|
||||
let offset = node.index as f32 * 0.01;
|
||||
let hue_shift = sin(t + offset) * 0.5 + 0.5;
|
||||
|
||||
if let Some(Gradient::Linear(gradient)) = bg_gradient.0.get_mut(0) {
|
||||
let color1 = Color::hsl(hue_shift * 360.0, 1.0, 0.5);
|
||||
let color2 = Color::hsl((hue_shift + 0.3) * 360.0 % 360.0, 1.0, 0.5);
|
||||
|
||||
gradient.stops = vec![
|
||||
ColorStop::new(color1, Val::Percent(0.0)),
|
||||
ColorStop::new(color2, Val::Percent(100.0)),
|
||||
ColorStop::new(
|
||||
Color::hsl((hue_shift + 0.1) * 360.0 % 360.0, 1.0, 0.5),
|
||||
Val::Percent(20.0),
|
||||
),
|
||||
ColorStop::new(
|
||||
Color::hsl((hue_shift + 0.15) * 360.0 % 360.0, 1.0, 0.5),
|
||||
Val::Percent(40.0),
|
||||
),
|
||||
ColorStop::new(
|
||||
Color::hsl((hue_shift + 0.2) * 360.0 % 360.0, 1.0, 0.5),
|
||||
Val::Percent(60.0),
|
||||
),
|
||||
ColorStop::new(
|
||||
Color::hsl((hue_shift + 0.25) * 360.0 % 360.0, 1.0, 0.5),
|
||||
Val::Percent(80.0),
|
||||
),
|
||||
ColorStop::new(
|
||||
Color::hsl((hue_shift + 0.28) * 360.0 % 360.0, 1.0, 0.5),
|
||||
Val::Percent(90.0),
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user