From b201369f73b4ad00f3162a3f99972a874e9d905c Mon Sep 17 00:00:00 2001 From: HifiExperiments Date: Thu, 10 Oct 2024 23:49:38 -0700 Subject: [PATCH] show tofu character on missing character --- libraries/render-utils/src/sdf_text3D.slf | 6 ++-- libraries/render-utils/src/sdf_text3D.slh | 12 ++++++-- libraries/render-utils/src/sdf_text3D.slv | 5 +++- libraries/render-utils/src/text/Font.cpp | 34 +++++++++++++++++------ libraries/render-utils/src/text/Font.h | 1 + libraries/render-utils/src/text/Glyph.h | 1 + 6 files changed, 45 insertions(+), 14 deletions(-) diff --git a/libraries/render-utils/src/sdf_text3D.slf b/libraries/render-utils/src/sdf_text3D.slf index 43eb3dbec7..a7f6d6f879 100644 --- a/libraries/render-utils/src/sdf_text3D.slf +++ b/libraries/render-utils/src/sdf_text3D.slf @@ -48,7 +48,9 @@ layout(location=RENDER_UTILS_ATTR_NORMAL_WS) in vec3 _normalWS; layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01; #define _texCoord0 _texCoord01.xy #define _texCoord1 _texCoord01.zw -layout(location=RENDER_UTILS_ATTR_FADE1) flat in vec4 _glyphBounds; // we're reusing the fade texcoord locations here + // we're reusing the fade texcoord locations here: +layout(location=RENDER_UTILS_ATTR_FADE1) flat in vec4 _glyphBounds; +layout(location=RENDER_UTILS_ATTR_FADE2) flat in float _isTofu; <@if HIFI_USE_MIRROR@> <@include graphics/ShaderConstants.h@> @@ -57,7 +59,7 @@ layout(location=RENDER_UTILS_ATTR_FADE1) flat in vec4 _glyphBounds; // we're reu <@endif@> void main() { - vec4 color = evalSDFSuperSampled(_texCoord0, _positionMS, _glyphBounds); + vec4 color = evalSDFSuperSampled(_texCoord0, _positionMS, _glyphBounds, _isTofu); <@if HIFI_USE_TRANSLUCENT or HIFI_USE_FORWARD@> color.a *= params.color.a; diff --git a/libraries/render-utils/src/sdf_text3D.slh b/libraries/render-utils/src/sdf_text3D.slh index 9b7d54f3da..3c047ab02d 100644 --- a/libraries/render-utils/src/sdf_text3D.slh +++ b/libraries/render-utils/src/sdf_text3D.slh @@ -60,8 +60,14 @@ vec2 evalSDF(vec2 texCoord) { return vec2(opacity, msdf.a); } -vec4 evalSDFColor(vec2 texCoord, vec4 glyphBounds) { +vec4 evalSDFColor(vec2 texCoord, vec4 glyphBounds, float isTofu) { vec3 color = params.color.rgb; + + if (isTofu > 0.0f) { + const float OUTLINE_WIDTH = 0.1; + return vec4(color, any(greaterThan(abs(texCoord - vec2(0.5)), vec2(0.5 - OUTLINE_WIDTH, 0.5 - (glyphBounds.z / glyphBounds.w) * OUTLINE_WIDTH)))); + } + vec2 sdf = evalSDF(texCoord); // Outline @@ -95,14 +101,14 @@ vec4 evalSDFColor(vec2 texCoord, vec4 glyphBounds) { return vec4(color, sdf.x); } -vec4 evalSDFSuperSampled(vec2 texCoord, vec2 positionMS, vec4 glyphBounds) { +vec4 evalSDFSuperSampled(vec2 texCoord, vec2 positionMS, vec4 glyphBounds, float isTofu) { // Clip to edges. Note: We don't need to check the top edge. if ((params.bounds.z > 0.0 && (positionMS.x < params.bounds.x || positionMS.x > (params.bounds.x + params.bounds.z))) || (params.bounds.w > 0.0 && (positionMS.y < params.bounds.y - params.bounds.w))) { return vec4(0.0); } - vec4 color = evalSDFColor(texCoord, glyphBounds); + vec4 color = evalSDFColor(texCoord, glyphBounds, isTofu); // Rely on TAA for anti-aliasing but smooth transition when minification // to help filtering diff --git a/libraries/render-utils/src/sdf_text3D.slv b/libraries/render-utils/src/sdf_text3D.slv index d7b2abd7bc..c21c653fa3 100644 --- a/libraries/render-utils/src/sdf_text3D.slv +++ b/libraries/render-utils/src/sdf_text3D.slv @@ -30,12 +30,15 @@ layout(location=RENDER_UTILS_ATTR_POSITION_MS) out vec2 _positionMS; <@endif@> layout(location=RENDER_UTILS_ATTR_NORMAL_WS) out vec3 _normalWS; layout(location=RENDER_UTILS_ATTR_TEXCOORD01) out vec4 _texCoord01; -layout(location=RENDER_UTILS_ATTR_FADE1) flat out vec4 _glyphBounds; // we're reusing the fade texcoord locations here +// we're reusing the fade texcoord locations here: +layout(location=RENDER_UTILS_ATTR_FADE1) flat out vec4 _glyphBounds; +layout(location=RENDER_UTILS_ATTR_FADE2) flat out float _isTofu; void main() { _positionMS = inPosition.xy; _texCoord01 = vec4(inTexCoord0.st, 0.0, 0.0); _glyphBounds = inTexCoord1; + _isTofu = inTexCoord2.x; vec4 position = inPosition; // if we're in shadow mode, we need to move each subsequent quad slightly forward so it doesn't z-fight diff --git a/libraries/render-utils/src/text/Font.cpp b/libraries/render-utils/src/text/Font.cpp index 23699de69a..9cfb9c2d41 100644 --- a/libraries/render-utils/src/text/Font.cpp +++ b/libraries/render-utils/src/text/Font.cpp @@ -40,8 +40,9 @@ struct TextureVertex { glm::vec2 pos; glm::vec2 tex; glm::vec4 bounds; + float isTofu; TextureVertex() {} - TextureVertex(const glm::vec2& pos, const glm::vec2& tex, const glm::vec4& bounds) : pos(pos), tex(tex), bounds(bounds) {} + TextureVertex(const glm::vec2& pos, const glm::vec2& tex, const glm::vec4& bounds, bool isTofu) : pos(pos), tex(tex), bounds(bounds), isTofu(isTofu ? 1.0f : 0.0f) {} }; static const int NUMBER_OF_INDICES_PER_QUAD = 6; // 1 quad = 2 triangles @@ -71,13 +72,13 @@ struct QuadBuilder { // min = bottomLeft vertices[0] = TextureVertex(min, - texMin + glm::vec2(0.0f, texSize.y), bounds); + texMin + glm::vec2(0.0f, texSize.y), bounds, glyph.isTofu); vertices[1] = TextureVertex(min + glm::vec2(size.x, 0.0f), - texMin + texSize, bounds); + texMin + texSize, bounds, glyph.isTofu); vertices[2] = TextureVertex(min + glm::vec2(0.0f, size.y), - texMin, bounds); + texMin, bounds, glyph.isTofu); vertices[3] = TextureVertex(min + size, - texMin + glm::vec2(texSize.x, 0.0f), bounds); + texMin + glm::vec2(texSize.x, 0.0f), bounds, glyph.isTofu); } }; @@ -129,7 +130,8 @@ void Font::read(QIODevice& in) { } _distanceRange = glm::vec2(arteryFont.variants[0].metrics.distanceRange); - _fontHeight = arteryFont.variants[0].metrics.ascender + fabs(arteryFont.variants[0].metrics.descender); + const float ascent = arteryFont.variants[0].metrics.ascender; + _fontHeight = ascent + fabs(arteryFont.variants[0].metrics.descender); _leading = arteryFont.variants[0].metrics.lineHeight; _spaceWidth = 0.5f * arteryFont.variants[0].metrics.emSize; // We use half the emSize as a first guess for _spaceWidth @@ -193,6 +195,7 @@ void Font::read(QIODevice& in) { _glyphs.clear(); glm::vec2 imageSize = toGlm(image.size()); _distanceRange /= imageSize; + bool hasTofu = false; foreach(Glyph g, glyphs) { // Adjust the pixel texture coordinates into UV coordinates, g.texSize /= imageSize; @@ -202,8 +205,21 @@ void Font::read(QIODevice& in) { g.offset.y = -(1.0f - (g.offset.y + g.size.y)); // store in the character to glyph hash _glyphs[g.c] = g; + if (g.c == '?') { + _tofuGlyph = g; + hasTofu = true; + } }; + _tofuGlyph.texSize = glm::vec2(1.0f); + _tofuGlyph.texOffset = glm::vec2(0.0f); + if (!hasTofu) { + _tofuGlyph.size = glm::vec2(2.0f * _spaceWidth, ascent); + _tofuGlyph.offset = glm::vec2(0.0f, -(1.0f - ascent)); + _tofuGlyph.d = 2.0f * _spaceWidth; + } + _tofuGlyph.isTofu = true; + image = image.convertToFormat(QImage::Format_RGBA8888); gpu::Element formatGPU = gpu::Element(gpu::VEC3, gpu::NUINT8, gpu::RGB); @@ -283,7 +299,7 @@ Font::Font(const QString& family) : _family(family) { // NERD RAGE: why doesn't QHash have a 'const T & operator[] const' member const Glyph& Font::getGlyph(const QChar& c) const { if (!_glyphs.contains(c)) { - return _glyphs[QChar('?')]; + return _tofuGlyph; } return _glyphs[c]; } @@ -360,6 +376,7 @@ void Font::setupGPU() { // Sanity checks static const int TEX_COORD_OFFSET = offsetof(TextureVertex, tex); static const int TEX_BOUNDS_OFFSET = offsetof(TextureVertex, bounds); + static const int TOFU_OFFSET = offsetof(TextureVertex, isTofu); assert(TEX_COORD_OFFSET == sizeof(glm::vec2)); assert(sizeof(TextureVertex) == 2 * sizeof(glm::vec2) + sizeof(glm::vec4)); assert(sizeof(QuadBuilder) == 4 * sizeof(TextureVertex)); @@ -369,6 +386,7 @@ void Font::setupGPU() { _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), TEX_COORD_OFFSET); _format->setAttribute(gpu::Stream::TEXCOORD1, 0, gpu::Element(gpu::VEC4, gpu::FLOAT, gpu::XYZW), TEX_BOUNDS_OFFSET); + _format->setAttribute(gpu::Stream::TEXCOORD2, 0, gpu::Element(gpu::SCALAR, gpu::FLOAT, gpu::RED), TOFU_OFFSET); } } @@ -441,7 +459,7 @@ void Font::buildVertices(Font::DrawInfo& drawInfo, const QString& str, const glm if (bounds.x != -1 && advance.x > rightEdge) { break; } - const Glyph& glyph = _glyphs[c]; + const Glyph& glyph = getGlyph(c); glyphsAndCorners.emplace_back(glyph, advance); diff --git a/libraries/render-utils/src/text/Font.h b/libraries/render-utils/src/text/Font.h index 730f6db758..001247f1e1 100644 --- a/libraries/render-utils/src/text/Font.h +++ b/libraries/render-utils/src/text/Font.h @@ -117,6 +117,7 @@ private: // we declare the hash as mutable in order to avoid such // copies mutable QHash _glyphs; + Glyph _tofuGlyph; // Font characteristics QString _family; diff --git a/libraries/render-utils/src/text/Glyph.h b/libraries/render-utils/src/text/Glyph.h index 5aeb96b2c6..1b3ffc7d2e 100644 --- a/libraries/render-utils/src/text/Glyph.h +++ b/libraries/render-utils/src/text/Glyph.h @@ -26,6 +26,7 @@ struct Glyph { vec2 size; vec2 offset; float d; // xadvance - adjusts character positioning + bool isTofu { false }; // We adjust bounds because offset is the bottom left corner of the font but the top left corner of a QRect QRectF bounds() const;