blob: 0b11d06c8929f48697233c4e51895fdff64e848b [file] [log] [blame]
// 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 "flutter/shell/common/animator.h"
#include <functional>
#include <future>
#include <memory>
#include "flutter/shell/common/shell_test.h"
#include "flutter/shell/common/shell_test_platform_view.h"
#include "flutter/testing/testing.h"
#include "gtest/gtest.h"
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;
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<int64_t>::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> shell;
TaskRunners task_runners = GetTaskRunnersForFixture();
// this is not used as we are not using simulated events.
const auto vsync_clock = std::make_shared<ShellTestVsyncClock>();
CreateVsyncWaiter create_vsync_waiter = [&]() {
return static_cast<std::unique_ptr<VsyncWaiter>>(
std::make_unique<ConstantFiringVsyncWaiter>(task_runners));
};
// create a shell with a constant firing vsync waiter.
auto platform_task = std::async(std::launch::async, [&]() {
fml::MessageLoop::EnsureInitializedForCurrentThread();
shell = Shell::Create(
flutter::PlatformData(), task_runners, settings,
[vsync_clock, &create_vsync_waiter](Shell& shell) {
return ShellTestPlatformView::Create(
shell, shell.GetTaskRunners(), vsync_clock,
std::move(create_vsync_waiter),
ShellTestPlatformView::BackendType::kDefaultBackend, nullptr);
},
[](Shell& shell) { return std::make_unique<Rasterizer>(shell); });
ASSERT_TRUE(DartVMRef::IsInstanceRunning());
auto configuration = RunConfiguration::InferFromSettings(settings);
ASSERT_TRUE(configuration.IsValid());
configuration.SetEntrypoint("onBeginFrameMain");
RunEngine(shell.get(), std::move(configuration));
});
platform_task.wait();
// schedule a frame to trigger window.onBeginFrame
fml::TaskRunner::RunNowOrPostTask(shell->GetTaskRunners().GetUITaskRunner(),
[engine = shell->GetEngine()]() {
if (engine) {
// Engine needs a surface for frames to
// be scheduled.
engine->OnOutputSurfaceCreated();
// 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();
const auto vsync_waiter_target_time =
ConstantFiringVsyncWaiter::frame_target_time;
ASSERT_EQ(vsync_waiter_target_time.ToEpochDelta().ToMicroseconds(),
target_time);
// validate that the latest target time has also been updated.
ASSERT_EQ(GetLatestFrameTargetTime(shell.get()), vsync_waiter_target_time);
// teardown.
DestroyShell(std::move(shell), std::move(task_runners));
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
}
TEST_F(ShellTest, AnimatorStartsPaused) {
// Create all te prerequisites for a shell.
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
auto settings = CreateSettingsForFixture();
TaskRunners task_runners = GetTaskRunnersForFixture();
auto shell = CreateShell(std::move(settings), task_runners,
/* simulate_vsync=*/true);
ASSERT_TRUE(DartVMRef::IsInstanceRunning());
auto configuration = RunConfiguration::InferFromSettings(settings);
ASSERT_TRUE(configuration.IsValid());
configuration.SetEntrypoint("emptyMain");
RunEngine(shell.get(), std::move(configuration));
ASSERT_FALSE(IsAnimatorRunning(shell.get()));
// teardown.
DestroyShell(std::move(shell), std::move(task_runners));
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