From a44054f9dbf403fdef4a11696d6ff020e5d395cf Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Sat, 23 May 2015 03:24:26 +0200 Subject: [PATCH 01/16] Make TextureCache::getImageTexture static --- interface/src/audio/AudioToolBox.cpp | 6 +++--- interface/src/devices/CameraToolBox.cpp | 4 ++-- interface/src/ui/ApplicationOverlay.cpp | 7 +++---- interface/src/ui/RearMirrorTools.cpp | 7 +++---- libraries/render-utils/src/TextureCache.cpp | 2 +- libraries/render-utils/src/TextureCache.h | 2 +- 6 files changed, 13 insertions(+), 15 deletions(-) diff --git a/interface/src/audio/AudioToolBox.cpp b/interface/src/audio/AudioToolBox.cpp index 85b8b19788..68328e151e 100644 --- a/interface/src/audio/AudioToolBox.cpp +++ b/interface/src/audio/AudioToolBox.cpp @@ -40,13 +40,13 @@ void AudioToolBox::render(int x, int y, int padding, bool boxed) { glEnable(GL_TEXTURE_2D); if (!_micTexture) { - _micTexture = DependencyManager::get()->getImageTexture(PathUtils::resourcesPath() + "images/mic.svg"); + _micTexture = TextureCache::getImageTexture(PathUtils::resourcesPath() + "images/mic.svg"); } if (!_muteTexture) { - _muteTexture = DependencyManager::get()->getImageTexture(PathUtils::resourcesPath() + "images/mic-mute.svg"); + _muteTexture = TextureCache::getImageTexture(PathUtils::resourcesPath() + "images/mic-mute.svg"); } if (_boxTexture) { - _boxTexture = DependencyManager::get()->getImageTexture(PathUtils::resourcesPath() + "images/audio-box.svg"); + _boxTexture = TextureCache::getImageTexture(PathUtils::resourcesPath() + "images/audio-box.svg"); } auto audioIO = DependencyManager::get(); diff --git a/interface/src/devices/CameraToolBox.cpp b/interface/src/devices/CameraToolBox.cpp index a1e00d7052..27cee5185b 100644 --- a/interface/src/devices/CameraToolBox.cpp +++ b/interface/src/devices/CameraToolBox.cpp @@ -76,10 +76,10 @@ void CameraToolBox::render(int x, int y, bool boxed) { glEnable(GL_TEXTURE_2D); if (!_enabledTexture) { - _enabledTexture = DependencyManager::get()->getImageTexture(PathUtils::resourcesPath() + "images/face.svg"); + _enabledTexture = TextureCache::getImageTexture(PathUtils::resourcesPath() + "images/face.svg"); } if (!_mutedTexture) { - _mutedTexture = DependencyManager::get()->getImageTexture(PathUtils::resourcesPath() + "images/face-mute.svg"); + _mutedTexture = TextureCache::getImageTexture(PathUtils::resourcesPath() + "images/face-mute.svg"); } const int MUTE_ICON_SIZE = 24; diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index c65de2afb0..1e86078a8b 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -423,8 +423,8 @@ void ApplicationOverlay::displayOverlayTextureStereo(Camera& whichCamera, float }); if (!_crosshairTexture) { - _crosshairTexture = DependencyManager::get()-> - 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()-> - getImageTexture(PathUtils::resourcesPath() + "images/sixense-reticle.png"); + _crosshairTexture = TextureCache::getImageTexture(PathUtils::resourcesPath() + "images/sixense-reticle.png"); } glEnable(GL_TEXTURE_2D); diff --git a/interface/src/ui/RearMirrorTools.cpp b/interface/src/ui/RearMirrorTools.cpp index ec73668d9e..25d37f4ef8 100644 --- a/interface/src/ui/RearMirrorTools.cpp +++ b/interface/src/ui/RearMirrorTools.cpp @@ -34,11 +34,10 @@ RearMirrorTools::RearMirrorTools(QRect& bounds) : _windowed(false), _fullScreen(false) { - auto textureCache = DependencyManager::get(); - _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); diff --git a/libraries/render-utils/src/TextureCache.cpp b/libraries/render-utils/src/TextureCache.cpp index 6facd99ff0..232e972707 100644 --- a/libraries/render-utils/src/TextureCache.cpp +++ b/libraries/render-utils/src/TextureCache.cpp @@ -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); diff --git a/libraries/render-utils/src/TextureCache.h b/libraries/render-utils/src/TextureCache.h index 603ab3a807..ba7176b2a4 100644 --- a/libraries/render-utils/src/TextureCache.h +++ b/libraries/render-utils/src/TextureCache.h @@ -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, From a52c79c378ff9f954b84145523ea6992bf3e438f Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Sat, 23 May 2015 03:25:57 +0200 Subject: [PATCH 02/16] typo --- libraries/render-utils/src/TextureCache.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/render-utils/src/TextureCache.cpp b/libraries/render-utils/src/TextureCache.cpp index 232e972707..97385cb060 100644 --- a/libraries/render-utils/src/TextureCache.cpp +++ b/libraries/render-utils/src/TextureCache.cpp @@ -383,9 +383,9 @@ ImageReader::ImageReader(const QWeakPointer& 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) { From cc7a67e05b5016bbb0e9d9683245824ae6f1a182 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Sun, 24 May 2015 19:18:33 +0200 Subject: [PATCH 03/16] Fix entityProperties.html undefined ref --- examples/html/entityProperties.html | 32 ++++++++++++++--------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/examples/html/entityProperties.html b/examples/html/entityProperties.html index ece2da7760..7c214624c2 100644 --- a/examples/html/entityProperties.html +++ b/examples/html/entityProperties.html @@ -491,22 +491,7 @@ elModelAnimationFrame.value = properties.animationFrameIndex; elModelAnimationSettings.value = properties.animationSettings; elModelTextures.value = properties.textures; - elModelOriginalTextures.value = properties.originalTextures; - if (properties.type == "ParticleEffect") { - for (var i = 0; i < elParticleSections.length; i++) { - elParticleSections[i].style.display = 'block'; - } - - elParticleMaxParticles.value = properties.maxParticles; - elParticleLifeSpan.value = properties.lifespan.toFixed(2); - elParticleEmitRate.value = properties.emitRate.toFixed(1); - elParticleEmitDirectionX.value = properties.emitDirection.x.toFixed(2); - elParticleEmitDirectionY.value = properties.emitDirection.y.toFixed(2); - elParticleEmitDirectionZ.value = properties.emitDirection.z.toFixed(2); - elParticleEmitStrength.value = properties.emitStrength.toFixed(2); - elParticleLocalGravity.value = properties.localGravity.toFixed(2); - elParticleRadius.value = properties.particleRadius.toFixed(3); - } + elModelOriginalTextures.value = properties.originalTextures; } else if (properties.type == "Web") { for (var i = 0; i < elWebSections.length; i++) { elWebSections[i].style.display = 'block'; @@ -589,6 +574,20 @@ showElements(document.getElementsByClassName('skybox-section'), elZoneBackgroundMode.value == 'skybox'); showElements(document.getElementsByClassName('atmosphere-section'), elZoneBackgroundMode.value == 'atmosphere'); + } else if (properties.type == "ParticleEffect") { + for (var i = 0; i < elParticleSections.length; i++) { + elParticleSections[i].style.display = 'block'; + } + + elParticleMaxParticles.value = properties.maxParticles; + elParticleLifeSpan.value = properties.lifespan.toFixed(2); + elParticleEmitRate.value = properties.emitRate.toFixed(1); + elParticleEmitDirectionX.value = properties.emitDirection.x.toFixed(2); + elParticleEmitDirectionY.value = properties.emitDirection.y.toFixed(2); + elParticleEmitDirectionZ.value = properties.emitDirection.z.toFixed(2); + elParticleEmitStrength.value = properties.emitStrength.toFixed(2); + elParticleLocalGravity.value = properties.localGravity.toFixed(2); + elParticleRadius.value = properties.particleRadius.toFixed(3); } if (selected) { @@ -709,7 +708,6 @@ elParticleMaxParticles.addEventListener('change', createEmitNumberPropertyUpdateFunction('maxParticles')); elParticleLifeSpan.addEventListener('change', createEmitNumberPropertyUpdateFunction('lifespan')); elParticleEmitRate.addEventListener('change', createEmitNumberPropertyUpdateFunction('emitRate')); - elParticleEmitDirection.addEventListener('change', createEmitNumberPropertyUpdateFunction('emitDirection')); var particleEmitDirectionChangeFunction = createEmitVec3PropertyUpdateFunctionWithMultiplier( 'emitDirection', elParticleEmitDirectionX, elParticleEmitDirectionY, elParticleEmitDirectionZ, DEGREES_TO_RADIANS); elParticleEmitDirectionX.addEventListener('change', particleEmitDirectionChangeFunction); From 3d7a8343567c3cd0037c43b9bb00fd483aa07639 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Sun, 24 May 2015 19:49:45 +0200 Subject: [PATCH 04/16] Ignore new entity dimensions if one of them is 0 --- libraries/entities/src/EntityItem.cpp | 7 +++++++ libraries/entities/src/EntityItem.h | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 6ad2bed291..06c9260891 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -1020,6 +1020,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). /// diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 8f88b6de07..0d210e5762 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -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; } From 526ec3d4894921b45c453ee5066f49c50eae762a Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Mon, 25 May 2015 11:45:16 +0200 Subject: [PATCH 05/16] Move #if #else #endif for convenience This is a convenience change. The double '{' created by the #else messes up Xcode autoindentation. It won't that way and the fact that useClientState is a const set to false when SUPPORT_LEGACY_OPENGL, the code inside the if should still get optimised out by the compiler. --- libraries/gpu/src/gpu/GLBackendInput.cpp | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/libraries/gpu/src/gpu/GLBackendInput.cpp b/libraries/gpu/src/gpu/GLBackendInput.cpp index fde6ac40d0..4af11f4566 100755 --- a/libraries/gpu/src/gpu/GLBackendInput.cpp +++ b/libraries/gpu/src/gpu/GLBackendInput.cpp @@ -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,8 +124,12 @@ 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 = i < NUM_CLASSIC_ATTRIBS; +#else + const bool useClientState = false; +#endif + if (useClientState) { switch (slot) { case Stream::POSITION: glVertexPointer(count, type, stride, reinterpret_cast(pointer)); @@ -140,9 +145,6 @@ void GLBackend::updateInput() { break; }; } else { - #else - { - #endif GLboolean isNormalized = attrib._element.isNormalized(); glVertexAttribPointer(slot, count, type, isNormalized, stride, reinterpret_cast(pointer)); From c0725813b6dfc2b1200b76ff8f915d03977bdf58 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Mon, 25 May 2015 12:11:24 +0200 Subject: [PATCH 06/16] typo --- libraries/render-utils/src/GeometryCache.cpp | 6 +++--- libraries/render-utils/src/GeometryCache.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/libraries/render-utils/src/GeometryCache.cpp b/libraries/render-utils/src/GeometryCache.cpp index f7280fc0ec..cf5a0d58da 100644 --- a/libraries/render-utils/src/GeometryCache.cpp +++ b/libraries/render-utils/src/GeometryCache.cpp @@ -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; diff --git a/libraries/render-utils/src/GeometryCache.h b/libraries/render-utils/src/GeometryCache.h index 9b21eab2d6..b438eb2d3b 100644 --- a/libraries/render-utils/src/GeometryCache.h +++ b/libraries/render-utils/src/GeometryCache.h @@ -270,7 +270,7 @@ private: QHash _cubeColors; gpu::BufferPointer _wireCubeIndexBuffer; - QHash _solidCubeVerticies; + QHash _solidCubeVertices; QHash _solidCubeColors; gpu::BufferPointer _solidCubeIndexBuffer; From 88d42f931edf1321eb98f2148f221d32fab95727 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Tue, 26 May 2015 18:12:20 +0200 Subject: [PATCH 07/16] Remove unused Headers --- libraries/render-utils/src/RenderUtil.h | 2 -- tests/render-utils/src/main.cpp | 1 - 2 files changed, 3 deletions(-) diff --git a/libraries/render-utils/src/RenderUtil.h b/libraries/render-utils/src/RenderUtil.h index cc823dc177..8c1b1e12e7 100644 --- a/libraries/render-utils/src/RenderUtil.h +++ b/libraries/render-utils/src/RenderUtil.h @@ -12,8 +12,6 @@ #ifndef hifi_RenderUtil_h #define hifi_RenderUtil_h -#include - /// 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); diff --git a/tests/render-utils/src/main.cpp b/tests/render-utils/src/main.cpp index 0ba7416b28..87338e414b 100644 --- a/tests/render-utils/src/main.cpp +++ b/tests/render-utils/src/main.cpp @@ -9,7 +9,6 @@ // #include "TextRenderer.h" -#include "MatrixStack.h" #include #include From bcee01b3a31aa49966ed7ee09fc503a84316b9f6 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Tue, 26 May 2015 18:13:23 +0200 Subject: [PATCH 08/16] First pass at moving TextureRender to use a batch --- libraries/render-utils/src/TextRenderer.cpp | 552 ++++++++------------ libraries/render-utils/src/TextRenderer.h | 21 +- libraries/render-utils/src/sdf_text.slf | 61 ++- libraries/render-utils/src/sdf_text.slv | 14 +- 4 files changed, 276 insertions(+), 372 deletions(-) diff --git a/libraries/render-utils/src/TextRenderer.cpp b/libraries/render-utils/src/TextRenderer.cpp index 87cf9b2728..fe4c1e4d74 100644 --- a/libraries/render-utils/src/TextRenderer.cpp +++ b/libraries/render-utils/src/TextRenderer.cpp @@ -11,6 +11,9 @@ #include +#include +#include + #include #include #include @@ -18,18 +21,9 @@ #include #include -// FIXME, decouple from the GL headers -#include -#include -#include -#include - #include #include -#include "gpu/GLBackend.h" -#include "gpu/Stream.h" - #include "GLMHelpers.h" #include "MatrixStack.h" #include "RenderUtilsLogging.h" @@ -38,22 +32,47 @@ #include "sdf_text_vert.h" #include "sdf_text_frag.h" +const float DEFAULT_POINT_SIZE = 12; + // Helper functions for reading binary data from an IO device template -void readStream(QIODevice & in, T & t) { +void readStream(QIODevice& in, T& t) { in.read((char*) &t, sizeof(t)); } template -void readStream(QIODevice & in, T (&t)[N]) { +void readStream(QIODevice& in, T (&t)[N]) { in.read((char*) t, N); } template -void fillBuffer(QBuffer & buffer, T (&t)[N]) { +void fillBuffer(QBuffer& buffer, T (&t)[N]) { buffer.setData((const char*) t, N); } +struct TextureVertex { + glm::vec2 pos; + glm::vec2 tex; + TextureVertex() { + } + TextureVertex(const glm::vec2& pos, const glm::vec2& tex) : + pos(pos), tex(tex) { + } + TextureVertex(const QPointF& pos, const QPointF& tex) : + pos(pos.x(), pos.y()), tex(tex.x(), tex.y()) { + } +}; + +struct QuadBuilder { + TextureVertex vertices[4]; + QuadBuilder(const QRectF& r, const QRectF& tr) { + vertices[0] = TextureVertex(r.bottomLeft(), tr.topLeft()); + vertices[1] = TextureVertex(r.bottomRight(), tr.topRight()); + vertices[2] = TextureVertex(r.topLeft(), tr.bottomLeft()); + vertices[3] = TextureVertex(r.topRight(), tr.bottomRight()); + } +}; + // FIXME support the shadow effect, or remove it from the API // FIXME figure out how to improve the anti-aliasing on the // interior of the outline fonts @@ -68,13 +87,13 @@ struct Glyph { float d; // xadvance - adjusts character positioning size_t indexOffset; - QRectF bounds() const; - QRectF textureBounds(const glm::vec2 & textureSize) const; + QRectF bounds() const { return glmToRect(offset, size); } + QRectF textureBounds() const { return glmToRect(texOffset, texSize); } - void read(QIODevice & in); + void read(QIODevice& in); }; -void Glyph::read(QIODevice & in) { +void Glyph::read(QIODevice& in) { uint16_t charcode; readStream(in, charcode); c = charcode; @@ -85,64 +104,52 @@ void Glyph::read(QIODevice & in) { texSize = size; } -const float DEFAULT_POINT_SIZE = 12; - class Font { public: - Font(); - using TexturePtr = QSharedPointer < QOpenGLTexture >; - using VertexArrayPtr = QSharedPointer< QOpenGLVertexArrayObject >; - using ProgramPtr = QSharedPointer < QOpenGLShaderProgram >; - using BufferPtr = QSharedPointer < QOpenGLBuffer >; - - // maps characters to cached glyph info - // HACK... the operator[] const for QHash returns a - // copy of the value, not a const value reference, so - // we declare the hash as mutable in order to avoid such - // copies - mutable QHash _glyphs; - - // the id of the glyph texture to which we're currently writing - GLuint _currentTextureID; - - int _pointSize; - - // the height of the current row of characters - int _rowHeight; - - QString _family; - float _fontSize { 0 }; - float _leading { 0 }; - float _ascent { 0 }; - float _descent { 0 }; - float _spaceWidth { 0 }; - - BufferPtr _vertices; - BufferPtr _indices; - TexturePtr _texture; - VertexArrayPtr _vao; - QImage _image; - ProgramPtr _program; - - const Glyph & getGlyph(const QChar & c) const; void read(QIODevice& path); - // Initialize the OpenGL structures - void setupGL(); - glm::vec2 computeExtent(const QString & str) const; - - glm::vec2 computeTokenExtent(const QString & str) const; - - glm::vec2 drawString(float x, float y, const QString & str, + glm::vec2 computeExtent(const QString& str) const; + + // Render string to batch + void drawString(gpu::Batch& batch, float x, float y, const QString& str, const glm::vec4& color, TextRenderer::EffectType effectType, const glm::vec2& bound); private: - QStringList tokenizeForWrapping(const QString & str) const; - - bool _initialized; + QStringList tokenizeForWrapping(const QString& str) const; + QStringList splitLines(const QString& str) const; + glm::vec2 computeTokenExtent(const QString& str) const; + + const Glyph& getGlyph(const QChar& c) const; + + // maps characters to cached glyph info + // HACK... the operator[] const for QHash returns a + // copy of the value, not a const value reference, so + // we declare the hash as mutable in order to avoid such + // copies + mutable QHash _glyphs; + + // Font characteristics + QString _family; + float _fontSize = 0.0f; + float _rowHeight = 0.0f; + float _leading = 0.0f; + float _ascent = 0.0f; + float _descent = 0.0f; + float _spaceWidth = 0.0f; + + // gpu structures + gpu::PipelinePointer _pipeline; + gpu::TexturePointer _texture; + gpu::Stream::FormatPointer _format; + gpu::BufferView _vertices; + gpu::BufferView _texCoords; + + // last string render characteristics + QString _lastStringRendered; + glm::vec2 _lastBounds; }; static QHash LOADED_FONTS; @@ -189,7 +196,7 @@ Font* loadFont(const QString& family) { return LOADED_FONTS[family]; } -Font::Font() : _initialized(false) { +Font::Font() { static bool fontResourceInitComplete = false; if (!fontResourceInitComplete) { Q_INIT_RESOURCE(fonts); @@ -198,13 +205,50 @@ Font::Font() : _initialized(false) { } // NERD RAGE: why doesn't QHash have a 'const T & operator[] const' member -const Glyph & Font::getGlyph(const QChar & c) const { +const Glyph& Font::getGlyph(const QChar& c) const { if (!_glyphs.contains(c)) { return _glyphs[QChar('?')]; } return _glyphs[c]; } +QStringList Font::splitLines(const QString& str) const { + return str.split('\n'); +} + +QStringList Font::tokenizeForWrapping(const QString& str) const { + QStringList tokens; + for(auto line : splitLines(str)) { + if (!tokens.empty()) { + tokens << QString('\n'); + } + tokens << line.split(' '); + } + return tokens; +} + +glm::vec2 Font::computeTokenExtent(const QString& token) const { + glm::vec2 advance(0, _fontSize); + foreach(QChar c, token) { + Q_ASSERT(c != '\n'); + advance.x += (c == ' ') ? _spaceWidth : getGlyph(c).d; + } + return advance; +} + +glm::vec2 Font::computeExtent(const QString& str) const { + glm::vec2 extent = glm::vec2(0.0f, 0.0f); + + QStringList tokens = splitLines(str); + foreach(const QString& token, tokens) { + glm::vec2 tokenExtent = computeTokenExtent(token); + extent.x = std::max(tokenExtent.x, extent.x); + } + extent.y = tokens.count() * _rowHeight; + + return extent; +} + void Font::read(QIODevice& in) { uint8_t header[4]; readStream(in, header); @@ -232,7 +276,7 @@ void Font::read(QIODevice& in) { readStream(in, _descent); readStream(in, _spaceWidth); _fontSize = _ascent + _descent; - _rowHeight = _fontSize + _descent; + _rowHeight = _fontSize + _leading; // Read character count uint16_t count; @@ -240,266 +284,127 @@ void Font::read(QIODevice& in) { // read metrics data for each character QVector glyphs(count); // std::for_each instead of Qt foreach because we need non-const references - std::for_each(glyphs.begin(), glyphs.end(), [&](Glyph & g) { + std::for_each(glyphs.begin(), glyphs.end(), [&](Glyph& g) { g.read(in); }); // read image data - if (!_image.loadFromData(in.readAll(), "PNG")) { + QImage image; + image.loadFromData(in.readAll(), "PNG"); + if (!image.isNull()) { qFatal("Failed to read SDFF image"); } + gpu::Element formatGPU = gpu::Element(gpu::VEC3, gpu::UINT8, gpu::RGB); + gpu::Element formatMip = gpu::Element(gpu::VEC3, gpu::UINT8, gpu::RGB); + if (image.hasAlphaChannel()) { + formatGPU = gpu::Element(gpu::VEC4, gpu::UINT8, gpu::RGBA); + formatMip = gpu::Element(gpu::VEC4, gpu::UINT8, gpu::BGRA); + } + _texture = gpu::TexturePointer( + gpu::Texture::create2D(formatGPU, image.width(), image.height(), + gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR))); + _texture->assignStoredMip(0, formatMip, image.byteCount(), image.constBits()); + _texture->autoGenerateMips(-1); _glyphs.clear(); + glm::vec2 imageSize = toGlm(image.size()); foreach(Glyph g, glyphs) { // Adjust the pixel texture coordinates into UV coordinates, - glm::vec2 imageSize = toGlm(_image.size()); g.texSize /= imageSize; g.texOffset /= imageSize; // store in the character to glyph hash _glyphs[g.c] = g; }; + + // Setup render pipeline + auto vertexShader = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(sdf_text_vert))); + auto pixelShader = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(sdf_text_frag))); + gpu::ShaderPointer program = gpu::ShaderPointer(gpu::Shader::createProgram(vertexShader, pixelShader)); + + gpu::Shader::BindingSet slotBindings; + slotBindings.insert(gpu::Shader::Binding("Outline", 0)); + slotBindings.insert(gpu::Shader::Binding("Offset", 1)); + slotBindings.insert(gpu::Shader::Binding("Color", 2)); + gpu::Shader::makeProgram(*program, slotBindings); + + gpu::StatePointer state = gpu::StatePointer(new gpu::State()); + + _pipeline = gpu::PipelinePointer(gpu::Pipeline::create(program, state)); } -struct TextureVertex { - glm::vec2 pos; - glm::vec2 tex; - TextureVertex() { - } - TextureVertex(const glm::vec2 & pos, const glm::vec2 & tex) : - pos(pos), tex(tex) { - } - TextureVertex(const QPointF & pos, const QPointF & tex) : - pos(pos.x(), pos.y()), tex(tex.x(), tex.y()) { - } -}; - -struct QuadBuilder { - TextureVertex vertices[4]; - QuadBuilder(const QRectF & r, const QRectF & tr) { - vertices[0] = TextureVertex(r.bottomLeft(), tr.topLeft()); - vertices[1] = TextureVertex(r.bottomRight(), tr.topRight()); - vertices[2] = TextureVertex(r.topRight(), tr.bottomRight()); - vertices[3] = TextureVertex(r.topLeft(), tr.bottomLeft()); - } -}; - -QRectF Glyph::bounds() const { - return glmToRect(offset, size); -} - -QRectF Glyph::textureBounds(const glm::vec2 & textureSize) const { - return glmToRect(texOffset, texSize); -} - -void Font::setupGL() { - if (_initialized) { - return; - } - _initialized = true; - - _texture = TexturePtr( - new QOpenGLTexture(_image, QOpenGLTexture::GenerateMipMaps)); - _program = ProgramPtr(new QOpenGLShaderProgram()); - if (!_program->create()) { - qFatal("Could not create text shader"); - } - if (!_program->addShaderFromSourceCode(QOpenGLShader::Vertex, sdf_text_vert) || // - !_program->addShaderFromSourceCode(QOpenGLShader::Fragment, sdf_text_frag) || // - !_program->link()) { - qFatal("%s", _program->log().toLocal8Bit().constData()); - } - - std::vector vertexData; - std::vector indexData; - vertexData.reserve(_glyphs.size() * 4); - std::for_each(_glyphs.begin(), _glyphs.end(), [&](Glyph & m) { - GLuint index = (GLuint)vertexData.size(); - - QRectF bounds = m.bounds(); - QRectF texBounds = m.textureBounds(toGlm(_image.size())); - QuadBuilder qb(bounds, texBounds); - for (int i = 0; i < 4; ++i) { - vertexData.push_back(qb.vertices[i]); - } - - m.indexOffset = indexData.size() * sizeof(GLuint); - // FIXME use triangle strips + primitive restart index - indexData.push_back(index + 0); - indexData.push_back(index + 1); - indexData.push_back(index + 2); - indexData.push_back(index + 0); - indexData.push_back(index + 2); - indexData.push_back(index + 3); - }); - - _vao = VertexArrayPtr(new QOpenGLVertexArrayObject()); - _vao->create(); - _vao->bind(); - - _vertices = BufferPtr(new QOpenGLBuffer(QOpenGLBuffer::VertexBuffer)); - _vertices->create(); - _vertices->bind(); - _vertices->allocate(&vertexData[0], - sizeof(TextureVertex) * vertexData.size()); - _indices = BufferPtr(new QOpenGLBuffer(QOpenGLBuffer::IndexBuffer)); - _indices->create(); - _indices->bind(); - _indices->allocate(&indexData[0], sizeof(GLuint) * indexData.size()); - - GLsizei stride = (GLsizei) sizeof(TextureVertex); - void* offset = (void*) offsetof(TextureVertex, tex); - int posLoc = _program->attributeLocation("Position"); - int texLoc = _program->attributeLocation("TexCoord"); - glEnableVertexAttribArray(posLoc); - glVertexAttribPointer(posLoc, 2, GL_FLOAT, false, stride, nullptr); - glEnableVertexAttribArray(texLoc); - glVertexAttribPointer(texLoc, 2, GL_FLOAT, false, stride, offset); - _vao->release(); -} - -// FIXME there has to be a cleaner way of doing this -QStringList Font::tokenizeForWrapping(const QString & str) const { - QStringList result; - foreach(const QString & token1, str.split(" ")) { - bool lineFeed = false; - if (token1.isEmpty()) { - result << token1; - continue; - } - foreach(const QString & token2, token1.split("\n")) { - if (lineFeed) { - result << "\n"; +void Font::drawString(gpu::Batch& batch, float x, float y, const QString& str, const glm::vec4& color, + TextRenderer::EffectType effectType, const glm::vec2& bounds) { + // Top left of text + glm::vec2 advance = glm::vec2(0.0f, 0.0f); + + if (str != _lastStringRendered || bounds != _lastBounds) { + gpu::BufferPointer vertices = gpu::BufferPointer(new gpu::Buffer()); + foreach(const QString& token, tokenizeForWrapping(str)) { + bool isNewLine = token == QString('\n'); + bool forceNewLine = false; + + // Handle wrapping + if (!isNewLine && (bounds.x != -1) && (advance.x + computeExtent(token).x)) { + // We are out of the x bound, force new line + forceNewLine = true; } - if (token2.size()) { - result << token2; - } - lineFeed = true; - } - } - return result; -} - - -glm::vec2 Font::computeTokenExtent(const QString & token) const { - glm::vec2 advance(0, _rowHeight - _descent); - foreach(QChar c, token) { - assert(c != ' ' && c != '\n'); - const Glyph & m = getGlyph(c); - advance.x += m.d; - } - return advance; -} - - -glm::vec2 Font::computeExtent(const QString & str) const { - glm::vec2 extent(0, _rowHeight - _descent); - // FIXME, come up with a better method of splitting text - // that will allow wrapping but will preserve things like - // tabs or consecutive spaces - bool firstTokenOnLine = true; - float lineWidth = 0.0f; - QStringList tokens = tokenizeForWrapping(str); - foreach(const QString & token, tokens) { - if (token == "\n") { - extent.x = std::max(lineWidth, extent.x); - lineWidth = 0.0f; - extent.y += _rowHeight; - firstTokenOnLine = true; - continue; - } - if (!firstTokenOnLine) { - lineWidth += _spaceWidth; - } - lineWidth += computeTokenExtent(token).x; - firstTokenOnLine = false; - } - extent.x = std::max(lineWidth, extent.x); - return extent; -} - -// FIXME support the maxWidth parameter and allow the text to automatically wrap -// even without explicit line feeds. -glm::vec2 Font::drawString(float x, float y, const QString & str, - const glm::vec4& color, TextRenderer::EffectType effectType, - const glm::vec2& bounds) { - - setupGL(); - - // Stores how far we've moved from the start of the string, in DTP units - glm::vec2 advance(0, -_rowHeight - _descent); - - _program->bind(); - _program->setUniformValue("Color", color.r, color.g, color.b, color.a); - _program->setUniformValue("Projection", - fromGlm(MatrixStack::projection().top())); - if (effectType == TextRenderer::OUTLINE_EFFECT) { - _program->setUniformValue("Outline", true); - } - // Needed? - glEnable(GL_TEXTURE_2D); - _texture->bind(); - _vao->bind(); - - MatrixStack & mv = MatrixStack::modelview(); - // scale the modelview into font units - mv.translate(glm::vec3(0, _ascent, 0)); - foreach(const QString & token, tokenizeForWrapping(str)) { - if (token == "\n") { - advance.x = 0.0f; - advance.y -= _rowHeight; - // If we've wrapped right out of the bounds, then we're - // done with rendering the tokens - if (bounds.y > 0 && std::abs(advance.y) > bounds.y) { - break; - } - continue; - } - - glm::vec2 tokenExtent = computeTokenExtent(token); - if (bounds.x > 0 && advance.x > 0) { - // We check if we'll be out of bounds - if (advance.x + tokenExtent.x >= bounds.x) { - // We're out of bounds, so wrap to the next line - advance.x = 0.0f; - advance.y -= _rowHeight; - // If we've wrapped right out of the bounds, then we're - // done with rendering the tokens - if (bounds.y > 0 && std::abs(advance.y) > bounds.y) { - break; + if (isNewLine || forceNewLine) { + // Character return, move the advance to a new line + advance = glm::vec2(0.0f, advance.y + _rowHeight); + if (isNewLine) { + // No need to draw anything, go directly to next token + continue; } } + if ((bounds.y != -1) && (advance.y + _fontSize > bounds.y)) { + // We are out of the y bound, stop drawing + break; + } + + // Draw the token + if (!isNewLine) { + for (auto c : token) { + auto glyph = _glyphs[c]; + + // Build translated quad and add it to the buffer + QuadBuilder qd(glyph.bounds().translated(advance.x, advance.y), + glyph.textureBounds()); + vertices->append(sizeof(QuadBuilder), (const gpu::Byte*)qd.vertices); + + // Advance by glyph size + advance.x += glyph.d; + } + + // Add space after all non return tokens + advance.x += _spaceWidth; + } } - - foreach(const QChar & c, token) { - // get metrics for this character to speed up measurements - const Glyph & m = getGlyph(c); - // We create an offset vec2 to hold the local offset of this character - // This includes compensating for the inverted Y axis of the font - // coordinates - glm::vec2 offset(advance); - offset.y -= m.size.y; - // Bind the new position - mv.withPush([&] { - mv.translate(offset); - // FIXME find a better (and GL ES 3.1 compatible) way of rendering the text - // that doesn't involve a single GL call per character. - // Most likely an 'indirect' call or an 'instanced' call. - _program->setUniformValue("ModelView", fromGlm(mv.top())); - glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, (void*)(m.indexOffset)); - }); - advance.x += m.d; - } - advance.x += _spaceWidth; + + // Setup rendering structures + static const int STRIDES = sizeof(TextureVertex); + static const int OFFSET = offsetof(TextureVertex, tex); + _format = gpu::Stream::FormatPointer(new gpu::Stream::Format()); + _format->setAttribute(gpu::Stream::POSITION, gpu::Stream::POSITION, + gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV)); + _format->setAttribute(gpu::Stream::TEXCOORD, gpu::Stream::TEXCOORD, + gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV)); + + _vertices = gpu::BufferView(vertices, 0, vertices->getSize(), STRIDES, + _format->getAttributes().at(gpu::Stream::POSITION)._element); + _texCoords = gpu::BufferView(vertices, OFFSET, vertices->getSize(), STRIDES, + _format->getAttributes().at(gpu::Stream::TEXCOORD)._element); + _lastStringRendered = str; + _lastBounds = bounds; } - - _vao->release(); - _texture->release(); // TODO: Brad & Sam, let's discuss this. Without this non-textured quads get their colors borked. - _program->release(); - // FIXME, needed? - // glDisable(GL_TEXTURE_2D); - - return advance; + batch.setInputFormat(_format); + batch.setInputBuffer(0, _vertices); + batch.setInputBuffer(1, _texCoords); + batch.setUniformTexture(2, _texture); + batch._glUniform1f(0, (effectType == TextRenderer::OUTLINE_EFFECT) ? 1.0f : 0.0f); + batch._glUniform2f(1, x, y); + batch._glUniform4fv(2, 4, (const GLfloat*)&color); + batch.draw(gpu::QUADS, _vertices.getNumElements()); } TextRenderer* TextRenderer::getInstance(const char* family, float pointSize, @@ -531,7 +436,7 @@ TextRenderer::TextRenderer(const char* family, float pointSize, int weight, TextRenderer::~TextRenderer() { } -glm::vec2 TextRenderer::computeExtent(const QString & str) const { +glm::vec2 TextRenderer::computeExtent(const QString& str) const { float scale = (_pointSize / DEFAULT_POINT_SIZE) * 0.25f; if (_font) { return _font->computeExtent(str) * scale; @@ -539,32 +444,25 @@ glm::vec2 TextRenderer::computeExtent(const QString & str) const { return glm::vec2(0.1f,0.1f); } -float TextRenderer::draw(float x, float y, const QString & str, - const glm::vec4& color, const glm::vec2 & bounds) { +void TextRenderer::draw(float x, float y, const QString& str, const glm::vec4& color, const glm::vec2& bounds) { + gpu::Batch batch; + draw(batch, x, y, str, color, bounds); + gpu::GLBackend::renderBatch(batch); +} + +void TextRenderer::draw(gpu::Batch& batch, float x, float y, const QString& str, const glm::vec4& color, + const glm::vec2& bounds) { + glm::vec4 actualColor(color); if (actualColor.r < 0) { actualColor = toGlm(_color); } - + float scale = (_pointSize / DEFAULT_POINT_SIZE) * 0.25f; - glm::vec2 result; - - MatrixStack::withPushAll([&] { - MatrixStack & mv = MatrixStack::modelview(); - MatrixStack & pr = MatrixStack::projection(); - gpu::GLBackend::fetchMatrix(GL_MODELVIEW_MATRIX, mv.top()); - gpu::GLBackend::fetchMatrix(GL_PROJECTION_MATRIX, pr.top()); - - // scale the modelview into font units - // FIXME migrate the constant scale factor into the geometry of the - // fonts so we don't have to flip the Y axis here and don't have to - // scale at all. - mv.translate(glm::vec2(x, y)).scale(glm::vec3(scale, -scale, scale)); - // The font does all the OpenGL work - if (_font) { - result = _font->drawString(x, y, str, actualColor, _effectType, bounds / scale); - } - }); - return result.x; + + // The font does all the OpenGL work + if (_font) { + _font->drawString(batch, x, y, str, actualColor, _effectType, bounds / scale); + } } diff --git a/libraries/render-utils/src/TextRenderer.h b/libraries/render-utils/src/TextRenderer.h index 85bb18cec9..6dbe3cbc33 100644 --- a/libraries/render-utils/src/TextRenderer.h +++ b/libraries/render-utils/src/TextRenderer.h @@ -25,9 +25,6 @@ #include #include -// a special "character" that renders as a solid block -const char SOLID_BLOCK_CHAR = 127; - // the standard sans serif font family #define SANS_FONT_FAMILY "Helvetica" @@ -46,6 +43,9 @@ const char SOLID_BLOCK_CHAR = 127; #define INCONSOLATA_FONT_WEIGHT QFont::Bold #endif +namespace gpu { +class Batch; +} class Font; // TextRenderer is actually a fairly thin wrapper around a Font class @@ -59,13 +59,12 @@ public: ~TextRenderer(); - glm::vec2 computeExtent(const QString & str) const; - - float draw( - float x, float y, - const QString & str, - const glm::vec4& color = glm::vec4(-1.0f), - const glm::vec2& bounds = glm::vec2(-1.0f)); + glm::vec2 computeExtent(const QString& str) const; + + void draw(float x, float y, const QString& str, const glm::vec4& color = glm::vec4(-1.0f), + const glm::vec2& bounds = glm::vec2(-1.0f)); + void draw(gpu::Batch& batch, float x, float y, const QString& str, const glm::vec4& color = glm::vec4(-1.0f), + const glm::vec2& bounds = glm::vec2(-1.0f)); private: TextRenderer(const char* family, float pointSize = -1, int weight = -1, bool italic = false, @@ -82,7 +81,7 @@ private: // text color const QColor _color; - Font * _font; + Font* _font; }; diff --git a/libraries/render-utils/src/sdf_text.slf b/libraries/render-utils/src/sdf_text.slf index 1affbe4c57..0cf6e3fb1b 100644 --- a/libraries/render-utils/src/sdf_text.slf +++ b/libraries/render-utils/src/sdf_text.slf @@ -10,11 +10,13 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -uniform sampler2D Font; -uniform vec4 Color; -uniform bool Outline; +<@include DeferredBufferWrite.slh@> -varying vec2 vTexCoord; +uniform sampler2D Font; +uniform float Outline; + +// the interpolated normal +varying vec4 interpolatedNormal; const float gamma = 2.6; const float smoothing = 100.0; @@ -22,28 +24,33 @@ const float interiorCutoff = 0.8; const float outlineExpansion = 0.2; void main() { - // retrieve signed distance - float sdf = texture2D(Font, vTexCoord).r; - if (Outline) { - if (sdf > interiorCutoff) { - sdf = 1.0 - sdf; - } else { - sdf += outlineExpansion; + // retrieve signed distance + float sdf = texture2D(Font, vTexCoord).r; + if (Outline == 1.0f) { + if (sdf > interiorCutoff) { + sdf = 1.0 - sdf; + } else { + sdf += outlineExpansion; + } } - } - // perform adaptive anti-aliasing of the edges - // The larger we're rendering, the less anti-aliasing we need - float s = smoothing * length(fwidth(vTexCoord)); - float w = clamp( s, 0.0, 0.5); - float a = smoothstep(0.5 - w, 0.5 + w, sdf); - - // gamma correction for linear attenuation - a = pow(a, 1.0 / gamma); - - if (a < 0.01) { - discard; - } - - // final color - gl_FragColor = vec4(Color.rgb, a); + // perform adaptive anti-aliasing of the edges + // The larger we're rendering, the less anti-aliasing we need + float s = smoothing * length(fwidth(vTexCoord)); + float w = clamp( s, 0.0, 0.5); + float a = smoothstep(0.5 - w, 0.5 + w, sdf); + + // gamma correction for linear attenuation + a = pow(a, 1.0 / gamma); + + if (a < 0.01) { + discard; + } + + // final color + packDeferredFragment( + normalize(interpolatedNormal.xyz), + a, + gl_Color.rgb, + gl_FrontMaterial.specular.rgb, + gl_FrontMaterial.shininess); } \ No newline at end of file diff --git a/libraries/render-utils/src/sdf_text.slv b/libraries/render-utils/src/sdf_text.slv index 27db1c4985..157efd7f4b 100644 --- a/libraries/render-utils/src/sdf_text.slv +++ b/libraries/render-utils/src/sdf_text.slv @@ -9,16 +9,16 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +<@include gpu/Transform.slh@> -uniform mat4 Projection; -uniform mat4 ModelView; +<$declareStandardTransform()$> -attribute vec2 Position; -attribute vec2 TexCoord; +uniform float Offset[2]; -varying vec2 vTexCoord; void main() { - vTexCoord = TexCoord; - gl_Position = Projection * ModelView * vec4(Position, 0.0, 1.0); + // standard transform + TransformCamera cam = getTransformCamera(); + TransformObject obj = getTransformObject(); + <$transformModelToClipPos(cam, obj, gl_Vertex + vec4(Offset[0], Offset[1], 0.0f, 0.0f), gl_Position)$> } \ No newline at end of file From 62bb1a49e416b17fbcab0c12c01ee83fdd440c78 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Tue, 26 May 2015 18:18:50 +0200 Subject: [PATCH 09/16] Fix inverted glyphs load check --- libraries/render-utils/src/TextRenderer.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/libraries/render-utils/src/TextRenderer.cpp b/libraries/render-utils/src/TextRenderer.cpp index fe4c1e4d74..abb65a20d9 100644 --- a/libraries/render-utils/src/TextRenderer.cpp +++ b/libraries/render-utils/src/TextRenderer.cpp @@ -290,8 +290,7 @@ void Font::read(QIODevice& in) { // read image data QImage image; - image.loadFromData(in.readAll(), "PNG"); - if (!image.isNull()) { + if (!image.loadFromData(in.readAll(), "PNG")) { qFatal("Failed to read SDFF image"); } gpu::Element formatGPU = gpu::Element(gpu::VEC3, gpu::UINT8, gpu::RGB); From 5d19431d265a4a250849dafd0cb935c4e41982d7 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Tue, 26 May 2015 20:20:39 +0200 Subject: [PATCH 10/16] Copy/paste error with useClientState --- libraries/gpu/src/gpu/GLBackendInput.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/gpu/src/gpu/GLBackendInput.cpp b/libraries/gpu/src/gpu/GLBackendInput.cpp index 4af11f4566..bef3289161 100755 --- a/libraries/gpu/src/gpu/GLBackendInput.cpp +++ b/libraries/gpu/src/gpu/GLBackendInput.cpp @@ -125,7 +125,7 @@ void GLBackend::updateInput() { GLuint stride = strides[bufferNum]; GLuint pointer = attrib._offset + offsets[bufferNum]; #if defined(SUPPORT_LEGACY_OPENGL) - const bool useClientState = i < NUM_CLASSIC_ATTRIBS; + const bool useClientState = slot < NUM_CLASSIC_ATTRIBS; #else const bool useClientState = false; #endif From c9022212e8a404d5681ed5848262a3578f6363a0 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Tue, 26 May 2015 20:34:23 +0200 Subject: [PATCH 11/16] More work on the text renderer --- .../src/DeferredLightingEffect.cpp | 4 +- libraries/render-utils/src/TextRenderer.cpp | 113 +++++++++--------- libraries/render-utils/src/TextRenderer.h | 10 -- libraries/render-utils/src/sdf_text.slf | 17 +-- libraries/render-utils/src/sdf_text.slv | 8 +- 5 files changed, 66 insertions(+), 86 deletions(-) diff --git a/libraries/render-utils/src/DeferredLightingEffect.cpp b/libraries/render-utils/src/DeferredLightingEffect.cpp index 8a9ee4bf6d..00c9a13387 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.cpp +++ b/libraries/render-utils/src/DeferredLightingEffect.cpp @@ -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; diff --git a/libraries/render-utils/src/TextRenderer.cpp b/libraries/render-utils/src/TextRenderer.cpp index abb65a20d9..be6a847031 100644 --- a/libraries/render-utils/src/TextRenderer.cpp +++ b/libraries/render-utils/src/TextRenderer.cpp @@ -9,25 +9,19 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include "TextRenderer.h" #include #include -#include -#include -#include -#include -#include #include +#include +#include #include -#include -#include - #include "GLMHelpers.h" #include "MatrixStack.h" #include "RenderUtilsLogging.h" -#include "TextRenderer.h" #include "sdf_text_vert.h" #include "sdf_text_frag.h" @@ -53,14 +47,9 @@ void fillBuffer(QBuffer& buffer, T (&t)[N]) { struct TextureVertex { glm::vec2 pos; glm::vec2 tex; - TextureVertex() { - } - TextureVertex(const glm::vec2& pos, const glm::vec2& tex) : - pos(pos), tex(tex) { - } - TextureVertex(const QPointF& pos, const QPointF& tex) : - pos(pos.x(), pos.y()), tex(tex.x(), tex.y()) { - } + 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 { @@ -141,11 +130,12 @@ private: float _spaceWidth = 0.0f; // gpu structures + static const unsigned int VERTEX_BUFFER_CHANNEL = 0; gpu::PipelinePointer _pipeline; gpu::TexturePointer _texture; gpu::Stream::FormatPointer _format; - gpu::BufferView _vertices; - gpu::BufferView _texCoords; + gpu::BufferPointer _vertices; + unsigned int _numVertices = 0; // last string render characteristics QString _lastStringRendered; @@ -327,8 +317,18 @@ void Font::read(QIODevice& in) { gpu::Shader::makeProgram(*program, slotBindings); 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)); + + // Setup rendering structures + static const int OFFSET = offsetof(TextureVertex, tex); + _format = gpu::Stream::FormatPointer(new gpu::Stream::Format()); + _format->setAttribute(gpu::Stream::POSITION, VERTEX_BUFFER_CHANNEL, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV), 0); + _format->setAttribute(gpu::Stream::TEXCOORD, VERTEX_BUFFER_CHANNEL, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV), OFFSET); } void Font::drawString(gpu::Batch& batch, float x, float y, const QString& str, const glm::vec4& color, @@ -337,7 +337,11 @@ void Font::drawString(gpu::Batch& batch, float x, float y, const QString& str, c glm::vec2 advance = glm::vec2(0.0f, 0.0f); if (str != _lastStringRendered || bounds != _lastBounds) { - gpu::BufferPointer vertices = gpu::BufferPointer(new gpu::Buffer()); + _vertices = gpu::BufferPointer(new gpu::Buffer()); + _numVertices = 0; + _lastStringRendered = str; + _lastBounds = bounds; + foreach(const QString& token, tokenizeForWrapping(str)) { bool isNewLine = token == QString('\n'); bool forceNewLine = false; @@ -368,7 +372,8 @@ void Font::drawString(gpu::Batch& batch, float x, float y, const QString& str, c // Build translated quad and add it to the buffer QuadBuilder qd(glyph.bounds().translated(advance.x, advance.y), glyph.textureBounds()); - vertices->append(sizeof(QuadBuilder), (const gpu::Byte*)qd.vertices); + _vertices->append(sizeof(qd.vertices), (const gpu::Byte*)qd.vertices); + _numVertices += 4; // Advance by glyph size advance.x += glyph.d; @@ -378,32 +383,19 @@ void Font::drawString(gpu::Batch& batch, float x, float y, const QString& str, c advance.x += _spaceWidth; } } - - // Setup rendering structures - static const int STRIDES = sizeof(TextureVertex); - static const int OFFSET = offsetof(TextureVertex, tex); - _format = gpu::Stream::FormatPointer(new gpu::Stream::Format()); - _format->setAttribute(gpu::Stream::POSITION, gpu::Stream::POSITION, - gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV)); - _format->setAttribute(gpu::Stream::TEXCOORD, gpu::Stream::TEXCOORD, - gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV)); - - _vertices = gpu::BufferView(vertices, 0, vertices->getSize(), STRIDES, - _format->getAttributes().at(gpu::Stream::POSITION)._element); - _texCoords = gpu::BufferView(vertices, OFFSET, vertices->getSize(), STRIDES, - _format->getAttributes().at(gpu::Stream::TEXCOORD)._element); - _lastStringRendered = str; - _lastBounds = bounds; } - batch.setInputFormat(_format); - batch.setInputBuffer(0, _vertices); - batch.setInputBuffer(1, _texCoords); + batch.setPipeline(_pipeline); batch.setUniformTexture(2, _texture); batch._glUniform1f(0, (effectType == TextRenderer::OUTLINE_EFFECT) ? 1.0f : 0.0f); batch._glUniform2f(1, x, y); batch._glUniform4fv(2, 4, (const GLfloat*)&color); - batch.draw(gpu::QUADS, _vertices.getNumElements()); + QRectF rect(0.0f, 0.0f, 1.0f, 1.0f); + QuadBuilder qd(rect, rect); + _vertices->append(sizeof(QuadBuilder), (const gpu::Byte*)qd.vertices); + batch.setInputFormat(_format); + batch.setInputBuffer(VERTEX_BUFFER_CHANNEL, _vertices); + batch.draw(gpu::QUADS, _numVertices); } TextRenderer* TextRenderer::getInstance(const char* family, float pointSize, @@ -416,10 +408,13 @@ TextRenderer* TextRenderer::getInstance(const char* family, float pointSize, 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)) { +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"); @@ -436,32 +431,32 @@ TextRenderer::~TextRenderer() { } glm::vec2 TextRenderer::computeExtent(const QString& str) const { - float scale = (_pointSize / DEFAULT_POINT_SIZE) * 0.25f; if (_font) { + float scale = (_pointSize / DEFAULT_POINT_SIZE) * 0.25f; return _font->computeExtent(str) * scale; } return glm::vec2(0.1f,0.1f); } void TextRenderer::draw(float x, float y, const QString& str, const glm::vec4& color, const glm::vec2& bounds) { - gpu::Batch batch; - draw(batch, x, y, str, color, bounds); - gpu::GLBackend::renderBatch(batch); + // The font does all the OpenGL work + if (_font) { +// gpu::Batch batch; +// draw(batch, x, y, str, color, bounds); +// gpu::GLBackend::renderBatch(batch, true); + } } void TextRenderer::draw(gpu::Batch& batch, float x, float y, const QString& str, const glm::vec4& color, const glm::vec2& bounds) { - - glm::vec4 actualColor(color); - if (actualColor.r < 0) { - actualColor = toGlm(_color); - } - - float scale = (_pointSize / DEFAULT_POINT_SIZE) * 0.25f; - // The font does all the OpenGL work if (_font) { - _font->drawString(batch, x, y, str, actualColor, _effectType, bounds / scale); + glm::vec4 actualColor(color); + if (actualColor.r < 0) { + actualColor = toGlm(_color); + } + + _font->drawString(batch, x, y, str, actualColor, _effectType, bounds); } } diff --git a/libraries/render-utils/src/TextRenderer.h b/libraries/render-utils/src/TextRenderer.h index 6dbe3cbc33..7bd0781f96 100644 --- a/libraries/render-utils/src/TextRenderer.h +++ b/libraries/render-utils/src/TextRenderer.h @@ -12,18 +12,8 @@ #ifndef hifi_TextRenderer_h #define hifi_TextRenderer_h -#include #include -#include #include -#include -#include -#include -#include -#include - -#include -#include // the standard sans serif font family #define SANS_FONT_FAMILY "Helvetica" diff --git a/libraries/render-utils/src/sdf_text.slf b/libraries/render-utils/src/sdf_text.slf index 0cf6e3fb1b..ad5b8fe2c2 100644 --- a/libraries/render-utils/src/sdf_text.slf +++ b/libraries/render-utils/src/sdf_text.slf @@ -10,13 +10,9 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -<@include DeferredBufferWrite.slh@> - uniform sampler2D Font; uniform float Outline; - -// the interpolated normal -varying vec4 interpolatedNormal; +uniform vec4 Color; const float gamma = 2.6; const float smoothing = 100.0; @@ -25,7 +21,7 @@ const float outlineExpansion = 0.2; void main() { // retrieve signed distance - float sdf = texture2D(Font, vTexCoord).r; + float sdf = texture2D(Font, gl_TexCoord[0].xy).r; if (Outline == 1.0f) { if (sdf > interiorCutoff) { sdf = 1.0 - sdf; @@ -35,7 +31,7 @@ void main() { } // 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 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); @@ -47,10 +43,5 @@ void main() { } // final color - packDeferredFragment( - normalize(interpolatedNormal.xyz), - a, - gl_Color.rgb, - gl_FrontMaterial.specular.rgb, - gl_FrontMaterial.shininess); + gl_FragColor = vec4(Color.rgb, a); } \ No newline at end of file diff --git a/libraries/render-utils/src/sdf_text.slv b/libraries/render-utils/src/sdf_text.slv index 157efd7f4b..142d8d41d9 100644 --- a/libraries/render-utils/src/sdf_text.slv +++ b/libraries/render-utils/src/sdf_text.slv @@ -15,10 +15,14 @@ uniform float Offset[2]; - void main() { + gl_TexCoord[0] = gl_MultiTexCoord0; + // standard transform TransformCamera cam = getTransformCamera(); TransformObject obj = getTransformObject(); - <$transformModelToClipPos(cam, obj, gl_Vertex + vec4(Offset[0], Offset[1], 0.0f, 0.0f), gl_Position)$> + <$transformModelToClipPos(cam, obj, gl_Vertex, gl_Position)$> + + gl_Position.x += Offset[0]; + gl_Position.y += Offset[1]; } \ No newline at end of file From 10c2f3f561a241393ca47c72a25d2970ef1d4f4e Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Thu, 28 May 2015 14:51:57 +0200 Subject: [PATCH 12/16] More TextRenderer work --- interface/src/ui/overlays/TextOverlay.cpp | 2 + interface/src/ui/overlays/TextOverlay.h | 2 +- .../src/RenderableTextEntityItem.cpp | 63 ++-- libraries/gpu/src/gpu/GLBackendInput.cpp | 26 +- libraries/gpu/src/gpu/Stream.cpp | 4 +- libraries/gpu/src/gpu/Texture.h | 20 +- libraries/render-utils/src/TextRenderer.cpp | 272 ++++++++++++------ libraries/render-utils/src/TextRenderer.h | 5 +- libraries/render-utils/src/sdf_text.slf | 4 +- libraries/render-utils/src/sdf_text.slv | 5 - 10 files changed, 244 insertions(+), 159 deletions(-) diff --git a/interface/src/ui/overlays/TextOverlay.cpp b/interface/src/ui/overlays/TextOverlay.cpp index 54156d5d2b..e709bbd9fc 100644 --- a/interface/src/ui/overlays/TextOverlay.cpp +++ b/interface/src/ui/overlays/TextOverlay.cpp @@ -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() { diff --git a/interface/src/ui/overlays/TextOverlay.h b/interface/src/ui/overlays/TextOverlay.h index 5b77be0cac..5a715ebfdf 100644 --- a/interface/src/ui/overlays/TextOverlay.h +++ b/interface/src/ui/overlays/TextOverlay.h @@ -60,7 +60,7 @@ public: private: - TextRenderer* _textRenderer = TextRenderer::getInstance(SANS_FONT_FAMILY, _fontSize, DEFAULT_FONT_WEIGHT); + TextRenderer* _textRenderer = nullptr; QString _text; xColor _backgroundColor; diff --git a/libraries/entities-renderer/src/RenderableTextEntityItem.cpp b/libraries/entities-renderer/src/RenderableTextEntityItem.cpp index a3bfd981ec..2eeec77d68 100644 --- a/libraries/entities-renderer/src/RenderableTextEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableTextEntityItem.cpp @@ -20,52 +20,39 @@ #include "RenderableTextEntityItem.h" #include "GLMHelpers.h" - EntityItem* RenderableTextEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { return 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()->bindSimpleProgram(); - DependencyManager::get()->renderQuad(topLeft, bottomRight, glm::vec4(toGlm(getBackgroundColorX()), alpha)); - //DependencyManager::get()->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(); + glm::vec2 bounds = glm::vec2(dimensions.x, dimensions.y); + + 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()->renderQuad(batch, minCorner, maxCorner, backgroundColor); + + float scale = _lineHeight / _textRenderer->getRowHeight(); + transformToTopLeft.setScale(scale); + batch.setModelTransform(transformToTopLeft); + _textRenderer->draw3D(batch, 0.0f, 0.0f, _text, textColor, bounds / scale); } - diff --git a/libraries/gpu/src/gpu/GLBackendInput.cpp b/libraries/gpu/src/gpu/GLBackendInput.cpp index bef3289161..aac7b56bc2 100755 --- a/libraries/gpu/src/gpu/GLBackendInput.cpp +++ b/libraries/gpu/src/gpu/GLBackendInput.cpp @@ -131,23 +131,23 @@ void GLBackend::updateInput() { #endif if (useClientState) { switch (slot) { - case Stream::POSITION: - glVertexPointer(count, type, stride, reinterpret_cast(pointer)); - break; - case Stream::NORMAL: - glNormalPointer(type, stride, reinterpret_cast(pointer)); - break; - case Stream::COLOR: - glColorPointer(count, type, stride, reinterpret_cast(pointer)); - break; - case Stream::TEXCOORD: - glTexCoordPointer(count, type, stride, reinterpret_cast(pointer)); - break; + case Stream::POSITION: + glVertexPointer(count, type, stride, reinterpret_cast(pointer)); + break; + case Stream::NORMAL: + glNormalPointer(type, stride, reinterpret_cast(pointer)); + break; + case Stream::COLOR: + glColorPointer(count, type, stride, reinterpret_cast(pointer)); + break; + case Stream::TEXCOORD: + glTexCoordPointer(count, type, stride, reinterpret_cast(pointer)); + break; }; } else { GLboolean isNormalized = attrib._element.isNormalized(); glVertexAttribPointer(slot, count, type, isNormalized, stride, - reinterpret_cast(pointer)); + reinterpret_cast(pointer)); } (void) CHECK_GL_ERROR(); } diff --git a/libraries/gpu/src/gpu/Stream.cpp b/libraries/gpu/src/gpu/Stream.cpp index e23a730370..634545b4dd 100644 --- a/libraries/gpu/src/gpu/Stream.cpp +++ b/libraries/gpu/src/gpu/Stream.cpp @@ -10,8 +10,8 @@ // #include "Stream.h" - -#include //min max and more + +#include //min max and more using namespace gpu; diff --git a/libraries/gpu/src/gpu/Texture.h b/libraries/gpu/src/gpu/Texture.h index 0d9664f1ab..9036f0f6db 100755 --- a/libraries/gpu/src/gpu/Texture.h +++ b/libraries/gpu/src/gpu/Texture.h @@ -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 TextureViews; +typedef std::vector TextureViews; }; diff --git a/libraries/render-utils/src/TextRenderer.cpp b/libraries/render-utils/src/TextRenderer.cpp index be6a847031..f48dddadbe 100644 --- a/libraries/render-utils/src/TextRenderer.cpp +++ b/libraries/render-utils/src/TextRenderer.cpp @@ -26,6 +26,12 @@ #include "sdf_text_vert.h" #include "sdf_text_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 @@ -44,28 +50,6 @@ void fillBuffer(QBuffer& buffer, T (&t)[N]) { buffer.setData((const char*) t, N); } -struct TextureVertex { - glm::vec2 pos; - glm::vec2 tex; - TextureVertex() {} - TextureVertex(const glm::vec2& pos, const glm::vec2& tex) : pos(pos), tex(tex) {} - TextureVertex(const QPointF& pos, const QPointF& tex) : pos(pos.x(), pos.y()), tex(tex.x(), tex.y()) {} -}; - -struct QuadBuilder { - TextureVertex vertices[4]; - QuadBuilder(const QRectF& r, const QRectF& tr) { - vertices[0] = TextureVertex(r.bottomLeft(), tr.topLeft()); - vertices[1] = TextureVertex(r.bottomRight(), tr.topRight()); - vertices[2] = TextureVertex(r.topLeft(), tr.bottomLeft()); - vertices[3] = TextureVertex(r.topRight(), tr.bottomRight()); - } -}; - -// FIXME support the shadow effect, or remove it from the API -// FIXME figure out how to improve the anti-aliasing on the -// interior of the outline fonts - // stores the font metrics for a single character struct Glyph { QChar c; @@ -76,7 +60,8 @@ struct Glyph { float d; // xadvance - adjusts character positioning size_t indexOffset; - QRectF bounds() const { return glmToRect(offset, size); } + // 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); @@ -93,6 +78,56 @@ void Glyph::read(QIODevice& in) { 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 Glyph& glyph, const glm::vec2& offset) : + QuadBuilder(offset + glyph.offset - glm::vec2(0.0f, glyph.size.y), glyph.size, + glyph.texOffset, glyph.texSize) {} + +}; + +QDebug operator<<(QDebug debug, glm::vec2& value) { + debug << value.x << value.y; + return debug; +} + +QDebug operator<<(QDebug debug, glm::vec3& value) { + debug << value.x << value.y << value.z; + return debug; +} + +QDebug operator<<(QDebug debug, TextureVertex& value) { + debug << "Pos:" << value.pos << ", Tex:" << value.tex; + return debug; +} + +QDebug operator<<(QDebug debug, QuadBuilder& value) { + debug << '\n' << value.vertices[0] + << '\n' << value.vertices[1] + << '\n' << value.vertices[2] + << '\n' << value.vertices[3]; + return debug; +} + class Font { public: Font(); @@ -100,6 +135,7 @@ public: 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, @@ -113,6 +149,8 @@ private: const Glyph& 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 @@ -129,14 +167,20 @@ private: float _descent = 0.0f; float _spaceWidth = 0.0f; + bool _initialized = false; + // gpu structures - static const unsigned int VERTEX_BUFFER_CHANNEL = 0; gpu::PipelinePointer _pipeline; gpu::TexturePointer _texture; gpu::Stream::FormatPointer _format; - gpu::BufferPointer _vertices; + 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; @@ -283,17 +327,6 @@ void Font::read(QIODevice& in) { if (!image.loadFromData(in.readAll(), "PNG")) { qFatal("Failed to read SDFF image"); } - gpu::Element formatGPU = gpu::Element(gpu::VEC3, gpu::UINT8, gpu::RGB); - gpu::Element formatMip = gpu::Element(gpu::VEC3, gpu::UINT8, gpu::RGB); - if (image.hasAlphaChannel()) { - formatGPU = gpu::Element(gpu::VEC4, gpu::UINT8, gpu::RGBA); - formatMip = gpu::Element(gpu::VEC4, gpu::UINT8, gpu::BGRA); - } - _texture = gpu::TexturePointer( - gpu::Texture::create2D(formatGPU, image.width(), image.height(), - gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR))); - _texture->assignStoredMip(0, formatMip, image.byteCount(), image.constBits()); - _texture->autoGenerateMips(-1); _glyphs.clear(); glm::vec2 imageSize = toGlm(image.size()); @@ -305,61 +338,119 @@ void Font::read(QIODevice& in) { _glyphs[g.c] = g; }; - // Setup render pipeline - auto vertexShader = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(sdf_text_vert))); - auto pixelShader = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(sdf_text_frag))); - gpu::ShaderPointer program = gpu::ShaderPointer(gpu::Shader::createProgram(vertexShader, pixelShader)); + qDebug() << _family << "size" << image.size(); + qDebug() << _family << "format" << image.format(); + image = image.convertToFormat(QImage::Format_RGBA8888); + qDebug() << _family << "size" << image.size(); + qDebug() << _family << "format" << image.format(); - gpu::Shader::BindingSet slotBindings; - slotBindings.insert(gpu::Shader::Binding("Outline", 0)); - slotBindings.insert(gpu::Shader::Binding("Offset", 1)); - slotBindings.insert(gpu::Shader::Binding("Color", 2)); - gpu::Shader::makeProgram(*program, slotBindings); - - gpu::StatePointer state = gpu::StatePointer(new gpu::State()); - 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)); - - // Setup rendering structures - static const int OFFSET = offsetof(TextureVertex, tex); - _format = gpu::Stream::FormatPointer(new gpu::Stream::Format()); - _format->setAttribute(gpu::Stream::POSITION, VERTEX_BUFFER_CHANNEL, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV), 0); - _format->setAttribute(gpu::Stream::TEXCOORD, VERTEX_BUFFER_CHANNEL, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV), OFFSET); + gpu::Element formatGPU = gpu::Element(gpu::VEC3, gpu::UINT8, gpu::RGB); + gpu::Element formatMip = gpu::Element(gpu::VEC3, gpu::UINT8, gpu::RGB); + if (image.hasAlphaChannel()) { + formatGPU = gpu::Element(gpu::VEC4, gpu::UINT8, gpu::RGBA); + formatMip = gpu::Element(gpu::VEC4, gpu::UINT8, gpu::BGRA); + } + _texture = gpu::TexturePointer(gpu::Texture::create2D(formatGPU, image.width(), image.height(), + gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR))); + _texture->assignStoredMip(0, formatMip, image.byteCount(), image.constBits()); + _texture->autoGenerateMips(-1); +} + +#include +void Font::setupGPU() { + if (!_initialized) { + _initialized = true; + + // Setup render pipeline + auto vertexShader = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(sdf_text_vert))); + auto pixelShader = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(sdf_text_frag))); + gpu::ShaderPointer program = gpu::ShaderPointer(gpu::Shader::createProgram(vertexShader, pixelShader)); + + gpu::Shader::BindingSet slotBindings; + gpu::Shader::makeProgram(*program, slotBindings); + + _fontLoc = program->getTextures().findLocation("Font"); + _outlineLoc = program->getUniforms().findLocation("Outline"); + _colorLoc = program->getUniforms().findLocation("Color"); + + + auto f = [&] (QString str, const gpu::Shader::SlotSet& set) { + if (set.size() == 0) { + return; + } + qDebug() << str << set.size(); + for (auto slot : set) { + qDebug() << " " << QString::fromStdString(slot._name) << slot._location; + } + }; + f("getUniforms:", program->getUniforms()); + f("getBuffers:", program->getBuffers()); + f("getTextures:", program->getTextures()); + f("getSamplers:", program->getSamplers()); + f("getInputs:", program->getInputs()); + f("getOutputs:", program->getOutputs()); + + qDebug() << "Texture:" << _texture.get(); + + 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(glm::vec3) == 3 * sizeof(float)); + assert(sizeof(TextureVertex) == sizeof(glm::vec2) + 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::drawString(gpu::Batch& batch, float x, float y, const QString& str, const glm::vec4& color, TextRenderer::EffectType effectType, const glm::vec2& bounds) { - // Top left of text - glm::vec2 advance = glm::vec2(0.0f, 0.0f); + if (str == "") { + return; + } if (str != _lastStringRendered || bounds != _lastBounds) { - _vertices = gpu::BufferPointer(new gpu::Buffer()); + _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 isNewLine = (token == QString('\n')); bool forceNewLine = false; // Handle wrapping - if (!isNewLine && (bounds.x != -1) && (advance.x + computeExtent(token).x)) { + if (!isNewLine && (bounds.x != -1) && (advance.x + computeExtent(token).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(0.0f, advance.y + _rowHeight); + advance = glm::vec2(0.0f, 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 > bounds.y)) { + if ((bounds.y != -1) && (advance.y - _fontSize < -bounds.y)) { // We are out of the y bound, stop drawing break; } @@ -369,10 +460,8 @@ void Font::drawString(gpu::Batch& batch, float x, float y, const QString& str, c for (auto c : token) { auto glyph = _glyphs[c]; - // Build translated quad and add it to the buffer - QuadBuilder qd(glyph.bounds().translated(advance.x, advance.y), - glyph.textureBounds()); - _vertices->append(sizeof(qd.vertices), (const gpu::Byte*)qd.vertices); + QuadBuilder qd(glyph, advance - glm::vec2(0.0f, _fontSize)); + _verticesBuffer->append(sizeof(QuadBuilder), (const gpu::Byte*)&qd); _numVertices += 4; // Advance by glyph size @@ -385,17 +474,15 @@ void Font::drawString(gpu::Batch& batch, float x, float y, const QString& str, c } } + setupGPU(); batch.setPipeline(_pipeline); - batch.setUniformTexture(2, _texture); - batch._glUniform1f(0, (effectType == TextRenderer::OUTLINE_EFFECT) ? 1.0f : 0.0f); - batch._glUniform2f(1, x, y); - batch._glUniform4fv(2, 4, (const GLfloat*)&color); - QRectF rect(0.0f, 0.0f, 1.0f, 1.0f); - QuadBuilder qd(rect, rect); - _vertices->append(sizeof(QuadBuilder), (const gpu::Byte*)qd.vertices); + batch.setUniformTexture(_fontLoc, _texture); + batch._glUniform1f(_outlineLoc, (effectType == TextRenderer::OUTLINE_EFFECT) ? 1.0f : 0.0f); + batch._glUniform4fv(_colorLoc, 1, (const GLfloat*)&color); + batch.setInputFormat(_format); - batch.setInputBuffer(VERTEX_BUFFER_CHANNEL, _vertices); - batch.draw(gpu::QUADS, _numVertices); + batch.setInputBuffer(0, _verticesBuffer, 0, _format->getChannels().at(0)._stride); + batch.draw(gpu::QUADS, _numVertices, 0); } TextRenderer* TextRenderer::getInstance(const char* family, float pointSize, @@ -413,7 +500,7 @@ TextRenderer::TextRenderer(const char* family, float pointSize, int weight, bool _effectType(effect), _effectThickness(effectThickness), _pointSize(pointSize), - _color(color), + _color(toGlm(color)), _font(loadFont(family)) { if (!_font) { qWarning() << "Unable to load font with family " << family; @@ -438,24 +525,37 @@ glm::vec2 TextRenderer::computeExtent(const QString& str) const { return glm::vec2(0.1f,0.1f); } +float TextRenderer::getRowHeight() const { + if (_font) { + return _font->getRowHeight(); + } + return 1.0f; +} + void TextRenderer::draw(float x, float y, const QString& str, const glm::vec4& color, const glm::vec2& bounds) { // The font does all the OpenGL work if (_font) { -// gpu::Batch batch; -// draw(batch, x, y, str, color, bounds); -// gpu::GLBackend::renderBatch(batch, true); + float scale = (_pointSize / DEFAULT_POINT_SIZE) * 0.25f; + glPushMatrix(); + gpu::Batch batch; + Transform transform; + transform.setTranslation(glm::vec3(x, y, 0.0f)); + transform.setScale(glm::vec3(scale, -scale, scale)); + batch.setModelTransform(transform); + draw3D(batch, 0.0f, 0.0f, str, color, bounds); + gpu::GLBackend::renderBatch(batch, true); + glPopMatrix(); } } -void TextRenderer::draw(gpu::Batch& batch, float x, float y, const QString& str, const glm::vec4& color, +void TextRenderer::draw3D(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 = toGlm(_color); + actualColor = _color; } - _font->drawString(batch, x, y, str, actualColor, _effectType, bounds); } } diff --git a/libraries/render-utils/src/TextRenderer.h b/libraries/render-utils/src/TextRenderer.h index 7bd0781f96..0c7faa9e6b 100644 --- a/libraries/render-utils/src/TextRenderer.h +++ b/libraries/render-utils/src/TextRenderer.h @@ -50,10 +50,11 @@ public: ~TextRenderer(); glm::vec2 computeExtent(const QString& str) const; + float getRowHeight() const; void draw(float x, float y, const QString& str, const glm::vec4& color = glm::vec4(-1.0f), const glm::vec2& bounds = glm::vec2(-1.0f)); - void draw(gpu::Batch& batch, float x, float y, const QString& str, const glm::vec4& color = glm::vec4(-1.0f), + void draw3D(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: @@ -69,7 +70,7 @@ private: const float _pointSize; // text color - const QColor _color; + const glm::vec4 _color; Font* _font; }; diff --git a/libraries/render-utils/src/sdf_text.slf b/libraries/render-utils/src/sdf_text.slf index ad5b8fe2c2..3980045d08 100644 --- a/libraries/render-utils/src/sdf_text.slf +++ b/libraries/render-utils/src/sdf_text.slf @@ -21,7 +21,7 @@ const float outlineExpansion = 0.2; void main() { // retrieve signed distance - float sdf = texture2D(Font, gl_TexCoord[0].xy).r; + float sdf = texture2D(Font, gl_TexCoord[0].xy).g; if (Outline == 1.0f) { if (sdf > interiorCutoff) { sdf = 1.0 - sdf; @@ -41,7 +41,7 @@ void main() { if (a < 0.01) { discard; } - + // final color gl_FragColor = vec4(Color.rgb, a); } \ No newline at end of file diff --git a/libraries/render-utils/src/sdf_text.slv b/libraries/render-utils/src/sdf_text.slv index 142d8d41d9..f7c35a257c 100644 --- a/libraries/render-utils/src/sdf_text.slv +++ b/libraries/render-utils/src/sdf_text.slv @@ -13,8 +13,6 @@ <$declareStandardTransform()$> -uniform float Offset[2]; - void main() { gl_TexCoord[0] = gl_MultiTexCoord0; @@ -22,7 +20,4 @@ void main() { TransformCamera cam = getTransformCamera(); TransformObject obj = getTransformObject(); <$transformModelToClipPos(cam, obj, gl_Vertex, gl_Position)$> - - gl_Position.x += Offset[0]; - gl_Position.y += Offset[1]; } \ No newline at end of file From c4ab18736de29b3cb7fea260c603c5818c687d82 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Thu, 28 May 2015 15:20:40 +0200 Subject: [PATCH 13/16] Restore TextRenderer and sdf_text --- libraries/render-utils/src/TextRenderer.cpp | 668 ++++++++++---------- libraries/render-utils/src/TextRenderer.h | 34 +- libraries/render-utils/src/sdf_text.slf | 48 +- libraries/render-utils/src/sdf_text.slv | 17 +- 4 files changed, 394 insertions(+), 373 deletions(-) diff --git a/libraries/render-utils/src/TextRenderer.cpp b/libraries/render-utils/src/TextRenderer.cpp index f48dddadbe..87cf9b2728 100644 --- a/libraries/render-utils/src/TextRenderer.cpp +++ b/libraries/render-utils/src/TextRenderer.cpp @@ -9,47 +9,55 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include "TextRenderer.h" #include -#include - -#include -#include +#include +#include +#include #include +#include #include +// FIXME, decouple from the GL headers +#include +#include +#include +#include + +#include +#include + +#include "gpu/GLBackend.h" +#include "gpu/Stream.h" + #include "GLMHelpers.h" #include "MatrixStack.h" #include "RenderUtilsLogging.h" +#include "TextRenderer.h" #include "sdf_text_vert.h" #include "sdf_text_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 -void readStream(QIODevice& in, T& t) { +void readStream(QIODevice & in, T & t) { in.read((char*) &t, sizeof(t)); } template -void readStream(QIODevice& in, T (&t)[N]) { +void readStream(QIODevice & in, T (&t)[N]) { in.read((char*) t, N); } template -void fillBuffer(QBuffer& buffer, T (&t)[N]) { +void fillBuffer(QBuffer & buffer, T (&t)[N]) { buffer.setData((const char*) t, N); } +// 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; @@ -60,14 +68,13 @@ struct Glyph { 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); } + QRectF bounds() const; + QRectF textureBounds(const glm::vec2 & textureSize) const; - void read(QIODevice& in); + void read(QIODevice & in); }; -void Glyph::read(QIODevice& in) { +void Glyph::read(QIODevice & in) { uint16_t charcode; readStream(in, charcode); c = charcode; @@ -78,112 +85,64 @@ void Glyph::read(QIODevice& in) { 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 Glyph& glyph, const glm::vec2& offset) : - QuadBuilder(offset + glyph.offset - glm::vec2(0.0f, glyph.size.y), glyph.size, - glyph.texOffset, glyph.texSize) {} - -}; - -QDebug operator<<(QDebug debug, glm::vec2& value) { - debug << value.x << value.y; - return debug; -} - -QDebug operator<<(QDebug debug, glm::vec3& value) { - debug << value.x << value.y << value.z; - return debug; -} - -QDebug operator<<(QDebug debug, TextureVertex& value) { - debug << "Pos:" << value.pos << ", Tex:" << value.tex; - return debug; -} - -QDebug operator<<(QDebug debug, QuadBuilder& value) { - debug << '\n' << value.vertices[0] - << '\n' << value.vertices[1] - << '\n' << value.vertices[2] - << '\n' << value.vertices[3]; - return debug; -} +const float DEFAULT_POINT_SIZE = 12; class Font { public: + Font(); - void read(QIODevice& path); + using TexturePtr = QSharedPointer < QOpenGLTexture >; + using VertexArrayPtr = QSharedPointer< QOpenGLVertexArrayObject >; + using ProgramPtr = QSharedPointer < QOpenGLShaderProgram >; + using BufferPtr = QSharedPointer < QOpenGLBuffer >; - 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, + // maps characters to cached glyph info + // HACK... the operator[] const for QHash returns a + // copy of the value, not a const value reference, so + // we declare the hash as mutable in order to avoid such + // copies + mutable QHash _glyphs; + + // the id of the glyph texture to which we're currently writing + GLuint _currentTextureID; + + int _pointSize; + + // the height of the current row of characters + int _rowHeight; + + QString _family; + float _fontSize { 0 }; + float _leading { 0 }; + float _ascent { 0 }; + float _descent { 0 }; + float _spaceWidth { 0 }; + + BufferPtr _vertices; + BufferPtr _indices; + TexturePtr _texture; + VertexArrayPtr _vao; + QImage _image; + ProgramPtr _program; + + const Glyph & getGlyph(const QChar & c) const; + void read(QIODevice& path); + // Initialize the OpenGL structures + void setupGL(); + + glm::vec2 computeExtent(const QString & str) const; + + glm::vec2 computeTokenExtent(const QString & str) const; + + glm::vec2 drawString(float x, float y, const QString & str, const glm::vec4& color, TextRenderer::EffectType effectType, const glm::vec2& bound); private: - QStringList tokenizeForWrapping(const QString& str) const; - QStringList splitLines(const QString& str) const; - glm::vec2 computeTokenExtent(const QString& str) const; - - const Glyph& getGlyph(const QChar& c) const; - - 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 _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; + QStringList tokenizeForWrapping(const QString & str) const; + + bool _initialized; }; static QHash LOADED_FONTS; @@ -230,7 +189,7 @@ Font* loadFont(const QString& family) { return LOADED_FONTS[family]; } -Font::Font() { +Font::Font() : _initialized(false) { static bool fontResourceInitComplete = false; if (!fontResourceInitComplete) { Q_INIT_RESOURCE(fonts); @@ -239,50 +198,13 @@ Font::Font() { } // NERD RAGE: why doesn't QHash have a 'const T & operator[] const' member -const Glyph& Font::getGlyph(const QChar& c) const { +const Glyph & Font::getGlyph(const QChar & c) const { if (!_glyphs.contains(c)) { return _glyphs[QChar('?')]; } return _glyphs[c]; } -QStringList Font::splitLines(const QString& str) const { - return str.split('\n'); -} - -QStringList Font::tokenizeForWrapping(const QString& str) const { - QStringList tokens; - for(auto line : splitLines(str)) { - if (!tokens.empty()) { - tokens << QString('\n'); - } - tokens << line.split(' '); - } - return tokens; -} - -glm::vec2 Font::computeTokenExtent(const QString& token) const { - glm::vec2 advance(0, _fontSize); - foreach(QChar c, token) { - Q_ASSERT(c != '\n'); - advance.x += (c == ' ') ? _spaceWidth : getGlyph(c).d; - } - return advance; -} - -glm::vec2 Font::computeExtent(const QString& str) const { - glm::vec2 extent = glm::vec2(0.0f, 0.0f); - - QStringList tokens = splitLines(str); - foreach(const QString& token, tokens) { - glm::vec2 tokenExtent = computeTokenExtent(token); - extent.x = std::max(tokenExtent.x, extent.x); - } - extent.y = tokens.count() * _rowHeight; - - return extent; -} - void Font::read(QIODevice& in) { uint8_t header[4]; readStream(in, header); @@ -310,7 +232,7 @@ void Font::read(QIODevice& in) { readStream(in, _descent); readStream(in, _spaceWidth); _fontSize = _ascent + _descent; - _rowHeight = _fontSize + _leading; + _rowHeight = _fontSize + _descent; // Read character count uint16_t count; @@ -318,171 +240,266 @@ void Font::read(QIODevice& in) { // read metrics data for each character QVector glyphs(count); // std::for_each instead of Qt foreach because we need non-const references - std::for_each(glyphs.begin(), glyphs.end(), [&](Glyph& g) { + std::for_each(glyphs.begin(), glyphs.end(), [&](Glyph & g) { g.read(in); }); // read image data - QImage image; - if (!image.loadFromData(in.readAll(), "PNG")) { + 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, + glm::vec2 imageSize = toGlm(_image.size()); g.texSize /= imageSize; g.texOffset /= imageSize; // store in the character to glyph hash _glyphs[g.c] = g; }; - - qDebug() << _family << "size" << image.size(); - qDebug() << _family << "format" << image.format(); - image = image.convertToFormat(QImage::Format_RGBA8888); - qDebug() << _family << "size" << image.size(); - qDebug() << _family << "format" << image.format(); - - gpu::Element formatGPU = gpu::Element(gpu::VEC3, gpu::UINT8, gpu::RGB); - gpu::Element formatMip = gpu::Element(gpu::VEC3, gpu::UINT8, gpu::RGB); - if (image.hasAlphaChannel()) { - formatGPU = gpu::Element(gpu::VEC4, gpu::UINT8, gpu::RGBA); - formatMip = gpu::Element(gpu::VEC4, gpu::UINT8, gpu::BGRA); - } - _texture = gpu::TexturePointer(gpu::Texture::create2D(formatGPU, image.width(), image.height(), - gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR))); - _texture->assignStoredMip(0, formatMip, image.byteCount(), image.constBits()); - _texture->autoGenerateMips(-1); } -#include -void Font::setupGPU() { - if (!_initialized) { - _initialized = true; - - // Setup render pipeline - auto vertexShader = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(sdf_text_vert))); - auto pixelShader = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(sdf_text_frag))); - gpu::ShaderPointer program = gpu::ShaderPointer(gpu::Shader::createProgram(vertexShader, pixelShader)); - - gpu::Shader::BindingSet slotBindings; - gpu::Shader::makeProgram(*program, slotBindings); - - _fontLoc = program->getTextures().findLocation("Font"); - _outlineLoc = program->getUniforms().findLocation("Outline"); - _colorLoc = program->getUniforms().findLocation("Color"); - - - auto f = [&] (QString str, const gpu::Shader::SlotSet& set) { - if (set.size() == 0) { - return; - } - qDebug() << str << set.size(); - for (auto slot : set) { - qDebug() << " " << QString::fromStdString(slot._name) << slot._location; - } - }; - f("getUniforms:", program->getUniforms()); - f("getBuffers:", program->getBuffers()); - f("getTextures:", program->getTextures()); - f("getSamplers:", program->getSamplers()); - f("getInputs:", program->getInputs()); - f("getOutputs:", program->getOutputs()); - - qDebug() << "Texture:" << _texture.get(); - - 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(glm::vec3) == 3 * sizeof(float)); - assert(sizeof(TextureVertex) == sizeof(glm::vec2) + 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); +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); } -void Font::drawString(gpu::Batch& batch, float x, float y, const QString& str, const glm::vec4& color, - TextRenderer::EffectType effectType, const glm::vec2& bounds) { - if (str == "") { +QRectF Glyph::textureBounds(const glm::vec2 & textureSize) const { + return glmToRect(texOffset, texSize); +} + +void Font::setupGL() { + if (_initialized) { 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 > 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(0.0f, advance.y - _rowHeight); + _initialized = true; - 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 + _texture = TexturePtr( + new QOpenGLTexture(_image, QOpenGLTexture::GenerateMipMaps)); + _program = ProgramPtr(new QOpenGLShaderProgram()); + if (!_program->create()) { + qFatal("Could not create text shader"); + } + if (!_program->addShaderFromSourceCode(QOpenGLShader::Vertex, sdf_text_vert) || // + !_program->addShaderFromSourceCode(QOpenGLShader::Fragment, sdf_text_frag) || // + !_program->link()) { + qFatal("%s", _program->log().toLocal8Bit().constData()); + } + + std::vector vertexData; + std::vector indexData; + vertexData.reserve(_glyphs.size() * 4); + std::for_each(_glyphs.begin(), _glyphs.end(), [&](Glyph & m) { + GLuint index = (GLuint)vertexData.size(); + + QRectF bounds = m.bounds(); + QRectF texBounds = m.textureBounds(toGlm(_image.size())); + QuadBuilder qb(bounds, texBounds); + for (int i = 0; i < 4; ++i) { + vertexData.push_back(qb.vertices[i]); + } + + m.indexOffset = indexData.size() * sizeof(GLuint); + // FIXME use triangle strips + primitive restart index + indexData.push_back(index + 0); + indexData.push_back(index + 1); + indexData.push_back(index + 2); + indexData.push_back(index + 0); + indexData.push_back(index + 2); + indexData.push_back(index + 3); + }); + + _vao = VertexArrayPtr(new QOpenGLVertexArrayObject()); + _vao->create(); + _vao->bind(); + + _vertices = BufferPtr(new QOpenGLBuffer(QOpenGLBuffer::VertexBuffer)); + _vertices->create(); + _vertices->bind(); + _vertices->allocate(&vertexData[0], + sizeof(TextureVertex) * vertexData.size()); + _indices = BufferPtr(new QOpenGLBuffer(QOpenGLBuffer::IndexBuffer)); + _indices->create(); + _indices->bind(); + _indices->allocate(&indexData[0], sizeof(GLuint) * indexData.size()); + + GLsizei stride = (GLsizei) sizeof(TextureVertex); + void* offset = (void*) offsetof(TextureVertex, tex); + int posLoc = _program->attributeLocation("Position"); + int texLoc = _program->attributeLocation("TexCoord"); + glEnableVertexAttribArray(posLoc); + glVertexAttribPointer(posLoc, 2, GL_FLOAT, false, stride, nullptr); + glEnableVertexAttribArray(texLoc); + glVertexAttribPointer(texLoc, 2, GL_FLOAT, false, stride, offset); + _vao->release(); +} + +// FIXME there has to be a cleaner way of doing this +QStringList Font::tokenizeForWrapping(const QString & str) const { + QStringList result; + foreach(const QString & token1, str.split(" ")) { + bool lineFeed = false; + if (token1.isEmpty()) { + result << token1; + continue; + } + foreach(const QString & token2, token1.split("\n")) { + if (lineFeed) { + result << "\n"; + } + if (token2.size()) { + result << token2; + } + lineFeed = true; + } + } + return result; +} + + +glm::vec2 Font::computeTokenExtent(const QString & token) const { + glm::vec2 advance(0, _rowHeight - _descent); + foreach(QChar c, token) { + assert(c != ' ' && c != '\n'); + const Glyph & m = getGlyph(c); + advance.x += m.d; + } + return advance; +} + + +glm::vec2 Font::computeExtent(const QString & str) const { + glm::vec2 extent(0, _rowHeight - _descent); + // FIXME, come up with a better method of splitting text + // that will allow wrapping but will preserve things like + // tabs or consecutive spaces + bool firstTokenOnLine = true; + float lineWidth = 0.0f; + QStringList tokens = tokenizeForWrapping(str); + foreach(const QString & token, tokens) { + if (token == "\n") { + extent.x = std::max(lineWidth, extent.x); + lineWidth = 0.0f; + extent.y += _rowHeight; + firstTokenOnLine = true; + continue; + } + if (!firstTokenOnLine) { + lineWidth += _spaceWidth; + } + lineWidth += computeTokenExtent(token).x; + firstTokenOnLine = false; + } + extent.x = std::max(lineWidth, extent.x); + return extent; +} + +// FIXME support the maxWidth parameter and allow the text to automatically wrap +// even without explicit line feeds. +glm::vec2 Font::drawString(float x, float y, const QString & str, + const glm::vec4& color, TextRenderer::EffectType effectType, + const glm::vec2& bounds) { + + setupGL(); + + // Stores how far we've moved from the start of the string, in DTP units + glm::vec2 advance(0, -_rowHeight - _descent); + + _program->bind(); + _program->setUniformValue("Color", color.r, color.g, color.b, color.a); + _program->setUniformValue("Projection", + fromGlm(MatrixStack::projection().top())); + if (effectType == TextRenderer::OUTLINE_EFFECT) { + _program->setUniformValue("Outline", true); + } + // Needed? + glEnable(GL_TEXTURE_2D); + _texture->bind(); + _vao->bind(); + + MatrixStack & mv = MatrixStack::modelview(); + // scale the modelview into font units + mv.translate(glm::vec3(0, _ascent, 0)); + foreach(const QString & token, tokenizeForWrapping(str)) { + if (token == "\n") { + advance.x = 0.0f; + advance.y -= _rowHeight; + // If we've wrapped right out of the bounds, then we're + // done with rendering the tokens + if (bounds.y > 0 && std::abs(advance.y) > bounds.y) { + break; + } + continue; + } + + glm::vec2 tokenExtent = computeTokenExtent(token); + if (bounds.x > 0 && advance.x > 0) { + // We check if we'll be out of bounds + if (advance.x + tokenExtent.x >= bounds.x) { + // We're out of bounds, so wrap to the next line + advance.x = 0.0f; + advance.y -= _rowHeight; + // If we've wrapped right out of the bounds, then we're + // done with rendering the tokens + if (bounds.y > 0 && std::abs(advance.y) > bounds.y) { break; } } - if ((bounds.y != -1) && (advance.y - _fontSize < -bounds.y)) { - // We are out of the y bound, stop drawing - break; - } - - // Draw the token - if (!isNewLine) { - for (auto c : token) { - auto glyph = _glyphs[c]; - - 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; - } } + + 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(); + // FIXME, needed? + // glDisable(GL_TEXTURE_2D); - setupGPU(); - batch.setPipeline(_pipeline); - batch.setUniformTexture(_fontLoc, _texture); - batch._glUniform1f(_outlineLoc, (effectType == TextRenderer::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); + return advance; } TextRenderer* TextRenderer::getInstance(const char* family, float pointSize, @@ -495,13 +512,10 @@ TextRenderer* TextRenderer::getInstance(const char* family, float pointSize, 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(toGlm(color)), - _font(loadFont(family)) { +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"); @@ -517,46 +531,40 @@ TextRenderer::TextRenderer(const char* family, float pointSize, int weight, bool TextRenderer::~TextRenderer() { } -glm::vec2 TextRenderer::computeExtent(const QString& str) const { +glm::vec2 TextRenderer::computeExtent(const QString & str) const { + float scale = (_pointSize / DEFAULT_POINT_SIZE) * 0.25f; if (_font) { - float scale = (_pointSize / DEFAULT_POINT_SIZE) * 0.25f; return _font->computeExtent(str) * scale; } return glm::vec2(0.1f,0.1f); } -float TextRenderer::getRowHeight() const { - if (_font) { - return _font->getRowHeight(); +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); } - return 1.0f; -} -void TextRenderer::draw(float x, float y, const QString& str, const glm::vec4& color, const glm::vec2& bounds) { - // The font does all the OpenGL work - if (_font) { - float scale = (_pointSize / DEFAULT_POINT_SIZE) * 0.25f; - glPushMatrix(); - gpu::Batch batch; - Transform transform; - transform.setTranslation(glm::vec3(x, y, 0.0f)); - transform.setScale(glm::vec3(scale, -scale, scale)); - batch.setModelTransform(transform); - draw3D(batch, 0.0f, 0.0f, str, color, bounds); - gpu::GLBackend::renderBatch(batch, true); - glPopMatrix(); - } -} + float scale = (_pointSize / DEFAULT_POINT_SIZE) * 0.25f; + glm::vec2 result; -void TextRenderer::draw3D(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; + 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); } - _font->drawString(batch, x, y, str, actualColor, _effectType, bounds); - } + }); + return result.x; } diff --git a/libraries/render-utils/src/TextRenderer.h b/libraries/render-utils/src/TextRenderer.h index 0c7faa9e6b..85bb18cec9 100644 --- a/libraries/render-utils/src/TextRenderer.h +++ b/libraries/render-utils/src/TextRenderer.h @@ -12,8 +12,21 @@ #ifndef hifi_TextRenderer_h #define hifi_TextRenderer_h +#include #include +#include #include +#include +#include +#include +#include +#include + +#include +#include + +// a special "character" that renders as a solid block +const char SOLID_BLOCK_CHAR = 127; // the standard sans serif font family #define SANS_FONT_FAMILY "Helvetica" @@ -33,9 +46,6 @@ #define INCONSOLATA_FONT_WEIGHT QFont::Bold #endif -namespace gpu { -class Batch; -} class Font; // TextRenderer is actually a fairly thin wrapper around a Font class @@ -49,13 +59,13 @@ public: ~TextRenderer(); - glm::vec2 computeExtent(const QString& str) const; - float getRowHeight() const; - - void draw(float x, float y, const QString& str, const glm::vec4& color = glm::vec4(-1.0f), - const glm::vec2& bounds = glm::vec2(-1.0f)); - void draw3D(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)); + 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, @@ -70,9 +80,9 @@ private: const float _pointSize; // text color - const glm::vec4 _color; + const QColor _color; - Font* _font; + Font * _font; }; diff --git a/libraries/render-utils/src/sdf_text.slf b/libraries/render-utils/src/sdf_text.slf index 3980045d08..1affbe4c57 100644 --- a/libraries/render-utils/src/sdf_text.slf +++ b/libraries/render-utils/src/sdf_text.slf @@ -11,8 +11,10 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html uniform sampler2D Font; -uniform float Outline; uniform vec4 Color; +uniform bool Outline; + +varying vec2 vTexCoord; const float gamma = 2.6; const float smoothing = 100.0; @@ -20,28 +22,28 @@ 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; + // 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); - // final color - gl_FragColor = vec4(Color.rgb, a); + // gamma correction for linear attenuation + a = pow(a, 1.0 / gamma); + + if (a < 0.01) { + discard; + } + + // final color + gl_FragColor = vec4(Color.rgb, a); } \ No newline at end of file diff --git a/libraries/render-utils/src/sdf_text.slv b/libraries/render-utils/src/sdf_text.slv index f7c35a257c..27db1c4985 100644 --- a/libraries/render-utils/src/sdf_text.slv +++ b/libraries/render-utils/src/sdf_text.slv @@ -9,15 +9,16 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -<@include gpu/Transform.slh@> -<$declareStandardTransform()$> +uniform mat4 Projection; +uniform mat4 ModelView; + +attribute vec2 Position; +attribute vec2 TexCoord; + +varying vec2 vTexCoord; void main() { - gl_TexCoord[0] = gl_MultiTexCoord0; - - // standard transform - TransformCamera cam = getTransformCamera(); - TransformObject obj = getTransformObject(); - <$transformModelToClipPos(cam, obj, gl_Vertex, gl_Position)$> + vTexCoord = TexCoord; + gl_Position = Projection * ModelView * vec4(Position, 0.0, 1.0); } \ No newline at end of file From 12d75481e58d04263cb9ec820a61ff7e7ccb154b Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Thu, 28 May 2015 15:43:16 +0200 Subject: [PATCH 14/16] Introducing TextRenderer3D --- .../src/RenderableTextEntityItem.cpp | 5 +- .../src/RenderableTextEntityItem.h | 4 +- libraries/render-utils/src/TextRenderer3D.cpp | 545 ++++++++++++++++++ libraries/render-utils/src/TextRenderer3D.h | 77 +++ libraries/render-utils/src/sdf_text3D.slf | 47 ++ libraries/render-utils/src/sdf_text3D.slv | 23 + 6 files changed, 697 insertions(+), 4 deletions(-) create mode 100644 libraries/render-utils/src/TextRenderer3D.cpp create mode 100644 libraries/render-utils/src/TextRenderer3D.h create mode 100644 libraries/render-utils/src/sdf_text3D.slf create mode 100644 libraries/render-utils/src/sdf_text3D.slv diff --git a/libraries/entities-renderer/src/RenderableTextEntityItem.cpp b/libraries/entities-renderer/src/RenderableTextEntityItem.cpp index 2eeec77d68..1e6a39d2a8 100644 --- a/libraries/entities-renderer/src/RenderableTextEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableTextEntityItem.cpp @@ -32,7 +32,8 @@ void RenderableTextEntityItem::render(RenderArgs* args) { glm::vec4 textColor = glm::vec4(toGlm(getTextColorX()), 1.0f); glm::vec4 backgroundColor = glm::vec4(toGlm(getBackgroundColorX()), 1.0f); glm::vec3 dimensions = getDimensions(); - glm::vec2 bounds = glm::vec2(dimensions.x, dimensions.y); + float leftMargin = 0.1f, topMargin = 0.1f; + glm::vec2 bounds = glm::vec2(dimensions.x - 2 * leftMargin, dimensions.y - 2 * topMargin); Transform transformToTopLeft = getTransformToCenter(); transformToTopLeft.postTranslate(glm::vec3(-0.5f, 0.5f, 0.0f)); // Go to the top left @@ -51,7 +52,7 @@ void RenderableTextEntityItem::render(RenderArgs* args) { float scale = _lineHeight / _textRenderer->getRowHeight(); transformToTopLeft.setScale(scale); batch.setModelTransform(transformToTopLeft); - _textRenderer->draw3D(batch, 0.0f, 0.0f, _text, textColor, bounds / scale); + _textRenderer->draw(batch, leftMargin, topMargin, _text, textColor, bounds / scale); } diff --git a/libraries/entities-renderer/src/RenderableTextEntityItem.h b/libraries/entities-renderer/src/RenderableTextEntityItem.h index 57a485241e..355847c300 100644 --- a/libraries/entities-renderer/src/RenderableTextEntityItem.h +++ b/libraries/entities-renderer/src/RenderableTextEntityItem.h @@ -13,7 +13,7 @@ #define hifi_RenderableTextEntityItem_h #include -#include +#include 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); }; diff --git a/libraries/render-utils/src/TextRenderer3D.cpp b/libraries/render-utils/src/TextRenderer3D.cpp new file mode 100644 index 0000000000..8785848cb4 --- /dev/null +++ b/libraries/render-utils/src/TextRenderer3D.cpp @@ -0,0 +1,545 @@ +// +// 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 +#include +#include + +#include +#include +#include +#include + +#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 +void readStream(QIODevice& in, T& t) { + in.read((char*) &t, sizeof(t)); +} + +template +void readStream(QIODevice& in, T (&t)[N]) { + in.read((char*) t, N); +} + +template +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) {} + +}; + +QDebug operator<<(QDebug debug, glm::vec2& value) { + debug << value.x << value.y; + return debug; +} + +QDebug operator<<(QDebug debug, glm::vec3& value) { + debug << value.x << value.y << value.z; + return debug; +} + +QDebug operator<<(QDebug debug, TextureVertex& value) { + debug << "Pos:" << value.pos << ", Tex:" << value.tex; + return debug; +} + +QDebug operator<<(QDebug debug, QuadBuilder& value) { + debug << '\n' << value.vertices[0] + << '\n' << value.vertices[1] + << '\n' << value.vertices[2] + << '\n' << value.vertices[3]; + return debug; +} + +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 _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 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 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; + }; + + qDebug() << _family << "size" << image.size(); + qDebug() << _family << "format" << image.format(); + image = image.convertToFormat(QImage::Format_RGBA8888); + qDebug() << _family << "size" << image.size(); + qDebug() << _family << "format" << image.format(); + + gpu::Element formatGPU = gpu::Element(gpu::VEC3, gpu::UINT8, gpu::RGB); + gpu::Element formatMip = gpu::Element(gpu::VEC3, gpu::UINT8, gpu::RGB); + if (image.hasAlphaChannel()) { + formatGPU = gpu::Element(gpu::VEC4, gpu::UINT8, gpu::RGBA); + formatMip = gpu::Element(gpu::VEC4, gpu::UINT8, gpu::BGRA); + } + _texture = gpu::TexturePointer(gpu::Texture::create2D(formatGPU, image.width(), image.height(), + gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR))); + _texture->assignStoredMip(0, formatMip, image.byteCount(), image.constBits()); + _texture->autoGenerateMips(-1); +} + +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"); + + + auto f = [&] (QString str, const gpu::Shader::SlotSet& set) { + if (set.size() == 0) { + return; + } + qDebug() << str << set.size(); + for (auto slot : set) { + qDebug() << " " << QString::fromStdString(slot._name) << slot._location; + } + }; + f("getUniforms:", program->getUniforms()); + f("getBuffers:", program->getBuffers()); + f("getTextures:", program->getTextures()); + f("getSamplers:", program->getSamplers()); + f("getInputs:", program->getInputs()); + f("getOutputs:", program->getOutputs()); + + qDebug() << "Texture:" << _texture.get(); + + 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(glm::vec3) == 3 * sizeof(float)); + assert(sizeof(TextureVertex) == sizeof(glm::vec2) + 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 > 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(0.0f, 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 < -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); + } +} + diff --git a/libraries/render-utils/src/TextRenderer3D.h b/libraries/render-utils/src/TextRenderer3D.h new file mode 100644 index 0000000000..8f55d0c977 --- /dev/null +++ b/libraries/render-utils/src/TextRenderer3D.h @@ -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 +#include + +// 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 diff --git a/libraries/render-utils/src/sdf_text3D.slf b/libraries/render-utils/src/sdf_text3D.slf new file mode 100644 index 0000000000..3980045d08 --- /dev/null +++ b/libraries/render-utils/src/sdf_text3D.slf @@ -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); +} \ No newline at end of file diff --git a/libraries/render-utils/src/sdf_text3D.slv b/libraries/render-utils/src/sdf_text3D.slv new file mode 100644 index 0000000000..f7c35a257c --- /dev/null +++ b/libraries/render-utils/src/sdf_text3D.slv @@ -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)$> +} \ No newline at end of file From ca529e4bb8a1403dc91efd2e5a77c5d87ce65901 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Thu, 28 May 2015 15:58:40 +0200 Subject: [PATCH 15/16] Remove debug/tweak sampler --- .../src/RenderableTextEntityItem.cpp | 9 ++-- libraries/render-utils/src/TextRenderer3D.cpp | 53 ++----------------- 2 files changed, 8 insertions(+), 54 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableTextEntityItem.cpp b/libraries/entities-renderer/src/RenderableTextEntityItem.cpp index 1e6a39d2a8..f76454e1b3 100644 --- a/libraries/entities-renderer/src/RenderableTextEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableTextEntityItem.cpp @@ -32,8 +32,6 @@ void RenderableTextEntityItem::render(RenderArgs* args) { glm::vec4 textColor = glm::vec4(toGlm(getTextColorX()), 1.0f); glm::vec4 backgroundColor = glm::vec4(toGlm(getBackgroundColorX()), 1.0f); glm::vec3 dimensions = getDimensions(); - float leftMargin = 0.1f, topMargin = 0.1f; - glm::vec2 bounds = glm::vec2(dimensions.x - 2 * leftMargin, dimensions.y - 2 * topMargin); Transform transformToTopLeft = getTransformToCenter(); transformToTopLeft.postTranslate(glm::vec3(-0.5f, 0.5f, 0.0f)); // Go to the top left @@ -50,9 +48,12 @@ void RenderableTextEntityItem::render(RenderArgs* args) { DependencyManager::get()->renderQuad(batch, minCorner, maxCorner, backgroundColor); float scale = _lineHeight / _textRenderer->getRowHeight(); - transformToTopLeft.setScale(scale); + transformToTopLeft.setScale(scale); // Scale to have the correct line height batch.setModelTransform(transformToTopLeft); - _textRenderer->draw(batch, leftMargin, topMargin, _text, textColor, bounds / scale); + + 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); } diff --git a/libraries/render-utils/src/TextRenderer3D.cpp b/libraries/render-utils/src/TextRenderer3D.cpp index 8785848cb4..cfa358ffb3 100644 --- a/libraries/render-utils/src/TextRenderer3D.cpp +++ b/libraries/render-utils/src/TextRenderer3D.cpp @@ -106,29 +106,6 @@ struct QuadBuilder { }; -QDebug operator<<(QDebug debug, glm::vec2& value) { - debug << value.x << value.y; - return debug; -} - -QDebug operator<<(QDebug debug, glm::vec3& value) { - debug << value.x << value.y << value.z; - return debug; -} - -QDebug operator<<(QDebug debug, TextureVertex& value) { - debug << "Pos:" << value.pos << ", Tex:" << value.tex; - return debug; -} - -QDebug operator<<(QDebug debug, QuadBuilder& value) { - debug << '\n' << value.vertices[0] - << '\n' << value.vertices[1] - << '\n' << value.vertices[2] - << '\n' << value.vertices[3]; - return debug; -} - class Font3D { public: Font3D(); @@ -339,11 +316,7 @@ void Font3D::read(QIODevice& in) { _glyphs[g.c] = g; }; - qDebug() << _family << "size" << image.size(); - qDebug() << _family << "format" << image.format(); image = image.convertToFormat(QImage::Format_RGBA8888); - qDebug() << _family << "size" << image.size(); - qDebug() << _family << "format" << image.format(); gpu::Element formatGPU = gpu::Element(gpu::VEC3, gpu::UINT8, gpu::RGB); gpu::Element formatMip = gpu::Element(gpu::VEC3, gpu::UINT8, gpu::RGB); @@ -352,7 +325,7 @@ void Font3D::read(QIODevice& in) { formatMip = gpu::Element(gpu::VEC4, gpu::UINT8, gpu::BGRA); } _texture = gpu::TexturePointer(gpu::Texture::create2D(formatGPU, image.width(), image.height(), - gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR))); + gpu::Sampler(gpu::Sampler::FILTER_MIN_POINT_MAG_LINEAR))); _texture->assignStoredMip(0, formatMip, image.byteCount(), image.constBits()); _texture->autoGenerateMips(-1); } @@ -373,25 +346,6 @@ void Font3D::setupGPU() { _outlineLoc = program->getUniforms().findLocation("Outline"); _colorLoc = program->getUniforms().findLocation("Color"); - - auto f = [&] (QString str, const gpu::Shader::SlotSet& set) { - if (set.size() == 0) { - return; - } - qDebug() << str << set.size(); - for (auto slot : set) { - qDebug() << " " << QString::fromStdString(slot._name) << slot._location; - } - }; - f("getUniforms:", program->getUniforms()); - f("getBuffers:", program->getBuffers()); - f("getTextures:", program->getTextures()); - f("getSamplers:", program->getSamplers()); - f("getInputs:", program->getInputs()); - f("getOutputs:", program->getOutputs()); - - qDebug() << "Texture:" << _texture.get(); - gpu::StatePointer state = gpu::StatePointer(new gpu::State()); state->setCullMode(gpu::State::CULL_BACK); state->setDepthTest(true, true, gpu::LESS_EQUAL); @@ -404,8 +358,7 @@ void Font3D::setupGPU() { static const int OFFSET = offsetof(TextureVertex, tex); assert(OFFSET == sizeof(glm::vec2)); assert(sizeof(glm::vec2) == 2 * sizeof(float)); - assert(sizeof(glm::vec3) == 3 * sizeof(float)); - assert(sizeof(TextureVertex) == sizeof(glm::vec2) + sizeof(glm::vec2)); + assert(sizeof(TextureVertex) == 2 * sizeof(glm::vec2)); assert(sizeof(QuadBuilder) == 4 * sizeof(TextureVertex)); // Setup rendering structures @@ -440,7 +393,7 @@ void Font3D::drawString(gpu::Batch& batch, float x, float y, const QString& str, } if (isNewLine || forceNewLine) { // Character return, move the advance to a new line - advance = glm::vec2(0.0f, advance.y - _rowHeight); + advance = glm::vec2(x, advance.y - _rowHeight); if (isNewLine) { // No need to draw anything, go directly to next token From b0cbf5c51a0ec3a403179cefb19bc55313a7ec71 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Thu, 28 May 2015 16:06:18 +0200 Subject: [PATCH 16/16] Adjust wraping with offset --- libraries/render-utils/src/TextRenderer3D.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/render-utils/src/TextRenderer3D.cpp b/libraries/render-utils/src/TextRenderer3D.cpp index cfa358ffb3..d081c0480a 100644 --- a/libraries/render-utils/src/TextRenderer3D.cpp +++ b/libraries/render-utils/src/TextRenderer3D.cpp @@ -387,7 +387,7 @@ void Font3D::drawString(gpu::Batch& batch, float x, float y, const QString& str, bool forceNewLine = false; // Handle wrapping - if (!isNewLine && (bounds.x != -1) && (advance.x + computeExtent(token).x > bounds.x)) { + 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; } @@ -403,7 +403,7 @@ void Font3D::drawString(gpu::Batch& batch, float x, float y, const QString& str, break; } } - if ((bounds.y != -1) && (advance.y - _fontSize < -bounds.y)) { + if ((bounds.y != -1) && (advance.y - _fontSize < -y - bounds.y)) { // We are out of the y bound, stop drawing break; }