Browse Source

Text: new DistanceFieldGlyphCacheArrayGL class.

Counterpart to GlyphCacheArrayGL. At first I expected that it'd need
TextureTools::DistanceFieldGL to be expanded with texture array support
but after enough massaging of brain matter I realized that not really,
since the procesing has to be done slice by slice anyway, and having to
upload the whole array just to have a temporary input for processing
would be a waste of memory.
pull/674/head
Vladimír Vondruš 1 year ago
parent
commit
8399b908d5
  1. 4
      doc/changelog.dox
  2. 29
      doc/snippets/Text-gl.cpp
  3. 9
      src/Magnum/Text/AbstractGlyphCache.h
  4. 157
      src/Magnum/Text/DistanceFieldGlyphCacheGL.cpp
  5. 81
      src/Magnum/Text/DistanceFieldGlyphCacheGL.h
  6. 12
      src/Magnum/Text/GlyphCacheGL.cpp
  7. 15
      src/Magnum/Text/GlyphCacheGL.h
  8. 420
      src/Magnum/Text/Test/DistanceFieldGlyphCacheGLTest.cpp
  9. 21
      src/Magnum/Text/Test/DistanceFieldGlyphCacheGL_Test.cpp
  10. 1
      src/Magnum/Text/Text.h
  11. 6
      src/Magnum/TextureTools/DistanceFieldGL.h

4
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

29
doc/snippets/Text-gl.cpp

@ -152,6 +152,35 @@ shader
/* [DistanceFieldGlyphCacheGL-usage-draw] */
}
#ifndef MAGNUM_TARGET_GLES2
{
PluginManager::Manager<Text::AbstractFont> manager;
/* [DistanceFieldGlyphCacheArrayGL-usage] */
Containers::Pointer<Text::AbstractFont> 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}};

9
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

157
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<State>(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&>(*_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
}}

81
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

12
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>&& 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&>(*_state).texture.setSubImage(0, offset, image);
}
void GlyphCacheArrayGL::doSetProcessedImage(const Vector3i& offset, const ImageView3D& image) {
static_cast<State&>(*_state).texture.setSubImage(0, offset, image);
}
#ifndef MAGNUM_TARGET_GLES
Image3D GlyphCacheArrayGL::doProcessedImage() {
return static_cast<State&>(*_state).texture.image(0, processedFormat());
}
#endif
#endif
}}

15
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>&& 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

420
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<GL::Extensions::EXT::texture_array>())
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<GL::Extensions::EXT::texture_array>())
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<DistanceFieldGlyphCacheGL>{});
CORRADE_VERIFY(!std::is_copy_assignable<DistanceFieldGlyphCacheGL>{});
}
#ifndef MAGNUM_TARGET_GLES2
void DistanceFieldGlyphCacheGLTest::constructCopyArray() {
CORRADE_VERIFY(!std::is_copy_constructible<DistanceFieldGlyphCacheArrayGL>{});
CORRADE_VERIFY(!std::is_copy_assignable<DistanceFieldGlyphCacheArrayGL>{});
}
#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<DistanceFieldGlyphCacheGL>::value);
}
#ifndef MAGNUM_TARGET_GLES2
void DistanceFieldGlyphCacheGLTest::constructMoveArray() {
#ifndef MAGNUM_TARGET_GLES
if(!GL::Context::current().isExtensionSupported<GL::Extensions::EXT::texture_array>())
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<DistanceFieldGlyphCacheGL>::value);
CORRADE_VERIFY(std::is_nothrow_move_assignable<DistanceFieldGlyphCacheGL>::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<GL::Extensions::EXT::texture_array>())
CORRADE_SKIP(GL::Extensions::EXT::texture_array::string() << "is not supported.");
#endif
Containers::Pointer<Trade::AbstractImporter> 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<Trade::ImageData2D> 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<char> 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<const UnsignedByte> src = inputImage->pixels<UnsignedByte>();
/* Test also uploading under an offset */
Utility::copy(src, cache.image().pixels<UnsignedByte>().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<const char> 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<GL::Extensions::EXT::texture_array>())
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<UnsignedByte> src = cache.image().pixels<UnsignedByte>()[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<const char> dst3 = actual.pixels();
Containers::StridedArrayView2D<const UnsignedByte> 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<GL::Extensions::EXT::texture_array>())
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();

21
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<NoCreateT, DistanceFieldGlyphCacheGL>::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<NoCreateT, DistanceFieldGlyphCacheArrayGL>::value);
}
#endif
}}}}
CORRADE_TEST_MAIN(Magnum::Text::Test::DistanceFieldGlyphCacheGL_Test)

1
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;

6
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,

Loading…
Cancel
Save