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