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
12 changes: 12 additions & 0 deletions shell/platform/windows/client_wrapper/flutter_engine.cc
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,18 @@ FlutterDesktopPluginRegistrarRef FlutterEngine::GetRegistrarForPlugin(
return FlutterDesktopEngineGetPluginRegistrar(engine_, plugin_name.c_str());
}

void FlutterEngine::SetNextFrameCallback(std::function<void()> callback) {
next_frame_callback_ = std::move(callback);
FlutterDesktopEngineSetNextFrameCallback(
engine_,
[](void* user_data) {
FlutterEngine* self = static_cast<FlutterEngine*>(user_data);
self->next_frame_callback_();
self->next_frame_callback_ = nullptr;
},
this);
}

FlutterDesktopEngineRef FlutterEngine::RelinquishEngine() {
owns_engine_ = false;
return engine_;
Expand Down
33 changes: 33 additions & 0 deletions shell/platform/windows/client_wrapper/flutter_engine_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,13 @@ class TestFlutterWindowsApi : public testing::StubFlutterWindowsApi {
// |flutter::testing::StubFlutterWindowsApi|
uint64_t EngineProcessMessages() override { return 99; }

// |flutter::testing::StubFlutterWindowsApi|
void EngineSetNextFrameCallback(VoidCallback callback,
void* user_data) override {
next_frame_callback_ = callback;
next_frame_user_data_ = user_data;
}

// |flutter::testing::StubFlutterWindowsApi|
void EngineReloadSystemFonts() override { reload_fonts_called_ = true; }

Expand All @@ -61,12 +68,20 @@ class TestFlutterWindowsApi : public testing::StubFlutterWindowsApi {
return dart_entrypoint_arguments_;
}

bool has_next_frame_callback() { return next_frame_callback_ != nullptr; }
void run_next_frame_callback() {
next_frame_callback_(next_frame_user_data_);
next_frame_callback_ = nullptr;
}

private:
bool create_called_ = false;
bool run_called_ = false;
bool destroy_called_ = false;
bool reload_fonts_called_ = false;
std::vector<std::string> dart_entrypoint_arguments_;
VoidCallback next_frame_callback_ = nullptr;
void* next_frame_user_data_ = nullptr;
};

} // namespace
Expand Down Expand Up @@ -168,4 +183,22 @@ TEST(FlutterEngineTest, DartEntrypointArgs) {
EXPECT_TRUE(arguments[1] == arguments_ref[1]);
}

TEST(FlutterEngineTest, SetNextFrameCallback) {
DartProject project(L"data");
testing::ScopedStubFlutterWindowsApi scoped_api_stub(
std::make_unique<TestFlutterWindowsApi>());
auto test_api = static_cast<TestFlutterWindowsApi*>(scoped_api_stub.stub());

FlutterEngine engine(DartProject(L"fake/project/path"));

bool success = false;
engine.SetNextFrameCallback([&success]() { success = true; });

EXPECT_TRUE(test_api->has_next_frame_callback());

test_api->run_next_frame_callback();

EXPECT_TRUE(success);
}

} // namespace flutter
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,12 @@ class FlutterEngine : public PluginRegistry {
// This pointer will remain valid for the lifetime of this instance.
BinaryMessenger* messenger() { return messenger_.get(); }

// Schedule a callback to be called after the next frame is drawn.
//
// This must be called from the platform thread. The callback is executed only
// once on the platform thread.
void SetNextFrameCallback(std::function<void()> callback);

private:
// For access to RelinquishEngine.
friend class FlutterViewController;
Expand All @@ -101,6 +107,9 @@ class FlutterEngine : public PluginRegistry {
// or if RelinquishEngine has been called (since the view controller will
// run the engine if it hasn't already been run).
bool has_been_run_ = false;

// The callback to execute once the next frame is drawn.
std::function<void()> next_frame_callback_ = nullptr;
};

} // namespace flutter
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,14 @@ uint64_t FlutterDesktopEngineProcessMessages(FlutterDesktopEngineRef engine) {
return 0;
}

void FlutterDesktopEngineSetNextFrameCallback(FlutterDesktopEngineRef engine,
VoidCallback callback,
void* user_data) {
if (s_stub_implementation) {
s_stub_implementation->EngineSetNextFrameCallback(callback, user_data);
}
}

void FlutterDesktopEngineReloadSystemFonts(FlutterDesktopEngineRef engine) {
if (s_stub_implementation) {
s_stub_implementation->EngineReloadSystemFonts();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ class StubFlutterWindowsApi {
// Called for FlutterDesktopEngineProcessMessages.
virtual uint64_t EngineProcessMessages() { return 0; }

// Called for FlutterDesktopEngineSetNextFrameCallback.
virtual void EngineSetNextFrameCallback(VoidCallback callback,
void* user_data) {}

// Called for FlutterDesktopEngineReloadSystemFonts.
virtual void EngineReloadSystemFonts() {}

Expand Down
31 changes: 31 additions & 0 deletions shell/platform/windows/fixtures/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:ui' as ui;

// Signals a waiting latch in the native test.
void signal() native 'Signal';

Expand All @@ -11,6 +13,9 @@ void signalBoolValue(bool value) native 'SignalBoolValue';
// Signals a waiting latch in the native test, which returns a value to the fixture.
bool signalBoolReturn() native 'SignalBoolReturn';

// Notify the native test that the first frame has been scheduled.
void notifyFirstFrameScheduled() native 'NotifyFirstFrameScheduled';

void main() {
}

Expand All @@ -33,3 +38,29 @@ void verifyNativeFunctionWithReturn() {
bool value = signalBoolReturn();
signalBoolValue(value);
}

@pragma('vm:entry-point')
void drawHelloWorld() {
ui.PlatformDispatcher.instance.onBeginFrame = (Duration duration) {
final ui.ParagraphBuilder paragraphBuilder = ui.ParagraphBuilder(ui.ParagraphStyle())
..addText('Hello world');
final ui.Paragraph paragraph = paragraphBuilder.build();

paragraph.layout(const ui.ParagraphConstraints(width: 800.0));

final ui.PictureRecorder recorder = ui.PictureRecorder();
final ui.Canvas canvas = ui.Canvas(recorder);

canvas.drawParagraph(paragraph, ui.Offset.zero);

final ui.Picture picture = recorder.endRecording();
final ui.SceneBuilder sceneBuilder = ui.SceneBuilder()
..addPicture(ui.Offset.zero, picture)
..pop();

ui.window.render(sceneBuilder.build());
};

ui.PlatformDispatcher.instance.scheduleFrame();
notifyFirstFrameScheduled();
}
7 changes: 7 additions & 0 deletions shell/platform/windows/flutter_windows.cc
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,13 @@ FlutterDesktopTextureRegistrarRef FlutterDesktopEngineGetTextureRegistrar(
EngineFromHandle(engine)->texture_registrar());
}

void FlutterDesktopEngineSetNextFrameCallback(FlutterDesktopEngineRef engine,
VoidCallback callback,
void* user_data) {
EngineFromHandle(engine)->SetNextFrameCallback(
[callback, user_data]() { callback(user_data); });
}

HWND FlutterDesktopViewGetHWND(FlutterDesktopViewRef view) {
return ViewFromHandle(view)->GetPlatformWindow();
}
Expand Down
16 changes: 16 additions & 0 deletions shell/platform/windows/flutter_windows_engine.cc
Original file line number Diff line number Diff line change
Expand Up @@ -506,6 +506,22 @@ void FlutterWindowsEngine::ScheduleFrame() {
embedder_api_.ScheduleFrame(engine_);
}

void FlutterWindowsEngine::SetNextFrameCallback(fml::closure callback) {
next_frame_callback_ = std::move(callback);

embedder_api_.SetNextFrameCallback(
engine_,
[](void* user_data) {
// Embedder callback runs on raster thread. Switch back to platform
// thread.
FlutterWindowsEngine* self =
static_cast<FlutterWindowsEngine*>(user_data);

self->task_runner_->PostTask(std::move(self->next_frame_callback_));
},
this);
}

void FlutterWindowsEngine::SendSystemLocales() {
std::vector<LanguageInfo> languages = GetPreferredLanguageInfo();
std::vector<FlutterLocale> flutter_locales;
Expand Down
6 changes: 6 additions & 0 deletions shell/platform/windows/flutter_windows_engine.h
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,9 @@ class FlutterWindowsEngine {
// Informs the engine that a new frame is needed to redraw the content.
void ScheduleFrame();

// Set the callback that is called when the next frame is drawn.
void SetNextFrameCallback(fml::closure callback);

// Attempts to register the texture with the given |texture_id|.
bool RegisterExternalTexture(int64_t texture_id);

Expand Down Expand Up @@ -294,6 +297,9 @@ class FlutterWindowsEngine {

// The root isolate creation callback.
fml::closure root_isolate_create_callback_;

// The on frame drawn callback.
fml::closure next_frame_callback_;
};

} // namespace flutter
Expand Down
15 changes: 15 additions & 0 deletions shell/platform/windows/flutter_windows_engine_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -389,5 +389,20 @@ TEST(FlutterWindowsEngine, ScheduleFrame) {
EXPECT_TRUE(called);
}

TEST(FlutterWindowsEngine, SetNextFrameCallback) {
std::unique_ptr<FlutterWindowsEngine> engine = GetTestEngine();
EngineModifier modifier(engine.get());

bool called = false;
modifier.embedder_api().SetNextFrameCallback = MOCK_ENGINE_PROC(
SetNextFrameCallback, ([&called](auto engine, auto callback, auto data) {
called = true;
return kSuccess;
}));

engine->SetNextFrameCallback([]() {});
EXPECT_TRUE(called);
}

} // namespace testing
} // namespace flutter
54 changes: 54 additions & 0 deletions shell/platform/windows/flutter_windows_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -149,5 +149,59 @@ TEST_F(WindowsTest, VerifyNativeFunctionWithReturn) {
EXPECT_TRUE(bool_value_passed);
}

// Verify the next frame callback is executed.
TEST_F(WindowsTest, NextFrameCallback) {
struct Captures {
fml::AutoResetWaitableEvent frame_scheduled_latch;
fml::AutoResetWaitableEvent frame_drawn_latch;
std::thread::id thread_id;
};
Captures captures;

CreateNewThread("test_platform_thread")->PostTask([&]() {
captures.thread_id = std::this_thread::get_id();

auto& context = GetContext();
WindowsConfigBuilder builder(context);
builder.SetDartEntrypoint("drawHelloWorld");

auto native_entry = CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
ASSERT_FALSE(captures.frame_drawn_latch.IsSignaledForTest());
captures.frame_scheduled_latch.Signal();
});
context.AddNativeFunction("NotifyFirstFrameScheduled", native_entry);

ViewControllerPtr controller{builder.Run()};
ASSERT_NE(controller, nullptr);

auto engine = FlutterDesktopViewControllerGetEngine(controller.get());

FlutterDesktopEngineSetNextFrameCallback(
engine,
[](void* user_data) {
auto captures = static_cast<Captures*>(user_data);

ASSERT_TRUE(captures->frame_scheduled_latch.IsSignaledForTest());

// Callback should execute on platform thread.
ASSERT_EQ(std::this_thread::get_id(), captures->thread_id);

// Signal the test passed and end the Windows message loop.
captures->frame_drawn_latch.Signal();
::PostQuitMessage(0);
},
&captures);

// Pump messages for the Windows platform task runner.
::MSG msg;
while (::GetMessage(&msg, nullptr, 0, 0)) {
::TranslateMessage(&msg);
::DispatchMessage(&msg);
}
Comment on lines +195 to +200
Copy link
Member Author

@loic-sharma loic-sharma Aug 15, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is necessary as otherwise platform tasks - including the next frame callback - won't run. The Windows platform thread's task runner is actually a window. It is woken up by posting a message to its window. If messages aren't dispatched, the Windows platform task runner is never woken up, and platform tasks are never executed.

});

captures.frame_drawn_latch.Wait();
}

} // namespace testing
} // namespace flutter
11 changes: 11 additions & 0 deletions shell/platform/windows/public/flutter_windows.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
extern "C" {
#endif

typedef void (*VoidCallback)(void* /* user data */);

// Opaque reference to a Flutter window controller.
typedef struct FlutterDesktopViewControllerState*
FlutterDesktopViewControllerRef;
Expand Down Expand Up @@ -185,6 +187,15 @@ FlutterDesktopEngineGetMessenger(FlutterDesktopEngineRef engine);
FLUTTER_EXPORT FlutterDesktopTextureRegistrarRef
FlutterDesktopEngineGetTextureRegistrar(FlutterDesktopEngineRef engine);

// Schedule a callback to be called after the next frame is drawn.
//
// This must be called from the platform thread. The callback is executed only
// once on the platform thread.
FLUTTER_EXPORT void FlutterDesktopEngineSetNextFrameCallback(
FlutterDesktopEngineRef engine,
VoidCallback callback,
void* user_data);

// ========== View ==========

// Return backing HWND for manipulation in host application.
Expand Down