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
This commit is contained in:
Kovid Goyal 2025-08-11 00:40:26 +05:30
parent 0afa0c4e50
commit d52f2e7981
No known key found for this signature in database
GPG key ID: 06BC317B515ACE7C
42 changed files with 1200 additions and 1176 deletions

View file

@ -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`)

View file

@ -1,14 +1,11 @@
#pragma kitty_include_shader <alpha_blend.glsl>
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);
}

View file

@ -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)

View file

@ -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);
}

13
kitty/blit_fragment.glsl Normal file
View file

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

2
kitty/blit_vertex.glsl Normal file
View file

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

View file

@ -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;
}

View file

@ -1,11 +1,21 @@
uniform uvec2 viewport;
#pragma kitty_include_shader <utils.glsl>
#define DEFAULT_BG 0
#define ACTIVE_BORDER_COLOR 1
#define INACTIVE_BORDER_COLOR 2
#define WINDOW_BACKGROUND_PLACEHOLDER 3
#define BELL_BORDER_COLOR 4
#define TAB_BAR_BG_COLOR 5
#define TAB_BAR_MARGIN_COLOR 6
#define TAB_BAR_EDGE_LEFT_COLOR 7
#define TAB_BAR_EDGE_RIGHT_COLOR 8
uniform uint colors[9];
uniform float background_opacity;
uniform float 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);
}

View file

@ -1,11 +1,12 @@
#!/usr/bin/env python
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
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)

View file

@ -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)

View file

@ -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

View file

@ -1,15 +1,15 @@
#pragma kitty_include_shader <alpha_blend.glsl>
#pragma kitty_include_shader <linear2srgb.glsl>
#pragma kitty_include_shader <cell_defines.glsl>
#pragma kitty_include_shader <utils.glsl>
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;
}

View file

@ -1,16 +1,17 @@
#extension GL_ARB_explicit_attrib_location : require
#pragma kitty_include_shader <cell_defines.glsl>
#pragma kitty_include_shader <utils.glsl>
// Inputs {{{
layout(std140) uniform CellRenderData {
float 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 <hsluv.glsl>
#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
}

View file

@ -10,7 +10,6 @@
#include "state.h"
#include "threading.h"
#include "screen.h"
#include "fonts.h"
#include "monotonic.h"
#include <termios.h>
#include <unistd.h>
@ -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;
}

View file

@ -1,6 +1,9 @@
#include <float.h>
#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]);

View file

@ -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,

105
kitty/gl-wrapper.h generated
View file

@ -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);

View file

@ -10,6 +10,7 @@
#include <stddef.h>
#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;
}

View file

@ -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);

View file

@ -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;

View file

@ -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;
}

View file

@ -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);

View file

@ -1,4 +1,5 @@
#pragma kitty_include_shader <alpha_blend.glsl>
#pragma kitty_include_shader <utils.glsl>
#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;
}

View file

@ -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 <blit_common_vertex.glsl>

View file

@ -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;

View file

@ -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(

View file

@ -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));
}

View file

@ -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)

View file

@ -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;

View file

@ -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.
'''
)

View file

@ -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);

View file

@ -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);

View file

@ -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;

File diff suppressed because it is too large Load diff

View file

@ -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()

View file

@ -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),

View file

@ -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);

View file

@ -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;

12
kitty/utils.glsl Normal file
View file

@ -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);
}

View file

@ -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)

View file

@ -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'),

View file

@ -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)