diff --git a/src/Magnum/Text/DistanceFieldGlyphCacheGL.cpp b/src/Magnum/Text/DistanceFieldGlyphCacheGL.cpp index dbac3902a..0606a531b 100644 --- a/src/Magnum/Text/DistanceFieldGlyphCacheGL.cpp +++ b/src/Magnum/Text/DistanceFieldGlyphCacheGL.cpp @@ -110,7 +110,13 @@ void DistanceFieldGlyphCacheGL::doSetImage(const Vector2i& auto& state = static_cast(*_state); GL::Texture2D input; - input.setWrapping(GL::SamplerWrapping::ClampToEdge) + input + /* In order to have correctly processed output, the input has to be + sufficiently padded. But in case it isn't and texelFetch() isn't + used, which clamps out-of-range reads to zero implicitly, clamp the + out-of-range reads to the edge instead of repeat to avoid even worse + artifacts. */ + .setWrapping(GL::SamplerWrapping::ClampToEdge) /* Use nearest filter to avoid minor rounding errors on ES2 compared to texelFetch() on ES3+ */ .setMinificationFilter(GL::SamplerFilter::Nearest, GL::SamplerMipmap::Base) diff --git a/src/Magnum/Text/Test/DistanceFieldGlyphCacheGLTest.cpp b/src/Magnum/Text/Test/DistanceFieldGlyphCacheGLTest.cpp index 99ff82fe6..66fcc8b68 100644 --- a/src/Magnum/Text/Test/DistanceFieldGlyphCacheGLTest.cpp +++ b/src/Magnum/Text/Test/DistanceFieldGlyphCacheGLTest.cpp @@ -62,6 +62,7 @@ struct DistanceFieldGlyphCacheGLTest: GL::OpenGLTester { void constructMove(); void setImage(); + void setImageEdgeClamp(); void setProcessedImage(); #ifdef MAGNUM_BUILD_DEPRECATED @@ -124,6 +125,8 @@ DistanceFieldGlyphCacheGLTest::DistanceFieldGlyphCacheGLTest() { addInstancedTests({&DistanceFieldGlyphCacheGLTest::setImage}, Containers::arraySize(SetImageData)); + addTests({&DistanceFieldGlyphCacheGLTest::setImageEdgeClamp}); + #ifndef MAGNUM_BUILD_DEPRECATED addTests({&DistanceFieldGlyphCacheGLTest::setProcessedImage}); #else @@ -294,6 +297,61 @@ void DistanceFieldGlyphCacheGLTest::setImage() { (DebugTools::CompareImageToFile{_manager, 1.0f, 0.178f})); } +void DistanceFieldGlyphCacheGLTest::setImageEdgeClamp() { + /* Verifies that the input texture filtering clamp is set to edge to not + have content from one side leak to another when the data don't have + enough padding. Affects only the non-texelFetch() codepath, texel + fetches return zero for out-of-bounds reads. Well, assuming robustness + enabled, at least. */ + + DistanceFieldGlyphCacheGL cache{{8, 4}, {4, 2}, 4}; + + /* Make the right edge all white */ + Containers::StridedArrayView2D src = cache.image().pixels()[0]; + src[0][7] = '\xff'; + src[1][7] = '\xff'; + src[2][7] = '\xff'; + src[3][7] = '\xff'; + cache.flushImage({{}, {8, 4}}); + MAGNUM_VERIFY_NO_GL_ERROR(); + + /* On GLES processedImage() isn't implemented as it'd mean creating a + temporary framebuffer. Do it via DebugTools here instead, we cannot + really verify that the size matches, but at least something. */ + #ifndef MAGNUM_TARGET_GLES + Image3D actual3 = cache.processedImage(); + /** @todo ugh have slicing on images directly already */ + MutableImageView2D actual{actual3.format(), actual3.size().xy(), actual3.data()}; + #elif !defined(MAGNUM_TARGET_GLES2) + Image2D actual = DebugTools::textureSubImage(cache.texture(), 0, {{}, {4, 2}}, cache.processedFormat()); + #else + /* On ES2, R8Unorm maps to Luminance, but here it's actually Red if + EXT_texture_rg is supported */ + Image2D actual = DebugTools::textureSubImage(cache.texture(), 0, {{}, {4, 2}}, + #ifndef MAGNUM_TARGET_WEBGL + cache.processedFormat() == PixelFormat::R8Unorm ? + Image2D{GL::PixelFormat::Red, GL::PixelType::UnsignedByte} : + #endif + Image2D{cache.processedFormat()} + ); + #endif + MAGNUM_VERIFY_NO_GL_ERROR(); + + /* The format may be three-component, consider just the first channel */ + Containers::StridedArrayView3D dst3 = actual.pixels(); + Containers::StridedArrayView2D dst = Containers::arrayCast<2, const UnsignedByte>(dst3.prefix({dst3.size()[0], dst3.size()[1], 1})); + + /* On the right side the pixels should be non-zero to verify processing got + done at all */ + CORRADE_VERIFY(dst[0][3] > 0); + CORRADE_VERIFY(dst[1][3] > 0); + + /* On the left side the pixels should be completely zero, without the right + side leaking for example due to accidental repeat clamp */ + CORRADE_COMPARE(dst[0][0], '\x00'); + CORRADE_COMPARE(dst[1][0], '\x00'); +} + void DistanceFieldGlyphCacheGLTest::setProcessedImage() { #ifdef MAGNUM_BUILD_DEPRECATED auto&& data = SetProcessedImageData[testCaseInstanceId()];