From b718dead6ffdef7df5836b7d9b112b4f38e82378 Mon Sep 17 00:00:00 2001 From: talha Date: Sat, 2 Sep 2023 22:10:39 +0500 Subject: Adding basic font rendering functions: - moving existing code into separate function calls to make it easier --- code/amr_fonts.h | 156 +++++++++++++++++++++++++++++++++++++++++++++++----- code/amr_memory.h | 2 +- code/win32_main.cpp | 127 +++++++++++------------------------------- 3 files changed, 176 insertions(+), 109 deletions(-) (limited to 'code') diff --git a/code/amr_fonts.h b/code/amr_fonts.h index f44893c..81666a0 100644 --- a/code/amr_fonts.h +++ b/code/amr_fonts.h @@ -1,25 +1,46 @@ #ifndef AMR_FONTS_H #define AMR_FONTS_H -typedef struct amr_glyph_info { - u32 charset_start_index; - u32 charset_end_index; -} amr_glyph_info; +#define LATIN_BASIC_START_INDEX 32 +#define LATIN_BASIC_END_INDEX 126 -typedef struct amr_font_details_debug { +typedef struct amr_letter_details_debug { i32 advance; i32 lsb; i32 x0, y0, x1, y1; - i32 kern; - f32 lx; - f32 ly; i32 byte_offset; - i32 baseline; -} debug_font_details; + // @TODO: check if offsets are in PX + f32 StartingYOffset; + f32 StartingXOffset; +} amr_letter_details_debug; typedef struct amr_font_state_debug { stbtt_fontinfo font_info; + amr_letter_details_debug font_details; + // font metrics + f32 font_size; + f32 scale_factor; + i32 ascent; + i32 descent; + i32 line_gap; + // glyphs + u32 charset_start_index; + u32 charset_end_index; + + // @TODO: make this array + u32 *glyph_index_array; + u32 glyph_count; + + // text specific metrics + // @TODO: these should probably be moved to an array or remain out of this struct + f32 Baseline; + f32 MaxLineWidthPx; + // font bitmap info + unsigned char *FontBitmap; + u32 FontBitmapWidth; + u32 FontBitmapHeight; } amr_font_state; + // initialise a font and loads the font in the fontbuffer // input: // - font path @@ -28,7 +49,16 @@ typedef struct amr_font_state_debug { // output: // - error code u32 amr_InitFont(const u8 *FontPath, u8 *FontBuffer, u32 FontFz); -u32 amr_GetGlyphIdFromCodepoint(amr_glyph_info glyph, u32 *GlyphIndexArray, u32 Codepoint); +void amr_LoadFontMetrics(amr_font_state font, u32 FontSize); +void amr_LoadGlyphsFromFont(amr_DebugArena *MemoryArena, amr_font_state *font); +u32 amr_GetGlyphIdFromCodepoint(amr_font_state font, u32 Codepoint); + +////////////////////////////////////////////////////////////////////////////// +/// Naive Font Bitmap generation +/// This approach is fast but usese more memory +/// We make a single draw call, but load the entire text in a single bitmap +////////////////////////////////////////////////////////////////////////////// +void amr_MakeFontBitmapNaive(GameState *State, amr_font_state *Font, char *Text, u32 TextSize); u32 amr_InitFont(stbtt_fontinfo *font_info, const char *FontPath, u8 *FontBuffer, u32 FontSz) { @@ -42,10 +72,110 @@ u32 amr_InitFont(stbtt_fontinfo *font_info, const char *FontPath, u8 *FontBuffer return 0; } -u32 amr_GetGlyphIdFromCodepoint(amr_glyph_info glyph, u32 *GlyphIndexArray, u32 Codepoint) +void amr_LoadFontMetrics(amr_font_state *Font, f32 FontSize) +{ + // get a SF (scale factor) + Font->scale_factor = stbtt_ScaleForPixelHeight(&(Font->font_info), FontSize); + + // get vertical metrics + stbtt_GetFontVMetrics(&(Font->font_info), &(Font->ascent), + &(Font->descent), &(Font->line_gap)); +} + +void amr_LoadGlyphsFromFont(amr_DebugArena *MemoryArena, amr_font_state *Font) +{ + Font->glyph_count = Font->charset_end_index - Font->charset_start_index; + Font->glyph_index_array = (u32 *)amr_ArenaAlloc(MemoryArena, + sizeof(Font->glyph_count) * Font->glyph_count); + + u32 *array_iter = Font->glyph_index_array; + for (u32 i = 0; i < Font->glyph_count; i++) + { + u32 offset_codepoint = Font->charset_start_index + i; + *array_iter = stbtt_FindGlyphIndex(&(Font->font_info), offset_codepoint); + array_iter++; + } +} + +u32 amr_GetGlyphIdFromCodepoint(amr_font_state Font, u32 Codepoint) { - u32 glyph_id = *(GlyphIndexArray + (Codepoint - glyph.charset_start_index)); + u32 glyph_id = *(Font.glyph_index_array + (Codepoint - Font.charset_start_index)); return glyph_id; } +void amr_MakeFontBitmapNaive(GameState *State, amr_font_state *Font, char *Text, u32 TextSize) +{ + Font->MaxLineWidthPx = 0.0f; + Font->Baseline = roundf(Font->ascent * Font->scale_factor); + + u32 fd_sz = sizeof(amr_letter_details_debug)*TextSize; + amr_letter_details_debug *fd_arr = (amr_letter_details_debug *)amr_ArenaAlloc(&(State->Memory.Arena), fd_sz); + amr_letter_details_debug *_fi = fd_arr; + + f32 RunningXOffset = 0.0f; + // convert str to font + for (i32 i = 0; i < TextSize; ++i) + { + _fi->StartingXOffset = RunningXOffset; + if (Text[i] == '\n') + { + // if new line, move baseline down + Font->Baseline += (i32)roundf((Font->ascent - Font->descent + + Font->line_gap) * Font->scale_factor); + RunningXOffset = 0; + ++_fi; + continue; + } + + u32 GlyphId = amr_GetGlyphIdFromCodepoint(*Font, Text[i]); + stbtt_GetGlyphHMetrics(&(Font->font_info), GlyphId, + &_fi->advance, &_fi->lsb); + stbtt_GetGlyphBitmapBox(&(Font->font_info), GlyphId, + Font->scale_factor, Font->scale_factor, + &_fi->x0, &_fi->y0, &_fi->x1, &_fi->y1); + + _fi->StartingYOffset = (f32)(Font->Baseline + _fi->y0); + RunningXOffset += (_fi->advance * Font->scale_factor); + + if (i < TextSize - 1 && Text[i+1] != '\n') + { + i32 kern = stbtt_GetGlyphKernAdvance(&(Font->font_info), + GlyphId, amr_GetGlyphIdFromCodepoint(*Font, Text[i+1])); + RunningXOffset += roundf(kern * Font->scale_factor); + } + + if (RunningXOffset > Font->MaxLineWidthPx) + { + Font->MaxLineWidthPx = RunningXOffset; + } + ++_fi; + } + + // make all the glyphs into bitmaps. + _fi = fd_arr; + Font->FontBitmapWidth = (u32)Font->MaxLineWidthPx; + Font->FontBitmapHeight = (u32)(Font->Baseline - + roundf(Font->scale_factor * Font->descent)); + + u32 bitmap_size = sizeof(unsigned char)*Font->FontBitmapWidth*Font->FontBitmapHeight; + Font->FontBitmap = (unsigned char *)amr_ArenaAlloc(&(State->Memory.Arena), bitmap_size); + + _fi = fd_arr; + for (int i = 0; i < TextSize; i++) + { + if (Text[i] == '\n') { + ++_fi; + continue; + } + _fi->byte_offset = (i32)(_fi->StartingXOffset + roundf(_fi->lsb * Font->scale_factor) + + (_fi->StartingYOffset * Font->FontBitmapWidth)) ; + // store image data in bitmap + stbtt_MakeGlyphBitmap(&(Font->font_info), Font->FontBitmap + _fi->byte_offset, + _fi->x1 - _fi->x0, _fi->y1 - _fi->y0, Font->FontBitmapWidth, + Font->scale_factor, Font->scale_factor, + amr_GetGlyphIdFromCodepoint(*Font, Text[i])); + _fi++; + } +} + #endif diff --git a/code/amr_memory.h b/code/amr_memory.h index f7638b3..bc5c1ad 100644 --- a/code/amr_memory.h +++ b/code/amr_memory.h @@ -6,10 +6,10 @@ #endif typedef struct amr_DebugArena { - u8 *Buffer; size_t Size; size_t CurrOffset; size_t PrevOffset; + u8 *Buffer; } amr_DebugArena; bool amr_IsPowerOfTwo(uintptr_t x); diff --git a/code/win32_main.cpp b/code/win32_main.cpp index 4ba86e1..2153ece 100644 --- a/code/win32_main.cpp +++ b/code/win32_main.cpp @@ -33,12 +33,13 @@ typedef double f64; #define internal static /** - * - enhance filereading to read bytes - * error todo: + * todo: + * - Add a stack allocator, have many cases where it would be useful + * - create a safe(r) way to use arrays with an array struct having a length field + * - will be useful for 2d arrays as well as we will have a len and height * - start having error codes to help with error detection in case something went wrong in functions * Font rendering todos: - * - Look into font-packing, what is that? why is it needed? - * - Look into generating a single bitmap and then extracting characters from the + * - Look into generating a font atlas and then generating quads from the * bitmap as needed * - SDF font rendering * */ @@ -105,8 +106,8 @@ Win32GetSecondsElapsed(LARGE_INTEGER Start, LARGE_INTEGER End) #include "amr_memory.c" #include "gl_graphics.cpp" //#include "amr_camera.c" -#include "amr_fonts.h" #include "game_main.h" +#include "amr_fonts.h" int main() { @@ -199,7 +200,6 @@ int main() amr_ArenaFreeAll(&State.Memory.Arena); // =================== FONT RENDERING =================== - // @resume: abstracting font rendering to draw text easily // --------- Init Font ----------------- // input: // - filepath, memory buffer, memory buffer size @@ -211,92 +211,29 @@ int main() amr_InitFont(&(ArialFontState.font_info), "c:/windows/fonts/arial.ttf", ArialBuffer, font_fz); ///////////////////////////////////// PREPARE TEXT INFO /////////////////// - // get a SF (scale factor) - f32 SF = stbtt_ScaleForPixelHeight(&(ArialFontState.font_info), 32.0f); - - // get vertical metrics - i32 f_ascent, f_descent, f_line_gap; - stbtt_GetFontVMetrics(&(ArialFontState.font_info), &f_ascent, &f_descent, &f_line_gap); - - // ========================= GLYPH AND FONT INFO STORAGE ================ - amr_glyph_info latin_basic = {32, 126}; - u32 glyph_count = latin_basic.charset_end_index - latin_basic.charset_start_index; - u32 *GlyphIndexArray = (u32 *)amr_ArenaAlloc(&State.Memory.Arena, sizeof(glyph_count) * glyph_count); - u32 *glyph_array_iter = GlyphIndexArray; - for (u32 i = 0; i < glyph_count; i++) - { - *glyph_array_iter = stbtt_FindGlyphIndex(&(ArialFontState.font_info), latin_basic.charset_start_index + i); - glyph_array_iter++; - } - - const char *text = "Hello this is a\npiece of test text \nor and more on a newline."; - u8 char_sz = 61; - - u32 fd_sz = sizeof(debug_font_details)*char_sz; - - debug_font_details *fd_arr = (debug_font_details *)amr_ArenaAlloc(&State.Memory.Arena, fd_sz); - debug_font_details *_fi = fd_arr; - - f32 lx = 0.0f, max_lx = 0.0f; - i32 baseline = (i32)roundf(f_ascent * SF); - // convert str to font - for (i32 i = 0; i < char_sz; ++i) - { - _fi->lx = lx; - _fi->baseline = baseline; - - if (text[i] == '\n') - { - // if new line, move baseline down - baseline += (i32)roundf((f_ascent - f_descent + f_line_gap) * SF); - lx = 0; - ++_fi; - continue; - } - - stbtt_GetGlyphHMetrics(&(ArialFontState.font_info), amr_GetGlyphIdFromCodepoint(latin_basic, GlyphIndexArray, text[i]), &_fi->advance, &_fi->lsb); - stbtt_GetGlyphBitmapBox(&(ArialFontState.font_info), amr_GetGlyphIdFromCodepoint(latin_basic, GlyphIndexArray, text[i]), SF, SF, &_fi->x0, &_fi->y0, &_fi->x1, &_fi->y1); - - _fi->ly = (f32)(_fi->baseline + _fi->y0); - lx += (_fi->advance * SF); - - i32 kern; - if (i < char_sz - 1 && text[i+1] != '\n') - { - kern = stbtt_GetGlyphKernAdvance(&(ArialFontState.font_info), amr_GetGlyphIdFromCodepoint(latin_basic, GlyphIndexArray, text[i]), amr_GetGlyphIdFromCodepoint(latin_basic, GlyphIndexArray, text[i+1])); - lx += roundf(kern * SF); - } - - if (lx > max_lx) - { - max_lx = lx; - } - ++_fi; - } - - // make all the glyphs into bitmaps. - _fi = fd_arr; - u32 bitmap_width_from_text_info = (u32)max_lx; - u32 bitmap_height_from_text_info = (u32)(baseline - roundf(SF * f_descent)); + amr_LoadFontMetrics(&ArialFontState, 32.0f); - i32 bmp_sz = sizeof(u8)*bitmap_width_from_text_info*bitmap_height_from_text_info; - u8 *Bitmap = (u8 *)amr_ArenaAlloc(&State.Memory.Arena, bmp_sz); - - _fi = fd_arr; - for (int i=0; ibyte_offset = (i32)(_fi->lx + roundf(_fi->lsb * SF) + (_fi->ly * bitmap_width_from_text_info)) ; - // store image data in bitmap - stbtt_MakeGlyphBitmap(&(ArialFontState.font_info), Bitmap + _fi->byte_offset, _fi->x1 - _fi->x0, _fi->y1 - _fi->y0, bitmap_width_from_text_info, SF, SF, amr_GetGlyphIdFromCodepoint(latin_basic, GlyphIndexArray, text[i])); - _fi++; - } + // ========================= GLYPH AND FONT INFO STORAGE ================ + ArialFontState.charset_start_index = LATIN_BASIC_START_INDEX; + ArialFontState.charset_end_index = LATIN_BASIC_END_INDEX; + amr_LoadGlyphsFromFont(&State.Memory.Arena, &ArialFontState); + + // ========================= NAIVE FONT BITMAP GENERATION =============== + // Load text into bitmaps + char *text = "Hello this is a\npiece of test text \nor and more on a newline."; + u8 text_sz = 61; + + // @TODO: Add support for multiple text loading + // this currently only supports 1 text at a time + // the struct amr_font_state_debug only has field for 1 font + // that can be fixed by adding an array object of a fixed size + // that user can initalize and will then support adding fonts to + // @TODO: look into refactoring this function, only did a basic pass + amr_MakeFontBitmapNaive(&State, &ArialFontState, text, text_sz); // ---------- prepare bitmap texture and buffer objects ----------- ////////////////////////////////// PREPARE TEXT FOR RENDERING //////////// + /// @resume: continue abstracting font rendering stuff into functions from here glPixelStorei(GL_UNPACK_ALIGNMENT, 1); f32 bmp_vertices[] = { @@ -317,9 +254,9 @@ int main() BufferO bo = CreateRectangleTextured(bmp_vertices, bmp_vsz, bmp_indices, bmp_isz, ATTR_TEX); Texture2D _tex = {0}; { - _tex.width = bitmap_width_from_text_info; - _tex.height = bitmap_height_from_text_info; - _tex.data = Bitmap; + _tex.width = ArialFontState.FontBitmapWidth; + _tex.height = ArialFontState.FontBitmapHeight; + _tex.data = ArialFontState.FontBitmap; glBindVertexArray(bo.VAO); glGenTextures(1, &bo.TexO); @@ -369,12 +306,12 @@ int main() // ////////////////////// DEFINE TEXT POSITION AND SCALE ////////////////// // ====================== TEXT POSITIONING AND SCALING ====== - Vec2 text_dims = {((f32)bitmap_width_from_text_info)/2.0f, ((f32)bitmap_height_from_text_info)/2.0f}; + Vec2 text_dims = {((f32)ArialFontState.FontBitmapWidth)/2.0f, ((f32)ArialFontState.FontBitmapHeight)/2.0f}; Vec2 text_scaled_dims = State.px_ratio * text_dims; Vec2 text_scaled_mid = text_scaled_dims/2.0f; Vec2 text_pos = {0.0f, 0.0f}; - Vec2 text_offset = {text_dims.x/2.0f, (f32)fd_arr->baseline}; + Vec2 text_offset = {text_dims.x/2.0f, ArialFontState.Baseline}; text_pos = text_offset + text_pos; Vec2 text_scaled_pos = State.px_ratio*(State.ScreenCoords.tl + text_pos) + text_scaled_mid; @@ -459,8 +396,8 @@ int main() LoadUniformMat4(sp, "Projection", projection); glEnable(GL_DEPTH_TEST); - // glEnable(GL_BLEND); - // glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glClearColor(0.2f, 0.3f, 0.3f, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); -- cgit v1.2.3