diff options
| author | navewindre <boneyaard@gmail.com> | 2025-09-03 20:10:09 +0200 |
|---|---|---|
| committer | navewindre <boneyaard@gmail.com> | 2025-09-03 20:10:09 +0200 |
| commit | f8b92ce3aa08b1445c9f956d8166830946562d12 (patch) | |
| tree | 94e63a5aec9f8f52b577f56799e0c9201fd976a5 /src/render/gl_2d_font.cpp | |
a
Diffstat (limited to 'src/render/gl_2d_font.cpp')
| -rw-r--r-- | src/render/gl_2d_font.cpp | 366 |
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'; +} |
