diff --git a/libraries/render-utils/src/TextRenderer.cpp b/libraries/render-utils/src/TextRenderer.cpp index f48dddadbe..87cf9b2728 100644 --- a/libraries/render-utils/src/TextRenderer.cpp +++ b/libraries/render-utils/src/TextRenderer.cpp @@ -9,47 +9,55 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include "TextRenderer.h" #include -#include - -#include -#include +#include +#include +#include #include +#include #include +// FIXME, decouple from the GL headers +#include +#include +#include +#include + +#include +#include + +#include "gpu/GLBackend.h" +#include "gpu/Stream.h" + #include "GLMHelpers.h" #include "MatrixStack.h" #include "RenderUtilsLogging.h" +#include "TextRenderer.h" #include "sdf_text_vert.h" #include "sdf_text_frag.h" -#include "GeometryCache.h" -#include "DeferredLightingEffect.h" - -// FIXME support the shadow effect, or remove it from the API -// FIXME figure out how to improve the anti-aliasing on the -// interior of the outline fonts -const float DEFAULT_POINT_SIZE = 12; - // Helper functions for reading binary data from an IO device template -void readStream(QIODevice& in, T& t) { +void readStream(QIODevice & in, T & t) { in.read((char*) &t, sizeof(t)); } template -void readStream(QIODevice& in, T (&t)[N]) { +void readStream(QIODevice & in, T (&t)[N]) { in.read((char*) t, N); } template -void fillBuffer(QBuffer& buffer, T (&t)[N]) { +void fillBuffer(QBuffer & buffer, T (&t)[N]) { buffer.setData((const char*) t, N); } +// FIXME support the shadow effect, or remove it from the API +// FIXME figure out how to improve the anti-aliasing on the +// interior of the outline fonts + // stores the font metrics for a single character struct Glyph { QChar c; @@ -60,14 +68,13 @@ struct Glyph { float d; // xadvance - adjusts character positioning size_t indexOffset; - // We adjust bounds because offset is the bottom left corner of the font but the top left corner of a QRect - QRectF bounds() const { return glmToRect(offset, size).translated(0.0f, -size.y); } - QRectF textureBounds() const { return glmToRect(texOffset, texSize); } + QRectF bounds() const; + QRectF textureBounds(const glm::vec2 & textureSize) const; - void read(QIODevice& in); + void read(QIODevice & in); }; -void Glyph::read(QIODevice& in) { +void Glyph::read(QIODevice & in) { uint16_t charcode; readStream(in, charcode); c = charcode; @@ -78,112 +85,64 @@ void Glyph::read(QIODevice& in) { texSize = size; } -struct TextureVertex { - glm::vec2 pos; - glm::vec2 tex; - TextureVertex() {} - TextureVertex(const glm::vec2& pos, const glm::vec2& tex) : pos(pos), tex(tex) {} -}; - -struct QuadBuilder { - TextureVertex vertices[4]; - QuadBuilder(const glm::vec2& min, const glm::vec2& size, - const glm::vec2& texMin, const glm::vec2& texSize) { - // min = bottomLeft - vertices[0] = TextureVertex(min, - texMin + glm::vec2(0.0f, texSize.y)); - vertices[1] = TextureVertex(min + glm::vec2(size.x, 0.0f), - texMin + texSize); - vertices[2] = TextureVertex(min + size, - texMin + glm::vec2(texSize.x, 0.0f)); - vertices[3] = TextureVertex(min + glm::vec2(0.0f, size.y), - texMin); - } - QuadBuilder(const Glyph& glyph, const glm::vec2& offset) : - QuadBuilder(offset + glyph.offset - glm::vec2(0.0f, glyph.size.y), glyph.size, - glyph.texOffset, glyph.texSize) {} - -}; - -QDebug operator<<(QDebug debug, glm::vec2& value) { - debug << value.x << value.y; - return debug; -} - -QDebug operator<<(QDebug debug, glm::vec3& value) { - debug << value.x << value.y << value.z; - return debug; -} - -QDebug operator<<(QDebug debug, TextureVertex& value) { - debug << "Pos:" << value.pos << ", Tex:" << value.tex; - return debug; -} - -QDebug operator<<(QDebug debug, QuadBuilder& value) { - debug << '\n' << value.vertices[0] - << '\n' << value.vertices[1] - << '\n' << value.vertices[2] - << '\n' << value.vertices[3]; - return debug; -} +const float DEFAULT_POINT_SIZE = 12; class Font { public: + Font(); - void read(QIODevice& path); + using TexturePtr = QSharedPointer < QOpenGLTexture >; + using VertexArrayPtr = QSharedPointer< QOpenGLVertexArrayObject >; + using ProgramPtr = QSharedPointer < QOpenGLShaderProgram >; + using BufferPtr = QSharedPointer < QOpenGLBuffer >; - glm::vec2 computeExtent(const QString& str) const; - float getRowHeight() const { return _rowHeight; } - - // Render string to batch - void drawString(gpu::Batch& batch, float x, float y, const QString& str, + // maps characters to cached glyph info + // HACK... the operator[] const for QHash returns a + // copy of the value, not a const value reference, so + // we declare the hash as mutable in order to avoid such + // copies + mutable QHash _glyphs; + + // the id of the glyph texture to which we're currently writing + GLuint _currentTextureID; + + int _pointSize; + + // 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 }; + + 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(); + + glm::vec2 computeExtent(const QString & str) const; + + glm::vec2 computeTokenExtent(const QString & str) const; + + glm::vec2 drawString(float x, float y, const QString & str, const glm::vec4& color, TextRenderer::EffectType effectType, const glm::vec2& bound); private: - QStringList tokenizeForWrapping(const QString& str) const; - QStringList splitLines(const QString& str) const; - glm::vec2 computeTokenExtent(const QString& str) const; - - const Glyph& getGlyph(const QChar& c) const; - - void setupGPU(); - - // maps characters to cached glyph info - // HACK... the operator[] const for QHash returns a - // copy of the value, not a const value reference, so - // we declare the hash as mutable in order to avoid such - // copies - mutable QHash _glyphs; - - // Font characteristics - QString _family; - float _fontSize = 0.0f; - float _rowHeight = 0.0f; - float _leading = 0.0f; - float _ascent = 0.0f; - float _descent = 0.0f; - float _spaceWidth = 0.0f; - - bool _initialized = false; - - // gpu structures - gpu::PipelinePointer _pipeline; - gpu::TexturePointer _texture; - gpu::Stream::FormatPointer _format; - gpu::BufferPointer _verticesBuffer; - gpu::BufferStreamPointer _stream; - unsigned int _numVertices = 0; - - int _fontLoc = -1; - int _outlineLoc = -1; - int _colorLoc = -1; - - // last string render characteristics - QString _lastStringRendered; - glm::vec2 _lastBounds; + QStringList tokenizeForWrapping(const QString & str) const; + + bool _initialized; }; static QHash LOADED_FONTS; @@ -230,7 +189,7 @@ Font* loadFont(const QString& family) { return LOADED_FONTS[family]; } -Font::Font() { +Font::Font() : _initialized(false) { static bool fontResourceInitComplete = false; if (!fontResourceInitComplete) { Q_INIT_RESOURCE(fonts); @@ -239,50 +198,13 @@ Font::Font() { } // NERD RAGE: why doesn't QHash have a 'const T & operator[] const' member -const Glyph& Font::getGlyph(const QChar& c) const { +const Glyph & Font::getGlyph(const QChar & c) const { if (!_glyphs.contains(c)) { return _glyphs[QChar('?')]; } return _glyphs[c]; } -QStringList Font::splitLines(const QString& str) const { - return str.split('\n'); -} - -QStringList Font::tokenizeForWrapping(const QString& str) const { - QStringList tokens; - for(auto line : splitLines(str)) { - if (!tokens.empty()) { - tokens << QString('\n'); - } - tokens << line.split(' '); - } - return tokens; -} - -glm::vec2 Font::computeTokenExtent(const QString& token) const { - glm::vec2 advance(0, _fontSize); - foreach(QChar c, token) { - Q_ASSERT(c != '\n'); - advance.x += (c == ' ') ? _spaceWidth : getGlyph(c).d; - } - return advance; -} - -glm::vec2 Font::computeExtent(const QString& str) const { - glm::vec2 extent = glm::vec2(0.0f, 0.0f); - - QStringList tokens = splitLines(str); - foreach(const QString& token, tokens) { - glm::vec2 tokenExtent = computeTokenExtent(token); - extent.x = std::max(tokenExtent.x, extent.x); - } - extent.y = tokens.count() * _rowHeight; - - return extent; -} - void Font::read(QIODevice& in) { uint8_t header[4]; readStream(in, header); @@ -310,7 +232,7 @@ void Font::read(QIODevice& in) { readStream(in, _descent); readStream(in, _spaceWidth); _fontSize = _ascent + _descent; - _rowHeight = _fontSize + _leading; + _rowHeight = _fontSize + _descent; // Read character count uint16_t count; @@ -318,171 +240,266 @@ void Font::read(QIODevice& in) { // 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) { + std::for_each(glyphs.begin(), glyphs.end(), [&](Glyph & g) { g.read(in); }); // read image data - QImage image; - if (!image.loadFromData(in.readAll(), "PNG")) { + if (!_image.loadFromData(in.readAll(), "PNG")) { qFatal("Failed to read SDFF image"); } _glyphs.clear(); - glm::vec2 imageSize = toGlm(image.size()); 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; }; - - qDebug() << _family << "size" << image.size(); - qDebug() << _family << "format" << image.format(); - image = image.convertToFormat(QImage::Format_RGBA8888); - qDebug() << _family << "size" << image.size(); - qDebug() << _family << "format" << image.format(); - - gpu::Element formatGPU = gpu::Element(gpu::VEC3, gpu::UINT8, gpu::RGB); - gpu::Element formatMip = gpu::Element(gpu::VEC3, gpu::UINT8, gpu::RGB); - if (image.hasAlphaChannel()) { - formatGPU = gpu::Element(gpu::VEC4, gpu::UINT8, gpu::RGBA); - formatMip = gpu::Element(gpu::VEC4, gpu::UINT8, gpu::BGRA); - } - _texture = gpu::TexturePointer(gpu::Texture::create2D(formatGPU, image.width(), image.height(), - gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR))); - _texture->assignStoredMip(0, formatMip, image.byteCount(), image.constBits()); - _texture->autoGenerateMips(-1); } -#include -void Font::setupGPU() { - if (!_initialized) { - _initialized = true; - - // Setup render pipeline - auto vertexShader = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(sdf_text_vert))); - auto pixelShader = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(sdf_text_frag))); - gpu::ShaderPointer program = gpu::ShaderPointer(gpu::Shader::createProgram(vertexShader, pixelShader)); - - gpu::Shader::BindingSet slotBindings; - gpu::Shader::makeProgram(*program, slotBindings); - - _fontLoc = program->getTextures().findLocation("Font"); - _outlineLoc = program->getUniforms().findLocation("Outline"); - _colorLoc = program->getUniforms().findLocation("Color"); - - - auto f = [&] (QString str, const gpu::Shader::SlotSet& set) { - if (set.size() == 0) { - return; - } - qDebug() << str << set.size(); - for (auto slot : set) { - qDebug() << " " << QString::fromStdString(slot._name) << slot._location; - } - }; - f("getUniforms:", program->getUniforms()); - f("getBuffers:", program->getBuffers()); - f("getTextures:", program->getTextures()); - f("getSamplers:", program->getSamplers()); - f("getInputs:", program->getInputs()); - f("getOutputs:", program->getOutputs()); - - qDebug() << "Texture:" << _texture.get(); - - gpu::StatePointer state = gpu::StatePointer(new gpu::State()); - state->setCullMode(gpu::State::CULL_BACK); - state->setDepthTest(true, true, gpu::LESS_EQUAL); - state->setBlendFunction(false, - gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, - gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); - _pipeline = gpu::PipelinePointer(gpu::Pipeline::create(program, state)); - - // Sanity checks - static const int OFFSET = offsetof(TextureVertex, tex); - assert(OFFSET == sizeof(glm::vec2)); - assert(sizeof(glm::vec2) == 2 * sizeof(float)); - assert(sizeof(glm::vec3) == 3 * sizeof(float)); - assert(sizeof(TextureVertex) == sizeof(glm::vec2) + sizeof(glm::vec2)); - assert(sizeof(QuadBuilder) == 4 * sizeof(TextureVertex)); - - // Setup rendering structures - _format.reset(new gpu::Stream::Format()); - _format->setAttribute(gpu::Stream::POSITION, 0, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::XYZ), 0); - _format->setAttribute(gpu::Stream::TEXCOORD, 0, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV), OFFSET); +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 Glyph::bounds() const { + return glmToRect(offset, size); } -void Font::drawString(gpu::Batch& batch, float x, float y, const QString& str, const glm::vec4& color, - TextRenderer::EffectType effectType, const glm::vec2& bounds) { - if (str == "") { +QRectF Glyph::textureBounds(const glm::vec2 & textureSize) const { + return glmToRect(texOffset, texSize); +} + +void Font::setupGL() { + if (_initialized) { return; } - - if (str != _lastStringRendered || bounds != _lastBounds) { - _verticesBuffer.reset(new gpu::Buffer()); - _numVertices = 0; - _lastStringRendered = str; - _lastBounds = bounds; - - // Top left of text - glm::vec2 advance = glm::vec2(x, y); - foreach(const QString& token, tokenizeForWrapping(str)) { - bool isNewLine = (token == QString('\n')); - bool forceNewLine = false; - - // Handle wrapping - if (!isNewLine && (bounds.x != -1) && (advance.x + computeExtent(token).x > bounds.x)) { - // We are out of the x bound, force new line - forceNewLine = true; - } - if (isNewLine || forceNewLine) { - // Character return, move the advance to a new line - advance = glm::vec2(0.0f, advance.y - _rowHeight); + _initialized = true; - if (isNewLine) { - // No need to draw anything, go directly to next token - continue; - } else if (computeExtent(token).x > bounds.x) { - // token will never fit, stop drawing + _texture = TexturePtr( + new QOpenGLTexture(_image, QOpenGLTexture::GenerateMipMaps)); + _program = ProgramPtr(new QOpenGLShaderProgram()); + if (!_program->create()) { + qFatal("Could not create text shader"); + } + if (!_program->addShaderFromSourceCode(QOpenGLShader::Vertex, sdf_text_vert) || // + !_program->addShaderFromSourceCode(QOpenGLShader::Fragment, sdf_text_frag) || // + !_program->link()) { + qFatal("%s", _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]); + } + + 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 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); + int posLoc = _program->attributeLocation("Position"); + int texLoc = _program->attributeLocation("TexCoord"); + glEnableVertexAttribArray(posLoc); + glVertexAttribPointer(posLoc, 2, GL_FLOAT, false, stride, nullptr); + glEnableVertexAttribArray(texLoc); + glVertexAttribPointer(texLoc, 2, GL_FLOAT, false, stride, offset); + _vao->release(); +} + +// FIXME there has to be a cleaner way of doing this +QStringList Font::tokenizeForWrapping(const QString & str) const { + QStringList result; + foreach(const QString & token1, str.split(" ")) { + bool lineFeed = false; + if (token1.isEmpty()) { + result << token1; + continue; + } + foreach(const QString & token2, token1.split("\n")) { + if (lineFeed) { + result << "\n"; + } + if (token2.size()) { + result << token2; + } + lineFeed = true; + } + } + return result; +} + + +glm::vec2 Font::computeTokenExtent(const QString & token) const { + glm::vec2 advance(0, _rowHeight - _descent); + foreach(QChar c, token) { + assert(c != ' ' && c != '\n'); + const Glyph & m = getGlyph(c); + advance.x += m.d; + } + return advance; +} + + +glm::vec2 Font::computeExtent(const QString & str) const { + glm::vec2 extent(0, _rowHeight - _descent); + // FIXME, come up with a better method of splitting text + // that will allow wrapping but will preserve things like + // tabs or consecutive spaces + bool firstTokenOnLine = true; + float lineWidth = 0.0f; + QStringList tokens = tokenizeForWrapping(str); + foreach(const QString & token, tokens) { + if (token == "\n") { + extent.x = std::max(lineWidth, extent.x); + lineWidth = 0.0f; + extent.y += _rowHeight; + firstTokenOnLine = true; + continue; + } + if (!firstTokenOnLine) { + lineWidth += _spaceWidth; + } + lineWidth += computeTokenExtent(token).x; + firstTokenOnLine = false; + } + extent.x = std::max(lineWidth, extent.x); + return extent; +} + +// 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, + const glm::vec2& bounds) { + + setupGL(); + + // 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); + } + // Needed? + glEnable(GL_TEXTURE_2D); + _texture->bind(); + _vao->bind(); + + MatrixStack & mv = MatrixStack::modelview(); + // scale the modelview into font units + mv.translate(glm::vec3(0, _ascent, 0)); + foreach(const QString & token, tokenizeForWrapping(str)) { + if (token == "\n") { + advance.x = 0.0f; + advance.y -= _rowHeight; + // If we've wrapped right out of the bounds, then we're + // done with rendering the tokens + if (bounds.y > 0 && std::abs(advance.y) > bounds.y) { + break; + } + continue; + } + + glm::vec2 tokenExtent = computeTokenExtent(token); + if (bounds.x > 0 && advance.x > 0) { + // We check if we'll be out of bounds + if (advance.x + tokenExtent.x >= bounds.x) { + // We're out of bounds, so wrap to the next line + advance.x = 0.0f; + advance.y -= _rowHeight; + // If we've wrapped right out of the bounds, then we're + // done with rendering the tokens + if (bounds.y > 0 && std::abs(advance.y) > bounds.y) { break; } } - if ((bounds.y != -1) && (advance.y - _fontSize < -bounds.y)) { - // We are out of the y bound, stop drawing - break; - } - - // Draw the token - if (!isNewLine) { - for (auto c : token) { - auto glyph = _glyphs[c]; - - QuadBuilder qd(glyph, advance - glm::vec2(0.0f, _fontSize)); - _verticesBuffer->append(sizeof(QuadBuilder), (const gpu::Byte*)&qd); - _numVertices += 4; - - // Advance by glyph size - advance.x += glyph.d; - } - - // Add space after all non return tokens - advance.x += _spaceWidth; - } } + + foreach(const QChar & c, token) { + // get metrics for this character to speed up measurements + const Glyph & m = getGlyph(c); + // 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(); + _texture->release(); // TODO: Brad & Sam, let's discuss this. Without this non-textured quads get their colors borked. + _program->release(); + // FIXME, needed? + // glDisable(GL_TEXTURE_2D); - setupGPU(); - batch.setPipeline(_pipeline); - batch.setUniformTexture(_fontLoc, _texture); - batch._glUniform1f(_outlineLoc, (effectType == TextRenderer::OUTLINE_EFFECT) ? 1.0f : 0.0f); - batch._glUniform4fv(_colorLoc, 1, (const GLfloat*)&color); - batch.setInputFormat(_format); - batch.setInputBuffer(0, _verticesBuffer, 0, _format->getChannels().at(0)._stride); - batch.draw(gpu::QUADS, _numVertices, 0); + return advance; } TextRenderer* TextRenderer::getInstance(const char* family, float pointSize, @@ -495,13 +512,10 @@ TextRenderer* TextRenderer::getInstance(const char* family, float pointSize, effectThickness, color); } -TextRenderer::TextRenderer(const char* family, float pointSize, int weight, bool italic, - EffectType effect, int effectThickness, const QColor& color) : - _effectType(effect), - _effectThickness(effectThickness), - _pointSize(pointSize), - _color(toGlm(color)), - _font(loadFont(family)) { +TextRenderer::TextRenderer(const char* family, float pointSize, int weight, + bool italic, EffectType effect, int effectThickness, + const QColor& color) : + _effectType(effect), _effectThickness(effectThickness), _pointSize(pointSize), _color(color), _font(loadFont(family)) { if (!_font) { qWarning() << "Unable to load font with family " << family; _font = loadFont("Courier"); @@ -517,46 +531,40 @@ TextRenderer::TextRenderer(const char* family, float pointSize, int weight, bool TextRenderer::~TextRenderer() { } -glm::vec2 TextRenderer::computeExtent(const QString& str) const { +glm::vec2 TextRenderer::computeExtent(const QString & str) const { + float scale = (_pointSize / DEFAULT_POINT_SIZE) * 0.25f; if (_font) { - float scale = (_pointSize / DEFAULT_POINT_SIZE) * 0.25f; return _font->computeExtent(str) * scale; } return glm::vec2(0.1f,0.1f); } -float TextRenderer::getRowHeight() const { - if (_font) { - return _font->getRowHeight(); +float TextRenderer::draw(float x, float y, const QString & str, + const glm::vec4& color, const glm::vec2 & bounds) { + glm::vec4 actualColor(color); + if (actualColor.r < 0) { + actualColor = toGlm(_color); } - return 1.0f; -} -void TextRenderer::draw(float x, float y, const QString& str, const glm::vec4& color, const glm::vec2& bounds) { - // The font does all the OpenGL work - if (_font) { - float scale = (_pointSize / DEFAULT_POINT_SIZE) * 0.25f; - glPushMatrix(); - gpu::Batch batch; - Transform transform; - transform.setTranslation(glm::vec3(x, y, 0.0f)); - transform.setScale(glm::vec3(scale, -scale, scale)); - batch.setModelTransform(transform); - draw3D(batch, 0.0f, 0.0f, str, color, bounds); - gpu::GLBackend::renderBatch(batch, true); - glPopMatrix(); - } -} + float scale = (_pointSize / DEFAULT_POINT_SIZE) * 0.25f; + glm::vec2 result; -void TextRenderer::draw3D(gpu::Batch& batch, float x, float y, const QString& str, const glm::vec4& color, - const glm::vec2& bounds) { - // The font does all the OpenGL work - if (_font) { - glm::vec4 actualColor(color); - if (actualColor.r < 0) { - actualColor = _color; + MatrixStack::withPushAll([&] { + MatrixStack & mv = MatrixStack::modelview(); + MatrixStack & pr = MatrixStack::projection(); + gpu::GLBackend::fetchMatrix(GL_MODELVIEW_MATRIX, mv.top()); + gpu::GLBackend::fetchMatrix(GL_PROJECTION_MATRIX, pr.top()); + + // 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 + if (_font) { + result = _font->drawString(x, y, str, actualColor, _effectType, bounds / scale); } - _font->drawString(batch, x, y, str, actualColor, _effectType, bounds); - } + }); + return result.x; } diff --git a/libraries/render-utils/src/TextRenderer.h b/libraries/render-utils/src/TextRenderer.h index 0c7faa9e6b..85bb18cec9 100644 --- a/libraries/render-utils/src/TextRenderer.h +++ b/libraries/render-utils/src/TextRenderer.h @@ -12,8 +12,21 @@ #ifndef hifi_TextRenderer_h #define hifi_TextRenderer_h +#include #include +#include #include +#include +#include +#include +#include +#include + +#include +#include + +// a special "character" that renders as a solid block +const char SOLID_BLOCK_CHAR = 127; // the standard sans serif font family #define SANS_FONT_FAMILY "Helvetica" @@ -33,9 +46,6 @@ #define INCONSOLATA_FONT_WEIGHT QFont::Bold #endif -namespace gpu { -class Batch; -} class Font; // TextRenderer is actually a fairly thin wrapper around a Font class @@ -49,13 +59,13 @@ public: ~TextRenderer(); - glm::vec2 computeExtent(const QString& str) const; - float getRowHeight() const; - - void draw(float x, float y, const QString& str, const glm::vec4& color = glm::vec4(-1.0f), - const glm::vec2& bounds = glm::vec2(-1.0f)); - void draw3D(gpu::Batch& batch, float x, float y, const QString& str, const glm::vec4& color = glm::vec4(-1.0f), - const glm::vec2& bounds = glm::vec2(-1.0f)); + glm::vec2 computeExtent(const QString & str) const; + + float draw( + float x, float y, + const QString & str, + const glm::vec4& color = glm::vec4(-1.0f), + const glm::vec2& bounds = glm::vec2(-1.0f)); private: TextRenderer(const char* family, float pointSize = -1, int weight = -1, bool italic = false, @@ -70,9 +80,9 @@ private: const float _pointSize; // text color - const glm::vec4 _color; + const QColor _color; - Font* _font; + Font * _font; }; diff --git a/libraries/render-utils/src/sdf_text.slf b/libraries/render-utils/src/sdf_text.slf index 3980045d08..1affbe4c57 100644 --- a/libraries/render-utils/src/sdf_text.slf +++ b/libraries/render-utils/src/sdf_text.slf @@ -11,8 +11,10 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html uniform sampler2D Font; -uniform float Outline; uniform vec4 Color; +uniform bool Outline; + +varying vec2 vTexCoord; const float gamma = 2.6; const float smoothing = 100.0; @@ -20,28 +22,28 @@ const float interiorCutoff = 0.8; const float outlineExpansion = 0.2; void main() { - // retrieve signed distance - float sdf = texture2D(Font, gl_TexCoord[0].xy).g; - if (Outline == 1.0f) { - if (sdf > interiorCutoff) { - sdf = 1.0 - sdf; - } else { - sdf += outlineExpansion; - } - } - // perform adaptive anti-aliasing of the edges - // The larger we're rendering, the less anti-aliasing we need - float s = smoothing * length(fwidth(gl_TexCoord[0])); - 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.01) { - discard; + // retrieve signed distance + float sdf = texture2D(Font, vTexCoord).r; + if (Outline) { + if (sdf > interiorCutoff) { + sdf = 1.0 - sdf; + } else { + sdf += outlineExpansion; } + } + // 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); - // final color - gl_FragColor = vec4(Color.rgb, a); + // gamma correction for linear attenuation + a = pow(a, 1.0 / gamma); + + if (a < 0.01) { + discard; + } + + // final color + gl_FragColor = vec4(Color.rgb, a); } \ No newline at end of file diff --git a/libraries/render-utils/src/sdf_text.slv b/libraries/render-utils/src/sdf_text.slv index f7c35a257c..27db1c4985 100644 --- a/libraries/render-utils/src/sdf_text.slv +++ b/libraries/render-utils/src/sdf_text.slv @@ -9,15 +9,16 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -<@include gpu/Transform.slh@> -<$declareStandardTransform()$> +uniform mat4 Projection; +uniform mat4 ModelView; + +attribute vec2 Position; +attribute vec2 TexCoord; + +varying vec2 vTexCoord; void main() { - gl_TexCoord[0] = gl_MultiTexCoord0; - - // standard transform - TransformCamera cam = getTransformCamera(); - TransformObject obj = getTransformObject(); - <$transformModelToClipPos(cam, obj, gl_Vertex, gl_Position)$> + vTexCoord = TexCoord; + gl_Position = Projection * ModelView * vec4(Position, 0.0, 1.0); } \ No newline at end of file