mirror of
https://github.com/ciphervance/supercell-wx.git
synced 2025-10-30 14:50:05 +00:00
Scalable fonts using freetype-gl
This commit is contained in:
parent
ec8e2643ff
commit
289ed430c7
8 changed files with 400 additions and 181 deletions
2
external/freetype-gl.cmake
vendored
2
external/freetype-gl.cmake
vendored
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -30,15 +30,15 @@ public:
|
||||||
TextShader& operator=(TextShader&&) noexcept;
|
TextShader& operator=(TextShader&&) noexcept;
|
||||||
|
|
||||||
bool Initialize();
|
bool Initialize();
|
||||||
void RenderText(const std::string& text,
|
void RenderText(const std::string& text,
|
||||||
float x,
|
float x,
|
||||||
float y,
|
float y,
|
||||||
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;
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
||||||
|
float Font::BufferText(std::shared_ptr<FontBuffer> 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<uint32_t>(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<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;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Font::GenerateGlyphs(OpenGLFunctions& gl,
|
float Font::Kerning(char c1, char c2) const
|
||||||
std::unordered_map<char, Glyph>& glyphs,
|
|
||||||
unsigned int height)
|
|
||||||
{
|
{
|
||||||
FT_Error error;
|
// TODO
|
||||||
FT_Face& face = p->face_;
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
// Allow single-byte texture colors
|
float Font::TextLength(const std::string& text, float scale) const
|
||||||
gl.glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
{
|
||||||
|
float x = 0.0f;
|
||||||
|
|
||||||
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_
|
||||||
|
<< "Character not found: " << 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;
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
GLuint texture;
|
x += glyph.advanceX_ * scale;
|
||||||
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}});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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<GLsizei>(p->atlas_->width),
|
||||||
|
static_cast<GLsizei>(p->atlas_->height),
|
||||||
|
0,
|
||||||
|
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())
|
||||||
|
|
@ -132,24 +269,45 @@ std::shared_ptr<Font> Font::Create(const std::string& resource)
|
||||||
return font;
|
return font;
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
void Font::ClearBuffer(std::shared_ptr<FontBuffer> buffer)
|
||||||
|
{
|
||||||
if (ft_ == nullptr && (error = FT_Init_FreeType(&ft_)) != 0)
|
if (buffer != nullptr)
|
||||||
{
|
{
|
||||||
BOOST_LOG_TRIVIAL(error)
|
std::scoped_lock lock(buffer->mutex_);
|
||||||
<< logPrefix_ << "Could not init FreeType library: " << error;
|
buffer->indices_.clear();
|
||||||
ft_ = nullptr;
|
buffer->vertices_.clear();
|
||||||
|
buffer->dirty_ = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Font::RenderBuffer(OpenGLFunctions& gl, std::shared_ptr<FontBuffer> 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<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
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue