implement slug data packing

This commit is contained in:
Matthew 2026-04-17 14:18:56 +10:00
parent 53dbb8bf34
commit 91d7c75ca4
6 changed files with 5686 additions and 17 deletions

17
alloc.d
View File

@ -314,24 +314,25 @@ AllocAlign(Arena* arena, u64 size, u64 alignment)
{ {
void* ptr = null; void* ptr = null;
u64 pool_alloc_size = size;
if(pool_alloc_size > arena.def_size)
{
pool_alloc_size += arena.def_size;
}
uintptr mem_pos, current, offset; uintptr mem_pos, current, offset;
ArenaPool* node = arena.first; ArenaPool* node = arena.first;
while (true) while (true)
{ {
if(node == null) if(node == null)
{ {
if(size > arena.def_size) AddArenaPool(arena, Max(pool_alloc_size, arena.def_size));
{
size += arena.def_size;
}
AddArenaPool(arena, Max(size, arena.def_size));
node = arena.first; node = arena.first;
} }
mem_pos = cast(uintptr)node.mem; mem_pos = cast(uintptr)node.mem;
current = mem_pos + node.pos; current = mem_pos + node.pos;
offset = AlignPow2(current, alignment) - mem_pos; offset = AlignPow2(current, alignment) - mem_pos;
if(offset+size <= node.length) if(offset+size <= node.length)
{ {
@ -341,7 +342,7 @@ AllocAlign(Arena* arena, u64 size, u64 alignment)
node = node.next; node = node.next;
} }
ptr = &node.mem[offset]; ptr = &node.mem[offset];
node.pos = offset+size; node.pos = offset+size;
return ptr; return ptr;

View File

@ -20,6 +20,7 @@
#ifndef NO_STBI #ifndef NO_STBI
# include "external/stb/stb_image.h" # include "external/stb/stb_image.h"
# include "external/stb/stb_image_write.h" # include "external/stb/stb_image_write.h"
# include "external/stb/stb_truetype.h"
#endif #endif
#define CGLM_FORCE_DEPTH_ZERO_TO_ONE #define CGLM_FORCE_DEPTH_ZERO_TO_ONE

3
external/stb/stb.c vendored
View File

@ -1,2 +1,5 @@
#define STB_IMAGE_IMPLEMENTATION #define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h" #include "stb_image.h"
#define STB_TRUETYPE_IMPLEMENTATION
#include "stb_truetype.h"

5079
external/stb/stb_truetype.h vendored Normal file

File diff suppressed because it is too large Load Diff

538
fonts.d
View File

@ -6,6 +6,120 @@ import dlib.util;
import dlib.alloc; import dlib.alloc;
import dlib.math; import dlib.math;
import std.math.traits : isNaN; import std.math.traits : isNaN;
import std.math : Floor = floor;
import std.algorithm.comparison : clamp;
import std.algorithm.sorting;
const u64 FONT_MAX_BANDS = 8;
const u64 FONT_TEX_WIDTH = 4096;
struct SlugCurveRef
{
u32 index;
f32 sort_key;
}
struct SlugBandEntry
{
u32[] indices;
u32 count;
}
struct SlugGlyphBandData
{
SlugBandEntry[FONT_MAX_BANDS][2] bands;
U8Vec2 band_counts;
}
struct SlugCurve
{
Vec2 p1, p2, p3;
}
struct SlugRect
{
Vec2 min, max;
}
struct SlugGlyph
{
SlugRect rect;
IVec2 bearing;
IVec2 size;
Vec2 band_scale;
Vec2 band_offset;
i32 glyph_index;
i32 advance;
i32 curve_start;
i32 curve_count;
U16Vec2 coords;
U8Vec2 max_bands;
}
struct SlugFontInfo
{
SlugCurve[] curves;
f32[] curve_texel_data;
u32[] band_texel_data;
SlugGlyphBandData[] glyph_bands;
SlugFont base;
alias base this;
}
struct SlugFont
{
SlugGlyph[128] glyphs;
f32 scale;
f32 line_height;
i32 ascent;
i32 descent;
i32 line_gap;
}
struct SlugVertex
{
union
{
Vec4 pos;
struct
{
Vec2 vertex_coords;
Vec2 normal;
};
};
union
{
Vec4 tex;
struct
{
Vec2 em_space_pos;
U16Vec2 glyph_data_coords;
struct max_bands
{
u8 x, pad, y, e;
};
}
};
union
{
Vec4 jac;
struct
{
f32 p00, p01, p10, p11;
};
};
union
{
Vec4 band;
struct
{
Vec4 scale;
Vec4 offset;
};
};
Vec4 color;
}
struct FontAtlas struct FontAtlas
{ {
@ -260,5 +374,429 @@ CreateAtlas(Arena* arena, FontFace font, u32 size, UVec2 atlas_dim)
return atlas; return atlas;
} }
void
BuildFontGlyph(Arena* arena, u32 index, u32 glyph_index, SlugFontInfo* font_info, u32* curve_index, stbtt_fontinfo* stb_font_info)
{
SlugGlyph* glyph = &font_info.glyphs[index];
glyph.glyph_index = glyph_index;
glyph.curve_start = *curve_index;
stbtt_vertex* vertices;
u32 vertex_count = stbtt_GetGlyphShape(stb_font_info, glyph_index, &vertices);
if(vertex_count > 0)
{
f32 cx = 0.0, cy = 0.0;
foreach(i; 0 .. vertex_count)
{
stbtt_vertex* vtx = &vertices[i];
f32 x = cast(f32)(vtx.x);
f32 y = cast(f32)(vtx.y);
switch(vtx.type)
{
case STBTT_vmove:
{
cx = x;
cy = y;
} break;
case STBTT_vline:
{
f32 dx = x-cx;
f32 dy = y-cy;
if(fabs(dx) < 0.1 && fabs(dy) < 0.1)
{
cx = x;
cy = y;
break;
}
f32 length = sqrt(dx*dx + dy*dy);
f32 nx = -dy/length*0.05;
f32 ny = -dx/length*0.05;
SlugCurve* curve = &font_info.curves[*curve_index];
*curve = SlugCurve(
Vec2(cx, cy),
Vec2((cx+x) * 0.5 + nx, (cy+y) * 0.5 + ny),
Vec2(x, y),
);
cx = x;
cy = y;
*curve_index += 1;
} break;
case STBTT_vcurve:
{
SlugCurve* curve = &font_info.curves[*curve_index];
*curve = SlugCurve(
Vec2(cx, cy),
Vec2(vtx.cx, vtx.cy),
Vec2(x, y),
);
*curve_index += 1;
} break;
case STBTT_vcubic:
{
f32 cx1 = cast(f32)(vtx.cx );
f32 cy1 = cast(f32)(vtx.cy );
f32 cx2 = cast(f32)(vtx.cx1);
f32 cy2 = cast(f32)(vtx.cy1);
Vec2 m01 = Vec2(cx+cx1, cy+cy1 ) * 0.5;
Vec2 m12 = Vec2(cx1+cx2, cy1+cy2) * 0.5;
Vec2 m23 = Vec2(cx2+x, cy2+y ) * 0.5;
Vec2 m012 = (m01+m12) * 0.5;
Vec2 m123 = (m12+m23) * 0.5;
Vec2 mid = (m012+m123) * 0.5;
font_info.curves[(*curve_index)+0] = SlugCurve( Vec2(cx, cy), m01, mid );
font_info.curves[(*curve_index)+1] = SlugCurve( mid, m123, Vec2(x, y) );
cx = x;
cy = y;
*curve_index += 2;
} break;
default: break;
}
}
stbtt_FreeShape(stb_font_info, vertices);
}
glyph.curve_count = *curve_index - glyph.curve_start;
i32 advance_width, left_side_bearing;
stbtt_GetGlyphHMetrics(stb_font_info, glyph_index, &advance_width, &left_side_bearing);
glyph.advance = advance_width;
IVec2 p0, p1;
if(!stbtt_GetGlyphBox(stb_font_info, glyph_index, &p0.x, &p0.y, &p1.x, &p1.y))
{
p0 = p1 = 0;
}
glyph.bearing = p0;
glyph.size = p1-p0;
glyph.rect.min = Vec2(p0.x, p0.y);
glyph.rect.max = Vec2(p1.x, p1.y);
glyph.coords = 0;
glyph.max_bands = 0;
glyph.band_scale = 0;
glyph.band_offset = 0;
SlugBuildBands(arena, glyph_index, font_info, FONT_MAX_BANDS);
}
bool
LoadFontCurves(Arena* arena, SlugFontInfo* font_info, u8[] font_file_data)
{
bool result = true;
u32 curve_count;
stbtt_fontinfo stb_font_info;
if(!stbtt_InitFont(&stb_font_info, font_file_data.ptr, stbtt_GetFontOffsetForIndex(font_file_data.ptr, 0)))
{
Errf("Failed to load font data");
result = false;
}
if(result)
{
foreach(i; 0 .. font_info.glyphs.length)
{
curve_count += SlugCountCurvesForGlyph(stbtt_FindGlyphIndex(&stb_font_info, cast(i32)i), &stb_font_info);
}
font_info.curves = Alloc!(SlugCurve)(arena, curve_count+1);
font_info.glyph_bands = Alloc!(SlugGlyphBandData)(arena, font_info.glyphs.length);
f32 font_scale = stbtt_ScaleForPixelHeight(&stb_font_info, 200.0);
stbtt_GetFontVMetrics(&stb_font_info, &font_info.ascent, &font_info.descent, &font_info.line_gap);
font_info.scale = font_scale;
font_info.line_height = cast(f32)(font_info.ascent-font_info.descent);
u32 curve_index = 1;
u32 curve_texel_count;
foreach(i; 0 .. font_info.glyphs.length)
{
BuildFontGlyph(arena, cast(u32)i, stbtt_FindGlyphIndex(&stb_font_info, cast(int)i), font_info, &curve_index, &stb_font_info);
curve_texel_count += font_info.glyphs[i].curve_count*2;
}
u32 curve_texel_height = clamp(cast(u32)((curve_texel_count+FONT_TEX_WIDTH-1) / FONT_TEX_WIDTH), 1, -1);
f32[] curve_texel_data = Alloc!(f32)(arena, FONT_TEX_WIDTH*curve_texel_height*4);
u32[] curve_start_indices = Alloc!(u32)(arena, font_info.glyphs.length);
curve_index = 1;
foreach(i; 0 .. font_info.glyphs.length)
{
SlugGlyph* glyph = &font_info.glyphs[i];
curve_start_indices[i] = curve_index;
SlugCurve[] glyph_curves = font_info.curves[glyph.curve_start .. glyph.curve_start+glyph.curve_count];
foreach(j; 0 .. glyph.curve_count)
{
SlugCurve* curve = &glyph_curves[j];
UVec2 t0 = UVec2(curve_index%FONT_TEX_WIDTH, curve_index/FONT_TEX_WIDTH);
u32 offset = cast(u32)((t0.y*FONT_TEX_WIDTH + t0.x) * 4);
curve_texel_data[offset .. offset+4] = [curve.p1.x, curve.p1.y, curve.p2.x, curve.p2.y];
curve_index += 1;
UVec2 t1 = UVec2(curve_index%FONT_TEX_WIDTH, curve_index/FONT_TEX_WIDTH);
offset = cast(u32)((t1.y*FONT_TEX_WIDTH + t1.x) * 4);
curve_texel_data[offset .. offset+4] = [curve.p3.x, curve.p3.y, 0.0, 0.0];
curve_index += 1;
}
}
u32 band_total_count;
foreach(i; 0 .. font_info.glyphs.length)
{
SlugGlyph* glyph = &font_info.glyphs[i];
SlugGlyphBandData* band_data = &font_info.glyph_bands[i];
u32 band_total = band_data.band_counts[0] + band_data.band_counts[1];
u32 padding = cast(u32)(FONT_TEX_WIDTH - (band_total_count%FONT_TEX_WIDTH));
if(padding < band_total && padding < FONT_TEX_WIDTH)
{
band_total_count += padding;
}
band_total_count += band_total;
foreach(axis; 0 .. 2)
{
foreach(j; 0 .. band_data.band_counts[axis])
{
band_total_count += band_data.bands[axis][j].count;
}
}
}
u32 band_texel_height = clamp(cast(u32)((band_total_count+FONT_TEX_WIDTH-1) / FONT_TEX_WIDTH), 1U, u32.max);
u32[] band_texel_data = Alloc!(u32)(arena, FONT_TEX_WIDTH*band_texel_height*4);
u32 band_index;
foreach(i; 0 .. font_info.glyphs.length)
{
SlugGlyph* glyph = &font_info.glyphs[i];
SlugGlyphBandData* band_data = &font_info.glyph_bands[i];
u32 glyph_curve_start = curve_start_indices[i];
u32 band_total = cast(u32)(band_data.band_counts.x + band_data.band_counts.y);
u32 next_band_index = band_index%FONT_TEX_WIDTH;
if(next_band_index+band_total > FONT_TEX_WIDTH)
{
band_index = cast(u32)((band_index/FONT_TEX_WIDTH+1) * FONT_TEX_WIDTH);
}
glyph.coords = U16Vec2(band_index%FONT_TEX_WIDTH, band_index/FONT_TEX_WIDTH);
u32 glyph_start = band_index;
SlugCurve[] curves = font_info.curves[glyph.curve_start .. glyph.curve_start+glyph.curve_count];
foreach(axis; 0 .. 2)
{
foreach(j; 0 .. band_data.band_counts[axis])
{
SlugSortBand(arena, &band_data.bands[axis][j], curves, axis);
}
}
u32 band_offset = band_total;
u32[] offsets = Alloc!(u32)(arena, band_total);
foreach(j; 0 .. band_data.band_counts.x)
{
offsets[j] = band_offset;
band_offset += band_data.bands[0][j].count;
}
foreach(j; 0 .. band_data.band_counts.y)
{
offsets[band_data.band_counts.x+j] = band_offset;
band_offset += band_data.bands[1][j].count;
}
foreach(j; 0 .. band_total)
{
u32 tl = glyph_start+j;
u32 di = cast(u32)((tl/FONT_TEX_WIDTH*FONT_TEX_WIDTH + tl%FONT_TEX_WIDTH) * 4);
u32 index = j;
u32 count = 0;
if(index < cast(u32)band_data.band_counts.x)
{
count = band_data.bands[0][index].count;
}
else
{
index -= band_data.band_counts.x;
count = band_data.bands[1][index].count;
}
band_texel_data[di .. di+2] = [count, offsets[j]];
}
foreach(j; 0 .. band_total)
{
u32 axis = j < band_data.band_counts.x ? 0 : 1;
u32 index = j < band_data.band_counts.x ? j : j - band_data.band_counts.x;
SlugBandEntry* band = &band_data.bands[axis][index];
u32 ls = glyph_start+offsets[j];
foreach(k; 0 .. band.count)
{
u32 ct = curve_start_indices[i] + band.indices[k]*2;
u32 tl = ls+k;
u32 di = cast(u32)((tl/FONT_TEX_WIDTH*FONT_TEX_WIDTH + tl%FONT_TEX_WIDTH) * 4);
band_texel_data[di .. di+2] = [ct%FONT_TEX_WIDTH, ct/FONT_TEX_WIDTH];
}
}
band_index = glyph_start+band_offset;
}
}
return result;
}
void
SlugSortBand(Arena* arena, SlugBandEntry* entry, SlugCurve[] curves, u32 axis)
{
if(entry.count <= 1) return;
SlugCurveRef[] refs = Alloc!(SlugCurveRef)(arena, entry.count);
foreach(i; 0 .. entry.count)
{
SlugCurve* curve = &curves[entry.indices[i]];
f32 mx = Max(curve.p1[axis], curve.p2[axis], curve.p3[axis]);
refs[i] = SlugCurveRef(entry.indices[i], mx);
}
sort!("a.sort_key < b.sort_key")(refs);
foreach(i; 0 .. entry.count)
{
entry.indices[i] = refs[i].index;
}
}
u32
SlugCountCurvesForGlyph(u32 glyph_index, stbtt_fontinfo* stb_font_info)
{
u32 result;
stbtt_vertex* vertices;
u32 vertex_count = stbtt_GetGlyphShape(stb_font_info, glyph_index, &vertices);
foreach(i; 0 .. vertex_count)
{
switch(vertices[i].type)
{
case STBTT_vline, STBTT_vcurve: result += 1; break;
case STBTT_vcubic: result += 2; break;
default: break;
}
}
if(vertex_count > 0)
{
stbtt_FreeShape(stb_font_info, vertices);
}
return result;
}
void
SlugBuildBands(Arena* arena, u32 glyph_index, SlugFontInfo* font_info, u32 band_count)
{
SlugGlyphBandData* band_data = &font_info.glyph_bands[glyph_index];
SlugGlyph* glyph = &font_info.glyphs[glyph_index];
Vec2 size = glyph.rect.max - glyph.rect.min;
band_data.band_counts = cast(u8)band_count;
for(u32 i = 0; i < band_count; i += 1)
{
band_data.bands[0][i].indices = Alloc!(u32)(arena, glyph.curve_count);
band_data.bands[1][i].indices = Alloc!(u32)(arena, glyph.curve_count);
}
glyph.max_bands = cast(u8)band_count;
glyph.band_scale = Vec2(
size.x > 0.0 ? cast(f32)(band_count)/size.x : 0.0,
size.y > 0.0 ? cast(f32)(band_count)/size.y : 0.0,
);
glyph.band_offset = -glyph.rect.min * glyph.band_scale;
for(u32 i = 0; i < glyph.curve_count; i += 1)
{
SlugCurve* curve = &font_info.curves[glyph.curve_start+i];
Vec2 curve_min = Vec2(
Min(curve.p1.x, curve.p2.x, curve.p3.x),
Min(curve.p1.y, curve.p2.y, curve.p3.y),
);
Vec2 curve_max = Vec2(
Max(curve.p1.x, curve.p2.x, curve.p3.x),
Min(curve.p1.y, curve.p2.y, curve.p3.y),
);
if(size.y > 0.0)
{
u32 b0 = cast(u32)clamp(floor(curve_min.y * glyph.band_scale.y + glyph.band_offset.y), 0.0, cast(f32)(band_count-1));
u32 b1 = cast(u32)clamp(floor(curve_max.y * glyph.band_scale.y + glyph.band_offset.y), 0.0, cast(f32)(band_count-1));
for(u32 j = b0; j <= b1; j += 1)
{
SlugBandEntry* entry = &band_data.bands[0][j];
entry.indices[entry.count++] = i;
}
}
if(size.x > 0.0)
{
u32 b0 = cast(u32)clamp(Floor(curve_min.x * glyph.band_scale.x + glyph.band_offset.x), 0.0, cast(f32)(band_count-1));
u32 b1 = cast(u32)clamp(Floor(curve_max.x * glyph.band_scale.x + glyph.band_offset.x), 0.0, cast(f32)(band_count-1));
for(u32 j = b0; j <= b1; j += 1)
{
SlugBandEntry* entry = &band_data.bands[1][j];
entry.indices[entry.count++] = i;
}
}
}
}

65
math.d
View File

@ -16,15 +16,28 @@ import std.format;
import std.stdio; import std.stdio;
T T
Max(T)(T a, T b) _MinOrMaxInner(T, bool max, Args...)(T first, Args args)
{ {
return a > b ? a : b; T value = first;
static foreach(i; 0 .. args.length)
{
static if(max) if(cast(T)(args[i]) > value) value = cast(T)(args[i]);
static if(!max) if(cast(T)(args[i]) < value) value = cast(T)(args[i]);
}
return value;
} }
T T
Min(T)(T a, T b) Max(T, Args...)(T first, Args args)
{ {
return a < b ? a : b; return _MinOrMaxInner!(T, true, Args)(first, args);
}
T
Min(T, Args...)(T first, Args args)
{
return _MinOrMaxInner!(T, false, Args)(first, args);
} }
T T
@ -94,11 +107,22 @@ struct Vector(T, int N)
{ {
static if(args.length == 1) static if(args.length == 1)
{ {
opAssign!(Args[0])(args[0]); enum bool is_array = isArray!(Args[0]);
enum bool is_assignable = isAssignable!(T, Args[0]);
enum bool types_are_numeric = TypesAreNumeric!(T, Args[0]);
static if(is_array || is_assignable || types_are_numeric)
{
opAssign!(Args[0])(args[0]);
}
else static assert(false, "Invalid Vector constructor");
} }
else static if(args.length == N) else static if(args.length == N)
{ {
mixin(GenerateLoop!("v[@] = args[@];", N)()); static foreach(i; 0 .. N)
{
v[i] = cast(T)args[i];
}
} }
else static if(args.length == 2 && N == 4) else static if(args.length == 2 && N == 4)
{ {
@ -113,15 +137,18 @@ struct Vector(T, int N)
} }
} }
ref Vector opAssign(U)(U x) if(is(U: T) || isAssignable!(T, U)) ref Vector opAssign(U)(U x) if(is(U: T) || isAssignable!(T, U) || TypesAreNumeric!(T, U))
{ {
mixin(GenerateLoop!("v[@] = x;", N)()); v[] = cast(T)x;
return this; return this;
} }
ref Vector opAssign(U)(U u) if(isStaticArray!(U) && U.length == N && isAssignable!(T, typeof(*U.init.ptr))) ref Vector opAssign(U)(U u) if(isStaticArray!(U) && U.length == N && isAssignable!(T, typeof(*U.init.ptr)))
{ {
mixin(GenerateLoop!("v[@] = u[@];", N)()); static foreach(i; 0 .. N)
{
v[i] = cast(T)u[i];
}
return this; return this;
} }
@ -309,6 +336,11 @@ struct Vector(T, int N)
enum bool IsConvertible = (!is(T : Vector)) && is(typeof({ T x; Vector v = x; }())); enum bool IsConvertible = (!is(T : Vector)) && is(typeof({ T x; Vector v = x; }()));
} }
template TypesAreNumeric(U, V)
{
enum bool TypesAreNumeric = isNumeric!(U) && isNumeric!(V);
}
template SwizzleIndex(char c) template SwizzleIndex(char c)
{ {
static if((c == 'x' || c == 'r') && N > 0) static if((c == 'x' || c == 'r') && N > 0)
@ -1223,4 +1255,19 @@ version(DLIB_TEST) unittest
v.r = 55; v.r = 55;
} }
} }
{
u32 max = Max(50, 33, 123.0);
assert(max == 123);
}
{
Vec2 v0 = 50U;
assert(fabsf(v0.x-50.0) < 0.0009 && fabsf(v0.y-50.0) < 0.0009);
U8Vec2 v1 = U8Vec2(55U);
assert(v1.x == 55 && v1.y == 55);
}
} }