From ff4f1e4cb0f060ea1b9b85c879e8398eb9ad437a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Mon, 30 Sep 2024 21:56:17 +0200 Subject: [PATCH] Text: make GlyphCache take an explicit PixelFormat + processed format. The internal GL texture format (especially the R8 vs Luminance mess on ES2) is now considered an implementation detail and shouldn't affect common use in any way. The format is now required always, in order to prepare for use cases where colored glyphs are a thing as well. Additionally, to match the recent change in AbstractGlyphCache, the processed format is specified separately, allowing the input and processed formats to be decoupled. Which ultimately fixes the regression on ES2 and WebGL 1 where it was no longer possible to call font.fillyGlyphCache() on a DistanceFieldGlyphCache. Also, as there's now a generic format on input, another ES2-specific issue is now fixed as well, in particular a case where a GL error would be emitted on drivers with EXT_texture_storage because an unsized format is passed to setStorage(). This was a problem since a long time ago, but I ignored it because it didn't affect WebGL 1 and all drivers that exposed EXT_texture_storage exposed EXT_texture_rg, effectively circumventing this issue. Or so I think, at least. The constructors taking either a GL::TextureFormat or no format at all are now deprecated aliases to the new functionality. --- doc/changelog.dox | 5 + doc/snippets/Text-gl.cpp | 8 +- src/Magnum/Text/DistanceFieldGlyphCache.cpp | 34 ++-- src/Magnum/Text/DistanceFieldGlyphCache.h | 21 ++- src/Magnum/Text/GlyphCache.cpp | 107 ++++++++--- src/Magnum/Text/GlyphCache.h | 105 ++++++++--- .../Test/DistanceFieldGlyphCacheGLTest.cpp | 19 ++ src/Magnum/Text/Test/GlyphCacheGLTest.cpp | 167 ++++++++++++++++-- src/Magnum/Text/Test/RendererGLTest.cpp | 3 +- src/Magnum/Text/fontconverter.cpp | 3 +- src/MagnumPlugins/MagnumFont/MagnumFont.cpp | 3 + 11 files changed, 380 insertions(+), 95 deletions(-) diff --git a/doc/changelog.dox b/doc/changelog.dox index 1da7d924b..325508549 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -1414,6 +1414,11 @@ See also: @cpp setDistanceFieldImage() @ce is deprecated in favor of @ref Text::AbstractGlyphCache::processedSize() and @relativeref{Text::AbstractGlyphCache,setProcessedImage()} + - @ref Text::GlyphCache constructors taking either a + @ref GL::TextureFormat or no texture / pixel format at all are + deprecated in favor of constructors taking an explicit + @ref PixelFormat. The internal texture format is now considered an + implementation detail. - 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/doc/snippets/Text-gl.cpp b/doc/snippets/Text-gl.cpp index a78b2f705..79eda1688 100644 --- a/doc/snippets/Text-gl.cpp +++ b/doc/snippets/Text-gl.cpp @@ -58,7 +58,7 @@ Containers::Pointer font = if(!font->openFile("font.ttf", 12.0f)) Fatal{} << "Can't open font.ttf with StbTrueTypeFont"; -Text::GlyphCache cache{Vector2i{128}}; +Text::GlyphCache cache{PixelFormat::R8Unorm, Vector2i{128}}; font->fillGlyphCache(cache, "abcdefghijklmnopqrstuvwxyz" "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "0123456789?!:;,. "); @@ -67,7 +67,7 @@ font->fillGlyphCache(cache, "abcdefghijklmnopqrstuvwxyz" { /* [AbstractGlyphCache-filling-construct] */ -Text::GlyphCache cache{Vector2i{512}}; +Text::GlyphCache cache{PixelFormat::R8Unorm, Vector2i{512}}; /* [AbstractGlyphCache-filling-construct] */ } @@ -96,7 +96,7 @@ PluginManager::Manager manager; Containers::Pointer font = DOXYGEN_ELLIPSIS(manager.loadAndInstantiate("")); font->openFile("font.ttf", 12.0f); -Text::GlyphCache cache{Vector2i{128}}; +Text::GlyphCache cache{PixelFormat::R8Unorm, Vector2i{128}}; font->fillGlyphCache(cache, "abcdefghijklmnopqrstuvwxyz" "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "0123456789?!:;,. "); @@ -116,7 +116,7 @@ Containers::Pointer font = DOXYGEN_ELLIPSIS(manager.loadAndI font->openFile("font.ttf", 12.0f); /* Populate a glyph cache */ -Text::GlyphCache cache{Vector2i{128}}; +Text::GlyphCache cache{PixelFormat::R8Unorm, Vector2i{128}}; font->fillGlyphCache(cache, "abcdefghijklmnopqrstuvwxyz" "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "0123456789?!:;,. "); diff --git a/src/Magnum/Text/DistanceFieldGlyphCache.cpp b/src/Magnum/Text/DistanceFieldGlyphCache.cpp index 2f56f2fc4..4c8002b95 100644 --- a/src/Magnum/Text/DistanceFieldGlyphCache.cpp +++ b/src/Magnum/Text/DistanceFieldGlyphCache.cpp @@ -40,23 +40,23 @@ namespace Magnum { namespace Text { DistanceFieldGlyphCache::DistanceFieldGlyphCache(const Vector2i& size, const Vector2i& processedSize, const UnsignedInt radius): - #if !(defined(MAGNUM_TARGET_GLES) && defined(MAGNUM_TARGET_GLES2)) - GlyphCache(GL::TextureFormat::R8, size, processedSize, Vector2i(radius)), - #elif !defined(MAGNUM_TARGET_WEBGL) - /* Luminance is not renderable in most cases. RGB is *theoretically* - space-efficient but practically the driver uses RGBA internally anyway, - so just use RGBA. */ - GlyphCache(GL::Context::current().isExtensionSupported() ? - GL::TextureFormat::R8 : GL::TextureFormat::RGBA8, size, processedSize, Vector2i(radius)), - #else - GlyphCache(GL::TextureFormat::RGBA, size, processedSize, Vector2i(radius)), - #endif + GlyphCache{PixelFormat::R8Unorm, size, + #if !defined(MAGNUM_TARGET_GLES) || !defined(MAGNUM_TARGET_GLES2) + PixelFormat::R8Unorm, + #else + #ifndef MAGNUM_TARGET_WEBGL + /* Without EXT_texture_rg, PixelFormat::R8Unorm maps to Luminance which + is not renderable in most cases. RGB is *theoretically* space- + efficient but practically the driver uses RGBA internally anyway, so + just use RGBA. */ + GL::Context::current().isExtensionSupported() ? + PixelFormat::R8Unorm : + #endif + PixelFormat::RGBA8Unorm, + #endif + processedSize, Vector2i(radius)}, _distanceField{radius} { - #ifndef MAGNUM_TARGET_GLES - MAGNUM_ASSERT_GL_EXTENSION_SUPPORTED(GL::Extensions::ARB::texture_rg); - #endif - /* Replicating the assertion from TextureTools::DistanceField so it gets checked during construction already instead of only later during the setImage() call */ @@ -65,7 +65,9 @@ DistanceFieldGlyphCache::DistanceFieldGlyphCache(const Vector2i& size, const Vec "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 */ + /* On ES2 print a warning to make it known that EXT_texture_rg wasn't + available. On WebGL 1 this is the case always, so a warning would be + just a noise. */ if(!GL::Context::current().isExtensionSupported()) Warning() << "Text::DistanceFieldGlyphCache:" << GL::Extensions::EXT::texture_rg::string() << "not supported, using a full RGBA format for the distance field texture"; #endif diff --git a/src/Magnum/Text/DistanceFieldGlyphCache.h b/src/Magnum/Text/DistanceFieldGlyphCache.h index 97df6c56b..bc93c5659 100644 --- a/src/Magnum/Text/DistanceFieldGlyphCache.h +++ b/src/Magnum/Text/DistanceFieldGlyphCache.h @@ -41,8 +41,8 @@ namespace Magnum { namespace Text { @brief Glyph cache with distance field rendering Unlike the base @ref GlyphCache, this class converts each binary image to a -distance field. It's not possible to use non-binary colors with this cache as -the internal texture format is single-channel. +distance field. It's not possible to only use this cache for monochrome glyphs +as the internal texture format is single-channel. @section Text-DistanceFieldGlyphCache-usage Usage @@ -72,6 +72,23 @@ See the @ref Renderer class for information about text rendering. The @ref AbstractGlyphCache base class has more information about general glyph cache usage. +@section Text-DistanceFieldGlyphCache-internal-format Internal texture format + +The @ref format() is always @ref PixelFormat::R8Unorm. + +On desktop OpenGL, OpenGL ES 3.0+, WebGL 2, and OpenGL ES 2.0 if +@gl_extension{EXT,texture_rg} is supported, the @ref processedFormat() is +always @ref PixelFormat::R8Unorm, which maps to @ref GL::TextureFormat::R8 for +the @ref texture(), matching +@ref Text-GlyphCache-internal-format "the behavior listed in GlyphCache docs". + +On OpenGL ES 2.0 without @gl_extension{EXT,texture_rg} and on WebGL 1, +@ref PixelFormat::R8Unorm maps to @ref GL::TextureFormat::Luminance, which +isn't renderable and thus cannot be used for calculating the distance field. +Instead, @ref PixelFormat::RGBA8Unorm is used for @ref processedFormat(). This +shouldn't affect common use through @ref image(), but code interacting with +@ref processedImage() or @ref setProcessedImage() may need to be aware of this. + @note This class is available only if Magnum is compiled with @ref MAGNUM_TARGET_GL enabled (done by default). See @ref building-features for more information. diff --git a/src/Magnum/Text/GlyphCache.cpp b/src/Magnum/Text/GlyphCache.cpp index b159f711e..857d04f44 100644 --- a/src/Magnum/Text/GlyphCache.cpp +++ b/src/Magnum/Text/GlyphCache.cpp @@ -25,44 +25,83 @@ #include "GlyphCache.h" +#ifdef MAGNUM_BUILD_DEPRECATED +#include +#endif + +#include "Magnum/ImageView.h" +#include "Magnum/GL/TextureFormat.h" +#ifdef MAGNUM_BUILD_DEPRECATED +#include "Magnum/PixelFormat.h" +#endif +#if !defined(MAGNUM_TARGET_GLES) || (defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL)) #include "Magnum/PixelFormat.h" #include "Magnum/GL/Context.h" #include "Magnum/GL/Extensions.h" -#include "Magnum/GL/TextureFormat.h" - -#ifdef MAGNUM_TARGET_GLES2 -#include "Magnum/ImageView.h" +#endif +#if defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) +#include "Magnum/GL/PixelFormat.h" #endif namespace Magnum { namespace Text { -GlyphCache::GlyphCache(const GL::TextureFormat internalFormat, const Vector2i& size, const Vector2i& padding): GlyphCache{internalFormat, size, size, padding} {} +GlyphCache::GlyphCache(const PixelFormat format, const Vector2i& size, const PixelFormat processedFormat, const Vector2i& processedSize, const Vector2i& padding): AbstractGlyphCache{format, size, processedFormat, processedSize, padding} { + #ifndef MAGNUM_TARGET_GLES + if(processedFormat == PixelFormat::R8Unorm) + MAGNUM_ASSERT_GL_EXTENSION_SUPPORTED(GL::Extensions::ARB::texture_rg); + #endif -/* 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& 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, processedSize); -} - -GlyphCache::GlyphCache(const Vector2i& size, const Vector2i& padding): GlyphCache{size, size, padding} {} + .setMagnificationFilter(GL::SamplerFilter::Linear); + + /* ES2 special-casing. WebGL 1 has neither EXT_texture_rg nor + EXT_texture_storage so it can use the common code path without + issues. */ + #if defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) + /* Prefer to use Red instead of Luminance if available, as Luminance isn't + renderable */ + GL::TextureFormat textureFormat = GL::textureFormat(processedFormat); + GL::PixelFormat pixelFormat = GL::pixelFormat(processedFormat); + if(textureFormat == GL::TextureFormat::Luminance && GL::Context::current().isExtensionSupported()) { + textureFormat = GL::TextureFormat::Red; + pixelFormat = GL::PixelFormat::Red; + } -GlyphCache::GlyphCache(const Vector2i& size, const Vector2i& processedSize, const Vector2i& padding): GlyphCache{ - #ifndef MAGNUM_TARGET_GLES2 - GL::TextureFormat::R8, + /* And use setImage() instead of setStorage() if the format is unsized, as + EXT_texture_storage doesn't allow those */ + if(textureFormat == GL::TextureFormat::Red || + textureFormat == GL::TextureFormat::Luminance || + textureFormat == GL::TextureFormat::RG || + textureFormat == GL::TextureFormat::LuminanceAlpha || + textureFormat == GL::TextureFormat::RGB || + textureFormat == GL::TextureFormat::SRGB || + textureFormat == GL::TextureFormat::RGBA || + textureFormat == GL::TextureFormat::SRGBAlpha) + _texture.setImage(0, textureFormat, ImageView2D{pixelFormat, GL::PixelType::UnsignedByte, processedSize}); + else + _texture.setStorage(1, textureFormat, processedSize); #else - GL::TextureFormat::Luminance, - #endif - size, processedSize, padding} -{ - #ifndef MAGNUM_TARGET_GLES - MAGNUM_ASSERT_GL_EXTENSION_SUPPORTED(GL::Extensions::ARB::texture_rg); + _texture.setStorage(1, GL::textureFormat(processedFormat), processedSize); #endif } +GlyphCache::GlyphCache(const PixelFormat format, const Vector2i& size, const Vector2i& padding): GlyphCache{format, size, format, size, padding} {} + +#ifdef MAGNUM_BUILD_DEPRECATED +/* The unconditional Optional unwrap in these two may assert in rare cases. + Let's hope it doesn't in practice. */ + +GlyphCache::GlyphCache(const GL::TextureFormat internalFormat, const Vector2i& size, const Vector2i& padding): GlyphCache{*GL::genericPixelFormat(internalFormat), size, padding} {} + +GlyphCache::GlyphCache(const GL::TextureFormat internalFormat, const Vector2i& size, const Vector2i& processedSize, const Vector2i& padding): GlyphCache{*GL::genericPixelFormat(internalFormat), size, *GL::genericPixelFormat(internalFormat), processedSize, padding} {} + +GlyphCache::GlyphCache(const Vector2i& size, const Vector2i& padding): GlyphCache{PixelFormat::R8Unorm, size, padding} {} + +GlyphCache::GlyphCache(const Vector2i& size, const Vector2i& processedSize, const Vector2i& padding): GlyphCache{PixelFormat::R8Unorm, size, PixelFormat::R8Unorm, processedSize, padding} {} +#endif + GlyphCache::GlyphCache(NoCreateT) noexcept: AbstractGlyphCache{NoCreate}, _texture{NoCreate} {} GlyphCacheFeatures GlyphCache::doFeatures() const { return {}; } @@ -76,7 +115,18 @@ void GlyphCache::doSetImage(const Vector2i& offset, const ImageView2D& image) { if(!GL::Context::current().isExtensionSupported()) #endif { - _texture.setSubImage(0, {}, ImageView2D{image.format(), size().xy(), image.data()}); + /* On ES2 if EXT_texture_rg is present, the single-channel texture + format is Red instead of Luminance. Have to duplicate the logic here + in addition to below because it's easier than extracting + formatExtra() and everything else from the view afterwards. */ + #ifndef MAGNUM_TARGET_WEBGL + if(image.format() == PixelFormat::R8Unorm && GL::Context::current().isExtensionSupported()) { + _texture.setSubImage(0, {}, ImageView2D{GL::PixelFormat::Red, GL::PixelType::UnsignedByte, size().xy(), image.data()}); + } else + #endif + { + _texture.setSubImage(0, {}, ImageView2D{image.format(), size().xy(), image.data()}); + } #ifdef MAGNUM_TARGET_WEBGL static_cast(offset); #endif @@ -87,7 +137,16 @@ void GlyphCache::doSetImage(const Vector2i& offset, const ImageView2D& image) { #endif #if !(defined(MAGNUM_TARGET_GLES2) && defined(MAGNUM_TARGET_WEBGL)) { - _texture.setSubImage(0, offset, image); + /* On ES2 if EXT_texture_rg is present, the single-channel texture + format is Red instead of Luminance */ + #ifdef MAGNUM_TARGET_GLES2 + if(image.format() == PixelFormat::R8Unorm && GL::Context::current().isExtensionSupported()) { + _texture.setSubImage(0, offset, ImageView2D{image.storage(), GL::PixelFormat::Red, GL::PixelType::UnsignedByte, image.size(), image.data()}); + } else + #endif + { + _texture.setSubImage(0, offset, image); + } } #endif } diff --git a/src/Magnum/Text/GlyphCache.h b/src/Magnum/Text/GlyphCache.h index 97f2fd0dc..3bd51c0d9 100644 --- a/src/Magnum/Text/GlyphCache.h +++ b/src/Magnum/Text/GlyphCache.h @@ -53,7 +53,29 @@ See the @ref Renderer class for information about text rendering. The @ref AbstractGlyphCache base class has more information about general glyph cache usage. -@todo Some way for Font to negotiate or check internal texture format +@section Text-GlyphCache-internal-format Internal texture format + +The @ref GL::TextureFormat used by @ref texture() is implicitly coming from +@ref GL::textureFormat(Magnum::PixelFormat) applied to @ref format(), or if +@ref GlyphCacheFeature::ImageProcessing is supported, to @ref processedFormat() +instead. + +If @ref PixelFormat::R8Unorm is used for @ref format() or if +@ref GlyphCacheFeature::ImageProcessing is supported and +@ref PixelFormat::R8Unorm is used for @ref processedFormat(), on desktop OpenGL +the class expects that @gl_extension{ARB,texture_rg} (OpenGL 3.0) is supported +and uses @ref GL::TextureFormat::R8. On OpenGL ES 2.0, if +@gl_extension{EXT,texture_rg} is supported, @ref GL::TextureFormat::Red / +@ref GL::TextureFormat::R8 is used instead of @ref GL::TextureFormat::Luminance +for @ref PixelFormat::R8Unorm. On WebGL 1 @ref GL::TextureFormat::Luminance is +used for @ref PixelFormat::R8Unorm always. + +While this is abstracted away to not affect common use through @ref image(), +@ref processedImage() or @ref setProcessedImage(), code interacting directly +with @ref texture() may need to special-case this. In particular, if image +processing needs to render to the texture, it may need to choose a different +format as luminance usually cannot be rendered to. + @todo Default glyph 0 with rect 0 0 0 0 will result in negative dimensions when nonzero padding is removed @@ -65,50 +87,75 @@ class MAGNUM_TEXT_EXPORT GlyphCache: public AbstractGlyphCache { public: /** * @brief Constructor - * @param internalFormat Internal texture format - * @param size Source glyph cache texture size in pixels - * @param processedSize Processed glyph cache texture size in - * pixels + * @param format Source image format + * @param size Source image size size in pixels * @param padding Padding around every glyph in pixels + * @m_since_latest * - * 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. + * 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. */ - explicit GlyphCache(GL::TextureFormat internalFormat, const Vector2i& size, const Vector2i& processedSize, const Vector2i& padding); + explicit GlyphCache(PixelFormat format, const Vector2i& size, const Vector2i& padding = Vector2i{1}); /** - * @brief Constructor + * @brief Construct with a specific processed format and size + * @param format Source image format + * @param size Source image size size in pixels + * @param processedFormat Processed image format + * @param processedSize Processed glyph cache texture 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 * - * 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. + * The @p size and @p processedSize is expected to be non-zero. All + * glyphs are saved in @p format relative to @p size and with + * @p padding, although the actual glyph cache texture is in + * @p processedFormat and has @p processedSize. + * @see @ref AbstractGlyphCache(PixelFormat, const Vector2i&, const Vector2i&) */ - explicit GlyphCache(GL::TextureFormat internalFormat, const Vector2i& size, const Vector2i& padding = Vector2i{1}); + explicit GlyphCache(PixelFormat format, const Vector2i& size, PixelFormat processedFormat, const Vector2i& processedSize, const Vector2i& padding = Vector2i{1}); + #ifdef MAGNUM_BUILD_DEPRECATED /** * @brief Constructor + * @m_deprecated_since_latest Use @ref GlyphCache(PixelFormat, const Vector2i&, const Vector2i&) + * instead. + */ + CORRADE_DEPRECATED("use GlyphCache(PixelFormat, const Vector2i&, const Vector2i&, const Vector2i&) instead") explicit GlyphCache(GL::TextureFormat internalFormat, const Vector2i& size, const Vector2i& padding = Vector2i{1}); + + /** + * @brief Construct with a specific processed size + * @m_deprecated_since_latest Use @ref GlyphCache(PixelFormat, const Vector2i&, PixelFormat, const Vector2i&, const Vector2i&) + * instead. + */ + CORRADE_DEPRECATED("use GlyphCache(PixelFormat, const Vector2i&, const Vector2i&, const Vector2i&) instead") explicit GlyphCache(GL::TextureFormat internalFormat, const Vector2i& size, const Vector2i& processedSize, const Vector2i& padding); + + /** + * @brief Construct with an implicit format * - * 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} (also part of OpenGL 3.0). On - * ES2 and WebGL 1 unconditionally uses @ref GL::TextureFormat::Luminance. - * This is done for consistency with @ref GL::pixelFormat(), which - * unconditionally returns @ref GL::PixelFormat::Luminance for - * @ref PixelFormat::R8Unorm. See - * @ref GlyphCache(GL::TextureFormat, const Vector2i&, const Vector2i&) - * for an alternative. + * Calls @ref GlyphCache(PixelFormat, const Vector2i&, const Vector2i&) + * with @p format set to @ref PixelFormat::R8Unorm. + * @m_deprecated_since_latest Use @ref GlyphCache(PixelFormat, const Vector2i&, const Vector2i&) + * and explicitly pass the format instead. */ - explicit GlyphCache(const Vector2i& size, const Vector2i& processedSize, const Vector2i& padding); + CORRADE_DEPRECATED("use GlyphCache(PixelFormat, const Vector2i&, const Vector2i&, const Vector2i&) instead") explicit GlyphCache(const Vector2i& size, const Vector2i& padding = Vector2i{1}); /** - * @brief Constructor + * @brief Construct with an implicit format and a specific processed size * - * 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. + * Calls @ref GlyphCache(PixelFormat, const Vector2i&, PixelFormat, const Vector2i&, const Vector2i&) + * with @p format and @p processedFormat set to + * @ref PixelFormat::R8Unorm. + * @m_deprecated_since_latest Use @ref GlyphCache(PixelFormat, const Vector2i&, PixelFormat, const Vector2i&, const Vector2i&) + * and explicitly pass the format instead. */ - explicit GlyphCache(const Vector2i& size, const Vector2i& padding = Vector2i{1}); + CORRADE_DEPRECATED("use GlyphCache(PixelFormat, const Vector2i&, const Vector2i&, const Vector2i&) instead") explicit GlyphCache(const Vector2i& size, const Vector2i& processedSize, const Vector2i& padding); + #endif /** * @brief Construct without creating the internal state and the OpenGL texture object diff --git a/src/Magnum/Text/Test/DistanceFieldGlyphCacheGLTest.cpp b/src/Magnum/Text/Test/DistanceFieldGlyphCacheGLTest.cpp index cb4799838..262e7a3b1 100644 --- a/src/Magnum/Text/Test/DistanceFieldGlyphCacheGLTest.cpp +++ b/src/Magnum/Text/Test/DistanceFieldGlyphCacheGLTest.cpp @@ -145,7 +145,26 @@ void DistanceFieldGlyphCacheGLTest::construct() { DistanceFieldGlyphCache cache{{1024, 2048}, {128, 256}, 16}; MAGNUM_VERIFY_NO_GL_ERROR(); + /* The input format is always single-channel */ + CORRADE_COMPARE(cache.format(), PixelFormat::R8Unorm); CORRADE_COMPARE(cache.size(), (Vector3i{1024, 2048, 1})); + /* The processed format is RGBA if it'd have to be Luminance */ + #ifdef MAGNUM_TARGET_GLES2 + #ifndef MAGNUM_TARGET_WEBGL + if(!GL::Context::current().isExtensionSupported()) + #endif + { + CORRADE_COMPARE(cache.processedFormat(), PixelFormat::RGBA8Unorm); + } + #ifndef MAGNUM_TARGET_WEBGL + else + #endif + #endif + #if !(defined(MAGNUM_TARGET_GLES2) && defined(MAGNUM_TARGET_WEBGL)) + { + CORRADE_COMPARE(cache.processedFormat(), PixelFormat::R8Unorm); + } + #endif CORRADE_COMPARE(cache.processedSize(), (Vector3i{128, 256, 1})); #ifndef MAGNUM_TARGET_GLES CORRADE_COMPARE(cache.texture().imageSize(0), (Vector2i{128, 256})); diff --git a/src/Magnum/Text/Test/GlyphCacheGLTest.cpp b/src/Magnum/Text/Test/GlyphCacheGLTest.cpp index ed8cca1d2..97480dab9 100644 --- a/src/Magnum/Text/Test/GlyphCacheGLTest.cpp +++ b/src/Magnum/Text/Test/GlyphCacheGLTest.cpp @@ -34,6 +34,11 @@ #ifdef MAGNUM_TARGET_GLES #include "Magnum/DebugTools/TextureImage.h" #endif +#if defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) +#include "Magnum/GL/Context.h" +#include "Magnum/GL/Extensions.h" +#include "Magnum/GL/PixelFormat.h" +#endif #include "Magnum/GL/OpenGLTester.h" #include "Magnum/GL/TextureFormat.h" #include "Magnum/Math/Color.h" @@ -46,28 +51,117 @@ struct GlyphCacheGLTest: GL::OpenGLTester { explicit GlyphCacheGLTest(); void construct(); - void constructCustomFormat(); + void constructNoPadding(); + void constructProcessed(); + void constructProcessedNoPadding(); + #ifdef MAGNUM_BUILD_DEPRECATED + void constructDeprecated(); + void constructDeprecatedProcessed(); + void constructDeprecatedTextureFormat(); + void constructDeprecatedTextureFormatProcessed(); + #endif void constructCopy(); void constructMove(); void setImage(); - void setImageCustomFormat(); + void setImageFourChannel(); }; GlyphCacheGLTest::GlyphCacheGLTest() { addTests({&GlyphCacheGLTest::construct, - &GlyphCacheGLTest::constructCustomFormat, + &GlyphCacheGLTest::constructNoPadding, + &GlyphCacheGLTest::constructProcessed, + &GlyphCacheGLTest::constructProcessedNoPadding, + #ifdef MAGNUM_BUILD_DEPRECATED + &GlyphCacheGLTest::constructDeprecated, + &GlyphCacheGLTest::constructDeprecatedProcessed, + &GlyphCacheGLTest::constructDeprecatedTextureFormat, + &GlyphCacheGLTest::constructDeprecatedTextureFormatProcessed, + #endif &GlyphCacheGLTest::constructCopy, &GlyphCacheGLTest::constructMove, &GlyphCacheGLTest::setImage, - &GlyphCacheGLTest::setImageCustomFormat}); + &GlyphCacheGLTest::setImageFourChannel}); } void GlyphCacheGLTest::construct() { + GlyphCache cache{PixelFormat::R8Unorm, {1024, 2048}, {3, 2}}; + MAGNUM_VERIFY_NO_GL_ERROR(); + + CORRADE_COMPARE(cache.format(), PixelFormat::R8Unorm); + CORRADE_COMPARE(cache.size(), (Vector3i{1024, 2048, 1})); + CORRADE_COMPARE(cache.padding(), (Vector2i{3, 2})); + #ifndef MAGNUM_TARGET_GLES + CORRADE_COMPARE(cache.texture().imageSize(0), (Vector2i{1024, 2048})); + #endif +} + +void GlyphCacheGLTest::constructNoPadding() { + GlyphCache cache{PixelFormat::RGBA8Unorm, {1024, 2048}}; + MAGNUM_VERIFY_NO_GL_ERROR(); + + CORRADE_COMPARE(cache.format(), PixelFormat::RGBA8Unorm); + CORRADE_COMPARE(cache.size(), (Vector3i{1024, 2048, 1})); + CORRADE_COMPARE(cache.padding(), Vector2i{1}); + #ifndef MAGNUM_TARGET_GLES + CORRADE_COMPARE(cache.texture().imageSize(0), (Vector2i{1024, 2048})); + #endif +} + +void GlyphCacheGLTest::constructProcessed() { + struct : GlyphCache { + using GlyphCache::GlyphCache; + + GlyphCacheFeatures doFeatures() const override { + return GlyphCacheFeature::ImageProcessing; + } + /* The symbol is private, we don't actually need it here, so just + override with an empty implementation */ + void doSetImage(const Vector2i&, const ImageView2D&) override {} + } cache{PixelFormat::R8Unorm, {1024, 2048}, PixelFormat::RGBA8Unorm, {128, 256}, {3, 2}}; + MAGNUM_VERIFY_NO_GL_ERROR(); + + CORRADE_COMPARE(cache.format(), PixelFormat::R8Unorm); + CORRADE_COMPARE(cache.size(), (Vector3i{1024, 2048, 1})); + CORRADE_COMPARE(cache.processedFormat(), PixelFormat::RGBA8Unorm); + CORRADE_COMPARE(cache.processedSize(), (Vector3i{128, 256, 1})); + CORRADE_COMPARE(cache.padding(), (Vector2i{3, 2})); + #ifndef MAGNUM_TARGET_GLES + CORRADE_COMPARE(cache.texture().imageSize(0), (Vector2i{128, 256})); + #endif +} + +void GlyphCacheGLTest::constructProcessedNoPadding() { + struct : GlyphCache { + using GlyphCache::GlyphCache; + + GlyphCacheFeatures doFeatures() const override { + return GlyphCacheFeature::ImageProcessing; + } + /* The symbol is private, we don't actually need it here, so just + override with an empty implementation */ + void doSetImage(const Vector2i&, const ImageView2D&) override {} + } cache{PixelFormat::R8Unorm, {1024, 2048}, PixelFormat::RGBA8Unorm, {128, 256}}; + MAGNUM_VERIFY_NO_GL_ERROR(); + + CORRADE_COMPARE(cache.format(), PixelFormat::R8Unorm); + CORRADE_COMPARE(cache.size(), (Vector3i{1024, 2048, 1})); + CORRADE_COMPARE(cache.processedFormat(), PixelFormat::RGBA8Unorm); + CORRADE_COMPARE(cache.processedSize(), (Vector3i{128, 256, 1})); + CORRADE_COMPARE(cache.padding(), Vector2i{1}); + #ifndef MAGNUM_TARGET_GLES + CORRADE_COMPARE(cache.texture().imageSize(0), (Vector2i{128, 256})); + #endif +} + +#ifdef MAGNUM_BUILD_DEPRECATED +void GlyphCacheGLTest::constructDeprecated() { + CORRADE_IGNORE_DEPRECATED_PUSH GlyphCache cache{{1024, 2048}}; + CORRADE_IGNORE_DEPRECATED_POP MAGNUM_VERIFY_NO_GL_ERROR(); CORRADE_COMPARE(cache.size(), (Vector3i{1024, 2048, 1})); @@ -76,7 +170,20 @@ void GlyphCacheGLTest::construct() { #endif } -void GlyphCacheGLTest::constructCustomFormat() { +void GlyphCacheGLTest::constructDeprecatedProcessed() { + CORRADE_IGNORE_DEPRECATED_PUSH + GlyphCache cache{{1024, 2048}, {128, 256}, {}}; + CORRADE_IGNORE_DEPRECATED_POP + MAGNUM_VERIFY_NO_GL_ERROR(); + + CORRADE_COMPARE(cache.size(), (Vector3i{1024, 2048, 1})); + #ifndef MAGNUM_TARGET_GLES + CORRADE_COMPARE(cache.texture().imageSize(0), (Vector2i{128, 256})); + #endif +} + +void GlyphCacheGLTest::constructDeprecatedTextureFormat() { + CORRADE_IGNORE_DEPRECATED_PUSH GlyphCache cache{ #ifndef MAGNUM_TARGET_GLES2 GL::TextureFormat::RGBA8, @@ -84,6 +191,7 @@ void GlyphCacheGLTest::constructCustomFormat() { GL::TextureFormat::RGBA, #endif {256, 512}}; + CORRADE_IGNORE_DEPRECATED_POP MAGNUM_VERIFY_NO_GL_ERROR(); CORRADE_COMPARE(cache.size(), (Vector3i{256, 512, 1})); @@ -92,19 +200,40 @@ void GlyphCacheGLTest::constructCustomFormat() { #endif } +void GlyphCacheGLTest::constructDeprecatedTextureFormatProcessed() { + CORRADE_IGNORE_DEPRECATED_PUSH + GlyphCache cache{ + #ifndef MAGNUM_TARGET_GLES2 + GL::TextureFormat::RGBA8, + #else + GL::TextureFormat::RGBA, + #endif + {256, 512}, {32, 64}, {}}; + CORRADE_IGNORE_DEPRECATED_POP + MAGNUM_VERIFY_NO_GL_ERROR(); + + CORRADE_COMPARE(cache.size(), (Vector3i{256, 512, 1})); + #ifndef MAGNUM_TARGET_GLES + CORRADE_COMPARE(cache.texture().imageSize(0), (Vector2i{32, 64})); + #endif +} +#endif + void GlyphCacheGLTest::constructCopy() { CORRADE_VERIFY(!std::is_copy_constructible{}); CORRADE_VERIFY(!std::is_copy_assignable{}); } void GlyphCacheGLTest::constructMove() { - GlyphCache a{{1024, 512}}; + GlyphCache a{PixelFormat::R8Unorm, {1024, 512}}; GlyphCache b = Utility::move(a); + CORRADE_COMPARE(b.format(), PixelFormat::R8Unorm); CORRADE_COMPARE(b.size(), (Vector3i{1024, 512, 1})); - GlyphCache c{{2, 3}}; + GlyphCache c{PixelFormat::RGBA8Unorm, {2, 3}}; c = Utility::move(b); + CORRADE_COMPARE(c.format(), PixelFormat::R8Unorm); CORRADE_COMPARE(c.size(), (Vector3i{1024, 512, 1})); CORRADE_VERIFY(std::is_nothrow_move_constructible::value); @@ -130,11 +259,19 @@ const UnsignedByte ExpectedData[]{ }; void GlyphCacheGLTest::setImage() { - GlyphCache cache{{16, 8}}; + GlyphCache cache{PixelFormat::R8Unorm, {16, 8}}; /* Fill the texture with non-zero data to verify the padding gets uploaded - as well */ - cache.texture().setSubImage(0, {}, Image2D{PixelFormat::R8Unorm, {16, 8}, Containers::Array{DirectInit, 16*8, '\xcd'}}); + as well. On ES2 with EXT_texture_rg the internal format isn't Luminance + but Red. */ + #if defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) + if(GL::Context::current().isExtensionSupported()) + cache.texture().setSubImage(0, {}, Image2D{GL::PixelFormat::Red, GL::PixelType::UnsignedByte, {16, 8}, Containers::Array{DirectInit, 16*8, '\xcd'}}); + else + #endif + { + cache.texture().setSubImage(0, {}, Image2D{PixelFormat::R8Unorm, {16, 8}, Containers::Array{DirectInit, 16*8, '\xcd'}}); + } MAGNUM_VERIFY_NO_GL_ERROR(); Utility::copy( @@ -191,19 +328,13 @@ void GlyphCacheGLTest::setImage() { #endif } -void GlyphCacheGLTest::setImageCustomFormat() { +void GlyphCacheGLTest::setImageFourChannel() { /* Same as setImage(), but with a four-channel format (so quarter of width). Needed to be able to read the texture on ES2 to verify the upload works, as there's a special case for when the EXT_unpack_subimage extension isn't present. */ - GlyphCache cache{ - #if !(defined(MAGNUM_TARGET_GLES2) && defined(MAGNUM_TARGET_WEBGL)) - GL::TextureFormat::RGBA8, - #else - GL::TextureFormat::RGBA, - #endif - {4, 8}}; + GlyphCache cache{PixelFormat::RGBA8Unorm, {4, 8}}; /* Zero the texture to avoid comparing against garbage */ cache.texture().setSubImage(0, {}, Image2D{PixelFormat::RGBA8Unorm, {4, 8}, Containers::Array{ValueInit, 4*4*8}}); diff --git a/src/Magnum/Text/Test/RendererGLTest.cpp b/src/Magnum/Text/Test/RendererGLTest.cpp index ab5bea76a..3a7ffbe56 100644 --- a/src/Magnum/Text/Test/RendererGLTest.cpp +++ b/src/Magnum/Text/Test/RendererGLTest.cpp @@ -30,6 +30,7 @@ #include #include /** @todo drop once Debug is stream-free */ +#include "Magnum/PixelFormat.h" #include "Magnum/GL/Context.h" #include "Magnum/GL/Extensions.h" #include "Magnum/GL/OpenGLTester.h" @@ -114,7 +115,7 @@ struct TestFont: AbstractFont { GlyphCache testGlyphCache(AbstractFont& font) { /* Default padding is 1 to avoid artifacts, set that to 0 to simplify */ - GlyphCache cache{{20, 20}, {}}; + GlyphCache cache{PixelFormat::R8Unorm, {20, 20}, {}}; /* Add one more font to verify the right one gets picked */ cache.addFont(96); diff --git a/src/Magnum/Text/fontconverter.cpp b/src/Magnum/Text/fontconverter.cpp index 56c9a546a..344dfcd1f 100644 --- a/src/Magnum/Text/fontconverter.cpp +++ b/src/Magnum/Text/fontconverter.cpp @@ -29,6 +29,7 @@ #include #include +#include "Magnum/PixelFormat.h" #include "Magnum/Math/ConfigurationValue.h" #include "Magnum/Text/AbstractFont.h" #include "Magnum/Text/AbstractFontConverter.h" @@ -211,7 +212,7 @@ int FontConverter::exec() { } else { Debug() << "Zero-size distance field output specified, populating normal glyph cache..."; - cache.emplace(args.value("atlas-size")); + cache.emplace(PixelFormat::R8Unorm, args.value("atlas-size")); } /* Fill the cache */ diff --git a/src/MagnumPlugins/MagnumFont/MagnumFont.cpp b/src/MagnumPlugins/MagnumFont/MagnumFont.cpp index a6ae8af42..8a0840e43 100644 --- a/src/MagnumPlugins/MagnumFont/MagnumFont.cpp +++ b/src/MagnumPlugins/MagnumFont/MagnumFont.cpp @@ -39,6 +39,7 @@ #include #include "Magnum/ImageView.h" +#include "Magnum/PixelFormat.h" #include "Magnum/Math/ConfigurationValue.h" #include "Magnum/Text/AbstractShaper.h" #include "Magnum/Text/GlyphCache.h" @@ -160,7 +161,9 @@ Vector2 MagnumFont::doGlyphAdvance(const UnsignedInt glyph) { Containers::Pointer MagnumFont::doCreateGlyphCache() { /* Set cache image */ Containers::Pointer cache{InPlaceInit, + PixelFormat::R8Unorm, _opened->conf.value("originalImageSize"), + PixelFormat::R8Unorm, _opened->image->size(), _opened->conf.value("padding")}; /* Copy the opened image data directly to the GL texture because (unlike