diff --git a/common/settings.h b/common/settings.h index b2872abdcd33b..49c5311aff8e3 100644 --- a/common/settings.h +++ b/common/settings.h @@ -229,6 +229,10 @@ struct Settings { bool enable_impeller = false; #endif + // If true, the UI thread is the platform thread on supported + // platforms. + bool merged_platform_ui_thread = false; + // Log a warning during shell initialization if Impeller is not enabled. bool warn_on_impeller_opt_out = false; diff --git a/shell/common/shell.cc b/shell/common/shell.cc index 5f6076b5a7f25..4992ee270a5e0 100644 --- a/shell/common/shell.cc +++ b/shell/common/shell.cc @@ -944,11 +944,12 @@ void Shell::OnPlatformViewDestroyed() { // Notify the Dart VM that the PlatformView has been destroyed and some // cleanup activity can be done (e.g: garbage collect the Dart heap). - task_runners_.GetUITaskRunner()->PostTask([engine = engine_->GetWeakPtr()]() { - if (engine) { - engine->NotifyDestroyed(); - } - }); + fml::TaskRunner::RunNowOrPostTask(task_runners_.GetUITaskRunner(), + [engine = engine_->GetWeakPtr()]() { + if (engine) { + engine->NotifyDestroyed(); + } + }); // Note: // This is a synchronous operation because certain platforms depend on @@ -1006,11 +1007,12 @@ void Shell::OnPlatformViewScheduleFrame() { FML_DCHECK(is_set_up_); FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread()); - task_runners_.GetUITaskRunner()->PostTask([engine = engine_->GetWeakPtr()]() { - if (engine) { - engine->ScheduleFrame(); - } - }); + fml::TaskRunner::RunNowOrPostTask(task_runners_.GetUITaskRunner(), + [engine = engine_->GetWeakPtr()]() { + if (engine) { + engine->ScheduleFrame(); + } + }); } // |PlatformView::Delegate| @@ -1038,7 +1040,8 @@ void Shell::OnPlatformViewSetViewportMetrics(int64_t view_id, } }); - task_runners_.GetUITaskRunner()->PostTask( + fml::TaskRunner::RunNowOrPostTask( + task_runners_.GetUITaskRunner(), [engine = engine_->GetWeakPtr(), view_id, metrics]() { if (engine) { engine->SetViewportMetrics(view_id, metrics); @@ -1078,8 +1081,10 @@ void Shell::OnPlatformViewDispatchPlatformMessage( // The static leak checker gets confused by the use of fml::MakeCopyable. // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) - task_runners_.GetUITaskRunner()->PostTask(fml::MakeCopyable( - [engine = engine_->GetWeakPtr(), message = std::move(message)]() mutable { + fml::TaskRunner::RunNowOrPostTask( + task_runners_.GetUITaskRunner(), + fml::MakeCopyable([engine = engine_->GetWeakPtr(), + message = std::move(message)]() mutable { if (engine) { engine->DispatchPlatformMessage(std::move(message)); } @@ -1095,7 +1100,8 @@ void Shell::OnPlatformViewDispatchPointerDataPacket( TRACE_FLOW_BEGIN("flutter", "PointerEvent", next_pointer_flow_id_); FML_DCHECK(is_set_up_); FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread()); - task_runners_.GetUITaskRunner()->PostTask( + fml::TaskRunner::RunNowOrPostTask( + task_runners_.GetUITaskRunner(), fml::MakeCopyable([engine = weak_engine_, packet = std::move(packet), flow_id = next_pointer_flow_id_]() mutable { if (engine) { @@ -1112,7 +1118,8 @@ void Shell::OnPlatformViewDispatchSemanticsAction(int32_t node_id, FML_DCHECK(is_set_up_); FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread()); - task_runners_.GetUITaskRunner()->PostTask( + fml::TaskRunner::RunNowOrPostTask( + task_runners_.GetUITaskRunner(), fml::MakeCopyable([engine = engine_->GetWeakPtr(), node_id, action, args = std::move(args)]() mutable { if (engine) { @@ -1126,12 +1133,12 @@ void Shell::OnPlatformViewSetSemanticsEnabled(bool enabled) { FML_DCHECK(is_set_up_); FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread()); - task_runners_.GetUITaskRunner()->PostTask( - [engine = engine_->GetWeakPtr(), enabled] { - if (engine) { - engine->SetSemanticsEnabled(enabled); - } - }); + fml::TaskRunner::RunNowOrPostTask(task_runners_.GetUITaskRunner(), + [engine = engine_->GetWeakPtr(), enabled] { + if (engine) { + engine->SetSemanticsEnabled(enabled); + } + }); } // |PlatformView::Delegate| @@ -1139,12 +1146,12 @@ void Shell::OnPlatformViewSetAccessibilityFeatures(int32_t flags) { FML_DCHECK(is_set_up_); FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread()); - task_runners_.GetUITaskRunner()->PostTask( - [engine = engine_->GetWeakPtr(), flags] { - if (engine) { - engine->SetAccessibilityFeatures(flags); - } - }); + fml::TaskRunner::RunNowOrPostTask(task_runners_.GetUITaskRunner(), + [engine = engine_->GetWeakPtr(), flags] { + if (engine) { + engine->SetAccessibilityFeatures(flags); + } + }); } // |PlatformView::Delegate| @@ -1205,11 +1212,12 @@ void Shell::OnPlatformViewMarkTextureFrameAvailable(int64_t texture_id) { }); // Schedule a new frame without having to rebuild the layer tree. - task_runners_.GetUITaskRunner()->PostTask([engine = engine_->GetWeakPtr()]() { - if (engine) { - engine->ScheduleFrame(false); - } - }); + fml::TaskRunner::RunNowOrPostTask(task_runners_.GetUITaskRunner(), + [engine = engine_->GetWeakPtr()]() { + if (engine) { + engine->ScheduleFrame(false); + } + }); } // |PlatformView::Delegate| @@ -1317,7 +1325,8 @@ void Shell::OnEngineUpdateSemantics(SemanticsNodeUpdates update, FML_DCHECK(is_set_up_); FML_DCHECK(task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread()); - task_runners_.GetPlatformTaskRunner()->PostTask( + task_runners_.GetPlatformTaskRunner()->RunNowOrPostTask( + task_runners_.GetPlatformTaskRunner(), [view = platform_view_->GetWeakPtr(), update = std::move(update), actions = std::move(actions)] { if (view) { @@ -2142,15 +2151,16 @@ void Shell::OnPlatformViewAddView(int64_t view_id, << "Unexpected request to add the implicit view #" << kFlutterImplicitViewId << ". This view should never be added."; - task_runners_.GetUITaskRunner()->PostTask([engine = engine_->GetWeakPtr(), // - viewport_metrics, // - view_id, // - callback = std::move(callback) // + task_runners_.GetUITaskRunner()->RunNowOrPostTask( + task_runners_.GetUITaskRunner(), [engine = engine_->GetWeakPtr(), // + viewport_metrics, // + view_id, // + callback = std::move(callback) // ] { - if (engine) { - engine->AddView(view_id, viewport_metrics, callback); - } - }); + if (engine) { + engine->AddView(view_id, viewport_metrics, callback); + } + }); } void Shell::OnPlatformViewRemoveView(int64_t view_id, @@ -2163,7 +2173,8 @@ void Shell::OnPlatformViewRemoveView(int64_t view_id, << kFlutterImplicitViewId << ". This view should never be removed."; expected_frame_sizes_.erase(view_id); - task_runners_.GetUITaskRunner()->PostTask( + task_runners_.GetUITaskRunner()->RunNowOrPostTask( + task_runners_.GetUITaskRunner(), [&task_runners = task_runners_, // engine = engine_->GetWeakPtr(), // rasterizer = rasterizer_->GetWeakPtr(), // @@ -2303,13 +2314,13 @@ void Shell::OnDisplayUpdates(std::vector> displays) { for (const auto& display : displays) { display_data.push_back(display->GetDisplayData()); } - task_runners_.GetUITaskRunner()->PostTask( - [engine = engine_->GetWeakPtr(), - display_data = std::move(display_data)]() { - if (engine) { - engine->SetDisplays(display_data); - } - }); + fml::TaskRunner::RunNowOrPostTask(task_runners_.GetUITaskRunner(), + [engine = engine_->GetWeakPtr(), + display_data = std::move(display_data)]() { + if (engine) { + engine->SetDisplays(display_data); + } + }); display_manager_->HandleDisplayUpdates(std::move(displays)); } diff --git a/shell/common/switches.cc b/shell/common/switches.cc index 6aa2b21ea8ce3..c07f45d210cb8 100644 --- a/shell/common/switches.cc +++ b/shell/common/switches.cc @@ -361,6 +361,9 @@ Settings SettingsFromCommandLine(const fml::CommandLine& command_line) { settings.verbose_logging = command_line.HasOption(FlagForSwitch(Switch::VerboseLogging)); + settings.merged_platform_ui_thread = command_line.HasOption( + FlagForSwitch(Switch::EnableMergedPlatformUIThread)); + command_line.GetOptionValue(FlagForSwitch(Switch::FlutterAssetsDir), &settings.assets_path); diff --git a/shell/common/switches.h b/shell/common/switches.h index 9d27d16fbc6d7..be0499543b4ee 100644 --- a/shell/common/switches.h +++ b/shell/common/switches.h @@ -294,6 +294,9 @@ DEF_SWITCH(EnableEmbedderAPI, DEF_SWITCH(EnablePlatformIsolates, "enable-platform-isolates", "Enable support for isolates that run on the platform thread.") +DEF_SWITCH(EnableMergedPlatformUIThread, + "enable-merged-platform-ui-thread", + "Merge the ui thread and platform thread.") DEF_SWITCHES_END void PrintUsage(const std::string& executable_name); diff --git a/shell/platform/android/android_shell_holder.cc b/shell/platform/android/android_shell_holder.cc index 3f425b73a62d5..74d37e5958c66 100644 --- a/shell/platform/android/android_shell_holder.cc +++ b/shell/platform/android/android_shell_holder.cc @@ -94,10 +94,12 @@ AndroidShellHolder::AndroidShellHolder( flutter::ThreadHost::ThreadHostConfig host_config( thread_label, mask, AndroidPlatformThreadConfigSetter); - host_config.ui_config = fml::Thread::ThreadConfig( - flutter::ThreadHost::ThreadHostConfig::MakeThreadName( - flutter::ThreadHost::Type::kUi, thread_label), - fml::Thread::ThreadPriority::kDisplay); + if (!settings.merged_platform_ui_thread) { + host_config.ui_config = fml::Thread::ThreadConfig( + flutter::ThreadHost::ThreadHostConfig::MakeThreadName( + flutter::ThreadHost::Type::kUi, thread_label), + fml::Thread::ThreadPriority::kDisplay); + } host_config.raster_config = fml::Thread::ThreadConfig( flutter::ThreadHost::ThreadHostConfig::MakeThreadName( flutter::ThreadHost::Type::kRaster, thread_label), @@ -137,7 +139,13 @@ AndroidShellHolder::AndroidShellHolder( fml::RefPtr platform_runner = fml::MessageLoop::GetCurrent().GetTaskRunner(); raster_runner = thread_host_->raster_thread->GetTaskRunner(); - ui_runner = thread_host_->ui_thread->GetTaskRunner(); + if (settings.merged_platform_ui_thread) { + FML_LOG(IMPORTANT) + << "Warning: Using highly experimental merged thread mode."; + ui_runner = platform_runner; + } else { + ui_runner = thread_host_->ui_thread->GetTaskRunner(); + } io_runner = thread_host_->io_thread->GetTaskRunner(); flutter::TaskRunners task_runners(thread_label, // label diff --git a/shell/platform/android/android_shell_holder.h b/shell/platform/android/android_shell_holder.h index 30530a896204b..276f46a0435a5 100644 --- a/shell/platform/android/android_shell_holder.h +++ b/shell/platform/android/android_shell_holder.h @@ -105,6 +105,9 @@ class AndroidShellHolder { void UpdateDisplayMetrics(); + // Visible for testing. + const std::unique_ptr& GetShellForTesting() const { return shell_; } + private: const flutter::Settings settings_; const std::shared_ptr jni_facade_; diff --git a/shell/platform/android/android_shell_holder_unittests.cc b/shell/platform/android/android_shell_holder_unittests.cc index ba13dd99187c4..5b1d3f0abe55d 100644 --- a/shell/platform/android/android_shell_holder_unittests.cc +++ b/shell/platform/android/android_shell_holder_unittests.cc @@ -160,5 +160,34 @@ TEST(AndroidShellHolder, HandlePlatformMessage) { holder->GetPlatformMessageHandler() ->InvokePlatformMessageEmptyResponseCallback(response_id); } + +TEST(AndroidShellHolder, CreateWithMergedPlatformAndUIThread) { + Settings settings; + settings.merged_platform_ui_thread = true; + auto jni = std::make_shared(); + auto holder = std::make_unique(settings, jni); + auto window = fml::MakeRefCounted( + nullptr, /*is_fake_window=*/true); + holder->GetPlatformView()->NotifyCreated(window); + + EXPECT_EQ( + holder->GetShellForTesting()->GetTaskRunners().GetUITaskRunner(), + holder->GetShellForTesting()->GetTaskRunners().GetPlatformTaskRunner()); +} + +TEST(AndroidShellHolder, CreateWithUnMergedPlatformAndUIThread) { + Settings settings; + settings.merged_platform_ui_thread = false; + auto jni = std::make_shared(); + auto holder = std::make_unique(settings, jni); + auto window = fml::MakeRefCounted( + nullptr, /*is_fake_window=*/true); + holder->GetPlatformView()->NotifyCreated(window); + + EXPECT_NE( + holder->GetShellForTesting()->GetTaskRunners().GetUITaskRunner(), + holder->GetShellForTesting()->GetTaskRunners().GetPlatformTaskRunner()); +} + } // namespace testing } // namespace flutter diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterShellArgs.java b/shell/platform/android/io/flutter/embedding/engine/FlutterShellArgs.java index 36a40d3923295..338a615a7ee98 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterShellArgs.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterShellArgs.java @@ -71,8 +71,6 @@ public static FlutterShellArgs fromIntent(@NonNull Intent intent) { // Before adding more entries to this list, consider that arbitrary // Android applications can generate intents with extra data and that // there are many security-sensitive args in the binary. - // TODO(mattcarroll): I left this warning as-is, but we should clarify what exactly this warning - // is warning against. ArrayList args = new ArrayList<>(); if (intent.getBooleanExtra(ARG_KEY_TRACE_STARTUP, false)) { diff --git a/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java b/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java index b540c20e14caf..0c2f2e5f449f1 100644 --- a/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java +++ b/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java @@ -47,6 +47,8 @@ public class FlutterLoader { "io.flutter.embedding.android.EnableOpenGLGPUTracing"; private static final String IMPELLER_VULKAN_GPU_TRACING_DATA_KEY = "io.flutter.embedding.android.EnableVulkanGPUTracing"; + private static final String ENABLED_MERGED_PLATFORM_UI_THREAD_KEY = + "io.flutter.embedding.android.EnableMergedPlatformUIThread"; /** * Set whether leave or clean up the VM after the last shell shuts down. It can be set from app's @@ -352,6 +354,9 @@ public void ensureInitializationComplete( if (metaData.getBoolean(IMPELLER_VULKAN_GPU_TRACING_DATA_KEY, false)) { shellArgs.add("--enable-vulkan-gpu-tracing"); } + if (metaData.getBoolean(ENABLED_MERGED_PLATFORM_UI_THREAD_KEY, false)) { + shellArgs.add("--enable-merged-platform-ui-thread"); + } String backend = metaData.getString(IMPELLER_BACKEND_META_DATA_KEY); if (backend != null) { shellArgs.add("--impeller-backend=" + backend); diff --git a/shell/platform/darwin/ios/framework/Source/FlutterDartProject.mm b/shell/platform/darwin/ios/framework/Source/FlutterDartProject.mm index b87204f2440bc..b7d3eb96c6807 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterDartProject.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterDartProject.mm @@ -212,6 +212,12 @@ static BOOL DoesHardwareSupportWideGamut() { settings.enable_dart_profiling = enableDartProfiling.boolValue; } + NSNumber* enableMergedPlatformUIThread = + [mainBundle objectForInfoDictionaryKey:@"FLTEnableMergedPlatformUIThread"]; + if (enableMergedPlatformUIThread != nil) { + settings.merged_platform_ui_thread = enableMergedPlatformUIThread.boolValue; + } + // Leak Dart VM settings, set whether leave or clean up the VM after the last shell shuts down. NSNumber* leakDartVM = [mainBundle objectForInfoDictionaryKey:@"FLTLeakDartVM"]; // It will change the default leak_vm value in settings only if the key exists. diff --git a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm index e2f355dca4505..5f2a1fa74ff82 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "common/settings.h" #define FML_USED_ON_EMBEDDER #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h" @@ -786,7 +787,8 @@ + (NSString*)generateThreadLabel:(NSString*)labelPrefix { return [NSString stringWithFormat:@"%@.%zu", labelPrefix, ++s_shellCount]; } -+ (flutter::ThreadHost)makeThreadHost:(NSString*)threadLabel { +static flutter::ThreadHost MakeThreadHost(NSString* thread_label, + const flutter::Settings& settings) { // The current thread will be used as the platform thread. Ensure that the message loop is // initialized. fml::MessageLoop::EnsureInitializedForCurrentThread(); @@ -798,22 +800,24 @@ + (NSString*)generateThreadLabel:(NSString*)labelPrefix { threadHostType = threadHostType | flutter::ThreadHost::Type::kProfiler; } - flutter::ThreadHost::ThreadHostConfig host_config(threadLabel.UTF8String, threadHostType, + flutter::ThreadHost::ThreadHostConfig host_config(thread_label.UTF8String, threadHostType, IOSPlatformThreadConfigSetter); - host_config.ui_config = - fml::Thread::ThreadConfig(flutter::ThreadHost::ThreadHostConfig::MakeThreadName( - flutter::ThreadHost::Type::kUi, threadLabel.UTF8String), - fml::Thread::ThreadPriority::kDisplay); + if (!settings.merged_platform_ui_thread) { + host_config.ui_config = + fml::Thread::ThreadConfig(flutter::ThreadHost::ThreadHostConfig::MakeThreadName( + flutter::ThreadHost::Type::kUi, thread_label.UTF8String), + fml::Thread::ThreadPriority::kDisplay); + } host_config.raster_config = fml::Thread::ThreadConfig(flutter::ThreadHost::ThreadHostConfig::MakeThreadName( - flutter::ThreadHost::Type::kRaster, threadLabel.UTF8String), + flutter::ThreadHost::Type::kRaster, thread_label.UTF8String), fml::Thread::ThreadPriority::kRaster); host_config.io_config = fml::Thread::ThreadConfig(flutter::ThreadHost::ThreadHostConfig::MakeThreadName( - flutter::ThreadHost::Type::kIo, threadLabel.UTF8String), + flutter::ThreadHost::Type::kIo, thread_label.UTF8String), fml::Thread::ThreadPriority::kNormal); return (flutter::ThreadHost){host_config}; @@ -858,7 +862,7 @@ - (BOOL)createShell:(NSString*)entrypoint NSString* threadLabel = [FlutterEngine generateThreadLabel:_labelPrefix]; _threadHost = std::make_shared(); - *_threadHost = [FlutterEngine makeThreadHost:threadLabel]; + *_threadHost = MakeThreadHost(threadLabel, settings); // Lambda captures by pointers to ObjC objects are fine here because the // create call is synchronous. @@ -873,10 +877,17 @@ - (BOOL)createShell:(NSString*)entrypoint flutter::Shell::CreateCallback on_create_rasterizer = [](flutter::Shell& shell) { return std::make_unique(shell); }; + fml::RefPtr ui_runner; + if (settings.merged_platform_ui_thread) { + FML_LOG(IMPORTANT) << "Warning: Using highly experimental merged thread mode."; + ui_runner = fml::MessageLoop::GetCurrent().GetTaskRunner(); + } else { + ui_runner = _threadHost->ui_thread->GetTaskRunner(); + } flutter::TaskRunners task_runners(threadLabel.UTF8String, // label fml::MessageLoop::GetCurrent().GetTaskRunner(), // platform _threadHost->raster_thread->GetTaskRunner(), // raster - _threadHost->ui_thread->GetTaskRunner(), // ui + ui_runner, // ui _threadHost->io_thread->GetTaskRunner() // io ); diff --git a/shell/platform/darwin/ios/framework/Source/FlutterEngineTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterEngineTest.mm index e0153800ed7a4..44c4427eac8e1 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterEngineTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterEngineTest.mm @@ -470,4 +470,26 @@ - (void)testEnableSemanticsWhenFlutterViewAccessibilityDidCall { XCTAssertTrue(engine.ensureSemanticsEnabledCalled); } +- (void)testCanMergePlatformAndUIThread { + auto settings = FLTDefaultSettingsForBundle(); + settings.merged_platform_ui_thread = true; + FlutterDartProject* project = [[FlutterDartProject alloc] initWithSettings:settings]; + FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar" project:project]; + [engine run]; + + XCTAssertEqual(engine.shell.GetTaskRunners().GetUITaskRunner(), + engine.shell.GetTaskRunners().GetPlatformTaskRunner()); +} + +- (void)testCanUnMergePlatformAndUIThread { + auto settings = FLTDefaultSettingsForBundle(); + settings.merged_platform_ui_thread = false; + FlutterDartProject* project = [[FlutterDartProject alloc] initWithSettings:settings]; + FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar" project:project]; + [engine run]; + + XCTAssertNotEqual(engine.shell.GetTaskRunners().GetUITaskRunner(), + engine.shell.GetTaskRunners().GetPlatformTaskRunner()); +} + @end