现象
总体而言,iOS 14 渲染性能变差,可以从以下三个测试看出。
测试1:
简单demo,使用egret引擎显示3000个图(都是同一个100*100 png 纹理),逐帧做旋转。(博客园视频播放可能有问题,视频地址:https://github.com/kenkozheng/kenkozheng.github.com/blob/master/WebGL/ios14/video/1.mp4?raw=true)
视频中,黑色机器是iOS14.0,白色是iOS13.7,都是iphone 7plus。
虽然从视频中来看,iOS 14的fps还要高一些,但实际上14明显卡顿。原因是:Egret检测的fps是web层面通过requestAnimationFrame得到的,实际上和画面渲染没有严格对等关系。
改为通过perfDog,从native层面看帧频,看到iOS14只有13fps,而旧版本有40+,这也解释了为什么肉眼看起来14的渲染要更卡顿。
测试2:
复杂demo,使用egret引擎显示candy爆炸龙骨动画100个。(博客园视频播放可能有问题,视频地址:https://github.com/kenkozheng/kenkozheng.github.com/blob/master/WebGL/ios14/video/2.mp4?raw=true)
和上边测试1类似,egret左上角的fps显示并不准确,通过perfDog检测,实际帧频只有7fps左右。
测试3:
在复杂demo基础上(还是100个爆炸动画),修改egret代码,禁用颜色混合shader,所有元素渲染都统一使用普通shader。
由于龙骨设定为24fps,而实际fps有40,从视频中肉眼无法看出卡顿。所以这里视频省去。
也是类似的情况,iOS14比iOS13渲染fps低,iOS14只有8fps左右,而iOS13有40+fps。
测试4:
使用自研的简单webgl引擎(min2d),显示15000个100*100的png。(博客园视频播放可能有问题,视频地址:https://github.com/kenkozheng/kenkozheng.github.com/blob/master/WebGL/ios14/video/3.mp4?raw=true)
情况和egret引擎类似,还是iOS14明显卡顿,虽然界面显示fps较大,但实际帧率只有10fps左右。题外话:自研引擎性能略比egret好10%左右,但上边测试中能支持15000个图片,只是因为自研引擎没有做像素密度加倍尺寸渲染。
由此可见,iOS14 webgl性能确实比iOS13有明显下降。
分析
从egret的监控来看,js层面的耗时(包括顶点计算、调用webgl)都没有明显问题,iOS14比iOS13甚至还有一些优化。但实际渲染帧频,iOS14又明显比iOS13更低,问题应该出于safari内部对webgl接口的具体实现上有一些改变。
调试过程中,发现两个比较奇怪的现象:
1、本来满帧运行,但运行一段时间后,会下降到40-50fps。
2、50个爆炸动画播放时能稳定在50fps,但增加到60个爆炸动画之后,fps会断崖式下跌,到14fps左右。
从这个现象,推测内部实现在显存管理上,可能出了较大变化,可能有一些缓存,数据达到阈值后,可能有反复的数据交换。
首先,排查了几个方面:
1、drawCall推送vertex buffer的方式(webgl.bufferData vs webgl.bufferSubData)
egret每次drawCall使用webgl.bufferData推送顶点数据,这个方法每次推送覆盖整个bufferData;而webgl.bufferSubData可以指定offset,只覆盖部分数据。
但由于每次修改的数据量并不多,两者从理论和实际测试来看,都没有区别。
2、推送纹理、webgl初始化设定(抗锯齿等)、frameBuffer
上述方面,egret的设置都属于通用做法,并没有特殊,而且调整了参数后,性能并没有提升。
3、去除shader的alpha计算
也没有明显变化
4、去除blendMode处理
虽然有明显的性能提升,但在iOS14上的性能提升并不比iOS13上的提升更大,blendMode并不是iOS14变慢的主要因素。
而且BlendMode是游戏素材制作的必需选项,影响到透明叠加效果,无法简单去除。
上述几个方面都没有找到解决方式。
另外,另外的游戏引擎cocos creator,官方提出在cocos引擎中使用了多次drawCall共享vertex buffer和index buffer的优化技术(也是常规的优化手段),但在iOS14中反而变成了性能瓶颈,已针对做了处理(针对iOS14,每次drawCall使用不同的vertex buffer)。
参考:
https://forum.cocos.org/t/ios-14-web/97808
https://github.com/cocos-creator/engine/pull/7415/files#
分析egret的实现,设置了默认每次drawcall最多同时批处理2048个元素,对于一般sprite来说,一个元素就等于一个图,等于一个长方形,等于两个三角形,也等于六个顶点。
进一步,egret在初始化时,会创建一个12288个unsigned short组成的index buffer,然后bindBuffer到GPU。这个过程一般只执行一次,后续不会再绑定,也不会再创建新的buffer(网格拉伸情况除外,会换一个indexbuffer数据内容)。
那么,每次drawcall时,无论是多少个元素,哪怕只有1个元素(6个顶点)都会使用这个12288长度的index buffer。
从这个角度来看,确实可能存在优化的可能。
引擎改进
从现象和分析过程得出,iOS14确实有性能下降,我们可以从一些维度,尽可能挽回一些性能下降。
目前确认可以从引擎层面改进的是index buffer问题。
改进的策略是:判断是否iOS14,如果是,就在每个drawcall前,推送新的index buffer和vertex buffer数据,这些数据只包括本次渲染所需,没有多余数据。
具体改动:
WebGLRenderContext的$drawWebGL方法中,判断是否Mesh绘制,在非Mesh绘制情况下,切分vao中的indices array和vertices array,取出本次drawcall所需部分,上传给gpu。而且,在这个情况下,drawData要忽略offset,改为固定的0(offset是对应vertex buffer中包含多次drawcall数据时才使用,现在每次按需推送,所以就不需要offset了)。
同样渲染50个爆炸龙骨动画,修改后的版本性能有明显提升。 如下图,左侧1分钟是原有版本下绘制50个爆炸龙骨动画的fps情况,右侧是优化后版本的fps情况。原有版本平均10fps,而优化后平均20fps(这个帧率可能和前边测试demo略有差异,是由于iphone渲染一段时间后变慢有关)。
index buffer的使用调整,确实能解决上述爆炸龙骨动画在iOS14的性能问题。
另外,排查过程中,还发现一些值得探索的方向:
1、带filter和不带filter的图元,如何批处理。egret引擎原来没有做批处理。如果能实现这个优化,将极大减少龙骨的渲染drawCall。
2、调整像素密度绘制策略。egret引擎默认以屏幕像素密度作为倍数绘制webgl画布,但游戏素材并没有这么大,这个扩大渲染对性能有影响,但视觉效果没有提升。
第2点,尤其可以针对低端机型,例如系统版本在6.x或以下的android,这部分机型本来性能就较差,但还可能按2-3倍像素去绘制webgl,渲染帧率就更低。
例如,oppo r9 系统版本5.1,屏幕像素密度3,强制以密度1绘制,性能能够提升30%。
除上述提到的方向外,针对iOS14,可能还存在更多针对性优化的方向,但还需要针对具体的场景,逐个分析。
性能结论
iOS14对比iOS13和以前版本,在webgl渲染性能上有明显下降,尤其在drawcall次数较大、渲染面积较大或使用较多颜色混合滤镜情况下,下降尤其明显。
针对iOS14,虽然能在一些方面改善性能,但单纯从js角度,无法让webgl渲染性能恢复到iOS13的水平,只能寄望于苹果官方自行修复底层问题(已有不少反馈到苹果论坛)。
素材开发建议
除了从引擎底层解决iOS14卡顿问题,另外,针对游戏业务素材,还可以做一些改动,提高渲染性能:
1、减少龙骨动画层级,减少图元个数;
2、避免使用颜色混合和BlendMode(混合模式);
3、避免使用有大面积透明区域的图片,可以把图片切分为只有有效内容的多个小图。
另外,iOS14在js层面监控到的帧频不是真正的webgl渲染帧频,性能优化需要直接连接perfDog做监控。