標籤:

[DOD Series][OGRE] Pitfalls and Design proposal for Ogre 2.0

Pitfalls and Design proposal for Ogre 2.0?

forums.ogre3d.org

這篇是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:遊戲引擎 |