summaryrefslogtreecommitdiffstats
path: root/cc/prioritized_texture_manager.cc
diff options
context:
space:
mode:
Diffstat (limited to 'cc/prioritized_texture_manager.cc')
-rw-r--r--cc/prioritized_texture_manager.cc410
1 files changed, 410 insertions, 0 deletions
diff --git a/cc/prioritized_texture_manager.cc b/cc/prioritized_texture_manager.cc
new file mode 100644
index 0000000..a609548
--- /dev/null
+++ b/cc/prioritized_texture_manager.cc
@@ -0,0 +1,410 @@
+// Copyright 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "config.h"
+
+#include "CCPrioritizedTextureManager.h"
+
+#include "base/stl_util.h"
+#include "CCPrioritizedTexture.h"
+#include "CCPriorityCalculator.h"
+#include "CCProxy.h"
+#include "TraceEvent.h"
+#include <algorithm>
+
+using namespace std;
+
+namespace cc {
+
+CCPrioritizedTextureManager::CCPrioritizedTextureManager(size_t maxMemoryLimitBytes, int, int pool)
+ : m_maxMemoryLimitBytes(maxMemoryLimitBytes)
+ , m_memoryUseBytes(0)
+ , m_memoryAboveCutoffBytes(0)
+ , m_memoryAvailableBytes(0)
+ , m_pool(pool)
+ , m_needsUpdateBackingsPrioritites(false)
+{
+}
+
+CCPrioritizedTextureManager::~CCPrioritizedTextureManager()
+{
+ while (m_textures.size() > 0)
+ unregisterTexture(*m_textures.begin());
+
+ deleteEvictedBackings();
+
+ // Each remaining backing is a leaked opengl texture. There should be none.
+ ASSERT(m_backings.isEmpty());
+}
+
+void CCPrioritizedTextureManager::prioritizeTextures()
+{
+ TRACE_EVENT0("cc", "CCPrioritizedTextureManager::prioritizeTextures");
+ ASSERT(CCProxy::isMainThread());
+
+ // Sorting textures in this function could be replaced by a slightly
+ // modified O(n) quick-select to partition textures rather than
+ // sort them (if performance of the sort becomes an issue).
+
+ TextureVector& sortedTextures = m_tempTextureVector;
+ sortedTextures.clear();
+
+ // Copy all textures into a vector and sort them.
+ for (TextureSet::iterator it = m_textures.begin(); it != m_textures.end(); ++it)
+ sortedTextures.append(*it);
+ std::sort(sortedTextures.begin(), sortedTextures.end(), compareTextures);
+
+ m_memoryAvailableBytes = m_maxMemoryLimitBytes;
+ m_priorityCutoff = CCPriorityCalculator::lowestPriority();
+ size_t memoryBytes = 0;
+ for (TextureVector::iterator it = sortedTextures.begin(); it != sortedTextures.end(); ++it) {
+ if ((*it)->requestPriority() == CCPriorityCalculator::lowestPriority())
+ break;
+
+ if ((*it)->isSelfManaged()) {
+ // Account for self-managed memory immediately by reducing the memory
+ // available (since it never gets acquired).
+ size_t newMemoryBytes = memoryBytes + (*it)->bytes();
+ if (newMemoryBytes > m_memoryAvailableBytes) {
+ m_priorityCutoff = (*it)->requestPriority();
+ m_memoryAvailableBytes = memoryBytes;
+ break;
+ }
+ m_memoryAvailableBytes -= (*it)->bytes();
+ } else {
+ size_t newMemoryBytes = memoryBytes + (*it)->bytes();
+ if (newMemoryBytes > m_memoryAvailableBytes) {
+ m_priorityCutoff = (*it)->requestPriority();
+ break;
+ }
+ memoryBytes = newMemoryBytes;
+ }
+ }
+
+ // Only allow textures if they are higher than the cutoff. All textures
+ // of the same priority are accepted or rejected together, rather than
+ // being partially allowed randomly.
+ m_memoryAboveCutoffBytes = 0;
+ for (TextureVector::iterator it = sortedTextures.begin(); it != sortedTextures.end(); ++it) {
+ bool isAbovePriorityCutoff = CCPriorityCalculator::priorityIsHigher((*it)->requestPriority(), m_priorityCutoff);
+ (*it)->setAbovePriorityCutoff(isAbovePriorityCutoff);
+ if (isAbovePriorityCutoff && !(*it)->isSelfManaged())
+ m_memoryAboveCutoffBytes += (*it)->bytes();
+ }
+ sortedTextures.clear();
+
+ m_needsUpdateBackingsPrioritites = true;
+
+ ASSERT(m_memoryAboveCutoffBytes <= m_memoryAvailableBytes);
+ ASSERT(memoryAboveCutoffBytes() <= maxMemoryLimitBytes());
+}
+
+void CCPrioritizedTextureManager::updateBackingsPriorities()
+{
+ TRACE_EVENT0("cc", "CCPrioritizedTextureManager::updateBackingsPriorities");
+ ASSERT(CCProxy::isImplThread() && CCProxy::isMainThreadBlocked());
+
+ if (!m_needsUpdateBackingsPrioritites)
+ return;
+
+#if !ASSERT_DISABLED
+ assertInvariants();
+#endif
+
+ // Update backings' priorities and put backings in eviction/recycling order.
+ BackingVector& sortedBackings = m_tempBackingVector;
+ sortedBackings.clear();
+ for (BackingSet::iterator it = m_backings.begin(); it != m_backings.end(); ++it) {
+ (*it)->updatePriority();
+ sortedBackings.append(*it);
+ }
+ std::sort(sortedBackings.begin(), sortedBackings.end(), compareBackings);
+
+ for (BackingVector::iterator it = sortedBackings.begin(); it != sortedBackings.end(); ++it) {
+ m_backings.remove(*it);
+ m_backings.add(*it);
+ }
+ sortedBackings.clear();
+ m_needsUpdateBackingsPrioritites = false;
+
+#if !ASSERT_DISABLED
+ assertInvariants();
+#endif
+}
+
+void CCPrioritizedTextureManager::clearPriorities()
+{
+ ASSERT(CCProxy::isMainThread());
+ for (TextureSet::iterator it = m_textures.begin(); it != m_textures.end(); ++it) {
+ // FIXME: We should remove this and just set all priorities to
+ // CCPriorityCalculator::lowestPriority() once we have priorities
+ // for all textures (we can't currently calculate distances for
+ // off-screen textures).
+ (*it)->setRequestPriority(CCPriorityCalculator::lingeringPriority((*it)->requestPriority()));
+ }
+}
+
+bool CCPrioritizedTextureManager::requestLate(CCPrioritizedTexture* texture)
+{
+ ASSERT(CCProxy::isMainThread());
+
+ // This is already above cutoff, so don't double count it's memory below.
+ if (texture->isAbovePriorityCutoff())
+ return true;
+
+ if (CCPriorityCalculator::priorityIsLower(texture->requestPriority(), m_priorityCutoff))
+ return false;
+
+ size_t newMemoryBytes = m_memoryAboveCutoffBytes + texture->bytes();
+ if (newMemoryBytes > m_memoryAvailableBytes)
+ return false;
+
+ m_memoryAboveCutoffBytes = newMemoryBytes;
+ texture->setAbovePriorityCutoff(true);
+ m_needsUpdateBackingsPrioritites = true;
+ return true;
+}
+
+void CCPrioritizedTextureManager::acquireBackingTextureIfNeeded(CCPrioritizedTexture* texture, CCResourceProvider* resourceProvider)
+{
+ ASSERT(CCProxy::isImplThread() && CCProxy::isMainThreadBlocked());
+ ASSERT(!texture->isSelfManaged());
+ ASSERT(texture->isAbovePriorityCutoff());
+ if (texture->backing() || !texture->isAbovePriorityCutoff())
+ return;
+
+ // Make sure that the backings list is up to date and sorted before traversing it.
+ updateBackingsPriorities();
+
+ // Find a backing below, by either recycling or allocating.
+ CCPrioritizedTexture::Backing* backing = 0;
+
+ // First try to recycle
+ for (BackingSet::iterator it = m_backings.begin(); it != m_backings.end(); ++it) {
+ if ((*it)->hadOwnerAtLastPriorityUpdate() && (*it)->wasAbovePriorityCutoffAtLastPriorityUpdate())
+ break;
+ if ((*it)->size() == texture->size() && (*it)->format() == texture->format()) {
+ backing = (*it);
+ break;
+ }
+ }
+
+ // Otherwise reduce memory and just allocate a new backing texures.
+ if (!backing) {
+ evictBackingsToReduceMemory(m_memoryAvailableBytes - texture->bytes(), RespectManagerPriorityCutoff, resourceProvider);
+ backing = createBacking(texture->size(), texture->format(), resourceProvider);
+ }
+
+ // Move the used backing texture to the end of the eviction list.
+ if (backing->owner())
+ backing->owner()->unlink();
+ texture->link(backing);
+ m_backings.remove(backing);
+ m_backings.add(backing);
+
+ // Update the backing's priority from its new owner.
+ backing->updatePriority();
+}
+
+void CCPrioritizedTextureManager::evictBackingsToReduceMemory(size_t limitBytes, EvictionPriorityPolicy evictionPolicy, CCResourceProvider* resourceProvider)
+{
+ ASSERT(CCProxy::isImplThread());
+ if (memoryUseBytes() <= limitBytes)
+ return;
+
+ // Destroy backings until we are below the limit,
+ // or until all backings remaining are above the cutoff.
+ while (memoryUseBytes() > limitBytes && m_backings.size() > 0) {
+ CCPrioritizedTexture::Backing* backing = *m_backings.begin();
+ if (evictionPolicy == RespectManagerPriorityCutoff)
+ if (backing->hadOwnerAtLastPriorityUpdate() && backing->wasAbovePriorityCutoffAtLastPriorityUpdate())
+ break;
+ evictBackingResource(backing, resourceProvider);
+ }
+}
+
+void CCPrioritizedTextureManager::reduceMemory(CCResourceProvider* resourceProvider)
+{
+ ASSERT(CCProxy::isImplThread() && CCProxy::isMainThreadBlocked());
+
+ // Make sure that the backings list is up to date and sorted before traversing it.
+ updateBackingsPriorities();
+
+ evictBackingsToReduceMemory(m_memoryAvailableBytes, RespectManagerPriorityCutoff, resourceProvider);
+ ASSERT(memoryUseBytes() <= maxMemoryLimitBytes());
+
+ // We currently collect backings from deleted textures for later recycling.
+ // However, if we do that forever we will always use the max limit even if
+ // we really need very little memory. This should probably be solved by reducing the
+ // limit externally, but until then this just does some "clean up" of unused
+ // backing textures (any more than 10%).
+ size_t wastedMemory = 0;
+ for (BackingSet::iterator it = m_backings.begin(); it != m_backings.end(); ++it) {
+ if ((*it)->owner())
+ break;
+ wastedMemory += (*it)->bytes();
+ }
+ size_t tenPercentOfMemory = m_memoryAvailableBytes / 10;
+ if (wastedMemory > tenPercentOfMemory)
+ evictBackingsToReduceMemory(memoryUseBytes() - (wastedMemory - tenPercentOfMemory), RespectManagerPriorityCutoff, resourceProvider);
+
+ deleteEvictedBackings();
+}
+
+void CCPrioritizedTextureManager::clearAllMemory(CCResourceProvider* resourceProvider)
+{
+ ASSERT(CCProxy::isImplThread() && CCProxy::isMainThreadBlocked());
+ ASSERT(resourceProvider);
+ evictBackingsToReduceMemory(0, DoNotRespectManagerPriorityCutoff, resourceProvider);
+ deleteEvictedBackings();
+}
+
+void CCPrioritizedTextureManager::reduceMemoryOnImplThread(size_t limitBytes, CCResourceProvider* resourceProvider)
+{
+ ASSERT(CCProxy::isImplThread());
+ ASSERT(resourceProvider);
+ evictBackingsToReduceMemory(limitBytes, DoNotRespectManagerPriorityCutoff, resourceProvider);
+}
+
+void CCPrioritizedTextureManager::getEvictedBackings(BackingVector& evictedBackings)
+{
+ ASSERT(CCProxy::isImplThread());
+ evictedBackings.clear();
+ evictedBackings.append(m_evictedBackings);
+}
+
+void CCPrioritizedTextureManager::unlinkEvictedBackings(const BackingVector& evictedBackings)
+{
+ ASSERT(CCProxy::isMainThread());
+ for (BackingVector::const_iterator it = evictedBackings.begin(); it != evictedBackings.end(); ++it) {
+ CCPrioritizedTexture::Backing* backing = (*it);
+ if (backing->owner())
+ backing->owner()->unlink();
+ }
+}
+
+bool CCPrioritizedTextureManager::deleteEvictedBackings()
+{
+ ASSERT(CCProxy::isMainThread() || (CCProxy::isImplThread() && CCProxy::isMainThreadBlocked()));
+ bool linkedEvictedBackingsExisted = false;
+ for (BackingVector::const_iterator it = m_evictedBackings.begin(); it != m_evictedBackings.end(); ++it) {
+ CCPrioritizedTexture::Backing* backing = (*it);
+ if (backing->owner()) {
+ linkedEvictedBackingsExisted = true;
+ backing->owner()->unlink();
+ }
+ delete backing;
+ }
+ m_evictedBackings.clear();
+ return linkedEvictedBackingsExisted;
+}
+
+void CCPrioritizedTextureManager::registerTexture(CCPrioritizedTexture* texture)
+{
+ ASSERT(CCProxy::isMainThread());
+ ASSERT(texture);
+ ASSERT(!texture->textureManager());
+ ASSERT(!texture->backing());
+ ASSERT(!ContainsKey(m_textures, texture));
+
+ texture->setManagerInternal(this);
+ m_textures.insert(texture);
+
+}
+
+void CCPrioritizedTextureManager::unregisterTexture(CCPrioritizedTexture* texture)
+{
+ ASSERT(CCProxy::isMainThread() || (CCProxy::isImplThread() && CCProxy::isMainThreadBlocked()));
+ ASSERT(texture);
+ ASSERT(ContainsKey(m_textures, texture));
+
+ returnBackingTexture(texture);
+ texture->setManagerInternal(0);
+ m_textures.erase(texture);
+ texture->setAbovePriorityCutoff(false);
+}
+
+void CCPrioritizedTextureManager::returnBackingTexture(CCPrioritizedTexture* texture)
+{
+ ASSERT(CCProxy::isMainThread() || (CCProxy::isImplThread() && CCProxy::isMainThreadBlocked()));
+ if (texture->backing()) {
+ texture->unlink();
+ m_needsUpdateBackingsPrioritites = true;
+ }
+}
+
+CCPrioritizedTexture::Backing* CCPrioritizedTextureManager::createBacking(IntSize size, GC3Denum format, CCResourceProvider* resourceProvider)
+{
+ ASSERT(CCProxy::isImplThread() && CCProxy::isMainThreadBlocked());
+ ASSERT(resourceProvider);
+ CCResourceProvider::ResourceId resourceId = resourceProvider->createResource(m_pool, size, format, CCResourceProvider::TextureUsageAny);
+ CCPrioritizedTexture::Backing* backing = new CCPrioritizedTexture::Backing(resourceId, resourceProvider, size, format);
+ m_memoryUseBytes += backing->bytes();
+ // Put backing texture at the front for eviction, since it isn't in use yet.
+ m_backings.insertBefore(m_backings.begin(), backing);
+ return backing;
+}
+
+void CCPrioritizedTextureManager::evictBackingResource(CCPrioritizedTexture::Backing* backing, CCResourceProvider* resourceProvider)
+{
+ ASSERT(CCProxy::isImplThread());
+ ASSERT(backing);
+ ASSERT(resourceProvider);
+ ASSERT(m_backings.find(backing) != m_backings.end());
+
+ // Note that we create a backing and its resource at the same time, but we
+ // delete the backing structure and its resource in two steps. This is because
+ // we can delete the resource while the main thread is running, but we cannot
+ // unlink backings while the main thread is running.
+ backing->deleteResource(resourceProvider);
+ m_memoryUseBytes -= backing->bytes();
+ m_backings.remove(backing);
+ m_evictedBackings.append(backing);
+}
+
+#if !ASSERT_DISABLED
+void CCPrioritizedTextureManager::assertInvariants()
+{
+ ASSERT(CCProxy::isImplThread() && CCProxy::isMainThreadBlocked());
+
+ // If we hit any of these asserts, there is a bug in this class. To see
+ // where the bug is, call this function at the beginning and end of
+ // every public function.
+
+ // Backings/textures must be doubly-linked and only to other backings/textures in this manager.
+ for (BackingSet::iterator it = m_backings.begin(); it != m_backings.end(); ++it) {
+ if ((*it)->owner()) {
+ ASSERT(ContainsKey(m_textures, (*it)->owner()));
+ ASSERT((*it)->owner()->backing() == (*it));
+ }
+ }
+ for (TextureSet::iterator it = m_textures.begin(); it != m_textures.end(); ++it) {
+ if ((*it)->backing()) {
+ ASSERT(m_backings.find((*it)->backing()) != m_backings.end());
+ ASSERT((*it)->backing()->owner() == (*it));
+ }
+ }
+
+ // At all times, backings that can be evicted must always come before
+ // backings that can't be evicted in the backing texture list (otherwise
+ // reduceMemory will not find all textures available for eviction/recycling).
+ bool reachedOwned = false;
+ bool reachedAboveCutoff = false;
+ for (BackingSet::iterator it = m_backings.begin(); it != m_backings.end(); ++it) {
+ if ((*it)->hadOwnerAtLastPriorityUpdate())
+ reachedOwned = true;
+ if ((*it)->wasAbovePriorityCutoffAtLastPriorityUpdate())
+ reachedAboveCutoff = true;
+ if (reachedOwned)
+ ASSERT((*it)->hadOwnerAtLastPriorityUpdate());
+ if (reachedAboveCutoff) {
+ ASSERT((*it)->hadOwnerAtLastPriorityUpdate() && (*it)->wasAbovePriorityCutoffAtLastPriorityUpdate());
+ ASSERT(reachedOwned);
+ }
+ }
+}
+#endif
+
+
+} // namespace cc