diff --git a/shell/platform/fuchsia/flutter/vsync_waiter.cc b/shell/platform/fuchsia/flutter/vsync_waiter.cc index d39a114693233..ca390fdd983e2 100644 --- a/shell/platform/fuchsia/flutter/vsync_waiter.cc +++ b/shell/platform/fuchsia/flutter/vsync_waiter.cc @@ -4,9 +4,14 @@ #include "vsync_waiter.h" +#include + #include + +#include "flutter/fml/logging.h" #include "flutter/fml/make_copyable.h" #include "flutter/fml/synchronization/waitable_event.h" +#include "flutter/fml/time/time_delta.h" #include "flutter/fml/trace_event.h" #include "vsync_recorder.h" @@ -61,14 +66,61 @@ VsyncWaiter::~VsyncWaiter() { ui_latch.Wait(); } -static fml::TimePoint SnapToNextPhase(fml::TimePoint value, - fml::TimePoint phase, - fml::TimeDelta interval) { - fml::TimeDelta offset = (phase - value) % interval; - if (offset < fml::TimeDelta::Zero()) { - offset = offset + interval; +/// Returns the system time at which the next frame is likely to be presented. +/// +/// Consider the following scenarios, where in both the +/// scenarious the result will be the same. +/// +/// Scenario 1: +/// presentation_interval is 2 +/// ^ ^ ^ ^ ^ +/// + + + + + +/// 0--1--2--3--4--5--6--7--8--9-- +/// + + + +/// | | +---------> result: next_presentation_time +/// | v +/// v now +/// last_presentation_time +/// +/// Scenario 2: +/// presentation_interval is 2 +/// ^ ^ ^ ^ ^ +/// + + + + + +/// 0--1--2--3--4--5--6--7--8--9-- +/// + + + +/// | | +--------->result: next_presentation_time +/// | | +/// | +>now +/// | +/// +->last_presentation_time +fml::TimePoint VsyncWaiter::SnapToNextPhase( + const fml::TimePoint now, + const fml::TimePoint last_frame_presentation_time, + const fml::TimeDelta presentation_interval) { + if (presentation_interval <= fml::TimeDelta::Zero()) { + FML_LOG(ERROR) << "Presentation interval must be positive. The value was: " + << presentation_interval.ToMilliseconds() << "ms."; + return now; + } + + if (last_frame_presentation_time >= now) { + FML_LOG(ERROR) + << "Last frame was presented in the future. Clamping to now."; + return now + presentation_interval; + } + + const fml::TimeDelta time_since_last_presentation = + now - last_frame_presentation_time; + // this will be the most likely scenario if we are rendering at a good + // frame rate, short circuiting the other checks in this case. + if (time_since_last_presentation < presentation_interval) { + return last_frame_presentation_time + presentation_interval; + } else { + const int64_t num_phases_passed = + (time_since_last_presentation / presentation_interval); + return last_frame_presentation_time + + (presentation_interval * (num_phases_passed + 1)); } - return value + offset; } void VsyncWaiter::AwaitVSync() { diff --git a/shell/platform/fuchsia/flutter/vsync_waiter.h b/shell/platform/fuchsia/flutter/vsync_waiter.h index ba1edea09e7d7..96b1cf4cd364c 100644 --- a/shell/platform/fuchsia/flutter/vsync_waiter.h +++ b/shell/platform/fuchsia/flutter/vsync_waiter.h @@ -18,6 +18,11 @@ class VsyncWaiter final : public flutter::VsyncWaiter { public: static constexpr zx_signals_t SessionPresentSignal = ZX_EVENT_SIGNALED; + static fml::TimePoint SnapToNextPhase( + const fml::TimePoint now, + const fml::TimePoint last_frame_presentation_time, + const fml::TimeDelta presentation_interval); + VsyncWaiter(std::string debug_label, zx_handle_t session_present_handle, flutter::TaskRunners task_runners); diff --git a/shell/platform/fuchsia/flutter/vsync_waiter_unittests.cc b/shell/platform/fuchsia/flutter/vsync_waiter_unittests.cc index 5f00940e475b3..f1bcfc12ba350 100644 --- a/shell/platform/fuchsia/flutter/vsync_waiter_unittests.cc +++ b/shell/platform/fuchsia/flutter/vsync_waiter_unittests.cc @@ -10,6 +10,8 @@ #include #include "flutter/fml/synchronization/waitable_event.h" +#include "flutter/fml/time/time_delta.h" +#include "flutter/fml/time/time_point.h" #include "flutter/shell/common/thread_host.h" #include "flutter/shell/common/vsync_waiter.h" #include "flutter/shell/platform/fuchsia/flutter/task_runner_adapter.h" @@ -85,4 +87,47 @@ TEST_F(VsyncWaiterTest, AwaitVsync) { } } +TEST_F(VsyncWaiterTest, SnapToNextPhaseOverlapsWithNow) { + const auto now = fml::TimePoint::Now(); + const auto last_presentation_time = now - fml::TimeDelta::FromNanoseconds(10); + const auto delta = fml::TimeDelta::FromNanoseconds(10); + const auto next_vsync = flutter_runner::VsyncWaiter::SnapToNextPhase( + now, last_presentation_time, delta); + + EXPECT_EQ(now + delta, next_vsync); +} + +TEST_F(VsyncWaiterTest, SnapToNextPhaseAfterNow) { + const auto now = fml::TimePoint::Now(); + const auto last_presentation_time = now - fml::TimeDelta::FromNanoseconds(9); + const auto delta = fml::TimeDelta::FromNanoseconds(10); + const auto next_vsync = flutter_runner::VsyncWaiter::SnapToNextPhase( + now, last_presentation_time, delta); + + // math here: 10 - 9 = 1 + EXPECT_EQ(now + fml::TimeDelta::FromNanoseconds(1), next_vsync); +} + +TEST_F(VsyncWaiterTest, SnapToNextPhaseAfterNowMultiJump) { + const auto now = fml::TimePoint::Now(); + const auto last_presentation_time = now - fml::TimeDelta::FromNanoseconds(34); + const auto delta = fml::TimeDelta::FromNanoseconds(10); + const auto next_vsync = flutter_runner::VsyncWaiter::SnapToNextPhase( + now, last_presentation_time, delta); + + // zeroes: -34, -24, -14, -4, 6, ... + EXPECT_EQ(now + fml::TimeDelta::FromNanoseconds(6), next_vsync); +} + +TEST_F(VsyncWaiterTest, SnapToNextPhaseAfterNowMultiJumpAccountForCeils) { + const auto now = fml::TimePoint::Now(); + const auto last_presentation_time = now - fml::TimeDelta::FromNanoseconds(20); + const auto delta = fml::TimeDelta::FromNanoseconds(16); + const auto next_vsync = flutter_runner::VsyncWaiter::SnapToNextPhase( + now, last_presentation_time, delta); + + // zeroes: -20, -4, 12, 28, ... + EXPECT_EQ(now + fml::TimeDelta::FromNanoseconds(12), next_vsync); +} + } // namespace flutter_runner_test