More OSX work, refactoring functions to shared, removing erroneuous Qt 5.4 dependency, fixing PrioVR text api usage

This commit is contained in:
Brad Davis 2015-02-04 12:42:27 -08:00
parent ba277e802a
commit 1745ba9a7e
9 changed files with 493 additions and 477 deletions

View file

@ -216,7 +216,7 @@ void PrioVR::renderCalibrationCountdown() {
false, TextRenderer::OUTLINE_EFFECT, 2);
QByteArray text = "Assume T-Pose in " + QByteArray::number(secondsRemaining) + "...";
auto glCanvas = DependencyManager::get<GLCanvas>();
textRenderer->draw((glCanvas->width() - textRenderer->computeWidth(text.constData())) / 2,
textRenderer->draw((glCanvas->width() - textRenderer->computeExtent(text.constData()).x) / 2,
glCanvas->height() / 2,
text, glm::vec4(1,1,1,1));
#endif

View file

@ -18,6 +18,7 @@
#include <TextRenderer.h>
#include "RenderableTextEntityItem.h"
#include "GLMHelpers.h"
const int FIXED_FONT_POINT_SIZE = 40;
const float LINE_SCALE_RATIO = 1.2f;
@ -26,11 +27,6 @@ EntityItem* RenderableTextEntityItem::factory(const EntityItemID& entityID, cons
return new RenderableTextEntityItem(entityID, properties);
}
glm::vec3 toGlm(const xColor & color) {
static const float MAX_COLOR = 255.0f;
return std::move(glm::vec3(color.red / MAX_COLOR, color.green / MAX_COLOR, color.blue / MAX_COLOR));
}
void RenderableTextEntityItem::render(RenderArgs* args) {
PerformanceTimer perfTimer("RenderableTextEntityItem::render");
assert(getType() == EntityTypes::Text);

View file

@ -1,6 +1 @@
#include "MatrixStack.h"
QMatrix4x4 fromGlm(const glm::mat4 & m) {
return QMatrix4x4(&m[0][0]).transposed();
}

View file

@ -25,7 +25,6 @@
#include <glm/gtc/epsilon.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <stack>
#include <QMatrix4x4>
#include <gpu/GPUConfig.h>
@ -201,8 +200,5 @@ public:
f();
});
}
};
QMatrix4x4 fromGlm(const glm::mat4 & m);

View file

@ -31,49 +31,40 @@
#include "gpu/GLBackend.h"
#include "gpu/Stream.h"
#include "GLMHelpers.h"
#include "FontInconsolataMedium.h"
#include "FontRoboto.h"
#include "FontTimeless.h"
#include "FontCourierPrime.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,
};
};
// Normally we could use 'enum class' to avoid namespace pollution,
// but we want easy conversion to GLuint
namespace Attributes {
enum {
Position = 0, TexCoord = 1,
};
}
}
// Helper functions for reading binary data from an IO device
template <class T>
template<class T>
void readStream(QIODevice & in, T & t) {
in.read((char*)&t, sizeof(t));
in.read((char*) &t, sizeof(t));
}
template <typename T, size_t N>
void readStream(QIODevice & in, T(&t)[N]) {
in.read((char*)t, N);
template<typename T, size_t N>
void readStream(QIODevice & in, T (&t)[N]) {
in.read((char*) t, N);
}
glm::uvec2 toGlm(const QSize & size) {
return glm::uvec2(size.width(), size.height());
template<class T, size_t N>
void fillBuffer(QBuffer & buffer, T (&t)[N]) {
buffer.setData((const char*) t, N);
}
template <class T, size_t N >
void fillBuffer(QBuffer & buffer, T(&t)[N]) {
buffer.setData((const char*)t, N);
}
QRectF glmToRect(const glm::vec2 & pos, const glm::vec2 & size) {
QRectF result(pos.x, pos.y, size.x, size.y);
return result;
}
const char SHADER_TEXT_VS[] = R"XXXX(#version 330
const char SHADER_TEXT_VS[] =
R"XXXX(#version 330
uniform mat4 Projection = mat4(1);
uniform mat4 ModelView = mat4(1);
@ -94,7 +85,6 @@ void main() {
gl_Position = Projection * clipVertex;
})XXXX";
// FIXME
// Work in progress to support clipping text to a specific region in
// worldspace. Right now this is a passthrough goemetry shader but
@ -105,7 +95,8 @@ void main() {
// However, it might be simpler to do this calculation inside the CPU
// draw call by aborting any letter where the bounding box falls outside
// given rectangle
const char SHADER_TEXT_GS[] = R"XXXX(#version 330
const char SHADER_TEXT_GS[] =
R"XXXX(#version 330
layout(triangles) in;
layout(triangle_strip, max_vertices = 3) out;
@ -125,7 +116,8 @@ const char SHADER_TEXT_GS[] = R"XXXX(#version 330
// FIXME figure out how to improve the anti-aliasing on the
// interior of the outline fonts
const char SHADER_TEXT_FS[] = R"XXXX(#version 330
const char SHADER_TEXT_FS[] =
R"XXXX(#version 330
uniform sampler2D Font;
uniform vec4 Color = vec4(1);
@ -168,419 +160,414 @@ void main() {
// stores the font metrics for a single character
struct Glyph {
QChar c;
glm::vec2 texOffset;
glm::vec2 texSize;
glm::vec2 size;
glm::vec2 offset;
float d; // xadvance - adjusts character positioning
size_t indexOffset;
QChar c;
glm::vec2 texOffset;
glm::vec2 texSize;
glm::vec2 size;
glm::vec2 offset;
float d; // xadvance - adjusts character positioning
size_t indexOffset;
QRectF bounds() const;
QRectF textureBounds(const glm::vec2 & textureSize) const;
QRectF bounds() const;
QRectF textureBounds(const glm::vec2 & textureSize) const;
void read(QIODevice & in);
void read(QIODevice & in);
};
void Glyph::read(QIODevice & in) {
uint16_t charcode;
readStream(in, charcode);
c = charcode;
readStream(in, texOffset);
readStream(in, size);
readStream(in, offset);
readStream(in, d);
texSize = size;
uint16_t charcode;
readStream(in, charcode);
c = charcode;
readStream(in, texOffset);
readStream(in, size);
readStream(in, offset);
readStream(in, d);
texSize = size;
}
const float DEFAULT_POINT_SIZE = 12;
class Font {
public:
using TexturePtr = QSharedPointer < QOpenGLTexture >;
using VertexArrayPtr = QSharedPointer< QOpenGLVertexArrayObject >;
using ProgramPtr = QSharedPointer < QOpenGLShaderProgram >;
using BufferPtr = QSharedPointer < QOpenGLBuffer >;
using TexturePtr = QSharedPointer < QOpenGLTexture >;
using VertexArrayPtr = QSharedPointer< QOpenGLVertexArrayObject >;
using ProgramPtr = QSharedPointer < QOpenGLShaderProgram >;
using BufferPtr = QSharedPointer < QOpenGLBuffer >;
// maps characters to cached glyph info
QHash<QChar, Glyph> _glyphs;
// maps characters to cached glyph info
QHash<QChar, Glyph> _glyphs;
// the id of the glyph texture to which we're currently writing
GLuint _currentTextureID;
// the id of the glyph texture to which we're currently writing
GLuint _currentTextureID;
int _pointSize;
int _pointSize;
// the height of the current row of characters
int _rowHeight;
// the height of the current row of characters
int _rowHeight;
QString _family;
float _fontSize{ 0 };
float _leading{ 0 };
float _ascent{ 0 };
float _descent{ 0 };
float _spaceWidth{ 0 };
QString _family;
float _fontSize { 0 };
float _leading { 0 };
float _ascent { 0 };
float _descent { 0 };
float _spaceWidth { 0 };
BufferPtr _vertices;
BufferPtr _indices;
TexturePtr _texture;
VertexArrayPtr _vao;
QImage _image;
ProgramPtr _program;
BufferPtr _vertices;
BufferPtr _indices;
TexturePtr _texture;
VertexArrayPtr _vao;
QImage _image;
ProgramPtr _program;
const Glyph & getGlyph(const QChar & c) const;
void read(QIODevice & path);
// Initialize the OpenGL structures
void setupGL();
const Glyph & getGlyph(const QChar & c);
void read(QIODevice & path);
// Initialize the OpenGL structures
void setupGL();
glm::vec2 computeExtent(const QString & str) const;
glm::vec2 computeExtent(const QString & str) const;
glm::vec2 drawString(
float x, float y,
const QString & str,
const glm::vec4& color,
TextRenderer::EffectType effectType,
float maxWidth) const;
glm::vec2 drawString(float x, float y, const QString & str,
const glm::vec4& color, TextRenderer::EffectType effectType,
float maxWidth) const;
};
static QHash<QString, Font*> LOADED_FONTS;
Font * loadFont(QIODevice & buffer) {
Font * result = new Font();
result->read(buffer);
return result;
Font * result = new Font();
result->read(buffer);
return result;
}
template <class T, size_t N >
Font * loadFont(T(&t)[N]) {
QBuffer buffer;
buffer.setData((const char*)t, N);
buffer.open(QBuffer::ReadOnly);
return loadFont(buffer);
template<class T, size_t N>
Font * loadFont(T (&t)[N]) {
QBuffer buffer;
buffer.setData((const char*) t, N);
buffer.open(QBuffer::ReadOnly);
return loadFont(buffer);
}
Font * loadFont(const QString & family) {
if (!LOADED_FONTS.contains(family)) {
if (family == MONO_FONT_FAMILY) {
if (!LOADED_FONTS.contains(family)) {
if (family == MONO_FONT_FAMILY) {
LOADED_FONTS[family] = loadFont(SDFF_COURIER_PRIME);
} else if (family == INCONSOLATA_FONT_FAMILY) {
LOADED_FONTS[family] = loadFont(SDFF_INCONSOLATA_MEDIUM);
} else if (family == SANS_FONT_FAMILY) {
LOADED_FONTS[family] = loadFont(SDFF_ROBOTO);
} else {
if (!LOADED_FONTS.contains(SERIF_FONT_FAMILY)) {
LOADED_FONTS[SERIF_FONT_FAMILY] = loadFont(SDFF_TIMELESS);
}
LOADED_FONTS[family] = LOADED_FONTS[SERIF_FONT_FAMILY];
LOADED_FONTS[family] = loadFont(SDFF_COURIER_PRIME);
} else if (family == INCONSOLATA_FONT_FAMILY) {
LOADED_FONTS[family] = loadFont(SDFF_INCONSOLATA_MEDIUM);
} else if (family == SANS_FONT_FAMILY) {
LOADED_FONTS[family] = loadFont(SDFF_ROBOTO);
} else {
if (!LOADED_FONTS.contains(SERIF_FONT_FAMILY)) {
LOADED_FONTS[SERIF_FONT_FAMILY] = loadFont(SDFF_TIMELESS);
}
LOADED_FONTS[family] = LOADED_FONTS[SERIF_FONT_FAMILY];
}
}
}
return LOADED_FONTS[family];
return LOADED_FONTS[family];
}
const Glyph & Font::getGlyph(const QChar & c) const {
if (!_glyphs.contains(c)) {
return _glyphs[QChar('?')];
}
return _glyphs[c];
};
// NERD RAGE: why doesn't QHash have a 'const T & operator[] const' member
const Glyph & Font::getGlyph(const QChar & c) {
if (!_glyphs.contains(c)) {
return _glyphs[QChar('?')];
}
return _glyphs[c];
}
void Font::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);
uint8_t header[4];
readStream(in, header);
if (memcmp(header, "SDFF", 4)) {
qFatal("Bad SDFF file");
}
}
// read font data
readStream(in, _leading);
readStream(in, _ascent);
readStream(in, _descent);
readStream(in, _spaceWidth);
_fontSize = _ascent + _descent;
_rowHeight = _fontSize + _descent;
uint16_t version;
readStream(in, version);
// Read character count
uint16_t count;
readStream(in, count);
// read metrics data for each character
QVector<Glyph> glyphs(count);
// std::for_each instead of Qt foreach because we need non-const references
std::for_each(glyphs.begin(), glyphs.end(), [&](Glyph & g){
g.read(in);
});
// read font name
_family = "";
if (version > 0x0001) {
char c;
readStream(in, c);
while (c) {
_family += c;
readStream(in, c);
}
}
// read image data
if (!_image.loadFromData(in.readAll(), "PNG")) {
qFatal("Failed to read SDFF image");
}
// read font data
readStream(in, _leading);
readStream(in, _ascent);
readStream(in, _descent);
readStream(in, _spaceWidth);
_fontSize = _ascent + _descent;
_rowHeight = _fontSize + _descent;
_glyphs.clear();
foreach(Glyph g, glyphs) {
// Adjust the pixel texture coordinates into UV coordinates,
glm::vec2 imageSize = toGlm(_image.size());
g.texSize /= imageSize;
g.texOffset /= imageSize;
// store in the character to glyph hash
_glyphs[g.c] = g;
};
// Read character count
uint16_t count;
readStream(in, count);
// read metrics data for each character
QVector<Glyph> glyphs(count);
// std::for_each instead of Qt foreach because we need non-const references
std::for_each(glyphs.begin(), glyphs.end(), [&](Glyph & g) {
g.read(in);
});
setupGL();
// read image data
if (!_image.loadFromData(in.readAll(), "PNG")) {
qFatal("Failed to read SDFF image");
}
_glyphs.clear();
foreach(Glyph g, glyphs) {
// Adjust the pixel texture coordinates into UV coordinates,
glm::vec2 imageSize = toGlm(_image.size());
g.texSize /= imageSize;
g.texOffset /= imageSize;
// store in the character to glyph hash
_glyphs[g.c] = g;
};
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()) {
}
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());
}
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 Glyph::bounds() const {
return glmToRect(offset, size);
return glmToRect(offset, size);
}
QRectF Glyph::textureBounds(const glm::vec2 & textureSize) const {
return glmToRect(texOffset, texSize);
return glmToRect(texOffset, texSize);
}
void Font::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->addShaderFromSourceCode(QOpenGLShader::Geometry, SHADER_TEXT_GS) ||
!_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]);
_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->addShaderFromSourceCode(QOpenGLShader::Geometry,
SHADER_TEXT_GS) || !_program->link()) {
qFatal(_program->log().toLocal8Bit().constData());
}
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);
});
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();
//_vao = VertexArrayPtr(new VertexArray());
_vao = VertexArrayPtr(new QOpenGLVertexArrayObject());
_vao->create();
_vao->bind();
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);
});
_vertices = BufferPtr(new QOpenGLBuffer(QOpenGLBuffer::VertexBuffer));
_vertices->create();
_vertices->bind();
_vertices->allocate(&vertexData[0], sizeof(TextureVertex) * vertexData.size());
//_vao = VertexArrayPtr(new VertexArray());
_vao = VertexArrayPtr(new QOpenGLVertexArrayObject());
_vao->create();
_vao->bind();
_indices = BufferPtr(new QOpenGLBuffer(QOpenGLBuffer::IndexBuffer));
_indices->create();
_indices->bind();
_indices->allocate(&indexData[0], sizeof(GLuint) * indexData.size());
_vertices = BufferPtr(new QOpenGLBuffer(QOpenGLBuffer::VertexBuffer));
_vertices->create();
_vertices->bind();
_vertices->allocate(&vertexData[0],
sizeof(TextureVertex) * vertexData.size());
GLsizei stride = (GLsizei)sizeof(TextureVertex);
void* offset = (void*)offsetof(TextureVertex, tex);
_indices = BufferPtr(new QOpenGLBuffer(QOpenGLBuffer::IndexBuffer));
_indices->create();
_indices->bind();
_indices->allocate(&indexData[0], sizeof(GLuint) * indexData.size());
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();
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();
}
glm::vec2 Font::computeExtent(const QString & str) const {
glm::vec2 advance(0, _rowHeight - _descent);
float maxX = 0;
foreach(QString token, str.split(" ")) {
foreach(QChar c, token) {
if (QChar('\n') == c) {
maxX = std::max(advance.x, maxX);
advance.x = 0;
advance.y += _rowHeight;
continue;
}
if (!_glyphs.contains(c)) {
c = QChar('?');
}
const Glyph & m = _glyphs[c];
advance.x += m.d;
glm::vec2 advance(0, _rowHeight - _descent);
float maxX = 0;
foreach(QString token, str.split(" ")) {
foreach(QChar c, token) {
if (QChar('\n') == c) {
maxX = std::max(advance.x, maxX);
advance.x = 0;
advance.y += _rowHeight;
continue;
}
if (!_glyphs.contains(c)) {
c = QChar('?');
}
const Glyph & m = _glyphs[c];
advance.x += m.d;
}
advance.x += _spaceWidth;
}
advance.x += _spaceWidth;
}
return glm::vec2(maxX, advance.y);
return glm::vec2(maxX, advance.y);
}
// FIXME support the maxWidth parameter and allow the text to automatically wrap
// even without explicit line feeds.
glm::vec2 Font::drawString(
float x, float y,
const QString & str,
const glm::vec4& color,
TextRenderer::EffectType effectType,
float maxWidth) const {
glm::vec2 Font::drawString(float x, float y, const QString & str,
const glm::vec4& color, TextRenderer::EffectType effectType,
float maxWidth) const {
// Stores how far we've moved from the start of the string, in DTP units
glm::vec2 advance(0, -_rowHeight - _descent);
// Stores how far we've moved from the start of the string, in DTP units
glm::vec2 advance(0, -_rowHeight - _descent);
_program->bind();
_program->setUniformValue("Color", color.r, color.g, color.b, color.a);
_program->setUniformValue("Projection", fromGlm(MatrixStack::projection().top()));
if (effectType == TextRenderer::OUTLINE_EFFECT) {
_program->setUniformValue("Outline", true);
}
_texture->bind();
_vao->bind();
MatrixStack & mv = MatrixStack::modelview();
// scale the modelview into font units
mv.translate(glm::vec3(0, _ascent, 0));
foreach(QString token, str.split(" ")) {
// float tokenWidth = measureWidth(token, fontSize);
// if (wrap && 0 != advance.x && (advance.x + tokenWidth) > maxWidth) {
// advance.x = 0;
// advance.y -= _rowHeight;
// }
foreach(QChar c, token) {
if (QChar('\n') == c) {
advance.x = 0;
advance.y -= _rowHeight;
continue;
}
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 -= _rowHeight;
//}
// 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);
// FIXME find a better (and GL ES 3.1 compatible) way of rendering the text
// that doesn't involve a single GL call per character.
// Most likely an 'indirect' call or an 'instanced' call.
_program->setUniformValue("ModelView", fromGlm(mv.top()));
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, (void*)(m.indexOffset));
});
advance.x += m.d;
_program->bind();
_program->setUniformValue("Color", color.r, color.g, color.b, color.a);
_program->setUniformValue("Projection",
fromGlm(MatrixStack::projection().top()));
if (effectType == TextRenderer::OUTLINE_EFFECT) {
_program->setUniformValue("Outline", true);
}
advance.x += _spaceWidth;
}
_texture->bind();
_vao->bind();
_vao->release();
_program->release();
return advance;
MatrixStack & mv = MatrixStack::modelview();
// scale the modelview into font units
mv.translate(glm::vec3(0, _ascent, 0));
foreach(QString token, str.split(" ")) {
// float tokenWidth = measureWidth(token, fontSize);
// if (wrap && 0 != advance.x && (advance.x + tokenWidth) > maxWidth) {
// advance.x = 0;
// advance.y -= _rowHeight;
// }
foreach(QChar c, token) {
if (QChar('\n') == c) {
advance.x = 0;
advance.y -= _rowHeight;
continue;
}
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 -= _rowHeight;
//}
// 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);
// FIXME find a better (and GL ES 3.1 compatible) way of rendering the text
// that doesn't involve a single GL call per character.
// Most likely an 'indirect' call or an 'instanced' call.
_program->setUniformValue("ModelView", fromGlm(mv.top()));
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, (void*)(m.indexOffset));
});
advance.x += m.d;
}
advance.x += _spaceWidth;
}
_vao->release();
_program->release();
return advance;
}
TextRenderer* TextRenderer::getInstance(const char* family, float pointSize, int weight, bool italic,
EffectType effect, int effectThickness, const QColor& color) {
if (pointSize < 0) {
pointSize = DEFAULT_POINT_SIZE;
}
return new TextRenderer(family, pointSize, weight, italic, effect, effectThickness, color);
TextRenderer* TextRenderer::getInstance(const char* family, float pointSize,
int weight, bool italic, EffectType effect, int effectThickness,
const QColor& color) {
if (pointSize < 0) {
pointSize = DEFAULT_POINT_SIZE;
}
return new TextRenderer(family, pointSize, weight, italic, effect,
effectThickness, color);
}
TextRenderer::TextRenderer(const char* family, float pointSize, int weight, bool italic,
EffectType effect, int effectThickness, const QColor& color) :
_effectType(effect),
_pointSize(pointSize),
_effectThickness(effectThickness),
_color(color),
_font(loadFont(family))
{
TextRenderer::TextRenderer(const char* family, float pointSize, int weight,
bool italic, EffectType effect, int effectThickness,
const QColor& color) :
_effectType(effect), _pointSize(pointSize), _effectThickness(
effectThickness), _color(color), _font(loadFont(family)) {
}
TextRenderer::~TextRenderer() {
}
glm::vec2 TextRenderer::computeExtent(const QString & str) const {
float scale = (_pointSize / DEFAULT_POINT_SIZE) * 0.25f;
return _font->computeExtent(str) * scale;
float scale = (_pointSize / DEFAULT_POINT_SIZE) * 0.25f;
return _font->computeExtent(str) * scale;
}
float TextRenderer::draw(
float x, float y,
const QString & str,
const glm::vec4& color,
float maxWidth) {
glm::vec4 actualColor(color);
if (actualColor.r < 0) {
actualColor = glm::vec4(_color.redF(), _color.greenF(), _color.blueF(), 1.0);
}
float TextRenderer::draw(float x, float y, const QString & str,
const glm::vec4& color, float maxWidth) {
glm::vec4 actualColor(color);
if (actualColor.r < 0) {
actualColor = glm::vec4(_color.redF(), _color.greenF(), _color.blueF(),
1.0);
}
float scale = (_pointSize / DEFAULT_POINT_SIZE) * 0.25f;
glm::vec2 result;
MatrixStack::withGlMatrices([&] {
MatrixStack & mv = MatrixStack::modelview();
// scale the modelview into font units
// FIXME migrate the constant scale factor into the geometry of the
// fonts so we don't have to flip the Y axis here and don't have to
// scale at all.
mv.translate(glm::vec2(x, y)).scale(glm::vec3(scale, -scale, scale));
// The font does all the OpenGL work
result = _font->drawString(x, y, str, actualColor, _effectType, maxWidth);
});
return result.x;
float scale = (_pointSize / DEFAULT_POINT_SIZE) * 0.25f;
glm::vec2 result;
MatrixStack::withGlMatrices([&] {
MatrixStack & mv = MatrixStack::modelview();
// scale the modelview into font units
// FIXME migrate the constant scale factor into the geometry of the
// fonts so we don't have to flip the Y axis here and don't have to
// scale at all.
mv.translate(glm::vec2(x, y)).scale(glm::vec3(scale, -scale, scale));
// The font does all the OpenGL work
result = _font->drawString(x, y, str, actualColor, _effectType, maxWidth);
});
return result.x;
}

View file

@ -2,7 +2,7 @@ set(TARGET_NAME shared)
# use setup_hifi_library macro to setup our project and link appropriate Qt modules
# TODO: there isn't really a good reason to have Script linked here - let's get what is requiring it out (RegisteredMetaTypes.cpp)
setup_hifi_library(Network Script Widgets)
setup_hifi_library(Gui Network Script Widgets)
# call macro to include our dependency includes and bubble them up via a property on our target
include_dependency_includes()

View file

@ -302,4 +302,32 @@ bool isSimilarPosition(const glm::vec3& positionA, const glm::vec3& positionB, f
// Compute the distance between the two points
float positionDistance = glm::distance(positionA, positionB);
return (positionDistance <= similarEnough);
}
}
glm::uvec2 toGlm(const QSize & size) {
return glm::uvec2(size.width(), size.height());
}
glm::ivec2 toGlm(const QPoint & pt) {
return glm::ivec2(pt.x(), pt.y());
}
glm::vec2 toGlm(const QPointF & pt) {
return glm::vec2(pt.x(), pt.y());
}
glm::vec3 toGlm(const xColor & color) {
static const float MAX_COLOR = 255.0f;
return std::move(glm::vec3(color.red / MAX_COLOR, color.green / MAX_COLOR, color.blue / MAX_COLOR));
}
QMatrix4x4 fromGlm(const glm::mat4 & m) {
return QMatrix4x4(&m[0][0]).transposed();
}
QRectF glmToRect(const glm::vec2 & pos, const glm::vec2 & size) {
QRectF result(pos.x, pos.y, size.x, size.y);
return result;
}

View file

@ -18,6 +18,7 @@
#include <glm/gtc/quaternion.hpp>
#include <QtCore/QByteArray>
#include <QtGui/QMatrix4x4>
#include "SharedUtil.h"
@ -86,5 +87,13 @@ bool isSimilarOrientation(const glm::quat& orientionA, const glm::quat& orientio
const float POSITION_SIMILAR_ENOUGH = 0.1f; // 0.1 meter
bool isSimilarPosition(const glm::vec3& positionA, const glm::vec3& positionB, float similarEnough = POSITION_SIMILAR_ENOUGH);
glm::uvec2 toGlm(const QSize & size);
glm::ivec2 toGlm(const QPoint & pt);
glm::vec2 toGlm(const QPointF & pt);
glm::vec3 toGlm(const xColor & color);
#endif // hifi_GLMHelpers_h
QMatrix4x4 fromGlm(const glm::mat4 & m);
QRectF glmToRect(const glm::vec2 & pos, const glm::vec2 & size);
#endif // hifi_GLMHelpers_h

View file

@ -20,7 +20,6 @@
#include <QOpenGLContext>
#include <QOpenGLBuffer>
#include <QOpenGLShaderProgram>
#include <QOpenGLWindow>
#include <QResizeEvent>
#include <QOpenGLTexture>
#include <QOpenGLVertexArrayObject>
@ -31,147 +30,153 @@
#include <PathUtils.h>
// Create a simple OpenGL window that renders text in various ways
class QTestWindow : public QWindow {
Q_OBJECT
QOpenGLContext * _context;
QSize _size;
TextRenderer* _textRenderer[4];
class QTestWindow: public QWindow {
Q_OBJECT
QOpenGLContext * _context;
QSize _size;
TextRenderer* _textRenderer[4];
protected:
void resizeEvent(QResizeEvent * ev) override {
QWindow::resizeEvent(ev);
_size = ev->size();
resizeGl();
}
void resizeEvent(QResizeEvent * ev) override {
QWindow::resizeEvent(ev);
_size = ev->size();
resizeGl();
}
void resizeGl() {
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0, _size.width(), _size.height(), 0, 1, -1);
glMatrixMode(GL_MODELVIEW);
glViewport(0, 0, _size.width(), _size.height());
}
void resizeGl() {
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0, _size.width(), _size.height(), 0, 1, -1);
glMatrixMode(GL_MODELVIEW);
glViewport(0, 0, _size.width(), _size.height());
}
public:
QTestWindow();
virtual ~QTestWindow() {
QTestWindow();
virtual ~QTestWindow() {
}
void makeCurrent() {
_context->makeCurrent(this);
}
}
void makeCurrent() {
_context->makeCurrent(this);
}
void draw();
void draw();
};
QTestWindow::QTestWindow() {
setSurfaceType(QSurface::OpenGLSurface);
setSurfaceType(QSurface::OpenGLSurface);
QSurfaceFormat format;
// Qt Quick may need a depth and stencil buffer. Always make sure these are available.
format.setDepthBufferSize(16);
format.setStencilBufferSize(8);
format.setVersion(4, 3);
format.setProfile(QSurfaceFormat::OpenGLContextProfile::CompatibilityProfile);
setFormat(format);
QSurfaceFormat format;
// Qt Quick may need a depth and stencil buffer. Always make sure these are available.
format.setDepthBufferSize(16);
format.setStencilBufferSize(8);
format.setVersion(3, 2);
format.setProfile(
QSurfaceFormat::OpenGLContextProfile::CompatibilityProfile);
setFormat(format);
_context = new QOpenGLContext;
_context->setFormat(format);
_context->create();
_context = new QOpenGLContext;
_context->setFormat(format);
_context->create();
show();
makeCurrent();
show();
makeCurrent();
qDebug() << (const char*)glGetString(GL_VERSION);
#ifdef WIN32
glewExperimental = true;
GLenum err = glewInit();
if (GLEW_OK != err) {
/* Problem: glewInit failed, something is seriously wrong. */
const GLubyte * errStr = glewGetErrorString(err);
qDebug("Error: %s\n", errStr);
}
qDebug("Status: Using GLEW %s\n", glewGetString(GLEW_VERSION));
glewExperimental = true;
GLenum err = glewInit();
if (GLEW_OK != err) {
/* Problem: glewInit failed, something is seriously wrong. */
const GLubyte * errStr = glewGetErrorString(err);
qDebug("Error: %s\n", errStr);
}
qDebug("Status: Using GLEW %s\n", glewGetString(GLEW_VERSION));
if (wglewGetExtension("WGL_EXT_swap_control")) {
int swapInterval = wglGetSwapIntervalEXT();
qDebug("V-Sync is %s\n", (swapInterval > 0 ? "ON" : "OFF"));
}
glGetError();
if (wglewGetExtension("WGL_EXT_swap_control")) {
int swapInterval = wglGetSwapIntervalEXT();
qDebug("V-Sync is %s\n", (swapInterval > 0 ? "ON" : "OFF"));
}
glGetError();
#endif
setFramePosition(QPoint(100, -900));
resize(QSize(800, 600));
_size = QSize(800, 600);
setFramePosition(QPoint(100, -900));
resize(QSize(800, 600));
_size = QSize(800, 600);
_textRenderer[0] = TextRenderer::getInstance(SANS_FONT_FAMILY, 12, false);
_textRenderer[1] = TextRenderer::getInstance(SERIF_FONT_FAMILY, 12, false, TextRenderer::SHADOW_EFFECT);
_textRenderer[2] = TextRenderer::getInstance(MONO_FONT_FAMILY, 48, -1, false, TextRenderer::OUTLINE_EFFECT);
_textRenderer[3] = TextRenderer::getInstance(INCONSOLATA_FONT_FAMILY, 24);
_textRenderer[0] = TextRenderer::getInstance(SANS_FONT_FAMILY, 12, false);
_textRenderer[1] = TextRenderer::getInstance(SERIF_FONT_FAMILY, 12, false,
TextRenderer::SHADOW_EFFECT);
_textRenderer[2] = TextRenderer::getInstance(MONO_FONT_FAMILY, 48, -1,
false, TextRenderer::OUTLINE_EFFECT);
_textRenderer[3] = TextRenderer::getInstance(INCONSOLATA_FONT_FAMILY, 24);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glClearColor(0.2f, 0.2f, 0.2f, 1);
glDisable(GL_DEPTH_TEST);
resizeGl();
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glClearColor(0.2f, 0.2f, 0.2f, 1);
glDisable(GL_DEPTH_TEST);
resizeGl();
}
static const wchar_t * EXAMPLE_TEXT = L"\xC1y Hello 1.0\ny\xC1 line 2\n\xC1y";
static const glm::uvec2 QUAD_OFFSET(10, 10);
static const glm::vec3 COLORS[4] = {
{ 1.0, 1.0, 1.0 },
{ 0.5, 1.0, 0.5 },
{ 1.0, 0.5, 0.5 },
{ 0.5, 0.5, 1.0 },
{ 1.0, 1.0, 1.0 },
{ 0.5, 1.0, 0.5 },
{ 1.0, 0.5, 0.5 },
{ 0.5, 0.5, 1.0 }
};
void QTestWindow::draw() {
makeCurrent();
glClear(GL_COLOR_BUFFER_BIT);
makeCurrent();
glClear(GL_COLOR_BUFFER_BIT);
const glm::uvec2 size = glm::uvec2(_size.width() / 2, _size.height() / 2);
const glm::uvec2 offsets[4] = {
{ QUAD_OFFSET.x, QUAD_OFFSET.y },
{ size.x + QUAD_OFFSET.x, QUAD_OFFSET.y },
{ size.x + QUAD_OFFSET.x, size.y + QUAD_OFFSET.y },
{ QUAD_OFFSET.x, size.y + QUAD_OFFSET.y },
};
const glm::uvec2 size = glm::uvec2(_size.width() / 2, _size.height() / 2);
const glm::uvec2 offsets[4] = { { QUAD_OFFSET.x, QUAD_OFFSET.y }, { size.x
+ QUAD_OFFSET.x, QUAD_OFFSET.y }, { size.x + QUAD_OFFSET.x, size.y
+ QUAD_OFFSET.y }, { QUAD_OFFSET.x, size.y + QUAD_OFFSET.y }, };
QString str = QString::fromWCharArray(EXAMPLE_TEXT);
QString str = QString::fromWCharArray(EXAMPLE_TEXT);
for (int i = 0; i < 4; ++i) {
glm::vec2 bounds = _textRenderer[i]->computeExtent(str);
for (int i = 0; i < 4; ++i) {
glm::vec2 bounds = _textRenderer[i]->computeExtent(str);
// Draw backgrounds around where the text will appear
glPushMatrix(); {
glTranslatef(offsets[i].x, offsets[i].y, 0);
glColor3f(0, 0, 0);
glBegin(GL_QUADS); {
glVertex2f(0, 0);
glVertex2f(0, bounds.y);
glVertex2f(bounds.x, bounds.y);
glVertex2f(bounds.x, 0);
} glEnd();
} glPopMatrix();
// Draw backgrounds around where the text will appear
glPushMatrix();
{
glTranslatef(offsets[i].x, offsets[i].y, 0);
glColor3f(0, 0, 0);
glBegin(GL_QUADS);
{
glVertex2f(0, 0);
glVertex2f(0, bounds.y);
glVertex2f(bounds.x, bounds.y);
glVertex2f(bounds.x, 0);
}
glEnd();
}
glPopMatrix();
// Draw the text itself
_textRenderer[i]->draw(offsets[i].x, offsets[i].y, str, glm::vec4(COLORS[i], 1.0f));
}
// Draw the text itself
_textRenderer[i]->draw(offsets[i].x, offsets[i].y, str,
glm::vec4(COLORS[i], 1.0f));
}
_context->swapBuffers(this);
_context->swapBuffers(this);
}
int main(int argc, char** argv) {
QApplication app(argc, argv);
QTestWindow window;
QTimer timer;
timer.setInterval(10);
app.connect(&timer, &QTimer::timeout, &app, [&] {
window.draw();
});
timer.start();
app.exec();
return 0;
QApplication app(argc, argv);
QTestWindow window;
QTimer timer;
timer.setInterval(10);
app.connect(&timer, &QTimer::timeout, &app, [&] {
window.draw();
});
timer.start();
app.exec();
return 0;
}
#include "main.moc"