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.
5 #include "content/browser/renderer_host/backing_store_manager.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"
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
*>
25 BackingStoreCache
* large_cache
= NULL
;
26 BackingStoreCache
* small_cache
= NULL
;
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;
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
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
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
);
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.
64 return std::min(5, 2 + (base::SysInfo::AmountOfPhysicalMemoryMB() / 256));
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
;
74 // Expires the given |backing_store| from |cache|.
75 void ExpireBackingStoreAt(BackingStoreCache
* cache
,
76 BackingStoreCache::iterator backing_store
) {
77 cache
->Erase(backing_store
);
80 size_t ExpireLastBackingStore(BackingStoreCache
* cache
) {
81 if (cache
->size() < 1)
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
);
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
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
)
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
);
118 large_cache
= new BackingStoreCache(BackingStoreCache::NO_AUTO_EVICT
);
119 small_cache
= new BackingStoreCache(BackingStoreCache::NO_AUTO_EVICT
);
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
);
135 DCHECK((BackingStoreManager::MemorySize() + new_mem
) <= max_mem
);
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
);
149 BackingStore
* backing_store
= RenderWidgetHostImpl::From(
150 host
)->AllocBackingStore(backing_store_size
);
152 cache
->Put(host
, backing_store
);
153 return backing_store
;
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.
160 for (size_t i
= 0; i
< rects
.size(); ++i
) {
161 for (size_t j
= 0; j
< rects
.size(); ++j
) {
163 DCHECK(!rects
[i
].Intersects(rects
[j
]));
168 for (size_t i
= 0; i
< rects
.size(); ++i
)
169 area
+= rects
[i
].size().GetArea();
175 // BackingStoreManager ---------------------------------------------------------
178 BackingStore
* BackingStoreManager::GetBackingStore(
179 RenderWidgetHost
* host
,
180 const gfx::Size
& desired_size
) {
181 BackingStore
* backing_store
= Lookup(host
);
183 // If we already have a backing store, then make sure it is the correct
185 if (backing_store
->size() == desired_size
)
186 return backing_store
;
187 backing_store
= NULL
;
190 return backing_store
;
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
,
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.
221 backing_store
->PaintToBackingStore(host
->GetProcess(), bitmap
,
222 bitmap_rect
, copy_rects
, scale_factor
,
224 scheduled_completion_callback
);
228 BackingStore
* BackingStoreManager::Lookup(RenderWidgetHost
* host
) {
230 BackingStoreCache::iterator it
= large_cache
->Get(host
);
231 if (it
!= large_cache
->end())
234 // This moves host to the front of the MRU.
235 it
= small_cache
->Get(host
);
236 if (it
!= small_cache
->end())
243 void BackingStoreManager::RemoveBackingStore(RenderWidgetHost
* host
) {
247 BackingStoreCache
* cache
= large_cache
;
248 BackingStoreCache::iterator it
= cache
->Peek(host
);
249 if (it
== cache
->end()) {
251 it
= cache
->Peek(host
);
252 if (it
== cache
->end())
259 void BackingStoreManager::RemoveAllBackingStores() {
261 large_cache
->Clear();
262 small_cache
->Clear();
267 size_t BackingStoreManager::MemorySize() {
272 BackingStoreCache::iterator it
;
273 for (it
= large_cache
->begin(); it
!= large_cache
->end(); ++it
)
274 mem
+= it
->second
->MemorySize();
276 for (it
= small_cache
->begin(); it
!= small_cache
->end(); ++it
)
277 mem
+= it
->second
->MemorySize();
282 } // namespace content