• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
PrioritizedResourceManager(const Proxy * proxy)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 
~PrioritizedResourceManager()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 
MemoryVisibleBytes() const42 size_t PrioritizedResourceManager::MemoryVisibleBytes() const {
43   DCHECK(proxy_->IsImplThread());
44   return memory_visible_last_pushed_bytes_;
45 }
46 
MemoryVisibleAndNearbyBytes() const47 size_t PrioritizedResourceManager::MemoryVisibleAndNearbyBytes() const {
48   DCHECK(proxy_->IsImplThread());
49   return memory_visible_and_nearby_last_pushed_bytes_;
50 }
51 
PrioritizeTextures()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 
PushTexturePrioritiesToBackings()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 
UpdateBackingsState(ResourceProvider * resource_provider)163 void PrioritizedResourceManager::UpdateBackingsState(
164     ResourceProvider* resource_provider) {
165   TRACE_EVENT0("cc",
166                "PrioritizedResourceManager::UpdateBackingsInDrawingImplTree");
167   DCHECK(proxy_->IsImplThread() && proxy_->IsMainThreadBlocked());
168 
169   AssertInvariants();
170   for (BackingList::iterator it = backings_.begin(); it != backings_.end();
171        ++it) {
172     PrioritizedResource::Backing* backing = (*it);
173     backing->UpdateState(resource_provider);
174   }
175   SortBackings();
176   AssertInvariants();
177 }
178 
SortBackings()179 void PrioritizedResourceManager::SortBackings() {
180   TRACE_EVENT0("cc", "PrioritizedResourceManager::SortBackings");
181   DCHECK(proxy_->IsImplThread());
182 
183   // Put backings in eviction/recycling order.
184   backings_.sort(CompareBackings);
185   backings_tail_not_sorted_ = false;
186 }
187 
ClearPriorities()188 void PrioritizedResourceManager::ClearPriorities() {
189   DCHECK(proxy_->IsMainThread());
190   for (TextureSet::iterator it = textures_.begin(); it != textures_.end();
191        ++it) {
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
195     // textures).
196     (*it)->set_request_priority(
197         PriorityCalculator::LingeringPriority((*it)->request_priority()));
198   }
199 }
200 
RequestLate(PrioritizedResource * texture)201 bool PrioritizedResourceManager::RequestLate(PrioritizedResource* texture) {
202   DCHECK(proxy_->IsMainThread());
203 
204   // This is already above cutoff, so don't double count it's memory below.
205   if (texture->is_above_priority_cutoff())
206     return true;
207 
208   // Allow textures that have priority equal to the cutoff, but not strictly
209   // lower.
210   if (PriorityCalculator::priority_is_lower(texture->request_priority(),
211                                             priority_cutoff_))
212     return false;
213 
214   // Disallow textures that do not have a priority strictly higher than the
215   // external cutoff.
216   if (!PriorityCalculator::priority_is_higher(texture->request_priority(),
217                                               external_priority_cutoff_))
218     return false;
219 
220   size_t new_memory_bytes = memory_above_cutoff_bytes_ + texture->bytes();
221   if (new_memory_bytes > memory_available_bytes_)
222     return false;
223 
224   memory_above_cutoff_bytes_ = new_memory_bytes;
225   texture->set_above_priority_cutoff(true);
226   return true;
227 }
228 
AcquireBackingTextureIfNeeded(PrioritizedResource * texture,ResourceProvider * resource_provider)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())
236     return;
237 
238   // Find a backing below, by either recycling or allocating.
239   PrioritizedResource::Backing* backing = NULL;
240 
241   // First try to recycle
242   for (BackingList::iterator it = backings_.begin(); it != backings_.end();
243        ++it) {
244     if (!(*it)->CanBeRecycledIfNotInExternalUse())
245       break;
246     if (resource_provider->InUseByConsumer((*it)->id()))
247       continue;
248     if ((*it)->size() == texture->size() &&
249         (*it)->format() == texture->format()) {
250       backing = (*it);
251       backings_.erase(it);
252       break;
253     }
254   }
255 
256   // Otherwise reduce memory and just allocate a new backing texures.
257   if (!backing) {
258     EvictBackingsToReduceMemory(memory_available_bytes_ - texture->bytes(),
259                                 PriorityCalculator::AllowEverythingCutoff(),
260                                 EVICT_ONLY_RECYCLABLE,
261                                 DO_NOT_UNLINK_BACKINGS,
262                                 resource_provider);
263     backing =
264         CreateBacking(texture->size(), texture->format(), resource_provider);
265   }
266 
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;
274 
275   // Update the backing's priority from its new owner.
276   backing->UpdatePriority();
277 }
278 
EvictBackingsToReduceMemory(size_t limit_bytes,int priority_cutoff,EvictionPolicy eviction_policy,UnlinkPolicy unlink_policy,ResourceProvider * resource_provider)279 bool PrioritizedResourceManager::EvictBackingsToReduceMemory(
280     size_t limit_bytes,
281     int priority_cutoff,
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)
290     return false;
291 
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(),
300             priority_cutoff))
301       break;
302     if (eviction_policy == EVICT_ONLY_RECYCLABLE &&
303         !backing->CanBeRecycledIfNotInExternalUse())
304       break;
305     if (unlink_policy == UNLINK_BACKINGS && backing->owner())
306       backing->owner()->Unlink();
307     EvictFirstBackingResource(resource_provider);
308     evicted_anything = true;
309   }
310   return evicted_anything;
311 }
312 
ReduceWastedMemory(ResourceProvider * resource_provider)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();
322        ++it) {
323     if ((*it)->owner())
324       break;
325     if ((*it)->in_parent_compositor())
326       continue;
327     wasted_memory += (*it)->bytes();
328   }
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,
340                                 resource_provider);
341 }
342 
ReduceMemory(ResourceProvider * resource_provider)343 void PrioritizedResourceManager::ReduceMemory(
344     ResourceProvider* resource_provider) {
345   DCHECK(proxy_->IsImplThread() && proxy_->IsMainThreadBlocked());
346   EvictBackingsToReduceMemory(memory_available_bytes_,
347                               PriorityCalculator::AllowEverythingCutoff(),
348                               EVICT_ANYTHING,
349                               UNLINK_BACKINGS,
350                               resource_provider);
351   DCHECK_LE(MemoryUseBytes(), memory_available_bytes_);
352 
353   ReduceWastedMemory(resource_provider);
354 }
355 
ClearAllMemory(ResourceProvider * resource_provider)356 void PrioritizedResourceManager::ClearAllMemory(
357     ResourceProvider* resource_provider) {
358   DCHECK(proxy_->IsImplThread() && proxy_->IsMainThreadBlocked());
359   if (!resource_provider) {
360     DCHECK(backings_.empty());
361     return;
362   }
363   EvictBackingsToReduceMemory(0,
364                               PriorityCalculator::AllowEverythingCutoff(),
365                               EVICT_ANYTHING,
366                               DO_NOT_UNLINK_BACKINGS,
367                               resource_provider);
368 }
369 
ReduceMemoryOnImplThread(size_t limit_bytes,int priority_cutoff,ResourceProvider * resource_provider)370 bool PrioritizedResourceManager::ReduceMemoryOnImplThread(
371     size_t limit_bytes,
372     int priority_cutoff,
373     ResourceProvider* resource_provider) {
374   DCHECK(proxy_->IsImplThread());
375   DCHECK(resource_provider);
376 
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
379   // eviction.
380   if (backings_tail_not_sorted_)
381     SortBackings();
382   return EvictBackingsToReduceMemory(limit_bytes,
383                                      priority_cutoff,
384                                      EVICT_ANYTHING,
385                                      DO_NOT_UNLINK_BACKINGS,
386                                      resource_provider);
387 }
388 
UnlinkAndClearEvictedBackings()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();
394        ++it) {
395     PrioritizedResource::Backing* backing = (*it);
396     if (backing->owner())
397       backing->owner()->Unlink();
398     delete backing;
399   }
400   evicted_backings_.clear();
401 }
402 
LinkedEvictedBackingsExist() const403 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();
408        ++it) {
409     if ((*it)->owner())
410       return true;
411   }
412   return false;
413 }
414 
RegisterTexture(PrioritizedResource * texture)415 void PrioritizedResourceManager::RegisterTexture(PrioritizedResource* texture) {
416   DCHECK(proxy_->IsMainThread());
417   DCHECK(texture);
418   DCHECK(!texture->resource_manager());
419   DCHECK(!texture->backing());
420   DCHECK(!ContainsKey(textures_, texture));
421 
422   texture->set_manager_internal(this);
423   textures_.insert(texture);
424 }
425 
UnregisterTexture(PrioritizedResource * texture)426 void PrioritizedResourceManager::UnregisterTexture(
427     PrioritizedResource* texture) {
428   DCHECK(proxy_->IsMainThread() ||
429          (proxy_->IsImplThread() && proxy_->IsMainThreadBlocked()));
430   DCHECK(texture);
431   DCHECK(ContainsKey(textures_, texture));
432 
433   ReturnBackingTexture(texture);
434   texture->set_manager_internal(NULL);
435   textures_.erase(texture);
436   texture->set_above_priority_cutoff(false);
437 }
438 
ReturnBackingTexture(PrioritizedResource * texture)439 void PrioritizedResourceManager::ReturnBackingTexture(
440     PrioritizedResource* texture) {
441   DCHECK(proxy_->IsMainThread() ||
442          (proxy_->IsImplThread() && proxy_->IsMainThreadBlocked()));
443   if (texture->backing())
444     texture->Unlink();
445 }
446 
CreateBacking(gfx::Size size,ResourceFormat format,ResourceProvider * resource_provider)447 PrioritizedResource::Backing* PrioritizedResourceManager::CreateBacking(
448     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(
455           size,
456           GL_TEXTURE_2D,
457           GL_CLAMP_TO_EDGE,
458           ResourceProvider::TextureUsageAny,
459           format);
460   PrioritizedResource::Backing* backing = new PrioritizedResource::Backing(
461       resource_id, resource_provider, size, format);
462   memory_use_bytes_ += backing->bytes();
463   return backing;
464 }
465 
EvictFirstBackingResource(ResourceProvider * resource_provider)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();
472 
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);
482 }
483 
AssertInvariants()484 void PrioritizedResourceManager::AssertInvariants() {
485 #ifndef NDEBUG
486   DCHECK(proxy_->IsImplThread() && proxy_->IsMainThreadBlocked());
487 
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.
491 
492   // Backings/textures must be doubly-linked and only to other backings/textures
493   // in this manager.
494   for (BackingList::iterator it = backings_.begin(); it != backings_.end();
495        ++it) {
496     if ((*it)->owner()) {
497       DCHECK(ContainsKey(textures_, (*it)->owner()));
498       DCHECK((*it)->owner()->backing() == (*it));
499     }
500   }
501   for (TextureSet::iterator it = textures_.begin(); it != textures_.end();
502        ++it) {
503     PrioritizedResource* texture = (*it);
504     PrioritizedResource::Backing* backing = texture->backing();
505     base::AutoLock scoped_lock(evicted_backings_lock_);
506     if (backing) {
507       if (backing->ResourceHasBeenDeleted()) {
508         DCHECK(std::find(backings_.begin(), backings_.end(), backing) ==
509                backings_.end());
510         DCHECK(std::find(evicted_backings_.begin(),
511                          evicted_backings_.end(),
512                          backing) != evicted_backings_.end());
513       } else {
514         DCHECK(std::find(backings_.begin(), backings_.end(), backing) !=
515                backings_.end());
516         DCHECK(std::find(evicted_backings_.begin(),
517                          evicted_backings_.end(),
518                          backing) == evicted_backings_.end());
519       }
520       DCHECK(backing->owner() == texture);
521     }
522   }
523 
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();
530        ++it) {
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());
540     else
541       DCHECK(backing->CanBeRecycledIfNotInExternalUse());
542     previous_backing = backing;
543   }
544 #endif
545 }
546 
ProxyForDebug() const547 const Proxy* PrioritizedResourceManager::ProxyForDebug() const {
548   return proxy_;
549 }
550 
551 }  // namespace cc
552