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