• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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