From 68514d626c917bbd6bc9e57291624448e533802c Mon Sep 17 00:00:00 2001 From: Tom Poole Date: Tue, 15 Mar 2022 11:41:24 +0000 Subject: [PATCH] Graphics: Added a global GlyphArrangement cache --- .../contexts/juce_GraphicsContext.cpp | 270 ++++++++++++++---- modules/juce_graphics/fonts/juce_Font.cpp | 22 +- modules/juce_graphics/fonts/juce_Font.h | 9 +- 3 files changed, 244 insertions(+), 57 deletions(-) diff --git a/modules/juce_graphics/contexts/juce_GraphicsContext.cpp b/modules/juce_graphics/contexts/juce_GraphicsContext.cpp index 3cd735f372..35231dc792 100644 --- a/modules/juce_graphics/contexts/juce_GraphicsContext.cpp +++ b/modules/juce_graphics/contexts/juce_GraphicsContext.cpp @@ -19,8 +19,108 @@ namespace juce { +struct GraphicsFontHelpers +{ + static auto compareFont (const Font& a, const Font& b) { return Font::compare (a, b); } +}; + +static auto operator< (const Font& a, const Font& b) +{ + return GraphicsFontHelpers::compareFont (a, b); +} + +template +static auto operator< (const Rectangle& a, const Rectangle& b) +{ + const auto tie = [] (auto& t) { return std::make_tuple (t.getX(), t.getY(), t.getWidth(), t.getHeight()); }; + return tie (a) < tie (b); +} + +static auto operator< (const Justification& a, const Justification& b) +{ + return a.getFlags() < b.getFlags(); +} + +//============================================================================== namespace { + struct ConfiguredArrangement + { + void draw (const Graphics& g) const { arrangement.draw (g, transform); } + + GlyphArrangement arrangement; + AffineTransform transform; + }; + + template + class GlyphArrangementCache final : public DeletedAtShutdown + { + public: + GlyphArrangementCache() = default; + + ~GlyphArrangementCache() override + { + clearSingletonInstance(); + } + + template + void draw (const Graphics& g, ArrangementArgs&& args, ConfigureArrangement&& configureArrangement) + { + const ScopedTryLock stl (lock); + + if (! stl.isLocked()) + { + configureArrangement (args).draw (g); + return; + } + + const auto cached = [&] + { + const auto iter = cache.find (args); + + if (iter != cache.end()) + { + if (iter->second.cachePosition != cacheOrder.begin()) + cacheOrder.splice (cacheOrder.begin(), cacheOrder, iter->second.cachePosition); + + return iter; + } + + auto result = cache.emplace (std::move (args), CachedGlyphArrangement { configureArrangement (args) }).first; + cacheOrder.push_front (result); + return result; + }(); + + cached->second.cachePosition = cacheOrder.begin(); + cached->second.configured.draw (g); + + while (cache.size() > cacheSize) + { + cache.erase (cacheOrder.back()); + cacheOrder.pop_back(); + } + } + + JUCE_DECLARE_SINGLETON (GlyphArrangementCache, false) + + private: + struct CachedGlyphArrangement + { + using CachePtr = typename std::map::const_iterator; + ConfiguredArrangement configured; + typename std::list::const_iterator cachePosition; + }; + + static constexpr size_t cacheSize = 128; + std::map cache; + std::list cacheOrder; + CriticalSection lock; + }; + + template + juce::SingletonHolder, juce::CriticalSection, false> GlyphArrangementCache::singletonHolder; + + //============================================================================== template Rectangle coordsToRectangle (Type x, Type y, Type w, Type h) noexcept { @@ -224,67 +324,120 @@ Font Graphics::getCurrentFont() const void Graphics::drawSingleLineText (const String& text, const int startX, const int baselineY, Justification justification) const { - if (text.isNotEmpty()) - { - // Don't pass any vertical placement flags to this method - they'll be ignored. - jassert (justification.getOnlyVerticalFlags() == 0); + if (text.isEmpty()) + return; + + // Don't pass any vertical placement flags to this method - they'll be ignored. + jassert (justification.getOnlyVerticalFlags() == 0); - auto flags = justification.getOnlyHorizontalFlags(); + auto flags = justification.getOnlyHorizontalFlags(); - if (flags == Justification::right && startX < context.getClipBounds().getX()) - return; + if (flags == Justification::right && startX < context.getClipBounds().getX()) + return; - if (flags == Justification::left && startX > context.getClipBounds().getRight()) - return; + if (flags == Justification::left && startX > context.getClipBounds().getRight()) + return; - GlyphArrangement arr; - arr.addLineOfText (context.getFont(), text, (float) startX, (float) baselineY); + struct ArrangementArgs + { + auto tie() const noexcept { return std::tie (font, text, startX, baselineY); } + bool operator< (const ArrangementArgs& other) const { return tie() < other.tie(); } + + const Font font; + const String text; + const int startX, baselineY, flags; + }; - if (flags != Justification::left) + auto configureArrangement = [] (const ArrangementArgs& args) + { + AffineTransform transform; + GlyphArrangement arrangement; + arrangement.addLineOfText (args.font, args.text, (float) args.startX, (float) args.baselineY); + + if (args.flags != Justification::left) { - auto w = arr.getBoundingBox (0, -1, true).getWidth(); + auto w = arrangement.getBoundingBox (0, -1, true).getWidth(); - if ((flags & (Justification::horizontallyCentred | Justification::horizontallyJustified)) != 0) + if ((args.flags & (Justification::horizontallyCentred | Justification::horizontallyJustified)) != 0) w /= 2.0f; - arr.draw (*this, AffineTransform::translation (-w, 0)); + transform = AffineTransform::translation (-w, 0); } - else - { - arr.draw (*this); - } - } + + return ConfiguredArrangement { std::move (arrangement), std::move (transform) }; + }; + + GlyphArrangementCache::getInstance()->draw (*this, + { context.getFont(), text, startX, baselineY, flags }, + std::move (configureArrangement)); } void Graphics::drawMultiLineText (const String& text, const int startX, const int baselineY, const int maximumLineWidth, Justification justification, const float leading) const { - if (text.isNotEmpty() - && startX < context.getClipBounds().getRight()) + if (text.isEmpty() || startX >= context.getClipBounds().getRight()) + return; + + struct ArrangementArgs { - GlyphArrangement arr; - arr.addJustifiedText (context.getFont(), text, - (float) startX, (float) baselineY, (float) maximumLineWidth, - justification, leading); - arr.draw (*this); - } + auto tie() const noexcept { return std::tie (font, text, startX, baselineY, maximumLineWidth, justification, leading); } + bool operator< (const ArrangementArgs& other) const { return tie() < other.tie(); } + + const Font font; + const String text; + const int startX, baselineY, maximumLineWidth; + const Justification justification; + const float leading; + }; + + auto configureArrangement = [] (const ArrangementArgs& args) + { + GlyphArrangement arrangement; + arrangement.addJustifiedText (args.font, args.text, + (float) args.startX, (float) args.baselineY, (float) args.maximumLineWidth, + args.justification, args.leading); + return ConfiguredArrangement { std::move (arrangement) }; + }; + + GlyphArrangementCache::getInstance()->draw (*this, + { context.getFont(), text, startX, baselineY, maximumLineWidth, justification, leading }, + std::move (configureArrangement)); } void Graphics::drawText (const String& text, Rectangle area, Justification justificationType, bool useEllipsesIfTooBig) const { - if (text.isNotEmpty() && context.clipRegionIntersects (area.getSmallestIntegerContainer())) + if (text.isEmpty() || ! context.clipRegionIntersects (area.getSmallestIntegerContainer())) + return; + + struct ArrangementArgs { - GlyphArrangement arr; - arr.addCurtailedLineOfText (context.getFont(), text, 0.0f, 0.0f, - area.getWidth(), useEllipsesIfTooBig); - - arr.justifyGlyphs (0, arr.getNumGlyphs(), - area.getX(), area.getY(), area.getWidth(), area.getHeight(), - justificationType); - arr.draw (*this); - } + auto tie() const noexcept { return std::tie (font, text, area, justificationType, useEllipsesIfTooBig); } + bool operator< (const ArrangementArgs& other) const { return tie() < other.tie(); } + + const Font font; + const String text; + const Rectangle area; + const Justification justificationType; + const bool useEllipsesIfTooBig; + }; + + auto configureArrangement = [] (const ArrangementArgs& args) + { + GlyphArrangement arrangement; + arrangement.addCurtailedLineOfText (args.font, args.text, 0.0f, 0.0f, + args.area.getWidth(), args.useEllipsesIfTooBig); + + arrangement.justifyGlyphs (0, arrangement.getNumGlyphs(), + args.area.getX(), args.area.getY(), args.area.getWidth(), args.area.getHeight(), + args.justificationType); + return ConfiguredArrangement { std::move (arrangement) }; + }; + + GlyphArrangementCache::getInstance()->draw (*this, + { context.getFont(), text, area, justificationType, useEllipsesIfTooBig }, + std::move (configureArrangement)); } void Graphics::drawText (const String& text, Rectangle area, @@ -304,18 +457,37 @@ void Graphics::drawFittedText (const String& text, Rectangle area, const int maximumNumberOfLines, const float minimumHorizontalScale) const { - if (text.isNotEmpty() && (! area.isEmpty()) && context.clipRegionIntersects (area)) + if (text.isEmpty() || area.isEmpty() || ! context.clipRegionIntersects (area)) + return; + + struct ArrangementArgs { - GlyphArrangement arr; - arr.addFittedText (context.getFont(), text, - (float) area.getX(), (float) area.getY(), - (float) area.getWidth(), (float) area.getHeight(), - justification, - maximumNumberOfLines, - minimumHorizontalScale); - - arr.draw (*this); - } + auto tie() const noexcept { return std::tie (font, text, area, justification, maximumNumberOfLines, minimumHorizontalScale); } + bool operator< (const ArrangementArgs& other) const noexcept { return tie() < other.tie(); } + + const Font font; + const String text; + const Rectangle area; + const Justification justification; + const int maximumNumberOfLines; + const float minimumHorizontalScale; + }; + + auto configureArrangement = [] (const ArrangementArgs& args) + { + GlyphArrangement arrangement; + arrangement.addFittedText (args.font, args.text, + args.area.getX(), args.area.getY(), + args.area.getWidth(), args.area.getHeight(), + args.justification, + args.maximumNumberOfLines, + args.minimumHorizontalScale); + return ConfiguredArrangement { std::move (arrangement) }; + }; + + GlyphArrangementCache::getInstance()->draw (*this, + { context.getFont(), text, area.toFloat(), justification, maximumNumberOfLines, minimumHorizontalScale }, + std::move (configureArrangement)); } void Graphics::drawFittedText (const String& text, int x, int y, int width, int height, diff --git a/modules/juce_graphics/fonts/juce_Font.cpp b/modules/juce_graphics/fonts/juce_Font.cpp index 0a0985a102..6b1ebdbc98 100644 --- a/modules/juce_graphics/fonts/juce_Font.cpp +++ b/modules/juce_graphics/fonts/juce_Font.cpp @@ -236,14 +236,19 @@ public: { } + auto tie() const + { + return std::tie (height, underline, horizontalScale, kerning, typefaceName, typefaceStyle); + } + bool operator== (const SharedFontInternal& other) const noexcept { - return height == other.height - && underline == other.underline - && horizontalScale == other.horizontalScale - && kerning == other.kerning - && typefaceName == other.typefaceName - && typefaceStyle == other.typefaceStyle; + return tie() == other.tie(); + } + + bool operator< (const SharedFontInternal& other) const noexcept + { + return tie() < other.tie(); } /* The typeface and ascent data members may be read/set from multiple threads @@ -411,6 +416,11 @@ bool Font::operator!= (const Font& other) const noexcept return ! operator== (other); } +bool Font::compare (const Font& a, const Font& b) noexcept +{ + return *a.font < *b.font; +} + void Font::dupeInternalIfShared() { if (font->getReferenceCount() > 1) diff --git a/modules/juce_graphics/fonts/juce_Font.h b/modules/juce_graphics/fonts/juce_Font.h index b9b95547be..399444889d 100644 --- a/modules/juce_graphics/fonts/juce_Font.h +++ b/modules/juce_graphics/fonts/juce_Font.h @@ -464,12 +464,17 @@ public: private: //============================================================================== - class SharedFontInternal; - ReferenceCountedObjectPtr font; + static bool compare (const Font&, const Font&) noexcept; + void dupeInternalIfShared(); void checkTypefaceSuitability(); float getHeightToPointsFactor() const; + friend struct GraphicsFontHelpers; + + class SharedFontInternal; + ReferenceCountedObjectPtr font; + JUCE_LEAK_DETECTOR (Font) };