start work on font atlas creation

This commit is contained in:
matthew 2025-08-13 07:45:00 +10:00
parent ead9c307bb
commit 2780f811f0
5 changed files with 154 additions and 7 deletions

View File

@ -11,11 +11,11 @@
"sourceFiles-windows": [],
"importPaths": ["src/gears", "src/codegen", "src/shared", "external/xxhash", "external/inteli"],
"sourcePaths": ["src/gears", "src/codegen", "src/shared", "external/xxhash", "external/inteli"],
"libs-linux": ["xcb", "X11", "X11-xcb", "vulkan", "stdc++", "xcb-xfixes"],
"libs-linux": ["xcb", "X11", "X11-xcb", "vulkan", "stdc++", "xcb-xfixes", "freetype"],
"libs-windows": [],
"preGenerateCommands-linux": ["./build.sh", "build/Packer"],
"preGenerateCommands-windows": [],
"dflags": ["-Xcc=-mno-sse"],
"dflags": ["-Xcc=-mno-sse", "-P-I/usr/include/freetype2"],
"dflags-dmd": ["-P=-DSTBI_NO_SIMD"]
},
{

View File

@ -116,7 +116,8 @@ InitGame(PlatformWindow* window)
u8[] img_slice = img[0 .. width * height * 4];
CreateImageView(&g.rd, &g.font_tex, FONT_ATLAS.width, FONT_ATLAS.height, 4, img_slice);
CreateImageView(&g.rd, &g.default_tex, 16, 16, 4, white_tex);
// TODO: fix buffer overflow between two textures
//CreateImageView(&g.rd, &g.default_tex, 16, 16, 4, white_tex);
WriteGUI(&g.rd, g.ui_desc_set, &g.font_tex);
@ -176,7 +177,7 @@ Cycle(Game* g)
Reset(&g.frame_arena);
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, 32.0, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ");
DrawText(g, 200.0, 200.0, FONT_ATLAS.size, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ");
BeginFrame(&g.rd);
@ -330,13 +331,15 @@ DrawText(Game* g, f32 x, f32 y, f32 px, string str)
{
UIVertex* v = g.ui_vertex_buf.ptr + g.ui_count;
f32 w = px * (glyph.plane_right - glyph.plane_left);
f32 r = px * glyph.plane_right;
f32 l = px * glyph.plane_left;
f32 w = r - l;
f32 h = px * (glyph.plane_bottom - glyph.plane_top);
f32 y_pos = px * glyph.plane_bottom;
v.dst_start.x = x_pos;
v.dst_start.x = x_pos + l;
v.dst_start.y = y + h - y_pos;
v.dst_end.x = x_pos + w;
v.dst_end.x = x_pos + w + l;
v.dst_end.y = y - y_pos;
v.src_start.x = glyph.atlas_left;

View File

@ -9,6 +9,10 @@ import core.simd;
import math;
import core.stdc.string : memcpy;
import font;
import alloc;
import assets;
// TODO:
// 1. Determine how to better handle inputs
// 2. Set up multisampling
@ -19,6 +23,12 @@ void main(string[] argv)
PlatformWindow window = CreateWindow("Video Game", 1920, 1080);
Game g = InitGame(&window);
Arena arena = CreateArena(MB(8));
InitFreeType();
u8[] font_data = LoadAssetData(&arena, "fonts/NuberNextCondensed-DemiBold.otf");
FontFace font = OpenFont(font_data);
FontAtlasBuf atlas = CreateAtlas(&arena, font, 32.0, 256);
while (true)
{
HandleEvents(&window);

131
src/shared/font.d Normal file
View File

@ -0,0 +1,131 @@
import aliases;
import includes;
import util;
import alloc;
FT_Library FT_LIB;
alias FontFace = FT_Face;
struct FontAtlasBuf
{
u8[] data;
FontAtlas atlas;
}
void
InitFreeType()
{
FT_Init_FreeType(&FT_LIB);
}
FontFace
OpenFont(u8[] data)
{
FontFace font;
FT_New_Memory_Face(FT_LIB, data.ptr, cast(FT_Long)data.length, 0, &font);
return font;
}
void
CloseFont(FontFace font)
{
if (font != null)
{
FT_Done_Face(font);
}
}
FontAtlasBuf
CreateAtlas(Arena* arena, FontFace font, f32 size, u64 dimension)
{
assert(dimension >= 128, "Dimension must be at least 128");
FontAtlasBuf atlas = {
data: AllocArray!(u8)(arena, dimension * dimension * 4),
};
// TODO: proper packing algorithm
if (font != null)
{
FT_Set_Pixel_Sizes(font, 0, cast(FT_UInt)((96.0/72.0) * size));
i64 f_ascent = cast(i64)(font.size.metrics.ascender >> 6);
i64 f_descent = cast(i64)(font.size.metrics.descender >> 6);
i64 f_height = cast(i64)(font.size.metrics.height >> 6);
u32 max_w = 0;
u32 max_h = 0;
u32 current_h = 0;
u32 count = 0;
FT_UInt index;
FT_ULong char_code = FT_Get_First_Char(font, &index);
while (index != 0)
{
FT_Load_Char(font, char_code, cast(FT_Int32)FT_LOAD_RENDER);
u32 bmp_w = font.glyph.bitmap.width;
u32 bmp_h = font.glyph.bitmap.rows;
if (max_w + bmp_w > dimension)
{
max_h += current_h;
max_w = 0;
}
assert(max_h < dimension, "Unable to pack atlas within dimensions");
max_w += bmp_w;
current_h = bmp_h > current_h ? bmp_h : current_h;
count += 1;
char_code = FT_Get_Next_Char(font, char_code, &index);
}
atlas.atlas.glyphs = AllocArray!(Glyph)(arena, count);
max_w = 0;
max_h = 0;
current_h = 0;
count = 0;
char_code = FT_Get_First_Char(font, &index);
while (index != 0)
{
FT_Load_Char(font, char_code, cast(FT_Int32)FT_LOAD_RENDER);
FT_Bitmap* bmp = &font.glyph.bitmap;
i32 top = font.glyph.bitmap_top;
i32 left = font.glyph.bitmap_left;
if (max_w + bmp.rows > dimension)
{
max_h += current_h;
max_w = 0;
}
foreach(r; 0 .. bmp.rows)
{
i32 y = cast(i32)(max_h + r);
foreach(c; 0 .. bmp.width)
{
i32 x = max_w + left + c;
u64 offset = (y*dimension + x) * 4;
atlas.data[offset+0] = 255;
atlas.data[offset+1] = 255;
atlas.data[offset+2] = 255;
atlas.data[offset+3] = bmp.buffer[r*bmp.pitch + c];
}
}
max_w += bmp.width;
current_h = bmp.rows > current_h ? bmp.rows : current_h;
char_code = FT_Get_Next_Char(font, char_code, &index);
}
}
return atlas;
}

View File

@ -6,6 +6,9 @@
# include <X11/Xlib.h>
# include <X11/keysym.h>
# include <X11/extensions/Xfixes.h>
# include <ft2build.h>
# include FT_FREETYPE_H
# include FT_GLYPH_H
#endif
#include <xmmintrin.h>