锁屏可变字体玻璃方案设计
一、控件(圆角矩形)玻璃算法
以菲涅耳方程为基础,手机上的折射模型建模为一个Background(壁纸、桌面),控件建模为有一定厚度的,边缘为圆角的模型,切与Background没有缝隙。
Snell’s Law / 2D折射模型:


算法可视化流程,OS4玻璃shader功能多,主要包括折射、反射、Bloom、混色、对比度亮度调整、高光边。
白板流程图:OS4玻璃shader算法可视化 SDF → Normal → Refraction → Reflection → Blend → 应用亮度曲线调整/应用饱和度和亮度调整 → bloom → 混色 → 内边缘效果 → 光照
优化结果:
圆角矩形大小:1000 x 500
机型:O3
| 优化版本\频点 | 222频点 | 443频点 | 734频点 | 832频点 | 补丁版本 |
|---|---|---|---|---|---|
| 基线 | 1200 us | 601 us | 370 us | 327 us | Patch 1 |
| 版本v0.2 | 849 us | 425 us | 257 us | 227 us | Patch 7 |
| 版本v0.3 | 740 us | 372 us | 225 us | 199 us | Patch 11 |
| 版本v0.4 | 640 us | 322 us | 196 us | 172 us | Patch 15 |
| 版本v0.5 | 669 us | 337 us | 204 us | 179 us | Patch 18 |
| 版本v0.6 | 333 us | 170 us | 107 us | 94 us | Patch 35 |
与Apple的耗时对比:
| MI | Apple | |
|---|---|---|
| 场景:控制中心 28个控件 | ![]() | ![]() |
| GPU耗时增加 | 0.48 ms | 3.47 ms |
二、锁屏可变字体玻璃方案
锁屏字体玻璃渲染流程:
白板流程图:锁屏字体玻璃渲染流程 上层输入可变字体的PATH → 字体SDF生成 → 根据SDF生成法线 → 容器截图(模糊半径46) → 容器截图(模糊半径500) → 玻璃shader上屏
与控件(圆角矩形)玻璃相比,锁屏字体玻璃难点更大,有以下几个方面的原因:
- 异形SDF的计算复杂度高
- Shader复杂,集成难点大
- 性能功耗挑战大,优化难度高
2.1 异形SDF的计算
锁屏字体是可变字体,由三阶贝塞尔曲线表示,在CPU上把曲线细分为线段:



然后上传GPU进行光栅化,共6个Draw Call: 1)Path Distance I;2)Path Interior I;3)Accumulator I;4)Path Distance II;5)Path Interior II;6)Accumulator II。前三个Draw Call和后三个Draw Call shader几乎一样,区别是反走样参数设置的不一样。详细算法原理参考《字体SDF生成算法原理》
白板流程图:SDF生成Draw Call流程 三阶贝塞尔曲线 →(CPU) 细分为线段上传GPU →(到轮廓到最小距离) path distance → path interior →(winding number计算) accumulator →(SDF) path distance II → path interior II → accumulator II
该算法是架构技术部竟分苹果的结果,我把metal实现用AI转成Vulkan实现,集成系统后,一个View的GPU耗时1.05ms,两个View GPU耗时2.1ms,很难满足落地需求。
我对算法进行了改造:
白板流程图:改造后的SDF渲染方案 View(任意大小) → CPU上转成线段表示 → GPU NDC坐标(x:[-1,1], y:[-1,1]) → 固定大小渲染(400x400) → 拉伸到和原始View同样大小
这样两个View,14个Draw Call的GPU耗时降低到0.5ms左右,用小分辨率的SDF不影响玻璃的渲染效果。

2.2 SDF渲染管线的集成
每个Draw Call都需要vertex和fragment shader,当前2D引擎SKIA对vertex shader支持的不好,需实现自定义OP来集成,集成难点大。

2.3 渲染线程优化方案
渲染线程在每帧render的时候都重新录制命令,导致cpu负载重。

主要的变量有:1)可变字体的三阶贝塞尔曲线每帧都变,每帧更新不可避免;2)每个shader的push constant数据和贝塞尔曲线相关,因此每帧都变;3)index/instance个数随贝塞尔线段的个数有关。第一个变量每帧都要更新不可避免,第二、第三个变量需要利用GPU Driven的计算来降低CPU的负载:

优化后CPU提交耗时由870us降低到68us。
优化前 / 优化后:


三、CPU指令与竞品对比
平均单帧优于荣耀70%。
| 场景 | 算力平均占比 | 算力峰值占比 | 平均单帧 | 最大单帧 |
|---|---|---|---|---|
| 新功能 下拉通知栏 | 2.9% | 16.2% | 10,587,100 | 46,657,524 |
| 线上 下拉通知栏 | 2.90% | 8.80% | 10,544,999 | 51,068,860 |
| 比线上多xx% | 0.4% | -8.6% | ||
| 新功能 亮灭屏 | 2.9% | 20.8% | 11,079,163 | 42,082,026 |
| 线上 亮灭屏 | 2.6% | 38.2% | 9,195,453 | 65,133,713 |
| 比线上多xx% | 20.8%(最新已优化到了15%) | -35% | ||
| 新功能锁屏堆叠滑动 | 3.9 | 27.30% | 12,229,127 | 45,620,406 |
| 荣耀gt pro 锁屏通知堆叠滑动 | 11.9% | 23.9% | 40,984,182 | 113,724,397 |
| -70% | -60% |
平均单帧差于线上20%
- 47% - 可变字体平均单帧增加0.7M负载,其中0.5M从字体库读取可变字体;
- 20% - 玻璃平均单帧增加0.3M负载,主要由数据更新及Draw Call提交导致的;
- 33% - 高级材质引起的__epoll_pwait,0.5M负载 —— 有优化方案,优化后只剩0.1M。
共计1.5M负载
四、功耗
时钟玻璃功耗增加65mA, 其中CPU增加5mA,GPU增加50mA,不超过40mA。
五、效果

六、下一步优化方向
SDF DC由6个减少为3个。

