// stdlib #include // graphics #define GLFW_INCLUDE_NONE #include #include "glad.c" #define IMGUI_IMPL_OPENGL_LOADER_CUSTOM #include "imgui/imgui.cpp" #include "imgui/imgui_demo.cpp" #include "imgui/imgui_draw.cpp" #include "imgui/imgui_tables.cpp" #include "imgui/imgui_widgets.cpp" #include "imgui/backends/imgui_impl_opengl3.cpp" #include "imgui/backends/imgui_impl_glfw.cpp" // fonts #include #include FT_FREETYPE_H // math types #include #include #include #include // STL #include // STB #define STB_SPRINTF_IMPLEMENTATION #include "stb_sprintf.h" #undef STB_SPRINTF_IMPLEMENTATION // POSIX #include struct debug_timer_t { const char *name; int64_t usec; }; extern debug_timer_t debug_timers[]; extern int ntimers; struct debug_clock_t { const char *name; int id; int64_t start; int64_t end; debug_clock_t(const char *name, int id) { this->name = name; this->id = id; struct timespec spec; clock_gettime(CLOCK_MONOTONIC, &spec); start = spec.tv_sec * 1000 * 1000 + spec.tv_nsec / 1000; } ~debug_clock_t() { struct timespec spec; clock_gettime(CLOCK_MONOTONIC, &spec); end = spec.tv_sec * 1000 * 1000 + spec.tv_nsec / 1000; debug_timers[id].name = this->name; debug_timers[id].usec = end - start; } }; #define TIME_BLOCK(name) debug_clock_t _name_##timer(#name, __COUNTER__); struct charglyph_t { int32_t tex; // index of character in tex array glm::ivec2 size; // 2d dimensions glm::ivec2 bearing; // offset from baseline to left/top of glyph uint32_t advance; // offset to next glyph }; struct hex_t { int32_t id; glm::vec3 position; float radius; glm::vec3 vertices[7]; glm::vec4 color; uint32_t vao; uint32_t vbo; uint32_t ebo; }; struct grid_t { int rows; int columns; hex_t *hexes; }; struct camera_t { glm::vec3 position; glm::vec2 size; }; struct selection_t { int32_t *indices; int32_t nselected; }; const int32_t ARRAY_LIMIT = 400; charglyph_t glyphmap[128]; uint32_t text_program; uint32_t textvao, textvbo; uint32_t hex_program; uint32_t texture_array; glm::mat4 transforms[ARRAY_LIMIT]; int32_t letter_map[ARRAY_LIMIT]; // error processing enum err_type { OK = 0, ERR_GLFW_INIT, ERR_GLFW_WINDOW_CREATION, ERR_GLAD_LOAD, ERR_FREETYPE_INIT, ERR_FONT_INIT, ERR_CHAR_LOAD, }; uint32_t main_errcode = 0; const char *errcode_string = ""; double mouse_scroll = 0.0; #define MAIN_ERROR(e) do { main_errcode = e; errcode_string = #e; goto exit_main_with_error; } while(0) bool is_selected(int idx, selection_t *sel) { for (int i = 0; i < sel->nselected; ++i) if (sel->indices[i] == idx) return true; return false; } void select(int idx, selection_t *sel) { if (!is_selected(idx, sel)) sel->indices[sel->nselected++] = idx; } void deselect(int idx, selection_t *sel) { for (int i = 0; i < sel->nselected; ++i) if (sel->indices[i] == idx) { sel->indices[i] = -1; --sel->nselected; } } void glfw_error_callback(int error, const char* description) { fprintf(stderr, "GLFW error %d: %s\n", error, description); } void scroll_callback([[maybe_unused]]GLFWwindow* window, [[maybe_unused]]double xoffset, double yoffset) { mouse_scroll = yoffset; } char* load_file(const char *pathname) { FILE *text_file = fopen(pathname, "r"); if (!text_file) { fprintf(stderr, "Failed trying to read file at %s", pathname); return NULL; } fseek(text_file, 0, SEEK_END); long file_size = ftell(text_file); fseek(text_file, 0, SEEK_SET); /* same as rewind(f); */ char *file_contents = (char*)calloc(sizeof(char), file_size + 1); fread(file_contents, 1, file_size, text_file); fclose(text_file); return file_contents; } uint32_t build_shader_from_source(const char *source, int32_t type) { char infoLog[512]; unsigned int shader = glCreateShader(type); glShaderSource(shader, 1, (const GLchar * const *)(&source), NULL); glCompileShader(shader); int success; glGetShaderiv(shader, GL_COMPILE_STATUS, &success); if (!success) { glGetShaderInfoLog(shader, 512, NULL, infoLog); const char *type_str = type == GL_VERTEX_SHADER ? "VERTEX" : "FRAGMENT"; fprintf(stderr, "Error: %s shader compilation failed: %s\n", type_str, infoLog); } // TODO: Maybe better error handling? return shader; } uint32_t make_gl_program(const char *pathname_vertex, const char *pathname_fragment) { char infoLog[512]; char *vertex_source = load_file(pathname_vertex); char *fragment_source = load_file(pathname_fragment); unsigned int vertex_shader = build_shader_from_source(vertex_source, GL_VERTEX_SHADER); unsigned int fragment_shader = build_shader_from_source(fragment_source, GL_FRAGMENT_SHADER); uint32_t program = glCreateProgram(); glAttachShader(program, vertex_shader); glAttachShader(program, fragment_shader); glLinkProgram(program); // print linking errors if any int32_t success; glGetProgramiv(program, GL_LINK_STATUS, &success); if (!success) { // TODO: Maybe better error handling? glGetProgramInfoLog(program, 512, NULL, infoLog); fprintf(stderr, "Error: shader linking failed: %s\n", infoLog); } else { // Cleanup of unneeded structures glDeleteShader(vertex_shader); glDeleteShader(fragment_shader); } return program; } void render_text(const char *text, float x, float y, float scale, glm::vec3 color) { // TODO: Group draw calls together instead of doing 1 draw call/character. Use an atlas with GL_TEXTURE_ARRAY // Take tips from https://www.youtube.com/watch?v=S0PyZKX4lyI scale *= 48.0f/256; // activate corresponding render state glUseProgram(text_program); glUniform3f(glGetUniformLocation(text_program, "text_color"), color.x, color.y, color.z); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D_ARRAY, texture_array); glBindVertexArray(textvao); glBindBuffer(GL_ARRAY_BUFFER, textvbo); int idx = 0; // iterate through 128 ASCI characters for (const char *p = text; *p != '\0'; ++p) { // Avoid drawing more than 400 chars at a time if (idx == ARRAY_LIMIT - 1) break; char c = *p; charglyph_t charglyph = glyphmap[(size_t)c]; if (c == ' ') // Just advance by normal horizontal distance and prepare for next char { x += (charglyph.advance >> 6) * scale; continue; } float xpos = x + charglyph.bearing.x * scale; float ypos = y - (256 - charglyph.bearing.y) * scale; transforms[idx] = glm::translate(glm::mat4(1.0f), glm::vec3(xpos, ypos, 0)) * glm::scale(glm::mat4(1.0f), glm::vec3(256 * scale, 256 * scale, 0)); letter_map[idx] = charglyph.tex; // now advance cursors for next glyph (note that advance is number of 1/64 pixels) x += (charglyph.advance >> 6) * scale; // bitshift by 6 to get value in pixels (2^6 = 64) ++idx; } glUniformMatrix4fv(glGetUniformLocation(text_program, "transforms"), idx, GL_FALSE, &transforms[0][0][0]); glUniform1iv(glGetUniformLocation(text_program, "letter_map"), idx, &letter_map[0]); glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, idx); glBindBuffer(GL_ARRAY_BUFFER, 0); glBindVertexArray(0); glBindTexture(GL_TEXTURE_2D, 0); } bool inside_hex(hex_t *hex, glm::vec2 point) { for (int i = 1; i < 7; ++i) { glm::vec2 outer_edge = glm::vec2(hex->vertices[(i % 6) +1].x, hex->vertices[(i % 6) + 1].y) - glm::vec2(hex->vertices[i].x, hex->vertices[i].y); glm::vec2 inner_vec = glm::vec2(point - glm::vec2(hex->vertices[i].x, hex->vertices[i].y)); float angle = glm::orientedAngle(glm::normalize(outer_edge), glm::normalize(inner_vec)); if (angle > 0.0f) return false; } return true; } void create_grid(grid_t *grid, int rows, int columns) { grid->rows = rows; grid->columns = columns; if (!grid->hexes) grid->hexes = (hex_t*)calloc(grid->rows * grid->columns, sizeof(hex_t)); else { hex_t *new_ptr = (hex_t*)realloc(grid->hexes, grid->rows * grid->columns * sizeof(hex_t)); if (new_ptr) grid->hexes = new_ptr; else { free(grid->hexes); grid->hexes = (hex_t*)calloc(grid->rows * grid->columns, sizeof(hex_t)); } } for (int i = 0; i < grid->rows; ++i) for (int j = 0; j < grid->columns; ++j) { hex_t *the_hexagon = &grid->hexes[i*grid->columns+j]; float radius = 0.2; the_hexagon->position = glm::vec3(0,0,0) + glm::vec3(j * radius * 3.0f * glm::cos(glm::radians(60.0)) , (i * radius * 2.0f * glm::sin(glm::radians(60.0))) + (j % 2 ? 0 : radius * glm::sin(glm::radians(60.0))), 0); the_hexagon->radius = radius; the_hexagon->id = i * grid->columns + j; the_hexagon->color = glm::vec4(1.0 - i * 1.0/rows, 1.0 - j * 1.0/columns, 1.0, 1.0); the_hexagon->vertices[0] = the_hexagon->position; the_hexagon->vertices[1] = the_hexagon->position + glm::vec3(-radius, 0, 0); the_hexagon->vertices[2] = the_hexagon->position + glm::vec3(-radius * glm::cos(glm::radians(60.0)), radius * glm::sin(glm::radians(60.0)), 0); the_hexagon->vertices[3] = the_hexagon->position + glm::vec3(radius * glm::cos(glm::radians(60.0)), radius * glm::sin(glm::radians(60.0)), 0); the_hexagon->vertices[4] = the_hexagon->position + glm::vec3(radius, 0, 0); the_hexagon->vertices[5] = the_hexagon->position + glm::vec3(radius * glm::cos(glm::radians(60.0)), -radius * glm::sin(glm::radians(60.0)), 0); the_hexagon->vertices[6] = the_hexagon->position + glm::vec3(-radius * glm::cos(glm::radians(60.0)), -radius * glm::sin(glm::radians(60.0)), 0); glGenVertexArrays(1, &the_hexagon->vao); glGenBuffers(1, &the_hexagon->vbo); glBindVertexArray(the_hexagon->vao); glBindBuffer(GL_ARRAY_BUFFER, the_hexagon->vbo); glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 7 * 3, the_hexagon->vertices, GL_STATIC_DRAW); // EBO setup unsigned int indices[18]; indices[0] = 0; indices[1] = 2; indices[2] = 1; indices[3] = 0; indices[4] = 3; indices[5] = 2; indices[6] = 0; indices[7] = 4; indices[8] = 3; indices[9] = 0; indices[10] = 5; indices[11] = 4; indices[12] = 0; indices[13] = 6; indices[14] = 5; indices[15] = 0; indices[16] = 1; indices[17] = 6; glGenBuffers(1, &the_hexagon->ebo); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, the_hexagon->ebo); glBufferData(GL_ELEMENT_ARRAY_BUFFER, 6*3 * sizeof(unsigned int), indices, GL_STATIC_DRAW); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (void*)0); glEnableVertexAttribArray(0); glBindBuffer(GL_ARRAY_BUFFER, 0); glBindVertexArray(0); } } // global state GLFWwindow *window = nullptr; hex_t hexes[4]; int main([[maybe_unused]]int argc, [[maybe_unused]]char **argv) { fprintf(stdout, "Hexnando\n"); // Window initialization glfwSetErrorCallback(glfw_error_callback); if (!glfwInit()) { MAIN_ERROR(ERR_GLFW_INIT); } window = glfwCreateWindow(640, 480, "Hexnando", NULL, NULL); if (!window) { MAIN_ERROR(ERR_GLFW_WINDOW_CREATION); } glfwMakeContextCurrent(window); glfwSwapInterval(1); glfwSetScrollCallback(window, scroll_callback); if (!gladLoadGL()) { MAIN_ERROR(ERR_GLAD_LOAD); } int gl_version_major, gl_version_minor; glGetIntegerv(GL_MAJOR_VERSION, &gl_version_major); glGetIntegerv(GL_MINOR_VERSION, &gl_version_minor); fprintf(stdout, "Using OpenGL %d.%d\n", gl_version_major, gl_version_minor); // font library initialization FT_Library ft; if (FT_Init_FreeType(&ft)) { MAIN_ERROR(ERR_FREETYPE_INIT); } FT_Face face; if (FT_New_Face(ft, "fonts/Inconsolata-Regular.ttf", 0, &face)) { MAIN_ERROR(ERR_FONT_INIT); } FT_Set_Pixel_Sizes(face, 256, 256); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); // disable byte-alignment restriction glGenTextures(1, &texture_array); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D_ARRAY, texture_array); glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_R8, 256, 256, 128, 0, GL_RED, GL_UNSIGNED_BYTE, 0); for (unsigned char c = 0; c < 128; c++) { // load character glyph if (FT_Load_Char(face, c, FT_LOAD_RENDER)) { MAIN_ERROR(ERR_CHAR_LOAD); } glTexSubImage3D(GL_TEXTURE_2D_ARRAY,0, 0, 0, int(c), face->glyph->bitmap.width, face->glyph->bitmap.rows, 1, GL_RED, GL_UNSIGNED_BYTE, face->glyph->bitmap.buffer); // set texture options glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR); // now store character for later use charglyph_t charglyph = { int(c), glm::ivec2(face->glyph->bitmap.width, face->glyph->bitmap.rows), glm::ivec2(face->glyph->bitmap_left, face->glyph->bitmap_top), (uint32_t)face->glyph->advance.x }; glyphmap[c] = charglyph; } // clear ft2 resources FT_Done_Face(face); FT_Done_FreeType(ft); // text initialization text_program = make_gl_program("shaders/text.vert", "shaders/text.frag"); for (int i = 0; i < ARRAY_LIMIT; ++i) { letter_map[i] = 0; transforms[i] = glm::mat4(1.0f); } static GLfloat vertex_data[] = { 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, }; glGenVertexArrays(1, &textvao); glGenBuffers(1, &textvbo); glBindVertexArray(textvao); glBindBuffer(GL_ARRAY_BUFFER, textvbo); glBufferData(GL_ARRAY_BUFFER, sizeof(vertex_data), vertex_data, GL_STATIC_DRAW); glEnableVertexAttribArray(0); glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, 0); glBindBuffer(GL_ARRAY_BUFFER, 0); glBindVertexArray(0); static grid_t grid = {}; create_grid(&grid, 10, 10); hex_program = make_gl_program("shaders/hex.vert", "shaders/hex.frag"); /* IMGui initalization */ IMGUI_CHECKVERSION(); ImGui::CreateContext(); static ImGuiIO& io = ImGui::GetIO(); (void)io; io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; ImGui::StyleColorsDark(); ImGui_ImplGlfw_InitForOpenGL(window, true); ImGui_ImplOpenGL3_Init("#version 330 core"); // same as shader version double cursor_x; double cursor_y; camera_t the_camera; bool mouse_pressed; bool mouse_over_gui; static bool show_hex_numbers = true; static selection_t selection; selection.indices = (int32_t*)calloc(1, sizeof(int32_t) * grid.rows * grid.columns); selection.nselected = 0; the_camera.position = glm::vec3(0.0f, 0.0f, 1.0f); the_camera.size = glm::vec2(1.0f, 1.0f); // main loop while (!glfwWindowShouldClose(window)) { TIME_BLOCK(full_frame); double cursor_dx = 0.0; double cursor_dy = 0.0; int hovered_hex = -1; int32_t window_width, window_height; { TIME_BLOCK(window_size_polling); glfwGetWindowSize(window, &window_width, &window_height); glViewport(0, 0, window_width, window_height); } float aspect_ratio = (float)window_width/window_height; { TIME_BLOCK(input_handling); /* Input handling */ if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS) the_camera.position.y += 0.1; if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS) the_camera.position.y -= 0.1; if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS) the_camera.position.x -= 0.1; if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS) the_camera.position.x += 0.1; if (glfwGetKey(window, GLFW_KEY_R) == GLFW_PRESS) the_camera.size -= glm::vec2(0.1); if (glfwGetKey(window, GLFW_KEY_F) == GLFW_PRESS) the_camera.size += glm::vec2(0.1); if (mouse_scroll > 0.0) { the_camera.size -= glm::vec2(0.1); mouse_scroll = 0.0; } if (mouse_scroll < 0.0) { the_camera.size += glm::vec2(0.1); mouse_scroll = 0.0; } mouse_pressed = glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_LEFT) == GLFW_PRESS; mouse_over_gui = ImGui::IsWindowHovered(ImGuiHoveredFlags_AnyWindow | ImGuiHoveredFlags_AllowWhenBlockedByActiveItem); cursor_dx = cursor_x; cursor_dy = cursor_y; glfwGetCursorPos(window, &cursor_x, &cursor_y); cursor_dx -= cursor_x; cursor_dy -= cursor_y; // Mouse movement if (!mouse_over_gui && glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_MIDDLE) == GLFW_PRESS) { fprintf(stdout, "Moving camera by %f %f\n", cursor_dx, cursor_dx); // mouse delta! the_camera.position.x += cursor_dx * the_camera.size.x/window_width; // The Y coord is inverted the_camera.position.y -= cursor_dy * the_camera.size.y/window_height; } if (the_camera.size.x < 0.1) the_camera.size.x = 0.1; if (the_camera.size.y < 0.1) the_camera.size.y = 0.1; } glm::mat4 model_matrix; glm::mat4 view_matrix; glm::mat4 proj_matrix; glm::vec2 cursor_world; /* Rendering */ { TIME_BLOCK(hex_render); glClearColor(0.1f, 0.2f, 0.3f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); model_matrix = glm::mat4(1.0f); view_matrix = glm::mat4(1.0f); proj_matrix = glm::ortho((the_camera.position.x - the_camera.size.x/2) * aspect_ratio, (the_camera.position.x + the_camera.size.x/2) * aspect_ratio, the_camera.position.y - the_camera.size.y/2, the_camera.position.y + the_camera.size.y/2); glm::vec4 aux((float)cursor_x, (float)cursor_y, 0.0f, 1.0f); // move screen coordinates to NDC aux.x -= window_width/2.0f; aux.x /= window_width/2.0f; aux.y -= window_height/2.0f; aux.y /= -window_height/2.0f; aux = glm::inverse(proj_matrix) * aux; cursor_world = glm::vec2(aux.x, aux.y); glUseProgram(hex_program); glUniformMatrix4fv(glGetUniformLocation(hex_program, "model"), 1, GL_FALSE, &model_matrix[0][0]); glUniformMatrix4fv(glGetUniformLocation(hex_program, "view"), 1, GL_FALSE, &view_matrix[0][0]); glUniformMatrix4fv(glGetUniformLocation(hex_program, "proj"), 1, GL_FALSE, &proj_matrix[0][0]); for (int i = 0; i < grid.rows; ++i) for (int j = 0; j < grid.columns; j++) { int idx = i * grid.columns + j; if (!mouse_over_gui && inside_hex(&grid.hexes[idx], cursor_world)) // draw hovered hex differently { hovered_hex = i * grid.columns + j; if (mouse_pressed) { select(idx, &selection); } else glUniform4f(glGetUniformLocation(hex_program, "hex_color"), 1.0f, 0.0f, 0.0f, 1.0f); } else { if (is_selected(idx, &selection)) { glUniform4f(glGetUniformLocation(hex_program, "hex_color"), 1.0f, 1.0f, 0.0f, 1.0f); } else glUniform4f(glGetUniformLocation(hex_program, "hex_color"), grid.hexes[idx].color.x, grid.hexes[idx].color.y, grid.hexes[idx].color.z, grid.hexes[idx].color.w); } glBindVertexArray(grid.hexes[idx].vao); glBindBuffer(GL_ARRAY_BUFFER, grid.hexes[idx].vbo); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, grid.hexes[idx].ebo); glEnable(GL_CULL_FACE); glDrawElements(GL_TRIANGLES, 3*6, GL_UNSIGNED_INT, 0); glDisable(GL_CULL_FACE); glBindVertexArray(0); } glUseProgram(text_program); // text rendering glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); } glm::mat4 text_proj_matrix = glm::ortho(0.0f, (float)window_width, 0.0f, (float)window_height); glUniformMatrix4fv(glGetUniformLocation(text_program, "projection"), 1, GL_FALSE, &text_proj_matrix[0][0]); static char debug_text_buf[256]; { TIME_BLOCK(text_render); stbsp_snprintf(debug_text_buf, 256, "Cursor position %.0f %.0f screen %.2f %.2f world", cursor_x, cursor_y, cursor_world.x, cursor_world.y); render_text(debug_text_buf, 25, 25, .5f, glm::vec3(1.0f, 1.0f, 1.0f)); for (int i = 0; show_hex_numbers && i < grid.rows * grid.columns; ++i) { // Get position of hexes in screen glm::vec4 v = glm::vec4(grid.hexes[i].position.x, grid.hexes[i].position.y, grid.hexes[i].position.z, 1.0); v = proj_matrix * view_matrix * model_matrix * v; v.x += 1; v.x *= window_width/2.0f; v.y += 1; v.y *= window_height/2.0f; // Do not draw text outside visible window if (v.x > window_width || v.x < 0 || v.y > window_height || v.y < 0) continue; char number_string[11]; stbsp_snprintf(number_string, 11, "%d", i); render_text(number_string, v.x, v.y, .2f + (0.3f / the_camera.size.x), glm::vec3(0.0f, 0.0f, 0.0f)); } if (hovered_hex != -1) { char buf[30]; stbsp_snprintf(buf, 30, "Cursor INSIDE hex %d", hovered_hex); render_text(buf, 25.0f, window_height - 25.0f, .5f, glm::vec3(1.0f, 1.0f, 1.0f)); } } glDisable(GL_BLEND); glfwPollEvents(); static int grid_rows = grid.rows; static int grid_columns = grid.columns; if (grid_rows != grid.rows || grid_columns != grid.columns) { create_grid(&grid, grid_rows, grid_columns); } // Start the Dear ImGui frame { TIME_BLOCK(gui_render); ImGui_ImplOpenGL3_NewFrame(); ImGui_ImplGlfw_NewFrame(); ImGui::NewFrame(); ImGui::Begin("Hi there"); ImGui::SliderInt("Rows", &grid_rows, 1, 100); ImGui::SliderInt("Columns", &grid_columns, 1, 100); ImGui::Checkbox("Show hex numbers", &show_hex_numbers); if (ImGui::CollapsingHeader("Debug timers")) { for (int i = 0; i < ntimers; ++i) { ImGui::Text("%s: %d nsecs", debug_timers[i].name, debug_timers[i].usec); } } ImGui::End(); ImGui::Render(); ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); } glfwSwapBuffers(window); } // Cleanup ImGui_ImplOpenGL3_Shutdown(); ImGui_ImplGlfw_Shutdown(); ImGui::DestroyContext(); return 0; exit_main_with_error: if (window) glfwDestroyWindow(window); glfwTerminate(); fprintf(stderr, "Hexnando failed with errcode %d: %s\n", main_errcode, errcode_string); return main_errcode; } #define NTIMERS __COUNTER__ int ntimers = NTIMERS; debug_timer_t debug_timers[NTIMERS];