diff --git a/CMakeLists.txt b/CMakeLists.txt index 22e0192..02fb0fc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.25) include(FetchContent) -set(BLT_GRAPHICS_VERSION 1.0.7) +set(BLT_GRAPHICS_VERSION 1.0.8) set(BLT_GRAPHICS_TEST_VERSION 0.0.1) project(BLT_WITH_GRAPHICS VERSION ${BLT_GRAPHICS_VERSION}) diff --git a/build_emscript.sh b/build_emscript.sh old mode 100755 new mode 100644 diff --git a/cloc.sh b/cloc.sh old mode 100755 new mode 100644 diff --git a/include/blt/gfx/renderer/font_renderer.h b/include/blt/gfx/renderer/font_renderer.h index c1c1672..3fcb6a9 100644 --- a/include/blt/gfx/renderer/font_renderer.h +++ b/include/blt/gfx/renderer/font_renderer.h @@ -35,7 +35,7 @@ namespace blt::gfx struct bounded_t { font::glyph_t glyph; - // starting point + // starting point inside texture atlas blt::vec2ui min; // ending point of the glyph, inside texture atlas blt::vec2ui max; @@ -49,18 +49,18 @@ namespace blt::gfx public: explicit font_texture_atlas(blt::i32 dimensions); - added_font_results_t add_font(const font::font_file_t& file); + added_font_results_t add_font(const font::font_file_t& file, float size); - added_font_results_t add_font(const font::font_file_t& file, blt::u64 start); + added_font_results_t add_font(const font::font_file_t& file, blt::u64 start, float size); - bounded_t& get_glyph(blt::u64 c) - { return glyphs.at(c); } + bounded_t& get_glyph(blt::u64 c, float size) + { return glyphs.at(size).at(c); } - [[nodiscard]] const bounded_t& get_glyph(blt::u64 c) const - { return glyphs.at(c); } + [[nodiscard]] const bounded_t& get_glyph(blt::u64 c, float size) const + { return glyphs.at(size).at(c); } private: - blt::hashmap_t glyphs; + blt::hashmap_t> glyphs; blt::u32 used_width = 0; blt::u32 used_height = 0; }; @@ -90,11 +90,14 @@ namespace blt::gfx { if (!(size >= atlas.min_size && size <= atlas.max_size)) continue; - if (atlas.min_char > c && atlas.max_char < c) + if (c >= atlas.min_char && c <= atlas.max_char) return *atlas.atlas; } throw std::runtime_error("Character not found inside atlases at this size"); } + + [[nodiscard]] blt::i32 get_dimensions() const + { return dimensions; } private: blt::i32 dimensions; @@ -111,27 +114,89 @@ namespace blt::gfx public: explicit compiled_text_t(font_generator_t& generator); - void change_text(std::string_view str); + void change_text(std::string_view str, float size); + + compiled_text_t& setPosition(const blt::vec2f& pos) + { + position = pos; + return *this; + } + + compiled_text_t& setScale(const blt::vec2f& s) + { + scale = s; + return *this; + } + + compiled_text_t& setColor(const blt::vec4f& c) + { + color = c; + return *this; + } + + compiled_text_t& setPosition(float x, float y) + { + position = {x, y}; + return *this; + } + + compiled_text_t& setScale(float x, float y) + { + scale = {x, y}; + return *this; + } + + compiled_text_t& setColor(float r, float g, float b, float a = 1) + { + color = {r, g, b, a}; + return *this; + } + + compiled_text_t& setZIndex(float s) + { + z_index = s; + return *this; + } + + [[nodiscard]] const vec2f& getPosition() const + { return position; } + + [[nodiscard]] const vec2f& getScale() const + { return scale; } + + [[nodiscard]] const vec4f& getColor() const + { return color; } + + [[nodiscard]] float getZIndex() const + { return z_index; } private: void draw(); struct text_render_info_t { - vertex_array_t vao{}; - font_texture_atlas* texture{}; + font_texture_atlas* texture = nullptr; + blt::size_t render_start = 0; blt::size_t render_count = 0; - text_render_info_t() = default; + text_render_info_t(font_texture_atlas* texture, size_t renderStart, size_t renderCount): + texture(texture), render_start(renderStart), render_count(renderCount) + {} }; font_generator_t& generator; - std::vector> renders; + vertex_array_t vao{}; + std::vector renders; + blt::vec2f position; + blt::vec2f scale = {1, 1}; + blt::vec4 color = blt::make_color(1, 1, 1); + float z_index = 0; }; public: explicit font_renderer_t(); + void create_default(blt::i32 dimensions = 0); void create(const std::vector& generated_font_sizes, blt::i32 dimensions = 0); void cleanup(); @@ -141,9 +206,15 @@ namespace blt::gfx generator->add_font(file); } - compiled_text_t* create_text(std::string_view str); + void add_default_font(std::string_view path); + + void add_default_font(const blt::u8* data, blt::size_t size, bool compressed = false); + + compiled_text_t* create_text(std::string_view str, float size); void destroy_text(compiled_text_t* text); + + void render(); private: std::unique_ptr generator; diff --git a/include/blt/gfx/renderer/shaders/2d_font.frag b/include/blt/gfx/renderer/shaders/2d_font.frag index fa56cdb..27aa0e7 100644 --- a/include/blt/gfx/renderer/shaders/2d_font.frag +++ b/include/blt/gfx/renderer/shaders/2d_font.frag @@ -1,18 +1,20 @@ #ifdef __cplusplus #include const std::string shader_2d_font_frag = R"(" -#version 300 es -precision mediump float; + #version 300 es + precision mediump float; -layout (location = 0) out vec4 FragColor; -in vec2 uv; + layout (location = 0) out vec4 FragColor; + in vec2 uv; -uniform sampler2D tex; -uniform vec4 color; + uniform sampler2D tex; + uniform vec4 color; -void main() { - FragColor = texture(tex, uv).r * color; -} + void main() { + FragColor = texture(tex, uv).r * color; + if (FragColor.a < 0.2) + discard; + } ")"; #endif \ No newline at end of file diff --git a/include/blt/gfx/renderer/shaders/2d_font.vert b/include/blt/gfx/renderer/shaders/2d_font.vert index 70636bc..ab083eb 100644 --- a/include/blt/gfx/renderer/shaders/2d_font.vert +++ b/include/blt/gfx/renderer/shaders/2d_font.vert @@ -18,9 +18,10 @@ layout (std140) uniform GlobalMatrices }; uniform float z_index; +uniform mat4 transform; void main() { - gl_Position = ovm * vec4(vertex.xy, z_index, 1.0); + gl_Position = ovm * transform * vec4(vertex.xy, z_index, 1.0); uv = vertex.zw; } diff --git a/resources/fonts/a.out b/resources/fonts/a.out old mode 100755 new mode 100644 diff --git a/src/blt/gfx/renderer/font_renderer.cpp b/src/blt/gfx/renderer/font_renderer.cpp index 878d60d..2c2debe 100644 --- a/src/blt/gfx/renderer/font_renderer.cpp +++ b/src/blt/gfx/renderer/font_renderer.cpp @@ -25,18 +25,27 @@ namespace blt::gfx { font_texture_atlas::font_texture_atlas(blt::i32 dimensions): texture_gl2D(dimensions, dimensions, GL_R8) - {} - - font_texture_atlas::added_font_results_t font_texture_atlas::add_font(const font::font_file_t& file) { - return add_font(file, file.character_min_index); + bind(); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 4); } - font_texture_atlas::added_font_results_t font_texture_atlas::add_font(const font::font_file_t& file, blt::u64 start) + font_texture_atlas::added_font_results_t font_texture_atlas::add_font(const font::font_file_t& file, float size) + { + return add_font(file, file.character_min_index, size); + } + + font_texture_atlas::added_font_results_t font_texture_atlas::add_font(const font::font_file_t& file, blt::u64 start, float size) { blt::u32 max_height = 0; blt::u64 min_index_used = file.character_max_index; blt::u64 max_index_used = start; + file.font.set_pixel_sizes(0, static_cast(size)); + + const blt::u32 padding = 2; + for (auto i = start; i < file.character_max_index; i++) { // returns an empty optional if there is no error code @@ -46,10 +55,14 @@ namespace blt::gfx auto width = face->glyph->bitmap.width; auto height = face->glyph->bitmap.rows; - max_height = std::max(max_height, height); + + auto texture_width = width + padding; + auto texture_height = height + padding; + + max_height = std::max(max_height, texture_height); // if adding this width overflows, we need to move to the next row. - if (used_width + width > static_cast(getWidth())) + if (used_width + texture_width > static_cast(getWidth())) { used_height += max_height; max_height = 0; @@ -66,14 +79,14 @@ namespace blt::gfx auto begin_height = used_height; auto end_width = used_width + width; auto end_height = used_height + height; - used_width += width; + used_width += texture_width; // BLT_TRACE("%d %d %d %d", begin_width, begin_height, width, height); // upload the texture upload(face->glyph->bitmap.buffer, GL_RED, 0, static_cast(begin_width), static_cast(begin_height), static_cast(width), static_cast(height), GL_UNSIGNED_BYTE); - glyphs.insert(std::pair{i, bounded_t{ + glyphs[size].insert(std::pair{i, bounded_t{ font::glyph_t{ {width, height}, {face->glyph->bitmap_left, face->glyph->bitmap_top}, @@ -81,6 +94,8 @@ namespace blt::gfx {begin_width, begin_height}, {end_width, end_height}} }); } + bind(); + generateMipmaps(); return {-1ul, min_index_used, max_index_used}; } @@ -98,7 +113,6 @@ namespace blt::gfx min_size = std::min(size, min_size); max_size = std::max(size, max_size); auto& vec = atlases; - file.font.set_pixel_sizes(0, static_cast(size)); if (vec.empty()) vec.emplace_back(std::make_unique(dimensions), min_size, max_size, file.character_min_index, 0); @@ -106,15 +120,17 @@ namespace blt::gfx blt::u64 start_index = file.character_min_index; while (start_index != -1ul) { - auto results = vec.back().atlas->add_font(file, start_index); + auto results = vec.back().atlas->add_font(file, start_index, size); vec.back().min_char = results.min_index_used; vec.back().max_char = results.max_index_used; vec.back().min_size = min_size; vec.back().max_size = max_size; - min_size = size; - max_size = size; if (results.last_inserted_index != -1ul) + { vec.emplace_back(std::make_unique(dimensions), min_size, max_size, start_index, 0); + min_size = size; + max_size = size; + } start_index = results.last_inserted_index; // BLT_INFO("%f, %ld", size, start_index); } @@ -126,12 +142,13 @@ namespace blt::gfx extern float square_vertices[20]; extern const unsigned int square_indices[6]; - font_renderer_t::font_renderer_t() - {} + font_renderer_t::font_renderer_t() = default; void font_renderer_t::create(const std::vector& generated_font_sizes, blt::i32 dimensions) { font_shader = shader_t::make_unique(shader_2d_font_vert, shader_2d_font_frag); + font_shader->bind(); + font_shader->bindAttribute(0, "vertex"); font::create(); if (dimensions == 0) { @@ -145,11 +162,19 @@ namespace blt::gfx void font_renderer_t::cleanup() { + for (auto& n : added_texts) + n = nullptr; + for (auto& c : cached_text_objects) + c = nullptr; + added_texts.clear(); + cached_text_objects.clear(); + text_ptr_to_index.clear(); + font_shader = nullptr; generator = nullptr; blt::gfx::font::cleanup(); } - font_renderer_t::compiled_text_t* font_renderer_t::create_text(std::string_view str) + font_renderer_t::compiled_text_t* font_renderer_t::create_text(std::string_view str, float size) { std::unique_ptr ptr; if (!cached_text_objects.empty()) @@ -160,7 +185,7 @@ namespace blt::gfx { ptr = std::make_unique(*generator); } - ptr->change_text(str); + ptr->change_text(str, size); auto index = added_texts.size(); added_texts.push_back(std::move(ptr)); text_ptr_to_index[added_texts.back().get()] = index; @@ -176,21 +201,108 @@ namespace blt::gfx added_texts.erase(added_texts.begin() + static_cast(index)); } - void font_renderer_t::compiled_text_t::change_text(std::string_view str) + void font_renderer_t::render() + { + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + font_shader->bind(); + glActiveTexture(GL_TEXTURE0); + for (auto& text : added_texts) + { + blt::mat4x4 transform; + transform.translate(text->position); + transform.scale(text->scale); + font_shader->setMatrix("transform", transform); + font_shader->setFloat("z_index", text->z_index); + font_shader->setVec4("color", text->color); + text->draw(); + } + } + + void font_renderer_t::create_default(blt::i32 dimensions) + { + create({9, 11, 12, 13, 14, 16, 18, 24, 32, 36, 40, 48, 52, 64, 72, 96, 106}, dimensions); + + add_default_font(reinterpret_cast(font::default_font_compressed_data), font::default_font_compressed_size, true); + } + + void font_renderer_t::add_default_font(std::string_view path) + { + font::font_face_t default_font_face{path}; + font::font_file_t default_font{default_font_face, 0, 128}; + add_font(default_font); + } + + void font_renderer_t::add_default_font(const blt::u8* data, blt::size_t size, bool compressed) + { + font::font_face_t default_font_face{data, size, compressed}; + font::font_file_t default_font{default_font_face, 0, 128}; + add_font(default_font); + } + + void font_renderer_t::compiled_text_t::change_text(std::string_view str, float size) { static std::vector vertices; vertices.clear(); + renders.clear(); + font_texture_atlas* last_texture = nullptr; + blt::size_t draw_start = 0; + blt::size_t draw_count = 0; + + float x = 0; + // TODO: parker UTF8 for (const auto& c : str) { - + auto& texture = generator.get_texture(size, c); + + if (last_texture == nullptr) + last_texture = &texture; + else if (last_texture != &texture) + { + renders.emplace_back(last_texture, draw_start, draw_count); + draw_start += draw_count; + draw_count = 0; + } + + auto& ch = texture.get_glyph(c, size); + auto dims = static_cast(generator.get_dimensions()); + + float x_pos = x + static_cast(ch.glyph.bearing.x()); + float y_pos = -static_cast(ch.glyph.size.y() - ch.glyph.bearing.y()); + + auto w = static_cast(ch.glyph.size.x()); + auto h = static_cast(ch.glyph.size.y()); + + auto texture_min_x = static_cast(ch.min.x()) / dims; + auto texture_min_y = static_cast(ch.min.y()) / dims; + auto texture_max_x = static_cast(ch.max.x()) / dims; + auto texture_max_y = static_cast(ch.max.y()) / dims; + +// BLT_TRACE("%c: %f | xy[%f %f] wh[%f %f] | min[%f %f] max[%f %f] |", c, x, x_pos, y_pos, w, h, texture_min_x, texture_min_y, texture_max_x, texture_max_y); + + std::array local_vert = { + x_pos, y_pos + h, texture_min_x, texture_min_y, + x_pos, y_pos, texture_min_x, texture_max_y, + x_pos + w, y_pos, texture_max_x, texture_max_y, + + x_pos, y_pos + h, texture_min_x, texture_min_y, + x_pos + w, y_pos, texture_max_x, texture_max_y, + x_pos + w, y_pos + h, texture_max_x, texture_min_y + }; + vertices.insert(vertices.end(), local_vert.begin(), local_vert.end()); + draw_count += 6; + x += static_cast(ch.glyph.advance >> 6); } + +// BLT_TRACE("size: %ld %ld %ld", draw_count, vertices.size(), vertices.size() / 4); + + renders.emplace_back(last_texture, draw_start, draw_count); + vao.getBuffer(0).update(static_cast(vertices.size() * sizeof(float)), vertices.data()); } font_renderer_t::compiled_text_t::compiled_text_t(font_generator_t& generator): generator(generator) { - renders.emplace_back(); - auto& vao = renders.back()->vao; auto vbo = vertex_array_t::make_vbo({}); vbo->vbo.create(); vbo->vbo.bind(); @@ -201,6 +313,17 @@ namespace blt::gfx void font_renderer_t::compiled_text_t::draw() { - + font_texture_atlas* last_texture = nullptr; + vao.bind(); + for (const auto& render : renders) + { + if (last_texture != render.texture) + { + last_texture = render.texture; + render.texture->bind(); + } + + glDrawArrays(GL_TRIANGLES, static_cast(render.render_start), static_cast(render.render_count)); + } } } \ No newline at end of file