// Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #define FML_USED_ON_EMBEDDER #include #include #include #include "flutter/assets/asset_manager.h" #include "flutter/assets/directory_asset_bundle.h" #include "flutter/flow/embedded_views.h" #include "flutter/fml/build_config.h" #include "flutter/fml/file.h" #include "flutter/fml/make_copyable.h" #include "flutter/fml/message_loop.h" #include "flutter/fml/paths.h" #include "flutter/fml/synchronization/waitable_event.h" #include "flutter/fml/task_runner.h" #include "flutter/shell/common/platform_view.h" #include "flutter/shell/common/rasterizer.h" #include "flutter/shell/common/shell.h" #include "flutter/shell/common/switches.h" #include "flutter/shell/common/thread_host.h" #include "flutter/shell/gpu/gpu_surface_software.h" #include "flutter/third_party/abseil-cpp/absl/base/no_destructor.h" #include "third_party/dart/runtime/include/bin/dart_io_api.h" #include "third_party/dart/runtime/include/dart_api.h" #include "third_party/skia/include/core/SkSurface.h" // Impeller should only be enabled if the Vulkan backend is enabled. #define ALLOW_IMPELLER (IMPELLER_SUPPORTS_RENDERING && IMPELLER_ENABLE_VULKAN) #if ALLOW_IMPELLER #include // nogncheck #include "impeller/entity/vk/entity_shaders_vk.h" // nogncheck #include "impeller/entity/vk/framebuffer_blend_shaders_vk.h" // nogncheck #include "impeller/entity/vk/modern_shaders_vk.h" // nogncheck #include "impeller/renderer/backend/vulkan/context_vk.h" // nogncheck #include "impeller/renderer/backend/vulkan/surface_context_vk.h" // nogncheck #include "impeller/renderer/context.h" // nogncheck #include "impeller/renderer/vk/compute_shaders_vk.h" // nogncheck #include "shell/gpu/gpu_surface_vulkan_impeller.h" // nogncheck static std::vector> ShaderLibraryMappings() { return { std::make_shared(impeller_entity_shaders_vk_data, impeller_entity_shaders_vk_length), std::make_shared(impeller_modern_shaders_vk_data, impeller_modern_shaders_vk_length), std::make_shared( impeller_framebuffer_blend_shaders_vk_data, impeller_framebuffer_blend_shaders_vk_length), std::make_shared( impeller_compute_shaders_vk_data, impeller_compute_shaders_vk_length), }; } struct ImpellerVulkanContextHolder { ImpellerVulkanContextHolder() = default; ImpellerVulkanContextHolder(ImpellerVulkanContextHolder&&) = default; std::shared_ptr context; std::shared_ptr surface_context; bool Initialize(bool enable_validation); }; bool ImpellerVulkanContextHolder::Initialize(bool enable_validation) { impeller::ContextVK::Settings context_settings; context_settings.proc_address_callback = &vkGetInstanceProcAddr; context_settings.shader_libraries_data = ShaderLibraryMappings(); context_settings.cache_directory = fml::paths::GetCachesDirectory(); context_settings.enable_validation = enable_validation; context = impeller::ContextVK::Create(std::move(context_settings)); if (!context || !context->IsValid()) { VALIDATION_LOG << "Could not create Vulkan context."; return false; } impeller::vk::SurfaceKHR vk_surface; impeller::vk::HeadlessSurfaceCreateInfoEXT surface_create_info; auto res = context->GetInstance().createHeadlessSurfaceEXT( &surface_create_info, // surface create info nullptr, // allocator &vk_surface // surface ); if (res != impeller::vk::Result::eSuccess) { VALIDATION_LOG << "Could not create surface for tester " << impeller::vk::to_string(res); return false; } impeller::vk::UniqueSurfaceKHR surface{vk_surface, context->GetInstance()}; surface_context = context->CreateSurfaceContext(); if (!surface_context->SetWindowSurface(std::move(surface), impeller::ISize{1, 1})) { VALIDATION_LOG << "Could not set up surface for context."; return false; } return true; } #else struct ImpellerVulkanContextHolder {}; #endif // IMPELLER_SUPPORTS_RENDERING #if defined(FML_OS_WIN) #include #endif // defined(FML_OS_WIN) #if defined(FML_OS_POSIX) #include #endif // defined(FML_OS_POSIX) namespace flutter { static absl::NoDestructor> g_shell; static constexpr int64_t kImplicitViewId = 0ll; static void ConfigureShell(Shell* shell) { auto device_pixel_ratio = 3.0; auto physical_width = 2400.0; // 800 at 3x resolution. auto physical_height = 1800.0; // 600 at 3x resolution. std::vector> displays; displays.push_back(std::make_unique( 0, 60, physical_width, physical_height, device_pixel_ratio)); shell->OnDisplayUpdates(std::move(displays)); flutter::ViewportMetrics metrics{}; metrics.device_pixel_ratio = device_pixel_ratio; metrics.physical_width = physical_width; metrics.physical_height = physical_height; metrics.display_id = 0; shell->GetPlatformView()->SetViewportMetrics(kImplicitViewId, metrics); } class TesterExternalViewEmbedder : public ExternalViewEmbedder { // |ExternalViewEmbedder| DlCanvas* GetRootCanvas() override { return nullptr; } // |ExternalViewEmbedder| void CancelFrame() override {} // |ExternalViewEmbedder| void BeginFrame(GrDirectContext* context, const fml::RefPtr& raster_thread_merger) override {} // |ExternalViewEmbedder| void PrepareFlutterView(SkISize frame_size, double device_pixel_ratio) override {} // |ExternalViewEmbedder| void PrerollCompositeEmbeddedView( int64_t view_id, std::unique_ptr params) override {} // |ExternalViewEmbedder| DlCanvas* CompositeEmbeddedView(int64_t view_id) override { return &builder_; } private: DisplayListBuilder builder_; }; class TesterGPUSurfaceSoftware : public GPUSurfaceSoftware { public: TesterGPUSurfaceSoftware(GPUSurfaceSoftwareDelegate* delegate, bool render_to_surface) : GPUSurfaceSoftware(delegate, render_to_surface) {} bool EnableRasterCache() const override { return false; } }; class TesterPlatformView : public PlatformView, public GPUSurfaceSoftwareDelegate { public: TesterPlatformView(Delegate& delegate, const TaskRunners& task_runners, ImpellerVulkanContextHolder&& impeller_context_holder) : PlatformView(delegate, task_runners), impeller_context_holder_(std::move(impeller_context_holder)) {} ~TesterPlatformView() { #if ALLOW_IMPELLER if (impeller_context_holder_.context) { impeller_context_holder_.context->Shutdown(); } #endif } // |PlatformView| std::shared_ptr GetImpellerContext() const override { #if ALLOW_IMPELLER return std::static_pointer_cast( impeller_context_holder_.context); #else return nullptr; #endif // ALLOW_IMPELLER } // |PlatformView| std::unique_ptr CreateRenderingSurface() override { #if ALLOW_IMPELLER if (delegate_.OnPlatformViewGetSettings().enable_impeller) { FML_DCHECK(impeller_context_holder_.context); auto surface = std::make_unique( impeller_context_holder_.surface_context); FML_DCHECK(surface->IsValid()); return surface; } #endif // ALLOW_IMPELLER auto surface = std::make_unique( this, true /* render to surface */); FML_DCHECK(surface->IsValid()); return surface; } // |GPUSurfaceSoftwareDelegate| sk_sp AcquireBackingStore(const SkISize& size) override { if (sk_surface_ != nullptr && SkISize::Make(sk_surface_->width(), sk_surface_->height()) == size) { // The old and new surface sizes are the same. Nothing to do here. return sk_surface_; } SkImageInfo info = SkImageInfo::MakeN32(size.fWidth, size.fHeight, kPremul_SkAlphaType, SkColorSpace::MakeSRGB()); sk_surface_ = SkSurfaces::Raster(info, nullptr); if (sk_surface_ == nullptr) { FML_LOG(ERROR) << "Could not create backing store for software rendering."; return nullptr; } return sk_surface_; } // |GPUSurfaceSoftwareDelegate| bool PresentBackingStore(sk_sp backing_store) override { return true; } // |PlatformView| std::shared_ptr CreateExternalViewEmbedder() override { return external_view_embedder_; } private: sk_sp sk_surface_ = nullptr; [[maybe_unused]] ImpellerVulkanContextHolder impeller_context_holder_; std::shared_ptr external_view_embedder_ = std::make_shared(); }; // Checks whether the engine's main Dart isolate has no pending work. If so, // then exit the given message loop. class ScriptCompletionTaskObserver { public: ScriptCompletionTaskObserver(Shell& shell, fml::RefPtr main_task_runner, bool run_forever) : shell_(shell), main_task_runner_(std::move(main_task_runner)), run_forever_(run_forever) {} int GetExitCodeForLastError() const { return static_cast(last_error_.value_or(DartErrorCode::NoError)); } void DidProcessTask() { last_error_ = shell_.GetUIIsolateLastError(); if (shell_.EngineHasLivePorts()) { // The UI isolate still has live ports and is running. Nothing to do // just yet. return; } if (run_forever_) { // We need this script to run forever. We have already recorded the last // error. Keep going. return; } if (!has_terminated_) { // Only try to terminate the loop once. has_terminated_ = true; fml::TaskRunner::RunNowOrPostTask(main_task_runner_, []() { fml::MessageLoop::GetCurrent().Terminate(); }); } } private: Shell& shell_; fml::RefPtr main_task_runner_; bool run_forever_ = false; std::optional last_error_; bool has_terminated_ = false; FML_DISALLOW_COPY_AND_ASSIGN(ScriptCompletionTaskObserver); }; // Processes spawned via dart:io inherit their signal handling from the parent // process. As part of spawning, the spawner blocks signals temporarily, so we // need to explicitly unblock the signals we care about in the new process. In // particular, we need to unblock SIGPROF for CPU profiling to work on the // mutator thread in the main isolate in this process (threads spawned by the VM // know about this limitation and automatically have this signal unblocked). static void UnblockSIGPROF() { #if defined(FML_OS_POSIX) sigset_t set; sigemptyset(&set); sigaddset(&set, SIGPROF); pthread_sigmask(SIG_UNBLOCK, &set, NULL); #endif // defined(FML_OS_POSIX) } int RunTester(const flutter::Settings& settings, bool run_forever, bool multithreaded) { const auto thread_label = "io.flutter.test."; // Necessary if we want to use the CPU profiler on the main isolate's mutator // thread. // // OSX WARNING: avoid spawning additional threads before this call due to a // kernel bug that may enable SIGPROF on an unintended thread in the process. UnblockSIGPROF(); fml::MessageLoop::EnsureInitializedForCurrentThread(); auto current_task_runner = fml::MessageLoop::GetCurrent().GetTaskRunner(); std::unique_ptr threadhost; fml::RefPtr platform_task_runner; fml::RefPtr raster_task_runner; fml::RefPtr ui_task_runner; fml::RefPtr io_task_runner; if (multithreaded) { threadhost = std::make_unique( thread_label, ThreadHost::Type::kPlatform | ThreadHost::Type::kIo | ThreadHost::Type::kUi | ThreadHost::Type::kRaster); platform_task_runner = current_task_runner; raster_task_runner = threadhost->raster_thread->GetTaskRunner(); ui_task_runner = threadhost->ui_thread->GetTaskRunner(); io_task_runner = threadhost->io_thread->GetTaskRunner(); } else { platform_task_runner = raster_task_runner = ui_task_runner = io_task_runner = current_task_runner; } const flutter::TaskRunners task_runners(thread_label, // dart thread label platform_task_runner, // platform raster_task_runner, // raster ui_task_runner, // ui io_task_runner // io ); ImpellerVulkanContextHolder impeller_context_holder; #if ALLOW_IMPELLER if (settings.enable_impeller) { if (!impeller_context_holder.Initialize( settings.enable_vulkan_validation)) { return EXIT_FAILURE; } } #endif // ALLOW_IMPELLER Shell::CreateCallback on_create_platform_view = fml::MakeCopyable([impeller_context_holder = std::move( impeller_context_holder)](Shell& shell) mutable { return std::make_unique( shell, shell.GetTaskRunners(), std::move(impeller_context_holder)); }); Shell::CreateCallback on_create_rasterizer = [](Shell& shell) { return std::make_unique( shell, Rasterizer::MakeGpuImageBehavior::kBitmap); }; g_shell->reset(Shell::Create(flutter::PlatformData(), // task_runners, // settings, // on_create_platform_view, // on_create_rasterizer // ) .release()); auto shell = g_shell->get(); if (!shell || !shell->IsSetup()) { FML_LOG(ERROR) << "Could not set up the shell."; return EXIT_FAILURE; } if (settings.application_kernel_asset.empty()) { FML_LOG(ERROR) << "Dart kernel file not specified."; return EXIT_FAILURE; } shell->GetPlatformView()->NotifyCreated(); // Initialize default testing locales. There is no platform to // pass locales on the tester, so to retain expected locale behavior, // we emulate it in here by passing in 'en_US' and 'zh_CN' as test locales. const char* locale_json = "{\"method\":\"setLocale\",\"args\":[\"en\",\"US\",\"\",\"\",\"zh\"," "\"CN\",\"\",\"\"]}"; auto locale_bytes = fml::MallocMapping::Copy( locale_json, locale_json + std::strlen(locale_json)); fml::RefPtr response; shell->GetPlatformView()->DispatchPlatformMessage( std::make_unique( "flutter/localization", std::move(locale_bytes), response)); std::initializer_list protection = { fml::FileMapping::Protection::kRead}; auto main_dart_file_mapping = std::make_unique( fml::OpenFile( fml::paths::AbsolutePath(settings.application_kernel_asset).c_str(), false, fml::FilePermission::kRead), protection); auto isolate_configuration = IsolateConfiguration::CreateForKernel(std::move(main_dart_file_mapping)); if (!isolate_configuration) { FML_LOG(ERROR) << "Could create isolate configuration."; return EXIT_FAILURE; } auto asset_manager = std::make_shared(); asset_manager->PushBack(std::make_unique( fml::Duplicate(settings.assets_dir), true)); asset_manager->PushBack(std::make_unique( fml::OpenDirectory(settings.assets_path.c_str(), false, fml::FilePermission::kRead), true)); RunConfiguration run_configuration(std::move(isolate_configuration), std::move(asset_manager)); // The script completion task observer that will be installed on the UI thread // that watched if the engine has any live ports. ScriptCompletionTaskObserver completion_observer( *shell, // a valid shell fml::MessageLoop::GetCurrent() .GetTaskRunner(), // the message loop to terminate run_forever // should the exit be ignored ); bool engine_did_run = false; fml::AutoResetWaitableEvent latch; auto task_observer_add = [&completion_observer]() { fml::MessageLoop::GetCurrent().AddTaskObserver( reinterpret_cast(&completion_observer), [&completion_observer]() { completion_observer.DidProcessTask(); }); }; auto task_observer_remove = [&completion_observer, &latch]() { fml::MessageLoop::GetCurrent().RemoveTaskObserver( reinterpret_cast(&completion_observer)); latch.Signal(); }; shell->RunEngine(std::move(run_configuration), [&engine_did_run, &ui_task_runner, &task_observer_add](Engine::RunStatus run_status) mutable { if (run_status != flutter::Engine::RunStatus::Failure) { engine_did_run = true; // Now that our engine is initialized we can install the // ScriptCompletionTaskObserver fml::TaskRunner::RunNowOrPostTask(ui_task_runner, task_observer_add); } }); ConfigureShell(shell); // Run the message loop and wait for the script to do its thing. fml::MessageLoop::GetCurrent().Run(); // Cleanup the completion observer synchronously as it is living on the // stack. fml::TaskRunner::RunNowOrPostTask(ui_task_runner, task_observer_remove); latch.Wait(); delete g_shell->release(); if (!engine_did_run) { // If the engine itself didn't have a chance to run, there is no point in // asking it if there was an error. Signal a failure unconditionally. return EXIT_FAILURE; } return completion_observer.GetExitCodeForLastError(); } #ifdef _WIN32 #define EXPORTED __declspec(dllexport) #else #define EXPORTED __attribute__((visibility("default"))) #endif extern "C" { EXPORTED Dart_Handle LoadLibraryFromKernel(const char* path) { std::shared_ptr mapping = fml::FileMapping::CreateReadOnly(path); if (!mapping) { return Dart_Null(); } return DartIsolate::LoadLibraryFromKernel(mapping); } EXPORTED Dart_Handle LookupEntryPoint(const char* uri, const char* name) { if (!uri || !name) { return Dart_Null(); } Dart_Handle lib = Dart_LookupLibrary(Dart_NewStringFromCString(uri)); if (Dart_IsError(lib)) { return lib; } return Dart_GetField(lib, Dart_NewStringFromCString(name)); } EXPORTED void Spawn(const char* entrypoint, const char* route) { auto shell = g_shell->get(); auto isolate = Dart_CurrentIsolate(); auto spawn_task = [shell, entrypoint = std::string(entrypoint), route = std::string(route)]() { auto configuration = RunConfiguration::InferFromSettings( shell->GetSettings(), /*io_worker=*/nullptr, /*launch_type=*/IsolateLaunchType::kExistingGroup); configuration.SetEntrypoint(entrypoint); Shell::CreateCallback on_create_platform_view = fml::MakeCopyable([](Shell& shell) mutable { ImpellerVulkanContextHolder impeller_context_holder; return std::make_unique( shell, shell.GetTaskRunners(), std::move(impeller_context_holder)); }); Shell::CreateCallback on_create_rasterizer = [](Shell& shell) { return std::make_unique( shell, Rasterizer::MakeGpuImageBehavior::kBitmap); }; // Spawn a shell, and keep it running until it has no live ports, then // delete it on the platform thread. auto spawned_shell = shell ->Spawn(std::move(configuration), route, on_create_platform_view, on_create_rasterizer) .release(); ConfigureShell(spawned_shell); fml::TaskRunner::RunNowOrPostTask( spawned_shell->GetTaskRunners().GetUITaskRunner(), [spawned_shell]() { fml::MessageLoop::GetCurrent().AddTaskObserver( reinterpret_cast(spawned_shell), [spawned_shell]() { if (spawned_shell->EngineHasLivePorts()) { return; } fml::MessageLoop::GetCurrent().RemoveTaskObserver( reinterpret_cast(spawned_shell)); // Shell must be deleted on the platform task runner. fml::TaskRunner::RunNowOrPostTask( spawned_shell->GetTaskRunners().GetPlatformTaskRunner(), [spawned_shell]() { delete spawned_shell; }); }); }); }; Dart_ExitIsolate(); // The global shell pointer is never deleted, short of application exit. // This UI task runner cannot be latched because it will block spawning a new // shell in the case where flutter_tester is running multithreaded. fml::TaskRunner::RunNowOrPostTask( shell->GetTaskRunners().GetPlatformTaskRunner(), spawn_task); Dart_EnterIsolate(isolate); } EXPORTED void ForceShutdownIsolate() { // Enable Isolate.exit(). FML_DCHECK(Dart_CurrentIsolate() != nullptr); Dart_Handle isolate_lib = Dart_LookupLibrary(tonic::ToDart("dart:isolate")); FML_CHECK(!tonic::CheckAndHandleError(isolate_lib)); Dart_Handle isolate_type = Dart_GetNonNullableType( isolate_lib, tonic::ToDart("Isolate"), 0, nullptr); FML_CHECK(!tonic::CheckAndHandleError(isolate_type)); Dart_Handle result = Dart_SetField(isolate_type, tonic::ToDart("_mayExit"), Dart_True()); FML_CHECK(!tonic::CheckAndHandleError(result)); } } } // namespace flutter int main(int argc, char* argv[]) { dart::bin::SetExecutableName(argv[0]); dart::bin::SetExecutableArguments(argc - 1, argv); auto command_line = fml::CommandLineFromPlatformOrArgcArgv(argc, argv); if (command_line.HasOption(flutter::FlagForSwitch(flutter::Switch::Help))) { flutter::PrintUsage("flutter_tester"); return EXIT_SUCCESS; } auto settings = flutter::SettingsFromCommandLine(command_line); if (!command_line.positional_args().empty()) { // The tester may not use the switch for the main dart file path. Specifying // it as a positional argument instead. settings.application_kernel_asset = command_line.positional_args()[0]; } if (settings.application_kernel_asset.empty()) { FML_LOG(ERROR) << "Dart kernel file not specified."; return EXIT_FAILURE; } settings.leak_vm = false; settings.enable_platform_isolates = true; if (settings.icu_data_path.empty()) { settings.icu_data_path = "icudtl.dat"; } // The tools that read logs get confused if there is a log tag specified. settings.log_tag = ""; settings.log_message_callback = [](const std::string& tag, const std::string& message) { if (!tag.empty()) { std::cout << tag << ": "; } std::cout << message << std::endl; }; settings.task_observer_add = [](intptr_t key, const fml::closure& callback) { fml::MessageLoop::GetCurrent().AddTaskObserver(key, callback); }; settings.task_observer_remove = [](intptr_t key) { fml::MessageLoop::GetCurrent().RemoveTaskObserver(key); }; settings.unhandled_exception_callback = [](const std::string& error, const std::string& stack_trace) { FML_LOG(ERROR) << "Unhandled exception" << std::endl << "Exception: " << error << std::endl << "Stack trace: " << stack_trace; ::exit(1); return true; }; #if defined(FML_OS_WIN) CoInitializeEx(nullptr, COINIT_MULTITHREADED); #endif // defined(FML_OS_WIN) return flutter::RunTester(settings, command_line.HasOption(flutter::FlagForSwitch( flutter::Switch::RunForever)), command_line.HasOption(flutter::FlagForSwitch( flutter::Switch::ForceMultithreading))); }