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.
5 #include "cc/resources/prioritized_resource_manager.h"
9 #include "base/trace_event/trace_event.h"
10 #include "cc/resources/prioritized_resource.h"
11 #include "cc/resources/priority_calculator.h"
12 #include "cc/trees/proxy.h"
16 PrioritizedResourceManager::PrioritizedResourceManager(const Proxy
* proxy
)
17 : max_memory_limit_bytes_(DefaultMemoryAllocationLimit()),
18 external_priority_cutoff_(PriorityCalculator::AllowEverythingCutoff()),
20 memory_above_cutoff_bytes_(0),
21 max_memory_needed_bytes_(0),
22 memory_available_bytes_(0),
24 backings_tail_not_sorted_(false),
25 memory_visible_bytes_(0),
26 memory_visible_and_nearby_bytes_(0),
27 memory_visible_last_pushed_bytes_(0),
28 memory_visible_and_nearby_last_pushed_bytes_(0) {}
30 PrioritizedResourceManager::~PrioritizedResourceManager() {
31 while (textures_
.size() > 0)
32 UnregisterTexture(*textures_
.begin());
34 UnlinkAndClearEvictedBackings();
35 DCHECK(evicted_backings_
.empty());
37 // Each remaining backing is a leaked opengl texture. There should be none.
38 DCHECK(backings_
.empty());
41 size_t PrioritizedResourceManager::MemoryVisibleBytes() const {
42 DCHECK(proxy_
->IsImplThread());
43 return memory_visible_last_pushed_bytes_
;
46 size_t PrioritizedResourceManager::MemoryVisibleAndNearbyBytes() const {
47 DCHECK(proxy_
->IsImplThread());
48 return memory_visible_and_nearby_last_pushed_bytes_
;
51 void PrioritizedResourceManager::PrioritizeTextures() {
52 TRACE_EVENT0("cc", "PrioritizedResourceManager::PrioritizeTextures");
53 DCHECK(proxy_
->IsMainThread());
55 // Sorting textures in this function could be replaced by a slightly
56 // modified O(n) quick-select to partition textures rather than
57 // sort them (if performance of the sort becomes an issue).
59 TextureVector
& sorted_textures
= temp_texture_vector_
;
60 sorted_textures
.clear();
62 // Copy all textures into a vector, sort them, and collect memory requirements
64 memory_visible_bytes_
= 0;
65 memory_visible_and_nearby_bytes_
= 0;
66 for (TextureSet::iterator it
= textures_
.begin(); it
!= textures_
.end();
68 PrioritizedResource
* texture
= (*it
);
69 sorted_textures
.push_back(texture
);
70 if (PriorityCalculator::priority_is_higher(
71 texture
->request_priority(),
72 PriorityCalculator::AllowVisibleOnlyCutoff()))
73 memory_visible_bytes_
+= texture
->bytes();
74 if (PriorityCalculator::priority_is_higher(
75 texture
->request_priority(),
76 PriorityCalculator::AllowVisibleAndNearbyCutoff()))
77 memory_visible_and_nearby_bytes_
+= texture
->bytes();
79 std::sort(sorted_textures
.begin(), sorted_textures
.end(), CompareTextures
);
81 // Compute a priority cutoff based on memory pressure
82 memory_available_bytes_
= max_memory_limit_bytes_
;
83 priority_cutoff_
= external_priority_cutoff_
;
84 size_t memory_bytes
= 0;
85 for (TextureVector::iterator it
= sorted_textures
.begin();
86 it
!= sorted_textures
.end();
88 if ((*it
)->is_self_managed()) {
89 // Account for self-managed memory immediately by reducing the memory
90 // available (since it never gets acquired).
91 size_t new_memory_bytes
= memory_bytes
+ (*it
)->bytes();
92 if (new_memory_bytes
> memory_available_bytes_
) {
93 priority_cutoff_
= (*it
)->request_priority();
94 memory_available_bytes_
= memory_bytes
;
97 memory_available_bytes_
-= (*it
)->bytes();
99 size_t new_memory_bytes
= memory_bytes
+ (*it
)->bytes();
100 if (new_memory_bytes
> memory_available_bytes_
) {
101 priority_cutoff_
= (*it
)->request_priority();
104 memory_bytes
= new_memory_bytes
;
108 // Disallow any textures with priority below the external cutoff to have
110 for (TextureVector::iterator it
= sorted_textures
.begin();
111 it
!= sorted_textures
.end();
113 PrioritizedResource
* texture
= (*it
);
114 if (!PriorityCalculator::priority_is_higher(texture
->request_priority(),
115 external_priority_cutoff_
) &&
116 texture
->have_backing_texture())
120 // Only allow textures if they are higher than the cutoff. All textures
121 // of the same priority are accepted or rejected together, rather than
122 // being partially allowed randomly.
123 max_memory_needed_bytes_
= 0;
124 memory_above_cutoff_bytes_
= 0;
125 for (TextureVector::iterator it
= sorted_textures
.begin();
126 it
!= sorted_textures
.end();
128 PrioritizedResource
* resource
= *it
;
129 bool is_above_priority_cutoff
= PriorityCalculator::priority_is_higher(
130 resource
->request_priority(), priority_cutoff_
);
131 resource
->set_above_priority_cutoff(is_above_priority_cutoff
);
132 if (!resource
->is_self_managed()) {
133 max_memory_needed_bytes_
+= resource
->bytes();
134 if (is_above_priority_cutoff
)
135 memory_above_cutoff_bytes_
+= resource
->bytes();
138 sorted_textures
.clear();
140 DCHECK_LE(memory_above_cutoff_bytes_
, memory_available_bytes_
);
141 DCHECK_LE(MemoryAboveCutoffBytes(), MaxMemoryLimitBytes());
144 void PrioritizedResourceManager::PushTexturePrioritiesToBackings() {
146 "PrioritizedResourceManager::PushTexturePrioritiesToBackings");
147 DCHECK(proxy_
->IsImplThread() && proxy_
->IsMainThreadBlocked());
150 for (BackingList::iterator it
= backings_
.begin(); it
!= backings_
.end();
152 (*it
)->UpdatePriority();
156 // Push memory requirements to the impl thread structure.
157 memory_visible_last_pushed_bytes_
= memory_visible_bytes_
;
158 memory_visible_and_nearby_last_pushed_bytes_
=
159 memory_visible_and_nearby_bytes_
;
162 void PrioritizedResourceManager::UpdateBackingsState(
163 ResourceProvider
* resource_provider
) {
165 "PrioritizedResourceManager::UpdateBackingsInDrawingImplTree");
166 DCHECK(proxy_
->IsImplThread() && proxy_
->IsMainThreadBlocked());
169 for (BackingList::iterator it
= backings_
.begin(); it
!= backings_
.end();
171 PrioritizedResource::Backing
* backing
= (*it
);
172 backing
->UpdateState(resource_provider
);
178 void PrioritizedResourceManager::SortBackings() {
179 TRACE_EVENT0("cc", "PrioritizedResourceManager::SortBackings");
180 DCHECK(proxy_
->IsImplThread());
182 // Put backings in eviction/recycling order.
183 backings_
.sort(CompareBackings
);
184 backings_tail_not_sorted_
= false;
187 void PrioritizedResourceManager::ClearPriorities() {
188 DCHECK(proxy_
->IsMainThread());
189 for (TextureSet::iterator it
= textures_
.begin(); it
!= textures_
.end();
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
195 (*it
)->set_request_priority(
196 PriorityCalculator::LingeringPriority((*it
)->request_priority()));
200 bool PrioritizedResourceManager::RequestLate(PrioritizedResource
* texture
) {
201 DCHECK(proxy_
->IsMainThread());
203 // This is already above cutoff, so don't double count it's memory below.
204 if (texture
->is_above_priority_cutoff())
207 // Allow textures that have priority equal to the cutoff, but not strictly
209 if (PriorityCalculator::priority_is_lower(texture
->request_priority(),
213 // Disallow textures that do not have a priority strictly higher than the
215 if (!PriorityCalculator::priority_is_higher(texture
->request_priority(),
216 external_priority_cutoff_
))
219 size_t new_memory_bytes
= memory_above_cutoff_bytes_
+ texture
->bytes();
220 if (new_memory_bytes
> memory_available_bytes_
)
223 memory_above_cutoff_bytes_
= new_memory_bytes
;
224 texture
->set_above_priority_cutoff(true);
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())
237 // Find a backing below, by either recycling or allocating.
238 PrioritizedResource::Backing
* backing
= NULL
;
240 // First try to recycle
241 for (BackingList::iterator it
= backings_
.begin(); it
!= backings_
.end();
243 if (!(*it
)->CanBeRecycledIfNotInExternalUse())
245 if (resource_provider
->InUseByConsumer((*it
)->id()))
247 if ((*it
)->size() == texture
->size() &&
248 (*it
)->format() == texture
->format()) {
255 // Otherwise reduce memory and just allocate a new backing texures.
257 EvictBackingsToReduceMemory(memory_available_bytes_
- texture
->bytes(),
258 PriorityCalculator::AllowEverythingCutoff(),
259 EVICT_ONLY_RECYCLABLE
,
260 DO_NOT_UNLINK_BACKINGS
,
263 CreateBacking(texture
->size(), texture
->format(), resource_provider
);
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;
274 // Update the backing's priority from its new owner.
275 backing
->UpdatePriority();
278 bool PrioritizedResourceManager::EvictBackingsToReduceMemory(
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
)
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(),
301 if (eviction_policy
== EVICT_ONLY_RECYCLABLE
&&
302 !backing
->CanBeRecycledIfNotInExternalUse())
304 if (unlink_policy
== UNLINK_BACKINGS
&& backing
->owner())
305 backing
->owner()->Unlink();
306 EvictFirstBackingResource(resource_provider
);
307 evicted_anything
= true;
309 return evicted_anything
;
312 void PrioritizedResourceManager::ReduceWastedMemory(
313 ResourceProvider
* resource_provider
) {
314 // We currently collect backings from deleted textures for later recycling.
315 // However, if we do that forever we will always use the max limit even if
316 // we really need very little memory. This should probably be solved by
317 // reducing the limit externally, but until then this just does some "clean
318 // up" of unused backing textures (any more than 10%).
319 size_t wasted_memory
= 0;
320 for (BackingList::iterator it
= backings_
.begin(); it
!= backings_
.end();
324 if ((*it
)->in_parent_compositor())
326 wasted_memory
+= (*it
)->bytes();
328 size_t wasted_memory_to_allow
= memory_available_bytes_
/ 10;
329 // If the external priority cutoff indicates that unused memory should be
330 // freed, then do not allow any memory for texture recycling.
331 if (external_priority_cutoff_
!= PriorityCalculator::AllowEverythingCutoff())
332 wasted_memory_to_allow
= 0;
333 if (wasted_memory
> wasted_memory_to_allow
)
334 EvictBackingsToReduceMemory(MemoryUseBytes() -
335 (wasted_memory
- wasted_memory_to_allow
),
336 PriorityCalculator::AllowEverythingCutoff(),
337 EVICT_ONLY_RECYCLABLE
,
338 DO_NOT_UNLINK_BACKINGS
,
342 void PrioritizedResourceManager::ReduceMemory(
343 ResourceProvider
* resource_provider
) {
344 DCHECK(proxy_
->IsImplThread() && proxy_
->IsMainThreadBlocked());
345 EvictBackingsToReduceMemory(memory_available_bytes_
,
346 PriorityCalculator::AllowEverythingCutoff(),
350 DCHECK_LE(MemoryUseBytes(), memory_available_bytes_
);
352 ReduceWastedMemory(resource_provider
);
355 void PrioritizedResourceManager::ClearAllMemory(
356 ResourceProvider
* resource_provider
) {
357 DCHECK(proxy_
->IsImplThread() && proxy_
->IsMainThreadBlocked());
358 if (!resource_provider
) {
359 DCHECK(backings_
.empty());
362 EvictBackingsToReduceMemory(0,
363 PriorityCalculator::AllowEverythingCutoff(),
365 DO_NOT_UNLINK_BACKINGS
,
369 bool PrioritizedResourceManager::ReduceMemoryOnImplThread(
372 ResourceProvider
* resource_provider
) {
373 DCHECK(proxy_
->IsImplThread());
374 DCHECK(resource_provider
);
376 // If we are in the process of uploading a new frame then the backings at the
377 // very end of the list are not sorted by priority. Sort them before doing the
379 if (backings_tail_not_sorted_
)
381 return EvictBackingsToReduceMemory(limit_bytes
,
384 DO_NOT_UNLINK_BACKINGS
,
388 void PrioritizedResourceManager::UnlinkAndClearEvictedBackings() {
389 DCHECK(proxy_
->IsMainThread());
390 base::AutoLock
scoped_lock(evicted_backings_lock_
);
391 for (BackingList::const_iterator it
= evicted_backings_
.begin();
392 it
!= evicted_backings_
.end();
394 PrioritizedResource::Backing
* backing
= (*it
);
395 if (backing
->owner())
396 backing
->owner()->Unlink();
399 evicted_backings_
.clear();
402 bool PrioritizedResourceManager::LinkedEvictedBackingsExist() const {
403 DCHECK(proxy_
->IsImplThread() && proxy_
->IsMainThreadBlocked());
404 base::AutoLock
scoped_lock(evicted_backings_lock_
);
405 for (BackingList::const_iterator it
= evicted_backings_
.begin();
406 it
!= evicted_backings_
.end();
414 void PrioritizedResourceManager::RegisterTexture(PrioritizedResource
* texture
) {
415 DCHECK(proxy_
->IsMainThread());
417 DCHECK(!texture
->resource_manager());
418 DCHECK(!texture
->backing());
419 DCHECK(!ContainsKey(textures_
, texture
));
421 texture
->set_manager_internal(this);
422 textures_
.insert(texture
);
425 void PrioritizedResourceManager::UnregisterTexture(
426 PrioritizedResource
* texture
) {
427 DCHECK(proxy_
->IsMainThread() ||
428 (proxy_
->IsImplThread() && proxy_
->IsMainThreadBlocked()));
430 DCHECK(ContainsKey(textures_
, texture
));
432 ReturnBackingTexture(texture
);
433 texture
->set_manager_internal(NULL
);
434 textures_
.erase(texture
);
435 texture
->set_above_priority_cutoff(false);
438 void PrioritizedResourceManager::ReturnBackingTexture(
439 PrioritizedResource
* texture
) {
440 DCHECK(proxy_
->IsMainThread() ||
441 (proxy_
->IsImplThread() && proxy_
->IsMainThreadBlocked()));
442 if (texture
->backing())
446 PrioritizedResource::Backing
* PrioritizedResourceManager::CreateBacking(
447 const gfx::Size
& size
,
448 ResourceFormat format
,
449 ResourceProvider
* resource_provider
) {
450 DCHECK(proxy_
->IsImplThread() && proxy_
->IsMainThreadBlocked());
451 DCHECK(resource_provider
);
452 ResourceProvider::ResourceId resource_id
=
453 resource_provider
->CreateManagedResource(
454 size
, GL_TEXTURE_2D
, GL_CLAMP_TO_EDGE
,
455 ResourceProvider::TEXTURE_HINT_IMMUTABLE
, format
);
456 PrioritizedResource::Backing
* backing
= new PrioritizedResource::Backing(
457 resource_id
, resource_provider
, size
, format
);
458 memory_use_bytes_
+= backing
->bytes();
462 void PrioritizedResourceManager::EvictFirstBackingResource(
463 ResourceProvider
* resource_provider
) {
464 DCHECK(proxy_
->IsImplThread());
465 DCHECK(resource_provider
);
466 DCHECK(!backings_
.empty());
467 PrioritizedResource::Backing
* backing
= backings_
.front();
469 // Note that we create a backing and its resource at the same time, but we
470 // delete the backing structure and its resource in two steps. This is because
471 // we can delete the resource while the main thread is running, but we cannot
472 // unlink backings while the main thread is running.
473 backing
->DeleteResource(resource_provider
);
474 memory_use_bytes_
-= backing
->bytes();
475 backings_
.pop_front();
476 base::AutoLock
scoped_lock(evicted_backings_lock_
);
477 evicted_backings_
.push_back(backing
);
480 void PrioritizedResourceManager::AssertInvariants() {
482 DCHECK(proxy_
->IsImplThread() && proxy_
->IsMainThreadBlocked());
484 // If we hit any of these asserts, there is a bug in this class. To see
485 // where the bug is, call this function at the beginning and end of
486 // every public function.
488 // Backings/textures must be doubly-linked and only to other backings/textures
490 for (BackingList::iterator it
= backings_
.begin(); it
!= backings_
.end();
492 if ((*it
)->owner()) {
493 DCHECK(ContainsKey(textures_
, (*it
)->owner()));
494 DCHECK((*it
)->owner()->backing() == (*it
));
497 for (TextureSet::iterator it
= textures_
.begin(); it
!= textures_
.end();
499 PrioritizedResource
* texture
= (*it
);
500 PrioritizedResource::Backing
* backing
= texture
->backing();
501 base::AutoLock
scoped_lock(evicted_backings_lock_
);
503 if (backing
->ResourceHasBeenDeleted()) {
504 DCHECK(std::find(backings_
.begin(), backings_
.end(), backing
) ==
506 DCHECK(std::find(evicted_backings_
.begin(),
507 evicted_backings_
.end(),
508 backing
) != evicted_backings_
.end());
510 DCHECK(std::find(backings_
.begin(), backings_
.end(), backing
) !=
512 DCHECK(std::find(evicted_backings_
.begin(),
513 evicted_backings_
.end(),
514 backing
) == evicted_backings_
.end());
516 DCHECK(backing
->owner() == texture
);
520 // At all times, backings that can be evicted must always come before
521 // backings that can't be evicted in the backing texture list (otherwise
522 // ReduceMemory will not find all textures available for eviction/recycling).
523 bool reached_unrecyclable
= false;
524 PrioritizedResource::Backing
* previous_backing
= NULL
;
525 for (BackingList::iterator it
= backings_
.begin(); it
!= backings_
.end();
527 PrioritizedResource::Backing
* backing
= *it
;
528 if (previous_backing
&&
529 (!backings_tail_not_sorted_
||
530 !backing
->was_above_priority_cutoff_at_last_priority_update()))
531 DCHECK(CompareBackings(previous_backing
, backing
));
532 if (!backing
->CanBeRecycledIfNotInExternalUse())
533 reached_unrecyclable
= true;
534 if (reached_unrecyclable
)
535 DCHECK(!backing
->CanBeRecycledIfNotInExternalUse());
537 DCHECK(backing
->CanBeRecycledIfNotInExternalUse());
538 previous_backing
= backing
;
540 #endif // DCHECK_IS_ON()
543 const Proxy
* PrioritizedResourceManager::ProxyForDebug() const {