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/LayoutCore.h"
21
22 #include <mutex>
23
24 #include <utils/LruCache.h>
25
26 #include "minikin/FontCollection.h"
27 #include "minikin/Hasher.h"
28 #include "minikin/MinikinPaint.h"
29
30 #ifdef _WIN32
31 #include <io.h>
32 #endif
33
34 namespace minikin {
35 const uint32_t LENGTH_LIMIT_CACHE = 128;
36 // Layout cache datatypes
37 class LayoutCacheKey {
38 public:
LayoutCacheKey(const U16StringPiece & text,const Range & range,const MinikinPaint & paint,bool dir,StartHyphenEdit startHyphen,EndHyphenEdit endHyphen)39 LayoutCacheKey(const U16StringPiece& text, const Range& range, const MinikinPaint& paint,
40 bool dir, StartHyphenEdit startHyphen, EndHyphenEdit endHyphen)
41 : mChars(text.data()),
42 mNchars(text.size()),
43 mStart(range.getStart()),
44 mCount(range.getLength()),
45 mId(paint.font->getId()),
46 mStyle(paint.fontStyle),
47 mSize(paint.size),
48 mScaleX(paint.scaleX),
49 mSkewX(paint.skewX),
50 mLetterSpacing(paint.letterSpacing),
51 mWordSpacing(paint.wordSpacing),
52 mFontFlags(paint.fontFlags),
53 mLocaleListId(paint.localeListId),
54 mFamilyVariant(paint.familyVariant),
55 mStartHyphen(startHyphen),
56 mEndHyphen(endHyphen),
57 mIsRtl(dir),
58 mHash(computeHash()) {}
59
60 bool operator==(const LayoutCacheKey& o) const {
61 return mId == o.mId && mStart == o.mStart && mCount == o.mCount && mStyle == o.mStyle &&
62 mSize == o.mSize && mScaleX == o.mScaleX && mSkewX == o.mSkewX &&
63 mLetterSpacing == o.mLetterSpacing && mWordSpacing == o.mWordSpacing &&
64 mFontFlags == o.mFontFlags && mLocaleListId == o.mLocaleListId &&
65 mFamilyVariant == o.mFamilyVariant && mStartHyphen == o.mStartHyphen &&
66 mEndHyphen == o.mEndHyphen && mIsRtl == o.mIsRtl && mNchars == o.mNchars &&
67 !memcmp(mChars, o.mChars, mNchars * sizeof(uint16_t));
68 }
69
hash()70 android::hash_t hash() const { return mHash; }
71
copyText()72 void copyText() {
73 uint16_t* charsCopy = new uint16_t[mNchars];
74 memcpy(charsCopy, mChars, mNchars * sizeof(uint16_t));
75 mChars = charsCopy;
76 }
freeText()77 void freeText() {
78 delete[] mChars;
79 mChars = NULL;
80 }
81
getMemoryUsage()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 mFontFlags;
97 uint32_t mLocaleListId;
98 FamilyVariant 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
computeHash()106 android::hash_t computeHash() const {
107 return Hasher()
108 .update(mId)
109 .update(mStart)
110 .update(mCount)
111 .update(mStyle.identifier())
112 .update(mSize)
113 .update(mScaleX)
114 .update(mSkewX)
115 .update(mLetterSpacing)
116 .update(mWordSpacing)
117 .update(mFontFlags)
118 .update(mLocaleListId)
119 .update(static_cast<uint8_t>(mFamilyVariant))
120 .update(packHyphenEdit(mStartHyphen, mEndHyphen))
121 .update(mIsRtl)
122 .updateShorts(mChars, mNchars)
123 .hash();
124 }
125 };
126
127 class LayoutCache : private android::OnEntryRemoved<LayoutCacheKey, LayoutPiece*> {
128 public:
clear()129 void clear() {
130 std::lock_guard<std::mutex> lock(mMutex);
131 mCache.clear();
132 }
133
134 // Do not use LayoutCache inside the callback function, otherwise dead-lock may happen.
135 template <typename F>
getOrCreate(const U16StringPiece & text,const Range & range,const MinikinPaint & paint,bool dir,StartHyphenEdit startHyphen,EndHyphenEdit endHyphen,F & f)136 void getOrCreate(const U16StringPiece& text, const Range& range, const MinikinPaint& paint,
137 bool dir, StartHyphenEdit startHyphen, EndHyphenEdit endHyphen, F& f) {
138 LayoutCacheKey key(text, range, paint, dir, startHyphen, endHyphen);
139 if (paint.skipCache() || range.getLength() >= LENGTH_LIMIT_CACHE) {
140 f(LayoutPiece(text, range, dir, paint, startHyphen, endHyphen), paint);
141 return;
142 }
143 mRequestCount++;
144 {
145 std::lock_guard<std::mutex> lock(mMutex);
146 LayoutPiece* layout = mCache.get(key);
147 if (layout != nullptr) {
148 mCacheHitCount++;
149 f(*layout, paint);
150 return;
151 }
152 }
153 // Doing text layout takes long time, so releases the mutex during doing layout.
154 // Don't care even if we do the same layout in other thred.
155 key.copyText();
156 std::unique_ptr<LayoutPiece> layout =
157 std::make_unique<LayoutPiece>(text, range, dir, paint, startHyphen, endHyphen);
158 f(*layout, paint);
159 {
160 std::lock_guard<std::mutex> lock(mMutex);
161 mCache.put(key, layout.release());
162 }
163 }
164
dumpStats(int fd)165 void dumpStats(int fd) {
166 std::lock_guard<std::mutex> lock(mMutex);
167 #ifdef _WIN32
168 float ratio = (mRequestCount == 0) ? 0 : mCacheHitCount / (float)mRequestCount;
169 int count = _scprintf(
170 "\nLayout Cache Info:\n Usage: %zd/%zd entries\n Hit ratio: %d/%d (%f)\n",
171 mCache.size(), kMaxEntries, mCacheHitCount, mRequestCount, ratio);
172 int size = count + 1;
173 char* buffer = new char[size];
174 sprintf_s(buffer, size,
175 "\nLayout Cache Info:\n Usage: %zd/%zd entries\n Hit ratio: %d/%d (%f)\n",
176 mCache.size(), kMaxEntries, mCacheHitCount, mRequestCount, ratio);
177 _write(fd, buffer, sizeof(buffer));
178 #else
179 dprintf(fd, "\nLayout Cache Info:\n");
180 dprintf(fd, " Usage: %zd/%zd entries\n", mCache.size(), kMaxEntries);
181 float ratio = (mRequestCount == 0) ? 0 : mCacheHitCount / (float)mRequestCount;
182 dprintf(fd, " Hit ratio: %d/%d (%f)\n", mCacheHitCount, mRequestCount, ratio);
183 #endif
184 }
185
getInstance()186 static LayoutCache& getInstance() {
187 static LayoutCache cache(kMaxEntries);
188 return cache;
189 }
190
191 protected:
LayoutCache(uint32_t maxEntries)192 LayoutCache(uint32_t maxEntries) : mCache(maxEntries), mRequestCount(0), mCacheHitCount(0) {
193 mCache.setOnEntryRemovedListener(this);
194 }
195
getCacheSize()196 uint32_t getCacheSize() {
197 std::lock_guard<std::mutex> lock(mMutex);
198 return mCache.size();
199 }
200
201 private:
202 // callback for OnEntryRemoved
operator()203 void operator()(LayoutCacheKey& key, LayoutPiece*& value) {
204 key.freeText();
205 delete value;
206 }
207
208 android::LruCache<LayoutCacheKey, LayoutPiece*> mCache GUARDED_BY(mMutex);
209
210 int32_t mRequestCount;
211 int32_t mCacheHitCount;
212
213 // static const size_t kMaxEntries = LruCache<LayoutCacheKey, Layout*>::kUnlimitedCapacity;
214
215 // TODO: eviction based on memory footprint; for now, we just use a constant
216 // number of strings
217 static const size_t kMaxEntries = 5000;
218
219 std::mutex mMutex;
220 };
221
hash_type(const LayoutCacheKey & key)222 inline android::hash_t hash_type(const LayoutCacheKey& key) {
223 return key.hash();
224 }
225
226 } // namespace minikin
227 #endif // MINIKIN_LAYOUT_CACHE_H
228