Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 43561d8

Browse files
authored
Embedder VsyncWaiter must schedule a frame for the right time (#29408)
1 parent 486dee3 commit 43561d8

File tree

8 files changed

+123
-16
lines changed

8 files changed

+123
-16
lines changed

shell/platform/embedder/embedder_engine.cc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -229,8 +229,8 @@ bool EmbedderEngine::OnVsyncEvent(intptr_t baton,
229229
return false;
230230
}
231231

232-
return VsyncWaiterEmbedder::OnEmbedderVsync(baton, frame_start_time,
233-
frame_target_time);
232+
return VsyncWaiterEmbedder::OnEmbedderVsync(
233+
task_runners_, baton, frame_start_time, frame_target_time);
234234
}
235235

236236
bool EmbedderEngine::ReloadSystemFonts() {

shell/platform/embedder/tests/embedder_config_builder.cc

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,13 @@ void EmbedderConfigBuilder::SetPlatformTaskRunner(
261261
project_args_.custom_task_runners = &custom_task_runners_;
262262
}
263263

264+
void EmbedderConfigBuilder::SetupVsyncCallback() {
265+
project_args_.vsync_callback = [](void* user_data, intptr_t baton) {
266+
auto context = reinterpret_cast<EmbedderTestContext*>(user_data);
267+
context->RunVsyncCallback(baton);
268+
};
269+
}
270+
264271
void EmbedderConfigBuilder::SetRenderTaskRunner(
265272
const FlutterTaskRunnerDescription* runner) {
266273
if (runner == nullptr) {

shell/platform/embedder/tests/embedder_config_builder.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,10 @@ class EmbedderConfigBuilder {
104104

105105
UniqueEngine InitializeEngine() const;
106106

107+
// Sets up the callback for vsync, the callbacks needs to be specified on the
108+
// text context vis `SetVsyncCallback`.
109+
void SetupVsyncCallback();
110+
107111
private:
108112
EmbedderTestContext& context_;
109113
FlutterProjectArgs project_args_ = {};

shell/platform/embedder/tests/embedder_test_context.cc

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,5 +224,14 @@ void EmbedderTestContext::FireRootSurfacePresentCallbackIfPresent(
224224
callback(image_callback());
225225
}
226226

227+
void EmbedderTestContext::SetVsyncCallback(
228+
std::function<void(intptr_t)> callback) {
229+
vsync_callback_ = callback;
230+
}
231+
232+
void EmbedderTestContext::RunVsyncCallback(intptr_t baton) {
233+
vsync_callback_(baton);
234+
}
235+
227236
} // namespace testing
228237
} // namespace flutter

shell/platform/embedder/tests/embedder_test_context.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,15 @@ class EmbedderTestContext {
8989

9090
virtual EmbedderTestContextType GetContextType() const = 0;
9191

92+
// Sets up the callback for vsync. This callback will be invoked
93+
// for every vsync. This should be used in conjunction with SetupVsyncCallback
94+
// on the EmbedderConfigBuilder. Any callback setup here must call
95+
// `FlutterEngineOnVsync` from the platform task runner.
96+
void SetVsyncCallback(std::function<void(intptr_t)> callback);
97+
98+
// Runs the vsync callback.
99+
void RunVsyncCallback(intptr_t baton);
100+
92101
// TODO(gw280): encapsulate these properly for subclasses to use
93102
protected:
94103
// This allows the builder to access the hooks.
@@ -112,6 +121,7 @@ class EmbedderTestContext {
112121
std::unique_ptr<EmbedderTestCompositor> compositor_;
113122
NextSceneCallback next_scene_callback_;
114123
SkMatrix root_surface_transformation_;
124+
std::function<void(intptr_t)> vsync_callback_ = nullptr;
115125

116126
static VoidCallback GetIsolateCreateCallbackHook();
117127

shell/platform/embedder/tests/embedder_unittests.cc

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5-
#include "fml/task_runner.h"
65
#define FML_USED_ON_EMBEDDER
76

87
#include <string>
@@ -18,7 +17,10 @@
1817
#include "flutter/fml/paths.h"
1918
#include "flutter/fml/synchronization/count_down_latch.h"
2019
#include "flutter/fml/synchronization/waitable_event.h"
20+
#include "flutter/fml/task_runner.h"
2121
#include "flutter/fml/thread.h"
22+
#include "flutter/fml/time/time_delta.h"
23+
#include "flutter/fml/time/time_point.h"
2224
#include "flutter/runtime/dart_vm.h"
2325
#include "flutter/shell/platform/embedder/tests/embedder_assertions.h"
2426
#include "flutter/shell/platform/embedder/tests/embedder_config_builder.h"
@@ -29,6 +31,16 @@
2931
#include "third_party/skia/include/core/SkSurface.h"
3032
#include "third_party/tonic/converter/dart_converter.h"
3133

34+
namespace {
35+
36+
static uint64_t NanosFromEpoch(int millis_from_now) {
37+
const auto now = fml::TimePoint::Now();
38+
const auto delta = fml::TimeDelta::FromMilliseconds(millis_from_now);
39+
return (now + delta).ToEpochDelta().ToNanoseconds();
40+
}
41+
42+
} // namespace
43+
3244
namespace flutter {
3345
namespace testing {
3446

@@ -1575,5 +1587,60 @@ TEST_F(EmbedderTest, BackToBackKeyEventResponsesCorrectlyInvoked) {
15751587
shutdown_latch.Wait();
15761588
}
15771589

1590+
// This test schedules a frame for the future and asserts that vsync waiter
1591+
// posts the event at the right frame start time (which is in the future).
1592+
TEST_F(EmbedderTest, VsyncCallbackPostedIntoFuture) {
1593+
UniqueEngine engine;
1594+
fml::AutoResetWaitableEvent present_latch;
1595+
1596+
// One of the threads that the callback (FlutterEngineOnVsync) will be posted
1597+
// to is the platform thread. So we cannot wait for assertions to complete on
1598+
// the platform thread. Create a new thread to manage the engine instance and
1599+
// wait for assertions on the test thread.
1600+
auto platform_task_runner = CreateNewThread("platform_thread");
1601+
1602+
platform_task_runner->PostTask([&]() {
1603+
auto& context =
1604+
GetEmbedderContext(EmbedderTestContextType::kSoftwareContext);
1605+
1606+
context.SetVsyncCallback([&](intptr_t baton) {
1607+
platform_task_runner->PostTask([baton = baton, engine = engine.get()]() {
1608+
FlutterEngineOnVsync(engine, baton, NanosFromEpoch(16),
1609+
NanosFromEpoch(32));
1610+
});
1611+
});
1612+
context.AddNativeCallback(
1613+
"SignalNativeTest", CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
1614+
present_latch.Signal();
1615+
}));
1616+
1617+
EmbedderConfigBuilder builder(context);
1618+
builder.SetSoftwareRendererConfig();
1619+
builder.SetupVsyncCallback();
1620+
builder.SetDartEntrypoint("empty_scene");
1621+
engine = builder.LaunchEngine();
1622+
ASSERT_TRUE(engine.is_valid());
1623+
1624+
// Send a window metrics events so frames may be scheduled.
1625+
FlutterWindowMetricsEvent event = {};
1626+
event.struct_size = sizeof(event);
1627+
event.width = 800;
1628+
event.height = 600;
1629+
event.pixel_ratio = 1.0;
1630+
1631+
ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event),
1632+
kSuccess);
1633+
});
1634+
1635+
present_latch.Wait();
1636+
1637+
fml::AutoResetWaitableEvent shutdown_latch;
1638+
platform_task_runner->PostTask([&]() {
1639+
engine.reset();
1640+
shutdown_latch.Signal();
1641+
});
1642+
shutdown_latch.Wait();
1643+
}
1644+
15781645
} // namespace testing
15791646
} // namespace flutter

shell/platform/embedder/vsync_waiter_embedder.cc

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,26 +17,35 @@ VsyncWaiterEmbedder::~VsyncWaiterEmbedder() = default;
1717
// |VsyncWaiter|
1818
void VsyncWaiterEmbedder::AwaitVSync() {
1919
auto* weak_waiter = new std::weak_ptr<VsyncWaiter>(shared_from_this());
20-
vsync_callback_(reinterpret_cast<intptr_t>(weak_waiter));
20+
intptr_t baton = reinterpret_cast<intptr_t>(weak_waiter);
21+
vsync_callback_(baton);
2122
}
2223

2324
// static
24-
bool VsyncWaiterEmbedder::OnEmbedderVsync(intptr_t baton,
25-
fml::TimePoint frame_start_time,
26-
fml::TimePoint frame_target_time) {
25+
bool VsyncWaiterEmbedder::OnEmbedderVsync(
26+
const flutter::TaskRunners& task_runners,
27+
intptr_t baton,
28+
fml::TimePoint frame_start_time,
29+
fml::TimePoint frame_target_time) {
2730
if (baton == 0) {
2831
return false;
2932
}
3033

31-
auto* weak_waiter = reinterpret_cast<std::weak_ptr<VsyncWaiter>*>(baton);
32-
auto strong_waiter = weak_waiter->lock();
33-
delete weak_waiter;
34+
// If the time here is in the future, the contract for `FlutterEngineOnVsync`
35+
// says that the engine will only process the frame when the time becomes
36+
// current.
37+
task_runners.GetUITaskRunner()->PostTaskForTime(
38+
[frame_start_time, frame_target_time, baton]() {
39+
std::weak_ptr<VsyncWaiter>* weak_waiter =
40+
reinterpret_cast<std::weak_ptr<VsyncWaiter>*>(baton);
41+
auto vsync_waiter = weak_waiter->lock();
42+
delete weak_waiter;
43+
if (vsync_waiter) {
44+
vsync_waiter->FireCallback(frame_start_time, frame_target_time);
45+
}
46+
},
47+
frame_start_time);
3448

35-
if (!strong_waiter) {
36-
return false;
37-
}
38-
39-
strong_waiter->FireCallback(frame_start_time, frame_target_time);
4049
return true;
4150
}
4251

shell/platform/embedder/vsync_waiter_embedder.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ class VsyncWaiterEmbedder final : public VsyncWaiter {
1919

2020
~VsyncWaiterEmbedder() override;
2121

22-
static bool OnEmbedderVsync(intptr_t baton,
22+
static bool OnEmbedderVsync(const flutter::TaskRunners& task_runners,
23+
intptr_t baton,
2324
fml::TimePoint frame_start_time,
2425
fml::TimePoint frame_target_time);
2526

0 commit comments

Comments
 (0)