diff --git a/libraries/render-utils/src/TextRenderer.cpp b/libraries/render-utils/src/TextRenderer.cpp index 87cf9b2728..fe4c1e4d74 100644 --- a/libraries/render-utils/src/TextRenderer.cpp +++ b/libraries/render-utils/src/TextRenderer.cpp @@ -11,6 +11,9 @@ #include +#include +#include + #include #include #include @@ -18,18 +21,9 @@ #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" @@ -38,22 +32,47 @@ #include "sdf_text_vert.h" #include "sdf_text_frag.h" +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); } +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.topLeft(), tr.bottomLeft()); + vertices[3] = TextureVertex(r.topRight(), tr.bottomRight()); + } +}; + // 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 @@ -68,13 +87,13 @@ struct Glyph { float d; // xadvance - adjusts character positioning size_t indexOffset; - QRectF bounds() const; - QRectF textureBounds(const glm::vec2 & textureSize) const; + QRectF bounds() const { return glmToRect(offset, size); } + QRectF textureBounds() const { return glmToRect(texOffset, texSize); } - 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; @@ -85,64 +104,52 @@ void Glyph::read(QIODevice & in) { texSize = size; } -const float DEFAULT_POINT_SIZE = 12; - class Font { public: - Font(); - using TexturePtr = QSharedPointer < QOpenGLTexture >; - using VertexArrayPtr = QSharedPointer< QOpenGLVertexArrayObject >; - using ProgramPtr = QSharedPointer < QOpenGLShaderProgram >; - using BufferPtr = QSharedPointer < QOpenGLBuffer >; - - // 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, + glm::vec2 computeExtent(const QString& str) const; + + // Render string to batch + void drawString(gpu::Batch& batch, 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; - - bool _initialized; + 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; + + // 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; + + // gpu structures + gpu::PipelinePointer _pipeline; + gpu::TexturePointer _texture; + gpu::Stream::FormatPointer _format; + gpu::BufferView _vertices; + gpu::BufferView _texCoords; + + // last string render characteristics + QString _lastStringRendered; + glm::vec2 _lastBounds; }; static QHash LOADED_FONTS; @@ -189,7 +196,7 @@ Font* loadFont(const QString& family) { return LOADED_FONTS[family]; } -Font::Font() : _initialized(false) { +Font::Font() { static bool fontResourceInitComplete = false; if (!fontResourceInitComplete) { Q_INIT_RESOURCE(fonts); @@ -198,13 +205,50 @@ Font::Font() : _initialized(false) { } // 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); @@ -232,7 +276,7 @@ void Font::read(QIODevice& in) { readStream(in, _descent); readStream(in, _spaceWidth); _fontSize = _ascent + _descent; - _rowHeight = _fontSize + _descent; + _rowHeight = _fontSize + _leading; // Read character count uint16_t count; @@ -240,266 +284,127 @@ 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 - if (!_image.loadFromData(in.readAll(), "PNG")) { + QImage image; + image.loadFromData(in.readAll(), "PNG"); + if (!image.isNull()) { qFatal("Failed to read SDFF image"); } + 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); _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; }; + + // 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; + slotBindings.insert(gpu::Shader::Binding("Outline", 0)); + slotBindings.insert(gpu::Shader::Binding("Offset", 1)); + slotBindings.insert(gpu::Shader::Binding("Color", 2)); + gpu::Shader::makeProgram(*program, slotBindings); + + gpu::StatePointer state = gpu::StatePointer(new gpu::State()); + + _pipeline = gpu::PipelinePointer(gpu::Pipeline::create(program, state)); } -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); -} - -QRectF Glyph::textureBounds(const glm::vec2 & textureSize) const { - return glmToRect(texOffset, texSize); -} - -void Font::setupGL() { - if (_initialized) { - return; - } - _initialized = true; - - _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"; +void Font::drawString(gpu::Batch& batch, float x, float y, const QString& str, const glm::vec4& color, + TextRenderer::EffectType effectType, const glm::vec2& bounds) { + // Top left of text + glm::vec2 advance = glm::vec2(0.0f, 0.0f); + + if (str != _lastStringRendered || bounds != _lastBounds) { + gpu::BufferPointer vertices = gpu::BufferPointer(new gpu::Buffer()); + 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)) { + // We are out of the x bound, force new line + forceNewLine = true; } - 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 (isNewLine || forceNewLine) { + // Character return, move the advance to a new line + advance = glm::vec2(0.0f, advance.y + _rowHeight); + if (isNewLine) { + // No need to draw anything, go directly to next token + continue; } } + 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]; + + // Build translated quad and add it to the buffer + QuadBuilder qd(glyph.bounds().translated(advance.x, advance.y), + glyph.textureBounds()); + vertices->append(sizeof(QuadBuilder), (const gpu::Byte*)qd.vertices); + + // 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; + + // Setup rendering structures + static const int STRIDES = sizeof(TextureVertex); + static const int OFFSET = offsetof(TextureVertex, tex); + _format = gpu::Stream::FormatPointer(new gpu::Stream::Format()); + _format->setAttribute(gpu::Stream::POSITION, gpu::Stream::POSITION, + gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV)); + _format->setAttribute(gpu::Stream::TEXCOORD, gpu::Stream::TEXCOORD, + gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV)); + + _vertices = gpu::BufferView(vertices, 0, vertices->getSize(), STRIDES, + _format->getAttributes().at(gpu::Stream::POSITION)._element); + _texCoords = gpu::BufferView(vertices, OFFSET, vertices->getSize(), STRIDES, + _format->getAttributes().at(gpu::Stream::TEXCOORD)._element); + _lastStringRendered = str; + _lastBounds = bounds; } - - _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); - - return advance; + batch.setInputFormat(_format); + batch.setInputBuffer(0, _vertices); + batch.setInputBuffer(1, _texCoords); + batch.setUniformTexture(2, _texture); + batch._glUniform1f(0, (effectType == TextRenderer::OUTLINE_EFFECT) ? 1.0f : 0.0f); + batch._glUniform2f(1, x, y); + batch._glUniform4fv(2, 4, (const GLfloat*)&color); + batch.draw(gpu::QUADS, _vertices.getNumElements()); } TextRenderer* TextRenderer::getInstance(const char* family, float pointSize, @@ -531,7 +436,7 @@ TextRenderer::TextRenderer(const char* family, float pointSize, int weight, 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) { return _font->computeExtent(str) * scale; @@ -539,32 +444,25 @@ glm::vec2 TextRenderer::computeExtent(const QString & str) const { return glm::vec2(0.1f,0.1f); } -float TextRenderer::draw(float x, float y, const QString & str, - const glm::vec4& color, const glm::vec2 & bounds) { +void TextRenderer::draw(float x, float y, const QString& str, const glm::vec4& color, const glm::vec2& bounds) { + gpu::Batch batch; + draw(batch, x, y, str, color, bounds); + gpu::GLBackend::renderBatch(batch); +} + +void TextRenderer::draw(gpu::Batch& batch, 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); } - + float scale = (_pointSize / DEFAULT_POINT_SIZE) * 0.25f; - glm::vec2 result; - - 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); - } - }); - return result.x; + + // The font does all the OpenGL work + if (_font) { + _font->drawString(batch, x, y, str, actualColor, _effectType, bounds / scale); + } } diff --git a/libraries/render-utils/src/TextRenderer.h b/libraries/render-utils/src/TextRenderer.h index 85bb18cec9..6dbe3cbc33 100644 --- a/libraries/render-utils/src/TextRenderer.h +++ b/libraries/render-utils/src/TextRenderer.h @@ -25,9 +25,6 @@ #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" @@ -46,6 +43,9 @@ const char SOLID_BLOCK_CHAR = 127; #define INCONSOLATA_FONT_WEIGHT QFont::Bold #endif +namespace gpu { +class Batch; +} class Font; // TextRenderer is actually a fairly thin wrapper around a Font class @@ -59,13 +59,12 @@ public: ~TextRenderer(); - 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)); + glm::vec2 computeExtent(const QString& str) 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 draw(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)); private: TextRenderer(const char* family, float pointSize = -1, int weight = -1, bool italic = false, @@ -82,7 +81,7 @@ private: // text 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 1affbe4c57..0cf6e3fb1b 100644 --- a/libraries/render-utils/src/sdf_text.slf +++ b/libraries/render-utils/src/sdf_text.slf @@ -10,11 +10,13 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -uniform sampler2D Font; -uniform vec4 Color; -uniform bool Outline; +<@include DeferredBufferWrite.slh@> -varying vec2 vTexCoord; +uniform sampler2D Font; +uniform float Outline; + +// the interpolated normal +varying vec4 interpolatedNormal; const float gamma = 2.6; const float smoothing = 100.0; @@ -22,28 +24,33 @@ const float interiorCutoff = 0.8; const float outlineExpansion = 0.2; void main() { - // retrieve signed distance - float sdf = texture2D(Font, vTexCoord).r; - if (Outline) { - if (sdf > interiorCutoff) { - sdf = 1.0 - sdf; - } else { - sdf += outlineExpansion; + // retrieve signed distance + float sdf = texture2D(Font, vTexCoord).r; + 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(vTexCoord)); - float w = clamp( s, 0.0, 0.5); - float a = smoothstep(0.5 - w, 0.5 + w, sdf); - - // gamma correction for linear attenuation - a = pow(a, 1.0 / gamma); - - if (a < 0.01) { - discard; - } - - // final color - gl_FragColor = vec4(Color.rgb, a); + // perform adaptive anti-aliasing of the edges + // The larger we're rendering, the less anti-aliasing we need + float s = smoothing * length(fwidth(vTexCoord)); + float w = clamp( s, 0.0, 0.5); + float a = smoothstep(0.5 - w, 0.5 + w, sdf); + + // gamma correction for linear attenuation + a = pow(a, 1.0 / gamma); + + if (a < 0.01) { + discard; + } + + // final color + packDeferredFragment( + normalize(interpolatedNormal.xyz), + a, + gl_Color.rgb, + gl_FrontMaterial.specular.rgb, + gl_FrontMaterial.shininess); } \ 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 27db1c4985..157efd7f4b 100644 --- a/libraries/render-utils/src/sdf_text.slv +++ b/libraries/render-utils/src/sdf_text.slv @@ -9,16 +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@> -uniform mat4 Projection; -uniform mat4 ModelView; +<$declareStandardTransform()$> -attribute vec2 Position; -attribute vec2 TexCoord; +uniform float Offset[2]; -varying vec2 vTexCoord; void main() { - vTexCoord = TexCoord; - gl_Position = Projection * ModelView * vec4(Position, 0.0, 1.0); + // standard transform + TransformCamera cam = getTransformCamera(); + TransformObject obj = getTransformObject(); + <$transformModelToClipPos(cam, obj, gl_Vertex + vec4(Offset[0], Offset[1], 0.0f, 0.0f), gl_Position)$> } \ No newline at end of file