mirror of
https://github.com/kovidgoyal/kitty.git
synced 2026-10-20 12:19:36 +00:00
Merge branch 'shaped-run-cache' of https://github.com/j-c-m/kitty
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
commit
fd6008cd99
4 changed files with 468 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) {
|
||||
|
|
|
|||
249
kitty/shaped-run-cache.c
Normal file
249
kitty/shaped-run-cache.c
Normal file
|
|
@ -0,0 +1,249 @@
|
|||
/*
|
||||
* shaped-run-cache.c
|
||||
* Copyright (C) 2026 Kovid Goyal <kovid at kovidgoyal.net>
|
||||
*
|
||||
* Distributed under terms of the GPL3 license.
|
||||
*/
|
||||
|
||||
#include "shaped-run-cache.h"
|
||||
|
||||
#define SHAPED_RUN_MAX_CELLS 32u
|
||||
#define SHAPED_RUN_MAX_ENTRIES 2048u
|
||||
#define SHAPED_RUN_MAX_BYTES (1024u * 1024u)
|
||||
|
||||
typedef struct ShapedRunKey {
|
||||
uint32_t keysz_in_bytes;
|
||||
uint8_t disable_ligature, force_ltr, scale, subscale;
|
||||
uint8_t data[];
|
||||
} ShapedRunKey;
|
||||
static_assert(sizeof(ShapedRunKey) == 8, "Fix the ordering of ShapedRunKey");
|
||||
|
||||
typedef struct ShapedRunValue {
|
||||
unsigned num_glyphs, num_groups;
|
||||
} ShapedRunValue;
|
||||
|
||||
static inline hb_glyph_info_t *
|
||||
val_info(ShapedRunValue *v) {
|
||||
return (hb_glyph_info_t *)(v + 1);
|
||||
}
|
||||
|
||||
static inline hb_glyph_position_t *
|
||||
val_positions(ShapedRunValue *v) {
|
||||
return (hb_glyph_position_t *)(val_info(v) + v->num_glyphs);
|
||||
}
|
||||
|
||||
static inline ShapeGroup *
|
||||
val_groups(ShapedRunValue *v) {
|
||||
return (ShapeGroup *)(val_positions(v) + v->num_glyphs);
|
||||
}
|
||||
|
||||
static inline size_t
|
||||
val_size(unsigned num_glyphs, unsigned num_groups) {
|
||||
return sizeof(ShapedRunValue) + num_glyphs * (sizeof(hb_glyph_info_t) + sizeof(hb_glyph_position_t)) + num_groups * sizeof(ShapeGroup);
|
||||
}
|
||||
|
||||
#define NAME shaped_run_map
|
||||
#define KEY_TY const ShapedRunKey *
|
||||
#define VAL_TY ShapedRunValue *
|
||||
static uint64_t shaped_run_map_hash(KEY_TY key);
|
||||
#define HASH_FN shaped_run_map_hash
|
||||
static bool shaped_run_map_cmpr(KEY_TY a, KEY_TY b);
|
||||
#define CMPR_FN shaped_run_map_cmpr
|
||||
#define MA_NAME Key
|
||||
#define MA_BLOCK_SIZE 16u
|
||||
static_assert(MA_BLOCK_SIZE > sizeof(ShapedRunKey), "increase arena block size");
|
||||
#define MA_ARENA_NUM_BLOCKS (2048u / MA_BLOCK_SIZE)
|
||||
#include "arena.h"
|
||||
#define MA_NAME Val
|
||||
#define MA_BLOCK_SIZE 16u
|
||||
#define MA_ARENA_NUM_BLOCKS (2048u / MA_BLOCK_SIZE)
|
||||
#include "arena.h"
|
||||
|
||||
#include "kitty-verstable.h"
|
||||
|
||||
static uint64_t
|
||||
shaped_run_map_hash(const ShapedRunKey *key) {
|
||||
return vt_hash_bytes(key, sizeof(ShapedRunKey) + key->keysz_in_bytes);
|
||||
}
|
||||
|
||||
static bool
|
||||
shaped_run_map_cmpr(const ShapedRunKey *a, const ShapedRunKey *b) {
|
||||
return a->keysz_in_bytes == b->keysz_in_bytes && memcmp(a, b, sizeof(ShapedRunKey) + a->keysz_in_bytes) == 0;
|
||||
}
|
||||
|
||||
typedef struct HashTable {
|
||||
shaped_run_map table;
|
||||
KeyMonotonicArena keys;
|
||||
ValMonotonicArena vals;
|
||||
struct {
|
||||
ShapedRunKey *key;
|
||||
size_t capacity;
|
||||
} scratch;
|
||||
} HashTable;
|
||||
|
||||
SHAPED_RUN_MAP_HANDLE
|
||||
create_shaped_run_hash_table(void) {
|
||||
HashTable *ans = calloc(1, sizeof(HashTable));
|
||||
if (ans) vt_init(&ans->table);
|
||||
return (SHAPED_RUN_MAP_HANDLE)ans;
|
||||
}
|
||||
|
||||
void
|
||||
free_shaped_run_hash_table(SHAPED_RUN_MAP_HANDLE *map) {
|
||||
HashTable **mapref = (HashTable **)map;
|
||||
if (*mapref) {
|
||||
vt_cleanup(&mapref[0]->table);
|
||||
Key_free_all(&mapref[0]->keys);
|
||||
Val_free_all(&mapref[0]->vals);
|
||||
free(mapref[0]->scratch.key);
|
||||
free(mapref[0]);
|
||||
mapref[0] = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
shaped_run_too_long(index_type num_cells) {
|
||||
return num_cells > SHAPED_RUN_MAX_CELLS;
|
||||
}
|
||||
|
||||
static bool
|
||||
fill_key(
|
||||
HashTable *ht, const CPUCell *cells, index_type num_cells, const TextCache *tc, bool disable_ligature, bool force_ltr, uint8_t scale, uint8_t subscale) {
|
||||
const size_t keysz_in_bytes = (size_t)num_cells * (sizeof(uint32_t) + MAX_NUM_CODEPOINTS_PER_CELL * sizeof(char_type));
|
||||
if (!ht->scratch.key || keysz_in_bytes > ht->scratch.capacity) {
|
||||
const size_t newsz = sizeof(ht->scratch.key[0]) + keysz_in_bytes + 64;
|
||||
ht->scratch.key = realloc(ht->scratch.key, newsz);
|
||||
if (!ht->scratch.key) {
|
||||
ht->scratch.capacity = 0;
|
||||
return false;
|
||||
}
|
||||
ht->scratch.capacity = newsz - sizeof(ht->scratch.key[0]);
|
||||
memset(ht->scratch.key, 0, newsz);
|
||||
}
|
||||
#define scratch ht->scratch.key
|
||||
RAII_ListOfChars(lc);
|
||||
size_t used = 0;
|
||||
scratch->disable_ligature = disable_ligature;
|
||||
scratch->force_ltr = force_ltr;
|
||||
scratch->scale = scale;
|
||||
scratch->subscale = subscale;
|
||||
for (; num_cells; cells++, num_cells--) {
|
||||
if (cells->is_multicell && cells->x) continue;
|
||||
text_in_cell(cells, tc, &lc);
|
||||
uint16_t advance = 1;
|
||||
if (cells->is_multicell) advance = (uint16_t)(cells->width * cells->scale);
|
||||
uint32_t hdr = ((uint32_t)advance << 16) | (uint32_t)lc.count;
|
||||
memcpy(scratch->data + used, &hdr, sizeof(hdr));
|
||||
used += sizeof(hdr);
|
||||
if (lc.count) {
|
||||
memcpy(scratch->data + used, lc.chars, lc.count * sizeof(char_type));
|
||||
used += lc.count * sizeof(char_type);
|
||||
}
|
||||
}
|
||||
scratch->keysz_in_bytes = (uint32_t)used;
|
||||
return true;
|
||||
#undef scratch
|
||||
}
|
||||
|
||||
bool
|
||||
shaped_run_get(
|
||||
SHAPED_RUN_MAP_HANDLE map,
|
||||
const CPUCell *cells,
|
||||
index_type num_cells,
|
||||
const TextCache *tc,
|
||||
bool disable_ligature,
|
||||
bool force_ltr,
|
||||
uint8_t scale,
|
||||
uint8_t subscale,
|
||||
ShapedRun *result) {
|
||||
HashTable *ht = (HashTable *)map;
|
||||
if (!ht) return false;
|
||||
if (shaped_run_too_long(num_cells)) return false;
|
||||
if (!fill_key(ht, cells, num_cells, tc, disable_ligature, force_ltr, scale, subscale)) return false;
|
||||
#define scratch ht->scratch.key
|
||||
shaped_run_map_itr n = vt_get(&ht->table, scratch);
|
||||
if (vt_is_end(n)) return false;
|
||||
ShapedRunValue *v = n.data->val;
|
||||
result->num_glyphs = v->num_glyphs;
|
||||
result->num_groups = v->num_groups;
|
||||
result->info = v->num_glyphs ? val_info(v) : NULL;
|
||||
result->positions = v->num_glyphs ? val_positions(v) : NULL;
|
||||
result->groups = v->num_groups ? val_groups(v) : NULL;
|
||||
return true;
|
||||
#undef scratch
|
||||
}
|
||||
|
||||
static size_t
|
||||
key_arena_alloc(const KeyMonotonicArena *a) {
|
||||
size_t n = a->capacity * sizeof(a->blocks[0]);
|
||||
for (size_t i = 0; i < a->count; i++) n += a->blocks[i].capacity;
|
||||
return n;
|
||||
}
|
||||
|
||||
static size_t
|
||||
val_arena_alloc(const ValMonotonicArena *a) {
|
||||
size_t n = a->capacity * sizeof(a->blocks[0]);
|
||||
for (size_t i = 0; i < a->count; i++) n += a->blocks[i].capacity;
|
||||
return n;
|
||||
}
|
||||
|
||||
static size_t
|
||||
shaped_run_live_bytes(const HashTable *ht) {
|
||||
const size_t buckets = vt_bucket_count(&ht->table);
|
||||
const size_t scratch = ht->scratch.key ? sizeof(ShapedRunKey) + ht->scratch.capacity : 0;
|
||||
return sizeof(HashTable) + scratch + key_arena_alloc(&ht->keys) + val_arena_alloc(&ht->vals) + buckets * (sizeof(shaped_run_map_bucket) + sizeof(uint16_t));
|
||||
}
|
||||
|
||||
static bool
|
||||
shaped_run_at_cap(const HashTable *ht) {
|
||||
if (vt_size(&ht->table) >= SHAPED_RUN_MAX_ENTRIES) return true;
|
||||
if (shaped_run_live_bytes(ht) >= SHAPED_RUN_MAX_BYTES) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static void
|
||||
shaped_run_drop(HashTable *ht) {
|
||||
vt_cleanup(&ht->table);
|
||||
Key_free_all(&ht->keys);
|
||||
Val_free_all(&ht->vals);
|
||||
vt_init(&ht->table);
|
||||
}
|
||||
|
||||
void
|
||||
shaped_run_put(
|
||||
SHAPED_RUN_MAP_HANDLE map,
|
||||
const CPUCell *cells,
|
||||
index_type num_cells,
|
||||
const TextCache *tc,
|
||||
bool disable_ligature,
|
||||
bool force_ltr,
|
||||
uint8_t scale,
|
||||
uint8_t subscale,
|
||||
const hb_glyph_info_t *info,
|
||||
const hb_glyph_position_t *positions,
|
||||
unsigned num_glyphs,
|
||||
const ShapeGroup *groups,
|
||||
unsigned num_groups) {
|
||||
HashTable *ht = (HashTable *)map;
|
||||
if (!ht) return;
|
||||
if (shaped_run_too_long(num_cells)) return;
|
||||
if ((num_glyphs && (!info || !positions)) || (num_groups && !groups)) return;
|
||||
if (!fill_key(ht, cells, num_cells, tc, disable_ligature, force_ltr, scale, subscale)) return;
|
||||
#define scratch ht->scratch.key
|
||||
if (!vt_is_end(vt_get(&ht->table, scratch))) return;
|
||||
if (shaped_run_at_cap(ht)) shaped_run_drop(ht);
|
||||
ShapedRunKey *key = Key_get(&ht->keys, sizeof(ShapedRunKey) + scratch->keysz_in_bytes);
|
||||
if (!key) return;
|
||||
memcpy(key, scratch, sizeof(scratch[0]) + scratch->keysz_in_bytes);
|
||||
ShapedRunValue *val = Val_get(&ht->vals, val_size(num_glyphs, num_groups));
|
||||
if (!val) return;
|
||||
val->num_glyphs = num_glyphs;
|
||||
val->num_groups = num_groups;
|
||||
if (num_glyphs) {
|
||||
memcpy(val_info(val), info, num_glyphs * sizeof(info[0]));
|
||||
memcpy(val_positions(val), positions, num_glyphs * sizeof(positions[0]));
|
||||
}
|
||||
if (num_groups) memcpy(val_groups(val), groups, num_groups * sizeof(groups[0]));
|
||||
if (vt_is_end(vt_insert(&ht->table, key, val))) return;
|
||||
#undef scratch
|
||||
}
|
||||
57
kitty/shaped-run-cache.h
Normal file
57
kitty/shaped-run-cache.h
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* Copyright (C) 2026 Kovid Goyal <kovid at kovidgoyal.net>
|
||||
*
|
||||
* Distributed under terms of the GPL3 license.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "line.h"
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wpedantic"
|
||||
#include <hb.h>
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
typedef struct ShapeGroup {
|
||||
unsigned int first_glyph_idx, first_cell_idx, num_glyphs, num_cells;
|
||||
bool has_special_glyph, started_with_infinite_ligature;
|
||||
} ShapeGroup;
|
||||
|
||||
typedef struct ShapedRun {
|
||||
const hb_glyph_info_t *info;
|
||||
const hb_glyph_position_t *positions;
|
||||
const ShapeGroup *groups;
|
||||
unsigned num_glyphs, num_groups;
|
||||
} ShapedRun;
|
||||
|
||||
typedef struct {
|
||||
int x;
|
||||
} *SHAPED_RUN_MAP_HANDLE;
|
||||
|
||||
SHAPED_RUN_MAP_HANDLE
|
||||
create_shaped_run_hash_table(void);
|
||||
void free_shaped_run_hash_table(SHAPED_RUN_MAP_HANDLE *handle);
|
||||
bool shaped_run_get(
|
||||
SHAPED_RUN_MAP_HANDLE map,
|
||||
const CPUCell *cells,
|
||||
index_type num_cells,
|
||||
const TextCache *tc,
|
||||
bool disable_ligature,
|
||||
bool force_ltr,
|
||||
uint8_t scale,
|
||||
uint8_t subscale,
|
||||
ShapedRun *result);
|
||||
void shaped_run_put(
|
||||
SHAPED_RUN_MAP_HANDLE map,
|
||||
const CPUCell *cells,
|
||||
index_type num_cells,
|
||||
const TextCache *tc,
|
||||
bool disable_ligature,
|
||||
bool force_ltr,
|
||||
uint8_t scale,
|
||||
uint8_t subscale,
|
||||
const hb_glyph_info_t *info,
|
||||
const hb_glyph_position_t *positions,
|
||||
unsigned num_glyphs,
|
||||
const ShapeGroup *groups,
|
||||
unsigned num_groups);
|
||||
Loading…
Add table
Add a link
Reference in a new issue