remove font test bits, just use 256 characters for codepoint caching
This commit is contained in:
parent
77fd98baaa
commit
62493e8e69
@ -1,8 +1,5 @@
|
||||
/* Project-wide configurable constants */
|
||||
|
||||
/* TODO: Remove this */
|
||||
#define FONT_TEST 0
|
||||
|
||||
#define WRITE_DIR "power_play"
|
||||
#define SETTINGS_FILENAME "settings.json"
|
||||
|
||||
|
||||
59
src/draw.c
59
src/draw.c
@ -270,64 +270,6 @@ void draw_text(struct renderer_canvas *canvas, struct font *font, struct v2 pos,
|
||||
|
||||
void draw_text_ex(struct renderer_canvas *canvas, struct font *font, struct v2 pos, f32 scale, struct string str)
|
||||
{
|
||||
#if FONT_TEST
|
||||
|
||||
struct temp_arena scratch = scratch_begin_no_conflict();
|
||||
|
||||
renderer_canvas_ensure_texture_cmd(canvas, (struct texture_shader_parameters) { .texture = font->texture.renderer_handle });
|
||||
struct string32 str32 = string32_from_string(scratch.arena, str);
|
||||
|
||||
struct v2 draw_pos = pos;
|
||||
draw_pos.y += font->point_size * scale;
|
||||
|
||||
struct font_glyphs_list glyphs = font_get_glyphs(font, str32);
|
||||
for (struct font_glyph *glyph = glyphs->first; glyph; glyph = glyph->next) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
struct string_codepoint_iter iter = string_codepoint_iter_begin(str);
|
||||
while (string_codepoint_iter_next(&iter)) {
|
||||
u32 codepoint = iter.codepoint;
|
||||
|
||||
/* TODO: Remove this (placeholder \n) */
|
||||
if (codepoint == '\n') {
|
||||
draw_pos.x = pos.x;
|
||||
draw_pos.y += (font->point_size * 1.5f) * scale;
|
||||
continue;
|
||||
}
|
||||
|
||||
struct font_glyph *glyph = font_get_glyph(font, codepoint);
|
||||
f32 x = draw_pos.x + glyph->off_x * scale;
|
||||
f32 y = draw_pos.y + glyph->off_y * scale;
|
||||
f32 width = glyph->width * scale;
|
||||
f32 height = glyph->height * scale;
|
||||
|
||||
struct clip_rect clip = {
|
||||
{
|
||||
glyph->atlas_rect.x / font->texture.size.x,
|
||||
glyph->atlas_rect.y / font->texture.size.y
|
||||
},
|
||||
|
||||
{
|
||||
(glyph->atlas_rect.x + glyph->atlas_rect.width) / font->texture.size.x,
|
||||
(glyph->atlas_rect.y + glyph->atlas_rect.height) / font->texture.size.y
|
||||
}
|
||||
};
|
||||
|
||||
struct quad quad = quad_from_rect(RECT(x, y, width, height));
|
||||
draw_texture_quad_internal(canvas, clip, 0xFFFFFFFF, quad);
|
||||
|
||||
draw_pos.x += glyph->advance * scale;
|
||||
}
|
||||
string_codepoint_iter_end(&iter);
|
||||
|
||||
#else
|
||||
renderer_canvas_ensure_texture_cmd(canvas, (struct texture_shader_parameters) { .texture = font->texture.renderer_handle });
|
||||
|
||||
struct v2 draw_pos = pos;
|
||||
@ -368,5 +310,4 @@ void draw_text_ex(struct renderer_canvas *canvas, struct font *font, struct v2 p
|
||||
draw_pos.x += glyph->advance * scale;
|
||||
}
|
||||
string_codepoint_iter_end(&iter);
|
||||
#endif
|
||||
}
|
||||
|
||||
285
src/font.c
285
src/font.c
@ -1,5 +1,3 @@
|
||||
#if FONT_TEST
|
||||
|
||||
#include "font.h"
|
||||
#include "arena.h"
|
||||
#include "ttf.h"
|
||||
@ -11,280 +9,11 @@
|
||||
#include "string.h"
|
||||
#include "renderer.h"
|
||||
|
||||
#if FONT_TEST
|
||||
|
||||
#define LOOKUP_TABLE_SIZE (ARRAY_COUNT(g_font_codes) * 2)
|
||||
GLOBAL u32 g_font_codes[(1 << 20)] = { 0 };
|
||||
|
||||
#else
|
||||
|
||||
#define LOOKUP_TABLE_SIZE (256)
|
||||
GLOBAL u32 g_font_codes[] = {
|
||||
0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2A,0x2B,0x2C,0x2D,0x2E,0x2F,0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3A,0x3B,0x3C,0x3D,0x3E,0x3F,0x40,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4A,0x4B,0x4C,0x4D,0x4E,0x4F,0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5A,0x5B,0x5C,0x5D,0x5E,0x5F,0x60,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6A,0x6B,0x6C,0x6D,0x6E,0x6F,0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7A,0x7B,0x7C,0x7D,0x7E,0x7F,0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F,0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F,0xA0,0xA1,0xA2,0xA3,0xA4,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF,0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF,0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF,0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF,0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
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;
|
||||
} G = { 0 }, DEBUG_ALIAS(G, G_font);
|
||||
|
||||
/* ========================== *
|
||||
* Startup
|
||||
* ========================== */
|
||||
|
||||
struct font_startup_receipt font_startup(struct work_startup_receipt *work_sr,
|
||||
struct renderer_startup_receipt *renderer_sr,
|
||||
struct asset_cache_startup_receipt *asset_cache_sr,
|
||||
struct ttf_startup_receipt *ttf_sr,
|
||||
struct resource_startup_receipt *resource_sr)
|
||||
{
|
||||
(UNUSED)work_sr;
|
||||
(UNUSED)renderer_sr;
|
||||
(UNUSED)asset_cache_sr;
|
||||
(UNUSED)ttf_sr;
|
||||
(UNUSED)resource_sr;
|
||||
|
||||
G.params.arena = arena_alloc(GIGABYTE(64));
|
||||
G.params.mutex = sys_mutex_alloc();
|
||||
|
||||
#if FONT_TEST
|
||||
for (u64 i = 0; i < ARRAY_COUNT(g_font_codes); ++i) {
|
||||
g_font_codes[i] = i;
|
||||
}
|
||||
#endif
|
||||
|
||||
return (struct font_startup_receipt) { 0 };
|
||||
}
|
||||
|
||||
/* ========================== *
|
||||
* Load task param store
|
||||
* ========================== */
|
||||
|
||||
INTERNAL struct font_task_params *font_task_params_alloc(void)
|
||||
{
|
||||
struct font_task_params *p = NULL;
|
||||
sys_mutex_lock(&G.params.mutex);
|
||||
{
|
||||
if (G.params.head_free) {
|
||||
p = G.params.head_free;
|
||||
G.params.head_free = p->next_free;
|
||||
} else {
|
||||
p = arena_push_zero(&G.params.arena, struct font_task_params);
|
||||
}
|
||||
}
|
||||
sys_mutex_unlock(&G.params.mutex);
|
||||
return p;
|
||||
}
|
||||
|
||||
INTERNAL void font_task_params_release(struct font_task_params *p)
|
||||
{
|
||||
sys_mutex_lock(&G.params.mutex);
|
||||
{
|
||||
p->next_free = G.params.head_free;
|
||||
G.params.head_free = p;
|
||||
}
|
||||
sys_mutex_unlock(&G.params.mutex);
|
||||
}
|
||||
|
||||
/* ========================== *
|
||||
* Load
|
||||
* ========================== */
|
||||
|
||||
INTERNAL WORK_TASK_FUNC_DEF(font_load_asset_task, 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)) {
|
||||
/* FIME: Load baked font instead of panicking */
|
||||
sys_panic(string_format(scratch.arena,
|
||||
STR("Font \"%F\" not found"),
|
||||
FMT_STR(path)));
|
||||
}
|
||||
|
||||
ASSERT(ARRAY_COUNT(g_font_codes) < LOOKUP_TABLE_SIZE);
|
||||
|
||||
/* Decode */
|
||||
struct resource res = resource_open(path);
|
||||
struct ttf_decode_result result = ttf_decode(scratch.arena, res.bytes, point_size, g_font_codes, ARRAY_COUNT(g_font_codes));
|
||||
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,
|
||||
.size = V2(result.image_data.width, result.image_data.height),
|
||||
};
|
||||
font->glyphs_count = result.glyphs_count;
|
||||
font->point_size = point_size;
|
||||
|
||||
/* FIXME: Load baked font instead of panicking */
|
||||
if (font->glyphs_count <= 0) {
|
||||
sys_panic(string_format(scratch.arena,
|
||||
STR("Parsed 0 glyphs from font \"%F\"!"),
|
||||
FMT_STR(path)));
|
||||
}
|
||||
|
||||
/* Copy glyphs from decode result */
|
||||
MEMCPY(font->glyphs, result.glyphs, sizeof(*font->glyphs) * result.glyphs_count);
|
||||
|
||||
/* Build lookup table */
|
||||
for (u64 i = 0; i < ARRAY_COUNT(g_font_codes); ++i) {
|
||||
u32 codepoint = g_font_codes[i];
|
||||
font->lookup[codepoint] = 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)));
|
||||
}
|
||||
cstr_buff_from_string(BUFFER_FROM_ARRAY(params->path_cstr), path);
|
||||
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, u32 codepoint)
|
||||
{
|
||||
if (codepoint < LOOKUP_TABLE_SIZE) {
|
||||
u16 index = font->lookup[codepoint];
|
||||
if (index < font->glyphs_count) {
|
||||
return &font->glyphs[index];
|
||||
}
|
||||
}
|
||||
return &font->glyphs[0];
|
||||
}
|
||||
|
||||
|
||||
#else
|
||||
|
||||
#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"
|
||||
#include "renderer.h"
|
||||
|
||||
#if FONT_TEST
|
||||
|
||||
#define LOOKUP_TABLE_SIZE (ARRAY_COUNT(g_font_codes) * 2)
|
||||
GLOBAL u32 g_font_codes[(1 << 20)] = { 0 };
|
||||
|
||||
#else
|
||||
|
||||
#define LOOKUP_TABLE_SIZE (256)
|
||||
GLOBAL u32 g_font_codes[] = {
|
||||
0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2A,0x2B,0x2C,0x2D,0x2E,0x2F,0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3A,0x3B,0x3C,0x3D,0x3E,0x3F,0x40,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4A,0x4B,0x4C,0x4D,0x4E,0x4F,0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5A,0x5B,0x5C,0x5D,0x5E,0x5F,0x60,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6A,0x6B,0x6C,0x6D,0x6E,0x6F,0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7A,0x7B,0x7C,0x7D,0x7E,0x7F,0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F,0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F,0xA0,0xA1,0xA2,0xA3,0xA4,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF,0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF,0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF,0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF,0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
struct font_task_params {
|
||||
struct font_task_params *next_free;
|
||||
|
||||
@ -327,12 +56,6 @@ struct font_startup_receipt font_startup(struct work_startup_receipt *work_sr,
|
||||
G.params.arena = arena_alloc(GIGABYTE(64));
|
||||
G.params.mutex = sys_mutex_alloc();
|
||||
|
||||
#if FONT_TEST
|
||||
for (u64 i = 0; i < ARRAY_COUNT(g_font_codes); ++i) {
|
||||
g_font_codes[i] = i;
|
||||
}
|
||||
#endif
|
||||
|
||||
return (struct font_startup_receipt) { 0 };
|
||||
}
|
||||
|
||||
@ -514,7 +237,9 @@ struct font_glyph *font_get_glyph(struct font *font, u32 codepoint)
|
||||
return &font->glyphs[index];
|
||||
}
|
||||
}
|
||||
return &font->glyphs[0];
|
||||
if (codepoint == '?') {
|
||||
return &font->glyphs[font->lookup[0]];
|
||||
} else {
|
||||
return &font->glyphs[font->lookup['?']];
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
59
src/font.h
59
src/font.h
@ -1,60 +1,3 @@
|
||||
#if FONT_TEST
|
||||
|
||||
#ifndef FONT_H
|
||||
#define FONT_H
|
||||
|
||||
#include "texture.h"
|
||||
#include "util.h"
|
||||
|
||||
|
||||
struct asset;
|
||||
struct work_startup_receipt;
|
||||
struct renderer_startup_receipt;
|
||||
struct asset_cache_startup_receipt;
|
||||
struct ttf_startup_receipt;
|
||||
struct resource_startup_receipt;
|
||||
|
||||
struct font_glyph_list {
|
||||
struct font_glyph *first;
|
||||
struct font_glyph *last;
|
||||
};
|
||||
|
||||
struct font_glyph {
|
||||
f32 off_x;
|
||||
f32 off_y;
|
||||
i32 advance;
|
||||
f32 width;
|
||||
f32 height;
|
||||
struct rect atlas_rect;
|
||||
|
||||
struct font_glyph *next;
|
||||
};
|
||||
|
||||
struct font {
|
||||
f32 point_size;
|
||||
struct texture texture;
|
||||
u16 glyphs_count;
|
||||
struct font_glyph *glyphs;
|
||||
u16 *lookup;
|
||||
};
|
||||
|
||||
struct font_startup_receipt { i32 _; };
|
||||
struct font_startup_receipt font_startup(struct work_startup_receipt *work_sr,
|
||||
struct renderer_startup_receipt *renderer_sr,
|
||||
struct asset_cache_startup_receipt *asset_cache_sr,
|
||||
struct ttf_startup_receipt *ttf_sr,
|
||||
struct resource_startup_receipt *resource_sr);
|
||||
|
||||
struct asset *font_load_asset(struct string path, f32 point_size, b32 help);
|
||||
struct font *font_load_async(struct string path, f32 point_size);
|
||||
struct font *font_load(struct string path, f32 point_size);
|
||||
|
||||
struct font_glyph *font_get_glyph(struct font *font, u32 codepoint);
|
||||
|
||||
#endif
|
||||
|
||||
#else
|
||||
|
||||
#ifndef FONT_H
|
||||
#define FONT_H
|
||||
|
||||
@ -100,5 +43,3 @@ struct font *font_load(struct string path, f32 point_size);
|
||||
struct font_glyph *font_get_glyph(struct font *font, u32 codepoint);
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
@ -1,308 +1,3 @@
|
||||
#if FONT_TEST
|
||||
/* Based on Allen Webster's dwrite rasterizer example -
|
||||
* https://github.com/4th-dimention/examps */
|
||||
|
||||
extern "C"
|
||||
{
|
||||
#include "ttf.h"
|
||||
#include "scratch.h"
|
||||
#include "util.h"
|
||||
#include "string.h"
|
||||
#include "memory.h"
|
||||
#include "arena.h"
|
||||
#include "font.h"
|
||||
}
|
||||
|
||||
#include <dwrite.h>
|
||||
#include <dwrite_3.h>
|
||||
|
||||
#pragma comment(lib, "dwrite")
|
||||
#pragma comment(lib, "gdi32")
|
||||
|
||||
/* TODO: Determine DPI accurately? */
|
||||
#define DPI (96.0f)
|
||||
|
||||
#define CPPSTR(cstr_lit) { (sizeof((cstr_lit)) - 1), (u8 *)(cstr_lit) }
|
||||
|
||||
/* ========================== *
|
||||
* Global state
|
||||
* ========================== */
|
||||
|
||||
GLOBAL struct {
|
||||
/* FIXME: Do we need to wrap this in a mutex? */
|
||||
IDWriteFactory5 *factory;
|
||||
} G = { 0 }, DEBUG_ALIAS(G, G_ttf_dwrite);
|
||||
|
||||
/* ========================== *
|
||||
* Decode font
|
||||
* ========================== */
|
||||
|
||||
INTERNAL i32 round_up(f32 x)
|
||||
{
|
||||
i32 r = (i32)x;
|
||||
if ((f32)r < x) {
|
||||
r += 1;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
/* Call this during font system startup */
|
||||
struct ttf_startup_receipt ttf_startup(void)
|
||||
{
|
||||
ASSERT(!G.factory);
|
||||
/* FIXME: I think IDWriteFactory5 only exists on later updates of windows
|
||||
* 10? Need to verify. Maybe should just use a custom loader. (We're only
|
||||
* using a factory5 since I think WriteInMemoryFileLoader wasn't
|
||||
* implemented until then) */
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wlanguage-extension-token" /* for __uuidof */
|
||||
HRESULT error = DWriteCreateFactory(
|
||||
DWRITE_FACTORY_TYPE_SHARED,
|
||||
__uuidof(IDWriteFactory5),
|
||||
(IUnknown **)&G.factory
|
||||
);
|
||||
#pragma clang diagnostic pop
|
||||
if (error) {
|
||||
sys_panic(STR("Error creating DWrite factory"));
|
||||
}
|
||||
|
||||
return (struct ttf_startup_receipt) { 0 };
|
||||
}
|
||||
|
||||
struct ttf_decode_result ttf_decode(struct arena *arena, struct buffer encoded, f32 point_size, u32 *cache_codes, u32 cache_codes_count)
|
||||
{
|
||||
COLORREF bg_color = RGB(0,0,0);
|
||||
COLORREF fg_color = RGB(255,255,255);
|
||||
|
||||
IDWriteFactory5 *factory = G.factory;
|
||||
|
||||
/* TODO: handle errors */
|
||||
HRESULT error = 0;
|
||||
(UNUSED)error;
|
||||
|
||||
/* File */
|
||||
IDWriteFontFile *font_file = NULL;
|
||||
{
|
||||
/* Create in memory loader */
|
||||
IDWriteInMemoryFontFileLoader *loader = NULL;
|
||||
error = factory->CreateInMemoryFontFileLoader(&loader);
|
||||
error = factory->RegisterFontFileLoader(loader);
|
||||
|
||||
IDWriteFontSetBuilder1 *builder = NULL;
|
||||
error = factory->CreateFontSetBuilder(&builder);
|
||||
error = loader->CreateInMemoryFontFileReference(factory, encoded.data, (u32)encoded.size, NULL, &font_file);
|
||||
error = builder->AddFontFile(font_file);
|
||||
}
|
||||
|
||||
/* Face */
|
||||
IDWriteFontFace *font_face = NULL;
|
||||
error = factory->CreateFontFace(DWRITE_FONT_FACE_TYPE_TRUETYPE, 1, &font_file, 0, DWRITE_FONT_SIMULATIONS_NONE, &font_face);
|
||||
|
||||
/* Render settings */
|
||||
IDWriteRenderingParams *default_rendering_params = NULL;
|
||||
error = factory->CreateRenderingParams(&default_rendering_params);
|
||||
IDWriteRenderingParams *rendering_params = NULL;
|
||||
FLOAT gamma = default_rendering_params->GetGamma();
|
||||
FLOAT enhanced_contrast = default_rendering_params->GetEnhancedContrast();
|
||||
FLOAT clear_type_level = default_rendering_params->GetClearTypeLevel();
|
||||
error = factory->CreateCustomRenderingParams(gamma,
|
||||
enhanced_contrast,
|
||||
clear_type_level,
|
||||
DWRITE_PIXEL_GEOMETRY_FLAT,
|
||||
DWRITE_RENDERING_MODE_DEFAULT,
|
||||
&rendering_params);
|
||||
|
||||
/* Interop */
|
||||
IDWriteGdiInterop *dwrite_gdi_interop = NULL;
|
||||
error = factory->GetGdiInterop(&dwrite_gdi_interop);
|
||||
|
||||
/* Get Metrics */
|
||||
DWRITE_FONT_METRICS metrics = { 0 };
|
||||
font_face->GetMetrics(&metrics);
|
||||
|
||||
f32 pixel_per_em = point_size * (DPI / 72.0f);
|
||||
f32 pixel_per_design_unit = pixel_per_em / ((f32)metrics.designUnitsPerEm);
|
||||
|
||||
i32 raster_target_w = (i32)(8.0f * ((f32)metrics.capHeight) * pixel_per_design_unit);
|
||||
i32 raster_target_h = raster_target_w;
|
||||
|
||||
f32 raster_target_x = (f32)(raster_target_w / 2);
|
||||
f32 raster_target_y = raster_target_x;
|
||||
|
||||
ASSERT((f32)((i32)raster_target_x) == raster_target_x);
|
||||
ASSERT((f32)((i32)raster_target_y) == raster_target_y);
|
||||
|
||||
/* Glyph count */
|
||||
u16 glyph_count = font_face->GetGlyphCount();
|
||||
|
||||
/* Render target */
|
||||
IDWriteBitmapRenderTarget *render_target = NULL;
|
||||
/* FIXME: errors when point_size too high */
|
||||
error = dwrite_gdi_interop->CreateBitmapRenderTarget(0, raster_target_w, raster_target_h, &render_target);
|
||||
render_target->SetPixelsPerDip(1.0);
|
||||
|
||||
/* Clear the render target */
|
||||
HDC dc = 0;
|
||||
{
|
||||
dc = render_target->GetMemoryDC();
|
||||
HGDIOBJ original = SelectObject(dc, GetStockObject(DC_PEN));
|
||||
SetDCPenColor(dc, bg_color);
|
||||
SelectObject(dc, GetStockObject(DC_BRUSH));
|
||||
SetDCBrushColor(dc, bg_color);
|
||||
Rectangle(dc, 0, 0, raster_target_w, raster_target_h);
|
||||
SelectObject(dc, original);
|
||||
}
|
||||
|
||||
/* Allocate font memory */
|
||||
struct font_glyph *glyphs = (struct font_glyph *)arena_push_array_zero(arena, struct font_glyph, glyph_count);
|
||||
|
||||
/* Allocate (starting) atlas memory
|
||||
* NOTE: This is unecessary since atlas memory will grow anyway. Could
|
||||
* just start w/ atlas height 0.
|
||||
*/
|
||||
u64 atlas_w = 1024;
|
||||
u64 atlas_h = 1;
|
||||
u32 *atlas_memory = arena_push_array_zero(arena, u32, atlas_w * atlas_h);
|
||||
|
||||
/* Fill CPU side atlas & metric data */
|
||||
u32 out_offset_x = 0;
|
||||
u32 out_offset_y = 0;
|
||||
u32 row_height = 0;
|
||||
for (u16 i = 0; i < glyph_count; ++i) {
|
||||
/* Render glyph to target */
|
||||
DWRITE_GLYPH_RUN glyph_run = { 0 };
|
||||
glyph_run.fontFace = font_face;
|
||||
glyph_run.fontEmSize = pixel_per_em;
|
||||
glyph_run.glyphCount = 1;
|
||||
glyph_run.glyphIndices = &i;
|
||||
|
||||
RECT bounding_box = { 0 };
|
||||
error = render_target->DrawGlyphRun(
|
||||
raster_target_x,
|
||||
raster_target_y,
|
||||
DWRITE_MEASURING_MODE_NATURAL,
|
||||
&glyph_run,
|
||||
rendering_params,
|
||||
fg_color,
|
||||
&bounding_box
|
||||
);
|
||||
|
||||
if (bounding_box.left < 0
|
||||
|| bounding_box.top < 0
|
||||
|| bounding_box.right > raster_target_w
|
||||
|| bounding_box.bottom > raster_target_h) {
|
||||
/* Skip */
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Compute glyph metrics */
|
||||
DWRITE_GLYPH_METRICS glyph_metrics = { 0 };
|
||||
error = font_face->GetDesignGlyphMetrics(&i, 1, &glyph_metrics, false);
|
||||
|
||||
f32 off_x = (f32)bounding_box.left - raster_target_x;
|
||||
f32 off_y = (f32)bounding_box.top - raster_target_y;
|
||||
f32 advance = (f32)glyph_metrics.advanceWidth * pixel_per_design_unit;
|
||||
i32 tex_w = bounding_box.right - bounding_box.left;
|
||||
i32 tex_h = bounding_box.bottom - bounding_box.top;
|
||||
|
||||
struct font_glyph *glyph = &glyphs[i];
|
||||
glyph->off_x = off_x;
|
||||
glyph->off_y = off_y;
|
||||
glyph->advance = round_up(advance);
|
||||
glyph->width = (f32)tex_w;
|
||||
glyph->height = (f32)tex_h;
|
||||
|
||||
/* Get the bitmap */
|
||||
HBITMAP bitmap = (HBITMAP)GetCurrentObject(dc, OBJ_BITMAP);
|
||||
DIBSECTION dib = { 0 };
|
||||
GetObject(bitmap, sizeof(dib), &dib);
|
||||
|
||||
/* Start new row if necessary */
|
||||
if ((out_offset_x + tex_w) >= atlas_w) {
|
||||
out_offset_y += row_height;
|
||||
out_offset_x = 0;
|
||||
row_height = 0;
|
||||
}
|
||||
|
||||
/* Grow atlas height */
|
||||
if ((out_offset_y + tex_h) > atlas_h) {
|
||||
u64 diff = (out_offset_y + tex_h) - atlas_h;
|
||||
/* NOTE: This allocation must be contiguous with the initial atlas
|
||||
* allocation (IE: No non-atlas arena PUSHes) */
|
||||
arena_push_array_zero(arena, u32, diff * atlas_w);
|
||||
atlas_h += diff;
|
||||
}
|
||||
|
||||
/* Set bounding box metrics (now that we know atlas x & y) */
|
||||
glyph->atlas_rect = { 0 };
|
||||
glyph->atlas_rect.x = (f32)out_offset_x;
|
||||
glyph->atlas_rect.y = (f32)out_offset_y;
|
||||
glyph->atlas_rect.width = (f32)tex_w;
|
||||
glyph->atlas_rect.height = (f32)tex_h;
|
||||
|
||||
/* Fill atlas */
|
||||
u64 in_pitch = dib.dsBm.bmWidthBytes / 4;
|
||||
u32 *in_data = (u32 *)dib.dsBm.bmBits;
|
||||
u32 *out_data = atlas_memory;
|
||||
for (i32 y = 0; y < tex_h; ++y) {
|
||||
u64 out_y = out_offset_y + y;
|
||||
u64 in_y = bounding_box.top + y;
|
||||
for (i32 x = 0; x < tex_w; ++x) {
|
||||
u64 out_x = out_offset_x + x;
|
||||
u64 in_x = bounding_box.left + x;
|
||||
u32 *out_pixel = out_data + (out_x + (out_y * atlas_w));
|
||||
u32 *in_pixel = in_data + (in_x + (in_y * in_pitch));
|
||||
*out_pixel = RGBA(0xFF, 0xFF, 0xFF, *in_pixel & 0xFF);
|
||||
}
|
||||
}
|
||||
out_offset_x += tex_w;
|
||||
|
||||
/* Grow row height */
|
||||
if ((u32)tex_h > row_height) {
|
||||
row_height = tex_h;
|
||||
}
|
||||
|
||||
/* Clear the render target */
|
||||
{
|
||||
HGDIOBJ original = SelectObject(dc, GetStockObject(DC_PEN));
|
||||
SetDCPenColor(dc, bg_color);
|
||||
SelectObject(dc, GetStockObject(DC_BRUSH));
|
||||
SetDCBrushColor(dc, bg_color);
|
||||
Rectangle(dc, bounding_box.left, bounding_box.top, bounding_box.right, bounding_box.bottom);
|
||||
SelectObject(dc, original);
|
||||
}
|
||||
}
|
||||
|
||||
/* Construct indices array */
|
||||
u16 *cache_indices = NULL;
|
||||
if (cache_codes_count > 0) {
|
||||
cache_indices = arena_push_array_zero(arena, u16, cache_codes_count);
|
||||
font_face->GetGlyphIndices(cache_codes, cache_codes_count, cache_indices);
|
||||
}
|
||||
|
||||
/* Release */
|
||||
dwrite_gdi_interop->Release();
|
||||
rendering_params->Release();
|
||||
default_rendering_params->Release();
|
||||
// NOTE FROM ALLEN: We don't release font face because we intend to keep the font face around after the baking process
|
||||
// font_face->Release();
|
||||
font_file->Release();
|
||||
//loader->Release();
|
||||
factory->Release();
|
||||
|
||||
/* Return */
|
||||
struct ttf_decode_result result = { 0 };
|
||||
result.glyphs = glyphs;
|
||||
result.glyphs_count = glyph_count;
|
||||
result.cache_indices = cache_indices;
|
||||
result.image_data.pixels = (u32 *)atlas_memory;
|
||||
result.image_data.width = (u32)atlas_w;
|
||||
result.image_data.height = (u32)atlas_h;
|
||||
return result;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
/* Based on Allen Webster's dwrite rasterizer example -
|
||||
* https://github.com/4th-dimention/examps */
|
||||
|
||||
@ -604,5 +299,3 @@ struct ttf_decode_result ttf_decode(struct arena *arena, struct buffer encoded,
|
||||
result.image_data.height = (u32)atlas_h;
|
||||
return result;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
Loading…
Reference in New Issue
Block a user