| // 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 "components/viz/service/display/display_scheduler.h" |
| |
| #include <vector> |
| |
| #include "base/auto_reset.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/stl_util.h" |
| #include "base/trace_event/trace_event.h" |
| #include "components/viz/common/surfaces/surface_info.h" |
| #include "components/viz/service/display/output_surface.h" |
| |
| namespace viz { |
| |
| DisplayScheduler::DisplayScheduler(BeginFrameSource* begin_frame_source, |
| base::SingleThreadTaskRunner* task_runner, |
| int max_pending_swaps, |
| bool wait_for_all_surfaces_before_draw) |
| : client_(nullptr), |
| begin_frame_source_(begin_frame_source), |
| task_runner_(task_runner), |
| inside_surface_damaged_(false), |
| visible_(false), |
| output_surface_lost_(false), |
| root_frame_missing_(true), |
| inside_begin_frame_deadline_interval_(false), |
| needs_draw_(false), |
| expecting_root_surface_damage_because_of_resize_(false), |
| has_pending_surfaces_(false), |
| next_swap_id_(1), |
| pending_swaps_(0), |
| max_pending_swaps_(max_pending_swaps), |
| wait_for_all_surfaces_before_draw_(wait_for_all_surfaces_before_draw), |
| observing_begin_frame_source_(false), |
| weak_ptr_factory_(this) { |
| begin_frame_deadline_closure_ = base::BindRepeating( |
| &DisplayScheduler::OnBeginFrameDeadline, weak_ptr_factory_.GetWeakPtr()); |
| |
| // The DisplayScheduler handles animate_only BeginFrames as if they were |
| // normal BeginFrames: Clients won't commit a CompositorFrame but will still |
| // acknowledge when they have completed the BeginFrame via BeginFrameAcks and |
| // the DisplayScheduler will still indicate when all clients have finished via |
| // DisplayObserver::OnDisplayDidFinishFrame. |
| wants_animate_only_begin_frames_ = true; |
| } |
| |
| DisplayScheduler::~DisplayScheduler() { |
| // It is possible for DisplayScheduler to be destroyed while there's an |
| // in-flight swap. So always mark the gpu as not busy during destruction. |
| begin_frame_source_->SetIsGpuBusy(false); |
| StopObservingBeginFrames(); |
| } |
| |
| void DisplayScheduler::SetClient(DisplaySchedulerClient* client) { |
| client_ = client; |
| } |
| |
| void DisplayScheduler::SetVisible(bool visible) { |
| if (visible_ == visible) |
| return; |
| |
| visible_ = visible; |
| // If going invisible, we'll stop observing begin frames once we try |
| // to draw and fail. |
| MaybeStartObservingBeginFrames(); |
| ScheduleBeginFrameDeadline(); |
| } |
| |
| void DisplayScheduler::SetRootFrameMissing(bool missing) { |
| TRACE_EVENT1("viz", "DisplayScheduler::SetRootFrameMissing", "missing", |
| missing); |
| if (root_frame_missing_ == missing) |
| return; |
| |
| root_frame_missing_ = missing; |
| MaybeStartObservingBeginFrames(); |
| ScheduleBeginFrameDeadline(); |
| } |
| |
| // This is used to force an immediate swap before a resize. |
| void DisplayScheduler::ForceImmediateSwapIfPossible() { |
| TRACE_EVENT0("viz", "DisplayScheduler::ForceImmediateSwapIfPossible"); |
| bool in_begin = inside_begin_frame_deadline_interval_; |
| bool did_draw = AttemptDrawAndSwap(); |
| if (in_begin) |
| DidFinishFrame(did_draw); |
| } |
| |
| void DisplayScheduler::DisplayResized() { |
| expecting_root_surface_damage_because_of_resize_ = 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(const SurfaceId& root_surface_id) { |
| TRACE_EVENT0("viz", "DisplayScheduler::SetNewRootSurface"); |
| root_surface_id_ = root_surface_id; |
| BeginFrameAck ack; |
| ack.has_damage = true; |
| ProcessSurfaceDamage(root_surface_id, ack, true); |
| } |
| |
| // 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::ProcessSurfaceDamage(const SurfaceId& surface_id, |
| const BeginFrameAck& ack, |
| bool display_damaged) { |
| TRACE_EVENT1("viz", "DisplayScheduler::SurfaceDamaged", "surface_id", |
| surface_id.ToString()); |
| |
| // We may cause a new BeginFrame to be run inside this method, but to help |
| // avoid being reentrant to the caller of SurfaceDamaged, track when this is |
| // happening with |inside_surface_damaged_|. |
| base::AutoReset<bool> auto_reset(&inside_surface_damaged_, true); |
| |
| if (display_damaged) { |
| needs_draw_ = true; |
| |
| if (surface_id == root_surface_id_) |
| expecting_root_surface_damage_because_of_resize_ = false; |
| |
| MaybeStartObservingBeginFrames(); |
| } |
| |
| // Update surface state. |
| bool valid_ack = ack.sequence_number != BeginFrameArgs::kInvalidFrameNumber; |
| if (valid_ack) { |
| auto it = surface_states_.find(surface_id); |
| // Ignore stray acknowledgments for prior BeginFrames, to ensure we don't |
| // override a newer sequence number in the surface state. We may receive |
| // such stray acks e.g. when a CompositorFrame activates in a later |
| // BeginFrame than it was created. |
| if (it != surface_states_.end() && |
| (it->second.last_ack.source_id != ack.source_id || |
| it->second.last_ack.sequence_number < ack.sequence_number)) { |
| it->second.last_ack = ack; |
| } else { |
| valid_ack = false; |
| } |
| } |
| |
| bool pending_surfaces_changed = false; |
| if (display_damaged || valid_ack) |
| pending_surfaces_changed = UpdateHasPendingSurfaces(); |
| |
| if (display_damaged || pending_surfaces_changed) |
| ScheduleBeginFrameDeadline(); |
| } |
| |
| bool DisplayScheduler::UpdateHasPendingSurfaces() { |
| // If we're not currently inside a deadline interval, we will call |
| // UpdateHasPendingSurfaces() again during OnBeginFrameImpl(). |
| if (!inside_begin_frame_deadline_interval_ || !client_) |
| return false; |
| |
| bool old_value = has_pending_surfaces_; |
| |
| for (const std::pair<SurfaceId, SurfaceBeginFrameState>& entry : |
| surface_states_) { |
| const SurfaceId& surface_id = entry.first; |
| const SurfaceBeginFrameState& state = entry.second; |
| |
| // Surface is ready if it hasn't received the current BeginFrame or receives |
| // BeginFrames from a different source and thus likely belongs to a |
| // different surface hierarchy. |
| uint64_t source_id = current_begin_frame_args_.source_id; |
| uint64_t sequence_number = current_begin_frame_args_.sequence_number; |
| if (!state.last_args.IsValid() || state.last_args.source_id != source_id || |
| state.last_args.sequence_number != sequence_number) { |
| continue; |
| } |
| |
| // Surface is ready if it has acknowledged the current BeginFrame. |
| if (state.last_ack.source_id == source_id && |
| state.last_ack.sequence_number == sequence_number) { |
| continue; |
| } |
| |
| // Surface is ready if there is an undrawn active CompositorFrame, because |
| // its producer is CompositorFrameAck throttled. |
| if (client_->SurfaceHasUndrawnFrame(entry.first)) |
| continue; |
| |
| has_pending_surfaces_ = true; |
| TRACE_EVENT_INSTANT2("viz", "DisplayScheduler::UpdateHasPendingSurfaces", |
| TRACE_EVENT_SCOPE_THREAD, "has_pending_surfaces", |
| has_pending_surfaces_, "pending_surface_id", |
| surface_id.ToString()); |
| return has_pending_surfaces_ != old_value; |
| } |
| has_pending_surfaces_ = false; |
| TRACE_EVENT_INSTANT1("viz", "DisplayScheduler::UpdateHasPendingSurfaces", |
| TRACE_EVENT_SCOPE_THREAD, "has_pending_surfaces", |
| has_pending_surfaces_); |
| return has_pending_surfaces_ != old_value; |
| } |
| |
| void DisplayScheduler::OutputSurfaceLost() { |
| TRACE_EVENT0("viz", "DisplayScheduler::OutputSurfaceLost"); |
| output_surface_lost_ = true; |
| ScheduleBeginFrameDeadline(); |
| } |
| |
| bool DisplayScheduler::DrawAndSwap() { |
| TRACE_EVENT0("viz", "DisplayScheduler::DrawAndSwap"); |
| DCHECK_LT(pending_swaps_, max_pending_swaps_); |
| DCHECK(!output_surface_lost_); |
| |
| bool success = client_->DrawAndSwap(); |
| if (!success) |
| return false; |
| |
| needs_draw_ = false; |
| return true; |
| } |
| |
| bool DisplayScheduler::OnBeginFrameDerivedImpl(const BeginFrameArgs& args) { |
| base::TimeTicks now = base::TimeTicks::Now(); |
| TRACE_EVENT2("viz", "DisplayScheduler::BeginFrame", "args", args.AsValue(), |
| "now", now); |
| |
| if (inside_surface_damaged_) { |
| // Repost this so that we don't run a missed BeginFrame on the same |
| // callstack. Otherwise we end up running unexpected scheduler actions |
| // immediately while inside some other action (such as submitting a |
| // CompositorFrame for a SurfaceFactory). |
| DCHECK_EQ(args.type, BeginFrameArgs::MISSED); |
| DCHECK(missed_begin_frame_task_.IsCancelled()); |
| missed_begin_frame_task_.Reset(base::BindOnce( |
| base::IgnoreResult(&DisplayScheduler::OnBeginFrameDerivedImpl), |
| // The CancelableCallback will not run after it is destroyed, which |
| // happens when |this| is destroyed. |
| base::Unretained(this), args)); |
| task_runner_->PostTask(FROM_HERE, missed_begin_frame_task_.callback()); |
| return true; |
| } |
| |
| // Save the |BeginFrameArgs| as the callback (missed_begin_frame_task_) can be |
| // destroyed if we StopObservingBeginFrames(), and it would take the |args| |
| // with it. Instead save the args and cancel the |missed_begin_frame_task_|. |
| BeginFrameArgs save_args = args; |
| // If we get another BeginFrame before a posted missed frame, just drop the |
| // missed frame. Also if this was the missed frame, drop the Callback inside |
| // it. |
| missed_begin_frame_task_.Cancel(); |
| |
| // 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_ = save_args; |
| current_begin_frame_args_.deadline -= |
| BeginFrameArgs::DefaultEstimatedParentDrawTime(); |
| inside_begin_frame_deadline_interval_ = true; |
| UpdateHasPendingSurfaces(); |
| ScheduleBeginFrameDeadline(); |
| |
| return true; |
| } |
| |
| void DisplayScheduler::SetNeedsOneBeginFrame() { |
| // If we are not currently observing BeginFrames because needs_draw_ is false, |
| // we will stop observing again after one BeginFrame in AttemptDrawAndSwap(). |
| StartObservingBeginFrames(); |
| } |
| |
| void DisplayScheduler::MaybeStartObservingBeginFrames() { |
| if (ShouldDraw()) |
| StartObservingBeginFrames(); |
| } |
| |
| void DisplayScheduler::StartObservingBeginFrames() { |
| if (!observing_begin_frame_source_) { |
| begin_frame_source_->AddObserver(this); |
| observing_begin_frame_source_ = true; |
| } |
| } |
| |
| void DisplayScheduler::StopObservingBeginFrames() { |
| if (observing_begin_frame_source_) { |
| begin_frame_source_->RemoveObserver(this); |
| observing_begin_frame_source_ = false; |
| |
| // A missed BeginFrame may be queued, so drop that too if we're going to |
| // stop listening. |
| missed_begin_frame_task_.Cancel(); |
| } |
| } |
| |
| bool DisplayScheduler::ShouldDraw() const { |
| // Note: When any of these cases becomes true, MaybeStartObservingBeginFrames |
| // must be called to ensure the draw will happen. |
| return needs_draw_ && !output_surface_lost_ && visible_ && |
| !root_frame_missing_; |
| } |
| |
| void DisplayScheduler::OnBeginFrameSourcePausedChanged(bool paused) { |
| // BeginFrameSources used with DisplayScheduler do not make use of this |
| // feature. |
| if (paused) |
| NOTIMPLEMENTED(); |
| } |
| |
| void DisplayScheduler::OnFirstSurfaceActivation( |
| const SurfaceInfo& surface_info) {} |
| |
| void DisplayScheduler::OnSurfaceActivated( |
| const SurfaceId& surface_id, |
| base::Optional<base::TimeDelta> duration) {} |
| |
| void DisplayScheduler::OnSurfaceDestroyed(const SurfaceId& surface_id) { |
| auto it = surface_states_.find(surface_id); |
| if (it == surface_states_.end()) |
| return; |
| surface_states_.erase(it); |
| if (UpdateHasPendingSurfaces()) |
| ScheduleBeginFrameDeadline(); |
| } |
| |
| bool DisplayScheduler::OnSurfaceDamaged(const SurfaceId& surface_id, |
| const BeginFrameAck& ack) { |
| bool damaged = client_->SurfaceDamaged(surface_id, ack); |
| ProcessSurfaceDamage(surface_id, ack, damaged); |
| |
| return damaged; |
| } |
| |
| void DisplayScheduler::OnSurfaceDiscarded(const SurfaceId& surface_id) { |
| client_->SurfaceDiscarded(surface_id); |
| } |
| |
| void DisplayScheduler::OnSurfaceDamageExpected(const SurfaceId& surface_id, |
| const BeginFrameArgs& args) { |
| TRACE_EVENT1("viz", "DisplayScheduler::SurfaceDamageExpected", "surface_id", |
| surface_id.ToString()); |
| // Insert a new state for the surface if we don't know of it yet. We don't use |
| // OnSurfaceCreated for this, because it may not be called if a |
| // CompositorFrameSinkSupport starts submitting frames to a different Display, |
| // but continues using the same Surface, or if a Surface does not activate its |
| // first CompositorFrame immediately. |
| surface_states_[surface_id].last_args = args; |
| if (UpdateHasPendingSurfaces()) |
| ScheduleBeginFrameDeadline(); |
| } |
| |
| base::TimeTicks DisplayScheduler::DesiredBeginFrameDeadlineTime() const { |
| switch (AdjustedBeginFrameDeadlineMode()) { |
| case BeginFrameDeadlineMode::kImmediate: |
| return base::TimeTicks(); |
| case BeginFrameDeadlineMode::kRegular: |
| return current_begin_frame_args_.deadline; |
| case BeginFrameDeadlineMode::kLate: |
| return current_begin_frame_args_.frame_time + |
| current_begin_frame_args_.interval; |
| case BeginFrameDeadlineMode::kNone: |
| return base::TimeTicks::Max(); |
| default: |
| NOTREACHED(); |
| return base::TimeTicks(); |
| } |
| } |
| |
| DisplayScheduler::BeginFrameDeadlineMode |
| DisplayScheduler::AdjustedBeginFrameDeadlineMode() const { |
| BeginFrameDeadlineMode mode = DesiredBeginFrameDeadlineMode(); |
| |
| // In blocking mode, late and regular deadline should not apply. Wait |
| // indefinitely instead. |
| if (wait_for_all_surfaces_before_draw_ && |
| (mode == BeginFrameDeadlineMode::kRegular || |
| mode == BeginFrameDeadlineMode::kLate)) { |
| return BeginFrameDeadlineMode::kNone; |
| } |
| |
| return mode; |
| } |
| |
| DisplayScheduler::BeginFrameDeadlineMode |
| DisplayScheduler::DesiredBeginFrameDeadlineMode() const { |
| if (output_surface_lost_) { |
| TRACE_EVENT_INSTANT0("viz", "Lost output surface", |
| TRACE_EVENT_SCOPE_THREAD); |
| return BeginFrameDeadlineMode::kImmediate; |
| } |
| |
| if (pending_swaps_ >= max_pending_swaps_) { |
| TRACE_EVENT_INSTANT0("viz", "Swap throttled", TRACE_EVENT_SCOPE_THREAD); |
| return BeginFrameDeadlineMode::kLate; |
| } |
| |
| if (root_frame_missing_) { |
| TRACE_EVENT_INSTANT0("viz", "Root frame missing", TRACE_EVENT_SCOPE_THREAD); |
| return BeginFrameDeadlineMode::kLate; |
| } |
| |
| bool all_surfaces_ready = !has_pending_surfaces_ && |
| root_surface_id_.is_valid() && |
| !expecting_root_surface_damage_because_of_resize_; |
| |
| // When no draw is needed, only allow an early deadline in full-pipe mode. |
| // This way, we can unblock the BeginFrame in full-pipe mode if no draw is |
| // necessary, but accommodate damage as a result of missed BeginFrames from |
| // clients otherwise. |
| bool allow_early_deadline_without_draw = wait_for_all_surfaces_before_draw_; |
| |
| if (all_surfaces_ready && |
| (needs_draw_ || allow_early_deadline_without_draw)) { |
| TRACE_EVENT_INSTANT0("viz", "All active surfaces ready", |
| TRACE_EVENT_SCOPE_THREAD); |
| return BeginFrameDeadlineMode::kImmediate; |
| } |
| |
| if (!needs_draw_) { |
| TRACE_EVENT_INSTANT0("viz", "No damage yet", TRACE_EVENT_SCOPE_THREAD); |
| return BeginFrameDeadlineMode::kLate; |
| } |
| |
| // TODO(mithro): Be smarter about resize deadlines. |
| if (expecting_root_surface_damage_because_of_resize_) { |
| TRACE_EVENT_INSTANT0("viz", "Entire display damaged", |
| TRACE_EVENT_SCOPE_THREAD); |
| return BeginFrameDeadlineMode::kLate; |
| } |
| |
| TRACE_EVENT_INSTANT0("viz", "More damage expected soon", |
| TRACE_EVENT_SCOPE_THREAD); |
| return BeginFrameDeadlineMode::kRegular; |
| } |
| |
| void DisplayScheduler::ScheduleBeginFrameDeadline() { |
| TRACE_EVENT0("viz", "DisplayScheduler::ScheduleBeginFrameDeadline"); |
| |
| // We need to wait for the next BeginFrame before scheduling a deadline. |
| if (!inside_begin_frame_deadline_interval_) { |
| TRACE_EVENT_INSTANT0("viz", "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("viz", "Using existing deadline", |
| TRACE_EVENT_SCOPE_THREAD); |
| return; |
| } |
| |
| // Schedule the deadline. |
| begin_frame_deadline_task_time_ = desired_deadline; |
| begin_frame_deadline_task_.Cancel(); |
| |
| if (begin_frame_deadline_task_time_ == base::TimeTicks::Max()) { |
| TRACE_EVENT_INSTANT0("viz", "Using infinite deadline", |
| TRACE_EVENT_SCOPE_THREAD); |
| return; |
| } |
| |
| 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("viz", "Using new deadline", "delta", delta.ToInternalValue(), |
| "desired_deadline", desired_deadline); |
| } |
| |
| bool DisplayScheduler::AttemptDrawAndSwap() { |
| inside_begin_frame_deadline_interval_ = false; |
| begin_frame_deadline_task_.Cancel(); |
| begin_frame_deadline_task_time_ = base::TimeTicks(); |
| |
| if (ShouldDraw()) { |
| if (pending_swaps_ < max_pending_swaps_) |
| return DrawAndSwap(); |
| } else { |
| // We are going idle, so reset expectations. |
| // TODO(eseckler): Should we avoid going idle if |
| // |expecting_root_surface_damage_because_of_resize_| is true? |
| expecting_root_surface_damage_because_of_resize_ = false; |
| |
| StopObservingBeginFrames(); |
| } |
| return false; |
| } |
| |
| void DisplayScheduler::OnBeginFrameDeadline() { |
| TRACE_EVENT0("viz", "DisplayScheduler::OnBeginFrameDeadline"); |
| DCHECK(inside_begin_frame_deadline_interval_); |
| |
| bool did_draw = AttemptDrawAndSwap(); |
| DidFinishFrame(did_draw); |
| } |
| |
| void DisplayScheduler::DidFinishFrame(bool did_draw) { |
| DCHECK(begin_frame_source_); |
| begin_frame_source_->DidFinishFrame(this); |
| BeginFrameAck ack(current_begin_frame_args_, did_draw); |
| client_->DidFinishFrame(ack); |
| } |
| |
| void DisplayScheduler::DidSwapBuffers() { |
| pending_swaps_++; |
| if (pending_swaps_ == max_pending_swaps_) |
| begin_frame_source_->SetIsGpuBusy(true); |
| |
| uint32_t swap_id = next_swap_id_++; |
| TRACE_EVENT_ASYNC_BEGIN0("viz", "DisplayScheduler:pending_swaps", swap_id); |
| } |
| |
| void DisplayScheduler::DidReceiveSwapBuffersAck() { |
| begin_frame_source_->SetIsGpuBusy(false); |
| |
| uint32_t swap_id = next_swap_id_ - pending_swaps_; |
| pending_swaps_--; |
| TRACE_EVENT_ASYNC_END0("viz", "DisplayScheduler:pending_swaps", swap_id); |
| ScheduleBeginFrameDeadline(); |
| } |
| |
| } // namespace viz |