diff --git a/kitty/cell_vertex.glsl b/kitty/cell_vertex.glsl index 117be4454..4e98d50a7 100644 --- a/kitty/cell_vertex.glsl +++ b/kitty/cell_vertex.glsl @@ -16,16 +16,18 @@ layout(std140) uniform CellRenderData { float bg_opacities0, bg_opacities1, bg_opacities2, bg_opacities3, bg_opacities4, bg_opacities5, bg_opacities6, bg_opacities7; 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 -// Have to use fixed locations here as all variants of the cell program share the same VAO +// Have to use fixed locations here as all variants of the cell program share the same VAOs layout(location=0) in uvec3 colors; layout(location=1) in uvec2 sprite_idx; layout(location=2) in uint is_selected; -uniform float gamma_lut[256]; - const int fg_index_map[] = int[3](0, 1, 0); const uvec2 cell_pos_map[] = uvec2[4]( @@ -58,13 +60,12 @@ out float effective_text_alpha; // Utility functions {{{ const uint BYTE_MASK = uint(0xFF); -const uint Z_MASK = uint(0x7fffffff); -const uint COLOR_MASK = uint(0x80000000); -const uint COLOR_SHIFT = uint(31); +const uint SPRITE_INDEX_MASK = uint(0x7fffffff); +const uint SPRITE_COLORED_MASK = uint(0x80000000); +const uint SPRITE_COLORED_SHIFT = uint(31); const uint ZERO = uint(0); const uint ONE = uint(1); const uint TWO = uint(2); -const uint STRIKE_SPRITE_INDEX = uint({STRIKE_SPRITE_INDEX}); const uint DECORATION_MASK = uint({DECORATION_MASK}); vec3 color_to_vec(uint c) { @@ -112,6 +113,26 @@ vec3 to_sprite_pos(uvec2 pos, uint idx) { return vec3(s_xpos[pos.x], s_ypos[pos.y], c.z); } +#ifdef NEEDS_FOREGROUND +uint read_sprite_decorations_idx() { + int idx = int(sprite_idx[0] & SPRITE_INDEX_MASK); + ivec2 sz = textureSize(sprite_decorations_map, 0); + int y = idx / sz[0]; + int x = idx % sz[0]; + return texelFetch(sprite_decorations_map, ivec2(x, y), 0).r; +} + +uvec2 get_decorations_indices(uint in_url /* [0, 1] */, uint text_attrs) { + uint decorations_idx = read_sprite_decorations_idx(); + uint strike_style = ((text_attrs >> STRIKE_SHIFT) & ONE); // 0 or 1 + uint strike_idx = decorations_idx * strike_style; + uint underline_style = ((text_attrs >> DECORATION_SHIFT) & DECORATION_MASK); + underline_style = in_url * url_style + (1u - in_url) * underline_style; // [0, 5] + 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); } @@ -151,8 +172,8 @@ CellData set_vertex_position() { gl_Position = vec4(xpos[pos.x], ypos[pos.y], 0, 1); #ifdef NEEDS_FOREGROUND // The character sprite being rendered - sprite_pos = to_sprite_pos(pos, sprite_idx[0] & Z_MASK); - colored_sprite = float((sprite_idx[0] & COLOR_MASK) >> COLOR_SHIFT); + 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); @@ -198,8 +219,6 @@ void main() { // Foreground {{{ #ifdef NEEDS_FOREGROUND - - // 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); @@ -213,8 +232,9 @@ void main() { foreground = choose_color(float(is_selected & ONE), selection_color, foreground); decoration_fg = choose_color(float(is_selected & ONE), selection_color, decoration_fg); // Underline and strike through (rendered via sprites) - underline_pos = choose_color(in_url, to_sprite_pos(cell_data.pos, url_style), to_sprite_pos(cell_data.pos, (text_attrs >> DECORATION_SHIFT) & DECORATION_MASK)); - strike_pos = to_sprite_pos(cell_data.pos, ((text_attrs >> STRIKE_SHIFT) & ONE) * STRIKE_SPRITE_INDEX); + uvec2 decs = get_decorations_indices(uint(in_url), text_attrs); + strike_pos = to_sprite_pos(cell_data.pos, decs[0]); + underline_pos = to_sprite_pos(cell_data.pos, decs[1]); // Cursor cursor_color_premult = vec4(color_to_vec(cursor_bg) * cursor_opacity, cursor_opacity); diff --git a/kitty/data-types.h b/kitty/data-types.h index 526c47736..578193c3a 100644 --- a/kitty/data-types.h +++ b/kitty/data-types.h @@ -331,7 +331,7 @@ void request_window_attention(id_type, bool); void play_canberra_sound(const char *which_sound, const char *event_id, bool is_path, const char *role, const char *theme_name); #endif SPRITE_MAP_HANDLE alloc_sprite_map(void); -SPRITE_MAP_HANDLE free_sprite_map(SPRITE_MAP_HANDLE); +void free_sprite_data(FONTS_DATA_HANDLE); const char* get_hyperlink_for_id(const HYPERLINK_POOL_HANDLE, hyperlink_id_type id, bool only_url); #define memset_array(array, val, count) if ((count) > 0) { \ diff --git a/kitty/fonts.c b/kitty/fonts.c index 39ac098ec..a87214be5 100644 --- a/kitty/fonts.c +++ b/kitty/fonts.c @@ -137,9 +137,8 @@ python_send_to_gpu(FontGroup *fg, sprite_index idx, pixel *buf) { static void current_send_sprite_to_gpu(FontGroup *fg, sprite_index idx, pixel *buf, sprite_index decorations_idx) { - (void)decorations_idx; - if (python_send_to_gpu_impl) python_send_to_gpu(fg, idx, buf); - else send_sprite_to_gpu((FONTS_DATA_HANDLE)fg, idx, buf); + if (python_send_to_gpu_impl) { python_send_to_gpu(fg, idx, buf); return; } + send_sprite_to_gpu((FONTS_DATA_HANDLE)fg, idx, buf, decorations_idx); } static void @@ -209,7 +208,7 @@ del_font(Font *f) { static void del_font_group(FontGroup *fg) { free(fg->canvas.buf); fg->canvas.buf = NULL; fg->canvas = (Canvas){0}; - fg->sprite_map = free_sprite_map(fg->sprite_map); + free_sprite_data((FONTS_DATA_HANDLE)fg); vt_cleanup(&fg->fallback_font_map); vt_cleanup(&fg->scaled_font_map); vt_cleanup(&fg->decorations_index_map); diff --git a/kitty/line.h b/kitty/line.h index 28c2c9255..684d3ea03 100644 --- a/kitty/line.h +++ b/kitty/line.h @@ -10,6 +10,7 @@ #include "text-cache.h" // TODO: Test setting of ch_and_idx to make sure the right ch_is_idx bit is set +// TODO: Test multiple OS windows with decorations also with changing font size and test on macOS and Linux for re-alloc // TODO: Test handling of calt ligatures with scale see is_group_calt_ligature() // TODO: Handle selection with multicell // TODO: URL detection with multicell diff --git a/kitty/shaders.c b/kitty/shaders.c index 8cfc6ca50..4e65970fe 100644 --- a/kitty/shaders.c +++ b/kitty/shaders.c @@ -19,13 +19,18 @@ #define BLEND_PREMULT glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); // blending of pre-multiplied colors 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 }; +enum { SPRITE_MAP_UNIT, GRAPHICS_UNIT, BGIMAGE_UNIT, SPRITE_DECORATIONS_MAP_UNIT }; // Sprites {{{ typedef struct { int xnum, ynum, x, y, z, last_num_of_layers, last_ynum; GLuint texture_id; GLint max_texture_size, max_array_texture_layers; + struct decorations_map { + GLuint texture_id; + unsigned width, height; + size_t count; + } decorations_map; } SpriteMap; static const SpriteMap NEW_SPRITE_MAP = { .xnum = 1, .ynum = 1, .last_num_of_layers = 1, .last_ynum = -1 }; @@ -68,65 +73,115 @@ alloc_sprite_map(void) { return (SPRITE_MAP_HANDLE)ans; } -SPRITE_MAP_HANDLE -free_sprite_map(SPRITE_MAP_HANDLE sm) { - SpriteMap *sprite_map = (SpriteMap*)sm; +void +free_sprite_data(FONTS_DATA_HANDLE fg) { + SpriteMap *sprite_map = (SpriteMap*)fg->sprite_map; if (sprite_map) { if (sprite_map->texture_id) free_texture(&sprite_map->texture_id); + if (sprite_map->decorations_map.texture_id) free_texture(&sprite_map->texture_id); free(sprite_map); + fg->sprite_map = NULL; } - return NULL; } -static bool copy_image_warned = false; static void -copy_image_sub_data(GLuint src_texture_id, GLuint dest_texture_id, unsigned int width, unsigned int height, unsigned int num_levels) { - if (!GLAD_GL_ARB_copy_image) { - // ARB_copy_image not available, do a slow roundtrip copy - if (!copy_image_warned) { - copy_image_warned = true; - log_error("WARNING: Your system's OpenGL implementation does not have glCopyImageSubData, falling back to a slower implementation"); - } - size_t sz = (size_t)width * height * num_levels; - pixel *src = malloc(sz * sizeof(pixel)); - if (src == NULL) { fatal("Out of memory."); } - glBindTexture(GL_TEXTURE_2D_ARRAY, src_texture_id); - glGetTexImage(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, GL_UNSIGNED_BYTE, src); - glBindTexture(GL_TEXTURE_2D_ARRAY, dest_texture_id); - glPixelStorei(GL_UNPACK_ALIGNMENT, 4); - glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, width, height, num_levels, GL_RGBA, GL_UNSIGNED_BYTE, src); - free(src); - } else { - glCopyImageSubData(src_texture_id, GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, dest_texture_id, GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, width, height, num_levels); +copy_32bit_texture(GLuint old_texture, GLuint new_texture, GLenum texture_type) { + // requires new texture to be at least as big as old texture. Assumes textures are 32bits per pixel + GLint width, height, layers; + glBindTexture(texture_type, old_texture); + glGetTexLevelParameteriv(texture_type, 0, GL_TEXTURE_WIDTH, &width); + glGetTexLevelParameteriv(texture_type, 0, GL_TEXTURE_HEIGHT, &height); + glGetTexLevelParameteriv(texture_type, 0, GL_TEXTURE_DEPTH, &layers); + if (GLAD_GL_ARB_copy_image) { glCopyImageSubData(old_texture, texture_type, 0, 0, 0, 0, new_texture, texture_type, 0, 0, 0, 0, width, height, layers); return; } + + static bool copy_image_warned = false; + // ARB_copy_image not available, do a slow roundtrip copy + if (!copy_image_warned) { + copy_image_warned = true; + log_error("WARNING: Your system's OpenGL implementation does not have glCopyImageSubData, falling back to a slower implementation"); } + + GLint internal_format; + glGetTexLevelParameteriv(texture_type, 0, GL_TEXTURE_INTERNAL_FORMAT, &internal_format); + GLenum format, type; + switch(internal_format) { + case GL_R8UI: case GL_R8I: case GL_R16UI: case GL_R16I: case GL_R32UI: case GL_R32I: case GL_RG8UI: case GL_RG8I: + case GL_RG16UI: case GL_RG16I: case GL_RG32UI: case GL_RG32I: case GL_RGB8UI: case GL_RGB8I: case GL_RGB16UI: + case GL_RGB16I: case GL_RGB32UI: case GL_RGB32I: case GL_RGBA8UI: case GL_RGBA8I: case GL_RGBA16UI: case GL_RGBA16I: + case GL_RGBA32UI: case GL_RGBA32I: + format = GL_RED_INTEGER; + type = GL_UNSIGNED_INT; + break; + default: + format = GL_RGBA; + type = GL_UNSIGNED_INT_8_8_8_8; + break; + } + glPixelStorei(GL_UNPACK_ALIGNMENT, 4); + RAII_ALLOC(uint8_t, pixels, malloc(width * height * layers * 4)); + if (!pixels) fatal("Out of memory"); + glGetTexImage(texture_type, 0, format, type, pixels); + glBindTexture(texture_type, new_texture); + glPixelStorei(GL_PACK_ALIGNMENT, 4); + if (texture_type == GL_TEXTURE_2D_ARRAY) glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, width, height, layers, format, type, pixels); + else glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, format, type, pixels); } +static GLuint +setup_new_sprites_texture(GLenum texture_type) { + GLuint tex; + glGenTextures(1, &tex); + glBindTexture(texture_type, tex); + // We use GL_NEAREST otherwise glyphs that touch the edge of the cell + // often show a border between cells + glTexParameteri(texture_type, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(texture_type, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(texture_type, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(texture_type, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + return tex; +} + +static void +realloc_sprite_decorations_texture_if_needed(FONTS_DATA_HANDLE fg) { +#define dm (sm->decorations_map) + SpriteMap *sm = (SpriteMap*)fg->sprite_map; + size_t current_capacity = dm.width * dm.height; + if (dm.count < current_capacity && dm.texture_id) return; + GLint new_capacity = dm.count + 256; + GLint width = new_capacity, height = 1; + if (new_capacity > sm->max_texture_size) { + width = sm->max_texture_size; + height = 1 + new_capacity / width; + } + if (height > sm->max_texture_size) fatal("Max texture size too small for sprite decorations map, maybe switch to using a GL_TEXTURE_2D_ARRAY"); + const GLenum texture_type = GL_TEXTURE_2D; + GLuint tex = setup_new_sprites_texture(texture_type); + 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); + } + glBindTexture(texture_type, 0); + dm.texture_id = tex; dm.width = width; dm.height = height; +#undef dm +} static void realloc_sprite_texture(FONTS_DATA_HANDLE fg) { - GLuint tex; - glGenTextures(1, &tex); - glBindTexture(GL_TEXTURE_2D_ARRAY, tex); - // We use GL_NEAREST otherwise glyphs that touch the edge of the cell - // often show a border between cells - glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - unsigned int xnum, ynum, z, znum, width, height, src_ynum; + unsigned int xnum, ynum, z, znum, width, height; sprite_tracker_current_layout(fg, &xnum, &ynum, &z); znum = z + 1; SpriteMap *sprite_map = (SpriteMap*)fg->sprite_map; width = xnum * fg->fcm.cell_width; height = ynum * fg->fcm.cell_height; - glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_SRGB8_ALPHA8, width, height, znum); - if (sprite_map->texture_id) { - // need to re-alloc - src_ynum = MAX(1, sprite_map->last_ynum); - copy_image_sub_data(sprite_map->texture_id, tex, width, src_ynum * fg->fcm.cell_height, sprite_map->last_num_of_layers); + const GLenum texture_type = GL_TEXTURE_2D_ARRAY; + GLuint tex = setup_new_sprites_texture(texture_type); + 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); } - glBindTexture(GL_TEXTURE_2D_ARRAY, 0); + glBindTexture(texture_type, 0); sprite_map->last_num_of_layers = znum; sprite_map->last_ynum = ynum; sprite_map->texture_id = tex; @@ -136,21 +191,37 @@ static void ensure_sprite_map(FONTS_DATA_HANDLE fg) { SpriteMap *sprite_map = (SpriteMap*)fg->sprite_map; if (!sprite_map->texture_id) realloc_sprite_texture(fg); + if (!sprite_map->decorations_map.texture_id) realloc_sprite_decorations_texture_if_needed(fg); // We have to rebind since we don't know if the texture was ever bound // in the context of the current OSWindow + glActiveTexture(GL_TEXTURE0 + SPRITE_DECORATIONS_MAP_UNIT); + glBindTexture(GL_TEXTURE_2D, sprite_map->decorations_map.texture_id); glActiveTexture(GL_TEXTURE0 + SPRITE_MAP_UNIT); glBindTexture(GL_TEXTURE_2D_ARRAY, sprite_map->texture_id); } void -send_sprite_to_gpu(FONTS_DATA_HANDLE fg, sprite_index idx, pixel *buf) { +send_sprite_to_gpu(FONTS_DATA_HANDLE fg, sprite_index idx, pixel *buf, sprite_index decoration_idx) { SpriteMap *sprite_map = (SpriteMap*)fg->sprite_map; unsigned int xnum, ynum, znum, x, y, z; +#define dm (sprite_map->decorations_map) + realloc_sprite_decorations_texture_if_needed(fg); + if (idx >= dm.count) { + dm.count = idx + 1; + div_t d = div(idx, dm.width); + x = d.rem; y = d.quot; + glActiveTexture(GL_TEXTURE0 + SPRITE_DECORATIONS_MAP_UNIT); + glBindTexture(GL_TEXTURE_2D, dm.texture_id); + glPixelStorei(GL_UNPACK_ALIGNMENT, 4); + glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, 1, 1, GL_RED_INTEGER, GL_UNSIGNED_INT, &decoration_idx); + } +#undef dm sprite_tracker_current_layout(fg, &xnum, &ynum, &znum); if ((int)znum >= sprite_map->last_num_of_layers || (znum == 0 && (int)ynum > sprite_map->last_ynum)) { realloc_sprite_texture(fg); sprite_tracker_current_layout(fg, &xnum, &ynum, &znum); } + glActiveTexture(GL_TEXTURE0 + SPRITE_MAP_UNIT); glBindTexture(GL_TEXTURE_2D_ARRAY, sprite_map->texture_id); glPixelStorei(GL_UNPACK_ALIGNMENT, 4); sprite_index_to_pos(idx, xnum, ynum, &x, &y, &z); @@ -232,7 +303,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++) { - C(p, colors, 0); C(p, sprite_idx, 1); C(p, is_selected, 2); + C(p, colors, 0); C(p, sprite_idx, 1); C(p, is_selected, 2); C(p, decorations_sprite_map, 3); } #undef C for (int i = GRAPHICS_PROGRAM; i <= GRAPHICS_ALPHA_MASK_PROGRAM; i++) { @@ -455,6 +526,7 @@ cell_prepare_to_render(ssize_t vao_idx, Screen *screen, GLfloat xstart, GLfloat #undef update_cell_data screen->last_rendered.columns = screen->columns; screen->last_rendered.lines = screen->lines; + return changed; } @@ -653,6 +725,7 @@ set_cell_uniforms(float current_inactive_text_alpha, bool force) { 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); diff --git a/kitty/shaders.py b/kitty/shaders.py index 46575ae47..61c68b1a4 100644 --- a/kitty/shaders.py +++ b/kitty/shaders.py @@ -161,7 +161,6 @@ class LoadShaderPrograms: MARK_SHIFT=MARK, MARK_MASK=MARK_MASK, DECORATION_MASK=DECORATION_MASK, - STRIKE_SPRITE_INDEX=0, ) def resolve_cell_defines(which: str, src: str) -> str: diff --git a/kitty/state.h b/kitty/state.h index 72f48d729..17d79bc77 100644 --- a/kitty/state.h +++ b/kitty/state.h @@ -387,7 +387,7 @@ void update_surface_size(int, int, uint32_t); 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); -void send_sprite_to_gpu(FONTS_DATA_HANDLE fg, sprite_index, pixel*); +void send_sprite_to_gpu(FONTS_DATA_HANDLE fg, sprite_index, pixel*, sprite_index); void blank_canvas(float, color_type); void blank_os_window(OSWindow *); void set_os_window_chrome(OSWindow *w);