| // 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 <algorithm> |
| |
| #include "base/auto_reset.h" |
| #include "base/trace_event/trace_event.h" |
| |
| namespace viz { |
| |
| class DisplayScheduler::BeginFrameObserver : public BeginFrameObserverBase { |
| public: |
| explicit BeginFrameObserver(DisplayScheduler* scheduler) |
| : scheduler_(scheduler) { |
| // 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; |
| } |
| // BeginFrameObserverBase implementation. |
| void OnBeginFrameSourcePausedChanged(bool paused) override { |
| // TODO(1033847): DisplayScheduler doesn't handle BeginFrameSource pause but |
| // it can happen on WebXR. |
| if (paused) { |
| NOTIMPLEMENTED(); |
| } |
| } |
| |
| bool OnBeginFrameDerivedImpl(const BeginFrameArgs& args) override { |
| return scheduler_->OnBeginFrame(args); |
| } |
| |
| private: |
| DisplayScheduler* const scheduler_; |
| }; |
| |
| DisplayScheduler::DisplayScheduler(BeginFrameSource* begin_frame_source, |
| base::SingleThreadTaskRunner* task_runner, |
| int max_pending_swaps, |
| bool wait_for_all_surfaces_before_draw, |
| gfx::RenderingPipeline* gpu_pipeline) |
| : begin_frame_observer_(std::make_unique<BeginFrameObserver>(this)), |
| begin_frame_source_(begin_frame_source), |
| task_runner_(task_runner), |
| gpu_pipeline_(gpu_pipeline), |
| inside_surface_damaged_(false), |
| visible_(false), |
| output_surface_lost_(false), |
| inside_begin_frame_deadline_interval_(false), |
| needs_draw_(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) { |
| begin_frame_deadline_closure_ = base::BindRepeating( |
| &DisplayScheduler::OnBeginFrameDeadline, weak_ptr_factory_.GetWeakPtr()); |
| } |
| |
| 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::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::OnRootFrameMissing(bool missing) { |
| MaybeStartObservingBeginFrames(); |
| ScheduleBeginFrameDeadline(); |
| } |
| |
| void DisplayScheduler::OnDisplayDamaged(SurfaceId surface_id) { |
| // 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); |
| |
| needs_draw_ = true; |
| MaybeStartObservingBeginFrames(); |
| UpdateHasPendingSurfaces(); |
| ScheduleBeginFrameDeadline(); |
| } |
| |
| void DisplayScheduler::OnPendingSurfacesChanged() { |
| if (UpdateHasPendingSurfaces()) |
| 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); |
| } |
| |
| 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_; |
| has_pending_surfaces_ = |
| damage_tracker_->HasPendingSurfaces(current_begin_frame_args_); |
| return has_pending_surfaces_ != old_value; |
| } |
| |
| void DisplayScheduler::OutputSurfaceLost() { |
| TRACE_EVENT0("viz", "DisplayScheduler::OutputSurfaceLost"); |
| output_surface_lost_ = true; |
| ScheduleBeginFrameDeadline(); |
| } |
| |
| void DisplayScheduler::SetGpuLatency(base::TimeDelta gpu_latency) { |
| if (gpu_pipeline_) |
| gpu_pipeline_->SetGpuLatency(gpu_latency); |
| } |
| |
| bool DisplayScheduler::DrawAndSwap() { |
| TRACE_EVENT0("viz", "DisplayScheduler::DrawAndSwap"); |
| DCHECK_LT(pending_swaps_, max_pending_swaps_); |
| DCHECK(!output_surface_lost_); |
| |
| bool success = client_ && client_->DrawAndSwap(current_frame_display_time()); |
| if (!success) |
| return false; |
| |
| needs_draw_ = false; |
| return true; |
| } |
| |
| bool DisplayScheduler::OnBeginFrame(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::OnBeginFrame), |
| // The CancelableOnceCallback 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::DefaultEstimatedDisplayDrawTime(save_args.interval); |
| inside_begin_frame_deadline_interval_ = true; |
| if (gpu_pipeline_) |
| gpu_pipeline_->SetTargetDuration(save_args.interval); |
| |
| UpdateHasPendingSurfaces(); |
| ScheduleBeginFrameDeadline(); |
| |
| return true; |
| } |
| |
| void DisplayScheduler::SetNeedsOneBeginFrame(bool needs_draw) { |
| // If we are not currently observing BeginFrames because needs_draw_ is false, |
| // we will stop observing again after one BeginFrame in AttemptDrawAndSwap(). |
| StartObservingBeginFrames(); |
| if (needs_draw) |
| needs_draw_ = true; |
| } |
| |
| void DisplayScheduler::MaybeStartObservingBeginFrames() { |
| if (ShouldDraw()) |
| StartObservingBeginFrames(); |
| } |
| |
| void DisplayScheduler::StartObservingBeginFrames() { |
| if (!observing_begin_frame_source_) { |
| begin_frame_source_->AddObserver(begin_frame_observer_.get()); |
| observing_begin_frame_source_ = true; |
| if (gpu_pipeline_) |
| gpu_pipeline_active_.emplace(gpu_pipeline_); |
| } |
| } |
| |
| void DisplayScheduler::StopObservingBeginFrames() { |
| if (observing_begin_frame_source_) { |
| begin_frame_source_->RemoveObserver(begin_frame_observer_.get()); |
| observing_begin_frame_source_ = false; |
| gpu_pipeline_active_.reset(); |
| |
| // 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_ && |
| !damage_tracker_->root_frame_missing(); |
| } |
| |
| 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 (damage_tracker_->root_frame_missing()) { |
| TRACE_EVENT_INSTANT0("viz", "Root frame missing", TRACE_EVENT_SCOPE_THREAD); |
| return BeginFrameDeadlineMode::kLate; |
| } |
| |
| bool all_surfaces_ready = |
| !has_pending_surfaces_ && damage_tracker_->IsRootSurfaceValid() && |
| !damage_tracker_->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 (damage_tracker_->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? |
| damage_tracker_->reset_expecting_root_surface_damage_because_of_resize(); |
| |
| StopObservingBeginFrames(); |
| } |
| return false; |
| } |
| |
| void DisplayScheduler::OnBeginFrameDeadline() { |
| TRACE_EVENT0("viz", "DisplayScheduler::OnBeginFrameDeadline"); |
| DCHECK(inside_begin_frame_deadline_interval_); |
| |
| bool did_draw = AttemptDrawAndSwap(); |
| DidFinishFrame(did_draw); |
| if (gpu_pipeline_) |
| gpu_pipeline_->NotifyFrameFinished(); |
| } |
| |
| void DisplayScheduler::DidFinishFrame(bool did_draw) { |
| DCHECK(begin_frame_source_); |
| begin_frame_source_->DidFinishFrame(begin_frame_observer_.get()); |
| BeginFrameAck ack(current_begin_frame_args_, did_draw); |
| if (client_) |
| 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() { |
| uint32_t swap_id = next_swap_id_ - pending_swaps_; |
| pending_swaps_--; |
| |
| // It is important to call this after updating |pending_swaps_| above to |
| // ensure any callback from BeginFrameSource observes the correct swap |
| // throttled state. |
| begin_frame_source_->SetIsGpuBusy(false); |
| TRACE_EVENT_ASYNC_END0("viz", "DisplayScheduler:pending_swaps", swap_id); |
| ScheduleBeginFrameDeadline(); |
| } |
| |
| } // namespace viz |