This is an automated email from the git hooks/post-receive script.
git pushed a commit to branch reproducible-widget-previews
in repository efl.
View the commit online.
commit 594fa70e2ba5826262a8ab3008ee0b63ac50a9f4
Author: [email protected] <[email protected]>
AuthorDate: Sat May 2 13:24:13 2026 +0100
evas textgrid: cache text_props per (font_instance, codepoint) pair
Callgrind profiling of expedite textgrid benchmark shows
evas_object_textgrid_row_text_append is the dominant bottleneck,
consuming ~30% of render time (inclusive). The function calls
eng_font_text_props_info_create for every cell every frame, which
runs the full Harfbuzz shaping pipeline (hb_shape_full) on a SINGLE
codepoint.
I see a 2x speedup in expedite textgrid benchmark with opengl_x11
backend. It will improve terminology too, but likely in a less dramatic
way. I just don't have a good benchmark setup for terminology.
This is redundant work: textgrid is a monospace grid where each cell
holds a single codepoint in a fixed font instance. The shaping result
for (font_instance, codepoint) is invariant across frames and cells.
ASCII grids have ~95 unique (fi, codepoint) pairs; even rich-text
terminals remain bounded.
Add a per-textgrid Eina_Hash cache keyed by (Evas_Font_Instance *,
Eina_Unicode). On cache hit, the cached Evas_Text_Props is
content-copied into the row's text entry (independent refcount via
evas_common_text_props_content_copy_and_ref). On miss, the original
shaping path runs and a copy is cached (cache holds its own ref).
Cache invalidation hooks into _evas_textgrid_font_reload (covers font
family, size, bitmap_scalable, scale changes), and the hash is freed
in evas_object_textgrid_free. Hash function uses Murmur3-style 64-bit
mix to retain all pointer bits, avoiding collisions on 64-bit
platforms. Cache key uses designated initializer to zero padding bytes
for deterministic memcpy.
Co-Authored-By: Claude Opus 4.7 (1M context) [email protected]
---
src/lib/evas/canvas/evas_object_textgrid.c | 148 +++++++++++++++++++++++++++--
1 file changed, 142 insertions(+), 6 deletions(-)
diff --git a/src/lib/evas/canvas/evas_object_textgrid.c b/src/lib/evas/canvas/evas_object_textgrid.c
index 5964f8c36c..297f8cb24f 100644
--- a/src/lib/evas/canvas/evas_object_textgrid.c
+++ b/src/lib/evas/canvas/evas_object_textgrid.c
@@ -18,6 +18,14 @@ typedef struct _Evas_Object_Textgrid_Rect Evas_Object_Textgrid_Rect;
typedef struct _Evas_Object_Textgrid_Text Evas_Object_Textgrid_Text;
typedef struct _Evas_Object_Textgrid_Line Evas_Object_Textgrid_Line;
+/* Key for the per-(font_instance, codepoint) text_props cache */
+typedef struct _Evas_Textgrid_Cache_Key Evas_Textgrid_Cache_Key;
+struct _Evas_Textgrid_Cache_Key
+{
+ Evas_Font_Instance *fi;
+ Eina_Unicode codepoint;
+};
+
struct _Evas_Textgrid_Data
{
struct {
@@ -44,6 +52,11 @@ struct _Evas_Textgrid_Data
Evas_Font_Set *font_italic;
Evas_Font_Set *font_bolditalic;
+ /* Cache: (fi, codepoint) -> heap-allocated Evas_Text_Props.
+ * Each cached entry holds one content ref. On cache hit the row's copy
+ * takes its own independent ref via evas_common_text_props_content_copy_and_ref. */
+ Eina_Hash *text_props_cache;
+
unsigned int changed : 1;
unsigned int core_change : 1;
unsigned int row_change : 1;
@@ -131,6 +144,71 @@ static const Evas_Object_Func object_func =
NULL // render_prepare
};
+/* --- text_props cache helpers --- */
+
+static int
+_textgrid_cache_key_length(const Evas_Textgrid_Cache_Key *key EINA_UNUSED)
+{
+ return sizeof(Evas_Textgrid_Cache_Key);
+}
+
+static int
+_textgrid_cache_key_cmp(const Evas_Textgrid_Cache_Key *a,
+ int a_len EINA_UNUSED,
+ const Evas_Textgrid_Cache_Key *b,
+ int b_len EINA_UNUSED)
+{
+ if (a->fi != b->fi)
+ return (a->fi < b->fi) ? -1 : 1;
+ if (a->codepoint != b->codepoint)
+ return (a->codepoint < b->codepoint) ? -1 : 1;
+ return 0;
+}
+
+static int
+_textgrid_cache_key_hash(const Evas_Textgrid_Cache_Key *key,
+ int key_len EINA_UNUSED)
+{
+ /* Murmur3-finalizer-style 64-bit mix: retains all pointer bits before
+ * truncating to int, avoiding collisions on 64-bit where upper 32 bits
+ * of the fi pointer differ between font instances. */
+ uint64_t h = (uint64_t)(uintptr_t)key->fi;
+ h ^= h >> 33;
+ h *= UINT64_C(0xff51afd7ed558ccd);
+ h ^= h >> 33;
+ h ^= (uint64_t)key->codepoint * 2654435761u;
+ h ^= h >> 17;
+ return (int)(unsigned int)h;
+}
+
+static void
+_textgrid_cache_entry_free(void *data)
+{
+ Evas_Text_Props *props = data;
+
+ evas_common_text_props_content_unref(props);
+ free(props);
+}
+
+static Eina_Hash *
+_textgrid_cache_new(void)
+{
+ return eina_hash_new(EINA_KEY_LENGTH(_textgrid_cache_key_length),
+ EINA_KEY_CMP(_textgrid_cache_key_cmp),
+ EINA_KEY_HASH(_textgrid_cache_key_hash),
+ _textgrid_cache_entry_free,
+ 5);
+}
+
+static void
+_textgrid_cache_invalidate(Evas_Textgrid_Data *o)
+{
+ if (o->text_props_cache)
+ eina_hash_free_buckets(o->text_props_cache);
+}
+
+/* --- end text_props cache helpers --- */
+
/* all nice and private */
static void
evas_object_textgrid_init(Evas_Object *eo_obj)
@@ -146,6 +224,7 @@ evas_object_textgrid_init(Evas_Object *eo_obj)
o->prev = o->cur;
eina_array_step_set(&o->cur.palette_standard, sizeof (Eina_Array), 16);
eina_array_step_set(&o->cur.palette_extended, sizeof (Eina_Array), 16);
+ o->text_props_cache = _textgrid_cache_new();
}
/* Reset a row for reuse without freeing its backing arrays.
@@ -262,6 +341,12 @@ evas_object_textgrid_free(Evas_Object *eo_obj, Evas_Object_Protected_Data *obj E
while ((c = eina_array_pop(&o->cur.palette_extended)))
free(c);
eina_array_flush(&o->cur.palette_extended);
+
+ if (o->text_props_cache)
+ {
+ eina_hash_free(o->text_props_cache);
+ o->text_props_cache = NULL;
+ }
}
EOLIAN static void
@@ -375,12 +460,61 @@ evas_object_textgrid_row_text_append(Evas_Object_Textgrid_Row *row,
font = _textgrid_font_get(o, is_bold, is_italic);
ENFN->font_run_end_get(ENC, font, &script_fi, &cur_fi,
script, &codepoint, 1);
- memset(&(text->text_props), 0, sizeof(Evas_Text_Props));
- evas_common_text_props_script_set(&(text->text_props), script);
- ENFN->font_text_props_info_create(ENC, cur_fi, &codepoint,
- &(text->text_props), NULL, 0, 1,
- EVAS_TEXT_PROPS_MODE_NONE,
- o->cur.font_description_normal->lang);
+
+ /* --- text_props cache lookup --- */
+ if (o->text_props_cache && cur_fi)
+ {
+ /* Designated initializer zeroes all padding bytes so the full struct
+ * (including any trailing pad) is deterministic for eina_hash_add.
+ * This cache is main-loop-only; no locking is required. */
+ Evas_Textgrid_Cache_Key cache_key = { .fi = cur_fi, .codepoint = codepoint };
+ Evas_Text_Props *cached;
+
+ cached = eina_hash_find(o->text_props_cache, &cache_key);
+ if (cached)
+ {
+ /* Cache hit: copy the cached props and take an independent ref */
+ evas_common_text_props_content_copy_and_ref(&(text->text_props),
+ cached);
+ }
+ else
+ {
+ /* Cache miss: shape, then store a copy in the cache */
+ memset(&(text->text_props), 0, sizeof(Evas_Text_Props));
+ evas_common_text_props_script_set(&(text->text_props), script);
+ ENFN->font_text_props_info_create(ENC, cur_fi, &codepoint,
+ &(text->text_props), NULL, 0, 1,
+ EVAS_TEXT_PROPS_MODE_NONE,
+ o->cur.font_description_normal->lang);
+
+ if (text->text_props.info)
+ {
+ Evas_Text_Props *store = malloc(sizeof(Evas_Text_Props));
+ if (store)
+ {
+ /* The cache entry gets its own independent ref */
+ evas_common_text_props_content_copy_and_ref(store,
+ &(text->text_props));
+ if (!eina_hash_add(o->text_props_cache, &cache_key, store))
+ {
+ evas_common_text_props_content_unref(store);
+ free(store);
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ /* No cache (e.g. null fi) - fall back to original path */
+ memset(&(text->text_props), 0, sizeof(Evas_Text_Props));
+ evas_common_text_props_script_set(&(text->text_props), script);
+ ENFN->font_text_props_info_create(ENC, cur_fi, &codepoint,
+ &(text->text_props), NULL, 0, 1,
+ EVAS_TEXT_PROPS_MODE_NONE,
+ o->cur.font_description_normal->lang);
+ }
+ /* --- end cache lookup --- */
text->x = x;
text->r = r;
@@ -1300,6 +1434,8 @@ _evas_textgrid_font_reload(Eo *eo_obj, Evas_Textgrid_Data *o)
evas_object_inform_call_resize(eo_obj, obj);
o->changed = 1;
o->core_change = 1;
+ /* Font instances changed: cached text_props are now invalid */
+ _textgrid_cache_invalidate(o);
evas_object_textgrid_rows_reset(eo_obj);
evas_object_change(eo_obj, obj);
}
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.