現象
總體而言,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做監控。