From 15e101dee7d0ccc5e3991049ed21d3d4abc92a0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Wed, 11 Oct 2023 23:59:36 +0200 Subject: [PATCH] Text: port Renderer away from deprecated APIs. The Renderer now does what was before redundantly copypasted into basically each and every font plugin. --- src/Magnum/Text/CMakeLists.txt | 6 +- src/Magnum/Text/Renderer.cpp | 92 +++++++++++++++++++------ src/Magnum/Text/Renderer.h | 12 ++-- src/Magnum/Text/Test/CMakeLists.txt | 5 +- src/Magnum/Text/Test/RendererGLTest.cpp | 44 +++++++++++- 5 files changed, 128 insertions(+), 31 deletions(-) diff --git a/src/Magnum/Text/CMakeLists.txt b/src/Magnum/Text/CMakeLists.txt index 154d17d55..70383d784 100644 --- a/src/Magnum/Text/CMakeLists.txt +++ b/src/Magnum/Text/CMakeLists.txt @@ -58,10 +58,10 @@ set(MagnumText_PRIVATE_HEADERS if(MAGNUM_TARGET_GL) list(APPEND MagnumText_SRCS - GlyphCache.cpp - Renderer.cpp) + GlyphCache.cpp) list(APPEND MagnumText_GracefulAssert_SRCS - DistanceFieldGlyphCache.cpp) + DistanceFieldGlyphCache.cpp + Renderer.cpp) list(APPEND MagnumText_HEADERS DistanceFieldGlyphCache.h GlyphCache.h diff --git a/src/Magnum/Text/Renderer.cpp b/src/Magnum/Text/Renderer.cpp index 933016b69..fc77c6d04 100644 --- a/src/Magnum/Text/Renderer.cpp +++ b/src/Magnum/Text/Renderer.cpp @@ -27,7 +27,9 @@ #include #include /** @todo remove once Renderer is STL-free */ +#include #include /** @todo remove once Renderer is STL-free */ +#include #include "Magnum/Mesh.h" #include "Magnum/GL/Context.h" @@ -37,6 +39,7 @@ #include "Magnum/Shaders/GenericGL.h" #include "Magnum/Text/AbstractFont.h" #include "Magnum/Text/AbstractGlyphCache.h" +#include "Magnum/Text/AbstractShaper.h" namespace Magnum { namespace Text { @@ -67,17 +70,31 @@ struct Vertex { }; std::tuple, Range2D> renderVerticesInternal(AbstractFont& font, const AbstractGlyphCache& cache, const Float size, const std::string& text, const Alignment alignment) { + /* This was originally added as a runtime error into plugin implementations + during the transition period for the new AbstractGlyphCache API, now + it's an assert in the transition period for the Renderer API. Shouldn't + get triggered by existing code that uses 2D caches. */ + CORRADE_ASSERT(cache.size().z() == 1, + "Text::Renderer: array glyph caches are not supported", {}); + + /* Find this font in the cache. This is an assert again, as not having a + font in the cache is a user error. */ + Containers::Optional fontId = cache.findFont(&font); + CORRADE_ASSERT(fontId, + "Text::Renderer: font not found among" << cache.fontCount() << "fonts in passed glyph cache", {}); + /* Output data, reserve memory as when the text would be ASCII-only. In reality the actual vertex count will be smaller, but allocating more at once is better than reallocating many times later. */ std::vector vertices; vertices.reserve(text.size()*4); - /* Total rendered bounds, initial line position, line increment, last+1 - vertex on previous line */ + /* Scaling factor, line advance, total rendered bounds, initial line + position, last+1 vertex on previous line */ + const Float scale = size/font.size(); + const Vector2 lineAdvance = Vector2::yAxis(font.lineHeight()*scale); Range2D rectangle; Vector2 linePosition; - const Vector2 lineAdvance = Vector2::yAxis(font.lineHeight()*size/font.size()); std::size_t lastLineLastVertex = 0; /* Temp buffer so we don't allocate for each new line */ @@ -87,6 +104,16 @@ std::tuple, Range2D> renderVerticesInternal(AbstractFont& fo */ std::string line; line.reserve(text.size()); + struct Glyph { + UnsignedInt id; + Vector2 offset; + Vector2 advance; + }; + Containers::Array glyphs{NoInit, text.size()}; + + /* Create a shaper */ + /** @todo even with reusing a shaper this is all horrific, rework!! */ + Containers::Pointer shaper = font.createShaper(); /* Render each line separately and align it horizontally */ std::size_t pos, prevPos = 0; @@ -97,39 +124,62 @@ std::tuple, Range2D> renderVerticesInternal(AbstractFont& fo /* Copy the line into the temp buffer */ line.assign(text, prevPos, pos-prevPos); - /* Layout the line */ - Containers::Pointer layouter = font.layout(cache, size, line); + /* Shape the line, get the results */ + shaper->shape(line); + const Containers::StridedArrayView1D lineGlyphs = glyphs.prefix(shaper->glyphCount()); + shaper->glyphsInto(lineGlyphs.slice(&Glyph::id), + lineGlyphs.slice(&Glyph::offset), + lineGlyphs.slice(&Glyph::advance)); /* Verify that we don't reallocate anything. The only problem might arise when the layouter decides to compose one character from more - than one glyph (i.e. accents). Will remove the assert when this + than one glyph (i.e. accents). Will remove the asserts when this issue arises. */ - CORRADE_INTERNAL_ASSERT(vertices.size() + layouter->glyphCount()*4 <= vertices.capacity()); + CORRADE_INTERNAL_ASSERT(vertices.size() + shaper->glyphCount()*4 <= vertices.capacity()); /* Bounds of rendered line */ Range2D lineRectangle; - /* Render all glyphs */ + /* Create quads for all glyphs */ Vector2 cursorPosition(linePosition); - for(UnsignedInt i = 0; i != layouter->glyphCount(); ++i) { - const Containers::Pair quadPositionTextureCoordinates = layouter->renderGlyph(i, cursorPosition, lineRectangle); + for(UnsignedInt i = 0; i != lineGlyphs.size(); ++i) { + /* Offset of the glyph rectangle relative to the cursor, layer, + texture coordinates. We checked that the glyph cache is 2D above + so the layer can be ignored. */ + const Containers::Triple cacheGlyph = cache.glyph(*fontId, glyphs[i].id); + CORRADE_INTERNAL_ASSERT(cacheGlyph.second() == 0); + + /* Quad rectangle, created from cache and shaper offset and the + texture rectangle, scaled to requested text size and translated + to current cursor */ + const Range2D quadPosition = Range2D::fromSize( + Vector2{cacheGlyph.first()} + glyphs[i].offset, + Vector2{cacheGlyph.third().size()}) + .scaled(Vector2{scale}) + .translated(cursorPosition); + + /* Normalized texture coordinates */ + const Range2D quadTextureCoordinates = Range2D{cacheGlyph.third()} + .scaled(1.0f/Vector2{cache.size().xy()}); /* 0---2 | | | | | | 1---3 */ - vertices.insert(vertices.end(), { - {quadPositionTextureCoordinates.first().topLeft(), - quadPositionTextureCoordinates.second().topLeft()}, - {quadPositionTextureCoordinates.first().bottomLeft(), - quadPositionTextureCoordinates.second().bottomLeft()}, - {quadPositionTextureCoordinates.first().topRight(), - quadPositionTextureCoordinates.second().topRight()}, - {quadPositionTextureCoordinates.first().bottomRight(), - quadPositionTextureCoordinates.second().bottomRight()} + {quadPosition.topLeft(), quadTextureCoordinates.topLeft()}, + {quadPosition.bottomLeft(), quadTextureCoordinates.bottomLeft()}, + {quadPosition.topRight(), quadTextureCoordinates.topRight()}, + {quadPosition.bottomRight(), quadTextureCoordinates.bottomRight()} }); + + /* Extend the line rectangle with current quad bounds. If the + original is zero size, it gets replaced. */ + lineRectangle = Math::join(lineRectangle, quadPosition); + + /* Advance cursor position to next character, again scaled */ + cursorPosition += glyphs[i].advance*scale; } /** @todo What about top-down text? */ @@ -150,8 +200,8 @@ std::tuple, Range2D> renderVerticesInternal(AbstractFont& fo for(auto it = vertices.begin()+lastLineLastVertex; it != vertices.end(); ++it) it->position.x() += alignmentOffsetX; - /* Extend the rectangle with final line bounds, similarly to - AbstractFont::renderGlyph() */ + /* Extend the rectangle with final line bounds, similarly to what was + done for each glyph above */ rectangle = Math::join(rectangle, lineRectangle); /* Move to next line */ diff --git a/src/Magnum/Text/Renderer.h b/src/Magnum/Text/Renderer.h index ee51726c6..4447d908e 100644 --- a/src/Magnum/Text/Renderer.h +++ b/src/Magnum/Text/Renderer.h @@ -67,8 +67,9 @@ class MAGNUM_TEXT_EXPORT AbstractRenderer { * @param text Text to render * @param alignment Text alignment * - * Returns tuple with vertex positions, texture coordinates, indices - * and rectangle spanning the rendered text. + * Returns a tuple with vertex positions, texture coordinates, indices + * and rectangle spanning the rendered text. Expects that @p font is + * present in @p cache and that @p cache isn't an array. */ static std::tuple, std::vector, std::vector, Range2D> render(AbstractFont& font, const AbstractGlyphCache& cache, Float size, const std::string& text, Alignment alignment = Alignment::LineLeft); @@ -291,9 +292,10 @@ template class MAGNUM_TEXT_EXPORT Renderer: public Abstr * @param usage Usage of vertex and index buffer * @param alignment Text alignment * - * Returns mesh prepared for use with @ref Shaders::VectorGL or - * @ref Shaders::DistanceFieldVectorGL and rectangle spanning the - * rendered text. + * Returns a mesh prepared for use with @ref Shaders::VectorGL or + * @ref Shaders::DistanceFieldVectorGL and a rectangle spanning the + * rendered text. Expects that @p font is present in @p cache and that + * @p cache isn't an array. */ static std::tuple render(AbstractFont& font, const AbstractGlyphCache& cache, Float size, const std::string& text, GL::Buffer& vertexBuffer, GL::Buffer& indexBuffer, GL::BufferUsage usage, Alignment alignment = Alignment::LineLeft); diff --git a/src/Magnum/Text/Test/CMakeLists.txt b/src/Magnum/Text/Test/CMakeLists.txt index 9427240d0..ee3b733df 100644 --- a/src/Magnum/Text/Test/CMakeLists.txt +++ b/src/Magnum/Text/Test/CMakeLists.txt @@ -113,5 +113,8 @@ if(MAGNUM_TARGET_GL AND MAGNUM_BUILD_GL_TESTS) MagnumText MagnumOpenGLTester MagnumDebugTools) - corrade_add_test(TextRendererGLTest RendererGLTest.cpp LIBRARIES MagnumText MagnumOpenGLTester) + corrade_add_test(TextRendererGLTest RendererGLTest.cpp + LIBRARIES + MagnumTextTestLib + MagnumOpenGLTester) endif() diff --git a/src/Magnum/Text/Test/RendererGLTest.cpp b/src/Magnum/Text/Test/RendererGLTest.cpp index 404a656eb..54a236e0e 100644 --- a/src/Magnum/Text/Test/RendererGLTest.cpp +++ b/src/Magnum/Text/Test/RendererGLTest.cpp @@ -23,12 +23,15 @@ DEALINGS IN THE SOFTWARE. */ +#include #include #include #include #include #include +#include /** @todo drop once Debug is stream-free */ +#include "Magnum/PixelFormat.h" #include "Magnum/GL/Context.h" #include "Magnum/GL/Extensions.h" #include "Magnum/GL/OpenGLTester.h" @@ -48,6 +51,9 @@ struct RendererGLTest: GL::OpenGLTester { void mutableText(); void multiline(); + + void arrayGlyphCache(); + void fontNotFoundInCache(); }; const struct { @@ -85,7 +91,10 @@ RendererGLTest::RendererGLTest() { &RendererGLTest::renderMeshIndexType, &RendererGLTest::mutableText, - &RendererGLTest::multiline}); + &RendererGLTest::multiline, + + &RendererGLTest::arrayGlyphCache, + &RendererGLTest::fontNotFoundInCache}); } struct TestShaper: AbstractShaper { @@ -543,6 +552,39 @@ void RendererGLTest::multiline() { }), TestSuite::Compare::Container); } +void RendererGLTest::arrayGlyphCache() { + CORRADE_SKIP_IF_NO_ASSERT(); + + TestFont font; + font.openFile({}, 0.5f); + struct: AbstractGlyphCache { + using AbstractGlyphCache::AbstractGlyphCache; + + GlyphCacheFeatures doFeatures() const override { return {}; } + } cache{PixelFormat::R8Unorm, {100, 100, 3}}; + + std::ostringstream out; + Error redirectError{&out}; + AbstractRenderer::render(font, cache, 0.25f, "abc"); + CORRADE_COMPARE(out.str(), "Text::Renderer: array glyph caches are not supported\n"); +} + +void RendererGLTest::fontNotFoundInCache() { + CORRADE_SKIP_IF_NO_ASSERT(); + + TestFont font; + font.openFile({}, 0.5f); + GlyphCache cache{{100, 100}}; + + cache.addFont(34); + cache.addFont(25); + + std::ostringstream out; + Error redirectError{&out}; + AbstractRenderer::render(font, cache, 0.25f, "abc"); + CORRADE_COMPARE(out.str(), "Text::Renderer: font not found among 2 fonts in passed glyph cache\n"); +} + }}}} CORRADE_TEST_MAIN(Magnum::Text::Test::RendererGLTest)