1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 #ifndef MINIKIN_LAYOUT_CACHE_H 18 #define MINIKIN_LAYOUT_CACHE_H 19 20 #include "minikin/Layout.h" 21 22 #include <mutex> 23 24 #include <utils/JenkinsHash.h> 25 #include <utils/LruCache.h> 26 27 namespace minikin { 28 29 // Layout cache datatypes 30 class LayoutCacheKey { 31 public: 32 LayoutCacheKey(const U16StringPiece& text, const Range& range, const MinikinPaint& paint, 33 bool dir, StartHyphenEdit startHyphen, EndHyphenEdit endHyphen) 34 : mChars(text.data()), 35 mNchars(text.size()), 36 mStart(range.getStart()), 37 mCount(range.getLength()), 38 mId(paint.font->getId()), 39 mStyle(paint.fontStyle), 40 mSize(paint.size), 41 mScaleX(paint.scaleX), 42 mSkewX(paint.skewX), 43 mLetterSpacing(paint.letterSpacing), 44 mWordSpacing(paint.wordSpacing), 45 mPaintFlags(paint.paintFlags), 46 mLocaleListId(paint.localeListId), 47 mFamilyVariant(paint.familyVariant), 48 mStartHyphen(startHyphen), 49 mEndHyphen(endHyphen), 50 mIsRtl(dir), 51 mHash(computeHash()) {} 52 53 bool operator==(const LayoutCacheKey& o) const { 54 return mId == o.mId && mStart == o.mStart && mCount == o.mCount && mStyle == o.mStyle && 55 mSize == o.mSize && mScaleX == o.mScaleX && mSkewX == o.mSkewX && 56 mLetterSpacing == o.mLetterSpacing && mWordSpacing == o.mWordSpacing && 57 mPaintFlags == o.mPaintFlags && mLocaleListId == o.mLocaleListId && 58 mFamilyVariant == o.mFamilyVariant && mStartHyphen == o.mStartHyphen && 59 mEndHyphen == o.mEndHyphen && mIsRtl == o.mIsRtl && mNchars == o.mNchars && 60 !memcmp(mChars, o.mChars, mNchars * sizeof(uint16_t)); 61 } 62 63 android::hash_t hash() const { return mHash; } 64 65 void copyText() { 66 uint16_t* charsCopy = new uint16_t[mNchars]; 67 memcpy(charsCopy, mChars, mNchars * sizeof(uint16_t)); 68 mChars = charsCopy; 69 } 70 void freeText() { 71 delete[] mChars; 72 mChars = NULL; 73 } 74 75 void doLayout(Layout* layout, const MinikinPaint& paint) const { 76 layout->mAdvances.resize(mCount, 0); 77 layout->mExtents.resize(mCount); 78 layout->doLayoutRun(mChars, mStart, mCount, mNchars, mIsRtl, paint, mStartHyphen, 79 mEndHyphen); 80 } 81 82 uint32_t getMemoryUsage() const { return sizeof(LayoutCacheKey) + sizeof(uint16_t) * mNchars; } 83 84 private: 85 const uint16_t* mChars; 86 size_t mNchars; 87 size_t mStart; 88 size_t mCount; 89 uint32_t mId; // for the font collection 90 FontStyle mStyle; 91 float mSize; 92 float mScaleX; 93 float mSkewX; 94 float mLetterSpacing; 95 float mWordSpacing; 96 int32_t mPaintFlags; 97 uint32_t mLocaleListId; 98 FontFamily::Variant mFamilyVariant; 99 StartHyphenEdit mStartHyphen; 100 EndHyphenEdit mEndHyphen; 101 bool mIsRtl; 102 // Note: any fields added to MinikinPaint must also be reflected here. 103 // TODO: language matching (possibly integrate into style) 104 android::hash_t mHash; 105 106 android::hash_t computeHash() const { 107 uint32_t hash = android::JenkinsHashMix(0, mId); 108 hash = android::JenkinsHashMix(hash, mStart); 109 hash = android::JenkinsHashMix(hash, mCount); 110 hash = android::JenkinsHashMix(hash, android::hash_type(mStyle.identifier())); 111 hash = android::JenkinsHashMix(hash, android::hash_type(mSize)); 112 hash = android::JenkinsHashMix(hash, android::hash_type(mScaleX)); 113 hash = android::JenkinsHashMix(hash, android::hash_type(mSkewX)); 114 hash = android::JenkinsHashMix(hash, android::hash_type(mLetterSpacing)); 115 hash = android::JenkinsHashMix(hash, android::hash_type(mWordSpacing)); 116 hash = android::JenkinsHashMix(hash, android::hash_type(mPaintFlags)); 117 hash = android::JenkinsHashMix(hash, android::hash_type(mLocaleListId)); 118 hash = android::JenkinsHashMix(hash, 119 android::hash_type(static_cast<uint8_t>(mFamilyVariant))); 120 hash = android::JenkinsHashMix( 121 hash, 122 android::hash_type(static_cast<uint8_t>(packHyphenEdit(mStartHyphen, mEndHyphen)))); 123 hash = android::JenkinsHashMix(hash, android::hash_type(mIsRtl)); 124 hash = android::JenkinsHashMixShorts(hash, mChars, mNchars); 125 return android::JenkinsHashWhiten(hash); 126 } 127 }; 128 129 class LayoutCache : private android::OnEntryRemoved<LayoutCacheKey, Layout*> { 130 public: 131 void clear() { 132 std::lock_guard<std::mutex> lock(mMutex); 133 mCache.clear(); 134 } 135 136 // Do not use LayoutCache inside the callback function, otherwise dead-lock may happen. 137 template <typename F> 138 void getOrCreate(const U16StringPiece& text, const Range& range, const MinikinPaint& paint, 139 bool dir, StartHyphenEdit startHyphen, EndHyphenEdit endHyphen, F& f) { 140 LayoutCacheKey key(text, range, paint, dir, startHyphen, endHyphen); 141 if (paint.skipCache()) { 142 Layout layoutForWord; 143 key.doLayout(&layoutForWord, paint); 144 f(layoutForWord); 145 return; 146 } 147 148 mRequestCount++; 149 { 150 std::lock_guard<std::mutex> lock(mMutex); 151 Layout* layout = mCache.get(key); 152 if (layout != nullptr) { 153 mCacheHitCount++; 154 f(*layout); 155 return; 156 } 157 } 158 // Doing text layout takes long time, so releases the mutex during doing layout. 159 // Don't care even if we do the same layout in other thred. 160 key.copyText(); 161 std::unique_ptr<Layout> layout = std::make_unique<Layout>(); 162 key.doLayout(layout.get(), paint); 163 f(*layout); 164 { 165 std::lock_guard<std::mutex> lock(mMutex); 166 mCache.put(key, layout.release()); 167 } 168 } 169 170 void dumpStats(int fd) { 171 std::lock_guard<std::mutex> lock(mMutex); 172 dprintf(fd, "\nLayout Cache Info:\n"); 173 dprintf(fd, " Usage: %zd/%zd entries\n", mCache.size(), kMaxEntries); 174 float ratio = (mRequestCount == 0) ? 0 : mCacheHitCount / (float)mRequestCount; 175 dprintf(fd, " Hit ratio: %d/%d (%f)\n", mCacheHitCount, mRequestCount, ratio); 176 } 177 178 static LayoutCache& getInstance() { 179 static LayoutCache cache(kMaxEntries); 180 return cache; 181 } 182 183 protected: 184 LayoutCache(uint32_t maxEntries) : mCache(maxEntries), mRequestCount(0), mCacheHitCount(0) { 185 mCache.setOnEntryRemovedListener(this); 186 } 187 188 private: 189 // callback for OnEntryRemoved 190 void operator()(LayoutCacheKey& key, Layout*& value) { 191 key.freeText(); 192 delete value; 193 } 194 195 android::LruCache<LayoutCacheKey, Layout*> mCache GUARDED_BY(mMutex); 196 197 int32_t mRequestCount; 198 int32_t mCacheHitCount; 199 200 // static const size_t kMaxEntries = LruCache<LayoutCacheKey, Layout*>::kUnlimitedCapacity; 201 202 // TODO: eviction based on memory footprint; for now, we just use a constant 203 // number of strings 204 static const size_t kMaxEntries = 5000; 205 206 std::mutex mMutex; 207 }; 208 209 inline android::hash_t hash_type(const LayoutCacheKey& key) { 210 return key.hash(); 211 } 212 213 } // namespace minikin 214 #endif // MINIKIN_LAYOUT_CACHE_H 215