346 lines
11 KiB
C
346 lines
11 KiB
C
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;
|
|
}
|