Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions shell/platform/embedder/embedder_engine.cc
Original file line number Diff line number Diff line change
Expand Up @@ -229,8 +229,8 @@ bool EmbedderEngine::OnVsyncEvent(intptr_t baton,
return false;
}

return VsyncWaiterEmbedder::OnEmbedderVsync(baton, frame_start_time,
frame_target_time);
return VsyncWaiterEmbedder::OnEmbedderVsync(
task_runners_, baton, frame_start_time, frame_target_time);
}

bool EmbedderEngine::ReloadSystemFonts() {
Expand Down
7 changes: 7 additions & 0 deletions shell/platform/embedder/tests/embedder_config_builder.cc
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,13 @@ void EmbedderConfigBuilder::SetPlatformTaskRunner(
project_args_.custom_task_runners = &custom_task_runners_;
}

void EmbedderConfigBuilder::SetupVsyncCallback() {
project_args_.vsync_callback = [](void* user_data, intptr_t baton) {
auto context = reinterpret_cast<EmbedderTestContext*>(user_data);
context->RunVsyncCallback(baton);
};
}

void EmbedderConfigBuilder::SetRenderTaskRunner(
const FlutterTaskRunnerDescription* runner) {
if (runner == nullptr) {
Expand Down
4 changes: 4 additions & 0 deletions shell/platform/embedder/tests/embedder_config_builder.h
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,10 @@ class EmbedderConfigBuilder {

UniqueEngine InitializeEngine() const;

// Sets up the callback for vsync, the callbacks needs to be specified on the
// text context vis `SetVsyncCallback`.
void SetupVsyncCallback();

private:
EmbedderTestContext& context_;
FlutterProjectArgs project_args_ = {};
Expand Down
9 changes: 9 additions & 0 deletions shell/platform/embedder/tests/embedder_test_context.cc
Original file line number Diff line number Diff line change
Expand Up @@ -224,5 +224,14 @@ void EmbedderTestContext::FireRootSurfacePresentCallbackIfPresent(
callback(image_callback());
}

void EmbedderTestContext::SetVsyncCallback(
std::function<void(intptr_t)> callback) {
vsync_callback_ = callback;
}

void EmbedderTestContext::RunVsyncCallback(intptr_t baton) {
vsync_callback_(baton);
}

} // namespace testing
} // namespace flutter
10 changes: 10 additions & 0 deletions shell/platform/embedder/tests/embedder_test_context.h
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,15 @@ class EmbedderTestContext {

virtual EmbedderTestContextType GetContextType() const = 0;

// Sets up the callback for vsync. This callback will be invoked
// for every vsync. This should be used in conjunction with SetupVsyncCallback
// on the EmbedderConfigBuilder. Any callback setup here must call
// `FlutterEngineOnVsync` from the platform task runner.
void SetVsyncCallback(std::function<void(intptr_t)> callback);

// Runs the vsync callback.
void RunVsyncCallback(intptr_t baton);

// TODO(gw280): encapsulate these properly for subclasses to use
protected:
// This allows the builder to access the hooks.
Expand All @@ -112,6 +121,7 @@ class EmbedderTestContext {
std::unique_ptr<EmbedderTestCompositor> compositor_;
NextSceneCallback next_scene_callback_;
SkMatrix root_surface_transformation_;
std::function<void(intptr_t)> vsync_callback_ = nullptr;

static VoidCallback GetIsolateCreateCallbackHook();

Expand Down
72 changes: 71 additions & 1 deletion shell/platform/embedder/tests/embedder_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "fml/task_runner.h"
#define FML_USED_ON_EMBEDDER

#include <string>
Expand All @@ -18,7 +17,10 @@
#include "flutter/fml/paths.h"
#include "flutter/fml/synchronization/count_down_latch.h"
#include "flutter/fml/synchronization/waitable_event.h"
#include "flutter/fml/task_runner.h"
#include "flutter/fml/thread.h"
#include "flutter/fml/time/time_delta.h"
#include "flutter/fml/time/time_point.h"
#include "flutter/runtime/dart_vm.h"
#include "flutter/shell/platform/embedder/tests/embedder_assertions.h"
#include "flutter/shell/platform/embedder/tests/embedder_config_builder.h"
Expand All @@ -29,6 +31,16 @@
#include "third_party/skia/include/core/SkSurface.h"
#include "third_party/tonic/converter/dart_converter.h"

namespace {

static uint64_t NanosFromEpoch(int millis_from_now) {
const auto now = fml::TimePoint::Now();
const auto delta = fml::TimeDelta::FromMilliseconds(millis_from_now);
return (now + delta).ToEpochDelta().ToNanoseconds();
}

} // namespace

namespace flutter {
namespace testing {

Expand Down Expand Up @@ -1575,5 +1587,63 @@ TEST_F(EmbedderTest, BackToBackKeyEventResponsesCorrectlyInvoked) {
shutdown_latch.Wait();
}

// This test schedules a frame for the future and asserts that vsync waiter
// posts the event at the right frame start time (which is in the future).
TEST_F(EmbedderTest, VsyncCallbackPostedIntoFuture) {
UniqueEngine engine;
fml::AutoResetWaitableEvent present_latch;
fml::AutoResetWaitableEvent vsync_latch;

// One of the threads that the callback (FlutterEngineOnVsync) will be posted
// to is the platform thread. So we cannot wait for assertions to complete on
// the platform thread. Create a new thread to manage the engine instance and
// wait for assertions on the test thread.
auto platform_task_runner = CreateNewThread("platform_thread");

platform_task_runner->PostTask([&]() {
auto& context =
GetEmbedderContext(EmbedderTestContextType::kSoftwareContext);

context.SetVsyncCallback([&](intptr_t baton) {
platform_task_runner->PostTask([baton = baton, &engine, &vsync_latch]() {
FlutterEngineOnVsync(engine.get(), baton, NanosFromEpoch(16),
NanosFromEpoch(32));
vsync_latch.Signal();
});
});
context.AddNativeCallback(
"SignalNativeTest", CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
present_latch.Signal();
}));

EmbedderConfigBuilder builder(context);
builder.SetSoftwareRendererConfig();
builder.SetupVsyncCallback();
builder.SetDartEntrypoint("empty_scene");
engine = builder.LaunchEngine();
ASSERT_TRUE(engine.is_valid());

// Send a window metrics events so frames may be scheduled.
FlutterWindowMetricsEvent event = {};
event.struct_size = sizeof(event);
event.width = 800;
event.height = 600;
event.pixel_ratio = 1.0;

ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event),
kSuccess);
});

vsync_latch.Wait();
present_latch.Wait();

fml::AutoResetWaitableEvent shutdown_latch;
platform_task_runner->PostTask([&]() {
engine.reset();
shutdown_latch.Signal();
});
shutdown_latch.Wait();
}

} // namespace testing
} // namespace flutter
33 changes: 21 additions & 12 deletions shell/platform/embedder/vsync_waiter_embedder.cc
Original file line number Diff line number Diff line change
Expand Up @@ -17,26 +17,35 @@ VsyncWaiterEmbedder::~VsyncWaiterEmbedder() = default;
// |VsyncWaiter|
void VsyncWaiterEmbedder::AwaitVSync() {
auto* weak_waiter = new std::weak_ptr<VsyncWaiter>(shared_from_this());
vsync_callback_(reinterpret_cast<intptr_t>(weak_waiter));
intptr_t baton = reinterpret_cast<intptr_t>(weak_waiter);
vsync_callback_(baton);
}

// static
bool VsyncWaiterEmbedder::OnEmbedderVsync(intptr_t baton,
fml::TimePoint frame_start_time,
fml::TimePoint frame_target_time) {
bool VsyncWaiterEmbedder::OnEmbedderVsync(
const flutter::TaskRunners& task_runners,
intptr_t baton,
fml::TimePoint frame_start_time,
fml::TimePoint frame_target_time) {
if (baton == 0) {
return false;
}

auto* weak_waiter = reinterpret_cast<std::weak_ptr<VsyncWaiter>*>(baton);
auto strong_waiter = weak_waiter->lock();
delete weak_waiter;
// If the time here is in the future, the contract for `FlutterEngineOnVsync`
// says that the engine will only process the frame when the time becomes
// current.
task_runners.GetUITaskRunner()->PostTaskForTime(
[frame_start_time, frame_target_time, baton]() {
std::weak_ptr<VsyncWaiter>* weak_waiter =
reinterpret_cast<std::weak_ptr<VsyncWaiter>*>(baton);
auto vsync_waiter = weak_waiter->lock();
delete weak_waiter;
if (vsync_waiter) {
vsync_waiter->FireCallback(frame_start_time, frame_target_time);
}
},
frame_start_time);

if (!strong_waiter) {
return false;
}

strong_waiter->FireCallback(frame_start_time, frame_target_time);
return true;
}

Expand Down
3 changes: 2 additions & 1 deletion shell/platform/embedder/vsync_waiter_embedder.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ class VsyncWaiterEmbedder final : public VsyncWaiter {

~VsyncWaiterEmbedder() override;

static bool OnEmbedderVsync(intptr_t baton,
static bool OnEmbedderVsync(const flutter::TaskRunners& task_runners,
intptr_t baton,
fml::TimePoint frame_start_time,
fml::TimePoint frame_target_time);

Expand Down