diff --git a/CMakeLists.txt b/CMakeLists.txt index 21978ef..227e3dd 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.10) +set(BLT_GRAPHICS_VERSION 1.0.11) 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 100644 new mode 100755 diff --git a/include/blt/gfx/font/font.h b/include/blt/gfx/font/font.h index 7de16ba..420a460 100644 --- a/include/blt/gfx/font/font.h +++ b/include/blt/gfx/font/font.h @@ -40,23 +40,25 @@ namespace blt::gfx::font class font_face_t { public: - explicit font_face_t(std::string_view path); + explicit font_face_t(std::string_view path, std::string_view font_name); - explicit font_face_t(const blt::u8* data, blt::size_t size, bool compressed = false); + explicit font_face_t(std::string_view font_name, const blt::u8* data, blt::size_t size, bool compressed = false); void set_pixel_sizes(blt::u32 width, blt::u32 height) const; [[nodiscard]] std::optional load_char(FT_ULong c) const; [[nodiscard]] FT_Face* get() const - { - return face.get(); - } + { return face.get(); } + + [[nodiscard]] std::string_view get_name() const + { return font_name; } private: // decompressed data ownership std::shared_ptr internal_uncompressed_data; std::shared_ptr face; + std::string font_name; }; struct font_file_t diff --git a/include/blt/gfx/renderer/font_renderer.h b/include/blt/gfx/renderer/font_renderer.h index 6935868..4643323 100644 --- a/include/blt/gfx/renderer/font_renderer.h +++ b/include/blt/gfx/renderer/font_renderer.h @@ -53,14 +53,14 @@ namespace blt::gfx added_font_results_t add_font(const font::font_file_t& file, blt::u64 start, float size); - bounded_t& get_glyph(blt::u64 c, float size) - { return glyphs.at(size).at(c); } + bounded_t& get_glyph(blt::u64 c, std::string_view font) + { return glyphs.at(font).at(c); } - [[nodiscard]] const bounded_t& get_glyph(blt::u64 c, float size) const - { return glyphs.at(size).at(c); } + [[nodiscard]] const bounded_t& get_glyph(blt::u64 c, std::string_view font) const + { return glyphs.at(font).at(c); } private: - blt::hashmap_t> glyphs; + blt::hashmap_t> glyphs; blt::u32 used_width = 0; blt::u32 used_height = 0; blt::u32 padding; @@ -78,45 +78,41 @@ namespace blt::gfx struct bounded_font_t { - std::unique_ptr atlas; -// float min_size, max_size; -// blt::u64 min_char, max_char; - std::vector size_limits; + font_texture_atlas* atlas; + blt::u64 min_char, max_char; - explicit bounded_font_t(std::unique_ptr atlas): atlas(std::move(atlas)) + bounded_font_t(font_texture_atlas* atlas, u64 minChar, u64 maxChar): atlas(atlas), min_char(minChar), max_char(maxChar) {} -// bounded_font_t(std::unique_ptr atlas, float minSize, float maxSize, u64 minChar, u64 maxChar): -// atlas(std::move(atlas)), min_size(minSize), max_size(maxSize), min_char(minChar), max_char(maxChar) -// {} + // replace with font -> char range. use scaling. }; public: - explicit font_generator_t(blt::i32 dimensions, blt::u32 padding, const std::vector& sizes_to_generate); + explicit font_generator_t(blt::i32 dimensions, blt::u32 padding, float gen_size); void add_font(const font::font_file_t& file); - inline font_texture_atlas& get_texture(float size, blt::u64 c) + inline font_texture_atlas& get_texture(const std::string& font, blt::u64 c) { - for (auto& atlas : atlases) + for (auto& atlas : font_associations[font]) { - BLT_TRACE("(%f %ld), %f %f, %ld %ld", size, c, atlas.min_size, atlas.max_size, atlas.min_char, atlas.max_char); - if (!(size >= atlas.min_size && size <= atlas.max_size)) - continue; if (c >= atlas.min_char && c <= atlas.max_char) return *atlas.atlas; } - throw std::runtime_error("Character not found inside atlases at this size"); + throw std::runtime_error("Character not found inside atlases with font '" + font + "'"); } [[nodiscard]] blt::i32 get_dimensions() const { return dimensions; } + + [[nodiscard]] float get_generated_size() const + { return gen_size; } private: blt::i32 dimensions; blt::u32 padding; - std::vector atlases; - // doesn't make sense to store this. - std::vector sizes_to_generate; + std::vector> atlases; + blt::hashmap_t> font_associations; + float gen_size; }; class font_renderer_t @@ -126,9 +122,9 @@ namespace blt::gfx { friend font_renderer_t; public: - explicit compiled_text_t(font_generator_t& generator); + compiled_text_t& setText(std::string_view str, float size, std::string_view font = "__default"); - void change_text(std::string_view str, float size); + compiled_text_t& recompile(); compiled_text_t& setPosition(const blt::vec2f& pos) { @@ -136,9 +132,22 @@ namespace blt::gfx return *this; } - compiled_text_t& setScale(const blt::vec2f& s) + compiled_text_t& setBounds(const blt::vec2f& limit) { - scale = s; + bounds = limit; + return *this; + } + + compiled_text_t& setUnrestrictedBounds() + { + bounds = {}; + return *this; + } + + compiled_text_t& setSize(float size) + { + current_size = size; + scale = {size / generator.get_generated_size(), size / generator.get_generated_size()}; return *this; } @@ -154,12 +163,6 @@ namespace blt::gfx 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}; @@ -172,6 +175,9 @@ namespace blt::gfx return *this; } + [[nodiscard]] const vec2f& getBounds() const + { return bounds; } + [[nodiscard]] const vec2f& getPosition() const { return position; } @@ -185,6 +191,8 @@ namespace blt::gfx { return z_index; } private: + explicit compiled_text_t(font_generator_t& generator); + void draw(); struct text_render_info_t @@ -199,10 +207,10 @@ namespace blt::gfx }; font_generator_t& generator; - std::string contents; + std::string contents, font; vertex_array_t vao{}; std::vector renders; - blt::vec2f position; + blt::vec2f position, bounds; blt::vec2f scale = {1, 1}; blt::vec4 color = blt::make_color(1, 1, 1); float z_index = 0; @@ -212,9 +220,9 @@ namespace blt::gfx public: explicit font_renderer_t(); - void create_default(blt::i32 dimensions = 0); + void create_default(float gen_size = 250, blt::i32 dimensions = 0); - void create(const std::vector& generated_font_sizes, blt::i32 dimensions = 0); + void create(float gen_size, blt::i32 dimensions = 0); void cleanup(); @@ -223,11 +231,11 @@ namespace blt::gfx generator->add_font(file); } - void add_default_font(std::string_view path); + void add_default_font(std::string_view path, std::string_view name = "__default"); - void add_default_font(const blt::u8* data, blt::size_t size, bool compressed = false); + void add_default_font(const blt::u8* data, blt::size_t size, bool compressed = false, std::string_view name = "__default"); - compiled_text_t* create_text(std::string_view str, float size); + compiled_text_t* create_text(std::string_view str, float size, std::string_view font = "__default"); void destroy_text(compiled_text_t* text); diff --git a/resources/fonts/a.out b/resources/fonts/a.out old mode 100644 new mode 100755 diff --git a/src/blt/gfx/font/font.cpp b/src/blt/gfx/font/font.cpp index e96cb74..dfb9ae6 100644 --- a/src/blt/gfx/font/font.cpp +++ b/src/blt/gfx/font/font.cpp @@ -208,8 +208,9 @@ namespace blt::gfx::font FT_Done_FreeType(*lib); } - font_face_t::font_face_t(std::string_view path) + font_face_t::font_face_t(std::string_view path, std::string_view name) { + font_name = name; face = std::shared_ptr(new FT_Face{}, [](FT_Face* ptr) { FT_Done_Face(*ptr); delete ptr; @@ -224,8 +225,9 @@ namespace blt::gfx::font } } - font_face_t::font_face_t(const blt::u8* data, blt::size_t size, bool compressed) + font_face_t::font_face_t(std::string_view name, const blt::u8* data, blt::size_t size, bool compressed) { + font_name = name; face = std::shared_ptr(new FT_Face{}, [](FT_Face* ptr) { FT_Done_Face(*ptr); delete ptr; diff --git a/src/blt/gfx/renderer/font_renderer.cpp b/src/blt/gfx/renderer/font_renderer.cpp index f03d0c9..b038c98 100644 --- a/src/blt/gfx/renderer/font_renderer.cpp +++ b/src/blt/gfx/renderer/font_renderer.cpp @@ -19,6 +19,7 @@ #include #include #include +#include // TODO: signed distance fonts //#include @@ -85,7 +86,7 @@ namespace blt::gfx 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[size].insert(std::pair{i, bounded_t{ + glyphs[file.font.get_name()].insert(std::pair{i, bounded_t{ font::glyph_t{ {width, height}, {face->glyph->bitmap_left, face->glyph->bitmap_top}, @@ -98,52 +99,36 @@ namespace blt::gfx return {-1ul, min_index_used, max_index_used}; } - font_generator_t::font_generator_t(blt::i32 dimensions, blt::u32 padding, const std::vector& sizes_to_generate): - dimensions(dimensions), padding(padding), sizes_to_generate(sizes_to_generate) + font_generator_t::font_generator_t(blt::i32 dimensions, blt::u32 padding, float gen_size): + dimensions(dimensions), padding(padding), gen_size(gen_size) {} void font_generator_t::add_font(const font::font_file_t& file) { glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - float min_size = std::numeric_limits::max(); - float max_size = 0; - for (const auto& size : sizes_to_generate) + auto& vec = atlases; + + if (atlases.empty()) + atlases.emplace_back(std::make_unique(dimensions, padding)); + auto& font_vec = font_associations[file.font.get_name()]; + + blt::u64 start_index = file.character_min_index; + while (start_index != -1ul) { - min_size = std::min(size, min_size); - max_size = std::max(size, max_size); - auto& vec = atlases; - - if (vec.empty()) - vec.emplace_back(std::make_unique(dimensions, padding), min_size, max_size, file.character_min_index, 0); - - blt::u64 start_index = file.character_min_index; - while (start_index != -1ul) - { - 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; - if (results.last_inserted_index != -1ul) - { - vec.emplace_back(std::make_unique(dimensions, padding), 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); - } + // add font and associate results with a font file + auto results = atlases.back()->add_font(file, start_index, gen_size); + font_vec.emplace_back(atlases.back().get(), results.min_index_used, results.max_index_used); + // didn't fit, need to push new atlas + if (results.last_inserted_index != -1ul) + vec.emplace_back(std::make_unique(dimensions, padding)); + start_index = results.last_inserted_index; } glPixelStorei(GL_UNPACK_ALIGNMENT, 4); -// BLT_TRACE(atlases.size()); } - extern float square_vertices[20]; - extern const unsigned int square_indices[6]; - font_renderer_t::font_renderer_t() = default; - void font_renderer_t::create(const std::vector& generated_font_sizes, blt::i32 dimensions) + void font_renderer_t::create(float gen_size, blt::i32 dimensions) { font_shader = shader_t::make_unique(shader_2d_font_vert, shader_2d_font_frag); font_shader->bind(); @@ -156,7 +141,14 @@ namespace blt::gfx dimensions = std::min(size, 2048); BLT_INFO("Max texture size %d, font renderer will use size %d", size, dimensions); } - generator = std::make_unique(dimensions, 2, generated_font_sizes); + generator = std::make_unique(dimensions, 2, gen_size); + } + + void font_renderer_t::create_default(float gen_size, blt::i32 dimensions) + { + create(gen_size, dimensions); + + add_default_font(reinterpret_cast(font::default_font_compressed_data), font::default_font_compressed_size, true); } void font_renderer_t::cleanup() @@ -173,7 +165,7 @@ namespace blt::gfx blt::gfx::font::cleanup(); } - font_renderer_t::compiled_text_t* font_renderer_t::create_text(std::string_view str, float size) + font_renderer_t::compiled_text_t* font_renderer_t::create_text(std::string_view str, float size, std::string_view font) { std::unique_ptr ptr; if (!cached_text_objects.empty()) @@ -182,9 +174,9 @@ namespace blt::gfx cached_text_objects.erase(cached_text_objects.begin()); } else { - ptr = std::make_unique(*generator); + ptr = std::unique_ptr(new compiled_text_t{*generator}); } - ptr->change_text(str, size); + ptr->setText(str, size, font); auto index = added_texts.size(); added_texts.push_back(std::move(ptr)); text_ptr_to_index[added_texts.back().get()] = index; @@ -218,90 +210,30 @@ namespace blt::gfx } } - void font_renderer_t::create_default(blt::i32 dimensions) + void font_renderer_t::add_default_font(std::string_view path, std::string_view name) { - create({9, 11, 12, 13, 14, 16, 18, 24, 32, 36, 40, 48, 52, 64, 72, 96, 106, 250}, 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_face_t default_font_face{path, name}; 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) + void font_renderer_t::add_default_font(const blt::u8* data, blt::size_t size, bool compressed, std::string_view name) { - font::font_face_t default_font_face{data, size, compressed}; + font::font_face_t default_font_face{name, 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) + font_renderer_t::compiled_text_t& font_renderer_t::compiled_text_t::setText(std::string_view str, float size, std::string_view f) { if (str == contents) - return; + return *this; + contents = str; - current_size = 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 = f; + setSize(size); + recompile(); + return *this; } font_renderer_t::compiled_text_t::compiled_text_t(font_generator_t& generator): generator(generator) @@ -330,5 +262,115 @@ namespace blt::gfx } } + struct word_t + { + std::vector vertices; + float x = 0; + + void apply(std::vector& out) + { + for (float v : vertices) + out.push_back(v + x); + vertices.clear(); + x = 0; + } + }; + + struct position_tracker_t + { + float global_x = 0, global_y = 0; + + void newline(float line_size) + { + global_y -= line_size; + global_x = 0; + } + + void advance(blt::u64 size) + { + global_x += static_cast(size >> 6); + } + }; + + font_renderer_t::compiled_text_t& font_renderer_t::compiled_text_t::recompile() + { + static std::vector vertices; + static word_t current_word; + vertices.clear(); + renders.clear(); + + font_texture_atlas* last_texture = nullptr; + blt::size_t draw_start = 0; + blt::size_t draw_count = 0; + + position_tracker_t current_pos; + // TODO: parker UTF8 + for (const auto& c : contents) + { +// std::cout << "C: " << c << " || " << static_cast(c) << std::endl; + if (c == '\n') + { + current_pos.newline(generator.get_generated_size()); + continue; + } + auto& texture = generator.get_texture(std::string(font), c); + auto& ch = texture.get_glyph(c, font); + +// if (std::isblank(c)) +// { +// if (bounds.x() > 0 && current_pos.global_x + current_word.x > bounds.x()) +// current_pos.newline(generator.get_generated_size()); +// +// } + if (std::isspace(c)) + current_word.apply(vertices); + + 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 dims = static_cast(generator.get_dimensions()); + + float x_pos = current_pos.global_x + static_cast(ch.glyph.bearing.x()); + float y_pos = current_pos.global_y - 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 + }; + current_word.vertices.insert(current_word.vertices.end(), local_vert.begin(), local_vert.end()); + + draw_count += 6; + current_pos.advance(ch.glyph.advance); + } + current_word.apply(vertices); + +// 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()); + return *this; + } + } \ No newline at end of file