From 289ed430c745157589123d86151fe7c01c898748 Mon Sep 17 00:00:00 2001 From: Dan Paulat Date: Fri, 6 Aug 2021 22:16:34 -0500 Subject: [PATCH] Scalable fonts using freetype-gl --- external/freetype-gl.cmake | 2 +- scwx-qt/gl/text.frag | 13 +- scwx-qt/gl/text.vert | 12 +- scwx-qt/scwx-qt.cmake | 3 +- scwx-qt/source/scwx/qt/gl/text_shader.cpp | 79 +--- scwx-qt/source/scwx/qt/gl/text_shader.hpp | 16 +- scwx-qt/source/scwx/qt/util/font.cpp | 429 +++++++++++++++++----- scwx-qt/source/scwx/qt/util/font.hpp | 27 +- 8 files changed, 400 insertions(+), 181 deletions(-) diff --git a/external/freetype-gl.cmake b/external/freetype-gl.cmake index 5f46a26b..2c4729fa 100644 --- a/external/freetype-gl.cmake +++ b/external/freetype-gl.cmake @@ -90,7 +90,7 @@ if(MSVC) target_compile_definitions(makefont PRIVATE _CRT_SECURE_NO_WARNINGS _CRT_NONSTDC_NO_DEPRECATE) endif(MSVC) -set(FTGL_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR} PARENT_SCOPE) +set(FTGL_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/freetype-gl PARENT_SCOPE) set_target_properties(doc PROPERTIES EXCLUDE_FROM_ALL True) set_target_properties(makefont PROPERTIES EXCLUDE_FROM_ALL True) diff --git a/scwx-qt/gl/text.frag b/scwx-qt/gl/text.frag index 23298e8e..bb4789a0 100644 --- a/scwx-qt/gl/text.frag +++ b/scwx-qt/gl/text.frag @@ -1,12 +1,17 @@ #version 330 core + in vec2 texCoords; +in vec4 textColor; + out vec4 color; -uniform sampler2D text; -uniform vec4 textColor; +uniform sampler2D uTexture; void main() { - vec4 sampled = vec4(1.0f, 1.0f, 1.0f, texture(text, texCoords).r); - color = textColor * sampled; + float dist = texture(uTexture, texCoords).r; + float width = fwidth(dist); + float alpha = smoothstep(0.5f - width, 0.5f + width, dist); + + color = vec4(textColor.rgb, textColor.a * alpha); } diff --git a/scwx-qt/gl/text.vert b/scwx-qt/gl/text.vert index ffc3a8be..9e14e429 100644 --- a/scwx-qt/gl/text.vert +++ b/scwx-qt/gl/text.vert @@ -1,11 +1,17 @@ #version 330 core -layout (location = 0) in vec4 vertex; + +layout (location = 0) in vec3 aVertex; +layout (location = 1) in vec2 aTexCoords; +layout (location = 2) in vec4 aColor; + out vec2 texCoords; +out vec4 textColor; uniform mat4 projection; void main() { - gl_Position = projection * vec4(vertex.xy, 0.0f, 1.0f); - texCoords = vertex.zw; + gl_Position = projection * vec4(aVertex, 1.0f); + texCoords = aTexCoords; + textColor = aColor; } diff --git a/scwx-qt/scwx-qt.cmake b/scwx-qt/scwx-qt.cmake index 8c28f3e0..538b02ea 100644 --- a/scwx-qt/scwx-qt.cmake +++ b/scwx-qt/scwx-qt.cmake @@ -122,6 +122,7 @@ if (WIN32) endif() target_include_directories(scwx-qt PRIVATE ${scwx-qt_SOURCE_DIR}/source + ${FTGL_INCLUDE_DIR} ${MBGL_INCLUDE_DIR}) target_link_libraries(scwx-qt PRIVATE Qt${QT_VERSION_MAJOR}::Widgets @@ -129,7 +130,7 @@ target_link_libraries(scwx-qt PRIVATE Qt${QT_VERSION_MAJOR}::Widgets Boost::timer qmapboxgl opengl32 - Freetype::Freetype + freetype-gl GeographicLib::GeographicLib glm::glm wxdata) diff --git a/scwx-qt/source/scwx/qt/gl/text_shader.cpp b/scwx-qt/source/scwx/qt/gl/text_shader.cpp index c4ad61a5..35f24007 100644 --- a/scwx-qt/source/scwx/qt/gl/text_shader.cpp +++ b/scwx-qt/source/scwx/qt/gl/text_shader.cpp @@ -16,11 +16,7 @@ class TextShaderImpl { public: explicit TextShaderImpl(OpenGLFunctions& gl) : - gl_ {gl}, - projectionLocation_(GL_INVALID_INDEX), - textColorLocation_(GL_INVALID_INDEX), - vao_ {GL_INVALID_INDEX}, - vbo_ {GL_INVALID_INDEX} + gl_ {gl}, projectionLocation_(GL_INVALID_INDEX) { } @@ -29,10 +25,6 @@ public: OpenGLFunctions& gl_; GLint projectionLocation_; - GLint textColorLocation_; - - GLuint vao_; - GLuint vbo_; }; TextShader::TextShader(OpenGLFunctions& gl) : @@ -57,23 +49,6 @@ bool TextShader::Initialize() BOOST_LOG_TRIVIAL(warning) << logPrefix_ << "Could not find projection"; } - p->textColorLocation_ = gl.glGetUniformLocation(id(), "textColor"); - if (p->textColorLocation_ == -1) - { - BOOST_LOG_TRIVIAL(warning) << logPrefix_ << "Could not find textColor"; - } - - gl.glGenVertexArrays(1, &p->vao_); - gl.glGenBuffers(1, &p->vbo_); - gl.glBindVertexArray(p->vao_); - gl.glBindBuffer(GL_ARRAY_BUFFER, p->vbo_); - gl.glBufferData( - GL_ARRAY_BUFFER, sizeof(float) * 6 * 4, nullptr, GL_DYNAMIC_DRAW); - gl.glEnableVertexAttribArray(0); - gl.glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(float), 0); - gl.glBindBuffer(GL_ARRAY_BUFFER, 0); - gl.glBindVertexArray(0); - return success; } @@ -83,7 +58,8 @@ void TextShader::RenderText(const std::string& text, float scale, const glm::mat4& projection, const boost::gil::rgba8_pixel_t& color, - const std::unordered_map& glyphs) + std::shared_ptr font, + GLuint textureId) { OpenGLFunctions& gl = p->gl_; @@ -94,48 +70,13 @@ void TextShader::RenderText(const std::string& text, gl.glUniformMatrix4fv( p->projectionLocation_, 1, GL_FALSE, glm::value_ptr(projection)); - gl.glUniform4f( - p->textColorLocation_, color[0], color[1], color[2], color[3]); gl.glActiveTexture(GL_TEXTURE0); - gl.glBindVertexArray(p->vao_); + gl.glBindTexture(GL_TEXTURE_2D, textureId); - for (auto c = text.cbegin(); c != text.cend(); c++) - { - if (glyphs.find(*c) == glyphs.end()) - { - continue; - } - - const util::Glyph& g = glyphs.at(*c); - - float xpos = x + g.bearing.x * scale; - float ypos = y - (g.size.y - g.bearing.y) * scale; - - float w = g.size.x * scale; - float h = g.size.y * scale; - - // Glyph vertices - float vertices[6][4] = {{xpos, ypos + h, 0.0f, 0.0f}, - {xpos, ypos, 0.0f, 1.0f}, - {xpos + w, ypos, 1.0f, 1.0f}, // - // - {xpos, ypos + h, 0.0f, 0.0f}, - {xpos + w, ypos, 1.0f, 1.0f}, - {xpos + w, ypos + h, 1.0f, 0.0f}}; - - // Render glyph texture - gl.glBindTexture(GL_TEXTURE_2D, g.textureId); - - gl.glBindBuffer(GL_ARRAY_BUFFER, p->vbo_); - gl.glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices); - gl.glBindBuffer(GL_ARRAY_BUFFER, 0); - - gl.glDrawArrays(GL_TRIANGLES, 0, 6); - - // Advance to the next glyph - x += (g.advance >> 6) * scale; - } + std::shared_ptr buffer = util::Font::CreateBuffer(); + font->BufferText(buffer, text, x, y, scale, color); + util::Font::RenderBuffer(gl, buffer); } void TextShader::SetProjection(const glm::mat4& projection) @@ -144,12 +85,6 @@ void TextShader::SetProjection(const glm::mat4& projection) p->projectionLocation_, 1, GL_FALSE, glm::value_ptr(projection)); } -void TextShader::SetTextColor(const boost::gil::rgba8_pixel_t color) -{ - p->gl_.glUniform4f( - p->textColorLocation_, color[0], color[1], color[2], color[3]); -} - } // namespace gl } // namespace qt } // namespace scwx diff --git a/scwx-qt/source/scwx/qt/gl/text_shader.hpp b/scwx-qt/source/scwx/qt/gl/text_shader.hpp index 0e3c5090..19655bbe 100644 --- a/scwx-qt/source/scwx/qt/gl/text_shader.hpp +++ b/scwx-qt/source/scwx/qt/gl/text_shader.hpp @@ -30,15 +30,15 @@ public: TextShader& operator=(TextShader&&) noexcept; bool Initialize(); - void RenderText(const std::string& text, - float x, - float y, - float scale, - const glm::mat4& projection, - const boost::gil::rgba8_pixel_t& color, - const std::unordered_map& glyphs); + void RenderText(const std::string& text, + float x, + float y, + float scale, + const glm::mat4& projection, + const boost::gil::rgba8_pixel_t& color, + std::shared_ptr font, + GLuint textureId); void SetProjection(const glm::mat4& projection); - void SetTextColor(const boost::gil::rgba8_pixel_t color); private: std::unique_ptr p; diff --git a/scwx-qt/source/scwx/qt/util/font.cpp b/scwx-qt/source/scwx/qt/util/font.cpp index e5ce57c1..6be10e51 100644 --- a/scwx-qt/source/scwx/qt/util/font.cpp +++ b/scwx-qt/source/scwx/qt/util/font.cpp @@ -4,10 +4,16 @@ #include #include -#include +#include #include -#include FT_FREETYPE_H +// #include (exclude opengl.h) +#include +#include +#include +#include +#include +#include namespace scwx { @@ -16,106 +22,237 @@ namespace qt namespace util { +struct TextureGlyph +{ + int offsetX_; + int offsetY_; + int width_; + int height_; + float s0_; + float t0_; + float s1_; + float t1_; + float advanceX_; + + TextureGlyph(int offsetX, + int offsetY, + int width, + int height, + float s0, + float t0, + float s1, + float t1, + float advanceX) : + offsetX_ {offsetX}, + offsetY_ {offsetY}, + width_ {width}, + height_ {height}, + s0_ {s0}, + t0_ {t0}, + s1_ {s1}, + t1_ {t1}, + advanceX_ {advanceX} + { + } +}; + +static const std::string CODEPOINTS = + " !\"#$%&'()*+,-./0123456789:;<=>?" + "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_" + "`abcdefghijklmnopqrstuvwxyz{|}~"; + static const std::string logPrefix_ = "[scwx::qt::util::font] "; static std::unordered_map> fontMap_; -static FT_Library ft_ {nullptr}; -static std::mutex ftMutex_; +class FontBuffer +{ +public: + explicit FontBuffer() : + vaoId_ {GL_INVALID_INDEX}, + verticesId_ {GL_INVALID_INDEX}, + indicesId_ {GL_INVALID_INDEX}, + gpuISize_ {0}, + gpuVSize_ {0}, + dirty_ {true} + { + } -static bool InitializeFreeType(); + ~FontBuffer() {} + + GLuint vaoId_ = GL_INVALID_INDEX; + GLuint verticesId_ = GL_INVALID_INDEX; + GLuint indicesId_ = GL_INVALID_INDEX; + GLsizei gpuISize_ = 0; + GLsizei gpuVSize_ = 0; + bool dirty_ = true; + + std::vector vertices_; + std::vector indices_; + + std::mutex mutex_; +}; class FontImpl { public: explicit FontImpl(const std::string& resource) : - resource_(resource), fontData_(), face_ {nullptr} + resource_(resource), atlas_ {nullptr} { } - ~FontImpl() {} + ~FontImpl() + { + if (atlas_ != nullptr) + { + ftgl::texture_atlas_delete(atlas_); + } + } const std::string resource_; - QByteArray fontData_; - FT_Face face_; + ftgl::texture_atlas_t* atlas_; + std::unordered_map glyphs_; }; Font::Font(const std::string& resource) : p(std::make_unique(resource)) { } -Font::~Font() +Font::~Font() = default; + +float Font::BufferText(std::shared_ptr buffer, + const std::string& text, + float x, + float y, + float scale, + boost::gil::rgba8_pixel_t color) const { - FT_Done_Face(p->face_); + static constexpr float colorScale = 1.0f / 255.0f; + + float r = color[0] * colorScale; + float g = color[1] * colorScale; + float b = color[2] * colorScale; + float a = color[3] * colorScale; + + for (size_t i = 0; i < text.length(); ++i) + { + const char& c = text[i]; + + auto it = p->glyphs_.find(c); + if (it == p->glyphs_.end()) + { + BOOST_LOG_TRIVIAL(info) + << logPrefix_ + << "Could not draw character: " << static_cast(c); + continue; + } + + TextureGlyph& glyph = it->second; + + if (i > 0) + { + x += Kerning(text[i - 1], c) * scale; + } + + float x0 = x + glyph.offsetX_ * scale; + float y0 = y + glyph.offsetY_ * scale; + float x1 = x0 + glyph.width_ * scale; + float y1 = y0 - glyph.height_ * scale; + + float s0 = glyph.s0_; + float t0 = glyph.t0_; + float s1 = glyph.s1_; + float t1 = glyph.t1_; + + { + std::scoped_lock lock(buffer->mutex_); + + const GLuint i0 = static_cast(buffer->vertices_.size() / 9u); + const GLuint i1 = i0 + 1; + const GLuint i2 = i1 + 1; + const GLuint i3 = i2 + 1; + + buffer->indices_.insert(buffer->indices_.end(), + {i0, i1, i2, i0, i2, i3}); + buffer->vertices_.insert(buffer->vertices_.end(), + {x0, y0, 0, s0, t0, r, g, b, a, // + x0, y1, 0, s0, t1, r, g, b, a, // + x1, y1, 0, s1, t1, r, g, b, a, // + x1, y0, 0, s1, t0, r, g, b, a}); + } + + x += glyph.advanceX_ * scale; + } + + buffer->dirty_ = true; + + return x; } -void Font::GenerateGlyphs(OpenGLFunctions& gl, - std::unordered_map& glyphs, - unsigned int height) +float Font::Kerning(char c1, char c2) const { - FT_Error error; - FT_Face& face = p->face_; + // TODO + return 0.0f; +} - // Allow single-byte texture colors - gl.glPixelStorei(GL_UNPACK_ALIGNMENT, 1); +float Font::TextLength(const std::string& text, float scale) const +{ + float x = 0.0f; - FT_Set_Pixel_Sizes(p->face_, 0, 48); - - for (unsigned char c = 0; c < 128; c++) + for (size_t i = 0; i < text.length(); ++i) { - if (glyphs.find(c) != glyphs.end()) + const char& c = text[i]; + + auto it = p->glyphs_.find(c); + if (it == p->glyphs_.end()) { - BOOST_LOG_TRIVIAL(warning) << logPrefix_ << "Found glyph " - << static_cast(c) << ", skipping"; + BOOST_LOG_TRIVIAL(info) + << logPrefix_ + << "Character not found: " << static_cast(c); continue; } - if ((error = FT_Load_Char(face, c, FT_LOAD_RENDER)) != 0) + TextureGlyph& glyph = it->second; + + if (i > 0) { - BOOST_LOG_TRIVIAL(error) << logPrefix_ << "Failed to load glyph " - << static_cast(c) << ": " << error; - continue; + x += Kerning(text[i - 1], c) * scale; } - GLuint texture; - gl.glGenTextures(1, &texture); - gl.glBindTexture(GL_TEXTURE_2D, texture); - - gl.glTexImage2D(GL_TEXTURE_2D, - 0, - GL_RED, - face->glyph->bitmap.width, - face->glyph->bitmap.rows, - 0, - GL_RED, - GL_UNSIGNED_BYTE, - face->glyph->bitmap.buffer); - - gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - - glyphs.insert( - {c, - Glyph { - texture, - glm::ivec2(face->glyph->bitmap.width, face->glyph->bitmap.rows), - glm::ivec2(face->glyph->bitmap_left, face->glyph->bitmap_top), - face->glyph->advance.x}}); + x += glyph.advanceX_ * scale; } + + return x; +} + +GLuint Font::GenerateTexture(OpenGLFunctions& gl) +{ + gl.glGenTextures(1, &p->atlas_->id); + gl.glBindTexture(GL_TEXTURE_2D, p->atlas_->id); + + gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + gl.glTexImage2D(GL_TEXTURE_2D, + 0, + GL_RED, + static_cast(p->atlas_->width), + static_cast(p->atlas_->height), + 0, + GL_RED, + GL_UNSIGNED_BYTE, + p->atlas_->data); + + return p->atlas_->id; } std::shared_ptr Font::Create(const std::string& resource) { - std::shared_ptr font = nullptr; - FT_Error error; - - if (!InitializeFreeType()) - { - return font; - } + std::shared_ptr font = nullptr; + boost::timer::cpu_timer timer; auto it = fontMap_.find(resource); if (it != fontMap_.end()) @@ -132,24 +269,45 @@ std::shared_ptr Font::Create(const std::string& resource) return font; } - font = std::make_shared(resource); - font->p->fontData_ = fontFile.readAll(); + font = std::make_shared(resource); + QByteArray fontData = fontFile.readAll(); + font->p->atlas_ = ftgl::texture_atlas_new(512, 512, 1); + ftgl::texture_font_t* textureFont = ftgl::texture_font_new_from_memory( + font->p->atlas_, 72, fontData.constData(), fontData.size()); + + textureFont->rendermode = ftgl::RENDER_SIGNED_DISTANCE_FIELD; + + timer.start(); + texture_font_load_glyphs(textureFont, CODEPOINTS.c_str()); + timer.stop(); + + // Single-byte UTF-8 characters + for (const char& c : CODEPOINTS) { - std::scoped_lock(ftMutex_); - if ((error = FT_New_Memory_Face( - ft_, - reinterpret_cast(font->p->fontData_.data()), - font->p->fontData_.size(), - 0, - &font->p->face_)) != 0) + const ftgl::texture_glyph_t* glyph = + ftgl::texture_font_get_glyph(textureFont, &c); + + if (glyph != nullptr) { - BOOST_LOG_TRIVIAL(error) - << logPrefix_ << "Failed to load font: " << error; - font.reset(); + font->p->glyphs_.emplace(c, + TextureGlyph(glyph->offset_x, + glyph->offset_y, + static_cast(glyph->width), + static_cast(glyph->height), + glyph->s0, + glyph->t0, + glyph->s1, + glyph->t1, + glyph->advance_x)); } } + BOOST_LOG_TRIVIAL(debug) << logPrefix_ << "Font \"" << resource + << "\" loaded in " << timer.format(6, "%ws"); + + texture_font_delete(textureFont); + if (font != nullptr) { fontMap_.insert({resource, font}); @@ -158,20 +316,127 @@ std::shared_ptr Font::Create(const std::string& resource) return font; } -static bool InitializeFreeType() +std::shared_ptr Font::CreateBuffer() { - std::scoped_lock(ftMutex_); + return std::make_shared(); +} - FT_Error error; - - if (ft_ == nullptr && (error = FT_Init_FreeType(&ft_)) != 0) +void Font::ClearBuffer(std::shared_ptr buffer) +{ + if (buffer != nullptr) { - BOOST_LOG_TRIVIAL(error) - << logPrefix_ << "Could not init FreeType library: " << error; - ft_ = nullptr; + std::scoped_lock lock(buffer->mutex_); + buffer->indices_.clear(); + buffer->vertices_.clear(); + buffer->dirty_ = true; + } +} + +void Font::RenderBuffer(OpenGLFunctions& gl, std::shared_ptr buffer) +{ + // TODO: + if (buffer == nullptr) + { + return; } - return (ft_ != nullptr); + std::scoped_lock lock(buffer->mutex_); + + // TODO: Vertex buffer upload + if (buffer->dirty_) + { + if (buffer->verticesId_ == GL_INVALID_INDEX) + { + gl.glGenBuffers(1, &buffer->verticesId_); + } + if (buffer->indicesId_ == GL_INVALID_INDEX) + { + gl.glGenBuffers(1, &buffer->indicesId_); + } + + GLsizei vSize = + static_cast(buffer->vertices_.size() * sizeof(GLfloat)); + GLsizei iSize = + static_cast(buffer->indices_.size() * sizeof(GLuint)); + + // Always upload vertices first to avoid rendering non-existent data + + // Upload vertices + gl.glBindBuffer(GL_ARRAY_BUFFER, buffer->verticesId_); + if (vSize != buffer->gpuVSize_) + { + gl.glBufferData( + GL_ARRAY_BUFFER, vSize, buffer->vertices_.data(), GL_DYNAMIC_DRAW); + buffer->gpuVSize_ = vSize; + } + else + { + gl.glBufferSubData( + GL_ARRAY_BUFFER, 0, vSize, buffer->vertices_.data()); + } + + // Upload indices + gl.glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer->indicesId_); + if (iSize != buffer->gpuISize_) + { + gl.glBufferData(GL_ELEMENT_ARRAY_BUFFER, + iSize, + buffer->indices_.data(), + GL_DYNAMIC_DRAW); + } + else + { + gl.glBufferSubData( + GL_ELEMENT_ARRAY_BUFFER, 0, iSize, buffer->indices_.data()); + } + + buffer->dirty_ = false; + } + + // TODO: Setup + if (buffer->vaoId_ == GL_INVALID_INDEX) + { + // Generate and setup VAO + gl.glGenVertexArrays(1, &buffer->vaoId_); + gl.glBindVertexArray(buffer->vaoId_); + + gl.glBindBuffer(GL_ARRAY_BUFFER, buffer->verticesId_); + + // vec3 aVertex + gl.glEnableVertexAttribArray(0); + gl.glVertexAttribPointer( + 0, 3, GL_FLOAT, GL_FALSE, 9 * sizeof(float), nullptr); + + // vec2 aTexCoords + gl.glEnableVertexAttribArray(1); + gl.glVertexAttribPointer( + 1, + 2, + GL_FLOAT, + GL_FALSE, + 9 * sizeof(float), + reinterpret_cast(3 * sizeof(float))); + + // vec4 aColor + gl.glEnableVertexAttribArray(2); + gl.glVertexAttribPointer( + 2, + 4, + GL_FLOAT, + GL_FALSE, + 9 * sizeof(float), + reinterpret_cast(5 * sizeof(float))); + + gl.glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer->indicesId_); + } + + // Bind VAO for drawing + gl.glBindVertexArray(buffer->vaoId_); + + gl.glDrawElements(GL_TRIANGLES, + static_cast(buffer->indices_.size()), + GL_UNSIGNED_INT, + 0); } } // namespace util diff --git a/scwx-qt/source/scwx/qt/util/font.hpp b/scwx-qt/source/scwx/qt/util/font.hpp index 0c123e79..bdba72f3 100644 --- a/scwx-qt/source/scwx/qt/util/font.hpp +++ b/scwx-qt/source/scwx/qt/util/font.hpp @@ -5,6 +5,7 @@ #include #include +#include #include namespace scwx @@ -14,13 +15,7 @@ namespace qt namespace util { -struct Glyph -{ - GLuint textureId; - glm::ivec2 size; // pixels - glm::ivec2 bearing; // pixels - GLint advance; // 1/64 pixels -}; +class FontBuffer; class FontImpl; @@ -36,12 +31,24 @@ public: Font(Font&&) = delete; Font& operator=(Font&&) = delete; - void GenerateGlyphs(OpenGLFunctions& gl, - std::unordered_map& glyphs, - unsigned int height); + float BufferText(std::shared_ptr buffer, + const std::string& text, + float x, + float y, + float scale, + boost::gil::rgba8_pixel_t color) const; + float Kerning(char c1, char c2) const; + float TextLength(const std::string& text, float scale) const; + + GLuint GenerateTexture(OpenGLFunctions& gl); static std::shared_ptr Create(const std::string& resource); + static std::shared_ptr CreateBuffer(); + static void ClearBuffer(std::shared_ptr buffer); + static void RenderBuffer(OpenGLFunctions& gl, + std::shared_ptr buffer); + private: std::unique_ptr p; };