| // 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 |