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