diff --git a/engine/src/flutter/benchmarks/paragraph_benchmarks.cc b/engine/src/flutter/benchmarks/paragraph_benchmarks.cc index 455b75e047..7bbfaacba5 100644 --- a/engine/src/flutter/benchmarks/paragraph_benchmarks.cc +++ b/engine/src/flutter/benchmarks/paragraph_benchmarks.cc @@ -146,7 +146,7 @@ static void BM_ParagraphTextBigO(benchmark::State& state) { } BENCHMARK(BM_ParagraphTextBigO) ->RangeMultiplier(20) - ->Range(1 << 4, 1 << 12) + ->Range(1 << 6, 1 << 14) ->Complexity(benchmark::oN); static void BM_ParagraphStylesBigO(benchmark::State& state) { @@ -174,7 +174,7 @@ static void BM_ParagraphStylesBigO(benchmark::State& state) { } BENCHMARK(BM_ParagraphStylesBigO) ->RangeMultiplier(20) - ->Range(1 << 2, 1 << 8) + ->Range(1 << 4, 1 << 12) ->Complexity(benchmark::oN); // ----------------------------------------------------------------------------- diff --git a/engine/src/flutter/src/font_collection.cc b/engine/src/flutter/src/font_collection.cc index 009382d3f2..eb0a793a42 100644 --- a/engine/src/flutter/src/font_collection.cc +++ b/engine/src/flutter/src/font_collection.cc @@ -16,9 +16,13 @@ #include "lib/txt/src/font_collection.h" +#include +#include #include #include #include +#include +#include #include "lib/ftl/logging.h" #include "lib/txt/src/font_skia.h" @@ -52,12 +56,17 @@ FontCollection::FontCollection() { FontCollection(""); } -FontCollection::FontCollection(std::string dir) { - std::vector dirs = {dir}; - FontCollection(std::move(dirs)); +FontCollection::FontCollection(CacheMethod cache_method) { + FontCollection("", cache_method); } -FontCollection::FontCollection(const std::vector& dirs) { +FontCollection::FontCollection(std::string dir, CacheMethod cache_method) { + std::vector dirs = {dir}; + FontCollection(std::move(dirs), cache_method); +} + +FontCollection::FontCollection(const std::vector& dirs, + CacheMethod cache_method) { #ifdef DIRECTORY_FONT_MANAGER_AVAILABLE for (std::string dir : dirs) { if (dir.length() != 0) { @@ -75,6 +84,8 @@ FontCollection::FontCollection(const std::vector& dirs) { family_names_.insert(std::string{str.writable_str()}); } } + + cache_method_ = cache_method; } FontCollection::~FontCollection() = default; @@ -83,6 +94,18 @@ std::set FontCollection::GetFamilyNames() { return family_names_; } +bool FontCollection::HasFamily(const std::string family) const { + return family_names_.count(family) == 1; +} + +void FontCollection::FlushCache() { + minikin_font_collection_map_.clear(); +} + +void FontCollection::SetCacheCapacity(const size_t cap) { + cache_capacity_ = cap; +} + // TODO(garyq): Rework this to use font fallback system. const std::string FontCollection::ProcessFamilyName(const std::string& family) { #ifdef DIRECTORY_FONT_MANAGER_AVAILABLE @@ -101,51 +124,70 @@ const std::string FontCollection::ProcessFamilyName(const std::string& family) { std::shared_ptr FontCollection::GetMinikinFontCollectionForFamily(const std::string& family) { FTL_DCHECK(skia_font_managers_.size() > 0); + std::string processed_family_name = ProcessFamilyName(family); + // Only obtain new font family if the font has changed between runs. + if (cache_method_ == CacheMethod::kNone || + minikin_font_collection_map_.count(processed_family_name) == 0) { + // Ask Skia to resolve a font style set for a font family name. + // FIXME(chinmaygarde): CoreText crashes when passed a null string. This + // seems to be a bug in Skia as SkFontMgr explicitly says passing in + // nullptr gives the default font. + for (sk_sp mgr : skia_font_managers_) { + FTL_DCHECK(mgr != nullptr); + auto font_style_set = mgr->matchFamily(processed_family_name.c_str()); + if (font_style_set != nullptr) { + std::vector minikin_fonts; - // Ask Skia to resolve a font style set for a font family name. - // FIXME(chinmaygarde): The name "Coolvetica" is hardcoded because CoreText - // crashes when passed a null string. This seems to be a bug in Skia as - // SkFontMgr explicitly says passing in nullptr gives the default font. - for (sk_sp mgr : skia_font_managers_) { - FTL_DCHECK(mgr != nullptr); - auto font_style_set = mgr->matchFamily(ProcessFamilyName(family).c_str()); - FTL_DCHECK(font_style_set != nullptr); + // Add fonts to the Minikin font family. + for (int i = 0, style_count = font_style_set->count(); i < style_count; + ++i) { + // Create the skia typeface + auto skia_typeface = + sk_ref_sp(font_style_set->createTypeface(i)); + if (skia_typeface == nullptr) { + continue; + } - std::vector minikin_fonts; + // Create the minikin font from the skia typeface. + minikin::Font minikin_font( + std::make_shared(skia_typeface), + minikin::FontStyle{skia_typeface->fontStyle().weight(), + skia_typeface->isItalic()}); - // Add fonts to the Minikin font family. - for (int i = 0, style_count = font_style_set->count(); i < style_count; - ++i) { - // Create the skia typeface - auto skia_typeface = - sk_ref_sp(font_style_set->createTypeface(i)); - if (skia_typeface == nullptr) { - continue; + minikin_fonts.emplace_back(std::move(minikin_font)); + } + + // Create a Minikin font family. + auto minikin_family = + std::make_shared(std::move(minikin_fonts)); + + // Create a vector of font families for the Minkin font collection. For + // now, we only have one family in our collection. + std::vector> minikin_families = { + minikin_family, + }; + + // Assign the font collection. + minikin_font_collection_map_[processed_family_name] = + std::make_shared(minikin_families); + return minikin_font_collection_map_[processed_family_name]; } - - // Create the minikin font from the skia typeface. - minikin::Font minikin_font( - std::make_shared(skia_typeface), - minikin::FontStyle{skia_typeface->fontStyle().weight(), - skia_typeface->isItalic()}); - - minikin_fonts.emplace_back(std::move(minikin_font)); } - - // Create a Minikin font family. - auto minikin_family = - std::make_shared(std::move(minikin_fonts)); - - // Create a vector of font families for the Minkin font collection. For now, - // we only have one family in our collection. - std::vector> minikin_families = { - minikin_family, - }; - - // Return the font collection. - return std::make_shared(minikin_families); + // Uh oh! Font family not found in any of the font managers! + minikin_font_collection_map_[processed_family_name] = nullptr; } - return nullptr; + + // Maintain LRU and evict old fonts no longer used. + if (cache_method_ == CacheMethod::kLRU) { + lru_tracker_.remove(processed_family_name); + lru_tracker_.push_front(processed_family_name); + if (lru_tracker_.size() > cache_capacity_) { + std::string family_to_evict = lru_tracker_.back(); + lru_tracker_.pop_back(); + minikin_font_collection_map_.erase(family_to_evict); + } + } + return minikin_font_collection_map_[processed_family_name]; } } // namespace txt diff --git a/engine/src/flutter/src/font_collection.h b/engine/src/flutter/src/font_collection.h index 0a05bda86d..efc1dab732 100644 --- a/engine/src/flutter/src/font_collection.h +++ b/engine/src/flutter/src/font_collection.h @@ -19,9 +19,11 @@ #define DEFAULT_FAMILY_NAME "Roboto" +#include #include #include #include +#include #include #include "lib/ftl/macros.h" @@ -35,6 +37,11 @@ namespace txt { class FontCollection { public: + enum CacheMethod { + kNone, + kLRU, // Least Recently Used. + kUnlimited, + }; // Will be deprecated when full compatibility with Flutter Engine is complete. static FontCollection& GetDefaultFontCollection(); @@ -47,9 +54,13 @@ class FontCollection { std::shared_ptr GetMinikinFontCollectionForFamily( const std::string& family); - FontCollection(const std::vector& dirs); + FontCollection(const std::vector& dirs, + CacheMethod cache_method = CacheMethod::kUnlimited); - FontCollection(std::string dir); + FontCollection(std::string dir, + CacheMethod cache_method = CacheMethod::kUnlimited); + + FontCollection(CacheMethod cache_method); FontCollection(); @@ -58,10 +69,25 @@ class FontCollection { // Provides a set of all available family names. std::set GetFamilyNames(); + bool HasFamily(const std::string family) const; + + void FlushCache(); + + void SetCacheCapacity(const size_t cap); + private: std::vector> skia_font_managers_; // Cache the names because GetFamilyNames() can be frequently called. std::set family_names_; + CacheMethod cache_method_ = CacheMethod::kUnlimited; + std::list lru_tracker_; + size_t cache_capacity_ = 20; + + // Cache minikin font collections to prevent slow disk reads. + // TODO(garyq): Implement optional low-memory optimized system to prevent + // fonts building up in memory. + std::unordered_map> + minikin_font_collection_map_; FRIEND_TEST(FontCollection, HasDefaultRegistrations); FRIEND_TEST(FontCollection, GetMinikinFontCollections); diff --git a/engine/src/flutter/src/paragraph.cc b/engine/src/flutter/src/paragraph.cc index 764291ef48..848bda5d54 100644 --- a/engine/src/flutter/src/paragraph.cc +++ b/engine/src/flutter/src/paragraph.cc @@ -132,15 +132,11 @@ void Paragraph::AddRunsToLineBreaker( minikin::MinikinPaint paint; for (size_t i = 0; i < runs_.size(); ++i) { auto run = runs_.GetRun(i); - // Only obtain new font family if the font has changed between runs. - if (collection_map.count(run.style.font_family) == 0) { - collection_map[run.style.font_family] = - font_collection_->GetMinikinFontCollectionForFamily( - run.style.font_family); - } GetFontAndMinikinPaint(run.style, &font, &paint); - breaker_.addStyleRun(&paint, collection_map.at(run.style.font_family), font, - run.start, run.end, false); + breaker_.addStyleRun(&paint, + font_collection_->GetMinikinFontCollectionForFamily( + run.style.font_family), + font, run.start, run.end, false); } } @@ -240,7 +236,8 @@ void Paragraph::Layout(double width, bool force) { int bidiFlags = 0; layout.doLayout(text_.data(), layout_start, layout_end - layout_start, text_.size(), bidiFlags, font, minikin_paint, - collection_map.at(run.style.font_family)); + font_collection_->GetMinikinFontCollectionForFamily( + run.style.font_family)); const size_t glyph_count = layout.nGlyphs(); size_t blob_start = 0; // Each blob. @@ -445,6 +442,8 @@ void Paragraph::SetFontCollection(FontCollection* font_collection) { font_collection_ = font_collection; } +// The x,y coordinates will be the very top left corner of the rendered +// paragraph. void Paragraph::Paint(SkCanvas* canvas, double x, double y) { for (const auto& record : records_) { SkPaint paint;