Switch over to the slang based shaders

This commit is contained in:
Kovid Goyal 2026-07-06 21:29:35 +05:30
parent 425c733c00
commit 61e24215e8
No known key found for this signature in database
GPG key ID: 06BC317B515ACE7C
35 changed files with 167 additions and 1529 deletions

View file

@ -1,22 +0,0 @@
vec4 alpha_blend(vec4 over, vec4 under) {
// Alpha blend two colors returning the resulting color pre-multiplied by its alpha
// and its alpha.
// See https://en.wikipedia.org/wiki/Alpha_compositing
float alpha = mix(under.a, 1.0f, over.a);
vec3 combined_color = mix(under.rgb * under.a, over.rgb, over.a);
return vec4(combined_color, alpha);
}
vec4 alpha_blend_premul(vec4 over, vec4 under) {
// Same as alpha_blend() except that it assumes over and under are both premultiplied.
float inv_over_alpha = 1.0f - over.a;
float alpha = over.a + under.a * inv_over_alpha;
return vec4(over.rgb + under.rgb * inv_over_alpha, alpha);
}
vec4 alpha_blend_premul(vec4 over, 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
float inv_over_alpha = 1.0f - over.a;
return vec4(over.rgb + under.rgb * inv_over_alpha, 1.0);
}

View file

@ -1,11 +0,0 @@
#pragma kitty_include_shader <alpha_blend.glsl>
uniform sampler2D image;
uniform vec4 background;
in vec2 texcoord;
out vec4 premult_color;
void main() {
vec4 color = texture(image, texcoord);
premult_color = alpha_blend(color, background);
}

View file

@ -1,48 +0,0 @@
#define left 0
#define top 1
#define right 2
#define bottom 3
#define tex_left 0
#define tex_top 0
#define tex_right 1
#define tex_bottom 1
uniform float tiled;
uniform vec4 sizes; // [ window_width, window_height, image_width, image_height ]
uniform vec4 positions; // [ left, top, right, bottom ]
out vec2 texcoord;
const vec2 tex_map[] = vec2[4](
vec2(tex_left, tex_top),
vec2(tex_left, tex_bottom),
vec2(tex_right, tex_bottom),
vec2(tex_right, tex_top)
);
float scale_factor(float window_size, float image_size) {
return window_size / image_size;
}
float tiling_factor(int i) {
#define window i
#define image i + 2
return tiled * scale_factor(sizes[window], sizes[image]) + (1 - tiled);
}
void main() {
vec2 pos_map[] = vec2[4](
vec2(positions[left], positions[top]),
vec2(positions[left], positions[bottom]),
vec2(positions[right], positions[bottom]),
vec2(positions[right], positions[top])
);
vec2 tex_coords = tex_map[gl_VertexID];
#define x_axis 0
#define y_axis 1
texcoord = vec2(
tex_coords[x_axis] * tiling_factor(x_axis),
tex_coords[y_axis] * tiling_factor(y_axis)
);
gl_Position = vec4(pos_map[gl_VertexID], 0, 1);
}

View file

@ -1,19 +0,0 @@
out vec2 texcoord;
#define left 0
#define top 1
#define right 2
#define bottom 3
const ivec2 vertex_pos_map[4] = ivec2[4](
ivec2(right, top),
ivec2(right, bottom),
ivec2(left, bottom),
ivec2(left, top)
);
void main() {
ivec2 pos = vertex_pos_map[gl_VertexID];
texcoord = vec2(src_rect[pos.x], src_rect[pos.y]);
gl_Position = vec4(dest_rect[pos.x], dest_rect[pos.y], 0, 1);
}

View file

@ -1,13 +0,0 @@
#pragma kitty_include_shader <alpha_blend.glsl>
#pragma kitty_include_shader <utils.glsl>
#pragma kitty_include_shader <linear2srgb.glsl>
uniform sampler2D image;
in vec2 texcoord;
out vec4 output_color;
void main() {
vec4 color_premul = texture(image, texcoord);
output_color = vec4_premul(linear2srgb(color_premul.rgb / color_premul.a), color_premul.a);
}

View file

@ -1,2 +0,0 @@
uniform vec4 src_rect, dest_rect;
#pragma kitty_include_shader <blit_common.glsl>

View file

@ -1,6 +0,0 @@
in vec4 color_premul;
out vec4 output_premul;
void main() {
output_premul = color_premul;
}

View file

@ -1,62 +0,0 @@
#pragma kitty_include_shader <utils.glsl>
#define DEFAULT_BG 0
#define ACTIVE_BORDER_COLOR 1
#define INACTIVE_BORDER_COLOR 2
#define WINDOW_BACKGROUND_PLACEHOLDER 3
#define BELL_BORDER_COLOR 4
#define TAB_BAR_BG_COLOR 5
#define TAB_BAR_MARGIN_COLOR 6
#define TAB_BAR_EDGE_LEFT_COLOR 7
#define TAB_BAR_EDGE_RIGHT_COLOR 8
uniform uint colors[9];
uniform float background_opacity;
uniform float gamma_lut[256];
in vec4 rect; // left, top, right, bottom
in uint rect_color;
out vec4 color_premul;
// indices into the rect vector
const int LEFT = 0;
const int TOP = 1;
const int RIGHT = 2;
const int BOTTOM = 3;
const uint FF = uint(0xff);
const uvec2 pos_map[] = uvec2[4](
uvec2(RIGHT, TOP),
uvec2(RIGHT, BOTTOM),
uvec2(LEFT, BOTTOM),
uvec2(LEFT, TOP)
);
float to_color(uint c) {
return gamma_lut[c & FF];
}
float is_integer_value(uint c, int x) {
return 1. - step(0.5, abs(float(c) - float(x)));
}
vec3 as_color_vector(uint c, int shift) {
return vec3(to_color(c >> shift), to_color(c >> (shift - 8)), to_color(c >> (shift - 16)));
}
void main() {
uvec2 pos = pos_map[gl_VertexID];
gl_Position = vec4(rect[pos.x], rect[pos.y], 0, 1);
vec3 window_bg = as_color_vector(rect_color, 24);
uint rc = rect_color & FF;
vec3 color3 = as_color_vector(colors[rc], 16);
float is_window_bg = is_integer_value(rc, WINDOW_BACKGROUND_PLACEHOLDER); // used by window padding areas
float is_default_bg = is_integer_value(rc, DEFAULT_BG);
color3 = if_one_then(is_window_bg, window_bg, color3);
// Actual border quads and tab bar edge strips must be always drawn opaque
float is_not_a_border = zero_or_one(abs(
(float(rc) - ACTIVE_BORDER_COLOR) * (float(rc) - INACTIVE_BORDER_COLOR) * (float(rc) - BELL_BORDER_COLOR) *
(float(rc) - TAB_BAR_EDGE_LEFT_COLOR) * (float(rc) - TAB_BAR_EDGE_RIGHT_COLOR)
));
float final_opacity = if_one_then(is_not_a_border, background_opacity, 1.);
color_premul = vec4_premul(color3, final_opacity);
}

View file

@ -149,7 +149,7 @@ from .session import (
most_recent_session,
save_as_session,
)
from .shaders.legacy import load_shader_programs
from .shaders.slang import load_shader_programs
from .simple_cli_definitions import grab_keyboard_docs
from .tabs import SpecialWindow, SpecialWindowInstance, Tab, TabDict, TabManager
from .types import AsyncResponse, LayerShellConfig, SingleInstanceData, WindowSystemMouseEvent, ac

View file

@ -1,33 +0,0 @@
#define FG_OVERRIDE_ALGO {FG_OVERRIDE_ALGO}
#define TEXT_NEW_GAMMA {TEXT_NEW_GAMMA}
#define DECORATION_SHIFT {DECORATION_SHIFT}
#define REVERSE_SHIFT {REVERSE_SHIFT}
#define STRIKE_SHIFT {STRIKE_SHIFT}
#define DIM_SHIFT {DIM_SHIFT}
#define BLINK_SHIFT {BLINK_SHIFT}
#define MARK_SHIFT {MARK_SHIFT}
#define MARK_MASK {MARK_MASK}
#define USE_SELECTION_FG
#define NUM_COLORS 256
#define COLOR_NOT_SET {COLOR_NOT_SET}
#define COLOR_IS_SPECIAL {COLOR_IS_SPECIAL}
#define COLOR_IS_RGB {COLOR_IS_RGB}
#define COLOR_IS_INDEX {COLOR_IS_INDEX}
#if {ONLY_BACKGROUND} == 1
#define ONLY_BACKGROUND
#endif
#if {ONLY_FOREGROUND} == 1
#define ONLY_FOREGROUND
#endif
#if FG_OVERRIDE_ALGO == 0
#define DO_FG_OVERRIDE 0
#else
#define DO_FG_OVERRIDE 1
#endif
// Linear space luminance values
const vec3 Y = vec3(0.2126, 0.7152, 0.0722);

View file

@ -1,111 +0,0 @@
#pragma kitty_include_shader <alpha_blend.glsl>
#pragma kitty_include_shader <linear2srgb.glsl>
#pragma kitty_include_shader <cell_defines.glsl>
#pragma kitty_include_shader <utils.glsl>
uniform float text_contrast;
uniform float text_gamma_adjustment;
uniform sampler2DArray sprites;
in vec3 background;
in vec4 effective_background_premul;
#ifndef ONLY_BACKGROUND
in float effective_text_alpha;
in vec3 sprite_pos;
in vec3 underline_pos;
in vec3 cursor_pos;
in vec3 strike_pos;
flat in uint underline_exclusion_pos;
in vec3 cell_foreground;
in vec4 cursor_color_premult;
in vec3 decoration_fg;
in float colored_sprite;
#endif
out vec4 output_color;
// Scaling factor for the extra text-alpha adjustment for luminance-difference.
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);
}
#ifndef ONLY_BACKGROUND
#if TEXT_NEW_GAMMA == 1
vec4 foreground_contrast(vec4 over, vec3 under) {
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(mix(over.a, pow(over.a, text_gamma_adjustment), (1 - over_lumininace + under_luminance) * text_gamma_scaling) * text_contrast);
return over;
}
#else
vec4 foreground_contrast(vec4 over, vec3 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;
}
#endif
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(cell_foreground, text_fg.rgb, colored_sprite), text_fg.a);
}
vec4 calculate_premul_foreground_from_sprites(vec4 text_fg) {
// Return premul foreground color from decorations (cursor, underline, strikethrough)
ivec3 sz = textureSize(sprites, 0);
float underline_alpha = texture(sprites, underline_pos).a;
float underline_exclusion = texelFetch(sprites, ivec3(int(
sprite_pos.x * float(sz.x)), int(underline_exclusion_pos), int(sprite_pos.z)), 0).a;
underline_alpha *= 1.0f - underline_exclusion;
float strike_alpha = texture(sprites, strike_pos).a;
float cursor_alpha = texture(sprites, cursor_pos).a;
// Since strike and text are the same color, we simply add the alpha values
float combined_alpha = min(text_fg.a + strike_alpha, 1.0f);
// Underline color might be different, so alpha blend
vec4 ans = alpha_blend(vec4(text_fg.rgb, combined_alpha * effective_text_alpha), vec4(decoration_fg, underline_alpha * effective_text_alpha));
return mix(ans, cursor_color_premult, cursor_alpha * cursor_color_premult.a);
}
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
return foreground_contrast(text_fg, bg);
}
#endif // ifndef ONLY_BACKGROUND
void main() {
#ifdef ONLY_FOREGROUND
vec4 ans_premul;
#else
vec4 ans_premul = effective_background_premul;
#endif
#ifndef ONLY_BACKGROUND
// blend in the foreground color
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 ONLY_FOREGROUND
ans_premul = text_fg_premul;
#else
ans_premul = alpha_blend_premul(text_fg_premul, ans_premul);
#endif
#endif // ifndef ONLY_BACKGROUND
output_color = ans_premul;
}

View file

@ -1,387 +0,0 @@
#extension GL_ARB_explicit_attrib_location : require
#pragma kitty_include_shader <cell_defines.glsl>
#pragma kitty_include_shader <utils.glsl>
// Inputs {{{
layout(std140) uniform CellRenderData {
float use_cell_bg_for_selection_fg, use_cell_fg_for_selection_fg, use_cell_for_selection_bg;
uint default_fg, highlight_fg, highlight_bg, main_cursor_fg, main_cursor_bg, url_color, url_style, inverted, extra_cursor_fg, extra_cursor_bg;
uint columns, lines, sprites_xnum, sprites_ynum, cursor_shape, cell_width, cell_height;
uint cursor_x1, cursor_x2, cursor_y1, cursor_y2;
float cursor_opacity, inactive_text_alpha, fg_override_threshold, row_offset, dim_opacity, blink_opacity;
// must have unique entries with 0 being default_bg and unset being UINT32_MAX
uint bg_colors0, bg_colors1, bg_colors2, bg_colors3, bg_colors4, bg_colors5, bg_colors6, bg_colors7;
float bg_opacities0, bg_opacities1, bg_opacities2, bg_opacities3, bg_opacities4, bg_opacities5, bg_opacities6, bg_opacities7;
};
layout(std140) uniform ColorTable {
uint color_table[NUM_COLORS + MARK_MASK + MARK_MASK + 2];
};
uniform float gamma_lut[256];
uniform uint draw_bg_bitfield;
uniform usampler2D sprite_decorations_map;
// Have to use fixed locations here as all variants of the cell program share the same VAOs
layout(location=0) in uvec3 colors;
layout(location=1) in uvec2 sprite_idx;
layout(location=2) in uint is_selected;
// }}}
const int fg_index_map[] = int[3](0, 1, 0);
const uvec2 cell_pos_map[] = uvec2[4](
uvec2(1u, 0u), // right, top
uvec2(1u, 1u), // right, bottom
uvec2(0u, 1u), // left, bottom
uvec2(0u, 0u) // left, top
);
const uint cursor_shape_map[] = uint[5]( // maps cursor shape to foreground sprite index
0u, // NO_CURSOR
0u, // BLOCK (this is rendered as background)
2u, // BEAM
3u, // UNDERLINE
4u // UNFOCUSED
);
out vec3 background;
out vec4 effective_background_premul;
#ifndef ONLY_BACKGROUND
out float effective_text_alpha;
out vec3 sprite_pos;
out vec3 underline_pos;
out vec3 cursor_pos;
out vec3 strike_pos;
flat out uint underline_exclusion_pos;
out vec3 cell_foreground;
out vec4 cursor_color_premult;
out vec3 decoration_fg;
out float colored_sprite;
#endif
// Utility functions {{{
const uint BYTE_MASK = uint(0xFF);
const uint SPRITE_INDEX_MASK = uint(0x7fffffff);
const uint SPRITE_COLORED_MASK = uint(0x80000000);
const uint SPRITE_COLORED_SHIFT = 31u;
const uint BIT_MASK = 1u;
const uint DECORATION_MASK = uint({DECORATION_MASK});
vec3 color_to_vec(uint c) {
uint r, g, b;
r = (c >> 16) & BYTE_MASK;
g = (c >> 8) & BYTE_MASK;
b = c & BYTE_MASK;
return vec3(gamma_lut[r], gamma_lut[g], gamma_lut[b]);
}
float one_if_equal_zero_otherwise(float a, float b) { return (1.0f - zero_or_one(abs(float(a) - float(b)))); }
// Wee need an integer variant to accommodate GPU driver bugs, see
// https://github.com/kovidgoyal/kitty/issues/9072
uint one_if_equal_zero_otherwise(int a, int b) { return (1u - uint(zero_or_one(abs(float(a) - float(b))))); }
uint one_if_equal_zero_otherwise(uint a, uint b) { return (1u - uint(zero_or_one(abs(float(a) - float(b))))); }
uint resolve_color(uint c, uint defval) {
// Convert a cell color to an actual color based on the color table
int t = int(c & BYTE_MASK);
uint is_one = one_if_equal_zero_otherwise(t, 1);
uint is_two = one_if_equal_zero_otherwise(t, 2);
uint is_neither_one_nor_two = 1u - is_one - is_two;
return is_one * color_table[(c >> 8) & BYTE_MASK] + is_two * (c >> 8) + is_neither_one_nor_two * defval;
}
vec3 to_color(uint c, uint defval) {
return color_to_vec(resolve_color(c, defval));
}
vec3 resolve_dynamic_color(uint c, vec3 special_val, vec3 defval) {
float type = float((c >> 24) & BYTE_MASK);
#define q(which, val) one_if_equal_zero_otherwise(type, float(which)) * val
return (
q(COLOR_IS_RGB, color_to_vec(c)) + q(COLOR_IS_INDEX, color_to_vec(color_table[c & BYTE_MASK])) +
q(COLOR_IS_SPECIAL, special_val) + q(COLOR_NOT_SET, defval)
);
#undef q
}
float contrast_ratio(float under_luminance, float over_luminance) {
return clamp((max(under_luminance, over_luminance) + 0.05f) / (min(under_luminance, over_luminance) + 0.05f), 1.f, 21.f);
}
float contrast_ratio(vec3 a, vec3 b) {
return contrast_ratio(dot(a, Y), dot(b, Y));
}
struct ColorPair {
vec3 bg, fg;
};
float contrast_ratio(ColorPair a) { return contrast_ratio(a.bg, a.fg); }
ColorPair if_less_than_pair(float a, float b, ColorPair thenval, ColorPair elseval) {
return ColorPair(if_less_than(a, b, thenval.bg, elseval.bg),
if_less_than(a, b, thenval.fg, elseval.fg));
}
ColorPair if_one_then_pair(float condition, ColorPair thenval, ColorPair elseval) {
return ColorPair(if_one_then(condition, thenval.bg, elseval.bg),
if_one_then(condition, thenval.fg, elseval.fg));
}
ColorPair resolve_extra_cursor_colors_for_special_cursor(vec3 cell_bg, vec3 cell_fg) {
ColorPair cell = ColorPair(cell_fg, cell_bg), base = ColorPair(color_to_vec(default_fg), color_to_vec(bg_colors0));
float cr = contrast_ratio(cell), br = contrast_ratio(base);
ColorPair higher_contrast_pair = if_less_than_pair(cr, br, base, cell);
return if_less_than_pair(cr, 2.5, higher_contrast_pair, cell);
}
ColorPair resolve_extra_cursor_colors(vec3 cell_bg, vec3 cell_fg, ColorPair main_cursor) {
ColorPair ans = ColorPair(
resolve_dynamic_color(extra_cursor_bg, main_cursor.bg, main_cursor.bg),
resolve_dynamic_color(extra_cursor_fg, cell_bg, main_cursor.fg)
);
ColorPair special = resolve_extra_cursor_colors_for_special_cursor(cell_bg, cell_fg);
return if_one_then_pair(zero_or_one(abs(float(extra_cursor_bg & BYTE_MASK) - COLOR_IS_SPECIAL)), ans, special);
}
uvec3 to_sprite_coords(uint idx) {
uint sprites_per_page = sprites_xnum * sprites_ynum;
uint z = idx / sprites_per_page;
uint num_on_last_page = idx - sprites_per_page * z;
uint y = num_on_last_page / sprites_xnum;
uint x = num_on_last_page - sprites_xnum * y;
return uvec3(x, y, z);
}
vec3 to_sprite_pos(uvec2 pos, uint idx) {
uvec3 c = to_sprite_coords(idx);
vec2 s_xpos = vec2(c.x, float(c.x) + 1.0f) * (1.0f / float(sprites_xnum));
vec2 s_ypos = vec2(c.y, float(c.y) + 1.0f) * (1.0f / float(sprites_ynum));
uint texture_height_px = (cell_height + 1u) * sprites_ynum;
float row_height = 1.0f / float(texture_height_px);
s_ypos[1] -= row_height; // skip the decorations_exclude row
return vec3(s_xpos[pos.x], s_ypos[pos.y], c.z);
}
uint to_underline_exclusion_pos() {
uvec3 c = to_sprite_coords(sprite_idx[0]);
uint cell_top_px = c.y * (cell_height + 1u);
return cell_top_px + cell_height;
}
uint read_sprite_decorations_idx() {
int idx = int(sprite_idx[0] & SPRITE_INDEX_MASK);
ivec2 sz = textureSize(sprite_decorations_map, 0);
int y = idx / sz[0];
int x = idx - y * sz[0];
return texelFetch(sprite_decorations_map, ivec2(x, y), 0).r;
}
uvec2 get_decorations_indices(uint in_url /* [0, 1] */, uint text_attrs) {
uint decorations_idx = read_sprite_decorations_idx();
// decorations_idx == 0 means no decorations, for example, for a blank line
// when drawing fractionally scaled text
uint has_decorations = uint(zero_or_one(float(decorations_idx)));
uint strike_style = ((text_attrs >> STRIKE_SHIFT) & BIT_MASK); // 0 or 1
uint strike_idx = decorations_idx * strike_style;
uint underline_style = ((text_attrs >> DECORATION_SHIFT) & DECORATION_MASK);
underline_style = in_url * url_style + (1u - in_url) * underline_style; // [0, 5]
uint has_underline = uint(step(0.5f, float(underline_style))); // [0, 1]
return has_decorations * uvec2(strike_idx, has_underline * (decorations_idx + underline_style));
}
uint is_cursor(uint x, uint y) {
uint clamped_x = clamp(x, cursor_x1, cursor_x2);
uint clamped_y = clamp(y, cursor_y1, cursor_y2);
return one_if_equal_zero_otherwise(x, clamped_x) * one_if_equal_zero_otherwise(y, clamped_y);
}
// }}}
struct CellData {
float has_cursor, has_block_cursor;
uvec2 pos;
uint cursor_fg_sprite_idx;
ColorPair cursor;
} cell_data;
CellData set_vertex_position(vec3 cell_fg, vec3 cell_bg) {
uint instance_id = uint(gl_InstanceID);
float dx = 2.0 / float(columns);
float dy = 2.0 / float(lines);
/* The current cell being rendered */
uint row = instance_id / columns;
uint column = instance_id - row * columns;
/* The position of this vertex, at a corner of the cell */
float left = -1.0 + column * dx;
float top = 1.0 - (float(row) + row_offset) * dy;
uvec2 pos = cell_pos_map[gl_VertexID];
gl_Position = vec4(vec2(left, left + dx)[pos.x], vec2(top, top - dy)[pos.y], 0, 1);
// The character sprite being rendered
#ifndef ONLY_BACKGROUND
sprite_pos = to_sprite_pos(pos, sprite_idx[0] & SPRITE_INDEX_MASK);
colored_sprite = float((sprite_idx[0] & SPRITE_COLORED_MASK) >> SPRITE_COLORED_SHIFT);
#endif
// Cursor shape and colors
float has_main_cursor = float(is_cursor(column, row));
float multicursor_shape = float((is_selected >> 2) & 3u);
float multicursor_uses_main_cursor_shape = float((is_selected >> 4) & BIT_MASK);
multicursor_shape = if_one_then(multicursor_uses_main_cursor_shape, cursor_shape, multicursor_shape);
float final_cursor_shape = if_one_then(has_main_cursor, cursor_shape, multicursor_shape);
float has_cursor = zero_or_one(final_cursor_shape);
float is_block_cursor = has_cursor * one_if_equal_zero_otherwise(final_cursor_shape, 1.0);
ColorPair main_cursor = ColorPair(color_to_vec(main_cursor_bg), color_to_vec(main_cursor_fg));
ColorPair extra_cursor = resolve_extra_cursor_colors(cell_bg, cell_fg, main_cursor);
ColorPair cursor = if_one_then_pair(has_main_cursor, main_cursor, extra_cursor);
return CellData(has_cursor, is_block_cursor, pos, cursor_shape_map[int(final_cursor_shape)], cursor);
}
float background_opacity_for(uint bg, uint colorval, float opacity_if_matched) { // opacity_if_matched if bg == colorval else 1
float not_matched = step(1.f, abs(float(colorval - bg))); // not_matched = 0 if bg == colorval else 1
return not_matched + opacity_if_matched * (1.f - not_matched);
}
float calc_background_opacity(uint bg) {
return (
background_opacity_for(bg, bg_colors0, bg_opacities0) *
background_opacity_for(bg, bg_colors1, bg_opacities1) *
background_opacity_for(bg, bg_colors2, bg_opacities2) *
background_opacity_for(bg, bg_colors3, bg_opacities3) *
background_opacity_for(bg, bg_colors4, bg_opacities4) *
background_opacity_for(bg, bg_colors5, bg_opacities5) *
background_opacity_for(bg, bg_colors6, bg_opacities6) *
background_opacity_for(bg, bg_colors7, bg_opacities7)
);
}
// Overriding of foreground colors for contrast requirements {{{
#if DO_FG_OVERRIDE == 1 && !defined(ONLY_BACKGROUND)
#define OVERRIDE_FG_COLORS
#pragma kitty_include_shader <hsluv.glsl>
#if (FG_OVERRIDE_ALGO == 1)
vec3 fg_override(float under_luminance, float over_lumininace, vec3 under, vec3 over) {
// If the difference in luminance is too small,
// force the foreground color to be black or white.
float diff_luminance = abs(under_luminance - over_lumininace);
float override_level = (1.f - colored_sprite) * step(diff_luminance, fg_override_threshold);
float original_level = 1.f - override_level;
return original_level * over + override_level * vec3(step(under_luminance, 0.5f));
}
#else
vec3 fg_override(float under_luminance, float over_luminance, vec3 under, vec3 over) {
float ratio = contrast_ratio(under_luminance, over_luminance);
vec3 diff = abs(under - over);
vec3 over_hsluv = rgbToHsluv(over);
const float min_contrast_ratio = fg_override_threshold;
float target_lum_a = clamp((under_luminance + 0.05f) * min_contrast_ratio - 0.05f, 0.f, 1.f);
float target_lum_b = clamp((under_luminance + 0.05f) / min_contrast_ratio - 0.05f, 0.f, 1.f);
vec3 result_a = clamp(hsluvToRgb(vec3(over_hsluv.x, over_hsluv.y, target_lum_a * 100.f)), 0.f, 1.f);
vec3 result_b = clamp(hsluvToRgb(vec3(over_hsluv.x, over_hsluv.y, target_lum_b * 100.f)), 0.f, 1.f);
float result_a_ratio = contrast_ratio(under_luminance, dot(result_a, Y));
float result_b_ratio = contrast_ratio(under_luminance, dot(result_b, Y));
vec3 result = mix(result_a, result_b, step(result_a_ratio, result_b_ratio));
return mix(result, over, max(step(diff.r + diff.g + diff.b, 0.001f), step(min_contrast_ratio, ratio)));
}
#endif
vec3 override_foreground_color(vec3 over, vec3 under) {
float under_luminance = dot(under, Y);
float over_lumininace = dot(over.rgb, Y);
return fg_override(under_luminance, over_lumininace, under, over);
}
#endif
// }}}
void main() {
// set cell color indices {{{
uvec2 default_colors = uvec2(default_fg, bg_colors0);
uint text_attrs = sprite_idx[1];
uint is_reversed = ((text_attrs >> REVERSE_SHIFT) & BIT_MASK);
uint is_inverted = is_reversed + inverted;
int fg_index = fg_index_map[is_inverted];
int bg_index = 1 - fg_index;
int mark = int(text_attrs >> MARK_SHIFT) & MARK_MASK;
uint has_mark = uint(step(1, float(mark)));
uint bg_as_uint = resolve_color(colors[bg_index], default_colors[bg_index]);
bg_as_uint = has_mark * color_table[NUM_COLORS + mark - 1] + (BIT_MASK - has_mark) * bg_as_uint;
float cell_has_default_bg = 1.f - step(1.f, abs(float(bg_as_uint - bg_colors0))); // 1 if has default bg else 0
vec3 bg = color_to_vec(bg_as_uint);
uint fg_as_uint = resolve_color(colors[fg_index], default_colors[fg_index]);
fg_as_uint = has_mark * color_table[NUM_COLORS + MARK_MASK + mark] + (1u - has_mark) * fg_as_uint;
vec3 foreground = color_to_vec(fg_as_uint);
CellData cell_data = set_vertex_position(foreground, bg);
// }}}
// Foreground {{{
#ifndef ONLY_BACKGROUND // background does not depend on foreground
float has_dim = float((text_attrs >> DIM_SHIFT) & BIT_MASK), has_blink = float((text_attrs >> BLINK_SHIFT) & BIT_MASK);
effective_text_alpha = inactive_text_alpha * if_one_then(has_dim, dim_opacity, 1.0) * if_one_then(
has_blink, blink_opacity, 1.0);
float in_url = float((is_selected >> 1) & BIT_MASK);
decoration_fg = if_one_then(in_url, color_to_vec(url_color), to_color(colors[2], fg_as_uint));
// Selection
vec3 selection_color = if_one_then(use_cell_bg_for_selection_fg, bg, color_to_vec(highlight_fg));
selection_color = if_one_then(use_cell_fg_for_selection_fg, foreground, selection_color);
foreground = if_one_then(float(is_selected & BIT_MASK), selection_color, foreground);
decoration_fg = if_one_then(float(is_selected & BIT_MASK), selection_color, decoration_fg);
// Underline and strike through (rendered via sprites)
uvec2 decs = get_decorations_indices(uint(in_url), text_attrs);
strike_pos = to_sprite_pos(cell_data.pos, decs[0]);
underline_pos = to_sprite_pos(cell_data.pos, decs[1]);
underline_exclusion_pos = to_underline_exclusion_pos();
// Cursor
cursor_color_premult = vec4(cell_data.cursor.bg * cursor_opacity, cursor_opacity);
vec3 final_cursor_text_color = mix(foreground, cell_data.cursor.fg, cursor_opacity);
foreground = if_one_then(cell_data.has_block_cursor, final_cursor_text_color, foreground);
decoration_fg = if_one_then(cell_data.has_block_cursor, final_cursor_text_color, decoration_fg);
cursor_pos = to_sprite_pos(cell_data.pos, cell_data.cursor_fg_sprite_idx * uint(cell_data.has_cursor));
#endif
// }}}
// Background {{{
float bg_alpha = calc_background_opacity(bg_as_uint);
// we use max so that opacity of the block cursor cell background goes from bg_alpha to 1
float effective_cursor_opacity = max(cursor_opacity, bg_alpha);
// is_special_cell is either 0 or 1
float is_special_cell = cell_data.has_block_cursor + float(is_selected & BIT_MASK);
is_special_cell += float(is_reversed); // reverse video cells should be opaque as well
is_special_cell = zero_or_one(is_special_cell);
cell_has_default_bg = if_one_then(is_special_cell, 0., cell_has_default_bg);
// special cells must always be fully opaque, otherwise leave bg_alpha untouched
bg_alpha = if_one_then(is_special_cell, 1.f, bg_alpha);
// Selection and cursor
bg_alpha = if_one_then(cell_data.has_block_cursor, effective_cursor_opacity, bg_alpha);
bg = if_one_then(float(is_selected & BIT_MASK), if_one_then(use_cell_for_selection_bg, color_to_vec(fg_as_uint), color_to_vec(highlight_bg)), bg);
vec3 background_rgb = if_one_then(cell_data.has_block_cursor, mix(bg, cell_data.cursor.bg, cursor_opacity), bg);
background = background_rgb;
// }}}
#if !defined(ONLY_BACKGROUND) && defined(OVERRIDE_FG_COLORS)
decoration_fg = override_foreground_color(decoration_fg, background_rgb);
foreground = override_foreground_color(foreground, background_rgb);
#endif
#if !defined(ONLY_FOREGROUND)
vec4 bgpremul = vec4_premul(background_rgb, bg_alpha);
// draw_bg_bitfield has bit 0 set to draw default bg cells and bit 1 set to draw non-default bg cells
float cell_has_non_default_bg = 1.f - cell_has_default_bg;
uint draw_bg_mask = uint(2.f * cell_has_non_default_bg + cell_has_default_bg); // 1 if has default bg else 2
float draw_bg = step(0.5, float(draw_bg_bitfield & draw_bg_mask));
bgpremul *= draw_bg;
effective_background_premul = bgpremul;
#endif
#ifndef ONLY_BACKGROUND
cell_foreground = foreground;
#endif
}

View file

@ -307,6 +307,9 @@ get_uniform_array_information(int program, const char *name) {
GLuint pid = program_id(program);
GLuint t;
glGetUniformIndices(pid, 1, (void*)names, &t);
if (t == GL_INVALID_INDEX) {
fatal("Could not find the index for uniform array: %s", name);
}
glGetActiveUniformsiv(pid, 1, &t, GL_UNIFORM_ARRAY_STRIDE, &ans.stride);
glGetActiveUniformsiv(pid, 1, &t, GL_UNIFORM_OFFSET, &ans.offset);
glGetActiveUniformsiv(pid, 1, &t, GL_UNIFORM_SIZE, &ans.size);
@ -484,10 +487,8 @@ add_located_attribute_to_vao(ssize_t vao_idx, GLint aloc, GLint size, GLenum dat
void
add_attribute_to_vao(int p, ssize_t vao_idx, const char *name, GLint size, GLenum data_type, GLsizei stride, void *offset, GLuint divisor) {
GLint aloc = attrib_location(p, name);
if (aloc == -1) fatal("No attribute named: %s found in this program", name);
add_located_attribute_to_vao(vao_idx, aloc, size, data_type, stride, offset, divisor);
add_attribute_to_vao(ssize_t vao_idx, int location, GLint size, GLenum data_type, GLsizei stride, void *offset, GLuint divisor) {
add_located_attribute_to_vao(vao_idx, location, size, data_type, stride, offset, divisor);
}
void

View file

@ -53,7 +53,7 @@ ArrayInformation get_uniform_array_information(int program, const char *name);
GLint attrib_location(int program, const char *name);
ssize_t create_vao(void);
size_t add_buffer_to_vao(ssize_t vao_idx, GLenum usage);
void add_attribute_to_vao(int p, ssize_t vao_idx, const char *name, GLint size, GLenum data_type, GLsizei stride, void *offset, GLuint divisor);
void add_attribute_to_vao(ssize_t vao_idx, int location, GLint size, GLenum data_type, GLsizei stride, void *offset, GLuint divisor);
ssize_t alloc_vao_buffer(ssize_t vao_idx, GLsizeiptr size, size_t bufnum, GLenum usage);
void* alloc_and_map_vao_buffer(ssize_t vao_idx, GLsizeiptr size, size_t bufnum, bool frequently_updated);
void unmap_vao_buffer(ssize_t vao_idx, size_t bufnum);

10
kitty/glsl-uniforms.h generated
View file

@ -48,14 +48,15 @@ static inline void get_uniform_locations_border(int program, BorderUniforms *ans
ans->rect_color = 1;
ans->Colors.index = block_index(program, "block_Colors_0");
ans->Colors.size = block_size(program, ans->Colors.index);
ans->colors = get_uniform_array_information(program, "block_Colors_0.colors_0[0]");
ans->colors = get_uniform_array_information(program, "colors_0");
ans->GammaLUT.index = block_index(program, "block_GammaLUT_0");
ans->GammaLUT.size = block_size(program, ans->GammaLUT.index);
ans->gamma_lut = get_uniform_array_information(program, "block_GammaLUT_0.gamma_lut_0[0]");
ans->gamma_lut = get_uniform_array_information(program, "gamma_lut_0");
}
typedef struct CellUniforms {
int draw_bg_bitfield;
int sprite_decorations_map;
int sprites;
int text_contrast;
int text_gamma_adjustment;
@ -73,6 +74,7 @@ typedef struct CellUniforms {
static inline void get_uniform_locations_cell(int program, CellUniforms *ans) {
ans->draw_bg_bitfield = get_uniform_location(program, "draw_bg_bitfield_0");
ans->sprite_decorations_map = get_uniform_location(program, "sprite_decorations_map_0");
ans->sprites = get_uniform_location(program, "sprites_0");
ans->text_contrast = get_uniform_location(program, "text_contrast_0");
ans->text_gamma_adjustment = get_uniform_location(program, "text_gamma_adjustment_0");
@ -83,10 +85,10 @@ static inline void get_uniform_locations_cell(int program, CellUniforms *ans) {
ans->CellRenderData.size = block_size(program, ans->CellRenderData.index);
ans->ColorTable.index = block_index(program, "block_ColorTable_0");
ans->ColorTable.size = block_size(program, ans->ColorTable.index);
ans->color_table = get_uniform_array_information(program, "block_ColorTable_0.color_table_0[0]");
ans->color_table = get_uniform_array_information(program, "color_table_0");
ans->GammaLUT.index = block_index(program, "block_GammaLUT_0");
ans->GammaLUT.size = block_size(program, ans->GammaLUT.index);
ans->gamma_lut = get_uniform_array_information(program, "block_GammaLUT_0.gamma_lut_0[0]");
ans->gamma_lut = get_uniform_array_information(program, "gamma_lut_0");
}
typedef struct GraphicsUniforms {

View file

@ -1,29 +0,0 @@
#pragma kitty_include_shader <alpha_blend.glsl>
#pragma kitty_include_shader <utils.glsl>
#define ALPHA_TYPE
uniform sampler2D image;
#ifdef ALPHA_MASK
uniform vec3 amask_fg;
uniform vec4 amask_bg_premult;
#else
uniform float extra_alpha;
#endif
in vec2 texcoord;
out vec4 output_color;
void main() {
vec4 color = texture(image, texcoord);
#ifdef ALPHA_MASK
color = vec4(amask_fg, color.r);
color = vec4_premul(color);
color = alpha_blend_premul(color, amask_bg_premult);
#else
color.a *= extra_alpha;
#if TEXTURE_IS_NOT_PREMULTIPLIED
color = vec4_premul(color);
#endif
#endif
output_color = color;
}

View file

@ -1,2 +0,0 @@
uniform vec4 src_rect, dest_rect;
#pragma kitty_include_shader <blit_common.glsl>

View file

@ -1,210 +0,0 @@
/*
HSLUV-GLSL v4.2
HSLUV is a human-friendly alternative to HSL. ( http://www.hsluv.org )
GLSL port by William Malo ( https://github.com/williammalo )
Put this code in your fragment shader.
*/
// stripped down and optimized (branchless) version
float divide(float num, float denom) {
return num / (abs(denom) + 1e-15) * sign(denom);
}
vec3 divide(vec3 num, vec3 denom) {
return num / (abs(denom) + 1e-15) * sign(denom);
}
vec3 hsluv_intersectLineLine(vec3 line1x, vec3 line1y, vec3 line2x, vec3 line2y) {
return (line1y - line2y) / (line2x - line1x);
}
vec3 hsluv_distanceFromPole(vec3 pointx,vec3 pointy) {
return sqrt(pointx*pointx + pointy*pointy);
}
vec3 hsluv_lengthOfRayUntilIntersect(float theta, vec3 x, vec3 y) {
vec3 len = divide(y, sin(theta) - x * cos(theta));
len = mix(len, vec3(1000.0), step(len, vec3(0.0)));
return len;
}
float hsluv_maxSafeChromaForL(float L){
mat3 m2 = mat3(
3.2409699419045214 ,-0.96924363628087983 , 0.055630079696993609,
-1.5373831775700935 , 1.8759675015077207 ,-0.20397695888897657 ,
-0.49861076029300328 , 0.041555057407175613, 1.0569715142428786
);
float sub0 = L + 16.0;
float sub1 = sub0 * sub0 * sub0 * .000000641;
float sub2 = mix(L / 903.2962962962963, sub1, step(0.0088564516790356308, sub1));
vec3 top1 = (284517.0 * m2[0] - 94839.0 * m2[2]) * sub2;
vec3 bottom = (632260.0 * m2[2] - 126452.0 * m2[1]) * sub2;
vec3 top2 = (838422.0 * m2[2] + 769860.0 * m2[1] + 731718.0 * m2[0]) * L * sub2;
vec3 bounds0x = top1 / bottom;
vec3 bounds0y = top2 / bottom;
vec3 bounds1x = top1 / (bottom+126452.0);
vec3 bounds1y = (top2-769860.0*L) / (bottom+126452.0);
vec3 xs0 = hsluv_intersectLineLine(bounds0x, bounds0y, -1.0/bounds0x, vec3(0.0) );
vec3 xs1 = hsluv_intersectLineLine(bounds1x, bounds1y, -1.0/bounds1x, vec3(0.0) );
vec3 lengths0 = hsluv_distanceFromPole( xs0, bounds0y + xs0 * bounds0x );
vec3 lengths1 = hsluv_distanceFromPole( xs1, bounds1y + xs1 * bounds1x );
return min(lengths0.r,
min(lengths1.r,
min(lengths0.g,
min(lengths1.g,
min(lengths0.b,
lengths1.b)))));
}
float hsluv_maxChromaForLH(float L, float H) {
float hrad = radians(H);
mat3 m2 = mat3(
3.2409699419045214 ,-0.96924363628087983 , 0.055630079696993609,
-1.5373831775700935 , 1.8759675015077207 ,-0.20397695888897657 ,
-0.49861076029300328 , 0.041555057407175613, 1.0569715142428786
);
float sub1 = pow(L + 16.0, 3.0) / 1560896.0;
float sub2 = mix(L / 903.2962962962963, sub1, step(0.0088564516790356308, sub1));
vec3 top1 = (284517.0 * m2[0] - 94839.0 * m2[2]) * sub2;
vec3 bottom = (632260.0 * m2[2] - 126452.0 * m2[1]) * sub2;
vec3 top2 = (838422.0 * m2[2] + 769860.0 * m2[1] + 731718.0 * m2[0]) * L * sub2;
vec3 bound0x = top1 / bottom;
vec3 bound0y = top2 / bottom;
vec3 bound1x = top1 / (bottom+126452.0);
vec3 bound1y = (top2-769860.0*L) / (bottom+126452.0);
vec3 lengths0 = hsluv_lengthOfRayUntilIntersect(hrad, bound0x, bound0y );
vec3 lengths1 = hsluv_lengthOfRayUntilIntersect(hrad, bound1x, bound1y );
return min(lengths0.r,
min(lengths1.r,
min(lengths0.g,
min(lengths1.g,
min(lengths0.b,
lengths1.b)))));
}
vec3 hsluv_fromLinear(vec3 c) {
return mix(c * 12.92, 1.055 * pow(max(c, vec3(0)), vec3(1.0 / 2.4)) - 0.055, step(0.0031308, c));
}
vec3 hsluv_toLinear(vec3 c) {
return mix(c / 12.92, pow(max((c + 0.055) / (1.0 + 0.055), vec3(0)), vec3(2.4)), step(0.04045, c));
}
float hsluv_yToL(float Y){
return mix(Y * 903.2962962962963, 116.0 * pow(max(Y, 0), 1.0 / 3.0) - 16.0, step(0.0088564516790356308, Y));
}
float hsluv_lToY(float L) {
return mix(L / 903.2962962962963, pow((max(L, 0) + 16.0) / 116.0, 3.0), step(8.0, L));
}
vec3 xyzToRgb(vec3 tuple) {
const mat3 m = mat3(
3.2409699419045214 ,-1.5373831775700935 ,-0.49861076029300328 ,
-0.96924363628087983 , 1.8759675015077207 , 0.041555057407175613,
0.055630079696993609,-0.20397695888897657, 1.0569715142428786 );
return hsluv_fromLinear(tuple*m);
}
vec3 rgbToXyz(vec3 tuple) {
const mat3 m = mat3(
0.41239079926595948 , 0.35758433938387796, 0.18048078840183429 ,
0.21263900587151036 , 0.71516867876775593, 0.072192315360733715,
0.019330818715591851, 0.11919477979462599, 0.95053215224966058
);
return hsluv_toLinear(tuple) * m;
}
vec3 xyzToLuv(vec3 tuple){
float X = tuple.x;
float Y = tuple.y;
float Z = tuple.z;
float L = hsluv_yToL(Y);
float div = 1. / max(dot(tuple, vec3(1, 15, 3)), 1e-15);
return vec3(
1.,
(52. * (X*div) - 2.57179),
(117.* (Y*div) - 6.08816)
) * L;
}
vec3 luvToXyz(vec3 tuple) {
float L = tuple.x;
float U = divide(tuple.y, 13.0 * L) + 0.19783000664283681;
float V = divide(tuple.z, 13.0 * L) + 0.468319994938791;
float Y = hsluv_lToY(L);
float X = 2.25 * U * Y / V;
float Z = (3./V - 5.)*Y - (X/3.);
return vec3(X, Y, Z);
}
vec3 luvToLch(vec3 tuple) {
float L = tuple.x;
float U = tuple.y;
float V = tuple.z;
float C = length(tuple.yz);
float H = degrees(atan(V,U));
H += 360.0 * step(H, 0.0);
return vec3(L, C, H);
}
vec3 lchToLuv(vec3 tuple) {
float hrad = radians(tuple.b);
return vec3(
tuple.r,
cos(hrad) * tuple.g,
sin(hrad) * tuple.g
);
}
vec3 hsluvToLch(vec3 tuple) {
tuple.g *= hsluv_maxChromaForLH(tuple.b, tuple.r) * .01;
return tuple.bgr;
}
vec3 lchToHsluv(vec3 tuple) {
tuple.g = divide(tuple.g, hsluv_maxChromaForLH(tuple.r, tuple.b) * .01);
return tuple.bgr;
}
vec3 lchToRgb(vec3 tuple) {
return xyzToRgb(luvToXyz(lchToLuv(tuple)));
}
vec3 rgbToLch(vec3 tuple) {
return luvToLch(xyzToLuv(rgbToXyz(tuple)));
}
vec3 hsluvToRgb(vec3 tuple) {
return lchToRgb(hsluvToLch(tuple));
}
vec3 rgbToHsluv(vec3 tuple) {
return lchToHsluv(rgbToLch(tuple));
}
vec3 luvToRgb(vec3 tuple){
return xyzToRgb(luvToXyz(tuple));
}

View file

@ -1,25 +0,0 @@
float srgb2linear(float x) {
// sRGB to linear conversion
float lower = x / 12.92;
float upper = pow((x + 0.055f) / 1.055f, 2.4f);
return mix(lower, upper, step(0.04045f, x));
}
float linear2srgb(float x) {
// Linear to sRGB conversion.
float lower = 12.92 * x;
float upper = 1.055 * pow(x, 1.0f / 2.4f) - 0.055f;
return mix(lower, upper, step(0.0031308f, x));
}
vec3 linear2srgb(vec3 x) {
vec3 lower = 12.92 * x;
vec3 upper = 1.055 * pow(x, vec3(1.0f / 2.4f)) - 0.055f;
return mix(lower, upper, step(0.0031308f, x));
}
vec3 srgb2linear(vec3 c) {
return vec3(srgb2linear(c.r), srgb2linear(c.g), srgb2linear(c.b));
}

View file

@ -58,7 +58,7 @@ from .options.types import Options
from .options.utils import DELETE_ENV_VAR
from .os_window_size import edge_spacing, initial_window_size_func
from .session import create_sessions, get_os_window_sizing_data
from .shaders.legacy import CompileError, load_shader_programs
from .shaders.slang import load_shader_programs
from .types import LayerShellConfig
from .utils import (
cleanup_ssh_control_masters,
@ -90,7 +90,7 @@ def set_custom_ibeam_cursor() -> None:
def load_all_shaders() -> None:
try:
load_shader_programs()
except CompileError as err:
except ValueError as err:
raise SystemExit(err)

View file

@ -1,41 +0,0 @@
#pragma kitty_include_shader <alpha_blend.glsl>
#pragma kitty_include_shader <utils.glsl>
in vec2 dimensions;
out vec4 output_color;
uniform vec4 rect;
uniform vec2 params;
uniform vec4 color;
uniform vec4 background_color;
// Signed distance function for a rounded rectangle
float rounded_rectangle_sdf(vec2 p, vec2 b, float r) {
// signed distance field
// first term is used for points outside the rectangle
vec2 q = abs(p) - b;
return length(max(q, 0.0)) + min(max(q.x, q.y), 0.0) - r;
}
void main() {
vec2 size = rect.ba, origin = rect.xy;
float thickness = params[0], corner_radius = params[1];
// Position must be relative to the center of the rectangle of (size) located at (origin)
vec2 position = gl_FragCoord.xy - size / 2.0 - origin;
// Calculate distance to rounded rectangle
float dist = rounded_rectangle_sdf(position, size*0.5 - corner_radius, corner_radius);
// The below is for a filled rounded rect
// float alpha = 1.0 - smoothstep(0.0, 1.0, dist);
// vec4 ans = color; ans.a *= alpha;
// output_color = alpha_blend(ans, background_color);
// The border is outer - inner rects
float outer_edge = -dist, inner_edge = outer_edge - thickness;
// Smooth borders (anti-alias)
const float step_size = 1.0; // controls how blurred the aliasing causes the rect to be
float alpha = smoothstep(-step_size, step_size, outer_edge) - smoothstep(-step_size, step_size, inner_edge);
vec4 ans = color; ans.a *= alpha;
// pre-multiplied output
output_color = alpha_blend(ans, background_color);
}

View file

@ -1,17 +0,0 @@
#define left 0
#define top 1
#define right 2
#define bottom 3
const ivec2 vertex_pos_map[4] = ivec2[4](
ivec2(right, top),
ivec2(right, bottom),
ivec2(left, bottom),
ivec2(left, top)
);
const vec4 dest_rect = vec4(-1, 1, 1, -1);
void main() {
ivec2 pos = vertex_pos_map[gl_VertexID];
gl_Position = vec4(dest_rect[pos.x], dest_rect[pos.y], 0, 1);
}

View file

@ -1,54 +0,0 @@
#pragma kitty_include_shader <linear2srgb.glsl>
uniform sampler2D image;
uniform vec2 src_size; // Source texture size in pixels
in vec2 texcoord;
out vec4 output_color;
void main() {
// The input texture contains sRGB colors with premultiplied alpha.
// We need to output unpremultiplied sRGB colors with proper downscaling.
// For proper downscaling, we need to:
// 1. Sample neighboring pixels
// 2. Convert from sRGB to linear (unpremultiplying first)
// 3. Average in linear space
// 4. Convert back to sRGB
// 5. Output unpremultiplied
// Calculate the texel size
vec2 texel_size = 1.0 / src_size;
// Sample a 2x2 grid for better quality downscaling
// This provides basic bilinear-like filtering in linear space
vec2 tc = texcoord;
vec4 s00 = texture(image, tc + vec2(-0.25, -0.25) * texel_size);
vec4 s10 = texture(image, tc + vec2( 0.25, -0.25) * texel_size);
vec4 s01 = texture(image, tc + vec2(-0.25, 0.25) * texel_size);
vec4 s11 = texture(image, tc + vec2( 0.25, 0.25) * texel_size);
// Unpremultiply and convert to linear for each sample
vec3 linear00 = s00.a > 0.0 ? srgb2linear(s00.rgb / s00.a) : vec3(0.0);
vec3 linear10 = s10.a > 0.0 ? srgb2linear(s10.rgb / s10.a) : vec3(0.0);
vec3 linear01 = s01.a > 0.0 ? srgb2linear(s01.rgb / s01.a) : vec3(0.0);
vec3 linear11 = s11.a > 0.0 ? srgb2linear(s11.rgb / s11.a) : vec3(0.0);
// Average the alpha values
float avg_alpha = (s00.a + s10.a + s01.a + s11.a) * 0.25;
// For proper downsampling with transparency, weight colors by their alpha
// This ensures partially transparent pixels contribute proportionally
vec3 weighted_sum = linear00 * s00.a + linear10 * s10.a + linear01 * s01.a + linear11 * s11.a;
float total_weight = s00.a + s10.a + s01.a + s11.a;
// Calculate the weighted average color in linear space
vec3 avg_linear = total_weight > 0.0 ? weighted_sum / total_weight : vec3(0.0);
// Convert back to sRGB
vec3 srgb_color = linear2srgb(avg_linear);
// Output unpremultiplied sRGB color
output_color = vec4(srgb_color, avg_alpha);
}

View file

@ -1,2 +0,0 @@
uniform vec4 src_rect, dest_rect;
#pragma kitty_include_shader <blit_common.glsl>

View file

@ -14,7 +14,7 @@
#include "text-cache.h"
#include "window_logo.h"
#include "srgb_gamma.h"
#include "uniforms_generated.h"
#include "glsl-uniforms.h"
#include "state.h"
enum {
@ -332,12 +332,13 @@ draw_rounded_rect(
// Cell {{{
enum { CELL_RENDER_DATA_BINDING_POINT = 0, COLOR_TABLE_BINDING_POINT = 1 };
enum { CELL_RENDER_DATA_BINDING_POINT = 0, COLOR_TABLE_BINDING_POINT = 1, GAMMA_LUT_BINDING_POINT = 2, BORDER_COLORS_BINDING_POINT = 3 };
enum { GAMMA_LUT_GLOBAL_BUFFER, BORDER_COLORS_GLOBAL_BUFFER };
// A VAO used only to hold buffers for UBOs that are shared amongst programs/windows,
// its vertex attribute/array facilities are unused.
static ssize_t shader_globals_vao_idx = -1;
typedef struct {
UniformBlock render_data;
UniformBlock color_table;
GLint color_table_stride;
CellUniforms uniforms;
} CellProgramLayout;
static CellProgramLayout cell_program_layouts[NUM_PROGRAMS];
@ -377,19 +378,29 @@ typedef struct BorderProgramLayout {
} BorderProgramLayout;
static BorderProgramLayout border_program_layout;
static void
write_float_array_to_ubo(void *dest, const GLfloat *src, size_t count, const ArrayInformation *ai) {
GLint stride = MAX((GLint)sizeof(GLfloat), ai->stride);
uint8_t *buf = (uint8_t*)dest + ai->offset;
for (size_t i = 0; i < count; i++, buf += stride) memcpy(buf, src + i, sizeof(GLfloat));
}
static void
write_uint_array_to_ubo(void *dest, const GLuint *src, size_t count, const ArrayInformation *ai) {
GLint stride = MAX((GLint)sizeof(GLuint), ai->stride);
uint8_t *buf = (uint8_t*)dest + ai->offset;
for (size_t i = 0; i < count; i++, buf += stride) memcpy(buf, src + i, sizeof(GLuint));
}
static void
init_cell_program(void) {
GLint gamma_lut_buf_size = 0;
for (int i = CELL_PROGRAM; i < CELL_PROGRAM_SENTINEL; i++) {
cell_program_layouts[i].render_data.index = block_index(i, "CellRenderData");
cell_program_layouts[i].render_data.size = block_size(i, cell_program_layouts[i].render_data.index);
cell_program_layouts[i].color_table.index = block_index(i, "ColorTable");
cell_program_layouts[i].color_table.size = block_size(i, cell_program_layouts[i].color_table.index);
cell_program_layouts[i].color_table_stride = get_uniform_information(i, "color_table[0]", GL_UNIFORM_ARRAY_STRIDE);
glUniformBlockBinding(program_id(i), cell_program_layouts[i].render_data.index, CELL_RENDER_DATA_BINDING_POINT);
glUniformBlockBinding(program_id(i), cell_program_layouts[i].color_table.index, COLOR_TABLE_BINDING_POINT);
get_uniform_locations_cell(i, &cell_program_layouts[i].uniforms);
bind_program(i);
glUniform1fv(cell_program_layouts[i].uniforms.gamma_lut, arraysz(srgb_lut), srgb_lut);
glUniformBlockBinding(program_id(i), cell_program_layouts[i].uniforms.CellRenderData.index, CELL_RENDER_DATA_BINDING_POINT);
glUniformBlockBinding(program_id(i), cell_program_layouts[i].uniforms.ColorTable.index, COLOR_TABLE_BINDING_POINT);
glUniformBlockBinding(program_id(i), cell_program_layouts[i].uniforms.GammaLUT.index, GAMMA_LUT_BINDING_POINT);
gamma_lut_buf_size = MAX(gamma_lut_buf_size, cell_program_layouts[i].uniforms.GammaLUT.size);
}
// Sanity check to ensure the attribute location binding worked
@ -407,9 +418,25 @@ init_cell_program(void) {
get_uniform_locations_blit(BLIT_PROGRAM, &blit_program_layout.uniforms);
get_uniform_locations_screenshot(SCREENSHOT_PROGRAM, &screenshot_program_layout.uniforms);
get_uniform_locations_rounded_rect(ROUNDED_RECT_PROGRAM, &rounded_rect_program_layout.uniforms);
bind_program(BORDERS_PROGRAM);
get_uniform_locations_border(BORDERS_PROGRAM, &border_program_layout.uniforms);
glUniform1fv(border_program_layout.uniforms.gamma_lut, arraysz(srgb_lut), srgb_lut);
glUniformBlockBinding(program_id(BORDERS_PROGRAM), border_program_layout.uniforms.Colors.index, BORDER_COLORS_BINDING_POINT);
glUniformBlockBinding(program_id(BORDERS_PROGRAM), border_program_layout.uniforms.GammaLUT.index, GAMMA_LUT_BINDING_POINT);
// The gamma LUT is a constant, shared amongst all the programs that use it via a single UBO.
if (shader_globals_vao_idx == -1) {
shader_globals_vao_idx = create_vao();
add_buffer_to_vao(shader_globals_vao_idx, GL_UNIFORM_BUFFER);
add_buffer_to_vao(shader_globals_vao_idx, GL_UNIFORM_BUFFER);
gamma_lut_buf_size = MAX(gamma_lut_buf_size, border_program_layout.uniforms.GammaLUT.size);
void *gamma_lut_buf = alloc_and_map_vao_buffer(shader_globals_vao_idx, gamma_lut_buf_size, GAMMA_LUT_GLOBAL_BUFFER, false);
const ArrayInformation *a = &cell_program_layouts[CELL_PROGRAM].uniforms.gamma_lut;
write_float_array_to_ubo(gamma_lut_buf, srgb_lut, arraysz(srgb_lut), a);
unmap_vao_buffer(shader_globals_vao_idx, GAMMA_LUT_GLOBAL_BUFFER);
// The border colors change on every draw, but are shared amongst all border VAOs (only one is ever drawn at a time)
alloc_vao_buffer(shader_globals_vao_idx, border_program_layout.uniforms.Colors.size, BORDER_COLORS_GLOBAL_BUFFER, GL_STREAM_DRAW);
bind_vao_uniform_buffer(shader_globals_vao_idx, GAMMA_LUT_GLOBAL_BUFFER, GAMMA_LUT_BINDING_POINT);
bind_vao_uniform_buffer(shader_globals_vao_idx, BORDER_COLORS_GLOBAL_BUFFER, BORDER_COLORS_BINDING_POINT);
}
}
#define CELL_BUFFERS enum { cell_data_buffer, selection_buffer, uniform_buffer, color_table_buffer };
@ -417,8 +444,9 @@ init_cell_program(void) {
ssize_t
create_cell_vao(void) {
ssize_t vao_idx = create_vao();
const struct CellUniforms *u = &cell_program_layouts[CELL_PROGRAM].uniforms;
#define A(name, size, dtype, offset, stride) \
add_attribute_to_vao(CELL_PROGRAM, vao_idx, #name, \
add_attribute_to_vao(vao_idx, u->name, \
/*size=*/size, /*dtype=*/dtype, /*stride=*/stride, /*offset=*/offset, /*divisor=*/1);
#define A1(name, size, dtype, offset) A(name, size, dtype, (void*)(offsetof(GPUCell, offset)), sizeof(GPUCell))
@ -430,24 +458,16 @@ create_cell_vao(void) {
A(is_selected, 1, GL_UNSIGNED_BYTE, NULL, 0);
size_t bufnum = add_buffer_to_vao(vao_idx, GL_UNIFORM_BUFFER);
alloc_vao_buffer(vao_idx, cell_program_layouts[CELL_PROGRAM].render_data.size, bufnum, GL_STREAM_DRAW);
alloc_vao_buffer(vao_idx, cell_program_layouts[CELL_PROGRAM].uniforms.CellRenderData.size, bufnum, GL_STREAM_DRAW);
size_t ctbufnum = add_buffer_to_vao(vao_idx, GL_UNIFORM_BUFFER);
alloc_vao_buffer(vao_idx, cell_program_layouts[CELL_PROGRAM].color_table.size, ctbufnum, GL_STATIC_DRAW);
alloc_vao_buffer(vao_idx, cell_program_layouts[CELL_PROGRAM].uniforms.ColorTable.size, ctbufnum, GL_STATIC_DRAW);
return vao_idx;
#undef A
#undef A1
}
ssize_t
create_graphics_vao(void) {
ssize_t vao_idx = create_vao();
add_buffer_to_vao(vao_idx, GL_ARRAY_BUFFER);
add_attribute_to_vao(GRAPHICS_PROGRAM, vao_idx, "src", 4, GL_FLOAT, 0, NULL, 0);
return vao_idx;
}
#define IS_SPECIAL_COLOR(name) (screen->color_profile->overridden.name.type == COLOR_IS_SPECIAL || (screen->color_profile->overridden.name.type == COLOR_NOT_SET && screen->color_profile->configured.name.type == COLOR_IS_SPECIAL))
static void
@ -486,11 +506,13 @@ cell_update_uniform_block(ssize_t vao_idx, Screen *screen, int uniform_buffer, i
// Send the uniform data
ColorProfile *cp = screen->paused_rendering.expires_at ? &screen->paused_rendering.color_profile : screen->color_profile;
if (cp->dirty || screen->reload_all_gpu_data) {
GLuint *ct_buf = (GLuint*)map_vao_buffer_for_write_only(vao_idx, color_table_buf, 0, cell_program_layouts[CELL_PROGRAM].color_table.size);
copy_color_table_to_buffer(cp, ct_buf, 0, cell_program_layouts[CELL_PROGRAM].color_table_stride / sizeof(GLuint));
const ArrayInformation *ai = &cell_program_layouts[CELL_PROGRAM].uniforms.color_table;
uint8_t *base = (uint8_t*)map_vao_buffer_for_write_only(vao_idx, color_table_buf, 0, cell_program_layouts[CELL_PROGRAM].uniforms.ColorTable.size);
GLuint *ct_buf = (GLuint*)(base + ai->offset);
copy_color_table_to_buffer(cp, ct_buf, 0, ai->stride / sizeof(GLuint));
unmap_vao_buffer(vao_idx, color_table_buf);
}
struct GPUCellRenderData *rd = (struct GPUCellRenderData*)map_vao_buffer_for_write_only(vao_idx, uniform_buffer, 0, cell_program_layouts[CELL_PROGRAM].render_data.size);
struct GPUCellRenderData *rd = (struct GPUCellRenderData*)map_vao_buffer_for_write_only(vao_idx, uniform_buffer, 0, cell_program_layouts[CELL_PROGRAM].uniforms.CellRenderData.size);
#define COLOR(name) colorprofile_to_color(cp, cp->overridden.name, cp->configured.name).rgb
rd->default_fg = COLOR(default_fg);
rd->highlight_fg = COLOR(highlight_fg); rd->highlight_bg = COLOR(highlight_bg);
@ -1467,9 +1489,10 @@ create_border_vao(void) {
ssize_t vao_idx = create_vao();
add_buffer_to_vao(vao_idx, GL_ARRAY_BUFFER);
add_attribute_to_vao(BORDERS_PROGRAM, vao_idx, "rect",
const BorderUniforms *u = &border_program_layout.uniforms;
add_attribute_to_vao(vao_idx, u->rect,
/*size=*/4, /*dtype=*/GL_FLOAT, /*stride=*/sizeof(BorderRect), /*offset=*/(void*)offsetof(BorderRect, left), /*divisor=*/1);
add_attribute_to_vao(BORDERS_PROGRAM, vao_idx, "rect_color",
add_attribute_to_vao(vao_idx, u->rect_color,
/*size=*/1, /*dtype=*/GL_UNSIGNED_INT, /*stride=*/sizeof(BorderRect), /*offset=*/(void*)(offsetof(BorderRect, color)), /*divisor=*/1);
return vao_idx;
@ -1494,7 +1517,9 @@ draw_borders(ssize_t vao_idx, unsigned int num_border_rects, BorderRect *rect_bu
OPT(bell_border_color), OPT(tab_bar_background), OPT(tab_bar_margin_color),
w->tab_bar_edge_color.left, w->tab_bar_edge_color.right
};
glUniform1uiv(border_program_layout.uniforms.colors, arraysz(colors), colors);
void *colors_buf = map_vao_buffer_for_write_only(shader_globals_vao_idx, BORDER_COLORS_GLOBAL_BUFFER, 0, border_program_layout.uniforms.Colors.size);
write_uint_array_to_ubo(colors_buf, colors, arraysz(colors), &border_program_layout.uniforms.colors);
unmap_vao_buffer(shader_globals_vao_idx, BORDER_COLORS_GLOBAL_BUFFER);
glUniform1f(border_program_layout.uniforms.background_opacity, background_opacity);
if (!w->needs_layers) glEnable(GL_FRAMEBUFFER_SRGB);
draw_quad(has_background_image, num_border_rects);

View file

@ -1,242 +0,0 @@
#!/usr/bin/env python
# License: GPLv3 Copyright: 2023, Kovid Goyal <kovid at kovidgoyal.net>
import re
from collections.abc import Callable, Iterator
from functools import lru_cache, partial
from itertools import count
from typing import Any, Literal, Optional
from kitty.constants import read_kitty_resource
from kitty.fast_data_types import (
BGIMAGE_PROGRAM,
BLINK,
BLIT_PROGRAM,
BORDERS_PROGRAM,
CELL_BG_PROGRAM,
CELL_FG_PROGRAM,
CELL_PROGRAM,
COLOR_IS_INDEX,
COLOR_IS_RGB,
COLOR_IS_SPECIAL,
COLOR_NOT_SET,
DECORATION,
DECORATION_MASK,
DIM,
GLSL_VERSION,
GRAPHICS_ALPHA_MASK_PROGRAM,
GRAPHICS_PREMULT_PROGRAM,
GRAPHICS_PROGRAM,
MARK,
MARK_MASK,
REVERSE,
ROUNDED_RECT_PROGRAM,
SCREENSHOT_PROGRAM,
STRIKETHROUGH,
TINT_PROGRAM,
TRAIL_PROGRAM,
compile_program,
get_options,
init_cell_program,
)
from kitty.options.types import Options, defaults
def identity(x: str) -> str:
return x
class CompileError(ValueError):
pass
class Program:
include_pat: Optional['re.Pattern[str]'] = None
filename_number_base: int = 7893000
def __init__(self, name: str, vertex_name: str = '', fragment_name: str = '') -> None:
self.name = name
self.filename_number_counter = count(self.filename_number_base + 1)
self.filename_map: dict[str, int] = {}
if Program.include_pat is None:
Program.include_pat = re.compile(r'^#pragma\s+kitty_include_shader\s+<(.+?)>', re.MULTILINE)
self.vertex_name = vertex_name or f'{name}_vertex.glsl'
self.fragment_name = fragment_name or f'{name}_fragment.glsl'
self.original_vertex_sources = tuple(self._load_sources(self.vertex_name, set()))
self.original_fragment_sources = tuple(self._load_sources(self.fragment_name, set()))
self.vertex_sources = self.original_vertex_sources
self.fragment_sources = self.original_fragment_sources
def _load_sources(self, name: str, seen: set[str], level: int = 0) -> Iterator[str]:
if level == 0:
yield f'#version {GLSL_VERSION}\n'
if name in seen:
return
seen.add(name)
self.filename_map[name] = fnum = next(self.filename_number_counter)
src = read_kitty_resource(name).decode('utf-8')
pos = 0
lnum = 0
assert Program.include_pat is not None
for m in Program.include_pat.finditer(src):
prefix = src[pos:m.start()]
if prefix:
yield f'\n#line {lnum} {fnum}\n{prefix}'
lnum += prefix.count('\n')
iname = m.group(1)
yield from self._load_sources(iname, seen, level+1)
pos = m.end()
if pos < len(src):
yield f'\n#line {lnum} {fnum}\n{src[pos:]}'
def apply_to_sources(self, vertex: Callable[[str], str] = identity, frag: Callable[[str], str] = identity) -> None:
self.vertex_sources = self.original_vertex_sources if vertex is identity else tuple(map(vertex, self.original_vertex_sources))
self.fragment_sources = self.original_fragment_sources if frag is identity else tuple(map(frag, self.original_fragment_sources))
def compile(self, program_id: int, allow_recompile: bool = False) -> None:
try:
compile_program(program_id, self.vertex_sources, self.fragment_sources, allow_recompile)
return
except ValueError as err:
lines = str(err).splitlines()
msg = []
pat = re.compile(r'\b(' + str(self.filename_number_base).replace('0', r'\d') + r')\b')
rmap = {str(v): k for k, v in self.filename_map.items()}
def sub(m: 're.Match[str]') -> str:
return rmap.get(m.group(1), m.group(1))
for line in lines:
msg.append(pat.sub(sub, line))
raise CompileError('\n'.join(msg))
@lru_cache(maxsize=64)
def program_for(name: str) -> Program:
return Program(name)
class MultiReplacer:
pat: Optional['re.Pattern[str]'] = None
def __init__(self, **replacements: Any):
self.replacements = {k: str(v) for k, v in replacements.items()}
if MultiReplacer.pat is None:
MultiReplacer.pat = re.compile(r'\{([A-Z_]+)\}')
def _sub(self, m: 're.Match[str]') -> str:
return self.replacements.get(m.group(1), m.group(1))
def __call__(self, src: str) -> str:
assert self.pat is not None
return self.pat.sub(self._sub, src)
null_replacer = MultiReplacer()
class LoadShaderPrograms:
text_fg_override_threshold: tuple[float, Literal['%', 'ratio']] = 0, '%'
text_old_gamma: bool = False
cell_program_replacer: MultiReplacer = null_replacer
opts: Options | None = None
compile_programs: bool = True
@property
def needs_recompile(self) -> bool:
opts = self.opts or get_options()
return (
bool(opts.text_fg_override_threshold[0]) != bool(self.text_fg_override_threshold[0]) or
opts.text_fg_override_threshold[1] != self.text_fg_override_threshold[1] or
(opts.text_composition_strategy == 'legacy') != self.text_old_gamma
)
def recompile_if_needed(self) -> None:
if self.needs_recompile:
self(allow_recompile=True)
def __call__(self, allow_recompile: bool = False) -> None:
opts = self.opts or get_options()
self.text_old_gamma = opts.text_composition_strategy == 'legacy'
self.text_fg_override_threshold = opts.text_fg_override_threshold
cell = program_for('cell')
if self.cell_program_replacer is null_replacer:
self.cell_program_replacer = MultiReplacer(
REVERSE_SHIFT=REVERSE,
STRIKE_SHIFT=STRIKETHROUGH,
DIM_SHIFT=DIM,
BLINK_SHIFT=BLINK,
DECORATION_SHIFT=DECORATION,
MARK_SHIFT=MARK,
MARK_MASK=MARK_MASK,
DECORATION_MASK=DECORATION_MASK,
COLOR_NOT_SET=COLOR_NOT_SET,
COLOR_IS_SPECIAL=COLOR_IS_SPECIAL,
COLOR_IS_INDEX=COLOR_IS_INDEX,
COLOR_IS_RGB=COLOR_IS_RGB,
)
def resolve_cell_defines(only_fg: int, only_bg: int, src: str) -> str:
algo = '1' if self.text_fg_override_threshold[1] == '%' else '2'
if not self.text_fg_override_threshold[0]:
algo = '0'
r = self.cell_program_replacer.replacements
r['ONLY_FOREGROUND'] = str(only_fg)
r['ONLY_BACKGROUND'] = str(only_bg)
r['FG_OVERRIDE_ALGO'] = algo
r['TEXT_NEW_GAMMA'] = '0' if self.text_old_gamma else '1'
return self.cell_program_replacer(src)
for prog, (only_fg, only_bg) in {
CELL_PROGRAM: (0, 0), CELL_FG_PROGRAM: (1, 0), CELL_BG_PROGRAM: (0, 1),
}.items():
fn = partial(resolve_cell_defines, only_fg, only_bg)
cell.apply_to_sources(vertex=fn, frag=fn)
if self.compile_programs:
cell.compile(prog, allow_recompile)
graphics = program_for('graphics')
def resolve_graphics_fragment_defines(which: str, is_premult: bool, f: str) -> str:
ans = f.replace('#define ALPHA_TYPE', f'#define {which}', 1)
return ans.replace('TEXTURE_IS_NOT_PREMULTIPLIED', '0' if is_premult else '1')
for p, (which, is_premult) in {
GRAPHICS_PROGRAM: ('IMAGE', False),
GRAPHICS_ALPHA_MASK_PROGRAM: ('ALPHA_MASK', False),
GRAPHICS_PREMULT_PROGRAM: ('IMAGE', True),
}.items():
graphics.apply_to_sources(frag=partial(resolve_graphics_fragment_defines, which, is_premult))
if self.compile_programs:
graphics.compile(p, allow_recompile)
if self.compile_programs:
program_for('bgimage').compile(BGIMAGE_PROGRAM, allow_recompile)
program_for('tint').compile(TINT_PROGRAM, allow_recompile)
program_for('trail').compile(TRAIL_PROGRAM, allow_recompile)
program_for('blit').compile(BLIT_PROGRAM, allow_recompile)
program_for('screenshot').compile(SCREENSHOT_PROGRAM, allow_recompile)
program_for('rounded_rect').compile(ROUNDED_RECT_PROGRAM, allow_recompile)
program_for('border').compile(BORDERS_PROGRAM, allow_recompile)
init_cell_program()
load_shader_programs = LoadShaderPrograms()
def dump_programs(dest_dir: str) -> None:
import os
load_shader_programs.compile_programs = False
load_shader_programs.opts = defaults
try:
load_shader_programs()
os.makedirs(dest_dir, exist_ok=True)
for name in 'border bgimage tint trail blit screenshot rounded_rect cell graphics'.split():
p = program_for(name)
with open(os.path.join(dest_dir, f'{name}.vert.glsl'), 'w') as f:
f.write('\n\n'.join(p.vertex_sources))
with open(os.path.join(dest_dir, f'{name}.frag.glsl'), 'w') as f:
f.write('\n\n'.join(p.fragment_sources))
finally:
load_shader_programs.compile_programs = True
load_shader_programs.opts = None

View file

@ -510,12 +510,14 @@ class GLSLMetadata:
uniform_structs: dict[str, dict[str, str]]
input_locations: dict[str, int]
uniform_struct_names: dict[str, str]
fragment_inputs: dict[int, str]
def __init__(self) -> None:
self.loose_uniforms = {}
self.uniform_structs = {}
self.input_locations = {}
self.uniform_struct_names = {}
self.fragment_inputs = {}
def merge(self, other: 'GLSLMetadata') -> None:
self.loose_uniforms.update(other.loose_uniforms)
@ -528,11 +530,24 @@ class GLSLMetadata:
'loose_uniforms': self.loose_uniforms,
'uniform_structs': self.uniform_structs,
'input_locations': self.input_locations,
'uniform_struct_names': self.uniform_struct_names}
'uniform_struct_names': self.uniform_struct_names,
'fragment_inputs': self.fragment_inputs,
}
@classmethod
def fromdict(cls, d: dict[str, Any]) -> 'GLSLMetadata':
ans = GLSLMetadata()
ans.loose_uniforms = d['loose_uniforms']
ans.uniform_structs = d['uniform_structs']
ans.input_locations = d['input_locations']
ans.uniform_struct_names = d['uniform_struct_names']
ans.fragment_inputs = d['fragment_inputs']
return ans
def fixup_opengl_code(glsl_code: str, path: str) -> tuple[str, GLSLMetadata]:
is_fragment_shader = 'frag' in os.path.basename(path).split('.')
def fixup_opengl_code(glsl_code: str, shader_name: str, existing_metadata: GLSLMetadata | None) -> tuple[str, GLSLMetadata]:
is_fragment_shader = existing_metadata is None
shader_name += '.frag.glsl' if is_fragment_shader else '.vert.glsl'
lines: list[str] = []
in_uniform_block = False
in_uniform_block_contents = False
@ -545,12 +560,33 @@ def fixup_opengl_code(glsl_code: str, path: str) -> tuple[str, GLSLMetadata]:
uniform_structs = {}
uniform_struct_names = {}
input_locations = {}
named_interface_blocks = set()
pipeline_io_vars: dict[int, str] = {}
replacements = {
'gl_VertexIndex': 'gl_VertexID',
'gl_BaseVertex': '0',
'gl_InstanceIndex': 'gl_InstanceID',
'gl_BaseInstance': '0',
}
fragment_inputs = {} if existing_metadata is None else existing_metadata.fragment_inputs.copy()
def register_pipeline_boundary_io(line: str, next_line: str) -> None:
m = re.search(r'location = (\d+)', line)
assert m is not None
name = next_line.split()[-1].rstrip(';')
location = int(m.group(1))
if existing_metadata is None:
if not next_line.startswith('out '):
pipeline_io_vars[location] = name
else:
with suppress(KeyError):
replacements[name] = fragment_inputs.pop(location)
def add_uniform_name(name: str, uniform_names: dict[str, str] = uniform_names) -> str:
name = name.rstrip(';')
uniform_name = name.rpartition('_')[0]
if uniform_name in uniform_names:
raise KeyError(f'The uniform name {uniform_name} is used with multiple suffixes in {path}')
raise KeyError(f'The uniform name {uniform_name} is used with multiple suffixes in {shader_name}')
if '[' in name:
name = name.partition('[')[0] + '[0]'
uniform_names[uniform_name] = name
@ -566,6 +602,8 @@ def fixup_opengl_code(glsl_code: str, path: str) -> tuple[str, GLSLMetadata]:
block_name = line.lstrip('}').rstrip(';').strip()
if uniform_block_is_struct:
uniform_structs[current_uniform_struct_name] = current_uniform_struct_members
named_interface_blocks.add(block_name)
line = '};'
else:
uniform_blocks[block_name] = current_uniform_names
line = '// ' + line
@ -591,9 +629,11 @@ def fixup_opengl_code(glsl_code: str, path: str) -> tuple[str, GLSLMetadata]:
line = '// ' + line
elif line.startswith('layout(binding ='):
line = '// ' + line
elif line.startswith('layout(location =') and (is_fragment_shader or next_line.startswith('out ')):
elif line.startswith('layout(location =') and (is_fragment_shader or next_line.startswith('out ')): # ))
register_pipeline_boundary_io(line, next_line)
line = '// ' + line
elif line.startswith('flat layout(location ='):
register_pipeline_boundary_io(line[len('flat '):], next_line)
line = 'flat'
elif line: # ))))
words = line.split()
@ -609,47 +649,66 @@ def fixup_opengl_code(glsl_code: str, path: str) -> tuple[str, GLSLMetadata]:
uniform_struct_names[current_uniform_struct_name] = words[-1]
else:
line = '// ' + line
elif words[0] == 'uniform' and len(words) > 2 and words[1].startswith('sampler'):
elif words[0] == 'uniform' and len(words) > 2 and words[1].removeprefix('u').removeprefix('i').startswith('sampler'):
add_uniform_name(words[2])
elif not is_fragment_shader and words[0] == 'in':
name = words[-1].rstrip(';')
input_locations[name.rpartition('_')[0]] = int(lines[-1].split()[-1].rstrip(')'))
lines.append(line)
if fragment_inputs:
raise ValueError(
f'Could not match vertex outputs to fragment inputs for shader: {shader_name}. Leftover fragment inputs: {", ".join(fragment_inputs.values())}')
ans = '\n'.join(lines)
for block_name, names in uniform_blocks.items():
for u in names:
u = u.partition('[')[0] # ]
ans = ans.replace(f'{block_name}.{u}', u)
ans = ans.replace('gl_VertexIndex', 'gl_VertexID')
ans = ans.replace('gl_BaseVertex', '0')
ans = ans.replace('gl_InstanceIndex', 'gl_InstanceID')
ans = ans.replace('gl_BaseInstance', '0')
replacements[f'{block_name}.{u}'] = u
for x in named_interface_blocks:
replacements[f'{x}.'] = ''
def sub(m: re.Match[str]) -> str:
return replacements[m.group(1)]
ans = re.sub(r'\b(' + '|'.join(re.escape(word) for word in replacements) + r')\b', sub, ans)
m = GLSLMetadata()
m.loose_uniforms = uniform_names
m.uniform_structs = uniform_structs
m.input_locations = input_locations
m.uniform_struct_names = uniform_struct_names
if is_fragment_shader:
m.fragment_inputs = pipeline_io_vars
return ans, m
def fixup_opengl_files(paths: Iterable[str], metadata_map: dict[str, GLSLMetadata]) -> None:
def shader_name_from_path(path: str) -> str:
parts = os.path.basename(path).split('.')
if parts[1] in ('vert', 'frag', 'glsl'):
return parts[0]
return '.'.join(parts[:2])
def fixup_opengl_files(paths: Iterable[str]) -> None:
' Convert the GLSL output of slangc to something that will work with OpenGL 3.1 '
for path in paths:
metadata_map: dict[str, GLSLMetadata] = {}
dest_dir = ''
for path in sorted(paths):
dest_dir = os.path.dirname(path)
with open(path, 'r') as f:
glsl_code = f.read()
shader_name = shader_name_from_path(path)
try:
fixed, metadata = fixup_opengl_code(glsl_code, path)
fixed, metadata = fixup_opengl_code(glsl_code, shader_name, metadata_map.get(shader_name))
except Exception:
os.unlink(path)
raise
parts = os.path.basename(path).split('.')
write_if_changed(path, fixed)
if len(parts) == 3:
name = parts[0]
if name in metadata_map:
metadata_map[name].merge(metadata)
else:
metadata_map[name] = metadata
if shader_name in metadata_map:
metadata_map[shader_name].merge(metadata)
else:
metadata_map[shader_name] = metadata
for name, gm in metadata_map.items():
with open(os.path.join(dest_dir, f'{name}.glsl.json'), 'w') as f:
f.write(json.dumps(gm.asdict()))
def write_if_changed(dest: str, text: str) -> None:
@ -661,10 +720,19 @@ def write_if_changed(dest: str, text: str) -> None:
f.write(text)
def write_glsl_header(metadata_map: dict[str, GLSLMetadata], dest: str = 'kitty/glsl-uniforms.h') -> None:
def write_glsl_header(dest_dir: str, dest: str = 'kitty/glsl-uniforms.h') -> None:
metadata_map = {}
for x in glob.glob(os.path.join(dest_dir, '*.glsl.json')):
shader_name = shader_name_from_path(x)
with open(x) as f:
d = json.load(f)
metadata_map[shader_name] = GLSLMetadata.fromdict(d)
lines = ['// generated by slang.py DO NOT EDIT', '#include "gl.h"', '']
a = lines.append
for name in sorted(metadata_map):
if '.' in name:
continue
m = metadata_map[name]
struct_name = name.capitalize() + 'Uniforms'
a('')
@ -694,11 +762,11 @@ def write_glsl_header(metadata_map: dict[str, GLSLMetadata], dest: str = 'kitty/
for u in sorted(m.uniform_structs):
a(f' ans->{u}.index = block_index(program, "{m.uniform_struct_names[u]}");')
a(f' ans->{u}.size = block_size(program, ans->{u}.index);')
block_name = m.uniform_struct_names[u]
for v in sorted(m.uniform_structs[u]):
vv = m.uniform_structs[u][v]
if ']' in vv:
a(f' ans->{v} = get_uniform_array_information(program, "{block_name}.{vv}");')
vv = vv.partition('[')[0] # ]
a(f' ans->{v} = get_uniform_array_information(program, "{vv}");')
a('}')
write_if_changed(dest, '\n'.join(lines))
# }}}
@ -773,12 +841,11 @@ def compile_builtin_shaders(build_dir: str, dest_dir: str, parallel_run: Paralle
glsl_commands = commands_to_compile_to_glsl(source_tree, build_dir, dest_dir, built_glsl_files)
# Now run all commands
parallel_run(chain(spirv_commands, glsl_commands))
metadata_map = {}
fixup_opengl_files(glob.glob(os.path.join(dest_dir, '*.glsl')), metadata_map=metadata_map)
fixup_opengl_files(built_glsl_files)
if shutil.which('glslangValidator'):
from kitty.shaders.validate_shaders import validation_command_for_file
parallel_run((True, f'Validating |{os.path.basename(x)}| ...', validation_command_for_file(x)) for x in built_glsl_files)
write_glsl_header(metadata_map)
write_glsl_header(dest_dir)
def main() -> None:

View file

@ -582,7 +582,6 @@ void os_window_regions(const OSWindow*, Region *main, Region *tab_bar);
bool drag_scroll(Window *, OSWindow*);
void draw_borders(ssize_t vao_idx, unsigned int num_border_rects, BorderRect *rect_buf, bool rect_data_is_dirty, color_type, unsigned int, bool, OSWindow *w);
ssize_t create_cell_vao(void);
ssize_t create_graphics_vao(void);
ssize_t create_border_vao(void);
bool send_cell_data_to_gpu(ssize_t, Screen *, OSWindow *);
void draw_cells(const WindowRenderData*, OSWindow *, bool, bool, bool, Window*);

View file

@ -1,6 +0,0 @@
uniform vec4 tint_color;
out vec4 color; // must be in linear space and pre-multiplied
void main() {
color = tint_color;
}

View file

@ -1,18 +0,0 @@
uniform vec4 edges;
void main() {
float left = edges[0];
float top = edges[1];
float right = edges[2];
float bottom = edges[3];
vec2 pos_map[] = vec2[4](
vec2(left, top),
vec2(left, bottom),
vec2(right, bottom),
vec2(right, top)
);
gl_Position = vec4(pos_map[gl_VertexID], 0, 1);
}

View file

@ -1,16 +0,0 @@
uniform vec2 cursor_edge_x;
uniform vec2 cursor_edge_y;
uniform vec3 trail_color;
uniform float trail_opacity;
in vec2 frag_pos;
out vec4 final_color;
void main() {
float opacity = trail_opacity;
// Dont render if fragment is within cursor area
float in_x = step(cursor_edge_x[0], frag_pos.x) * step(frag_pos.x, cursor_edge_x[1]);
float in_y = step(cursor_edge_y[1], frag_pos.y) * step(frag_pos.y, cursor_edge_y[0]);
opacity *= 1.0f - in_x * in_y;
final_color = vec4(trail_color * opacity, opacity);
}

View file

@ -1,10 +0,0 @@
uniform vec4 x_coords;
uniform vec4 y_coords;
out vec2 frag_pos;
void main() {
vec2 pos = vec2(x_coords[gl_VertexID], y_coords[gl_VertexID]);
gl_Position = vec4(pos, 1.0, 1.0);
frag_pos = pos;
}

View file

@ -1,14 +0,0 @@
// Return 0 if x < 1 otherwise 1
#define zero_or_one(x) step(1.f, x)
// condition must be zero or one. When 1 thenval is returned otherwise elseval
#define if_one_then(condition, thenval, elseval) mix(elseval, thenval, condition)
// a < b ? thenval : elseval
#define if_less_than(a, b, thenval, elseval) mix(thenval, elseval, step(b, a))
vec4 vec4_premul(vec3 rgb, float a) {
return vec4(rgb * a, a);
}
vec4 vec4_premul(vec4 rgba) {
return vec4(rgba.rgb * rgba.a, rgba.a);
}

View file

@ -39,11 +39,6 @@ class TestBuild(BaseTest):
from kitty.shaders.slang import test_slang_build
test_slang_build()
def test_loading_shaders(self) -> None:
from kitty.shaders.legacy import Program
for name in 'cell border bgimage tint graphics'.split():
Program(name)
def test_macos_dictation_forwarding(self) -> None:
from kitty.constants import glfw_path, is_macos
if not is_macos or not shutil.which('clang'):

View file

@ -1361,56 +1361,6 @@ def build_cli_parser_specs(skip_generation: bool = False) -> str:
return dest
def build_uniforms_header(skip_generation: bool = False) -> str:
dest = 'kitty/uniforms_generated.h'
if skip_generation:
return dest
lines: list[str] = []
a = lines.append
uniform_names: Dict[str, Tuple[str, ...]] = {}
class_names = {}
function_names = {}
def find_uniform_names(raw: str) -> Iterator[str]:
for m in re.finditer(r'^uniform\s+\S+\s+(.+?);', raw, flags=re.MULTILINE):
for x in m.group(1).split(','):
yield x.strip().partition('[')[0]
for x in sorted(glob.glob('kitty/*.glsl')):
name = os.path.basename(x).partition('.')[0]
name, sep, shader_type = name.rpartition('_')
if not sep or shader_type not in ('fragment', 'vertex'):
continue
class_names[name] = f'{name.capitalize()}Uniforms'
function_names[name] = f'get_uniform_locations_{name}'
with open(x) as f:
raw = f.read()
uniform_names[name] = uniform_names.setdefault(name, ()) + tuple(find_uniform_names(raw))
for name in sorted(class_names):
class_name, function_name, uniforms = class_names[name], function_names[name], uniform_names[name]
a(f'typedef struct {class_name} ''{')
for n in uniforms:
a(f' int {n};')
a('}'f' {class_name};')
a('')
a(f'static inline void\n{function_name}(int program, {class_name} *ans) ''{')
for n in uniforms:
a(f' ans->{n} = get_uniform_location(program, "{n}");')
a('}')
a('')
# }]]]))
src = '\n'.join(lines)
try:
with open(dest) as f:
current = f.read()
except FileNotFoundError:
current = ''
if src != current:
with open(dest, 'w') as f:
f.write(src)
return dest
@lru_cache
def wrapped_kittens() -> str:
with open('shell-integration/ssh/kitty') as f:
@ -1446,7 +1396,6 @@ def build(args: Options, native_optimizations: bool = True, call_init: bool = Tr
sources, headers = find_c_files()
headers.append(build_ref_map(args.skip_code_generation))
headers.append(build_cli_parser_specs(args.skip_code_generation))
headers.append(build_uniforms_header(args.skip_code_generation))
compile_c_extension(
kitty_env(args), 'kitty/fast_data_types', args.compilation_database, sources, headers,
build_dsym=args.build_dsym,