FREETYPEEEEEE

This commit is contained in:
2026-03-05 02:31:31 -05:00
parent fb358e3c4b
commit 6c43a29f9f
293 changed files with 153022 additions and 262 deletions

View File

@@ -12,6 +12,15 @@
#endif
#include <windows.h>
// FreeType headers — temporarily undefine `internal` macro (base_core.h: #define internal static)
// because FreeType uses `internal` as a struct field name.
#undef internal
#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_BITMAP_H
#define internal static
#include "generated/font_inter.h"
#ifdef _DEBUG
#define DX12_ENABLE_DEBUG_LAYER
#endif
@@ -214,10 +223,9 @@ struct Renderer {
ID3D12Resource *icon_texture;
ID3D12DescriptorHeap *icon_srv_heap;
// GDI text measurement
HDC measure_dc;
HFONT measure_font;
F32 measure_font_size;
// FreeType
FT_Library ft_lib;
FT_Face ft_face;
// Clear color
F32 clear_r = 0.12f;
@@ -400,126 +408,66 @@ static FrameContext *wait_for_next_frame(Renderer *r) {
}
////////////////////////////////
// Font atlas
// Font atlas (FreeType)
static void init_text_measurement(Renderer *r) {
r->measure_dc = CreateCompatibleDC(nullptr);
r->measure_font_size = 0;
r->measure_font = nullptr;
}
static void ensure_measure_font(Renderer *r, F32 font_size) {
if (r->measure_font && r->measure_font_size == font_size) return;
if (r->measure_font) DeleteObject(r->measure_font);
r->measure_font = CreateFontW(
-(S32)(font_size + 0.5f), 0, 0, 0,
FW_NORMAL, FALSE, FALSE, FALSE,
DEFAULT_CHARSET, OUT_TT_PRECIS, CLIP_DEFAULT_PRECIS,
CLEARTYPE_QUALITY, DEFAULT_PITCH | FF_DONTCARE,
L"Segoe UI"
);
r->measure_font_size = font_size;
SelectObject(r->measure_dc, r->measure_font);
static void init_freetype(Renderer *r) {
FT_Init_FreeType(&r->ft_lib);
FT_New_Memory_Face(r->ft_lib, font_inter_data, font_inter_size, 0, &r->ft_face);
}
static B32 create_font_atlas(Renderer *r, F32 font_size) {
const S32 SS = 2; // supersample factor
F32 render_size = font_size * SS;
S32 render_w = FONT_ATLAS_W * SS;
S32 render_h = FONT_ATLAS_H * SS;
S32 pixel_size = (S32)(font_size + 0.5f);
r->font_atlas_size = font_size;
// Create a GDI bitmap to render glyphs at supersampled resolution
HDC dc = CreateCompatibleDC(nullptr);
HFONT font = CreateFontW(
-(S32)(render_size + 0.5f), 0, 0, 0,
FW_NORMAL, FALSE, FALSE, FALSE,
DEFAULT_CHARSET, OUT_TT_PRECIS, CLIP_DEFAULT_PRECIS,
ANTIALIASED_QUALITY, DEFAULT_PITCH | FF_DONTCARE,
L"Segoe UI"
);
SelectObject(dc, font);
FT_Set_Pixel_Sizes(r->ft_face, 0, pixel_size);
r->font_line_height = (F32)(r->ft_face->size->metrics.height >> 6);
// Get line height (at supersample scale, divide back to get 1x)
TEXTMETRICW tm;
GetTextMetricsW(dc, &tm);
r->font_line_height = (F32)tm.tmHeight / SS;
U8 *atlas_data = (U8 *)calloc(1, FONT_ATLAS_W * FONT_ATLAS_H);
// Create DIB section at supersampled resolution
BITMAPINFO bmi = {};
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmi.bmiHeader.biWidth = render_w;
bmi.bmiHeader.biHeight = -render_h; // top-down
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = 32;
bmi.bmiHeader.biCompression = BI_RGB;
void *dib_bits = nullptr;
HBITMAP dib = CreateDIBSection(dc, &bmi, DIB_RGB_COLORS, &dib_bits, nullptr, 0);
SelectObject(dc, dib);
SelectObject(dc, font);
// Clear to black
memset(dib_bits, 0, render_w * render_h * 4);
SetTextColor(dc, RGB(255, 255, 255));
SetBkMode(dc, TRANSPARENT);
// Render each glyph at supersampled resolution
S32 pen_x = SS, pen_y = SS;
S32 pen_x = 1, pen_y = 1;
S32 row_height = 0;
F32 ascender = (F32)(r->ft_face->size->metrics.ascender >> 6);
for (S32 i = 0; i < GLYPH_COUNT; i++) {
char ch = (char)(GLYPH_FIRST + i);
SIZE ch_size = {};
GetTextExtentPoint32A(dc, &ch, 1, &ch_size);
if (FT_Load_Char(r->ft_face, ch, FT_LOAD_RENDER)) continue;
S32 gw = ch_size.cx + 2 * SS; // padding scaled by SS
S32 gh = ch_size.cy + 2 * SS;
FT_GlyphSlot g = r->ft_face->glyph;
S32 bw = (S32)g->bitmap.width;
S32 bh = (S32)g->bitmap.rows;
S32 pad = 2;
S32 cell_w = bw + pad;
S32 cell_h = (S32)r->font_line_height + pad;
if (pen_x + gw >= render_w) {
pen_x = SS;
pen_y += row_height + SS;
if (pen_x + cell_w >= FONT_ATLAS_W) {
pen_x = 1;
pen_y += row_height + 1;
row_height = 0;
}
if (pen_y + cell_h >= FONT_ATLAS_H) break;
if (pen_y + gh >= render_h) break; // out of space
TextOutA(dc, pen_x + SS, pen_y + SS, &ch, 1);
// UVs are fractional — pen_x/render_w == (pen_x/SS)/FONT_ATLAS_W
r->glyphs[i].u0 = (F32)pen_x / (F32)render_w;
r->glyphs[i].v0 = (F32)pen_y / (F32)render_h;
r->glyphs[i].u1 = (F32)(pen_x + gw) / (F32)render_w;
r->glyphs[i].v1 = (F32)(pen_y + gh) / (F32)render_h;
r->glyphs[i].w = (F32)gw / SS; // store at 1x scale
r->glyphs[i].h = (F32)gh / SS;
r->glyphs[i].x_advance = (F32)ch_size.cx / SS;
if (gh > row_height) row_height = gh;
pen_x += gw + SS;
}
GdiFlush();
// Box-filter downsample from supersampled resolution to atlas resolution
U8 *atlas_data = (U8 *)malloc(FONT_ATLAS_W * FONT_ATLAS_H);
U8 *src = (U8 *)dib_bits;
for (S32 y = 0; y < FONT_ATLAS_H; y++) {
for (S32 x = 0; x < FONT_ATLAS_W; x++) {
S32 sum = 0;
for (S32 sy = 0; sy < SS; sy++) {
for (S32 sx = 0; sx < SS; sx++) {
S32 src_idx = ((y * SS + sy) * render_w + (x * SS + sx)) * 4;
sum += src[src_idx + 2]; // R channel from BGRA
}
// Copy glyph bitmap into atlas at baseline-relative position
S32 y_off = (S32)ascender - g->bitmap_top;
for (S32 y = 0; y < bh; y++) {
S32 dst_y = pen_y + y_off + y;
if (dst_y < 0 || dst_y >= FONT_ATLAS_H) continue;
for (S32 x = 0; x < bw; x++) {
S32 dst_x = pen_x + g->bitmap_left + x;
if (dst_x < 0 || dst_x >= FONT_ATLAS_W) continue;
atlas_data[dst_y * FONT_ATLAS_W + dst_x] = g->bitmap.buffer[y * g->bitmap.pitch + x];
}
F32 a = (F32)sum / (F32)(SS * SS * 255);
a = powf(a, 0.55f);
atlas_data[y * FONT_ATLAS_W + x] = (U8)(a * 255.0f + 0.5f);
}
r->glyphs[i].u0 = (F32)pen_x / (F32)FONT_ATLAS_W;
r->glyphs[i].v0 = (F32)pen_y / (F32)FONT_ATLAS_H;
r->glyphs[i].u1 = (F32)(pen_x + cell_w) / (F32)FONT_ATLAS_W;
r->glyphs[i].v1 = (F32)(pen_y + cell_h) / (F32)FONT_ATLAS_H;
r->glyphs[i].w = (F32)cell_w;
r->glyphs[i].h = (F32)cell_h;
r->glyphs[i].x_advance = (F32)(g->advance.x >> 6);
if (cell_h > row_height) row_height = cell_h;
pen_x += cell_w + 1;
}
// Create D3D12 texture
@@ -540,9 +488,6 @@ static B32 create_font_atlas(Renderer *r, F32 font_size) {
&tex_desc, D3D12_RESOURCE_STATE_COPY_DEST, nullptr,
IID_PPV_ARGS(&r->font_texture)) != S_OK) {
free(atlas_data);
DeleteObject(dib);
DeleteObject(font);
DeleteDC(dc);
return false;
}
@@ -611,9 +556,6 @@ static B32 create_font_atlas(Renderer *r, F32 font_size) {
upload_buf->Release();
free(atlas_data);
DeleteObject(dib);
DeleteObject(font);
DeleteDC(dc);
// Create SRV
D3D12_SHADER_RESOURCE_VIEW_DESC srv_desc = {};
@@ -821,11 +763,15 @@ Vec2F32 renderer_measure_text(const char *text, S32 length, F32 font_size, void
Renderer *r = (Renderer *)user_data;
if (!r || length == 0) return v2f32(0, font_size);
ensure_measure_font(r, font_size);
FT_Set_Pixel_Sizes(r->ft_face, 0, (FT_UInt)(font_size + 0.5f));
SIZE sz = {};
GetTextExtentPoint32A(r->measure_dc, text, length, &sz);
return v2f32((F32)sz.cx, (F32)sz.cy);
F32 width = 0;
for (S32 i = 0; i < length; i++) {
if (FT_Load_Char(r->ft_face, (FT_ULong)(unsigned char)text[i], FT_LOAD_DEFAULT)) continue;
width += (F32)(r->ft_face->glyph->advance.x >> 6);
}
F32 height = (F32)(r->ft_face->size->metrics.height >> 6);
return v2f32(width, height);
}
////////////////////////////////
@@ -1107,8 +1053,8 @@ Renderer *renderer_create(RendererDesc *desc) {
if (!create_ui_pipeline(r)) goto fail;
if (!create_ui_buffers(r)) goto fail;
init_text_measurement(r);
if (!create_font_atlas(r, 15.0f)) goto fail;
init_freetype(r);
if (!create_font_atlas(r, 22.0f)) goto fail;
return r;
@@ -1132,8 +1078,8 @@ void renderer_destroy(Renderer *r) {
if (r->pipeline_state) r->pipeline_state->Release();
if (r->root_signature) r->root_signature->Release();
if (r->measure_font) DeleteObject(r->measure_font);
if (r->measure_dc) DeleteDC(r->measure_dc);
if (r->ft_face) FT_Done_Face(r->ft_face);
if (r->ft_lib) FT_Done_FreeType(r->ft_lib);
cleanup_render_targets(r);
@@ -1504,7 +1450,9 @@ void renderer_set_clear_color(Renderer *r, F32 cr, F32 cg, F32 cb) {
}
void renderer_set_font_scale(Renderer *r, F32 scale) {
F32 target_size = 15.0f * scale;
// Build atlas at the largest font size used in the UI (22px clock display)
// so all smaller sizes scale down (crisper) rather than up (blurry).
F32 target_size = 22.0f * scale;
if (fabsf(target_size - r->font_atlas_size) < 0.1f) return;
wait_for_pending(r);
if (r->font_texture) { r->font_texture->Release(); r->font_texture = nullptr; }