diff --git a/kitty/blit_fragment.glsl b/kitty/blit_fragment.glsl index f7b319180..8a160a203 100644 --- a/kitty/blit_fragment.glsl +++ b/kitty/blit_fragment.glsl @@ -7,5 +7,5 @@ out vec4 color; void main() { color = texture(image, texcoord); - color = vec4(color.rgb / color.a, color.a); + color = vec4(color.rgb, color.a); } diff --git a/kitty/cell_fragment.glsl b/kitty/cell_fragment.glsl index 586c8811f..ffc80fedd 100644 --- a/kitty/cell_fragment.glsl +++ b/kitty/cell_fragment.glsl @@ -51,10 +51,45 @@ vec4 alpha_blend_premul(vec3 over, float over_alpha, vec3 under, float under_alp float alpha = mix(under_alpha, 1.0f, over_alpha); return vec4(premul_blend(over, over_alpha, under), alpha); } + +vec4 blend_onto_opaque_premul(vec3 over, float over_alpha, vec3 under) { + // same as alpha_blend_premul with under_alpha = 1 outputs a blended color + // with alpha 1 which is effectively pre-multiplied since alpha is 1 + return vec4(premul_blend(over, over_alpha, under), 1.0); +} + + // }}} + +/* + * Explanation of rendering: + * There are a couple of cases, in order of increasing complexity: + * 1) Simple -- this path is used when there are either no images, or all images are + * drawn on top of text and the background is opaque. In this case, there is a single pass, + * of this shader with cell foreground and background colors blended directly. + * Expected output is a color premultiplied by alpha, with an alpha specified as well. + * + * 2) Interleaved -- this path is used if background is not opaque and there are images or + * if the background is opaque but there are images under text. Rendering happens in + * multiple passes drawing the background and foreground separately and blending. + * + * 2a) Opaque bg with images under text + * There are multiple passes, each pass is blended onto the previous using the opaque blend func (alpha, 1- alpha): + * 1) Draw only the background -- expected output is color with alpha 1 + * 2) Draw the images that are supposed to be below text. This happens in the graphics shader + * 3) Draw the special cells (selection/cursor). Output is same as from step 1, with bg_alpha 1 for special cells and 0 otherwise + * 4) Draw the foreground -- expected output is color with alpha which is blended using the opaque blend func + * 5) Draw the images that are supposed to be above text again in the graphics shader + * + * 2b) Transparent bg with images + * First everything is rendered into a framebuffer, and then the framebauffer is blended onto + * the screen. The framebuffer is needed because it allows access to the background color pixels + * to blend with the image pixels. The steps are basically the same as for 2a. + */ #ifdef NEEDS_FOREGROUND vec4 calculate_foreground() { + // returns the effective foreground color in pre-multiplied form vec4 text_fg = texture(sprites, sprite_pos); vec3 fg = mix(foreground, text_fg.rgb, colored_sprite); float text_alpha = text_fg.a; @@ -70,15 +105,12 @@ vec4 calculate_foreground() { #endif void main() { -#ifdef BACKGROUND +#ifdef SIMPLE + vec4 fg = calculate_foreground(); #ifdef TRANSPARENT - // bg_alpha is doubled to match rendering in the SIMPLE case - // and also rendering of the margin/padding, see https://github.com/kovidgoyal/kitty/pull/1291 - // to test use background_opacity, window_margin_width and icat to display - // an image. - final_color = vec4(background.rgb * bg_alpha * bg_alpha, bg_alpha); + final_color = alpha_blend_premul(fg.rgb, fg.a, background.rgb * bg_alpha, bg_alpha); #else - final_color = vec4(background.rgb, 1.0f); + final_color = blend_onto_opaque_premul(fg.rgb, fg.a, background.rgb); #endif #endif @@ -90,29 +122,22 @@ void main() { #endif #endif -#if defined(FOREGROUND) || defined(SIMPLE) - // FOREGROUND or SIMPLE - vec4 fg = calculate_foreground(); // pre-multiplied foreground +#ifdef BACKGROUND +#ifdef TRANSPARENT + final_color = vec4(background.rgb * bg_alpha, bg_alpha); +#else + final_color = vec4(background.rgb, 1.0f); +#endif +#endif #ifdef FOREGROUND - // FOREGROUND + vec4 fg = calculate_foreground(); // pre-multiplied foreground #ifdef TRANSPARENT final_color = fg; #else final_color = vec4(fg.rgb / fg.a, fg.a); #endif -#else - // SIMPLE -#ifdef TRANSPARENT - final_color = alpha_blend_premul(fg.rgb, fg.a, background * bg_alpha, bg_alpha); - final_color = vec4(final_color.rgb, final_color.a); -#else - // since background alpha is 1.0, it is effectively pre-multiplied - final_color = vec4(premul_blend(fg.rgb, fg.a, background), 1.0f); - final_color = vec4(final_color.rgb, final_color.a); -#endif #endif -#endif } diff --git a/kitty/shaders.c b/kitty/shaders.c index 404ccd208..347998652 100644 --- a/kitty/shaders.c +++ b/kitty/shaders.c @@ -438,7 +438,7 @@ draw_cells_interleaved_premult(ssize_t vao_idx, ssize_t gvao_idx, Screen *screen glDisable(GL_BLEND); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); - // Now render the framebuffer to the screen reversing alpha pre-multiplication + // Now render the framebuffer to the screen glEnable(GL_SCISSOR_TEST); bind_program(BLIT_PROGRAM); bind_vertex_array(blit_vertex_array); static bool blit_constants_set = false;