| // 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 "services/gfx/compositor/backend/vsync_scheduler.h" |
| |
| #include <algorithm> |
| |
| #include "base/bind.h" |
| #include "base/location.h" |
| #include "base/logging.h" |
| #include "base/time/time.h" |
| #include "base/trace_event/trace_event.h" |
| |
| namespace compositor { |
| |
| constexpr int64_t VsyncScheduler::kMinVsyncInterval; |
| constexpr int64_t VsyncScheduler::kMaxVsyncInterval; |
| |
| VsyncScheduler::VsyncScheduler( |
| const scoped_refptr<base::TaskRunner>& task_runner, |
| const SchedulerCallbacks& callbacks) |
| : VsyncScheduler(task_runner, callbacks, base::Bind(&MojoGetTimeTicksNow)) { |
| } |
| |
| VsyncScheduler::VsyncScheduler( |
| const scoped_refptr<base::TaskRunner>& task_runner, |
| const SchedulerCallbacks& callbacks, |
| const Clock& clock) |
| : state_(std::make_shared<State>(task_runner, callbacks, clock)) {} |
| |
| VsyncScheduler::~VsyncScheduler() {} |
| |
| void VsyncScheduler::ScheduleFrame(SchedulingMode scheduling_mode) { |
| state_->ScheduleFrame(scheduling_mode); |
| } |
| |
| VsyncScheduler::State::State(const scoped_refptr<base::TaskRunner>& task_runner, |
| const SchedulerCallbacks& callbacks, |
| const Clock& clock) |
| : task_runner_(task_runner), callbacks_(callbacks), clock_(clock) {} |
| |
| VsyncScheduler::State::~State() {} |
| |
| bool VsyncScheduler::State::Start(int64_t vsync_timebase, |
| int64_t vsync_interval, |
| int64_t update_phase, |
| int64_t snapshot_phase, |
| int64_t presentation_phase) { |
| // Be slightly paranoid. Timing glitches are hard to find and the |
| // vsync parameters will typically come from other services. |
| // Ensure vsync timing is anchored on actual observations from the past. |
| MojoTimeTicks now = GetTimeTicksNow(); |
| if (vsync_timebase > now) { |
| LOG(WARNING) << "Vsync timebase is in the future: vsync_timebase=" |
| << vsync_timebase << ", now=" << now; |
| return false; |
| } |
| if (vsync_interval < kMinVsyncInterval || |
| vsync_interval > kMaxVsyncInterval) { |
| LOG(WARNING) << "Vsync interval is invalid: vsync_interval=" |
| << vsync_interval << ", min=" << kMinVsyncInterval |
| << ", max=" << kMaxVsyncInterval; |
| return false; |
| } |
| if (snapshot_phase < update_phase || |
| snapshot_phase > update_phase + vsync_interval || |
| presentation_phase < snapshot_phase) { |
| // Updating and snapshotting must happen within the same frame interval |
| // to avoid having multiple updates in progress simultanteously (which |
| // doesn't make much sense if we're already compute bound). |
| LOG(WARNING) << "Vsync scheduling phases are invalid: update_phase=" |
| << update_phase << ", snapshot_phase=" << snapshot_phase |
| << ", presentation_phase=" << presentation_phase; |
| return false; |
| } |
| |
| { |
| std::lock_guard<std::mutex> lock(mutex_); |
| |
| // Suppress spurious updates. |
| if (running_ && vsync_timebase_ == vsync_timebase && |
| vsync_interval_ == vsync_interval && update_phase_ == update_phase && |
| snapshot_phase_ == snapshot_phase && |
| presentation_phase_ == presentation_phase) |
| return true; |
| |
| // Get running with these new parameters. |
| // Note that |last_delivered_update_time_| and |
| // |last_delivered_presentation_time_| are preserved. |
| running_ = true; |
| generation_++; // cancels pending undelivered callbacks |
| vsync_timebase_ = vsync_timebase; |
| vsync_interval_ = vsync_interval; |
| update_phase_ = update_phase; |
| snapshot_phase_ = snapshot_phase; |
| presentation_phase_ = presentation_phase; |
| need_update_ = true; |
| pending_dispatch_ = false; |
| ScheduleLocked(now); |
| return true; |
| } |
| } |
| |
| void VsyncScheduler::State::Stop() { |
| std::lock_guard<std::mutex> lock(mutex_); |
| running_ = false; |
| } |
| |
| void VsyncScheduler::State::ScheduleFrame(SchedulingMode scheduling_mode) { |
| MojoTimeTicks now = GetTimeTicksNow(); |
| |
| { |
| std::lock_guard<std::mutex> lock(mutex_); |
| if (running_) { |
| if (scheduling_mode == SchedulingMode::kUpdateAndSnapshot) |
| need_update_ = true; |
| ScheduleLocked(now); |
| } |
| } |
| } |
| |
| void VsyncScheduler::State::ScheduleLocked(MojoTimeTicks now) { |
| TRACE_EVENT2("gfx", "VsyncScheduler::ScheduleLocked", "pending_dispatch", |
| pending_dispatch_, "need_update", need_update_); |
| |
| DCHECK(running_); |
| DCHECK(now >= vsync_timebase_); |
| |
| if (pending_dispatch_) |
| return; |
| |
| // Determine the time of the earliest achievable frame snapshot in |
| // the near future. |
| int64_t snapshot_timebase = vsync_timebase_ + snapshot_phase_; |
| uint64_t snapshot_offset = (now - snapshot_timebase) % vsync_interval_; |
| int64_t snapshot_time = now - snapshot_offset + vsync_interval_; |
| DCHECK(snapshot_time >= now); |
| |
| // Determine when the update that produced this snapshot must have begun. |
| // This time may be in the past. |
| int64_t update_time = snapshot_time - snapshot_phase_ + update_phase_; |
| DCHECK(update_time <= snapshot_time); |
| int64_t presentation_time = |
| snapshot_time - snapshot_phase_ + presentation_phase_; |
| |
| // When changing vsync parameters, it's possible for the next update or |
| // presentation time to regress. Prevent applications from observing that |
| // by skipping frames if needed to preserve monotonicity. |
| if (update_time <= last_delivered_update_time_ || |
| presentation_time <= last_delivered_presentation_time_) { |
| int64_t delay = |
| std::max(last_delivered_update_time_ - update_time, |
| last_delivered_presentation_time_ - presentation_time); |
| int64_t frames = delay / vsync_interval_ + 1; |
| int64_t adjustment = frames * vsync_interval_; |
| update_time += adjustment; |
| snapshot_time += adjustment; |
| } |
| |
| // Schedule dispatching at that time. |
| if (update_time >= now) { |
| PostDispatchLocked(now, update_time, Action::kUpdate, update_time); |
| } else { |
| PostDispatchLocked(now, snapshot_time, Action::kEarlySnapshot, update_time); |
| } |
| |
| pending_dispatch_ = true; |
| } |
| |
| void VsyncScheduler::State::PostDispatchLocked(int64_t now, |
| int64_t delivery_time, |
| Action action, |
| int64_t update_time) { |
| TRACE_EVENT2("gfx", "VsyncScheduler::PostDispatchLocked", "delivery_time", |
| delivery_time, "update_time", update_time); |
| |
| task_runner_->PostDelayedTask( |
| FROM_HERE, |
| base::Bind(&VsyncScheduler::State::DispatchThunk, shared_from_this(), |
| generation_, action, update_time), |
| base::TimeDelta::FromMicroseconds( |
| std::max(delivery_time - now, static_cast<int64_t>(0)))); |
| } |
| |
| void VsyncScheduler::State::DispatchThunk( |
| const std::weak_ptr<State>& state_weak, |
| int32_t generation, |
| Action action, |
| int64_t update_time) { |
| std::shared_ptr<State> state = state_weak.lock(); |
| if (state) |
| state->Dispatch(generation, action, update_time); |
| } |
| |
| void VsyncScheduler::State::Dispatch(int32_t generation, |
| Action action, |
| int64_t update_time) { |
| TRACE_EVENT2("gfx", "VsyncScheduler::Dispatch", "action", |
| static_cast<int>(action), "update_time", update_time); |
| |
| MojoTimeTicks now = GetTimeTicksNow(); |
| DCHECK(update_time <= now); |
| |
| // Time may have passed since the callback was originally scheduled and |
| // it's possible that we completely missed the deadline we were aiming for. |
| // Reevaluate the schedule and jump ahead if necessary. |
| mojo::gfx::composition::FrameInfo frame_info; |
| { |
| std::lock_guard<std::mutex> lock(mutex_); |
| if (!running_ || generation_ != generation) |
| return; |
| |
| DCHECK(pending_dispatch_); |
| |
| // Check whether we missed any deadlines. |
| bool missed_deadline = false; |
| if (action == Action::kUpdate) { |
| int64_t update_deadline = update_time - update_phase_ + snapshot_phase_; |
| if (now > update_deadline) { |
| DLOG(WARNING) << "Compositor missed update deadline by " |
| << (now - update_deadline) << " us"; |
| missed_deadline = true; |
| } |
| } else { |
| int64_t snapshot_deadline = update_time + vsync_interval_; |
| if (now > snapshot_deadline) { |
| DLOG(WARNING) << "Compositor missed snapshot deadline by " |
| << (now - snapshot_deadline) << " us"; |
| missed_deadline = true; |
| } |
| } |
| if (missed_deadline) { |
| uint64_t offset = (now - update_time) % vsync_interval_; |
| update_time = now - offset; |
| DCHECK(update_time > now - vsync_interval_ && update_time <= now); |
| } |
| |
| // Schedule the corresponding snapshot for the update. |
| if (action == Action::kUpdate) { |
| int64_t snapshot_time = update_time - update_phase_ + snapshot_phase_; |
| PostDispatchLocked(now, snapshot_time, Action::kLateSnapshot, |
| update_time); |
| need_update_ = false; |
| } else if (need_update_) { |
| int64_t next_update_time = update_time + vsync_interval_; |
| PostDispatchLocked(now, next_update_time, Action::kUpdate, |
| next_update_time); |
| |
| // If we missed the deadline on an early snapshot, then just skip it |
| // and wait for the following update instead. |
| if (action == Action::kEarlySnapshot && missed_deadline) { |
| TRACE_EVENT_INSTANT0( |
| "gfx", "VsyncScheduler::StateDispatch Skipped early snapshot", |
| TRACE_EVENT_SCOPE_THREAD); |
| return; |
| } |
| } else { |
| pending_dispatch_ = false; |
| } |
| |
| SetFrameInfoLocked(&frame_info, update_time); |
| last_delivered_update_time_ = update_time; |
| last_delivered_presentation_time_ = frame_info.presentation_time; |
| } |
| |
| if (action == Action::kUpdate) { |
| callbacks_.update_callback.Run(frame_info); |
| } else { |
| callbacks_.snapshot_callback.Run(frame_info); |
| } |
| } |
| |
| void VsyncScheduler::State::SetFrameInfoLocked( |
| mojo::gfx::composition::FrameInfo* frame_info, |
| int64_t update_time) { |
| DCHECK(frame_info); |
| frame_info->frame_time = update_time; |
| frame_info->frame_interval = vsync_interval_; |
| frame_info->frame_deadline = update_time - update_phase_ + snapshot_phase_; |
| frame_info->presentation_time = |
| update_time - update_phase_ + presentation_phase_; |
| } |
| |
| } // namespace compositor |