First pass at moving TextureRender to use a batch

This commit is contained in:
Atlante45 2015-05-26 18:13:23 +02:00
parent 88d42f931e
commit bcee01b3a3
4 changed files with 276 additions and 372 deletions

View file

@ -11,6 +11,9 @@
#include <gpu/GPUConfig.h> #include <gpu/GPUConfig.h>
#include <gpu/GLBackend.h>
#include <gpu/Stream.h>
#include <QApplication> #include <QApplication>
#include <QtDebug> #include <QtDebug>
#include <QString> #include <QString>
@ -18,18 +21,9 @@
#include <QBuffer> #include <QBuffer>
#include <QFile> #include <QFile>
// FIXME, decouple from the GL headers
#include <QOpenGLShaderProgram>
#include <QOpenGLTexture>
#include <QOpenGLVertexArrayObject>
#include <QOpenGLBuffer>
#include <glm/glm.hpp> #include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp> #include <glm/gtc/matrix_transform.hpp>
#include "gpu/GLBackend.h"
#include "gpu/Stream.h"
#include "GLMHelpers.h" #include "GLMHelpers.h"
#include "MatrixStack.h" #include "MatrixStack.h"
#include "RenderUtilsLogging.h" #include "RenderUtilsLogging.h"
@ -38,22 +32,47 @@
#include "sdf_text_vert.h" #include "sdf_text_vert.h"
#include "sdf_text_frag.h" #include "sdf_text_frag.h"
const float DEFAULT_POINT_SIZE = 12;
// Helper functions for reading binary data from an IO device // Helper functions for reading binary data from an IO device
template<class T> template<class T>
void readStream(QIODevice & in, T & t) { void readStream(QIODevice& in, T& t) {
in.read((char*) &t, sizeof(t)); in.read((char*) &t, sizeof(t));
} }
template<typename T, size_t N> template<typename T, size_t N>
void readStream(QIODevice & in, T (&t)[N]) { void readStream(QIODevice& in, T (&t)[N]) {
in.read((char*) t, N); in.read((char*) t, N);
} }
template<class T, size_t N> template<class T, size_t N>
void fillBuffer(QBuffer & buffer, T (&t)[N]) { void fillBuffer(QBuffer& buffer, T (&t)[N]) {
buffer.setData((const char*) 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 support the shadow effect, or remove it from the API
// FIXME figure out how to improve the anti-aliasing on the // FIXME figure out how to improve the anti-aliasing on the
// interior of the outline fonts // interior of the outline fonts
@ -68,13 +87,13 @@ struct Glyph {
float d; // xadvance - adjusts character positioning float d; // xadvance - adjusts character positioning
size_t indexOffset; size_t indexOffset;
QRectF bounds() const; QRectF bounds() const { return glmToRect(offset, size); }
QRectF textureBounds(const glm::vec2 & textureSize) const; 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; uint16_t charcode;
readStream(in, charcode); readStream(in, charcode);
c = charcode; c = charcode;
@ -85,17 +104,25 @@ void Glyph::read(QIODevice & in) {
texSize = size; texSize = size;
} }
const float DEFAULT_POINT_SIZE = 12;
class Font { class Font {
public: public:
Font(); Font();
using TexturePtr = QSharedPointer < QOpenGLTexture >; void read(QIODevice& path);
using VertexArrayPtr = QSharedPointer< QOpenGLVertexArrayObject >;
using ProgramPtr = QSharedPointer < QOpenGLShaderProgram >; glm::vec2 computeExtent(const QString& str) const;
using BufferPtr = QSharedPointer < QOpenGLBuffer >;
// 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;
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 // maps characters to cached glyph info
// HACK... the operator[] const for QHash returns a // HACK... the operator[] const for QHash returns a
@ -104,45 +131,25 @@ public:
// copies // copies
mutable QHash<QChar, Glyph> _glyphs; mutable QHash<QChar, Glyph> _glyphs;
// the id of the glyph texture to which we're currently writing // Font characteristics
GLuint _currentTextureID;
int _pointSize;
// the height of the current row of characters
int _rowHeight;
QString _family; QString _family;
float _fontSize { 0 }; float _fontSize = 0.0f;
float _leading { 0 }; float _rowHeight = 0.0f;
float _ascent { 0 }; float _leading = 0.0f;
float _descent { 0 }; float _ascent = 0.0f;
float _spaceWidth { 0 }; float _descent = 0.0f;
float _spaceWidth = 0.0f;
BufferPtr _vertices; // gpu structures
BufferPtr _indices; gpu::PipelinePointer _pipeline;
TexturePtr _texture; gpu::TexturePointer _texture;
VertexArrayPtr _vao; gpu::Stream::FormatPointer _format;
QImage _image; gpu::BufferView _vertices;
ProgramPtr _program; gpu::BufferView _texCoords;
const Glyph & getGlyph(const QChar & c) const; // last string render characteristics
void read(QIODevice& path); QString _lastStringRendered;
// Initialize the OpenGL structures glm::vec2 _lastBounds;
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;
bool _initialized;
}; };
static QHash<QString, Font*> LOADED_FONTS; static QHash<QString, Font*> LOADED_FONTS;
@ -189,7 +196,7 @@ Font* loadFont(const QString& family) {
return LOADED_FONTS[family]; return LOADED_FONTS[family];
} }
Font::Font() : _initialized(false) { Font::Font() {
static bool fontResourceInitComplete = false; static bool fontResourceInitComplete = false;
if (!fontResourceInitComplete) { if (!fontResourceInitComplete) {
Q_INIT_RESOURCE(fonts); 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 // 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)) { if (!_glyphs.contains(c)) {
return _glyphs[QChar('?')]; return _glyphs[QChar('?')];
} }
return _glyphs[c]; 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) { void Font::read(QIODevice& in) {
uint8_t header[4]; uint8_t header[4];
readStream(in, header); readStream(in, header);
@ -232,7 +276,7 @@ void Font::read(QIODevice& in) {
readStream(in, _descent); readStream(in, _descent);
readStream(in, _spaceWidth); readStream(in, _spaceWidth);
_fontSize = _ascent + _descent; _fontSize = _ascent + _descent;
_rowHeight = _fontSize + _descent; _rowHeight = _fontSize + _leading;
// Read character count // Read character count
uint16_t count; uint16_t count;
@ -240,266 +284,127 @@ void Font::read(QIODevice& in) {
// read metrics data for each character // read metrics data for each character
QVector<Glyph> glyphs(count); QVector<Glyph> glyphs(count);
// std::for_each instead of Qt foreach because we need non-const references // 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); g.read(in);
}); });
// read image data // 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"); 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(); _glyphs.clear();
glm::vec2 imageSize = toGlm(image.size());
foreach(Glyph g, glyphs) { foreach(Glyph g, glyphs) {
// Adjust the pixel texture coordinates into UV coordinates, // Adjust the pixel texture coordinates into UV coordinates,
glm::vec2 imageSize = toGlm(_image.size());
g.texSize /= imageSize; g.texSize /= imageSize;
g.texOffset /= imageSize; g.texOffset /= imageSize;
// store in the character to glyph hash // store in the character to glyph hash
_glyphs[g.c] = g; _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 { void Font::drawString(gpu::Batch& batch, float x, float y, const QString& str, const glm::vec4& color,
glm::vec2 pos; TextRenderer::EffectType effectType, const glm::vec2& bounds) {
glm::vec2 tex; // Top left of text
TextureVertex() { 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;
} }
TextureVertex(const glm::vec2 & pos, const glm::vec2 & tex) : if (isNewLine || forceNewLine) {
pos(pos), tex(tex) { // Character return, move the advance to a new line
} advance = glm::vec2(0.0f, advance.y + _rowHeight);
TextureVertex(const QPointF & pos, const QPointF & tex) : if (isNewLine) {
pos(pos.x(), pos.y()), tex(tex.x(), tex.y()) { // No need to draw anything, go directly to next token
}
};
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<TextureVertex> vertexData;
std::vector<GLuint> indexData;
vertexData.reserve(_glyphs.size() * 4);
std::for_each(_glyphs.begin(), _glyphs.end(), [&](Glyph & m) {
GLuint index = (GLuint)vertexData.size();
QRectF bounds = m.bounds();
QRectF texBounds = m.textureBounds(toGlm(_image.size()));
QuadBuilder qb(bounds, texBounds);
for (int i = 0; i < 4; ++i) {
vertexData.push_back(qb.vertices[i]);
}
m.indexOffset = indexData.size() * sizeof(GLuint);
// FIXME use triangle strips + primitive restart index
indexData.push_back(index + 0);
indexData.push_back(index + 1);
indexData.push_back(index + 2);
indexData.push_back(index + 0);
indexData.push_back(index + 2);
indexData.push_back(index + 3);
});
_vao = VertexArrayPtr(new 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; continue;
} }
foreach(const QString & token2, token1.split("\n")) {
if (lineFeed) {
result << "\n";
} }
if (token2.size()) { if ((bounds.y != -1) && (advance.y + _fontSize > bounds.y)) {
result << token2; // We are out of the y bound, stop drawing
}
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; break;
} }
continue;
// 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;
} }
glm::vec2 tokenExtent = computeTokenExtent(token); // Add space after all non return tokens
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;
}
}
}
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; advance.x += _spaceWidth;
} }
}
_vao->release(); // Setup rendering structures
_texture->release(); // TODO: Brad & Sam, let's discuss this. Without this non-textured quads get their colors borked. static const int STRIDES = sizeof(TextureVertex);
_program->release(); static const int OFFSET = offsetof(TextureVertex, tex);
// FIXME, needed? _format = gpu::Stream::FormatPointer(new gpu::Stream::Format());
// glDisable(GL_TEXTURE_2D); _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;
}
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, TextRenderer* TextRenderer::getInstance(const char* family, float pointSize,
@ -531,7 +436,7 @@ TextRenderer::TextRenderer(const char* family, float pointSize, int weight,
TextRenderer::~TextRenderer() { 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; float scale = (_pointSize / DEFAULT_POINT_SIZE) * 0.25f;
if (_font) { if (_font) {
return _font->computeExtent(str) * scale; return _font->computeExtent(str) * scale;
@ -539,32 +444,25 @@ glm::vec2 TextRenderer::computeExtent(const QString & str) const {
return glm::vec2(0.1f,0.1f); return glm::vec2(0.1f,0.1f);
} }
float TextRenderer::draw(float x, float y, const QString & str, void TextRenderer::draw(float x, float y, const QString& str, const glm::vec4& color, const glm::vec2& bounds) {
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); glm::vec4 actualColor(color);
if (actualColor.r < 0) { if (actualColor.r < 0) {
actualColor = toGlm(_color); actualColor = toGlm(_color);
} }
float scale = (_pointSize / DEFAULT_POINT_SIZE) * 0.25f; 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 // The font does all the OpenGL work
if (_font) { if (_font) {
result = _font->drawString(x, y, str, actualColor, _effectType, bounds / scale); _font->drawString(batch, x, y, str, actualColor, _effectType, bounds / scale);
} }
});
return result.x;
} }

View file

@ -25,9 +25,6 @@
#include <gpu/Resource.h> #include <gpu/Resource.h>
#include <gpu/Stream.h> #include <gpu/Stream.h>
// a special "character" that renders as a solid block
const char SOLID_BLOCK_CHAR = 127;
// the standard sans serif font family // the standard sans serif font family
#define SANS_FONT_FAMILY "Helvetica" #define SANS_FONT_FAMILY "Helvetica"
@ -46,6 +43,9 @@ const char SOLID_BLOCK_CHAR = 127;
#define INCONSOLATA_FONT_WEIGHT QFont::Bold #define INCONSOLATA_FONT_WEIGHT QFont::Bold
#endif #endif
namespace gpu {
class Batch;
}
class Font; class Font;
// TextRenderer is actually a fairly thin wrapper around a Font class // TextRenderer is actually a fairly thin wrapper around a Font class
@ -59,12 +59,11 @@ public:
~TextRenderer(); ~TextRenderer();
glm::vec2 computeExtent(const QString & str) const; glm::vec2 computeExtent(const QString& str) const;
float draw( void draw(float x, float y, const QString& str, const glm::vec4& color = glm::vec4(-1.0f),
float x, float y, const glm::vec2& bounds = glm::vec2(-1.0f));
const QString & str, void draw(gpu::Batch& batch, float x, float y, const QString& str, const glm::vec4& color = glm::vec4(-1.0f),
const glm::vec4& color = glm::vec4(-1.0f),
const glm::vec2& bounds = glm::vec2(-1.0f)); const glm::vec2& bounds = glm::vec2(-1.0f));
private: private:
@ -82,7 +81,7 @@ private:
// text color // text color
const QColor _color; const QColor _color;
Font * _font; Font* _font;
}; };

View file

@ -10,11 +10,13 @@
// Distributed under the Apache License, Version 2.0. // Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
uniform sampler2D Font; <@include DeferredBufferWrite.slh@>
uniform vec4 Color;
uniform bool Outline;
varying vec2 vTexCoord; uniform sampler2D Font;
uniform float Outline;
// the interpolated normal
varying vec4 interpolatedNormal;
const float gamma = 2.6; const float gamma = 2.6;
const float smoothing = 100.0; const float smoothing = 100.0;
@ -24,7 +26,7 @@ const float outlineExpansion = 0.2;
void main() { void main() {
// retrieve signed distance // retrieve signed distance
float sdf = texture2D(Font, vTexCoord).r; float sdf = texture2D(Font, vTexCoord).r;
if (Outline) { if (Outline == 1.0f) {
if (sdf > interiorCutoff) { if (sdf > interiorCutoff) {
sdf = 1.0 - sdf; sdf = 1.0 - sdf;
} else { } else {
@ -45,5 +47,10 @@ void main() {
} }
// final color // final color
gl_FragColor = vec4(Color.rgb, a); packDeferredFragment(
normalize(interpolatedNormal.xyz),
a,
gl_Color.rgb,
gl_FrontMaterial.specular.rgb,
gl_FrontMaterial.shininess);
} }

View file

@ -9,16 +9,16 @@
// Distributed under the Apache License, Version 2.0. // Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
// //
<@include gpu/Transform.slh@>
uniform mat4 Projection; <$declareStandardTransform()$>
uniform mat4 ModelView;
attribute vec2 Position; uniform float Offset[2];
attribute vec2 TexCoord;
varying vec2 vTexCoord;
void main() { void main() {
vTexCoord = TexCoord; // standard transform
gl_Position = Projection * ModelView * vec4(Position, 0.0, 1.0); TransformCamera cam = getTransformCamera();
TransformObject obj = getTransformObject();
<$transformModelToClipPos(cam, obj, gl_Vertex + vec4(Offset[0], Offset[1], 0.0f, 0.0f), gl_Position)$>
} }