TMG-Skia cache miss 2.0预制缓存优化方案

1、方案背景

安卓HWUI渲染依赖于底层OpenGL或Vulkan的渲染管线机制,这一机制中最关键的是顶点着色器和片元着色器这两个可编程阶段。

所谓着色器(shader)可以看做是在GPU上运行的小程序,这些程序可以由开发者自定义(根据开发者调用API层次的不同,有些shader是间接生成的)。shader在被使用到时,需要根据特定的GPU编译成可以运行的二级制文件。这一编译过程会Block渲染线程,进而导致丢帧和延长启动时间。

针对这一问题,Android提供了一套两级缓存机制

• LRU-Map Cache

• Blob Cache

以上两个缓存都用于保存shader编译产物,其中LRU-Map Cache保存常用的shader,Blob Cache保存所有使用过的shader(实际会有最大size限制)。应用运行时,RenderThread会依次在LRU-Map和Blob中寻找当前使用的shader的编译产物,当两级缓存中都没有找到时,就会触发cache_miss,进而做实时shader编译。编译完成后当前shader cache会被添加到LRU-Map cache和Blob cache中。另外,Blob Cache会在有新shader被写入时将Blob写入文件,从而可以在下次应用启动时直接加载已有的shader cache。

这套机制的问题是:首次打开应用时会有大量的shader需要编译,从而导致明显的卡顿。

2、方案设计

2.1 1.0方案

我们在2022年开发了skia cache miss 1.0方案,该方案的基本思路是在Rom打包阶段即对应用做预制缓存,将应用的shader cache作为Rom的一部分上线,在应用运行时直接读取已经保存的shader缓存。从而避免了在运行时的shader编译,从而提高了应用的冷启动效率和渲染流畅度。

该方案作为核心应用流畅度专项的一部分,对Top 6三方应用做了shader预制缓存优化,实现了滑动流畅度2分左右,冷启动5%以上的优化,并最终落地了L和M系列的大部分机型。

L1_Skia Cache方案最终验收测试

L2_核心应用图形渲染专项T版本收益验收报告

L2S_核心应用图形渲染专项T版本收益验收报告

M3_核心应用图形渲染专项T版本收益验收报告

N系列机型上,我们尝试对包括home、systemui、provision和weather等多个系统应用适配了我们的方案,同样也取得了明显的优化效果。

过程中也发现存在一些问题:

方案适配:由于不同机型硬件和应用版本的差异,其shader cache通常不能通用,这就要求每个机型的每个需要优化的应用都需要分别做适配。随着适配机型和应用的增加,小组内部已很难应对所有的适配需求。

Shader更新:shader的编译产物与应用本身的绘制方式和OpenGL ES的版本相关,当应用或OpenGL ES版本变化时,就需要重新制作shader cache。

OTA升级:原方案只在系统第一次启动时做一次性替换,无法覆盖OTA升级的场景。

国际版支持:原方案通过固定Property和云控做上层控制,无法应对国际版优化的需求。

2.2 2.0方案

针对上面提到的问题,2.0方案主要完成如下目标

提供系统应用和三方应用自身适配的基础设施。

Shader复用:每次应用和系统更新时,需要重制的shader cache是有限的。因此需要从Shader语义入手,实现Shader cache的复用和合并机制。

Property和云控可扩展。

基本的Cache流程如下:

2.3 SurfaceFlinger方案

SurfaceFlinger中也会使用到一些Shader,但与应用在RenderThread中的LRU-Map和BlobCache二级缓存方案不同的是,SurfaceFlinger在自己独立的RenderEngine中只做LRU-Map缓存,其在启动时会有一层PrimeShaderCache机制,基本逻辑是在空白的Surface上调用所有绘制指令,从而提前完成所有Shader的编译和缓存。

北京团队计划将该方案拓展到应用上,该方案的特点是不需要后期适配,但存在的挑战是需要从API层适配每个应用。对比之下,skia cache miss 2.0方案完全从底层做优化,避免了上层业务逻辑,同时系统开销也更小。

3、优化目标

  1. 支持海外系统适配。

  2. 支持大部分系统应用和Top30应用。

  3. 降低每个机型和应用的shader_cache适配和维护成本。

  4. 被优化应用的冷启动时间缩短8%以上,滑动流畅度提升2分以上。

  5. 减少或消除应用在第一次启动时由cache_miss导致的卡顿丢帧问题。

4、风险评估

预制缓存会占用一定内存空间

RAM的占用,原Android的LRU-Map和Blob Cache二级缓存方案本身就需要占用一定的RAM,我们的方案只是在其基础上提前将部分shader加载。这部分加载的shader cache在Android原方案中也会在应用启动后短时间内被消耗,因此可以说我们的方案基本没有额外RAM的占用。

ROM的占用,预制缓存本身必定会占用一定的Disk空间,根据1.0方案的适配情况统计,几乎所有应用的预制缓存文件大小都在0.5M之内,大部分都只有0.3M左右。以Top 6应用为例,其总ROM占用不会超过3M。

Shader的识别和编译可能会带来额外的负载

单个Shader Cache的识别是以key-value的形式完成的,shader总数根据以往的统计也是很有限的(以com.miui.home为例,只有50-80个shader),因此其识别过程带来的负载是完全可以接受的。主要的风险是OpenGL ES驱动升级时,需要整体编译shader的场景,这里计划将系统更新带来的编译过程放在系统启动Provision过程中,这样可以避免对用户实际使用应用的过程造成影响。

5、落地计划

落地时间:2024.3