/* gb.h - v0.04b - OpenGL Helper Library - public domain - no warranty implied; use at your own risk This is a single header file with a bunch of useful stuff for working with OpenGL =========================================================================== YOU MUST #define GBGL_IMPLEMENTATION in EXACTLY _one_ C or C++ file that includes this header, BEFORE the include like this: #define GBGL_IMPLEMENTATION #include "gb_gl.h" All other files should just #include "gb_gl.h" without #define Dependencies NOTE: You may need change the path This library REQUIRES "stb_image.h" for loading images from file This library REQUIRES "gb.h" at this moment in time. If you are using the font library (e.g. GBGL_NO_FONTS is _not_ defined): This library then REQUIRES "stb_truetype.h" for ttf handling This library then REQUIRES "stb_rect_pack.h" NOTE(bill): I may remove these dependencies for the font handling by embedding the needed types and procedures. Optional Dependencies "gb_math.h" as a lot of useful things in it over . Why not have a look at it? Optional Defines GBGL_NO_FONTS - Do no use font subsystem GBGL_NO_BASIC_STATE - Do no use basic state subsystem Steps for supporting dynamic reload: You _MUST_ defined you own malloc and free that use whatever permanent memory systen you are using: #define gbgl_malloc #define gbgl_free =========================================================================== Conventions used: gbglTypesAreLikeThis (None core types) gbgl_functions_and_variables_like_this Prefer // Comments Never use _t suffix for types (I think they are stupid...) Version History: 0.04b - Work with the new gb.h 0.04a - Better Documentation 0.04 - Remove gb_math.h dependency 0.03a - Better Rounded Rect 0.03 - Basic State Rendering 0.02 - Font Caching and Rendering 0.01 - Initial Version LICENSE This software is dual-licensed to the public domain and under the following license: you are granted a perpetual, irrevocable license to copy, modify, publish, and distribute this file as you see fit. WARNING - This library is _highly_ experimental and features may not work as expected. - This also means that many functions are not documented. CREDITS Written by Ginger Bill */ #ifndef GBGL_INCLUDE_GB_GL_H #define GBGL_INCLUDE_GB_GL_H #ifndef GB_IMPLEMENTATION #include "gb.h" #endif #ifndef STB_IMAGE_IMPLEMENTATION #include "stb_image.h" #endif #if !defined(GBGL_NO_FONTS) #ifndef STB_RECT_PACK_IMPLEMENTATION #include "stb_rect_pack.h" #endif #ifndef STB_TRUETYPE_IMPLEMENTATION #include "stb_truetype.h" #endif #endif #if defined(GBGL_USE_GB_MATH) #ifndef GB_MATH_IMPLEMENTATION #include "gb_math.h" #endif #define gbgl_lerp(x, y, t) gb_lerp(x, y, t) #define gbgl_sin(x) gb_sin(x) #define gbgl_cos(x) gb_cos(x) #define gbgl_abs(x) gb_abs(x) #define gbgl_min(x, y) gb_min(x, y) #define gbgl_max(x, y) gb_max(x, y) #define gbgl_round(x) gb_round(x) #else #if !defined(GBGL_USE_CUSTOM_MATH) #include #define gbgl_lerp(x, y, t) ((x)*(1.0f-(t)) + (y)*(t)) #define gbgl_sin(x) sinf(x) #define gbgl_cos(x) cosf(x) #define gbgl_abs(x) ((x) >= 0 ? (x) : -(x)) #define gbgl_min(x, y) ((x) < (y) ? (x) : (y)) #define gbgl_max(x, y) ((x) > (y) ? (x) : (y)) #define gbgl_round(x) (((x) >= 0.0f) ? floorf((x) + 0.5f) : ceilf((x) - 0.5f)) #endif #endif #if defined(__cplusplus) extern "C" { #endif #ifndef GBGL_DEF #define GBGL_DEF extern #endif #ifndef gbgl_malloc #define gbgl_malloc(sz) malloc(sz) #endif #ifndef gbgl_free #define gbgl_free(ptr) free(ptr) #endif #ifndef GBGL_TAU #define GBGL_TAU 6.28318530717958647692528676655900576f #endif //////////////////////////////////////////////////////////////// // // Generic Stuff // // #ifndef GBGL_VERT_PTR_AA_GENERIC #define GBGL_VERT_PTR_AA_GENERIC // NOTE(bill) The "default" is just the f32 version #define gbgl_vert_ptr_aa(index, element_count, Type, var_name) \ gbgl_vert_ptr_aa_f32(index, element_count, Type, var_name) #define gbgl_vert_ptr_aa_f32(index, element_count, Type, var_name) do { \ glVertexAttribPointer(index, \ element_count, \ GL_FLOAT, \ false, \ gb_size_of(Type), \ (void const *)(gb_offset_of(Type, var_name))); \ glEnableVertexAttribArray(index); \ } while (0) #define gbgl_vert_ptr_aa_u8(index, element_count, Type, var_name) do { \ glVertexAttribPointer(index, \ element_count, \ GL_UNSIGNED_BYTE, \ false, \ gb_size_of(Type), \ (void const *)(gb_offset_of(Type, var_name))); \ glEnableVertexAttribArray(index); \ } while (0) #define gbgl_vert_ptr_aa_u8n(index, element_count, Type, var_name) do { \ glVertexAttribPointer(index, \ element_count, \ GL_UNSIGNED_BYTE, \ true, \ gb_size_of(Type), \ (void const *)(gb_offset_of(Type, var_name))); \ glEnableVertexAttribArray(index); \ } while (0) #define gbgl_vert_ptr_aa_u32(index, element_count, Type, var_name) do { \ glVertexAttribIPointer(index, \ element_count, \ GL_UNSIGNED_INT, \ gb_size_of(Type), \ (void const *)(gb_offset_of(Type, var_name))); \ glEnableVertexAttribArray(index); \ } while (0) #define gbgl_vert_ptr_aa_u16(index, element_count, Type, var_name) do { \ glVertexAttribIPointer(index, \ element_count, \ GL_UNSIGNED_SHORT, \ gb_size_of(Type), \ (void const *)(gb_offset_of(Type, var_name))); \ glEnableVertexAttribArray(index); \ } while (0) #define gbgl_vert_ptr_aa_u16n(index, element_count, Type, var_name) do { \ glVertexAttribPointer(index, \ element_count, \ GL_UNSIGNED_SHORT, \ true, \ gb_size_of(Type), \ (void const *)(gb_offset_of(Type, var_name))); \ glEnableVertexAttribArray(index); \ } while (0) #endif GBGL_DEF u32 gbgl_make_sampler(u32 min_filter, u32 max_filter, u32 s_wrap, u32 t_wrap); //////////////////////////////////////////////////////////////// // // Data Buffers // // typedef enum gbglBufferDataType { GBGL_BDT_U8 = GL_UNSIGNED_BYTE, GBGL_BDT_I8 = GL_BYTE, GBGL_BDT_U16 = GL_UNSIGNED_SHORT, GBGL_BDT_I16 = GL_SHORT, GBGL_BDT_F16 = GL_HALF_FLOAT, GBGL_BDT_U32 = GL_UNSIGNED_INT, GBGL_BDT_I32 = GL_INT, GBGL_BDT_F32 = GL_FLOAT, GBGL_BDT_F8, // NOTE(bill): This is not a "real" OpenGL type but it is needed for internal format enums } gbglBufferDataType; // NOTE(bill) index+1 = channels count #if defined(GBGL_USE_SRGB_TEXTURE_FORMAT) i32 const GBGL_TEXTURE_FORMAT[4] = { GL_RED, GL_RG, GL_SRGB8, GL_SRGB8_ALPHA8 }; #else i32 const GBGL_TEXTURE_FORMAT[4] = { GL_RED, GL_RG, GL_RGB, GL_RGBA }; #endif i32 const GBGL_INTERNAL_TEXTURE_FORMAT_8[4] = { GL_R8, GL_RG8, GL_RGB8, GL_RGBA8 }; i32 const GBGL_INTERNAL_TEXTURE_FORMAT_16[4] = { GL_R16, GL_RG16, GL_RGB16, GL_RGBA16 }; i32 const GBGL_INTERNAL_TEXTURE_FORMAT_32[4] = { GL_R32F, GL_RG32F, GL_RGB32F, GL_RGBA32F }; i32 const GBGL_INTERNAL_TEXTURE_FORMAT_U8[4] = { GL_R8UI, GL_RG8UI, GL_RGB8UI, GL_RGB8UI }; i32 const GBGL_INTERNAL_TEXTURE_FORMAT_I8[4] = { GL_R8I, GL_RG8I, GL_RGB8I, GL_RGB8I }; i32 const GBGL_INTERNAL_TEXTURE_FORMAT_F8[4] = { GL_R8, GL_RG8, GL_RGB8, GL_RGB8 }; i32 const GBGL_INTERNAL_TEXTURE_FORMAT_U16[4] = { GL_R16UI, GL_RG16UI, GL_RGB16UI, GL_RGB16UI }; i32 const GBGL_INTERNAL_TEXTURE_FORMAT_I16[4] = { GL_R16I, GL_RG16I, GL_RGB16I, GL_RGB16I }; i32 const GBGL_INTERNAL_TEXTURE_FORMAT_F16[4] = { GL_R16F, GL_RG16F, GL_RGB16F, GL_RGB16F }; i32 const GBGL_INTERNAL_TEXTURE_FORMAT_U32[4] = { GL_R32UI, GL_RG32UI, GL_RGB32UI, GL_RGBA32UI }; i32 const GBGL_INTERNAL_TEXTURE_FORMAT_I32[4] = { GL_R32I, GL_RG32I, GL_RGB32I, GL_RGBA32I }; i32 const GBGL_INTERNAL_TEXTURE_FORMAT_F32[4] = { GL_R32F, GL_RG32F, GL_RGB32F, GL_RGBA32F }; gb_inline i32 gbgl__get_texture_format(gbglBufferDataType data_type, i32 channel_count) { GB_ASSERT(channel_count >= 1 && channel_count <= 4); switch (data_type) { case GBGL_BDT_U8: return GBGL_INTERNAL_TEXTURE_FORMAT_U8[channel_count-1]; case GBGL_BDT_I8: return GBGL_INTERNAL_TEXTURE_FORMAT_I8[channel_count-1]; case GBGL_BDT_F8: return GBGL_INTERNAL_TEXTURE_FORMAT_F8[channel_count-1]; case GBGL_BDT_U16: return GBGL_INTERNAL_TEXTURE_FORMAT_U16[channel_count-1]; case GBGL_BDT_I16: return GBGL_INTERNAL_TEXTURE_FORMAT_I16[channel_count-1]; case GBGL_BDT_F16: return GBGL_INTERNAL_TEXTURE_FORMAT_F16[channel_count-1]; case GBGL_BDT_U32: return GBGL_INTERNAL_TEXTURE_FORMAT_U32[channel_count-1]; case GBGL_BDT_I32: return GBGL_INTERNAL_TEXTURE_FORMAT_I32[channel_count-1]; case GBGL_BDT_F32: return GBGL_INTERNAL_TEXTURE_FORMAT_F32[channel_count-1]; } return GBGL_INTERNAL_TEXTURE_FORMAT_F32[4-1]; } typedef struct gbglTBO { u32 buffer_obj_handle; u32 buffer_handle; } gbglTBO; // NOTE(bill): usage_hint == (GL_STATIC_DRAW, GL_STREAM_DRAW, GL_DYNAMIC_DRAW) GBGL_DEF u32 gbgl_make_vbo(void const *data, isize size, i32 usage_hint); GBGL_DEF u32 gbgl_make_ebo(void const *data, isize size, i32 usage_hint); GBGL_DEF gbglTBO gbgl_make_tbo(gbglBufferDataType data_type, i32 channel_count, void const *data, isize size, i32 usage_hint); GBGL_DEF void gbgl_vbo_copy(u32 vbo_handle, void *const data, isize size, isize offset); GBGL_DEF void gbgl_ebo_copy(u32 ebo_handle, void *const data, isize size, isize offset); GBGL_DEF void gbgl_tbo_copy(gbglTBO tbo, void *const data, isize size, isize offset); GBGL_DEF void gbgl_bind_vbo(u32 vbo_handle); GBGL_DEF void gbgl_bind_ebo(u32 ebo_handle); GBGL_DEF void gbgl_bind_tbo(gbglTBO tbo, i32 sampler_handle, i32 tex_unit); // NOTE(bill): access = GL_WRITE_ONLY, etc. GBGL_DEF void *gbgl_map_vbo(u32 vbo_handle, i32 access); GBGL_DEF void *gbgl_map_ebo(u32 ebo_handle, i32 access); GBGL_DEF void gbgl_unmap_vbo(void); GBGL_DEF void gbgl_unmap_ebo(void); //////////////////////////////////////////////////////////////// // // Shader // // typedef enum gbglShaderType { GBGL_SHADER_TYPE_VERTEX, GBGL_SHADER_TYPE_FRAGMENT, GBGL_SHADER_TYPE_GEOMETRY, GBGL_SHADER_TYPE_COUNT, } gbglShaderType; i32 const GBGL_SHADER_TYPE[GBGL_SHADER_TYPE_COUNT] = { GL_VERTEX_SHADER, /* GBGL_SHADER_TYPE_VERTEX */ GL_FRAGMENT_SHADER, /* GBGL_SHADER_TYPE_FRAGMENT */ GL_GEOMETRY_SHADER, /* GBGL_SHADER_TYPE_GEOMETRY */ }; typedef enum gbglShaderError { GBGL_SHADER_ERROR_NONE, GBGL_SHADER_ERROR_SHADER_COMPILE, GBGL_SHADER_ERROR_LINKING, GBGL_SHADER_ERROR_UNABLE_TO_READ_FILE, GBGL_SHADER_ERROR_COUNT, } gbglShaderError; #ifndef GBGL_MAX_UNIFORM_COUNT #define GBGL_MAX_UNIFORM_COUNT 32 #endif typedef struct gbglShader { u32 shaders[GBGL_SHADER_TYPE_COUNT]; u32 program; i32 uniform_locs[GBGL_MAX_UNIFORM_COUNT]; char *uniform_names[GBGL_MAX_UNIFORM_COUNT]; i32 uniform_count; u32 type_flags; gbFile files[GBGL_SHADER_TYPE_COUNT]; char base_name[64]; } gbglShader; #ifndef GBGL_SHADER_FILE_EXTENSIONS_DEFINED #define GBGL_SHADER_FILE_EXTENSIONS_DEFINED gb_global char const *GBGL_SHADER_FILE_EXTENSIONS[GBGL_SHADER_TYPE_COUNT] = {".vs", ".fs", ".gs"}; #endif GBGL_DEF gbglShaderError gbgl_load_shader_from_file (gbglShader *s, u32 type_bits, char const *filename); GBGL_DEF gbglShaderError gbgl_load_shader_from_memory_vf (gbglShader *s, char const *vert_source, char const *frag_source); GBGL_DEF gbglShaderError gbgl_load_shader_from_memory_vfg(gbglShader *s, char const *vert_source, char const *frag_source, char const *geom_source); GBGL_DEF void gbgl_destroy_shader (gbglShader *shader); GBGL_DEF b32 gbgl_has_shader_changed(gbglShader *shader); GBGL_DEF b32 gbgl_reload_shader (gbglShader *shader); // TODO(bill): Return an error code? GBGL_DEF void gbgl_use_shader (gbglShader *shader); GBGL_DEF b32 gbgl_is_shader_in_use (gbglShader *shader); GBGL_DEF i32 gbgl_get_uniform(gbglShader *shader, char const *name); GBGL_DEF void gbgl_set_uniform_int (gbglShader *s, char const *name, i32 i); GBGL_DEF void gbgl_set_uniform_float (gbglShader *s, char const *name, f32 f); GBGL_DEF void gbgl_set_uniform_vec2 (gbglShader *s, char const *name, f32 const *v); GBGL_DEF void gbgl_set_uniform_vec3 (gbglShader *s, char const *name, f32 const *v); GBGL_DEF void gbgl_set_uniform_vec4 (gbglShader *s, char const *name, f32 const *v); GBGL_DEF void gbgl_set_uniform_mat4 (gbglShader *s, char const *name, f32 const *m); GBGL_DEF void gbgl_set_uniform_mat4_count(gbglShader *s, char const *name, f32 const *m, isize count); GBGL_DEF void gbgl_set_uniform_colour (gbglShader *s, char const *name, gbColour col); //////////////////////////////////////////////////////////////// // // Texture // // typedef enum gbglTextureType { GBGL_TEXTURE_TYPE_2D, GBGL_TEXTURE_TYPE_CUBE_MAP, GBGL_TEXTURE_TYPE_COUNT, } gbglTextureType; gb_global i32 const GBGL_TEXTURE_TYPE[GBGL_TEXTURE_TYPE_COUNT] = { GL_TEXTURE_2D, /* GBGL_TEXTURE_TYPE_2D */ GL_TEXTURE_CUBE_MAP, /* GBGL_TEXTURE_TYPE_CUBE_MAP */ }; typedef struct gbglTexture { i32 width, height, channel_count; gbglBufferDataType data_type; gbglTextureType type; u32 handle; } gbglTexture; GBGL_DEF b32 gbgl_load_texture2d_from_file (gbglTexture *texture, b32 flip_vertically, char const *filename, ...); GBGL_DEF b32 gbgl_load_texture2d_from_memory(gbglTexture *texture, void const *data, i32 width, i32 height, i32 channel_count); GBGL_DEF b32 gbgl_init_texture2d_coloured (gbglTexture *texture, gbColour colour); GBGL_DEF void gbgl_destroy_texture (gbglTexture *texture); GBGL_DEF void gbgl_bind_texture2d(gbglTexture const *texture, u32 position, u32 sampler); //////////////////////////////////////////////////////////////// // // Render Buffer // // // TODO(bill): Record depth and stencil and numerous colour attachments typedef struct gbglRenderBuffer { i32 width, height; i32 channel_count; u32 handle; gbglTexture colour_texture; } gbglRenderBuffer; #define GBGL_MAX_RENDER_COLOUR_BUFFERS 16 gb_global u32 const GBGL_COLOUR_BUFFER_ATTACHMENTS[GBGL_MAX_RENDER_COLOUR_BUFFERS] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2, GL_COLOR_ATTACHMENT3, GL_COLOR_ATTACHMENT4, GL_COLOR_ATTACHMENT5, GL_COLOR_ATTACHMENT6, GL_COLOR_ATTACHMENT7, GL_COLOR_ATTACHMENT8, GL_COLOR_ATTACHMENT9, GL_COLOR_ATTACHMENT10, GL_COLOR_ATTACHMENT11, GL_COLOR_ATTACHMENT12, GL_COLOR_ATTACHMENT13, GL_COLOR_ATTACHMENT14, GL_COLOR_ATTACHMENT15, }; GBGL_DEF b32 gbgl_init_render_buffer (gbglRenderBuffer *rb, i32 width, i32 height, i32 channel_count); 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 // // #if !defined(GBGL_NO_FONTS) 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; }; } 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; // NOTE(bill): Allow as linked list } 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; #if 0 GBGL_DEF void gbgl_destroy_font_cache(gbglFontCache *fc); #endif // NOTE(bill): gbgl_load_font_from_file will load from file if it is not found GBGL_DEF gbglFont *gbgl_load_font_from_file (gbglFontCache *fc, char const *ttf_filename, f32 font_size); GBGL_DEF gbglFont *gbgl_get_font_only_from_cache(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 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_kerning_from_glyph_indices(gbglFont *font, isize left_index, isize right_index); GBGL_DEF void gbgl_get_string_dimensions (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); #endif //////////////////////////////////////////////////////////////// // // Basic State // // #if !defined(GBGL_NO_BASIC_STATE) #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 #if !defined(GBGL_NO_FONTS) #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{|}~"\ "ŠšŒœŸÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõøùúûüýþÿ®™£"\ " \t\r\n" #endif #ifndef GBGL_PT_TO_PX_SCALE #define GBGL_PT_TO_PX_SCALE (96.0f / 72.0f) #endif #ifndef GBGL_TAB_CHARACTER_WIDTH #define GBGL_TAB_CHARACTER_WIDTH 4 #endif #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]; u32 vao, vbo, ebo; u32 nearest_sampler; u32 linear_sampler; gbglShader ortho_tex_shader; gbglShader ortho_col_shader; f32 ortho_mat[16]; i32 width, height; #if !defined(GBGL_NO_FONTS) 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]; #endif } 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); GBGL_DEF void gbgl_bs_begin(gbglBasicState *bs, i32 window_width, i32 window_height); GBGL_DEF void gbgl_bs_end(gbglBasicState *bs); GBGL_DEF void gbgl_bs_draw_textured_rect(gbglBasicState *bs, gbglTexture *tex, f32 x, f32 y, f32 w, f32 h, b32 v_up); GBGL_DEF void gbgl_bs_draw_rect(gbglBasicState *bs, f32 x, f32 y, f32 w, f32 h, gbColour col); GBGL_DEF void gbgl_bs_draw_rect_outline(gbglBasicState *bs, f32 x, f32 y, f32 w, f32 h, gbColour col, f32 thickness); GBGL_DEF void gbgl_bs_draw_quad(gbglBasicState *bs, f32 x0, f32 y0, f32 x1, f32 y1, f32 x2, f32 y2, f32 x3, f32 y3, gbColour col); GBGL_DEF void gbgl_bs_draw_quad_outline(gbglBasicState *bs, f32 x0, f32 y0, f32 x1, f32 y1, f32 x2, f32 y2, f32 x3, f32 y3, gbColour col, f32 thickness); GBGL_DEF void gbgl_bs_draw_line(gbglBasicState *bs, f32 x0, f32 y0, f32 x1, f32 y1, gbColour col, f32 thickness); GBGL_DEF void gbgl_bs_draw_elliptical_arc(gbglBasicState *bs, f32 x, f32 y, f32 radius_a, f32 radius_b, f32 min_angle, f32 max_angle, gbColour col); GBGL_DEF void gbgl_bs_draw_elliptical_arc_outline(gbglBasicState *bs, f32 x, f32 y, f32 radius_a, f32 radius_b, f32 min_angle, f32 max_angle, gbColour col, f32 thickness); GBGL_DEF void gbgl_bs_draw_circle(gbglBasicState *bs, f32 x, f32 y, f32 radius, gbColour col); GBGL_DEF void gbgl_bs_draw_circle_outline(gbglBasicState *bs, f32 x, f32 y, f32 radius, gbColour col, f32 thickness); // Corners Flags: // 1 - Bottom Left // 2 - Bottom Right // 4 - Top Right // 8 - Top Left // NOTE(bill): Apple, please don't sue me! GBGL_DEF void gbgl_bs_draw_rounded_rect_corners(gbglBasicState *bs, f32 x, f32 y, f32 w, f32 h, f32 roundness, gbColour col, u32 corners); GBGL_DEF void gbgl_bs_draw_rounded_rect(gbglBasicState *bs, f32 x, f32 y, f32 w, f32 h, f32 roundness, gbColour col); GBGL_DEF void gbgl_bs_draw_rounded_rect_corners_outline(gbglBasicState *bs, f32 x, f32 y, f32 w, f32 h, f32 roundness, gbColour col, f32 thickness, u32 corners); GBGL_DEF void gbgl_bs_draw_rounded_rect_outline(gbglBasicState *bs, f32 x, f32 y, f32 w, f32 h, f32 roundness, gbColour col, f32 thickness); #if !defined(GBGL_NO_FONTS) GBGL_DEF isize gbgl_bs_draw_substring(gbglBasicState *bs, gbglFont *font, f32 x, f32 y, gbColour col, char const *str, isize len); GBGL_DEF isize gbgl_bs_draw_string (gbglBasicState *bs, gbglFont *font, f32 x, f32 y, gbColour col, char const *fmt, ...); GBGL_DEF isize gbgl_bs_draw_string_va(gbglBasicState *bs, gbglFont *font, f32 x, f32 y, gbColour col, char const *fmt, va_list va); #endif #endif #if defined(__cplusplus) } #endif #endif //////////////////////////////////////////////////////////////// // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // Implementation // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // //////////////////////////////////////////////////////////////// #if defined(GBGL_IMPLEMENTATION) u32 gbgl_make_sampler(u32 min_filter, u32 max_filter, u32 s_wrap, u32 t_wrap) { u32 samp; glGenSamplers(1, &samp); glSamplerParameteri(samp, GL_TEXTURE_MIN_FILTER, min_filter); glSamplerParameteri(samp, GL_TEXTURE_MAG_FILTER, max_filter); glSamplerParameteri(samp, GL_TEXTURE_WRAP_S, s_wrap); glSamplerParameteri(samp, GL_TEXTURE_WRAP_T, t_wrap); return samp; } //////////////////////////////////////////////////////////////// // // Data Buffers // // gb_inline u32 gbgl__make_buffer(isize size, void const *data, i32 target, i32 usage_hint) { u32 buffer_handle; glGenBuffers(1, &buffer_handle); glBindBuffer(target, buffer_handle); glBufferData(target, size, data, usage_hint); return buffer_handle; } gb_inline void gbgl__buffer_copy(u32 buffer_handle, i32 target, void const *data, isize size, isize offset) { glBindBuffer(target, buffer_handle); glBufferSubData(target, offset, size, data); } // NOTE(bill): usage_hint == (GL_STATIC_DRAW, GL_STREAM_DRAW, GL_DYNAMIC_DRAW) gb_inline u32 gbgl_make_vbo(void const *data, isize size, i32 usage_hint) { return gbgl__make_buffer(size, data, GL_ARRAY_BUFFER, usage_hint); } gb_inline u32 gbgl_make_ebo(void const *data, isize size, i32 usage_hint) { return gbgl__make_buffer(size, data, GL_ELEMENT_ARRAY_BUFFER, usage_hint); } gb_inline gbglTBO gbgl_make_tbo(gbglBufferDataType data_type, i32 channel_count, void const *data, isize size, i32 usage_hint) { gbglTBO tbo; i32 internal_format; tbo.buffer_obj_handle = gbgl__make_buffer(size, data, GL_TEXTURE_BUFFER, usage_hint); glGenTextures(1, &tbo.buffer_handle); glBindTexture(GL_TEXTURE_BUFFER, tbo.buffer_handle); internal_format = gbgl__get_texture_format(data_type, channel_count); glTexBuffer(GL_TEXTURE_BUFFER, internal_format, tbo.buffer_obj_handle); return tbo; } gb_inline void gbgl_vbo_copy(u32 vbo_handle, void *const data, isize size, isize offset) { gbgl__buffer_copy(vbo_handle, GL_ARRAY_BUFFER, data, size, offset); } gb_inline void gbgl_ebo_copy(u32 ebo_handle, void *const data, isize size, isize offset) { gbgl__buffer_copy(ebo_handle, GL_ELEMENT_ARRAY_BUFFER, data, size, offset); } gb_inline void gbgl_tbo_copy(gbglTBO tbo, void *const data, isize size, isize offset) { gbgl__buffer_copy(tbo.buffer_obj_handle, GL_TEXTURE_BUFFER, data, size, offset); } gb_inline void gbgl_bind_vbo(u32 vbo_handle) { glBindBuffer(GL_ARRAY_BUFFER, vbo_handle); } gb_inline void gbgl_bind_ebo(u32 ebo_handle) { glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo_handle); } gb_inline void gbgl_bind_tbo(gbglTBO tbo, i32 sampler_handle, i32 tex_unit) { glActiveTexture(GL_TEXTURE0 + tex_unit); glBindTexture(GL_TEXTURE_BUFFER, tbo.buffer_handle); glBindSampler(0, sampler_handle); } // NOTE(bill): access = GL_WRITE_ONLY, etc. gb_inline void * gbgl_map_vbo(u32 vbo_handle, i32 access) { gbgl_bind_vbo(vbo_handle); return glMapBuffer(GL_ARRAY_BUFFER, access); } gb_inline void * gbgl_map_ebo(u32 ebo_handle, i32 access) { gbgl_bind_ebo(ebo_handle); return glMapBuffer(GL_ELEMENT_ARRAY_BUFFER, access); } gb_inline void gbgl_unmap_vbo(void) { glUnmapBuffer(GL_ARRAY_BUFFER); } gb_inline void gbgl_unmap_ebo(void) { glUnmapBuffer(GL_ELEMENT_ARRAY_BUFFER); } //////////////////////////////////////////////////////////////// // // Shader // // gbglShaderError gbgl__load_single_shader_from_file(gbglShader *shader, gbglShaderType type, char const *name) { gbglShaderError err = GBGL_SHADER_ERROR_NONE; if (!gb_file_open(&shader->files[type], "%s%s", name, GBGL_SHADER_FILE_EXTENSIONS[type])) { err = GBGL_SHADER_ERROR_UNABLE_TO_READ_FILE; } else { gb_local_persist char info_log[4096]; i64 file_size = gb_file_size(&shader->files[type]); char *file_source = cast(char *)gbgl_malloc(file_size+1); GB_ASSERT_NOT_NULL(file_source); if (file_source) { i32 params; gb_file_read_at(&shader->files[type], file_source, file_size, 0); file_source[file_size] = '\0'; shader->shaders[type] = glCreateShader(GBGL_SHADER_TYPE[type]); glShaderSource(shader->shaders[type], 1, &file_source, NULL); glCompileShader(shader->shaders[type]); glGetShaderiv(shader->shaders[type], GL_COMPILE_STATUS, ¶ms); if (!params) { gb_fprintf(stderr, "Shader Source:\n%s\n", file_source); glGetShaderInfoLog(shader->shaders[type], gb_size_of(info_log), NULL, info_log); gb_fprintf(stderr, "Shader compilation failed:\n %s\n", info_log); err = GBGL_SHADER_ERROR_SHADER_COMPILE; } gbgl_free(file_source); } gb_file_close(&shader->files[type]); } return err; } gbglShaderError gbgl__load_single_shader_from_memory(gbglShader *s, gbglShaderType type, char const *text) { gbglShaderError err = GBGL_SHADER_ERROR_NONE; i32 status; s->shaders[type] = glCreateShader(GBGL_SHADER_TYPE[type]); glShaderSource(s->shaders[type], 1, &text, 0); glCompileShader(s->shaders[type]); glGetShaderiv(s->shaders[type], GL_COMPILE_STATUS, &status); if (!status) { gb_local_persist char log_info[4096]; i32 total_len, log_len; gb_fprintf(stderr, "Unable to compile shader: %s\n", text); glGetShaderiv(s->shaders[type], GL_INFO_LOG_LENGTH, &status); total_len = status; glGetShaderInfoLog(s->shaders[type], 4095, &log_len, log_info); gb_fprintf(stderr, log_info); err = GBGL_SHADER_ERROR_SHADER_COMPILE; } return err; } gbglShaderError gbgl__link_shader(gbglShader *shader) { gbglShaderError err = GBGL_SHADER_ERROR_NONE; i32 i, status; shader->program = glCreateProgram(); for (i = 0; i < GBGL_SHADER_TYPE_COUNT; i++) { if (shader->type_flags & GB_BIT(i)) glAttachShader(shader->program, shader->shaders[i]); } glLinkProgram(shader->program); glGetProgramiv(shader->program, GL_LINK_STATUS, &status); if (!status) { gb_local_persist char log_info[4096]; glGetProgramInfoLog(shader->program, gb_size_of(log_info), NULL, log_info); gb_fprintf(stderr, "Shader linking failed:\n %s \n", log_info); err = GBGL_SHADER_ERROR_LINKING; } for (i = 0; i < GBGL_SHADER_TYPE_COUNT; i++) { if (shader->type_flags & GB_BIT(i)) glDetachShader(shader->program, shader->shaders[i]); } return err; } gbglShaderError gbgl_load_shader_from_file(gbglShader *shader, u32 type_bits, char const *filename) { gbglShaderError err = GBGL_SHADER_ERROR_NONE; b32 loaded_shader[GBGL_SHADER_TYPE_COUNT] = {0}; i32 i; gb_zero_struct(shader); shader->type_flags = type_bits; gb_strncpy(shader->base_name, filename, gb_size_of(shader->base_name)); for (i = 0; i < GBGL_SHADER_TYPE_COUNT; i++) { if (type_bits & GB_BIT(i)) { err = gbgl__load_single_shader_from_file(shader, cast(gbglShaderType)i, filename); if (err != GBGL_SHADER_ERROR_NONE) return err; loaded_shader[i] = true; } } err = gbgl__link_shader(shader); return err; } gbglShaderError gbgl_load_shader_from_memory_vf(gbglShader *s, char const *vert_source, char const *frag_source) { gbglShaderError err = GBGL_SHADER_ERROR_NONE; gb_zero_struct(s); s->type_flags = GB_BIT(GBGL_SHADER_TYPE_VERTEX) | GB_BIT(GBGL_SHADER_TYPE_FRAGMENT); err = gbgl__load_single_shader_from_memory(s, GBGL_SHADER_TYPE_VERTEX, vert_source); if (err != GBGL_SHADER_ERROR_NONE) return err; err = gbgl__load_single_shader_from_memory(s, GBGL_SHADER_TYPE_FRAGMENT, frag_source); if (err != GBGL_SHADER_ERROR_NONE) return err; err = gbgl__link_shader(s); return err; } gbglShaderError gbgl_load_shader_from_memory_vfg(gbglShader *s, char const *vert_source, char const *frag_source, char const *geom_source) { gbglShaderError err = GBGL_SHADER_ERROR_NONE; gb_zero_struct(s); s->type_flags = GB_BIT(GBGL_SHADER_TYPE_VERTEX) | GB_BIT(GBGL_SHADER_TYPE_FRAGMENT) | GB_BIT(GBGL_SHADER_TYPE_GEOMETRY); err = gbgl__load_single_shader_from_memory(s, GBGL_SHADER_TYPE_VERTEX, vert_source); if (err != GBGL_SHADER_ERROR_NONE) return err; err = gbgl__load_single_shader_from_memory(s, GBGL_SHADER_TYPE_FRAGMENT, frag_source); if (err != GBGL_SHADER_ERROR_NONE) return err; err = gbgl__load_single_shader_from_memory(s, GBGL_SHADER_TYPE_GEOMETRY, geom_source); if (err != GBGL_SHADER_ERROR_NONE) return err; err = gbgl__link_shader(s); return err; } gb_inline void gbgl_destroy_shader(gbglShader *shader) { i32 i; for (i = 0; i < GBGL_SHADER_TYPE_COUNT; i++) { if (shader->type_flags & GB_BIT(i)) { gb_file_close(&shader->files[i]); glDeleteShader(shader->shaders[i]); } } glDeleteProgram(shader->program); for (i = 0; i < shader->uniform_count; i++) { gbgl_free(shader->uniform_names[i]); } } gb_inline b32 gbgl_has_shader_changed(gbglShader *shader) { i32 i; for (i = 0; i < GBGL_SHADER_TYPE_COUNT; i++) { if (shader->type_flags & GB_BIT(i)) { if (gb_file_has_changed(&shader->files[i])) { return true; } } } return false; } b32 gbgl_reload_shader(gbglShader *shader) { i32 i; for (i = 0; i < GBGL_SHADER_TYPE_COUNT; i++) { if (shader->type_flags & GB_BIT(i)) { if (gbgl__load_single_shader_from_file(shader, cast(gbglShaderType)i, shader->base_name) != GBGL_SHADER_ERROR_NONE) return false; } } if (gbgl__link_shader(shader) != GBGL_SHADER_ERROR_NONE) return false; for (i = 0; i < shader->uniform_count; i++) shader->uniform_locs[i] = glGetUniformLocation(shader->program, shader->uniform_names[i]); return true; } gb_inline void gbgl_use_shader(gbglShader *s) { glUseProgram(s ? s->program : 0); } gb_inline b32 gbgl_is_shader_in_use(gbglShader *s) { if (s) { i32 curr = 0; glGetIntegerv(GL_CURRENT_PROGRAM, &curr); return (curr == cast(i32)s->program); } return false; } i32 gbgl_get_uniform(gbglShader *s, char const *name) { i32 i, loc = -1; for (i = 0; i < s->uniform_count; i++) { if (gb_strcmp(s->uniform_names[i], name) == 0) { return s->uniform_locs[i]; } } GB_ASSERT_MSG(s->uniform_count < GBGL_MAX_UNIFORM_COUNT, "Uniform array for shader is full"); loc = glGetUniformLocation(s->program, name); s->uniform_names[s->uniform_count] = gb_alloc_str(gb_heap_allocator(), name); s->uniform_locs[s->uniform_count] = loc; s->uniform_count++; return loc; } gb_inline void gbgl_set_uniform_int(gbglShader *s, char const *name, i32 i) { glUniform1i(gbgl_get_uniform(s, name), i); } gb_inline void gbgl_set_uniform_float(gbglShader *s, char const *name, f32 f) { glUniform1f(gbgl_get_uniform(s, name), f); } gb_inline void gbgl_set_uniform_vec2(gbglShader *s, char const *name, f32 const *v) { glUniform2fv(gbgl_get_uniform(s, name), 1, v); } gb_inline void gbgl_set_uniform_vec3(gbglShader *s, char const *name, f32 const *v) { glUniform3fv(gbgl_get_uniform(s, name), 1, v); } gb_inline void gbgl_set_uniform_vec4(gbglShader *s, char const *name, f32 const *v) { glUniform4fv(gbgl_get_uniform(s, name), 1, v); } gb_inline void gbgl_set_uniform_mat4(gbglShader *s, char const *name, f32 const *m) { gbgl_set_uniform_mat4_count(s, name, m, 1); } gb_inline void gbgl_set_uniform_mat4_count(gbglShader *s, char const *name, f32 const *m, isize count) { glUniformMatrix4fv(gbgl_get_uniform(s, name), count, false, m); } gb_inline void gbgl_set_uniform_colour(gbglShader *s, char const *name, gbColour col) { f32 v[4]; v[0] = col.r / 255.0f; v[1] = col.g / 255.0f; v[2] = col.b / 255.0f; v[3] = col.a / 255.0f; gbgl_set_uniform_vec4(s, name, v); } //////////////////////////////////////////////////////////////// // // Render Buffer // // b32 gbgl_init_render_buffer(gbglRenderBuffer *rb, i32 width, i32 height, i32 channel_count) { if ((rb->width == width) && (rb->height == height) && (rb->channel_count == channel_count)) return true; gbgl_destroy_render_buffer(rb); gb_zero_struct(rb); rb->width = width; rb->height = height; glEnable(GL_FRAMEBUFFER_SRGB); glGenFramebuffers(1, &rb->handle); glBindFramebuffer(GL_FRAMEBUFFER, rb->handle); gbgl_load_texture2d_from_memory(&rb->colour_texture, NULL, width, height, channel_count); glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, rb->colour_texture.handle, 0); glBindTexture(GL_TEXTURE_2D, 0); glBindFramebuffer(GL_FRAMEBUFFER, 0); { u32 draw_buffers[] = {GL_COLOR_ATTACHMENT0}; glDrawBuffers(gb_count_of(draw_buffers), draw_buffers); } { u32 status = glCheckFramebufferStatus(GL_FRAMEBUFFER); if (status != GL_FRAMEBUFFER_COMPLETE) { gb_fprintf(stderr, "Framebuffer Status: 0x%x\n", status); return false; } } return true; } gb_inline void gbgl_destroy_render_buffer(gbglRenderBuffer *rb) { if (rb->handle) glDeleteFramebuffers(1, &rb->handle); gbgl_destroy_texture(&rb->colour_texture); } gb_inline void gbgl_render_to_buffer(gbglRenderBuffer const *rb) { GB_ASSERT_NOT_NULL(rb); glViewport(0, 0, rb->width, rb->height); glBindFramebuffer(GL_FRAMEBUFFER, rb->handle); } gb_inline void gbgl_render_to_screen(i32 width, i32 height) { glViewport(0, 0, width, height); glBindFramebuffer(GL_FRAMEBUFFER, 0); } //////////////////////////////////////////////////////////////// // // Texture // // b32 gbgl_load_texture2d_from_memory(gbglTexture *tex, void const *data, i32 width, i32 height, i32 channel_count) { b32 result = true; gb_zero_struct(tex); tex->width = width; tex->height = height; tex->channel_count = channel_count; tex->data_type = GBGL_BDT_U8; tex->type = GBGL_TEXTURE_TYPE_2D; glGenTextures(1, &tex->handle); glBindTexture(GL_TEXTURE_2D, tex->handle); glTexImage2D(GL_TEXTURE_2D, 0, GBGL_INTERNAL_TEXTURE_FORMAT_8[channel_count-1], width, height, 0, GBGL_TEXTURE_FORMAT[channel_count-1], GL_UNSIGNED_BYTE, data); glBindTexture(GL_TEXTURE_2D, 0); glGenerateMipmap(GL_TEXTURE_2D); glFinish(); return result; } b32 gbgl_load_texture2d_from_file(gbglTexture *texture, b32 flip_vertically, char const *filename, ...) { b32 result; u8 *data; int width, height, comp; char *path; va_list va; va_start(va, filename); path = gb_sprintf_va(filename, va); va_end(va); stbi_set_flip_vertically_on_load(flip_vertically); data = stbi_load(path, &width, &height, &comp, 0); if (data == NULL) { gb_fprintf(stderr, "Failed to load image: %s\n", path); result = false; } else { result = gbgl_load_texture2d_from_memory(texture, data, width, height, comp); stbi_image_free(data); } return result; } gb_inline b32 gbgl_make_texture2d_coloured(gbglTexture *t, gbColour colour) { return gbgl_load_texture2d_from_memory(t, &colour.rgba, 1, 1, 4); } gb_inline void gbgl_bind_texture2d(gbglTexture const *t, u32 position, u32 sampler) { GB_ASSERT(t->type == GBGL_TEXTURE_TYPE_2D); if (position > 31) { position = 31; gb_fprintf(stderr, "Textures can only bound to position [0 ... 31]\n"); gb_fprintf(stderr, "Will bind to position [31]\n"); } glActiveTexture(GL_TEXTURE0 + position); glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, t ? t->handle : 0); glBindSampler(position, sampler); } gb_inline void gbgl_destroy_texture(gbglTexture *t) { if (t->handle) { glDeleteTextures(1, &t->handle); } } //////////////////////////////////////////////////////////////// // // Font // // #if !defined(GBGL_NO_FONTS) gb_inline GB_COMPARE_PROC(gbgl__kern_pair_compare) { gbglKernPair *kp0 = cast(gbglKernPair *)a; gbglKernPair *kp1 = cast(gbglKernPair *)b; return kp0->packed - kp1->packed; } gb_inline GB_COMPARE_PROC(gbgl__glyph_map_compare) { 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, NULL, *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); gb_qsort_array(cache->rect_cache, cache->codepoint_count, 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, NULL, *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; } #if 0 void gbgl_destroy_font_cache(gbglFontCache *fc) { gbglFontCachedTTF *curr_ttf = fc->ttf_buffer; gbglFontCachedTTF *next_ttf = NULL; gbglFont *curr_font = fc->fonts; gbglFont *next_font = NULL; gbgl_free(fc->font_char_list); gbgl_free(fc->codepoints); gbgl_free(fc->ranges); gbgl_free(fc->packed_char_data); gbgl_free(fc->rect_cache); // NOTE(bill): Free all linked listed ttfs while (curr_ttf) { gbgl_free(curr_ttf->name); gbgl_free(curr_ttf->ttf); next_ttf = curr_ttf->next; gbgl_free(curr_ttf); curr_ttf = next_ttf; } // NOTE(bill): Free all linked listed fonts while (curr_font) { gbgl_free(curr_font->ttf_filename); gbgl_destroy_texture(&curr_font->texture); gbgl_free(curr_font->glyph_map); gbgl_free(curr_font->glyphs); gbgl_free(curr_font->kern_table); next_font = curr_font->next; gbgl_free(curr_font); curr_font = next_font; } } #endif gb_inline gbglFont * gbgl_load_font_from_file(gbglFontCache *fc, char const *ttf_filename, f32 font_size) { gbglFont *f = gbgl_get_font_only_from_cache(fc, ttf_filename, font_size); if (f) return f; return gbgl_cache_font(fc, ttf_filename, font_size); } gb_inline gbglFont * gbgl_get_font_only_from_cache(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) { gbglFont *f = gbgl_get_font_only_from_cache(fc, ttf_filename, font_size); gbglFontCachedTTF *ttf = NULL; isize i; if (f) { // NOTE(bill): The font is already cached return f; } if (!fc->fonts) { fc->fonts = cast(gbglFont *)gbgl_malloc(gb_size_of(gbglFont)); f = fc->fonts; } else { f = fc->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) { gb_fprintf(stderr, "Failed to cache font\n"); return NULL; } gb_zero_struct(f); // NOTE(bill): Make sure the character list file has been loaded if (!fc->font_char_list) { isize codepoint_count = 0, cpi = 0; fc->font_char_list = GBGL_FONT_CHAR_LIST; fc->font_char_list_count = gb_strlen(GBGL_FONT_CHAR_LIST); for (i = 0; i < fc->font_char_list_count; i++) { char32 c; isize utf8_len = gb_utf8_decode_len(fc->font_char_list + i, fc->font_char_list_count, &c); i += utf8_len-1; codepoint_count++; } fc->codepoint_count = codepoint_count; fc->ranges = cast(stbtt_pack_range *)gbgl_malloc(gb_size_of(stbtt_pack_range) * codepoint_count); fc->codepoints = cast(char32 *) gbgl_malloc(gb_size_of(char32) * codepoint_count); fc->packed_char_data = cast(stbtt_packedchar *)gbgl_malloc(gb_size_of(stbtt_packedchar) * codepoint_count); fc->rect_cache = cast(stbrp_rect *) gbgl_malloc(gb_size_of(stbrp_rect) * codepoint_count); if (!fc->ranges || !fc->codepoints || !fc->packed_char_data) { gb_fprintf(stderr, "Unable to get memory for fonts"); } for (i = 0; i < fc->font_char_list_count; i++) { isize utf8_len = gb_utf8_decode_len(fc->font_char_list+i, fc->font_char_list_count, fc->codepoints+cpi); i += utf8_len-1; cpi++; } GB_ASSERT(cpi == fc->codepoint_count); for (i = 0; i < fc->codepoint_count; i++) { fc->ranges[i].first_unicode_codepoint_in_range = fc->codepoints[i]; fc->ranges[i].array_of_unicode_codepoints = 0; fc->ranges[i].num_chars = 1; fc->ranges[i].chardata_for_range = fc->packed_char_data + i; } } { gbglFontCachedTTF **ttf_cache = &fc->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; gbFile file; *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'; if (gb_file_open(&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_file_close(&file); } else { GB_PANIC("Could not open ttf file"); } 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 < fc->codepoint_count; i++) fc->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(fc, ttf, &w, &h)) { isize str_len, i, j; // NOTE(bill): Setup the font data f->glyph_count = fc->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, NULL); GB_ASSERT(res == 1); res = stbtt_PackFontRanges(&spc, ttf->ttf, 0, fc->ranges, fc->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)fc->packed_char_data[i].x0; gi->t0 = cast(f32)fc->packed_char_data[i].y0; gi->s1 = cast(f32)fc->packed_char_data[i].x1; gi->t1 = cast(f32)fc->packed_char_data[i].y1; gi->xoff = cast(i16)fc->packed_char_data[i].xoff; gi->yoff = cast(i16)fc->packed_char_data[i].yoff; gi->xadv = fc->packed_char_data[i].xadvance; } for (i = 0; i < f->glyph_count; i++) { f->glyph_map[i].codepoint = fc->codepoints[i]; f->glyph_map[i].index = i; } gb_qsort_array(f->glyph_map, f->glyph_count, gbgl__glyph_map_compare); { // 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, fc->codepoints[i], fc->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, fc->codepoints[i], fc->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_array(f->kern_table, f->kern_pair_count, gbgl__kern_pair_compare); } } } } else { GB_PANIC("Failure loading font"); gb_zero_struct(&f); } } return f; } gb_inline GB_COMPARE_PROC(gbgl__font_glyph_map_search_proc) { gbglGlyphMapKVPair const *gm = cast(gbglGlyphMapKVPair const *)a; char32 ucp = *cast(char32 const *)b; return cast(i32)(cast(i64)gm->codepoint - cast(i64)ucp); } gb_inline gbglGlyphInfo * gbgl_get_glyph_info(gbglFont *font, char32 codepoint, isize *out_index) { isize index = gb_binary_search_array(font->glyph_map, font->glyph_count, &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; } gb_inline f32 gbgl_get_font_kerning_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_dimensions(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_kerning_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_kerning_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_kerning_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); } #endif //////////////////////////////////////////////////////////////// // // Basic State // // #if !defined(GBGL_NO_BASIC_STATE) void gbgl_bs_init(gbglBasicState *bs, i32 window_width, i32 window_height) { isize i; gbgl_bs_set_resolution(bs, window_width, window_height); glGenVertexArrays(1, &bs->vao); glBindVertexArray(bs->vao); bs->vbo = gbgl_make_vbo(NULL, gb_size_of(gbglBasicVertex) * GBGL_BS_MAX_VERTEX_COUNT, GL_DYNAMIC_DRAW); for (i = 0; i < GBGL_BS_MAX_INDEX_COUNT / 6; i++) { bs->indices[i*6 + 0] = i*4 + 0; bs->indices[i*6 + 1] = i*4 + 1; bs->indices[i*6 + 2] = i*4 + 2; bs->indices[i*6 + 3] = i*4 + 2; bs->indices[i*6 + 4] = i*4 + 3; bs->indices[i*6 + 5] = i*4 + 0; } bs->ebo = gbgl_make_ebo(bs->indices, gb_size_of(u16) * GBGL_BS_MAX_INDEX_COUNT, GL_STATIC_DRAW); bs->nearest_sampler = gbgl_make_sampler(GL_NEAREST, GL_NEAREST, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE); bs->linear_sampler = gbgl_make_sampler(GL_LINEAR, GL_LINEAR, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE); gbgl_load_shader_from_memory_vf(&bs->ortho_tex_shader, "#version 410 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 410 core\n" "precision mediump float;" "in vec2 v_tex_coord;\n" "layout (binding = 0) uniform sampler2D u_tex;\n" "out vec4 o_colour;\n" "void main(void) {\n" " o_colour = texture2D(u_tex, v_tex_coord);\n" "}\n" ); gbgl_load_shader_from_memory_vf(&bs->ortho_col_shader, "#version 410 core\n" "precision mediump float;" "layout (location = 0) in vec4 a_position;\n" "uniform mat4 u_ortho_mat;\n" "void main(void) {\n" " gl_Position = u_ortho_mat * a_position;\n" "}\n", "#version 410 core\n" "uniform vec4 u_colour;\n" "out vec4 o_colour;\n" "void main(void) {\n" " o_colour = u_colour;\n" "}\n" ); #if !defined(GBGL_NO_FONTS) gbgl_load_shader_from_memory_vf(&bs->font_shader, "#version 410 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 410 core\n" "in vec2 v_tex_coord;\n" "uniform vec4 u_colour;\n" "layout (binding = 0) uniform sampler2D u_tex;\n" "out vec4 o_colour;\n" "void main(void) {\n" " o_colour = u_colour * texture2D(u_tex, v_tex_coord).r;\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_make_sampler(GL_NEAREST, GL_NEAREST, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE); bs->font_samplers[1] = gbgl_make_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; #endif } gb_inline void gbgl_bs_set_resolution(gbglBasicState *bs, i32 window_width, i32 window_height) { f32 left = 0.0f; f32 right = cast(f32)window_width; f32 bottom = 0.0f; f32 top = cast(f32)window_height; f32 znear = 0.0f; f32 zfar = 1.0f; bs->width = window_width; bs->height = window_height; bs->ortho_mat[0] = 2.0f / (right - left); bs->ortho_mat[1] = 0.0f; bs->ortho_mat[2] = 0.0f; bs->ortho_mat[3] = 0.0f; bs->ortho_mat[4] = 0.0f; bs->ortho_mat[5] = 2.0f / (top - bottom); bs->ortho_mat[6] = 0.0f; bs->ortho_mat[7] = 0.0f; bs->ortho_mat[8] = 0.0f; bs->ortho_mat[9] = 0.0f; bs->ortho_mat[10] = -2.0f / (zfar - znear); bs->ortho_mat[11] = 0.0f; bs->ortho_mat[12] = -(right + left) / (right - left); bs->ortho_mat[13] = -(top + bottom) / (top - bottom); bs->ortho_mat[14] = -(zfar + znear) / (zfar - znear); bs->ortho_mat[15] = 1.0f; } gb_inline void gbgl_bs_begin(gbglBasicState *bs, i32 window_width, i32 window_height) { glBindVertexArray(bs->vao); glDisable(GL_SCISSOR_TEST); gbgl_bs_set_resolution(bs, window_width, window_height); } gb_inline void gbgl_bs_end(gbglBasicState *bs) { glBindVertexArray(0); } void gbgl_bs_draw_textured_rect(gbglBasicState *bs, gbglTexture *tex, f32 x, f32 y, f32 w, f32 h, b32 v_up) { bs->vertices[0].x = x; bs->vertices[0].y = y; bs->vertices[0].u = 0.0f; bs->vertices[0].v = v_up ? 0.0f : 1.0f; bs->vertices[1].x = x + w; bs->vertices[1].y = y; bs->vertices[1].u = 1.0f; bs->vertices[1].v = v_up ? 0.0f : 1.0f; bs->vertices[2].x = x + w; bs->vertices[2].y = y + h; bs->vertices[2].u = 1.0f; bs->vertices[2].v = v_up ? 1.0f : 0.0f; bs->vertices[3].x = x; bs->vertices[3].y = y + h; bs->vertices[3].u = 0.0f; bs->vertices[3].v = v_up ? 1.0f : 0.0f; gbgl_use_shader(&bs->ortho_tex_shader); gbgl_set_uniform_mat4(&bs->ortho_tex_shader, "u_ortho_mat", bs->ortho_mat); gbgl_bind_texture2d(tex, 0, bs->nearest_sampler); gbgl_vbo_copy(bs->vbo, bs->vertices, 4*gb_size_of(bs->vertices[0]), 0); gbgl_vert_ptr_aa(0, 2, gbglBasicVertex, x); gbgl_vert_ptr_aa(1, 2, gbglBasicVertex, u); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bs->ebo); glEnable(GL_BLEND); glBlendEquationi(0, GL_FUNC_ADD); glBlendFunci(0, GL_ONE, GL_ONE_MINUS_SRC_ALPHA); glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, NULL); } gb_inline void gbgl_bs_draw_rect(gbglBasicState *bs, f32 x, f32 y, f32 w, f32 h, gbColour col) { gbgl_bs_draw_quad(bs, x, y, x+w, y, x+w, y+h, x, y+h, col); } gb_inline void gbgl_bs_draw_rect_outline(gbglBasicState *bs, f32 x, f32 y, f32 w, f32 h, gbColour col, f32 thickness) { gbgl_bs_draw_quad_outline(bs, x, y, x+w, y, x+w, y+h, x, y+h, col, thickness); } gb_internal void gbgl__bs_setup_ortho_colour_state(gbglBasicState *bs, isize vertex_count, gbColour col) { gbgl_use_shader(&bs->ortho_col_shader); gbgl_set_uniform_mat4(&bs->ortho_col_shader, "u_ortho_mat", bs->ortho_mat); gbgl_set_uniform_colour(&bs->ortho_col_shader, "u_colour", col); gbgl_vbo_copy(bs->vbo, bs->vertices, vertex_count*gb_size_of(bs->vertices[0]), 0); gbgl_vert_ptr_aa(0, 2, gbglBasicVertex, x); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bs->ebo); glEnable(GL_BLEND); glBlendEquation(GL_FUNC_ADD); glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); } gb_inline void gbgl_bs_draw_quad(gbglBasicState *bs, f32 x0, f32 y0, f32 x1, f32 y1, f32 x2, f32 y2, f32 x3, f32 y3, gbColour col) { bs->vertices[0].x = x0; bs->vertices[0].y = y0; bs->vertices[1].x = x1; bs->vertices[1].y = y1; bs->vertices[2].x = x2; bs->vertices[2].y = y2; bs->vertices[3].x = x3; bs->vertices[3].y = y3; gbgl__bs_setup_ortho_colour_state(bs, 4, col); glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, NULL); } gb_inline void gbgl_bs_draw_quad_outline(gbglBasicState *bs, f32 x0, f32 y0, f32 x1, f32 y1, f32 x2, f32 y2, f32 x3, f32 y3, gbColour col, f32 thickness) { bs->vertices[0].x = x0; bs->vertices[0].y = y0; bs->vertices[1].x = x1; bs->vertices[1].y = y1; bs->vertices[2].x = x2; bs->vertices[2].y = y2; bs->vertices[3].x = x3; bs->vertices[3].y = y3; gbgl__bs_setup_ortho_colour_state(bs, 4, col); glLineWidth(thickness); glDrawArrays(GL_LINE_LOOP, 0, 4); } gb_inline void gbgl_bs_draw_line(gbglBasicState *bs, f32 x0, f32 y0, f32 x1, f32 y1, gbColour col, f32 thickness) { bs->vertices[0].x = x0; bs->vertices[0].y = y0; bs->vertices[1].x = x1; bs->vertices[1].y = y1; gbgl__bs_setup_ortho_colour_state(bs, 2, col); glLineWidth(thickness); glDrawArrays(GL_LINES, 0, 2); } gb_inline void gbgl_bs_draw_elliptical_arc(gbglBasicState *bs, f32 x, f32 y, f32 radius_a, f32 radius_b, f32 min_angle, f32 max_angle, gbColour col) { isize i; bs->vertices[0].x = x; bs->vertices[0].y = y; for (i = 0; i < 31; i++) { f32 t = cast(f32)i / 30.0f; f32 a = gbgl_lerp(min_angle, max_angle, t); f32 c = gbgl_cos(a); f32 s = gbgl_sin(a); bs->vertices[i+1].x = x + c*radius_a; bs->vertices[i+1].y = y + s*radius_b; } gbgl__bs_setup_ortho_colour_state(bs, 32, col); glDrawArrays(GL_TRIANGLE_FAN, 0, 32); } gb_inline void gbgl_bs_draw_elliptical_arc_outline(gbglBasicState *bs, f32 x, f32 y, f32 radius_a, f32 radius_b, f32 min_angle, f32 max_angle, gbColour col, f32 thickness) { isize i; for (i = 0; i < 32; i++) { f32 t = cast(f32)i / 31.0f; f32 a = gbgl_lerp(min_angle, max_angle, t); f32 c = gbgl_cos(a); f32 s = gbgl_sin(a); bs->vertices[i+1].x = x + c*radius_a; bs->vertices[i+1].y = y + s*radius_b; } gbgl__bs_setup_ortho_colour_state(bs, 32, col); glLineWidth(thickness); glDrawArrays(GL_LINES, 0, 32); } gb_inline void gbgl_bs_draw_circle(gbglBasicState *bs, f32 x, f32 y, f32 radius, gbColour col) { gbgl_bs_draw_elliptical_arc(bs, x, y, radius, radius, 0, GBGL_TAU, col); } gb_inline void gbgl_bs_draw_circle_outline(gbglBasicState *bs, f32 x, f32 y, f32 radius, gbColour col, f32 thickness) { gbgl_bs_draw_elliptical_arc_outline(bs, x, y, radius, radius, 0, GBGL_TAU, col, thickness); } void gbgl_bs_draw_rounded_rect_corners(gbglBasicState *bs, f32 x, f32 y, f32 w, f32 h, f32 roundness, gbColour col, u32 corners) { if ((2.0f*roundness > gbgl_abs(w)) || (2.0f*roundness > gbgl_abs(h))) { roundness = 0.5f*gbgl_min(gbgl_abs(w), gbgl_abs(h)); } if (roundness == 0 || corners == 0) { gbgl_bs_draw_rect(bs, x, y, w, h, col); } else { isize i, vc = 0; bs->vertices[vc].x = x + 0.5f*w; bs->vertices[vc].y = y + 0.5f*h; vc++; if (corners & 1) { for (i = 0; i < 6; i++) { f32 t = cast(f32)i / 5.0f; f32 a = gbgl_lerp(0.5f*GBGL_TAU, 0.75f*GBGL_TAU, t); f32 c = gbgl_cos(a); f32 s = gbgl_sin(a); bs->vertices[vc].x = x + roundness + c*roundness; bs->vertices[vc].y = y + roundness + s*roundness; vc++; } } else { bs->vertices[vc].x = x; bs->vertices[vc].y = y; vc++; } if (corners & 2) { for (i = 0; i < 6; i++) { f32 t = cast(f32)i / 5.0f; f32 a = gbgl_lerp(0.75f*GBGL_TAU, 1.00f*GBGL_TAU, t); f32 c = gbgl_cos(a); f32 s = gbgl_sin(a); bs->vertices[vc].x = x + w - roundness + c*roundness; bs->vertices[vc].y = y + roundness + s*roundness; vc++; } } else { bs->vertices[vc].x = x + w; bs->vertices[vc].y = y; vc++; } if (corners & 4) { for (i = 0; i < 6; i++) { f32 t = cast(f32)i / 5.0f; f32 a = gbgl_lerp(0.00f*GBGL_TAU, 0.25f*GBGL_TAU, t); f32 c = gbgl_cos(a); f32 s = gbgl_sin(a); bs->vertices[vc].x = x + w - roundness + c*roundness; bs->vertices[vc].y = y + h - roundness + s*roundness; vc++; } } else { bs->vertices[vc].x = x + w; bs->vertices[vc].y = y + h; vc++; } if (corners & 8) { for (i = 0; i < 6; i++) { f32 t = cast(f32)i / 5.0f; f32 a = gbgl_lerp(0.25f*GBGL_TAU, 0.50f*GBGL_TAU, t); f32 c = gbgl_cos(a); f32 s = gbgl_sin(a); bs->vertices[vc].x = x + roundness + c*roundness; bs->vertices[vc].y = y + h - roundness + s*roundness; vc++; } } else { bs->vertices[vc].x = x; bs->vertices[vc].y = y + h; vc++; } if (corners & 1) { bs->vertices[vc].x = x; bs->vertices[vc].y = y + roundness; } else { bs->vertices[vc].x = x; bs->vertices[vc].y = y; } vc++; gbgl__bs_setup_ortho_colour_state(bs, vc, col); glDrawArrays(GL_TRIANGLE_FAN, 0, vc); } } gb_inline void gbgl_bs_draw_rounded_rect(gbglBasicState *bs, f32 x, f32 y, f32 w, f32 h, f32 roundness, gbColour col) { gbgl_bs_draw_rounded_rect_corners(bs, x, y, w, h, roundness, col, 1|2|4|8); } void gbgl_bs_draw_rounded_rect_corners_outline(gbglBasicState *bs, f32 x, f32 y, f32 w, f32 h, f32 roundness, gbColour col, f32 thickness, u32 corners) { if ((2.0f*roundness > gbgl_abs(w)) || (2.0f*roundness > gbgl_abs(h))) { roundness = 0.5f*gbgl_min(gbgl_abs(w), gbgl_abs(h)); } if (roundness == 0 || corners == 0) { gbgl_bs_draw_rect_outline(bs, x, y, w, h, col, thickness); } else { isize i, vc = 0; if (corners & 1) { for (i = 0; i < 6; i++) { f32 t = cast(f32)i / 5.0f; f32 a = gbgl_lerp(0.5f*GBGL_TAU, 0.75f*GBGL_TAU, t); f32 c = gbgl_cos(a); f32 s = gbgl_sin(a); bs->vertices[vc].x = x + roundness + c*roundness; bs->vertices[vc].y = y + roundness + s*roundness; vc++; } } else { bs->vertices[vc].x = x; bs->vertices[vc].y = y; vc++; } if (corners & 2) { for (i = 0; i < 6; i++) { f32 t = cast(f32)i / 5.0f; f32 a = gbgl_lerp(0.75f*GBGL_TAU, 1.00f*GBGL_TAU, t); f32 c = gbgl_cos(a); f32 s = gbgl_sin(a); bs->vertices[vc].x = x + w - roundness + c*roundness; bs->vertices[vc].y = y + roundness + s*roundness; vc++; } } else { bs->vertices[vc].x = x + w; bs->vertices[vc].y = y; vc++; } if (corners & 4) { for (i = 0; i < 6; i++) { f32 t = cast(f32)i / 5.0f; f32 a = gbgl_lerp(0.00f*GBGL_TAU, 0.25f*GBGL_TAU, t); f32 c = gbgl_cos(a); f32 s = gbgl_sin(a); bs->vertices[vc].x = x + w - roundness + c*roundness; bs->vertices[vc].y = y + h - roundness + s*roundness; vc++; } } else { bs->vertices[vc].x = x + w; bs->vertices[vc].y = y + h; vc++; } if (corners & 8) { for (i = 0; i < 6; i++) { f32 t = cast(f32)i / 5.0f; f32 a = gbgl_lerp(0.25f*GBGL_TAU, 0.50f*GBGL_TAU, t); f32 c = gbgl_cos(a); f32 s = gbgl_sin(a); bs->vertices[vc].x = x + roundness + c*roundness; bs->vertices[vc].y = y + h - roundness + s*roundness; vc++; } } else { bs->vertices[vc].x = x; bs->vertices[vc].y = y + h; vc++; } gbgl__bs_setup_ortho_colour_state(bs, vc, col); glLineWidth(thickness); glDrawArrays(GL_LINE_LOOP, 0, vc); } } gb_inline void gbgl_bs_draw_rounded_rect_outline(gbglBasicState *bs, f32 x, f32 y, f32 w, f32 h, f32 roundness, gbColour col, f32 thickness) { gbgl_bs_draw_rounded_rect_corners_outline(bs, x, y, w, h, roundness, col, thickness, 1|2|4|8); } #if !defined(GBGL_NO_FONTS) isize gbgl_bs_draw_substring(gbglBasicState *bs, gbglFont *font, f32 x, f32 y, gbColour col, char const *str, isize len) { isize char_count = gb_utf8_strnlen(str, len); isize line_count = 0; if (char_count > 0) { char const *ptr = str; f32 sf = 1.0f / cast(f32)font->bitmap_width; f32 tf = 1.0f / cast(f32)font->bitmap_height; f32 ox, oy; f32 px, py; isize glyph_count = 0, i; f32 font_height = font->size; i32 max_width = bs->text_params[GBGL_TEXT_PARAM_MAX_WIDTH].val_i32; gbglJustifyType justify = cast(gbglJustifyType)bs->text_params[GBGL_TEXT_PARAM_JUSTIFY].val_i32; if (justify == GBGL_JUSTIFY_CENTRE) { f32 width; gbgl_get_string_dimensions(font, ptr, &width, NULL); x = gbgl_round(x - width*0.5f); } else if (justify == GBGL_JUSTIFY_RIGHT) { f32 width; gbgl_get_string_dimensions(font, ptr, &width, NULL); x = gbgl_round(x - width); } line_count = 1; ox = x; oy = y; px = ox; py = oy; for (i = 0; i < char_count; i++) { char32 cp; isize byte_len, curr_index, draw_this_glyph_count = 1, j; gbglGlyphInfo *gi; byte_len = gb_utf8_decode_len(ptr, len-(ptr-str), &cp); ptr += byte_len; if (ptr - str > len) break; if (cp == '\t') { draw_this_glyph_count = GBGL_TAB_CHARACTER_WIDTH; cp = ' '; // TODO(bill): Set tab to be space } gi = gbgl_get_glyph_info(font, cp, &curr_index); if (!gi) { gi = gbgl_get_glyph_info(font, ' ', &curr_index); } if (gi) { for (j = 0; j < draw_this_glyph_count; j++) { f32 s0, t0, s1, t1; f32 x0, y0, x1, y1; f32 kern = 0.0f; if (cp == '\r' || cp == '\n' || (max_width > 0 && px - ox + gi->xadv >= cast(f32)max_width)) { px = ox; py -= font_height; line_count += 2; if (cp == '\r' || cp == '\n') { if (cp == '\r' && ptr[1] == '\n') ptr++; continue; } } s0 = cast(f32)gi->s0 * sf; t0 = cast(f32)gi->t0 * tf; s1 = cast(f32)gi->s1 * sf; t1 = cast(f32)gi->t1 * tf; x0 = px + gi->xoff; y0 = py - gi->yoff; x1 = x0 + (gi->s1 - gi->s0); y1 = y0 + (gi->t0 - gi->t1); bs->font_vertices[glyph_count*4 + 0].x = x0; bs->font_vertices[glyph_count*4 + 0].y = y0; bs->font_vertices[glyph_count*4 + 0].u = s0; bs->font_vertices[glyph_count*4 + 0].v = t0; bs->font_vertices[glyph_count*4 + 1].x = x1; bs->font_vertices[glyph_count*4 + 1].y = y0; bs->font_vertices[glyph_count*4 + 1].u = s1; bs->font_vertices[glyph_count*4 + 1].v = t0; bs->font_vertices[glyph_count*4 + 2].x = x1; bs->font_vertices[glyph_count*4 + 2].y = y1; bs->font_vertices[glyph_count*4 + 2].u = s1; bs->font_vertices[glyph_count*4 + 2].v = t1; bs->font_vertices[glyph_count*4 + 3].x = x0; bs->font_vertices[glyph_count*4 + 3].y = y1; bs->font_vertices[glyph_count*4 + 3].u = s0; bs->font_vertices[glyph_count*4 + 3].v = t1; glyph_count++; 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_kerning_from_glyph_indices(font, curr_index, next_index); } } px += gi->xadv + kern; } } } if (glyph_count > 0) { isize sampler_index = 0; gbgl_use_shader(&bs->font_shader); gbgl_set_uniform_mat4(&bs->font_shader, "u_ortho_mat", bs->ortho_mat); gbgl_set_uniform_colour(&bs->font_shader, "u_colour", col); GB_ASSERT(bs->text_params[GBGL_TEXT_PARAM_TEXTURE_FILTER].val_i32 < gb_count_of(bs->font_samplers)); if (bs->text_params[GBGL_TEXT_PARAM_TEXTURE_FILTER].val_i32 < gb_count_of(bs->font_samplers)) sampler_index = bs->text_params[GBGL_TEXT_PARAM_TEXTURE_FILTER].val_i32; gbgl_bind_texture2d(&font->texture, 0, bs->font_samplers[sampler_index]); gbgl_vbo_copy(bs->font_vbo, bs->font_vertices, gb_size_of(bs->font_vertices[0]) * glyph_count * 4, 0); gbgl_vert_ptr_aa(0, 2, gbglBasicVertex, x); gbgl_vert_ptr_aa(1, 2, gbglBasicVertex, u); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bs->font_ebo); glEnable(GL_BLEND); glBlendEquation(GL_FUNC_ADD); glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); glDrawElements(GL_TRIANGLES, glyph_count*6, GL_UNSIGNED_SHORT, NULL); } } return line_count; } isize gbgl_bs_draw_string(gbglBasicState *bs, gbglFont *font, f32 x, f32 y, gbColour col, char const *fmt, ...) { isize len; va_list va; va_start(va, fmt); len = gbgl_bs_draw_string_va(bs, font, x, y, col, fmt, va); va_end(va); return len; } gb_inline isize gbgl_bs_draw_string_va(gbglBasicState *bs, gbglFont *font, f32 x, f32 y, gbColour col, char const *fmt, va_list va) { isize len = gb_snprintf_va(bs->font_text_buffer, gb_size_of(bs->font_text_buffer), fmt, va); isize char_count = gb_utf8_strnlen(bs->font_text_buffer, len); if (char_count <= 0) return 0; return gbgl_bs_draw_substring(bs, font, x, y, col, bs->font_text_buffer, len); } #endif // !defined(GBGL_NO_FONTS) #endif // !defined(GBGL_NO_BASIC_STATE) #endif