From 1252974765068f3f5bdf76d7d18eddaf55d288c8 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 2 Jul 2026 14:47:36 +0530 Subject: [PATCH] Port the cell fragment shader --- kitty/shaders/cell.slang | 96 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/kitty/shaders/cell.slang b/kitty/shaders/cell.slang index 45f7992c4..be379b85b 100644 --- a/kitty/shaders/cell.slang +++ b/kitty/shaders/cell.slang @@ -7,6 +7,8 @@ import utils; import hsluv; +import alpha_blend; +import linear2srgb; #define NUM_COLORS 256 @@ -470,4 +472,98 @@ VertexOutput vertex_main( return vo; } +uniform Sampler2DArray sprites; +// Scaling factor for the extra text-alpha adjustment for luminance-difference. +static const float text_gamma_scaling = 0.5; +float clamp_to_unit_float(float x) { + // Clamp value to suitable output range + return clamp(x, 0.0f, 1.0f); +} + +float4 foreground_contrast_new(float4 over, float3 under, float text_contrast, float text_gamma_adjustment) { + float under_luminance = dot(under, Y); + float over_lumininace = dot(over.rgb, Y); + // Apply additional gamma-adjustment scaled by the luminance difference, the darker the foreground the more adjustment we apply. + // A multiplicative contrast is also available to increase saturation. + over.a = clamp_to_unit_float(lerp(over.a, pow(over.a, text_gamma_adjustment), (1 - over_lumininace + under_luminance) * text_gamma_scaling) * text_contrast); + return over; +} + +float4 foreground_contrast_old(float4 over, float3 under) { + // Simulation of gamma-incorrect blending + float under_luminance = dot(under, Y); + float over_lumininace = dot(over.rgb, Y); + // This is the original gamma-incorrect rendering, it is the solution of the following equation: + // + // linear2srgb(over * overA2 + under * (1 - overA2)) = linear2srgb(over) * over.a + linear2srgb(under) * (1 - over.a) + // ^ gamma correct blending with new alpha ^ gamma incorrect blending with old alpha + 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; +} + +float4 foreground_contrast(float4 over, float3 under, float text_contrast, float text_gamma_adjustment) { + if (TEXT_NEW_GAMMA) return foreground_contrast_new(over, under, text_contrast, text_gamma_adjustment); + return foreground_contrast_old(over, under); +} + +float4 load_text_foreground_color(float3 sprite_pos, float colored_sprite, float3 cell_foreground) { + // For colored sprites use the color from the sprite rather than the text foreground + // Return non-premultiplied foreground color + float4 text_fg = sprites.Sample(sprite_pos); + return float4(lerp(cell_foreground, text_fg.xyz, colored_sprite), text_fg.w); +} + +float4 calculate_premul_foreground_from_sprites( + float3 sprite_pos, float3 underline_pos, float3 cursor_pos, float3 strike_pos, uint underline_exclusion_pos, + float4 text_fg, float3 decoration_fg, float4 cursor_color_premult, + float effective_text_alpha, +) { + // Return premul foreground color from decorations (cursor, underline, strikethrough) + int width, height, layer; + sprites.GetDimensions(width, height, layer); + float3 sz = float3(float(width), float(height), float(layer)); + + float underline_alpha = sprites.Sample(underline_pos).w; + int3 fetch_coord = int3(int(sprite_pos.x * sz.x), int(underline_exclusion_pos), int(sprite_pos.z)); + float underline_exclusion = sprites[fetch_coord].w; + + underline_alpha *= 1.0 - underline_exclusion; + float strike_alpha = sprites.Sample(strike_pos).w; + float cursor_alpha = sprites.Sample(cursor_pos).w; + + float combined_alpha = min(text_fg.w + strike_alpha, 1.0); + + float4 ans = alpha_blend( + float4(text_fg.rgb, combined_alpha * effective_text_alpha), + float4(decoration_fg, underline_alpha * effective_text_alpha) + ); + + return lerp(ans, cursor_color_premult, cursor_alpha * cursor_color_premult.w); +} + +float4 adjust_foreground_contrast_with_background(float4 text_fg, float3 bg, float text_contrast, float text_gamma_adjustment) { + return foreground_contrast(text_fg, bg, text_contrast, text_gamma_adjustment); +} + +[shader("fragment")] +float4 fragment_main( + VertexOutput vo, + uniform float text_contrast, + uniform float text_gamma_adjustment, +) : SV_Target { + float4 ans_premul = 0; + if (!ONLY_FOREGROUND) ans_premul = vo.effective_background_premul; + + if (!ONLY_BACKGROUND) { + // blend in the foreground color + float4 text_fg = load_text_foreground_color(vo.sprite_pos, vo.colored_sprite, vo.cell_foreground); + text_fg = adjust_foreground_contrast_with_background(text_fg, vo.background, text_contrast, text_gamma_adjustment); + float4 text_fg_premul = calculate_premul_foreground_from_sprites( + vo.sprite_pos, vo.underline_pos, vo.cursor_pos, vo.strike_pos, vo.underline_exclusion_pos, + text_fg, vo.decoration_fg, vo.cursor_color_premult, vo.effective_text_alpha); + if (ONLY_FOREGROUND) ans_premul = text_fg_premul; + else ans_premul = alpha_blend_premul(text_fg_premul, ans_premul); + } + return ans_premul; +}