| // Copyright 2015 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/surfaces/display_scheduler.h" |
| |
| #include <vector> |
| |
| #include "base/stl_util.h" |
| #include "base/trace_event/trace_event.h" |
| #include "cc/output/output_surface.h" |
| |
| namespace cc { |
| |
| DisplayScheduler::DisplayScheduler(BeginFrameSource* begin_frame_source, |
| base::SingleThreadTaskRunner* task_runner, |
| int max_pending_swaps) |
| : begin_frame_source_(begin_frame_source), |
| task_runner_(task_runner), |
| output_surface_lost_(false), |
| root_surface_resources_locked_(true), |
| inside_begin_frame_deadline_interval_(false), |
| needs_draw_(false), |
| expecting_root_surface_damage_because_of_resize_(false), |
| all_active_child_surfaces_ready_to_draw_(false), |
| pending_swaps_(0), |
| max_pending_swaps_(max_pending_swaps), |
| observing_begin_frame_source_(false), |
| root_surface_damaged_(false), |
| expect_damage_from_root_surface_(false), |
| weak_ptr_factory_(this) { |
| begin_frame_deadline_closure_ = base::Bind( |
| &DisplayScheduler::OnBeginFrameDeadline, weak_ptr_factory_.GetWeakPtr()); |
| } |
| |
| DisplayScheduler::~DisplayScheduler() { |
| if (observing_begin_frame_source_) |
| begin_frame_source_->RemoveObserver(this); |
| } |
| |
| void DisplayScheduler::SetClient(DisplaySchedulerClient* client) { |
| client_ = client; |
| } |
| |
| // If we try to draw when the root surface resources are locked, the |
| // draw will fail. |
| void DisplayScheduler::SetRootSurfaceResourcesLocked(bool locked) { |
| TRACE_EVENT1("cc", "DisplayScheduler::SetRootSurfaceResourcesLocked", |
| "locked", locked); |
| root_surface_resources_locked_ = locked; |
| ScheduleBeginFrameDeadline(); |
| } |
| |
| // This is used to force an immediate swap before a resize. |
| void DisplayScheduler::ForceImmediateSwapIfPossible() { |
| TRACE_EVENT0("cc", "DisplayScheduler::ForceImmediateSwapIfPossible"); |
| bool in_begin = inside_begin_frame_deadline_interval_; |
| AttemptDrawAndSwap(); |
| if (in_begin) |
| begin_frame_source_->DidFinishFrame(this, 0); |
| } |
| |
| void DisplayScheduler::DisplayResized() { |
| expecting_root_surface_damage_because_of_resize_ = true; |
| expect_damage_from_root_surface_ = true; |
| needs_draw_ = true; |
| ScheduleBeginFrameDeadline(); |
| } |
| |
| // Notification that there was a resize or the root surface changed and |
| // that we should just draw immediately. |
| void DisplayScheduler::SetNewRootSurface(SurfaceId root_surface_id) { |
| TRACE_EVENT0("cc", "DisplayScheduler::SetNewRootSurface"); |
| root_surface_id_ = root_surface_id; |
| SurfaceDamaged(root_surface_id); |
| } |
| |
| // Indicates that there was damage to one of the surfaces. |
| // Has some logic to wait for multiple active surfaces before |
| // triggering the deadline. |
| void DisplayScheduler::SurfaceDamaged(SurfaceId surface_id) { |
| TRACE_EVENT1("cc", "DisplayScheduler::SurfaceDamaged", "surface_id", |
| surface_id.ToString()); |
| |
| needs_draw_ = true; |
| |
| if (surface_id == root_surface_id_) { |
| root_surface_damaged_ = true; |
| expecting_root_surface_damage_because_of_resize_ = false; |
| } else { |
| child_surface_ids_damaged_.insert(surface_id); |
| |
| // TODO(mithro): Use hints from SetNeedsBeginFrames and SwapAborts. |
| all_active_child_surfaces_ready_to_draw_ = base::STLIncludes( |
| child_surface_ids_damaged_, child_surface_ids_to_expect_damage_from_); |
| } |
| |
| if (!output_surface_lost_ && !observing_begin_frame_source_) { |
| observing_begin_frame_source_ = true; |
| begin_frame_source_->AddObserver(this); |
| } |
| |
| ScheduleBeginFrameDeadline(); |
| } |
| |
| void DisplayScheduler::OutputSurfaceLost() { |
| TRACE_EVENT0("cc", "DisplayScheduler::OutputSurfaceLost"); |
| output_surface_lost_ = true; |
| ScheduleBeginFrameDeadline(); |
| } |
| |
| void DisplayScheduler::DrawAndSwap() { |
| TRACE_EVENT0("cc", "DisplayScheduler::DrawAndSwap"); |
| DCHECK_LT(pending_swaps_, max_pending_swaps_); |
| DCHECK(!output_surface_lost_); |
| |
| bool success = client_->DrawAndSwap(); |
| if (!success) |
| return; |
| |
| child_surface_ids_to_expect_damage_from_ = |
| base::STLSetIntersection<std::vector<SurfaceId>>( |
| child_surface_ids_damaged_, child_surface_ids_damaged_prev_); |
| |
| child_surface_ids_damaged_prev_.swap(child_surface_ids_damaged_); |
| child_surface_ids_damaged_.clear(); |
| |
| needs_draw_ = false; |
| all_active_child_surfaces_ready_to_draw_ = |
| child_surface_ids_to_expect_damage_from_.empty(); |
| |
| expect_damage_from_root_surface_ = root_surface_damaged_; |
| root_surface_damaged_ = false; |
| } |
| |
| bool DisplayScheduler::OnBeginFrameDerivedImpl(const BeginFrameArgs& args) { |
| base::TimeTicks now = base::TimeTicks::Now(); |
| TRACE_EVENT2("cc", "DisplayScheduler::BeginFrame", "args", args.AsValue(), |
| "now", now); |
| |
| // If we get another BeginFrame before the previous deadline, |
| // synchronously trigger the previous deadline before progressing. |
| if (inside_begin_frame_deadline_interval_) { |
| OnBeginFrameDeadline(); |
| } |
| |
| // Schedule the deadline. |
| current_begin_frame_args_ = args; |
| current_begin_frame_args_.deadline -= |
| BeginFrameArgs::DefaultEstimatedParentDrawTime(); |
| inside_begin_frame_deadline_interval_ = true; |
| ScheduleBeginFrameDeadline(); |
| |
| return true; |
| } |
| |
| void DisplayScheduler::OnBeginFrameSourcePausedChanged(bool paused) { |
| // BeginFrameSources used with DisplayScheduler do not make use of this |
| // feature. |
| if (paused) |
| NOTIMPLEMENTED(); |
| } |
| |
| base::TimeTicks DisplayScheduler::DesiredBeginFrameDeadlineTime() { |
| if (output_surface_lost_) { |
| TRACE_EVENT_INSTANT0("cc", "Lost output surface", TRACE_EVENT_SCOPE_THREAD); |
| return base::TimeTicks(); |
| } |
| |
| if (pending_swaps_ >= max_pending_swaps_) { |
| TRACE_EVENT_INSTANT0("cc", "Swap throttled", TRACE_EVENT_SCOPE_THREAD); |
| return current_begin_frame_args_.frame_time + |
| current_begin_frame_args_.interval; |
| } |
| |
| if (!needs_draw_) { |
| TRACE_EVENT_INSTANT0("cc", "No damage yet", TRACE_EVENT_SCOPE_THREAD); |
| return current_begin_frame_args_.frame_time + |
| current_begin_frame_args_.interval; |
| } |
| |
| if (root_surface_resources_locked_) { |
| TRACE_EVENT_INSTANT0("cc", "Root surface resources locked", |
| TRACE_EVENT_SCOPE_THREAD); |
| return current_begin_frame_args_.frame_time + |
| current_begin_frame_args_.interval; |
| } |
| |
| bool root_ready_to_draw = |
| !expect_damage_from_root_surface_ || root_surface_damaged_; |
| |
| if (all_active_child_surfaces_ready_to_draw_ && root_ready_to_draw) { |
| TRACE_EVENT_INSTANT0("cc", "All active surfaces ready", |
| TRACE_EVENT_SCOPE_THREAD); |
| return base::TimeTicks(); |
| } |
| |
| // TODO(mithro): Be smarter about resize deadlines. |
| if (expecting_root_surface_damage_because_of_resize_) { |
| TRACE_EVENT_INSTANT0("cc", "Entire display damaged", |
| TRACE_EVENT_SCOPE_THREAD); |
| return current_begin_frame_args_.frame_time + |
| current_begin_frame_args_.interval; |
| } |
| |
| // Use an earlier deadline if we are only waiting for the root surface |
| // in case our expect_damage_from_root_surface heuristic is incorrect. |
| // TODO(mithro): Replace this with SetNeedsBeginFrame and SwapAbort |
| // logic. |
| if (all_active_child_surfaces_ready_to_draw_ && |
| expect_damage_from_root_surface_) { |
| TRACE_EVENT_INSTANT0("cc", "Waiting for damage from root surface", |
| TRACE_EVENT_SCOPE_THREAD); |
| // This adjusts the deadline by DefaultEstimatedParentDrawTime for |
| // a second time. The first one represented the Surfaces draw to display |
| // latency. This one represents root surface commit+raster+draw latency. |
| // We treat the root surface differently since it lives on the same thread |
| // as Surfaces and waiting for it too long may push out the Surfaces draw. |
| // If we also assume the root surface is fast to start a commit after the |
| // beginning of a frame, it'll have a chance to lock its resources, which |
| // will cause us to wait for it to unlock its resources above. |
| // TODO(mithro): Replace hard coded estimates. |
| return current_begin_frame_args_.deadline - |
| BeginFrameArgs::DefaultEstimatedParentDrawTime(); |
| } |
| |
| TRACE_EVENT_INSTANT0("cc", "More damage expected soon", |
| TRACE_EVENT_SCOPE_THREAD); |
| return current_begin_frame_args_.deadline; |
| } |
| |
| void DisplayScheduler::ScheduleBeginFrameDeadline() { |
| TRACE_EVENT0("cc", "DisplayScheduler::ScheduleBeginFrameDeadline"); |
| |
| // We need to wait for the next BeginFrame before scheduling a deadline. |
| if (!inside_begin_frame_deadline_interval_) { |
| TRACE_EVENT_INSTANT0("cc", "Waiting for next BeginFrame", |
| TRACE_EVENT_SCOPE_THREAD); |
| DCHECK(begin_frame_deadline_task_.IsCancelled()); |
| return; |
| } |
| |
| // Determine the deadline we want to use. |
| base::TimeTicks desired_deadline = DesiredBeginFrameDeadlineTime(); |
| |
| // Avoid re-scheduling the deadline if it's already correctly scheduled. |
| if (!begin_frame_deadline_task_.IsCancelled() && |
| desired_deadline == begin_frame_deadline_task_time_) { |
| TRACE_EVENT_INSTANT0("cc", "Using existing deadline", |
| TRACE_EVENT_SCOPE_THREAD); |
| return; |
| } |
| |
| // Schedule the deadline. |
| begin_frame_deadline_task_time_ = desired_deadline; |
| begin_frame_deadline_task_.Cancel(); |
| begin_frame_deadline_task_.Reset(begin_frame_deadline_closure_); |
| |
| base::TimeDelta delta = |
| std::max(base::TimeDelta(), desired_deadline - base::TimeTicks::Now()); |
| task_runner_->PostDelayedTask(FROM_HERE, |
| begin_frame_deadline_task_.callback(), delta); |
| TRACE_EVENT2("cc", "Using new deadline", "delta", delta.ToInternalValue(), |
| "desired_deadline", desired_deadline); |
| } |
| |
| void DisplayScheduler::AttemptDrawAndSwap() { |
| inside_begin_frame_deadline_interval_ = false; |
| begin_frame_deadline_task_.Cancel(); |
| begin_frame_deadline_task_time_ = base::TimeTicks(); |
| |
| if (needs_draw_ && !output_surface_lost_) { |
| if (pending_swaps_ < max_pending_swaps_ && !root_surface_resources_locked_) |
| DrawAndSwap(); |
| } else { |
| // We are going idle, so reset expectations. |
| child_surface_ids_to_expect_damage_from_.clear(); |
| child_surface_ids_damaged_prev_.clear(); |
| child_surface_ids_damaged_.clear(); |
| all_active_child_surfaces_ready_to_draw_ = true; |
| expect_damage_from_root_surface_ = false; |
| |
| if (observing_begin_frame_source_) { |
| observing_begin_frame_source_ = false; |
| begin_frame_source_->RemoveObserver(this); |
| } |
| } |
| } |
| |
| void DisplayScheduler::OnBeginFrameDeadline() { |
| TRACE_EVENT0("cc", "DisplayScheduler::OnBeginFrameDeadline"); |
| |
| AttemptDrawAndSwap(); |
| begin_frame_source_->DidFinishFrame(this, 0); |
| } |
| |
| void DisplayScheduler::DidSwapBuffers() { |
| pending_swaps_++; |
| TRACE_EVENT_ASYNC_BEGIN1("cc", "DisplayScheduler:pending_swaps", this, |
| "pending_frames", pending_swaps_); |
| } |
| |
| void DisplayScheduler::DidSwapBuffersComplete() { |
| pending_swaps_--; |
| TRACE_EVENT_ASYNC_END1("cc", "DisplayScheduler:pending_swaps", this, |
| "pending_frames", pending_swaps_); |
| ScheduleBeginFrameDeadline(); |
| } |
| |
| } // namespace cc |