Home | History | Annotate | Download | only in text
      1 /*
      2  * Copyright 2015 Google Inc.
      3  *
      4  * Use of this source code is governed by a BSD-style license that can be
      5  * found in the LICENSE file.
      6  */
      7 
      8 #ifndef GrTextBlobCache_DEFINED
      9 #define GrTextBlobCache_DEFINED
     10 
     11 #include "GrAtlasTextContext.h"
     12 #include "SkMessageBus.h"
     13 #include "SkRefCnt.h"
     14 #include "SkTArray.h"
     15 #include "SkTextBlobRunIterator.h"
     16 #include "SkTHash.h"
     17 
     18 class GrTextBlobCache {
     19 public:
     20     /**
     21      * The callback function used by the cache when it is still over budget after a purge. The
     22      * passed in 'data' is the same 'data' handed to setOverbudgetCallback.
     23      */
     24     typedef void (*PFOverBudgetCB)(void* data);
     25 
     26     GrTextBlobCache(PFOverBudgetCB cb, void* data)
     27         : fPool(kPreAllocSize, kMinGrowthSize)
     28         , fCallback(cb)
     29         , fData(data)
     30         , fBudget(kDefaultBudget) {
     31         SkASSERT(cb && data);
     32     }
     33     ~GrTextBlobCache();
     34 
     35     // creates an uncached blob
     36     sk_sp<GrAtlasTextBlob> makeBlob(int glyphCount, int runCount) {
     37         return GrAtlasTextBlob::Make(&fPool, glyphCount, runCount);
     38     }
     39 
     40     sk_sp<GrAtlasTextBlob> makeBlob(const SkTextBlob* blob) {
     41         int glyphCount = 0;
     42         int runCount = 0;
     43         BlobGlyphCount(&glyphCount, &runCount, blob);
     44         return GrAtlasTextBlob::Make(&fPool, glyphCount, runCount);
     45     }
     46 
     47     sk_sp<GrAtlasTextBlob> makeCachedBlob(const SkTextBlob* blob,
     48                                           const GrAtlasTextBlob::Key& key,
     49                                           const SkMaskFilter::BlurRec& blurRec,
     50                                           const SkPaint& paint) {
     51         sk_sp<GrAtlasTextBlob> cacheBlob(this->makeBlob(blob));
     52         cacheBlob->setupKey(key, blurRec, paint);
     53         this->add(cacheBlob);
     54         blob->notifyAddedToCache();
     55         return cacheBlob;
     56     }
     57 
     58     sk_sp<GrAtlasTextBlob> find(const GrAtlasTextBlob::Key& key) const {
     59         const auto* idEntry = fBlobIDCache.find(key.fUniqueID);
     60         return idEntry ? idEntry->find(key) : nullptr;
     61     }
     62 
     63     void remove(GrAtlasTextBlob* blob) {
     64         auto  id      = GrAtlasTextBlob::GetKey(*blob).fUniqueID;
     65         auto* idEntry = fBlobIDCache.find(id);
     66         SkASSERT(idEntry);
     67 
     68         fBlobList.remove(blob);
     69         idEntry->removeBlob(blob);
     70         if (idEntry->fBlobs.empty()) {
     71             fBlobIDCache.remove(id);
     72         }
     73     }
     74 
     75     void makeMRU(GrAtlasTextBlob* blob) {
     76         if (fBlobList.head() == blob) {
     77             return;
     78         }
     79 
     80         fBlobList.remove(blob);
     81         fBlobList.addToHead(blob);
     82     }
     83 
     84     void freeAll();
     85 
     86     // TODO move to SkTextBlob
     87     static void BlobGlyphCount(int* glyphCount, int* runCount, const SkTextBlob* blob) {
     88         SkTextBlobRunIterator itCounter(blob);
     89         for (; !itCounter.done(); itCounter.next(), (*runCount)++) {
     90             *glyphCount += itCounter.glyphCount();
     91         }
     92     }
     93 
     94     void setBudget(size_t budget) {
     95         fBudget = budget;
     96         this->checkPurge();
     97     }
     98 
     99     struct PurgeBlobMessage {
    100         uint32_t fID;
    101     };
    102 
    103     static void PostPurgeBlobMessage(uint32_t);
    104 
    105 private:
    106     using BitmapBlobList = SkTInternalLList<GrAtlasTextBlob>;
    107 
    108     struct BlobIDCacheEntry {
    109         BlobIDCacheEntry() : fID(SK_InvalidGenID) {}
    110         explicit BlobIDCacheEntry(uint32_t id) : fID(id) {}
    111 
    112         static uint32_t GetKey(const BlobIDCacheEntry& entry) {
    113             return entry.fID;
    114         }
    115 
    116         void addBlob(sk_sp<GrAtlasTextBlob> blob) {
    117             SkASSERT(blob);
    118             SkASSERT(GrAtlasTextBlob::GetKey(*blob).fUniqueID == fID);
    119             SkASSERT(!this->find(GrAtlasTextBlob::GetKey(*blob)));
    120 
    121             fBlobs.emplace_back(std::move(blob));
    122         }
    123 
    124         void removeBlob(GrAtlasTextBlob* blob) {
    125             SkASSERT(blob);
    126             SkASSERT(GrAtlasTextBlob::GetKey(*blob).fUniqueID == fID);
    127 
    128             auto index = this->findBlobIndex(GrAtlasTextBlob::GetKey(*blob));
    129             SkASSERT(index >= 0);
    130 
    131             fBlobs.removeShuffle(index);
    132         }
    133 
    134         sk_sp<GrAtlasTextBlob> find(const GrAtlasTextBlob::Key& key) const {
    135             auto index = this->findBlobIndex(key);
    136             return index < 0 ? nullptr : fBlobs[index];
    137         }
    138 
    139         int findBlobIndex(const GrAtlasTextBlob::Key& key) const{
    140             for (int i = 0; i < fBlobs.count(); ++i) {
    141                 if (GrAtlasTextBlob::GetKey(*fBlobs[i]) == key) {
    142                     return i;
    143                 }
    144             }
    145             return -1;
    146         }
    147 
    148         uint32_t                             fID;
    149         // Current clients don't generate multiple GrAtlasTextBlobs per SkTextBlob, so an array w/
    150         // linear search is acceptable.  If usage changes, we should re-evaluate this structure.
    151         SkSTArray<1, sk_sp<GrAtlasTextBlob>, true> fBlobs;
    152     };
    153 
    154     void add(sk_sp<GrAtlasTextBlob> blob) {
    155         auto  id      = GrAtlasTextBlob::GetKey(*blob).fUniqueID;
    156         auto* idEntry = fBlobIDCache.find(id);
    157         if (!idEntry) {
    158             idEntry = fBlobIDCache.set(id, BlobIDCacheEntry(id));
    159         }
    160 
    161         // Safe to retain a raw ptr temporarily here, because the cache will hold a ref.
    162         GrAtlasTextBlob* rawBlobPtr = blob.get();
    163         fBlobList.addToHead(rawBlobPtr);
    164         idEntry->addBlob(std::move(blob));
    165 
    166         this->checkPurge(rawBlobPtr);
    167     }
    168 
    169     void checkPurge(GrAtlasTextBlob* blob = nullptr) {
    170         // First, purge all stale blob IDs.
    171         {
    172             SkTArray<PurgeBlobMessage> msgs;
    173             fPurgeBlobInbox.poll(&msgs);
    174 
    175             for (const auto& msg : msgs) {
    176                 auto* idEntry = fBlobIDCache.find(msg.fID);
    177                 if (!idEntry) {
    178                     // no cache entries for id
    179                     continue;
    180                 }
    181 
    182                 // remove all blob entries from the LRU list
    183                 for (const auto& blob : idEntry->fBlobs) {
    184                     fBlobList.remove(blob.get());
    185                 }
    186 
    187                 // drop the idEntry itself (unrefs all blobs)
    188                 fBlobIDCache.remove(msg.fID);
    189             }
    190         }
    191 
    192         // If we are still overbudget, then unref until we are below budget again
    193         if (fPool.size() > fBudget) {
    194             BitmapBlobList::Iter iter;
    195             iter.init(fBlobList, BitmapBlobList::Iter::kTail_IterStart);
    196             GrAtlasTextBlob* lruBlob = nullptr;
    197             while (fPool.size() > fBudget && (lruBlob = iter.get()) && lruBlob != blob) {
    198                 // Backup the iterator before removing and unrefing the blob
    199                 iter.prev();
    200 
    201                 this->remove(lruBlob);
    202             }
    203 
    204             // If we break out of the loop with lruBlob == blob, then we haven't purged enough
    205             // use the call back and try to free some more.  If we are still overbudget after this,
    206             // then this single textblob is over our budget
    207             if (blob && lruBlob == blob) {
    208                 (*fCallback)(fData);
    209             }
    210 
    211 #ifdef SPEW_BUDGET_MESSAGE
    212             if (fPool.size() > fBudget) {
    213                 SkDebugf("Single textblob is larger than our whole budget");
    214             }
    215 #endif
    216         }
    217     }
    218 
    219     // Budget was chosen to be ~4 megabytes.  The min alloc and pre alloc sizes in the pool are
    220     // based off of the largest cached textblob I have seen in the skps(a couple of kilobytes).
    221     static const int kPreAllocSize = 1 << 17;
    222     static const int kMinGrowthSize = 1 << 17;
    223     static const int kDefaultBudget = 1 << 22;
    224     GrMemoryPool fPool;
    225     BitmapBlobList fBlobList;
    226     SkTHashMap<uint32_t, BlobIDCacheEntry> fBlobIDCache;
    227     PFOverBudgetCB fCallback;
    228     void* fData;
    229     size_t fBudget;
    230     SkMessageBus<PurgeBlobMessage>::Inbox fPurgeBlobInbox;
    231 };
    232 
    233 #endif
    234