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.
5 #include "cc/prioritized_resource_manager.h"
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"
17 PrioritizedResourceManager::PrioritizedResourceManager(const Proxy
* proxy
)
19 , m_maxMemoryLimitBytes(defaultMemoryAllocationLimit())
20 , m_externalPriorityCutoff(PriorityCalculator::allowEverythingCutoff())
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)
32 PrioritizedResourceManager::~PrioritizedResourceManager()
34 while (m_textures
.size() > 0)
35 unregisterTexture(*m_textures
.begin());
37 unlinkAndClearEvictedBackings();
38 DCHECK(m_evictedBackings
.empty());
40 // Each remaining backing is a leaked opengl texture. There should be none.
41 DCHECK(m_backings
.empty());
44 size_t PrioritizedResourceManager::memoryVisibleBytes() const
46 DCHECK(m_proxy
->isImplThread());
47 return m_memoryVisibleLastPushedBytes
;
50 size_t PrioritizedResourceManager::memoryVisibleAndNearbyBytes() const
52 DCHECK(m_proxy
->isImplThread());
53 return m_memoryVisibleAndNearbyLastPushedBytes
;
56 void PrioritizedResourceManager::prioritizeTextures()
58 TRACE_EVENT0("cc", "PrioritizedResourceManager::prioritizeTextures");
59 DCHECK(m_proxy
->isMainThread());
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).
65 TextureVector
& sortedTextures
= m_tempTextureVector
;
66 sortedTextures
.clear();
68 // Copy all textures into a vector, sort them, and collect memory requirements 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::priorityIsHigher(texture
->requestPriority(), PriorityCalculator::allowVisibleOnlyCutoff()))
75 m_memoryVisibleBytes
+= texture
->bytes();
76 if (PriorityCalculator::priorityIsHigher(texture
->requestPriority(), PriorityCalculator::allowVisibleAndNearbyCutoff()))
77 m_memoryVisibleAndNearbyBytes
+= texture
->bytes();
79 std::sort(sortedTextures
.begin(), sortedTextures
.end(), compareTextures
);
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
!= sortedTextures
.end(); ++it
) {
86 if ((*it
)->isSelfManaged()) {
87 // Account for self-managed memory immediately by reducing the memory
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
;
95 m_memoryAvailableBytes
-= (*it
)->bytes();
97 size_t newMemoryBytes
= memoryBytes
+ (*it
)->bytes();
98 if (newMemoryBytes
> m_memoryAvailableBytes
) {
99 m_priorityCutoff
= (*it
)->requestPriority();
102 memoryBytes
= newMemoryBytes
;
106 // Disallow any textures with priority below the external cutoff to have backings.
107 for (TextureVector::iterator it
= sortedTextures
.begin(); it
!= sortedTextures
.end(); ++it
) {
108 PrioritizedResource
* texture
= (*it
);
109 if (!PriorityCalculator::priorityIsHigher(texture
->requestPriority(), m_externalPriorityCutoff
) &&
110 texture
->haveBackingTexture())
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
!= sortedTextures
.end(); ++it
) {
119 bool isAbovePriorityCutoff
= PriorityCalculator::priorityIsHigher((*it
)->requestPriority(), m_priorityCutoff
);
120 (*it
)->setAbovePriorityCutoff(isAbovePriorityCutoff
);
121 if (isAbovePriorityCutoff
&& !(*it
)->isSelfManaged())
122 m_memoryAboveCutoffBytes
+= (*it
)->bytes();
124 sortedTextures
.clear();
126 DCHECK(m_memoryAboveCutoffBytes
<= m_memoryAvailableBytes
);
127 DCHECK(memoryAboveCutoffBytes() <= maxMemoryLimitBytes());
130 void PrioritizedResourceManager::pushTexturePrioritiesToBackings()
132 TRACE_EVENT0("cc", "PrioritizedResourceManager::pushTexturePrioritiesToBackings");
133 DCHECK(m_proxy
->isImplThread() && m_proxy
->isMainThreadBlocked());
136 for (BackingList::iterator it
= m_backings
.begin(); it
!= m_backings
.end(); ++it
)
137 (*it
)->updatePriority();
141 // Push memory requirements to the impl thread structure.
142 m_memoryVisibleLastPushedBytes
= m_memoryVisibleBytes
;
143 m_memoryVisibleAndNearbyLastPushedBytes
= m_memoryVisibleAndNearbyBytes
;
146 void PrioritizedResourceManager::updateBackingsInDrawingImplTree()
148 TRACE_EVENT0("cc", "PrioritizedResourceManager::updateBackingsInDrawingImplTree");
149 DCHECK(m_proxy
->isImplThread() && m_proxy
->isMainThreadBlocked());
152 for (BackingList::iterator it
= m_backings
.begin(); it
!= m_backings
.end(); ++it
) {
153 PrioritizedResource::Backing
* backing
= (*it
);
154 backing
->updateInDrawingImplTree();
160 void PrioritizedResourceManager::sortBackings()
162 TRACE_EVENT0("cc", "PrioritizedResourceManager::sortBackings");
163 DCHECK(m_proxy
->isImplThread());
165 // Put backings in eviction/recycling order.
166 m_backings
.sort(compareBackings
);
167 m_backingsTailNotSorted
= false;
170 void PrioritizedResourceManager::clearPriorities()
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
)->requestPriority()));
182 bool PrioritizedResourceManager::requestLate(PrioritizedResource
* texture
)
184 DCHECK(m_proxy
->isMainThread());
186 // This is already above cutoff, so don't double count it's memory below.
187 if (texture
->isAbovePriorityCutoff())
190 // Allow textures that have priority equal to the cutoff, but not strictly lower.
191 if (PriorityCalculator::priorityIsLower(texture
->requestPriority(), m_priorityCutoff
))
194 // Disallow textures that do not have a priority strictly higher than the external cutoff.
195 if (!PriorityCalculator::priorityIsHigher(texture
->requestPriority(), m_externalPriorityCutoff
))
198 size_t newMemoryBytes
= m_memoryAboveCutoffBytes
+ texture
->bytes();
199 if (newMemoryBytes
> m_memoryAvailableBytes
)
202 m_memoryAboveCutoffBytes
= newMemoryBytes
;
203 texture
->setAbovePriorityCutoff(true);
207 void PrioritizedResourceManager::acquireBackingTextureIfNeeded(PrioritizedResource
* texture
, ResourceProvider
* resourceProvider
)
209 DCHECK(m_proxy
->isImplThread() && m_proxy
->isMainThreadBlocked());
210 DCHECK(!texture
->isSelfManaged());
211 DCHECK(texture
->isAbovePriorityCutoff());
212 if (texture
->backing() || !texture
->isAbovePriorityCutoff())
215 // Find a backing below, by either recycling or allocating.
216 PrioritizedResource::Backing
* backing
= 0;
218 // First try to recycle
219 for (BackingList::iterator it
= m_backings
.begin(); it
!= m_backings
.end(); ++it
) {
220 if (!(*it
)->canBeRecycled())
222 if (resourceProvider
->inUseByConsumer((*it
)->id()))
224 if ((*it
)->size() == texture
->size() && (*it
)->format() == texture
->format()) {
226 m_backings
.erase(it
);
231 // Otherwise reduce memory and just allocate a new backing texures.
233 evictBackingsToReduceMemory(m_memoryAvailableBytes
- texture
->bytes(),
234 PriorityCalculator::allowEverythingCutoff(),
238 backing
= createBacking(texture
->size(), texture
->format(), resourceProvider
);
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;
249 // Update the backing's priority from its new owner.
250 backing
->updatePriority();
253 bool PrioritizedResourceManager::evictBackingsToReduceMemory(size_t limitBytes
,
255 EvictionPolicy evictionPolicy
,
256 UnlinkPolicy unlinkPolicy
,
257 ResourceProvider
* resourceProvider
)
259 DCHECK(m_proxy
->isImplThread());
260 if (unlinkPolicy
== UnlinkBackings
)
261 DCHECK(m_proxy
->isMainThreadBlocked());
262 if (memoryUseBytes() <= limitBytes
&& PriorityCalculator::allowEverythingCutoff() == priorityCutoff
)
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::priorityIsHigher(backing
->requestPriorityAtLastPriorityUpdate(), priorityCutoff
))
272 if (evictionPolicy
== EvictOnlyRecyclable
&& !backing
->canBeRecycled())
274 if (unlinkPolicy
== UnlinkBackings
&& backing
->owner())
275 backing
->owner()->unlink();
276 evictFirstBackingResource(resourceProvider
);
281 void PrioritizedResourceManager::reduceMemory(ResourceProvider
* resourceProvider
)
283 DCHECK(m_proxy
->isImplThread() && m_proxy
->isMainThreadBlocked());
284 evictBackingsToReduceMemory(m_memoryAvailableBytes
,
285 PriorityCalculator::allowEverythingCutoff(),
289 DCHECK(memoryUseBytes() <= m_memoryAvailableBytes
);
291 // We currently collect backings from deleted textures for later recycling.
292 // However, if we do that forever we will always use the max limit even if
293 // we really need very little memory. This should probably be solved by reducing the
294 // limit externally, but until then this just does some "clean up" of unused
295 // backing textures (any more than 10%).
296 size_t wastedMemory
= 0;
297 for (BackingList::iterator it
= m_backings
.begin(); it
!= m_backings
.end(); ++it
) {
300 wastedMemory
+= (*it
)->bytes();
302 size_t tenPercentOfMemory
= m_memoryAvailableBytes
/ 10;
303 if (wastedMemory
> tenPercentOfMemory
)
304 evictBackingsToReduceMemory(memoryUseBytes() - (wastedMemory
- tenPercentOfMemory
),
305 PriorityCalculator::allowEverythingCutoff(),
311 void PrioritizedResourceManager::clearAllMemory(ResourceProvider
* resourceProvider
)
313 DCHECK(m_proxy
->isImplThread() && m_proxy
->isMainThreadBlocked());
314 if (!resourceProvider
) {
315 DCHECK(m_backings
.empty());
318 evictBackingsToReduceMemory(0,
319 PriorityCalculator::allowEverythingCutoff(),
325 bool PrioritizedResourceManager::reduceMemoryOnImplThread(size_t limitBytes
, int priorityCutoff
, ResourceProvider
* resourceProvider
)
327 DCHECK(m_proxy
->isImplThread());
328 DCHECK(resourceProvider
);
329 // If we are in the process of uploading a new frame then the backings at the very end of
330 // the list are not sorted by priority. Sort them before doing the eviction.
331 if (m_backingsTailNotSorted
)
333 return evictBackingsToReduceMemory(limitBytes
,
340 void PrioritizedResourceManager::unlinkAndClearEvictedBackings()
342 DCHECK(m_proxy
->isMainThread());
343 base::AutoLock
scoped_lock(m_evictedBackingsLock
);
344 for (BackingList::const_iterator it
= m_evictedBackings
.begin(); it
!= m_evictedBackings
.end(); ++it
) {
345 PrioritizedResource::Backing
* backing
= (*it
);
346 if (backing
->owner())
347 backing
->owner()->unlink();
350 m_evictedBackings
.clear();
353 bool PrioritizedResourceManager::linkedEvictedBackingsExist() const
355 DCHECK(m_proxy
->isImplThread() && m_proxy
->isMainThreadBlocked());
356 base::AutoLock
scoped_lock(m_evictedBackingsLock
);
357 for (BackingList::const_iterator it
= m_evictedBackings
.begin(); it
!= m_evictedBackings
.end(); ++it
) {
364 void PrioritizedResourceManager::registerTexture(PrioritizedResource
* texture
)
366 DCHECK(m_proxy
->isMainThread());
368 DCHECK(!texture
->resourceManager());
369 DCHECK(!texture
->backing());
370 DCHECK(!ContainsKey(m_textures
, texture
));
372 texture
->setManagerInternal(this);
373 m_textures
.insert(texture
);
377 void PrioritizedResourceManager::unregisterTexture(PrioritizedResource
* texture
)
379 DCHECK(m_proxy
->isMainThread() || (m_proxy
->isImplThread() && m_proxy
->isMainThreadBlocked()));
381 DCHECK(ContainsKey(m_textures
, texture
));
383 returnBackingTexture(texture
);
384 texture
->setManagerInternal(0);
385 m_textures
.erase(texture
);
386 texture
->setAbovePriorityCutoff(false);
389 void PrioritizedResourceManager::returnBackingTexture(PrioritizedResource
* texture
)
391 DCHECK(m_proxy
->isMainThread() || (m_proxy
->isImplThread() && m_proxy
->isMainThreadBlocked()));
392 if (texture
->backing())
396 PrioritizedResource::Backing
* PrioritizedResourceManager::createBacking(gfx::Size size
, GLenum format
, ResourceProvider
* resourceProvider
)
398 DCHECK(m_proxy
->isImplThread() && m_proxy
->isMainThreadBlocked());
399 DCHECK(resourceProvider
);
400 ResourceProvider::ResourceId resourceId
= resourceProvider
->createManagedResource(size
, format
, ResourceProvider::TextureUsageAny
);
401 PrioritizedResource::Backing
* backing
= new PrioritizedResource::Backing(resourceId
, resourceProvider
, size
, format
);
402 m_memoryUseBytes
+= backing
->bytes();
406 void PrioritizedResourceManager::evictFirstBackingResource(ResourceProvider
* resourceProvider
)
408 DCHECK(m_proxy
->isImplThread());
409 DCHECK(resourceProvider
);
410 DCHECK(!m_backings
.empty());
411 PrioritizedResource::Backing
* backing
= m_backings
.front();
413 // Note that we create a backing and its resource at the same time, but we
414 // delete the backing structure and its resource in two steps. This is because
415 // we can delete the resource while the main thread is running, but we cannot
416 // unlink backings while the main thread is running.
417 backing
->deleteResource(resourceProvider
);
418 m_memoryUseBytes
-= backing
->bytes();
419 m_backings
.pop_front();
420 base::AutoLock
scoped_lock(m_evictedBackingsLock
);
421 m_evictedBackings
.push_back(backing
);
424 void PrioritizedResourceManager::assertInvariants()
427 DCHECK(m_proxy
->isImplThread() && m_proxy
->isMainThreadBlocked());
429 // If we hit any of these asserts, there is a bug in this class. To see
430 // where the bug is, call this function at the beginning and end of
431 // every public function.
433 // Backings/textures must be doubly-linked and only to other backings/textures in this manager.
434 for (BackingList::iterator it
= m_backings
.begin(); it
!= m_backings
.end(); ++it
) {
435 if ((*it
)->owner()) {
436 DCHECK(ContainsKey(m_textures
, (*it
)->owner()));
437 DCHECK((*it
)->owner()->backing() == (*it
));
440 for (TextureSet::iterator it
= m_textures
.begin(); it
!= m_textures
.end(); ++it
) {
441 PrioritizedResource
* texture
= (*it
);
442 PrioritizedResource::Backing
* backing
= texture
->backing();
443 base::AutoLock
scoped_lock(m_evictedBackingsLock
);
445 if (backing
->resourceHasBeenDeleted()) {
446 DCHECK(std::find(m_backings
.begin(), m_backings
.end(), backing
) == m_backings
.end());
447 DCHECK(std::find(m_evictedBackings
.begin(), m_evictedBackings
.end(), backing
) != m_evictedBackings
.end());
449 DCHECK(std::find(m_backings
.begin(), m_backings
.end(), backing
) != m_backings
.end());
450 DCHECK(std::find(m_evictedBackings
.begin(), m_evictedBackings
.end(), backing
) == m_evictedBackings
.end());
452 DCHECK(backing
->owner() == texture
);
456 // At all times, backings that can be evicted must always come before
457 // backings that can't be evicted in the backing texture list (otherwise
458 // reduceMemory will not find all textures available for eviction/recycling).
459 bool reachedUnrecyclable
= false;
460 PrioritizedResource::Backing
* previous_backing
= NULL
;
461 for (BackingList::iterator it
= m_backings
.begin(); it
!= m_backings
.end(); ++it
) {
462 PrioritizedResource::Backing
* backing
= *it
;
463 if (previous_backing
&& (!m_backingsTailNotSorted
|| !backing
->wasAbovePriorityCutoffAtLastPriorityUpdate()))
464 DCHECK(compareBackings(previous_backing
, backing
));
465 if (!backing
->canBeRecycled())
466 reachedUnrecyclable
= true;
467 if (reachedUnrecyclable
)
468 DCHECK(!backing
->canBeRecycled());
470 DCHECK(backing
->canBeRecycled());
471 previous_backing
= backing
;
476 const Proxy
* PrioritizedResourceManager::proxyForDebug() const