first version of font atlas generator complete

This commit is contained in:
matthew 2025-08-14 12:20:43 +10:00
parent 2780f811f0
commit 63c942294c
6 changed files with 68 additions and 31 deletions

Binary file not shown.

View File

@ -10,6 +10,7 @@ import math;
import core.stdc.math : cosf, sinf; import core.stdc.math : cosf, sinf;
import std.algorithm.sorting; import std.algorithm.sorting;
import fonts; import fonts;
import main;
f32 g_DELTA; f32 g_DELTA;
@ -109,13 +110,7 @@ InitGame(PlatformWindow* window)
u8[16*16*4] white_tex; u8[16*16*4] white_tex;
white_tex[] = u8.max; white_tex[] = u8.max;
u8[] atlas = LoadAssetData(&g.frame_arena, "textures/atlas.png"); CreateImageView(&g.rd, &g.font_tex, FONT_ATLAS_TEST.atlas.width, FONT_ATLAS_TEST.atlas.height, 4, FONT_ATLAS_TEST.data);
int width, height, has_ch;
auto img = stbi_load_from_memory(atlas.ptr, cast(int)atlas.length, &width, &height, &has_ch, 4);
assert(width == FONT_ATLAS.width && height == FONT_ATLAS.height && has_ch == 1, "atlas height and width do not match");
u8[] img_slice = img[0 .. width * height * 4];
CreateImageView(&g.rd, &g.font_tex, FONT_ATLAS.width, FONT_ATLAS.height, 4, img_slice);
// TODO: fix buffer overflow between two textures // TODO: fix buffer overflow between two textures
//CreateImageView(&g.rd, &g.default_tex, 16, 16, 4, white_tex); //CreateImageView(&g.rd, &g.default_tex, 16, 16, 4, white_tex);
@ -177,7 +172,7 @@ Cycle(Game* g)
Reset(&g.frame_arena); Reset(&g.frame_arena);
DrawRect(g, 500.0, 500.0, 800.0, 800.0, Vec4(0.2, 0.3, 0.7, 1.0)); DrawRect(g, 500.0, 500.0, 800.0, 800.0, Vec4(0.2, 0.3, 0.7, 1.0));
DrawText(g, 200.0, 200.0, FONT_ATLAS.size, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"); DrawText(g, 200.0, 200.0, 128.0, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ");
BeginFrame(&g.rd); BeginFrame(&g.rd);
@ -323,19 +318,20 @@ void
DrawText(Game* g, f32 x, f32 y, f32 px, string str) DrawText(Game* g, f32 x, f32 y, f32 px, string str)
{ {
f32 x_pos = x; f32 x_pos = x;
f32 scale = px / FONT_ATLAS_TEST.atlas.size;
foreach(ch; str) foreach(ch; str)
{ {
foreach(glyph; FONT_ATLAS.glyphs) foreach(glyph; FONT_ATLAS_TEST.atlas.glyphs)
{ {
if (ch == glyph.ch) if (ch == glyph.ch)
{ {
UIVertex* v = g.ui_vertex_buf.ptr + g.ui_count; UIVertex* v = g.ui_vertex_buf.ptr + g.ui_count;
f32 r = px * glyph.plane_right; f32 r = glyph.plane_right * scale;
f32 l = px * glyph.plane_left; f32 l = glyph.plane_left * scale;
f32 w = r - l; f32 w = r - l;
f32 h = px * (glyph.plane_bottom - glyph.plane_top); f32 h = (glyph.plane_bottom - glyph.plane_top) * scale;
f32 y_pos = px * glyph.plane_bottom; f32 y_pos = glyph.plane_top * scale;
v.dst_start.x = x_pos + l; v.dst_start.x = x_pos + l;
v.dst_start.y = y + h - y_pos; v.dst_start.y = y + h - y_pos;
@ -349,9 +345,11 @@ DrawText(Game* g, f32 x, f32 y, f32 px, string str)
v.col = Vec4(1.0, 1.0, 1.0, 1.0); v.col = Vec4(1.0, 1.0, 1.0, 1.0);
x_pos += px * glyph.advance; x_pos += glyph.advance * scale;
AddUIIndices(g); AddUIIndices(g);
break;
} }
} }
} }

View File

@ -1,4 +1,4 @@
public import includes; import includes;
import std.stdio; import std.stdio;
import aliases; import aliases;
import core.memory; import core.memory;
@ -13,6 +13,8 @@ import font;
import alloc; import alloc;
import assets; import assets;
FontAtlasBuf FONT_ATLAS_TEST;
// TODO: // TODO:
// 1. Determine how to better handle inputs // 1. Determine how to better handle inputs
// 2. Set up multisampling // 2. Set up multisampling
@ -20,14 +22,14 @@ import assets;
void main(string[] argv) void main(string[] argv)
{ {
PlatformWindow window = CreateWindow("Video Game", 1920, 1080); Arena arena = CreateArena(MB(32));
Game g = InitGame(&window);
Arena arena = CreateArena(MB(8));
InitFreeType(); InitFreeType();
u8[] font_data = LoadAssetData(&arena, "fonts/NuberNextCondensed-DemiBold.otf"); u8[] font_data = LoadAssetData(&arena, "fonts/NuberNextCondensed-DemiBold.otf");
FontFace font = OpenFont(font_data); FontFace font = OpenFont(font_data);
FontAtlasBuf atlas = CreateAtlas(&arena, font, 32.0, 256); FONT_ATLAS_TEST = CreateAtlas(&arena, font, 128.0, 2048);
PlatformWindow window = CreateWindow("Video Game", 1920, 1080);
Game g = InitGame(&window);
while (true) while (true)
{ {

View File

@ -1685,7 +1685,7 @@ CreateGraphicsPipeline(Vulkan* vk, GfxPipelineInfo* build_info)
cullMode: VK_CULL_MODE_BACK_BIT, cullMode: VK_CULL_MODE_BACK_BIT,
polygonMode: VK_POLYGON_MODE_FILL, polygonMode: VK_POLYGON_MODE_FILL,
lineWidth: 1.0, lineWidth: 1.0,
frontFace: VK_FRONT_FACE_CLOCKWISE, frontFace: VK_FRONT_FACE_COUNTER_CLOCKWISE,
}; };
VkPipelineMultisampleStateCreateInfo multisample_info = { VkPipelineMultisampleStateCreateInfo multisample_info = {

View File

@ -41,10 +41,10 @@ void main()
vec2 src_pos = (Vertices[gl_VertexIndex] * src_half_size + src_center); vec2 src_pos = (Vertices[gl_VertexIndex] * src_half_size + src_center);
vec2 uvs[4] = vec2[4]( vec2 uvs[4] = vec2[4](
vec2(in_src_start.x, in_src_end.y),
vec2(in_src_start.x, in_src_start.y), vec2(in_src_start.x, in_src_start.y),
vec2(in_src_end.x, in_src_end.y), vec2(in_src_start.x, in_src_end.y),
vec2(in_src_end.x, in_src_start.y) vec2(in_src_end.x, in_src_start.y),
vec2(in_src_end.x, in_src_end.y)
); );
FragData.color = in_col; FragData.color = in_col;

View File

@ -18,6 +18,15 @@ InitFreeType()
FT_Init_FreeType(&FT_LIB); FT_Init_FreeType(&FT_LIB);
} }
void
CloseFreeType()
{
if (FT_LIB)
{
FT_Done_FreeType(FT_LIB);
}
}
FontFace FontFace
OpenFont(u8[] data) OpenFont(u8[] data)
{ {
@ -36,12 +45,17 @@ CloseFont(FontFace font)
} }
FontAtlasBuf FontAtlasBuf
CreateAtlas(Arena* arena, FontFace font, f32 size, u64 dimension) CreateAtlas(Arena* arena, FontFace font, f32 size, u32 dimension)
{ {
assert(dimension >= 128, "Dimension must be at least 128"); assert(dimension >= 128, "Dimension must be at least 128");
FontAtlasBuf atlas = { FontAtlasBuf atlas = {
data: AllocArray!(u8)(arena, dimension * dimension * 4), data: AllocArray!(u8)(arena, dimension * dimension * 4),
atlas: {
size: size,
width: dimension,
height: dimension,
},
}; };
// TODO: proper packing algorithm // TODO: proper packing algorithm
@ -88,11 +102,15 @@ CreateAtlas(Arena* arena, FontFace font, f32 size, u64 dimension)
current_h = 0; current_h = 0;
count = 0; count = 0;
u32 font_w = font.size.metrics.x_ppem;
u32 font_h = font.size.metrics.y_ppem;
char_code = FT_Get_First_Char(font, &index); char_code = FT_Get_First_Char(font, &index);
while (index != 0) while (index != 0)
{ {
FT_Load_Char(font, char_code, cast(FT_Int32)FT_LOAD_RENDER); FT_Load_Char(font, char_code, cast(FT_Int32)FT_LOAD_RENDER);
FT_GlyphSlot glyph = font.glyph;
FT_Bitmap* bmp = &font.glyph.bitmap; FT_Bitmap* bmp = &font.glyph.bitmap;
i32 top = font.glyph.bitmap_top; i32 top = font.glyph.bitmap_top;
i32 left = font.glyph.bitmap_left; i32 left = font.glyph.bitmap_left;
@ -103,29 +121,48 @@ CreateAtlas(Arena* arena, FontFace font, f32 size, u64 dimension)
max_w = 0; max_w = 0;
} }
i32 x, y;
foreach(r; 0 .. bmp.rows) foreach(r; 0 .. bmp.rows)
{ {
i32 y = cast(i32)(max_h + r); y = cast(i32)(max_h + r);
foreach(c; 0 .. bmp.width) foreach(c; 0 .. bmp.width)
{ {
i32 x = max_w + left + c; x = max_w + c;
u64 offset = (y*dimension + x) * 4; u64 offset = (y*dimension + x) * 4;
atlas.data[offset+0] = 255; atlas.data[offset+0] = bmp.buffer[r*bmp.pitch + c];
atlas.data[offset+1] = 255; atlas.data[offset+1] = bmp.buffer[r*bmp.pitch + c];
atlas.data[offset+2] = 255; atlas.data[offset+2] = bmp.buffer[r*bmp.pitch + c];
atlas.data[offset+3] = bmp.buffer[r*bmp.pitch + c]; atlas.data[offset+3] = 255;
} }
} }
Glyph* g = atlas.atlas.glyphs.ptr + count;
g.ch = cast(dchar)char_code;
g.advance = cast(f32)(glyph.advance.x >> 6);
g.plane_left = cast(f32)left;
g.plane_right = g.plane_left + bmp.width;
g.plane_top = cast(f32)top;
g.plane_bottom = g.plane_top + bmp.rows;
g.atlas_top = max_h;
g.atlas_left = max_w;
g.atlas_bottom = max_h + bmp.rows;
g.atlas_right = max_w + bmp.width;
max_w += bmp.width; max_w += bmp.width;
current_h = bmp.rows > current_h ? bmp.rows : current_h; current_h = bmp.rows > current_h ? bmp.rows : current_h;
char_code = FT_Get_Next_Char(font, char_code, &index); char_code = FT_Get_Next_Char(font, char_code, &index);
count += 1;
} }
} }
return atlas; return atlas;
} }