[DOD Series][OGRE] Pitfalls and Design proposal for Ogre 2.0
這篇是OGRE 3D升級的過程中主程自己的感想和心得,從中我們也可以看出為什麼遊戲會有如此突出的DOD需求(對比Assasin Creed和OGRE),對此可以有一個更加直觀的感受。另外我們也可以看到一個老舊的Engine是如何一點點Profile並改進的心路歷程。
OGRE 2.0
太多cache miss
WinCE因為cache的原因比Linux差,最後優化提升了3x多
能找出什麼問題嗎
Branch misprediction。不知道為什麼作者會寫cache miss,如果考慮bound/FrustumPlane class的數據結構可能會有miss;Load Hit Store是一種打斷pipeline流水的操作,即存之後才能讀取,但是也不知道為什麼作者會寫LHS
Mike的CppCon分享過,DICE的這一篇也有不少分享
SCEE的文章也分享過了
根據是否updated狀態直接分開調用,保證每一幀只調一次
FPU的計算和branch不能一起work,使用fsel /fcmov來替換branch,具體可參考文獻5
一個if都沒了
對於float point的register用mask做位操作會引發性能問題
一點計算
過早的優化是不對的是不對的。。。
不信?
40個sample都在wait函數中,邏輯線程在等16ms的固定幀率時間,渲染線程的每幀時間不固定
全是transform,animation,frustum culling的熱點
不信服?
AC比OGRE多畫了3倍的物體,結果性能還比OGRE好2倍。。。
OGRE的渲染線程用了43ms,邏輯和物理其實只用8ms,渲染線程只更新所有可見物體的位置並調用renderOneFrame,主要原因在於cache miss和複雜的pass
如果返回true,OGRE則更新骨骼。mFrameBonesLastUpdated來記錄是否dirty。每個Entity每幀被調用3-6次。
這種方式不停的用flag標誌來判斷,會有什麼問題?
RenderScene會重複調用它多次:1)對於每個shadowmap;2)對於每個render pass;CSM用了3層,所以一共6次。沒有reuse數據,沒必要的重複access,大量沒必要的if(cache miss,
pipeline stall)Render pipeline腳本,10-90的物體會被cull traverse兩次
理想的整套管線
Update一次,cull list可以復用。明確每個component的角色。SIMD。最大程度線程化,read only能減少false sharing,同時處理多個pass。Compositor manager升級為調度器。HighLevelCull的實現,UpdateAnimations/Transforms可以有自己的多線程。
Shadow需要自己重新culling,如果不是並行畫兩個pass,可以復用上一個pass的結果
HLC不使用二叉樹結構,因為對多線程/cache不友好。Voxel grid更加容易並行,但是要注意false sharing
如果層級太深,對SIMD太不友好了(因為divergence,做過SIMD kd-tree traversal的含淚路過),所以最高層深2。HLC的主要目的是剔除不必要的animation和transforms更新,對接下來的精細culling做一個粗略的剔除。
最上層4*4*4的grid
下一層,3*3*3個grids,每個grid中2*2*1個nodes
每個cell指向一塊連續分配的數據,包含nodes和SOA的數據,最好根據render
queue ID分好組了。有興趣的可以看看[16]的文獻
UpdateAllAnimation,多線程去update,使用SOA。
目前使用了hashmap來儲存子節點,但是插入刪除的操作比遍歷少多了,遍歷使用廣度優先(cache friendly)
上一篇有詳細說過了,另外可以加入並行處理每層之間的數據(注意false sharing),使用SOA不光有助於cache,而且對SIMD更友好。
在更新完之後調用listener等函數會引發流水線stall(LHS,因為update會store數據,listener馬上需要read)。因為現在集中update了,所以可以集中來做listener的calling。
如何把SOA轉為AOS
這也是cache-friendly
每個象限都有自己的chunk,要預分配好空間,可以reallocate
每4個node組成一系列連續塊
方便SIMD處理,一次處理4個node
Base的偏移,index的偏移
Cacheline 64B,能放下4組xyzw
Matrix也是4個一組,4個cacheline;Quaternion
4個一組,1個cacheline每個階段的cacheline fetch
整套管線的tips
wvp在culling之前做會有很多物體不必要,但是culling之後做會有很多if。Bone的matrix也同理。
Low-level的culling method,如果cpu bound,啥都不做;取個平衡就做frustum culling;如果gpu bound,則用occlusion culling
Occlusion culling主要比較麻煩生成物體的內包圍盒,tx的occlusion中間件據說是miloyip大神寫的,效率超高;一般商業的是umbra,intel也有開源的occlusion
使用id代替name string,盡量使用static string。
Vertex Format
44B
CE3 20B(2B unused),JC2 24B(2B unused)
只用一般的容量裝了更多數據。。。
16bit float足夠給不太大mesh的pos,uv 16bit也夠了,normal
encode有必要,weight 4B足夠Text文件來定義layout
分兩個stream,semantic是input定義,output是layout的順序
TEXCOORD3的input會被轉換為ubyte4,輸出位TEXCOORD0
X:TEX2.x,YZ:TEX0.xy,W:TEX1.x,pack成short4
壓縮normal,定義normal的壓縮機制,w無意義
Stream1同理
定義vertex格式分stream的好處:更徹底的優化,對於不同平台可以提供一組預設規則,不需要美術干涉,只需要寫一個格式txt,可以給很多資源用
大世界問題
精度不夠
加上動作簡直不能忍,誰做誰知道
轉到相對相機坐標系,會帶來一堆if,不過大部分的分支預測都是對的,但是還是有瑕疵
在local space處理骨骼,最後乘wvp。省去了每個骨骼*world的運算,坐標系相對都是小數值精度足夠,另外在view space做光照。可以看SIGGRAPH的這篇文獻,講的更詳細一些,具體到float格式造成精度損失的原因和各種注意事項等。
Shader code divergence造成的register/cache pressure,之前我們也提到過,CBuffer的cache是有限的,對於同一的代碼最好不要有access divergence,否則會造成resource hazard,每個warp/wavefront跑不滿
使用TBuffer或者Texture,他們是用Tex單元來索引,雖然延遲更高,但是有一個L1 Cache彌補了這一點,另外推薦ALU/TEX代碼的interleave。推薦也使用VTF來做morph
GPU specific,老的GPU會慢一些
這一章節略過
關於OOD中的虛函數
討論起opensource造成的overload/modify的思維邏輯了,也是醉。。。stallman喜歡用virtual?
可以定義它需要有多flexibility
主要是需要rebuild,對於shipping不友好
另外,call once instead call multiple times
平台不同,virtual性能千差萬別,主要是console
嗯
RandomAscii大神的博客真心推薦。主要是64bit的bug需要運行一段時間才能出現,可以從高地址分配,直接崩,代碼就不貼了
OGRE2.0向我們展示了一個老舊的Engine如何垂死掙扎逐步進化,進而跟上新時代引擎的發展的。還是思想上需要轉變,認識到差距與不足,基於profile事實說話,逐點改進,另外,多向大神學習討論…貌似這點最重要(逃
推薦閱讀:
※《Exploring in UE4》Session與Onlinesubsystem[概念理解]
※[翻譯]DOOM(2016) - Graphic Study
※從零開始手敲次世代遊戲引擎(四十二)
※[GDC16] Optimizing the Graphics Pipeline with Compute
※[翻譯]Metal Gear Solid V – Graphics Study
TAG:遊戲引擎 |