Home | History | Annotate | Download | only in minikin
      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