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
19namespace {
20
21class PoolDiscardableMemory;
22
23/**
24 * This non-global pool can be used for unit tests to verify that the
25 * pool works.
26 */
27class DiscardableMemoryPool : public SkDiscardableMemoryPool {
28public:
29 DiscardableMemoryPool(size_t budget);
30 ~DiscardableMemoryPool() override;
31
32 std::unique_ptr<SkDiscardableMemory> make(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;
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
45 int getCacheHits() override { return fCacheHits; }
46 int getCacheMisses() override { return fCacheMisses; }
47 void resetCacheHitsAndMisses() override {
48 fCacheHits = fCacheMisses = 0;
49 }
50 int fCacheHits;
51 int fCacheMisses;
52 #endif // SK_LAZY_CACHE_STATS
53
54private:
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 typedef SkDiscardableMemory::Factory INHERITED;
72};
73
74/**
75 * A PoolDiscardableMemory is a SkDiscardableMemory that relies on
76 * a DiscardableMemoryPool object to manage the memory.
77 */
78class PoolDiscardableMemory : public SkDiscardableMemory {
79public:
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;
86private:
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
94PoolDiscardableMemory::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
103PoolDiscardableMemory::~PoolDiscardableMemory() {
104 SkASSERT(!fLocked); // contract for SkDiscardableMemory
105 fPool->removeFromPool(this);
106}
107
108bool PoolDiscardableMemory::lock() {
109 SkASSERT(!fLocked); // contract for SkDiscardableMemory
110 return fPool->lock(this);
111}
112
113void* PoolDiscardableMemory::data() {
114 SkASSERT(fLocked); // contract for SkDiscardableMemory
115 return fPointer.get();
116}
117
118void PoolDiscardableMemory::unlock() {
119 SkASSERT(fLocked); // contract for SkDiscardableMemory
120 fPool->unlock(this);
121}
122
123////////////////////////////////////////////////////////////////////////////////
124
125DiscardableMemoryPool::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}
133DiscardableMemoryPool::~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
140void 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
165std::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
178void 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
190bool 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
209void DiscardableMemoryPool::unlock(PoolDiscardableMemory* dm) {
210 SkASSERT(dm != nullptr);
211 SkAutoMutexExclusive autoMutexAcquire(fMutex);
212 dm->fLocked = false;
213 this->dumpDownTo(fBudget);
214}
215
216size_t DiscardableMemoryPool::getRAMUsed() {
217 return fUsed;
218}
219void DiscardableMemoryPool::setRAMBudget(size_t budget) {
220 SkAutoMutexExclusive autoMutexAcquire(fMutex);
221 fBudget = budget;
222 this->dumpDownTo(fBudget);
223}
224void DiscardableMemoryPool::dumpPool() {
225 SkAutoMutexExclusive autoMutexAcquire(fMutex);
226 this->dumpDownTo(0);
227}
228
229} // namespace
230
231sk_sp<SkDiscardableMemoryPool> SkDiscardableMemoryPool::Make(size_t size) {
232 return sk_make_sp<DiscardableMemoryPool>(size);
233}
234
235SkDiscardableMemoryPool* 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