typedef struct rn_shader_t rn_shader_t; struct rn_shader_t { u32 pipeline; u32 fshader; u32 vshader; }; typedef struct rn_vertex_t rn_vertex_t; struct rn_vertex_t { v2f32_t pos; v2f32_t tex; v4f32_t color; }; typedef enum { rn_cmd_kind_null, rn_cmd_kind_quad, rn_cmd_kind_set_clip, } rn_cmd_kind_t; typedef struct rn_cmd_t rn_cmd_t; struct rn_cmd_t { rn_cmd_t *next; rn_cmd_kind_t kind; union { v4f32_t color; r2f32_t rect; struct { rn_vertex_t *vertex; u32 len; }; }; }; typedef struct rn_state_t rn_state_t; struct rn_state_t { rn_font_t main_font; rn_shader_t shader2d; rn_cmd_t *first_cmd; rn_cmd_t *last_cmd; rn_vertex_t *vertices; u32 len; u32 cap; u32 vao; u32 vbo; }; rn_state_t rn_state; rn_shader_t rn_create_shader(char *glsl_vshader, char *glsl_fshader) { rn_shader_t result = {}; result.vshader = glCreateShaderProgramv(GL_VERTEX_SHADER, 1, &glsl_vshader); result.fshader = glCreateShaderProgramv(GL_FRAGMENT_SHADER, 1, &glsl_fshader); GLint linked; glGetProgramiv(result.vshader, GL_LINK_STATUS, &linked); if (!linked) { char message[1024]; glGetProgramInfoLog(result.vshader, sizeof(message), NULL, message); fatalf("failed to create vertex shader: %s", message); } glGetProgramiv(result.fshader, GL_LINK_STATUS, &linked); if (!linked) { char message[1024]; glGetProgramInfoLog(result.fshader, sizeof(message), NULL, message); fatalf("failed to create fragment shader: %s", message); } glGenProgramPipelines(1, &result.pipeline); glUseProgramStages(result.pipeline, GL_VERTEX_SHADER_BIT, result.vshader); glUseProgramStages(result.pipeline, GL_FRAGMENT_SHADER_BIT, result.fshader); return result; } void gl_debug_callback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *message, const void *user) { if (severity == GL_DEBUG_SEVERITY_HIGH || severity == GL_DEBUG_SEVERITY_MEDIUM) { fatalf("opengl message: %s", message); } else { debugf("opengl message: %s", message); } } rn_cmd_t *rn_get_cmd(rn_cmd_kind_t kind) { b32 alloc_new = false; if (rn_state.last_cmd == NULL) { alloc_new = true; } else if (rn_state.last_cmd->kind != kind) { alloc_new = true; } rn_cmd_t *result = rn_state.last_cmd; if (alloc_new) { result = ma_push_type(tcx.temp, rn_cmd_t); result->kind = kind; SLLQ_APPEND(rn_state.first_cmd, rn_state.last_cmd, result); if (rn_cmd_kind_quad) { result->vertex = rn_state.vertices + rn_state.len; } } return result; } rn_vertex_t *rn_push_vertex(rn_cmd_t *cmd, u32 count) { rn_vertex_t *result = cmd->vertex + cmd->len; rn_state.len += count; cmd->len += count; return result; } void rn_push_quad(r2f32_t rect, r2f32_t tex, v4f32_t color) { rn_cmd_t *cmd = rn_get_cmd(rn_cmd_kind_quad); rn_vertex_t *v = rn_push_vertex(cmd, 6); v[0] = (rn_vertex_t){ {rect.min.x, rect.max.y}, { tex.min.x, tex.max.y}, color }; v[1] = (rn_vertex_t){ {rect.max.x, rect.max.y}, { tex.max.x, tex.max.y}, color }; v[2] = (rn_vertex_t){ {rect.min.x, rect.min.y}, { tex.min.x, tex.min.y}, color }; v[3] = (rn_vertex_t){ {rect.min.x, rect.min.y}, { tex.min.x, tex.min.y}, color }; v[4] = (rn_vertex_t){ {rect.max.x, rect.max.y}, { tex.max.x, tex.max.y}, color }; v[5] = (rn_vertex_t){ {rect.max.x, rect.min.y}, { tex.max.x, tex.min.y}, color }; } void rn_draw_rect(r2f32_t rect, v4f32_t color) { rn_push_quad(rect, rn_state.main_font.white_texture_bounding_box, color); } void rn_draw_rect_border(r2f32_t rect, v4f32_t color) { r2f32_t left = r2f32_cut_left(&rect, 1); r2f32_t right = r2f32_cut_right(&rect, 1); r2f32_t top = r2f32_cut_top(&rect, 1); r2f32_t bottom = r2f32_cut_bottom(&rect, 1); rn_push_quad(left, rn_state.main_font.white_texture_bounding_box, color); rn_push_quad(right, rn_state.main_font.white_texture_bounding_box, color); rn_push_quad(top, rn_state.main_font.white_texture_bounding_box, color); rn_push_quad(bottom, rn_state.main_font.white_texture_bounding_box, color); } i64 rn_get_char_spacing(rn_font_t *font, u32 codepoint) { rn_glyph_t *g = rn_get_glyph(font, codepoint); if (g->xadvance) return (i64)g->xadvance; return (i64)g->size.x; } i64 rn_get_line_spacing(rn_font_t *font) { i64 result = (i64)(font->ascent - font->descent + font->line_gap); return result; } v2f32_t rn_base_draw_string(rn_font_t *font, s8_t string, v2f32_t pos, v4f32_t color, b32 draw) { pos.y += rn_get_line_spacing(font) + font->descent; v2f32_t original_pos = pos; for (utf8_iter_t iter = utf8_iterate_ex(string.str, (int)string.len); iter.item; utf8_advance(&iter)) { u32 codepoint = iter.item; rn_glyph_t *g = rn_get_glyph(font, codepoint); r2f32_t rect = r2f32_mindim(v2f32_add(pos, g->offset), g->size); if (draw && codepoint != '\n' && codepoint != ' ' && codepoint != '\t') { rn_push_quad(rect, g->atlas_bounding_box, color); } pos.x += g->xadvance; } v2f32_t result = {pos.x - original_pos.x, font->size}; return result; } v2f32_t rn_draw_string(rn_font_t *font, v2f32_t pos, v4f32_t color, s8_t string) { return rn_base_draw_string(font, string, pos, color, true); } v2f32_t rn_draw_stringf(rn_font_t *font, v2f32_t pos, v4f32_t color, char *str, ...) { S8_FMT(tcx.temp, str, result); return rn_draw_string(font, pos, color, result); } v2f32_t rn_measure_string(rn_font_t *font, s8_t string) { return rn_base_draw_string(font, string, v2f32(0,0), v4f32(0,0,0,0), false); } void rn_set_clip(r2f32_t rect) { rn_cmd_t *cmd = rn_get_cmd(rn_cmd_kind_set_clip); cmd->rect = rect; } void rn_init(ma_arena_t *perm, f32 _font_size) { rn_state.cap = 1024*256; rn_state.vertices = ma_push_array(perm, rn_vertex_t, rn_state.cap); { ma_temp_t scratch = ma_begin_scratch1(perm); s8_t font_data = rn_get_default_font(tcx.temp); rn_atlas_t *atlas = rn_create_atlas(scratch.arena, (v2i32_t){2048, 2048}); u32 font_size = (u32)_font_size; rn_state.main_font = rn_create_font(perm, font_data, atlas, font_size); GLint filter = GL_NEAREST; // filter = GL_LINEAR; glCreateTextures(GL_TEXTURE_2D, 1, &atlas->texture_id); glTextureParameteri(atlas->texture_id, GL_TEXTURE_MIN_FILTER, filter); glTextureParameteri(atlas->texture_id, GL_TEXTURE_MAG_FILTER, filter); glTextureParameteri(atlas->texture_id, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTextureParameteri(atlas->texture_id, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTextureStorage2D(atlas->texture_id, 1, GL_R8, (GLsizei)atlas->size.x, (GLsizei)atlas->size.y); glTextureSubImage2D(atlas->texture_id, 0, 0, 0, (GLsizei)atlas->size.x, (GLsizei)atlas->size.y, GL_RED, GL_UNSIGNED_BYTE, atlas->bitmap); rn_state.main_font.texture_id = atlas->texture_id; ma_end_scratch(scratch); } glDebugMessageCallback(&gl_debug_callback, NULL); glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); glCreateBuffers(1, &rn_state.vbo); glNamedBufferStorage(rn_state.vbo, rn_state.cap * sizeof(rn_vertex_t), 0, GL_DYNAMIC_STORAGE_BIT); glCreateVertexArrays(1, &rn_state.vao); GLint vbuf_index = 0; glVertexArrayVertexBuffer(rn_state.vao, vbuf_index, rn_state.vbo, 0, sizeof(struct rn_vertex_t)); GLint a_pos = 0; glVertexArrayAttribFormat(rn_state.vao, a_pos, 2, GL_FLOAT, GL_FALSE, offsetof(struct rn_vertex_t, pos)); glVertexArrayAttribBinding(rn_state.vao, a_pos, vbuf_index); glEnableVertexArrayAttrib(rn_state.vao, a_pos); GLint a_tex = 1; glVertexArrayAttribFormat(rn_state.vao, a_tex, 2, GL_FLOAT, GL_FALSE, offsetof(struct rn_vertex_t, tex)); glVertexArrayAttribBinding(rn_state.vao, a_tex, vbuf_index); glEnableVertexArrayAttrib(rn_state.vao, a_tex); GLint a_color = 2; glVertexArrayAttribFormat(rn_state.vao, a_color, 4, GL_FLOAT, GL_FALSE, offsetof(struct rn_vertex_t, color)); glVertexArrayAttribBinding(rn_state.vao, a_color, vbuf_index); glEnableVertexArrayAttrib(rn_state.vao, a_color); char *glsl_vshader = "#version 450 core\n" "layout(location=0) uniform vec2 U_InvHalfScreenSize;\n" "layout(location=0) in vec2 VPos;\n" "layout(location=1) in vec2 VTex;\n" "layout(location=2) in vec4 VColor;\n" "\n" "out gl_PerVertex { vec4 gl_Position; }; // required because of ARB_separate_shader_objects\n" "out vec2 FUV;\n" "out vec4 FColor;\n" "\n" "void main() {\n" " vec2 pos = VPos * U_InvHalfScreenSize;\n" " pos.y = 2 - pos.y; // invert y axis\n" " pos -= vec2(1, 1); // convert to 0,1 range\n" "\n" " gl_Position = vec4(pos, 0, 1);\n" " FUV = VTex;\n" "\n" " FColor = VColor;\n" "}\n"; char *glsl_fshader = "#version 450 core\n" "\n" "in vec2 FUV;\n" "in vec4 FColor;\n" "\n" "layout (binding=0) uniform sampler2D S_Texture;\n" "layout (location=0) out vec4 OutColor;\n" "\n" "void main() {\n" " vec4 c = FColor;\n" " c.a *= texture(S_Texture, FUV).r;\n" " OutColor = c;\n" "}\n"; rn_state.shader2d = rn_create_shader(glsl_vshader, glsl_fshader); } void rn_begin(void) { } void rn_end(v2f32_t window_size, v4f32_t color) { f32 wx = window_size.x; f32 wy = window_size.y; glEnable(GL_BLEND); glEnable(GL_SCISSOR_TEST); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glDisable(GL_DEPTH_TEST); glDisable(GL_CULL_FACE); glViewport(0, 0, (GLsizei)wx, (GLsizei)wy); glScissor(0, 0, (GLsizei)wx, (GLsizei)wy); glClearColor(color.r, color.g, color.b, color.a); glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); // Default draw using the font texture glBindProgramPipeline(rn_state.shader2d.pipeline); float xinverse = 1.f / (wx / 2.f); float yinverse = 1.f / (wy / 2.f); glProgramUniform2f(rn_state.shader2d.vshader, 0, xinverse, yinverse); for (rn_cmd_t *it = rn_state.first_cmd; it; it = it->next) { if (it->kind == rn_cmd_kind_quad) { glNamedBufferSubData(rn_state.vbo, 0, it->len * sizeof(rn_vertex_t), it->vertex); glBindVertexArray(rn_state.vao); GLint s_texture = 0; // texture unit that sampler2D will use in GLSL code glBindTextureUnit(s_texture, rn_state.main_font.texture_id); glDrawArrays(GL_TRIANGLES, 0, it->len); } else if (it->kind == rn_cmd_kind_set_clip) { GLint x = (GLint)it->rect.min.x; GLint y = (GLint)it->rect.min.y; GLsizei w = (GLsizei)(it->rect.max.x - x); GLsizei h = (GLsizei)(it->rect.max.y - y); if (w <= 0) w = 0; if (h <= 0) h = 0; glScissor(x, (GLint)window_size.y - (GLint)it->rect.max.y, w, h); } else_is_invalid; } rn_state.first_cmd = NULL; rn_state.last_cmd = NULL; rn_state.len = 0; }