From fb635a6ca4694eb35131ab165ce2519d02b9f2b4 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Sun, 26 Jan 2025 20:55:02 -0800
Subject: [PATCH] Added a cache for glyph positions
---
src/SDL_ttf.c | 101 +++++++++++++++++++++++++++++++++++++++++---------
1 file changed, 83 insertions(+), 18 deletions(-)
diff --git a/src/SDL_ttf.c b/src/SDL_ttf.c
index c4d5386b..efa9e4c2 100644
--- a/src/SDL_ttf.c
+++ b/src/SDL_ttf.c
@@ -235,6 +235,12 @@ typedef struct GlyphPositions {
int maxlen;
} GlyphPositions;
+typedef struct CachedGlyphPositions {
+ char *text;
+ size_t length;
+ GlyphPositions positions;
+} CachedGlyphPositions;
+
// A structure maintaining a list of fonts
typedef struct TTF_FontList {
TTF_Font *font;
@@ -295,7 +301,9 @@ struct TTF_Font {
/* Internal buffer to store positions computed by TTF_Size_Internal()
* for rendered string by Render_Line() */
- GlyphPositions positions;
+ int next_cached_positions;
+ CachedGlyphPositions cached_positions[8];
+ GlyphPositions *positions;
int num_clusters;
// Hinting modes
@@ -1191,8 +1199,8 @@ static bool Render_Line_##NAME(TTF_Font *font, SDL_Surface *textbuf, int xstart,
const int bpp = ((IS_BLENDED || IS_LCD) ? 4 : 1); \
int i; \
Uint8 fg_alpha = (fg ? fg->a : 0); \
- for (i = 0; i < font->positions.len; i++) { \
- GlyphPosition *pos = &font->positions.pos[i]; \
+ for (i = 0; i < font->positions->len; i++) { \
+ GlyphPosition *pos = &font->positions->pos[i]; \
TTF_Font *glyph_font = pos->font; \
FT_UInt idx = pos->index; \
int x = pos->x; \
@@ -1455,8 +1463,8 @@ static bool Render_Line_TextEngine(TTF_Font *font, int xstart, int ystart, int w
bounds.w = 0;
bounds.h = font->height;
- for (i = 0; i < font->positions.len; i++) {
- GlyphPosition *pos = &font->positions.pos[i];
+ for (i = 0; i < font->positions->len; i++) {
+ GlyphPosition *pos = &font->positions->pos[i];
TTF_Font *glyph_font = pos->font;
FT_UInt idx = pos->index;
int x = pos->x;
@@ -2356,6 +2364,21 @@ static void Flush_Cache(TTF_Font *font)
}
}
+ for (unsigned int i = 0; i < SDL_arraysize(font->cached_positions); ++i) {
+ CachedGlyphPositions *cached = &font->cached_positions[i];
+ if (cached->text) {
+ SDL_free(cached->text);
+ cached->text = NULL;
+ cached->length = 0;
+ }
+ if (cached->positions.pos) {
+ SDL_free(cached->positions.pos);
+ cached->positions.pos = NULL;
+ cached->positions.maxlen = 0;
+ }
+ }
+ font->positions = NULL;
+
++font->generation;
if (font->generation == 0) {
++font->generation;
@@ -3140,7 +3163,7 @@ bool TTF_GetGlyphKerning(TTF_Font *font, Uint32 previous_ch, Uint32 ch, int *ker
return true;
}
-static bool TTF_CollectGlyphsFromFont(TTF_Font *font, const char *text, size_t length, GlyphPositions *positions)
+static bool CollectGlyphsFromFont(TTF_Font *font, const char *text, size_t length, GlyphPositions *positions)
{
#if TTF_USE_HARFBUZZ
// Create a buffer for harfbuzz to use
@@ -3317,7 +3340,7 @@ static bool ReplaceGlyphPositions(GlyphPositions *positions, int start, int leng
return true;
}
-static bool TTF_CollectGlyphsWithFallbacks(TTF_Font *font, const char *text, size_t length, GlyphPositions *positions, TTF_Font *initial_font)
+static bool CollectGlyphsWithFallbacks(TTF_Font *font, const char *text, size_t length, GlyphPositions *positions, TTF_Font *initial_font)
{
if (!initial_font) {
initial_font = font;
@@ -3326,7 +3349,7 @@ static bool TTF_CollectGlyphsWithFallbacks(TTF_Font *font, const char *text, siz
return true;
}
- if (!TTF_CollectGlyphsFromFont(font, text, length, positions)) {
+ if (!CollectGlyphsFromFont(font, text, length, positions)) {
return false;
}
@@ -3349,7 +3372,7 @@ static bool TTF_CollectGlyphsWithFallbacks(TTF_Font *font, const char *text, siz
SDL_zero(span);
int span_offset = positions->pos[start].offset;
int span_length = pos->offset - span_offset;
- TTF_CollectGlyphsWithFallbacks(fallback->font, text + span_offset, span_length, &span, initial_font);
+ CollectGlyphsWithFallbacks(fallback->font, text + span_offset, span_length, &span, initial_font);
if (span.len > 0) {
ReplaceGlyphPositions(positions, start, (i - start), &span);
SDL_free(span.pos);
@@ -3364,7 +3387,7 @@ static bool TTF_CollectGlyphsWithFallbacks(TTF_Font *font, const char *text, siz
SDL_zero(span);
int span_offset = positions->pos[start].offset;
int span_length = (int)(length - span_offset);
- TTF_CollectGlyphsWithFallbacks(fallback->font, text + span_offset, span_length, &span, initial_font);
+ CollectGlyphsWithFallbacks(fallback->font, text + span_offset, span_length, &span, initial_font);
if (span.len > 0) {
ReplaceGlyphPositions(positions, start, (positions->len - start), &span);
SDL_free(span.pos);
@@ -3376,9 +3399,9 @@ static bool TTF_CollectGlyphsWithFallbacks(TTF_Font *font, const char *text, siz
return true;
}
-static bool TTF_CollectGlyphs(TTF_Font *font, const char *text, size_t length, GlyphPositions *positions)
+static bool CollectGlyphs(TTF_Font *font, const char *text, size_t length, GlyphPositions *positions)
{
- if (!TTF_CollectGlyphsWithFallbacks(font, text, length, positions, NULL)) {
+ if (!CollectGlyphsWithFallbacks(font, text, length, positions, NULL)) {
return false;
}
@@ -3392,6 +3415,51 @@ static bool TTF_CollectGlyphs(TTF_Font *font, const char *text, size_t length, G
return true;
}
+static GlyphPositions *GetCachedGlyphPositions(TTF_Font *font, const char *text, size_t length)
+{
+ CachedGlyphPositions *cached;
+
+ font->positions = NULL;
+ for (unsigned int i = 0; i < SDL_arraysize(font->cached_positions); ++i) {
+ cached = &font->cached_positions[i];
+ if (length == cached->length &&
+ SDL_memcmp(text, cached->text, length) == 0) {
+#ifdef DEBUG_TTF_CACHE
+ SDL_Log("Found cached positions for '%s'\n", cached->text);
+#endif
+ font->positions = &cached->positions;
+ break;
+ }
+ }
+
+ if (!font->positions) {
+ // We could do something fancy like an LRU cache, but it's probably not worth the complexity
+ cached = &font->cached_positions[font->next_cached_positions];
+ font->next_cached_positions = (font->next_cached_positions + 1) % SDL_arraysize(font->cached_positions);
+ font->positions = &cached->positions;
+
+ SDL_free(cached->text);
+ cached->text = (char *)SDL_malloc(length + 1);
+ if (cached->text) {
+ SDL_memcpy(cached->text, text, length);
+ cached->text[length] = '\0';
+ cached->length = length;
+ } else {
+ cached->length = 0;
+ return NULL;
+ }
+
+ if (!CollectGlyphs(font, text, length, font->positions)) {
+ cached->length = 0;
+ return NULL;
+ }
+#ifdef DEBUG_TTF_CACHE
+ SDL_Log("Added cached positions for '%s'\n", cached->text);
+#endif
+ }
+ return font->positions;
+}
+
static bool TTF_Size_Internal(TTF_Font *font, const char *text, size_t length, int *w, int *h, int *xstart, int *ystart, bool measure_width, int max_width, int *measured_width, size_t *measured_length)
{
int x = 0, y = 0;
@@ -3425,8 +3493,8 @@ static bool TTF_Size_Internal(TTF_Font *font, const char *text, size_t length, i
// Reset buffer
font->num_clusters = 0;
- GlyphPositions *positions = &font->positions;
- if (!TTF_CollectGlyphs(font, text, length, positions)) {
+ GlyphPositions *positions = GetCachedGlyphPositions(font, text, length);
+ if (!positions) {
return false;
}
@@ -4240,7 +4308,7 @@ static bool LayoutText(TTF_Text *text)
}
// Allocate space for the operations on this line
- additional_ops = (font->positions.len + extra_ops);
+ additional_ops = (font->positions->len + extra_ops);
new_ops = (TTF_DrawOperation *)SDL_realloc(ops, (max_ops + additional_ops) * sizeof(*new_ops));
if (!new_ops) {
goto done;
@@ -5749,9 +5817,6 @@ void TTF_CloseFont(TTF_Font *font)
if (font->closeio) {
SDL_CloseIO(font->src);
}
- if (font->positions.pos) {
- SDL_free(font->positions.pos);
- }
SDL_free(font);
}