| // 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. |
| |
| #include "flutter/shell/platform/embedder/tests/embedder_config_builder.h" |
| |
| #include "flutter/runtime/dart_vm.h" |
| #include "flutter/shell/platform/embedder/embedder.h" |
| #include "tests/embedder_test_context.h" |
| #include "third_party/skia/include/core/SkBitmap.h" |
| #include "vulkan/vulkan_core.h" |
| |
| #ifdef SHELL_ENABLE_GL |
| #include "flutter/shell/platform/embedder/tests/embedder_test_compositor_gl.h" |
| #include "flutter/shell/platform/embedder/tests/embedder_test_context_gl.h" |
| #endif |
| |
| #ifdef SHELL_ENABLE_VULKAN |
| #include "flutter/shell/platform/embedder/tests/embedder_test_context_vulkan.h" |
| #include "flutter/vulkan/vulkan_device.h" |
| #endif |
| |
| #ifdef SHELL_ENABLE_METAL |
| #include "flutter/shell/platform/embedder/tests/embedder_test_context_metal.h" |
| #endif |
| |
| namespace flutter { |
| namespace testing { |
| |
| EmbedderConfigBuilder::EmbedderConfigBuilder( |
| EmbedderTestContext& context, |
| InitializationPreference preference) |
| : context_(context) { |
| project_args_.struct_size = sizeof(project_args_); |
| project_args_.shutdown_dart_vm_when_done = true; |
| project_args_.platform_message_callback = |
| [](const FlutterPlatformMessage* message, void* context) { |
| reinterpret_cast<EmbedderTestContext*>(context) |
| ->PlatformMessageCallback(message); |
| }; |
| |
| custom_task_runners_.struct_size = sizeof(FlutterCustomTaskRunners); |
| |
| #ifdef SHELL_ENABLE_GL |
| opengl_renderer_config_.struct_size = sizeof(FlutterOpenGLRendererConfig); |
| opengl_renderer_config_.make_current = [](void* context) -> bool { |
| return reinterpret_cast<EmbedderTestContextGL*>(context)->GLMakeCurrent(); |
| }; |
| opengl_renderer_config_.clear_current = [](void* context) -> bool { |
| return reinterpret_cast<EmbedderTestContextGL*>(context)->GLClearCurrent(); |
| }; |
| opengl_renderer_config_.present_with_info = |
| [](void* context, const FlutterPresentInfo* present_info) -> bool { |
| return reinterpret_cast<EmbedderTestContextGL*>(context)->GLPresent( |
| *present_info); |
| }; |
| opengl_renderer_config_.fbo_with_frame_info_callback = |
| [](void* context, const FlutterFrameInfo* frame_info) -> uint32_t { |
| return reinterpret_cast<EmbedderTestContextGL*>(context)->GLGetFramebuffer( |
| *frame_info); |
| }; |
| opengl_renderer_config_.populate_existing_damage = nullptr; |
| opengl_renderer_config_.make_resource_current = [](void* context) -> bool { |
| return reinterpret_cast<EmbedderTestContextGL*>(context) |
| ->GLMakeResourceCurrent(); |
| }; |
| opengl_renderer_config_.gl_proc_resolver = [](void* context, |
| const char* name) -> void* { |
| return reinterpret_cast<EmbedderTestContextGL*>(context)->GLGetProcAddress( |
| name); |
| }; |
| opengl_renderer_config_.fbo_reset_after_present = true; |
| opengl_renderer_config_.surface_transformation = |
| [](void* context) -> FlutterTransformation { |
| return reinterpret_cast<EmbedderTestContext*>(context) |
| ->GetRootSurfaceTransformation(); |
| }; |
| #endif |
| |
| #ifdef SHELL_ENABLE_METAL |
| InitializeMetalRendererConfig(); |
| #endif |
| |
| #ifdef SHELL_ENABLE_VULKAN |
| InitializeVulkanRendererConfig(); |
| #endif |
| |
| software_renderer_config_.struct_size = sizeof(FlutterSoftwareRendererConfig); |
| software_renderer_config_.surface_present_callback = |
| [](void* context, const void* allocation, size_t row_bytes, |
| size_t height) { |
| auto image_info = |
| SkImageInfo::MakeN32Premul(SkISize::Make(row_bytes / 4, height)); |
| SkBitmap bitmap; |
| if (!bitmap.installPixels(image_info, const_cast<void*>(allocation), |
| row_bytes)) { |
| FML_LOG(ERROR) << "Could not copy pixels for the software " |
| "composition from the engine."; |
| return false; |
| } |
| bitmap.setImmutable(); |
| return reinterpret_cast<EmbedderTestContextSoftware*>(context)->Present( |
| SkImage::MakeFromBitmap(bitmap)); |
| }; |
| |
| // The first argument is always the executable name. Don't make tests have to |
| // do this manually. |
| AddCommandLineArgument("embedder_unittest"); |
| |
| if (preference != InitializationPreference::kNoInitialize) { |
| SetAssetsPath(); |
| SetIsolateCreateCallbackHook(); |
| SetSemanticsCallbackHooks(); |
| SetLogMessageCallbackHook(); |
| SetLocalizationCallbackHooks(); |
| AddCommandLineArgument("--disable-observatory"); |
| |
| if (preference == InitializationPreference::kSnapshotsInitialize || |
| preference == InitializationPreference::kMultiAOTInitialize) { |
| SetSnapshots(); |
| } |
| if (preference == InitializationPreference::kAOTDataInitialize || |
| preference == InitializationPreference::kMultiAOTInitialize) { |
| SetAOTDataElf(); |
| } |
| } |
| } |
| |
| EmbedderConfigBuilder::~EmbedderConfigBuilder() = default; |
| |
| FlutterProjectArgs& EmbedderConfigBuilder::GetProjectArgs() { |
| return project_args_; |
| } |
| |
| void EmbedderConfigBuilder::SetSoftwareRendererConfig(SkISize surface_size) { |
| renderer_config_.type = FlutterRendererType::kSoftware; |
| renderer_config_.software = software_renderer_config_; |
| context_.SetupSurface(surface_size); |
| } |
| |
| void EmbedderConfigBuilder::SetOpenGLFBOCallBack() { |
| #ifdef SHELL_ENABLE_GL |
| // SetOpenGLRendererConfig must be called before this. |
| FML_CHECK(renderer_config_.type == FlutterRendererType::kOpenGL); |
| renderer_config_.open_gl.fbo_callback = [](void* context) -> uint32_t { |
| FlutterFrameInfo frame_info = {}; |
| // fbo_callback doesn't use the frame size information, only |
| // fbo_callback_with_frame_info does. |
| frame_info.struct_size = sizeof(FlutterFrameInfo); |
| frame_info.size.width = 0; |
| frame_info.size.height = 0; |
| return reinterpret_cast<EmbedderTestContextGL*>(context)->GLGetFramebuffer( |
| frame_info); |
| }; |
| #endif |
| } |
| |
| void EmbedderConfigBuilder::SetOpenGLPresentCallBack() { |
| #ifdef SHELL_ENABLE_GL |
| // SetOpenGLRendererConfig must be called before this. |
| FML_CHECK(renderer_config_.type == FlutterRendererType::kOpenGL); |
| renderer_config_.open_gl.present = [](void* context) -> bool { |
| // passing a placeholder fbo_id. |
| return reinterpret_cast<EmbedderTestContextGL*>(context)->GLPresent( |
| FlutterPresentInfo{ |
| .fbo_id = 0, |
| }); |
| }; |
| #endif |
| } |
| |
| void EmbedderConfigBuilder::SetRendererConfig(EmbedderTestContextType type, |
| SkISize surface_size) { |
| switch (type) { |
| case EmbedderTestContextType::kOpenGLContext: |
| SetOpenGLRendererConfig(surface_size); |
| break; |
| case EmbedderTestContextType::kMetalContext: |
| SetMetalRendererConfig(surface_size); |
| break; |
| case EmbedderTestContextType::kVulkanContext: |
| SetVulkanRendererConfig(surface_size); |
| break; |
| case EmbedderTestContextType::kSoftwareContext: |
| SetSoftwareRendererConfig(surface_size); |
| break; |
| } |
| } |
| |
| void EmbedderConfigBuilder::SetOpenGLRendererConfig(SkISize surface_size) { |
| #ifdef SHELL_ENABLE_GL |
| renderer_config_.type = FlutterRendererType::kOpenGL; |
| renderer_config_.open_gl = opengl_renderer_config_; |
| context_.SetupSurface(surface_size); |
| #endif |
| } |
| |
| void EmbedderConfigBuilder::SetMetalRendererConfig(SkISize surface_size) { |
| #ifdef SHELL_ENABLE_METAL |
| renderer_config_.type = FlutterRendererType::kMetal; |
| renderer_config_.metal = metal_renderer_config_; |
| context_.SetupSurface(surface_size); |
| #endif |
| } |
| |
| void EmbedderConfigBuilder::SetVulkanRendererConfig(SkISize surface_size) { |
| #ifdef SHELL_ENABLE_VULKAN |
| renderer_config_.type = FlutterRendererType::kVulkan; |
| renderer_config_.vulkan = vulkan_renderer_config_; |
| context_.SetupSurface(surface_size); |
| #endif |
| } |
| |
| void EmbedderConfigBuilder::SetAssetsPath() { |
| project_args_.assets_path = context_.GetAssetsPath().c_str(); |
| } |
| |
| void EmbedderConfigBuilder::SetSnapshots() { |
| if (auto mapping = context_.GetVMSnapshotData()) { |
| project_args_.vm_snapshot_data = mapping->GetMapping(); |
| project_args_.vm_snapshot_data_size = mapping->GetSize(); |
| } |
| |
| if (auto mapping = context_.GetVMSnapshotInstructions()) { |
| project_args_.vm_snapshot_instructions = mapping->GetMapping(); |
| project_args_.vm_snapshot_instructions_size = mapping->GetSize(); |
| } |
| |
| if (auto mapping = context_.GetIsolateSnapshotData()) { |
| project_args_.isolate_snapshot_data = mapping->GetMapping(); |
| project_args_.isolate_snapshot_data_size = mapping->GetSize(); |
| } |
| |
| if (auto mapping = context_.GetIsolateSnapshotInstructions()) { |
| project_args_.isolate_snapshot_instructions = mapping->GetMapping(); |
| project_args_.isolate_snapshot_instructions_size = mapping->GetSize(); |
| } |
| } |
| |
| void EmbedderConfigBuilder::SetAOTDataElf() { |
| project_args_.aot_data = context_.GetAOTData(); |
| } |
| |
| void EmbedderConfigBuilder::SetIsolateCreateCallbackHook() { |
| project_args_.root_isolate_create_callback = |
| EmbedderTestContext::GetIsolateCreateCallbackHook(); |
| } |
| |
| void EmbedderConfigBuilder::SetSemanticsCallbackHooks() { |
| project_args_.update_semantics_callback = |
| context_.GetUpdateSemanticsCallbackHook(); |
| project_args_.update_semantics_node_callback = |
| context_.GetUpdateSemanticsNodeCallbackHook(); |
| project_args_.update_semantics_custom_action_callback = |
| context_.GetUpdateSemanticsCustomActionCallbackHook(); |
| } |
| |
| void EmbedderConfigBuilder::SetLogMessageCallbackHook() { |
| project_args_.log_message_callback = |
| EmbedderTestContext::GetLogMessageCallbackHook(); |
| } |
| |
| void EmbedderConfigBuilder::SetLogTag(std::string tag) { |
| log_tag_ = std::move(tag); |
| project_args_.log_tag = log_tag_.c_str(); |
| } |
| |
| void EmbedderConfigBuilder::SetLocalizationCallbackHooks() { |
| project_args_.compute_platform_resolved_locale_callback = |
| EmbedderTestContext::GetComputePlatformResolvedLocaleCallbackHook(); |
| } |
| |
| void EmbedderConfigBuilder::SetExecutableName(std::string executable_name) { |
| if (executable_name.empty()) { |
| return; |
| } |
| command_line_arguments_[0] = std::move(executable_name); |
| } |
| |
| void EmbedderConfigBuilder::SetDartEntrypoint(std::string entrypoint) { |
| if (entrypoint.empty()) { |
| return; |
| } |
| |
| dart_entrypoint_ = std::move(entrypoint); |
| project_args_.custom_dart_entrypoint = dart_entrypoint_.c_str(); |
| } |
| |
| void EmbedderConfigBuilder::AddCommandLineArgument(std::string arg) { |
| if (arg.empty()) { |
| return; |
| } |
| |
| command_line_arguments_.emplace_back(std::move(arg)); |
| } |
| |
| void EmbedderConfigBuilder::AddDartEntrypointArgument(std::string arg) { |
| if (arg.empty()) { |
| return; |
| } |
| |
| dart_entrypoint_arguments_.emplace_back(std::move(arg)); |
| } |
| |
| void EmbedderConfigBuilder::SetPlatformTaskRunner( |
| const FlutterTaskRunnerDescription* runner) { |
| if (runner == nullptr) { |
| return; |
| } |
| custom_task_runners_.platform_task_runner = runner; |
| project_args_.custom_task_runners = &custom_task_runners_; |
| } |
| |
| void EmbedderConfigBuilder::SetupVsyncCallback() { |
| project_args_.vsync_callback = [](void* user_data, intptr_t baton) { |
| auto context = reinterpret_cast<EmbedderTestContext*>(user_data); |
| context->RunVsyncCallback(baton); |
| }; |
| } |
| |
| FlutterRendererConfig& EmbedderConfigBuilder::GetRendererConfig() { |
| return renderer_config_; |
| } |
| |
| void EmbedderConfigBuilder::SetRenderTaskRunner( |
| const FlutterTaskRunnerDescription* runner) { |
| if (runner == nullptr) { |
| return; |
| } |
| |
| custom_task_runners_.render_task_runner = runner; |
| project_args_.custom_task_runners = &custom_task_runners_; |
| } |
| |
| void EmbedderConfigBuilder::SetPlatformMessageCallback( |
| const std::function<void(const FlutterPlatformMessage*)>& callback) { |
| context_.SetPlatformMessageCallback(callback); |
| } |
| |
| void EmbedderConfigBuilder::SetCompositor(bool avoid_backing_store_cache) { |
| context_.SetupCompositor(); |
| auto& compositor = context_.GetCompositor(); |
| compositor_.struct_size = sizeof(compositor_); |
| compositor_.user_data = &compositor; |
| compositor_.create_backing_store_callback = |
| [](const FlutterBackingStoreConfig* config, // |
| FlutterBackingStore* backing_store_out, // |
| void* user_data // |
| ) { |
| return reinterpret_cast<EmbedderTestCompositor*>(user_data) |
| ->CreateBackingStore(config, backing_store_out); |
| }; |
| compositor_.collect_backing_store_callback = |
| [](const FlutterBackingStore* backing_store, // |
| void* user_data // |
| ) { |
| return reinterpret_cast<EmbedderTestCompositor*>(user_data) |
| ->CollectBackingStore(backing_store); |
| }; |
| compositor_.present_layers_callback = [](const FlutterLayer** layers, // |
| size_t layers_count, // |
| void* user_data // |
| ) { |
| return reinterpret_cast<EmbedderTestCompositor*>(user_data)->Present( |
| layers, // |
| layers_count // |
| |
| ); |
| }; |
| compositor_.avoid_backing_store_cache = avoid_backing_store_cache; |
| project_args_.compositor = &compositor_; |
| } |
| |
| FlutterCompositor& EmbedderConfigBuilder::GetCompositor() { |
| return compositor_; |
| } |
| |
| void EmbedderConfigBuilder::SetRenderTargetType( |
| EmbedderTestBackingStoreProducer::RenderTargetType type, |
| FlutterSoftwarePixelFormat software_pixfmt) { |
| auto& compositor = context_.GetCompositor(); |
| // TODO(wrightgeorge): figure out a better way of plumbing through the |
| // GrDirectContext |
| compositor.SetBackingStoreProducer( |
| std::make_unique<EmbedderTestBackingStoreProducer>( |
| compositor.GetGrContext(), type, software_pixfmt)); |
| } |
| |
| UniqueEngine EmbedderConfigBuilder::LaunchEngine() const { |
| return SetupEngine(true); |
| } |
| |
| UniqueEngine EmbedderConfigBuilder::InitializeEngine() const { |
| return SetupEngine(false); |
| } |
| |
| UniqueEngine EmbedderConfigBuilder::SetupEngine(bool run) const { |
| FlutterEngine engine = nullptr; |
| FlutterProjectArgs project_args = project_args_; |
| |
| std::vector<const char*> args; |
| args.reserve(command_line_arguments_.size()); |
| |
| for (const auto& arg : command_line_arguments_) { |
| args.push_back(arg.c_str()); |
| } |
| |
| if (!args.empty()) { |
| project_args.command_line_argv = args.data(); |
| project_args.command_line_argc = args.size(); |
| } else { |
| // Clear it out in case this is not the first engine launch from the |
| // embedder config builder. |
| project_args.command_line_argv = nullptr; |
| project_args.command_line_argc = 0; |
| } |
| |
| std::vector<const char*> dart_args; |
| dart_args.reserve(dart_entrypoint_arguments_.size()); |
| |
| for (const auto& arg : dart_entrypoint_arguments_) { |
| dart_args.push_back(arg.c_str()); |
| } |
| |
| if (!dart_args.empty()) { |
| project_args.dart_entrypoint_argv = dart_args.data(); |
| project_args.dart_entrypoint_argc = dart_args.size(); |
| } else { |
| // Clear it out in case this is not the first engine launch from the |
| // embedder config builder. |
| project_args.dart_entrypoint_argv = nullptr; |
| project_args.dart_entrypoint_argc = 0; |
| } |
| |
| auto result = |
| run ? FlutterEngineRun(FLUTTER_ENGINE_VERSION, &renderer_config_, |
| &project_args, &context_, &engine) |
| : FlutterEngineInitialize(FLUTTER_ENGINE_VERSION, &renderer_config_, |
| &project_args, &context_, &engine); |
| |
| if (result != kSuccess) { |
| return {}; |
| } |
| |
| return UniqueEngine{engine}; |
| } |
| |
| #ifdef SHELL_ENABLE_METAL |
| |
| void EmbedderConfigBuilder::InitializeMetalRendererConfig() { |
| if (context_.GetContextType() != EmbedderTestContextType::kMetalContext) { |
| return; |
| } |
| |
| metal_renderer_config_.struct_size = sizeof(metal_renderer_config_); |
| EmbedderTestContextMetal& metal_context = |
| reinterpret_cast<EmbedderTestContextMetal&>(context_); |
| |
| metal_renderer_config_.device = |
| metal_context.GetTestMetalContext()->GetMetalDevice(); |
| metal_renderer_config_.present_command_queue = |
| metal_context.GetTestMetalContext()->GetMetalCommandQueue(); |
| metal_renderer_config_.get_next_drawable_callback = |
| [](void* user_data, const FlutterFrameInfo* frame_info) { |
| return reinterpret_cast<EmbedderTestContextMetal*>(user_data) |
| ->GetNextDrawable(frame_info); |
| }; |
| metal_renderer_config_.present_drawable_callback = |
| [](void* user_data, const FlutterMetalTexture* texture) -> bool { |
| EmbedderTestContextMetal* metal_context = |
| reinterpret_cast<EmbedderTestContextMetal*>(user_data); |
| return metal_context->Present(texture->texture_id); |
| }; |
| metal_renderer_config_.external_texture_frame_callback = |
| [](void* user_data, int64_t texture_id, size_t width, size_t height, |
| FlutterMetalExternalTexture* texture_out) -> bool { |
| EmbedderTestContextMetal* metal_context = |
| reinterpret_cast<EmbedderTestContextMetal*>(user_data); |
| return metal_context->PopulateExternalTexture(texture_id, width, height, |
| texture_out); |
| }; |
| } |
| |
| #endif // SHELL_ENABLE_METAL |
| |
| #ifdef SHELL_ENABLE_VULKAN |
| |
| void EmbedderConfigBuilder::InitializeVulkanRendererConfig() { |
| if (context_.GetContextType() != EmbedderTestContextType::kVulkanContext) { |
| return; |
| } |
| |
| vulkan_renderer_config_.struct_size = sizeof(FlutterVulkanRendererConfig); |
| vulkan_renderer_config_.version = |
| static_cast<EmbedderTestContextVulkan&>(context_) |
| .vulkan_context_->application_->GetAPIVersion(); |
| vulkan_renderer_config_.instance = |
| static_cast<EmbedderTestContextVulkan&>(context_) |
| .vulkan_context_->application_->GetInstance(); |
| vulkan_renderer_config_.physical_device = |
| static_cast<EmbedderTestContextVulkan&>(context_) |
| .vulkan_context_->device_->GetPhysicalDeviceHandle(); |
| vulkan_renderer_config_.device = |
| static_cast<EmbedderTestContextVulkan&>(context_) |
| .vulkan_context_->device_->GetHandle(); |
| vulkan_renderer_config_.queue_family_index = |
| static_cast<EmbedderTestContextVulkan&>(context_) |
| .vulkan_context_->device_->GetGraphicsQueueIndex(); |
| vulkan_renderer_config_.queue = |
| static_cast<EmbedderTestContextVulkan&>(context_) |
| .vulkan_context_->device_->GetQueueHandle(); |
| vulkan_renderer_config_.get_instance_proc_address_callback = |
| [](void* context, FlutterVulkanInstanceHandle instance, |
| const char* name) -> void* { |
| auto proc_addr = reinterpret_cast<EmbedderTestContextVulkan*>(context) |
| ->vulkan_context_->vk_->GetInstanceProcAddr; |
| return reinterpret_cast<void*>(proc_addr); |
| }; |
| vulkan_renderer_config_.get_next_image_callback = |
| [](void* context, |
| const FlutterFrameInfo* frame_info) -> FlutterVulkanImage { |
| VkImage image = |
| reinterpret_cast<EmbedderTestContextVulkan*>(context)->GetNextImage( |
| {static_cast<int>(frame_info->size.width), |
| static_cast<int>(frame_info->size.height)}); |
| return { |
| .struct_size = sizeof(FlutterVulkanImage), |
| .image = reinterpret_cast<uint64_t>(image), |
| .format = VK_FORMAT_R8G8B8A8_UNORM, |
| }; |
| }; |
| vulkan_renderer_config_.present_image_callback = |
| [](void* context, const FlutterVulkanImage* image) -> bool { |
| return reinterpret_cast<EmbedderTestContextVulkan*>(context)->PresentImage( |
| reinterpret_cast<VkImage>(image->image)); |
| }; |
| } |
| |
| #endif |
| |
| } // namespace testing |
| } // namespace flutter |