From ea9a94dc08fb2cd35579fd866c299028ca744d77 Mon Sep 17 00:00:00 2001 From: Phireh Date: Mon, 12 May 2025 00:57:25 +0200 Subject: [PATCH] Add PNG loading support --- Makefile | 4 +- main.cpp | 172 +++++++++++++++++++++++++++++++++++++++++++++++------- notes.org | 4 ++ 3 files changed, 158 insertions(+), 22 deletions(-) create mode 100644 notes.org diff --git a/Makefile b/Makefile index b0d5145..37dd724 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,8 @@ -LINK_FLAGS=-lm -lpthread -ldl -lm -lGLEW -lEGL -lGL -lGLU -lOpenGL -lglfw -lfreetype +LINK_FLAGS=-lm -lpthread -ldl -lm -lGLEW -lEGL -lGL -lGLU -lOpenGL -lglfw -lfreetype -lpng16 -lz CFLAGS=-I./include -I/usr/include/freetype2 -I/usr/include/libpng16 -I/usr/include/harfbuzz -I/usr/include/glib-2.0 -I/usr/lib/glib-2.0/include -I/usr/include/sysprof-4 -Iimgui -pthread -fno-exceptions .PHONY: hexnando hexnando: main.cpp - g++ -std=c++20 -Wall -g -Wextra -pedantic -Werror -Og main.cpp ${LINK_FLAGS} ${CFLAGS} -o hexnando + g++ -std=c++20 -Wall -g -Wextra -pedantic -Werror -Wno-calloc-transposed-args -Og main.cpp ${LINK_FLAGS} ${CFLAGS} -o hexnando diff --git a/main.cpp b/main.cpp index 615f4ab..48e8450 100644 --- a/main.cpp +++ b/main.cpp @@ -36,6 +36,9 @@ #include "stb_sprintf.h" #undef STB_SPRINTF_IMPLEMENTATION +// libpng +#include + // POSIX #include @@ -110,6 +113,12 @@ struct selection_t { int32_t nselected; }; +struct texture_t { + uint32_t gl_id; // OpenGL GLuint ID + uint32_t w; + uint32_t h; +}; + const int32_t ARRAY_LIMIT = 400; charglyph_t glyphmap[128]; @@ -121,6 +130,7 @@ glm::mat4 transforms[ARRAY_LIMIT]; int32_t letter_map[ARRAY_LIMIT]; int32_t text_frame_idx; +texture_t pngtex = {}; // error processing enum err_type { @@ -172,6 +182,65 @@ void scroll_callback([[maybe_unused]]GLFWwindow* window, [[maybe_unused]]double mouse_scroll = yoffset; } +int path_callback([[maybe_unused]]ImGuiInputTextCallbackData *data) +{ + fprintf(stderr, "Path callback\n"); + return 0; +} + +int read_png(const char *path) +{ + + FILE *fd = fopen(path, "rb"); + if (!fd) return -1; + + png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (!png_ptr) + return 4; + + png_infop info_ptr = png_create_info_struct(png_ptr); + if (!info_ptr) + return 4; + + if (setjmp(png_jmpbuf(png_ptr))) + { + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + fclose(fd); + fprintf(stderr, "Error routine\n"); + return -1; + } + + png_init_io(png_ptr, fd); + png_set_crc_action(png_ptr, PNG_CRC_WARN_USE, PNG_CRC_WARN_USE); + png_set_keep_unknown_chunks(png_ptr, PNG_HANDLE_CHUNK_NEVER, NULL, 0); + // TODO: Use low-level interface to read textures in inverted order as an optimization + png_read_png(png_ptr, info_ptr, PNG_TRANSFORM_STRIP_16 | PNG_TRANSFORM_PACKING | PNG_TRANSFORM_EXPAND, NULL); + + fprintf(stdout, "File %s read correctly\n", path); + + uint32_t row_bytes = png_get_rowbytes(png_ptr, info_ptr); + uint32_t width = png_get_image_width(png_ptr, info_ptr); + uint32_t height = png_get_image_height(png_ptr, info_ptr); + fprintf(stdout, "\tWidth: %d px; Height: %d px; Rowbytes: %d\n", width, height, row_bytes); + + uint8_t **row_pointers = png_get_rows(png_ptr, info_ptr); + uint8_t *data = (uint8_t*) calloc(1, row_bytes * height); + for (uint32_t i = 0; i < height; ++i) + memcpy(data + i * row_bytes, row_pointers[height - i - 1], row_bytes); + + GLuint tex_id; + glGenTextures(1, &tex_id); + glBindTexture(GL_TEXTURE_2D, tex_id); + GLenum tex_format = GL_RGBA; + glTexImage2D(GL_TEXTURE_2D, 0, tex_format, width, height, 0, tex_format, GL_UNSIGNED_BYTE, data); + glGenerateMipmap(GL_TEXTURE_2D); + pngtex.gl_id = tex_id; + pngtex.w = width; + pngtex.h = height; + + return 0; +} + char* load_file(const char *pathname) { FILE *text_file = fopen(pathname, "r"); @@ -445,6 +514,9 @@ int main([[maybe_unused]]int argc, [[maybe_unused]]char **argv) glGetIntegerv(GL_MINOR_VERSION, &gl_version_minor); fprintf(stdout, "Using OpenGL %d.%d\n", gl_version_major, gl_version_minor); + fprintf(stdout, "Compiled using libPNG version %s\n", PNG_LIBPNG_VER_STRING); + fprintf(stdout, "Runtime using libPNG version %s\n", png_libpng_ver); + // font library initialization FT_Library ft; @@ -543,8 +615,11 @@ int main([[maybe_unused]]int argc, [[maybe_unused]]char **argv) double cursor_x; double cursor_y; + double cursor_x_scaled; + double cursor_y_scaled; camera_t the_camera; bool mouse_pressed; + bool right_mouse_pressed; bool mouse_over_gui; static bool show_hex_numbers = true; static selection_t selection; @@ -561,12 +636,19 @@ int main([[maybe_unused]]int argc, [[maybe_unused]]char **argv) double cursor_dy = 0.0; int hovered_hex = -1; int32_t window_width, window_height; + int lf, tf, rf, bf; + float content_scale_x, content_scale_y; + int32_t scaled_window_width, scaled_window_height; { TIME_BLOCK(window_size_polling); glfwGetWindowSize(window, &window_width, &window_height); - glViewport(0, 0, window_width, window_height); + glfwGetWindowFrameSize(window, &lf, &tf, &rf, &bf); + glfwGetWindowContentScale(window, &content_scale_x, &content_scale_y); + scaled_window_width = (int)(window_width * content_scale_x); + scaled_window_height = (int)(window_height * content_scale_y); + glViewport(0, 0, scaled_window_width, scaled_window_height); } - float aspect_ratio = (float)window_width/window_height; + float aspect_ratio = (float)scaled_window_width/scaled_window_height; { TIME_BLOCK(input_handling); @@ -595,24 +677,27 @@ int main([[maybe_unused]]int argc, [[maybe_unused]]char **argv) } mouse_pressed = glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_LEFT) == GLFW_PRESS; + right_mouse_pressed = glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_RIGHT) == GLFW_PRESS; mouse_over_gui = ImGui::IsWindowHovered(ImGuiHoveredFlags_AnyWindow | ImGuiHoveredFlags_AllowWhenBlockedByActiveItem); - cursor_dx = cursor_x; - cursor_dy = cursor_y; + cursor_dx = cursor_x * content_scale_x; + cursor_dy = cursor_y * content_scale_y; glfwGetCursorPos(window, &cursor_x, &cursor_y); + cursor_x_scaled = cursor_x * content_scale_x; + cursor_y_scaled = cursor_y * content_scale_y; - cursor_dx -= cursor_x; - cursor_dy -= cursor_y; + cursor_dx -= cursor_x_scaled; + cursor_dy -= cursor_y_scaled; // 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_camera.position.x += cursor_dx * the_camera.size.x/scaled_window_width; // The Y coord is inverted - the_camera.position.y -= cursor_dy * the_camera.size.y/window_height; + the_camera.position.y -= cursor_dy * the_camera.size.y/scaled_window_height; } if (the_camera.size.x < 0.1) @@ -636,13 +721,13 @@ int main([[maybe_unused]]int argc, [[maybe_unused]]char **argv) 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); + glm::vec4 aux((float)cursor_x_scaled, (float)cursor_y_scaled, 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.x -= scaled_window_width/2.0f; + aux.x /= scaled_window_width/2.0f; + aux.y -= scaled_window_height/2.0f; + aux.y /= -scaled_window_height/2.0f; aux = glm::inverse(proj_matrix) * aux; cursor_world = glm::vec2(aux.x, aux.y); @@ -662,15 +747,21 @@ int main([[maybe_unused]]int argc, [[maybe_unused]]char **argv) { select(idx, &selection); } + else if (right_mouse_pressed) + { + deselect(idx, &selection); + } + if (!is_selected(idx, &selection)) + glUniform4f(glGetUniformLocation(hex_program, "hex_color"), 1.0f, 0.0f, 0.0f, 1.0f); // red else - glUniform4f(glGetUniformLocation(hex_program, "hex_color"), 1.0f, 0.0f, 0.0f, 1.0f); + glUniform4f(glGetUniformLocation(hex_program, "hex_color"), 1.0f, 1.0f, 0.0f, 1.0f); // yellow } else { if (is_selected(idx, &selection)) { - glUniform4f(glGetUniformLocation(hex_program, "hex_color"), 1.0f, 1.0f, 0.0f, 1.0f); + glUniform4f(glGetUniformLocation(hex_program, "hex_color"), 1.0f, 1.0f, 0.0f, 1.0f); // yellow } 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); @@ -691,7 +782,7 @@ int main([[maybe_unused]]int argc, [[maybe_unused]]char **argv) 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); + glm::mat4 text_proj_matrix = glm::ortho(0.0f, (float)scaled_window_width, 0.0f, (float)scaled_window_height); glUniformMatrix4fv(glGetUniformLocation(text_program, "projection"), 1, GL_FALSE, &text_proj_matrix[0][0]); static char debug_text_buf[256]; @@ -704,7 +795,7 @@ int main([[maybe_unused]]int argc, [[maybe_unused]]char **argv) glBindVertexArray(textvao); glBindBuffer(GL_ARRAY_BUFFER, textvbo); - stbsp_snprintf(debug_text_buf, 256, "Cursor position %.0f %.0f screen %.2f %.2f world", cursor_x, cursor_y, cursor_world.x, cursor_world.y); + stbsp_snprintf(debug_text_buf, 256, "Cursor position %.0f %.0f screen %.2f %.2f world", cursor_x_scaled, cursor_y_scaled, cursor_world.x, cursor_world.y); render_text(debug_text_buf, 25, 25, .5f, glm::vec3(1.0f, 1.0f, 1.0f), true); glm::mat4 camera_transform = proj_matrix * view_matrix * model_matrix; @@ -714,11 +805,11 @@ int main([[maybe_unused]]int argc, [[maybe_unused]]char **argv) // Get position of hexes in screen glm::vec4 v = camera_transform * glm::vec4(grid.hexes[i].position.x, grid.hexes[i].position.y, grid.hexes[i].position.z, 1.0); v.x += 1; - v.x *= window_width/2.0f; + v.x *= scaled_window_width/2.0f; v.y += 1; - v.y *= window_height/2.0f; + v.y *= scaled_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) + if (v.x > scaled_window_width || v.x < 0 || v.y > scaled_window_height || v.y < 0) continue; render_text(&hex_labels[i*label_size], v.x, v.y, .2f + (0.3f / the_camera.size.x), glm::vec3(0.0f, 0.0f, 0.0f), false); } @@ -740,6 +831,7 @@ int main([[maybe_unused]]int argc, [[maybe_unused]]char **argv) static int grid_rows = grid.rows; static int grid_columns = grid.columns; + static char image_path[PATH_MAX]; if (grid_rows != grid.rows || grid_columns != grid.columns) { @@ -756,6 +848,25 @@ int main([[maybe_unused]]int argc, [[maybe_unused]]char **argv) 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("Image")) + { + if (ImGui::InputText("Input image", image_path, PATH_MAX, ImGuiInputTextFlags_EnterReturnsTrue, path_callback)) + { + // TODO: Remove testing code + fprintf(stdout, "Trying to read file %s\n", image_path); + if (read_png(image_path)) + { + fprintf(stdout, "Could not read file %s\n", image_path); + pngtex = {}; + } + } + if (pngtex.gl_id) + { + glBindTexture(GL_TEXTURE_2D, pngtex.gl_id); + if (ImGui::CollapsingHeader("Preview")) + ImGui::Image((void*)pngtex.gl_id, ImVec2(pngtex.w, pngtex.h), {0,1}, {1,0}); + } + } if (ImGui::CollapsingHeader("Debug timers")) { for (int i = 0; i < ntimers; ++i) @@ -763,6 +874,27 @@ int main([[maybe_unused]]int argc, [[maybe_unused]]char **argv) ImGui::Text("%s: %d nsecs", debug_timers[i].name, debug_timers[i].usec); } } + if (ImGui::CollapsingHeader("Debug vars")) + { + ImGui::Text("Window width %d", window_width); + ImGui::Text("Window height %d", window_height); + ImGui::Text("Window borders: left %d top %d right %d bottom %d", lf, tf, rf, bf); + ImGui::Text("Window content scale: %f X %f Y", content_scale_x, content_scale_y); + ImGui::Text("True (scaled) width %d", scaled_window_width); + ImGui::Text("True (scaled) height %d", scaled_window_height); + } + if (ImGui::CollapsingHeader("Debug selection")) + { + ImGui::Text("Selected %d indices", selection.nselected); + std::string aux; + for (int i = 0; i < selection.nselected - 1; ++i) + aux += std::to_string(selection.indices[i]) + ","; + + if (selection.nselected) + aux += std::to_string(selection.indices[selection.nselected - 1]); + + ImGui::Text(aux.c_str()); + } ImGui::End(); ImGui::Render(); ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); diff --git a/notes.org b/notes.org new file mode 100644 index 0000000..7a55936 --- /dev/null +++ b/notes.org @@ -0,0 +1,4 @@ +- Y axis going upwards, just like OGL +- Text rendering optimized yet still taking too much time. Using atlas with GL_TEXTURE_ARRAY +- All vertex data lives in the CPU for each hex too +- Need UI to modify hex appeareance (show texture from file and then apply it to hex)