|
1 |
|
2 /* |
|
3 * Copyright 2010 Google Inc. |
|
4 * |
|
5 * Use of this source code is governed by a BSD-style license that can be |
|
6 * found in the LICENSE file. |
|
7 */ |
|
8 |
|
9 |
|
10 |
|
11 #include "GrResourceCache.h" |
|
12 #include "GrResource.h" |
|
13 |
|
14 DECLARE_SKMESSAGEBUS_MESSAGE(GrResourceInvalidatedMessage); |
|
15 |
|
16 GrResourceKey::ResourceType GrResourceKey::GenerateResourceType() { |
|
17 static int32_t gNextType = 0; |
|
18 |
|
19 int32_t type = sk_atomic_inc(&gNextType); |
|
20 if (type >= (1 << 8 * sizeof(ResourceType))) { |
|
21 GrCrash("Too many Resource Types"); |
|
22 } |
|
23 |
|
24 return static_cast<ResourceType>(type); |
|
25 } |
|
26 |
|
27 /////////////////////////////////////////////////////////////////////////////// |
|
28 |
|
29 GrResourceEntry::GrResourceEntry(const GrResourceKey& key, GrResource* resource) |
|
30 : fKey(key), fResource(resource) { |
|
31 // we assume ownership of the resource, and will unref it when we die |
|
32 SkASSERT(resource); |
|
33 resource->ref(); |
|
34 } |
|
35 |
|
36 GrResourceEntry::~GrResourceEntry() { |
|
37 fResource->setCacheEntry(NULL); |
|
38 fResource->unref(); |
|
39 } |
|
40 |
|
41 #ifdef SK_DEBUG |
|
42 void GrResourceEntry::validate() const { |
|
43 SkASSERT(fResource); |
|
44 SkASSERT(fResource->getCacheEntry() == this); |
|
45 fResource->validate(); |
|
46 } |
|
47 #endif |
|
48 |
|
49 /////////////////////////////////////////////////////////////////////////////// |
|
50 |
|
51 GrResourceCache::GrResourceCache(int maxCount, size_t maxBytes) : |
|
52 fMaxCount(maxCount), |
|
53 fMaxBytes(maxBytes) { |
|
54 #if GR_CACHE_STATS |
|
55 fHighWaterEntryCount = 0; |
|
56 fHighWaterEntryBytes = 0; |
|
57 fHighWaterClientDetachedCount = 0; |
|
58 fHighWaterClientDetachedBytes = 0; |
|
59 #endif |
|
60 |
|
61 fEntryCount = 0; |
|
62 fEntryBytes = 0; |
|
63 fClientDetachedCount = 0; |
|
64 fClientDetachedBytes = 0; |
|
65 |
|
66 fPurging = false; |
|
67 |
|
68 fOverbudgetCB = NULL; |
|
69 fOverbudgetData = NULL; |
|
70 } |
|
71 |
|
72 GrResourceCache::~GrResourceCache() { |
|
73 GrAutoResourceCacheValidate atcv(this); |
|
74 |
|
75 EntryList::Iter iter; |
|
76 |
|
77 // Unlike the removeAll, here we really remove everything, including locked resources. |
|
78 while (GrResourceEntry* entry = fList.head()) { |
|
79 GrAutoResourceCacheValidate atcv(this); |
|
80 |
|
81 // remove from our cache |
|
82 fCache.remove(entry->fKey, entry); |
|
83 |
|
84 // remove from our llist |
|
85 this->internalDetach(entry); |
|
86 |
|
87 delete entry; |
|
88 } |
|
89 } |
|
90 |
|
91 void GrResourceCache::getLimits(int* maxResources, size_t* maxResourceBytes) const{ |
|
92 if (NULL != maxResources) { |
|
93 *maxResources = fMaxCount; |
|
94 } |
|
95 if (NULL != maxResourceBytes) { |
|
96 *maxResourceBytes = fMaxBytes; |
|
97 } |
|
98 } |
|
99 |
|
100 void GrResourceCache::setLimits(int maxResources, size_t maxResourceBytes) { |
|
101 bool smaller = (maxResources < fMaxCount) || (maxResourceBytes < fMaxBytes); |
|
102 |
|
103 fMaxCount = maxResources; |
|
104 fMaxBytes = maxResourceBytes; |
|
105 |
|
106 if (smaller) { |
|
107 this->purgeAsNeeded(); |
|
108 } |
|
109 } |
|
110 |
|
111 void GrResourceCache::internalDetach(GrResourceEntry* entry, |
|
112 BudgetBehaviors behavior) { |
|
113 fList.remove(entry); |
|
114 |
|
115 // update our stats |
|
116 if (kIgnore_BudgetBehavior == behavior) { |
|
117 fClientDetachedCount += 1; |
|
118 fClientDetachedBytes += entry->resource()->sizeInBytes(); |
|
119 |
|
120 #if GR_CACHE_STATS |
|
121 if (fHighWaterClientDetachedCount < fClientDetachedCount) { |
|
122 fHighWaterClientDetachedCount = fClientDetachedCount; |
|
123 } |
|
124 if (fHighWaterClientDetachedBytes < fClientDetachedBytes) { |
|
125 fHighWaterClientDetachedBytes = fClientDetachedBytes; |
|
126 } |
|
127 #endif |
|
128 |
|
129 } else { |
|
130 SkASSERT(kAccountFor_BudgetBehavior == behavior); |
|
131 |
|
132 fEntryCount -= 1; |
|
133 fEntryBytes -= entry->resource()->sizeInBytes(); |
|
134 } |
|
135 } |
|
136 |
|
137 void GrResourceCache::attachToHead(GrResourceEntry* entry, |
|
138 BudgetBehaviors behavior) { |
|
139 fList.addToHead(entry); |
|
140 |
|
141 // update our stats |
|
142 if (kIgnore_BudgetBehavior == behavior) { |
|
143 fClientDetachedCount -= 1; |
|
144 fClientDetachedBytes -= entry->resource()->sizeInBytes(); |
|
145 } else { |
|
146 SkASSERT(kAccountFor_BudgetBehavior == behavior); |
|
147 |
|
148 fEntryCount += 1; |
|
149 fEntryBytes += entry->resource()->sizeInBytes(); |
|
150 |
|
151 #if GR_CACHE_STATS |
|
152 if (fHighWaterEntryCount < fEntryCount) { |
|
153 fHighWaterEntryCount = fEntryCount; |
|
154 } |
|
155 if (fHighWaterEntryBytes < fEntryBytes) { |
|
156 fHighWaterEntryBytes = fEntryBytes; |
|
157 } |
|
158 #endif |
|
159 } |
|
160 } |
|
161 |
|
162 // This functor just searches for an entry with only a single ref (from |
|
163 // the texture cache itself). Presumably in this situation no one else |
|
164 // is relying on the texture. |
|
165 class GrTFindUnreffedFunctor { |
|
166 public: |
|
167 bool operator()(const GrResourceEntry* entry) const { |
|
168 return entry->resource()->unique(); |
|
169 } |
|
170 }; |
|
171 |
|
172 GrResource* GrResourceCache::find(const GrResourceKey& key, uint32_t ownershipFlags) { |
|
173 GrAutoResourceCacheValidate atcv(this); |
|
174 |
|
175 GrResourceEntry* entry = NULL; |
|
176 |
|
177 if (ownershipFlags & kNoOtherOwners_OwnershipFlag) { |
|
178 GrTFindUnreffedFunctor functor; |
|
179 |
|
180 entry = fCache.find<GrTFindUnreffedFunctor>(key, functor); |
|
181 } else { |
|
182 entry = fCache.find(key); |
|
183 } |
|
184 |
|
185 if (NULL == entry) { |
|
186 return NULL; |
|
187 } |
|
188 |
|
189 if (ownershipFlags & kHide_OwnershipFlag) { |
|
190 this->makeExclusive(entry); |
|
191 } else { |
|
192 // Make this resource MRU |
|
193 this->internalDetach(entry); |
|
194 this->attachToHead(entry); |
|
195 } |
|
196 |
|
197 return entry->fResource; |
|
198 } |
|
199 |
|
200 void GrResourceCache::addResource(const GrResourceKey& key, |
|
201 GrResource* resource, |
|
202 uint32_t ownershipFlags) { |
|
203 SkASSERT(NULL == resource->getCacheEntry()); |
|
204 // we don't expect to create new resources during a purge. In theory |
|
205 // this could cause purgeAsNeeded() into an infinite loop (e.g. |
|
206 // each resource destroyed creates and locks 2 resources and |
|
207 // unlocks 1 thereby causing a new purge). |
|
208 SkASSERT(!fPurging); |
|
209 GrAutoResourceCacheValidate atcv(this); |
|
210 |
|
211 GrResourceEntry* entry = SkNEW_ARGS(GrResourceEntry, (key, resource)); |
|
212 resource->setCacheEntry(entry); |
|
213 |
|
214 this->attachToHead(entry); |
|
215 fCache.insert(key, entry); |
|
216 |
|
217 if (ownershipFlags & kHide_OwnershipFlag) { |
|
218 this->makeExclusive(entry); |
|
219 } |
|
220 |
|
221 } |
|
222 |
|
223 void GrResourceCache::makeExclusive(GrResourceEntry* entry) { |
|
224 GrAutoResourceCacheValidate atcv(this); |
|
225 |
|
226 // When scratch textures are detached (to hide them from future finds) they |
|
227 // still count against the resource budget |
|
228 this->internalDetach(entry, kIgnore_BudgetBehavior); |
|
229 fCache.remove(entry->key(), entry); |
|
230 |
|
231 #ifdef SK_DEBUG |
|
232 fExclusiveList.addToHead(entry); |
|
233 #endif |
|
234 } |
|
235 |
|
236 void GrResourceCache::removeInvalidResource(GrResourceEntry* entry) { |
|
237 // If the resource went invalid while it was detached then purge it |
|
238 // This can happen when a 3D context was lost, |
|
239 // the client called GrContext::contextDestroyed() to notify Gr, |
|
240 // and then later an SkGpuDevice's destructor releases its backing |
|
241 // texture (which was invalidated at contextDestroyed time). |
|
242 fClientDetachedCount -= 1; |
|
243 fEntryCount -= 1; |
|
244 size_t size = entry->resource()->sizeInBytes(); |
|
245 fClientDetachedBytes -= size; |
|
246 fEntryBytes -= size; |
|
247 } |
|
248 |
|
249 void GrResourceCache::makeNonExclusive(GrResourceEntry* entry) { |
|
250 GrAutoResourceCacheValidate atcv(this); |
|
251 |
|
252 #ifdef SK_DEBUG |
|
253 fExclusiveList.remove(entry); |
|
254 #endif |
|
255 |
|
256 if (entry->resource()->isValid()) { |
|
257 // Since scratch textures still count against the cache budget even |
|
258 // when they have been removed from the cache, re-adding them doesn't |
|
259 // alter the budget information. |
|
260 attachToHead(entry, kIgnore_BudgetBehavior); |
|
261 fCache.insert(entry->key(), entry); |
|
262 } else { |
|
263 this->removeInvalidResource(entry); |
|
264 } |
|
265 } |
|
266 |
|
267 /** |
|
268 * Destroying a resource may potentially trigger the unlock of additional |
|
269 * resources which in turn will trigger a nested purge. We block the nested |
|
270 * purge using the fPurging variable. However, the initial purge will keep |
|
271 * looping until either all resources in the cache are unlocked or we've met |
|
272 * the budget. There is an assertion in createAndLock to check against a |
|
273 * resource's destructor inserting new resources into the cache. If these |
|
274 * new resources were unlocked before purgeAsNeeded completed it could |
|
275 * potentially make purgeAsNeeded loop infinitely. |
|
276 * |
|
277 * extraCount and extraBytes are added to the current resource totals to account |
|
278 * for incoming resources (e.g., GrContext is about to add 10MB split between |
|
279 * 10 textures). |
|
280 */ |
|
281 void GrResourceCache::purgeAsNeeded(int extraCount, size_t extraBytes) { |
|
282 if (fPurging) { |
|
283 return; |
|
284 } |
|
285 |
|
286 fPurging = true; |
|
287 |
|
288 this->purgeInvalidated(); |
|
289 |
|
290 this->internalPurge(extraCount, extraBytes); |
|
291 if (((fEntryCount+extraCount) > fMaxCount || |
|
292 (fEntryBytes+extraBytes) > fMaxBytes) && |
|
293 NULL != fOverbudgetCB) { |
|
294 // Despite the purge we're still over budget. See if Ganesh can |
|
295 // release some resources and purge again. |
|
296 if ((*fOverbudgetCB)(fOverbudgetData)) { |
|
297 this->internalPurge(extraCount, extraBytes); |
|
298 } |
|
299 } |
|
300 |
|
301 fPurging = false; |
|
302 } |
|
303 |
|
304 void GrResourceCache::purgeInvalidated() { |
|
305 SkTDArray<GrResourceInvalidatedMessage> invalidated; |
|
306 fInvalidationInbox.poll(&invalidated); |
|
307 |
|
308 for (int i = 0; i < invalidated.count(); i++) { |
|
309 // We're somewhat missing an opportunity here. We could use the |
|
310 // default find functor that gives us back resources whether we own |
|
311 // them exclusively or not, and when they're not exclusively owned mark |
|
312 // them for purging later when they do become exclusively owned. |
|
313 // |
|
314 // This is complicated and confusing. May try this in the future. For |
|
315 // now, these resources are just LRU'd as if we never got the message. |
|
316 while (GrResourceEntry* entry = fCache.find(invalidated[i].key, GrTFindUnreffedFunctor())) { |
|
317 this->deleteResource(entry); |
|
318 } |
|
319 } |
|
320 } |
|
321 |
|
322 void GrResourceCache::deleteResource(GrResourceEntry* entry) { |
|
323 SkASSERT(1 == entry->fResource->getRefCnt()); |
|
324 |
|
325 // remove from our cache |
|
326 fCache.remove(entry->key(), entry); |
|
327 |
|
328 // remove from our llist |
|
329 this->internalDetach(entry); |
|
330 delete entry; |
|
331 } |
|
332 |
|
333 void GrResourceCache::internalPurge(int extraCount, size_t extraBytes) { |
|
334 SkASSERT(fPurging); |
|
335 |
|
336 bool withinBudget = false; |
|
337 bool changed = false; |
|
338 |
|
339 // The purging process is repeated several times since one pass |
|
340 // may free up other resources |
|
341 do { |
|
342 EntryList::Iter iter; |
|
343 |
|
344 changed = false; |
|
345 |
|
346 // Note: the following code relies on the fact that the |
|
347 // doubly linked list doesn't invalidate its data/pointers |
|
348 // outside of the specific area where a deletion occurs (e.g., |
|
349 // in internalDetach) |
|
350 GrResourceEntry* entry = iter.init(fList, EntryList::Iter::kTail_IterStart); |
|
351 |
|
352 while (NULL != entry) { |
|
353 GrAutoResourceCacheValidate atcv(this); |
|
354 |
|
355 if ((fEntryCount+extraCount) <= fMaxCount && |
|
356 (fEntryBytes+extraBytes) <= fMaxBytes) { |
|
357 withinBudget = true; |
|
358 break; |
|
359 } |
|
360 |
|
361 GrResourceEntry* prev = iter.prev(); |
|
362 if (entry->fResource->unique()) { |
|
363 changed = true; |
|
364 this->deleteResource(entry); |
|
365 } |
|
366 entry = prev; |
|
367 } |
|
368 } while (!withinBudget && changed); |
|
369 } |
|
370 |
|
371 void GrResourceCache::purgeAllUnlocked() { |
|
372 GrAutoResourceCacheValidate atcv(this); |
|
373 |
|
374 // we can have one GrResource holding a lock on another |
|
375 // so we don't want to just do a simple loop kicking each |
|
376 // entry out. Instead change the budget and purge. |
|
377 |
|
378 size_t savedMaxBytes = fMaxBytes; |
|
379 int savedMaxCount = fMaxCount; |
|
380 fMaxBytes = (size_t) -1; |
|
381 fMaxCount = 0; |
|
382 this->purgeAsNeeded(); |
|
383 |
|
384 #ifdef SK_DEBUG |
|
385 SkASSERT(fExclusiveList.countEntries() == fClientDetachedCount); |
|
386 SkASSERT(countBytes(fExclusiveList) == fClientDetachedBytes); |
|
387 if (!fCache.count()) { |
|
388 // Items may have been detached from the cache (such as the backing |
|
389 // texture for an SkGpuDevice). The above purge would not have removed |
|
390 // them. |
|
391 SkASSERT(fEntryCount == fClientDetachedCount); |
|
392 SkASSERT(fEntryBytes == fClientDetachedBytes); |
|
393 SkASSERT(fList.isEmpty()); |
|
394 } |
|
395 #endif |
|
396 |
|
397 fMaxBytes = savedMaxBytes; |
|
398 fMaxCount = savedMaxCount; |
|
399 } |
|
400 |
|
401 /////////////////////////////////////////////////////////////////////////////// |
|
402 |
|
403 #ifdef SK_DEBUG |
|
404 size_t GrResourceCache::countBytes(const EntryList& list) { |
|
405 size_t bytes = 0; |
|
406 |
|
407 EntryList::Iter iter; |
|
408 |
|
409 const GrResourceEntry* entry = iter.init(const_cast<EntryList&>(list), |
|
410 EntryList::Iter::kTail_IterStart); |
|
411 |
|
412 for ( ; NULL != entry; entry = iter.prev()) { |
|
413 bytes += entry->resource()->sizeInBytes(); |
|
414 } |
|
415 return bytes; |
|
416 } |
|
417 |
|
418 static bool both_zero_or_nonzero(int count, size_t bytes) { |
|
419 return (count == 0 && bytes == 0) || (count > 0 && bytes > 0); |
|
420 } |
|
421 |
|
422 void GrResourceCache::validate() const { |
|
423 fList.validate(); |
|
424 fExclusiveList.validate(); |
|
425 SkASSERT(both_zero_or_nonzero(fEntryCount, fEntryBytes)); |
|
426 SkASSERT(both_zero_or_nonzero(fClientDetachedCount, fClientDetachedBytes)); |
|
427 SkASSERT(fClientDetachedBytes <= fEntryBytes); |
|
428 SkASSERT(fClientDetachedCount <= fEntryCount); |
|
429 SkASSERT((fEntryCount - fClientDetachedCount) == fCache.count()); |
|
430 |
|
431 EntryList::Iter iter; |
|
432 |
|
433 // check that the exclusively held entries are okay |
|
434 const GrResourceEntry* entry = iter.init(const_cast<EntryList&>(fExclusiveList), |
|
435 EntryList::Iter::kHead_IterStart); |
|
436 |
|
437 for ( ; NULL != entry; entry = iter.next()) { |
|
438 entry->validate(); |
|
439 } |
|
440 |
|
441 // check that the shareable entries are okay |
|
442 entry = iter.init(const_cast<EntryList&>(fList), EntryList::Iter::kHead_IterStart); |
|
443 |
|
444 int count = 0; |
|
445 for ( ; NULL != entry; entry = iter.next()) { |
|
446 entry->validate(); |
|
447 SkASSERT(fCache.find(entry->key())); |
|
448 count += 1; |
|
449 } |
|
450 SkASSERT(count == fEntryCount - fClientDetachedCount); |
|
451 |
|
452 size_t bytes = countBytes(fList); |
|
453 SkASSERT(bytes == fEntryBytes - fClientDetachedBytes); |
|
454 |
|
455 bytes = countBytes(fExclusiveList); |
|
456 SkASSERT(bytes == fClientDetachedBytes); |
|
457 |
|
458 SkASSERT(fList.countEntries() == fEntryCount - fClientDetachedCount); |
|
459 |
|
460 SkASSERT(fExclusiveList.countEntries() == fClientDetachedCount); |
|
461 } |
|
462 #endif // SK_DEBUG |
|
463 |
|
464 #if GR_CACHE_STATS |
|
465 |
|
466 void GrResourceCache::printStats() { |
|
467 int locked = 0; |
|
468 |
|
469 EntryList::Iter iter; |
|
470 |
|
471 GrResourceEntry* entry = iter.init(fList, EntryList::Iter::kTail_IterStart); |
|
472 |
|
473 for ( ; NULL != entry; entry = iter.prev()) { |
|
474 if (entry->fResource->getRefCnt() > 1) { |
|
475 ++locked; |
|
476 } |
|
477 } |
|
478 |
|
479 SkDebugf("Budget: %d items %d bytes\n", fMaxCount, fMaxBytes); |
|
480 SkDebugf("\t\tEntry Count: current %d (%d locked) high %d\n", |
|
481 fEntryCount, locked, fHighWaterEntryCount); |
|
482 SkDebugf("\t\tEntry Bytes: current %d high %d\n", |
|
483 fEntryBytes, fHighWaterEntryBytes); |
|
484 SkDebugf("\t\tDetached Entry Count: current %d high %d\n", |
|
485 fClientDetachedCount, fHighWaterClientDetachedCount); |
|
486 SkDebugf("\t\tDetached Bytes: current %d high %d\n", |
|
487 fClientDetachedBytes, fHighWaterClientDetachedBytes); |
|
488 } |
|
489 |
|
490 #endif |
|
491 |
|
492 /////////////////////////////////////////////////////////////////////////////// |