From af6967151e96390dfb4864d0628c7de623ff9be5 Mon Sep 17 00:00:00 2001 From: Corentin Date: Mon, 12 Jul 2021 17:30:44 +0900 Subject: [PATCH] Working FontManager and Label implementation (WIP) --- .ccls | 9 + include/graphic_context.hpp | 5 +- include/uui/opengl/application.hpp | 14 +- include/uui/opengl/component.hpp | 14 +- include/uui/opengl/font_manager.hpp | 68 ++++++ include/uui/opengl/label.hpp | 33 +++ make.py | 4 +- src/test/example_gl.cpp | 29 +-- src/uui/opengl/application.cpp | 34 +-- src/uui/opengl/component.cpp | 18 +- src/uui/opengl/font_manager.cpp | 308 ++++++++++++++++++++++++++++ src/uui/opengl/label.cpp | 35 ++++ src/uui/opengl/shaders/text.frag | 12 ++ src/uui/opengl/shaders/text.vert | 12 ++ src/uui/opengl/window.cpp | 74 +++++-- 15 files changed, 608 insertions(+), 61 deletions(-) create mode 100644 .ccls create mode 100644 include/uui/opengl/font_manager.hpp create mode 100644 include/uui/opengl/label.hpp create mode 100644 src/uui/opengl/font_manager.cpp create mode 100644 src/uui/opengl/label.cpp create mode 100644 src/uui/opengl/shaders/text.frag create mode 100644 src/uui/opengl/shaders/text.vert diff --git a/.ccls b/.ccls new file mode 100644 index 0000000..68cd814 --- /dev/null +++ b/.ccls @@ -0,0 +1,9 @@ +clang++ +-std=c++17 +-DDEBUG +-Iinclude +-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 diff --git a/include/graphic_context.hpp b/include/graphic_context.hpp index 989ede2..800a70d 100644 --- a/include/graphic_context.hpp +++ b/include/graphic_context.hpp @@ -7,6 +7,7 @@ #define GLM_FORCE_RADIANS #define GLM_FORCE_DEPTH_ZERO_TO_ONE -#include -#include +#include +#include +#include // clang-format off diff --git a/include/uui/opengl/application.hpp b/include/uui/opengl/application.hpp index 32d6c28..31fa0da 100644 --- a/include/uui/opengl/application.hpp +++ b/include/uui/opengl/application.hpp @@ -6,6 +6,7 @@ #include #include "graphic_context.hpp" +#include "uui/opengl/font_manager.hpp" namespace uui { @@ -33,11 +34,13 @@ private: static GlApplication* application_pointer; }; +class GlLabel; class GlComponent; class GlWindow { friend class GlApplication; friend class GlComponent; + friend class GlLabel; public: std::string title; @@ -50,14 +53,21 @@ public: std::shared_ptr create_component( const std::variant x, const std::variant y, const std::variant width, const std::variant height); + std::shared_ptr create_label( + const std::variant x, const std::variant y, const std::string& text, + const std::tuple& text_color); -private: +protected: GlApplication* app; + GlFontManager font_manager; + int width; int height; bool need_resize_refresh; + bool iconified; + size_t index; bool initialized; @@ -72,6 +82,8 @@ private: void draw(); static void glfw_resize_callback(GLFWwindow* window, int width, int height); + static void glfw_iconify_callback(GLFWwindow* window, int iconified); + static void glfw_key_callback(GLFWwindow* window, int key, int scancode, int action, int mods); }; } // namespace uui diff --git a/include/uui/opengl/component.hpp b/include/uui/opengl/component.hpp index c7b05b2..1f44a39 100644 --- a/include/uui/opengl/component.hpp +++ b/include/uui/opengl/component.hpp @@ -1,7 +1,7 @@ #pragma once #include -// #include +#include #include #include "graphic_context.hpp" @@ -20,7 +20,7 @@ public: void set_background_color(float r, float g, float b, float a = 0.0f); -private: +protected: std::shared_ptr window; std::variant position_x; @@ -41,11 +41,11 @@ private: std::shared_ptr window, const std::variant x, const std::variant y, const std::variant width, const std::variant height); - void init(); - void set_vbo_data(); - void deinit(); - void render(); - void on_resize(); + virtual void init(); + virtual void set_vbo_data(); + virtual void deinit(); + virtual void render(); + virtual void on_resize(); }; } // namespace uui diff --git a/include/uui/opengl/font_manager.hpp b/include/uui/opengl/font_manager.hpp new file mode 100644 index 0000000..3e1d7d3 --- /dev/null +++ b/include/uui/opengl/font_manager.hpp @@ -0,0 +1,68 @@ +#pragma once + +#include +#include +#include +#include + +#include +#include FT_FREETYPE_H +#include "graphic_context.hpp" + +namespace uui +{ +class GlWindow; +class GlFontManager +{ + friend class GlWindow; + +public: + struct character_info + { + float advance_x; // advance.x + float advance_y; // advance.y + + float bitmap_width; // bitmap.width; + float bitmap_height; // bitmap.rows; + + float bitmap_left; // bitmap_left; + float bitmap_top; // bitmap_top; + + float texture_x; // x offset of glyph in texture coordinates + float texture_y; // y offset of glyph in texture coordinates + }; + + struct Font + { + character_info char_infos[256]; + GLuint tex; + unsigned int atlas_width; + unsigned int atlas_height; + }; + + std::vector fonts; + + GlFontManager(); + ~GlFontManager(); + + void render_text( + const std::string& text, Font& font, float x, float y, float scale_x, float scale_y, + const std::tuple& text_color); + std::tuple get_text_size(const std::string& text, Font& font, float scale_x, float scale_y); + +private: + static constexpr int MAX_WIDTH = 1024; + + bool initialized; + + FT_Library ft; + + GLuint gl_program; + GLuint gl_vao; + GLuint gl_vbo; + + void init(); + void deinit(); + const Font& init_font(const std::string& font_path, size_t font_size); +}; +} // namespace uui diff --git a/include/uui/opengl/label.hpp b/include/uui/opengl/label.hpp new file mode 100644 index 0000000..e589d26 --- /dev/null +++ b/include/uui/opengl/label.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include +#include + +#include "uui/opengl/component.hpp" + +namespace uui +{ +class GlLabel: public GlComponent +{ + friend class GlWindow; + + size_t font_index; + + float get_width() const; + float get_height() const; + +protected: + std::string text; + const std::tuple& text_color; + + int text_width; + int text_height; + + GlLabel( + std::shared_ptr window, const std::variant x, const std::variant y, const std::string& text, + const std::tuple& text_color); + + void render(); +}; + +} // namespace uui diff --git a/make.py b/make.py index 63adb60..8b6748e 100644 --- a/make.py +++ b/make.py @@ -21,8 +21,8 @@ class Config: COMMON_FLAGS = '-std=c++17 -fno-semantic-interposition' COMMON_DEBUG_FLAGS = '-g -DDEBUG' COMMON_RELEASE_FLAGS = '-O2' # -flto' - COMPILE_FLAGS = f'-Wall -I{INCLUDE_DIR} `pkg-config --cflags glfw3 vulkan gl x11`' - LINK_FLAGS = '-lpthread -ldl `pkg-config --libs glfw3 vulkan gl x11`' + COMPILE_FLAGS = f'-Wall -I{INCLUDE_DIR} `pkg-config --cflags glfw3 vulkan gl x11 freetype2`' + LINK_FLAGS = '-lpthread -ldl `pkg-config --libs glfw3 vulkan gl x11 freetype2`' PRE_COMPILE_FUNCTION = None diff --git a/src/test/example_gl.cpp b/src/test/example_gl.cpp index 4b3c9d5..708f533 100644 --- a/src/test/example_gl.cpp +++ b/src/test/example_gl.cpp @@ -2,6 +2,7 @@ #include "uui/opengl/application.hpp" #include "uui/opengl/component.hpp" +#include "uui/opengl/label.hpp" using namespace std; @@ -16,21 +17,23 @@ int main() auto window = app.create_window(800, 600, "OpenGl!"); auto comp = window->create_component(0, 100, 0.5f, 50); comp->set_background_color(0.8f, 0.1f, 0.5f); + auto label = window->create_label(0.5f, 0.5f, "Hello World!", {1.0f, 1.0f, 1.0f, 0.5f}); + label->set_background_color(0.1f, 0.5f, 0.1f); app.run(); } - cout << "\nStarting example 2" << endl; - { - uui::GlApplication app; - auto window_1 = app.create_window(800, 600, "OpenGl!"); - auto comp = window_1->create_component(0.25f, 0.25f, 0.5f, 0.5f); - comp->set_background_color(0.8f, 0.1f, 0.5f); - comp = window_1->create_component(0.75f, 0.75f, 0.25f, 0.25f); - comp->set_background_color(0.2f, 0.1f, 0.5f); - auto window_2 = app.create_window(600, 600, "OpenGL 2!"); - comp = window_2->create_component(0, 0.5f, 100, 0.5f); - comp->set_background_color(0.2f, 0.5f, 0.3f); - app.run(); - } + // cout << "\nStarting example 2" << endl; + // { + // uui::GlApplication app; + // auto window_1 = app.create_window(800, 600, "OpenGl!"); + // auto comp = window_1->create_component(0.25f, 0.25f, 0.5f, 0.5f); + // comp->set_background_color(0.8f, 0.1f, 0.5f); + // comp = window_1->create_component(0.75f, 0.75f, 0.25f, 0.25f); + // comp->set_background_color(0.2f, 0.1f, 0.5f); + // auto window_2 = app.create_window(600, 600, "OpenGL 2!"); + // comp = window_2->create_component(0, 0.5f, 100, 0.5f); + // comp->set_background_color(0.2f, 0.5f, 0.3f); + // app.run(); + // } } catch(const std::exception& error) { diff --git a/src/uui/opengl/application.cpp b/src/uui/opengl/application.cpp index 1bb18a9..8c3d07f 100644 --- a/src/uui/opengl/application.cpp +++ b/src/uui/opengl/application.cpp @@ -3,7 +3,7 @@ #include #include -uui::GlApplication* uui::GlApplication::application_pointer = nullptr; +uui::GlApplication* uui::GlApplication::application_pointer = nullptr; uui::GlApplication::GlApplication() { @@ -28,7 +28,8 @@ uui::GlApplication::GlApplication() uui::GlApplication::~GlApplication() { - std::cout << "Destroying the GlApplication" << std::endl; + if constexpr(debug_mode) + std::cout << "Destroying the GlApplication" << std::endl; windows.clear(); glfwTerminate(); application_pointer = nullptr; @@ -36,23 +37,28 @@ uui::GlApplication::~GlApplication() void uui::GlApplication::run() { - std::cout << "Starting the GlApplication" << std::endl; + if constexpr(debug_mode) + std::cout << "Starting the GlApplication" << std::endl; auto start_time = std::chrono::high_resolution_clock::now(); auto end_time = start_time; float render_duration; float frame_count = 0.0f; - std::cout << "\n"; + if constexpr(debug_mode) + std::cout << "\n"; while(!windows.empty()) { - end_time = std::chrono::high_resolution_clock::now(); - render_duration = std::chrono::duration_cast(end_time - start_time).count(); - // std::cout << "Frame " << frame_count << ", duration: " << render_duration << std::endl; - if(frame_count > 0.0f && render_duration > 500.0f) + if constexpr(debug_mode) { - std::cout << "\rFPS : " << (frame_count * 1000.0f) / render_duration << " " << std::flush; - frame_count = 0.0f; - start_time = end_time; + end_time = std::chrono::high_resolution_clock::now(); + render_duration = std::chrono::duration_cast(end_time - start_time).count(); + // std::cout << "Frame " << frame_count << ", duration: " << render_duration << std::endl; + if(frame_count > 0.0f && render_duration > 500.0f) + { + std::cout << "\rFPS : " << (frame_count * 1000.0f) / render_duration << " " << std::flush; + frame_count = 0.0f; + start_time = end_time; + } } glfwPollEvents(); @@ -74,9 +80,11 @@ void uui::GlApplication::run() else window->draw(); } - frame_count += 1; + if constexpr(debug_mode) + frame_count += 1; } - std::cout << "Ending the GlApplication" << std::endl; + if constexpr(debug_mode) + std::cout << "Ending the GlApplication" << std::endl; } std::shared_ptr uui::GlApplication::create_window(int width, int height, const std::string& title) diff --git a/src/uui/opengl/component.cpp b/src/uui/opengl/component.cpp index 242ad3e..4b83875 100644 --- a/src/uui/opengl/component.cpp +++ b/src/uui/opengl/component.cpp @@ -11,7 +11,8 @@ uui::GlComponent::GlComponent( window(window), position_x(x), position_y(y), size_width(width), size_height(height), initialized(false) { - std::cout << "Creating a GlBox" << std::endl; + if constexpr(uui::GlApplication::debug_mode) + std::cout << "Creating a GlBox" << std::endl; coord_all_relative = true; auto parse_coords = [&](std::variant& value) { @@ -33,7 +34,8 @@ uui::GlComponent::GlComponent( uui::GlComponent::~GlComponent() { - std::cout << "Closing a GlBox" << std::endl; + if constexpr(uui::GlApplication::debug_mode) + std::cout << "Closing a GlBox" << std::endl; if(initialized) deinit(); } @@ -94,12 +96,12 @@ void uui::GlComponent::set_vbo_data() float y_min = (-2.0f * vert_y) + 1.0f; float y_max = y_min - (2.0f * vert_h); // clang-format off - float vertices[] = { - x_max, y_min, 0.0f, background_color[0] , background_color[1], background_color[2], background_color[3], // top right - x_max, y_max, 0.0f, background_color[0] , background_color[1], background_color[2], background_color[3], // bottom right - x_min, y_max, 0.0f, background_color[0] , background_color[1], background_color[2], background_color[3], // bottom left - x_min, y_min, 0.0f, background_color[0] , background_color[1], background_color[2], background_color[3] // top left - }; + float vertices[] = { + x_max, y_min, 0.0f, background_color[0] , background_color[1], background_color[2], background_color[3], // top right + x_max, y_max, 0.0f, background_color[0] , background_color[1], background_color[2], background_color[3], // bottom right + x_min, y_max, 0.0f, background_color[0] , background_color[1], background_color[2], background_color[3], // bottom left + x_min, y_min, 0.0f, background_color[0] , background_color[1], background_color[2], background_color[3] // top left + }; // clang-format on glBindBuffer(GL_ARRAY_BUFFER, gl_vbo); if constexpr(uui::GlApplication::debug_mode) diff --git a/src/uui/opengl/font_manager.cpp b/src/uui/opengl/font_manager.cpp new file mode 100644 index 0000000..c368bf5 --- /dev/null +++ b/src/uui/opengl/font_manager.cpp @@ -0,0 +1,308 @@ +#include "uui/opengl/font_manager.hpp" + +#include +#include + +#include +#include + +#include "uui/opengl/application.hpp" +#include "uui/opengl/gl_utils.hpp" +#include "uui/shader.hpp" + +uui::GlFontManager::GlFontManager(): initialized(false) {} + +uui::GlFontManager::~GlFontManager() +{ + if(initialized) + deinit(); +} + +void uui::GlFontManager::init() +{ + if(initialized) + throw std::runtime_error("GlFontManager error : calling init while initialized"); + + if constexpr(uui::GlApplication::debug_mode) + std::cout << "Init glFontManager" << std::endl; + + if(FT_Init_FreeType(&ft)) + throw std::runtime_error("ERROR::FREETYPE: Could not init FreeType Library"); + + gl_program = glCreateProgram(); + // Vertex shader compilation + GLuint vertex_shader = glCreateShader(GL_VERTEX_SHADER); + { + auto vertex_src = readFile("bin/uui/opengl/shaders/text.vert"); + auto vertex_str = std::string(vertex_src.begin(), vertex_src.end()); + auto vertex_code = vertex_str.c_str(); + glShaderSource(vertex_shader, 1, &vertex_code, NULL); + glCompileShader(vertex_shader); + if constexpr(uui::GlApplication::debug_mode) + { + int success; + char info[512]; + glGetShaderiv(vertex_shader, GL_COMPILE_STATUS, &success); + if(!success) + { + glGetShaderInfoLog(vertex_shader, 512, NULL, info); + std::cout << "GlBox vertex shader creation error : " << info << std::endl; + } + } + } + + // Fragment shader compilation + GLuint fragment_shader = glCreateShader(GL_FRAGMENT_SHADER); + { + auto fragment_src = readFile("bin/uui/opengl/shaders/text.frag"); + auto fragment_str = std::string(fragment_src.begin(), fragment_src.end()); + auto fragment_code = fragment_str.c_str(); + glShaderSource(fragment_shader, 1, &fragment_code, NULL); + glCompileShader(fragment_shader); + if constexpr(uui::GlApplication::debug_mode) + { + int success; + char info[512]; + glGetShaderiv(fragment_shader, GL_COMPILE_STATUS, &success); + if(!success) + { + glGetShaderInfoLog(fragment_shader, 512, NULL, info); + std::cout << "GlBox fragment shader creation error : " << info << std::endl; + } + } + } + + // Linking shader program + glAttachShader(gl_program, vertex_shader); + if constexpr(uui::GlApplication::debug_mode) + _glCheckError(); + glAttachShader(gl_program, fragment_shader); + if constexpr(uui::GlApplication::debug_mode) + _glCheckError(); + glLinkProgram(gl_program); + if constexpr(uui::GlApplication::debug_mode) + { + int success; + char info[512]; + glGetProgramiv(gl_program, GL_LINK_STATUS, &success); + if(!success) + { + glGetProgramInfoLog(gl_program, 512, NULL, info); + std::cout << "GlBox error : " << info << std::endl; + } + } + glDeleteShader(vertex_shader); + glDeleteShader(fragment_shader); + + // init_font("/usr/share/fonts/noto/NotoSans-Regular.ttf", 32); + init_font("FreeSans.ttf", 32); + + // Initialize VAO/VBO for rendering + glGenVertexArrays(1, &gl_vao); + glGenBuffers(1, &gl_vbo); + glBindVertexArray(gl_vao); + glBindBuffer(GL_ARRAY_BUFFER, gl_vbo); + glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 6 * 4, NULL, GL_DYNAMIC_DRAW); + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(float), 0); + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindVertexArray(0); + + initialized = true; +} + +void uui::GlFontManager::deinit() +{ + if(!initialized) + throw std::runtime_error("GlFontManager error : calling deinit while not initialized"); + + for(const auto& font: fonts) glDeleteTextures(1, &font.tex); + glDeleteBuffers(1, &gl_vbo); + glDeleteBuffers(1, &gl_vao); + glDeleteProgram(gl_program); + FT_Done_FreeType(ft); + initialized = false; +} + +const uui::GlFontManager::Font& uui::GlFontManager::init_font(const std::string& font_path, size_t font_size) +{ + fonts.emplace_back(); + auto& font = fonts.back(); + FT_Face face; + if(FT_New_Face(ft, font_path.c_str(), 0, &face)) + throw std::runtime_error("ERROR::FREETYPE: Failed to load font"); + FT_Set_Pixel_Sizes(face, 0, font_size); + + FT_GlyphSlot glyph = face->glyph; + + unsigned int row_width = 0; + unsigned int row_height = 0; + + memset(font.char_infos, 0, sizeof(font.char_infos)); + + // Find minimum size for a texture holding all visible ASCII characters + for(int i = 32; i < 128; i++) + { + if(FT_Load_Char(face, i, FT_LOAD_RENDER)) + { + std::cout << "GlFontManager warning : loading character " << i << " failed!" << std::endl; + continue; + } + if(row_width + glyph->bitmap.width + 1 >= MAX_WIDTH) + { + font.atlas_width = std::max(font.atlas_width, row_width); + font.atlas_height += row_height; + row_width = 0; + row_height = 0; + } + row_width += glyph->bitmap.width + 1; + row_height = std::max(row_height, glyph->bitmap.rows); + } + + font.atlas_width = std::max(font.atlas_width, row_width); + font.atlas_height += row_height; + + glLinkProgram(gl_program); + + // Create a texture that will be used to hold all ASCII glyphs + glActiveTexture(GL_TEXTURE0); + glGenTextures(1, &font.tex); + glBindTexture(GL_TEXTURE_2D, font.tex); + + glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, font.atlas_width, font.atlas_height, 0, GL_RED, GL_UNSIGNED_BYTE, 0); + + // We require 1 byte alignment when uploading texture data + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + + // Clamping to edges is important to prevent artifacts when scaling + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + // Linear filtering usually looks best for text + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + // Paste all glyph bitmaps into the texture, remembering the offset + int offset_x = 0; + int offset_y = 0; + + row_height = 0; + + for(int i = 32; i < 128; i++) + { + if(FT_Load_Char(face, i, FT_LOAD_RENDER)) + { + std::cout << "GlFontManager warning : loading character " << i << " failed!" << std::endl; + continue; + } + + if(offset_x + glyph->bitmap.width + 1 >= MAX_WIDTH) + { + offset_y += row_height; + row_height = 0; + offset_x = 0; + } + + glTexSubImage2D( + GL_TEXTURE_2D, 0, offset_x, offset_y, glyph->bitmap.width, glyph->bitmap.rows, GL_RED, GL_UNSIGNED_BYTE, glyph->bitmap.buffer); + font.char_infos[i].advance_x = glyph->advance.x >> 6; + font.char_infos[i].advance_y = glyph->advance.y >> 6; + + font.char_infos[i].bitmap_width = glyph->bitmap.width; + font.char_infos[i].bitmap_height = glyph->bitmap.rows; + + font.char_infos[i].bitmap_left = glyph->bitmap_left; + font.char_infos[i].bitmap_top = glyph->bitmap_top; + + font.char_infos[i].texture_x = offset_x / (float)font.atlas_width; + font.char_infos[i].texture_y = offset_y / (float)font.atlas_height; + + row_height = std::max(row_height, glyph->bitmap.rows); + offset_x += glyph->bitmap.width + 1; + } + FT_Done_Face(face); + + return font; +} + +std::tuple uui::GlFontManager::get_text_size( + const std::string& text, uui::GlFontManager::Font& font, float scale_x, float scale_y) +{ + float width = 0.0f; + float height = 0.0f; + for(auto current_char: text) + { + const auto& char_info = font.char_infos[static_cast(current_char)]; + width += char_info.advance_x * scale_x; + // height = std::max(height, char_info.bitmap_top + char_info.bitmap_height * scale_y); + height = std::max(height, roundf(char_info.bitmap_height * scale_y)); + } + return {static_cast(width), static_cast(height)}; +} + +void uui::GlFontManager::render_text( + const std::string& text, uui::GlFontManager::Font& font, float x, float y, float scale_x, float scale_y, + const std::tuple& text_color) +{ + GLuint gl_prev_program; + glGetIntegerv(GL_CURRENT_PROGRAM, (GLint*)(&gl_prev_program)); + glUseProgram(gl_program); + + glActiveTexture(GL_TEXTURE0); + auto [color_r, color_g, color_b, color_a] = text_color; + glUniform4f(glGetUniformLocation(gl_program, "textColor"), color_r, color_g, color_b, color_a); + glBindVertexArray(gl_vao); + + // Use the texture containing the atlas + glBindTexture(GL_TEXTURE_2D, font.tex); + + std::vector vertices; + vertices.resize(24 * text.size()); + // vertices.resize(text.size()); + int char_count = 0; + + // Loop through all characters + float out_base_x = x; + float out_base_y = y; + for(auto current_char: text) + { + const auto& char_info = font.char_infos[static_cast(current_char)]; + // Calculate the vertex and texture coordinates + float char_x = out_base_x + char_info.bitmap_left * scale_x; + float char_y = -out_base_y - char_info.bitmap_top * scale_y; + float w = char_info.bitmap_width * scale_x; + float h = char_info.bitmap_height * scale_y; + + // Advance the cursor to the start of the next character + out_base_x += char_info.advance_x * scale_x; + out_base_y += char_info.advance_y * scale_y; + + // Skip glyphs that have no pixels + if(!w || !h) + continue; + + // clang-format off + float current_vertex[24] = { + char_x, -char_y, char_info.texture_x, char_info.texture_y, // top_left + char_x + w, -char_y, char_info.texture_x + char_info.bitmap_width / font.atlas_width, char_info.texture_y, // top-right + char_x, -char_y - h, char_info.texture_x, char_info.texture_y + char_info.bitmap_height / font.atlas_height, // bottom-right + char_x + w, -char_y, char_info.texture_x + char_info.bitmap_width / font.atlas_width, char_info.texture_y, // top-right + char_x, -char_y - h, char_info.texture_x, char_info.texture_y + char_info.bitmap_height / font.atlas_height, // bottom-left + char_x + w, -char_y - h, char_info.texture_x + char_info.bitmap_width / font.atlas_width, + char_info.texture_y + char_info.bitmap_height / font.atlas_height // bottom-right + }; + std::memcpy(vertices.data() + (char_count * 24), current_vertex, 24 * sizeof(float)); + // clang-format on + char_count += 1; + } + + // Draw all the character on the screen in one go + glBindBuffer(GL_ARRAY_BUFFER, gl_vbo); + glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(float), vertices.data(), GL_DYNAMIC_DRAW); + glDrawArrays(GL_TRIANGLES, 0, vertices.size()); + + // glDisableVertexAttribArray(0); + glBindTexture(GL_TEXTURE_2D, 0); + glBindVertexArray(0); + glUseProgram(gl_prev_program); +} diff --git a/src/uui/opengl/label.cpp b/src/uui/opengl/label.cpp new file mode 100644 index 0000000..63aaee2 --- /dev/null +++ b/src/uui/opengl/label.cpp @@ -0,0 +1,35 @@ +#include "uui/opengl/label.hpp" + +#include + +#include "uui/opengl/gl_utils.hpp" + +uui::GlLabel::GlLabel( + std::shared_ptr window, const std::variant x, const std::variant y, const std::string& text, + const std::tuple& text_color): + GlComponent(window, x, y, 0.0f, 0.0f), + text(text), text_color(text_color) +{ + if constexpr(uui::GlApplication::debug_mode) + std::cout << "Creating a GlLabel" << std::endl; + + font_index = 0; + std::tie(text_width, text_height) = window->font_manager.get_text_size(text, window->font_manager.fonts[font_index], 1.0f, 1.0f); + std::cout << "Text size: " << this->text_width << "x" << text_height << std::endl; + size_width = text_width; + size_height = text_height; +} + +void uui::GlLabel::render() +{ + float text_x = + position_x.index() == 1 ? std::get<1>(position_x) * static_cast(window->width) : static_cast(std::get<0>(position_x)); + float text_y = + position_y.index() == 1 ? std::get<1>(position_y) * static_cast(window->height) : static_cast(std::get<0>(position_y)); + text_y -= static_cast(text_height) - 1; + + glBindVertexArray(gl_vao); + glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, (void*)0); + window->font_manager.render_text(text.c_str(), window->font_manager.fonts.front(), text_x, text_y, 1.0f, 1.0f, text_color); + glBindVertexArray(0); +} diff --git a/src/uui/opengl/shaders/text.frag b/src/uui/opengl/shaders/text.frag new file mode 100644 index 0000000..dc0c11b --- /dev/null +++ b/src/uui/opengl/shaders/text.frag @@ -0,0 +1,12 @@ +#version 460 core + +in vec2 textCoords; + +out vec4 color; + +uniform sampler2D text; +uniform vec4 textColor; + +void main(void) { + color = vec4(1.0, 1.0, 1.0, texture2D(text, textCoords).r) * textColor; +} diff --git a/src/uui/opengl/shaders/text.vert b/src/uui/opengl/shaders/text.vert new file mode 100644 index 0000000..cf90299 --- /dev/null +++ b/src/uui/opengl/shaders/text.vert @@ -0,0 +1,12 @@ +#version 460 core + +layout (location = 0) in vec4 coord; + +out vec2 textCoords; + +uniform mat4 projection; + +void main(void) { + gl_Position = projection * vec4(coord.xy, 0.0, 1.0); + textCoords = coord.zw; +} diff --git a/src/uui/opengl/window.cpp b/src/uui/opengl/window.cpp index 197a605..d9e7f2a 100644 --- a/src/uui/opengl/window.cpp +++ b/src/uui/opengl/window.cpp @@ -4,6 +4,7 @@ #include "uui/opengl/component.hpp" #include "uui/opengl/gl_utils.hpp" +#include "uui/opengl/label.hpp" #include "uui/shader.hpp" static void APIENTRY @@ -59,27 +60,39 @@ void uui::GlWindow::glfw_resize_callback(GLFWwindow* window, int width, int heig gl_window->width = width; gl_window->height = height; gl_window->need_resize_refresh = true; + + glm::mat4 projection = glm::ortho(0.0f, static_cast(width), 0.0f, static_cast(height)); + glUniformMatrix4fv(glGetUniformLocation(gl_window->font_manager.gl_program, "projection"), 1, GL_FALSE, glm::value_ptr(projection)); } -static void glfw_key_callback(GLFWwindow* window, int key, int scancode, int action, int mods) +void uui::GlWindow::glfw_iconify_callback(GLFWwindow* window, int iconified) +{ + auto gl_window = reinterpret_cast(glfwGetWindowUserPointer(window)); + gl_window->iconified = iconified; +} + +void uui::GlWindow::glfw_key_callback(GLFWwindow* window, int key, int scancode, int action, int mods) { if(key == GLFW_KEY_ESCAPE && action == GLFW_PRESS) glfwSetWindowShouldClose(window, true); } uui::GlWindow::GlWindow(uui::GlApplication* app, size_t index, int width, int height, const std::string& title): - app(app), width(width), height(height), need_resize_refresh(false), index(index), initialized(false) + app(app), width(width), height(height), need_resize_refresh(false), iconified(false), index(index), initialized(false) { - std::cout << "Creating a GlWindow" << std::endl; + if constexpr(uui::GlApplication::debug_mode) + std::cout << "Creating a GlWindow" << std::endl; this->title = title; } uui::GlWindow::~GlWindow() { - std::cout << "Closing a GlWindow" << std::endl; + if constexpr(uui::GlApplication::debug_mode) + std::cout << "Closing a GlWindow" << std::endl; if(initialized) { - std::cout << "Destructor-deinit a GlWindow" << std::endl; + if constexpr(uui::GlApplication::debug_mode) + std::cout << "Destructor-deinit a GlWindow" << std::endl; deinit(); } } @@ -97,10 +110,15 @@ void uui::GlWindow::init() glfwSetWindowUserPointer(glfw_window, this); glfwSetFramebufferSizeCallback(glfw_window, glfw_resize_callback); glfwSetKeyCallback(glfw_window, glfw_key_callback); + glfwSetWindowIconifyCallback(glfw_window, glfw_iconify_callback); if(!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) throw std::runtime_error("GLWindow : Failed to initialize OpenGL context"); + // glEnable(GL_CULL_FACE); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + if constexpr(uui::GlApplication::debug_mode) { GLint flags; @@ -178,6 +196,13 @@ void uui::GlWindow::init() } glDeleteShader(vertex_shader); glDeleteShader(fragment_shader); + + font_manager.init(); + glUseProgram(font_manager.gl_program); + glm::mat4 projection = glm::ortho(0.0f, static_cast(width), 0.0f, static_cast(height)); + glUniformMatrix4fv(glGetUniformLocation(font_manager.gl_program, "projection"), 1, GL_FALSE, glm::value_ptr(projection)); + glUseProgram(gl_program); + initialized = true; } @@ -186,12 +211,14 @@ void uui::GlWindow::deinit() if(!initialized) throw std::runtime_error("GlWindow error : calling deinit while not initialized"); - std::cout << "Deinit a GlWindow" << std::endl; + if constexpr(uui::GlApplication::debug_mode) + std::cout << "Deinit a GlWindow" << std::endl; glfwMakeContextCurrent(glfw_window); glDeleteProgram(gl_program); for(auto& component: components) component->deinit(); components.clear(); + font_manager.deinit(); glfwDestroyWindow(glfw_window); initialized = false; @@ -199,17 +226,20 @@ void uui::GlWindow::deinit() void uui::GlWindow::draw() { - glfwMakeContextCurrent(glfw_window); - glClearColor(0.1f, 0.1f, 0.1f, 1.0f); - glClear(GL_COLOR_BUFFER_BIT); - - glUseProgram(gl_program); - if(need_resize_refresh) + if(!iconified) { - for(auto& component: components) component->on_resize(); - need_resize_refresh = false; + glfwMakeContextCurrent(glfw_window); + glClearColor(0.1f, 0.1f, 0.1f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + + glUseProgram(gl_program); + if(need_resize_refresh) + { + for(auto& component: components) component->on_resize(); + need_resize_refresh = false; + } + for(auto& component: components) component->render(); } - for(auto& component: components) component->render(); glfwSwapBuffers(glfw_window); } @@ -227,3 +257,17 @@ std::shared_ptr uui::GlWindow::create_component( components.push_back(component); return component; } + +std::shared_ptr uui::GlWindow::create_label( + const std::variant x, const std::variant y, const std::string& text, + const std::tuple& text_color) +{ + if(!initialized) + throw std::runtime_error("GlWindow error : cannot create component while not initialized"); + + glfwMakeContextCurrent(glfw_window); + std::shared_ptr label(new uui::GlLabel(app->get_window(index), x, y, text, text_color)); + label->init(); + components.push_back(label); + return label; +}