From d52f2e798164c2f2800eb28d227bead4f1b913a4 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 11 Aug 2025 00:40:26 +0530 Subject: [PATCH] Rewrite rendering pipeline This was needed to fix various corner cases when doing blending of colors in linear space. The new architecture has the same performance as the old in the common case of opaque rendering with no UI layers or images. In the case of only positive z-index images there is a performance decrease as the OS Window is now rendered to a offscreen texture and then blitted to screen. However, in the future when we move to Vulkan or I can figure out how to get Wayland to accept buffers with colors in linear space, this performance penalty can be removed. The performance penalty was not significant on my system but this is highly GPU dependent. Modern GPUs are supposedly optimised for rendering to offscreen buffers, so we will see. The awrit project might be a good test case. Now either we have 1-shot rendering for the case of opaque with only ext or all the various pieces are rendered in successive draw calls into an offscreen buffer that is blitted to the output buffer after all drawing is done. Fixes #8869 --- docs/changelog.rst | 3 + kitty/bgimage_fragment.glsl | 13 +- kitty/bgimage_vertex.glsl | 8 +- kitty/blit_common_vertex.glsl | 19 + kitty/blit_fragment.glsl | 13 + kitty/blit_vertex.glsl | 2 + kitty/border_fragment.glsl | 6 +- kitty/border_vertex.glsl | 39 +- kitty/borders.py | 63 +- kitty/boss.py | 11 +- kitty/cell_defines.glsl | 19 +- kitty/cell_fragment.glsl | 118 +--- kitty/cell_vertex.glsl | 131 ++-- kitty/child-monitor.c | 38 +- kitty/cursor_trail.c | 33 +- kitty/fast_data_types.pyi | 20 +- kitty/gl-wrapper.h | 105 ++- kitty/gl.c | 124 +++- kitty/gl.h | 10 +- kitty/glfw.c | 12 +- kitty/graphics.c | 45 +- kitty/graphics.h | 29 +- kitty/graphics_fragment.glsl | 16 +- kitty/graphics_vertex.glsl | 26 +- kitty/keys.c | 2 +- kitty/layout/base.py | 2 +- kitty/linear2srgb.glsl | 8 + kitty/main.py | 4 +- kitty/mouse.c | 14 +- kitty/options/definition.py | 10 +- kitty/png-reader.c | 66 ++ kitty/png-reader.h | 1 + kitty/screen.h | 1 + kitty/shaders.c | 1177 ++++++++++++++++----------------- kitty/shaders.py | 52 +- kitty/state.c | 69 +- kitty/state.h | 43 +- kitty/tint_fragment.glsl | 2 +- kitty/utils.glsl | 12 + kitty/utils.py | 2 +- kitty/window.py | 4 +- kitty_tests/datatypes.py | 4 +- 42 files changed, 1200 insertions(+), 1176 deletions(-) create mode 100644 kitty/blit_common_vertex.glsl create mode 100644 kitty/blit_fragment.glsl create mode 100644 kitty/blit_vertex.glsl create mode 100644 kitty/utils.glsl 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)