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/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"
17 PrioritizedResourceManager::PrioritizedResourceManager(const Proxy
* proxy
)
18 : max_memory_limit_bytes_(DefaultMemoryAllocationLimit()),
19 external_priority_cutoff_(PriorityCalculator::AllowEverythingCutoff()),
21 memory_above_cutoff_bytes_(0),
22 max_memory_needed_bytes_(0),
23 memory_available_bytes_(0),
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) {}
31 PrioritizedResourceManager::~PrioritizedResourceManager() {
32 while (textures_
.size() > 0)
33 UnregisterTexture(*textures_
.begin());
35 UnlinkAndClearEvictedBackings();
36 DCHECK(evicted_backings_
.empty());
38 // Each remaining backing is a leaked opengl texture. There should be none.
39 DCHECK(backings_
.empty());
42 size_t PrioritizedResourceManager::MemoryVisibleBytes() const {
43 DCHECK(proxy_
->IsImplThread());
44 return memory_visible_last_pushed_bytes_
;
47 size_t PrioritizedResourceManager::MemoryVisibleAndNearbyBytes() const {
48 DCHECK(proxy_
->IsImplThread());
49 return memory_visible_and_nearby_last_pushed_bytes_
;
52 void PrioritizedResourceManager::PrioritizeTextures() {
53 TRACE_EVENT0("cc", "PrioritizedResourceManager::PrioritizeTextures");
54 DCHECK(proxy_
->IsMainThread());
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).
60 TextureVector
& sorted_textures
= temp_texture_vector_
;
61 sorted_textures
.clear();
63 // Copy all textures into a vector, sort them, and collect memory requirements
65 memory_visible_bytes_
= 0;
66 memory_visible_and_nearby_bytes_
= 0;
67 for (TextureSet::iterator it
= textures_
.begin(); it
!= textures_
.end();
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();
80 std::sort(sorted_textures
.begin(), sorted_textures
.end(), CompareTextures
);
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();
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
;
98 memory_available_bytes_
-= (*it
)->bytes();
100 size_t new_memory_bytes
= memory_bytes
+ (*it
)->bytes();
101 if (new_memory_bytes
> memory_available_bytes_
) {
102 priority_cutoff_
= (*it
)->request_priority();
105 memory_bytes
= new_memory_bytes
;
109 // Disallow any textures with priority below the external cutoff to have
111 for (TextureVector::iterator it
= sorted_textures
.begin();
112 it
!= sorted_textures
.end();
114 PrioritizedResource
* texture
= (*it
);
115 if (!PriorityCalculator::priority_is_higher(texture
->request_priority(),
116 external_priority_cutoff_
) &&
117 texture
->have_backing_texture())
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();
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();
139 sorted_textures
.clear();
141 DCHECK_LE(memory_above_cutoff_bytes_
, memory_available_bytes_
);
142 DCHECK_LE(MemoryAboveCutoffBytes(), MaxMemoryLimitBytes());
145 void PrioritizedResourceManager::PushTexturePrioritiesToBackings() {
147 "PrioritizedResourceManager::PushTexturePrioritiesToBackings");
148 DCHECK(proxy_
->IsImplThread() && proxy_
->IsMainThreadBlocked());
151 for (BackingList::iterator it
= backings_
.begin(); it
!= backings_
.end();
153 (*it
)->UpdatePriority();
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_
;
163 void PrioritizedResourceManager::UpdateBackingsState(
164 ResourceProvider
* resource_provider
) {
166 "PrioritizedResourceManager::UpdateBackingsInDrawingImplTree");
167 DCHECK(proxy_
->IsImplThread() && proxy_
->IsMainThreadBlocked());
170 for (BackingList::iterator it
= backings_
.begin(); it
!= backings_
.end();
172 PrioritizedResource::Backing
* backing
= (*it
);
173 backing
->UpdateState(resource_provider
);
179 void PrioritizedResourceManager::SortBackings() {
180 TRACE_EVENT0("cc", "PrioritizedResourceManager::SortBackings");
181 DCHECK(proxy_
->IsImplThread());
183 // Put backings in eviction/recycling order.
184 backings_
.sort(CompareBackings
);
185 backings_tail_not_sorted_
= false;
188 void PrioritizedResourceManager::ClearPriorities() {
189 DCHECK(proxy_
->IsMainThread());
190 for (TextureSet::iterator it
= textures_
.begin(); it
!= textures_
.end();
192 // TODO(reveman): We should remove this and just set all priorities to
193 // PriorityCalculator::lowestPriority() once we have priorities for all
194 // textures (we can't currently calculate distances for off-screen
196 (*it
)->set_request_priority(
197 PriorityCalculator::LingeringPriority((*it
)->request_priority()));
201 bool PrioritizedResourceManager::RequestLate(PrioritizedResource
* texture
) {
202 DCHECK(proxy_
->IsMainThread());
204 // This is already above cutoff, so don't double count it's memory below.
205 if (texture
->is_above_priority_cutoff())
208 // Allow textures that have priority equal to the cutoff, but not strictly
210 if (PriorityCalculator::priority_is_lower(texture
->request_priority(),
214 // Disallow textures that do not have a priority strictly higher than the
216 if (!PriorityCalculator::priority_is_higher(texture
->request_priority(),
217 external_priority_cutoff_
))
220 size_t new_memory_bytes
= memory_above_cutoff_bytes_
+ texture
->bytes();
221 if (new_memory_bytes
> memory_available_bytes_
)
224 memory_above_cutoff_bytes_
= new_memory_bytes
;
225 texture
->set_above_priority_cutoff(true);
229 void PrioritizedResourceManager::AcquireBackingTextureIfNeeded(
230 PrioritizedResource
* texture
,
231 ResourceProvider
* resource_provider
) {
232 DCHECK(proxy_
->IsImplThread() && proxy_
->IsMainThreadBlocked());
233 DCHECK(!texture
->is_self_managed());
234 DCHECK(texture
->is_above_priority_cutoff());
235 if (texture
->backing() || !texture
->is_above_priority_cutoff())
238 // Find a backing below, by either recycling or allocating.
239 PrioritizedResource::Backing
* backing
= NULL
;
241 // First try to recycle
242 for (BackingList::iterator it
= backings_
.begin(); it
!= backings_
.end();
244 if (!(*it
)->CanBeRecycledIfNotInExternalUse())
246 if (resource_provider
->InUseByConsumer((*it
)->id()))
248 if ((*it
)->size() == texture
->size() &&
249 (*it
)->format() == texture
->format()) {
256 // Otherwise reduce memory and just allocate a new backing texures.
258 EvictBackingsToReduceMemory(memory_available_bytes_
- texture
->bytes(),
259 PriorityCalculator::AllowEverythingCutoff(),
260 EVICT_ONLY_RECYCLABLE
,
261 DO_NOT_UNLINK_BACKINGS
,
264 CreateBacking(texture
->size(), texture
->format(), resource_provider
);
267 // Move the used backing to the end of the eviction list, and note that
268 // the tail is not sorted.
269 if (backing
->owner())
270 backing
->owner()->Unlink();
271 texture
->Link(backing
);
272 backings_
.push_back(backing
);
273 backings_tail_not_sorted_
= true;
275 // Update the backing's priority from its new owner.
276 backing
->UpdatePriority();
279 bool PrioritizedResourceManager::EvictBackingsToReduceMemory(
282 EvictionPolicy eviction_policy
,
283 UnlinkPolicy unlink_policy
,
284 ResourceProvider
* resource_provider
) {
285 DCHECK(proxy_
->IsImplThread());
286 if (unlink_policy
== UNLINK_BACKINGS
)
287 DCHECK(proxy_
->IsMainThreadBlocked());
288 if (MemoryUseBytes() <= limit_bytes
&&
289 PriorityCalculator::AllowEverythingCutoff() == priority_cutoff
)
292 // Destroy backings until we are below the limit,
293 // or until all backings remaining are above the cutoff.
294 bool evicted_anything
= false;
295 while (backings_
.size() > 0) {
296 PrioritizedResource::Backing
* backing
= backings_
.front();
297 if (MemoryUseBytes() <= limit_bytes
&&
298 PriorityCalculator::priority_is_higher(
299 backing
->request_priority_at_last_priority_update(),
302 if (eviction_policy
== EVICT_ONLY_RECYCLABLE
&&
303 !backing
->CanBeRecycledIfNotInExternalUse())
305 if (unlink_policy
== UNLINK_BACKINGS
&& backing
->owner())
306 backing
->owner()->Unlink();
307 EvictFirstBackingResource(resource_provider
);
308 evicted_anything
= true;
310 return evicted_anything
;
313 void PrioritizedResourceManager::ReduceWastedMemory(
314 ResourceProvider
* resource_provider
) {
315 // We currently collect backings from deleted textures for later recycling.
316 // However, if we do that forever we will always use the max limit even if
317 // we really need very little memory. This should probably be solved by
318 // reducing the limit externally, but until then this just does some "clean
319 // up" of unused backing textures (any more than 10%).
320 size_t wasted_memory
= 0;
321 for (BackingList::iterator it
= backings_
.begin(); it
!= backings_
.end();
325 if ((*it
)->in_parent_compositor())
327 wasted_memory
+= (*it
)->bytes();
329 size_t wasted_memory_to_allow
= memory_available_bytes_
/ 10;
330 // If the external priority cutoff indicates that unused memory should be
331 // freed, then do not allow any memory for texture recycling.
332 if (external_priority_cutoff_
!= PriorityCalculator::AllowEverythingCutoff())
333 wasted_memory_to_allow
= 0;
334 if (wasted_memory
> wasted_memory_to_allow
)
335 EvictBackingsToReduceMemory(MemoryUseBytes() -
336 (wasted_memory
- wasted_memory_to_allow
),
337 PriorityCalculator::AllowEverythingCutoff(),
338 EVICT_ONLY_RECYCLABLE
,
339 DO_NOT_UNLINK_BACKINGS
,
343 void PrioritizedResourceManager::ReduceMemory(
344 ResourceProvider
* resource_provider
) {
345 DCHECK(proxy_
->IsImplThread() && proxy_
->IsMainThreadBlocked());
346 EvictBackingsToReduceMemory(memory_available_bytes_
,
347 PriorityCalculator::AllowEverythingCutoff(),
351 DCHECK_LE(MemoryUseBytes(), memory_available_bytes_
);
353 ReduceWastedMemory(resource_provider
);
356 void PrioritizedResourceManager::ClearAllMemory(
357 ResourceProvider
* resource_provider
) {
358 DCHECK(proxy_
->IsImplThread() && proxy_
->IsMainThreadBlocked());
359 if (!resource_provider
) {
360 DCHECK(backings_
.empty());
363 EvictBackingsToReduceMemory(0,
364 PriorityCalculator::AllowEverythingCutoff(),
366 DO_NOT_UNLINK_BACKINGS
,
370 bool PrioritizedResourceManager::ReduceMemoryOnImplThread(
373 ResourceProvider
* resource_provider
) {
374 DCHECK(proxy_
->IsImplThread());
375 DCHECK(resource_provider
);
377 // If we are in the process of uploading a new frame then the backings at the
378 // very end of the list are not sorted by priority. Sort them before doing the
380 if (backings_tail_not_sorted_
)
382 return EvictBackingsToReduceMemory(limit_bytes
,
385 DO_NOT_UNLINK_BACKINGS
,
389 void PrioritizedResourceManager::UnlinkAndClearEvictedBackings() {
390 DCHECK(proxy_
->IsMainThread());
391 base::AutoLock
scoped_lock(evicted_backings_lock_
);
392 for (BackingList::const_iterator it
= evicted_backings_
.begin();
393 it
!= evicted_backings_
.end();
395 PrioritizedResource::Backing
* backing
= (*it
);
396 if (backing
->owner())
397 backing
->owner()->Unlink();
400 evicted_backings_
.clear();
403 bool PrioritizedResourceManager::LinkedEvictedBackingsExist() const {
404 DCHECK(proxy_
->IsImplThread() && proxy_
->IsMainThreadBlocked());
405 base::AutoLock
scoped_lock(evicted_backings_lock_
);
406 for (BackingList::const_iterator it
= evicted_backings_
.begin();
407 it
!= evicted_backings_
.end();
415 void PrioritizedResourceManager::RegisterTexture(PrioritizedResource
* texture
) {
416 DCHECK(proxy_
->IsMainThread());
418 DCHECK(!texture
->resource_manager());
419 DCHECK(!texture
->backing());
420 DCHECK(!ContainsKey(textures_
, texture
));
422 texture
->set_manager_internal(this);
423 textures_
.insert(texture
);
426 void PrioritizedResourceManager::UnregisterTexture(
427 PrioritizedResource
* texture
) {
428 DCHECK(proxy_
->IsMainThread() ||
429 (proxy_
->IsImplThread() && proxy_
->IsMainThreadBlocked()));
431 DCHECK(ContainsKey(textures_
, texture
));
433 ReturnBackingTexture(texture
);
434 texture
->set_manager_internal(NULL
);
435 textures_
.erase(texture
);
436 texture
->set_above_priority_cutoff(false);
439 void PrioritizedResourceManager::ReturnBackingTexture(
440 PrioritizedResource
* texture
) {
441 DCHECK(proxy_
->IsMainThread() ||
442 (proxy_
->IsImplThread() && proxy_
->IsMainThreadBlocked()));
443 if (texture
->backing())
447 PrioritizedResource::Backing
* PrioritizedResourceManager::CreateBacking(
448 const gfx::Size
& size
,
449 ResourceFormat format
,
450 ResourceProvider
* resource_provider
) {
451 DCHECK(proxy_
->IsImplThread() && proxy_
->IsMainThreadBlocked());
452 DCHECK(resource_provider
);
453 ResourceProvider::ResourceId resource_id
=
454 resource_provider
->CreateManagedResource(
458 ResourceProvider::TextureUsageAny
,
460 PrioritizedResource::Backing
* backing
= new PrioritizedResource::Backing(
461 resource_id
, resource_provider
, size
, format
);
462 memory_use_bytes_
+= backing
->bytes();
466 void PrioritizedResourceManager::EvictFirstBackingResource(
467 ResourceProvider
* resource_provider
) {
468 DCHECK(proxy_
->IsImplThread());
469 DCHECK(resource_provider
);
470 DCHECK(!backings_
.empty());
471 PrioritizedResource::Backing
* backing
= backings_
.front();
473 // Note that we create a backing and its resource at the same time, but we
474 // delete the backing structure and its resource in two steps. This is because
475 // we can delete the resource while the main thread is running, but we cannot
476 // unlink backings while the main thread is running.
477 backing
->DeleteResource(resource_provider
);
478 memory_use_bytes_
-= backing
->bytes();
479 backings_
.pop_front();
480 base::AutoLock
scoped_lock(evicted_backings_lock_
);
481 evicted_backings_
.push_back(backing
);
484 void PrioritizedResourceManager::AssertInvariants() {
486 DCHECK(proxy_
->IsImplThread() && proxy_
->IsMainThreadBlocked());
488 // If we hit any of these asserts, there is a bug in this class. To see
489 // where the bug is, call this function at the beginning and end of
490 // every public function.
492 // Backings/textures must be doubly-linked and only to other backings/textures
494 for (BackingList::iterator it
= backings_
.begin(); it
!= backings_
.end();
496 if ((*it
)->owner()) {
497 DCHECK(ContainsKey(textures_
, (*it
)->owner()));
498 DCHECK((*it
)->owner()->backing() == (*it
));
501 for (TextureSet::iterator it
= textures_
.begin(); it
!= textures_
.end();
503 PrioritizedResource
* texture
= (*it
);
504 PrioritizedResource::Backing
* backing
= texture
->backing();
505 base::AutoLock
scoped_lock(evicted_backings_lock_
);
507 if (backing
->ResourceHasBeenDeleted()) {
508 DCHECK(std::find(backings_
.begin(), backings_
.end(), backing
) ==
510 DCHECK(std::find(evicted_backings_
.begin(),
511 evicted_backings_
.end(),
512 backing
) != evicted_backings_
.end());
514 DCHECK(std::find(backings_
.begin(), backings_
.end(), backing
) !=
516 DCHECK(std::find(evicted_backings_
.begin(),
517 evicted_backings_
.end(),
518 backing
) == evicted_backings_
.end());
520 DCHECK(backing
->owner() == texture
);
524 // At all times, backings that can be evicted must always come before
525 // backings that can't be evicted in the backing texture list (otherwise
526 // ReduceMemory will not find all textures available for eviction/recycling).
527 bool reached_unrecyclable
= false;
528 PrioritizedResource::Backing
* previous_backing
= NULL
;
529 for (BackingList::iterator it
= backings_
.begin(); it
!= backings_
.end();
531 PrioritizedResource::Backing
* backing
= *it
;
532 if (previous_backing
&&
533 (!backings_tail_not_sorted_
||
534 !backing
->was_above_priority_cutoff_at_last_priority_update()))
535 DCHECK(CompareBackings(previous_backing
, backing
));
536 if (!backing
->CanBeRecycledIfNotInExternalUse())
537 reached_unrecyclable
= true;
538 if (reached_unrecyclable
)
539 DCHECK(!backing
->CanBeRecycledIfNotInExternalUse());
541 DCHECK(backing
->CanBeRecycledIfNotInExternalUse());
542 previous_backing
= backing
;
544 #endif // DCHECK_IS_ON
547 const Proxy
* PrioritizedResourceManager::ProxyForDebug() const {