blob: 80da392b7c07040c7422105906bab49842f36458 [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 Lavrova5207f352019-06-21 12:22:32 -040012 , fFontSwitches(paragraph->switches())
13 , fTextStyles(paragraph->fTextStyles)
14 , fParagraphStyle(paragraph->paragraphStyle()) { }
15
Julia Lavrovab7b0b3a2019-07-30 13:32:08 -040016 SkString fText;
17 SkTArray<FontDescr> fFontSwitches;
18 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))
26 , fInternalState(paragraph->state())
27 , fRuns(paragraph->fRuns)
Julia Lavrovab7b0b3a2019-07-30 13:32:08 -040028 , fClusters(paragraph->fClusters) { }
Julia Lavrova5207f352019-06-21 12:22:32 -040029
Julia Lavrovab7b0b3a2019-07-30 13:32:08 -040030 // Input == key
31 ParagraphCacheKey fKey;
Julia Lavrova5207f352019-06-21 12:22:32 -040032
Julia Lavrovab7b0b3a2019-07-30 13:32:08 -040033 // Shaped results:
34 InternalState fInternalState;
35 SkTArray<Run> fRuns;
36 SkTArray<Cluster, true> fClusters;
37 SkTArray<RunShifts, true> fRunShifts;
38};
Julia Lavrova5207f352019-06-21 12:22:32 -040039
Julia Lavrova5207f352019-06-21 12:22:32 -040040
Julia Lavrovab7b0b3a2019-07-30 13:32:08 -040041uint32_t ParagraphCache::KeyHash::mix(uint32_t hash, uint32_t data) const {
42 hash += data;
43 hash += (hash << 10);
44 hash ^= (hash >> 6);
45 return hash;
Julia Lavrova5207f352019-06-21 12:22:32 -040046}
Julia Lavrovab7b0b3a2019-07-30 13:32:08 -040047uint32_t ParagraphCache::KeyHash::operator()(const ParagraphCacheKey& key) const {
48 uint32_t hash = 0;
49 for (auto& fd : key.fFontSwitches) {
50 hash = mix(hash, SkGoodHash()(fd.fStart));
51 hash = mix(hash, SkGoodHash()(fd.fFont.getSize()));
Julia Lavrova5207f352019-06-21 12:22:32 -040052
Julia Lavrovab7b0b3a2019-07-30 13:32:08 -040053 if (fd.fFont.getTypeface() != nullptr) {
54 SkString name;
55 fd.fFont.getTypeface()->getFamilyName(&name);
56 hash = mix(hash, SkGoodHash()(name));
57 hash = mix(hash, SkGoodHash()(fd.fFont.getTypeface()->fontStyle()));
Julia Lavrova5207f352019-06-21 12:22:32 -040058 }
Julia Lavrova5207f352019-06-21 12:22:32 -040059 }
Julia Lavrovab7b0b3a2019-07-30 13:32:08 -040060 for (auto& ts : key.fTextStyles) {
Julia Lavrova916a9042019-08-08 16:51:27 -040061 if (!ts.fStyle.isPlaceholder()) {
62 hash = mix(hash, SkGoodHash()(ts.fStyle.getLetterSpacing()));
63 hash = mix(hash, SkGoodHash()(ts.fStyle.getWordSpacing()));
64 hash = mix(hash, SkGoodHash()(ts.fRange));
65 } else {
66 // TODO: cache placeholders
67 }
Julia Lavrovab7b0b3a2019-07-30 13:32:08 -040068 }
69 hash = mix(hash, SkGoodHash()(key.fText));
70 return hash;
Julia Lavrova5207f352019-06-21 12:22:32 -040071}
72
73bool operator==(const ParagraphCacheKey& a, const ParagraphCacheKey& b) {
74 if (a.fText.size() != b.fText.size()) {
75 return false;
76 }
77 if (a.fFontSwitches.count() != b.fFontSwitches.count()) {
78 return false;
79 }
80 if (a.fText != b.fText) {
81 return false;
82 }
83 if (a.fTextStyles.size() != b.fTextStyles.size()) {
84 return false;
85 }
86
87 if (a.fParagraphStyle.getMaxLines() != b.fParagraphStyle.getMaxLines()) {
Julia Lavrovab7b0b3a2019-07-30 13:32:08 -040088 // This is too strong, but at least we will not lose lines
Julia Lavrova5207f352019-06-21 12:22:32 -040089 return false;
90 }
91
92 for (size_t i = 0; i < a.fFontSwitches.size(); ++i) {
93 auto& fda = a.fFontSwitches[i];
94 auto& fdb = b.fFontSwitches[i];
95 if (fda.fStart != fdb.fStart) {
96 return false;
97 }
98 if (fda.fFont != fdb.fFont) {
99 return false;
100 }
101 }
102
103 for (size_t i = 0; i < a.fTextStyles.size(); ++i) {
104 auto& tsa = a.fTextStyles[i];
105 auto& tsb = b.fTextStyles[i];
Julia Lavrova916a9042019-08-08 16:51:27 -0400106 if (!(tsa.fStyle == tsb.fStyle)) {
Julia Lavrova5207f352019-06-21 12:22:32 -0400107 return false;
108 }
Julia Lavrova916a9042019-08-08 16:51:27 -0400109 if (!tsa.fStyle.isPlaceholder()) {
110 if (tsa.fStyle.getLetterSpacing() != tsb.fStyle.getLetterSpacing()) {
111 return false;
112 }
113 if (tsa.fStyle.getWordSpacing() != tsb.fStyle.getWordSpacing()) {
114 return false;
115 }
116 if (tsa.fRange.width() != tsb.fRange.width()) {
117 return false;
118 }
119 if (tsa.fRange.start != tsb.fRange.start) {
120 return false;
121 }
122 } else {
123 // TODO: compare placeholders
Julia Lavrova5207f352019-06-21 12:22:32 -0400124 }
125 }
126
127 return true;
128}
129
Julia Lavrovab7b0b3a2019-07-30 13:32:08 -0400130struct ParagraphCache::Entry {
Julia Lavrova5207f352019-06-21 12:22:32 -0400131
Julia Lavrovab7b0b3a2019-07-30 13:32:08 -0400132 Entry(ParagraphCacheValue* value) : fValue(value) {}
133 ParagraphCacheValue* fValue;
134};
Julia Lavrova5207f352019-06-21 12:22:32 -0400135
Julia Lavrovab7b0b3a2019-07-30 13:32:08 -0400136ParagraphCache::ParagraphCache()
137 : fChecker([](ParagraphImpl* impl, const char*, bool){ })
138 , fLRUCacheMap(kMaxEntries)
139 , fCacheIsOn(true)
140#ifdef PARAGRAPH_CACHE_STATS
141 , fTotalRequests(0)
142 , fCacheMisses(0)
143 , fHashMisses(0)
144#endif
145{ }
146
147ParagraphCache::~ParagraphCache() { }
148
149void ParagraphCache::updateFrom(const ParagraphImpl* paragraph, Entry* entry) {
150
151 entry->fValue->fInternalState = paragraph->state();
152 entry->fValue->fRunShifts = paragraph->fRunShifts;
153 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
174 paragraph->fRunShifts.reset();
175 for (auto& runShift : entry->fValue->fRunShifts) {
176 paragraph->fRunShifts.push_back(runShift);
177 }
178
179 paragraph->fState = entry->fValue->fInternalState;
180}
181
182void ParagraphCache::printStatistics() {
183 SkDebugf("--- Paragraph Cache ---\n");
184 SkDebugf("Total requests: %d\n", fTotalRequests);
185 SkDebugf("Cache misses: %d\n", fCacheMisses);
186 SkDebugf("Cache miss %%: %f\n", (fTotalRequests > 0) ? 100.f * fCacheMisses / fTotalRequests : 0.f);
187 int cacheHits = fTotalRequests - fCacheMisses;
188 SkDebugf("Hash miss %%: %f\n", (cacheHits > 0) ? 100.f * fHashMisses / cacheHits : 0.f);
189 SkDebugf("---------------------\n");
190}
191
192void ParagraphCache::abandon() {
193 SkAutoMutexExclusive lock(fParagraphMutex);
194 fLRUCacheMap.foreach([](std::unique_ptr<Entry>* e) {
195 });
196
197 this->reset();
198}
199
200void ParagraphCache::reset() {
201 SkAutoMutexExclusive lock(fParagraphMutex);
202#ifdef PARAGRAPH_CACHE_STATS
203 fTotalRequests = 0;
204 fCacheMisses = 0;
205 fHashMisses = 0;
206#endif
207 fLRUCacheMap.reset();
208}
209
210bool ParagraphCache::findParagraph(ParagraphImpl* paragraph) {
211 if (!fCacheIsOn) {
212 return false;
213 }
214#ifdef PARAGRAPH_CACHE_STATS
215 ++fTotalRequests;
216#endif
217 SkAutoMutexExclusive lock(fParagraphMutex);
218 ParagraphCacheKey key(paragraph);
219 std::unique_ptr<Entry>* entry = fLRUCacheMap.find(key);
220 if (!entry) {
221 // We have a cache miss
222#ifdef PARAGRAPH_CACHE_STATS
223 ++fCacheMisses;
224#endif
225 fChecker(paragraph, "missingParagraph", true);
226 return false;
227 }
228 updateTo(paragraph, entry->get());
229 fChecker(paragraph, "foundParagraph", true);
230 return true;
231}
232
233bool ParagraphCache::updateParagraph(ParagraphImpl* paragraph) {
234 if (!fCacheIsOn) {
235 return false;
236 }
237#ifdef PARAGRAPH_CACHE_STATS
238 ++fTotalRequests;
239#endif
240 SkAutoMutexExclusive lock(fParagraphMutex);
241 ParagraphCacheKey key(paragraph);
242 std::unique_ptr<Entry>* entry = fLRUCacheMap.find(key);
243 if (!entry) {
244 ParagraphCacheValue* value = new ParagraphCacheValue(paragraph);
245 fLRUCacheMap.insert(key, std::unique_ptr<Entry>(new Entry(value)));
246 fChecker(paragraph, "addedParagraph", true);
247 return true;
248 } else {
249 updateFrom(paragraph, entry->get());
250 fChecker(paragraph, "updatedParagraph", true);
251 return false;
Julia Lavrova5207f352019-06-21 12:22:32 -0400252 }
253}
Julia Lavrova5207f352019-06-21 12:22:32 -0400254}
255}