diff --git a/.claude/settings.local.json b/.claude/settings.local.json index ae5a06d..01fe938 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -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)" ] } } diff --git a/src/renderer/renderer_dx12.cpp b/src/renderer/renderer_dx12.cpp index a9a7e44..5c032b0 100644 --- a/src/renderer/renderer_dx12.cpp +++ b/src/renderer/renderer_dx12.cpp @@ -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); } }