diff --git a/kitty/cell_defines.glsl b/kitty/cell_defines.glsl new file mode 100644 index 000000000..02580105e --- /dev/null +++ b/kitty/cell_defines.glsl @@ -0,0 +1,35 @@ +#define PHASE_BOTH 1 +#define PHASE_BACKGROUND 2 +#define PHASE_SPECIAL 3 +#define PHASE_FOREGROUND 4 + +#define PHASE {WHICH_PHASE} +#define HAS_TRANSPARENCY {TRANSPARENT} +#define FG_OVERRIDE {FG_OVERRIDE} +#define FG_OVERRIDE_THRESHOLD {FG_OVERRIDE_THRESHOLD} +#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 MARK_SHIFT {MARK_SHIFT} +#define MARK_MASK {MARK_MASK} +#define USE_SELECTION_FG +#define NUM_COLORS 256 + +#if (PHASE == PHASE_BOTH) || (PHASE == PHASE_BACKGROUND) || (PHASE == PHASE_SPECIAL) +#define NEEDS_BACKROUND +#endif + +#if (PHASE == PHASE_BOTH) || (PHASE == PHASE_FOREGROUND) +#define NEEDS_FOREGROUND +#endif + +#if (HAS_TRANSPARENCY == 1) +#define TRANSPARENT +#endif + +#if defined(TRANSPARENT) || (PHASE == PHASE_SPECIAL) +#define NEEDS_BG_ALPHA +#endif diff --git a/kitty/cell_fragment.glsl b/kitty/cell_fragment.glsl index 082de7637..c8044e65a 100644 --- a/kitty/cell_fragment.glsl +++ b/kitty/cell_fragment.glsl @@ -1,23 +1,11 @@ #pragma kitty_include_shader #pragma kitty_include_shader - -#define {WHICH_PROGRAM} -#define NOT_TRANSPARENT -#define NO_FG_OVERRIDE -#define TEXT_NEW_GAMMA - -#if defined(SIMPLE) || defined(BACKGROUND) || defined(SPECIAL) -#define NEEDS_BACKROUND -#endif - -#if defined(SIMPLE) || defined(FOREGROUND) -#define NEEDS_FOREGROUND -#endif +#pragma kitty_include_shader #ifdef NEEDS_BACKROUND in vec3 background; in float draw_bg; -#if defined(TRANSPARENT) || defined(SPECIAL) +#ifdef NEEDS_BG_ALPHA in float bg_alpha; #endif #endif @@ -53,8 +41,10 @@ vec4 vec4_premul(vec4 rgba) { /* * Explanation of rendering: - * There are a couple of cases, in order of increasing complexity: - * 1) Simple -- this path is used when there are either no images, or all images are + * There are two types of rendering, single pass and multi-pass. Multi-pass rendering is used when there + * are images that are below the foreground. Single pass rendering has PHASE=PHASE_BOTH. Otherwise, there + * are three passes, PHASE=PHASE_BACKGROUND, PHASE=PHASE_SPECIAL, PHASE=PHASE_FOREGROUND. + * 1) Single pass -- this path is used when there are either no images, or all images are * drawn on top of text and the background is opaque. In this case, there is a single pass, * of this shader with cell foreground and background colors blended directly. * Expected output is a color premultiplied by alpha, with an alpha specified as well. @@ -77,10 +67,9 @@ vec4 vec4_premul(vec4 rgba) { * 2b) Transparent bg with images * Same as (2a) except blending is done with PREMULT_BLEND and TRANSPARENT is defined in the shaders. background_opacity * is applied to default colored background cells in step (1). - * - * In this shader exactly *one* of SIMPLE, SPECIAL, FOREGROUND or BACKGROUND will be defined, corresponding - * to the appropriate rendering pass from above. */ + +// foreground functions {{{ #ifdef NEEDS_FOREGROUND // sRGB luminance values const vec3 Y = vec3(0.2126, 0.7152, 0.0722); @@ -92,12 +81,12 @@ float clamp_to_unit_float(float x) { return clamp(x, 0.0f, 1.0f); } -#ifdef FG_OVERRIDE +#if (FG_OVERRIDE == 1) vec3 fg_override(float under_luminance, float over_lumininace, 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); + 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)); } @@ -107,7 +96,7 @@ vec4 foreground_contrast(vec4 over, vec3 under) { float under_luminance = dot(under, Y); float over_lumininace = dot(over.rgb, Y); -#ifdef FG_OVERRIDE +#if (FG_OVERRIDE == 1) over.rgb = fg_override(under_luminance, over_lumininace, over.rgb); #endif @@ -117,12 +106,12 @@ vec4 foreground_contrast(vec4 over, vec3 under) { return over; } -#ifdef TEXT_OLD_GAMMA +#if (TEXT_NEW_GAMMA == 0) vec4 foreground_contrast_incorrect(vec4 over, vec3 under) { // Simulation of gamma-incorrect blending float under_luminance = dot(under, Y); float over_lumininace = dot(over.rgb, Y); -#ifdef FG_OVERRIDE +#if (FG_OVERRIDE == 1) over.rgb = fg_override(under_luminance, over_lumininace, over.rgb); #endif // This is the original gamma-incorrect rendering, it is the solution of the following equation: @@ -156,7 +145,7 @@ vec4 calculate_premul_foreground_from_sprites(vec4 text_fg) { 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 -#ifdef TEXT_OLD_GAMMA +#if (TEXT_NEW_GAMMA == 0) return foreground_contrast_incorrect(text_fg, bg); #else return foreground_contrast(text_fg, bg); @@ -164,6 +153,7 @@ vec4 adjust_foreground_contrast_with_background(vec4 text_fg, vec3 bg) { } #endif +// end foreground functions }}} float adjust_alpha_for_incorrect_blending_by_compositor(float text_fg_alpha, float final_alpha) { // Adjust the transparent alpha-channel to account for incorrect @@ -179,7 +169,7 @@ float adjust_alpha_for_incorrect_blending_by_compositor(float text_fg_alpha, flo } void main() { -#ifdef SIMPLE +#if (PHASE == PHASE_BOTH) 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); @@ -191,7 +181,7 @@ void main() { #endif #endif -#ifdef SPECIAL +#if (PHASE == PHASE_SPECIAL) #ifdef TRANSPARENT final_color = vec4_premul(background, bg_alpha); #else @@ -199,7 +189,7 @@ void main() { #endif #endif -#ifdef BACKGROUND +#if (PHASE == PHASE_BACKGROUND) #ifdef TRANSPARENT final_color = vec4_premul(background, bg_alpha); #else @@ -207,7 +197,7 @@ void main() { #endif #endif -#ifdef FOREGROUND +#if (PHASE == PHASE_FOREGROUND) vec4 text_fg = load_text_foreground_color(); vec4 text_fg_premul = calculate_premul_foreground_from_sprites(text_fg); final_color = text_fg_premul; diff --git a/kitty/cell_vertex.glsl b/kitty/cell_vertex.glsl index b0c64bb29..ba6f75376 100644 --- a/kitty/cell_vertex.glsl +++ b/kitty/cell_vertex.glsl @@ -1,16 +1,7 @@ #extension GL_ARB_explicit_attrib_location : require #pragma kitty_include_shader +#pragma kitty_include_shader -#define {WHICH_PROGRAM} -#define NOT_TRANSPARENT -#define DECORATION_SHIFT {DECORATION_SHIFT} -#define REVERSE_SHIFT {REVERSE_SHIFT} -#define STRIKE_SHIFT {STRIKE_SHIFT} -#define DIM_SHIFT {DIM_SHIFT} -#define MARK_SHIFT {MARK_SHIFT} -#define MARK_MASK {MARK_MASK} -#define USE_SELECTION_FG -#define NUM_COLORS 256 // Inputs {{{ layout(std140) uniform CellRenderData { @@ -23,7 +14,7 @@ layout(std140) uniform CellRenderData { uint color_table[NUM_COLORS + MARK_MASK + MARK_MASK + 2]; }; -#ifdef BACKGROUND +#if (PHASE == PHASE_BACKGROUND) uniform uint draw_bg_bitfield; #endif @@ -43,18 +34,10 @@ const uvec2 cell_pos_map[] = uvec2[4]( // }}} -#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; out float draw_bg; -#if defined(TRANSPARENT) || defined(SPECIAL) +#ifdef NEEDS_BG_ALPHA out float bg_alpha; #endif #endif @@ -217,7 +200,7 @@ void main() { float cell_has_non_default_bg = step(1, float(abs(bg_as_uint - default_colors[1]))); draw_bg = 1; -#if defined(BACKGROUND) +#if (PHASE == PHASE_BACKGROUND) background = bg; // draw_bg_bitfield has bit 0 set to draw default bg cells and bit 1 set to draw non-default bg cells uint draw_bg_mask = uint(2 * cell_has_non_default_bg + (1 - cell_has_non_default_bg)); @@ -230,21 +213,21 @@ void main() { // On other cells it should be 1. For the SPECIAL program it should be 1 on cells with // selections/block cursor and 0 everywhere else. float is_special_cell = cell_has_block_cursor + float(is_selected & ONE); -#ifndef SPECIAL +#if (PHASE != PHASE_SPECIAL) is_special_cell += cell_has_non_default_bg + float(is_reversed); #endif bg_alpha = step(0.5, is_special_cell); -#ifndef SPECIAL +#if (PHASE != PHASE_SPECIAL) bg_alpha = bg_alpha + (1.0f - bg_alpha) * background_opacity; bg_alpha *= draw_bg; #endif #endif -#if defined(SPECIAL) || defined(SIMPLE) +#if (PHASE == PHASE_SPECIAL) || (PHASE == PHASE_BOTH) // Selection and cursor bg = choose_color(float(is_selected & ONE), choose_color(use_cell_for_selection_bg, color_to_vec(fg_as_uint), color_to_vec(highlight_bg)), bg); background = choose_color(cell_has_block_cursor, color_to_vec(cursor_bg), bg); -#if !defined(TRANSPARENT) && defined(SPECIAL) +#if !defined(TRANSPARENT) && (PHASE == PHASE_SPECIAL) float is_special_cell = cell_has_block_cursor + float(is_selected & ONE); bg_alpha = step(0.5, is_special_cell); #endif diff --git a/kitty/shaders.py b/kitty/shaders.py index c22c1974c..7063110d1 100644 --- a/kitty/shaders.py +++ b/kitty/shaders.py @@ -4,7 +4,7 @@ import re from functools import lru_cache, partial from itertools import count -from typing import Any, Callable, Dict, Iterator, Optional +from typing import Any, Callable, Dict, Iterator, Optional, Set from .constants import read_kitty_resource from .fast_data_types import ( @@ -53,16 +53,17 @@ class Program: 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)) - self.original_fragment_sources = tuple(self._load_sources(self.fragment_name)) + 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, level: int = 0) -> Iterator[str]: + 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 self.filename_map: + 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 @@ -74,7 +75,7 @@ class Program: yield f'\n#line {lnum} {fnum}\n{prefix}' lnum += prefix.count('\n') iname = m.group(1) - yield from self._load_sources(iname, level+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:]}' @@ -161,32 +162,24 @@ class LoadShaderPrograms: STRIKE_SPRITE_INDEX=NUM_UNDERLINE_STYLES + 1, ) - def resolve_cell_vertex_defines(which: str, v: str) -> str: - self.cell_program_replacer.replacements['WHICH_PROGRAM'] = which - v = self.cell_program_replacer(v) - if semi_transparent: - v = v.replace('#define NOT_TRANSPARENT', '#define TRANSPARENT') - return v - - def resolve_cell_fragment_defines(which: str, f: str) -> str: - f = f.replace('{WHICH_PROGRAM}', which) - if self.text_fg_override_threshold != 0.: - f = f.replace('#define NO_FG_OVERRIDE', f'#define FG_OVERRIDE {self.text_fg_override_threshold}') - if self.text_old_gamma: - f = f.replace('#define TEXT_NEW_GAMMA', '#define TEXT_OLD_GAMMA') - if semi_transparent: - f = f.replace('#define NOT_TRANSPARENT', '#define TRANSPARENT') - return f + def resolve_cell_defines(which: str, src: str) -> str: + r = self.cell_program_replacer.replacements + r['WHICH_PHASE'] = f'PHASE_{which}' + r['TRANSPARENT'] = '1' if semi_transparent else '0' + r['FG_OVERRIDE_THRESHOLD'] = str(self.text_fg_override_threshold) + r['FG_OVERRIDE'] = '1' if self.text_fg_override_threshold != 0. else '0' + r['TEXT_NEW_GAMMA'] = '0' if self.text_old_gamma else '1' + return self.cell_program_replacer(src) for which, p in { - 'SIMPLE': CELL_PROGRAM, + 'BOTH': CELL_PROGRAM, 'BACKGROUND': CELL_BG_PROGRAM, 'SPECIAL': CELL_SPECIAL_PROGRAM, 'FOREGROUND': CELL_FG_PROGRAM, }.items(): cell.apply_to_sources( - vertex=partial(resolve_cell_vertex_defines, which), - frag=partial(resolve_cell_fragment_defines, which), + vertex=partial(resolve_cell_defines, which), + frag=partial(resolve_cell_defines, which), ) cell.compile(p, allow_recompile)