diff --git a/crates/egui-wgpu/src/egui.wgsl b/crates/egui-wgpu/src/egui.wgsl index b60d9de9e83..0ad746f8f21 100644 --- a/crates/egui-wgpu/src/egui.wgsl +++ b/crates/egui-wgpu/src/egui.wgsl @@ -97,9 +97,11 @@ fn vs_main( @fragment fn fs_main_linear_framebuffer(in: VertexOutput) -> @location(0) vec4 { - // We always have an sRGB aware texture at the moment. - let tex_linear = textureSample(r_tex_color, r_tex_sampler, in.tex_coord); - let tex_gamma = gamma_from_linear_rgba(tex_linear); + // We use sRGB unware textures, and store (premultiplied) sRGB values. + // Hence any interpolation done by the GPU will be in gamma space. + // Note: Because we store premultiplied alpha, we can't rely on sRGB aware + // textures that convert "sRGB -> linear RGB" before interpolating. + let tex_gamma = textureSample(r_tex_color, r_tex_sampler, in.tex_coord); var out_color_gamma = in.color * tex_gamma; // Dither the float color down to eight bits to reduce banding. // This step is optional for egui backends. @@ -115,9 +117,11 @@ fn fs_main_linear_framebuffer(in: VertexOutput) -> @location(0) vec4 { @fragment fn fs_main_gamma_framebuffer(in: VertexOutput) -> @location(0) vec4 { - // We always have an sRGB aware texture at the moment. - let tex_linear = textureSample(r_tex_color, r_tex_sampler, in.tex_coord); - let tex_gamma = gamma_from_linear_rgba(tex_linear); + // We use sRGB unware textures, and store (premultiplied) sRGB values. + // Hence any interpolation done by the GPU will be in gamma space. + // Note: Because we store premultiplied alpha, we can't rely on sRGB aware + // textures that convert "sRGB -> linear RGB" before interpolating. + let tex_gamma = textureSample(r_tex_color, r_tex_sampler, in.tex_coord); var out_color_gamma = in.color * tex_gamma; // Dither the float color down to eight bits to reduce banding. // This step is optional for egui backends. diff --git a/crates/egui-wgpu/src/renderer.rs b/crates/egui-wgpu/src/renderer.rs index 41a9b3b7885..b75011e12d7 100644 --- a/crates/egui-wgpu/src/renderer.rs +++ b/crates/egui-wgpu/src/renderer.rs @@ -629,9 +629,9 @@ impl Renderer { mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, - format: wgpu::TextureFormat::Rgba8UnormSrgb, // Minspec for wgpu WebGL emulation is WebGL2, so this should always be supported. + format: wgpu::TextureFormat::Rgba8Unorm, usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, - view_formats: &[wgpu::TextureFormat::Rgba8UnormSrgb], + view_formats: &[wgpu::TextureFormat::Rgba8Unorm], }) }; let origin = wgpu::Origin3d::ZERO; diff --git a/crates/egui/src/lib.rs b/crates/egui/src/lib.rs index 7abc5ba41f9..ca8b5058f48 100644 --- a/crates/egui/src/lib.rs +++ b/crates/egui/src/lib.rs @@ -161,11 +161,10 @@ //! //! * egui uses premultiplied alpha, so make sure your blending function is `(ONE, ONE_MINUS_SRC_ALPHA)`. //! * Make sure your texture sampler is clamped (`GL_CLAMP_TO_EDGE`). -//! * egui prefers linear color spaces for all blending so: -//! * Use an sRGBA-aware texture if available (e.g. `GL_SRGB8_ALPHA8`). -//! * Otherwise: remember to decode gamma in the fragment shader. -//! * Decode the gamma of the incoming vertex colors in your vertex shader. -//! * Turn on sRGBA/linear framebuffer if available (`GL_FRAMEBUFFER_SRGB`). +//! * egui prefers gamma color spaces for all blending so: +//! * Do NOT use an sRGBA-aware texture (NOT `GL_SRGB8_ALPHA8`). +//! * Multiply texture and vertex colors in gamma space +//! * HOWEVER: Turn on sRGBA/gamma framebuffer if available (`GL_FRAMEBUFFER_SRGB`). //! * Otherwise: gamma-encode the colors before you write them again. //! //! diff --git a/crates/egui_demo_lib/src/rendering_test.rs b/crates/egui_demo_lib/src/rendering_test.rs index 427d0f4eef6..ba70ca8de5a 100644 --- a/crates/egui_demo_lib/src/rendering_test.rs +++ b/crates/egui_demo_lib/src/rendering_test.rs @@ -159,7 +159,7 @@ impl ColorTest { ui.separator(); // TODO(emilk): test color multiplication (image tint), - // to make sure vertex and texture color multiplication is done in linear space. + // to make sure vertex and texture color multiplication is done in the correct space. ui.label("Gamma interpolation:"); self.show_gradients(ui, WHITE, (RED, GREEN), Interpolation::Gamma); @@ -191,8 +191,8 @@ impl ColorTest { ui.separator(); - ui.label("Linear interpolation (texture sampling):"); - self.show_gradients(ui, WHITE, (RED, GREEN), Interpolation::Linear); + ui.label("Texture interpolation (texture sampling) should be in gamma space:"); + self.show_gradients(ui, WHITE, (RED, GREEN), Interpolation::Gamma); } fn show_gradients( @@ -245,11 +245,10 @@ impl ColorTest { let g = Gradient::endpoints(left, right); match interpolation { - Interpolation::Linear => { - // texture sampler is sRGBA aware, and should therefore be linear - self.tex_gradient(ui, "Texture of width 2 (test texture sampler)", bg_fill, &g); - } + Interpolation::Linear => {} Interpolation::Gamma => { + self.tex_gradient(ui, "Texture of width 2 (test texture sampler)", bg_fill, &g); + // vertex shader uses gamma self.vertex_gradient( ui, @@ -330,7 +329,10 @@ fn vertex_gradient(ui: &mut Ui, bg_fill: Color32, gradient: &Gradient) -> Respon #[derive(Clone, Copy)] enum Interpolation { + /// egui used to want Linear interpolation for some things, but now we're always in gamma space. + #[expect(unused)] Linear, + Gamma, } diff --git a/crates/egui_glow/src/painter.rs b/crates/egui_glow/src/painter.rs index 5833d73ef5e..c200172e742 100644 --- a/crates/egui_glow/src/painter.rs +++ b/crates/egui_glow/src/painter.rs @@ -172,12 +172,7 @@ impl Painter { let supported_extensions = gl.supported_extensions(); log::trace!("OpenGL extensions: {supported_extensions:?}"); - let srgb_textures = shader_version == ShaderVersion::Es300 // WebGL2 always support sRGB - || supported_extensions.iter().any(|extension| { - // EXT_sRGB, GL_ARB_framebuffer_sRGB, GL_EXT_sRGB, GL_EXT_texture_sRGB_decode, … - extension.contains("sRGB") - }); - log::debug!("SRGB texture Support: {:?}", srgb_textures); + let srgb_textures = false; // egui wants linear textures let supports_srgb_framebuffer = !cfg!(target_arch = "wasm32") && supported_extensions.iter().any(|extension| { @@ -202,11 +197,10 @@ impl Painter { &gl, glow::FRAGMENT_SHADER, &format!( - "{}\n#define NEW_SHADER_INTERFACE {}\n#define DITHERING {}\n#define SRGB_TEXTURES {}\n{}\n{}", + "{}\n#define NEW_SHADER_INTERFACE {}\n#define DITHERING {}\n{}\n{}", shader_version_declaration, shader_version.is_new_shader_interface() as i32, dithering as i32, - srgb_textures as i32, shader_prefix, FRAG_SRC ), diff --git a/crates/egui_glow/src/shader/fragment.glsl b/crates/egui_glow/src/shader/fragment.glsl index f2792ed04aa..07a931b5367 100644 --- a/crates/egui_glow/src/shader/fragment.glsl +++ b/crates/egui_glow/src/shader/fragment.glsl @@ -43,25 +43,8 @@ vec3 dither_interleaved(vec3 rgb, float levels) { return rgb + noise / (levels - 1.0); } -// 0-1 sRGB gamma from 0-1 linear -vec3 srgb_gamma_from_linear(vec3 rgb) { - bvec3 cutoff = lessThan(rgb, vec3(0.0031308)); - vec3 lower = rgb * vec3(12.92); - vec3 higher = vec3(1.055) * pow(rgb, vec3(1.0 / 2.4)) - vec3(0.055); - return mix(higher, lower, vec3(cutoff)); -} - -// 0-1 sRGBA gamma from 0-1 linear -vec4 srgba_gamma_from_linear(vec4 rgba) { - return vec4(srgb_gamma_from_linear(rgba.rgb), rgba.a); -} - void main() { -#if SRGB_TEXTURES - vec4 texture_in_gamma = srgba_gamma_from_linear(texture2D(u_sampler, v_tc)); -#else vec4 texture_in_gamma = texture2D(u_sampler, v_tc); -#endif // We multiply the colors in gamma space, because that's the only way to get text to look right. vec4 frag_color_gamma = v_rgba_in_gamma * texture_in_gamma;