From 9d1f2f9bc91d19538f5d55ad6883f031c320176c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Mon, 30 Sep 2024 14:08:47 +0200 Subject: [PATCH] Text: make AbstractGlyphCache aware of processed image format and size. The refactor in 6707534ce65375a3b79598f6d08f125071ee02c9 somehow didn't account for the need to have a different pixel format for the input and the processed texture, which is needed on ES2 / WebGL 1 because there's no renderable single-channel format. Which means distance field text rendering was broken since then. This commit is the first part of fixing that regression. It makes the AbstractGlyphCache aware of the processing being done, decoupling the input and processed format and size. Which also allows the base implementation to provide interfaces that so far were only limited to DistanceFieldGlyphCache, thus eventually making it possible for font plugins to supply a pregenerated distance field image through fillGlyphCache() and not just through createGlyphCache(). The two DistanceFieldGlyphCache APIs that are now directly on the AbstractGlyphCache are now deprecated aliases to the new functionality. The actual fix for the ES2 / WebGL 1 regression will come in the next commit, as it's pretty much a separate step that involves updating the GlyphCache as well. --- doc/changelog.dox | 2 +- src/Magnum/Text/AbstractGlyphCache.cpp | 59 ++- src/Magnum/Text/AbstractGlyphCache.h | 161 ++++++- src/Magnum/Text/DistanceFieldGlyphCache.cpp | 58 ++- src/Magnum/Text/DistanceFieldGlyphCache.h | 34 +- src/Magnum/Text/GlyphCache.cpp | 8 +- src/Magnum/Text/GlyphCache.h | 25 +- .../Text/Test/AbstractGlyphCacheTest.cpp | 448 +++++++++++++++++- .../Test/DistanceFieldGlyphCacheGLTest.cpp | 139 +++--- 9 files changed, 778 insertions(+), 156 deletions(-) diff --git a/doc/changelog.dox b/doc/changelog.dox index 230c102e2..1da7d924b 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -1413,7 +1413,7 @@ See also: - @cpp Text::DistanceFieldGlyphCache::distanceFieldTextureSize() @ce and @cpp setDistanceFieldImage() @ce is deprecated in favor of @ref Text::AbstractGlyphCache::processedSize() and - @relativeref{Text,AbstractGlyphCache,setProcessedImage()} + @relativeref{Text::AbstractGlyphCache,setProcessedImage()} - The @cpp TextureTools::atlas() @ce utility is deprecated in favor of @ref TextureTools::AtlasLandfill, which has a vastly better packing efficiency, supports incremental packing and doesn't force the caller to diff --git a/src/Magnum/Text/AbstractGlyphCache.cpp b/src/Magnum/Text/AbstractGlyphCache.cpp index 2fa965721..564b3d272 100644 --- a/src/Magnum/Text/AbstractGlyphCache.cpp +++ b/src/Magnum/Text/AbstractGlyphCache.cpp @@ -70,7 +70,7 @@ Debug& operator<<(Debug& debug, const GlyphCacheFeatures value) { } struct AbstractGlyphCache::State { - explicit State(PixelFormat format, const Vector3i& size, const Vector2i& padding): image{format, size, Containers::Array{ValueInit, 4*((pixelFormatSize(format)*size.x() + 3)/4)*size.y()*size.z()}}, atlas{size}, padding{padding} { + explicit State(PixelFormat format, const Vector3i& size, PixelFormat processedFormat, const Vector2i& processedSize, const Vector2i& padding): image{format, size, Containers::Array{ValueInit, 4*((pixelFormatSize(format)*size.x() + 3)/4)*size.y()*size.z()}}, atlas{size}, processedFormat{processedFormat}, processedSize{processedSize}, padding{padding} { /* Flags are currently cleared as well, will be enabled back in a later step once the behavior is specified (with negative ranges) and Math::join() is fixed to handle those correctly. */ @@ -82,8 +82,12 @@ struct AbstractGlyphCache::State { Image3D image; TextureTools::AtlasLandfill atlas; + PixelFormat processedFormat; + Vector2i processedSize; Vector2i padding; + /* 0/4 bytes free */ + /* First element is glyph position relative to a point on the baseline, second layer in the texture atlas, third a region in the atlas slice. Index of the item is ID of the glyph in the cache, refered to @@ -117,13 +121,15 @@ struct AbstractGlyphCache::State { Containers::Array fontGlyphMapping; }; -AbstractGlyphCache::AbstractGlyphCache(const PixelFormat format, const Vector3i& size, const Vector2i& padding) { +AbstractGlyphCache::AbstractGlyphCache(const PixelFormat format, const Vector3i& size, const PixelFormat processedFormat, const Vector2i& processedSize, const Vector2i& padding) { CORRADE_ASSERT(size.product(), "Text::AbstractGlyphCache: expected non-zero size, got" << Debug::packed << size, ); + CORRADE_ASSERT(processedSize.product(), + "Text::AbstractGlyphCache: expected non-zero processed size, got" << Debug::packed << processedSize, ); /* Creating the state only after the assert as the AtlasLandfill would assert on zero size as well */ - _state.emplace(format, size, padding); + _state.emplace(format, size, processedFormat, processedSize, padding); /* Default invalid glyph -- empty / zero-area */ arrayAppend(_state->glyphs, InPlaceInit); @@ -132,6 +138,14 @@ AbstractGlyphCache::AbstractGlyphCache(const PixelFormat format, const Vector3i& arrayAppend(_state->fonts, InPlaceInit, 0u, nullptr); } +AbstractGlyphCache::AbstractGlyphCache(const PixelFormat format, const Vector3i& size, const PixelFormat processedFormat, const Vector2i& processedSize): AbstractGlyphCache{format, size, processedFormat, processedSize, Vector2i{1}} {} + +AbstractGlyphCache::AbstractGlyphCache(const PixelFormat format, const Vector2i& size, const PixelFormat processedFormat, const Vector2i& processedSize, const Vector2i& padding): AbstractGlyphCache{format, Vector3i{size, 1}, processedFormat, processedSize, padding} {} + +AbstractGlyphCache::AbstractGlyphCache(const PixelFormat format, const Vector2i& size, const PixelFormat processedFormat, const Vector2i& processedSize): AbstractGlyphCache{format, size, processedFormat, processedSize, Vector2i{1}} {} + +AbstractGlyphCache::AbstractGlyphCache(const PixelFormat format, const Vector3i& size, const Vector2i& padding): AbstractGlyphCache{format, size, format, size.xy(), padding} {} + AbstractGlyphCache::AbstractGlyphCache(const PixelFormat format, const Vector3i& size): AbstractGlyphCache{format, size, Vector2i{1}} {} AbstractGlyphCache::AbstractGlyphCache(const PixelFormat format, const Vector2i& size, const Vector2i& padding): AbstractGlyphCache{format, Vector3i{size, 1}, padding} {} @@ -156,10 +170,18 @@ PixelFormat AbstractGlyphCache::format() const { return _state->image.format(); } +PixelFormat AbstractGlyphCache::processedFormat() const { + return _state->processedFormat; +} + Vector3i AbstractGlyphCache::size() const { return _state->image.size(); } +Vector3i AbstractGlyphCache::processedSize() const { + return {_state->processedSize, _state->image.size().z()}; +} + #ifdef MAGNUM_BUILD_DEPRECATED Vector2i AbstractGlyphCache::textureSize() const { CORRADE_ASSERT(_state->image.size().z() == 1, @@ -454,6 +476,37 @@ Image3D AbstractGlyphCache::doProcessedImage() { CORRADE_ASSERT_UNREACHABLE("Text::AbstractGlyphCache::processedImage(): feature advertised but not implemented", Image3D{PixelFormat::R8Unorm}); } +void AbstractGlyphCache::setProcessedImage(const Vector3i& offset, const ImageView3D& image) { + #ifndef CORRADE_NO_ASSERT + State& state = *_state; + #endif + CORRADE_ASSERT(features() >= GlyphCacheFeature::ImageProcessing, + "Text::AbstractGlyphCache::setProcessedImage(): feature not supported", ); + CORRADE_ASSERT((offset >= Vector3i{} && offset + image.size() <= processedSize()).all(), + "Text::AbstractGlyphCache::setProcessedImage():" << Debug::packed << Range3Di::fromSize(offset, image.size()) << "out of range for size" << Debug::packed << processedSize(), ); + CORRADE_ASSERT(image.format() == state.processedFormat, + "Text::AbstractGlyphCache::setProcessedImage(): expected" << state.processedFormat << "but got" << image.format(), ); + doSetProcessedImage(offset, image); +} + +void AbstractGlyphCache::setProcessedImage(const Vector2i& offset, const ImageView2D& image) { + CORRADE_ASSERT(_state->image.size().z() == 1, + "Text::AbstractGlyphCache::setProcessedImage(): use the 3D overload for an array glyph cache", ); + setProcessedImage({offset, 0}, image); +} + +void AbstractGlyphCache::doSetProcessedImage(const Vector3i& offset, const ImageView3D& image) { + if(_state->image.size().z() == 1) + /** @todo ugh have slicing on images directly already */ + return doSetProcessedImage(offset.xy(), ImageView2D{image.storage(), image.format(), image.size().xy(), image.data()}); + + CORRADE_ASSERT_UNREACHABLE("Text::AbstractGlyphCache::setProcessedImage(): feature advertised but not implemented", ); +} + +void AbstractGlyphCache::doSetProcessedImage(const Vector2i&, const ImageView2D&) { + CORRADE_ASSERT_UNREACHABLE("Text::AbstractGlyphCache::setProcessedImage(): feature advertised but not implemented", ); +} + UnsignedInt AbstractGlyphCache::glyphId(const UnsignedInt fontId, const UnsignedInt fontGlyphId) const { const State& state = *_state; CORRADE_DEBUG_ASSERT(fontId < state.fonts.size() - 1, diff --git a/src/Magnum/Text/AbstractGlyphCache.h b/src/Magnum/Text/AbstractGlyphCache.h index a5537a4b7..fb3f537ae 100644 --- a/src/Magnum/Text/AbstractGlyphCache.h +++ b/src/Magnum/Text/AbstractGlyphCache.h @@ -55,6 +55,10 @@ enum class GlyphCacheFeature: UnsignedByte { /** * The glyph cache processes the input image, potentially to a different * size or format. + * @see @ref AbstractGlyphCache::processedFormat(), + * @relativeref{AbstractGlyphCache,processedSize()}, + * @relativeref{AbstractGlyphCache,setProcessedImage()}, + * @ref GlyphCacheFeature::ProcessedImageDownload * @m_since_latest */ ImageProcessing = 1 << 0, @@ -271,14 +275,15 @@ padding even further. A subclass needs to implement the @ref doFeatures() and @ref doSetImage() functions. If the subclass does additional processing of the glyph cache image, -it should advertise that with @ref GlyphCacheFeature::ImageProcessing. If it's -desirable to populate the processed image directly and/or download it, it -should provide an appropriate setter and/or advertise -@ref GlyphCacheFeature::ProcessedImageDownload as well and implement +it should advertise that with @ref GlyphCacheFeature::ImageProcessing and +implement @ref doSetProcessedImage() as well. If it's desirable and possible to +download the processed image as well, it should advertise +@ref GlyphCacheFeature::ProcessedImageDownload and implement @ref doProcessedImage(). The public @ref flushImage() function already does checking for rectangle -bounds so it's not needed to do it again inside @ref doSetImage(). +bounds so it's not needed to do it again inside @ref doSetImage(), similarly +the bounds checking is done for @ref doSetProcessedImage(). */ class MAGNUM_TEXT_EXPORT AbstractGlyphCache { public: @@ -291,7 +296,11 @@ class MAGNUM_TEXT_EXPORT AbstractGlyphCache { * the default. * @m_since_latest * - * The @p size is expected to be non-zero. + * The @p size is expected to be non-zero. If the implementation + * advertises @ref GlyphCacheFeature::ImageProcessing, the + * @ref processedFormat() and @ref processedSize() is the same as + * @p format and @p size, use @ref AbstractGlyphCache(PixelFormat, const Vector3i&, PixelFormat, const Vector2i&, const Vector2i&) + * to specify different values. * @see @ref AbstractGlyphCache(PixelFormat, const Vector2i&, const Vector2i&) */ #ifdef DOXYGEN_GENERATING_OUTPUT @@ -323,6 +332,55 @@ class MAGNUM_TEXT_EXPORT AbstractGlyphCache { explicit AbstractGlyphCache(PixelFormat format, const Vector2i& size); #endif + /** + * @brief Construct a 2D array glyph cache with a specific processed format and size + * @param format Source image format + * @param size Source image size in pixels + * @param processedFormat Processed image format + * @param processedSize Processed image size in pixels + * @param padding Padding around every glyph in pixelss. See + * @ref Text-AbstractGlyphCache-padding for more information about + * the default. + * @m_since_latest + * + * The @p size and @p processedSize is expected to be non-zero, depth + * of processed size is implicitly the same as in @p size. If the + * implementation doesn't advertise @ref GlyphCacheFeature::ImageProcessing, + * the @p processedFormat and @p processedSize are unused. + * @see @ref AbstractGlyphCache(PixelFormat, const Vector3i&, const Vector2i&), + * @ref AbstractGlyphCache(PixelFormat, const Vector2i&, PixelFormat, const Vector2i&, const Vector2i&) + */ + #ifdef DOXYGEN_GENERATING_OUTPUT + explicit AbstractGlyphCache(PixelFormat format, const Vector3i& size, PixelFormat processedFormat, const Vector2i& processedSize, const Vector2i& padding = Vector2i{1}); + #else + /* To not need to include Vector */ + explicit AbstractGlyphCache(PixelFormat format, const Vector3i& size, PixelFormat processedFormat, const Vector2i& processedSize, const Vector2i& padding); + explicit AbstractGlyphCache(PixelFormat format, const Vector3i& size, PixelFormat processedFormat, const Vector2i& processedSize); + #endif + + /** + * @brief Construct a 2D glyph cache with a specific processed format and size + * @param format Source image format + * @param size Source image size in pixels + * @param processedFormat Processed image format + * @param processedSize Processed image size in pixels + * @param padding Padding around every glyph in pixels. See + * @ref Text-AbstractGlyphCache-padding for more information about + * the default. + * @m_since_latest + * + * Equivalent to calling + * @ref AbstractGlyphCache(PixelFormat, const Vector3i&, PixelFormat, const Vector2i&, const Vector2i&) + * with @p size depth set to @cpp 1 @ce. + */ + #ifdef DOXYGEN_GENERATING_OUTPUT + explicit AbstractGlyphCache(PixelFormat format, const Vector2i& size, PixelFormat processedFormat, const Vector2i& processedSize, const Vector2i& padding = Vector2i{1}); + #else + /* To not need to include Vector */ + explicit AbstractGlyphCache(PixelFormat format, const Vector2i& size, PixelFormat processedFormat, const Vector2i& processedSize, const Vector2i& padding); + explicit AbstractGlyphCache(PixelFormat format, const Vector2i& size, PixelFormat processedFormat, const Vector2i& processedSize); + #endif + #ifdef MAGNUM_BUILD_DEPRECATED /** * @brief Construct a 2D glyph cache @@ -386,23 +444,53 @@ class MAGNUM_TEXT_EXPORT AbstractGlyphCache { GlyphCacheFeatures features() const { return doFeatures(); } /** - * @brief Glyph cache texture format + * @brief Glyph cache format * @m_since_latest * * Corresponds to the format of the image view returned from - * @ref image(). + * @ref image(). Note that if the implementation advertises + * @ref GlyphCacheFeature::ImageProcessing, the format actually used + * for rendering exposed in @ref processedFormat() may be different + * from the input format. + * @see @ref features(), @ref size() */ PixelFormat format() const; /** - * @brief Glyph cache texture size + * @brief Processed glyph cache format + * @m_since_latest + * + * Corresponds to the format of the image view returned from + * @ref processedImage(), if @ref GlyphCacheFeature::ImageProcessing is + * supported. + * @see @ref features(), @ref format(), @ref processedSize() + */ + PixelFormat processedFormat() const; + + /** + * @brief Glyph cache size * @m_since_latest * * Corresponds to the size of the image view returned from - * @ref image(). + * @ref image(). Note that if the implementation advertises + * @ref GlyphCacheFeature::ImageProcessing, the size actually used for + * rendering exposed in @ref processedSize() may be different from + * the input size. + * @see @ref features(), @ref format() */ Vector3i size() const; + /** + * @brief Processed glyph cache size + * @m_since_latest + * + * Corresponds to the size of the image view returned from + * @ref processedImage(), if @ref GlyphCacheFeature::ImageProcessing is + * supported. The depth is always the same as in @ref size(). + * @see @ref features(), @ref processedFormat() + */ + Vector3i processedSize() const; + #ifdef MAGNUM_BUILD_DEPRECATED /** * @brief 2D glyph cache texture size @@ -701,10 +789,35 @@ class MAGNUM_TEXT_EXPORT AbstractGlyphCache { * @ref GlyphCacheFeature::ProcessedImageDownload is supported. For a * glyph cache without @ref GlyphCacheFeature::ImageProcessing you can * get the image directly through @ref image(). - * @see @ref features() + * @see @ref features(), @ref processedFormat(), @ref processedSize() */ Image3D processedImage(); + /** + * @brief Set processed cache image + * @m_since_latest + * + * Expects that the implementation supports + * @ref GlyphCacheFeature::ImageProcessing. The @ref ImageView::format() + * is expected to match @ref processedFormat(), the @p offset and + * @ref ImageView::size() are expected to be in bounds for + * @ref processedSize(). + * @see @ref features(), @ref processedImage() + */ + void setProcessedImage(const Vector3i& offset, const ImageView3D& image); + + /** + * @brief Set 2D processed cache image + * @m_since_latest + * + * Equivalent to calling + * @ref setProcessedImage(const Vector3i&, const ImageView3D&) with + * @p offset depth @cpp 0 @ce and @p image depth @cpp 1 @ce. Can be + * called only if @ref size() depth (and thus also @ref processedSize() + * depth) is @cpp 1 @ce. + */ + void setProcessedImage(const Vector2i& offset, const ImageView2D& image); + /** * @brief Query a cache-global glyph ID from a font-local glyph ID * @param fontId Font ID returned by @ref addFont() @@ -882,6 +995,32 @@ class MAGNUM_TEXT_EXPORT AbstractGlyphCache { */ virtual Image3D doProcessedImage(); + /** + * @brief Implementation for @ref setProcessedImage() + * @m_since_latest + * + * The @p offset and @ref ImageView::size() are guaranteed to be in + * bounds for @ref processedSize(). For a glyph cache with @ref size() + * depth (and thus also @ref processedSize() depth) being @cpp 1 @ce + * default implementation delegates to + * @ref doSetProcessedImage(const Vector2i&, const ImageView2D&). + * Implement either this or the other overload. + */ + virtual void doSetProcessedImage(const Vector3i& offset, const ImageView3D& image); + + /** + * @brief Implementation for @ref setProcessedImage() + * @m_since_latest + * + * Delegated to from the default implementation of + * @ref doSetProcessedImage(const Vector3i&, const ImageView3D&) if + * @ref size() depth (and thus also @ref processedSize() depth) is + * @cpp 1 @ce. The @p offset and @ref ImageView::size() are guaranteed + * to be in bounds for @ref processedSize(). Implement either this or + * the other overload. + */ + virtual void doSetProcessedImage(const Vector2i& offset, const ImageView2D& image); + struct State; Containers::Pointer _state; }; diff --git a/src/Magnum/Text/DistanceFieldGlyphCache.cpp b/src/Magnum/Text/DistanceFieldGlyphCache.cpp index 14d654dd4..2a0da15a5 100644 --- a/src/Magnum/Text/DistanceFieldGlyphCache.cpp +++ b/src/Magnum/Text/DistanceFieldGlyphCache.cpp @@ -39,17 +39,17 @@ namespace Magnum { namespace Text { -DistanceFieldGlyphCache::DistanceFieldGlyphCache(const Vector2i& sourceSize, const Vector2i& size, const UnsignedInt radius): +DistanceFieldGlyphCache::DistanceFieldGlyphCache(const Vector2i& size, const Vector2i& processedSize, const UnsignedInt radius): #if !(defined(MAGNUM_TARGET_GLES) && defined(MAGNUM_TARGET_GLES2)) - GlyphCache(GL::TextureFormat::R8, sourceSize, size, Vector2i(radius)), + GlyphCache(GL::TextureFormat::R8, size, processedSize, Vector2i(radius)), #elif !defined(MAGNUM_TARGET_WEBGL) /* Luminance is not renderable in most cases */ GlyphCache(GL::Context::current().isExtensionSupported() ? - GL::TextureFormat::R8 : GL::TextureFormat::RGB8, sourceSize, size, Vector2i(radius)), + GL::TextureFormat::R8 : GL::TextureFormat::RGB8, size, processedSize, Vector2i(radius)), #else - GlyphCache(GL::TextureFormat::RGB, sourceSize, size, Vector2i(radius)), + GlyphCache(GL::TextureFormat::RGB, size, processedSize, Vector2i(radius)), #endif - _size{size}, _distanceField{radius} + _distanceField{radius} { #ifndef MAGNUM_TARGET_GLES MAGNUM_ASSERT_GL_EXTENSION_SUPPORTED(GL::Extensions::ARB::texture_rg); @@ -58,9 +58,9 @@ DistanceFieldGlyphCache::DistanceFieldGlyphCache(const Vector2i& sourceSize, con /* Replicating the assertion from TextureTools::DistanceField so it gets checked during construction already instead of only later during the setImage() call */ - CORRADE_ASSERT(sourceSize % size == Vector2i{0} && - (sourceSize/size) % 2 == Vector2i{0}, - "Text::DistanceFieldGlyphCache: expected source and destination size ratio to be a multiple of 2, got" << Debug::packed << sourceSize << "and" << Debug::packed << size, ); + CORRADE_ASSERT(size % processedSize == Vector2i{0} && + (size/processedSize) % 2 == Vector2i{0}, + "Text::DistanceFieldGlyphCache: expected source and processed size ratio to be a multiple of 2, got" << Debug::packed << size << "and" << Debug::packed << processedSize, ); #if defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) /* Luminance is not renderable in most cases */ @@ -87,8 +87,8 @@ void DistanceFieldGlyphCache::doSetImage(const Vector2i& offset, const ImageView /* The constructor already checked that the ratio is an integer multiple, so this division should lead to no information loss */ - CORRADE_INTERNAL_ASSERT(size().xy() % _size == Vector2i{0}); - const Vector2i ratio = size().xy()/_size; + CORRADE_INTERNAL_ASSERT(size().xy() % processedSize().xy() == Vector2i{0}); + const Vector2i ratio = size().xy()/processedSize().xy(); /* Upload the input texture and create a distance field from it. On ES2 without EXT_unpack_subimage and on WebGL 1 there's no possibility to @@ -146,30 +146,26 @@ void DistanceFieldGlyphCache::doSetImage(const Vector2i& offset, const ImageView #endif } +#ifdef MAGNUM_BUILD_DEPRECATED void DistanceFieldGlyphCache::setDistanceFieldImage(const Vector2i& offset, const ImageView2D& image) { - CORRADE_ASSERT((offset >= Vector2i{} && offset + image.size() <= _size).all(), - "Text::DistanceFieldGlyphCache::setDistanceFieldImage():" << Range2Di::fromSize(offset, image.size()) << "out of range for texture size" << _size, ); - - #ifndef CORRADE_NO_ASSERT - const GL::PixelFormat format = GL::pixelFormat(image.format()); - #endif - #if !(defined(MAGNUM_TARGET_GLES) && defined(MAGNUM_TARGET_GLES2)) - CORRADE_ASSERT(format == GL::PixelFormat::Red, - "Text::DistanceFieldGlyphCache::setDistanceFieldImage(): expected" << GL::PixelFormat::Red << "but got" << format, ); - #else - #ifndef MAGNUM_TARGET_WEBGL - if(GL::Context::current().isExtensionSupported()) - CORRADE_ASSERT(format == GL::PixelFormat::Red, - "Text::DistanceFieldGlyphCache::setDistanceFieldImage(): expected" << GL::PixelFormat::Red << "but got" << format, ); - else - #endif - { - /* Luminance is not renderable in most cases */ - CORRADE_ASSERT(format == GL::PixelFormat::RGB, - "Text::DistanceFieldGlyphCache::setDistanceFieldImage(): expected" << GL::PixelFormat::RGB << "but got" << format, ); + /* The original function accepted GL pixel formats as well, try to + translate them back to the generic format. If that fails, pass the image + as-is let the base implementation deal with that instead. + + Replacing the whole view instead of just the format so we don't need to + do any special-casing for when the format stays implementation-specific + and requires a pixel size to be specified externally. */ + ImageView2D imageToUse = image; + if(isPixelFormatImplementationSpecific(image.format())) { + if(const Containers::Optional candidateFormat = GL::genericPixelFormat(pixelFormatUnwrap(image.format()), GL::PixelType(image.formatExtra()))) + imageToUse = ImageView2D{image.storage(), *candidateFormat, image.size(), image.data()}; } - #endif + setProcessedImage(offset, imageToUse); +} +#endif + +void DistanceFieldGlyphCache::doSetProcessedImage(const Vector2i& offset, const ImageView2D& image) { texture().setSubImage(0, offset, image); } diff --git a/src/Magnum/Text/DistanceFieldGlyphCache.h b/src/Magnum/Text/DistanceFieldGlyphCache.h index 60828aef7..97df6c56b 100644 --- a/src/Magnum/Text/DistanceFieldGlyphCache.h +++ b/src/Magnum/Text/DistanceFieldGlyphCache.h @@ -82,23 +82,23 @@ class MAGNUM_TEXT_EXPORT DistanceFieldGlyphCache: public GlyphCache { public: /** * @brief Constructor - * @param sourceSize Size of the source image - * @param size Resulting distance field texture size + * @param size Size of the source image + * @param processedSize Resulting distance field texture size * @param radius Distance field computation radius * * See @ref TextureTools::DistanceField for more information about the * parameters. Size restrictions from it apply here as well, in - * particular the ratio of @p sourceSize and @p size is expected to be - * a multiple of 2. + * particular the ratio of @p size and @p processedSize is expected to + * be a multiple of 2. * - * Sets the internal texture format to single-channel. On OpenGL ES - * 3.0+ and WebGL 2 uses @ref GL::TextureFormat::R8. On desktop OpenGL - * requires @gl_extension{ARB,texture_rg} (part of OpenGL 3.0), on ES2 - * uses @gl_extension{EXT,texture_rg} if available or - * @ref GL::TextureFormat::RGB as fallback, on WebGL 1 uses - * @ref GL::TextureFormat::RGB always. + * Sets the @ref processedFormat() to @ref PixelFormat::R8Unorm, if + * available. On OpenGL ES 3.0+ and WebGL 2 uses always. On desktop + * OpenGL requires @gl_extension{ARB,texture_rg} (part of OpenGL 3.0), + * on ES2 uses @gl_extension{EXT,texture_rg} if available and uses + * @ref PixelFormat::RGB8Unorm as fallback if not, on WebGL 1 uses + * @ref PixelFormat::RGB8Unorm always. */ - explicit DistanceFieldGlyphCache(const Vector2i& sourceSize, const Vector2i& size, UnsignedInt radius); + explicit DistanceFieldGlyphCache(const Vector2i& size, const Vector2i& processedSize, UnsignedInt radius); /** * @brief Construct without creating the internal state and the OpenGL texture object @@ -116,14 +116,18 @@ class MAGNUM_TEXT_EXPORT DistanceFieldGlyphCache: public GlyphCache { */ explicit DistanceFieldGlyphCache(NoCreateT) noexcept; + #ifdef MAGNUM_BUILD_DEPRECATED /** * @brief Distance field texture size * * Compared to @ref textureSize(), which is the size of the source * image, this function returns size of the resulting distance field * texture. + * @m_deprecated_since_latest Use @ref processedSize() instead. */ - Vector2i distanceFieldTextureSize() const { return _size; } + CORRADE_DEPRECATED("use processedSize() instead") Vector2i distanceFieldTextureSize() const { + return processedSize().xy(); + } /** * @brief Set a distance field cache image @@ -132,18 +136,20 @@ class MAGNUM_TEXT_EXPORT DistanceFieldGlyphCache: public GlyphCache { * field image to given offset in the distance field texture. The * @p offset and @ref ImageView::size() are expected to be in bounds * for @ref distanceFieldTextureSize(). + * @m_deprecated_since_latest Use @ref setProcessedImage() instead. */ - void setDistanceFieldImage(const Vector2i& offset, const ImageView2D& image); + CORRADE_DEPRECATED("use setProcessedImage() instead") void setDistanceFieldImage(const Vector2i& offset, const ImageView2D& image); + #endif private: MAGNUM_TEXT_LOCAL GlyphCacheFeatures doFeatures() const override; MAGNUM_TEXT_LOCAL void doSetImage(const Vector2i& offset, const ImageView2D& image) override; + MAGNUM_TEXT_LOCAL void doSetProcessedImage(const Vector2i& offset, const ImageView2D& image) override; #ifndef MAGNUM_TARGET_GLES MAGNUM_TEXT_LOCAL Image3D doProcessedImage() override; #endif - Vector2i _size; TextureTools::DistanceField _distanceField; }; diff --git a/src/Magnum/Text/GlyphCache.cpp b/src/Magnum/Text/GlyphCache.cpp index b9bbb00d9..b159f711e 100644 --- a/src/Magnum/Text/GlyphCache.cpp +++ b/src/Magnum/Text/GlyphCache.cpp @@ -40,23 +40,23 @@ GlyphCache::GlyphCache(const GL::TextureFormat internalFormat, const Vector2i& s /* The unconditional Optional unwrap in here two may assert in rare cases. Let's hope it doesn't in practice. */ -GlyphCache::GlyphCache(const GL::TextureFormat internalFormat, const Vector2i& originalSize, const Vector2i& size, const Vector2i& padding): AbstractGlyphCache{*GL::genericPixelFormat(internalFormat), originalSize, padding} { +GlyphCache::GlyphCache(const GL::TextureFormat internalFormat, const Vector2i& size, const Vector2i& processedSize, const Vector2i& padding): AbstractGlyphCache{*GL::genericPixelFormat(internalFormat), size, *GL::genericPixelFormat(internalFormat), processedSize, padding} { /* Initialize the texture */ _texture.setWrapping(GL::SamplerWrapping::ClampToEdge) .setMinificationFilter(GL::SamplerFilter::Linear) .setMagnificationFilter(GL::SamplerFilter::Linear) - .setStorage(1, internalFormat, size); + .setStorage(1, internalFormat, processedSize); } GlyphCache::GlyphCache(const Vector2i& size, const Vector2i& padding): GlyphCache{size, size, padding} {} -GlyphCache::GlyphCache(const Vector2i& originalSize, const Vector2i& size, const Vector2i& padding): GlyphCache{ +GlyphCache::GlyphCache(const Vector2i& size, const Vector2i& processedSize, const Vector2i& padding): GlyphCache{ #ifndef MAGNUM_TARGET_GLES2 GL::TextureFormat::R8, #else GL::TextureFormat::Luminance, #endif - originalSize, size, padding} + size, processedSize, padding} { #ifndef MAGNUM_TARGET_GLES MAGNUM_ASSERT_GL_EXTENSION_SUPPORTED(GL::Extensions::ARB::texture_rg); diff --git a/src/Magnum/Text/GlyphCache.h b/src/Magnum/Text/GlyphCache.h index b8db17cd9..97f2fd0dc 100644 --- a/src/Magnum/Text/GlyphCache.h +++ b/src/Magnum/Text/GlyphCache.h @@ -66,22 +66,23 @@ class MAGNUM_TEXT_EXPORT GlyphCache: public AbstractGlyphCache { /** * @brief Constructor * @param internalFormat Internal texture format - * @param originalSize Unscaled glyph cache texture size in pixels - * @param size Actual glyph cache texture size in pixels + * @param size Source glyph cache texture size in pixels + * @param processedSize Processed glyph cache texture size in + * pixels * @param padding Padding around every glyph in pixels * - * All glyphs parameters are saved relative to @p originalSize, - * although the actual glyph cache texture has @p size. Glyph + * All glyphs parameters are saved relative to @p size, + * although the actual glyph cache texture has @p processedSize. Glyph * @p padding can be used to account for e.g. glyph shadows. */ - explicit GlyphCache(GL::TextureFormat internalFormat, const Vector2i& originalSize, const Vector2i& size, const Vector2i& padding); + explicit GlyphCache(GL::TextureFormat internalFormat, const Vector2i& size, const Vector2i& processedSize, const Vector2i& padding); /** * @brief Constructor * - * Same as calling the above with @p originalSize and @p size being set - * to the same value. See @ref Text-AbstractGlyphCache-padding for more - * information about the default @p padding. + * Same as calling the above with @p size and @p processedSize being + * set to the same value. See @ref Text-AbstractGlyphCache-padding for + * more information about the default @p padding. */ explicit GlyphCache(GL::TextureFormat internalFormat, const Vector2i& size, const Vector2i& padding = Vector2i{1}); @@ -98,14 +99,14 @@ class MAGNUM_TEXT_EXPORT GlyphCache: public AbstractGlyphCache { * @ref GlyphCache(GL::TextureFormat, const Vector2i&, const Vector2i&) * for an alternative. */ - explicit GlyphCache(const Vector2i& originalSize, const Vector2i& size, const Vector2i& padding); + explicit GlyphCache(const Vector2i& size, const Vector2i& processedSize, const Vector2i& padding); /** * @brief Constructor * - * Same as calling the above with @p originalSize and @p size being set - * to the same value. See @ref Text-AbstractGlyphCache-padding for more - * information about the default @p padding. + * Same as calling the above with @p size and @p processedSize being + * set to the same value. See @ref Text-AbstractGlyphCache-padding for + * more information about the default @p padding. */ explicit GlyphCache(const Vector2i& size, const Vector2i& padding = Vector2i{1}); diff --git a/src/Magnum/Text/Test/AbstractGlyphCacheTest.cpp b/src/Magnum/Text/Test/AbstractGlyphCacheTest.cpp index 1cc102187..130839e61 100644 --- a/src/Magnum/Text/Test/AbstractGlyphCacheTest.cpp +++ b/src/Magnum/Text/Test/AbstractGlyphCacheTest.cpp @@ -60,6 +60,10 @@ struct AbstractGlyphCacheTest: TestSuite::Tester { void constructNoPadding(); void construct2D(); void construct2DNoPadding(); + void constructProcessed(); + void constructProcessedNoPadding(); + void constructProcessed2D(); + void constructProcessed2DNoPadding(); #ifdef MAGNUM_BUILD_DEPRECATED void constructDeprecated(); void constructDeprecatedNoPadding(); @@ -130,6 +134,15 @@ struct AbstractGlyphCacheTest: TestSuite::Tester { void processedImageNotSupported(); void processedImageNotImplemented(); + void setProcessedImage(); + void setProcessedImage2D(); + void setProcessedImage2DPassthrough2D(); + void setProcessedImageNotImplemented(); + void setProcessedImagePassthrough2DNotImplemented(); + void setProcessedImageOutOfRange(); + void setProcessedImageInvalidFormat(); + void setProcessedImage2DNot2D(); + void access(); void accessBatch(); void accessInvalid(); @@ -143,9 +156,11 @@ struct AbstractGlyphCacheTest: TestSuite::Tester { const struct { const char* name; Vector2i padding; + bool differentProcessedFormatSize; } FlushImageData[]{ - {"", {}}, - {"with padding", {2, 3}}, + {"", {}, false}, + {"with padding", {2, 3}, false}, + {"with different processed format and size", {}, false} }; const struct { @@ -156,6 +171,14 @@ const struct { {"no processed image download", GlyphCacheFeature::ImageProcessing}, }; +const struct { + const char* name; + Vector2i padding; +} SetProcessedImageOutOfRangeData[]{ + {"", {}}, + {"with padding", {2, 3}}, +}; + AbstractGlyphCacheTest::AbstractGlyphCacheTest() { addTests({&AbstractGlyphCacheTest::debugFeature, &AbstractGlyphCacheTest::debugFeatures, @@ -165,6 +188,10 @@ AbstractGlyphCacheTest::AbstractGlyphCacheTest() { &AbstractGlyphCacheTest::constructNoPadding, &AbstractGlyphCacheTest::construct2D, &AbstractGlyphCacheTest::construct2DNoPadding, + &AbstractGlyphCacheTest::constructProcessed, + &AbstractGlyphCacheTest::constructProcessedNoPadding, + &AbstractGlyphCacheTest::constructProcessed2D, + &AbstractGlyphCacheTest::constructProcessed2DNoPadding, #ifdef MAGNUM_BUILD_DEPRECATED &AbstractGlyphCacheTest::constructDeprecated, &AbstractGlyphCacheTest::constructDeprecatedNoPadding, @@ -246,6 +273,18 @@ AbstractGlyphCacheTest::AbstractGlyphCacheTest() { addTests({&AbstractGlyphCacheTest::processedImageNotImplemented, + &AbstractGlyphCacheTest::setProcessedImage, + &AbstractGlyphCacheTest::setProcessedImage2D, + &AbstractGlyphCacheTest::setProcessedImage2DPassthrough2D, + &AbstractGlyphCacheTest::setProcessedImageNotImplemented, + &AbstractGlyphCacheTest::setProcessedImagePassthrough2DNotImplemented}); + + addInstancedTests({&AbstractGlyphCacheTest::setProcessedImageOutOfRange}, + Containers::arraySize(SetProcessedImageOutOfRangeData)); + + addTests({&AbstractGlyphCacheTest::setProcessedImageInvalidFormat, + &AbstractGlyphCacheTest::setProcessedImage2DNot2D, + &AbstractGlyphCacheTest::access, &AbstractGlyphCacheTest::accessBatch, &AbstractGlyphCacheTest::accessInvalid, @@ -285,10 +324,21 @@ struct DummyGlyphCache: AbstractGlyphCache { void doSetImage(const Vector2i&, const ImageView2D&) override {} }; +struct DummyProcessingGlyphCache: AbstractGlyphCache { + using AbstractGlyphCache::AbstractGlyphCache; + + GlyphCacheFeatures doFeatures() const override { + return GlyphCacheFeature::ImageProcessing; + } + void doSetImage(const Vector2i&, const ImageView2D&) override {} +}; + void AbstractGlyphCacheTest::construct() { DummyGlyphCache cache{PixelFormat::R32F, {1024, 512, 3}, {2, 5}}; CORRADE_COMPARE(cache.format(), PixelFormat::R32F); CORRADE_COMPARE(cache.size(), (Vector3i{1024, 512, 3})); + CORRADE_COMPARE(cache.processedFormat(), PixelFormat::R32F); + CORRADE_COMPARE(cache.processedSize(), (Vector3i{1024, 512, 3})); CORRADE_COMPARE(cache.padding(), (Vector2i{2, 5})); CORRADE_COMPARE(cache.fontCount(), 0); CORRADE_COMPARE(cache.glyphCount(), 1); @@ -325,6 +375,8 @@ void AbstractGlyphCacheTest::constructNoPadding() { DummyGlyphCache cache{PixelFormat::R32F, {1024, 512, 3}}; CORRADE_COMPARE(cache.format(), PixelFormat::R32F); CORRADE_COMPARE(cache.size(), (Vector3i{1024, 512, 3})); + CORRADE_COMPARE(cache.processedFormat(), PixelFormat::R32F); + CORRADE_COMPARE(cache.processedSize(), (Vector3i{1024, 512, 3})); /* 1 by default to avoid artifacts */ CORRADE_COMPARE(cache.padding(), Vector2i{1}); CORRADE_COMPARE(cache.fontCount(), 0); @@ -357,6 +409,8 @@ void AbstractGlyphCacheTest::construct2D() { DummyGlyphCache cache{PixelFormat::R32F, {1024, 512}, {2, 5}}; CORRADE_COMPARE(cache.format(), PixelFormat::R32F); CORRADE_COMPARE(cache.size(), (Vector3i{1024, 512, 1})); + CORRADE_COMPARE(cache.processedFormat(), PixelFormat::R32F); + CORRADE_COMPARE(cache.processedSize(), (Vector3i{1024, 512, 1})); CORRADE_COMPARE(cache.padding(), (Vector2i{2, 5})); CORRADE_COMPARE(cache.fontCount(), 0); /* Invalid glyph is always present */ @@ -373,6 +427,82 @@ void AbstractGlyphCacheTest::construct2DNoPadding() { DummyGlyphCache cache{PixelFormat::R32F, {1024, 512}}; CORRADE_COMPARE(cache.format(), PixelFormat::R32F); CORRADE_COMPARE(cache.size(), (Vector3i{1024, 512, 1})); + CORRADE_COMPARE(cache.processedFormat(), PixelFormat::R32F); + CORRADE_COMPARE(cache.processedSize(), (Vector3i{1024, 512, 1})); + /* 1 by default to avoid artifacts */ + CORRADE_COMPARE(cache.padding(), Vector2i{1}); + CORRADE_COMPARE(cache.fontCount(), 0); + /* Invalid glyph is always present */ + CORRADE_COMPARE(cache.glyphCount(), 1); + CORRADE_COMPARE(cache.atlas().size(), (Vector3i{1024, 512, 1})); + CORRADE_COMPARE(cache.atlas().filledSize(), (Vector3i{1024, 0, 1})); + CORRADE_COMPARE(cache.atlas().flags(), TextureTools::AtlasLandfillFlag::WidestFirst); + CORRADE_COMPARE(cache.atlas().padding(), Vector2i{1}); + + /* The rest shouldn't be any different */ +} + +void AbstractGlyphCacheTest::constructProcessed() { + DummyProcessingGlyphCache cache{PixelFormat::R32F, {1024, 512, 3}, PixelFormat::R16F, {256, 128}, {2, 5}}; + CORRADE_COMPARE(cache.format(), PixelFormat::R32F); + CORRADE_COMPARE(cache.size(), (Vector3i{1024, 512, 3})); + CORRADE_COMPARE(cache.processedFormat(), PixelFormat::R16F); + CORRADE_COMPARE(cache.processedSize(), (Vector3i{256, 128, 3})); + CORRADE_COMPARE(cache.padding(), (Vector2i{2, 5})); + CORRADE_COMPARE(cache.fontCount(), 0); + /* Invalid glyph is always present */ + CORRADE_COMPARE(cache.glyphCount(), 1); + CORRADE_COMPARE(cache.atlas().size(), (Vector3i{1024, 512, 3})); + CORRADE_COMPARE(cache.atlas().filledSize(), (Vector3i{1024, 512, 0})); + CORRADE_COMPARE(cache.atlas().flags(), TextureTools::AtlasLandfillFlag::WidestFirst); + CORRADE_COMPARE(cache.atlas().padding(), (Vector2i{2, 5})); + + /* The rest shouldn't be any different */ +} + +void AbstractGlyphCacheTest::constructProcessedNoPadding() { + DummyProcessingGlyphCache cache{PixelFormat::R32F, {1024, 512, 3}, PixelFormat::R16F, {256, 128}}; + CORRADE_COMPARE(cache.format(), PixelFormat::R32F); + CORRADE_COMPARE(cache.size(), (Vector3i{1024, 512, 3})); + CORRADE_COMPARE(cache.processedFormat(), PixelFormat::R16F); + CORRADE_COMPARE(cache.processedSize(), (Vector3i{256, 128, 3})); + /* 1 by default to avoid artifacts */ + CORRADE_COMPARE(cache.padding(), Vector2i{1}); + CORRADE_COMPARE(cache.fontCount(), 0); + /* Invalid glyph is always present */ + CORRADE_COMPARE(cache.glyphCount(), 1); + CORRADE_COMPARE(cache.atlas().size(), (Vector3i{1024, 512, 3})); + CORRADE_COMPARE(cache.atlas().filledSize(), (Vector3i{1024, 512, 0})); + CORRADE_COMPARE(cache.atlas().flags(), TextureTools::AtlasLandfillFlag::WidestFirst); + CORRADE_COMPARE(cache.atlas().padding(), Vector2i{1}); + + /* The rest shouldn't be any different */ +} + +void AbstractGlyphCacheTest::constructProcessed2D() { + DummyProcessingGlyphCache cache{PixelFormat::R32F, {1024, 512}, PixelFormat::R16F, {256, 128}, {2, 5}}; + CORRADE_COMPARE(cache.format(), PixelFormat::R32F); + CORRADE_COMPARE(cache.size(), (Vector3i{1024, 512, 1})); + CORRADE_COMPARE(cache.processedFormat(), PixelFormat::R16F); + CORRADE_COMPARE(cache.processedSize(), (Vector3i{256, 128, 1})); + CORRADE_COMPARE(cache.padding(), (Vector2i{2, 5})); + CORRADE_COMPARE(cache.fontCount(), 0); + /* Invalid glyph is always present */ + CORRADE_COMPARE(cache.glyphCount(), 1); + CORRADE_COMPARE(cache.atlas().size(), (Vector3i{1024, 512, 1})); + CORRADE_COMPARE(cache.atlas().filledSize(), (Vector3i{1024, 0, 1})); + CORRADE_COMPARE(cache.atlas().flags(), TextureTools::AtlasLandfillFlag::WidestFirst); + CORRADE_COMPARE(cache.atlas().padding(), (Vector2i{2, 5})); + + /* The rest shouldn't be any different */ +} + +void AbstractGlyphCacheTest::constructProcessed2DNoPadding() { + DummyProcessingGlyphCache cache{PixelFormat::R32F, {1024, 512}, PixelFormat::R16F, {256, 128}}; + CORRADE_COMPARE(cache.format(), PixelFormat::R32F); + CORRADE_COMPARE(cache.size(), (Vector3i{1024, 512, 1})); + CORRADE_COMPARE(cache.processedFormat(), PixelFormat::R16F); + CORRADE_COMPARE(cache.processedSize(), (Vector3i{256, 128, 1})); /* 1 by default to avoid artifacts */ CORRADE_COMPARE(cache.padding(), Vector2i{1}); CORRADE_COMPARE(cache.fontCount(), 0); @@ -460,11 +590,17 @@ void AbstractGlyphCacheTest::constructZeroSize() { std::ostringstream out; Error redirectError{&out}; - DummyGlyphCache{PixelFormat::R8Unorm, {2, 0}}; - DummyGlyphCache{PixelFormat::R8Unorm, {0, 2}}; + DummyGlyphCache{PixelFormat::R8Unorm, {2, 0, 1}}; + DummyGlyphCache{PixelFormat::R8Unorm, {0, 2, 1}}; + DummyGlyphCache{PixelFormat::R8Unorm, {2, 2, 0}}; + DummyGlyphCache{PixelFormat::R8Unorm, {2, 2}, PixelFormat::R8Unorm, {2, 0}}; + DummyGlyphCache{PixelFormat::R8Unorm, {2, 2}, PixelFormat::R8Unorm, {0, 2}}; CORRADE_COMPARE(out.str(), "Text::AbstractGlyphCache: expected non-zero size, got {2, 0, 1}\n" - "Text::AbstractGlyphCache: expected non-zero size, got {0, 2, 1}\n"); + "Text::AbstractGlyphCache: expected non-zero size, got {0, 2, 1}\n" + "Text::AbstractGlyphCache: expected non-zero size, got {2, 2, 0}\n" + "Text::AbstractGlyphCache: expected non-zero processed size, got {2, 0}\n" + "Text::AbstractGlyphCache: expected non-zero processed size, got {0, 2}\n"); } void AbstractGlyphCacheTest::constructNoCreate() { @@ -1033,7 +1169,7 @@ void AbstractGlyphCacheTest::flushImage() { auto&& data = FlushImageData[testCaseInstanceId()]; setTestCaseDescription(data.name); - struct: AbstractGlyphCache { + struct Cache: AbstractGlyphCache { using AbstractGlyphCache::AbstractGlyphCache; GlyphCacheFeatures doFeatures() const override { return {}; } @@ -1089,7 +1225,12 @@ void AbstractGlyphCacheTest::flushImage() { } bool called = false; - } cache{PixelFormat::R8Snorm, {45, 35, 5}, data.padding}; + } cache{NoCreate}; + + if(data.differentProcessedFormatSize) + cache = Cache{PixelFormat::R8Snorm, {45, 35, 5}, PixelFormat::RG32F, {12, 34}}; + else + cache = Cache{PixelFormat::R8Snorm, {45, 35, 5}, data.padding}; /* Capture correct function name */ CORRADE_VERIFY(true); @@ -1117,7 +1258,7 @@ void AbstractGlyphCacheTest::flushImageWholeArea() { checking. The padding doesn't affect the call in this case -- the actual range is always the whole image. */ - struct: AbstractGlyphCache { + struct Cache: AbstractGlyphCache { using AbstractGlyphCache::AbstractGlyphCache; GlyphCacheFeatures doFeatures() const override { return {}; } @@ -1143,7 +1284,12 @@ void AbstractGlyphCacheTest::flushImageWholeArea() { } bool called = false; - } cache{PixelFormat::R8Snorm, {3, 2, 2}, data.padding}; + } cache{NoCreate}; + + if(data.differentProcessedFormatSize) + cache = Cache{PixelFormat::R8Snorm, {3, 2, 5}, PixelFormat::RG32F, {1, 1}}; + else + cache = Cache{PixelFormat::R8Snorm, {3, 2, 2}, data.padding}; /* Capture correct function name */ CORRADE_VERIFY(true); @@ -1169,7 +1315,7 @@ void AbstractGlyphCacheTest::flushImageLayer() { /* Single slice subset of flushImage() */ - struct: AbstractGlyphCache { + struct Cache: AbstractGlyphCache { using AbstractGlyphCache::AbstractGlyphCache; GlyphCacheFeatures doFeatures() const override { return {}; } @@ -1205,7 +1351,12 @@ void AbstractGlyphCacheTest::flushImageLayer() { } bool called = false; - } cache{PixelFormat::R8Snorm, {45, 35, 5}, data.padding}; + } cache{NoCreate}; + + if(data.differentProcessedFormatSize) + cache = Cache{PixelFormat::R8Snorm, {45, 35, 5}, PixelFormat::RG32F, {12, 34}}; + else + cache = Cache{PixelFormat::R8Snorm, {45, 35, 5}, data.padding}; /* Capture correct function name */ CORRADE_VERIFY(true); @@ -1228,7 +1379,7 @@ void AbstractGlyphCacheTest::flushImage2D() { /* Like flushImageLayer() but reduced to two dimensions */ - struct: AbstractGlyphCache { + struct Cache: AbstractGlyphCache { using AbstractGlyphCache::AbstractGlyphCache; GlyphCacheFeatures doFeatures() const override { return {}; } @@ -1264,7 +1415,12 @@ void AbstractGlyphCacheTest::flushImage2D() { } bool called = false; - } cache{PixelFormat::R8Snorm, {45, 35}, data.padding}; + } cache{NoCreate}; + + if(data.differentProcessedFormatSize) + cache = Cache{PixelFormat::R8Snorm, {45, 35}, PixelFormat::RG32F, {12, 34}}; + else + cache = Cache{PixelFormat::R8Snorm, {45, 35}, data.padding}; /* Capture correct function name */ CORRADE_VERIFY(true); @@ -1287,7 +1443,7 @@ void AbstractGlyphCacheTest::flushImage2DPassthrough2D() { /* Like flushImage2D() but with 2D doSetImage() */ - struct: AbstractGlyphCache { + struct Cache: AbstractGlyphCache { using AbstractGlyphCache::AbstractGlyphCache; GlyphCacheFeatures doFeatures() const override { return {}; } @@ -1323,7 +1479,12 @@ void AbstractGlyphCacheTest::flushImage2DPassthrough2D() { } bool called = false; - } cache{PixelFormat::R8Snorm, {45, 35}, data.padding}; + } cache{NoCreate}; + + if(data.differentProcessedFormatSize) + cache = Cache{PixelFormat::R8Snorm, {45, 35}, PixelFormat::RG32F, {12, 34}}; + else + cache = Cache{PixelFormat::R8Snorm, {45, 35}, data.padding}; /* Capture correct function name */ CORRADE_VERIFY(true); @@ -1384,8 +1545,14 @@ void AbstractGlyphCacheTest::flushImageOutOfRange() { CORRADE_SKIP_IF_NO_ASSERT(); - /* The padding should not have any effect on the check */ - DummyGlyphCache cache{PixelFormat::R32F, {1024, 512, 8}, data.padding}; + DummyGlyphCache cache{NoCreate}; + + /* Neither the padding nor the processed size should not have any effect on + the check */ + if(data.differentProcessedFormatSize) + cache = DummyGlyphCache{PixelFormat::R32F, {1024, 512, 8}, PixelFormat::R8Snorm, {1536, 768}}; + else + cache = DummyGlyphCache{PixelFormat::R32F, {1024, 512, 8}, data.padding}; std::ostringstream out; Error redirectError{&out}; @@ -1614,6 +1781,253 @@ void AbstractGlyphCacheTest::processedImageNotImplemented() { CORRADE_COMPARE(out.str(), "Text::AbstractGlyphCache::processedImage(): feature advertised but not implemented\n"); } +void AbstractGlyphCacheTest::setProcessedImage() { + struct: AbstractGlyphCache { + using AbstractGlyphCache::AbstractGlyphCache; + + GlyphCacheFeatures doFeatures() const override { + return GlyphCacheFeature::ImageProcessing; + } + void doSetProcessedImage(const Vector3i& offset, const ImageView3D& image) override { + called = true; + + CORRADE_COMPARE(offset, (Vector3i{15, 30, 3})); + CORRADE_COMPARE(image.size(), (Vector3i{3, 2, 2})); + + char pixels0[]{ + 'a', 'b', 'c', 0, + 'd', 'e', 'f', 0, + }; + char pixels1[]{ + '0', '1', '2', 0, + '3', '4', '5', 0, + }; + CORRADE_COMPARE_AS(image.pixels()[0], + (ImageView2D{PixelFormat::R8Snorm, {3, 2}, pixels0}), + DebugTools::CompareImage); + CORRADE_COMPARE_AS(image.pixels()[1], + (ImageView2D{PixelFormat::R8Snorm, {3, 2}, pixels1}), + DebugTools::CompareImage); + } + + bool called = false; + } cache{PixelFormat::RGB16Unorm, {4, 3, 5}, PixelFormat::R8Snorm, {45, 35}}; + + /* Capture correct function name */ + CORRADE_VERIFY(true); + + char pixels[]{ + 'a', 'b', 'c', 0, + 'd', 'e', 'f', 0, + '0', '1', '2', 0, + '3', '4', '5', 0, + }; + cache.setProcessedImage({15, 30, 3}, ImageView3D{PixelFormat::R8Snorm, {3, 2, 2}, pixels}); + CORRADE_VERIFY(cache.called); +} + +void AbstractGlyphCacheTest::setProcessedImage2D() { + /* Like setProcessedImage() but reduced to two dimensions */ + + struct: AbstractGlyphCache { + using AbstractGlyphCache::AbstractGlyphCache; + + GlyphCacheFeatures doFeatures() const override { + return GlyphCacheFeature::ImageProcessing; + } + void doSetProcessedImage(const Vector3i& offset, const ImageView3D& image) override { + called = true; + + CORRADE_COMPARE(offset, (Vector3i{15, 30, 0})); + CORRADE_COMPARE(image.size(), (Vector3i{3, 2, 1})); + + char pixels0[]{ + 'a', 'b', 'c', 0, + 'd', 'e', 'f', 0, + }; + CORRADE_COMPARE_AS(image.pixels()[0], + (ImageView2D{PixelFormat::R8Snorm, {3, 2}, pixels0}), + DebugTools::CompareImage); + } + + bool called = false; + } cache{PixelFormat::RGB16Unorm, {4, 3}, PixelFormat::R8Snorm, {45, 35}}; + + /* Capture correct function name */ + CORRADE_VERIFY(true); + + char pixels[]{ + 'a', 'b', 'c', 0, + 'd', 'e', 'f', 0, + }; + cache.setProcessedImage({15, 30}, ImageView2D{PixelFormat::R8Snorm, {3, 2}, pixels}); + CORRADE_VERIFY(cache.called); +} + +void AbstractGlyphCacheTest::setProcessedImage2DPassthrough2D() { + /* Like setProcessedImage2D() but with 2D doSetProcessedImage() */ + + struct: AbstractGlyphCache { + using AbstractGlyphCache::AbstractGlyphCache; + + GlyphCacheFeatures doFeatures() const override { + return GlyphCacheFeature::ImageProcessing; + } + void doSetProcessedImage(const Vector2i& offset, const ImageView2D& image) override { + called = true; + + CORRADE_COMPARE(offset, (Vector2i{15, 30})); + CORRADE_COMPARE(image.size(), (Vector2i{3, 2})); + + char pixels0[]{ + 'a', 'b', 'c', 0, + 'd', 'e', 'f', 0, + }; + CORRADE_COMPARE_AS(image.pixels(), + (ImageView2D{PixelFormat::R8Snorm, {3, 2}, pixels0}), + DebugTools::CompareImage); + } + + bool called = false; + } cache{PixelFormat::RGB16Unorm, {4, 3}, PixelFormat::R8Snorm, {45, 35}}; + + /* Capture correct function name */ + CORRADE_VERIFY(true); + + char pixels[]{ + 'a', 'b', 'c', 0, + 'd', 'e', 'f', 0, + }; + cache.setProcessedImage({15, 30}, ImageView2D{PixelFormat::R8Snorm, {3, 2}, pixels}); + CORRADE_VERIFY(cache.called); +} + +void AbstractGlyphCacheTest::setProcessedImageNotImplemented() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractGlyphCache { + using AbstractGlyphCache::AbstractGlyphCache; + + GlyphCacheFeatures doFeatures() const override { + return GlyphCacheFeature::ImageProcessing; + } + + /* The 2D variant shouldn't be called on an array cache */ + void doSetProcessedImage(const Vector2i&, const ImageView2D&) override { + CORRADE_FAIL("This should not be called"); + } + } cache{PixelFormat::R32F, {1024, 512, 8}}; + + std::ostringstream out; + Error redirectError{&out}; + cache.setProcessedImage({}, ImageView3D{PixelFormat::R32F, {}}); + CORRADE_COMPARE(out.str(), "Text::AbstractGlyphCache::setProcessedImage(): feature advertised but not implemented\n"); +} + +void AbstractGlyphCacheTest::setProcessedImagePassthrough2DNotImplemented() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractGlyphCache { + using AbstractGlyphCache::AbstractGlyphCache; + + GlyphCacheFeatures doFeatures() const override { + return GlyphCacheFeature::ImageProcessing; + } + + /* The 2D variant shouldn't be called on an array cache */ + void doSetProcessedImage(const Vector2i&, const ImageView2D&) override { + CORRADE_FAIL("This should not be called"); + } + } cache{PixelFormat::R32F, {1024, 512, 8}}; + + std::ostringstream out; + Error redirectError{&out}; + cache.setProcessedImage({}, ImageView3D{PixelFormat::R32F, {}}); + CORRADE_COMPARE(out.str(), "Text::AbstractGlyphCache::setProcessedImage(): feature advertised but not implemented\n"); +} + +void AbstractGlyphCacheTest::setProcessedImageOutOfRange() { + auto&& data = SetProcessedImageOutOfRangeData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + CORRADE_SKIP_IF_NO_ASSERT(); + + /* Like flushImage(), but for setProcessedImage() */ + + struct: AbstractGlyphCache { + using AbstractGlyphCache::AbstractGlyphCache; + + GlyphCacheFeatures doFeatures() const override { + return GlyphCacheFeature::ImageProcessing; + } + /* The source size and padding should not have any effect on the check */ + } cache{PixelFormat::RGBA32F, {1536, 768, 8}, PixelFormat::R8Snorm, {1024, 512}, data.padding}; + + /* Large enough data to fit in all cases below, 4-byte aligned rows */ + Containers::Array image{NoInit, 1012*5*2}; + + std::ostringstream out; + Error redirectError{&out}; + /* Negative min X, Y, layer */ + cache.setProcessedImage({-1, 30, 4}, + ImageView3D{PixelFormat::R8Snorm, {46, 5, 2}, image}); + cache.setProcessedImage({15, -1, 4}, + ImageView3D{PixelFormat::R8Snorm, {30, 36, 2}, image}); + cache.setProcessedImage({15, 30, -1}, + ImageView3D{PixelFormat::R8Snorm, {30, 5, 7}, image}); + /* Too large max X, Y, layer */ + cache.setProcessedImage({15, 30, 4}, + ImageView3D{PixelFormat::R8Snorm, {1010, 5, 2}}); + cache.setProcessedImage({15, 30, 4}, + ImageView3D{PixelFormat::R8Snorm, {30, 483, 2}}); + cache.setProcessedImage({15, 30, 4}, + ImageView3D{PixelFormat::R8Snorm, {30, 5, 5}}); + CORRADE_COMPARE_AS(out.str(), + "Text::AbstractGlyphCache::setProcessedImage(): {{-1, 30, 4}, {45, 35, 6}} out of range for size {1024, 512, 8}\n" + "Text::AbstractGlyphCache::setProcessedImage(): {{15, -1, 4}, {45, 35, 6}} out of range for size {1024, 512, 8}\n" + "Text::AbstractGlyphCache::setProcessedImage(): {{15, 30, -1}, {45, 35, 6}} out of range for size {1024, 512, 8}\n" + + "Text::AbstractGlyphCache::setProcessedImage(): {{15, 30, 4}, {1025, 35, 6}} out of range for size {1024, 512, 8}\n" + "Text::AbstractGlyphCache::setProcessedImage(): {{15, 30, 4}, {45, 513, 6}} out of range for size {1024, 512, 8}\n" + "Text::AbstractGlyphCache::setProcessedImage(): {{15, 30, 4}, {45, 35, 9}} out of range for size {1024, 512, 8}\n", + TestSuite::Compare::String); +} + +void AbstractGlyphCacheTest::setProcessedImageInvalidFormat() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractGlyphCache { + using AbstractGlyphCache::AbstractGlyphCache; + + GlyphCacheFeatures doFeatures() const override { + return GlyphCacheFeature::ImageProcessing; + } + /* The source format should not have any effect on the check */ + } cache{PixelFormat::RGBA32F, {1024, 512, 8}, PixelFormat::R8Snorm, {3, 2}}; + + std::ostringstream out; + Error redirectError{&out}; + cache.setProcessedImage({}, ImageView3D{PixelFormat::R8Unorm, {3, 2, 1}, "abcdefgh"}); + CORRADE_COMPARE(out.str(), "Text::AbstractGlyphCache::setProcessedImage(): expected PixelFormat::R8Snorm but got PixelFormat::R8Unorm\n"); +} + +void AbstractGlyphCacheTest::setProcessedImage2DNot2D() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractGlyphCache { + using AbstractGlyphCache::AbstractGlyphCache; + + GlyphCacheFeatures doFeatures() const override { + return GlyphCacheFeature::ImageProcessing; + } + } cache{PixelFormat::R8Unorm, {3, 2, 8}}; + + std::ostringstream out; + Error redirectError{&out}; + cache.setProcessedImage({}, ImageView2D{PixelFormat::R8Unorm, {3, 2}, "abcdefgh"}); + CORRADE_COMPARE(out.str(), "Text::AbstractGlyphCache::setProcessedImage(): use the 3D overload for an array glyph cache\n"); +} + void AbstractGlyphCacheTest::access() { /* Padding tested well enough in addGlyph(), resetting it back to 0 here */ DummyGlyphCache cache{PixelFormat::R32F, {1024, 512, 3}, {}}; diff --git a/src/Magnum/Text/Test/DistanceFieldGlyphCacheGLTest.cpp b/src/Magnum/Text/Test/DistanceFieldGlyphCacheGLTest.cpp index b2993e99c..9c2fe99f2 100644 --- a/src/Magnum/Text/Test/DistanceFieldGlyphCacheGLTest.cpp +++ b/src/Magnum/Text/Test/DistanceFieldGlyphCacheGLTest.cpp @@ -62,8 +62,10 @@ struct DistanceFieldGlyphCacheGLTest: GL::OpenGLTester { void setImage(); - void setDistanceFieldImage(); - void setDistanceFieldImageOutOfRange(); + void setProcessedImage(); + #ifdef MAGNUM_BUILD_DEPRECATED + void setDistanceFieldImageUnsupportedGLFormat(); + #endif PluginManager::Manager _manager{"nonexistent"}; }; @@ -99,6 +101,16 @@ const struct { {}}, }; +#ifdef MAGNUM_BUILD_DEPRECATED +const struct { + const char* name; + bool deprecated; + bool glPixelFormat; +} SetProcessedImageData[]{ + {"", false, false}, +}; +#endif + DistanceFieldGlyphCacheGLTest::DistanceFieldGlyphCacheGLTest() { addTests({&DistanceFieldGlyphCacheGLTest::construct, &DistanceFieldGlyphCacheGLTest::constructSizeRatioNotMultipleOfTwo, @@ -109,8 +121,14 @@ DistanceFieldGlyphCacheGLTest::DistanceFieldGlyphCacheGLTest() { addInstancedTests({&DistanceFieldGlyphCacheGLTest::setImage}, Containers::arraySize(SetImageData)); - addTests({&DistanceFieldGlyphCacheGLTest::setDistanceFieldImage, - &DistanceFieldGlyphCacheGLTest::setDistanceFieldImageOutOfRange}); + #ifndef MAGNUM_BUILD_DEPRECATED + addTests({&DistanceFieldGlyphCacheGLTest::setProcessedImage}); + #else + addInstancedTests({&DistanceFieldGlyphCacheGLTest::setProcessedImage}, + Containers::arraySize(SetProcessedImageData)); + + addTests({&DistanceFieldGlyphCacheGLTest::setDistanceFieldImageUnsupportedGLFormat}); + #endif /* Load the plugin directly from the build tree. Otherwise it's either static and already loaded or not present in the build tree */ @@ -127,7 +145,7 @@ void DistanceFieldGlyphCacheGLTest::construct() { MAGNUM_VERIFY_NO_GL_ERROR(); CORRADE_COMPARE(cache.size(), (Vector3i{1024, 2048, 1})); - CORRADE_COMPARE(cache.distanceFieldTextureSize(), (Vector2i{128, 256})); + CORRADE_COMPARE(cache.processedSize(), (Vector3i{128, 256, 1})); #ifndef MAGNUM_TARGET_GLES CORRADE_COMPARE(cache.texture().imageSize(0), (Vector2i{128, 256})); #endif @@ -149,12 +167,13 @@ void DistanceFieldGlyphCacheGLTest::constructSizeRatioNotMultipleOfTwo() { /* Almost correct except that it's not an integer multiply */ DistanceFieldGlyphCache{Vector2i{23*14}, {22, 23}, 4}; DistanceFieldGlyphCache{Vector2i{23*14}, {23, 22}, 4}; - CORRADE_COMPARE(out.str(), - "Text::DistanceFieldGlyphCache: expected source and destination size ratio to be a multiple of 2, got {322, 322} and {46, 46}\n" - "Text::DistanceFieldGlyphCache: expected source and destination size ratio to be a multiple of 2, got {322, 322} and {46, 23}\n" - "Text::DistanceFieldGlyphCache: expected source and destination size ratio to be a multiple of 2, got {322, 322} and {23, 46}\n" - "Text::DistanceFieldGlyphCache: expected source and destination size ratio to be a multiple of 2, got {322, 322} and {22, 23}\n" - "Text::DistanceFieldGlyphCache: expected source and destination size ratio to be a multiple of 2, got {322, 322} and {23, 22}\n"); + CORRADE_COMPARE_AS(out.str(), + "Text::DistanceFieldGlyphCache: expected source and processed size ratio to be a multiple of 2, got {322, 322} and {46, 46}\n" + "Text::DistanceFieldGlyphCache: expected source and processed size ratio to be a multiple of 2, got {322, 322} and {46, 23}\n" + "Text::DistanceFieldGlyphCache: expected source and processed size ratio to be a multiple of 2, got {322, 322} and {23, 46}\n" + "Text::DistanceFieldGlyphCache: expected source and processed size ratio to be a multiple of 2, got {322, 322} and {22, 23}\n" + "Text::DistanceFieldGlyphCache: expected source and processed size ratio to be a multiple of 2, got {322, 322} and {23, 22}\n", + TestSuite::Compare::String); } void DistanceFieldGlyphCacheGLTest::constructCopy() { @@ -213,22 +232,7 @@ void DistanceFieldGlyphCacheGLTest::setImage() { /** @todo ugh have slicing on images directly already */ MutableImageView2D actual{actual3.format(), actual3.size().xy(), actual3.data()}; #else - /* Pick a format that matches the internal texture format. This is rather - shitty, TBH. */ - #if !(defined(MAGNUM_TARGET_GLES) && defined(MAGNUM_TARGET_GLES2)) - const GL::PixelFormat format = GL::PixelFormat::Red; - #else - GL::PixelFormat format; - #ifndef MAGNUM_TARGET_WEBGL - if(GL::Context::current().isExtensionSupported()) { - format = GL::PixelFormat::Red; - } else - #endif - { - format = GL::PixelFormat::RGB; - } - #endif - Image2D actual = DebugTools::textureSubImage(cache.texture(), 0, {{}, data.size}, {format, GL::PixelType::UnsignedByte}); + Image2D actual = DebugTools::textureSubImage(cache.texture(), 0, {{}, data.size}, cache.processedFormat()); #endif MAGNUM_VERIFY_NO_GL_ERROR(); @@ -244,38 +248,51 @@ void DistanceFieldGlyphCacheGLTest::setImage() { (DebugTools::CompareImageToFile{_manager, 1.0f, 0.178f})); } -void DistanceFieldGlyphCacheGLTest::setDistanceFieldImage() { +void DistanceFieldGlyphCacheGLTest::setProcessedImage() { + #ifdef MAGNUM_BUILD_DEPRECATED + auto&& data = SetProcessedImageData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + #endif + DistanceFieldGlyphCache cache({64, 32}, {16, 8}, 16); - /* Pick a format that matches the internal texture format. This is rather - shitty, TBH. */ - #if !(defined(MAGNUM_TARGET_GLES) && defined(MAGNUM_TARGET_GLES2)) - const GL::PixelFormat format = GL::PixelFormat::Red; - #else - GL::PixelFormat format; - #ifndef MAGNUM_TARGET_WEBGL - if(GL::Context::current().isExtensionSupported()) { - format = GL::PixelFormat::Red; - } else - #endif - { - /* Ugh, don't want to bother implementing this */ + #ifdef MAGNUM_TARGET_GLES2 + /* Ugh, don't want to bother implementing this */ + if(cache.processedFormat() == PixelFormat::RGB8Unorm) CORRADE_SKIP("A three-component input is expected on ES2, skipping due to developer laziness."); - } #endif /* Clear the texture first, as it'd have random garbage otherwise */ UnsignedByte zeros[16*8]{}; - cache.setDistanceFieldImage({}, ImageView2D{format, GL::PixelType::UnsignedByte, {16, 8}, zeros}); + cache.setProcessedImage({}, ImageView2D{PixelFormat::R8Unorm, {16, 8}, zeros}); MAGNUM_VERIFY_NO_GL_ERROR(); - UnsignedByte data[]{ + UnsignedByte imageData[]{ 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, }; - cache.setDistanceFieldImage({8, 4}, ImageView2D{format, GL::PixelType::UnsignedByte, {8, 4}, data}); + + #ifdef MAGNUM_BUILD_DEPRECATED + if(data.deprecated) { + CORRADE_IGNORE_DEPRECATED_PUSH + if(data.glPixelFormat) + cache.setDistanceFieldImage({8, 4}, ImageView2D{ + #if !(defined(MAGNUM_TARGET_GLES2) && defined(MAGNUM_TARGET_WEBGL)) + GL::PixelFormat::Red, + #else + GL::PixelFormat::Luminance, + #endif + GL::PixelType::UnsignedByte, {8, 4}, imageData}); + else + cache.setDistanceFieldImage({8, 4}, ImageView2D{PixelFormat::R8Unorm, {8, 4}, imageData}); + CORRADE_IGNORE_DEPRECATED_POP + } else + #endif + { + cache.setProcessedImage({8, 4}, ImageView2D{PixelFormat::R8Unorm, {8, 4}, imageData}); + } MAGNUM_VERIFY_NO_GL_ERROR(); /* On GLES processedImage() isn't implemented as it'd mean creating a @@ -286,8 +303,7 @@ void DistanceFieldGlyphCacheGLTest::setDistanceFieldImage() { /** @todo ugh have slicing on images directly already */ MutableImageView2D actual{actual3.format(), actual3.size().xy(), actual3.data()}; #else - Image2D actualGL = DebugTools::textureSubImage(cache.texture(), 0, {{}, {16, 8}}, {format, GL::PixelType::UnsignedByte}); - ImageView2D actual{*GL::genericPixelFormat(format, GL::PixelType::UnsignedByte), actualGL.size(), actualGL.data()}; + Image2D actual = DebugTools::textureSubImage(cache.texture(), 0, {{}, {16, 8}}, cache.processedFormat()); #endif MAGNUM_VERIFY_NO_GL_ERROR(); @@ -306,30 +322,27 @@ void DistanceFieldGlyphCacheGLTest::setDistanceFieldImage() { DebugTools::CompareImage); } -void DistanceFieldGlyphCacheGLTest::setDistanceFieldImageOutOfRange() { +#ifdef MAGNUM_BUILD_DEPRECATED +void DistanceFieldGlyphCacheGLTest::setDistanceFieldImageUnsupportedGLFormat() { CORRADE_SKIP_IF_NO_ASSERT(); - DistanceFieldGlyphCache cache{{200, 400}, {100, 200}, 4}; - - /* This is fine. Not testing on ES2 as there it would need the complicated - format logic from above. */ - #if !(defined(MAGNUM_TARGET_GLES) && defined(MAGNUM_TARGET_GLES2)) - cache.setDistanceFieldImage({80, 175}, ImageView2D{PixelFormat::R8Unorm, {20, 25}}); - #endif + DistanceFieldGlyphCache cache{{4, 4}, {1, 1}, 4}; std::ostringstream out; Error redirectError{&out}; - cache.setDistanceFieldImage({81, 175}, ImageView2D{PixelFormat::R8Unorm, {20, 25}}); - cache.setDistanceFieldImage({80, 176}, ImageView2D{PixelFormat::R8Unorm, {20, 25}}); - cache.setDistanceFieldImage({-1, 175}, ImageView2D{PixelFormat::R8Unorm, {20, 25}}); - cache.setDistanceFieldImage({80, -1}, ImageView2D{PixelFormat::R8Unorm, {20, 25}}); + CORRADE_IGNORE_DEPRECATED_PUSH + /* Format that is convertible back to the generic format but isn't + supported */ + cache.setDistanceFieldImage({}, ImageView2D{GL::PixelFormat::RGBA, GL::PixelType::Float, {1, 1}, "hellohellohello"}); + /* Format that doesn't have a generic equivalent gets passed as-is */ + cache.setDistanceFieldImage({}, ImageView2D{GL::PixelFormat::RGBA, GL::PixelType::UnsignedShort5551, {1, 1}, "hello!!"}); + CORRADE_IGNORE_DEPRECATED_POP CORRADE_COMPARE_AS(out.str(), - "Text::DistanceFieldGlyphCache::setDistanceFieldImage(): Range({81, 175}, {101, 200}) out of range for texture size Vector(100, 200)\n" - "Text::DistanceFieldGlyphCache::setDistanceFieldImage(): Range({80, 176}, {100, 201}) out of range for texture size Vector(100, 200)\n" - "Text::DistanceFieldGlyphCache::setDistanceFieldImage(): Range({-1, 175}, {19, 200}) out of range for texture size Vector(100, 200)\n" - "Text::DistanceFieldGlyphCache::setDistanceFieldImage(): Range({80, -1}, {100, 24}) out of range for texture size Vector(100, 200)\n", + "Text::AbstractGlyphCache::setProcessedImage(): expected PixelFormat::R8Unorm but got PixelFormat::RGBA32F\n" + "Text::AbstractGlyphCache::setProcessedImage(): expected PixelFormat::R8Unorm but got PixelFormat::ImplementationSpecific(0x1908)\n", TestSuite::Compare::String); } +#endif }}}}