微信交流群

作者:易旭昕

原文链接:https://zhuanlan.zhihu.com/p/232005984

写作费时,敬请点赞,关注,收藏三连。

Flutter 渲染引擎在 iOS 上的实现请参考我的文章 Flutter 渲染引擎详解 - iOS Metal 篇

Flutter 渲染引擎在 Android 上也支持三种渲染方式,分别是纯软件(CPU),GL 和 Vulkan。其中纯软件模式是运行时由 Settings 的 enable_software_rendering 开关控制,默认为 false,目前只看到是通过命令行开关来控制,猜测是用于特定的测试用途。

跟 iOS 不一样的是,Flutter 在 Android 上并不是动态判断系统和硬件环境,在运行时来选择 Vulkan 或者 GL,而是需要开发者自行编译一个开启 SHELL_ENABLE_VULKAN 宏的 Flutter Engine,可能 Skia 对 Vulkan 在 Android 上的支持还不够完善的缘故,这个宏是默认关闭的,所以 Flutter 在 Android 上目前还是以 GL 为主。

这篇文章的主要内容是讲解在 Android 上,Flutter 渲染引擎:

  1. 需要的 GL GPU 上下文环境是如何完成初始化;
  2. 目标输出 Surface 的设置过程;
  3. 渲染流水线执行光栅化的调用过程。

img

上图显示了 Flutter 渲染引擎在 Android 上主要涉及的对象,黄色背景是平台相关的适配对象,白色背景是平台无关的通用对象。后面的内容我们会频繁地引用图中的对象,这张图可以方便读者了解它们之间的关系。

Flutter 在 1.20 之前的版本,Context 和 Surface 这部分的实现存在比较多的 hardcode,代码逻辑比较混乱,1.20 版本做了比较大的重构,整体设计跟 iOS 基本趋同,本文是根据 1.20 的代码写就。

# GL GPU 上下文环境初始化

img

上图显示了 Android 应用在主线程初始化 Flutter Engine 的调用栈:

  1. Flutter Engine Native 部分的主要入口在 AndroidShellHolder,它在 engine 初始化的时候被创建;
  2. AndroidShellHolder 创建 Shell 并持有它;
  3. Shell 在创建时调用 AndroidShellHolder 提供的 Callback 创建 PlatformViewAndroid 并获得所有权;
  4. PlatformViewAndroid 在被创建时先创建 AndroidContextGL,然后把它传递给接着创建的 AndroidSurfaceGL,最后 PlatformViewAndroid 持有 AndroidSurfaceGL,AndroidSurfaceGL 持有 AndroidContextGL;

AndroidContextGL 提供了 Flutter 渲染引擎所需要的 GL 上下文环境。它会先创建两个 EGLContext,一个 main context 在 raster 线程用于光栅化,一个 resource context 在 io 线程用于图片纹理上传,因为 main context 是 resource context 的 share context,所以 resource context 上传的纹理可以被 main context 直接使用。AndroidSurfaceGL 在构建时会马上请求 AndroidContextGL 创建 offscreen 的 AndroidEGLSurface。AndroidEGLSurface 是 EGLSurface 的封装,对于 offscreen 来说,只是用一个 1x1 的 PbufferSurface 作为 resource context 的 target EGLSurface,这个PbufferSurface 只是作为必须的占位符,没有实际用途。

AndroidSurfaceGL 构建完毕之后,Flutter 渲染引擎所需要的 GL 上下文环境就已经初始化完成了。跟 iOS Metal 不同的是,因为 GL Context 的线程相关性,Skia GrContext 需要延迟到 GL Context 设置到目标线程真正使用的时候才创建,无法提前创建。

# 设置目标输出 Surface

Flutter 允许应用选择 SurfaceView 或者 TextureView 作为目标输出 Window,无论是选择 SurfaceView 还是 TextureView,Flutter 都是通过注册回调方法,在 SurfaceView/TextureView 可见时获得回调,得到封装了 Android Native Window 的 Surface 对象。

img

  1. 当主线程获得系统回调通知 Surface 已经创建时,它会调用 Engine Native 的代码 SurfaceCreated(platform_view_android_jni_impl.cc);
  2. SurfaceCreated 会取出 Surface 对象里面的 Window Handle,并封装成 AndroidNativeWindow 对象传递给 PlatformViewAndroid::NotifyCreated;
  3. PlatformViewAndroid::NotifyCreated 在主线程被调用时,会通知 raster 线程,并同步等待结果;
  4. raster 线程接收到消息后会调用 AndroidSurfaceGL::SetNativeWindow;
  5. AndroidSurfaceGL::SetNativeWindow 调用 AndroidContextGL::CreateOnscreenSurface;
std::unique_ptr<AndroidEGLSurface> AndroidContextGL::CreateOnscreenSurface(
    fml::RefPtr<AndroidNativeWindow> window) const {
  EGLDisplay display = environment_->Display();

  const EGLint attribs[] = {EGL_NONE};

  EGLSurface surface = eglCreateWindowSurface(
      display, config_, reinterpret_cast<EGLNativeWindowType>(window->handle()),
      attribs);
  return std::make_unique<AndroidEGLSurface>(surface, display, context_);
}
1
2
3
4
5
6
7
8
9
10
11

AndroidContextGL::CreateOnscreenSurface 实际上就是使用 Window Handle 创建对应的 EGLSurface,并包装成 AndroidEGLSurface 返回给 AndroidSurfaceGL 作为光栅化输出的 onscreen EGLSurface。

主线程等待完 raster 线程创建 onscreen EGLSurface 后,再次同步请求 raster 线程调用 PlatformViewAndroid::CreateRenderingSurface,实际上这里就是创建 GPUSurfaceGL。GPUSurfaceGL 被创建时会先请求 AndroidSurfaceGL 将 AndroidContextGL 里面的 main EGLContext 作为 raster 线程的当前 GL 上下文,然后创建对应的 GrContext 并持有。这个 GrContext 将来就会用于光栅化。

主线程同步等待 GPUSurfaceGL 创建完毕后,把获得的 GPUSurfaceGL 对象通过 Shell 传递给 Rasterizer 持有,到这里光栅化器就完成了目标输出 Surface 的设置,现在我们可以开始绘制第一帧了。

# 光栅化输出

关于 Flutter 渲染流水线比较完整的说明请参考我之前的文章 Flutter 渲染流水线浅析,在这里我们只关注光栅化的部分。Flutter 光栅化的过程比较简单,Android 和 iOS 基本流程也是大同小异:

  1. Rasterizer 请求 GPUSurfaceGL 创建一个 SkSurface,这个 SkSurface 实际上就是当前 EGLContext 的 FBO 0 的包装,也就是之前创建的 onscreen EGLSurface,实际上就是源自 SurfaceView 或者 TextureView 的 Android Native Window;
  2. 通过上述获得 SkSurface 对象,取得对应的 SkCanvas 对象;
  3. 将生成的图层树里面的 DisplayList(SkPicture)通过上面的 SkCanvas 逐个绘制到 SkSurface 上,Skia 会先存储经过预处理的 2D 绘图指令;
  4. Flush SkCanvas,相当于生成相应的 GL GPU 绘图指令请求 GPU 执行;
  5. 最后调用 eglSwapBuffers 请求 Android Native Window 交换前后台缓冲区,并触发 Android UI 的重绘(TextureView)或者 SurfaceFlinger 的窗口重新合成(SurfaceView);