blob: 05d951c6b95ee9d12148cd69afa26521e4e9d34d [file] [log] [blame]
Julia Lavrova5207f352019-06-21 12:22:32 -04001// Copyright 2019 Google LLC.
Julia Lavrovab7b0b3a2019-07-30 13:32:08 -04002#include "modules/skparagraph/include/ParagraphCache.h"
Mike Klein52337de2019-07-25 09:00:52 -05003#include "modules/skparagraph/src/ParagraphImpl.h"
Julia Lavrova5207f352019-06-21 12:22:32 -04004
5namespace skia {
6namespace textlayout {
7
Julia Lavrovab7b0b3a2019-07-30 13:32:08 -04008class ParagraphCacheKey {
9public:
10 ParagraphCacheKey(const ParagraphImpl* paragraph)
11 : fText(paragraph->fText.c_str(), paragraph->fText.size())
Julia Lavrova2e30fde2019-10-09 09:43:02 -040012 , fResolvedFonts(paragraph->resolvedFonts())
Julia Lavrova5207f352019-06-21 12:22:32 -040013 , fTextStyles(paragraph->fTextStyles)
14 , fParagraphStyle(paragraph->paragraphStyle()) { }
15
Julia Lavrovab7b0b3a2019-07-30 13:32:08 -040016 SkString fText;
Julia Lavrova2e30fde2019-10-09 09:43:02 -040017 SkTArray<ResolvedFontDescriptor> fResolvedFonts;
Julia Lavrovab7b0b3a2019-07-30 13:32:08 -040018 SkTArray<Block, true> fTextStyles;
19 ParagraphStyle fParagraphStyle;
20};
21
22class ParagraphCacheValue {
23public:
24 ParagraphCacheValue(const ParagraphImpl* paragraph)
Julia Lavrova5207f352019-06-21 12:22:32 -040025 : fKey(ParagraphCacheKey(paragraph))
Julia Lavrova3281b962019-12-02 11:32:25 -050026 , fInternalState(paragraph->fState)
Julia Lavrova5207f352019-06-21 12:22:32 -040027 , fRuns(paragraph->fRuns)
Julia Lavrova3281b962019-12-02 11:32:25 -050028 , fClusters(paragraph->fClusters)
29 , fUnresolvedGlyphs(paragraph->fUnresolvedGlyphs){ }
Julia Lavrova5207f352019-06-21 12:22:32 -040030
Julia Lavrovab7b0b3a2019-07-30 13:32:08 -040031 // Input == key
32 ParagraphCacheKey fKey;
Julia Lavrova5207f352019-06-21 12:22:32 -040033
Julia Lavrovab7b0b3a2019-07-30 13:32:08 -040034 // Shaped results:
35 InternalState fInternalState;
Julia Lavrova2e30fde2019-10-09 09:43:02 -040036 SkTArray<Run, false> fRuns;
Julia Lavrovab7b0b3a2019-07-30 13:32:08 -040037 SkTArray<Cluster, true> fClusters;
Julia Lavrova3281b962019-12-02 11:32:25 -050038 size_t fUnresolvedGlyphs;
Julia Lavrovab7b0b3a2019-07-30 13:32:08 -040039};
Julia Lavrova5207f352019-06-21 12:22:32 -040040
Julia Lavrova5207f352019-06-21 12:22:32 -040041
Julia Lavrovab7b0b3a2019-07-30 13:32:08 -040042uint32_t ParagraphCache::KeyHash::mix(uint32_t hash, uint32_t data) const {
43 hash += data;
44 hash += (hash << 10);
45 hash ^= (hash >> 6);
46 return hash;
Julia Lavrova5207f352019-06-21 12:22:32 -040047}
Julia Lavrovab7b0b3a2019-07-30 13:32:08 -040048uint32_t ParagraphCache::KeyHash::operator()(const ParagraphCacheKey& key) const {
49 uint32_t hash = 0;
Julia Lavrova2e30fde2019-10-09 09:43:02 -040050 for (auto& fd : key.fResolvedFonts) {
51 hash = mix(hash, SkGoodHash()(fd.fTextStart));
Julia Lavrovab7b0b3a2019-07-30 13:32:08 -040052 hash = mix(hash, SkGoodHash()(fd.fFont.getSize()));
Julia Lavrova5207f352019-06-21 12:22:32 -040053
Julia Lavrovab7b0b3a2019-07-30 13:32:08 -040054 if (fd.fFont.getTypeface() != nullptr) {
55 SkString name;
56 fd.fFont.getTypeface()->getFamilyName(&name);
57 hash = mix(hash, SkGoodHash()(name));
58 hash = mix(hash, SkGoodHash()(fd.fFont.getTypeface()->fontStyle()));
Julia Lavrova5207f352019-06-21 12:22:32 -040059 }
Julia Lavrova5207f352019-06-21 12:22:32 -040060 }
Julia Lavrovab7b0b3a2019-07-30 13:32:08 -040061 for (auto& ts : key.fTextStyles) {
Julia Lavrova916a9042019-08-08 16:51:27 -040062 if (!ts.fStyle.isPlaceholder()) {
63 hash = mix(hash, SkGoodHash()(ts.fStyle.getLetterSpacing()));
64 hash = mix(hash, SkGoodHash()(ts.fStyle.getWordSpacing()));
65 hash = mix(hash, SkGoodHash()(ts.fRange));
66 } else {
67 // TODO: cache placeholders
68 }
Julia Lavrovab7b0b3a2019-07-30 13:32:08 -040069 }
70 hash = mix(hash, SkGoodHash()(key.fText));
71 return hash;
Julia Lavrova5207f352019-06-21 12:22:32 -040072}
73
74bool operator==(const ParagraphCacheKey& a, const ParagraphCacheKey& b) {
75 if (a.fText.size() != b.fText.size()) {
76 return false;
77 }
Julia Lavrova2e30fde2019-10-09 09:43:02 -040078 if (a.fResolvedFonts.count() != b.fResolvedFonts.count()) {
Julia Lavrova5207f352019-06-21 12:22:32 -040079 return false;
80 }
81 if (a.fText != b.fText) {
82 return false;
83 }
84 if (a.fTextStyles.size() != b.fTextStyles.size()) {
85 return false;
86 }
87
88 if (a.fParagraphStyle.getMaxLines() != b.fParagraphStyle.getMaxLines()) {
Julia Lavrovab7b0b3a2019-07-30 13:32:08 -040089 // This is too strong, but at least we will not lose lines
Julia Lavrova5207f352019-06-21 12:22:32 -040090 return false;
91 }
92
Julia Lavrova2e30fde2019-10-09 09:43:02 -040093 for (size_t i = 0; i < a.fResolvedFonts.size(); ++i) {
94 auto& fda = a.fResolvedFonts[i];
95 auto& fdb = b.fResolvedFonts[i];
96 if (fda.fTextStart != fdb.fTextStart) {
Julia Lavrova5207f352019-06-21 12:22:32 -040097 return false;
98 }
99 if (fda.fFont != fdb.fFont) {
100 return false;
101 }
102 }
103
104 for (size_t i = 0; i < a.fTextStyles.size(); ++i) {
105 auto& tsa = a.fTextStyles[i];
106 auto& tsb = b.fTextStyles[i];
Julia Lavrova916a9042019-08-08 16:51:27 -0400107 if (!(tsa.fStyle == tsb.fStyle)) {
Julia Lavrova5207f352019-06-21 12:22:32 -0400108 return false;
109 }
Julia Lavrova916a9042019-08-08 16:51:27 -0400110 if (!tsa.fStyle.isPlaceholder()) {
111 if (tsa.fStyle.getLetterSpacing() != tsb.fStyle.getLetterSpacing()) {
112 return false;
113 }
114 if (tsa.fStyle.getWordSpacing() != tsb.fStyle.getWordSpacing()) {
115 return false;
116 }
117 if (tsa.fRange.width() != tsb.fRange.width()) {
118 return false;
119 }
120 if (tsa.fRange.start != tsb.fRange.start) {
121 return false;
122 }
123 } else {
124 // TODO: compare placeholders
Julia Lavrova5207f352019-06-21 12:22:32 -0400125 }
126 }
127
128 return true;
129}
130
Julia Lavrovab7b0b3a2019-07-30 13:32:08 -0400131struct ParagraphCache::Entry {
Julia Lavrova5207f352019-06-21 12:22:32 -0400132
Julia Lavrovab7b0b3a2019-07-30 13:32:08 -0400133 Entry(ParagraphCacheValue* value) : fValue(value) {}
134 ParagraphCacheValue* fValue;
135};
Julia Lavrova5207f352019-06-21 12:22:32 -0400136
Julia Lavrovab7b0b3a2019-07-30 13:32:08 -0400137ParagraphCache::ParagraphCache()
138 : fChecker([](ParagraphImpl* impl, const char*, bool){ })
139 , fLRUCacheMap(kMaxEntries)
140 , fCacheIsOn(true)
141#ifdef PARAGRAPH_CACHE_STATS
142 , fTotalRequests(0)
143 , fCacheMisses(0)
144 , fHashMisses(0)
145#endif
146{ }
147
148ParagraphCache::~ParagraphCache() { }
149
150void ParagraphCache::updateFrom(const ParagraphImpl* paragraph, Entry* entry) {
151
152 entry->fValue->fInternalState = paragraph->state();
Julia Lavrovab7b0b3a2019-07-30 13:32:08 -0400153 for (size_t i = 0; i < paragraph->fRuns.size(); ++i) {
154 auto& run = paragraph->fRuns[i];
155 if (run.fSpaced) {
156 entry->fValue->fRuns[i] = run;
Julia Lavrova5207f352019-06-21 12:22:32 -0400157 }
158 }
Julia Lavrova5207f352019-06-21 12:22:32 -0400159}
160
Julia Lavrovab7b0b3a2019-07-30 13:32:08 -0400161void ParagraphCache::updateTo(ParagraphImpl* paragraph, const Entry* entry) {
162 paragraph->fRuns.reset();
163 paragraph->fRuns = entry->fValue->fRuns;
164 for (auto& run : paragraph->fRuns) {
165 run.setMaster(paragraph);
166 }
Julia Lavrova5207f352019-06-21 12:22:32 -0400167
Julia Lavrovab7b0b3a2019-07-30 13:32:08 -0400168 paragraph->fClusters.reset();
169 paragraph->fClusters = entry->fValue->fClusters;
170 for (auto& cluster : paragraph->fClusters) {
171 cluster.setMaster(paragraph);
172 }
173
Julia Lavrovab7b0b3a2019-07-30 13:32:08 -0400174 paragraph->fState = entry->fValue->fInternalState;
Julia Lavrova3281b962019-12-02 11:32:25 -0500175 paragraph->fUnresolvedGlyphs = entry->fValue->fUnresolvedGlyphs;
Julia Lavrovab7b0b3a2019-07-30 13:32:08 -0400176}
177
178void ParagraphCache::printStatistics() {
179 SkDebugf("--- Paragraph Cache ---\n");
180 SkDebugf("Total requests: %d\n", fTotalRequests);
181 SkDebugf("Cache misses: %d\n", fCacheMisses);
182 SkDebugf("Cache miss %%: %f\n", (fTotalRequests > 0) ? 100.f * fCacheMisses / fTotalRequests : 0.f);
183 int cacheHits = fTotalRequests - fCacheMisses;
184 SkDebugf("Hash miss %%: %f\n", (cacheHits > 0) ? 100.f * fHashMisses / cacheHits : 0.f);
185 SkDebugf("---------------------\n");
186}
187
188void ParagraphCache::abandon() {
189 SkAutoMutexExclusive lock(fParagraphMutex);
190 fLRUCacheMap.foreach([](std::unique_ptr<Entry>* e) {
191 });
192
193 this->reset();
194}
195
196void ParagraphCache::reset() {
197 SkAutoMutexExclusive lock(fParagraphMutex);
198#ifdef PARAGRAPH_CACHE_STATS
199 fTotalRequests = 0;
200 fCacheMisses = 0;
201 fHashMisses = 0;
202#endif
203 fLRUCacheMap.reset();
204}
205
206bool ParagraphCache::findParagraph(ParagraphImpl* paragraph) {
207 if (!fCacheIsOn) {
208 return false;
209 }
210#ifdef PARAGRAPH_CACHE_STATS
211 ++fTotalRequests;
212#endif
213 SkAutoMutexExclusive lock(fParagraphMutex);
214 ParagraphCacheKey key(paragraph);
215 std::unique_ptr<Entry>* entry = fLRUCacheMap.find(key);
216 if (!entry) {
217 // We have a cache miss
218#ifdef PARAGRAPH_CACHE_STATS
219 ++fCacheMisses;
220#endif
221 fChecker(paragraph, "missingParagraph", true);
222 return false;
223 }
224 updateTo(paragraph, entry->get());
225 fChecker(paragraph, "foundParagraph", true);
226 return true;
227}
228
229bool ParagraphCache::updateParagraph(ParagraphImpl* paragraph) {
230 if (!fCacheIsOn) {
231 return false;
232 }
233#ifdef PARAGRAPH_CACHE_STATS
234 ++fTotalRequests;
235#endif
236 SkAutoMutexExclusive lock(fParagraphMutex);
237 ParagraphCacheKey key(paragraph);
238 std::unique_ptr<Entry>* entry = fLRUCacheMap.find(key);
239 if (!entry) {
240 ParagraphCacheValue* value = new ParagraphCacheValue(paragraph);
241 fLRUCacheMap.insert(key, std::unique_ptr<Entry>(new Entry(value)));
242 fChecker(paragraph, "addedParagraph", true);
243 return true;
244 } else {
245 updateFrom(paragraph, entry->get());
246 fChecker(paragraph, "updatedParagraph", true);
247 return false;
Julia Lavrova5207f352019-06-21 12:22:32 -0400248 }
249}
Julia Lavrova5207f352019-06-21 12:22:32 -0400250}
251}