fonts
parent
de454940bc
commit
635589b04d
|
@ -1,7 +1,7 @@
|
||||||
cmake_minimum_required(VERSION 3.25)
|
cmake_minimum_required(VERSION 3.25)
|
||||||
include(FetchContent)
|
include(FetchContent)
|
||||||
|
|
||||||
set(BLT_GRAPHICS_VERSION 1.0.4)
|
set(BLT_GRAPHICS_VERSION 1.0.5)
|
||||||
set(BLT_GRAPHICS_TEST_VERSION 0.0.1)
|
set(BLT_GRAPHICS_TEST_VERSION 0.0.1)
|
||||||
|
|
||||||
project(BLT_WITH_GRAPHICS VERSION ${BLT_GRAPHICS_VERSION})
|
project(BLT_WITH_GRAPHICS VERSION ${BLT_GRAPHICS_VERSION})
|
||||||
|
|
|
@ -33,21 +33,18 @@ namespace blt::gfx::font
|
||||||
extern const unsigned int default_font_compressed_data[];
|
extern const unsigned int default_font_compressed_data[];
|
||||||
extern const unsigned int default_font_compressed_size;
|
extern const unsigned int default_font_compressed_size;
|
||||||
|
|
||||||
FT_Library* init();
|
FT_Library* create();
|
||||||
|
|
||||||
inline void destroy(FT_Library* lib)
|
void cleanup(FT_Library* lib = nullptr);
|
||||||
{
|
|
||||||
FT_Done_FreeType(*lib);
|
|
||||||
}
|
|
||||||
|
|
||||||
class font_face_t
|
class font_face_t
|
||||||
{
|
{
|
||||||
public:
|
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<int> load_char(FT_ULong c) const;
|
[[nodiscard]] std::optional<int> load_char(FT_ULong c) const;
|
||||||
|
|
||||||
|
@ -58,7 +55,7 @@ namespace blt::gfx::font
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// decompressed data ownership
|
// decompressed data ownership
|
||||||
std::shared_ptr<blt::u8> internal_uncompressed_data;
|
std::shared_ptr<blt::u8[]> internal_uncompressed_data;
|
||||||
std::shared_ptr<FT_Face> face;
|
std::shared_ptr<FT_Face> face;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,8 @@
|
||||||
|
|
||||||
#include <blt/gfx/font/font.h>
|
#include <blt/gfx/font/font.h>
|
||||||
#include <blt/gfx/texture.h>
|
#include <blt/gfx/texture.h>
|
||||||
|
#include <blt/gfx/shader.h>
|
||||||
|
#include <blt/gfx/model.h>
|
||||||
#include <blt/std/hashmap.h>
|
#include <blt/std/hashmap.h>
|
||||||
#include <blt/std/binary_tree.h>
|
#include <blt/std/binary_tree.h>
|
||||||
|
|
||||||
|
@ -33,15 +35,23 @@ namespace blt::gfx
|
||||||
struct bounded_t
|
struct bounded_t
|
||||||
{
|
{
|
||||||
font::glyph_t glyph;
|
font::glyph_t glyph;
|
||||||
|
// starting point
|
||||||
blt::vec2ui min;
|
blt::vec2ui min;
|
||||||
|
// ending point of the glyph, inside texture atlas
|
||||||
blt::vec2ui max;
|
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);
|
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)
|
bounded_t& get_glyph(blt::u64 c)
|
||||||
{ return glyphs.at(c); }
|
{ return glyphs.at(c); }
|
||||||
|
@ -55,12 +65,68 @@ namespace blt::gfx
|
||||||
blt::u32 used_height = 0;
|
blt::u32 used_height = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class font_generator_t
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
struct bounded_font_t
|
||||||
|
{
|
||||||
|
std::unique_ptr<font_texture_atlas> atlas;
|
||||||
|
float min_size, max_size;
|
||||||
|
blt::u64 min_char, max_char;
|
||||||
|
|
||||||
|
bounded_font_t(std::unique_ptr<font_texture_atlas> 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<float>& 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<bounded_font_t> atlases;
|
||||||
|
std::vector<float> sizes_to_generate;
|
||||||
|
};
|
||||||
|
|
||||||
class font_renderer_t
|
class font_renderer_t
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit font_renderer_t(const std::vector<font::font_file_t>& files, blt::i32 dimensions = 4096);
|
struct compiled_text_t
|
||||||
|
{
|
||||||
|
std::shared_ptr<vertex_array_t> vao;
|
||||||
|
blt::size_t render_count;
|
||||||
|
};
|
||||||
|
public:
|
||||||
|
explicit font_renderer_t();
|
||||||
|
|
||||||
|
void create(const std::vector<float>& 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:
|
private:
|
||||||
|
std::unique_ptr<font_generator_t> generator;
|
||||||
|
std::unique_ptr<shader_t> font_shader;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
#ifdef __cplusplus
|
||||||
|
#include <string>
|
||||||
|
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
|
|
@ -0,0 +1,28 @@
|
||||||
|
#ifdef __cplusplus
|
||||||
|
#include <string>
|
||||||
|
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
|
|
@ -1 +1 @@
|
||||||
Subproject commit 05e5fcf7f1091d6c85711ff7ce53d840c17e547b
|
Subproject commit 1c931b2515ce83eb38a1e532d15ee11d99c67d18
|
|
@ -186,9 +186,10 @@ namespace blt::gfx::font
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FT_Library* init()
|
|
||||||
{
|
|
||||||
static FT_Library ft;
|
static FT_Library ft;
|
||||||
|
|
||||||
|
FT_Library* create()
|
||||||
|
{
|
||||||
if (int err = FT_Init_FreeType(&ft))
|
if (int err = FT_Init_FreeType(&ft))
|
||||||
{
|
{
|
||||||
BLT_FATAL("Failed to init FreeType library!");
|
BLT_FATAL("Failed to init FreeType library!");
|
||||||
|
@ -200,13 +201,20 @@ namespace blt::gfx::font
|
||||||
return &ft;
|
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<FT_Face>(new FT_Face{}, [](FT_Face* ptr) {
|
face = std::shared_ptr<FT_Face>(new FT_Face{}, [](FT_Face* ptr) {
|
||||||
FT_Done_Face(*ptr);
|
FT_Done_Face(*ptr);
|
||||||
delete 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);
|
auto str = FT_Error_String(err);
|
||||||
if (str != nullptr)
|
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<FT_Face>(new FT_Face{}, [](FT_Face* ptr) {
|
face = std::shared_ptr<FT_Face>(new FT_Face{}, [](FT_Face* ptr) {
|
||||||
FT_Done_Face(*ptr);
|
FT_Done_Face(*ptr);
|
||||||
|
@ -227,17 +235,17 @@ namespace blt::gfx::font
|
||||||
if (compressed)
|
if (compressed)
|
||||||
{
|
{
|
||||||
uncompressed_size = stb_decompress_length((const unsigned char*) data);
|
uncompressed_size = stb_decompress_length((const unsigned char*) data);
|
||||||
internal_uncompressed_data = std::shared_ptr<blt::u8>(new blt::u8[uncompressed_size]);
|
internal_uncompressed_data = std::shared_ptr<blt::u8[]>(new blt::u8[uncompressed_size]);
|
||||||
stb_decompress(internal_uncompressed_data.get(), static_cast<const unsigned char*>(data), static_cast<unsigned int>(size));
|
stb_decompress(internal_uncompressed_data.get(), static_cast<const unsigned char*>(data), static_cast<unsigned int>(size));
|
||||||
} else
|
} else
|
||||||
{
|
{
|
||||||
// make an owning copy which will always exist as long as this class does.
|
// make an owning copy which will always exist as long as this class does.
|
||||||
internal_uncompressed_data = std::shared_ptr<blt::u8>(new blt::u8[size]);
|
internal_uncompressed_data = std::shared_ptr<blt::u8[]>(new blt::u8[size]);
|
||||||
std::memcpy(internal_uncompressed_data.get(), data, size);
|
std::memcpy(internal_uncompressed_data.get(), data, size);
|
||||||
}
|
}
|
||||||
blt::u8* uncompressed_data = internal_uncompressed_data.get();
|
blt::u8* uncompressed_data = internal_uncompressed_data.get();
|
||||||
|
|
||||||
if (int err = FT_New_Memory_Face(*lib, uncompressed_data, static_cast<FT_Long>(uncompressed_size), 0, face.get()))
|
if (int err = FT_New_Memory_Face(ft, uncompressed_data, static_cast<FT_Long>(uncompressed_size), 0, face.get()))
|
||||||
{
|
{
|
||||||
auto str = FT_Error_String(err);
|
auto str = FT_Error_String(err);
|
||||||
if (str != nullptr)
|
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))
|
if (FT_Set_Pixel_Sizes(*face, width, height))
|
||||||
throw std::runtime_error("Failed to set pixel sizes!");
|
throw std::runtime_error("Failed to set pixel sizes!");
|
||||||
return *this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<int> font_face_t::load_char(FT_ULong c) const
|
std::optional<int> font_face_t::load_char(FT_ULong c) const
|
||||||
|
|
|
@ -16,20 +16,27 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
#include <blt/gfx/renderer/font_renderer.h>
|
#include <blt/gfx/renderer/font_renderer.h>
|
||||||
|
#include <blt/gfx/renderer/shaders/2d_font.vert>
|
||||||
|
#include <blt/gfx/renderer/shaders/2d_font.frag>
|
||||||
|
#include "blt/gfx/shader.h"
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
namespace blt::gfx
|
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);
|
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::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++)
|
for (auto i = start; i < file.character_max_index; i++)
|
||||||
{
|
{
|
||||||
// returns an empty optional if there is no error code
|
// 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 we can't fit the height of this character we should move to the next texture atlas.
|
||||||
if (used_height + height > static_cast<blt::u32>(getHeight()))
|
if (used_height + height > static_cast<blt::u32>(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_width = used_width;
|
||||||
auto begin_height = used_height;
|
auto begin_height = used_height;
|
||||||
auto end_width = used_width + width;
|
auto end_width = used_width + width;
|
||||||
auto end_height = used_height + height;
|
auto end_height = used_height + height;
|
||||||
|
used_width += width;
|
||||||
|
|
||||||
// upload the texture
|
// upload the texture
|
||||||
upload(face->glyph->bitmap.buffer, GL_RED, 0, static_cast<blt::i32>(begin_width), static_cast<blt::i32>(begin_height),
|
upload(face->glyph->bitmap.buffer, GL_RED, 0, static_cast<blt::i32>(begin_width), static_cast<blt::i32>(begin_height),
|
||||||
|
@ -70,11 +80,77 @@ namespace blt::gfx
|
||||||
{begin_width, begin_height}, {end_width, end_height}}
|
{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<font::font_file_t>& files, blt::i32 dimensions)
|
font_generator_t::font_generator_t(blt::i32 dimensions, const std::vector<float>& 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<float>::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<blt::u32>(size));
|
||||||
|
|
||||||
|
if (vec.empty())
|
||||||
|
vec.emplace_back(std::make_unique<font_texture_atlas>(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<font_texture_atlas>(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<float>& 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<font_generator_t>(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();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
|
Loading…
Reference in New Issue