blob: 84a5c560205940490e16f1c081ef6f8e38ba9fdc [file] [log] [blame]
reed@google.com602a1d72013-07-23 19:13:54 +00001/*
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 "SkScaledImageCache.h"
reed@google.comd94697c2013-07-24 14:31:33 +00009#include "SkMipMap.h"
reed@google.com602a1d72013-07-23 19:13:54 +000010#include "SkPixelRef.h"
11#include "SkRect.h"
12
13#ifndef SK_DEFAULT_IMAGE_CACHE_LIMIT
14 #define SK_DEFAULT_IMAGE_CACHE_LIMIT (2 * 1024 * 1024)
15#endif
16
17#if 1
18 // Implemented from en.wikipedia.org/wiki/MurmurHash.
19static uint32_t compute_hash(const uint32_t data[], int count) {
20 uint32_t hash = 0;
skia.committer@gmail.com7f1af502013-07-24 07:01:12 +000021
reed@google.com602a1d72013-07-23 19:13:54 +000022 for (int i = 0; i < count; ++i) {
23 uint32_t k = data[i];
24 k *= 0xcc9e2d51;
25 k = (k << 15) | (k >> 17);
26 k *= 0x1b873593;
skia.committer@gmail.com7f1af502013-07-24 07:01:12 +000027
reed@google.com602a1d72013-07-23 19:13:54 +000028 hash ^= k;
29 hash = (hash << 13) | (hash >> 19);
30 hash *= 5;
31 hash += 0xe6546b64;
32 }
skia.committer@gmail.com7f1af502013-07-24 07:01:12 +000033
reed@google.com602a1d72013-07-23 19:13:54 +000034 // hash ^= size;
35 hash ^= hash >> 16;
36 hash *= 0x85ebca6b;
37 hash ^= hash >> 13;
38 hash *= 0xc2b2ae35;
39 hash ^= hash >> 16;
skia.committer@gmail.com7f1af502013-07-24 07:01:12 +000040
reed@google.com602a1d72013-07-23 19:13:54 +000041 return hash;
42}
43#else
44static uint32_t mix(uint32_t a, uint32_t b) {
45 return ((a >> 1) | (a << 31)) ^ b;
46}
47
48static uint32_t compute_hash(const uint32_t data[], int count) {
49 uint32_t hash = 0;
skia.committer@gmail.com7f1af502013-07-24 07:01:12 +000050
reed@google.com602a1d72013-07-23 19:13:54 +000051 for (int i = 0; i < count; ++i) {
52 hash = mix(hash, data[i]);
53 }
54 return hash;
55}
56#endif
57
58struct Key {
59 bool init(const SkBitmap& bm, SkScalar scaleX, SkScalar scaleY) {
60 SkPixelRef* pr = bm.pixelRef();
61 if (!pr) {
62 return false;
63 }
64
65 size_t offset = bm.pixelRefOffset();
66 size_t rowBytes = bm.rowBytes();
67 int x = (offset % rowBytes) >> 2;
68 int y = offset / rowBytes;
69
70 fGenID = pr->getGenerationID();
71 fBounds.set(x, y, x + bm.width(), y + bm.height());
72 fScaleX = scaleX;
73 fScaleY = scaleY;
74
75 fHash = compute_hash(&fGenID, 7);
76 return true;
77 }
78
79 bool operator<(const Key& other) {
80 const uint32_t* a = &fGenID;
81 const uint32_t* b = &other.fGenID;
82 for (int i = 0; i < 7; ++i) {
83 if (a[i] < b[i]) {
84 return true;
85 }
86 if (a[i] > b[i]) {
87 return false;
88 }
89 }
90 return false;
91 }
92
93 bool operator==(const Key& other) {
94 const uint32_t* a = &fHash;
95 const uint32_t* b = &other.fHash;
96 for (int i = 0; i < 8; ++i) {
97 if (a[i] != b[i]) {
98 return false;
99 }
100 }
101 return true;
102 }
103
104 uint32_t fHash;
105 uint32_t fGenID;
106 float fScaleX;
107 float fScaleY;
108 SkIRect fBounds;
109};
110
111struct SkScaledImageCache::Rec {
112 Rec(const Key& key, const SkBitmap& bm) : fKey(key), fBitmap(bm) {
113 fLockCount = 1;
reed@google.comd94697c2013-07-24 14:31:33 +0000114 fMip = NULL;
reed@google.com602a1d72013-07-23 19:13:54 +0000115 }
reed@google.comd94697c2013-07-24 14:31:33 +0000116
117 Rec(const Key& key, const SkMipMap* mip) : fKey(key) {
118 fLockCount = 1;
119 fMip = mip;
120 mip->ref();
121 }
122
123 ~Rec() {
124 SkSafeUnref(fMip);
125 }
126
reed@google.com602a1d72013-07-23 19:13:54 +0000127 size_t bytesUsed() const {
reed@google.comd94697c2013-07-24 14:31:33 +0000128 return fMip ? fMip->getSize() : fBitmap.getSize();
reed@google.com602a1d72013-07-23 19:13:54 +0000129 }
130
131 Rec* fNext;
132 Rec* fPrev;
133
134 // this guy wants to be 64bit aligned
135 Key fKey;
136
137 int32_t fLockCount;
reed@google.comd94697c2013-07-24 14:31:33 +0000138
139 // we use either fBitmap or fMip, but not both
reed@google.com602a1d72013-07-23 19:13:54 +0000140 SkBitmap fBitmap;
reed@google.comd94697c2013-07-24 14:31:33 +0000141 const SkMipMap* fMip;
reed@google.com602a1d72013-07-23 19:13:54 +0000142};
143
144SkScaledImageCache::SkScaledImageCache(size_t byteLimit) {
145 fHead = NULL;
146 fTail = NULL;
147 fBytesUsed = 0;
148 fByteLimit = byteLimit;
149 fCount = 0;
150}
151
152SkScaledImageCache::~SkScaledImageCache() {
153 Rec* rec = fHead;
154 while (rec) {
155 Rec* next = rec->fNext;
156 SkDELETE(rec);
157 rec = next;
158 }
159}
160
reed@google.comd94697c2013-07-24 14:31:33 +0000161SkScaledImageCache::Rec* SkScaledImageCache::findAndLock(const SkBitmap& orig,
reed@google.com602a1d72013-07-23 19:13:54 +0000162 SkScalar scaleX,
reed@google.comd94697c2013-07-24 14:31:33 +0000163 SkScalar scaleY) {
reed@google.com602a1d72013-07-23 19:13:54 +0000164 Key key;
165 if (!key.init(orig, scaleX, scaleY)) {
166 return NULL;
167 }
reed@google.comd94697c2013-07-24 14:31:33 +0000168
reed@google.com602a1d72013-07-23 19:13:54 +0000169 Rec* rec = fHead;
170 while (rec != NULL) {
171 if (rec->fKey == key) {
172 this->moveToHead(rec); // for our LRU
173 rec->fLockCount += 1;
reed@google.comd94697c2013-07-24 14:31:33 +0000174 return rec;
reed@google.com602a1d72013-07-23 19:13:54 +0000175 }
176 rec = rec->fNext;
177 }
178 return NULL;
179}
180
reed@google.comd94697c2013-07-24 14:31:33 +0000181SkScaledImageCache::ID* SkScaledImageCache::findAndLock(const SkBitmap& orig,
182 SkScalar scaleX,
183 SkScalar scaleY,
184 SkBitmap* scaled) {
185 if (0 == scaleX || 0 == scaleY) {
186 // degenerate, and the key we use for mipmaps
187 return NULL;
188 }
189
190 Rec* rec = this->findAndLock(orig, scaleX, scaleY);
191 if (rec) {
192 SkASSERT(NULL == rec->fMip);
193 SkASSERT(rec->fBitmap.pixelRef());
194 *scaled = rec->fBitmap;
195 }
196 return (ID*)rec;
197}
198
199SkScaledImageCache::ID* SkScaledImageCache::findAndLockMip(const SkBitmap& orig,
200 SkMipMap const ** mip) {
201 Rec* rec = this->findAndLock(orig, 0, 0);
202 if (rec) {
203 SkASSERT(rec->fMip);
204 SkASSERT(NULL == rec->fBitmap.pixelRef());
205 *mip = rec->fMip;
206 }
207 return (ID*)rec;
208}
209
reed@google.com602a1d72013-07-23 19:13:54 +0000210SkScaledImageCache::ID* SkScaledImageCache::addAndLock(const SkBitmap& orig,
211 SkScalar scaleX,
212 SkScalar scaleY,
213 const SkBitmap& scaled) {
reed@google.comd94697c2013-07-24 14:31:33 +0000214 if (0 == scaleX || 0 == scaleY) {
215 // degenerate, and the key we use for mipmaps
216 return NULL;
217 }
218
reed@google.com602a1d72013-07-23 19:13:54 +0000219 Key key;
220 if (!key.init(orig, scaleX, scaleY)) {
221 return NULL;
222 }
reed@google.comd94697c2013-07-24 14:31:33 +0000223
reed@google.com602a1d72013-07-23 19:13:54 +0000224 Rec* rec = SkNEW_ARGS(Rec, (key, scaled));
225 this->addToHead(rec);
226 SkASSERT(1 == rec->fLockCount);
reed@google.comd94697c2013-07-24 14:31:33 +0000227
228 // We may (now) be overbudget, so see if we need to purge something.
229 this->purgeAsNeeded();
230 return (ID*)rec;
231}
reed@google.com602a1d72013-07-23 19:13:54 +0000232
reed@google.comd94697c2013-07-24 14:31:33 +0000233SkScaledImageCache::ID* SkScaledImageCache::addAndLockMip(const SkBitmap& orig,
234 const SkMipMap* mip) {
235 Key key;
236 if (!key.init(orig, 0, 0)) {
237 return NULL;
238 }
239
240 Rec* rec = SkNEW_ARGS(Rec, (key, mip));
241 this->addToHead(rec);
242 SkASSERT(1 == rec->fLockCount);
243
reed@google.com602a1d72013-07-23 19:13:54 +0000244 // We may (now) be overbudget, so see if we need to purge something.
245 this->purgeAsNeeded();
246 return (ID*)rec;
247}
248
249void SkScaledImageCache::unlock(SkScaledImageCache::ID* id) {
250 SkASSERT(id);
251
252#ifdef SK_DEBUG
253 {
254 bool found = false;
255 Rec* rec = fHead;
256 while (rec != NULL) {
257 if ((ID*)rec == id) {
258 found = true;
259 break;
260 }
261 rec = rec->fNext;
262 }
263 SkASSERT(found);
264 }
265#endif
266 Rec* rec = (Rec*)id;
267 SkASSERT(rec->fLockCount > 0);
268 rec->fLockCount -= 1;
269
270// SkDebugf("Unlock: [%d %d] %d\n", rec->fBitmap.width(), rec->fBitmap.height(), rec->fLockCount);
271
272 // we may have been over-budget, but now have released something, so check
273 // if we should purge.
274 if (0 == rec->fLockCount) {
275 this->purgeAsNeeded();
276 }
277}
278
279void SkScaledImageCache::purgeAsNeeded() {
280 size_t byteLimit = fByteLimit;
281 size_t bytesUsed = fBytesUsed;
skia.committer@gmail.com7f1af502013-07-24 07:01:12 +0000282
reed@google.com602a1d72013-07-23 19:13:54 +0000283 Rec* rec = fTail;
284 while (rec) {
285 if (bytesUsed < byteLimit) {
286 break;
287 }
288 Rec* prev = rec->fPrev;
289 if (0 == rec->fLockCount) {
290// SkDebugf("Purge: [%d %d] %d\n", rec->fBitmap.width(), rec->fBitmap.height(), fCount);
291 size_t used = rec->bytesUsed();
292 SkASSERT(used <= bytesUsed);
293 bytesUsed -= used;
294 this->detach(rec);
295 SkDELETE(rec);
296 fCount -= 1;
297 }
298 rec = prev;
299 }
300 fBytesUsed = bytesUsed;
301}
302
303size_t SkScaledImageCache::setByteLimit(size_t newLimit) {
304 size_t prevLimit = fByteLimit;
305 fByteLimit = newLimit;
306 if (newLimit < prevLimit) {
307 this->purgeAsNeeded();
308 }
309 return prevLimit;
310}
311
312///////////////////////////////////////////////////////////////////////////////
313
314void SkScaledImageCache::detach(Rec* rec) {
315 Rec* prev = rec->fPrev;
316 Rec* next = rec->fNext;
skia.committer@gmail.com7f1af502013-07-24 07:01:12 +0000317
reed@google.com602a1d72013-07-23 19:13:54 +0000318 if (!prev) {
319 SkASSERT(fHead == rec);
320 fHead = next;
321 } else {
322 prev->fNext = next;
323 }
skia.committer@gmail.com7f1af502013-07-24 07:01:12 +0000324
reed@google.com602a1d72013-07-23 19:13:54 +0000325 if (!next) {
326 fTail = prev;
327 } else {
328 next->fPrev = prev;
329 }
skia.committer@gmail.com7f1af502013-07-24 07:01:12 +0000330
reed@google.com602a1d72013-07-23 19:13:54 +0000331 rec->fNext = rec->fPrev = NULL;
332}
333
334void SkScaledImageCache::moveToHead(Rec* rec) {
335 if (fHead == rec) {
336 return;
337 }
338
339 SkASSERT(fHead);
340 SkASSERT(fTail);
341
342 this->validate();
343
344 this->detach(rec);
345
346 fHead->fPrev = rec;
347 rec->fNext = fHead;
348 fHead = rec;
skia.committer@gmail.com7f1af502013-07-24 07:01:12 +0000349
reed@google.com602a1d72013-07-23 19:13:54 +0000350 this->validate();
351}
352
353void SkScaledImageCache::addToHead(Rec* rec) {
354 this->validate();
355
356 rec->fPrev = NULL;
357 rec->fNext = fHead;
358 if (fHead) {
359 fHead->fPrev = rec;
360 }
361 fHead = rec;
362 if (!fTail) {
363 fTail = rec;
364 }
365 fBytesUsed += rec->bytesUsed();
366 fCount += 1;
367
368 this->validate();
369}
370
371#ifdef SK_DEBUG
372void SkScaledImageCache::validate() const {
373 if (NULL == fHead) {
374 SkASSERT(NULL == fTail);
375 SkASSERT(0 == fBytesUsed);
376 return;
377 }
378
379 if (fHead == fTail) {
380 SkASSERT(NULL == fHead->fPrev);
381 SkASSERT(NULL == fHead->fNext);
382 SkASSERT(fHead->bytesUsed() == fBytesUsed);
383 return;
384 }
385
386 SkASSERT(NULL == fHead->fPrev);
387 SkASSERT(NULL != fHead->fNext);
388 SkASSERT(NULL == fTail->fNext);
389 SkASSERT(NULL != fTail->fPrev);
390
391 size_t used = 0;
392 int count = 0;
393 const Rec* rec = fHead;
394 while (rec) {
395 count += 1;
396 used += rec->bytesUsed();
397 SkASSERT(used <= fBytesUsed);
398 rec = rec->fNext;
399 }
400 SkASSERT(fCount == count);
401
402 rec = fTail;
403 while (rec) {
404 SkASSERT(count > 0);
405 count -= 1;
406 SkASSERT(used >= rec->bytesUsed());
407 used -= rec->bytesUsed();
408 rec = rec->fPrev;
409 }
skia.committer@gmail.com7f1af502013-07-24 07:01:12 +0000410
reed@google.com602a1d72013-07-23 19:13:54 +0000411 SkASSERT(0 == count);
412 SkASSERT(0 == used);
413}
414#endif
415
416///////////////////////////////////////////////////////////////////////////////
417
418#include "SkThread.h"
419
420static SkMutex gMutex;
421
422static SkScaledImageCache* get_cache() {
423 static SkScaledImageCache* gCache;
424 if (!gCache) {
425 gCache = SkNEW_ARGS(SkScaledImageCache, (SK_DEFAULT_IMAGE_CACHE_LIMIT));
426 }
427 return gCache;
428}
429
430SkScaledImageCache::ID* SkScaledImageCache::FindAndLock(const SkBitmap& orig,
431 SkScalar scaleX,
432 SkScalar scaleY,
433 SkBitmap* scaled) {
434 SkAutoMutexAcquire am(gMutex);
435 return get_cache()->findAndLock(orig, scaleX, scaleY, scaled);
436}
437
reed@google.comd94697c2013-07-24 14:31:33 +0000438SkScaledImageCache::ID* SkScaledImageCache::FindAndLockMip(const SkBitmap& orig,
439 SkMipMap const ** mip) {
440 SkAutoMutexAcquire am(gMutex);
441 return get_cache()->findAndLockMip(orig, mip);
442}
443
reed@google.com602a1d72013-07-23 19:13:54 +0000444SkScaledImageCache::ID* SkScaledImageCache::AddAndLock(const SkBitmap& orig,
445 SkScalar scaleX,
446 SkScalar scaleY,
447 const SkBitmap& scaled) {
448 SkAutoMutexAcquire am(gMutex);
449 return get_cache()->addAndLock(orig, scaleX, scaleY, scaled);
450}
451
reed@google.comd94697c2013-07-24 14:31:33 +0000452SkScaledImageCache::ID* SkScaledImageCache::AddAndLockMip(const SkBitmap& orig,
453 const SkMipMap* mip) {
454 SkAutoMutexAcquire am(gMutex);
455 return get_cache()->addAndLockMip(orig, mip);
456}
457
reed@google.com602a1d72013-07-23 19:13:54 +0000458void SkScaledImageCache::Unlock(SkScaledImageCache::ID* id) {
459 SkAutoMutexAcquire am(gMutex);
460 return get_cache()->unlock(id);
461}
462
463size_t SkScaledImageCache::GetBytesUsed() {
464 SkAutoMutexAcquire am(gMutex);
465 return get_cache()->getBytesUsed();
466}
467
468size_t SkScaledImageCache::GetByteLimit() {
469 SkAutoMutexAcquire am(gMutex);
470 return get_cache()->getByteLimit();
471}
472
473size_t SkScaledImageCache::SetByteLimit(size_t newLimit) {
474 SkAutoMutexAcquire am(gMutex);
475 return get_cache()->setByteLimit(newLimit);
476}
477
478///////////////////////////////////////////////////////////////////////////////
479
480#include "SkGraphics.h"
481
482size_t SkGraphics::GetImageCacheBytesUsed() {
483 return SkScaledImageCache::GetBytesUsed();
484}
485
486size_t SkGraphics::GetImageCacheByteLimit() {
487 return SkScaledImageCache::GetByteLimit();
488}
489
490size_t SkGraphics::SetImageCacheByteLimit(size_t newLimit) {
491 return SkScaledImageCache::SetByteLimit(newLimit);
492}