SkResourceCache.cpp revision 171e5b73a862418f4acd61faf8cecfbc8f58694c
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 "SkChecksum.h" 9#include "SkResourceCache.h" 10#include "SkMipMap.h" 11#include "SkPixelRef.h" 12 13#include <stddef.h> 14 15// This can be defined by the caller's build system 16//#define SK_USE_DISCARDABLE_SCALEDIMAGECACHE 17 18#ifndef SK_DISCARDABLEMEMORY_SCALEDIMAGECACHE_COUNT_LIMIT 19# define SK_DISCARDABLEMEMORY_SCALEDIMAGECACHE_COUNT_LIMIT 1024 20#endif 21 22#ifndef SK_DEFAULT_IMAGE_CACHE_LIMIT 23 #define SK_DEFAULT_IMAGE_CACHE_LIMIT (2 * 1024 * 1024) 24#endif 25 26void SkResourceCache::Key::init(void* nameSpace, size_t length) { 27 SkASSERT(SkAlign4(length) == length); 28 29 // fCount32 and fHash are not hashed 30 static const int kUnhashedLocal32s = 2; 31 static const int kLocal32s = kUnhashedLocal32s + (sizeof(fNamespace) >> 2); 32 33 SK_COMPILE_ASSERT(sizeof(Key) == (kLocal32s << 2), unaccounted_key_locals); 34 SK_COMPILE_ASSERT(sizeof(Key) == offsetof(Key, fNamespace) + sizeof(fNamespace), 35 namespace_field_must_be_last); 36 37 fCount32 = SkToS32(kLocal32s + (length >> 2)); 38 fNamespace = nameSpace; 39 // skip unhashed fields when computing the murmur 40 fHash = SkChecksum::Murmur3(this->as32() + kUnhashedLocal32s, 41 (fCount32 - kUnhashedLocal32s) << 2); 42} 43 44#include "SkTDynamicHash.h" 45 46class SkResourceCache::Hash : 47 public SkTDynamicHash<SkResourceCache::Rec, SkResourceCache::Key> {}; 48 49 50/////////////////////////////////////////////////////////////////////////////// 51 52void SkResourceCache::init() { 53 fHead = NULL; 54 fTail = NULL; 55 fHash = new Hash; 56 fTotalBytesUsed = 0; 57 fCount = 0; 58 fSingleAllocationByteLimit = 0; 59 fAllocator = NULL; 60 61 // One of these should be explicit set by the caller after we return. 62 fTotalByteLimit = 0; 63 fDiscardableFactory = NULL; 64} 65 66#include "SkDiscardableMemory.h" 67 68class SkOneShotDiscardablePixelRef : public SkPixelRef { 69public: 70 SK_DECLARE_INST_COUNT(SkOneShotDiscardablePixelRef) 71 // Ownership of the discardablememory is transfered to the pixelref 72 SkOneShotDiscardablePixelRef(const SkImageInfo&, SkDiscardableMemory*, size_t rowBytes); 73 ~SkOneShotDiscardablePixelRef(); 74 75protected: 76 virtual bool onNewLockPixels(LockRec*) SK_OVERRIDE; 77 virtual void onUnlockPixels() SK_OVERRIDE; 78 virtual size_t getAllocatedSizeInBytes() const SK_OVERRIDE; 79 80private: 81 SkDiscardableMemory* fDM; 82 size_t fRB; 83 bool fFirstTime; 84 85 typedef SkPixelRef INHERITED; 86}; 87 88SkOneShotDiscardablePixelRef::SkOneShotDiscardablePixelRef(const SkImageInfo& info, 89 SkDiscardableMemory* dm, 90 size_t rowBytes) 91 : INHERITED(info) 92 , fDM(dm) 93 , fRB(rowBytes) 94{ 95 SkASSERT(dm->data()); 96 fFirstTime = true; 97} 98 99SkOneShotDiscardablePixelRef::~SkOneShotDiscardablePixelRef() { 100 SkDELETE(fDM); 101} 102 103bool SkOneShotDiscardablePixelRef::onNewLockPixels(LockRec* rec) { 104 if (fFirstTime) { 105 // we're already locked 106 SkASSERT(fDM->data()); 107 fFirstTime = false; 108 goto SUCCESS; 109 } 110 111 // A previous call to onUnlock may have deleted our DM, so check for that 112 if (NULL == fDM) { 113 return false; 114 } 115 116 if (!fDM->lock()) { 117 // since it failed, we delete it now, to free-up the resource 118 delete fDM; 119 fDM = NULL; 120 return false; 121 } 122 123SUCCESS: 124 rec->fPixels = fDM->data(); 125 rec->fColorTable = NULL; 126 rec->fRowBytes = fRB; 127 return true; 128} 129 130void SkOneShotDiscardablePixelRef::onUnlockPixels() { 131 SkASSERT(!fFirstTime); 132 fDM->unlock(); 133} 134 135size_t SkOneShotDiscardablePixelRef::getAllocatedSizeInBytes() const { 136 return this->info().getSafeSize(fRB); 137} 138 139class SkResourceCacheDiscardableAllocator : public SkBitmap::Allocator { 140public: 141 SkResourceCacheDiscardableAllocator(SkResourceCache::DiscardableFactory factory) { 142 SkASSERT(factory); 143 fFactory = factory; 144 } 145 146 virtual bool allocPixelRef(SkBitmap*, SkColorTable*) SK_OVERRIDE; 147 148private: 149 SkResourceCache::DiscardableFactory fFactory; 150}; 151 152bool SkResourceCacheDiscardableAllocator::allocPixelRef(SkBitmap* bitmap, SkColorTable* ctable) { 153 size_t size = bitmap->getSize(); 154 uint64_t size64 = bitmap->computeSize64(); 155 if (0 == size || size64 > (uint64_t)size) { 156 return false; 157 } 158 159 SkDiscardableMemory* dm = fFactory(size); 160 if (NULL == dm) { 161 return false; 162 } 163 164 // can we relax this? 165 if (kN32_SkColorType != bitmap->colorType()) { 166 return false; 167 } 168 169 SkImageInfo info = bitmap->info(); 170 bitmap->setPixelRef(SkNEW_ARGS(SkOneShotDiscardablePixelRef, 171 (info, dm, bitmap->rowBytes())))->unref(); 172 bitmap->lockPixels(); 173 return bitmap->readyToDraw(); 174} 175 176SkResourceCache::SkResourceCache(DiscardableFactory factory) { 177 this->init(); 178 fDiscardableFactory = factory; 179 180 fAllocator = SkNEW_ARGS(SkResourceCacheDiscardableAllocator, (factory)); 181} 182 183SkResourceCache::SkResourceCache(size_t byteLimit) { 184 this->init(); 185 fTotalByteLimit = byteLimit; 186} 187 188SkResourceCache::~SkResourceCache() { 189 SkSafeUnref(fAllocator); 190 191 Rec* rec = fHead; 192 while (rec) { 193 Rec* next = rec->fNext; 194 SkDELETE(rec); 195 rec = next; 196 } 197 delete fHash; 198} 199 200//////////////////////////////////////////////////////////////////////////////// 201 202bool SkResourceCache::find(const Key& key, VisitorProc visitor, void* context) { 203 Rec* rec = fHash->find(key); 204 if (rec) { 205 if (visitor(*rec, context)) { 206 this->moveToHead(rec); // for our LRU 207 return true; 208 } else { 209 this->remove(rec); // stale 210 return false; 211 } 212 } 213 return false; 214} 215 216static void make_size_str(size_t size, SkString* str) { 217 const char suffix[] = { 'b', 'k', 'm', 'g', 't', 0 }; 218 int i = 0; 219 while (suffix[i] && (size > 1024)) { 220 i += 1; 221 size >>= 10; 222 } 223 str->printf("%zu%c", size, suffix[i]); 224} 225 226static bool gDumpCacheTransactions; 227 228void SkResourceCache::add(Rec* rec) { 229 SkASSERT(rec); 230 // See if we already have this key (racy inserts, etc.) 231 Rec* existing = fHash->find(rec->getKey()); 232 if (existing) { 233 SkDELETE(rec); 234 return; 235 } 236 237 this->addToHead(rec); 238 fHash->add(rec); 239 240 if (gDumpCacheTransactions) { 241 SkString bytesStr, totalStr; 242 make_size_str(rec->bytesUsed(), &bytesStr); 243 make_size_str(fTotalBytesUsed, &totalStr); 244 SkDebugf("RC: add %5s %12p key %08x -- total %5s, count %d\n", 245 bytesStr.c_str(), rec, rec->getHash(), totalStr.c_str(), fCount); 246 } 247 248 // since the new rec may push us over-budget, we perform a purge check now 249 this->purgeAsNeeded(); 250} 251 252void SkResourceCache::remove(Rec* rec) { 253 size_t used = rec->bytesUsed(); 254 SkASSERT(used <= fTotalBytesUsed); 255 256 this->detach(rec); 257 fHash->remove(rec->getKey()); 258 259 fTotalBytesUsed -= used; 260 fCount -= 1; 261 262 if (gDumpCacheTransactions) { 263 SkString bytesStr, totalStr; 264 make_size_str(used, &bytesStr); 265 make_size_str(fTotalBytesUsed, &totalStr); 266 SkDebugf("RC: remove %5s %12p key %08x -- total %5s, count %d\n", 267 bytesStr.c_str(), rec, rec->getHash(), totalStr.c_str(), fCount); 268 } 269 270 SkDELETE(rec); 271} 272 273void SkResourceCache::purgeAsNeeded(bool forcePurge) { 274 size_t byteLimit; 275 int countLimit; 276 277 if (fDiscardableFactory) { 278 countLimit = SK_DISCARDABLEMEMORY_SCALEDIMAGECACHE_COUNT_LIMIT; 279 byteLimit = SK_MaxU32; // no limit based on bytes 280 } else { 281 countLimit = SK_MaxS32; // no limit based on count 282 byteLimit = fTotalByteLimit; 283 } 284 285 Rec* rec = fTail; 286 while (rec) { 287 if (!forcePurge && fTotalBytesUsed < byteLimit && fCount < countLimit) { 288 break; 289 } 290 291 Rec* prev = rec->fPrev; 292 this->remove(rec); 293 rec = prev; 294 } 295} 296 297size_t SkResourceCache::setTotalByteLimit(size_t newLimit) { 298 size_t prevLimit = fTotalByteLimit; 299 fTotalByteLimit = newLimit; 300 if (newLimit < prevLimit) { 301 this->purgeAsNeeded(); 302 } 303 return prevLimit; 304} 305 306/////////////////////////////////////////////////////////////////////////////// 307 308void SkResourceCache::detach(Rec* rec) { 309 Rec* prev = rec->fPrev; 310 Rec* next = rec->fNext; 311 312 if (!prev) { 313 SkASSERT(fHead == rec); 314 fHead = next; 315 } else { 316 prev->fNext = next; 317 } 318 319 if (!next) { 320 fTail = prev; 321 } else { 322 next->fPrev = prev; 323 } 324 325 rec->fNext = rec->fPrev = NULL; 326} 327 328void SkResourceCache::moveToHead(Rec* rec) { 329 if (fHead == rec) { 330 return; 331 } 332 333 SkASSERT(fHead); 334 SkASSERT(fTail); 335 336 this->validate(); 337 338 this->detach(rec); 339 340 fHead->fPrev = rec; 341 rec->fNext = fHead; 342 fHead = rec; 343 344 this->validate(); 345} 346 347void SkResourceCache::addToHead(Rec* rec) { 348 this->validate(); 349 350 rec->fPrev = NULL; 351 rec->fNext = fHead; 352 if (fHead) { 353 fHead->fPrev = rec; 354 } 355 fHead = rec; 356 if (!fTail) { 357 fTail = rec; 358 } 359 fTotalBytesUsed += rec->bytesUsed(); 360 fCount += 1; 361 362 this->validate(); 363} 364 365/////////////////////////////////////////////////////////////////////////////// 366 367#ifdef SK_DEBUG 368void SkResourceCache::validate() const { 369 if (NULL == fHead) { 370 SkASSERT(NULL == fTail); 371 SkASSERT(0 == fTotalBytesUsed); 372 return; 373 } 374 375 if (fHead == fTail) { 376 SkASSERT(NULL == fHead->fPrev); 377 SkASSERT(NULL == fHead->fNext); 378 SkASSERT(fHead->bytesUsed() == fTotalBytesUsed); 379 return; 380 } 381 382 SkASSERT(NULL == fHead->fPrev); 383 SkASSERT(fHead->fNext); 384 SkASSERT(NULL == fTail->fNext); 385 SkASSERT(fTail->fPrev); 386 387 size_t used = 0; 388 int count = 0; 389 const Rec* rec = fHead; 390 while (rec) { 391 count += 1; 392 used += rec->bytesUsed(); 393 SkASSERT(used <= fTotalBytesUsed); 394 rec = rec->fNext; 395 } 396 SkASSERT(fCount == count); 397 398 rec = fTail; 399 while (rec) { 400 SkASSERT(count > 0); 401 count -= 1; 402 SkASSERT(used >= rec->bytesUsed()); 403 used -= rec->bytesUsed(); 404 rec = rec->fPrev; 405 } 406 407 SkASSERT(0 == count); 408 SkASSERT(0 == used); 409} 410#endif 411 412void SkResourceCache::dump() const { 413 this->validate(); 414 415 SkDebugf("SkResourceCache: count=%d bytes=%d %s\n", 416 fCount, fTotalBytesUsed, fDiscardableFactory ? "discardable" : "malloc"); 417} 418 419size_t SkResourceCache::setSingleAllocationByteLimit(size_t newLimit) { 420 size_t oldLimit = fSingleAllocationByteLimit; 421 fSingleAllocationByteLimit = newLimit; 422 return oldLimit; 423} 424 425size_t SkResourceCache::getSingleAllocationByteLimit() const { 426 return fSingleAllocationByteLimit; 427} 428 429/////////////////////////////////////////////////////////////////////////////// 430 431#include "SkThread.h" 432 433SK_DECLARE_STATIC_MUTEX(gMutex); 434static SkResourceCache* gResourceCache = NULL; 435static void cleanup_gResourceCache() { 436 // We'll clean this up in our own tests, but disable for clients. 437 // Chrome seems to have funky multi-process things going on in unit tests that 438 // makes this unsafe to delete when the main process atexit()s. 439 // SkLazyPtr does the same sort of thing. 440#if SK_DEVELOPER 441 SkDELETE(gResourceCache); 442#endif 443} 444 445/** Must hold gMutex when calling. */ 446static SkResourceCache* get_cache() { 447 // gMutex is always held when this is called, so we don't need to be fancy in here. 448 gMutex.assertHeld(); 449 if (NULL == gResourceCache) { 450#ifdef SK_USE_DISCARDABLE_SCALEDIMAGECACHE 451 gResourceCache = SkNEW_ARGS(SkResourceCache, (SkDiscardableMemory::Create)); 452#else 453 gResourceCache = SkNEW_ARGS(SkResourceCache, (SK_DEFAULT_IMAGE_CACHE_LIMIT)); 454#endif 455 atexit(cleanup_gResourceCache); 456 } 457 return gResourceCache; 458} 459 460size_t SkResourceCache::GetTotalBytesUsed() { 461 SkAutoMutexAcquire am(gMutex); 462 return get_cache()->getTotalBytesUsed(); 463} 464 465size_t SkResourceCache::GetTotalByteLimit() { 466 SkAutoMutexAcquire am(gMutex); 467 return get_cache()->getTotalByteLimit(); 468} 469 470size_t SkResourceCache::SetTotalByteLimit(size_t newLimit) { 471 SkAutoMutexAcquire am(gMutex); 472 return get_cache()->setTotalByteLimit(newLimit); 473} 474 475SkResourceCache::DiscardableFactory SkResourceCache::GetDiscardableFactory() { 476 SkAutoMutexAcquire am(gMutex); 477 return get_cache()->discardableFactory(); 478} 479 480SkBitmap::Allocator* SkResourceCache::GetAllocator() { 481 SkAutoMutexAcquire am(gMutex); 482 return get_cache()->allocator(); 483} 484 485void SkResourceCache::Dump() { 486 SkAutoMutexAcquire am(gMutex); 487 get_cache()->dump(); 488} 489 490size_t SkResourceCache::SetSingleAllocationByteLimit(size_t size) { 491 SkAutoMutexAcquire am(gMutex); 492 return get_cache()->setSingleAllocationByteLimit(size); 493} 494 495size_t SkResourceCache::GetSingleAllocationByteLimit() { 496 SkAutoMutexAcquire am(gMutex); 497 return get_cache()->getSingleAllocationByteLimit(); 498} 499 500void SkResourceCache::PurgeAll() { 501 SkAutoMutexAcquire am(gMutex); 502 return get_cache()->purgeAll(); 503} 504 505bool SkResourceCache::Find(const Key& key, VisitorProc visitor, void* context) { 506 SkAutoMutexAcquire am(gMutex); 507 return get_cache()->find(key, visitor, context); 508} 509 510void SkResourceCache::Add(Rec* rec) { 511 SkAutoMutexAcquire am(gMutex); 512 get_cache()->add(rec); 513} 514 515/////////////////////////////////////////////////////////////////////////////// 516 517#include "SkGraphics.h" 518 519size_t SkGraphics::GetResourceCacheTotalBytesUsed() { 520 return SkResourceCache::GetTotalBytesUsed(); 521} 522 523size_t SkGraphics::GetResourceCacheTotalByteLimit() { 524 return SkResourceCache::GetTotalByteLimit(); 525} 526 527size_t SkGraphics::SetResourceCacheTotalByteLimit(size_t newLimit) { 528 return SkResourceCache::SetTotalByteLimit(newLimit); 529} 530 531size_t SkGraphics::GetResourceCacheSingleAllocationByteLimit() { 532 return SkResourceCache::GetSingleAllocationByteLimit(); 533} 534 535size_t SkGraphics::SetResourceCacheSingleAllocationByteLimit(size_t newLimit) { 536 return SkResourceCache::SetSingleAllocationByteLimit(newLimit); 537} 538 539void SkGraphics::PurgeResourceCache() { 540 return SkResourceCache::PurgeAll(); 541} 542 543