ttf layer refactor

This commit is contained in:
jacob 2025-07-31 00:07:37 -05:00
parent adcff577ce
commit b60b799199
8 changed files with 85 additions and 74 deletions

View File

@ -239,7 +239,7 @@ void P_AppStartup(String args_str)
/* Subsystems */ /* Subsystems */
AC_StartupReceipt asset_cache_sr = AC_Startup(); 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); F_StartupReceipt font_sr = F_Startup(&asset_cache_sr, &ttf_sr);
S_StartupReceipt sprite_sr = sprite_startup(); S_StartupReceipt sprite_sr = sprite_startup();
M_StartupReceipt mixer_sr = M_Startup(); M_StartupReceipt mixer_sr = M_Startup();

View File

@ -89,7 +89,7 @@ P_JobDef(F_LoadAssetJob, job)
Lit("Font \"%F\" not found"), Lit("Font \"%F\" not found"),
FmtString(path))); 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); R_CloseResource(&res);
/* Send texture to GPU */ /* Send texture to GPU */

View File

@ -4,9 +4,9 @@
Struct(F_Glyph) { Struct(F_Glyph) {
f32 off_x; f32 off_x;
f32 off_y; f32 off_y;
i32 advance;
f32 width; f32 width;
f32 height; f32 height;
i32 advance;
Rect atlas_rect; Rect atlas_rect;
}; };

View File

@ -4,7 +4,7 @@ extern "C"
} }
#if PlatformIsWindows #if PlatformIsWindows
# include "ttf_core_dwrite.cpp" # include "ttf_dwrite.cpp"
#else #else
# error TTF core not implemented for this platform # error TTF not implemented for this platform
#endif #endif

View File

@ -5,4 +5,8 @@
#include "ttf_core.h" #include "ttf_core.h"
#if PlatformIsWindows
# include "ttf_dwrite.h"
#endif
#endif #endif

View File

@ -1,15 +1,15 @@
typedef struct TTF_Glyph TTF_Glyph; Struct(TTF_Glyph)
struct TTF_Glyph { {
f32 off_x; f32 off_x;
f32 off_y; f32 off_y;
i32 advance;
f32 width; f32 width;
f32 height; f32 height;
i32 advance;
Rect atlas_rect; Rect atlas_rect;
}; };
typedef struct TTF_Result TTF_Result; Struct(TTF_Result)
struct TTF_Result { {
TTF_Glyph *glyphs; TTF_Glyph *glyphs;
u16 glyphs_count; u16 glyphs_count;
u16 *cache_indices; /* Array of indices into the `glyphs` array in order of `cache_chars` */ 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 */ u32 *image_pixels; /* Array of [width * height] pixels */
}; };
typedef struct TTF_StartupReceipt TTF_StartupReceipt; Struct(TTF_StartupReceipt) { i32 _; };
struct TTF_StartupReceipt { i32 _; }; TTF_StartupReceipt TTF_Startup(void);
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);

View File

@ -1,6 +1,11 @@
/* Based on Allen Webster's dwrite rasterizer example - /* Based on Allen Webster's dwrite rasterizer example -
* https://github.com/4th-dimention/examps */ * https://github.com/4th-dimention/examps */
extern TTF_DW_SharedState TTF_DW_shared_state = ZI;
////////////////////////////////
//~ Win32 libs
#define WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN
#define UNICODE #define UNICODE
#pragma warning(push, 0) #pragma warning(push, 0)
@ -12,38 +17,15 @@
#pragma comment(lib, "dwrite") #pragma comment(lib, "dwrite")
#pragma comment(lib, "gdi32") #pragma comment(lib, "gdi32")
/* TODO: Determine DPI accurately? */ ////////////////////////////////
#define DPI (96.0f) //~ Startup
#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;
}
/* Call this during font system startup */ /* Call this during font system startup */
TTF_StartupReceipt ttf_startup(void) TTF_StartupReceipt TTF_Startup(void)
{ {
__prof; __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 /* 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 * 10? Need to verify. Maybe should just use a custom loader. (We're only
* using a factory5 since I think WriteInMemoryFileLoader wasn't * using a factory5 since I think WriteInMemoryFileLoader wasn't
@ -55,12 +37,13 @@ TTF_StartupReceipt ttf_startup(void)
HRESULT error = DWriteCreateFactory( HRESULT error = DWriteCreateFactory(
DWRITE_FACTORY_TYPE_SHARED, DWRITE_FACTORY_TYPE_SHARED,
__uuidof(IDWriteFactory5), __uuidof(IDWriteFactory5),
(IUnknown **)&G.factory (IUnknown **)&g->factory
); );
#if CompilerIsClang #if CompilerIsClang
# pragma clang diagnostic pop # pragma clang diagnostic pop
#endif #endif
if (error != S_OK) { if (error != S_OK)
{
/* FIXME: Enable this */ /* FIXME: Enable this */
//P_Panic(Lit("Error creating DWrite factory")); //P_Panic(Lit("Error creating DWrite factory"));
(*(volatile int *)0) = 0; (*(volatile int *)0) = 0;
@ -69,13 +52,17 @@ TTF_StartupReceipt ttf_startup(void)
return { 0 }; 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; __prof;
TTF_DW_SharedState *g = &TTF_DW_shared_state;
COLORREF bg_color = Rgb32(0, 0, 0); COLORREF bg_color = Rgb32(0, 0, 0);
COLORREF fg_color = Rgb32(255, 255, 255); COLORREF fg_color = Rgb32(255, 255, 255);
IDWriteFactory5 *factory = G.factory; IDWriteFactory5 *factory = g->factory;
/* TODO: handle errors */ /* TODO: handle errors */
HRESULT error = 0; 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); error = builder->AddFontFile(font_file);
} }
/* Face */ //- Create face
IDWriteFontFace *font_face = 0; IDWriteFontFace *font_face = 0;
error = factory->CreateFontFace(DWRITE_FONT_FACE_TYPE_TRUETYPE, 1, &font_file, 0, DWRITE_FONT_SIMULATIONS_NONE, &font_face); 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; IDWriteRenderingParams *default_rendering_params = 0;
error = factory->CreateRenderingParams(&default_rendering_params); error = factory->CreateRenderingParams(&default_rendering_params);
IDWriteRenderingParams *rendering_params = 0; 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, DWRITE_RENDERING_MODE_DEFAULT,
&rendering_params); &rendering_params);
/* Interop */ //- Setup interop
IDWriteGdiInterop *dwrite_gdi_interop = 0; IDWriteGdiInterop *dwrite_gdi_interop = 0;
error = factory->GetGdiInterop(&dwrite_gdi_interop); error = factory->GetGdiInterop(&dwrite_gdi_interop);
/* Get Metrics */ //- Get metrics
DWRITE_FONT_METRICS metrics = ZI; DWRITE_FONT_METRICS metrics = ZI;
font_face->GetMetrics(&metrics); 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); 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_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_x) == raster_target_x);
Assert((f32)((i32)raster_target_y) == raster_target_y); Assert((f32)((i32)raster_target_y) == raster_target_y);
/* Glyph count */ //- Setup render target
u16 glyph_count = font_face->GetGlyphCount();
/* Render target */
IDWriteBitmapRenderTarget *render_target = 0; IDWriteBitmapRenderTarget *render_target = 0;
/* FIXME: errors when point_size too high */ /* FIXME: errors when point_size too high */
error = dwrite_gdi_interop->CreateBitmapRenderTarget(0, (UINT32)raster_target_w, (UINT32)raster_target_h, &render_target); 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); SelectObject(dc, original);
} }
//- Setup atlas
/* Allocate font memory */ /* Allocate font memory */
TTF_Glyph *glyphs = (TTF_Glyph *)PushStructs(arena, TTF_Glyph, glyph_count); 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; u64 atlas_h = 1;
u32 *atlas_memory = PushStructs(arena, u32, atlas_w * atlas_h); 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_x = 0;
u32 out_offset_y = 0; u32 out_offset_y = 0;
u32 row_height = 0; u32 row_height = 0;
{ {
__profn("Build atlas"); __profn("Build atlas");
for (u16 i = 0; i < glyph_count; ++i) { for (u16 i = 0; i < glyph_count; ++i)
/* Render glyph to target */ {
//- Render glyph to render target
DWRITE_GLYPH_RUN glyph_run = ZI; DWRITE_GLYPH_RUN glyph_run = ZI;
glyph_run.fontFace = font_face; glyph_run.fontFace = font_face;
glyph_run.fontEmSize = pixel_per_em; 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 if (bounding_box.left < 0
|| bounding_box.top < 0 || bounding_box.top < 0
|| bounding_box.right > raster_target_w || bounding_box.right > raster_target_w
|| bounding_box.bottom > raster_target_h) { || bounding_box.bottom > raster_target_h)
{
/* Skip */ /* Skip */
continue; continue;
} }
/* Compute glyph metrics */ //- Compute glyph metrics
DWRITE_GLYPH_METRICS glyph_metrics = ZI; DWRITE_GLYPH_METRICS glyph_metrics = ZI;
error = font_face->GetDesignGlyphMetrics(&i, 1, &glyph_metrics, 0); error = font_face->GetDesignGlyphMetrics(&i, 1, &glyph_metrics, 0);
f32 off_x = (f32)bounding_box.left - raster_target_x; f32 off_x = (f32)bounding_box.left - raster_target_x;
f32 off_y = (f32)bounding_box.top - raster_target_y; 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_w = bounding_box.right - bounding_box.left;
i32 tex_h = bounding_box.bottom - bounding_box.top; 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]; TTF_Glyph *glyph = &glyphs[i];
glyph->off_x = off_x; glyph->off_x = off_x;
glyph->off_y = off_y; glyph->off_y = off_y;
glyph->advance = round_up(advance);
glyph->width = (f32)tex_w; glyph->width = (f32)tex_w;
glyph->height = (f32)tex_h; glyph->height = (f32)tex_h;
glyph->advance = advance;
/* Get the bitmap */ /* Get the bitmap */
HBITMAP bitmap = (HBITMAP)GetCurrentObject(dc, OBJ_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); GetObject(bitmap, sizeof(dib), &dib);
/* Start new row if necessary */ /* 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_y += row_height;
out_offset_x = 0; out_offset_x = 0;
row_height = 0; row_height = 0;
} }
/* Grow atlas height */ /* 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; u64 diff = (out_offset_y + tex_h) - atlas_h;
/* NOTE: This allocation must be contiguous with the initial atlas /* NOTE: This allocation must be contiguous with the initial atlas
* allocation (IE: No non-atlas arena PUSHes) */ * 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.width = (f32)tex_w;
glyph->atlas_rect.height = (f32)tex_h; glyph->atlas_rect.height = (f32)tex_h;
/* Fill atlas */ //- Fill atlas
u64 in_pitch = (u64)dib.dsBm.bmWidthBytes / 4; u64 in_pitch = (u64)dib.dsBm.bmWidthBytes / 4;
u32 *in_data = (u32 *)dib.dsBm.bmBits; u32 *in_data = (u32 *)dib.dsBm.bmBits;
u32 *out_data = atlas_memory; 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 out_y = out_offset_y + y;
u64 in_y = (u64)bounding_box.top + 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 out_x = out_offset_x + x;
u64 in_x = (u64)bounding_box.left + x; u64 in_x = (u64)bounding_box.left + x;
u32 *out_pixel = out_data + (out_x + (out_y * atlas_w)); 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; out_offset_x += tex_w;
/* Grow row height */ /* Grow row height */
if ((u32)tex_h > row_height) { if ((u32)tex_h > row_height)
{
row_height = (u32)tex_h; row_height = (u32)tex_h;
} }
/* Clear the render target */ //- Clear render target
{ {
HGDIOBJ original = SelectObject(dc, GetStockObject(DC_PEN)); HGDIOBJ original = SelectObject(dc, GetStockObject(DC_PEN));
SetDCPenColor(dc, bg_color); 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; u16 *cache_indices = 0;
if (cache_codes_count > 0) { if (cache_codes_count > 0)
{
cache_indices = PushStructs(arena, u16, cache_codes_count); cache_indices = PushStructs(arena, u16, cache_codes_count);
font_face->GetGlyphIndices(cache_codes, cache_codes_count, cache_indices); font_face->GetGlyphIndices(cache_codes, cache_codes_count, cache_indices);
} }
/* Release */ //- Release
/* FIXME: Check for leaks */
dwrite_gdi_interop->Release(); dwrite_gdi_interop->Release();
rendering_params->Release(); rendering_params->Release();
default_rendering_params->Release(); default_rendering_params->Release();

12
src/ttf/ttf_dwrite.h Normal file
View File

@ -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;