blob: 4d79b0ff4f2fec1432eb0e326b15bee0b449e383 [file] [log] [blame]
// 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