Skip to content

Commit e6c333b

Browse files
knopploic-sharma
andauthored
[windows] Implement merged UI and platform thread (#162935)
Original issue: flutter/flutter#150525 Thread merging is currently disabled by default. It is controlled from the application through `DartProject`: ```cpp flutter::DartProject project(L"data"); // Enables running UI isolate on platform thread project.set_merged_platform_ui_thread(true); ``` Required changes to windows embedder: - Resize synchronization no longer blocks on condition variable, instead it polls the Flutter run loop (ignoring other windows messages) until the frame is available. This way resize synchronization works with both thread merging enabled and disabled. ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --------- Co-authored-by: Loïc Sharma <737941+loic-sharma@users.noreply.github.com>
1 parent fca021d commit e6c333b

13 files changed

+115
-56
lines changed

engine/src/flutter/shell/platform/windows/client_wrapper/flutter_engine.cc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ FlutterEngine::FlutterEngine(const DartProject& project) {
1919
c_engine_properties.dart_entrypoint = project.dart_entrypoint().c_str();
2020
c_engine_properties.gpu_preference =
2121
static_cast<FlutterDesktopGpuPreference>(project.gpu_preference());
22+
c_engine_properties.merged_platform_ui_thread =
23+
project.merged_platform_ui_thread();
2224

2325
const std::vector<std::string>& entrypoint_args =
2426
project.dart_entrypoint_arguments();

engine/src/flutter/shell/platform/windows/client_wrapper/include/flutter/dart_project.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,18 @@ class DartProject {
9090
// Defaults to NoPreference.
9191
GpuPreference gpu_preference() const { return gpu_preference_; }
9292

93+
// Sets whether the UI isolate should run on the platform thread.
94+
// In a future release, this setting will become a no-op when
95+
// Flutter Windows requires merged platform and UI threads.
96+
void set_merged_platform_ui_thread(bool merged_platform_ui_thread) {
97+
merged_platform_ui_thread_ = merged_platform_ui_thread;
98+
}
99+
100+
// Returns whether the UI isolate should run on the platform thread.
101+
// Defaults to false. In a future release, this setting will default
102+
// to true.
103+
bool merged_platform_ui_thread() const { return merged_platform_ui_thread_; }
104+
93105
private:
94106
// Accessors for internals are private, so that they can be changed if more
95107
// flexible options for project structures are needed later without it
@@ -116,6 +128,8 @@ class DartProject {
116128
std::vector<std::string> dart_entrypoint_arguments_;
117129
// The preference for GPU to be used by flutter engine.
118130
GpuPreference gpu_preference_ = GpuPreference::NoPreference;
131+
// Whether the UI isolate should run on the platform thread.
132+
bool merged_platform_ui_thread_ = false;
119133
};
120134

121135
} // namespace flutter

engine/src/flutter/shell/platform/windows/flutter_project_bundle.cc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ FlutterProjectBundle::FlutterProjectBundle(
3232
gpu_preference_ =
3333
static_cast<FlutterGpuPreference>(properties.gpu_preference);
3434

35+
merged_platform_ui_thread_ = properties.merged_platform_ui_thread;
36+
3537
// Resolve any relative paths.
3638
if (assets_path_.is_relative() || icu_path_.is_relative() ||
3739
(!aot_library_path_.empty() && aot_library_path_.is_relative())) {

engine/src/flutter/shell/platform/windows/flutter_project_bundle.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@ class FlutterProjectBundle {
6767
// Returns the app's GPU preference.
6868
FlutterGpuPreference gpu_preference() const { return gpu_preference_; }
6969

70+
// Whether the UI isolate should be running on the platform thread.
71+
bool merged_platform_ui_thread() const { return merged_platform_ui_thread_; }
72+
7073
private:
7174
std::filesystem::path assets_path_;
7275
std::filesystem::path icu_path_;
@@ -85,6 +88,9 @@ class FlutterProjectBundle {
8588

8689
// App's GPU preference.
8790
FlutterGpuPreference gpu_preference_;
91+
92+
// Whether the UI isolate should be running on the platform thread.
93+
bool merged_platform_ui_thread_;
8894
};
8995

9096
} // namespace flutter

engine/src/flutter/shell/platform/windows/flutter_windows_engine.cc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,12 @@ bool FlutterWindowsEngine::Run(std::string_view entrypoint) {
294294
custom_task_runners.thread_priority_setter =
295295
&WindowsPlatformThreadPrioritySetter;
296296

297+
if (project_->merged_platform_ui_thread()) {
298+
FML_LOG(WARNING)
299+
<< "Running with merged platform and UI thread. Experimental.";
300+
custom_task_runners.ui_task_runner = &platform_task_runner;
301+
}
302+
297303
FlutterProjectArgs args = {};
298304
args.struct_size = sizeof(FlutterProjectArgs);
299305
args.shutdown_dart_vm_when_done = true;

engine/src/flutter/shell/platform/windows/flutter_windows_view.cc

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
namespace flutter {
1919

2020
namespace {
21-
// The maximum duration to block the platform thread for while waiting
21+
// The maximum duration to block the Windows event loop while waiting
2222
// for a window resize operation to complete.
2323
constexpr std::chrono::milliseconds kWindowResizeTimeout{100};
2424

@@ -191,10 +191,8 @@ void FlutterWindowsView::ForceRedraw() {
191191
}
192192
}
193193

194+
// Called on the platform thread.
194195
bool FlutterWindowsView::OnWindowSizeChanged(size_t width, size_t height) {
195-
// Called on the platform thread.
196-
std::unique_lock<std::mutex> lock(resize_mutex_);
197-
198196
if (!engine_->egl_manager()) {
199197
SendWindowMetrics(width, height, binding_handler_->GetDpiScale());
200198
return true;
@@ -214,19 +212,30 @@ bool FlutterWindowsView::OnWindowSizeChanged(size_t width, size_t height) {
214212
return true;
215213
}
216214

217-
resize_status_ = ResizeState::kResizeStarted;
218-
resize_target_width_ = width;
219-
resize_target_height_ = height;
215+
{
216+
std::unique_lock<std::mutex> lock(resize_mutex_);
217+
resize_status_ = ResizeState::kResizeStarted;
218+
resize_target_width_ = width;
219+
resize_target_height_ = height;
220+
}
220221

221222
SendWindowMetrics(width, height, binding_handler_->GetDpiScale());
222223

223-
// Block the platform thread until a frame is presented with the target
224-
// size. See |OnFrameGenerated|, |OnEmptyFrameGenerated|, and
225-
// |OnFramePresented|.
226-
return resize_cv_.wait_for(lock, kWindowResizeTimeout,
227-
[&resize_status = resize_status_] {
228-
return resize_status == ResizeState::kDone;
229-
});
224+
std::chrono::time_point<std::chrono::steady_clock> start_time =
225+
std::chrono::steady_clock::now();
226+
227+
while (true) {
228+
if (std::chrono::steady_clock::now() > start_time + kWindowResizeTimeout) {
229+
return false;
230+
}
231+
std::unique_lock<std::mutex> lock(resize_mutex_);
232+
if (resize_status_ == ResizeState::kDone) {
233+
break;
234+
}
235+
lock.unlock();
236+
engine_->task_runner()->PollOnce(kWindowResizeTimeout);
237+
}
238+
return true;
230239
}
231240

232241
void FlutterWindowsView::OnWindowRepaint() {
@@ -664,10 +673,11 @@ void FlutterWindowsView::OnFramePresented() {
664673
return;
665674
case ResizeState::kFrameGenerated: {
666675
// A frame was generated for a pending resize.
667-
// Unblock the platform thread.
668676
resize_status_ = ResizeState::kDone;
677+
// Unblock the platform thread.
678+
engine_->task_runner()->PostTask([this] {});
679+
669680
lock.unlock();
670-
resize_cv_.notify_all();
671681

672682
// Blocking the raster thread until DWM flushes alleviates glitches where
673683
// previous size surface is stretched over current size view.

engine/src/flutter/shell/platform/windows/flutter_windows_view.h

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -429,10 +429,8 @@ class FlutterWindowsView : public WindowBindingHandlerDelegate {
429429
// Currently configured WindowBindingHandler for view.
430430
std::unique_ptr<WindowBindingHandler> binding_handler_;
431431

432-
// Resize events are synchronized using this mutex and the corresponding
433-
// condition variable.
432+
// Protects resize_status_, resize_target_width_ and resize_target_height_.
434433
std::mutex resize_mutex_;
435-
std::condition_variable resize_cv_;
436434

437435
// Indicates the state of a window resize event. Platform thread will be
438436
// blocked while this is not done. Guarded by resize_mutex_.

engine/src/flutter/shell/platform/windows/flutter_windows_view_unittests.cc

Lines changed: 21 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -925,21 +925,18 @@ TEST(FlutterWindowsViewTest, WindowResizeTests) {
925925
return kSuccess;
926926
}));
927927

928-
fml::AutoResetWaitableEvent resized_latch;
929-
std::thread([&resized_latch, &view]() {
930-
// Start the window resize. This sends the new window metrics
931-
// and then blocks until another thread completes the window resize.
932-
EXPECT_TRUE(view->OnWindowSizeChanged(500, 500));
933-
resized_latch.Signal();
928+
// Simulate raster thread.
929+
std::thread([&metrics_sent_latch, &view]() {
930+
metrics_sent_latch.Wait();
931+
// Frame generated and presented from the raster thread.
932+
EXPECT_TRUE(view->OnFrameGenerated(500, 500));
933+
view->OnFramePresented();
934934
}).detach();
935935

936-
// Wait until the platform thread has started the window resize.
937-
metrics_sent_latch.Wait();
938-
939-
// Complete the window resize by reporting a frame with the new window size.
940-
ASSERT_TRUE(view->OnFrameGenerated(500, 500));
941-
view->OnFramePresented();
942-
resized_latch.Wait();
936+
// Start the window resize. This sends the new window metrics
937+
// and then blocks polling run loop until another thread completes the window
938+
// resize.
939+
EXPECT_TRUE(view->OnWindowSizeChanged(500, 500));
943940
}
944941

945942
// Verify that an empty frame completes a view resize.
@@ -989,21 +986,18 @@ TEST(FlutterWindowsViewTest, TestEmptyFrameResizes) {
989986
engine_modifier.SetEGLManager(std::move(egl_manager));
990987
view_modifier.SetSurface(std::move(surface));
991988

992-
fml::AutoResetWaitableEvent resized_latch;
993-
std::thread([&resized_latch, &view]() {
994-
// Start the window resize. This sends the new window metrics
995-
// and then blocks until another thread completes the window resize.
996-
EXPECT_TRUE(view->OnWindowSizeChanged(500, 500));
997-
resized_latch.Signal();
998-
}).detach();
989+
// Simulate raster thread.
990+
std::thread([&metrics_sent_latch, &view]() {
991+
metrics_sent_latch.Wait();
999992

1000-
// Wait until the platform thread has started the window resize.
1001-
metrics_sent_latch.Wait();
993+
// Empty frame generated and presented from the raster thread.
994+
EXPECT_TRUE(view->OnEmptyFrameGenerated());
995+
view->OnFramePresented();
996+
}).detach();
1002997

1003-
// Complete the window resize by reporting an empty frame.
1004-
view->OnEmptyFrameGenerated();
1005-
view->OnFramePresented();
1006-
resized_latch.Wait();
998+
// Start the window resize. This sends the new window metrics
999+
// and then blocks until another thread completes the window resize.
1000+
EXPECT_TRUE(view->OnWindowSizeChanged(500, 500));
10071001
}
10081002

10091003
// A window resize can be interleaved between a frame generation and
@@ -1038,15 +1032,7 @@ TEST(FlutterWindowsViewTest, WindowResizeRace) {
10381032

10391033
// Inject a window resize between the frame generation and
10401034
// frame presentation. The new size invalidates the current frame.
1041-
fml::AutoResetWaitableEvent resized_latch;
1042-
std::thread([&resized_latch, &view]() {
1043-
// The resize is never completed. The view times out and returns false.
1044-
EXPECT_FALSE(view->OnWindowSizeChanged(500, 500));
1045-
resized_latch.Signal();
1046-
}).detach();
1047-
1048-
// Wait until the platform thread has started the window resize.
1049-
resized_latch.Wait();
1035+
EXPECT_FALSE(view->OnWindowSizeChanged(500, 500));
10501036

10511037
// Complete the invalidated frame while a resize is pending. Although this
10521038
// might mean that we presented a frame with the wrong size, this should not

engine/src/flutter/shell/platform/windows/public/flutter_windows.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,9 @@ typedef struct {
8080

8181
// GPU choice preference
8282
FlutterDesktopGpuPreference gpu_preference;
83+
84+
// Whether the UI isolate should run on the platform thread.
85+
bool merged_platform_ui_thread;
8386
} FlutterDesktopEngineProperties;
8487

8588
// ========== View Controller ==========

engine/src/flutter/shell/platform/windows/task_runner.cc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,10 @@ void TaskRunner::PostTask(TaskClosure closure) {
9292
EnqueueTask(std::move(task));
9393
}
9494

95+
void TaskRunner::PollOnce(std::chrono::milliseconds timeout) {
96+
task_runner_window_->PollOnce(timeout);
97+
}
98+
9599
void TaskRunner::EnqueueTask(Task task) {
96100
static std::atomic_uint64_t sGlobalTaskOrder(0);
97101

0 commit comments

Comments
 (0)