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 uint32_t mNchars;
87 uint32_t mStart;
88 uint32_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 {
144 std::lock_guard<std::mutex> lock(mMutex);
145 LayoutPiece* layout = mCache.get(key);
146 if (layout != nullptr) {
147 f(*layout, paint);
148 return;
149 }
150 }
151 // Doing text layout takes long time, so releases the mutex during doing layout.
152 // Don't care even if we do the same layout in other thred.
153 key.copyText();
154 std::unique_ptr<LayoutPiece> layout =
155 std::make_unique<LayoutPiece>(text, range, dir, paint, startHyphen, endHyphen);
156 f(*layout, paint);
157 {
158 std::lock_guard<std::mutex> lock(mMutex);
159 mCache.put(key, layout.release());
160 }
161 }
162
getInstance()163 static LayoutCache& getInstance() {
164 static LayoutCache cache(kMaxEntries);
165 return cache;
166 }
167
168 protected:
LayoutCache(uint32_t maxEntries)169 LayoutCache(uint32_t maxEntries) : mCache(maxEntries) {
170 mCache.setOnEntryRemovedListener(this);
171 }
172
getCacheSize()173 uint32_t getCacheSize() {
174 std::lock_guard<std::mutex> lock(mMutex);
175 return mCache.size();
176 }
177
178 private:
179 // callback for OnEntryRemoved
operator()180 void operator()(LayoutCacheKey& key, LayoutPiece*& value) {
181 key.freeText();
182 delete value;
183 }
184
185 android::LruCache<LayoutCacheKey, LayoutPiece*> mCache GUARDED_BY(mMutex);
186
187 // static const size_t kMaxEntries = LruCache<LayoutCacheKey, Layout*>::kUnlimitedCapacity;
188
189 // TODO: eviction based on memory footprint; for now, we just use a constant
190 // number of strings
191 static const size_t kMaxEntries = 5000;
192
193 std::mutex mMutex;
194 };
195
hash_type(const LayoutCacheKey & key)196 inline android::hash_t hash_type(const LayoutCacheKey& key) {
197 return key.hash();
198 }
199
200 } // namespace minikin
201 #endif // MINIKIN_LAYOUT_CACHE_H
202