Paragraph cache: caching the shaped results only.

That makes layout phase 10 times faster (since the shaping takes 90% of it).

LRU cache is attached to the FontCollection object and has the same life time.
Currently it has hardcoded limit on the entry numbers (128).
One the number reached, the least recently used element is removed from the cache
to free the space for a new one.

Change-Id: I597e334422614e33715d7a9ed13acf7b1f9cd0e4
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/230755
Commit-Queue: Julia Lavrova <jlavrova@google.com>
Reviewed-by: Derek Sollenberger <djsollen@google.com>
Reviewed-by: Julia Lavrova <jlavrova@google.com>
This commit is contained in:
Julia Lavrova 2019-07-30 13:32:08 -04:00 committed by Skia Commit-Bot
parent c4abade253
commit b7b0b3ad53
12 changed files with 475 additions and 296 deletions

View File

@ -7,11 +7,14 @@
#include "include/core/SkFontMgr.h"
#include "include/core/SkRefCnt.h"
#include "include/private/SkTHash.h"
#include "modules/skparagraph/include/ParagraphCache.h"
#include "modules/skparagraph/include/TextStyle.h"
namespace skia {
namespace textlayout {
class TextStyle;
class Paragraph;
class FontCollection : public SkRefCnt {
public:
FontCollection();
@ -35,6 +38,8 @@ public:
void disableFontFallback();
bool fontFallbackEnabled() { return fEnableFontFallback; }
ParagraphCache* getParagraphCache() { return &fParagraphCache; }
private:
std::vector<sk_sp<SkFontMgr>> getFontManagerOrder() const;
@ -61,7 +66,9 @@ private:
sk_sp<SkFontMgr> fAssetFontManager;
sk_sp<SkFontMgr> fDynamicFontManager;
sk_sp<SkFontMgr> fTestFontManager;
SkString fDefaultFamilyName;
const char* fDefaultFamilyName;
ParagraphCache fParagraphCache;
};
} // namespace textlayout
} // namespace skia

View File

@ -0,0 +1,85 @@
// Copyright 2019 Google LLC.
#ifndef ParagraphCache_DEFINED
#define ParagraphCache_DEFINED
#include "include/private/SkMutex.h"
#include "src/core/SkLRUCache.h"
#define PARAGRAPH_CACHE_STATS
namespace skia {
namespace textlayout {
struct Measurement {
SkScalar fAlphabeticBaseline;
SkScalar fIdeographicBaseline;
SkScalar fHeight;
SkScalar fWidth;
SkScalar fMaxIntrinsicWidth;
SkScalar fMinIntrinsicWidth;
};
enum InternalState {
kUnknown = 0,
kShaped = 1,
kClusterized = 2,
kMarked = 3,
kLineBroken = 4,
kFormatted = 5,
kDrawn = 6
};
class ParagraphImpl;
class ParagraphCacheKey;
class ParagraphCacheValue;
bool operator==(const ParagraphCacheKey& a, const ParagraphCacheKey& b);
class ParagraphCache {
public:
ParagraphCache();
~ParagraphCache();
void abandon();
void reset();
bool updateParagraph(ParagraphImpl* paragraph);
bool findParagraph(ParagraphImpl* paragraph);
// For testing
void setChecker(std::function<void(ParagraphImpl* impl, const char*, bool)> checker) {
fChecker = std::move(checker);
}
void printStatistics();
void turnOn(bool value) { fCacheIsOn = value; }
int count() { return fLRUCacheMap.count(); }
private:
struct Entry;
void updateFrom(const ParagraphImpl* paragraph, Entry* entry);
void updateTo(ParagraphImpl* paragraph, const Entry* entry);
mutable SkMutex fParagraphMutex;
std::function<void(ParagraphImpl* impl, const char*, bool)> fChecker;
static const int kMaxEntries = 128;
struct KeyHash {
uint32_t mix(uint32_t hash, uint32_t data) const;
uint32_t operator()(const ParagraphCacheKey& key) const;
};
SkLRUCache<ParagraphCacheKey, std::unique_ptr<Entry>, KeyHash> fLRUCacheMap;
bool fCacheIsOn;
#ifdef PARAGRAPH_CACHE_STATS
int fTotalRequests;
int fCacheMisses;
int fHashMisses; // cache hit but hash table missed
#endif
};
} // namespace textlayout
} // namespace skia
#endif // ParagraphCache_DEFINED

View File

@ -12,6 +12,7 @@ skparagraph_public = [
"$_include/FontCollection.h",
"$_include/Paragraph.h",
"$_include/ParagraphBuilder.h",
"$_include/ParagraphCache.h",
"$_include/DartTypes.h",
"$_include/TypefaceFontProvider.h",
"$_utils/TestFontCollection.h",
@ -24,7 +25,6 @@ skparagraph_sources = [
"$_src/Iterators.h",
"$_src/ParagraphBuilderImpl.h",
"$_src/ParagraphBuilderImpl.cpp",
"$_src/ParagraphCache.h",
"$_src/ParagraphCache.cpp",
"$_src/ParagraphImpl.h",
"$_src/ParagraphImpl.cpp",

View File

@ -1,5 +1,8 @@
// Copyright 2019 Google LLC.
#include "include/core/SkTypeface.h"
#include "modules/skparagraph/include/FontCollection.h"
#include "modules/skparagraph/include/Paragraph.h"
#include "modules/skparagraph/src/ParagraphImpl.h"
namespace skia {
namespace textlayout {
@ -93,7 +96,7 @@ sk_sp<SkTypeface> FontCollection::matchTypeface(const char familyName[], SkFontS
sk_sp<SkTypeface> FontCollection::matchDefaultTypeface(SkFontStyle fontStyle) {
// Look inside the font collections cache first
FamilyKey familyKey(fDefaultFamilyName.c_str(), "en", fontStyle);
FamilyKey familyKey(fDefaultFamilyName, "en", fontStyle);
auto found = fTypefaces.find(familyKey);
if (found) {
return *found;
@ -101,7 +104,7 @@ sk_sp<SkTypeface> FontCollection::matchDefaultTypeface(SkFontStyle fontStyle) {
sk_sp<SkTypeface> typeface = nullptr;
for (const auto& manager : this->getFontManagerOrder()) {
SkFontStyleSet* set = manager->matchFamily(fDefaultFamilyName.c_str());
SkFontStyleSet* set = manager->matchFamily(fDefaultFamilyName);
if (nullptr == set || set->count() == 0) {
continue;
}
@ -137,7 +140,7 @@ sk_sp<SkTypeface> FontCollection::defaultFallback(SkUnichar unicode, SkFontStyle
if (fDefaultFontManager == nullptr) {
return nullptr;
}
auto result = fDefaultFontManager->matchFamilyStyle(fDefaultFamilyName.c_str(), fontStyle);
auto result = fDefaultFontManager->matchFamilyStyle(fDefaultFamilyName, fontStyle);
return sk_ref_sp<SkTypeface>(result);
}

View File

@ -232,13 +232,11 @@ SkUnichar FontResolver::firstUnresolved() {
return fCodepoints[index];
}
void FontResolver::findAllFontsForAllStyledBlocks(SkSpan<const char> utf8,
SkSpan<Block> styles,
sk_sp<FontCollection> fontCollection) {
fFontCollection = fontCollection;
fStyles = styles;
fText = utf8;
fTextRange = TextRange(0, utf8.size());
void FontResolver::findAllFontsForAllStyledBlocks(ParagraphImpl* master) {
fFontCollection = master->fontCollection();
fStyles = master->styles();
fText = master->text();
fTextRange = TextRange(0, fText.size());
Block combined;
for (auto& block : fStyles) {
@ -262,7 +260,7 @@ void FontResolver::findAllFontsForAllStyledBlocks(SkSpan<const char> utf8,
fFontSwitches.reset();
FontDescr* prev = nullptr;
for (auto& ch : utf8) {
for (auto& ch : fText) {
if (fFontSwitches.count() == fFontMapping.count()) {
// Checked all
break;

View File

@ -33,12 +33,10 @@ public:
FontResolver() = default;
~FontResolver() = default;
void findAllFontsForAllStyledBlocks(SkSpan<const char> utf8,
SkSpan<Block> styles,
sk_sp<FontCollection> fontCollection);
void findAllFontsForAllStyledBlocks(ParagraphImpl* master);
bool findNext(const char* codepoint, SkFont* font, SkScalar* height);
SkTArray<FontDescr>& switches() { return fFontSwitches; }
const SkTArray<FontDescr>& switches() const { return fFontSwitches; }
private:
void findAllFontsForStyledBlock(const TextStyle& style, TextRange textRange);

View File

@ -1,104 +1,69 @@
// Copyright 2019 Google LLC.
#include "modules/skparagraph/src/ParagraphCache.h"
#include "modules/skparagraph/include/ParagraphCache.h"
#include "modules/skparagraph/src/ParagraphImpl.h"
namespace skia {
namespace textlayout {
// Just the flutter input for now
// TODO: We don't really need to copy anything...
ParagraphCacheKey::ParagraphCacheKey(ParagraphImpl* paragraph)
: fText(paragraph->fText)
class ParagraphCacheKey {
public:
ParagraphCacheKey(const ParagraphImpl* paragraph)
: fText(paragraph->fText.c_str(), paragraph->fText.size())
, fFontSwitches(paragraph->switches())
, fTextStyles(paragraph->fTextStyles)
, fParagraphStyle(paragraph->paragraphStyle()) { }
// TODO: copying clusters and runs for now (there are minor things changed on formatting)
ParagraphCacheValue::ParagraphCacheValue(ParagraphImpl* paragraph)
SkString fText;
SkTArray<FontDescr> fFontSwitches;
SkTArray<Block, true> fTextStyles;
ParagraphStyle fParagraphStyle;
};
class ParagraphCacheValue {
public:
ParagraphCacheValue(const ParagraphImpl* paragraph)
: fKey(ParagraphCacheKey(paragraph))
, fInternalState(paragraph->state())
, fRuns(paragraph->fRuns)
, fClusters(paragraph->fClusters)
, fMeasurement(paragraph->measurement())
, fPicture(paragraph->fPicture) { }
, fClusters(paragraph->fClusters) { }
bool ParagraphCache::findParagraph(ParagraphImpl* paragraph) {
// Input == key
ParagraphCacheKey fKey;
SkAutoMutexExclusive lock(fParagraphMutex);
if (this->count() == 0) {
return false;
}
// Shaped results:
InternalState fInternalState;
SkTArray<Run> fRuns;
SkTArray<Cluster, true> fClusters;
SkTArray<RunShifts, true> fRunShifts;
};
ParagraphCacheKey key(paragraph);
auto found = this->find(key);
if (found == nullptr) {
fChecker(paragraph, "findParagraph", false);
return false;
}
paragraph->fRuns.reset();
paragraph->fRuns = found->fRuns;
for (auto& run : paragraph->fRuns) {
run.setMaster(paragraph);
}
paragraph->fClusters.reset();
paragraph->fClusters = found->fClusters;
for (auto& cluster : paragraph->fClusters) {
cluster.setMaster(paragraph);
}
paragraph->fLines.reset();
for (auto& line : found->fLines) {
paragraph->fLines.push_back(line);
paragraph->fLines.back().setMaster(paragraph);
}
paragraph->fState = found->fInternalState;
paragraph->setMeasurement(found->fMeasurement);
paragraph->fOldWidth = found->fMeasurement.fWidth;
paragraph->fOldHeight = found->fMeasurement.fHeight;
paragraph->fPicture = found->fPicture;
fChecker(paragraph, "findParagraph", true);
return true;
uint32_t ParagraphCache::KeyHash::mix(uint32_t hash, uint32_t data) const {
hash += data;
hash += (hash << 10);
hash ^= (hash >> 6);
return hash;
}
uint32_t ParagraphCache::KeyHash::operator()(const ParagraphCacheKey& key) const {
uint32_t hash = 0;
for (auto& fd : key.fFontSwitches) {
hash = mix(hash, SkGoodHash()(fd.fStart));
hash = mix(hash, SkGoodHash()(fd.fFont.getSize()));
void ParagraphCache::addParagraph(ParagraphImpl* paragraph) {
SkAutoMutexExclusive lock(fParagraphMutex);
auto value = new ParagraphCacheValue(paragraph);
this->add(value);
fChecker(paragraph, "addParagraph1", true);
}
void ParagraphCache::updateParagraph(ParagraphImpl* paragraph) {
SkAutoMutexExclusive lock(fParagraphMutex);
ParagraphCacheKey key(paragraph);
auto found = this->find(key);
if (found != nullptr) {
found->fInternalState = paragraph->fState;
found->fMeasurement = paragraph->measurement();
found->fLines = paragraph->fLines;
for (size_t i = 0; i < paragraph->fRuns.size(); ++i) {
auto& run = paragraph->fRuns[i];
if (run.fSpaced) {
found->fRuns[i] = run;
}
if (fd.fFont.getTypeface() != nullptr) {
SkString name;
fd.fFont.getTypeface()->getFamilyName(&name);
hash = mix(hash, SkGoodHash()(name));
hash = mix(hash, SkGoodHash()(fd.fFont.getTypeface()->fontStyle()));
}
found->fPicture = paragraph->fPicture;
fChecker(paragraph, "updateParagraph", true);
} else {
auto value = new ParagraphCacheValue(paragraph);
this->add(value);
fChecker(paragraph, "addParagraph2", true);
}
}
const ParagraphCacheKey& LookupTrait::GetKey(const ParagraphCacheValue& paragraph) {
return paragraph.fKey;
for (auto& ts : key.fTextStyles) {
hash = mix(hash, SkGoodHash()(ts.fStyle.getLetterSpacing()));
hash = mix(hash, SkGoodHash()(ts.fStyle.getWordSpacing()));
hash = mix(hash, SkGoodHash()(ts.fRange));
}
hash = mix(hash, SkGoodHash()(key.fText));
return hash;
}
bool operator==(const ParagraphCacheKey& a, const ParagraphCacheKey& b) {
@ -116,7 +81,7 @@ bool operator==(const ParagraphCacheKey& a, const ParagraphCacheKey& b) {
}
if (a.fParagraphStyle.getMaxLines() != b.fParagraphStyle.getMaxLines()) {
// TODO: this is too strong, but at least we will not lose lines
// This is too strong, but at least we will not lose lines
return false;
}
@ -151,55 +116,129 @@ bool operator==(const ParagraphCacheKey& a, const ParagraphCacheKey& b) {
return true;
}
uint32_t LookupTrait::mix(uint32_t hash, uint32_t data) {
hash += data;
hash += (hash << 10);
hash ^= (hash >> 6);
return hash;
}
struct ParagraphCache::Entry {
uint32_t LookupTrait::Hash(const ParagraphCacheKey& key) {
uint32_t hash = 0;
for (auto& fd : key.fFontSwitches) {
hash = mix(hash, SkGoodHash()(fd.fStart));
hash = mix(hash, SkGoodHash()(fd.fFont.getSize()));
Entry(ParagraphCacheValue* value) : fValue(value) {}
ParagraphCacheValue* fValue;
};
if (fd.fFont.getTypeface() != nullptr) {
SkString name;
fd.fFont.getTypeface()->getFamilyName(&name);
hash = mix(hash, SkGoodHash()(name));
hash = mix(hash, SkGoodHash()(fd.fFont.getTypeface()->fontStyle()));
ParagraphCache::ParagraphCache()
: fChecker([](ParagraphImpl* impl, const char*, bool){ })
, fLRUCacheMap(kMaxEntries)
, fCacheIsOn(true)
#ifdef PARAGRAPH_CACHE_STATS
, fTotalRequests(0)
, fCacheMisses(0)
, fHashMisses(0)
#endif
{ }
ParagraphCache::~ParagraphCache() { }
void ParagraphCache::updateFrom(const ParagraphImpl* paragraph, Entry* entry) {
entry->fValue->fInternalState = paragraph->state();
entry->fValue->fRunShifts = paragraph->fRunShifts;
for (size_t i = 0; i < paragraph->fRuns.size(); ++i) {
auto& run = paragraph->fRuns[i];
if (run.fSpaced) {
entry->fValue->fRuns[i] = run;
}
}
for (auto& ts : key.fTextStyles) {
hash = mix(hash, SkGoodHash()(ts.fStyle.getLetterSpacing()));
hash = mix(hash, SkGoodHash()(ts.fStyle.getWordSpacing()));
}
hash = mix(hash, SkGoodHash()(key.fText));
return hash;
}
void ParagraphCache::printCache(const char* title) {
void ParagraphCache::updateTo(ParagraphImpl* paragraph, const Entry* entry) {
paragraph->fRuns.reset();
paragraph->fRuns = entry->fValue->fRuns;
for (auto& run : paragraph->fRuns) {
run.setMaster(paragraph);
}
SkDebugf("\n\n%s\n", title);
SkTDynamicHash<ParagraphCacheValue, ParagraphCacheKey, LookupTrait>::Iter iter(this);
while (!iter.done()) {
ParagraphCacheValue* v = &*iter;
const ParagraphCacheKey& k = LookupTrait::GetKey(*v);
SkDebugf("key: '%s' runs(%d) clusters(%d)\n", k.fText.c_str(), v->fRuns.size(), v->fClusters.size());
++iter;
paragraph->fClusters.reset();
paragraph->fClusters = entry->fValue->fClusters;
for (auto& cluster : paragraph->fClusters) {
cluster.setMaster(paragraph);
}
paragraph->fRunShifts.reset();
for (auto& runShift : entry->fValue->fRunShifts) {
paragraph->fRunShifts.push_back(runShift);
}
paragraph->fState = entry->fValue->fInternalState;
}
void ParagraphCache::printStatistics() {
SkDebugf("--- Paragraph Cache ---\n");
SkDebugf("Total requests: %d\n", fTotalRequests);
SkDebugf("Cache misses: %d\n", fCacheMisses);
SkDebugf("Cache miss %%: %f\n", (fTotalRequests > 0) ? 100.f * fCacheMisses / fTotalRequests : 0.f);
int cacheHits = fTotalRequests - fCacheMisses;
SkDebugf("Hash miss %%: %f\n", (cacheHits > 0) ? 100.f * fHashMisses / cacheHits : 0.f);
SkDebugf("---------------------\n");
}
void ParagraphCache::abandon() {
SkAutoMutexExclusive lock(fParagraphMutex);
fLRUCacheMap.foreach([](std::unique_ptr<Entry>* e) {
});
this->reset();
}
void ParagraphCache::reset() {
SkAutoMutexExclusive lock(fParagraphMutex);
#ifdef PARAGRAPH_CACHE_STATS
fTotalRequests = 0;
fCacheMisses = 0;
fHashMisses = 0;
#endif
fLRUCacheMap.reset();
}
bool ParagraphCache::findParagraph(ParagraphImpl* paragraph) {
if (!fCacheIsOn) {
return false;
}
#ifdef PARAGRAPH_CACHE_STATS
++fTotalRequests;
#endif
SkAutoMutexExclusive lock(fParagraphMutex);
ParagraphCacheKey key(paragraph);
std::unique_ptr<Entry>* entry = fLRUCacheMap.find(key);
if (!entry) {
// We have a cache miss
#ifdef PARAGRAPH_CACHE_STATS
++fCacheMisses;
#endif
fChecker(paragraph, "missingParagraph", true);
return false;
}
updateTo(paragraph, entry->get());
fChecker(paragraph, "foundParagraph", true);
return true;
}
bool ParagraphCache::updateParagraph(ParagraphImpl* paragraph) {
if (!fCacheIsOn) {
return false;
}
#ifdef PARAGRAPH_CACHE_STATS
++fTotalRequests;
#endif
SkAutoMutexExclusive lock(fParagraphMutex);
ParagraphCacheKey key(paragraph);
std::unique_ptr<Entry>* entry = fLRUCacheMap.find(key);
if (!entry) {
ParagraphCacheValue* value = new ParagraphCacheValue(paragraph);
fLRUCacheMap.insert(key, std::unique_ptr<Entry>(new Entry(value)));
fChecker(paragraph, "addedParagraph", true);
return true;
} else {
updateFrom(paragraph, entry->get());
fChecker(paragraph, "updatedParagraph", true);
return false;
}
}
void ParagraphCache::printKeyValue(const char* title, ParagraphImpl* paragraph, bool found) {
/*
SkDebugf("%s '%s' ", title, paragraph->text().data());
for (auto& fd : paragraph->switches()) {
SkDebugf("%d ", fd.fFont.getTypeface() != nullptr ? fd.fFont.getTypeface()->uniqueID(): 0);
};
SkDebugf("\n");
*/
}
}
}

View File

@ -1,98 +0,0 @@
// Copyright 2019 Google LLC.
#ifndef ParagraphCache_DEFINED
#define ParagraphCache_DEFINED
#include "include/core/SkFont.h"
#include "include/core/SkPicture.h"
#include "include/private/SkMutex.h"
#include "include/private/SkTHash.h"
#include "modules/skparagraph/include/ParagraphStyle.h"
#include "modules/skparagraph/src/FontResolver.h"
#include "src/core/SkLRUCache.h"
#include "src/core/SkSpan.h"
#include "src/core/SkTDynamicHash.h"
// TODO: we can cache shaped results or line broken results or formatted results or recorded picture...
// TODO: let's start from the first: the shaped results
namespace skia {
namespace textlayout {
struct Measurement {
SkScalar fAlphabeticBaseline;
SkScalar fIdeographicBaseline;
SkScalar fHeight;
SkScalar fWidth;
SkScalar fMaxIntrinsicWidth;
SkScalar fMinIntrinsicWidth;
};
enum InternalState {
kUnknown = 0,
kShaped = 1,
kClusterized = 2,
kMarked = 3,
kLineBroken = 4,
kFormatted = 5,
kDrawn = 6
};
class ParagraphImpl;
class ParagraphCacheKey {
public:
ParagraphCacheKey(ParagraphImpl* paragraph);
SkString fText;
SkTArray<FontDescr> fFontSwitches;
// TODO: Do not check for more than we have to (which is text styles with letter spacing and word spacing)
SkTArray<Block, true> fTextStyles;
ParagraphStyle fParagraphStyle;
};
class ParagraphCacheValue {
public:;
ParagraphCacheValue(ParagraphImpl* paragraph);
// Input == key
ParagraphCacheKey fKey;
// Shaped results:
InternalState fInternalState;
SkTArray<Run> fRuns;
SkTArray<Cluster, true> fClusters;
SkTArray<TextLine, true> fLines;
Measurement fMeasurement;
sk_sp<SkPicture> fPicture;
};
bool operator==(const ParagraphCacheKey& a, const ParagraphCacheKey& b);
struct LookupTrait {
static const ParagraphCacheKey& GetKey(const ParagraphCacheValue& paragraph);
static uint32_t Hash(const ParagraphCacheKey& key);
static uint32_t mix(uint32_t hash, uint32_t data);
};
// TODO: can we cache the key by the text?... So we do not need to do font resolution again
class ParagraphCache : public SkTDynamicHash<ParagraphCacheValue, ParagraphCacheKey, LookupTrait> {
public:
ParagraphCache() : fChecker([](ParagraphImpl* impl, const char*, bool){ }){ }
bool findParagraph(ParagraphImpl* paragraph);
void addParagraph(ParagraphImpl* paragraph);
void updateParagraph(ParagraphImpl* paragraph);
// For testing
void setChecker(std::function<void(ParagraphImpl* impl, const char*, bool)> checker) { fChecker = checker; }
void printCache(const char* title);
void printKeyValue(const char* title, ParagraphImpl* paragraph, bool found);
private:
mutable SkMutex fParagraphMutex;
std::function<void(ParagraphImpl* impl, const char*, bool)> fChecker;
};
} // namespace textlayout
} // namespace skia
#endif // ParagraphCache_DEFINED

View File

@ -85,8 +85,6 @@ TextRange operator*(const TextRange& a, const TextRange& b) {
return end > begin ? TextRange(begin, end) : EMPTY_TEXT;
}
ParagraphCache ParagraphImpl::fParagraphCache;
ParagraphImpl::ParagraphImpl(const SkString& text,
ParagraphStyle style,
SkTArray<Block, true> blocks,
@ -98,8 +96,7 @@ ParagraphImpl::ParagraphImpl(const SkString& text,
, fState(kUnknown)
, fPicture(nullptr)
, fOldWidth(0)
, fOldHeight(0)
, fParagraphCacheOn(false) {
, fOldHeight(0) {
// TODO: extractStyles();
}
@ -112,8 +109,7 @@ ParagraphImpl::ParagraphImpl(const std::u16string& utf16text,
, fState(kUnknown)
, fPicture(nullptr)
, fOldWidth(0)
, fOldHeight(0)
, fParagraphCacheOn(false) {
, fOldHeight(0) {
icu::UnicodeString unicode((UChar*)utf16text.data(), SkToS32(utf16text.size()));
std::string str;
unicode.toUTF8String(str);
@ -161,10 +157,9 @@ void ParagraphImpl::layout(SkScalar width) {
fState = kClusterized;
this->markLineBreaks();
fState = kMarked;
// Add the paragraph to the cache
if (fParagraphCacheOn) {
fParagraphCache.updateParagraph(this);
}
fFontCollection->getParagraphCache()->updateParagraph(this);
}
}
@ -187,10 +182,6 @@ void ParagraphImpl::layout(SkScalar width) {
// Build the picture lazily not until we actually have to paint (or never)
this->formatLines(fWidth);
fState = kFormatted;
// Add the paragraph to the cache
if (fParagraphCacheOn) {
fParagraphCache.updateParagraph(this);
}
}
this->fOldWidth = width;
@ -203,9 +194,6 @@ void ParagraphImpl::paint(SkCanvas* canvas, SkScalar x, SkScalar y) {
// Record the picture anyway (but if we have some pieces in the cache they will be used)
this->paintLinesIntoPicture();
fState = kDrawn;
if (fParagraphCacheOn) {
fParagraphCache.updateParagraph(this);
}
}
SkMatrix matrix = SkMatrix::MakeTrans(x, y);
@ -355,28 +343,27 @@ bool ParagraphImpl::shapeTextIntoEndlessLine() {
}
// This is a pretty big step - resolving all characters against all given fonts
fFontResolver.findAllFontsForAllStyledBlocks(fTextSpan, styles(), fFontCollection);
fFontResolver.findAllFontsForAllStyledBlocks(this);
// Check the font-resolved text against the cache
if (fParagraphCacheOn && fParagraphCache.findParagraph(this)) {
return true;
}
if (!fFontCollection->getParagraphCache()->findParagraph(this)) {
LangIterator lang(fTextSpan, styles(), paragraphStyle().getTextStyle());
FontIterator font(fTextSpan, &fFontResolver);
ShapeHandler handler(*this, &font);
std::unique_ptr<SkShaper> shaper = SkShaper::MakeShapeDontWrapOrReorder();
SkASSERT_RELEASE(shaper != nullptr);
auto bidi = SkShaper::MakeIcuBiDiRunIterator(
fTextSpan.begin(), fTextSpan.size(),
fParagraphStyle.getTextDirection() == TextDirection::kLtr ? (uint8_t)2
: (uint8_t)1);
if (bidi == nullptr) {
return false;
}
auto script = SkShaper::MakeHbIcuScriptRunIterator(fTextSpan.begin(), fTextSpan.size());
LangIterator lang(fTextSpan, styles(), paragraphStyle().getTextStyle());
FontIterator font(fTextSpan, &fFontResolver);
ShapeHandler handler(*this, &font);
std::unique_ptr<SkShaper> shaper = SkShaper::MakeShapeDontWrapOrReorder();
SkASSERT_RELEASE(shaper != nullptr);
auto bidi = SkShaper::MakeIcuBiDiRunIterator(
fTextSpan.begin(), fTextSpan.size(),
fParagraphStyle.getTextDirection() == TextDirection::kLtr ? (uint8_t)2 : (uint8_t)1);
if (bidi == nullptr) {
return false;
shaper->shape(fTextSpan.begin(), fTextSpan.size(), font, *bidi, *script, lang,
std::numeric_limits<SkScalar>::max(), &handler);
}
auto script = SkShaper::MakeHbIcuScriptRunIterator(fTextSpan.begin(), fTextSpan.size());
shaper->shape(fTextSpan.begin(), fTextSpan.size(), font, *bidi, *script, lang,
std::numeric_limits<SkScalar>::max(), &handler);
if (fParagraphStyle.getTextAlign() == TextAlign::kJustify) {
fRunShifts.reset();

View File

@ -9,7 +9,6 @@
#include "modules/skparagraph/include/ParagraphStyle.h"
#include "modules/skparagraph/include/TextStyle.h"
#include "modules/skparagraph/src/FontResolver.h"
#include "modules/skparagraph/src/ParagraphCache.h"
#include "modules/skparagraph/src/Run.h"
#include "modules/skparagraph/src/TextLine.h"
@ -73,13 +72,14 @@ public:
SkSpan<const char> text() const { return fTextSpan; }
InternalState state() const { return fState; }
SkSpan<Run> runs() { return SkSpan<Run>(fRuns.data(), fRuns.size()); }
SkTArray<FontDescr>& switches() { return fFontResolver.switches(); }
const SkTArray<FontDescr>& switches() const { return fFontResolver.switches(); }
SkSpan<Block> styles() {
return SkSpan<Block>(fTextStyles.data(), fTextStyles.size());
}
SkSpan<TextLine> lines() { return SkSpan<TextLine>(fLines.data(), fLines.size()); }
ParagraphStyle paragraphStyle() const { return fParagraphStyle; }
SkSpan<Cluster> clusters() { return SkSpan<Cluster>(fClusters.begin(), fClusters.size()); }
sk_sp<FontCollection> fontCollection() const { return fFontCollection; }
void formatLines(SkScalar maxWidth);
void shiftCluster(ClusterIndex index, SkScalar shift) {
@ -130,9 +130,8 @@ public:
Block& block(BlockIndex blockIndex);
void markDirty() override { fState = kUnknown; }
void turnOnCache(bool on) { fParagraphCacheOn = on; }
FontResolver& getResolver() { return fFontResolver; }
void setState(InternalState state);
void resetCache() { fParagraphCache.reset(); }
sk_sp<SkPicture> getPicture() { return fPicture; }
void resetContext();
@ -178,10 +177,6 @@ private:
SkScalar fOldWidth;
SkScalar fOldHeight;
// Cache
bool fParagraphCacheOn;
static ParagraphCache fParagraphCache;
};
} // namespace textlayout
} // namespace skia

View File

@ -29,9 +29,13 @@ namespace {
class ParagraphView_Base : public Sample {
protected:
sk_sp<TestFontCollection> fFC;
void onOnceBeforeDraw() override {
fFC = sk_make_sp<TestFontCollection>(GetResourcePath("fonts").c_str());
sk_sp<TestFontCollection> getFontCollection() {
// If we reset font collection we need to reset paragraph cache
static sk_sp<TestFontCollection> fFC = nullptr;
if (fFC == nullptr) {
fFC = sk_make_sp<TestFontCollection>(GetResourcePath("fonts").c_str());
}
return fFC;
}
};
@ -98,11 +102,11 @@ protected:
defaultStyle.setForegroundColor(paint);
ParagraphStyle paraStyle;
auto fontCollection = sk_make_sp<FontCollection>();
fontCollection->setDefaultFontManager(SkFontMgr::RefDefault());
for (auto i = 1; i < 5; ++i) {
defaultStyle.setFontSize(24 * i);
paraStyle.setTextStyle(defaultStyle);
auto fontCollection = sk_make_sp<FontCollection>();
fontCollection->setDefaultFontManager(SkFontMgr::RefDefault());
ParagraphBuilderImpl builder(paraStyle, fontCollection);
std::string name = "Paragraph: " + std::to_string(24 * i);
builder.addText(name.c_str());
@ -276,7 +280,7 @@ protected:
TextStyle defaultStyle;
defaultStyle.setFontSize(20);
paraStyle.setTextStyle(defaultStyle);
ParagraphBuilderImpl builder(paraStyle, fFC);
ParagraphBuilderImpl builder(paraStyle, getFontCollection());
SkPaint foreground;
foreground.setColor(fg);
@ -621,7 +625,7 @@ protected:
const char* logo5 = "google_lo";
const char* logo6 = "go";
{
ParagraphBuilderImpl builder(paraStyle, fFC);
ParagraphBuilderImpl builder(paraStyle, getFontCollection());
builder.pushStyle(style0);
builder.addText(logo1);
@ -711,7 +715,7 @@ protected:
paraStyle.setEllipsis(ellipsis);
ParagraphBuilderImpl builder(paraStyle, fFC);
ParagraphBuilderImpl builder(paraStyle, getFontCollection());
if (text.empty()) {
const std::u16string text0 = u"\u202Dabc";
@ -871,7 +875,7 @@ protected:
const char* logo5 = "Ski";
const char* logo6 = "a";
{
ParagraphBuilderImpl builder(paraStyle, fFC);
ParagraphBuilderImpl builder(paraStyle, getFontCollection());
builder.pushStyle(style0);
builder.addText(logo1);
@ -911,7 +915,7 @@ protected:
const char* logo15 = "S";
const char* logo16 = "S";
{
ParagraphBuilderImpl builder(paraStyle, fFC);
ParagraphBuilderImpl builder(paraStyle, getFontCollection());
builder.pushStyle(style0);
builder.addText(logo11);
@ -982,7 +986,7 @@ protected:
textStyle.setFontStyle(SkFontStyle(SkFontStyle::kMedium_Weight, SkFontStyle::kNormal_Width,
SkFontStyle::kUpright_Slant));
ParagraphBuilderImpl builder(paragraphStyle, fFC);
ParagraphBuilderImpl builder(paragraphStyle, getFontCollection());
builder.pushStyle(textStyle);
builder.addText(line);
builder.pop();
@ -1052,7 +1056,7 @@ protected:
textStyle.setFontStyle(SkFontStyle(SkFontStyle::kMedium_Weight, SkFontStyle::kNormal_Width,
SkFontStyle::kUpright_Slant));
ParagraphBuilderImpl builder(paragraphStyle, fFC);
ParagraphBuilderImpl builder(paragraphStyle, getFontCollection());
builder.pushStyle(textStyle);
builder.addText(line);
builder.pop();
@ -1143,7 +1147,7 @@ protected:
textStyle.setFontStyle(SkFontStyle(SkFontStyle::kMedium_Weight, SkFontStyle::kNormal_Width,
SkFontStyle::kUpright_Slant));
ParagraphBuilderImpl builder(paragraphStyle, fFC);
ParagraphBuilderImpl builder(paragraphStyle, getFontCollection());
builder.pushStyle(textStyle);
builder.addText(text);
builder.pop();
@ -1202,7 +1206,7 @@ protected:
const char* text = "English English 字典 字典 😀😃😄 😀😃😄";
ParagraphStyle paragraph_style;
paragraph_style.turnHintingOff();
ParagraphBuilderImpl builder(paragraph_style, fFC);
ParagraphBuilderImpl builder(paragraph_style, getFontCollection());
TextStyle text_style;
text_style.setFontFamilies({SkString("Roboto"),
@ -1242,7 +1246,7 @@ protected:
for (size_t i = 0; i < 10; i++) {
ParagraphStyle paragraph_style;
ParagraphBuilderImpl builder(paragraph_style, fFC);
ParagraphBuilderImpl builder(paragraph_style, getFontCollection());
TextStyle text_style;
text_style.setFontFamilies({SkString("Roboto")});
text_style.setColor(SK_ColorBLACK);
@ -1270,7 +1274,7 @@ protected:
paragraph_style.setMaxLines(14);
paragraph_style.setTextAlign(TextAlign::kLeft);
paragraph_style.turnHintingOff();
ParagraphBuilderImpl builder(paragraph_style, fFC);
ParagraphBuilderImpl builder(paragraph_style, getFontCollection());
TextStyle text_style;
text_style.setFontFamilies({SkString("Roboto")});
@ -1285,7 +1289,6 @@ protected:
auto paragraph = builder.Build();
auto impl = reinterpret_cast<ParagraphImpl*>(paragraph.get());
impl->turnOnCache(false);
for (auto i = 0; i < 1000; ++i) {
impl->setState(kUnknown);

View File

@ -2944,3 +2944,165 @@ DEF_TEST(SkParagraph_JSON2, reporter) {
SkASSERT(cluster <= 2);
}
DEF_TEST(SkParagraph_CacheText, reporter) {
ParagraphCache cache;
sk_sp<TestFontCollection> fontCollection = sk_make_sp<TestFontCollection>();
if (!fontCollection->fontsFound()) return;
ParagraphStyle paragraph_style;
paragraph_style.turnHintingOff();
TextStyle text_style;
text_style.setFontFamilies({SkString("Roboto")});
text_style.setColor(SK_ColorBLACK);
auto test = [&](const char* text, int count, bool expectedToBeFound) {
ParagraphBuilderImpl builder(paragraph_style, fontCollection);
builder.pushStyle(text_style);
builder.addText(text);
builder.pop();
auto paragraph = builder.Build();
auto impl = static_cast<ParagraphImpl*>(paragraph.get());
REPORTER_ASSERT(reporter, count == cache.count());
auto found = cache.findParagraph(impl);
REPORTER_ASSERT(reporter, found == expectedToBeFound);
auto added = cache.updateParagraph(impl);
REPORTER_ASSERT(reporter, added != expectedToBeFound);
};
test("text1", 0, false);
test("text1", 1, true);
test("text2", 1, false);
test("text2", 2, true);
test("text3", 2, false);
}
DEF_TEST(SkParagraph_CacheFonts, reporter) {
ParagraphCache cache;
sk_sp<TestFontCollection> fontCollection = sk_make_sp<TestFontCollection>();
if (!fontCollection->fontsFound()) return;
ParagraphStyle paragraph_style;
paragraph_style.turnHintingOff();
TextStyle text_style;
text_style.setColor(SK_ColorBLACK);
auto test = [&](int count, bool expectedToBeFound) {
ParagraphBuilderImpl builder(paragraph_style, fontCollection);
builder.pushStyle(text_style);
builder.addText("text");
builder.pop();
auto paragraph = builder.Build();
auto impl = static_cast<ParagraphImpl*>(paragraph.get());
impl->getResolver().findAllFontsForAllStyledBlocks(impl);
REPORTER_ASSERT(reporter, count == cache.count());
auto found = cache.findParagraph(impl);
REPORTER_ASSERT(reporter, found == expectedToBeFound);
auto added = cache.updateParagraph(impl);
REPORTER_ASSERT(reporter, added != expectedToBeFound);
};
text_style.setFontFamilies({SkString("Roboto")});
test(0, false);
test(1, true);
text_style.setFontFamilies({SkString("Homemade Apple")});
test(1, false);
test(2, true);
text_style.setFontFamilies({SkString("Noto Color Emoji")});
test(2, false);
}
DEF_TEST(SkParagraph_CacheFontRanges, reporter) {
ParagraphCache cache;
sk_sp<TestFontCollection> fontCollection = sk_make_sp<TestFontCollection>();
if (!fontCollection->fontsFound()) return;
ParagraphStyle paragraph_style;
paragraph_style.turnHintingOff();
TextStyle text_style;
text_style.setFontFamilies({SkString("Roboto")});
text_style.setColor(SK_ColorBLACK);
auto test = [&](const char* text1,
const char* text2,
const char* font1,
const char* font2,
int count,
bool expectedToBeFound) {
ParagraphBuilderImpl builder(paragraph_style, fontCollection);
text_style.setFontFamilies({SkString(font1)});
builder.pushStyle(text_style);
builder.addText(text1);
builder.pop();
text_style.setFontFamilies({SkString(font2)});
builder.pushStyle(text_style);
builder.addText(text2);
builder.pop();
auto paragraph = builder.Build();
auto impl = static_cast<ParagraphImpl*>(paragraph.get());
impl->getResolver().findAllFontsForAllStyledBlocks(impl);
REPORTER_ASSERT(reporter, count == cache.count());
auto found = cache.findParagraph(impl);
REPORTER_ASSERT(reporter, found == expectedToBeFound);
auto added = cache.updateParagraph(impl);
REPORTER_ASSERT(reporter, added != expectedToBeFound);
};
test("text", "", "Roboto", "Homemade Apple", 0, false);
test("t", "ext", "Roboto", "Homemade Apple", 1, false);
test("te", "xt", "Roboto", "Homemade Apple", 2, false);
test("tex", "t", "Roboto", "Homemade Apple", 3, false);
test("text", "", "Roboto", "Homemade Apple", 4, true);
}
DEF_TEST(SkParagraph_CacheStyles, reporter) {
ParagraphCache cache;
sk_sp<TestFontCollection> fontCollection = sk_make_sp<TestFontCollection>();
if (!fontCollection->fontsFound()) return;
ParagraphStyle paragraph_style;
paragraph_style.turnHintingOff();
TextStyle text_style;
text_style.setFontFamilies({SkString("Roboto")});
text_style.setColor(SK_ColorBLACK);
auto test = [&](int count, bool expectedToBeFound) {
ParagraphBuilderImpl builder(paragraph_style, fontCollection);
builder.pushStyle(text_style);
builder.addText("text");
builder.pop();
auto paragraph = builder.Build();
auto impl = static_cast<ParagraphImpl*>(paragraph.get());
impl->getResolver().findAllFontsForAllStyledBlocks(impl);
REPORTER_ASSERT(reporter, count == cache.count());
auto found = cache.findParagraph(impl);
REPORTER_ASSERT(reporter, found == expectedToBeFound);
auto added = cache.updateParagraph(impl);
REPORTER_ASSERT(reporter, added != expectedToBeFound);
};
test(0, false);
test(1, true);
text_style.setLetterSpacing(10);
test(1, false);
test(2, true);
text_style.setWordSpacing(10);
test(2, false);
}