From 4969f7e18b660d66679ef5c8c45e2506f686ef8f Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 23 Mar 2022 13:45:50 +0100 Subject: [PATCH] Do all blending in gamma space This is an experiment to investigate solutions to * https://github.com/emilk/egui/issues/1410 --- egui_glow/src/painter.rs | 2 +- egui_glow/src/shader/fragment.glsl | 76 +++++++----------------------- egui_glow/src/shader/vertex.glsl | 19 ++------ epaint/src/color.rs | 8 ++-- epaint/src/image.rs | 15 ++---- 5 files changed, 30 insertions(+), 90 deletions(-) diff --git a/egui_glow/src/painter.rs b/egui_glow/src/painter.rs index 22d8559f..adef0e2d 100644 --- a/egui_glow/src/painter.rs +++ b/egui_glow/src/painter.rs @@ -273,7 +273,7 @@ impl Painter { ); if !cfg!(target_arch = "wasm32") { - self.gl.enable(glow::FRAMEBUFFER_SRGB); + self.gl.disable(glow::FRAMEBUFFER_SRGB); check_for_gl_error!(&self.gl, "FRAMEBUFFER_SRGB"); } diff --git a/egui_glow/src/shader/fragment.glsl b/egui_glow/src/shader/fragment.glsl index 9760c4ef..a6d54bc7 100644 --- a/egui_glow/src/shader/fragment.glsl +++ b/egui_glow/src/shader/fragment.glsl @@ -5,72 +5,32 @@ uniform sampler2D u_sampler; #ifdef NEW_SHADER_INTERFACE - in vec4 v_rgba; + in vec4 v_rgba_gamma; // 0-1 RGBA gamma in vec2 v_tc; out vec4 f_color; // a dirty hack applied to support webGL2 #define gl_FragColor f_color #define texture2D texture #else - varying vec4 v_rgba; + varying vec4 v_rgba_gamma; // 0-1 RGBA gamma varying vec2 v_tc; #endif -#ifdef SRGB_SUPPORTED - void main() { - // The texture sampler is sRGB aware, and OpenGL already expects linear rgba output - // so no need for any sRGB conversions here: - gl_FragColor = v_rgba * texture2D(u_sampler, v_tc); - } -#else - // 0-255 sRGB from 0-1 linear - vec3 srgb_from_linear(vec3 rgb) { - bvec3 cutoff = lessThan(rgb, vec3(0.0031308)); - vec3 lower = rgb * vec3(3294.6); - vec3 higher = vec3(269.025) * pow(rgb, vec3(1.0 / 2.4)) - vec3(14.025); - return mix(higher, lower, vec3(cutoff)); - } +// 0-255 sRGB from 0-1 linear +vec3 srgb_from_linear(vec3 rgb) { + bvec3 cutoff = lessThan(rgb, vec3(0.0031308)); + vec3 lower = rgb * vec3(3294.6); + vec3 higher = vec3(269.025) * pow(rgb, vec3(1.0 / 2.4)) - vec3(14.025); + return mix(higher, lower, vec3(cutoff)); +} - vec4 srgba_from_linear(vec4 rgba) { - return vec4(srgb_from_linear(rgba.rgb), 255.0 * rgba.a); - } +// 0-255 sRGBA from 0-1 linear +vec4 srgba_from_linear(vec4 rgba) { + return vec4(srgb_from_linear(rgba.rgb), 255.0 * rgba.a); +} - // 0-1 linear from 0-255 sRGB - vec3 linear_from_srgb(vec3 srgb) { - bvec3 cutoff = lessThan(srgb, vec3(10.31475)); - vec3 lower = srgb / vec3(3294.6); - vec3 higher = pow((srgb + vec3(14.025)) / vec3(269.025), vec3(2.4)); - return mix(higher, lower, vec3(cutoff)); - } - - vec4 linear_from_srgba(vec4 srgba) { - return vec4(linear_from_srgb(srgba.rgb), srgba.a / 255.0); - } - - void main() { - // We must decode the colors, since WebGL1 doesn't come with sRGBA textures: - vec4 texture_rgba = linear_from_srgba(texture2D(u_sampler, v_tc) * 255.0); - /// Multiply vertex color with texture color (in linear space). - gl_FragColor = v_rgba * texture_rgba; - - // WebGL1 doesn't support linear blending in the framebuffer, - // so we do a hack here where we change the premultiplied alpha - // to do the multiplication in gamma space instead: - - // Unmultiply alpha: - if (gl_FragColor.a > 0.0) { - gl_FragColor.rgb /= gl_FragColor.a; - } - - // Empiric tweak to make e.g. shadows look more like they should: - gl_FragColor.a *= sqrt(gl_FragColor.a); - - // To gamma: - gl_FragColor = srgba_from_linear(gl_FragColor) / 255.0; - - // Premultiply alpha, this time in gamma space: - if (gl_FragColor.a > 0.0) { - gl_FragColor.rgb *= gl_FragColor.a; - } - } -#endif +void main() { + vec4 tex_color_linear = texture2D(u_sampler, v_tc); + vec4 tex_color_gamma = srgba_from_linear(tex_color_linear) / 255.0; + gl_FragColor = v_rgba_gamma * tex_color_gamma; +} diff --git a/egui_glow/src/shader/vertex.glsl b/egui_glow/src/shader/vertex.glsl index 43a3b6b3..0fa47cdf 100644 --- a/egui_glow/src/shader/vertex.glsl +++ b/egui_glow/src/shader/vertex.glsl @@ -14,30 +14,17 @@ uniform vec2 u_screen_size; I vec2 a_pos; -I vec4 a_srgba; // 0-255 sRGB +I vec4 a_srgba; // 0-255 sRGBA gamma I vec2 a_tc; -O vec4 v_rgba; +O vec4 v_rgba_gamma; // 0-1 RGBA gamma O vec2 v_tc; -// 0-1 linear from 0-255 sRGB -vec3 linear_from_srgb(vec3 srgb) { - bvec3 cutoff = lessThan(srgb, vec3(10.31475)); - vec3 lower = srgb / vec3(3294.6); - vec3 higher = pow((srgb + vec3(14.025)) / vec3(269.025), vec3(2.4)); - return mix(higher, lower, V(cutoff)); -} - -vec4 linear_from_srgba(vec4 srgba) { - return vec4(linear_from_srgb(srgba.rgb), srgba.a / 255.0); -} - void main() { gl_Position = vec4( 2.0 * a_pos.x / u_screen_size.x - 1.0, 1.0 - 2.0 * a_pos.y / u_screen_size.y, 0.0, 1.0); - // egui encodes vertex colors in gamma space, so we must decode the colors here: - v_rgba = linear_from_srgba(a_srgba); + v_rgba_gamma = a_srgba / 255.0; v_tc = a_tc; } diff --git a/epaint/src/color.rs b/epaint/src/color.rs index de29b6a8..13a2e611 100644 --- a/epaint/src/color.rs +++ b/epaint/src/color.rs @@ -311,22 +311,22 @@ impl Rgba { } pub fn from_luminance_alpha(l: f32, a: f32) -> Self { - crate::epaint_assert!(0.0 <= l && l <= 1.0); - crate::epaint_assert!(0.0 <= a && a <= 1.0); + crate::epaint_assert!(0.0 <= l && l <= 1.0, "l: {}", l); + crate::epaint_assert!(0.0 <= a && a <= 1.0, "a: {}", a); Self([l * a, l * a, l * a, a]) } /// Transparent black #[inline(always)] pub fn from_black_alpha(a: f32) -> Self { - crate::epaint_assert!(0.0 <= a && a <= 1.0); + crate::epaint_assert!(0.0 <= a && a <= 1.0, "a: {}", a); Self([0.0, 0.0, 0.0, a]) } /// Transparent white #[inline(always)] pub fn from_white_alpha(a: f32) -> Self { - crate::epaint_assert!(0.0 <= a && a <= 1.0); + crate::epaint_assert!(0.0 <= a && a <= 1.0, "a: {}", a); Self([a, a, a, a]) } diff --git a/epaint/src/image.rs b/epaint/src/image.rs index 7fe1a1ac..0bd5875d 100644 --- a/epaint/src/image.rs +++ b/epaint/src/image.rs @@ -197,13 +197,10 @@ impl FontImage { /// If you are having problems with text looking skinny and pixelated, try /// setting a lower gamma, e.g. `0.5`. pub fn srgba_pixels(&'_ self, gamma: f32) -> impl ExactSizeIterator + '_ { - self.pixels.iter().map(move |coverage| { - // This is arbitrarily chosen to make text look as good as possible. - // In particular, it looks good with gamma=1 and the default eframe backend, - // which uses linear blending. - // See https://github.com/emilk/egui/issues/1410 - let a = fast_round(coverage.powf(gamma / 2.2) * 255.0); - Color32::from_rgba_premultiplied(a, a, a, a) // this makes no sense, but works + self.pixels.iter().map(move |&coverage| { + Color32::from(crate::Rgba::from_white_alpha( + coverage.clamp(0.0, 1.0).powf(gamma), + )) }) } @@ -252,10 +249,6 @@ impl From for ImageData { } } -fn fast_round(r: f32) -> u8 { - (r + 0.5).floor() as _ // rust does a saturating cast since 1.45 -} - // ---------------------------------------------------------------------------- /// A change to an image.