diff --git a/CMakeLists.txt b/CMakeLists.txt index b590688..e1833fd 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.4) +set(BLT_GRAPHICS_VERSION 1.0.5) 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/font/font.h b/include/blt/gfx/font/font.h index 6924d39..7de16ba 100644 --- a/include/blt/gfx/font/font.h +++ b/include/blt/gfx/font/font.h @@ -33,21 +33,18 @@ namespace blt::gfx::font extern const unsigned int default_font_compressed_data[]; extern const unsigned int default_font_compressed_size; - FT_Library* init(); + FT_Library* create(); - inline void destroy(FT_Library* lib) - { - FT_Done_FreeType(*lib); - } + void cleanup(FT_Library* lib = nullptr); class font_face_t { public: - explicit font_face_t(FT_Library* lib, std::string_view path); + explicit font_face_t(std::string_view path); - explicit font_face_t(FT_Library* lib, blt::u8* data, blt::size_t size, bool compressed = false); + explicit font_face_t(const blt::u8* data, blt::size_t size, bool compressed = false); - font_face_t& set_pixel_sizes(blt::u32 width, blt::u32 height); + void set_pixel_sizes(blt::u32 width, blt::u32 height) const; [[nodiscard]] std::optional load_char(FT_ULong c) const; @@ -58,7 +55,7 @@ namespace blt::gfx::font private: // decompressed data ownership - std::shared_ptr internal_uncompressed_data; + std::shared_ptr internal_uncompressed_data; std::shared_ptr face; }; diff --git a/include/blt/gfx/renderer/font_renderer.h b/include/blt/gfx/renderer/font_renderer.h index 7925615..9b7db17 100644 --- a/include/blt/gfx/renderer/font_renderer.h +++ b/include/blt/gfx/renderer/font_renderer.h @@ -21,6 +21,8 @@ #include #include +#include +#include #include #include @@ -33,15 +35,23 @@ namespace blt::gfx struct bounded_t { font::glyph_t glyph; + // starting point blt::vec2ui min; + // ending point of the glyph, inside texture atlas blt::vec2ui max; }; - + struct added_font_results_t + { + blt::u64 last_inserted_index; + blt::u64 min_index_used; + blt::u64 max_index_used; + }; + public: explicit font_texture_atlas(blt::i32 dimensions); - blt::u64 add_font(const font::font_file_t& file); + added_font_results_t add_font(const font::font_file_t& file); - blt::u64 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); bounded_t& get_glyph(blt::u64 c) { return glyphs.at(c); } @@ -55,12 +65,68 @@ namespace blt::gfx blt::u32 used_height = 0; }; + class font_generator_t + { + public: + struct bounded_font_t + { + std::unique_ptr atlas; + float min_size, max_size; + blt::u64 min_char, max_char; + + 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) + {} + }; + + public: + explicit font_generator_t(blt::i32 dimensions, const std::vector& sizes_to_generate); + + void add_font(const font::font_file_t& file); + + inline font_texture_atlas& get_texture(float size, blt::u64 c) + { + for (auto& atlas : atlases) + { + if (!(size >= atlas.min_size && size <= atlas.max_size)) + continue; + if (atlas.min_char > c && atlas.max_char < c) + return *atlas.atlas; + } + throw std::runtime_error("Character not found inside atlases at this size"); + } + + private: + blt::i32 dimensions; + std::vector atlases; + std::vector sizes_to_generate; + }; + class font_renderer_t { public: - explicit font_renderer_t(const std::vector& files, blt::i32 dimensions = 4096); + struct compiled_text_t + { + std::shared_ptr vao; + blt::size_t render_count; + }; + public: + explicit font_renderer_t(); + + void create(const std::vector& generated_font_sizes, blt::i32 dimensions = 0); + + void cleanup(); + + void add_font(const font::font_file_t& file) + { + generator->add_font(file); + } + + compiled_text_t create_text(std::string_view str); private: + std::unique_ptr generator; + std::unique_ptr font_shader; }; } diff --git a/include/blt/gfx/renderer/shaders/2d_font.frag b/include/blt/gfx/renderer/shaders/2d_font.frag new file mode 100644 index 0000000..fa56cdb --- /dev/null +++ b/include/blt/gfx/renderer/shaders/2d_font.frag @@ -0,0 +1,18 @@ +#ifdef __cplusplus +#include +const std::string shader_2d_font_frag = R"(" +#version 300 es +precision mediump float; + +layout (location = 0) out vec4 FragColor; +in vec2 uv; + +uniform sampler2D tex; +uniform vec4 color; + +void main() { + FragColor = texture(tex, uv).r * color; +} + +")"; +#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 new file mode 100644 index 0000000..70636bc --- /dev/null +++ b/include/blt/gfx/renderer/shaders/2d_font.vert @@ -0,0 +1,28 @@ +#ifdef __cplusplus +#include +const std::string shader_2d_font_vert = R"(" +#version 300 es +precision mediump float; + +layout (location = 0) in vec4 vertex; + +out vec2 uv; + +layout (std140) uniform GlobalMatrices +{ + mat4 projection; + mat4 ortho; + mat4 view; + mat4 pvm; + mat4 ovm; +}; + +uniform float z_index; + +void main() { + gl_Position = ovm * vec4(vertex.xy, z_index, 1.0); + uv = vertex.zw; +} + +")"; +#endif \ No newline at end of file diff --git a/libraries/BLT b/libraries/BLT index 05e5fcf..1c931b2 160000 --- a/libraries/BLT +++ b/libraries/BLT @@ -1 +1 @@ -Subproject commit 05e5fcf7f1091d6c85711ff7ce53d840c17e547b +Subproject commit 1c931b2515ce83eb38a1e532d15ee11d99c67d18 diff --git a/resources/fonts/a.out b/resources/fonts/a.out old mode 100755 new mode 100644 diff --git a/src/blt/gfx/font/font.cpp b/src/blt/gfx/font/font.cpp index 721d72f..e96cb74 100644 --- a/src/blt/gfx/font/font.cpp +++ b/src/blt/gfx/font/font.cpp @@ -186,9 +186,10 @@ namespace blt::gfx::font } } - FT_Library* init() + static FT_Library ft; + + FT_Library* create() { - static FT_Library ft; if (int err = FT_Init_FreeType(&ft)) { BLT_FATAL("Failed to init FreeType library!"); @@ -200,13 +201,20 @@ namespace blt::gfx::font return &ft; } - font_face_t::font_face_t(FT_Library* lib, std::string_view path) + void cleanup(FT_Library* lib) + { + if (lib == nullptr) + lib = &ft; + FT_Done_FreeType(*lib); + } + + font_face_t::font_face_t(std::string_view path) { face = std::shared_ptr(new FT_Face{}, [](FT_Face* ptr) { FT_Done_Face(*ptr); delete ptr; }); - if (int err = FT_New_Face(*lib, std::string(path).c_str(), 0, face.get())) + if (int err = FT_New_Face(ft, std::string(path).c_str(), 0, face.get())) { auto str = FT_Error_String(err); if (str != nullptr) @@ -216,7 +224,7 @@ namespace blt::gfx::font } } - font_face_t::font_face_t(FT_Library* lib, blt::u8* data, blt::size_t size, bool compressed) + font_face_t::font_face_t(const blt::u8* data, blt::size_t size, bool compressed) { face = std::shared_ptr(new FT_Face{}, [](FT_Face* ptr) { FT_Done_Face(*ptr); @@ -227,17 +235,17 @@ namespace blt::gfx::font if (compressed) { uncompressed_size = stb_decompress_length((const unsigned char*) data); - internal_uncompressed_data = std::shared_ptr(new blt::u8[uncompressed_size]); + internal_uncompressed_data = std::shared_ptr(new blt::u8[uncompressed_size]); stb_decompress(internal_uncompressed_data.get(), static_cast(data), static_cast(size)); } else { // make an owning copy which will always exist as long as this class does. - internal_uncompressed_data = std::shared_ptr(new blt::u8[size]); + internal_uncompressed_data = std::shared_ptr(new blt::u8[size]); std::memcpy(internal_uncompressed_data.get(), data, size); } blt::u8* uncompressed_data = internal_uncompressed_data.get(); - if (int err = FT_New_Memory_Face(*lib, uncompressed_data, static_cast(uncompressed_size), 0, face.get())) + if (int err = FT_New_Memory_Face(ft, uncompressed_data, static_cast(uncompressed_size), 0, face.get())) { auto str = FT_Error_String(err); if (str != nullptr) @@ -246,11 +254,10 @@ namespace blt::gfx::font } } - font_face_t& font_face_t::set_pixel_sizes(blt::u32 width, blt::u32 height) + void font_face_t::set_pixel_sizes(blt::u32 width, blt::u32 height) const { if (FT_Set_Pixel_Sizes(*face, width, height)) throw std::runtime_error("Failed to set pixel sizes!"); - return *this; } std::optional font_face_t::load_char(FT_ULong c) const diff --git a/src/blt/gfx/renderer/font_renderer.cpp b/src/blt/gfx/renderer/font_renderer.cpp index 8c32ec2..30747cc 100644 --- a/src/blt/gfx/renderer/font_renderer.cpp +++ b/src/blt/gfx/renderer/font_renderer.cpp @@ -16,20 +16,27 @@ * along with this program. If not, see . */ #include +#include +#include +#include "blt/gfx/shader.h" + +#include namespace blt::gfx { - font_texture_atlas::font_texture_atlas(blt::i32 dimensions): texture_gl2D(dimensions, dimensions, GL_RED) + font_texture_atlas::font_texture_atlas(blt::i32 dimensions): texture_gl2D(dimensions, dimensions, GL_R8) {} - blt::u64 font_texture_atlas::add_font(const font::font_file_t& file) + 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); } - blt::u64 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, blt::u64 start) { blt::u32 max_height = 0; + blt::u64 min_index_used = file.character_max_index; + blt::u64 max_index_used = start; for (auto i = start; i < file.character_max_index; i++) { // returns an empty optional if there is no error code @@ -51,12 +58,15 @@ namespace blt::gfx // if we can't fit the height of this character we should move to the next texture atlas. if (used_height + height > static_cast(getHeight())) - return i; + return {i, min_index_used, max_index_used}; + min_index_used = std::min(min_index_used, i); + max_index_used = std::max(max_index_used, i); auto begin_width = used_width; auto begin_height = used_height; auto end_width = used_width + width; auto end_height = used_height + height; + used_width += width; // upload the texture upload(face->glyph->bitmap.buffer, GL_RED, 0, static_cast(begin_width), static_cast(begin_height), @@ -70,11 +80,77 @@ namespace blt::gfx {begin_width, begin_height}, {end_width, end_height}} }); } - return -1; + return {-1ul, min_index_used, max_index_used}; } - font_renderer_t::font_renderer_t(const std::vector& files, blt::i32 dimensions) + font_generator_t::font_generator_t(blt::i32 dimensions, const std::vector& sizes_to_generate): + dimensions(dimensions), sizes_to_generate(sizes_to_generate) + {} + + 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) + { + 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); + + blt::u64 start_index = file.character_min_index; + while (start_index != -1ul) + { + auto results = vec.back().atlas->add_font(file, start_index); + 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); + start_index = results.last_inserted_index; +// BLT_INFO("%f, %ld", size, start_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() + {} + + 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::create(); + if (dimensions == 0) + { + int size; + glGetIntegerv(GL_MAX_TEXTURE_SIZE, &size); + dimensions = std::min(size, 4096); + BLT_INFO("Max texture size %d, font renderer will use size %d", size, dimensions); + } + generator = std::make_unique(dimensions, generated_font_sizes); + } + + void font_renderer_t::cleanup() + { + generator = nullptr; + blt::gfx::font::cleanup(); + } + + font_renderer_t::compiled_text_t font_renderer_t::create_text(std::string_view str) + { + return font_renderer_t::compiled_text_t(); + } + } \ No newline at end of file