最近在做項(xiàng)目的優(yōu)化工作治唤,測試發(fā)現(xiàn)UI的開銷占到了一半以上棒动,所以先從他下手。
源碼分析
NGUI有幾個重要的類宾添,UIPanel船惨,UIWidget柜裸,UIDrawCall。其中UIPanel用一個靜態(tài)鏈表保存游戲里的所有UIPanel粱锐,每個UIPanel在列表里的順序是由depth決定的疙挺,注意這里是靜態(tài)鏈表,并沒有父子關(guān)系怜浅。
public int depth
{
get
{
return mDepth;
}
set
{
if (mDepth != value)
{
mDepth = value;
#if UNITY_EDITOR
NGUITools.SetDirty(this);
#endif
list.Sort(CompareFunc);
}
}
}
每一個UIPanel用一個list保存所有的UIWidget, 順序是先按照depth排铐然,depth相同再按照材質(zhì)排
public void SortWidgets ()
{
mSortWidgets = false;
widgets.Sort(UIWidget.PanelCompareFunc);
}
static public int PanelCompareFunc (UIWidget left, UIWidget right)
{
if (left.mDepth < right.mDepth) return -1;
if (left.mDepth > right.mDepth) return 1;
Material leftMat = left.material;
Material rightMat = right.material;
if (leftMat == rightMat) return 0;
if (leftMat != null) return -1;
if (rightMat != null) return 1;
return (leftMat.GetInstanceID() < rightMat.GetInstanceID()) ? -1 : 1;
}
每個UIPanel管理自己的Drawcall
public List<UIDrawCall> drawCalls = new List<UIDrawCall>();
大概流程就是每一幀每個UIPanel去更新所有屬于他的UIWidget,什么算屬于恶座,其實(shí)就是每個UIWidget去他的父節(jié)點(diǎn)里一直向上找搀暑,第一個找到的就是它屬于的UIPanel
static public UIPanel Find (Transform trans, bool createIfMissing, int layer)
{
UIPanel panel = null;
while (panel == null && trans != null)
{
panel = trans.GetComponent<UIPanel>();
if (panel != null) return panel;
if (trans.parent == null) break;
trans = trans.parent;
}
return createIfMissing ? NGUITools.CreateUI(trans, false, layer) : null;
}
更新UIWidget的過程, 就是準(zhǔn)備UIDrawCall的過程,先去現(xiàn)有的DrawCall里面去找跨琳,如果找到材質(zhì)相同险掀,并且貼圖相同,shader也相同的就認(rèn)為可以放到一個DrawCall里湾宙,否則的話,就只能重建當(dāng)前這個UIPanel的所有DrawCall
public UIDrawCall FindDrawCall (UIWidget w)
{
Material mat = w.material;
Texture tex = w.mainTexture;
int depth = w.depth;
for (int i = 0; i < drawCalls.Count; ++i)
{
UIDrawCall dc = drawCalls[i];
int dcStart = (i == 0) ? int.MinValue : drawCalls[i - 1].depthEnd + 1;
int dcEnd = (i + 1 == drawCalls.Count) ? int.MaxValue : drawCalls[i + 1].depthStart - 1;
if (dcStart <= depth && dcEnd >= depth)
{
if (dc.baseMaterial == mat && dc.mainTexture == tex && w.shader == dc.shader)
{
if (w.isVisible)
{
w.drawCall = dc;
if (w.hasVertices) dc.isDirty = true;
return dc;
}
}
else mRebuild = true;
return null;
}
}
mRebuild = true;
return null;
}
void UpdateSelf ()
{
mUpdateTime = RealTime.time;
UpdateTransformMatrix();
UpdateLayers();
UpdateWidgets();
if (mRebuild)
{
mRebuild = false;
FillAllDrawCalls();
}
else
{
......
優(yōu)化策略
UIPanel的數(shù)量一定要盡量少冈绊,UIPanel的數(shù)量一定要盡量少侠鳄,UIPanel的數(shù)量一定要盡量少!
這個主要是因?yàn)閁IPanel之間的DrawCall是不會合并的,所以如果濫用UIPanel死宣,DrawCall是永遠(yuǎn)不會降下來的-
同一個UIPanel下面的UIWidget的depth一定要有一個好的規(guī)劃
因?yàn)閁IWidget的順序是先depth伟恶,然后再材質(zhì),所以我們要盡量把同一個材質(zhì)貼圖的都放在同一個depth段里毅该。這里以游戲中的主界面為例:
因?yàn)橹鹘缑媸谴蠖鄶?shù)時候停留的界面博秫,所以最值得優(yōu)化,建議主界面下如果沒有特殊需求的話眶掌,建議把所有靜態(tài)的東西都放在一個UIPanel下挡育。假設(shè)用到的shader都是半透明混合的shader,大部分的UI元素放到了一個公共的圖集里common朴爬,其他的有atlas1即寒,atlas2等等,還有文字用到的貼圖font1召噩,font2母赵,主界面盡量不要出現(xiàn)文字在貼圖下面的情況,這樣由源碼分析可知具滴,我們要盡量把同一個atlas的放在一起凹嘲,這里主要圍繞著common來,因?yàn)槠渌馁N圖很可能根據(jù)游戲邏輯變化构韵,depth范圍如下- common 1-20
- 其他atlas 21-40
- common 41-60
- 其他atlas 61-80
- common 81-100
這樣設(shè)置了以后周蹭,是可以保證common圖集的都合并到一個drawcall里的趋艘。
-
關(guān)于什么時候會重建DrawCall
重建主要有這么幾種情況- UIWidget第一次更新的時候,沒有找到對應(yīng)的DrawCall
- UIPanel OnEnable的時候
- UIPanel OnInit的時候谷醉,即Start的時候
- 手動調(diào)用Refresh的時候 (盡量不要手動調(diào)用這個接口)
- UIWidget從UIPanel上移除致稀,并且是DrawCall depth開始或者結(jié)束的時候
public void RemoveWidget (UIWidget w) { if (widgets.Remove(w) && w.drawCall != null) { int depth = w.depth; if (depth == w.drawCall.depthStart || depth == w.drawCall.depthEnd) mRebuild = true; w.drawCall.isDirty = true; w.drawCall = null; } }
盡量減少重建,每一幀都重建是不能忍的俱尼。所以原則就是盡量不要改變同一個UIPanel下面的UIWidget的列表抖单,增加,刪除遇八,改順序都會重建DrawCall
-
關(guān)于什么時候會更新DrawCall,也就是DrawCall的Dirty
- 后加入的UIWidget找到之前的同樣材質(zhì)的DrawCall的時候
- UIWidget從以前的DrawCall中移除的時候
- 手動SetDirty的時候
- UIWidget UpdateGeometry的時候
其中主要是減少第4種情況矛绘,這里只要UIWidget的transform發(fā)生變化,或者color發(fā)生變化刃永,都會重新填充當(dāng)前的DrawCall
所以優(yōu)化策略是把每幀都發(fā)生變化的UIWidget放在單獨(dú)的DrawCall里货矮,或者單獨(dú)的UIPanel,就是所謂的動靜分離
to be continued
專欄文章全都轉(zhuǎn)移到知乎專欄了斯够,歡迎關(guān)注囚玫,交流。
知乎專欄-程序員的自我修養(yǎng)