mirror of
https://github.com/lubosz/overte.git
synced 2025-04-24 02:53:43 +02:00
resolve conflicts on merge with upstream master
This commit is contained in:
commit
f8c832e50b
37 changed files with 751 additions and 1317 deletions
23
interface/resources/qml/TextOverlayElement.qml
Normal file
23
interface/resources/qml/TextOverlayElement.qml
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -29,7 +29,6 @@ BoxEntityItem::BoxEntityItem(const EntityItemID& entityItemID, const EntityItemP
|
|||
EntityItem(entityItemID)
|
||||
{
|
||||
_type = EntityTypes::Box;
|
||||
_created = properties.getCreated();
|
||||
setProperties(properties);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -38,7 +38,6 @@ LineEntityItem::LineEntityItem(const EntityItemID& entityItemID, const EntityIte
|
|||
_points(QVector<glm::vec3>(0))
|
||||
{
|
||||
_type = EntityTypes::Line;
|
||||
_created = properties.getCreated();
|
||||
setProperties(properties);
|
||||
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -37,7 +37,6 @@ TextEntityItem::TextEntityItem(const EntityItemID& entityItemID, const EntityIte
|
|||
EntityItem(entityItemID)
|
||||
{
|
||||
_type = EntityTypes::Text;
|
||||
_created = properties.getCreated();
|
||||
setProperties(properties);
|
||||
}
|
||||
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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
|
|
@ -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";
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
15
libraries/render-utils/src/text/EffectType.h
Normal file
15
libraries/render-utils/src/text/EffectType.h
Normal 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
|
322
libraries/render-utils/src/text/Font.cpp
Normal file
322
libraries/render-utils/src/text/Font.cpp
Normal 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);
|
||||
}
|
80
libraries/render-utils/src/text/Font.h
Normal file
80
libraries/render-utils/src/text/Font.h
Normal 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
|
||||
|
31
libraries/render-utils/src/text/FontFamilies.h
Normal file
31
libraries/render-utils/src/text/FontFamilies.h
Normal 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
|
22
libraries/render-utils/src/text/Glyph.cpp
Normal file
22
libraries/render-utils/src/text/Glyph.cpp
Normal 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;
|
||||
}
|
38
libraries/render-utils/src/text/Glyph.h
Normal file
38
libraries/render-utils/src/text/Glyph.h
Normal 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
|
32
libraries/shared/src/StreamHelpers.h
Normal file
32
libraries/shared/src/StreamHelpers.h
Normal 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
|
|
@ -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();
|
||||
|
||||
|
|
Loading…
Reference in a new issue