From c140b6271bb89a3831f6c0d8e2784280600492da Mon Sep 17 00:00:00 2001 From: jacob Date: Sun, 14 Dec 2025 13:28:03 -0600 Subject: [PATCH] cache direct-write font faces --- src/base/base_async.c | 2 +- src/base/base_wave.c | 2 +- src/config.h | 4 +- src/pp/pp_vis/pp_vis_widgets.c | 7 +- src/ttf/ttf_dwrite/ttf_dwrite.c | 460 ++++++++++++++++++-------------- src/ttf/ttf_dwrite/ttf_dwrite.h | 26 ++ 6 files changed, 295 insertions(+), 206 deletions(-) diff --git a/src/base/base_async.c b/src/base/base_async.c index c894f85d..f46b95e2 100644 --- a/src/base/base_async.c +++ b/src/base/base_async.c @@ -39,7 +39,7 @@ void AsyncWorkerEntryPoint(WaveLaneCtx *lane) AsyncWorkerCtx *w = &Base.async.worker_ctx; /* FIXME: Remove this */ - SleepSeconds(0.100); + SleepSeconds(0.001); ////////////////////////////// //- Begin tick diff --git a/src/base/base_wave.c b/src/base/base_wave.c index 11d7d01e..500b24b0 100644 --- a/src/base/base_wave.c +++ b/src/base/base_wave.c @@ -5,7 +5,7 @@ void WaveSyncEx(WaveLaneCtx *lane, u64 spin_count) { WaveCtx *wave = lane->wave; i32 lanes_count = wave->lanes_count; - if (lanes_count > 0) + if (lanes_count > 1) { i64 sync_gen = Atomic64Fetch(&wave->sync_gen.v); i32 blocked_count = Atomic32FetchAdd(&wave->sync_count.v, 1) + 1; diff --git a/src/config.h b/src/config.h index 960b7842..6329442a 100644 --- a/src/config.h +++ b/src/config.h @@ -69,8 +69,8 @@ #define FLOOD_DEBUG 0 -#define GPU_DEBUG 1 -#define GPU_DEBUG_VALIDATION 1 +#define GPU_DEBUG 0 +#define GPU_DEBUG_VALIDATION 0 #define GPU_SHADER_PRINT 1 #define GPU_SHADER_PRINT_BUFFER_SIZE Kibi(64); diff --git a/src/pp/pp_vis/pp_vis_widgets.c b/src/pp/pp_vis/pp_vis_widgets.c index c67160a1..62822622 100644 --- a/src/pp/pp_vis/pp_vis_widgets.c +++ b/src/pp/pp_vis/pp_vis_widgets.c @@ -5,8 +5,11 @@ V_WidgetTheme V_GetWidgetTheme(void) { V_WidgetTheme theme = ZI; - theme.font = GC_FontKeyFromResource(ResourceKeyFromStore(&V_Resources, Lit("font/fixedsys.ttf"))); - theme.font_size = 16; + // theme.font = GC_FontKeyFromResource(ResourceKeyFromStore(&V_Resources, Lit("font/fixedsys.ttf"))); + // 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_border_color = Rgb32(0xff343a3b); diff --git a/src/ttf/ttf_dwrite/ttf_dwrite.c b/src/ttf/ttf_dwrite/ttf_dwrite.c index 938e812d..31730fac 100644 --- a/src/ttf/ttf_dwrite/ttf_dwrite.c +++ b/src/ttf/ttf_dwrite/ttf_dwrite.c @@ -8,18 +8,62 @@ extern TTF_DW_Ctx TTF_DW = ZI; 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 * using a factory5 since I think WriteInMemoryFileLoader wasn't - * implemented until then) */ - HRESULT hr = DWriteCreateFactory( - DWRITE_FACTORY_TYPE_SHARED, - &IID_IDWriteFactory5, - (void **)&TTF_DW.factory - ); + * implemented until then) + */ + HRESULT hr = 0; + { + /* Factory */ + if (SUCCEEDED(hr)) + { + hr = DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, &IID_IDWriteFactory5, (void **)&TTF_DW.factory); + } + /* GDI interop */ + if (SUCCEEDED(hr)) + { + hr = IDWriteFactory5_GetGdiInterop(TTF_DW.factory, &TTF_DW.gdi_interop); + } + /* Loader */ + if (SUCCEEDED(hr)) + { + hr = IDWriteFactory5_CreateInMemoryFontFileLoader(TTF_DW.factory, &TTF_DW.loader); + if (SUCCEEDED(hr)) + { + hr = IDWriteFactory5_RegisterFontFileLoader(TTF_DW.factory, (IDWriteFontFileLoader *)TTF_DW.loader); + } + } + /* Builder */ + if (SUCCEEDED(hr)) + { + hr = IDWriteFactory5_CreateFontSetBuilder1(TTF_DW.factory, &TTF_DW.builder); + } + /* Rendering params */ + if (SUCCEEDED(hr)) + { + IDWriteRenderingParams *default_rendering_params = 0; + { + hr = IDWriteFactory5_CreateRenderingParams(TTF_DW.factory, &default_rendering_params); + } + f32 gamma = IDWriteRenderingParams_GetGamma(default_rendering_params); + f32 enhanced_contrast = IDWriteRenderingParams_GetEnhancedContrast(default_rendering_params); + f32 clear_type_level = IDWriteRenderingParams_GetClearTypeLevel(default_rendering_params); + hr = IDWriteFactory5_CreateCustomRenderingParams( + TTF_DW.factory, + gamma, + enhanced_contrast, + clear_type_level, + DWRITE_PIXEL_GEOMETRY_FLAT, + DWRITE_RENDERING_MODE_DEFAULT, + &TTF_DW.rendering_params + ); + IDWriteRenderingParams_Release(default_rendering_params); + } + } if (!SUCCEEDED(hr)) { - Panic(Lit("Error creating DWrite factory")); + Panic(Lit("Failed to initialize DirectWrite")); } } @@ -28,219 +72,235 @@ void TTF_Bootstrap(void) 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 pixel_per_em = em_size * (TTF_DW_Dpi / 72.0f); + f32 pixels_per_em = em_size * (TTF_DW_Dpi / 72.0f); - /* TODO: handle errors */ + /* Load font */ HRESULT hr = 0; - - String encoded = DataFromResource(ttf); - - /* File */ - IDWriteFontFile *font_file = 0; + TTF_DW_Font *font = 0; { - /* Create in memory loader */ - IDWriteInMemoryFontFileLoader *loader = 0; - hr = IDWriteFactory5_CreateInMemoryFontFileLoader(TTF_DW.factory, &loader); - hr = IDWriteFactory5_RegisterFontFileLoader(TTF_DW.factory, (IDWriteFontFileLoader *)loader); - - 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); - } - - /* Face */ - 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); - } - - /* Glyph idx */ - u16 glyph_idx = 0; - { - hr = IDWriteFontFace_GetGlyphIndices(font_face, &codepoint, 1, &glyph_idx); - } - - /* 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); - f32 font_ascent = font_metrics.ascent * pixel_per_design_unit; - 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); - } - f32 advance = (f32)m.advanceWidth * pixel_per_design_unit; - - - - - - - - - - - - - - - - /* 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)); + u64 hash = RandU64FromSeeds(ttf.v, (u64)(*(u32 *)&font_size)); + /* Grab font from cache */ { - 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; - { - hr = IDWriteFactory5_CreateRenderingParams(TTF_DW.factory, &default_rendering_params); - } - f32 gamma = IDWriteRenderingParams_GetGamma(default_rendering_params); - f32 enhanced_contrast = IDWriteRenderingParams_GetEnhancedContrast(default_rendering_params); - f32 clear_type_level = IDWriteRenderingParams_GetClearTypeLevel(default_rendering_params); - hr = IDWriteFactory5_CreateCustomRenderingParams( - TTF_DW.factory, - gamma, - enhanced_contrast, - clear_type_level, - DWRITE_PIXEL_GEOMETRY_FLAT, - DWRITE_RENDERING_MODE_DEFAULT, - &rendering_params - ); - } - - /* Render glyph to target */ - Rng2I32 rt_slice = ZI; - { - DWRITE_GLYPH_RUN glyph_run = ZI; - { - glyph_run.fontFace = font_face; - glyph_run.fontEmSize = pixel_per_em; - glyph_run.glyphCount = 1; - glyph_run.glyphIndices = &glyph_idx; - } - RECT bounding_box = ZI; - hr = IDWriteBitmapRenderTarget_DrawGlyphRun( - render_target, - rt_baseline.x, - rt_baseline.y, - DWRITE_MEASURING_MODE_NATURAL, - &glyph_run, - rendering_params, - fg_color, - &bounding_box - ); - rt_slice.p0.x = bounding_box.left; - rt_slice.p0.y = bounding_box.top; - rt_slice.p1.x = bounding_box.right; - rt_slice.p1.y = bounding_box.bottom; - } - rt_slice.p0.x = MaxI32(rt_slice.p0.x, 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.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 */ - { - u64 src_pitch = (u64)dib.dsBm.bmWidthBytes / 4; - u32 *src_pixels = (u32 *)dib.dsBm.bmBits; - for (i32 y = 0; y < dst_dims.y; ++y) - { - u64 src_y = rt_slice.p0.y + y; - u64 dst_y = y; - for (i32 x = 0; x < dst_dims.x; ++x) + Lock lock = LockS(&TTF_DW.font_bins_mutex); { - u64 src_x = rt_slice.p0.x + x; - u64 dst_x = x; - u32 *src_pixel = src_pixels + (src_x + (src_y * src_pitch)); - u32 *dst_pixel = dst_pixels + (dst_x + (dst_y * dst_dims.x)); - *dst_pixel = 0x00FFFFFF | ((*src_pixel & 0xFF) << 24); + 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; + 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; + { + glyph_run.fontFace = font->face; + glyph_run.fontEmSize = pixels_per_em; + glyph_run.glyphCount = 1; + glyph_run.glyphIndices = &glyph_idx; + } + RECT bounding_box = ZI; + hr = IDWriteBitmapRenderTarget_DrawGlyphRun( + render_target, + rt_baseline.x, + rt_baseline.y, + DWRITE_MEASURING_MODE_NATURAL, + &glyph_run, + TTF_DW.rendering_params, + fg_color, + &bounding_box + ); + rt_slice.p0.x = bounding_box.left; + rt_slice.p0.y = bounding_box.top; + rt_slice.p1.x = bounding_box.right; + rt_slice.p1.y = bounding_box.bottom; + } + rt_slice.p0.x = MaxI32(rt_slice.p0.x, 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.y = MinI32(rt_slice.p1.y, rt_dims.y); + } + + /* 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; + u32 *src_pixels = (u32 *)dib.dsBm.bmBits; + for (i32 y = 0; y < dst_dims.y; ++y) + { + u64 src_y = rt_slice.p0.y + y; + u64 dst_y = y; + for (i32 x = 0; x < dst_dims.x; ++x) + { + u64 src_x = rt_slice.p0.x + x; + u64 dst_x = x; + u32 *src_pixel = src_pixels + (src_x + (src_y * src_pitch)); + u32 *dst_pixel = dst_pixels + (dst_x + (dst_y * dst_dims.x)); + *dst_pixel = 0x00FFFFFF | ((*src_pixel & 0xFF) << 24); + } + } + } + + result.advance = advance; + result.bounds = RNG2(Vec2FromVec(rt_slice.p0), Vec2FromVec(rt_slice.p1)); + 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.advance = advance; - result.bounds = RNG2(Vec2FromVec(rt_slice.p0), Vec2FromVec(rt_slice.p1)); - result.bounds = AddRng2Vec2(result.bounds, NegVec2(Vec2FromVec(rt_baseline))); - result.font_size = font_size; result.font_ascent = font_ascent; result.font_descent = font_descent; result.font_cap = font_cap; - - result.image_dims = dst_dims; - result.image_pixels = dst_pixels; } + return result; } diff --git a/src/ttf/ttf_dwrite/ttf_dwrite.h b/src/ttf/ttf_dwrite/ttf_dwrite.h index d541cd3b..4cca115b 100644 --- a/src/ttf/ttf_dwrite/ttf_dwrite.h +++ b/src/ttf/ttf_dwrite/ttf_dwrite.h @@ -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_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 IDWriteBitmapRenderTarget_Release (IDWriteBitmapRenderTarget* this) { return ((UINT32 (WINAPI*)(IDWriteBitmapRenderTarget*))this->v->tbl[2])(this); } //- Functions 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 @@ -148,6 +167,13 @@ EXTERN_C HRESULT DECLSPEC_IMPORT WINAPI DWriteCreateFactory (DWRITE_FACTORY_TYPE Struct(TTF_DW_Ctx) { 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;