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/resource_pool.h"
9 #include "base/format_macros.h"
10 #include "base/strings/stringprintf.h"
11 #include "base/thread_task_runner_handle.h"
12 #include "base/trace_event/memory_dump_manager.h"
13 #include "cc/resources/resource_provider.h"
14 #include "cc/resources/resource_util.h"
15 #include "cc/resources/scoped_resource.h"
20 // Delay before a resource is considered expired.
21 const int kResourceExpirationDelayMs
= 1000;
25 void ResourcePool::PoolResource::OnMemoryDump(
26 base::trace_event::ProcessMemoryDump
* pmd
,
27 const ResourceProvider
* resource_provider
,
29 // Resource IDs are not process-unique, so log with the ResourceProvider's
31 std::string parent_node
=
32 base::StringPrintf("cc/resource_memory/provider_%d/resource_%d",
33 resource_provider
->tracing_id(), id());
35 std::string dump_name
=
36 base::StringPrintf("cc/tile_memory/provider_%d/resource_%d",
37 resource_provider
->tracing_id(), id());
38 base::trace_event::MemoryAllocatorDump
* dump
=
39 pmd
->CreateAllocatorDump(dump_name
);
41 pmd
->AddSuballocation(dump
->guid(), parent_node
);
43 uint64_t total_bytes
=
44 ResourceUtil::UncheckedSizeInBytesAligned
<size_t>(size(), format());
45 dump
->AddScalar(base::trace_event::MemoryAllocatorDump::kNameSize
,
46 base::trace_event::MemoryAllocatorDump::kUnitsBytes
,
50 dump
->AddScalar("free_size",
51 base::trace_event::MemoryAllocatorDump::kUnitsBytes
,
56 ResourcePool::ResourcePool(ResourceProvider
* resource_provider
,
57 base::SingleThreadTaskRunner
* task_runner
,
59 : resource_provider_(resource_provider
),
61 max_memory_usage_bytes_(0),
62 max_resource_count_(0),
63 in_use_memory_usage_bytes_(0),
64 total_memory_usage_bytes_(0),
65 total_resource_count_(0),
66 task_runner_(task_runner
),
67 evict_expired_resources_pending_(false),
68 resource_expiration_delay_(
69 base::TimeDelta::FromMilliseconds(kResourceExpirationDelayMs
)),
70 weak_ptr_factory_(this) {
71 base::trace_event::MemoryDumpManager::GetInstance()->RegisterDumpProvider(
72 this, task_runner_
.get());
75 ResourcePool::~ResourcePool() {
76 base::trace_event::MemoryDumpManager::GetInstance()->UnregisterDumpProvider(
79 DCHECK_EQ(0u, in_use_resources_
.size());
81 while (!busy_resources_
.empty()) {
82 DidFinishUsingResource(busy_resources_
.take_front());
85 SetResourceUsageLimits(0, 0);
86 DCHECK_EQ(0u, unused_resources_
.size());
87 DCHECK_EQ(0u, in_use_memory_usage_bytes_
);
88 DCHECK_EQ(0u, total_memory_usage_bytes_
);
89 DCHECK_EQ(0u, total_resource_count_
);
92 Resource
* ResourcePool::AcquireResource(const gfx::Size
& size
,
93 ResourceFormat format
) {
94 for (ResourceDeque::iterator it
= unused_resources_
.begin();
95 it
!= unused_resources_
.end(); ++it
) {
96 ScopedResource
* resource
= *it
;
97 DCHECK(resource_provider_
->CanLockForWrite(resource
->id()));
99 if (resource
->format() != format
)
101 if (resource
->size() != size
)
104 // Transfer resource to |in_use_resources_|.
105 in_use_resources_
.set(resource
->id(), unused_resources_
.take(it
));
106 in_use_memory_usage_bytes_
+= ResourceUtil::UncheckedSizeInBytes
<size_t>(
107 resource
->size(), resource
->format());
111 scoped_ptr
<PoolResource
> pool_resource
=
112 PoolResource::Create(resource_provider_
);
114 target_
? target_
: resource_provider_
->GetImageTextureTarget(format
);
115 pool_resource
->AllocateManaged(size
, target
, format
);
117 DCHECK(ResourceUtil::VerifySizeInBytes
<size_t>(pool_resource
->size(),
118 pool_resource
->format()));
119 total_memory_usage_bytes_
+= ResourceUtil::UncheckedSizeInBytes
<size_t>(
120 pool_resource
->size(), pool_resource
->format());
121 ++total_resource_count_
;
123 Resource
* resource
= pool_resource
.get();
124 in_use_resources_
.set(resource
->id(), pool_resource
.Pass());
125 in_use_memory_usage_bytes_
+= ResourceUtil::UncheckedSizeInBytes
<size_t>(
126 resource
->size(), resource
->format());
130 Resource
* ResourcePool::TryAcquireResourceWithContentId(uint64_t content_id
) {
133 auto it
= std::find_if(unused_resources_
.begin(), unused_resources_
.end(),
134 [content_id
](const PoolResource
* pool_resource
) {
135 return pool_resource
->content_id() == content_id
;
137 if (it
== unused_resources_
.end())
140 Resource
* resource
= *it
;
141 DCHECK(resource_provider_
->CanLockForWrite(resource
->id()));
143 // Transfer resource to |in_use_resources_|.
144 in_use_resources_
.set(resource
->id(), unused_resources_
.take(it
));
145 in_use_memory_usage_bytes_
+= ResourceUtil::UncheckedSizeInBytes
<size_t>(
146 resource
->size(), resource
->format());
150 void ResourcePool::ReleaseResource(Resource
* resource
, uint64_t content_id
) {
151 auto it
= in_use_resources_
.find(resource
->id());
152 DCHECK(it
!= in_use_resources_
.end());
154 PoolResource
* pool_resource
= it
->second
;
155 pool_resource
->set_content_id(content_id
);
156 pool_resource
->set_last_usage(base::TimeTicks::Now());
158 // Transfer resource to |busy_resources_|.
159 busy_resources_
.push_back(in_use_resources_
.take_and_erase(it
));
160 in_use_memory_usage_bytes_
-= ResourceUtil::UncheckedSizeInBytes
<size_t>(
161 pool_resource
->size(), pool_resource
->format());
163 // Now that we have evictable resources, schedule an eviction call for this
164 // resource if necessary.
165 ScheduleEvictExpiredResourcesIn(resource_expiration_delay_
);
168 void ResourcePool::SetResourceUsageLimits(size_t max_memory_usage_bytes
,
169 size_t max_resource_count
) {
170 max_memory_usage_bytes_
= max_memory_usage_bytes
;
171 max_resource_count_
= max_resource_count
;
173 ReduceResourceUsage();
176 void ResourcePool::ReduceResourceUsage() {
177 while (!unused_resources_
.empty()) {
178 if (!ResourceUsageTooHigh())
181 // LRU eviction pattern. Most recently used might be blocked by
182 // a read lock fence but it's still better to evict the least
183 // recently used as it prevents a resource that is hard to reuse
184 // because of unique size from being kept around. Resources that
185 // can't be locked for write might also not be truly free-able.
186 // We can free the resource here but it doesn't mean that the
187 // memory is necessarily returned to the OS.
188 DeleteResource(unused_resources_
.take_front());
192 bool ResourcePool::ResourceUsageTooHigh() {
193 if (total_resource_count_
> max_resource_count_
)
195 if (total_memory_usage_bytes_
> max_memory_usage_bytes_
)
200 void ResourcePool::DeleteResource(scoped_ptr
<PoolResource
> resource
) {
201 size_t resource_bytes
= ResourceUtil::UncheckedSizeInBytes
<size_t>(
202 resource
->size(), resource
->format());
203 total_memory_usage_bytes_
-= resource_bytes
;
204 --total_resource_count_
;
207 void ResourcePool::CheckBusyResources() {
208 for (size_t i
= 0; i
< busy_resources_
.size();) {
209 ResourceDeque::iterator
it(busy_resources_
.begin() + i
);
210 PoolResource
* resource
= *it
;
212 if (resource_provider_
->CanLockForWrite(resource
->id())) {
213 DidFinishUsingResource(busy_resources_
.take(it
));
214 } else if (resource_provider_
->IsLost(resource
->id())) {
215 // Remove lost resources from pool.
216 DeleteResource(busy_resources_
.take(it
));
223 void ResourcePool::DidFinishUsingResource(scoped_ptr
<PoolResource
> resource
) {
224 unused_resources_
.push_back(resource
.Pass());
227 void ResourcePool::ScheduleEvictExpiredResourcesIn(
228 base::TimeDelta time_from_now
) {
229 if (evict_expired_resources_pending_
)
232 evict_expired_resources_pending_
= true;
234 task_runner_
->PostDelayedTask(FROM_HERE
,
235 base::Bind(&ResourcePool::EvictExpiredResources
,
236 weak_ptr_factory_
.GetWeakPtr()),
240 void ResourcePool::EvictExpiredResources() {
241 evict_expired_resources_pending_
= false;
242 base::TimeTicks current_time
= base::TimeTicks::Now();
244 EvictResourcesNotUsedSince(current_time
- resource_expiration_delay_
);
246 if (unused_resources_
.empty() && busy_resources_
.empty()) {
247 // Nothing is evictable.
251 // If we still have evictable resources, schedule a call to
252 // EvictExpiredResources at the time when the LRU buffer expires.
253 ScheduleEvictExpiredResourcesIn(GetUsageTimeForLRUResource() +
254 resource_expiration_delay_
- current_time
);
257 void ResourcePool::EvictResourcesNotUsedSince(base::TimeTicks time_limit
) {
258 while (!unused_resources_
.empty()) {
259 // |unused_resources_| is not strictly ordered with regards to last_usage,
260 // as this may not exactly line up with the time a resource became non-busy.
261 // However, this should be roughly ordered, and will only introduce slight
262 // delays in freeing expired resources.
263 if (unused_resources_
.front()->last_usage() > time_limit
)
266 DeleteResource(unused_resources_
.take_front());
269 // Also free busy resources older than the delay. With a sufficiently large
270 // delay, such as the 1 second used here, any "busy" resources which have
271 // expired are not likely to be busy. Additionally, freeing a "busy" resource
272 // has no downside other than incorrect accounting.
273 while (!busy_resources_
.empty()) {
274 if (busy_resources_
.front()->last_usage() > time_limit
)
277 DeleteResource(busy_resources_
.take_front());
281 base::TimeTicks
ResourcePool::GetUsageTimeForLRUResource() const {
282 if (!unused_resources_
.empty()) {
283 return unused_resources_
.front()->last_usage();
286 // This is only called when we have at least one evictable resource.
287 DCHECK(!busy_resources_
.empty());
288 return busy_resources_
.front()->last_usage();
291 bool ResourcePool::OnMemoryDump(const base::trace_event::MemoryDumpArgs
& args
,
292 base::trace_event::ProcessMemoryDump
* pmd
) {
293 for (const auto& resource
: unused_resources_
) {
294 resource
->OnMemoryDump(pmd
, resource_provider_
, true /* is_free */);
296 for (const auto& resource
: busy_resources_
) {
297 resource
->OnMemoryDump(pmd
, resource_provider_
, false /* is_free */);
299 for (const auto& entry
: in_use_resources_
) {
300 entry
.second
->OnMemoryDump(pmd
, resource_provider_
, false /* is_free */);