blob: e25244e44716ae83bd37edfbef228109f7c1a05d [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))
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;
Julia Lavrova2e30fde2019-10-09 09:43:02 -040035 SkTArray<Run, false> fRuns;
Julia Lavrovab7b0b3a2019-07-30 13:32:08 -040036 SkTArray<Cluster, true> fClusters;
Julia Lavrovab7b0b3a2019-07-30 13:32:08 -040037};
Julia Lavrova5207f352019-06-21 12:22:32 -040038
Julia Lavrova5207f352019-06-21 12:22:32 -040039
Julia Lavrovab7b0b3a2019-07-30 13:32:08 -040040uint32_t ParagraphCache::KeyHash::mix(uint32_t hash, uint32_t data) const {
41 hash += data;
42 hash += (hash << 10);
43 hash ^= (hash >> 6);
44 return hash;
Julia Lavrova5207f352019-06-21 12:22:32 -040045}
Julia Lavrovab7b0b3a2019-07-30 13:32:08 -040046uint32_t ParagraphCache::KeyHash::operator()(const ParagraphCacheKey& key) const {
47 uint32_t hash = 0;
Julia Lavrova2e30fde2019-10-09 09:43:02 -040048 for (auto& fd : key.fResolvedFonts) {
49 hash = mix(hash, SkGoodHash()(fd.fTextStart));
Julia Lavrovab7b0b3a2019-07-30 13:32:08 -040050 hash = mix(hash, SkGoodHash()(fd.fFont.getSize()));
Julia Lavrova5207f352019-06-21 12:22:32 -040051
Julia Lavrovab7b0b3a2019-07-30 13:32:08 -040052 if (fd.fFont.getTypeface() != nullptr) {
53 SkString name;
54 fd.fFont.getTypeface()->getFamilyName(&name);
55 hash = mix(hash, SkGoodHash()(name));
56 hash = mix(hash, SkGoodHash()(fd.fFont.getTypeface()->fontStyle()));
Julia Lavrova5207f352019-06-21 12:22:32 -040057 }
Julia Lavrova5207f352019-06-21 12:22:32 -040058 }
Julia Lavrovab7b0b3a2019-07-30 13:32:08 -040059 for (auto& ts : key.fTextStyles) {
Julia Lavrova916a9042019-08-08 16:51:27 -040060 if (!ts.fStyle.isPlaceholder()) {
61 hash = mix(hash, SkGoodHash()(ts.fStyle.getLetterSpacing()));
62 hash = mix(hash, SkGoodHash()(ts.fStyle.getWordSpacing()));
63 hash = mix(hash, SkGoodHash()(ts.fRange));
64 } else {
65 // TODO: cache placeholders
66 }
Julia Lavrovab7b0b3a2019-07-30 13:32:08 -040067 }
68 hash = mix(hash, SkGoodHash()(key.fText));
69 return hash;
Julia Lavrova5207f352019-06-21 12:22:32 -040070}
71
72bool operator==(const ParagraphCacheKey& a, const ParagraphCacheKey& b) {
73 if (a.fText.size() != b.fText.size()) {
74 return false;
75 }
Julia Lavrova2e30fde2019-10-09 09:43:02 -040076 if (a.fResolvedFonts.count() != b.fResolvedFonts.count()) {
Julia Lavrova5207f352019-06-21 12:22:32 -040077 return false;
78 }
79 if (a.fText != b.fText) {
80 return false;
81 }
82 if (a.fTextStyles.size() != b.fTextStyles.size()) {
83 return false;
84 }
85
86 if (a.fParagraphStyle.getMaxLines() != b.fParagraphStyle.getMaxLines()) {
Julia Lavrovab7b0b3a2019-07-30 13:32:08 -040087 // This is too strong, but at least we will not lose lines
Julia Lavrova5207f352019-06-21 12:22:32 -040088 return false;
89 }
90
Julia Lavrova2e30fde2019-10-09 09:43:02 -040091 for (size_t i = 0; i < a.fResolvedFonts.size(); ++i) {
92 auto& fda = a.fResolvedFonts[i];
93 auto& fdb = b.fResolvedFonts[i];
94 if (fda.fTextStart != fdb.fTextStart) {
Julia Lavrova5207f352019-06-21 12:22:32 -040095 return false;
96 }
97 if (fda.fFont != fdb.fFont) {
98 return false;
99 }
100 }
101
102 for (size_t i = 0; i < a.fTextStyles.size(); ++i) {
103 auto& tsa = a.fTextStyles[i];
104 auto& tsb = b.fTextStyles[i];
Julia Lavrova916a9042019-08-08 16:51:27 -0400105 if (!(tsa.fStyle == tsb.fStyle)) {
Julia Lavrova5207f352019-06-21 12:22:32 -0400106 return false;
107 }
Julia Lavrova916a9042019-08-08 16:51:27 -0400108 if (!tsa.fStyle.isPlaceholder()) {
109 if (tsa.fStyle.getLetterSpacing() != tsb.fStyle.getLetterSpacing()) {
110 return false;
111 }
112 if (tsa.fStyle.getWordSpacing() != tsb.fStyle.getWordSpacing()) {
113 return false;
114 }
115 if (tsa.fRange.width() != tsb.fRange.width()) {
116 return false;
117 }
118 if (tsa.fRange.start != tsb.fRange.start) {
119 return false;
120 }
121 } else {
122 // TODO: compare placeholders
Julia Lavrova5207f352019-06-21 12:22:32 -0400123 }
124 }
125
126 return true;
127}
128
Julia Lavrovab7b0b3a2019-07-30 13:32:08 -0400129struct ParagraphCache::Entry {
Julia Lavrova5207f352019-06-21 12:22:32 -0400130
Julia Lavrovab7b0b3a2019-07-30 13:32:08 -0400131 Entry(ParagraphCacheValue* value) : fValue(value) {}
132 ParagraphCacheValue* fValue;
133};
Julia Lavrova5207f352019-06-21 12:22:32 -0400134
Julia Lavrovab7b0b3a2019-07-30 13:32:08 -0400135ParagraphCache::ParagraphCache()
136 : fChecker([](ParagraphImpl* impl, const char*, bool){ })
137 , fLRUCacheMap(kMaxEntries)
138 , fCacheIsOn(true)
139#ifdef PARAGRAPH_CACHE_STATS
140 , fTotalRequests(0)
141 , fCacheMisses(0)
142 , fHashMisses(0)
143#endif
144{ }
145
146ParagraphCache::~ParagraphCache() { }
147
148void ParagraphCache::updateFrom(const ParagraphImpl* paragraph, Entry* entry) {
149
150 entry->fValue->fInternalState = paragraph->state();
Julia Lavrovab7b0b3a2019-07-30 13:32:08 -0400151 for (size_t i = 0; i < paragraph->fRuns.size(); ++i) {
152 auto& run = paragraph->fRuns[i];
153 if (run.fSpaced) {
154 entry->fValue->fRuns[i] = run;
Julia Lavrova5207f352019-06-21 12:22:32 -0400155 }
156 }
Julia Lavrova5207f352019-06-21 12:22:32 -0400157}
158
Julia Lavrovab7b0b3a2019-07-30 13:32:08 -0400159void ParagraphCache::updateTo(ParagraphImpl* paragraph, const Entry* entry) {
160 paragraph->fRuns.reset();
161 paragraph->fRuns = entry->fValue->fRuns;
162 for (auto& run : paragraph->fRuns) {
163 run.setMaster(paragraph);
164 }
Julia Lavrova5207f352019-06-21 12:22:32 -0400165
Julia Lavrovab7b0b3a2019-07-30 13:32:08 -0400166 paragraph->fClusters.reset();
167 paragraph->fClusters = entry->fValue->fClusters;
168 for (auto& cluster : paragraph->fClusters) {
169 cluster.setMaster(paragraph);
170 }
171
Julia Lavrovab7b0b3a2019-07-30 13:32:08 -0400172 paragraph->fState = entry->fValue->fInternalState;
173}
174
175void ParagraphCache::printStatistics() {
176 SkDebugf("--- Paragraph Cache ---\n");
177 SkDebugf("Total requests: %d\n", fTotalRequests);
178 SkDebugf("Cache misses: %d\n", fCacheMisses);
179 SkDebugf("Cache miss %%: %f\n", (fTotalRequests > 0) ? 100.f * fCacheMisses / fTotalRequests : 0.f);
180 int cacheHits = fTotalRequests - fCacheMisses;
181 SkDebugf("Hash miss %%: %f\n", (cacheHits > 0) ? 100.f * fHashMisses / cacheHits : 0.f);
182 SkDebugf("---------------------\n");
183}
184
185void ParagraphCache::abandon() {
186 SkAutoMutexExclusive lock(fParagraphMutex);
187 fLRUCacheMap.foreach([](std::unique_ptr<Entry>* e) {
188 });
189
190 this->reset();
191}
192
193void ParagraphCache::reset() {
194 SkAutoMutexExclusive lock(fParagraphMutex);
195#ifdef PARAGRAPH_CACHE_STATS
196 fTotalRequests = 0;
197 fCacheMisses = 0;
198 fHashMisses = 0;
199#endif
200 fLRUCacheMap.reset();
201}
202
203bool ParagraphCache::findParagraph(ParagraphImpl* paragraph) {
204 if (!fCacheIsOn) {
205 return false;
206 }
207#ifdef PARAGRAPH_CACHE_STATS
208 ++fTotalRequests;
209#endif
210 SkAutoMutexExclusive lock(fParagraphMutex);
211 ParagraphCacheKey key(paragraph);
212 std::unique_ptr<Entry>* entry = fLRUCacheMap.find(key);
213 if (!entry) {
214 // We have a cache miss
215#ifdef PARAGRAPH_CACHE_STATS
216 ++fCacheMisses;
217#endif
218 fChecker(paragraph, "missingParagraph", true);
219 return false;
220 }
221 updateTo(paragraph, entry->get());
222 fChecker(paragraph, "foundParagraph", true);
223 return true;
224}
225
226bool ParagraphCache::updateParagraph(ParagraphImpl* paragraph) {
227 if (!fCacheIsOn) {
228 return false;
229 }
230#ifdef PARAGRAPH_CACHE_STATS
231 ++fTotalRequests;
232#endif
233 SkAutoMutexExclusive lock(fParagraphMutex);
234 ParagraphCacheKey key(paragraph);
235 std::unique_ptr<Entry>* entry = fLRUCacheMap.find(key);
236 if (!entry) {
237 ParagraphCacheValue* value = new ParagraphCacheValue(paragraph);
238 fLRUCacheMap.insert(key, std::unique_ptr<Entry>(new Entry(value)));
239 fChecker(paragraph, "addedParagraph", true);
240 return true;
241 } else {
242 updateFrom(paragraph, entry->get());
243 fChecker(paragraph, "updatedParagraph", true);
244 return false;
Julia Lavrova5207f352019-06-21 12:22:32 -0400245 }
246}
Julia Lavrova5207f352019-06-21 12:22:32 -0400247}
248}