diff --git a/docs/changelog.rst b/docs/changelog.rst index 0ab978ca2..94ce71420 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -115,6 +115,9 @@ Detailed list of changes - macOS: Add the default :kbd:`Cmd+L` mapping from Terminal.app to erase the last command and its output (:disc:`6040`) +- Fix :opt:`background_opacity` being non-linear with light color themes + (:iss:`8869`) + - Wayland: Fix incorrect window size calculation when transitioning from full screen to non-full screen with client side decorations (:iss:`8826`) diff --git a/kitty/bgimage_fragment.glsl b/kitty/bgimage_fragment.glsl index 231b628e4..06fefdfd1 100644 --- a/kitty/bgimage_fragment.glsl +++ b/kitty/bgimage_fragment.glsl @@ -1,14 +1,11 @@ +#pragma kitty_include_shader uniform sampler2D image; -uniform float opacity; -uniform float premult; +uniform vec4 background; in vec2 texcoord; -out vec4 color; +out vec4 premult_color; void main() { - color = texture(image, texcoord); - float alpha = color.a * opacity; - vec4 premult_color = vec4(color.rgb * alpha, alpha); - color = vec4(color.rgb, alpha); - color = premult * premult_color + (1 - premult) * color; + vec4 color = texture(image, texcoord); + premult_color = alpha_blend(color, background); } diff --git a/kitty/bgimage_vertex.glsl b/kitty/bgimage_vertex.glsl index fb5ccd05e..2100e86e0 100644 --- a/kitty/bgimage_vertex.glsl +++ b/kitty/bgimage_vertex.glsl @@ -6,10 +6,6 @@ #define tex_top 0 #define tex_right 1 #define tex_bottom 1 -#define x_axis 0 -#define y_axis 1 -#define window i -#define image i + 2 uniform float tiled; uniform vec4 sizes; // [ window_width, window_height, image_width, image_height ] @@ -29,6 +25,8 @@ float scale_factor(float window_size, float image_size) { } float tiling_factor(int i) { +#define window i +#define image i + 2 return tiled * scale_factor(sizes[window], sizes[image]) + (1 - tiled); } @@ -40,6 +38,8 @@ void main() { 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) diff --git a/kitty/blit_common_vertex.glsl b/kitty/blit_common_vertex.glsl new file mode 100644 index 000000000..06ab1ffd2 --- /dev/null +++ b/kitty/blit_common_vertex.glsl @@ -0,0 +1,19 @@ +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); +} diff --git a/kitty/blit_fragment.glsl b/kitty/blit_fragment.glsl new file mode 100644 index 000000000..207bdd1d3 --- /dev/null +++ b/kitty/blit_fragment.glsl @@ -0,0 +1,13 @@ +#pragma kitty_include_shader +#pragma kitty_include_shader +#pragma kitty_include_shader + +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); +} diff --git a/kitty/blit_vertex.glsl b/kitty/blit_vertex.glsl new file mode 100644 index 000000000..57408a3b4 --- /dev/null +++ b/kitty/blit_vertex.glsl @@ -0,0 +1,2 @@ +uniform vec4 src_rect, dest_rect; +#pragma kitty_include_shader diff --git a/kitty/border_fragment.glsl b/kitty/border_fragment.glsl index b770f9f99..8327584de 100644 --- a/kitty/border_fragment.glsl +++ b/kitty/border_fragment.glsl @@ -1,6 +1,6 @@ -in vec4 color; -out vec4 final_color; +in vec4 color_premul; +out vec4 output_premul; void main() { - final_color = color; + output_premul = color_premul; } diff --git a/kitty/border_vertex.glsl b/kitty/border_vertex.glsl index 16fd540df..adee97510 100644 --- a/kitty/border_vertex.glsl +++ b/kitty/border_vertex.glsl @@ -1,11 +1,21 @@ -uniform uvec2 viewport; +#pragma kitty_include_shader + +#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 tint_opacity, tint_premult; uniform float gamma_lut[256]; + in vec4 rect; // left, top, right, bottom in uint rect_color; -out vec4 color; +out vec4 color_premul; // indices into the rect vector const int LEFT = 0; @@ -25,8 +35,8 @@ float to_color(uint c) { return gamma_lut[c & FF]; } -float is_integer_value(uint c, float x) { - return 1. - step(0.5, abs(float(c) - x)); +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) { @@ -39,14 +49,13 @@ void main() { 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, 3.); - float is_default_bg = is_integer_value(rc, 0.); - color3 = is_window_bg * window_bg + (1. - is_window_bg) * color3; - // Border must be always drawn opaque - float is_border_bg = 1. - step(0.5, abs((float(rc) - 2.) * (float(rc) - 1.) * (float(rc) - 4.))); // 1 if rc in (1, 2, 4) else 0 - float final_opacity = is_default_bg * tint_opacity + (1. - is_default_bg) * background_opacity; - final_opacity = is_border_bg + (1. - is_border_bg) * final_opacity; - float final_premult_opacity = is_default_bg * tint_premult + (1. - is_default_bg) * background_opacity; - final_premult_opacity = is_border_bg + (1. - is_border_bg) * final_premult_opacity; - color = vec4(color3 * final_premult_opacity, final_opacity); + 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 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 final_opacity = if_one_then(is_not_a_border, background_opacity, 1.); + color_premul = vec4_premul(color3, final_opacity); } diff --git a/kitty/borders.py b/kitty/borders.py index 3577c49b4..6f305621c 100644 --- a/kitty/borders.py +++ b/kitty/borders.py @@ -1,11 +1,12 @@ #!/usr/bin/env python # License: GPL v3 Copyright: 2016, Kovid Goyal -from collections.abc import Iterable, Sequence +from collections.abc import Iterable from enum import IntFlag +from functools import partial from typing import NamedTuple -from .fast_data_types import BORDERS_PROGRAM, add_borders_rect, get_options, init_borders_program, os_window_has_background_image +from .fast_data_types import BORDERS_PROGRAM, get_options, init_borders_program, set_borders_rects from .shaders import program_for from .typing_compat import LayoutType from .utils import color_as_int @@ -25,17 +26,17 @@ class Border(NamedTuple): color: BorderColor -def vertical_edge(os_window_id: int, tab_id: int, color: int, width: int, top: int, bottom: int, left: int) -> None: +def vertical_edge(rects: list[Border], color: BorderColor, width: int, top: int, bottom: int, left: int) -> None: if width > 0: - add_borders_rect(os_window_id, tab_id, left, top, left + width, bottom, color) + rects.append(Border(left, top, left + width, bottom, color)) -def horizontal_edge(os_window_id: int, tab_id: int, color: int, height: int, left: int, right: int, top: int) -> None: +def horizontal_edge(rects: list[Border], color: BorderColor, height: int, left: int, right: int, top: int) -> None: if height > 0: - add_borders_rect(os_window_id, tab_id, left, top, right, top + height, color) + rects.append(Border(left, top, right, top + height, color)) -def draw_edges(os_window_id: int, tab_id: int, colors: Sequence[int], wg: WindowGroup, borders: bool = False) -> None: +def add_borders(rects: list[Border], color: BorderColor, wg: WindowGroup) -> None: geometry = wg.geometry if geometry is None: return @@ -47,19 +48,20 @@ def draw_edges(os_window_id: int, tab_id: int, colors: Sequence[int], wg: Window right = lr + pr bt = geometry.bottom bottom = bt + pb - if borders: - width = wg.effective_border() - bt = bottom - lr = right - left -= width - top -= width - right += width - bottom += width - pl = pr = pb = pt = width - horizontal_edge(os_window_id, tab_id, colors[1], pt, left, right, top) - horizontal_edge(os_window_id, tab_id, colors[3], pb, left, right, bt) - vertical_edge(os_window_id, tab_id, colors[0], pl, top, bottom, left) - vertical_edge(os_window_id, tab_id, colors[2], pr, top, bottom, lr) + h = partial(horizontal_edge, rects, color) + v = partial(vertical_edge, rects, color) + width = wg.effective_border() + bt = bottom + lr = right + left -= width + top -= width + right += width + bottom += width + pl = pr = pb = pt = width + h(pt, left, right, top) + h(pb, left, right, bt) + v(pl, top, bottom, left) + v(pr, top, bottom, lr) def load_borders_program() -> None: @@ -83,13 +85,10 @@ class Borders: opts = get_options() draw_active_borders = opts.active_border_color is not None draw_minimal_borders = opts.draw_minimal_borders and max(opts.window_margin_width) < 1 - add_borders_rect(self.os_window_id, self.tab_id, 0, 0, 0, 0, BorderColor.default_bg) - has_background_image = os_window_has_background_image(self.os_window_id) - if not has_background_image or opts.background_tint > 0.0: - for br in current_layout.blank_rects: - add_borders_rect(self.os_window_id, self.tab_id, *br, BorderColor.default_bg) - for tbr in tab_bar_rects: - add_borders_rect(self.os_window_id, self.tab_id, *tbr) + rects: list[Border] = [] + for br in current_layout.blank_rects: + rects.append(Border(*br, BorderColor.default_bg)) + rects.extend(tab_bar_rects) bw = 0 groups = tuple(all_windows.iter_all_layoutable_groups(only_visible=True)) if groups: @@ -106,13 +105,9 @@ class Borders: color = BorderColor.active else: color = BorderColor.bell if wg.needs_attention else BorderColor.inactive - draw_edges(self.os_window_id, self.tab_id, (color, color, color, color), wg, borders=True) - if not has_background_image: - # Draw the background rectangles over the padding region - colors = window_bg, window_bg, window_bg, window_bg - draw_edges(self.os_window_id, self.tab_id, colors, wg) + add_borders(rects, color, wg) if draw_minimal_borders: for border_line in current_layout.get_minimal_borders(all_windows): - left, top, right, bottom = border_line.edges - add_borders_rect(self.os_window_id, self.tab_id, left, top, right, bottom, border_line.color) + rects.append(Border(*border_line.edges, border_line.color)) + set_borders_rects(self.os_window_id, self.tab_id, rects) diff --git a/kitty/boss.py b/kitty/boss.py index ab91614ac..9d168a6ef 100644 --- a/kitty/boss.py +++ b/kitty/boss.py @@ -144,7 +144,7 @@ from .utils import ( parse_uri_list, platform_window_id, safe_print, - sanitize_url_for_dispay_to_user, + sanitize_url_for_display_to_user, shlex_split, startup_notification_handler, timed_debug_print, @@ -916,7 +916,7 @@ class Boss: window.send_cmd_response(response) def mark_os_window_for_close(self, os_window_id: int, request_type: int = IMPERATIVE_CLOSE_REQUESTED) -> None: - if self.current_visual_select is not None and self.current_visual_select.os_window_id == os_window_id and request_type == IMPERATIVE_CLOSE_REQUESTED: + if self.current_visual_select is not None and self.current_visual_select.os_window_id == os_window_id: self.cancel_current_visual_select() mark_os_window_for_close(os_window_id, request_type) @@ -1504,6 +1504,7 @@ class Boss: if self.current_visual_select: self.current_visual_select.cancel() self.current_visual_select = None + self.mappings.pop_keyboard_mode_if_is('__visual_select__') def visual_window_select_action( self, tab: Tab, @@ -1827,6 +1828,8 @@ class Boss: if tm is None: self.mark_os_window_for_close(os_window_id) return + if self.current_visual_select is not None and self.current_visual_select.os_window_id == os_window_id: + self.cancel_current_visual_select() active_window = tm.active_window windows = [] for tab in tm: @@ -3160,8 +3163,8 @@ class Boss: pass mouse_discard_event = discard_event - def sanitize_url_for_dispay_to_user(self, url: str) -> str: - return sanitize_url_for_dispay_to_user(url) + def sanitize_url_for_display_to_user(self, url: str) -> str: + return sanitize_url_for_display_to_user(url) def on_system_color_scheme_change(self, appearance: ColorSchemes, is_initial_value: bool) -> None: theme_colors.on_system_color_scheme_change(appearance, is_initial_value) diff --git a/kitty/cell_defines.glsl b/kitty/cell_defines.glsl index e0aed3fb5..04f882c31 100644 --- a/kitty/cell_defines.glsl +++ b/kitty/cell_defines.glsl @@ -1,10 +1,3 @@ -#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 DO_FG_OVERRIDE {DO_FG_OVERRIDE} #define FG_OVERRIDE_THRESHOLD {FG_OVERRIDE_THRESHOLD} #define FG_OVERRIDE_ALGO {FG_OVERRIDE_ALGO} @@ -19,16 +12,12 @@ #define USE_SELECTION_FG #define NUM_COLORS 256 -#if (PHASE == PHASE_BOTH) || (PHASE == PHASE_BACKGROUND) || (PHASE == PHASE_SPECIAL) -#define NEEDS_BACKROUND +#if {ONLY_BACKGROUND} == 1 +#define ONLY_BACKGROUND #endif -#if (PHASE == PHASE_BOTH) || (PHASE == PHASE_FOREGROUND) -#define NEEDS_FOREGROUND -#endif - -#if (HAS_TRANSPARENCY == 1) -#define TRANSPARENT +#if {ONLY_FOREGROUND} == 1 +#define ONLY_FOREGROUND #endif // sRGB luminance values diff --git a/kitty/cell_fragment.glsl b/kitty/cell_fragment.glsl index 497bd98b1..88ba50b3d 100644 --- a/kitty/cell_fragment.glsl +++ b/kitty/cell_fragment.glsl @@ -1,15 +1,15 @@ #pragma kitty_include_shader #pragma kitty_include_shader #pragma kitty_include_shader +#pragma kitty_include_shader -in vec3 background; -in float draw_bg; -in float bg_alpha; - -#ifdef NEEDS_FOREGROUND -uniform sampler2DArray sprites; 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; @@ -24,49 +24,6 @@ in float colored_sprite; out vec4 output_color; -// Util functions {{{ -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); -} -// }}} - - -/* - * Explanation of rendering: - * 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. In this case, there is a single pass, - * of this shader with cell foreground and background colors blended directly. - * Expected output is either opaque colors or pre-multiplied colors. - * - * 2) Interleaved -- this path is used if background is not opaque and there are images or - * if the background is opaque but there are images under text. Rendering happens in - * multiple passes drawing the background and foreground separately and blending. - * - * 2a) Opaque bg with images under text - * There are multiple passes, each pass is blended onto the previous using the opaque blend func (alpha, 1- alpha). TRANSPARENT is not - * defined in the shaders. - * 1) Draw background for all cells - * 2) Draw the images that are supposed to be below both the background and text, if any. This happens in the graphics shader - * 3) Draw the background of cells that don't have the default background if any images were drawn in 2 above - * 4) Draw the images that are supposed to be below text but not background, again in graphics shader. - * 5) Draw the special cells (selection/cursor). Output is same as from step 1, with bg_alpha 1 for special cells and 0 otherwise - * 6) Draw the foreground -- expected output is color with alpha premultiplied which is blended using the premult blend func - * 7) Draw the images that are supposed to be above text again in the graphics shader - * - * 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). - */ - -// foreground functions {{{ -#ifdef NEEDS_FOREGROUND // Scaling factor for the extra text-alpha adjustment for luminance-difference. const float text_gamma_scaling = 0.5; @@ -75,6 +32,7 @@ float clamp_to_unit_float(float x) { 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); @@ -128,60 +86,26 @@ vec4 adjust_foreground_contrast_with_background(vec4 text_fg, vec3 bg) { // to improve legibility based on the source and destination colors return foreground_contrast(text_fg, bg); } +#endif // ifndef ONLY_BACKGROUND -#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 - // gamma-blending performed by the compositor (true for at least wlroots, picom) - // We have a linear alpha channel apply the sRGB curve to it once again to compensate - // for the incorrect blending in the compositor. - // We apply the correction only if there was actual text at this pixel, so as to not make - // background_opacity non-linear - // See https://github.com/kovidgoyal/kitty/issues/6209 for discussion. - // ans = text_fg_alpha * linear2srgb(final_alpha) + (1 - text_fg_alpha) * final_alpha - return mix(final_alpha, linear2srgb(final_alpha), text_fg_alpha); -} void main() { - vec4 final_color; -#if (PHASE == PHASE_BOTH) +#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 TRANSPARENT - final_color = alpha_blend_premul(text_fg_premul, vec4_premul(background, bg_alpha)); - final_color.a = adjust_alpha_for_incorrect_blending_by_compositor(text_fg_premul.a, final_color.a); +#ifdef ONLY_FOREGROUND + ans_premul = text_fg_premul; #else - final_color = alpha_blend_premul(text_fg_premul, background); + ans_premul = alpha_blend_premul(text_fg_premul, ans_premul); #endif -#endif - -#if (PHASE == PHASE_SPECIAL) -#ifdef TRANSPARENT - final_color = vec4_premul(background, bg_alpha); -#else - final_color = vec4(background, bg_alpha); -#endif -#endif - -#if (PHASE == PHASE_BACKGROUND) -#ifdef TRANSPARENT - final_color = vec4_premul(background, bg_alpha); -#else - final_color = vec4(background, draw_bg * bg_alpha); -#endif -#endif - -#if (PHASE == PHASE_FOREGROUND) - 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); - final_color = text_fg_premul; -#ifdef TRANSPARENT - final_color.a = adjust_alpha_for_incorrect_blending_by_compositor(text_fg_premul.a, final_color.a); -#endif -#endif - output_color = final_color; +#endif // ifndef ONLY_BACKGROUND + output_color = ans_premul; } diff --git a/kitty/cell_vertex.glsl b/kitty/cell_vertex.glsl index b36d1bb43..25a2fb794 100644 --- a/kitty/cell_vertex.glsl +++ b/kitty/cell_vertex.glsl @@ -1,16 +1,17 @@ #extension GL_ARB_explicit_attrib_location : require #pragma kitty_include_shader +#pragma kitty_include_shader // Inputs {{{ layout(std140) uniform CellRenderData { - float xstart, ystart, dx, dy, use_cell_bg_for_selection_fg, use_cell_fg_for_selection_fg, use_cell_for_selection_bg; + 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, cursor_fg, cursor_bg, url_color, url_style, inverted; - uint xnum, ynum, sprites_xnum, sprites_ynum, cursor_fg_sprite_idx, cell_height; + uint columns, lines, sprites_xnum, sprites_ynum, cursor_fg_sprite_idx, cell_width, cell_height; uint cursor_x1, cursor_x2, cursor_y1, cursor_y2; - float cursor_opacity; + float cursor_opacity, inactive_text_alpha, dim_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; @@ -18,12 +19,8 @@ layout(std140) uniform CellRenderData { uint color_table[NUM_COLORS + MARK_MASK + MARK_MASK + 2]; }; uniform float gamma_lut[256]; -#ifdef NEEDS_FOREGROUND -uniform usampler2D sprite_decorations_map; -#endif -#if (PHASE == PHASE_BACKGROUND) uniform uint draw_bg_bitfield; -#endif +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; @@ -41,12 +38,8 @@ const uvec2 cell_pos_map[] = uvec2[4]( out vec3 background; -out float draw_bg; -out float bg_alpha; - -#ifdef NEEDS_FOREGROUND -uniform float inactive_text_alpha; -uniform float dim_opacity; +out vec4 effective_background_premul; +#ifndef ONLY_BACKGROUND out vec3 sprite_pos; out vec3 underline_pos; out vec3 cursor_pos; @@ -100,7 +93,6 @@ vec3 to_color(uint c, uint defval) { return color_to_vec(resolve_color(c, defval)); } -#ifdef NEEDS_FOREGROUND uvec3 to_sprite_coords(uint idx) { uint sprites_per_page = sprites_xnum * sprites_ynum; @@ -144,16 +136,6 @@ uvec2 get_decorations_indices(uint in_url /* [0, 1] */, uint text_attrs) { uint has_underline = uint(step(0.5f, float(underline_style))); // [0, 1] return uvec2(strike_idx, has_underline * (decorations_idx + underline_style)); } -#endif - -vec3 choose_color(float q, vec3 a, vec3 b) { - return mix(b, a, q); -} - -float choose_alpha(float q, float a, float b) { - return mix(b, a, q); -} - float is_cursor(uint x, uint y) { uint clamped_x = clamp(x, cursor_x1, cursor_x2); @@ -169,24 +151,23 @@ struct CellData { CellData set_vertex_position() { uint instance_id = uint(gl_InstanceID); + float dx = 2.0 / float(columns); + float dy = 2.0 / float(lines); /* The current cell being rendered */ - uint r = instance_id / xnum; - uint c = instance_id - r * xnum; - + uint row = instance_id / columns; + uint column = instance_id - row * columns; /* 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); + float left = -1.0 + column * dx; + float top = 1.0 - row * dy; uvec2 pos = cell_pos_map[gl_VertexID]; - gl_Position = vec4(xpos[pos.x], ypos[pos.y], 0, 1); -#ifdef NEEDS_FOREGROUND + 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 float is_block_cursor = step(float(cursor_fg_sprite_idx), 0.5); - float has_cursor = is_cursor(c, r); + float has_cursor = is_cursor(column, row); return CellData(has_cursor, has_cursor * is_block_cursor, pos); } @@ -209,7 +190,7 @@ float calc_background_opacity(uint bg) { } // Overriding of foreground colors for contrast requirements {{{ -#if defined(NEEDS_FOREGROUND) && DO_FG_OVERRIDE == 1 +#if DO_FG_OVERRIDE == 1 #define OVERRIDE_FG_COLORS #pragma kitty_include_shader #if (FG_OVERRIDE_ALGO == 1) @@ -269,22 +250,22 @@ void main() { bg_as_uint = has_mark * color_table[NUM_COLORS + mark - 1] + (ONE - has_mark) * bg_as_uint; vec3 bg = color_to_vec(bg_as_uint); uint fg_as_uint = resolve_color(colors[fg_index], default_colors[fg_index]); + float cell_has_default_bg = 1.f - step(1.f, abs(float(bg_as_uint - bg_colors0))); // 1 if has default bg else 0 // }}} // Foreground {{{ -#ifdef NEEDS_FOREGROUND - // Foreground +#ifndef ONLY_BACKGROUND // background does not depend on foreground fg_as_uint = has_mark * color_table[NUM_COLORS + MARK_MASK + mark] + (ONE - has_mark) * fg_as_uint; foreground = color_to_vec(fg_as_uint); float has_dim = float((text_attrs >> DIM_SHIFT) & ONE); effective_text_alpha = inactive_text_alpha * mix(1.0, dim_opacity, has_dim); float in_url = float((is_selected & TWO) >> 1); - decoration_fg = choose_color(in_url, color_to_vec(url_color), to_color(colors[2], fg_as_uint)); + decoration_fg = if_one_then(in_url, color_to_vec(url_color), to_color(colors[2], fg_as_uint)); // Selection - vec3 selection_color = choose_color(use_cell_bg_for_selection_fg, bg, color_to_vec(highlight_fg)); - selection_color = choose_color(use_cell_fg_for_selection_fg, foreground, selection_color); - foreground = choose_color(float(is_selected & ONE), selection_color, foreground); - decoration_fg = choose_color(float(is_selected & ONE), selection_color, decoration_fg); + 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 & ONE), selection_color, foreground); + decoration_fg = if_one_then(float(is_selected & ONE), 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]); @@ -294,51 +275,43 @@ void main() { // Cursor cursor_color_premult = vec4(color_to_vec(cursor_bg) * cursor_opacity, cursor_opacity); vec3 final_cursor_text_color = mix(foreground, color_to_vec(cursor_fg), cursor_opacity); - foreground = choose_color(cell_data.has_block_cursor, final_cursor_text_color, foreground); - decoration_fg = choose_color(cell_data.has_block_cursor, final_cursor_text_color, decoration_fg); + 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, cursor_fg_sprite_idx * uint(cell_data.has_cursor)); #endif // }}} // Background {{{ - float orig_bg_alpha = 1; -#if PHASE == PHASE_BOTH && !defined(TRANSPARENT) // fast case single pass opaque background - bg_alpha = 1; - draw_bg = 1; -#else - bg_alpha = calc_background_opacity(bg_as_uint); - orig_bg_alpha = bg_alpha; -#if (PHASE == PHASE_BACKGROUND) - // 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 = step(1.f, abs(float(bg_as_uint - bg_colors0))); // 0 if has default bg else 1 - uint draw_bg_mask = uint(2.f * cell_has_non_default_bg + (1.f - cell_has_non_default_bg)); // 1 if has default bg else 2 - draw_bg = step(0.5, float(draw_bg_bitfield & draw_bg_mask)); -#else - draw_bg = 1; -#endif - + 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 & ONE); -#if PHASE == PHASE_SPECIAL - // Only special cells must be drawn and they must have bg_alpha 1 - bg_alpha = step(0.5, is_special_cell); // bg_alpha = 1 if is_special_cell else 0 -#else - is_special_cell += float(is_reversed); // bg_alpha should be 1 for reverse video cells as well - is_special_cell = step(0.5, is_special_cell); // is_special_cell = 1 if is_special_cell else 0 - bg_alpha = bg_alpha * (1. - float(is_special_cell)) + is_special_cell; // bg_alpha = 1 if is_special_cell else bg_alpha -#endif - bg_alpha *= draw_bg; -#endif // ends fast case #if else + 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 = 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_data.has_block_cursor, mix(bg, color_to_vec(cursor_bg), cursor_opacity), bg); - // we use max so that opacity of the block cursor cell background goes from orig_bg_alpha to 1 - float effective_cursor_opacity = max(cursor_opacity, orig_bg_alpha) * draw_bg; - bg_alpha = choose_alpha(cell_data.has_block_cursor, effective_cursor_opacity, bg_alpha); + bg_alpha = if_one_then(cell_data.has_block_cursor, effective_cursor_opacity, bg_alpha); + bg = if_one_then(float(is_selected & ONE), 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, color_to_vec(cursor_bg), cursor_opacity), bg); + background = background_rgb; // }}} -#ifdef OVERRIDE_FG_COLORS - decoration_fg = override_foreground_color(decoration_fg, background); - foreground = override_foreground_color(foreground, background); +#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 } diff --git a/kitty/child-monitor.c b/kitty/child-monitor.c index a259c74e6..253047cb3 100644 --- a/kitty/child-monitor.c +++ b/kitty/child-monitor.c @@ -10,7 +10,6 @@ #include "state.h" #include "threading.h" #include "screen.h" -#include "fonts.h" #include "monotonic.h" #include #include @@ -710,12 +709,15 @@ prepare_to_render_os_window(OSWindow *os_window, monotonic_t now, unsigned int * #define TD os_window->tab_bar_render_data bool needs_render = os_window->needs_render; os_window->needs_render = false; + os_window->needs_layers = os_window->is_semi_transparent || os_window->live_resize.in_progress || ( + os_window->bgimage && os_window->bgimage->texture_id > 0); if (TD.screen && os_window->num_tabs >= OPT(tab_bar_min_tabs)) { if (!os_window->tab_bar_data_updated) { call_boss(update_tab_bar_data, "K", os_window->id); os_window->tab_bar_data_updated = true; } - if (send_cell_data_to_gpu(TD.vao_idx, TD.xstart, TD.ystart, TD.dx, TD.dy, TD.screen, os_window)) needs_render = true; + if (send_cell_data_to_gpu(TD.vao_idx, TD.screen, os_window)) needs_render = true; + os_window->needs_layers = os_window->needs_layers || screen_needs_rendering_in_layers(os_window, NULL, TD.screen); } if (OPT(mouse_hide.hide_wait) > 0 && !is_mouse_hidden(os_window)) { if (now - os_window->last_mouse_activity_at >= OPT(mouse_hide.hide_wait)) hide_mouse(os_window); @@ -730,6 +732,7 @@ prepare_to_render_os_window(OSWindow *os_window, monotonic_t now, unsigned int * Window *w = tab->windows + i; #define WD w->render_data if (w->visible && WD.screen) { + os_window->needs_layers = os_window->needs_layers || screen_needs_rendering_in_layers(os_window, w, WD.screen); screen_check_pause_rendering(WD.screen, now); *num_visible_windows += 1; color_type window_bg = colorprofile_to_color(WD.screen->color_profile, WD.screen->color_profile->overridden.default_bg, WD.screen->color_profile->configured.default_bg).rgb; @@ -781,36 +784,21 @@ prepare_to_render_os_window(OSWindow *os_window, monotonic_t now, unsigned int * set_maximum_wait(min_gap); } } - if (send_cell_data_to_gpu(WD.vao_idx, WD.xstart, WD.ystart, WD.dx, WD.dy, WD.screen, os_window)) needs_render = true; + if (send_cell_data_to_gpu(WD.vao_idx, WD.screen, os_window)) needs_render = true; if (WD.screen->start_visual_bell_at != 0) needs_render = true; } } return needs_render; } -static void -draw_resizing_text(OSWindow *w) { - if (monotonic() - w->created_at > ms_to_monotonic_t(1000) && w->live_resize.num_of_resize_events > 1) { - char text[32] = {0}; - unsigned int width = w->live_resize.width, height = w->live_resize.height; - snprintf(text, sizeof(text), "%u x %u cells", width / w->fonts_data->fcm.cell_width, height / w->fonts_data->fcm.cell_height); - StringCanvas rendered = render_simple_text(w->fonts_data, text); - if (rendered.canvas) { - draw_centered_alpha_mask(w, width, height, rendered.width, rendered.height, rendered.canvas, OPT(background_opacity)); - free(rendered.canvas); - } - } -} - static void render_prepared_os_window(OSWindow *os_window, unsigned int active_window_id, color_type active_window_bg, unsigned int num_visible_windows, bool all_windows_have_same_bg) { - // ensure all pixels are cleared to background color at least once in every buffer - if (os_window->clear_count++ < 3) blank_os_window(os_window); + setup_os_window_for_rendering(os_window, true); Tab *tab = os_window->tabs + os_window->active_tab; BorderRects *br = &tab->border_rects; - draw_borders(br->vao_idx, br->num_border_rects, br->rect_buf, br->is_dirty, os_window->viewport_width, os_window->viewport_height, active_window_bg, num_visible_windows, all_windows_have_same_bg, os_window); + draw_borders(br->vao_idx, br->num_border_rects, br->rect_buf, br->is_dirty, active_window_bg, num_visible_windows, all_windows_have_same_bg, os_window); br->is_dirty = false; - if (TD.screen && os_window->num_tabs >= OPT(tab_bar_min_tabs)) draw_cells(TD.vao_idx, &TD, os_window, true, true, false, NULL); + if (TD.screen && os_window->num_tabs >= OPT(tab_bar_min_tabs)) draw_cells(&TD, os_window, true, true, false, NULL); unsigned int num_of_visible_windows = 0; Window *active_window = NULL; for (unsigned int i = 0; i < tab->num_windows; i++) { if (tab->windows[i].visible) num_of_visible_windows++; } @@ -819,13 +807,13 @@ render_prepared_os_window(OSWindow *os_window, unsigned int active_window_id, co if (w->visible && WD.screen) { bool is_active_window = i == tab->active_window; if (is_active_window) active_window = w; - draw_cells(WD.vao_idx, &WD, os_window, is_active_window, false, num_of_visible_windows == 1, w); + draw_cells(&WD, os_window, is_active_window, false, num_of_visible_windows == 1, w); if (WD.screen->start_visual_bell_at != 0) set_maximum_wait(ANIMATION_SAMPLE_WAIT); w->cursor_opacity_at_last_render = WD.screen->cursor_render_info.opacity; w->last_cursor_shape = WD.screen->cursor_render_info.shape; } } if (OPT(cursor_trail) && tab->cursor_trail.needs_render) draw_cursor_trail(&tab->cursor_trail, active_window); - if (os_window->live_resize.in_progress) draw_resizing_text(os_window); + setup_os_window_for_rendering(os_window, false); swap_window_buffers(os_window); os_window->last_active_tab = os_window->active_tab; os_window->last_num_tabs = os_window->num_tabs; os_window->last_active_window_id = active_window_id; os_window->focused_at_last_render = os_window->is_focused; @@ -866,11 +854,9 @@ render_os_window(OSWindow *w, monotonic_t now, bool scan_for_animated_images) { } w->render_calls++; make_os_window_context_current(w); - if (w->live_resize.in_progress) blank_os_window(w); bool needs_render = w->redraw_count > 0 || w->live_resize.in_progress; if (w->viewport_size_dirty) { - w->clear_count = 0; - update_surface_size(w->viewport_width, w->viewport_height, 0); + set_gpu_viewport(w->viewport_width, w->viewport_height); w->viewport_size_dirty = false; needs_render = true; } diff --git a/kitty/cursor_trail.c b/kitty/cursor_trail.c index 564d16800..6e32d9f34 100644 --- a/kitty/cursor_trail.c +++ b/kitty/cursor_trail.c @@ -1,6 +1,9 @@ #include #include "state.h" +#define WD w->render_data +#define EDGE(axis, index) ct->cursor_edge_##axis[index] + inline static float norm(float x, float y) { return sqrtf(x * x + y * y); @@ -8,32 +11,32 @@ norm(float x, float y) { static void update_cursor_trail_target(CursorTrail *ct, Window *w) { -#define EDGE(axis, index) ct->cursor_edge_##axis[index] -#define WD w->render_data + float dy = 2.f/WD.screen->lines, dx = 2.f/WD.screen->columns; float left = FLT_MAX, right = FLT_MAX, top = FLT_MAX, bottom = FLT_MAX; + static const float xstart = -1.f, ystart = 1.f; switch (WD.screen->cursor_render_info.shape) { case CURSOR_BLOCK: case CURSOR_HOLLOW: case CURSOR_BEAM: case CURSOR_UNDERLINE: - left = WD.xstart + WD.screen->cursor_render_info.x * WD.dx; - bottom = WD.ystart - (WD.screen->cursor_render_info.y + 1) * WD.dy; + left = xstart + WD.screen->cursor_render_info.x * dx; + bottom = ystart - (WD.screen->cursor_render_info.y + 1) * dy; default: break; } switch (WD.screen->cursor_render_info.shape) { case CURSOR_BLOCK: case CURSOR_HOLLOW: - right = left + WD.dx; - top = bottom + WD.dy; + right = left + dx; + top = bottom + dy; break; case CURSOR_BEAM: - right = left + WD.dx / WD.screen->cell_size.width * OPT(cursor_beam_thickness); - top = bottom + WD.dy; + right = left + dx / WD.screen->cell_size.width * OPT(cursor_beam_thickness); + top = bottom + dy; break; case CURSOR_UNDERLINE: - right = left + WD.dx; - top = bottom + WD.dy / WD.screen->cell_size.height * OPT(cursor_underline_thickness); + right = left + dx; + top = bottom + dy / WD.screen->cell_size.height * OPT(cursor_underline_thickness); break; default: break; @@ -53,8 +56,9 @@ should_skip_cursor_trail_update(CursorTrail *ct, Window *w, OSWindow *os_window) } if (OPT(cursor_trail_start_threshold) > 0 && !ct->needs_render) { - int dx = (int)round((ct->corner_x[0] - EDGE(x, 1)) / WD.dx); - int dy = (int)round((ct->corner_y[0] - EDGE(y, 0)) / WD.dy); + float fdy = 2.f/WD.screen->lines, fdx = 2.f/WD.screen->columns; + int dx = (int)round((ct->corner_x[0] - EDGE(x, 1)) / fdx); + int dy = (int)round((ct->corner_y[0] - EDGE(y, 0)) / fdy); if (abs(dx) + abs(dy) <= OPT(cursor_trail_start_threshold)) { return true; } @@ -140,10 +144,11 @@ static void update_cursor_trail_needs_render(CursorTrail *ct, Window *w) { static const int corner_index[2][4] = {{1, 1, 0, 0}, {0, 1, 1, 0}}; ct->needs_render = false; + float dy = 2.f/WD.screen->lines, dx = 2.f/WD.screen->columns; // check if any corner is still far from the cursor corner, so it should be rendered - const float dx_threshold = WD.dx / WD.screen->cell_size.width * 0.5f; - const float dy_threshold = WD.dy / WD.screen->cell_size.height * 0.5f; + const float dx_threshold = dx / WD.screen->cell_size.width * 0.5f; + const float dy_threshold = dy / WD.screen->cell_size.height * 0.5f; for (int i = 0; i < 4; ++i) { float dx = fabsf(EDGE(x, corner_index[0][i]) - ct->corner_x[i]); float dy = fabsf(EDGE(y, corner_index[1][i]) - ct->corner_y[i]); diff --git a/kitty/fast_data_types.pyi b/kitty/fast_data_types.pyi index 19f6af7ea..58615cd2b 100644 --- a/kitty/fast_data_types.pyi +++ b/kitty/fast_data_types.pyi @@ -1,6 +1,7 @@ import termios from typing import Any, Callable, Dict, Iterator, List, Literal, NewType, Optional, Tuple, TypedDict, Union, overload +from kitty.borders import Border from kitty.boss import Boss from kitty.fonts import VariableData from kitty.fonts.render import FontObject @@ -270,15 +271,15 @@ NO_CURSOR_SHAPE: int CURSOR_UNDERLINE: int DECAWM: int BGIMAGE_PROGRAM: int -CELL_BG_PROGRAM: int -CELL_FG_PROGRAM: int CELL_PROGRAM: int -CELL_SPECIAL_PROGRAM: int +CELL_FG_PROGRAM: int +CELL_BG_PROGRAM: int +BLIT_PROGRAM: int DECORATION: int DIM: int GRAPHICS_ALPHA_MASK_PROGRAM: int -GRAPHICS_PREMULT_PROGRAM: int GRAPHICS_PROGRAM: int +GRAPHICS_PREMULT_PROGRAM: int MARK: int MARK_MASK: int DECORATION_MASK: int @@ -545,19 +546,12 @@ def set_os_window_chrome(os_window_id: int) -> bool: pass -def add_borders_rect( - os_window_id: int, tab_id: int, left: int, top: int, right: int, - bottom: int, color: int -) -> None: - pass +def set_borders_rects(os_window_id: int, tab_id: int, rects: list[Border]) -> None: ... def init_borders_program() -> None: pass -def init_trail_program() -> None: - pass - def os_window_has_background_image(os_window_id: int) -> bool: pass @@ -607,7 +601,7 @@ def create_os_window( wm_class_name: str, wm_class_class: str, window_state: Optional[int] = WINDOW_NORMAL, - load_programs: Optional[Callable[[bool], None]] = None, + load_programs: Optional[Callable[[], None]] = None, x: Optional[int] = None, y: Optional[int] = None, disallow_override_title: bool = False, diff --git a/kitty/gl-wrapper.h b/kitty/gl-wrapper.h index ea5cc00e6..8d41a1852 100644 --- a/kitty/gl-wrapper.h +++ b/kitty/gl-wrapper.h @@ -1,5 +1,5 @@ /** - * Loader generated by glad 2.0.2 on Tue Dec 20 16:15:26 2022 + * Loader generated by glad 2.0.8 on Mon Aug 4 08:35:54 2025 * * SPDX-License-Identifier: (WTFPL OR CC0-1.0) AND Apache-2.0 * @@ -146,7 +146,7 @@ extern "C" { #define GLAD_MAKE_VERSION(major, minor) (major * 10000 + minor) #define GLAD_VERSION_MAJOR(version) (version / 10000) #define GLAD_VERSION_MINOR(version) (version % 10000) -#define GLAD_GENERATOR_VERSION "2.0.2" +#define GLAD_GENERATOR_VERSION "2.0.8" typedef void (*GLADapiproc)(void); typedef GLADapiproc (*GLADloadfunc)(const char *name); typedef GLADapiproc (*GLADuserptrloadfunc)(void *userptr, const char *name); @@ -9991,35 +9991,24 @@ static void glad_gl_load_GL_KHR_debug( GLADuserptrloadfunc load, void* userptr) glad_glPopDebugGroup = (PFNGLPOPDEBUGGROUPPROC) load(userptr, "glPopDebugGroup"); glad_glPushDebugGroup = (PFNGLPUSHDEBUGGROUPPROC) load(userptr, "glPushDebugGroup"); } -#if defined(GL_ES_VERSION_3_0) || defined(GL_VERSION_3_0) -#define GLAD_GL_IS_SOME_NEW_VERSION 1 -#else -#define GLAD_GL_IS_SOME_NEW_VERSION 0 -#endif -static int glad_gl_get_extensions( int version, const char **out_exts, unsigned int *out_num_exts_i, char ***out_exts_i) { -#if GLAD_GL_IS_SOME_NEW_VERSION - if(GLAD_VERSION_MAJOR(version) < 3) { -#else - GLAD_UNUSED(version); - GLAD_UNUSED(out_num_exts_i); - GLAD_UNUSED(out_exts_i); -#endif - if (glad_glGetString == NULL) { - return 0; +static void glad_gl_free_extensions(char **exts_i) { + if (exts_i != NULL) { + unsigned int index; + for(index = 0; exts_i[index]; index++) { + free((void *) (exts_i[index])); } - *out_exts = (const char *)glad_glGetString(GL_EXTENSIONS); -#if GLAD_GL_IS_SOME_NEW_VERSION - } else { + free((void *)exts_i); + exts_i = NULL; + } +} +static int glad_gl_get_extensions( const char **out_exts, char ***out_exts_i) { +#if defined(GL_ES_VERSION_3_0) || defined(GL_VERSION_3_0) + if (glad_glGetStringi != NULL && glad_glGetIntegerv != NULL) { unsigned int index = 0; unsigned int num_exts_i = 0; char **exts_i = NULL; - if (glad_glGetStringi == NULL || glad_glGetIntegerv == NULL) { - return 0; - } glad_glGetIntegerv(GL_NUM_EXTENSIONS, (int*) &num_exts_i); - if (num_exts_i > 0) { - exts_i = (char **) malloc(num_exts_i * (sizeof *exts_i)); - } + exts_i = (char **) malloc((num_exts_i + 1) * (sizeof *exts_i)); if (exts_i == NULL) { return 0; } @@ -10027,29 +10016,37 @@ static int glad_gl_get_extensions( int version, const char **out_exts, unsigned const char *gl_str_tmp = (const char*) glad_glGetStringi(GL_EXTENSIONS, index); size_t len = strlen(gl_str_tmp) + 1; char *local_str = (char*) malloc(len * sizeof(char)); - if(local_str != NULL) { - memcpy(local_str, gl_str_tmp, len * sizeof(char)); + if(local_str == NULL) { + exts_i[index] = NULL; + glad_gl_free_extensions(exts_i); + return 0; } + memcpy(local_str, gl_str_tmp, len * sizeof(char)); exts_i[index] = local_str; } - *out_num_exts_i = num_exts_i; + exts_i[index] = NULL; *out_exts_i = exts_i; + return 1; } +#else + GLAD_UNUSED(out_exts_i); #endif + if (glad_glGetString == NULL) { + return 0; + } + *out_exts = (const char *)glad_glGetString(GL_EXTENSIONS); return 1; } -static void glad_gl_free_extensions(char **exts_i, unsigned int num_exts_i) { - if (exts_i != NULL) { +static int glad_gl_has_extension(const char *exts, char **exts_i, const char *ext) { + if(exts_i) { unsigned int index; - for(index = 0; index < num_exts_i; index++) { - free((void *) (exts_i[index])); + for(index = 0; exts_i[index]; index++) { + const char *e = exts_i[index]; + if(strcmp(e, ext) == 0) { + return 1; + } } - free((void *)exts_i); - exts_i = NULL; - } -} -static int glad_gl_has_extension(int version, const char *exts, unsigned int num_exts_i, char **exts_i, const char *ext) { - if(GLAD_VERSION_MAJOR(version) < 3 || !GLAD_GL_IS_SOME_NEW_VERSION) { + } else { const char *extensions; const char *loc; const char *terminator; @@ -10069,32 +10066,23 @@ static int glad_gl_has_extension(int version, const char *exts, unsigned int num } extensions = terminator; } - } else { - unsigned int index; - for(index = 0; index < num_exts_i; index++) { - const char *e = exts_i[index]; - if(strcmp(e, ext) == 0) { - return 1; - } - } } return 0; } static GLADapiproc glad_gl_get_proc_from_userptr(void *userptr, const char* name) { return (GLAD_GNUC_EXTENSION (GLADapiproc (*)(const char *name)) userptr)(name); } -static int glad_gl_find_extensions_gl( int version) { +static int glad_gl_find_extensions_gl(void) { const char *exts = NULL; - unsigned int num_exts_i = 0; char **exts_i = NULL; - if (!glad_gl_get_extensions(version, &exts, &num_exts_i, &exts_i)) return 0; - GLAD_GL_ARB_copy_image = glad_gl_has_extension(version, exts, num_exts_i, exts_i, "GL_ARB_copy_image"); - GLAD_GL_ARB_instanced_arrays = glad_gl_has_extension(version, exts, num_exts_i, exts_i, "GL_ARB_instanced_arrays"); - GLAD_GL_ARB_multisample = glad_gl_has_extension(version, exts, num_exts_i, exts_i, "GL_ARB_multisample"); - GLAD_GL_ARB_robustness = glad_gl_has_extension(version, exts, num_exts_i, exts_i, "GL_ARB_robustness"); - GLAD_GL_ARB_texture_storage = glad_gl_has_extension(version, exts, num_exts_i, exts_i, "GL_ARB_texture_storage"); - GLAD_GL_KHR_debug = glad_gl_has_extension(version, exts, num_exts_i, exts_i, "GL_KHR_debug"); - glad_gl_free_extensions(exts_i, num_exts_i); + if (!glad_gl_get_extensions(&exts, &exts_i)) return 0; + GLAD_GL_ARB_copy_image = glad_gl_has_extension(exts, exts_i, "GL_ARB_copy_image"); + GLAD_GL_ARB_instanced_arrays = glad_gl_has_extension(exts, exts_i, "GL_ARB_instanced_arrays"); + GLAD_GL_ARB_multisample = glad_gl_has_extension(exts, exts_i, "GL_ARB_multisample"); + GLAD_GL_ARB_robustness = glad_gl_has_extension(exts, exts_i, "GL_ARB_robustness"); + GLAD_GL_ARB_texture_storage = glad_gl_has_extension(exts, exts_i, "GL_ARB_texture_storage"); + GLAD_GL_KHR_debug = glad_gl_has_extension(exts, exts_i, "GL_KHR_debug"); + glad_gl_free_extensions(exts_i); return 1; } static int glad_gl_find_core_gl(void) { @@ -10135,7 +10123,6 @@ int gladLoadGLUserPtr( GLADuserptrloadfunc load, void *userptr) { int version; glad_glGetString = (PFNGLGETSTRINGPROC) load(userptr, "glGetString"); if(glad_glGetString == NULL) return 0; - if(glad_glGetString(GL_VERSION) == NULL) return 0; version = glad_gl_find_core_gl(); glad_gl_load_GL_VERSION_1_0(load, userptr); glad_gl_load_GL_VERSION_1_1(load, userptr); @@ -10147,7 +10134,7 @@ int gladLoadGLUserPtr( GLADuserptrloadfunc load, void *userptr) { glad_gl_load_GL_VERSION_2_1(load, userptr); glad_gl_load_GL_VERSION_3_0(load, userptr); glad_gl_load_GL_VERSION_3_1(load, userptr); - if (!glad_gl_find_extensions_gl(version)) return 0; + if (!glad_gl_find_extensions_gl()) return 0; glad_gl_load_GL_ARB_copy_image(load, userptr); glad_gl_load_GL_ARB_instanced_arrays(load, userptr); glad_gl_load_GL_ARB_multisample(load, userptr); diff --git a/kitty/gl.c b/kitty/gl.c index 3193bf5cd..b1f025b1f 100644 --- a/kitty/gl.c +++ b/kitty/gl.c @@ -10,6 +10,7 @@ #include #include "glfw-wrapper.h" #include "state.h" +#include "png-reader.h" // GL setup and error handling {{{ static void @@ -76,15 +77,28 @@ gl_init(void) { } } -void -update_surface_size(int w, int h, GLuint offscreen_texture_id) { - glViewport(0, 0, w, h); - if (offscreen_texture_id) { - glBindTexture(GL_TEXTURE_2D, offscreen_texture_id); - glTexImage2D(GL_TEXTURE_2D, 0, GL_SRGB_ALPHA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); +static const char* +check_framebuffer_status(void) { + GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + switch (status) { + case GL_FRAMEBUFFER_COMPLETE: return NULL; + case GL_FRAMEBUFFER_UNDEFINED: return("GL_FRAMEBUFFER_UNDEFINED"); + case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: return("GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT"); + case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: return("GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT"); + case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER: return("GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER"); + case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER: return("GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER"); + case GL_FRAMEBUFFER_UNSUPPORTED: return("GL_FRAMEBUFFER_UNSUPPORTED"); + case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE: return("GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE"); + default: return("Unknown error"); } } +void +check_framebuffer_status_or_die(void) { + const char *err = check_framebuffer_status(); + if (err != NULL) fatal("Framebuffer not complete with error: %s", err); +} + void free_texture(GLuint *tex_id) { glDeleteTextures(1, tex_id); @@ -97,6 +111,102 @@ free_framebuffer(GLuint *fb_id) { *fb_id = 0; } +static GLuint output_framebuffer = 0; + +void +bind_framebuffer_for_output(unsigned fbid) { + glBindFramebuffer(GL_FRAMEBUFFER, fbid ? fbid : output_framebuffer); +} + +void +set_framebuffer_to_use_for_output(unsigned fbid) { + output_framebuffer = fbid; +} + +static void +set_blending(bool allowed) { + if (allowed) { glEnable(GL_BLEND); glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); } // blending of pre-multiplied colors + else { glDisable(GL_BLEND); glBlendFunc(GL_ONE, GL_ZERO); } // no blending +} + +void +draw_quad(bool blend, unsigned instance_count) { + set_blending(blend); + if (instance_count) glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, instance_count); + else glDrawArrays(GL_TRIANGLE_FAN, 0, 4); +} + +static struct { + GLsizei items[16][4]; + size_t used; +} saved_viewports; + +void +set_gpu_viewport(unsigned w, unsigned h) { glViewport(0, 0, w, h); } + +void +save_viewport_using_bottom_left_origin(GLsizei newx, GLsizei newy, GLsizei width, GLsizei height) { + if (saved_viewports.used >= arraysz(saved_viewports.items)) fatal("Too many nested saved viewports"); + GLsizei *saved_viewport = saved_viewports.items[saved_viewports.used++]; + glGetIntegerv(GL_VIEWPORT, saved_viewport); + glViewport(newx, newy, width, height); +} + +void +save_viewport_using_top_left_origin(GLsizei newx, GLsizei newy, GLsizei width, GLsizei height, GLsizei full_framebuffer_height) { + // Converts the viewport defined by the specified arguments which are + // assumed to be in the usual co-ord system with origin at top left to the + // OpenGL viewport co-ord system with origin at bottom left. + // Use restore_viewport() to restore the viewport to what it was before. + if (saved_viewports.used >= arraysz(saved_viewports.items)) fatal("Too many nested saved viewports"); + GLsizei *saved_viewport = saved_viewports.items[saved_viewports.used++]; + glGetIntegerv(GL_VIEWPORT, saved_viewport); + newy = full_framebuffer_height - (newy + height); + glViewport(newx, newy, width, height); +} + +void +restore_viewport(void) { + if (!saved_viewports.used) fatal("Trying to restore a viewport when none is saved"); + GLsizei *saved_viewport = saved_viewports.items[--saved_viewports.used]; + glViewport(saved_viewport[0], saved_viewport[1], saved_viewport[2], saved_viewport[3]); +} + +static float +linear_to_srgb(float c) { return (c <= 0.0031308f) ? 12.92f * c : 1.055f * powf(c, 1.0f / 2.4f) - 0.055f; } + +void +save_texture_as_png(uint32_t texture_id, const char *filename) { + GLint prev_tex = 0; glGetIntegerv(GL_TEXTURE_BINDING_2D, &prev_tex); + glBindTexture(GL_TEXTURE_2D, texture_id); + int width = 0, height = 0; + glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &width); + glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &height); + size_t sz = width * height * sizeof(uint32_t); + uint32_t* data = malloc(sz); + glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); + // assume data is linear and pre0multiplied + for (int i = 0; i < width * height; i++) { + uint32_t px = data[i]; + uint8_t r = (px >> 0) & 0xFF; uint8_t g = (px >> 8) & 0xFF; uint8_t b = (px >> 16) & 0xFF; + uint8_t a = (px >> 24) & 0xFF; float alpha = a / 255.0f; + float rf = 0, gf = 0, bf = 0; + if (alpha > 0.0f) { rf = (r / 255.0f) / alpha; gf = (g / 255.0f) / alpha; bf = (b / 255.0f) / alpha; } + rf = linear_to_srgb(rf); gf = linear_to_srgb(gf); bf = linear_to_srgb(bf); + r = (uint8_t)(rf*255); g = (uint8_t)(gf * 255); b = (uint8_t)(bf * 255); + data[i] = (r << 0) | (g << 8) | (b << 16) | (a << 24); + } + + const char *png = png_from_32bit_rgba(data, width, height, &sz, true); + if (!sz) fatal("Failed to save PNG to %s with error: %s", filename, png); + free(data); + FILE* file = fopen(filename, "wb"); + fwrite(png, 1, sz, file); + fclose(file); + glBindTexture(GL_TEXTURE_2D, prev_tex); +} + + // }}} // Programs {{{ @@ -180,7 +290,7 @@ attrib_location(int program, const char *name) { GLuint block_index(int program, const char *name) { GLuint ans = glGetUniformBlockIndex(programs[program].id, name); - if (ans == GL_INVALID_INDEX) { fatal("Could not find block index"); } + if (ans == GL_INVALID_INDEX) { fatal("Could not find block index for %s", name); } return ans; } diff --git a/kitty/gl.h b/kitty/gl.h index 629d0f0f2..182ff8083 100644 --- a/kitty/gl.h +++ b/kitty/gl.h @@ -32,7 +32,9 @@ typedef struct { void gl_init(void); const char* gl_version_string(void); -void update_surface_size(int w, int h, GLuint offscreen_texture_id); +void set_gpu_viewport(unsigned w, unsigned h); +void draw_quad(bool blend, unsigned instance_count); +void save_texture_as_png(uint32_t texture_id, const char *filename); void free_texture(GLuint *tex_id); void free_framebuffer(GLuint *fb_id); void remove_vao(ssize_t vao_idx); @@ -57,3 +59,9 @@ void bind_vao_uniform_buffer(ssize_t vao_idx, size_t bufnum, GLuint block_index) void unbind_vertex_array(void); void unbind_program(void); GLuint compile_shaders(GLenum shader_type, GLsizei count, const GLchar * const * string); +void save_viewport_using_top_left_origin(GLsizei x, GLsizei y, GLsizei width, GLsizei height, GLsizei full_framebuffer_height); +void save_viewport_using_bottom_left_origin(GLsizei x, GLsizei y, GLsizei width, GLsizei height); +void check_framebuffer_status_or_die(void); +void restore_viewport(void); +void bind_framebuffer_for_output(unsigned fbid); +void set_framebuffer_to_use_for_output(unsigned fbid); diff --git a/kitty/glfw.c b/kitty/glfw.c index 6348608f9..c41a2fde0 100644 --- a/kitty/glfw.c +++ b/kitty/glfw.c @@ -425,7 +425,7 @@ framebuffer_size_callback(GLFWwindow *w, int width, int height) { window->live_resize.width = MAX(0, width); window->live_resize.height = MAX(0, height); window->live_resize.num_of_resize_events++; make_os_window_context_current(window); - update_surface_size(width, height, 0); + set_gpu_viewport(width, height); request_tick_callback(); } else log_error("Ignoring resize request for tiny size: %dx%d", width, height); global_state.callback_os_window = NULL; @@ -738,7 +738,7 @@ apple_url_open_callback(const char* url) { bool -draw_window_title(OSWindow *window UNUSED, const char *text, color_type fg, color_type bg, uint8_t *output_buf, size_t width, size_t height) { +draw_window_title(double font_sz_pts UNUSED, double ydpi UNUSED, const char *text, color_type fg, color_type bg, uint8_t *output_buf, size_t width, size_t height) { static char buf[2048]; strip_csi_(text, buf, arraysz(buf)); return cocoa_render_line_of_text(buf, fg, bg, output_buf, width, height); @@ -786,11 +786,11 @@ draw_text_callback(GLFWwindow *window, const char *text, uint32_t fg, uint32_t b } bool -draw_window_title(OSWindow *window, const char *text, color_type fg, color_type bg, uint8_t *output_buf, size_t width, size_t height) { +draw_window_title(double font_sz_pts, double ydpi, const char *text, color_type fg, color_type bg, uint8_t *output_buf, size_t width, size_t height) { if (!ensure_csd_title_render_ctx()) return false; static char buf[2048]; strip_csi_(text, buf, arraysz(buf)); - unsigned px_sz = (unsigned)(window->fonts_data->font_sz_in_pts * window->fonts_data->logical_dpi_y / 72.); + unsigned px_sz = (unsigned)(font_sz_pts * ydpi / 72.); px_sz = MIN(px_sz, 3 * height / 4); #define RGB2BGR(x) (x & 0xFF000000) | ((x & 0xFF0000) >> 16) | (x & 0x00FF00) | ((x & 0x0000FF) << 16) bool ok = render_single_line(csd_title_render_ctx, buf, px_sz, RGB2BGR(fg), RGB2BGR(bg), output_buf, width, height, 0, 0, 0, false); @@ -1460,7 +1460,6 @@ create_os_window(PyObject UNUSED *self, PyObject *args, PyObject *kw) { glfwMakeContextCurrent(glfw_window); if (is_first_window) gl_init(); // Will make the GPU automatically apply SRGB gamma curve on the resulting framebuffer - glEnable(GL_FRAMEBUFFER_SRGB); bool is_semi_transparent = glfwGetWindowAttrib(glfw_window, GLFW_TRANSPARENT_FRAMEBUFFER); // blank the window once so that there is no initial flash of color // changing, in case the background color is not black @@ -1487,7 +1486,7 @@ create_os_window(PyObject UNUSED *self, PyObject *args, PyObject *kw) { } } if (is_first_window) { - PyObject *ret = PyObject_CallFunction(load_programs, "O", is_semi_transparent ? Py_True : Py_False); + PyObject *ret = PyObject_CallNoArgs(load_programs); if (ret == NULL) return NULL; Py_DECREF(ret); get_platform_dependent_config_values(glfw_window); @@ -1495,7 +1494,6 @@ create_os_window(PyObject UNUSED *self, PyObject *args, PyObject *kw) { glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_BACK_LEFT, GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING, &encoding); if (encoding != GL_SRGB) log_error("The output buffer does not support sRGB color encoding, colors will be incorrect."); is_first_window = false; - } OSWindow *w = add_os_window(); w->handle = glfw_window; diff --git a/kitty/graphics.c b/kitty/graphics.c index dce1099b5..45ba56cb5 100644 --- a/kitty/graphics.c +++ b/kitty/graphics.c @@ -212,10 +212,15 @@ ref_by_client_id(const Image *img, uint32_t id) { return NULL; } +static void +set_layers_dirty(GraphicsManager *self) { + self->layers_dirty = true; +} + static image_map_itr remove_image_itr(GraphicsManager *self, image_map_itr i) { free_image(self, i.data->val); - self->layers_dirty = true; + set_layers_dirty(self); return vt_erase_itr(&self->images_by_internal_id, i); } @@ -697,7 +702,9 @@ upload_to_gpu(GraphicsManager *self, Image *img, const bool is_opaque, const boo if (!make_window_context_current(self->window_id)) return; self->context_made_current_for_this_command = true; } - if (img->texture) send_image_to_gpu(&img->texture->id, data, img->width, img->height, is_opaque, is_4byte_aligned, true, REPEAT_CLAMP); + if (img->texture) { + send_image_to_gpu(&img->texture->id, data, img->width, img->height, is_opaque, is_4byte_aligned, true, REPEAT_CLAMP); + } } static Image* @@ -720,7 +727,7 @@ handle_add_command(GraphicsManager *self, const GraphicsCommand *g, const uint8_ img->current_frame_shown_at = 0; img->extra_framecnt = 0; *is_dirty = true; - self->layers_dirty = true; + set_layers_dirty(self); } else { img->client_id = iid; img->client_number = g->image_number; @@ -1010,7 +1017,7 @@ void grman_put_cell_image(GraphicsManager *self, uint32_t screen_row, ImageRef *real_ref = create_ref(img, &ref); img->atime = monotonic(); - self->layers_dirty = true; + set_layers_dirty(self); update_src_rect(real_ref, img); update_dest_rect(real_ref, ref.num_cols, ref.num_rows, cell); @@ -1103,7 +1110,7 @@ handle_put_command(GraphicsManager *self, const GraphicsCommand *g, Cursor *c, b if (ref == NULL) ref = create_ref(img, NULL); *is_dirty = true; - self->layers_dirty = true; + set_layers_dirty(self); img->atime = monotonic(); ref->src_x = g->x_offset; ref->src_y = g->y_offset; ref->src_width = g->width ? g->width : img->width; ref->src_height = g->height ? g->height : img->height; ref->src_width = MIN(ref->src_width, img->width - ((float)img->width > ref->src_x ? ref->src_x : (float)img->width)); @@ -1140,17 +1147,6 @@ handle_put_command(GraphicsManager *self, const GraphicsCommand *g, Cursor *c, b return img->client_id; } -void -scale_rendered_graphic(ImageRenderData *rd, float xstart, float ystart, float x_scale, float y_scale) { - // Scale the graphic so that it appears at the same position and size during a live resize - // this means scale factors are applied to both the position and size of the graphic. - float width = rd->dest_rect.right - rd->dest_rect.left, height = rd->dest_rect.bottom - rd->dest_rect.top; - rd->dest_rect.left = xstart + (rd->dest_rect.left - xstart) * x_scale; - rd->dest_rect.right = rd->dest_rect.left + width * x_scale; - rd->dest_rect.top = ystart + (rd->dest_rect.top - ystart) * y_scale; - rd->dest_rect.bottom = rd->dest_rect.top + height * y_scale; -} - void gpu_data_for_image(ImageRenderData *ans, float left, float top, float right, float bottom) { // For dest rect: x-axis is from -1 to 1, y axis is from 1 to -1 @@ -1203,7 +1199,7 @@ resolve_parent_offset(const GraphicsManager *self, const ImageRef *ref, int32_t bool grman_update_layers(GraphicsManager *self, unsigned int scrolled_by, float screen_left, float screen_top, float dx, float dy, unsigned int num_cols, unsigned int num_rows, CellPixelSize cell) { - if (self->last_scrolled_by != scrolled_by) self->layers_dirty = true; + if (self->last_scrolled_by != scrolled_by) set_layers_dirty(self); self->last_scrolled_by = scrolled_by; if (!self->layers_dirty) return false; self->layers_dirty = false; @@ -1900,7 +1896,7 @@ filter_refs(GraphicsManager *self, const void* data, bool free_images, bool (*fi for (ref_map_itr ri = vt_first(&img->refs_by_internal_id); !vt_is_end(ri); ) { ImageRef *ref = ri.data->val; if (filter_func(ref, img, data, cell)) { ri = remove_ref_itr(img, ri); - self->layers_dirty = true; + set_layers_dirty(self); matched = true; } else ri = vt_next(ri); } @@ -1980,7 +1976,7 @@ scroll_filter_margins_func(ImageRef* ref, Image* img, const void* data, CellPixe void grman_scroll_images(GraphicsManager *self, const ScrollData *data, CellPixelSize cell) { if (vt_size(&self->images_by_internal_id)) { - self->layers_dirty = true; + set_layers_dirty(self); modify_refs(self, data, data->has_margins ? scroll_filter_margins_func : scroll_filter_func, cell); } } @@ -2131,7 +2127,7 @@ handle_delete_command(GraphicsManager *self, const GraphicsCommand *g, Cursor *c for (ref_map_itr ri = vt_first(&img->refs_by_internal_id); !vt_is_end(ri); ) { ImageRef *ref = ri.data->val; if (!g->placement_id || g->placement_id == ref->client_id) { ri = remove_ref_itr(img, ri); - self->layers_dirty = true; + set_layers_dirty(self); } else ri = vt_next(ri); } if (!vt_size(&img->refs_by_internal_id) && (g->delete_action == 'N' || img->client_id == 0)) remove_image(self, img); @@ -2162,7 +2158,7 @@ end: void grman_resize(GraphicsManager *self, index_type old_lines UNUSED, index_type lines UNUSED, index_type old_columns, index_type columns, index_type num_content_lines_before, index_type num_content_lines_after) { ImageRef *ref; Image *img; - self->layers_dirty = true; + set_layers_dirty(self); if (columns == old_columns && num_content_lines_before > num_content_lines_after) { const unsigned int vertical_shrink_size = num_content_lines_before - num_content_lines_after; iter_images(self) { img = i.data->val; @@ -2177,7 +2173,7 @@ grman_resize(GraphicsManager *self, index_type old_lines UNUSED, index_type line void grman_rescale(GraphicsManager *self, CellPixelSize cell) { ImageRef *ref; Image *img; - self->layers_dirty = true; + set_layers_dirty(self); iter_images(self) { img = i.data->val; iter_refs(img) { ref = i.data->val; if (ref->is_virtual_ref || is_cell_image(ref)) continue; @@ -2464,13 +2460,14 @@ init_graphics(PyObject *module) { return true; } -void grman_mark_layers_dirty(GraphicsManager *self) { self->layers_dirty = true; } +void grman_mark_layers_dirty(GraphicsManager *self) { set_layers_dirty(self); } void grman_set_window_id(GraphicsManager *self, id_type id) { self->window_id = id; } +bool grman_has_images(GraphicsManager *self) { return self->num_of_below_refs + self->num_of_negative_refs + self->num_of_positive_refs > 0; } GraphicsRenderData grman_render_data(GraphicsManager *self) { GraphicsRenderData ans = { .count=self->render_data.count, .capacity=self->render_data.capacity, .images=self->render_data.item, .num_of_below_refs=self->num_of_below_refs, .num_of_negative_refs=self->num_of_negative_refs, - .num_of_positive_refs=self->num_of_positive_refs + .num_of_positive_refs=self->num_of_positive_refs, }; return ans; } diff --git a/kitty/graphics.h b/kitty/graphics.h index 84a4f4775..50de464b5 100644 --- a/kitty/graphics.h +++ b/kitty/graphics.h @@ -40,7 +40,7 @@ typedef struct { uint32_t texture_id; unsigned int height, width; uint8_t* bitmap; - uint32_t refcnt; + uint32_t refcnt, id; size_t mmap_size; } BackgroundImage; @@ -170,26 +170,29 @@ gl_size(const unsigned int sz, const unsigned int viewport_size) { } static inline float -clamp_position_to_nearest_pixel(float pos, const unsigned int viewport_size) { - // clamp the specified opengl position to the nearest pixel - const float px = 2.f / viewport_size; - const float distance = pos + 1.f; - const float num_of_pixels = roundf(distance / px); - return -1.f + num_of_pixels * px; -} - -static inline float -gl_pos_x(const unsigned int px_from_left_margin, const unsigned int viewport_size) { +gl_pos_x(const int px_from_left_margin, const unsigned int viewport_size) { const float px = 2.f / viewport_size; return -1.f + px_from_left_margin * px; } static inline float -gl_pos_y(const unsigned int px_from_top_margin, const unsigned int viewport_size) { +tex_pos_x(const int px_from_left_margin, const unsigned texture_width) { + return px_from_left_margin / (float)texture_width; +} + +static inline float +gl_pos_y(const int px_from_top_margin, const unsigned int viewport_size) { const float px = 2.f / viewport_size; return 1.f - px_from_top_margin * px; } +static inline float +tex_pos_y(const int px_from_top_margin, const unsigned texture_height) { + const int px_from_bottom_margin = texture_height - px_from_top_margin; + return px_from_bottom_margin / (float)texture_height; +} + + typedef struct GraphicsRenderData { size_t count, capacity, num_of_below_refs, num_of_negative_refs, num_of_positive_refs; ImageRenderData *images; @@ -211,8 +214,8 @@ bool png_path_to_bitmap(const char *path, uint8_t** data, unsigned int* width, u bool png_from_data(void *png_data, size_t png_data_sz, const char *path_for_error_messages, uint8_t** data, unsigned int* width, unsigned int* height, size_t* sz); bool image_path_to_bitmap(const char *path, uint8_t** data, unsigned int* width, unsigned int* height, size_t* sz); bool scan_active_animations(GraphicsManager *self, const monotonic_t now, monotonic_t *minimum_gap, bool os_window_context_set); -void scale_rendered_graphic(ImageRenderData*, float xstart, float ystart, float x_scale, float y_scale); void grman_pause_rendering(GraphicsManager *self, GraphicsManager *dest); void grman_mark_layers_dirty(GraphicsManager *self); void grman_set_window_id(GraphicsManager *self, id_type id); +bool grman_has_images(GraphicsManager *self); GraphicsRenderData grman_render_data(GraphicsManager *self); diff --git a/kitty/graphics_fragment.glsl b/kitty/graphics_fragment.glsl index 95ce0cbbb..1afbd9778 100644 --- a/kitty/graphics_fragment.glsl +++ b/kitty/graphics_fragment.glsl @@ -1,4 +1,5 @@ #pragma kitty_include_shader +#pragma kitty_include_shader #define ALPHA_TYPE uniform sampler2D image; @@ -6,22 +7,23 @@ uniform sampler2D image; uniform vec3 amask_fg; uniform vec4 amask_bg_premult; #else -uniform float inactive_text_alpha; +uniform float extra_alpha; #endif in vec2 texcoord; -out vec4 color; +out vec4 output_color; void main() { - color = texture(image, texcoord); + vec4 color = texture(image, texcoord); #ifdef ALPHA_MASK color = vec4(amask_fg, color.r); - color = vec4(color.rgb * color.a, color.a); + color = vec4_premul(color); color = alpha_blend_premul(color, amask_bg_premult); #else - color.a *= inactive_text_alpha; -#ifdef PREMULT - color = vec4(color.rgb * color.a, color.a); + color.a *= extra_alpha; +#if TEXTURE_IS_NOT_PREMULTIPLIED + color = vec4_premul(color); #endif #endif + output_color = color; } diff --git a/kitty/graphics_vertex.glsl b/kitty/graphics_vertex.glsl index 336a8252b..57408a3b4 100644 --- a/kitty/graphics_vertex.glsl +++ b/kitty/graphics_vertex.glsl @@ -1,24 +1,2 @@ -out vec2 texcoord; -uniform vec4 src_rect, dest_rect, viewport; - -#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); - gl_ClipDistance[left] = gl_Position.x - viewport[left]; - gl_ClipDistance[right] = viewport[right] - gl_Position.x; - gl_ClipDistance[top] = viewport[top] - gl_Position.y; - gl_ClipDistance[bottom] = gl_Position.y - viewport[bottom]; -} +uniform vec4 src_rect, dest_rect; +#pragma kitty_include_shader diff --git a/kitty/keys.c b/kitty/keys.c index 2307657c1..f24bfea49 100644 --- a/kitty/keys.c +++ b/kitty/keys.c @@ -125,7 +125,7 @@ update_ime_focus(OSWindow *osw, bool focused) { void prepare_ime_position_update_event(OSWindow *osw, Window *w, Screen *screen, GLFWIMEUpdateEvent *ev) { unsigned int cell_width = osw->fonts_data->fcm.cell_width, cell_height = osw->fonts_data->fcm.cell_height; - unsigned int left = w->geometry.left, top = w->geometry.top; + unsigned int left = w->render_data.geometry.left, top = w->render_data.geometry.top; if (screen_is_overlay_active(screen)) { left += screen->overlay_line.cursor_x * cell_width; top += MIN(screen->overlay_line.ynum + screen->scrolled_by, screen->lines - 1) * cell_height; diff --git a/kitty/layout/base.py b/kitty/layout/base.py index 4e4f8e92c..37c3b2ffb 100644 --- a/kitty/layout/base.py +++ b/kitty/layout/base.py @@ -380,7 +380,7 @@ class Layout: ),) geom = layout_single_window(xdecoration_pairs, ydecoration_pairs, xalignment=lgd.alignment_x, yalignment=lgd.alignment_y) wg.set_geometry(geom) - if add_blank_rects and wg: + if add_blank_rects: self.blank_rects.extend(blank_rects_for_window(geom)) def xlayout( diff --git a/kitty/linear2srgb.glsl b/kitty/linear2srgb.glsl index 11efd10f5..637632d55 100644 --- a/kitty/linear2srgb.glsl +++ b/kitty/linear2srgb.glsl @@ -13,3 +13,11 @@ float linear2srgb(float x) { return mix(lower, upper, step(0.0031308f, x)); } + +vec3 linear2srgb(vec3 c) { + return vec3(linear2srgb(c.r), linear2srgb(c.g), linear2srgb(c.b)); +} + +vec3 srgb2linear(vec3 c) { + return vec3(srgb2linear(c.r), srgb2linear(c.g), srgb2linear(c.b)); +} diff --git a/kitty/main.py b/kitty/main.py index 9746fd95c..a273c5187 100644 --- a/kitty/main.py +++ b/kitty/main.py @@ -86,9 +86,9 @@ def set_custom_ibeam_cursor() -> None: log_error(f'Failed to set custom beam cursor with error: {e}') -def load_all_shaders(semi_transparent: bool = False) -> None: +def load_all_shaders() -> None: try: - load_shader_programs(semi_transparent) + load_shader_programs() load_borders_program() except CompileError as err: raise SystemExit(err) diff --git a/kitty/mouse.c b/kitty/mouse.c index be945fec5..790aa5d87 100644 --- a/kitty/mouse.c +++ b/kitty/mouse.c @@ -214,22 +214,22 @@ dispatch_mouse_event(Window *w, int button, int count, int modifiers, bool grabb static unsigned int window_left(Window *w) { - return w->geometry.left - w->padding.left; + return w->render_data.geometry.left - w->padding.left; } static unsigned int window_right(Window *w) { - return w->geometry.right + w->padding.right; + return w->render_data.geometry.right + w->padding.right; } static unsigned int window_top(Window *w) { - return w->geometry.top - w->padding.top; + return w->render_data.geometry.top - w->padding.top; } static unsigned int window_bottom(Window *w) { - return w->geometry.bottom + w->padding.bottom; + return w->render_data.geometry.bottom + w->padding.bottom; } static bool @@ -250,7 +250,7 @@ static bool clamp_to_window = false; static bool cell_for_pos(Window *w, unsigned int *x, unsigned int *y, bool *in_left_half_of_cell, OSWindow *os_window) { - WindowGeometry *g = &w->geometry; + WindowGeometry *g = &w->render_data.geometry; Screen *screen = w->render_data.screen; if (!screen) return false; unsigned int qx = 0, qy = 0; @@ -323,8 +323,8 @@ bool drag_scroll(Window *w, OSWindow *frame) { unsigned int margin = frame->fonts_data->fcm.cell_height / 2; double y = frame->mouse_y; - bool upwards = y <= (w->geometry.top + margin); - if (upwards || y >= w->geometry.bottom - margin) { + bool upwards = y <= (w->render_data.geometry.top + margin); + if (upwards || y >= w->render_data.geometry.bottom - margin) { if (do_drag_scroll(w, upwards)) { frame->last_mouse_activity_at = monotonic(); return true; diff --git a/kitty/options/definition.py b/kitty/options/definition.py index 9bfc92b4c..5537ca732 100644 --- a/kitty/options/definition.py +++ b/kitty/options/definition.py @@ -1592,11 +1592,7 @@ launch your editor. See also :opt:`transparent_background_colors`. Be aware that using a value less than 1.0 is a (possibly significant) performance hit. When using a low value for this setting, it is desirable that you set the :opt:`background` color to a color the matches the -general color of the desktop background, for best text rendering. Note that -to workaround window managers not doing gamma-corrected blending kitty -makes background_opacity non-linear which means, especially for light backgrounds -you might need to make the value much lower than you expect to get good results, -see :iss:`6218` for details. +general color of the desktop background, for best text rendering. If you want to dynamically change transparency of windows, set :opt:`dynamic_background_opacity` to :code:`yes` (this is off by default as it @@ -1632,6 +1628,7 @@ part is optional. When unspecified, the value of :opt:`background_opacity` is us transparent_background_colors red@0.5 #00ff00@0.3 +Note that you must also set :opt:`background_opacity` to something less than 1 for this setting to work properly. ''' ) @@ -1675,8 +1672,7 @@ opt('background_tint', '0.0', long_text=''' How much to tint the background image by the background color. This option makes it easier to read the text. Tinting is done using the current background -color for each window. This option applies only if :opt:`background_opacity` is -set and transparent windows are supported or :opt:`background_image` is set. +color for each window. This option applies only if :opt:`background_image` is set. Note that when using :ref:`auto_color_scheme` this option is overridden by the color scheme file and must be set inside it to take effect. ''' ) diff --git a/kitty/png-reader.c b/kitty/png-reader.c index 73f9e39aa..740d5eab0 100644 --- a/kitty/png-reader.c +++ b/kitty/png-reader.c @@ -131,6 +131,72 @@ err: return; } +// Structure to hold memory write state +typedef struct { + unsigned char* buffer; + size_t size, capacity; +} png_memory_write_state; + +// Custom write function for writing PNG data to memory +static void +png_write_to_memory(png_structp png_ptr, png_bytep data, png_size_t length) { + png_memory_write_state* state = (png_memory_write_state*)png_get_io_ptr(png_ptr); + if (state->size + length > state->capacity) { + // Double the capacity or add enough space for the new data, whichever is larger + size_t new_capacity = state->capacity * 2; + if (new_capacity < state->size + length) new_capacity = state->size + length; + unsigned char* new_buffer = realloc(state->buffer, new_capacity); + if (!new_buffer) { + png_error(png_ptr, "Failed to allocate memory for PNG buffer"); + return; + } + state->buffer = new_buffer; + state->capacity = new_capacity; + } + // Copy the data to the buffer + memcpy(state->buffer + state->size, data, length); + state->size += length; +} +static void png_flush_memory(png_structp png_ptr) { (void)png_ptr; } + +const char* +png_from_32bit_rgba(uint32_t *data, size_t width, size_t height, size_t *out_size, bool flip_vertically) { + *out_size = 0; + png_memory_write_state state = {.capacity=width*height * sizeof(uint32_t)}; + state.buffer = malloc(state.capacity); + if (!state.buffer) return "Out of memory"; + png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (!png_ptr) { free(state.buffer); return "Failed to create PNG write struct"; } + png_infop info_ptr = png_create_info_struct(png_ptr); + if (!info_ptr) { + free(state.buffer); png_destroy_write_struct(&png_ptr, NULL); + return "Failed to create PNG info struct"; + } + if (setjmp(png_jmpbuf(png_ptr))) { + png_destroy_write_struct(&png_ptr, &info_ptr); free(state.buffer); + return("Error during PNG creation\n"); + } + png_set_write_fn(png_ptr, &state, png_write_to_memory, png_flush_memory); + png_set_IHDR(png_ptr, info_ptr, width, height, 8, PNG_COLOR_TYPE_RGBA, + PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); + // Allocate memory for row pointers + png_bytep *row_pointers = (png_bytep*)malloc(sizeof(png_bytep) * height); + if (!row_pointers) { + png_destroy_write_struct(&png_ptr, &info_ptr); + free(state.buffer); + return ("Failed to allocate memory for row pointers"); + } + if (flip_vertically) for (size_t y = 0; y < height; y++) row_pointers[height - 1 - y] = (png_byte*)&data[y * width]; + else for (size_t y = 0; y < height; y++) row_pointers[y] = (png_byte*)&data[y * width]; + png_write_info(png_ptr, info_ptr); + png_write_image(png_ptr, row_pointers); + png_write_end(png_ptr, NULL); + png_destroy_write_struct(&png_ptr, &info_ptr); + free(row_pointers); + *out_size = state.size; + return (char*)state.buffer; +} + static void png_error_handler(png_read_data *d UNUSED, const char *code, const char *msg) { if (!PyErr_Occurred()) PyErr_Format(PyExc_ValueError, "[%s] %s", code, msg); diff --git a/kitty/png-reader.h b/kitty/png-reader.h index 451055a66..4d6175333 100644 --- a/kitty/png-reader.h +++ b/kitty/png-reader.h @@ -28,3 +28,4 @@ typedef struct png_read_data { } png_read_data; void inflate_png_inner(png_read_data *d, const uint8_t *buf, size_t bufsz); +const char* png_from_32bit_rgba(uint32_t *data, size_t width, size_t height, size_t *out_size, bool flip_vertically); diff --git a/kitty/screen.h b/kitty/screen.h index e5100bbf3..3edebcabd 100644 --- a/kitty/screen.h +++ b/kitty/screen.h @@ -11,6 +11,7 @@ #include "monotonic.h" #include "line-buf.h" #include "history.h" +#include "window_logo.h" typedef enum ScrollTypes { SCROLL_LINE = -999999, SCROLL_PAGE, SCROLL_FULL } ScrollType; diff --git a/kitty/shaders.c b/kitty/shaders.c index 9cb3b6638..dc7acadb4 100644 --- a/kitty/shaders.c +++ b/kitty/shaders.c @@ -14,12 +14,27 @@ #include "srgb_gamma.h" #include "uniforms_generated.h" -#define BLEND_ONTO_OPAQUE glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // blending onto opaque colors -#define BLEND_ONTO_OPAQUE_WITH_OPAQUE_OUTPUT glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE); // blending onto opaque colors with final color having alpha 1 -#define BLEND_PREMULT glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); // blending of pre-multiplied colors +enum { + CELL_PROGRAM, CELL_FG_PROGRAM, CELL_BG_PROGRAM, CELL_PROGRAM_SENTINEL, + BORDERS_PROGRAM, + GRAPHICS_PROGRAM, GRAPHICS_PREMULT_PROGRAM, GRAPHICS_ALPHA_MASK_PROGRAM, + BGIMAGE_PROGRAM, + TINT_PROGRAM, + TRAIL_PROGRAM, + BLIT_PROGRAM, + NUM_PROGRAMS +}; +enum { SPRITE_MAP_UNIT, GRAPHICS_UNIT, SPRITE_DECORATIONS_MAP_UNIT }; -enum { CELL_PROGRAM, CELL_BG_PROGRAM, CELL_SPECIAL_PROGRAM, CELL_FG_PROGRAM, BORDERS_PROGRAM, GRAPHICS_PROGRAM, GRAPHICS_PREMULT_PROGRAM, GRAPHICS_ALPHA_MASK_PROGRAM, BGIMAGE_PROGRAM, TINT_PROGRAM, TRAIL_PROGRAM, NUM_PROGRAMS }; -enum { SPRITE_MAP_UNIT, GRAPHICS_UNIT, BGIMAGE_UNIT, SPRITE_DECORATIONS_MAP_UNIT }; +typedef struct UIRenderData { + unsigned screen_width, screen_height, cell_width, cell_height, screen_left, screen_top, full_framebuffer_width, full_framebuffer_height; + Window *window; Screen *screen; OSWindow *os_window; + GraphicsRenderData grd; + WindowLogoRenderData *window_logo; + float inactive_text_alpha; + bool has_background_image; + color_type background_color; // RGB only +} UIRenderData; // Sprites {{{ typedef struct { @@ -51,6 +66,15 @@ color_vec4_premult(GLint location, color_type color, GLfloat alpha) { glUniform4f(location, srgb_lut[(color >> 16) & 0xFF]*alpha, srgb_lut[(color >> 8) & 0xFF]*alpha, srgb_lut[color & 0xFF]*alpha, alpha); } +static void +color_vec4(GLint location, color_type color, GLfloat alpha) { + glUniform4f(location, srgb_lut[(color >> 16) & 0xFF], srgb_lut[(color >> 8) & 0xFF], srgb_lut[color & 0xFF], alpha); +} + + + +static void +clear_current_framebuffer(void) { glClearColor(0, 0, 0, 0); glClear(GL_COLOR_BUFFER_BIT); } SPRITE_MAP_HANDLE alloc_sprite_map(void) { @@ -160,7 +184,7 @@ realloc_sprite_decorations_texture_if_needed(FONTS_DATA_HANDLE fg) { glTexImage2D(texture_type, 0, GL_R32UI, width, height, 0, GL_RED_INTEGER, GL_UNSIGNED_INT, NULL); if (dm.texture_id) { // copy data from old texture copy_32bit_texture(dm.texture_id, tex, texture_type); - glDeleteTextures(1, &dm.texture_id); + free_texture(&dm.texture_id); } glBindTexture(texture_type, 0); dm.texture_id = tex; dm.width = width; dm.height = height; @@ -179,7 +203,7 @@ realloc_sprite_texture(FONTS_DATA_HANDLE fg) { glTexStorage3D(texture_type, 1, GL_SRGB8_ALPHA8, width, height, znum); if (sprite_map->texture_id) { // copy old texture data into new texture copy_32bit_texture(sprite_map->texture_id, tex, texture_type); - glDeleteTextures(1, &sprite_map->texture_id); + free_texture(&sprite_map->texture_id); } glBindTexture(texture_type, 0); sprite_map->last_num_of_layers = znum; @@ -256,13 +280,6 @@ send_image_to_gpu(GLuint *tex_id, const void* data, GLsizei width, GLsizei heigh // Cell {{{ -typedef struct CellRenderData { - struct { - GLfloat xstart, ystart, dx, dy, width, height; - } gl; - float x_ratio, y_ratio; -} CellRenderData; - typedef struct { UniformBlock render_data; ArrayInformation color_table; @@ -285,9 +302,20 @@ typedef struct { } TintProgramLayout; static TintProgramLayout tint_program_layout; +typedef struct { + TrailUniforms uniforms; +} TrailProgramLayout; +static TrailProgramLayout trail_program_layout; + +typedef struct { + BlitUniforms uniforms; +} BlitProgramLayout; +static BlitProgramLayout blit_program_layout; + + static void init_cell_program(void) { - for (int i = CELL_PROGRAM; i < BORDERS_PROGRAM; i++) { + 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.size = get_uniform_information(i, "color_table[0]", GL_UNIFORM_SIZE); @@ -300,7 +328,7 @@ init_cell_program(void) { // 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 < BORDERS_PROGRAM; p++) { + for (int p = CELL_PROGRAM; p < CELL_PROGRAM_SENTINEL; p++) { C(p, colors, 0); C(p, sprite_idx, 1); C(p, is_selected, 2); C(p, decorations_sprite_map, 3); } #undef C @@ -309,6 +337,8 @@ init_cell_program(void) { } get_uniform_locations_bgimage(BGIMAGE_PROGRAM, &bgimage_program_layout.uniforms); get_uniform_locations_tint(TINT_PROGRAM, &tint_program_layout.uniforms); + get_uniform_locations_trail(TRAIL_PROGRAM, &trail_program_layout.uniforms); + get_uniform_locations_blit(BLIT_PROGRAM, &blit_program_layout.uniforms); } #define CELL_BUFFERS enum { cell_data_buffer, selection_buffer, uniform_buffer }; @@ -361,16 +391,21 @@ pick_cursor_color(Line *line, const ColorProfile *color_profile, color_type cell } } -static void -cell_update_uniform_block(ssize_t vao_idx, Screen *screen, int uniform_buffer, const CellRenderData *crd, CursorRenderInfo *cursor, OSWindow *os_window) { +static bool +has_bgimage(OSWindow *w) { + return w->bgimage && w->bgimage->texture_id > 0; +} + +static color_type +cell_update_uniform_block(ssize_t vao_idx, Screen *screen, int uniform_buffer, CursorRenderInfo *cursor, OSWindow *os_window, float inactive_text_alpha) { struct GPUCellRenderData { - GLfloat xstart, ystart, dx, dy, use_cell_bg_for_selection_fg, use_cell_fg_for_selection_color, use_cell_for_selection_bg; + GLfloat use_cell_bg_for_selection_fg, use_cell_fg_for_selection_color, use_cell_for_selection_bg; GLuint default_fg, highlight_fg, highlight_bg, cursor_fg, cursor_bg, url_color, url_style, inverted; - GLuint xnum, ynum, sprites_xnum, sprites_ynum, cursor_fg_sprite_idx, cell_height; + GLuint columns, lines, sprites_xnum, sprites_ynum, cursor_fg_sprite_idx, cell_width, cell_height; GLuint cursor_x1, cursor_x2, cursor_y1, cursor_y2; - GLfloat cursor_opacity; + GLfloat cursor_opacity, inactive_text_alpha, dim_opacity; GLuint bg_colors0, bg_colors1, bg_colors2, bg_colors3, bg_colors4, bg_colors5, bg_colors6, bg_colors7; GLfloat bg_opacities0, bg_opacities1, bg_opacities2, bg_opacities3, bg_opacities4, bg_opacities5, bg_opacities6, bg_opacities7; @@ -386,7 +421,8 @@ cell_update_uniform_block(ssize_t vao_idx, Screen *screen, int uniform_buffer, c rd->highlight_fg = COLOR(highlight_fg); rd->highlight_bg = COLOR(highlight_bg); rd->bg_colors0 = COLOR(default_bg); rd->bg_opacities0 = os_window->is_semi_transparent ? os_window->background_opacity : 1.0f; -#define SETBG(which) colorprofile_to_transparent_color(cp, which - 1, &rd->bg_colors##which, &rd->bg_opacities##which) +#define SETBG(which) { \ + colorprofile_to_transparent_color(cp, which - 1, &rd->bg_colors##which, &rd->bg_opacities##which); } SETBG(1); SETBG(2); SETBG(3); SETBG(4); SETBG(5); SETBG(6); SETBG(7); #undef SETBG // selection @@ -467,23 +503,26 @@ cell_update_uniform_block(ssize_t vao_idx, Screen *screen, int uniform_buffer, c rd->cursor_y1 = screen->lines + 1; rd->cursor_y2 = screen->lines; } - rd->xnum = screen->columns; rd->ynum = screen->lines; + rd->columns = screen->columns; rd->lines = screen->lines; - rd->xstart = crd->gl.xstart; rd->ystart = crd->gl.ystart; rd->dx = crd->gl.dx; rd->dy = crd->gl.dy; unsigned int x, y, z; sprite_tracker_current_layout(os_window->fonts_data, &x, &y, &z); rd->sprites_xnum = x; rd->sprites_ynum = y; rd->inverted = screen_invert_colors(screen) ? 1 : 0; + rd->cell_width = os_window->fonts_data->fcm.cell_width; rd->cell_height = os_window->fonts_data->fcm.cell_height; + rd->inactive_text_alpha = inactive_text_alpha; + rd->dim_opacity = OPT(dim_opacity); #undef COLOR rd->url_color = OPT(url_color); rd->url_style = OPT(url_style); - + color_type default_bg = rd->bg_colors0; unmap_vao_buffer(vao_idx, uniform_buffer); rd = NULL; + return default_bg; } static bool -cell_prepare_to_render(ssize_t vao_idx, Screen *screen, GLfloat xstart, GLfloat ystart, GLfloat dx, GLfloat dy, FONTS_DATA_HANDLE fonts_data) { +cell_prepare_to_render(ssize_t vao_idx, Screen *screen, FONTS_DATA_HANDLE fonts_data) { size_t sz; CELL_BUFFERS; void *address; @@ -523,7 +562,7 @@ cell_prepare_to_render(ssize_t vao_idx, Screen *screen, GLfloat xstart, GLfloat } #define update_graphics_data(grman) \ - grman_update_layers(grman, screen->scrolled_by, xstart, ystart, dx, dy, screen->columns, screen->lines, screen->cell_size) + grman_update_layers(grman, screen->scrolled_by, -1.f, 1.f, 2.f/screen->columns, 2.f/screen->lines, screen->columns, screen->lines, screen->cell_size) if (screen->paused_rendering.expires_at) { if (!screen->paused_rendering.cell_data_updated) { @@ -545,66 +584,11 @@ cell_prepare_to_render(ssize_t vao_idx, Screen *screen, GLfloat xstart, GLfloat } static void -draw_background_image(OSWindow *w) { - blank_canvas(w->is_semi_transparent ? OPT(background_opacity) : 1.0f, OPT(background)); - bind_program(BGIMAGE_PROGRAM); - - glUniform1i(bgimage_program_layout.uniforms.image, BGIMAGE_UNIT); - glUniform1f(bgimage_program_layout.uniforms.opacity, OPT(background_opacity)); -#ifdef __APPLE__ - int window_width = w->window_width, window_height = w->window_height; -#else - int window_width = w->viewport_width, window_height = w->viewport_height; -#endif - GLfloat iwidth = (GLfloat)w->bgimage->width; - GLfloat iheight = (GLfloat)w->bgimage->height; - GLfloat vwidth = (GLfloat)window_width; - GLfloat vheight = (GLfloat)window_height; - if (CENTER_SCALED == OPT(background_image_layout)) { - GLfloat ifrac = iwidth / iheight; - if (ifrac > (vwidth / vheight)) { - iheight = vheight; - iwidth = iheight * ifrac; - } else { - iwidth = vwidth; - iheight = iwidth / ifrac; - } - } - glUniform4f(bgimage_program_layout.uniforms.sizes, - vwidth, vheight, iwidth, iheight); - glUniform1f(bgimage_program_layout.uniforms.premult, w->is_semi_transparent ? 1.f : 0.f); - GLfloat tiled = 0.f;; - GLfloat left = -1.0, top = 1.0, right = 1.0, bottom = -1.0; - switch (OPT(background_image_layout)) { - case TILING: case MIRRORED: case CLAMPED: - tiled = 1.f; break; - case SCALED: - break; - case CENTER_CLAMPED: - case CENTER_SCALED: { - GLfloat wfrac = (vwidth - iwidth) / vwidth; - GLfloat hfrac = (vheight - iheight) / vheight; - left += wfrac; - right -= wfrac; - top -= hfrac; - bottom += hfrac; - } break; - } - glUniform1f(bgimage_program_layout.uniforms.tiled, tiled); - glUniform4f(bgimage_program_layout.uniforms.positions, left, top, right, bottom); - glActiveTexture(GL_TEXTURE0 + BGIMAGE_UNIT); - glBindTexture(GL_TEXTURE_2D, w->bgimage->texture_id); - glDrawArrays(GL_TRIANGLE_FAN, 0, 4); - unbind_program(); -} - -static void -draw_graphics(int program, ssize_t vao_idx, ImageRenderData *data, GLuint start, GLuint count, ImageRect viewport) { +draw_graphics(int program, ImageRenderData *data, GLuint start, GLuint count, float extra_alpha) { bind_program(program); + if (program != GRAPHICS_ALPHA_MASK_PROGRAM) glUniform1f(graphics_program_layouts[program].uniforms.extra_alpha, extra_alpha); glActiveTexture(GL_TEXTURE0 + GRAPHICS_UNIT); GraphicsUniforms *u = &graphics_program_layouts[program].uniforms; - glUniform4f(u->viewport, viewport.left, viewport.top, viewport.right, viewport.bottom); - glEnable(GL_CLIP_DISTANCE0); glEnable(GL_CLIP_DISTANCE1); glEnable(GL_CLIP_DISTANCE2); glEnable(GL_CLIP_DISTANCE3); for (GLuint i=0; i < count;) { ImageRenderData *group = data + start + i; glBindTexture(GL_TEXTURE_2D, group->texture_id); @@ -613,11 +597,9 @@ draw_graphics(int program, ssize_t vao_idx, ImageRenderData *data, GLuint start, ImageRenderData *rd = data + start + i; glUniform4f(u->src_rect, rd->src_rect.left, rd->src_rect.top, rd->src_rect.right, rd->src_rect.bottom); glUniform4f(u->dest_rect, rd->dest_rect.left, rd->dest_rect.top, rd->dest_rect.right, rd->dest_rect.bottom); - glDrawArrays(GL_TRIANGLE_FAN, 0, 4); + draw_quad(true, 0); } } - glDisable(GL_CLIP_DISTANCE0); glDisable(GL_CLIP_DISTANCE1); glDisable(GL_CLIP_DISTANCE2); glDisable(GL_CLIP_DISTANCE3); - bind_vertex_array(vao_idx); } static ImageRenderData* @@ -635,439 +617,49 @@ load_alpha_mask_texture(size_t width, size_t height, uint8_t *canvas) { } static void -gpu_data_for_centered_image(ImageRenderData *ans, unsigned int screen_width_px, unsigned int screen_height_px, unsigned int width, unsigned int height) { - float width_frac = 2 * MIN(1, width / (float)screen_width_px), height_frac = 2 * MIN(1, height / (float)screen_height_px); - float hmargin = (2 - width_frac) / 2; - float vmargin = (2 - height_frac) / 2; - gpu_data_for_image(ans, -1 + hmargin, 1 - vmargin, -1 + hmargin + width_frac, 1 - vmargin - height_frac); -} - - -void -draw_centered_alpha_mask(OSWindow *os_window, size_t screen_width, size_t screen_height, size_t width, size_t height, uint8_t *canvas, float background_opacity) { - ImageRenderData *data = load_alpha_mask_texture(width, height, canvas); - gpu_data_for_centered_image(data, screen_width, screen_height, width, height); - bind_program(GRAPHICS_ALPHA_MASK_PROGRAM); - glUniform1i(graphics_program_layouts[GRAPHICS_ALPHA_MASK_PROGRAM].uniforms.image, GRAPHICS_UNIT); - color_vec3(graphics_program_layouts[GRAPHICS_ALPHA_MASK_PROGRAM].uniforms.amask_fg, OPT(foreground)); - color_vec4_premult(graphics_program_layouts[GRAPHICS_ALPHA_MASK_PROGRAM].uniforms.amask_bg_premult, OPT(background), background_opacity); - glEnable(GL_BLEND); - if (os_window->is_semi_transparent) { - BLEND_PREMULT; - } else { - BLEND_ONTO_OPAQUE; - } - draw_graphics(GRAPHICS_ALPHA_MASK_PROGRAM, 0, data, 0, 1, (ImageRect){-1, 1, 1, -1}); - glDisable(GL_BLEND); -} - -static ImageRect -viewport_for_cells(const CellRenderData *crd) { - return (ImageRect){crd->gl.xstart, crd->gl.ystart, crd->gl.xstart + crd->gl.width, crd->gl.ystart - crd->gl.height}; +setup_texture_as_render_target(unsigned width, unsigned height, GLuint *texture_id, GLuint *framebuffer_id) { + glGenTextures(1, texture_id); glGenFramebuffers(1, framebuffer_id); + glBindTexture(GL_TEXTURE_2D, *texture_id); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); + bind_framebuffer_for_output(*framebuffer_id); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, *texture_id, 0); } static void -draw_cells_simple(ssize_t vao_idx, Screen *screen, const CellRenderData *crd, GraphicsRenderData grd, bool is_semi_transparent) { - bind_program(CELL_PROGRAM); - glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, screen->lines * screen->columns); - if (grd.count) { - glEnable(GL_BLEND); - int program = GRAPHICS_PROGRAM; - if (is_semi_transparent) { BLEND_PREMULT; program = GRAPHICS_PREMULT_PROGRAM; } else { BLEND_ONTO_OPAQUE; } - draw_graphics(program, vao_idx, grd.images, 0, grd.count, viewport_for_cells(crd)); - glDisable(GL_BLEND); - } -} - -static bool -has_bgimage(OSWindow *w) { - return w->bgimage && w->bgimage->texture_id > 0; -} - -static void -draw_tint(bool premult, Screen *screen, const CellRenderData *crd) { - if (premult) { BLEND_PREMULT } else { BLEND_ONTO_OPAQUE_WITH_OPAQUE_OUTPUT } - bind_program(TINT_PROGRAM); - color_type window_bg = colorprofile_to_color(screen->color_profile, screen->color_profile->overridden.default_bg, screen->color_profile->configured.default_bg).rgb; -#define C(shift) srgb_color((window_bg >> shift) & 0xFF) * premult_factor - GLfloat premult_factor = premult ? OPT(background_tint) : 1.0f; - glUniform4f(tint_program_layout.uniforms.tint_color, C(16), C(8), C(0), OPT(background_tint)); -#undef C - glUniform4f(tint_program_layout.uniforms.edges, crd->gl.xstart, crd->gl.ystart - crd->gl.height, crd->gl.xstart + crd->gl.width, crd->gl.ystart); - glDrawArrays(GL_TRIANGLE_FAN, 0, 4); -} - -static bool -draw_scroll_indicator(bool premult, Screen *screen, const CellRenderData *crd) { - if (OPT(scrollback_indicator_opacity) <= 0 || screen->linebuf != screen->main_linebuf || !screen->scrolled_by) return false; - glEnable(GL_BLEND); - if (premult) { BLEND_PREMULT } else { BLEND_ONTO_OPAQUE } - bind_program(TINT_PROGRAM); - const color_type bar_color = colorprofile_to_color(screen->color_profile, screen->color_profile->overridden.highlight_bg, screen->color_profile->configured.highlight_bg).rgb; - GLfloat alpha = OPT(scrollback_indicator_opacity); - float frac = (float)screen->scrolled_by / (float)screen->historybuf->count; - const GLfloat bar_height = crd->gl.dy; - GLfloat bottom = (crd->gl.ystart - crd->gl.height); - bottom += MAX(0, crd->gl.height - bar_height) * frac; -#define C(shift) srgb_color((bar_color >> shift) & 0xFF) * premult_factor - GLfloat premult_factor = premult ? alpha : 1.0f; - glUniform4f(tint_program_layout.uniforms.tint_color, C(16), C(8), C(0), alpha); -#undef C - GLfloat width = 0.5f * crd->gl.dx; - GLfloat left = (GLfloat)(crd->gl.xstart + (screen->columns * crd->gl.dx - width)); - glUniform4f(tint_program_layout.uniforms.edges, left, bottom, left + width, bottom + bar_height); - glDrawArrays(GL_TRIANGLE_FAN, 0, 4); - glDisable(GL_BLEND); - return true; -} - - -static float prev_inactive_text_alpha = -1; - -static void -set_cell_uniforms(float current_inactive_text_alpha, bool force) { +set_cell_uniforms(bool force) { static bool constants_set = false; if (!constants_set || force) { float text_contrast = 1.0f + OPT(text_contrast) * 0.01f; float text_gamma_adjustment = OPT(text_gamma_adjustment) < 0.01f ? 1.0f : 1.0f / OPT(text_gamma_adjustment); - - for (int i = GRAPHICS_PROGRAM; i <= GRAPHICS_PREMULT_PROGRAM; i++) { + for (int i = GRAPHICS_PROGRAM; i <= GRAPHICS_ALPHA_MASK_PROGRAM; i++) { bind_program(i); glUniform1i(graphics_program_layouts[i].uniforms.image, GRAPHICS_UNIT); } - for (int i = CELL_PROGRAM; i <= CELL_FG_PROGRAM; i++) { + for (int i = CELL_PROGRAM; i < CELL_PROGRAM_SENTINEL; i++) { bind_program(i); const CellUniforms *cu = &cell_program_layouts[i].uniforms; - switch(i) { - case CELL_PROGRAM: case CELL_FG_PROGRAM: - glUniform1i(cu->sprites, SPRITE_MAP_UNIT); - glUniform1i(cu->sprite_decorations_map, SPRITE_DECORATIONS_MAP_UNIT); - glUniform1f(cu->dim_opacity, OPT(dim_opacity)); - glUniform1f(cu->text_contrast, text_contrast); - glUniform1f(cu->text_gamma_adjustment, text_gamma_adjustment); - break; - } + glUniform1i(cu->sprites, SPRITE_MAP_UNIT); + glUniform1i(cu->sprite_decorations_map, SPRITE_DECORATIONS_MAP_UNIT); + glUniform1f(cu->text_contrast, text_contrast); + glUniform1f(cu->text_gamma_adjustment, text_gamma_adjustment); } + bind_program(BLIT_PROGRAM); glUniform1i(blit_program_layout.uniforms.image, GRAPHICS_UNIT); constants_set = true; } - if (current_inactive_text_alpha != prev_inactive_text_alpha || force) { - prev_inactive_text_alpha = current_inactive_text_alpha; - for (int i = GRAPHICS_PROGRAM; i <= GRAPHICS_PREMULT_PROGRAM; i++) { - bind_program(i); glUniform1f(graphics_program_layouts[i].uniforms.inactive_text_alpha, current_inactive_text_alpha); - } -#define S(prog, loc) bind_program(prog); glUniform1f(cell_program_layouts[prog].uniforms.inactive_text_alpha, current_inactive_text_alpha); - S(CELL_PROGRAM, cploc); S(CELL_FG_PROGRAM, cfploc); -#undef S - } } -static GLfloat -render_a_bar(OSWindow *os_window, Screen *screen, const CellRenderData *crd, WindowBarData *bar, PyObject *title, bool along_bottom) { - GLfloat left = os_window->viewport_width * (crd->gl.xstart + 1.f) / 2.f; - GLfloat right = left + os_window->viewport_width * crd->gl.width / 2.f; - unsigned bar_height = os_window->fonts_data->fcm.cell_height + 2; - if (!bar_height || right <= left) return 0; - unsigned bar_width = (unsigned)ceilf(right - left); - if (!bar->buf || bar->width != bar_width || bar->height != bar_height) { - free(bar->buf); - bar->buf = malloc((size_t)4 * bar_width * bar_height); - if (!bar->buf) return 0; - bar->height = bar_height; - bar->width = bar_width; - bar->needs_render = true; - } - - if (bar->last_drawn_title_object_id != title || bar->needs_render) { - static char titlebuf[2048] = {0}; - if (!title) return 0; - snprintf(titlebuf, arraysz(titlebuf), " %s", PyUnicode_AsUTF8(title)); -#define RGBCOL(which, fallback) ( 0xff000000 | colorprofile_to_color_with_fallback(screen->color_profile, screen->color_profile->overridden.which, screen->color_profile->configured.which, screen->color_profile->overridden.fallback, screen->color_profile->configured.fallback)) - if (!draw_window_title(os_window, titlebuf, RGBCOL(highlight_fg, default_fg), RGBCOL(highlight_bg, default_bg), bar->buf, bar_width, bar_height)) return 0; -#undef RGBCOL - Py_CLEAR(bar->last_drawn_title_object_id); - bar->last_drawn_title_object_id = title; - Py_INCREF(bar->last_drawn_title_object_id); - } - static ImageRenderData data = {.group_count=1}; - GLfloat xstart, ystart; - xstart = clamp_position_to_nearest_pixel(crd->gl.xstart, os_window->viewport_width); - GLfloat height_gl = gl_size(bar_height, os_window->viewport_height); - if (along_bottom) ystart = crd->gl.ystart - crd->gl.height + height_gl; - else ystart = clamp_position_to_nearest_pixel(crd->gl.ystart, os_window->viewport_height); - gpu_data_for_image(&data, xstart, ystart, xstart + crd->gl.width, ystart - height_gl); - if (!data.texture_id) { glGenTextures(1, &data.texture_id); } - glBindTexture(GL_TEXTURE_2D, data.texture_id); - glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexImage2D(GL_TEXTURE_2D, 0, GL_SRGB_ALPHA, bar_width, bar_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, bar->buf); - set_cell_uniforms(1.f, false); - bind_program(GRAPHICS_PROGRAM); - glEnable(GL_BLEND); - if (os_window->is_semi_transparent) { BLEND_PREMULT; } else { BLEND_ONTO_OPAQUE; } - draw_graphics(GRAPHICS_PROGRAM, 0, &data, 0, 1, viewport_for_cells(crd)); - glDisable(GL_BLEND); - return height_gl; -} - -static void -draw_hyperlink_target(OSWindow *os_window, Screen *screen, const CellRenderData *crd, Window *window) { - WindowBarData *bd = &window->url_target_bar_data; - if (bd->hyperlink_id_for_title_object != screen->current_hyperlink_under_mouse.id) { - bd->hyperlink_id_for_title_object = screen->current_hyperlink_under_mouse.id; - Py_CLEAR(bd->last_drawn_title_object_id); - const char *url = get_hyperlink_for_id(screen->hyperlink_pool, bd->hyperlink_id_for_title_object, true); - if (url == NULL) url = ""; - bd->last_drawn_title_object_id = PyObject_CallMethod(global_state.boss, "sanitize_url_for_dispay_to_user", "s", url); - if (bd->last_drawn_title_object_id == NULL) { PyErr_Print(); return; } - bd->needs_render = true; - } - if (bd->last_drawn_title_object_id == NULL) return; - const bool along_bottom = screen->current_hyperlink_under_mouse.y < 3; - PyObject *ref = bd->last_drawn_title_object_id; - Py_INCREF(ref); - render_a_bar(os_window, screen, crd, &window->title_bar_data, bd->last_drawn_title_object_id, along_bottom); - Py_DECREF(ref); -} - -static void -draw_window_logo(ssize_t vao_idx, OSWindow *os_window, const WindowLogoRenderData *wl, const CellRenderData *crd) { - if (os_window->live_resize.in_progress) return; - BLEND_PREMULT; - GLfloat logo_width_gl = gl_size(wl->instance->width, os_window->viewport_width); - GLfloat logo_height_gl = gl_size(wl->instance->height, os_window->viewport_height); - - if (OPT(window_logo_scale.width) > 0 || OPT(window_logo_scale.height) > 0) { - unsigned int scaled_wl_width = os_window->viewport_width; - unsigned int scaled_wl_height = os_window->viewport_height; - - // [sx] Scales logo to sx % of the viewports shortest dimension, preserving aspect ratio - if (OPT(window_logo_scale.height) < 0) { - if (os_window->viewport_height < os_window->viewport_width) { - scaled_wl_height = (int)(os_window->viewport_height * OPT(window_logo_scale.width) / 100); - scaled_wl_width = wl->instance->width * scaled_wl_height / wl->instance->height; - } else { - scaled_wl_width = (int)(os_window->viewport_width * OPT(window_logo_scale.width) / 100); - scaled_wl_height = wl->instance->height * scaled_wl_width / wl->instance->width; - } - } - // [0 sy] Scales logo's y dimension to sy % of viewporty keeping original x dimension - else if (OPT(window_logo_scale.width) == 0.0) { - scaled_wl_height = (int)(scaled_wl_height * OPT(window_logo_scale.height) / 100); - scaled_wl_width = wl->instance->width; - } - // [sx 0] Scales logo's x dimension to sx % of viewportx keeping original y dimension - else if (OPT(window_logo_scale.height) == 0.0) { - scaled_wl_width = (int)(scaled_wl_width * OPT(window_logo_scale.width) / 100); - scaled_wl_height = wl->instance->height; - } - // [sx sy] Scales logo's x and y dimension to sx and sy % of viewportx and viewporty respectively - else { - scaled_wl_height = (int)(scaled_wl_height * OPT(window_logo_scale.height) / 100); - scaled_wl_width = (int)(scaled_wl_width * OPT(window_logo_scale.width) / 100); - } - - logo_height_gl = gl_size(scaled_wl_height, os_window->viewport_height); - logo_width_gl = gl_size(scaled_wl_width, os_window->viewport_width); - } - - GLfloat logo_left_gl = clamp_position_to_nearest_pixel( - crd->gl.xstart + crd->gl.width * wl->position.canvas_x - logo_width_gl * wl->position.image_x, os_window->viewport_width); - GLfloat logo_top_gl = clamp_position_to_nearest_pixel( - crd->gl.ystart - crd->gl.height * wl->position.canvas_y + logo_height_gl * wl->position.image_y, os_window->viewport_height); - static ImageRenderData ird = {.group_count=1}; - ird.texture_id = wl->instance->texture_id; - gpu_data_for_image(&ird, logo_left_gl, logo_top_gl, logo_left_gl + logo_width_gl, logo_top_gl - logo_height_gl); - bind_program(GRAPHICS_PREMULT_PROGRAM); - glUniform1f(graphics_program_layouts[GRAPHICS_PREMULT_PROGRAM].uniforms.inactive_text_alpha, prev_inactive_text_alpha * wl->alpha); - draw_graphics(GRAPHICS_PREMULT_PROGRAM, vao_idx, &ird, 0, 1, viewport_for_cells(crd)); - glUniform1f(graphics_program_layouts[GRAPHICS_PREMULT_PROGRAM].uniforms.inactive_text_alpha, prev_inactive_text_alpha); -} - -static void -draw_window_number(OSWindow *os_window, Screen *screen, const CellRenderData *crd, Window *window) { - GLfloat left = os_window->viewport_width * (crd->gl.xstart + 1.f) / 2.f; - GLfloat right = left + os_window->viewport_width * crd->gl.width / 2.f; - GLfloat title_bar_height = 0; - size_t requested_height = (size_t)(os_window->viewport_height * crd->gl.height / 2.f); - if (window->title && PyUnicode_Check(window->title) && (requested_height > (os_window->fonts_data->fcm.cell_height + 1) * 2)) { - title_bar_height = render_a_bar(os_window, screen, crd, &window->title_bar_data, window->title, false); - } - GLfloat ystart = crd->gl.ystart, height = crd->gl.height, xstart = crd->gl.xstart, width = crd->gl.width; - if (title_bar_height > 0) { - ystart -= title_bar_height; - height -= title_bar_height; - } - ystart -= crd->gl.dy / 2.f; height -= crd->gl.dy; // top and bottom margins - xstart += crd->gl.dx / 2.f; width -= crd->gl.dx; // left and right margins - GLfloat height_gl = MIN(MIN(12 * crd->gl.dy, height), width); - requested_height = (size_t)(os_window->viewport_height * height_gl / 2.f); - if (requested_height < 4) return; -#define lr screen->last_rendered_window_char - if (!lr.canvas || lr.ch != screen->display_window_char || lr.requested_height != requested_height) { - free(lr.canvas); lr.canvas = NULL; - lr.requested_height = requested_height; lr.height_px = requested_height; lr.ch = 0; - lr.canvas = draw_single_ascii_char(screen->display_window_char, &lr.width_px, &lr.height_px); - if (lr.height_px < 4 || lr.width_px < 4 || !lr.canvas) return; - lr.ch = screen->display_window_char; - } - - GLfloat width_gl = gl_size(lr.width_px, os_window->viewport_width); - height_gl = gl_size(lr.height_px, os_window->viewport_height); - left = xstart + (width - width_gl) / 2.f; - left = clamp_position_to_nearest_pixel(left, os_window->viewport_width); - right = left + width_gl; - GLfloat top = ystart - (height - height_gl) / 2.f; - top = clamp_position_to_nearest_pixel(top, os_window->viewport_height); - GLfloat bottom = top - height_gl; - bind_program(GRAPHICS_ALPHA_MASK_PROGRAM); - ImageRenderData *ird = load_alpha_mask_texture(lr.width_px, lr.height_px, lr.canvas); -#undef lr - gpu_data_for_image(ird, left, top, right, bottom); - glEnable(GL_BLEND); - BLEND_PREMULT; - glUniform1i(graphics_program_layouts[GRAPHICS_ALPHA_MASK_PROGRAM].uniforms.image, GRAPHICS_UNIT); - color_type digit_color = colorprofile_to_color_with_fallback(screen->color_profile, screen->color_profile->overridden.highlight_bg, screen->color_profile->configured.highlight_bg, screen->color_profile->overridden.default_fg, screen->color_profile->configured.default_fg); - color_vec3(graphics_program_layouts[GRAPHICS_ALPHA_MASK_PROGRAM].uniforms.amask_fg, digit_color); - glUniform4f(graphics_program_layouts[GRAPHICS_ALPHA_MASK_PROGRAM].uniforms.amask_bg_premult, 0.f, 0.f, 0.f, 0.f); - draw_graphics(GRAPHICS_ALPHA_MASK_PROGRAM, 0, ird, 0, 1, viewport_for_cells(crd)); - glDisable(GL_BLEND); -} - -static void -draw_visual_bell_flash(GLfloat intensity, const CellRenderData *crd, Screen *screen) { - glEnable(GL_BLEND); - // BLEND_PREMULT - glBlendFuncSeparate(GL_ONE, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE); - bind_program(TINT_PROGRAM); - GLfloat attenuation = 0.4f; -#define COLOR(name, fallback) colorprofile_to_color_with_fallback(screen->color_profile, screen->color_profile->overridden.name, screen->color_profile->configured.name, screen->color_profile->overridden.fallback, screen->color_profile->configured.fallback) - const color_type flash = !IS_SPECIAL_COLOR(highlight_bg) ? COLOR(visual_bell_color, highlight_bg) : COLOR(visual_bell_color, default_fg); -#undef COLOR -#define C(shift) srgb_color((flash >> shift) & 0xFF) - const GLfloat r = C(16), g = C(8), b = C(0); - const GLfloat max_channel = r > g ? (r > b ? r : b) : (g > b ? g : b); -#undef C -#define C(x) (x * intensity * attenuation) - if (max_channel > 0.45) attenuation = 0.6f; // light color - glUniform4f(tint_program_layout.uniforms.tint_color, C(r), C(g), C(b), C(1)); -#undef C - glUniform4f(tint_program_layout.uniforms.edges, crd->gl.xstart, crd->gl.ystart - crd->gl.height, crd->gl.xstart + crd->gl.width, crd->gl.ystart); - glDrawArrays(GL_TRIANGLE_FAN, 0, 4); - glDisable(GL_BLEND); -} - -static void -draw_cells_interleaved(ssize_t vao_idx, Screen *screen, OSWindow *w, const CellRenderData *crd, GraphicsRenderData grd, const WindowLogoRenderData *wl) { - glEnable(GL_BLEND); - BLEND_ONTO_OPAQUE; - - // draw background for all cells - if (!has_bgimage(w)) { - bind_program(CELL_BG_PROGRAM); - glUniform1ui(cell_program_layouts[CELL_BG_PROGRAM].uniforms.draw_bg_bitfield, 3); - glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, screen->lines * screen->columns); - } else if (OPT(background_tint) > 0) { - draw_tint(false, screen, crd); - BLEND_ONTO_OPAQUE; - } - - if (grd.num_of_below_refs || has_bgimage(w) || wl) { - if (wl) { - draw_window_logo(vao_idx, w, wl, crd); - BLEND_ONTO_OPAQUE; - } - if (grd.num_of_below_refs) draw_graphics( - GRAPHICS_PROGRAM, vao_idx, grd.images, 0, grd.num_of_below_refs, viewport_for_cells(crd)); - bind_program(CELL_BG_PROGRAM); - // draw background for non-default bg cells - glUniform1ui(cell_program_layouts[CELL_BG_PROGRAM].uniforms.draw_bg_bitfield, 2); - glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, screen->lines * screen->columns); - } - - if (grd.num_of_negative_refs) draw_graphics(GRAPHICS_PROGRAM, vao_idx, grd.images, grd.num_of_below_refs, grd.num_of_negative_refs, viewport_for_cells(crd)); - - bind_program(CELL_SPECIAL_PROGRAM); - glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, screen->lines * screen->columns); - - bind_program(CELL_FG_PROGRAM); - BLEND_PREMULT; - glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, screen->lines * screen->columns); - BLEND_ONTO_OPAQUE; - - if (grd.num_of_positive_refs) draw_graphics(GRAPHICS_PROGRAM, vao_idx, grd.images, grd.num_of_negative_refs + grd.num_of_below_refs, grd.num_of_positive_refs, viewport_for_cells(crd)); - - glDisable(GL_BLEND); -} - -static void -draw_cells_interleaved_premult(ssize_t vao_idx, Screen *screen, OSWindow *os_window, const CellRenderData *crd, GraphicsRenderData grd, const WindowLogoRenderData *wl) { - if (OPT(background_tint) > 0.f) { - glEnable(GL_BLEND); - draw_tint(true, screen, crd); - glDisable(GL_BLEND); - } - bind_program(CELL_BG_PROGRAM); - if (!has_bgimage(os_window)) { - // draw background for all cells - glUniform1ui(cell_program_layouts[CELL_BG_PROGRAM].uniforms.draw_bg_bitfield, 3); - glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, screen->lines * screen->columns); - } - glEnable(GL_BLEND); - BLEND_PREMULT; - - if (grd.num_of_below_refs || has_bgimage(os_window) || wl) { - if (wl) { - draw_window_logo(vao_idx, os_window, wl, crd); - BLEND_PREMULT; - } - if (grd.num_of_below_refs) draw_graphics( - GRAPHICS_PREMULT_PROGRAM, vao_idx, grd.images, 0, grd.num_of_below_refs, viewport_for_cells(crd)); - bind_program(CELL_BG_PROGRAM); - // Draw background for non-default bg cells - glUniform1ui(cell_program_layouts[CELL_BG_PROGRAM].uniforms.draw_bg_bitfield, 2); - glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, screen->lines * screen->columns); - } else { - // Apply background_opacity - glUniform1ui(cell_program_layouts[CELL_BG_PROGRAM].uniforms.draw_bg_bitfield, 0); - glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, screen->lines * screen->columns); - } - - if (grd.num_of_negative_refs) { - draw_graphics(GRAPHICS_PREMULT_PROGRAM, vao_idx, grd.images, grd.num_of_below_refs, grd.num_of_negative_refs, viewport_for_cells(crd)); - } - - bind_program(CELL_SPECIAL_PROGRAM); - glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, screen->lines * screen->columns); - - bind_program(CELL_FG_PROGRAM); - glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, screen->lines * screen->columns); - - if (grd.num_of_positive_refs) draw_graphics(GRAPHICS_PREMULT_PROGRAM, vao_idx, grd.images, grd.num_of_negative_refs + grd.num_of_below_refs, grd.num_of_positive_refs, viewport_for_cells(crd)); - - glDisable(GL_BLEND); -} - -void -blank_canvas(float background_opacity, color_type color) { - // See https://github.com/glfw/glfw/issues/1538 for why we use pre-multiplied alpha -#define C(shift) srgb_color((color >> shift) & 0xFF) - glClearColor(C(16), C(8), C(0), background_opacity); -#undef C - glClear(GL_COLOR_BUFFER_BIT); -} - -bool -send_cell_data_to_gpu(ssize_t vao_idx, GLfloat xstart, GLfloat ystart, GLfloat dx, GLfloat dy, Screen *screen, OSWindow *os_window) { - bool changed = false; - if (os_window->fonts_data) { - if (cell_prepare_to_render(vao_idx, screen, xstart, ystart, dx, dy, os_window->fonts_data)) changed = true; - } - return changed; -} +// UI Layer {{{ static Animation *default_visual_bell_animation = NULL; +static bool +has_visual_bell(Screen *screen) { + return screen->start_visual_bell_at > 0; + +} + static float get_visual_bell_intensity(Screen *screen) { if (screen->start_visual_bell_at > 0) { @@ -1088,68 +680,332 @@ get_visual_bell_intensity(Screen *screen) { return 0.0f; } -void -draw_cells(ssize_t vao_idx, const WindowRenderData *srd, OSWindow *os_window, bool is_active_window, bool is_tab_bar, bool is_single_window, Window *window) { - float x_ratio = 1., y_ratio = 1.; - if (os_window->live_resize.in_progress) { - x_ratio = (float) os_window->viewport_width / (float) os_window->live_resize.width; - y_ratio = (float) os_window->viewport_height / (float) os_window->live_resize.height; +static void +draw_visual_bell_flash(GLfloat intensity, const color_type flash) { + bind_program(TINT_PROGRAM); + GLfloat attenuation = 0.4f; +#define C(shift) srgb_color((flash >> shift) & 0xFF) + const GLfloat r = C(16), g = C(8), b = C(0); + const GLfloat max_channel = r > g ? (r > b ? r : b) : (g > b ? g : b); +#undef C +#define C(x) (x * intensity * attenuation) + if (max_channel > 0.45) attenuation = 0.6f; // light color + glUniform4f(tint_program_layout.uniforms.tint_color, C(r), C(g), C(b), C(1)); +#undef C + glUniform4f(tint_program_layout.uniforms.edges, -1, 1, 1, -1); + draw_quad(true, 0); +} + +static void +draw_visual_bell(const UIRenderData *ui) { + if (!has_visual_bell(ui->screen)) return; + Screen *screen = ui->screen; + float intensity = get_visual_bell_intensity(screen); + if (intensity <= 0) return; +#define COLOR(name, fallback) colorprofile_to_color_with_fallback(screen->color_profile, screen->color_profile->overridden.name, screen->color_profile->configured.name, screen->color_profile->overridden.fallback, screen->color_profile->configured.fallback) + color_type flash = !IS_SPECIAL_COLOR(highlight_bg) ? COLOR(visual_bell_color, highlight_bg) : COLOR(visual_bell_color, default_fg); + draw_visual_bell_flash(intensity, flash); +#undef COLOR +} + +static bool +has_scrollbar(Screen *screen) { + return OPT(scrollback_indicator_opacity) > 0 && screen->linebuf == screen->main_linebuf && screen->scrolled_by; +} + +static bool +draw_scroll_indicator(color_type bar_color, GLfloat alpha, float frac, const UIRenderData *ui) { + bind_program(TINT_PROGRAM); +#define C(shift) srgb_color((bar_color >> shift) & 0xFF) * alpha + glUniform4f(tint_program_layout.uniforms.tint_color, C(16), C(8), C(0), alpha); +#undef C + float bar_width = 0.5f * gl_size(ui->cell_width, ui->screen_width); + float bar_height = gl_size(ui->cell_height, ui->screen_height); + float bottom = -1.f + MAX(0, 2.f - bar_height) * frac; + glUniform4f(tint_program_layout.uniforms.edges, 1.f - bar_width, bottom + bar_height, 1.f, bottom); + draw_quad(true, 0); + return true; +} + +static unsigned +render_a_bar(const UIRenderData *ui, WindowBarData *bar, PyObject *title, bool along_bottom) { + unsigned bar_height = ui->cell_height + 2; + unsigned bar_width = ui->screen_width; + if (!bar->buf || bar->width != bar_width || bar->height != bar_height) { + free(bar->buf); + bar->buf = malloc((size_t)4 * bar_width * bar_height); + if (!bar->buf) return 0; + bar->height = bar_height; + bar->width = bar_width; + bar->needs_render = true; } + + if (bar->last_drawn_title_object_id != title || bar->needs_render) { + static char titlebuf[2048] = {0}; + if (!title) return 0; + snprintf(titlebuf, arraysz(titlebuf), " %s", PyUnicode_AsUTF8(title)); +#define RGBCOL(which, fallback) ( 0xff000000 | colorprofile_to_color_with_fallback(ui->screen->color_profile, ui->screen->color_profile->overridden.which, ui->screen->color_profile->configured.which, ui->screen->color_profile->overridden.fallback, ui->screen->color_profile->configured.fallback)) + if (!draw_window_title(ui->os_window->fonts_data->font_sz_in_pts, ui->os_window->fonts_data->logical_dpi_y, titlebuf, RGBCOL(highlight_fg, default_fg), RGBCOL(highlight_bg, default_bg), bar->buf, bar_width, bar_height)) return 0; +#undef RGBCOL + Py_CLEAR(bar->last_drawn_title_object_id); + bar->last_drawn_title_object_id = Py_NewRef(title); + } + static ImageRenderData data = {.group_count=1}; + gpu_data_for_image(&data, -1, 1, 1, -1); + glGenTextures(1, &data.texture_id); + glBindTexture(GL_TEXTURE_2D, data.texture_id); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexImage2D(GL_TEXTURE_2D, 0, GL_SRGB_ALPHA, bar_width, bar_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, bar->buf); + bind_program(GRAPHICS_PROGRAM); + // current viewport is (0, 0, ui->screen_width, ui->screen_height) coz we are rendering to FBO texture + if (along_bottom) save_viewport_using_bottom_left_origin(0, 0, ui->screen_width, bar_height); + else save_viewport_using_top_left_origin(0, 0, ui->screen_width, bar_height, ui->screen_height); + draw_graphics(GRAPHICS_PROGRAM, &data, 0, 1, 1.f); + restore_viewport(); + free_texture(&data.texture_id); + return bar_height; +} + +static bool +has_hyperlink_target(OSWindow *os_window, Window *w, Screen *screen) { + return OPT(show_hyperlink_targets) && screen->current_hyperlink_under_mouse.id && w && !is_mouse_hidden(os_window); +} + +static void +draw_hyperlink_target(const UIRenderData *ui) { + if (!has_hyperlink_target(ui->os_window, ui->window, ui->screen)) return; + Screen *screen = ui->screen; + const bool along_bottom = screen->current_hyperlink_under_mouse.y < 3; + Window *window = ui->window; + WindowBarData *bd = &window->url_target_bar_data; + if (bd->hyperlink_id_for_title_object != screen->current_hyperlink_under_mouse.id) { + bd->hyperlink_id_for_title_object = screen->current_hyperlink_under_mouse.id; + Py_CLEAR(bd->last_drawn_title_object_id); + const char *url = get_hyperlink_for_id(screen->hyperlink_pool, bd->hyperlink_id_for_title_object, true); + if (url == NULL) url = ""; + bd->last_drawn_title_object_id = PyObject_CallMethod(global_state.boss, "sanitize_url_for_display_to_user", "s", url); + if (bd->last_drawn_title_object_id == NULL) { PyErr_Print(); return; } + bd->needs_render = true; + } + if (bd->last_drawn_title_object_id == NULL) return; + PyObject *ref = Py_NewRef(bd->last_drawn_title_object_id); // render_a_bar clears bd->last_drawn_title_object_id + render_a_bar(ui, &window->title_bar_data, bd->last_drawn_title_object_id, along_bottom); + Py_DECREF(ref); +} + +static bool +has_window_number(Window *w, Screen *screen) { + return w != NULL && screen->display_window_char != 0; +} + +static void +draw_window_number(const UIRenderData *ui) { + if (!has_window_number(ui->window, ui->screen)) return; + unsigned title_bar_height = 0, requested_height = ui->screen_height; + if (ui->window->title && PyUnicode_Check(ui->window->title) && (requested_height > (ui->cell_height + 1) * 2)) { + title_bar_height = render_a_bar(ui, &ui->window->title_bar_data, ui->window->title, false); + } + unsigned height_for_letter = ui->screen_height - title_bar_height - ui->cell_height; + unsigned width_for_letter = ui->screen_width - ui->cell_width; + requested_height = MIN(12 * ui->cell_height, MIN(height_for_letter, width_for_letter)); + if (requested_height < 4) return; +#define lr ui->screen->last_rendered_window_char + if (!lr.canvas || lr.ch != ui->screen->display_window_char || lr.requested_height != requested_height) { + free(lr.canvas); lr.canvas = NULL; + lr.requested_height = requested_height; lr.height_px = requested_height; lr.ch = 0; + lr.canvas = draw_single_ascii_char(ui->screen->display_window_char, &lr.width_px, &lr.height_px); + if (lr.height_px < 4 || lr.width_px < 4 || !lr.canvas) return; + lr.ch = ui->screen->display_window_char; + } + unsigned letter_x = 0, letter_y = title_bar_height; + if (lr.width_px < ui->screen_width) letter_x = (ui->screen_width - lr.width_px) / 2; + if (lr.height_px + title_bar_height < ui->screen_height) letter_y += (ui->screen_height - lr.height_px - title_bar_height) / 2; + bind_program(GRAPHICS_ALPHA_MASK_PROGRAM); + ImageRenderData *ird = load_alpha_mask_texture(lr.width_px, lr.height_px, lr.canvas); + gpu_data_for_image(ird, -1, 1, 1, -1); + glUniform1i(graphics_program_layouts[GRAPHICS_ALPHA_MASK_PROGRAM].uniforms.image, GRAPHICS_UNIT); + color_type digit_color = colorprofile_to_color_with_fallback(ui->screen->color_profile, ui->screen->color_profile->overridden.highlight_bg, ui->screen->color_profile->configured.highlight_bg, ui->screen->color_profile->overridden.default_fg, ui->screen->color_profile->configured.default_fg); + color_vec3(graphics_program_layouts[GRAPHICS_ALPHA_MASK_PROGRAM].uniforms.amask_fg, digit_color); + glUniform4f(graphics_program_layouts[GRAPHICS_ALPHA_MASK_PROGRAM].uniforms.amask_bg_premult, 0.f, 0.f, 0.f, 0.f); + save_viewport_using_top_left_origin(letter_x, letter_y, lr.width_px, lr.height_px, ui->screen_height); + draw_graphics(GRAPHICS_ALPHA_MASK_PROGRAM, ird, 0, 1, 1.f); + restore_viewport(); +#undef lr +} + +static void +draw_scrollbar(const UIRenderData *ui) { + if (!has_scrollbar(ui->screen)) return; + Screen *screen = ui->screen; + color_type bar_color = colorprofile_to_color(screen->color_profile, screen->color_profile->overridden.highlight_bg, screen->color_profile->configured.highlight_bg).rgb; + float bar_frac = (float)screen->scrolled_by / (float)screen->historybuf->count; + draw_scroll_indicator(bar_color, OPT(scrollback_indicator_opacity), bar_frac, ui); +} + +static void +draw_window_logo(const UIRenderData *ui) { + struct { unsigned width, height; int left, top; } w; + WindowLogoRenderData *wl = ui->window_logo; + w.height = wl->instance->height; w.width = wl->instance->width; + if (OPT(window_logo_scale.width) > 0 || OPT(window_logo_scale.height) > 0) { + unsigned scaled_wl_width = ui->screen_width, scaled_wl_height = ui->screen_height; + + // [sx] Scales logo to sx % of the viewports shortest dimension, preserving aspect ratio + if (OPT(window_logo_scale.height) < 0) { + if (ui->screen_height < ui->screen_width) { + scaled_wl_height = (int)(ui->screen_height * OPT(window_logo_scale.width) / 100); + scaled_wl_width = wl->instance->width * scaled_wl_height / wl->instance->height; + } else { + scaled_wl_width = (int)(ui->screen_width * OPT(window_logo_scale.width) / 100); + scaled_wl_height = wl->instance->height * scaled_wl_width / wl->instance->width; + } + } + // [0 sy] Scales logo's y dimension to sy % of viewporty keeping original x dimension + else if (OPT(window_logo_scale.width) == 0.0) { + scaled_wl_height = (int)(scaled_wl_height * OPT(window_logo_scale.height) / 100); + scaled_wl_width = wl->instance->width; + } + // [sx 0] Scales logo's x dimension to sx % of viewportx keeping original y dimension + else if (OPT(window_logo_scale.height) == 0.0) { + scaled_wl_width = (int)(scaled_wl_width * OPT(window_logo_scale.width) / 100); + scaled_wl_height = wl->instance->height; + } + // [sx sy] Scales logo's x and y dimension to sx and sy % of viewportx and viewporty respectively + else { + scaled_wl_height = (int)(scaled_wl_height * OPT(window_logo_scale.height) / 100); + scaled_wl_width = (int)(scaled_wl_width * OPT(window_logo_scale.width) / 100); + } + w.width = scaled_wl_width; w.height = scaled_wl_height; + } + w.left = (int)(ui->screen_width * wl->position.canvas_x - w.width * wl->position.image_x); + w.top = (int)(ui->screen_height * wl->position.canvas_y - w.height * wl->position.image_y); + float left = gl_pos_x(w.left, ui->screen_width), top = gl_pos_y(w.top, ui->screen_height); + ImageRenderData d = {.texture_id = wl->instance->texture_id}; + gpu_data_for_image(&d, left, top, left + gl_size(w.width, ui->screen_width), top - gl_size(w.height, ui->screen_height)); + draw_graphics(GRAPHICS_PROGRAM, &d, 0, 1, ui->inactive_text_alpha * OPT(window_logo_alpha)); +} + +bool +screen_needs_rendering_in_layers(OSWindow *os_window, Window *w, Screen *screen) { + const bool has_ui = has_visual_bell(screen) || has_scrollbar(screen) || has_hyperlink_target(os_window, w, screen) || has_window_number(w, screen); + GraphicsManager *grman = screen->paused_rendering.expires_at && screen->paused_rendering.grman ? screen->paused_rendering.grman : screen->grman; + return has_ui || (w && w->window_logo.id) || grman_has_images(grman); +} + +// }}} + +enum { DRAW_NEITHER_BG = 0, DRAW_DEFAULT_BG = 1, DRAW_NON_DEFAULT_BG = 2, DRAW_BOTH_BG = 3}; + +static void +call_cell_program(int program, const UIRenderData *ui, ssize_t vao_idx, bool for_final_output, unsigned draw_bg_bitfield) { + bind_program(program); + CELL_BUFFERS; + bind_vao_uniform_buffer(vao_idx, uniform_buffer, cell_program_layouts[program].render_data.index); + glUniform1ui(cell_program_layouts[program].uniforms.draw_bg_bitfield, draw_bg_bitfield); + if (for_final_output) glEnable(GL_FRAMEBUFFER_SRGB); + draw_quad(!for_final_output, ui->screen->lines * ui->screen->columns); + if (for_final_output) glDisable(GL_FRAMEBUFFER_SRGB); +} + +static void +draw_cells_without_layers(const UIRenderData *ui, ssize_t vao_idx) { + call_cell_program(CELL_PROGRAM, ui, vao_idx, true, DRAW_BOTH_BG); +} + +static void +draw_tint(const UIRenderData *ui) { + bind_program(TINT_PROGRAM); + color_vec4_premult(tint_program_layout.uniforms.tint_color, ui->background_color, OPT(background_tint)); + glUniform4f(tint_program_layout.uniforms.edges, -1, 1, 1, -1); + draw_quad(true, 0); +} + +static void +draw_cells_with_layers(const UIRenderData *ui, ssize_t vao_idx) { + if (ui->has_background_image && OPT(background_tint) > 0) draw_tint(ui); + const bool has_content_between_background_and_foreground = ui->window_logo != NULL || ui->grd.num_of_below_refs > 0 || ui->grd.num_of_negative_refs > 0; + if (has_content_between_background_and_foreground) { + if (!ui->has_background_image) call_cell_program(CELL_BG_PROGRAM, ui, vao_idx, false, DRAW_DEFAULT_BG); + if (ui->window_logo != NULL) draw_window_logo(ui); + if (ui->grd.num_of_below_refs > 0) draw_graphics( + GRAPHICS_PROGRAM, ui->grd.images, 0, ui->grd.num_of_below_refs, ui->inactive_text_alpha); + call_cell_program(CELL_BG_PROGRAM, ui, vao_idx, false, DRAW_NON_DEFAULT_BG); + if (ui->grd.num_of_negative_refs) draw_graphics( + GRAPHICS_PROGRAM, ui->grd.images, ui->grd.num_of_below_refs, ui->grd.num_of_negative_refs, + ui->inactive_text_alpha); + call_cell_program(CELL_FG_PROGRAM, ui, vao_idx, false, DRAW_NEITHER_BG); + } else call_cell_program(CELL_PROGRAM, ui, vao_idx, false, ui->has_background_image ? DRAW_NON_DEFAULT_BG : DRAW_BOTH_BG); + + if (ui->grd.num_of_positive_refs > 0) draw_graphics( + GRAPHICS_PROGRAM, ui->grd.images, ui->grd.num_of_below_refs + ui->grd.num_of_negative_refs, + ui->grd.num_of_positive_refs, ui->inactive_text_alpha); + + draw_visual_bell(ui); + draw_scrollbar(ui); + draw_hyperlink_target(ui); + draw_window_number(ui); +} + +void +blank_canvas(float background_opacity, color_type color) { + // See https://github.com/glfw/glfw/issues/1538 for why we use pre-multiplied alpha +#define C(shift) srgb_color((color >> shift) & 0xFF) + glClearColor(C(16), C(8), C(0), background_opacity); +#undef C + glClear(GL_COLOR_BUFFER_BIT); +} + +bool +send_cell_data_to_gpu(ssize_t vao_idx, Screen *screen, OSWindow *os_window) { + bool changed = false; + if (os_window->fonts_data) { + if (cell_prepare_to_render(vao_idx, screen, os_window->fonts_data)) changed = true; + } + return changed; +} + +void +draw_cells(const WindowRenderData *srd, OSWindow *os_window, bool is_active_window, bool is_tab_bar, bool is_single_window, Window *window) { Screen *screen = srd->screen; CELL_BUFFERS; - CellRenderData crd = { - .gl={.xstart = srd->xstart, .ystart = srd->ystart, .dx = srd->dx * x_ratio, .dy = srd->dy * y_ratio}, - .x_ratio=x_ratio, .y_ratio=y_ratio - }; - crd.gl.width = crd.gl.dx * screen->columns; crd.gl.height = crd.gl.dy * screen->lines; - cell_update_uniform_block(vao_idx, screen, uniform_buffer, &crd, &screen->cursor_render_info, os_window); - - bind_vao_uniform_buffer(vao_idx, uniform_buffer, cell_program_layouts[CELL_PROGRAM].render_data.index); - bind_vertex_array(vao_idx); - + bind_vertex_array(srd->vao_idx); // We draw with inactive text alpha if: // - We're not drawing the tab bar // - There's only a single window and the os window is not focused // - There are multiple windows and the current window is not active float current_inactive_text_alpha = is_tab_bar || (!is_single_window && is_active_window) || (is_single_window && screen->cursor_render_info.is_focused) ? 1.0f : (float)OPT(inactive_text_alpha); - set_cell_uniforms(current_inactive_text_alpha, screen->reload_all_gpu_data); - screen->reload_all_gpu_data = false; - bool has_underlying_image = has_bgimage(os_window); + + color_type default_bg = cell_update_uniform_block( + srd->vao_idx, screen, uniform_buffer, &screen->cursor_render_info, os_window, current_inactive_text_alpha); + set_cell_uniforms(screen->reload_all_gpu_data); WindowLogoRenderData *wl; - if (window && (wl = &window->window_logo) && wl->id && (wl->instance = find_window_logo(global_state.all_window_logos, wl->id)) && wl->instance && wl->instance->load_from_disk_ok) { - has_underlying_image = true; - set_on_gpu_state(window->window_logo.instance, true); - } else wl = NULL; - ImageRenderData *scaled_render_data = NULL; - GraphicsManager *grman = screen->paused_rendering.expires_at && screen->paused_rendering.grman ? screen->paused_rendering.grman : screen->grman; - GraphicsRenderData grd = grman_render_data(grman); - if (os_window->live_resize.in_progress && grd.count && (crd.x_ratio != 1 || crd.y_ratio != 1)) { - scaled_render_data = malloc(sizeof(scaled_render_data[0]) * grd.count); - if (scaled_render_data) { - memcpy(scaled_render_data, grd.images, sizeof(scaled_render_data[0]) * grd.count); - grd.images = scaled_render_data; - for (size_t i = 0; i < grd.count; i++) - scale_rendered_graphic(grd.images + i, srd->xstart, srd->ystart, crd.x_ratio, crd.y_ratio); + if (window && (wl = &window->window_logo) && wl->id && (wl->instance = find_window_logo(global_state.all_window_logos, wl->id)) && wl->instance->load_from_disk_ok) { + if (!window->window_logo.instance->texture_id) { + set_on_gpu_state(window->window_logo.instance, true); } - } - has_underlying_image |= grd.num_of_below_refs > 0 || grd.num_of_negative_refs > 0; - if (os_window->is_semi_transparent) { - if (has_underlying_image) { draw_cells_interleaved_premult(vao_idx, screen, os_window, &crd, grd, wl); } - else draw_cells_simple(vao_idx, screen, &crd, grd, os_window->is_semi_transparent); - } else { - if (has_underlying_image) draw_cells_interleaved(vao_idx, screen, os_window, &crd, grd, wl); - else draw_cells_simple(vao_idx, screen, &crd, grd, os_window->is_semi_transparent); - } - draw_scroll_indicator(os_window->is_semi_transparent, screen, &crd); - - if (screen->start_visual_bell_at) { - GLfloat intensity = get_visual_bell_intensity(screen); - if (intensity > 0.0f) draw_visual_bell_flash(intensity, &crd, screen); - } - - if (window && screen->display_window_char) draw_window_number(os_window, screen, &crd, window); - if (OPT(show_hyperlink_targets) && window && screen->current_hyperlink_under_mouse.id && !is_mouse_hidden(os_window)) draw_hyperlink_target(os_window, screen, &crd, window); - free(scaled_render_data); + } else wl = NULL; + GraphicsManager *grman = screen->paused_rendering.expires_at && screen->paused_rendering.grman ? screen->paused_rendering.grman : screen->grman; + UIRenderData ui = { + .screen_width = srd->geometry.right - srd->geometry.left, + .screen_height = srd->geometry.bottom - srd->geometry.top, + .cell_width = os_window->fonts_data->fcm.cell_width, + .cell_height = os_window->fonts_data->fcm.cell_height, + .screen_left = srd->geometry.left, .screen_top = srd->geometry.top, + .full_framebuffer_width = os_window->viewport_width, .full_framebuffer_height = os_window->viewport_height, + .window = window, .screen = screen, .os_window = os_window, .grd = grman_render_data(grman), .window_logo = wl, + .inactive_text_alpha = current_inactive_text_alpha, .has_background_image = has_bgimage(os_window), + .background_color = default_bg, + }; + screen->reload_all_gpu_data = false; + save_viewport_using_top_left_origin( + ui.screen_left, ui.screen_top, ui.screen_width, ui.screen_height, ui.full_framebuffer_height); + if (ui.os_window->needs_layers) draw_cells_with_layers(&ui, srd->vao_idx); + else draw_cells_without_layers(&ui, srd->vao_idx); + restore_viewport(); } // }}} @@ -1181,69 +1037,38 @@ create_border_vao(void) { } void -draw_borders(ssize_t vao_idx, unsigned int num_border_rects, BorderRect *rect_buf, bool rect_data_is_dirty, uint32_t viewport_width, uint32_t viewport_height, color_type active_window_bg, unsigned int num_visible_windows, bool all_windows_have_same_bg, OSWindow *w) { +draw_borders(ssize_t vao_idx, unsigned int num_border_rects, BorderRect *rect_buf, bool rect_data_is_dirty, color_type active_window_bg, unsigned int num_visible_windows, bool all_windows_have_same_bg, OSWindow *w) { float background_opacity = w->is_semi_transparent ? w->background_opacity: 1.0f; - float tint_opacity = background_opacity; - float tint_premult = background_opacity; - bind_vertex_array(vao_idx); - if (has_bgimage(w)) { - glEnable(GL_BLEND); - BLEND_ONTO_OPAQUE; - draw_background_image(w); - BLEND_ONTO_OPAQUE; - background_opacity = 1.0f; - tint_opacity = OPT(background_tint) * OPT(background_tint_gaps); - tint_premult = w->is_semi_transparent ? OPT(background_tint) : 1.0f; + if (!num_border_rects) return; + bind_program(BORDERS_PROGRAM); bind_vertex_array(vao_idx); + const bool has_background_image = has_bgimage(w); + if (has_background_image) background_opacity = OPT(background_tint) * OPT(background_tint_gaps); + if (rect_data_is_dirty) { + const size_t sz = sizeof(BorderRect) * num_border_rects; + void *borders_buf_address = alloc_and_map_vao_buffer(vao_idx, sz, 0, GL_STATIC_DRAW, GL_WRITE_ONLY); + if (borders_buf_address) memcpy(borders_buf_address, rect_buf, sz); + unmap_vao_buffer(vao_idx, 0); } - - if (num_border_rects) { - bind_program(BORDERS_PROGRAM); - if (rect_data_is_dirty) { - const size_t sz = sizeof(BorderRect) * num_border_rects; - void *borders_buf_address = alloc_and_map_vao_buffer(vao_idx, sz, 0, GL_STATIC_DRAW, GL_WRITE_ONLY); - if (borders_buf_address) memcpy(borders_buf_address, rect_buf, sz); - unmap_vao_buffer(vao_idx, 0); - } - color_type default_bg = (num_visible_windows > 1 && !all_windows_have_same_bg) ? OPT(background) : active_window_bg; - GLuint colors[9] = { - default_bg, OPT(active_border_color), OPT(inactive_border_color), 0, - 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); - glUniform1f(border_program_layout.uniforms.background_opacity, background_opacity); - glUniform1f(border_program_layout.uniforms.tint_opacity, tint_opacity); - glUniform1f(border_program_layout.uniforms.tint_premult, tint_premult); - glUniform2ui(border_program_layout.uniforms.viewport, viewport_width, viewport_height); - if (has_bgimage(w)) { - if (w->is_semi_transparent) { BLEND_PREMULT; } - else { BLEND_ONTO_OPAQUE_WITH_OPAQUE_OUTPUT; } - } - glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, num_border_rects); - unbind_program(); - } - unbind_vertex_array(); - if (has_bgimage(w)) glDisable(GL_BLEND); + color_type default_bg = (num_visible_windows > 1 && !all_windows_have_same_bg) ? OPT(background) : active_window_bg; + GLuint colors[9] = { + default_bg, OPT(active_border_color), OPT(inactive_border_color), 0, + 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); + 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); + if (!w->needs_layers) glDisable(GL_FRAMEBUFFER_SRGB); + unbind_program(); unbind_vertex_array(); } // }}} // Cursor Trail {{{ -typedef struct { - TrailUniforms uniforms; -} TrailProgramLayout; -static TrailProgramLayout trail_program_layout; - -static void -init_trail_program(void) { - get_uniform_locations_trail(TRAIL_PROGRAM, &trail_program_layout.uniforms); -} - void draw_cursor_trail(CursorTrail *trail, Window *active_window) { bind_program(TRAIL_PROGRAM); - glEnable(GL_BLEND); - BLEND_ONTO_OPAQUE; glUniform4fv(trail_program_layout.uniforms.x_coords, 1, trail->corner_x); glUniform4fv(trail_program_layout.uniforms.y_coords, 1, trail->corner_y); @@ -1259,13 +1084,137 @@ draw_cursor_trail(CursorTrail *trail, Window *active_window) { glUniform1fv(trail_program_layout.uniforms.trail_opacity, 1, &trail->opacity); - glDrawArrays(GL_TRIANGLE_FAN, 0, 4); - glDisable(GL_BLEND); + draw_quad(true, 0); unbind_program(); } // }}} +// OSWindow {{{ +static void +draw_bg_image(OSWindow *os_window) { + if (!has_bgimage(os_window)) return; + BackgroundImageRenderSettings s = { + .os_window.width = os_window->viewport_width, .os_window.height = os_window->viewport_height, + .instance_id = os_window->bgimage->id, .layout=OPT(background_image_layout), + .linear=OPT(background_image_linear), .bgcolor=OPT(background), .opacity=os_window->background_opacity, + }; + bind_program(BGIMAGE_PROGRAM); + GLfloat iwidth = os_window->bgimage->width, iheight = os_window->bgimage->height; + GLfloat vwidth = s.os_window.width, vheight = s.os_window.height; + if (CENTER_SCALED == OPT(background_image_layout)) { + GLfloat ifrac = iwidth / iheight; + if (ifrac > (vwidth / vheight)) { + iheight = vheight; + iwidth = iheight * ifrac; + } else { + iwidth = vwidth; + iheight = iwidth / ifrac; + } + } + GLfloat tiled = 0.f;; + GLfloat left = -1.0, top = 1.0, right = 1.0, bottom = -1.0; + switch (OPT(background_image_layout)) { + case TILING: case MIRRORED: case CLAMPED: + tiled = 1.f; break; + case SCALED: + break; + case CENTER_CLAMPED: + case CENTER_SCALED: { + GLfloat wfrac = (vwidth - iwidth) / vwidth; + GLfloat hfrac = (vheight - iheight) / vheight; + left += wfrac; + right -= wfrac; + top -= hfrac; + bottom += hfrac; + } break; + } + glUniform4f(bgimage_program_layout.uniforms.sizes, vwidth, vheight, iwidth, iheight); + glUniform1f(bgimage_program_layout.uniforms.tiled, tiled); + glUniform4f(bgimage_program_layout.uniforms.positions, left, top, right, bottom); + glUniform1i(bgimage_program_layout.uniforms.image, GRAPHICS_UNIT); + color_vec4(bgimage_program_layout.uniforms.background, s.bgcolor, s.opacity); + glActiveTexture(GL_TEXTURE0 + GRAPHICS_UNIT); + glBindTexture(GL_TEXTURE_2D, os_window->bgimage->texture_id); + draw_quad(false, 0); + unbind_program(); +} + +static void +gpu_data_for_centered_image(ImageRenderData *ans, unsigned int screen_width_px, unsigned int screen_height_px, unsigned int width, unsigned int height) { + float width_frac = 2 * MIN(1, width / (float)screen_width_px), height_frac = 2 * MIN(1, height / (float)screen_height_px); + float hmargin = (2 - width_frac) / 2; + float vmargin = (2 - height_frac) / 2; + gpu_data_for_image(ans, -1 + hmargin, 1 - vmargin, -1 + hmargin + width_frac, 1 - vmargin - height_frac); +} + +static void +draw_centered_alpha_mask(size_t screen_width, size_t screen_height, size_t width, size_t height, uint8_t *canvas, float background_opacity) { + ImageRenderData *data = load_alpha_mask_texture(width, height, canvas); + gpu_data_for_centered_image(data, screen_width, screen_height, width, height); + bind_program(GRAPHICS_ALPHA_MASK_PROGRAM); + glUniform1i(graphics_program_layouts[GRAPHICS_ALPHA_MASK_PROGRAM].uniforms.image, GRAPHICS_UNIT); + color_vec3(graphics_program_layouts[GRAPHICS_ALPHA_MASK_PROGRAM].uniforms.amask_fg, OPT(foreground)); + color_vec4_premult(graphics_program_layouts[GRAPHICS_ALPHA_MASK_PROGRAM].uniforms.amask_bg_premult, OPT(background), background_opacity); + draw_graphics(GRAPHICS_ALPHA_MASK_PROGRAM, data, 0, 1, 1.0); +} + +static void +draw_resizing_text(OSWindow *w) { + if (monotonic() - w->created_at > ms_to_monotonic_t(1000) && w->live_resize.num_of_resize_events > 1) { + char text[32] = {0}; + unsigned int width = w->live_resize.width, height = w->live_resize.height; + snprintf(text, sizeof(text), "%u x %u cells", width / w->fonts_data->fcm.cell_width, height / w->fonts_data->fcm.cell_height); + StringCanvas rendered = render_simple_text(w->fonts_data, text); + if (rendered.canvas) { + draw_centered_alpha_mask(width, height, rendered.width, rendered.height, rendered.canvas, OPT(background_opacity)); + free(rendered.canvas); + } + } +} + + +void +setup_os_window_for_rendering(OSWindow *os_window, bool start) { + if (start) { + if (os_window->live_resize.in_progress) { + blank_os_window(os_window); + save_viewport_using_bottom_left_origin(0, 0, os_window->viewport_width, os_window->viewport_height); + } + if (os_window->needs_layers) { + if (os_window->indirect_output.width != os_window->viewport_width || os_window->indirect_output.height != os_window->viewport_height) { + if (os_window->indirect_output.texture_id) free_texture(&os_window->indirect_output.texture_id); + if (os_window->indirect_output.framebuffer_id) free_framebuffer(&os_window->indirect_output.framebuffer_id); + } + if (os_window->indirect_output.texture_id == 0) { + os_window->indirect_output.width = os_window->viewport_width; + os_window->indirect_output.height = os_window->viewport_height; + setup_texture_as_render_target((unsigned) os_window->viewport_width, (unsigned)os_window->viewport_height, &os_window->indirect_output.texture_id, &os_window->indirect_output.framebuffer_id); + } + set_framebuffer_to_use_for_output(os_window->indirect_output.framebuffer_id); + bind_framebuffer_for_output(0); + clear_current_framebuffer(); + draw_bg_image(os_window); + } + } else { + if (os_window->needs_layers) { + set_framebuffer_to_use_for_output(0); + bind_framebuffer_for_output(0); + bind_program(BLIT_PROGRAM); + glActiveTexture(GL_TEXTURE0 + GRAPHICS_UNIT); + glBindTexture(GL_TEXTURE_2D, os_window->indirect_output.texture_id); + glUniform4f(blit_program_layout.uniforms.src_rect, 0, 1, 1, 0); + glUniform4f(blit_program_layout.uniforms.dest_rect, -1, 1, 1, -1); + draw_quad(false, 0); + } + if (os_window->live_resize.in_progress) { + restore_viewport(); + draw_resizing_text(os_window); + } + } +} +// }}} + // Python API {{{ static bool @@ -1338,8 +1287,6 @@ NO_ARG(init_borders_program) NO_ARG(init_cell_program) -NO_ARG(init_trail_program) - static PyObject* sprite_map_set_limits(PyObject UNUSED *self, PyObject *args) { unsigned int w, h; @@ -1349,8 +1296,6 @@ sprite_map_set_limits(PyObject UNUSED *self, PyObject *args) { Py_RETURN_NONE; } - - #define M(name, arg_type) {#name, (PyCFunction)name, arg_type, NULL} #define MW(name, arg_type) {#name, (PyCFunction)py##name, arg_type, NULL} static PyMethodDef module_methods[] = { @@ -1364,7 +1309,6 @@ static PyMethodDef module_methods[] = { MW(unbind_program, METH_NOARGS), MW(init_borders_program, METH_NOARGS), MW(init_cell_program, METH_NOARGS), - MW(init_trail_program, METH_NOARGS), {NULL, NULL, 0, NULL} /* Sentinel */ }; @@ -1377,7 +1321,9 @@ finalize(void) { bool init_shaders(PyObject *module) { #define C(x) if (PyModule_AddIntConstant(module, #x, x) != 0) { PyErr_NoMemory(); return false; } - C(CELL_PROGRAM); C(CELL_BG_PROGRAM); C(CELL_SPECIAL_PROGRAM); C(CELL_FG_PROGRAM); C(BORDERS_PROGRAM); C(GRAPHICS_PROGRAM); C(GRAPHICS_PREMULT_PROGRAM); C(GRAPHICS_ALPHA_MASK_PROGRAM); C(BGIMAGE_PROGRAM); C(TINT_PROGRAM); C(TRAIL_PROGRAM); + C(CELL_PROGRAM); C(CELL_FG_PROGRAM); C(CELL_BG_PROGRAM); C(BORDERS_PROGRAM); + C(GRAPHICS_PROGRAM); C(GRAPHICS_PREMULT_PROGRAM); C(GRAPHICS_ALPHA_MASK_PROGRAM); + C(BGIMAGE_PROGRAM); C(TINT_PROGRAM); C(TRAIL_PROGRAM); C(BLIT_PROGRAM); C(GLSL_VERSION); C(GL_VERSION); C(GL_VENDOR); @@ -1391,7 +1337,6 @@ init_shaders(PyObject *module) { C(GL_FALSE); C(GL_COMPILE_STATUS); C(GL_LINK_STATUS); - C(GL_TEXTURE0); C(GL_TEXTURE1); C(GL_TEXTURE2); C(GL_TEXTURE3); C(GL_TEXTURE4); C(GL_TEXTURE5); C(GL_TEXTURE6); C(GL_TEXTURE7); C(GL_TEXTURE8); C(GL_MAX_ARRAY_TEXTURE_LAYERS); C(GL_TEXTURE_BINDING_BUFFER); C(GL_MAX_TEXTURE_BUFFER_SIZE); C(GL_MAX_TEXTURE_SIZE); C(GL_TEXTURE_2D_ARRAY); diff --git a/kitty/shaders.py b/kitty/shaders.py index 84e30c23d..849376f7f 100644 --- a/kitty/shaders.py +++ b/kitty/shaders.py @@ -10,10 +10,10 @@ from typing import Any, Literal, NamedTuple, Optional from .constants import read_kitty_resource from .fast_data_types import ( BGIMAGE_PROGRAM, + BLIT_PROGRAM, CELL_BG_PROGRAM, CELL_FG_PROGRAM, CELL_PROGRAM, - CELL_SPECIAL_PROGRAM, DECORATION, DECORATION_MASK, DIM, @@ -30,7 +30,6 @@ from .fast_data_types import ( compile_program, get_options, init_cell_program, - init_trail_program, ) @@ -139,7 +138,6 @@ class TextFgOverrideThreshold(NamedTuple): class LoadShaderPrograms: text_fg_override_threshold: TextFgOverrideThreshold = TextFgOverrideThreshold() text_old_gamma: bool = False - semi_transparent: bool = False cell_program_replacer: MultiReplacer = null_replacer @property @@ -152,10 +150,9 @@ class LoadShaderPrograms: def recompile_if_needed(self) -> None: if self.needs_recompile: - self(self.semi_transparent, allow_recompile=True) + self(allow_recompile=True) - def __call__(self, semi_transparent: bool = False, allow_recompile: bool = False) -> None: - self.semi_transparent = semi_transparent + def __call__(self, allow_recompile: bool = False) -> None: opts = get_options() self.text_old_gamma = opts.text_composition_strategy == 'legacy' @@ -180,47 +177,40 @@ class LoadShaderPrograms: DECORATION_MASK=DECORATION_MASK, ) - def resolve_cell_defines(which: str, src: str) -> str: + def resolve_cell_defines(only_fg: int, only_bg: int, src: str) -> str: r = self.cell_program_replacer.replacements - r['WHICH_PHASE'] = f'PHASE_{which}' - r['TRANSPARENT'] = '1' if semi_transparent else '0' + r['ONLY_FOREGROUND'] = str(only_fg) + r['ONLY_BACKGROUND'] = str(only_bg) r['DO_FG_OVERRIDE'] = '1' if self.text_fg_override_threshold.scaled_value else '0' r['FG_OVERRIDE_ALGO'] = '1' if self.text_fg_override_threshold.unit == '%' else '2' r['FG_OVERRIDE_THRESHOLD'] = str(self.text_fg_override_threshold.scaled_value) r['TEXT_NEW_GAMMA'] = '0' if self.text_old_gamma else '1' return self.cell_program_replacer(src) - - for which, p in { - 'BOTH': CELL_PROGRAM, - 'BACKGROUND': CELL_BG_PROGRAM, - 'SPECIAL': CELL_SPECIAL_PROGRAM, - 'FOREGROUND': CELL_FG_PROGRAM, + for prog, (only_fg, only_bg) in { + CELL_PROGRAM: (0, 0), CELL_FG_PROGRAM: (1, 0), CELL_BG_PROGRAM: (0, 1), }.items(): - cell.apply_to_sources( - vertex=partial(resolve_cell_defines, which), - frag=partial(resolve_cell_defines, which), - ) - cell.compile(p, allow_recompile) - + fn = partial(resolve_cell_defines, only_fg, only_bg) + cell.apply_to_sources(vertex=fn, frag=fn) + cell.compile(prog, allow_recompile) graphics = program_for('graphics') - def resolve_graphics_fragment_defines(which: str, f: str) -> str: - return f.replace('#define ALPHA_TYPE', f'#define {which}', 1) + 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 which, p in { - 'SIMPLE': GRAPHICS_PROGRAM, - 'PREMULT': GRAPHICS_PREMULT_PROGRAM, - 'ALPHA_MASK': GRAPHICS_ALPHA_MASK_PROGRAM, + 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)) + graphics.apply_to_sources(frag=partial(resolve_graphics_fragment_defines, which, is_premult)) graphics.compile(p, allow_recompile) program_for('bgimage').compile(BGIMAGE_PROGRAM, allow_recompile) program_for('tint').compile(TINT_PROGRAM, allow_recompile) - init_cell_program() - program_for('trail').compile(TRAIL_PROGRAM, allow_recompile) - init_trail_program() + program_for('blit').compile(BLIT_PROGRAM, allow_recompile) + init_cell_program() load_shader_programs = LoadShaderPrograms() diff --git a/kitty/state.c b/kitty/state.c index ece1296f3..308ebff21 100644 --- a/kitty/state.c +++ b/kitty/state.c @@ -489,7 +489,9 @@ destroy_os_window_item(OSWindow *w) { remove_vao(w->tab_bar_render_data.vao_idx); free(w->tabs); w->tabs = NULL; free_bgimage(&w->bgimage, true); - w->bgimage = NULL; + zero_at_ptr(&w->bgimage); + if (w->indirect_output.texture_id) free_texture(&w->indirect_output.texture_id); + if (w->indirect_output.framebuffer_id) free_framebuffer(&w->indirect_output.framebuffer_id); } bool @@ -562,20 +564,29 @@ swap_tabs(id_type os_window_id, unsigned int a, unsigned int b) { END_WITH_OS_WINDOW } -static void -add_borders_rect(id_type os_window_id, id_type tab_id, uint32_t left, uint32_t top, uint32_t right, uint32_t bottom, uint32_t color) { +static PyObject* +pyset_borders_rects(PyObject *self UNUSED, PyObject *args) { + id_type os_window_id, tab_id; + PyObject *rects; + if (!PyArg_ParseTuple(args, "KKO!", &os_window_id, &tab_id, &PyList_Type, &rects)) return NULL; WITH_TAB(os_window_id, tab_id) BorderRects *br = &tab->border_rects; br->is_dirty = true; - if (!left && !top && !right && !bottom) { br->num_border_rects = 0; return; } + br->num_border_rects = PyList_GET_SIZE(rects); ensure_space_for(br, rect_buf, BorderRect, br->num_border_rects + 1, capacity, 32, false); - BorderRect *r = br->rect_buf + br->num_border_rects++; - r->left = gl_pos_x(left, osw->viewport_width); - r->top = gl_pos_y(top, osw->viewport_height); - r->right = r->left + gl_size(right - left, osw->viewport_width); - r->bottom = r->top - gl_size(bottom - top, osw->viewport_height); - r->color = color; + for (unsigned i = 0; i < br->num_border_rects; i++) { + PyObject *pr = PyList_GET_ITEM(rects, i); + unsigned long left, top, right, bottom, color; + if (!PyArg_ParseTuple(pr, "kkkkk", &left, &top, &right, &bottom, &color)) return NULL; + BorderRect *r = br->rect_buf + i; + r->left = gl_pos_x(left, osw->viewport_width); + r->top = gl_pos_y(top, osw->viewport_height); + r->right = r->left + gl_size(right - left, osw->viewport_width); + r->bottom = r->top - gl_size(bottom - top, osw->viewport_height); + r->color = color; + } END_WITH_TAB + Py_RETURN_NONE; } @@ -789,24 +800,18 @@ PYWRAP1(set_ignore_os_keyboard_processing) { } static void -init_window_render_data(OSWindow *osw, const WindowGeometry *g, WindowRenderData *d) { - d->dx = gl_size(osw->fonts_data->fcm.cell_width, osw->viewport_width); - d->dy = gl_size(osw->fonts_data->fcm.cell_height, osw->viewport_height); - d->xstart = gl_pos_x(g->left, osw->viewport_width); - d->ystart = gl_pos_y(g->top, osw->viewport_height); +init_window_render_data(WindowRenderData *d, const WindowGeometry g, Screen *screen) { + d->geometry = g; + Py_CLEAR(d->screen); d->screen = (Screen*)Py_NewRef(screen); } PYWRAP1(set_tab_bar_render_data) { - WindowRenderData d = {0}; - WindowGeometry g = {0}; + WindowGeometry g; id_type os_window_id; - PA("KOIIII", &os_window_id, &d.screen, &g.left, &g.top, &g.right, &g.bottom); + Screen *screen; + PA("KOIIII", &os_window_id, &screen, &g.left, &g.top, &g.right, &g.bottom); WITH_OS_WINDOW(os_window_id) - Py_CLEAR(os_window->tab_bar_render_data.screen); - d.vao_idx = os_window->tab_bar_render_data.vao_idx; - init_window_render_data(os_window, &g, &d); - os_window->tab_bar_render_data = d; - Py_INCREF(os_window->tab_bar_render_data.screen); + init_window_render_data(&os_window->tab_bar_render_data, g, screen); END_WITH_OS_WINDOW Py_RETURN_NONE; } @@ -979,23 +984,16 @@ PYWRAP1(set_window_padding) { } PYWRAP1(set_window_render_data) { -#define A(name) &(d.name) #define B(name) &(g.name) id_type os_window_id, tab_id, window_id; - WindowRenderData d = {0}; WindowGeometry g = {0}; - PA("KKKOIIII", &os_window_id, &tab_id, &window_id, A(screen), B(left), B(top), B(right), B(bottom)); + Screen *screen; + PA("KKKOIIII", &os_window_id, &tab_id, &window_id, &screen, B(left), B(top), B(right), B(bottom)); WITH_WINDOW(os_window_id, tab_id, window_id); - Py_CLEAR(window->render_data.screen); - d.vao_idx = window->render_data.vao_idx; - init_window_render_data(osw, &g, &d); - window->render_data = d; - window->geometry = g; - Py_INCREF(window->render_data.screen); + init_window_render_data(&window->render_data, g, screen); END_WITH_WINDOW; Py_RETURN_NONE; -#undef A #undef B } @@ -1240,6 +1238,8 @@ pyset_background_image(PyObject *self UNUSED, PyObject *args, PyObject *kw) { free(bgimage); return NULL; } + static uint32_t bgimage_id_counter = 0; + bgimage->id = ++bgimage_id_counter; send_bgimage_to_gpu(layout, bgimage); bgimage->refcnt++; } @@ -1411,7 +1411,6 @@ KI(set_active_tab) K(mark_os_window_dirty) KKK(set_active_window) KII(swap_tabs) -KK5I(add_borders_rect) KKKK(set_redirect_keys_to_overlay) static PyObject* @@ -1475,7 +1474,7 @@ static PyMethodDef module_methods[] = { MW(buffer_keys_in_window, METH_VARARGS), MW(set_active_window, METH_VARARGS), MW(swap_tabs, METH_VARARGS), - MW(add_borders_rect, METH_VARARGS), + MW(set_borders_rects, METH_VARARGS), MW(set_tab_bar_render_data, METH_VARARGS), MW(set_window_render_data, METH_VARARGS), MW(set_window_padding, METH_VARARGS), diff --git a/kitty/state.h b/kitty/state.h index 076ac1b44..c45418ce7 100644 --- a/kitty/state.h +++ b/kitty/state.h @@ -142,16 +142,16 @@ typedef struct WindowLogoRenderData { bool using_default; } WindowLogoRenderData; -typedef struct { - ssize_t vao_idx; - float xstart, ystart, dx, dy; - Screen *screen; -} WindowRenderData; - typedef struct { unsigned int left, top, right, bottom; } WindowGeometry; +typedef struct { + ssize_t vao_idx; + WindowGeometry geometry; + Screen *screen; +} WindowRenderData; + typedef struct { monotonic_t at; int button, modifiers; @@ -202,7 +202,6 @@ typedef struct { struct { unsigned int left, top, right, bottom; } padding; - WindowGeometry geometry; ClickQueue click_queues[8]; monotonic_t last_drag_scroll_at; uint32_t last_special_key_pressed; @@ -272,6 +271,13 @@ typedef struct WindowChromeState { float background_opacity; } WindowChromeState; +typedef struct BackgroundImageRenderSettings { + struct { unsigned width, height; } os_window; + unsigned instance_id; + BackgroundImageLayout layout; + bool linear; uint32_t bgcolor; float opacity; +} BackgroundImageRenderSettings; + typedef struct { void *handle; id_type id; @@ -284,8 +290,12 @@ typedef struct { double viewport_x_ratio, viewport_y_ratio; Tab *tabs; BackgroundImage *bgimage; + struct { + uint32_t texture_id, framebuffer_id; + int width, height; + } indirect_output; unsigned int active_tab, num_tabs, capacity, last_active_tab, last_num_tabs, last_active_window_id; - bool focused_at_last_render, needs_render; + bool focused_at_last_render, needs_render, needs_layers; unsigned keep_rendering_till_swap; WindowRenderData tab_bar_render_data; struct { @@ -304,7 +314,7 @@ typedef struct { monotonic_t viewport_resized_at; LiveResizeInfo live_resize; bool has_pending_resizes, is_semi_transparent, shown_once, ignore_resize_events; - unsigned int clear_count, redraw_count; + unsigned int redraw_count; WindowChromeState last_window_chrome; float background_opacity; FONTS_DATA_HANDLE fonts_data; @@ -371,7 +381,6 @@ void mark_os_window_for_close(OSWindow* w, CloseRequest cr); void update_os_window_viewport(OSWindow *window, bool notify_boss); bool should_os_window_be_rendered(OSWindow* w); void wakeup_main_loop(void); -void swap_window_buffers(OSWindow *w); bool make_window_context_current(id_type); void hide_mouse(OSWindow *w); bool is_mouse_hidden(OSWindow *w); @@ -385,16 +394,15 @@ OSWindow* add_os_window(void); OSWindow* current_os_window(void); void os_window_regions(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, uint32_t viewport_width, uint32_t viewport_height, color_type, unsigned int, bool, OSWindow *w); +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, float, float, float, float, Screen *, OSWindow *); -void draw_cells(ssize_t, const WindowRenderData*, OSWindow *, bool, bool, bool, Window*); -void draw_centered_alpha_mask(OSWindow *w, size_t screen_width, size_t screen_height, size_t width, size_t height, uint8_t *canvas, float); +bool send_cell_data_to_gpu(ssize_t, Screen *, OSWindow *); +void draw_cells(const WindowRenderData*, OSWindow *, bool, bool, bool, Window*); void draw_cursor_trail(CursorTrail *trail, Window *active_window); bool update_cursor_trail(CursorTrail *ct, Window *w, monotonic_t now, OSWindow *os_window); -void update_surface_size(int, int, uint32_t); +void set_gpu_viewport(unsigned w, unsigned h); void free_texture(uint32_t*); void free_framebuffer(uint32_t*); void send_image_to_gpu(uint32_t*, const void*, int32_t, int32_t, bool, bool, bool, RepeatStrategy); @@ -430,7 +438,7 @@ const char* format_mods(unsigned mods); void dispatch_pending_clicks(id_type, void*); void send_pending_click_to_window(Window*, int); void get_platform_dependent_config_values(void *glfw_window); -bool draw_window_title(OSWindow *window, const char *text, color_type fg, color_type bg, uint8_t *output_buf, size_t width, size_t height); +bool draw_window_title(double, double, const char *text, color_type fg, color_type bg, uint8_t *output_buf, size_t width, size_t height); uint8_t* draw_single_ascii_char(const char ch, size_t *result_width, size_t *result_height); bool is_os_window_fullscreen(OSWindow *); void update_ime_focus(OSWindow* osw, bool focused); @@ -443,3 +451,6 @@ bool render_os_window(OSWindow *w, monotonic_t now, bool scan_for_animated_image void update_mouse_pointer_shape(void); void adjust_window_size_for_csd(OSWindow *w, int width, int height, int *adjusted_width, int *adjusted_height); void dispatch_buffered_keys(Window *w); +bool screen_needs_rendering_in_layers(OSWindow *os_window, Window *w, Screen *screen); +void setup_os_window_for_rendering(OSWindow*, bool); +void swap_window_buffers(OSWindow *w); diff --git a/kitty/tint_fragment.glsl b/kitty/tint_fragment.glsl index 3f82c857e..209c1e76b 100644 --- a/kitty/tint_fragment.glsl +++ b/kitty/tint_fragment.glsl @@ -1,5 +1,5 @@ uniform vec4 tint_color; -out vec4 color; +out vec4 color; // must be in linear space and pre-multiplied void main() { color = tint_color; diff --git a/kitty/utils.glsl b/kitty/utils.glsl new file mode 100644 index 000000000..708c7ff44 --- /dev/null +++ b/kitty/utils.glsl @@ -0,0 +1,12 @@ +// 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) + +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); +} diff --git a/kitty/utils.py b/kitty/utils.py index e8a32481a..8836ddc76 100644 --- a/kitty/utils.py +++ b/kitty/utils.py @@ -1021,7 +1021,7 @@ def sanitize_for_bracketed_paste(text: bytes) -> bytes: @lru_cache(maxsize=64) -def sanitize_url_for_dispay_to_user(url: str) -> str: +def sanitize_url_for_display_to_user(url: str) -> str: from urllib.parse import unquote, urlparse, urlunparse try: purl = urlparse(url) diff --git a/kitty/window.py b/kitty/window.py index 2a6297bcf..aa4434620 100644 --- a/kitty/window.py +++ b/kitty/window.py @@ -108,7 +108,7 @@ from .utils import ( sanitize_control_codes, sanitize_for_bracketed_paste, sanitize_title, - sanitize_url_for_dispay_to_user, + sanitize_url_for_display_to_user, shlex_split, ) @@ -1196,7 +1196,7 @@ class Window: if opts.allow_hyperlinks & 0b10: from kittens.tui.operations import styled boss.choose( - 'What would you like to do with this URL:\n' + styled(sanitize_url_for_dispay_to_user(url), fg='yellow'), + 'What would you like to do with this URL:\n' + styled(sanitize_url_for_display_to_user(url), fg='yellow'), partial(self.hyperlink_open_confirmed, url, cwd), 'o:Open', 'c:Copy to clipboard', 'n;red:Nothing', default='o', window=self, title=_('Hyperlink activated'), diff --git a/kitty_tests/datatypes.py b/kitty_tests/datatypes.py index d65cd007e..fbe55e5cf 100644 --- a/kitty_tests/datatypes.py +++ b/kitty_tests/datatypes.py @@ -30,7 +30,7 @@ from kitty.fast_data_types import ( ) from kitty.fast_data_types import Cursor as C from kitty.rgb import to_color -from kitty.utils import is_ok_to_read_image_file, is_path_in_temp_dir, sanitize_title, sanitize_url_for_dispay_to_user, shlex_split, shlex_split_with_positions +from kitty.utils import is_ok_to_read_image_file, is_path_in_temp_dir, sanitize_title, sanitize_url_for_display_to_user, shlex_split, shlex_split_with_positions from . import BaseTest, filled_cursor, filled_history_buf, filled_line_buf @@ -466,7 +466,7 @@ class TestDataTypes(BaseTest): if os.path.isdir('/dev/shm'): with tempfile.NamedTemporaryFile(dir='/dev/shm') as tf: self.assertTrue(is_ok_to_read_image_file(tf.name, tf.fileno()), fifo) - self.ae(sanitize_url_for_dispay_to_user( + self.ae(sanitize_url_for_display_to_user( 'h://a\u0430b.com/El%20Ni%C3%B1o/'), 'h://xn--ab-7kc.com/El NiƱo/') for x in ('~', '~/', '', '~root', '~root/~', '/~', '/a/b/', '~xx/a', '~~'): self.assertEqual(os.path.expanduser(x), expanduser(x), x)