From 5d195bf50b99c54446bd2ba6268b64a2802a3ff1 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 12 Dec 2024 09:59:27 +0530 Subject: [PATCH] Give sprites a metadata row accessible in the shaders Will allow sprites to point to where their decorations should be read from, for instance. Needed for scaled text and also if we want to implement decoration avoidance. --- kitty/cell_vertex.glsl | 18 +++++++++++++----- kitty/fast_data_types.pyi | 2 +- kitty/fonts.c | 16 +++++++++++----- kitty/fonts/box_drawing.py | 4 ++-- kitty/fonts/render.py | 12 +++++++----- kitty/shaders.c | 11 ++++++----- kitty_tests/fonts.py | 4 ++-- 7 files changed, 42 insertions(+), 25 deletions(-) diff --git a/kitty/cell_vertex.glsl b/kitty/cell_vertex.glsl index 2822d211c..5cbeceb23 100644 --- a/kitty/cell_vertex.glsl +++ b/kitty/cell_vertex.glsl @@ -8,7 +8,7 @@ layout(std140) uniform CellRenderData { 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; + uint xnum, ynum, sprites_xnum, sprites_ynum, cursor_fg_sprite_idx, cell_height; float cursor_x, cursor_y, cursor_w, cursor_opacity; // must have unique entries with 0 being default_bg and unset being UINT32_MAX @@ -96,15 +96,23 @@ vec3 to_color(uint c, uint defval) { return color_to_vec(resolve_color(c, defval)); } -vec3 to_sprite_pos(uvec2 pos, uint idx) { +uvec3 to_sprite_coords(uint idx) { uint sprites_per_page = sprites_xnum * sprites_ynum; uint z = idx / sprites_per_page; uint num_on_last_page = idx % sprites_per_page; uint y = num_on_last_page / sprites_xnum; uint x = num_on_last_page % sprites_xnum; - vec2 s_xpos = vec2(x, float(x) + 1.0f) * (1.0f / float(sprites_xnum)); - vec2 s_ypos = vec2(y, float(y) + 1.0f) * (1.0f / float(sprites_ynum)); - return vec3(s_xpos[pos.x], s_ypos[pos.y], z); + return uvec3(x, y, z); +} + +vec3 to_sprite_pos(uvec2 pos, uint idx) { + uvec3 c = to_sprite_coords(idx); + vec2 s_xpos = vec2(c.x, float(c.x) + 1.0f) * (1.0f / float(sprites_xnum)); + vec2 s_ypos = vec2(c.y, float(c.y) + 1.0f) * (1.0f / float(sprites_ynum)); + uint texture_height_px = (cell_height + 1u) * sprites_ynum; + float row_height = 1.0f / float(texture_height_px); + s_ypos[1] -= row_height; // skip the metadata row + return vec3(s_xpos[pos.x], s_ypos[pos.y], c.z); } vec3 choose_color(float q, vec3 a, vec3 b) { diff --git a/kitty/fast_data_types.pyi b/kitty/fast_data_types.pyi index da3db5ff3..3c39b2937 100644 --- a/kitty/fast_data_types.pyi +++ b/kitty/fast_data_types.pyi @@ -1126,7 +1126,7 @@ def sprite_map_set_limits(w: int, h: int) -> None: def set_send_sprite_to_gpu( - func: Optional[Callable[[int, int, int, bytes], None]] + func: Optional[Callable[[int, int, int, bytes, bytes], None]] ) -> None: pass diff --git a/kitty/fonts.c b/kitty/fonts.c index aa1e3c414..c894d20fa 100644 --- a/kitty/fonts.c +++ b/kitty/fonts.c @@ -111,7 +111,7 @@ static void initialize_font_group(FontGroup *fg); 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 * scale * scale) +#define cs(cells, scale) (sizeof(fg->canvas.buf[0]) * 3u * cells * fg->fcm.cell_width * (fg->fcm.cell_height + 1) * scale * scale) size_t size_in_bytes = cs(cells, scale); if (size_in_bytes > fg->canvas.size_in_bytes) { free(fg->canvas.buf); @@ -294,7 +294,7 @@ sprite_tracker_current_layout(FONTS_DATA_HANDLE data, unsigned int *x, unsigned static void sprite_tracker_set_layout(GPUSpriteTracker *sprite_tracker, unsigned int cell_width, unsigned int cell_height) { sprite_tracker->xnum = MIN(MAX(1u, max_texture_size / cell_width), (size_t)UINT16_MAX); - sprite_tracker->max_y = MIN(MAX(1u, max_texture_size / cell_height), (size_t)UINT16_MAX); + sprite_tracker->max_y = MIN(MAX(1u, max_texture_size / (cell_height + 1)), (size_t)UINT16_MAX); sprite_tracker->ynum = 1; sprite_tracker->x = 0; sprite_tracker->y = 0; sprite_tracker->z = 0; } @@ -415,7 +415,8 @@ python_send_to_gpu(FONTS_DATA_HANDLE fg_, unsigned int idx, pixel *buf) { 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); - PyObject *ret = PyObject_CallFunction(python_send_to_gpu_impl, "IIIN", x, y, z, PyBytes_FromStringAndSize((const char*)buf, sizeof(pixel) * fg->fcm.cell_width * fg->fcm.cell_height)); + 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); } @@ -795,9 +796,14 @@ apply_scale_to_font_group(FontGroup *fg, RunFont *rf) { return scale; } +static pixel* +pointer_to_space_for_last_sprite(Canvas *canvas, FontCellMetrics fcm) { + return canvas->buf + (canvas->size_in_bytes / sizeof(canvas->buf[0]) - fcm.cell_width * (fcm.cell_height + 1)); +} + static pixel* extract_cell_from_canvas(FontGroup *fg, unsigned int i, unsigned int num_cells) { - pixel *ans = fg->canvas.buf + (fg->canvas.size_in_bytes / sizeof(fg->canvas.buf[0]) - fg->fcm.cell_width * fg->fcm.cell_height); + pixel *ans = pointer_to_space_for_last_sprite(&fg->canvas, fg->fcm); pixel *dest = ans, *src = fg->canvas.buf + (i * fg->fcm.cell_width); unsigned int stride = fg->fcm.cell_width * num_cells; for (unsigned int r = 0; r < fg->fcm.cell_height; r++, dest += fg->fcm.cell_width, src += stride) memcpy(dest, src, fg->fcm.cell_width * sizeof(fg->canvas.buf[0])); @@ -834,7 +840,7 @@ static pixel* extract_cell_region(Canvas *canvas, unsigned i, Region *src, const Region *dest, unsigned src_width, FontCellMetrics unscaled_metrics) { src->left = i * unscaled_metrics.cell_width; src->right = MIN(src_width, src->left + unscaled_metrics.cell_width); unsigned unscaled_cell_area = unscaled_metrics.cell_width * unscaled_metrics.cell_height; - pixel *ans = canvas->buf + (canvas->size_in_bytes / sizeof(canvas->buf[0]) - unscaled_cell_area); + pixel *ans = pointer_to_space_for_last_sprite(canvas, unscaled_metrics); memset(ans, 0, sizeof(ans[0]) * unscaled_cell_area); unsigned width = MIN(src->right - src->left, unscaled_metrics.cell_width); for (unsigned srcy = src->top, desty = dest->top; srcy < src->bottom && desty < dest->bottom; srcy++, desty++) { diff --git a/kitty/fonts/box_drawing.py b/kitty/fonts/box_drawing.py index 4c435bad3..823adbae1 100644 --- a/kitty/fonts/box_drawing.py +++ b/kitty/fonts/box_drawing.py @@ -1439,7 +1439,7 @@ def test_char(ch: str, sz: int = 48) -> None: from kitty.fast_data_types import concat_cells, set_send_sprite_to_gpu from .render import display_bitmap, setup_for_testing - with setup_for_testing('monospace', sz) as (_, width, height): + with setup_for_testing('monospace', sz) as (_, _, width, height): buf = bytearray(width * height) try: render_box_char(ch, buf, width, height) @@ -1460,7 +1460,7 @@ def test_drawing(sz: int = 48, family: str = 'monospace', start: int = 0x2500, n from .render import display_bitmap, setup_for_testing - with setup_for_testing(family, sz) as (_, width, height): + with setup_for_testing(family, sz) as (_, _, width, height): space = bytearray(width * height) def join_cells(cells: Iterable[bytes]) -> bytes: diff --git a/kitty/fonts/render.py b/kitty/fonts/render.py index 213115ca9..5362a53d7 100644 --- a/kitty/fonts/render.py +++ b/kitty/fonts/render.py @@ -236,14 +236,16 @@ class setup_for_testing: self.family, self.size, self.dpi = family, size, dpi self.main_face_path = main_face_path - def __enter__(self) -> tuple[dict[tuple[int, int, int], bytes], int, int]: + def __enter__(self) -> tuple[dict[tuple[int, int, int], bytes], dict[tuple[int, int, int], bytes], int, int]: global descriptor_overrides opts = defaults._replace(font_family=parse_font_spec(self.family), font_size=self.size) set_options(opts) sprites = {} + sprite_metadata = {} - def send_to_gpu(x: int, y: int, z: int, data: bytes) -> None: + def send_to_gpu(x: int, y: int, z: int, data: bytes, metadata: bytes) -> None: sprites[(x, y, z)] = data + sprite_metadata[(x, y, z)] = metadata sprite_map_set_limits(self.xnum, self.ynum) set_send_sprite_to_gpu(send_to_gpu) @@ -254,7 +256,7 @@ class setup_for_testing: try: set_font_family(opts) cell_width, cell_height = create_test_font_group(self.size, self.dpi, self.dpi) - return sprites, cell_width, cell_height + return sprites, sprite_metadata, cell_width, cell_height except Exception: set_send_sprite_to_gpu(None) raise @@ -266,7 +268,7 @@ class setup_for_testing: def render_string(text: str, family: str = 'monospace', size: float = 11.0, dpi: float = 96.0) -> tuple[int, int, list[bytes]]: - with setup_for_testing(family, size, dpi) as (sprites, cell_width, cell_height): + with setup_for_testing(family, size, dpi) as (sprites, sprite_metadata, cell_width, cell_height): s = Screen(None, 1, len(text)*2) line = s.line(0) s.draw(text) @@ -286,7 +288,7 @@ def render_string(text: str, family: str = 'monospace', size: float = 11.0, dpi: def shape_string( text: str = "abcd", family: str = 'monospace', size: float = 11.0, dpi: float = 96.0, path: Optional[str] = None ) -> list[tuple[int, int, int, tuple[int, ...]]]: - with setup_for_testing(family, size, dpi) as (sprites, cell_width, cell_height): + with setup_for_testing(family, size, dpi) as (sprites, sprite_metadata, cell_width, cell_height): s = Screen(None, 1, len(text)*2) line = s.line(0) s.draw(text) diff --git a/kitty/shaders.c b/kitty/shaders.c index bb4da3a40..0170662f2 100644 --- a/kitty/shaders.c +++ b/kitty/shaders.c @@ -118,12 +118,12 @@ realloc_sprite_texture(FONTS_DATA_HANDLE fg) { 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; + width = xnum * fg->fcm.cell_width; height = ynum * (fg->fcm.cell_height + 1); 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); + copy_image_sub_data(sprite_map->texture_id, tex, width, src_ynum * (fg->fcm.cell_height + 1), sprite_map->last_num_of_layers); glDeleteTextures(1, &sprite_map->texture_id); } glBindTexture(GL_TEXTURE_2D_ARRAY, 0); @@ -154,8 +154,8 @@ send_sprite_to_gpu(FONTS_DATA_HANDLE fg, unsigned int idx, pixel *buf) { glBindTexture(GL_TEXTURE_2D_ARRAY, sprite_map->texture_id); glPixelStorei(GL_UNPACK_ALIGNMENT, 4); sprite_index_to_pos(idx, xnum, ynum, &x, &y, &z); - x *= fg->fcm.cell_width; y *= fg->fcm.cell_height; - glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, x, y, z, fg->fcm.cell_width, fg->fcm.cell_height, 1, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, buf); + x *= fg->fcm.cell_width; y *= fg->fcm.cell_height + 1; + glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, x, y, z, fg->fcm.cell_width, fg->fcm.cell_height + 1, 1, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, buf); } void @@ -299,7 +299,7 @@ cell_update_uniform_block(ssize_t vao_idx, Screen *screen, int uniform_buffer, c GLuint default_fg, highlight_fg, highlight_bg, cursor_fg, cursor_bg, url_color, url_style, inverted; - GLuint xnum, ynum, sprites_xnum, sprites_ynum, cursor_fg_sprite_idx; + GLuint xnum, ynum, sprites_xnum, sprites_ynum, cursor_fg_sprite_idx, cell_height; GLfloat cursor_x, cursor_y, cursor_w, cursor_opacity; GLuint bg_colors0, bg_colors1, bg_colors2, bg_colors3, bg_colors4, bg_colors5, bg_colors6, bg_colors7; GLfloat bg_opacities0, bg_opacities1, bg_opacities2, bg_opacities3, bg_opacities4, bg_opacities5, bg_opacities6, bg_opacities7; @@ -389,6 +389,7 @@ cell_update_uniform_block(ssize_t vao_idx, Screen *screen, int uniform_buffer, c unsigned int x, y, z; sprite_tracker_current_layout(os_window->fonts_data, &x, &y, &z); rd->sprites_xnum = x; rd->sprites_ynum = y; + rd->cell_height = os_window->fonts_data->fcm.cell_height; rd->inverted = screen_invert_colors(screen) ? 1 : 0; #undef COLOR diff --git a/kitty_tests/fonts.py b/kitty_tests/fonts.py index 933f55c08..189b8053d 100644 --- a/kitty_tests/fonts.py +++ b/kitty_tests/fonts.py @@ -268,7 +268,7 @@ class FontBaseTest(BaseTest): self.addCleanup(self.rmtree_ignoring_errors, self.tdir) path = self.path_for_font(self.font_name) if self.font_name else '' tc = setup_for_testing(size=self.font_size, dpi=self.dpi, main_face_path=path) - self.sprites, self.cell_width, self.cell_height = tc.__enter__() + 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]) @@ -283,7 +283,7 @@ class Rendering(FontBaseTest): def test_sprite_map(self): sprite_map_set_limits(10, 2) - sprite_map_set_layout(5, 5) + sprite_map_set_layout(5, 4) # 4 because of metadata row self.ae(test_sprite_position_for(0), (0, 0, 0)) self.ae(test_sprite_position_for(1), (1, 0, 0)) self.ae(test_sprite_position_for(2), (0, 1, 0))