diff --git a/kitty/cell_fragment.glsl b/kitty/cell_fragment.glsl index 02a1ac1fc..f58106dea 100644 --- a/kitty/cell_fragment.glsl +++ b/kitty/cell_fragment.glsl @@ -1,6 +1,23 @@ #version GLSL_VERSION #define WHICH_PROGRAM -#if defined(FOREGROUND) || defined(ALL) +#define NOT_TRANSPARENT + +#if defined(SIMPLE) || defined(BACKGROUND) || defined(SPECIAL) +#define NEEDS_BACKROUND +#endif + +#if defined(SIMPLE) || defined(FOREGROUND) +#define NEEDS_FOREGROUND +#endif + +#ifdef NEEDS_BACKROUND +in vec3 background; +#ifdef TRANSPARENT +in float bg_alpha; +#endif +#endif + +#ifdef NEEDS_FOREGROUND uniform sampler2DArray sprites; in vec3 sprite_pos; in vec3 underline_pos; @@ -8,13 +25,10 @@ in vec3 strike_pos; in vec3 foreground; in vec3 decoration_fg; #endif -in vec3 background; -#ifdef SPECIAL -in vec4 special_bg; -#endif out vec4 final_color; +// Util functions {{{ vec4 alpha_blend(vec3 over, float over_alpha, vec3 under, float under_alpha) { // Alpha blend two colors returning the resulting color pre-multiplied by its alpha // and its alpha. @@ -24,33 +38,51 @@ vec4 alpha_blend(vec3 over, float over_alpha, vec3 under, float under_alpha) { return vec4(combined_color, alpha); } -void main() { -#if defined(FOREGROUND) || defined(ALL) +vec3 premul_blend(vec3 over, float over_alpha, vec3 under) { + return over + (1.0f - over_alpha) * under; +} + +vec4 alpha_blend_premul(vec3 over, float over_alpha, vec3 under, float under_alpha) { + // Same as alpha_blend() except that it assumes over and under are both premultiplied. + float alpha = mix(under_alpha, 1.0f, over_alpha); + return vec4(premul_blend(over, over_alpha, under), alpha); +} +// }}} + +#ifdef NEEDS_FOREGROUND +vec4 calculate_foreground() { float text_alpha = texture(sprites, sprite_pos).r; float underline_alpha = texture(sprites, underline_pos).r; float strike_alpha = texture(sprites, strike_pos).r; // Since strike and text are the same color, we simply add the alpha values float combined_alpha = min(text_alpha + strike_alpha, 1.0f); // Underline color might be different, so alpha blend - vec4 fg = alpha_blend(decoration_fg, underline_alpha, foreground, combined_alpha); - -#ifdef ALL - // since background alpha is 1.0 and fg color is pre-multiplied by its alpha, - // we can simplify the alpha blend equation - final_color = vec4(fg.rgb + (1.0f - fg.a) * background, 1.0f); -#else - // FOREGROUND - // fg is pre-multipled so divide it by alpha - final_color = vec4(fg.rgb / fg.a, fg.a); + return alpha_blend(decoration_fg, underline_alpha, foreground, combined_alpha); +} #endif +void main() { +#if defined(BACKGROUND) || defined(SPECIAL) +#ifdef TRANSPARENT + final_color = vec4(background.rgb * bg_alpha, bg_alpha); +#else + final_color = vec4(background.rgb, 1.0f); +#endif + +#else + vec4 fg = calculate_foreground(); // pre-multiplied foreground +#ifdef FOREGROUND + final_color = fg; #else -#ifdef SPECIAL - final_color = special_bg; +#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, final_color.a); #else - // BACKGROUND - final_color = vec4(background, 1.0f); + // since background alpha is 1.0, it is effectively premultipled + final_color = vec4(premul_blend(fg.rgb, fg.a, background), 1.0f); + final_color = vec4(final_color.rgb / final_color.a, final_color.a); +#endif #endif #endif diff --git a/kitty/cell_vertex.glsl b/kitty/cell_vertex.glsl index d7ffb249b..c628f5ce9 100644 --- a/kitty/cell_vertex.glsl +++ b/kitty/cell_vertex.glsl @@ -1,7 +1,10 @@ #version GLSL_VERSION #define WHICH_PROGRAM +#define NOT_TRANSPARENT + +// Inputs {{{ layout(std140) uniform CellRenderData { - float xstart, ystart, dx, dy, sprite_dx, sprite_dy; + float xstart, ystart, dx, dy, sprite_dx, sprite_dy, background_opacity; uint default_fg, default_bg, highlight_fg, highlight_bg, cursor_color, url_color; @@ -17,26 +20,42 @@ layout(location=0) in uvec3 colors; layout(location=1) in uvec4 sprite_coords; layout(location=2) in float is_selected; -#if defined(FOREGROUND) || defined(ALL) + +const uvec2 cell_pos_map[] = uvec2[4]( + uvec2(1, 0), // right, top + uvec2(1, 1), // right, bottom + uvec2(0, 1), // left, bottom + uvec2(0, 0) // left, top +); +// }}} + + +#if defined(SIMPLE) || defined(BACKGROUND) || defined(SPECIAL) +#define NEEDS_BACKROUND +#endif + +#if defined(SIMPLE) || defined(FOREGROUND) +#define NEEDS_FOREGROUND +#endif + +#ifdef NEEDS_BACKROUND +out vec3 background; +#ifdef TRANSPARENT +out float bg_alpha; +#endif +#endif + +#ifdef NEEDS_FOREGROUND out vec3 sprite_pos; out vec3 underline_pos; out vec3 strike_pos; out vec3 foreground; out vec3 decoration_fg; #endif -out vec3 background; -#ifdef SPECIAL -out vec4 special_bg; -#endif -const uvec2 pos_map[] = uvec2[4]( - uvec2(1, 0), // right, top - uvec2(1, 1), // right, bottom - uvec2(0, 1), // left, bottom - uvec2(0, 0) // left, top -); +// Utility functions {{{ const uint BYTE_MASK = uint(0xFF); const uint SHORT_MASK = uint(0xFFFF); const uint ZERO = uint(0); @@ -83,7 +102,7 @@ vec3 to_sprite_pos(uvec2 pos, uint x, uint y, uint z) { } vec3 choose_color(float q, vec3 a, vec3 b) { - return q * a + (1.0 - q) * b; + return mix(b, a, q); } float in_range(uint x, uint y) { @@ -95,35 +114,44 @@ float is_cursor(uint x, uint y) { if (y == cursor_y && (x == cursor_x || x == cursor_w)) return 1.0; return 0.0; } +// }}} + void main() { - float cursor; - uint instance_id = uint(gl_InstanceID); - // The current cell being rendered - uint r = instance_id / xnum; - uint c = instance_id - r * xnum; - // The position of this vertex, at a corner of the cell - float left = xstart + c * dx; - float top = ystart - r * dy; - vec2 xpos = vec2(left, left + dx); - vec2 ypos = vec2(top, top - dy); - uvec2 pos = pos_map[gl_VertexID]; + // set cell vertex position {{{ + uint instance_id = uint(gl_InstanceID); + /* The current cell being rendered */ + uint r = instance_id / xnum; + uint c = instance_id - r * xnum; + + /* The position of this vertex, at a corner of the cell */ + float left = xstart + c * dx; + float top = ystart - r * dy; + vec2 xpos = vec2(left, left + dx); + vec2 ypos = vec2(top, top - dy); + uvec2 pos = cell_pos_map[gl_VertexID]; gl_Position = vec4(xpos[pos.x], ypos[pos.y], 0, 1); - uvec2 default_colors = uvec2(default_fg, default_bg); - ivec2 color_indices = ivec2(color1, color2); + // }}} + + // set cell color indices {{{ + uvec2 default_colors = uvec2(default_fg, default_bg); + ivec2 color_indices = ivec2(color1, color2); + uint text_attrs = sprite_coords[3]; + int fg_index = color_indices[(text_attrs >> 6) & REVERSE_MASK]; + int bg_index = color_indices[1 - fg_index]; + float cursor = is_cursor(c, r); + vec3 bg = to_color(colors[bg_index], default_colors[bg_index]); + // }}} - uint text_attrs = sprite_coords[3]; - int fg_index = color_indices[(text_attrs >> 6) & REVERSE_MASK]; - int bg_index = color_indices[1 - fg_index]; - background = to_color(colors[bg_index], default_colors[bg_index]); + // Foreground {{{ +#ifdef NEEDS_FOREGROUND -#if defined(FOREGROUND) || defined(ALL) // The character sprite being rendered sprite_pos = to_sprite_pos(pos, sprite_coords.x, sprite_coords.y, sprite_coords.z & SHORT_MASK); - // Foreground and background colors + // Foreground uint resolved_fg = resolve_color(colors[fg_index], default_colors[fg_index]); foreground = color_to_vec(resolved_fg); // Selection @@ -133,20 +161,31 @@ void main() { decoration_fg = choose_color(in_url, color_to_vec(url_color), to_color(colors[2], resolved_fg)); underline_pos = choose_color(in_url, to_sprite_pos(pos, TWO, ZERO, ZERO), to_sprite_pos(pos, (text_attrs >> 2) & DECORATION_MASK, ZERO, ZERO)); strike_pos = to_sprite_pos(pos, ((text_attrs >> 7) & STRIKE_MASK) * THREE, ZERO, ZERO); - // Block cursor rendering - cursor = is_cursor(c, r); - foreground = choose_color(cursor, background, foreground); - decoration_fg = choose_color(cursor, background, decoration_fg); + + // Cursor + foreground = choose_color(cursor, bg, foreground); + decoration_fg = choose_color(cursor, bg, decoration_fg); +#endif + // }}} + + // Background {{{ +#ifdef NEEDS_BACKROUND + +#if defined(BACKGROUND) + background = bg; +#else + // Selection and cursor + bg = choose_color(is_selected, color_to_vec(highlight_bg), bg); + background = choose_color(cursor, color_to_vec(cursor_color), bg); #endif -#if defined(ALL) - // Selection and cursor - background = choose_color(is_selected, color_to_vec(highlight_bg), background); - background = choose_color(cursor, color_to_vec(cursor_color), background); -#elif defined(SPECIAL) - cursor = is_cursor(c, r); - background = choose_color(is_selected, color_to_vec(highlight_bg), background); - background = choose_color(cursor, color_to_vec(cursor_color), background); - special_bg = vec4(background, max(cursor, is_selected)); +#ifdef TRANSPARENT + // If the background color is default, set its opacity to background_opacity, otherwise it should be opaque + bg_alpha = step(0.5, float(colors[bg_index] & BYTE_MASK)); + bg_alpha = bg_alpha + (1.0f - bg_alpha) * background_opacity; #endif + +#endif + // }}} + } diff --git a/kitty/glfw.c b/kitty/glfw.c index 509f313b2..cb09a2491 100644 --- a/kitty/glfw.c +++ b/kitty/glfw.c @@ -238,7 +238,7 @@ create_os_window(PyObject UNUSED *self, PyObject *args) { glfwSwapInterval(swap_interval); // a value of 1 makes mouse selection laggy if (is_first_window) { gl_init(); - PyObject *ret = PyObject_CallFunction(load_programs, NULL); + PyObject *ret = PyObject_CallFunction(load_programs, "i", glfwGetWindowAttrib(glfw_window, GLFW_TRANSPARENT_FRAMEBUFFER)); if (ret == NULL) return NULL; Py_DECREF(ret); #ifdef __APPLE__ diff --git a/kitty/kitty.conf b/kitty/kitty.conf index 591e47d79..2b61e517f 100644 --- a/kitty/kitty.conf +++ b/kitty/kitty.conf @@ -44,8 +44,15 @@ foreground #dddddd background #000000 # The opacity of the background. A number between 0 and 1, where 1 is opaque and 0 is fully transparent. -# Note that this will only work if supported by the OS (for instance, when using a compositor under X11). -# Be aware that using a value less than 1.0 is a performance hit. +# This will only work if supported by the OS (for instance, when using a compositor under X11). Note +# that it only sets the default background color's opacity. This is so that +# things like the status bar in vim, powerline prompts, etc. still look good. +# But it means that if you use a color theme with a background color in your +# editor, it will not be rendered as transparent. Instead you should change the +# default background color in your kitty config and not use a background color +# in the editor color scheme. Or use the escape codes to set the terminals +# default colors in a shell script to launch your editor. +# Be aware that using a value less than 1.0 is a (possibly significant) performance hit. background_opacity 1.0 # The foreground for selections diff --git a/kitty/main.py b/kitty/main.py index e03dac2a0..28d9504f2 100644 --- a/kitty/main.py +++ b/kitty/main.py @@ -26,8 +26,8 @@ from .utils import ( from .window import load_shader_programs -def load_all_shaders(): - load_shader_programs() +def load_all_shaders(semi_transparent=0): + load_shader_programs(semi_transparent) load_borders_program() diff --git a/kitty/shaders.c b/kitty/shaders.c index daf51038c..d45119da0 100644 --- a/kitty/shaders.c +++ b/kitty/shaders.c @@ -9,7 +9,7 @@ #include "fonts.h" #include -enum { CELL_PROGRAM, CELL_BACKGROUND_PROGRAM, CELL_SPECIAL_PROGRAM, CELL_FOREGROUND_PROGRAM, CURSOR_PROGRAM, BORDERS_PROGRAM, GRAPHICS_PROGRAM, NUM_PROGRAMS }; +enum { CELL_PROGRAM, CELL_BG_PROGRAM, CELL_SPECIAL_PROGRAM, CELL_FG_PROGRAM, CURSOR_PROGRAM, BORDERS_PROGRAM, GRAPHICS_PROGRAM, NUM_PROGRAMS }; enum { SPRITE_MAP_UNIT, GRAPHICS_UNIT }; // Sprites {{{ @@ -145,7 +145,7 @@ static CellProgramLayout cell_program_layouts[NUM_PROGRAMS]; static void init_cell_program() { - for (int i = CELL_PROGRAM; i <= CELL_FOREGROUND_PROGRAM; i++) { + for (int i = CELL_PROGRAM; i < CURSOR_PROGRAM; 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.size = get_uniform_information(i, "color_table[0]", GL_UNIFORM_SIZE); @@ -154,7 +154,7 @@ init_cell_program() { } // Sanity check to ensure the attribute location binding worked #define C(p, name, expected) { int aloc = attrib_location(p, #name); if (aloc != expected && aloc != -1) fatal("The attribute location for %s is %d != %d in program: %d", #name, aloc, expected, p); } - for (int p = CELL_PROGRAM; p <= CELL_FOREGROUND_PROGRAM; p++) { + for (int p = CELL_PROGRAM; p < CURSOR_PROGRAM; p++) { C(p, colors, 0); C(p, sprite_coords, 1); C(p, is_selected, 2); } #undef C @@ -197,7 +197,7 @@ create_graphics_vao() { static inline void cell_update_uniform_block(ssize_t vao_idx, Screen *screen, int uniform_buffer, GLfloat xstart, GLfloat ystart, GLfloat dx, GLfloat dy, CursorRenderInfo *cursor, bool inverted) { struct CellRenderData { - GLfloat xstart, ystart, dx, dy, sprite_dx, sprite_dy; + GLfloat xstart, ystart, dx, dy, sprite_dx, sprite_dy, background_opacity; GLuint default_fg, default_bg, highlight_fg, highlight_bg, cursor_color, url_color; @@ -228,6 +228,7 @@ cell_update_uniform_block(ssize_t vao_idx, Screen *screen, int uniform_buffer, G sprite_tracker_current_layout(&x, &y, &z); rd->sprite_dx = 1.0f / (float)x; rd->sprite_dy = 1.0f / (float)y; rd->color1 = inverted & 1; rd->color2 = 1 - (inverted & 1); + rd->background_opacity = OPT(background_opacity); #define COLOR(name) colorprofile_to_color(screen->color_profile, screen->color_profile->overridden.name, screen->color_profile->configured.name) rd->default_fg = COLOR(default_fg); rd->default_bg = COLOR(default_bg); rd->highlight_fg = COLOR(highlight_fg); rd->highlight_bg = COLOR(highlight_bg); @@ -312,21 +313,22 @@ draw_all_cells(ssize_t vao_idx, ssize_t gvao_idx, Screen *screen) { static void draw_cells_interleaved(ssize_t vao_idx, ssize_t gvao_idx, Screen *screen) { - bind_program(CELL_BACKGROUND_PROGRAM); - glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, screen->lines * screen->columns); - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - - if (screen->grman->num_of_negative_refs) draw_graphics(vao_idx, gvao_idx, screen->grman->render_data, 0, screen->grman->num_of_negative_refs); - - bind_program(CELL_SPECIAL_PROGRAM); - glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, screen->lines * screen->columns); - - bind_program(CELL_FOREGROUND_PROGRAM); - glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, screen->lines * screen->columns); - - if (screen->grman->num_of_positive_refs) draw_graphics(vao_idx, gvao_idx, screen->grman->render_data, screen->grman->num_of_negative_refs, screen->grman->num_of_positive_refs); - glDisable(GL_BLEND); + (void)vao_idx; (void)gvao_idx; (void)screen; + /* bind_program(CELL_BACKGROUND_PROGRAM); */ + /* glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, screen->lines * screen->columns); */ + /* glEnable(GL_BLEND); */ + /* glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); */ + /* */ + /* if (screen->grman->num_of_negative_refs) draw_graphics(vao_idx, gvao_idx, screen->grman->render_data, 0, screen->grman->num_of_negative_refs); */ + /* */ + /* bind_program(CELL_SPECIAL_PROGRAM); */ + /* glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, screen->lines * screen->columns); */ + /* */ + /* bind_program(CELL_FOREGROUND_PROGRAM); */ + /* glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, screen->lines * screen->columns); */ + /* */ + /* if (screen->grman->num_of_positive_refs) draw_graphics(vao_idx, gvao_idx, screen->grman->render_data, screen->grman->num_of_negative_refs, screen->grman->num_of_positive_refs); */ + /* glDisable(GL_BLEND); */ } void @@ -345,11 +347,10 @@ draw_cells(ssize_t vao_idx, ssize_t gvao_idx, GLfloat xstart, GLfloat ystart, GL if (!cell_constants_set) { bind_program(CELL_PROGRAM); glUniform1i(glGetUniformLocation(program_id(CELL_PROGRAM), "sprites"), SPRITE_MAP_UNIT); - bind_program(CELL_FOREGROUND_PROGRAM); - glUniform1i(glGetUniformLocation(program_id(CELL_FOREGROUND_PROGRAM), "sprites"), SPRITE_MAP_UNIT); cell_constants_set = true; } - if (screen->grman->num_of_negative_refs) draw_cells_interleaved(vao_idx, gvao_idx, screen); + bool needs_complex_rendering = screen->grman->num_of_negative_refs || (screen->grman->num_of_positive_refs && os_window->is_semi_transparent); + if (needs_complex_rendering) draw_cells_interleaved(vao_idx, gvao_idx, screen); else draw_all_cells(vao_idx, gvao_idx, screen); } // }}} @@ -526,7 +527,7 @@ static PyMethodDef module_methods[] = { bool init_shaders(PyObject *module) { #define C(x) if (PyModule_AddIntConstant(module, #x, x) != 0) { PyErr_NoMemory(); return false; } - C(CELL_PROGRAM); C(CELL_BACKGROUND_PROGRAM); C(CELL_SPECIAL_PROGRAM); C(CELL_FOREGROUND_PROGRAM); C(CURSOR_PROGRAM); C(BORDERS_PROGRAM); C(GRAPHICS_PROGRAM); + C(CELL_PROGRAM); C(CELL_BG_PROGRAM); C(CELL_SPECIAL_PROGRAM); C(CELL_FG_PROGRAM); C(CURSOR_PROGRAM); C(BORDERS_PROGRAM); C(GRAPHICS_PROGRAM); C(GLSL_VERSION); C(GL_VERSION); C(GL_VENDOR); diff --git a/kitty/window.py b/kitty/window.py index 6e53009b9..f3d26b5bb 100644 --- a/kitty/window.py +++ b/kitty/window.py @@ -12,13 +12,12 @@ from .constants import ( ScreenGeometry, WindowGeometry, appname, get_boss, wakeup ) from .fast_data_types import ( - BRACKETED_PASTE_END, BRACKETED_PASTE_START, CELL_BACKGROUND_PROGRAM, - CELL_FOREGROUND_PROGRAM, CELL_PROGRAM, CELL_SPECIAL_PROGRAM, - CURSOR_PROGRAM, GRAPHICS_PROGRAM, SCROLL_FULL, SCROLL_LINE, SCROLL_PAGE, - Screen, add_window, compile_program, glfw_post_empty_event, - init_cell_program, init_cursor_program, set_clipboard_string, - set_window_render_data, update_window_title, update_window_visibility, - viewport_for_window + BRACKETED_PASTE_END, BRACKETED_PASTE_START, CELL_BG_PROGRAM, + CELL_FG_PROGRAM, CELL_PROGRAM, CELL_SPECIAL_PROGRAM, CURSOR_PROGRAM, + GRAPHICS_PROGRAM, SCROLL_FULL, SCROLL_LINE, SCROLL_PAGE, Screen, + add_window, compile_program, glfw_post_empty_event, init_cell_program, + init_cursor_program, set_clipboard_string, set_window_render_data, + update_window_title, update_window_visibility, viewport_for_window ) from .keys import keyboard_mode_name from .rgb import to_color @@ -52,14 +51,19 @@ def calculate_gl_geometry(window_geometry, viewport_width, viewport_height, cell return ScreenGeometry(xstart, ystart, window_geometry.xnum, window_geometry.ynum, dx, dy) -def load_shader_programs(): +def load_shader_programs(semi_transparent=0): v, f = load_shaders('cell') compile_program(GRAPHICS_PROGRAM, *load_shaders('graphics')) for which, p in { - 'ALL': CELL_PROGRAM, 'BACKGROUND': CELL_BACKGROUND_PROGRAM, 'SPECIAL': CELL_SPECIAL_PROGRAM, - 'FOREGROUND': CELL_FOREGROUND_PROGRAM + 'SIMPLE': CELL_PROGRAM, + 'BACKGROUND': CELL_BG_PROGRAM, + 'SPECIAL': CELL_SPECIAL_PROGRAM, + 'FOREGROUND': CELL_FG_PROGRAM, }.items(): vv, ff = v.replace('WHICH_PROGRAM', which), f.replace('WHICH_PROGRAM', which) + if semi_transparent: + vv = vv.replace('#define NOT_TRANSPARENT', '#define TRANSPARENT') + ff = ff.replace('#define NOT_TRANSPARENT', '#define TRANSPARENT') compile_program(p, vv, ff) init_cell_program() compile_program(CURSOR_PROGRAM, *load_shaders('cursor'))