diff --git a/shell/common/animator.cc b/shell/common/animator.cc index d0f91bcaae8f4..32403f1c2e70a 100644 --- a/shell/common/animator.cc +++ b/shell/common/animator.cc @@ -147,7 +147,7 @@ void Animator::BeginFrame( delegate_.OnAnimatorBeginFrame(frame_target_time, frame_number); } - if (!frame_scheduled_) { + if (!frame_scheduled_ && has_rendered_) { // Under certain workloads (such as our parent view resizing us, which is // communicated to us by repeat viewport metrics events), we won't // actually have a frame scheduled yet, despite the fact that we *will* be @@ -177,6 +177,7 @@ void Animator::BeginFrame( } void Animator::Render(std::unique_ptr layer_tree) { + has_rendered_ = true; if (dimension_change_pending_ && layer_tree->frame_size() != last_layer_tree_size_) { dimension_change_pending_ = false; @@ -268,9 +269,10 @@ void Animator::AwaitVSync() { } } }); - - delegate_.OnAnimatorNotifyIdle( - dart_frame_deadline_.ToEpochDelta().ToMicroseconds()); + if (has_rendered_) { + delegate_.OnAnimatorNotifyIdle( + dart_frame_deadline_.ToEpochDelta().ToMicroseconds()); + } } void Animator::ScheduleSecondaryVsyncCallback(uintptr_t id, diff --git a/shell/common/animator.h b/shell/common/animator.h index 544c6c77ba370..85f940700dbc6 100644 --- a/shell/common/animator.h +++ b/shell/common/animator.h @@ -117,6 +117,7 @@ class Animator final { bool dimension_change_pending_ = false; SkISize last_layer_tree_size_ = {0, 0}; std::deque trace_flow_ids_; + bool has_rendered_ = false; fml::WeakPtrFactory weak_factory_; diff --git a/shell/common/animator_unittests.cc b/shell/common/animator_unittests.cc index 34c237ea93182..0b11d06c8929f 100644 --- a/shell/common/animator_unittests.cc +++ b/shell/common/animator_unittests.cc @@ -18,6 +18,25 @@ namespace flutter { namespace testing { +class FakeAnimatorDelegate : public Animator::Delegate { + public: + void OnAnimatorBeginFrame(fml::TimePoint frame_target_time, + uint64_t frame_number) override {} + + void OnAnimatorNotifyIdle(int64_t deadline) override { + notify_idle_called_ = true; + } + + void OnAnimatorDraw( + std::shared_ptr> pipeline, + std::unique_ptr frame_timings_recorder) override {} + + void OnAnimatorDrawLastLayerTree( + std::unique_ptr frame_timings_recorder) override {} + + bool notify_idle_called_ = false; +}; + TEST_F(ShellTest, VSyncTargetTime) { // Add native callbacks to listen for window.onBeginFrame int64_t target_time; @@ -103,7 +122,8 @@ TEST_F(ShellTest, AnimatorStartsPaused) { auto settings = CreateSettingsForFixture(); TaskRunners task_runners = GetTaskRunnersForFixture(); - auto shell = CreateShell(std::move(settings), task_runners); + auto shell = CreateShell(std::move(settings), task_runners, + /* simulate_vsync=*/true); ASSERT_TRUE(DartVMRef::IsInstanceRunning()); auto configuration = RunConfiguration::InferFromSettings(settings); @@ -120,5 +140,88 @@ TEST_F(ShellTest, AnimatorStartsPaused) { ASSERT_FALSE(DartVMRef::IsInstanceRunning()); } +TEST_F(ShellTest, AnimatorDoesNotNotifyIdleBeforeRender) { + FakeAnimatorDelegate delegate; + TaskRunners task_runners = { + "test", + CreateNewThread(), // platform + CreateNewThread(), // raster + CreateNewThread(), // ui + CreateNewThread() // io + }; + + auto clock = std::make_shared(); + fml::AutoResetWaitableEvent latch; + std::shared_ptr animator; + + auto flush_vsync_task = [&] { + fml::AutoResetWaitableEvent ui_latch; + task_runners.GetUITaskRunner()->PostTask([&] { ui_latch.Signal(); }); + do { + clock->SimulateVSync(); + } while (ui_latch.WaitWithTimeout(fml::TimeDelta::FromMilliseconds(1))); + latch.Signal(); + }; + + // Create the animator on the UI task runner. + task_runners.GetUITaskRunner()->PostTask([&] { + auto vsync_waiter = static_cast>( + std::make_unique(task_runners, clock)); + animator = std::make_unique(delegate, task_runners, + std::move(vsync_waiter)); + latch.Signal(); + }); + latch.Wait(); + + // Validate it has not notified idle and start it. This will request a frame. + task_runners.GetUITaskRunner()->PostTask([&] { + ASSERT_FALSE(delegate.notify_idle_called_); + animator->Start(); + // Immediately request a frame saying it can reuse the last layer tree to + // avoid more calls to BeginFrame by the animator. + animator->RequestFrame(false); + task_runners.GetPlatformTaskRunner()->PostTask(flush_vsync_task); + }); + latch.Wait(); + ASSERT_FALSE(delegate.notify_idle_called_); + + // Validate it has not notified idle and try to render. + task_runners.GetUITaskRunner()->PostDelayedTask( + [&] { + ASSERT_FALSE(delegate.notify_idle_called_); + auto layer_tree = + std::make_unique(SkISize::Make(600, 800), 1.0); + animator->Render(std::move(layer_tree)); + task_runners.GetPlatformTaskRunner()->PostTask(flush_vsync_task); + }, + // See kNotifyIdleTaskWaitTime in animator.cc. + fml::TimeDelta::FromMilliseconds(60)); + latch.Wait(); + + // Still hasn't notified idle because there has been no frame request. + task_runners.GetUITaskRunner()->PostTask([&] { + ASSERT_FALSE(delegate.notify_idle_called_); + // False to avoid getting cals to BeginFrame that will request more frames + // before we are ready. + animator->RequestFrame(false); + task_runners.GetPlatformTaskRunner()->PostTask(flush_vsync_task); + }); + latch.Wait(); + + // Now it should notify idle. Make sure it is destroyed on the UI thread. + ASSERT_TRUE(delegate.notify_idle_called_); + + // Stop and do one more flush so we can safely clean up on the UI thread. + animator->Stop(); + task_runners.GetPlatformTaskRunner()->PostTask(flush_vsync_task); + latch.Wait(); + + task_runners.GetUITaskRunner()->PostTask([&] { + animator.reset(); + latch.Signal(); + }); + latch.Wait(); +} + } // namespace testing } // namespace flutter