diff --git a/kitty/blit_fragment.glsl b/kitty/blit_fragment.glsl index 0eea558a5..0d2d0d307 100644 --- a/kitty/blit_fragment.glsl +++ b/kitty/blit_fragment.glsl @@ -1,5 +1,3 @@ -#pragma kitty_include_shader - uniform sampler2D image; in vec2 texcoord; @@ -8,5 +6,4 @@ out vec4 color; void main() { color = texture(image, texcoord); - color.a = linear2srgb(color.a); } diff --git a/kitty/border_vertex.glsl b/kitty/border_vertex.glsl index 6a14345a6..4a74b50b2 100644 --- a/kitty/border_vertex.glsl +++ b/kitty/border_vertex.glsl @@ -1,8 +1,6 @@ -#pragma kitty_include_shader - uniform uvec2 viewport; uniform uint colors[9]; -uniform float background_opacity, do_srgb_correction; +uniform float background_opacity; uniform float tint_opacity, tint_premult; uniform float gamma_lut[256]; in vec4 rect; // left, top, right, bottom @@ -46,6 +44,5 @@ void main() { color3 = is_window_bg * window_bg + (1. - is_window_bg) * color3; float final_opacity = is_default_bg * tint_opacity + (1. - is_default_bg) * background_opacity; float final_premult_opacity = is_default_bg * tint_premult + (1. - is_default_bg) * background_opacity; - final_opacity = do_srgb_correction * linear2srgb(final_opacity) + (1. - do_srgb_correction) * final_opacity; color = vec4(color3 * final_premult_opacity, final_opacity); } diff --git a/kitty/cell_fragment.glsl b/kitty/cell_fragment.glsl index 573ff0703..7cda68b11 100644 --- a/kitty/cell_fragment.glsl +++ b/kitty/cell_fragment.glsl @@ -117,6 +117,7 @@ vec4 foreground_contrast(vec4 over, vec3 under) { return over; } +#ifdef TEXT_OLD_GAMMA vec4 foreground_contrast_incorrect(vec4 over, vec3 under) { // Simulation of gamma-incorrect blending float under_luminance = dot(under, Y); @@ -131,13 +132,17 @@ vec4 foreground_contrast_incorrect(vec4 over, vec3 under) { over.a = clamp_to_unit_float((srgb2linear(linear2srgb(over_lumininace) * over.a + linear2srgb(under_luminance) * (1.0f - over.a)) - under_luminance) / (over_lumininace - under_luminance)); return over; } +#endif -vec4 foreground_color() { +vec4 load_text_foreground_color() { + // For colored sprites use the color from the sprite rather than the text foreground + // Return non-premultiplied foreground color vec4 text_fg = texture(sprites, sprite_pos); return vec4(mix(foreground, text_fg.rgb, colored_sprite), text_fg.a); } -vec4 foreground_with_decorations(vec4 text_fg) { +vec4 calculate_premul_foreground_from_sprites(vec4 text_fg) { + // Return premul foreground color from decorations (cursor, underline, strikethrough) float underline_alpha = texture(sprites, underline_pos).a; float strike_alpha = texture(sprites, strike_pos).a; float cursor_alpha = texture(sprites, cursor_pos).a; @@ -148,39 +153,41 @@ vec4 foreground_with_decorations(vec4 text_fg) { return mix(ans, cursor_color_vec, cursor_alpha); } -vec4 calculate_foreground() { - // returns the effective foreground color in pre-multiplied form - vec4 text_fg = foreground_color(); - return foreground_with_decorations(text_fg); -} - -vec4 calculate_foreground(vec3 bg) { +vec4 adjust_foreground_contrast_with_background(vec4 text_fg, vec3 bg) { // When rendering on a background we can adjust the alpha channel contrast // to improve legibility based on the source and destination colors - vec4 text_fg = foreground_color(); #ifdef TEXT_OLD_GAMMA - text_fg = foreground_contrast_incorrect(text_fg, bg); + return foreground_contrast_incorrect(text_fg, bg); #else - text_fg = foreground_contrast(text_fg, bg); + return foreground_contrast(text_fg, bg); #endif - return foreground_with_decorations(text_fg); } #endif +float adjust_alpha_for_incorrect_blending_by_compositor(float text_fg_alpha, float final_alpha) { + // Adjust the transparent alpha-channel to account for incorrect + // gamma-blending performed by the compositor (true for at least wlroots, picom) + // We have a linear alpha channel apply the sRGB curve to it once again to compensate + // for the incorrect blending in the compositor. + // We apply the correction only if there was actual text at this pixel, so as to not make + // background_opacity non-linear + // See https://github.com/kovidgoyal/kitty/issues/6209 for discussion. + const float threshold = 0.000001; + // linear2srgb(final_alpha) if text_fg_alpha >= threshold else final_alpha + return mix(final_alpha, linear2srgb(final_alpha), step(threshold, text_fg_alpha)); +} + void main() { #ifdef SIMPLE - vec4 fg = calculate_foreground(background); + vec4 text_fg = load_text_foreground_color(); + text_fg = adjust_foreground_contrast_with_background(text_fg, background); + vec4 text_fg_premul = calculate_premul_foreground_from_sprites(text_fg); #ifdef TRANSPARENT - final_color = alpha_blend_premul(fg, vec4_premul(background, bg_alpha)); - - // Adjust the transparent alpha-channel to account for incorrect - // gamma-blending performed by the compositor (true for at least wlroots, - // picom, GNOME, MacOS). - // This is the last pass: - final_color.a = linear2srgb(final_color.a); + final_color = alpha_blend_premul(text_fg_premul, vec4_premul(background, bg_alpha)); + final_color.a = adjust_alpha_for_incorrect_blending_by_compositor(text_fg_premul.a, final_color.a); #else - final_color = alpha_blend_premul(fg, background); + final_color = alpha_blend_premul(text_fg_premul, background); #endif #endif @@ -201,11 +208,12 @@ void main() { #endif #ifdef FOREGROUND - final_color = calculate_foreground(); // pre-multiplied foreground - - // This is the last pass, called both with transparency and without but not in draw_cells_simple(). - // When transparent it is drawn into a framebuffer and linear2srgb() will be done - // when blitting that framebuffer. When not transparent there is not need to do linear2srgb() anyway. + vec4 text_fg = load_text_foreground_color(); + vec4 text_fg_premul = calculate_premul_foreground_from_sprites(text_fg); + final_color = text_fg_premul; +#ifdef TRANSPARENT + final_color.a = adjust_alpha_for_incorrect_blending_by_compositor(text_fg_premul.a, final_color.a); +#endif #endif } diff --git a/kitty/shaders.c b/kitty/shaders.c index dee72e38e..6b7388e89 100644 --- a/kitty/shaders.c +++ b/kitty/shaders.c @@ -988,7 +988,7 @@ draw_cells(ssize_t vao_idx, ssize_t gvao_idx, const ScreenRenderData *srd, float // }}} // Borders {{{ -enum BorderUniforms { BORDER_viewport, BORDER_background_opacity, BORDER_tint_opacity, BORDER_tint_premult, BORDER_colors, BORDER_gamma_lut, BORDER_do_srgb_correction, NUM_BORDER_UNIFORMS }; +enum BorderUniforms { BORDER_viewport, BORDER_background_opacity, BORDER_tint_opacity, BORDER_tint_premult, BORDER_colors, BORDER_gamma_lut, NUM_BORDER_UNIFORMS }; static GLint border_uniform_locations[NUM_BORDER_UNIFORMS] = {0}; static void @@ -998,7 +998,6 @@ init_borders_program(void) { SET_LOC(background_opacity) SET_LOC(tint_opacity) SET_LOC(tint_premult) - SET_LOC(do_srgb_correction) SET_LOC(colors) SET_LOC(gamma_lut) #undef SET_LOC @@ -1022,7 +1021,6 @@ draw_borders(ssize_t vao_idx, unsigned int num_border_rects, BorderRect *rect_bu float background_opacity = w->is_semi_transparent ? w->background_opacity: 1.0f; float tint_opacity = background_opacity; float tint_premult = background_opacity; - float do_srgb_correction = 1.0f; if (has_bgimage(w)) { glEnable(GL_BLEND); BLEND_ONTO_OPAQUE; @@ -1031,7 +1029,6 @@ draw_borders(ssize_t vao_idx, unsigned int num_border_rects, BorderRect *rect_bu background_opacity = 1.0f; tint_opacity = OPT(background_tint) * OPT(background_tint_gaps); tint_premult = w->is_semi_transparent ? OPT(background_tint) : 1.0f; - do_srgb_correction = 0.0f; } if (num_border_rects) { @@ -1053,7 +1050,6 @@ draw_borders(ssize_t vao_idx, unsigned int num_border_rects, BorderRect *rect_bu glUniform1f(border_uniform_locations[BORDER_background_opacity], background_opacity); glUniform1f(border_uniform_locations[BORDER_tint_opacity], tint_opacity); glUniform1f(border_uniform_locations[BORDER_tint_premult], tint_premult); - glUniform1f(border_uniform_locations[BORDER_do_srgb_correction], do_srgb_correction); glUniform2ui(border_uniform_locations[BORDER_viewport], viewport_width, viewport_height); glUniform1fv(border_uniform_locations[BORDER_gamma_lut], 256, srgb_lut); if (has_bgimage(w)) {