Smooth font rendering

This commit is contained in:
2026-03-03 02:39:20 -05:00
parent 322366719e
commit b523e233cb
2 changed files with 44 additions and 30 deletions

View File

@@ -2,7 +2,8 @@
"permissions": {
"allow": [
"Bash(cd /c/Users/mta/projects/autosample && ./nob.exe debug 2>&1)",
"Bash(cd /c/Users/mta/projects/autosample && rm -f build/*.pdb build/*.obj build/*.ilk && ./nob.exe debug 2>&1)"
"Bash(cd /c/Users/mta/projects/autosample && rm -f build/*.pdb build/*.obj build/*.ilk && ./nob.exe debug 2>&1)",
"Bash(cd /c/Users/mta/projects/autosample && ./nob.exe 2>&1)"
]
}
}

View File

@@ -407,12 +407,17 @@ static void ensure_measure_font(Renderer *r, F32 font_size) {
}
static bool create_font_atlas(Renderer *r, F32 font_size) {
const int SS = 2; // supersample factor
F32 render_size = font_size * SS;
int render_w = FONT_ATLAS_W * SS;
int render_h = FONT_ATLAS_H * SS;
r->font_atlas_size = font_size;
// Create a GDI bitmap to render glyphs into
// Create a GDI bitmap to render glyphs at supersampled resolution
HDC dc = CreateCompatibleDC(nullptr);
HFONT font = CreateFontW(
-(int)(font_size + 0.5f), 0, 0, 0,
-(int)(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,
@@ -420,16 +425,16 @@ static bool create_font_atlas(Renderer *r, F32 font_size) {
);
SelectObject(dc, font);
// Get line height
// Get line height (at supersample scale, divide back to get 1x)
TEXTMETRICW tm;
GetTextMetricsW(dc, &tm);
r->font_line_height = (F32)tm.tmHeight;
r->font_line_height = (F32)tm.tmHeight / SS;
// Create DIB section for rendering
// Create DIB section at supersampled resolution
BITMAPINFO bmi = {};
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmi.bmiHeader.biWidth = FONT_ATLAS_W;
bmi.bmiHeader.biHeight = -FONT_ATLAS_H; // top-down
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;
@@ -440,13 +445,13 @@ static bool create_font_atlas(Renderer *r, F32 font_size) {
SelectObject(dc, font);
// Clear to black
memset(dib_bits, 0, FONT_ATLAS_W * FONT_ATLAS_H * 4);
memset(dib_bits, 0, render_w * render_h * 4);
SetTextColor(dc, RGB(255, 255, 255));
SetBkMode(dc, TRANSPARENT);
// Render each glyph
int pen_x = 1, pen_y = 1;
// Render each glyph at supersampled resolution
int pen_x = SS, pen_y = SS;
int row_height = 0;
for (int i = 0; i < GLYPH_COUNT; i++) {
@@ -454,41 +459,49 @@ static bool create_font_atlas(Renderer *r, F32 font_size) {
SIZE ch_size = {};
GetTextExtentPoint32A(dc, &ch, 1, &ch_size);
int gw = ch_size.cx + 2; // 1px padding each side
int gh = ch_size.cy + 2;
int gw = ch_size.cx + 2 * SS; // padding scaled by SS
int gh = ch_size.cy + 2 * SS;
if (pen_x + gw >= FONT_ATLAS_W) {
pen_x = 1;
pen_y += row_height + 1;
if (pen_x + gw >= render_w) {
pen_x = SS;
pen_y += row_height + SS;
row_height = 0;
}
if (pen_y + gh >= FONT_ATLAS_H) break; // out of space
if (pen_y + gh >= render_h) break; // out of space
TextOutA(dc, pen_x + 1, pen_y + 1, &ch, 1);
TextOutA(dc, pen_x + SS, pen_y + SS, &ch, 1);
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 + gw) / (F32)FONT_ATLAS_W;
r->glyphs[i].v1 = (F32)(pen_y + gh) / (F32)FONT_ATLAS_H;
r->glyphs[i].w = (F32)gw;
r->glyphs[i].h = (F32)gh;
r->glyphs[i].x_advance = (F32)ch_size.cx;
// 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 + 1;
pen_x += gw + SS;
}
GdiFlush();
// Convert BGRA bitmap to single-channel alpha
// 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 (int y = 0; y < FONT_ATLAS_H; y++) {
for (int x = 0; x < FONT_ATLAS_W; x++) {
int idx = (y * FONT_ATLAS_W + x);
U8 r_val = src[idx * 4 + 2]; // BGRA layout: R=2
atlas_data[idx] = r_val;
int sum = 0;
for (int sy = 0; sy < SS; sy++) {
for (int sx = 0; sx < SS; sx++) {
int src_idx = ((y * SS + sy) * render_w + (x * SS + sx)) * 4;
sum += src[src_idx + 2]; // R channel from BGRA
}
}
float a = (float)sum / (float)(SS * SS * 255);
a = powf(a, 0.55f);
atlas_data[y * FONT_ATLAS_W + x] = (U8)(a * 255.0f + 0.5f);
}
}