diff --git a/engine/src/flutter/ci/licenses_golden/licenses_flutter b/engine/src/flutter/ci/licenses_golden/licenses_flutter index d8b95216e1a..a56ee47e468 100644 --- a/engine/src/flutter/ci/licenses_golden/licenses_flutter +++ b/engine/src/flutter/ci/licenses_golden/licenses_flutter @@ -533,6 +533,7 @@ FILE: ../../../flutter/runtime/test_font_data.cc FILE: ../../../flutter/runtime/test_font_data.h FILE: ../../../flutter/shell/common/animator.cc FILE: ../../../flutter/shell/common/animator.h +FILE: ../../../flutter/shell/common/animator_unittests.cc FILE: ../../../flutter/shell/common/canvas_spy.cc FILE: ../../../flutter/shell/common/canvas_spy.h FILE: ../../../flutter/shell/common/canvas_spy_unittests.cc diff --git a/engine/src/flutter/fml/platform/linux/timerfd.cc b/engine/src/flutter/fml/platform/linux/timerfd.cc index f20ffc7c5ed..0dbcb89ad3d 100644 --- a/engine/src/flutter/fml/platform/linux/timerfd.cc +++ b/engine/src/flutter/fml/platform/linux/timerfd.cc @@ -34,7 +34,13 @@ namespace fml { #endif bool TimerRearm(int fd, fml::TimePoint time_point) { - const uint64_t nano_secs = time_point.ToEpochDelta().ToNanoseconds(); + uint64_t nano_secs = time_point.ToEpochDelta().ToNanoseconds(); + + // "0" will disarm the timer, desired behavior is to immediately + // trigger the timer. + if (nano_secs < 1) { + nano_secs = 1; + } struct itimerspec spec = {}; spec.it_value.tv_sec = (time_t)(nano_secs / NSEC_PER_SEC); diff --git a/engine/src/flutter/shell/common/BUILD.gn b/engine/src/flutter/shell/common/BUILD.gn index fcf9ce280bd..458cbb8ff4a 100644 --- a/engine/src/flutter/shell/common/BUILD.gn +++ b/engine/src/flutter/shell/common/BUILD.gn @@ -159,6 +159,7 @@ if (current_toolchain == host_toolchain) { shell_host_executable("shell_unittests") { sources = [ + "animator_unittests.cc", "canvas_spy_unittests.cc", "input_events_unittests.cc", "persistent_cache_unittests.cc", diff --git a/engine/src/flutter/shell/common/animator.cc b/engine/src/flutter/shell/common/animator.cc index 690c47d3839..5ae3862a93a 100644 --- a/engine/src/flutter/shell/common/animator.cc +++ b/engine/src/flutter/shell/common/animator.cc @@ -137,7 +137,7 @@ void Animator::BeginFrame(fml::TimePoint frame_start_time, { TRACE_EVENT2("flutter", "Framework Workload", "mode", "basic", "frame", FrameParity()); - delegate_.OnAnimatorBeginFrame(last_begin_frame_time_); + delegate_.OnAnimatorBeginFrame(frame_target_time); } if (!frame_scheduled_) { diff --git a/engine/src/flutter/shell/common/animator_unittests.cc b/engine/src/flutter/shell/common/animator_unittests.cc new file mode 100644 index 00000000000..12e3042317a --- /dev/null +++ b/engine/src/flutter/shell/common/animator_unittests.cc @@ -0,0 +1,95 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#define FML_USED_ON_EMBEDDER + +#include +#include +#include + +#include "flutter/shell/common/animator.h" +#include "flutter/shell/common/shell_test.h" +#include "flutter/testing/testing.h" +#include "gtest/gtest.h" + +namespace flutter { +namespace testing { + +TEST_F(ShellTest, VSyncTargetTime) { + // Add native callbacks to listen for window.onBeginFrame + int64_t target_time; + fml::AutoResetWaitableEvent on_target_time_latch; + auto nativeOnBeginFrame = [&on_target_time_latch, + &target_time](Dart_NativeArguments args) { + Dart_Handle exception = nullptr; + target_time = + tonic::DartConverter::FromArguments(args, 0, exception); + on_target_time_latch.Signal(); + }; + AddNativeCallback("NativeOnBeginFrame", + CREATE_NATIVE_ENTRY(nativeOnBeginFrame)); + + // Create all te prerequisites for a shell. + ASSERT_FALSE(DartVMRef::IsInstanceRunning()); + auto settings = CreateSettingsForFixture(); + + std::unique_ptr shell; + + TaskRunners task_runners = GetTaskRunnersForFixture(); + // this is not used as we are not using simulated events. + const auto vsync_clock = std::make_shared(); + CreateVsyncWaiter create_vsync_waiter = [&]() { + return static_cast>( + std::make_unique(task_runners)); + }; + + // create a shell with a constant firing vsync waiter. + fml::AutoResetWaitableEvent shell_creation; + + auto platform_task = std::async(std::launch::async, [&]() { + shell = Shell::Create( + task_runners, settings, + [vsync_clock, &create_vsync_waiter](Shell& shell) { + return std::make_unique( + shell, shell.GetTaskRunners(), vsync_clock, + std::move(create_vsync_waiter)); + }, + [](Shell& shell) { + return std::make_unique(shell, shell.GetTaskRunners()); + }); + ASSERT_TRUE(DartVMRef::IsInstanceRunning()); + + auto configuration = RunConfiguration::InferFromSettings(settings); + ASSERT_TRUE(configuration.IsValid()); + configuration.SetEntrypoint("onBeginFrameMain"); + + RunEngine(shell.get(), std::move(configuration)); + shell_creation.Signal(); + }); + + shell_creation.Wait(); + + // schedule a frame to trigger window.onBeginFrame + fml::TaskRunner::RunNowOrPostTask(shell->GetTaskRunners().GetUITaskRunner(), + [engine = shell->GetEngine()]() { + if (engine) { + // this implies we can re-use the last + // frame to trigger begin frame rather + // than re-generating the layer tree. + engine->ScheduleFrame(true); + } + }); + + on_target_time_latch.Wait(); + ASSERT_EQ(ConstantFiringVsyncWaiter::frame_target_time.ToEpochDelta() + .ToMicroseconds(), + target_time); + + // teardown. + DestroyShell(std::move(shell), std::move(task_runners)); + ASSERT_FALSE(DartVMRef::IsInstanceRunning()); +} + +} // namespace testing +} // namespace flutter diff --git a/engine/src/flutter/shell/common/shell_unittests.cc b/engine/src/flutter/shell/common/shell_unittests.cc index 8b7763bb896..d1e78eb6925 100644 --- a/engine/src/flutter/shell/common/shell_unittests.cc +++ b/engine/src/flutter/shell/common/shell_unittests.cc @@ -435,10 +435,10 @@ TEST_F(ShellTest, FrameRasterizedCallbackIsCalled) { auto configuration = RunConfiguration::InferFromSettings(settings); configuration.SetEntrypoint("onBeginFrameMain"); - int64_t begin_frame; - auto nativeOnBeginFrame = [&begin_frame](Dart_NativeArguments args) { + int64_t frame_target_time; + auto nativeOnBeginFrame = [&frame_target_time](Dart_NativeArguments args) { Dart_Handle exception = nullptr; - begin_frame = + frame_target_time = tonic::DartConverter::FromArguments(args, 0, exception); }; AddNativeCallback("NativeOnBeginFrame", @@ -455,10 +455,11 @@ TEST_F(ShellTest, FrameRasterizedCallbackIsCalled) { std::vector timings = {timing}; CheckFrameTimings(timings, start, finish); - // Check that onBeginFrame has the same timestamp as FrameTiming's build start + // Check that onBeginFrame, which is the frame_target_time, is after + // FrameTiming's build start int64_t build_start = timing.Get(FrameTiming::kBuildStart).ToEpochDelta().ToMicroseconds(); - ASSERT_EQ(build_start, begin_frame); + ASSERT_GT(frame_target_time, build_start); DestroyShell(std::move(shell)); } diff --git a/engine/src/flutter/shell/common/test_vsync_waiters.cc b/engine/src/flutter/shell/common/test_vsync_waiters.cc index 9f5f2de3289..b5437d01994 100644 --- a/engine/src/flutter/shell/common/test_vsync_waiters.cc +++ b/engine/src/flutter/shell/common/test_vsync_waiters.cc @@ -57,5 +57,13 @@ void ShellTestVsyncWaiter::AwaitVSync() { }); } +void ConstantFiringVsyncWaiter::AwaitVSync() { + FML_DCHECK(task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread()); + auto async_wait = std::async([this]() { + task_runners_.GetPlatformTaskRunner()->PostTask( + [this]() { FireCallback(frame_begin_time, frame_target_time); }); + }); +} + } // namespace testing } // namespace flutter diff --git a/engine/src/flutter/shell/common/test_vsync_waiters.h b/engine/src/flutter/shell/common/test_vsync_waiters.h index 861f517f3a5..8a9b81da359 100644 --- a/engine/src/flutter/shell/common/test_vsync_waiters.h +++ b/engine/src/flutter/shell/common/test_vsync_waiters.h @@ -41,6 +41,21 @@ class ShellTestVsyncWaiter : public VsyncWaiter { std::shared_ptr clock_; }; +class ConstantFiringVsyncWaiter : public VsyncWaiter { + public: + // both of these are set in the past so as to fire immediately. + static constexpr fml::TimePoint frame_begin_time = + fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromSeconds(0)); + static constexpr fml::TimePoint frame_target_time = + fml::TimePoint::FromEpochDelta(fml::TimeDelta::FromSeconds(100)); + + ConstantFiringVsyncWaiter(TaskRunners task_runners) + : VsyncWaiter(std::move(task_runners)) {} + + protected: + void AwaitVSync() override; +}; + } // namespace testing } // namespace flutter