From afe56a5d499ad3591b2731cee927640d4b9117f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Wed, 16 Apr 2025 14:22:24 +0200 Subject: [PATCH] Text: move [DistanceField]GlyphCacheGL internals to the state struct. Same approach as done e.g. in the Ui library -- taking advantage of the base class already allocating an internal state struct, deriving from it and putting the state there instead of either having it as a class member (at the cost of extra header dependency) or as a separate state struct (at the cost of extra allocation). The only downside is a virtual destructor in the state struct, but compared to the alternatives that's completely fine. --- src/Magnum/Text/AbstractGlyphCache.cpp | 56 ++---------- src/Magnum/Text/AbstractGlyphCache.h | 13 ++- src/Magnum/Text/CMakeLists.txt | 3 + src/Magnum/Text/DistanceFieldGlyphCacheGL.cpp | 30 ++++-- src/Magnum/Text/DistanceFieldGlyphCacheGL.h | 9 +- src/Magnum/Text/GlyphCacheGL.cpp | 49 ++++++---- src/Magnum/Text/GlyphCacheGL.h | 17 ++-- .../Implementation/abstractGlyphCacheState.h | 91 +++++++++++++++++++ .../Text/Implementation/glyphCacheGLState.h | 53 +++++++++++ .../Test/DistanceFieldGlyphCacheGLTest.cpp | 1 + src/Magnum/Text/Test/GlyphCacheGLTest.cpp | 4 + src/Magnum/Text/Test/RendererGLTest.cpp | 4 + .../MagnumFont/Test/MagnumFontGLTest.cpp | 1 + 13 files changed, 243 insertions(+), 88 deletions(-) create mode 100644 src/Magnum/Text/Implementation/abstractGlyphCacheState.h create mode 100644 src/Magnum/Text/Implementation/glyphCacheGLState.h diff --git a/src/Magnum/Text/AbstractGlyphCache.cpp b/src/Magnum/Text/AbstractGlyphCache.cpp index f67b3159f..e7762ffe7 100644 --- a/src/Magnum/Text/AbstractGlyphCache.cpp +++ b/src/Magnum/Text/AbstractGlyphCache.cpp @@ -26,18 +26,15 @@ #include "AbstractGlyphCache.h" -#include #include #include #include -#include #include +#include -#include "Magnum/Image.h" #include "Magnum/ImageView.h" -#include "Magnum/PixelFormat.h" #include "Magnum/Math/Range.h" -#include "Magnum/TextureTools/Atlas.h" +#include "Magnum/Text/Implementation/abstractGlyphCacheState.h" #ifdef MAGNUM_BUILD_DEPRECATED #include @@ -70,51 +67,6 @@ Debug& operator<<(Debug& debug, const GlyphCacheFeatures value) { }); } -struct AbstractGlyphCache::State { - explicit State(PixelFormat format, const Vector3i& size, PixelFormat processedFormat, const Vector2i& processedSize, const Vector2i& padding); - - Image3D image; - TextureTools::AtlasLandfill atlas; - - PixelFormat processedFormat; - Vector2i processedSize; - Vector2i padding; - - /* 0/4 bytes free */ - - /* First element is glyph position relative to a point on the baseline, - second layer in the texture atlas, third a region in the atlas - slice. Index of the item is ID of the glyph in the cache, refered to - from the fontGlyphMapping array. Index 0 is reserved for an invalid - glyph. */ - Containers::Array> glyphs; - /* `fontRanges[i]` to `fontRanges[i + 1]` is the range in - `fontGlyphMapping` containing a mapping for glyphs from font `i`, - `fontGlyphMapping[fontRanges[i]] + j` is then mapping from glyph ID `j` - from font `i` to index in the `glyphs` array, or is 0 if given - glyph isn't present in the cache (which then maps to the invalid - glyph). */ - struct Font { - UnsignedInt offset; - /* 4 bytes free on 64b, but not so critical I think */ - const AbstractFont* pointer; - }; - Containers::Array fonts; - /* With an assumption that majority of font glyphs get put into a cache, - this achieves O(1) mapping from a font ID + font-specific glyph ID pair - to a cache-global glyph ID with far less overhead than a hashmap would, - and much less memory used as well compared to storing a key, value and a - hash for each mapping entry. - - Another assumption is that there's no more than 64k glyphs in total, - which makes the mapping save half memory compared to storing 32-bit - ints. 64K glyphs is enough to fill a 4K texture with 16x16 glyphs, which - seems enough for now. It however might get reached at some point in - practice, in which case the type would simply get changed to a 32-bit - one (and the assertion in addGlyph() then removed). */ - Containers::Array fontGlyphMapping; -}; - AbstractGlyphCache::State::State(const PixelFormat format, const Vector3i& size, const PixelFormat processedFormat, const Vector2i& processedSize, const Vector2i& padding): image{format, size, Containers::Array{ValueInit, 4*((pixelFormatSize(format)*size.x() + 3)/4)*size.y()*size.z()}}, atlas{NoCreate}, processedFormat{processedFormat}, processedSize{processedSize}, padding{padding} { CORRADE_ASSERT(size.product(), "Text::AbstractGlyphCache: expected non-zero size, got" << Debug::packed << size, ); @@ -137,6 +89,8 @@ AbstractGlyphCache::State::State(const PixelFormat format, const Vector3i& size, arrayAppend(fonts, InPlaceInit, 0u, nullptr); } +AbstractGlyphCache::State::~State() = default; + AbstractGlyphCache::AbstractGlyphCache(const PixelFormat format, const Vector3i& size, const PixelFormat processedFormat, const Vector2i& processedSize, const Vector2i& padding): _state{InPlaceInit, format, size, processedFormat, processedSize, padding} {} AbstractGlyphCache::AbstractGlyphCache(const PixelFormat format, const Vector3i& size, const PixelFormat processedFormat, const Vector2i& processedSize): AbstractGlyphCache{format, size, processedFormat, processedSize, Vector2i{1}} {} @@ -159,6 +113,8 @@ AbstractGlyphCache::AbstractGlyphCache(const Vector2i& size, const Vector2i& pad AbstractGlyphCache::AbstractGlyphCache(const Vector2i& size): AbstractGlyphCache{PixelFormat::R8Unorm, size, Vector2i{1}} {} #endif +AbstractGlyphCache::AbstractGlyphCache(Containers::Pointer&& state) noexcept: _state{Utility::move(state)} {} + AbstractGlyphCache::AbstractGlyphCache(NoCreateT) noexcept {} AbstractGlyphCache::AbstractGlyphCache(AbstractGlyphCache&&) noexcept = default; diff --git a/src/Magnum/Text/AbstractGlyphCache.h b/src/Magnum/Text/AbstractGlyphCache.h index f3748644e..cc8cf3460 100644 --- a/src/Magnum/Text/AbstractGlyphCache.h +++ b/src/Magnum/Text/AbstractGlyphCache.h @@ -980,6 +980,17 @@ class MAGNUM_TEXT_EXPORT AbstractGlyphCache { CORRADE_DEPRECATED("use glyph() instead") std::pair operator[](UnsignedInt glyphId) const; #endif + #ifndef DOXYGEN_GENERATING_OUTPUT + protected: + #else + private: + #endif + struct State; + Containers::Pointer _state; + + /* Used by GlyphCacheGL, GlyphCacheArrayGL */ + explicit AbstractGlyphCache(Containers::Pointer&& state) noexcept; + private: /** @brief Implementation for @ref features() */ virtual GlyphCacheFeatures doFeatures() const = 0; @@ -1040,8 +1051,6 @@ class MAGNUM_TEXT_EXPORT AbstractGlyphCache { */ virtual void doSetProcessedImage(const Vector2i& offset, const ImageView2D& image); - struct State; - Containers::Pointer _state; }; }} diff --git a/src/Magnum/Text/CMakeLists.txt b/src/Magnum/Text/CMakeLists.txt index a45429636..27ba3ea59 100644 --- a/src/Magnum/Text/CMakeLists.txt +++ b/src/Magnum/Text/CMakeLists.txt @@ -61,6 +61,7 @@ set(MagnumText_HEADERS set(MagnumText_PRIVATE_HEADERS Implementation/printFourCC.h + Implementation/abstractGlyphCacheState.h Implementation/rendererState.h) if(MAGNUM_TARGET_GL) @@ -72,6 +73,8 @@ if(MAGNUM_TARGET_GL) DistanceFieldGlyphCacheGL.h GlyphCacheGL.h RendererGL.h) + list(APPEND MagnumText_PRIVATE_HEADERS + Implementation/glyphCacheGLState.h) if(MAGNUM_BUILD_DEPRECATED) list(APPEND MagnumText_HEADERS DistanceFieldGlyphCache.h diff --git a/src/Magnum/Text/DistanceFieldGlyphCacheGL.cpp b/src/Magnum/Text/DistanceFieldGlyphCacheGL.cpp index 034707181..e2d51cb2d 100644 --- a/src/Magnum/Text/DistanceFieldGlyphCacheGL.cpp +++ b/src/Magnum/Text/DistanceFieldGlyphCacheGL.cpp @@ -39,11 +39,19 @@ #endif #include "Magnum/GL/TextureFormat.h" #include "Magnum/Math/Range.h" +#include "Magnum/Text/Implementation/glyphCacheGLState.h" +#include "Magnum/TextureTools/DistanceFieldGL.h" namespace Magnum { namespace Text { -DistanceFieldGlyphCacheGL::DistanceFieldGlyphCacheGL(const Vector2i& size, const Vector2i& processedSize, const UnsignedInt radius): - GlyphCacheGL{PixelFormat::R8Unorm, size, +struct DistanceFieldGlyphCacheGL::State: GlyphCacheGL::State { + explicit State(const Vector2i& size, const Vector2i& processedSize, UnsignedInt radius); + + TextureTools::DistanceFieldGL distanceField; +}; + +DistanceFieldGlyphCacheGL::State::State(const Vector2i& size, const Vector2i& processedSize, const UnsignedInt radius): + GlyphCacheGL::State{PixelFormat::R8Unorm, size, #if !defined(MAGNUM_TARGET_GLES) || !defined(MAGNUM_TARGET_GLES2) PixelFormat::R8Unorm, #else @@ -58,7 +66,7 @@ DistanceFieldGlyphCacheGL::DistanceFieldGlyphCacheGL(const Vector2i& size, const PixelFormat::RGBA8Unorm, #endif processedSize, Vector2i(radius)}, - _distanceField{radius} + distanceField{radius} { /* Replicating the assertion from TextureTools::DistanceFieldGL so it gets checked during construction already instead of only later during the @@ -76,7 +84,15 @@ DistanceFieldGlyphCacheGL::DistanceFieldGlyphCacheGL(const Vector2i& size, const #endif } -DistanceFieldGlyphCacheGL::DistanceFieldGlyphCacheGL(NoCreateT) noexcept: GlyphCacheGL{NoCreate}, _distanceField{NoCreate} {} +DistanceFieldGlyphCacheGL::DistanceFieldGlyphCacheGL(const Vector2i& size, const Vector2i& processedSize, UnsignedInt radius): GlyphCacheGL{Containers::pointer(size, processedSize, radius)} {} + +DistanceFieldGlyphCacheGL::DistanceFieldGlyphCacheGL(NoCreateT) noexcept: GlyphCacheGL{NoCreate} {} + +#ifdef MAGNUM_BUILD_DEPRECATED +Vector2i DistanceFieldGlyphCacheGL::distanceFieldTextureSize() const { + return processedSize().xy(); +} +#endif GlyphCacheFeatures DistanceFieldGlyphCacheGL::doFeatures() const { return GlyphCacheFeature::ImageProcessing @@ -87,6 +103,8 @@ GlyphCacheFeatures DistanceFieldGlyphCacheGL::doFeatures() const { } void DistanceFieldGlyphCacheGL::doSetImage(const Vector2i& offset, const ImageView2D& image) { + auto& state = static_cast(*_state); + GL::Texture2D input; input.setWrapping(GL::SamplerWrapping::ClampToEdge) /* Use nearest filter to avoid minor rounding errors on ES2 compared to @@ -110,7 +128,7 @@ void DistanceFieldGlyphCacheGL::doSetImage(const Vector2i& offset, const ImageVi #endif { input.setImage(0, GL::textureFormat(image.format()), ImageView2D{image.format(), size().xy(), image.data()}); - _distanceField(input, texture(), {{}, size().xy()/ratio}, size().xy()); + state.distanceField(input, texture(), {{}, size().xy()/ratio}, size().xy()); #ifdef MAGNUM_TARGET_WEBGL static_cast(offset); #endif @@ -150,7 +168,7 @@ void DistanceFieldGlyphCacheGL::doSetImage(const Vector2i& offset, const ImageVi image.data()}; input.setImage(0, GL::textureFormat(paddedImage.format()), paddedImage); - _distanceField(input, texture(), Range2Di::fromSize(paddedMinRounded/ratio, paddedImage.size()/ratio), paddedImage.size()); + state.distanceField(input, texture(), Range2Di::fromSize(paddedMinRounded/ratio, paddedImage.size()/ratio), paddedImage.size()); } #endif } diff --git a/src/Magnum/Text/DistanceFieldGlyphCacheGL.h b/src/Magnum/Text/DistanceFieldGlyphCacheGL.h index 2efd0401d..ff89f598f 100644 --- a/src/Magnum/Text/DistanceFieldGlyphCacheGL.h +++ b/src/Magnum/Text/DistanceFieldGlyphCacheGL.h @@ -35,7 +35,6 @@ #ifdef MAGNUM_TARGET_GL #include "Magnum/Text/GlyphCacheGL.h" -#include "Magnum/TextureTools/DistanceFieldGL.h" namespace Magnum { namespace Text { @@ -154,9 +153,7 @@ class MAGNUM_TEXT_EXPORT DistanceFieldGlyphCacheGL: public GlyphCacheGL { * texture. * @m_deprecated_since_latest Use @ref processedSize() instead. */ - CORRADE_DEPRECATED("use processedSize() instead") Vector2i distanceFieldTextureSize() const { - return processedSize().xy(); - } + CORRADE_DEPRECATED("use processedSize() instead") Vector2i distanceFieldTextureSize() const; /** * @brief Set a distance field cache image @@ -171,10 +168,10 @@ class MAGNUM_TEXT_EXPORT DistanceFieldGlyphCacheGL: public GlyphCacheGL { #endif private: + struct State; + MAGNUM_TEXT_LOCAL GlyphCacheFeatures doFeatures() const override; MAGNUM_TEXT_LOCAL void doSetImage(const Vector2i& offset, const ImageView2D& image) override; - - TextureTools::DistanceFieldGL _distanceField; }; }} diff --git a/src/Magnum/Text/GlyphCacheGL.cpp b/src/Magnum/Text/GlyphCacheGL.cpp index e4d96c699..2bda36218 100644 --- a/src/Magnum/Text/GlyphCacheGL.cpp +++ b/src/Magnum/Text/GlyphCacheGL.cpp @@ -44,17 +44,18 @@ #if defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) #include "Magnum/GL/PixelFormat.h" #endif +#include "Magnum/Text/Implementation/glyphCacheGLState.h" namespace Magnum { namespace Text { -GlyphCacheGL::GlyphCacheGL(const PixelFormat format, const Vector2i& size, const PixelFormat processedFormat, const Vector2i& processedSize, const Vector2i& padding): AbstractGlyphCache{format, size, processedFormat, processedSize, padding} { +GlyphCacheGL::State::State(const PixelFormat format, const Vector2i& size, const PixelFormat processedFormat, const Vector2i& processedSize, const Vector2i& padding): AbstractGlyphCache::State{format, {size, 1}, 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) + texture.setWrapping(GL::SamplerWrapping::ClampToEdge) .setMinificationFilter(GL::SamplerFilter::Linear) .setMagnificationFilter(GL::SamplerFilter::Linear); @@ -81,14 +82,16 @@ GlyphCacheGL::GlyphCacheGL(const PixelFormat format, const Vector2i& size, const textureFormat == GL::TextureFormat::SRGB || textureFormat == GL::TextureFormat::RGBA || textureFormat == GL::TextureFormat::SRGBAlpha) - _texture.setImage(0, textureFormat, ImageView2D{pixelFormat, GL::PixelType::UnsignedByte, processedSize}); + texture.setImage(0, textureFormat, ImageView2D{pixelFormat, GL::PixelType::UnsignedByte, processedSize}); else - _texture.setStorage(1, textureFormat, processedSize); + texture.setStorage(1, textureFormat, processedSize); #else - _texture.setStorage(1, GL::textureFormat(processedFormat), processedSize); + texture.setStorage(1, GL::textureFormat(processedFormat), processedSize); #endif } +GlyphCacheGL::GlyphCacheGL(PixelFormat format, const Vector2i& size, PixelFormat processedFormat, const Vector2i& processedSize, const Vector2i& padding): AbstractGlyphCache{Containers::pointer(format, size, processedFormat, processedSize, padding)} {} + GlyphCacheGL::GlyphCacheGL(const PixelFormat format, const Vector2i& size, const Vector2i& padding): GlyphCacheGL{format, size, format, size, padding} {} #ifdef MAGNUM_BUILD_DEPRECATED @@ -104,11 +107,19 @@ GlyphCacheGL::GlyphCacheGL(const Vector2i& size, const Vector2i& padding): Glyph GlyphCacheGL::GlyphCacheGL(const Vector2i& size, const Vector2i& processedSize, const Vector2i& padding): GlyphCacheGL{PixelFormat::R8Unorm, size, PixelFormat::R8Unorm, processedSize, padding} {} #endif -GlyphCacheGL::GlyphCacheGL(NoCreateT) noexcept: AbstractGlyphCache{NoCreate}, _texture{NoCreate} {} +GlyphCacheGL::GlyphCacheGL(Containers::Pointer&& state) noexcept: AbstractGlyphCache{Utility::move(state)} {} + +GlyphCacheGL::GlyphCacheGL(NoCreateT) noexcept: AbstractGlyphCache{NoCreate} {} + +GL::Texture2D& GlyphCacheGL::texture() { + return static_cast(*_state).texture; +} GlyphCacheFeatures GlyphCacheGL::doFeatures() const { return {}; } void GlyphCacheGL::doSetImage(const Vector2i& offset, const ImageView2D& image) { + auto& state = static_cast(*_state); + CORRADE_ASSERT(format() == processedFormat() && size() == processedSize(), "Text::GlyphCacheGL::flushImage(): subclass expected to provide a doSetImage() implementation to handle different processed format or size", ); @@ -126,11 +137,11 @@ void GlyphCacheGL::doSetImage(const Vector2i& offset, const ImageView2D& image) formatExtra() and everything else from the view afterwards. */ #ifndef MAGNUM_TARGET_WEBGL if(image.format() == PixelFormat::R8Unorm && GL::Context::current().isExtensionSupported()) { - _texture.setSubImage(0, {}, ImageView2D{GL::PixelFormat::Red, GL::PixelType::UnsignedByte, size().xy(), image.data()}); + state.texture.setSubImage(0, {}, ImageView2D{GL::PixelFormat::Red, GL::PixelType::UnsignedByte, size().xy(), image.data()}); } else #endif { - _texture.setSubImage(0, {}, ImageView2D{image.format(), size().xy(), image.data()}); + state.texture.setSubImage(0, {}, ImageView2D{image.format(), size().xy(), image.data()}); } #ifdef MAGNUM_TARGET_WEBGL static_cast(offset); @@ -146,11 +157,11 @@ void GlyphCacheGL::doSetImage(const Vector2i& offset, const ImageView2D& image) format is Red instead of Luminance */ #ifdef MAGNUM_TARGET_GLES2 if(image.format() == PixelFormat::R8Unorm && GL::Context::current().isExtensionSupported()) { - _texture.setSubImage(0, offset, ImageView2D{image.storage(), GL::PixelFormat::Red, GL::PixelType::UnsignedByte, image.size(), image.data()}); + state.texture.setSubImage(0, offset, ImageView2D{image.storage(), GL::PixelFormat::Red, GL::PixelType::UnsignedByte, image.size(), image.data()}); } else #endif { - _texture.setSubImage(0, offset, image); + state.texture.setSubImage(0, offset, image); } } #endif @@ -172,33 +183,39 @@ void GlyphCacheGL::doSetProcessedImage(const Vector2i& offset, const ImageView2D } #endif - texture().setSubImage(0, offset, imageToUse); + static_cast(*_state).texture.setSubImage(0, offset, imageToUse); } #ifndef MAGNUM_TARGET_GLES Image3D GlyphCacheGL::doProcessedImage() { - Image2D out = _texture.image(0, PixelFormat::R8Unorm); + Image2D out = static_cast(*_state).texture.image(0, PixelFormat::R8Unorm); return Image3D{out.format(), {out.size(), 1}, out.release()}; } #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} { +GlyphCacheArrayGL::State::State(const PixelFormat format, const Vector3i& size, const PixelFormat processedFormat, const Vector2i& processedSize, const Vector2i& padding): AbstractGlyphCache::State{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) + texture.setWrapping(GL::SamplerWrapping::ClampToEdge) .setMinificationFilter(GL::SamplerFilter::Linear) .setMagnificationFilter(GL::SamplerFilter::Linear) .setStorage(1, GL::textureFormat(processedFormat), {processedSize, size.z()}); } +GlyphCacheArrayGL::GlyphCacheArrayGL(PixelFormat format, const Vector3i& size, PixelFormat processedFormat, const Vector2i& processedSize, const Vector2i& padding): AbstractGlyphCache{Containers::pointer(format, size, processedFormat, processedSize, padding)} {} + 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} {} +GlyphCacheArrayGL::GlyphCacheArrayGL(NoCreateT) noexcept: AbstractGlyphCache{NoCreate} {} + +GL::Texture2DArray& GlyphCacheArrayGL::texture() { + return static_cast(*_state).texture; +} GlyphCacheFeatures GlyphCacheArrayGL::doFeatures() const { return {}; } @@ -206,7 +223,7 @@ void GlyphCacheArrayGL::doSetImage(const Vector3i& offset, const ImageView3D& im 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); + static_cast(*_state).texture.setSubImage(0, offset, image); } #endif diff --git a/src/Magnum/Text/GlyphCacheGL.h b/src/Magnum/Text/GlyphCacheGL.h index 3bea290ed..6e5754212 100644 --- a/src/Magnum/Text/GlyphCacheGL.h +++ b/src/Magnum/Text/GlyphCacheGL.h @@ -34,10 +34,8 @@ #include "Magnum/configure.h" #ifdef MAGNUM_TARGET_GL -#include "Magnum/GL/Texture.h" -#ifndef MAGNUM_TARGET_GLES2 -#include "Magnum/GL/TextureArray.h" -#endif +#include "Magnum/Math/Vector2.h" +#include "Magnum/GL/GL.h" #include "Magnum/Text/AbstractGlyphCache.h" namespace Magnum { namespace Text { @@ -154,7 +152,7 @@ class MAGNUM_TEXT_EXPORT GlyphCacheGL: public AbstractGlyphCache { explicit GlyphCacheGL(NoCreateT) noexcept; /** @brief Cache texture */ - GL::Texture2D& texture() { return _texture; } + GL::Texture2D& texture(); protected: /** @@ -188,7 +186,10 @@ class MAGNUM_TEXT_EXPORT GlyphCacheGL: public AbstractGlyphCache { #else protected: #endif - GL::Texture2D _texture; + struct State; + + /* Used by DistanceFieldGlyphCacheGL */ + explicit GlyphCacheGL(Containers::Pointer&& state) noexcept; private: MAGNUM_TEXT_LOCAL GlyphCacheFeatures doFeatures() const override; @@ -286,14 +287,14 @@ class MAGNUM_TEXT_EXPORT GlyphCacheArrayGL: public AbstractGlyphCache { explicit GlyphCacheArrayGL(NoCreateT) noexcept; /** @brief Cache texture */ - GL::Texture2DArray& texture() { return _texture; } + GL::Texture2DArray& texture(); #ifdef DOXYGEN_GENERATING_OUTPUT private: #else protected: #endif - GL::Texture2DArray _texture; + struct State; private: MAGNUM_TEXT_LOCAL GlyphCacheFeatures doFeatures() const override; diff --git a/src/Magnum/Text/Implementation/abstractGlyphCacheState.h b/src/Magnum/Text/Implementation/abstractGlyphCacheState.h new file mode 100644 index 000000000..aea19bd2f --- /dev/null +++ b/src/Magnum/Text/Implementation/abstractGlyphCacheState.h @@ -0,0 +1,91 @@ +#ifndef Magnum_Text_Implementation_abstractGlyphCacheState_h +#define Magnum_Text_Implementation_abstractGlyphCacheState_h +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020, 2021, 2022, 2023, 2024, 2025 + Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include + +#include "Magnum/Image.h" +#include "Magnum/PixelFormat.h" +#include "Magnum/Math/Vector2.h" +#include "Magnum/Text/AbstractGlyphCache.h" +#include "Magnum/TextureTools/Atlas.h" + +namespace Magnum { namespace Text { + +struct AbstractGlyphCache::State { + explicit State(PixelFormat format, const Vector3i& size, PixelFormat processedFormat, const Vector2i& processedSize, const Vector2i& padding); + + /* GlyphCacheGL, GlyphCacheArrayGL, DistanceFieldGlyphCacheGL derive from + this to avoid another state struct allocation or putting the extra state + as members, inflating header size */ + virtual ~State(); + + Image3D image; + TextureTools::AtlasLandfill atlas; + + PixelFormat processedFormat; + Vector2i processedSize; + Vector2i padding; + + /* 0/4 bytes free */ + + /* First element is glyph position relative to a point on the baseline, + second layer in the texture atlas, third a region in the atlas + slice. Index of the item is ID of the glyph in the cache, refered to + from the fontGlyphMapping array. Index 0 is reserved for an invalid + glyph. */ + Containers::Array> glyphs; + /* `fontRanges[i]` to `fontRanges[i + 1]` is the range in + `fontGlyphMapping` containing a mapping for glyphs from font `i`, + `fontGlyphMapping[fontRanges[i]] + j` is then mapping from glyph ID `j` + from font `i` to index in the `glyphs` array, or is 0 if given + glyph isn't present in the cache (which then maps to the invalid + glyph). */ + struct Font { + UnsignedInt offset; + /* 4 bytes free on 64b, but not so critical I think */ + const AbstractFont* pointer; + }; + Containers::Array fonts; + /* With an assumption that majority of font glyphs get put into a cache, + this achieves O(1) mapping from a font ID + font-specific glyph ID pair + to a cache-global glyph ID with far less overhead than a hashmap would, + and much less memory used as well compared to storing a key, value and a + hash for each mapping entry. + + Another assumption is that there's no more than 64k glyphs in total, + which makes the mapping save half memory compared to storing 32-bit + ints. 64K glyphs is enough to fill a 4K texture with 16x16 glyphs, which + seems enough for now. It however might get reached at some point in + practice, in which case the type would simply get changed to a 32-bit + one (and the assertion in addGlyph() then removed). */ + Containers::Array fontGlyphMapping; +}; + +}} + +#endif diff --git a/src/Magnum/Text/Implementation/glyphCacheGLState.h b/src/Magnum/Text/Implementation/glyphCacheGLState.h new file mode 100644 index 000000000..186e98018 --- /dev/null +++ b/src/Magnum/Text/Implementation/glyphCacheGLState.h @@ -0,0 +1,53 @@ +#ifndef Magnum_Text_Implementation_glyphCacheGLState_h +#define Magnum_Text_Implementation_glyphCacheGLState_h +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020, 2021, 2022, 2023, 2024, 2025 + Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include "Magnum/GL/Texture.h" +#ifndef MAGNUM_TARGET_GLES2 +#include "Magnum/GL/TextureArray.h" +#endif +#include "Magnum/Text/Implementation/abstractGlyphCacheState.h" + +namespace Magnum { namespace Text { + +struct GlyphCacheGL::State: AbstractGlyphCache::State { + explicit State(PixelFormat format, const Vector2i& size, PixelFormat processedFormat, const Vector2i& processedSize, const Vector2i& padding); + + GL::Texture2D texture; +}; + +#ifndef MAGNUM_TARGET_GLES2 +struct GlyphCacheArrayGL::State: AbstractGlyphCache::State { + explicit State(PixelFormat format, const Vector3i& size, PixelFormat processedFormat, const Vector2i& processedSize, const Vector2i& padding); + + GL::Texture2DArray texture; +}; +#endif + +}} + +#endif diff --git a/src/Magnum/Text/Test/DistanceFieldGlyphCacheGLTest.cpp b/src/Magnum/Text/Test/DistanceFieldGlyphCacheGLTest.cpp index f5c3224c6..84617e0a5 100644 --- a/src/Magnum/Text/Test/DistanceFieldGlyphCacheGLTest.cpp +++ b/src/Magnum/Text/Test/DistanceFieldGlyphCacheGLTest.cpp @@ -43,6 +43,7 @@ #include "Magnum/GL/OpenGLTester.h" #include "Magnum/GL/Extensions.h" #include "Magnum/GL/PixelFormat.h" +#include "Magnum/GL/Texture.h" #include "Magnum/Text/DistanceFieldGlyphCacheGL.h" #include "Magnum/Trade/AbstractImporter.h" #include "Magnum/Trade/ImageData.h" diff --git a/src/Magnum/Text/Test/GlyphCacheGLTest.cpp b/src/Magnum/Text/Test/GlyphCacheGLTest.cpp index aba1cf10d..5f5bb79ba 100644 --- a/src/Magnum/Text/Test/GlyphCacheGLTest.cpp +++ b/src/Magnum/Text/Test/GlyphCacheGLTest.cpp @@ -42,6 +42,10 @@ #include "Magnum/GL/PixelFormat.h" #endif #include "Magnum/GL/OpenGLTester.h" +#include "Magnum/GL/Texture.h" +#ifndef MAGNUM_TARGET_GLES2 +#include "Magnum/GL/TextureArray.h" +#endif #include "Magnum/GL/TextureFormat.h" #include "Magnum/Math/Color.h" #include "Magnum/Math/Range.h" diff --git a/src/Magnum/Text/Test/RendererGLTest.cpp b/src/Magnum/Text/Test/RendererGLTest.cpp index 44fc3808a..752476c4f 100644 --- a/src/Magnum/Text/Test/RendererGLTest.cpp +++ b/src/Magnum/Text/Test/RendererGLTest.cpp @@ -45,6 +45,10 @@ #include "Magnum/GL/OpenGLTester.h" #include "Magnum/GL/Renderbuffer.h" #include "Magnum/GL/RenderbufferFormat.h" +#include "Magnum/GL/Texture.h" +#ifndef MAGNUM_TARGET_GLES2 +#include "Magnum/GL/TextureArray.h" +#endif #include "Magnum/Shaders/VectorGL.h" #include "Magnum/Text/AbstractFont.h" #include "Magnum/Text/AbstractShaper.h" diff --git a/src/MagnumPlugins/MagnumFont/Test/MagnumFontGLTest.cpp b/src/MagnumPlugins/MagnumFont/Test/MagnumFontGLTest.cpp index e90dab041..93ec46492 100644 --- a/src/MagnumPlugins/MagnumFont/Test/MagnumFontGLTest.cpp +++ b/src/MagnumPlugins/MagnumFont/Test/MagnumFontGLTest.cpp @@ -38,6 +38,7 @@ #endif #include "Magnum/Math/Range.h" #include "Magnum/GL/OpenGLTester.h" +#include "Magnum/GL/Texture.h" #include "Magnum/Text/AbstractFont.h" #include "Magnum/Text/GlyphCacheGL.h" #include "Magnum/Trade/AbstractImporter.h"