Merge pull request #1395 from HifiExperiments/tofu
Some checks are pending
Master API-docs CI Build and Deploy / Build and deploy API-docs (push) Waiting to run
Master Doxygen CI Build and Deploy / Build and deploy Doxygen documentation (push) Waiting to run

show tofu character on missing character
This commit is contained in:
ksuprynowicz 2025-04-06 18:25:25 +02:00 committed by GitHub
commit 22122acab5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
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;
#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;

View file

@ -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

View file

@ -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

View file

@ -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);

View file

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

View file

@ -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;