1 // Copyright 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 "cc/resources/prioritized_resource_manager.h" 6 7 #include <algorithm> 8 9 #include "base/debug/trace_event.h" 10 #include "base/stl_util.h" 11 #include "cc/resources/prioritized_resource.h" 12 #include "cc/resources/priority_calculator.h" 13 #include "cc/trees/proxy.h" 14 15 namespace cc { 16 17 PrioritizedResourceManager::PrioritizedResourceManager(const Proxy* proxy) 18 : max_memory_limit_bytes_(DefaultMemoryAllocationLimit()), 19 external_priority_cutoff_(PriorityCalculator::AllowEverythingCutoff()), 20 memory_use_bytes_(0), 21 memory_above_cutoff_bytes_(0), 22 max_memory_needed_bytes_(0), 23 memory_available_bytes_(0), 24 proxy_(proxy), 25 backings_tail_not_sorted_(false), 26 memory_visible_bytes_(0), 27 memory_visible_and_nearby_bytes_(0), 28 memory_visible_last_pushed_bytes_(0), 29 memory_visible_and_nearby_last_pushed_bytes_(0) {} 30 31 PrioritizedResourceManager::~PrioritizedResourceManager() { 32 while (textures_.size() > 0) 33 UnregisterTexture(*textures_.begin()); 34 35 UnlinkAndClearEvictedBackings(); 36 DCHECK(evicted_backings_.empty()); 37 38 // Each remaining backing is a leaked opengl texture. There should be none. 39 DCHECK(backings_.empty()); 40 } 41 42 size_t PrioritizedResourceManager::MemoryVisibleBytes() const { 43 DCHECK(proxy_->IsImplThread()); 44 return memory_visible_last_pushed_bytes_; 45 } 46 47 size_t PrioritizedResourceManager::MemoryVisibleAndNearbyBytes() const { 48 DCHECK(proxy_->IsImplThread()); 49 return memory_visible_and_nearby_last_pushed_bytes_; 50 } 51 52 void PrioritizedResourceManager::PrioritizeTextures() { 53 TRACE_EVENT0("cc", "PrioritizedResourceManager::PrioritizeTextures"); 54 DCHECK(proxy_->IsMainThread()); 55 56 // Sorting textures in this function could be replaced by a slightly 57 // modified O(n) quick-select to partition textures rather than 58 // sort them (if performance of the sort becomes an issue). 59 60 TextureVector& sorted_textures = temp_texture_vector_; 61 sorted_textures.clear(); 62 63 // Copy all textures into a vector, sort them, and collect memory requirements 64 // statistics. 65 memory_visible_bytes_ = 0; 66 memory_visible_and_nearby_bytes_ = 0; 67 for (TextureSet::iterator it = textures_.begin(); it != textures_.end(); 68 ++it) { 69 PrioritizedResource* texture = (*it); 70 sorted_textures.push_back(texture); 71 if (PriorityCalculator::priority_is_higher( 72 texture->request_priority(), 73 PriorityCalculator::AllowVisibleOnlyCutoff())) 74 memory_visible_bytes_ += texture->bytes(); 75 if (PriorityCalculator::priority_is_higher( 76 texture->request_priority(), 77 PriorityCalculator::AllowVisibleAndNearbyCutoff())) 78 memory_visible_and_nearby_bytes_ += texture->bytes(); 79 } 80 std::sort(sorted_textures.begin(), sorted_textures.end(), CompareTextures); 81 82 // Compute a priority cutoff based on memory pressure 83 memory_available_bytes_ = max_memory_limit_bytes_; 84 priority_cutoff_ = external_priority_cutoff_; 85 size_t memory_bytes = 0; 86 for (TextureVector::iterator it = sorted_textures.begin(); 87 it != sorted_textures.end(); 88 ++it) { 89 if ((*it)->is_self_managed()) { 90 // Account for self-managed memory immediately by reducing the memory 91 // available (since it never gets acquired). 92 size_t new_memory_bytes = memory_bytes + (*it)->bytes(); 93 if (new_memory_bytes > memory_available_bytes_) { 94 priority_cutoff_ = (*it)->request_priority(); 95 memory_available_bytes_ = memory_bytes; 96 break; 97 } 98 memory_available_bytes_ -= (*it)->bytes(); 99 } else { 100 size_t new_memory_bytes = memory_bytes + (*it)->bytes(); 101 if (new_memory_bytes > memory_available_bytes_) { 102 priority_cutoff_ = (*it)->request_priority(); 103 break; 104 } 105 memory_bytes = new_memory_bytes; 106 } 107 } 108 109 // Disallow any textures with priority below the external cutoff to have 110 // backings. 111 for (TextureVector::iterator it = sorted_textures.begin(); 112 it != sorted_textures.end(); 113 ++it) { 114 PrioritizedResource* texture = (*it); 115 if (!PriorityCalculator::priority_is_higher(texture->request_priority(), 116 external_priority_cutoff_) && 117 texture->have_backing_texture()) 118 texture->Unlink(); 119 } 120 121 // Only allow textures if they are higher than the cutoff. All textures 122 // of the same priority are accepted or rejected together, rather than 123 // being partially allowed randomly. 124 max_memory_needed_bytes_ = 0; 125 memory_above_cutoff_bytes_ = 0; 126 for (TextureVector::iterator it = sorted_textures.begin(); 127 it != sorted_textures.end(); 128 ++it) { 129 PrioritizedResource* resource = *it; 130 bool is_above_priority_cutoff = PriorityCalculator::priority_is_higher( 131 resource->request_priority(), priority_cutoff_); 132 resource->set_above_priority_cutoff(is_above_priority_cutoff); 133 if (!resource->is_self_managed()) { 134 max_memory_needed_bytes_ += resource->bytes(); 135 if (is_above_priority_cutoff) 136 memory_above_cutoff_bytes_ += resource->bytes(); 137 } 138 } 139 sorted_textures.clear(); 140 141 DCHECK_LE(memory_above_cutoff_bytes_, memory_available_bytes_); 142 DCHECK_LE(MemoryAboveCutoffBytes(), MaxMemoryLimitBytes()); 143 } 144 145 void PrioritizedResourceManager::PushTexturePrioritiesToBackings() { 146 TRACE_EVENT0("cc", 147 "PrioritizedResourceManager::PushTexturePrioritiesToBackings"); 148 DCHECK(proxy_->IsImplThread() && proxy_->IsMainThreadBlocked()); 149 150 AssertInvariants(); 151 for (BackingList::iterator it = backings_.begin(); it != backings_.end(); 152 ++it) 153 (*it)->UpdatePriority(); 154 SortBackings(); 155 AssertInvariants(); 156 157 // Push memory requirements to the impl thread structure. 158 memory_visible_last_pushed_bytes_ = memory_visible_bytes_; 159 memory_visible_and_nearby_last_pushed_bytes_ = 160 memory_visible_and_nearby_bytes_; 161 } 162 163 void PrioritizedResourceManager::UpdateBackingsInDrawingImplTree() { 164 TRACE_EVENT0("cc", 165 "PrioritizedResourceManager::UpdateBackingsInDrawingImplTree"); 166 DCHECK(proxy_->IsImplThread() && proxy_->IsMainThreadBlocked()); 167 168 AssertInvariants(); 169 for (BackingList::iterator it = backings_.begin(); it != backings_.end(); 170 ++it) { 171 PrioritizedResource::Backing* backing = (*it); 172 backing->UpdateInDrawingImplTree(); 173 } 174 SortBackings(); 175 AssertInvariants(); 176 } 177 178 void PrioritizedResourceManager::SortBackings() { 179 TRACE_EVENT0("cc", "PrioritizedResourceManager::SortBackings"); 180 DCHECK(proxy_->IsImplThread()); 181 182 // Put backings in eviction/recycling order. 183 backings_.sort(CompareBackings); 184 backings_tail_not_sorted_ = false; 185 } 186 187 void PrioritizedResourceManager::ClearPriorities() { 188 DCHECK(proxy_->IsMainThread()); 189 for (TextureSet::iterator it = textures_.begin(); it != textures_.end(); 190 ++it) { 191 // TODO(reveman): We should remove this and just set all priorities to 192 // PriorityCalculator::lowestPriority() once we have priorities for all 193 // textures (we can't currently calculate distances for off-screen 194 // textures). 195 (*it)->set_request_priority( 196 PriorityCalculator::LingeringPriority((*it)->request_priority())); 197 } 198 } 199 200 bool PrioritizedResourceManager::RequestLate(PrioritizedResource* texture) { 201 DCHECK(proxy_->IsMainThread()); 202 203 // This is already above cutoff, so don't double count it's memory below. 204 if (texture->is_above_priority_cutoff()) 205 return true; 206 207 // Allow textures that have priority equal to the cutoff, but not strictly 208 // lower. 209 if (PriorityCalculator::priority_is_lower(texture->request_priority(), 210 priority_cutoff_)) 211 return false; 212 213 // Disallow textures that do not have a priority strictly higher than the 214 // external cutoff. 215 if (!PriorityCalculator::priority_is_higher(texture->request_priority(), 216 external_priority_cutoff_)) 217 return false; 218 219 size_t new_memory_bytes = memory_above_cutoff_bytes_ + texture->bytes(); 220 if (new_memory_bytes > memory_available_bytes_) 221 return false; 222 223 memory_above_cutoff_bytes_ = new_memory_bytes; 224 texture->set_above_priority_cutoff(true); 225 return true; 226 } 227 228 void PrioritizedResourceManager::AcquireBackingTextureIfNeeded( 229 PrioritizedResource* texture, 230 ResourceProvider* resource_provider) { 231 DCHECK(proxy_->IsImplThread() && proxy_->IsMainThreadBlocked()); 232 DCHECK(!texture->is_self_managed()); 233 DCHECK(texture->is_above_priority_cutoff()); 234 if (texture->backing() || !texture->is_above_priority_cutoff()) 235 return; 236 237 // Find a backing below, by either recycling or allocating. 238 PrioritizedResource::Backing* backing = NULL; 239 240 // First try to recycle 241 for (BackingList::iterator it = backings_.begin(); it != backings_.end(); 242 ++it) { 243 if (!(*it)->CanBeRecycled()) 244 break; 245 if (resource_provider->InUseByConsumer((*it)->id())) 246 continue; 247 if ((*it)->size() == texture->size() && 248 (*it)->format() == texture->format()) { 249 backing = (*it); 250 backings_.erase(it); 251 break; 252 } 253 } 254 255 // Otherwise reduce memory and just allocate a new backing texures. 256 if (!backing) { 257 EvictBackingsToReduceMemory(memory_available_bytes_ - texture->bytes(), 258 PriorityCalculator::AllowEverythingCutoff(), 259 EVICT_ONLY_RECYCLABLE, 260 DO_NOT_UNLINK_BACKINGS, 261 resource_provider); 262 backing = 263 CreateBacking(texture->size(), texture->format(), resource_provider); 264 } 265 266 // Move the used backing to the end of the eviction list, and note that 267 // the tail is not sorted. 268 if (backing->owner()) 269 backing->owner()->Unlink(); 270 texture->Link(backing); 271 backings_.push_back(backing); 272 backings_tail_not_sorted_ = true; 273 274 // Update the backing's priority from its new owner. 275 backing->UpdatePriority(); 276 } 277 278 bool PrioritizedResourceManager::EvictBackingsToReduceMemory( 279 size_t limit_bytes, 280 int priority_cutoff, 281 EvictionPolicy eviction_policy, 282 UnlinkPolicy unlink_policy, 283 ResourceProvider* resource_provider) { 284 DCHECK(proxy_->IsImplThread()); 285 if (unlink_policy == UNLINK_BACKINGS) 286 DCHECK(proxy_->IsMainThreadBlocked()); 287 if (MemoryUseBytes() <= limit_bytes && 288 PriorityCalculator::AllowEverythingCutoff() == priority_cutoff) 289 return false; 290 291 // Destroy backings until we are below the limit, 292 // or until all backings remaining are above the cutoff. 293 bool evicted_anything = false; 294 while (backings_.size() > 0) { 295 PrioritizedResource::Backing* backing = backings_.front(); 296 if (MemoryUseBytes() <= limit_bytes && 297 PriorityCalculator::priority_is_higher( 298 backing->request_priority_at_last_priority_update(), 299 priority_cutoff)) 300 break; 301 if (eviction_policy == EVICT_ONLY_RECYCLABLE && !backing->CanBeRecycled()) 302 break; 303 if (unlink_policy == UNLINK_BACKINGS && backing->owner()) 304 backing->owner()->Unlink(); 305 EvictFirstBackingResource(resource_provider); 306 evicted_anything = true; 307 } 308 return evicted_anything; 309 } 310 311 void PrioritizedResourceManager::ReduceWastedMemory( 312 ResourceProvider* resource_provider) { 313 // We currently collect backings from deleted textures for later recycling. 314 // However, if we do that forever we will always use the max limit even if 315 // we really need very little memory. This should probably be solved by 316 // reducing the limit externally, but until then this just does some "clean 317 // up" of unused backing textures (any more than 10%). 318 size_t wasted_memory = 0; 319 for (BackingList::iterator it = backings_.begin(); it != backings_.end(); 320 ++it) { 321 if ((*it)->owner()) 322 break; 323 wasted_memory += (*it)->bytes(); 324 } 325 size_t ten_percent_of_memory = memory_available_bytes_ / 10; 326 if (wasted_memory > ten_percent_of_memory) 327 EvictBackingsToReduceMemory(MemoryUseBytes() - 328 (wasted_memory - ten_percent_of_memory), 329 PriorityCalculator::AllowEverythingCutoff(), 330 EVICT_ONLY_RECYCLABLE, 331 DO_NOT_UNLINK_BACKINGS, 332 resource_provider); 333 } 334 335 void PrioritizedResourceManager::ReduceMemory( 336 ResourceProvider* resource_provider) { 337 DCHECK(proxy_->IsImplThread() && proxy_->IsMainThreadBlocked()); 338 EvictBackingsToReduceMemory(memory_available_bytes_, 339 PriorityCalculator::AllowEverythingCutoff(), 340 EVICT_ANYTHING, 341 UNLINK_BACKINGS, 342 resource_provider); 343 DCHECK_LE(MemoryUseBytes(), memory_available_bytes_); 344 345 ReduceWastedMemory(resource_provider); 346 } 347 348 void PrioritizedResourceManager::ClearAllMemory( 349 ResourceProvider* resource_provider) { 350 DCHECK(proxy_->IsImplThread() && proxy_->IsMainThreadBlocked()); 351 if (!resource_provider) { 352 DCHECK(backings_.empty()); 353 return; 354 } 355 EvictBackingsToReduceMemory(0, 356 PriorityCalculator::AllowEverythingCutoff(), 357 EVICT_ANYTHING, 358 DO_NOT_UNLINK_BACKINGS, 359 resource_provider); 360 } 361 362 bool PrioritizedResourceManager::ReduceMemoryOnImplThread( 363 size_t limit_bytes, 364 int priority_cutoff, 365 ResourceProvider* resource_provider) { 366 DCHECK(proxy_->IsImplThread()); 367 DCHECK(resource_provider); 368 // If we are in the process of uploading a new frame then the backings at the 369 // very end of the list are not sorted by priority. Sort them before doing the 370 // eviction. 371 if (backings_tail_not_sorted_) 372 SortBackings(); 373 return EvictBackingsToReduceMemory(limit_bytes, 374 priority_cutoff, 375 EVICT_ANYTHING, 376 DO_NOT_UNLINK_BACKINGS, 377 resource_provider); 378 } 379 380 void PrioritizedResourceManager::ReduceWastedMemoryOnImplThread( 381 ResourceProvider* resource_provider) { 382 DCHECK(proxy_->IsImplThread()); 383 DCHECK(resource_provider); 384 // If we are in the process of uploading a new frame then the backings at the 385 // very end of the list are not sorted by priority. Sort them before doing the 386 // eviction. 387 if (backings_tail_not_sorted_) 388 SortBackings(); 389 ReduceWastedMemory(resource_provider); 390 } 391 392 void PrioritizedResourceManager::UnlinkAndClearEvictedBackings() { 393 DCHECK(proxy_->IsMainThread()); 394 base::AutoLock scoped_lock(evicted_backings_lock_); 395 for (BackingList::const_iterator it = evicted_backings_.begin(); 396 it != evicted_backings_.end(); 397 ++it) { 398 PrioritizedResource::Backing* backing = (*it); 399 if (backing->owner()) 400 backing->owner()->Unlink(); 401 delete backing; 402 } 403 evicted_backings_.clear(); 404 } 405 406 bool PrioritizedResourceManager::LinkedEvictedBackingsExist() const { 407 DCHECK(proxy_->IsImplThread() && proxy_->IsMainThreadBlocked()); 408 base::AutoLock scoped_lock(evicted_backings_lock_); 409 for (BackingList::const_iterator it = evicted_backings_.begin(); 410 it != evicted_backings_.end(); 411 ++it) { 412 if ((*it)->owner()) 413 return true; 414 } 415 return false; 416 } 417 418 void PrioritizedResourceManager::RegisterTexture(PrioritizedResource* texture) { 419 DCHECK(proxy_->IsMainThread()); 420 DCHECK(texture); 421 DCHECK(!texture->resource_manager()); 422 DCHECK(!texture->backing()); 423 DCHECK(!ContainsKey(textures_, texture)); 424 425 texture->set_manager_internal(this); 426 textures_.insert(texture); 427 } 428 429 void PrioritizedResourceManager::UnregisterTexture( 430 PrioritizedResource* texture) { 431 DCHECK(proxy_->IsMainThread() || 432 (proxy_->IsImplThread() && proxy_->IsMainThreadBlocked())); 433 DCHECK(texture); 434 DCHECK(ContainsKey(textures_, texture)); 435 436 ReturnBackingTexture(texture); 437 texture->set_manager_internal(NULL); 438 textures_.erase(texture); 439 texture->set_above_priority_cutoff(false); 440 } 441 442 void PrioritizedResourceManager::ReturnBackingTexture( 443 PrioritizedResource* texture) { 444 DCHECK(proxy_->IsMainThread() || 445 (proxy_->IsImplThread() && proxy_->IsMainThreadBlocked())); 446 if (texture->backing()) 447 texture->Unlink(); 448 } 449 450 PrioritizedResource::Backing* PrioritizedResourceManager::CreateBacking( 451 gfx::Size size, 452 GLenum format, 453 ResourceProvider* resource_provider) { 454 DCHECK(proxy_->IsImplThread() && proxy_->IsMainThreadBlocked()); 455 DCHECK(resource_provider); 456 ResourceProvider::ResourceId resource_id = 457 resource_provider->CreateManagedResource( 458 size, format, ResourceProvider::TextureUsageAny); 459 PrioritizedResource::Backing* backing = new PrioritizedResource::Backing( 460 resource_id, resource_provider, size, format); 461 memory_use_bytes_ += backing->bytes(); 462 return backing; 463 } 464 465 void PrioritizedResourceManager::EvictFirstBackingResource( 466 ResourceProvider* resource_provider) { 467 DCHECK(proxy_->IsImplThread()); 468 DCHECK(resource_provider); 469 DCHECK(!backings_.empty()); 470 PrioritizedResource::Backing* backing = backings_.front(); 471 472 // Note that we create a backing and its resource at the same time, but we 473 // delete the backing structure and its resource in two steps. This is because 474 // we can delete the resource while the main thread is running, but we cannot 475 // unlink backings while the main thread is running. 476 backing->DeleteResource(resource_provider); 477 memory_use_bytes_ -= backing->bytes(); 478 backings_.pop_front(); 479 base::AutoLock scoped_lock(evicted_backings_lock_); 480 evicted_backings_.push_back(backing); 481 } 482 483 void PrioritizedResourceManager::AssertInvariants() { 484 #ifndef NDEBUG 485 DCHECK(proxy_->IsImplThread() && proxy_->IsMainThreadBlocked()); 486 487 // If we hit any of these asserts, there is a bug in this class. To see 488 // where the bug is, call this function at the beginning and end of 489 // every public function. 490 491 // Backings/textures must be doubly-linked and only to other backings/textures 492 // in this manager. 493 for (BackingList::iterator it = backings_.begin(); it != backings_.end(); 494 ++it) { 495 if ((*it)->owner()) { 496 DCHECK(ContainsKey(textures_, (*it)->owner())); 497 DCHECK((*it)->owner()->backing() == (*it)); 498 } 499 } 500 for (TextureSet::iterator it = textures_.begin(); it != textures_.end(); 501 ++it) { 502 PrioritizedResource* texture = (*it); 503 PrioritizedResource::Backing* backing = texture->backing(); 504 base::AutoLock scoped_lock(evicted_backings_lock_); 505 if (backing) { 506 if (backing->ResourceHasBeenDeleted()) { 507 DCHECK(std::find(backings_.begin(), backings_.end(), backing) == 508 backings_.end()); 509 DCHECK(std::find(evicted_backings_.begin(), 510 evicted_backings_.end(), 511 backing) != evicted_backings_.end()); 512 } else { 513 DCHECK(std::find(backings_.begin(), backings_.end(), backing) != 514 backings_.end()); 515 DCHECK(std::find(evicted_backings_.begin(), 516 evicted_backings_.end(), 517 backing) == evicted_backings_.end()); 518 } 519 DCHECK(backing->owner() == texture); 520 } 521 } 522 523 // At all times, backings that can be evicted must always come before 524 // backings that can't be evicted in the backing texture list (otherwise 525 // ReduceMemory will not find all textures available for eviction/recycling). 526 bool reached_unrecyclable = false; 527 PrioritizedResource::Backing* previous_backing = NULL; 528 for (BackingList::iterator it = backings_.begin(); it != backings_.end(); 529 ++it) { 530 PrioritizedResource::Backing* backing = *it; 531 if (previous_backing && 532 (!backings_tail_not_sorted_ || 533 !backing->was_above_priority_cutoff_at_last_priority_update())) 534 DCHECK(CompareBackings(previous_backing, backing)); 535 if (!backing->CanBeRecycled()) 536 reached_unrecyclable = true; 537 if (reached_unrecyclable) 538 DCHECK(!backing->CanBeRecycled()); 539 else 540 DCHECK(backing->CanBeRecycled()); 541 previous_backing = backing; 542 } 543 #endif 544 } 545 546 const Proxy* PrioritizedResourceManager::ProxyForDebug() const { 547 return proxy_; 548 } 549 550 } // namespace cc 551