GB GL
GB GL
===========================================================================
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"
Dependencies
NOTE: You may need change the path
Optional Dependencies
"gb_math.h" as a lot of useful things in it over <math.h>.
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
#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.06 - Enum convention change
0.05 - gbglColour
0.04e - Change brace style because why not?
0.04d - Use new gb.h file handling system
0.04c - Use new gb.h file handling system
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
#else
#if !defined(GBGL_USE_CUSTOM_MATH)
#include <math.h>
#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
////////////////////////////////////////////////////////////////
//
// Colour Type
// It's quite useful
// TODO(bill): Does this need to be in this library?
// Can I remove the anonymous struct extension?
//
#if defined(_MSC_VER)
#pragma warning(push)
#pragma warning(disable:4201)
#endif
#if defined(_MSC_VER)
#pragma warning(pop)
#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)
#endif
////////////////////////////////////////////////////////////////
//
// Data Buffers
//
//
gbglBufferData_u16 = GL_UNSIGNED_SHORT,
gbglBufferData_i16 = GL_SHORT,
gbglBufferData_f16 = GL_HALF_FLOAT,
gbglBufferData_u32 = GL_UNSIGNED_INT,
gbglBufferData_i32 = GL_INT,
gbglBufferData_f32 = GL_FLOAT,
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);
////////////////////////////////////////////////////////////////
//
// Shader
//
//
gbglShader_Count,
} gbglShaderType;
gbglShaderError_Count,
} gbglShaderError;
#ifndef GBGL_MAX_UNIFORM_COUNT
#define GBGL_MAX_UNIFORM_COUNT 32
#endif
i32 uniform_locs[GBGL_MAX_UNIFORM_COUNT];
char *uniform_names[GBGL_MAX_UNIFORM_COUNT];
i32 uniform_count;
u32 type_flags;
gbFile files[gbglShader_Count];
char base_name[64];
} gbglShader;
#ifndef GBGL_SHADER_FILE_EXTENSIONS_DEFINED
#define GBGL_SHADER_FILE_EXTENSIONS_DEFINED
gb_global char const *gbglShaderFileExtensions[gbglShader_Count] = {".vs", ".fs",
".gs"};
#endif
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,
gbglColour col);
////////////////////////////////////////////////////////////////
//
// Texture
//
//
gbglgTexture_Count,
} gbglTextureType;
gbglTexture colour_texture;
} gbglRenderBuffer;
#define GBGL_MAX_RENDER_COLOUR_BUFFERS 16
gb_global u32 const gbglColourBufferAttachments[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,
};
////////////////////////////////////////////////////////////////
//
// Font Stuff
//
//
#if !defined(GBGL_NO_FONTS)
gbglTextParam_Count,
} gbglTextParamType;
gbglGlyphMapKVPair *glyph_map;
gbglGlyphInfo * glyphs;
gbglKernPair * kern_table;
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
#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 gbglTextParam_Stack_size
#define gbglTextParam_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
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[gbglTextParam_Stack_size];
isize text_param_stack_count;
gbglTextParam text_params[gbglTextParam_Count];
#endif
} gbglBasicState;
GBGL_DEF void gbgl_bs_draw_line(gbglBasicState *bs, f32 x0, f32 y0, f32 x1, f32 y1,
gbglColour 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, gbglColour col, u32 corners);
GBGL_DEF void gbgl_bs_draw_rounded_rect(gbglBasicState *bs, f32 x, f32 y, f32 w,
f32 h, f32 roundness, gbglColour col);
#if !defined(GBGL_NO_FONTS)
GBGL_DEF isize gbgl_bs_draw_substring(gbglBasicState *bs, gbglFont *font, f32 x,
f32 y, gbglColour col, char const *str, isize len);
GBGL_DEF isize gbgl_bs_draw_string (gbglBasicState *bs, gbglFont *font, f32 x,
f32 y, gbglColour col, char const *fmt, ...);
GBGL_DEF isize gbgl_bs_draw_string_va(gbglBasicState *bs, gbglFont *font, f32 x,
f32 y, gbglColour col, char const *fmt, va_list va);
#endif
#endif
#if defined(__cplusplus)
}
#endif
#endif
////////////////////////////////////////////////////////////////
// //
// //
// //
// //
// //
// //
// //
// //
// //
// //
// //
// //
// //
// //
// //
// //
// //
// //
// //
// //
// //
// Implementation //
// //
// //
// //
// //
// //
// //
// //
// //
// //
// //
// //
// //
// //
// //
// //
// //
// //
// //
// //
// //
// //
// //
// //
////////////////////////////////////////////////////////////////
#define GBGL_IMPLEMENTATION
#if defined(GBGL_IMPLEMENTATION)
////////////////////////////////////////////////////////////////
//
// 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;
}
glGenTextures(1, &tbo.buffer_handle);
glBindTexture(GL_TEXTURE_BUFFER, tbo.buffer_handle);
internal_format = gbgl_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);
}
////////////////////////////////////////////////////////////////
//
// Shader
//
//
if (ferr != gbFileError_None) {
err = gbglShaderError_UnableToReadFile;
} 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;
shader->shaders[type] = glCreateShader(gbglShaderMap[type]);
glShaderSource(shader->shaders[type], 1, &file_source, NULL);
glCompileShader(shader->shaders[type]);
glGetShaderiv(shader->shaders[type], GL_COMPILE_STATUS, ¶ms);
if (!params) {
gb_printf_err("Shader Source:\n%s\n", file_source);
glGetShaderInfoLog(shader->shaders[type],
gb_size_of(info_log), NULL, info_log);
gb_printf_err("Shader compilation failed:\n %s\n",
info_log);
err = gbglShaderError_ShaderCompile;
}
gbgl_free(file_source);
}
gb_file_close(&shader->files[type]);
}
return err;
}
s->shaders[type] = glCreateShader(gbglShaderMap[type]);
glShaderSource(s->shaders[type], 1, &text, 0);
glCompileShader(s->shaders[type]);
return err;
}
glLinkProgram(shader->program);
gb_zero_item(shader);
shader->type_flags = type_bits;
gb_strncpy(shader->base_name, filename, gb_size_of(shader->base_name));
return err;
}
gb_zero_item(s);
s->type_flags = GB_BIT(gbglShader_Vertex) | GB_BIT(gbglShader_Fragment);
err = gbgl__link_shader(s);
return err;
}
gb_zero_item(s);
s->type_flags = GB_BIT(gbglShader_Vertex) | GB_BIT(gbglShader_Fragment) |
GB_BIT(gbglShader_Geometry);
err = gbgl__load_single_shader_from_memory(s, gbglShader_Vertex,
vert_source);
if (err != gbglShaderError_None) return err;
err = gbgl__load_single_shader_from_memory(s, gbglShader_Fragment,
frag_source);
if (err != gbglShaderError_None) return err;
err = gbgl__load_single_shader_from_memory(s, gbglShader_Geometry,
geom_source);
if (err != gbglShaderError_None) return err;
err = gbgl__link_shader(s);
return err;
}
glDeleteProgram(shader->program);
if (gbgl__link_shader(shader) != gbglShaderError_None)
return false;
for (i = 0; i < shader->uniform_count; i++)
shader->uniform_locs[i] = glGetUniformLocation(shader->program, shader-
>uniform_names[i]);
return true;
}
return loc;
}
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);
}
////////////////////////////////////////////////////////////////
//
// Render Buffer
//
//
rb->width = width;
rb->height = height;
glEnable(GL_FRAMEBUFFER_SRGB);
glGenFramebuffers(1, &rb->handle);
glBindFramebuffer(GL_FRAMEBUFFER, rb->handle);
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_printf_err("Framebuffer Status: 0x%x\n", status);
return false;
}
}
return true;
}
gbgl_destroy_texture(&rb->colour_texture);
}
////////////////////////////////////////////////////////////////
//
// Texture
//
//
gb_zero_item(tex);
tex->width = width;
tex->height = height;
tex->channel_count = channel_count;
tex->data_type = gbglBufferData_u8;
tex->type = gbglgTexture_2D;
glGenTextures(1, &tex->handle);
glBindTexture(GL_TEXTURE_2D, tex->handle);
glTexImage2D(GL_TEXTURE_2D, 0,
gbglInternalTextureFormat_8[channel_count-1],
width, height, 0,
gbglTextureFormat[channel_count-1],
GL_UNSIGNED_BYTE, data);
glGenerateMipmap(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, 0);
glFinish();
return result;
}
va_list va;
va_start(va, filename);
path = gb_bprintf_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_printf_err("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;
}
glActiveTexture(GL_TEXTURE0 + position);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, t ? t->handle : 0);
glBindSampler(position, sampler);
}
////////////////////////////////////////////////////////////////
//
// 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;
}
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_sort_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;
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);
next_ttf = curr_ttf->next;
gbgl_free(curr_ttf);
curr_ttf = next_ttf;
}
next_font = curr_font->next;
gbgl_free(curr_font);
curr_font = next_font;
}
}
#endif
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_printf_err("Failed to cache font\n");
return NULL;
}
gb_zero_item(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);
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);
{
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_item(&(*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';
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);
}
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;
gbgl_free(px);
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;
}
gb_sort_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_sort_array(f->kern_table, f-
>kern_pair_count, gbgl__kern_pair_compare);
}
}
}
} else {
GB_PANIC("Failure loading font");
gb_zero_item(&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);
}
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;
}
f32 w = 0.0f;
f32 h = 0.0f;
char const *ptr = str;
len = gb_strlen(str);
char_count = gb_utf8_strnlen(str, len);
if (out_width) *out_width = w;
if (out_height) *out_height = h;
}
}
return w;
}
if (i < char_count-1) {
char32 next_cp;
isize next_index;
gb_utf8_decode_len(ptr, str_len-(ptr-str), &next_cp);
if (gi) {
w += gi->xadv + kern;
}
}
return line_count;
}
#endif
////////////////////////////////////////////////////////////////
//
// Basic State
//
//
#if !defined(GBGL_NO_BASIC_STATE)
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",
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",
#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",
glGenVertexArrays(1, &bs->font_vao);
glBindVertexArray(bs->font_vao);
bs->text_params[gbglTextParam_MaxWidth] .val_i32 = 0;
bs->text_params[gbglTextParam_Justify] .val_i32 = gbglJustify_Left;
bs->text_params[gbglTextParam_TextureFilter].val_i32 = 0;
#endif
}
bs->width = window_width;
bs->height = window_height;
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->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->mipmap_sampler);
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);
}
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);
glEnable(GL_BLEND);
glBlendEquation(GL_FUNC_ADD);
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
}
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);
}
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, gbglColour 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);
}
bs->vertices[0].x = x;
bs->vertices[0].y = y;
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++;
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++;
}
#if !defined(GBGL_NO_FONTS)
isize glyph_count = 0, i;
f32 font_height = font->size;
i32 max_width = bs->text_params[gbglTextParam_MaxWidth].val_i32;
line_count = 1;
ox = x;
oy = y;
px = ox;
py = oy;
if (cp == '\t') {
draw_this_glyph_count = GBGL_TAB_CHARACTER_WIDTH;
cp = ' '; // TODO(bill): Set tab to be space
}
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;
py -= font_height;
line_count += 2;
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);
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[gbglTextParam_TextureFilter].val_i32 <
gb_count_of(bs->font_samplers));
if (bs->text_params[gbglTextParam_TextureFilter].val_i32 <
gb_count_of(bs->font_samplers))
sampler_index = bs-
>text_params[gbglTextParam_TextureFilter].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);
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,
gbglColour 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;
}
#endif // !defined(GBGL_NO_FONTS)
#endif // !defined(GBGL_NO_BASIC_STATE)
#endif