Compare commits

...

10 Commits

Author SHA1 Message Date
mid
56ecba05b1 change isnanf to isnan 2026-01-21 22:27:06 +02:00
mid
9160c8ce35 Forgot to add this 2026-01-18 13:42:50 +02:00
mid
2175d32da4 Many bug fixes 2026-01-18 13:42:43 +02:00
Mid
c44e58c4ac State machine bug fixed 2026-01-14 11:37:59 +02:00
Mid
63d641cc82 Texture API improvements
1. Selective mipmapping
2. Manual mipmap generation (actually produced better results at least
for alpha-tested foliage)
2026-01-14 11:27:03 +02:00
Mid
a571d2d999 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.
2025-12-01 23:58:55 +02:00
Mid
8207743308 Assign textures in depth-only pass (fixed state desync bug) 2025-12-01 23:55:17 +02:00
Mid
b532ccd68f Begin transition to dynamic glyph cache 2025-11-24 11:30:14 +02:00
Mid
6cbd201b63 Huge load
1. k3Update added, which must be called per frame.
2. Added GPU timers for profiling.
3. Added ARB_direct_state_access support because Mesa is being a bitch
again.
4. Cached uniform locations in an open-addressing hash table.
Unfortunately, I'm pretty sure there was no performance increase, at
least on my development machine, but it shouldn't hurt anywhere else.
2025-10-12 20:46:56 +03:00
Mid
2ebab9358d Shadowmap size limit 2025-10-12 20:43:17 +03:00
10 changed files with 959 additions and 304 deletions

388
src/glyphcache/glca.h Normal file
View File

@@ -0,0 +1,388 @@
/*
* This is a mostly standalone file that implements an SDF glyph cache.
* It can support any graphics backend as long as it can modify and load subtextures.
*
* It is SDF-only since this way it is less critical to support different font sizes.
*
* Example initialization:
* FT_Library ftlib;
* FT_Init_FreeType(&ftlib);
*
* FT_Property_Set(ftlib, "sdf", "spread", &(int) {24});
*
* FT_Face face;
* assert(FT_New_Face(ftlib, "my font file.ttf", 0, &face) == 0);
* FT_Select_Charmap(face, FT_ENCODING_UNICODE);
* FT_Set_Pixel_Sizes(face, 64, 0);
*
* void *userdata = NULL;
*
* GlyphCache gc = {};
* glca_init(&gc, face, 2048, 2048, userdata, my_set_image, my_get_image, my_fill_custom_data);
*
* my_fill_custom_data can be used in case you need glyph metrics.
* If GLCA_CUSTOM_GLYPH_DATA is not defined, my_fill_custom_data is unused.
*
* Example usage:
* // Should be called regularly (recommended once per frame)
* glca_set_time(&gc, get_time_now_somehow());
* ...
* // When you need to render text:
* GlyphCacheGlyph *glyph = glca_request(&gc, 'Ь');
* if(!glyph) {
* // Skip
* }
*
* Safety padding is added (in height) to each new row. It is by default
* zero but it is a good idea to experiment with other values.
*
* WARNING: GlyphCache relies on a few heuristics that assume the following:
* 1. Glyphs are roughly equal in size (true for text)
* 2. The atlas is reasonably larger in area than a single glyph (at least ~100x)
* If either does not hold, GlyphCache will perform AWFULLY.
*/
#ifndef GLCA_H
#define GLCA_H
#include<freetype/freetype.h>
#include<unistd.h>
#include<assert.h>
#include<stdint.h>
#include<stddef.h>
#include<stdbool.h>
#ifndef GLCA_CUSTOM_GLYPH_DATA
typedef struct GlyphCacheGlyphData {} GlyphCacheGlyphData;
#endif
typedef struct GlyphCacheGlyph {
uint32_t codepoint;
uint16_t x;
uint16_t y;
uint16_t w;
uint16_t h;
GlyphCacheGlyphData data;
uint64_t last_use;
} GlyphCacheGlyph;
typedef struct GlyphCacheRow {
size_t y;
size_t width;
size_t height;
size_t entry_count;
uint32_t *entries;
} GlyphCacheRow;
struct GlyphCache;
typedef void(*GlyphCacheSetImage)(struct GlyphCache *gc, int x, int y, int w, int h, const void *buf);
typedef void(*GlyphCacheGetImage)(struct GlyphCache *gc, int x, int y, int w, int h, void *buf);
typedef void(*GlyphCacheFillCustomData)(struct GlyphCache *gc, FT_GlyphSlot, GlyphCacheGlyph*);
typedef struct GlyphCache {
FT_Face face;
uint64_t current_time;
int safety_padding;
size_t total_width;
size_t total_height;
size_t row_count;
GlyphCacheRow *rows;
size_t item_count;
size_t item_capacity;
GlyphCacheGlyph *items;
void *userdata;
GlyphCacheSetImage set_image;
GlyphCacheGetImage get_image;
GlyphCacheFillCustomData fill_custom_data;
} GlyphCache;
int glca_init(GlyphCache*, FT_Face, size_t w, size_t h, void *userdata, GlyphCacheSetImage, GlyphCacheGetImage, GlyphCacheFillCustomData);
GlyphCacheGlyph *glca_get_noupdate(GlyphCache *gc, uint32_t codepoint);
GlyphCacheGlyph *glca_get(GlyphCache *gc, uint32_t codepoint);
GlyphCacheGlyph *glca_request(GlyphCache *gc, uint32_t codepoint);
void glca_try_evict(GlyphCache *gc);
void glca_set_safety_padding(GlyphCache *gc, int safety_padding);
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;
gc->total_width = total_width;
gc->total_height = total_height;
gc->userdata = userdata;
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;
}
// Returns row number for new glyph. If == -1, impossible.
static int choose_row(GlyphCache *gc, size_t w, size_t h) {
size_t y = 0;
if(gc->row_count == 0) {
goto make_new;
}
bool found = false;
int bestRow = -1;
int bestRowHDiff = 10000000;
for(int row = 0; row < gc->row_count; row++) {
y += gc->rows[row].height;
if(gc->total_width - gc->rows[row].width < w) {
// No width remains in row.
continue;
}
if(gc->rows[row].height < h) {
// Row too short.
continue;
}
int diff = gc->rows[row].height - h;
if(bestRowHDiff > diff) {
bestRowHDiff = diff;
bestRow = row;
found = true;
}
}
if(found) {
return bestRow;
}
make_new:
if(y + h > gc->total_height) {
return -1;
}
h += gc->safety_padding;
if(y + h > gc->total_height) {
h = gc->total_height - y;
}
int row = gc->row_count;
gc->rows = realloc(gc->rows, sizeof(*gc->rows) * (++gc->row_count));
memset(&gc->rows[row], 0, sizeof(*gc->rows));
gc->rows[row].y = y;
gc->rows[row].height = h;
return row;
}
GlyphCacheGlyph *glca_get_noupdate(GlyphCache *gc, uint32_t codepoint) {
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);
if(glyph) {
glyph->last_use = gc->current_time;
}
return glyph;
}
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;
}
if(max_time < gc->items[i].last_use) {
max_time = gc->items[i].last_use;
}
}
uint64_t threshold = max_time / 2 + min_time / 2;
for(size_t r = 0; r < gc->row_count; r++) {
GlyphCacheRow *gcr = &gc->rows[r];
size_t x_shift = 0;
for(size_t i = 0; i < gcr->entry_count;) {
GlyphCacheGlyph *glyph = glca_get_noupdate(gc, gcr->entries[i]);
if(x_shift) {
void *buf = malloc(glyph->w * glyph->h);
gc->get_image(gc, glyph->x, glyph->y, glyph->w, glyph->h, buf);
glyph->x -= x_shift;
gc->set_image(gc, glyph->x, glyph->y, glyph->w, glyph->h, buf);
free(buf);
}
if(glyph->last_use >= threshold) {
// Do not evict.
i++;
continue;
}
x_shift += glyph->w;
memmove(gcr->entries + i, gcr->entries + i + 1, sizeof(*gcr->entries) * (gcr->entry_count - i - 1));
gcr->width -= glyph->w;
gcr->entry_count--;
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) {
return glyph;
}
assert(FT_Load_Char(gc->face, codepoint, 0) == 0);
assert(FT_Render_Glyph(gc->face->glyph, FT_RENDER_MODE_NORMAL) == 0);
assert(FT_Render_Glyph(gc->face->glyph, FT_RENDER_MODE_SDF) == 0);
size_t w = gc->face->glyph->bitmap.width, h = gc->face->glyph->bitmap.rows;
int row = choose_row(gc, w, h);
if(row == -1) {
glca_try_evict(gc);
row = choose_row(gc, w, h);
if(row == -1) {
return NULL;
}
}
GlyphCacheRow *gcr = &gc->rows[row];
size_t x = gcr->width;
size_t y = gcr->y;
gcr->width += w;
gcr->entries = realloc(gcr->entries, sizeof(*gcr->entries) * (gcr->entry_count + 1));
gcr->entries[gcr->entry_count++] = codepoint;
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,
.w = w,
.h = h,
};
#ifdef GLCA_CUSTOM_GLYPH_DATA
if(gc->fill_custom_data) {
gc->fill_custom_data(gc, gc->face->glyph, glyph);
}
#endif
uint8_t *buf = malloc(w * h);
for(int y = 0; y < h; y++) {
memcpy(buf + y * w, gc->face->glyph->bitmap.buffer + y * gc->face->glyph->bitmap.pitch, w);
}
gc->set_image(gc, x, y, w, h, buf);
free(buf);
return glca_get(gc, codepoint);
}
void glca_set_safety_padding(GlyphCache *gc, int safety_padding) {
gc->safety_padding = safety_padding;
}
void glca_set_time(GlyphCache *gc, uint64_t time) {
gc->current_time = time;
}
#endif

View File

@@ -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};

553
src/k3.c

File diff suppressed because it is too large Load Diff

View File

@@ -22,9 +22,18 @@ enum k3TexType {
k3_RAWCOLOR,
};
enum k3TexParam {
k3_TEX_SET_MIPMAP_ALPHA,
k3_TEX_SET_MIPMAPPING_DISABLED,
};
#define k3_PARAM_STANDARD 0
#define k3_PARAM_MAX_DOWNSAMPLE 1
struct k3Tex;
struct k3Tex *k3TexCreate(enum k3TexType type);
int k3TexSetParameter(struct k3Tex*, enum k3TexParam, void*);
void k3TexUpdate(struct k3Tex*, enum k3TexType type, int index, uint16_t width, uint16_t height, void *data);
void k3TexUpdateSub(struct k3Tex*, int index, uint16_t x, uint16_t y, uint16_t width, uint16_t height, void *data);
uint32_t k3TexSzX(struct k3Tex*);
uint32_t k3TexSzY(struct k3Tex*);
uint32_t k3TexSzZ(struct k3Tex*);
@@ -40,7 +49,7 @@ struct k3GLSLG *k3ShaderGLSLG(const char *src_, const char*(*ldr)(const char *fn
struct k3GLSLP;
struct k3GLSLP *k3ProgramGLSL(struct k3GLSLV*, struct k3GLSLF*, struct k3GLSLG*);
uint16_t k3ProgramGetUId(struct k3GLSLP*, const char *key);
int16_t k3ProgramGetUId(struct k3GLSLP*, const char *key);
struct k3ARBVP;
struct k3ARBVP *k3ProgramARBVP(const char *src);
@@ -68,7 +77,7 @@ struct k3Mat {
} arbfp;
struct {
struct k3GLSLP *hp;
#ifdef k3_IRREGULAR_SHADOWS
struct k3GLSLP *hpIrreg1;
struct k3GLSLP *hpIrreg2;
@@ -97,7 +106,7 @@ struct k3Mat {
char additive;
char transparent;
char nocull;
char alphatest;
float alphatest;
char depthwrite;
} passes[1];
};
@@ -159,6 +168,7 @@ void k3StorageUnref(struct k3Storage*);
struct k3Mdl;
struct k3Mdl *k3MdlCreate(size_t verts, size_t indices, size_t boneCount, vec3 *pos, uint8_t *nrm, float *uvs, uint8_t *cols, uint8_t *boneids, uint16_t *boneweights, uint16_t *inds, mat4 *invBind, uint8_t *boneParents);
void k3MdlUpdatePos(struct k3Mdl *mdl, vec3 *pos);
void k3MdlUpdateNrm(struct k3Mdl *mdl, uint8_t *nrm);
void k3MdlAddMesh(struct k3Mdl*, struct k3Mat*, uint32_t idxStart, uint32_t idxNumber);
struct k3Mesh *k3MdlGetMeshes(struct k3Mdl*, size_t *count);
void k3MdlAddAnim(struct k3Mdl*, struct k3AnimationFountain*);
@@ -185,7 +195,7 @@ void k3BatchClear();
void k3PassForward(mat4 projection, mat4 cam);
void k3PassDepthOnly(mat4 projection, mat4 cam, int clear, int cull);
void k3PassShadowmap(mat4 projection, mat4 cam, struct k3Offscreen *offscr);
void k3PassShadowmap(mat4 projection, mat4 cam, struct k3Offscreen *offscr, int minimumSubdivision);
struct k3Offscreen;
struct k3Offscreen *k3OffscreenCreateMultisampled(struct k3Tex *diffuse, struct k3Tex *depth, uint8_t samples);
@@ -205,7 +215,7 @@ int k3CubemapTraditional(struct k3Tex*, mat4 proj, mat4 cam);
void k3SetTime(float t);
enum k3LogLevel {
k3_DEBUG, k3_INFO, k3_WARN, k3_ERR
k3_TRACE, k3_DEBUG, k3_INFO, k3_WARN, k3_ERR
};
typedef void(*k3LogCallback)(enum k3LogLevel, const char *str, size_t len);
void k3SetLogCallback(k3LogCallback);

View File

@@ -7,12 +7,13 @@
#include<cglm/vec2.h>
#include<cglm/frustum.h>
#include<cglm/cam.h>
#include<string.h>
#define GL_FROM_K3TEX(k3t) ((k3t) ? (k3t)->tex : 0)
#define GL_FROM_K3MARCHER(k3m) ((GLuint) (uintptr_t) (k3m))
#define GL_FROM_K3ARBVP(k3m) ((GLuint) (uintptr_t) (k3m))
#define GL_FROM_K3ARBFP(k3m) ((GLuint) (uintptr_t) (k3m))
#define GL_FROM_K3GLSL(k3m) ((GLuint) (uintptr_t) (k3m))
#define GL_FROM_K3GLSL(k3m) (((struct k3GLSLP*) k3m)->handle)
extern bool k3IsCore;
@@ -27,10 +28,8 @@ struct k3Tex {
GLuint glInType;
bool mipmap;
// Asynchronous decoding
uint8_t deferredRemaining;
uint8_t *deferredData[6];
struct k3Tex *deferredNext;
bool mipmapForceDisabled;
int mipmapAlphaMode;
};
struct k3Storage {
@@ -84,3 +83,52 @@ struct k3Mdl {
const char *debugname;
};
struct k3GLSLP {
GLhandleARB handle;
size_t ucount;
char **uname;
GLint *uloc;
};
struct k3Timer {
GLuint qStart;
GLuint qEnd;
char name[64];
};
extern struct k3Timer *k3Timers;
extern size_t k3TimerCount;
static inline struct k3Timer k3StartTimer(char *name) {
struct k3Timer t = {};
if(!GLAD_GL_ARB_timer_query) {
return t;
}
glGenQueries(2, (GLuint*) &t);
strncpy(t.name, name, sizeof(t.name));
glQueryCounter(t.qStart, GL_TIMESTAMP);
if(GLAD_GL_KHR_debug) {
glPushDebugGroup(GL_DEBUG_SOURCE_APPLICATION, 0, -1, name);
}
return t;
}
static inline void k3EndTimer(struct k3Timer t) {
if(!GLAD_GL_ARB_timer_query) {
return;
}
glQueryCounter(t.qEnd, GL_TIMESTAMP);
if(GLAD_GL_KHR_debug) {
glPopDebugGroup();
}
k3Timers = realloc(k3Timers, sizeof(*k3Timers) * (k3TimerCount + 1));
k3Timers[k3TimerCount++] = t;
}

View File

@@ -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,7 +136,12 @@ void k3BatchFlush() {
}
if(k3IsCore) {
glUseProgram((GLuint) coreProg);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
GLuint handle = GL_FROM_K3GLSL(coreProg);
glUseProgram(handle);
glUniform2f(coreUResolution, ResolutionX, ResolutionY);
float *farr = alloca(SCount * 60 * sizeof(*farr));
@@ -211,14 +220,14 @@ void k3BatchFlush() {
glBindBufferARB(GL_ARRAY_BUFFER_ARB, coreVBO);
glBufferDataARB(GL_ARRAY_BUFFER_ARB, SCount * 60 * sizeof(*farr), farr, GL_DYNAMIC_DRAW);
glUniform1f(glGetUniformLocation((GLuint) coreProg, "u_texuse"), !!activeTex);
glUniform1f(k3ProgramGetUId(coreProg, "u_texuse"), !!activeTex);
glUniform1f(k3ProgramGetUId(coreProg, "u_borderradius"), activeBorderRadius);
glUniform1f(k3ProgramGetUId(coreProg, "u_minalpha"), activeMinAlpha);
glUniform1f(glGetUniformLocation((GLuint) coreProg, "u_borderradius"), activeBorderRadius);
GLint aPos = glGetAttribLocation((GLuint) coreProg, "a_pos");
GLint aUv = glGetAttribLocation((GLuint) coreProg, "a_uv");
GLint aColor = glGetAttribLocation((GLuint) coreProg, "a_color");
GLint aSize = glGetAttribLocation((GLuint) coreProg, "a_size");
GLint aPos = glGetAttribLocation(handle, "a_pos");
GLint aUv = glGetAttribLocation(handle, "a_uv");
GLint aColor = glGetAttribLocation(handle, "a_color");
GLint aSize = glGetAttribLocation(handle, "a_size");
glEnableVertexAttribArray(aPos);
glEnableVertexAttribArray(aUv);
@@ -241,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++) {
@@ -262,6 +281,8 @@ void k3BatchFlush() {
}
glEnd();
glDisable(GL_ALPHA_TEST);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
}

View File

@@ -10,7 +10,9 @@ struct k3RectF {
float h;
};
void k3BatchAdd(struct k3Tex *tex, struct k3RectF src, struct k3RectF dst, float rot, vec4 color, float borderRadius);
void k3BatchInit();
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);

View File

@@ -3,72 +3,64 @@
#include"k3batch.h"
#include<string.h>
#include"gl.h"
#include<freetype/freetype.h>
#include<freetype/ftmodapi.h>
#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);
const uint8_t *end = buf + len;
k3TexSetParameter(this->atlas, k3_TEX_SET_MIPMAPPING_DISABLED, &(bool) {true});
buf += 4;
k3TexUpdate(this->atlas, k3_ALPHA, 0, this->atlasSize, this->atlasSize, NULL);
uint16_t pages = 0;
this->fontPixelSize = this->atlasSize / 16;
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;
}
}
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);
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 +109,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 +145,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 +174,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 +203,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);
}

View File

@@ -4,49 +4,32 @@
#include"k3batch.h"
#include<string.h>
#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;

4
src/k3menu_internal.h Normal file
View File

@@ -0,0 +1,4 @@
#pragma once
struct k3Tex;
typedef struct k3Tex *k3MImageData;