blob: 999bf1a94f1f0bb392c8662527db81114569078b [file] [log] [blame]
epoger@google.comec3ed6a2011-07-28 14:26:00 +00001
2/*
3 * Copyright 2011 Google Inc.
4 *
5 * Use of this source code is governed by a BSD-style license that can be
6 * found in the LICENSE file.
7 */
reed@android.com8a1c16f2008-12-17 15:59:43 +00008#include "SkTextureCache.h"
9
10//#define TRACE_HASH_HITS
11//#define TRACE_TEXTURE_CACHE_PURGE
12
13SkTextureCache::Entry::Entry(const SkBitmap& bitmap)
14 : fName(0), fKey(bitmap), fPrev(NULL), fNext(NULL) {
15
16 fMemSize = SkGL::ComputeTextureMemorySize(bitmap);
17 fLockCount = 0;
18}
19
20SkTextureCache::Entry::~Entry() {
21 if (fName != 0) {
22 glDeleteTextures(1, &fName);
23 }
24}
25
26///////////////////////////////////////////////////////////////////////////////
27
28SkTextureCache::SkTextureCache(size_t countMax, size_t sizeMax)
29 : fHead(NULL), fTail(NULL),
30 fTexCountMax(countMax), fTexSizeMax(sizeMax),
31 fTexCount(0), fTexSize(0) {
32
reed@android.com4516f472009-06-29 16:25:36 +000033 sk_bzero(fHash, sizeof(fHash));
reed@android.com8a1c16f2008-12-17 15:59:43 +000034 this->validate();
35}
36
37SkTextureCache::~SkTextureCache() {
38#ifdef SK_DEBUG
39 Entry* entry = fHead;
40 while (entry) {
41 SkASSERT(entry->lockCount() == 0);
42 entry = entry->fNext;
43 }
44#endif
45 this->validate();
46}
47
48void SkTextureCache::deleteAllCaches(bool texturesAreValid) {
49 this->validate();
50
51 Entry* entry = fHead;
52 while (entry) {
53 Entry* next = entry->fNext;
54 if (!texturesAreValid) {
55 entry->abandonTexture();
56 }
57 SkDELETE(entry);
58 entry = next;
59 }
60
61 fSorted.reset();
reed@android.com4516f472009-06-29 16:25:36 +000062 sk_bzero(fHash, sizeof(fHash));
reed@android.com8a1c16f2008-12-17 15:59:43 +000063
64 fTexCount = 0;
65 fTexSize = 0;
66
67 fTail = fHead = NULL;
68
69 this->validate();
70}
71
72///////////////////////////////////////////////////////////////////////////////
73
74int SkTextureCache::findInSorted(const Key& key) const {
75 int count = fSorted.count();
76 if (count == 0) {
77 return ~0;
78 }
79
80 Entry** sorted = fSorted.begin();
81 int lo = 0;
82 int hi = count - 1;
83 while (lo < hi) {
84 int mid = (hi + lo) >> 1;
85 if (sorted[mid]->getKey() < key) {
86 lo = mid + 1;
87 } else {
88 hi = mid;
89 }
90 }
91
92 // hi is now our best guess
93 const Entry* entry = sorted[hi];
94 if (entry->getKey() == key) {
95 return hi;
96 }
97
98 // return where to insert it
99 if (entry->getKey() < key) {
100 hi += 1;
101 }
102 return ~hi; // we twiddle to indicate not-found
103}
104
105#ifdef TRACE_HASH_HITS
106static int gHashHits;
107static int gSortedHits;
108#endif
109
110SkTextureCache::Entry* SkTextureCache::find(const Key& key, int* insert) const {
111 int count = fSorted.count();
112 if (count == 0) {
113 *insert = 0;
114 return NULL;
115 }
116
117 // check the hash first
118 int hashIndex = key.getHashIndex();
119 Entry* entry = fHash[hashIndex];
120 if (NULL != entry && entry->getKey() == key) {
121#ifdef TRACE_HASH_HITS
122 gHashHits += 1;
123#endif
124 return entry;
125 }
126
127 int index = this->findInSorted(key);
128 if (index >= 0) {
129#ifdef TRACE_HASH_HITS
130 gSortedHits += 1;
131#endif
132 entry = fSorted[index];
133 fHash[hashIndex] = entry;
134 return entry;
135 }
136
137 // ~index is where to insert the entry
138 *insert = ~index;
139 return NULL;
140}
141
142SkTextureCache::Entry* SkTextureCache::lock(const SkBitmap& bitmap) {
143 this->validate();
144
145 // call this before we call find(), so we don't reorder after find() and
146 // invalidate our index
147 this->purgeIfNecessary(SkGL::ComputeTextureMemorySize(bitmap));
148
149 Key key(bitmap);
150 int index;
151 Entry* entry = this->find(key, &index);
152
153 if (NULL == entry) {
154 entry = SkNEW_ARGS(Entry, (bitmap));
155
156 entry->fName = SkGL::BindNewTexture(bitmap, &entry->fTexSize);
157 if (0 == entry->fName) {
158 SkDELETE(entry);
159 return NULL;
160 }
161 fHash[key.getHashIndex()] = entry;
162 *fSorted.insert(index) = entry;
163
164 fTexCount += 1;
165 fTexSize += entry->memSize();
166 } else {
167 // detach from our llist
168 Entry* prev = entry->fPrev;
169 Entry* next = entry->fNext;
170 if (prev) {
171 prev->fNext = next;
172 } else {
173 SkASSERT(fHead == entry);
174 fHead = next;
175 }
176 if (next) {
177 next->fPrev = prev;
178 } else {
179 SkASSERT(fTail == entry);
180 fTail = prev;
181 }
182 // now bind the texture
183 glBindTexture(GL_TEXTURE_2D, entry->fName);
184 }
185
186 // add to head of llist for LRU
187 entry->fPrev = NULL;
188 entry->fNext = fHead;
189 if (NULL != fHead) {
190 SkASSERT(NULL == fHead->fPrev);
191 fHead->fPrev = entry;
192 }
193 fHead = entry;
194 if (NULL == fTail) {
195 fTail = entry;
196 }
197
198 this->validate();
199 entry->lock();
200
201#ifdef TRACE_HASH_HITS
202 SkDebugf("---- texture cache hash=%d sorted=%d\n", gHashHits, gSortedHits);
203#endif
204 return entry;
205}
206
207void SkTextureCache::unlock(Entry* entry) {
208 this->validate();
209
210#ifdef SK_DEBUG
211 SkASSERT(entry);
212 int index = this->findInSorted(entry->getKey());
213 SkASSERT(fSorted[index] == entry);
214#endif
215
216 SkASSERT(entry->fLockCount > 0);
217 entry->unlock();
218}
219
220void SkTextureCache::purgeIfNecessary(size_t extraSize) {
221 this->validate();
222
223 size_t countMax = fTexCountMax;
224 size_t sizeMax = fTexSizeMax;
225
226 // take extraSize into account, but watch for underflow of size_t
227 if (extraSize > sizeMax) {
228 sizeMax = 0;
229 } else {
230 sizeMax -= extraSize;
231 }
232
233 Entry* entry = fTail;
234 while (entry) {
235 if (fTexCount <= countMax && fTexSize <= sizeMax) {
236 break;
237 }
238
239 Entry* prev = entry->fPrev;
240 // don't purge an entry that is locked
241 if (entry->isLocked()) {
242 entry = prev;
243 continue;
244 }
245
246 fTexCount -= 1;
247 fTexSize -= entry->memSize();
248
249 // remove from our sorted and hash arrays
250 int index = this->findInSorted(entry->getKey());
251 SkASSERT(index >= 0);
252 fSorted.remove(index);
253 index = entry->getKey().getHashIndex();
254 if (entry == fHash[index]) {
255 fHash[index] = NULL;
256 }
257
258 // now detach it from our llist
259 Entry* next = entry->fNext;
260 if (prev) {
261 prev->fNext = next;
262 } else {
263 fHead = next;
264 }
265 if (next) {
266 next->fPrev = prev;
267 } else {
268 fTail = prev;
269 }
270
271 // now delete it
272#ifdef TRACE_TEXTURE_CACHE_PURGE
273 SkDebugf("---- purge texture cache %d size=%d\n",
274 entry->name(), entry->memSize());
275#endif
276 SkDELETE(entry);
277
278 // keep going
279 entry = prev;
280 }
281
282 this->validate();
283}
284
285void SkTextureCache::setMaxCount(size_t count) {
286 if (fTexCountMax != count) {
287 fTexCountMax = count;
288 this->purgeIfNecessary(0);
289 }
290}
291
292void SkTextureCache::setMaxSize(size_t size) {
293 if (fTexSizeMax != size) {
294 fTexSizeMax = size;
295 this->purgeIfNecessary(0);
296 }
297}
298
299///////////////////////////////////////////////////////////////////////////////
300
301#ifdef SK_DEBUG
302void SkTextureCache::validate() const {
303 if (0 == fTexCount) {
304 SkASSERT(0 == fTexSize);
305 SkASSERT(NULL == fHead);
306 SkASSERT(NULL == fTail);
307 return;
308 }
309
310 SkASSERT(fTexSize); // do we allow a zero-sized texture?
311 SkASSERT(fHead);
312 SkASSERT(fTail);
313
314 SkASSERT(NULL == fHead->fPrev);
315 SkASSERT(NULL == fTail->fNext);
316 if (1 == fTexCount) {
317 SkASSERT(fHead == fTail);
318 }
319
320 const Entry* entry = fHead;
321 size_t count = 0;
322 size_t size = 0;
323 size_t i;
324
325 while (entry != NULL) {
326 SkASSERT(count < fTexCount);
327 SkASSERT(size < fTexSize);
328 size += entry->memSize();
329 count += 1;
330 if (NULL == entry->fNext) {
331 SkASSERT(fTail == entry);
332 }
333 entry = entry->fNext;
334 }
335 SkASSERT(count == fTexCount);
336 SkASSERT(size == fTexSize);
337
338 count = 0;
339 size = 0;
340 entry = fTail;
341 while (entry != NULL) {
342 SkASSERT(count < fTexCount);
343 SkASSERT(size < fTexSize);
344 size += entry->memSize();
345 count += 1;
346 if (NULL == entry->fPrev) {
347 SkASSERT(fHead == entry);
348 }
349 entry = entry->fPrev;
350 }
351 SkASSERT(count == fTexCount);
352 SkASSERT(size == fTexSize);
353
354 SkASSERT(count == (size_t)fSorted.count());
355 for (i = 1; i < count; i++) {
356 SkASSERT(fSorted[i-1]->getKey() < fSorted[i]->getKey());
357 }
358
359 for (i = 0; i < kHashCount; i++) {
360 if (fHash[i]) {
361 size_t index = fHash[i]->getKey().getHashIndex();
362 SkASSERT(index == i);
363 index = fSorted.find(fHash[i]);
364 SkASSERT((size_t)index < count);
365 }
366 }
367}
368#endif
369
370