Merge pull request #4986 from Atlante45/text-renderer

TEAM TEACHING - Text Entities render in Batch
This commit is contained in:
Brad Hefta-Gaub 2015-05-28 12:32:36 -07:00
commit 3d898c39d6
24 changed files with 744 additions and 104 deletions

View file

@ -40,13 +40,13 @@ void AudioToolBox::render(int x, int y, int padding, bool boxed) {
glEnable(GL_TEXTURE_2D);
if (!_micTexture) {
_micTexture = DependencyManager::get<TextureCache>()->getImageTexture(PathUtils::resourcesPath() + "images/mic.svg");
_micTexture = TextureCache::getImageTexture(PathUtils::resourcesPath() + "images/mic.svg");
}
if (!_muteTexture) {
_muteTexture = DependencyManager::get<TextureCache>()->getImageTexture(PathUtils::resourcesPath() + "images/mic-mute.svg");
_muteTexture = TextureCache::getImageTexture(PathUtils::resourcesPath() + "images/mic-mute.svg");
}
if (_boxTexture) {
_boxTexture = DependencyManager::get<TextureCache>()->getImageTexture(PathUtils::resourcesPath() + "images/audio-box.svg");
_boxTexture = TextureCache::getImageTexture(PathUtils::resourcesPath() + "images/audio-box.svg");
}
auto audioIO = DependencyManager::get<AudioClient>();

View file

@ -76,10 +76,10 @@ void CameraToolBox::render(int x, int y, bool boxed) {
glEnable(GL_TEXTURE_2D);
if (!_enabledTexture) {
_enabledTexture = DependencyManager::get<TextureCache>()->getImageTexture(PathUtils::resourcesPath() + "images/face.svg");
_enabledTexture = TextureCache::getImageTexture(PathUtils::resourcesPath() + "images/face.svg");
}
if (!_mutedTexture) {
_mutedTexture = DependencyManager::get<TextureCache>()->getImageTexture(PathUtils::resourcesPath() + "images/face-mute.svg");
_mutedTexture = TextureCache::getImageTexture(PathUtils::resourcesPath() + "images/face-mute.svg");
}
const int MUTE_ICON_SIZE = 24;

View file

@ -423,8 +423,8 @@ void ApplicationOverlay::displayOverlayTextureStereo(Camera& whichCamera, float
});
if (!_crosshairTexture) {
_crosshairTexture = DependencyManager::get<TextureCache>()->
getImageTexture(PathUtils::resourcesPath() + "images/sixense-reticle.png");
_crosshairTexture = TextureCache::getImageTexture(PathUtils::resourcesPath() +
"images/sixense-reticle.png");
}
//draw the mouse pointer
@ -564,8 +564,7 @@ bool ApplicationOverlay::calculateRayUICollisionPoint(const glm::vec3& position,
void ApplicationOverlay::renderPointers() {
//lazily load crosshair texture
if (_crosshairTexture == 0) {
_crosshairTexture = DependencyManager::get<TextureCache>()->
getImageTexture(PathUtils::resourcesPath() + "images/sixense-reticle.png");
_crosshairTexture = TextureCache::getImageTexture(PathUtils::resourcesPath() + "images/sixense-reticle.png");
}
glEnable(GL_TEXTURE_2D);

View file

@ -34,11 +34,10 @@ RearMirrorTools::RearMirrorTools(QRect& bounds) :
_windowed(false),
_fullScreen(false)
{
auto textureCache = DependencyManager::get<TextureCache>();
_closeTexture = textureCache->getImageTexture(PathUtils::resourcesPath() + "images/close.svg");
_closeTexture = TextureCache::getImageTexture(PathUtils::resourcesPath() + "images/close.svg");
_zoomHeadTexture = textureCache->getImageTexture(PathUtils::resourcesPath() + "images/plus.svg");
_zoomBodyTexture = textureCache->getImageTexture(PathUtils::resourcesPath() + "images/minus.svg");
_zoomHeadTexture = TextureCache::getImageTexture(PathUtils::resourcesPath() + "images/plus.svg");
_zoomBodyTexture = TextureCache::getImageTexture(PathUtils::resourcesPath() + "images/minus.svg");
_shrinkIconRect = QRect(ICON_PADDING, ICON_PADDING, ICON_SIZE, ICON_SIZE);
_closeIconRect = QRect(_bounds.left() + ICON_PADDING, _bounds.top() + ICON_PADDING, ICON_SIZE, ICON_SIZE);

View file

@ -24,6 +24,7 @@ TextOverlay::TextOverlay() :
_topMargin(DEFAULT_MARGIN),
_fontSize(DEFAULT_FONTSIZE)
{
_textRenderer = TextRenderer::getInstance(SANS_FONT_FAMILY, _fontSize, DEFAULT_FONT_WEIGHT);
}
TextOverlay::TextOverlay(const TextOverlay* textOverlay) :
@ -35,6 +36,7 @@ TextOverlay::TextOverlay(const TextOverlay* textOverlay) :
_topMargin(textOverlay->_topMargin),
_fontSize(textOverlay->_fontSize)
{
_textRenderer = TextRenderer::getInstance(SANS_FONT_FAMILY, _fontSize, DEFAULT_FONT_WEIGHT);
}
TextOverlay::~TextOverlay() {

View file

@ -60,7 +60,7 @@ public:
private:
TextRenderer* _textRenderer = TextRenderer::getInstance(SANS_FONT_FAMILY, _fontSize, DEFAULT_FONT_WEIGHT);
TextRenderer* _textRenderer = nullptr;
QString _text;
xColor _backgroundColor;

View file

@ -20,52 +20,41 @@
#include "RenderableTextEntityItem.h"
#include "GLMHelpers.h"
EntityItemPointer RenderableTextEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) {
return EntityItemPointer(new RenderableTextEntityItem(entityID, properties));
}
void RenderableTextEntityItem::render(RenderArgs* args) {
PerformanceTimer perfTimer("RenderableTextEntityItem::render");
assert(getType() == EntityTypes::Text);
glm::vec3 position = getPosition();
Q_ASSERT(getType() == EntityTypes::Text);
static const float SLIGHTLY_BEHIND = -0.005f;
glm::vec4 textColor = glm::vec4(toGlm(getTextColorX()), 1.0f);
glm::vec4 backgroundColor = glm::vec4(toGlm(getBackgroundColorX()), 1.0f);
glm::vec3 dimensions = getDimensions();
glm::vec3 halfDimensions = dimensions / 2.0f;
glm::quat rotation = getRotation();
float leftMargin = 0.1f;
float topMargin = 0.1f;
//qCDebug(entitytree) << "RenderableTextEntityItem::render() id:" << getEntityItemID() << "text:" << getText();
glPushMatrix();
{
glTranslatef(position.x, position.y, position.z);
glm::vec3 axis = glm::axis(rotation);
glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z);
float alpha = 1.0f; //getBackgroundAlpha();
static const float SLIGHTLY_BEHIND = -0.005f;
glm::vec3 topLeft(-halfDimensions.x, -halfDimensions.y, SLIGHTLY_BEHIND);
glm::vec3 bottomRight(halfDimensions.x, halfDimensions.y, SLIGHTLY_BEHIND);
// TODO: Determine if we want these entities to have the deferred lighting effect? I think we do, so that the color
// used for a sphere, or box have the same look as those used on a text entity.
//DependencyManager::get<DeferredLightingEffect>()->bindSimpleProgram();
DependencyManager::get<GeometryCache>()->renderQuad(topLeft, bottomRight, glm::vec4(toGlm(getBackgroundColorX()), alpha));
//DependencyManager::get<DeferredLightingEffect>()->releaseSimpleProgram();
glTranslatef(-(halfDimensions.x - leftMargin), halfDimensions.y - topMargin, 0.0f);
glm::vec4 textColor(toGlm(getTextColorX()), alpha);
// this is a ratio determined through experimentation
const float scaleFactor = 0.08f * _lineHeight;
glScalef(scaleFactor, -scaleFactor, scaleFactor);
glm::vec2 bounds(dimensions.x / scaleFactor, dimensions.y / scaleFactor);
_textRenderer->draw(0, 0, _text, textColor, bounds);
}
glPopMatrix();
Transform transformToTopLeft = getTransformToCenter();
transformToTopLeft.postTranslate(glm::vec3(-0.5f, 0.5f, 0.0f)); // Go to the top left
transformToTopLeft.setScale(1.0f); // Use a scale of one so that the text is not deformed
// Batch render calls
Q_ASSERT(args->_batch);
gpu::Batch& batch = *args->_batch;
batch.setModelTransform(transformToTopLeft);
// Render background
glm::vec3 minCorner = glm::vec3(0.0f, -dimensions.y, SLIGHTLY_BEHIND);
glm::vec3 maxCorner = glm::vec3(dimensions.x, 0.0f, SLIGHTLY_BEHIND);
DependencyManager::get<DeferredLightingEffect>()->renderQuad(batch, minCorner, maxCorner, backgroundColor);
float scale = _lineHeight / _textRenderer->getRowHeight();
transformToTopLeft.setScale(scale); // Scale to have the correct line height
batch.setModelTransform(transformToTopLeft);
float leftMargin = 0.5f * _lineHeight, topMargin = 0.5f * _lineHeight;
glm::vec2 bounds = glm::vec2(dimensions.x - 2.0f * leftMargin, dimensions.y - 2.0f * topMargin);
_textRenderer->draw(batch, leftMargin / scale, -topMargin / scale, _text, textColor, bounds / scale);
}

View file

@ -13,7 +13,7 @@
#define hifi_RenderableTextEntityItem_h
#include <TextEntityItem.h>
#include <TextRenderer.h>
#include <TextRenderer3D.h>
const int FIXED_FONT_POINT_SIZE = 40;
@ -29,7 +29,7 @@ public:
virtual void render(RenderArgs* args);
private:
TextRenderer* _textRenderer = TextRenderer::getInstance(SANS_FONT_FAMILY, FIXED_FONT_POINT_SIZE / 2.0f);
TextRenderer3D* _textRenderer = TextRenderer3D::getInstance(SANS_FONT_FAMILY, FIXED_FONT_POINT_SIZE / 2.0f);
};

View file

@ -1005,6 +1005,13 @@ void EntityItem::setTranformToCenter(const Transform& transform) {
setTransform(copy);
}
void EntityItem::setDimensions(const glm::vec3& value) {
if (value.x == 0.0f || value.y == 0.0f || value.z == 0.0f) {
return;
}
_transform.setScale(value);
}
/// The maximum bounding cube for the entity, independent of it's rotation.
/// This accounts for the registration point (upon which rotation occurs around).
///

View file

@ -195,7 +195,7 @@ public:
/// Dimensions in meters (0.0 - TREE_SCALE)
inline const glm::vec3& getDimensions() const { return _transform.getScale(); }
inline virtual void setDimensions(const glm::vec3& value) { _transform.setScale(glm::abs(value)); }
virtual void setDimensions(const glm::vec3& value);
float getGlowLevel() const { return _glowLevel; }

View file

@ -66,22 +66,23 @@ void GLBackend::updateInput() {
newActivation.set(attrib._slot);
}
}
// Manage Activation what was and what is expected now
for (unsigned int i = 0; i < newActivation.size(); i++) {
bool newState = newActivation[i];
if (newState != _input._attributeActivation[i]) {
#if defined(SUPPORT_LEGACY_OPENGL)
if (i < NUM_CLASSIC_ATTRIBS) {
const bool useClientState = i < NUM_CLASSIC_ATTRIBS;
#else
const bool useClientState = false;
#endif
if (useClientState) {
if (newState) {
glEnableClientState(attributeSlotToClassicAttribName[i]);
} else {
glDisableClientState(attributeSlotToClassicAttribName[i]);
}
} else {
#else
{
#endif
if (newState) {
glEnableVertexAttribArray(i);
} else {
@ -89,7 +90,7 @@ void GLBackend::updateInput() {
}
}
(void) CHECK_GL_ERROR();
_input._attributeActivation.flip(i);
}
}
@ -123,29 +124,30 @@ void GLBackend::updateInput() {
GLenum type = _elementTypeToGLType[attrib._element.getType()];
GLuint stride = strides[bufferNum];
GLuint pointer = attrib._offset + offsets[bufferNum];
#if defined(SUPPORT_LEGACY_OPENGL)
if (slot < NUM_CLASSIC_ATTRIBS) {
#if defined(SUPPORT_LEGACY_OPENGL)
const bool useClientState = slot < NUM_CLASSIC_ATTRIBS;
#else
const bool useClientState = false;
#endif
if (useClientState) {
switch (slot) {
case Stream::POSITION:
glVertexPointer(count, type, stride, reinterpret_cast<GLvoid*>(pointer));
break;
case Stream::NORMAL:
glNormalPointer(type, stride, reinterpret_cast<GLvoid*>(pointer));
break;
case Stream::COLOR:
glColorPointer(count, type, stride, reinterpret_cast<GLvoid*>(pointer));
break;
case Stream::TEXCOORD:
glTexCoordPointer(count, type, stride, reinterpret_cast<GLvoid*>(pointer));
break;
case Stream::POSITION:
glVertexPointer(count, type, stride, reinterpret_cast<GLvoid*>(pointer));
break;
case Stream::NORMAL:
glNormalPointer(type, stride, reinterpret_cast<GLvoid*>(pointer));
break;
case Stream::COLOR:
glColorPointer(count, type, stride, reinterpret_cast<GLvoid*>(pointer));
break;
case Stream::TEXCOORD:
glTexCoordPointer(count, type, stride, reinterpret_cast<GLvoid*>(pointer));
break;
};
} else {
#else
{
#endif
GLboolean isNormalized = attrib._element.isNormalized();
glVertexAttribPointer(slot, count, type, isNormalized, stride,
reinterpret_cast<GLvoid*>(pointer));
reinterpret_cast<GLvoid*>(pointer));
}
(void) CHECK_GL_ERROR();
}

View file

@ -10,8 +10,8 @@
//
#include "Stream.h"
#include <algorithm> //min max and more
#include <algorithm> //min max and more
using namespace gpu;

View file

@ -17,11 +17,11 @@
namespace gpu {
// THe spherical harmonics is a nice tool for cubemap, so if required, the irradiance SH can be automatically generated
// with the cube texture
class Texture;
class SphericalHarmonics {
public:
// THe spherical harmonics is a nice tool for cubemap, so if required, the irradiance SH can be automatically generated
// with the cube texture
class Texture;
class SphericalHarmonics {
public:
glm::vec3 L00 ; float spare0;
glm::vec3 L1m1 ; float spare1;
glm::vec3 L10 ; float spare2;
@ -44,15 +44,15 @@ public:
VINE_STREET_KITCHEN,
BREEZEWAY,
CAMPUS_SUNSET,
FUNSTON_BEACH_SUNSET,
NUM_PRESET,
FUNSTON_BEACH_SUNSET,
NUM_PRESET,
};
void assignPreset(int p);
void evalFromTexture(const Texture& texture);
};
};
typedef std::shared_ptr< SphericalHarmonics > SHPointer;
class Sampler {
@ -438,7 +438,7 @@ public:
explicit operator bool() const { return bool(_texture); }
bool operator !() const { return (!_texture); }
};
typedef std::vector<TextureView> TextureViews;
typedef std::vector<TextureView> TextureViews;
};

View file

@ -62,8 +62,8 @@ void DeferredLightingEffect::init(AbstractViewStateInterface* viewState) {
state->setCullMode(gpu::State::CULL_BACK);
state->setDepthTest(true, true, gpu::LESS_EQUAL);
state->setBlendFunction(false,
gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA,
gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE);
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);
_simpleProgram = gpu::PipelinePointer(gpu::Pipeline::create(program, state));
_viewState = viewState;

View file

@ -815,9 +815,9 @@ void GeometryCache::renderSolidCube(gpu::Batch& batch, float size, const glm::ve
const int VERTEX_STRIDE = sizeof(GLfloat) * FLOATS_PER_VERTEX * 2; // vertices and normals
const int NORMALS_OFFSET = sizeof(GLfloat) * FLOATS_PER_VERTEX;
if (!_solidCubeVerticies.contains(size)) {
if (!_solidCubeVertices.contains(size)) {
gpu::BufferPointer verticesBuffer(new gpu::Buffer());
_solidCubeVerticies[size] = verticesBuffer;
_solidCubeVertices[size] = verticesBuffer;
GLfloat* vertexData = new GLfloat[vertexPoints * 2]; // vertices and normals
GLfloat* vertex = vertexData;
@ -892,7 +892,7 @@ void GeometryCache::renderSolidCube(gpu::Batch& batch, float size, const glm::ve
colorBuffer->append(sizeof(colors), (gpu::Byte*) colors);
}
gpu::BufferPointer verticesBuffer = _solidCubeVerticies[size];
gpu::BufferPointer verticesBuffer = _solidCubeVertices[size];
gpu::BufferPointer colorBuffer = _solidCubeColors[colorKey];
const int VERTICES_SLOT = 0;

View file

@ -270,7 +270,7 @@ private:
QHash<Vec2Pair, gpu::BufferPointer> _cubeColors;
gpu::BufferPointer _wireCubeIndexBuffer;
QHash<float, gpu::BufferPointer> _solidCubeVerticies;
QHash<float, gpu::BufferPointer> _solidCubeVertices;
QHash<Vec2Pair, gpu::BufferPointer> _solidCubeColors;
gpu::BufferPointer _solidCubeIndexBuffer;

View file

@ -12,8 +12,6 @@
#ifndef hifi_RenderUtil_h
#define hifi_RenderUtil_h
#include <MatrixStack.h>
/// Renders a quad from (-1, -1, 0) to (1, 1, 0) with texture coordinates from (sMin, tMin) to (sMax, tMax).
void renderFullscreenQuad(float sMin = 0.0f, float sMax = 1.0f, float tMin = 0.0f, float tMax = 1.0f);

View file

@ -0,0 +1,498 @@
//
// TextRenderer3D.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 "TextRenderer3D.h"
#include <gpu/GPUConfig.h>
#include <gpu/GLBackend.h>
#include <gpu/Shader.h>
#include <QBuffer>
#include <QImage>
#include <QStringList>
#include <QFile>
#include "GLMHelpers.h"
#include "MatrixStack.h"
#include "RenderUtilsLogging.h"
#include "sdf_text3D_vert.h"
#include "sdf_text3D_frag.h"
#include "GeometryCache.h"
#include "DeferredLightingEffect.h"
// FIXME support the shadow effect, or remove it from the API
// FIXME figure out how to improve the anti-aliasing on the
// interior of the outline fonts
const float DEFAULT_POINT_SIZE = 12;
// Helper functions for reading binary data from an IO device
template<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);
}
// 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 + glyph.offset - glm::vec2(0.0f, 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 getRowHeight() const { return _rowHeight; }
// 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 _rowHeight = 0.0f;
float _leading = 0.0f;
float _ascent = 0.0f;
float _descent = 0.0f;
float _spaceWidth = 0.0f;
bool _initialized = false;
// gpu structures
gpu::PipelinePointer _pipeline;
gpu::TexturePointer _texture;
gpu::Stream::FormatPointer _format;
gpu::BufferPointer _verticesBuffer;
gpu::BufferStreamPointer _stream;
unsigned int _numVertices = 0;
int _fontLoc = -1;
int _outlineLoc = -1;
int _colorLoc = -1;
// last string render characteristics
QString _lastStringRendered;
glm::vec2 _lastBounds;
};
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 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 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;
_rowHeight = _fontSize + _leading;
// 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(false,
gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA,
gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE);
_pipeline = gpu::PipelinePointer(gpu::Pipeline::create(program, state));
// Sanity checks
static const int OFFSET = offsetof(TextureVertex, tex);
assert(OFFSET == sizeof(glm::vec2));
assert(sizeof(glm::vec2) == 2 * sizeof(float));
assert(sizeof(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 - _rowHeight);
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, _fontSize));
_verticesBuffer->append(sizeof(QuadBuilder), (const gpu::Byte*)&qd);
_numVertices += 4;
// Advance by glyph size
advance.x += glyph.d;
}
// Add space after all non return tokens
advance.x += _spaceWidth;
}
}
}
setupGPU();
batch.setPipeline(_pipeline);
batch.setUniformTexture(_fontLoc, _texture);
batch._glUniform1f(_outlineLoc, (effectType == TextRenderer3D::OUTLINE_EFFECT) ? 1.0f : 0.0f);
batch._glUniform4fv(_colorLoc, 1, (const GLfloat*)&color);
batch.setInputFormat(_format);
batch.setInputBuffer(0, _verticesBuffer, 0, _format->getChannels().at(0)._stride);
batch.draw(gpu::QUADS, _numVertices, 0);
}
TextRenderer3D* TextRenderer3D::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 TextRenderer3D(family, pointSize, weight, italic, effect,
effectThickness, color);
}
TextRenderer3D::TextRenderer3D(const char* family, float pointSize, int weight, bool italic,
EffectType effect, int effectThickness, const QColor& color) :
_effectType(effect),
_effectThickness(effectThickness),
_pointSize(pointSize),
_color(toGlm(color)),
_font(loadFont3D(family)) {
if (!_font) {
qWarning() << "Unable to load font with family " << family;
_font = loadFont3D("Courier");
}
if (1 != _effectThickness) {
qWarning() << "Effect thickness not current supported";
}
if (NO_EFFECT != _effectType && OUTLINE_EFFECT != _effectType) {
qWarning() << "Effect thickness not current supported";
}
}
TextRenderer3D::~TextRenderer3D() {
}
glm::vec2 TextRenderer3D::computeExtent(const QString& str) const {
if (_font) {
return _font->computeExtent(str);
}
return glm::vec2(0.0f, 0.0f);
}
float TextRenderer3D::getRowHeight() const {
if (_font) {
return _font->getRowHeight();
}
return 0.0f;
}
void TextRenderer3D::draw(gpu::Batch& batch, float x, float y, const QString& str, const glm::vec4& color,
const glm::vec2& bounds) {
// The font does all the OpenGL work
if (_font) {
glm::vec4 actualColor(color);
if (actualColor.r < 0) {
actualColor = _color;
}
_font->drawString(batch, x, y, str, actualColor, _effectType, bounds);
}
}

View file

@ -0,0 +1,77 @@
//
// TextRenderer3D.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_TextRenderer3D_h
#define hifi_TextRenderer3D_h
#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;
// 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 TextRenderer3D* 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));
~TextRenderer3D();
glm::vec2 computeExtent(const QString& str) const;
float getRowHeight() const;
void draw(gpu::Batch& batch, float x, float y, const QString& str, const glm::vec4& color = glm::vec4(-1.0f),
const glm::vec2& bounds = glm::vec2(-1.0f));
private:
TextRenderer3D(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 glm::vec4 _color;
Font3D* _font;
};
#endif // hifi_TextRenderer3D_h

View file

@ -296,7 +296,7 @@ GLuint TextureCache::getShadowDepthTextureID() {
}
/// Returns a texture version of an image file
gpu::TexturePointer TextureCache::getImageTexture(const QString & path) {
gpu::TexturePointer TextureCache::getImageTexture(const QString& path) {
QImage image = QImage(path).mirrored(false, true);
gpu::Element formatGPU = gpu::Element(gpu::VEC3, gpu::UINT8, gpu::RGB);
gpu::Element formatMip = gpu::Element(gpu::VEC3, gpu::UINT8, gpu::RGB);
@ -383,9 +383,9 @@ ImageReader::ImageReader(const QWeakPointer<Resource>& texture, TextureType type
_content(content) {
}
std::once_flag onceListSuppoertedFormatsflag;
std::once_flag onceListSupportedFormatsflag;
void listSupportedImageFormats() {
std::call_once(onceListSuppoertedFormatsflag, [](){
std::call_once(onceListSupportedFormatsflag, [](){
auto supportedFormats = QImageReader::supportedImageFormats();
QString formats;
foreach(const QByteArray& f, supportedFormats) {

View file

@ -56,7 +56,7 @@ public:
const gpu::TexturePointer& getBlueTexture();
/// Returns a texture version of an image file
gpu::TexturePointer getImageTexture(const QString & path);
static gpu::TexturePointer getImageTexture(const QString& path);
/// Loads a texture from the specified URL.
NetworkTexturePointer getTexture(const QUrl& url, TextureType type = DEFAULT_TEXTURE, bool dilatable = false,

View file

@ -0,0 +1,47 @@
<@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 float Outline;
uniform vec4 Color;
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, gl_TexCoord[0].xy).g;
if (Outline == 1.0f) {
if (sdf > interiorCutoff) {
sdf = 1.0 - sdf;
} else {
sdf += outlineExpansion;
}
}
// perform adaptive anti-aliasing of the edges
// The larger we're rendering, the less anti-aliasing we need
float s = smoothing * length(fwidth(gl_TexCoord[0]));
float w = clamp( s, 0.0, 0.5);
float a = smoothstep(0.5 - w, 0.5 + w, sdf);
// gamma correction for linear attenuation
a = pow(a, 1.0 / gamma);
if (a < 0.01) {
discard;
}
// final color
gl_FragColor = vec4(Color.rgb, a);
}

View file

@ -0,0 +1,23 @@
<@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
//
<@include gpu/Transform.slh@>
<$declareStandardTransform()$>
void main() {
gl_TexCoord[0] = gl_MultiTexCoord0;
// standard transform
TransformCamera cam = getTransformCamera();
TransformObject obj = getTransformObject();
<$transformModelToClipPos(cam, obj, gl_Vertex, gl_Position)$>
}

View file

@ -9,7 +9,6 @@
//
#include "TextRenderer.h"
#include "MatrixStack.h"
#include <QWindow>
#include <QFile>