mirror of
https://github.com/kovidgoyal/kitty.git
synced 2026-10-20 12:19:36 +00:00
Cache HarfBuzz text shaping results
Wire the shaper-cache to actually be used for shaped runs. On a 10s loop of the same short-word screen (~16ms/frame), main-thread CPU dropped ~28% vs master and hb_shape samples went to 0. Parse speed remains unchanged (and fast), verified with kitten __benchmark__.
This commit is contained in:
parent
c655a592f9
commit
073957f014
2 changed files with 162 additions and 25 deletions
|
|
@ -39,6 +39,8 @@ should see a 15-35% improvement depending on workload. Some details:
|
|||
|
||||
#. Speed up pixel compositing with :term:`SIMD` vectorization: alpha blending of graphics protocol images and animation frames is 2-3.5x faster and glyph alpha masks are composited onto canvases using the same vectorized primitives.
|
||||
|
||||
#. Cache HarfBuzz results for repeated short runs (≤32 cells) so redraws of the same on-screen text are not reshaped
|
||||
|
||||
|
||||
Vertical tabs [0.48]
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
|
|
|||
185
kitty/fonts.c
185
kitty/fonts.c
|
|
@ -13,6 +13,7 @@
|
|||
#include "char-props.h"
|
||||
#include "decorations.h"
|
||||
#include "glyph-cache.h"
|
||||
#include "shaped-run-cache.h"
|
||||
#include "print-graphics.h"
|
||||
#include "simd-string.h"
|
||||
|
||||
|
|
@ -70,6 +71,7 @@ typedef struct {
|
|||
hb_feature_t *ffs_hb_features;
|
||||
size_t num_ffs_hb_features;
|
||||
GLYPH_PROPERTIES_MAP_HANDLE glyph_properties_hash_table;
|
||||
SHAPED_RUN_MAP_HANDLE shaped_run_hash_table;
|
||||
bool bold, italic, emoji_presentation;
|
||||
SpacerStrategy spacer_strategy;
|
||||
} Font;
|
||||
|
|
@ -237,6 +239,7 @@ void
|
|||
free_maps(Font *font) {
|
||||
free_sprite_position_hash_table(&font->sprite_position_hash_table);
|
||||
free_glyph_properties_hash_table(&font->glyph_properties_hash_table);
|
||||
free_shaped_run_hash_table(&font->shaped_run_hash_table);
|
||||
}
|
||||
|
||||
static void
|
||||
|
|
@ -523,6 +526,11 @@ init_hash_tables(Font *f) {
|
|||
PyErr_NoMemory();
|
||||
return false;
|
||||
}
|
||||
f->shaped_run_hash_table = create_shaped_run_hash_table();
|
||||
if (!f->shaped_run_hash_table) {
|
||||
PyErr_NoMemory();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -1420,10 +1428,7 @@ typedef struct {
|
|||
char_type current_codepoint;
|
||||
} CellData;
|
||||
|
||||
typedef struct {
|
||||
unsigned int first_glyph_idx, first_cell_idx, num_glyphs, num_cells;
|
||||
bool has_special_glyph, started_with_infinite_ligature;
|
||||
} Group;
|
||||
typedef ShapeGroup Group;
|
||||
|
||||
typedef struct {
|
||||
uint32_t previous_cluster;
|
||||
|
|
@ -1433,19 +1438,70 @@ typedef struct {
|
|||
size_t groups_capacity, group_idx, glyph_idx, cell_idx, num_cells, num_glyphs;
|
||||
CPUCell *first_cpu_cell, *last_cpu_cell;
|
||||
GPUCell *first_gpu_cell, *last_gpu_cell;
|
||||
hb_glyph_info_t *info;
|
||||
hb_glyph_position_t *positions;
|
||||
hb_glyph_info_t *info, *info_storage;
|
||||
hb_glyph_position_t *positions, *pos_storage;
|
||||
size_t glyph_capacity;
|
||||
} GroupState;
|
||||
|
||||
static GroupState group_state = {0};
|
||||
|
||||
static bool
|
||||
ensure_group_glyph_storage(size_t n) {
|
||||
if (group_state.glyph_capacity >= n) return true;
|
||||
size_t cap = MAX(n, group_state.glyph_capacity * 2);
|
||||
if (!cap) cap = 64;
|
||||
hb_glyph_info_t *info = realloc(group_state.info_storage, cap * sizeof(*info));
|
||||
if (!info) return false;
|
||||
group_state.info_storage = info;
|
||||
hb_glyph_position_t *pos = realloc(group_state.pos_storage, cap * sizeof(*pos));
|
||||
if (!pos) return false;
|
||||
group_state.pos_storage = pos;
|
||||
group_state.glyph_capacity = cap;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
ensure_group_capacity(size_t num_cells, size_t num_groups) {
|
||||
size_t needed = MAX(num_groups, 2 * num_cells);
|
||||
if (group_state.groups_capacity >= needed && group_state.groups) return true;
|
||||
size_t cap = MAX(128u, needed);
|
||||
Group *groups = realloc(group_state.groups, sizeof(Group) * cap);
|
||||
if (!groups) return false;
|
||||
group_state.groups = groups;
|
||||
group_state.groups_capacity = cap;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
restore_shaped_run(CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, index_type num_cells, const ShapedRun *cached) {
|
||||
if (!ensure_group_capacity(num_cells, cached->num_groups)) return false;
|
||||
zero_at_ptr_count(group_state.groups, group_state.groups_capacity);
|
||||
if (cached->num_groups) memcpy(group_state.groups, cached->groups, cached->num_groups * sizeof(Group));
|
||||
if (cached->num_glyphs) {
|
||||
if (!ensure_group_glyph_storage(cached->num_glyphs)) return false;
|
||||
memcpy(group_state.info_storage, cached->info, cached->num_glyphs * sizeof(cached->info[0]));
|
||||
memcpy(group_state.pos_storage, cached->positions, cached->num_glyphs * sizeof(cached->positions[0]));
|
||||
group_state.info = group_state.info_storage;
|
||||
group_state.positions = group_state.pos_storage;
|
||||
} else {
|
||||
group_state.info = NULL;
|
||||
group_state.positions = NULL;
|
||||
}
|
||||
group_state.num_glyphs = cached->num_glyphs;
|
||||
group_state.num_cells = num_cells;
|
||||
group_state.group_idx = cached->num_groups ? cached->num_groups - 1 : 0;
|
||||
group_state.glyph_idx = 0;
|
||||
group_state.cell_idx = 0;
|
||||
group_state.first_cpu_cell = first_cpu_cell;
|
||||
group_state.first_gpu_cell = first_gpu_cell;
|
||||
group_state.last_cpu_cell = first_cpu_cell + (num_cells ? num_cells - 1 : 0);
|
||||
group_state.last_gpu_cell = first_gpu_cell + (num_cells ? num_cells - 1 : 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
shape(CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, index_type num_cells, hb_font_t *font, Font *fobj, bool disable_ligature, const TextCache *tc) {
|
||||
if (group_state.groups_capacity <= 2 * num_cells) {
|
||||
group_state.groups_capacity = MAX(128u, 2 * num_cells); // avoid unnecessary reallocs
|
||||
group_state.groups = realloc(group_state.groups, sizeof(Group) * group_state.groups_capacity);
|
||||
if (!group_state.groups) fatal("Out of memory");
|
||||
}
|
||||
if (!ensure_group_capacity(num_cells, 0)) fatal("Out of memory");
|
||||
RAII_ListOfChars(lc);
|
||||
text_in_cell(first_cpu_cell, tc, &lc);
|
||||
group_state.previous_cluster = UINT32_MAX;
|
||||
|
|
@ -1821,6 +1877,15 @@ shape_run(
|
|||
const TextCache *tc,
|
||||
ListOfChars *lc) {
|
||||
float scale = apply_scale_to_font_group(fg, &rf);
|
||||
const uint8_t key_scale = (uint8_t)MAX(1u, rf.scale);
|
||||
uint8_t subscale = ((rf.subscale_n & 0xf) << 4) | (rf.subscale_d & 0xf);
|
||||
ShapedRun cached;
|
||||
if (shaped_run_get(font->shaped_run_hash_table, first_cpu_cell, num_cells, tc, disable_ligature, OPT(force_ltr), key_scale, subscale, &cached)) {
|
||||
if (restore_shaped_run(first_cpu_cell, first_gpu_cell, num_cells, &cached)) {
|
||||
if (scale != 1.f) apply_scale_to_font_group(fg, NULL);
|
||||
return scale;
|
||||
}
|
||||
}
|
||||
if (scale != 1.f)
|
||||
if (!face_apply_scaling(font->face, (FONTS_DATA_HANDLE)fg) && PyErr_Occurred()) PyErr_Print();
|
||||
hb_font_t *hbf = harfbuzz_font_for_face(font->face);
|
||||
|
|
@ -1834,6 +1899,20 @@ shape_run(
|
|||
hb_buffer_serialize_glyphs(harfbuzz_buffer, 0, group_state.num_glyphs, dbuf, sizeof(dbuf), NULL, harfbuzz_font_for_face(font->face), HB_BUFFER_SERIALIZE_FORMAT_TEXT, HB_BUFFER_SERIALIZE_FLAG_DEFAULT | HB_BUFFER_SERIALIZE_FLAG_GLYPH_EXTENTS);
|
||||
printf("\n%s\n", dbuf);
|
||||
#endif
|
||||
shaped_run_put(
|
||||
font->shaped_run_hash_table,
|
||||
first_cpu_cell,
|
||||
num_cells,
|
||||
tc,
|
||||
disable_ligature,
|
||||
OPT(force_ltr),
|
||||
key_scale,
|
||||
subscale,
|
||||
G(info),
|
||||
G(positions),
|
||||
(unsigned)G(num_glyphs),
|
||||
G(groups),
|
||||
(unsigned)(G(group_idx) + 1));
|
||||
if (scale != 1.f) {
|
||||
apply_scale_to_font_group(fg, NULL);
|
||||
if (!face_apply_scaling(font->face, (FONTS_DATA_HANDLE)fg) && PyErr_Occurred()) PyErr_Print();
|
||||
|
|
@ -1914,6 +1993,45 @@ render_groups(FontGroup *fg, RunFont rf, bool center_glyph, const TextCache *tc)
|
|||
}
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
shaped_groups_as_python(void) {
|
||||
PyObject *ans = PyList_New(0);
|
||||
if (!ans) return NULL;
|
||||
unsigned int idx = 0;
|
||||
while (idx <= G(group_idx)) {
|
||||
Group *group = G(groups) + idx;
|
||||
if (!group->num_cells) break;
|
||||
glyph_index first_glyph = group->num_glyphs ? G(info)[group->first_glyph_idx].codepoint : 0;
|
||||
PyObject *eg = PyTuple_New(group->num_glyphs);
|
||||
if (!eg) {
|
||||
Py_DECREF(ans);
|
||||
return NULL;
|
||||
}
|
||||
for (size_t g = 0; g < group->num_glyphs; g++) {
|
||||
PyObject *gid = Py_BuildValue("H", G(info)[group->first_glyph_idx + g].codepoint);
|
||||
if (!gid) {
|
||||
Py_DECREF(eg);
|
||||
Py_DECREF(ans);
|
||||
return NULL;
|
||||
}
|
||||
PyTuple_SET_ITEM(eg, g, gid);
|
||||
}
|
||||
PyObject *item = Py_BuildValue("IIHN", group->num_cells, group->num_glyphs, first_glyph, eg);
|
||||
if (!item) {
|
||||
Py_DECREF(ans);
|
||||
return NULL;
|
||||
}
|
||||
if (PyList_Append(ans, item) != 0) {
|
||||
Py_DECREF(item);
|
||||
Py_DECREF(ans);
|
||||
return NULL;
|
||||
}
|
||||
Py_DECREF(item);
|
||||
idx++;
|
||||
}
|
||||
return ans;
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
test_shape(PyObject UNUSED *self, PyObject *args) {
|
||||
Line *line;
|
||||
|
|
@ -1938,28 +2056,38 @@ test_shape(PyObject UNUSED *self, PyObject *args) {
|
|||
face = face_from_path(path, index, (FONTS_DATA_HANDLE)font_groups);
|
||||
if (face == NULL) return NULL;
|
||||
font = calloc(1, sizeof(Font));
|
||||
if (!font) {
|
||||
Py_CLEAR(face);
|
||||
return PyErr_NoMemory();
|
||||
}
|
||||
font->face = face;
|
||||
if (!init_hash_tables(font)) return NULL;
|
||||
if (!init_hash_tables(font)) {
|
||||
Py_CLEAR(face);
|
||||
free_maps(font);
|
||||
free(font);
|
||||
return NULL;
|
||||
}
|
||||
} else {
|
||||
font = fg->fonts + fg->medium_font_idx;
|
||||
}
|
||||
RunFont rf = {0};
|
||||
RAII_ListOfChars(lc);
|
||||
shape_run(line->cpu_cells, line->gpu_cells, num, font, rf, fg, false, line->text_cache, &lc);
|
||||
|
||||
PyObject *ans = PyList_New(0);
|
||||
unsigned int idx = 0;
|
||||
glyph_index first_glyph;
|
||||
while (idx <= G(group_idx)) {
|
||||
Group *group = G(groups) + idx;
|
||||
if (!group->num_cells) break;
|
||||
first_glyph = group->num_glyphs ? G(info)[group->first_glyph_idx].codepoint : 0;
|
||||
|
||||
PyObject *eg = PyTuple_New(group->num_glyphs);
|
||||
for (size_t g = 0; g < group->num_glyphs; g++) PyTuple_SET_ITEM(eg, g, Py_BuildValue("H", G(info)[group->first_glyph_idx + g].codepoint));
|
||||
PyList_Append(ans, Py_BuildValue("IIHN", group->num_cells, group->num_glyphs, first_glyph, eg));
|
||||
idx++;
|
||||
PyObject *ans = shaped_groups_as_python();
|
||||
if (!ans) goto done;
|
||||
shape_run(line->cpu_cells, line->gpu_cells, num, font, rf, fg, false, line->text_cache, &lc);
|
||||
PyObject *cached = shaped_groups_as_python();
|
||||
if (!cached) {
|
||||
Py_CLEAR(ans);
|
||||
goto done;
|
||||
}
|
||||
int same = PyObject_RichCompareBool(ans, cached, Py_EQ);
|
||||
Py_DECREF(cached);
|
||||
if (same != 1) {
|
||||
Py_CLEAR(ans);
|
||||
if (same == 0) PyErr_SetString(PyExc_RuntimeError, "shaped run cache returned a different result than HarfBuzz");
|
||||
}
|
||||
done:
|
||||
if (face) {
|
||||
Py_CLEAR(face);
|
||||
free_maps(font);
|
||||
|
|
@ -2370,8 +2498,15 @@ finalize(void) {
|
|||
harfbuzz_buffer = NULL;
|
||||
}
|
||||
free(group_state.groups);
|
||||
free(group_state.info_storage);
|
||||
free(group_state.pos_storage);
|
||||
group_state.groups = NULL;
|
||||
group_state.info_storage = NULL;
|
||||
group_state.pos_storage = NULL;
|
||||
group_state.info = NULL;
|
||||
group_state.positions = NULL;
|
||||
group_state.groups_capacity = 0;
|
||||
group_state.glyph_capacity = 0;
|
||||
free(global_glyph_render_scratch.glyphs);
|
||||
free(global_glyph_render_scratch.sprite_positions);
|
||||
if (global_glyph_render_scratch.lc) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue