blob: 147697af4eb51cdfa2b8763b700de3bcac816ba0 [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
reed@google.com5d1e5582013-07-25 14:36:15 +000017
reed@google.com602a1d72013-07-23 19:13:54 +000018 // 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}
reed@google.com602a1d72013-07-23 19:13:54 +000043
44struct Key {
45 bool init(const SkBitmap& bm, SkScalar scaleX, SkScalar scaleY) {
46 SkPixelRef* pr = bm.pixelRef();
47 if (!pr) {
48 return false;
49 }
50
51 size_t offset = bm.pixelRefOffset();
52 size_t rowBytes = bm.rowBytes();
53 int x = (offset % rowBytes) >> 2;
54 int y = offset / rowBytes;
55
56 fGenID = pr->getGenerationID();
57 fBounds.set(x, y, x + bm.width(), y + bm.height());
58 fScaleX = scaleX;
59 fScaleY = scaleY;
60
61 fHash = compute_hash(&fGenID, 7);
62 return true;
63 }
64
reed@google.com5d1e5582013-07-25 14:36:15 +000065 bool operator<(const Key& other) const {
reed@google.com602a1d72013-07-23 19:13:54 +000066 const uint32_t* a = &fGenID;
67 const uint32_t* b = &other.fGenID;
68 for (int i = 0; i < 7; ++i) {
69 if (a[i] < b[i]) {
70 return true;
71 }
72 if (a[i] > b[i]) {
73 return false;
74 }
75 }
76 return false;
77 }
78
reed@google.com5d1e5582013-07-25 14:36:15 +000079 bool operator==(const Key& other) const {
reed@google.com602a1d72013-07-23 19:13:54 +000080 const uint32_t* a = &fHash;
81 const uint32_t* b = &other.fHash;
82 for (int i = 0; i < 8; ++i) {
83 if (a[i] != b[i]) {
84 return false;
85 }
86 }
87 return true;
88 }
89
90 uint32_t fHash;
91 uint32_t fGenID;
92 float fScaleX;
93 float fScaleY;
94 SkIRect fBounds;
95};
96
97struct SkScaledImageCache::Rec {
98 Rec(const Key& key, const SkBitmap& bm) : fKey(key), fBitmap(bm) {
99 fLockCount = 1;
reed@google.comd94697c2013-07-24 14:31:33 +0000100 fMip = NULL;
reed@google.com602a1d72013-07-23 19:13:54 +0000101 }
skia.committer@gmail.com5c561cb2013-07-25 07:01:00 +0000102
reed@google.comd94697c2013-07-24 14:31:33 +0000103 Rec(const Key& key, const SkMipMap* mip) : fKey(key) {
104 fLockCount = 1;
105 fMip = mip;
106 mip->ref();
107 }
skia.committer@gmail.com5c561cb2013-07-25 07:01:00 +0000108
reed@google.comd94697c2013-07-24 14:31:33 +0000109 ~Rec() {
110 SkSafeUnref(fMip);
111 }
skia.committer@gmail.com5c561cb2013-07-25 07:01:00 +0000112
reed@google.com602a1d72013-07-23 19:13:54 +0000113 size_t bytesUsed() const {
reed@google.comd94697c2013-07-24 14:31:33 +0000114 return fMip ? fMip->getSize() : fBitmap.getSize();
reed@google.com602a1d72013-07-23 19:13:54 +0000115 }
116
117 Rec* fNext;
118 Rec* fPrev;
119
120 // this guy wants to be 64bit aligned
121 Key fKey;
122
123 int32_t fLockCount;
skia.committer@gmail.com5c561cb2013-07-25 07:01:00 +0000124
reed@google.comd94697c2013-07-24 14:31:33 +0000125 // we use either fBitmap or fMip, but not both
reed@google.com602a1d72013-07-23 19:13:54 +0000126 SkBitmap fBitmap;
reed@google.comd94697c2013-07-24 14:31:33 +0000127 const SkMipMap* fMip;
reed@google.com602a1d72013-07-23 19:13:54 +0000128};
129
reed@google.com5d1e5582013-07-25 14:36:15 +0000130#include "SkTDynamicHash.h"
131
132static const Key& key_from_rec(const SkScaledImageCache::Rec& rec) {
133 return rec.fKey;
134}
135
136static uint32_t hash_from_key(const Key& key) {
137 return key.fHash;
138}
139
140static bool eq_rec_key(const SkScaledImageCache::Rec& rec, const Key& key) {
141 return rec.fKey == key;
142}
143
144class SkScaledImageCache::Hash : public SkTDynamicHash<SkScaledImageCache::Rec,
145 Key, key_from_rec, hash_from_key,
146 eq_rec_key> {};
147
148///////////////////////////////////////////////////////////////////////////////
149
150//#define USE_HASH
151
reed@google.com602a1d72013-07-23 19:13:54 +0000152SkScaledImageCache::SkScaledImageCache(size_t byteLimit) {
153 fHead = NULL;
154 fTail = NULL;
reed@google.com5d1e5582013-07-25 14:36:15 +0000155#ifdef USE_HASH
156 fHash = new Hash;
157#else
158 fHash = NULL;
159#endif
reed@google.com602a1d72013-07-23 19:13:54 +0000160 fBytesUsed = 0;
161 fByteLimit = byteLimit;
162 fCount = 0;
163}
164
165SkScaledImageCache::~SkScaledImageCache() {
166 Rec* rec = fHead;
167 while (rec) {
168 Rec* next = rec->fNext;
169 SkDELETE(rec);
170 rec = next;
171 }
reed@google.com5d1e5582013-07-25 14:36:15 +0000172 delete fHash;
reed@google.com602a1d72013-07-23 19:13:54 +0000173}
174
reed@google.comd94697c2013-07-24 14:31:33 +0000175SkScaledImageCache::Rec* SkScaledImageCache::findAndLock(const SkBitmap& orig,
reed@google.com602a1d72013-07-23 19:13:54 +0000176 SkScalar scaleX,
reed@google.comd94697c2013-07-24 14:31:33 +0000177 SkScalar scaleY) {
reed@google.com602a1d72013-07-23 19:13:54 +0000178 Key key;
179 if (!key.init(orig, scaleX, scaleY)) {
180 return NULL;
181 }
skia.committer@gmail.com5c561cb2013-07-25 07:01:00 +0000182
reed@google.com5d1e5582013-07-25 14:36:15 +0000183#ifdef USE_HASH
184 Rec* rec = fHash->find(key);
185#else
reed@google.com602a1d72013-07-23 19:13:54 +0000186 Rec* rec = fHead;
187 while (rec != NULL) {
188 if (rec->fKey == key) {
reed@google.com5d1e5582013-07-25 14:36:15 +0000189 break;
reed@google.com602a1d72013-07-23 19:13:54 +0000190 }
191 rec = rec->fNext;
192 }
reed@google.com5d1e5582013-07-25 14:36:15 +0000193#endif
194
195 if (rec) {
196 this->moveToHead(rec); // for our LRU
197 rec->fLockCount += 1;
198 }
199 return rec;
reed@google.com602a1d72013-07-23 19:13:54 +0000200}
201
reed@google.comd94697c2013-07-24 14:31:33 +0000202SkScaledImageCache::ID* SkScaledImageCache::findAndLock(const SkBitmap& orig,
203 SkScalar scaleX,
204 SkScalar scaleY,
205 SkBitmap* scaled) {
206 if (0 == scaleX || 0 == scaleY) {
207 // degenerate, and the key we use for mipmaps
208 return NULL;
209 }
210
211 Rec* rec = this->findAndLock(orig, scaleX, scaleY);
212 if (rec) {
213 SkASSERT(NULL == rec->fMip);
214 SkASSERT(rec->fBitmap.pixelRef());
215 *scaled = rec->fBitmap;
216 }
217 return (ID*)rec;
218}
219
220SkScaledImageCache::ID* SkScaledImageCache::findAndLockMip(const SkBitmap& orig,
221 SkMipMap const ** mip) {
222 Rec* rec = this->findAndLock(orig, 0, 0);
223 if (rec) {
224 SkASSERT(rec->fMip);
225 SkASSERT(NULL == rec->fBitmap.pixelRef());
226 *mip = rec->fMip;
227 }
228 return (ID*)rec;
229}
230
reed@google.com602a1d72013-07-23 19:13:54 +0000231SkScaledImageCache::ID* SkScaledImageCache::addAndLock(const SkBitmap& orig,
232 SkScalar scaleX,
233 SkScalar scaleY,
234 const SkBitmap& scaled) {
reed@google.comd94697c2013-07-24 14:31:33 +0000235 if (0 == scaleX || 0 == scaleY) {
236 // degenerate, and the key we use for mipmaps
237 return NULL;
238 }
239
reed@google.com602a1d72013-07-23 19:13:54 +0000240 Key key;
241 if (!key.init(orig, scaleX, scaleY)) {
242 return NULL;
243 }
skia.committer@gmail.com5c561cb2013-07-25 07:01:00 +0000244
reed@google.com602a1d72013-07-23 19:13:54 +0000245 Rec* rec = SkNEW_ARGS(Rec, (key, scaled));
246 this->addToHead(rec);
247 SkASSERT(1 == rec->fLockCount);
skia.committer@gmail.com5c561cb2013-07-25 07:01:00 +0000248
reed@google.com5d1e5582013-07-25 14:36:15 +0000249#ifdef USE_HASH
250 fHash->add(key, rec);
251#endif
252
reed@google.comd94697c2013-07-24 14:31:33 +0000253 // We may (now) be overbudget, so see if we need to purge something.
254 this->purgeAsNeeded();
255 return (ID*)rec;
256}
reed@google.com602a1d72013-07-23 19:13:54 +0000257
reed@google.comd94697c2013-07-24 14:31:33 +0000258SkScaledImageCache::ID* SkScaledImageCache::addAndLockMip(const SkBitmap& orig,
259 const SkMipMap* mip) {
260 Key key;
261 if (!key.init(orig, 0, 0)) {
262 return NULL;
263 }
skia.committer@gmail.com5c561cb2013-07-25 07:01:00 +0000264
reed@google.comd94697c2013-07-24 14:31:33 +0000265 Rec* rec = SkNEW_ARGS(Rec, (key, mip));
266 this->addToHead(rec);
267 SkASSERT(1 == rec->fLockCount);
skia.committer@gmail.com5c561cb2013-07-25 07:01:00 +0000268
reed@google.com5d1e5582013-07-25 14:36:15 +0000269#ifdef USE_HASH
270 fHash->add(key, rec);
271#endif
272
reed@google.com602a1d72013-07-23 19:13:54 +0000273 // We may (now) be overbudget, so see if we need to purge something.
274 this->purgeAsNeeded();
275 return (ID*)rec;
276}
277
278void SkScaledImageCache::unlock(SkScaledImageCache::ID* id) {
279 SkASSERT(id);
280
281#ifdef SK_DEBUG
282 {
283 bool found = false;
284 Rec* rec = fHead;
285 while (rec != NULL) {
286 if ((ID*)rec == id) {
287 found = true;
288 break;
289 }
290 rec = rec->fNext;
291 }
292 SkASSERT(found);
293 }
294#endif
295 Rec* rec = (Rec*)id;
296 SkASSERT(rec->fLockCount > 0);
297 rec->fLockCount -= 1;
298
299// SkDebugf("Unlock: [%d %d] %d\n", rec->fBitmap.width(), rec->fBitmap.height(), rec->fLockCount);
300
301 // we may have been over-budget, but now have released something, so check
302 // if we should purge.
303 if (0 == rec->fLockCount) {
304 this->purgeAsNeeded();
305 }
306}
307
308void SkScaledImageCache::purgeAsNeeded() {
309 size_t byteLimit = fByteLimit;
310 size_t bytesUsed = fBytesUsed;
skia.committer@gmail.com7f1af502013-07-24 07:01:12 +0000311
reed@google.com602a1d72013-07-23 19:13:54 +0000312 Rec* rec = fTail;
313 while (rec) {
314 if (bytesUsed < byteLimit) {
315 break;
316 }
317 Rec* prev = rec->fPrev;
318 if (0 == rec->fLockCount) {
319// SkDebugf("Purge: [%d %d] %d\n", rec->fBitmap.width(), rec->fBitmap.height(), fCount);
320 size_t used = rec->bytesUsed();
321 SkASSERT(used <= bytesUsed);
322 bytesUsed -= used;
323 this->detach(rec);
reed@google.com5d1e5582013-07-25 14:36:15 +0000324#ifdef USE_HASH
325 fHash->remove(rec->fKey);
326#endif
327
reed@google.com602a1d72013-07-23 19:13:54 +0000328 SkDELETE(rec);
329 fCount -= 1;
330 }
331 rec = prev;
332 }
333 fBytesUsed = bytesUsed;
334}
335
336size_t SkScaledImageCache::setByteLimit(size_t newLimit) {
337 size_t prevLimit = fByteLimit;
338 fByteLimit = newLimit;
339 if (newLimit < prevLimit) {
340 this->purgeAsNeeded();
341 }
342 return prevLimit;
343}
344
345///////////////////////////////////////////////////////////////////////////////
346
347void SkScaledImageCache::detach(Rec* rec) {
348 Rec* prev = rec->fPrev;
349 Rec* next = rec->fNext;
skia.committer@gmail.com7f1af502013-07-24 07:01:12 +0000350
reed@google.com602a1d72013-07-23 19:13:54 +0000351 if (!prev) {
352 SkASSERT(fHead == rec);
353 fHead = next;
354 } else {
355 prev->fNext = next;
356 }
skia.committer@gmail.com7f1af502013-07-24 07:01:12 +0000357
reed@google.com602a1d72013-07-23 19:13:54 +0000358 if (!next) {
359 fTail = prev;
360 } else {
361 next->fPrev = prev;
362 }
skia.committer@gmail.com7f1af502013-07-24 07:01:12 +0000363
reed@google.com602a1d72013-07-23 19:13:54 +0000364 rec->fNext = rec->fPrev = NULL;
365}
366
367void SkScaledImageCache::moveToHead(Rec* rec) {
368 if (fHead == rec) {
369 return;
370 }
371
372 SkASSERT(fHead);
373 SkASSERT(fTail);
374
375 this->validate();
376
377 this->detach(rec);
378
379 fHead->fPrev = rec;
380 rec->fNext = fHead;
381 fHead = rec;
skia.committer@gmail.com7f1af502013-07-24 07:01:12 +0000382
reed@google.com602a1d72013-07-23 19:13:54 +0000383 this->validate();
384}
385
386void SkScaledImageCache::addToHead(Rec* rec) {
387 this->validate();
388
389 rec->fPrev = NULL;
390 rec->fNext = fHead;
391 if (fHead) {
392 fHead->fPrev = rec;
393 }
394 fHead = rec;
395 if (!fTail) {
396 fTail = rec;
397 }
398 fBytesUsed += rec->bytesUsed();
399 fCount += 1;
400
401 this->validate();
402}
403
404#ifdef SK_DEBUG
405void SkScaledImageCache::validate() const {
406 if (NULL == fHead) {
407 SkASSERT(NULL == fTail);
408 SkASSERT(0 == fBytesUsed);
409 return;
410 }
411
412 if (fHead == fTail) {
413 SkASSERT(NULL == fHead->fPrev);
414 SkASSERT(NULL == fHead->fNext);
415 SkASSERT(fHead->bytesUsed() == fBytesUsed);
416 return;
417 }
418
419 SkASSERT(NULL == fHead->fPrev);
420 SkASSERT(NULL != fHead->fNext);
421 SkASSERT(NULL == fTail->fNext);
422 SkASSERT(NULL != fTail->fPrev);
423
424 size_t used = 0;
425 int count = 0;
426 const Rec* rec = fHead;
427 while (rec) {
428 count += 1;
429 used += rec->bytesUsed();
430 SkASSERT(used <= fBytesUsed);
431 rec = rec->fNext;
432 }
433 SkASSERT(fCount == count);
434
435 rec = fTail;
436 while (rec) {
437 SkASSERT(count > 0);
438 count -= 1;
439 SkASSERT(used >= rec->bytesUsed());
440 used -= rec->bytesUsed();
441 rec = rec->fPrev;
442 }
skia.committer@gmail.com7f1af502013-07-24 07:01:12 +0000443
reed@google.com602a1d72013-07-23 19:13:54 +0000444 SkASSERT(0 == count);
445 SkASSERT(0 == used);
446}
447#endif
448
449///////////////////////////////////////////////////////////////////////////////
450
451#include "SkThread.h"
452
reed@google.combe19dbe2013-07-24 15:06:34 +0000453SK_DECLARE_STATIC_MUTEX(gMutex);
reed@google.com602a1d72013-07-23 19:13:54 +0000454
455static SkScaledImageCache* get_cache() {
456 static SkScaledImageCache* gCache;
457 if (!gCache) {
458 gCache = SkNEW_ARGS(SkScaledImageCache, (SK_DEFAULT_IMAGE_CACHE_LIMIT));
459 }
460 return gCache;
461}
462
463SkScaledImageCache::ID* SkScaledImageCache::FindAndLock(const SkBitmap& orig,
464 SkScalar scaleX,
465 SkScalar scaleY,
466 SkBitmap* scaled) {
467 SkAutoMutexAcquire am(gMutex);
468 return get_cache()->findAndLock(orig, scaleX, scaleY, scaled);
469}
470
reed@google.comd94697c2013-07-24 14:31:33 +0000471SkScaledImageCache::ID* SkScaledImageCache::FindAndLockMip(const SkBitmap& orig,
472 SkMipMap const ** mip) {
473 SkAutoMutexAcquire am(gMutex);
474 return get_cache()->findAndLockMip(orig, mip);
475}
476
reed@google.com602a1d72013-07-23 19:13:54 +0000477SkScaledImageCache::ID* SkScaledImageCache::AddAndLock(const SkBitmap& orig,
478 SkScalar scaleX,
479 SkScalar scaleY,
480 const SkBitmap& scaled) {
481 SkAutoMutexAcquire am(gMutex);
482 return get_cache()->addAndLock(orig, scaleX, scaleY, scaled);
483}
484
reed@google.comd94697c2013-07-24 14:31:33 +0000485SkScaledImageCache::ID* SkScaledImageCache::AddAndLockMip(const SkBitmap& orig,
486 const SkMipMap* mip) {
487 SkAutoMutexAcquire am(gMutex);
488 return get_cache()->addAndLockMip(orig, mip);
489}
490
reed@google.com602a1d72013-07-23 19:13:54 +0000491void SkScaledImageCache::Unlock(SkScaledImageCache::ID* id) {
492 SkAutoMutexAcquire am(gMutex);
493 return get_cache()->unlock(id);
494}
495
496size_t SkScaledImageCache::GetBytesUsed() {
497 SkAutoMutexAcquire am(gMutex);
498 return get_cache()->getBytesUsed();
499}
500
501size_t SkScaledImageCache::GetByteLimit() {
502 SkAutoMutexAcquire am(gMutex);
503 return get_cache()->getByteLimit();
504}
505
506size_t SkScaledImageCache::SetByteLimit(size_t newLimit) {
507 SkAutoMutexAcquire am(gMutex);
508 return get_cache()->setByteLimit(newLimit);
509}
510
511///////////////////////////////////////////////////////////////////////////////
512
513#include "SkGraphics.h"
514
515size_t SkGraphics::GetImageCacheBytesUsed() {
516 return SkScaledImageCache::GetBytesUsed();
517}
518
519size_t SkGraphics::GetImageCacheByteLimit() {
520 return SkScaledImageCache::GetByteLimit();
521}
522
523size_t SkGraphics::SetImageCacheByteLimit(size_t newLimit) {
524 return SkScaledImageCache::SetByteLimit(newLimit);
525}