
微信交流群
作者:易旭昕
写作费时,敬请点赞,关注,收藏三连。
Flutter 渲染引擎在 iOS 上支持三种渲染方式,分别是纯软件(CPU),Metal 和 GL。其中纯软件的方式仅限于特定的构建,需要在编译时开启 TARGET_IPHONE_SIMULATOR 宏,应该是用于在模拟器上的测试,实机运行只会使用 Metal 和 GL。Flutter 会在运行时先判断是否能够使用 Metal,如果设备不支持,才会降级到 GL。iOS 10 以上的版本默认使用 Metal,GL 只用于兼容 iOS 9 的老旧设备。
这篇文章的主要内容是讲解在 iOS 上,Flutter 渲染引擎:
- 需要的 GL GPU 上下文环境是如何完成初始化;
- 目标输出 Surface 的设置过程;
- 渲染流水线执行光栅化的调用过程。
上图显示了 Flutter 渲染引擎在 iOS 上主要涉及的对象,绿色背景是 iOS SDK 原生对象,黄色背景是平台相关的适配对象,白色背景是平台无关的通用对象。后面的内容我们会频繁地引用图中的对象,这张图可以方便读者了解它们之间的关系。
# GL GPU 上下文环境初始化
上图显示了 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;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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]);
}
}
2
3
4
5
6
7
8
9
10
11
从上面代码我们可以看到在 IOSContextGL 的构造函数里面,主要就是创建两个 EAGLContext GL 上下文对象,分别在 io 线程用于图片纹理上传(resource_context_)和在 raster 线程用于光栅化(context_),并且 resource_context_ 作为 context_ 的 sharegroup,从而在两个上下文之间共享纹理。
到目前为止,我们已经完成了 GL GPU 上下文环境的初始化,跟 iOS Metal 的实现不同,跟 Android GL 的实现类似,光栅化使用的 Skia GrContext 不是在这里创建,而是延迟到设置目标输出 Surface 时创建。
# 设置目标输出 Surface
当 FlutterViewController 加载 View 结束后被系统回调 viewDidLoad,触发了 PlatformViewIOS::attachView 被调用。
void PlatformViewIOS::attachView() {
ios_surface_ =
[static_cast<FlutterView*>(owner_controller_.get().view) createSurface:ios_context_];
...
}
2
3
4
5
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
);
}
2
3
4
5
6
7
8
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));
}
2
3
4
5
6
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;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
上面的代码显示了:
- IOSSwitchableGLContext 用来设置 EAGLContext 作为调用线程的当前上下文;
- 产生 Framebuffer GL 对象;
- 产生 Renderbuffer GL 对象并绑定为上面 Framebuffer 的 Color Attachment(像素缓冲区);
- CAEAGLLayer 对象跟 Renderbuffer 的绑定会延迟到第一次进行光栅化的时候;
系统调用 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 上进行光栅化的操作如下:
- 将目标输出的 Surface,也就是 CAEAGLLayer,跟为它分配的 Renderbuffer GL 对象绑定,Renderbuffer 作为对应的 Framebuffer 的 Color Attachment,也就是对这个 Framebuffer 写入,光栅化后像素值的结果实际上是写入到对应的 CAEAGLLayer 的内部像素缓冲区里面;
- 将上面的 Framebuffer 包装成一个 SkSurface 对象,并取得对应的 SkCanvas 对象;
- 将生成的图层树里面的 DisplayList(SkPicture)通过上面的 SkCanvas 逐个绘制到 SkSurface 上,Skia 会先存储经过预处理的 2D 绘图指令;
- Flush SkCanvas,相当于生成相应的 GL 指令,执行时将光栅化的结果写入上面的 Framebuffer;
- 等待执行完毕后,请求提交绘制完成的像素缓冲器,并请求 iOS 重绘 UI,CAEAGLLayer 在被绘制的过程中输出新的像素缓冲器到屏幕上;
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;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
上面的代码显示了一个简化后的光栅化器光栅化图层树的流程(不考虑使用 ExternalViewEmbedder 的场景):
- Rasterizer 首先调用 GPUSurfaceGL::AcquireFrame 获取一个 SurfaceFrame;
- 然后通过 SurfaceFrame 获取用于绘制目标缓冲器的 SkCanvas(frame->SkiaCanvas());
- 然后将 SkCanvas 包裹成一个 CompositorContext::ScopedFrame 对象,并请求它光栅化图层树(compositor_frame->Raster(layer_tree, false));
- 最后调用 SurfaceFrame::Submit 提交绘制结果(frame->Submit());
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];
}
2
3
4
5
6
7
8
9
10
11
12
13
IOSRenderTargetGL::PresentRenderBuffer 主要是调用 CAEAGLLayer::presentRenderbuffer 来请求 CAEAGLLayer 提交绘制完毕的像素缓冲区。
如果读者对更多的具体细节感兴趣的话,可以去阅读 Skia 内部的实现代码,这部分相对来说就比较复杂了。
版权所有,禁止私自转发、克隆网站。