OLD | NEW |
| (Empty) |
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/tile_manager.h" | |
6 | |
7 #include <algorithm> | |
8 #include <limits> | |
9 #include <string> | |
10 | |
11 #include "base/bind.h" | |
12 #include "base/json/json_writer.h" | |
13 #include "base/logging.h" | |
14 #include "base/metrics/histogram.h" | |
15 #include "base/trace_event/trace_event_argument.h" | |
16 #include "cc/debug/devtools_instrumentation.h" | |
17 #include "cc/debug/frame_viewer_instrumentation.h" | |
18 #include "cc/debug/traced_value.h" | |
19 #include "cc/layers/picture_layer_impl.h" | |
20 #include "cc/resources/raster_buffer.h" | |
21 #include "cc/resources/rasterizer.h" | |
22 #include "cc/resources/tile.h" | |
23 #include "cc/resources/tile_task_runner.h" | |
24 #include "ui/gfx/geometry/rect_conversions.h" | |
25 | |
26 namespace cc { | |
27 namespace { | |
28 | |
29 // Flag to indicate whether we should try and detect that | |
30 // a tile is of solid color. | |
31 const bool kUseColorEstimator = true; | |
32 | |
33 class RasterTaskImpl : public RasterTask { | |
34 public: | |
35 RasterTaskImpl( | |
36 const Resource* resource, | |
37 RasterSource* raster_source, | |
38 const gfx::Rect& content_rect, | |
39 float contents_scale, | |
40 TileResolution tile_resolution, | |
41 int layer_id, | |
42 const void* tile_id, | |
43 int source_frame_number, | |
44 bool analyze_picture, | |
45 const base::Callback<void(const RasterSource::SolidColorAnalysis&, bool)>& | |
46 reply, | |
47 ImageDecodeTask::Vector* dependencies) | |
48 : RasterTask(resource, dependencies), | |
49 raster_source_(raster_source), | |
50 content_rect_(content_rect), | |
51 contents_scale_(contents_scale), | |
52 tile_resolution_(tile_resolution), | |
53 layer_id_(layer_id), | |
54 tile_id_(tile_id), | |
55 source_frame_number_(source_frame_number), | |
56 analyze_picture_(analyze_picture), | |
57 reply_(reply) {} | |
58 | |
59 // Overridden from Task: | |
60 void RunOnWorkerThread() override { | |
61 TRACE_EVENT0("cc", "RasterizerTaskImpl::RunOnWorkerThread"); | |
62 | |
63 DCHECK(raster_source_.get()); | |
64 DCHECK(raster_buffer_); | |
65 | |
66 if (analyze_picture_) { | |
67 Analyze(raster_source_.get()); | |
68 if (analysis_.is_solid_color) | |
69 return; | |
70 } | |
71 | |
72 Raster(raster_source_.get()); | |
73 } | |
74 | |
75 // Overridden from TileTask: | |
76 void ScheduleOnOriginThread(TileTaskClient* client) override { | |
77 DCHECK(!raster_buffer_); | |
78 raster_buffer_ = client->AcquireBufferForRaster(resource()); | |
79 } | |
80 void CompleteOnOriginThread(TileTaskClient* client) override { | |
81 client->ReleaseBufferForRaster(raster_buffer_.Pass()); | |
82 } | |
83 void RunReplyOnOriginThread() override { | |
84 DCHECK(!raster_buffer_); | |
85 reply_.Run(analysis_, !HasFinishedRunning()); | |
86 } | |
87 | |
88 protected: | |
89 ~RasterTaskImpl() override { DCHECK(!raster_buffer_); } | |
90 | |
91 private: | |
92 void Analyze(const RasterSource* raster_source) { | |
93 frame_viewer_instrumentation::ScopedAnalyzeTask analyze_task( | |
94 tile_id_, tile_resolution_, source_frame_number_, layer_id_); | |
95 | |
96 DCHECK(raster_source); | |
97 | |
98 raster_source->PerformSolidColorAnalysis(content_rect_, contents_scale_, | |
99 &analysis_); | |
100 | |
101 // Record the solid color prediction. | |
102 UMA_HISTOGRAM_BOOLEAN("Renderer4.SolidColorTilesAnalyzed", | |
103 analysis_.is_solid_color); | |
104 | |
105 // Clear the flag if we're not using the estimator. | |
106 analysis_.is_solid_color &= kUseColorEstimator; | |
107 } | |
108 | |
109 void Raster(const RasterSource* raster_source) { | |
110 frame_viewer_instrumentation::ScopedRasterTask raster_task( | |
111 tile_id_, tile_resolution_, source_frame_number_, layer_id_); | |
112 | |
113 DCHECK(raster_source); | |
114 | |
115 raster_buffer_->Playback(raster_source_.get(), content_rect_, | |
116 contents_scale_); | |
117 } | |
118 | |
119 RasterSource::SolidColorAnalysis analysis_; | |
120 scoped_refptr<RasterSource> raster_source_; | |
121 gfx::Rect content_rect_; | |
122 float contents_scale_; | |
123 TileResolution tile_resolution_; | |
124 int layer_id_; | |
125 const void* tile_id_; | |
126 int source_frame_number_; | |
127 bool analyze_picture_; | |
128 const base::Callback<void(const RasterSource::SolidColorAnalysis&, bool)> | |
129 reply_; | |
130 scoped_ptr<RasterBuffer> raster_buffer_; | |
131 | |
132 DISALLOW_COPY_AND_ASSIGN(RasterTaskImpl); | |
133 }; | |
134 | |
135 class ImageDecodeTaskImpl : public ImageDecodeTask { | |
136 public: | |
137 ImageDecodeTaskImpl(SkPixelRef* pixel_ref, | |
138 const base::Callback<void(bool was_canceled)>& reply) | |
139 : pixel_ref_(skia::SharePtr(pixel_ref)), | |
140 reply_(reply) {} | |
141 | |
142 // Overridden from Task: | |
143 void RunOnWorkerThread() override { | |
144 TRACE_EVENT0("cc", "ImageDecodeTaskImpl::RunOnWorkerThread"); | |
145 | |
146 devtools_instrumentation::ScopedImageDecodeTask image_decode_task( | |
147 pixel_ref_.get()); | |
148 // This will cause the image referred to by pixel ref to be decoded. | |
149 pixel_ref_->lockPixels(); | |
150 pixel_ref_->unlockPixels(); | |
151 | |
152 // Release the reference after decoding image to ensure that it is not | |
153 // kept alive unless needed. | |
154 pixel_ref_.clear(); | |
155 } | |
156 | |
157 // Overridden from TileTask: | |
158 void ScheduleOnOriginThread(TileTaskClient* client) override {} | |
159 void CompleteOnOriginThread(TileTaskClient* client) override {} | |
160 void RunReplyOnOriginThread() override { reply_.Run(!HasFinishedRunning()); } | |
161 | |
162 protected: | |
163 ~ImageDecodeTaskImpl() override {} | |
164 | |
165 private: | |
166 skia::RefPtr<SkPixelRef> pixel_ref_; | |
167 const base::Callback<void(bool was_canceled)> reply_; | |
168 | |
169 DISALLOW_COPY_AND_ASSIGN(ImageDecodeTaskImpl); | |
170 }; | |
171 | |
172 const char* TaskSetName(TaskSet task_set) { | |
173 switch (task_set) { | |
174 case TileManager::ALL: | |
175 return "ALL"; | |
176 case TileManager::REQUIRED_FOR_ACTIVATION: | |
177 return "REQUIRED_FOR_ACTIVATION"; | |
178 case TileManager::REQUIRED_FOR_DRAW: | |
179 return "REQUIRED_FOR_DRAW"; | |
180 } | |
181 | |
182 NOTREACHED(); | |
183 return "Invalid TaskSet"; | |
184 } | |
185 | |
186 } // namespace | |
187 | |
188 RasterTaskCompletionStats::RasterTaskCompletionStats() | |
189 : completed_count(0u), canceled_count(0u) {} | |
190 | |
191 scoped_refptr<base::trace_event::ConvertableToTraceFormat> | |
192 RasterTaskCompletionStatsAsValue(const RasterTaskCompletionStats& stats) { | |
193 scoped_refptr<base::trace_event::TracedValue> state = | |
194 new base::trace_event::TracedValue(); | |
195 state->SetInteger("completed_count", stats.completed_count); | |
196 state->SetInteger("canceled_count", stats.canceled_count); | |
197 return state; | |
198 } | |
199 | |
200 // static | |
201 scoped_ptr<TileManager> TileManager::Create( | |
202 TileManagerClient* client, | |
203 base::SequencedTaskRunner* task_runner, | |
204 ResourcePool* resource_pool, | |
205 TileTaskRunner* tile_task_runner, | |
206 Rasterizer* rasterizer, | |
207 size_t scheduled_raster_task_limit) { | |
208 return make_scoped_ptr(new TileManager(client, task_runner, resource_pool, | |
209 tile_task_runner, rasterizer, | |
210 scheduled_raster_task_limit)); | |
211 } | |
212 | |
213 TileManager::TileManager( | |
214 TileManagerClient* client, | |
215 const scoped_refptr<base::SequencedTaskRunner>& task_runner, | |
216 ResourcePool* resource_pool, | |
217 TileTaskRunner* tile_task_runner, | |
218 Rasterizer* rasterizer, | |
219 size_t scheduled_raster_task_limit) | |
220 : client_(client), | |
221 task_runner_(task_runner), | |
222 resource_pool_(resource_pool), | |
223 tile_task_runner_(tile_task_runner), | |
224 rasterizer_(rasterizer), | |
225 scheduled_raster_task_limit_(scheduled_raster_task_limit), | |
226 all_tiles_that_need_to_be_rasterized_are_scheduled_(true), | |
227 did_check_for_completed_tasks_since_last_schedule_tasks_(true), | |
228 did_oom_on_last_assign_(false), | |
229 ready_to_activate_notifier_( | |
230 task_runner_.get(), | |
231 base::Bind(&TileManager::NotifyReadyToActivate, | |
232 base::Unretained(this))), | |
233 ready_to_draw_notifier_( | |
234 task_runner_.get(), | |
235 base::Bind(&TileManager::NotifyReadyToDraw, base::Unretained(this))), | |
236 ready_to_activate_check_notifier_( | |
237 task_runner_.get(), | |
238 base::Bind(&TileManager::CheckIfReadyToActivate, | |
239 base::Unretained(this))), | |
240 ready_to_draw_check_notifier_( | |
241 task_runner_.get(), | |
242 base::Bind(&TileManager::CheckIfReadyToDraw, base::Unretained(this))), | |
243 more_tiles_need_prepare_check_notifier_( | |
244 task_runner_.get(), | |
245 base::Bind(&TileManager::CheckIfMoreTilesNeedToBePrepared, | |
246 base::Unretained(this))), | |
247 did_notify_ready_to_activate_(false), | |
248 did_notify_ready_to_draw_(false) { | |
249 tile_task_runner_->SetClient(this); | |
250 } | |
251 | |
252 TileManager::~TileManager() { | |
253 // Reset global state and manage. This should cause | |
254 // our memory usage to drop to zero. | |
255 global_state_ = GlobalStateThatImpactsTilePriority(); | |
256 | |
257 TileTaskQueue empty; | |
258 tile_task_runner_->ScheduleTasks(&empty); | |
259 orphan_raster_tasks_.clear(); | |
260 | |
261 // This should finish all pending tasks and release any uninitialized | |
262 // resources. | |
263 tile_task_runner_->Shutdown(); | |
264 tile_task_runner_->CheckForCompletedTasks(); | |
265 | |
266 FreeResourcesForReleasedTiles(); | |
267 CleanUpReleasedTiles(); | |
268 } | |
269 | |
270 void TileManager::Release(Tile* tile) { | |
271 released_tiles_.push_back(tile); | |
272 } | |
273 | |
274 TaskSetCollection TileManager::TasksThatShouldBeForcedToComplete() const { | |
275 TaskSetCollection tasks_that_should_be_forced_to_complete; | |
276 if (global_state_.tree_priority != SMOOTHNESS_TAKES_PRIORITY) | |
277 tasks_that_should_be_forced_to_complete[REQUIRED_FOR_ACTIVATION] = true; | |
278 return tasks_that_should_be_forced_to_complete; | |
279 } | |
280 | |
281 void TileManager::FreeResourcesForReleasedTiles() { | |
282 for (std::vector<Tile*>::iterator it = released_tiles_.begin(); | |
283 it != released_tiles_.end(); | |
284 ++it) { | |
285 Tile* tile = *it; | |
286 FreeResourcesForTile(tile); | |
287 } | |
288 } | |
289 | |
290 void TileManager::CleanUpReleasedTiles() { | |
291 std::vector<Tile*>::iterator it = released_tiles_.begin(); | |
292 while (it != released_tiles_.end()) { | |
293 Tile* tile = *it; | |
294 | |
295 if (tile->HasRasterTask()) { | |
296 ++it; | |
297 continue; | |
298 } | |
299 | |
300 DCHECK(!tile->HasResource()); | |
301 DCHECK(tiles_.find(tile->id()) != tiles_.end()); | |
302 tiles_.erase(tile->id()); | |
303 | |
304 LayerCountMap::iterator layer_it = | |
305 used_layer_counts_.find(tile->layer_id()); | |
306 DCHECK_GT(layer_it->second, 0); | |
307 if (--layer_it->second == 0) { | |
308 used_layer_counts_.erase(layer_it); | |
309 image_decode_tasks_.erase(tile->layer_id()); | |
310 } | |
311 | |
312 delete tile; | |
313 it = released_tiles_.erase(it); | |
314 } | |
315 } | |
316 | |
317 void TileManager::DidFinishRunningTileTasks(TaskSet task_set) { | |
318 TRACE_EVENT1("cc", "TileManager::DidFinishRunningTileTasks", "task_set", | |
319 TaskSetName(task_set)); | |
320 | |
321 switch (task_set) { | |
322 case ALL: { | |
323 bool memory_usage_above_limit = | |
324 resource_pool_->total_memory_usage_bytes() > | |
325 global_state_.soft_memory_limit_in_bytes; | |
326 | |
327 if (all_tiles_that_need_to_be_rasterized_are_scheduled_ && | |
328 !memory_usage_above_limit) | |
329 return; | |
330 | |
331 more_tiles_need_prepare_check_notifier_.Schedule(); | |
332 return; | |
333 } | |
334 case REQUIRED_FOR_ACTIVATION: | |
335 ready_to_activate_check_notifier_.Schedule(); | |
336 return; | |
337 case REQUIRED_FOR_DRAW: | |
338 ready_to_draw_check_notifier_.Schedule(); | |
339 return; | |
340 } | |
341 | |
342 NOTREACHED(); | |
343 } | |
344 | |
345 void TileManager::PrepareTiles( | |
346 const GlobalStateThatImpactsTilePriority& state) { | |
347 TRACE_EVENT0("cc", "TileManager::PrepareTiles"); | |
348 | |
349 global_state_ = state; | |
350 | |
351 PrepareTilesMode prepare_tiles_mode = rasterizer_->GetPrepareTilesMode(); | |
352 | |
353 // TODO(hendrikw): Consider moving some of this code to the rasterizer. | |
354 if (prepare_tiles_mode != PrepareTilesMode::PREPARE_NONE) { | |
355 // We need to call CheckForCompletedTasks() once in-between each call | |
356 // to ScheduleTasks() to prevent canceled tasks from being scheduled. | |
357 if (!did_check_for_completed_tasks_since_last_schedule_tasks_) { | |
358 tile_task_runner_->CheckForCompletedTasks(); | |
359 did_check_for_completed_tasks_since_last_schedule_tasks_ = true; | |
360 } | |
361 | |
362 FreeResourcesForReleasedTiles(); | |
363 CleanUpReleasedTiles(); | |
364 | |
365 TileVector tiles_that_need_to_be_rasterized; | |
366 scoped_ptr<RasterTilePriorityQueue> raster_priority_queue( | |
367 client_->BuildRasterQueue(global_state_.tree_priority, | |
368 RasterTilePriorityQueue::Type::ALL)); | |
369 AssignGpuMemoryToTiles(raster_priority_queue.get(), | |
370 scheduled_raster_task_limit_, | |
371 &tiles_that_need_to_be_rasterized); | |
372 | |
373 // Inform the client that will likely require a draw if the highest priority | |
374 // tile that will be rasterized is required for draw. | |
375 client_->SetIsLikelyToRequireADraw( | |
376 !tiles_that_need_to_be_rasterized.empty() && | |
377 (*tiles_that_need_to_be_rasterized.begin())->required_for_draw()); | |
378 | |
379 // Schedule tile tasks. | |
380 ScheduleTasks(tiles_that_need_to_be_rasterized); | |
381 | |
382 did_notify_ready_to_activate_ = false; | |
383 did_notify_ready_to_draw_ = false; | |
384 } else { | |
385 if (global_state_.hard_memory_limit_in_bytes == 0) { | |
386 resource_pool_->CheckBusyResources(false); | |
387 MemoryUsage memory_limit(0, 0); | |
388 MemoryUsage memory_usage(resource_pool_->acquired_memory_usage_bytes(), | |
389 resource_pool_->acquired_resource_count()); | |
390 FreeTileResourcesUntilUsageIsWithinLimit(nullptr, memory_limit, | |
391 &memory_usage); | |
392 } | |
393 | |
394 did_notify_ready_to_activate_ = false; | |
395 did_notify_ready_to_draw_ = false; | |
396 ready_to_activate_notifier_.Schedule(); | |
397 ready_to_draw_notifier_.Schedule(); | |
398 } | |
399 | |
400 TRACE_EVENT_INSTANT1("cc", "DidPrepareTiles", TRACE_EVENT_SCOPE_THREAD, | |
401 "state", BasicStateAsValue()); | |
402 | |
403 TRACE_COUNTER_ID1("cc", "unused_memory_bytes", this, | |
404 resource_pool_->total_memory_usage_bytes() - | |
405 resource_pool_->acquired_memory_usage_bytes()); | |
406 } | |
407 | |
408 void TileManager::SynchronouslyRasterizeTiles( | |
409 const GlobalStateThatImpactsTilePriority& state) { | |
410 TRACE_EVENT0("cc", "TileManager::SynchronouslyRasterizeTiles"); | |
411 | |
412 DCHECK(rasterizer_->GetPrepareTilesMode() != | |
413 PrepareTilesMode::RASTERIZE_PRIORITIZED_TILES); | |
414 | |
415 global_state_ = state; | |
416 | |
417 FreeResourcesForReleasedTiles(); | |
418 CleanUpReleasedTiles(); | |
419 | |
420 scoped_ptr<RasterTilePriorityQueue> required_for_draw_queue( | |
421 client_->BuildRasterQueue( | |
422 global_state_.tree_priority, | |
423 RasterTilePriorityQueue::Type::REQUIRED_FOR_DRAW)); | |
424 TileVector tiles_that_need_to_be_rasterized; | |
425 AssignGpuMemoryToTiles(required_for_draw_queue.get(), | |
426 std::numeric_limits<size_t>::max(), | |
427 &tiles_that_need_to_be_rasterized); | |
428 | |
429 // We must reduce the amount of unused resources before calling | |
430 // RunTasks to prevent usage from rising above limits. | |
431 resource_pool_->ReduceResourceUsage(); | |
432 | |
433 // Run and complete all raster task synchronously. | |
434 rasterizer_->RasterizeTiles( | |
435 tiles_that_need_to_be_rasterized, resource_pool_, | |
436 tile_task_runner_->GetResourceFormat(), | |
437 base::Bind(&TileManager::UpdateTileDrawInfo, base::Unretained(this))); | |
438 | |
439 // Use on-demand raster for any required-for-draw tiles that have not been | |
440 // assigned memory after reaching a steady memory state. | |
441 // TODO(hendrikw): Figure out why this would improve jank on some tests - See | |
442 // crbug.com/449288 | |
443 required_for_draw_queue = client_->BuildRasterQueue( | |
444 global_state_.tree_priority, | |
445 RasterTilePriorityQueue::Type::REQUIRED_FOR_DRAW); | |
446 | |
447 // Change to OOM mode for any tiles that have not been been assigned memory. | |
448 // This ensures that we draw even when OOM. | |
449 for (; !required_for_draw_queue->IsEmpty(); required_for_draw_queue->Pop()) { | |
450 Tile* tile = required_for_draw_queue->Top(); | |
451 tile->draw_info().set_oom(); | |
452 client_->NotifyTileStateChanged(tile); | |
453 } | |
454 | |
455 TRACE_EVENT_INSTANT1("cc", "DidRasterize", TRACE_EVENT_SCOPE_THREAD, "state", | |
456 BasicStateAsValue()); | |
457 | |
458 TRACE_COUNTER_ID1("cc", "unused_memory_bytes", this, | |
459 resource_pool_->total_memory_usage_bytes() - | |
460 resource_pool_->acquired_memory_usage_bytes()); | |
461 } | |
462 | |
463 void TileManager::UpdateVisibleTiles( | |
464 const GlobalStateThatImpactsTilePriority& state) { | |
465 TRACE_EVENT0("cc", "TileManager::UpdateVisibleTiles"); | |
466 | |
467 tile_task_runner_->CheckForCompletedTasks(); | |
468 | |
469 DCHECK(rasterizer_); | |
470 PrepareTilesMode prepare_tiles_mode = rasterizer_->GetPrepareTilesMode(); | |
471 if (prepare_tiles_mode != PrepareTilesMode::RASTERIZE_PRIORITIZED_TILES) | |
472 SynchronouslyRasterizeTiles(state); | |
473 | |
474 did_check_for_completed_tasks_since_last_schedule_tasks_ = true; | |
475 | |
476 TRACE_EVENT_INSTANT1( | |
477 "cc", | |
478 "DidUpdateVisibleTiles", | |
479 TRACE_EVENT_SCOPE_THREAD, | |
480 "stats", | |
481 RasterTaskCompletionStatsAsValue(update_visible_tiles_stats_)); | |
482 update_visible_tiles_stats_ = RasterTaskCompletionStats(); | |
483 } | |
484 | |
485 scoped_refptr<base::trace_event::ConvertableToTraceFormat> | |
486 TileManager::BasicStateAsValue() const { | |
487 scoped_refptr<base::trace_event::TracedValue> value = | |
488 new base::trace_event::TracedValue(); | |
489 BasicStateAsValueInto(value.get()); | |
490 return value; | |
491 } | |
492 | |
493 void TileManager::BasicStateAsValueInto( | |
494 base::trace_event::TracedValue* state) const { | |
495 state->SetInteger("tile_count", tiles_.size()); | |
496 state->SetBoolean("did_oom_on_last_assign", did_oom_on_last_assign_); | |
497 state->BeginDictionary("global_state"); | |
498 global_state_.AsValueInto(state); | |
499 state->EndDictionary(); | |
500 } | |
501 | |
502 scoped_ptr<EvictionTilePriorityQueue> | |
503 TileManager::FreeTileResourcesUntilUsageIsWithinLimit( | |
504 scoped_ptr<EvictionTilePriorityQueue> eviction_priority_queue, | |
505 const MemoryUsage& limit, | |
506 MemoryUsage* usage) { | |
507 while (usage->Exceeds(limit)) { | |
508 if (!eviction_priority_queue) { | |
509 eviction_priority_queue = | |
510 client_->BuildEvictionQueue(global_state_.tree_priority); | |
511 } | |
512 if (eviction_priority_queue->IsEmpty()) | |
513 break; | |
514 | |
515 Tile* tile = eviction_priority_queue->Top(); | |
516 *usage -= MemoryUsage::FromTile(tile); | |
517 FreeResourcesForTileAndNotifyClientIfTileWasReadyToDraw(tile); | |
518 eviction_priority_queue->Pop(); | |
519 } | |
520 return eviction_priority_queue; | |
521 } | |
522 | |
523 scoped_ptr<EvictionTilePriorityQueue> | |
524 TileManager::FreeTileResourcesWithLowerPriorityUntilUsageIsWithinLimit( | |
525 scoped_ptr<EvictionTilePriorityQueue> eviction_priority_queue, | |
526 const MemoryUsage& limit, | |
527 const TilePriority& other_priority, | |
528 MemoryUsage* usage) { | |
529 while (usage->Exceeds(limit)) { | |
530 if (!eviction_priority_queue) { | |
531 eviction_priority_queue = | |
532 client_->BuildEvictionQueue(global_state_.tree_priority); | |
533 } | |
534 if (eviction_priority_queue->IsEmpty()) | |
535 break; | |
536 | |
537 Tile* tile = eviction_priority_queue->Top(); | |
538 if (!other_priority.IsHigherPriorityThan(tile->combined_priority())) | |
539 break; | |
540 | |
541 *usage -= MemoryUsage::FromTile(tile); | |
542 FreeResourcesForTileAndNotifyClientIfTileWasReadyToDraw(tile); | |
543 eviction_priority_queue->Pop(); | |
544 } | |
545 return eviction_priority_queue; | |
546 } | |
547 | |
548 bool TileManager::TilePriorityViolatesMemoryPolicy( | |
549 const TilePriority& priority) { | |
550 switch (global_state_.memory_limit_policy) { | |
551 case ALLOW_NOTHING: | |
552 return true; | |
553 case ALLOW_ABSOLUTE_MINIMUM: | |
554 return priority.priority_bin > TilePriority::NOW; | |
555 case ALLOW_PREPAINT_ONLY: | |
556 return priority.priority_bin > TilePriority::SOON; | |
557 case ALLOW_ANYTHING: | |
558 return priority.distance_to_visible == | |
559 std::numeric_limits<float>::infinity(); | |
560 } | |
561 NOTREACHED(); | |
562 return true; | |
563 } | |
564 | |
565 void TileManager::AssignGpuMemoryToTiles( | |
566 RasterTilePriorityQueue* raster_priority_queue, | |
567 size_t scheduled_raster_task_limit, | |
568 TileVector* tiles_that_need_to_be_rasterized) { | |
569 TRACE_EVENT_BEGIN0("cc", "TileManager::AssignGpuMemoryToTiles"); | |
570 | |
571 // Maintain the list of released resources that can potentially be re-used | |
572 // or deleted. If this operation becomes expensive too, only do this after | |
573 // some resource(s) was returned. Note that in that case, one also need to | |
574 // invalidate when releasing some resource from the pool. | |
575 resource_pool_->CheckBusyResources(false); | |
576 | |
577 // Now give memory out to the tiles until we're out, and build | |
578 // the needs-to-be-rasterized queue. | |
579 unsigned schedule_priority = 1u; | |
580 all_tiles_that_need_to_be_rasterized_are_scheduled_ = true; | |
581 bool had_enough_memory_to_schedule_tiles_needed_now = true; | |
582 | |
583 MemoryUsage hard_memory_limit(global_state_.hard_memory_limit_in_bytes, | |
584 global_state_.num_resources_limit); | |
585 MemoryUsage soft_memory_limit(global_state_.soft_memory_limit_in_bytes, | |
586 global_state_.num_resources_limit); | |
587 MemoryUsage memory_usage(resource_pool_->acquired_memory_usage_bytes(), | |
588 resource_pool_->acquired_resource_count()); | |
589 | |
590 scoped_ptr<EvictionTilePriorityQueue> eviction_priority_queue; | |
591 for (; !raster_priority_queue->IsEmpty(); raster_priority_queue->Pop()) { | |
592 Tile* tile = raster_priority_queue->Top(); | |
593 TilePriority priority = tile->combined_priority(); | |
594 | |
595 if (TilePriorityViolatesMemoryPolicy(priority)) { | |
596 TRACE_EVENT_INSTANT0( | |
597 "cc", "TileManager::AssignGpuMemory tile violates memory policy", | |
598 TRACE_EVENT_SCOPE_THREAD); | |
599 break; | |
600 } | |
601 | |
602 // We won't be able to schedule this tile, so break out early. | |
603 if (tiles_that_need_to_be_rasterized->size() >= | |
604 scheduled_raster_task_limit) { | |
605 all_tiles_that_need_to_be_rasterized_are_scheduled_ = false; | |
606 break; | |
607 } | |
608 | |
609 TileDrawInfo& draw_info = tile->draw_info(); | |
610 tile->scheduled_priority_ = schedule_priority++; | |
611 | |
612 DCHECK_IMPLIES(draw_info.mode() != TileDrawInfo::OOM_MODE, | |
613 !draw_info.IsReadyToDraw()); | |
614 | |
615 // If the tile already has a raster_task, then the memory used by it is | |
616 // already accounted for in memory_usage. Otherwise, we'll have to acquire | |
617 // more memory to create a raster task. | |
618 MemoryUsage memory_required_by_tile_to_be_scheduled; | |
619 if (!tile->raster_task_.get()) { | |
620 memory_required_by_tile_to_be_scheduled = MemoryUsage::FromConfig( | |
621 tile->desired_texture_size(), tile_task_runner_->GetResourceFormat()); | |
622 } | |
623 | |
624 bool tile_is_needed_now = priority.priority_bin == TilePriority::NOW; | |
625 | |
626 // This is the memory limit that will be used by this tile. Depending on | |
627 // the tile priority, it will be one of hard_memory_limit or | |
628 // soft_memory_limit. | |
629 MemoryUsage& tile_memory_limit = | |
630 tile_is_needed_now ? hard_memory_limit : soft_memory_limit; | |
631 | |
632 const MemoryUsage& scheduled_tile_memory_limit = | |
633 tile_memory_limit - memory_required_by_tile_to_be_scheduled; | |
634 eviction_priority_queue = | |
635 FreeTileResourcesWithLowerPriorityUntilUsageIsWithinLimit( | |
636 eviction_priority_queue.Pass(), scheduled_tile_memory_limit, | |
637 priority, &memory_usage); | |
638 bool memory_usage_is_within_limit = | |
639 !memory_usage.Exceeds(scheduled_tile_memory_limit); | |
640 | |
641 // If we couldn't fit the tile into our current memory limit, then we're | |
642 // done. | |
643 if (!memory_usage_is_within_limit) { | |
644 if (tile_is_needed_now) | |
645 had_enough_memory_to_schedule_tiles_needed_now = false; | |
646 all_tiles_that_need_to_be_rasterized_are_scheduled_ = false; | |
647 break; | |
648 } | |
649 | |
650 memory_usage += memory_required_by_tile_to_be_scheduled; | |
651 tiles_that_need_to_be_rasterized->push_back(tile); | |
652 } | |
653 | |
654 // Note that we should try and further reduce memory in case the above loop | |
655 // didn't reduce memory. This ensures that we always release as many resources | |
656 // as possible to stay within the memory limit. | |
657 eviction_priority_queue = FreeTileResourcesUntilUsageIsWithinLimit( | |
658 eviction_priority_queue.Pass(), hard_memory_limit, &memory_usage); | |
659 | |
660 UMA_HISTOGRAM_BOOLEAN("TileManager.ExceededMemoryBudget", | |
661 !had_enough_memory_to_schedule_tiles_needed_now); | |
662 did_oom_on_last_assign_ = !had_enough_memory_to_schedule_tiles_needed_now; | |
663 | |
664 memory_stats_from_last_assign_.total_budget_in_bytes = | |
665 global_state_.hard_memory_limit_in_bytes; | |
666 memory_stats_from_last_assign_.total_bytes_used = memory_usage.memory_bytes(); | |
667 memory_stats_from_last_assign_.had_enough_memory = | |
668 had_enough_memory_to_schedule_tiles_needed_now; | |
669 | |
670 TRACE_EVENT_END2("cc", "TileManager::AssignGpuMemoryToTiles", | |
671 "all_tiles_that_need_to_be_rasterized_are_scheduled", | |
672 all_tiles_that_need_to_be_rasterized_are_scheduled_, | |
673 "had_enough_memory_to_schedule_tiles_needed_now", | |
674 had_enough_memory_to_schedule_tiles_needed_now); | |
675 } | |
676 | |
677 void TileManager::FreeResourcesForTile(Tile* tile) { | |
678 TileDrawInfo& draw_info = tile->draw_info(); | |
679 if (draw_info.resource_) | |
680 resource_pool_->ReleaseResource(draw_info.resource_.Pass()); | |
681 } | |
682 | |
683 void TileManager::FreeResourcesForTileAndNotifyClientIfTileWasReadyToDraw( | |
684 Tile* tile) { | |
685 bool was_ready_to_draw = tile->IsReadyToDraw(); | |
686 FreeResourcesForTile(tile); | |
687 if (was_ready_to_draw) | |
688 client_->NotifyTileStateChanged(tile); | |
689 } | |
690 | |
691 void TileManager::ScheduleTasks( | |
692 const TileVector& tiles_that_need_to_be_rasterized) { | |
693 TRACE_EVENT1("cc", | |
694 "TileManager::ScheduleTasks", | |
695 "count", | |
696 tiles_that_need_to_be_rasterized.size()); | |
697 | |
698 DCHECK(did_check_for_completed_tasks_since_last_schedule_tasks_); | |
699 | |
700 raster_queue_.Reset(); | |
701 | |
702 // Build a new task queue containing all task currently needed. Tasks | |
703 // are added in order of priority, highest priority task first. | |
704 for (TileVector::const_iterator it = tiles_that_need_to_be_rasterized.begin(); | |
705 it != tiles_that_need_to_be_rasterized.end(); | |
706 ++it) { | |
707 Tile* tile = *it; | |
708 TileDrawInfo& draw_info = tile->draw_info(); | |
709 | |
710 DCHECK(draw_info.requires_resource()); | |
711 DCHECK(!draw_info.resource_); | |
712 | |
713 if (!tile->raster_task_.get()) | |
714 tile->raster_task_ = CreateRasterTask(tile); | |
715 | |
716 TaskSetCollection task_sets; | |
717 if (tile->required_for_activation()) | |
718 task_sets.set(REQUIRED_FOR_ACTIVATION); | |
719 if (tile->required_for_draw()) | |
720 task_sets.set(REQUIRED_FOR_DRAW); | |
721 task_sets.set(ALL); | |
722 raster_queue_.items.push_back( | |
723 TileTaskQueue::Item(tile->raster_task_.get(), task_sets)); | |
724 } | |
725 | |
726 // We must reduce the amount of unused resoruces before calling | |
727 // ScheduleTasks to prevent usage from rising above limits. | |
728 resource_pool_->ReduceResourceUsage(); | |
729 | |
730 // Schedule running of |raster_queue_|. This replaces any previously | |
731 // scheduled tasks and effectively cancels all tasks not present | |
732 // in |raster_queue_|. | |
733 tile_task_runner_->ScheduleTasks(&raster_queue_); | |
734 | |
735 // It's now safe to clean up orphan tasks as raster worker pool is not | |
736 // allowed to keep around unreferenced raster tasks after ScheduleTasks() has | |
737 // been called. | |
738 orphan_raster_tasks_.clear(); | |
739 | |
740 did_check_for_completed_tasks_since_last_schedule_tasks_ = false; | |
741 } | |
742 | |
743 scoped_refptr<ImageDecodeTask> TileManager::CreateImageDecodeTask( | |
744 Tile* tile, | |
745 SkPixelRef* pixel_ref) { | |
746 return make_scoped_refptr(new ImageDecodeTaskImpl( | |
747 pixel_ref, | |
748 base::Bind(&TileManager::OnImageDecodeTaskCompleted, | |
749 base::Unretained(this), | |
750 tile->layer_id(), | |
751 base::Unretained(pixel_ref)))); | |
752 } | |
753 | |
754 scoped_refptr<RasterTask> TileManager::CreateRasterTask(Tile* tile) { | |
755 scoped_ptr<ScopedResource> resource = | |
756 resource_pool_->AcquireResource(tile->desired_texture_size(), | |
757 tile_task_runner_->GetResourceFormat()); | |
758 const ScopedResource* const_resource = resource.get(); | |
759 | |
760 // Create and queue all image decode tasks that this tile depends on. | |
761 ImageDecodeTask::Vector decode_tasks; | |
762 PixelRefTaskMap& existing_pixel_refs = image_decode_tasks_[tile->layer_id()]; | |
763 std::vector<SkPixelRef*> pixel_refs; | |
764 tile->raster_source()->GatherPixelRefs( | |
765 tile->content_rect(), tile->contents_scale(), &pixel_refs); | |
766 for (SkPixelRef* pixel_ref : pixel_refs) { | |
767 uint32_t id = pixel_ref->getGenerationID(); | |
768 | |
769 // Append existing image decode task if available. | |
770 PixelRefTaskMap::iterator decode_task_it = existing_pixel_refs.find(id); | |
771 if (decode_task_it != existing_pixel_refs.end()) { | |
772 decode_tasks.push_back(decode_task_it->second); | |
773 continue; | |
774 } | |
775 | |
776 // Create and append new image decode task for this pixel ref. | |
777 scoped_refptr<ImageDecodeTask> decode_task = | |
778 CreateImageDecodeTask(tile, pixel_ref); | |
779 decode_tasks.push_back(decode_task); | |
780 existing_pixel_refs[id] = decode_task; | |
781 } | |
782 | |
783 return make_scoped_refptr(new RasterTaskImpl( | |
784 const_resource, tile->raster_source(), tile->content_rect(), | |
785 tile->contents_scale(), tile->combined_priority().resolution, | |
786 tile->layer_id(), static_cast<const void*>(tile), | |
787 tile->source_frame_number(), tile->use_picture_analysis(), | |
788 base::Bind(&TileManager::OnRasterTaskCompleted, base::Unretained(this), | |
789 tile->id(), base::Passed(&resource)), | |
790 &decode_tasks)); | |
791 } | |
792 | |
793 void TileManager::OnImageDecodeTaskCompleted(int layer_id, | |
794 SkPixelRef* pixel_ref, | |
795 bool was_canceled) { | |
796 // If the task was canceled, we need to clean it up | |
797 // from |image_decode_tasks_|. | |
798 if (!was_canceled) | |
799 return; | |
800 | |
801 LayerPixelRefTaskMap::iterator layer_it = image_decode_tasks_.find(layer_id); | |
802 if (layer_it == image_decode_tasks_.end()) | |
803 return; | |
804 | |
805 PixelRefTaskMap& pixel_ref_tasks = layer_it->second; | |
806 PixelRefTaskMap::iterator task_it = | |
807 pixel_ref_tasks.find(pixel_ref->getGenerationID()); | |
808 | |
809 if (task_it != pixel_ref_tasks.end()) | |
810 pixel_ref_tasks.erase(task_it); | |
811 } | |
812 | |
813 void TileManager::OnRasterTaskCompleted( | |
814 Tile::Id tile_id, | |
815 scoped_ptr<ScopedResource> resource, | |
816 const RasterSource::SolidColorAnalysis& analysis, | |
817 bool was_canceled) { | |
818 DCHECK(tiles_.find(tile_id) != tiles_.end()); | |
819 | |
820 Tile* tile = tiles_[tile_id]; | |
821 DCHECK(tile->raster_task_.get()); | |
822 orphan_raster_tasks_.push_back(tile->raster_task_); | |
823 tile->raster_task_ = nullptr; | |
824 | |
825 if (was_canceled) { | |
826 ++update_visible_tiles_stats_.canceled_count; | |
827 resource_pool_->ReleaseResource(resource.Pass()); | |
828 return; | |
829 } | |
830 | |
831 UpdateTileDrawInfo(tile, resource.Pass(), analysis); | |
832 } | |
833 | |
834 void TileManager::UpdateTileDrawInfo( | |
835 Tile* tile, | |
836 scoped_ptr<ScopedResource> resource, | |
837 const RasterSource::SolidColorAnalysis& analysis) { | |
838 TileDrawInfo& draw_info = tile->draw_info(); | |
839 | |
840 ++update_visible_tiles_stats_.completed_count; | |
841 | |
842 if (analysis.is_solid_color) { | |
843 draw_info.set_solid_color(analysis.solid_color); | |
844 if (resource) | |
845 resource_pool_->ReleaseResource(resource.Pass()); | |
846 } else { | |
847 DCHECK(resource); | |
848 draw_info.set_use_resource(); | |
849 draw_info.resource_ = resource.Pass(); | |
850 } | |
851 | |
852 client_->NotifyTileStateChanged(tile); | |
853 } | |
854 | |
855 scoped_refptr<Tile> TileManager::CreateTile( | |
856 RasterSource* raster_source, | |
857 const gfx::Size& desired_texture_size, | |
858 const gfx::Rect& content_rect, | |
859 float contents_scale, | |
860 int layer_id, | |
861 int source_frame_number, | |
862 int flags) { | |
863 scoped_refptr<Tile> tile = make_scoped_refptr( | |
864 new Tile(this, raster_source, desired_texture_size, content_rect, | |
865 contents_scale, layer_id, source_frame_number, flags)); | |
866 DCHECK(tiles_.find(tile->id()) == tiles_.end()); | |
867 | |
868 tiles_[tile->id()] = tile.get(); | |
869 used_layer_counts_[tile->layer_id()]++; | |
870 return tile; | |
871 } | |
872 | |
873 void TileManager::SetTileTaskRunnerForTesting( | |
874 TileTaskRunner* tile_task_runner) { | |
875 tile_task_runner_ = tile_task_runner; | |
876 tile_task_runner_->SetClient(this); | |
877 } | |
878 | |
879 bool TileManager::AreRequiredTilesReadyToDraw( | |
880 RasterTilePriorityQueue::Type type) const { | |
881 scoped_ptr<RasterTilePriorityQueue> raster_priority_queue( | |
882 client_->BuildRasterQueue(global_state_.tree_priority, type)); | |
883 // It is insufficient to check whether the raster queue we constructed is | |
884 // empty. The reason for this is that there are situations (rasterize on | |
885 // demand) when the tile both needs raster and it's ready to draw. Hence, we | |
886 // have to iterate the queue to check whether the required tiles are ready to | |
887 // draw. | |
888 for (; !raster_priority_queue->IsEmpty(); raster_priority_queue->Pop()) { | |
889 if (!raster_priority_queue->Top()->IsReadyToDraw()) | |
890 return false; | |
891 } | |
892 return true; | |
893 } | |
894 bool TileManager::IsReadyToActivate() const { | |
895 TRACE_EVENT0("cc", "TileManager::IsReadyToActivate"); | |
896 return AreRequiredTilesReadyToDraw( | |
897 RasterTilePriorityQueue::Type::REQUIRED_FOR_ACTIVATION); | |
898 } | |
899 | |
900 bool TileManager::IsReadyToDraw() const { | |
901 TRACE_EVENT0("cc", "TileManager::IsReadyToDraw"); | |
902 return AreRequiredTilesReadyToDraw( | |
903 RasterTilePriorityQueue::Type::REQUIRED_FOR_DRAW); | |
904 } | |
905 | |
906 void TileManager::NotifyReadyToActivate() { | |
907 TRACE_EVENT0("cc", "TileManager::NotifyReadyToActivate"); | |
908 if (did_notify_ready_to_activate_) | |
909 return; | |
910 client_->NotifyReadyToActivate(); | |
911 did_notify_ready_to_activate_ = true; | |
912 } | |
913 | |
914 void TileManager::NotifyReadyToDraw() { | |
915 TRACE_EVENT0("cc", "TileManager::NotifyReadyToDraw"); | |
916 if (did_notify_ready_to_draw_) | |
917 return; | |
918 client_->NotifyReadyToDraw(); | |
919 did_notify_ready_to_draw_ = true; | |
920 } | |
921 | |
922 void TileManager::CheckIfReadyToActivate() { | |
923 TRACE_EVENT0("cc", "TileManager::CheckIfReadyToActivate"); | |
924 | |
925 tile_task_runner_->CheckForCompletedTasks(); | |
926 did_check_for_completed_tasks_since_last_schedule_tasks_ = true; | |
927 | |
928 if (did_notify_ready_to_activate_) | |
929 return; | |
930 if (!IsReadyToActivate()) | |
931 return; | |
932 | |
933 NotifyReadyToActivate(); | |
934 } | |
935 | |
936 void TileManager::CheckIfReadyToDraw() { | |
937 TRACE_EVENT0("cc", "TileManager::CheckIfReadyToDraw"); | |
938 | |
939 tile_task_runner_->CheckForCompletedTasks(); | |
940 did_check_for_completed_tasks_since_last_schedule_tasks_ = true; | |
941 | |
942 if (did_notify_ready_to_draw_) | |
943 return; | |
944 if (!IsReadyToDraw()) | |
945 return; | |
946 | |
947 NotifyReadyToDraw(); | |
948 } | |
949 | |
950 void TileManager::CheckIfMoreTilesNeedToBePrepared() { | |
951 tile_task_runner_->CheckForCompletedTasks(); | |
952 did_check_for_completed_tasks_since_last_schedule_tasks_ = true; | |
953 | |
954 // When OOM, keep re-assigning memory until we reach a steady state | |
955 // where top-priority tiles are initialized. | |
956 TileVector tiles_that_need_to_be_rasterized; | |
957 scoped_ptr<RasterTilePriorityQueue> raster_priority_queue( | |
958 client_->BuildRasterQueue(global_state_.tree_priority, | |
959 RasterTilePriorityQueue::Type::ALL)); | |
960 AssignGpuMemoryToTiles(raster_priority_queue.get(), | |
961 scheduled_raster_task_limit_, | |
962 &tiles_that_need_to_be_rasterized); | |
963 | |
964 // |tiles_that_need_to_be_rasterized| will be empty when we reach a | |
965 // steady memory state. Keep scheduling tasks until we reach this state. | |
966 if (!tiles_that_need_to_be_rasterized.empty()) { | |
967 ScheduleTasks(tiles_that_need_to_be_rasterized); | |
968 return; | |
969 } | |
970 | |
971 FreeResourcesForReleasedTiles(); | |
972 | |
973 resource_pool_->ReduceResourceUsage(); | |
974 | |
975 // We don't reserve memory for required-for-activation tiles during | |
976 // accelerated gestures, so we just postpone activation when we don't | |
977 // have these tiles, and activate after the accelerated gesture. | |
978 // Likewise if we don't allow any tiles (as is the case when we're | |
979 // invisible), if we have tiles that aren't ready, then we shouldn't | |
980 // activate as activation can cause checkerboards. | |
981 bool wait_for_all_required_tiles = | |
982 global_state_.tree_priority == SMOOTHNESS_TAKES_PRIORITY || | |
983 global_state_.memory_limit_policy == ALLOW_NOTHING; | |
984 | |
985 // Mark any required-for-activation tiles that have not been been assigned | |
986 // memory after reaching a steady memory state as OOM. This ensures that we | |
987 // activate even when OOM. Note that we can't reuse the queue we used for | |
988 // AssignGpuMemoryToTiles, since the AssignGpuMemoryToTiles call could have | |
989 // evicted some tiles that would not be picked up by the old raster queue. | |
990 scoped_ptr<RasterTilePriorityQueue> required_for_activation_queue( | |
991 client_->BuildRasterQueue( | |
992 global_state_.tree_priority, | |
993 RasterTilePriorityQueue::Type::REQUIRED_FOR_ACTIVATION)); | |
994 | |
995 // If we have tiles left to raster for activation, and we don't allow | |
996 // activating without them, then skip activation and return early. | |
997 if (!required_for_activation_queue->IsEmpty() && wait_for_all_required_tiles) | |
998 return; | |
999 | |
1000 // Mark required tiles as OOM so that we can activate without them. | |
1001 for (; !required_for_activation_queue->IsEmpty(); | |
1002 required_for_activation_queue->Pop()) { | |
1003 Tile* tile = required_for_activation_queue->Top(); | |
1004 tile->draw_info().set_oom(); | |
1005 client_->NotifyTileStateChanged(tile); | |
1006 } | |
1007 | |
1008 DCHECK(IsReadyToActivate()); | |
1009 ready_to_activate_check_notifier_.Schedule(); | |
1010 } | |
1011 | |
1012 TileManager::MemoryUsage::MemoryUsage() : memory_bytes_(0), resource_count_(0) { | |
1013 } | |
1014 | |
1015 TileManager::MemoryUsage::MemoryUsage(int64 memory_bytes, int resource_count) | |
1016 : memory_bytes_(memory_bytes), resource_count_(resource_count) { | |
1017 } | |
1018 | |
1019 // static | |
1020 TileManager::MemoryUsage TileManager::MemoryUsage::FromConfig( | |
1021 const gfx::Size& size, | |
1022 ResourceFormat format) { | |
1023 return MemoryUsage(Resource::MemorySizeBytes(size, format), 1); | |
1024 } | |
1025 | |
1026 // static | |
1027 TileManager::MemoryUsage TileManager::MemoryUsage::FromTile(const Tile* tile) { | |
1028 const TileDrawInfo& draw_info = tile->draw_info(); | |
1029 if (draw_info.resource_) { | |
1030 return MemoryUsage::FromConfig(draw_info.resource_->size(), | |
1031 draw_info.resource_->format()); | |
1032 } | |
1033 return MemoryUsage(); | |
1034 } | |
1035 | |
1036 TileManager::MemoryUsage& TileManager::MemoryUsage::operator+=( | |
1037 const MemoryUsage& other) { | |
1038 memory_bytes_ += other.memory_bytes_; | |
1039 resource_count_ += other.resource_count_; | |
1040 return *this; | |
1041 } | |
1042 | |
1043 TileManager::MemoryUsage& TileManager::MemoryUsage::operator-=( | |
1044 const MemoryUsage& other) { | |
1045 memory_bytes_ -= other.memory_bytes_; | |
1046 resource_count_ -= other.resource_count_; | |
1047 return *this; | |
1048 } | |
1049 | |
1050 TileManager::MemoryUsage TileManager::MemoryUsage::operator-( | |
1051 const MemoryUsage& other) { | |
1052 MemoryUsage result = *this; | |
1053 result -= other; | |
1054 return result; | |
1055 } | |
1056 | |
1057 bool TileManager::MemoryUsage::Exceeds(const MemoryUsage& limit) const { | |
1058 return memory_bytes_ > limit.memory_bytes_ || | |
1059 resource_count_ > limit.resource_count_; | |
1060 } | |
1061 | |
1062 } // namespace cc | |
OLD | NEW |