Scalable fonts using freetype-gl

This commit is contained in:
Dan Paulat 2021-08-06 22:16:34 -05:00
parent ec8e2643ff
commit 289ed430c7
8 changed files with 400 additions and 181 deletions

View file

@ -90,7 +90,7 @@ if(MSVC)
target_compile_definitions(makefont PRIVATE _CRT_SECURE_NO_WARNINGS _CRT_NONSTDC_NO_DEPRECATE) target_compile_definitions(makefont PRIVATE _CRT_SECURE_NO_WARNINGS _CRT_NONSTDC_NO_DEPRECATE)
endif(MSVC) 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(doc PROPERTIES EXCLUDE_FROM_ALL True)
set_target_properties(makefont PROPERTIES EXCLUDE_FROM_ALL True) set_target_properties(makefont PROPERTIES EXCLUDE_FROM_ALL True)

View file

@ -1,12 +1,17 @@
#version 330 core #version 330 core
in vec2 texCoords; in vec2 texCoords;
in vec4 textColor;
out vec4 color; out vec4 color;
uniform sampler2D text; uniform sampler2D uTexture;
uniform vec4 textColor;
void main() void main()
{ {
vec4 sampled = vec4(1.0f, 1.0f, 1.0f, texture(text, texCoords).r); float dist = texture(uTexture, texCoords).r;
color = textColor * sampled; float width = fwidth(dist);
float alpha = smoothstep(0.5f - width, 0.5f + width, dist);
color = vec4(textColor.rgb, textColor.a * alpha);
} }

View file

@ -1,11 +1,17 @@
#version 330 core #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 vec2 texCoords;
out vec4 textColor;
uniform mat4 projection; uniform mat4 projection;
void main() void main()
{ {
gl_Position = projection * vec4(vertex.xy, 0.0f, 1.0f); gl_Position = projection * vec4(aVertex, 1.0f);
texCoords = vertex.zw; texCoords = aTexCoords;
textColor = aColor;
} }

View file

@ -122,6 +122,7 @@ if (WIN32)
endif() endif()
target_include_directories(scwx-qt PRIVATE ${scwx-qt_SOURCE_DIR}/source target_include_directories(scwx-qt PRIVATE ${scwx-qt_SOURCE_DIR}/source
${FTGL_INCLUDE_DIR}
${MBGL_INCLUDE_DIR}) ${MBGL_INCLUDE_DIR})
target_link_libraries(scwx-qt PRIVATE Qt${QT_VERSION_MAJOR}::Widgets 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 Boost::timer
qmapboxgl qmapboxgl
opengl32 opengl32
Freetype::Freetype freetype-gl
GeographicLib::GeographicLib GeographicLib::GeographicLib
glm::glm glm::glm
wxdata) wxdata)

View file

@ -16,11 +16,7 @@ class TextShaderImpl
{ {
public: public:
explicit TextShaderImpl(OpenGLFunctions& gl) : explicit TextShaderImpl(OpenGLFunctions& gl) :
gl_ {gl}, gl_ {gl}, projectionLocation_(GL_INVALID_INDEX)
projectionLocation_(GL_INVALID_INDEX),
textColorLocation_(GL_INVALID_INDEX),
vao_ {GL_INVALID_INDEX},
vbo_ {GL_INVALID_INDEX}
{ {
} }
@ -29,10 +25,6 @@ public:
OpenGLFunctions& gl_; OpenGLFunctions& gl_;
GLint projectionLocation_; GLint projectionLocation_;
GLint textColorLocation_;
GLuint vao_;
GLuint vbo_;
}; };
TextShader::TextShader(OpenGLFunctions& gl) : TextShader::TextShader(OpenGLFunctions& gl) :
@ -57,23 +49,6 @@ bool TextShader::Initialize()
BOOST_LOG_TRIVIAL(warning) << logPrefix_ << "Could not find projection"; 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; return success;
} }
@ -83,7 +58,8 @@ void TextShader::RenderText(const std::string& text,
float scale, float scale,
const glm::mat4& projection, const glm::mat4& projection,
const boost::gil::rgba8_pixel_t& color, const boost::gil::rgba8_pixel_t& color,
const std::unordered_map<char, util::Glyph>& glyphs) std::shared_ptr<util::Font> font,
GLuint textureId)
{ {
OpenGLFunctions& gl = p->gl_; OpenGLFunctions& gl = p->gl_;
@ -94,48 +70,13 @@ void TextShader::RenderText(const std::string& text,
gl.glUniformMatrix4fv( gl.glUniformMatrix4fv(
p->projectionLocation_, 1, GL_FALSE, glm::value_ptr(projection)); 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.glActiveTexture(GL_TEXTURE0);
gl.glBindVertexArray(p->vao_); gl.glBindTexture(GL_TEXTURE_2D, textureId);
for (auto c = text.cbegin(); c != text.cend(); c++) std::shared_ptr<util::FontBuffer> buffer = util::Font::CreateBuffer();
{ font->BufferText(buffer, text, x, y, scale, color);
if (glyphs.find(*c) == glyphs.end()) util::Font::RenderBuffer(gl, buffer);
{
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;
}
} }
void TextShader::SetProjection(const glm::mat4& projection) 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)); 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 gl
} // namespace qt } // namespace qt
} // namespace scwx } // namespace scwx

View file

@ -36,9 +36,9 @@ public:
float scale, float scale,
const glm::mat4& projection, const glm::mat4& projection,
const boost::gil::rgba8_pixel_t& color, const boost::gil::rgba8_pixel_t& color,
const std::unordered_map<char, util::Glyph>& glyphs); std::shared_ptr<util::Font> font,
GLuint textureId);
void SetProjection(const glm::mat4& projection); void SetProjection(const glm::mat4& projection);
void SetTextColor(const boost::gil::rgba8_pixel_t color);
private: private:
std::unique_ptr<TextShaderImpl> p; std::unique_ptr<TextShaderImpl> p;

View file

@ -4,10 +4,16 @@
#include <unordered_map> #include <unordered_map>
#include <boost/log/trivial.hpp> #include <boost/log/trivial.hpp>
#include <ft2build.h> #include <boost/timer/timer.hpp>
#include <QFile> #include <QFile>
#include FT_FREETYPE_H // #include <freetype-gl.h> (exclude opengl.h)
#include <platform.h>
#include <vec234.h>
#include <vector.h>
#include <texture-atlas.h>
#include <texture-font.h>
#include <ftgl-utils.h>
namespace scwx namespace scwx
{ {
@ -16,106 +22,237 @@ namespace qt
namespace util 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 const std::string logPrefix_ = "[scwx::qt::util::font] ";
static std::unordered_map<std::string, std::shared_ptr<Font>> fontMap_; static std::unordered_map<std::string, std::shared_ptr<Font>> fontMap_;
static FT_Library ft_ {nullptr}; class FontBuffer
static std::mutex ftMutex_; {
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<GLfloat> vertices_;
std::vector<GLuint> indices_;
std::mutex mutex_;
};
class FontImpl class FontImpl
{ {
public: public:
explicit FontImpl(const std::string& resource) : 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_; const std::string resource_;
QByteArray fontData_; ftgl::texture_atlas_t* atlas_;
FT_Face face_; std::unordered_map<char, TextureGlyph> glyphs_;
}; };
Font::Font(const std::string& resource) : Font::Font(const std::string& resource) :
p(std::make_unique<FontImpl>(resource)) p(std::make_unique<FontImpl>(resource))
{ {
} }
Font::~Font() Font::~Font() = default;
{
FT_Done_Face(p->face_);
}
void Font::GenerateGlyphs(OpenGLFunctions& gl, float Font::BufferText(std::shared_ptr<FontBuffer> buffer,
std::unordered_map<char, Glyph>& glyphs, const std::string& text,
unsigned int height) float x,
float y,
float scale,
boost::gil::rgba8_pixel_t color) const
{ {
FT_Error error; static constexpr float colorScale = 1.0f / 255.0f;
FT_Face& face = p->face_;
// Allow single-byte texture colors float r = color[0] * colorScale;
gl.glPixelStorei(GL_UNPACK_ALIGNMENT, 1); float g = color[1] * colorScale;
float b = color[2] * colorScale;
float a = color[3] * colorScale;
FT_Set_Pixel_Sizes(p->face_, 0, 48); for (size_t i = 0; i < text.length(); ++i)
for (unsigned char c = 0; c < 128; c++)
{ {
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 " BOOST_LOG_TRIVIAL(info)
<< static_cast<uint16_t>(c) << ", skipping"; << logPrefix_
<< "Could not draw character: " << static_cast<uint32_t>(c);
continue; 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 " x += Kerning(text[i - 1], c) * scale;
<< static_cast<uint16_t>(c) << ": " << error; }
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<GLuint>(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;
}
float Font::Kerning(char c1, char c2) const
{
// TODO
return 0.0f;
}
float Font::TextLength(const std::string& text, float scale) const
{
float x = 0.0f;
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_
<< "Character not found: " << static_cast<uint32_t>(c);
continue; continue;
} }
GLuint texture; TextureGlyph& glyph = it->second;
gl.glGenTextures(1, &texture);
gl.glBindTexture(GL_TEXTURE_2D, texture);
gl.glTexImage2D(GL_TEXTURE_2D, if (i > 0)
0, {
GL_RED, x += Kerning(text[i - 1], c) * scale;
face->glyph->bitmap.width, }
face->glyph->bitmap.rows,
0, x += glyph.advanceX_ * scale;
GL_RED, }
GL_UNSIGNED_BYTE,
face->glyph->bitmap.buffer); 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_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_WRAP_T, GL_CLAMP_TO_EDGE);
gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glyphs.insert( gl.glTexImage2D(GL_TEXTURE_2D,
{c, 0,
Glyph { GL_RED,
texture, static_cast<GLsizei>(p->atlas_->width),
glm::ivec2(face->glyph->bitmap.width, face->glyph->bitmap.rows), static_cast<GLsizei>(p->atlas_->height),
glm::ivec2(face->glyph->bitmap_left, face->glyph->bitmap_top), 0,
face->glyph->advance.x}}); GL_RED,
} GL_UNSIGNED_BYTE,
p->atlas_->data);
return p->atlas_->id;
} }
std::shared_ptr<Font> Font::Create(const std::string& resource) std::shared_ptr<Font> Font::Create(const std::string& resource)
{ {
std::shared_ptr<Font> font = nullptr; std::shared_ptr<Font> font = nullptr;
FT_Error error; boost::timer::cpu_timer timer;
if (!InitializeFreeType())
{
return font;
}
auto it = fontMap_.find(resource); auto it = fontMap_.find(resource);
if (it != fontMap_.end()) if (it != fontMap_.end())
@ -133,23 +270,44 @@ std::shared_ptr<Font> Font::Create(const std::string& resource)
} }
font = std::make_shared<Font>(resource); font = std::make_shared<Font>(resource);
font->p->fontData_ = fontFile.readAll(); 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_); const ftgl::texture_glyph_t* glyph =
if ((error = FT_New_Memory_Face( ftgl::texture_font_get_glyph(textureFont, &c);
ft_,
reinterpret_cast<const FT_Byte*>(font->p->fontData_.data()), if (glyph != nullptr)
font->p->fontData_.size(),
0,
&font->p->face_)) != 0)
{ {
BOOST_LOG_TRIVIAL(error) font->p->glyphs_.emplace(c,
<< logPrefix_ << "Failed to load font: " << error; TextureGlyph(glyph->offset_x,
font.reset(); glyph->offset_y,
static_cast<int>(glyph->width),
static_cast<int>(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) if (font != nullptr)
{ {
fontMap_.insert({resource, font}); fontMap_.insert({resource, font});
@ -158,20 +316,127 @@ std::shared_ptr<Font> Font::Create(const std::string& resource)
return font; return font;
} }
static bool InitializeFreeType() std::shared_ptr<FontBuffer> Font::CreateBuffer()
{ {
std::scoped_lock(ftMutex_); return std::make_shared<FontBuffer>();
FT_Error error;
if (ft_ == nullptr && (error = FT_Init_FreeType(&ft_)) != 0)
{
BOOST_LOG_TRIVIAL(error)
<< logPrefix_ << "Could not init FreeType library: " << error;
ft_ = nullptr;
} }
return (ft_ != nullptr); void Font::ClearBuffer(std::shared_ptr<FontBuffer> buffer)
{
if (buffer != nullptr)
{
std::scoped_lock lock(buffer->mutex_);
buffer->indices_.clear();
buffer->vertices_.clear();
buffer->dirty_ = true;
}
}
void Font::RenderBuffer(OpenGLFunctions& gl, std::shared_ptr<FontBuffer> buffer)
{
// TODO:
if (buffer == nullptr)
{
return;
}
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<GLsizei>(buffer->vertices_.size() * sizeof(GLfloat));
GLsizei iSize =
static_cast<GLsizei>(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<const GLvoid*>(3 * sizeof(float)));
// vec4 aColor
gl.glEnableVertexAttribArray(2);
gl.glVertexAttribPointer(
2,
4,
GL_FLOAT,
GL_FALSE,
9 * sizeof(float),
reinterpret_cast<const GLvoid*>(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<GLsizei>(buffer->indices_.size()),
GL_UNSIGNED_INT,
0);
} }
} // namespace util } // namespace util

View file

@ -5,6 +5,7 @@
#include <memory> #include <memory>
#include <string> #include <string>
#include <boost/gil.hpp>
#include <glm/glm.hpp> #include <glm/glm.hpp>
namespace scwx namespace scwx
@ -14,13 +15,7 @@ namespace qt
namespace util namespace util
{ {
struct Glyph class FontBuffer;
{
GLuint textureId;
glm::ivec2 size; // pixels
glm::ivec2 bearing; // pixels
GLint advance; // 1/64 pixels
};
class FontImpl; class FontImpl;
@ -36,12 +31,24 @@ public:
Font(Font&&) = delete; Font(Font&&) = delete;
Font& operator=(Font&&) = delete; Font& operator=(Font&&) = delete;
void GenerateGlyphs(OpenGLFunctions& gl, float BufferText(std::shared_ptr<FontBuffer> buffer,
std::unordered_map<char, Glyph>& glyphs, const std::string& text,
unsigned int height); 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<Font> Create(const std::string& resource); static std::shared_ptr<Font> Create(const std::string& resource);
static std::shared_ptr<FontBuffer> CreateBuffer();
static void ClearBuffer(std::shared_ptr<FontBuffer> buffer);
static void RenderBuffer(OpenGLFunctions& gl,
std::shared_ptr<FontBuffer> buffer);
private: private:
std::unique_ptr<FontImpl> p; std::unique_ptr<FontImpl> p;
}; };