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
18class GrTextBlobCache {
19public:
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)
27        : fPool(0u, kMinGrowthSize)
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
109private:
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
175    static const int kMinGrowthSize = 1 << 16;
176    static const int kDefaultBudget = 1 << 22;
177    GrMemoryPool fPool;
178    BitmapBlobList fBlobList;
179    SkTHashMap<uint32_t, BlobIDCacheEntry> fBlobIDCache;
180    PFOverBudgetCB fCallback;
181    void* fData;
182    size_t fBudget;
183    uint32_t fUniqueID;      // unique id to use for messaging
184    SkMessageBus<PurgeBlobMessage>::Inbox fPurgeBlobInbox;
185};
186
187#endif
188