OLD | NEW |
| (Empty) |
1 // Copyright 2012 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #include "cc/prioritized_resource_manager.h" | |
6 | |
7 #include <algorithm> | |
8 | |
9 #include "base/debug/trace_event.h" | |
10 #include "base/stl_util.h" | |
11 #include "cc/prioritized_resource.h" | |
12 #include "cc/priority_calculator.h" | |
13 #include "cc/trees/proxy.h" | |
14 | |
15 namespace cc { | |
16 | |
17 PrioritizedResourceManager::PrioritizedResourceManager(const Proxy* proxy) | |
18 : m_proxy(proxy) | |
19 , m_maxMemoryLimitBytes(defaultMemoryAllocationLimit()) | |
20 , m_externalPriorityCutoff(PriorityCalculator::AllowEverythingCutoff()) | |
21 , m_memoryUseBytes(0) | |
22 , m_memoryAboveCutoffBytes(0) | |
23 , m_memoryAvailableBytes(0) | |
24 , m_backingsTailNotSorted(false) | |
25 , m_memoryVisibleBytes(0) | |
26 , m_memoryVisibleAndNearbyBytes(0) | |
27 , m_memoryVisibleLastPushedBytes(0) | |
28 , m_memoryVisibleAndNearbyLastPushedBytes(0) | |
29 { | |
30 } | |
31 | |
32 PrioritizedResourceManager::~PrioritizedResourceManager() | |
33 { | |
34 while (m_textures.size() > 0) | |
35 unregisterTexture(*m_textures.begin()); | |
36 | |
37 unlinkAndClearEvictedBackings(); | |
38 DCHECK(m_evictedBackings.empty()); | |
39 | |
40 // Each remaining backing is a leaked opengl texture. There should be none. | |
41 DCHECK(m_backings.empty()); | |
42 } | |
43 | |
44 size_t PrioritizedResourceManager::memoryVisibleBytes() const | |
45 { | |
46 DCHECK(m_proxy->IsImplThread()); | |
47 return m_memoryVisibleLastPushedBytes; | |
48 } | |
49 | |
50 size_t PrioritizedResourceManager::memoryVisibleAndNearbyBytes() const | |
51 { | |
52 DCHECK(m_proxy->IsImplThread()); | |
53 return m_memoryVisibleAndNearbyLastPushedBytes; | |
54 } | |
55 | |
56 void PrioritizedResourceManager::prioritizeTextures() | |
57 { | |
58 TRACE_EVENT0("cc", "PrioritizedResourceManager::prioritizeTextures"); | |
59 DCHECK(m_proxy->IsMainThread()); | |
60 | |
61 // Sorting textures in this function could be replaced by a slightly | |
62 // modified O(n) quick-select to partition textures rather than | |
63 // sort them (if performance of the sort becomes an issue). | |
64 | |
65 TextureVector& sortedTextures = m_tempTextureVector; | |
66 sortedTextures.clear(); | |
67 | |
68 // Copy all textures into a vector, sort them, and collect memory requiremen
ts statistics. | |
69 m_memoryVisibleBytes = 0; | |
70 m_memoryVisibleAndNearbyBytes = 0; | |
71 for (TextureSet::iterator it = m_textures.begin(); it != m_textures.end(); +
+it) { | |
72 PrioritizedResource* texture = (*it); | |
73 sortedTextures.push_back(texture); | |
74 if (PriorityCalculator::priority_is_higher(texture->requestPriority(), P
riorityCalculator::AllowVisibleOnlyCutoff())) | |
75 m_memoryVisibleBytes += texture->bytes(); | |
76 if (PriorityCalculator::priority_is_higher(texture->requestPriority(), P
riorityCalculator::AllowVisibleAndNearbyCutoff())) | |
77 m_memoryVisibleAndNearbyBytes += texture->bytes(); | |
78 } | |
79 std::sort(sortedTextures.begin(), sortedTextures.end(), compareTextures); | |
80 | |
81 // Compute a priority cutoff based on memory pressure | |
82 m_memoryAvailableBytes = m_maxMemoryLimitBytes; | |
83 m_priorityCutoff = m_externalPriorityCutoff; | |
84 size_t memoryBytes = 0; | |
85 for (TextureVector::iterator it = sortedTextures.begin(); it != sortedTextur
es.end(); ++it) { | |
86 if ((*it)->isSelfManaged()) { | |
87 // Account for self-managed memory immediately by reducing the memor
y | |
88 // available (since it never gets acquired). | |
89 size_t newMemoryBytes = memoryBytes + (*it)->bytes(); | |
90 if (newMemoryBytes > m_memoryAvailableBytes) { | |
91 m_priorityCutoff = (*it)->requestPriority(); | |
92 m_memoryAvailableBytes = memoryBytes; | |
93 break; | |
94 } | |
95 m_memoryAvailableBytes -= (*it)->bytes(); | |
96 } else { | |
97 size_t newMemoryBytes = memoryBytes + (*it)->bytes(); | |
98 if (newMemoryBytes > m_memoryAvailableBytes) { | |
99 m_priorityCutoff = (*it)->requestPriority(); | |
100 break; | |
101 } | |
102 memoryBytes = newMemoryBytes; | |
103 } | |
104 } | |
105 | |
106 // Disallow any textures with priority below the external cutoff to have bac
kings. | |
107 for (TextureVector::iterator it = sortedTextures.begin(); it != sortedTextur
es.end(); ++it) { | |
108 PrioritizedResource* texture = (*it); | |
109 if (!PriorityCalculator::priority_is_higher(texture->requestPriority(),
m_externalPriorityCutoff) && | |
110 texture->haveBackingTexture()) | |
111 texture->unlink(); | |
112 } | |
113 | |
114 // Only allow textures if they are higher than the cutoff. All textures | |
115 // of the same priority are accepted or rejected together, rather than | |
116 // being partially allowed randomly. | |
117 m_memoryAboveCutoffBytes = 0; | |
118 for (TextureVector::iterator it = sortedTextures.begin(); it != sortedTextur
es.end(); ++it) { | |
119 bool isAbovePriorityCutoff = PriorityCalculator::priority_is_higher((*it
)->requestPriority(), m_priorityCutoff); | |
120 (*it)->setAbovePriorityCutoff(isAbovePriorityCutoff); | |
121 if (isAbovePriorityCutoff && !(*it)->isSelfManaged()) | |
122 m_memoryAboveCutoffBytes += (*it)->bytes(); | |
123 } | |
124 sortedTextures.clear(); | |
125 | |
126 DCHECK(m_memoryAboveCutoffBytes <= m_memoryAvailableBytes); | |
127 DCHECK(memoryAboveCutoffBytes() <= maxMemoryLimitBytes()); | |
128 } | |
129 | |
130 void PrioritizedResourceManager::pushTexturePrioritiesToBackings() | |
131 { | |
132 TRACE_EVENT0("cc", "PrioritizedResourceManager::pushTexturePrioritiesToBacki
ngs"); | |
133 DCHECK(m_proxy->IsImplThread() && m_proxy->IsMainThreadBlocked()); | |
134 | |
135 assertInvariants(); | |
136 for (BackingList::iterator it = m_backings.begin(); it != m_backings.end();
++it) | |
137 (*it)->updatePriority(); | |
138 sortBackings(); | |
139 assertInvariants(); | |
140 | |
141 // Push memory requirements to the impl thread structure. | |
142 m_memoryVisibleLastPushedBytes = m_memoryVisibleBytes; | |
143 m_memoryVisibleAndNearbyLastPushedBytes = m_memoryVisibleAndNearbyBytes; | |
144 } | |
145 | |
146 void PrioritizedResourceManager::updateBackingsInDrawingImplTree() | |
147 { | |
148 TRACE_EVENT0("cc", "PrioritizedResourceManager::updateBackingsInDrawingImplT
ree"); | |
149 DCHECK(m_proxy->IsImplThread() && m_proxy->IsMainThreadBlocked()); | |
150 | |
151 assertInvariants(); | |
152 for (BackingList::iterator it = m_backings.begin(); it != m_backings.end();
++it) { | |
153 PrioritizedResource::Backing* backing = (*it); | |
154 backing->updateInDrawingImplTree(); | |
155 } | |
156 sortBackings(); | |
157 assertInvariants(); | |
158 } | |
159 | |
160 void PrioritizedResourceManager::sortBackings() | |
161 { | |
162 TRACE_EVENT0("cc", "PrioritizedResourceManager::sortBackings"); | |
163 DCHECK(m_proxy->IsImplThread()); | |
164 | |
165 // Put backings in eviction/recycling order. | |
166 m_backings.sort(compareBackings); | |
167 m_backingsTailNotSorted = false; | |
168 } | |
169 | |
170 void PrioritizedResourceManager::clearPriorities() | |
171 { | |
172 DCHECK(m_proxy->IsMainThread()); | |
173 for (TextureSet::iterator it = m_textures.begin(); it != m_textures.end(); +
+it) { | |
174 // FIXME: We should remove this and just set all priorities to | |
175 // PriorityCalculator::lowestPriority() once we have priorities | |
176 // for all textures (we can't currently calculate distances for | |
177 // off-screen textures). | |
178 (*it)->setRequestPriority(PriorityCalculator::LingeringPriority((*it)->r
equestPriority())); | |
179 } | |
180 } | |
181 | |
182 bool PrioritizedResourceManager::requestLate(PrioritizedResource* texture) | |
183 { | |
184 DCHECK(m_proxy->IsMainThread()); | |
185 | |
186 // This is already above cutoff, so don't double count it's memory below. | |
187 if (texture->isAbovePriorityCutoff()) | |
188 return true; | |
189 | |
190 // Allow textures that have priority equal to the cutoff, but not strictly l
ower. | |
191 if (PriorityCalculator::priority_is_lower(texture->requestPriority(), m_prio
rityCutoff)) | |
192 return false; | |
193 | |
194 // Disallow textures that do not have a priority strictly higher than the ex
ternal cutoff. | |
195 if (!PriorityCalculator::priority_is_higher(texture->requestPriority(), m_ex
ternalPriorityCutoff)) | |
196 return false; | |
197 | |
198 size_t newMemoryBytes = m_memoryAboveCutoffBytes + texture->bytes(); | |
199 if (newMemoryBytes > m_memoryAvailableBytes) | |
200 return false; | |
201 | |
202 m_memoryAboveCutoffBytes = newMemoryBytes; | |
203 texture->setAbovePriorityCutoff(true); | |
204 return true; | |
205 } | |
206 | |
207 void PrioritizedResourceManager::acquireBackingTextureIfNeeded(PrioritizedResour
ce* texture, ResourceProvider* resourceProvider) | |
208 { | |
209 DCHECK(m_proxy->IsImplThread() && m_proxy->IsMainThreadBlocked()); | |
210 DCHECK(!texture->isSelfManaged()); | |
211 DCHECK(texture->isAbovePriorityCutoff()); | |
212 if (texture->backing() || !texture->isAbovePriorityCutoff()) | |
213 return; | |
214 | |
215 // Find a backing below, by either recycling or allocating. | |
216 PrioritizedResource::Backing* backing = 0; | |
217 | |
218 // First try to recycle | |
219 for (BackingList::iterator it = m_backings.begin(); it != m_backings.end();
++it) { | |
220 if (!(*it)->canBeRecycled()) | |
221 break; | |
222 if (resourceProvider->InUseByConsumer((*it)->id())) | |
223 continue; | |
224 if ((*it)->size() == texture->size() && (*it)->format() == texture->form
at()) { | |
225 backing = (*it); | |
226 m_backings.erase(it); | |
227 break; | |
228 } | |
229 } | |
230 | |
231 // Otherwise reduce memory and just allocate a new backing texures. | |
232 if (!backing) { | |
233 evictBackingsToReduceMemory(m_memoryAvailableBytes - texture->bytes(), | |
234 PriorityCalculator::AllowEverythingCutoff(), | |
235 EvictOnlyRecyclable, | |
236 DoNotUnlinkBackings, | |
237 resourceProvider); | |
238 backing = createBacking(texture->size(), texture->format(), resourceProv
ider); | |
239 } | |
240 | |
241 // Move the used backing to the end of the eviction list, and note that | |
242 // the tail is not sorted. | |
243 if (backing->owner()) | |
244 backing->owner()->unlink(); | |
245 texture->link(backing); | |
246 m_backings.push_back(backing); | |
247 m_backingsTailNotSorted = true; | |
248 | |
249 // Update the backing's priority from its new owner. | |
250 backing->updatePriority(); | |
251 } | |
252 | |
253 bool PrioritizedResourceManager::evictBackingsToReduceMemory(size_t limitBytes, | |
254 int priorityCutoff, | |
255 EvictionPolicy evic
tionPolicy, | |
256 UnlinkPolicy unlink
Policy, | |
257 ResourceProvider* r
esourceProvider) | |
258 { | |
259 DCHECK(m_proxy->IsImplThread()); | |
260 if (unlinkPolicy == UnlinkBackings) | |
261 DCHECK(m_proxy->IsMainThreadBlocked()); | |
262 if (memoryUseBytes() <= limitBytes && PriorityCalculator::AllowEverythingCut
off() == priorityCutoff) | |
263 return false; | |
264 | |
265 // Destroy backings until we are below the limit, | |
266 // or until all backings remaining are above the cutoff. | |
267 while (m_backings.size() > 0) { | |
268 PrioritizedResource::Backing* backing = m_backings.front(); | |
269 if (memoryUseBytes() <= limitBytes && | |
270 PriorityCalculator::priority_is_higher(backing->requestPriorityAtLas
tPriorityUpdate(), priorityCutoff)) | |
271 break; | |
272 if (evictionPolicy == EvictOnlyRecyclable && !backing->canBeRecycled()) | |
273 break; | |
274 if (unlinkPolicy == UnlinkBackings && backing->owner()) | |
275 backing->owner()->unlink(); | |
276 evictFirstBackingResource(resourceProvider); | |
277 } | |
278 return true; | |
279 } | |
280 | |
281 void PrioritizedResourceManager::reduceWastedMemory(ResourceProvider* resourcePr
ovider) | |
282 { | |
283 // We currently collect backings from deleted textures for later recycling. | |
284 // However, if we do that forever we will always use the max limit even if | |
285 // we really need very little memory. This should probably be solved by redu
cing the | |
286 // limit externally, but until then this just does some "clean up" of unused | |
287 // backing textures (any more than 10%). | |
288 size_t wastedMemory = 0; | |
289 for (BackingList::iterator it = m_backings.begin(); it != m_backings.end();
++it) { | |
290 if ((*it)->owner()) | |
291 break; | |
292 wastedMemory += (*it)->bytes(); | |
293 } | |
294 size_t tenPercentOfMemory = m_memoryAvailableBytes / 10; | |
295 if (wastedMemory > tenPercentOfMemory) | |
296 evictBackingsToReduceMemory(memoryUseBytes() - (wastedMemory - tenPercen
tOfMemory), | |
297 PriorityCalculator::AllowEverythingCutoff(), | |
298 EvictOnlyRecyclable, | |
299 DoNotUnlinkBackings, | |
300 resourceProvider); | |
301 } | |
302 | |
303 void PrioritizedResourceManager::reduceMemory(ResourceProvider* resourceProvider
) | |
304 { | |
305 DCHECK(m_proxy->IsImplThread() && m_proxy->IsMainThreadBlocked()); | |
306 evictBackingsToReduceMemory(m_memoryAvailableBytes, | |
307 PriorityCalculator::AllowEverythingCutoff(), | |
308 EvictAnything, | |
309 UnlinkBackings, | |
310 resourceProvider); | |
311 DCHECK(memoryUseBytes() <= m_memoryAvailableBytes); | |
312 | |
313 reduceWastedMemory(resourceProvider); | |
314 } | |
315 | |
316 void PrioritizedResourceManager::clearAllMemory(ResourceProvider* resourceProvid
er) | |
317 { | |
318 DCHECK(m_proxy->IsImplThread() && m_proxy->IsMainThreadBlocked()); | |
319 if (!resourceProvider) { | |
320 DCHECK(m_backings.empty()); | |
321 return; | |
322 } | |
323 evictBackingsToReduceMemory(0, | |
324 PriorityCalculator::AllowEverythingCutoff(), | |
325 EvictAnything, | |
326 DoNotUnlinkBackings, | |
327 resourceProvider); | |
328 } | |
329 | |
330 bool PrioritizedResourceManager::reduceMemoryOnImplThread(size_t limitBytes, int
priorityCutoff, ResourceProvider* resourceProvider) | |
331 { | |
332 DCHECK(m_proxy->IsImplThread()); | |
333 DCHECK(resourceProvider); | |
334 // If we are in the process of uploading a new frame then the backings at th
e very end of | |
335 // the list are not sorted by priority. Sort them before doing the eviction. | |
336 if (m_backingsTailNotSorted) | |
337 sortBackings(); | |
338 return evictBackingsToReduceMemory(limitBytes, | |
339 priorityCutoff, | |
340 EvictAnything, | |
341 DoNotUnlinkBackings, | |
342 resourceProvider); | |
343 } | |
344 | |
345 void PrioritizedResourceManager::reduceWastedMemoryOnImplThread(ResourceProvider
* resourceProvider) | |
346 { | |
347 DCHECK(m_proxy->IsImplThread()); | |
348 DCHECK(resourceProvider); | |
349 // If we are in the process of uploading a new frame then the backings at th
e very end of | |
350 // the list are not sorted by priority. Sort them before doing the eviction. | |
351 if (m_backingsTailNotSorted) | |
352 sortBackings(); | |
353 reduceWastedMemory(resourceProvider); | |
354 } | |
355 | |
356 void PrioritizedResourceManager::unlinkAndClearEvictedBackings() | |
357 { | |
358 DCHECK(m_proxy->IsMainThread()); | |
359 base::AutoLock scoped_lock(m_evictedBackingsLock); | |
360 for (BackingList::const_iterator it = m_evictedBackings.begin(); it != m_evi
ctedBackings.end(); ++it) { | |
361 PrioritizedResource::Backing* backing = (*it); | |
362 if (backing->owner()) | |
363 backing->owner()->unlink(); | |
364 delete backing; | |
365 } | |
366 m_evictedBackings.clear(); | |
367 } | |
368 | |
369 bool PrioritizedResourceManager::linkedEvictedBackingsExist() const | |
370 { | |
371 DCHECK(m_proxy->IsImplThread() && m_proxy->IsMainThreadBlocked()); | |
372 base::AutoLock scoped_lock(m_evictedBackingsLock); | |
373 for (BackingList::const_iterator it = m_evictedBackings.begin(); it != m_evi
ctedBackings.end(); ++it) { | |
374 if ((*it)->owner()) | |
375 return true; | |
376 } | |
377 return false; | |
378 } | |
379 | |
380 void PrioritizedResourceManager::registerTexture(PrioritizedResource* texture) | |
381 { | |
382 DCHECK(m_proxy->IsMainThread()); | |
383 DCHECK(texture); | |
384 DCHECK(!texture->resourceManager()); | |
385 DCHECK(!texture->backing()); | |
386 DCHECK(!ContainsKey(m_textures, texture)); | |
387 | |
388 texture->setManagerInternal(this); | |
389 m_textures.insert(texture); | |
390 | |
391 } | |
392 | |
393 void PrioritizedResourceManager::unregisterTexture(PrioritizedResource* texture) | |
394 { | |
395 DCHECK(m_proxy->IsMainThread() || (m_proxy->IsImplThread() && m_proxy->IsMai
nThreadBlocked())); | |
396 DCHECK(texture); | |
397 DCHECK(ContainsKey(m_textures, texture)); | |
398 | |
399 returnBackingTexture(texture); | |
400 texture->setManagerInternal(0); | |
401 m_textures.erase(texture); | |
402 texture->setAbovePriorityCutoff(false); | |
403 } | |
404 | |
405 void PrioritizedResourceManager::returnBackingTexture(PrioritizedResource* textu
re) | |
406 { | |
407 DCHECK(m_proxy->IsMainThread() || (m_proxy->IsImplThread() && m_proxy->IsMai
nThreadBlocked())); | |
408 if (texture->backing()) | |
409 texture->unlink(); | |
410 } | |
411 | |
412 PrioritizedResource::Backing* PrioritizedResourceManager::createBacking(gfx::Siz
e size, GLenum format, ResourceProvider* resourceProvider) | |
413 { | |
414 DCHECK(m_proxy->IsImplThread() && m_proxy->IsMainThreadBlocked()); | |
415 DCHECK(resourceProvider); | |
416 ResourceProvider::ResourceId resourceId = resourceProvider->CreateManagedRes
ource(size, format, ResourceProvider::TextureUsageAny); | |
417 PrioritizedResource::Backing* backing = new PrioritizedResource::Backing(res
ourceId, resourceProvider, size, format); | |
418 m_memoryUseBytes += backing->bytes(); | |
419 return backing; | |
420 } | |
421 | |
422 void PrioritizedResourceManager::evictFirstBackingResource(ResourceProvider* res
ourceProvider) | |
423 { | |
424 DCHECK(m_proxy->IsImplThread()); | |
425 DCHECK(resourceProvider); | |
426 DCHECK(!m_backings.empty()); | |
427 PrioritizedResource::Backing* backing = m_backings.front(); | |
428 | |
429 // Note that we create a backing and its resource at the same time, but we | |
430 // delete the backing structure and its resource in two steps. This is becau
se | |
431 // we can delete the resource while the main thread is running, but we canno
t | |
432 // unlink backings while the main thread is running. | |
433 backing->deleteResource(resourceProvider); | |
434 m_memoryUseBytes -= backing->bytes(); | |
435 m_backings.pop_front(); | |
436 base::AutoLock scoped_lock(m_evictedBackingsLock); | |
437 m_evictedBackings.push_back(backing); | |
438 } | |
439 | |
440 void PrioritizedResourceManager::assertInvariants() | |
441 { | |
442 #ifndef NDEBUG | |
443 DCHECK(m_proxy->IsImplThread() && m_proxy->IsMainThreadBlocked()); | |
444 | |
445 // If we hit any of these asserts, there is a bug in this class. To see | |
446 // where the bug is, call this function at the beginning and end of | |
447 // every public function. | |
448 | |
449 // Backings/textures must be doubly-linked and only to other backings/textur
es in this manager. | |
450 for (BackingList::iterator it = m_backings.begin(); it != m_backings.end();
++it) { | |
451 if ((*it)->owner()) { | |
452 DCHECK(ContainsKey(m_textures, (*it)->owner())); | |
453 DCHECK((*it)->owner()->backing() == (*it)); | |
454 } | |
455 } | |
456 for (TextureSet::iterator it = m_textures.begin(); it != m_textures.end(); +
+it) { | |
457 PrioritizedResource* texture = (*it); | |
458 PrioritizedResource::Backing* backing = texture->backing(); | |
459 base::AutoLock scoped_lock(m_evictedBackingsLock); | |
460 if (backing) { | |
461 if (backing->resourceHasBeenDeleted()) { | |
462 DCHECK(std::find(m_backings.begin(), m_backings.end(), backing)
== m_backings.end()); | |
463 DCHECK(std::find(m_evictedBackings.begin(), m_evictedBackings.en
d(), backing) != m_evictedBackings.end()); | |
464 } else { | |
465 DCHECK(std::find(m_backings.begin(), m_backings.end(), backing)
!= m_backings.end()); | |
466 DCHECK(std::find(m_evictedBackings.begin(), m_evictedBackings.en
d(), backing) == m_evictedBackings.end()); | |
467 } | |
468 DCHECK(backing->owner() == texture); | |
469 } | |
470 } | |
471 | |
472 // At all times, backings that can be evicted must always come before | |
473 // backings that can't be evicted in the backing texture list (otherwise | |
474 // reduceMemory will not find all textures available for eviction/recycling)
. | |
475 bool reachedUnrecyclable = false; | |
476 PrioritizedResource::Backing* previous_backing = NULL; | |
477 for (BackingList::iterator it = m_backings.begin(); it != m_backings.end();
++it) { | |
478 PrioritizedResource::Backing* backing = *it; | |
479 if (previous_backing && (!m_backingsTailNotSorted || !backing->wasAboveP
riorityCutoffAtLastPriorityUpdate())) | |
480 DCHECK(compareBackings(previous_backing, backing)); | |
481 if (!backing->canBeRecycled()) | |
482 reachedUnrecyclable = true; | |
483 if (reachedUnrecyclable) | |
484 DCHECK(!backing->canBeRecycled()); | |
485 else | |
486 DCHECK(backing->canBeRecycled()); | |
487 previous_backing = backing; | |
488 } | |
489 #endif | |
490 } | |
491 | |
492 const Proxy* PrioritizedResourceManager::proxyForDebug() const | |
493 { | |
494 return m_proxy; | |
495 } | |
496 | |
497 } // namespace cc | |
OLD | NEW |