Index: services/gfx/compositor/backend/vsync_scheduler.cc |
diff --git a/services/gfx/compositor/backend/vsync_scheduler.cc b/services/gfx/compositor/backend/vsync_scheduler.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..71be4f33a5f840fb8a8404a1643eae74b473a3d2 |
--- /dev/null |
+++ b/services/gfx/compositor/backend/vsync_scheduler.cc |
@@ -0,0 +1,273 @@ |
+// 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" |
+ |
+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) { |
+ DVLOG(1) << "Vsync timebase is in the future: vsync_timebase=" |
+ << vsync_timebase << ", now=" << now; |
+ return false; |
+ } |
+ if (vsync_interval < kMinVsyncInterval || |
+ vsync_interval > kMaxVsyncInterval) { |
+ DVLOG(1) << "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). |
+ DVLOG(1) << "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_| is 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) { |
+ DCHECK(running_); |
+ DCHECK(now >= vsync_timebase_); |
+ DVLOG(2) << "schedule: now=" << now << ", need_update_=" << need_update_ |
+ << ", last_delivered_update_time_=" << last_delivered_update_time_; |
+ 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); |
+ |
+ // When changing vsync parameters, it's possible for the next update time |
+ // to regress. Prevent applications from observing that. |
+ if (update_time <= last_delivered_update_time_) { |
+ int64_t frames = |
+ (last_delivered_update_time_ - update_time) / 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) { |
+ DVLOG(2) << "post: now=" << now << ", delivery_time=" << delivery_time |
+ << ", action=" << static_cast<int>(action) |
+ << ", 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) { |
+ 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) { |
+ LOG(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) { |
+ LOG(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); |
+ } |
+ |
+ DVLOG(2) << "dispatch: now=" << now |
+ << ", action=" << static_cast<int>(action) |
+ << ", update_time=" << update_time; |
+ |
+ // 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) { |
+ DVLOG(2) << "skip early snapshot"; |
+ return; |
+ } |
+ } else { |
+ pending_dispatch_ = false; |
+ } |
+ |
+ SetFrameInfoLocked(&frame_info, update_time); |
+ last_delivered_update_time_ = update_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 |