font rendering works now

main
Brett 2024-11-12 18:26:34 -05:00
parent 29a7923269
commit b84d29ee9b
8 changed files with 245 additions and 48 deletions

View File

@ -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.7) set(BLT_GRAPHICS_VERSION 1.0.8)
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})

0
build_emscript.sh Executable file → Normal file
View File

0
cloc.sh Executable file → Normal file
View File

View File

@ -35,7 +35,7 @@ namespace blt::gfx
struct bounded_t struct bounded_t
{ {
font::glyph_t glyph; font::glyph_t glyph;
// starting point // starting point inside texture atlas
blt::vec2ui min; blt::vec2ui min;
// ending point of the glyph, inside texture atlas // ending point of the glyph, inside texture atlas
blt::vec2ui max; blt::vec2ui max;
@ -49,18 +49,18 @@ namespace blt::gfx
public: public:
explicit font_texture_atlas(blt::i32 dimensions); 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) bounded_t& get_glyph(blt::u64 c, float size)
{ return glyphs.at(c); } { return glyphs.at(size).at(c); }
[[nodiscard]] const bounded_t& get_glyph(blt::u64 c) const [[nodiscard]] const bounded_t& get_glyph(blt::u64 c, float size) const
{ return glyphs.at(c); } { return glyphs.at(size).at(c); }
private: private:
blt::hashmap_t<blt::u64, bounded_t> glyphs; blt::hashmap_t<float, blt::hashmap_t<blt::u64, bounded_t>> glyphs;
blt::u32 used_width = 0; blt::u32 used_width = 0;
blt::u32 used_height = 0; blt::u32 used_height = 0;
}; };
@ -90,12 +90,15 @@ namespace blt::gfx
{ {
if (!(size >= atlas.min_size && size <= atlas.max_size)) if (!(size >= atlas.min_size && size <= atlas.max_size))
continue; continue;
if (atlas.min_char > c && atlas.max_char < c) if (c >= atlas.min_char && c <= atlas.max_char)
return *atlas.atlas; return *atlas.atlas;
} }
throw std::runtime_error("Character not found inside atlases at this size"); throw std::runtime_error("Character not found inside atlases at this size");
} }
[[nodiscard]] blt::i32 get_dimensions() const
{ return dimensions; }
private: private:
blt::i32 dimensions; blt::i32 dimensions;
std::vector<bounded_font_t> atlases; std::vector<bounded_font_t> atlases;
@ -111,27 +114,89 @@ namespace blt::gfx
public: public:
explicit compiled_text_t(font_generator_t& generator); 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: private:
void draw(); void draw();
struct text_render_info_t struct text_render_info_t
{ {
vertex_array_t vao{}; font_texture_atlas* texture = nullptr;
font_texture_atlas* texture{}; blt::size_t render_start = 0;
blt::size_t render_count = 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; font_generator_t& generator;
std::vector<std::unique_ptr<text_render_info_t>> renders; vertex_array_t vao{};
std::vector<text_render_info_t> renders;
blt::vec2f position;
blt::vec2f scale = {1, 1};
blt::vec4 color = blt::make_color(1, 1, 1);
float z_index = 0;
}; };
public: public:
explicit font_renderer_t(); explicit font_renderer_t();
void create_default(blt::i32 dimensions = 0);
void create(const std::vector<float>& generated_font_sizes, blt::i32 dimensions = 0); void create(const std::vector<float>& generated_font_sizes, blt::i32 dimensions = 0);
void cleanup(); void cleanup();
@ -141,10 +206,16 @@ namespace blt::gfx
generator->add_font(file); 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 destroy_text(compiled_text_t* text);
void render();
private: private:
std::unique_ptr<font_generator_t> generator; std::unique_ptr<font_generator_t> generator;
std::unique_ptr<shader_t> font_shader; std::unique_ptr<shader_t> font_shader;

View File

@ -1,18 +1,20 @@
#ifdef __cplusplus #ifdef __cplusplus
#include <string> #include <string>
const std::string shader_2d_font_frag = R"(" const std::string shader_2d_font_frag = R"("
#version 300 es #version 300 es
precision mediump float; precision mediump float;
layout (location = 0) out vec4 FragColor; layout (location = 0) out vec4 FragColor;
in vec2 uv; in vec2 uv;
uniform sampler2D tex; uniform sampler2D tex;
uniform vec4 color; uniform vec4 color;
void main() { void main() {
FragColor = texture(tex, uv).r * color; FragColor = texture(tex, uv).r * color;
} if (FragColor.a < 0.2)
discard;
}
")"; ")";
#endif #endif

View File

@ -18,9 +18,10 @@ layout (std140) uniform GlobalMatrices
}; };
uniform float z_index; uniform float z_index;
uniform mat4 transform;
void main() { 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; uv = vertex.zw;
} }

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

View File

@ -25,18 +25,27 @@
namespace blt::gfx namespace blt::gfx
{ {
font_texture_atlas::font_texture_atlas(blt::i32 dimensions): texture_gl2D(dimensions, dimensions, GL_R8) 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::u32 max_height = 0;
blt::u64 min_index_used = file.character_max_index; blt::u64 min_index_used = file.character_max_index;
blt::u64 max_index_used = start; blt::u64 max_index_used = start;
file.font.set_pixel_sizes(0, static_cast<blt::u32>(size));
const blt::u32 padding = 2;
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
@ -46,10 +55,14 @@ namespace blt::gfx
auto width = face->glyph->bitmap.width; auto width = face->glyph->bitmap.width;
auto height = face->glyph->bitmap.rows; 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 adding this width overflows, we need to move to the next row.
if (used_width + width > static_cast<blt::u32>(getWidth())) if (used_width + texture_width > static_cast<blt::u32>(getWidth()))
{ {
used_height += max_height; used_height += max_height;
max_height = 0; max_height = 0;
@ -66,14 +79,14 @@ namespace blt::gfx
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; used_width += texture_width;
// BLT_TRACE("%d %d %d %d", begin_width, begin_height, width, height); // BLT_TRACE("%d %d %d %d", begin_width, begin_height, width, height);
// 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),
static_cast<blt::i32>(width), static_cast<blt::i32>(height), GL_UNSIGNED_BYTE); static_cast<blt::i32>(width), static_cast<blt::i32>(height), GL_UNSIGNED_BYTE);
glyphs.insert(std::pair{i, bounded_t{ glyphs[size].insert(std::pair{i, bounded_t{
font::glyph_t{ font::glyph_t{
{width, height}, {width, height},
{face->glyph->bitmap_left, face->glyph->bitmap_top}, {face->glyph->bitmap_left, face->glyph->bitmap_top},
@ -81,6 +94,8 @@ namespace blt::gfx
{begin_width, begin_height}, {end_width, end_height}} {begin_width, begin_height}, {end_width, end_height}}
}); });
} }
bind();
generateMipmaps();
return {-1ul, min_index_used, max_index_used}; return {-1ul, min_index_used, max_index_used};
} }
@ -98,7 +113,6 @@ namespace blt::gfx
min_size = std::min(size, min_size); min_size = std::min(size, min_size);
max_size = std::max(size, max_size); max_size = std::max(size, max_size);
auto& vec = atlases; auto& vec = atlases;
file.font.set_pixel_sizes(0, static_cast<blt::u32>(size));
if (vec.empty()) if (vec.empty())
vec.emplace_back(std::make_unique<font_texture_atlas>(dimensions), min_size, max_size, file.character_min_index, 0); vec.emplace_back(std::make_unique<font_texture_atlas>(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; blt::u64 start_index = file.character_min_index;
while (start_index != -1ul) 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().min_char = results.min_index_used;
vec.back().max_char = results.max_index_used; vec.back().max_char = results.max_index_used;
vec.back().min_size = min_size; vec.back().min_size = min_size;
vec.back().max_size = max_size; vec.back().max_size = max_size;
min_size = size;
max_size = size;
if (results.last_inserted_index != -1ul) if (results.last_inserted_index != -1ul)
{
vec.emplace_back(std::make_unique<font_texture_atlas>(dimensions), min_size, max_size, start_index, 0); vec.emplace_back(std::make_unique<font_texture_atlas>(dimensions), min_size, max_size, start_index, 0);
min_size = size;
max_size = size;
}
start_index = results.last_inserted_index; start_index = results.last_inserted_index;
// BLT_INFO("%f, %ld", size, start_index); // BLT_INFO("%f, %ld", size, start_index);
} }
@ -126,12 +142,13 @@ namespace blt::gfx
extern float square_vertices[20]; extern float square_vertices[20];
extern const unsigned int square_indices[6]; 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<float>& generated_font_sizes, blt::i32 dimensions) 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_shader = shader_t::make_unique(shader_2d_font_vert, shader_2d_font_frag);
font_shader->bind();
font_shader->bindAttribute(0, "vertex");
font::create(); font::create();
if (dimensions == 0) if (dimensions == 0)
{ {
@ -145,11 +162,19 @@ namespace blt::gfx
void font_renderer_t::cleanup() 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; generator = nullptr;
blt::gfx::font::cleanup(); 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<font_renderer_t::compiled_text_t> ptr; std::unique_ptr<font_renderer_t::compiled_text_t> ptr;
if (!cached_text_objects.empty()) if (!cached_text_objects.empty())
@ -160,7 +185,7 @@ namespace blt::gfx
{ {
ptr = std::make_unique<compiled_text_t>(*generator); ptr = std::make_unique<compiled_text_t>(*generator);
} }
ptr->change_text(str); ptr->change_text(str, size);
auto index = added_texts.size(); auto index = added_texts.size();
added_texts.push_back(std::move(ptr)); added_texts.push_back(std::move(ptr));
text_ptr_to_index[added_texts.back().get()] = index; text_ptr_to_index[added_texts.back().get()] = index;
@ -176,21 +201,108 @@ namespace blt::gfx
added_texts.erase(added_texts.begin() + static_cast<blt::ptrdiff_t>(index)); added_texts.erase(added_texts.begin() + static_cast<blt::ptrdiff_t>(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<const blt::u8*>(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<float> vertices; static std::vector<float> vertices;
vertices.clear(); 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) 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<float>(generator.get_dimensions());
float x_pos = x + static_cast<float>(ch.glyph.bearing.x());
float y_pos = -static_cast<float>(ch.glyph.size.y() - ch.glyph.bearing.y());
auto w = static_cast<float>(ch.glyph.size.x());
auto h = static_cast<float>(ch.glyph.size.y());
auto texture_min_x = static_cast<float>(ch.min.x()) / dims;
auto texture_min_y = static_cast<float>(ch.min.y()) / dims;
auto texture_max_x = static_cast<float>(ch.max.x()) / dims;
auto texture_max_y = static_cast<float>(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<float, 6 * 4> 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<float>(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<long>(vertices.size() * sizeof(float)), vertices.data());
} }
font_renderer_t::compiled_text_t::compiled_text_t(font_generator_t& generator): generator(generator) 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({}); auto vbo = vertex_array_t::make_vbo({});
vbo->vbo.create(); vbo->vbo.create();
vbo->vbo.bind(); vbo->vbo.bind();
@ -201,6 +313,17 @@ namespace blt::gfx
void font_renderer_t::compiled_text_t::draw() 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<int>(render.render_start), static_cast<int>(render.render_count));
}
} }
} }