Browse Source

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.
pull/650/head
Vladimír Vondruš 2 years ago
parent
commit
ff4f1e4cb0
  1. 5
      doc/changelog.dox
  2. 8
      doc/snippets/Text-gl.cpp
  3. 30
      src/Magnum/Text/DistanceFieldGlyphCache.cpp
  4. 21
      src/Magnum/Text/DistanceFieldGlyphCache.h
  5. 101
      src/Magnum/Text/GlyphCache.cpp
  6. 105
      src/Magnum/Text/GlyphCache.h
  7. 19
      src/Magnum/Text/Test/DistanceFieldGlyphCacheGLTest.cpp
  8. 165
      src/Magnum/Text/Test/GlyphCacheGLTest.cpp
  9. 3
      src/Magnum/Text/Test/RendererGLTest.cpp
  10. 3
      src/Magnum/Text/fontconverter.cpp
  11. 3
      src/MagnumPlugins/MagnumFont/MagnumFont.cpp

5
doc/changelog.dox

@ -1414,6 +1414,11 @@ See also:
@cpp setDistanceFieldImage() @ce is deprecated in favor of @cpp setDistanceFieldImage() @ce is deprecated in favor of
@ref Text::AbstractGlyphCache::processedSize() and @ref Text::AbstractGlyphCache::processedSize() and
@relativeref{Text::AbstractGlyphCache,setProcessedImage()} @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 - The @cpp TextureTools::atlas() @ce utility is deprecated in favor of
@ref TextureTools::AtlasLandfill, which has a vastly better packing @ref TextureTools::AtlasLandfill, which has a vastly better packing
efficiency, supports incremental packing and doesn't force the caller to efficiency, supports incremental packing and doesn't force the caller to

8
doc/snippets/Text-gl.cpp

@ -58,7 +58,7 @@ Containers::Pointer<Text::AbstractFont> font =
if(!font->openFile("font.ttf", 12.0f)) if(!font->openFile("font.ttf", 12.0f))
Fatal{} << "Can't open font.ttf with StbTrueTypeFont"; Fatal{} << "Can't open font.ttf with StbTrueTypeFont";
Text::GlyphCache cache{Vector2i{128}}; Text::GlyphCache cache{PixelFormat::R8Unorm, Vector2i{128}};
font->fillGlyphCache(cache, "abcdefghijklmnopqrstuvwxyz" font->fillGlyphCache(cache, "abcdefghijklmnopqrstuvwxyz"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ" "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"0123456789?!:;,. "); "0123456789?!:;,. ");
@ -67,7 +67,7 @@ font->fillGlyphCache(cache, "abcdefghijklmnopqrstuvwxyz"
{ {
/* [AbstractGlyphCache-filling-construct] */ /* [AbstractGlyphCache-filling-construct] */
Text::GlyphCache cache{Vector2i{512}}; Text::GlyphCache cache{PixelFormat::R8Unorm, Vector2i{512}};
/* [AbstractGlyphCache-filling-construct] */ /* [AbstractGlyphCache-filling-construct] */
} }
@ -96,7 +96,7 @@ PluginManager::Manager<Text::AbstractFont> manager;
Containers::Pointer<Text::AbstractFont> font = DOXYGEN_ELLIPSIS(manager.loadAndInstantiate("")); Containers::Pointer<Text::AbstractFont> font = DOXYGEN_ELLIPSIS(manager.loadAndInstantiate(""));
font->openFile("font.ttf", 12.0f); font->openFile("font.ttf", 12.0f);
Text::GlyphCache cache{Vector2i{128}}; Text::GlyphCache cache{PixelFormat::R8Unorm, Vector2i{128}};
font->fillGlyphCache(cache, "abcdefghijklmnopqrstuvwxyz" font->fillGlyphCache(cache, "abcdefghijklmnopqrstuvwxyz"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ" "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"0123456789?!:;,. "); "0123456789?!:;,. ");
@ -116,7 +116,7 @@ Containers::Pointer<Text::AbstractFont> font = DOXYGEN_ELLIPSIS(manager.loadAndI
font->openFile("font.ttf", 12.0f); font->openFile("font.ttf", 12.0f);
/* Populate a glyph cache */ /* Populate a glyph cache */
Text::GlyphCache cache{Vector2i{128}}; Text::GlyphCache cache{PixelFormat::R8Unorm, Vector2i{128}};
font->fillGlyphCache(cache, "abcdefghijklmnopqrstuvwxyz" font->fillGlyphCache(cache, "abcdefghijklmnopqrstuvwxyz"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ" "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"0123456789?!:;,. "); "0123456789?!:;,. ");

30
src/Magnum/Text/DistanceFieldGlyphCache.cpp

@ -40,23 +40,23 @@
namespace Magnum { namespace Text { namespace Magnum { namespace Text {
DistanceFieldGlyphCache::DistanceFieldGlyphCache(const Vector2i& size, const Vector2i& processedSize, const UnsignedInt radius): DistanceFieldGlyphCache::DistanceFieldGlyphCache(const Vector2i& size, const Vector2i& processedSize, const UnsignedInt radius):
#if !(defined(MAGNUM_TARGET_GLES) && defined(MAGNUM_TARGET_GLES2)) GlyphCache{PixelFormat::R8Unorm, size,
GlyphCache(GL::TextureFormat::R8, size, processedSize, Vector2i(radius)), #if !defined(MAGNUM_TARGET_GLES) || !defined(MAGNUM_TARGET_GLES2)
#elif !defined(MAGNUM_TARGET_WEBGL) PixelFormat::R8Unorm,
/* 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::Extensions::EXT::texture_rg>() ?
GL::TextureFormat::R8 : GL::TextureFormat::RGBA8, size, processedSize, Vector2i(radius)),
#else #else
GlyphCache(GL::TextureFormat::RGBA, size, processedSize, Vector2i(radius)), #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<GL::Extensions::EXT::texture_rg>() ?
PixelFormat::R8Unorm :
#endif
PixelFormat::RGBA8Unorm,
#endif #endif
processedSize, Vector2i(radius)},
_distanceField{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 /* Replicating the assertion from TextureTools::DistanceField so it gets
checked during construction already instead of only later during the checked during construction already instead of only later during the
setImage() call */ 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, ); "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) #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<GL::Extensions::EXT::texture_rg>()) if(!GL::Context::current().isExtensionSupported<GL::Extensions::EXT::texture_rg>())
Warning() << "Text::DistanceFieldGlyphCache:" << GL::Extensions::EXT::texture_rg::string() << "not supported, using a full RGBA format for the distance field texture"; Warning() << "Text::DistanceFieldGlyphCache:" << GL::Extensions::EXT::texture_rg::string() << "not supported, using a full RGBA format for the distance field texture";
#endif #endif

21
src/Magnum/Text/DistanceFieldGlyphCache.h

@ -41,8 +41,8 @@ namespace Magnum { namespace Text {
@brief Glyph cache with distance field rendering @brief Glyph cache with distance field rendering
Unlike the base @ref GlyphCache, this class converts each binary image to a 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 distance field. It's not possible to only use this cache for monochrome glyphs
the internal texture format is single-channel. as the internal texture format is single-channel.
@section Text-DistanceFieldGlyphCache-usage Usage @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 @ref AbstractGlyphCache base class has more information about general glyph
cache usage. 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 @note This class is available only if Magnum is compiled with
@ref MAGNUM_TARGET_GL enabled (done by default). See @ref building-features @ref MAGNUM_TARGET_GL enabled (done by default). See @ref building-features
for more information. for more information.

101
src/Magnum/Text/GlyphCache.cpp

@ -25,44 +25,83 @@
#include "GlyphCache.h" #include "GlyphCache.h"
#ifdef MAGNUM_BUILD_DEPRECATED
#include <Corrade/Containers/Optional.h>
#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/PixelFormat.h"
#include "Magnum/GL/Context.h" #include "Magnum/GL/Context.h"
#include "Magnum/GL/Extensions.h" #include "Magnum/GL/Extensions.h"
#include "Magnum/GL/TextureFormat.h" #endif
#if defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL)
#ifdef MAGNUM_TARGET_GLES2 #include "Magnum/GL/PixelFormat.h"
#include "Magnum/ImageView.h"
#endif #endif
namespace Magnum { namespace Text { 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 */ /* Initialize the texture */
_texture.setWrapping(GL::SamplerWrapping::ClampToEdge) _texture.setWrapping(GL::SamplerWrapping::ClampToEdge)
.setMinificationFilter(GL::SamplerFilter::Linear) .setMinificationFilter(GL::SamplerFilter::Linear)
.setMagnificationFilter(GL::SamplerFilter::Linear) .setMagnificationFilter(GL::SamplerFilter::Linear);
.setStorage(1, internalFormat, processedSize);
/* 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<GL::Extensions::EXT::texture_rg>()) {
textureFormat = GL::TextureFormat::Red;
pixelFormat = GL::PixelFormat::Red;
} }
GlyphCache::GlyphCache(const Vector2i& size, const Vector2i& padding): GlyphCache{size, size, padding} {} /* And use setImage() instead of setStorage() if the format is unsized, as
EXT_texture_storage doesn't allow those */
GlyphCache::GlyphCache(const Vector2i& size, const Vector2i& processedSize, const Vector2i& padding): GlyphCache{ if(textureFormat == GL::TextureFormat::Red ||
#ifndef MAGNUM_TARGET_GLES2 textureFormat == GL::TextureFormat::Luminance ||
GL::TextureFormat::R8, 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 #else
GL::TextureFormat::Luminance, _texture.setStorage(1, GL::textureFormat(processedFormat), processedSize);
#endif
size, processedSize, padding}
{
#ifndef MAGNUM_TARGET_GLES
MAGNUM_ASSERT_GL_EXTENSION_SUPPORTED(GL::Extensions::ARB::texture_rg);
#endif #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} {} GlyphCache::GlyphCache(NoCreateT) noexcept: AbstractGlyphCache{NoCreate}, _texture{NoCreate} {}
GlyphCacheFeatures GlyphCache::doFeatures() const { return {}; } GlyphCacheFeatures GlyphCache::doFeatures() const { return {}; }
@ -75,8 +114,19 @@ void GlyphCache::doSetImage(const Vector2i& offset, const ImageView2D& image) {
#ifndef MAGNUM_TARGET_WEBGL #ifndef MAGNUM_TARGET_WEBGL
if(!GL::Context::current().isExtensionSupported<GL::Extensions::EXT::unpack_subimage>()) if(!GL::Context::current().isExtensionSupported<GL::Extensions::EXT::unpack_subimage>())
#endif #endif
{
/* 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<GL::Extensions::EXT::texture_rg>()) {
_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()}); _texture.setSubImage(0, {}, ImageView2D{image.format(), size().xy(), image.data()});
}
#ifdef MAGNUM_TARGET_WEBGL #ifdef MAGNUM_TARGET_WEBGL
static_cast<void>(offset); static_cast<void>(offset);
#endif #endif
@ -86,9 +136,18 @@ void GlyphCache::doSetImage(const Vector2i& offset, const ImageView2D& image) {
#endif #endif
#endif #endif
#if !(defined(MAGNUM_TARGET_GLES2) && defined(MAGNUM_TARGET_WEBGL)) #if !(defined(MAGNUM_TARGET_GLES2) && defined(MAGNUM_TARGET_WEBGL))
{
/* 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<GL::Extensions::EXT::texture_rg>()) {
_texture.setSubImage(0, offset, ImageView2D{image.storage(), GL::PixelFormat::Red, GL::PixelType::UnsignedByte, image.size(), image.data()});
} else
#endif
{ {
_texture.setSubImage(0, offset, image); _texture.setSubImage(0, offset, image);
} }
}
#endif #endif
} }

105
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 @ref AbstractGlyphCache base class has more information about general glyph
cache usage. 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 @todo Default glyph 0 with rect 0 0 0 0 will result in negative dimensions when
nonzero padding is removed nonzero padding is removed
@ -65,50 +87,75 @@ class MAGNUM_TEXT_EXPORT GlyphCache: public AbstractGlyphCache {
public: public:
/** /**
* @brief Constructor * @brief Constructor
* @param internalFormat Internal texture format * @param format Source image format
* @param size Source glyph cache texture size in pixels * @param size Source image size size in pixels
* @param processedSize Processed glyph cache texture size in
* pixels
* @param padding Padding around every glyph in pixels * @param padding Padding around every glyph in pixels
* @m_since_latest
* *
* All glyphs parameters are saved relative to @p size, * The @p size is expected to be non-zero. If the implementation
* although the actual glyph cache texture has @p processedSize. Glyph * advertises @ref GlyphCacheFeature::ImageProcessing, the
* @p padding can be used to account for e.g. glyph shadows. * @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 * The @p size and @p processedSize is expected to be non-zero. All
* set to the same value. See @ref Text-AbstractGlyphCache-padding for * glyphs are saved in @p format relative to @p size and with
* more information about the default @p padding. * @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 * @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 * Calls @ref GlyphCache(PixelFormat, const Vector2i&, const Vector2i&)
* 3.0+ and WebGL 2 uses @ref GL::TextureFormat::R8. On desktop OpenGL * with @p format set to @ref PixelFormat::R8Unorm.
* requires @gl_extension{ARB,texture_rg} (also part of OpenGL 3.0). On * @m_deprecated_since_latest Use @ref GlyphCache(PixelFormat, const Vector2i&, const Vector2i&)
* ES2 and WebGL 1 unconditionally uses @ref GL::TextureFormat::Luminance. * and explicitly pass the format instead.
* 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.
*/ */
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 * Calls @ref GlyphCache(PixelFormat, const Vector2i&, PixelFormat, const Vector2i&, const Vector2i&)
* set to the same value. See @ref Text-AbstractGlyphCache-padding for * with @p format and @p processedFormat set to
* more information about the default @p padding. * @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 * @brief Construct without creating the internal state and the OpenGL texture object

19
src/Magnum/Text/Test/DistanceFieldGlyphCacheGLTest.cpp

@ -145,7 +145,26 @@ void DistanceFieldGlyphCacheGLTest::construct() {
DistanceFieldGlyphCache cache{{1024, 2048}, {128, 256}, 16}; DistanceFieldGlyphCache cache{{1024, 2048}, {128, 256}, 16};
MAGNUM_VERIFY_NO_GL_ERROR(); 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})); 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<GL::Extensions::EXT::texture_rg>())
#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})); CORRADE_COMPARE(cache.processedSize(), (Vector3i{128, 256, 1}));
#ifndef MAGNUM_TARGET_GLES #ifndef MAGNUM_TARGET_GLES
CORRADE_COMPARE(cache.texture().imageSize(0), (Vector2i{128, 256})); CORRADE_COMPARE(cache.texture().imageSize(0), (Vector2i{128, 256}));

165
src/Magnum/Text/Test/GlyphCacheGLTest.cpp

@ -34,6 +34,11 @@
#ifdef MAGNUM_TARGET_GLES #ifdef MAGNUM_TARGET_GLES
#include "Magnum/DebugTools/TextureImage.h" #include "Magnum/DebugTools/TextureImage.h"
#endif #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/OpenGLTester.h"
#include "Magnum/GL/TextureFormat.h" #include "Magnum/GL/TextureFormat.h"
#include "Magnum/Math/Color.h" #include "Magnum/Math/Color.h"
@ -46,28 +51,117 @@ struct GlyphCacheGLTest: GL::OpenGLTester {
explicit GlyphCacheGLTest(); explicit GlyphCacheGLTest();
void construct(); 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 constructCopy();
void constructMove(); void constructMove();
void setImage(); void setImage();
void setImageCustomFormat(); void setImageFourChannel();
}; };
GlyphCacheGLTest::GlyphCacheGLTest() { GlyphCacheGLTest::GlyphCacheGLTest() {
addTests({&GlyphCacheGLTest::construct, 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::constructCopy,
&GlyphCacheGLTest::constructMove, &GlyphCacheGLTest::constructMove,
&GlyphCacheGLTest::setImage, &GlyphCacheGLTest::setImage,
&GlyphCacheGLTest::setImageCustomFormat}); &GlyphCacheGLTest::setImageFourChannel});
} }
void GlyphCacheGLTest::construct() { 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}}; GlyphCache cache{{1024, 2048}};
CORRADE_IGNORE_DEPRECATED_POP
MAGNUM_VERIFY_NO_GL_ERROR(); MAGNUM_VERIFY_NO_GL_ERROR();
CORRADE_COMPARE(cache.size(), (Vector3i{1024, 2048, 1})); CORRADE_COMPARE(cache.size(), (Vector3i{1024, 2048, 1}));
@ -76,7 +170,20 @@ void GlyphCacheGLTest::construct() {
#endif #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{ GlyphCache cache{
#ifndef MAGNUM_TARGET_GLES2 #ifndef MAGNUM_TARGET_GLES2
GL::TextureFormat::RGBA8, GL::TextureFormat::RGBA8,
@ -84,6 +191,7 @@ void GlyphCacheGLTest::constructCustomFormat() {
GL::TextureFormat::RGBA, GL::TextureFormat::RGBA,
#endif #endif
{256, 512}}; {256, 512}};
CORRADE_IGNORE_DEPRECATED_POP
MAGNUM_VERIFY_NO_GL_ERROR(); MAGNUM_VERIFY_NO_GL_ERROR();
CORRADE_COMPARE(cache.size(), (Vector3i{256, 512, 1})); CORRADE_COMPARE(cache.size(), (Vector3i{256, 512, 1}));
@ -92,19 +200,40 @@ void GlyphCacheGLTest::constructCustomFormat() {
#endif #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() { void GlyphCacheGLTest::constructCopy() {
CORRADE_VERIFY(!std::is_copy_constructible<GlyphCache>{}); CORRADE_VERIFY(!std::is_copy_constructible<GlyphCache>{});
CORRADE_VERIFY(!std::is_copy_assignable<GlyphCache>{}); CORRADE_VERIFY(!std::is_copy_assignable<GlyphCache>{});
} }
void GlyphCacheGLTest::constructMove() { void GlyphCacheGLTest::constructMove() {
GlyphCache a{{1024, 512}}; GlyphCache a{PixelFormat::R8Unorm, {1024, 512}};
GlyphCache b = Utility::move(a); GlyphCache b = Utility::move(a);
CORRADE_COMPARE(b.format(), PixelFormat::R8Unorm);
CORRADE_COMPARE(b.size(), (Vector3i{1024, 512, 1})); CORRADE_COMPARE(b.size(), (Vector3i{1024, 512, 1}));
GlyphCache c{{2, 3}}; GlyphCache c{PixelFormat::RGBA8Unorm, {2, 3}};
c = Utility::move(b); c = Utility::move(b);
CORRADE_COMPARE(c.format(), PixelFormat::R8Unorm);
CORRADE_COMPARE(c.size(), (Vector3i{1024, 512, 1})); CORRADE_COMPARE(c.size(), (Vector3i{1024, 512, 1}));
CORRADE_VERIFY(std::is_nothrow_move_constructible<GlyphCache>::value); CORRADE_VERIFY(std::is_nothrow_move_constructible<GlyphCache>::value);
@ -130,11 +259,19 @@ const UnsignedByte ExpectedData[]{
}; };
void GlyphCacheGLTest::setImage() { 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 /* Fill the texture with non-zero data to verify the padding gets uploaded
as well */ 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<GL::Extensions::EXT::texture_rg>())
cache.texture().setSubImage(0, {}, Image2D{GL::PixelFormat::Red, GL::PixelType::UnsignedByte, {16, 8}, Containers::Array<char>{DirectInit, 16*8, '\xcd'}});
else
#endif
{
cache.texture().setSubImage(0, {}, Image2D{PixelFormat::R8Unorm, {16, 8}, Containers::Array<char>{DirectInit, 16*8, '\xcd'}}); cache.texture().setSubImage(0, {}, Image2D{PixelFormat::R8Unorm, {16, 8}, Containers::Array<char>{DirectInit, 16*8, '\xcd'}});
}
MAGNUM_VERIFY_NO_GL_ERROR(); MAGNUM_VERIFY_NO_GL_ERROR();
Utility::copy( Utility::copy(
@ -191,19 +328,13 @@ void GlyphCacheGLTest::setImage() {
#endif #endif
} }
void GlyphCacheGLTest::setImageCustomFormat() { void GlyphCacheGLTest::setImageFourChannel() {
/* Same as setImage(), but with a four-channel format (so quarter of /* 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 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 upload works, as there's a special case for when the EXT_unpack_subimage
extension isn't present. */ extension isn't present. */
GlyphCache cache{ GlyphCache cache{PixelFormat::RGBA8Unorm, {4, 8}};
#if !(defined(MAGNUM_TARGET_GLES2) && defined(MAGNUM_TARGET_WEBGL))
GL::TextureFormat::RGBA8,
#else
GL::TextureFormat::RGBA,
#endif
{4, 8}};
/* Zero the texture to avoid comparing against garbage */ /* Zero the texture to avoid comparing against garbage */
cache.texture().setSubImage(0, {}, Image2D{PixelFormat::RGBA8Unorm, {4, 8}, Containers::Array<char>{ValueInit, 4*4*8}}); cache.texture().setSubImage(0, {}, Image2D{PixelFormat::RGBA8Unorm, {4, 8}, Containers::Array<char>{ValueInit, 4*4*8}});

3
src/Magnum/Text/Test/RendererGLTest.cpp

@ -30,6 +30,7 @@
#include <Corrade/TestSuite/Compare/Container.h> #include <Corrade/TestSuite/Compare/Container.h>
#include <Corrade/Utility/DebugStl.h> /** @todo drop once Debug is stream-free */ #include <Corrade/Utility/DebugStl.h> /** @todo drop once Debug is stream-free */
#include "Magnum/PixelFormat.h"
#include "Magnum/GL/Context.h" #include "Magnum/GL/Context.h"
#include "Magnum/GL/Extensions.h" #include "Magnum/GL/Extensions.h"
#include "Magnum/GL/OpenGLTester.h" #include "Magnum/GL/OpenGLTester.h"
@ -114,7 +115,7 @@ struct TestFont: AbstractFont {
GlyphCache testGlyphCache(AbstractFont& font) { GlyphCache testGlyphCache(AbstractFont& font) {
/* Default padding is 1 to avoid artifacts, set that to 0 to simplify */ /* 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 */ /* Add one more font to verify the right one gets picked */
cache.addFont(96); cache.addFont(96);

3
src/Magnum/Text/fontconverter.cpp

@ -29,6 +29,7 @@
#include <Corrade/Utility/DebugStl.h> #include <Corrade/Utility/DebugStl.h>
#include <Corrade/Utility/Path.h> #include <Corrade/Utility/Path.h>
#include "Magnum/PixelFormat.h"
#include "Magnum/Math/ConfigurationValue.h" #include "Magnum/Math/ConfigurationValue.h"
#include "Magnum/Text/AbstractFont.h" #include "Magnum/Text/AbstractFont.h"
#include "Magnum/Text/AbstractFontConverter.h" #include "Magnum/Text/AbstractFontConverter.h"
@ -211,7 +212,7 @@ int FontConverter::exec() {
} else { } else {
Debug() << "Zero-size distance field output specified, populating normal glyph cache..."; Debug() << "Zero-size distance field output specified, populating normal glyph cache...";
cache.emplace<GlyphCache>(args.value<Vector2i>("atlas-size")); cache.emplace<GlyphCache>(PixelFormat::R8Unorm, args.value<Vector2i>("atlas-size"));
} }
/* Fill the cache */ /* Fill the cache */

3
src/MagnumPlugins/MagnumFont/MagnumFont.cpp

@ -39,6 +39,7 @@
#include <Corrade/Utility/Unicode.h> #include <Corrade/Utility/Unicode.h>
#include "Magnum/ImageView.h" #include "Magnum/ImageView.h"
#include "Magnum/PixelFormat.h"
#include "Magnum/Math/ConfigurationValue.h" #include "Magnum/Math/ConfigurationValue.h"
#include "Magnum/Text/AbstractShaper.h" #include "Magnum/Text/AbstractShaper.h"
#include "Magnum/Text/GlyphCache.h" #include "Magnum/Text/GlyphCache.h"
@ -160,7 +161,9 @@ Vector2 MagnumFont::doGlyphAdvance(const UnsignedInt glyph) {
Containers::Pointer<AbstractGlyphCache> MagnumFont::doCreateGlyphCache() { Containers::Pointer<AbstractGlyphCache> MagnumFont::doCreateGlyphCache() {
/* Set cache image */ /* Set cache image */
Containers::Pointer<GlyphCache> cache{InPlaceInit, Containers::Pointer<GlyphCache> cache{InPlaceInit,
PixelFormat::R8Unorm,
_opened->conf.value<Vector2i>("originalImageSize"), _opened->conf.value<Vector2i>("originalImageSize"),
PixelFormat::R8Unorm,
_opened->image->size(), _opened->image->size(),
_opened->conf.value<Vector2i>("padding")}; _opened->conf.value<Vector2i>("padding")};
/* Copy the opened image data directly to the GL texture because (unlike /* Copy the opened image data directly to the GL texture because (unlike

Loading…
Cancel
Save