mirror of
https://github.com/overte-org/overte.git
synced 2025-04-20 11:45:36 +02:00
Working on signed distance fields text rendering
This commit is contained in:
parent
71b677ef5c
commit
60317b3526
9 changed files with 74189 additions and 317 deletions
|
@ -913,7 +913,8 @@ void Avatar::setAttachmentData(const QVector<AttachmentData>& attachmentData) {
|
|||
|
||||
void Avatar::setDisplayName(const QString& displayName) {
|
||||
AvatarData::setDisplayName(displayName);
|
||||
_displayNameBoundingRect = textRenderer(DISPLAYNAME)->metrics().tightBoundingRect(displayName);
|
||||
// FIXME need to find an alternate mechanism for this.
|
||||
//_displayNameBoundingRect = textRenderer(DISPLAYNAME)->metrics().tightBoundingRect(displayName);
|
||||
}
|
||||
|
||||
void Avatar::setBillboard(const QByteArray& billboard) {
|
||||
|
|
16218
libraries/render-utils/src/FontInconsolataMedium.h
Normal file
16218
libraries/render-utils/src/FontInconsolataMedium.h
Normal file
File diff suppressed because it is too large
Load diff
10892
libraries/render-utils/src/FontJackInput.h
Normal file
10892
libraries/render-utils/src/FontJackInput.h
Normal file
File diff suppressed because it is too large
Load diff
36287
libraries/render-utils/src/FontRoboto.h
Normal file
36287
libraries/render-utils/src/FontRoboto.h
Normal file
File diff suppressed because it is too large
Load diff
10037
libraries/render-utils/src/FontTimeless.h
Normal file
10037
libraries/render-utils/src/FontTimeless.h
Normal file
File diff suppressed because it is too large
Load diff
7
libraries/render-utils/src/MatrixStack.cpp
Normal file
7
libraries/render-utils/src/MatrixStack.cpp
Normal file
|
@ -0,0 +1,7 @@
|
|||
#include "MatrixStack.h"
|
||||
|
||||
QMatrix4x4 fromGlm(const glm::mat4 & m) {
|
||||
return QMatrix4x4(&m[0][0]).transposed();
|
||||
return QMatrix4x4(&m[0][0]);
|
||||
}
|
||||
|
205
libraries/render-utils/src/MatrixStack.h
Normal file
205
libraries/render-utils/src/MatrixStack.h
Normal file
|
@ -0,0 +1,205 @@
|
|||
/************************************************************************************
|
||||
|
||||
Authors : Bradley Austin Davis <bdavis@saintandreas.org>
|
||||
Copyright : Copyright Brad Davis. All Rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
************************************************************************************/
|
||||
|
||||
#pragma once
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
#include <glm/gtc/noise.hpp>
|
||||
#include <glm/gtc/epsilon.hpp>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <stack>
|
||||
#include <QMatrix4x4>
|
||||
|
||||
|
||||
class MatrixStack : public std::stack<glm::mat4> {
|
||||
|
||||
public:
|
||||
|
||||
MatrixStack() {
|
||||
push(glm::mat4());
|
||||
}
|
||||
|
||||
explicit MatrixStack(const MatrixStack & other) {
|
||||
*((std::stack<glm::mat4>*)this) = *((std::stack<glm::mat4>*)&other);
|
||||
}
|
||||
|
||||
operator const glm::mat4 & () const {
|
||||
return top();
|
||||
}
|
||||
|
||||
MatrixStack & pop() {
|
||||
std::stack<glm::mat4>::pop();
|
||||
assert(!empty());
|
||||
return *this;
|
||||
}
|
||||
|
||||
MatrixStack & push() {
|
||||
emplace(top());
|
||||
return *this;
|
||||
}
|
||||
|
||||
MatrixStack & identity() {
|
||||
top() = glm::mat4();
|
||||
return *this;
|
||||
}
|
||||
|
||||
MatrixStack & push(const glm::mat4 & mat) {
|
||||
std::stack<glm::mat4>::push(mat);
|
||||
return *this;
|
||||
}
|
||||
|
||||
MatrixStack & rotate(const glm::mat3 & rotation) {
|
||||
return postMultiply(glm::mat4(rotation));
|
||||
}
|
||||
|
||||
MatrixStack & rotate(const glm::quat & rotation) {
|
||||
return postMultiply(glm::mat4_cast(rotation));
|
||||
}
|
||||
|
||||
MatrixStack & rotate(float theta, const glm::vec3 & axis) {
|
||||
return postMultiply(glm::rotate(glm::mat4(), theta, axis));
|
||||
}
|
||||
|
||||
MatrixStack & translate(float translation) {
|
||||
return translate(glm::vec3(translation, 0, 0));
|
||||
}
|
||||
|
||||
MatrixStack & translate(const glm::vec2 & translation) {
|
||||
return translate(glm::vec3(translation, 0));
|
||||
}
|
||||
|
||||
MatrixStack & translate(const glm::vec3 & translation) {
|
||||
return postMultiply(glm::translate(glm::mat4(), translation));
|
||||
}
|
||||
|
||||
MatrixStack & preTranslate(const glm::vec3 & translation) {
|
||||
return preMultiply(glm::translate(glm::mat4(), translation));
|
||||
}
|
||||
|
||||
|
||||
MatrixStack & scale(float factor) {
|
||||
return scale(glm::vec3(factor));
|
||||
}
|
||||
|
||||
MatrixStack & scale(const glm::vec3 & scale) {
|
||||
return postMultiply(glm::scale(glm::mat4(), scale));
|
||||
}
|
||||
|
||||
MatrixStack & transform(const glm::mat4 & xfm) {
|
||||
return postMultiply(xfm);
|
||||
}
|
||||
|
||||
MatrixStack & preMultiply(const glm::mat4 & xfm) {
|
||||
top() = xfm * top();
|
||||
return *this;
|
||||
}
|
||||
|
||||
MatrixStack & postMultiply(const glm::mat4 & xfm) {
|
||||
top() *= xfm;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Remove the rotation component of a matrix. useful for billboarding
|
||||
MatrixStack & unrotate() {
|
||||
glm::quat inverse = glm::inverse(glm::quat_cast(top()));
|
||||
top() = top() * glm::mat4_cast(inverse);
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Remove the translation component of a matrix. useful for skyboxing
|
||||
MatrixStack & untranslate() {
|
||||
top()[3] = glm::vec4(0, 0, 0, 1);
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename Function>
|
||||
void withPush(Function f) {
|
||||
size_t startingDepth = size();
|
||||
push();
|
||||
f();
|
||||
pop();
|
||||
assert(startingDepth = size());
|
||||
}
|
||||
|
||||
template <typename Function>
|
||||
void withIdentity(Function f) {
|
||||
withPush([&] {
|
||||
identity();
|
||||
f();
|
||||
});
|
||||
}
|
||||
|
||||
static MatrixStack & projection() {
|
||||
static MatrixStack projection;
|
||||
return projection;
|
||||
}
|
||||
|
||||
static MatrixStack & modelview() {
|
||||
static MatrixStack modelview;
|
||||
return modelview;
|
||||
}
|
||||
|
||||
template <typename Function>
|
||||
static void withPushAll(Function f) {
|
||||
withPush(projection(), modelview(), f);
|
||||
}
|
||||
|
||||
template <typename Function>
|
||||
static void withIdentityAll(Function f) {
|
||||
withPush(projection(), modelview(), [=] {
|
||||
projection().identity();
|
||||
modelview().identity();
|
||||
f();
|
||||
});
|
||||
}
|
||||
|
||||
template <typename Function>
|
||||
static void withPush(MatrixStack & stack, Function f) {
|
||||
stack.withPush(f);
|
||||
}
|
||||
|
||||
template <typename Function>
|
||||
static void withPush(MatrixStack & stack1, MatrixStack & stack2, Function f) {
|
||||
stack1.withPush([&]{
|
||||
stack2.withPush(f);
|
||||
});
|
||||
}
|
||||
|
||||
template <typename Function>
|
||||
static void withGlMatrices(Function f) {
|
||||
// Push the current stack, and then copy the values out of OpenGL
|
||||
withPushAll([&] {
|
||||
// Fetch the current matrices out of GL stack
|
||||
// FIXME, eliminate the usage of deprecated GL
|
||||
MatrixStack & mv = MatrixStack::modelview();
|
||||
MatrixStack & pr = MatrixStack::projection();
|
||||
glm::mat4 & mvm = mv.top();
|
||||
glGetFloatv(GL_MODELVIEW_MATRIX, &(mvm[0][0]));
|
||||
|
||||
glm::mat4 & prm = pr.top();
|
||||
glGetFloatv(GL_PROJECTION_MATRIX, &(prm[0][0]));
|
||||
f();
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
QMatrix4x4 fromGlm(const glm::mat4 & m);
|
||||
|
|
@ -9,32 +9,93 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <gpu/GPUConfig.h>
|
||||
#include "TextRenderer.h"
|
||||
#include "MatrixStack.h"
|
||||
|
||||
#include <gpu/GPUConfig.h>
|
||||
#include <QApplication>
|
||||
#include <QDesktopWidget>
|
||||
#include <QFont>
|
||||
#include <QPaintEngine>
|
||||
#include <QtDebug>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QWindow>
|
||||
#include <QBuffer>
|
||||
#include <QFile>
|
||||
|
||||
#include "TextRenderer.h"
|
||||
|
||||
#include "glm/glm.hpp"
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
|
||||
#include "gpu/GLBackend.h"
|
||||
#include "gpu/Stream.h"
|
||||
#include "FontInconsolataMedium.h"
|
||||
#include "FontRoboto.h"
|
||||
#include "FontJackInput.h"
|
||||
#include "FontTimeless.h"
|
||||
|
||||
namespace Shaders {
|
||||
// Normally we could use 'enum class' to avoid namespace pollution,
|
||||
// but we want easy conversion to GLuint
|
||||
namespace Attributes {
|
||||
enum {
|
||||
Position = 0,
|
||||
TexCoord = 1,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
// the width/height of the cached glyph textures
|
||||
const int IMAGE_SIZE = 512;
|
||||
const char TextRenderer::SHADER_TEXT_VS[] = R"XXXX(#version 330
|
||||
|
||||
uniform mat4 Projection = mat4(1);
|
||||
uniform mat4 ModelView = mat4(1);
|
||||
|
||||
layout(location = 0) in vec3 Position;
|
||||
layout(location = 1) in vec2 TexCoord;
|
||||
|
||||
out vec2 vTexCoord;
|
||||
|
||||
void main() {
|
||||
vTexCoord = TexCoord;
|
||||
gl_Position = Projection * ModelView * vec4(Position, 1);
|
||||
})XXXX";
|
||||
|
||||
const char TextRenderer::SHADER_TEXT_FS[] = R"XXXX(#version 330
|
||||
|
||||
uniform sampler2D Font;
|
||||
uniform vec4 Color;
|
||||
|
||||
in vec2 vTexCoord;
|
||||
out vec4 FragColor;
|
||||
|
||||
const float gamma = 2.6;
|
||||
const float smoothing = 100.0;
|
||||
|
||||
void main() {
|
||||
FragColor = vec4(1);
|
||||
|
||||
// retrieve signed distance
|
||||
float sdf = texture(Font, vTexCoord).r;
|
||||
|
||||
// perform adaptive anti-aliasing of the edges
|
||||
// The larger we're rendering, the less anti-aliasing we need
|
||||
float s = smoothing * length(fwidth(vTexCoord));
|
||||
float w = clamp( s, 0.0, 0.5);
|
||||
float a = smoothstep(0.5 - w, 0.5 + w, sdf);
|
||||
|
||||
// gamma correction for linear attenuation
|
||||
a = pow(a, 1.0 / gamma);
|
||||
|
||||
if (a < 0.001) {
|
||||
discard;
|
||||
}
|
||||
|
||||
// final color
|
||||
FragColor = vec4(Color.rgb, a);
|
||||
})XXXX";
|
||||
|
||||
const float TextRenderer::DTP_TO_METERS = 0.003528f;
|
||||
const float TextRenderer::METERS_TO_DTP = 1.0f / TextRenderer::DTP_TO_METERS;
|
||||
|
||||
static uint qHash(const TextRenderer::Properties& key, uint seed = 0) {
|
||||
// can be switched to qHash(key.font, seed) when we require Qt 5.3+
|
||||
return qHash(key.font.family(), qHash(key.font.pointSize(), seed));
|
||||
return qHash(key.font);
|
||||
}
|
||||
|
||||
static bool operator==(const TextRenderer::Properties& p1, const TextRenderer::Properties& p2) {
|
||||
|
@ -43,7 +104,7 @@ static bool operator==(const TextRenderer::Properties& p1, const TextRenderer::P
|
|||
|
||||
TextRenderer* TextRenderer::getInstance(const char* family, int pointSize, int weight, bool italic,
|
||||
EffectType effect, int effectThickness, const QColor& color) {
|
||||
Properties properties = { QFont(family, pointSize, weight, italic), effect, effectThickness, color };
|
||||
Properties properties = { QString(family), effect, effectThickness, color };
|
||||
TextRenderer*& instance = _instances[properties];
|
||||
if (!instance) {
|
||||
instance = new TextRenderer(properties);
|
||||
|
@ -51,18 +112,43 @@ TextRenderer* TextRenderer::getInstance(const char* family, int pointSize, int w
|
|||
return instance;
|
||||
}
|
||||
|
||||
|
||||
|
||||
TextRenderer::TextRenderer(const Properties& properties) :
|
||||
_effectType(properties.effect),
|
||||
_effectThickness(properties.effectThickness),
|
||||
_rowHeight(0),
|
||||
_color(properties.color) {
|
||||
|
||||
QBuffer buffer;
|
||||
if (properties.font == MONO_FONT_FAMILY) {
|
||||
buffer.setData((const char*)SDFF_JACKINPUT, sizeof(SDFF_JACKINPUT));
|
||||
} else if (properties.font == INCONSOLATA_FONT_FAMILY) {
|
||||
buffer.setData((const char*)SDFF_INCONSOLATA_MEDIUM, sizeof(SDFF_INCONSOLATA_MEDIUM));
|
||||
} else if (properties.font == SANS_FONT_FAMILY) {
|
||||
buffer.setData((const char*)SDFF_ROBOTO, sizeof(SDFF_ROBOTO));
|
||||
} else {
|
||||
buffer.setData((const char*)SDFF_TIMELESS, sizeof(SDFF_TIMELESS));
|
||||
}
|
||||
buffer.open(QIODevice::ReadOnly);
|
||||
read(buffer);
|
||||
}
|
||||
|
||||
|
||||
TextRenderer::~TextRenderer() {
|
||||
glDeleteTextures(_allTextureIDs.size(), _allTextureIDs.constData());
|
||||
}
|
||||
|
||||
int TextRenderer::calculateHeight(const char* str) {
|
||||
const TextRenderer::Glyph & TextRenderer::getGlyph(const QChar & c) const {
|
||||
if (!_glyphs.contains(c)) {
|
||||
return _glyphs[QChar('?')];
|
||||
}
|
||||
return _glyphs[c];
|
||||
};
|
||||
|
||||
int TextRenderer::calculateHeight(const char* str) const {
|
||||
int maxHeight = 0;
|
||||
for (const char* ch = str; *ch != 0; ch++) {
|
||||
const Glyph& glyph = getGlyph(*ch);
|
||||
if (glyph.textureID() == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (glyph.bounds().height() > maxHeight) {
|
||||
maxHeight = glyph.bounds().height();
|
||||
}
|
||||
|
@ -70,258 +156,371 @@ int TextRenderer::calculateHeight(const char* str) {
|
|||
return maxHeight;
|
||||
}
|
||||
|
||||
int TextRenderer::draw(int x, int y, const char* str, const glm::vec4& color) {
|
||||
int compactColor = ((int(color.x * 255.0f) & 0xFF)) |
|
||||
((int(color.y * 255.0f) & 0xFF) << 8) |
|
||||
((int(color.z * 255.0f) & 0xFF) << 16) |
|
||||
((int(color.w * 255.0f) & 0xFF) << 24);
|
||||
|
||||
int maxHeight = 0;
|
||||
for (const char* ch = str; *ch != 0; ch++) {
|
||||
const Glyph& glyph = getGlyph(*ch);
|
||||
if (glyph.textureID() == 0) {
|
||||
x += glyph.width();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (glyph.bounds().height() > maxHeight) {
|
||||
maxHeight = glyph.bounds().height();
|
||||
}
|
||||
//glBindTexture(GL_TEXTURE_2D, glyph.textureID());
|
||||
|
||||
int left = x + glyph.bounds().x();
|
||||
int right = x + glyph.bounds().x() + glyph.bounds().width();
|
||||
int bottom = y + glyph.bounds().y();
|
||||
int top = y + glyph.bounds().y() + glyph.bounds().height();
|
||||
|
||||
glm::vec2 leftBottom = glm::vec2(float(left), float(bottom));
|
||||
glm::vec2 rightTop = glm::vec2(float(right), float(top));
|
||||
|
||||
float scale = QApplication::desktop()->windowHandle()->devicePixelRatio() / IMAGE_SIZE;
|
||||
float ls = glyph.location().x() * scale;
|
||||
float rs = (glyph.location().x() + glyph.bounds().width()) * scale;
|
||||
float bt = glyph.location().y() * scale;
|
||||
float tt = (glyph.location().y() + glyph.bounds().height()) * scale;
|
||||
|
||||
const int NUM_COORDS_SCALARS_PER_GLYPH = 16;
|
||||
float vertexBuffer[NUM_COORDS_SCALARS_PER_GLYPH] = { leftBottom.x, leftBottom.y, ls, bt,
|
||||
rightTop.x, leftBottom.y, rs, bt,
|
||||
rightTop.x, rightTop.y, rs, tt,
|
||||
leftBottom.x, rightTop.y, ls, tt, };
|
||||
|
||||
const int NUM_COLOR_SCALARS_PER_GLYPH = 4;
|
||||
int colorBuffer[NUM_COLOR_SCALARS_PER_GLYPH] = { compactColor, compactColor, compactColor, compactColor };
|
||||
|
||||
gpu::Buffer::Size offset = sizeof(vertexBuffer) * _numGlyphsBatched;
|
||||
gpu::Buffer::Size colorOffset = sizeof(colorBuffer) * _numGlyphsBatched;
|
||||
if ((offset + sizeof(vertexBuffer)) > _glyphsBuffer->getSize()) {
|
||||
_glyphsBuffer->append(sizeof(vertexBuffer), (gpu::Buffer::Byte*) vertexBuffer);
|
||||
_glyphsColorBuffer->append(sizeof(colorBuffer), (gpu::Buffer::Byte*) colorBuffer);
|
||||
} else {
|
||||
_glyphsBuffer->setSubData(offset, sizeof(vertexBuffer), (gpu::Buffer::Byte*) vertexBuffer);
|
||||
_glyphsColorBuffer->setSubData(colorOffset, sizeof(colorBuffer), (gpu::Buffer::Byte*) colorBuffer);
|
||||
}
|
||||
_numGlyphsBatched++;
|
||||
|
||||
x += glyph.width();
|
||||
}
|
||||
|
||||
// TODO: remove these calls once we move to a full batched rendering of the text, for now, one draw call per draw() function call
|
||||
drawBatch();
|
||||
clearBatch();
|
||||
|
||||
return maxHeight;
|
||||
template <class T>
|
||||
void readStream(QIODevice & in, T & t) {
|
||||
in.read((char*)&t, sizeof(t));
|
||||
}
|
||||
|
||||
int TextRenderer::computeWidth(char ch)
|
||||
template <typename T, size_t N>
|
||||
void readStream(T(&t)[N]) {
|
||||
in.read((char*)t, N);
|
||||
}
|
||||
|
||||
void TextRenderer::read(const QString & path) {
|
||||
QFile file(path);
|
||||
file.open(QFile::ReadOnly);
|
||||
read(file);
|
||||
}
|
||||
|
||||
void TextRenderer::Glyph::read(QIODevice & in) {
|
||||
uint16_t charcode;
|
||||
readStream(in, charcode);
|
||||
c = charcode;
|
||||
readStream(in, ul);
|
||||
readStream(in, size);
|
||||
readStream(in, offset);
|
||||
readStream(in, d);
|
||||
lr = ul + size;
|
||||
}
|
||||
|
||||
void TextRenderer::read(QIODevice & in) {
|
||||
uint8_t header[4];
|
||||
readStream(in, header);
|
||||
if (memcmp(header, "SDFF", 4)) {
|
||||
qFatal("Bad SDFF file");
|
||||
}
|
||||
|
||||
uint16_t version;
|
||||
readStream(in, version);
|
||||
|
||||
// read font name
|
||||
_family = "";
|
||||
if (version > 0x0001) {
|
||||
char c;
|
||||
readStream(in, c);
|
||||
while (c) {
|
||||
_family += c;
|
||||
readStream(in, c);
|
||||
}
|
||||
}
|
||||
|
||||
// read font data
|
||||
readStream(in, _leading);
|
||||
readStream(in, _ascent);
|
||||
readStream(in, _descent);
|
||||
readStream(in, _spaceWidth);
|
||||
_fontSize = _ascent + _descent;
|
||||
|
||||
// read metrics data
|
||||
_glyphs.clear();
|
||||
|
||||
uint16_t count;
|
||||
readStream(in, count);
|
||||
|
||||
for (int i = 0; i < count; ++i) {
|
||||
Glyph g;
|
||||
g.read(in);
|
||||
_glyphs[g.c] = g;
|
||||
}
|
||||
|
||||
// read image data
|
||||
if (!_image.loadFromData(in.readAll(), "PNG")) {
|
||||
qFatal("Failed to read SDFF image");
|
||||
}
|
||||
setupGL();
|
||||
}
|
||||
|
||||
struct TextureVertex {
|
||||
glm::vec2 pos;
|
||||
glm::vec2 tex;
|
||||
TextureVertex() {
|
||||
}
|
||||
TextureVertex(const glm::vec2 & pos, const glm::vec2 & tex)
|
||||
: pos(pos), tex(tex) {
|
||||
}
|
||||
TextureVertex(const QPointF & pos, const QPointF & tex)
|
||||
: pos(pos.x(), pos.y()), tex(tex.x(), tex.y()) {
|
||||
}
|
||||
};
|
||||
|
||||
struct QuadBuilder {
|
||||
TextureVertex vertices[4];
|
||||
QuadBuilder(const QRectF & r, const QRectF & tr) {
|
||||
vertices[0] = TextureVertex(r.bottomLeft(), tr.topLeft());
|
||||
vertices[1] = TextureVertex(r.bottomRight(), tr.topRight());
|
||||
vertices[2] = TextureVertex(r.topRight(), tr.bottomRight());
|
||||
vertices[3] = TextureVertex(r.topLeft(), tr.bottomLeft());
|
||||
}
|
||||
};
|
||||
|
||||
QRectF glmToRect(const glm::vec2 & pos, const glm::vec2 & size) {
|
||||
QRectF result(pos.x, pos.y, size.x, size.y);
|
||||
return result;
|
||||
}
|
||||
|
||||
glm::uvec2 toGlm(const QSize & size) {
|
||||
return glm::uvec2(size.width(), size.height());
|
||||
}
|
||||
|
||||
QRectF TextRenderer::Glyph::bounds() const {
|
||||
return glmToRect(offset, size);
|
||||
}
|
||||
|
||||
QRectF TextRenderer::Glyph::textureBounds(const glm::vec2 & textureSize) const {
|
||||
glm::vec2 pos = ul;
|
||||
glm::vec2 size = lr - ul;
|
||||
pos /= textureSize;
|
||||
size /= textureSize;
|
||||
return glmToRect(pos, size);
|
||||
}
|
||||
|
||||
void TextRenderer::setupGL() {
|
||||
_texture = TexturePtr(new QOpenGLTexture(_image, QOpenGLTexture::DontGenerateMipMaps));
|
||||
_program = ProgramPtr(new QOpenGLShaderProgram());
|
||||
if (!_program->create()) {
|
||||
qFatal("Could not create text shader");
|
||||
}
|
||||
if (
|
||||
!_program->addShaderFromSourceCode(QOpenGLShader::Vertex, SHADER_TEXT_VS) ||
|
||||
!_program->addShaderFromSourceCode(QOpenGLShader::Fragment, SHADER_TEXT_FS) ||
|
||||
!_program->link()) {
|
||||
qFatal(_program->log().toLocal8Bit().constData());
|
||||
}
|
||||
|
||||
std::vector<TextureVertex> vertexData;
|
||||
std::vector<GLuint> indexData;
|
||||
vertexData.reserve(_glyphs.size() * 4);
|
||||
std::for_each(_glyphs.begin(), _glyphs.end(), [&](Glyph & m){
|
||||
GLuint index = (GLuint)vertexData.size();
|
||||
|
||||
QRectF bounds = m.bounds();
|
||||
QRectF texBounds = m.textureBounds(toGlm(_image.size()));
|
||||
QuadBuilder qb(bounds, texBounds);
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
vertexData.push_back(qb.vertices[i]);
|
||||
}
|
||||
|
||||
m.indexOffset = indexData.size() * sizeof(GLuint);
|
||||
// FIXME use triangle strips + primitive restart index
|
||||
indexData.push_back(index + 0);
|
||||
indexData.push_back(index + 1);
|
||||
indexData.push_back(index + 2);
|
||||
indexData.push_back(index + 0);
|
||||
indexData.push_back(index + 2);
|
||||
indexData.push_back(index + 3);
|
||||
});
|
||||
|
||||
//_vao = VertexArrayPtr(new VertexArray());
|
||||
_vao = VertexArrayPtr(new QOpenGLVertexArrayObject());
|
||||
_vao->create();
|
||||
_vao->bind();
|
||||
|
||||
|
||||
_vertices = BufferPtr(new QOpenGLBuffer(QOpenGLBuffer::VertexBuffer));
|
||||
_vertices->create();
|
||||
_vertices->bind();
|
||||
_vertices->allocate(&vertexData[0], sizeof(TextureVertex) * vertexData.size());
|
||||
|
||||
_indices = BufferPtr(new QOpenGLBuffer(QOpenGLBuffer::IndexBuffer));
|
||||
_indices->create();
|
||||
_indices->bind();
|
||||
_indices->allocate(&indexData[0], sizeof(GLuint) * indexData.size());
|
||||
|
||||
GLsizei stride = (GLsizei)sizeof(TextureVertex);
|
||||
void* offset = (void*)offsetof(TextureVertex, tex);
|
||||
|
||||
glEnableVertexAttribArray(Shaders::Attributes::Position);
|
||||
glVertexAttribPointer(Shaders::Attributes::Position, 3, GL_FLOAT, false, stride, nullptr);
|
||||
glEnableVertexAttribArray(Shaders::Attributes::TexCoord);
|
||||
glVertexAttribPointer(Shaders::Attributes::TexCoord, 2, GL_FLOAT, false, stride, offset);
|
||||
_vao->release();
|
||||
}
|
||||
|
||||
int TextRenderer::computeWidth(const QChar & ch) const
|
||||
{
|
||||
return getGlyph(ch).width();
|
||||
}
|
||||
|
||||
int TextRenderer::computeWidth(const char* str)
|
||||
int TextRenderer::computeWidth(const QString & str) const
|
||||
{
|
||||
int width = 0;
|
||||
for (const char* ch = str; *ch != 0; ch++) {
|
||||
width += computeWidth(*ch);
|
||||
foreach(QChar c, str) {
|
||||
width += computeWidth(c);
|
||||
}
|
||||
return width;
|
||||
}
|
||||
|
||||
TextRenderer::TextRenderer(const Properties& properties) :
|
||||
_font(properties.font),
|
||||
_metrics(_font),
|
||||
_effectType(properties.effect),
|
||||
_effectThickness(properties.effectThickness),
|
||||
_x(IMAGE_SIZE),
|
||||
_y(IMAGE_SIZE),
|
||||
_rowHeight(0),
|
||||
_color(properties.color),
|
||||
_glyphsBuffer(new gpu::Buffer()),
|
||||
_glyphsColorBuffer(new gpu::Buffer()),
|
||||
_glyphsStreamFormat(new gpu::Stream::Format()),
|
||||
_glyphsStream(new gpu::BufferStream()),
|
||||
_numGlyphsBatched(0)
|
||||
{
|
||||
_glyphsStreamFormat->setAttribute(gpu::Stream::POSITION, 0, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::XYZ), 0);
|
||||
const int NUM_POS_COORDS = 2;
|
||||
const int VERTEX_TEXCOORD_OFFSET = NUM_POS_COORDS * sizeof(float);
|
||||
_glyphsStreamFormat->setAttribute(gpu::Stream::TEXCOORD, 0, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV), VERTEX_TEXCOORD_OFFSET);
|
||||
|
||||
_glyphsStreamFormat->setAttribute(gpu::Stream::COLOR, 1, gpu::Element(gpu::VEC4, gpu::UINT8, gpu::RGBA));
|
||||
|
||||
_glyphsStream->addBuffer(_glyphsBuffer, 0, _glyphsStreamFormat->getChannels().at(0)._stride);
|
||||
_glyphsStream->addBuffer(_glyphsColorBuffer, 0, _glyphsStreamFormat->getChannels().at(1)._stride);
|
||||
|
||||
_font.setKerning(false);
|
||||
int TextRenderer::computeWidth(const char * str) const {
|
||||
int width = 0;
|
||||
while (*str) {
|
||||
width += computeWidth(*str++);
|
||||
}
|
||||
return width;
|
||||
}
|
||||
|
||||
const Glyph& TextRenderer::getGlyph(char c) {
|
||||
Glyph& glyph = _glyphs[c];
|
||||
if (glyph.isValid()) {
|
||||
return glyph;
|
||||
}
|
||||
// we use 'J' as a representative size for the solid block character
|
||||
QChar ch = (c == SOLID_BLOCK_CHAR) ? QChar('J') : QChar(c);
|
||||
QRect baseBounds = _metrics.boundingRect(ch);
|
||||
if (baseBounds.isEmpty()) {
|
||||
glyph = Glyph(0, QPoint(), QRect(), _metrics.width(ch));
|
||||
return glyph;
|
||||
}
|
||||
// grow the bounds to account for effect, if any
|
||||
if (_effectType == SHADOW_EFFECT) {
|
||||
baseBounds.adjust(-_effectThickness, 0, 0, _effectThickness);
|
||||
|
||||
} else if (_effectType == OUTLINE_EFFECT) {
|
||||
baseBounds.adjust(-_effectThickness, -_effectThickness, _effectThickness, _effectThickness);
|
||||
}
|
||||
|
||||
// grow the bounds to account for antialiasing
|
||||
baseBounds.adjust(-1, -1, 1, 1);
|
||||
|
||||
// adjust bounds for device pixel scaling
|
||||
float ratio = QApplication::desktop()->windowHandle()->devicePixelRatio();
|
||||
QRect bounds(baseBounds.x() * ratio, baseBounds.y() * ratio, baseBounds.width() * ratio, baseBounds.height() * ratio);
|
||||
|
||||
if (_x + bounds.width() > IMAGE_SIZE) {
|
||||
// we can't fit it on the current row; move to next
|
||||
_y += _rowHeight;
|
||||
_x = _rowHeight = 0;
|
||||
}
|
||||
if (_y + bounds.height() > IMAGE_SIZE) {
|
||||
// can't fit it on current texture; make a new one
|
||||
glGenTextures(1, &_currentTextureID);
|
||||
_x = _y = _rowHeight = 0;
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, _currentTextureID);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, IMAGE_SIZE, IMAGE_SIZE, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
|
||||
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
_allTextureIDs.append(_currentTextureID);
|
||||
|
||||
} else {
|
||||
glBindTexture(GL_TEXTURE_2D, _currentTextureID);
|
||||
}
|
||||
// render the glyph into an image and copy it into the texture
|
||||
QImage image(bounds.width(), bounds.height(), QImage::Format_ARGB32);
|
||||
if (c == SOLID_BLOCK_CHAR) {
|
||||
image.fill(_color);
|
||||
|
||||
} else {
|
||||
image.fill(0);
|
||||
QPainter painter(&image);
|
||||
QFont font = _font;
|
||||
if (ratio == 1.0f) {
|
||||
painter.setFont(_font);
|
||||
} else {
|
||||
QFont enlargedFont = _font;
|
||||
enlargedFont.setPointSize(_font.pointSize() * ratio);
|
||||
painter.setFont(enlargedFont);
|
||||
}
|
||||
if (_effectType == SHADOW_EFFECT) {
|
||||
for (int i = 0; i < _effectThickness * ratio; i++) {
|
||||
painter.drawText(-bounds.x() - 1 - i, -bounds.y() + 1 + i, ch);
|
||||
}
|
||||
} else if (_effectType == OUTLINE_EFFECT) {
|
||||
QPainterPath path;
|
||||
QFont font = _font;
|
||||
font.setStyleStrategy(QFont::ForceOutline);
|
||||
path.addText(-bounds.x() - 0.5, -bounds.y() + 0.5, font, ch);
|
||||
QPen pen;
|
||||
pen.setWidth(_effectThickness * ratio);
|
||||
pen.setJoinStyle(Qt::RoundJoin);
|
||||
pen.setCapStyle(Qt::RoundCap);
|
||||
painter.setPen(pen);
|
||||
painter.setRenderHint(QPainter::Antialiasing);
|
||||
painter.drawPath(path);
|
||||
}
|
||||
painter.setPen(_color);
|
||||
painter.drawText(-bounds.x(), -bounds.y(), ch);
|
||||
}
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, _x, _y, bounds.width(), bounds.height(), GL_RGBA, GL_UNSIGNED_BYTE, image.constBits());
|
||||
|
||||
glyph = Glyph(_currentTextureID, QPoint(_x / ratio, _y / ratio), baseBounds, _metrics.width(ch));
|
||||
_x += bounds.width();
|
||||
_rowHeight = qMax(_rowHeight, bounds.height());
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
return glyph;
|
||||
}
|
||||
|
||||
void TextRenderer::drawBatch() {
|
||||
if (_numGlyphsBatched <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Right now the drawBatch is called while calling the draw() function but in the future we'll need to apply the correct transform stack
|
||||
/*
|
||||
GLint matrixMode;
|
||||
glGetIntegerv(GL_MATRIX_MODE, &matrixMode);
|
||||
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
glPushMatrix();
|
||||
glLoadIdentity();
|
||||
|
||||
glMatrixMode(GL_PROJECTION);
|
||||
glPushMatrix();
|
||||
glLoadIdentity();
|
||||
*/
|
||||
|
||||
gpu::Batch batch;
|
||||
|
||||
glEnable(GL_TEXTURE_2D);
|
||||
// TODO: Apply the correct font atlas texture, for now only one texture per TextRenderer so it should be good
|
||||
glBindTexture(GL_TEXTURE_2D, _currentTextureID);
|
||||
|
||||
batch.setInputFormat(_glyphsStreamFormat);
|
||||
batch.setInputStream(0, *_glyphsStream);
|
||||
batch.draw(gpu::QUADS, _numGlyphsBatched * 4, 0);
|
||||
|
||||
gpu::GLBackend::renderBatch(batch);
|
||||
|
||||
glDisableClientState(GL_VERTEX_ARRAY);
|
||||
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
|
||||
glDisableClientState(GL_COLOR_ARRAY);
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
glDisable(GL_TEXTURE_2D);
|
||||
|
||||
// TODO: Right now the drawBatch is called while calling the draw() function but in the future we'll need to apply the correct transform stack
|
||||
/*
|
||||
glPopMatrix();
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
glPopMatrix();
|
||||
glMatrixMode(matrixMode);
|
||||
*/
|
||||
}
|
||||
|
||||
void TextRenderer::clearBatch() {
|
||||
_numGlyphsBatched = 0;
|
||||
int TextRenderer::computeWidth(char ch) const {
|
||||
return computeWidth(QChar(ch));
|
||||
}
|
||||
|
||||
QHash<TextRenderer::Properties, TextRenderer*> TextRenderer::_instances;
|
||||
|
||||
Glyph::Glyph(int textureID, const QPoint& location, const QRect& bounds, int width) :
|
||||
_textureID(textureID), _location(location), _bounds(bounds), _width(width) {
|
||||
float TextRenderer::drawString(
|
||||
float x, float y,
|
||||
const QString & str,
|
||||
const glm::vec4& color,
|
||||
float fontSize,
|
||||
float maxWidth) {
|
||||
|
||||
// This is a hand made scale intended to match the previous scale of text in the application
|
||||
float scale = 0.25f; // DTP_TO_METERS;
|
||||
if (fontSize > 0.0) {
|
||||
scale *= fontSize / _fontSize;
|
||||
}
|
||||
|
||||
//bool wrap = false; // (maxWidth == maxWidth);
|
||||
//if (wrap) {
|
||||
// maxWidth /= scale;
|
||||
//}
|
||||
|
||||
// Stores how far we've moved from the start of the string, in DTP units
|
||||
static const float SPACE_ADVANCE = getGlyph(' ').d;
|
||||
glm::vec2 advance;
|
||||
MatrixStack::withGlMatrices([&] {
|
||||
// Fetch the matrices out of GL
|
||||
_program->bind();
|
||||
_program->setUniformValue("Color", color.r, color.g, color.b, color.a);
|
||||
_program->setUniformValue("Projection", fromGlm(MatrixStack::projection().top()));
|
||||
_texture->bind();
|
||||
_vao->bind();
|
||||
|
||||
MatrixStack & mv = MatrixStack::modelview();
|
||||
MatrixStack & pr = MatrixStack::projection();
|
||||
// scale the modelview into font units
|
||||
mv.translate(glm::vec2(x, y)).translate(glm::vec2(0, scale * -_ascent)).scale(glm::vec3(scale, -scale, scale));
|
||||
|
||||
foreach(QString token, str.split(" ")) {
|
||||
// float tokenWidth = measureWidth(token, fontSize);
|
||||
// if (wrap && 0 != advance.x && (advance.x + tokenWidth) > maxWidth) {
|
||||
// advance.x = 0;
|
||||
// advance.y -= (_ascent + _descent);
|
||||
// }
|
||||
|
||||
foreach(QChar c, token) {
|
||||
if (QChar('\n') == c) {
|
||||
advance.x = 0;
|
||||
advance.y -= (_ascent + _descent);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_glyphs.contains(c)) {
|
||||
c = QChar('?');
|
||||
}
|
||||
|
||||
// get metrics for this character to speed up measurements
|
||||
const Glyph & m = _glyphs[c];
|
||||
//if (wrap && ((advance.x + m.d) > maxWidth)) {
|
||||
// advance.x = 0;
|
||||
// advance.y -= (_ascent + _descent);
|
||||
//}
|
||||
|
||||
// We create an offset vec2 to hold the local offset of this character
|
||||
// This includes compensating for the inverted Y axis of the font
|
||||
// coordinates
|
||||
glm::vec2 offset(advance);
|
||||
offset.y -= m.size.y;
|
||||
// Bind the new position
|
||||
mv.withPush([&] {
|
||||
mv.translate(offset);
|
||||
_program->setUniformValue("ModelView", fromGlm(mv.top()));
|
||||
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, (void*)(m.indexOffset));
|
||||
});
|
||||
advance.x += m.d;//+ m.offset.x;// font->getAdvance(m, mFontSize);
|
||||
}
|
||||
advance.x += SPACE_ADVANCE;
|
||||
}
|
||||
|
||||
_vao->release();
|
||||
_program->release();
|
||||
});
|
||||
|
||||
return advance.x * scale;
|
||||
}
|
||||
|
||||
|
||||
#if 0
|
||||
|
||||
int TextRenderer::draw(int x, int y, const char* str, const glm::vec4& color) {
|
||||
int compactColor = ((int(color.x * 255.0f) & 0xFF)) |
|
||||
((int(color.y * 255.0f) & 0xFF) << 8) |
|
||||
((int(color.z * 255.0f) & 0xFF) << 16) |
|
||||
((int(color.w * 255.0f) & 0xFF) << 24);
|
||||
|
||||
int maxHeight = 0;
|
||||
for (const char* ch = str; *ch != 0; ch++) {
|
||||
const Glyph& glyph = getGlyph(*ch);
|
||||
if (glyph.textureID() == 0) {
|
||||
x += glyph.width();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (glyph.bounds().height() > maxHeight) {
|
||||
maxHeight = glyph.bounds().height();
|
||||
}
|
||||
//glBindTexture(GL_TEXTURE_2D, glyph.textureID());
|
||||
|
||||
int left = x + glyph.bounds().x();
|
||||
int right = x + glyph.bounds().x() + glyph.bounds().width();
|
||||
int bottom = y + glyph.bounds().y();
|
||||
int top = y + glyph.bounds().y() + glyph.bounds().height();
|
||||
|
||||
glm::vec2 leftBottom = glm::vec2(float(left), float(bottom));
|
||||
glm::vec2 rightTop = glm::vec2(float(right), float(top));
|
||||
|
||||
float scale = QApplication::desktop()->windowHandle()->devicePixelRatio() / IMAGE_SIZE;
|
||||
float ls = glyph.location().x() * scale;
|
||||
float rs = (glyph.location().x() + glyph.bounds().width()) * scale;
|
||||
float bt = glyph.location().y() * scale;
|
||||
float tt = (glyph.location().y() + glyph.bounds().height()) * scale;
|
||||
|
||||
const int NUM_COORDS_SCALARS_PER_GLYPH = 16;
|
||||
float vertexBuffer[NUM_COORDS_SCALARS_PER_GLYPH] = { leftBottom.x, leftBottom.y, ls, bt,
|
||||
rightTop.x, leftBottom.y, rs, bt,
|
||||
rightTop.x, rightTop.y, rs, tt,
|
||||
leftBottom.x, rightTop.y, ls, tt, };
|
||||
|
||||
const int NUM_COLOR_SCALARS_PER_GLYPH = 4;
|
||||
int colorBuffer[NUM_COLOR_SCALARS_PER_GLYPH] = { compactColor, compactColor, compactColor, compactColor };
|
||||
|
||||
gpu::Buffer::Size offset = sizeof(vertexBuffer) * _numGlyphsBatched;
|
||||
gpu::Buffer::Size colorOffset = sizeof(colorBuffer) * _numGlyphsBatched;
|
||||
if ((offset + sizeof(vertexBuffer)) > _glyphsBuffer->getSize()) {
|
||||
_glyphsBuffer->append(sizeof(vertexBuffer), (gpu::Buffer::Byte*) vertexBuffer);
|
||||
_glyphsColorBuffer->append(sizeof(colorBuffer), (gpu::Buffer::Byte*) colorBuffer);
|
||||
} else {
|
||||
_glyphsBuffer->setSubData(offset, sizeof(vertexBuffer), (gpu::Buffer::Byte*) vertexBuffer);
|
||||
_glyphsColorBuffer->setSubData(colorOffset, sizeof(colorBuffer), (gpu::Buffer::Byte*) colorBuffer);
|
||||
}
|
||||
_numGlyphsBatched++;
|
||||
|
||||
x += glyph.width();
|
||||
}
|
||||
|
||||
// TODO: remove these calls once we move to a full batched rendering of the text, for now, one draw call per draw() function call
|
||||
drawBatch();
|
||||
clearBatch();
|
||||
|
||||
return maxHeight;
|
||||
}
|
||||
//_glyphsStreamFormat->setAttribute(gpu::Stream::POSITION, 0, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::XYZ), 0);
|
||||
//const int NUM_POS_COORDS = 2;
|
||||
//const int VERTEX_TEXCOORD_OFFSET = NUM_POS_COORDS * sizeof(float);
|
||||
//_glyphsStreamFormat->setAttribute(gpu::Stream::TEXCOORD, 0, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV), VERTEX_TEXCOORD_OFFSET);
|
||||
|
||||
//_glyphsStreamFormat->setAttribute(gpu::Stream::COLOR, 1, gpu::Element(gpu::VEC4, gpu::UINT8, gpu::RGBA));
|
||||
|
||||
//_glyphsStream->addBuffer(_glyphsBuffer, 0, _glyphsStreamFormat->getChannels().at(0)._stride);
|
||||
//_glyphsStream->addBuffer(_glyphsColorBuffer, 0, _glyphsStreamFormat->getChannels().at(1)._stride);
|
||||
|
||||
//_font.setKerning(false);
|
||||
#endif
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
|
||||
#include <gpu/GPUConfig.h>
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include <unordered_map>
|
||||
#include <QColor>
|
||||
#include <QFont>
|
||||
#include <QFontMetrics>
|
||||
|
@ -22,11 +22,15 @@
|
|||
#include <QImage>
|
||||
#include <QVector>
|
||||
|
||||
// FIXME, decouple from the GL headers
|
||||
#include <QOpenGLShaderProgram>
|
||||
#include <QOpenGLTexture>
|
||||
#include <QOpenGLVertexArrayObject>
|
||||
#include <QOpenGLBuffer>
|
||||
|
||||
#include <gpu/Resource.h>
|
||||
#include <gpu/Stream.h>
|
||||
|
||||
|
||||
|
||||
// a special "character" that renders as a solid block
|
||||
const char SOLID_BLOCK_CHAR = 127;
|
||||
|
||||
|
@ -45,19 +49,17 @@ const char SOLID_BLOCK_CHAR = 127;
|
|||
#define INCONSOLATA_FONT_WEIGHT QFont::Bold
|
||||
#endif
|
||||
|
||||
class Glyph;
|
||||
|
||||
class TextRenderer {
|
||||
public:
|
||||
|
||||
enum EffectType { NO_EFFECT, SHADOW_EFFECT, OUTLINE_EFFECT };
|
||||
|
||||
class Properties {
|
||||
public:
|
||||
QFont font;
|
||||
EffectType effect;
|
||||
int effectThickness;
|
||||
QColor color;
|
||||
QString font;
|
||||
EffectType effect;
|
||||
int effectThickness;
|
||||
QColor color;
|
||||
};
|
||||
|
||||
static TextRenderer* getInstance(const char* family, int pointSize = -1, int weight = -1, bool italic = false,
|
||||
|
@ -65,30 +67,72 @@ public:
|
|||
|
||||
~TextRenderer();
|
||||
|
||||
const QFontMetrics& metrics() const { return _metrics; }
|
||||
|
||||
// returns the height of the tallest character
|
||||
int calculateHeight(const char* str);
|
||||
int calculateHeight(const char * str) const;
|
||||
int calculateHeight(const QString & str) const;
|
||||
|
||||
int computeWidth(char ch) const;
|
||||
int computeWidth(const QChar & ch) const;
|
||||
|
||||
int computeWidth(const QString & str) const;
|
||||
int computeWidth(const char * str) const;
|
||||
|
||||
// also returns the height of the tallest character
|
||||
int draw(int x, int y, const char* str, const glm::vec4& color);
|
||||
|
||||
int computeWidth(char ch);
|
||||
int computeWidth(const char* str);
|
||||
|
||||
inline int draw(int x, int y, const char* str,
|
||||
const glm::vec4& color = glm::vec4(-1),
|
||||
float fontSize = -1,
|
||||
float maxWidth = -1
|
||||
) {
|
||||
return drawString(x, y, QString(str), color, fontSize, maxWidth);
|
||||
}
|
||||
|
||||
float drawString(
|
||||
float x, float y,
|
||||
const QString & str,
|
||||
const glm::vec4& color = glm::vec4(-1),
|
||||
float fontSize = -1,
|
||||
float maxWidth = -1);
|
||||
|
||||
void drawBatch();
|
||||
void clearBatch();
|
||||
private:
|
||||
|
||||
static QHash<Properties, TextRenderer*> _instances;
|
||||
|
||||
// Allow fonts to scale appropriately when rendered in a
|
||||
// scene where units are meters
|
||||
static const float DTP_TO_METERS; // = 0.003528f;
|
||||
static const float METERS_TO_DTP; // = 1.0 / DTP_TO_METERS;
|
||||
static const char SHADER_TEXT_FS[];
|
||||
static const char SHADER_TEXT_VS[];
|
||||
|
||||
TextRenderer(const Properties& properties);
|
||||
|
||||
const Glyph& getGlyph(char c);
|
||||
// stores the font metrics for a single character
|
||||
struct Glyph {
|
||||
QChar c;
|
||||
glm::vec2 ul;
|
||||
glm::vec2 lr;
|
||||
glm::vec2 size;
|
||||
glm::vec2 offset;
|
||||
float d; // xadvance - adjusts character positioning
|
||||
size_t indexOffset;
|
||||
|
||||
// the font to render
|
||||
QFont _font;
|
||||
|
||||
// the font metrics
|
||||
QFontMetrics _metrics;
|
||||
int width() const {
|
||||
return size.x;
|
||||
}
|
||||
QRectF bounds() const;
|
||||
QRectF textureBounds(const glm::vec2 & textureSize) const;
|
||||
|
||||
void read(QIODevice & in);
|
||||
};
|
||||
|
||||
using TexturePtr = QSharedPointer < QOpenGLTexture >;
|
||||
using VertexArrayPtr = QSharedPointer< QOpenGLVertexArrayObject >;
|
||||
using ProgramPtr = QSharedPointer < QOpenGLShaderProgram >;
|
||||
using BufferPtr = QSharedPointer < QOpenGLBuffer >;
|
||||
|
||||
const Glyph& getGlyph(const QChar & c) const;
|
||||
|
||||
// the type of effect to apply
|
||||
EffectType _effectType;
|
||||
|
@ -97,58 +141,40 @@ private:
|
|||
int _effectThickness;
|
||||
|
||||
// maps characters to cached glyph info
|
||||
QHash<char, Glyph> _glyphs;
|
||||
QHash<QChar, Glyph> _glyphs;
|
||||
|
||||
// the id of the glyph texture to which we're currently writing
|
||||
GLuint _currentTextureID;
|
||||
|
||||
// the position within the current glyph texture
|
||||
int _x, _y;
|
||||
int _pointSize;
|
||||
|
||||
// the height of the current row of characters
|
||||
int _rowHeight;
|
||||
|
||||
// the list of all texture ids for which we're responsible
|
||||
QVector<GLuint> _allTextureIDs;
|
||||
|
||||
// text color
|
||||
QColor _color;
|
||||
|
||||
// Graphics Buffer containing the current accumulated glyphs to render
|
||||
gpu::BufferPointer _glyphsBuffer;
|
||||
gpu::BufferPointer _glyphsColorBuffer;
|
||||
gpu::Stream::FormatPointer _glyphsStreamFormat;
|
||||
gpu::BufferStreamPointer _glyphsStream;
|
||||
int _numGlyphsBatched;
|
||||
|
||||
static QHash<Properties, TextRenderer*> _instances;
|
||||
QString _family;
|
||||
float _fontSize{ 12 };
|
||||
float _leading{ 0 };
|
||||
float _ascent{ 0 };
|
||||
float _descent{ 0 };
|
||||
float _spaceWidth{ 0 };
|
||||
|
||||
BufferPtr _vertices;
|
||||
BufferPtr _indices;
|
||||
TexturePtr _texture;
|
||||
VertexArrayPtr _vao;
|
||||
QImage _image;
|
||||
ProgramPtr _program;
|
||||
|
||||
// Parse the signed distance field font file
|
||||
void read(const QString & path);
|
||||
void read(QIODevice & path);
|
||||
|
||||
// Initialize the OpenGL structures
|
||||
void setupGL();
|
||||
};
|
||||
|
||||
class Glyph {
|
||||
public:
|
||||
|
||||
Glyph(int textureID = 0, const QPoint& location = QPoint(), const QRect& bounds = QRect(), int width = 0);
|
||||
|
||||
GLuint textureID() const { return _textureID; }
|
||||
const QPoint& location () const { return _location; }
|
||||
const QRect& bounds() const { return _bounds; }
|
||||
int width () const { return _width; }
|
||||
|
||||
bool isValid() { return _width != 0; }
|
||||
|
||||
private:
|
||||
|
||||
// the id of the OpenGL texture containing the glyph
|
||||
GLuint _textureID;
|
||||
|
||||
// the location of the character within the texture
|
||||
QPoint _location;
|
||||
|
||||
// the bounds of the character
|
||||
QRect _bounds;
|
||||
|
||||
// the width of the character (distance to next, as opposed to bounds width)
|
||||
int _width;
|
||||
};
|
||||
|
||||
#endif // hifi_TextRenderer_h
|
||||
|
|
Loading…
Reference in a new issue