power_play/src/font.c

220 lines
6.6 KiB
C

#include "font.h"
#include "arena.h"
#include "ttf.h"
#include "work.h"
#include "scratch.h"
#include "asset_cache.h"
#include "resource.h"
#include "log.h"
#include "string.h"
#define FONT_CHARS " !\"#$%&\'()-.,*/0123456789:;<=+>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"
#define LOOKUP_TABLE_SIZE (256)
struct font_task_params {
struct font_task_params *next_free;
struct asset *asset;
f32 point_size;
u64 path_len;
char path_cstr[1024];
};
struct font_task_params_store {
struct font_task_params *head_free;
struct arena arena;
struct sys_mutex mutex;
};
/* ========================== *
* Global state
* ========================== */
GLOBAL struct {
struct font_task_params_store params;
} L = { 0 }, DEBUG_LVAR(L_font);
/* ========================== *
* Startup
* ========================== */
void font_startup(void)
{
L.params.arena = arena_alloc(GIGABYTE(64));
L.params.mutex = sys_mutex_alloc();
}
/* ========================== *
* Load task param store
* ========================== */
INTERNAL struct font_task_params *font_task_params_alloc(void)
{
struct font_task_params *p = NULL;
{
sys_mutex_lock(&L.params.mutex);
if (L.params.head_free) {
p = L.params.head_free;
L.params.head_free = p->next_free;
} else {
p = arena_push_zero(&L.params.arena, struct font_task_params);
}
sys_mutex_unlock(&L.params.mutex);
}
return p;
}
INTERNAL void font_task_params_release(struct font_task_params *p)
{
sys_mutex_lock(&L.params.mutex);
p->next_free = L.params.head_free;
L.params.head_free = p;
sys_mutex_unlock(&L.params.mutex);
}
/* ========================== *
* Load
* ========================== */
INTERNAL void font_load_asset_task(void *vparams)
{
__prof;
struct font_task_params *params = (struct font_task_params *)vparams;
struct temp_arena scratch = scratch_begin_no_conflict();
struct string path = string_from_cstr_len(params->path_cstr, params->path_len);
f32 point_size = params->point_size;
struct asset *asset = params->asset;
logf_info("Loading font \"%F\" (point size %F)", FMT_STR(path), FMT_FLOAT((f64)point_size));
sys_timestamp_t start_ts = sys_timestamp();
ASSERT(string_ends_with(path, STR(".ttf")));
if (!resource_exists(path)) {
/* TODO: Load baked font instead of panicking */
sys_panic(string_format(scratch.arena,
STR("Font \"%F\" not found"),
FMT_STR(path)));
}
struct string font_chars = STR(FONT_CHARS);
/* Decode */
struct resource res = resource_open(path);
struct ttf_decode_result result = ttf_decode(scratch.arena, res.bytes, point_size, font_chars);
resource_close(res);
/* Send texture to GPU */
struct renderer_handle texture_renderer_handle = renderer_texture_alloc(result.image_data);
/* Allocate store memory */
struct font *font = NULL;
{
struct asset_cache_store store = asset_cache_store_open();
font = arena_push_zero(store.arena, struct font);
font->glyphs = arena_push_array(store.arena, struct font_glyph, result.glyphs_count);
font->lookup = arena_push_array_zero(store.arena, u16, LOOKUP_TABLE_SIZE);
asset_cache_store_close(&store);
}
/* Set font data */
font->texture = (struct texture) {
.renderer_handle = texture_renderer_handle,
.width = result.image_data.width,
.height = result.image_data.height
};
font->glyphs_count = result.glyphs_count;
font->point_size = point_size;
/* Copy glyphs from decode result */
MEMCPY(font->glyphs, result.glyphs, sizeof(*font->glyphs) * result.glyphs_count);
/* Build lookup table */
for (u64 i = 0; i < font_chars.len; ++i) {
u8 c = font_chars.text[i];
font->lookup[c] = result.cache_indices[i];
}
font_task_params_release(params);
logf_info("Finished loading font \"%F\" (point size %F) in %F seconds", FMT_STR(path), FMT_FLOAT((f64)point_size), FMT_FLOAT(sys_timestamp_seconds(sys_timestamp() - start_ts)));
asset_cache_mark_ready(asset, font);
scratch_end_and_decommit(scratch);
}
/* Returns the asset from the asset cache */
struct asset *font_load_asset(struct string path, f32 point_size, b32 help)
{
__prof;
struct temp_arena scratch = scratch_begin_no_conflict();
/* Concatenate point_size to path for key */
struct string key = string_format(scratch.arena,
STR("%F%F_font"),
FMT_STR(path),
FMT_FLOAT_P((f64)point_size, 3));
u64 hash = asset_cache_hash(key);
b32 is_first_touch;
struct asset *asset = asset_cache_touch(key, hash, &is_first_touch);
if (is_first_touch) {
/* Assemble task params */
struct font_task_params *params = font_task_params_alloc();
if (path.len > (sizeof(params->path_cstr) - 1)) {
sys_panic(string_format(scratch.arena,
STR("Font path \"%F\" too long!"),
FMT_STR(path)));
}
string_to_cstr_buff(path, BUFFER_FROM_ARRAY(params->path_cstr));
params->path_len = path.len;
params->asset = asset;
params->point_size = point_size;
/* Push task */
asset_cache_mark_loading(asset);
struct work_handle wh = { 0 };
if (help) {
wh = work_push_task_and_help(&font_load_asset_task, params, WORK_PRIORITY_NORMAL);
} else {
wh = work_push_task(&font_load_asset_task, params, WORK_PRIORITY_NORMAL);
}
asset_cache_set_work(asset, &wh);
}
scratch_end(scratch);
return asset;
}
struct font *font_load_async(struct string path, f32 point_size)
{
__prof;
struct asset *asset = font_load_asset(path, point_size, false);
struct font *f = (struct font *)asset_cache_get_store_data(asset);
return f;
}
struct font *font_load(struct string path, f32 point_size)
{
__prof;
struct asset *asset = font_load_asset(path, point_size, true);
asset_cache_wait(asset);
struct font *f = (struct font *)asset_cache_get_store_data(asset);
return f;
}
/* ========================== *
* Other
* ========================== */
struct font_glyph *font_get_glyph(struct font *font, u8 c)
{
struct font_glyph *g;
u16 index = font->lookup[c];
if (index < LOOKUP_TABLE_SIZE) {
g = &font->glyphs[index];
} else {
g = &font->glyphs[0];
}
return g;
}