Home | History | Annotate | Download | only in renderer_host
      1 // Copyright (c) 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 "content/browser/renderer_host/backing_store_manager.h"
      6 
      7 #include "base/bind.h"
      8 #include "base/command_line.h"
      9 #include "base/containers/mru_cache.h"
     10 #include "base/sys_info.h"
     11 #include "content/browser/renderer_host/backing_store.h"
     12 #include "content/browser/renderer_host/render_widget_host_impl.h"
     13 #include "content/public/common/content_switches.h"
     14 
     15 namespace content {
     16 namespace {
     17 
     18 // There are two separate caches, |large_cache| and |small_cache|.  large_cache
     19 // is meant for large items (tabs, popup windows), while small_cache is meant
     20 // for small items (extension popups, HTML5 notifications). The idea is that
     21 // we'll almost always try to evict from large_cache first since small_cache
     22 // items will tend to be visible more of the time.
     23 typedef base::OwningMRUCache<RenderWidgetHost*, BackingStore*>
     24     BackingStoreCache;
     25 BackingStoreCache* large_cache = NULL;
     26 BackingStoreCache* small_cache = NULL;
     27 
     28 // Threshold is based on a single large-monitor-width toolstrip.
     29 // (32bpp, 32 pixels high, 1920 pixels wide)
     30 // TODO(aa): The extension system no longer supports toolstrips, but we think
     31 // this might be helping for other examples of small HTML views in Chrome.
     32 // Maybe this cache should be redesigned to simply prefer smaller objects to
     33 // larger ones, rather than having a fixed threshold.
     34 // For more background, see: crbug.com/100506.
     35 const size_t kSmallThreshold = 4 * 32 * 1920;
     36 
     37 // Pick a large monitor size to use as a multiplier.  This is multiplied by the
     38 // max number of large backing stores (usually tabs) to pick a ceiling on the
     39 // max memory to use.
     40 // TODO(erikkay) Perhaps we should actually use monitor size?  That way we
     41 // could make an assertion like "worse case, there are two tabs in the cache".
     42 // However, the small_cache might mess up these calculations a bit.
     43 // TODO(erikkay) 32bpp assumption isn't great.
     44 const size_t kMemoryMultiplier = 4 * 1920 * 1200;  // ~9MB
     45 
     46 // The maximum number of large BackingStoreCache objects (tabs) to use.
     47 // Use a minimum of 2, and add one for each 256MB of physical memory you have.
     48 // Cap at 5, the thinking being that even if you have a gigantic amount of
     49 // RAM, there's a limit to how much caching helps beyond a certain number
     50 // of tabs. If users *really* want unlimited stores, allow it via the
     51 // --disable-backing-store-limit flag.
     52 static size_t MaxNumberOfBackingStores() {
     53   static bool unlimited = false;
     54   const CommandLine& command = *CommandLine::ForCurrentProcess();
     55   unlimited = command.HasSwitch(switches::kDisableBackingStoreLimit);
     56 
     57 
     58   if (unlimited) {
     59     // 100 isn't truly unlimited, but given that backing stores count against
     60     // GDI memory, it's well past any reasonable number. Many systems will
     61     // begin to fail in strange ways well before they hit 100 stores.
     62     return 100;
     63   } else {
     64     return std::min(5, 2 + (base::SysInfo::AmountOfPhysicalMemoryMB() / 256));
     65   }
     66 }
     67 
     68 // The maximum about of memory to use for all BackingStoreCache object combined.
     69 static size_t MaxBackingStoreMemory() {
     70   // Compute in terms of the number of large monitor's worth of backing-store.
     71   return MaxNumberOfBackingStores() * kMemoryMultiplier;
     72 }
     73 
     74 // Expires the given |backing_store| from |cache|.
     75 void ExpireBackingStoreAt(BackingStoreCache* cache,
     76                           BackingStoreCache::iterator backing_store) {
     77   cache->Erase(backing_store);
     78 }
     79 
     80 size_t ExpireLastBackingStore(BackingStoreCache* cache) {
     81   if (cache->size() < 1)
     82     return 0;
     83 
     84   // Crazy C++ alert: rbegin.base() is a forward iterator pointing to end(),
     85   // so we need to do -- to move one back to the actual last item.
     86   BackingStoreCache::iterator entry = --cache->rbegin().base();
     87   size_t entry_size = entry->second->MemorySize();
     88   ExpireBackingStoreAt(cache, entry);
     89   return entry_size;
     90 }
     91 
     92 void CreateCacheSpace(size_t size) {
     93   // Given a request for |size|, first free from the large cache (until there's
     94   // only one item left) and then do the same from the small cache if we still
     95   // don't have enough.
     96   while (size > 0 && (large_cache->size() > 1 || small_cache->size() > 1)) {
     97     BackingStoreCache* cache =
     98         (large_cache->size() > 1) ? large_cache : small_cache;
     99     while (size > 0 && cache->size() > 1) {
    100       size_t entry_size = ExpireLastBackingStore(cache);
    101       if (size > entry_size)
    102         size -= entry_size;
    103       else
    104         size = 0;
    105     }
    106   }
    107   DCHECK(size == 0);
    108 }
    109 
    110 // Creates the backing store for the host based on the dimensions passed in.
    111 // Removes the existing backing store if there is one.
    112 BackingStore* CreateBackingStore(RenderWidgetHost* host,
    113                                  const gfx::Size& backing_store_size) {
    114   // Remove any existing backing store in case we're replacing it.
    115   BackingStoreManager::RemoveBackingStore(host);
    116 
    117   if (!large_cache) {
    118     large_cache = new BackingStoreCache(BackingStoreCache::NO_AUTO_EVICT);
    119     small_cache = new BackingStoreCache(BackingStoreCache::NO_AUTO_EVICT);
    120   }
    121 
    122   // TODO(erikkay) 32bpp is not always accurate
    123   size_t new_mem = backing_store_size.GetArea() * 4;
    124   size_t current_mem = BackingStoreManager::MemorySize();
    125   size_t max_mem = MaxBackingStoreMemory();
    126   DCHECK(new_mem < max_mem);
    127   if (current_mem + new_mem > max_mem) {
    128     // Need to remove old backing stores to make room for the new one. We
    129     // don't want to do this when the backing store is being replace by a new
    130     // one for the same WebContents, but this case won't get called then: we'll
    131     // have removed the old one in the RemoveBackingStore above, and the cache
    132     // won't be over-sized.
    133     CreateCacheSpace((current_mem + new_mem) - max_mem);
    134   }
    135   DCHECK((BackingStoreManager::MemorySize() + new_mem) <= max_mem);
    136 
    137   BackingStoreCache* cache;
    138   if (new_mem > kSmallThreshold) {
    139     // Limit the number of large backing stores (tabs) to the memory tier number
    140     // (between 2-5). While we allow a larger amount of memory for people who
    141     // have large windows, this means that those who use small browser windows
    142     // won't ever cache more than 5 tabs, so they pay a smaller memory cost.
    143     if (large_cache->size() >= MaxNumberOfBackingStores())
    144       ExpireLastBackingStore(large_cache);
    145     cache = large_cache;
    146   } else {
    147     cache = small_cache;
    148   }
    149   BackingStore* backing_store = RenderWidgetHostImpl::From(
    150       host)->AllocBackingStore(backing_store_size);
    151   if (backing_store)
    152     cache->Put(host, backing_store);
    153   return backing_store;
    154 }
    155 
    156 int ComputeTotalArea(const std::vector<gfx::Rect>& rects) {
    157   // We assume that the given rects are non-overlapping, which is a property of
    158   // the paint rects generated by the PaintAggregator.
    159 #ifndef NDEBUG
    160   for (size_t i = 0; i < rects.size(); ++i) {
    161     for (size_t j = 0; j < rects.size(); ++j) {
    162       if (i != j)
    163         DCHECK(!rects[i].Intersects(rects[j]));
    164     }
    165   }
    166 #endif
    167   int area = 0;
    168   for (size_t i = 0; i < rects.size(); ++i)
    169     area += rects[i].size().GetArea();
    170   return area;
    171 }
    172 
    173 }  // namespace
    174 
    175 // BackingStoreManager ---------------------------------------------------------
    176 
    177 // static
    178 BackingStore* BackingStoreManager::GetBackingStore(
    179     RenderWidgetHost* host,
    180     const gfx::Size& desired_size) {
    181   BackingStore* backing_store = Lookup(host);
    182   if (backing_store) {
    183     // If we already have a backing store, then make sure it is the correct
    184     // size.
    185     if (backing_store->size() == desired_size)
    186       return backing_store;
    187     backing_store = NULL;
    188   }
    189 
    190   return backing_store;
    191 }
    192 
    193 // static
    194 void BackingStoreManager::PrepareBackingStore(
    195     RenderWidgetHost* host,
    196     const gfx::Size& backing_store_size,
    197     TransportDIB::Id bitmap,
    198     const gfx::Rect& bitmap_rect,
    199     const std::vector<gfx::Rect>& copy_rects,
    200     float scale_factor,
    201     const base::Closure& completion_callback,
    202     bool* needs_full_paint,
    203     bool* scheduled_completion_callback) {
    204   BackingStore* backing_store = GetBackingStore(host, backing_store_size);
    205   if (!backing_store) {
    206     // We need to get Webkit to generate a new paint here, as we
    207     // don't have a previous snapshot.
    208     if (bitmap_rect.size() != backing_store_size ||
    209         bitmap_rect.x() != 0 || bitmap_rect.y() != 0 ||
    210         ComputeTotalArea(copy_rects) != backing_store_size.GetArea() ||
    211         !(backing_store = CreateBackingStore(host, backing_store_size))) {
    212       DCHECK(needs_full_paint != NULL);
    213       *needs_full_paint = true;
    214       *scheduled_completion_callback = false;
    215       // Makes no sense to paint the transport dib if we are going
    216       // to request a full paint.
    217       return;
    218     }
    219   }
    220 
    221   backing_store->PaintToBackingStore(host->GetProcess(), bitmap,
    222                                      bitmap_rect, copy_rects, scale_factor,
    223                                      completion_callback,
    224                                      scheduled_completion_callback);
    225 }
    226 
    227 // static
    228 BackingStore* BackingStoreManager::Lookup(RenderWidgetHost* host) {
    229   if (large_cache) {
    230     BackingStoreCache::iterator it = large_cache->Get(host);
    231     if (it != large_cache->end())
    232       return it->second;
    233 
    234     // This moves host to the front of the MRU.
    235     it = small_cache->Get(host);
    236     if (it != small_cache->end())
    237       return it->second;
    238   }
    239   return NULL;
    240 }
    241 
    242 // static
    243 void BackingStoreManager::RemoveBackingStore(RenderWidgetHost* host) {
    244   if (!large_cache)
    245     return;
    246 
    247   BackingStoreCache* cache = large_cache;
    248   BackingStoreCache::iterator it = cache->Peek(host);
    249   if (it == cache->end()) {
    250     cache = small_cache;
    251     it = cache->Peek(host);
    252     if (it == cache->end())
    253       return;
    254   }
    255   cache->Erase(it);
    256 }
    257 
    258 // static
    259 void BackingStoreManager::RemoveAllBackingStores() {
    260   if (large_cache) {
    261     large_cache->Clear();
    262     small_cache->Clear();
    263   }
    264 }
    265 
    266 // static
    267 size_t BackingStoreManager::MemorySize() {
    268   if (!large_cache)
    269     return 0;
    270 
    271   size_t mem = 0;
    272   BackingStoreCache::iterator it;
    273   for (it = large_cache->begin(); it != large_cache->end(); ++it)
    274     mem += it->second->MemorySize();
    275 
    276   for (it = small_cache->begin(); it != small_cache->end(); ++it)
    277     mem += it->second->MemorySize();
    278 
    279   return mem;
    280 }
    281 
    282 }  // namespace content
    283