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 */
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();

View File

@ -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 */

View File

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

View File

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

View File

@ -5,4 +5,8 @@
#include "ttf_core.h"
#if PlatformIsWindows
# include "ttf_dwrite.h"
#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_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);

View File

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

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;