diff --git a/doc/changelog.dox b/doc/changelog.dox index 0b697cc6e..c1ae1ce31 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -468,6 +468,10 @@ See also: - New @ref Text::Renderer, @ref Text::RendererGL and @ref Text::RendererCore classes that provide high-level multi-line and multi-font text rendering functionality +- New @ref Text::GlyphCacheArrayGL and + @ref Text::DistanceFieldGlyphCacheArrayGL classes that make use of + @ref GL::Texture2DArray instead of @ref GL::Texture2D to support larger + cache sizes without hitting texture size limits - New @ref Text::renderLineGlyphPositionsInto(), @ref Text::renderGlyphQuadsInto(), @ref Text::glyphQuadBounds(), @ref Text::alignRenderedLine(), @ref Text::alignRenderedBlock() and diff --git a/doc/snippets/Text-gl.cpp b/doc/snippets/Text-gl.cpp index 4fbf94478..8ab7e592f 100644 --- a/doc/snippets/Text-gl.cpp +++ b/doc/snippets/Text-gl.cpp @@ -152,6 +152,35 @@ shader /* [DistanceFieldGlyphCacheGL-usage-draw] */ } +#ifndef MAGNUM_TARGET_GLES2 +{ +PluginManager::Manager manager; +/* [DistanceFieldGlyphCacheArrayGL-usage] */ +Containers::Pointer font = DOXYGEN_ELLIPSIS(manager.loadAndInstantiate("")); +font->openFile("font.ttf", 48.0f); + +Text::DistanceFieldGlyphCacheArrayGL cache{{512, 512, 4}, {128, 128}, 12}; +if(!font->fillGlyphCache(cache, "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "0123456789?!:;,. ")) + Fatal{} << "Glyph cache too small to fit all characters"; +/* [DistanceFieldGlyphCacheArrayGL-usage] */ + +/* [DistanceFieldGlyphCacheArrayGL-usage-draw] */ +Text::RendererGL renderer{cache}; +DOXYGEN_ELLIPSIS() + +Shaders::DistanceFieldVectorGL2D shader{ + Shaders::DistanceFieldVectorGL2D::Configuration{} + .setFlags(Shaders::DistanceFieldVectorGL2D::Flag::TextureArrays)}; +shader + DOXYGEN_ELLIPSIS() + .bindVectorTexture(cache.texture()) + .draw(renderer.mesh()); +/* [DistanceFieldGlyphCacheArrayGL-usage-draw] */ +} +#endif + { /* [Renderer-usage-construct] */ Text::GlyphCacheGL cache{PixelFormat::R8Unorm, {256, 256}}; diff --git a/src/Magnum/Text/AbstractGlyphCache.h b/src/Magnum/Text/AbstractGlyphCache.h index cc8cf3460..e5cf14147 100644 --- a/src/Magnum/Text/AbstractGlyphCache.h +++ b/src/Magnum/Text/AbstractGlyphCache.h @@ -109,8 +109,9 @@ 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, @ref GlyphCacheArrayGL and @ref DistanceFieldGlyphCacheGL -subclasses then provide concrete implementations backed with an OpenGL texture. +@ref GlyphCacheGL, @ref GlyphCacheArrayGL, @ref DistanceFieldGlyphCacheGL and +@ref DistanceFieldGlyphCacheArrayGL subclasses then provide concrete +implementations backed with an OpenGL texture. @section Text-AbstractGlyphCache-usage Basic usage @@ -204,8 +205,8 @@ letters *j* or *q* that reach below the baseline). Important is to call @ref flushImage() at the end, which makes the glyph cache update its actual GPU-side texture based on what area of the image was updated. -In case of @ref DistanceFieldGlyphCacheGL for example it also triggers distance -field generation for given area. +In case of @ref DistanceFieldGlyphCacheGL / @ref DistanceFieldGlyphCacheArrayGL +for example it also triggers distance field generation for given area. If the images put into the cache are meant to be used with general meshes, the @ref TextureTools::atlasTextureCoordinateTransformation() function can be used diff --git a/src/Magnum/Text/DistanceFieldGlyphCacheGL.cpp b/src/Magnum/Text/DistanceFieldGlyphCacheGL.cpp index 476df2803..00f5ad028 100644 --- a/src/Magnum/Text/DistanceFieldGlyphCacheGL.cpp +++ b/src/Magnum/Text/DistanceFieldGlyphCacheGL.cpp @@ -102,6 +102,28 @@ GlyphCacheFeatures DistanceFieldGlyphCacheGL::doFeatures() const { ; } +namespace { + +Range2Di paddedImageRange(const Vector3i& cacheSize, const Vector2i& imageOffset, const Vector2i& imageSize, const Vector2i& ratio) { + const Vector2i paddedMin = imageOffset; + const Vector2i paddedMax = imageOffset + imageSize; + + /* TextureTools::DistanceFieldGL expects the input size and output + rectangle size ratio to be a multiple of 2 in order for the shader to + perform pixel addressing correctly. That might not always be the case + with the rectangle passed to flushImage(), so round the paddedMin *down* + to a multiple of the ratio and paddedMax *up* to a multiple of the + ratio. */ + const Vector2i paddedMinRounded = ratio*(paddedMin/ratio); + const Vector2i paddedMaxRounded = ratio*((paddedMax + ratio - Vector2i{1})/ratio); + /* As the size is also a multiple of ratio, the resulting size should not + get larger */ + CORRADE_INTERNAL_ASSERT(paddedMaxRounded <= cacheSize.xy()); + return {paddedMinRounded, paddedMaxRounded}; +} + +} + void DistanceFieldGlyphCacheGL::doSetImage(const Vector2i& #if !(defined(MAGNUM_TARGET_GLES2) && defined(MAGNUM_TARGET_WEBGL)) && !defined(CORRADE_NO_ASSERT) offset @@ -154,26 +176,12 @@ void DistanceFieldGlyphCacheGL::doSetImage(const Vector2i& /* The image range was already expanded to include the padding in flushImage() */ CORRADE_INTERNAL_ASSERT(image.storage().skip().xy() == offset); - const Vector2i paddedMin = image.storage().skip().xy(); - const Vector2i paddedMax = image.size() + image.storage().skip().xy(); - - /* TextureTools::DistanceFieldGL expects the input size and output - rectangle size ratio to be a multiple of 2 in order for the shader - to perform pixel addressing correctly. That might not always be the - case with the rectangle passed to flushImage(), so round the - paddedMin *down* to a multiple of the ratio and paddedMax *up* to a - multiple of the ratio. */ - const Vector2i paddedMinRounded = ratio*(paddedMin/ratio); - const Vector2i paddedMaxRounded = ratio*((paddedMax + ratio - Vector2i{1})/ratio); - /* As the size is also a multiple of ratio, the resulting size should - not get larger. */ - CORRADE_INTERNAL_ASSERT(paddedMaxRounded <= size().xy()); - + const Range2Di paddedRange = paddedImageRange(size(), image.storage().skip().xy(), image.size(), ratio); const ImageView2D paddedImage{ PixelStorage{image.storage()} - .setSkip({paddedMinRounded, image.storage().skip().z()}), + .setSkip({paddedRange.min(), image.storage().skip().z()}), image.format(), - paddedMaxRounded - paddedMinRounded, + paddedRange.size(), image.data()}; /** @todo investigate if using setStorage() + setSubImage() is any @@ -181,7 +189,7 @@ void DistanceFieldGlyphCacheGL::doSetImage(const Vector2i& temporary it doesn't matter much anyway; similarly with the temporary framebuffer created inside */ input.setImage(0, GL::textureFormat(paddedImage.format()), paddedImage); - state.distanceField(input, texture(), {paddedMinRounded/ratio, paddedMaxRounded/ratio}, paddedImage.size()); + state.distanceField(input, texture(), {paddedRange.min()/ratio, paddedRange.max()/ratio}, paddedRange.size()); } #endif } @@ -205,4 +213,117 @@ void DistanceFieldGlyphCacheGL::setDistanceFieldImage(const Vector2i& offset, co } #endif +#ifndef MAGNUM_TARGET_GLES2 +struct DistanceFieldGlyphCacheArrayGL::State: GlyphCacheArrayGL::State { + explicit State(const Vector3i& size, const Vector2i& processedSize, UnsignedInt radius); + + TextureTools::DistanceFieldGL distanceField; +}; + +DistanceFieldGlyphCacheArrayGL::State::State(const Vector3i& size, const Vector2i& processedSize, const UnsignedInt radius): + GlyphCacheArrayGL::State{ + PixelFormat::R8Unorm, size, PixelFormat::R8Unorm, + processedSize, Vector2i(radius)}, + distanceField{radius} +{ + /* Replicating the assertion from TextureTools::DistanceFieldGL so it gets + checked during construction already instead of only later during the + setImage() call */ + CORRADE_ASSERT(size.xy() % processedSize == Vector2i{0} && + (size.xy()/processedSize) % 2 == Vector2i{0}, + "Text::DistanceFieldGlyphCacheArrayGL: expected source and processed size ratio to be a multiple of 2, got" << Debug::packed << size.xy() << "and" << Debug::packed << processedSize, ); +} + +DistanceFieldGlyphCacheArrayGL::DistanceFieldGlyphCacheArrayGL(const Vector3i& size, const Vector2i& processedSize, UnsignedInt radius): GlyphCacheArrayGL{Containers::pointer(size, processedSize, radius)} {} + +DistanceFieldGlyphCacheArrayGL::DistanceFieldGlyphCacheArrayGL(NoCreateT) noexcept: GlyphCacheArrayGL{NoCreate} {} + +GlyphCacheFeatures DistanceFieldGlyphCacheArrayGL::doFeatures() const { + return GlyphCacheFeature::ImageProcessing + #ifndef MAGNUM_TARGET_GLES + |GlyphCacheFeature::ProcessedImageDownload + #endif + ; +} + +void DistanceFieldGlyphCacheArrayGL::doSetImage(const Vector3i& offset, const ImageView3D& image) { + auto& state = static_cast(*_state); + + /* Like with DistanceFieldGlyphCacheGL above, the assumption is that a + temporary texture instance is better than a persistent one */ + GL::Texture2D input; + input + /* Unlike with DistanceFieldGlyphCacheGL, neither wrapping nor nearest + filter should be needed as we always use texelFetch(), but use it + for consistency. The Base mipmap setting is however for some reason + needed even for texelFetch() as with Nearest / Linear it results in + zero output (likely due to setImage() being used below instead of + setStorage()?). */ + /** @todo might want to clear this up once setStorage() is used? */ + .setWrapping(GL::SamplerWrapping::ClampToEdge) + .setMinificationFilter(GL::SamplerFilter::Nearest, GL::SamplerMipmap::Base) + .setMagnificationFilter(GL::SamplerFilter::Nearest); + + /* The constructor already checked that the ratio is an integer multiple, + so this division should lead to no information loss */ + CORRADE_INTERNAL_ASSERT(size().xy() % processedSize().xy() == Vector2i{0}); + const Vector2i ratio = size().xy()/processedSize().xy(); + + /* Upload the input texture and create a distance field from it */ + + /* The image range was already expanded to include the padding in + flushImage() */ + CORRADE_INTERNAL_ASSERT(image.storage().skip().xy() == offset.xy()); + const Range2Di paddedRange = paddedImageRange(size(), image.storage().skip().xy(), image.size().xy(), ratio); + const ImageView3D paddedImage{ + PixelStorage{image.storage()} + .setSkip({paddedRange.min(), image.storage().skip().z()}), + image.format(), + {paddedRange.size(), image.size().z()}, + image.data()}; + + /* Properties needed for slicing the image to individual layers below */ + /** @todo clean this up once Image APIs stop being shit */ + const std::size_t firstLayerOffset = paddedImage.dataProperties().first.z(); + const std::size_t layerStride = paddedImage.dataProperties().second.xy().product(); + + /* Cycle through all layers, for each upload slice of the input image, + attach the corresponding output texture array layer to the framebuffer + and run the distance field processing. Yes, this means a separate GPU + call for each layer, but: + + - The processing has to be done layer by layer anyway, as drawing to + multiple layers at once is only possible with geometry shaders or + image load/store. GS isn't available on WebGL or other ES3 + platforms we care about and generally has perf pitfalls unless a + GS passthrough extension is available, which is basically just on + NVidia. Image load/store is available only where compute is, so + also just ES3.1+ or desktop, and generally fragment shader + processing is always faster because the invocations are done in a + more cache friendly manner than with compute. With compute one + *can* emulate such behavior by hand, but it sidesteps the GPU's + builtin implementation, likely always only playing catch up. + - Because only a single input layer is uploaded at a time, the GPU + memory use is reduced compared to allocating the whole input + texture array and then uploading and processing just a part. */ + for(Int i = 0; i != image.size().z(); ++i) { + /** @todo like with DistanceFieldGlyphCacheGL above, investigate if + using setStorage() + setSubImage() or a persistent framebuffer + instance is any faster than this */ + input.setImage(0, GL::textureFormat(paddedImage.format()), ImageView2D{ + /* Ideally, with a sane API, I wouldn't need to reset the Z skip to + 0 and offset the data pointer, but with 2D images GL ignores the + Z skip */ + /** @todo clean up all this once this useless GL-shaped API is + dropped */ + PixelStorage{paddedImage.storage()} + .setSkip({paddedRange.min(), 0}), + paddedImage.format(), + paddedImage.size().xy(), + paddedImage.data().exceptPrefix(firstLayerOffset + i*layerStride)}); + state.distanceField(input, texture(), offset.z() + i, {paddedRange.min()/ratio, paddedRange.max()/ratio}, paddedRange.size()); + } +} +#endif + }} diff --git a/src/Magnum/Text/DistanceFieldGlyphCacheGL.h b/src/Magnum/Text/DistanceFieldGlyphCacheGL.h index 99fe92416..32e0c33b0 100644 --- a/src/Magnum/Text/DistanceFieldGlyphCacheGL.h +++ b/src/Magnum/Text/DistanceFieldGlyphCacheGL.h @@ -27,7 +27,7 @@ */ /** @file - * @brief Class @ref Magnum::Text::DistanceFieldGlyphCacheGL + * @brief Class @ref Magnum::Text::DistanceFieldGlyphCacheGL, @ref Magnum::Text::DistanceFieldGlyphCacheArrayGL * @m_since_latest */ @@ -175,6 +175,85 @@ class MAGNUM_TEXT_EXPORT DistanceFieldGlyphCacheGL: public GlyphCacheGL { MAGNUM_TEXT_LOCAL void doSetImage(const Vector2i& offset, const ImageView2D& image) override; }; +#ifndef MAGNUM_TARGET_GLES2 +/** +@brief OpenGL array glyph cache with distance field rendering +@m_since_latest + +Like @ref DistanceFieldGlyphCacheGL, but backed by a @ref GL::Texture2DArray +instead of @ref GL::Texture2D. See the @ref AbstractGlyphCache class +documentation for information about setting up a glyph cache instance and +filling it with glyphs, and @ref DistanceFieldGlyphCacheGL for details specific +to distance field processing and used internal texture format. The setup +differs from @ref DistanceFieldGlyphCacheGL only in specifying one extra +dimension for size: + +@snippet Text-gl.cpp DistanceFieldGlyphCacheArrayGL-usage + +Assuming a @ref RendererGL is used with this cache for rendering the text, its +@relativeref{RendererGL,mesh()} can be then drawn using +@ref Shaders::DistanceFieldVectorGL that has +@ref Shaders::DistanceFieldVectorGL::Flag::TextureArrays enabled, together with +binding @ref texture() for drawing: + +@snippet Text-gl.cpp DistanceFieldGlyphCacheArrayGL-usage-draw + +@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 DistanceFieldGlyphCacheArrayGL: public GlyphCacheArrayGL { + public: + /** + * @brief Constructor + * @param size Size of the source image + * @param processedSize Resulting distance field texture size. + * Depth of the resulting texture is @cpp size.z() @ce. + * @param radius Distance field calculation radius + * + * See @ref TextureTools::DistanceFieldGL for more information about + * the parameters. Size restrictions from it apply here as well, in + * particular the ratio of @cpp size.xy() @ce and @p processedSize is + * expected to be a multiple of 2. + * + * Sets the @ref processedFormat() to @ref PixelFormat::R8Unorm, if + * available. On OpenGL ES 3.0+ and WebGL 2 uses + * @ref PixelFormat::R8Unorm always. On desktop OpenGL requires + * @gl_extension{ARB,texture_rg} (part of OpenGL 3.0), on ES2 uses + * @gl_extension{EXT,texture_rg} if available and uses + * @ref PixelFormat::RGB8Unorm as fallback if not, on WebGL 1 uses + * @ref PixelFormat::RGB8Unorm always. + */ + explicit DistanceFieldGlyphCacheArrayGL(const Vector3i& size, const Vector2i& processedSize, UnsignedInt radius); + + /** + * @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 DistanceFieldGlyphCacheArrayGL(NoCreateT) noexcept; + + private: + struct State; + + MAGNUM_TEXT_LOCAL GlyphCacheFeatures doFeatures() const override; + MAGNUM_TEXT_LOCAL 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/GlyphCacheGL.cpp b/src/Magnum/Text/GlyphCacheGL.cpp index c1351bf73..36367eea2 100644 --- a/src/Magnum/Text/GlyphCacheGL.cpp +++ b/src/Magnum/Text/GlyphCacheGL.cpp @@ -219,6 +219,8 @@ GlyphCacheArrayGL::GlyphCacheArrayGL(const PixelFormat format, const Vector3i& s GlyphCacheArrayGL::GlyphCacheArrayGL(const PixelFormat format, const Vector3i& size): GlyphCacheArrayGL{format, size, Vector2i{1}} {} +GlyphCacheArrayGL::GlyphCacheArrayGL(Containers::Pointer&& state) noexcept: AbstractGlyphCache{Utility::move(state)} {} + GlyphCacheArrayGL::GlyphCacheArrayGL(NoCreateT) noexcept: AbstractGlyphCache{NoCreate} {} GL::Texture2DArray& GlyphCacheArrayGL::texture() { @@ -233,6 +235,16 @@ void GlyphCacheArrayGL::doSetImage(const Vector3i& offset, const ImageView3D& im static_cast(*_state).texture.setSubImage(0, offset, image); } + +void GlyphCacheArrayGL::doSetProcessedImage(const Vector3i& offset, const ImageView3D& image) { + static_cast(*_state).texture.setSubImage(0, offset, image); +} + +#ifndef MAGNUM_TARGET_GLES +Image3D GlyphCacheArrayGL::doProcessedImage() { + return static_cast(*_state).texture.image(0, processedFormat()); +} +#endif #endif }} diff --git a/src/Magnum/Text/GlyphCacheGL.h b/src/Magnum/Text/GlyphCacheGL.h index b604ef621..af15ba9ff 100644 --- a/src/Magnum/Text/GlyphCacheGL.h +++ b/src/Magnum/Text/GlyphCacheGL.h @@ -226,8 +226,10 @@ 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 setup differs from -@ref GlyphCacheGL only in specifying one extra dimension for size: +details on how the internal texture format is picked. See the +@ref DistanceFieldGlyphCacheArrayGL subclass for a variant that adds distance +field processing on top. The setup differs from @ref GlyphCacheGL only in +specifying one extra dimension for size: @snippet Text-gl.cpp GlyphCacheArrayGL-usage @@ -322,10 +324,19 @@ class MAGNUM_TEXT_EXPORT GlyphCacheArrayGL: public AbstractGlyphCache { #endif struct State; + /* Used by DistanceFieldGlyphCacheArrayGL */ + explicit GlyphCacheArrayGL(Containers::Pointer&& state) noexcept; + 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; + /* Used if a subclass advertises GlyphCacheFeature::ImageProcessing / + ProcessedImageDownload in its doFeatures() */ + void doSetProcessedImage(const Vector3i& offset, const ImageView3D& image) override; + #ifndef MAGNUM_TARGET_GLES + Image3D doProcessedImage() override; + #endif }; #endif diff --git a/src/Magnum/Text/Test/DistanceFieldGlyphCacheGLTest.cpp b/src/Magnum/Text/Test/DistanceFieldGlyphCacheGLTest.cpp index 66fcc8b68..80917aef4 100644 --- a/src/Magnum/Text/Test/DistanceFieldGlyphCacheGLTest.cpp +++ b/src/Magnum/Text/Test/DistanceFieldGlyphCacheGLTest.cpp @@ -44,6 +44,9 @@ #include "Magnum/GL/Extensions.h" #include "Magnum/GL/PixelFormat.h" #include "Magnum/GL/Texture.h" +#ifndef MAGNUM_TARGET_GLES2 +#include "Magnum/GL/TextureArray.h" +#endif #include "Magnum/Text/DistanceFieldGlyphCacheGL.h" #include "Magnum/Trade/AbstractImporter.h" #include "Magnum/Trade/ImageData.h" @@ -56,15 +59,35 @@ struct DistanceFieldGlyphCacheGLTest: GL::OpenGLTester { explicit DistanceFieldGlyphCacheGLTest(); void construct(); + #ifndef MAGNUM_TARGET_GLES2 + void constructArray(); + #endif void constructSizeRatioNotMultipleOfTwo(); - + #ifndef MAGNUM_TARGET_GLES2 + void constructSizeRatioNotMultipleOfTwoArray(); + #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 setImageEdgeClamp(); + #ifndef MAGNUM_TARGET_GLES2 + void setImageEdgeClampArray(); + #endif void setProcessedImage(); + #ifndef MAGNUM_TARGET_GLES2 + void setProcessedImageArray(); + #endif #ifdef MAGNUM_BUILD_DEPRECATED void setDistanceFieldImageUnsupportedGLFormat(); #endif @@ -103,6 +126,61 @@ const struct { {}}, }; +/* Expands upon SetImageData with third dimension. For simplicity only a single + layer always contains data to process, but it's changed which one it is to + verify all uploaded layers get processed. */ +const struct { + const char* name; + Vector3i sourceSize; + Vector2i size; + Vector3i sourceOffset; + Range3Di flushRange; + Containers::Size2D offset; +} SetImageArrayData[]{ + {"single layer", + {256, 256, 1}, {64, 64}, {}, + {{}, {256, 256, 1}}, + {}}, + {"multiple layers, data in the first layer", + {256, 256, 7}, {64, 64}, {}, + {{}, {256, 256, 7}}, + {}}, + {"multiple layers, data in the middle layer", + {256, 256, 7}, {64, 64}, {0, 0, 3}, + {{}, {256, 256, 7}}, + {}}, + {"multiple layers, data in the last layer", + {256, 256, 7}, {64, 64}, {0, 0, 6}, + {{}, {256, 256, 7}}, + {}}, + {"single layer, upload with offset", + {512, 384, 1}, {128, 96}, {256, 128, 0}, + {{256, 128, 0}, {512, 384, 1}}, + {128/4, 256/4}}, + {"multiple layers, upload with offset, data in the first flushed layer", + {512, 384, 7}, {128, 96}, {256, 128, 3}, + {{256, 128, 3}, {512, 384, 7}}, + {128/4, 256/4}}, + {"multiple layers, upload with offset, data in the middle flushed layer", + {512, 384, 7}, {128, 96}, {256, 128, 3}, + {{256, 128, 1}, {512, 384, 5}}, + {128/4, 256/4}}, + {"multiple layers, upload with offset, data in the last flushed layer", + {512, 384, 7}, {128, 96}, {256, 128, 4}, + {{256, 128, 1}, {512, 384, 5}}, + {128/4, 256/4}}, + /* These two don't have the other layer offset variants as that should be + sufficiently tested above */ + {"tight flush rectangle", + {256, 256, 7}, {64, 64}, {0, 0, 3}, + {{48, 48, 2}, {208, 208, 6}}, + {}}, + {"tight flush rectangle, ratio not a multiple of 2", + {256, 256, 7}, {64, 64}, {0, 0, 3}, + {{47, 48, 2}, {208, 209, 6}}, + {}}, +}; + #ifdef MAGNUM_BUILD_DEPRECATED const struct { const char* name; @@ -117,22 +195,49 @@ const struct { DistanceFieldGlyphCacheGLTest::DistanceFieldGlyphCacheGLTest() { addTests({&DistanceFieldGlyphCacheGLTest::construct, + #ifndef MAGNUM_TARGET_GLES2 + &DistanceFieldGlyphCacheGLTest::constructArray, + #endif &DistanceFieldGlyphCacheGLTest::constructSizeRatioNotMultipleOfTwo, - + #ifndef MAGNUM_TARGET_GLES2 + &DistanceFieldGlyphCacheGLTest::constructSizeRatioNotMultipleOfTwoArray, + #endif &DistanceFieldGlyphCacheGLTest::constructCopy, - &DistanceFieldGlyphCacheGLTest::constructMove}); + #ifndef MAGNUM_TARGET_GLES2 + &DistanceFieldGlyphCacheGLTest::constructCopyArray, + #endif + &DistanceFieldGlyphCacheGLTest::constructMove, + #ifndef MAGNUM_TARGET_GLES2 + &DistanceFieldGlyphCacheGLTest::constructMoveArray + #endif + }); addInstancedTests({&DistanceFieldGlyphCacheGLTest::setImage}, Containers::arraySize(SetImageData)); - addTests({&DistanceFieldGlyphCacheGLTest::setImageEdgeClamp}); + #ifndef MAGNUM_TARGET_GLES2 + addInstancedTests({&DistanceFieldGlyphCacheGLTest::setImageArray}, + Containers::arraySize(SetImageArrayData)); + #endif + + addTests({&DistanceFieldGlyphCacheGLTest::setImageEdgeClamp, + #ifndef MAGNUM_TARGET_GLES2 + &DistanceFieldGlyphCacheGLTest::setImageEdgeClampArray + #endif + }); #ifndef MAGNUM_BUILD_DEPRECATED addTests({&DistanceFieldGlyphCacheGLTest::setProcessedImage}); #else addInstancedTests({&DistanceFieldGlyphCacheGLTest::setProcessedImage}, Containers::arraySize(SetProcessedImageData)); + #endif + + #ifndef MAGNUM_TARGET_GLES2 + addTests({&DistanceFieldGlyphCacheGLTest::setProcessedImageArray}); + #endif + #ifdef MAGNUM_BUILD_DEPRECATED addTests({&DistanceFieldGlyphCacheGLTest::setDistanceFieldImageUnsupportedGLFormat}); #endif @@ -181,6 +286,32 @@ void DistanceFieldGlyphCacheGLTest::construct() { #endif } +#ifndef MAGNUM_TARGET_GLES2 +void DistanceFieldGlyphCacheGLTest::constructArray() { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::texture_array::string() << "is not supported."); + #endif + + DistanceFieldGlyphCacheArrayGL cache{{256, 512, 7}, {64, 128}, 16}; + MAGNUM_VERIFY_NO_GL_ERROR(); + + #ifndef MAGNUM_TARGET_GLES + CORRADE_COMPARE(cache.features(), GlyphCacheFeature::ImageProcessing|GlyphCacheFeature::ProcessedImageDownload); + #else + CORRADE_COMPARE(cache.features(), GlyphCacheFeature::ImageProcessing); + #endif + /* The input format is always single-channel */ + CORRADE_COMPARE(cache.format(), PixelFormat::R8Unorm); + CORRADE_COMPARE(cache.size(), (Vector3i{256, 512, 7})); + CORRADE_COMPARE(cache.processedFormat(), PixelFormat::R8Unorm); + CORRADE_COMPARE(cache.processedSize(), (Vector3i{64, 128, 7})); + #ifndef MAGNUM_TARGET_GLES + CORRADE_COMPARE(cache.texture().imageSize(0), (Vector3i{64, 128, 7})); + #endif +} +#endif + void DistanceFieldGlyphCacheGLTest::constructSizeRatioNotMultipleOfTwo() { CORRADE_SKIP_IF_NO_ASSERT(); @@ -206,11 +337,50 @@ void DistanceFieldGlyphCacheGLTest::constructSizeRatioNotMultipleOfTwo() { TestSuite::Compare::String); } +#ifndef MAGNUM_TARGET_GLES2 +void DistanceFieldGlyphCacheGLTest::constructSizeRatioNotMultipleOfTwoArray() { + CORRADE_SKIP_IF_NO_ASSERT(); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::texture_array::string() << "is not supported."); + #endif + + /* This should be fine. THe depth doesn't affect anything. */ + DistanceFieldGlyphCacheArrayGL{{Vector2i{23*14}, 7}, Vector2i{23}, 4}; + + /* It's the same assert as in TextureTools::DistanceFieldGL */ + Containers::String out; + Error redirectError{&out}; + DistanceFieldGlyphCacheArrayGL{{Vector2i{23*14}, 7}, Vector2i{23*2}, 4}; + /* Verify also just one axis wrong */ + DistanceFieldGlyphCacheArrayGL{{Vector2i{23*14}, 7}, {23*2, 23}, 4}; + DistanceFieldGlyphCacheArrayGL{{Vector2i{23*14}, 7}, {23, 23*2}, 4}; + /* Almost correct except that it's not an integer multiply */ + DistanceFieldGlyphCacheArrayGL{{Vector2i{23*14}, 7}, {22, 23}, 4}; + DistanceFieldGlyphCacheArrayGL{{Vector2i{23*14}, 7}, {23, 22}, 4}; + CORRADE_COMPARE_AS(out, + "Text::DistanceFieldGlyphCacheArrayGL: expected source and processed size ratio to be a multiple of 2, got {322, 322} and {46, 46}\n" + "Text::DistanceFieldGlyphCacheArrayGL: expected source and processed size ratio to be a multiple of 2, got {322, 322} and {46, 23}\n" + "Text::DistanceFieldGlyphCacheArrayGL: expected source and processed size ratio to be a multiple of 2, got {322, 322} and {23, 46}\n" + "Text::DistanceFieldGlyphCacheArrayGL: expected source and processed size ratio to be a multiple of 2, got {322, 322} and {22, 23}\n" + "Text::DistanceFieldGlyphCacheArrayGL: expected source and processed size ratio to be a multiple of 2, got {322, 322} and {23, 22}\n", + TestSuite::Compare::String); +} +#endif + void DistanceFieldGlyphCacheGLTest::constructCopy() { CORRADE_VERIFY(!std::is_copy_constructible{}); CORRADE_VERIFY(!std::is_copy_assignable{}); } +#ifndef MAGNUM_TARGET_GLES2 +void DistanceFieldGlyphCacheGLTest::constructCopyArray() { + CORRADE_VERIFY(!std::is_copy_constructible{}); + CORRADE_VERIFY(!std::is_copy_assignable{}); +} +#endif + void DistanceFieldGlyphCacheGLTest::constructMove() { DistanceFieldGlyphCacheGL a{{256, 512}, {64, 64}, 3}; @@ -225,6 +395,27 @@ void DistanceFieldGlyphCacheGLTest::constructMove() { CORRADE_VERIFY(std::is_nothrow_move_assignable::value); } +#ifndef MAGNUM_TARGET_GLES2 +void DistanceFieldGlyphCacheGLTest::constructMoveArray() { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::texture_array::string() << "is not supported."); + #endif + + DistanceFieldGlyphCacheArrayGL a{{256, 512, 7}, {64, 64}, 3}; + + DistanceFieldGlyphCacheArrayGL b = Utility::move(a); + CORRADE_COMPARE(b.size(), (Vector3i{256, 512, 7})); + + DistanceFieldGlyphCacheArrayGL c{{2, 4, 3}, {1, 2}, 1}; + c = Utility::move(b); + CORRADE_COMPARE(c.size(), (Vector3i{256, 512, 7})); + + CORRADE_VERIFY(std::is_nothrow_move_constructible::value); + CORRADE_VERIFY(std::is_nothrow_move_assignable::value); +} +#endif + void DistanceFieldGlyphCacheGLTest::setImage() { auto&& data = SetImageData[testCaseInstanceId()]; setTestCaseDescription(data.name); @@ -297,6 +488,70 @@ void DistanceFieldGlyphCacheGLTest::setImage() { (DebugTools::CompareImageToFile{_manager, 1.0f, 0.178f})); } +#ifndef MAGNUM_TARGET_GLES2 +void DistanceFieldGlyphCacheGLTest::setImageArray() { + auto&& data = SetImageArrayData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::texture_array::string() << "is not supported."); + #endif + + Containers::Pointer importer; + if(!(importer = _manager.loadAndInstantiate("TgaImporter"))) + CORRADE_SKIP("TgaImporter plugin not found."); + + CORRADE_VERIFY(importer->openFile(Utility::Path::join(TEXTURETOOLS_DISTANCEFIELDGLTEST_DIR, "input.tga"))); + CORRADE_COMPARE(importer->image2DCount(), 1); + Containers::Optional inputImage = importer->image2D(0); + CORRADE_VERIFY(inputImage); + CORRADE_COMPARE(inputImage->format(), PixelFormat::R8Unorm); + CORRADE_COMPARE(inputImage->size(), (Vector2i{256, 256})); + + DistanceFieldGlyphCacheArrayGL cache{data.sourceSize, data.size, 32}; + + /* Clear the target texture to avoid random garbage getting in when the + data.flushRange isn't covering the whole output */ + Containers::Array zeros{ValueInit, data.size.product()*data.sourceSize.z()*pixelFormatSize(cache.processedFormat())}; + cache.texture().setSubImage(0, {}, ImageView3D{cache.processedFormat(), {data.size, data.sourceSize.z()}, zeros}); + + Containers::StridedArrayView3D src = inputImage->pixels(); + /* Test also uploading under an offset */ + Utility::copy(src, cache.image().pixels().sliceSize({ + std::size_t(data.sourceOffset.z()), + std::size_t(data.sourceOffset.y()), + std::size_t(data.sourceOffset.x())}, src.size())); + cache.flushImage(data.flushRange); + MAGNUM_VERIFY_NO_GL_ERROR(); + + /* On GLES processedImage() isn't implemented as it'd mean creating a + temporary framebuffer. Do it via DebugTools here instead, we cannot + really verify that the size matches, but at least something. + + Only one layer always contains the processed data, get just that one. */ + #ifndef MAGNUM_TARGET_GLES + Image3D actual3 = cache.processedImage(); + /** @todo ugh have slicing on images directly already */ + MutableImageView2D actual{actual3.format(), actual3.size().xy(), actual3.data().exceptPrefix(actual3.size().xy().product()*actual3.pixelSize()*data.sourceOffset.z())}; + #else + Image2D actual = DebugTools::textureSubImage(cache.texture(), 0, data.sourceOffset.z(), {{}, data.size}, cache.processedFormat()); + #endif + MAGNUM_VERIFY_NO_GL_ERROR(); + + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + + /* The format may be three-component, consider just the first channel */ + Containers::StridedArrayView3D pixels = actual.pixels(); + CORRADE_COMPARE_WITH((Containers::arrayCast<2, const UnsignedByte>(pixels.prefix({pixels.size()[0], pixels.size()[1], 1})).exceptPrefix(data.offset)), + Utility::Path::join(TEXTURETOOLS_DISTANCEFIELDGLTEST_DIR, "output.tga"), + /* Same threshold as in TextureTools DistanceFieldGLTest */ + (DebugTools::CompareImageToFile{_manager, 1.0f, 0.178f})); +} +#endif + void DistanceFieldGlyphCacheGLTest::setImageEdgeClamp() { /* Verifies that the input texture filtering clamp is set to edge to not have content from one side leak to another when the data don't have @@ -352,6 +607,63 @@ void DistanceFieldGlyphCacheGLTest::setImageEdgeClamp() { CORRADE_COMPARE(dst[1][0], '\x00'); } +#ifndef MAGNUM_TARGET_GLES2 +void DistanceFieldGlyphCacheGLTest::setImageEdgeClampArray() { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::texture_array::string() << "is not supported."); + #endif + + /* Like setImageEdgeClamp(), but for texture arrays. As texelFetch() is + always used in this case, a leak should never happen, nevertheless it's + good to have it verified. */ + + DistanceFieldGlyphCacheArrayGL cache{{8, 4, 1}, {4, 2}, 4}; + + /* Make the right edge all white */ + Containers::StridedArrayView2D src = cache.image().pixels()[0]; + src[0][7] = '\xff'; + src[1][7] = '\xff'; + src[2][7] = '\xff'; + src[3][7] = '\xff'; + cache.flushImage({{}, {8, 4}}); + MAGNUM_VERIFY_NO_GL_ERROR(); + + /* On GLES processedImage() isn't implemented as it'd mean creating a + temporary framebuffer. Do it via DebugTools here instead, we cannot + really verify that the size matches, but at least something. */ + #ifndef MAGNUM_TARGET_GLES + Image3D actual3 = cache.processedImage(); + /** @todo ugh have slicing on images directly already */ + MutableImageView2D actual{actual3.format(), actual3.size().xy(), actual3.data()}; + #else + Image2D actual = DebugTools::textureSubImage(cache.texture(), 0, 0, {{}, {4, 2}}, cache.processedFormat()); + #endif + MAGNUM_VERIFY_NO_GL_ERROR(); + + /* The format may be three-component, consider just the first channel */ + Containers::StridedArrayView3D dst3 = actual.pixels(); + Containers::StridedArrayView2D dst = Containers::arrayCast<2, const UnsignedByte>(dst3.prefix({dst3.size()[0], dst3.size()[1], 1})); + + /* On the right side the pixels should be non-zero to verify processing got + done at all */ + CORRADE_VERIFY(dst[0][3] > 0); + CORRADE_VERIFY(dst[1][3] > 0); + + /* On the left side the pixels should be completely zero, without the right + side leaking for example due to accidental repeat clamp */ + CORRADE_COMPARE(dst[0][0], '\x00'); + CORRADE_COMPARE(dst[1][0], '\x00'); +} +#endif + +const UnsignedByte InputData[]{ + 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, + 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, + 0x00, 0xff, 0x11, 0xee, 0x22, 0xdd, 0x33, 0xcc, + 0x44, 0xbb, 0x55, 0xaa, 0x66, 0x99, 0x77, 0x88, +}; + void DistanceFieldGlyphCacheGLTest::setProcessedImage() { #ifdef MAGNUM_BUILD_DEPRECATED auto&& data = SetProcessedImageData[testCaseInstanceId()]; @@ -371,13 +683,6 @@ void DistanceFieldGlyphCacheGLTest::setProcessedImage() { cache.setProcessedImage({}, ImageView2D{PixelFormat::R8Unorm, {16, 8}, zeros}); MAGNUM_VERIFY_NO_GL_ERROR(); - UnsignedByte imageData[]{ - 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, - 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, - 0x00, 0xff, 0x11, 0xee, 0x22, 0xdd, 0x33, 0xcc, - 0x44, 0xbb, 0x55, 0xaa, 0x66, 0x99, 0x77, 0x88, - }; - #ifdef MAGNUM_BUILD_DEPRECATED if(data.deprecated) { CORRADE_IGNORE_DEPRECATED_PUSH @@ -388,14 +693,14 @@ void DistanceFieldGlyphCacheGLTest::setProcessedImage() { #else GL::PixelFormat::Luminance, #endif - GL::PixelType::UnsignedByte, {8, 4}, imageData}); + GL::PixelType::UnsignedByte, {8, 4}, InputData}); else - cache.setDistanceFieldImage({8, 4}, ImageView2D{PixelFormat::R8Unorm, {8, 4}, imageData}); + cache.setDistanceFieldImage({8, 4}, ImageView2D{PixelFormat::R8Unorm, {8, 4}, InputData}); CORRADE_IGNORE_DEPRECATED_POP } else #endif { - cache.setProcessedImage({8, 4}, ImageView2D{PixelFormat::R8Unorm, {8, 4}, imageData}); + cache.setProcessedImage({8, 4}, ImageView2D{PixelFormat::R8Unorm, {8, 4}, InputData}); } MAGNUM_VERIFY_NO_GL_ERROR(); @@ -438,6 +743,93 @@ void DistanceFieldGlyphCacheGLTest::setProcessedImage() { DebugTools::CompareImage); } +#ifndef MAGNUM_TARGET_GLES2 +void DistanceFieldGlyphCacheGLTest::setProcessedImageArray() { + #ifndef MAGNUM_TARGET_GLES + if(!GL::Context::current().isExtensionSupported()) + CORRADE_SKIP(GL::Extensions::EXT::texture_array::string() << "is not supported."); + #endif + + /* Adapted from GlyphCacheGLTest::setImageArray(), with a difference that a + single-component is used so the X size is 16 instead of 8 */ + + DistanceFieldGlyphCacheArrayGL cache{{64, 32, 4}, {16, 8}, 16}; + + /* Clear the texture first, as it'd have random garbage otherwise */ + UnsignedByte zeros[16*8*4]{}; + cache.setProcessedImage({}, ImageView3D{PixelFormat::R8Unorm, {16, 8, 4}, zeros}); + MAGNUM_VERIFY_NO_GL_ERROR(); + + cache.setProcessedImage({6, 4, 1}, ImageView3D{PixelFormat::R8Unorm, {8, 2, 2}, InputData}); + MAGNUM_VERIFY_NO_GL_ERROR(); + + /* On GLES processedImage() isn't implemented as it'd mean creating a + temporary framebuffer. Do it via DebugTools here instead, we cannot + really verify that the size matches, but at least something. */ + #ifndef MAGNUM_TARGET_GLES + Image3D image = cache.processedImage(); + /** @todo ugh have slicing on images directly already, and 3D image + comparison */ + const std::size_t sliceSize = image.size().xy().product(); + ImageView2D image0{image.format(), image.size().xy(), image.data()}; + ImageView2D image1{image.format(), image.size().xy(), image.data().exceptPrefix(1*sliceSize)}; + ImageView2D image2{image.format(), image.size().xy(), image.data().exceptPrefix(2*sliceSize)}; + ImageView2D image3{image.format(), image.size().xy(), image.data().exceptPrefix(3*sliceSize)}; + #else + Image2D image0 = DebugTools::textureSubImage(cache.texture(), 0, 0, {{}, {16, 8}}, {PixelFormat::R8Unorm}); + Image2D image1 = DebugTools::textureSubImage(cache.texture(), 0, 1, {{}, {16, 8}}, {PixelFormat::R8Unorm}); + Image2D image2 = DebugTools::textureSubImage(cache.texture(), 0, 2, {{}, {16, 8}}, {PixelFormat::R8Unorm}); + Image2D image3 = DebugTools::textureSubImage(cache.texture(), 0, 3, {{}, {16, 8}}, {PixelFormat::R8Unorm}); + #endif + MAGNUM_VERIFY_NO_GL_ERROR(); + + const UnsignedByte expected03[16*8]{}; + const UnsignedByte expected1[]{ + 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 expected2[]{ + 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, + }; + CORRADE_COMPARE_AS(image0, + (ImageView2D{PixelFormat::R8Unorm, {16, 8}, expected03}), + DebugTools::CompareImage); + { + #ifdef MAGNUM_TARGET_GLES + CORRADE_EXPECT_FAIL_IF(GL::Context::current().detectedDriver() >= GL::Context::DetectedDriver::SwiftShader, + "SwiftShader is trash and doesn't implement reading from non-zero array layers."); + #endif + CORRADE_COMPARE_AS(image1, + (ImageView2D{PixelFormat::R8Unorm, {16, 8}, expected1}), + DebugTools::CompareImage); + CORRADE_COMPARE_AS(image2, + (ImageView2D{PixelFormat::R8Unorm, {16, 8}, expected2}), + DebugTools::CompareImage); + } + /* This is broken on SwiftShader too, returning the first layer (or all + zeros) but since we expect the same as first layer (which is all zeros), + it passes */ + CORRADE_COMPARE_AS(image3, + (ImageView2D{PixelFormat::R8Unorm, {16, 8}, expected03}), + DebugTools::CompareImage); +} +#endif + #ifdef MAGNUM_BUILD_DEPRECATED void DistanceFieldGlyphCacheGLTest::setDistanceFieldImageUnsupportedGLFormat() { CORRADE_SKIP_IF_NO_ASSERT(); diff --git a/src/Magnum/Text/Test/DistanceFieldGlyphCacheGL_Test.cpp b/src/Magnum/Text/Test/DistanceFieldGlyphCacheGL_Test.cpp index 8de325525..e04481eb5 100644 --- a/src/Magnum/Text/Test/DistanceFieldGlyphCacheGL_Test.cpp +++ b/src/Magnum/Text/Test/DistanceFieldGlyphCacheGL_Test.cpp @@ -34,10 +34,17 @@ struct DistanceFieldGlyphCacheGL_Test: TestSuite::Tester { explicit DistanceFieldGlyphCacheGL_Test(); void constructNoCreate(); + #ifndef MAGNUM_TARGET_GLES2 + void constructNoCreateArray(); + #endif }; DistanceFieldGlyphCacheGL_Test::DistanceFieldGlyphCacheGL_Test() { - addTests({&DistanceFieldGlyphCacheGL_Test::constructNoCreate}); + addTests({&DistanceFieldGlyphCacheGL_Test::constructNoCreate, + #ifndef MAGNUM_TARGET_GLES2 + &DistanceFieldGlyphCacheGL_Test::constructNoCreateArray + #endif + }); } void DistanceFieldGlyphCacheGL_Test::constructNoCreate() { @@ -50,6 +57,18 @@ void DistanceFieldGlyphCacheGL_Test::constructNoCreate() { CORRADE_VERIFY(!std::is_convertible::value); } +#ifndef MAGNUM_TARGET_GLES2 +void DistanceFieldGlyphCacheGL_Test::constructNoCreateArray() { + DistanceFieldGlyphCacheArrayGL 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::DistanceFieldGlyphCacheGL_Test) diff --git a/src/Magnum/Text/Text.h b/src/Magnum/Text/Text.h index 822d774ca..970522957 100644 --- a/src/Magnum/Text/Text.h +++ b/src/Magnum/Text/Text.h @@ -67,6 +67,7 @@ typedef CORRADE_DEPRECATED("use DistanceFieldGlyphCacheGL instead") DistanceFiel typedef CORRADE_DEPRECATED("use GlyphCacheGL instead") GlyphCacheGL GlyphCache; #endif #ifndef MAGNUM_TARGET_GLES2 +class DistanceFieldGlyphCacheArrayGL; class GlyphCacheArrayGL; #endif class RendererGL; diff --git a/src/Magnum/TextureTools/DistanceFieldGL.h b/src/Magnum/TextureTools/DistanceFieldGL.h index 18227b1de..cfbeba601 100644 --- a/src/Magnum/TextureTools/DistanceFieldGL.h +++ b/src/Magnum/TextureTools/DistanceFieldGL.h @@ -71,9 +71,9 @@ essentially for free. You can use the @ref magnum-distancefieldconverter "magnum-distancefieldconverter" utility to perform distance field conversion on a command line. Distance field textures can be rendered with @ref Shaders::DistanceFieldVectorGL, this -functionality is also used to implement @ref Text::DistanceFieldGlyphCacheGL -for text rendering, which is then exposed in the -@ref magnum-fontconverter "magnum-fontconverter" utility. +functionality is also used to implement @ref Text::DistanceFieldGlyphCacheGL / +@ref Text::DistanceFieldGlyphCacheArrayGL for text rendering, which is then +exposed in the @ref magnum-fontconverter "magnum-fontconverter" utility. Algorithm based on: *Chris Green - Improved Alpha-Tested Magnification for Vector Textures and Special Effects, SIGGRAPH 2007,