From 9e9ab3e6f93c0ffb90f7210d9405a48d0420b630 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 13 Dec 2024 13:10:20 +0530 Subject: [PATCH] pass decorations location as sprite metadata --- kitty/data-types.c | 1 - kitty/fast_data_types.pyi | 1 - kitty/fonts.c | 143 +++++++++++++++++++++++++++++--------- kitty/line.h | 2 +- kitty/shaders.c | 4 +- kitty/shaders.py | 3 +- kitty/state.h | 2 +- kitty_tests/fonts.py | 4 +- 8 files changed, 117 insertions(+), 43 deletions(-) diff --git a/kitty/data-types.c b/kitty/data-types.c index d4feca297..3805b3150 100644 --- a/kitty/data-types.c +++ b/kitty/data-types.c @@ -758,7 +758,6 @@ PyInit_fast_data_types(void) { #undef s PyModule_AddIntConstant(m, "MARK_MASK", MARK_MASK); PyModule_AddIntConstant(m, "DECORATION_MASK", DECORATION_MASK); - PyModule_AddIntConstant(m, "NUM_UNDERLINE_STYLES", NUM_UNDERLINE_STYLES); PyModule_AddStringMacro(m, ERROR_PREFIX); #ifdef KITTY_VCS_REV PyModule_AddStringMacro(m, KITTY_VCS_REV); diff --git a/kitty/fast_data_types.pyi b/kitty/fast_data_types.pyi index 3c39b2937..7192585da 100644 --- a/kitty/fast_data_types.pyi +++ b/kitty/fast_data_types.pyi @@ -276,7 +276,6 @@ GRAPHICS_PROGRAM: int MARK: int MARK_MASK: int DECORATION_MASK: int -NUM_UNDERLINE_STYLES: int FILE_TRANSFER_CODE: int ESC_CSI: int ESC_OSC: int diff --git a/kitty/fonts.c b/kitty/fonts.c index c894d20fa..d8dc7e860 100644 --- a/kitty/fonts.c +++ b/kitty/fonts.c @@ -15,13 +15,12 @@ #include "decorations.h" #include "glyph-cache.h" -#define MISSING_GLYPH (NUM_UNDERLINE_STYLES + 2) +#define MISSING_GLYPH 1 #define MAX_NUM_EXTRA_GLYPHS_PUA 4u #define debug debug_fonts static PyObject *python_send_to_gpu_impl = NULL; -#define current_send_sprite_to_gpu(...) (python_send_to_gpu_impl ? python_send_to_gpu(__VA_ARGS__) : send_sprite_to_gpu(__VA_ARGS__)) extern PyTypeObject Line_Type; enum {NO_FONT=-3, MISSING_FONT=-2, BLANK_FONT=-1, BOX_FONT=0}; @@ -91,6 +90,22 @@ typedef struct ScaledFontData { #define CMPR_FN vt_cmpr_float #include "kitty-verstable.h" +typedef union DecorationsKey { + struct { uint8_t scale, subscale_n, subscale_d, vertical_align, multicell_y; }; + uint64_t val; +} DecorationsKey; +static_assert(sizeof(DecorationsKey) == sizeof(uint64_t), "Fix the ordering of DecorationsKey"); + +static uint64_t hash_decorations_key(DecorationsKey k) { return vt_hash_integer(k.val); } +static bool cmpr_decorations_key(DecorationsKey a, DecorationsKey b) { return a.val == b.val; } +#define NAME decorations_index_map_t +#define KEY_TY DecorationsKey +#define VAL_TY sprite_index +#define HASH_FN hash_decorations_key +#define CMPR_FN cmpr_decorations_key +#include "kitty-verstable.h" + + typedef struct { FONTS_DATA_HEAD id_type id; @@ -101,6 +116,7 @@ typedef struct { GPUSpriteTracker sprite_tracker; fallback_font_map_t fallback_font_map; scaled_font_map_t scaled_font_map; + decorations_index_map_t decorations_index_map; } FontGroup; static FontGroup* font_groups = NULL; @@ -109,6 +125,25 @@ static size_t num_font_groups = 0; static id_type font_group_id_counter = 0; static void initialize_font_group(FontGroup *fg); +static void +python_send_to_gpu(FontGroup *fg, sprite_index idx, pixel *buf) { + unsigned int x, y, z; + sprite_index_to_pos(idx, fg->sprite_tracker.xnum, fg->sprite_tracker.ynum, &x, &y, &z); + const size_t sprite_size = fg->fcm.cell_width * fg->fcm.cell_height; + PyObject *ret = PyObject_CallFunction(python_send_to_gpu_impl, "IIIy#y#", x, y, z, buf, sprite_size * sizeof(buf[0]), buf + sprite_size, fg->fcm.cell_width * sizeof(buf[0])); + if (ret == NULL) PyErr_Print(); + else Py_DECREF(ret); +} + +static void +current_send_sprite_to_gpu(FontGroup *fg, sprite_index idx, pixel *buf, sprite_index decorations_idx) { + pixel *metadata = buf + fg->fcm.cell_width * fg->fcm.cell_height; + metadata[0] = decorations_idx; + memset(metadata + 1, 0, (fg->fcm.cell_width - 1) * sizeof(metadata[0])); + if (python_send_to_gpu_impl) python_send_to_gpu(fg, idx, buf); + else send_sprite_to_gpu((FONTS_DATA_HANDLE)fg, idx, buf); +} + static void ensure_canvas_can_fit(FontGroup *fg, unsigned cells, unsigned scale) { #define cs(cells, scale) (sizeof(fg->canvas.buf[0]) * 3u * cells * fg->fcm.cell_width * (fg->fcm.cell_height + 1) * scale * scale) @@ -179,6 +214,7 @@ del_font_group(FontGroup *fg) { fg->sprite_map = free_sprite_map(fg->sprite_map); vt_cleanup(&fg->fallback_font_map); vt_cleanup(&fg->scaled_font_map); + vt_cleanup(&fg->decorations_index_map); for (size_t i = 0; i < fg->fonts_count; i++) del_font(fg->fonts + i); free(fg->fonts); fg->fonts = NULL; fg->fonts_count = 0; } @@ -393,7 +429,7 @@ init_font(Font *f, PyObject *face, bool bold, bool italic, bool emoji_presentati f->ffs_hb_features = calloc(1 + features->count, sizeof(hb_feature_t)); if (!f->ffs_hb_features) { PyErr_NoMemory(); return false; } f->num_ffs_hb_features = features->count; - memcpy(f->ffs_hb_features, features->features, sizeof(hb_feature_t) * features->count); + if (features->count) memcpy(f->ffs_hb_features, features->features, sizeof(hb_feature_t) * features->count); memcpy(f->ffs_hb_features + f->num_ffs_hb_features++, &hb_features[CALT_FEATURE], sizeof(hb_feature_t)); return true; } @@ -408,20 +444,6 @@ free_font_groups(void) { free_glyph_cache_global_resources(); } -static void -python_send_to_gpu(FONTS_DATA_HANDLE fg_, unsigned int idx, pixel *buf) { - FontGroup *fg = (FontGroup*)fg_; - if (python_send_to_gpu_impl) { - if (!num_font_groups) fatal("Cannot call send to gpu with no font groups"); - unsigned int x, y, z; - sprite_index_to_pos(idx, fg->sprite_tracker.xnum, fg->sprite_tracker.ynum, &x, &y, &z); - const size_t sprite_size = fg->fcm.cell_width * fg->fcm.cell_height; - PyObject *ret = PyObject_CallFunction(python_send_to_gpu_impl, "IIIy#y#", x, y, z, buf, sprite_size * sizeof(pixel), buf + sprite_size, fg->fcm.cell_width * sizeof(pixel)); - if (ret == NULL) PyErr_Print(); - else Py_DECREF(ret); - } -} - static void adjust_metric(unsigned int *metric, float adj, AdjustmentUnit unit, double dpi) { if (adj == 0.f) return; @@ -869,6 +891,59 @@ set_cell_sprite(GPUCell *cell, const SpritePosition *sp) { if (sp->colored) cell->sprite_idx |= 0x80000000; } +static void +render_scaled_decoration(FontCellMetrics unscaled_metrics, FontCellMetrics scaled_metrics, uint8_t *alpha_mask, pixel *output, Region src, Region dest) { + pixel col = 0xffffff00; + unsigned src_limit = MIN(scaled_metrics.cell_height, src.bottom), dest_limit = MIN(unscaled_metrics.cell_height, dest.bottom); + unsigned cell_width = MIN(scaled_metrics.cell_width, unscaled_metrics.cell_width); + for (unsigned srcy = src.top, desty=dest.top; srcy < src_limit && desty < dest_limit; srcy++, desty++) { + uint8_t *srcp = alpha_mask + cell_width * srcy; + pixel *destp = output + cell_width * desty; + for (unsigned x = 0; x < cell_width; x++) destp[x] = col | srcp[x]; + } +} + +static sprite_index +render_decorations(FontGroup *fg, Region src, Region dest, FontCellMetrics scaled_metrics) { + if ((src.bottom == src.top) || (dest.bottom == dest.top)) return 0; // no overlap + const FontCellMetrics unscaled_metrics = fg->fcm; + scaled_metrics.cell_width = unscaled_metrics.cell_width; + RAII_ALLOC(uint8_t, alpha_mask, malloc(scaled_metrics.cell_height * scaled_metrics.cell_width)); + RAII_ALLOC(pixel, buf, malloc(unscaled_metrics.cell_width * (unscaled_metrics.cell_height + 1) * sizeof(pixel))); + if (!alpha_mask || !buf) fatal("Out of memory"); + int error = 0; + sprite_index idx = current_sprite_index(&fg->sprite_tracker); +#define increment_sprite_index do_increment(fg, &error); if (error != 0) { sprite_map_set_error(error); PyErr_Print(); fatal("Failed"); } +#define do_one(call) \ + memset(alpha_mask, 0, scaled_metrics.cell_width * scaled_metrics.cell_height * sizeof(alpha_mask[0])); \ + call; \ + memset(buf, 0, unscaled_metrics.cell_width * unscaled_metrics.cell_height * sizeof(buf[0])); \ + render_scaled_decoration(unscaled_metrics, scaled_metrics, alpha_mask, buf, src, dest); \ + current_send_sprite_to_gpu(fg, current_sprite_index(&fg->sprite_tracker), buf, 0); \ + increment_sprite_index; + + do_one(add_strikethrough(alpha_mask, scaled_metrics)); + do_one(add_straight_underline(alpha_mask, scaled_metrics)); + do_one(add_double_underline(alpha_mask, scaled_metrics)); + do_one(add_curl_underline(alpha_mask, scaled_metrics)); + do_one(add_dotted_underline(alpha_mask, scaled_metrics)); + do_one(add_dashed_underline(alpha_mask, scaled_metrics)); + + return idx; +#undef increment_sprite_index +#undef do_one +} + +static sprite_index +index_for_decorations(FontGroup *fg, RunFont rf, Region src, Region dest, FontCellMetrics scaled_metrics) { + DecorationsKey key = {.scale=rf.scale, .subscale_n = rf.subscale_n, .subscale_d = rf.subscale_d, .vertical_align = rf.vertical_align, .multicell_y = rf.multicell_y }; + decorations_index_map_t_itr i = vt_get(&fg->decorations_index_map, key); + if (!vt_is_end(i)) return i.data->val; + sprite_index idx = render_decorations(fg, src, dest, scaled_metrics); + if (vt_is_end(vt_insert(&fg->decorations_index_map, key, idx))) fatal("Out of memory"); + return idx; +} + static void render_box_cell(FontGroup *fg, RunFont rf, CPUCell *cpu_cell, GPUCell *gpu_cell, const TextCache *tc) { int error = 0; @@ -900,8 +975,10 @@ render_box_cell(FontGroup *fg, RunFont rf, CPUCell *cpu_cell, GPUCell *gpu_cell, if (!sp[ligature_index]->rendered) all_rendered = false; } if (all_rendered) return; + float scale = apply_scale_to_font_group(fg, &rf); + FontCellMetrics scaled_metrics = fg->fcm; unsigned width = fg->fcm.cell_width, height = fg->fcm.cell_height; - float scale = scaled_cell_dimensions(rf, &width, &height); + if (scale != 1) apply_scale_to_font_group(fg, NULL); RAII_PyObject(ret, NULL); uint8_t *alpha_mask = NULL; ensure_canvas_can_fit(fg, num_glyphs + 1, rf.scale); if (num_glyphs == 1) { @@ -929,7 +1006,7 @@ render_box_cell(FontGroup *fg, RunFont rf, CPUCell *cpu_cell, GPUCell *gpu_cell, /*printf("Rendered char sz: (%u, %u)\n", width, height); dump_sprite(fg->canvas.buf, width, height, false);*/ if (scale == 1.f && rf.scale == 1 && !rf.subscale_n) { sp[0]->rendered = true; - current_send_sprite_to_gpu((FONTS_DATA_HANDLE)fg, sp[0]->idx, fg->canvas.buf); + current_send_sprite_to_gpu(fg, sp[0]->idx, fg->canvas.buf, index_for_decorations(fg, rf, src, dest, scaled_metrics)); } else { calculate_regions_for_line(rf, fg->fcm.cell_height, &src, &dest); /*printf("width: %u height: %u unscaled_cell_width: %u unscaled_cell_height: %u src.top: %u src.bottom: %u rf.scale: %u\n", width, height, fg->fcm.cell_width, fg->fcm.cell_height, src.top, src.bottom, rf.scale);*/ @@ -937,7 +1014,7 @@ render_box_cell(FontGroup *fg, RunFont rf, CPUCell *cpu_cell, GPUCell *gpu_cell, pixel *b = extract_cell_region(&fg->canvas, i, &src, &dest, width, fg->fcm); /*printf("Sprite %u: pos: (%u, %u, %u) sz: (%u, %u)\n", i, sp[i]->x, sp[i]->y, sp[i]->z, fg->fcm.cell_width, fg->fcm.cell_height); dump_sprite(b, fg->fcm.cell_width, fg->fcm.cell_height, false);*/ sp[i]->rendered = true; - current_send_sprite_to_gpu((FONTS_DATA_HANDLE)fg, sp[i]->idx, b); + current_send_sprite_to_gpu(fg, sp[i]->idx, b, index_for_decorations(fg, rf, src, dest, scaled_metrics)); } } #undef sp @@ -1025,7 +1102,8 @@ render_group( bool is_repeat_sprite = is_infinite_ligature && i > 0 && sp[i]->idx == sp[i-1]->idx; if (!is_repeat_sprite) { pixel *b = num_cells == 1 ? fg->canvas.buf : extract_cell_from_canvas(fg, i, num_cells); - current_send_sprite_to_gpu((FONTS_DATA_HANDLE)fg, sp[i]->idx, b); + Region src = {.bottom=unscaled_metrics.cell_height, .right=unscaled_metrics.cell_width}, dest = src; + current_send_sprite_to_gpu(fg, sp[i]->idx, b, index_for_decorations(fg, rf, src, dest, scaled_metrics)); } sp[i]->rendered = true; sp[i]->colored = was_colored; } @@ -1039,7 +1117,7 @@ render_group( if (!sp[i]->rendered) { pixel *b = extract_cell_region(&fg->canvas, i, &src, &dest, scaled_metrics.cell_width * num_scaled_cells, unscaled_metrics); /*printf("cell %u src -> dest: (%u %u) -> (%u %u)\n", i, src.left, src.right, dest.left, dest.right);*/ - current_send_sprite_to_gpu((FONTS_DATA_HANDLE)fg, sp[i]->idx, b); + current_send_sprite_to_gpu(fg, sp[i]->idx, b, index_for_decorations(fg, rf, src, dest, scaled_metrics)); /*dump_sprite(b, unscaled_metrics.cell_width, unscaled_metrics.cell_height, false);*/ sp[i]->rendered = true; sp[i]->colored = was_colored; } @@ -1769,7 +1847,8 @@ send_prerendered_sprites(FontGroup *fg) { int error = 0; // blank cell ensure_canvas_can_fit(fg, 1, 1); - current_send_sprite_to_gpu((FONTS_DATA_HANDLE)fg, 0, fg->canvas.buf); + sprite_index dec_idx = 5; + current_send_sprite_to_gpu(fg, 0, fg->canvas.buf, dec_idx); const unsigned cell_area = fg->fcm.cell_height * fg->fcm.cell_width; RAII_ALLOC(uint8_t, alpha_mask, malloc(cell_area)); if (!alpha_mask) fatal("Out of memory"); @@ -1781,23 +1860,20 @@ send_prerendered_sprites(FontGroup *fg) { ensure_canvas_can_fit(fg, 1, 1); /* clear canvas */ \ render_alpha_mask(alpha_mask, fg->canvas.buf, &r, &r, fg->fcm.cell_width, fg->fcm.cell_width, 0xffffff); \ increment_sprite_index; \ - current_send_sprite_to_gpu((FONTS_DATA_HANDLE)fg, current_sprite_index(&fg->sprite_tracker), fg->canvas.buf); + current_send_sprite_to_gpu(fg, current_sprite_index(&fg->sprite_tracker), fg->canvas.buf, dec_idx); // If you change the mapping of these cells you will need to change - // NUM_UNDERLINE_STYLES and BEAM_IDX in shader.c and STRIKE_SPRITE_INDEX in - // window.py and MISSING_GLYPH in font.c - do_one(add_straight_underline(alpha_mask, fg->fcm)); - do_one(add_double_underline(alpha_mask, fg->fcm)); - do_one(add_curl_underline(alpha_mask, fg->fcm)); - do_one(add_dotted_underline(alpha_mask, fg->fcm)); - do_one(add_dashed_underline(alpha_mask, fg->fcm)); - do_one(add_strikethrough(alpha_mask, fg->fcm)); + // BEAM_IDX in shader.c and STRIKE_SPRITE_INDEX in + // shaders.py and MISSING_GLYPH in font.c and dec_idx above do_one(add_missing_glyph(alpha_mask, fg->fcm)); do_one(add_beam_cursor(alpha_mask, fg->fcm, fg->logical_dpi_x)); do_one(add_underline_cursor(alpha_mask, fg->fcm, fg->logical_dpi_y)); do_one(add_hollow_cursor(alpha_mask, fg->fcm, fg->logical_dpi_x, fg->logical_dpi_y)); - increment_sprite_index; + RunFont rf = {.scale=1}; + Region rg = {.bottom = fg->fcm.cell_height, .right = fg->fcm.cell_width}; + sprite_index actual_dec_idx = index_for_decorations(fg, rf, rg, rg, fg->fcm); + if (actual_dec_idx != dec_idx) fatal("dec_idx: %u != actual_dec_idx: %u", dec_idx, actual_dec_idx); #undef increment_sprite_index #undef do_one @@ -1832,6 +1908,7 @@ initialize_font_group(FontGroup *fg) { if (!init_hash_tables(fg->fonts)) fatal("Out of memory"); vt_init(&fg->fallback_font_map); vt_init(&fg->scaled_font_map); + vt_init(&fg->decorations_index_map); #define I(attr) if (descriptor_indices.attr) fg->attr##_font_idx = initialize_font(fg, descriptor_indices.attr, #attr); else fg->attr##_font_idx = -1; fg->medium_font_idx = initialize_font(fg, 0, "medium"); I(bold); I(italic); I(bi); diff --git a/kitty/line.h b/kitty/line.h index da5e3f211..28c2c9255 100644 --- a/kitty/line.h +++ b/kitty/line.h @@ -14,6 +14,7 @@ // TODO: Handle selection with multicell // TODO: URL detection with multicell // TODO: Cursor rendering over multicell +// TODO: Wrapping of multicell draw commands // TODO: Handle rewrap and restitch of multiline chars // TODO: Handle rewrap when a character is too wide/tall to fit on resized screen // TODO: Implement baseline align for box drawing @@ -35,7 +36,6 @@ static_assert(sizeof(CellAttrs) == sizeof(uint32_t), "Fix the ordering of CellAt #define WIDTH_MASK (3u) #define DECORATION_MASK (7u) -#define NUM_UNDERLINE_STYLES (5u) #define SGR_MASK (~(((CellAttrs){.mark=MARK_MASK}).val)) // Text presentation selector #define VS15 0xfe0e diff --git a/kitty/shaders.c b/kitty/shaders.c index 0170662f2..0eb754085 100644 --- a/kitty/shaders.c +++ b/kitty/shaders.c @@ -143,7 +143,7 @@ ensure_sprite_map(FONTS_DATA_HANDLE fg) { } void -send_sprite_to_gpu(FONTS_DATA_HANDLE fg, unsigned int idx, pixel *buf) { +send_sprite_to_gpu(FONTS_DATA_HANDLE fg, sprite_index idx, pixel *buf) { SpriteMap *sprite_map = (SpriteMap*)fg->sprite_map; unsigned int xnum, ynum, znum, x, y, z; sprite_tracker_current_layout(fg, &xnum, &ynum, &znum); @@ -330,7 +330,7 @@ cell_update_uniform_block(ssize_t vao_idx, Screen *screen, int uniform_buffer, c } rd->use_cell_for_selection_bg = IS_SPECIAL_COLOR(highlight_bg) ? 1. : 0.; // Cursor position - enum { BLOCK_IDX = 0, BEAM_IDX = NUM_UNDERLINE_STYLES + 3, UNDERLINE_IDX = NUM_UNDERLINE_STYLES + 4, UNFOCUSED_IDX = NUM_UNDERLINE_STYLES + 5 }; + enum { BLOCK_IDX = 0, BEAM_IDX = 2, UNDERLINE_IDX = 3, UNFOCUSED_IDX = 4 }; Line *line_for_cursor = NULL; if (cursor->opacity > 0) { rd->cursor_x = cursor->x, rd->cursor_y = cursor->y; diff --git a/kitty/shaders.py b/kitty/shaders.py index bea5fcb3c..46575ae47 100644 --- a/kitty/shaders.py +++ b/kitty/shaders.py @@ -23,7 +23,6 @@ from .fast_data_types import ( GRAPHICS_PROGRAM, MARK, MARK_MASK, - NUM_UNDERLINE_STYLES, REVERSE, STRIKETHROUGH, TINT_PROGRAM, @@ -162,7 +161,7 @@ class LoadShaderPrograms: MARK_SHIFT=MARK, MARK_MASK=MARK_MASK, DECORATION_MASK=DECORATION_MASK, - STRIKE_SPRITE_INDEX=NUM_UNDERLINE_STYLES + 1, + STRIKE_SPRITE_INDEX=0, ) def resolve_cell_defines(which: str, src: str) -> str: diff --git a/kitty/state.h b/kitty/state.h index b4896e5d1..72f48d729 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, unsigned int, pixel*); +void send_sprite_to_gpu(FONTS_DATA_HANDLE fg, sprite_index, pixel*); void blank_canvas(float, color_type); void blank_os_window(OSWindow *); void set_os_window_chrome(OSWindow *w); diff --git a/kitty_tests/fonts.py b/kitty_tests/fonts.py index 189b8053d..b7cdf5ae5 100644 --- a/kitty_tests/fonts.py +++ b/kitty_tests/fonts.py @@ -270,7 +270,7 @@ class FontBaseTest(BaseTest): tc = setup_for_testing(size=self.font_size, dpi=self.dpi, main_face_path=path) self.sprites, self.sprite_metadata, self.cell_width, self.cell_height = tc.__enter__() self.addCleanup(tc.__exit__) - self.assertEqual([k[0] for k in self.sprites], [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) + self.assertEqual([k[0] for k in self.sprites], list(range(11))) def tearDown(self): del self.sprites, self.cell_width, self.cell_height @@ -296,8 +296,8 @@ class Rendering(FontBaseTest): self.ae(test_sprite_position_for(0, 2), (1, 0, 2)) def test_box_drawing(self): - prerendered = len(self.sprites) s = self.create_screen(cols=len(box_chars) + 1, lines=1, scrollback=0) + prerendered = len(self.sprites) s.draw(''.join(box_chars)) line = s.line(0) test_render_line(line)