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, uint32_t uniqueID, bool usePool)
27         : fPool(usePool ? new GrMemoryPool(0u, kMinGrowthSize) : nullptr)
28         , fCallback(cb)
29         , fData(data)
30         , fBudget(kDefaultBudget)
31         , fUniqueID(uniqueID)
32         , fPurgeBlobInbox(uniqueID) {
33         SkASSERT(cb && data);
34     }
35     ~GrTextBlobCache();
36 
37     // creates an uncached blob
38     sk_sp<GrAtlasTextBlob> makeBlob(int glyphCount, int runCount) {
39         return GrAtlasTextBlob::Make(fPool, glyphCount, runCount);
40     }
41 
42     sk_sp<GrAtlasTextBlob> makeBlob(const SkTextBlob* blob) {
43         int glyphCount = 0;
44         int runCount = 0;
45         BlobGlyphCount(&glyphCount, &runCount, blob);
46         return GrAtlasTextBlob::Make(fPool, glyphCount, runCount);
47     }
48 
49     sk_sp<GrAtlasTextBlob> makeCachedBlob(const SkTextBlob* blob,
50                                           const GrAtlasTextBlob::Key& key,
51                                           const SkMaskFilterBase::BlurRec& blurRec,
52                                           const SkPaint& paint) {
53         sk_sp<GrAtlasTextBlob> cacheBlob(this->makeBlob(blob));
54         cacheBlob->setupKey(key, blurRec, paint);
55         this->add(cacheBlob);
56         blob->notifyAddedToCache(fUniqueID);
57         return cacheBlob;
58     }
59 
60     sk_sp<GrAtlasTextBlob> find(const GrAtlasTextBlob::Key& key) const {
61         const auto* idEntry = fBlobIDCache.find(key.fUniqueID);
62         return idEntry ? idEntry->find(key) : nullptr;
63     }
64 
65     void remove(GrAtlasTextBlob* blob) {
66         auto  id      = GrAtlasTextBlob::GetKey(*blob).fUniqueID;
67         auto* idEntry = fBlobIDCache.find(id);
68         SkASSERT(idEntry);
69 
70         fBlobList.remove(blob);
71         idEntry->removeBlob(blob);
72         if (idEntry->fBlobs.empty()) {
73             fBlobIDCache.remove(id);
74         }
75     }
76 
77     void makeMRU(GrAtlasTextBlob* blob) {
78         if (fBlobList.head() == blob) {
79             return;
80         }
81 
82         fBlobList.remove(blob);
83         fBlobList.addToHead(blob);
84     }
85 
86     void freeAll();
87 
88     // TODO move to SkTextBlob
89     static void BlobGlyphCount(int* glyphCount, int* runCount, const SkTextBlob* blob) {
90         SkTextBlobRunIterator itCounter(blob);
91         for (; !itCounter.done(); itCounter.next(), (*runCount)++) {
92             *glyphCount += itCounter.glyphCount();
93         }
94     }
95 
96     void setBudget(size_t budget) {
97         fBudget = budget;
98         this->checkPurge();
99     }
100 
101     struct PurgeBlobMessage {
102         uint32_t fID;
103     };
104 
105     static void PostPurgeBlobMessage(uint32_t blobID, uint32_t cacheID);
106 
107     void purgeStaleBlobs();
108 
109 private:
110     using BitmapBlobList = SkTInternalLList<GrAtlasTextBlob>;
111 
112     struct BlobIDCacheEntry {
113         BlobIDCacheEntry() : fID(SK_InvalidGenID) {}
114         explicit BlobIDCacheEntry(uint32_t id) : fID(id) {}
115 
116         static uint32_t GetKey(const BlobIDCacheEntry& entry) {
117             return entry.fID;
118         }
119 
120         void addBlob(sk_sp<GrAtlasTextBlob> blob) {
121             SkASSERT(blob);
122             SkASSERT(GrAtlasTextBlob::GetKey(*blob).fUniqueID == fID);
123             SkASSERT(!this->find(GrAtlasTextBlob::GetKey(*blob)));
124 
125             fBlobs.emplace_back(std::move(blob));
126         }
127 
128         void removeBlob(GrAtlasTextBlob* blob) {
129             SkASSERT(blob);
130             SkASSERT(GrAtlasTextBlob::GetKey(*blob).fUniqueID == fID);
131 
132             auto index = this->findBlobIndex(GrAtlasTextBlob::GetKey(*blob));
133             SkASSERT(index >= 0);
134 
135             fBlobs.removeShuffle(index);
136         }
137 
138         sk_sp<GrAtlasTextBlob> find(const GrAtlasTextBlob::Key& key) const {
139             auto index = this->findBlobIndex(key);
140             return index < 0 ? nullptr : fBlobs[index];
141         }
142 
143         int findBlobIndex(const GrAtlasTextBlob::Key& key) const{
144             for (int i = 0; i < fBlobs.count(); ++i) {
145                 if (GrAtlasTextBlob::GetKey(*fBlobs[i]) == key) {
146                     return i;
147                 }
148             }
149             return -1;
150         }
151 
152         uint32_t                             fID;
153         // Current clients don't generate multiple GrAtlasTextBlobs per SkTextBlob, so an array w/
154         // linear search is acceptable.  If usage changes, we should re-evaluate this structure.
155         SkSTArray<1, sk_sp<GrAtlasTextBlob>, true> fBlobs;
156     };
157 
158     void add(sk_sp<GrAtlasTextBlob> blob) {
159         auto  id      = GrAtlasTextBlob::GetKey(*blob).fUniqueID;
160         auto* idEntry = fBlobIDCache.find(id);
161         if (!idEntry) {
162             idEntry = fBlobIDCache.set(id, BlobIDCacheEntry(id));
163         }
164 
165         // Safe to retain a raw ptr temporarily here, because the cache will hold a ref.
166         GrAtlasTextBlob* rawBlobPtr = blob.get();
167         fBlobList.addToHead(rawBlobPtr);
168         idEntry->addBlob(std::move(blob));
169 
170         this->checkPurge(rawBlobPtr);
171     }
172 
173     void checkPurge(GrAtlasTextBlob* blob = nullptr);
174     bool overBudget() const;
175 
176     static const int kMinGrowthSize = 1 << 16;
177     static const int kDefaultBudget = 1 << 22;
178     GrMemoryPool* fPool;
179     BitmapBlobList fBlobList;
180     SkTHashMap<uint32_t, BlobIDCacheEntry> fBlobIDCache;
181     PFOverBudgetCB fCallback;
182     void* fData;
183     size_t fBudget;
184     uint32_t fUniqueID;      // unique id to use for messaging
185     SkMessageBus<PurgeBlobMessage>::Inbox fPurgeBlobInbox;
186 };
187 
188 #endif
189