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.
7 #include "CCPrioritizedTextureManager.h"
9 #include "base/stl_util.h"
10 #include "CCPrioritizedTexture.h"
11 #include "CCPriorityCalculator.h"
13 #include "TraceEvent.h"
20 CCPrioritizedTextureManager::CCPrioritizedTextureManager(size_t maxMemoryLimitBytes
, int, int pool
)
21 : m_maxMemoryLimitBytes(maxMemoryLimitBytes
)
23 , m_memoryAboveCutoffBytes(0)
24 , m_memoryAvailableBytes(0)
29 CCPrioritizedTextureManager::~CCPrioritizedTextureManager()
31 while (m_textures
.size() > 0)
32 unregisterTexture(*m_textures
.begin());
34 deleteUnlinkedEvictedBackings();
35 ASSERT(m_evictedBackings
.isEmpty());
37 // Each remaining backing is a leaked opengl texture. There should be none.
38 ASSERT(m_backings
.isEmpty());
41 void CCPrioritizedTextureManager::prioritizeTextures()
43 TRACE_EVENT0("cc", "CCPrioritizedTextureManager::prioritizeTextures");
44 ASSERT(CCProxy::isMainThread());
46 // Sorting textures in this function could be replaced by a slightly
47 // modified O(n) quick-select to partition textures rather than
48 // sort them (if performance of the sort becomes an issue).
50 TextureVector
& sortedTextures
= m_tempTextureVector
;
51 sortedTextures
.clear();
53 // Copy all textures into a vector and sort them.
54 for (TextureSet::iterator it
= m_textures
.begin(); it
!= m_textures
.end(); ++it
)
55 sortedTextures
.append(*it
);
56 std::sort(sortedTextures
.begin(), sortedTextures
.end(), compareTextures
);
58 m_memoryAvailableBytes
= m_maxMemoryLimitBytes
;
59 m_priorityCutoff
= CCPriorityCalculator::lowestPriority();
60 size_t memoryBytes
= 0;
61 for (TextureVector::iterator it
= sortedTextures
.begin(); it
!= sortedTextures
.end(); ++it
) {
62 if ((*it
)->requestPriority() == CCPriorityCalculator::lowestPriority())
65 if ((*it
)->isSelfManaged()) {
66 // Account for self-managed memory immediately by reducing the memory
67 // available (since it never gets acquired).
68 size_t newMemoryBytes
= memoryBytes
+ (*it
)->bytes();
69 if (newMemoryBytes
> m_memoryAvailableBytes
) {
70 m_priorityCutoff
= (*it
)->requestPriority();
71 m_memoryAvailableBytes
= memoryBytes
;
74 m_memoryAvailableBytes
-= (*it
)->bytes();
76 size_t newMemoryBytes
= memoryBytes
+ (*it
)->bytes();
77 if (newMemoryBytes
> m_memoryAvailableBytes
) {
78 m_priorityCutoff
= (*it
)->requestPriority();
81 memoryBytes
= newMemoryBytes
;
85 // Only allow textures if they are higher than the cutoff. All textures
86 // of the same priority are accepted or rejected together, rather than
87 // being partially allowed randomly.
88 m_memoryAboveCutoffBytes
= 0;
89 for (TextureVector::iterator it
= sortedTextures
.begin(); it
!= sortedTextures
.end(); ++it
) {
90 bool isAbovePriorityCutoff
= CCPriorityCalculator::priorityIsHigher((*it
)->requestPriority(), m_priorityCutoff
);
91 (*it
)->setAbovePriorityCutoff(isAbovePriorityCutoff
);
92 if (isAbovePriorityCutoff
&& !(*it
)->isSelfManaged())
93 m_memoryAboveCutoffBytes
+= (*it
)->bytes();
95 sortedTextures
.clear();
97 ASSERT(m_memoryAboveCutoffBytes
<= m_memoryAvailableBytes
);
98 ASSERT(memoryAboveCutoffBytes() <= maxMemoryLimitBytes());
101 void CCPrioritizedTextureManager::pushTexturePrioritiesToBackings()
103 TRACE_EVENT0("cc", "CCPrioritizedTextureManager::pushTexturePrioritiesToBackings");
104 ASSERT(CCProxy::isImplThread() && CCProxy::isMainThreadBlocked());
106 for (BackingSet::iterator it
= m_backings
.begin(); it
!= m_backings
.end(); ++it
)
107 (*it
)->updatePriority();
112 void CCPrioritizedTextureManager::updateBackingsInDrawingImplTree()
114 TRACE_EVENT0("cc", "CCPrioritizedTextureManager::updateBackingsInDrawingImplTree");
115 ASSERT(CCProxy::isImplThread() && CCProxy::isMainThreadBlocked());
117 for (BackingSet::iterator it
= m_backings
.begin(); it
!= m_backings
.end(); ++it
) {
118 CCPrioritizedTexture::Backing
* backing
= (*it
);
119 backing
->updateInDrawingImplTree();
125 void CCPrioritizedTextureManager::sortBackings()
127 TRACE_EVENT0("cc", "CCPrioritizedTextureManager::updateBackingsPriorities");
128 ASSERT(CCProxy::isImplThread() && CCProxy::isMainThreadBlocked());
130 // Update backings' priorities and put backings in eviction/recycling order.
131 BackingVector
& sortedBackings
= m_tempBackingVector
;
132 sortedBackings
.clear();
133 for (BackingSet::iterator it
= m_backings
.begin(); it
!= m_backings
.end(); ++it
)
134 sortedBackings
.append(*it
);
135 std::sort(sortedBackings
.begin(), sortedBackings
.end(), compareBackings
);
137 for (BackingVector::iterator it
= sortedBackings
.begin(); it
!= sortedBackings
.end(); ++it
) {
138 m_backings
.remove(*it
);
141 sortedBackings
.clear();
148 void CCPrioritizedTextureManager::clearPriorities()
150 ASSERT(CCProxy::isMainThread());
151 for (TextureSet::iterator it
= m_textures
.begin(); it
!= m_textures
.end(); ++it
) {
152 // FIXME: We should remove this and just set all priorities to
153 // CCPriorityCalculator::lowestPriority() once we have priorities
154 // for all textures (we can't currently calculate distances for
155 // off-screen textures).
156 (*it
)->setRequestPriority(CCPriorityCalculator::lingeringPriority((*it
)->requestPriority()));
160 bool CCPrioritizedTextureManager::requestLate(CCPrioritizedTexture
* texture
)
162 ASSERT(CCProxy::isMainThread());
164 // This is already above cutoff, so don't double count it's memory below.
165 if (texture
->isAbovePriorityCutoff())
168 if (CCPriorityCalculator::priorityIsLower(texture
->requestPriority(), m_priorityCutoff
))
171 size_t newMemoryBytes
= m_memoryAboveCutoffBytes
+ texture
->bytes();
172 if (newMemoryBytes
> m_memoryAvailableBytes
)
175 m_memoryAboveCutoffBytes
= newMemoryBytes
;
176 texture
->setAbovePriorityCutoff(true);
180 void CCPrioritizedTextureManager::acquireBackingTextureIfNeeded(CCPrioritizedTexture
* texture
, CCResourceProvider
* resourceProvider
)
182 ASSERT(CCProxy::isImplThread() && CCProxy::isMainThreadBlocked());
183 ASSERT(!texture
->isSelfManaged());
184 ASSERT(texture
->isAbovePriorityCutoff());
185 if (texture
->backing() || !texture
->isAbovePriorityCutoff())
188 // Find a backing below, by either recycling or allocating.
189 CCPrioritizedTexture::Backing
* backing
= 0;
191 // First try to recycle
192 for (BackingSet::iterator it
= m_backings
.begin(); it
!= m_backings
.end(); ++it
) {
193 if (!(*it
)->canBeRecycled())
195 if ((*it
)->size() == texture
->size() && (*it
)->format() == texture
->format()) {
201 // Otherwise reduce memory and just allocate a new backing texures.
203 evictBackingsToReduceMemory(m_memoryAvailableBytes
- texture
->bytes(), RespectManagerPriorityCutoff
, resourceProvider
);
204 backing
= createBacking(texture
->size(), texture
->format(), resourceProvider
);
207 // Move the used backing texture to the end of the eviction list.
208 if (backing
->owner())
209 backing
->owner()->unlink();
210 texture
->link(backing
);
211 m_backings
.remove(backing
);
212 m_backings
.add(backing
);
214 // Update the backing's priority from its new owner.
215 backing
->updatePriority();
218 void CCPrioritizedTextureManager::evictBackingsToReduceMemory(size_t limitBytes
, EvictionPriorityPolicy evictionPolicy
, CCResourceProvider
* resourceProvider
)
220 ASSERT(CCProxy::isImplThread());
221 if (memoryUseBytes() <= limitBytes
)
224 // Destroy backings until we are below the limit,
225 // or until all backings remaining are above the cutoff.
226 while (memoryUseBytes() > limitBytes
&& m_backings
.size() > 0) {
227 CCPrioritizedTexture::Backing
* backing
= *m_backings
.begin();
228 if (evictionPolicy
== RespectManagerPriorityCutoff
)
229 if (backing
->wasAbovePriorityCutoffAtLastPriorityUpdate())
231 evictBackingResource(backing
, resourceProvider
);
235 void CCPrioritizedTextureManager::reduceMemory(CCResourceProvider
* resourceProvider
)
237 ASSERT(CCProxy::isImplThread() && CCProxy::isMainThreadBlocked());
239 evictBackingsToReduceMemory(m_memoryAvailableBytes
, RespectManagerPriorityCutoff
, resourceProvider
);
240 ASSERT(memoryUseBytes() <= maxMemoryLimitBytes());
242 // We currently collect backings from deleted textures for later recycling.
243 // However, if we do that forever we will always use the max limit even if
244 // we really need very little memory. This should probably be solved by reducing the
245 // limit externally, but until then this just does some "clean up" of unused
246 // backing textures (any more than 10%).
247 size_t wastedMemory
= 0;
248 for (BackingSet::iterator it
= m_backings
.begin(); it
!= m_backings
.end(); ++it
) {
251 wastedMemory
+= (*it
)->bytes();
253 size_t tenPercentOfMemory
= m_memoryAvailableBytes
/ 10;
254 if (wastedMemory
> tenPercentOfMemory
)
255 evictBackingsToReduceMemory(memoryUseBytes() - (wastedMemory
- tenPercentOfMemory
), RespectManagerPriorityCutoff
, resourceProvider
);
257 // Unlink all evicted backings
258 for (BackingVector::const_iterator it
= m_evictedBackings
.begin(); it
!= m_evictedBackings
.end(); ++it
) {
260 (*it
)->owner()->unlink();
263 // And clear the list of evicted backings
264 deleteUnlinkedEvictedBackings();
267 void CCPrioritizedTextureManager::clearAllMemory(CCResourceProvider
* resourceProvider
)
269 ASSERT(CCProxy::isImplThread() && CCProxy::isMainThreadBlocked());
270 ASSERT(resourceProvider
);
271 evictBackingsToReduceMemory(0, DoNotRespectManagerPriorityCutoff
, resourceProvider
);
274 void CCPrioritizedTextureManager::reduceMemoryOnImplThread(size_t limitBytes
, CCResourceProvider
* resourceProvider
)
276 ASSERT(CCProxy::isImplThread());
277 ASSERT(resourceProvider
);
278 evictBackingsToReduceMemory(limitBytes
, DoNotRespectManagerPriorityCutoff
, resourceProvider
);
281 void CCPrioritizedTextureManager::getEvictedBackings(BackingVector
& evictedBackings
)
283 ASSERT(CCProxy::isImplThread());
284 evictedBackings
.clear();
285 evictedBackings
.append(m_evictedBackings
);
288 void CCPrioritizedTextureManager::unlinkEvictedBackings(const BackingVector
& evictedBackings
)
290 ASSERT(CCProxy::isMainThread());
291 for (BackingVector::const_iterator it
= evictedBackings
.begin(); it
!= evictedBackings
.end(); ++it
) {
292 CCPrioritizedTexture::Backing
* backing
= (*it
);
293 if (backing
->owner())
294 backing
->owner()->unlink();
298 void CCPrioritizedTextureManager::deleteUnlinkedEvictedBackings()
300 ASSERT(CCProxy::isMainThread() || (CCProxy::isImplThread() && CCProxy::isMainThreadBlocked()));
301 BackingVector newEvictedBackings
;
302 for (BackingVector::const_iterator it
= m_evictedBackings
.begin(); it
!= m_evictedBackings
.end(); ++it
) {
303 CCPrioritizedTexture::Backing
* backing
= (*it
);
304 if (backing
->owner())
305 newEvictedBackings
.append(backing
);
309 m_evictedBackings
.swap(newEvictedBackings
);
312 bool CCPrioritizedTextureManager::linkedEvictedBackingsExist() const
314 for (BackingVector::const_iterator it
= m_evictedBackings
.begin(); it
!= m_evictedBackings
.end(); ++it
) {
321 void CCPrioritizedTextureManager::registerTexture(CCPrioritizedTexture
* texture
)
323 ASSERT(CCProxy::isMainThread());
325 ASSERT(!texture
->textureManager());
326 ASSERT(!texture
->backing());
327 ASSERT(!ContainsKey(m_textures
, texture
));
329 texture
->setManagerInternal(this);
330 m_textures
.insert(texture
);
334 void CCPrioritizedTextureManager::unregisterTexture(CCPrioritizedTexture
* texture
)
336 ASSERT(CCProxy::isMainThread() || (CCProxy::isImplThread() && CCProxy::isMainThreadBlocked()));
338 ASSERT(ContainsKey(m_textures
, texture
));
340 returnBackingTexture(texture
);
341 texture
->setManagerInternal(0);
342 m_textures
.erase(texture
);
343 texture
->setAbovePriorityCutoff(false);
346 void CCPrioritizedTextureManager::returnBackingTexture(CCPrioritizedTexture
* texture
)
348 ASSERT(CCProxy::isMainThread() || (CCProxy::isImplThread() && CCProxy::isMainThreadBlocked()));
349 if (texture
->backing())
353 CCPrioritizedTexture::Backing
* CCPrioritizedTextureManager::createBacking(IntSize size
, GC3Denum format
, CCResourceProvider
* resourceProvider
)
355 ASSERT(CCProxy::isImplThread() && CCProxy::isMainThreadBlocked());
356 ASSERT(resourceProvider
);
357 CCResourceProvider::ResourceId resourceId
= resourceProvider
->createResource(m_pool
, size
, format
, CCResourceProvider::TextureUsageAny
);
358 CCPrioritizedTexture::Backing
* backing
= new CCPrioritizedTexture::Backing(resourceId
, resourceProvider
, size
, format
);
359 m_memoryUseBytes
+= backing
->bytes();
360 // Put backing texture at the front for eviction, since it isn't in use yet.
361 m_backings
.insertBefore(m_backings
.begin(), backing
);
365 void CCPrioritizedTextureManager::evictBackingResource(CCPrioritizedTexture::Backing
* backing
, CCResourceProvider
* resourceProvider
)
367 ASSERT(CCProxy::isImplThread());
369 ASSERT(resourceProvider
);
370 ASSERT(m_backings
.find(backing
) != m_backings
.end());
372 // Note that we create a backing and its resource at the same time, but we
373 // delete the backing structure and its resource in two steps. This is because
374 // we can delete the resource while the main thread is running, but we cannot
375 // unlink backings while the main thread is running.
376 backing
->deleteResource(resourceProvider
);
377 m_memoryUseBytes
-= backing
->bytes();
378 m_backings
.remove(backing
);
379 m_evictedBackings
.append(backing
);
383 void CCPrioritizedTextureManager::assertInvariants()
385 ASSERT(CCProxy::isImplThread() && CCProxy::isMainThreadBlocked());
387 // If we hit any of these asserts, there is a bug in this class. To see
388 // where the bug is, call this function at the beginning and end of
389 // every public function.
391 // Backings/textures must be doubly-linked and only to other backings/textures in this manager.
392 for (BackingSet::iterator it
= m_backings
.begin(); it
!= m_backings
.end(); ++it
) {
393 if ((*it
)->owner()) {
394 ASSERT(ContainsKey(m_textures
, (*it
)->owner()));
395 ASSERT((*it
)->owner()->backing() == (*it
));
398 for (TextureSet::iterator it
= m_textures
.begin(); it
!= m_textures
.end(); ++it
) {
399 CCPrioritizedTexture
* texture
= (*it
);
400 CCPrioritizedTexture::Backing
* backing
= texture
->backing();
402 if (backing
->resourceHasBeenDeleted()) {
403 ASSERT(m_backings
.find(backing
) == m_backings
.end());
404 ASSERT(m_evictedBackings
.contains(backing
));
406 ASSERT(m_backings
.find(backing
) != m_backings
.end());
407 ASSERT(!m_evictedBackings
.contains(backing
));
409 ASSERT(backing
->owner() == texture
);
413 // At all times, backings that can be evicted must always come before
414 // backings that can't be evicted in the backing texture list (otherwise
415 // reduceMemory will not find all textures available for eviction/recycling).
416 bool reachedUnrecyclable
= false;
417 for (BackingSet::iterator it
= m_backings
.begin(); it
!= m_backings
.end(); ++it
) {
418 if (!(*it
)->canBeRecycled())
419 reachedUnrecyclable
= true;
420 if (reachedUnrecyclable
)
421 ASSERT(!(*it
)->canBeRecycled());
423 ASSERT((*it
)->canBeRecycled());