1 /*
2  * Copyright 2013 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 #include "include/private/SkMalloc.h"
9 #include "include/private/SkMutex.h"
10 #include "include/private/SkTemplates.h"
11 #include "src/core/SkDiscardableMemory.h"
12 #include "src/core/SkTInternalLList.h"
13 #include "src/lazy/SkDiscardableMemoryPool.h"
14 
15 // Note:
16 // A PoolDiscardableMemory is memory that is counted in a pool.
17 // A DiscardableMemoryPool is a pool of PoolDiscardableMemorys.
18 
19 namespace {
20 
21 class PoolDiscardableMemory;
22 
23 /**
24  *  This non-global pool can be used for unit tests to verify that the
25  *  pool works.
26  */
27 class DiscardableMemoryPool : public SkDiscardableMemoryPool {
28 public:
29     DiscardableMemoryPool(size_t budget);
30     ~DiscardableMemoryPool() override;
31 
32     std::unique_ptr<SkDiscardableMemory> make(size_t bytes);
create(size_t bytes)33     SkDiscardableMemory* create(size_t bytes) override {
34         return this->make(bytes).release();  // TODO: change API
35     }
36 
37     size_t getRAMUsed() override;
38     void setRAMBudget(size_t budget) override;
getRAMBudget()39     size_t getRAMBudget() override { return fBudget; }
40 
41     /** purges all unlocked DMs */
42     void dumpPool() override;
43 
44     #if SK_LAZY_CACHE_STATS  // Defined in SkDiscardableMemoryPool.h
getCacheHits()45     int getCacheHits() override { return fCacheHits; }
getCacheMisses()46     int getCacheMisses() override { return fCacheMisses; }
resetCacheHitsAndMisses()47     void resetCacheHitsAndMisses() override {
48         fCacheHits = fCacheMisses = 0;
49     }
50     int          fCacheHits;
51     int          fCacheMisses;
52     #endif  // SK_LAZY_CACHE_STATS
53 
54 private:
55     SkMutex      fMutex;
56     size_t       fBudget;
57     size_t       fUsed;
58     SkTInternalLList<PoolDiscardableMemory> fList;
59 
60     /** Function called to free memory if needed */
61     void dumpDownTo(size_t budget);
62     /** called by DiscardableMemoryPool upon destruction */
63     void removeFromPool(PoolDiscardableMemory* dm);
64     /** called by DiscardableMemoryPool::lock() */
65     bool lock(PoolDiscardableMemory* dm);
66     /** called by DiscardableMemoryPool::unlock() */
67     void unlock(PoolDiscardableMemory* dm);
68 
69     friend class PoolDiscardableMemory;
70 
71     using INHERITED = SkDiscardableMemory::Factory;
72 };
73 
74 /**
75  *  A PoolDiscardableMemory is a SkDiscardableMemory that relies on
76  *  a DiscardableMemoryPool object to manage the memory.
77  */
78 class PoolDiscardableMemory : public SkDiscardableMemory {
79 public:
80     PoolDiscardableMemory(sk_sp<DiscardableMemoryPool> pool, SkAutoFree pointer, size_t bytes);
81     ~PoolDiscardableMemory() override;
82     bool lock() override;
83     void* data() override;
84     void unlock() override;
85     friend class DiscardableMemoryPool;
86 private:
87     SK_DECLARE_INTERNAL_LLIST_INTERFACE(PoolDiscardableMemory);
88     sk_sp<DiscardableMemoryPool> fPool;
89     bool                         fLocked;
90     SkAutoFree                   fPointer;
91     const size_t                 fBytes;
92 };
93 
PoolDiscardableMemory(sk_sp<DiscardableMemoryPool> pool,SkAutoFree pointer,size_t bytes)94 PoolDiscardableMemory::PoolDiscardableMemory(sk_sp<DiscardableMemoryPool> pool,
95                                              SkAutoFree pointer,
96                                              size_t bytes)
97         : fPool(std::move(pool)), fLocked(true), fPointer(std::move(pointer)), fBytes(bytes) {
98     SkASSERT(fPool != nullptr);
99     SkASSERT(fPointer != nullptr);
100     SkASSERT(fBytes > 0);
101 }
102 
~PoolDiscardableMemory()103 PoolDiscardableMemory::~PoolDiscardableMemory() {
104     SkASSERT(!fLocked); // contract for SkDiscardableMemory
105     fPool->removeFromPool(this);
106 }
107 
lock()108 bool PoolDiscardableMemory::lock() {
109     SkASSERT(!fLocked); // contract for SkDiscardableMemory
110     return fPool->lock(this);
111 }
112 
data()113 void* PoolDiscardableMemory::data() {
114     SkASSERT(fLocked); // contract for SkDiscardableMemory
115     return fPointer.get();
116 }
117 
unlock()118 void PoolDiscardableMemory::unlock() {
119     SkASSERT(fLocked); // contract for SkDiscardableMemory
120     fPool->unlock(this);
121 }
122 
123 ////////////////////////////////////////////////////////////////////////////////
124 
DiscardableMemoryPool(size_t budget)125 DiscardableMemoryPool::DiscardableMemoryPool(size_t budget)
126     : fBudget(budget)
127     , fUsed(0) {
128     #if SK_LAZY_CACHE_STATS
129     fCacheHits = 0;
130     fCacheMisses = 0;
131     #endif  // SK_LAZY_CACHE_STATS
132 }
~DiscardableMemoryPool()133 DiscardableMemoryPool::~DiscardableMemoryPool() {
134     // PoolDiscardableMemory objects that belong to this pool are
135     // always deleted before deleting this pool since each one has a
136     // ref to the pool.
137     SkASSERT(fList.isEmpty());
138 }
139 
dumpDownTo(size_t budget)140 void DiscardableMemoryPool::dumpDownTo(size_t budget) {
141     fMutex.assertHeld();
142     if (fUsed <= budget) {
143         return;
144     }
145     using Iter = SkTInternalLList<PoolDiscardableMemory>::Iter;
146     Iter iter;
147     PoolDiscardableMemory* cur = iter.init(fList, Iter::kTail_IterStart);
148     while ((fUsed > budget) && (cur)) {
149         if (!cur->fLocked) {
150             PoolDiscardableMemory* dm = cur;
151             SkASSERT(dm->fPointer != nullptr);
152             dm->fPointer = nullptr;
153             SkASSERT(fUsed >= dm->fBytes);
154             fUsed -= dm->fBytes;
155             cur = iter.prev();
156             // Purged DMs are taken out of the list.  This saves times
157             // looking them up.  Purged DMs are NOT deleted.
158             fList.remove(dm);
159         } else {
160             cur = iter.prev();
161         }
162     }
163 }
164 
make(size_t bytes)165 std::unique_ptr<SkDiscardableMemory> DiscardableMemoryPool::make(size_t bytes) {
166     SkAutoFree addr(sk_malloc_canfail(bytes));
167     if (nullptr == addr) {
168         return nullptr;
169     }
170     auto dm = std::make_unique<PoolDiscardableMemory>(sk_ref_sp(this), std::move(addr), bytes);
171     SkAutoMutexExclusive autoMutexAcquire(fMutex);
172     fList.addToHead(dm.get());
173     fUsed += bytes;
174     this->dumpDownTo(fBudget);
175     return std::move(dm);
176 }
177 
removeFromPool(PoolDiscardableMemory * dm)178 void DiscardableMemoryPool::removeFromPool(PoolDiscardableMemory* dm) {
179     SkAutoMutexExclusive autoMutexAcquire(fMutex);
180     // This is called by dm's destructor.
181     if (dm->fPointer != nullptr) {
182         SkASSERT(fUsed >= dm->fBytes);
183         fUsed -= dm->fBytes;
184         fList.remove(dm);
185     } else {
186         SkASSERT(!fList.isInList(dm));
187     }
188 }
189 
lock(PoolDiscardableMemory * dm)190 bool DiscardableMemoryPool::lock(PoolDiscardableMemory* dm) {
191     SkASSERT(dm != nullptr);
192     SkAutoMutexExclusive autoMutexAcquire(fMutex);
193     if (nullptr == dm->fPointer) {
194         // May have been purged while waiting for lock.
195         #if SK_LAZY_CACHE_STATS
196         ++fCacheMisses;
197         #endif  // SK_LAZY_CACHE_STATS
198         return false;
199     }
200     dm->fLocked = true;
201     fList.remove(dm);
202     fList.addToHead(dm);
203     #if SK_LAZY_CACHE_STATS
204     ++fCacheHits;
205     #endif  // SK_LAZY_CACHE_STATS
206     return true;
207 }
208 
unlock(PoolDiscardableMemory * dm)209 void DiscardableMemoryPool::unlock(PoolDiscardableMemory* dm) {
210     SkASSERT(dm != nullptr);
211     SkAutoMutexExclusive autoMutexAcquire(fMutex);
212     dm->fLocked = false;
213     this->dumpDownTo(fBudget);
214 }
215 
getRAMUsed()216 size_t DiscardableMemoryPool::getRAMUsed() {
217     return fUsed;
218 }
setRAMBudget(size_t budget)219 void DiscardableMemoryPool::setRAMBudget(size_t budget) {
220     SkAutoMutexExclusive autoMutexAcquire(fMutex);
221     fBudget = budget;
222     this->dumpDownTo(fBudget);
223 }
dumpPool()224 void DiscardableMemoryPool::dumpPool() {
225     SkAutoMutexExclusive autoMutexAcquire(fMutex);
226     this->dumpDownTo(0);
227 }
228 
229 }  // namespace
230 
Make(size_t size)231 sk_sp<SkDiscardableMemoryPool> SkDiscardableMemoryPool::Make(size_t size) {
232     return sk_make_sp<DiscardableMemoryPool>(size);
233 }
234 
SkGetGlobalDiscardableMemoryPool()235 SkDiscardableMemoryPool* SkGetGlobalDiscardableMemoryPool() {
236     // Intentionally leak this global pool.
237     static SkDiscardableMemoryPool* global =
238             new DiscardableMemoryPool(SK_DEFAULT_GLOBAL_DISCARDABLE_MEMORY_POOL_SIZE);
239     return global;
240 }
241