From b60b799199803c33d8b5ef104b2c3cf80e5f58c8 Mon Sep 17 00:00:00 2001 From: jacob Date: Thu, 31 Jul 2025 00:07:37 -0500 Subject: [PATCH] ttf layer refactor --- src/app/app_core.c | 2 +- src/font/font_core.c | 2 +- src/font/font_core.h | 2 +- src/ttf/ttf.cpp | 4 +- src/ttf/ttf.h | 4 + src/ttf/ttf_core.h | 17 ++- .../{ttf_core_dwrite.cpp => ttf_dwrite.cpp} | 116 +++++++++--------- src/ttf/ttf_dwrite.h | 12 ++ 8 files changed, 85 insertions(+), 74 deletions(-) rename src/ttf/{ttf_core_dwrite.cpp => ttf_dwrite.cpp} (83%) create mode 100644 src/ttf/ttf_dwrite.h diff --git a/src/app/app_core.c b/src/app/app_core.c index 04db0524..f4266fe6 100644 --- a/src/app/app_core.c +++ b/src/app/app_core.c @@ -239,7 +239,7 @@ void P_AppStartup(String args_str) /* Subsystems */ AC_StartupReceipt asset_cache_sr = AC_Startup(); - TTF_StartupReceipt ttf_sr = ttf_startup(); + TTF_StartupReceipt ttf_sr = TTF_Startup(); F_StartupReceipt font_sr = F_Startup(&asset_cache_sr, &ttf_sr); S_StartupReceipt sprite_sr = sprite_startup(); M_StartupReceipt mixer_sr = M_Startup(); diff --git a/src/font/font_core.c b/src/font/font_core.c index 89572cd1..3a1c7f64 100644 --- a/src/font/font_core.c +++ b/src/font/font_core.c @@ -89,7 +89,7 @@ P_JobDef(F_LoadAssetJob, job) Lit("Font \"%F\" not found"), FmtString(path))); } - TTF_Result result = ttf_decode(scratch.arena, R_GetResourceData(&res), point_size, font_codes, countof(font_codes)); + TTF_Result result = TTF_Decode(scratch.arena, R_GetResourceData(&res), point_size, font_codes, countof(font_codes)); R_CloseResource(&res); /* Send texture to GPU */ diff --git a/src/font/font_core.h b/src/font/font_core.h index 1c631e82..532a93b2 100644 --- a/src/font/font_core.h +++ b/src/font/font_core.h @@ -4,9 +4,9 @@ Struct(F_Glyph) { f32 off_x; f32 off_y; - i32 advance; f32 width; f32 height; + i32 advance; Rect atlas_rect; }; diff --git a/src/ttf/ttf.cpp b/src/ttf/ttf.cpp index 17c91f97..cad50f59 100644 --- a/src/ttf/ttf.cpp +++ b/src/ttf/ttf.cpp @@ -4,7 +4,7 @@ extern "C" } #if PlatformIsWindows -# include "ttf_core_dwrite.cpp" +# include "ttf_dwrite.cpp" #else -# error TTF core not implemented for this platform +# error TTF not implemented for this platform #endif diff --git a/src/ttf/ttf.h b/src/ttf/ttf.h index eb453b45..dbee8c4b 100644 --- a/src/ttf/ttf.h +++ b/src/ttf/ttf.h @@ -5,4 +5,8 @@ #include "ttf_core.h" +#if PlatformIsWindows +# include "ttf_dwrite.h" +#endif + #endif diff --git a/src/ttf/ttf_core.h b/src/ttf/ttf_core.h index 9cbd05fe..1e240d8e 100644 --- a/src/ttf/ttf_core.h +++ b/src/ttf/ttf_core.h @@ -1,15 +1,15 @@ -typedef struct TTF_Glyph TTF_Glyph; -struct TTF_Glyph { +Struct(TTF_Glyph) +{ f32 off_x; f32 off_y; - i32 advance; f32 width; f32 height; + i32 advance; Rect atlas_rect; }; -typedef struct TTF_Result TTF_Result; -struct TTF_Result { +Struct(TTF_Result) +{ TTF_Glyph *glyphs; u16 glyphs_count; u16 *cache_indices; /* Array of indices into the `glyphs` array in order of `cache_chars` */ @@ -18,8 +18,7 @@ struct TTF_Result { u32 *image_pixels; /* Array of [width * height] pixels */ }; -typedef struct TTF_StartupReceipt TTF_StartupReceipt; -struct TTF_StartupReceipt { i32 _; }; -TTF_StartupReceipt ttf_startup(void); +Struct(TTF_StartupReceipt) { i32 _; }; +TTF_StartupReceipt TTF_Startup(void); -TTF_Result ttf_decode(Arena *arena, String encoded, f32 point_size, u32 *cache_codes, u32 cache_codes_count); +TTF_Result TTF_Decode(Arena *arena, String encoded, f32 point_size, u32 *cache_codes, u32 cache_codes_count); diff --git a/src/ttf/ttf_core_dwrite.cpp b/src/ttf/ttf_dwrite.cpp similarity index 83% rename from src/ttf/ttf_core_dwrite.cpp rename to src/ttf/ttf_dwrite.cpp index a2836f83..6569f21a 100644 --- a/src/ttf/ttf_core_dwrite.cpp +++ b/src/ttf/ttf_dwrite.cpp @@ -1,6 +1,11 @@ /* Based on Allen Webster's dwrite rasterizer example - * https://github.com/4th-dimention/examps */ +extern TTF_DW_SharedState TTF_DW_shared_state = ZI; + +//////////////////////////////// +//~ Win32 libs + #define WIN32_LEAN_AND_MEAN #define UNICODE #pragma warning(push, 0) @@ -12,38 +17,15 @@ #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 = ZI, DebugAlias(G, G_ttf_dwrite); - -/* ========================== * - * Decode font - * ========================== */ - -internal i32 round_up(f32 x) -{ - i32 r = (i32)x; - if ((f32)r < x) { - r += 1; - } - return r; -} +//////////////////////////////// +//~ Startup /* Call this during font system startup */ -TTF_StartupReceipt ttf_startup(void) +TTF_StartupReceipt TTF_Startup(void) { __prof; - Assert(!G.factory); + TTF_DW_SharedState *g = &TTF_DW_shared_state; + 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 @@ -55,12 +37,13 @@ TTF_StartupReceipt ttf_startup(void) HRESULT error = DWriteCreateFactory( DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory5), - (IUnknown **)&G.factory + (IUnknown **)&g->factory ); #if CompilerIsClang # pragma clang diagnostic pop #endif - if (error != S_OK) { + if (error != S_OK) + { /* FIXME: Enable this */ //P_Panic(Lit("Error creating DWrite factory")); (*(volatile int *)0) = 0; @@ -69,13 +52,17 @@ TTF_StartupReceipt ttf_startup(void) return { 0 }; } -TTF_Result ttf_decode(Arena *arena, String encoded, f32 point_size, u32 *cache_codes, u32 cache_codes_count) +//////////////////////////////// +//~ Decode + +TTF_Result TTF_Decode(Arena *arena, String encoded, f32 point_size, u32 *cache_codes, u32 cache_codes_count) { __prof; - COLORREF bg_color = Rgb32(0,0,0); - COLORREF fg_color = Rgb32(255,255,255); + TTF_DW_SharedState *g = &TTF_DW_shared_state; + COLORREF bg_color = Rgb32(0, 0, 0); + COLORREF fg_color = Rgb32(255, 255, 255); - IDWriteFactory5 *factory = G.factory; + IDWriteFactory5 *factory = g->factory; /* TODO: handle errors */ HRESULT error = 0; @@ -95,11 +82,11 @@ TTF_Result ttf_decode(Arena *arena, String encoded, f32 point_size, u32 *cache_c error = builder->AddFontFile(font_file); } - /* Face */ + //- Create face IDWriteFontFace *font_face = 0; error = factory->CreateFontFace(DWRITE_FONT_FACE_TYPE_TRUETYPE, 1, &font_file, 0, DWRITE_FONT_SIMULATIONS_NONE, &font_face); - /* Render settings */ + //- Setup rendering params IDWriteRenderingParams *default_rendering_params = 0; error = factory->CreateRenderingParams(&default_rendering_params); IDWriteRenderingParams *rendering_params = 0; @@ -113,15 +100,17 @@ TTF_Result ttf_decode(Arena *arena, String encoded, f32 point_size, u32 *cache_c DWRITE_RENDERING_MODE_DEFAULT, &rendering_params); - /* Interop */ + //- Setup interop IDWriteGdiInterop *dwrite_gdi_interop = 0; error = factory->GetGdiInterop(&dwrite_gdi_interop); - /* Get Metrics */ + //- Get metrics DWRITE_FONT_METRICS metrics = ZI; font_face->GetMetrics(&metrics); - f32 pixel_per_em = point_size * (DPI / 72.0f); + u16 glyph_count = font_face->GetGlyphCount(); + + f32 pixel_per_em = point_size * (TTF_DW_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); @@ -133,10 +122,7 @@ TTF_Result ttf_decode(Arena *arena, String encoded, f32 point_size, u32 *cache_c 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 */ + //- Setup render target IDWriteBitmapRenderTarget *render_target = 0; /* FIXME: errors when point_size too high */ error = dwrite_gdi_interop->CreateBitmapRenderTarget(0, (UINT32)raster_target_w, (UINT32)raster_target_h, &render_target); @@ -154,6 +140,7 @@ TTF_Result ttf_decode(Arena *arena, String encoded, f32 point_size, u32 *cache_c SelectObject(dc, original); } + //- Setup atlas /* Allocate font memory */ TTF_Glyph *glyphs = (TTF_Glyph *)PushStructs(arena, TTF_Glyph, glyph_count); @@ -165,14 +152,15 @@ TTF_Result ttf_decode(Arena *arena, String encoded, f32 point_size, u32 *cache_c u64 atlas_h = 1; u32 *atlas_memory = PushStructs(arena, u32, atlas_w * atlas_h); - /* Fill CPU side atlas & metric data */ + //- Fill atlas & metric data u32 out_offset_x = 0; u32 out_offset_y = 0; u32 row_height = 0; { __profn("Build atlas"); - for (u16 i = 0; i < glyph_count; ++i) { - /* Render glyph to target */ + for (u16 i = 0; i < glyph_count; ++i) + { + //- Render glyph to render target DWRITE_GLYPH_RUN glyph_run = ZI; glyph_run.fontFace = font_face; glyph_run.fontEmSize = pixel_per_em; @@ -193,27 +181,28 @@ TTF_Result ttf_decode(Arena *arena, String encoded, f32 point_size, u32 *cache_c if (bounding_box.left < 0 || bounding_box.top < 0 || bounding_box.right > raster_target_w - || bounding_box.bottom > raster_target_h) { + || bounding_box.bottom > raster_target_h) + { /* Skip */ continue; } - /* Compute glyph metrics */ + //- Compute glyph metrics DWRITE_GLYPH_METRICS glyph_metrics = ZI; error = font_face->GetDesignGlyphMetrics(&i, 1, &glyph_metrics, 0); 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; + f32 advance = CeilF32ToI32((f32)glyph_metrics.advanceWidth * pixel_per_design_unit); TTF_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; + glyph->advance = advance; /* Get the bitmap */ HBITMAP bitmap = (HBITMAP)GetCurrentObject(dc, OBJ_BITMAP); @@ -221,14 +210,16 @@ TTF_Result ttf_decode(Arena *arena, String encoded, f32 point_size, u32 *cache_c GetObject(bitmap, sizeof(dib), &dib); /* Start new row if necessary */ - if ((out_offset_x + tex_w) >= atlas_w) { + 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) { + 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) */ @@ -243,14 +234,16 @@ TTF_Result ttf_decode(Arena *arena, String encoded, f32 point_size, u32 *cache_c glyph->atlas_rect.width = (f32)tex_w; glyph->atlas_rect.height = (f32)tex_h; - /* Fill atlas */ + //- Fill atlas u64 in_pitch = (u64)dib.dsBm.bmWidthBytes / 4; u32 *in_data = (u32 *)dib.dsBm.bmBits; u32 *out_data = atlas_memory; - for (i32 y = 0; y < tex_h; ++y) { + for (i32 y = 0; y < tex_h; ++y) + { u64 out_y = out_offset_y + y; u64 in_y = (u64)bounding_box.top + y; - for (i32 x = 0; x < tex_w; ++x) { + for (i32 x = 0; x < tex_w; ++x) + { u64 out_x = out_offset_x + x; u64 in_x = (u64)bounding_box.left + x; u32 *out_pixel = out_data + (out_x + (out_y * atlas_w)); @@ -261,11 +254,12 @@ TTF_Result ttf_decode(Arena *arena, String encoded, f32 point_size, u32 *cache_c out_offset_x += tex_w; /* Grow row height */ - if ((u32)tex_h > row_height) { + if ((u32)tex_h > row_height) + { row_height = (u32)tex_h; } - /* Clear the render target */ + //- Clear render target { HGDIOBJ original = SelectObject(dc, GetStockObject(DC_PEN)); SetDCPenColor(dc, bg_color); @@ -277,14 +271,16 @@ TTF_Result ttf_decode(Arena *arena, String encoded, f32 point_size, u32 *cache_c } } - /* Construct indices array */ + //- Construct indices u16 *cache_indices = 0; - if (cache_codes_count > 0) { + if (cache_codes_count > 0) + { cache_indices = PushStructs(arena, u16, cache_codes_count); font_face->GetGlyphIndices(cache_codes, cache_codes_count, cache_indices); } - /* Release */ + //- Release + /* FIXME: Check for leaks */ dwrite_gdi_interop->Release(); rendering_params->Release(); default_rendering_params->Release(); diff --git a/src/ttf/ttf_dwrite.h b/src/ttf/ttf_dwrite.h new file mode 100644 index 00000000..b56cadf2 --- /dev/null +++ b/src/ttf/ttf_dwrite.h @@ -0,0 +1,12 @@ +//////////////////////////////// +//~ Shared state + +/* TODO: Determine font dpi dynamically */ +#define TTF_DW_Dpi (96.0f) + +Struct(TTF_DW_SharedState) +{ + struct IDWriteFactory5 *factory; +}; + +extern TTF_DW_SharedState TTF_DW_shared_state;