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