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 "SkResourceCache.h" 9 10 #include "SkDiscardableMemory.h" 11 #include "SkMessageBus.h" 12 #include "SkMipMap.h" 13 #include "SkMutex.h" 14 #include "SkOpts.h" 15 #include "SkTo.h" 16 #include "SkTraceMemoryDump.h" 17 18 #include <stddef.h> 19 #include <stdlib.h> 20 21 DECLARE_SKMESSAGEBUS_MESSAGE(SkResourceCache::PurgeSharedIDMessage) 22 23 static inline bool SkShouldPostMessageToBus( 24 const SkResourceCache::PurgeSharedIDMessage&, uint32_t) { 25 // SkResourceCache is typically used as a singleton and we don't label Inboxes so all messages 26 // go to all inboxes. 27 return true; 28 } 29 30 // This can be defined by the caller's build system 31 //#define SK_USE_DISCARDABLE_SCALEDIMAGECACHE 32 33 #ifndef SK_DISCARDABLEMEMORY_SCALEDIMAGECACHE_COUNT_LIMIT 34 # define SK_DISCARDABLEMEMORY_SCALEDIMAGECACHE_COUNT_LIMIT 1024 35 #endif 36 37 #ifndef SK_DEFAULT_IMAGE_CACHE_LIMIT 38 #define SK_DEFAULT_IMAGE_CACHE_LIMIT (32 * 1024 * 1024) 39 #endif 40 41 void SkResourceCache::Key::init(void* nameSpace, uint64_t sharedID, size_t dataSize) { 42 SkASSERT(SkAlign4(dataSize) == dataSize); 43 44 // fCount32 and fHash are not hashed 45 static const int kUnhashedLocal32s = 2; // fCache32 + fHash 46 static const int kSharedIDLocal32s = 2; // fSharedID_lo + fSharedID_hi 47 static const int kHashedLocal32s = kSharedIDLocal32s + (sizeof(fNamespace) >> 2); 48 static const int kLocal32s = kUnhashedLocal32s + kHashedLocal32s; 49 50 static_assert(sizeof(Key) == (kLocal32s << 2), "unaccounted_key_locals"); 51 static_assert(sizeof(Key) == offsetof(Key, fNamespace) + sizeof(fNamespace), 52 "namespace_field_must_be_last"); 53 54 fCount32 = SkToS32(kLocal32s + (dataSize >> 2)); 55 fSharedID_lo = (uint32_t)sharedID; 56 fSharedID_hi = (uint32_t)(sharedID >> 32); 57 fNamespace = nameSpace; 58 // skip unhashed fields when computing the hash 59 fHash = SkOpts::hash(this->as32() + kUnhashedLocal32s, 60 (fCount32 - kUnhashedLocal32s) << 2); 61 } 62 63 #include "SkTHash.h" 64 65 namespace { 66 struct HashTraits { 67 static uint32_t Hash(const SkResourceCache::Key& key) { return key.hash(); } 68 static const SkResourceCache::Key& GetKey(const SkResourceCache::Rec* rec) { 69 return rec->getKey(); 70 } 71 }; 72 } 73 74 class SkResourceCache::Hash : 75 public SkTHashTable<SkResourceCache::Rec*, SkResourceCache::Key, HashTraits> {}; 76 77 78 /////////////////////////////////////////////////////////////////////////////// 79 80 void SkResourceCache::init() { 81 fHead = nullptr; 82 fTail = nullptr; 83 fHash = new Hash; 84 fTotalBytesUsed = 0; 85 fCount = 0; 86 fSingleAllocationByteLimit = 0; 87 88 // One of these should be explicit set by the caller after we return. 89 fTotalByteLimit = 0; 90 fDiscardableFactory = nullptr; 91 } 92 93 SkResourceCache::SkResourceCache(DiscardableFactory factory) { 94 this->init(); 95 fDiscardableFactory = factory; 96 } 97 98 SkResourceCache::SkResourceCache(size_t byteLimit) { 99 this->init(); 100 fTotalByteLimit = byteLimit; 101 } 102 103 SkResourceCache::~SkResourceCache() { 104 Rec* rec = fHead; 105 while (rec) { 106 Rec* next = rec->fNext; 107 delete rec; 108 rec = next; 109 } 110 delete fHash; 111 } 112 113 //////////////////////////////////////////////////////////////////////////////// 114 115 bool SkResourceCache::find(const Key& key, FindVisitor visitor, void* context) { 116 this->checkMessages(); 117 118 if (auto found = fHash->find(key)) { 119 Rec* rec = *found; 120 if (visitor(*rec, context)) { 121 this->moveToHead(rec); // for our LRU 122 return true; 123 } else { 124 this->remove(rec); // stale 125 return false; 126 } 127 } 128 return false; 129 } 130 131 static void make_size_str(size_t size, SkString* str) { 132 const char suffix[] = { 'b', 'k', 'm', 'g', 't', 0 }; 133 int i = 0; 134 while (suffix[i] && (size > 1024)) { 135 i += 1; 136 size >>= 10; 137 } 138 str->printf("%zu%c", size, suffix[i]); 139 } 140 141 static bool gDumpCacheTransactions; 142 143 void SkResourceCache::add(Rec* rec, void* payload) { 144 this->checkMessages(); 145 146 SkASSERT(rec); 147 // See if we already have this key (racy inserts, etc.) 148 if (Rec** preexisting = fHash->find(rec->getKey())) { 149 Rec* prev = *preexisting; 150 if (prev->canBePurged()) { 151 // if it can be purged, the install may fail, so we have to remove it 152 this->remove(prev); 153 } else { 154 // if it cannot be purged, we reuse it and delete the new one 155 prev->postAddInstall(payload); 156 delete rec; 157 return; 158 } 159 } 160 161 this->addToHead(rec); 162 fHash->set(rec); 163 rec->postAddInstall(payload); 164 165 if (gDumpCacheTransactions) { 166 SkString bytesStr, totalStr; 167 make_size_str(rec->bytesUsed(), &bytesStr); 168 make_size_str(fTotalBytesUsed, &totalStr); 169 SkDebugf("RC: add %5s %12p key %08x -- total %5s, count %d\n", 170 bytesStr.c_str(), rec, rec->getHash(), totalStr.c_str(), fCount); 171 } 172 173 // since the new rec may push us over-budget, we perform a purge check now 174 this->purgeAsNeeded(); 175 } 176 177 void SkResourceCache::remove(Rec* rec) { 178 SkASSERT(rec->canBePurged()); 179 size_t used = rec->bytesUsed(); 180 SkASSERT(used <= fTotalBytesUsed); 181 182 this->release(rec); 183 fHash->remove(rec->getKey()); 184 185 fTotalBytesUsed -= used; 186 fCount -= 1; 187 188 //SkDebugf("-RC count [%3d] bytes %d\n", fCount, fTotalBytesUsed); 189 190 if (gDumpCacheTransactions) { 191 SkString bytesStr, totalStr; 192 make_size_str(used, &bytesStr); 193 make_size_str(fTotalBytesUsed, &totalStr); 194 SkDebugf("RC: remove %5s %12p key %08x -- total %5s, count %d\n", 195 bytesStr.c_str(), rec, rec->getHash(), totalStr.c_str(), fCount); 196 } 197 198 delete rec; 199 } 200 201 void SkResourceCache::purgeAsNeeded(bool forcePurge) { 202 size_t byteLimit; 203 int countLimit; 204 205 if (fDiscardableFactory) { 206 countLimit = SK_DISCARDABLEMEMORY_SCALEDIMAGECACHE_COUNT_LIMIT; 207 byteLimit = UINT32_MAX; // no limit based on bytes 208 } else { 209 countLimit = SK_MaxS32; // no limit based on count 210 byteLimit = fTotalByteLimit; 211 } 212 213 Rec* rec = fTail; 214 while (rec) { 215 if (!forcePurge && fTotalBytesUsed < byteLimit && fCount < countLimit) { 216 break; 217 } 218 219 Rec* prev = rec->fPrev; 220 if (rec->canBePurged()) { 221 this->remove(rec); 222 } 223 rec = prev; 224 } 225 } 226 227 //#define SK_TRACK_PURGE_SHAREDID_HITRATE 228 229 #ifdef SK_TRACK_PURGE_SHAREDID_HITRATE 230 static int gPurgeCallCounter; 231 static int gPurgeHitCounter; 232 #endif 233 234 void SkResourceCache::purgeSharedID(uint64_t sharedID) { 235 if (0 == sharedID) { 236 return; 237 } 238 239 #ifdef SK_TRACK_PURGE_SHAREDID_HITRATE 240 gPurgeCallCounter += 1; 241 bool found = false; 242 #endif 243 // go backwards, just like purgeAsNeeded, just to make the code similar. 244 // could iterate either direction and still be correct. 245 Rec* rec = fTail; 246 while (rec) { 247 Rec* prev = rec->fPrev; 248 if (rec->getKey().getSharedID() == sharedID) { 249 // even though the "src" is now dead, caches could still be in-flight, so 250 // we have to check if it can be removed. 251 if (rec->canBePurged()) { 252 this->remove(rec); 253 } 254 #ifdef SK_TRACK_PURGE_SHAREDID_HITRATE 255 found = true; 256 #endif 257 } 258 rec = prev; 259 } 260 261 #ifdef SK_TRACK_PURGE_SHAREDID_HITRATE 262 if (found) { 263 gPurgeHitCounter += 1; 264 } 265 266 SkDebugf("PurgeShared calls=%d hits=%d rate=%g\n", gPurgeCallCounter, gPurgeHitCounter, 267 gPurgeHitCounter * 100.0 / gPurgeCallCounter); 268 #endif 269 } 270 271 void SkResourceCache::visitAll(Visitor visitor, void* context) { 272 // go backwards, just like purgeAsNeeded, just to make the code similar. 273 // could iterate either direction and still be correct. 274 Rec* rec = fTail; 275 while (rec) { 276 visitor(*rec, context); 277 rec = rec->fPrev; 278 } 279 } 280 281 /////////////////////////////////////////////////////////////////////////////////////////////////// 282 283 size_t SkResourceCache::setTotalByteLimit(size_t newLimit) { 284 size_t prevLimit = fTotalByteLimit; 285 fTotalByteLimit = newLimit; 286 if (newLimit < prevLimit) { 287 this->purgeAsNeeded(); 288 } 289 return prevLimit; 290 } 291 292 SkCachedData* SkResourceCache::newCachedData(size_t bytes) { 293 this->checkMessages(); 294 295 if (fDiscardableFactory) { 296 SkDiscardableMemory* dm = fDiscardableFactory(bytes); 297 return dm ? new SkCachedData(bytes, dm) : nullptr; 298 } else { 299 return new SkCachedData(sk_malloc_throw(bytes), bytes); 300 } 301 } 302 303 /////////////////////////////////////////////////////////////////////////////// 304 305 void SkResourceCache::release(Rec* rec) { 306 Rec* prev = rec->fPrev; 307 Rec* next = rec->fNext; 308 309 if (!prev) { 310 SkASSERT(fHead == rec); 311 fHead = next; 312 } else { 313 prev->fNext = next; 314 } 315 316 if (!next) { 317 fTail = prev; 318 } else { 319 next->fPrev = prev; 320 } 321 322 rec->fNext = rec->fPrev = nullptr; 323 } 324 325 void SkResourceCache::moveToHead(Rec* rec) { 326 if (fHead == rec) { 327 return; 328 } 329 330 SkASSERT(fHead); 331 SkASSERT(fTail); 332 333 this->validate(); 334 335 this->release(rec); 336 337 fHead->fPrev = rec; 338 rec->fNext = fHead; 339 fHead = rec; 340 341 this->validate(); 342 } 343 344 void SkResourceCache::addToHead(Rec* rec) { 345 this->validate(); 346 347 rec->fPrev = nullptr; 348 rec->fNext = fHead; 349 if (fHead) { 350 fHead->fPrev = rec; 351 } 352 fHead = rec; 353 if (!fTail) { 354 fTail = rec; 355 } 356 fTotalBytesUsed += rec->bytesUsed(); 357 fCount += 1; 358 359 this->validate(); 360 } 361 362 /////////////////////////////////////////////////////////////////////////////// 363 364 #ifdef SK_DEBUG 365 void SkResourceCache::validate() const { 366 if (nullptr == fHead) { 367 SkASSERT(nullptr == fTail); 368 SkASSERT(0 == fTotalBytesUsed); 369 return; 370 } 371 372 if (fHead == fTail) { 373 SkASSERT(nullptr == fHead->fPrev); 374 SkASSERT(nullptr == fHead->fNext); 375 SkASSERT(fHead->bytesUsed() == fTotalBytesUsed); 376 return; 377 } 378 379 SkASSERT(nullptr == fHead->fPrev); 380 SkASSERT(fHead->fNext); 381 SkASSERT(nullptr == fTail->fNext); 382 SkASSERT(fTail->fPrev); 383 384 size_t used = 0; 385 int count = 0; 386 const Rec* rec = fHead; 387 while (rec) { 388 count += 1; 389 used += rec->bytesUsed(); 390 SkASSERT(used <= fTotalBytesUsed); 391 rec = rec->fNext; 392 } 393 SkASSERT(fCount == count); 394 395 rec = fTail; 396 while (rec) { 397 SkASSERT(count > 0); 398 count -= 1; 399 SkASSERT(used >= rec->bytesUsed()); 400 used -= rec->bytesUsed(); 401 rec = rec->fPrev; 402 } 403 404 SkASSERT(0 == count); 405 SkASSERT(0 == used); 406 } 407 #endif 408 409 void SkResourceCache::dump() const { 410 this->validate(); 411 412 SkDebugf("SkResourceCache: count=%d bytes=%d %s\n", 413 fCount, fTotalBytesUsed, fDiscardableFactory ? "discardable" : "malloc"); 414 } 415 416 size_t SkResourceCache::setSingleAllocationByteLimit(size_t newLimit) { 417 size_t oldLimit = fSingleAllocationByteLimit; 418 fSingleAllocationByteLimit = newLimit; 419 return oldLimit; 420 } 421 422 size_t SkResourceCache::getSingleAllocationByteLimit() const { 423 return fSingleAllocationByteLimit; 424 } 425 426 size_t SkResourceCache::getEffectiveSingleAllocationByteLimit() const { 427 // fSingleAllocationByteLimit == 0 means the caller is asking for our default 428 size_t limit = fSingleAllocationByteLimit; 429 430 // if we're not discardable (i.e. we are fixed-budget) then cap the single-limit 431 // to our budget. 432 if (nullptr == fDiscardableFactory) { 433 if (0 == limit) { 434 limit = fTotalByteLimit; 435 } else { 436 limit = SkTMin(limit, fTotalByteLimit); 437 } 438 } 439 return limit; 440 } 441 442 void SkResourceCache::checkMessages() { 443 SkTArray<PurgeSharedIDMessage> msgs; 444 fPurgeSharedIDInbox.poll(&msgs); 445 for (int i = 0; i < msgs.count(); ++i) { 446 this->purgeSharedID(msgs[i].fSharedID); 447 } 448 } 449 450 /////////////////////////////////////////////////////////////////////////////// 451 452 SK_DECLARE_STATIC_MUTEX(gMutex); 453 static SkResourceCache* gResourceCache = nullptr; 454 455 /** Must hold gMutex when calling. */ 456 static SkResourceCache* get_cache() { 457 // gMutex is always held when this is called, so we don't need to be fancy in here. 458 gMutex.assertHeld(); 459 if (nullptr == gResourceCache) { 460 #ifdef SK_USE_DISCARDABLE_SCALEDIMAGECACHE 461 gResourceCache = new SkResourceCache(SkDiscardableMemory::Create); 462 #else 463 gResourceCache = new SkResourceCache(SK_DEFAULT_IMAGE_CACHE_LIMIT); 464 #endif 465 } 466 return gResourceCache; 467 } 468 469 size_t SkResourceCache::GetTotalBytesUsed() { 470 SkAutoMutexAcquire am(gMutex); 471 return get_cache()->getTotalBytesUsed(); 472 } 473 474 size_t SkResourceCache::GetTotalByteLimit() { 475 SkAutoMutexAcquire am(gMutex); 476 return get_cache()->getTotalByteLimit(); 477 } 478 479 size_t SkResourceCache::SetTotalByteLimit(size_t newLimit) { 480 SkAutoMutexAcquire am(gMutex); 481 return get_cache()->setTotalByteLimit(newLimit); 482 } 483 484 SkResourceCache::DiscardableFactory SkResourceCache::GetDiscardableFactory() { 485 SkAutoMutexAcquire am(gMutex); 486 return get_cache()->discardableFactory(); 487 } 488 489 SkCachedData* SkResourceCache::NewCachedData(size_t bytes) { 490 SkAutoMutexAcquire am(gMutex); 491 return get_cache()->newCachedData(bytes); 492 } 493 494 void SkResourceCache::Dump() { 495 SkAutoMutexAcquire am(gMutex); 496 get_cache()->dump(); 497 } 498 499 size_t SkResourceCache::SetSingleAllocationByteLimit(size_t size) { 500 SkAutoMutexAcquire am(gMutex); 501 return get_cache()->setSingleAllocationByteLimit(size); 502 } 503 504 size_t SkResourceCache::GetSingleAllocationByteLimit() { 505 SkAutoMutexAcquire am(gMutex); 506 return get_cache()->getSingleAllocationByteLimit(); 507 } 508 509 size_t SkResourceCache::GetEffectiveSingleAllocationByteLimit() { 510 SkAutoMutexAcquire am(gMutex); 511 return get_cache()->getEffectiveSingleAllocationByteLimit(); 512 } 513 514 void SkResourceCache::PurgeAll() { 515 SkAutoMutexAcquire am(gMutex); 516 return get_cache()->purgeAll(); 517 } 518 519 bool SkResourceCache::Find(const Key& key, FindVisitor visitor, void* context) { 520 SkAutoMutexAcquire am(gMutex); 521 return get_cache()->find(key, visitor, context); 522 } 523 524 void SkResourceCache::Add(Rec* rec, void* payload) { 525 SkAutoMutexAcquire am(gMutex); 526 get_cache()->add(rec, payload); 527 } 528 529 void SkResourceCache::VisitAll(Visitor visitor, void* context) { 530 SkAutoMutexAcquire am(gMutex); 531 get_cache()->visitAll(visitor, context); 532 } 533 534 void SkResourceCache::PostPurgeSharedID(uint64_t sharedID) { 535 if (sharedID) { 536 SkMessageBus<PurgeSharedIDMessage>::Post(PurgeSharedIDMessage(sharedID)); 537 } 538 } 539 540 /////////////////////////////////////////////////////////////////////////////// 541 542 #include "SkGraphics.h" 543 #include "SkImageFilter.h" 544 545 size_t SkGraphics::GetResourceCacheTotalBytesUsed() { 546 return SkResourceCache::GetTotalBytesUsed(); 547 } 548 549 size_t SkGraphics::GetResourceCacheTotalByteLimit() { 550 return SkResourceCache::GetTotalByteLimit(); 551 } 552 553 size_t SkGraphics::SetResourceCacheTotalByteLimit(size_t newLimit) { 554 return SkResourceCache::SetTotalByteLimit(newLimit); 555 } 556 557 size_t SkGraphics::GetResourceCacheSingleAllocationByteLimit() { 558 return SkResourceCache::GetSingleAllocationByteLimit(); 559 } 560 561 size_t SkGraphics::SetResourceCacheSingleAllocationByteLimit(size_t newLimit) { 562 return SkResourceCache::SetSingleAllocationByteLimit(newLimit); 563 } 564 565 void SkGraphics::PurgeResourceCache() { 566 SkImageFilter::PurgeCache(); 567 return SkResourceCache::PurgeAll(); 568 } 569 570 ///////////// 571 572 static void dump_visitor(const SkResourceCache::Rec& rec, void*) { 573 SkDebugf("RC: %12s bytes %9lu discardable %p\n", 574 rec.getCategory(), rec.bytesUsed(), rec.diagnostic_only_getDiscardable()); 575 } 576 577 void SkResourceCache::TestDumpMemoryStatistics() { 578 VisitAll(dump_visitor, nullptr); 579 } 580 581 static void sk_trace_dump_visitor(const SkResourceCache::Rec& rec, void* context) { 582 SkTraceMemoryDump* dump = static_cast<SkTraceMemoryDump*>(context); 583 SkString dumpName = SkStringPrintf("skia/sk_resource_cache/%s_%p", rec.getCategory(), &rec); 584 SkDiscardableMemory* discardable = rec.diagnostic_only_getDiscardable(); 585 if (discardable) { 586 dump->setDiscardableMemoryBacking(dumpName.c_str(), *discardable); 587 588 // The discardable memory size will be calculated by dumper, but we also dump what we think 589 // the size of object in memory is irrespective of whether object is live or dead. 590 dump->dumpNumericValue(dumpName.c_str(), "discardable_size", "bytes", rec.bytesUsed()); 591 } else { 592 dump->dumpNumericValue(dumpName.c_str(), "size", "bytes", rec.bytesUsed()); 593 dump->setMemoryBacking(dumpName.c_str(), "malloc", nullptr); 594 } 595 } 596 597 void SkResourceCache::DumpMemoryStatistics(SkTraceMemoryDump* dump) { 598 // Since resource could be backed by malloc or discardable, the cache always dumps detailed 599 // stats to be accurate. 600 VisitAll(sk_trace_dump_visitor, dump); 601 } 602