summaryrefslogtreecommitdiff
path: root/src/render/gl_2d_font.cpp
diff options
context:
space:
mode:
authornavewindre <boneyaard@gmail.com>2025-09-03 20:10:09 +0200
committernavewindre <boneyaard@gmail.com>2025-09-03 20:10:09 +0200
commitf8b92ce3aa08b1445c9f956d8166830946562d12 (patch)
tree94e63a5aec9f8f52b577f56799e0c9201fd976a5 /src/render/gl_2d_font.cpp
a
Diffstat (limited to 'src/render/gl_2d_font.cpp')
-rw-r--r--src/render/gl_2d_font.cpp366
1 files changed, 366 insertions, 0 deletions
diff --git a/src/render/gl_2d_font.cpp b/src/render/gl_2d_font.cpp
new file mode 100644
index 0000000..1dc498b
--- /dev/null
+++ b/src/render/gl_2d_font.cpp
@@ -0,0 +1,366 @@
+#include "gl_2d_font.h"
+#include "gl.h"
+
+FT_Library libft = NULL;
+
+THREAD_MUTEX font_mutex;
+
+void freetype_init() {
+ U32 err;
+
+ if( !libft ) {
+ err = FT_Init_FreeType( &libft );
+
+ if( err )
+ dlog( "gl_font_create() : FT_Init_FreeType() failed\n" );
+ }
+}
+
+void gl_font_create_bitmap( GL_FONT* font ) {
+ U32 char_width = (font->face->size->metrics.max_advance >> 6) + 1;
+ U32 char_height = (font->face->size->metrics.height >> 6) + 1;
+ U32 max_width = char_width * 16;
+ U32 max_height = char_height * 16;
+
+ if( !max_width || !max_height ) {
+ dlog( "gl_font_create() : invalid font size\n" );
+ return;
+ }
+
+ font->bitmap = (U32*)malloc( sizeof(U32) * max_width * max_height );
+ memset( font->bitmap, 0, sizeof(U32) * max_width * max_height );
+ font->bitmap_width = max_width;
+ font->bitmap_height = max_height;
+ font->char_width = char_width;
+ font->char_height = char_height;
+
+ U32 cur_x = 0, cur_y = 1;
+ for( I32 c = 0; c < GL_FONT_MAX_GLYPHS; ++c ) {
+ U32 err = FT_Load_Char( font->face, c, FT_LOAD_RENDER );
+ if( !err ) {
+ FT_Bitmap* bitmap = &font->face->glyph->bitmap;
+ for( U32 y = 0; y < bitmap->rows; ++y ) {
+ for( U32 x = 0; x < bitmap->width; ++x ) {
+ U32 dest_x = x + cur_x;
+ U32 dest_y = y + cur_y;
+
+ U8 pixel = bitmap->buffer[x + bitmap->width * y];
+ U32 out_pixel = 0x00FFFFFF;
+ out_pixel |= ( pixel << 24 );
+ font->bitmap[dest_x + max_width * dest_y] = out_pixel;
+ }
+ }
+
+ font->glyphs[c].offset_x = (F32)cur_x;
+ font->glyphs[c].offset_y = (F32)cur_y;
+ font->glyphs[c].width = bitmap->width;
+ font->glyphs[c].height = bitmap->rows;
+ font->glyphs[c].advance_x = (F32)(font->face->glyph->metrics.horiAdvance >> 6);
+ font->glyphs[c].advance_y = (F32)(char_height - (font->face->glyph->metrics.horiBearingY >> 6));
+ font->glyphs[c].bearing = (F32)(font->face->glyph->metrics.horiBearingX >> 6);
+ }
+ else {
+ dlog( "gl_font_create() : FT_Load_Char() failed (%s: %c) (%x)\n", font->name, c, err );
+ }
+
+ if( cur_x + char_width >= max_width ) {
+ cur_x = 0;
+ cur_y += char_height;
+ } else {
+ cur_x += char_width;
+ }
+ }
+}
+
+void gl_font_create_atlas( GL_DATA* gl, GL_FONT* font ) {
+ if( !font->bitmap ) {
+ dlog( "gl_font_create_atlas() : no bitmap\n" );
+ return;
+ }
+
+ char texture_name[256];
+ sprintf( texture_name, "%s_%d_atlas", font->name, font->size );
+
+ font->atlas = gl_texture_from_bitmap( gl,
+ texture_name,
+ (U8*)font->bitmap,
+ font->bitmap_width,
+ font->bitmap_height
+ );
+
+ free( font->bitmap );
+}
+
+GL_FONT* gl_font_create( GL_DATA* gl, const char* path, I32 size ) {
+ freetype_init();
+
+ if( size <= 2 ) {
+ dlog( "gl_font_create() : size too small.\n" );
+ return 0;
+ }
+
+ char full_path[256];
+ GL_FONT* font = (GL_FONT*)malloc( sizeof( GL_FONT ) );
+ font->size = size;
+ font->name = path;
+
+ sprintf( full_path, "../assets/fonts/%s", path );
+ U32 err = FT_New_Face( libft, full_path, 0, &font->face );
+
+ if( err ) {
+ dlog( "gl_font_create() : FT_New_Face() failed (%x)\n", err );
+ free( font );
+ return 0;
+ }
+
+ err = FT_Set_Pixel_Sizes( font->face, 0, size );
+ if( err ) {
+ dlog( "gl_font_create() : FT_Set_Char_Size() failed\n" );
+ free( font );
+ return 0;
+ }
+
+ gl_font_create_bitmap( font );
+ FT_Done_Face( font->face );
+
+ gl_font_create_atlas( gl, font );
+ gl->fonts.push( font );
+ return font;
+}
+
+void gl_font_destroy( GL_DATA* gl, GL_FONT* font ) {
+ gl_texture_destroy( gl, font->atlas );
+ I32 idx = gl->fonts.idx_of( font );
+ if( idx != -1 )
+ gl->fonts.erase( idx );
+
+ free( font );
+}
+
+
+void gl_font_calc_vertices_uvs(
+ GL_FONT* font,
+ VEC2 origin,
+ const char* text,
+ F32 _scale,
+ VERTEX* vertices,
+ U16* indices,
+ VEC2* coords,
+ CLR clr
+) {
+ U32 len = (U32)strlen( text );
+
+ F32 cur_x = origin.x;
+ F32 cur_y = origin.y;
+
+ for( U32 i = 0; i < len; ++i ) {
+ VERTEX* v = &vertices[i * 6];
+ U16* idx = &indices[i * 6];
+
+ if( text[i] == '\n' ) {
+ v[0] = v[1] = v[2] = v[3] = v[4] = v[5] = { { cur_x, cur_y }, {} };
+ idx[0] = idx[1] = idx[2] = idx[3] = idx[4] = idx[5] = i * 6;
+
+ cur_x = origin.x;
+ cur_y += (F32)font->char_height * _scale;
+ continue;
+ }
+
+ U32 c = (U8)text[i];
+ FONT_GLYPH* glyph = &font->glyphs[c];
+ F32 final_y = cur_y + glyph->advance_y * _scale;
+ F32 final_x = cur_x + (F32)glyph->bearing * _scale;
+
+ v[0].pos = { final_x, final_y };
+ v[1].pos = { final_x + glyph->width * _scale, final_y };
+ v[2].pos = { final_x, final_y + glyph->height * _scale };
+ v[3].pos = { final_x + glyph->width * _scale, final_y + glyph->height * _scale };
+ v[4].pos = { final_x, final_y + glyph->height * _scale };
+ v[5].pos = { final_x + glyph->width * _scale, final_y };
+
+ idx[0] = i * 6;
+ idx[1] = i * 6 + 1;
+ idx[2] = i * 6 + 2;
+ idx[3] = i * 6 + 3;
+ idx[4] = i * 6 + 4;
+ idx[5] = i * 6 + 5;
+
+ v[0].uv = { glyph->offset_x / (F32)font->bitmap_width, glyph->offset_y / font->bitmap_height };
+ v[1].uv = { (glyph->offset_x + glyph->width) / (F32)font->bitmap_width, glyph->offset_y / font->bitmap_height };
+ v[2].uv = { glyph->offset_x / (F32)font->bitmap_width, (glyph->offset_y + glyph->height) / font->bitmap_height };
+ v[3].uv = { (glyph->offset_x + glyph->width) / (F32)font->bitmap_width, (glyph->offset_y + glyph->height) / font->bitmap_height };
+ v[4].uv = { glyph->offset_x / (F32)font->bitmap_width, (glyph->offset_y + glyph->height) / font->bitmap_height };
+ v[5].uv = { (glyph->offset_x + glyph->width) / (F32)font->bitmap_width, glyph->offset_y / font->bitmap_height };
+
+ v[0].clr = v[1].clr = v[2].clr = v[3].clr = v[4].clr = v[5].clr = clr;
+ v[0].sampler = v[1].sampler = v[2].sampler = v[3].sampler = v[4].sampler = v[5].sampler = 0;
+
+ cur_x += glyph->advance_x * _scale;
+ }
+}
+
+void gl_font_draw( GL_FONT* font, GL_SHADER_PROGRAM* shader, VEC2 origin, const char* text, CLR clr, F32 _scale ) {
+ U32 len = strlen( text );
+ VERTEX* vertices = (VERTEX*)malloc( sizeof(VERTEX) * 6 * len );
+ U16* indices = (U16*)malloc( sizeof(U16) * 6 * len );
+ VEC2* coords = (VEC2*)malloc( sizeof(VEC2) * 6 * len );
+
+ gl_font_calc_vertices_uvs( font, origin, text, _scale, vertices, indices, coords, clr );
+ glUseProgram( shader->id );
+
+ glBindBuffer( GL_ARRAY_BUFFER, shader->gl->vbuffer );
+ glBufferData( GL_ARRAY_BUFFER, sizeof(VERTEX) * 6 * len, vertices, GL_STATIC_DRAW );
+ I32 position = glGetAttribLocation( shader->id, "in_pos" );
+ glEnableVertexAttribArray( position );
+ glVertexAttribPointer( position, 2, GL_FLOAT, 0, sizeof(VERTEX), &( (VERTEX*)nullptr)->pos );
+
+ I32 color = glGetAttribLocation( shader->id, "in_clr" );
+ glEnableVertexAttribArray( color );
+ glVertexAttribPointer( color, 4, GL_FLOAT, 0, sizeof(VERTEX), &( (VERTEX*)nullptr)->clr );
+
+ I32 texcoord = glGetAttribLocation( shader->id, "in_texcoord" );
+ glEnableVertexAttribArray( texcoord );
+ glVertexAttribPointer( texcoord, 2, GL_FLOAT, 0, sizeof(VERTEX), &( (VERTEX*)nullptr)->uv );
+
+ I32 sampler = glGetAttribLocation( shader->id, "in_sampler" );
+ glEnableVertexAttribArray( sampler );
+ glVertexAttribPointer( sampler, 1, GL_UNSIGNED_BYTE, 1, sizeof(VERTEX), &( (VERTEX*)nullptr)->sampler );
+
+ glBindBuffer( GL_ARRAY_BUFFER, 0 );
+
+ glActiveTexture( GL_TEXTURE0 );
+
+ glBindTexture( GL_TEXTURE_2D, font->atlas->id );
+
+ glDrawElements( GL_TRIANGLES, len * 6, GL_UNSIGNED_SHORT, indices );
+ glBindTexture( GL_TEXTURE_2D, 0 );
+
+ free( vertices );
+ free( indices );
+ free( coords );
+}
+
+void gl_font_textured(
+ GL_FONT* font,
+ GL_SHADER_PROGRAM* shader,
+ VEC2 origin,
+ const char* text,
+ GL_TEX2D* tex,
+ CLR clr,
+ F32 _scale
+) {
+ U32 len = strlen( text );
+ struct FONT_CUSTOM_VERTEX {
+ VERTEX v;
+ VEC2 uv2;
+ };
+
+ FONT_CUSTOM_VERTEX* custom_vertices = (FONT_CUSTOM_VERTEX*)malloc( sizeof(FONT_CUSTOM_VERTEX) * 6 * len );
+ VERTEX* vertices = (VERTEX*)malloc( sizeof(VERTEX) * 6 * len );
+ U16* indices = (U16*)malloc( sizeof(U16) * 6 * len );
+ VEC2* coords = (VEC2*)malloc( sizeof(VEC2) * 6 * len );
+
+ gl_font_calc_vertices_uvs( font, origin, text, _scale, vertices, indices, coords, clr );
+ VEC2 dim = gl_font_dim( font, text, _scale );
+
+ for( U32 i = 0; i < len * 6; ++i ) {
+ custom_vertices[i].v = vertices[i];
+ custom_vertices[i].uv2 = {
+ (vertices[i].pos.x - origin.x) / dim.x,
+ (vertices[i].pos.y - origin.y - vertices[0].pos.y) / dim.y
+ };
+ }
+
+ glUseProgram( shader->id );
+
+ glBindBuffer( GL_ARRAY_BUFFER, shader->gl->vbuffer );
+ glBufferData( GL_ARRAY_BUFFER, sizeof(FONT_CUSTOM_VERTEX) * 6 * len, custom_vertices, GL_STATIC_DRAW );
+ I32 position = glGetAttribLocation( shader->id, "in_pos" );
+ glEnableVertexAttribArray( position );
+ glVertexAttribPointer( position, 2, GL_FLOAT, 0, sizeof(FONT_CUSTOM_VERTEX), 0 );
+
+ I32 texcoord = glGetAttribLocation( shader->id, "in_texcoord" );
+ glEnableVertexAttribArray( texcoord );
+ glVertexAttribPointer( texcoord, 2, GL_FLOAT, 0, sizeof(FONT_CUSTOM_VERTEX), (void*)(sizeof(VEC2)) );
+
+ I32 texcoord2 = glGetAttribLocation( shader->id, "in_texcoord2" );
+ glEnableVertexAttribArray( texcoord2 );
+ glVertexAttribPointer( texcoord2, 2, GL_FLOAT, 0, sizeof(FONT_CUSTOM_VERTEX), (void*)(sizeof(VERTEX)) );
+
+ glBindBuffer( GL_ARRAY_BUFFER, 0 );
+
+ I32 color = glGetUniformLocation( shader->id, "in_color" );
+ glUniform4fv( color, 1, (F32*)&clr );
+
+ glActiveTexture( GL_TEXTURE0 );
+ glUniform1iv( glGetUniformLocation( shader->id, "in_sampler" ), 0, 0 );
+ glBindTexture( GL_TEXTURE_2D, font->atlas->id );
+
+ glDrawElements( GL_TRIANGLES, len * 6, GL_UNSIGNED_SHORT, indices );
+ glBindTexture( GL_TEXTURE_2D, 0 );
+
+ free( vertices );
+ free( indices );
+ free( coords );
+ free( custom_vertices );
+}
+
+
+VEC2 gl_font_dim( GL_FONT* font, const char* text, F32 _scale ) {
+ U32 len = strlen( text );
+
+ F32 cur_x = 0.f;
+ F32 cur_y = font->char_height * _scale;
+ F32 max_x = 0.f;
+
+ for( U32 i = 0; i < len; ++i ) {
+ if( text[i] == '\n' ) {
+ cur_x = 0;
+ cur_y += font->char_height * _scale;
+ continue;
+ }
+
+ U32 c = (U8)text[i];
+ FONT_GLYPH* glyph = &font->glyphs[c];
+ cur_x += glyph->advance_x * _scale;
+ if( cur_x > max_x )
+ max_x = cur_x;
+ }
+
+ return { max_x, cur_y };
+}
+
+void gl_font_fit_into_box( GL_FONT* font, const char* source, char* out, F32 box_width, F32 box_height, F32 scale ) {
+ U32 len = strlen( source );
+ U32 last_space = 0;
+
+ F32 cur_x = 0.f;
+ for( U32 i = 0; i < len; ++i ) {
+ if( source[i] == ' ' )
+ last_space = i;
+
+ if( source[i] == '\n' ) {
+ cur_x = 0;
+ continue;
+ }
+
+ FONT_GLYPH* glyph = &font->glyphs[(U8)source[i]];
+ cur_x += glyph->advance_x * scale;
+
+ out[i] = source[i];
+ if( cur_x > box_width ) {
+ if( last_space == 0 ) {
+ out[i] = '\n';
+ cur_x = 0;
+ } else {
+ out[last_space] = '\n';
+ cur_x = 0;
+ i = last_space;
+ last_space = 0;
+ }
+ }
+
+ }
+
+ out[len] = '\0';
+}