aggressive caching of rendered text

This commit is contained in:
Andrei Alexeyev 2017-12-14 05:48:30 +02:00
parent 8c14aac51c
commit 276759d0fd
No known key found for this signature in database
GPG key ID: 363707CD4C7FE8A4
11 changed files with 235 additions and 65 deletions

View file

@ -27,7 +27,7 @@ Boss* create_boss(char *name, char *ani, char *dialog, complex pos) {
return buf;
}
void draw_boss_text(Alignment align, float x, float y, const char *text, TTF_Font *fnt, Color clr) {
void draw_boss_text(Alignment align, float x, float y, const char *text, Font *fnt, Color clr) {
Color black = rgb(0, 0, 0);
Color white = rgb(1, 1, 1);

View file

@ -10,3 +10,9 @@ static void* objpool_release_list_callback(List **dest, List *elem, void *vpool)
void objpool_release_list(ObjectPool *pool, List **dest) {
list_foreach(dest, objpool_release_list_callback, pool);
}
bool objpool_is_full(ObjectPool *pool) {
ObjectPoolStats stats;
objpool_get_stats(pool, &stats);
return stats.capacity == stats.usage;
}

View file

@ -4,3 +4,4 @@
#include "objectpool.h"
void objpool_release_list(ObjectPool *pool, List **dest);
bool objpool_is_full(ObjectPool *pool);

View file

@ -59,7 +59,7 @@ void player_free(Player *plr) {
static void player_full_power(Player *plr) {
play_sound("full_power");
stage_clear_hazards(false);
stagetext_add("Full Power!", VIEWPORT_W * 0.5 + VIEWPORT_H * 0.33 * I, AL_Center, _fonts.mainmenu, rgb(1, 1, 1), 0, 60, 20, 20);
stagetext_add("Full Power!", VIEWPORT_W * 0.5 + VIEWPORT_H * 0.33 * I, AL_Center, &_fonts.mainmenu, rgb(1, 1, 1), 0, 60, 20, 20);
}
bool player_set_power(Player *plr, short npow) {

View file

@ -9,10 +9,53 @@
#include "font.h"
#include "global.h"
#include "util.h"
#include "objectpool.h"
#include "objectpool_util.h"
#define CACHE_EXPIRE_TIME 1000
#ifdef DEBUG
// #define VERBOSE_CACHE_LOG
#endif
#ifdef VERBOSE_CACHE_LOG
#define CACHELOG(fmt, ...) log_debug(fmt, __VA_ARGS__)
#else
#define CACHELOG(fmt, ...)
#endif
typedef struct CacheEntry {
union {
ObjectInterface object_interface;
struct {
struct CacheEntry *next;
struct CacheEntry *prev;
};
};
SDL_Surface *surf;
int width;
int height;
uint32_t ref_time;
struct {
// to simplify invalidation
Hashtable *ht;
char *ht_key;
} owner;
} CacheEntry;
static ObjectPool *cache_pool;
static CacheEntry *cache_entries;
struct Font {
TTF_Font *ttf;
Hashtable *cache;
};
struct Fonts _fonts;
TTF_Font* load_font(char *vfspath, int size) {
static TTF_Font* load_ttf(char *vfspath, int size) {
char *syspath = vfs_repr(vfspath, true);
SDL_RWops *rwops = vfs_open(vfspath, VFS_MODE_READ | VFS_MODE_SEEKABLE);
@ -36,6 +79,76 @@ TTF_Font* load_font(char *vfspath, int size) {
return f;
}
static Font* load_font(char *vfspath, int size) {
TTF_Font *ttf = load_ttf(vfspath, size);
Font *font = calloc(1, sizeof(Font));
font->ttf = ttf;
font->cache = hashtable_new_stringkeys(2048);
return font;
}
static void free_cache_entry(CacheEntry *e) {
if(!e) {
return;
}
if(e->surf) {
SDL_FreeSurface(e->surf);
}
CACHELOG("Wiping cache entry %p [%s]", (void*)e, e->owner.ht_key);
free(e->owner.ht_key);
list_unlink((List**)&cache_entries, (List*)e);
objpool_release(cache_pool, (ObjectInterface*)e);
}
static CacheEntry* get_cache_entry(Font *font, const char *text) {
CacheEntry *e = hashtable_get_unsafe(font->cache, (void*)text);
if(!e) {
if(objpool_is_full(cache_pool)) {
CacheEntry *oldest = cache_entries;
for(CacheEntry *e = cache_entries->next; e; e = e->next) {
if(e->ref_time < oldest->ref_time) {
oldest = e;
}
}
hashtable_unset_string(oldest->owner.ht, oldest->owner.ht_key);
free_cache_entry(oldest);
}
e = (CacheEntry*)objpool_acquire(cache_pool);
list_push((List**)&cache_entries, (List*)e);
hashtable_set_string(font->cache, text, e);
e->owner.ht = font->cache;
e->owner.ht_key = strdup(text);
CACHELOG("New entry for text: [%s]", text);
}
e->ref_time = SDL_GetTicks();
return e;
}
void update_font_cache(void) {
uint32_t now = SDL_GetTicks();
CacheEntry *next;
for(CacheEntry *e = cache_entries; e; e = next) {
next = e->next;
if(now - e->ref_time > CACHE_EXPIRE_TIME) {
hashtable_unset(e->owner.ht, e->owner.ht_key);
free_cache_entry(e);
}
}
}
void fontrenderer_init(FontRenderer *f, float quality) {
f->quality = quality = sanitize_scale(quality);
@ -96,8 +209,16 @@ void fontrenderer_draw_prerendered(FontRenderer *f, SDL_Surface *surf) {
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
}
SDL_Surface* fontrender_render(FontRenderer *f, const char *text, TTF_Font *font) {
SDL_Surface *surf = TTF_RenderUTF8_Blended(font, text, (SDL_Color){255, 255, 255});
SDL_Surface* fontrender_render(FontRenderer *f, const char *text, Font *font) {
CacheEntry *e = get_cache_entry(font, text);
SDL_Surface *surf = e->surf;
if(surf) {
return surf;
}
CACHELOG("Rendering text: [%s]", text);
surf = e->surf = TTF_RenderUTF8_Blended(font->ttf, text, (SDL_Color){255, 255, 255});
if(!surf) {
log_fatal("TTF_RenderUTF8_Blended() failed: %s", TTF_GetError());
@ -110,15 +231,15 @@ SDL_Surface* fontrender_render(FontRenderer *f, const char *text, TTF_Font *font
return surf;
}
void fontrenderer_draw(FontRenderer *f, const char *text, TTF_Font *font) {
void fontrenderer_draw(FontRenderer *f, const char *text, Font *font) {
SDL_Surface *surf = fontrender_render(f, text, font);
fontrenderer_draw_prerendered(f, surf);
SDL_FreeSurface(surf);
}
void init_fonts(void) {
TTF_Init();
memset(&resources.fontren, 0, sizeof(resources.fontren));
cache_pool = objpool_alloc(sizeof(CacheEntry), 512, "fontcache");
}
void uninit_fonts(void) {
@ -150,11 +271,25 @@ void reload_fonts(float quality) {
}
}
static void free_font(Font *font) {
CacheEntry *e;
TTF_CloseFont(font->ttf);
for(HashtableIterator *i = hashtable_iter(font->cache); hashtable_iter_next(i, 0, (void**)&e);) {
free_cache_entry(e);
}
hashtable_free(font->cache);
free(font);
}
void free_fonts(void) {
fontrenderer_free(&resources.fontren);
TTF_CloseFont(_fonts.standard);
TTF_CloseFont(_fonts.mainmenu);
TTF_CloseFont(_fonts.small);
Font **last = &_fonts.first + (sizeof(_fonts)/sizeof(Font*) - 1);
for(Font **font = &_fonts.first; font <= last; ++font) {
free_font(*font);
}
}
static void draw_text_texture(Alignment align, float x, float y, Texture *tex) {
@ -213,7 +348,7 @@ void draw_text_prerendered(Alignment align, float x, float y, SDL_Surface *surf)
draw_text_texture(align, x, y, &resources.fontren.tex);
}
void draw_text(Alignment align, float x, float y, const char *text, TTF_Font *font) {
void draw_text(Alignment align, float x, float y, const char *text, Font *font) {
assert(text != NULL);
if(!*text) {
@ -234,32 +369,49 @@ void draw_text(Alignment align, float x, float y, const char *text, TTF_Font *fo
free(buf);
}
void draw_text_auto_wrapped(Alignment align, float x, float y, const char *text, int width, TTF_Font *font) {
void draw_text_auto_wrapped(Alignment align, float x, float y, const char *text, int width, Font *font) {
char buf[strlen(text) * 2];
wrap_text(buf, sizeof(buf), text, width, font);
draw_text(align, x, y, buf, font);
}
int stringwidth(char *s, TTF_Font *font) {
static void string_dimensions(char *s, Font *font, int *w, int *h) {
CacheEntry *e = get_cache_entry(font, s);
if(e->width <= 0 || e->height <= 0) {
TTF_SizeUTF8(font->ttf, s, &e->width, &e->height);
CACHELOG("Got size %ix%i for text: [%s]", e->width, e->height, s);
}
if(w) {
*w = e->width;
}
if(h) {
*h = e->height;
}
}
int stringwidth(char *s, Font *font) {
int w;
TTF_SizeUTF8(font, s, &w, NULL);
string_dimensions(s, font, &w, NULL);
return w / resources.fontren.quality;
}
int stringheight(char *s, TTF_Font *font) {
int stringheight(char *s, Font *font) {
int h;
TTF_SizeUTF8(font, s, NULL, &h);
string_dimensions(s, font, NULL, &h);
return h / resources.fontren.quality;
}
int charwidth(char c, TTF_Font *font) {
int charwidth(char c, Font *font) {
char s[2];
s[0] = c;
s[1] = 0;
return stringwidth(s, font);
}
void shorten_text_up_to_width(char *s, float width, TTF_Font *font) {
void shorten_text_up_to_width(char *s, float width, Font *font) {
while(stringwidth(s, font) > width) {
int l = strlen(s);
@ -276,7 +428,7 @@ void shorten_text_up_to_width(char *s, float width, TTF_Font *font) {
}
}
void wrap_text(char *buf, size_t bufsize, const char *src, int width, TTF_Font *font) {
void wrap_text(char *buf, size_t bufsize, const char *src, int width, Font *font) {
assert(buf != NULL);
assert(src != NULL);
assert(font != NULL);

View file

@ -9,7 +9,9 @@
#pragma once
#include <SDL_ttf.h>
#include "texture.h"
#include "hashtable.h"
typedef enum {
AL_Center,
@ -28,45 +30,51 @@ enum {
FONTREN_MAXH = 64, // must be a power of two that is > largest font size
};
typedef struct FontRenderer FontRenderer;
struct FontRenderer {
typedef struct Font Font;
typedef struct FontRenderer {
Texture tex;
GLuint pbo;
float quality;
};
} FontRenderer;
void fontrenderer_init(FontRenderer *f, float quality);
void fontrenderer_free(FontRenderer *f);
void fontrenderer_draw(FontRenderer *f, const char *text, TTF_Font *font);
void fontrenderer_draw(FontRenderer *f, const char *text, Font *font);
void fontrenderer_draw_prerendered(FontRenderer *f, SDL_Surface *surf);
SDL_Surface* fontrender_render(FontRenderer *f, const char *text, TTF_Font *font);
SDL_Surface* fontrender_render(FontRenderer *f, const char *text, Font *font);
Texture *load_text(const char *text, TTF_Font *font);
void draw_text(Alignment align, float x, float y, const char *text, TTF_Font *font);
void draw_text_auto_wrapped(Alignment align, float x, float y, const char *text, int width, TTF_Font *font);
Texture *load_text(const char *text, Font *font);
void draw_text(Alignment align, float x, float y, const char *text, Font *font);
void draw_text_auto_wrapped(Alignment align, float x, float y, const char *text, int width, Font *font);
void draw_text_prerendered(Alignment align, float x, float y, SDL_Surface *surf);
int stringwidth(char *s, TTF_Font *font);
int stringheight(char *s, TTF_Font *font);
int charwidth(char c, TTF_Font *font);
int stringwidth(char *s, Font *font);
int stringheight(char *s, Font *font);
int charwidth(char c, Font *font);
void shorten_text_up_to_width(char *s, float width, TTF_Font *font);
void wrap_text(char *buf, size_t bufsize, const char *src, int width, TTF_Font *font);
void shorten_text_up_to_width(char *s, float width, Font *font);
void wrap_text(char *buf, size_t bufsize, const char *src, int width, Font *font);
void init_fonts(void);
void uninit_fonts(void);
void load_fonts(float quality);
void reload_fonts(float quality);
void free_fonts(void);
void update_font_cache(void);
struct Fonts {
TTF_Font *standard;
TTF_Font *mainmenu;
TTF_Font *small;
TTF_Font *hud;
TTF_Font *mono;
TTF_Font *monosmall;
TTF_Font *monotiny;
};
extern struct Fonts {
union {
struct {
Font *standard;
Font *mainmenu;
Font *small;
Font *hud;
Font *mono;
Font *monosmall;
Font *monotiny;
};
extern struct Fonts _fonts;
Font *first;
};
} _fonts;

View file

@ -506,8 +506,8 @@ static void stage_preload(void) {
}
static void display_stage_title(StageInfo *info) {
stagetext_add(info->title, VIEWPORT_W/2 + I * (VIEWPORT_H/2-40), AL_Center, _fonts.mainmenu, rgb(1, 1, 1), 50, 85, 35, 35);
stagetext_add(info->subtitle, VIEWPORT_W/2 + I * (VIEWPORT_H/2), AL_Center, _fonts.standard, rgb(1, 1, 1), 60, 85, 35, 35);
stagetext_add(info->title, VIEWPORT_W/2 + I * (VIEWPORT_H/2-40), AL_Center, &_fonts.mainmenu, rgb(1, 1, 1), 50, 85, 35, 35);
stagetext_add(info->subtitle, VIEWPORT_W/2 + I * (VIEWPORT_H/2), AL_Center, &_fonts.standard, rgb(1, 1, 1), 60, 85, 35, 35);
}
@ -517,7 +517,7 @@ void stage_start_bgm(const char *bgm) {
if(current_bgm.title && current_bgm.started_at >= 0) {
char txt[strlen(current_bgm.title) + 6];
snprintf(txt, sizeof(txt), "BGM: %s", current_bgm.title);
stagetext_add(txt, VIEWPORT_W-15 + I * (VIEWPORT_H-20), AL_Right, _fonts.standard, rgb(1, 1, 1), 30, 85, 35, 35);
stagetext_add(txt, VIEWPORT_W-15 + I * (VIEWPORT_H-20), AL_Right, &_fonts.standard, rgb(1, 1, 1), 30, 85, 35, 35);
}
}

View file

@ -491,7 +491,7 @@ static void stage_draw_hud_scores(float ypos_hiscore, float ypos_score, char *bu
glUniform1f(stagedraw.hud_text.u_split, 0.0);
}
static void stage_draw_hud_objpool_stats(float x, float y, float width, TTF_Font *font) {
static void stage_draw_hud_objpool_stats(float x, float y, float width, Font *font) {
ObjectPool **last = &stage_object_pools.first + (sizeof(StageObjectPools)/sizeof(ObjectPool*) - 1);
for(ObjectPool **pool = &stage_object_pools.first; pool <= last; ++pool) {

View file

@ -12,9 +12,12 @@
static StageText *textlist = NULL;
StageText* stagetext_add(const char *text, complex pos, Alignment align, TTF_Font *font, Color clr, int delay, int lifetime, int fadeintime, int fadeouttime) {
#define NUM_PLACEHOLDER "........................"
StageText* stagetext_add(const char *text, complex pos, Alignment align, Font **font, Color clr, int delay, int lifetime, int fadeintime, int fadeouttime) {
StageText *t = (StageText*)list_append((List**)&textlist, malloc(sizeof(StageText)));
t->rendered_text = fontrender_render(&resources.fontren, text, font);
t->text = strdup(text);
t->font = font;
t->pos = pos;
t->align = align;
@ -30,22 +33,18 @@ StageText* stagetext_add(const char *text, complex pos, Alignment align, TTF_Fon
}
void stagetext_numeric_predraw(StageText *txt, int t, float a) {
char buf[32];
SDL_FreeSurface(txt->rendered_text);
snprintf(buf, sizeof(buf), "%i", (int)((intptr_t)txt->custom.data1 * pow(a, 5)));
txt->rendered_text = fontrender_render(&resources.fontren, buf, *(TTF_Font**)txt->custom.data2);
snprintf(txt->text, sizeof(NUM_PLACEHOLDER), "%i", (int)((intptr_t)txt->custom.data1 * pow(a, 5)));
}
StageText* stagetext_add_numeric(int n, complex pos, Alignment align, TTF_Font **font, Color clr, int delay, int lifetime, int fadeintime, int fadeouttime) {
StageText *t = stagetext_add("0", pos, align, *font, clr, delay, lifetime, fadeintime, fadeouttime);
StageText* stagetext_add_numeric(int n, complex pos, Alignment align, Font **font, Color clr, int delay, int lifetime, int fadeintime, int fadeouttime) {
StageText *t = stagetext_add(NUM_PLACEHOLDER, pos, align, font, clr, delay, lifetime, fadeintime, fadeouttime);
t->custom.data1 = (void*)(intptr_t)n;
t->custom.data2 = (void*)font;
t->custom.predraw = stagetext_numeric_predraw;
return t;
}
static void* stagetext_delete(List **dest, List *txt, void *arg) {
SDL_FreeSurface(((StageText*)txt)->rendered_text);
free(((StageText*)txt)->text);
free(list_unlink(dest, txt));
return NULL;
}
@ -81,9 +80,9 @@ static void stagetext_draw_single(StageText *txt) {
glActiveTexture(GL_TEXTURE0);
glUniform3f(uniloc(sha, "color"), 0,0,0);
draw_text_prerendered(txt->align, creal(txt->pos)+10*f*f+1, cimag(txt->pos)+10*f*f+1, txt->rendered_text);
draw_text(txt->align, creal(txt->pos)+10*f*f+1, cimag(txt->pos)+10*f*f+1, txt->text, *txt->font);
glUniform3fv(uniloc(sha, "color"), 1, txt->clr);
draw_text_prerendered(txt->align, creal(txt->pos)+10*f*f, cimag(txt->pos)+10*f*f, txt->rendered_text);
draw_text(txt->align, creal(txt->pos)+10*f*f, cimag(txt->pos)+10*f*f, txt->text, *txt->font);
glUseProgram(0);
}
@ -99,7 +98,7 @@ static void stagetext_table_push(StageTextTable *tbl, StageText *txt, bool updat
list_append(&tbl->elems, list_wrap_container(txt));
if(update_pos) {
tbl->pos += txt->rendered_text->h / resources.fontren.quality * I;
tbl->pos += stringheight(txt->text, *txt->font) * I;
}
tbl->delay += 5;
@ -115,7 +114,7 @@ void stagetext_begin_table(StageTextTable *tbl, const char *title, Color titlecl
tbl->fadeouttime = fadeouttime;
tbl->delay = delay;
StageText *txt = stagetext_add(title, tbl->pos, AL_Center, _fonts.mainmenu, titleclr, tbl->delay, lifetime, fadeintime, fadeouttime);
StageText *txt = stagetext_add(title, tbl->pos, AL_Center, &_fonts.mainmenu, titleclr, tbl->delay, lifetime, fadeintime, fadeouttime);
stagetext_table_push(tbl, txt, true);
}
@ -130,13 +129,13 @@ void stagetext_end_table(StageTextTable *tbl) {
}
static void stagetext_table_add_label(StageTextTable *tbl, const char *title) {
StageText *txt = stagetext_add(title, tbl->pos - tbl->width * 0.5, AL_Left, _fonts.standard, tbl->clr, tbl->delay, tbl->lifetime, tbl->fadeintime, tbl->fadeouttime);
StageText *txt = stagetext_add(title, tbl->pos - tbl->width * 0.5, AL_Left, &_fonts.standard, tbl->clr, tbl->delay, tbl->lifetime, tbl->fadeintime, tbl->fadeouttime);
stagetext_table_push(tbl, txt, false);
}
void stagetext_table_add(StageTextTable *tbl, const char *title, const char *val) {
stagetext_table_add_label(tbl, title);
StageText *txt = stagetext_add(val, tbl->pos + tbl->width * 0.5, AL_Right, _fonts.standard, tbl->clr, tbl->delay, tbl->lifetime, tbl->fadeintime, tbl->fadeouttime);
StageText *txt = stagetext_add(val, tbl->pos + tbl->width * 0.5, AL_Right, &_fonts.standard, tbl->clr, tbl->delay, tbl->lifetime, tbl->fadeintime, tbl->fadeouttime);
stagetext_table_push(tbl, txt, true);
}

View file

@ -21,7 +21,9 @@ struct StageText {
StageText *next;
StageText *prev;
SDL_Surface *rendered_text;
char *text;
Font **font;
complex pos;
Alignment align;
float clr[4];
@ -42,8 +44,8 @@ struct StageText {
void stagetext_free(void);
void stagetext_draw(void);
StageText* stagetext_add(const char *text, complex pos, Alignment align, TTF_Font *font, Color clr, int delay, int lifetime, int fadeintime, int fadeouttime);
StageText* stagetext_add_numeric(int n, complex pos, Alignment align, TTF_Font **font, Color clr, int delay, int lifetime, int fadeintime, int fadeouttime);
StageText* stagetext_add(const char *text, complex pos, Alignment align, Font **font, Color clr, int delay, int lifetime, int fadeintime, int fadeouttime);
StageText* stagetext_add_numeric(int n, complex pos, Alignment align, Font **font, Color clr, int delay, int lifetime, int fadeintime, int fadeouttime);
struct StageTextTable {
complex pos;

View file

@ -364,6 +364,8 @@ begin_frame:
return;
}
// update_font_cache();
// fpscounter_update(&global.fps_busy);
#ifdef DEBUG