| // Copyright 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "cc/resources/tile_manager.h" |
| |
| #include <algorithm> |
| #include <string> |
| |
| #include "base/bind.h" |
| #include "base/json/json_writer.h" |
| #include "base/logging.h" |
| #include "base/metrics/histogram.h" |
| #include "cc/debug/devtools_instrumentation.h" |
| #include "cc/debug/traced_value.h" |
| #include "cc/resources/raster_worker_pool.h" |
| #include "cc/resources/resource_pool.h" |
| #include "cc/resources/tile.h" |
| #include "third_party/skia/include/core/SkDevice.h" |
| #include "ui/gfx/rect_conversions.h" |
| |
| namespace cc { |
| |
| namespace { |
| |
| // If we raster too fast we become upload bound, and pending |
| // uploads consume memory. For maximum upload throughput, we would |
| // want to allow for upload_throughput * pipeline_time of pending |
| // uploads, after which we are just wasting memory. Since we don't |
| // know our upload throughput yet, this just caps our memory usage. |
| #if defined(OS_ANDROID) |
| // For reference, the Nexus10 can upload 1MB in about 2.5ms. |
| // Assuming a three frame deep pipeline this implies ~20MB. |
| const size_t kMaxPendingUploadBytes = 20 * 1024 * 1024; |
| // TODO(epenner): We should remove this upload limit (crbug.com/176197) |
| const size_t kMaxPendingUploads = 72; |
| #else |
| const size_t kMaxPendingUploadBytes = 100 * 1024 * 1024; |
| const size_t kMaxPendingUploads = 1000; |
| #endif |
| |
| #if defined(OS_ANDROID) |
| const int kMaxNumPendingTasksPerThread = 8; |
| #else |
| const int kMaxNumPendingTasksPerThread = 40; |
| #endif |
| |
| // Determine bin based on three categories of tiles: things we need now, |
| // things we need soon, and eventually. |
| inline TileManagerBin BinFromTilePriority(const TilePriority& prio) { |
| // The amount of time for which we want to have prepainting coverage. |
| const float kPrepaintingWindowTimeSeconds = 1.0f; |
| const float kBackflingGuardDistancePixels = 314.0f; |
| |
| if (prio.time_to_visible_in_seconds == 0) |
| return NOW_BIN; |
| |
| if (prio.resolution == NON_IDEAL_RESOLUTION) |
| return EVENTUALLY_BIN; |
| |
| if (prio.distance_to_visible_in_pixels < kBackflingGuardDistancePixels || |
| prio.time_to_visible_in_seconds < kPrepaintingWindowTimeSeconds) |
| return SOON_BIN; |
| |
| return EVENTUALLY_BIN; |
| } |
| |
| } // namespace |
| |
| scoped_ptr<base::Value> TileManagerBinAsValue(TileManagerBin bin) { |
| switch (bin) { |
| case NOW_BIN: |
| return scoped_ptr<base::Value>(base::Value::CreateStringValue( |
| "NOW_BIN")); |
| case SOON_BIN: |
| return scoped_ptr<base::Value>(base::Value::CreateStringValue( |
| "SOON_BIN")); |
| case EVENTUALLY_BIN: |
| return scoped_ptr<base::Value>(base::Value::CreateStringValue( |
| "EVENTUALLY_BIN")); |
| case NEVER_BIN: |
| return scoped_ptr<base::Value>(base::Value::CreateStringValue( |
| "NEVER_BIN")); |
| default: |
| DCHECK(false) << "Unrecognized TileManagerBin value " << bin; |
| return scoped_ptr<base::Value>(base::Value::CreateStringValue( |
| "<unknown TileManagerBin value>")); |
| } |
| } |
| |
| scoped_ptr<base::Value> TileManagerBinPriorityAsValue( |
| TileManagerBinPriority bin_priority) { |
| switch (bin_priority) { |
| case HIGH_PRIORITY_BIN: |
| return scoped_ptr<base::Value>(base::Value::CreateStringValue( |
| "HIGH_PRIORITY_BIN")); |
| case LOW_PRIORITY_BIN: |
| return scoped_ptr<base::Value>(base::Value::CreateStringValue( |
| "LOW_PRIORITY_BIN")); |
| default: |
| DCHECK(false) << "Unrecognized TileManagerBinPriority value"; |
| return scoped_ptr<base::Value>(base::Value::CreateStringValue( |
| "<unknown TileManagerBinPriority value>")); |
| } |
| } |
| |
| // static |
| scoped_ptr<TileManager> TileManager::Create( |
| TileManagerClient* client, |
| ResourceProvider* resource_provider, |
| size_t num_raster_threads, |
| bool use_color_estimator, |
| RenderingStatsInstrumentation* rendering_stats_instrumentation) { |
| scoped_ptr<RasterWorkerPool> raster_worker_pool = |
| RasterWorkerPool::Create(num_raster_threads); |
| return make_scoped_ptr(new TileManager(client, |
| resource_provider, |
| raster_worker_pool.Pass(), |
| num_raster_threads, |
| use_color_estimator, |
| rendering_stats_instrumentation)); |
| } |
| |
| TileManager::TileManager( |
| TileManagerClient* client, |
| ResourceProvider* resource_provider, |
| scoped_ptr<RasterWorkerPool> raster_worker_pool, |
| size_t num_raster_threads, |
| bool use_color_estimator, |
| RenderingStatsInstrumentation* rendering_stats_instrumentation) |
| : client_(client), |
| resource_pool_(ResourcePool::Create(resource_provider)), |
| raster_worker_pool_(raster_worker_pool.Pass()), |
| manage_tiles_pending_(false), |
| manage_tiles_call_count_(0), |
| bytes_pending_upload_(0), |
| has_performed_uploads_since_last_flush_(false), |
| ever_exceeded_memory_budget_(false), |
| rendering_stats_instrumentation_(rendering_stats_instrumentation), |
| use_color_estimator_(use_color_estimator), |
| did_initialize_visible_tile_(false), |
| pending_tasks_(0), |
| max_pending_tasks_(kMaxNumPendingTasksPerThread * num_raster_threads) { |
| raster_worker_pool_->SetClient(this); |
| } |
| |
| TileManager::~TileManager() { |
| // Reset global state and manage. This should cause |
| // our memory usage to drop to zero. |
| global_state_ = GlobalStateThatImpactsTilePriority(); |
| AssignGpuMemoryToTiles(); |
| // This should finish all pending tasks and release any uninitialized |
| // resources. |
| raster_worker_pool_->Shutdown(); |
| AbortPendingTileUploads(); |
| DCHECK_EQ(0u, tiles_with_pending_upload_.size()); |
| DCHECK_EQ(0u, tiles_.size()); |
| } |
| |
| void TileManager::SetGlobalState( |
| const GlobalStateThatImpactsTilePriority& global_state) { |
| global_state_ = global_state; |
| resource_pool_->SetMaxMemoryUsageBytes( |
| global_state_.memory_limit_in_bytes, |
| global_state_.unused_memory_limit_in_bytes); |
| ScheduleManageTiles(); |
| } |
| |
| void TileManager::RegisterTile(Tile* tile) { |
| DCHECK(std::find(tiles_.begin(), tiles_.end(), tile) == tiles_.end()); |
| DCHECK(!tile->required_for_activation()); |
| tiles_.push_back(tile); |
| ScheduleManageTiles(); |
| } |
| |
| void TileManager::UnregisterTile(Tile* tile) { |
| TileVector::iterator raster_iter = |
| std::find(tiles_that_need_to_be_rasterized_.begin(), |
| tiles_that_need_to_be_rasterized_.end(), |
| tile); |
| if (raster_iter != tiles_that_need_to_be_rasterized_.end()) |
| tiles_that_need_to_be_rasterized_.erase(raster_iter); |
| |
| tiles_that_need_to_be_initialized_for_activation_.erase(tile); |
| |
| DCHECK(std::find(tiles_.begin(), tiles_.end(), tile) != tiles_.end()); |
| FreeResourcesForTile(tile); |
| tiles_.erase(std::remove(tiles_.begin(), tiles_.end(), tile)); |
| } |
| |
| class BinComparator { |
| public: |
| bool operator() (const Tile* a, const Tile* b) const { |
| const ManagedTileState& ams = a->managed_state(); |
| const ManagedTileState& bms = b->managed_state(); |
| if (ams.bin[HIGH_PRIORITY_BIN] != bms.bin[HIGH_PRIORITY_BIN]) |
| return ams.bin[HIGH_PRIORITY_BIN] < bms.bin[HIGH_PRIORITY_BIN]; |
| |
| if (ams.bin[LOW_PRIORITY_BIN] != bms.bin[LOW_PRIORITY_BIN]) |
| return ams.bin[LOW_PRIORITY_BIN] < bms.bin[LOW_PRIORITY_BIN]; |
| |
| if (ams.required_for_activation != bms.required_for_activation) |
| return ams.required_for_activation; |
| |
| if (ams.resolution != bms.resolution) |
| return ams.resolution < bms.resolution; |
| |
| if (ams.time_to_needed_in_seconds != bms.time_to_needed_in_seconds) |
| return ams.time_to_needed_in_seconds < bms.time_to_needed_in_seconds; |
| |
| if (ams.distance_to_visible_in_pixels != |
| bms.distance_to_visible_in_pixels) { |
| return ams.distance_to_visible_in_pixels < |
| bms.distance_to_visible_in_pixels; |
| } |
| |
| gfx::Rect a_rect = a->content_rect(); |
| gfx::Rect b_rect = b->content_rect(); |
| if (a_rect.y() != b_rect.y()) |
| return a_rect.y() < b_rect.y(); |
| return a_rect.x() < b_rect.x(); |
| } |
| }; |
| |
| void TileManager::AssignBinsToTiles() { |
| const TreePriority tree_priority = global_state_.tree_priority; |
| |
| // Memory limit policy works by mapping some bin states to the NEVER bin. |
| TileManagerBin bin_map[NUM_BINS]; |
| if (global_state_.memory_limit_policy == ALLOW_NOTHING) { |
| bin_map[NOW_BIN] = NEVER_BIN; |
| bin_map[SOON_BIN] = NEVER_BIN; |
| bin_map[EVENTUALLY_BIN] = NEVER_BIN; |
| bin_map[NEVER_BIN] = NEVER_BIN; |
| } else if (global_state_.memory_limit_policy == ALLOW_ABSOLUTE_MINIMUM) { |
| bin_map[NOW_BIN] = NOW_BIN; |
| bin_map[SOON_BIN] = NEVER_BIN; |
| bin_map[EVENTUALLY_BIN] = NEVER_BIN; |
| bin_map[NEVER_BIN] = NEVER_BIN; |
| } else if (global_state_.memory_limit_policy == ALLOW_PREPAINT_ONLY) { |
| bin_map[NOW_BIN] = NOW_BIN; |
| bin_map[SOON_BIN] = SOON_BIN; |
| bin_map[EVENTUALLY_BIN] = NEVER_BIN; |
| bin_map[NEVER_BIN] = NEVER_BIN; |
| } else { |
| bin_map[NOW_BIN] = NOW_BIN; |
| bin_map[SOON_BIN] = SOON_BIN; |
| bin_map[EVENTUALLY_BIN] = EVENTUALLY_BIN; |
| bin_map[NEVER_BIN] = NEVER_BIN; |
| } |
| |
| // For each tree, bin into different categories of tiles. |
| for (TileVector::iterator it = tiles_.begin(); |
| it != tiles_.end(); |
| ++it) { |
| Tile* tile = *it; |
| ManagedTileState& mts = tile->managed_state(); |
| |
| TilePriority prio[NUM_BIN_PRIORITIES]; |
| switch (tree_priority) { |
| case SAME_PRIORITY_FOR_BOTH_TREES: |
| prio[HIGH_PRIORITY_BIN] = prio[LOW_PRIORITY_BIN] = |
| tile->combined_priority(); |
| break; |
| case SMOOTHNESS_TAKES_PRIORITY: |
| prio[HIGH_PRIORITY_BIN] = tile->priority(ACTIVE_TREE); |
| prio[LOW_PRIORITY_BIN] = tile->priority(PENDING_TREE); |
| break; |
| case NEW_CONTENT_TAKES_PRIORITY: |
| prio[HIGH_PRIORITY_BIN] = tile->priority(PENDING_TREE); |
| prio[LOW_PRIORITY_BIN] = tile->priority(ACTIVE_TREE); |
| break; |
| } |
| |
| mts.resolution = prio[HIGH_PRIORITY_BIN].resolution; |
| mts.time_to_needed_in_seconds = |
| prio[HIGH_PRIORITY_BIN].time_to_visible_in_seconds; |
| mts.distance_to_visible_in_pixels = |
| prio[HIGH_PRIORITY_BIN].distance_to_visible_in_pixels; |
| mts.required_for_activation = |
| prio[HIGH_PRIORITY_BIN].required_for_activation; |
| mts.bin[HIGH_PRIORITY_BIN] = BinFromTilePriority(prio[HIGH_PRIORITY_BIN]); |
| mts.bin[LOW_PRIORITY_BIN] = BinFromTilePriority(prio[LOW_PRIORITY_BIN]); |
| mts.gpu_memmgr_stats_bin = BinFromTilePriority(tile->combined_priority()); |
| |
| DidTileTreeBinChange(tile, |
| bin_map[BinFromTilePriority(tile->priority(ACTIVE_TREE))], |
| ACTIVE_TREE); |
| DidTileTreeBinChange(tile, |
| bin_map[BinFromTilePriority(tile->priority(PENDING_TREE))], |
| PENDING_TREE); |
| |
| for (int i = 0; i < NUM_BIN_PRIORITIES; ++i) |
| mts.bin[i] = bin_map[mts.bin[i]]; |
| } |
| } |
| |
| void TileManager::SortTiles() { |
| TRACE_EVENT0("cc", "TileManager::SortTiles"); |
| |
| // Sort by bin, resolution and time until needed. |
| std::sort(tiles_.begin(), tiles_.end(), BinComparator()); |
| } |
| |
| void TileManager::ManageTiles() { |
| TRACE_EVENT0("cc", "TileManager::ManageTiles"); |
| |
| manage_tiles_pending_ = false; |
| ++manage_tiles_call_count_; |
| |
| AssignBinsToTiles(); |
| SortTiles(); |
| AssignGpuMemoryToTiles(); |
| |
| TRACE_EVENT_INSTANT1( |
| "cc", "DidManage", TRACE_EVENT_SCOPE_THREAD, |
| "state", TracedValue::FromValue(BasicStateAsValue().release())); |
| |
| // Finally, kick the rasterizer. |
| DispatchMoreTasks(); |
| } |
| |
| void TileManager::CheckForCompletedTileUploads() { |
| while (!tiles_with_pending_upload_.empty()) { |
| Tile* tile = tiles_with_pending_upload_.front(); |
| DCHECK(tile->tile_version().resource_); |
| |
| // Set pixel tasks complete in the order they are posted. |
| if (!resource_pool_->resource_provider()->DidSetPixelsComplete( |
| tile->tile_version().resource_->id())) { |
| break; |
| } |
| |
| // It's now safe to release the pixel buffer. |
| resource_pool_->resource_provider()->ReleasePixelBuffer( |
| tile->tile_version().resource_->id()); |
| |
| bytes_pending_upload_ -= tile->bytes_consumed_if_allocated(); |
| bool was_forced = tile->tile_version().forced_upload_; |
| // Reset forced_upload_ since we now got the upload completed notification. |
| tile->tile_version().forced_upload_ = false; |
| tile->tile_version().memory_state_ = USING_RELEASABLE_MEMORY; |
| if (!was_forced) |
| DidFinishTileInitialization(tile); |
| |
| tiles_with_pending_upload_.pop(); |
| } |
| |
| DispatchMoreTasks(); |
| } |
| |
| void TileManager::AbortPendingTileUploads() { |
| while (!tiles_with_pending_upload_.empty()) { |
| Tile* tile = tiles_with_pending_upload_.front(); |
| DCHECK(tile->tile_version().resource_); |
| |
| resource_pool_->resource_provider()->AbortSetPixels( |
| tile->tile_version().resource_->id()); |
| resource_pool_->resource_provider()->ReleasePixelBuffer( |
| tile->tile_version().resource_->id()); |
| tile->tile_version().memory_state_ = USING_RELEASABLE_MEMORY; |
| |
| FreeResourcesForTile(tile); |
| |
| bytes_pending_upload_ -= tile->bytes_consumed_if_allocated(); |
| tiles_with_pending_upload_.pop(); |
| } |
| } |
| |
| void TileManager::ForceTileUploadToComplete(Tile* tile) { |
| DCHECK(tile); |
| if (tile->tile_version().resource_ && |
| tile->tile_version().memory_state_ == USING_UNRELEASABLE_MEMORY && |
| !tile->tile_version().forced_upload_) { |
| resource_pool_->resource_provider()-> |
| ForceSetPixelsToComplete(tile->tile_version().resource_->id()); |
| |
| // We have to set the memory state to be unreleasable, to ensure |
| // that the tile will not be freed until we get the upload finished |
| // notification. However, setting |forced_upload_| to true makes |
| // this tile ready to draw. |
| tile->tile_version().memory_state_ = USING_UNRELEASABLE_MEMORY; |
| tile->tile_version().forced_upload_ = true; |
| DidFinishTileInitialization(tile); |
| DCHECK(tile->tile_version().IsReadyToDraw()); |
| } |
| |
| if (did_initialize_visible_tile_) { |
| did_initialize_visible_tile_ = false; |
| client_->DidInitializeVisibleTile(); |
| } |
| } |
| |
| void TileManager::GetMemoryStats( |
| size_t* memory_required_bytes, |
| size_t* memory_nice_to_have_bytes, |
| size_t* memory_used_bytes) const { |
| *memory_required_bytes = 0; |
| *memory_nice_to_have_bytes = 0; |
| *memory_used_bytes = 0; |
| for (TileVector::const_iterator it = tiles_.begin(); |
| it != tiles_.end(); |
| ++it) { |
| const Tile* tile = *it; |
| if (!tile->tile_version().requires_resource()) |
| continue; |
| |
| const ManagedTileState& mts = tile->managed_state(); |
| size_t tile_bytes = tile->bytes_consumed_if_allocated(); |
| if (mts.gpu_memmgr_stats_bin == NOW_BIN) |
| *memory_required_bytes += tile_bytes; |
| if (mts.gpu_memmgr_stats_bin != NEVER_BIN) |
| *memory_nice_to_have_bytes += tile_bytes; |
| if (tile->tile_version().memory_state_ != NOT_ALLOWED_TO_USE_MEMORY) |
| *memory_used_bytes += tile_bytes; |
| } |
| } |
| |
| scoped_ptr<base::Value> TileManager::BasicStateAsValue() const { |
| scoped_ptr<base::DictionaryValue> state(new base::DictionaryValue()); |
| state->SetInteger("tile_count", tiles_.size()); |
| state->Set("global_state", global_state_.AsValue().release()); |
| state->Set("memory_requirements", GetMemoryRequirementsAsValue().release()); |
| return state.PassAs<base::Value>(); |
| } |
| |
| scoped_ptr<base::Value> TileManager::AllTilesAsValue() const { |
| scoped_ptr<base::ListValue> state(new base::ListValue()); |
| for (TileVector::const_iterator it = tiles_.begin(); |
| it != tiles_.end(); |
| it++) { |
| state->Append((*it)->AsValue().release()); |
| } |
| return state.PassAs<base::Value>(); |
| } |
| |
| scoped_ptr<base::Value> TileManager::GetMemoryRequirementsAsValue() const { |
| scoped_ptr<base::DictionaryValue> requirements( |
| new base::DictionaryValue()); |
| |
| size_t memory_required_bytes; |
| size_t memory_nice_to_have_bytes; |
| size_t memory_used_bytes; |
| GetMemoryStats(&memory_required_bytes, |
| &memory_nice_to_have_bytes, |
| &memory_used_bytes); |
| requirements->SetInteger("memory_required_bytes", memory_required_bytes); |
| requirements->SetInteger("memory_nice_to_have_bytes", |
| memory_nice_to_have_bytes); |
| requirements->SetInteger("memory_used_bytes", memory_used_bytes); |
| return requirements.PassAs<base::Value>(); |
| } |
| |
| void TileManager::AddRequiredTileForActivation(Tile* tile) { |
| DCHECK(std::find(tiles_that_need_to_be_initialized_for_activation_.begin(), |
| tiles_that_need_to_be_initialized_for_activation_.end(), |
| tile) == |
| tiles_that_need_to_be_initialized_for_activation_.end()); |
| tiles_that_need_to_be_initialized_for_activation_.insert(tile); |
| } |
| |
| void TileManager::DidFinishDispatchingWorkerPoolCompletionCallbacks() { |
| // If a flush is needed, do it now before starting to dispatch more tasks. |
| if (has_performed_uploads_since_last_flush_) { |
| resource_pool_->resource_provider()->ShallowFlushIfSupported(); |
| has_performed_uploads_since_last_flush_ = false; |
| } |
| |
| DispatchMoreTasks(); |
| } |
| |
| void TileManager::AssignGpuMemoryToTiles() { |
| TRACE_EVENT0("cc", "TileManager::AssignGpuMemoryToTiles"); |
| size_t unreleasable_bytes = 0; |
| |
| // Now give memory out to the tiles until we're out, and build |
| // the needs-to-be-rasterized queue. |
| tiles_that_need_to_be_rasterized_.clear(); |
| tiles_that_need_to_be_initialized_for_activation_.clear(); |
| |
| // By clearing the tiles_that_need_to_be_rasterized_ vector list |
| // above we move all tiles currently waiting for raster to idle state. |
| // Some memory cannot be released. We figure out how much in this |
| // loop as well. |
| for (TileVector::const_iterator it = tiles_.begin(); |
| it != tiles_.end(); |
| ++it) { |
| const Tile* tile = *it; |
| if (tile->tile_version().memory_state_ == USING_UNRELEASABLE_MEMORY) |
| unreleasable_bytes += tile->bytes_consumed_if_allocated(); |
| } |
| |
| // Global state's memory limit can decrease, causing |
| // it to be less than unreleasable_bytes |
| size_t bytes_allocatable = |
| global_state_.memory_limit_in_bytes > unreleasable_bytes ? |
| global_state_.memory_limit_in_bytes - unreleasable_bytes : |
| 0; |
| size_t bytes_that_exceeded_memory_budget_in_now_bin = 0; |
| size_t bytes_left = bytes_allocatable; |
| size_t bytes_oom_in_now_bin_on_pending_tree = 0; |
| TileVector tiles_requiring_memory_but_oomed; |
| for (TileVector::iterator it = tiles_.begin(); |
| it != tiles_.end(); |
| ++it) { |
| Tile* tile = *it; |
| ManagedTileState& mts = tile->managed_state(); |
| ManagedTileState::TileVersion& tile_version = tile->tile_version(); |
| |
| // If this tile doesn't need a resource, then nothing to do. |
| if (!tile_version.requires_resource()) |
| continue; |
| |
| // If the memory is unreleasable, then we do not need to do anything. |
| if (tile_version.memory_state_ == USING_UNRELEASABLE_MEMORY) { |
| if (tile->required_for_activation()) { |
| AddRequiredTileForActivation(tile); |
| // If after rasterizing, this tile has become required or the client has |
| // changed its mind about forcing tiles, do that now. |
| if (!tile->tile_version().forced_upload_ && |
| client_->ShouldForceTileUploadsRequiredForActivationToComplete()) { |
| ForceTileUploadToComplete(tile); |
| } |
| } |
| continue; |
| } |
| |
| size_t tile_bytes = tile->bytes_consumed_if_allocated(); |
| // If the tile is not needed, free it up. |
| if (mts.is_in_never_bin_on_both_trees()) { |
| FreeResourcesForTile(tile); |
| tile_version.memory_state_ = NOT_ALLOWED_TO_USE_MEMORY; |
| continue; |
| } |
| // Tile is OOM. |
| if (tile_bytes > bytes_left) { |
| FreeResourcesForTile(tile); |
| tile->tile_version().set_rasterize_on_demand(); |
| if (mts.tree_bin[PENDING_TREE] == NOW_BIN) { |
| tiles_requiring_memory_but_oomed.push_back(tile); |
| bytes_oom_in_now_bin_on_pending_tree += tile_bytes; |
| } |
| continue; |
| } |
| tile_version.set_use_resource(); |
| bytes_left -= tile_bytes; |
| if (!tile_version.resource_ && |
| tile_version.memory_state_ == CAN_USE_MEMORY) { |
| tiles_that_need_to_be_rasterized_.push_back(tile); |
| } |
| if (!tile_version.resource_ && tile->required_for_activation()) |
| AddRequiredTileForActivation(tile); |
| } |
| |
| // In OOM situation, we iterate tiles_, remove the memory for active tree |
| // and not the now bin. And give them to bytes_oom_in_now_bin_on_pending_tree |
| if (!tiles_requiring_memory_but_oomed.empty()) { |
| size_t bytes_freed = 0; |
| for (TileVector::iterator it = tiles_.begin(); it != tiles_.end(); ++it) { |
| Tile* tile = *it; |
| ManagedTileState& mts = tile->managed_state(); |
| ManagedTileState::TileVersion& tile_version = tile->tile_version(); |
| if ((tile_version.memory_state_ == CAN_USE_MEMORY || |
| tile_version.memory_state_ == USING_RELEASABLE_MEMORY) && |
| mts.tree_bin[PENDING_TREE] == NEVER_BIN && |
| mts.tree_bin[ACTIVE_TREE] != NOW_BIN) { |
| DCHECK(!tile->required_for_activation()); |
| FreeResourcesForTile(tile); |
| tile_version.set_rasterize_on_demand(); |
| bytes_freed += tile->bytes_consumed_if_allocated(); |
| TileVector::iterator it = std::find( |
| tiles_that_need_to_be_rasterized_.begin(), |
| tiles_that_need_to_be_rasterized_.end(), |
| tile); |
| if (it != tiles_that_need_to_be_rasterized_.end()) |
| tiles_that_need_to_be_rasterized_.erase(it); |
| if (bytes_oom_in_now_bin_on_pending_tree <= bytes_freed) |
| break; |
| } |
| } |
| |
| for (TileVector::iterator it = tiles_requiring_memory_but_oomed.begin(); |
| it != tiles_requiring_memory_but_oomed.end() && bytes_freed > 0; |
| ++it) { |
| Tile* tile = *it; |
| size_t bytes_needed = tile->bytes_consumed_if_allocated(); |
| if (bytes_needed > bytes_freed) |
| continue; |
| tile->tile_version().set_use_resource(); |
| bytes_freed -= bytes_needed; |
| tiles_that_need_to_be_rasterized_.push_back(tile); |
| if (tile->required_for_activation()) |
| AddRequiredTileForActivation(tile); |
| } |
| } |
| |
| ever_exceeded_memory_budget_ |= |
| bytes_that_exceeded_memory_budget_in_now_bin > 0; |
| if (ever_exceeded_memory_budget_) { |
| TRACE_COUNTER_ID2("cc", "over_memory_budget", this, |
| "budget", global_state_.memory_limit_in_bytes, |
| "over", bytes_that_exceeded_memory_budget_in_now_bin); |
| } |
| memory_stats_from_last_assign_.total_budget_in_bytes = |
| global_state_.memory_limit_in_bytes; |
| memory_stats_from_last_assign_.bytes_allocated = |
| bytes_allocatable - bytes_left; |
| memory_stats_from_last_assign_.bytes_unreleasable = unreleasable_bytes; |
| memory_stats_from_last_assign_.bytes_over = |
| bytes_that_exceeded_memory_budget_in_now_bin; |
| |
| // Reverse two tiles_that_need_* vectors such that pop_back gets |
| // the highest priority tile. |
| std::reverse( |
| tiles_that_need_to_be_rasterized_.begin(), |
| tiles_that_need_to_be_rasterized_.end()); |
| } |
| |
| void TileManager::FreeResourcesForTile(Tile* tile) { |
| DCHECK(tile->tile_version().memory_state_ != USING_UNRELEASABLE_MEMORY); |
| if (tile->tile_version().resource_) { |
| resource_pool_->ReleaseResource( |
| tile->tile_version().resource_.Pass()); |
| } |
| tile->tile_version().memory_state_ = NOT_ALLOWED_TO_USE_MEMORY; |
| } |
| |
| bool TileManager::CanDispatchRasterTask(Tile* tile) const { |
| if (pending_tasks_ >= max_pending_tasks_) |
| return false; |
| size_t new_bytes_pending = bytes_pending_upload_; |
| new_bytes_pending += tile->bytes_consumed_if_allocated(); |
| return new_bytes_pending <= kMaxPendingUploadBytes && |
| tiles_with_pending_upload_.size() < kMaxPendingUploads; |
| } |
| |
| void TileManager::DispatchMoreTasks() { |
| TileVector tiles_with_image_decoding_tasks; |
| |
| // Process all tiles in the need_to_be_rasterized queue: |
| // 1. Dispatch image decode tasks. |
| // 2. If the image decode isn't done, save the tile for later processing. |
| // 3. Attempt to dispatch a raster task, or break out of the loop. |
| while (!tiles_that_need_to_be_rasterized_.empty()) { |
| Tile* tile = tiles_that_need_to_be_rasterized_.back(); |
| |
| DCHECK(tile->tile_version().requires_resource()); |
| |
| if (DispatchImageDecodeTasksForTile(tile)) { |
| tiles_with_image_decoding_tasks.push_back(tile); |
| } else if (!CanDispatchRasterTask(tile)) { |
| break; |
| } else { |
| DispatchOneRasterTask(tile); |
| } |
| tiles_that_need_to_be_rasterized_.pop_back(); |
| } |
| |
| // Put the saved tiles back into the queue. The order is reversed |
| // to preserve original ordering. |
| tiles_that_need_to_be_rasterized_.insert( |
| tiles_that_need_to_be_rasterized_.end(), |
| tiles_with_image_decoding_tasks.rbegin(), |
| tiles_with_image_decoding_tasks.rend()); |
| |
| if (did_initialize_visible_tile_) { |
| did_initialize_visible_tile_ = false; |
| client_->DidInitializeVisibleTile(); |
| } |
| } |
| |
| bool TileManager::DispatchImageDecodeTasksForTile(Tile* tile) { |
| TRACE_EVENT0("cc", "TileManager::DispatchImageDecodeTasksForTile"); |
| ManagedTileState& mts = tile->managed_state(); |
| bool pending_decode_tasks = false; |
| |
| for (PicturePileImpl::PixelRefIterator iter(tile->content_rect(), |
| tile->contents_scale(), |
| tile->picture_pile()); |
| iter; ++iter) { |
| skia::LazyPixelRef* pixel_ref = *iter; |
| uint32_t id = pixel_ref->getGenerationID(); |
| |
| // Check if image has already been decoded. |
| if (mts.decoded_pixel_refs.find(id) != mts.decoded_pixel_refs.end()) |
| continue; |
| |
| // Check if decode task is already pending. |
| if (pending_decode_tasks_.find(id) != pending_decode_tasks_.end()) { |
| pending_decode_tasks = true; |
| continue; |
| } |
| |
| // TODO(qinmin): passing correct image size to PrepareToDecode(). |
| if (pixel_ref->PrepareToDecode(skia::LazyPixelRef::PrepareParams())) { |
| rendering_stats_instrumentation_->IncrementDeferredImageCacheHitCount(); |
| mts.decoded_pixel_refs.insert(id); |
| continue; |
| } |
| |
| if (pending_tasks_ >= max_pending_tasks_) |
| break; |
| |
| DispatchOneImageDecodeTask(tile, pixel_ref); |
| pending_decode_tasks = true; |
| } |
| |
| return pending_decode_tasks; |
| } |
| |
| void TileManager::DispatchOneImageDecodeTask( |
| scoped_refptr<Tile> tile, skia::LazyPixelRef* pixel_ref) { |
| TRACE_EVENT0("cc", "TileManager::DispatchOneImageDecodeTask"); |
| uint32_t pixel_ref_id = pixel_ref->getGenerationID(); |
| DCHECK(pending_decode_tasks_.end() == |
| pending_decode_tasks_.find(pixel_ref_id)); |
| pending_decode_tasks_.insert(pixel_ref_id); |
| |
| raster_worker_pool_->PostTaskAndReply( |
| base::Bind(&TileManager::RunImageDecodeTask, |
| pixel_ref, |
| tile->layer_id(), |
| rendering_stats_instrumentation_), |
| base::Bind(&TileManager::OnImageDecodeTaskCompleted, |
| base::Unretained(this), |
| tile, |
| pixel_ref_id)); |
| pending_tasks_++; |
| } |
| |
| void TileManager::OnImageDecodeTaskCompleted( |
| scoped_refptr<Tile> tile, uint32_t pixel_ref_id) { |
| TRACE_EVENT0("cc", "TileManager::OnImageDecodeTaskCompleted"); |
| ManagedTileState& mts = tile->managed_state(); |
| mts.decoded_pixel_refs.insert(pixel_ref_id); |
| pending_decode_tasks_.erase(pixel_ref_id); |
| pending_tasks_--; |
| } |
| |
| scoped_ptr<ResourcePool::Resource> TileManager::PrepareTileForRaster( |
| Tile* tile) { |
| scoped_ptr<ResourcePool::Resource> resource = resource_pool_->AcquireResource( |
| tile->tile_size_.size(), |
| tile->tile_version().resource_format_); |
| resource_pool_->resource_provider()->AcquirePixelBuffer(resource->id()); |
| |
| tile->tile_version().memory_state_ = USING_UNRELEASABLE_MEMORY; |
| |
| return resource.Pass(); |
| } |
| |
| void TileManager::DispatchOneRasterTask(scoped_refptr<Tile> tile) { |
| TRACE_EVENT0("cc", "TileManager::DispatchOneRasterTask"); |
| scoped_ptr<ResourcePool::Resource> resource = PrepareTileForRaster(tile); |
| ResourceProvider::ResourceId resource_id = resource->id(); |
| PicturePileImpl::Analysis* analysis = new PicturePileImpl::Analysis; |
| |
| // MapPixelBuffer() returns NULL if context was lost at the time |
| // AcquirePixelBuffer() was called. For simplicity we still post |
| // a raster task that is essentially a noop in these situations. |
| uint8* buffer = resource_pool_->resource_provider()->MapPixelBuffer( |
| resource_id); |
| |
| // skia requires that our buffer be 4-byte aligned |
| CHECK(!(reinterpret_cast<intptr_t>(buffer) & 3)); |
| |
| raster_worker_pool_->PostRasterTaskAndReply( |
| tile->picture_pile(), |
| base::Bind(&TileManager::RunAnalyzeAndRasterTask, |
| base::Bind(&TileManager::RunAnalyzeTask, |
| analysis, |
| tile->content_rect(), |
| tile->contents_scale(), |
| use_color_estimator_, |
| GetRasterTaskMetadata(*tile), |
| rendering_stats_instrumentation_), |
| base::Bind(&TileManager::RunRasterTask, |
| buffer, |
| analysis, |
| tile->content_rect(), |
| tile->contents_scale(), |
| GetRasterTaskMetadata(*tile), |
| rendering_stats_instrumentation_)), |
| base::Bind(&TileManager::OnRasterTaskCompleted, |
| base::Unretained(this), |
| tile, |
| base::Passed(&resource), |
| base::Owned(analysis), |
| manage_tiles_call_count_)); |
| pending_tasks_++; |
| } |
| |
| TileManager::RasterTaskMetadata TileManager::GetRasterTaskMetadata( |
| const Tile& tile) const { |
| RasterTaskMetadata metadata; |
| const ManagedTileState& mts = tile.managed_state(); |
| metadata.is_tile_in_pending_tree_now_bin = |
| mts.tree_bin[PENDING_TREE] == NOW_BIN; |
| metadata.tile_resolution = mts.resolution; |
| metadata.layer_id = tile.layer_id(); |
| metadata.tile_id = &tile; |
| metadata.source_frame_number = tile.source_frame_number(); |
| return metadata; |
| } |
| |
| void TileManager::OnRasterTaskCompleted( |
| scoped_refptr<Tile> tile, |
| scoped_ptr<ResourcePool::Resource> resource, |
| PicturePileImpl::Analysis* analysis, |
| int manage_tiles_call_count_when_dispatched) { |
| TRACE_EVENT0("cc", "TileManager::OnRasterTaskCompleted"); |
| |
| pending_tasks_--; |
| |
| // Release raster resources. |
| resource_pool_->resource_provider()->UnmapPixelBuffer(resource->id()); |
| |
| tile->tile_version().memory_state_ = USING_RELEASABLE_MEMORY; |
| |
| ManagedTileState& managed_tile_state = tile->managed_state(); |
| managed_tile_state.picture_pile_analysis = *analysis; |
| managed_tile_state.picture_pile_analyzed = true; |
| |
| if (analysis->is_solid_color) { |
| tile->tile_version().set_solid_color(analysis->solid_color); |
| resource_pool_->resource_provider()->ReleasePixelBuffer(resource->id()); |
| resource_pool_->ReleaseResource(resource.Pass()); |
| DidFinishTileInitialization(tile); |
| return; |
| } |
| |
| // Tile can be freed after the completion of the raster task. Call |
| // AssignGpuMemoryToTiles() to re-assign gpu memory to highest priority |
| // tiles if ManageTiles() was called since task was dispatched. The result |
| // of this could be that this tile is no longer allowed to use gpu |
| // memory and in that case we need to abort initialization and free all |
| // associated resources before calling DispatchMoreTasks(). |
| if (manage_tiles_call_count_when_dispatched != manage_tiles_call_count_) |
| AssignGpuMemoryToTiles(); |
| |
| // Finish resource initialization we're still using memory. |
| if (tile->tile_version().memory_state_ == USING_RELEASABLE_MEMORY) { |
| // Tile resources can't be freed until upload has completed. |
| tile->tile_version().memory_state_ = USING_UNRELEASABLE_MEMORY; |
| |
| resource_pool_->resource_provider()->BeginSetPixels(resource->id()); |
| has_performed_uploads_since_last_flush_ = true; |
| |
| tile->tile_version().resource_ = resource.Pass(); |
| |
| bytes_pending_upload_ += tile->bytes_consumed_if_allocated(); |
| tiles_with_pending_upload_.push(tile); |
| |
| if (tile->required_for_activation() && |
| client_->ShouldForceTileUploadsRequiredForActivationToComplete()) |
| ForceTileUploadToComplete(tile); |
| } else { |
| resource_pool_->resource_provider()->ReleasePixelBuffer(resource->id()); |
| resource_pool_->ReleaseResource(resource.Pass()); |
| } |
| } |
| |
| void TileManager::DidFinishTileInitialization(Tile* tile) { |
| if (tile->priority(ACTIVE_TREE).distance_to_visible_in_pixels == 0) |
| did_initialize_visible_tile_ = true; |
| if (tile->required_for_activation()) { |
| // It's possible that a tile required for activation is not in this list |
| // if it was marked as being required after being dispatched for |
| // rasterization but before AssignGPUMemory was called again. |
| tiles_that_need_to_be_initialized_for_activation_.erase(tile); |
| } |
| } |
| |
| void TileManager::DidTileTreeBinChange(Tile* tile, |
| TileManagerBin new_tree_bin, |
| WhichTree tree) { |
| ManagedTileState& mts = tile->managed_state(); |
| mts.tree_bin[tree] = new_tree_bin; |
| } |
| |
| // static |
| void TileManager::RunAnalyzeAndRasterTask( |
| const RasterWorkerPool::RasterCallback& analyze_task, |
| const RasterWorkerPool::RasterCallback& raster_task, |
| PicturePileImpl* picture_pile) { |
| analyze_task.Run(picture_pile); |
| raster_task.Run(picture_pile); |
| } |
| |
| // static |
| void TileManager::RunAnalyzeTask( |
| PicturePileImpl::Analysis* analysis, |
| gfx::Rect rect, |
| float contents_scale, |
| bool use_color_estimator, |
| const RasterTaskMetadata& metadata, |
| RenderingStatsInstrumentation* stats_instrumentation, |
| PicturePileImpl* picture_pile) { |
| TRACE_EVENT0("cc", "TileManager::RunAnalyzeTask"); |
| |
| DCHECK(picture_pile); |
| DCHECK(analysis); |
| DCHECK(stats_instrumentation); |
| |
| base::TimeTicks start_time = stats_instrumentation->StartRecording(); |
| picture_pile->AnalyzeInRect(rect, contents_scale, analysis); |
| base::TimeDelta duration = stats_instrumentation->EndRecording(start_time); |
| |
| // Record the solid color prediction. |
| UMA_HISTOGRAM_BOOLEAN("Renderer4.SolidColorTilesAnalyzed", |
| analysis->is_solid_color); |
| stats_instrumentation->AddTileAnalysisResult(duration, |
| analysis->is_solid_color); |
| |
| // Clear the flag if we're not using the estimator. |
| analysis->is_solid_color &= use_color_estimator; |
| } |
| |
| scoped_ptr<base::Value> TileManager::RasterTaskMetadata::AsValue() const { |
| scoped_ptr<base::DictionaryValue> res(new base::DictionaryValue()); |
| res->Set("tile_id", TracedValue::CreateIDRef(tile_id).release()); |
| res->SetBoolean("is_tile_in_pending_tree_now_bin", |
| is_tile_in_pending_tree_now_bin); |
| res->Set("resolution", TileResolutionAsValue(tile_resolution).release()); |
| res->SetInteger("source_frame_number", source_frame_number); |
| return res.PassAs<base::Value>(); |
| } |
| |
| // static |
| void TileManager::RunRasterTask( |
| uint8* buffer, |
| PicturePileImpl::Analysis* analysis, |
| gfx::Rect rect, |
| float contents_scale, |
| const RasterTaskMetadata& metadata, |
| RenderingStatsInstrumentation* stats_instrumentation, |
| PicturePileImpl* picture_pile) { |
| TRACE_EVENT1( |
| "cc", "TileManager::RunRasterTask", |
| "metadata", TracedValue::FromValue(metadata.AsValue().release())); |
| devtools_instrumentation::ScopedLayerTask raster_task( |
| devtools_instrumentation::kRasterTask, metadata.layer_id); |
| |
| DCHECK(picture_pile); |
| DCHECK(analysis); |
| |
| // |buffer| can be NULL in lost context situations. |
| if (!buffer) |
| return; |
| |
| if (analysis->is_solid_color) |
| return; |
| |
| SkBitmap bitmap; |
| bitmap.setConfig(SkBitmap::kARGB_8888_Config, rect.width(), rect.height()); |
| bitmap.setPixels(buffer); |
| SkDevice device(bitmap); |
| SkCanvas canvas(&device); |
| |
| if (stats_instrumentation->record_rendering_stats()) { |
| PicturePileImpl::RasterStats raster_stats; |
| picture_pile->RasterToBitmap(&canvas, rect, contents_scale, &raster_stats); |
| stats_instrumentation->AddRaster( |
| raster_stats.total_rasterize_time, |
| raster_stats.best_rasterize_time, |
| raster_stats.total_pixels_rasterized, |
| metadata.is_tile_in_pending_tree_now_bin); |
| |
| HISTOGRAM_CUSTOM_COUNTS( |
| "Renderer4.PictureRasterTimeUS", |
| raster_stats.total_rasterize_time.InMicroseconds(), |
| 0, |
| 100000, |
| 100); |
| } else { |
| picture_pile->RasterToBitmap(&canvas, rect, contents_scale, NULL); |
| } |
| } |
| |
| // static |
| void TileManager::RunImageDecodeTask( |
| skia::LazyPixelRef* pixel_ref, |
| int layer_id, |
| RenderingStatsInstrumentation* stats_instrumentation) { |
| TRACE_EVENT0("cc", "TileManager::RunImageDecodeTask"); |
| devtools_instrumentation::ScopedLayerTask image_decode_task( |
| devtools_instrumentation::kImageDecodeTask, layer_id); |
| base::TimeTicks start_time = stats_instrumentation->StartRecording(); |
| pixel_ref->Decode(); |
| base::TimeDelta duration = stats_instrumentation->EndRecording(start_time); |
| stats_instrumentation->AddDeferredImageDecode(duration); |
| } |
| |
| } // namespace cc |