show tofu character on missing character

This commit is contained in:
HifiExperiments 2024-10-10 23:49:38 -07:00
parent b38237cb8d
commit b201369f73
6 changed files with 45 additions and 14 deletions

View file

@ -48,7 +48,9 @@ layout(location=RENDER_UTILS_ATTR_NORMAL_WS) in vec3 _normalWS;
layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01; layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01;
#define _texCoord0 _texCoord01.xy #define _texCoord0 _texCoord01.xy
#define _texCoord1 _texCoord01.zw #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@> <@if HIFI_USE_MIRROR@>
<@include graphics/ShaderConstants.h@> <@include graphics/ShaderConstants.h@>
@ -57,7 +59,7 @@ layout(location=RENDER_UTILS_ATTR_FADE1) flat in vec4 _glyphBounds; // we're reu
<@endif@> <@endif@>
void main() { void main() {
vec4 color = evalSDFSuperSampled(_texCoord0, _positionMS, _glyphBounds); vec4 color = evalSDFSuperSampled(_texCoord0, _positionMS, _glyphBounds, _isTofu);
<@if HIFI_USE_TRANSLUCENT or HIFI_USE_FORWARD@> <@if HIFI_USE_TRANSLUCENT or HIFI_USE_FORWARD@>
color.a *= params.color.a; color.a *= params.color.a;

View file

@ -60,8 +60,14 @@ vec2 evalSDF(vec2 texCoord) {
return vec2(opacity, msdf.a); return vec2(opacity, msdf.a);
} }
vec4 evalSDFColor(vec2 texCoord, vec4 glyphBounds) { vec4 evalSDFColor(vec2 texCoord, vec4 glyphBounds, float isTofu) {
vec3 color = params.color.rgb; 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); vec2 sdf = evalSDF(texCoord);
// Outline // Outline
@ -95,14 +101,14 @@ vec4 evalSDFColor(vec2 texCoord, vec4 glyphBounds) {
return vec4(color, sdf.x); 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. // 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))) || 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))) { (params.bounds.w > 0.0 && (positionMS.y < params.bounds.y - params.bounds.w))) {
return vec4(0.0); 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 // Rely on TAA for anti-aliasing but smooth transition when minification
// to help filtering // to help filtering

View file

@ -30,12 +30,15 @@ layout(location=RENDER_UTILS_ATTR_POSITION_MS) out vec2 _positionMS;
<@endif@> <@endif@>
layout(location=RENDER_UTILS_ATTR_NORMAL_WS) out vec3 _normalWS; layout(location=RENDER_UTILS_ATTR_NORMAL_WS) out vec3 _normalWS;
layout(location=RENDER_UTILS_ATTR_TEXCOORD01) out vec4 _texCoord01; 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() { void main() {
_positionMS = inPosition.xy; _positionMS = inPosition.xy;
_texCoord01 = vec4(inTexCoord0.st, 0.0, 0.0); _texCoord01 = vec4(inTexCoord0.st, 0.0, 0.0);
_glyphBounds = inTexCoord1; _glyphBounds = inTexCoord1;
_isTofu = inTexCoord2.x;
vec4 position = inPosition; vec4 position = inPosition;
// if we're in shadow mode, we need to move each subsequent quad slightly forward so it doesn't z-fight // if we're in shadow mode, we need to move each subsequent quad slightly forward so it doesn't z-fight

View file

@ -40,8 +40,9 @@ struct TextureVertex {
glm::vec2 pos; glm::vec2 pos;
glm::vec2 tex; glm::vec2 tex;
glm::vec4 bounds; glm::vec4 bounds;
float isTofu;
TextureVertex() {} 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 static const int NUMBER_OF_INDICES_PER_QUAD = 6; // 1 quad = 2 triangles
@ -71,13 +72,13 @@ struct QuadBuilder {
// min = bottomLeft // min = bottomLeft
vertices[0] = TextureVertex(min, 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), 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), vertices[2] = TextureVertex(min + glm::vec2(0.0f, size.y),
texMin, bounds); texMin, bounds, glyph.isTofu);
vertices[3] = TextureVertex(min + size, 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); _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; _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 _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(); _glyphs.clear();
glm::vec2 imageSize = toGlm(image.size()); glm::vec2 imageSize = toGlm(image.size());
_distanceRange /= imageSize; _distanceRange /= imageSize;
bool hasTofu = false;
foreach(Glyph g, glyphs) { foreach(Glyph g, glyphs) {
// Adjust the pixel texture coordinates into UV coordinates, // Adjust the pixel texture coordinates into UV coordinates,
g.texSize /= imageSize; g.texSize /= imageSize;
@ -202,8 +205,21 @@ void Font::read(QIODevice& in) {
g.offset.y = -(1.0f - (g.offset.y + g.size.y)); g.offset.y = -(1.0f - (g.offset.y + g.size.y));
// store in the character to glyph hash // store in the character to glyph hash
_glyphs[g.c] = g; _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); image = image.convertToFormat(QImage::Format_RGBA8888);
gpu::Element formatGPU = gpu::Element(gpu::VEC3, gpu::NUINT8, gpu::RGB); 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 // 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)) { if (!_glyphs.contains(c)) {
return _glyphs[QChar('?')]; return _tofuGlyph;
} }
return _glyphs[c]; return _glyphs[c];
} }
@ -360,6 +376,7 @@ void Font::setupGPU() {
// Sanity checks // Sanity checks
static const int TEX_COORD_OFFSET = offsetof(TextureVertex, tex); static const int TEX_COORD_OFFSET = offsetof(TextureVertex, tex);
static const int TEX_BOUNDS_OFFSET = offsetof(TextureVertex, bounds); 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(TEX_COORD_OFFSET == sizeof(glm::vec2));
assert(sizeof(TextureVertex) == 2 * sizeof(glm::vec2) + sizeof(glm::vec4)); assert(sizeof(TextureVertex) == 2 * sizeof(glm::vec2) + sizeof(glm::vec4));
assert(sizeof(QuadBuilder) == 4 * sizeof(TextureVertex)); 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::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::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::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) { if (bounds.x != -1 && advance.x > rightEdge) {
break; break;
} }
const Glyph& glyph = _glyphs[c]; const Glyph& glyph = getGlyph(c);
glyphsAndCorners.emplace_back(glyph, advance); glyphsAndCorners.emplace_back(glyph, advance);

View file

@ -117,6 +117,7 @@ private:
// we declare the hash as mutable in order to avoid such // we declare the hash as mutable in order to avoid such
// copies // copies
mutable QHash<QChar, Glyph> _glyphs; mutable QHash<QChar, Glyph> _glyphs;
Glyph _tofuGlyph;
// Font characteristics // Font characteristics
QString _family; QString _family;

View file

@ -26,6 +26,7 @@ struct Glyph {
vec2 size; vec2 size;
vec2 offset; vec2 offset;
float d; // xadvance - adjusts character positioning 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 // We adjust bounds because offset is the bottom left corner of the font but the top left corner of a QRect
QRectF bounds() const; QRectF bounds() const;