| 1 | // Copyright 2019 Google LLC. | 
|---|
| 2 | #include <memory> | 
|---|
| 3 |  | 
|---|
| 4 | #include "modules/skparagraph/include/ParagraphCache.h" | 
|---|
| 5 | #include "modules/skparagraph/src/ParagraphImpl.h" | 
|---|
| 6 |  | 
|---|
| 7 | namespace skia { | 
|---|
| 8 | namespace textlayout { | 
|---|
| 9 |  | 
|---|
| 10 | namespace { | 
|---|
| 11 | SkScalar relax(SkScalar a) { | 
|---|
| 12 | // This rounding is done to match Flutter tests. Must be removed.. | 
|---|
| 13 | if (SkScalarIsFinite(a)) { | 
|---|
| 14 | auto threshold = SkIntToScalar(1 << 12); | 
|---|
| 15 | return SkScalarRoundToScalar(a * threshold)/threshold; | 
|---|
| 16 | } else { | 
|---|
| 17 | return a; | 
|---|
| 18 | } | 
|---|
| 19 | } | 
|---|
| 20 | }  // namespace | 
|---|
| 21 |  | 
|---|
| 22 | class ParagraphCacheKey { | 
|---|
| 23 | public: | 
|---|
| 24 | ParagraphCacheKey(const ParagraphImpl* paragraph) | 
|---|
| 25 | : fText(paragraph->fText.c_str(), paragraph->fText.size()) | 
|---|
| 26 | , fPlaceholders(paragraph->fPlaceholders) | 
|---|
| 27 | , fTextStyles(paragraph->fTextStyles) | 
|---|
| 28 | , fParagraphStyle(paragraph->paragraphStyle()) { } | 
|---|
| 29 |  | 
|---|
| 30 | SkString fText; | 
|---|
| 31 | SkTArray<Placeholder, true> fPlaceholders; | 
|---|
| 32 | SkTArray<Block, true> fTextStyles; | 
|---|
| 33 | ParagraphStyle fParagraphStyle; | 
|---|
| 34 | }; | 
|---|
| 35 |  | 
|---|
| 36 | class ParagraphCacheValue { | 
|---|
| 37 | public: | 
|---|
| 38 | ParagraphCacheValue(const ParagraphImpl* paragraph) | 
|---|
| 39 | : fKey(ParagraphCacheKey(paragraph)) | 
|---|
| 40 | , fRuns(paragraph->fRuns) | 
|---|
| 41 | , fCodeUnitProperties(paragraph->fCodeUnitProperties) | 
|---|
| 42 | , fWords(paragraph->fWords) | 
|---|
| 43 | , fBidiRegions(paragraph->fBidiRegions) | 
|---|
| 44 | , fUTF8IndexForUTF16Index(paragraph->fUTF8IndexForUTF16Index) | 
|---|
| 45 | , fUTF16IndexForUTF8Index(paragraph->fUTF16IndexForUTF8Index) { } | 
|---|
| 46 |  | 
|---|
| 47 | // Input == key | 
|---|
| 48 | ParagraphCacheKey fKey; | 
|---|
| 49 |  | 
|---|
| 50 | // Shaped results | 
|---|
| 51 | SkTArray<Run, false> fRuns; | 
|---|
| 52 | // ICU results | 
|---|
| 53 | SkTArray<CodeUnitFlags> fCodeUnitProperties; | 
|---|
| 54 | std::vector<size_t> fWords; | 
|---|
| 55 | std::vector<BidiRegion> fBidiRegions; | 
|---|
| 56 | SkTArray<TextIndex, true> fUTF8IndexForUTF16Index; | 
|---|
| 57 | SkTArray<size_t, true> fUTF16IndexForUTF8Index; | 
|---|
| 58 | }; | 
|---|
| 59 |  | 
|---|
| 60 | uint32_t ParagraphCache::KeyHash::mix(uint32_t hash, uint32_t data) const { | 
|---|
| 61 | hash += data; | 
|---|
| 62 | hash += (hash << 10); | 
|---|
| 63 | hash ^= (hash >> 6); | 
|---|
| 64 | return hash; | 
|---|
| 65 | } | 
|---|
| 66 |  | 
|---|
| 67 | uint32_t ParagraphCache::KeyHash::operator()(const ParagraphCacheKey& key) const { | 
|---|
| 68 | uint32_t hash = 0; | 
|---|
| 69 | for (auto& ph : key.fPlaceholders) { | 
|---|
| 70 | if (ph.fRange.width() == 0) { | 
|---|
| 71 | continue; | 
|---|
| 72 | } | 
|---|
| 73 | hash = mix(hash, SkGoodHash()(ph.fRange.start)); | 
|---|
| 74 | hash = mix(hash, SkGoodHash()(ph.fRange.end)); | 
|---|
| 75 | hash = mix(hash, SkGoodHash()(relax(ph.fStyle.fHeight))); | 
|---|
| 76 | hash = mix(hash, SkGoodHash()(relax(ph.fStyle.fWidth))); | 
|---|
| 77 | hash = mix(hash, SkGoodHash()(ph.fStyle.fAlignment)); | 
|---|
| 78 | hash = mix(hash, SkGoodHash()(ph.fStyle.fBaseline)); | 
|---|
| 79 | if (ph.fStyle.fAlignment == PlaceholderAlignment::kBaseline) { | 
|---|
| 80 | hash = mix(hash, SkGoodHash()(relax(ph.fStyle.fBaselineOffset))); | 
|---|
| 81 | } | 
|---|
| 82 | } | 
|---|
| 83 |  | 
|---|
| 84 | for (auto& ts : key.fTextStyles) { | 
|---|
| 85 | if (ts.fStyle.isPlaceholder()) { | 
|---|
| 86 | continue; | 
|---|
| 87 | } | 
|---|
| 88 | hash = mix(hash, SkGoodHash()(relax(ts.fStyle.getLetterSpacing()))); | 
|---|
| 89 | hash = mix(hash, SkGoodHash()(relax(ts.fStyle.getWordSpacing()))); | 
|---|
| 90 | hash = mix(hash, SkGoodHash()(ts.fStyle.getLocale())); | 
|---|
| 91 | hash = mix(hash, SkGoodHash()(relax(ts.fStyle.getHeight()))); | 
|---|
| 92 | for (auto& ff : ts.fStyle.getFontFamilies()) { | 
|---|
| 93 | hash = mix(hash, SkGoodHash()(ff)); | 
|---|
| 94 | } | 
|---|
| 95 | for (auto& ff : ts.fStyle.getFontFeatures()) { | 
|---|
| 96 | hash = mix(hash, SkGoodHash()(ff.fValue)); | 
|---|
| 97 | hash = mix(hash, SkGoodHash()(ff.fName)); | 
|---|
| 98 | } | 
|---|
| 99 | hash = mix(hash, SkGoodHash()(ts.fStyle.getFontStyle())); | 
|---|
| 100 | hash = mix(hash, SkGoodHash()(relax(ts.fStyle.getFontSize()))); | 
|---|
| 101 | hash = mix(hash, SkGoodHash()(ts.fRange)); | 
|---|
| 102 | } | 
|---|
| 103 |  | 
|---|
| 104 | hash = mix(hash, SkGoodHash()(relax(key.fParagraphStyle.getHeight()))); | 
|---|
| 105 | hash = mix(hash, SkGoodHash()(key.fParagraphStyle.getTextDirection())); | 
|---|
| 106 |  | 
|---|
| 107 | auto& strutStyle = key.fParagraphStyle.getStrutStyle(); | 
|---|
| 108 | if (strutStyle.getStrutEnabled()) { | 
|---|
| 109 | hash = mix(hash, SkGoodHash()(relax(strutStyle.getHeight()))); | 
|---|
| 110 | hash = mix(hash, SkGoodHash()(relax(strutStyle.getLeading()))); | 
|---|
| 111 | hash = mix(hash, SkGoodHash()(relax(strutStyle.getFontSize()))); | 
|---|
| 112 | hash = mix(hash, SkGoodHash()(strutStyle.getHeightOverride())); | 
|---|
| 113 | hash = mix(hash, SkGoodHash()(strutStyle.getFontStyle())); | 
|---|
| 114 | hash = mix(hash, SkGoodHash()(strutStyle.getForceStrutHeight())); | 
|---|
| 115 | for (auto& ff : strutStyle.getFontFamilies()) { | 
|---|
| 116 | hash = mix(hash, SkGoodHash()(ff)); | 
|---|
| 117 | } | 
|---|
| 118 | } | 
|---|
| 119 |  | 
|---|
| 120 | hash = mix(hash, SkGoodHash()(key.fText)); | 
|---|
| 121 | return hash; | 
|---|
| 122 | } | 
|---|
| 123 |  | 
|---|
| 124 | bool operator==(const ParagraphCacheKey& a, const ParagraphCacheKey& b) { | 
|---|
| 125 | if (a.fText.size() != b.fText.size()) { | 
|---|
| 126 | return false; | 
|---|
| 127 | } | 
|---|
| 128 | if (a.fPlaceholders.count() != b.fPlaceholders.count()) { | 
|---|
| 129 | return false; | 
|---|
| 130 | } | 
|---|
| 131 | if (a.fText != b.fText) { | 
|---|
| 132 | return false; | 
|---|
| 133 | } | 
|---|
| 134 | if (a.fTextStyles.size() != b.fTextStyles.size()) { | 
|---|
| 135 | return false; | 
|---|
| 136 | } | 
|---|
| 137 |  | 
|---|
| 138 | // There is no need to compare default paragraph styles - they are included into fTextStyles | 
|---|
| 139 | if (!nearlyEqual(a.fParagraphStyle.getHeight(), b.fParagraphStyle.getHeight())) { | 
|---|
| 140 | return false; | 
|---|
| 141 | } | 
|---|
| 142 | if (a.fParagraphStyle.getTextDirection() != b.fParagraphStyle.getTextDirection()) { | 
|---|
| 143 | return false; | 
|---|
| 144 | } | 
|---|
| 145 |  | 
|---|
| 146 | if (!(a.fParagraphStyle.getStrutStyle() == b.fParagraphStyle.getStrutStyle())) { | 
|---|
| 147 | return false; | 
|---|
| 148 | } | 
|---|
| 149 |  | 
|---|
| 150 | for (size_t i = 0; i < a.fTextStyles.size(); ++i) { | 
|---|
| 151 | auto& tsa = a.fTextStyles[i]; | 
|---|
| 152 | auto& tsb = b.fTextStyles[i]; | 
|---|
| 153 | if (tsa.fStyle.isPlaceholder()) { | 
|---|
| 154 | continue; | 
|---|
| 155 | } | 
|---|
| 156 | if (!(tsa.fStyle.equalsByFonts(tsb.fStyle))) { | 
|---|
| 157 | return false; | 
|---|
| 158 | } | 
|---|
| 159 | if (tsa.fRange.width() != tsb.fRange.width()) { | 
|---|
| 160 | return false; | 
|---|
| 161 | } | 
|---|
| 162 | if (tsa.fRange.start != tsb.fRange.start) { | 
|---|
| 163 | return false; | 
|---|
| 164 | } | 
|---|
| 165 | } | 
|---|
| 166 | for (size_t i = 0; i < a.fPlaceholders.size(); ++i) { | 
|---|
| 167 | auto& tsa = a.fPlaceholders[i]; | 
|---|
| 168 | auto& tsb = b.fPlaceholders[i]; | 
|---|
| 169 | if (tsa.fRange.width() == 0 && tsb.fRange.width() == 0) { | 
|---|
| 170 | continue; | 
|---|
| 171 | } | 
|---|
| 172 | if (!(tsa.fStyle.equals(tsb.fStyle))) { | 
|---|
| 173 | return false; | 
|---|
| 174 | } | 
|---|
| 175 | if (tsa.fRange.width() != tsb.fRange.width()) { | 
|---|
| 176 | return false; | 
|---|
| 177 | } | 
|---|
| 178 | if (tsa.fRange.start != tsb.fRange.start) { | 
|---|
| 179 | return false; | 
|---|
| 180 | } | 
|---|
| 181 | } | 
|---|
| 182 |  | 
|---|
| 183 | return true; | 
|---|
| 184 | } | 
|---|
| 185 |  | 
|---|
| 186 | struct ParagraphCache::Entry { | 
|---|
| 187 |  | 
|---|
| 188 | Entry(ParagraphCacheValue* value) : fValue(value) {} | 
|---|
| 189 | std::unique_ptr<ParagraphCacheValue> fValue; | 
|---|
| 190 | }; | 
|---|
| 191 |  | 
|---|
| 192 | ParagraphCache::ParagraphCache() | 
|---|
| 193 | : fChecker([](ParagraphImpl* impl, const char*, bool){ }) | 
|---|
| 194 | , fLRUCacheMap(kMaxEntries) | 
|---|
| 195 | , fCacheIsOn(true) | 
|---|
| 196 | #ifdef PARAGRAPH_CACHE_STATS | 
|---|
| 197 | , fTotalRequests(0) | 
|---|
| 198 | , fCacheMisses(0) | 
|---|
| 199 | , fHashMisses(0) | 
|---|
| 200 | #endif | 
|---|
| 201 | { } | 
|---|
| 202 |  | 
|---|
| 203 | ParagraphCache::~ParagraphCache() { } | 
|---|
| 204 |  | 
|---|
| 205 | void ParagraphCache::updateTo(ParagraphImpl* paragraph, const Entry* entry) { | 
|---|
| 206 |  | 
|---|
| 207 | paragraph->fRuns.reset(); | 
|---|
| 208 | paragraph->fRuns = entry->fValue->fRuns; | 
|---|
| 209 | paragraph->fCodeUnitProperties = entry->fValue->fCodeUnitProperties; | 
|---|
| 210 | paragraph->fWords = entry->fValue->fWords; | 
|---|
| 211 | paragraph->fBidiRegions = entry->fValue->fBidiRegions; | 
|---|
| 212 | paragraph->fUTF8IndexForUTF16Index = entry->fValue->fUTF8IndexForUTF16Index; | 
|---|
| 213 | paragraph->fUTF16IndexForUTF8Index = entry->fValue->fUTF16IndexForUTF8Index; | 
|---|
| 214 | for (auto& run : paragraph->fRuns) { | 
|---|
| 215 | run.setOwner(paragraph); | 
|---|
| 216 | } | 
|---|
| 217 | } | 
|---|
| 218 |  | 
|---|
| 219 | void ParagraphCache::printStatistics() { | 
|---|
| 220 | SkDebugf( "--- Paragraph Cache ---\n"); | 
|---|
| 221 | SkDebugf( "Total requests: %d\n", fTotalRequests); | 
|---|
| 222 | SkDebugf( "Cache misses: %d\n", fCacheMisses); | 
|---|
| 223 | SkDebugf( "Cache miss %%: %f\n", (fTotalRequests > 0) ? 100.f * fCacheMisses / fTotalRequests : 0.f); | 
|---|
| 224 | int cacheHits = fTotalRequests - fCacheMisses; | 
|---|
| 225 | SkDebugf( "Hash miss %%: %f\n", (cacheHits > 0) ? 100.f * fHashMisses / cacheHits : 0.f); | 
|---|
| 226 | SkDebugf( "---------------------\n"); | 
|---|
| 227 | } | 
|---|
| 228 |  | 
|---|
| 229 | void ParagraphCache::abandon() { | 
|---|
| 230 | SkAutoMutexExclusive lock(fParagraphMutex); | 
|---|
| 231 | fLRUCacheMap.foreach([](ParagraphCacheKey*, std::unique_ptr<Entry>* e) { | 
|---|
| 232 | }); | 
|---|
| 233 |  | 
|---|
| 234 | this->reset(); | 
|---|
| 235 | } | 
|---|
| 236 |  | 
|---|
| 237 | void ParagraphCache::reset() { | 
|---|
| 238 | SkAutoMutexExclusive lock(fParagraphMutex); | 
|---|
| 239 | #ifdef PARAGRAPH_CACHE_STATS | 
|---|
| 240 | fTotalRequests = 0; | 
|---|
| 241 | fCacheMisses = 0; | 
|---|
| 242 | fHashMisses = 0; | 
|---|
| 243 | #endif | 
|---|
| 244 | fLRUCacheMap.reset(); | 
|---|
| 245 | } | 
|---|
| 246 |  | 
|---|
| 247 | bool ParagraphCache::findParagraph(ParagraphImpl* paragraph) { | 
|---|
| 248 | if (!fCacheIsOn) { | 
|---|
| 249 | return false; | 
|---|
| 250 | } | 
|---|
| 251 | #ifdef PARAGRAPH_CACHE_STATS | 
|---|
| 252 | ++fTotalRequests; | 
|---|
| 253 | #endif | 
|---|
| 254 | SkAutoMutexExclusive lock(fParagraphMutex); | 
|---|
| 255 | ParagraphCacheKey key(paragraph); | 
|---|
| 256 | std::unique_ptr<Entry>* entry = fLRUCacheMap.find(key); | 
|---|
| 257 |  | 
|---|
| 258 | if (!entry) { | 
|---|
| 259 | // We have a cache miss | 
|---|
| 260 | #ifdef PARAGRAPH_CACHE_STATS | 
|---|
| 261 | ++fCacheMisses; | 
|---|
| 262 | #endif | 
|---|
| 263 | fChecker(paragraph, "missingParagraph", true); | 
|---|
| 264 | return false; | 
|---|
| 265 | } | 
|---|
| 266 | updateTo(paragraph, entry->get()); | 
|---|
| 267 | fChecker(paragraph, "foundParagraph", true); | 
|---|
| 268 | return true; | 
|---|
| 269 | } | 
|---|
| 270 |  | 
|---|
| 271 | bool ParagraphCache::updateParagraph(ParagraphImpl* paragraph) { | 
|---|
| 272 | if (!fCacheIsOn) { | 
|---|
| 273 | return false; | 
|---|
| 274 | } | 
|---|
| 275 | #ifdef PARAGRAPH_CACHE_STATS | 
|---|
| 276 | ++fTotalRequests; | 
|---|
| 277 | #endif | 
|---|
| 278 | SkAutoMutexExclusive lock(fParagraphMutex); | 
|---|
| 279 | ParagraphCacheKey key(paragraph); | 
|---|
| 280 | std::unique_ptr<Entry>* entry = fLRUCacheMap.find(key); | 
|---|
| 281 | if (!entry) { | 
|---|
| 282 | ParagraphCacheValue* value = new ParagraphCacheValue(paragraph); | 
|---|
| 283 | fLRUCacheMap.insert(key, std::make_unique<Entry>(value)); | 
|---|
| 284 | fChecker(paragraph, "addedParagraph", true); | 
|---|
| 285 | return true; | 
|---|
| 286 | } else { | 
|---|
| 287 | // We do not have to update the paragraph | 
|---|
| 288 | return false; | 
|---|
| 289 | } | 
|---|
| 290 | } | 
|---|
| 291 | }  // namespace textlayout | 
|---|
| 292 | }  // namespace skia | 
|---|
| 293 |  | 
|---|