diff --git a/interface/src/devices/PrioVR.cpp b/interface/src/devices/PrioVR.cpp index 56b2587b0d..06c00b2727 100644 --- a/interface/src/devices/PrioVR.cpp +++ b/interface/src/devices/PrioVR.cpp @@ -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(); - 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 diff --git a/libraries/entities-renderer/src/RenderableTextEntityItem.cpp b/libraries/entities-renderer/src/RenderableTextEntityItem.cpp index d1d7b49484..b10035732d 100644 --- a/libraries/entities-renderer/src/RenderableTextEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableTextEntityItem.cpp @@ -18,6 +18,7 @@ #include #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); diff --git a/libraries/render-utils/src/MatrixStack.cpp b/libraries/render-utils/src/MatrixStack.cpp index f3900fbdd3..dc22b160d9 100644 --- a/libraries/render-utils/src/MatrixStack.cpp +++ b/libraries/render-utils/src/MatrixStack.cpp @@ -1,6 +1 @@ #include "MatrixStack.h" - -QMatrix4x4 fromGlm(const glm::mat4 & m) { - return QMatrix4x4(&m[0][0]).transposed(); -} - diff --git a/libraries/render-utils/src/MatrixStack.h b/libraries/render-utils/src/MatrixStack.h index 325c1b8ee9..505818fc4a 100644 --- a/libraries/render-utils/src/MatrixStack.h +++ b/libraries/render-utils/src/MatrixStack.h @@ -25,7 +25,6 @@ #include #include #include -#include #include @@ -201,8 +200,5 @@ public: f(); }); } - }; -QMatrix4x4 fromGlm(const glm::mat4 & m); - diff --git a/libraries/render-utils/src/TextRenderer.cpp b/libraries/render-utils/src/TextRenderer.cpp index f15fbc3c8c..a02a156409 100644 --- a/libraries/render-utils/src/TextRenderer.cpp +++ b/libraries/render-utils/src/TextRenderer.cpp @@ -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 +template void readStream(QIODevice & in, T & t) { - in.read((char*)&t, sizeof(t)); + in.read((char*) &t, sizeof(t)); } -template -void readStream(QIODevice & in, T(&t)[N]) { - in.read((char*)t, N); +template +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 +void fillBuffer(QBuffer & buffer, T (&t)[N]) { + buffer.setData((const char*) t, N); } -template -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 _glyphs; - + // maps characters to cached glyph info + QHash _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 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 -Font * loadFont(T(&t)[N]) { - QBuffer buffer; - buffer.setData((const char*)t, N); - buffer.open(QBuffer::ReadOnly); - return loadFont(buffer); +template +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 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 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 vertexData; - std::vector 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 vertexData; + std::vector 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; } diff --git a/libraries/shared/CMakeLists.txt b/libraries/shared/CMakeLists.txt index 4b271bfeda..0bbb39fdb5 100644 --- a/libraries/shared/CMakeLists.txt +++ b/libraries/shared/CMakeLists.txt @@ -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() \ No newline at end of file diff --git a/libraries/shared/src/GLMHelpers.cpp b/libraries/shared/src/GLMHelpers.cpp index a57aa75e9b..370823f7a4 100644 --- a/libraries/shared/src/GLMHelpers.cpp +++ b/libraries/shared/src/GLMHelpers.cpp @@ -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); -} \ No newline at end of file +} + +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; +} + + diff --git a/libraries/shared/src/GLMHelpers.h b/libraries/shared/src/GLMHelpers.h index 296d6bcd46..5b833707d1 100644 --- a/libraries/shared/src/GLMHelpers.h +++ b/libraries/shared/src/GLMHelpers.h @@ -18,6 +18,7 @@ #include #include +#include #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 \ No newline at end of file +QMatrix4x4 fromGlm(const glm::mat4 & m); + +QRectF glmToRect(const glm::vec2 & pos, const glm::vec2 & size); + +#endif // hifi_GLMHelpers_h diff --git a/tests/render-utils/src/main.cpp b/tests/render-utils/src/main.cpp index 2acdb7ec54..7cb7fb361d 100644 --- a/tests/render-utils/src/main.cpp +++ b/tests/render-utils/src/main.cpp @@ -20,7 +20,6 @@ #include #include #include -#include #include #include #include @@ -31,147 +30,153 @@ #include // 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"