Do not call Animator::Delegate::OnAnimatorNotifyIdle until at least one frame has been rendered. (flutter/engine#29015)

This commit is contained in:
Dan Field 2021-10-07 09:41:58 -07:00 committed by GitHub
parent b107c3700e
commit 074f1d0842
3 changed files with 111 additions and 5 deletions

View File

@ -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<flutter::LayerTree> 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,

View File

@ -117,6 +117,7 @@ class Animator final {
bool dimension_change_pending_ = false;
SkISize last_layer_tree_size_ = {0, 0};
std::deque<uint64_t> trace_flow_ids_;
bool has_rendered_ = false;
fml::WeakPtrFactory<Animator> weak_factory_;

View File

@ -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<flutter::LayerTree>> pipeline,
std::unique_ptr<FrameTimingsRecorder> frame_timings_recorder) override {}
void OnAnimatorDrawLastLayerTree(
std::unique_ptr<FrameTimingsRecorder> 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<ShellTestVsyncClock>();
fml::AutoResetWaitableEvent latch;
std::shared_ptr<Animator> 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::unique_ptr<VsyncWaiter>>(
std::make_unique<ShellTestVsyncWaiter>(task_runners, clock));
animator = std::make_unique<Animator>(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<LayerTree>(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