diff --git a/CMakeLists.txt b/CMakeLists.txt index b869ea0..6dffe92 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,6 +16,7 @@ set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) find_package(OpenGL REQUIRED) +find_package(Freetype REQUIRED) if (UNIX AND NOT APPLE) find_package(PkgConfig REQUIRED) pkg_check_modules(EGL REQUIRED egl) @@ -75,6 +76,7 @@ add_executable(${PROJECT_NAME} src/texture_manager.cpp src/tools/math_tools.cpp src/tools/shader_tools.cpp + src/tools/font.cpp src/tools/log.cpp src/window.cpp ) @@ -102,6 +104,7 @@ target_link_libraries(${PROJECT_NAME} glm::glm OpenGL::GL soil2 + Freetype::Freetype ) if (UNIX AND NOT APPLE) diff --git a/assets/fonts/IBMPlexSans-Regular.ttf b/assets/fonts/IBMPlexSans-Regular.ttf new file mode 100644 index 0000000..bd6817d Binary files /dev/null and b/assets/fonts/IBMPlexSans-Regular.ttf differ diff --git a/include/Cubed/app.hpp b/include/Cubed/app.hpp index 143dd11..5439669 100644 --- a/include/Cubed/app.hpp +++ b/include/Cubed/app.hpp @@ -16,9 +16,11 @@ public: static void mouse_button_callback(GLFWwindow* window, int button, int action, int mods); static void window_focus_callback(GLFWwindow* window, int focused); static void window_reshape_callback(GLFWwindow* window, int new_width, int new_height); + static int start_cubed_application(int argc, char** argv); - + static float delte_time(); + static float get_fps(); private: Camera m_camera; TextureManager m_texture_manager; @@ -30,7 +32,12 @@ private: GLuint m_texture_array; - + inline static double last_time = glfwGetTime(); + inline static double current_time = glfwGetTime(); + inline static double delta_time = 0.0f; + inline static double fps_time_count = 0.0f; + inline static int frame_count = 0; + inline static int fps; void init(); diff --git a/include/Cubed/renderer.hpp b/include/Cubed/renderer.hpp index 6a1887f..de70ea6 100644 --- a/include/Cubed/renderer.hpp +++ b/include/Cubed/renderer.hpp @@ -30,11 +30,13 @@ private: GLuint m_proj_loc; GLuint m_sky_vbo; + GLuint m_text_vbo; GLuint m_outline_indices_vbo; GLuint m_outline_vbo; GLuint m_ui_vbo; GLuint m_sky_program; + GLuint m_text_program; GLuint m_outline_program; GLuint m_ui_program; GLuint m_world_program; @@ -47,5 +49,7 @@ private: std::vector m_ui; void render_outline(); void render_sky(); + void render_text(); void render_ui(); + }; \ No newline at end of file diff --git a/include/Cubed/tools/font.hpp b/include/Cubed/tools/font.hpp new file mode 100644 index 0000000..7a69bd4 --- /dev/null +++ b/include/Cubed/tools/font.hpp @@ -0,0 +1,35 @@ +#pragma once +#include +#include FT_FREETYPE_H + +#include +#include + +#include +#include + +struct Character { + GLuint texture_id; + glm::ivec2 size; + glm::ivec2 bearing; + GLuint advance; +}; + +class Font { +public: + Font(); + ~Font(); + + static void render_text(GLuint program, GLuint vbo, const std::string& text, float x, float y, float scale, const glm::vec3& color); + +private: + FT_Library m_ft; + FT_Face m_face; + + std::unordered_map m_characters; + + void load_character(char8_t c); + void setup_font_character(); + + +}; \ No newline at end of file diff --git a/src/app.cpp b/src/app.cpp index 49c6de9..e599bbf 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -6,13 +6,6 @@ #include -static double last_time = glfwGetTime(); -static double current_time = glfwGetTime(); -static double delta_time = 0.0f; -static double fps_time_count = 0.0f; -static int frame_count = 0; -static int fps; - App::App() { } @@ -163,4 +156,12 @@ int App::start_cubed_application(int argc, char** argv) { } return 1; +} + +float App::delte_time() { + return delta_time; +} + +float App::get_fps() { + return fps; } \ No newline at end of file diff --git a/src/renderer.cpp b/src/renderer.cpp index b705126..8b27d68 100644 --- a/src/renderer.cpp +++ b/src/renderer.cpp @@ -1,9 +1,11 @@ +#include #include #include #include #include #include #include +#include #include #include @@ -23,6 +25,7 @@ Renderer::~Renderer() { glDeleteBuffers(1, &m_outline_indices_vbo); glDeleteBuffers(1, &m_sky_vbo); glDeleteBuffers(1, &m_ui_vbo); + glDeleteBuffers(1, &m_text_vbo); glBindVertexArray(0); glDeleteVertexArrays(NUM_VAO, m_vao.data()); glDeleteProgram(m_world_program); @@ -45,11 +48,15 @@ void Renderer::init() { m_outline_program = Shader::create_shader_program("shaders/outline_v_shader.glsl", "shaders/outline_f_shader.glsl"); m_sky_program = Shader::create_shader_program("shaders/sky_v_shader.glsl", "shaders/sky_f_shader.glsl"); m_ui_program = Shader::create_shader_program("shaders/ui_v_shader.glsl", "shaders/ui_f_shader.glsl"); - + m_text_program = Shader::create_shader_program("shaders/text_v_shader.glsl", "shaders/text_f_shader.glsl"); + + glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LEQUAL); + glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + #ifndef NDEBUG glEnable(GL_DEBUG_OUTPUT); glDebugMessageCallback([](GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const void* user_param) { @@ -57,7 +64,6 @@ void Renderer::init() { }, nullptr); #endif - m_vao.resize(NUM_VAO); glGenVertexArrays(NUM_VAO, m_vao.data()); glBindVertexArray(m_vao[0]); @@ -91,6 +97,9 @@ void Renderer::init() { glBufferData(GL_ARRAY_BUFFER, m_ui.size() * sizeof(Vertex2D), m_ui.data(), GL_STATIC_DRAW); + glGenBuffers(1, &m_text_vbo); + glBindBuffer(GL_ARRAY_BUFFER, m_text_vbo); + glBufferData(GL_ARRAY_BUFFER, sizeof(float)* 6 * 4, NULL, GL_DYNAMIC_DRAW); glBindBuffer(GL_ARRAY_BUFFER, 0); } @@ -118,6 +127,8 @@ void Renderer::render() { render_outline(); render_ui(); + + render_text(); } void Renderer::render_outline() { @@ -170,6 +181,16 @@ void Renderer::render_sky() { } +void Renderer::render_text() { + glUseProgram(m_text_program); + glDisable(GL_DEPTH_TEST); + m_proj_loc = glGetUniformLocation(m_text_program, "projection"); + + glUniformMatrix4fv(m_proj_loc, 1, GL_FALSE, glm::value_ptr(m_ui_proj)); + Font::render_text(m_text_program, m_text_vbo, std::string{"fps" + std::to_string(static_cast(App::get_fps()))}, 0.0f, 50.0f, 1.0f, glm::vec3(1.0f, 1.0f, 1.0f)); + glEnable(GL_DEPTH_TEST); +} + void Renderer::render_ui() { glUseProgram(m_ui_program); glDisable(GL_DEPTH_TEST); diff --git a/src/shaders/text_f_shader.glsl b/src/shaders/text_f_shader.glsl new file mode 100644 index 0000000..3756dd4 --- /dev/null +++ b/src/shaders/text_f_shader.glsl @@ -0,0 +1,12 @@ +#version 460 + +in vec2 texCoord; +out vec4 color; + +layout (binding = 0) uniform sampler2D text; +uniform vec3 textColor; + +void main(void) { + vec4 smapled = vec4(1.0, 1.0, 1.0, texture(text, texCoord).r); + color = vec4(textColor, 1.0) * smapled; +} \ No newline at end of file diff --git a/src/shaders/text_v_shader.glsl b/src/shaders/text_v_shader.glsl new file mode 100644 index 0000000..60d2663 --- /dev/null +++ b/src/shaders/text_v_shader.glsl @@ -0,0 +1,11 @@ +#version 460 + +layout (location = 0) in vec4 vertex; +out vec2 texCoord; + +uniform mat4 projection; + +void main(void) { + gl_Position = projection * vec4(vertex.xy, 0.0, 1.0); + texCoord = vec2(vertex.zw); +} \ No newline at end of file diff --git a/src/tools/font.cpp b/src/tools/font.cpp new file mode 100644 index 0000000..175880c --- /dev/null +++ b/src/tools/font.cpp @@ -0,0 +1,112 @@ + + +#include +#include + + + + +Font::Font() { + + if (FT_Init_FreeType(&m_ft)) { + LOG::error("FREETYPE: Could not init FreeType Library"); + } + if (FT_New_Face(m_ft, "assets/fonts/IBMPlexSans-Regular.ttf", 0, &m_face)) { + LOG::error("FREETYPE: Failed to load font"); + } + + FT_Set_Pixel_Sizes(m_face, 0, 48); + setup_font_character(); +} + +Font::~Font() { + + FT_Done_Face(m_face); + FT_Done_FreeType(m_ft); + for (const auto& [key, character] : m_characters) { + glDeleteTextures(1, &character.texture_id); + } +} + +void Font::load_character(char8_t c) { + if (FT_Load_Char(m_face, c, FT_LOAD_RENDER)) { + LOG::error("FREETYTPE: Failed to load Glyph"); + return; + } + GLuint texture; + glGenTextures(1, &texture); + glBindTexture(GL_TEXTURE_2D, texture); + glTexImage2D( + GL_TEXTURE_2D, + 0, + GL_RED, + m_face->glyph->bitmap.width, + m_face->glyph->bitmap.rows, + 0, + GL_RED, + GL_UNSIGNED_BYTE, + m_face->glyph->bitmap.buffer + ); + 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); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + Character character = { + texture, + glm::ivec2(m_face->glyph->bitmap.width, m_face->glyph->bitmap.rows), + glm::ivec2(m_face->glyph->bitmap_left, m_face->glyph->bitmap_top), + static_cast(m_face->glyph->advance.x) + }; + + m_characters.insert({c, std::move(character)}); +} + +void Font::setup_font_character() { + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + for (char8_t c = 0; c < 128; c++) { + load_character(c); + } +} + + + +void Font::render_text(GLuint program, GLuint vbo, const std::string& text, float x, float y, float scale, const glm::vec3& color) { + static Font font; + glUseProgram(program); + glUniform3f(glGetUniformLocation(program, "textColor"), color.x, color.y, color.z); + glActiveTexture(GL_TEXTURE0); + for (char8_t c : text) { + auto it = font.m_characters.find(c); + if (it == font.m_characters.end()) { + LOG::error("Can't find character {}", static_cast(c)); + continue; + } + Character& ch = it->second; + float xpos = x + ch.bearing.x * scale; + float ypos = y - ch.bearing.y * scale; + + float w = ch.size.x * scale; + float h = ch.size.y * scale; + + float vertices[6][4] { + { xpos, ypos + h, 0.0, 1.0 }, + { xpos, ypos, 0.0, 0.0 }, + { xpos + w, ypos, 1.0, 0.0 }, + + { xpos, ypos + h, 0.0, 1.0 }, + { xpos + w, ypos, 1.0, 0.0 }, + { xpos + w, ypos + h, 1.0, 1.0 } + }; + glBindTexture(GL_TEXTURE_2D, ch.texture_id); + glBindBuffer(GL_ARRAY_BUFFER, vbo); + glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices); + glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), 0); + glEnableVertexAttribArray(0); + glDrawArrays(GL_TRIANGLES, 0, 6); + x += (ch.advance >> 6) * scale; + glBindBuffer(GL_ARRAY_BUFFER, 0); + } + glBindTexture(GL_TEXTURE_2D, 0); + +}