OLD | NEW |
| (Empty) |
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 #include "GrResourceCache.h" | |
10 #include "GrGpuResource.h" | |
11 #include "GrGpuResourceCacheAccess.h" | |
12 #include "GrTexturePriv.h" | |
13 | |
14 DECLARE_SKMESSAGEBUS_MESSAGE(GrResourceInvalidatedMessage); | |
15 | |
16 /////////////////////////////////////////////////////////////////////////////// | |
17 | |
18 void GrGpuResource::didChangeGpuMemorySize() const { | |
19 fGpuMemorySize = kInvalidGpuMemorySize; | |
20 if (this->cacheAccess().isInCache()) { | |
21 fCacheEntry->didChangeResourceSize(); | |
22 } | |
23 } | |
24 | |
25 /////////////////////////////////////////////////////////////////////////////// | |
26 | |
27 GrResourceKey::ResourceType GrResourceKey::GenerateResourceType() { | |
28 static int32_t gNextType = 0; | |
29 | |
30 int32_t type = sk_atomic_inc(&gNextType); | |
31 if (type >= (1 << 8 * sizeof(ResourceType))) { | |
32 SkFAIL("Too many Resource Types"); | |
33 } | |
34 | |
35 return static_cast<ResourceType>(type); | |
36 } | |
37 | |
38 /////////////////////////////////////////////////////////////////////////////// | |
39 | |
40 GrResourceCacheEntry::GrResourceCacheEntry(GrResourceCache* resourceCache, GrGpu
Resource* resource) | |
41 : fResourceCache(resourceCache), | |
42 fResource(resource), | |
43 fCachedSize(resource->gpuMemorySize()) { | |
44 // we assume ownership of the resource, and will unref it when we die | |
45 SkASSERT(resource); | |
46 resource->ref(); | |
47 } | |
48 | |
49 GrResourceCacheEntry::~GrResourceCacheEntry() { | |
50 // We're relying on having the cache entry to remove this from GrResourceCac
he2's content hash. | |
51 // fResource->setCacheEntry(NULL); | |
52 fResource->unref(); | |
53 } | |
54 | |
55 #ifdef SK_DEBUG | |
56 void GrResourceCacheEntry::validate() const { | |
57 SkASSERT(fResourceCache); | |
58 SkASSERT(fResource); | |
59 SkASSERT(fResource->cacheAccess().getCacheEntry() == this); | |
60 SkASSERT(fResource->gpuMemorySize() == fCachedSize); | |
61 fResource->validate(); | |
62 } | |
63 #endif | |
64 | |
65 void GrResourceCacheEntry::didChangeResourceSize() { | |
66 size_t oldSize = fCachedSize; | |
67 fCachedSize = fResource->gpuMemorySize(); | |
68 if (fCachedSize > oldSize) { | |
69 fResourceCache->didIncreaseResourceSize(this, fCachedSize - oldSize); | |
70 } else if (fCachedSize < oldSize) { | |
71 fResourceCache->didDecreaseResourceSize(this, oldSize - fCachedSize); | |
72 } | |
73 } | |
74 | |
75 /////////////////////////////////////////////////////////////////////////////// | |
76 | |
77 GrResourceCache::GrResourceCache(const GrDrawTargetCaps* caps, int maxCount, siz
e_t maxBytes) | |
78 : fMaxCount(maxCount) | |
79 , fMaxBytes(maxBytes) | |
80 , fCaps(SkRef(caps)) { | |
81 #if GR_CACHE_STATS | |
82 fHighWaterEntryCount = 0; | |
83 fHighWaterEntryBytes = 0; | |
84 #endif | |
85 | |
86 fEntryCount = 0; | |
87 fEntryBytes = 0; | |
88 | |
89 fPurging = false; | |
90 | |
91 fOverbudgetCB = NULL; | |
92 fOverbudgetData = NULL; | |
93 } | |
94 | |
95 GrResourceCache::~GrResourceCache() { | |
96 GrAutoResourceCacheValidate atcv(this); | |
97 | |
98 EntryList::Iter iter; | |
99 | |
100 // Unlike the removeAll, here we really remove everything, including locked
resources. | |
101 while (GrResourceCacheEntry* entry = fList.head()) { | |
102 GrAutoResourceCacheValidate atcv(this); | |
103 | |
104 // remove from our llist | |
105 this->internalDetach(entry); | |
106 | |
107 delete entry; | |
108 } | |
109 } | |
110 | |
111 void GrResourceCache::getLimits(int* maxResources, size_t* maxResourceBytes) con
st{ | |
112 if (maxResources) { | |
113 *maxResources = fMaxCount; | |
114 } | |
115 if (maxResourceBytes) { | |
116 *maxResourceBytes = fMaxBytes; | |
117 } | |
118 } | |
119 | |
120 void GrResourceCache::setLimits(int maxResources, size_t maxResourceBytes) { | |
121 bool smaller = (maxResources < fMaxCount) || (maxResourceBytes < fMaxBytes); | |
122 | |
123 fMaxCount = maxResources; | |
124 fMaxBytes = maxResourceBytes; | |
125 | |
126 if (smaller) { | |
127 this->purgeAsNeeded(); | |
128 } | |
129 } | |
130 | |
131 void GrResourceCache::internalDetach(GrResourceCacheEntry* entry) { | |
132 fList.remove(entry); | |
133 fEntryCount -= 1; | |
134 fEntryBytes -= entry->fCachedSize; | |
135 } | |
136 | |
137 void GrResourceCache::attachToHead(GrResourceCacheEntry* entry) { | |
138 fList.addToHead(entry); | |
139 | |
140 fEntryCount += 1; | |
141 fEntryBytes += entry->fCachedSize; | |
142 | |
143 #if GR_CACHE_STATS | |
144 if (fHighWaterEntryCount < fEntryCount) { | |
145 fHighWaterEntryCount = fEntryCount; | |
146 } | |
147 if (fHighWaterEntryBytes < fEntryBytes) { | |
148 fHighWaterEntryBytes = fEntryBytes; | |
149 } | |
150 #endif | |
151 } | |
152 | |
153 | |
154 void GrResourceCache::makeResourceMRU(GrGpuResource* resource) { | |
155 GrResourceCacheEntry* entry = resource->cacheAccess().getCacheEntry(); | |
156 if (entry) { | |
157 this->internalDetach(entry); | |
158 this->attachToHead(entry); | |
159 } | |
160 } | |
161 | |
162 void GrResourceCache::notifyPurgable(const GrGpuResource* resource) { | |
163 // Remove scratch textures from the cache the moment they become purgeable i
f | |
164 // scratch texture reuse is turned off. | |
165 SkASSERT(resource->cacheAccess().getCacheEntry()); | |
166 if (resource->cacheAccess().isScratch()) { | |
167 const GrResourceKey& key = resource->cacheAccess().getScratchKey(); | |
168 if (key.getResourceType() == GrTexturePriv::ResourceType() && | |
169 !fCaps->reuseScratchTextures() && | |
170 !(static_cast<const GrSurface*>(resource)->desc().fFlags & kRenderTa
rget_GrSurfaceFlag)) { | |
171 this->deleteResource(resource->cacheAccess().getCacheEntry()); | |
172 } | |
173 } | |
174 } | |
175 | |
176 bool GrResourceCache::addResource(const GrResourceKey& key, GrGpuResource* resou
rce) { | |
177 if (NULL != resource->cacheAccess().getCacheEntry()) { | |
178 return false; | |
179 } | |
180 | |
181 if (key.isScratch()) { | |
182 SkASSERT(resource->cacheAccess().isScratch()); | |
183 SkASSERT(key == resource->cacheAccess().getScratchKey()); | |
184 } else { | |
185 if (!resource->cacheAccess().setContentKey(key)) { | |
186 return false; | |
187 } | |
188 } | |
189 | |
190 // we don't expect to create new resources during a purge. In theory | |
191 // this could cause purgeAsNeeded() into an infinite loop (e.g. | |
192 // each resource destroyed creates and locks 2 resources and | |
193 // unlocks 1 thereby causing a new purge). | |
194 SkASSERT(!fPurging); | |
195 GrAutoResourceCacheValidate atcv(this); | |
196 | |
197 GrResourceCacheEntry* entry = SkNEW_ARGS(GrResourceCacheEntry, (this, resour
ce)); | |
198 resource->cacheAccess().setCacheEntry(entry); | |
199 | |
200 this->attachToHead(entry); | |
201 this->purgeAsNeeded(); | |
202 return true; | |
203 } | |
204 | |
205 void GrResourceCache::didIncreaseResourceSize(const GrResourceCacheEntry* entry,
size_t amountInc) { | |
206 fEntryBytes += amountInc; | |
207 this->purgeAsNeeded(); | |
208 } | |
209 | |
210 void GrResourceCache::didDecreaseResourceSize(const GrResourceCacheEntry* entry,
size_t amountDec) { | |
211 fEntryBytes -= amountDec; | |
212 #ifdef SK_DEBUG | |
213 this->validate(); | |
214 #endif | |
215 } | |
216 | |
217 /** | |
218 * Destroying a resource may potentially trigger the unlock of additional | |
219 * resources which in turn will trigger a nested purge. We block the nested | |
220 * purge using the fPurging variable. However, the initial purge will keep | |
221 * looping until either all resources in the cache are unlocked or we've met | |
222 * the budget. There is an assertion in createAndLock to check against a | |
223 * resource's destructor inserting new resources into the cache. If these | |
224 * new resources were unlocked before purgeAsNeeded completed it could | |
225 * potentially make purgeAsNeeded loop infinitely. | |
226 * | |
227 * extraCount and extraBytes are added to the current resource totals to account | |
228 * for incoming resources (e.g., GrContext is about to add 10MB split between | |
229 * 10 textures). | |
230 */ | |
231 void GrResourceCache::purgeAsNeeded(int extraCount, size_t extraBytes) { | |
232 if (fPurging) { | |
233 return; | |
234 } | |
235 | |
236 fPurging = true; | |
237 | |
238 this->internalPurge(extraCount, extraBytes); | |
239 if (((fEntryCount+extraCount) > fMaxCount || | |
240 (fEntryBytes+extraBytes) > fMaxBytes) && | |
241 fOverbudgetCB) { | |
242 // Despite the purge we're still over budget. See if Ganesh can | |
243 // release some resources and purge again. | |
244 if ((*fOverbudgetCB)(fOverbudgetData)) { | |
245 this->internalPurge(extraCount, extraBytes); | |
246 } | |
247 } | |
248 | |
249 fPurging = false; | |
250 } | |
251 | |
252 void GrResourceCache::purgeInvalidated() { | |
253 // TODO: Implement this in GrResourceCache2. | |
254 } | |
255 | |
256 void GrResourceCache::deleteResource(GrResourceCacheEntry* entry) { | |
257 SkASSERT(entry->fResource->isPurgable()); | |
258 // remove from our llist | |
259 this->internalDetach(entry); | |
260 delete entry; | |
261 } | |
262 | |
263 void GrResourceCache::internalPurge(int extraCount, size_t extraBytes) { | |
264 SkASSERT(fPurging); | |
265 | |
266 bool withinBudget = false; | |
267 bool changed = false; | |
268 | |
269 // The purging process is repeated several times since one pass | |
270 // may free up other resources | |
271 do { | |
272 EntryList::Iter iter; | |
273 | |
274 changed = false; | |
275 | |
276 // Note: the following code relies on the fact that the | |
277 // doubly linked list doesn't invalidate its data/pointers | |
278 // outside of the specific area where a deletion occurs (e.g., | |
279 // in internalDetach) | |
280 GrResourceCacheEntry* entry = iter.init(fList, EntryList::Iter::kTail_It
erStart); | |
281 | |
282 while (entry) { | |
283 GrAutoResourceCacheValidate atcv(this); | |
284 | |
285 if ((fEntryCount+extraCount) <= fMaxCount && | |
286 (fEntryBytes+extraBytes) <= fMaxBytes) { | |
287 withinBudget = true; | |
288 break; | |
289 } | |
290 | |
291 GrResourceCacheEntry* prev = iter.prev(); | |
292 if (entry->fResource->isPurgable()) { | |
293 changed = true; | |
294 this->deleteResource(entry); | |
295 } | |
296 entry = prev; | |
297 } | |
298 } while (!withinBudget && changed); | |
299 } | |
300 | |
301 void GrResourceCache::purgeAllUnlocked() { | |
302 GrAutoResourceCacheValidate atcv(this); | |
303 | |
304 // we can have one GrCacheable holding a lock on another | |
305 // so we don't want to just do a simple loop kicking each | |
306 // entry out. Instead change the budget and purge. | |
307 | |
308 size_t savedMaxBytes = fMaxBytes; | |
309 int savedMaxCount = fMaxCount; | |
310 fMaxBytes = (size_t) -1; | |
311 fMaxCount = 0; | |
312 this->purgeAsNeeded(); | |
313 | |
314 fMaxBytes = savedMaxBytes; | |
315 fMaxCount = savedMaxCount; | |
316 } | |
317 | |
318 /////////////////////////////////////////////////////////////////////////////// | |
319 | |
320 #ifdef SK_DEBUG | |
321 size_t GrResourceCache::countBytes(const EntryList& list) { | |
322 size_t bytes = 0; | |
323 | |
324 EntryList::Iter iter; | |
325 | |
326 const GrResourceCacheEntry* entry = iter.init(const_cast<EntryList&>(list), | |
327 EntryList::Iter::kTail_IterSta
rt); | |
328 | |
329 for ( ; entry; entry = iter.prev()) { | |
330 bytes += entry->resource()->gpuMemorySize(); | |
331 } | |
332 return bytes; | |
333 } | |
334 | |
335 static bool both_zero_or_nonzero(int count, size_t bytes) { | |
336 return (count == 0 && bytes == 0) || (count > 0 && bytes > 0); | |
337 } | |
338 | |
339 void GrResourceCache::validate() const { | |
340 fList.validate(); | |
341 SkASSERT(both_zero_or_nonzero(fEntryCount, fEntryBytes)); | |
342 | |
343 EntryList::Iter iter; | |
344 | |
345 // check that the shareable entries are okay | |
346 const GrResourceCacheEntry* entry = iter.init(const_cast<EntryList&>(fList), | |
347 EntryList::Iter::kHead_IterSta
rt); | |
348 | |
349 int count = 0; | |
350 for ( ; entry; entry = iter.next()) { | |
351 entry->validate(); | |
352 count += 1; | |
353 } | |
354 SkASSERT(count == fEntryCount); | |
355 | |
356 size_t bytes = this->countBytes(fList); | |
357 SkASSERT(bytes == fEntryBytes); | |
358 SkASSERT(fList.countEntries() == fEntryCount); | |
359 } | |
360 #endif // SK_DEBUG | |
361 | |
362 #if GR_CACHE_STATS | |
363 | |
364 void GrResourceCache::printStats() { | |
365 int locked = 0; | |
366 int scratch = 0; | |
367 | |
368 EntryList::Iter iter; | |
369 | |
370 GrResourceCacheEntry* entry = iter.init(fList, EntryList::Iter::kTail_IterSt
art); | |
371 | |
372 for ( ; entry; entry = iter.prev()) { | |
373 if (!entry->fResource->isPurgable()) { | |
374 ++locked; | |
375 } | |
376 if (entry->fResource->cacheAccess().isScratch()) { | |
377 ++scratch; | |
378 } | |
379 } | |
380 | |
381 float countUtilization = (100.f * fEntryCount) / fMaxCount; | |
382 float byteUtilization = (100.f * fEntryBytes) / fMaxBytes; | |
383 | |
384 SkDebugf("Budget: %d items %d bytes\n", fMaxCount, fMaxBytes); | |
385 SkDebugf("\t\tEntry Count: current %d (%d locked, %d scratch %.2g%% full), h
igh %d\n", | |
386 fEntryCount, locked, scratch, countUtilization, fHighWaterEntryC
ount); | |
387 SkDebugf("\t\tEntry Bytes: current %d (%.2g%% full) high %d\n", | |
388 fEntryBytes, byteUtilization, fHighWaterEntryBytes); | |
389 } | |
390 | |
391 #endif | |
392 | |
393 /////////////////////////////////////////////////////////////////////////////// | |
OLD | NEW |