cache direct-write font faces

This commit is contained in:
jacob 2025-12-14 13:28:03 -06:00
parent 4ddc2f1666
commit c140b6271b
6 changed files with 295 additions and 206 deletions

View File

@ -39,7 +39,7 @@ void AsyncWorkerEntryPoint(WaveLaneCtx *lane)
AsyncWorkerCtx *w = &Base.async.worker_ctx; AsyncWorkerCtx *w = &Base.async.worker_ctx;
/* FIXME: Remove this */ /* FIXME: Remove this */
SleepSeconds(0.100); SleepSeconds(0.001);
////////////////////////////// //////////////////////////////
//- Begin tick //- Begin tick

View File

@ -5,7 +5,7 @@ void WaveSyncEx(WaveLaneCtx *lane, u64 spin_count)
{ {
WaveCtx *wave = lane->wave; WaveCtx *wave = lane->wave;
i32 lanes_count = wave->lanes_count; i32 lanes_count = wave->lanes_count;
if (lanes_count > 0) if (lanes_count > 1)
{ {
i64 sync_gen = Atomic64Fetch(&wave->sync_gen.v); i64 sync_gen = Atomic64Fetch(&wave->sync_gen.v);
i32 blocked_count = Atomic32FetchAdd(&wave->sync_count.v, 1) + 1; i32 blocked_count = Atomic32FetchAdd(&wave->sync_count.v, 1) + 1;

View File

@ -69,8 +69,8 @@
#define FLOOD_DEBUG 0 #define FLOOD_DEBUG 0
#define GPU_DEBUG 1 #define GPU_DEBUG 0
#define GPU_DEBUG_VALIDATION 1 #define GPU_DEBUG_VALIDATION 0
#define GPU_SHADER_PRINT 1 #define GPU_SHADER_PRINT 1
#define GPU_SHADER_PRINT_BUFFER_SIZE Kibi(64); #define GPU_SHADER_PRINT_BUFFER_SIZE Kibi(64);

View File

@ -5,8 +5,11 @@ V_WidgetTheme V_GetWidgetTheme(void)
{ {
V_WidgetTheme theme = ZI; V_WidgetTheme theme = ZI;
theme.font = GC_FontKeyFromResource(ResourceKeyFromStore(&V_Resources, Lit("font/fixedsys.ttf"))); // theme.font = GC_FontKeyFromResource(ResourceKeyFromStore(&V_Resources, Lit("font/fixedsys.ttf")));
theme.font_size = 16; // theme.font_size = 16;
theme.font = GC_FontKeyFromResource(ResourceKeyFromStore(&V_Resources, Lit("font/roboto-med.ttf")));
theme.font_size = 100;
theme.window_background_color = Rgb32(0xff1a1d1e); theme.window_background_color = Rgb32(0xff1a1d1e);
theme.window_border_color = Rgb32(0xff343a3b); theme.window_border_color = Rgb32(0xff343a3b);

View File

@ -8,152 +8,40 @@ extern TTF_DW_Ctx TTF_DW = ZI;
void TTF_Bootstrap(void) void TTF_Bootstrap(void)
{ {
/* FIXME: I think IDWriteFactory5 only exists on later updates of windows /* TODO: 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
* implemented until then) */ * implemented until then)
HRESULT hr = DWriteCreateFactory( */
DWRITE_FACTORY_TYPE_SHARED,
&IID_IDWriteFactory5,
(void **)&TTF_DW.factory
);
if (!SUCCEEDED(hr))
{
Panic(Lit("Error creating DWrite factory"));
}
}
////////////////////////////////////////////////////////////
//~ @hookimpl Rasterize
TTF_GlyphResult TTF_RasterizeGlyphFromCodepoint(Arena *arena, u32 codepoint, ResourceKey ttf, f32 font_size)
{
COLORREF bg_color = 0xFF000000;
COLORREF fg_color = 0xFFFFFFFF;
f32 em_size = font_size * (3.0 / 4.0);
f32 pixel_per_em = em_size * (TTF_DW_Dpi / 72.0f);
/* TODO: handle errors */
HRESULT hr = 0; HRESULT hr = 0;
String encoded = DataFromResource(ttf);
/* File */
IDWriteFontFile *font_file = 0;
{ {
/* Create in memory loader */ /* Factory */
IDWriteInMemoryFontFileLoader *loader = 0; if (SUCCEEDED(hr))
hr = IDWriteFactory5_CreateInMemoryFontFileLoader(TTF_DW.factory, &loader); {
hr = IDWriteFactory5_RegisterFontFileLoader(TTF_DW.factory, (IDWriteFontFileLoader *)loader); hr = DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, &IID_IDWriteFactory5, (void **)&TTF_DW.factory);
IDWriteFontSetBuilder1 *builder = 0;
hr = IDWriteFactory5_CreateFontSetBuilder1(TTF_DW.factory, &builder);
hr = IDWriteInMemoryFontFileLoader_CreateInMemoryFontFileReference(loader, (IDWriteFactory *)TTF_DW.factory, encoded.text, (u32)encoded.len, 0, &font_file);
hr = IDWriteFontSetBuilder1_AddFontFile(builder, font_file);
} }
/* GDI interop */
/* Face */ if (SUCCEEDED(hr))
IDWriteFontFace *font_face = 0;
{ {
hr = IDWriteFactory5_CreateFontFace(TTF_DW.factory, DWRITE_FONT_FACE_TYPE_TRUETYPE, 1, &font_file, 0, DWRITE_FONT_SIMULATIONS_NONE, &font_face); hr = IDWriteFactory5_GetGdiInterop(TTF_DW.factory, &TTF_DW.gdi_interop);
} }
/* Loader */
/* Glyph idx */ if (SUCCEEDED(hr))
u16 glyph_idx = 0;
{ {
hr = IDWriteFontFace_GetGlyphIndices(font_face, &codepoint, 1, &glyph_idx); hr = IDWriteFactory5_CreateInMemoryFontFileLoader(TTF_DW.factory, &TTF_DW.loader);
if (SUCCEEDED(hr))
{
hr = IDWriteFactory5_RegisterFontFileLoader(TTF_DW.factory, (IDWriteFontFileLoader *)TTF_DW.loader);
} }
/* Font metrics */
DWRITE_FONT_METRICS font_metrics = ZI;
{
IDWriteFontFace_GetMetrics(font_face, &font_metrics);
} }
f32 pixel_per_design_unit = pixel_per_em / ((f32)font_metrics.designUnitsPerEm); /* Builder */
f32 font_ascent = font_metrics.ascent * pixel_per_design_unit; if (SUCCEEDED(hr))
f32 font_descent = font_metrics.descent * pixel_per_design_unit;
f32 font_cap = font_metrics.capHeight * pixel_per_design_unit;
/* Glyph metrics */
DWRITE_GLYPH_METRICS m = ZI;
{ {
hr = IDWriteFontFace_GetDesignGlyphMetrics(font_face, &glyph_idx, 1, &m, 0); hr = IDWriteFactory5_CreateFontSetBuilder1(TTF_DW.factory, &TTF_DW.builder);
} }
f32 advance = (f32)m.advanceWidth * pixel_per_design_unit; /* Rendering params */
if (SUCCEEDED(hr))
/* Get interop */
IDWriteGdiInterop *dwrite_gdi_interop = 0;
{ {
hr = IDWriteFactory5_GetGdiInterop(TTF_DW.factory, &dwrite_gdi_interop);
}
/* FIXME: Dynamic render target */
Vec2I32 rt_dims = VEC2I32(256, 256);
/* Best-guess a position in the middle of the render target based on metrics */
Vec2I32 rt_baseline = ZI;
rt_baseline.x = (rt_dims.x / 2) - (advance / 2);
rt_baseline.y = (rt_dims.y / 2) + (font_cap / 2);
/* Create render target */
IDWriteBitmapRenderTarget *render_target = 0;
{
hr = IDWriteGdiInterop_CreateBitmapRenderTarget(dwrite_gdi_interop, 0, (UINT32)rt_dims.x, (UINT32)rt_dims.y, &render_target);
hr = IDWriteBitmapRenderTarget_SetPixelsPerDip(render_target, TTF_DW_Dpi / 96.0);
}
/* Get device context */
HDC dc = 0;
{
dc = IDWriteBitmapRenderTarget_GetMemoryDC(render_target);
}
/* Get dib info */
DIBSECTION dib = ZI;
{
HBITMAP bitmap = (HBITMAP)GetCurrentObject(dc, OBJ_BITMAP);
GetObject(bitmap, sizeof(dib), &dib);
}
/* Clear render target */
{
HGDIOBJ original = SelectObject(dc, GetStockObject(DC_PEN));
{
SetDCPenColor(dc, bg_color);
SelectObject(dc, GetStockObject(DC_BRUSH));
SetDCBrushColor(dc, bg_color);
Rectangle(dc, 0, 0, rt_dims.x, rt_dims.y);
}
SelectObject(dc, original);
}
/* Create rendering params */
IDWriteRenderingParams *rendering_params = 0;
{
/* Get default rendering params */
IDWriteRenderingParams *default_rendering_params = 0; IDWriteRenderingParams *default_rendering_params = 0;
{ {
hr = IDWriteFactory5_CreateRenderingParams(TTF_DW.factory, &default_rendering_params); hr = IDWriteFactory5_CreateRenderingParams(TTF_DW.factory, &default_rendering_params);
@ -168,17 +56,187 @@ TTF_GlyphResult TTF_RasterizeGlyphFromCodepoint(Arena *arena, u32 codepoint, Res
clear_type_level, clear_type_level,
DWRITE_PIXEL_GEOMETRY_FLAT, DWRITE_PIXEL_GEOMETRY_FLAT,
DWRITE_RENDERING_MODE_DEFAULT, DWRITE_RENDERING_MODE_DEFAULT,
&rendering_params &TTF_DW.rendering_params
); );
IDWriteRenderingParams_Release(default_rendering_params);
}
}
if (!SUCCEEDED(hr))
{
Panic(Lit("Failed to initialize DirectWrite"));
}
} }
/* Render glyph to target */ ////////////////////////////////////////////////////////////
//~ @hookimpl Rasterize
TTF_GlyphResult TTF_RasterizeGlyphFromCodepoint(Arena *arena, u32 codepoint, ResourceKey ttf, f32 font_size)
{
/* TODO: handle errors */
COLORREF bg_color = 0xFF000000;
COLORREF fg_color = 0xFFFFFFFF;
f32 em_size = font_size * (3.0 / 4.0);
f32 pixels_per_em = em_size * (TTF_DW_Dpi / 72.0f);
/* Load font */
HRESULT hr = 0;
TTF_DW_Font *font = 0;
{
u64 hash = RandU64FromSeeds(ttf.v, (u64)(*(u32 *)&font_size));
/* Grab font from cache */
{
Lock lock = LockS(&TTF_DW.font_bins_mutex);
{
TTF_DW_Font **bin = &TTF_DW.font_bins[hash % countof(TTF_DW.font_bins)];
for (font = *bin; font; font = font->next)
{
if (font->hash == hash && font->ttf.v == ttf.v && font->size == font_size)
{
break;
}
}
}
Unlock(&lock);
}
/* Create font new font */
if (!font)
{
Lock lock = LockE(&TTF_DW.font_bins_mutex);
{
TTF_DW_Font **bin = &TTF_DW.font_bins[hash % countof(TTF_DW.font_bins)];
for (font = *bin; font; font = font->next)
{
if (font->hash == hash && font->ttf.v == ttf.v && font->size == font_size)
{
break;
}
}
if (!font)
{
Arena *perm = PermArena();
String encoded = DataFromResource(ttf);
font = PushStruct(perm, TTF_DW_Font);
font->hash = hash;
font->ttf = ttf;
font->size = font_size;
/* File */
if (SUCCEEDED(hr))
{
hr = IDWriteInMemoryFontFileLoader_CreateInMemoryFontFileReference(
TTF_DW.loader,
(IDWriteFactory *)TTF_DW.factory,
encoded.text,
(u32)encoded.len,
0,
&font->file
);
if (SUCCEEDED(hr))
{
hr = IDWriteFontSetBuilder1_AddFontFile(TTF_DW.builder, font->file);
}
}
/* Face */
if (SUCCEEDED(hr))
{
hr = IDWriteFactory5_CreateFontFace(
TTF_DW.factory,
DWRITE_FONT_FACE_TYPE_TRUETYPE,
1,
&font->file,
0,
DWRITE_FONT_SIMULATIONS_NONE,
&font->face
);
}
/* Metrics */
if (SUCCEEDED(hr))
{
IDWriteFontFace_GetMetrics(font->face, &font->design_metrics);
}
font->hr = hr;
SllStackPush(*bin, font);
}
}
Unlock(&lock);
}
if (SUCCEEDED(hr))
{
hr = font->hr;
}
}
f32 design_units_per_em = font->design_metrics.designUnitsPerEm;
f32 pixels_per_design_unit = design_units_per_em > 0 ? (pixels_per_em / design_units_per_em) : 0;
f32 font_ascent = font->design_metrics.ascent * pixels_per_design_unit;
f32 font_descent = font->design_metrics.descent * pixels_per_design_unit;
f32 font_cap = font->design_metrics.capHeight * pixels_per_design_unit;
/* Render glyph */
TTF_GlyphResult result = ZI;
{
if (SUCCEEDED(hr))
{
/* Glyph idx */
u16 glyph_idx = 0;
if (SUCCEEDED(hr))
{
hr = IDWriteFontFace_GetGlyphIndices(font->face, &codepoint, 1, &glyph_idx);
}
/* Glyph metrics */
DWRITE_GLYPH_METRICS m = ZI;
if (SUCCEEDED(hr))
{
hr = IDWriteFontFace_GetDesignGlyphMetrics(font->face, &glyph_idx, 1, &m, 0);
}
f32 advance = (f32)m.advanceWidth * pixels_per_design_unit;
/* FIXME: Dynamic render target */
Vec2I32 rt_dims = VEC2I32(256, 256);
/* Best-guess a position in the middle of the render target based on metrics */
Vec2I32 rt_baseline = ZI;
rt_baseline.x = (rt_dims.x / 2) - (advance / 2);
rt_baseline.y = (rt_dims.y / 2) + (font_cap / 2);
/* Create render target */
IDWriteBitmapRenderTarget *render_target = 0;
HDC dc = 0;
DIBSECTION dib = ZI;
if (SUCCEEDED(hr))
{
hr = IDWriteGdiInterop_CreateBitmapRenderTarget(TTF_DW.gdi_interop, 0, (UINT32)rt_dims.x, (UINT32)rt_dims.y, &render_target);
if (SUCCEEDED(hr))
{
hr = IDWriteBitmapRenderTarget_SetPixelsPerDip(render_target, TTF_DW_Dpi / 96.0);
dc = IDWriteBitmapRenderTarget_GetMemoryDC(render_target);
HBITMAP bitmap = (HBITMAP)GetCurrentObject(dc, OBJ_BITMAP);
GetObject(bitmap, sizeof(dib), &dib);
}
}
/* Render */
Rng2I32 rt_slice = ZI; Rng2I32 rt_slice = ZI;
if (SUCCEEDED(hr))
{
/* Clear target */
{
HGDIOBJ original = SelectObject(dc, GetStockObject(DC_PEN));
{
SetDCPenColor(dc, bg_color);
SelectObject(dc, GetStockObject(DC_BRUSH));
SetDCBrushColor(dc, bg_color);
Rectangle(dc, 0, 0, rt_dims.x, rt_dims.y);
}
SelectObject(dc, original);
}
/* Draw glyph */
{ {
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 = pixels_per_em;
glyph_run.glyphCount = 1; glyph_run.glyphCount = 1;
glyph_run.glyphIndices = &glyph_idx; glyph_run.glyphIndices = &glyph_idx;
} }
@ -189,7 +247,7 @@ TTF_GlyphResult TTF_RasterizeGlyphFromCodepoint(Arena *arena, u32 codepoint, Res
rt_baseline.y, rt_baseline.y,
DWRITE_MEASURING_MODE_NATURAL, DWRITE_MEASURING_MODE_NATURAL,
&glyph_run, &glyph_run,
rendering_params, TTF_DW.rendering_params,
fg_color, fg_color,
&bounding_box &bounding_box
); );
@ -202,12 +260,15 @@ TTF_GlyphResult TTF_RasterizeGlyphFromCodepoint(Arena *arena, u32 codepoint, Res
rt_slice.p0.y = MaxI32(rt_slice.p0.y, 0); rt_slice.p0.y = MaxI32(rt_slice.p0.y, 0);
rt_slice.p1.x = MinI32(rt_slice.p1.x, rt_dims.x); rt_slice.p1.x = MinI32(rt_slice.p1.x, rt_dims.x);
rt_slice.p1.y = MinI32(rt_slice.p1.y, rt_dims.y); rt_slice.p1.y = MinI32(rt_slice.p1.y, rt_dims.y);
}
Vec2I32 dst_dims = DimsFromRng2I32(rt_slice);
u32 *dst_pixels = PushStructsNoZero(arena, u32, dst_dims.x * dst_dims.y);
/* Copy from target to result */ /* Copy from target to result */
Vec2I32 dst_dims = ZI;
u32 *dst_pixels = 0;
if (SUCCEEDED(hr))
{ {
dst_dims = DimsFromRng2I32(rt_slice);
dst_pixels = PushStructsNoZero(arena, u32, dst_dims.x * dst_dims.y);
u64 src_pitch = (u64)dib.dsBm.bmWidthBytes / 4; u64 src_pitch = (u64)dib.dsBm.bmWidthBytes / 4;
u32 *src_pixels = (u32 *)dib.dsBm.bmBits; u32 *src_pixels = (u32 *)dib.dsBm.bmBits;
for (i32 y = 0; y < dst_dims.y; ++y) for (i32 y = 0; y < dst_dims.y; ++y)
@ -225,22 +286,21 @@ TTF_GlyphResult TTF_RasterizeGlyphFromCodepoint(Arena *arena, u32 codepoint, Res
} }
} }
TTF_GlyphResult result = ZI;
{
result.ttf = ttf;
result.codepoint = codepoint;
result.advance = advance; result.advance = advance;
result.bounds = RNG2(Vec2FromVec(rt_slice.p0), Vec2FromVec(rt_slice.p1)); result.bounds = RNG2(Vec2FromVec(rt_slice.p0), Vec2FromVec(rt_slice.p1));
result.bounds = AddRng2Vec2(result.bounds, NegVec2(Vec2FromVec(rt_baseline))); result.bounds = AddRng2Vec2(result.bounds, NegVec2(Vec2FromVec(rt_baseline)));
result.image_dims = dst_dims;
result.image_pixels = dst_pixels;
IDWriteBitmapRenderTarget_Release(render_target);
}
result.ttf = ttf;
result.codepoint = codepoint;
result.font_size = font_size; result.font_size = font_size;
result.font_ascent = font_ascent; result.font_ascent = font_ascent;
result.font_descent = font_descent; result.font_descent = font_descent;
result.font_cap = font_cap; result.font_cap = font_cap;
result.image_dims = dst_dims;
result.image_pixels = dst_pixels;
} }
return result; return result;
} }

View File

@ -135,10 +135,29 @@ static inline HRESULT IDWriteBitmapRenderTarget_DrawGl
static inline HRESULT IDWriteFontFace_GetDesignGlyphMetrics (IDWriteFontFace* this, const UINT16* glyphIndices, UINT32 glyphCount, DWRITE_GLYPH_METRICS* glyphMetrics, BOOL isSideways) { return ((HRESULT (WINAPI*)(IDWriteFontFace*, const UINT16*, UINT32, DWRITE_GLYPH_METRICS*, BOOL))this->v->tbl[10])(this, glyphIndices, glyphCount, glyphMetrics, isSideways); } static inline HRESULT IDWriteFontFace_GetDesignGlyphMetrics (IDWriteFontFace* this, const UINT16* glyphIndices, UINT32 glyphCount, DWRITE_GLYPH_METRICS* glyphMetrics, BOOL isSideways) { return ((HRESULT (WINAPI*)(IDWriteFontFace*, const UINT16*, UINT32, DWRITE_GLYPH_METRICS*, BOOL))this->v->tbl[10])(this, glyphIndices, glyphCount, glyphMetrics, isSideways); }
static inline HRESULT IDWriteFontFace_GetGlyphIndices (IDWriteFontFace* this, const UINT32* codePoints, UINT32 codePointCount, UINT16* glyphIndices) { return ((HRESULT (WINAPI*)(IDWriteFontFace*, const UINT32*, UINT32, UINT16*))this->v->tbl[11])(this, codePoints, codePointCount, glyphIndices); } static inline HRESULT IDWriteFontFace_GetGlyphIndices (IDWriteFontFace* this, const UINT32* codePoints, UINT32 codePointCount, UINT16* glyphIndices) { return ((HRESULT (WINAPI*)(IDWriteFontFace*, const UINT32*, UINT32, UINT16*))this->v->tbl[11])(this, codePoints, codePointCount, glyphIndices); }
static inline UINT32 IDWriteGdiInterop_Release (IDWriteGdiInterop* this) { return ((UINT32 (WINAPI*)(IDWriteGdiInterop*))this->v->tbl[2])(this); } static inline UINT32 IDWriteGdiInterop_Release (IDWriteGdiInterop* this) { return ((UINT32 (WINAPI*)(IDWriteGdiInterop*))this->v->tbl[2])(this); }
static inline UINT32 IDWriteBitmapRenderTarget_Release (IDWriteBitmapRenderTarget* this) { return ((UINT32 (WINAPI*)(IDWriteBitmapRenderTarget*))this->v->tbl[2])(this); }
//- Functions //- Functions
EXTERN_C HRESULT DECLSPEC_IMPORT WINAPI DWriteCreateFactory (DWRITE_FACTORY_TYPE factoryType, const GUID* iid, void** factory) WIN_NOEXCEPT; EXTERN_C HRESULT DECLSPEC_IMPORT WINAPI DWriteCreateFactory (DWRITE_FACTORY_TYPE factoryType, const GUID* iid, void** factory) WIN_NOEXCEPT;
////////////////////////////////////////////////////////////
//~ Cache types
Struct(TTF_DW_Font)
{
TTF_DW_Font *next;
u64 hash;
ResourceKey ttf;
f32 size;
IDWriteFontFile *file;
IDWriteFontFace *face;
DWRITE_FONT_METRICS design_metrics;
HRESULT hr;
};
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
//~ Context types //~ Context types
@ -148,6 +167,13 @@ EXTERN_C HRESULT DECLSPEC_IMPORT WINAPI DWriteCreateFactory (DWRITE_FACTORY_TYPE
Struct(TTF_DW_Ctx) Struct(TTF_DW_Ctx)
{ {
IDWriteFactory5 *factory; IDWriteFactory5 *factory;
IDWriteGdiInterop *gdi_interop;
IDWriteInMemoryFontFileLoader *loader;
IDWriteFontSetBuilder1 *builder;
IDWriteRenderingParams *rendering_params;
Mutex font_bins_mutex;
TTF_DW_Font *font_bins[1024];
}; };
extern TTF_DW_Ctx TTF_DW; extern TTF_DW_Ctx TTF_DW;