#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; }