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"
7 #include "base/debug/trace_event.h"
8 #include "base/stl_util.h"
9 #include "cc/prioritized_resource.h"
10 #include "cc/priority_calculator.h"
18 PrioritizedResourceManager::PrioritizedResourceManager(const Proxy
* proxy
)
20 , m_maxMemoryLimitBytes(defaultMemoryAllocationLimit())
21 , m_externalPriorityCutoff(PriorityCalculator::allowEverythingCutoff())
23 , m_memoryAboveCutoffBytes(0)
24 , m_memoryAvailableBytes(0)
25 , m_backingsTailNotSorted(false)
26 , m_memoryVisibleBytes(0)
27 , m_memoryVisibleAndNearbyBytes(0)
28 , m_memoryVisibleLastPushedBytes(0)
29 , m_memoryVisibleAndNearbyLastPushedBytes(0)
33 PrioritizedResourceManager::~PrioritizedResourceManager()
35 while (m_textures
.size() > 0)
36 unregisterTexture(*m_textures
.begin());
38 unlinkAndClearEvictedBackings();
39 DCHECK(m_evictedBackings
.empty());
41 // Each remaining backing is a leaked opengl texture. There should be none.
42 DCHECK(m_backings
.empty());
45 size_t PrioritizedResourceManager::memoryVisibleBytes() const
47 DCHECK(m_proxy
->isImplThread());
48 return m_memoryVisibleLastPushedBytes
;
51 size_t PrioritizedResourceManager::memoryVisibleAndNearbyBytes() const
53 DCHECK(m_proxy
->isImplThread());
54 return m_memoryVisibleAndNearbyLastPushedBytes
;
57 void PrioritizedResourceManager::prioritizeTextures()
59 TRACE_EVENT0("cc", "PrioritizedResourceManager::prioritizeTextures");
60 DCHECK(m_proxy
->isMainThread());
62 // Sorting textures in this function could be replaced by a slightly
63 // modified O(n) quick-select to partition textures rather than
64 // sort them (if performance of the sort becomes an issue).
66 TextureVector
& sortedTextures
= m_tempTextureVector
;
67 sortedTextures
.clear();
69 // Copy all textures into a vector, sort them, and collect memory requirements statistics.
70 m_memoryVisibleBytes
= 0;
71 m_memoryVisibleAndNearbyBytes
= 0;
72 for (TextureSet::iterator it
= m_textures
.begin(); it
!= m_textures
.end(); ++it
) {
73 PrioritizedResource
* texture
= (*it
);
74 sortedTextures
.push_back(texture
);
75 if (PriorityCalculator::priorityIsHigher(texture
->requestPriority(), PriorityCalculator::allowVisibleOnlyCutoff()))
76 m_memoryVisibleBytes
+= texture
->bytes();
77 if (PriorityCalculator::priorityIsHigher(texture
->requestPriority(), PriorityCalculator::allowVisibleAndNearbyCutoff()))
78 m_memoryVisibleAndNearbyBytes
+= texture
->bytes();
80 std::sort(sortedTextures
.begin(), sortedTextures
.end(), compareTextures
);
82 // Compute a priority cutoff based on memory pressure
83 m_memoryAvailableBytes
= m_maxMemoryLimitBytes
;
84 m_priorityCutoff
= m_externalPriorityCutoff
;
85 size_t memoryBytes
= 0;
86 for (TextureVector::iterator it
= sortedTextures
.begin(); it
!= sortedTextures
.end(); ++it
) {
87 if ((*it
)->isSelfManaged()) {
88 // Account for self-managed memory immediately by reducing the memory
89 // available (since it never gets acquired).
90 size_t newMemoryBytes
= memoryBytes
+ (*it
)->bytes();
91 if (newMemoryBytes
> m_memoryAvailableBytes
) {
92 m_priorityCutoff
= (*it
)->requestPriority();
93 m_memoryAvailableBytes
= memoryBytes
;
96 m_memoryAvailableBytes
-= (*it
)->bytes();
98 size_t newMemoryBytes
= memoryBytes
+ (*it
)->bytes();
99 if (newMemoryBytes
> m_memoryAvailableBytes
) {
100 m_priorityCutoff
= (*it
)->requestPriority();
103 memoryBytes
= newMemoryBytes
;
107 // Disallow any textures with priority below the external cutoff to have backings.
108 for (TextureVector::iterator it
= sortedTextures
.begin(); it
!= sortedTextures
.end(); ++it
) {
109 PrioritizedResource
* texture
= (*it
);
110 if (!PriorityCalculator::priorityIsHigher(texture
->requestPriority(), m_externalPriorityCutoff
) &&
111 texture
->haveBackingTexture())
115 // Only allow textures if they are higher than the cutoff. All textures
116 // of the same priority are accepted or rejected together, rather than
117 // being partially allowed randomly.
118 m_memoryAboveCutoffBytes
= 0;
119 for (TextureVector::iterator it
= sortedTextures
.begin(); it
!= sortedTextures
.end(); ++it
) {
120 bool isAbovePriorityCutoff
= PriorityCalculator::priorityIsHigher((*it
)->requestPriority(), m_priorityCutoff
);
121 (*it
)->setAbovePriorityCutoff(isAbovePriorityCutoff
);
122 if (isAbovePriorityCutoff
&& !(*it
)->isSelfManaged())
123 m_memoryAboveCutoffBytes
+= (*it
)->bytes();
125 sortedTextures
.clear();
127 DCHECK(m_memoryAboveCutoffBytes
<= m_memoryAvailableBytes
);
128 DCHECK(memoryAboveCutoffBytes() <= maxMemoryLimitBytes());
131 void PrioritizedResourceManager::pushTexturePrioritiesToBackings()
133 TRACE_EVENT0("cc", "PrioritizedResourceManager::pushTexturePrioritiesToBackings");
134 DCHECK(m_proxy
->isImplThread() && m_proxy
->isMainThreadBlocked());
137 for (BackingList::iterator it
= m_backings
.begin(); it
!= m_backings
.end(); ++it
)
138 (*it
)->updatePriority();
142 // Push memory requirements to the impl thread structure.
143 m_memoryVisibleLastPushedBytes
= m_memoryVisibleBytes
;
144 m_memoryVisibleAndNearbyLastPushedBytes
= m_memoryVisibleAndNearbyBytes
;
147 void PrioritizedResourceManager::updateBackingsInDrawingImplTree()
149 TRACE_EVENT0("cc", "PrioritizedResourceManager::updateBackingsInDrawingImplTree");
150 DCHECK(m_proxy
->isImplThread() && m_proxy
->isMainThreadBlocked());
153 for (BackingList::iterator it
= m_backings
.begin(); it
!= m_backings
.end(); ++it
) {
154 PrioritizedResource::Backing
* backing
= (*it
);
155 backing
->updateInDrawingImplTree();
161 void PrioritizedResourceManager::sortBackings()
163 TRACE_EVENT0("cc", "PrioritizedResourceManager::sortBackings");
164 DCHECK(m_proxy
->isImplThread());
166 // Put backings in eviction/recycling order.
167 m_backings
.sort(compareBackings
);
168 m_backingsTailNotSorted
= false;
171 void PrioritizedResourceManager::clearPriorities()
173 DCHECK(m_proxy
->isMainThread());
174 for (TextureSet::iterator it
= m_textures
.begin(); it
!= m_textures
.end(); ++it
) {
175 // FIXME: We should remove this and just set all priorities to
176 // PriorityCalculator::lowestPriority() once we have priorities
177 // for all textures (we can't currently calculate distances for
178 // off-screen textures).
179 (*it
)->setRequestPriority(PriorityCalculator::lingeringPriority((*it
)->requestPriority()));
183 bool PrioritizedResourceManager::requestLate(PrioritizedResource
* texture
)
185 DCHECK(m_proxy
->isMainThread());
187 // This is already above cutoff, so don't double count it's memory below.
188 if (texture
->isAbovePriorityCutoff())
191 // Allow textures that have priority equal to the cutoff, but not strictly lower.
192 if (PriorityCalculator::priorityIsLower(texture
->requestPriority(), m_priorityCutoff
))
195 // Disallow textures that do not have a priority strictly higher than the external cutoff.
196 if (!PriorityCalculator::priorityIsHigher(texture
->requestPriority(), m_externalPriorityCutoff
))
199 size_t newMemoryBytes
= m_memoryAboveCutoffBytes
+ texture
->bytes();
200 if (newMemoryBytes
> m_memoryAvailableBytes
)
203 m_memoryAboveCutoffBytes
= newMemoryBytes
;
204 texture
->setAbovePriorityCutoff(true);
208 void PrioritizedResourceManager::acquireBackingTextureIfNeeded(PrioritizedResource
* texture
, ResourceProvider
* resourceProvider
)
210 DCHECK(m_proxy
->isImplThread() && m_proxy
->isMainThreadBlocked());
211 DCHECK(!texture
->isSelfManaged());
212 DCHECK(texture
->isAbovePriorityCutoff());
213 if (texture
->backing() || !texture
->isAbovePriorityCutoff())
216 // Find a backing below, by either recycling or allocating.
217 PrioritizedResource::Backing
* backing
= 0;
219 // First try to recycle
220 for (BackingList::iterator it
= m_backings
.begin(); it
!= m_backings
.end(); ++it
) {
221 if (!(*it
)->canBeRecycled())
223 if (resourceProvider
->inUseByConsumer((*it
)->id()))
225 if ((*it
)->size() == texture
->size() && (*it
)->format() == texture
->format()) {
227 m_backings
.erase(it
);
232 // Otherwise reduce memory and just allocate a new backing texures.
234 evictBackingsToReduceMemory(m_memoryAvailableBytes
- texture
->bytes(),
235 PriorityCalculator::allowEverythingCutoff(),
239 backing
= createBacking(texture
->size(), texture
->format(), resourceProvider
);
242 // Move the used backing to the end of the eviction list, and note that
243 // the tail is not sorted.
244 if (backing
->owner())
245 backing
->owner()->unlink();
246 texture
->link(backing
);
247 m_backings
.push_back(backing
);
248 m_backingsTailNotSorted
= true;
250 // Update the backing's priority from its new owner.
251 backing
->updatePriority();
254 bool PrioritizedResourceManager::evictBackingsToReduceMemory(size_t limitBytes
,
256 EvictionPolicy evictionPolicy
,
257 UnlinkPolicy unlinkPolicy
,
258 ResourceProvider
* resourceProvider
)
260 DCHECK(m_proxy
->isImplThread());
261 if (unlinkPolicy
== UnlinkBackings
)
262 DCHECK(m_proxy
->isMainThreadBlocked());
263 if (memoryUseBytes() <= limitBytes
&& PriorityCalculator::allowEverythingCutoff() == priorityCutoff
)
266 // Destroy backings until we are below the limit,
267 // or until all backings remaining are above the cutoff.
268 while (m_backings
.size() > 0) {
269 PrioritizedResource::Backing
* backing
= m_backings
.front();
270 if (memoryUseBytes() <= limitBytes
&&
271 PriorityCalculator::priorityIsHigher(backing
->requestPriorityAtLastPriorityUpdate(), priorityCutoff
))
273 if (evictionPolicy
== EvictOnlyRecyclable
&& !backing
->canBeRecycled())
275 if (unlinkPolicy
== UnlinkBackings
&& backing
->owner())
276 backing
->owner()->unlink();
277 evictFirstBackingResource(resourceProvider
);
282 void PrioritizedResourceManager::reduceMemory(ResourceProvider
* resourceProvider
)
284 DCHECK(m_proxy
->isImplThread() && m_proxy
->isMainThreadBlocked());
285 evictBackingsToReduceMemory(m_memoryAvailableBytes
,
286 PriorityCalculator::allowEverythingCutoff(),
290 DCHECK(memoryUseBytes() <= m_memoryAvailableBytes
);
292 // We currently collect backings from deleted textures for later recycling.
293 // However, if we do that forever we will always use the max limit even if
294 // we really need very little memory. This should probably be solved by reducing the
295 // limit externally, but until then this just does some "clean up" of unused
296 // backing textures (any more than 10%).
297 size_t wastedMemory
= 0;
298 for (BackingList::iterator it
= m_backings
.begin(); it
!= m_backings
.end(); ++it
) {
301 wastedMemory
+= (*it
)->bytes();
303 size_t tenPercentOfMemory
= m_memoryAvailableBytes
/ 10;
304 if (wastedMemory
> tenPercentOfMemory
)
305 evictBackingsToReduceMemory(memoryUseBytes() - (wastedMemory
- tenPercentOfMemory
),
306 PriorityCalculator::allowEverythingCutoff(),
312 void PrioritizedResourceManager::clearAllMemory(ResourceProvider
* resourceProvider
)
314 DCHECK(m_proxy
->isImplThread() && m_proxy
->isMainThreadBlocked());
315 if (!resourceProvider
) {
316 DCHECK(m_backings
.empty());
319 evictBackingsToReduceMemory(0,
320 PriorityCalculator::allowEverythingCutoff(),
326 bool PrioritizedResourceManager::reduceMemoryOnImplThread(size_t limitBytes
, int priorityCutoff
, ResourceProvider
* resourceProvider
)
328 DCHECK(m_proxy
->isImplThread());
329 DCHECK(resourceProvider
);
330 // If we are in the process of uploading a new frame then the backings at the very end of
331 // the list are not sorted by priority. Sort them before doing the eviction.
332 if (m_backingsTailNotSorted
)
334 return evictBackingsToReduceMemory(limitBytes
,
341 void PrioritizedResourceManager::unlinkAndClearEvictedBackings()
343 DCHECK(m_proxy
->isMainThread());
344 base::AutoLock
scoped_lock(m_evictedBackingsLock
);
345 for (BackingList::const_iterator it
= m_evictedBackings
.begin(); it
!= m_evictedBackings
.end(); ++it
) {
346 PrioritizedResource::Backing
* backing
= (*it
);
347 if (backing
->owner())
348 backing
->owner()->unlink();
351 m_evictedBackings
.clear();
354 bool PrioritizedResourceManager::linkedEvictedBackingsExist() const
356 DCHECK(m_proxy
->isImplThread() && m_proxy
->isMainThreadBlocked());
357 base::AutoLock
scoped_lock(m_evictedBackingsLock
);
358 for (BackingList::const_iterator it
= m_evictedBackings
.begin(); it
!= m_evictedBackings
.end(); ++it
) {
365 void PrioritizedResourceManager::registerTexture(PrioritizedResource
* texture
)
367 DCHECK(m_proxy
->isMainThread());
369 DCHECK(!texture
->resourceManager());
370 DCHECK(!texture
->backing());
371 DCHECK(!ContainsKey(m_textures
, texture
));
373 texture
->setManagerInternal(this);
374 m_textures
.insert(texture
);
378 void PrioritizedResourceManager::unregisterTexture(PrioritizedResource
* texture
)
380 DCHECK(m_proxy
->isMainThread() || (m_proxy
->isImplThread() && m_proxy
->isMainThreadBlocked()));
382 DCHECK(ContainsKey(m_textures
, texture
));
384 returnBackingTexture(texture
);
385 texture
->setManagerInternal(0);
386 m_textures
.erase(texture
);
387 texture
->setAbovePriorityCutoff(false);
390 void PrioritizedResourceManager::returnBackingTexture(PrioritizedResource
* texture
)
392 DCHECK(m_proxy
->isMainThread() || (m_proxy
->isImplThread() && m_proxy
->isMainThreadBlocked()));
393 if (texture
->backing())
397 PrioritizedResource::Backing
* PrioritizedResourceManager::createBacking(gfx::Size size
, GLenum format
, ResourceProvider
* resourceProvider
)
399 DCHECK(m_proxy
->isImplThread() && m_proxy
->isMainThreadBlocked());
400 DCHECK(resourceProvider
);
401 ResourceProvider::ResourceId resourceId
= resourceProvider
->createManagedResource(size
, format
, ResourceProvider::TextureUsageAny
);
402 PrioritizedResource::Backing
* backing
= new PrioritizedResource::Backing(resourceId
, resourceProvider
, size
, format
);
403 m_memoryUseBytes
+= backing
->bytes();
407 void PrioritizedResourceManager::evictFirstBackingResource(ResourceProvider
* resourceProvider
)
409 DCHECK(m_proxy
->isImplThread());
410 DCHECK(resourceProvider
);
411 DCHECK(!m_backings
.empty());
412 PrioritizedResource::Backing
* backing
= m_backings
.front();
414 // Note that we create a backing and its resource at the same time, but we
415 // delete the backing structure and its resource in two steps. This is because
416 // we can delete the resource while the main thread is running, but we cannot
417 // unlink backings while the main thread is running.
418 backing
->deleteResource(resourceProvider
);
419 m_memoryUseBytes
-= backing
->bytes();
420 m_backings
.pop_front();
421 base::AutoLock
scoped_lock(m_evictedBackingsLock
);
422 m_evictedBackings
.push_back(backing
);
425 void PrioritizedResourceManager::assertInvariants()
428 DCHECK(m_proxy
->isImplThread() && m_proxy
->isMainThreadBlocked());
430 // If we hit any of these asserts, there is a bug in this class. To see
431 // where the bug is, call this function at the beginning and end of
432 // every public function.
434 // Backings/textures must be doubly-linked and only to other backings/textures in this manager.
435 for (BackingList::iterator it
= m_backings
.begin(); it
!= m_backings
.end(); ++it
) {
436 if ((*it
)->owner()) {
437 DCHECK(ContainsKey(m_textures
, (*it
)->owner()));
438 DCHECK((*it
)->owner()->backing() == (*it
));
441 for (TextureSet::iterator it
= m_textures
.begin(); it
!= m_textures
.end(); ++it
) {
442 PrioritizedResource
* texture
= (*it
);
443 PrioritizedResource::Backing
* backing
= texture
->backing();
444 base::AutoLock
scoped_lock(m_evictedBackingsLock
);
446 if (backing
->resourceHasBeenDeleted()) {
447 DCHECK(std::find(m_backings
.begin(), m_backings
.end(), backing
) == m_backings
.end());
448 DCHECK(std::find(m_evictedBackings
.begin(), m_evictedBackings
.end(), backing
) != m_evictedBackings
.end());
450 DCHECK(std::find(m_backings
.begin(), m_backings
.end(), backing
) != m_backings
.end());
451 DCHECK(std::find(m_evictedBackings
.begin(), m_evictedBackings
.end(), backing
) == m_evictedBackings
.end());
453 DCHECK(backing
->owner() == texture
);
457 // At all times, backings that can be evicted must always come before
458 // backings that can't be evicted in the backing texture list (otherwise
459 // reduceMemory will not find all textures available for eviction/recycling).
460 bool reachedUnrecyclable
= false;
461 PrioritizedResource::Backing
* previous_backing
= NULL
;
462 for (BackingList::iterator it
= m_backings
.begin(); it
!= m_backings
.end(); ++it
) {
463 PrioritizedResource::Backing
* backing
= *it
;
464 if (previous_backing
&& (!m_backingsTailNotSorted
|| !backing
->wasAbovePriorityCutoffAtLastPriorityUpdate()))
465 DCHECK(compareBackings(previous_backing
, backing
));
466 if (!backing
->canBeRecycled())
467 reachedUnrecyclable
= true;
468 if (reachedUnrecyclable
)
469 DCHECK(!backing
->canBeRecycled());
471 DCHECK(backing
->canBeRecycled());
472 previous_backing
= backing
;
477 const Proxy
* PrioritizedResourceManager::proxyForDebug() const