resolve conflicts on merge with upstream master

This commit is contained in:
Stephen Birarda 2015-07-17 16:48:46 -07:00
commit f8c832e50b
37 changed files with 751 additions and 1317 deletions

View file

@ -0,0 +1,23 @@
import Hifi 1.0
import QtQuick 2.3
import QtQuick.Controls 1.2
TextOverlayElement {
id: root
Rectangle {
color: root.backgroundColor
anchors.fill: parent
Text {
x: root.leftMargin
y: root.topMargin
id: text
objectName: "textElement"
text: root.text
color: root.textColor
font.family: root.fontFamily
font.pixelSize: root.fontSize
lineHeightMode: Text.FixedHeight
lineHeight: root.lineHeight
}
}
}

View file

@ -92,7 +92,6 @@
#include <SettingHandle.h>
#include <SimpleAverage.h>
#include <SoundCache.h>
#include <TextRenderer.h>
#include <Tooltip.h>
#include <UserActivityLogger.h>
#include <UUID.h>

View file

@ -23,7 +23,6 @@
#include <ByteCountCoding.h>
#include <SharedUtil.h>
#include <TextRenderer.h>
#include "InterfaceConfig.h"
#include "world.h"
@ -79,27 +78,6 @@ const glm::vec3 randVector() {
return glm::vec3(randFloat() - 0.5f, randFloat() - 0.5f, randFloat() - 0.5f) * 2.0f;
}
static TextRenderer* textRenderer(int mono) {
static TextRenderer* monoRenderer = TextRenderer::getInstance(MONO_FONT_FAMILY);
static TextRenderer* proportionalRenderer = TextRenderer::getInstance(SANS_FONT_FAMILY,
-1, -1, false, TextRenderer::SHADOW_EFFECT);
static TextRenderer* inconsolataRenderer = TextRenderer::getInstance(INCONSOLATA_FONT_FAMILY, -1, INCONSOLATA_FONT_WEIGHT,
false);
switch (mono) {
case 1:
return monoRenderer;
case 2:
return inconsolataRenderer;
case 0:
default:
return proportionalRenderer;
}
}
int widthText(float scale, int mono, char const* string) {
return textRenderer(mono)->computeExtent(string).x; // computeWidth(string) * (scale / 0.10);
}
void renderCollisionOverlay(int width, int height, float magnitude, float red, float blue, float green) {
const float MIN_VISIBLE_COLLISION = 0.01f;
if (magnitude > MIN_VISIBLE_COLLISION) {

View file

@ -22,7 +22,6 @@ float randFloat();
const glm::vec3 randVector();
void renderWorldBox(gpu::Batch& batch);
int widthText(float scale, int mono, char const* string);
void renderCollisionOverlay(int width, int height, float magnitude, float red = 0, float blue = 0, float green = 0);

View file

@ -281,7 +281,7 @@ enum TextRendererType {
static TextRenderer3D* textRenderer(TextRendererType type) {
static TextRenderer3D* chatRenderer = TextRenderer3D::getInstance(SANS_FONT_FAMILY, -1,
false, TextRenderer3D::SHADOW_EFFECT);
false, SHADOW_EFFECT);
static TextRenderer3D* displayNameRenderer = TextRenderer3D::getInstance(SANS_FONT_FAMILY);
switch(type) {

View file

@ -19,6 +19,7 @@
#include <QtCore/QTimer>
#include <gpu/GPUConfig.h>
#include <AccountManager.h>
#include <AddressManager.h>
#include <AnimationHandle.h>
@ -31,7 +32,7 @@
#include <PerfStat.h>
#include <ShapeCollider.h>
#include <SharedUtil.h>
#include <TextRenderer.h>
#include <TextRenderer3D.h>
#include <UserActivityLogger.h>
#include "devices/Faceshift.h"

View file

@ -55,15 +55,15 @@ void ImageOverlay::render(RenderArgs* args) {
_isLoaded = true;
_texture = DependencyManager::get<TextureCache>()->getTexture(_imageURL);
}
// If we are not visible or loaded, return. If we are trying to render an
// image but the texture hasn't loaded, return.
if (!_visible || !_isLoaded || (_renderImage && !_texture->isLoaded())) {
return;
}
auto geometryCache = DependencyManager::get<GeometryCache>();
gpu::Batch& batch = *args->_batch;
geometryCache->useSimpleDrawPipeline(batch);
if (_renderImage) {
batch.setResourceTexture(0, _texture->getGPUTexture());
} else {

View file

@ -10,15 +10,72 @@
// include this before QGLWidget, which includes an earlier version of OpenGL
#include "InterfaceConfig.h"
#include "Application.h"
#include "TextOverlay.h"
#include "OffscreenUi.h"
#include "text/FontFamilies.h"
#include <gpu/GLBackend.h>
#include <DependencyManager.h>
#include <GeometryCache.h>
#include <TextureCache.h>
#include <GLMHelpers.h>
#include <RegisteredMetaTypes.h>
#include <SharedUtil.h>
#include <TextRenderer.h>
#include <ViewFrustum.h>
#include <QQuickItem>
#define TEXT_OVERLAY_PROPERTY(type, name, initialValue) \
Q_PROPERTY(type name READ name WRITE set##name NOTIFY name##Changed) \
public: \
type name() { return _##name; }; \
void set##name(const type& name) { \
if (name != _##name) { \
_##name = name; \
emit name##Changed(); \
} \
} \
private: \
type _##name{ initialValue };
class TextOverlayElement : public QQuickItem {
Q_OBJECT
HIFI_QML_DECL
private:
TEXT_OVERLAY_PROPERTY(QString, text, "")
TEXT_OVERLAY_PROPERTY(QString, fontFamily, SANS_FONT_FAMILY)
TEXT_OVERLAY_PROPERTY(QString, textColor, "#ffffffff")
TEXT_OVERLAY_PROPERTY(QString, backgroundColor, "#B2000000")
TEXT_OVERLAY_PROPERTY(qreal, fontSize, 18)
TEXT_OVERLAY_PROPERTY(qreal, lineHeight, 18)
TEXT_OVERLAY_PROPERTY(qreal, leftMargin, 0)
TEXT_OVERLAY_PROPERTY(qreal, topMargin, 0)
public:
TextOverlayElement(QQuickItem* parent = nullptr) : QQuickItem(parent) {
}
signals:
void textChanged();
void fontFamilyChanged();
void fontSizeChanged();
void lineHeightChanged();
void leftMarginChanged();
void topMarginChanged();
void textColorChanged();
void backgroundColorChanged();
};
HIFI_QML_DEF(TextOverlayElement)
QString toQmlColor(const glm::vec4& v) {
QString templat("#%1%2%3%4");
return templat.
arg((int)(v.a * 255), 2, 16, QChar('0')).
arg((int)(v.r * 255), 2, 16, QChar('0')).
arg((int)(v.g * 255), 2, 16, QChar('0')).
arg((int)(v.b * 255), 2, 16, QChar('0'));
}
TextOverlay::TextOverlay() :
_backgroundColor(DEFAULT_BACKGROUND_COLOR),
@ -27,7 +84,20 @@ TextOverlay::TextOverlay() :
_topMargin(DEFAULT_MARGIN),
_fontSize(DEFAULT_FONTSIZE)
{
_textRenderer = TextRenderer::getInstance(SANS_FONT_FAMILY, _fontSize, DEFAULT_FONT_WEIGHT);
qApp->postLambdaEvent([=] {
static std::once_flag once;
std::call_once(once, [] {
TextOverlayElement::registerType();
});
auto offscreenUi = DependencyManager::get<OffscreenUi>();
TextOverlayElement::show([=](QQmlContext* context, QObject* object) {
_qmlElement = static_cast<TextOverlayElement*>(object);
});
});
while (!_qmlElement) {
QThread::msleep(1);
}
}
TextOverlay::TextOverlay(const TextOverlay* textOverlay) :
@ -39,11 +109,21 @@ TextOverlay::TextOverlay(const TextOverlay* textOverlay) :
_topMargin(textOverlay->_topMargin),
_fontSize(textOverlay->_fontSize)
{
_textRenderer = TextRenderer::getInstance(SANS_FONT_FAMILY, _fontSize, DEFAULT_FONT_WEIGHT);
qApp->postLambdaEvent([=] {
auto offscreenUi = DependencyManager::get<OffscreenUi>();
TextOverlayElement::show([this](QQmlContext* context, QObject* object) {
_qmlElement = static_cast<TextOverlayElement*>(object);
});
});
while (!_qmlElement) {
QThread::sleep(1);
}
}
TextOverlay::~TextOverlay() {
delete _textRenderer;
if (_qmlElement) {
_qmlElement->deleteLater();
}
}
xColor TextOverlay::getBackgroundColor() {
@ -65,45 +145,34 @@ xColor TextOverlay::getBackgroundColor() {
return result;
}
void TextOverlay::render(RenderArgs* args) {
if (!_visible) {
return; // do nothing if we're not visible
if (_visible != _qmlElement->isVisible()) {
_qmlElement->setVisible(_visible);
}
float pulseLevel = updatePulse();
static float _oldPulseLevel = 0.0f;
if (pulseLevel != _oldPulseLevel) {
const float MAX_COLOR = 255.0f;
xColor backgroundColor = getBackgroundColor();
glm::vec4 quadColor(backgroundColor.red / MAX_COLOR, backgroundColor.green / MAX_COLOR, backgroundColor.blue / MAX_COLOR,
getBackgroundAlpha());
int left = _bounds.left();
int right = _bounds.right() + 1;
int top = _bounds.top();
int bottom = _bounds.bottom() + 1;
glm::vec2 topLeft(left, top);
glm::vec2 bottomRight(right, bottom);
glBindTexture(GL_TEXTURE_2D, 0);
DependencyManager::get<GeometryCache>()->renderQuad(topLeft, bottomRight, quadColor);
const int leftAdjust = -1; // required to make text render relative to left edge of bounds
const int topAdjust = -2; // required to make text render relative to top edge of bounds
int x = _bounds.left() + _leftMargin + leftAdjust;
int y = _bounds.top() + _topMargin + topAdjust;
float alpha = getAlpha();
glm::vec4 textColor = {_color.red / MAX_COLOR, _color.green / MAX_COLOR, _color.blue / MAX_COLOR, alpha };
_textRenderer->draw(x, y, _text, textColor);
}
}
void TextOverlay::setProperties(const QScriptValue& properties) {
Overlay2D::setProperties(properties);
_qmlElement->setX(_bounds.left());
_qmlElement->setY(_bounds.top());
_qmlElement->setWidth(_bounds.width());
_qmlElement->setHeight(_bounds.height());
_qmlElement->settextColor(toQmlColor(vec4(toGlm(_color), _alpha)));
QScriptValue font = properties.property("font");
if (font.isObject()) {
if (font.property("size").isValid()) {
setFontSize(font.property("size").toInt32());
}
QFont font(_qmlElement->fontFamily());
font.setPixelSize(_qmlElement->fontSize());
QFontMetrics fm(font);
_qmlElement->setlineHeight(fm.lineSpacing() * 1.2);
}
QScriptValue text = properties.property("text");
@ -126,6 +195,7 @@ void TextOverlay::setProperties(const QScriptValue& properties) {
if (properties.property("backgroundAlpha").isValid()) {
_backgroundAlpha = properties.property("backgroundAlpha").toVariant().toFloat();
}
_qmlElement->setbackgroundColor(toQmlColor(vec4(toGlm(_backgroundColor), _backgroundAlpha)));
if (properties.property("leftMargin").isValid()) {
setLeftMargin(properties.property("leftMargin").toVariant().toInt());
@ -166,15 +236,37 @@ QScriptValue TextOverlay::getProperty(const QString& property) {
}
QSizeF TextOverlay::textSize(const QString& text) const {
auto extents = _textRenderer->computeExtent(text);
return QSizeF(extents.x, extents.y);
int lines = 1;
foreach(QChar c, text) {
if (c == QChar('\n')) {
++lines;
}
}
QFont font(_qmlElement->fontFamily());
font.setPixelSize(_qmlElement->fontSize());
QFontMetrics fm(font);
QSizeF result = QSizeF(fm.width(text), _qmlElement->lineHeight() * lines);
return result;
}
void TextOverlay::setFontSize(int fontSize) {
_fontSize = fontSize;
auto oldTextRenderer = _textRenderer;
_textRenderer = TextRenderer::getInstance(SANS_FONT_FAMILY, _fontSize, DEFAULT_FONT_WEIGHT);
delete oldTextRenderer;
_qmlElement->setfontSize(fontSize);
}
void TextOverlay::setText(const QString& text) {
_text = text;
_qmlElement->settext(text);
}
void TextOverlay::setLeftMargin(int margin) {
_leftMargin = margin;
_qmlElement->setleftMargin(margin);
}
void TextOverlay::setTopMargin(int margin) {
_topMargin = margin;
_qmlElement->settopMargin(margin);
}
#include "TextOverlay.moc"

View file

@ -26,7 +26,7 @@ const int DEFAULT_MARGIN = 10;
const int DEFAULT_FONTSIZE = 12;
const int DEFAULT_FONT_WEIGHT = 50;
class TextRenderer;
class TextOverlayElement;
class TextOverlay : public Overlay2D {
Q_OBJECT
@ -45,9 +45,9 @@ public:
float getBackgroundAlpha() const { return _backgroundAlpha; }
// setters
void setText(const QString& text) { _text = text; }
void setLeftMargin(int margin) { _leftMargin = margin; }
void setTopMargin(int margin) { _topMargin = margin; }
void setText(const QString& text);
void setLeftMargin(int margin);
void setTopMargin(int margin);
void setFontSize(int fontSize);
virtual void setProperties(const QScriptValue& properties);
@ -57,9 +57,7 @@ public:
QSizeF textSize(const QString& text) const; // Pixels
private:
TextRenderer* _textRenderer = nullptr;
TextOverlayElement* _qmlElement{ nullptr };
QString _text;
xColor _backgroundColor;
float _backgroundAlpha;

View file

@ -17,7 +17,6 @@
#include <DeferredLightingEffect.h>
#include <GeometryCache.h>
#include <PerfStat.h>
#include <TextRenderer.h>
#include <OffscreenQmlSurface.h>
#include <AbstractViewStateInterface.h>
#include <GLMHelpers.h>

View file

@ -29,7 +29,6 @@ BoxEntityItem::BoxEntityItem(const EntityItemID& entityItemID, const EntityItemP
EntityItem(entityItemID)
{
_type = EntityTypes::Box;
_created = properties.getCreated();
setProperties(properties);
}

View file

@ -45,9 +45,7 @@ EntityItem::EntityItem(const EntityItemID& entityItemID) :
_lastEditedFromRemoteInRemoteTime(0),
_created(UNKNOWN_CREATED_TIME),
_changedOnServer(0),
_transform(ENTITY_ITEM_DEFAULT_ROTATION,
ENTITY_ITEM_DEFAULT_DIMENSIONS,
ENTITY_ITEM_DEFAULT_POSITION),
_transform(),
_glowLevel(ENTITY_ITEM_DEFAULT_GLOW_LEVEL),
_localRenderAlpha(ENTITY_ITEM_DEFAULT_LOCAL_RENDER_ALPHA),
_density(ENTITY_ITEM_DEFAULT_DENSITY),
@ -80,16 +78,15 @@ EntityItem::EntityItem(const EntityItemID& entityItemID) :
_physicsInfo(nullptr),
_simulated(false)
{
// explicitly set transform parts to set dirty flags used by batch rendering
_transform.setTranslation(ENTITY_ITEM_DEFAULT_POSITION);
_transform.setRotation(ENTITY_ITEM_DEFAULT_ROTATION);
_transform.setScale(ENTITY_ITEM_DEFAULT_DIMENSIONS);
quint64 now = usecTimestampNow();
_lastSimulated = now;
_lastUpdated = now;
}
EntityItem::EntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties) : EntityItem(entityItemID)
{
setProperties(properties);
}
EntityItem::~EntityItem() {
// clear out any left-over actions
EntityTree* entityTree = _element ? _element->getTree() : nullptr;

View file

@ -119,7 +119,6 @@ public:
DONT_ALLOW_INSTANTIATION // This class can not be instantiated directly
EntityItem(const EntityItemID& entityItemID);
EntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties);
virtual ~EntityItem();
// ID and EntityItemID related methods

View file

@ -772,6 +772,9 @@ int EntityTreeElement::readElementDataFromBuffer(const unsigned char* data, int
entityItemID = entityItem->getEntityItemID();
_myTree->setContainingElement(entityItemID, this);
_myTree->postAddEntity(entityItem);
if (entityItem->getCreated() == UNKNOWN_CREATED_TIME) {
entityItem->recordCreationTime();
}
}
}
// Move the buffer forward to read more entities

View file

@ -29,7 +29,7 @@ EntityItemPointer LightEntityItem::factory(const EntityItemID& entityID, const E
// our non-pure virtual subclass for now...
LightEntityItem::LightEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties) :
EntityItem(entityItemID, properties)
EntityItem(entityItemID)
{
_type = EntityTypes::Light;

View file

@ -38,7 +38,6 @@ LineEntityItem::LineEntityItem(const EntityItemID& entityItemID, const EntityIte
_points(QVector<glm::vec3>(0))
{
_type = EntityTypes::Line;
_created = properties.getCreated();
setProperties(properties);

View file

@ -33,7 +33,7 @@ EntityItemPointer ModelEntityItem::factory(const EntityItemID& entityID, const E
}
ModelEntityItem::ModelEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties) :
EntityItem(entityItemID, properties)
EntityItem(entityItemID)
{
_type = EntityTypes::Model;
setProperties(properties);

View file

@ -61,7 +61,7 @@ EntityItemPointer ParticleEffectEntityItem::factory(const EntityItemID& entityID
// our non-pure virtual subclass for now...
ParticleEffectEntityItem::ParticleEffectEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties) :
EntityItem(entityItemID, properties),
EntityItem(entityItemID),
_maxParticles(DEFAULT_MAX_PARTICLES),
_lifespan(DEFAULT_LIFESPAN),
_emitRate(DEFAULT_EMIT_RATE),

View file

@ -57,7 +57,6 @@ PolyVoxEntityItem::PolyVoxEntityItem(const EntityItemID& entityItemID, const Ent
_voxelSurfaceStyle(PolyVoxEntityItem::DEFAULT_VOXEL_SURFACE_STYLE)
{
_type = EntityTypes::PolyVox;
_created = properties.getCreated();
setProperties(properties);
}

View file

@ -30,7 +30,7 @@ EntityItemPointer SphereEntityItem::factory(const EntityItemID& entityID, const
// our non-pure virtual subclass for now...
SphereEntityItem::SphereEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties) :
EntityItem(entityItemID, properties)
EntityItem(entityItemID)
{
_type = EntityTypes::Sphere;
setProperties(properties);

View file

@ -37,7 +37,6 @@ TextEntityItem::TextEntityItem(const EntityItemID& entityItemID, const EntityIte
EntityItem(entityItemID)
{
_type = EntityTypes::Text;
_created = properties.getCreated();
setProperties(properties);
}

View file

@ -30,7 +30,6 @@ WebEntityItem::WebEntityItem(const EntityItemID& entityItemID, const EntityItemP
EntityItem(entityItemID)
{
_type = EntityTypes::Web;
_created = properties.getCreated();
setProperties(properties);
}
@ -149,4 +148,4 @@ void WebEntityItem::setSourceUrl(const QString& value) {
}
}
const QString& WebEntityItem::getSourceUrl() const { return _sourceUrl; }
const QString& WebEntityItem::getSourceUrl() const { return _sourceUrl; }

View file

@ -37,7 +37,6 @@ ZoneEntityItem::ZoneEntityItem(const EntityItemID& entityItemID, const EntityIte
EntityItem(entityItemID)
{
_type = EntityTypes::Zone;
_created = properties.getCreated();
_keyLightColor[RED_INDEX] = DEFAULT_KEYLIGHT_COLOR.red;
_keyLightColor[GREEN_INDEX] = DEFAULT_KEYLIGHT_COLOR.green;

View file

@ -1,576 +0,0 @@
//
// TextRenderer.cpp
// interface/src/ui
//
// Created by Andrzej Kapolka on 4/24/13.
// Copyright 2013 High Fidelity, Inc.
//
// 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/GPUConfig.h>
#include <QApplication>
#include <QtDebug>
#include <QString>
#include <QStringList>
#include <QBuffer>
#include <QFile>
#if defined(__GNUC__) && !defined(__clang__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdouble-promotion"
#endif
// FIXME, decouple from the GL headers
#include <QOpenGLShaderProgram>
#include <QOpenGLTexture>
#include <QOpenGLVertexArrayObject>
#include <QOpenGLBuffer>
#if defined(__GNUC__) && !defined(__clang__)
#pragma GCC diagnostic pop
#endif
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#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"
// Helper functions for reading binary data from an IO device
template<class T>
void readStream(QIODevice & in, T & t) {
in.read((char*) &t, sizeof(t));
}
template<typename T, size_t N>
void readStream(QIODevice & in, T (&t)[N]) {
in.read((char*) t, N);
}
template<class T, size_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;
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;
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;
}
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<QChar, Glyph> _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;
bool _initialized;
};
static QHash<QString, Font*> LOADED_FONTS;
Font* loadFont(QIODevice& fontFile) {
Font* result = new Font();
result->read(fontFile);
return result;
}
Font* loadFont(const QString& family) {
if (!LOADED_FONTS.contains(family)) {
const QString SDFF_COURIER_PRIME_FILENAME = ":/CourierPrime.sdff";
const QString SDFF_INCONSOLATA_MEDIUM_FILENAME = ":/InconsolataMedium.sdff";
const QString SDFF_ROBOTO_FILENAME = ":/Roboto.sdff";
const QString SDFF_TIMELESS_FILENAME = ":/Timeless.sdff";
QString loadFilename;
if (family == MONO_FONT_FAMILY) {
loadFilename = SDFF_COURIER_PRIME_FILENAME;
} else if (family == INCONSOLATA_FONT_FAMILY) {
loadFilename = SDFF_INCONSOLATA_MEDIUM_FILENAME;
} else if (family == SANS_FONT_FAMILY) {
loadFilename = SDFF_ROBOTO_FILENAME;
} else {
if (!LOADED_FONTS.contains(SERIF_FONT_FAMILY)) {
loadFilename = SDFF_TIMELESS_FILENAME;
} else {
LOADED_FONTS[family] = LOADED_FONTS[SERIF_FONT_FAMILY];
}
}
if (!loadFilename.isEmpty()) {
QFile fontFile(loadFilename);
fontFile.open(QIODevice::ReadOnly);
qCDebug(renderutils) << "Loaded font" << loadFilename << "from Qt Resource System.";
LOADED_FONTS[family] = loadFont(fontFile);
}
}
return LOADED_FONTS[family];
}
Font::Font() : _initialized(false) {
static bool fontResourceInitComplete = false;
if (!fontResourceInitComplete) {
Q_INIT_RESOURCE(fonts);
fontResourceInitComplete = true;
}
}
// NERD RAGE: why doesn't QHash have a 'const T & operator[] const' member
const Glyph & Font::getGlyph(const QChar & c) const {
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);
}
}
// read font data
readStream(in, _leading);
readStream(in, _ascent);
readStream(in, _descent);
readStream(in, _spaceWidth);
_fontSize = _ascent + _descent;
_rowHeight = _fontSize + _descent;
// Read character count
uint16_t count;
readStream(in, count);
// read metrics data for each character
QVector<Glyph> glyphs(count);
// std::for_each instead of Qt foreach because we need non-const references
std::for_each(glyphs.begin(), glyphs.end(), [&](Glyph & g) {
g.read(in);
});
// read 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;
};
}
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<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;
}
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;
}
}
}
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();
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(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");
}
if (1 != _effectThickness) {
qWarning() << "Effect thickness not current supported";
}
if (NO_EFFECT != _effectType && OUTLINE_EFFECT != _effectType) {
qWarning() << "Effect thickness not current supported";
}
}
TextRenderer::~TextRenderer() {
}
glm::vec2 TextRenderer::computeExtent(const QString & str) const {
float scale = (_pointSize / DEFAULT_POINT_SIZE) * 0.25f;
if (_font) {
return _font->computeExtent(str) * scale;
}
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) {
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;
}

View file

@ -1,89 +0,0 @@
//
// TextRenderer.h
// interface/src/ui
//
// Created by Andrzej Kapolka on 4/26/13.
// Copyright 2013 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_TextRenderer_h
#define hifi_TextRenderer_h
#include <gpu/GPUConfig.h>
#include <glm/glm.hpp>
#include <unordered_map>
#include <QColor>
#include <QFont>
#include <QFontMetrics>
#include <QHash>
#include <QImage>
#include <QVector>
#include <gpu/Resource.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
#define SANS_FONT_FAMILY "Helvetica"
// the standard sans serif font family
#define SERIF_FONT_FAMILY "Timeless"
// the standard mono font family
#define MONO_FONT_FAMILY "Courier"
// the Inconsolata font family
#ifdef Q_OS_WIN
#define INCONSOLATA_FONT_FAMILY "Fixedsys"
#define INCONSOLATA_FONT_WEIGHT QFont::Normal
#else
#define INCONSOLATA_FONT_FAMILY "Inconsolata"
#define INCONSOLATA_FONT_WEIGHT QFont::Bold
#endif
class Font;
// TextRenderer is actually a fairly thin wrapper around a Font class
// defined in the cpp file.
class TextRenderer {
public:
enum EffectType { NO_EFFECT, SHADOW_EFFECT, OUTLINE_EFFECT };
static TextRenderer* getInstance(const char* family, float pointSize = -1, int weight = -1, bool italic = false,
EffectType effect = NO_EFFECT, int effectThickness = 1, const QColor& color = QColor(255, 255, 255));
~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));
private:
TextRenderer(const char* family, float pointSize = -1, int weight = -1, bool italic = false,
EffectType effect = NO_EFFECT, int effectThickness = 1, const QColor& color = QColor(255, 255, 255));
// the type of effect to apply
const EffectType _effectType;
// the thickness of the effect
const int _effectThickness;
const float _pointSize;
// text color
const QColor _color;
Font * _font;
};
#endif // hifi_TextRenderer_h

View file

@ -10,6 +10,7 @@
//
#include "TextRenderer3D.h"
#include <StreamHelpers.h>
#include <gpu/GPUConfig.h>
#include <gpu/GLBackend.h>
@ -20,6 +21,8 @@
#include <QStringList>
#include <QFile>
#include "text/Font.h"
#include "GLMHelpers.h"
#include "MatrixStack.h"
#include "RenderUtilsLogging.h"
@ -30,421 +33,22 @@
#include "GeometryCache.h"
#include "DeferredLightingEffect.h"
// Helper functions for reading binary data from an IO device
template<class T>
void readStream(QIODevice& in, T& t) {
in.read((char*) &t, sizeof(t));
const float TextRenderer3D::DEFAULT_POINT_SIZE = 12;
TextRenderer3D* TextRenderer3D::getInstance(const char* family, float pointSize,
bool bold, bool italic, EffectType effect, int effectThickness) {
return new TextRenderer3D(family, pointSize, false, italic, effect, effectThickness);
}
template<typename T, size_t N>
void readStream(QIODevice& in, T (&t)[N]) {
in.read((char*) t, N);
}
template<class T, size_t N>
void fillBuffer(QBuffer& buffer, T (&t)[N]) {
buffer.setData((const char*) t, N);
}
// stores the font metrics for a single character
struct Glyph3D {
QChar c;
glm::vec2 texOffset;
glm::vec2 texSize;
glm::vec2 size;
glm::vec2 offset;
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); }
void read(QIODevice& in);
};
void Glyph3D::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;
}
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 Glyph3D& glyph, const glm::vec2& offset) :
QuadBuilder(offset + glm::vec2(glyph.offset.x, glyph.offset.y - glyph.size.y), glyph.size,
glyph.texOffset, glyph.texSize) {}
};
class Font3D {
public:
Font3D();
void read(QIODevice& path);
glm::vec2 computeExtent(const QString& str) const;
float getFontSize() const { return _fontSize; }
// Render string to batch
void drawString(gpu::Batch& batch, float x, float y, const QString& str,
const glm::vec4* color, TextRenderer3D::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 Glyph3D& 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<QChar, Glyph3D> _glyphs;
// Font characteristics
QString _family;
float _fontSize = 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;
};
static QHash<QString, Font3D*> LOADED_FONTS;
Font3D* loadFont3D(QIODevice& fontFile) {
Font3D* result = new Font3D();
result->read(fontFile);
return result;
}
Font3D* loadFont3D(const QString& family) {
if (!LOADED_FONTS.contains(family)) {
const QString SDFF_COURIER_PRIME_FILENAME = ":/CourierPrime.sdff";
const QString SDFF_INCONSOLATA_MEDIUM_FILENAME = ":/InconsolataMedium.sdff";
const QString SDFF_ROBOTO_FILENAME = ":/Roboto.sdff";
const QString SDFF_TIMELESS_FILENAME = ":/Timeless.sdff";
QString loadFilename;
if (family == MONO_FONT_FAMILY) {
loadFilename = SDFF_COURIER_PRIME_FILENAME;
} else if (family == INCONSOLATA_FONT_FAMILY) {
loadFilename = SDFF_INCONSOLATA_MEDIUM_FILENAME;
} else if (family == SANS_FONT_FAMILY) {
loadFilename = SDFF_ROBOTO_FILENAME;
} else {
if (!LOADED_FONTS.contains(SERIF_FONT_FAMILY)) {
loadFilename = SDFF_TIMELESS_FILENAME;
} else {
LOADED_FONTS[family] = LOADED_FONTS[SERIF_FONT_FAMILY];
}
}
if (!loadFilename.isEmpty()) {
QFile fontFile(loadFilename);
fontFile.open(QIODevice::ReadOnly);
qCDebug(renderutils) << "Loaded font" << loadFilename << "from Qt Resource System.";
LOADED_FONTS[family] = loadFont3D(fontFile);
}
}
return LOADED_FONTS[family];
}
Font3D::Font3D() {
static bool fontResourceInitComplete = false;
if (!fontResourceInitComplete) {
Q_INIT_RESOURCE(fonts);
fontResourceInitComplete = true;
}
}
// NERD RAGE: why doesn't QHash have a 'const T & operator[] const' member
const Glyph3D& Font3D::getGlyph(const QChar& c) const {
if (!_glyphs.contains(c)) {
return _glyphs[QChar('?')];
}
return _glyphs[c];
}
QStringList Font3D::splitLines(const QString& str) const {
return str.split('\n');
}
QStringList Font3D::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 Font3D::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 Font3D::computeExtent(const QString& str) const {
glm::vec2 extent = glm::vec2(0.0f, 0.0f);
QStringList lines{ splitLines(str) };
if (!lines.empty()) {
for(const auto& line : lines) {
glm::vec2 tokenExtent = computeTokenExtent(line);
extent.x = std::max(tokenExtent.x, extent.x);
}
extent.y = lines.count() * _fontSize;
}
return extent;
}
void Font3D::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);
}
}
// read font data
readStream(in, _leading);
readStream(in, _ascent);
readStream(in, _descent);
readStream(in, _spaceWidth);
_fontSize = _ascent + _descent;
// Read character count
uint16_t count;
readStream(in, count);
// read metrics data for each character
QVector<Glyph3D> glyphs(count);
// std::for_each instead of Qt foreach because we need non-const references
std::for_each(glyphs.begin(), glyphs.end(), [&](Glyph3D& g) {
g.read(in);
});
// read image data
QImage image;
if (!image.loadFromData(in.readAll(), "PNG")) {
qFatal("Failed to read SDFF image");
}
_glyphs.clear();
glm::vec2 imageSize = toGlm(image.size());
foreach(Glyph3D g, glyphs) {
// Adjust the pixel texture coordinates into UV coordinates,
g.texSize /= imageSize;
g.texOffset /= imageSize;
// store in the character to glyph hash
_glyphs[g.c] = g;
};
image = image.convertToFormat(QImage::Format_RGBA8888);
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_POINT_MAG_LINEAR)));
_texture->assignStoredMip(0, formatMip, image.byteCount(), image.constBits());
_texture->autoGenerateMips(-1);
}
void Font3D::setupGPU() {
if (!_initialized) {
_initialized = true;
// Setup render pipeline
auto vertexShader = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(sdf_text3D_vert)));
auto pixelShader = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(sdf_text3D_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");
gpu::StatePointer state = gpu::StatePointer(new gpu::State());
state->setCullMode(gpu::State::CULL_BACK);
state->setDepthTest(true, true, gpu::LESS_EQUAL);
state->setBlendFunction(true,
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(TextureVertex) == 2 * 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);
}
}
void Font3D::drawString(gpu::Batch& batch, float x, float y, const QString& str, const glm::vec4* color,
TextRenderer3D::EffectType effectType, const glm::vec2& bounds) {
if (str == "") {
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 > 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(x, advance.y - _leading);
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
break;
}
}
if ((bounds.y != -1) && (advance.y - _fontSize < -y - 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, _ascent));
_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;
}
}
}
setupGPU();
batch.setPipeline(_pipeline);
batch.setResourceTexture(_fontLoc, _texture);
batch._glUniform1i(_outlineLoc, (effectType == TextRenderer3D::OUTLINE_EFFECT));
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);
}
TextRenderer3D* TextRenderer3D::getInstance(const char* family,
int weight, bool italic, EffectType effect, int effectThickness) {
return new TextRenderer3D(family, weight, italic, effect, effectThickness);
}
TextRenderer3D::TextRenderer3D(const char* family, int weight, bool italic,
EffectType effect, int effectThickness) :
TextRenderer3D::TextRenderer3D(const char* family, float pointSize, int weight, bool italic,
EffectType effect, int effectThickness) :
_pointSize(pointSize),
_effectType(effect),
_effectThickness(effectThickness),
_font(loadFont3D(family)) {
_font(Font::load(family)) {
if (!_font) {
qWarning() << "Unable to load font with family " << family;
_font = loadFont3D("Courier");
_font = Font::load("Courier");
}
if (1 != _effectThickness) {
qWarning() << "Effect thickness not currently supported";

View file

@ -15,37 +15,25 @@
#include <glm/glm.hpp>
#include <QColor>
// the standard sans serif font family
#define SANS_FONT_FAMILY "Helvetica"
// the standard sans serif font family
#define SERIF_FONT_FAMILY "Timeless"
// the standard mono font family
#define MONO_FONT_FAMILY "Courier"
// the Inconsolata font family
#ifdef Q_OS_WIN
#define INCONSOLATA_FONT_FAMILY "Fixedsys"
#define INCONSOLATA_FONT_WEIGHT QFont::Normal
#else
#define INCONSOLATA_FONT_FAMILY "Inconsolata"
#define INCONSOLATA_FONT_WEIGHT QFont::Bold
#endif
namespace gpu {
class Batch;
}
class Font3D;
class Font;
#include "text/EffectType.h"
#include "text/FontFamilies.h"
// TextRenderer3D is actually a fairly thin wrapper around a Font class
// defined in the cpp file.
class TextRenderer3D {
public:
enum EffectType { NO_EFFECT, SHADOW_EFFECT, OUTLINE_EFFECT };
static const float DEFAULT_POINT_SIZE;
static TextRenderer3D* getInstance(const char* family, float pointSize = DEFAULT_POINT_SIZE,
bool bold = false, bool italic = false, EffectType effect = NO_EFFECT, int effectThickness = 1);
static TextRenderer3D* getInstance(const char* family, int weight = -1, bool italic = false,
EffectType effect = NO_EFFECT, int effectThickness = 1);
~TextRenderer3D();
glm::vec2 computeExtent(const QString& str) const;
@ -55,7 +43,7 @@ public:
const glm::vec2& bounds = glm::vec2(-1.0f));
private:
TextRenderer3D(const char* family, int weight = -1, bool italic = false,
TextRenderer3D(const char* family, float pointSize, int weight = -1, bool italic = false,
EffectType effect = NO_EFFECT, int effectThickness = 1);
// the type of effect to apply
@ -66,8 +54,9 @@ private:
// text color
glm::vec4 _color;
float _pointSize{ DEFAULT_POINT_SIZE };
Font3D* _font;
Font* _font;
};

View file

@ -1,49 +0,0 @@
<@include gpu/Config.slh@>
<$VERSION_HEADER$>
// Generated on <$_SCRIBE_DATE$>
// sdf_text.frag
// fragment shader
//
// Created by Bradley Austin Davis on 2015-02-04
// Based on fragment shader code from
// https://github.com/paulhoux/Cinder-Samples/blob/master/TextRendering/include/text/Text.cpp
// 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;
varying vec2 vTexCoord;
const float gamma = 2.6;
const float smoothing = 100.0;
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;
}
}
// 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);
}

View file

@ -1,24 +0,0 @@
<@include gpu/Config.slh@>
<$VERSION_HEADER$>
// Generated on <$_SCRIBE_DATE$>
// sdf_text.vert
// vertex shader
//
// Created by Brad Davis on 10/14/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 mat4 Projection;
uniform mat4 ModelView;
attribute vec2 Position;
attribute vec2 TexCoord;
varying vec2 vTexCoord;
void main() {
vTexCoord = TexCoord;
gl_Position = Projection * ModelView * vec4(Position, 0.0, 1.0);
}

View file

@ -0,0 +1,15 @@
//
// Created by Bradley Austin Davis on 2015/07/16
// Copyright 2013 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#pragma once
#ifndef hifi_EffectType_h
#define hifi_EffectType_h
enum EffectType { NO_EFFECT, SHADOW_EFFECT, OUTLINE_EFFECT };
#endif

View file

@ -0,0 +1,322 @@
#include "Font.h"
#include <QFile>
#include <QImage>
#include <StreamHelpers.h>
#include "sdf_text3D_vert.h"
#include "sdf_text3D_frag.h"
#include "../RenderUtilsLogging.h"
#include "FontFamilies.h"
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 + glm::vec2(glyph.offset.x, glyph.offset.y - glyph.size.y), glyph.size,
glyph.texOffset, glyph.texSize) {}
};
static QHash<QString, Font*> LOADED_FONTS;
Font* Font::load(QIODevice& fontFile) {
Font* result = new Font();
result->read(fontFile);
return result;
}
Font* Font::load(const QString& family) {
if (!LOADED_FONTS.contains(family)) {
static const QString SDFF_COURIER_PRIME_FILENAME{ ":/CourierPrime.sdff" };
static const QString SDFF_INCONSOLATA_MEDIUM_FILENAME{ ":/InconsolataMedium.sdff" };
static const QString SDFF_ROBOTO_FILENAME{ ":/Roboto.sdff" };
static const QString SDFF_TIMELESS_FILENAME{ ":/Timeless.sdff" };
QString loadFilename;
if (family == MONO_FONT_FAMILY) {
loadFilename = SDFF_COURIER_PRIME_FILENAME;
} else if (family == INCONSOLATA_FONT_FAMILY) {
loadFilename = SDFF_INCONSOLATA_MEDIUM_FILENAME;
} else if (family == SANS_FONT_FAMILY) {
loadFilename = SDFF_ROBOTO_FILENAME;
} else {
if (!LOADED_FONTS.contains(SERIF_FONT_FAMILY)) {
loadFilename = SDFF_TIMELESS_FILENAME;
} else {
LOADED_FONTS[family] = LOADED_FONTS[SERIF_FONT_FAMILY];
}
}
if (!loadFilename.isEmpty()) {
QFile fontFile(loadFilename);
fontFile.open(QIODevice::ReadOnly);
qCDebug(renderutils) << "Loaded font" << loadFilename << "from Qt Resource System.";
LOADED_FONTS[family] = load(fontFile);
}
}
return LOADED_FONTS[family];
}
Font::Font() {
static bool fontResourceInitComplete = false;
if (!fontResourceInitComplete) {
Q_INIT_RESOURCE(fonts);
fontResourceInitComplete = true;
}
}
// NERD RAGE: why doesn't QHash have a 'const T & operator[] const' member
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 lines{ splitLines(str) };
if (!lines.empty()) {
for(const auto& line : lines) {
glm::vec2 tokenExtent = computeTokenExtent(line);
extent.x = std::max(tokenExtent.x, extent.x);
}
extent.y = lines.count() * _fontSize;
}
return extent;
}
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);
}
}
// read font data
readStream(in, _leading);
readStream(in, _ascent);
readStream(in, _descent);
readStream(in, _spaceWidth);
_fontSize = _ascent + _descent;
// Read character count
uint16_t count;
readStream(in, count);
// read metrics data for each character
QVector<Glyph> glyphs(count);
// std::for_each instead of Qt foreach because we need non-const references
std::for_each(glyphs.begin(), glyphs.end(), [&](Glyph& g) {
g.read(in);
});
// read image data
QImage image;
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,
g.texSize /= imageSize;
g.texOffset /= imageSize;
// store in the character to glyph hash
_glyphs[g.c] = g;
};
image = image.convertToFormat(QImage::Format_RGBA8888);
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_POINT_MAG_LINEAR)));
_texture->assignStoredMip(0, formatMip, image.byteCount(), image.constBits());
_texture->autoGenerateMips(-1);
}
void Font::setupGPU() {
if (!_initialized) {
_initialized = true;
// Setup render pipeline
{
auto vertexShader = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(sdf_text3D_vert)));
auto pixelShader = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(sdf_text3D_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");
gpu::StatePointer state = gpu::StatePointer(new gpu::State());
state->setCullMode(gpu::State::CULL_BACK);
state->setDepthTest(true, true, gpu::LESS_EQUAL);
state->setBlendFunction(true,
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(TextureVertex) == 2 * 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);
}
}
void Font::rebuildVertices(float x, float y, const QString& str, const glm::vec2& bounds) {
_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 > 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(x, advance.y - _leading);
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
break;
}
}
if ((bounds.y != -1) && (advance.y - _fontSize < -y - 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, _ascent));
_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;
}
}
}
void Font::drawString(gpu::Batch& batch, float x, float y, const QString& str, const glm::vec4* color,
EffectType effectType, const glm::vec2& bounds) {
if (str == "") {
return;
}
if (str != _lastStringRendered || bounds != _lastBounds) {
rebuildVertices(x, y, str, bounds);
}
setupGPU();
batch.setPipeline(_pipeline);
batch.setResourceTexture(_fontLoc, _texture);
batch._glUniform1i(_outlineLoc, (effectType == OUTLINE_EFFECT));
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);
}

View file

@ -0,0 +1,80 @@
//
// Created by Bradley Austin Davis on 2015/07/16
// Copyright 2013 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#pragma once
#ifndef hifi_Font_h
#define hifi_Font_h
#include "Glyph.h"
#include "EffectType.h"
#include <gpu/Batch.h>
#include <gpu/Pipeline.h>
class Font {
public:
Font();
void read(QIODevice& path);
glm::vec2 computeExtent(const QString& str) const;
float getFontSize() const { return _fontSize; }
// Render string to batch
void drawString(gpu::Batch& batch, float x, float y, const QString& str,
const glm::vec4* color, EffectType effectType,
const glm::vec2& bound);
static Font* load(QIODevice& fontFile);
static Font* load(const QString& family);
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 rebuildVertices(float x, float y, const QString& str, const glm::vec2& bounds);
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<QChar, Glyph> _glyphs;
// Font characteristics
QString _family;
float _fontSize = 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;
};
#endif

View file

@ -0,0 +1,31 @@
//
// Created by Bradley Austin Davis on 2015/07/16
// Copyright 2013 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#pragma once
#ifndef hifi_FontFamilies_h
#define hifi_FontFamilies_h
// the standard sans serif font family
#define SANS_FONT_FAMILY "Helvetica"
// the standard sans serif font family
#define SERIF_FONT_FAMILY "Timeless"
// the standard mono font family
#define MONO_FONT_FAMILY "Courier"
// the Inconsolata font family
#ifdef Q_OS_WIN
#define INCONSOLATA_FONT_FAMILY "Fixedsys"
#define INCONSOLATA_FONT_WEIGHT QFont::Normal
#else
#define INCONSOLATA_FONT_FAMILY "Inconsolata"
#define INCONSOLATA_FONT_WEIGHT QFont::Bold
#endif
#endif

View file

@ -0,0 +1,22 @@
#include "Glyph.h"
#include <StreamHelpers.h>
// We adjust bounds because offset is the bottom left corner of the font but the top left corner of a QRect
QRectF Glyph::bounds() const {
return glmToRect(offset, size).translated(0.0f, -size.y);
}
QRectF Glyph::textureBounds() const {
return glmToRect(texOffset, texSize);
}
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;
}

View file

@ -0,0 +1,38 @@
//
// Created by Bradley Austin Davis on 2015/07/16
// Copyright 2013 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#pragma once
#ifndef hifi_Glyph_h
#define hifi_Glyph_h
#include <stdint.h>
#include <QChar>
#include <QRect>
#include <QIODevice>
#include <GLMHelpers.h>
// stores the font metrics for a single character
struct Glyph {
QChar c;
vec2 texOffset;
vec2 texSize;
vec2 size;
vec2 offset;
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;
QRectF textureBounds() const;
void read(QIODevice& in);
};
#endif

View file

@ -0,0 +1,32 @@
//
// Created by Bradley Austin Davis 2015/07/16
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#pragma once
#ifndef hifi_StreamHelpers_h
#define hifi_StreamHelpers_h
#include <QIODevice>
#include <QBuffer>
// Helper functions for reading binary data from an IO device
template<class T>
inline void readStream(QIODevice& in, T& t) {
in.read((char*) &t, sizeof(t));
}
template<typename T, size_t N>
inline void readStream(QIODevice& in, T (&t)[N]) {
in.read((char*) t, N);
}
template<class T, size_t N>
inline void fillBuffer(QBuffer& buffer, T (&t)[N]) {
buffer.setData((const char*) t, N);
}
#endif

View file

@ -8,7 +8,7 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "TextRenderer.h"
#include <gpu/GPUConfig.h>
#include <QWindow>
#include <QFile>
@ -29,6 +29,7 @@
#include <unordered_map>
#include <memory>
#include <glm/glm.hpp>
#include <PathUtils.h>
#include <QDir>
@ -86,7 +87,7 @@ class QTestWindow : public QWindow {
QOpenGLContext* _context{ nullptr };
QSize _size;
TextRenderer* _textRenderer[4];
//TextRenderer* _textRenderer[4];
RateCounter fps;
protected:
@ -146,12 +147,12 @@ public:
glGetError();
#endif
_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);
@ -190,48 +191,6 @@ 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 } };
void QTestWindow::renderText() {
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0, _size.width(), _size.height(), 0, 1, -1);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
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);
for (int i = 0; i < 4; ++i) {
glm::vec2 bounds = _textRenderer[i]->computeExtent(str);
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();
const int testCount = 100;
for (int j = 0; j < testCount; ++j) {
// Draw backgrounds around where the text will appear
// Draw the text itself
_textRenderer[i]->draw(offsets[i].x, offsets[i].y, str.toLocal8Bit().constData(),
glm::vec4(COLORS[i], 1.0f));
}
}
}
void QTestWindow::draw() {
if (!isVisible()) {
@ -242,8 +201,6 @@ void QTestWindow::draw() {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glViewport(0, 0, _size.width() * devicePixelRatio(), _size.height() * devicePixelRatio());
renderText();
_context->swapBuffers(this);
glFinish();