作者:易旭昕 原文链接:https://zhuanlan.zhihu.com/p/258492344 本文由作者授权发布。
写作费时,敬请点赞,关注,收藏三连。
Flutter 渲染引擎在 iOS 上支持三种渲染方式,分别是纯软件(CPU),Metal 和 GL。其中纯软件的方式仅限于特定的构建,需要在编译时开启 TARGET_IPHONE_SIMULATOR 宏,应该是用于在模拟器上的测试,实机运行只会使用 Metal 和 GL。Flutter 会在运行时先判断是否能够使用 Metal,如果设备不支持,才会降级到 GL。iOS 10 以上的版本默认使用 Metal,GL 只用于兼容 iOS 9 的老旧设备。
这篇文章的主要内容是讲解在 iOS 上,Flutter 渲染引擎:
上图显示了 Flutter 渲染引擎在 iOS 上主要涉及的对象,绿色背景是 iOS SDK 原生对象,黄色背景是平台相关的适配对象,白色背景是平台无关的通用对象。后面的内容我们会频繁地引用图中的对象,这张图可以方便读者了解它们之间的关系。
上图显示了 iOS 应用在主线程初始化 Flutter Engine 的调用栈。FlutterViewController 在被系统初始化时创建了 FlutterEngine,并请求 engine 创建 Shell 对象,FlutterEngine 在 Shell 对象的创建过程中生成了 PlatformViewIOS 对象并将它传递给 Shell。
std::unique_ptr<IOSContext> IOSContext::Create(IOSRenderingAPI rendering_api) {
switch (rendering_api) {
case IOSRenderingAPI::kOpenGLES:
return std::make_unique<IOSContextGL>();
case IOSRenderingAPI::kSoftware:
return std::make_unique<IOSContextSoftware>();
#if FLUTTER_SHELL_ENABLE_METAL
case IOSRenderingAPI::kMetal:
return std::make_unique<IOSContextMetal>();
#endif // FLUTTER_SHELL_ENABLE_METAL
default:
break;
}
FML_CHECK(false);
return nullptr;
}
PlatformViewIOS 一个主要的职责就是创建 IOSContext 对象,由它来为渲染引擎提供 GPU 上下文环境,在使用 GL API 的情况下,创建的实际上是 IOSContextGL 对象。
IOSContextGL::IOSContextGL() {
resource_context_.reset([[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3]);
if (resource_context_ != nullptr) {
context_.reset([[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3
sharegroup:resource_context_.get().sharegroup]);
} else {
resource_context_.reset([[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2]);
context_.reset([[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2
sharegroup:resource_context_.get().sharegroup]);
}
}
从上面代码我们可以看到在 IOSContextGL 的构造函数里面,主要就是创建两个 EAGLContext GL 上下文对象,分别在 io 线程用于图片纹理上传(resource_context_)和在 raster 线程用于光栅化(context_),并且 resource_context_ 作为 context_ 的 sharegroup,从而在两个上下文之间共享纹理。
到目前为止,我们已经完成了 GL GPU 上下文环境的初始化,跟 iOS Metal 的实现不同,跟 Android GL 的实现类似,光栅化使用的 Skia GrContext 不是在这里创建,而是延迟到设置目标输出 Surface 时创建。
当 FlutterViewController 加载 View 结束后被系统回调 viewDidLoad,触发了 PlatformViewIOS::attachView 被调用。
void PlatformViewIOS::attachView() {
ios_surface_ =
[static_cast<FlutterView*>(owner_controller_.get().view) createSurface:ios_context_];
...
}
PlatformViewIOS::attachView 通过 FlutterViewController 获取 FlutterView,然后调用它的 createSurface 方法创建 IOSSurface,传递 IOSContext 对象作为参数。
- (std::unique_ptr<flutter::IOSSurface>)createSurface:
(std::shared_ptr<flutter::IOSContext>)ios_context {
return flutter::IOSSurface::Create(
std::move(ios_context), // context
fml::scoped_nsobject<CALayer>{[self.layer retain]}, // layer
[_delegate platformViewsController] // platform views controller
);
}
FlutterView::createSurface 调用 IOSSurface::Create 方法创建 IOSSurface 对象,并传递自己的 layer 对象作为参数,在使用 GL API 的情况下,layer 对象实际是 CAEAGLLayer,创建的 IOSSurface 实际上是 IOSSurfaceGL。
IOSSurfaceGL::IOSSurfaceGL(fml::scoped_nsobject<CAEAGLLayer> layer,
std::shared_ptr<IOSContext> context,
FlutterPlatformViewsController* platform_views_controller)
: IOSSurface(context, platform_views_controller) {
render_target_ = CastToGLContext(context)->CreateRenderTarget(std::move(layer));
}
IOSSurfaceGL 主要是调用 IOSContextGL::CreateRenderTarget 方法创建 IOSRenderTargetGL 对象并持有。IOSRenderTargetGL 主要是用来持有 CAEAGLLayer 对象,和从 IOSContextGL 获得的用于光栅化的 EAGLContext 对象,并为 CAEAGLLayer 分配 Framebuffer 和 Renderbuffer GL 对象。
IOSRenderTargetGL::IOSRenderTargetGL(fml::scoped_nsobject<CAEAGLLayer> layer,
fml::scoped_nsobject<EAGLContext> context)
: layer_(std::move(layer)), context_(context) {
...
auto context_switch = GLContextSwitch(std::make_unique<IOSSwitchableGLContext>(context_.get()));
bool context_current = context_switch.GetResult();
// Generate the framebuffer
glGenFramebuffers(1, &framebuffer_);
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer_);
// Setup color attachment
glGenRenderbuffers(1, &colorbuffer_);
glBindRenderbuffer(GL_RENDERBUFFER, colorbuffer_);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, colorbuffer_);
valid_ = true;
}
上面的代码显示了:
系统调用 FlutterViewController::viewDidLayoutSubviews 通知它 FlutterView 布局计算完成,大小已经确定,会触发 PlatformView::NotifyCreated 被调用。在这里,主线程会同步请求 raster 线程创建 Rendering Surface,实际上就是请求之前创建的 IOSSurfaceGL 创建 GPUSurfaceGL。
GPUSurfaceGL 在构造函数里面会创建光栅化用的 Skia GrContext 对象并持有,该 GrContext 对象对应当前线程的 GL 上下文对象,也就是 IOSRenderTargetGL 持有的光栅化用的 EAGLContext 对象。GPUSurfaceGL 对象最终通过 Shell 传递给 Rasterizer 持有,到这里光栅化器就完成了目标输出 Surface 的设置,现在我们可以开始绘制第一帧了。
关于 Flutter 渲染流水线比较完整的说明请参考我之前的文章Flutter 渲染流水线浅析,在这里我们只关注光栅化的部分。Flutter 在 iOS GL 上进行光栅化的操作如下:
RasterStatus Rasterizer::DrawToSurface(flutter::LayerTree& layer_tree) {
auto frame = surface_->AcquireFrame(layer_tree.frame_size());
SkMatrix root_surface_transformation = surface_->GetRootTransformation();
auto root_surface_canvas = frame->SkiaCanvas();
auto compositor_frame = compositor_context_->AcquireFrame(
surface_->GetContext(), // skia GrContext
root_surface_canvas, // root surface canvas
external_view_embedder, // external view embedder
root_surface_transformation, // root surface transformation
true, // instrumentation enabled
frame->supports_readback(), // surface supports pixel reads
raster_thread_merger_ // thread merger
);
if (compositor_frame) {
RasterStatus raster_status = compositor_frame->Raster(layer_tree, false);
frame->Submit();
return raster_status;
}
return RasterStatus::kFailed;
}
上面的代码显示了一个简化后的光栅化器光栅化图层树的流程(不考虑使用 ExternalViewEmbedder 的场景):
GPUSurfaceGL::AcquireFrame 需要调用 IOSSurfaceGL::GLContextMakeCurrent 设置当前线程的 GL 上下文,并且调用 IOSRenderTargetGL::UpdateStorageSizeIfNecessary 将 CAEAGLLayer 和 IOSRenderTargetGL 创建时分配的 Renderbuffer 绑定。
当 SurfaceFrame::Submit 的时候,IOSRenderTargetGL::PresentRenderBuffer 会被调用到。
bool IOSRenderTargetGL::PresentRenderBuffer() const {
const GLenum discards[] = {
GL_DEPTH_ATTACHMENT,
GL_STENCIL_ATTACHMENT,
};
glDiscardFramebufferEXT(GL_FRAMEBUFFER, sizeof(discards) / sizeof(GLenum), discards);
glBindRenderbuffer(GL_RENDERBUFFER, colorbuffer_);
auto current_context = [EAGLContext currentContext];
FML_DCHECK(current_context != nullptr);
return [current_context presentRenderbuffer:GL_RENDERBUFFER];
}
IOSRenderTargetGL::PresentRenderBuffer 主要是调用 CAEAGLLayer::presentRenderbuffer 来请求 CAEAGLLayer 提交绘制完毕的像素缓冲区。
如果读者对更多的具体细节感兴趣的话,可以去阅读 Skia 内部的实现代码,这部分相对来说就比较复杂了。