| // Copyright (c) 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 "content/browser/renderer_host/backing_store_manager.h" |
| |
| #include "base/bind.h" |
| #include "base/command_line.h" |
| #include "base/containers/mru_cache.h" |
| #include "base/sys_info.h" |
| #include "content/browser/renderer_host/backing_store.h" |
| #include "content/browser/renderer_host/render_widget_host_impl.h" |
| #include "content/public/common/content_switches.h" |
| |
| namespace content { |
| namespace { |
| |
| // There are two separate caches, |large_cache| and |small_cache|. large_cache |
| // is meant for large items (tabs, popup windows), while small_cache is meant |
| // for small items (extension popups, HTML5 notifications). The idea is that |
| // we'll almost always try to evict from large_cache first since small_cache |
| // items will tend to be visible more of the time. |
| typedef base::OwningMRUCache<RenderWidgetHost*, BackingStore*> |
| BackingStoreCache; |
| BackingStoreCache* large_cache = NULL; |
| BackingStoreCache* small_cache = NULL; |
| |
| // Threshold is based on a single large-monitor-width toolstrip. |
| // (32bpp, 32 pixels high, 1920 pixels wide) |
| // TODO(aa): The extension system no longer supports toolstrips, but we think |
| // this might be helping for other examples of small HTML views in Chrome. |
| // Maybe this cache should be redesigned to simply prefer smaller objects to |
| // larger ones, rather than having a fixed threshold. |
| // For more background, see: crbug.com/100506. |
| const size_t kSmallThreshold = 4 * 32 * 1920; |
| |
| // Pick a large monitor size to use as a multiplier. This is multiplied by the |
| // max number of large backing stores (usually tabs) to pick a ceiling on the |
| // max memory to use. |
| // TODO(erikkay) Perhaps we should actually use monitor size? That way we |
| // could make an assertion like "worse case, there are two tabs in the cache". |
| // However, the small_cache might mess up these calculations a bit. |
| // TODO(erikkay) 32bpp assumption isn't great. |
| const size_t kMemoryMultiplier = 4 * 1920 * 1200; // ~9MB |
| |
| // The maximum number of large BackingStoreCache objects (tabs) to use. |
| // Use a minimum of 2, and add one for each 256MB of physical memory you have. |
| // Cap at 5, the thinking being that even if you have a gigantic amount of |
| // RAM, there's a limit to how much caching helps beyond a certain number |
| // of tabs. If users *really* want unlimited stores, allow it via the |
| // --disable-backing-store-limit flag. |
| static size_t MaxNumberOfBackingStores() { |
| static bool unlimited = false; |
| const CommandLine& command = *CommandLine::ForCurrentProcess(); |
| unlimited = command.HasSwitch(switches::kDisableBackingStoreLimit); |
| |
| |
| if (unlimited) { |
| // 100 isn't truly unlimited, but given that backing stores count against |
| // GDI memory, it's well past any reasonable number. Many systems will |
| // begin to fail in strange ways well before they hit 100 stores. |
| return 100; |
| } else { |
| return std::min(5, 2 + (base::SysInfo::AmountOfPhysicalMemoryMB() / 256)); |
| } |
| } |
| |
| // The maximum about of memory to use for all BackingStoreCache object combined. |
| static size_t MaxBackingStoreMemory() { |
| // Compute in terms of the number of large monitor's worth of backing-store. |
| return MaxNumberOfBackingStores() * kMemoryMultiplier; |
| } |
| |
| // Expires the given |backing_store| from |cache|. |
| void ExpireBackingStoreAt(BackingStoreCache* cache, |
| BackingStoreCache::iterator backing_store) { |
| cache->Erase(backing_store); |
| } |
| |
| size_t ExpireLastBackingStore(BackingStoreCache* cache) { |
| if (cache->size() < 1) |
| return 0; |
| |
| // Crazy C++ alert: rbegin.base() is a forward iterator pointing to end(), |
| // so we need to do -- to move one back to the actual last item. |
| BackingStoreCache::iterator entry = --cache->rbegin().base(); |
| size_t entry_size = entry->second->MemorySize(); |
| ExpireBackingStoreAt(cache, entry); |
| return entry_size; |
| } |
| |
| void CreateCacheSpace(size_t size) { |
| // Given a request for |size|, first free from the large cache (until there's |
| // only one item left) and then do the same from the small cache if we still |
| // don't have enough. |
| while (size > 0 && (large_cache->size() > 1 || small_cache->size() > 1)) { |
| BackingStoreCache* cache = |
| (large_cache->size() > 1) ? large_cache : small_cache; |
| while (size > 0 && cache->size() > 1) { |
| size_t entry_size = ExpireLastBackingStore(cache); |
| if (size > entry_size) |
| size -= entry_size; |
| else |
| size = 0; |
| } |
| } |
| DCHECK(size == 0); |
| } |
| |
| // Creates the backing store for the host based on the dimensions passed in. |
| // Removes the existing backing store if there is one. |
| BackingStore* CreateBackingStore(RenderWidgetHost* host, |
| const gfx::Size& backing_store_size) { |
| // Remove any existing backing store in case we're replacing it. |
| BackingStoreManager::RemoveBackingStore(host); |
| |
| if (!large_cache) { |
| large_cache = new BackingStoreCache(BackingStoreCache::NO_AUTO_EVICT); |
| small_cache = new BackingStoreCache(BackingStoreCache::NO_AUTO_EVICT); |
| } |
| |
| // TODO(erikkay) 32bpp is not always accurate |
| size_t new_mem = backing_store_size.GetArea() * 4; |
| size_t current_mem = BackingStoreManager::MemorySize(); |
| size_t max_mem = MaxBackingStoreMemory(); |
| DCHECK(new_mem < max_mem); |
| if (current_mem + new_mem > max_mem) { |
| // Need to remove old backing stores to make room for the new one. We |
| // don't want to do this when the backing store is being replace by a new |
| // one for the same WebContents, but this case won't get called then: we'll |
| // have removed the old one in the RemoveBackingStore above, and the cache |
| // won't be over-sized. |
| CreateCacheSpace((current_mem + new_mem) - max_mem); |
| } |
| DCHECK((BackingStoreManager::MemorySize() + new_mem) <= max_mem); |
| |
| BackingStoreCache* cache; |
| if (new_mem > kSmallThreshold) { |
| // Limit the number of large backing stores (tabs) to the memory tier number |
| // (between 2-5). While we allow a larger amount of memory for people who |
| // have large windows, this means that those who use small browser windows |
| // won't ever cache more than 5 tabs, so they pay a smaller memory cost. |
| if (large_cache->size() >= MaxNumberOfBackingStores()) |
| ExpireLastBackingStore(large_cache); |
| cache = large_cache; |
| } else { |
| cache = small_cache; |
| } |
| BackingStore* backing_store = RenderWidgetHostImpl::From( |
| host)->AllocBackingStore(backing_store_size); |
| if (backing_store) |
| cache->Put(host, backing_store); |
| return backing_store; |
| } |
| |
| int ComputeTotalArea(const std::vector<gfx::Rect>& rects) { |
| // We assume that the given rects are non-overlapping, which is a property of |
| // the paint rects generated by the PaintAggregator. |
| #ifndef NDEBUG |
| for (size_t i = 0; i < rects.size(); ++i) { |
| for (size_t j = 0; j < rects.size(); ++j) { |
| if (i != j) |
| DCHECK(!rects[i].Intersects(rects[j])); |
| } |
| } |
| #endif |
| int area = 0; |
| for (size_t i = 0; i < rects.size(); ++i) |
| area += rects[i].size().GetArea(); |
| return area; |
| } |
| |
| } // namespace |
| |
| // BackingStoreManager --------------------------------------------------------- |
| |
| // static |
| BackingStore* BackingStoreManager::GetBackingStore( |
| RenderWidgetHost* host, |
| const gfx::Size& desired_size) { |
| BackingStore* backing_store = Lookup(host); |
| if (backing_store) { |
| // If we already have a backing store, then make sure it is the correct |
| // size. |
| if (backing_store->size() == desired_size) |
| return backing_store; |
| backing_store = NULL; |
| } |
| |
| return backing_store; |
| } |
| |
| // static |
| void BackingStoreManager::PrepareBackingStore( |
| RenderWidgetHost* host, |
| const gfx::Size& backing_store_size, |
| TransportDIB::Id bitmap, |
| const gfx::Rect& bitmap_rect, |
| const std::vector<gfx::Rect>& copy_rects, |
| float scale_factor, |
| const base::Closure& completion_callback, |
| bool* needs_full_paint, |
| bool* scheduled_completion_callback) { |
| BackingStore* backing_store = GetBackingStore(host, backing_store_size); |
| if (!backing_store) { |
| // We need to get Webkit to generate a new paint here, as we |
| // don't have a previous snapshot. |
| if (bitmap_rect.size() != backing_store_size || |
| bitmap_rect.x() != 0 || bitmap_rect.y() != 0 || |
| ComputeTotalArea(copy_rects) != backing_store_size.GetArea() || |
| !(backing_store = CreateBackingStore(host, backing_store_size))) { |
| DCHECK(needs_full_paint != NULL); |
| *needs_full_paint = true; |
| *scheduled_completion_callback = false; |
| // Makes no sense to paint the transport dib if we are going |
| // to request a full paint. |
| return; |
| } |
| } |
| |
| backing_store->PaintToBackingStore(host->GetProcess(), bitmap, |
| bitmap_rect, copy_rects, scale_factor, |
| completion_callback, |
| scheduled_completion_callback); |
| } |
| |
| // static |
| BackingStore* BackingStoreManager::Lookup(RenderWidgetHost* host) { |
| if (large_cache) { |
| BackingStoreCache::iterator it = large_cache->Get(host); |
| if (it != large_cache->end()) |
| return it->second; |
| |
| // This moves host to the front of the MRU. |
| it = small_cache->Get(host); |
| if (it != small_cache->end()) |
| return it->second; |
| } |
| return NULL; |
| } |
| |
| // static |
| void BackingStoreManager::RemoveBackingStore(RenderWidgetHost* host) { |
| if (!large_cache) |
| return; |
| |
| BackingStoreCache* cache = large_cache; |
| BackingStoreCache::iterator it = cache->Peek(host); |
| if (it == cache->end()) { |
| cache = small_cache; |
| it = cache->Peek(host); |
| if (it == cache->end()) |
| return; |
| } |
| cache->Erase(it); |
| } |
| |
| // static |
| void BackingStoreManager::RemoveAllBackingStores() { |
| if (large_cache) { |
| large_cache->Clear(); |
| small_cache->Clear(); |
| } |
| } |
| |
| // static |
| size_t BackingStoreManager::MemorySize() { |
| if (!large_cache) |
| return 0; |
| |
| size_t mem = 0; |
| BackingStoreCache::iterator it; |
| for (it = large_cache->begin(); it != large_cache->end(); ++it) |
| mem += it->second->MemorySize(); |
| |
| for (it = small_cache->begin(); it != small_cache->end(); ++it) |
| mem += it->second->MemorySize(); |
| |
| return mem; |
| } |
| |
| } // namespace content |