summaryrefslogtreecommitdiff
path: root/base/overlay.c
diff options
context:
space:
mode:
Diffstat (limited to 'base/overlay.c')
-rw-r--r--base/overlay.c375
1 files changed, 375 insertions, 0 deletions
diff --git a/base/overlay.c b/base/overlay.c
new file mode 100644
index 0000000..2316b24
--- /dev/null
+++ b/base/overlay.c
@@ -0,0 +1,375 @@
+#include "data/font_info.h"
+INCBIN(_font_texture, "data/font.ugg");
+struct ugg *font_texture_data = (struct ugg*)_font_texture_data;
+
+/* Overlay state structure */
+struct overlay {
+ GLuint vao;
+ GLuint vbo;
+ GLuint ebo;
+ GLuint program;
+ GLint loc_proj;
+ GLint loc_tex;
+ GLint loc_color;
+ GLint loc_pos_offset;
+ GLuint font_texture;
+ GLuint white_texture;
+};
+
+static struct overlay overlay_state;
+static float mat[16];
+
+/* ------------------------------------------------------------------------- */
+/* Shader sources for a top-left orthographic approach */
+/* ------------------------------------------------------------------------- */
+static const char* overlay_vertex_shader_src =
+"#version 140\n"
+"in vec2 in_pos;\n"
+"in vec2 in_uv;\n"
+"uniform mat4 u_projection;\n"
+"uniform vec2 u_pos_offset;\n"
+"out vec2 v_uv;\n"
+"void main() {\n"
+" vec2 pos = in_pos + u_pos_offset;\n"
+" gl_Position = u_projection * vec4(pos, 0.0, 1.0);\n"
+" v_uv = in_uv;\n"
+"}\n";
+
+static const char* overlay_fragment_shader_src =
+"#version 140\n"
+"uniform sampler2D u_font_texture;\n"
+"uniform vec4 u_color;\n"
+"in vec2 v_uv;\n"
+"out vec4 frag_color;\n"
+"void main() {\n"
+" vec4 tex_sample = texture(u_font_texture, v_uv);\n"
+" float alpha = tex_sample.a;\n"
+" frag_color = vec4(u_color.rgb * alpha, u_color.a * alpha);\n"
+"}\n";
+
+/* ------------------------------------------------------------------------- */
+/* Shader helpers */
+/* ------------------------------------------------------------------------- */
+__attribute__((cold, noinline, section(".init_section")))
+static GLuint overlay_compile_shader(const char *source, GLenum type) {
+ GLuint shader = glCreateShader(type);
+ if(shader == 0) {
+ fprintf(stderr, "Error creating shader of type %d.\n", type);
+ return 0;
+ }
+
+ glShaderSource(shader, 1, &source, 0);
+ glCompileShader(shader);
+
+ /* Check for compilation errors */
+ GLint success;
+ glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
+ if(!success) {
+ GLint log_size = 0;
+ glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &log_size);
+ char log[2048];
+ glGetShaderInfoLog(shader, log_size, 0, log);
+ fprintf(stderr, "Shader compilation failed:\n%s\n", log);
+ glDeleteShader(shader);
+ return 0;
+ }
+
+ return shader;
+}
+
+__attribute__((cold, noinline, section(".init_section")))
+static GLuint overlay_create_program(const char *vs_source, const char *fs_source) {
+ GLuint vs = overlay_compile_shader(vs_source, GL_VERTEX_SHADER);
+ GLuint fs = overlay_compile_shader(fs_source, GL_FRAGMENT_SHADER);
+ GLuint prog = glCreateProgram();
+
+ glAttachShader(prog, vs);
+ glAttachShader(prog, fs);
+ glLinkProgram(prog);
+
+ /* Check for linking errors */
+ GLint success;
+ glGetProgramiv(prog, GL_LINK_STATUS, &success);
+ if(!success) {
+ /* Get and print log */
+ GLint log_size = 0;
+ glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &log_size);
+ char log[2048];
+ glGetProgramInfoLog(prog, log_size, 0, log);
+ fprintf(stderr, "Program linking failed:\n%s\n", log);
+ glDeleteProgram(prog);
+ glDeleteShader(vs);
+ glDeleteShader(fs);
+ return 0;
+ }
+
+ /* Shaders can be deleted after linking */
+ glDeleteShader(vs);
+ glDeleteShader(fs);
+
+ return prog;
+}
+
+static void overlay_make_ortho_top_left(float w, float h, float m[16]) {
+ m[0] = 2.0f / w;
+ m[5] = -2.0f / h;
+ m[10] = -1.0f;
+ m[12] = -1.0f;
+ m[13] = 1.0f;
+ m[15] = 1.0f;
+}
+
+#define MAX_GLYPHS 128 // NOTE(peter): max glyphs per ROW
+#define MAX_VERTICES (MAX_PROFILING_ENTRIES * MAX_GLYPHS * 4 * 4)
+float vertices[MAX_VERTICES] __attribute__((section(".bss")));
+
+static void overlay_init(void) {
+ overlay_state.program = overlay_create_program(overlay_vertex_shader_src, overlay_fragment_shader_src);
+ overlay_state.loc_proj = glGetUniformLocation(overlay_state.program, "u_projection");
+ overlay_state.loc_tex = glGetUniformLocation(overlay_state.program, "u_font_texture");
+ overlay_state.loc_color = glGetUniformLocation(overlay_state.program, "u_color");
+ overlay_state.loc_pos_offset = glGetUniformLocation(overlay_state.program, "u_pos_offset");
+
+ glGenVertexArrays(1, &overlay_state.vao);
+ glBindVertexArray(overlay_state.vao);
+
+ glGenBuffers(1, &overlay_state.vbo);
+ glBindBuffer(GL_ARRAY_BUFFER, overlay_state.vbo);
+ glBufferData(GL_ARRAY_BUFFER, MAX_VERTICES * sizeof(float), 0, GL_DYNAMIC_DRAW);
+
+ glGenBuffers(1, &overlay_state.ebo);
+
+ unsigned short indices[MAX_PROFILING_ENTRIES * MAX_GLYPHS * 6];
+ size_t index_offset = 0;
+ size_t vertex_offset = 0;
+
+ for(size_t i = 0; i < MAX_PROFILING_ENTRIES * MAX_GLYPHS; i++) {
+ indices[index_offset++] = vertex_offset + 0;
+ indices[index_offset++] = vertex_offset + 1;
+ indices[index_offset++] = vertex_offset + 2;
+ indices[index_offset++] = vertex_offset + 0;
+ indices[index_offset++] = vertex_offset + 2;
+ indices[index_offset++] = vertex_offset + 3;
+ vertex_offset += 4;
+ }
+
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, overlay_state.ebo);
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
+
+ GLint in_pos_attrib = glGetAttribLocation(overlay_state.program, "in_pos");
+ GLint in_uv_attrib = glGetAttribLocation(overlay_state.program, "in_uv");
+
+ glEnableVertexAttribArray(in_pos_attrib);
+ glVertexAttribPointer(in_pos_attrib, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)0);
+ glEnableVertexAttribArray(in_uv_attrib);
+ glVertexAttribPointer(in_uv_attrib, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)(2 * sizeof(float)));
+
+ glBindVertexArray(0);
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+
+
+ uint8_t *rgba_data = mks_alloc(512 * 512 * 4);
+ uint8_t *dst = rgba_data;
+ for(uint32_t i = 0; i < 512 * 512; ++i) {
+ uint8_t alpha = font_texture_data->data[i];
+ if(alpha) {
+ *dst++ = 255;
+ *dst++ = 255;
+ *dst++ = 255;
+ *dst++ = alpha;
+
+ } else {
+ *dst++ = 0;
+ *dst++ = 0;
+ *dst++ = 0;
+ *dst++ = 0;
+ }
+ }
+ glGenTextures(1, &overlay_state.font_texture);
+ glBindTexture(GL_TEXTURE_2D, overlay_state.font_texture);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ // glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ // glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ // glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 512, 512, 0, GL_RGBA, GL_UNSIGNED_BYTE, rgba_data);
+ glGenerateMipmap(GL_TEXTURE_2D);
+ mks_free(rgba_data);
+
+ glGenTextures(1, &overlay_state.white_texture);
+ glBindTexture(GL_TEXTURE_2D, overlay_state.white_texture);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ uint32_t white[] = { 0xffffffff };
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_SRGB8_ALPHA8, 1, 1, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, white);
+
+ glBindTexture(GL_TEXTURE_2D, 0);
+}
+
+__attribute__((cold, noinline, section(".init_section")))
+static void overlay_shutdown(void) {
+ glDeleteProgram(overlay_state.program);
+ glDeleteBuffers(1, &overlay_state.vbo);
+ glDeleteBuffers(1, &overlay_state.ebo);
+ glDeleteVertexArrays(1, &overlay_state.vao);
+ glDeleteTextures(1, &overlay_state.font_texture);
+}
+
+static void overlay_render_rect(float x1, float y1, float x2, float y2, float r, float g, float b, float a) {
+ float vertices[4 * 4] = {
+ /* pos.x, pos.y, u, v */
+ x1, y1, 0.f, 0.f, // Top-left
+ x2, y1, 1.f, 0.f, // Top-right
+ x2, y2, 1.f, 1.f, // Bottom-right
+ x1, y2, 0.f, 1.f // Bottom-left
+ };
+
+ glBindVertexArray(overlay_state.vao);
+ glBindBuffer(GL_ARRAY_BUFFER, overlay_state.vbo);
+ glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices);
+ glUseProgram(overlay_state.program);
+ glUniform4f(overlay_state.loc_color, r, g, b, a);
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_2D, overlay_state.white_texture);
+ glUniform1i(overlay_state.loc_tex, 0);
+ glUniform2f(overlay_state.loc_pos_offset, 0.0f, 0.0f);
+ glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, 0);
+ glBindTexture(GL_TEXTURE_2D, 0);
+ glBindVertexArray(0);
+}
+
+__attribute__((always_inline))
+static inline void overlay_render_text_line(uint8_t **lines, float x, float y, float r, float g, float b, float a) {
+ uint32_t vertex_offset = 0;
+ uint32_t glyph_count = 0;
+
+ glBindVertexArray(overlay_state.vao);
+ glBindBuffer(GL_ARRAY_BUFFER, overlay_state.vbo);
+
+ float target_width = 24.f * .40f;
+ float target_height = 48.f * .40f;
+
+ // Convert font units to pixels
+ float _scale = 48.0f / (1900 - (-480));
+ float scaled_ascent = 1900 * _scale;
+ float scale_x = target_width / 24.0f;
+ float scale_y = target_height / 48.0f;
+
+ // **Apply baseline correction once (not per character!)**
+ y += scaled_ascent * scale_y;
+
+ for(int i = 0; i < MAX_PROFILING_ENTRIES; i++) {
+ uint8_t *text = lines[i];
+ if(!text) continue;
+
+ float cx = x;
+ while(*text) {
+ uint8_t c = *text++;
+ struct glyph_info *g = &glyph_data[c];
+
+ // Normalize texture coordinates
+ float u0 = g->x / 512.f;
+ float v0 = g->y / 512.f;
+ float u1 = (g->x + g->width) / 512.f;
+ float v1 = (g->y + g->height) / 512.f;
+
+ // Apply width & height scaling
+ float glyph_width = g->width * scale_x;
+ float glyph_height = g->height * scale_y;
+
+ // **Fix baseline positioning**
+ float x0 = roundf(cx + (g->x_offset * scale_x));
+ float y0 = roundf(y + (g->y_offset * scale_y));
+ float x1 = x0 + glyph_width;
+ float y1 = y0 + glyph_height;
+
+ // Generate quad for the character
+ vertices[vertex_offset + 0] = x0;
+ vertices[vertex_offset + 1] = y0;
+ vertices[vertex_offset + 2] = u0;
+ vertices[vertex_offset + 3] = v0;
+
+ vertices[vertex_offset + 4] = x1;
+ vertices[vertex_offset + 5] = y0;
+ vertices[vertex_offset + 6] = u1;
+ vertices[vertex_offset + 7] = v0;
+
+ vertices[vertex_offset + 8] = x1;
+ vertices[vertex_offset + 9] = y1;
+ vertices[vertex_offset + 10] = u1;
+ vertices[vertex_offset + 11] = v1;
+
+ vertices[vertex_offset + 12] = x0;
+ vertices[vertex_offset + 13] = y1;
+ vertices[vertex_offset + 14] = u0;
+ vertices[vertex_offset + 15] = v1;
+
+ // Move cursor forward
+ cx += g->advance * scale_x;
+ vertex_offset += 16;
+ glyph_count++;
+ }
+ y += target_height; // Move to next row
+ }
+
+
+ if(glyph_count > 0) {
+ glBindVertexArray(overlay_state.vao);
+ glBindBuffer(GL_ARRAY_BUFFER, overlay_state.vbo);
+ glBufferSubData(GL_ARRAY_BUFFER, 0, vertex_offset * sizeof(float), vertices);
+
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_2D, overlay_state.font_texture);
+ glUniform1i(overlay_state.loc_tex, 0);
+ glUniform4f(overlay_state.loc_color, r, g, b, a);
+ glDrawElements(GL_TRIANGLES, glyph_count * 6, GL_UNSIGNED_SHORT, 0);
+ glBindVertexArray(0);
+ }
+}
+
+static void overlay_render(float rect_x, float rect_y, float rect_w, float rect_h, uint8_t **lines, int window_w, int window_h) {
+ overlay_make_ortho_top_left((float)window_w, (float)window_h, mat);
+ glViewport(0.f, 0.f, window_w, window_h);
+ glUseProgram(overlay_state.program);
+ glUniformMatrix4fv(overlay_state.loc_proj, 1, GL_FALSE, mat);
+
+ overlay_render_rect(rect_x, rect_y, rect_x + rect_w, rect_y + rect_h, .01f, .013f, .04f, .6f);
+ overlay_render_text_line(lines, rect_x + 2.f, rect_y + 2.f, 0.f, 0.f, 0.f, 1.f);
+ overlay_render_text_line(lines, rect_x, rect_y, 1.f, 1.f, 1.f, 1.f);
+}
+
+/* [=]===^=[ debug_render ]=================================================================^===[=] */
+__attribute__((section(".bss")))
+uint8_t *debug_lines[MAX_PROFILING_ENTRIES];
+static void debug_render(void) {
+ if(state.overlay) {
+ size_t offset = 0;
+ for(uint32_t i = 0; i < MAX_PROFILING_ENTRIES; ++i) {
+ if(state.debug.timings[i].count) {
+ debug_lines[i] = &debug_line_buffer[offset];
+ offset += stbsp_sprintf((char *)&debug_line_buffer[offset], "%25s: cycles=%7" PRIu64 ", count=%2u, cycles/count=%7" PRIu64, state.debug.timings[i].name, state.debug.timings[i].cycles, state.debug.timings[i].count, state.debug.timings[i].cycles / state.debug.timings[i].count) + 1;
+ } else {
+ debug_lines[i] = 0;
+ }
+ }
+ overlay_render(20.f, 20.f, 705.f, 280.f, debug_lines, state.screen_width, state.screen_height);
+
+ offset = 0;
+ for(uint32_t i = 0; i < MAX_PROFILING_ENTRIES; ++i) {
+ debug_lines[i] = 0;
+ }
+ debug_lines[0] = &debug_line_buffer[offset];
+ offset += stbsp_sprintf((char *)&debug_line_buffer[offset], "Total memory allocated: %" PRIu64, state.total_allocated) + 1;
+ overlay_render(20.f, 340.f, 500.f, 200.f, debug_lines, state.screen_width, state.screen_height);
+ }
+}
+
+/* [=]===^=[ reset_profiling_data ]=================================================================^===[=] */
+static inline void reset_profiling_data(void) {
+ memset(&state.debug, 0, sizeof(state.debug));
+}