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系列的大部分机型。
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、优化目标
-
支持海外系统适配。
-
支持大部分系统应用和Top30应用。
-
降低每个机型和应用的shader_cache适配和维护成本。
-
被优化应用的冷启动时间缩短8%以上,滑动流畅度提升2分以上。
-
减少或消除应用在第一次启动时由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