// 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 "cc/resources/prioritized_resource_manager.h" #include <algorithm> #include "base/debug/trace_event.h" #include "base/stl_util.h" #include "cc/resources/prioritized_resource.h" #include "cc/resources/priority_calculator.h" #include "cc/trees/proxy.h" namespace cc { PrioritizedResourceManager::PrioritizedResourceManager(const Proxy* proxy) : max_memory_limit_bytes_(DefaultMemoryAllocationLimit()), external_priority_cutoff_(PriorityCalculator::AllowEverythingCutoff()), memory_use_bytes_(0), memory_above_cutoff_bytes_(0), max_memory_needed_bytes_(0), memory_available_bytes_(0), proxy_(proxy), backings_tail_not_sorted_(false), memory_visible_bytes_(0), memory_visible_and_nearby_bytes_(0), memory_visible_last_pushed_bytes_(0), memory_visible_and_nearby_last_pushed_bytes_(0) {} PrioritizedResourceManager::~PrioritizedResourceManager() { while (textures_.size() > 0) UnregisterTexture(*textures_.begin()); UnlinkAndClearEvictedBackings(); DCHECK(evicted_backings_.empty()); // Each remaining backing is a leaked opengl texture. There should be none. DCHECK(backings_.empty()); } size_t PrioritizedResourceManager::MemoryVisibleBytes() const { DCHECK(proxy_->IsImplThread()); return memory_visible_last_pushed_bytes_; } size_t PrioritizedResourceManager::MemoryVisibleAndNearbyBytes() const { DCHECK(proxy_->IsImplThread()); return memory_visible_and_nearby_last_pushed_bytes_; } void PrioritizedResourceManager::PrioritizeTextures() { TRACE_EVENT0("cc", "PrioritizedResourceManager::PrioritizeTextures"); DCHECK(proxy_->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& sorted_textures = temp_texture_vector_; sorted_textures.clear(); // Copy all textures into a vector, sort them, and collect memory requirements // statistics. memory_visible_bytes_ = 0; memory_visible_and_nearby_bytes_ = 0; for (TextureSet::iterator it = textures_.begin(); it != textures_.end(); ++it) { PrioritizedResource* texture = (*it); sorted_textures.push_back(texture); if (PriorityCalculator::priority_is_higher( texture->request_priority(), PriorityCalculator::AllowVisibleOnlyCutoff())) memory_visible_bytes_ += texture->bytes(); if (PriorityCalculator::priority_is_higher( texture->request_priority(), PriorityCalculator::AllowVisibleAndNearbyCutoff())) memory_visible_and_nearby_bytes_ += texture->bytes(); } std::sort(sorted_textures.begin(), sorted_textures.end(), CompareTextures); // Compute a priority cutoff based on memory pressure memory_available_bytes_ = max_memory_limit_bytes_; priority_cutoff_ = external_priority_cutoff_; size_t memory_bytes = 0; for (TextureVector::iterator it = sorted_textures.begin(); it != sorted_textures.end(); ++it) { if ((*it)->is_self_managed()) { // Account for self-managed memory immediately by reducing the memory // available (since it never gets acquired). size_t new_memory_bytes = memory_bytes + (*it)->bytes(); if (new_memory_bytes > memory_available_bytes_) { priority_cutoff_ = (*it)->request_priority(); memory_available_bytes_ = memory_bytes; break; } memory_available_bytes_ -= (*it)->bytes(); } else { size_t new_memory_bytes = memory_bytes + (*it)->bytes(); if (new_memory_bytes > memory_available_bytes_) { priority_cutoff_ = (*it)->request_priority(); break; } memory_bytes = new_memory_bytes; } } // Disallow any textures with priority below the external cutoff to have // backings. for (TextureVector::iterator it = sorted_textures.begin(); it != sorted_textures.end(); ++it) { PrioritizedResource* texture = (*it); if (!PriorityCalculator::priority_is_higher(texture->request_priority(), external_priority_cutoff_) && texture->have_backing_texture()) texture->Unlink(); } // 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. max_memory_needed_bytes_ = 0; memory_above_cutoff_bytes_ = 0; for (TextureVector::iterator it = sorted_textures.begin(); it != sorted_textures.end(); ++it) { PrioritizedResource* resource = *it; bool is_above_priority_cutoff = PriorityCalculator::priority_is_higher( resource->request_priority(), priority_cutoff_); resource->set_above_priority_cutoff(is_above_priority_cutoff); if (!resource->is_self_managed()) { max_memory_needed_bytes_ += resource->bytes(); if (is_above_priority_cutoff) memory_above_cutoff_bytes_ += resource->bytes(); } } sorted_textures.clear(); DCHECK_LE(memory_above_cutoff_bytes_, memory_available_bytes_); DCHECK_LE(MemoryAboveCutoffBytes(), MaxMemoryLimitBytes()); } void PrioritizedResourceManager::PushTexturePrioritiesToBackings() { TRACE_EVENT0("cc", "PrioritizedResourceManager::PushTexturePrioritiesToBackings"); DCHECK(proxy_->IsImplThread() && proxy_->IsMainThreadBlocked()); AssertInvariants(); for (BackingList::iterator it = backings_.begin(); it != backings_.end(); ++it) (*it)->UpdatePriority(); SortBackings(); AssertInvariants(); // Push memory requirements to the impl thread structure. memory_visible_last_pushed_bytes_ = memory_visible_bytes_; memory_visible_and_nearby_last_pushed_bytes_ = memory_visible_and_nearby_bytes_; } void PrioritizedResourceManager::UpdateBackingsState( ResourceProvider* resource_provider) { TRACE_EVENT0("cc", "PrioritizedResourceManager::UpdateBackingsInDrawingImplTree"); DCHECK(proxy_->IsImplThread() && proxy_->IsMainThreadBlocked()); AssertInvariants(); for (BackingList::iterator it = backings_.begin(); it != backings_.end(); ++it) { PrioritizedResource::Backing* backing = (*it); backing->UpdateState(resource_provider); } SortBackings(); AssertInvariants(); } void PrioritizedResourceManager::SortBackings() { TRACE_EVENT0("cc", "PrioritizedResourceManager::SortBackings"); DCHECK(proxy_->IsImplThread()); // Put backings in eviction/recycling order. backings_.sort(CompareBackings); backings_tail_not_sorted_ = false; } void PrioritizedResourceManager::ClearPriorities() { DCHECK(proxy_->IsMainThread()); for (TextureSet::iterator it = textures_.begin(); it != textures_.end(); ++it) { // TODO(reveman): We should remove this and just set all priorities to // PriorityCalculator::lowestPriority() once we have priorities for all // textures (we can't currently calculate distances for off-screen // textures). (*it)->set_request_priority( PriorityCalculator::LingeringPriority((*it)->request_priority())); } } bool PrioritizedResourceManager::RequestLate(PrioritizedResource* texture) { DCHECK(proxy_->IsMainThread()); // This is already above cutoff, so don't double count it's memory below. if (texture->is_above_priority_cutoff()) return true; // Allow textures that have priority equal to the cutoff, but not strictly // lower. if (PriorityCalculator::priority_is_lower(texture->request_priority(), priority_cutoff_)) return false; // Disallow textures that do not have a priority strictly higher than the // external cutoff. if (!PriorityCalculator::priority_is_higher(texture->request_priority(), external_priority_cutoff_)) return false; size_t new_memory_bytes = memory_above_cutoff_bytes_ + texture->bytes(); if (new_memory_bytes > memory_available_bytes_) return false; memory_above_cutoff_bytes_ = new_memory_bytes; texture->set_above_priority_cutoff(true); return true; } void PrioritizedResourceManager::AcquireBackingTextureIfNeeded( PrioritizedResource* texture, ResourceProvider* resource_provider) { DCHECK(proxy_->IsImplThread() && proxy_->IsMainThreadBlocked()); DCHECK(!texture->is_self_managed()); DCHECK(texture->is_above_priority_cutoff()); if (texture->backing() || !texture->is_above_priority_cutoff()) return; // Find a backing below, by either recycling or allocating. PrioritizedResource::Backing* backing = NULL; // First try to recycle for (BackingList::iterator it = backings_.begin(); it != backings_.end(); ++it) { if (!(*it)->CanBeRecycledIfNotInExternalUse()) break; if (resource_provider->InUseByConsumer((*it)->id())) continue; if ((*it)->size() == texture->size() && (*it)->format() == texture->format()) { backing = (*it); backings_.erase(it); break; } } // Otherwise reduce memory and just allocate a new backing texures. if (!backing) { EvictBackingsToReduceMemory(memory_available_bytes_ - texture->bytes(), PriorityCalculator::AllowEverythingCutoff(), EVICT_ONLY_RECYCLABLE, DO_NOT_UNLINK_BACKINGS, resource_provider); backing = CreateBacking(texture->size(), texture->format(), resource_provider); } // Move the used backing to the end of the eviction list, and note that // the tail is not sorted. if (backing->owner()) backing->owner()->Unlink(); texture->Link(backing); backings_.push_back(backing); backings_tail_not_sorted_ = true; // Update the backing's priority from its new owner. backing->UpdatePriority(); } bool PrioritizedResourceManager::EvictBackingsToReduceMemory( size_t limit_bytes, int priority_cutoff, EvictionPolicy eviction_policy, UnlinkPolicy unlink_policy, ResourceProvider* resource_provider) { DCHECK(proxy_->IsImplThread()); if (unlink_policy == UNLINK_BACKINGS) DCHECK(proxy_->IsMainThreadBlocked()); if (MemoryUseBytes() <= limit_bytes && PriorityCalculator::AllowEverythingCutoff() == priority_cutoff) return false; // Destroy backings until we are below the limit, // or until all backings remaining are above the cutoff. bool evicted_anything = false; while (backings_.size() > 0) { PrioritizedResource::Backing* backing = backings_.front(); if (MemoryUseBytes() <= limit_bytes && PriorityCalculator::priority_is_higher( backing->request_priority_at_last_priority_update(), priority_cutoff)) break; if (eviction_policy == EVICT_ONLY_RECYCLABLE && !backing->CanBeRecycledIfNotInExternalUse()) break; if (unlink_policy == UNLINK_BACKINGS && backing->owner()) backing->owner()->Unlink(); EvictFirstBackingResource(resource_provider); evicted_anything = true; } return evicted_anything; } void PrioritizedResourceManager::ReduceWastedMemory( ResourceProvider* resource_provider) { // 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 wasted_memory = 0; for (BackingList::iterator it = backings_.begin(); it != backings_.end(); ++it) { if ((*it)->owner()) break; if ((*it)->in_parent_compositor()) continue; wasted_memory += (*it)->bytes(); } size_t wasted_memory_to_allow = memory_available_bytes_ / 10; // If the external priority cutoff indicates that unused memory should be // freed, then do not allow any memory for texture recycling. if (external_priority_cutoff_ != PriorityCalculator::AllowEverythingCutoff()) wasted_memory_to_allow = 0; if (wasted_memory > wasted_memory_to_allow) EvictBackingsToReduceMemory(MemoryUseBytes() - (wasted_memory - wasted_memory_to_allow), PriorityCalculator::AllowEverythingCutoff(), EVICT_ONLY_RECYCLABLE, DO_NOT_UNLINK_BACKINGS, resource_provider); } void PrioritizedResourceManager::ReduceMemory( ResourceProvider* resource_provider) { DCHECK(proxy_->IsImplThread() && proxy_->IsMainThreadBlocked()); EvictBackingsToReduceMemory(memory_available_bytes_, PriorityCalculator::AllowEverythingCutoff(), EVICT_ANYTHING, UNLINK_BACKINGS, resource_provider); DCHECK_LE(MemoryUseBytes(), memory_available_bytes_); ReduceWastedMemory(resource_provider); } void PrioritizedResourceManager::ClearAllMemory( ResourceProvider* resource_provider) { DCHECK(proxy_->IsImplThread() && proxy_->IsMainThreadBlocked()); if (!resource_provider) { DCHECK(backings_.empty()); return; } EvictBackingsToReduceMemory(0, PriorityCalculator::AllowEverythingCutoff(), EVICT_ANYTHING, DO_NOT_UNLINK_BACKINGS, resource_provider); } bool PrioritizedResourceManager::ReduceMemoryOnImplThread( size_t limit_bytes, int priority_cutoff, ResourceProvider* resource_provider) { DCHECK(proxy_->IsImplThread()); DCHECK(resource_provider); // If we are in the process of uploading a new frame then the backings at the // very end of the list are not sorted by priority. Sort them before doing the // eviction. if (backings_tail_not_sorted_) SortBackings(); return EvictBackingsToReduceMemory(limit_bytes, priority_cutoff, EVICT_ANYTHING, DO_NOT_UNLINK_BACKINGS, resource_provider); } void PrioritizedResourceManager::UnlinkAndClearEvictedBackings() { DCHECK(proxy_->IsMainThread()); base::AutoLock scoped_lock(evicted_backings_lock_); for (BackingList::const_iterator it = evicted_backings_.begin(); it != evicted_backings_.end(); ++it) { PrioritizedResource::Backing* backing = (*it); if (backing->owner()) backing->owner()->Unlink(); delete backing; } evicted_backings_.clear(); } bool PrioritizedResourceManager::LinkedEvictedBackingsExist() const { DCHECK(proxy_->IsImplThread() && proxy_->IsMainThreadBlocked()); base::AutoLock scoped_lock(evicted_backings_lock_); for (BackingList::const_iterator it = evicted_backings_.begin(); it != evicted_backings_.end(); ++it) { if ((*it)->owner()) return true; } return false; } void PrioritizedResourceManager::RegisterTexture(PrioritizedResource* texture) { DCHECK(proxy_->IsMainThread()); DCHECK(texture); DCHECK(!texture->resource_manager()); DCHECK(!texture->backing()); DCHECK(!ContainsKey(textures_, texture)); texture->set_manager_internal(this); textures_.insert(texture); } void PrioritizedResourceManager::UnregisterTexture( PrioritizedResource* texture) { DCHECK(proxy_->IsMainThread() || (proxy_->IsImplThread() && proxy_->IsMainThreadBlocked())); DCHECK(texture); DCHECK(ContainsKey(textures_, texture)); ReturnBackingTexture(texture); texture->set_manager_internal(NULL); textures_.erase(texture); texture->set_above_priority_cutoff(false); } void PrioritizedResourceManager::ReturnBackingTexture( PrioritizedResource* texture) { DCHECK(proxy_->IsMainThread() || (proxy_->IsImplThread() && proxy_->IsMainThreadBlocked())); if (texture->backing()) texture->Unlink(); } PrioritizedResource::Backing* PrioritizedResourceManager::CreateBacking( const gfx::Size& size, ResourceFormat format, ResourceProvider* resource_provider) { DCHECK(proxy_->IsImplThread() && proxy_->IsMainThreadBlocked()); DCHECK(resource_provider); ResourceProvider::ResourceId resource_id = resource_provider->CreateManagedResource( size, GL_TEXTURE_2D, GL_CLAMP_TO_EDGE, ResourceProvider::TextureUsageAny, format); PrioritizedResource::Backing* backing = new PrioritizedResource::Backing( resource_id, resource_provider, size, format); memory_use_bytes_ += backing->bytes(); return backing; } void PrioritizedResourceManager::EvictFirstBackingResource( ResourceProvider* resource_provider) { DCHECK(proxy_->IsImplThread()); DCHECK(resource_provider); DCHECK(!backings_.empty()); PrioritizedResource::Backing* backing = backings_.front(); // 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(resource_provider); memory_use_bytes_ -= backing->bytes(); backings_.pop_front(); base::AutoLock scoped_lock(evicted_backings_lock_); evicted_backings_.push_back(backing); } void PrioritizedResourceManager::AssertInvariants() { #if DCHECK_IS_ON DCHECK(proxy_->IsImplThread() && proxy_->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 (BackingList::iterator it = backings_.begin(); it != backings_.end(); ++it) { if ((*it)->owner()) { DCHECK(ContainsKey(textures_, (*it)->owner())); DCHECK((*it)->owner()->backing() == (*it)); } } for (TextureSet::iterator it = textures_.begin(); it != textures_.end(); ++it) { PrioritizedResource* texture = (*it); PrioritizedResource::Backing* backing = texture->backing(); base::AutoLock scoped_lock(evicted_backings_lock_); if (backing) { if (backing->ResourceHasBeenDeleted()) { DCHECK(std::find(backings_.begin(), backings_.end(), backing) == backings_.end()); DCHECK(std::find(evicted_backings_.begin(), evicted_backings_.end(), backing) != evicted_backings_.end()); } else { DCHECK(std::find(backings_.begin(), backings_.end(), backing) != backings_.end()); DCHECK(std::find(evicted_backings_.begin(), evicted_backings_.end(), backing) == evicted_backings_.end()); } 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 reached_unrecyclable = false; PrioritizedResource::Backing* previous_backing = NULL; for (BackingList::iterator it = backings_.begin(); it != backings_.end(); ++it) { PrioritizedResource::Backing* backing = *it; if (previous_backing && (!backings_tail_not_sorted_ || !backing->was_above_priority_cutoff_at_last_priority_update())) DCHECK(CompareBackings(previous_backing, backing)); if (!backing->CanBeRecycledIfNotInExternalUse()) reached_unrecyclable = true; if (reached_unrecyclable) DCHECK(!backing->CanBeRecycledIfNotInExternalUse()); else DCHECK(backing->CanBeRecycledIfNotInExternalUse()); previous_backing = backing; } #endif // DCHECK_IS_ON } const Proxy* PrioritizedResourceManager::ProxyForDebug() const { return proxy_; } } // namespace cc