#include "glad.h" #include #include #include #include "shorthand.h" #include "bd-font.c" #define TEXT_OVERLAY_SIZE 4 static char text[2048]; static u32 text_n = 0; // // Callbacks. // void glfw_error_callback(int error, const char* description) { (void)error; fprintf(stderr, "error: %s\n", description); } void glfw_key_callback(GLFWwindow* window, int key, int scancode, int action, int mods) { (void)mods; (void)scancode; if (action == GLFW_PRESS && (key == GLFW_KEY_ESCAPE || key == GLFW_KEY_CAPS_LOCK)) { glfwSetWindowShouldClose(window, GLFW_TRUE); } } void glfw_text_callback(GLFWwindow* window, unsigned int codepoint) { (void)window; text[text_n++] = codepoint; } // Letter for a the monospace text overlay layer. struct Letter { f32 x; f32 y; f32 idx; }; typedef struct Context { GLFWwindow *window; u32 gl_version_major; u32 gl_version_minor; s32 win_width; s32 win_height; char win_title[256]; bool win_resizable; struct { f64 prev; f64 elapsed; size_t n_frames; } frame_time; struct { u32 program; u32 tex_id; u32 vao; u32 vbo; u32 ebo; struct Letter letters[2048]; u32 cur_x; u32 cur_y; u32 n_chars; u32 size; } text_overlay; } Context; u32 compile_program(Context *ctx, const char *vert_src, const char *frag_src) { // Compile vertex shader. u32 vert_shader = glCreateShader(GL_VERTEX_SHADER); { glShaderSource(vert_shader, 1, &vert_src, NULL); glCompileShader(vert_shader); int success = 0; glGetShaderiv(vert_shader, GL_COMPILE_STATUS, &success); if (!success) { fprintf(stderr, "error: vertex shader compilation failed\n"); glfwDestroyWindow(ctx->window); glfwTerminate(); exit(EXIT_FAILURE); } } // Compile fragment shader. u32 frag_shader = glCreateShader(GL_FRAGMENT_SHADER); { glShaderSource(frag_shader, 1, &frag_src, NULL); glCompileShader(frag_shader); int success = 0; glGetShaderiv(vert_shader, GL_COMPILE_STATUS, &success); if (!success) { fprintf(stderr, "error: fragment shader compilation failed\n"); glfwDestroyWindow(ctx->window); glfwTerminate(); exit(EXIT_FAILURE); } } // Link shader program. u32 program = glCreateProgram(); { glAttachShader(program, vert_shader); glAttachShader(program, frag_shader); glLinkProgram(program); int success = 0; glGetProgramiv(program, GL_LINK_STATUS, &success); if(!success) { fprintf(stderr, "error: program shader linking failed\n"); glfwDestroyWindow(ctx->window); glfwTerminate(); exit(EXIT_FAILURE); } } // Delete unused objects. glDeleteShader(vert_shader); glDeleteShader(frag_shader); return program; } void init_context(Context *ctx) { if (!glfwInit()) { fprintf(stderr, "error: GLFW initialization failed"); exit(EXIT_FAILURE); } // Init window and OpenGL context. glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, ctx->gl_version_major); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, ctx->gl_version_minor); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); glfwWindowHint(GLFW_RESIZABLE, ctx->win_resizable); GLFWwindow* window = glfwCreateWindow( ctx->win_width, ctx->win_height, ctx->win_title, NULL, NULL); if (!window) { fprintf(stderr, "error: couldn't open OpenGL window"); glfwTerminate(); exit(EXIT_FAILURE); } // Enable current OpenGL context and init GLAD. glfwMakeContextCurrent(window); if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) { fprintf(stderr, "error: GLAD initialization failed"); glfwDestroyWindow(window); glfwTerminate(); exit(EXIT_FAILURE); } ctx->window = window; } static inline u32 unpack_nibble(u8 hex) { const u32 conversion_u32[16] = { 0x00000000, 0x00000001, 0x00000100, 0x00000101, 0x00010000, 0x00010001, 0x00010100, 0x00010101, 0x01000000, 0x01000001, 0x01000100, 0x01000101, 0x01010000, 0x01010001, 0x01010100, 0x01010101, }; return conversion_u32[hex & 0x0F]; } // Unpack N tiles packed at 1bpp. static inline void unpack_letter(const u32 *src, u32 *dst) { *dst++ = unpack_nibble((src[1] >> 0) & 0x0F); *dst++ = unpack_nibble((src[1] >> 4) & 0x0F); *dst++ = unpack_nibble((src[1] >> 8) & 0x0F); *dst++ = unpack_nibble((src[1] >> 12) & 0x0F); *dst++ = unpack_nibble((src[1] >> 16) & 0x0F); *dst++ = unpack_nibble((src[1] >> 20) & 0x0F); *dst++ = unpack_nibble((src[1] >> 24) & 0x0F); *dst++ = unpack_nibble((src[1] >> 28) & 0x0F); *dst++ = unpack_nibble((src[0] >> 0) & 0x0F); *dst++ = unpack_nibble((src[0] >> 4) & 0x0F); *dst++ = unpack_nibble((src[0] >> 8) & 0x0F); *dst++ = unpack_nibble((src[0] >> 12) & 0x0F); *dst++ = unpack_nibble((src[0] >> 16) & 0x0F); *dst++ = unpack_nibble((src[0] >> 20) & 0x0F); *dst++ = unpack_nibble((src[0] >> 24) & 0x0F); *dst++ = unpack_nibble((src[0] >> 28) & 0x0F); } void init_debug_overlay(Context *ctx) { // Prepare quad rendering. f32 vertices[] = { // position // tex_coords 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, -1.0f, 1.0f, 0.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f, 1.0f, }; GLuint indices[] = { 0, 1, 3, 1, 2, 3 }; // Vertex array object. glGenVertexArrays(1, &ctx->text_overlay.vao); glBindVertexArray(ctx->text_overlay.vao); // Vertex buffer object setup. u32 vbo; glGenBuffers(1, &vbo); glBindBuffer(GL_ARRAY_BUFFER, vbo); glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); glEnableVertexAttribArray(0); glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(f32), (void*)0); glEnableVertexAttribArray(1); glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(f32), (void*)(2 * sizeof(f32))); glGenBuffers(1, &ctx->text_overlay.vbo); glBindBuffer(GL_ARRAY_BUFFER, ctx->text_overlay.vbo); glEnableVertexAttribArray(2); glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(f32), (void*)0); glVertexAttribDivisor(2, 1); // Element buffer object setup. glGenBuffers(1, &ctx->text_overlay.ebo); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ctx->text_overlay.ebo); glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW); // Unpack texture atlas. u8 texture[256 * 8 * 8] = {0}; for (size_t i = 0; i < 256; i++) { unpack_letter(&bd_font[i * 2], (u32*)&texture[i * 8 * 8]); } // Create texture for text. glGenTextures(1, &ctx->text_overlay.tex_id); glBindTexture(GL_TEXTURE_2D, ctx->text_overlay.tex_id); glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, 8, // Width 256 * 8, // Height 0, GL_RED, GL_UNSIGNED_BYTE, &texture); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glBindTexture(GL_TEXTURE_2D, 0); // Unbind objects to avoid accidental modifications. glBindVertexArray(0); glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); // TODO: Store the shaders as separate files for easier editing. const char* vert_src = "#version 330 core\n" "layout (location = 0) in vec2 position;\n" "layout (location = 1) in vec2 tex_coords;\n" "layout (location = 2) in vec3 offset;\n" "out vec2 tex;\n" "uniform float w;\n" "uniform float h;\n" "uniform float size;\n" "void main() {\n" " float N = 256;\n" " float idx = offset.z;\n" " float m = 1.0 / N;\n" " float k = m * idx;\n" // TODO: Only 8x8 monospace fonts for now " float font_w = 8;\n" " float font_h = 8;\n" // NOTE: This makes sures that the fonts have the right size. " vec2 scalar = vec2(font_w / w, font_h / h) * size;\n" " vec2 pos = tex_coords * scalar;\n" " pos += vec2(0.0, 1.0 - scalar.y);\n" " pos += scalar * offset.xy;\n" " pos = mix(vec2(-1.0), vec2(1.0), pos);\n" " gl_Position = vec4(pos, 0.0, 1.0);\n" " tex = tex_coords * vec2(1.0, m) + vec2(0.0, k);\n" "}"; // Fragment shader. const char* frag_src = "#version 330 core\n" "in vec2 tex;\n" "out vec4 frag;\n" "uniform usampler2D tex_sampler;\n" "void main() {\n" " uint val = texture(tex_sampler, tex).r;\n" " frag = vec4(val, val, val, 1.0);\n" "}\n"; ctx->text_overlay.program = compile_program(ctx, vert_src, frag_src); ctx->text_overlay.cur_x = 0; ctx->text_overlay.cur_y = 0; ctx->text_overlay.n_chars = 0; ctx->text_overlay.size = TEXT_OVERLAY_SIZE; } void setup_callbacks(Context *ctx) { glfwSetErrorCallback(glfw_error_callback); glfwSetKeyCallback(ctx->window, glfw_key_callback); glfwSetCharCallback(ctx->window, glfw_text_callback); } void update_frame_time(Context *ctx) { // Measure frame times and fps. f64 cur_time = glfwGetTime(); f64 delta_time = cur_time - ctx->frame_time.prev; ctx->frame_time.elapsed += delta_time; ctx->frame_time.n_frames++; if (ctx->frame_time.elapsed >= 1.0) { f64 fps = 0; fps = ctx->frame_time.n_frames / ctx->frame_time.elapsed; ctx->frame_time.elapsed = 0; ctx->frame_time.n_frames = 0; // Update title with timing data. char title_buf[256 * 2]; sprintf(title_buf, "%s [%.2fms, %.2f FPS]", ctx->win_title, delta_time * 1000, fps); glfwSetWindowTitle(ctx->window, title_buf); } ctx->frame_time.prev = cur_time; } void clear_text(Context *ctx) { ctx->text_overlay.cur_x = 0; ctx->text_overlay.cur_y = 0; ctx->text_overlay.n_chars = 0; } void add_text(Context *ctx, char *txt) { while (*txt != '\0') { if (*txt == '\n') { ctx->text_overlay.cur_x = 0; ctx->text_overlay.cur_y++; txt++; continue; } ctx->text_overlay.letters[ctx->text_overlay.n_chars].x = ctx->text_overlay.cur_x++; ctx->text_overlay.letters[ctx->text_overlay.n_chars].y = ctx->text_overlay.cur_y; ctx->text_overlay.letters[ctx->text_overlay.n_chars].idx = *txt++; ctx->text_overlay.n_chars++; } } void update_text(Context *ctx) { clear_text(ctx); add_text(ctx, text); glBindBuffer(GL_ARRAY_BUFFER, ctx->text_overlay.vbo); glBufferData(GL_ARRAY_BUFFER, sizeof(struct Letter) * ctx->text_overlay.n_chars, ctx->text_overlay.letters, GL_STATIC_DRAW); } void update(Context *ctx) { update_frame_time(ctx); update_text(ctx); } void render(Context *ctx) { glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glfwGetFramebufferSize(ctx->window, &ctx->win_width, &ctx->win_height); glViewport(0, 0, ctx->win_width, ctx->win_height); glUseProgram(ctx->text_overlay.program); glBindVertexArray(ctx->text_overlay.vao); glBindTexture(GL_TEXTURE_2D, ctx->text_overlay.tex_id); // Update uniforms. glUniform1f(glGetUniformLocation(ctx->text_overlay.program, "w"), ctx->win_width); glUniform1f(glGetUniformLocation(ctx->text_overlay.program, "h"), ctx->win_height); glUniform1f(glGetUniformLocation(ctx->text_overlay.program, "size"), ctx->text_overlay.size); // TODO: Which blend mode to use? // glEnable(GL_BLEND); // glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); // // glBlendFunc(GL_ONE, GL_ONE); // // glBlendFunc(GL_DST_ALPHA, GL_ONE_MINUS_DST_ALPHA); // // glBlendFunc(GL_SRC_ALPHA, GL_ONE); // // glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // // glBlendFunc(GL_ONE, GL_ZERO); // // glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA); // // glBlendFunc(GL_DST_ALPHA,GL_ONE); // // glBlendFunc(GL_DST_ALPHA, GL_SRC_ALPHA); // // glBlendFunc(GL_DST_ALPHA, GL_DST_ALPHA); // // glBlendFunc(GL_DST_ALPHA, GL_ONE_MINUS_DST_ALPHA); // // glBlendEquation(GL_FUNC_ADD); // glDrawElementsInstanced(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0, ctx->text_overlay.n_chars); // glDisable(GL_BLEND); glfwSwapBuffers(ctx->window); } int main(void) { // // Initialization. // Context ctx = (Context){ .gl_version_major = 3, .gl_version_minor = 2, .win_width = 800, .win_height = 600, .win_title = "OpenGL experiments", }; init_context(&ctx); setup_callbacks(&ctx); init_debug_overlay(&ctx); // // Main loop. // ctx.frame_time.prev = glfwGetTime(); ctx.frame_time.elapsed = 0; ctx.frame_time.n_frames = 0; while (!glfwWindowShouldClose(ctx.window)) { update(&ctx); render(&ctx); glfwPollEvents(); // constant updates. // glfwWaitEvents(); // updates only when input arrives (better cpu usage). } glfwDestroyWindow(ctx.window); glfwTerminate(); return EXIT_SUCCESS; }