From 8168a06babde7330cb6a4a744e8804ffa99b1b56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Mon, 10 Mar 2025 09:40:11 +0100 Subject: [PATCH] Text: add a GlyphCacheArrayGL. So far the Renderer doesn't work with that, and neither the builtin Vector shaders are be able use it, but gotta start somewhere. I wanted to have the texture contents tested on ES, but it turns out that implementing DebugTools::textureSubImage() for arrays is blocked on another feature I badly need to finish first. Sigh. --- doc/snippets/Text-gl.cpp | 15 + src/Magnum/Text/AbstractGlyphCache.h | 4 +- src/Magnum/Text/GlyphCacheGL.cpp | 28 ++ src/Magnum/Text/GlyphCacheGL.h | 101 ++++++- src/Magnum/Text/Test/GlyphCacheGLTest.cpp | 321 ++++++++++++++++++++- src/Magnum/Text/Test/GlyphCacheGL_Test.cpp | 21 +- src/Magnum/Text/Text.h | 3 + 7 files changed, 487 insertions(+), 6 deletions(-) diff --git a/doc/snippets/Text-gl.cpp b/doc/snippets/Text-gl.cpp index 630ac4ea1..08b6e0fb6 100644 --- a/doc/snippets/Text-gl.cpp +++ b/doc/snippets/Text-gl.cpp @@ -75,6 +75,21 @@ Text::GlyphCacheGL cache{PixelFormat::R8Unorm, {256, 256}}; /* [AbstractGlyphCache-usage-construct] */ } +#ifndef MAGNUM_TARGET_GLES2 +{ +PluginManager::Manager manager; +/* [GlyphCacheArrayGL-usage] */ +Containers::Pointer font = DOXYGEN_ELLIPSIS(manager.loadAndInstantiate("")); + +Text::GlyphCacheArrayGL cache{PixelFormat::R8Unorm, {256, 256, 8}}; +if(!font->fillGlyphCache(cache, "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "0123456789?!:;,. ")) + Fatal{} << "Glyph cache too small to fit all characters"; +/* [GlyphCacheArrayGL-usage] */ +} +#endif + { /* -Wnonnull in GCC 11+ "helpfully" says "this is null" if I don't initialize the font pointer. I don't care, I just want you to check compilation errors, diff --git a/src/Magnum/Text/AbstractGlyphCache.h b/src/Magnum/Text/AbstractGlyphCache.h index 2abaf8cf0..a49912123 100644 --- a/src/Magnum/Text/AbstractGlyphCache.h +++ b/src/Magnum/Text/AbstractGlyphCache.h @@ -109,8 +109,8 @@ MAGNUM_TEXT_EXPORT Debug& operator<<(Debug& output, GlyphCacheFeatures value); A GPU-API-agnostic base for glyph caches, supporting multiple fonts and both 2D and 2D array textures. Provides a common interface for adding fonts, glyph properties, uploading glyph data and retrieving glyph properties back, the -@ref GlyphCacheGL and @ref DistanceFieldGlyphCacheGL subclasses then provide -concrete implementations backed with an OpenGL texture. +@ref GlyphCacheGL, @ref GlyphCacheArrayGL and @ref DistanceFieldGlyphCacheGL +subclasses then provide concrete implementations backed with an OpenGL texture. @section Text-AbstractGlyphCache-usage Basic usage diff --git a/src/Magnum/Text/GlyphCacheGL.cpp b/src/Magnum/Text/GlyphCacheGL.cpp index 08ff6d2a9..e4d96c699 100644 --- a/src/Magnum/Text/GlyphCacheGL.cpp +++ b/src/Magnum/Text/GlyphCacheGL.cpp @@ -182,4 +182,32 @@ Image3D GlyphCacheGL::doProcessedImage() { } #endif +#ifndef MAGNUM_TARGET_GLES2 +GlyphCacheArrayGL::GlyphCacheArrayGL(const PixelFormat format, const Vector3i& 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 + + /* Initialize the texture */ + _texture.setWrapping(GL::SamplerWrapping::ClampToEdge) + .setMinificationFilter(GL::SamplerFilter::Linear) + .setMagnificationFilter(GL::SamplerFilter::Linear) + .setStorage(1, GL::textureFormat(processedFormat), {processedSize, size.z()}); +} + +GlyphCacheArrayGL::GlyphCacheArrayGL(const PixelFormat format, const Vector3i& size, const Vector2i& padding): GlyphCacheArrayGL{format, size, format, size.xy(), padding} {} + +GlyphCacheArrayGL::GlyphCacheArrayGL(NoCreateT) noexcept: AbstractGlyphCache{NoCreate}, _texture{NoCreate} {} + +GlyphCacheFeatures GlyphCacheArrayGL::doFeatures() const { return {}; } + +void GlyphCacheArrayGL::doSetImage(const Vector3i& offset, const ImageView3D& image) { + CORRADE_ASSERT(format() == processedFormat() && size() == processedSize(), + "Text::GlyphCacheArrayGL::flushImage(): subclass expected to provide a doSetImage() implementation to handle different processed format or size", ); + + _texture.setSubImage(0, offset, image); +} +#endif + }} diff --git a/src/Magnum/Text/GlyphCacheGL.h b/src/Magnum/Text/GlyphCacheGL.h index 735938340..9ce0a108c 100644 --- a/src/Magnum/Text/GlyphCacheGL.h +++ b/src/Magnum/Text/GlyphCacheGL.h @@ -27,7 +27,7 @@ */ /** @file - * @brief Class @ref Magnum::Text::GlyphCacheGL + * @brief Class @ref Magnum::Text::GlyphCacheGL, @ref Magnum::Text::GlyphCacheArrayGL * @m_since_latest */ @@ -35,6 +35,9 @@ #ifdef MAGNUM_TARGET_GL #include "Magnum/GL/Texture.h" +#ifndef MAGNUM_TARGET_GLES2 +#include "Magnum/GL/TextureArray.h" +#endif #include "Magnum/Text/AbstractGlyphCache.h" namespace Magnum { namespace Text { @@ -47,7 +50,8 @@ Implementation of an @ref AbstractGlyphCache backed by a @ref GL::Texture2D. See the @ref AbstractGlyphCache class documentation for information about setting up an instance of this class and filling it with glyphs. See the @ref DistanceFieldGlyphCacheGL subclass for a variant that adds distance field -processing on top. +processing on top, @ref GlyphCacheArrayGL is then using a @ref GL::Texture2DArray +instead. @section Text-GlyphCacheGL-internal-format Internal texture format @@ -198,6 +202,99 @@ class MAGNUM_TEXT_EXPORT GlyphCacheGL: public AbstractGlyphCache { #endif }; +#ifndef MAGNUM_TARGET_GLES2 +/** +@brief OpenGL array glyph cache +@m_since_latest + +Implementation of an @ref AbstractGlyphCache backed by a +@ref GL::Texture2DArray, other than that equivalent to @ref GlyphCacheGL. See +the @ref AbstractGlyphCache class documentation for information about setting +up a glyph cache instance and filling it with glyphs, and @ref GlyphCacheGL for +details on how the internal texture format is picked. The usage differs from +@ref GlyphCacheGL only in specifying one extra dimension for size: + +@snippet Text-gl.cpp GlyphCacheArrayGL-usage + +@requires_gl30 Extension @gl_extension{EXT,texture_array} +@requires_gles30 Texture arrays are not available in OpenGL ES 2.0. +@requires_webgl20 Texture arrays are not available in WebGL 1.0. + +@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. +*/ +class MAGNUM_TEXT_EXPORT GlyphCacheArrayGL: public AbstractGlyphCache { + public: + /** + * @brief Constructor + * @param format Source image format + * @param size Source image size size in pixels + * @param padding Padding around every glyph in pixels + * @m_since_latest + * + * 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 GlyphCacheArrayGL(PixelFormat format, const Vector3i& size, const Vector2i& padding = Vector2i{1}); + + /** + * @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 + * + * The @p size and @p processedSize is expected to be non-zero, depth + * of processed size is implicitly the same as in @p size. 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 GlyphCacheArrayGL(PixelFormat format, const Vector3i& size, PixelFormat processedFormat, const Vector2i& processedSize, const Vector2i& padding = Vector2i{1}); + + /** + * @brief Construct without creating the internal state and the OpenGL texture object + * @m_since_latest + * + * The constructed instance is equivalent to moved-from state, i.e. no + * APIs can be safely called on the object. Useful in cases where you + * will overwrite the instance later anyway. Move another object over + * it to make it useful. + * + * This function can be safely used for constructing (and later + * destructing) objects even without any OpenGL context being active. + * However note that this is a low-level and a potentially dangerous + * API, see the documentation of @ref NoCreate for alternatives. + */ + explicit GlyphCacheArrayGL(NoCreateT) noexcept; + + /** @brief Cache texture */ + GL::Texture2DArray& texture() { return _texture; } + + #ifdef DOXYGEN_GENERATING_OUTPUT + private: + #else + protected: + #endif + GL::Texture2DArray _texture; + + private: + MAGNUM_TEXT_LOCAL GlyphCacheFeatures doFeatures() const override; + /* These are not MAGNUM_TEXT_LOCAL because the test makes a subclass */ + void doSetImage(const Vector3i& offset, const ImageView3D& image) override; +}; +#endif + }} #else #error this header is available only in the OpenGL build diff --git a/src/Magnum/Text/Test/GlyphCacheGLTest.cpp b/src/Magnum/Text/Test/GlyphCacheGLTest.cpp index cd09412e6..afe2c0088 100644 --- a/src/Magnum/Text/Test/GlyphCacheGLTest.cpp +++ b/src/Magnum/Text/Test/GlyphCacheGLTest.cpp @@ -53,9 +53,21 @@ struct GlyphCacheGLTest: GL::OpenGLTester { explicit GlyphCacheGLTest(); void construct(); + #ifndef MAGNUM_TARGET_GLES2 + void constructArray(); + #endif void constructNoPadding(); + #ifndef MAGNUM_TARGET_GLES2 + void constructNoPaddingArray(); + #endif void constructProcessed(); + #ifndef MAGNUM_TARGET_GLES2 + void constructProcessedArray(); + #endif void constructProcessedNoPadding(); + #ifndef MAGNUM_TARGET_GLES2 + void constructProcessedNoPaddingArray(); + #endif #ifdef MAGNUM_BUILD_DEPRECATED void constructDeprecated(); void constructDeprecatedProcessed(); @@ -64,19 +76,45 @@ struct GlyphCacheGLTest: GL::OpenGLTester { #endif void constructCopy(); + #ifndef MAGNUM_TARGET_GLES2 + void constructCopyArray(); + #endif void constructMove(); + #ifndef MAGNUM_TARGET_GLES2 + void constructMoveArray(); + #endif void setImage(); + #ifndef MAGNUM_TARGET_GLES2 + void setImageArray(); + #endif void setImageFourChannel(); + /* setImageArray() tests a two-channel format, so no need for + setImageFourChannelArray() */ void flushImageSubclassProcessedFormatSize(); + #ifndef MAGNUM_TARGET_GLES2 + void flushImageSubclassProcessedFormatSizeArray(); + #endif }; GlyphCacheGLTest::GlyphCacheGLTest() { addTests({&GlyphCacheGLTest::construct, + #ifndef MAGNUM_TARGET_GLES2 + &GlyphCacheGLTest::constructArray, + #endif &GlyphCacheGLTest::constructNoPadding, + #ifndef MAGNUM_TARGET_GLES2 + &GlyphCacheGLTest::constructNoPaddingArray, + #endif &GlyphCacheGLTest::constructProcessed, + #ifndef MAGNUM_TARGET_GLES2 + &GlyphCacheGLTest::constructProcessedArray, + #endif &GlyphCacheGLTest::constructProcessedNoPadding, + #ifndef MAGNUM_TARGET_GLES2 + &GlyphCacheGLTest::constructProcessedNoPaddingArray, + #endif #ifdef MAGNUM_BUILD_DEPRECATED &GlyphCacheGLTest::constructDeprecated, &GlyphCacheGLTest::constructDeprecatedProcessed, @@ -85,12 +123,25 @@ GlyphCacheGLTest::GlyphCacheGLTest() { #endif &GlyphCacheGLTest::constructCopy, + #ifndef MAGNUM_TARGET_GLES2 + &GlyphCacheGLTest::constructCopyArray, + #endif &GlyphCacheGLTest::constructMove, + #ifndef MAGNUM_TARGET_GLES2 + &GlyphCacheGLTest::constructMoveArray, + #endif &GlyphCacheGLTest::setImage, + #ifndef MAGNUM_TARGET_GLES2 + &GlyphCacheGLTest::setImageArray, + #endif &GlyphCacheGLTest::setImageFourChannel, - &GlyphCacheGLTest::flushImageSubclassProcessedFormatSize}); + &GlyphCacheGLTest::flushImageSubclassProcessedFormatSize, + #ifndef MAGNUM_TARGET_GLES2 + &GlyphCacheGLTest::flushImageSubclassProcessedFormatSizeArray, + #endif + }); } void GlyphCacheGLTest::construct() { @@ -105,6 +156,20 @@ void GlyphCacheGLTest::construct() { #endif } +#ifndef MAGNUM_TARGET_GLES2 +void GlyphCacheGLTest::constructArray() { + GlyphCacheArrayGL cache{PixelFormat::R8Unorm, {1024, 2048, 7}, {3, 2}}; + MAGNUM_VERIFY_NO_GL_ERROR(); + + CORRADE_COMPARE(cache.format(), PixelFormat::R8Unorm); + CORRADE_COMPARE(cache.size(), (Vector3i{1024, 2048, 7})); + CORRADE_COMPARE(cache.padding(), (Vector2i{3, 2})); + #ifndef MAGNUM_TARGET_GLES + CORRADE_COMPARE(cache.texture().imageSize(0), (Vector3i{1024, 2048, 7})); + #endif +} +#endif + void GlyphCacheGLTest::constructNoPadding() { GlyphCacheGL cache{PixelFormat::RGBA8Unorm, {1024, 2048}}; MAGNUM_VERIFY_NO_GL_ERROR(); @@ -117,6 +182,20 @@ void GlyphCacheGLTest::constructNoPadding() { #endif } +#ifndef MAGNUM_TARGET_GLES2 +void GlyphCacheGLTest::constructNoPaddingArray() { + GlyphCacheArrayGL cache{PixelFormat::RGBA8Unorm, {1024, 2048, 7}}; + MAGNUM_VERIFY_NO_GL_ERROR(); + + CORRADE_COMPARE(cache.format(), PixelFormat::RGBA8Unorm); + CORRADE_COMPARE(cache.size(), (Vector3i{1024, 2048, 7})); + CORRADE_COMPARE(cache.padding(), Vector2i{1}); + #ifndef MAGNUM_TARGET_GLES + CORRADE_COMPARE(cache.texture().imageSize(0), (Vector3i{1024, 2048, 7})); + #endif +} +#endif + void GlyphCacheGLTest::constructProcessed() { struct Cache: GlyphCacheGL { explicit Cache(PixelFormat format, const Vector2i& size, PixelFormat processedFormat, const Vector2i& processedSize, const Vector2i& padding): GlyphCacheGL{format, size, processedFormat, processedSize, padding} {} @@ -137,6 +216,28 @@ void GlyphCacheGLTest::constructProcessed() { #endif } +#ifndef MAGNUM_TARGET_GLES2 +void GlyphCacheGLTest::constructProcessedArray() { + struct Cache: GlyphCacheArrayGL { + explicit Cache(PixelFormat format, const Vector3i& size, PixelFormat processedFormat, const Vector2i& processedSize, const Vector2i& padding): GlyphCacheArrayGL{format, size, processedFormat, processedSize, padding} {} + + GlyphCacheFeatures doFeatures() const override { + return GlyphCacheFeature::ImageProcessing; + } + } cache{PixelFormat::R8Unorm, {1024, 2048, 7}, PixelFormat::RGBA8Unorm, {128, 256}, {3, 2}}; + MAGNUM_VERIFY_NO_GL_ERROR(); + + CORRADE_COMPARE(cache.format(), PixelFormat::R8Unorm); + CORRADE_COMPARE(cache.size(), (Vector3i{1024, 2048, 7})); + CORRADE_COMPARE(cache.processedFormat(), PixelFormat::RGBA8Unorm); + CORRADE_COMPARE(cache.processedSize(), (Vector3i{128, 256, 7})); + CORRADE_COMPARE(cache.padding(), (Vector2i{3, 2})); + #ifndef MAGNUM_TARGET_GLES + CORRADE_COMPARE(cache.texture().imageSize(0), (Vector3i{128, 256, 7})); + #endif +} +#endif + void GlyphCacheGLTest::constructProcessedNoPadding() { struct Cache: GlyphCacheGL { explicit Cache(PixelFormat format, const Vector2i& size, PixelFormat processedFormat, const Vector2i& processedSize): GlyphCacheGL{format, size, processedFormat, processedSize} {} @@ -157,6 +258,28 @@ void GlyphCacheGLTest::constructProcessedNoPadding() { #endif } +#ifndef MAGNUM_TARGET_GLES2 +void GlyphCacheGLTest::constructProcessedNoPaddingArray() { + struct Cache: GlyphCacheArrayGL { + explicit Cache(PixelFormat format, const Vector3i& size, PixelFormat processedFormat, const Vector2i& processedSize): GlyphCacheArrayGL{format, size, processedFormat, processedSize} {} + + GlyphCacheFeatures doFeatures() const override { + return GlyphCacheFeature::ImageProcessing; + } + } cache{PixelFormat::R8Unorm, {1024, 2048, 7}, PixelFormat::RGBA8Unorm, {128, 256}}; + MAGNUM_VERIFY_NO_GL_ERROR(); + + CORRADE_COMPARE(cache.format(), PixelFormat::R8Unorm); + CORRADE_COMPARE(cache.size(), (Vector3i{1024, 2048, 7})); + CORRADE_COMPARE(cache.processedFormat(), PixelFormat::RGBA8Unorm); + CORRADE_COMPARE(cache.processedSize(), (Vector3i{128, 256, 7})); + CORRADE_COMPARE(cache.padding(), Vector2i{1}); + #ifndef MAGNUM_TARGET_GLES + CORRADE_COMPARE(cache.texture().imageSize(0), (Vector3i{128, 256, 7})); + #endif +} +#endif + #ifdef MAGNUM_BUILD_DEPRECATED void GlyphCacheGLTest::constructDeprecated() { CORRADE_IGNORE_DEPRECATED_PUSH @@ -224,6 +347,13 @@ void GlyphCacheGLTest::constructCopy() { CORRADE_VERIFY(!std::is_copy_assignable{}); } +#ifndef MAGNUM_TARGET_GLES2 +void GlyphCacheGLTest::constructCopyArray() { + CORRADE_VERIFY(!std::is_copy_constructible{}); + CORRADE_VERIFY(!std::is_copy_assignable{}); +} +#endif + void GlyphCacheGLTest::constructMove() { GlyphCacheGL a{PixelFormat::R8Unorm, {1024, 512}}; @@ -240,6 +370,24 @@ void GlyphCacheGLTest::constructMove() { CORRADE_VERIFY(std::is_nothrow_move_assignable::value); } +#ifndef MAGNUM_TARGET_GLES2 +void GlyphCacheGLTest::constructMoveArray() { + GlyphCacheArrayGL a{PixelFormat::R8Unorm, {1024, 512, 7}}; + + GlyphCacheArrayGL b = Utility::move(a); + CORRADE_COMPARE(b.format(), PixelFormat::R8Unorm); + CORRADE_COMPARE(b.size(), (Vector3i{1024, 512, 7})); + + GlyphCacheArrayGL c{PixelFormat::RGBA8Unorm, {2, 3, 3}}; + c = Utility::move(b); + CORRADE_COMPARE(c.format(), PixelFormat::R8Unorm); + CORRADE_COMPARE(c.size(), (Vector3i{1024, 512, 7})); + + CORRADE_VERIFY(std::is_nothrow_move_constructible::value); + CORRADE_VERIFY(std::is_nothrow_move_assignable::value); +} +#endif + const UnsignedByte InputData[]{ 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, @@ -328,6 +476,152 @@ void GlyphCacheGLTest::setImage() { #endif } +#ifndef MAGNUM_TARGET_GLES2 +void GlyphCacheGLTest::setImageArray() { + GlyphCacheArrayGL cache{PixelFormat::RG8Unorm, {8, 8, 4}}; + + /* Fill the texture with non-zero data to verify the padding gets uploaded + as well */ + cache.texture().setSubImage(0, {}, Image3D{PixelFormat::RG8Unorm, {8, 8, 4}, Containers::Array{DirectInit, 8*8*4*2, '\xcd'}}); + MAGNUM_VERIFY_NO_GL_ERROR(); + + Utility::copy( + Containers::arrayCast<3, const Vector2ub>(Containers::StridedArrayView4D{InputData, {2, 2, 4, 2}}), + cache.image().pixels().sliceSize({1, 4, 3}, {2, 2, 4})); + cache.flushImage(Range3Di::fromSize({3, 4, 1}, {4, 2, 2})); + MAGNUM_VERIFY_NO_GL_ERROR(); + + ImageView3D actual3D = cache.image(); + MAGNUM_VERIFY_NO_GL_ERROR(); + + /* The CPU-side image is zero-initialized, what was set above in the + texture isn't present there */ + const UnsignedByte expectedData03[8*8*2]{}; + const UnsignedByte expectedData1[]{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + + 0, 0, 0, 0, 0, 0, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0, 0, + 0, 0, 0, 0, 0, 0, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }; + const UnsignedByte expectedData2[]{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + + 0, 0, 0, 0, 0, 0, 0x00, 0xff, 0x11, 0xee, 0x22, 0xdd, 0x33, 0xcc, 0, 0, + 0, 0, 0, 0, 0, 0, 0x44, 0xbb, 0x55, 0xaa, 0x66, 0x99, 0x77, 0x88, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }; + /** @todo ugh have slicing on images directly already, and 3D image + comparison */ + const std::size_t sliceSize = actual3D.size().xy().product()*2; + CORRADE_COMPARE_AS((ImageView2D{actual3D.format(), actual3D.size().xy(), actual3D.data()}), + (ImageView2D{PixelFormat::RG8Unorm, {8, 8}, expectedData03}), + DebugTools::CompareImage); + CORRADE_COMPARE_AS((ImageView2D{actual3D.format(), actual3D.size().xy(), actual3D.data().exceptPrefix(1*sliceSize)}), + (ImageView2D{PixelFormat::RG8Unorm, {8, 8}, expectedData1}), + DebugTools::CompareImage); + CORRADE_COMPARE_AS((ImageView2D{actual3D.format(), actual3D.size().xy(), actual3D.data().exceptPrefix(2*sliceSize)}), + (ImageView2D{PixelFormat::RG8Unorm, {8, 8}, expectedData2}), + DebugTools::CompareImage); + CORRADE_COMPARE_AS((ImageView2D{actual3D.format(), actual3D.size().xy(), actual3D.data().exceptPrefix(3*sliceSize)}), + (ImageView2D{PixelFormat::RG8Unorm, {8, 8}, expectedData03}), + DebugTools::CompareImage); + + /* The actual texture has just the slice updated, the rest stays. On GLES + we cannot really verify that the size matches, but at least + something. */ + #ifdef MAGNUM_TARGET_GLES + /** @todo implement; blocked on Image being able to allocate and slice + itself so I don't need to implement & test all that from scratch just + for that one utility */ + CORRADE_SKIP("Cannot verify texture contents because DebugTools::textureSubImage() isn't implemented for texture arrays yet"); + #else + Image3D image = cache.texture().image(0, {PixelFormat::RG8Unorm}); + MAGNUM_VERIFY_NO_GL_ERROR(); + + const UnsignedByte expectedTextureData03[]{ + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + }; + const UnsignedByte expectedTextureData1[]{ + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + 0xcd, 0xcd, 0xcd, 0xcd, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + + 0xcd, 0xcd, 0xcd, 0xcd, 0, 0, 0x00, 0x11, + 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0, 0, + 0xcd, 0xcd, 0xcd, 0xcd, 0, 0, 0x88, 0x99, + 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0, 0, + 0xcd, 0xcd, 0xcd, 0xcd, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + }; + const UnsignedByte expectedTextureData2[]{ + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + 0xcd, 0xcd, 0xcd, 0xcd, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + + 0xcd, 0xcd, 0xcd, 0xcd, 0, 0, 0x00, 0xff, + 0x11, 0xee, 0x22, 0xdd, 0x33, 0xcc, 0, 0, + 0xcd, 0xcd, 0xcd, 0xcd, 0, 0, 0x44, 0xbb, + 0x55, 0xaa, 0x66, 0x99, 0x77, 0x88, 0, 0, + 0xcd, 0xcd, 0xcd, 0xcd, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + }; + /** @todo ugh have slicing on images directly already, and 3D image + comparison */ + CORRADE_COMPARE_AS((ImageView2D{image.format(), image.size().xy(), image.data()}), + (ImageView2D{PixelFormat::RG8Unorm, {8, 8}, expectedTextureData03}), + DebugTools::CompareImage); + CORRADE_COMPARE_AS((ImageView2D{image.format(), image.size().xy(), image.data().exceptPrefix(1*sliceSize)}), + (ImageView2D{PixelFormat::RG8Unorm, {8, 8}, expectedTextureData1}), + DebugTools::CompareImage); + CORRADE_COMPARE_AS((ImageView2D{image.format(), image.size().xy(), image.data().exceptPrefix(2*sliceSize)}), + (ImageView2D{PixelFormat::RG8Unorm, {8, 8}, expectedTextureData2}), + DebugTools::CompareImage); + CORRADE_COMPARE_AS((ImageView2D{image.format(), image.size().xy(), image.data().exceptPrefix(3*sliceSize)}), + (ImageView2D{PixelFormat::RG8Unorm, {8, 8}, expectedTextureData03}), + DebugTools::CompareImage); + #endif +} +#endif + 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 @@ -391,6 +685,31 @@ void GlyphCacheGLTest::flushImageSubclassProcessedFormatSize() { TestSuite::Compare::String); } +#ifndef MAGNUM_TARGET_GLES2 +void GlyphCacheGLTest::flushImageSubclassProcessedFormatSizeArray() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct Cache: GlyphCacheArrayGL { + explicit Cache(PixelFormat format, const Vector3i& size, PixelFormat processedFormat, const Vector2i& processedSize): GlyphCacheArrayGL{format, size, processedFormat, processedSize} {} + + GlyphCacheFeatures doFeatures() const override { + return GlyphCacheFeature::ImageProcessing; + } + }; + Cache differentFormat{PixelFormat::R8Unorm, {32, 32, 7}, PixelFormat::RGBA8Unorm, {32, 32}}; + Cache differentSize{PixelFormat::R8Unorm, {32, 32, 7}, PixelFormat::R8Unorm, {16, 32}}; + + Containers::String out; + Error redirectError{&out}; + differentFormat.flushImage({{}, {32, 32, 3}}); + differentSize.flushImage({{}, {32, 32, 3}}); + CORRADE_COMPARE_AS(out, + "Text::GlyphCacheArrayGL::flushImage(): subclass expected to provide a doSetImage() implementation to handle different processed format or size\n" + "Text::GlyphCacheArrayGL::flushImage(): subclass expected to provide a doSetImage() implementation to handle different processed format or size\n", + TestSuite::Compare::String); +} +#endif + }}}} CORRADE_TEST_MAIN(Magnum::Text::Test::GlyphCacheGLTest) diff --git a/src/Magnum/Text/Test/GlyphCacheGL_Test.cpp b/src/Magnum/Text/Test/GlyphCacheGL_Test.cpp index d103a0445..84eec104b 100644 --- a/src/Magnum/Text/Test/GlyphCacheGL_Test.cpp +++ b/src/Magnum/Text/Test/GlyphCacheGL_Test.cpp @@ -34,10 +34,17 @@ struct GlyphCacheGL_Test: TestSuite::Tester { explicit GlyphCacheGL_Test(); void constructNoCreate(); + #ifndef MAGNUM_TARGET_GLES2 + void constructNoCreateArray(); + #endif }; GlyphCacheGL_Test::GlyphCacheGL_Test() { - addTests({&GlyphCacheGL_Test::constructNoCreate}); + addTests({&GlyphCacheGL_Test::constructNoCreate, + #ifndef MAGNUM_TARGET_GLES2 + &GlyphCacheGL_Test::constructNoCreateArray + #endif + }); } void GlyphCacheGL_Test::constructNoCreate() { @@ -50,6 +57,18 @@ void GlyphCacheGL_Test::constructNoCreate() { CORRADE_VERIFY(!std::is_convertible::value); } +#ifndef MAGNUM_TARGET_GLES2 +void GlyphCacheGL_Test::constructNoCreateArray() { + GlyphCacheArrayGL cache{NoCreate}; + + /* Shouldn't crash or try to acces GL */ + CORRADE_VERIFY(true); + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!std::is_convertible::value); +} +#endif + }}}} CORRADE_TEST_MAIN(Magnum::Text::Test::GlyphCacheGL_Test) diff --git a/src/Magnum/Text/Text.h b/src/Magnum/Text/Text.h index b30c1bf36..f8aa8c217 100644 --- a/src/Magnum/Text/Text.h +++ b/src/Magnum/Text/Text.h @@ -63,6 +63,9 @@ class GlyphCacheGL; typedef CORRADE_DEPRECATED("use DistanceFieldGlyphCacheGL instead") DistanceFieldGlyphCacheGL DistanceFieldGlyphCache; typedef CORRADE_DEPRECATED("use GlyphCacheGL instead") GlyphCacheGL GlyphCache; #endif +#ifndef MAGNUM_TARGET_GLES2 +class GlyphCacheArrayGL; +#endif class AbstractRenderer; template class BasicRenderer; typedef BasicRenderer<2> Renderer2D;