From a571d2d999ea11ec5b62cc4314a6b74b9cbbe7a9 Mon Sep 17 00:00:00 2001 From: Mid <> Date: Mon, 1 Dec 2025 23:58:55 +0200 Subject: [PATCH] Move to runtime generated SDF fonts GlyphCache is introduced as a layer that handles the bin packing with a shelf heuristic. SDF rendering is handled in the fixed-function pipeline using a simple alpha test. The shader version also applies a little smoothing. k3Batch had to be updated to add a minimum alpha parameter. --- src/glyphcache/glca.h | 109 +++++++++++++++++++++++++++------ src/immdraw.h | 4 +- src/k3.c | 31 ++++++---- src/k3batch.c | 33 +++++++--- src/k3batch.h | 2 +- src/k3font.c | 136 +++++++++++++++++++----------------------- src/k3font.h | 45 +++++--------- 7 files changed, 211 insertions(+), 149 deletions(-) diff --git a/src/glyphcache/glca.h b/src/glyphcache/glca.h index 5591f33..8016dde 100644 --- a/src/glyphcache/glca.h +++ b/src/glyphcache/glca.h @@ -95,6 +95,7 @@ typedef struct GlyphCache { GlyphCacheRow *rows; size_t item_count; + size_t item_capacity; GlyphCacheGlyph *items; void *userdata; @@ -117,6 +118,13 @@ void glca_set_time(GlyphCache *gc, uint64_t time); #endif #ifdef GLCA_IMPLEMENTATION +static uint32_t glca_hash(uint32_t x) { + x = ((x >> 16) ^ x) * 0x45d9f3bu; + x = ((x >> 16) ^ x) * 0x45d9f3bu; + x = (x >> 16) ^ x; + return x; +} + int glca_init(GlyphCache *gc, FT_Face face, size_t total_width, size_t total_height, void *userdata, GlyphCacheSetImage set_image, GlyphCacheGetImage get_image, GlyphCacheFillCustomData fill_custom_data) { memset(gc, 0, sizeof(*gc)); gc->face = face; @@ -126,6 +134,8 @@ int glca_init(GlyphCache *gc, FT_Face face, size_t total_width, size_t total_hei gc->set_image = set_image; gc->get_image = get_image; gc->fill_custom_data = fill_custom_data; + gc->item_capacity = 128; + gc->items = calloc(gc->item_capacity, sizeof(*gc->items)); return 0; } @@ -185,18 +195,22 @@ make_new: return row; } -static int comparator(const void *A, const void *B) { - uint32_t cpa = *(uint32_t*) A; - uint32_t cpb = *(uint32_t*) B; - - if(cpa == cpb) { - return 0; - } - - return cpa > cpb ? 1 : -1; -} GlyphCacheGlyph *glca_get_noupdate(GlyphCache *gc, uint32_t codepoint) { - return bsearch(&codepoint, gc->items, gc->item_count, sizeof(*gc->items), comparator); + uint32_t i = glca_hash(codepoint); + for(size_t probe = 0; probe < 5; probe++) { + i &= gc->item_capacity - 1; + + if(gc->items[i].codepoint == 0 && gc->items[i].w == 0) { + break; + } + + if(gc->items[i].codepoint == codepoint) { + return &gc->items[i]; + } + + i++; + } + return NULL; } GlyphCacheGlyph *glca_get(GlyphCache *gc, uint32_t codepoint) { GlyphCacheGlyph *glyph = glca_get_noupdate(gc, codepoint); @@ -209,6 +223,10 @@ GlyphCacheGlyph *glca_get(GlyphCache *gc, uint32_t codepoint) { void glca_try_evict(GlyphCache *gc) { uint64_t min_time = gc->current_time, max_time = 0; for(size_t i = 0; i < gc->item_count; i++) { + if(gc->items[i].codepoint == 0) { + continue; + } + if(min_time > gc->items[i].last_use) { min_time = gc->items[i].last_use; } @@ -246,13 +264,51 @@ void glca_try_evict(GlyphCache *gc) { gcr->width -= glyph->w; gcr->entry_count--; - size_t glyph_index = glyph - gc->items; - memmove(glyph, glyph + 1, sizeof(*glyph) * (gc->item_count - glyph_index - 1)); - gc->item_count--; - glyph = NULL; + glyph->codepoint = 0; } } } + +static void glca_expand(GlyphCache *gc, size_t shift) { + size_t new_capacity = gc->item_capacity << shift; + GlyphCacheGlyph *newarray = calloc(new_capacity, sizeof(*newarray)); + + for(size_t idx = 0; idx < gc->item_capacity; idx++) { + + if(gc->items[idx].codepoint == 0) { + continue; + } + + uint32_t i = glca_hash(gc->items[idx].codepoint); + size_t probe; + for(probe = 0; probe < 5; probe++) { + i &= new_capacity - 1; + + if(newarray[i].codepoint == 0) { + newarray[i] = gc->items[idx]; + break; + } + + i++; + } + + if(probe == 5) { + goto failed_to_expand; + } + + } + + free(gc->items); + + gc->item_capacity = new_capacity; + gc->items = newarray; + + return; +failed_to_expand: + free(newarray); + glca_expand(gc, shift + 1); +} + GlyphCacheGlyph *glca_request(GlyphCache *gc, uint32_t codepoint) { GlyphCacheGlyph *glyph = glca_get(gc, codepoint); if(glyph) { @@ -282,8 +338,24 @@ GlyphCacheGlyph *glca_request(GlyphCache *gc, uint32_t codepoint) { gcr->entries = realloc(gcr->entries, sizeof(*gcr->entries) * (gcr->entry_count + 1)); gcr->entries[gcr->entry_count++] = codepoint; - gc->items = realloc(gc->items, sizeof(*gc->items) * (gc->item_count + 1)); - gc->items[gc->item_count++] = (GlyphCacheGlyph) { + glyph = NULL; + while(1) { + uint32_t i = glca_hash(codepoint); + for(size_t probe = 0; probe < 5; probe++) { + i &= gc->item_capacity - 1; + if(gc->items[i].codepoint == 0) { + glyph = &gc->items[i]; + break; + } + i++; + } + if(glyph) { + break; + } + glca_expand(gc, 1); + } + + *glyph = (GlyphCacheGlyph) { .codepoint = codepoint, .x = x, .y = y, @@ -292,10 +364,9 @@ GlyphCacheGlyph *glca_request(GlyphCache *gc, uint32_t codepoint) { }; #ifdef GLCA_CUSTOM_GLYPH_DATA if(gc->fill_custom_data) { - gc->fill_custom_data(gc, gc->face->glyph, &gc->items[gc->item_count - 1]); + gc->fill_custom_data(gc, gc->face->glyph, glyph); } #endif - qsort(gc->items, gc->item_count, sizeof(*gc->items), comparator); uint8_t *buf = malloc(w * h); for(int y = 0; y < h; y++) { diff --git a/src/immdraw.h b/src/immdraw.h index b9eb845..ec3282f 100644 --- a/src/immdraw.h +++ b/src/immdraw.h @@ -10,7 +10,7 @@ static inline void immdraw_fill_rect(int16_t x, int16_t y, int16_t w, int16_t h, k3BatchAdd(NULL, (struct k3RectF) {0, 0, 1, 1}, (struct k3RectF) { x, GameWndH - y - h, w, h - }, 0, (vec4) {r, g, b, a}, borderRadius); + }, 0, (vec4) {r, g, b, a}, borderRadius, 0); } static inline void immdraw_font_draw(struct k3Font *font, int16_t x, int16_t y, int16_t w, float sz, size_t len, const char *txt, int alignment, float r, float g, float b, float a) { @@ -26,7 +26,7 @@ static inline void immdraw_font_size(struct k3Font *font, float sz, const char * static inline void immdraw_image_draw(k3MImageData *data, int16_t x, int16_t y, int16_t w, int16_t h, float r, float g, float b, float a) { struct k3Tex *tex = *data; - k3BatchAdd(tex, (struct k3RectF) {0, 0, 1, 1}, (struct k3RectF) {x, GameWndH - y - h, w, h}, 0, (vec4) {r, g, b, a}, 0); + k3BatchAdd(tex, (struct k3RectF) {0, 0, 1, 1}, (struct k3RectF) {x, GameWndH - y - h, w, h}, 0, (vec4) {r, g, b, a}, 0, 0); } static int16_t crop_aabb[4] = {-1, -1, -1, -1}; diff --git a/src/k3.c b/src/k3.c index 18a1ced..0094029 100644 --- a/src/k3.c +++ b/src/k3.c @@ -624,6 +624,8 @@ void k3TexUpdate(struct k3Tex *tex, enum k3TexType type, int index, uint16_t wid } if(compressed && TextureOfflineCompressor) { + k3Log(k3_DEBUG, "Compressing texture..."); + size_t len; data = TextureOfflineCompressor(data, width, height, externalFmt, intype, &len); @@ -643,6 +645,8 @@ void k3TexUpdate(struct k3Tex *tex, enum k3TexType type, int index, uint16_t wid tex->szZ = 0; tex->glInternalFormat = internalFmt; + tex->glExternalFormat = externalFmt; + tex->glInType = intype; } uint32_t k3TexSzX(struct k3Tex *this) { return this->szX; @@ -1132,8 +1136,8 @@ static void disable_glsl_bones(struct k3Mdl *mdl, struct k3GLSLP *p) { GLint a0; GLint a1; - a0 = glGetAttribLocationARB(p->handle, "a_boneids"); - a1 = glGetAttribLocationARB(p->handle, "a_boneweights"); + a0 = (glGetAttribLocationARB ? glGetAttribLocationARB : glGetAttribLocation)(p->handle, "a_boneids"); + a1 = (glGetAttribLocationARB ? glGetAttribLocationARB : glGetAttribLocation)(p->handle, "a_boneweights"); if(a0 != -1) { if(!k3IsCore) { @@ -1419,6 +1423,7 @@ static void forward_subpass(mat4 projection, mat4 view, int transparent, int lig struct k3Mat *lastMaterial = NULL; int lastAdditive = -1; int lastDepthwrite = -1; + int lastNocull = -1; for(size_t rble = rbleStart; rble < rbleEnd; rble++) { struct k3Mdl *mdl = renderQueue[rble].mdl; @@ -1478,6 +1483,15 @@ static void forward_subpass(mat4 projection, mat4 view, int transparent, int lig } } + if(lastNocull != mat->passes[0].nocull) { + lastNocull = mat->passes[0].nocull; + if(lastNocull) { + glDisable(GL_CULL_FACE); + } else { + glEnable(GL_CULL_FACE); + } + } + if(glslp) { if(lastGLSLP != glslp) { if(k3IsCore) @@ -1498,10 +1512,8 @@ static void forward_subpass(mat4 projection, mat4 view, int transparent, int lig setup_glsl_model_uniforms(glslp, modelmat); - if(mat != lastMaterial) { - bind_mat_textures(mat, 0); - bind_shadow_texture(mat->passes[0].unitsUsed); - } + bind_mat_textures(mat, 0); + bind_shadow_texture(mat->passes[0].unitsUsed); } else if(!k3IsCore) { if(lastGLSLP && GLAD_GL_ARB_shading_language_100) { glUseProgramObjectARB(0); @@ -1557,12 +1569,6 @@ static void forward_subpass(mat4 projection, mat4 view, int transparent, int lig } lastMaterial = mat; - - if(mat->passes[0].nocull) { - glDisable(GL_CULL_FACE); - } else { - glEnable(GL_CULL_FACE); - } if(!k3IsCore) { if(mat->passes[0].alphatest) { glEnable(GL_ALPHA_TEST); @@ -1634,7 +1640,6 @@ void k3PassForward(mat4 projection, mat4 cam) { glEnable(GL_BLEND); glEnable(GL_MULTISAMPLE); - glEnable(GL_CULL_FACE); glFrontFace(GL_CCW); if(!k3IsCore) { diff --git a/src/k3batch.c b/src/k3batch.c index 92baac5..a5419e4 100644 --- a/src/k3batch.c +++ b/src/k3batch.c @@ -14,6 +14,7 @@ struct S { static struct k3Tex *activeTex; static float activeBorderRadius; +static float activeMinAlpha; static size_t SCount, SCapacity; static struct S *S; @@ -48,6 +49,7 @@ void k3BatchInit() { "uniform sampler2D u_tex;\n" "uniform float u_texuse;\n" "uniform float u_borderradius;\n" + "uniform float u_minalpha;\n" "in vec2 v_uv;\n" "in vec4 v_color;\n" "in vec2 v_size;\n" @@ -60,7 +62,11 @@ void k3BatchInit() { " if(length(c) > u_borderradius) {\n" " discard;\n" " }\n" - " fragcolor = mix(vec4(1, 1, 1, 1), texture2D(u_tex, v_uv), u_texuse) * v_color;\n" + " vec4 col = texture2D(u_tex, v_uv);\n" + " if(u_minalpha > 0.0) {\n" + " col.a = clamp((col.a - u_minalpha + 0.1) / 0.1, 0.0, 1.0);\n" + " }\n" + " fragcolor = mix(vec4(1, 1, 1, 1), col, u_texuse) * v_color;\n" "}\n" , NULL), NULL); @@ -70,11 +76,12 @@ void k3BatchInit() { } } -void k3BatchAdd(struct k3Tex *tex, struct k3RectF src, struct k3RectF dst, float rot, vec4 color, float borderRadius) { - if(activeTex != tex || borderRadius != activeBorderRadius) { +void k3BatchAdd(struct k3Tex *tex, struct k3RectF src, struct k3RectF dst, float rot, vec4 color, float borderRadius, float minAlpha) { + if(activeTex != tex || borderRadius != activeBorderRadius || minAlpha != activeMinAlpha) { k3BatchFlush(); activeTex = tex; activeBorderRadius = borderRadius; + activeMinAlpha = minAlpha; } if(SCount == SCapacity) { @@ -118,9 +125,6 @@ void k3BatchFlush() { glDisable(GL_FRAGMENT_PROGRAM_ARB); } - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glActiveTexture(GL_TEXTURE0); if(activeTex) { @@ -132,6 +136,9 @@ void k3BatchFlush() { } if(k3IsCore) { + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + GLuint handle = GL_FROM_K3GLSL(coreProg); glUseProgram(handle); @@ -214,8 +221,8 @@ void k3BatchFlush() { glBufferDataARB(GL_ARRAY_BUFFER_ARB, SCount * 60 * sizeof(*farr), farr, GL_DYNAMIC_DRAW); glUniform1f(k3ProgramGetUId(coreProg, "u_texuse"), !!activeTex); - glUniform1f(k3ProgramGetUId(coreProg, "u_borderradius"), activeBorderRadius); + glUniform1f(k3ProgramGetUId(coreProg, "u_minalpha"), activeMinAlpha); GLint aPos = glGetAttribLocation(handle, "a_pos"); GLint aUv = glGetAttribLocation(handle, "a_uv"); @@ -243,6 +250,16 @@ void k3BatchFlush() { glUseProgramObjectARB(0); } + if(activeMinAlpha) { + glDisable(GL_BLEND); + } else { + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + } + + glEnable(GL_ALPHA_TEST); + glAlphaFunc(GL_GREATER, activeMinAlpha); + glBegin(GL_QUADS); struct S *s = S; for(size_t i = 0; i < SCount; i++) { @@ -264,6 +281,8 @@ void k3BatchFlush() { } glEnd(); + glDisable(GL_ALPHA_TEST); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); } diff --git a/src/k3batch.h b/src/k3batch.h index adb2e6c..43b50c3 100644 --- a/src/k3batch.h +++ b/src/k3batch.h @@ -10,7 +10,7 @@ struct k3RectF { float h; }; -void k3BatchAdd(struct k3Tex *tex, struct k3RectF src, struct k3RectF dst, float rot, vec4 color, float borderRadius); +void k3BatchAdd(struct k3Tex *tex, struct k3RectF src, struct k3RectF dst, float rot, vec4 color, float borderRadius, float minAlpha); void k3BatchFlush(); void k3BatchSetResolution(float w, float h); diff --git a/src/k3font.c b/src/k3font.c index 119a04d..42bbb5d 100644 --- a/src/k3font.c +++ b/src/k3font.c @@ -3,72 +3,60 @@ #include"k3batch.h" #include #include"gl.h" +#include + +#define GLCA_IMPLEMENTATION +#include"glyphcache/glca.h" + +#include"k3_internal.h" + +static _Thread_local FT_Library ftlib; struct k3Font *k3FontCreate() { + if(!ftlib) { + assert(FT_Init_FreeType(&ftlib) == 0); + FT_Property_Set(ftlib, "sdf", "spread", &(int) {64}); + } + struct k3Font *ret = calloc(sizeof(*ret), 1); return ret; } -static int cmpglyph(const void *a, const void *b) { - return *(const uint32_t*) a - *(const uint32_t*) b; +void my_set_image(struct GlyphCache *gc, int x, int y, int w, int h, const void *buf) { + struct k3Font *this = gc->userdata; + + for(size_t row = 0; row < h; row++) { + memcpy(&this->atlasCPU[this->atlasSize * (y + row) + x], buf + w * row, w); + } + + //k3TexUpdateSub(this->atlas, 0, x, y, w, h, buf); + k3TexUpdate(this->atlas, k3_ALPHA, 0, this->atlasSize, this->atlasSize, this->atlasCPU); } - -int k3FontLoad(struct k3Font *this, const uint8_t *buf, size_t len, k3FontTexLoader texldr) { - if(*(uint32_t*) buf != 0x03464D42) { - return 0; +void my_get_image(struct GlyphCache *gc, int x, int y, int w, int h, void *buf) { + struct k3Font *this = gc->userdata; + + for(size_t row = 0; row < h; row++) { + memcpy(buf + w * row, &this->atlasCPU[this->atlasSize * (y + row)], w); } +} +void my_fill_custom_data(struct GlyphCache *gc, FT_GlyphSlot slot, GlyphCacheGlyph *glyph) { + glyph->data.xAdvance = slot->metrics.horiAdvance / 64.f; + glyph->data.xOffset = slot->metrics.horiBearingX / 64.f; + glyph->data.yOffset = slot->metrics.horiBearingY / 64.f; +} +int k3FontLoad(struct k3Font *this, const char *fn) { + this->atlasSize = k3TexSzMax() / 4; + this->atlas = k3TexCreate(k3_ALPHA); + this->atlasCPU = malloc(this->atlasSize * this->atlasSize); + k3TexUpdate(this->atlas, k3_ALPHA, 0, this->atlasSize, this->atlasSize, NULL); - const uint8_t *end = buf + len; + this->fontPixelSize = this->atlasSize / 16; - buf += 4; + assert(FT_New_Face(ftlib, fn, 0, &this->ftface) == 0); + FT_Select_Charmap(this->ftface, FT_ENCODING_UNICODE); + FT_Set_Pixel_Sizes(this->ftface, this->fontPixelSize, 0); - uint16_t pages = 0; - - while(buf + 5 < end) { - uint8_t blockType = *buf; - uint32_t blockSize = *(uint32_t*) (buf + 1); - buf += 5; - - if(blockType == 1) { //Info block - buf += 14; - while(*buf) buf++; - buf++; - } else if(blockType == 2) { //Common block - this->lineScale = 1.f / *(uint16_t*) buf; - buf += 2; - this->baseline = *(uint16_t*) buf; - buf += 2; - this->texW = *(uint16_t*) buf; - buf += 2; - this->texH = *(uint16_t*) buf; - buf += 2; - pages = *(uint16_t*) buf; - buf += 7; - } else if(blockType == 3) { //Pages block - if(pages == 0) return 0; - - this->pageCount = pages; - this->pages = malloc(sizeof(*this->pages) * this->pageCount); - - for(size_t i = 0; i < this->pageCount; i++) { - this->pages[i] = texldr(this, buf); - buf += strlen(buf) + 1; - } - } else if(blockType == 4) { //Chars block - size_t num = blockSize / 20; - - this->glyphs = calloc(sizeof(*this->glyphs), this->glyphCount = num); - - memcpy(this->glyphs, buf, num * 20); - - qsort(this->glyphs, num, sizeof(*this->glyphs), cmpglyph); - - buf += blockSize; - } else if(blockType == 5) { //Kerning block - // Ignore kerning for now - buf += blockSize; - } - } + glca_init(&this->glca, this->ftface, this->atlasSize, this->atlasSize, this, my_set_image, my_get_image, my_fill_custom_data); return 1; } @@ -117,16 +105,16 @@ void k3FontSz(struct k3Font *this, float sz, const char *txt, float wall, struct y += sz; } - struct k3FontGlyph *g = k3FontGetGlyph(this, cp); + struct GlyphCacheGlyph *g = glca_request(&this->glca, cp); if(!g) continue; - if(x + g->width * this->lineScale * sz > wall) { + if(x + sz * g->w / this->fontPixelSize > wall) { x = 0; y += sz; } - x += g->xadvance * this->lineScale * sz; + x += sz * g->data.xAdvance / this->fontPixelSize; maxX = fmaxf(maxX, x); } @@ -153,16 +141,16 @@ void k3FontDraw(struct k3Font *this, float xStart, float yStart, float sz, const break; } - struct k3FontGlyph *g = k3FontGetGlyph(this, cp2); + struct GlyphCacheGlyph *g = glca_request(&this->glca, cp2); if(g) { - if(lineWidth + g->width * this->lineScale * sz > wall) { + if(lineWidth + sz * g->w / this->fontPixelSize > wall) { break; } } lineLength++; if(g) { - lineWidth += g->xadvance * this->lineScale * sz; + lineWidth += sz * g->data.xAdvance / this->fontPixelSize; } } @@ -182,24 +170,24 @@ void k3FontDraw(struct k3Font *this, float xStart, float yStart, float sz, const for(size_t i = 0; i < lineLength; i++) { uint32_t cp = read_utf8(&txt); - struct k3FontGlyph *g = k3FontGetGlyph(this, cp); + struct GlyphCacheGlyph *g = glca_request(&this->glca, cp); if(!g) continue; - struct k3Tex *tex = this->pages[g->page]; - size_t texW = this->texW; - size_t texH = this->texH; + struct k3Tex *tex = this->atlas; + size_t texW = k3TexSzX(tex); + size_t texH = k3TexSzY(tex); k3BatchAdd(tex, - (struct k3RectF) {(float) (g->x + 0.5) / texW, (float) (g->y + 0.5) / texH, (float) (g->width - 1) / texW, (float) (g->height - 1) / texH}, + (struct k3RectF) {(float) (g->x + 0.5) / texW, (float) (g->y - 0.5) / texH, (float) (g->w - 1) / texW, (float) (g->h - 1) / texH}, (struct k3RectF) { - x + g->xoffset * this->lineScale * sz, - y + ((-g->height - g->yoffset) * this->lineScale + 1) * sz, - g->width * this->lineScale * sz, - g->height * this->lineScale * sz - }, 0, color, 0); + x + sz * g->data.xOffset / this->fontPixelSize, + y - sz * ((g->h - g->data.yOffset) / (float) this->fontPixelSize - 0.5f), + sz * g->w / this->fontPixelSize, + sz * g->h / this->fontPixelSize + }, 0, color, 0, k3IsCore ? 0.5 : 0.45); - x += g->xadvance * this->lineScale * sz; + x += sz * g->data.xAdvance / this->fontPixelSize; } // If the line break was caused directly by a LF, skip over it for next line @@ -211,7 +199,3 @@ void k3FontDraw(struct k3Font *this, float xStart, float yStart, float sz, const } k3BatchFlush(); } - -struct k3FontGlyph *k3FontGetGlyph(struct k3Font *this, uint32_t cp) { - return bsearch(&cp, this->glyphs, this->glyphCount, sizeof(*this->glyphs), cmpglyph); -} diff --git a/src/k3font.h b/src/k3font.h index 3e196d9..2b18257 100644 --- a/src/k3font.h +++ b/src/k3font.h @@ -4,49 +4,32 @@ #include"k3batch.h" #include -#define k3_FONT_ALIGN_LEFT 0 -#define k3_FONT_ALIGN_CENTER 1 -#define k3_FONT_ALIGN_RIGHT 2 - -struct k3FontGlyph { - uint32_t cp; - uint16_t x; - uint16_t y; - uint16_t width; - uint16_t height; - int16_t xoffset; - int16_t yoffset; - uint16_t xadvance; - uint8_t page; - uint8_t chnl; -}; +#define GLCA_CUSTOM_GLYPH_DATA +typedef struct GlyphCacheGlyphData { + float xAdvance; + float xOffset; + float yOffset; +} GlyphCacheGlyphData; +#include"glyphcache/glca.h" struct k3Font { void *ud; - float lineScale; + int atlasSize; + uint8_t *atlasCPU; + struct k3Tex *atlas; - uint16_t baseline; - - uint16_t texW, texH; - - size_t glyphCount; - struct k3FontGlyph *glyphs; - - size_t pageCount; - struct k3Tex **pages; + int fontPixelSize; + FT_Face ftface; + GlyphCache glca; }; -typedef struct k3Tex*(*k3FontTexLoader)(struct k3Font*, const char *name); - struct k3Font *k3FontCreate(); -int k3FontLoad(struct k3Font*, const uint8_t *buf, size_t len, k3FontTexLoader); +int k3FontLoad(struct k3Font*, const char *fn); void k3FontSz(struct k3Font*, float sz, const char *txt, float wall, struct k3RectF *ret); void k3FontDraw(struct k3Font*, float x, float y, float sz, const char *txt, float wall, int alignment, vec4 color); -struct k3FontGlyph *k3FontGetGlyph(struct k3Font*, uint32_t cp); - static inline int k3UTF8Encode(uint32_t cp, uint8_t ret[static 4]) { if(cp < 0x80) { ret[0] = cp;