main
Brett 2024-11-11 19:21:42 -05:00
parent de454940bc
commit 635589b04d
11 changed files with 224 additions and 32 deletions

View File

@ -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})

0
build_emscript.sh Executable file → Normal file
View File

0
cloc.sh Executable file → Normal file
View File

View File

@ -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<int> load_char(FT_ULong c) const;
@ -58,7 +55,7 @@ namespace blt::gfx::font
private:
// 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;
};

View File

@ -21,6 +21,8 @@
#include <blt/gfx/font/font.h>
#include <blt/gfx/texture.h>
#include <blt/gfx/shader.h>
#include <blt/gfx/model.h>
#include <blt/std/hashmap.h>
#include <blt/std/binary_tree.h>
@ -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<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
{
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:
std::unique_ptr<font_generator_t> generator;
std::unique_ptr<shader_t> font_shader;
};
}

View File

@ -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

View File

@ -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

0
resources/fonts/a.out Executable file → Normal file
View File

View File

@ -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<FT_Face>(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<FT_Face>(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<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));
} else
{
// 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);
}
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);
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<int> font_face_t::load_char(FT_ULong c) const

View File

@ -16,20 +16,27 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#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
{
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<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_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<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}}
});
}
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();
}
}