最近在知乎到收到了一个这样的问题,提问者对于 Android 和 iOS 渲染架构差异提出了疑问,而对于问题的描述我觉得挺有意思,不知道你怎么看?
其实我觉得这个不应该是描述里的 「SurfaceFlinger 和 backboardd 的区别」,更多应该是 Metal 和 Vulkan 的区别。
为什么这么说?首先简单区分下 Android 和 iOS framework 层面渲染框架的差异。
Android
Android 上一切内容都会渲染到 Surface 上,Surface 大部分来自 Window ,在 framework 层面调用了 Canvas.draw 之后,实际上转化为绘制指令提交给 RenderThread ,RenderThread 向 GPU 发出命令,最终通过 SurfaceFlinger 合成 Layer (单个 buffer)提交 HAL 渲染。
回到对应到问题上 「各个应用先分别渲染到各自的 Surface 上,再提交到 SurfaceFlinger 做合成」,其实 Surface 上并不是真的渲染,只是提交了指令到 RenderThread 渲染,最终通过 SurfaceFlinger 去合成消费,所以 CPU 层面 View 只是构建了 DisplayList 。
其实 Android 不是在每一帧上都直接在 CPU 上执行绘图调用,这样对于动画等场景过于“繁重”,所以才会有 RenderThread 的存在。
所以如果把 SurfaceFlinger 和 RenderThread 放一起才是一个完整的 Graphics Pipeline ,它们是真正负责渲染的部分。
iOS
iOS 里 CALayer 主要来自 UIView,CALayer 是显示的基础,负责渲染和动画,而问题里提到的 backboard 其实就是 iOS 上的独立进程 render server。
而 CALayer 上有个 contents 属性,它是一个 bitmap(backing store),contents 主要就是用于静态保存渲染好的内容,在需要时提取显示。
在 iOS 里,上层主要的渲染和构建流程是 Core Animation, 这个阶段主要就是在 CPU 阶段通过 Commit Transaction 处理布局然后计算好图层,视图层次结构被编码成 CALayer 发送到 render server ,之后会被 decode(解码)然后提交 GPU 绘制。
简单说 CALayer 等同于一个纹理,而它的 contents 等于一个缓存区 backing store
所以其实 Core Animation 里也不是实际绘制,例如 Core Animation 的 Display 阶段也只是根据Layout 的结果创建得到 primitives(图元) ,而 contents 里的 bitmap 是 GPU 中根据 primitives 绘制得到的,所以最终绘制是在 render server 。
结论
所以其实不管是 Android 在 surface 还是 iOS Core Animation,它们都不是在上层直接通过 CPU 调用绘制 ,而是在 RenderThread 和 render server 合并实现绘制,从这点看其实大家意念并没有差别,只是实现方式和管理模式不一样。
不过不管是 Android 的 RenderThread 还是 iOS 的 render server ,都不是实际渲染性能的「大魔王」,因为 Metal 和 Vulkan 这些底层 API 对渲染性能影响更大。
为什么底层渲染 API 更重要,以 OpenGL 为例子,从顶点处理(vertex processing)、图元装配(triangle assembly)、光栅化(rasterization)、片段处理(fragment processing)、测试和混合(testing and blending)这样的 Graphics Pipeline 组成了一个简单的画面渲染流程。
而在这个流程里,光栅化是一个非常耗时的过程,一般是通过 GPU 来加速,而将数据从 CPU 传输到 GPU 的过程也是一个耗时过程。
例如在 Android 里,RenderThread 主要就是从 UI 线程获取输入并将它们处理到 GPU ,RenderThread 是与 GPU 通信的单独线程。
而到了 Metal 和 Vulkan ,它们的出现弥补了 OpenGL 很多历史问题,将渲染性能和执行效率提高了一个层级,这些变化足以让 Runtime framework 上差异被忽略,举个例子:
-
OpenGL 是单线程模型,所有的渲染操作都放在一个线程;而 Vulkan 中引入了 Command Buffer ,每个线程都可以往 Command Buffer 提交渲染命令,可以更多利用多核多线程的能力
-
OpenGL 很大一部分支持需要驱动的实现,OpenGL 驱动包揽了一大堆工作,在简化上层操作的同时也牺牲了性能;Vulkan 里驱动不再负责跟踪资源和 API 验证,虽然这提高了框架使用的复杂度,但是性能得到了大幅提升
又比如前面 iOS 所说的 render server ,我们可以简单看作是 OpenGL 或者 Metal ,而 Metal 相比 OpenGL 可以“更接近”底层硬件,同时降低了资源开销,例如:
Metal 里资源在 CPU 和 GPU 之间的同步访问是由开发者自己负责,它提供更快捷的资源同步 API,可以有效降低 OpenGL 里纹理和缓冲区复制时的耗时;另外 Metal 使用 GCD 在 CPU 和 GPU 之间保持同步,CPU 和 GPU 是共享内存无需复制就可以交换数据。
可以看到 Vulkan 和 Metal 都给 Android 和 iOS 带来了巨大的性能提升,所以如果讨论渲染实现带来的性能差异,现阶段更多应该是 Vulkan 和 Metal 的差异。
Vulkan VS Metal
Metal 应该是 2014 年开始在 iOS 8 设备加入,而 Vulkan 是在 2016 年 Android 7 时投入使用,事实上如果真的要对比它们两个的性能很难,因为 Vulkan 是一个通用的底层渲染 API ,它不止考虑 Android,而 Metal 专职于苹果设备,这导致它们没办法在“公平”的变量下比对。
如果要说使用上的差异,那么 Metal 其实更简单,绝大部分实际是 Metal 对 Vulkan 在概念上的合并和简化,例如:
Metal 会自动帮助开发者处理幕后管理工作,它会执行更多自动化操作来处理加速视觉效果和增强性能等后台管理,然而 Vulkan 是更多提供 API,主要取决于开发者自主的控制。
所以从这角度说,Vulkan 似乎提供了比 Metal 更大的性能可能性,当然也更复杂更容易写出有问题的代码。
Metal 比 Vulkan 更简单还有一个体现就是:3.10开始 Flutter 上 iOS 的 Impeller 能首发,而 3.13 了 Android 还依旧等待的原因。
总体而言,Metal 更容易使用,而 Vulkan 更灵活可控,当然,对比 OpenGL 其实都变复杂,特别是 Vulkan ,因为更接近底层,所以复杂度更高。
另外值得注意的是,有一个叫 MoltenVK 的项目,它支持将 Vulkan 映射到 iOS 和 macOS 上的 Metal 去运行,这一定程度也表明了 Vulkan 和 Metal 并不疏远。
当然,这里有个误区,你感受到的性能差异可能只是 App 本身写的不够好,就比如很多人写的 App 或者游戏,并不能真实体验到 OpenGL -> Metal/ Vulkan 的性能大幅提升,因为他可能连 OpenGL 的极限都没有达到····
所以用自己写的 App 去评判 Metal 和 Vulkan 的优劣其实并不“公允”,很多时候可能自己你在某个平台并没做好自己该做的。