| // 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 | |
| #define RAPIDJSON_HAS_STDSTRING 1 | |
| #include <cstring> | |
| #include <iostream> | |
| #include <memory> | |
| #include <set> | |
| #include <string> | |
| #include <vector> | |
| #include "flutter/fml/build_config.h" | |
| #include "flutter/fml/closure.h" | |
| #include "flutter/fml/make_copyable.h" | |
| #include "flutter/fml/native_library.h" | |
| #include "flutter/fml/thread.h" | |
| #include "third_party/dart/runtime/bin/elf_loader.h" | |
| #include "third_party/dart/runtime/include/dart_native_api.h" | |
| #include "third_party/skia/include/core/SkSurface.h" | |
| #include "third_party/skia/include/gpu/GpuTypes.h" | |
| #include "third_party/skia/include/gpu/GrBackendSurface.h" | |
| #include "third_party/skia/include/gpu/ganesh/SkSurfaceGanesh.h" | |
| #if !defined(FLUTTER_NO_EXPORT) | |
| #if FML_OS_WIN | |
| #define FLUTTER_EXPORT __declspec(dllexport) | |
| #else // FML_OS_WIN | |
| #define FLUTTER_EXPORT __attribute__((visibility("default"))) | |
| #endif // FML_OS_WIN | |
| #endif // !FLUTTER_NO_EXPORT | |
| extern "C" { | |
| #if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG | |
| // Used for debugging dart:* sources. | |
| extern const uint8_t kPlatformStrongDill[]; | |
| extern const intptr_t kPlatformStrongDillSize; | |
| #endif // FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG | |
| } | |
| #include "flutter/assets/directory_asset_bundle.h" | |
| #include "flutter/common/graphics/persistent_cache.h" | |
| #include "flutter/common/task_runners.h" | |
| #include "flutter/fml/command_line.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/trace_event.h" | |
| #include "flutter/shell/common/rasterizer.h" | |
| #include "flutter/shell/common/switches.h" | |
| #include "flutter/shell/platform/embedder/embedder.h" | |
| #include "flutter/shell/platform/embedder/embedder_engine.h" | |
| #include "flutter/shell/platform/embedder/embedder_external_texture_resolver.h" | |
| #include "flutter/shell/platform/embedder/embedder_platform_message_response.h" | |
| #include "flutter/shell/platform/embedder/embedder_render_target.h" | |
| #include "flutter/shell/platform/embedder/embedder_render_target_skia.h" | |
| #include "flutter/shell/platform/embedder/embedder_semantics_update.h" | |
| #include "flutter/shell/platform/embedder/embedder_struct_macros.h" | |
| #include "flutter/shell/platform/embedder/embedder_task_runner.h" | |
| #include "flutter/shell/platform/embedder/embedder_thread_host.h" | |
| #include "flutter/shell/platform/embedder/pixel_formats.h" | |
| #include "flutter/shell/platform/embedder/platform_view_embedder.h" | |
| #include "rapidjson/rapidjson.h" | |
| #include "rapidjson/writer.h" | |
| // Note: the IMPELLER_SUPPORTS_RENDERING may be defined even when the | |
| // embedder/BUILD.gn variable impeller_supports_rendering is disabled. | |
| #ifdef SHELL_ENABLE_GL | |
| #include "flutter/shell/platform/embedder/embedder_external_texture_gl.h" | |
| #include "third_party/skia/include/gpu/ganesh/gl/GrGLBackendSurface.h" | |
| #include "third_party/skia/include/gpu/gl/GrGLTypes.h" | |
| #ifdef IMPELLER_SUPPORTS_RENDERING | |
| #include "flutter/shell/platform/embedder/embedder_render_target_impeller.h" // nogncheck | |
| #include "flutter/shell/platform/embedder/embedder_surface_gl_impeller.h" // nogncheck | |
| #include "impeller/core/texture.h" // nogncheck | |
| #include "impeller/renderer/backend/gles/context_gles.h" // nogncheck | |
| #include "impeller/renderer/backend/gles/texture_gles.h" // nogncheck | |
| #include "impeller/renderer/context.h" // nogncheck | |
| #include "impeller/renderer/render_target.h" // nogncheck | |
| #endif // IMPELLER_SUPPORTS_RENDERING | |
| #endif // SHELL_ENABLE_GL | |
| #ifdef SHELL_ENABLE_METAL | |
| #include "flutter/shell/platform/embedder/embedder_surface_metal.h" | |
| #include "third_party/skia/include/gpu/ganesh/mtl/GrMtlBackendSurface.h" | |
| #include "third_party/skia/include/gpu/ganesh/mtl/GrMtlTypes.h" | |
| #include "third_party/skia/include/ports/SkCFObject.h" | |
| #ifdef IMPELLER_SUPPORTS_RENDERING | |
| #include "flutter/shell/platform/embedder/embedder_render_target_impeller.h" // nogncheck | |
| #include "flutter/shell/platform/embedder/embedder_surface_metal_impeller.h" // nogncheck | |
| #include "impeller/core/texture.h" // nogncheck | |
| #include "impeller/renderer/backend/metal/texture_wrapper_mtl.h" // nogncheck | |
| #include "impeller/renderer/render_target.h" // nogncheck | |
| #endif // IMPELLER_SUPPORTS_RENDERING | |
| #endif // SHELL_ENABLE_METAL | |
| #ifdef SHELL_ENABLE_VULKAN | |
| #include "third_party/skia/include/gpu/ganesh/vk/GrVkBackendSurface.h" | |
| #endif // SHELL_ENABLE_VULKAN | |
| const int32_t kFlutterSemanticsNodeIdBatchEnd = -1; | |
| const int32_t kFlutterSemanticsCustomActionIdBatchEnd = -1; | |
| static constexpr FlutterViewId kFlutterImplicitViewId = 0; | |
| // A message channel to send platform-independent FlutterKeyData to the | |
| // framework. | |
| // | |
| // This should be kept in sync with the following variables: | |
| // | |
| // - lib/ui/platform_dispatcher.dart, _kFlutterKeyDataChannel | |
| // - shell/platform/darwin/ios/framework/Source/FlutterEngine.mm, | |
| // FlutterKeyDataChannel | |
| // - io/flutter/embedding/android/KeyData.java, | |
| // CHANNEL | |
| // | |
| // Not to be confused with "flutter/keyevent", which is used to send raw | |
| // key event data in a platform-dependent format. | |
| // | |
| // ## Format | |
| // | |
| // Send: KeyDataPacket.data(). | |
| // | |
| // Expected reply: Whether the event is handled. Exactly 1 byte long, with value | |
| // 1 for handled, and 0 for not. Malformed value is considered false. | |
| const char* kFlutterKeyDataChannel = "flutter/keydata"; | |
| static FlutterEngineResult LogEmbedderError(FlutterEngineResult code, | |
| const char* reason, | |
| const char* code_name, | |
| const char* function, | |
| const char* file, | |
| int line) { | |
| #if FML_OS_WIN | |
| constexpr char kSeparator = '\\'; | |
| #else | |
| constexpr char kSeparator = '/'; | |
| #endif | |
| const auto file_base = | |
| (::strrchr(file, kSeparator) ? strrchr(file, kSeparator) + 1 : file); | |
| char error[256] = {}; | |
| snprintf(error, (sizeof(error) / sizeof(char)), | |
| "%s (%d): '%s' returned '%s'. %s", file_base, line, function, | |
| code_name, reason); | |
| std::cerr << error << std::endl; | |
| return code; | |
| } | |
| #define LOG_EMBEDDER_ERROR(code, reason) \ | |
| LogEmbedderError(code, reason, #code, __FUNCTION__, __FILE__, __LINE__) | |
| static bool IsOpenGLRendererConfigValid(const FlutterRendererConfig* config) { | |
| if (config->type != kOpenGL) { | |
| return false; | |
| } | |
| const FlutterOpenGLRendererConfig* open_gl_config = &config->open_gl; | |
| if (!SAFE_EXISTS(open_gl_config, make_current) || | |
| !SAFE_EXISTS(open_gl_config, clear_current) || | |
| !SAFE_EXISTS_ONE_OF(open_gl_config, fbo_callback, | |
| fbo_with_frame_info_callback) || | |
| !SAFE_EXISTS_ONE_OF(open_gl_config, present, present_with_info)) { | |
| return false; | |
| } | |
| return true; | |
| } | |
| static bool IsSoftwareRendererConfigValid(const FlutterRendererConfig* config) { | |
| if (config->type != kSoftware) { | |
| return false; | |
| } | |
| const FlutterSoftwareRendererConfig* software_config = &config->software; | |
| if (SAFE_ACCESS(software_config, surface_present_callback, nullptr) == | |
| nullptr) { | |
| return false; | |
| } | |
| return true; | |
| } | |
| static bool IsMetalRendererConfigValid(const FlutterRendererConfig* config) { | |
| if (config->type != kMetal) { | |
| return false; | |
| } | |
| const FlutterMetalRendererConfig* metal_config = &config->metal; | |
| bool device = SAFE_ACCESS(metal_config, device, nullptr); | |
| bool command_queue = | |
| SAFE_ACCESS(metal_config, present_command_queue, nullptr); | |
| bool present = SAFE_ACCESS(metal_config, present_drawable_callback, nullptr); | |
| bool get_texture = | |
| SAFE_ACCESS(metal_config, get_next_drawable_callback, nullptr); | |
| return device && command_queue && present && get_texture; | |
| } | |
| static bool IsVulkanRendererConfigValid(const FlutterRendererConfig* config) { | |
| if (config->type != kVulkan) { | |
| return false; | |
| } | |
| const FlutterVulkanRendererConfig* vulkan_config = &config->vulkan; | |
| if (!SAFE_EXISTS(vulkan_config, instance) || | |
| !SAFE_EXISTS(vulkan_config, physical_device) || | |
| !SAFE_EXISTS(vulkan_config, device) || | |
| !SAFE_EXISTS(vulkan_config, queue) || | |
| !SAFE_EXISTS(vulkan_config, get_instance_proc_address_callback) || | |
| !SAFE_EXISTS(vulkan_config, get_next_image_callback) || | |
| !SAFE_EXISTS(vulkan_config, present_image_callback)) { | |
| return false; | |
| } | |
| return true; | |
| } | |
| static bool IsRendererValid(const FlutterRendererConfig* config) { | |
| if (config == nullptr) { | |
| return false; | |
| } | |
| switch (config->type) { | |
| case kOpenGL: | |
| return IsOpenGLRendererConfigValid(config); | |
| case kSoftware: | |
| return IsSoftwareRendererConfigValid(config); | |
| case kMetal: | |
| return IsMetalRendererConfigValid(config); | |
| case kVulkan: | |
| return IsVulkanRendererConfigValid(config); | |
| default: | |
| return false; | |
| } | |
| return false; | |
| } | |
| #if FML_OS_LINUX || FML_OS_WIN | |
| static void* DefaultGLProcResolver(const char* name) { | |
| static fml::RefPtr<fml::NativeLibrary> proc_library = | |
| #if FML_OS_LINUX | |
| fml::NativeLibrary::CreateForCurrentProcess(); | |
| #elif FML_OS_WIN // FML_OS_LINUX | |
| fml::NativeLibrary::Create("opengl32.dll"); | |
| #endif // FML_OS_WIN | |
| return static_cast<void*>( | |
| const_cast<uint8_t*>(proc_library->ResolveSymbol(name))); | |
| } | |
| #endif // FML_OS_LINUX || FML_OS_WIN | |
| #ifdef SHELL_ENABLE_GL | |
| // Auxiliary function used to translate rectangles of type SkIRect to | |
| // FlutterRect. | |
| static FlutterRect SkIRectToFlutterRect(const SkIRect sk_rect) { | |
| FlutterRect flutter_rect = {static_cast<double>(sk_rect.fLeft), | |
| static_cast<double>(sk_rect.fTop), | |
| static_cast<double>(sk_rect.fRight), | |
| static_cast<double>(sk_rect.fBottom)}; | |
| return flutter_rect; | |
| } | |
| // Auxiliary function used to translate rectangles of type FlutterRect to | |
| // SkIRect. | |
| static const SkIRect FlutterRectToSkIRect(FlutterRect flutter_rect) { | |
| SkIRect rect = {static_cast<int32_t>(flutter_rect.left), | |
| static_cast<int32_t>(flutter_rect.top), | |
| static_cast<int32_t>(flutter_rect.right), | |
| static_cast<int32_t>(flutter_rect.bottom)}; | |
| return rect; | |
| } | |
| #endif | |
| static inline flutter::Shell::CreateCallback<flutter::PlatformView> | |
| InferOpenGLPlatformViewCreationCallback( | |
| const FlutterRendererConfig* config, | |
| void* user_data, | |
| const flutter::PlatformViewEmbedder::PlatformDispatchTable& | |
| platform_dispatch_table, | |
| std::unique_ptr<flutter::EmbedderExternalViewEmbedder> | |
| external_view_embedder, | |
| bool enable_impeller) { | |
| #ifdef SHELL_ENABLE_GL | |
| if (config->type != kOpenGL) { | |
| return nullptr; | |
| } | |
| auto gl_make_current = [ptr = config->open_gl.make_current, | |
| user_data]() -> bool { return ptr(user_data); }; | |
| auto gl_clear_current = [ptr = config->open_gl.clear_current, | |
| user_data]() -> bool { return ptr(user_data); }; | |
| auto gl_present = | |
| [present = config->open_gl.present, | |
| present_with_info = config->open_gl.present_with_info, | |
| user_data](flutter::GLPresentInfo gl_present_info) -> bool { | |
| if (present) { | |
| return present(user_data); | |
| } else { | |
| // Format the frame and buffer damages accordingly. Note that, since the | |
| // current compute damage algorithm only returns one rectangle for damage | |
| // we are assuming the number of rectangles provided in frame and buffer | |
| // damage are always 1. Once the function that computes damage implements | |
| // support for multiple damage rectangles, GLPresentInfo should also | |
| // contain the number of damage rectangles. | |
| std::optional<FlutterRect> frame_damage_rect; | |
| if (gl_present_info.frame_damage) { | |
| frame_damage_rect = | |
| SkIRectToFlutterRect(*(gl_present_info.frame_damage)); | |
| } | |
| std::optional<FlutterRect> buffer_damage_rect; | |
| if (gl_present_info.buffer_damage) { | |
| buffer_damage_rect = | |
| SkIRectToFlutterRect(*(gl_present_info.buffer_damage)); | |
| } | |
| FlutterDamage frame_damage{ | |
| .struct_size = sizeof(FlutterDamage), | |
| .num_rects = frame_damage_rect ? size_t{1} : size_t{0}, | |
| .damage = frame_damage_rect ? &frame_damage_rect.value() : nullptr, | |
| }; | |
| FlutterDamage buffer_damage{ | |
| .struct_size = sizeof(FlutterDamage), | |
| .num_rects = buffer_damage_rect ? size_t{1} : size_t{0}, | |
| .damage = buffer_damage_rect ? &buffer_damage_rect.value() : nullptr, | |
| }; | |
| // Construct the present information concerning the frame being rendered. | |
| FlutterPresentInfo present_info = { | |
| .struct_size = sizeof(FlutterPresentInfo), | |
| .fbo_id = gl_present_info.fbo_id, | |
| .frame_damage = frame_damage, | |
| .buffer_damage = buffer_damage, | |
| }; | |
| return present_with_info(user_data, &present_info); | |
| } | |
| }; | |
| auto gl_fbo_callback = | |
| [fbo_callback = config->open_gl.fbo_callback, | |
| fbo_with_frame_info_callback = | |
| config->open_gl.fbo_with_frame_info_callback, | |
| user_data](flutter::GLFrameInfo gl_frame_info) -> intptr_t { | |
| if (fbo_callback) { | |
| return fbo_callback(user_data); | |
| } else { | |
| FlutterFrameInfo frame_info = {}; | |
| frame_info.struct_size = sizeof(FlutterFrameInfo); | |
| frame_info.size = {gl_frame_info.width, gl_frame_info.height}; | |
| return fbo_with_frame_info_callback(user_data, &frame_info); | |
| } | |
| }; | |
| auto gl_populate_existing_damage = | |
| [populate_existing_damage = config->open_gl.populate_existing_damage, | |
| user_data](intptr_t id) -> flutter::GLFBOInfo { | |
| // If no populate_existing_damage was provided, disable partial | |
| // repaint. | |
| if (!populate_existing_damage) { | |
| return flutter::GLFBOInfo{ | |
| .fbo_id = static_cast<uint32_t>(id), | |
| .existing_damage = std::nullopt, | |
| }; | |
| } | |
| // Given the FBO's ID, get its existing damage. | |
| FlutterDamage existing_damage; | |
| populate_existing_damage(user_data, id, &existing_damage); | |
| std::optional<SkIRect> existing_damage_rect = std::nullopt; | |
| // Verify that at least one damage rectangle was provided. | |
| if (existing_damage.num_rects <= 0 || existing_damage.damage == nullptr) { | |
| FML_LOG(INFO) << "No damage was provided. Forcing full repaint."; | |
| } else { | |
| existing_damage_rect = SkIRect::MakeEmpty(); | |
| for (size_t i = 0; i < existing_damage.num_rects; i++) { | |
| existing_damage_rect->join( | |
| FlutterRectToSkIRect(existing_damage.damage[i])); | |
| } | |
| } | |
| // Pass the information about this FBO to the rendering backend. | |
| return flutter::GLFBOInfo{ | |
| .fbo_id = static_cast<uint32_t>(id), | |
| .existing_damage = existing_damage_rect, | |
| }; | |
| }; | |
| const FlutterOpenGLRendererConfig* open_gl_config = &config->open_gl; | |
| std::function<bool()> gl_make_resource_current_callback = nullptr; | |
| if (SAFE_ACCESS(open_gl_config, make_resource_current, nullptr) != nullptr) { | |
| gl_make_resource_current_callback = | |
| [ptr = config->open_gl.make_resource_current, user_data]() { | |
| return ptr(user_data); | |
| }; | |
| } | |
| std::function<SkMatrix(void)> gl_surface_transformation_callback = nullptr; | |
| if (SAFE_ACCESS(open_gl_config, surface_transformation, nullptr) != nullptr) { | |
| gl_surface_transformation_callback = | |
| [ptr = config->open_gl.surface_transformation, user_data]() { | |
| FlutterTransformation transformation = ptr(user_data); | |
| return SkMatrix::MakeAll(transformation.scaleX, // | |
| transformation.skewX, // | |
| transformation.transX, // | |
| transformation.skewY, // | |
| transformation.scaleY, // | |
| transformation.transY, // | |
| transformation.pers0, // | |
| transformation.pers1, // | |
| transformation.pers2 // | |
| ); | |
| }; | |
| // If there is an external view embedder, ask it to apply the surface | |
| // transformation to its surfaces as well. | |
| if (external_view_embedder) { | |
| external_view_embedder->SetSurfaceTransformationCallback( | |
| gl_surface_transformation_callback); | |
| } | |
| } | |
| flutter::GPUSurfaceGLDelegate::GLProcResolver gl_proc_resolver = nullptr; | |
| if (SAFE_ACCESS(open_gl_config, gl_proc_resolver, nullptr) != nullptr) { | |
| gl_proc_resolver = [ptr = config->open_gl.gl_proc_resolver, | |
| user_data](const char* gl_proc_name) { | |
| return ptr(user_data, gl_proc_name); | |
| }; | |
| } else { | |
| #if FML_OS_LINUX || FML_OS_WIN | |
| gl_proc_resolver = DefaultGLProcResolver; | |
| #endif | |
| } | |
| bool fbo_reset_after_present = | |
| SAFE_ACCESS(open_gl_config, fbo_reset_after_present, false); | |
| flutter::EmbedderSurfaceGL::GLDispatchTable gl_dispatch_table = { | |
| gl_make_current, // gl_make_current_callback | |
| gl_clear_current, // gl_clear_current_callback | |
| gl_present, // gl_present_callback | |
| gl_fbo_callback, // gl_fbo_callback | |
| gl_make_resource_current_callback, // gl_make_resource_current_callback | |
| gl_surface_transformation_callback, // gl_surface_transformation_callback | |
| gl_proc_resolver, // gl_proc_resolver | |
| gl_populate_existing_damage, // gl_populate_existing_damage | |
| }; | |
| return fml::MakeCopyable( | |
| [gl_dispatch_table, fbo_reset_after_present, platform_dispatch_table, | |
| enable_impeller, | |
| external_view_embedder = | |
| std::move(external_view_embedder)](flutter::Shell& shell) mutable { | |
| std::shared_ptr<flutter::EmbedderExternalViewEmbedder> view_embedder = | |
| std::move(external_view_embedder); | |
| if (enable_impeller) { | |
| return std::make_unique<flutter::PlatformViewEmbedder>( | |
| shell, // delegate | |
| shell.GetTaskRunners(), // task runners | |
| std::make_unique<flutter::EmbedderSurfaceGLImpeller>( | |
| gl_dispatch_table, fbo_reset_after_present, | |
| view_embedder), // embedder_surface | |
| platform_dispatch_table, // embedder platform dispatch table | |
| view_embedder // external view embedder | |
| ); | |
| } | |
| return std::make_unique<flutter::PlatformViewEmbedder>( | |
| shell, // delegate | |
| shell.GetTaskRunners(), // task runners | |
| std::make_unique<flutter::EmbedderSurfaceGL>( | |
| gl_dispatch_table, fbo_reset_after_present, | |
| view_embedder), // embedder_surface | |
| platform_dispatch_table, // embedder platform dispatch table | |
| view_embedder // external view embedder | |
| ); | |
| }); | |
| #else | |
| return nullptr; | |
| #endif | |
| } | |
| static flutter::Shell::CreateCallback<flutter::PlatformView> | |
| InferMetalPlatformViewCreationCallback( | |
| const FlutterRendererConfig* config, | |
| void* user_data, | |
| const flutter::PlatformViewEmbedder::PlatformDispatchTable& | |
| platform_dispatch_table, | |
| std::unique_ptr<flutter::EmbedderExternalViewEmbedder> | |
| external_view_embedder, | |
| bool enable_impeller) { | |
| if (config->type != kMetal) { | |
| return nullptr; | |
| } | |
| #ifdef SHELL_ENABLE_METAL | |
| std::function<bool(flutter::GPUMTLTextureInfo texture)> metal_present = | |
| [ptr = config->metal.present_drawable_callback, | |
| user_data](flutter::GPUMTLTextureInfo texture) { | |
| FlutterMetalTexture embedder_texture; | |
| embedder_texture.struct_size = sizeof(FlutterMetalTexture); | |
| embedder_texture.texture = texture.texture; | |
| embedder_texture.texture_id = texture.texture_id; | |
| embedder_texture.user_data = texture.destruction_context; | |
| embedder_texture.destruction_callback = texture.destruction_callback; | |
| return ptr(user_data, &embedder_texture); | |
| }; | |
| auto metal_get_texture = | |
| [ptr = config->metal.get_next_drawable_callback, | |
| user_data](const SkISize& frame_size) -> flutter::GPUMTLTextureInfo { | |
| FlutterFrameInfo frame_info = {}; | |
| frame_info.struct_size = sizeof(FlutterFrameInfo); | |
| frame_info.size = {static_cast<uint32_t>(frame_size.width()), | |
| static_cast<uint32_t>(frame_size.height())}; | |
| flutter::GPUMTLTextureInfo texture_info; | |
| FlutterMetalTexture metal_texture = ptr(user_data, &frame_info); | |
| texture_info.texture_id = metal_texture.texture_id; | |
| texture_info.texture = metal_texture.texture; | |
| texture_info.destruction_callback = metal_texture.destruction_callback; | |
| texture_info.destruction_context = metal_texture.user_data; | |
| return texture_info; | |
| }; | |
| std::shared_ptr<flutter::EmbedderExternalViewEmbedder> view_embedder = | |
| std::move(external_view_embedder); | |
| std::unique_ptr<flutter::EmbedderSurface> embedder_surface; | |
| if (enable_impeller) { | |
| flutter::EmbedderSurfaceMetalImpeller::MetalDispatchTable | |
| metal_dispatch_table = { | |
| .present = metal_present, | |
| .get_texture = metal_get_texture, | |
| }; | |
| embedder_surface = std::make_unique<flutter::EmbedderSurfaceMetalImpeller>( | |
| const_cast<flutter::GPUMTLDeviceHandle>(config->metal.device), | |
| const_cast<flutter::GPUMTLCommandQueueHandle>( | |
| config->metal.present_command_queue), | |
| metal_dispatch_table, view_embedder); | |
| } else { | |
| flutter::EmbedderSurfaceMetal::MetalDispatchTable metal_dispatch_table = { | |
| .present = metal_present, | |
| .get_texture = metal_get_texture, | |
| }; | |
| embedder_surface = std::make_unique<flutter::EmbedderSurfaceMetal>( | |
| const_cast<flutter::GPUMTLDeviceHandle>(config->metal.device), | |
| const_cast<flutter::GPUMTLCommandQueueHandle>( | |
| config->metal.present_command_queue), | |
| metal_dispatch_table, view_embedder); | |
| } | |
| // The static leak checker gets confused by the use of fml::MakeCopyable. | |
| // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) | |
| return fml::MakeCopyable( | |
| [embedder_surface = std::move(embedder_surface), platform_dispatch_table, | |
| external_view_embedder = view_embedder](flutter::Shell& shell) mutable { | |
| return std::make_unique<flutter::PlatformViewEmbedder>( | |
| shell, // delegate | |
| shell.GetTaskRunners(), // task runners | |
| std::move(embedder_surface), // embedder surface | |
| platform_dispatch_table, // platform dispatch table | |
| std::move(external_view_embedder) // external view embedder | |
| ); | |
| }); | |
| #else | |
| return nullptr; | |
| #endif | |
| } | |
| static flutter::Shell::CreateCallback<flutter::PlatformView> | |
| InferVulkanPlatformViewCreationCallback( | |
| const FlutterRendererConfig* config, | |
| void* user_data, | |
| const flutter::PlatformViewEmbedder::PlatformDispatchTable& | |
| platform_dispatch_table, | |
| std::unique_ptr<flutter::EmbedderExternalViewEmbedder> | |
| external_view_embedder) { | |
| if (config->type != kVulkan) { | |
| return nullptr; | |
| } | |
| #ifdef SHELL_ENABLE_VULKAN | |
| std::function<void*(VkInstance, const char*)> | |
| vulkan_get_instance_proc_address = | |
| [ptr = config->vulkan.get_instance_proc_address_callback, user_data]( | |
| VkInstance instance, const char* proc_name) -> void* { | |
| return ptr(user_data, instance, proc_name); | |
| }; | |
| auto vulkan_get_next_image = | |
| [ptr = config->vulkan.get_next_image_callback, | |
| user_data](const SkISize& frame_size) -> FlutterVulkanImage { | |
| FlutterFrameInfo frame_info = { | |
| .struct_size = sizeof(FlutterFrameInfo), | |
| .size = {static_cast<uint32_t>(frame_size.width()), | |
| static_cast<uint32_t>(frame_size.height())}, | |
| }; | |
| return ptr(user_data, &frame_info); | |
| }; | |
| auto vulkan_present_image_callback = | |
| [ptr = config->vulkan.present_image_callback, user_data]( | |
| VkImage image, VkFormat format) -> bool { | |
| FlutterVulkanImage image_desc = { | |
| .struct_size = sizeof(FlutterVulkanImage), | |
| .image = reinterpret_cast<uint64_t>(image), | |
| .format = static_cast<uint32_t>(format), | |
| }; | |
| return ptr(user_data, &image_desc); | |
| }; | |
| auto vk_instance = static_cast<VkInstance>(config->vulkan.instance); | |
| auto proc_addr = | |
| vulkan_get_instance_proc_address(vk_instance, "vkGetInstanceProcAddr"); | |
| flutter::EmbedderSurfaceVulkan::VulkanDispatchTable vulkan_dispatch_table = { | |
| .get_instance_proc_address = | |
| reinterpret_cast<PFN_vkGetInstanceProcAddr>(proc_addr), | |
| .get_next_image = vulkan_get_next_image, | |
| .present_image = vulkan_present_image_callback, | |
| }; | |
| std::shared_ptr<flutter::EmbedderExternalViewEmbedder> view_embedder = | |
| std::move(external_view_embedder); | |
| std::unique_ptr<flutter::EmbedderSurfaceVulkan> embedder_surface = | |
| std::make_unique<flutter::EmbedderSurfaceVulkan>( | |
| config->vulkan.version, vk_instance, | |
| config->vulkan.enabled_instance_extension_count, | |
| config->vulkan.enabled_instance_extensions, | |
| config->vulkan.enabled_device_extension_count, | |
| config->vulkan.enabled_device_extensions, | |
| static_cast<VkPhysicalDevice>(config->vulkan.physical_device), | |
| static_cast<VkDevice>(config->vulkan.device), | |
| config->vulkan.queue_family_index, | |
| static_cast<VkQueue>(config->vulkan.queue), vulkan_dispatch_table, | |
| view_embedder); | |
| return fml::MakeCopyable( | |
| [embedder_surface = std::move(embedder_surface), platform_dispatch_table, | |
| external_view_embedder = | |
| std::move(view_embedder)](flutter::Shell& shell) mutable { | |
| return std::make_unique<flutter::PlatformViewEmbedder>( | |
| shell, // delegate | |
| shell.GetTaskRunners(), // task runners | |
| std::move(embedder_surface), // embedder surface | |
| platform_dispatch_table, // platform dispatch table | |
| std::move(external_view_embedder) // external view embedder | |
| ); | |
| }); | |
| #else | |
| return nullptr; | |
| #endif | |
| } | |
| static flutter::Shell::CreateCallback<flutter::PlatformView> | |
| InferSoftwarePlatformViewCreationCallback( | |
| const FlutterRendererConfig* config, | |
| void* user_data, | |
| const flutter::PlatformViewEmbedder::PlatformDispatchTable& | |
| platform_dispatch_table, | |
| std::unique_ptr<flutter::EmbedderExternalViewEmbedder> | |
| external_view_embedder) { | |
| if (config->type != kSoftware) { | |
| return nullptr; | |
| } | |
| auto software_present_backing_store = | |
| [ptr = config->software.surface_present_callback, user_data]( | |
| const void* allocation, size_t row_bytes, size_t height) -> bool { | |
| return ptr(user_data, allocation, row_bytes, height); | |
| }; | |
| flutter::EmbedderSurfaceSoftware::SoftwareDispatchTable | |
| software_dispatch_table = { | |
| software_present_backing_store, // required | |
| }; | |
| return fml::MakeCopyable( | |
| [software_dispatch_table, platform_dispatch_table, | |
| external_view_embedder = | |
| std::move(external_view_embedder)](flutter::Shell& shell) mutable { | |
| return std::make_unique<flutter::PlatformViewEmbedder>( | |
| shell, // delegate | |
| shell.GetTaskRunners(), // task runners | |
| software_dispatch_table, // software dispatch table | |
| platform_dispatch_table, // platform dispatch table | |
| std::move(external_view_embedder) // external view embedder | |
| ); | |
| }); | |
| } | |
| static flutter::Shell::CreateCallback<flutter::PlatformView> | |
| InferPlatformViewCreationCallback( | |
| const FlutterRendererConfig* config, | |
| void* user_data, | |
| const flutter::PlatformViewEmbedder::PlatformDispatchTable& | |
| platform_dispatch_table, | |
| std::unique_ptr<flutter::EmbedderExternalViewEmbedder> | |
| external_view_embedder, | |
| bool enable_impeller) { | |
| if (config == nullptr) { | |
| return nullptr; | |
| } | |
| switch (config->type) { | |
| case kOpenGL: | |
| return InferOpenGLPlatformViewCreationCallback( | |
| config, user_data, platform_dispatch_table, | |
| std::move(external_view_embedder), enable_impeller); | |
| case kSoftware: | |
| return InferSoftwarePlatformViewCreationCallback( | |
| config, user_data, platform_dispatch_table, | |
| std::move(external_view_embedder)); | |
| case kMetal: | |
| return InferMetalPlatformViewCreationCallback( | |
| config, user_data, platform_dispatch_table, | |
| std::move(external_view_embedder), enable_impeller); | |
| case kVulkan: | |
| return InferVulkanPlatformViewCreationCallback( | |
| config, user_data, platform_dispatch_table, | |
| std::move(external_view_embedder)); | |
| default: | |
| return nullptr; | |
| } | |
| return nullptr; | |
| } | |
| static sk_sp<SkSurface> MakeSkSurfaceFromBackingStore( | |
| GrDirectContext* context, | |
| const FlutterBackingStoreConfig& config, | |
| const FlutterOpenGLTexture* texture) { | |
| #ifdef SHELL_ENABLE_GL | |
| GrGLTextureInfo texture_info; | |
| texture_info.fTarget = texture->target; | |
| texture_info.fID = texture->name; | |
| texture_info.fFormat = texture->format; | |
| GrBackendTexture backend_texture = | |
| GrBackendTextures::MakeGL(config.size.width, config.size.height, | |
| skgpu::Mipmapped::kNo, texture_info); | |
| SkSurfaceProps surface_properties(0, kUnknown_SkPixelGeometry); | |
| auto surface = SkSurfaces::WrapBackendTexture( | |
| context, // context | |
| backend_texture, // back-end texture | |
| kBottomLeft_GrSurfaceOrigin, // surface origin | |
| 1, // sample count | |
| kN32_SkColorType, // color type | |
| SkColorSpace::MakeSRGB(), // color space | |
| &surface_properties, // surface properties | |
| static_cast<SkSurfaces::TextureReleaseProc>( | |
| texture->destruction_callback), // release proc | |
| texture->user_data // release context | |
| ); | |
| if (!surface) { | |
| FML_LOG(ERROR) << "Could not wrap embedder supplied render texture."; | |
| return nullptr; | |
| } | |
| return surface; | |
| #else | |
| return nullptr; | |
| #endif | |
| } | |
| static sk_sp<SkSurface> MakeSkSurfaceFromBackingStore( | |
| GrDirectContext* context, | |
| const FlutterBackingStoreConfig& config, | |
| const FlutterOpenGLFramebuffer* framebuffer) { | |
| #ifdef SHELL_ENABLE_GL | |
| GrGLFramebufferInfo framebuffer_info = {}; | |
| framebuffer_info.fFormat = framebuffer->target; | |
| framebuffer_info.fFBOID = framebuffer->name; | |
| auto backend_render_target = | |
| GrBackendRenderTargets::MakeGL(config.size.width, // width | |
| config.size.height, // height | |
| 1, // sample count | |
| 0, // stencil bits | |
| framebuffer_info // framebuffer info | |
| ); | |
| SkSurfaceProps surface_properties(0, kUnknown_SkPixelGeometry); | |
| auto surface = SkSurfaces::WrapBackendRenderTarget( | |
| context, // context | |
| backend_render_target, // backend render target | |
| kBottomLeft_GrSurfaceOrigin, // surface origin | |
| kN32_SkColorType, // color type | |
| SkColorSpace::MakeSRGB(), // color space | |
| &surface_properties, // surface properties | |
| static_cast<SkSurfaces::RenderTargetReleaseProc>( | |
| framebuffer->destruction_callback), // release proc | |
| framebuffer->user_data // release context | |
| ); | |
| if (!surface) { | |
| FML_LOG(ERROR) << "Could not wrap embedder supplied frame-buffer."; | |
| return nullptr; | |
| } | |
| return surface; | |
| #else | |
| return nullptr; | |
| #endif | |
| } | |
| static sk_sp<SkSurface> MakeSkSurfaceFromBackingStore( | |
| GrDirectContext* context, | |
| const FlutterBackingStoreConfig& config, | |
| const FlutterSoftwareBackingStore* software) { | |
| const auto image_info = | |
| SkImageInfo::MakeN32Premul(config.size.width, config.size.height); | |
| struct Captures { | |
| VoidCallback destruction_callback; | |
| void* user_data; | |
| }; | |
| auto captures = std::make_unique<Captures>(); | |
| captures->destruction_callback = software->destruction_callback; | |
| captures->user_data = software->user_data; | |
| auto release_proc = [](void* pixels, void* context) { | |
| auto captures = reinterpret_cast<Captures*>(context); | |
| if (captures->destruction_callback) { | |
| captures->destruction_callback(captures->user_data); | |
| } | |
| delete captures; | |
| }; | |
| auto surface = | |
| SkSurfaces::WrapPixels(image_info, // image info | |
| const_cast<void*>(software->allocation), // pixels | |
| software->row_bytes, // row bytes | |
| release_proc, // release proc | |
| captures.get() // get context | |
| ); | |
| if (!surface) { | |
| FML_LOG(ERROR) | |
| << "Could not wrap embedder supplied software render buffer."; | |
| if (software->destruction_callback) { | |
| software->destruction_callback(software->user_data); | |
| } | |
| return nullptr; | |
| } | |
| if (surface) { | |
| captures.release(); // Skia has assumed ownership of the struct. | |
| } | |
| return surface; | |
| } | |
| static sk_sp<SkSurface> MakeSkSurfaceFromBackingStore( | |
| GrDirectContext* context, | |
| const FlutterBackingStoreConfig& config, | |
| const FlutterSoftwareBackingStore2* software) { | |
| const auto color_info = getSkColorInfo(software->pixel_format); | |
| if (!color_info) { | |
| return nullptr; | |
| } | |
| const auto image_info = SkImageInfo::Make( | |
| SkISize::Make(config.size.width, config.size.height), *color_info); | |
| struct Captures { | |
| VoidCallback destruction_callback; | |
| void* user_data; | |
| }; | |
| auto captures = std::make_unique<Captures>(); | |
| captures->destruction_callback = software->destruction_callback; | |
| captures->user_data = software->user_data; | |
| auto release_proc = [](void* pixels, void* context) { | |
| auto captures = reinterpret_cast<Captures*>(context); | |
| if (captures->destruction_callback) { | |
| captures->destruction_callback(captures->user_data); | |
| } | |
| }; | |
| auto surface = | |
| SkSurfaces::WrapPixels(image_info, // image info | |
| const_cast<void*>(software->allocation), // pixels | |
| software->row_bytes, // row bytes | |
| release_proc, // release proc | |
| captures.release() // release context | |
| ); | |
| if (!surface) { | |
| FML_LOG(ERROR) | |
| << "Could not wrap embedder supplied software render buffer."; | |
| if (software->destruction_callback) { | |
| software->destruction_callback(software->user_data); | |
| } | |
| return nullptr; | |
| } | |
| return surface; | |
| } | |
| static sk_sp<SkSurface> MakeSkSurfaceFromBackingStore( | |
| GrDirectContext* context, | |
| const FlutterBackingStoreConfig& config, | |
| const FlutterMetalBackingStore* metal) { | |
| #ifdef SHELL_ENABLE_METAL | |
| GrMtlTextureInfo texture_info; | |
| if (!metal->texture.texture) { | |
| FML_LOG(ERROR) << "Embedder supplied null Metal texture."; | |
| return nullptr; | |
| } | |
| sk_cfp<FlutterMetalTextureHandle> mtl_texture; | |
| mtl_texture.retain(metal->texture.texture); | |
| texture_info.fTexture = mtl_texture; | |
| GrBackendTexture backend_texture = | |
| GrBackendTextures::MakeMtl(config.size.width, // | |
| config.size.height, // | |
| skgpu::Mipmapped::kNo, // | |
| texture_info // | |
| ); | |
| SkSurfaceProps surface_properties(0, kUnknown_SkPixelGeometry); | |
| auto surface = SkSurfaces::WrapBackendTexture( | |
| context, // context | |
| backend_texture, // back-end texture | |
| kTopLeft_GrSurfaceOrigin, // surface origin | |
| // TODO(dnfield): Update this when embedders support MSAA, see | |
| // https://github.com/flutter/flutter/issues/100392 | |
| 1, // sample count | |
| kBGRA_8888_SkColorType, // color type | |
| nullptr, // color space | |
| &surface_properties, // surface properties | |
| static_cast<SkSurfaces::TextureReleaseProc>( | |
| metal->texture.destruction_callback), // release proc | |
| metal->texture.user_data // release context | |
| ); | |
| if (!surface) { | |
| FML_LOG(ERROR) << "Could not wrap embedder supplied Metal render texture."; | |
| return nullptr; | |
| } | |
| return surface; | |
| #else | |
| return nullptr; | |
| #endif | |
| } | |
| static std::unique_ptr<flutter::EmbedderRenderTarget> | |
| MakeRenderTargetFromBackingStoreImpeller( | |
| FlutterBackingStore backing_store, | |
| const fml::closure& on_release, | |
| const std::shared_ptr<impeller::AiksContext>& aiks_context, | |
| const FlutterBackingStoreConfig& config, | |
| const FlutterOpenGLFramebuffer* framebuffer) { | |
| #if defined(SHELL_ENABLE_GL) && defined(IMPELLER_SUPPORTS_RENDERING) | |
| const auto& gl_context = | |
| impeller::ContextGLES::Cast(*aiks_context->GetContext()); | |
| const auto size = impeller::ISize(config.size.width, config.size.height); | |
| impeller::TextureDescriptor color0_tex; | |
| color0_tex.type = impeller::TextureType::kTexture2D; | |
| color0_tex.format = impeller::PixelFormat::kR8G8B8A8UNormInt; | |
| color0_tex.size = size; | |
| color0_tex.usage = static_cast<impeller::TextureUsageMask>( | |
| impeller::TextureUsage::kRenderTarget); | |
| color0_tex.sample_count = impeller::SampleCount::kCount1; | |
| color0_tex.storage_mode = impeller::StorageMode::kDevicePrivate; | |
| impeller::ColorAttachment color0; | |
| color0.texture = impeller::TextureGLES::WrapFBO( | |
| gl_context.GetReactor(), color0_tex, framebuffer->name); | |
| color0.clear_color = impeller::Color::DarkSlateGray(); | |
| color0.load_action = impeller::LoadAction::kClear; | |
| color0.store_action = impeller::StoreAction::kStore; | |
| impeller::TextureDescriptor depth_stencil_texture_desc; | |
| depth_stencil_texture_desc.type = impeller::TextureType::kTexture2D; | |
| depth_stencil_texture_desc.format = impeller::PixelFormat::kR8G8B8A8UNormInt; | |
| depth_stencil_texture_desc.size = size; | |
| depth_stencil_texture_desc.usage = static_cast<impeller::TextureUsageMask>( | |
| impeller::TextureUsage::kRenderTarget); | |
| depth_stencil_texture_desc.sample_count = impeller::SampleCount::kCount1; | |
| auto depth_stencil_tex = std::make_shared<impeller::TextureGLES>( | |
| gl_context.GetReactor(), depth_stencil_texture_desc, | |
| impeller::TextureGLES::IsWrapped::kWrapped); | |
| impeller::DepthAttachment depth0; | |
| depth0.clear_depth = 0; | |
| depth0.texture = depth_stencil_tex; | |
| depth0.load_action = impeller::LoadAction::kClear; | |
| depth0.store_action = impeller::StoreAction::kDontCare; | |
| impeller::StencilAttachment stencil0; | |
| stencil0.clear_stencil = 0; | |
| stencil0.texture = depth_stencil_tex; | |
| stencil0.load_action = impeller::LoadAction::kClear; | |
| stencil0.store_action = impeller::StoreAction::kDontCare; | |
| impeller::RenderTarget render_target_desc; | |
| render_target_desc.SetColorAttachment(color0, 0u); | |
| render_target_desc.SetDepthAttachment(depth0); | |
| render_target_desc.SetStencilAttachment(stencil0); | |
| fml::closure framebuffer_destruct = | |
| [callback = framebuffer->destruction_callback, | |
| user_data = framebuffer->user_data]() { callback(user_data); }; | |
| return std::make_unique<flutter::EmbedderRenderTargetImpeller>( | |
| backing_store, aiks_context, | |
| std::make_unique<impeller::RenderTarget>(std::move(render_target_desc)), | |
| on_release, framebuffer_destruct); | |
| #else | |
| return nullptr; | |
| #endif | |
| } | |
| static std::unique_ptr<flutter::EmbedderRenderTarget> | |
| MakeRenderTargetFromBackingStoreImpeller( | |
| FlutterBackingStore backing_store, | |
| const fml::closure& on_release, | |
| const std::shared_ptr<impeller::AiksContext>& aiks_context, | |
| const FlutterBackingStoreConfig& config, | |
| const FlutterMetalBackingStore* metal) { | |
| #if defined(SHELL_ENABLE_METAL) && defined(IMPELLER_SUPPORTS_RENDERING) | |
| if (!metal->texture.texture) { | |
| FML_LOG(ERROR) << "Embedder supplied null Metal texture."; | |
| return nullptr; | |
| } | |
| const auto size = impeller::ISize(config.size.width, config.size.height); | |
| impeller::TextureDescriptor resolve_tex_desc; | |
| resolve_tex_desc.size = size; | |
| resolve_tex_desc.sample_count = impeller::SampleCount::kCount1; | |
| resolve_tex_desc.storage_mode = impeller::StorageMode::kDevicePrivate; | |
| resolve_tex_desc.usage = impeller::TextureUsage::kRenderTarget | | |
| impeller::TextureUsage::kShaderRead; | |
| auto resolve_tex = impeller::WrapTextureMTL( | |
| resolve_tex_desc, metal->texture.texture, | |
| [callback = metal->texture.destruction_callback, | |
| user_data = metal->texture.user_data]() { callback(user_data); }); | |
| if (!resolve_tex) { | |
| FML_LOG(ERROR) << "Could not wrap embedder supplied Metal render texture."; | |
| return nullptr; | |
| } | |
| resolve_tex->SetLabel("ImpellerBackingStoreResolve"); | |
| impeller::TextureDescriptor msaa_tex_desc; | |
| msaa_tex_desc.storage_mode = impeller::StorageMode::kDeviceTransient; | |
| msaa_tex_desc.type = impeller::TextureType::kTexture2DMultisample; | |
| msaa_tex_desc.sample_count = impeller::SampleCount::kCount4; | |
| msaa_tex_desc.format = resolve_tex->GetTextureDescriptor().format; | |
| msaa_tex_desc.size = size; | |
| msaa_tex_desc.usage = impeller::TextureUsage::kRenderTarget; | |
| auto msaa_tex = | |
| aiks_context->GetContext()->GetResourceAllocator()->CreateTexture( | |
| msaa_tex_desc); | |
| if (!msaa_tex) { | |
| FML_LOG(ERROR) << "Could not allocate MSAA color texture."; | |
| return nullptr; | |
| } | |
| msaa_tex->SetLabel("ImpellerBackingStoreColorMSAA"); | |
| impeller::ColorAttachment color0; | |
| color0.texture = msaa_tex; | |
| color0.clear_color = impeller::Color::DarkSlateGray(); | |
| color0.load_action = impeller::LoadAction::kClear; | |
| color0.store_action = impeller::StoreAction::kMultisampleResolve; | |
| color0.resolve_texture = resolve_tex; | |
| impeller::RenderTarget render_target_desc; | |
| render_target_desc.SetColorAttachment(color0, 0u); | |
| return std::make_unique<flutter::EmbedderRenderTargetImpeller>( | |
| backing_store, aiks_context, | |
| std::make_unique<impeller::RenderTarget>(std::move(render_target_desc)), | |
| on_release, fml::closure()); | |
| #else | |
| return nullptr; | |
| #endif | |
| } | |
| static sk_sp<SkSurface> MakeSkSurfaceFromBackingStore( | |
| GrDirectContext* context, | |
| const FlutterBackingStoreConfig& config, | |
| const FlutterVulkanBackingStore* vulkan) { | |
| #ifdef SHELL_ENABLE_VULKAN | |
| if (!vulkan->image) { | |
| FML_LOG(ERROR) << "Embedder supplied null Vulkan image."; | |
| return nullptr; | |
| } | |
| GrVkImageInfo image_info = { | |
| .fImage = reinterpret_cast<VkImage>(vulkan->image->image), | |
| .fImageTiling = VK_IMAGE_TILING_OPTIMAL, | |
| .fImageLayout = VK_IMAGE_LAYOUT_UNDEFINED, | |
| .fFormat = static_cast<VkFormat>(vulkan->image->format), | |
| .fImageUsageFlags = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | | |
| VK_IMAGE_USAGE_TRANSFER_SRC_BIT | | |
| VK_IMAGE_USAGE_TRANSFER_DST_BIT | | |
| VK_IMAGE_USAGE_SAMPLED_BIT, | |
| .fSampleCount = 1, | |
| .fLevelCount = 1, | |
| }; | |
| auto backend_texture = GrBackendTextures::MakeVk( | |
| config.size.width, config.size.height, image_info); | |
| SkSurfaceProps surface_properties(0, kUnknown_SkPixelGeometry); | |
| auto surface = SkSurfaces::WrapBackendTexture( | |
| context, // context | |
| backend_texture, // back-end texture | |
| kTopLeft_GrSurfaceOrigin, // surface origin | |
| 1, // sample count | |
| flutter::GPUSurfaceVulkan::ColorTypeFromFormat( | |
| static_cast<VkFormat>(vulkan->image->format)), // color type | |
| SkColorSpace::MakeSRGB(), // color space | |
| &surface_properties, // surface properties | |
| static_cast<SkSurfaces::TextureReleaseProc>( | |
| vulkan->destruction_callback), // release proc | |
| vulkan->user_data // release context | |
| ); | |
| if (!surface) { | |
| FML_LOG(ERROR) << "Could not wrap embedder supplied Vulkan render texture."; | |
| return nullptr; | |
| } | |
| return surface; | |
| #else | |
| return nullptr; | |
| #endif | |
| } | |
| static std::unique_ptr<flutter::EmbedderRenderTarget> | |
| MakeRenderTargetFromSkSurface(FlutterBackingStore backing_store, | |
| sk_sp<SkSurface> skia_surface, | |
| fml::closure on_release) { | |
| if (!skia_surface) { | |
| return nullptr; | |
| } | |
| return std::make_unique<flutter::EmbedderRenderTargetSkia>( | |
| backing_store, std::move(skia_surface), std::move(on_release)); | |
| } | |
| static std::unique_ptr<flutter::EmbedderRenderTarget> | |
| CreateEmbedderRenderTarget( | |
| const FlutterCompositor* compositor, | |
| const FlutterBackingStoreConfig& config, | |
| GrDirectContext* context, | |
| const std::shared_ptr<impeller::AiksContext>& aiks_context, | |
| bool enable_impeller) { | |
| FlutterBackingStore backing_store = {}; | |
| backing_store.struct_size = sizeof(backing_store); | |
| // Safe access checks on the compositor struct have been performed in | |
| // InferExternalViewEmbedderFromArgs and are not necessary here. | |
| auto c_create_callback = compositor->create_backing_store_callback; | |
| auto c_collect_callback = compositor->collect_backing_store_callback; | |
| { | |
| TRACE_EVENT0("flutter", "FlutterCompositorCreateBackingStore"); | |
| if (!c_create_callback(&config, &backing_store, compositor->user_data)) { | |
| FML_LOG(ERROR) << "Could not create the embedder backing store."; | |
| return nullptr; | |
| } | |
| } | |
| if (backing_store.struct_size != sizeof(backing_store)) { | |
| FML_LOG(ERROR) << "Embedder modified the backing store struct size."; | |
| return nullptr; | |
| } | |
| // In case we return early without creating an embedder render target, the | |
| // embedder has still given us ownership of its baton which we must return | |
| // back to it. If this method is successful, the closure is released when the | |
| // render target is eventually released. | |
| fml::ScopedCleanupClosure collect_callback( | |
| [c_collect_callback, backing_store, user_data = compositor->user_data]() { | |
| TRACE_EVENT0("flutter", "FlutterCompositorCollectBackingStore"); | |
| c_collect_callback(&backing_store, user_data); | |
| }); | |
| // No safe access checks on the renderer are necessary since we allocated | |
| // the struct. | |
| std::unique_ptr<flutter::EmbedderRenderTarget> render_target; | |
| switch (backing_store.type) { | |
| case kFlutterBackingStoreTypeOpenGL: { | |
| switch (backing_store.open_gl.type) { | |
| case kFlutterOpenGLTargetTypeTexture: { | |
| auto skia_surface = MakeSkSurfaceFromBackingStore( | |
| context, config, &backing_store.open_gl.texture); | |
| render_target = MakeRenderTargetFromSkSurface( | |
| backing_store, std::move(skia_surface), | |
| collect_callback.Release()); | |
| break; | |
| } | |
| case kFlutterOpenGLTargetTypeFramebuffer: { | |
| if (enable_impeller) { | |
| render_target = MakeRenderTargetFromBackingStoreImpeller( | |
| backing_store, collect_callback.Release(), aiks_context, config, | |
| &backing_store.open_gl.framebuffer); | |
| break; | |
| } else { | |
| auto skia_surface = MakeSkSurfaceFromBackingStore( | |
| context, config, &backing_store.open_gl.framebuffer); | |
| render_target = MakeRenderTargetFromSkSurface( | |
| backing_store, std::move(skia_surface), | |
| collect_callback.Release()); | |
| break; | |
| } | |
| } | |
| } | |
| break; | |
| } | |
| case kFlutterBackingStoreTypeSoftware: { | |
| auto skia_surface = MakeSkSurfaceFromBackingStore( | |
| context, config, &backing_store.software); | |
| render_target = MakeRenderTargetFromSkSurface( | |
| backing_store, std::move(skia_surface), collect_callback.Release()); | |
| break; | |
| } | |
| case kFlutterBackingStoreTypeSoftware2: { | |
| auto skia_surface = MakeSkSurfaceFromBackingStore( | |
| context, config, &backing_store.software2); | |
| render_target = MakeRenderTargetFromSkSurface( | |
| backing_store, std::move(skia_surface), collect_callback.Release()); | |
| break; | |
| } | |
| case kFlutterBackingStoreTypeMetal: { | |
| if (enable_impeller) { | |
| render_target = MakeRenderTargetFromBackingStoreImpeller( | |
| backing_store, collect_callback.Release(), aiks_context, config, | |
| &backing_store.metal); | |
| } else { | |
| auto skia_surface = MakeSkSurfaceFromBackingStore(context, config, | |
| &backing_store.metal); | |
| render_target = MakeRenderTargetFromSkSurface( | |
| backing_store, std::move(skia_surface), collect_callback.Release()); | |
| } | |
| break; | |
| } | |
| case kFlutterBackingStoreTypeVulkan: { | |
| auto skia_surface = | |
| MakeSkSurfaceFromBackingStore(context, config, &backing_store.vulkan); | |
| render_target = MakeRenderTargetFromSkSurface( | |
| backing_store, std::move(skia_surface), collect_callback.Release()); | |
| break; | |
| } | |
| }; | |
| if (!render_target) { | |
| FML_LOG(ERROR) << "Could not create a surface from an embedder provided " | |
| "render target."; | |
| } | |
| return render_target; | |
| } | |
| static std::pair<std::unique_ptr<flutter::EmbedderExternalViewEmbedder>, | |
| bool /* halt engine launch if true */> | |
| InferExternalViewEmbedderFromArgs(const FlutterCompositor* compositor, | |
| bool enable_impeller) { | |
| if (compositor == nullptr) { | |
| return {nullptr, false}; | |
| } | |
| auto c_create_callback = | |
| SAFE_ACCESS(compositor, create_backing_store_callback, nullptr); | |
| auto c_collect_callback = | |
| SAFE_ACCESS(compositor, collect_backing_store_callback, nullptr); | |
| auto c_present_callback = | |
| SAFE_ACCESS(compositor, present_layers_callback, nullptr); | |
| auto c_present_view_callback = | |
| SAFE_ACCESS(compositor, present_view_callback, nullptr); | |
| bool avoid_backing_store_cache = | |
| SAFE_ACCESS(compositor, avoid_backing_store_cache, false); | |
| // Make sure the required callbacks are present | |
| if (!c_create_callback || !c_collect_callback) { | |
| FML_LOG(ERROR) << "Required compositor callbacks absent."; | |
| return {nullptr, true}; | |
| } | |
| // Either the present view or the present layers callback must be provided. | |
| if ((!c_present_view_callback && !c_present_callback) || | |
| (c_present_view_callback && c_present_callback)) { | |
| FML_LOG(ERROR) << "Either present_layers_callback or present_view_callback " | |
| "must be provided but not both."; | |
| return {nullptr, true}; | |
| } | |
| FlutterCompositor captured_compositor = *compositor; | |
| flutter::EmbedderExternalViewEmbedder::CreateRenderTargetCallback | |
| create_render_target_callback = | |
| [captured_compositor, enable_impeller]( | |
| GrDirectContext* context, | |
| const std::shared_ptr<impeller::AiksContext>& aiks_context, | |
| const auto& config) { | |
| return CreateEmbedderRenderTarget(&captured_compositor, config, | |
| context, aiks_context, | |
| enable_impeller); | |
| }; | |
| flutter::EmbedderExternalViewEmbedder::PresentCallback present_callback; | |
| if (c_present_callback) { | |
| present_callback = [c_present_callback, user_data = compositor->user_data]( | |
| FlutterViewId view_id, const auto& layers) { | |
| TRACE_EVENT0("flutter", "FlutterCompositorPresentLayers"); | |
| return c_present_callback(const_cast<const FlutterLayer**>(layers.data()), | |
| layers.size(), user_data); | |
| }; | |
| } else { | |
| FML_DCHECK(c_present_view_callback != nullptr); | |
| present_callback = [c_present_view_callback, | |
| user_data = compositor->user_data]( | |
| FlutterViewId view_id, const auto& layers) { | |
| TRACE_EVENT0("flutter", "FlutterCompositorPresentLayers"); | |
| FlutterPresentViewInfo info = { | |
| .struct_size = sizeof(FlutterPresentViewInfo), | |
| .view_id = view_id, | |
| .layers = const_cast<const FlutterLayer**>(layers.data()), | |
| .layers_count = layers.size(), | |
| .user_data = user_data, | |
| }; | |
| return c_present_view_callback(&info); | |
| }; | |
| } | |
| return {std::make_unique<flutter::EmbedderExternalViewEmbedder>( | |
| avoid_backing_store_cache, create_render_target_callback, | |
| present_callback), | |
| false}; | |
| } | |
| // Translates embedder metrics to engine metrics, or returns a string on error. | |
| static std::variant<flutter::ViewportMetrics, std::string> | |
| MakeViewportMetricsFromWindowMetrics( | |
| const FlutterWindowMetricsEvent* flutter_metrics) { | |
| if (flutter_metrics == nullptr) { | |
| return "Invalid metrics handle."; | |
| } | |
| flutter::ViewportMetrics metrics; | |
| metrics.physical_width = SAFE_ACCESS(flutter_metrics, width, 0.0); | |
| metrics.physical_height = SAFE_ACCESS(flutter_metrics, height, 0.0); | |
| metrics.device_pixel_ratio = SAFE_ACCESS(flutter_metrics, pixel_ratio, 1.0); | |
| metrics.physical_view_inset_top = | |
| SAFE_ACCESS(flutter_metrics, physical_view_inset_top, 0.0); | |
| metrics.physical_view_inset_right = | |
| SAFE_ACCESS(flutter_metrics, physical_view_inset_right, 0.0); | |
| metrics.physical_view_inset_bottom = | |
| SAFE_ACCESS(flutter_metrics, physical_view_inset_bottom, 0.0); | |
| metrics.physical_view_inset_left = | |
| SAFE_ACCESS(flutter_metrics, physical_view_inset_left, 0.0); | |
| metrics.display_id = SAFE_ACCESS(flutter_metrics, display_id, 0); | |
| if (metrics.device_pixel_ratio <= 0.0) { | |
| return "Device pixel ratio was invalid. It must be greater than zero."; | |
| } | |
| if (metrics.physical_view_inset_top < 0 || | |
| metrics.physical_view_inset_right < 0 || | |
| metrics.physical_view_inset_bottom < 0 || | |
| metrics.physical_view_inset_left < 0) { | |
| return "Physical view insets are invalid. They must be non-negative."; | |
| } | |
| if (metrics.physical_view_inset_top > metrics.physical_height || | |
| metrics.physical_view_inset_right > metrics.physical_width || | |
| metrics.physical_view_inset_bottom > metrics.physical_height || | |
| metrics.physical_view_inset_left > metrics.physical_width) { | |
| return "Physical view insets are invalid. They cannot be greater than " | |
| "physical height or width."; | |
| } | |
| return metrics; | |
| } | |
| struct _FlutterPlatformMessageResponseHandle { | |
| std::unique_ptr<flutter::PlatformMessage> message; | |
| }; | |
| struct LoadedElfDeleter { | |
| void operator()(Dart_LoadedElf* elf) { | |
| if (elf) { | |
| ::Dart_UnloadELF(elf); | |
| } | |
| } | |
| }; | |
| using UniqueLoadedElf = std::unique_ptr<Dart_LoadedElf, LoadedElfDeleter>; | |
| struct _FlutterEngineAOTData { | |
| UniqueLoadedElf loaded_elf = nullptr; | |
| const uint8_t* vm_snapshot_data = nullptr; | |
| const uint8_t* vm_snapshot_instrs = nullptr; | |
| const uint8_t* vm_isolate_data = nullptr; | |
| const uint8_t* vm_isolate_instrs = nullptr; | |
| }; | |
| FlutterEngineResult FlutterEngineCreateAOTData( | |
| const FlutterEngineAOTDataSource* source, | |
| FlutterEngineAOTData* data_out) { | |
| if (!flutter::DartVM::IsRunningPrecompiledCode()) { | |
| return LOG_EMBEDDER_ERROR(kInvalidArguments, | |
| "AOT data can only be created in AOT mode."); | |
| } else if (!source) { | |
| return LOG_EMBEDDER_ERROR(kInvalidArguments, "Null source specified."); | |
| } else if (!data_out) { | |
| return LOG_EMBEDDER_ERROR(kInvalidArguments, "Null data_out specified."); | |
| } | |
| switch (source->type) { | |
| case kFlutterEngineAOTDataSourceTypeElfPath: { | |
| if (!source->elf_path || !fml::IsFile(source->elf_path)) { | |
| return LOG_EMBEDDER_ERROR(kInvalidArguments, | |
| "Invalid ELF path specified."); | |
| } | |
| auto aot_data = std::make_unique<_FlutterEngineAOTData>(); | |
| const char* error = nullptr; | |
| #if OS_FUCHSIA | |
| // TODO(gw280): https://github.com/flutter/flutter/issues/50285 | |
| // Dart doesn't implement Dart_LoadELF on Fuchsia | |
| Dart_LoadedElf* loaded_elf = nullptr; | |
| #else | |
| Dart_LoadedElf* loaded_elf = Dart_LoadELF( | |
| source->elf_path, // file path | |
| 0, // file offset | |
| &error, // error (out) | |
| &aot_data->vm_snapshot_data, // vm snapshot data (out) | |
| &aot_data->vm_snapshot_instrs, // vm snapshot instr (out) | |
| &aot_data->vm_isolate_data, // vm isolate data (out) | |
| &aot_data->vm_isolate_instrs // vm isolate instr (out) | |
| ); | |
| #endif | |
| if (loaded_elf == nullptr) { | |
| return LOG_EMBEDDER_ERROR(kInvalidArguments, error); | |
| } | |
| aot_data->loaded_elf.reset(loaded_elf); | |
| *data_out = aot_data.release(); | |
| return kSuccess; | |
| } | |
| } | |
| return LOG_EMBEDDER_ERROR( | |
| kInvalidArguments, | |
| "Invalid FlutterEngineAOTDataSourceType type specified."); | |
| } | |
| FlutterEngineResult FlutterEngineCollectAOTData(FlutterEngineAOTData data) { | |
| if (!data) { | |
| // Deleting a null object should be a no-op. | |
| return kSuccess; | |
| } | |
| // Created in a unique pointer in `FlutterEngineCreateAOTData`. | |
| delete data; | |
| return kSuccess; | |
| } | |
| // Constructs appropriate mapping callbacks if JIT snapshot locations have been | |
| // explictly specified. | |
| void PopulateJITSnapshotMappingCallbacks(const FlutterProjectArgs* args, | |
| flutter::Settings& settings) { | |
| auto make_mapping_callback = [](const char* path, bool executable) { | |
| return [path, executable]() { | |
| if (executable) { | |
| return fml::FileMapping::CreateReadExecute(path); | |
| } else { | |
| return fml::FileMapping::CreateReadOnly(path); | |
| } | |
| }; | |
| }; | |
| // Users are allowed to specify only certain snapshots if they so desire. | |
| if (SAFE_ACCESS(args, vm_snapshot_data, nullptr) != nullptr) { | |
| settings.vm_snapshot_data = make_mapping_callback( | |
| reinterpret_cast<const char*>(args->vm_snapshot_data), false); | |
| } | |
| if (SAFE_ACCESS(args, vm_snapshot_instructions, nullptr) != nullptr) { | |
| settings.vm_snapshot_instr = make_mapping_callback( | |
| reinterpret_cast<const char*>(args->vm_snapshot_instructions), true); | |
| } | |
| if (SAFE_ACCESS(args, isolate_snapshot_data, nullptr) != nullptr) { | |
| settings.isolate_snapshot_data = make_mapping_callback( | |
| reinterpret_cast<const char*>(args->isolate_snapshot_data), false); | |
| } | |
| if (SAFE_ACCESS(args, isolate_snapshot_instructions, nullptr) != nullptr) { | |
| settings.isolate_snapshot_instr = make_mapping_callback( | |
| reinterpret_cast<const char*>(args->isolate_snapshot_instructions), | |
| true); | |
| } | |
| #if !OS_FUCHSIA && (FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG) | |
| settings.dart_library_sources_kernel = []() { | |
| return std::make_unique<fml::NonOwnedMapping>(kPlatformStrongDill, | |
| kPlatformStrongDillSize); | |
| }; | |
| #endif // !OS_FUCHSIA && (FLUTTER_RUNTIME_MODE == | |
| // FLUTTER_RUNTIME_MODE_DEBUG) | |
| } | |
| void PopulateAOTSnapshotMappingCallbacks( | |
| const FlutterProjectArgs* args, | |
| flutter::Settings& settings) { // NOLINT(google-runtime-references) | |
| // There are no ownership concerns here as all mappings are owned by the | |
| // embedder and not the engine. | |
| auto make_mapping_callback = [](const uint8_t* mapping, size_t size) { | |
| return [mapping, size]() { | |
| return std::make_unique<fml::NonOwnedMapping>(mapping, size); | |
| }; | |
| }; | |
| if (SAFE_ACCESS(args, aot_data, nullptr) != nullptr) { | |
| settings.vm_snapshot_data = | |
| make_mapping_callback(args->aot_data->vm_snapshot_data, 0); | |
| settings.vm_snapshot_instr = | |
| make_mapping_callback(args->aot_data->vm_snapshot_instrs, 0); | |
| settings.isolate_snapshot_data = | |
| make_mapping_callback(args->aot_data->vm_isolate_data, 0); | |
| settings.isolate_snapshot_instr = | |
| make_mapping_callback(args->aot_data->vm_isolate_instrs, 0); | |
| } | |
| if (SAFE_ACCESS(args, vm_snapshot_data, nullptr) != nullptr) { | |
| settings.vm_snapshot_data = make_mapping_callback( | |
| args->vm_snapshot_data, SAFE_ACCESS(args, vm_snapshot_data_size, 0)); | |
| } | |
| if (SAFE_ACCESS(args, vm_snapshot_instructions, nullptr) != nullptr) { | |
| settings.vm_snapshot_instr = make_mapping_callback( | |
| args->vm_snapshot_instructions, | |
| SAFE_ACCESS(args, vm_snapshot_instructions_size, 0)); | |
| } | |
| if (SAFE_ACCESS(args, isolate_snapshot_data, nullptr) != nullptr) { | |
| settings.isolate_snapshot_data = | |
| make_mapping_callback(args->isolate_snapshot_data, | |
| SAFE_ACCESS(args, isolate_snapshot_data_size, 0)); | |
| } | |
| if (SAFE_ACCESS(args, isolate_snapshot_instructions, nullptr) != nullptr) { | |
| settings.isolate_snapshot_instr = make_mapping_callback( | |
| args->isolate_snapshot_instructions, | |
| SAFE_ACCESS(args, isolate_snapshot_instructions_size, 0)); | |
| } | |
| } | |
| // Create a callback to notify the embedder of semantic updates | |
| // using the legacy embedder callbacks 'update_semantics_node_callback' and | |
| // 'update_semantics_custom_action_callback'. | |
| flutter::PlatformViewEmbedder::UpdateSemanticsCallback | |
| CreateEmbedderSemanticsUpdateCallbackV1( | |
| FlutterUpdateSemanticsNodeCallback update_semantics_node_callback, | |
| FlutterUpdateSemanticsCustomActionCallback | |
| update_semantics_custom_action_callback, | |
| void* user_data) { | |
| return [update_semantics_node_callback, | |
| update_semantics_custom_action_callback, | |
| user_data](const flutter::SemanticsNodeUpdates& nodes, | |
| const flutter::CustomAccessibilityActionUpdates& actions) { | |
| flutter::EmbedderSemanticsUpdate update{nodes, actions}; | |
| FlutterSemanticsUpdate* update_ptr = update.get(); | |
| // First, queue all node and custom action updates. | |
| if (update_semantics_node_callback != nullptr) { | |
| for (size_t i = 0; i < update_ptr->nodes_count; i++) { | |
| update_semantics_node_callback(&update_ptr->nodes[i], user_data); | |
| } | |
| } | |
| if (update_semantics_custom_action_callback != nullptr) { | |
| for (size_t i = 0; i < update_ptr->custom_actions_count; i++) { | |
| update_semantics_custom_action_callback(&update_ptr->custom_actions[i], | |
| user_data); | |
| } | |
| } | |
| // Second, mark node and action batches completed now that all | |
| // updates are queued. | |
| if (update_semantics_node_callback != nullptr) { | |
| const FlutterSemanticsNode batch_end_sentinel = { | |
| sizeof(FlutterSemanticsNode), | |
| kFlutterSemanticsNodeIdBatchEnd, | |
| }; | |
| update_semantics_node_callback(&batch_end_sentinel, user_data); | |
| } | |
| if (update_semantics_custom_action_callback != nullptr) { | |
| const FlutterSemanticsCustomAction batch_end_sentinel = { | |
| sizeof(FlutterSemanticsCustomAction), | |
| kFlutterSemanticsCustomActionIdBatchEnd, | |
| }; | |
| update_semantics_custom_action_callback(&batch_end_sentinel, user_data); | |
| } | |
| }; | |
| } | |
| // Create a callback to notify the embedder of semantic updates | |
| // using the deprecated embedder callback 'update_semantics_callback'. | |
| flutter::PlatformViewEmbedder::UpdateSemanticsCallback | |
| CreateEmbedderSemanticsUpdateCallbackV2( | |
| FlutterUpdateSemanticsCallback update_semantics_callback, | |
| void* user_data) { | |
| return [update_semantics_callback, user_data]( | |
| const flutter::SemanticsNodeUpdates& nodes, | |
| const flutter::CustomAccessibilityActionUpdates& actions) { | |
| flutter::EmbedderSemanticsUpdate update{nodes, actions}; | |
| update_semantics_callback(update.get(), user_data); | |
| }; | |
| } | |
| // Create a callback to notify the embedder of semantic updates | |
| // using the new embedder callback 'update_semantics_callback2'. | |
| flutter::PlatformViewEmbedder::UpdateSemanticsCallback | |
| CreateEmbedderSemanticsUpdateCallbackV3( | |
| FlutterUpdateSemanticsCallback2 update_semantics_callback, | |
| void* user_data) { | |
| return [update_semantics_callback, user_data]( | |
| const flutter::SemanticsNodeUpdates& nodes, | |
| const flutter::CustomAccessibilityActionUpdates& actions) { | |
| flutter::EmbedderSemanticsUpdate2 update{nodes, actions}; | |
| update_semantics_callback(update.get(), user_data); | |
| }; | |
| } | |
| // Creates a callback that receives semantic updates from the engine | |
| // and notifies the embedder's callback(s). Returns null if the embedder | |
| // did not register any callbacks. | |
| flutter::PlatformViewEmbedder::UpdateSemanticsCallback | |
| CreateEmbedderSemanticsUpdateCallback(const FlutterProjectArgs* args, | |
| void* user_data) { | |
| // There are three variants for the embedder API's semantic update callbacks. | |
| // Create a callback that maps to the embedder's desired semantic update API. | |
| // | |
| // Handle the case where the embedder registered the callback | |
| // 'updated_semantics_callback2' | |
| if (SAFE_ACCESS(args, update_semantics_callback2, nullptr) != nullptr) { | |
| return CreateEmbedderSemanticsUpdateCallbackV3( | |
| args->update_semantics_callback2, user_data); | |
| } | |
| // Handle the case where the embedder registered the deprecated callback | |
| // 'update_semantics_callback'. | |
| if (SAFE_ACCESS(args, update_semantics_callback, nullptr) != nullptr) { | |
| return CreateEmbedderSemanticsUpdateCallbackV2( | |
| args->update_semantics_callback, user_data); | |
| } | |
| // Handle the case where the embedder registered the deprecated callbacks | |
| // 'update_semantics_node_callback' and | |
| // 'update_semantics_custom_action_callback'. | |
| FlutterUpdateSemanticsNodeCallback update_semantics_node_callback = nullptr; | |
| if (SAFE_ACCESS(args, update_semantics_node_callback, nullptr) != nullptr) { | |
| update_semantics_node_callback = args->update_semantics_node_callback; | |
| } | |
| FlutterUpdateSemanticsCustomActionCallback | |
| update_semantics_custom_action_callback = nullptr; | |
| if (SAFE_ACCESS(args, update_semantics_custom_action_callback, nullptr) != | |
| nullptr) { | |
| update_semantics_custom_action_callback = | |
| args->update_semantics_custom_action_callback; | |
| } | |
| if (update_semantics_node_callback != nullptr || | |
| update_semantics_custom_action_callback != nullptr) { | |
| return CreateEmbedderSemanticsUpdateCallbackV1( | |
| update_semantics_node_callback, update_semantics_custom_action_callback, | |
| user_data); | |
| } | |
| // Handle the case where the embedder registered no callbacks. | |
| return nullptr; | |
| } | |
| FlutterEngineResult FlutterEngineRun(size_t version, | |
| const FlutterRendererConfig* config, | |
| const FlutterProjectArgs* args, | |
| void* user_data, | |
| FLUTTER_API_SYMBOL(FlutterEngine) * | |
| engine_out) { | |
| auto result = | |
| FlutterEngineInitialize(version, config, args, user_data, engine_out); | |
| if (result != kSuccess) { | |
| return result; | |
| } | |
| return FlutterEngineRunInitialized(*engine_out); | |
| } | |
| FlutterEngineResult FlutterEngineInitialize(size_t version, | |
| const FlutterRendererConfig* config, | |
| const FlutterProjectArgs* args, | |
| void* user_data, | |
| FLUTTER_API_SYMBOL(FlutterEngine) * | |
| engine_out) { | |
| // Step 0: Figure out arguments for shell creation. | |
| if (version != FLUTTER_ENGINE_VERSION) { | |
| return LOG_EMBEDDER_ERROR( | |
| kInvalidLibraryVersion, | |
| "Flutter embedder version mismatch. There has been a breaking change. " | |
| "Please consult the changelog and update the embedder."); | |
| } | |
| if (engine_out == nullptr) { | |
| return LOG_EMBEDDER_ERROR(kInvalidArguments, | |
| "The engine out parameter was missing."); | |
| } | |
| if (args == nullptr) { | |
| return LOG_EMBEDDER_ERROR(kInvalidArguments, | |
| "The Flutter project arguments were missing."); | |
| } | |
| if (SAFE_ACCESS(args, assets_path, nullptr) == nullptr) { | |
| return LOG_EMBEDDER_ERROR( | |
| kInvalidArguments, | |
| "The assets path in the Flutter project arguments was missing."); | |
| } | |
| if (SAFE_ACCESS(args, main_path__unused__, nullptr) != nullptr) { | |
| FML_LOG(WARNING) | |
| << "FlutterProjectArgs.main_path is deprecated and should be set null."; | |
| } | |
| if (SAFE_ACCESS(args, packages_path__unused__, nullptr) != nullptr) { | |
| FML_LOG(WARNING) << "FlutterProjectArgs.packages_path is deprecated and " | |
| "should be set null."; | |
| } | |
| if (!IsRendererValid(config)) { | |
| return LOG_EMBEDDER_ERROR(kInvalidArguments, | |
| "The renderer configuration was invalid."); | |
| } | |
| std::string icu_data_path; | |
| if (SAFE_ACCESS(args, icu_data_path, nullptr) != nullptr) { | |
| icu_data_path = SAFE_ACCESS(args, icu_data_path, nullptr); | |
| } | |
| if (SAFE_ACCESS(args, persistent_cache_path, nullptr) != nullptr) { | |
| std::string persistent_cache_path = | |
| SAFE_ACCESS(args, persistent_cache_path, nullptr); | |
| flutter::PersistentCache::SetCacheDirectoryPath(persistent_cache_path); | |
| } | |
| if (SAFE_ACCESS(args, is_persistent_cache_read_only, false)) { | |
| flutter::PersistentCache::gIsReadOnly = true; | |
| } | |
| fml::CommandLine command_line; | |
| if (SAFE_ACCESS(args, command_line_argc, 0) != 0 && | |
| SAFE_ACCESS(args, command_line_argv, nullptr) != nullptr) { | |
| command_line = fml::CommandLineFromArgcArgv( | |
| SAFE_ACCESS(args, command_line_argc, 0), | |
| SAFE_ACCESS(args, command_line_argv, nullptr)); | |
| } | |
| flutter::Settings settings = flutter::SettingsFromCommandLine(command_line); | |
| if (SAFE_ACCESS(args, aot_data, nullptr)) { | |
| if (SAFE_ACCESS(args, vm_snapshot_data, nullptr) || | |
| SAFE_ACCESS(args, vm_snapshot_instructions, nullptr) || | |
| SAFE_ACCESS(args, isolate_snapshot_data, nullptr) || | |
| SAFE_ACCESS(args, isolate_snapshot_instructions, nullptr)) { | |
| return LOG_EMBEDDER_ERROR( | |
| kInvalidArguments, | |
| "Multiple AOT sources specified. Embedders should provide either " | |
| "*_snapshot_* buffers or aot_data, not both."); | |
| } | |
| } | |
| if (flutter::DartVM::IsRunningPrecompiledCode()) { | |
| PopulateAOTSnapshotMappingCallbacks(args, settings); | |
| } else { | |
| PopulateJITSnapshotMappingCallbacks(args, settings); | |
| } | |
| settings.icu_data_path = icu_data_path; | |
| settings.assets_path = args->assets_path; | |
| settings.leak_vm = !SAFE_ACCESS(args, shutdown_dart_vm_when_done, false); | |
| settings.old_gen_heap_size = SAFE_ACCESS(args, dart_old_gen_heap_size, -1); | |
| if (!flutter::DartVM::IsRunningPrecompiledCode()) { | |
| // Verify the assets path contains Dart 2 kernel assets. | |
| const std::string kApplicationKernelSnapshotFileName = "kernel_blob.bin"; | |
| std::string application_kernel_path = fml::paths::JoinPaths( | |
| {settings.assets_path, kApplicationKernelSnapshotFileName}); | |
| if (!fml::IsFile(application_kernel_path)) { | |
| return LOG_EMBEDDER_ERROR( | |
| kInvalidArguments, | |
| "Not running in AOT mode but could not resolve the kernel binary."); | |
| } | |
| settings.application_kernel_asset = kApplicationKernelSnapshotFileName; | |
| } | |
| 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); | |
| }; | |
| if (SAFE_ACCESS(args, root_isolate_create_callback, nullptr) != nullptr) { | |
| VoidCallback callback = | |
| SAFE_ACCESS(args, root_isolate_create_callback, nullptr); | |
| settings.root_isolate_create_callback = | |
| [callback, user_data](const auto& isolate) { callback(user_data); }; | |
| } | |
| if (SAFE_ACCESS(args, log_message_callback, nullptr) != nullptr) { | |
| FlutterLogMessageCallback callback = | |
| SAFE_ACCESS(args, log_message_callback, nullptr); | |
| settings.log_message_callback = [callback, user_data]( | |
| const std::string& tag, | |
| const std::string& message) { | |
| callback(tag.c_str(), message.c_str(), user_data); | |
| }; | |
| } | |
| if (SAFE_ACCESS(args, log_tag, nullptr) != nullptr) { | |
| settings.log_tag = SAFE_ACCESS(args, log_tag, nullptr); | |
| } | |
| bool has_update_semantics_2_callback = | |
| SAFE_ACCESS(args, update_semantics_callback2, nullptr) != nullptr; | |
| bool has_update_semantics_callback = | |
| SAFE_ACCESS(args, update_semantics_callback, nullptr) != nullptr; | |
| bool has_legacy_update_semantics_callback = | |
| SAFE_ACCESS(args, update_semantics_node_callback, nullptr) != nullptr || | |
| SAFE_ACCESS(args, update_semantics_custom_action_callback, nullptr) != | |
| nullptr; | |
| int semantic_callback_count = (has_update_semantics_2_callback ? 1 : 0) + | |
| (has_update_semantics_callback ? 1 : 0) + | |
| (has_legacy_update_semantics_callback ? 1 : 0); | |
| if (semantic_callback_count > 1) { | |
| return LOG_EMBEDDER_ERROR( | |
| kInvalidArguments, | |
| "Multiple semantics update callbacks provided. " | |
| "Embedders should provide either `update_semantics_callback2`, " | |
| "`update_semantics_callback`, or both " | |
| "`update_semantics_node_callback` and " | |
| "`update_semantics_custom_action_callback`."); | |
| } | |
| flutter::PlatformViewEmbedder::UpdateSemanticsCallback | |
| update_semantics_callback = | |
| CreateEmbedderSemanticsUpdateCallback(args, user_data); | |
| flutter::PlatformViewEmbedder::PlatformMessageResponseCallback | |
| platform_message_response_callback = nullptr; | |
| if (SAFE_ACCESS(args, platform_message_callback, nullptr) != nullptr) { | |
| platform_message_response_callback = | |
| [ptr = args->platform_message_callback, | |
| user_data](std::unique_ptr<flutter::PlatformMessage> message) { | |
| auto handle = new FlutterPlatformMessageResponseHandle(); | |
| const FlutterPlatformMessage incoming_message = { | |
| sizeof(FlutterPlatformMessage), // struct_size | |
| message->channel().c_str(), // channel | |
| message->data().GetMapping(), // message | |
| message->data().GetSize(), // message_size | |
| handle, // response_handle | |
| }; | |
| handle->message = std::move(message); | |
| return ptr(&incoming_message, user_data); | |
| }; | |
| } | |
| flutter::VsyncWaiterEmbedder::VsyncCallback vsync_callback = nullptr; | |
| if (SAFE_ACCESS(args, vsync_callback, nullptr) != nullptr) { | |
| vsync_callback = [ptr = args->vsync_callback, user_data](intptr_t baton) { | |
| return ptr(user_data, baton); | |
| }; | |
| } | |
| flutter::PlatformViewEmbedder::ComputePlatformResolvedLocaleCallback | |
| compute_platform_resolved_locale_callback = nullptr; | |
| if (SAFE_ACCESS(args, compute_platform_resolved_locale_callback, nullptr) != | |
| nullptr) { | |
| compute_platform_resolved_locale_callback = | |
| [ptr = args->compute_platform_resolved_locale_callback]( | |
| const std::vector<std::string>& supported_locales_data) { | |
| const size_t number_of_strings_per_locale = 3; | |
| size_t locale_count = | |
| supported_locales_data.size() / number_of_strings_per_locale; | |
| std::vector<FlutterLocale> supported_locales; | |
| std::vector<const FlutterLocale*> supported_locales_ptr; | |
| for (size_t i = 0; i < locale_count; ++i) { | |
| supported_locales.push_back( | |
| {.struct_size = sizeof(FlutterLocale), | |
| .language_code = | |
| supported_locales_data[i * number_of_strings_per_locale + | |
| 0] | |
| .c_str(), | |
| .country_code = | |
| supported_locales_data[i * number_of_strings_per_locale + | |
| 1] | |
| .c_str(), | |
| .script_code = | |
| supported_locales_data[i * number_of_strings_per_locale + | |
| 2] | |
| .c_str(), | |
| .variant_code = nullptr}); | |
| supported_locales_ptr.push_back(&supported_locales[i]); | |
| } | |
| const FlutterLocale* result = | |
| ptr(supported_locales_ptr.data(), locale_count); | |
| std::unique_ptr<std::vector<std::string>> out = | |
| std::make_unique<std::vector<std::string>>(); | |
| if (result) { | |
| std::string language_code(SAFE_ACCESS(result, language_code, "")); | |
| if (language_code != "") { | |
| out->push_back(language_code); | |
| out->emplace_back(SAFE_ACCESS(result, country_code, "")); | |
| out->emplace_back(SAFE_ACCESS(result, script_code, "")); | |
| } | |
| } | |
| return out; | |
| }; | |
| } | |
| flutter::PlatformViewEmbedder::OnPreEngineRestartCallback | |
| on_pre_engine_restart_callback = nullptr; | |
| if (SAFE_ACCESS(args, on_pre_engine_restart_callback, nullptr) != nullptr) { | |
| on_pre_engine_restart_callback = [ptr = | |
| args->on_pre_engine_restart_callback, | |
| user_data]() { return ptr(user_data); }; | |
| } | |
| flutter::PlatformViewEmbedder::ChanneUpdateCallback channel_update_callback = | |
| nullptr; | |
| if (SAFE_ACCESS(args, channel_update_callback, nullptr) != nullptr) { | |
| channel_update_callback = [ptr = args->channel_update_callback, user_data]( | |
| const std::string& name, bool listening) { | |
| FlutterChannelUpdate update{sizeof(FlutterChannelUpdate), name.c_str(), | |
| listening}; | |
| ptr(&update, user_data); | |
| }; | |
| } | |
| auto external_view_embedder_result = InferExternalViewEmbedderFromArgs( | |
| SAFE_ACCESS(args, compositor, nullptr), settings.enable_impeller); | |
| if (external_view_embedder_result.second) { | |
| return LOG_EMBEDDER_ERROR(kInvalidArguments, | |
| "Compositor arguments were invalid."); | |
| } | |
| flutter::PlatformViewEmbedder::PlatformDispatchTable platform_dispatch_table = | |
| { | |
| update_semantics_callback, // | |
| platform_message_response_callback, // | |
| vsync_callback, // | |
| compute_platform_resolved_locale_callback, // | |
| on_pre_engine_restart_callback, // | |
| channel_update_callback, // | |
| }; | |
| auto on_create_platform_view = InferPlatformViewCreationCallback( | |
| config, user_data, platform_dispatch_table, | |
| std::move(external_view_embedder_result.first), settings.enable_impeller); | |
| if (!on_create_platform_view) { | |
| return LOG_EMBEDDER_ERROR( | |
| kInternalInconsistency, | |
| "Could not infer platform view creation callback."); | |
| } | |
| flutter::Shell::CreateCallback<flutter::Rasterizer> on_create_rasterizer = | |
| [](flutter::Shell& shell) { | |
| return std::make_unique<flutter::Rasterizer>(shell); | |
| }; | |
| using ExternalTextureResolver = flutter::EmbedderExternalTextureResolver; | |
| std::unique_ptr<ExternalTextureResolver> external_texture_resolver; | |
| external_texture_resolver = std::make_unique<ExternalTextureResolver>(); | |
| #ifdef SHELL_ENABLE_GL | |
| flutter::EmbedderExternalTextureGL::ExternalTextureCallback | |
| external_texture_callback; | |
| if (config->type == kOpenGL) { | |
| const FlutterOpenGLRendererConfig* open_gl_config = &config->open_gl; | |
| if (SAFE_ACCESS(open_gl_config, gl_external_texture_frame_callback, | |
| nullptr) != nullptr) { | |
| external_texture_callback = | |
| [ptr = open_gl_config->gl_external_texture_frame_callback, user_data]( | |
| int64_t texture_identifier, size_t width, | |
| size_t height) -> std::unique_ptr<FlutterOpenGLTexture> { | |
| std::unique_ptr<FlutterOpenGLTexture> texture = | |
| std::make_unique<FlutterOpenGLTexture>(); | |
| if (!ptr(user_data, texture_identifier, width, height, texture.get())) { | |
| return nullptr; | |
| } | |
| return texture; | |
| }; | |
| external_texture_resolver = | |
| std::make_unique<ExternalTextureResolver>(external_texture_callback); | |
| } | |
| } | |
| #endif | |
| #ifdef SHELL_ENABLE_METAL | |
| flutter::EmbedderExternalTextureMetal::ExternalTextureCallback | |
| external_texture_metal_callback; | |
| if (config->type == kMetal) { | |
| const FlutterMetalRendererConfig* metal_config = &config->metal; | |
| if (SAFE_ACCESS(metal_config, external_texture_frame_callback, nullptr)) { | |
| external_texture_metal_callback = | |
| [ptr = metal_config->external_texture_frame_callback, user_data]( | |
| int64_t texture_identifier, size_t width, | |
| size_t height) -> std::unique_ptr<FlutterMetalExternalTexture> { | |
| std::unique_ptr<FlutterMetalExternalTexture> texture = | |
| std::make_unique<FlutterMetalExternalTexture>(); | |
| texture->struct_size = sizeof(FlutterMetalExternalTexture); | |
| if (!ptr(user_data, texture_identifier, width, height, texture.get())) { | |
| return nullptr; | |
| } | |
| return texture; | |
| }; | |
| external_texture_resolver = std::make_unique<ExternalTextureResolver>( | |
| external_texture_metal_callback); | |
| } | |
| } | |
| #endif | |
| auto custom_task_runners = SAFE_ACCESS(args, custom_task_runners, nullptr); | |
| auto thread_config_callback = [&custom_task_runners]( | |
| const fml::Thread::ThreadConfig& config) { | |
| fml::Thread::SetCurrentThreadName(config); | |
| if (!custom_task_runners || !custom_task_runners->thread_priority_setter) { | |
| return; | |
| } | |
| FlutterThreadPriority priority = FlutterThreadPriority::kNormal; | |
| switch (config.priority) { | |
| case fml::Thread::ThreadPriority::kBackground: | |
| priority = FlutterThreadPriority::kBackground; | |
| break; | |
| case fml::Thread::ThreadPriority::kNormal: | |
| priority = FlutterThreadPriority::kNormal; | |
| break; | |
| case fml::Thread::ThreadPriority::kDisplay: | |
| priority = FlutterThreadPriority::kDisplay; | |
| break; | |
| case fml::Thread::ThreadPriority::kRaster: | |
| priority = FlutterThreadPriority::kRaster; | |
| break; | |
| } | |
| custom_task_runners->thread_priority_setter(priority); | |
| }; | |
| auto thread_host = | |
| flutter::EmbedderThreadHost::CreateEmbedderOrEngineManagedThreadHost( | |
| custom_task_runners, thread_config_callback); | |
| if (!thread_host || !thread_host->IsValid()) { | |
| return LOG_EMBEDDER_ERROR(kInvalidArguments, | |
| "Could not set up or infer thread configuration " | |
| "to run the Flutter engine on."); | |
| } | |
| auto task_runners = thread_host->GetTaskRunners(); | |
| if (!task_runners.IsValid()) { | |
| return LOG_EMBEDDER_ERROR(kInternalInconsistency, | |
| "Task runner configuration was invalid."); | |
| } | |
| auto run_configuration = | |
| flutter::RunConfiguration::InferFromSettings(settings); | |
| if (SAFE_ACCESS(args, custom_dart_entrypoint, nullptr) != nullptr) { | |
| auto dart_entrypoint = std::string{args->custom_dart_entrypoint}; | |
| if (!dart_entrypoint.empty()) { | |
| run_configuration.SetEntrypoint(std::move(dart_entrypoint)); | |
| } | |
| } | |
| if (SAFE_ACCESS(args, dart_entrypoint_argc, 0) > 0) { | |
| if (SAFE_ACCESS(args, dart_entrypoint_argv, nullptr) == nullptr) { | |
| return LOG_EMBEDDER_ERROR(kInvalidArguments, | |
| "Could not determine Dart entrypoint arguments " | |
| "as dart_entrypoint_argc " | |
| "was set, but dart_entrypoint_argv was null."); | |
| } | |
| std::vector<std::string> arguments(args->dart_entrypoint_argc); | |
| for (int i = 0; i < args->dart_entrypoint_argc; ++i) { | |
| arguments[i] = std::string{args->dart_entrypoint_argv[i]}; | |
| } | |
| run_configuration.SetEntrypointArgs(std::move(arguments)); | |
| } | |
| if (!run_configuration.IsValid()) { | |
| return LOG_EMBEDDER_ERROR( | |
| kInvalidArguments, | |
| "Could not infer the Flutter project to run from given arguments."); | |
| } | |
| // Create the engine but don't launch the shell or run the root isolate. | |
| auto embedder_engine = std::make_unique<flutter::EmbedderEngine>( | |
| std::move(thread_host), // | |
| std::move(task_runners), // | |
| std::move(settings), // | |
| std::move(run_configuration), // | |
| on_create_platform_view, // | |
| on_create_rasterizer, // | |
| std::move(external_texture_resolver) // | |
| ); | |
| // Release the ownership of the embedder engine to the caller. | |
| *engine_out = reinterpret_cast<FLUTTER_API_SYMBOL(FlutterEngine)>( | |
| embedder_engine.release()); | |
| return kSuccess; | |
| } | |
| FlutterEngineResult FlutterEngineRunInitialized( | |
| FLUTTER_API_SYMBOL(FlutterEngine) engine) { | |
| if (!engine) { | |
| return LOG_EMBEDDER_ERROR(kInvalidArguments, "Engine handle was invalid."); | |
| } | |
| auto embedder_engine = reinterpret_cast<flutter::EmbedderEngine*>(engine); | |
| // The engine must not already be running. Initialize may only be called | |
| // once on an engine instance. | |
| if (embedder_engine->IsValid()) { | |
| return LOG_EMBEDDER_ERROR(kInvalidArguments, "Engine handle was invalid."); | |
| } | |
| // Step 1: Launch the shell. | |
| if (!embedder_engine->LaunchShell()) { | |
| return LOG_EMBEDDER_ERROR(kInvalidArguments, | |
| "Could not launch the engine using supplied " | |
| "initialization arguments."); | |
| } | |
| // Step 2: Tell the platform view to initialize itself. | |
| if (!embedder_engine->NotifyCreated()) { | |
| return LOG_EMBEDDER_ERROR(kInternalInconsistency, | |
| "Could not create platform view components."); | |
| } | |
| // Step 3: Launch the root isolate. | |
| if (!embedder_engine->RunRootIsolate()) { | |
| return LOG_EMBEDDER_ERROR( | |
| kInvalidArguments, | |
| "Could not run the root isolate of the Flutter application using the " | |
| "project arguments specified."); | |
| } | |
| return kSuccess; | |
| } | |
| FLUTTER_EXPORT | |
| FlutterEngineResult FlutterEngineAddView(FLUTTER_API_SYMBOL(FlutterEngine) | |
| engine, | |
| const FlutterAddViewInfo* info) { | |
| if (!engine) { | |
| return LOG_EMBEDDER_ERROR(kInvalidArguments, "Engine handle was invalid."); | |
| } | |
| if (!info || !info->view_metrics || !info->add_view_callback) { | |
| return LOG_EMBEDDER_ERROR(kInvalidArguments, | |
| "Add view info handle was invalid."); | |
| } | |
| FlutterViewId view_id = info->view_id; | |
| if (view_id == kFlutterImplicitViewId) { | |
| return LOG_EMBEDDER_ERROR( | |
| kInvalidArguments, | |
| "Add view info was invalid. The implicit view cannot be added."); | |
| } | |
| if (SAFE_ACCESS(info->view_metrics, view_id, kFlutterImplicitViewId) != | |
| view_id) { | |
| if (view_id == kFlutterImplicitViewId) { | |
| return LOG_EMBEDDER_ERROR(kInvalidArguments, | |
| "Add view info was invalid. The info and " | |
| "window metric view IDs must match."); | |
| } | |
| } | |
| // TODO(loicsharma): Return an error if the engine was initialized with | |
| // callbacks that are incompatible with multiple views. | |
| // https://github.com/flutter/flutter/issues/144806 | |
| std::variant<flutter::ViewportMetrics, std::string> metrics_or_error = | |
| MakeViewportMetricsFromWindowMetrics(info->view_metrics); | |
| if (const std::string* error = std::get_if<std::string>(&metrics_or_error)) { | |
| return LOG_EMBEDDER_ERROR(kInvalidArguments, error->c_str()); | |
| } | |
| auto metrics = std::get<flutter::ViewportMetrics>(metrics_or_error); | |
| // The engine must be running to add a view. | |
| auto embedder_engine = reinterpret_cast<flutter::EmbedderEngine*>(engine); | |
| if (!embedder_engine->IsValid()) { | |
| return LOG_EMBEDDER_ERROR(kInvalidArguments, "Engine handle was invalid."); | |
| } | |
| flutter::PlatformView::AddViewCallback callback = | |
| [c_callback = info->add_view_callback, | |
| user_data = info->user_data](bool added) { | |
| FlutterAddViewResult result = {}; | |
| result.struct_size = sizeof(FlutterAddViewResult); | |
| result.added = added; | |
| result.user_data = user_data; | |
| c_callback(&result); | |
| }; | |
| embedder_engine->GetShell().GetPlatformView()->AddView(view_id, metrics, | |
| callback); | |
| return kSuccess; | |
| } | |
| FLUTTER_EXPORT | |
| FlutterEngineResult FlutterEngineRemoveView(FLUTTER_API_SYMBOL(FlutterEngine) | |
| engine, | |
| const FlutterRemoveViewInfo* info) { | |
| if (!engine) { | |
| return LOG_EMBEDDER_ERROR(kInvalidArguments, "Engine handle was invalid."); | |
| } | |
| if (!info || !info->remove_view_callback) { | |
| return LOG_EMBEDDER_ERROR(kInvalidArguments, | |
| "Remove view info handle was invalid."); | |
| } | |
| if (info->view_id == kFlutterImplicitViewId) { | |
| return LOG_EMBEDDER_ERROR( | |
| kInvalidArguments, | |
| "Remove view info was invalid. The implicit view cannot be removed."); | |
| } | |
| // TODO(loicsharma): Return an error if the engine was initialized with | |
| // callbacks that are incompatible with multiple views. | |
| // https://github.com/flutter/flutter/issues/144806 | |
| // The engine must be running to remove a view. | |
| auto embedder_engine = reinterpret_cast<flutter::EmbedderEngine*>(engine); | |
| if (!embedder_engine->IsValid()) { | |
| return LOG_EMBEDDER_ERROR(kInvalidArguments, "Engine handle was invalid."); | |
| } | |
| flutter::PlatformView::RemoveViewCallback callback = | |
| [c_callback = info->remove_view_callback, | |
| user_data = info->user_data](bool removed) { | |
| FlutterRemoveViewResult result = {}; | |
| result.struct_size = sizeof(FlutterRemoveViewResult); | |
| result.removed = removed; | |
| result.user_data = user_data; | |
| c_callback(&result); | |
| }; | |
| embedder_engine->GetShell().GetPlatformView()->RemoveView(info->view_id, | |
| callback); | |
| return kSuccess; | |
| } | |
| FLUTTER_EXPORT | |
| FlutterEngineResult FlutterEngineDeinitialize(FLUTTER_API_SYMBOL(FlutterEngine) | |
| engine) { | |
| if (engine == nullptr) { | |
| return LOG_EMBEDDER_ERROR(kInvalidArguments, "Engine handle was invalid."); | |
| } | |
| auto embedder_engine = reinterpret_cast<flutter::EmbedderEngine*>(engine); | |
| embedder_engine->NotifyDestroyed(); | |
| embedder_engine->CollectShell(); | |
| return kSuccess; | |
| } | |
| FlutterEngineResult FlutterEngineShutdown(FLUTTER_API_SYMBOL(FlutterEngine) | |
| engine) { | |
| auto result = FlutterEngineDeinitialize(engine); | |
| if (result != kSuccess) { | |
| return result; | |
| } | |
| auto embedder_engine = reinterpret_cast<flutter::EmbedderEngine*>(engine); | |
| delete embedder_engine; | |
| return kSuccess; | |
| } | |
| FlutterEngineResult FlutterEngineSendWindowMetricsEvent( | |
| FLUTTER_API_SYMBOL(FlutterEngine) engine, | |
| const FlutterWindowMetricsEvent* flutter_metrics) { | |
| if (engine == nullptr || flutter_metrics == nullptr) { | |
| return LOG_EMBEDDER_ERROR(kInvalidArguments, "Engine handle was invalid."); | |
| } | |
| FlutterViewId view_id = | |
| SAFE_ACCESS(flutter_metrics, view_id, kFlutterImplicitViewId); | |
| std::variant<flutter::ViewportMetrics, std::string> metrics_or_error = | |
| MakeViewportMetricsFromWindowMetrics(flutter_metrics); | |
| if (const std::string* error = std::get_if<std::string>(&metrics_or_error)) { | |
| return LOG_EMBEDDER_ERROR(kInvalidArguments, error->c_str()); | |
| } | |
| auto metrics = std::get<flutter::ViewportMetrics>(metrics_or_error); | |
| return reinterpret_cast<flutter::EmbedderEngine*>(engine)->SetViewportMetrics( | |
| view_id, metrics) | |
| ? kSuccess | |
| : LOG_EMBEDDER_ERROR(kInvalidArguments, | |
| "Viewport metrics were invalid."); | |
| } | |
| // Returns the flutter::PointerData::Change for the given FlutterPointerPhase. | |
| inline flutter::PointerData::Change ToPointerDataChange( | |
| FlutterPointerPhase phase) { | |
| switch (phase) { | |
| case kCancel: | |
| return flutter::PointerData::Change::kCancel; | |
| case kUp: | |
| return flutter::PointerData::Change::kUp; | |
| case kDown: | |
| return flutter::PointerData::Change::kDown; | |
| case kMove: | |
| return flutter::PointerData::Change::kMove; | |
| case kAdd: | |
| return flutter::PointerData::Change::kAdd; | |
| case kRemove: | |
| return flutter::PointerData::Change::kRemove; | |
| case kHover: | |
| return flutter::PointerData::Change::kHover; | |
| case kPanZoomStart: | |
| return flutter::PointerData::Change::kPanZoomStart; | |
| case kPanZoomUpdate: | |
| return flutter::PointerData::Change::kPanZoomUpdate; | |
| case kPanZoomEnd: | |
| return flutter::PointerData::Change::kPanZoomEnd; | |
| } | |
| return flutter::PointerData::Change::kCancel; | |
| } | |
| // Returns the flutter::PointerData::DeviceKind for the given | |
| // FlutterPointerDeviceKind. | |
| inline flutter::PointerData::DeviceKind ToPointerDataKind( | |
| FlutterPointerDeviceKind device_kind) { | |
| switch (device_kind) { | |
| case kFlutterPointerDeviceKindMouse: | |
| return flutter::PointerData::DeviceKind::kMouse; | |
| case kFlutterPointerDeviceKindTouch: | |
| return flutter::PointerData::DeviceKind::kTouch; | |
| case kFlutterPointerDeviceKindStylus: | |
| return flutter::PointerData::DeviceKind::kStylus; | |
| case kFlutterPointerDeviceKindTrackpad: | |
| return flutter::PointerData::DeviceKind::kTrackpad; | |
| } | |
| return flutter::PointerData::DeviceKind::kMouse; | |
| } | |
| // Returns the flutter::PointerData::SignalKind for the given | |
| // FlutterPointerSignaKind. | |
| inline flutter::PointerData::SignalKind ToPointerDataSignalKind( | |
| FlutterPointerSignalKind kind) { | |
| switch (kind) { | |
| case kFlutterPointerSignalKindNone: | |
| return flutter::PointerData::SignalKind::kNone; | |
| case kFlutterPointerSignalKindScroll: | |
| return flutter::PointerData::SignalKind::kScroll; | |
| case kFlutterPointerSignalKindScrollInertiaCancel: | |
| return flutter::PointerData::SignalKind::kScrollInertiaCancel; | |
| case kFlutterPointerSignalKindScale: | |
| return flutter::PointerData::SignalKind::kScale; | |
| } | |
| return flutter::PointerData::SignalKind::kNone; | |
| } | |
| // Returns the buttons to synthesize for a PointerData from a | |
| // FlutterPointerEvent with no type or buttons set. | |
| inline int64_t PointerDataButtonsForLegacyEvent( | |
| flutter::PointerData::Change change) { | |
| switch (change) { | |
| case flutter::PointerData::Change::kDown: | |
| case flutter::PointerData::Change::kMove: | |
| // These kinds of change must have a non-zero `buttons`, otherwise | |
| // gesture recognizers will ignore these events. | |
| return flutter::kPointerButtonMousePrimary; | |
| case flutter::PointerData::Change::kCancel: | |
| case flutter::PointerData::Change::kAdd: | |
| case flutter::PointerData::Change::kRemove: | |
| case flutter::PointerData::Change::kHover: | |
| case flutter::PointerData::Change::kUp: | |
| case flutter::PointerData::Change::kPanZoomStart: | |
| case flutter::PointerData::Change::kPanZoomUpdate: | |
| case flutter::PointerData::Change::kPanZoomEnd: | |
| return 0; | |
| } | |
| return 0; | |
| } | |
| FlutterEngineResult FlutterEngineSendPointerEvent( | |
| FLUTTER_API_SYMBOL(FlutterEngine) engine, | |
| const FlutterPointerEvent* pointers, | |
| size_t events_count) { | |
| if (engine == nullptr) { | |
| return LOG_EMBEDDER_ERROR(kInvalidArguments, "Engine handle was invalid."); | |
| } | |
| if (pointers == nullptr || events_count == 0) { | |
| return LOG_EMBEDDER_ERROR(kInvalidArguments, "Invalid pointer events."); | |
| } | |
| auto packet = std::make_unique<flutter::PointerDataPacket>(events_count); | |
| const FlutterPointerEvent* current = pointers; | |
| for (size_t i = 0; i < events_count; ++i) { | |
| flutter::PointerData pointer_data; | |
| pointer_data.Clear(); | |
| // this is currely in use only on android embedding. | |
| pointer_data.embedder_id = 0; | |
| pointer_data.time_stamp = SAFE_ACCESS(current, timestamp, 0); | |
| pointer_data.change = ToPointerDataChange( | |
| SAFE_ACCESS(current, phase, FlutterPointerPhase::kCancel)); | |
| pointer_data.physical_x = SAFE_ACCESS(current, x, 0.0); | |
| pointer_data.physical_y = SAFE_ACCESS(current, y, 0.0); | |
| // Delta will be generated in pointer_data_packet_converter.cc. | |
| pointer_data.physical_delta_x = 0.0; | |
| pointer_data.physical_delta_y = 0.0; | |
| pointer_data.device = SAFE_ACCESS(current, device, 0); | |
| // Pointer identifier will be generated in | |
| // pointer_data_packet_converter.cc. | |
| pointer_data.pointer_identifier = 0; | |
| pointer_data.signal_kind = ToPointerDataSignalKind( | |
| SAFE_ACCESS(current, signal_kind, kFlutterPointerSignalKindNone)); | |
| pointer_data.scroll_delta_x = SAFE_ACCESS(current, scroll_delta_x, 0.0); | |
| pointer_data.scroll_delta_y = SAFE_ACCESS(current, scroll_delta_y, 0.0); | |
| FlutterPointerDeviceKind device_kind = SAFE_ACCESS(current, device_kind, 0); | |
| // For backwards compatibility with embedders written before the device | |
| // kind and buttons were exposed, if the device kind is not set treat it | |
| // as a mouse, with a synthesized primary button state based on the phase. | |
| if (device_kind == 0) { | |
| pointer_data.kind = flutter::PointerData::DeviceKind::kMouse; | |
| pointer_data.buttons = | |
| PointerDataButtonsForLegacyEvent(pointer_data.change); | |
| } else { | |
| pointer_data.kind = ToPointerDataKind(device_kind); | |
| if (pointer_data.kind == flutter::PointerData::DeviceKind::kTouch) { | |
| // For touch events, set the button internally rather than requiring | |
| // it at the API level, since it's a confusing construction to expose. | |
| if (pointer_data.change == flutter::PointerData::Change::kDown || | |
| pointer_data.change == flutter::PointerData::Change::kMove) { | |
| pointer_data.buttons = flutter::kPointerButtonTouchContact; | |
| } | |
| } else { | |
| // Buttons use the same mask values, so pass them through directly. | |
| pointer_data.buttons = SAFE_ACCESS(current, buttons, 0); | |
| } | |
| } | |
| pointer_data.pan_x = SAFE_ACCESS(current, pan_x, 0.0); | |
| pointer_data.pan_y = SAFE_ACCESS(current, pan_y, 0.0); | |
| // Delta will be generated in pointer_data_packet_converter.cc. | |
| pointer_data.pan_delta_x = 0.0; | |
| pointer_data.pan_delta_y = 0.0; | |
| pointer_data.scale = SAFE_ACCESS(current, scale, 0.0); | |
| pointer_data.rotation = SAFE_ACCESS(current, rotation, 0.0); | |
| pointer_data.view_id = | |
| SAFE_ACCESS(current, view_id, kFlutterImplicitViewId); | |
| packet->SetPointerData(i, pointer_data); | |
| current = reinterpret_cast<const FlutterPointerEvent*>( | |
| reinterpret_cast<const uint8_t*>(current) + current->struct_size); | |
| } | |
| return reinterpret_cast<flutter::EmbedderEngine*>(engine) | |
| ->DispatchPointerDataPacket(std::move(packet)) | |
| ? kSuccess | |
| : LOG_EMBEDDER_ERROR(kInternalInconsistency, | |
| "Could not dispatch pointer events to the " | |
| "running Flutter application."); | |
| } | |
| static inline flutter::KeyEventType MapKeyEventType( | |
| FlutterKeyEventType event_kind) { | |
| switch (event_kind) { | |
| case kFlutterKeyEventTypeUp: | |
| return flutter::KeyEventType::kUp; | |
| case kFlutterKeyEventTypeDown: | |
| return flutter::KeyEventType::kDown; | |
| case kFlutterKeyEventTypeRepeat: | |
| return flutter::KeyEventType::kRepeat; | |
| } | |
| return flutter::KeyEventType::kUp; | |
| } | |
| static inline flutter::KeyEventDeviceType MapKeyEventDeviceType( | |
| FlutterKeyEventDeviceType event_kind) { | |
| switch (event_kind) { | |
| case kFlutterKeyEventDeviceTypeKeyboard: | |
| return flutter::KeyEventDeviceType::kKeyboard; | |
| case kFlutterKeyEventDeviceTypeDirectionalPad: | |
| return flutter::KeyEventDeviceType::kDirectionalPad; | |
| case kFlutterKeyEventDeviceTypeGamepad: | |
| return flutter::KeyEventDeviceType::kGamepad; | |
| case kFlutterKeyEventDeviceTypeJoystick: | |
| return flutter::KeyEventDeviceType::kJoystick; | |
| case kFlutterKeyEventDeviceTypeHdmi: | |
| return flutter::KeyEventDeviceType::kHdmi; | |
| } | |
| return flutter::KeyEventDeviceType::kKeyboard; | |
| } | |
| // Send a platform message to the framework. | |
| // | |
| // The `data_callback` will be invoked with `user_data`, and must not be empty. | |
| static FlutterEngineResult InternalSendPlatformMessage( | |
| FLUTTER_API_SYMBOL(FlutterEngine) engine, | |
| const char* channel, | |
| const uint8_t* data, | |
| size_t size, | |
| FlutterDataCallback data_callback, | |
| void* user_data) { | |
| FlutterEngineResult result; | |
| FlutterPlatformMessageResponseHandle* response_handle; | |
| result = FlutterPlatformMessageCreateResponseHandle( | |
| engine, data_callback, user_data, &response_handle); | |
| if (result != kSuccess) { | |
| return result; | |
| } | |
| const FlutterPlatformMessage message = { | |
| sizeof(FlutterPlatformMessage), // struct_size | |
| channel, // channel | |
| data, // message | |
| size, // message_size | |
| response_handle, // response_handle | |
| }; | |
| result = FlutterEngineSendPlatformMessage(engine, &message); | |
| // Whether `SendPlatformMessage` succeeds or not, the response handle must be | |
| // released. | |
| FlutterEngineResult release_result = | |
| FlutterPlatformMessageReleaseResponseHandle(engine, response_handle); | |
| if (result != kSuccess) { | |
| return result; | |
| } | |
| return release_result; | |
| } | |
| FlutterEngineResult FlutterEngineSendKeyEvent(FLUTTER_API_SYMBOL(FlutterEngine) | |
| engine, | |
| const FlutterKeyEvent* event, | |
| FlutterKeyEventCallback callback, | |
| void* user_data) { | |
| if (engine == nullptr) { | |
| return LOG_EMBEDDER_ERROR(kInvalidArguments, "Engine handle was invalid."); | |
| } | |
| if (event == nullptr) { | |
| return LOG_EMBEDDER_ERROR(kInvalidArguments, "Invalid key event."); | |
| } | |
| const char* character = SAFE_ACCESS(event, character, nullptr); | |
| flutter::KeyData key_data; | |
| key_data.Clear(); | |
| key_data.timestamp = static_cast<uint64_t>(SAFE_ACCESS(event, timestamp, 0)); | |
| key_data.type = MapKeyEventType( | |
| SAFE_ACCESS(event, type, FlutterKeyEventType::kFlutterKeyEventTypeUp)); | |
| key_data.physical = SAFE_ACCESS(event, physical, 0); | |
| key_data.logical = SAFE_ACCESS(event, logical, 0); | |
| key_data.synthesized = SAFE_ACCESS(event, synthesized, false); | |
| key_data.device_type = MapKeyEventDeviceType(SAFE_ACCESS( | |
| event, device_type, | |
| FlutterKeyEventDeviceType::kFlutterKeyEventDeviceTypeKeyboard)); | |
| auto packet = std::make_unique<flutter::KeyDataPacket>(key_data, character); | |
| struct MessageData { | |
| FlutterKeyEventCallback callback; | |
| void* user_data; | |
| }; | |
| MessageData* message_data = | |
| new MessageData{.callback = callback, .user_data = user_data}; | |
| return InternalSendPlatformMessage( | |
| engine, kFlutterKeyDataChannel, packet->data().data(), | |
| packet->data().size(), | |
| [](const uint8_t* data, size_t size, void* user_data) { | |
| auto message_data = std::unique_ptr<MessageData>( | |
| reinterpret_cast<MessageData*>(user_data)); | |
| if (message_data->callback == nullptr) { | |
| return; | |
| } | |
| bool handled = false; | |
| if (size == 1) { | |
| handled = *data != 0; | |
| } | |
| message_data->callback(handled, message_data->user_data); | |
| }, | |
| message_data); | |
| } | |
| FlutterEngineResult FlutterEngineSendPlatformMessage( | |
| FLUTTER_API_SYMBOL(FlutterEngine) engine, | |
| const FlutterPlatformMessage* flutter_message) { | |
| if (engine == nullptr) { | |
| return LOG_EMBEDDER_ERROR(kInvalidArguments, "Invalid engine handle."); | |
| } | |
| if (flutter_message == nullptr) { | |
| return LOG_EMBEDDER_ERROR(kInvalidArguments, "Invalid message argument."); | |
| } | |
| if (SAFE_ACCESS(flutter_message, channel, nullptr) == nullptr) { | |
| return LOG_EMBEDDER_ERROR( | |
| kInvalidArguments, "Message argument did not specify a valid channel."); | |
| } | |
| size_t message_size = SAFE_ACCESS(flutter_message, message_size, 0); | |
| const uint8_t* message_data = SAFE_ACCESS(flutter_message, message, nullptr); | |
| if (message_size != 0 && message_data == nullptr) { | |
| return LOG_EMBEDDER_ERROR( | |
| kInvalidArguments, | |
| "Message size was non-zero but the message data was nullptr."); | |
| } | |
| const FlutterPlatformMessageResponseHandle* response_handle = | |
| SAFE_ACCESS(flutter_message, response_handle, nullptr); | |
| fml::RefPtr<flutter::PlatformMessageResponse> response; | |
| if (response_handle && response_handle->message) { | |
| response = response_handle->message->response(); | |
| } | |
| std::unique_ptr<flutter::PlatformMessage> message; | |
| if (message_size == 0) { | |
| message = std::make_unique<flutter::PlatformMessage>( | |
| flutter_message->channel, response); | |
| } else { | |
| message = std::make_unique<flutter::PlatformMessage>( | |
| flutter_message->channel, | |
| fml::MallocMapping::Copy(message_data, message_size), response); | |
| } | |
| return reinterpret_cast<flutter::EmbedderEngine*>(engine) | |
| ->SendPlatformMessage(std::move(message)) | |
| ? kSuccess | |
| : LOG_EMBEDDER_ERROR(kInternalInconsistency, | |
| "Could not send a message to the running " | |
| "Flutter application."); | |
| } | |
| FlutterEngineResult FlutterPlatformMessageCreateResponseHandle( | |
| FLUTTER_API_SYMBOL(FlutterEngine) engine, | |
| FlutterDataCallback data_callback, | |
| void* user_data, | |
| FlutterPlatformMessageResponseHandle** response_out) { | |
| if (engine == nullptr) { | |
| return LOG_EMBEDDER_ERROR(kInvalidArguments, "Engine handle was invalid."); | |
| } | |
| if (data_callback == nullptr || response_out == nullptr) { | |
| return LOG_EMBEDDER_ERROR( | |
| kInvalidArguments, "Data callback or the response handle was invalid."); | |
| } | |
| flutter::EmbedderPlatformMessageResponse::Callback response_callback = | |
| [user_data, data_callback](const uint8_t* data, size_t size) { | |
| data_callback(data, size, user_data); | |
| }; | |
| auto platform_task_runner = reinterpret_cast<flutter::EmbedderEngine*>(engine) | |
| ->GetTaskRunners() | |
| .GetPlatformTaskRunner(); | |
| auto handle = new FlutterPlatformMessageResponseHandle(); | |
| handle->message = std::make_unique<flutter::PlatformMessage>( | |
| "", // The channel is empty and unused as the response handle is going | |
| // to referenced directly in the |FlutterEngineSendPlatformMessage| | |
| // with the container message discarded. | |
| fml::MakeRefCounted<flutter::EmbedderPlatformMessageResponse>( | |
| std::move(platform_task_runner), response_callback)); | |
| *response_out = handle; | |
| return kSuccess; | |
| } | |
| FlutterEngineResult FlutterPlatformMessageReleaseResponseHandle( | |
| FLUTTER_API_SYMBOL(FlutterEngine) engine, | |
| FlutterPlatformMessageResponseHandle* response) { | |
| if (engine == nullptr) { | |
| return LOG_EMBEDDER_ERROR(kInvalidArguments, "Invalid engine handle."); | |
| } | |
| if (response == nullptr) { | |
| return LOG_EMBEDDER_ERROR(kInvalidArguments, "Invalid response handle."); | |
| } | |
| delete response; | |
| return kSuccess; | |
| } | |
| // Note: This can execute on any thread. | |
| FlutterEngineResult FlutterEngineSendPlatformMessageResponse( | |
| FLUTTER_API_SYMBOL(FlutterEngine) engine, | |
| const FlutterPlatformMessageResponseHandle* handle, | |
| const uint8_t* data, | |
| size_t data_length) { | |
| if (data_length != 0 && data == nullptr) { | |
| return LOG_EMBEDDER_ERROR( | |
| kInvalidArguments, | |
| "Data size was non zero but the pointer to the data was null."); | |
| } | |
| auto response = handle->message->response(); | |
| if (response) { | |
| if (data_length == 0) { | |
| response->CompleteEmpty(); | |
| } else { | |
| response->Complete(std::make_unique<fml::DataMapping>( | |
| std::vector<uint8_t>({data, data + data_length}))); | |
| } | |
| } | |
| delete handle; | |
| return kSuccess; | |
| } | |
| FlutterEngineResult __FlutterEngineFlushPendingTasksNow() { | |
| fml::MessageLoop::GetCurrent().RunExpiredTasksNow(); | |
| return kSuccess; | |
| } | |
| FlutterEngineResult FlutterEngineRegisterExternalTexture( | |
| FLUTTER_API_SYMBOL(FlutterEngine) engine, | |
| int64_t texture_identifier) { | |
| if (engine == nullptr) { | |
| return LOG_EMBEDDER_ERROR(kInvalidArguments, "Engine handle was invalid."); | |
| } | |
| if (texture_identifier == 0) { | |
| return LOG_EMBEDDER_ERROR(kInvalidArguments, | |
| "Texture identifier was invalid."); | |
| } | |
| if (!reinterpret_cast<flutter::EmbedderEngine*>(engine)->RegisterTexture( | |
| texture_identifier)) { | |
| return LOG_EMBEDDER_ERROR(kInternalInconsistency, | |
| "Could not register the specified texture."); | |
| } | |
| return kSuccess; | |
| } | |
| FlutterEngineResult FlutterEngineUnregisterExternalTexture( | |
| FLUTTER_API_SYMBOL(FlutterEngine) engine, | |
| int64_t texture_identifier) { | |
| if (engine == nullptr) { | |
| return LOG_EMBEDDER_ERROR(kInvalidArguments, "Engine handle was invalid."); | |
| } | |
| if (texture_identifier == 0) { | |
| return LOG_EMBEDDER_ERROR(kInvalidArguments, | |
| "Texture identifier was invalid."); | |
| } | |
| if (!reinterpret_cast<flutter::EmbedderEngine*>(engine)->UnregisterTexture( | |
| texture_identifier)) { | |
| return LOG_EMBEDDER_ERROR(kInternalInconsistency, | |
| "Could not un-register the specified texture."); | |
| } | |
| return kSuccess; | |
| } | |
| FlutterEngineResult FlutterEngineMarkExternalTextureFrameAvailable( | |
| FLUTTER_API_SYMBOL(FlutterEngine) engine, | |
| int64_t texture_identifier) { | |
| if (engine == nullptr) { | |
| return LOG_EMBEDDER_ERROR(kInvalidArguments, "Invalid engine handle."); | |
| } | |
| if (texture_identifier == 0) { | |
| return LOG_EMBEDDER_ERROR(kInvalidArguments, "Invalid texture identifier."); | |
| } | |
| if (!reinterpret_cast<flutter::EmbedderEngine*>(engine) | |
| ->MarkTextureFrameAvailable(texture_identifier)) { | |
| return LOG_EMBEDDER_ERROR( | |
| kInternalInconsistency, | |
| "Could not mark the texture frame as being available."); | |
| } | |
| return kSuccess; | |
| } | |
| FlutterEngineResult FlutterEngineUpdateSemanticsEnabled( | |
| FLUTTER_API_SYMBOL(FlutterEngine) engine, | |
| bool enabled) { | |
| if (engine == nullptr) { | |
| return LOG_EMBEDDER_ERROR(kInvalidArguments, "Invalid engine handle."); | |
| } | |
| if (!reinterpret_cast<flutter::EmbedderEngine*>(engine)->SetSemanticsEnabled( | |
| enabled)) { | |
| return LOG_EMBEDDER_ERROR(kInternalInconsistency, | |
| "Could not update semantics state."); | |
| } | |
| return kSuccess; | |
| } | |
| FlutterEngineResult FlutterEngineUpdateAccessibilityFeatures( | |
| FLUTTER_API_SYMBOL(FlutterEngine) engine, | |
| FlutterAccessibilityFeature flags) { | |
| if (engine == nullptr) { | |
| return LOG_EMBEDDER_ERROR(kInvalidArguments, "Invalid engine handle."); | |
| } | |
| if (!reinterpret_cast<flutter::EmbedderEngine*>(engine) | |
| ->SetAccessibilityFeatures(flags)) { | |
| return LOG_EMBEDDER_ERROR(kInternalInconsistency, | |
| "Could not update accessibility features."); | |
| } | |
| return kSuccess; | |
| } | |
| FlutterEngineResult FlutterEngineDispatchSemanticsAction( | |
| FLUTTER_API_SYMBOL(FlutterEngine) engine, | |
| uint64_t node_id, | |
| FlutterSemanticsAction action, | |
| const uint8_t* data, | |
| size_t data_length) { | |
| if (engine == nullptr) { | |
| return LOG_EMBEDDER_ERROR(kInvalidArguments, "Invalid engine handle."); | |
| } | |
| auto engine_action = static_cast<flutter::SemanticsAction>(action); | |
| if (!reinterpret_cast<flutter::EmbedderEngine*>(engine) | |
| ->DispatchSemanticsAction( | |
| node_id, engine_action, | |
| fml::MallocMapping::Copy(data, data_length))) { | |
| return LOG_EMBEDDER_ERROR(kInternalInconsistency, | |
| "Could not dispatch semantics action."); | |
| } | |
| return kSuccess; | |
| } | |
| FlutterEngineResult FlutterEngineOnVsync(FLUTTER_API_SYMBOL(FlutterEngine) | |
| engine, | |
| intptr_t baton, | |
| uint64_t frame_start_time_nanos, | |
| uint64_t frame_target_time_nanos) { | |
| if (engine == nullptr) { | |
| return LOG_EMBEDDER_ERROR(kInvalidArguments, "Invalid engine handle."); | |
| } | |
| TRACE_EVENT0("flutter", "FlutterEngineOnVsync"); | |
| auto start_time = fml::TimePoint::FromEpochDelta( | |
| fml::TimeDelta::FromNanoseconds(frame_start_time_nanos)); | |
| auto target_time = fml::TimePoint::FromEpochDelta( | |
| fml::TimeDelta::FromNanoseconds(frame_target_time_nanos)); | |
| if (!reinterpret_cast<flutter::EmbedderEngine*>(engine)->OnVsyncEvent( | |
| baton, start_time, target_time)) { | |
| return LOG_EMBEDDER_ERROR( | |
| kInternalInconsistency, | |
| "Could not notify the running engine instance of a Vsync event."); | |
| } | |
| return kSuccess; | |
| } | |
| FlutterEngineResult FlutterEngineReloadSystemFonts( | |
| FLUTTER_API_SYMBOL(FlutterEngine) engine) { | |
| if (engine == nullptr) { | |
| return LOG_EMBEDDER_ERROR(kInvalidArguments, "Invalid engine handle."); | |
| } | |
| TRACE_EVENT0("flutter", "FlutterEngineReloadSystemFonts"); | |
| if (!reinterpret_cast<flutter::EmbedderEngine*>(engine) | |
| ->ReloadSystemFonts()) { | |
| return LOG_EMBEDDER_ERROR(kInternalInconsistency, | |
| "Could not reload system fonts."); | |
| } | |
| return kSuccess; | |
| } | |
| void FlutterEngineTraceEventDurationBegin(const char* name) { | |
| fml::tracing::TraceEvent0("flutter", name, /*flow_id_count=*/0, | |
| /*flow_ids=*/nullptr); | |
| } | |
| void FlutterEngineTraceEventDurationEnd(const char* name) { | |
| fml::tracing::TraceEventEnd(name); | |
| } | |
| void FlutterEngineTraceEventInstant(const char* name) { | |
| fml::tracing::TraceEventInstant0("flutter", name, /*flow_id_count=*/0, | |
| /*flow_ids=*/nullptr); | |
| } | |
| FlutterEngineResult FlutterEnginePostRenderThreadTask( | |
| FLUTTER_API_SYMBOL(FlutterEngine) engine, | |
| VoidCallback callback, | |
| void* baton) { | |
| if (engine == nullptr) { | |
| return LOG_EMBEDDER_ERROR(kInvalidArguments, "Invalid engine handle."); | |
| } | |
| if (callback == nullptr) { | |
| return LOG_EMBEDDER_ERROR(kInvalidArguments, | |
| "Render thread callback was null."); | |
| } | |
| auto task = [callback, baton]() { callback(baton); }; | |
| return reinterpret_cast<flutter::EmbedderEngine*>(engine) | |
| ->PostRenderThreadTask(task) | |
| ? kSuccess | |
| : LOG_EMBEDDER_ERROR(kInternalInconsistency, | |
| "Could not post the render thread task."); | |
| } | |
| uint64_t FlutterEngineGetCurrentTime() { | |
| return fml::TimePoint::Now().ToEpochDelta().ToNanoseconds(); | |
| } | |
| FlutterEngineResult FlutterEngineRunTask(FLUTTER_API_SYMBOL(FlutterEngine) | |
| engine, | |
| const FlutterTask* task) { | |
| if (engine == nullptr) { | |
| return LOG_EMBEDDER_ERROR(kInvalidArguments, "Invalid engine handle."); | |
| } | |
| return reinterpret_cast<flutter::EmbedderEngine*>(engine)->RunTask(task) | |
| ? kSuccess | |
| : LOG_EMBEDDER_ERROR(kInvalidArguments, | |
| "Could not run the specified task."); | |
| } | |
| static bool DispatchJSONPlatformMessage(FLUTTER_API_SYMBOL(FlutterEngine) | |
| engine, | |
| const rapidjson::Document& document, | |
| const std::string& channel_name) { | |
| if (channel_name.empty()) { | |
| return false; | |
| } | |
| rapidjson::StringBuffer buffer; | |
| rapidjson::Writer<rapidjson::StringBuffer> writer(buffer); | |
| if (!document.Accept(writer)) { | |
| return false; | |
| } | |
| const char* message = buffer.GetString(); | |
| if (message == nullptr || buffer.GetSize() == 0) { | |
| return false; | |
| } | |
| auto platform_message = std::make_unique<flutter::PlatformMessage>( | |
| channel_name.c_str(), // channel | |
| fml::MallocMapping::Copy(message, | |
| buffer.GetSize()), // message | |
| nullptr // response | |
| ); | |
| return reinterpret_cast<flutter::EmbedderEngine*>(engine) | |
| ->SendPlatformMessage(std::move(platform_message)); | |
| } | |
| FlutterEngineResult FlutterEngineUpdateLocales(FLUTTER_API_SYMBOL(FlutterEngine) | |
| engine, | |
| const FlutterLocale** locales, | |
| size_t locales_count) { | |
| if (engine == nullptr) { | |
| return LOG_EMBEDDER_ERROR(kInvalidArguments, "Invalid engine handle."); | |
| } | |
| if (locales_count == 0) { | |
| return kSuccess; | |
| } | |
| if (locales == nullptr) { | |
| return LOG_EMBEDDER_ERROR(kInvalidArguments, "No locales were specified."); | |
| } | |
| rapidjson::Document document; | |
| auto& allocator = document.GetAllocator(); | |
| document.SetObject(); | |
| document.AddMember("method", "setLocale", allocator); | |
| rapidjson::Value args(rapidjson::kArrayType); | |
| args.Reserve(locales_count * 4, allocator); | |
| for (size_t i = 0; i < locales_count; ++i) { | |
| const FlutterLocale* locale = locales[i]; | |
| const char* language_code_str = SAFE_ACCESS(locale, language_code, nullptr); | |
| if (language_code_str == nullptr || ::strlen(language_code_str) == 0) { | |
| return LOG_EMBEDDER_ERROR( | |
| kInvalidArguments, | |
| "Language code is required but not present in FlutterLocale."); | |
| } | |
| const char* country_code_str = SAFE_ACCESS(locale, country_code, ""); | |
| const char* script_code_str = SAFE_ACCESS(locale, script_code, ""); | |
| const char* variant_code_str = SAFE_ACCESS(locale, variant_code, ""); | |
| rapidjson::Value language_code, country_code, script_code, variant_code; | |
| language_code.SetString(language_code_str, allocator); | |
| country_code.SetString(country_code_str ? country_code_str : "", allocator); | |
| script_code.SetString(script_code_str ? script_code_str : "", allocator); | |
| variant_code.SetString(variant_code_str ? variant_code_str : "", allocator); | |
| // Required. | |
| args.PushBack(language_code, allocator); | |
| args.PushBack(country_code, allocator); | |
| args.PushBack(script_code, allocator); | |
| args.PushBack(variant_code, allocator); | |
| } | |
| document.AddMember("args", args, allocator); | |
| return DispatchJSONPlatformMessage(engine, document, "flutter/localization") | |
| ? kSuccess | |
| : LOG_EMBEDDER_ERROR(kInternalInconsistency, | |
| "Could not send message to update locale of " | |
| "a running Flutter application."); | |
| } | |
| bool FlutterEngineRunsAOTCompiledDartCode(void) { | |
| return flutter::DartVM::IsRunningPrecompiledCode(); | |
| } | |
| FlutterEngineResult FlutterEnginePostDartObject( | |
| FLUTTER_API_SYMBOL(FlutterEngine) engine, | |
| FlutterEngineDartPort port, | |
| const FlutterEngineDartObject* object) { | |
| if (engine == nullptr) { | |
| return LOG_EMBEDDER_ERROR(kInvalidArguments, "Invalid engine handle."); | |
| } | |
| if (!reinterpret_cast<flutter::EmbedderEngine*>(engine)->IsValid()) { | |
| return LOG_EMBEDDER_ERROR(kInvalidArguments, "Engine not running."); | |
| } | |
| if (port == ILLEGAL_PORT) { | |
| return LOG_EMBEDDER_ERROR(kInvalidArguments, | |
| "Attempted to post to an illegal port."); | |
| } | |
| if (object == nullptr) { | |
| return LOG_EMBEDDER_ERROR(kInvalidArguments, | |
| "Invalid Dart object to post."); | |
| } | |
| Dart_CObject dart_object = {}; | |
| fml::ScopedCleanupClosure typed_data_finalizer; | |
| switch (object->type) { | |
| case kFlutterEngineDartObjectTypeNull: | |
| dart_object.type = Dart_CObject_kNull; | |
| break; | |
| case kFlutterEngineDartObjectTypeBool: | |
| dart_object.type = Dart_CObject_kBool; | |
| dart_object.value.as_bool = object->bool_value; | |
| break; | |
| case kFlutterEngineDartObjectTypeInt32: | |
| dart_object.type = Dart_CObject_kInt32; | |
| dart_object.value.as_int32 = object->int32_value; | |
| break; | |
| case kFlutterEngineDartObjectTypeInt64: | |
| dart_object.type = Dart_CObject_kInt64; | |
| dart_object.value.as_int64 = object->int64_value; | |
| break; | |
| case kFlutterEngineDartObjectTypeDouble: | |
| dart_object.type = Dart_CObject_kDouble; | |
| dart_object.value.as_double = object->double_value; | |
| break; | |
| case kFlutterEngineDartObjectTypeString: | |
| if (object->string_value == nullptr) { | |
| return LOG_EMBEDDER_ERROR(kInvalidArguments, | |
| "kFlutterEngineDartObjectTypeString must be " | |
| "a null terminated string but was null."); | |
| } | |
| dart_object.type = Dart_CObject_kString; | |
| dart_object.value.as_string = const_cast<char*>(object->string_value); | |
| break; | |
| case kFlutterEngineDartObjectTypeBuffer: { | |
| auto* buffer = SAFE_ACCESS(object->buffer_value, buffer, nullptr); | |
| if (buffer == nullptr) { | |
| return LOG_EMBEDDER_ERROR(kInvalidArguments, | |
| "kFlutterEngineDartObjectTypeBuffer must " | |
| "specify a buffer but found nullptr."); | |
| } | |
| auto buffer_size = SAFE_ACCESS(object->buffer_value, buffer_size, 0); | |
| auto callback = | |
| SAFE_ACCESS(object->buffer_value, buffer_collect_callback, nullptr); | |
| auto user_data = SAFE_ACCESS(object->buffer_value, user_data, nullptr); | |
| // The user has provided a callback, let them manage the lifecycle of | |
| // the underlying data. If not, copy it out from the provided buffer. | |
| if (callback == nullptr) { | |
| dart_object.type = Dart_CObject_kTypedData; | |
| dart_object.value.as_typed_data.type = Dart_TypedData_kUint8; | |
| dart_object.value.as_typed_data.length = buffer_size; | |
| dart_object.value.as_typed_data.values = buffer; | |
| } else { | |
| struct ExternalTypedDataPeer { | |
| void* user_data = nullptr; | |
| VoidCallback trampoline = nullptr; | |
| }; | |
| auto peer = new ExternalTypedDataPeer(); | |
| peer->user_data = user_data; | |
| peer->trampoline = callback; | |
| // This finalizer is set so that in case of failure of the | |
| // Dart_PostCObject below, we collect the peer. The embedder is still | |
| // responsible for collecting the buffer in case of non-kSuccess | |
| // returns from this method. This finalizer must be released in case | |
| // of kSuccess returns from this method. | |
| typed_data_finalizer.SetClosure([peer]() { | |
| // This is the tiny object we use as the peer to the Dart call so | |
| // that we can attach the a trampoline to the embedder supplied | |
| // callback. In case of error, we need to collect this object lest | |
| // we introduce a tiny leak. | |
| delete peer; | |
| }); | |
| dart_object.type = Dart_CObject_kExternalTypedData; | |
| dart_object.value.as_external_typed_data.type = Dart_TypedData_kUint8; | |
| dart_object.value.as_external_typed_data.length = buffer_size; | |
| dart_object.value.as_external_typed_data.data = buffer; | |
| dart_object.value.as_external_typed_data.peer = peer; | |
| dart_object.value.as_external_typed_data.callback = | |
| +[](void* unused_isolate_callback_data, void* peer) { | |
| auto typed_peer = reinterpret_cast<ExternalTypedDataPeer*>(peer); | |
| typed_peer->trampoline(typed_peer->user_data); | |
| delete typed_peer; | |
| }; | |
| } | |
| } break; | |
| default: | |
| return LOG_EMBEDDER_ERROR( | |
| kInvalidArguments, | |
| "Invalid FlutterEngineDartObjectType type specified."); | |
| } | |
| if (!Dart_PostCObject(port, &dart_object)) { | |
| return LOG_EMBEDDER_ERROR(kInternalInconsistency, | |
| "Could not post the object to the Dart VM."); | |
| } | |
| // On a successful call, the VM takes ownership of and is responsible for | |
| // invoking the finalizer. | |
| typed_data_finalizer.Release(); | |
| return kSuccess; | |
| } | |
| FlutterEngineResult FlutterEngineNotifyLowMemoryWarning( | |
| FLUTTER_API_SYMBOL(FlutterEngine) raw_engine) { | |
| auto engine = reinterpret_cast<flutter::EmbedderEngine*>(raw_engine); | |
| if (engine == nullptr || !engine->IsValid()) { | |
| return LOG_EMBEDDER_ERROR(kInvalidArguments, "Engine was invalid."); | |
| } | |
| engine->GetShell().NotifyLowMemoryWarning(); | |
| rapidjson::Document document; | |
| auto& allocator = document.GetAllocator(); | |
| document.SetObject(); | |
| document.AddMember("type", "memoryPressure", allocator); | |
| return DispatchJSONPlatformMessage(raw_engine, document, "flutter/system") | |
| ? kSuccess | |
| : LOG_EMBEDDER_ERROR( | |
| kInternalInconsistency, | |
| "Could not dispatch the low memory notification message."); | |
| } | |
| FlutterEngineResult FlutterEnginePostCallbackOnAllNativeThreads( | |
| FLUTTER_API_SYMBOL(FlutterEngine) engine, | |
| FlutterNativeThreadCallback callback, | |
| void* user_data) { | |
| if (engine == nullptr) { | |
| return LOG_EMBEDDER_ERROR(kInvalidArguments, "Invalid engine handle."); | |
| } | |
| if (callback == nullptr) { | |
| return LOG_EMBEDDER_ERROR(kInvalidArguments, | |
| "Invalid native thread callback."); | |
| } | |
| return reinterpret_cast<flutter::EmbedderEngine*>(engine) | |
| ->PostTaskOnEngineManagedNativeThreads( | |
| [callback, user_data](FlutterNativeThreadType type) { | |
| callback(type, user_data); | |
| }) | |
| ? kSuccess | |
| : LOG_EMBEDDER_ERROR(kInvalidArguments, | |
| "Internal error while attempting to post " | |
| "tasks to all threads."); | |
| } | |
| namespace { | |
| static bool ValidDisplayConfiguration(const FlutterEngineDisplay* displays, | |
| size_t display_count) { | |
| std::set<FlutterEngineDisplayId> display_ids; | |
| for (size_t i = 0; i < display_count; i++) { | |
| if (displays[i].single_display && display_count != 1) { | |
| return false; | |
| } | |
| display_ids.insert(displays[i].display_id); | |
| } | |
| return display_ids.size() == display_count; | |
| } | |
| } // namespace | |
| FlutterEngineResult FlutterEngineNotifyDisplayUpdate( | |
| FLUTTER_API_SYMBOL(FlutterEngine) raw_engine, | |
| const FlutterEngineDisplaysUpdateType update_type, | |
| const FlutterEngineDisplay* embedder_displays, | |
| size_t display_count) { | |
| if (raw_engine == nullptr) { | |
| return LOG_EMBEDDER_ERROR(kInvalidArguments, "Invalid engine handle."); | |
| } | |
| if (!ValidDisplayConfiguration(embedder_displays, display_count)) { | |
| return LOG_EMBEDDER_ERROR( | |
| kInvalidArguments, | |
| "Invalid FlutterEngineDisplay configuration specified."); | |
| } | |
| auto engine = reinterpret_cast<flutter::EmbedderEngine*>(raw_engine); | |
| switch (update_type) { | |
| case kFlutterEngineDisplaysUpdateTypeStartup: { | |
| std::vector<std::unique_ptr<flutter::Display>> displays; | |
| const auto* display = embedder_displays; | |
| for (size_t i = 0; i < display_count; i++) { | |
| displays.push_back(std::make_unique<flutter::Display>( | |
| SAFE_ACCESS(display, display_id, i), // | |
| SAFE_ACCESS(display, refresh_rate, 0), // | |
| SAFE_ACCESS(display, width, 0), // | |
| SAFE_ACCESS(display, height, 0), // | |
| SAFE_ACCESS(display, device_pixel_ratio, 1))); | |
| display = reinterpret_cast<const FlutterEngineDisplay*>( | |
| reinterpret_cast<const uint8_t*>(display) + display->struct_size); | |
| } | |
| engine->GetShell().OnDisplayUpdates(std::move(displays)); | |
| return kSuccess; | |
| } | |
| default: | |
| return LOG_EMBEDDER_ERROR( | |
| kInvalidArguments, | |
| "Invalid FlutterEngineDisplaysUpdateType type specified."); | |
| } | |
| } | |
| FlutterEngineResult FlutterEngineScheduleFrame(FLUTTER_API_SYMBOL(FlutterEngine) | |
| engine) { | |
| if (engine == nullptr) { | |
| return LOG_EMBEDDER_ERROR(kInvalidArguments, "Invalid engine handle."); | |
| } | |
| return reinterpret_cast<flutter::EmbedderEngine*>(engine)->ScheduleFrame() | |
| ? kSuccess | |
| : LOG_EMBEDDER_ERROR(kInvalidArguments, | |
| "Could not schedule frame."); | |
| } | |
| FlutterEngineResult FlutterEngineSetNextFrameCallback( | |
| FLUTTER_API_SYMBOL(FlutterEngine) engine, | |
| VoidCallback callback, | |
| void* user_data) { | |
| if (engine == nullptr) { | |
| return LOG_EMBEDDER_ERROR(kInvalidArguments, "Invalid engine handle."); | |
| } | |
| if (callback == nullptr) { | |
| return LOG_EMBEDDER_ERROR(kInvalidArguments, | |
| "Next frame callback was null."); | |
| } | |
| flutter::EmbedderEngine* embedder_engine = | |
| reinterpret_cast<flutter::EmbedderEngine*>(engine); | |
| fml::WeakPtr<flutter::PlatformView> weak_platform_view = | |
| embedder_engine->GetShell().GetPlatformView(); | |
| if (!weak_platform_view) { | |
| return LOG_EMBEDDER_ERROR(kInternalInconsistency, | |
| "Platform view unavailable."); | |
| } | |
| weak_platform_view->SetNextFrameCallback( | |
| [callback, user_data]() { callback(user_data); }); | |
| return kSuccess; | |
| } | |
| FlutterEngineResult FlutterEngineGetProcAddresses( | |
| FlutterEngineProcTable* table) { | |
| if (!table) { | |
| return LOG_EMBEDDER_ERROR(kInvalidArguments, "Null table specified."); | |
| } | |
| #define SET_PROC(member, function) \ | |
| if (STRUCT_HAS_MEMBER(table, member)) { \ | |
| table->member = &function; \ | |
| } | |
| SET_PROC(CreateAOTData, FlutterEngineCreateAOTData); | |
| SET_PROC(CollectAOTData, FlutterEngineCollectAOTData); | |
| SET_PROC(Run, FlutterEngineRun); | |
| SET_PROC(Shutdown, FlutterEngineShutdown); | |
| SET_PROC(Initialize, FlutterEngineInitialize); | |
| SET_PROC(Deinitialize, FlutterEngineDeinitialize); | |
| SET_PROC(RunInitialized, FlutterEngineRunInitialized); | |
| SET_PROC(SendWindowMetricsEvent, FlutterEngineSendWindowMetricsEvent); | |
| SET_PROC(SendPointerEvent, FlutterEngineSendPointerEvent); | |
| SET_PROC(SendKeyEvent, FlutterEngineSendKeyEvent); | |
| SET_PROC(SendPlatformMessage, FlutterEngineSendPlatformMessage); | |
| SET_PROC(PlatformMessageCreateResponseHandle, | |
| FlutterPlatformMessageCreateResponseHandle); | |
| SET_PROC(PlatformMessageReleaseResponseHandle, | |
| FlutterPlatformMessageReleaseResponseHandle); | |
| SET_PROC(SendPlatformMessageResponse, | |
| FlutterEngineSendPlatformMessageResponse); | |
| SET_PROC(RegisterExternalTexture, FlutterEngineRegisterExternalTexture); | |
| SET_PROC(UnregisterExternalTexture, FlutterEngineUnregisterExternalTexture); | |
| SET_PROC(MarkExternalTextureFrameAvailable, | |
| FlutterEngineMarkExternalTextureFrameAvailable); | |
| SET_PROC(UpdateSemanticsEnabled, FlutterEngineUpdateSemanticsEnabled); | |
| SET_PROC(UpdateAccessibilityFeatures, | |
| FlutterEngineUpdateAccessibilityFeatures); | |
| SET_PROC(DispatchSemanticsAction, FlutterEngineDispatchSemanticsAction); | |
| SET_PROC(OnVsync, FlutterEngineOnVsync); | |
| SET_PROC(ReloadSystemFonts, FlutterEngineReloadSystemFonts); | |
| SET_PROC(TraceEventDurationBegin, FlutterEngineTraceEventDurationBegin); | |
| SET_PROC(TraceEventDurationEnd, FlutterEngineTraceEventDurationEnd); | |
| SET_PROC(TraceEventInstant, FlutterEngineTraceEventInstant); | |
| SET_PROC(PostRenderThreadTask, FlutterEnginePostRenderThreadTask); | |
| SET_PROC(GetCurrentTime, FlutterEngineGetCurrentTime); | |
| SET_PROC(RunTask, FlutterEngineRunTask); | |
| SET_PROC(UpdateLocales, FlutterEngineUpdateLocales); | |
| SET_PROC(RunsAOTCompiledDartCode, FlutterEngineRunsAOTCompiledDartCode); | |
| SET_PROC(PostDartObject, FlutterEnginePostDartObject); | |
| SET_PROC(NotifyLowMemoryWarning, FlutterEngineNotifyLowMemoryWarning); | |
| SET_PROC(PostCallbackOnAllNativeThreads, | |
| FlutterEnginePostCallbackOnAllNativeThreads); | |
| SET_PROC(NotifyDisplayUpdate, FlutterEngineNotifyDisplayUpdate); | |
| SET_PROC(ScheduleFrame, FlutterEngineScheduleFrame); | |
| SET_PROC(SetNextFrameCallback, FlutterEngineSetNextFrameCallback); | |
| SET_PROC(AddView, FlutterEngineAddView); | |
| SET_PROC(RemoveView, FlutterEngineRemoveView); | |
| #undef SET_PROC | |
| return kSuccess; | |
| } |