mirror of
https://github.com/overte-org/overte.git
synced 2025-08-10 20:03:06 +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) {
|
void Avatar::setDisplayName(const QString& displayName) {
|
||||||
AvatarData::setDisplayName(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) {
|
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
|
// 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 <QApplication>
|
||||||
#include <QDesktopWidget>
|
|
||||||
#include <QFont>
|
|
||||||
#include <QPaintEngine>
|
|
||||||
#include <QtDebug>
|
#include <QtDebug>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QStringList>
|
#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 <glm/gtc/matrix_transform.hpp>
|
||||||
|
|
||||||
#include "gpu/GLBackend.h"
|
#include "gpu/GLBackend.h"
|
||||||
#include "gpu/Stream.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 char TextRenderer::SHADER_TEXT_VS[] = R"XXXX(#version 330
|
||||||
const int IMAGE_SIZE = 512;
|
|
||||||
|
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) {
|
static uint qHash(const TextRenderer::Properties& key, uint seed = 0) {
|
||||||
// can be switched to qHash(key.font, seed) when we require Qt 5.3+
|
// 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) {
|
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,
|
TextRenderer* TextRenderer::getInstance(const char* family, int pointSize, int weight, bool italic,
|
||||||
EffectType effect, int effectThickness, const QColor& color) {
|
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];
|
TextRenderer*& instance = _instances[properties];
|
||||||
if (!instance) {
|
if (!instance) {
|
||||||
instance = new TextRenderer(properties);
|
instance = new TextRenderer(properties);
|
||||||
|
@ -51,18 +112,43 @@ TextRenderer* TextRenderer::getInstance(const char* family, int pointSize, int w
|
||||||
return instance;
|
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() {
|
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;
|
int maxHeight = 0;
|
||||||
for (const char* ch = str; *ch != 0; ch++) {
|
for (const char* ch = str; *ch != 0; ch++) {
|
||||||
const Glyph& glyph = getGlyph(*ch);
|
const Glyph& glyph = getGlyph(*ch);
|
||||||
if (glyph.textureID() == 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (glyph.bounds().height() > maxHeight) {
|
if (glyph.bounds().height() > maxHeight) {
|
||||||
maxHeight = glyph.bounds().height();
|
maxHeight = glyph.bounds().height();
|
||||||
}
|
}
|
||||||
|
@ -70,258 +156,371 @@ int TextRenderer::calculateHeight(const char* str) {
|
||||||
return maxHeight;
|
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;
|
template <class T>
|
||||||
for (const char* ch = str; *ch != 0; ch++) {
|
void readStream(QIODevice & in, T & t) {
|
||||||
const Glyph& glyph = getGlyph(*ch);
|
in.read((char*)&t, sizeof(t));
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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();
|
return getGlyph(ch).width();
|
||||||
}
|
}
|
||||||
|
|
||||||
int TextRenderer::computeWidth(const char* str)
|
int TextRenderer::computeWidth(const QString & str) const
|
||||||
{
|
{
|
||||||
int width = 0;
|
int width = 0;
|
||||||
for (const char* ch = str; *ch != 0; ch++) {
|
foreach(QChar c, str) {
|
||||||
width += computeWidth(*ch);
|
width += computeWidth(c);
|
||||||
}
|
}
|
||||||
return width;
|
return width;
|
||||||
}
|
}
|
||||||
|
|
||||||
TextRenderer::TextRenderer(const Properties& properties) :
|
int TextRenderer::computeWidth(const char * str) const {
|
||||||
_font(properties.font),
|
int width = 0;
|
||||||
_metrics(_font),
|
while (*str) {
|
||||||
_effectType(properties.effect),
|
width += computeWidth(*str++);
|
||||||
_effectThickness(properties.effectThickness),
|
}
|
||||||
_x(IMAGE_SIZE),
|
return width;
|
||||||
_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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const Glyph& TextRenderer::getGlyph(char c) {
|
int TextRenderer::computeWidth(char ch) const {
|
||||||
Glyph& glyph = _glyphs[c];
|
return computeWidth(QChar(ch));
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QHash<TextRenderer::Properties, TextRenderer*> TextRenderer::_instances;
|
QHash<TextRenderer::Properties, TextRenderer*> TextRenderer::_instances;
|
||||||
|
|
||||||
Glyph::Glyph(int textureID, const QPoint& location, const QRect& bounds, int width) :
|
float TextRenderer::drawString(
|
||||||
_textureID(textureID), _location(location), _bounds(bounds), _width(width) {
|
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 <gpu/GPUConfig.h>
|
||||||
#include <glm/glm.hpp>
|
#include <glm/glm.hpp>
|
||||||
|
#include <unordered_map>
|
||||||
#include <QColor>
|
#include <QColor>
|
||||||
#include <QFont>
|
#include <QFont>
|
||||||
#include <QFontMetrics>
|
#include <QFontMetrics>
|
||||||
|
@ -22,11 +22,15 @@
|
||||||
#include <QImage>
|
#include <QImage>
|
||||||
#include <QVector>
|
#include <QVector>
|
||||||
|
|
||||||
|
// FIXME, decouple from the GL headers
|
||||||
|
#include <QOpenGLShaderProgram>
|
||||||
|
#include <QOpenGLTexture>
|
||||||
|
#include <QOpenGLVertexArrayObject>
|
||||||
|
#include <QOpenGLBuffer>
|
||||||
|
|
||||||
#include <gpu/Resource.h>
|
#include <gpu/Resource.h>
|
||||||
#include <gpu/Stream.h>
|
#include <gpu/Stream.h>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// a special "character" that renders as a solid block
|
// a special "character" that renders as a solid block
|
||||||
const char SOLID_BLOCK_CHAR = 127;
|
const char SOLID_BLOCK_CHAR = 127;
|
||||||
|
|
||||||
|
@ -45,19 +49,17 @@ const char SOLID_BLOCK_CHAR = 127;
|
||||||
#define INCONSOLATA_FONT_WEIGHT QFont::Bold
|
#define INCONSOLATA_FONT_WEIGHT QFont::Bold
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
class Glyph;
|
|
||||||
|
|
||||||
class TextRenderer {
|
class TextRenderer {
|
||||||
public:
|
public:
|
||||||
|
|
||||||
enum EffectType { NO_EFFECT, SHADOW_EFFECT, OUTLINE_EFFECT };
|
enum EffectType { NO_EFFECT, SHADOW_EFFECT, OUTLINE_EFFECT };
|
||||||
|
|
||||||
class Properties {
|
class Properties {
|
||||||
public:
|
public:
|
||||||
QFont font;
|
QString font;
|
||||||
EffectType effect;
|
EffectType effect;
|
||||||
int effectThickness;
|
int effectThickness;
|
||||||
QColor color;
|
QColor color;
|
||||||
};
|
};
|
||||||
|
|
||||||
static TextRenderer* getInstance(const char* family, int pointSize = -1, int weight = -1, bool italic = false,
|
static TextRenderer* getInstance(const char* family, int pointSize = -1, int weight = -1, bool italic = false,
|
||||||
|
@ -65,30 +67,72 @@ public:
|
||||||
|
|
||||||
~TextRenderer();
|
~TextRenderer();
|
||||||
|
|
||||||
const QFontMetrics& metrics() const { return _metrics; }
|
|
||||||
|
|
||||||
// returns the height of the tallest character
|
// 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
|
// also returns the height of the tallest character
|
||||||
int draw(int x, int y, const char* str, const glm::vec4& color);
|
inline int draw(int x, int y, const char* str,
|
||||||
|
const glm::vec4& color = glm::vec4(-1),
|
||||||
int computeWidth(char ch);
|
float fontSize = -1,
|
||||||
int computeWidth(const char* str);
|
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 drawBatch();
|
||||||
void clearBatch();
|
void clearBatch();
|
||||||
private:
|
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);
|
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
|
int width() const {
|
||||||
QFont _font;
|
return size.x;
|
||||||
|
}
|
||||||
// the font metrics
|
QRectF bounds() const;
|
||||||
QFontMetrics _metrics;
|
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
|
// the type of effect to apply
|
||||||
EffectType _effectType;
|
EffectType _effectType;
|
||||||
|
@ -97,58 +141,40 @@ private:
|
||||||
int _effectThickness;
|
int _effectThickness;
|
||||||
|
|
||||||
// maps characters to cached glyph info
|
// 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
|
// the id of the glyph texture to which we're currently writing
|
||||||
GLuint _currentTextureID;
|
GLuint _currentTextureID;
|
||||||
|
|
||||||
// the position within the current glyph texture
|
int _pointSize;
|
||||||
int _x, _y;
|
|
||||||
|
|
||||||
// the height of the current row of characters
|
// the height of the current row of characters
|
||||||
int _rowHeight;
|
int _rowHeight;
|
||||||
|
|
||||||
// the list of all texture ids for which we're responsible
|
|
||||||
QVector<GLuint> _allTextureIDs;
|
|
||||||
|
|
||||||
// text color
|
// text color
|
||||||
QColor _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
|
#endif // hifi_TextRenderer_h
|
||||||
|
|
Loading…
Reference in a new issue