diff --git a/gb.h b/gb.h index 28d8aa5..d22c437 100644 --- a/gb.h +++ b/gb.h @@ -1,5 +1,5 @@ /* gb.h - v0.03 - Ginger Bill's C Helper Library - public domain - - no warranty implied; use at your own risk + - no warranty implied; use at your own risk This is a single header file with a bunch of useful stuff to replace the C/C++ standard library @@ -506,6 +506,9 @@ namespace gb { #define gb_clamp01(x) gb_clamp((x), 0, 1) #endif +#define gb_is_between(x, lower, upper) (((x) >= (lower)) && ((x) <= (upper))) + + //////////////////////////////// // @@ -879,7 +882,20 @@ GB_DEF GB_ALLOCATOR_PROC(gb_pool_allocator_proc); +//////////////////////////////////////////////////////////////// +// +// Sort & Search +// +// +#define GB_COMPARE_PROC(name) int name(void const *a, void const *b) +typedef GB_COMPARE_PROC(gbCompareProc); + + +GB_DEF void gb_qsort(void *base, isize count, isize size, gbCompareProc compare_proc); + +// NOTE(bill): Returns index +GB_DEF isize gb_binary_search(void const *base, isize count, isize size, void const *key, gbCompareProc compare_proc); @@ -904,10 +920,14 @@ GB_DEF void gb_to_lower(char *str); GB_DEF void gb_to_upper(char *str); GB_DEF isize gb_strlen(char const *str); +GB_DEF isize gb_strnlen(char const *str, isize max_len); GB_DEF i32 gb_strcmp(char const *s1, char const *s2); GB_DEF char * gb_strncpy(char *dest, char const *source, isize len); GB_DEF i32 gb_strncmp(char const *s1, char const *s2, isize len); +GB_DEF isize gb_utf8_strlen(char const *str); +GB_DEF isize gb_utf8_strnlen(char const *str, isize max_len); + GB_DEF char const *gb_first_occurence_of_char(char const *s1, char c); GB_DEF char const *gb_last_occurence_of_char(char const *s1, char c); @@ -926,6 +946,10 @@ GB_DEF void gb_cstr_concat(char *dest, isize dest_len, GB_DEF char16 *gb_utf8_to_utf16(char16 *buffer, char *str, isize len); GB_DEF char * gb_utf16_to_utf8(char *buffer, char16 *str, isize len); +// NOTE(bill): Returns size of codepoint in bytes +GB_DEF isize gb_utf8_decode(char const *str, char32 *codepoint); +GB_DEF isize gb_utf8_decode_len(char const *str, isize str_len, char32 *codepoint); + //////////////////////////////////////////////////////////////// // @@ -1733,7 +1757,7 @@ gb_destory_thread(gbThread *t) } -gb_internal void +gb_inline void gb__run_thread(gbThread *t) { gb_post_semaphore(&t->semaphore, 1); @@ -1741,9 +1765,9 @@ gb__run_thread(gbThread *t) } #if defined(GB_SYSTEM_WINDOWS) - gb_internal DWORD WINAPI gb__thread_proc(void *arg) { gb__run_thread(cast(gbThread *)arg); return 0; } + gb_inline DWORD WINAPI gb__thread_proc(void *arg) { gb__run_thread(cast(gbThread *)arg); return 0; } #else - gb_internal void *gb__thread_proc(void *arg) { gb__run_thread(cast(gbThread *)arg); return NULL; } + gb_inline void *gb__thread_proc(void *arg) { gb__run_thread(cast(gbThread *)arg); return NULL; } #endif gb_inline void gb_start_thread(gbThread *t, gbThreadProc *proc, void *data) { gb_start_thread_with_stack(t, proc, data, 0); } @@ -2111,9 +2135,45 @@ GB_ALLOCATOR_PROC(gb_pool_allocator_proc) } +void +gb_qsort(void *base, isize count, isize size, gbCompareProc compare_proc) +{ + qsort(base, count, size, compare_proc); +} +isize +gb_binary_search(void const *base, isize count, isize size, void const *key, gbCompareProc compare_proc) +{ + isize start = 0, end = count; + isize result; + + while (start < end) { + isize mid = start + (end-start)/2; + + result = compare_proc(key, cast(u8 *)base + mid*size); + if (result < 0) + end = mid; + else if (result > 0) + start = mid+1; + else + return mid; + } + + return 0; // TODO(bill): Should this be -1? +} + + + + +//////////////////////////////////////////////////////////////// +// +// Char things +// +// + + gb_inline char @@ -2214,6 +2274,42 @@ gb_strlen(char const *str) return result; } +gb_inline isize +gb_strnlen(char const *str, isize max_len) +{ + isize result = 0; + if (str) { + char const *end = str; + while (*end && result < max_len) end++; + result = end - str; + } + return result; +} + + +gb_inline isize +gb_utf8_strlen(char const *str) +{ + isize result = 0; + for (; *str; str++) { + if ((*str & 0xc0) != 0x80) + result++; + } + return result; +} + +gb_inline isize +gb_utf8_strnlen(char const *str, isize max_len) +{ + isize result = 0; + for (; *str && result < max_len; str++) { + if ((*str & 0xc0) != 0x80) + result++; + } + return result; +} + + gb_inline i32 gb_strcmp(char const *s1, char const *s2) { @@ -2605,6 +2701,77 @@ gb_utf16_to_utf8(char *buffer, char16 *str, isize len) } +#define GB_UTF_SIZE 4 +#define GB_UTF_INVALID 0xfffd + +gb_global u8 const gb_utfbyte[GB_UTF_SIZE+1] = {0x80, 0, 0xc0, 0xe0, 0xf0}; +gb_global u8 const gb_utfmask[GB_UTF_SIZE+1] = {0xc0, 0x80, 0xe0, 0xf0, 0xf8}; +gb_global char32 const gb_utfmin[GB_UTF_SIZE+1] = {0, 0, 0x80, 0x800, 0x10000}; +gb_global char32 const gb_utfmax[GB_UTF_SIZE+1] = {0x10ffff, 0x7f, 0x7ff, 0xffff, 0x10ffff}; + +gb_internal isize +gb__utf_validate(char32 *c, isize i) +{ + GB_ASSERT_NOT_NULL(c); + if (!c) return 0; + if (!gb_is_between(*c, gb_utfmin[i], gb_utfmax[i]) || + gb_is_between(*c, 0xd800, 0xdfff)) { + *c = GB_UTF_INVALID; + } + i = 1; + while (*c > gb_utfmax[i]) { + i++; + } + return i; +} + +gb_internal char32 +gb__utf_decode_byte(char c, isize *i) +{ + GB_ASSERT_NOT_NULL(i); + if (!i) return 0; + for (*i = 0; *i < gb_count_of(gb_utfmask); (*i)++) { + if ((cast(u8)c & gb_utfmask[*i]) == gb_utfbyte[*i]) + return cast(u8)(c & ~gb_utfmask[*i]); + } + return 0; +} + +gb_inline isize gb_utf8_decode(char const *str, char32 *codepoint) { return gb_utf8_decode_len(str, gb_strlen(str), codepoint); } + +isize +gb_utf8_decode_len(char const *s, isize str_len, char32 *c) +{ + isize i, j, len, type = 0; + char32 cd; + + GB_ASSERT_NOT_NULL(s); + GB_ASSERT_NOT_NULL(c); + + if (!s || !c) return 0; + if (!str_len) return 0; + *c = GB_UTF_INVALID; + + cd = gb__utf_decode_byte(s[0], &len); + if (!gb_is_between(len, 1, GB_UTF_SIZE)) + return 1; + + for (i = 1, j = 1; i < str_len && j < len; i++, j++) { + cd = (cd << 6) | gb__utf_decode_byte(s[i], &type); + if (type != 0) + return j; + } + if (j < len) + return 0; + *c = cd; + gb__utf_validate(c, len); + return len; +} + + + + + gb_no_inline void @@ -2935,20 +3102,19 @@ gb_inline gbDllProc gb_dll_proc_address(gbDllHandle dll, char const *proc_name) gb_inline u64 gb_rdtsc(void) { return __rdtsc(); } -gb_global LARGE_INTEGER gb__win32_perf_count_freq = {0}; - gb_inline f64 gb_time_now(void) { + gb_local_persist LARGE_INTEGER win32_perf_count_freq = {0}; f64 result; LARGE_INTEGER counter; - if (!gb__win32_perf_count_freq.QuadPart) - QueryPerformanceFrequency(&gb__win32_perf_count_freq); - GB_ASSERT(gb__win32_perf_count_freq.QuadPart != 0); + if (!win32_perf_count_freq.QuadPart) + QueryPerformanceFrequency(&win32_perf_count_freq); + GB_ASSERT(win32_perf_count_freq.QuadPart != 0); QueryPerformanceCounter(&counter); - result = counter.QuadPart / cast(f64)(gb__win32_perf_count_freq.QuadPart); + result = counter.QuadPart / cast(f64)(win32_perf_count_freq.QuadPart); return result; } diff --git a/gb_gl.h b/gb_gl.h index 7cb9b3a..97efc2f 100644 --- a/gb_gl.h +++ b/gb_gl.h @@ -61,11 +61,18 @@ CREDITS #include "gb_math.h" #endif - #ifndef STB_IMAGE_IMPLEMENTATION #include "stb_image.h" #endif +#ifndef STB_RECT_PACK_IMPLEMENTATION +#include "stb_rect_pack.h" +#endif + +#ifndef STB_TRUETYPE_IMPLEMENTATION +#include "stb_truetype.h" +#endif + #if defined(__cplusplus) extern "C" { @@ -329,13 +336,13 @@ GBGL_DEF i32 gbgl_get_uniform(gbglShader *shader, char const *name); #ifndef GBGL_UNIFORM_SET #define GBGL_UNIFORM_SET -#define gbgl_set_uniform_int(loc, i) glUniform1i(loc, i) -#define gbgl_set_uniform_float(loc, f) glUniform1f(loc, f) -#define gbgl_set_uniform_vec2(loc, v) glUniform2fv(loc, 1, &v.x) -#define gbgl_set_uniform_vec3(loc, v) glUniform3fv(loc, 1, &v.x) -#define gbgl_set_uniform_vec4(loc, v) glUniform4fv(loc, 1, &v.x) -#define gbgl_set_uniform_mat4(loc, mat) glUniformMatrix4fv(loc, 1, false, mat) -#define gbgl_set_uniform_mat4_count(loc, mat, count) glUniformMatrix4fv(loc, count, false, mat) +#define gbgl_set_uniform_int(shdr, name, i) glUniform1i (gbgl_get_uniform(shdr, name), i) +#define gbgl_set_uniform_float(shdr, name, f) glUniform1f (gbgl_get_uniform(shdr, name), f) +#define gbgl_set_uniform_vec2(shdr, name, v) glUniform2fv (gbgl_get_uniform(shdr, name), 1, &v.x) +#define gbgl_set_uniform_vec3(shdr, name, v) glUniform3fv (gbgl_get_uniform(shdr, name), 1, &v.x) +#define gbgl_set_uniform_vec4(shdr, name, v) glUniform4fv (gbgl_get_uniform(shdr, name), 1, &v.x) +#define gbgl_set_uniform_mat4(shdr, name, mat) glUniformMatrix4fv(gbgl_get_uniform(shdr, name), 1, false, mat) +#define gbgl_set_uniform_mat4_count(shdr, name, mat, count) glUniformMatrix4fv(gbgl_get_uniform(shdr, name), count, false, mat) #endif @@ -419,6 +426,110 @@ GBGL_DEF void gbgl_destroy_render_buffer(gbglRenderBuffer *rb); GBGL_DEF void gbgl_render_to_buffer(gbglRenderBuffer const *rb); GBGL_DEF void gbgl_render_to_screen(i32 width, i32 height); + +//////////////////////////////////////////////////////////////// +// +// Font Stuff +// +// + + +typedef struct gbglGlyphMapKVPair { + char32 codepoint; + u16 index; +} gbglGlyphMapKVPair; + +typedef struct gbglGlyphInfo { + f32 s0, t0, s1, t1; + i16 xoff, yoff; + f32 xadv; +} gbglGlyphInfo; + +typedef struct gbglKernPair { + union { + i32 packed; + struct { u16 i0, i1; }; + }; + f32 kern; +} gbglKernPair; + +typedef enum gbglJustifyType { + GBGL_JUSTIFY_LEFT, + GBGL_JUSTIFY_CENTRE, + GBGL_JUSTIFY_RIGHT, +} gbglJustifyType; + +typedef enum gbglTextParamType { + GBGL_TEXT_PARAM_INVALID, + GBGL_TEXT_PARAM_MAX_WIDTH, + GBGL_TEXT_PARAM_JUSTIFY, + GBGL_TEXT_PARAM_TEXTURE_FILTER, + + GBGL_TEXT_PARAM_COUNT, +} gbglTextParamType; + +typedef struct gbglTextParam { + gbglTextParamType type; + union { + f32 val_f32; + i32 val_i32; + gbVec2 val_vec2; + }; +} gbglTextParam; + +typedef struct gbglFont { + isize glyph_count; + isize kern_pair_count; + i32 bitmap_width, bitmap_height; + f32 size; + i32 ascent, descent, line_gap; + char *ttf_filename; + gbglTexture texture; + + gbglGlyphMapKVPair *glyph_map; + gbglGlyphInfo *glyphs; + gbglKernPair *kern_table; + struct gbglFont *next; +} gbglFont; + +typedef struct gbglFontCachedTTF { + char * name; + u8 * ttf; + stbtt_fontinfo finfo; + struct gbglFontCachedTTF *next; +} gbglFontCachedTTF; + +typedef struct gbglFontCache { + isize font_char_list_count; + char *font_char_list; + + isize codepoint_count; + char32 *codepoints; + + stbtt_pack_range *ranges; + stbtt_packedchar *packed_char_data; + stbrp_rect * rect_cache; + + gbglFontCachedTTF *ttf_buffer; + gbglFont * fonts; +} gbglFontCache; + + +GBGL_DEF gbglFont *gbgl_get_font (gbglFontCache *fc, char const *ttf_filename, f32 font_size); +GBGL_DEF gbglFont *gbgl_get_font_only(gbglFontCache *fc, char const *ttf_filename, f32 font_size); +GBGL_DEF gbglFont *gbgl_cache_font (gbglFontCache *fc, char const *ttf_filename, f32 font_size); + +GBGL_DEF i32 gbgl_font_glyph_map_search_proc(void const *a, void const *b); + +GBGL_DEF b32 gbgl_get_packed_font_dim (gbglFontCache *cache, gbglFontCachedTTF *ttf, i32 *width, i32 *height); +GBGL_DEF gbglGlyphInfo *gbgl_get_glyph_info (gbglFont *font, char32 codepoint, isize *out_index); +GBGL_DEF f32 gbgl_get_font_kern_amt_from_glyph_indices(gbglFont *font, isize left_index, isize right_index); +GBGL_DEF void gbgl_get_string_dimenstions (gbglFont *font, char const *str, f32 *out_width, f32 *out_height); +GBGL_DEF f32 gbgl_get_sub_string_width (gbglFont *font, char const *str, isize char_count); +GBGL_DEF i32 gbgl_get_wrapped_line_count (gbglFont *font, char const *str, isize max_len, isize max_width); +GBGL_DEF f32 gbgl_get_string_width (gbglFont *font, char const *str, isize max_len); + + //////////////////////////////////////////////////////////////// // // Basic State @@ -426,21 +537,35 @@ GBGL_DEF void gbgl_render_to_screen(i32 width, i32 height); // -typedef struct gbglBasicVertex { - f32 x, y; - f32 u, v; -} gbglBasicVertex; - - #ifndef GBGL_BS_MAX_VERTEX_COUNT #define GBGL_BS_MAX_VERTEX_COUNT 32 #endif - #ifndef GBGL_BS_MAX_INDEX_COUNT #define GBGL_BS_MAX_INDEX_COUNT 6 #endif +#ifndef GBGL_MAX_RENDER_STRING_LENGTH +#define GBGL_MAX_RENDER_STRING_LENGTH 4096 +#endif + +#ifndef GBGL_TEXT_PARAM_STACK_SIZE +#define GBGL_TEXT_PARAM_STACK_SIZE 128 +#endif + +#ifndef GBGL_FONT_CHAR_LIST +#define GBGL_FONT_CHAR_LIST "Āā㥹ĆćĈĉĊċČčĎďĐđĒēĔĕĖėĘęĚěĜĝĞğĠġĢģĤĥĨĩĪīĬĭĮįİıIJijĴĵĶķĸĹĺĻļĽľŁłŃńŅņņŇňʼnŊŋŌōōŎŏŐőŒœŕŖŗŘřŚśŜŝŞşŠšŢţŤťŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽža!ö\"#$%%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~Šš?ŒœŸÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõøùúûüýþÿ®™" +#endif + +#ifndef GBGL_PT_TO_PX_SCALE +#define GBGL_PT_TO_PX_SCALE (96.0f / 72.0f) +#endif + +typedef struct gbglBasicVertex { + f32 x, y; + f32 u, v; +} gbglBasicVertex; + typedef struct gbglBasicState { gbglBasicVertex vertices[GBGL_BS_MAX_VERTEX_COUNT]; u16 indices[GBGL_BS_MAX_INDEX_COUNT]; @@ -454,8 +579,19 @@ typedef struct gbglBasicState { gbMat4 ortho_mat; i32 width, height; -} gbglBasicState; + gbglFontCache font_cache; + gbglShader font_shader; + gbglBasicVertex font_vertices[GBGL_MAX_RENDER_STRING_LENGTH * 4]; + u16 font_indices[GBGL_MAX_RENDER_STRING_LENGTH * 6]; + u32 font_vao, font_vbo, font_ebo; + char font_text_buffer[GBGL_MAX_RENDER_STRING_LENGTH * 4]; // NOTE(bill): Maximum of 4 bytes per char for utf-8 + u32 font_samplers[2]; + gbglTextParam text_param_stack[GBGL_TEXT_PARAM_STACK_SIZE]; + isize text_param_stack_count; + gbglTextParam text_params[GBGL_TEXT_PARAM_COUNT]; + +} gbglBasicState; GBGL_DEF void gbgl_bs_init(gbglBasicState *bs, i32 window_width, i32 window_height); GBGL_DEF void gbgl_bs_set_resolution(gbglBasicState *bs, i32 window_width, i32 window_height); @@ -464,17 +600,18 @@ GBGL_DEF void gbgl_bs_end(gbglBasicState *bs); GBGL_DEF void gbgl_bs_draw_textured_rect(gbglBasicState *bs, gbglTexture *tex, gbVec2 pos, gbVec2 dim, b32 v_up); GBGL_DEF void gbgl_bs_draw_rect(gbglBasicState *bs, gbVec2 pos, gbVec2 dim, gbColour col); -GBGL_DEF void gbgl_bs_draw_outlined_rect(gbglBasicState *bs, gbVec2 pos, gbVec2 dim, gbColour col, f32 thickness); +GBGL_DEF void gbgl_bs_draw_rect_outline(gbglBasicState *bs, gbVec2 pos, gbVec2 dim, gbColour col, f32 thickness); GBGL_DEF void gbgl_bs_draw_quad(gbglBasicState *bs, - gbVec2 p0, gbVec2 p1, gbVec2 p2, gbVec2 p3, - gbColour col); -GBGL_DEF void gbgl_bs_draw_outlined_quad(gbglBasicState *bs, - gbVec2 p0, gbVec2 p1, gbVec2 p2, gbVec2 p3, - gbColour col, f32 thickness); + gbVec2 p0, gbVec2 p1, gbVec2 p2, gbVec2 p3, + gbColour col); +GBGL_DEF void gbgl_bs_draw_quad_outline(gbglBasicState *bs, + gbVec2 p0, gbVec2 p1, gbVec2 p2, gbVec2 p3, + gbColour col, f32 thickness); GBGL_DEF void gbgl_bs_draw_line(gbglBasicState *bs, gbVec2 p0, gbVec2 p1, gbColour col, f32 thickness); GBGL_DEF void gbgl_bs_draw_circle(gbglBasicState *bs, gbVec2 p, f32 radius, gbColour col); -GBGL_DEF void gbgl_bs_draw_outlined_circle(gbglBasicState *bs, gbVec2 p, f32 radius, gbColour col, f32 thickness); +GBGL_DEF void gbgl_bs_draw_circle_outline(gbglBasicState *bs, gbVec2 p, f32 radius, gbColour col, f32 thickness); + #if defined(__cplusplus) @@ -1132,6 +1269,514 @@ gbgl_destroy_texture(gbglTexture *t) +//////////////////////////////////////////////////////////////// +// +// Font +// +// + +gb_inline i32 +gbgl__sort_kern_pair(void const *a, void const *b) +{ + gbglKernPair *kp0 = cast(gbglKernPair *)a; + gbglKernPair *kp1 = cast(gbglKernPair *)b; + return kp0->packed - kp1->packed; +} + + +gb_inline i32 +gbgl__glypth_map_sort(void const *a, void const *b) +{ + gbglGlyphMapKVPair g0 = *cast(gbglGlyphMapKVPair *)a; + gbglGlyphMapKVPair g1 = *cast(gbglGlyphMapKVPair *)b; + return g0.codepoint - g1.codepoint; +} + + +b32 +gbgl_get_packed_font_dim(gbglFontCache *cache, gbglFontCachedTTF *ttf, i32 *width, i32 *height) +{ + b32 result = true; + stbtt_pack_context spc; + b32 ext_w = true; + isize sanity_counter = 0, i, j; + *width = *height = (1<<5); + for (j = 0; j < cache->codepoint_count; j++) { + cache->rect_cache[j].x = cache->rect_cache[j].y = 0; + } + + for (;;) { + i32 res = stbtt_PackBegin(&spc, 0, *width, *height, 0, 1, 0); + GB_ASSERT(res == 1); + if (res != 1) { + return false; + } else { + stbrp_context *rp_ctx; + b32 fit = true; + + gb_zero_array(cache->rect_cache, cache->codepoint_count); + rp_ctx = cast(stbrp_context *)spc.pack_info; + stbtt_PackFontRangesGatherRects(&spc, &ttf->finfo, cache->ranges, cache->codepoint_count, cache->rect_cache); + STBRP_SORT(cache->rect_cache, cache->codepoint_count, gb_size_of(cache->rect_cache[0]), rect_height_compare); + + for (i = 0; i < cache->codepoint_count; i++) { + stbrp__findresult fr = stbrp__skyline_pack_rectangle(rp_ctx, cache->rect_cache[i].w, cache->rect_cache[i].h); + if (fr.prev_link) { + cache->rect_cache[i].x = cast(stbrp_coord)fr.x; + cache->rect_cache[i].y = cast(stbrp_coord)fr.y; + } else { + int res; + if (ext_w) { + ext_w = false; + *width <<= 1; + } else { + ext_w = true; + *height <<= 1; + } + fit = false; + for (j = 0; j < cache->codepoint_count; j++) { + cache->rect_cache[j].x = cache->rect_cache[j].y = 0; + } + stbtt_PackEnd(&spc); + res = stbtt_PackBegin(&spc, 0, *width, *height, 0, 1, 0); + GB_ASSERT(res == 1); + if (res != 1) { + result = false; + goto done; + } + break; + } + } + if (fit) { + result = true; + goto done; + } + if (++sanity_counter > 32) { + result = false; + goto done; + } + } + } + +done: + stbtt_PackEnd(&spc); + + return result; +} + + +gb_inline gbglFont * +gbgl_get_font(gbglFontCache *fc, char const *ttf_filename, f32 font_size) +{ + gbglFont *f = gbgl_get_font_only(fc, ttf_filename, font_size); + if (f) { + return f; + } else { + return gbgl_cache_font(fc, ttf_filename, font_size); + } +} + + +gb_inline gbglFont * +gbgl_get_font_only(gbglFontCache *fc, char const *ttf_filename, f32 font_size) +{ + gbglFont *f = fc->fonts; + while (f) { + if (f->size == font_size && gb_strcmp(ttf_filename, f->ttf_filename) == 0) { + return f; + } + f = f->next; + } + return NULL; +} + +gbglFont * +gbgl_cache_font(gbglFontCache *fc, char const *ttf_filename, f32 font_size) +{ + gbglFontCache *font_cache = fc; + gbglFont *f = gbgl_get_font_only(fc, ttf_filename, font_size); + gbglFontCachedTTF *ttf = NULL; + isize i; + + if (f) + return f; + + if (!font_cache->fonts) { + font_cache->fonts = cast(gbglFont *)gbgl_malloc(gb_size_of(gbglFont)); + f = font_cache->fonts; + } else { + f = font_cache->fonts; + while (f && f->next) + f = f->next; + f->next = cast(gbglFont *)gbgl_malloc(gb_size_of(gbglFont)); + f = f->next; + } + GB_ASSERT_NOT_NULL(f); + if (!f) + return NULL; + + gb_zero_struct(f); + + // NOTE(bill): Make sure the character list file has been loaded + if (!font_cache->font_char_list) { + isize codepoint_count = 0, cpi = 0; + font_cache->font_char_list = GBGL_FONT_CHAR_LIST; + font_cache->font_char_list_count = gb_strlen(GBGL_FONT_CHAR_LIST); + + for (i = 0; i < font_cache->font_char_list_count; i++) { + char32 c; + isize utf8_len = gb_utf8_decode_len(font_cache->font_char_list + i, font_cache->font_char_list_count, &c); + i += utf8_len-1; + codepoint_count++; + } + + font_cache->codepoint_count = codepoint_count; + font_cache->ranges = cast(stbtt_pack_range *)gbgl_malloc(gb_size_of(stbtt_pack_range) * codepoint_count); + font_cache->codepoints = cast(char32 *) gbgl_malloc(gb_size_of(char32) * codepoint_count); + font_cache->packed_char_data = cast(stbtt_packedchar *)gbgl_malloc(gb_size_of(stbtt_packedchar) * codepoint_count); + font_cache->rect_cache = cast(stbrp_rect *) gbgl_malloc(gb_size_of(stbrp_rect) * codepoint_count); + + if (!font_cache->ranges || !font_cache->codepoints || !font_cache->packed_char_data) { + gb_fprintf(stderr, "Unable to get memory for fonts"); + } + + for (i = 0; i < font_cache->font_char_list_count; i++) { + isize utf8_len = gb_utf8_decode_len(font_cache->font_char_list+i, font_cache->font_char_list_count, font_cache->codepoints+cpi); + i += utf8_len-1; + cpi++; + } + GB_ASSERT(cpi == font_cache->codepoint_count); + for (i = 0; i < font_cache->codepoint_count; i++) { + font_cache->ranges[i].first_unicode_codepoint_in_range = font_cache->codepoints[i]; + font_cache->ranges[i].array_of_unicode_codepoints = 0; + font_cache->ranges[i].num_chars = 1; + font_cache->ranges[i].chardata_for_range = font_cache->packed_char_data + i; + } + } + + { + gbglFontCachedTTF **ttf_cache = &font_cache->ttf_buffer; + + while (*ttf_cache) { + if (gb_strcmp((*ttf_cache)->name, ttf_filename) == 0) + break; + ttf_cache = &(*ttf_cache)->next; + } + if (!*ttf_cache) { + isize name_len; + + *ttf_cache = cast(gbglFontCachedTTF *)gbgl_malloc(gb_size_of(gbglFontCachedTTF)); + GB_ASSERT_NOT_NULL(*ttf_cache); + (*ttf_cache)->name = NULL; + (*ttf_cache)->ttf = NULL; + gb_zero_struct(&(*ttf_cache)->finfo); + (*ttf_cache)->next = NULL; + + + name_len = gb_strlen(ttf_filename); + (*ttf_cache)->name = cast(char *)gbgl_malloc(name_len+1); + gb_strncpy((*ttf_cache)->name, ttf_filename, name_len); + (*ttf_cache)->name[name_len] = '\0'; + + { + gbFile file; + + if (gb_open_file(&file, ttf_filename)) { + i64 len = gb_file_size(&file); + (*ttf_cache)->ttf = cast(u8 *)gbgl_malloc(len); + GB_ASSERT_NOT_NULL((*ttf_cache)->ttf); + + gb_file_read_at(&file, (*ttf_cache)->ttf, len, 0); + + gb_close_file(&file); + } else { + GB_PANIC("Could not open ttf file: %s", ttf_filename); + } + + stbtt_InitFont(&(*ttf_cache)->finfo, (*ttf_cache)->ttf, stbtt_GetFontOffsetForIndex((*ttf_cache)->ttf, 0)); + } + } + ttf = *ttf_cache; + GB_ASSERT_NOT_NULL(ttf); + } + + // NOTE(bill): Set the range for the this look up + for (i = 0; i < font_cache->codepoint_count; i++) + font_cache->ranges[i].font_size = font_size; + + { // NOTE(bill): Figure out smallest non-square power of 2 texture size + i32 w, h; + if (gbgl_get_packed_font_dim(font_cache, ttf, &w, &h)) { + isize str_len, i, j; + + // NOTE(bill): Setup the font data + f->glyph_count = font_cache->codepoint_count; + f->bitmap_width = w; + f->bitmap_height = h; + f->size = font_size; + + str_len = gb_strlen(ttf_filename); + f->ttf_filename = cast(char *)gbgl_malloc(str_len+1); + gb_strncpy(f->ttf_filename, ttf_filename, str_len); + + f->glyph_map = cast(gbglGlyphMapKVPair *)gbgl_malloc(gb_size_of(*f->glyph_map) * f->glyph_count); + f->glyphs = cast(gbglGlyphInfo *) gbgl_malloc(gb_size_of(*f->glyphs) * f->glyph_count); + if (!f->glyph_map || !f->glyphs) { + f = NULL; + return f; + } else { + stbtt_pack_context spc; + u8 *px; + i32 res; + f32 scale; + + px = cast(u8 *)gbgl_malloc(w * h); + res = stbtt_PackBegin(&spc, px, w, h, 0, 1, 0); + GB_ASSERT(res == 1); + res = stbtt_PackFontRanges(&spc, ttf->ttf, 0, font_cache->ranges, font_cache->codepoint_count); + GB_ASSERT(res == 1); + stbtt_PackEnd(&spc); + + gbgl_load_texture2d_from_memory(&f->texture, px, w, h, 1); + + gbgl_free(px); + + scale = stbtt_ScaleForPixelHeight(&ttf->finfo, font_size); + stbtt_GetFontVMetrics(&ttf->finfo, &f->ascent, &f->descent, &f->line_gap); + f->ascent = cast(i32)(cast(f32)f->ascent * scale); + f->descent = cast(i32)(cast(f32)f->descent * scale); + f->line_gap = cast(i32)(cast(f32)f->line_gap * scale); + + for (i = 0; i < f->glyph_count; i++) { + gbglGlyphInfo *gi = f->glyphs + i; + gi->s0 = cast(f32)font_cache->packed_char_data[i].x0; + gi->t0 = cast(f32)font_cache->packed_char_data[i].y0; + gi->s1 = cast(f32)font_cache->packed_char_data[i].x1; + gi->t1 = cast(f32)font_cache->packed_char_data[i].y1; + + gi->xoff = cast(i16)font_cache->packed_char_data[i].xoff; + gi->yoff = cast(i16)font_cache->packed_char_data[i].yoff; + gi->xadv = font_cache->packed_char_data[i].xadvance; + } + + for (i = 0; i < f->glyph_count; i++) { + f->glyph_map[i].codepoint = font_cache->codepoints[i]; + f->glyph_map[i].index = i; + } + + gb_qsort(f->glyph_map, f->glyph_count, gb_size_of(*f->glyph_map), gbgl__glypth_map_sort); + + { // Kerning Table + isize kps_count = 0; + for (i = 0; i < f->glyph_count; i++) { + for (j = 0; j < f->glyph_count; j++) { + i32 kern = stbtt_GetCodepointKernAdvance(&ttf->finfo, font_cache->codepoints[i], font_cache->codepoints[j]); + if (kern != 0) + kps_count++; + } + } + f->kern_pair_count = kps_count; + if (kps_count > 0) { + int ikp = 0; + f->kern_table = cast(gbglKernPair *)gbgl_malloc(gb_size_of(*f->kern_table) * kps_count); + for (i = 0; i < f->glyph_count; i++) { + for (j = 0; j < f->glyph_count; j++) { + isize kern = stbtt_GetCodepointKernAdvance(&ttf->finfo, font_cache->codepoints[i], font_cache->codepoints[j]); + if (kern != 0) { + gbglKernPair *kp = f->kern_table + ikp++; + kp->i0 = cast(u16)i; + kp->i1 = cast(u16)j; + kp->kern = cast(f32)kern * scale; + } + } + } + gb_qsort(f->kern_table, f->kern_pair_count, gb_size_of(f->kern_table[0]), gbgl__sort_kern_pair); + } + } + } + } else { + GB_PANIC("Failure loading font"); + gb_zero_struct(&f); + } + } + return f; +} + + +gb_inline i32 +gbgl_font_glyph_map_search_proc(void const *a, void const *b) +{ + gbglGlyphMapKVPair const *gm = cast(gbglGlyphMapKVPair const *)a; + char32 ucp = *cast(char32 const *)b; + return cast(i32)(cast(i64)gm->codepoint - cast(i64)ucp); +} + +gbglGlyphInfo * +gbgl_get_glyph_info(gbglFont *font, char32 codepoint, isize *out_index) +{ + isize index = gb_binary_search(font->glyph_map, font->glyph_count, gb_size_of(*font->glyph_map), &codepoint, gbgl_font_glyph_map_search_proc); + if (index >= 0) { + GB_ASSERT(codepoint == font->glyph_map[index].codepoint); + if (out_index) + *out_index = font->glyph_map[index].index; + return font->glyphs + font->glyph_map[index].index; + } + return NULL; +} + +f32 +gbgl_get_font_kern_amt_from_glyph_indices(gbglFont *font, isize left_index, isize right_index) +{ + isize needle = (right_index << 16) | (left_index & 0xff); + + isize f = 0; + isize l = font->kern_pair_count - 1; + isize m = (f + l) >> 1; + + while (f <= l) { + isize cmp = font->kern_table[m].packed - needle; + if (cmp < 0) + f = m + 1; + else if (cmp > 0) + l = m - 1; + else + return font->kern_table[m].kern; + m = (f + l) >> 1; + } + return 0.0f; +} + +void +gbgl_get_string_dimenstions(gbglFont *font, char const *str, f32 *out_width, f32 *out_height) +{ + isize len, char_count, i; + + f32 w = 0.0f; + f32 h = 0.0f; + char const *ptr = str; + + len = gb_strlen(str); + char_count = gb_utf8_strnlen(str, len); + + for (i = 0; i < char_count; i++) { + char32 cp; + isize byte_len, curr_index; + gbglGlyphInfo *gi; + + byte_len = gb_utf8_decode_len(ptr, len-(ptr-str), &cp); + ptr += byte_len; + gi = gbgl_get_glyph_info(font, cp, &curr_index); + if (gi) { + f32 kern = 0; + if (i < char_count-1) { + isize next_index; + char32 next_cp = 0; + gbglGlyphInfo *ngi; + gb_utf8_decode_len(ptr, len-(ptr-str), &next_cp); + ngi = gbgl_get_glyph_info(font, next_cp, &next_index); + if (ngi) kern = gbgl_get_font_kern_amt_from_glyph_indices(font, curr_index, next_index); + } + w += gi->xadv + kern; + } + } + + if (out_width) *out_width = w; + if (out_height) *out_height = h; +} + +f32 +gbgl_get_sub_string_width(gbglFont *font, char const *str, isize char_count) +{ + isize i, len; + f32 w = 0; + char const *ptr = str; + len = gb_strlen(str); + for (i = 0; i < char_count; i++) { + if (*ptr == 0) { + break; + } else { + char32 cp; + isize byte_len, curr_index; + f32 kern = 0; + gbglGlyphInfo *gi; + + byte_len = gb_utf8_decode_len(ptr, len-(ptr-str), &cp); + ptr += byte_len; + if (ptr - str > char_count) + break; + + gi = gbgl_get_glyph_info(font, cp, &curr_index); + if (i < char_count-1) { + isize next_index; + char32 next_cp = 0; + gb_utf8_decode_len(ptr, len-(ptr-str), &next_cp); + gbgl_get_glyph_info(font, next_cp, &next_index); + kern = gbgl_get_font_kern_amt_from_glyph_indices(font, curr_index, next_index); + } + w += gi->xadv + kern; + } + + } + return w; +} + +i32 +gbgl_get_wrapped_line_count(gbglFont *font, char const *str, isize max_len, isize max_width) +{ + isize i, str_len, char_count, line_count = 1; + f32 w = 0; + char const *ptr = str; + + str_len = gb_strnlen(str, max_len); + char_count = gb_utf8_strnlen(str, str_len); + + for (i = 0; i < char_count; i++) { + char32 cp; + isize byte_len, curr_index; + gbglGlyphInfo *gi; + f32 kern = 0; + + byte_len = gb_utf8_decode_len(ptr, str_len-(ptr-str), &cp); + ptr += byte_len; + // NOTE(bill): Check calculation here + if (ptr-str >= max_len-6) + break; + + gi = gbgl_get_glyph_info(font, cp, &curr_index); + if (gi) { + if (w + gi->xadv >= cast(f32)max_width) { + line_count++; + w = 0.0f; + } + } + + if (i < char_count-1) { + char32 next_cp; + isize next_index; + gb_utf8_decode_len(ptr, str_len-(ptr-str), &next_cp); + + gbgl_get_glyph_info(font, next_cp, &next_index); + kern = gbgl_get_font_kern_amt_from_glyph_indices(font, curr_index, next_index); + } + + if (gi) { + w += gi->xadv + kern; + } + } + + return line_count; +} + +gb_inline f32 +gbgl_get_string_width(gbglFont *font, char const *str, isize max_len) +{ + isize len = gb_strnlen(str, max_len); + isize char_count = gb_utf8_strnlen(str, len); + return gbgl_get_sub_string_width(font, str, char_count); +} + + + //////////////////////////////////////////////////////////////// // @@ -1166,7 +1811,7 @@ gbgl_bs_init(gbglBasicState *bs, i32 window_width, i32 window_height) bs->linear_sampler = gbgl_generate_sampler(GL_LINEAR, GL_LINEAR, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE); gbgl_load_shader_vf_from_source(&bs->ortho_tex_shader, - "#version 410 core\n" + "#version 330 core\n" "layout (location = 0) in vec4 a_position;\n" "layout (location = 1) in vec2 a_tex_coord;\n" "uniform mat4 u_ortho_mat;\n" @@ -1176,7 +1821,7 @@ gbgl_bs_init(gbglBasicState *bs, i32 window_width, i32 window_height) " v_tex_coord = a_tex_coord;\n" "}\n", - "#version 410 core\n" + "#version 330 core\n" "precision mediump float;" "in vec2 v_tex_coord;\n" "layout (binding = 0) uniform sampler2D u_tex;\n" @@ -1186,7 +1831,7 @@ gbgl_bs_init(gbglBasicState *bs, i32 window_width, i32 window_height) "}\n"); gbgl_load_shader_vf_from_source(&bs->ortho_col_shader, - "#version 410 core\n" + "#version 330 core\n" "precision mediump float;" "layout (location = 0) in vec4 a_position;\n" "uniform mat4 u_ortho_mat;\n" @@ -1194,12 +1839,57 @@ gbgl_bs_init(gbglBasicState *bs, i32 window_width, i32 window_height) " gl_Position = u_ortho_mat * a_position;\n" "}\n", - "#version 410 core\n" + "#version 330 core\n" "uniform vec4 u_colour;\n" "out vec4 o_colour;\n" "void main(void) {\n" " o_colour = u_colour;\n" "}\n"); + + + gbgl_load_shader_vf_from_source(&bs->font_shader, + "#version 330 core\n" + "layout (location = 0) in vec4 a_position;\n" + "layout (location = 1) in vec2 a_tex_coord;\n" + "uniform mat4 u_ortho_mat;\n" + "out vec2 v_tex_coord;\n" + "void main(void) {\n" + " gl_Position = u_ortho_mat * a_position;\n" + " v_tex_coord = a_tex_coord;\n" + "}\n", + + "#version 330 core\n" + "in vec2 v_tex_coord;\n" + "layout (location = 0) uniform vec4 u_colour;\n" + "layout (binding = 0) uniform sampler2D u_tex;\n" + "out vec4 o_colour;\n" + "void main(void {\n" + " float alpha = texture2D(u_tex, v_tex_coord).r;\n" + " o_colour = u_colour * alpha;\n" + "\n" + ); + + glGenVertexArrays(1, &bs->font_vao); + glBindVertexArray(bs->font_vao); + + bs->font_vbo = gbgl_make_vbo(NULL, gb_size_of(gbglBasicVertex) * GBGL_MAX_RENDER_STRING_LENGTH * 4, GL_DYNAMIC_DRAW); + + for (i = 0; i < GBGL_MAX_RENDER_STRING_LENGTH; i++) { + bs->font_indices[i*6 + 0] = i*4 + 0; + bs->font_indices[i*6 + 1] = i*4 + 1; + bs->font_indices[i*6 + 2] = i*4 + 2; + bs->font_indices[i*6 + 3] = i*4 + 2; + bs->font_indices[i*6 + 4] = i*4 + 3; + bs->font_indices[i*6 + 5] = i*4 + 0; + } + bs->font_ebo = gbgl_make_ebo(bs->font_indices, gb_size_of(u16) * GBGL_MAX_RENDER_STRING_LENGTH * 6, GL_STATIC_DRAW); + + bs->font_samplers[0] = gbgl_generate_sampler(GL_NEAREST, GL_NEAREST, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE); + bs->font_samplers[1] = gbgl_generate_sampler(GL_LINEAR, GL_LINEAR, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE); + + bs->text_params[GBGL_TEXT_PARAM_MAX_WIDTH].val_i32 = 0; + bs->text_params[GBGL_TEXT_PARAM_JUSTIFY].val_i32 = GBGL_JUSTIFY_LEFT; + bs->text_params[GBGL_TEXT_PARAM_TEXTURE_FILTER].val_i32 = 0; } void @@ -1252,7 +1942,7 @@ gbgl_bs_draw_textured_rect(gbglBasicState *bs, gbglTexture *tex, gbVec2 pos, gbV bs->vertices[3].v = v_up ? 1.0f : 0.0f; gbgl_use_shader(&bs->ortho_tex_shader); - gbgl_set_uniform_mat4(gbgl_get_uniform(&bs->ortho_tex_shader, "u_ortho_mat"), bs->ortho_mat.e); + gbgl_set_uniform_mat4(&bs->ortho_tex_shader, "u_ortho_mat", bs->ortho_mat.e); gbgl_bind_texture2d(tex, 0, bs->nearest_sampler); gbgl_vbo_copy(bs->vbo, bs->vertices, 4*gb_size_of(bs->vertices[0]), 0); @@ -1280,15 +1970,15 @@ gbgl_bs_draw_rect(gbglBasicState *bs, gbVec2 pos, gbVec2 dim, gbColour col) } void -gbgl_bs_draw_outlined_rect(gbglBasicState *bs, gbVec2 pos, gbVec2 dim, gbColour col, f32 thickness) +gbgl_bs_draw_rect_outline(gbglBasicState *bs, gbVec2 pos, gbVec2 dim, gbColour col, f32 thickness) { - gbgl_bs_draw_outlined_quad(bs, - gb_vec2(pos.x, pos.y), - gb_vec2(pos.x+dim.x, pos.y), - gb_vec2(pos.x+dim.x, pos.y+dim.y), - gb_vec2(pos.x, pos.y+dim.y), - col, - thickness); + gbgl_bs_draw_quad_outline(bs, + gb_vec2(pos.x, pos.y), + gb_vec2(pos.x+dim.x, pos.y), + gb_vec2(pos.x+dim.x, pos.y+dim.y), + gb_vec2(pos.x, pos.y+dim.y), + col, + thickness); } @@ -1298,8 +1988,8 @@ gbgl__bs_setup_ortho_colour_state(gbglBasicState *bs, isize vertex_count, gbColo gbVec4 vcol = gb_vec4(col.r/255.0f, col.g/255.0f, col.b/255.0f, col.a/255.0f); gbgl_use_shader(&bs->ortho_col_shader); - gbgl_set_uniform_mat4(gbgl_get_uniform(&bs->ortho_col_shader, "u_ortho_mat"), bs->ortho_mat.e); - gbgl_set_uniform_vec4(gbgl_get_uniform(&bs->ortho_col_shader, "u_colour"), vcol); + gbgl_set_uniform_mat4(&bs->ortho_col_shader, "u_ortho_mat", bs->ortho_mat.e); + gbgl_set_uniform_vec4(&bs->ortho_col_shader, "u_colour", vcol); gbgl_vbo_copy(bs->vbo, bs->vertices, vertex_count*gb_size_of(bs->vertices[0]), 0); gbgl_vert_ptr_aa(0, 2, gbglBasicVertex, x); @@ -1332,8 +2022,7 @@ gbgl_bs_draw_quad(gbglBasicState *bs, } void -gbgl_bs_draw_outlined_quad(gbglBasicState *bs, - gbVec2 p0, gbVec2 p1, gbVec2 p2, gbVec2 p3, gbColour col, f32 thickness) +gbgl_bs_draw_quad_outline(gbglBasicState *bs, gbVec2 p0, gbVec2 p1, gbVec2 p2, gbVec2 p3, gbColour col, f32 thickness) { bs->vertices[0].x = p0.x; bs->vertices[0].y = p0.y; @@ -1388,7 +2077,7 @@ gbgl_bs_draw_circle(gbglBasicState *bs, gbVec2 p, f32 radius, gbColour col) } void -gbgl_bs_draw_outlined_circle(gbglBasicState *bs, gbVec2 p, f32 radius, gbColour col, f32 thickness) +gbgl_bs_draw_circle_outline(gbglBasicState *bs, gbVec2 p, f32 radius, gbColour col, f32 thickness) { isize i;