在 Metal 应用中, 使用 Metal Shading Language(以下简称 MSL )编写的着色器源码首先被处理为 AIR (Apple IR) 格式的中间表示。如果着色器源码是以字符形式在工程中引用,这一步会在运行时在用户设备上进行,如果着色器被添加为工程的Target,着色器源码会在编译期在 Xcode 中跟随项目构建生成 MetalLib: 一种设计用来存放 AIR 的容器格式。随后 AIR 会在运行时,根据当前设备 GPU 的硬件信息,被 Metal Compiler Service 用 JIT 编译为可供执行的机器码。相比源码形式,将着色器源码打包为 MetalLib 有助于降低运行时生着色器机器码的开销。着色器机器码的编译会在每一次渲染管线状态对象(PipelineStateObject,下文简称 PSO)创建时发生,一个 PSO 持有当前渲染管线关联的所有状态,包含光栅化各阶段的着色器机器码,颜色混合状态,深度信息,模版掩码状态,多重采样信息等等。PSO 通常被设计为一个 imutable object(不可变对象),如果需要更改 PSO 中的状态需要创建一个新的 PSO 拷贝。
由于 PSO 可能在应用生命周期中多次创建, 为了防止着色器的重复编译开销,所有编译过的着色器机器码会被 Metal 缓存用来加速后续 PSO 的创建过程,这个缓存称为 Metal Shader Cache ,完全由 Metal 内部管理,不受开发者控制。应用通常会在启动阶段一次性创建大量 PSO 对象,由于此时 Metal 中没有任何着色器的编译缓存,PSO 的创建会触发所有的着色器完整执行从 AIR 到机器码的编译过程,整个集中编译阶段是一个 CPU 密集型操作。在游戏中通常在玩家进入新关卡前利用 Loading Screen 准备好下一场景所需的 PSO,然而常规 app 中用户的预期是能够即点即用,一旦着色器编译时间超过 16ms,用户就会感受到明显的卡顿和掉帧。
在 Metal 2 中, Apple 首次为开发者引入了手动控制着色器缓存的能力:Metal Binary Archive。Metal Binary Archive 的缓存层次位于 Metal Shader Cache 之上, 这意味着 Metal Binary Archive 中的缓存在 PSO 创建时会被优先使用 。 在运行时,开发者可以通过 MetalPipelineManager 手动将性能敏感的着色器函数添加至 Metal Binary Archive 对象中并序列化至磁盘中。应用再次冷启后,此时创建相同的 PSO 即是一个轻量化操作,没有任何着色器编译开销。缓存的 Binary Archive 甚至可以二次分发给相同设备的用户,如果本地 Binary Archive 中缓存的机器码与当前设备的硬件信息不匹配,Metal 会回落至完整的编译流水线,确保应用的正常执行。游戏堡垒之夜「Fortnite」 在启动阶段需要创建多达 1700 个 PSO 对象,通过使用 Metal Binary Archive 来加速 PSO 创建,启动耗时从 1m26s 优化为 3s, 速度提升28倍。 Metal Binary Archive 通过内存映射的方式供 GPU 直接访问文件系统中的着色器缓存,因此打开 Metal Binary Archive 时会占用设备宝贵的虚拟内存地址空间。与缓存所有的着色器函数相比,更明智的做法是根据具体的业务场景将缓存分层,在页面退出后及时关闭对应的缓存 , 释放不必要的虚拟内存空间。Metal Shader Cache 的黑盒管理机制无法保证着色器在使用时不会出现二次编译 , 而 Metal Binary Archive 可以确保其中的缓存的着色器函数在应用生命周期内始终可用。Metal Binary Archive 虽然允许开发者手动管理着色器缓存,却依然需要通过在运行时搜集机器码来构建,无法保证应用初次安装时的使用体验。在 2022 年 WWDC 中,Metal 3 终于弥补了这个遗留的缺陷,为开发者带来了在离线构建 Metal Binary Archive 的能力:
构建离线 Metal Binary Archive 需要使用一种全新的配置文件 PipelineScript,Pipeline Script 其实是 Pipeline State Descriptor 的一种 JSON 表示,其中配置了 PSO 创建所需的各种状态信息,开发者可以直接编辑生成,也可以在运行时捕获 PSO 获得。给定 Pipeline Script 和 MetalLib,通过 Metal 工具链提供的 metal 命令即可离线构建出包含着色器机器码的 Metal Binary Archive。Metal Binary Archive 中的机器码可能会包含多种 GPU 架构 , 由于 Metal Binary Archive 需要内置在应用中提交市场 , 开发者可以综合考虑包体积的因素剔除不必要的架构支持。
通过离线构建 Metal Binary Archive,着色器编译的开销只存在于编译阶段,应用启动阶段 PSO 的创建开销大大降低。Metal Binary Archive 不止可以优化应用的首屏性能, 真实的业务场景下,一些 PSO 对象会迟滞到具体页面才会被创建,触发新的着色器编译流程。一旦编译耗时过长,就会影响当前 RunLoop 下 Metal 绘制指令的提交, Metal Binary Archive 可以确保在应用的生命周期内, 核心交互路径下的着色器缓存始终为可用状态,将节省的 CPU 时间片用来处理与用户交互强相关的逻辑, 大大提升应用的响应性和使用体验。 矢量渲染基础概念
矢量渲染泛指在平面坐标系内通过组装几何图元来生成图像信息的手段,通过定义一套完整的绘制指令,可以在不同的终端上还原出不失真的图形, 任何前端的视窗都可以被看作一个 2D 平面的矢量渲染画布,Chrome 与 Android 渲染系统就是基于 Google 的 2D 图形库 Skia 构建。对应用开发而言,矢量渲染技术也扮演重要角色,如文本 / 图表 / 地图 / SVG / Lottie 等都依赖矢量渲染能力来提供高品质的视觉效果。