// 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 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) { } CCPrioritizedTextureManager::~CCPrioritizedTextureManager() { while (m_textures.size() > 0) unregisterTexture(*m_textures.begin()); deleteUnlinkedEvictedBackings(); DCHECK(m_evictedBackings.isEmpty()); // Each remaining backing is a leaked opengl texture. There should be none. DCHECK(m_backings.isEmpty()); } void CCPrioritizedTextureManager::prioritizeTextures() { TRACE_EVENT0("cc", "CCPrioritizedTextureManager::prioritizeTextures"); DCHECK(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(); DCHECK(m_memoryAboveCutoffBytes <= m_memoryAvailableBytes); DCHECK(memoryAboveCutoffBytes() <= maxMemoryLimitBytes()); } void CCPrioritizedTextureManager::pushTexturePrioritiesToBackings() { TRACE_EVENT0("cc", "CCPrioritizedTextureManager::pushTexturePrioritiesToBackings"); DCHECK(CCProxy::isImplThread() && CCProxy::isMainThreadBlocked()); for (BackingSet::iterator it = m_backings.begin(); it != m_backings.end(); ++it) (*it)->updatePriority(); sortBackings(); } void CCPrioritizedTextureManager::updateBackingsInDrawingImplTree() { TRACE_EVENT0("cc", "CCPrioritizedTextureManager::updateBackingsInDrawingImplTree"); DCHECK(CCProxy::isImplThread() && CCProxy::isMainThreadBlocked()); for (BackingSet::iterator it = m_backings.begin(); it != m_backings.end(); ++it) { CCPrioritizedTexture::Backing* backing = (*it); backing->updateInDrawingImplTree(); } sortBackings(); } void CCPrioritizedTextureManager::sortBackings() { TRACE_EVENT0("cc", "CCPrioritizedTextureManager::updateBackingsPriorities"); DCHECK(CCProxy::isImplThread() && CCProxy::isMainThreadBlocked()); // 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) 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(); #if CC_DCHECK_ENABLED() assertInvariants(); #endif } void CCPrioritizedTextureManager::clearPriorities() { DCHECK(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) { DCHECK(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); return true; } void CCPrioritizedTextureManager::acquireBackingTextureIfNeeded(CCPrioritizedTexture* texture, CCResourceProvider* resourceProvider) { DCHECK(CCProxy::isImplThread() && CCProxy::isMainThreadBlocked()); DCHECK(!texture->isSelfManaged()); DCHECK(texture->isAbovePriorityCutoff()); if (texture->backing() || !texture->isAbovePriorityCutoff()) return; // 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)->canBeRecycled()) 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) { DCHECK(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->wasAbovePriorityCutoffAtLastPriorityUpdate()) break; evictBackingResource(backing, resourceProvider); } } void CCPrioritizedTextureManager::reduceMemory(CCResourceProvider* resourceProvider) { DCHECK(CCProxy::isImplThread() && CCProxy::isMainThreadBlocked()); evictBackingsToReduceMemory(m_memoryAvailableBytes, RespectManagerPriorityCutoff, resourceProvider); DCHECK(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); // Unlink all evicted backings for (BackingVector::const_iterator it = m_evictedBackings.begin(); it != m_evictedBackings.end(); ++it) { if ((*it)->owner()) (*it)->owner()->unlink(); } // And clear the list of evicted backings deleteUnlinkedEvictedBackings(); } void CCPrioritizedTextureManager::clearAllMemory(CCResourceProvider* resourceProvider) { DCHECK(CCProxy::isImplThread() && CCProxy::isMainThreadBlocked()); DCHECK(resourceProvider); evictBackingsToReduceMemory(0, DoNotRespectManagerPriorityCutoff, resourceProvider); } void CCPrioritizedTextureManager::reduceMemoryOnImplThread(size_t limitBytes, CCResourceProvider* resourceProvider) { DCHECK(CCProxy::isImplThread()); DCHECK(resourceProvider); evictBackingsToReduceMemory(limitBytes, DoNotRespectManagerPriorityCutoff, resourceProvider); } void CCPrioritizedTextureManager::getEvictedBackings(BackingVector& evictedBackings) { DCHECK(CCProxy::isImplThread()); evictedBackings.clear(); evictedBackings.append(m_evictedBackings); } void CCPrioritizedTextureManager::unlinkEvictedBackings(const BackingVector& evictedBackings) { DCHECK(CCProxy::isMainThread()); for (BackingVector::const_iterator it = evictedBackings.begin(); it != evictedBackings.end(); ++it) { CCPrioritizedTexture::Backing* backing = (*it); if (backing->owner()) backing->owner()->unlink(); } } void CCPrioritizedTextureManager::deleteUnlinkedEvictedBackings() { DCHECK(CCProxy::isMainThread() || (CCProxy::isImplThread() && CCProxy::isMainThreadBlocked())); BackingVector newEvictedBackings; for (BackingVector::const_iterator it = m_evictedBackings.begin(); it != m_evictedBackings.end(); ++it) { CCPrioritizedTexture::Backing* backing = (*it); if (backing->owner()) newEvictedBackings.append(backing); else delete backing; } m_evictedBackings.swap(newEvictedBackings); } bool CCPrioritizedTextureManager::linkedEvictedBackingsExist() const { for (BackingVector::const_iterator it = m_evictedBackings.begin(); it != m_evictedBackings.end(); ++it) { if ((*it)->owner()) return true; } return false; } void CCPrioritizedTextureManager::registerTexture(CCPrioritizedTexture* texture) { DCHECK(CCProxy::isMainThread()); DCHECK(texture); DCHECK(!texture->textureManager()); DCHECK(!texture->backing()); DCHECK(!ContainsKey(m_textures, texture)); texture->setManagerInternal(this); m_textures.insert(texture); } void CCPrioritizedTextureManager::unregisterTexture(CCPrioritizedTexture* texture) { DCHECK(CCProxy::isMainThread() || (CCProxy::isImplThread() && CCProxy::isMainThreadBlocked())); DCHECK(texture); DCHECK(ContainsKey(m_textures, texture)); returnBackingTexture(texture); texture->setManagerInternal(0); m_textures.erase(texture); texture->setAbovePriorityCutoff(false); } void CCPrioritizedTextureManager::returnBackingTexture(CCPrioritizedTexture* texture) { DCHECK(CCProxy::isMainThread() || (CCProxy::isImplThread() && CCProxy::isMainThreadBlocked())); if (texture->backing()) texture->unlink(); } CCPrioritizedTexture::Backing* CCPrioritizedTextureManager::createBacking(IntSize size, GC3Denum format, CCResourceProvider* resourceProvider) { DCHECK(CCProxy::isImplThread() && CCProxy::isMainThreadBlocked()); DCHECK(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) { DCHECK(CCProxy::isImplThread()); DCHECK(backing); DCHECK(resourceProvider); DCHECK(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 CC_DCHECK_ENABLED() void CCPrioritizedTextureManager::assertInvariants() { DCHECK(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()) { DCHECK(ContainsKey(m_textures, (*it)->owner())); DCHECK((*it)->owner()->backing() == (*it)); } } for (TextureSet::iterator it = m_textures.begin(); it != m_textures.end(); ++it) { CCPrioritizedTexture* texture = (*it); CCPrioritizedTexture::Backing* backing = texture->backing(); if (backing) { if (backing->resourceHasBeenDeleted()) { DCHECK(m_backings.find(backing) == m_backings.end()); DCHECK(m_evictedBackings.contains(backing)); } else { DCHECK(m_backings.find(backing) != m_backings.end()); DCHECK(!m_evictedBackings.contains(backing)); } DCHECK(backing->owner() == texture); } } // 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 reachedUnrecyclable = false; for (BackingSet::iterator it = m_backings.begin(); it != m_backings.end(); ++it) { if (!(*it)->canBeRecycled()) reachedUnrecyclable = true; if (reachedUnrecyclable) DCHECK(!(*it)->canBeRecycled()); else DCHECK((*it)->canBeRecycled()); } } #endif } // namespace cc