// 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 #include #include #include "base/bind.h" #include "base/debug/trace_event_argument.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/frame_viewer_instrumentation.h" #include "cc/debug/traced_value.h" #include "cc/layers/picture_layer_impl.h" #include "cc/resources/raster_buffer.h" #include "cc/resources/rasterizer.h" #include "cc/resources/tile.h" #include "cc/resources/tile_task_runner.h" #include "ui/gfx/geometry/rect_conversions.h" namespace cc { namespace { // Flag to indicate whether we should try and detect that // a tile is of solid color. const bool kUseColorEstimator = true; class RasterTaskImpl : public RasterTask { public: RasterTaskImpl( const Resource* resource, RasterSource* raster_source, const gfx::Rect& content_rect, float contents_scale, TileResolution tile_resolution, int layer_id, const void* tile_id, int source_frame_number, bool analyze_picture, const base::Callback& reply, ImageDecodeTask::Vector* dependencies) : RasterTask(resource, dependencies), raster_source_(raster_source), content_rect_(content_rect), contents_scale_(contents_scale), tile_resolution_(tile_resolution), layer_id_(layer_id), tile_id_(tile_id), source_frame_number_(source_frame_number), analyze_picture_(analyze_picture), reply_(reply) {} // Overridden from Task: void RunOnWorkerThread() override { TRACE_EVENT0("cc", "RasterizerTaskImpl::RunOnWorkerThread"); DCHECK(raster_source_.get()); DCHECK(raster_buffer_); if (analyze_picture_) { Analyze(raster_source_.get()); if (analysis_.is_solid_color) return; } Raster(raster_source_.get()); } // Overridden from TileTask: void ScheduleOnOriginThread(TileTaskClient* client) override { DCHECK(!raster_buffer_); raster_buffer_ = client->AcquireBufferForRaster(resource()); } void CompleteOnOriginThread(TileTaskClient* client) override { client->ReleaseBufferForRaster(raster_buffer_.Pass()); } void RunReplyOnOriginThread() override { DCHECK(!raster_buffer_); reply_.Run(analysis_, !HasFinishedRunning()); } protected: ~RasterTaskImpl() override { DCHECK(!raster_buffer_); } private: void Analyze(const RasterSource* raster_source) { frame_viewer_instrumentation::ScopedAnalyzeTask analyze_task( tile_id_, tile_resolution_, source_frame_number_, layer_id_); DCHECK(raster_source); raster_source->PerformSolidColorAnalysis(content_rect_, contents_scale_, &analysis_); // Record the solid color prediction. UMA_HISTOGRAM_BOOLEAN("Renderer4.SolidColorTilesAnalyzed", analysis_.is_solid_color); // Clear the flag if we're not using the estimator. analysis_.is_solid_color &= kUseColorEstimator; } void Raster(const RasterSource* raster_source) { frame_viewer_instrumentation::ScopedRasterTask raster_task( tile_id_, tile_resolution_, source_frame_number_, layer_id_); DCHECK(raster_source); raster_buffer_->Playback(raster_source_.get(), content_rect_, contents_scale_); } RasterSource::SolidColorAnalysis analysis_; scoped_refptr raster_source_; gfx::Rect content_rect_; float contents_scale_; TileResolution tile_resolution_; int layer_id_; const void* tile_id_; int source_frame_number_; bool analyze_picture_; const base::Callback reply_; scoped_ptr raster_buffer_; DISALLOW_COPY_AND_ASSIGN(RasterTaskImpl); }; class ImageDecodeTaskImpl : public ImageDecodeTask { public: ImageDecodeTaskImpl(SkPixelRef* pixel_ref, int layer_id, const base::Callback& reply) : pixel_ref_(skia::SharePtr(pixel_ref)), layer_id_(layer_id), reply_(reply) {} // Overridden from Task: void RunOnWorkerThread() override { TRACE_EVENT0("cc", "ImageDecodeTaskImpl::RunOnWorkerThread"); devtools_instrumentation::ScopedImageDecodeTask image_decode_task( pixel_ref_.get()); // This will cause the image referred to by pixel ref to be decoded. pixel_ref_->lockPixels(); pixel_ref_->unlockPixels(); } // Overridden from TileTask: void ScheduleOnOriginThread(TileTaskClient* client) override {} void CompleteOnOriginThread(TileTaskClient* client) override {} void RunReplyOnOriginThread() override { reply_.Run(!HasFinishedRunning()); } protected: ~ImageDecodeTaskImpl() override {} private: skia::RefPtr pixel_ref_; int layer_id_; const base::Callback reply_; DISALLOW_COPY_AND_ASSIGN(ImageDecodeTaskImpl); }; const char* TaskSetName(TaskSet task_set) { switch (task_set) { case TileManager::ALL: return "ALL"; case TileManager::REQUIRED_FOR_ACTIVATION: return "REQUIRED_FOR_ACTIVATION"; case TileManager::REQUIRED_FOR_DRAW: return "REQUIRED_FOR_DRAW"; } NOTREACHED(); return "Invalid TaskSet"; } } // namespace RasterTaskCompletionStats::RasterTaskCompletionStats() : completed_count(0u), canceled_count(0u) {} scoped_refptr RasterTaskCompletionStatsAsValue(const RasterTaskCompletionStats& stats) { scoped_refptr state = new base::debug::TracedValue(); state->SetInteger("completed_count", stats.completed_count); state->SetInteger("canceled_count", stats.canceled_count); return state; } // static scoped_ptr TileManager::Create( TileManagerClient* client, base::SequencedTaskRunner* task_runner, ResourcePool* resource_pool, TileTaskRunner* tile_task_runner, Rasterizer* rasterizer, size_t scheduled_raster_task_limit) { return make_scoped_ptr(new TileManager(client, task_runner, resource_pool, tile_task_runner, rasterizer, scheduled_raster_task_limit)); } TileManager::TileManager( TileManagerClient* client, const scoped_refptr& task_runner, ResourcePool* resource_pool, TileTaskRunner* tile_task_runner, Rasterizer* rasterizer, size_t scheduled_raster_task_limit) : client_(client), task_runner_(task_runner), resource_pool_(resource_pool), tile_task_runner_(tile_task_runner), rasterizer_(rasterizer), scheduled_raster_task_limit_(scheduled_raster_task_limit), all_tiles_that_need_to_be_rasterized_are_scheduled_(true), did_check_for_completed_tasks_since_last_schedule_tasks_(true), did_oom_on_last_assign_(false), ready_to_activate_notifier_( task_runner_.get(), base::Bind(&TileManager::NotifyReadyToActivate, base::Unretained(this))), ready_to_draw_notifier_( task_runner_.get(), base::Bind(&TileManager::NotifyReadyToDraw, base::Unretained(this))), ready_to_activate_check_notifier_( task_runner_.get(), base::Bind(&TileManager::CheckIfReadyToActivate, base::Unretained(this))), ready_to_draw_check_notifier_( task_runner_.get(), base::Bind(&TileManager::CheckIfReadyToDraw, base::Unretained(this))), more_tiles_need_prepare_check_notifier_( task_runner_.get(), base::Bind(&TileManager::CheckIfMoreTilesNeedToBePrepared, base::Unretained(this))), eviction_priority_queue_is_up_to_date_(false), did_notify_ready_to_activate_(false), did_notify_ready_to_draw_(false) { tile_task_runner_->SetClient(this); } TileManager::~TileManager() { // Reset global state and manage. This should cause // our memory usage to drop to zero. global_state_ = GlobalStateThatImpactsTilePriority(); TileTaskQueue empty; tile_task_runner_->ScheduleTasks(&empty); orphan_raster_tasks_.clear(); // This should finish all pending tasks and release any uninitialized // resources. tile_task_runner_->Shutdown(); tile_task_runner_->CheckForCompletedTasks(); FreeResourcesForReleasedTiles(); CleanUpReleasedTiles(); } void TileManager::Release(Tile* tile) { released_tiles_.push_back(tile); } TaskSetCollection TileManager::TasksThatShouldBeForcedToComplete() const { TaskSetCollection tasks_that_should_be_forced_to_complete; if (global_state_.tree_priority != SMOOTHNESS_TAKES_PRIORITY) tasks_that_should_be_forced_to_complete[REQUIRED_FOR_ACTIVATION] = true; return tasks_that_should_be_forced_to_complete; } void TileManager::FreeResourcesForReleasedTiles() { for (std::vector::iterator it = released_tiles_.begin(); it != released_tiles_.end(); ++it) { Tile* tile = *it; FreeResourcesForTile(tile); } } void TileManager::CleanUpReleasedTiles() { std::vector::iterator it = released_tiles_.begin(); while (it != released_tiles_.end()) { Tile* tile = *it; if (tile->HasRasterTask()) { ++it; continue; } DCHECK(!tile->HasResource()); DCHECK(tiles_.find(tile->id()) != tiles_.end()); tiles_.erase(tile->id()); LayerCountMap::iterator layer_it = used_layer_counts_.find(tile->layer_id()); DCHECK_GT(layer_it->second, 0); if (--layer_it->second == 0) { used_layer_counts_.erase(layer_it); image_decode_tasks_.erase(tile->layer_id()); } delete tile; it = released_tiles_.erase(it); } } void TileManager::DidFinishRunningTileTasks(TaskSet task_set) { TRACE_EVENT1("cc", "TileManager::DidFinishRunningTileTasks", "task_set", TaskSetName(task_set)); switch (task_set) { case ALL: { bool memory_usage_above_limit = resource_pool_->total_memory_usage_bytes() > global_state_.soft_memory_limit_in_bytes; if (all_tiles_that_need_to_be_rasterized_are_scheduled_ && !memory_usage_above_limit) return; more_tiles_need_prepare_check_notifier_.Schedule(); return; } case REQUIRED_FOR_ACTIVATION: ready_to_activate_check_notifier_.Schedule(); return; case REQUIRED_FOR_DRAW: ready_to_draw_check_notifier_.Schedule(); return; } NOTREACHED(); } void TileManager::PrepareTiles( const GlobalStateThatImpactsTilePriority& state) { TRACE_EVENT0("cc", "TileManager::PrepareTiles"); global_state_ = state; PrepareTilesMode prepare_tiles_mode = rasterizer_->GetPrepareTilesMode(); // TODO(hendrikw): Consider moving some of this code to the rasterizer. if (prepare_tiles_mode != PrepareTilesMode::PREPARE_NONE) { // We need to call CheckForCompletedTasks() once in-between each call // to ScheduleTasks() to prevent canceled tasks from being scheduled. if (!did_check_for_completed_tasks_since_last_schedule_tasks_) { tile_task_runner_->CheckForCompletedTasks(); did_check_for_completed_tasks_since_last_schedule_tasks_ = true; } FreeResourcesForReleasedTiles(); CleanUpReleasedTiles(); TileVector tiles_that_need_to_be_rasterized; AssignGpuMemoryToTiles(&tiles_that_need_to_be_rasterized, scheduled_raster_task_limit_, false); // Schedule tile tasks. ScheduleTasks(tiles_that_need_to_be_rasterized); did_notify_ready_to_activate_ = false; did_notify_ready_to_draw_ = false; } else { if (global_state_.hard_memory_limit_in_bytes == 0) { TileVector tiles_that_need_to_be_rasterized; AssignGpuMemoryToTiles(&tiles_that_need_to_be_rasterized, scheduled_raster_task_limit_, false); DCHECK(tiles_that_need_to_be_rasterized.empty()); } did_notify_ready_to_activate_ = false; did_notify_ready_to_draw_ = false; ready_to_activate_notifier_.Schedule(); ready_to_draw_notifier_.Schedule(); } TRACE_EVENT_INSTANT1("cc", "DidPrepareTiles", TRACE_EVENT_SCOPE_THREAD, "state", BasicStateAsValue()); TRACE_COUNTER_ID1("cc", "unused_memory_bytes", this, resource_pool_->total_memory_usage_bytes() - resource_pool_->acquired_memory_usage_bytes()); } void TileManager::SynchronouslyRasterizeTiles( const GlobalStateThatImpactsTilePriority& state) { TRACE_EVENT0("cc", "TileManager::SynchronouslyRasterizeTiles"); DCHECK(rasterizer_->GetPrepareTilesMode() != PrepareTilesMode::RASTERIZE_PRIORITIZED_TILES); global_state_ = state; FreeResourcesForReleasedTiles(); CleanUpReleasedTiles(); TileVector tiles_that_need_to_be_rasterized; AssignGpuMemoryToTiles(&tiles_that_need_to_be_rasterized, std::numeric_limits::max(), true); // We must reduce the amount of unused resources before calling // RunTasks to prevent usage from rising above limits. resource_pool_->ReduceResourceUsage(); // Run and complete all raster task synchronously. rasterizer_->RasterizeTiles( tiles_that_need_to_be_rasterized, resource_pool_, base::Bind(&TileManager::UpdateTileDrawInfo, base::Unretained(this))); // Use on-demand raster for any required-for-activation tiles that have not // been been assigned memory after reaching a steady memory state. This // ensures that we activate even when OOM. Note that we have to rebuilt the // queue in case the last AssignGpuMemoryToTiles evicted some tiles that would // otherwise not be picked up by the old raster queue. client_->BuildRasterQueue(&raster_priority_queue_, global_state_.tree_priority); // Use on-demand raster for any tiles that have not been been assigned // memory. This ensures that we draw even when OOM. while (!raster_priority_queue_.IsEmpty()) { Tile* tile = raster_priority_queue_.Top(); TileDrawInfo& draw_info = tile->draw_info(); if (tile->required_for_draw() && !draw_info.IsReadyToDraw()) { draw_info.set_rasterize_on_demand(); client_->NotifyTileStateChanged(tile); } raster_priority_queue_.Pop(); } raster_priority_queue_.Reset(); TRACE_EVENT_INSTANT1("cc", "DidRasterize", TRACE_EVENT_SCOPE_THREAD, "state", BasicStateAsValue()); TRACE_COUNTER_ID1("cc", "unused_memory_bytes", this, resource_pool_->total_memory_usage_bytes() - resource_pool_->acquired_memory_usage_bytes()); } void TileManager::UpdateVisibleTiles( const GlobalStateThatImpactsTilePriority& state) { TRACE_EVENT0("cc", "TileManager::UpdateVisibleTiles"); tile_task_runner_->CheckForCompletedTasks(); DCHECK(rasterizer_); PrepareTilesMode prepare_tiles_mode = rasterizer_->GetPrepareTilesMode(); if (prepare_tiles_mode != PrepareTilesMode::RASTERIZE_PRIORITIZED_TILES) SynchronouslyRasterizeTiles(state); did_check_for_completed_tasks_since_last_schedule_tasks_ = true; TRACE_EVENT_INSTANT1( "cc", "DidUpdateVisibleTiles", TRACE_EVENT_SCOPE_THREAD, "stats", RasterTaskCompletionStatsAsValue(update_visible_tiles_stats_)); update_visible_tiles_stats_ = RasterTaskCompletionStats(); } scoped_refptr TileManager::BasicStateAsValue() const { scoped_refptr value = new base::debug::TracedValue(); BasicStateAsValueInto(value.get()); return value; } void TileManager::BasicStateAsValueInto(base::debug::TracedValue* state) const { state->SetInteger("tile_count", tiles_.size()); state->SetBoolean("did_oom_on_last_assign", did_oom_on_last_assign_); state->BeginDictionary("global_state"); global_state_.AsValueInto(state); state->EndDictionary(); } void TileManager::RebuildEvictionQueueIfNeeded() { TRACE_EVENT1("cc", "TileManager::RebuildEvictionQueueIfNeeded", "eviction_priority_queue_is_up_to_date", eviction_priority_queue_is_up_to_date_); if (eviction_priority_queue_is_up_to_date_) return; eviction_priority_queue_.Reset(); client_->BuildEvictionQueue(&eviction_priority_queue_, global_state_.tree_priority); eviction_priority_queue_is_up_to_date_ = true; } bool TileManager::FreeTileResourcesUntilUsageIsWithinLimit( const MemoryUsage& limit, MemoryUsage* usage) { while (usage->Exceeds(limit)) { RebuildEvictionQueueIfNeeded(); if (eviction_priority_queue_.IsEmpty()) return false; Tile* tile = eviction_priority_queue_.Top(); *usage -= MemoryUsage::FromTile(tile); FreeResourcesForTileAndNotifyClientIfTileWasReadyToDraw(tile); eviction_priority_queue_.Pop(); } return true; } bool TileManager::FreeTileResourcesWithLowerPriorityUntilUsageIsWithinLimit( const MemoryUsage& limit, const TilePriority& other_priority, MemoryUsage* usage) { while (usage->Exceeds(limit)) { RebuildEvictionQueueIfNeeded(); if (eviction_priority_queue_.IsEmpty()) return false; Tile* tile = eviction_priority_queue_.Top(); if (!other_priority.IsHigherPriorityThan(tile->combined_priority())) return false; *usage -= MemoryUsage::FromTile(tile); FreeResourcesForTileAndNotifyClientIfTileWasReadyToDraw(tile); eviction_priority_queue_.Pop(); } return true; } bool TileManager::TilePriorityViolatesMemoryPolicy( const TilePriority& priority) { switch (global_state_.memory_limit_policy) { case ALLOW_NOTHING: return true; case ALLOW_ABSOLUTE_MINIMUM: return priority.priority_bin > TilePriority::NOW; case ALLOW_PREPAINT_ONLY: return priority.priority_bin > TilePriority::SOON; case ALLOW_ANYTHING: return priority.distance_to_visible == std::numeric_limits::infinity(); } NOTREACHED(); return true; } void TileManager::AssignGpuMemoryToTiles( TileVector* tiles_that_need_to_be_rasterized, size_t scheduled_raster_task_limit, bool required_for_draw_only) { TRACE_EVENT_BEGIN0("cc", "TileManager::AssignGpuMemoryToTiles"); // Maintain the list of released resources that can potentially be re-used // or deleted. // If this operation becomes expensive too, only do this after some // resource(s) was returned. Note that in that case, one also need to // invalidate when releasing some resource from the pool. resource_pool_->CheckBusyResources(false); // Now give memory out to the tiles until we're out, and build // the needs-to-be-rasterized queue. unsigned schedule_priority = 1u; all_tiles_that_need_to_be_rasterized_are_scheduled_ = true; bool had_enough_memory_to_schedule_tiles_needed_now = true; MemoryUsage hard_memory_limit(global_state_.hard_memory_limit_in_bytes, global_state_.num_resources_limit); MemoryUsage soft_memory_limit(global_state_.soft_memory_limit_in_bytes, global_state_.num_resources_limit); MemoryUsage memory_usage(resource_pool_->acquired_memory_usage_bytes(), resource_pool_->acquired_resource_count()); eviction_priority_queue_is_up_to_date_ = false; client_->BuildRasterQueue(&raster_priority_queue_, global_state_.tree_priority); while (!raster_priority_queue_.IsEmpty()) { Tile* tile = raster_priority_queue_.Top(); // TODO(vmpstr): Remove this when the iterator returns the correct tiles // to draw for GPU rasterization. if (required_for_draw_only) { if (!tile->required_for_draw()) { raster_priority_queue_.Pop(); continue; } } TilePriority priority = tile->combined_priority(); if (TilePriorityViolatesMemoryPolicy(priority)) { TRACE_EVENT_INSTANT0( "cc", "TileManager::AssignGpuMemory tile violates memory policy", TRACE_EVENT_SCOPE_THREAD); break; } // We won't be able to schedule this tile, so break out early. if (tiles_that_need_to_be_rasterized->size() >= scheduled_raster_task_limit) { all_tiles_that_need_to_be_rasterized_are_scheduled_ = false; break; } TileDrawInfo& draw_info = tile->draw_info(); tile->scheduled_priority_ = schedule_priority++; DCHECK(draw_info.mode() == TileDrawInfo::PICTURE_PILE_MODE || !draw_info.IsReadyToDraw()); // If the tile already has a raster_task, then the memory used by it is // already accounted for in memory_usage. Otherwise, we'll have to acquire // more memory to create a raster task. MemoryUsage memory_required_by_tile_to_be_scheduled; if (!tile->raster_task_.get()) { memory_required_by_tile_to_be_scheduled = MemoryUsage::FromConfig( tile->desired_texture_size(), resource_pool_->resource_format()); } bool tile_is_needed_now = priority.priority_bin == TilePriority::NOW; // This is the memory limit that will be used by this tile. Depending on // the tile priority, it will be one of hard_memory_limit or // soft_memory_limit. MemoryUsage& tile_memory_limit = tile_is_needed_now ? hard_memory_limit : soft_memory_limit; bool memory_usage_is_within_limit = FreeTileResourcesWithLowerPriorityUntilUsageIsWithinLimit( tile_memory_limit - memory_required_by_tile_to_be_scheduled, priority, &memory_usage); // If we couldn't fit the tile into our current memory limit, then we're // done. if (!memory_usage_is_within_limit) { if (tile_is_needed_now) had_enough_memory_to_schedule_tiles_needed_now = false; all_tiles_that_need_to_be_rasterized_are_scheduled_ = false; break; } memory_usage += memory_required_by_tile_to_be_scheduled; tiles_that_need_to_be_rasterized->push_back(tile); raster_priority_queue_.Pop(); } // Note that we should try and further reduce memory in case the above loop // didn't reduce memory. This ensures that we always release as many resources // as possible to stay within the memory limit. FreeTileResourcesUntilUsageIsWithinLimit(hard_memory_limit, &memory_usage); UMA_HISTOGRAM_BOOLEAN("TileManager.ExceededMemoryBudget", !had_enough_memory_to_schedule_tiles_needed_now); did_oom_on_last_assign_ = !had_enough_memory_to_schedule_tiles_needed_now; memory_stats_from_last_assign_.total_budget_in_bytes = global_state_.hard_memory_limit_in_bytes; memory_stats_from_last_assign_.total_bytes_used = memory_usage.memory_bytes(); memory_stats_from_last_assign_.had_enough_memory = had_enough_memory_to_schedule_tiles_needed_now; raster_priority_queue_.Reset(); TRACE_EVENT_END2("cc", "TileManager::AssignGpuMemoryToTiles", "all_tiles_that_need_to_be_rasterized_are_scheduled", all_tiles_that_need_to_be_rasterized_are_scheduled_, "had_enough_memory_to_schedule_tiles_needed_now", had_enough_memory_to_schedule_tiles_needed_now); } void TileManager::FreeResourcesForTile(Tile* tile) { TileDrawInfo& draw_info = tile->draw_info(); if (draw_info.resource_) resource_pool_->ReleaseResource(draw_info.resource_.Pass()); } void TileManager::FreeResourcesForTileAndNotifyClientIfTileWasReadyToDraw( Tile* tile) { bool was_ready_to_draw = tile->IsReadyToDraw(); FreeResourcesForTile(tile); if (was_ready_to_draw) client_->NotifyTileStateChanged(tile); } void TileManager::ScheduleTasks( const TileVector& tiles_that_need_to_be_rasterized) { TRACE_EVENT1("cc", "TileManager::ScheduleTasks", "count", tiles_that_need_to_be_rasterized.size()); DCHECK(did_check_for_completed_tasks_since_last_schedule_tasks_); raster_queue_.Reset(); // Build a new task queue containing all task currently needed. Tasks // are added in order of priority, highest priority task first. for (TileVector::const_iterator it = tiles_that_need_to_be_rasterized.begin(); it != tiles_that_need_to_be_rasterized.end(); ++it) { Tile* tile = *it; TileDrawInfo& draw_info = tile->draw_info(); DCHECK(draw_info.requires_resource()); DCHECK(!draw_info.resource_); if (!tile->raster_task_.get()) tile->raster_task_ = CreateRasterTask(tile); TaskSetCollection task_sets; if (tile->required_for_activation()) task_sets.set(REQUIRED_FOR_ACTIVATION); if (tile->required_for_draw()) task_sets.set(REQUIRED_FOR_DRAW); task_sets.set(ALL); raster_queue_.items.push_back( TileTaskQueue::Item(tile->raster_task_.get(), task_sets)); } // We must reduce the amount of unused resoruces before calling // ScheduleTasks to prevent usage from rising above limits. resource_pool_->ReduceResourceUsage(); // Schedule running of |raster_queue_|. This replaces any previously // scheduled tasks and effectively cancels all tasks not present // in |raster_queue_|. tile_task_runner_->ScheduleTasks(&raster_queue_); // It's now safe to clean up orphan tasks as raster worker pool is not // allowed to keep around unreferenced raster tasks after ScheduleTasks() has // been called. orphan_raster_tasks_.clear(); did_check_for_completed_tasks_since_last_schedule_tasks_ = false; } scoped_refptr TileManager::CreateImageDecodeTask( Tile* tile, SkPixelRef* pixel_ref) { return make_scoped_refptr(new ImageDecodeTaskImpl( pixel_ref, tile->layer_id(), base::Bind(&TileManager::OnImageDecodeTaskCompleted, base::Unretained(this), tile->layer_id(), base::Unretained(pixel_ref)))); } scoped_refptr TileManager::CreateRasterTask(Tile* tile) { scoped_ptr resource = resource_pool_->AcquireResource(tile->desired_texture_size()); const ScopedResource* const_resource = resource.get(); // Create and queue all image decode tasks that this tile depends on. ImageDecodeTask::Vector decode_tasks; PixelRefTaskMap& existing_pixel_refs = image_decode_tasks_[tile->layer_id()]; std::vector pixel_refs; tile->raster_source()->GatherPixelRefs( tile->content_rect(), tile->contents_scale(), &pixel_refs); for (SkPixelRef* pixel_ref : pixel_refs) { uint32_t id = pixel_ref->getGenerationID(); // Append existing image decode task if available. PixelRefTaskMap::iterator decode_task_it = existing_pixel_refs.find(id); if (decode_task_it != existing_pixel_refs.end()) { decode_tasks.push_back(decode_task_it->second); continue; } // Create and append new image decode task for this pixel ref. scoped_refptr decode_task = CreateImageDecodeTask(tile, pixel_ref); decode_tasks.push_back(decode_task); existing_pixel_refs[id] = decode_task; } return make_scoped_refptr(new RasterTaskImpl( const_resource, tile->raster_source(), tile->content_rect(), tile->contents_scale(), tile->combined_priority().resolution, tile->layer_id(), static_cast(tile), tile->source_frame_number(), tile->use_picture_analysis(), base::Bind(&TileManager::OnRasterTaskCompleted, base::Unretained(this), tile->id(), base::Passed(&resource)), &decode_tasks)); } void TileManager::OnImageDecodeTaskCompleted(int layer_id, SkPixelRef* pixel_ref, bool was_canceled) { // If the task was canceled, we need to clean it up // from |image_decode_tasks_|. if (!was_canceled) return; LayerPixelRefTaskMap::iterator layer_it = image_decode_tasks_.find(layer_id); if (layer_it == image_decode_tasks_.end()) return; PixelRefTaskMap& pixel_ref_tasks = layer_it->second; PixelRefTaskMap::iterator task_it = pixel_ref_tasks.find(pixel_ref->getGenerationID()); if (task_it != pixel_ref_tasks.end()) pixel_ref_tasks.erase(task_it); } void TileManager::OnRasterTaskCompleted( Tile::Id tile_id, scoped_ptr resource, const RasterSource::SolidColorAnalysis& analysis, bool was_canceled) { DCHECK(tiles_.find(tile_id) != tiles_.end()); Tile* tile = tiles_[tile_id]; DCHECK(tile->raster_task_.get()); orphan_raster_tasks_.push_back(tile->raster_task_); tile->raster_task_ = nullptr; if (was_canceled) { ++update_visible_tiles_stats_.canceled_count; resource_pool_->ReleaseResource(resource.Pass()); return; } UpdateTileDrawInfo(tile, resource.Pass(), analysis); } void TileManager::UpdateTileDrawInfo( Tile* tile, scoped_ptr resource, const RasterSource::SolidColorAnalysis& analysis) { TileDrawInfo& draw_info = tile->draw_info(); ++update_visible_tiles_stats_.completed_count; if (analysis.is_solid_color) { draw_info.set_solid_color(analysis.solid_color); resource_pool_->ReleaseResource(resource.Pass()); } else { draw_info.set_use_resource(); draw_info.resource_ = resource.Pass(); } client_->NotifyTileStateChanged(tile); } scoped_refptr TileManager::CreateTile( RasterSource* raster_source, const gfx::Size& desired_texture_size, const gfx::Rect& content_rect, float contents_scale, int layer_id, int source_frame_number, int flags) { scoped_refptr tile = make_scoped_refptr( new Tile(this, raster_source, desired_texture_size, content_rect, contents_scale, layer_id, source_frame_number, flags)); DCHECK(tiles_.find(tile->id()) == tiles_.end()); tiles_[tile->id()] = tile.get(); used_layer_counts_[tile->layer_id()]++; return tile; } void TileManager::SetTileTaskRunnerForTesting( TileTaskRunner* tile_task_runner) { tile_task_runner_ = tile_task_runner; tile_task_runner_->SetClient(this); } bool TileManager::IsReadyToActivate() const { TRACE_EVENT0("cc", "TileManager::IsReadyToActivate"); const std::vector& layers = client_->GetPictureLayers(); for (const auto& layer : layers) { if (!layer->AllTilesRequiredForActivationAreReadyToDraw()) return false; } return true; } bool TileManager::IsReadyToDraw() const { const std::vector& layers = client_->GetPictureLayers(); for (const auto& layer : layers) { if (!layer->AllTilesRequiredForDrawAreReadyToDraw()) return false; } return true; } void TileManager::NotifyReadyToActivate() { TRACE_EVENT0("cc", "TileManager::NotifyReadyToActivate"); if (did_notify_ready_to_activate_) return; client_->NotifyReadyToActivate(); did_notify_ready_to_activate_ = true; } void TileManager::NotifyReadyToDraw() { TRACE_EVENT0("cc", "TileManager::NotifyReadyToDraw"); if (did_notify_ready_to_draw_) return; client_->NotifyReadyToDraw(); did_notify_ready_to_draw_ = true; } void TileManager::CheckIfReadyToActivate() { TRACE_EVENT0("cc", "TileManager::CheckIfReadyToActivate"); tile_task_runner_->CheckForCompletedTasks(); did_check_for_completed_tasks_since_last_schedule_tasks_ = true; if (did_notify_ready_to_activate_) return; if (!IsReadyToActivate()) return; NotifyReadyToActivate(); } void TileManager::CheckIfReadyToDraw() { TRACE_EVENT0("cc", "TileManager::CheckIfReadyToDraw"); tile_task_runner_->CheckForCompletedTasks(); did_check_for_completed_tasks_since_last_schedule_tasks_ = true; if (did_notify_ready_to_draw_) return; if (!IsReadyToDraw()) return; NotifyReadyToDraw(); } void TileManager::CheckIfMoreTilesNeedToBePrepared() { tile_task_runner_->CheckForCompletedTasks(); did_check_for_completed_tasks_since_last_schedule_tasks_ = true; // When OOM, keep re-assigning memory until we reach a steady state // where top-priority tiles are initialized. TileVector tiles_that_need_to_be_rasterized; AssignGpuMemoryToTiles(&tiles_that_need_to_be_rasterized, scheduled_raster_task_limit_, false); // |tiles_that_need_to_be_rasterized| will be empty when we reach a // steady memory state. Keep scheduling tasks until we reach this state. if (!tiles_that_need_to_be_rasterized.empty()) { ScheduleTasks(tiles_that_need_to_be_rasterized); return; } FreeResourcesForReleasedTiles(); resource_pool_->ReduceResourceUsage(); // We don't reserve memory for required-for-activation tiles during // accelerated gestures, so we just postpone activation when we don't // have these tiles, and activate after the accelerated gesture. // Likewise if we don't allow any tiles (as is the case when we're // invisible), if we have tiles that aren't ready, then we shouldn't // activate as activation can cause checkerboards. bool allow_rasterize_on_demand = global_state_.tree_priority != SMOOTHNESS_TAKES_PRIORITY && global_state_.memory_limit_policy != ALLOW_NOTHING; // Use on-demand raster for any required-for-activation tiles that have // not been been assigned memory after reaching a steady memory state. This // ensures that we activate even when OOM. Note that we have to rebuilt the // queue in case the last AssignGpuMemoryToTiles evicted some tiles that // would otherwise not be picked up by the old raster queue. client_->BuildRasterQueue(&raster_priority_queue_, global_state_.tree_priority); bool ready_to_activate = true; while (!raster_priority_queue_.IsEmpty()) { Tile* tile = raster_priority_queue_.Top(); TileDrawInfo& draw_info = tile->draw_info(); if (tile->required_for_activation() && !draw_info.IsReadyToDraw()) { // If we can't raster on demand, give up early (and don't activate). if (!allow_rasterize_on_demand) { ready_to_activate = false; break; } draw_info.set_rasterize_on_demand(); client_->NotifyTileStateChanged(tile); } raster_priority_queue_.Pop(); } if (ready_to_activate) { DCHECK(IsReadyToActivate()); ready_to_activate_check_notifier_.Schedule(); } raster_priority_queue_.Reset(); } TileManager::MemoryUsage::MemoryUsage() : memory_bytes_(0), resource_count_(0) { } TileManager::MemoryUsage::MemoryUsage(int64 memory_bytes, int resource_count) : memory_bytes_(memory_bytes), resource_count_(resource_count) { } // static TileManager::MemoryUsage TileManager::MemoryUsage::FromConfig( const gfx::Size& size, ResourceFormat format) { return MemoryUsage(Resource::MemorySizeBytes(size, format), 1); } // static TileManager::MemoryUsage TileManager::MemoryUsage::FromTile(const Tile* tile) { const TileDrawInfo& draw_info = tile->draw_info(); if (draw_info.resource_) { return MemoryUsage::FromConfig(draw_info.resource_->size(), draw_info.resource_->format()); } return MemoryUsage(); } TileManager::MemoryUsage& TileManager::MemoryUsage::operator+=( const MemoryUsage& other) { memory_bytes_ += other.memory_bytes_; resource_count_ += other.resource_count_; return *this; } TileManager::MemoryUsage& TileManager::MemoryUsage::operator-=( const MemoryUsage& other) { memory_bytes_ -= other.memory_bytes_; resource_count_ -= other.resource_count_; return *this; } TileManager::MemoryUsage TileManager::MemoryUsage::operator-( const MemoryUsage& other) { MemoryUsage result = *this; result -= other; return result; } bool TileManager::MemoryUsage::Exceeds(const MemoryUsage& limit) const { return memory_bytes_ > limit.memory_bytes_ || resource_count_ > limit.resource_count_; } } // namespace cc