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 "SkTDynamicHash.h"
13 #include "SkTextBlob.h"
14 
15 class GrTextBlobCache {
16 public:
17     typedef GrAtlasTextContext::BitmapTextBlob BitmapTextBlob;
18 
19     /**
20      * The callback function used by the cache when it is still over budget after a purge. The
21      * passed in 'data' is the same 'data' handed to setOverbudgetCallback.
22      */
23     typedef void (*PFOverBudgetCB)(void* data);
24 
GrTextBlobCache(PFOverBudgetCB cb,void * data)25     GrTextBlobCache(PFOverBudgetCB cb, void* data)
26         : fPool(kPreAllocSize, kMinGrowthSize)
27         , fCallback(cb)
28         , fData(data) {
29         SkASSERT(cb && data);
30     }
31     ~GrTextBlobCache();
32 
33     // creates an uncached blob
34     BitmapTextBlob* createBlob(int glyphCount, int runCount, size_t maxVASize);
createBlob(const SkTextBlob * blob,size_t maxVAStride)35     BitmapTextBlob* createBlob(const SkTextBlob* blob, size_t maxVAStride) {
36         int glyphCount = 0;
37         int runCount = 0;
38         BlobGlyphCount(&glyphCount, &runCount, blob);
39         BitmapTextBlob* cacheBlob = this->createBlob(glyphCount, runCount, maxVAStride);
40         return cacheBlob;
41     }
42 
createCachedBlob(const SkTextBlob * blob,const BitmapTextBlob::Key & key,const SkMaskFilter::BlurRec & blurRec,const SkPaint & paint,size_t maxVAStride)43     BitmapTextBlob* createCachedBlob(const SkTextBlob* blob,
44                                      const BitmapTextBlob::Key& key,
45                                      const SkMaskFilter::BlurRec& blurRec,
46                                      const SkPaint& paint,
47                                      size_t maxVAStride) {
48         int glyphCount = 0;
49         int runCount = 0;
50         BlobGlyphCount(&glyphCount, &runCount, blob);
51         BitmapTextBlob* cacheBlob = this->createBlob(glyphCount, runCount, maxVAStride);
52         cacheBlob->fKey = key;
53         if (key.fHasBlur) {
54             cacheBlob->fBlurRec = blurRec;
55         }
56         if (key.fStyle != SkPaint::kFill_Style) {
57             cacheBlob->fStrokeInfo.fFrameWidth = paint.getStrokeWidth();
58             cacheBlob->fStrokeInfo.fMiterLimit = paint.getStrokeMiter();
59             cacheBlob->fStrokeInfo.fJoin = paint.getStrokeJoin();
60         }
61         this->add(cacheBlob);
62         return cacheBlob;
63     }
64 
find(const BitmapTextBlob::Key & key)65     BitmapTextBlob* find(const BitmapTextBlob::Key& key) {
66         return fCache.find(key);
67     }
68 
remove(BitmapTextBlob * blob)69     void remove(BitmapTextBlob* blob) {
70         fCache.remove(blob->fKey);
71         fBlobList.remove(blob);
72         blob->unref();
73     }
74 
add(BitmapTextBlob * blob)75     void add(BitmapTextBlob* blob) {
76         fCache.add(blob);
77         fBlobList.addToHead(blob);
78 
79         // If we are overbudget, then unref until we are below budget again
80         if (fPool.size() > kBudget) {
81             BitmapBlobList::Iter iter;
82             iter.init(fBlobList, BitmapBlobList::Iter::kTail_IterStart);
83             BitmapTextBlob* lruBlob = iter.get();
84             SkASSERT(lruBlob);
85             while (fPool.size() > kBudget && (lruBlob = iter.get()) && lruBlob != blob) {
86                 fCache.remove(lruBlob->fKey);
87 
88                 // Backup the iterator before removing and unrefing the blob
89                 iter.prev();
90                 fBlobList.remove(lruBlob);
91                 lruBlob->unref();
92             }
93 
94             // If we break out of the loop with lruBlob == blob, then we haven't purged enough
95             // use the call back and try to free some more.  If we are still overbudget after this,
96             // then this single textblob is over our budget
97             if (lruBlob == blob) {
98                 (*fCallback)(fData);
99             }
100 
101 #ifdef SK_DEBUG
102             if (fPool.size() > kBudget) {
103                 SkDebugf("Single textblob is larger than our whole budget");
104             }
105 #endif
106         }
107     }
108 
makeMRU(BitmapTextBlob * blob)109     void makeMRU(BitmapTextBlob* blob) {
110         if (fBlobList.head() == blob) {
111             return;
112         }
113 
114         fBlobList.remove(blob);
115         fBlobList.addToHead(blob);
116     }
117 
118     void freeAll();
119 
120 private:
121     // TODO move to SkTextBlob
BlobGlyphCount(int * glyphCount,int * runCount,const SkTextBlob * blob)122     void BlobGlyphCount(int* glyphCount, int* runCount, const SkTextBlob* blob) {
123         SkTextBlob::RunIterator itCounter(blob);
124         for (; !itCounter.done(); itCounter.next(), (*runCount)++) {
125             *glyphCount += itCounter.glyphCount();
126         }
127     }
128 
129     typedef SkTInternalLList<BitmapTextBlob> BitmapBlobList;
130 
131     // Budget was chosen to be ~4 megabytes.  The min alloc and pre alloc sizes in the pool are
132     // based off of the largest cached textblob I have seen in the skps(a couple of kilobytes).
133     static const int kPreAllocSize = 1 << 17;
134     static const int kMinGrowthSize = 1 << 17;
135     static const int kBudget = 1 << 22;
136     BitmapBlobList fBlobList;
137     SkTDynamicHash<BitmapTextBlob, BitmapTextBlob::Key> fCache;
138     GrMemoryPool fPool;
139     PFOverBudgetCB fCallback;
140     void* fData;
141 };
142 
143 #endif
144