ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

从零构建自定义 Flutter Engine Embedder:以 GLFW 为例的跨平台嵌入式集成实战

从零构建自定义 Flutter Engine Embedder:以 GLFW 为例的跨平台嵌入式集成实战 跨平台图形学前端【免费下载链接】engineThe Flutter engine项目地址https://gitcode.com/gh_mirrors/eng/engine点击查看免费下载导读Flutter Engine 在设计上对窗口工具包window toolkit完全无感知——它只关心如何在一个由宿主环境提供的渲染表面OpenGL、Metal、Vulkan 或纯软件缓冲上绘制每一帧至于窗口如何创建、事件如何采集、画面如何呈现全部交由宿主应用即 embedder嵌入器负责。本指南基于 Flutter Engine 官方文档 Custom-Flutter-Engine-Embedders完整讲解如何在 iOS、Android 之外的平台上自行编写嵌入器从获取引擎动态库、理解单一头文件embedder.h的稳定 ABI到剖析仓库自带的 GLFW 示例 并跑通整个构建 运行流程。读完本文你将掌握引擎初始化、渲染回调注册、指针与窗口事件注入、多后端渲染配置等核心能力并清楚了解官方对自定义嵌入方案的边界立场。注意这是一套非常底层的 API官方明确表示不适合初学者。它面向的是需要在嵌入式硬件、定制桌面环境等未开箱支持平台上运行 Flutter 的团队。1. 理解 Embedder 的定位引擎与窗口工具包之间的桥梁Flutter 的分层架构决定了引擎层只负责 Dart 运行时、帧管线flow与渲染后端而把平台相关的窗口管理、输入事件、渲染表面生命周期全部抽象出来交给宿主。这一抽象正是shell/platform/embedder目录下全部代码的使命。从构建系统看这套跨平台组件以动态库形式暴露对应的 GN 目标是flutter_engine即 shell/platform/embedder/BUILD.gn 中的group(flutter_engine)。该目标要求作为host GN 构建的一部分来编译——桌面 Linux 与 Mac 已内置对应的 host 工具链可直接构建若目标是其他平台如嵌入式 ARM 板卡则需要自行配置一套对应的 GN 工具链。在仓库中嵌入层源码主体位于 shell/platform/embedder其内部实现包括模块职责embedder.cc/embedder_engine.cc对外 API 的 C 语言实现与引擎实例管理embedder_surface_gl_skia.cc/embedder_surface_gl_impeller.ccOpenGL 渲染表面Skia 与 Impeller 两条路径embedder_surface_metal_*.mmmacOS/iOS 上的 Metal 渲染表面embedder_surface_vulkan*.ccVulkan 渲染表面embedder_surface_software.cc纯软件渲染表面embedder_external_texture_*.cc外部纹理视频解码等接入vsync_waiter_embedder.cc垂直同步信号等待器embedder_task_runner.cc引擎任务在宿主线程上的调度platform_view_embedder.cc平台视图PlatformView与自定义合成器支持从源码结构可以推断嵌入器 API 覆盖了渲染、事件、平台消息、语义树无障碍、自定义合成器、外部纹理、任务调度等多个维度宿主只需要实现其中与自己平台相关的一部分。2. 获取引擎动态库构建或下载二选一你既可以选择自行编译flutter_engine目标也可以直接下载 CIbuildbot在每次提交时自动上传的现成产物。2.1 下载 CI 预构建产物各平台构建机会把产物上传到flutter_infra_release存储桶下的固定位置路径中的FLUTTER_ENGINE需替换为你要使用的引擎提交 SHAMachttps://storage.googleapis.com/flutter_infra_release/flutter/FLUTTER_ENGINE/darwin-x64/FlutterEmbedder.framework.zip打包为.framework内含动态库、头文件与icudtl.dat。Linuxhttps://storage.googleapis.com/flutter_infra_release/flutter/FLUTTER_ENGINE/linux-x64/linux-x64-embedder。官方文档特别提醒该二进制未剥离符号、包含调试信息嵌入器在部署前应自行 strip 二进制以减小体积。Windowshttps://storage.googleapis.com/flutter_infra_release/flutter/FLUTTER_ENGINE/windows-x64/windows-x64-embedder.zip。2.2 获取与 Flutter Framework 精确匹配的引擎 SHA如果你使用的是官方 Flutter SDK可以从 Flutter framework 检出中的bin/internal/engine.version文件读取当前框架所对应的引擎提交 SHA用该 SHA 替换上述 URL 中的FLUTTER_ENGINE即可让引擎版本与框架版本严格一致。这与仓库中 DEPS 文件锁定的引擎版本机制是同一思路。2.3 自行构建 host 目标在仓库根目录配置 GN 后详见 docs/contributing/Compiling-the-engine.md 与 docs/contributing/Setting-up-the-Engine-development-environment.mdhost 构建会产出动态库与头文件。从 BUILD.gn 可见其打包细节核心动态库目标为flutter_engine_library输出名flutter_engineLinux 下为libflutter_engine.soWindows 下为flutter_engine.dll与导入库flutter_engine.dll.lib头文件会被复制为flutter_embedder.h对应copy_headers目标见 BUILD.gnMac 上还会组装成规范的FlutterEmbedder.framework含Versions/A/Headers/FlutterEmbedder.h、Resources/icudtl.dat、Info.plist与 modulemap并进一步打成FlutterEmbedder.framework.zip归档Linux/Windows 则由embedder-archive目标打成一个包含头文件与动态库的 zip 包命名形如平台-embedder.zip。其中icudtl.dat是 ICU 国际化数据文件GLFW 示例运行时也需要显式指定它的路径见下文 run.sh。3. 单一 C 头文件稳定 ABI 的完整契约嵌入器 API 没有任何平台相关依赖且具有稳定 ABI整个 API 面集中在一个 C 头文件中shell/platform/embedder/embedder.h。文件开头的注释详尽说明了 ABI 稳定性规则是编写与升级嵌入器时必须遵守的契约结构体成员的顺序、类型、尺寸不得改变成员不得删除所有新加入 ABI 的结构体必须以size_t struct_size;作为第一个成员并通过sizeof(Type)初始化——这是引擎校验结构版本、保证前后向兼容的核心机制枚举值不得变更或删除无显式值的枚举成员不得重排函数签名名称、参数个数、顺序与类型不得改变既有函数的核心行为不得改变结构体嵌套优先使用指针而非值嵌套数组优先使用指向结构的指针数组以便后续向结构体追加成员时不破坏既有 ABI允许的演进方式包括在结构体末尾追加新成员前提是该结构未被按值嵌套进其他结构、追加新枚举值、在保持类型/尺寸/语义不变的前提下重命名成员。版本方面头文件定义了FLUTTER_ENGINE_VERSION为1见 embedder.hFlutterEngineRun的第一个参数即要求传入该版本号GLFW 示例中甚至用static_assert(FLUTTER_ENGINE_VERSION 1, ...)在编译期强制校验 API 版本匹配见 FlutterEmbedderGLFW.cc一旦 API 发生破坏性变更嵌入器会立刻在编译期得到提示。此外所有入口函数返回FlutterEngineResult枚举kSuccess/kInvalidLibraryVersion/kInvalidArguments/kInternalInconsistency宿主应据此判断初始化是否成功。4. 最小嵌入流程初始化、渲染与事件注入4.1 启动引擎FlutterEngineRun一个嵌入器最小化的启动路径是调用 FlutterEngineRun其签名如下FlutterEngineResult FlutterEngineRun( size_t version, // 必须为 FLUTTER_ENGINE_VERSION const FlutterRendererConfig* config, // 渲染后端配置 const FlutterProjectArgs* args, // 项目参数资源路径、ICU、回调等 void* user_data, // 透传给所有回调的宿主数据 FlutterEngine* engine_out); // 输出的引擎句柄引擎内部还提供了拆分式的初始化路径FlutterEngineInitializeFlutterEngineRunInitialized以及FlutterEngineShutdown见 embedder.h适用于需要自定义任务调度器、在FlutterEngineRun返回前就可能向引擎回投任务的复杂场景。文档中对FlutterEngineRun的使用场景做了明确说明当嵌入器通过FlutterProjectArgs::custom_task_runners提供自定义任务运行器时引擎可能在FlutterEngineRun返回之前就需要宿主把任务投递回引擎此时宿主必须先拿到引擎句柄因此更推荐使用FlutterEngineInitialize拆分流程。4.2 渲染后端配置FlutterRendererConfigFlutterRendererConfig是一个带类型标签的联合体见 embedder.htype字段取值来自FlutterRendererType枚举kOpenGL通过 GL 回调向引擎提供 FBO/表面跨平台最通用kSoftware引擎直接写一块内存缓冲宿主在present回调中取走像素kMetal仅 Darwin 平台可用iOS 10.0 真机 / 13.0 模拟器macOS 10.14kVulkanVulkan 渲染路径。以 OpenGL 配置为例FlutterOpenGLRendererConfig中宿主必须实现的关键回调包括详见 embedder.h回调必填语义make_current/clear_current是在引擎管理的线程上使渲染上下文当前化/解除通过opengl state changed参数告知引擎 GL 状态是否被改动改动会触发引擎失效其内部 GL 状态缓存present或present_with_info二选一提交帧到屏幕present_with_info携带帧/缓冲损伤区域是脏矩形dirty region管理的前提fbo_callback或fbo_with_frame_info_callback二选一返回 Flutter 应绘制到的帧缓冲对象 ID同时指定两者是错误引擎初始化将被终止gl_proc_resolver是按名字解析 GL 函数指针make_resource_current可选但推荐在后台线程建立与主渲染上下文共享组的 GL 上下文用于异步纹理上传能显著改善纹理处理性能gl_external_texture_frame_callback可选宿主在外部纹理有新帧时被引擎回调供其填充纹理细节surface_transformation可选渲染前对目标表面的变换配合硬件 overlay 层使用时尤其有用需要特别说明的是present_with_info与populate_existing_damage若宿主不实现脏矩形管理引擎每帧都会全量重绘屏幕所有像素反之引擎只重绘两帧间发生变化的区域可显著降低渲染时间与能耗。4.3 项目参数FlutterProjectArgsFlutterProjectArgsembedder.h中最核心的字段是assets_pathFlutter 资产目录由flutter build bundle生成内含kernel_blob.bin等注意自 Dart 2 起已不支持从 Dart 源文件直接运行代码需编译为 kernel 形式由资产目录加载icu_data_pathicudtl.dat的路径command_line_argc/argv传给引擎的命令行参数第一个参数若提供会被解释为可执行文件名因此引擎 flag 不能放在列表首位platform_message_callback平台消息通道回调Platform Channelvm_snapshot_data等AOT 模式下的快照数据参见 docs/Flutter-engine-operation-in-AOT-Mode.mdcustom_task_runners自定义平台/渲染任务调度器需提供post_task_callback等见 embedder.hvsync_callback宿主在FlutterEngineOnVsync时机向引擎报告垂直同步帧时刻见 embedder.h无障碍相关的update_semantics_callback系列、on_pre_engine_restart_callback等。4.4 注入窗口指标与指针事件引擎本身不监听操作系统事件宿主必须把窗口尺寸变化与输入事件主动喂给引擎窗口指标调用 FlutterEngineSendWindowMetricsEvent 发送FlutterWindowMetricsEventembedder.h字段包括物理宽高width/height、pixel_ratio物理像素与逻辑像素的比例驱动 Flutter 的 DPR 逻辑、窗口屏幕位置与四边 inset以及view_id多视图场景下标识目标视图。指针事件调用 FlutterEngineSendPointerEvent 批量发送FlutterPointerEvent数组事件包含phase指针阶段、timestamp微秒需与FlutterEngineGetCurrentTime使用同一时钟、坐标与device_kind等。指针阶段枚举embedder.h要求宿主遵守严格的状态机触摸/鼠标触点进入区域时先发kAdd按下发kDown移动发kMove注意多键状态下释放其中一键也应视为kMove而非kUp抬起发kUp离开区域发kRemove另有悬停kHover与取消kCancel。此外引擎还提供键盘事件FlutterEngineSendKeyEvent、平台消息FlutterEngineSendPlatformMessage、本地化更新FlutterEngineUpdateLocales、任务执行FlutterEngineRunTask见 embedder.h等 API共同构成完整的宿主交互面。5. 实战剖析官方 GLFW 示例嵌入器仓库在 examples/glfw 提供了一个完整可用的参考实现它用 GLFW 做窗口管理与渲染呈现是理解整套 API 的最佳入门范本。核心文件是 FlutterEmbedderGLFW.cc。5.1 渲染回调注册RunFlutter函数中构造FlutterRendererConfig并选择kOpenGL路径见 FlutterEmbedderGLFW.ccFlutterRendererConfig config {}; config.type kOpenGL; config.open_gl.struct_size sizeof(config.open_gl); config.open_gl.make_current [](void* userdata) - bool { glfwMakeContextCurrent(static_castGLFWwindow*(userdata)); return true; }; config.open_gl.clear_current [](void*) - bool { glfwMakeContextCurrent(nullptr); return true; }; config.open_gl.present [](void* userdata) - bool { glfwSwapBuffers(static_castGLFWwindow*(userdata)); return true; }; config.open_gl.fbo_callback [](void*) - uint32_t { return 0; // FBO0直接绘制到窗口默认帧缓冲 }; config.open_gl.gl_proc_resolver [](void*, const char* name) - void* { return reinterpret_castvoid*(glfwGetProcAddress(name)); };四个 GL 回调分别映射到 GLFW 的上下文切换、缓冲区交换与函数指针解析逻辑直白引擎在需要时通过make_current拿到 GL 上下文绘制完成后通过present交换缓冲。5.2 项目参数与引擎启动接着构造FlutterProjectArgs并调用FlutterEngineRun见 FlutterEmbedderGLFW.ccstd::string assets_path project_path /build/flutter_assets; FlutterProjectArgs args { .struct_size sizeof(FlutterProjectArgs), .assets_path assets_path.c_str(), // 由 flutter build bundle 生成 .icu_data_path icudtl_path.c_str(), // 引擎 bin/cache 中的 icudtl.dat }; FlutterEngine engine nullptr; FlutterEngineResult result FlutterEngineRun(FLUTTER_ENGINE_VERSION, config, args, window, engine); if (result ! kSuccess || engine nullptr) { std::cout Could not run the Flutter Engine. std::endl; return false; } glfwSetWindowUserPointer(window, engine); // 把引擎句柄挂到 GLFW 窗口上 GLFWwindowSizeCallback(window, kInitialWindowWidth, kInitialWindowHeight);注意启动后立即手动调用一次窗口尺寸回调把初始尺寸800×600见 FlutterEmbedderGLFW.cc同步给引擎。5.3 事件转发示例通过 GLFW 回调把窗口尺寸、鼠标与键盘事件转发给引擎。窗口尺寸回调FlutterEmbedderGLFW.cc把 GLFW 的逻辑尺寸乘以pixel_ratio得到物理尺寸再调用FlutterEngineSendWindowMetricsEventvoid GLFWwindowSizeCallback(GLFWwindow* window, int width, int height) { FlutterWindowMetricsEvent event {}; event.struct_size sizeof(event); event.width width * g_pixelRatio; event.height height * g_pixelRatio; event.pixel_ratio g_pixelRatio; event.view_id kImplicitViewId; // 单窗口示例固定使用隐式视图 0 FlutterEngineSendWindowMetricsEvent( reinterpret_castFlutterEngine(glfwGetWindowUserPointer(window)), event); }指针回调FlutterEmbedderGLFW.cc则把 GLFW 的鼠标按钮动作映射为FlutterPointerPhase的kDown/kUp/kMove坐标同样乘以像素比时间戳取自std::chrono::high_resolution_clock的微秒计数然后通过FlutterEngineSendPointerEvent提交FlutterPointerEvent event {}; event.struct_size sizeof(event); event.phase phase; event.x x * g_pixelRatio; event.y y * g_pixelRatio; event.timestamp /* 微秒时间戳 */; event.view_id kImplicitViewId; FlutterEngineSendPointerEvent( reinterpret_castFlutterEngine(glfwGetWindowUserPointer(window)), event, 1);主循环则是朴素的while (!glfwWindowShouldClose(window)) { glfwWaitEvents(); }——所有 Flutter 工作都由引擎自己的线程驱动宿主的 UI 线程只负责事件循环。5.4 构建脚本与注意事项examples/glfw/run.sh 给出了端到端运行流程在examples/glfw目录下执行./run.sh即可# 1) 用 CMake 构建宿主 C 项目默认工程变体 host_debug_unopt cmake -DCMAKE_BUILD_TYPEDebug -DFLUTTER_ENGINE_VARIANT$variant .. make # 2) 创建并构建 guest Flutter 项目 flutter create myapp cd myapp flutter pub add flutter_gpu --sdkflutter cp ../../main.dart lib/main.dart flutter build bundle --local-engine-src-path ../../../../../ \ --local-engine$variant --local-engine-host$variant cd - # 3) 运行嵌入器参数为项目目录与 icudtl.dat 路径 ./flutter_glfw ./myapp ../../../third_party/icu/common/icudtl.dat配套的 CMakeLists.txt 展示了宿主工程的链接方式头文件目录指向仓库内的shell/platform/embedder并用find_library(FLUTTER_LIB flutter_engine PATHS .../out/${FLUTTER_ENGINE_VARIANT})在本地引擎构建输出目录中查找libflutter_enginePOST_BUILD步骤会把动态库复制到可执行文件旁边。同时它通过add_subdirectory直接编译了仓库自带的third_party/glfw从而避免对系统 GLFW 的依赖。示例的 guest 工程 examples/glfw/main.dart 里有两个值得注意的细节一是通过debugDefaultTargetPlatformOverride TargetPlatform.fuchsia让 Flutter 框架把运行平台伪装成 Fuchsia从而绕过不受支持平台的运行时报错可见宿主平台不在 Flutter 框架的默认支持名单内二是主动引用package:flutter_gpu以实例化 Flutter GPU 上下文配合 Impeller 后端。示例 README.md 还列出了常见排障点均可对照源码定位引擎位置CMakeLists 中find_library搜索的路径未必指向你的引擎构建目录需要按实际情况调整FLUTTER_ENGINE_VARIANT或路径像素比若画面以错误的比例绘制需调整 FlutterEmbedderGLFW.cc 中的g_pixelRatio计算示例中由帧缓冲宽度与初始逻辑宽度之比得出GLFW 位置CMake 找不到 GLFW 库时需修改third_party/glfw的引用路径。另外仓库还提供了 glfw_drm面向 DRM 的变体与 vulkan_glfwVulkan 渲染变体两个示例目录说明同一套嵌入思路可以迁移到不同的窗口系统与渲染后端。6. 渲染后端开关从构建系统看多后端支持shell/platform/embedder/BUILD.gn 通过 GN 参数控制嵌入层编译哪些渲染后端这些开关默认继承自 shell 的全局配置declare_args() { embedder_enable_software shell_enable_software embedder_enable_vulkan shell_enable_vulkan embedder_enable_gl shell_enable_gl embedder_enable_metal shell_enable_metal }随后embedder_source_set模板按开关条件性地把对应源码纳入编译BUILD.gn启用 GL 时加入embedder_surface_gl_skia.cc若 Impeller 支持渲染再加入embedder_surface_gl_impeller.cc并依赖//flutter/impeller/renderer/backend/gles启用 Metal 时加入.mm文件与//flutter/impeller/renderer/backend/metal依赖启用 Vulkan 时加入embedder_surface_vulkan*.cc与//flutter/flutter_vma、//flutter/vulkan/procs依赖。这意味着嵌入器可以同时具备多套渲染后端并在运行时通过FlutterRendererConfig.type选择其一——例如既有桌面又有关键业务的宿主可让同一份嵌入代码在不同设备上分别走 OpenGL 或 Vulkan 路径。工程内部还提供了两个特殊目标embedder_as_internal_library用于把嵌入层作为宿主内部实现细节通过FLUTTER_API_SYMBOL_PREFIXEmbedder给符号加前缀、用FLUTTER_NO_EXPORT不导出 API 面见 BUILD.gnembedder_proctable_unittests则验证FLUTTER_ENGINE_NO_PROTOTYPES模式下通过函数指针表调用 API 的兼容性。对嵌入层的回归验证由 tests/embedder_unittests.cc 等测试套件承载覆盖 GL/Metal/Vulkan/软件渲染与无障碍等多个维度测试目标定义见 BUILD.gn。7. 官方立场与支持边界务必提前知晓自定义引擎嵌入方案虽然可行但官方对其支持边界有非常明确的声明见 docs/Custom-Flutter-Engine-Embedders.md写入方案前应充分评估不受支持unsupported官方不反对团队为自身目的构建自定义引擎但不为这种配置提供支持——不承诺修复此类配置中出现的 bug 的时间线即便对通常愿意做出承诺的客户也是如此。团队应把这类配置视为短期方案并尽早规划迁移。难以长期可持续自定义引擎构建在官方计划作为运行时发布方的所有平台上都不被支持即官方自己发布运行时、区别于运行在该运行时之上的应用的那些平台且移植到 Web、桌面等新目标平台需要大量额外工作官方每新增一个特性自定义构建都需要同步更新以支持该特性维护负担昂贵。推荐的适用场景官方倾向于仅在把 Flutter 移植到未开箱支持的平台例如嵌入式硬件时使用自定义构建。换言之能走官方平台的场景优先走官方平台自定义嵌入方案是特定平台下的必要手段而非长期架构选择。若你在使用过程中遇到问题可参照 docs/Crashes.md、docs/Debugging-the-engine.md 等仓库文档定位排查路径但请做好官方不承诺修复的心理预期。8. 总结上手路径一览获取产物从flutter_infra_release下载与你 Flutter 框架bin/internal/engine.version匹配的引擎动态库Linux/Mac/Windows 均有对应归档或按 docs/contributing/Compiling-the-engine.md 自行构建flutter_enginehost 目标。引入 API在你的宿主工程中 include 引擎头文件构建产物为flutter_embedder.h源码位于 shell/platform/embedder/embedder.h链接libflutter_engine。实现渲染回调按目标平台选择kOpenGL/kSoftware/kMetal/kVulkan填充FlutterRendererConfig中必填回调。启动引擎以FLUTTER_ENGINE_VERSION调用FlutterEngineRun传入FlutterProjectArgs含assets_path、icu_data_path。接入事件通过FlutterEngineSendWindowMetricsEvent/FlutterEngineSendPointerEvent同步窗口与输入事件并实现vsync_callback以驱动帧调度。验证与排障对照 examples/glfw/FlutterEmbedderGLFW.cc 的完整实现与 tests/embedder_unittests.cc 的测试用例逐一确认各回调语义注意像素比、引擎路径与 GLFW 位置三个高频排障点。以 examples/glfw 为起点跑通最小闭环再按需扩展自定义合成器、外部纹理、无障碍与多视图能力便能在任意窗口系统上构建出属于自己的 Flutter 宿主。赞分享跨平台图形学前端【免费下载链接】engineThe Flutter engine项目地址https://gitcode.com/gh_mirrors/eng/engine点击查看免费下载相关推荐从零剖析 Flutter Engine Embedder 的 GLFW 桌面示例原理、构建与运行从零剖析 Flutter Engine Embedder 的 GLFW 桌面示例原理、构建与运行 本指南以 Flutter 仓库中的 GLFW Embedde跨平台移动开发前端UI组件桌面应用Flutter 引擎嵌入器Embedder API为 Flutter 未开箱支持的平台编写自定义宿主Flutter 引擎嵌入器Embedder API为 Flutter 未开箱支持的平台编写自定义宿主 本文基于仓库文档 Custom Flutter En跨平台移动开发前端UI组件桌面应用模型不收敛怎么办Dive-Into-Deep-Learning-PyTorch-PDF第6章优化算法SGD/Adam等7大算法对比实战模型不收敛怎么办Dive Into Deep Learning PyTorch PDF第6章优化算法SGD/Adam等7大算法对比实战 训练深度学习模型时损跨平台图形学前端上一篇小米智能家居Hass集成终极指南5分钟快速接入全屋设备下一篇Windows系统字体自定义技术深度解析noMeiryoUI架构原理与实现机制创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表