diff --git a/doc/changelog.dox b/doc/changelog.dox index 83332325a..0192722c1 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -459,6 +459,9 @@ See also: - Reworked @ref Text::AbstractGlyphCache on top of @ref TextureTools::AtlasLandfill allowing more efficient and incremental glyph packing together with support for texture arrays +- 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::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 08b6e0fb6..18cfb364e 100644 --- a/doc/snippets/Text-gl.cpp +++ b/doc/snippets/Text-gl.cpp @@ -32,10 +32,14 @@ #include "Magnum/PixelFormat.h" #include "Magnum/Math/Color.h" #include "Magnum/Math/Matrix3.h" +#include "Magnum/GL/MeshView.h" +#include "Magnum/GL/Renderer.h" #include "Magnum/Shaders/VectorGL.h" +#include "Magnum/Shaders/DistanceFieldVectorGL.h" #include "Magnum/Text/AbstractFont.h" +#include "Magnum/Text/AbstractShaper.h" #include "Magnum/Text/DistanceFieldGlyphCacheGL.h" -#include "Magnum/Text/Renderer.h" +#include "Magnum/Text/RendererGL.h" #define DOXYGEN_ELLIPSIS(...) __VA_ARGS__ @@ -44,8 +48,6 @@ using namespace Magnum::Math::Literals; namespace { Vector2i windowSize() { return {}; } - Vector2i framebufferSize() { return {}; } - Vector2 dpiScaling() { return {}; } } /* Make sure the name doesn't conflict with any other snippets to avoid linker @@ -73,6 +75,17 @@ if(!font->fillGlyphCache(cache, "abcdefghijklmnopqrstuvwxyz" /* [AbstractGlyphCache-usage-construct] */ Text::GlyphCacheGL cache{PixelFormat::R8Unorm, {256, 256}}; /* [AbstractGlyphCache-usage-construct] */ + +/* [AbstractGlyphCache-usage-draw] */ +Text::RendererGL renderer{cache}; +DOXYGEN_ELLIPSIS() + +Shaders::VectorGL2D shader; +shader + DOXYGEN_ELLIPSIS() + .bindVectorTexture(cache.texture()) + .draw(renderer.mesh()); +/* [AbstractGlyphCache-usage-draw] */ } #ifndef MAGNUM_TARGET_GLES2 @@ -87,6 +100,19 @@ if(!font->fillGlyphCache(cache, "abcdefghijklmnopqrstuvwxyz" "0123456789?!:;,. ")) Fatal{} << "Glyph cache too small to fit all characters"; /* [GlyphCacheArrayGL-usage] */ + +/* [GlyphCacheArrayGL-usage-draw] */ +Text::RendererGL renderer{cache}; +DOXYGEN_ELLIPSIS() + +Shaders::VectorGL2D shader{Shaders::VectorGL2D::Configuration{} + .setFlags(Shaders::VectorGL2D::Flag::TextureArrays) +}; +shader + DOXYGEN_ELLIPSIS() + .bindVectorTexture(cache.texture()) + .draw(renderer.mesh()); +/* [GlyphCacheArrayGL-usage-draw] */ } #endif @@ -105,6 +131,72 @@ if(!font->fillGlyphCache(cache, "abcdefghijklmnopqrstuvwxyz" "0123456789?!:;,. ")) Fatal{} << "Glyph cache too small to fit all characters"; /* [DistanceFieldGlyphCacheGL-usage] */ + +/* [DistanceFieldGlyphCacheGL-usage-draw] */ +Text::RendererGL renderer{cache}; +DOXYGEN_ELLIPSIS() + +Shaders::DistanceFieldVectorGL2D shader; +shader + DOXYGEN_ELLIPSIS() + .bindVectorTexture(cache.texture()) + .draw(renderer.mesh()); +/* [DistanceFieldGlyphCacheGL-usage-draw] */ +} + +{ +/* [Renderer-usage-construct] */ +Text::GlyphCacheGL cache{PixelFormat::R8Unorm, {256, 256}}; +DOXYGEN_ELLIPSIS() + +Text::RendererGL renderer{cache}; +/* [Renderer-usage-construct] */ + +/* [Renderer-usage-draw] */ +GL::Renderer::enable(GL::Renderer::Feature::Blending); +GL::Renderer::setBlendFunction( + GL::Renderer::BlendFunction::One, + GL::Renderer::BlendFunction::OneMinusSourceAlpha); + +Shaders::VectorGL2D shader; +shader + .setTransformationProjectionMatrix(Matrix3::projection(Vector2{windowSize()})) + .bindVectorTexture(cache.texture()) + .draw(renderer.mesh()); +/* [Renderer-usage-draw] */ +} + +{ + +PluginManager::Manager manager; +Containers::Pointer font = manager.loadAndInstantiate(""); +Text::GlyphCacheGL cache{PixelFormat::R8Unorm, {256, 256}}; +Text::RendererGL renderer{cache}; +Containers::Pointer shaper = font->createShaper(); +Shaders::VectorGL2D shader; +/* [Renderer-usage-blocks-draw] */ +Range1Dui helloRuns = renderer + DOXYGEN_ELLIPSIS() + .render(*shaper, shaper->font().size(), "Hello,").second(); +Range1Dui helloGlyphs = renderer.glyphsForRuns(helloRuns); + +Range1Dui worldRuns = renderer + DOXYGEN_ELLIPSIS() + .render(*shaper, shaper->font().size(), "world!").second(); +Range1Dui worldGlyphs = renderer.glyphsForRuns(worldRuns); + +shader + .setTransformationProjectionMatrix(Matrix3::projection(Vector2{windowSize()})) + .bindVectorTexture(cache.texture()) + .setColor(0x3bd267_rgbf) + .draw(GL::MeshView{renderer.mesh()} + .setIndexOffset(helloGlyphs.min()*6) + .setCount(helloGlyphs.size()*6)) + .setColor(0x2f83cc_rgbf) + .draw(GL::MeshView{renderer.mesh()} + .setIndexOffset(worldGlyphs.min()*6) + .setCount(worldGlyphs.size()*6)); +/* [Renderer-usage-blocks-draw] */ } { @@ -161,17 +253,4 @@ shader.setTransformationProjectionMatrix(projectionMatrix) .draw(renderer.mesh()); /* [BasicRenderer-usage2] */ } - -{ -/* [BasicRenderer-dpi-interface-size] */ -Vector2 interfaceSize = Vector2{windowSize()}/dpiScaling(); -/* [BasicRenderer-dpi-interface-size] */ -/* [BasicRenderer-dpi-size-multiplier] */ -Float sizeMultiplier = - (Vector2{framebufferSize()}*dpiScaling()/Vector2{windowSize()}).max(); -/* [BasicRenderer-dpi-size-multiplier] */ -static_cast(interfaceSize); -static_cast(sizeMultiplier); -} - } diff --git a/doc/snippets/Text.cpp b/doc/snippets/Text.cpp index b7d038e21..93e9ae9be 100644 --- a/doc/snippets/Text.cpp +++ b/doc/snippets/Text.cpp @@ -55,14 +55,22 @@ #include "Magnum/Text/AbstractShaper.h" #include "Magnum/Text/Direction.h" #include "Magnum/Text/Feature.h" +#include "Magnum/Text/Renderer.h" #include "Magnum/Text/Script.h" #include "Magnum/TextureTools/Atlas.h" #define DOXYGEN_ELLIPSIS(...) __VA_ARGS__ +#define DOXYGEN_IGNORE(...) __VA_ARGS__ using namespace Magnum; using namespace Magnum::Math::Literals; +namespace { + Vector2i windowSize() { return {}; } + Vector2i framebufferSize() { return {}; } + Vector2 dpiScaling() { return {}; } +} + namespace MyNamespace { struct MyFont: Text::AbstractFont { @@ -317,10 +325,7 @@ PluginManager::Manager manager; Containers::Pointer font = DOXYGEN_ELLIPSIS(manager.loadAndInstantiate("SomethingWhatever")); Containers::Pointer shaper = font->createShaper(); -/* Set text properties and shape it */ -shaper->setScript(Text::Script::Latin); -shaper->setDirection(Text::ShapeDirection::LeftToRight); -shaper->setLanguage("en"); +/* Shape a piece of text */ shaper->shape("Hello, world!"); /* Get the glyph info back */ @@ -336,6 +341,13 @@ shaper->glyphOffsetsAdvancesInto( stridedArrayView(glyphs).slice(&GlyphInfo::offset), stridedArrayView(glyphs).slice(&GlyphInfo::advance)); /* [AbstractShaper-shape] */ + +/* [AbstractShaper-shape-properties] */ +shaper->setScript(Text::Script::Latin); +shaper->setDirection(Text::ShapeDirection::LeftToRight); +shaper->setLanguage("en"); +shaper->shape("Hello, world!"); +/* [AbstractShaper-shape-properties] */ } { @@ -369,10 +381,11 @@ Containers::Pointer shaper = font->createShaper(); Containers::Pointer boldShaper = boldFont->createShaper(); DOXYGEN_ELLIPSIS() +Containers::StringView text = "Hello, world!"; Containers::Array glyphs; /* Shape "Hello, " with a regular font */ -shaper->shape("Hello, world!", 0, 7); +shaper->shape(text, 0, 7); Containers::StridedArrayView1D glyphs1 = arrayAppend(glyphs, NoInit, shaper->glyphCount()); shaper->glyphIdsInto( @@ -382,7 +395,7 @@ shaper->glyphOffsetsAdvancesInto( glyphs1.slice(&GlyphInfo::advance)); /* Append "world" shaped with a bold font */ -boldShaper->shape("Hello, world!", 7, 12); +boldShaper->shape(text, 7, 12); Containers::StridedArrayView1D glyphs2 = arrayAppend(glyphs, NoInit, boldShaper->glyphCount()); shaper->glyphIdsInto( @@ -392,7 +405,7 @@ shaper->glyphOffsetsAdvancesInto( glyphs2.slice(&GlyphInfo::advance)); /* Finally shape "!" with a regular font again */ -shaper->shape("Hello, world!", 12, 13); +shaper->shape(text, 12, 13); Containers::StridedArrayView1D glyphs3 = arrayAppend(glyphs, NoInit, shaper->glyphCount()); shaper->glyphIdsInto( @@ -410,7 +423,7 @@ shaper->glyphOffsetsAdvancesInto( PluginManager::Manager manager; Containers::Pointer font = manager.loadAndInstantiate("SomethingWhatever"); Containers::Pointer shaper = font->createShaper(); -/* [AbstractShaper-shape-clusters] */ +/* [AbstractShaper-shape-clusters-to-bytes] */ Containers::StringView text = DOXYGEN_ELLIPSIS({}); shaper->shape(text); @@ -420,8 +433,364 @@ Containers::Array clusters{NoInit, shaper->glyphCount()}; shaper->glyphClustersInto(clusters); Containers::StringView selection = text.slice(clusters[2], clusters[5]); -/* [AbstractShaper-shape-clusters] */ -static_cast(selection); +/* [AbstractShaper-shape-clusters-to-bytes] */ + +/* [AbstractShaper-shape-bytes-to-clusters] */ +Containers::Pair selectionGlyphs = + Text::glyphRangeForBytes(clusters, selection.begin() - text.begin(), + selection.end() - text.begin()); +/* [AbstractShaper-shape-bytes-to-clusters] */ +static_cast(selectionGlyphs); } +{ +struct: Text::AbstractGlyphCache { + using Text::AbstractGlyphCache::AbstractGlyphCache; + + Text::GlyphCacheFeatures doFeatures() const override { return {}; } +} cache{PixelFormat::R8Unorm, Vector2i{256}}; +PluginManager::Manager manager; +Containers::Pointer font = manager.loadAndInstantiate(""); +Containers::Pointer shaperPointer = font->createShaper(); +Text::AbstractShaper& shaper = *shaperPointer; +Float size{}; +/* [RendererCore-usage] */ +Text::RendererCore renderer{cache}; + +renderer.render(shaper, size, "Hello, world!"); +/* [RendererCore-usage] */ + +/* [RendererCore-usage-quads] */ +Range1Dui runs = renderer.render(DOXYGEN_ELLIPSIS(shaper, size, "Hello, world!")).second(); + +struct Vertex { + Vector2 position; + Vector2 textureCoordinates; /* or Vector3 for an array glyph cache */ +}; +Containers::Array vertices; +for(UnsignedInt run = runs.min(); run != runs.max(); ++run) { + Range1Dui glyphs = renderer.glyphsForRuns({run, run + 1}); + Containers::StridedArrayView1D runVertices = + arrayAppend(vertices, NoInit, glyphs.size()); + Text::renderGlyphQuadsInto(renderer.glyphCache(), + renderer.runScales()[run], + renderer.glyphPositions().slice(glyphs.min(), glyphs.max()), + renderer.glyphIds().slice(glyphs.min(), glyphs.max()), + runVertices.slice(&Vertex::position), + runVertices.slice(&Vertex::textureCoordinates)); +} +/* [RendererCore-usage-quads] */ +} + +{ +struct: Text::AbstractGlyphCache { + using Text::AbstractGlyphCache::AbstractGlyphCache; + + Text::GlyphCacheFeatures doFeatures() const override { return {}; } +} cache{PixelFormat::R8Unorm, Vector2i{256}}; +/* [RendererCore-allocators-static] */ +struct Glyph { + Vector2 position; + UnsignedInt id; + Vector2 advance; +} glyphs[256]; +struct Run { + Float scale; + UnsignedInt end; +} runs[16]; + +Text::RendererCore renderer{cache, + [](void* state, UnsignedInt glyphCount, + Containers::StridedArrayView1D& glyphPositions, + Containers::StridedArrayView1D& glyphIds, + Containers::StridedArrayView1D*, + Containers::StridedArrayView1D& glyphAdvances + ) { + Containers::ArrayView glyphs = *static_cast(state); + CORRADE_INTERNAL_ASSERT(glyphCount <= glyphs.size());DOXYGEN_IGNORE(static_cast(glyphCount)); + glyphPositions = stridedArrayView(glyphs).slice(&Glyph::position); + glyphIds = stridedArrayView(glyphs).slice(&Glyph::id); + glyphAdvances = stridedArrayView(glyphs).slice(&Glyph::advance); + }, glyphs, + [](void* state, UnsignedInt runCount, + Containers::StridedArrayView1D& runScales, + Containers::StridedArrayView1D& runEnds + ) { + Containers::ArrayView runs = *static_cast(state); + CORRADE_INTERNAL_ASSERT(runCount <= runs.size());DOXYGEN_IGNORE(static_cast(runCount)); + runScales = Containers::stridedArrayView(runs).slice(&Run::scale); + runEnds = Containers::stridedArrayView(runs).slice(&Run::end); + }, runs +}; +/* [RendererCore-allocators-static] */ +} + +{ +struct: Text::AbstractGlyphCache { + using Text::AbstractGlyphCache::AbstractGlyphCache; + + Text::GlyphCacheFeatures doFeatures() const override { return {}; } +} cache{PixelFormat::R8Unorm, Vector2i{256}}; +struct Glyph { + Vector2 position; + UnsignedInt id; + Vector2 advance; +}; +PluginManager::Manager manager; +Containers::Pointer font = manager.loadAndInstantiate(""); +Containers::Pointer shaperPointer = font->createShaper(); +Text::AbstractShaper& shaper = *shaperPointer; +Float size{}; +/* [RendererCore-allocators-redirect] */ +struct Allocation { + UnsignedInt current = 0; + /* Using just a fixed set of texts for brevity */ + Containers::Array texts[5]; +} allocation; + +Text::RendererCore renderer{cache, + [](void* state, UnsignedInt glyphCount, + Containers::StridedArrayView1D& glyphPositions, + Containers::StridedArrayView1D& glyphIds, + Containers::StridedArrayView1D*, + Containers::StridedArrayView1D& glyphAdvances + ) { + auto& allocation = *static_cast(state); + Containers::Array& glyphs = allocation.texts[allocation.current]; + if(glyphCount > glyphs.size()) + arrayResize(glyphs, glyphCount); + + glyphPositions = stridedArrayView(glyphs).slice(&Glyph::position); + glyphIds = stridedArrayView(glyphs).slice(&Glyph::id); + glyphAdvances = stridedArrayView(glyphs).slice(&Glyph::advance); + }, &allocation, + /* Text runs use the renderer's default allocator */ + nullptr, nullptr +}; + +DOXYGEN_ELLIPSIS() + +/* Updating text 3 */ +allocation.current = 3; +renderer + .clear() + .render(shaper, size, "Hello, world!"); + +/* Updating text 1 */ +allocation.current = 1; +renderer + .clear() + .render(shaper, size, "This doesn't replace text 3!"); +/* [RendererCore-allocators-redirect] */ +} + +{ +struct: Text::AbstractGlyphCache { + using Text::AbstractGlyphCache::AbstractGlyphCache; + + Text::GlyphCacheFeatures doFeatures() const override { return {}; } +} cache{PixelFormat::R8Unorm, Vector2i{256}}; +PluginManager::Manager manager; +/* [Renderer-usage-fill] */ +Containers::Pointer font = DOXYGEN_ELLIPSIS(manager.loadAndInstantiate("")); + +if(!font->fillGlyphCache(cache, "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "0123456789?!:;,. ")) + Fatal{} << "Glyph cache too small to fit all characters"; +/* [Renderer-usage-fill] */ + +Text::Renderer renderer{cache}; +/* [Renderer-usage-render] */ +renderer.render(*font->createShaper(), font->size(), "Hello, world!"); +/* [Renderer-usage-render] */ + +/* [Renderer-usage-layout-options] */ +renderer + .setCursor({+windowSize().x()*0.5f - 10.0f, + -windowSize().y()*0.5f + 10.0f}) + .setAlignment(Text::Alignment::BottomRight) + .render(*font->createShaper(), font->size(), "Hello,\nworld!"); +/* [Renderer-usage-layout-options] */ + +/* [Renderer-usage-shape-properties] */ +Containers::Pointer shaper = font->createShaper(); +shaper->setScript(Text::Script::Latin); +shaper->setLanguage("en"); +shaper->setDirection(Text::ShapeDirection::LeftToRight); + +renderer.render(*shaper, shaper->font().size(), "Hello, world!"); +/* [Renderer-usage-shape-properties] */ + +/* [Renderer-usage-shape-features] */ +renderer.render(*shaper, shaper->font().size(), "Hello, world!", { + {Text::Feature::SmallCapitals, 7, 12} +}); +/* [Renderer-usage-shape-features] */ + +{ +Containers::Pointer shaper = font->createShaper(); +/* [Renderer-usage-blocks] */ +renderer + .setCursor({-windowSize().x()*0.5f + 10.0f, + -windowSize().y()*0.5f + 10.0f}) + .setAlignment(Text::Alignment::BottomLeft) + .render(*shaper, shaper->font().size(), "Hello,"); + +renderer + .setCursor({+windowSize().x()*0.5f - 10.0f, + -windowSize().y()*0.5f + 10.0f}) + .setAlignment(Text::Alignment::BottomRight) + .render(*shaper, shaper->font().size(), "world!"); +/* [Renderer-usage-blocks] */ +} + +{ +Containers::Pointer boldFont = DOXYGEN_ELLIPSIS(manager.loadAndInstantiate("")); +/* [Renderer-usage-runs] */ +Containers::Pointer shaper = font->createShaper(); +Containers::Pointer boldShaper = boldFont->createShaper(); +DOXYGEN_ELLIPSIS() + +renderer + .add(*shaper, shaper->font().size(), "Hello, ") + .add(*boldShaper, boldShaper->font().size(), "world") + .add(*shaper, shaper->font().size(), "!") + .render(); +/* [Renderer-usage-runs] */ + +/* [Renderer-usage-runs-begin-end] */ +Containers::StringView text = "Hello, world!"; + +renderer + .add(*shaper, shaper->font().size(), text, 0, 7) + .add(*boldShaper, boldShaper->font().size(), text, 7, 12) + .add(*shaper, shaper->font().size(), text, 12, 13) + .render(); +/* [Renderer-usage-runs-begin-end] */ +} +} + +{ +PluginManager::Manager manager; +Float size{}; +struct: Text::AbstractGlyphCache { + using Text::AbstractGlyphCache::AbstractGlyphCache; + + Text::GlyphCacheFeatures doFeatures() const override { return {}; } +} cache{PixelFormat::R8Unorm, Vector2i{256}}; +Text::Renderer renderer{cache}; +/* [Renderer-dpi-supersampling] */ +Containers::Pointer font = DOXYGEN_ELLIPSIS(manager.loadAndInstantiate("")); +if(!font->openFile("font.ttf", size*2.0f)) /* Supersample 2x */ + DOXYGEN_ELLIPSIS({}) + +DOXYGEN_ELLIPSIS() +renderer.render(*font->createShaper(), size, DOXYGEN_ELLIPSIS("")); +/* [Renderer-dpi-supersampling] */ +} + +{ +/* [Renderer-dpi-interface-size] */ +Vector2 interfaceSize = Vector2{windowSize()}/dpiScaling(); +/* [Renderer-dpi-interface-size] */ +/* [Renderer-dpi-size-multiplier] */ +Float sizeMultiplier = + (Vector2{framebufferSize()}*dpiScaling()/Vector2{windowSize()}).max(); +/* [Renderer-dpi-size-multiplier] */ +static_cast(interfaceSize); +static_cast(sizeMultiplier); +} + +{ +struct: Text::AbstractGlyphCache { + using Text::AbstractGlyphCache::AbstractGlyphCache; + + Text::GlyphCacheFeatures doFeatures() const override { return {}; } +} cache{PixelFormat::R8Unorm, Vector2i{256}}; +PluginManager::Manager manager; +Containers::Pointer font = manager.loadAndInstantiate(""); +Containers::StringView text; +/* [Renderer-clusters] */ +Text::Renderer renderer{cache, Text::RendererFlag::GlyphPositionsClusters}; + +Range1Dui runs = renderer.render(DOXYGEN_ELLIPSIS(*font->createShaper(), 0.0f), text).second(); +Range1Dui glyphs = renderer.glyphsForRuns(runs); +Containers::StridedArrayView1D clusters = + renderer.glyphClusters().slice(glyphs.min(), glyphs.max()); + +/* Input text corresponding to glyphs 2 to 5 */ +Containers::StringView selection = text.slice(clusters[2], clusters[5]); + +/* Or glyphs corresponding to a concrete text selection */ +Containers::Pair selectionGlyphs = + Text::glyphRangeForBytes(clusters, selection.begin() - text.begin(), + selection.end() - text.begin()); +/* [Renderer-clusters] */ +static_cast(selectionGlyphs); +} + +{ +struct: Text::AbstractGlyphCache { + using Text::AbstractGlyphCache::AbstractGlyphCache; + + Text::GlyphCacheFeatures doFeatures() const override { return {}; } +} cache{PixelFormat::R8Unorm, Vector2i{256}}; +/* [Renderer-allocators-vertex] */ +struct Vertex { + Vector2 position; + Vector2 textureCoordinates; + Color4 color; +}; +Containers::Array vertices; + +Text::Renderer renderer{cache, + /* Glyphs, runs and indices use renderer's default allocators */ + nullptr, nullptr, + nullptr, nullptr, + nullptr, nullptr, + [](void* state, UnsignedInt vertexCount, + Containers::StridedArrayView1D& vertexPositions, + Containers::StridedArrayView1D& vertexTextureCoordinates + ) { + auto& vertices = *static_cast*>(state); + if(vertexCount > vertices.size()) + arrayResize(vertices, vertexCount); + + vertexPositions = stridedArrayView(vertices).slice(&Vertex::position); + vertexTextureCoordinates = + stridedArrayView(vertices).slice(&Vertex::textureCoordinates); + }, &vertices +}; + +/* Render a text and fill vertex colors. Each glyph quad is four vertices. */ +Range1Dui runs = renderer.render(DOXYGEN_ELLIPSIS()).second(); +Range1Dui glyphs = renderer.glyphsForRuns(runs); +for(Vertex& vertex: vertices.slice(glyphs.min()*4, glyphs.max()*4)) + vertex.color = 0x3bd267_rgbf; +/* [Renderer-allocators-vertex] */ +} + +{ +struct: Text::AbstractGlyphCache { + using Text::AbstractGlyphCache::AbstractGlyphCache; + + Text::GlyphCacheFeatures doFeatures() const override { return {}; } +} cache{PixelFormat::R8Unorm, Vector2i{256}}; +/* [Renderer-allocators-index] */ +/* A 2-byte index type can index at most 65k vertices, which is enough for 16k + glyph quads, and each glyph quad needs six indices */ +char indices[2*16384*6]; + +Text::Renderer renderer{cache, + nullptr, nullptr, + nullptr, nullptr, + [](void* state, UnsignedInt size, Containers::ArrayView& indices) { + indices = *static_cast(state); + CORRADE_INTERNAL_ASSERT(size <= indices.size());DOXYGEN_IGNORE(static_cast(size)); + }, indices, + nullptr, nullptr +}; +/* [Renderer-allocators-index] */ +} } diff --git a/src/Magnum/Text/AbstractFont.h b/src/Magnum/Text/AbstractFont.h index d963b43eb..6ce2e653d 100644 --- a/src/Magnum/Text/AbstractFont.h +++ b/src/Magnum/Text/AbstractFont.h @@ -102,8 +102,9 @@ MAGNUM_TEXT_EXPORT Debug& operator<<(Debug& debug, FontFeatures value); /** @brief Base for font plugins -Provides interface for opening fonts, filling a glyph cache and layouting the -glyphs. +Provides interface for opening font files, filling a glyph cache with +rasterized glyphs and shaping a Unicode text into a sequence of glyph IDs and +their positions. @section Text-AbstractFont-usage Usage @@ -124,9 +125,8 @@ that the characters won't all fit, which should be checked by the application: See @ref plugins for more information about general plugin usage and the list of @m_class{m-doc} derived classes for available font plugins. See @ref AbstractGlyphCache for more information about glyph -caches, @ref BasicRenderer "Renderer*D" for high-level text rendering, and -@ref AbstractShaper for low-level access to the font text shaping -functionality. +caches, @ref Renderer for high-level text rendering, and @ref AbstractShaper +for low-level access to the font text shaping functionality. @section Text-AbstractFont-font-size Font size @@ -140,16 +140,18 @@ properties are specified *in pixels* in @ref lineHeight(), @ref ascent() and @ref descent(). The font size used when opening the font affects how large the glyphs will be -when rendered into the glyph cache. Actual text rendering with -@ref BasicRenderer "Renderer*D" however uses its own font size, and the -rendered size is then additionally depending on the actual projection used. +when rasterized into the glyph cache. Actual text rendering with @ref Renderer +then uses its own font size, and the actual size that's visible on the screen +is then additionally depending on the actual projection used when drawing the +rendered mesh. + This decoupling of font sizes is useful for example in case of @ref DistanceFieldGlyphCacheGL, where a single prerendered glyph size can be used to render arbitrarily large font sizes without becoming blurry or jaggy. When not using a distance field glyph cache, it's usually desirable to have the font size and the actual rendered size match. See -@ref Text-BasicRenderer-usage-font-size "the Renderer*D documentation" for -further information about picking font sizes. +@ref Text-Renderer-usage-font-size "the Renderer documentation" for further +information about picking font sizes. @section Text-AbstractFont-glyph-cache Glyph cache filling options diff --git a/src/Magnum/Text/AbstractGlyphCache.h b/src/Magnum/Text/AbstractGlyphCache.h index a49912123..caff64fcc 100644 --- a/src/Magnum/Text/AbstractGlyphCache.h +++ b/src/Magnum/Text/AbstractGlyphCache.h @@ -134,7 +134,12 @@ won't all fit, which should be checked by the application: As long as the cache size allows, you can call @ref AbstractFont::fillGlyphCache() multiple times with additional glyphs and other fonts. See the @ref Text-AbstractFont-glyph-cache "AbstractFont documentation" -for more options for glyph cache filling. +for more options for glyph cache filling. Finally, assuming a @ref RendererGL +is used with this cache for rendering the text, its +@relativeref{RendererGL,mesh()} can be then drawn using @ref Shaders::VectorGL, +together with binding @ref GlyphCacheGL::texture() for drawing: + +@snippet Text-gl.cpp AbstractGlyphCache-usage-draw @section Text-AbstractGlyphCache-filling Filling the glyph cache directly diff --git a/src/Magnum/Text/AbstractShaper.h b/src/Magnum/Text/AbstractShaper.h index 42fa2eb34..36013957b 100644 --- a/src/Magnum/Text/AbstractShaper.h +++ b/src/Magnum/Text/AbstractShaper.h @@ -44,9 +44,11 @@ namespace Magnum { namespace Text { @brief Base for text shapers @m_since_latest -Returned from @ref AbstractFont::createShaper(), provides an interface for -* *shaping* text with the @ref AbstractFont it originated from. Meant to be -(privately) subclassed by @ref AbstractFont plugin implementations. +Returned from @ref AbstractFont::createShaper(), provides a low-level interface +for *shaping* text with the @ref AbstractFont it originated from. Meant to be +(privately) subclassed by @ref AbstractFont plugin implementations. For common +text rendering you'll likely want to use the high-level @ref Renderer, which +then invokes @ref AbstractShaper internally. * *Shaping* is a process of converting a sequence of Unicode codepoints to a visual form, i.e. a list of glyphs of a particular font, their offsets and horizontal or vertical advances. Shaping is often not a 1:1 mapping from @@ -69,27 +71,38 @@ shaped text. @snippet Text.cpp AbstractShaper-shape -For best results, it's recommended to call (a subset of) @ref setScript(), -@ref setLanguage() and @ref setDirection() if at least some properties of the -input text are known, as shown above. Without these, the font plugin may -attempt to autodetect the properties, which might not always give a correct -result. If a particular font plugin doesn't implement given script, language or +@subsection Text-AbstractShaper-usage-properties Specifying shaping properties + +By default, and depending on the font plugin capabilities, the shaper +autodetects the script, language and direction of the shaped text. It's +possible to call (a subset of) @ref setScript(), @ref setLanguage() and +@ref setDirection() if at least some properties of the input text are known, +which can make the shaping process faster, or help in cases the properties +can't be unambiguously detected from the input: + +@snippet Text.cpp AbstractShaper-shape-properties + +If a particular font plugin doesn't implement given script, language or direction or if it doesn't have any special handling for it, given function will return @cpp false @ce. The @ref script() const, @ref language() const and @ref direction() const can be used to inspect results of autodetection after -@ref shape() has been called. The set of supported scripts, languages and -directions and exact behavior for unsupported values is plugin-specific --- it -may for example choose a fallback instead, or it may ignore the setting -altogeter. See documentation of particular @ref AbstractFont subclasses for -more information. +@ref shape() has been called. Setting @ref Script::Unspecified, an empty +language string and @ref ShapeDirection::Unspecified makes the implementation +go back to autodetection for the next shaping operation. + +The set of supported scripts, languages and directions and exact behavior for +unsupported values is plugin-specific --- it may for example choose a fallback +instead, or it may ignore the setting altogeter. See documentation of +particular @ref AbstractFont subclasses for more information. @subsection Text-AbstractShaper-usage-features Enabling and disabling typographic features In the above snippet, the whole text is shaped using typographic features that -are default in the font. For example, assuming the font would support small -capitals (and the particular @ref AbstractFont plugin would recognize and use -the feature), we could render the "world" part with small caps, resulting in -"Hello, ᴡᴏʀʟᴅ!". +are default in the font. The last argument to @ref shape() takes a list of +@ref FeatureRange items to override those. For example, assuming the font would +support small capitals (and the particular @ref AbstractFont plugin would +recognize and use the feature), we could render the "world" part with small +caps, resulting in "Hello, ᴡᴏʀʟᴅ!". @snippet Text.cpp AbstractShaper-shape-features @@ -100,25 +113,24 @@ argument. The range, if present, is always given in *bytes* of the UTF-8 input. Capabilities of typographic features are rather broad, see the @ref Feature enum and documentation linked from it for exhaustive information. -@subsection Text-AbstractShaper-usage-multiple Combining different shapers +@section Text-AbstractShaper-multiple Combining different shapers -Sometimes it's desirable to render different parts of the text with different -fonts, not just different features of the same font. A variation of the above -example could be rendering the "world" part with a bold font: +If it's desirable to render different parts of the text with different fonts, +the output from multiple shapers can be combined togeter. The following code is +a variation of the above example, shaping the "world" part with a bold font, +although in a quite verbose way compared to +@ref Text-Renderer-usage-runs "the same achieved with the high-level Renderer": @snippet Text.cpp AbstractShaper-shape-multiple The resulting `glyphs` array is usable the same way as in the above case, with a difference that the glyph IDs have to be looked up in an @ref AbstractGlyphCache with a font ID corresponding to the range they're in. -Also note that the whole text is passed every time and a begin & end is -specified for it instead of passing just the slice alone. While possibly not -having any visible effect in this particular case, in general it allows the -shaper to make additional decisions based on surrounding context, for example -picking glyphs that are better connected to their neighbors in handwriting -fonts. +Similarly as with the linked higher-level example, the whole text is passed +every time and a begin & end is specified for it instead of passing just the +slice alone, to allow the shaper to get additional context if needed. -@subsection Text-AbstractShaper-usage-instances Managing multiple instances +@section Text-AbstractShaper-instances Managing multiple instances As shown above, a particular @ref AbstractShaper instance is reusable, i.e. it's possible to call @ref shape() (and potentially also @ref setScript(), @@ -134,7 +146,7 @@ every time. Or for example have a few persistent @ref AbstractShaper instances for dynamic text that changes every frame, or have dedicated preconfigured per-font, per-script or per-language instances. -@subsection Text-AbstractShaper-usage-clusters Mapping between input text and shaped glyphs +@section Text-AbstractShaper-clusters Mapping between input text and shaped glyphs For implementing text selection or editing, mapping from screen position to concrete glyphs can be done using the advances returned from @@ -145,16 +157,20 @@ rarely a 1:1 mapping from the shaped glyphs back to the input text. The mapping from glyph IDs to bytes of the text passed to @ref shape() can be retrieved using @ref glyphClustersInto(). In the following example, a range -between glyphs 2 and 5 is mapped to the input text bytes, for example to copy -it as a selection to clipboard: +between glyphs @cpp 2 @ce and @cpp 5 @ce is mapped to the input text bytes, for +example to copy it as a selection to clipboard: -@snippet Text.cpp AbstractShaper-shape-clusters +@snippet Text.cpp AbstractShaper-shape-clusters-to-bytes In the other direction, picking a range of glyphs corresponding to a range of -input bytes, involves finding cluster IDs with a lower and upper bound for -given byte positions. See the documentation of @ref glyphClustersInto() for -concrete examples of how retrieved cluster IDs may look like depending on what -operations the shaper performs. +input bytes, involves finding cluster IDs matching given byte positions, which +is doable with the @ref glyphRangeForBytes() utility: + +@snippet Text.cpp AbstractShaper-shape-bytes-to-clusters + +See the documentation of @ref glyphClustersInto() for concrete examples of how +retrieved cluster IDs may look like depending on what operations the shaper +performs. @section Text-AbstractShaper-subclassing Subclassing @@ -299,7 +315,7 @@ class MAGNUM_TEXT_EXPORT AbstractShaper { * @p text this allows the implementation to perform shaping aware of * surrounding context, such as picking correct glyphs for beginning, * middle or end of a word or a paragraph. - * @see @ref Text-AbstractShaper-usage-multiple + * @see @ref Text-AbstractShaper-multiple */ #ifdef DOXYGEN_GENERATING_OUTPUT UnsignedInt shape(Containers::StringView text, UnsignedInt begin, UnsignedInt end, Containers::ArrayView features = {}); diff --git a/src/Magnum/Text/Alignment.h b/src/Magnum/Text/Alignment.h index c8a6bea2a..e4d456b72 100644 --- a/src/Magnum/Text/Alignment.h +++ b/src/Magnum/Text/Alignment.h @@ -86,9 +86,8 @@ respectively, if @ref ShapeDirection::LeftToRight is passed to @ref ShapeDirection::RightToLeft is set (or detected for @ref ShapeDirection::Unspecified), they're swapped, i.e. `*Begin` becomes `*Right` and `*End` becomes `*Left`. -@see @ref BasicRenderer::render() "Renderer*D::render()", - @ref BasicRenderer::BasicRenderer() "Renderer*D::Renderer*D()", - @see @ref alignmentForDirection() +@see @ref Renderer::render(AbstractShaper&, Float, Containers::StringView, Containers::ArrayView) "Renderer::render()", + @ref Renderer::add(), @ref alignmentForDirection() */ enum class Alignment: UnsignedByte { /** diff --git a/src/Magnum/Text/DistanceFieldGlyphCacheGL.h b/src/Magnum/Text/DistanceFieldGlyphCacheGL.h index 17c9d62bc..2efd0401d 100644 --- a/src/Magnum/Text/DistanceFieldGlyphCacheGL.h +++ b/src/Magnum/Text/DistanceFieldGlyphCacheGL.h @@ -71,9 +71,18 @@ channels. @snippet Text-gl.cpp DistanceFieldGlyphCacheGL-usage -See the @ref BasicRenderer "Renderer*D" class for information about text -rendering. The @ref AbstractGlyphCache base class has more information about -general glyph cache usage. +As long as the cache size allows, you can call +@ref AbstractFont::fillGlyphCache() multiple times with additional glyphs and +other fonts, each time the input will be incrementally converted to a distance +field texture. See the @ref Text-AbstractFont-glyph-cache "AbstractFont documentation" +for more options for glyph cache filling. The @ref AbstractGlyphCache base +class has more information about general glyph cache usage. Finally, 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, together with binding @ref texture() for +drawing: + +@snippet Text-gl.cpp DistanceFieldGlyphCacheGL-usage-draw @section Text-DistanceFieldGlyphCacheGL-internal-format Internal texture format diff --git a/src/Magnum/Text/GlyphCacheGL.h b/src/Magnum/Text/GlyphCacheGL.h index 9ce0a108c..3bea290ed 100644 --- a/src/Magnum/Text/GlyphCacheGL.h +++ b/src/Magnum/Text/GlyphCacheGL.h @@ -48,10 +48,10 @@ namespace Magnum { namespace Text { Implementation of an @ref AbstractGlyphCache backed by a @ref GL::Texture2D. See the @ref AbstractGlyphCache class documentation for information about -setting up an instance of this class and filling it with glyphs. See the -@ref DistanceFieldGlyphCacheGL subclass for a variant that adds distance field -processing on top, @ref GlyphCacheArrayGL is then using a @ref GL::Texture2DArray -instead. +setting up an instance of this class, filling it with glyphs and drawing the +text with it. See the @ref DistanceFieldGlyphCacheGL subclass for a variant +that adds distance field processing on top, @ref GlyphCacheArrayGL is then +using a @ref GL::Texture2DArray instead. @section Text-GlyphCacheGL-internal-format Internal texture format @@ -211,11 +211,18 @@ Implementation of an @ref AbstractGlyphCache backed by a @ref GL::Texture2DArray, other than that equivalent to @ref GlyphCacheGL. See the @ref AbstractGlyphCache class documentation for information about setting up a glyph cache instance and filling it with glyphs, and @ref GlyphCacheGL for -details on how the internal texture format is picked. The usage differs from +details on how the internal texture format is picked. The setup differs from @ref GlyphCacheGL only in specifying one extra dimension for size: @snippet Text-gl.cpp GlyphCacheArrayGL-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::VectorGL +that has @ref Shaders::VectorGL::Flag::TextureArrays enabled, together with +binding @ref texture() for drawing: + +@snippet Text-gl.cpp GlyphCacheArrayGL-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. diff --git a/src/Magnum/Text/Renderer.h b/src/Magnum/Text/Renderer.h index 7c62ddf5b..7216d9661 100644 --- a/src/Magnum/Text/Renderer.h +++ b/src/Magnum/Text/Renderer.h @@ -98,6 +98,66 @@ MAGNUM_TEXT_EXPORT Debug& operator<<(Debug& output, RendererCoreFlags value); /** @brief Text renderer core @m_since_latest + +Implements essential logic for rendering text formed from multiple runs, lines +and fonts, providing access to glyph positions, glyph IDs and glyph cluster +information that can be subsequently used to show the text on the screen and +perform cursor or selection placement. + +See the higher-level @ref Renderer subclass for full usage documentation --- +the input interface is mostly the same between the two, with just the output +being something else. The rest of this documentation thus only highlights the +differences between the two. For lowest-level functionality see the +@ref AbstractShaper class, which exposes text shaping capabilities implemented +directly by particular font plugins. + +@section Text-RendererCore-usage Usage + +A @ref RendererCore instance is created and populated the same way as a +@ref Renderer or @ref RendererGL, with @ref add() and @ref render() behaving +exactly the same: + +@snippet Text.cpp RendererCore-usage + +Once rendered, the @ref glyphPositions() and @ref glyphIds() views provide +access to rendered glyph data, and @ref runScales() with @ref runEnds() +describe which scale is applied to particular glyph ranges in order to place +the glyphs at given positions apprpriately scaled. You can then use the +low-level @ref renderGlyphQuadsInto() utility to create textured quads, or feed +the data to some entirely different system that renders the text without +needing textured quads at all. + +@snippet Text.cpp RendererCore-usage-quads + +@section Text-RendererCore-clusters Mapping between input text and shaped glyphs + +For implementing text selection or editing, if +@ref RendererCoreFlag::GlyphClusters is enabled on @ref RendererCore +construction, the renderer exposes also the glyph cluster information for each +run via @ref glyphClusters(). See the @ref Text-Renderer-clusters "relevant Renderer documentation" +for a detailed explanation of how the data get used. + +@section Text-RendererCore-allocators Providing custom glyph and run data allocators + +For more control over memory allocations or for very customized use, it's +possible to hook up custom allocators for glyph and run data. For example, if +you always use the @ref RendererCore to render only up to a fixed amount of +glyphs, you can direct it to statically sized arrays: + +@snippet Text.cpp RendererCore-allocators-static + +A behavior worth mentioning is that on @ref clear() or @ref reset() the +allocators get called with the count being @cpp 0 @ce, which can be used to +"redirect" the allocation to some entirely different memory. That allows you to +use a single renderer instance to independently render and replace several +different text blocks, instead of having a dedicated renderer instance for each +or having to copy the rendered data out every time: + +@snippet Text.cpp RendererCore-allocators-redirect + +Expected allocator behavior is fully documented in the @ref RendererCore() +constructor, note especially the special casing for glyph advances. The +@ref Text-Renderer-allocators "Renderer subclass then provides two additional allocator hooks for index and vertex data." */ class MAGNUM_TEXT_EXPORT RendererCore { public: @@ -683,6 +743,286 @@ MAGNUM_TEXT_EXPORT Debug& operator<<(Debug& output, RendererFlags value); /** @brief Text renderer @m_since_latest + +Implements logic for rendering text formed from multiple runs, lines and fonts, +resulting in a textured quad mesh, optionally with glyph cluster information +that can be used to perform cursor and selection placement. You'll likely use +the renderer through the @ref RendererGL subclass, which directly populates a +@ref GL::Mesh instance with the rendered data. + +The @ref RendererCore base implements just glyph positioning and layout, to be +used in scenarios where forming textured quads is deferred to later or not +needed at all. For lowest-level functionality see the @ref AbstractShaper +class, which exposes text shaping capabilities implemented directly by +particular font plugins. + +@section Text-Renderer-usage Usage + +Assuming you'll want to subsequently render the text with OpenGL, construct the +renderer using the @ref RendererGL subclass and an OpenGL glyph cache +implementation, such as a @ref GlyphCacheGL. + +@snippet Text-gl.cpp Renderer-usage-construct + +Before rendering with a particular @ref AbstractFont, the glyph cache has to be +filled with desired glyphs from it, if not done already, as shown below. The +@ref AbstractFont class documentation shows additional ways how to fill the +glyph cache. + +@snippet Text.cpp Renderer-usage-fill + +To render a text, pass an @ref AbstractShaper instance created from the font +together with desired font size and actual text to +@ref render(AbstractShaper&, Float, Containers::StringView, Containers::ArrayView) "render()": + +@snippet Text.cpp Renderer-usage-render + +Once rendered, the @ref RendererGL::mesh() contains the rendered glyphs as +textured quads. The mesh can be drawn using @ref Shaders::VectorGL together +with binding @ref GlyphCacheGL::texture() for drawing. Usually you'll also want +to set up @ref GL::Renderer::Feature::Blending "alpha blending" so overlapping +glyph quads don't cut into each other. Assuming the drawing is performed in a +@ref Platform::Sdl2Application "Platform::*Application" subclass, the code +below sets up the drawing that the font pixel size matches the window pixels by +specifying @ref Platform::Sdl2Application::windowSize() "Platform::*Application::windowSize()" +as the projection size, and it appears in the center of the window. With +@ref GlyphCacheArrayGL or @ref DistanceFieldGlyphCacheGL the drawing setup is +slightly different, see their documentation for examples. + +@snippet Text-gl.cpp Renderer-usage-draw + +If you use just the base @ref Renderer, the rendered index and vertex data are +exposed through @ref indices(), @ref vertexPositions() and +@ref vertexTextureCoordinates() / @ref vertexTextureArrayCoordinates(), which +you can pass to a custom renderer, for example. Likewise, the glyph cache can +be a custom @ref AbstractGlyphCache subclass that uploads the rasterized glyph +data to a texture in a custom renderer. For more control over data layout in +custom use cases see the @ref Text-Renderer-allocators section down below. + +@subsection Text-Renderer-usage-layout-options Cursor, alignment and other layouting options + +Internally, the renderer splits the passed text to individual lines and shapes +each separately, placing them together at a concrete cursor position with +specific alignment and line advance. The initial cursor position is at +@cpp {0.0f, 0.0f} @ce with a @ref Text::Alignment::MiddleCenter and line +advance matching @ref AbstractFont::lineHeight(). This can be overriden using +@ref setCursor(), @ref setAlignment() and @ref setLineAdvance() before +rendering a particular piece of text. The following snippet renders the same +text as above but wrapped to two lines, aligned to bottom right and positioned +ten pixels from the bottom right corner of the window: + +@snippet Text.cpp Renderer-usage-layout-options + +The renderer supports only horizontal text layout right now. The +@ref setLayoutDirection() API is currently just a placeholder for when vertical +layout is supported in the future as well. + +@subsection Text-Renderer-usage-shape-properties Font script, language and shaping direction + +The @ref AbstractShaper instance contains internal font-specific state used for +actual text shaping. If you create an instance and reuse it multiple times +instead of creating it on the fly, it allows the implementation to +@ref Text-AbstractShaper-instances "reuse allocated resources". It's also +possible to call @ref AbstractShaper::setScript(), +@relativeref{AbstractShaper,setLanguage()} and +@relativeref{AbstractShaper,setDirection()} on the instance if at least some +properties of the input text are known, which can make the shaping process +faster, or help in cases the properties can't be unambiguously detected from +the input: + +@snippet Text.cpp Renderer-usage-shape-properties + +The @ref Text-AbstractShaper-usage-properties "AbstractShaper documentation" +has additional info about how the shaping properties are handled and how to +check what's supported by a particular font plugin. + +@subsection Text-Renderer-usage-shape-features Font features + +The last argument to @ref render(AbstractShaper&, Float, Containers::StringView, Containers::ArrayView) "render()" +allows enabling and disabling typographic features. For example, assuming the +font would support small capitals (and the particular @ref AbstractFont plugin +would recognize and use the feature), we could render the "world" part with +small caps, resulting in "Hello, ᴡᴏʀʟᴅ!": + +@snippet Text.cpp Renderer-usage-shape-features + +The behavior is equivalent to font features passed to @ref AbstractShaper::shape(), +see the @ref Text-AbstractShaper-usage-features "AbstractShaper documentation" +for further details. + +@subsection Text-Renderer-usage-blocks Rendering multiple text blocks + +Each call to @ref render(AbstractShaper&, Float, Containers::StringView, Containers::ArrayView) "render()" +doesn't replace the previously rendered but *appends* to it, starting again +from the position specified with @ref setCursor(). Ultimately that means you +can render multiple blocks of text into the same mesh. The following snippet +places the two parts of the text to bottom left and bottom right window +corners: + +@snippet Text.cpp Renderer-usage-blocks + +Then you can either draw everything at once, with the same shader setup as +listed above, or draw particular parts with different settings. For that, the +@ref render(AbstractShaper&, Float, Containers::StringView, Containers::ArrayView) "render()" +function returns two values --- a bounding box that can be used for various +placement and alignment purposes, and a range of text *runs* the rendered text +spans. A run is a range of glyphs together with an associated scale, and the +run offsets can be converted to glyph offsets using @ref glyphsForRuns(). +Glyph offsets then directly correspond to vertex and index offsets, as each +glyph is a quad consisting of four vertices and six indices. The following code +draws each of the two pieces with a different color by making temporary +@ref GL::MeshView instances spanning just the corresponding glyph range: + +@snippet Text-gl.cpp Renderer-usage-blocks-draw + +Finally, @ref clear() discards all text rendered so far, meaning that the next +@ref render(AbstractShaper&, Float, Containers::StringView, Containers::ArrayView) "render()" +will start from an empty state. The @ref reset() function additionally resets +also all other state like cursor position and alignment to default values. + +@subsection Text-Renderer-usage-runs Rendering multiple text runs together + +Besides rendering the whole text at once with a single font, it's possible to +render different parts of the text with different fonts, sizes or even just +differently configured shaper instances. This is done by using @ref add(), +which, compared to @ref render(AbstractShaper&, Float, Containers::StringView, Containers::ArrayView) "render()", continues shaping where +the previous @ref add() ended instead of going back to the cursor specified +with @ref setCursor(). When done, a call to the parameter-less @ref render() +performs final alignment and wraps up the rendering. In the following snippet, +a bold font is used to render the "world" part: + +@snippet Text.cpp Renderer-usage-runs + +Fonts can be switched anywhere, not just at word boundaries. However, to make +use of the full shaping capabilities of a certain font implementation, it's +recommended to supply the text with additional context so the shaper can +perform kerning, glyph substitution and other operations even across the +individual pieces. This is done by passing the whole text every time and +specifying the range to shape by a begin and end *byte* offset into it, +allowing the shaper to peek outside of the actually shaped piece of text: + +@snippet Text.cpp Renderer-usage-runs-begin-end + +@section Text-Renderer-usage-font-size Font size + +So far, for simplicity, the snippets above passed @ref AbstractFont::size() to +@ref render() or @ref add(), making the text rendered at exactly the size the +font glyphs were rasterized into the cache. The size at which the glyphs are +rasterized into the cache and the size at which a text is drawn on the screen +don't have to match, however. + +When rendering the text, there are two common approaches --- either setting up +the size to match a global user interface scale, or having the text size +proportional to the window size. The first approach is what's shown in the +above snippets, with a projection that matches @ref Platform::Sdl2Application::windowSize() "Platform::*Application::windowSize()", +and results in e.g. a 12 pt font matching a 12 pt font in other applications. +With the regular @ref GlyphCacheGL / @ref GlyphCacheArrayGL, an even crisper +result may be achieved by doubly supersampling the rasterized font compared to +the size it's drawn at, which is shown in the following snippet. Additional +considerations for proper DPI awareness are described further below. + +@snippet Text.cpp Renderer-dpi-supersampling + +The second approach, with text size being relative to the window size, is for +cases where the text is meant to match surrounding art, such as in a game menu. +In this case the projection size is usually something arbitrary that doesn't +match window pixels, and the text point size then has to be relative to that. +For this use case a @ref DistanceFieldGlyphCacheGL is the better match, as it +can provide text at different sizes without the scaling causing blurriness or +aliased edges. See its documentation for details about picking the right font +size and other parameters for best results. + +@subsection Text-Renderer-usage-font-size-dpi DPI awareness + +To achieve crisp rendering and/or text size matching other applications on +HiDPI displays, additional steps need to be taken. There are two separate +concepts for DPI-aware rendering: + +- Interface size --- size at which the interface elements are positioned on + the screen. Often, for simplicity, the interface is using some "virtual + units", so a 12 pt font is still a 12 pt font independently of how the + interface is scaled compared to actual display properties (for example by + setting a global 150% scale in the desktop environment, or by zooming a + browser window). The size passed to @ref render(AbstractShaper&, Float, Containers::StringView, Containers::ArrayView) "render()" + or @ref add() should match these virtual units. +- Framebuffer size --- how many pixels is actually there. If a 192 DPI + display has a 200% interface scale, a 12 pt font would be 32 pixels. But if + it only has a 150% scale, all interface elements will be smaller, and a 12 + pt font would be only 24 pixels. The size used by the @ref AbstractFont and + @ref GlyphCacheGL should be chosen with respect to the actual physical + pixels. + +When using for example @ref Platform::Sdl2Application or other `*Application` +implementations, you usually have three values at your disposal --- +@ref Platform::Sdl2Application::windowSize() "windowSize()", +@ref Platform::Sdl2Application::framebufferSize() "framebufferSize()" and +@ref Platform::Sdl2Application::dpiScaling() "dpiScaling()". Their relation is +documented thoroughly in @ref Platform-Sdl2Application-dpi, for this particular +case a scaled interface size, used instead of window size for the projection, +would be calculated like this: + +@snippet Text.cpp Renderer-dpi-interface-size + +And a multiplier for the @ref AbstractFont and @ref GlyphCacheGL font size like +this. The @ref render(AbstractShaper&, Float, Containers::StringView, Containers::ArrayView) "render()" +or @ref add() keeps using the size without this multiplier. + +@snippet Text.cpp Renderer-dpi-size-multiplier + +@section Text-Renderer-performance Rendering and mesh drawing performance + +To avoid repeated reallocations when rendering a larger chunk of text, when the +text is combined of many small runs or when rendering many separate text blocks +together, call @ref reserve() with expected glyph and run count. + +For the mesh the @ref Renderer by default uses @ref MeshIndexType::UnsignedByte, +and changes it to a larger type each time the @ref glyphCapacity() exceeds the +range representable in given type. Call @ref setIndexType() if you want to use +a larger type right from the start even if the glyph capacity doesn't need it. +In case of @ref RendererGL, @ref MeshIndexType::UnsignedShort is used by +default instead, as 8-bit indices are discouraged on contemporary GPUs. + +@todo update this once @ref AbstractShaper has @ref reserve() as well + +@section Text-Renderer-clusters Mapping between input text and shaped glyphs + +For implementing text selection or editing, if +@ref RendererFlag::GlyphPositionsClusters is enabled on @ref Renderer +construction, the renderer exposes also glyph position and cluster information +for each run via @ref glyphPositions() and @ref glyphClusters(). The clusters +are used the same way @ref Text-AbstractShaper-clusters "as described in the AbstractShaper documentation", +but additionally with mapping a particular text run to a concrete range of +glyphs for which to query the runs: + +@snippet Text.cpp Renderer-clusters + +When using @ref RendererGL, the flag is +@ref RendererGLFlag::GlyphPositionsClusters instead, for @ref RendererCore it's +just @ref RendererCoreFlag::GlyphClusters as glyph positions are available +always. + +@section Text-Renderer-allocators Providing custom index and vertex data allocators + +If you're using @ref Renderer directly and not the @ref RendererGL subclass, +it's possible to hook up custom allocators for index and vertex data. Let's say +that you want to have per-vertex colors in addition to positions and texture +coordinates. The text renderer produces position and texture coordinate data +inside @ref render() and colors get filled in after, by querying the vertex +range corresponding to the produced glyph run produced using +@ref glyphsForRuns(): + +@snippet Text.cpp Renderer-allocators-vertex + +In case of indices you can for example use a single statically-allocated memory +across all renderers, to avoid each allocating its own copy: + +@snippet Text.cpp Renderer-allocators-index + +Expected allocator behavior is fully documented in the @ref Renderer() +constructor, note especially the differences when array glyph caches are used. +The @ref Text-RendererCore-allocators section in the @ref RendererCore +documentation shows more use cases with examples for the remaining two +allocators. */ class MAGNUM_TEXT_EXPORT Renderer: public RendererCore { public: @@ -1568,75 +1908,6 @@ that doesn't recreate everything on each text change: @snippet Text-gl.cpp BasicRenderer-usage2 -@subsection Text-BasicRenderer-usage-font-size Font size - -As mentioned in @ref Text-AbstractFont-font-size "AbstractFont class documentation", -the size at which the font is loaded is decoupled from the size at which a -concrete text is rendered. In particular, with a concrete projection matrix, -the size you pass to either @ref render() or to the @ref BasicRenderer() -constructor will always result in the same size of the rendered text, -independently of the size the font was loaded in. Size of the loaded font is -the size at which the glyphs get prerendered into the glyph cache, affecting -visual quality. - -When rendering the text, there are two common approaches --- either setting up -the size to match a global user interface scale, or having the text size -proportional to the window size. The first approach results in e.g. a 12 pt -font matching a 12 pt font in other applications, and is what's shown in the -above snippets. The most straightforward way to achieve that is to set up the -projection matrix size to match actual window pixels, such as @ref Platform::Sdl2Application::windowSize() "Platform::*Application::windowSize()". -If using the regular @ref GlyphCacheGL, for best visual quality it should be -created with the @ref AbstractFont loaded at the same size as the text to be -rendered, although often a double supersampling achieves a crisper result. -I.e., loading the font with 24 pt, but rendering with 12 pt. See below for -@ref Text-BasicRenderer-usage-font-size-dpi "additional considerations for proper DPI awareness". - -The second approach, with text size being relative to the window size, is for -cases where the text is meant to match surrounding art, such as in a game menu. -In this case the projection size is usually something arbitrary that doesn't -match window pixels, and the text point size then has to be relative to that. -For this use case a @ref DistanceFieldGlyphCacheGL is the better match, as it -can provide text at different sizes with minimal quality loss. See its -documentation for details about picking the right font size and other -parameters for best results. - -@subsection Text-BasicRenderer-usage-font-size-dpi DPI awareness - -To achieve crisp rendering and/or text size matching other applications on -HiDPI displays, additional steps need to be taken. There are two separate -concepts for DPI-aware rendering: - -- Interface size --- size at which the interface elements are positioned on - the screen. Often, for simplicity, the interface is using some "virtual - units", so a 12 pt font is still a 12 pt font independently of how the - interface is scaled compared to actual display properties (for example by - setting a global 150% scale in the desktop environment, or by zooming a - browser window). The size used by the @ref BasicRenderer "Renderer*D" - should match these virtual units. -- Framebuffer size --- how many pixels is actually there. If a 192 DPI - display has a 200% interface scale, a 12 pt font would be 32 pixels. But if - it only has a 150% scale, all interface elements will be smaller, and a 12 - pt font would be only 24 pixels. The size used by the @ref AbstractFont and - @ref GlyphCacheGL should be chosen with respect to the actual physical - pixels. - -When using for example @ref Platform::Sdl2Application or other `*Application` -implementations, you usually have three values at your disposal --- -@ref Platform::Sdl2Application::windowSize() "windowSize()", -@ref Platform::Sdl2Application::framebufferSize() "framebufferSize()" and -@ref Platform::Sdl2Application::dpiScaling() "dpiScaling()". Their relation is -documented thoroughly in @ref Platform-Sdl2Application-dpi, for this particular -case a scaled interface size, used instead of window size for the projection, -would be calculated like this: - -@snippet Text-gl.cpp BasicRenderer-dpi-interface-size - -And a multiplier for the @ref AbstractFont and @ref GlyphCacheGL font size like -this. The @ref BasicRenderer "Renderer*D" keeps using the size without this -multiplier. - -@snippet Text-gl.cpp BasicRenderer-dpi-size-multiplier - @section Text-BasicRenderer-required-opengl-functionality Required OpenGL functionality Mutable text rendering requires @gl_extension{ARB,map_buffer_range} on desktop diff --git a/src/Magnum/Text/RendererGL.h b/src/Magnum/Text/RendererGL.h index 8bbcd420b..b57622be9 100644 --- a/src/Magnum/Text/RendererGL.h +++ b/src/Magnum/Text/RendererGL.h @@ -96,6 +96,11 @@ MAGNUM_TEXT_EXPORT Debug& operator<<(Debug& output, RendererGLFlags value); @brief OpenGL text renderer @m_since_latest +Specialization of a @ref Renderer that uploads index and vertex data to a +@ref GL::Mesh. See the @ref Renderer class documentation for information about +setting up an instance of this class, filling it with data and drawing the text +with it. + @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.