OLD | NEW |
| (Empty) |
1 | |
2 /* | |
3 * Copyright 2014 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 #include "GrResourceCache2.h" | |
11 #include "GrGpuResource.h" | |
12 | |
13 #include "SkChecksum.h" | |
14 #include "SkGr.h" | |
15 #include "SkMessageBus.h" | |
16 | |
17 DECLARE_SKMESSAGEBUS_MESSAGE(GrContentKeyInvalidatedMessage); | |
18 | |
19 ////////////////////////////////////////////////////////////////////////////// | |
20 | |
21 GrScratchKey::ResourceType GrScratchKey::GenerateResourceType() { | |
22 static int32_t gType = INHERITED::kInvalidDomain + 1; | |
23 | |
24 int32_t type = sk_atomic_inc(&gType); | |
25 if (type > SK_MaxU16) { | |
26 SkFAIL("Too many Resource Types"); | |
27 } | |
28 | |
29 return static_cast<ResourceType>(type); | |
30 } | |
31 | |
32 GrContentKey::Domain GrContentKey::GenerateDomain() { | |
33 static int32_t gDomain = INHERITED::kInvalidDomain + 1; | |
34 | |
35 int32_t domain = sk_atomic_inc(&gDomain); | |
36 if (domain > SK_MaxU16) { | |
37 SkFAIL("Too many Content Key Domains"); | |
38 } | |
39 | |
40 return static_cast<Domain>(domain); | |
41 } | |
42 uint32_t GrResourceKeyHash(const uint32_t* data, size_t size) { | |
43 return SkChecksum::Compute(data, size); | |
44 } | |
45 | |
46 ////////////////////////////////////////////////////////////////////////////// | |
47 | |
48 class GrResourceCache2::AutoValidate : ::SkNoncopyable { | |
49 public: | |
50 AutoValidate(GrResourceCache2* cache) : fCache(cache) { cache->validate(); } | |
51 ~AutoValidate() { fCache->validate(); } | |
52 private: | |
53 GrResourceCache2* fCache; | |
54 }; | |
55 | |
56 ////////////////////////////////////////////////////////////////////////////// | |
57 | |
58 static const int kDefaultMaxCount = 2 * (1 << 10); | |
59 static const size_t kDefaultMaxSize = 96 * (1 << 20); | |
60 | |
61 GrResourceCache2::GrResourceCache2() | |
62 : fMaxCount(kDefaultMaxCount) | |
63 , fMaxBytes(kDefaultMaxSize) | |
64 #if GR_CACHE_STATS | |
65 , fHighWaterCount(0) | |
66 , fHighWaterBytes(0) | |
67 , fBudgetedHighWaterCount(0) | |
68 , fBudgetedHighWaterBytes(0) | |
69 #endif | |
70 , fCount(0) | |
71 , fBytes(0) | |
72 , fBudgetedCount(0) | |
73 , fBudgetedBytes(0) | |
74 , fPurging(false) | |
75 , fNewlyPurgeableResourceWhilePurging(false) | |
76 , fOverBudgetCB(NULL) | |
77 , fOverBudgetData(NULL) { | |
78 } | |
79 | |
80 GrResourceCache2::~GrResourceCache2() { | |
81 this->releaseAll(); | |
82 } | |
83 | |
84 void GrResourceCache2::setLimits(int count, size_t bytes) { | |
85 fMaxCount = count; | |
86 fMaxBytes = bytes; | |
87 this->purgeAsNeeded(); | |
88 } | |
89 | |
90 void GrResourceCache2::insertResource(GrGpuResource* resource) { | |
91 SkASSERT(resource); | |
92 SkASSERT(!resource->wasDestroyed()); | |
93 SkASSERT(!this->isInCache(resource)); | |
94 SkASSERT(!fPurging); | |
95 fResources.addToHead(resource); | |
96 | |
97 size_t size = resource->gpuMemorySize(); | |
98 ++fCount; | |
99 fBytes += size; | |
100 #if GR_CACHE_STATS | |
101 fHighWaterCount = SkTMax(fCount, fHighWaterCount); | |
102 fHighWaterBytes = SkTMax(fBytes, fHighWaterBytes); | |
103 #endif | |
104 if (resource->cacheAccess().isBudgeted()) { | |
105 ++fBudgetedCount; | |
106 fBudgetedBytes += size; | |
107 #if GR_CACHE_STATS | |
108 fBudgetedHighWaterCount = SkTMax(fBudgetedCount, fBudgetedHighWaterCount
); | |
109 fBudgetedHighWaterBytes = SkTMax(fBudgetedBytes, fBudgetedHighWaterBytes
); | |
110 #endif | |
111 } | |
112 if (resource->cacheAccess().getScratchKey().isValid()) { | |
113 SkASSERT(!resource->cacheAccess().isWrapped()); | |
114 fScratchMap.insert(resource->cacheAccess().getScratchKey(), resource); | |
115 } | |
116 | |
117 this->purgeAsNeeded(); | |
118 } | |
119 | |
120 void GrResourceCache2::removeResource(GrGpuResource* resource) { | |
121 SkASSERT(this->isInCache(resource)); | |
122 | |
123 size_t size = resource->gpuMemorySize(); | |
124 --fCount; | |
125 fBytes -= size; | |
126 if (resource->cacheAccess().isBudgeted()) { | |
127 --fBudgetedCount; | |
128 fBudgetedBytes -= size; | |
129 } | |
130 | |
131 fResources.remove(resource); | |
132 if (resource->cacheAccess().getScratchKey().isValid()) { | |
133 fScratchMap.remove(resource->cacheAccess().getScratchKey(), resource); | |
134 } | |
135 if (resource->getContentKey().isValid()) { | |
136 fContentHash.remove(resource->getContentKey()); | |
137 } | |
138 this->validate(); | |
139 } | |
140 | |
141 void GrResourceCache2::abandonAll() { | |
142 AutoValidate av(this); | |
143 | |
144 SkASSERT(!fPurging); | |
145 while (GrGpuResource* head = fResources.head()) { | |
146 SkASSERT(!head->wasDestroyed()); | |
147 head->cacheAccess().abandon(); | |
148 // abandon should have already removed this from the list. | |
149 SkASSERT(head != fResources.head()); | |
150 } | |
151 SkASSERT(!fScratchMap.count()); | |
152 SkASSERT(!fContentHash.count()); | |
153 SkASSERT(!fCount); | |
154 SkASSERT(!fBytes); | |
155 SkASSERT(!fBudgetedCount); | |
156 SkASSERT(!fBudgetedBytes); | |
157 } | |
158 | |
159 void GrResourceCache2::releaseAll() { | |
160 AutoValidate av(this); | |
161 | |
162 SkASSERT(!fPurging); | |
163 while (GrGpuResource* head = fResources.head()) { | |
164 SkASSERT(!head->wasDestroyed()); | |
165 head->cacheAccess().release(); | |
166 // release should have already removed this from the list. | |
167 SkASSERT(head != fResources.head()); | |
168 } | |
169 SkASSERT(!fScratchMap.count()); | |
170 SkASSERT(!fCount); | |
171 SkASSERT(!fBytes); | |
172 SkASSERT(!fBudgetedCount); | |
173 SkASSERT(!fBudgetedBytes); | |
174 } | |
175 | |
176 class GrResourceCache2::AvailableForScratchUse { | |
177 public: | |
178 AvailableForScratchUse(bool rejectPendingIO) : fRejectPendingIO(rejectPendin
gIO) { } | |
179 | |
180 bool operator()(const GrGpuResource* resource) const { | |
181 if (resource->internalHasRef() || !resource->cacheAccess().isScratch())
{ | |
182 return false; | |
183 } | |
184 return !fRejectPendingIO || !resource->internalHasPendingIO(); | |
185 } | |
186 | |
187 private: | |
188 bool fRejectPendingIO; | |
189 }; | |
190 | |
191 GrGpuResource* GrResourceCache2::findAndRefScratchResource(const GrScratchKey& s
cratchKey, | |
192 uint32_t flags) { | |
193 SkASSERT(!fPurging); | |
194 SkASSERT(scratchKey.isValid()); | |
195 | |
196 GrGpuResource* resource; | |
197 if (flags & (kPreferNoPendingIO_ScratchFlag | kRequireNoPendingIO_ScratchFla
g)) { | |
198 resource = fScratchMap.find(scratchKey, AvailableForScratchUse(true)); | |
199 if (resource) { | |
200 resource->ref(); | |
201 this->makeResourceMRU(resource); | |
202 this->validate(); | |
203 return resource; | |
204 } else if (flags & kRequireNoPendingIO_ScratchFlag) { | |
205 return NULL; | |
206 } | |
207 // TODO: fail here when kPrefer is specified, we didn't find a resource
without pending io, | |
208 // but there is still space in our budget for the resource. | |
209 } | |
210 resource = fScratchMap.find(scratchKey, AvailableForScratchUse(false)); | |
211 if (resource) { | |
212 resource->ref(); | |
213 this->makeResourceMRU(resource); | |
214 this->validate(); | |
215 } | |
216 return resource; | |
217 } | |
218 | |
219 void GrResourceCache2::willRemoveScratchKey(const GrGpuResource* resource) { | |
220 SkASSERT(resource->cacheAccess().getScratchKey().isValid()); | |
221 fScratchMap.remove(resource->cacheAccess().getScratchKey(), resource); | |
222 } | |
223 | |
224 void GrResourceCache2::willRemoveContentKey(const GrGpuResource* resource) { | |
225 // Someone has a ref to this resource in order to invalidate it. When the re
f count reaches | |
226 // zero we will get a notifyPurgable() and figure out what to do with it. | |
227 SkASSERT(resource->getContentKey().isValid()); | |
228 fContentHash.remove(resource->getContentKey()); | |
229 } | |
230 | |
231 bool GrResourceCache2::didSetContentKey(GrGpuResource* resource) { | |
232 SkASSERT(!fPurging); | |
233 SkASSERT(resource); | |
234 SkASSERT(this->isInCache(resource)); | |
235 SkASSERT(resource->getContentKey().isValid()); | |
236 | |
237 GrGpuResource* res = fContentHash.find(resource->getContentKey()); | |
238 if (NULL != res) { | |
239 return false; | |
240 } | |
241 | |
242 fContentHash.add(resource); | |
243 this->validate(); | |
244 return true; | |
245 } | |
246 | |
247 void GrResourceCache2::makeResourceMRU(GrGpuResource* resource) { | |
248 SkASSERT(!fPurging); | |
249 SkASSERT(resource); | |
250 SkASSERT(this->isInCache(resource)); | |
251 fResources.remove(resource); | |
252 fResources.addToHead(resource); | |
253 } | |
254 | |
255 void GrResourceCache2::notifyPurgeable(GrGpuResource* resource) { | |
256 SkASSERT(resource); | |
257 SkASSERT(this->isInCache(resource)); | |
258 SkASSERT(resource->isPurgeable()); | |
259 | |
260 // We can't purge if in the middle of purging because purge is iterating. In
stead record | |
261 // that additional resources became purgeable. | |
262 if (fPurging) { | |
263 fNewlyPurgeableResourceWhilePurging = true; | |
264 return; | |
265 } | |
266 | |
267 bool release = false; | |
268 | |
269 if (resource->cacheAccess().isWrapped()) { | |
270 release = true; | |
271 } else if (!resource->cacheAccess().isBudgeted()) { | |
272 // Check whether this resource could still be used as a scratch resource
. | |
273 if (resource->cacheAccess().getScratchKey().isValid()) { | |
274 // We won't purge an existing resource to make room for this one. | |
275 bool underBudget = fBudgetedCount < fMaxCount && | |
276 fBudgetedBytes + resource->gpuMemorySize() <= fMa
xBytes; | |
277 if (underBudget) { | |
278 resource->cacheAccess().makeBudgeted(); | |
279 } else { | |
280 release = true; | |
281 } | |
282 } else { | |
283 release = true; | |
284 } | |
285 } else { | |
286 // Purge the resource if we're over budget | |
287 bool overBudget = fBudgetedCount > fMaxCount || fBudgetedBytes > fMaxByt
es; | |
288 | |
289 // Also purge if the resource has neither a valid scratch key nor a cont
ent key. | |
290 bool noKey = !resource->cacheAccess().getScratchKey().isValid() && | |
291 !resource->getContentKey().isValid(); | |
292 if (overBudget || noKey) { | |
293 release = true; | |
294 } | |
295 } | |
296 | |
297 if (release) { | |
298 SkDEBUGCODE(int beforeCount = fCount;) | |
299 resource->cacheAccess().release(); | |
300 // We should at least free this resource, perhaps dependent resources as
well. | |
301 SkASSERT(fCount < beforeCount); | |
302 } | |
303 this->validate(); | |
304 } | |
305 | |
306 void GrResourceCache2::didChangeGpuMemorySize(const GrGpuResource* resource, siz
e_t oldSize) { | |
307 // SkASSERT(!fPurging); GrPathRange increases size during flush. :( | |
308 SkASSERT(resource); | |
309 SkASSERT(this->isInCache(resource)); | |
310 | |
311 ptrdiff_t delta = resource->gpuMemorySize() - oldSize; | |
312 | |
313 fBytes += delta; | |
314 #if GR_CACHE_STATS | |
315 fHighWaterBytes = SkTMax(fBytes, fHighWaterBytes); | |
316 #endif | |
317 if (resource->cacheAccess().isBudgeted()) { | |
318 fBudgetedBytes += delta; | |
319 #if GR_CACHE_STATS | |
320 fBudgetedHighWaterBytes = SkTMax(fBudgetedBytes, fBudgetedHighWaterBytes
); | |
321 #endif | |
322 } | |
323 | |
324 this->purgeAsNeeded(); | |
325 this->validate(); | |
326 } | |
327 | |
328 void GrResourceCache2::didChangeBudgetStatus(GrGpuResource* resource) { | |
329 SkASSERT(!fPurging); | |
330 SkASSERT(resource); | |
331 SkASSERT(this->isInCache(resource)); | |
332 | |
333 size_t size = resource->gpuMemorySize(); | |
334 | |
335 if (resource->cacheAccess().isBudgeted()) { | |
336 ++fBudgetedCount; | |
337 fBudgetedBytes += size; | |
338 #if GR_CACHE_STATS | |
339 fBudgetedHighWaterBytes = SkTMax(fBudgetedBytes, fBudgetedHighWaterBytes
); | |
340 fBudgetedHighWaterCount = SkTMax(fBudgetedCount, fBudgetedHighWaterCount
); | |
341 #endif | |
342 this->purgeAsNeeded(); | |
343 } else { | |
344 --fBudgetedCount; | |
345 fBudgetedBytes -= size; | |
346 } | |
347 | |
348 this->validate(); | |
349 } | |
350 | |
351 void GrResourceCache2::internalPurgeAsNeeded() { | |
352 SkASSERT(!fPurging); | |
353 SkASSERT(!fNewlyPurgeableResourceWhilePurging); | |
354 SkASSERT(fBudgetedCount > fMaxCount || fBudgetedBytes > fMaxBytes); | |
355 | |
356 fPurging = true; | |
357 | |
358 bool overBudget = true; | |
359 do { | |
360 fNewlyPurgeableResourceWhilePurging = false; | |
361 ResourceList::Iter resourceIter; | |
362 GrGpuResource* resource = resourceIter.init(fResources, | |
363 ResourceList::Iter::kTail_It
erStart); | |
364 | |
365 while (resource) { | |
366 GrGpuResource* prev = resourceIter.prev(); | |
367 if (resource->isPurgeable()) { | |
368 resource->cacheAccess().release(); | |
369 } | |
370 resource = prev; | |
371 if (fBudgetedCount <= fMaxCount && fBudgetedBytes <= fMaxBytes) { | |
372 overBudget = false; | |
373 resource = NULL; | |
374 } | |
375 } | |
376 | |
377 if (!fNewlyPurgeableResourceWhilePurging && overBudget && fOverBudgetCB)
{ | |
378 // Despite the purge we're still over budget. Call our over budget c
allback. | |
379 (*fOverBudgetCB)(fOverBudgetData); | |
380 } | |
381 } while (overBudget && fNewlyPurgeableResourceWhilePurging); | |
382 | |
383 fNewlyPurgeableResourceWhilePurging = false; | |
384 fPurging = false; | |
385 this->validate(); | |
386 } | |
387 | |
388 void GrResourceCache2::purgeAllUnlocked() { | |
389 SkASSERT(!fPurging); | |
390 SkASSERT(!fNewlyPurgeableResourceWhilePurging); | |
391 | |
392 fPurging = true; | |
393 | |
394 do { | |
395 fNewlyPurgeableResourceWhilePurging = false; | |
396 ResourceList::Iter resourceIter; | |
397 GrGpuResource* resource = | |
398 resourceIter.init(fResources, ResourceList::Iter::kTail_IterStart); | |
399 | |
400 while (resource) { | |
401 GrGpuResource* prev = resourceIter.prev(); | |
402 if (resource->isPurgeable()) { | |
403 resource->cacheAccess().release(); | |
404 } | |
405 resource = prev; | |
406 } | |
407 | |
408 if (!fNewlyPurgeableResourceWhilePurging && fCount && fOverBudgetCB) { | |
409 (*fOverBudgetCB)(fOverBudgetData); | |
410 } | |
411 } while (fNewlyPurgeableResourceWhilePurging); | |
412 fPurging = false; | |
413 this->validate(); | |
414 } | |
415 | |
416 void GrResourceCache2::processInvalidContentKeys( | |
417 const SkTArray<GrContentKeyInvalidatedMessage>& msgs) { | |
418 for (int i = 0; i < msgs.count(); ++i) { | |
419 GrGpuResource* resource = this->findAndRefContentResource(msgs[i].key())
; | |
420 if (resource) { | |
421 resource->cacheAccess().removeContentKey(); | |
422 resource->unref(); // will call notifyPurgeable, if it is indeed now
purgeable. | |
423 } | |
424 } | |
425 } | |
426 | |
427 #ifdef SK_DEBUG | |
428 void GrResourceCache2::validate() const { | |
429 // Reduce the frequency of validations for large resource counts. | |
430 static SkRandom gRandom; | |
431 int mask = (SkNextPow2(fCount + 1) >> 5) - 1; | |
432 if (~mask && (gRandom.nextU() & mask)) { | |
433 return; | |
434 } | |
435 | |
436 size_t bytes = 0; | |
437 int count = 0; | |
438 int budgetedCount = 0; | |
439 size_t budgetedBytes = 0; | |
440 int locked = 0; | |
441 int scratch = 0; | |
442 int couldBeScratch = 0; | |
443 int content = 0; | |
444 | |
445 ResourceList::Iter iter; | |
446 GrGpuResource* resource = iter.init(fResources, ResourceList::Iter::kHead_It
erStart); | |
447 for ( ; resource; resource = iter.next()) { | |
448 bytes += resource->gpuMemorySize(); | |
449 ++count; | |
450 | |
451 if (!resource->isPurgeable()) { | |
452 ++locked; | |
453 } | |
454 | |
455 if (resource->cacheAccess().isScratch()) { | |
456 SkASSERT(!resource->getContentKey().isValid()); | |
457 ++scratch; | |
458 SkASSERT(fScratchMap.countForKey(resource->cacheAccess().getScratchK
ey())); | |
459 SkASSERT(!resource->cacheAccess().isWrapped()); | |
460 } else if (resource->cacheAccess().getScratchKey().isValid()) { | |
461 SkASSERT(!resource->cacheAccess().isBudgeted() || | |
462 resource->getContentKey().isValid()); | |
463 ++couldBeScratch; | |
464 SkASSERT(fScratchMap.countForKey(resource->cacheAccess().getScratchK
ey())); | |
465 SkASSERT(!resource->cacheAccess().isWrapped()); | |
466 } | |
467 const GrContentKey& contentKey = resource->getContentKey(); | |
468 if (contentKey.isValid()) { | |
469 ++content; | |
470 SkASSERT(fContentHash.find(contentKey) == resource); | |
471 SkASSERT(!resource->cacheAccess().isWrapped()); | |
472 SkASSERT(resource->cacheAccess().isBudgeted()); | |
473 } | |
474 | |
475 if (resource->cacheAccess().isBudgeted()) { | |
476 ++budgetedCount; | |
477 budgetedBytes += resource->gpuMemorySize(); | |
478 } | |
479 } | |
480 | |
481 SkASSERT(fBudgetedCount <= fCount); | |
482 SkASSERT(fBudgetedBytes <= fBudgetedBytes); | |
483 SkASSERT(bytes == fBytes); | |
484 SkASSERT(count == fCount); | |
485 SkASSERT(budgetedBytes == fBudgetedBytes); | |
486 SkASSERT(budgetedCount == fBudgetedCount); | |
487 #if GR_CACHE_STATS | |
488 SkASSERT(fBudgetedHighWaterCount <= fHighWaterCount); | |
489 SkASSERT(fBudgetedHighWaterBytes <= fHighWaterBytes); | |
490 SkASSERT(bytes <= fHighWaterBytes); | |
491 SkASSERT(count <= fHighWaterCount); | |
492 SkASSERT(budgetedBytes <= fBudgetedHighWaterBytes); | |
493 SkASSERT(budgetedCount <= fBudgetedHighWaterCount); | |
494 #endif | |
495 SkASSERT(content == fContentHash.count()); | |
496 SkASSERT(scratch + couldBeScratch == fScratchMap.count()); | |
497 | |
498 // This assertion is not currently valid because we can be in recursive noti
fyIsPurgeable() | |
499 // calls. This will be fixed when subresource registration is explicit. | |
500 // bool overBudget = budgetedBytes > fMaxBytes || budgetedCount > fMaxCount; | |
501 // SkASSERT(!overBudget || locked == count || fPurging); | |
502 } | |
503 #endif | |
OLD | NEW |