Flutter Engine
The Flutter Engine
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Modules Pages
SkResourceCache.cpp
Go to the documentation of this file.
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
9
22#include "src/core/SkChecksum.h"
25#include "src/core/SkTHash.h"
26
27#if defined(SK_USE_DISCARDABLE_SCALEDIMAGECACHE)
29#endif
30
31#include <algorithm>
32
33using namespace skia_private;
34
36
37static inline bool SkShouldPostMessageToBus(
38 const SkResourceCache::PurgeSharedIDMessage&, uint32_t) {
39 // SkResourceCache is typically used as a singleton and we don't label Inboxes so all messages
40 // go to all inboxes.
41 return true;
42}
43
44// This can be defined by the caller's build system
45//#define SK_USE_DISCARDABLE_SCALEDIMAGECACHE
46
47#ifndef SK_DISCARDABLEMEMORY_SCALEDIMAGECACHE_COUNT_LIMIT
48# define SK_DISCARDABLEMEMORY_SCALEDIMAGECACHE_COUNT_LIMIT 1024
49#endif
50
51#ifndef SK_DEFAULT_IMAGE_CACHE_LIMIT
52 #define SK_DEFAULT_IMAGE_CACHE_LIMIT (32 * 1024 * 1024)
53#endif
54
55void SkResourceCache::Key::init(void* nameSpace, uint64_t sharedID, size_t dataSize) {
56 SkASSERT(SkAlign4(dataSize) == dataSize);
57
58 // fCount32 and fHash are not hashed
59 static const int kUnhashedLocal32s = 2; // fCache32 + fHash
60 static const int kSharedIDLocal32s = 2; // fSharedID_lo + fSharedID_hi
61 static const int kHashedLocal32s = kSharedIDLocal32s + (sizeof(fNamespace) >> 2);
62 static const int kLocal32s = kUnhashedLocal32s + kHashedLocal32s;
63
64 static_assert(sizeof(Key) == (kLocal32s << 2), "unaccounted_key_locals");
65 static_assert(sizeof(Key) == offsetof(Key, fNamespace) + sizeof(fNamespace),
66 "namespace_field_must_be_last");
67
68 fCount32 = SkToS32(kLocal32s + (dataSize >> 2));
69 fSharedID_lo = (uint32_t)(sharedID & 0xFFFFFFFF);
70 fSharedID_hi = (uint32_t)(sharedID >> 32);
71 fNamespace = nameSpace;
72 // skip unhashed fields when computing the hash
73 fHash = SkChecksum::Hash32(this->as32() + kUnhashedLocal32s,
74 (fCount32 - kUnhashedLocal32s) << 2);
75}
76
77namespace {
78 struct HashTraits {
79 static uint32_t Hash(const SkResourceCache::Key& key) { return key.hash(); }
80 static const SkResourceCache::Key& GetKey(const SkResourceCache::Rec* rec) {
81 return rec->getKey();
82 }
83 };
84} // namespace
85
87 public THashTable<SkResourceCache::Rec*, SkResourceCache::Key, HashTraits> {};
88
89
90///////////////////////////////////////////////////////////////////////////////
91
92void SkResourceCache::init() {
93 fHead = nullptr;
94 fTail = nullptr;
95 fHash = new Hash;
96 fTotalBytesUsed = 0;
97 fCount = 0;
98 fSingleAllocationByteLimit = 0;
99
100 // One of these should be explicit set by the caller after we return.
101 fTotalByteLimit = 0;
102 fDiscardableFactory = nullptr;
103}
104
106 : fPurgeSharedIDInbox(SK_InvalidUniqueID) {
107 this->init();
108 fDiscardableFactory = factory;
109}
110
112 : fPurgeSharedIDInbox(SK_InvalidUniqueID) {
113 this->init();
114 fTotalByteLimit = byteLimit;
115}
116
118 Rec* rec = fHead;
119 while (rec) {
120 Rec* next = rec->fNext;
121 delete rec;
122 rec = next;
123 }
124 delete fHash;
125}
126
127////////////////////////////////////////////////////////////////////////////////
128
129bool SkResourceCache::find(const Key& key, FindVisitor visitor, void* context) {
130 this->checkMessages();
131
132 if (auto found = fHash->find(key)) {
133 Rec* rec = *found;
134 if (visitor(*rec, context)) {
135 this->moveToHead(rec); // for our LRU
136 return true;
137 } else {
138 this->remove(rec); // stale
139 return false;
140 }
141 }
142 return false;
143}
144
145static void make_size_str(size_t size, SkString* str) {
146 const char suffix[] = { 'b', 'k', 'm', 'g', 't', 0 };
147 int i = 0;
148 while (suffix[i] && (size > 1024)) {
149 i += 1;
150 size >>= 10;
151 }
152 str->printf("%zu%c", size, suffix[i]);
153}
154
156
157void SkResourceCache::add(Rec* rec, void* payload) {
158 this->checkMessages();
159
160 SkASSERT(rec);
161 // See if we already have this key (racy inserts, etc.)
162 if (Rec** preexisting = fHash->find(rec->getKey())) {
163 Rec* prev = *preexisting;
164 if (prev->canBePurged()) {
165 // if it can be purged, the install may fail, so we have to remove it
166 this->remove(prev);
167 } else {
168 // if it cannot be purged, we reuse it and delete the new one
169 prev->postAddInstall(payload);
170 delete rec;
171 return;
172 }
173 }
174
175 this->addToHead(rec);
176 fHash->set(rec);
177 rec->postAddInstall(payload);
178
180 SkString bytesStr, totalStr;
181 make_size_str(rec->bytesUsed(), &bytesStr);
182 make_size_str(fTotalBytesUsed, &totalStr);
183 SkDebugf("RC: add %5s %12p key %08x -- total %5s, count %d\n",
184 bytesStr.c_str(), rec, rec->getHash(), totalStr.c_str(), fCount);
185 }
186
187 // since the new rec may push us over-budget, we perform a purge check now
188 this->purgeAsNeeded();
189}
190
191void SkResourceCache::remove(Rec* rec) {
192 SkASSERT(rec->canBePurged());
193 size_t used = rec->bytesUsed();
194 SkASSERT(used <= fTotalBytesUsed);
195
196 this->release(rec);
197 fHash->remove(rec->getKey());
198
199 fTotalBytesUsed -= used;
200 fCount -= 1;
201
202 //SkDebugf("-RC count [%3d] bytes %d\n", fCount, fTotalBytesUsed);
203
205 SkString bytesStr, totalStr;
206 make_size_str(used, &bytesStr);
207 make_size_str(fTotalBytesUsed, &totalStr);
208 SkDebugf("RC: remove %5s %12p key %08x -- total %5s, count %d\n",
209 bytesStr.c_str(), rec, rec->getHash(), totalStr.c_str(), fCount);
210 }
211
212 delete rec;
213}
214
215void SkResourceCache::purgeAsNeeded(bool forcePurge) {
216 size_t byteLimit;
217 int countLimit;
218
219 if (fDiscardableFactory) {
221 byteLimit = UINT32_MAX; // no limit based on bytes
222 } else {
223 countLimit = SK_MaxS32; // no limit based on count
224 byteLimit = fTotalByteLimit;
225 }
226
227 Rec* rec = fTail;
228 while (rec) {
229 if (!forcePurge && fTotalBytesUsed < byteLimit && fCount < countLimit) {
230 break;
231 }
232
233 Rec* prev = rec->fPrev;
234 if (rec->canBePurged()) {
235 this->remove(rec);
236 }
237 rec = prev;
238 }
239}
240
241//#define SK_TRACK_PURGE_SHAREDID_HITRATE
242
243#ifdef SK_TRACK_PURGE_SHAREDID_HITRATE
244static int gPurgeCallCounter;
245static int gPurgeHitCounter;
246#endif
247
248void SkResourceCache::purgeSharedID(uint64_t sharedID) {
249 if (0 == sharedID) {
250 return;
251 }
252
253#ifdef SK_TRACK_PURGE_SHAREDID_HITRATE
254 gPurgeCallCounter += 1;
255 bool found = false;
256#endif
257 // go backwards, just like purgeAsNeeded, just to make the code similar.
258 // could iterate either direction and still be correct.
259 Rec* rec = fTail;
260 while (rec) {
261 Rec* prev = rec->fPrev;
262 if (rec->getKey().getSharedID() == sharedID) {
263 // even though the "src" is now dead, caches could still be in-flight, so
264 // we have to check if it can be removed.
265 if (rec->canBePurged()) {
266 this->remove(rec);
267 }
268#ifdef SK_TRACK_PURGE_SHAREDID_HITRATE
269 found = true;
270#endif
271 }
272 rec = prev;
273 }
274
275#ifdef SK_TRACK_PURGE_SHAREDID_HITRATE
276 if (found) {
277 gPurgeHitCounter += 1;
278 }
279
280 SkDebugf("PurgeShared calls=%d hits=%d rate=%g\n", gPurgeCallCounter, gPurgeHitCounter,
281 gPurgeHitCounter * 100.0 / gPurgeCallCounter);
282#endif
283}
284
285void SkResourceCache::visitAll(Visitor visitor, void* context) {
286 // go backwards, just like purgeAsNeeded, just to make the code similar.
287 // could iterate either direction and still be correct.
288 Rec* rec = fTail;
289 while (rec) {
290 visitor(*rec, context);
291 rec = rec->fPrev;
292 }
293}
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
307 this->checkMessages();
308
309 if (fDiscardableFactory) {
310 SkDiscardableMemory* dm = fDiscardableFactory(bytes);
311 return dm ? new SkCachedData(bytes, dm) : nullptr;
312 } else {
313 return new SkCachedData(sk_malloc_throw(bytes), bytes);
314 }
315}
316
317///////////////////////////////////////////////////////////////////////////////
318
319void SkResourceCache::release(Rec* rec) {
320 Rec* prev = rec->fPrev;
321 Rec* next = rec->fNext;
322
323 if (!prev) {
324 SkASSERT(fHead == rec);
325 fHead = next;
326 } else {
327 prev->fNext = next;
328 }
329
330 if (!next) {
331 fTail = prev;
332 } else {
333 next->fPrev = prev;
334 }
335
336 rec->fNext = rec->fPrev = nullptr;
337}
338
339void SkResourceCache::moveToHead(Rec* rec) {
340 if (fHead == rec) {
341 return;
342 }
343
344 SkASSERT(fHead);
345 SkASSERT(fTail);
346
347 this->validate();
348
349 this->release(rec);
350
351 fHead->fPrev = rec;
352 rec->fNext = fHead;
353 fHead = rec;
354
355 this->validate();
356}
357
358void SkResourceCache::addToHead(Rec* rec) {
359 this->validate();
360
361 rec->fPrev = nullptr;
362 rec->fNext = fHead;
363 if (fHead) {
364 fHead->fPrev = rec;
365 }
366 fHead = rec;
367 if (!fTail) {
368 fTail = rec;
369 }
370 fTotalBytesUsed += rec->bytesUsed();
371 fCount += 1;
372
373 this->validate();
374}
375
376///////////////////////////////////////////////////////////////////////////////
377
378#ifdef SK_DEBUG
379void SkResourceCache::validate() const {
380 if (nullptr == fHead) {
381 SkASSERT(nullptr == fTail);
382 SkASSERT(0 == fTotalBytesUsed);
383 return;
384 }
385
386 if (fHead == fTail) {
387 SkASSERT(nullptr == fHead->fPrev);
388 SkASSERT(nullptr == fHead->fNext);
389 SkASSERT(fHead->bytesUsed() == fTotalBytesUsed);
390 return;
391 }
392
393 SkASSERT(nullptr == fHead->fPrev);
394 SkASSERT(fHead->fNext);
395 SkASSERT(nullptr == fTail->fNext);
396 SkASSERT(fTail->fPrev);
397
398 size_t used = 0;
399 int count = 0;
400 const Rec* rec = fHead;
401 while (rec) {
402 count += 1;
403 used += rec->bytesUsed();
404 SkASSERT(used <= fTotalBytesUsed);
405 rec = rec->fNext;
406 }
407 SkASSERT(fCount == count);
408
409 rec = fTail;
410 while (rec) {
411 SkASSERT(count > 0);
412 count -= 1;
413 SkASSERT(used >= rec->bytesUsed());
414 used -= rec->bytesUsed();
415 rec = rec->fPrev;
416 }
417
418 SkASSERT(0 == count);
419 SkASSERT(0 == used);
420}
421#endif
422
424 this->validate();
425
426 SkDebugf("SkResourceCache: count=%d bytes=%zu %s\n",
427 fCount, fTotalBytesUsed, fDiscardableFactory ? "discardable" : "malloc");
428}
429
431 size_t oldLimit = fSingleAllocationByteLimit;
432 fSingleAllocationByteLimit = newLimit;
433 return oldLimit;
434}
435
437 return fSingleAllocationByteLimit;
438}
439
441 // fSingleAllocationByteLimit == 0 means the caller is asking for our default
442 size_t limit = fSingleAllocationByteLimit;
443
444 // if we're not discardable (i.e. we are fixed-budget) then cap the single-limit
445 // to our budget.
446 if (nullptr == fDiscardableFactory) {
447 if (0 == limit) {
448 limit = fTotalByteLimit;
449 } else {
450 limit = std::min(limit, fTotalByteLimit);
451 }
452 }
453 return limit;
454}
455
456void SkResourceCache::checkMessages() {
458 fPurgeSharedIDInbox.poll(&msgs);
459 for (int i = 0; i < msgs.size(); ++i) {
460 this->purgeSharedID(msgs[i].fSharedID);
461 }
462}
463
464///////////////////////////////////////////////////////////////////////////////
465
467static SkMutex& resource_cache_mutex() {
468 static SkMutex& mutex = *(new SkMutex);
469 return mutex;
470}
471
472/** Must hold resource_cache_mutex() when calling. */
474 // resource_cache_mutex() is always held when this is called, so we don't need to be fancy in here.
475 resource_cache_mutex().assertHeld();
476 if (nullptr == gResourceCache) {
477#ifdef SK_USE_DISCARDABLE_SCALEDIMAGECACHE
479#else
481#endif
482 }
483 return gResourceCache;
484}
485
488 return get_cache()->getTotalBytesUsed();
489}
490
493 return get_cache()->getTotalByteLimit();
494}
495
496size_t SkResourceCache::SetTotalByteLimit(size_t newLimit) {
498 return get_cache()->setTotalByteLimit(newLimit);
499}
500
503 return get_cache()->discardableFactory();
504}
505
508 return get_cache()->newCachedData(bytes);
509}
510
513 get_cache()->dump();
514}
515
519}
520
524}
525
529}
530
533 return get_cache()->purgeAll();
534}
535
538 return get_cache()->checkMessages();
539}
540
541bool SkResourceCache::Find(const Key& key, FindVisitor visitor, void* context) {
543 return get_cache()->find(key, visitor, context);
544}
545
546void SkResourceCache::Add(Rec* rec, void* payload) {
548 get_cache()->add(rec, payload);
549}
550
551void SkResourceCache::VisitAll(Visitor visitor, void* context) {
553 get_cache()->visitAll(visitor, context);
554}
555
556void SkResourceCache::PostPurgeSharedID(uint64_t sharedID) {
557 if (sharedID) {
559 }
560}
561
562///////////////////////////////////////////////////////////////////////////////
563
566}
567
570}
571
573 return SkResourceCache::SetTotalByteLimit(newLimit);
574}
575
578}
579
582}
583
585 SkImageFilter_Base::PurgeCache();
587}
588
589/////////////
590
591static void dump_visitor(const SkResourceCache::Rec& rec, void*) {
592 SkDebugf("RC: %12s bytes %9zu discardable %p\n",
594}
595
597 VisitAll(dump_visitor, nullptr);
598}
599
600static void sk_trace_dump_visitor(const SkResourceCache::Rec& rec, void* context) {
601 SkTraceMemoryDump* dump = static_cast<SkTraceMemoryDump*>(context);
602 SkString dumpName = SkStringPrintf("skia/sk_resource_cache/%s_%p", rec.getCategory(), &rec);
604 if (discardable) {
605 dump->setDiscardableMemoryBacking(dumpName.c_str(), *discardable);
606
607 // The discardable memory size will be calculated by dumper, but we also dump what we think
608 // the size of object in memory is irrespective of whether object is live or dead.
609 dump->dumpNumericValue(dumpName.c_str(), "discardable_size", "bytes", rec.bytesUsed());
610 } else {
611 dump->dumpNumericValue(dumpName.c_str(), "size", "bytes", rec.bytesUsed());
612 dump->setMemoryBacking(dumpName.c_str(), "malloc", nullptr);
613 }
614}
615
617 // Since resource could be backed by malloc or discardable, the cache always dumps detailed
618 // stats to be accurate.
620}
int count
Definition: FontMgrTest.cpp:50
TArray< uint32_t > Key
static float next(float f)
static float prev(float f)
static constexpr T SkAlign4(T x)
Definition: SkAlign.h:16
#define SkASSERT(cond)
Definition: SkAssert.h:116
void SK_SPI SkDebugf(const char format[],...) SK_PRINTF_LIKE(1
static void * sk_malloc_throw(size_t size)
Definition: SkMalloc.h:67
static constexpr int32_t SK_MaxS32
Definition: SkMath.h:21
#define DECLARE_SKMESSAGEBUS_MESSAGE(Message, IDType, AllowCopyableMessage)
Definition: SkMessageBus.h:74
static void make_size_str(size_t size, SkString *str)
static SkMutex & resource_cache_mutex()
static void dump_visitor(const SkResourceCache::Rec &rec, void *)
static SkResourceCache * get_cache()
static bool SkShouldPostMessageToBus(const SkResourceCache::PurgeSharedIDMessage &, uint32_t)
static void sk_trace_dump_visitor(const SkResourceCache::Rec &rec, void *context)
static SkResourceCache * gResourceCache
#define SK_DISCARDABLEMEMORY_SCALEDIMAGECACHE_COUNT_LIMIT
static bool gDumpCacheTransactions
#define SK_DEFAULT_IMAGE_CACHE_LIMIT
SK_API SkString SkStringPrintf(const char *format,...) SK_PRINTF_LIKE(1
Creates a new string and writes into it using a printf()-style format.
constexpr int32_t SkToS32(S x)
Definition: SkTo.h:25
static constexpr uint32_t SK_InvalidUniqueID
Definition: SkTypes.h:196
static void dump(const float m[20], SkYUVColorSpace cs, bool rgb2yuv)
Definition: SkYUVMath.cpp:629
static SkDiscardableMemory * Create(size_t bytes)
static size_t GetResourceCacheTotalByteLimit()
static size_t GetResourceCacheSingleAllocationByteLimit()
static size_t SetResourceCacheSingleAllocationByteLimit(size_t newLimit)
static size_t SetResourceCacheTotalByteLimit(size_t newLimit)
static void PurgeResourceCache()
static size_t GetResourceCacheTotalBytesUsed()
static void Post(Message m)
Definition: SkMessageBus.h:130
SkResourceCache(DiscardableFactory)
static size_t SetSingleAllocationByteLimit(size_t)
static SkCachedData * NewCachedData(size_t bytes)
static void Add(Rec *, void *payload=nullptr)
size_t getSingleAllocationByteLimit() const
size_t getTotalByteLimit() const
static size_t GetEffectiveSingleAllocationByteLimit()
DiscardableFactory discardableFactory() const
size_t getTotalBytesUsed() const
void visitAll(Visitor, void *context)
static void PurgeAll()
static void PostPurgeSharedID(uint64_t sharedID)
static size_t GetTotalBytesUsed()
void purgeSharedID(uint64_t sharedID)
static void CheckMessages()
static DiscardableFactory GetDiscardableFactory()
bool find(const Key &, FindVisitor, void *context)
static void DumpMemoryStatistics(SkTraceMemoryDump *dump)
static size_t GetSingleAllocationByteLimit()
static void VisitAll(Visitor, void *context)
static size_t GetTotalByteLimit()
static bool Find(const Key &key, FindVisitor, void *context)
static void TestDumpMemoryStatistics()
size_t setTotalByteLimit(size_t newLimit)
SkCachedData * newCachedData(size_t bytes)
SkDiscardableMemory *(* DiscardableFactory)(size_t bytes)
size_t setSingleAllocationByteLimit(size_t maximumAllocationSize)
size_t getEffectiveSingleAllocationByteLimit() const
static void Dump()
static size_t SetTotalByteLimit(size_t newLimit)
void add(Rec *, void *payload=nullptr)
void printf(const char format[],...) SK_PRINTF_LIKE(2
Definition: SkString.cpp:534
const char * c_str() const
Definition: SkString.h:133
int size() const
Definition: SkTArray.h:421
void remove(const K &key)
Definition: SkTHash.h:157
T * find(const K &key) const
Definition: SkTHash.h:107
static float min(float r, float g, float b)
Definition: hsl.cpp:48
uint32_t Hash32(const void *data, size_t bytes, uint32_t seed)
Definition: SkChecksum.cpp:113
Visitor(Ts...) -> Visitor< Ts... >
static uint32_t Hash(uint32_t key)
Definition: hashmap_test.cc:65
it will be possible to load the file into Perfetto s trace viewer disable asset Prevents usage of any non test fonts unless they were explicitly Loaded via prefetched default font Indicates whether the embedding started a prefetch of the default font manager before creating the engine run In non interactive keep the shell running after the Dart script has completed enable serial On low power devices with low core running concurrent GC tasks on threads can cause them to contend with the UI thread which could potentially lead to jank This option turns off all concurrent GC activities domain network JSON encoded network policy per domain This overrides the DisallowInsecureConnections switch Embedder can specify whether to allow or disallow insecure connections at a domain level old gen heap size
Definition: switches.h:259
uint64_t getSharedID() const
void init(void *nameSpace, uint64_t sharedID, size_t dataSize)
virtual void postAddInstall(void *)
virtual const char * getCategory() const =0
virtual SkDiscardableMemory * diagnostic_only_getDiscardable() const
virtual size_t bytesUsed() const =0
uint32_t getHash() const
virtual const Key & getKey() const =0
virtual bool canBePurged()