Move font caches to FontCollection and multiple eviction schemes. ~x200 speed improvement.

Change-Id: I1e2f940ddb36f9ac51cc95c42c2f121140ff3f6f
This commit is contained in:
Gary Qian
2017-06-30 12:05:11 -07:00
parent 4c93f0e976
commit 11580cea63
4 changed files with 123 additions and 56 deletions

View File

@@ -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);
// -----------------------------------------------------------------------------

View File

@@ -16,9 +16,13 @@
#include "lib/txt/src/font_collection.h"
#include <list>
#include <memory>
#include <mutex>
#include <set>
#include <string>
#include <unordered_map>
#include <vector>
#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<std::string> dirs = {dir};
FontCollection(std::move(dirs));
FontCollection::FontCollection(CacheMethod cache_method) {
FontCollection("", cache_method);
}
FontCollection::FontCollection(const std::vector<std::string>& dirs) {
FontCollection::FontCollection(std::string dir, CacheMethod cache_method) {
std::vector<std::string> dirs = {dir};
FontCollection(std::move(dirs), cache_method);
}
FontCollection::FontCollection(const std::vector<std::string>& 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<std::string>& dirs) {
family_names_.insert(std::string{str.writable_str()});
}
}
cache_method_ = cache_method;
}
FontCollection::~FontCollection() = default;
@@ -83,6 +94,18 @@ std::set<std::string> 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<minikin::FontCollection>
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<SkFontMgr> 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::Font> 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<SkFontMgr> 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<SkTypeface>(font_style_set->createTypeface(i));
if (skia_typeface == nullptr) {
continue;
}
std::vector<minikin::Font> minikin_fonts;
// Create the minikin font from the skia typeface.
minikin::Font minikin_font(
std::make_shared<FontSkia>(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<SkTypeface>(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<minikin::FontFamily>(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<std::shared_ptr<minikin::FontFamily>> minikin_families = {
minikin_family,
};
// Assign the font collection.
minikin_font_collection_map_[processed_family_name] =
std::make_shared<minikin::FontCollection>(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<FontSkia>(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<minikin::FontFamily>(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<std::shared_ptr<minikin::FontFamily>> minikin_families = {
minikin_family,
};
// Return the font collection.
return std::make_shared<minikin::FontCollection>(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

View File

@@ -19,9 +19,11 @@
#define DEFAULT_FAMILY_NAME "Roboto"
#include <list>
#include <memory>
#include <set>
#include <string>
#include <unordered_map>
#include <vector>
#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<minikin::FontCollection> GetMinikinFontCollectionForFamily(
const std::string& family);
FontCollection(const std::vector<std::string>& dirs);
FontCollection(const std::vector<std::string>& 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<std::string> GetFamilyNames();
bool HasFamily(const std::string family) const;
void FlushCache();
void SetCacheCapacity(const size_t cap);
private:
std::vector<sk_sp<SkFontMgr>> skia_font_managers_;
// Cache the names because GetFamilyNames() can be frequently called.
std::set<std::string> family_names_;
CacheMethod cache_method_ = CacheMethod::kUnlimited;
std::list<std::string> 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<std::string, std::shared_ptr<minikin::FontCollection>>
minikin_font_collection_map_;
FRIEND_TEST(FontCollection, HasDefaultRegistrations);
FRIEND_TEST(FontCollection, GetMinikinFontCollections);

View File

@@ -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;