Home | History | Annotate | Download | only in resources
      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.
      4 
      5 #include "cc/resources/prioritized_resource_manager.h"
      6 
      7 #include <algorithm>
      8 
      9 #include "base/debug/trace_event.h"
     10 #include "base/stl_util.h"
     11 #include "cc/resources/prioritized_resource.h"
     12 #include "cc/resources/priority_calculator.h"
     13 #include "cc/trees/proxy.h"
     14 
     15 namespace cc {
     16 
     17 PrioritizedResourceManager::PrioritizedResourceManager(const Proxy* proxy)
     18     : max_memory_limit_bytes_(DefaultMemoryAllocationLimit()),
     19       external_priority_cutoff_(PriorityCalculator::AllowEverythingCutoff()),
     20       memory_use_bytes_(0),
     21       memory_above_cutoff_bytes_(0),
     22       max_memory_needed_bytes_(0),
     23       memory_available_bytes_(0),
     24       proxy_(proxy),
     25       backings_tail_not_sorted_(false),
     26       memory_visible_bytes_(0),
     27       memory_visible_and_nearby_bytes_(0),
     28       memory_visible_last_pushed_bytes_(0),
     29       memory_visible_and_nearby_last_pushed_bytes_(0) {}
     30 
     31 PrioritizedResourceManager::~PrioritizedResourceManager() {
     32   while (textures_.size() > 0)
     33     UnregisterTexture(*textures_.begin());
     34 
     35   UnlinkAndClearEvictedBackings();
     36   DCHECK(evicted_backings_.empty());
     37 
     38   // Each remaining backing is a leaked opengl texture. There should be none.
     39   DCHECK(backings_.empty());
     40 }
     41 
     42 size_t PrioritizedResourceManager::MemoryVisibleBytes() const {
     43   DCHECK(proxy_->IsImplThread());
     44   return memory_visible_last_pushed_bytes_;
     45 }
     46 
     47 size_t PrioritizedResourceManager::MemoryVisibleAndNearbyBytes() const {
     48   DCHECK(proxy_->IsImplThread());
     49   return memory_visible_and_nearby_last_pushed_bytes_;
     50 }
     51 
     52 void PrioritizedResourceManager::PrioritizeTextures() {
     53   TRACE_EVENT0("cc", "PrioritizedResourceManager::PrioritizeTextures");
     54   DCHECK(proxy_->IsMainThread());
     55 
     56   // Sorting textures in this function could be replaced by a slightly
     57   // modified O(n) quick-select to partition textures rather than
     58   // sort them (if performance of the sort becomes an issue).
     59 
     60   TextureVector& sorted_textures = temp_texture_vector_;
     61   sorted_textures.clear();
     62 
     63   // Copy all textures into a vector, sort them, and collect memory requirements
     64   // statistics.
     65   memory_visible_bytes_ = 0;
     66   memory_visible_and_nearby_bytes_ = 0;
     67   for (TextureSet::iterator it = textures_.begin(); it != textures_.end();
     68        ++it) {
     69     PrioritizedResource* texture = (*it);
     70     sorted_textures.push_back(texture);
     71     if (PriorityCalculator::priority_is_higher(
     72             texture->request_priority(),
     73             PriorityCalculator::AllowVisibleOnlyCutoff()))
     74       memory_visible_bytes_ += texture->bytes();
     75     if (PriorityCalculator::priority_is_higher(
     76             texture->request_priority(),
     77             PriorityCalculator::AllowVisibleAndNearbyCutoff()))
     78       memory_visible_and_nearby_bytes_ += texture->bytes();
     79   }
     80   std::sort(sorted_textures.begin(), sorted_textures.end(), CompareTextures);
     81 
     82   // Compute a priority cutoff based on memory pressure
     83   memory_available_bytes_ = max_memory_limit_bytes_;
     84   priority_cutoff_ = external_priority_cutoff_;
     85   size_t memory_bytes = 0;
     86   for (TextureVector::iterator it = sorted_textures.begin();
     87        it != sorted_textures.end();
     88        ++it) {
     89     if ((*it)->is_self_managed()) {
     90       // Account for self-managed memory immediately by reducing the memory
     91       // available (since it never gets acquired).
     92       size_t new_memory_bytes = memory_bytes + (*it)->bytes();
     93       if (new_memory_bytes > memory_available_bytes_) {
     94         priority_cutoff_ = (*it)->request_priority();
     95         memory_available_bytes_ = memory_bytes;
     96         break;
     97       }
     98       memory_available_bytes_ -= (*it)->bytes();
     99     } else {
    100       size_t new_memory_bytes = memory_bytes + (*it)->bytes();
    101       if (new_memory_bytes > memory_available_bytes_) {
    102         priority_cutoff_ = (*it)->request_priority();
    103         break;
    104       }
    105       memory_bytes = new_memory_bytes;
    106     }
    107   }
    108 
    109   // Disallow any textures with priority below the external cutoff to have
    110   // backings.
    111   for (TextureVector::iterator it = sorted_textures.begin();
    112        it != sorted_textures.end();
    113        ++it) {
    114     PrioritizedResource* texture = (*it);
    115     if (!PriorityCalculator::priority_is_higher(texture->request_priority(),
    116                                                 external_priority_cutoff_) &&
    117         texture->have_backing_texture())
    118       texture->Unlink();
    119   }
    120 
    121   // Only allow textures if they are higher than the cutoff. All textures
    122   // of the same priority are accepted or rejected together, rather than
    123   // being partially allowed randomly.
    124   max_memory_needed_bytes_ = 0;
    125   memory_above_cutoff_bytes_ = 0;
    126   for (TextureVector::iterator it = sorted_textures.begin();
    127        it != sorted_textures.end();
    128        ++it) {
    129     PrioritizedResource* resource = *it;
    130     bool is_above_priority_cutoff = PriorityCalculator::priority_is_higher(
    131         resource->request_priority(), priority_cutoff_);
    132     resource->set_above_priority_cutoff(is_above_priority_cutoff);
    133     if (!resource->is_self_managed()) {
    134       max_memory_needed_bytes_ += resource->bytes();
    135       if (is_above_priority_cutoff)
    136         memory_above_cutoff_bytes_ += resource->bytes();
    137     }
    138   }
    139   sorted_textures.clear();
    140 
    141   DCHECK_LE(memory_above_cutoff_bytes_, memory_available_bytes_);
    142   DCHECK_LE(MemoryAboveCutoffBytes(), MaxMemoryLimitBytes());
    143 }
    144 
    145 void PrioritizedResourceManager::PushTexturePrioritiesToBackings() {
    146   TRACE_EVENT0("cc",
    147                "PrioritizedResourceManager::PushTexturePrioritiesToBackings");
    148   DCHECK(proxy_->IsImplThread() && proxy_->IsMainThreadBlocked());
    149 
    150   AssertInvariants();
    151   for (BackingList::iterator it = backings_.begin(); it != backings_.end();
    152        ++it)
    153     (*it)->UpdatePriority();
    154   SortBackings();
    155   AssertInvariants();
    156 
    157   // Push memory requirements to the impl thread structure.
    158   memory_visible_last_pushed_bytes_ = memory_visible_bytes_;
    159   memory_visible_and_nearby_last_pushed_bytes_ =
    160       memory_visible_and_nearby_bytes_;
    161 }
    162 
    163 void PrioritizedResourceManager::UpdateBackingsState(
    164     ResourceProvider* resource_provider) {
    165   TRACE_EVENT0("cc",
    166                "PrioritizedResourceManager::UpdateBackingsInDrawingImplTree");
    167   DCHECK(proxy_->IsImplThread() && proxy_->IsMainThreadBlocked());
    168 
    169   AssertInvariants();
    170   for (BackingList::iterator it = backings_.begin(); it != backings_.end();
    171        ++it) {
    172     PrioritizedResource::Backing* backing = (*it);
    173     backing->UpdateState(resource_provider);
    174   }
    175   SortBackings();
    176   AssertInvariants();
    177 }
    178 
    179 void PrioritizedResourceManager::SortBackings() {
    180   TRACE_EVENT0("cc", "PrioritizedResourceManager::SortBackings");
    181   DCHECK(proxy_->IsImplThread());
    182 
    183   // Put backings in eviction/recycling order.
    184   backings_.sort(CompareBackings);
    185   backings_tail_not_sorted_ = false;
    186 }
    187 
    188 void PrioritizedResourceManager::ClearPriorities() {
    189   DCHECK(proxy_->IsMainThread());
    190   for (TextureSet::iterator it = textures_.begin(); it != textures_.end();
    191        ++it) {
    192     // TODO(reveman): We should remove this and just set all priorities to
    193     // PriorityCalculator::lowestPriority() once we have priorities for all
    194     // textures (we can't currently calculate distances for off-screen
    195     // textures).
    196     (*it)->set_request_priority(
    197         PriorityCalculator::LingeringPriority((*it)->request_priority()));
    198   }
    199 }
    200 
    201 bool PrioritizedResourceManager::RequestLate(PrioritizedResource* texture) {
    202   DCHECK(proxy_->IsMainThread());
    203 
    204   // This is already above cutoff, so don't double count it's memory below.
    205   if (texture->is_above_priority_cutoff())
    206     return true;
    207 
    208   // Allow textures that have priority equal to the cutoff, but not strictly
    209   // lower.
    210   if (PriorityCalculator::priority_is_lower(texture->request_priority(),
    211                                             priority_cutoff_))
    212     return false;
    213 
    214   // Disallow textures that do not have a priority strictly higher than the
    215   // external cutoff.
    216   if (!PriorityCalculator::priority_is_higher(texture->request_priority(),
    217                                               external_priority_cutoff_))
    218     return false;
    219 
    220   size_t new_memory_bytes = memory_above_cutoff_bytes_ + texture->bytes();
    221   if (new_memory_bytes > memory_available_bytes_)
    222     return false;
    223 
    224   memory_above_cutoff_bytes_ = new_memory_bytes;
    225   texture->set_above_priority_cutoff(true);
    226   return true;
    227 }
    228 
    229 void PrioritizedResourceManager::AcquireBackingTextureIfNeeded(
    230     PrioritizedResource* texture,
    231     ResourceProvider* resource_provider) {
    232   DCHECK(proxy_->IsImplThread() && proxy_->IsMainThreadBlocked());
    233   DCHECK(!texture->is_self_managed());
    234   DCHECK(texture->is_above_priority_cutoff());
    235   if (texture->backing() || !texture->is_above_priority_cutoff())
    236     return;
    237 
    238   // Find a backing below, by either recycling or allocating.
    239   PrioritizedResource::Backing* backing = NULL;
    240 
    241   // First try to recycle
    242   for (BackingList::iterator it = backings_.begin(); it != backings_.end();
    243        ++it) {
    244     if (!(*it)->CanBeRecycledIfNotInExternalUse())
    245       break;
    246     if (resource_provider->InUseByConsumer((*it)->id()))
    247       continue;
    248     if ((*it)->size() == texture->size() &&
    249         (*it)->format() == texture->format()) {
    250       backing = (*it);
    251       backings_.erase(it);
    252       break;
    253     }
    254   }
    255 
    256   // Otherwise reduce memory and just allocate a new backing texures.
    257   if (!backing) {
    258     EvictBackingsToReduceMemory(memory_available_bytes_ - texture->bytes(),
    259                                 PriorityCalculator::AllowEverythingCutoff(),
    260                                 EVICT_ONLY_RECYCLABLE,
    261                                 DO_NOT_UNLINK_BACKINGS,
    262                                 resource_provider);
    263     backing =
    264         CreateBacking(texture->size(), texture->format(), resource_provider);
    265   }
    266 
    267   // Move the used backing to the end of the eviction list, and note that
    268   // the tail is not sorted.
    269   if (backing->owner())
    270     backing->owner()->Unlink();
    271   texture->Link(backing);
    272   backings_.push_back(backing);
    273   backings_tail_not_sorted_ = true;
    274 
    275   // Update the backing's priority from its new owner.
    276   backing->UpdatePriority();
    277 }
    278 
    279 bool PrioritizedResourceManager::EvictBackingsToReduceMemory(
    280     size_t limit_bytes,
    281     int priority_cutoff,
    282     EvictionPolicy eviction_policy,
    283     UnlinkPolicy unlink_policy,
    284     ResourceProvider* resource_provider) {
    285   DCHECK(proxy_->IsImplThread());
    286   if (unlink_policy == UNLINK_BACKINGS)
    287     DCHECK(proxy_->IsMainThreadBlocked());
    288   if (MemoryUseBytes() <= limit_bytes &&
    289       PriorityCalculator::AllowEverythingCutoff() == priority_cutoff)
    290     return false;
    291 
    292   // Destroy backings until we are below the limit,
    293   // or until all backings remaining are above the cutoff.
    294   bool evicted_anything = false;
    295   while (backings_.size() > 0) {
    296     PrioritizedResource::Backing* backing = backings_.front();
    297     if (MemoryUseBytes() <= limit_bytes &&
    298         PriorityCalculator::priority_is_higher(
    299             backing->request_priority_at_last_priority_update(),
    300             priority_cutoff))
    301       break;
    302     if (eviction_policy == EVICT_ONLY_RECYCLABLE &&
    303         !backing->CanBeRecycledIfNotInExternalUse())
    304       break;
    305     if (unlink_policy == UNLINK_BACKINGS && backing->owner())
    306       backing->owner()->Unlink();
    307     EvictFirstBackingResource(resource_provider);
    308     evicted_anything = true;
    309   }
    310   return evicted_anything;
    311 }
    312 
    313 void PrioritizedResourceManager::ReduceWastedMemory(
    314     ResourceProvider* resource_provider) {
    315   // We currently collect backings from deleted textures for later recycling.
    316   // However, if we do that forever we will always use the max limit even if
    317   // we really need very little memory. This should probably be solved by
    318   // reducing the limit externally, but until then this just does some "clean
    319   // up" of unused backing textures (any more than 10%).
    320   size_t wasted_memory = 0;
    321   for (BackingList::iterator it = backings_.begin(); it != backings_.end();
    322        ++it) {
    323     if ((*it)->owner())
    324       break;
    325     if ((*it)->in_parent_compositor())
    326       continue;
    327     wasted_memory += (*it)->bytes();
    328   }
    329   size_t wasted_memory_to_allow = memory_available_bytes_ / 10;
    330   // If the external priority cutoff indicates that unused memory should be
    331   // freed, then do not allow any memory for texture recycling.
    332   if (external_priority_cutoff_ != PriorityCalculator::AllowEverythingCutoff())
    333     wasted_memory_to_allow = 0;
    334   if (wasted_memory > wasted_memory_to_allow)
    335     EvictBackingsToReduceMemory(MemoryUseBytes() -
    336                                 (wasted_memory - wasted_memory_to_allow),
    337                                 PriorityCalculator::AllowEverythingCutoff(),
    338                                 EVICT_ONLY_RECYCLABLE,
    339                                 DO_NOT_UNLINK_BACKINGS,
    340                                 resource_provider);
    341 }
    342 
    343 void PrioritizedResourceManager::ReduceMemory(
    344     ResourceProvider* resource_provider) {
    345   DCHECK(proxy_->IsImplThread() && proxy_->IsMainThreadBlocked());
    346   EvictBackingsToReduceMemory(memory_available_bytes_,
    347                               PriorityCalculator::AllowEverythingCutoff(),
    348                               EVICT_ANYTHING,
    349                               UNLINK_BACKINGS,
    350                               resource_provider);
    351   DCHECK_LE(MemoryUseBytes(), memory_available_bytes_);
    352 
    353   ReduceWastedMemory(resource_provider);
    354 }
    355 
    356 void PrioritizedResourceManager::ClearAllMemory(
    357     ResourceProvider* resource_provider) {
    358   DCHECK(proxy_->IsImplThread() && proxy_->IsMainThreadBlocked());
    359   if (!resource_provider) {
    360     DCHECK(backings_.empty());
    361     return;
    362   }
    363   EvictBackingsToReduceMemory(0,
    364                               PriorityCalculator::AllowEverythingCutoff(),
    365                               EVICT_ANYTHING,
    366                               DO_NOT_UNLINK_BACKINGS,
    367                               resource_provider);
    368 }
    369 
    370 bool PrioritizedResourceManager::ReduceMemoryOnImplThread(
    371     size_t limit_bytes,
    372     int priority_cutoff,
    373     ResourceProvider* resource_provider) {
    374   DCHECK(proxy_->IsImplThread());
    375   DCHECK(resource_provider);
    376 
    377   // If we are in the process of uploading a new frame then the backings at the
    378   // very end of the list are not sorted by priority. Sort them before doing the
    379   // eviction.
    380   if (backings_tail_not_sorted_)
    381     SortBackings();
    382   return EvictBackingsToReduceMemory(limit_bytes,
    383                                      priority_cutoff,
    384                                      EVICT_ANYTHING,
    385                                      DO_NOT_UNLINK_BACKINGS,
    386                                      resource_provider);
    387 }
    388 
    389 void PrioritizedResourceManager::UnlinkAndClearEvictedBackings() {
    390   DCHECK(proxy_->IsMainThread());
    391   base::AutoLock scoped_lock(evicted_backings_lock_);
    392   for (BackingList::const_iterator it = evicted_backings_.begin();
    393        it != evicted_backings_.end();
    394        ++it) {
    395     PrioritizedResource::Backing* backing = (*it);
    396     if (backing->owner())
    397       backing->owner()->Unlink();
    398     delete backing;
    399   }
    400   evicted_backings_.clear();
    401 }
    402 
    403 bool PrioritizedResourceManager::LinkedEvictedBackingsExist() const {
    404   DCHECK(proxy_->IsImplThread() && proxy_->IsMainThreadBlocked());
    405   base::AutoLock scoped_lock(evicted_backings_lock_);
    406   for (BackingList::const_iterator it = evicted_backings_.begin();
    407        it != evicted_backings_.end();
    408        ++it) {
    409     if ((*it)->owner())
    410       return true;
    411   }
    412   return false;
    413 }
    414 
    415 void PrioritizedResourceManager::RegisterTexture(PrioritizedResource* texture) {
    416   DCHECK(proxy_->IsMainThread());
    417   DCHECK(texture);
    418   DCHECK(!texture->resource_manager());
    419   DCHECK(!texture->backing());
    420   DCHECK(!ContainsKey(textures_, texture));
    421 
    422   texture->set_manager_internal(this);
    423   textures_.insert(texture);
    424 }
    425 
    426 void PrioritizedResourceManager::UnregisterTexture(
    427     PrioritizedResource* texture) {
    428   DCHECK(proxy_->IsMainThread() ||
    429          (proxy_->IsImplThread() && proxy_->IsMainThreadBlocked()));
    430   DCHECK(texture);
    431   DCHECK(ContainsKey(textures_, texture));
    432 
    433   ReturnBackingTexture(texture);
    434   texture->set_manager_internal(NULL);
    435   textures_.erase(texture);
    436   texture->set_above_priority_cutoff(false);
    437 }
    438 
    439 void PrioritizedResourceManager::ReturnBackingTexture(
    440     PrioritizedResource* texture) {
    441   DCHECK(proxy_->IsMainThread() ||
    442          (proxy_->IsImplThread() && proxy_->IsMainThreadBlocked()));
    443   if (texture->backing())
    444     texture->Unlink();
    445 }
    446 
    447 PrioritizedResource::Backing* PrioritizedResourceManager::CreateBacking(
    448     const gfx::Size& size,
    449     ResourceFormat format,
    450     ResourceProvider* resource_provider) {
    451   DCHECK(proxy_->IsImplThread() && proxy_->IsMainThreadBlocked());
    452   DCHECK(resource_provider);
    453   ResourceProvider::ResourceId resource_id =
    454       resource_provider->CreateManagedResource(
    455           size,
    456           GL_TEXTURE_2D,
    457           GL_CLAMP_TO_EDGE,
    458           ResourceProvider::TextureHintImmutable,
    459           format);
    460   PrioritizedResource::Backing* backing = new PrioritizedResource::Backing(
    461       resource_id, resource_provider, size, format);
    462   memory_use_bytes_ += backing->bytes();
    463   return backing;
    464 }
    465 
    466 void PrioritizedResourceManager::EvictFirstBackingResource(
    467     ResourceProvider* resource_provider) {
    468   DCHECK(proxy_->IsImplThread());
    469   DCHECK(resource_provider);
    470   DCHECK(!backings_.empty());
    471   PrioritizedResource::Backing* backing = backings_.front();
    472 
    473   // Note that we create a backing and its resource at the same time, but we
    474   // delete the backing structure and its resource in two steps. This is because
    475   // we can delete the resource while the main thread is running, but we cannot
    476   // unlink backings while the main thread is running.
    477   backing->DeleteResource(resource_provider);
    478   memory_use_bytes_ -= backing->bytes();
    479   backings_.pop_front();
    480   base::AutoLock scoped_lock(evicted_backings_lock_);
    481   evicted_backings_.push_back(backing);
    482 }
    483 
    484 void PrioritizedResourceManager::AssertInvariants() {
    485 #if DCHECK_IS_ON
    486   DCHECK(proxy_->IsImplThread() && proxy_->IsMainThreadBlocked());
    487 
    488   // If we hit any of these asserts, there is a bug in this class. To see
    489   // where the bug is, call this function at the beginning and end of
    490   // every public function.
    491 
    492   // Backings/textures must be doubly-linked and only to other backings/textures
    493   // in this manager.
    494   for (BackingList::iterator it = backings_.begin(); it != backings_.end();
    495        ++it) {
    496     if ((*it)->owner()) {
    497       DCHECK(ContainsKey(textures_, (*it)->owner()));
    498       DCHECK((*it)->owner()->backing() == (*it));
    499     }
    500   }
    501   for (TextureSet::iterator it = textures_.begin(); it != textures_.end();
    502        ++it) {
    503     PrioritizedResource* texture = (*it);
    504     PrioritizedResource::Backing* backing = texture->backing();
    505     base::AutoLock scoped_lock(evicted_backings_lock_);
    506     if (backing) {
    507       if (backing->ResourceHasBeenDeleted()) {
    508         DCHECK(std::find(backings_.begin(), backings_.end(), backing) ==
    509                backings_.end());
    510         DCHECK(std::find(evicted_backings_.begin(),
    511                          evicted_backings_.end(),
    512                          backing) != evicted_backings_.end());
    513       } else {
    514         DCHECK(std::find(backings_.begin(), backings_.end(), backing) !=
    515                backings_.end());
    516         DCHECK(std::find(evicted_backings_.begin(),
    517                          evicted_backings_.end(),
    518                          backing) == evicted_backings_.end());
    519       }
    520       DCHECK(backing->owner() == texture);
    521     }
    522   }
    523 
    524   // At all times, backings that can be evicted must always come before
    525   // backings that can't be evicted in the backing texture list (otherwise
    526   // ReduceMemory will not find all textures available for eviction/recycling).
    527   bool reached_unrecyclable = false;
    528   PrioritizedResource::Backing* previous_backing = NULL;
    529   for (BackingList::iterator it = backings_.begin(); it != backings_.end();
    530        ++it) {
    531     PrioritizedResource::Backing* backing = *it;
    532     if (previous_backing &&
    533         (!backings_tail_not_sorted_ ||
    534          !backing->was_above_priority_cutoff_at_last_priority_update()))
    535       DCHECK(CompareBackings(previous_backing, backing));
    536     if (!backing->CanBeRecycledIfNotInExternalUse())
    537       reached_unrecyclable = true;
    538     if (reached_unrecyclable)
    539       DCHECK(!backing->CanBeRecycledIfNotInExternalUse());
    540     else
    541       DCHECK(backing->CanBeRecycledIfNotInExternalUse());
    542     previous_backing = backing;
    543   }
    544 #endif  // DCHECK_IS_ON
    545 }
    546 
    547 const Proxy* PrioritizedResourceManager::ProxyForDebug() const {
    548   return proxy_;
    549 }
    550 
    551 }  // namespace cc
    552