不知當初是在那看到,說是Ogre2.0浪費了一個版本號,當時也沒多想,覺得沒多大更新,一直到如今想作一個編輯器時,突然想到要看下最新版本的更新,不看不知道,一看嚇一跳,因此說,網絡上的話少信,你不認識別人,別人張嘴就來,對別人也沒損失,還能夠裝B下,靠.html
從如今Ogre2.1的代碼來看,大約總結下,更新包含去掉過多的設計模式,SoA的數據結構(用於SIMD,DOD),新的線程模式,新的渲染流程與場景更新,新的材質管理系統,新的模型格式,新的合成器方案,更新是全方面的,能夠說,Ogre2.x與Ogre1.x徹底不是同一個引擎,無論是效率,仍是從渲染新思路的使用上. node
大致上參照二份主要文檔,一份是OGRE.2.0.Proposal.Slides.odp,如今Ogre的維護者之一dark_sylinc比對其餘的引擎以及相關測試寫的Ogre2.0要修改的方向,一是Ogre 2.0 Porting Manual DRAFT.odt,移植手冊,簡單來講,Ogr2.0具體的修改位置與說明.很是有價值的二份文檔,能夠說,這是全新Ogre改動的精華,咱們從這二份文檔裏,能學到如何針對C++遊戲引擎級別的包含效率,可用性的重構.這是一個幸運的學習經歷. 算法
從https://bitbucket.org/sinbad/ogre下載最新版本,裏面的DOC文件夾,有多份文檔,我整理了下,每部分包含改動緣由,改動位置,相關代碼來講,由於全是英文文檔,因此若是理解有錯誤,歡迎你們指出,不但願誤導了你們.本文只針對新模型新功能,也就是加了v1命名空間的(Ogre1.x中的功能,有對應Ogre2.x版本),本文不會特別說明.編程
看看做者的幻燈片,哈哈,圖片特別形象生動. 設計模式
這個是函數是判斷模型是否在當前攝像機可見,若是可見,加入渲染通道.不過你去看如今的Ogre2.1的代碼,這個方法沒有變,不是由於沒有改,是由於渲染流程變了,這個函數的功能被MovableObject::cullFrustum包含了,其中判斷攝像機與模型AABB相交的算法也換了. 緩存
這個函數由於每楨都對每一個模型來計算,若是Cache misses,損失有點大. 安全
一樣這個通常用來獲得模型的世界座標位置,也是每楨每一個模型要計算的,若是Cache miss,同上. 網絡
那麼如何改進,像上面,你不要那些判斷,要麼多計算,要麼結果不對,做者給出的答案就是改進渲染流程,減小判斷條件的出現.後面會細說. 數據結構
能夠看到場景每次更新都在重複,檢查是否須要更新,而後更新.不少沒必要要的變量和是否更新狀態的跟蹤,以及太多的判斷,分別形成cache misses緩存不友好.(我去,if判斷有這麼大的破壞力?仍是隻是引擎級別的代碼纔會形成這樣的影響,後面渲染流程中,原來不少if都去掉了). 多線程
而後指出Ogre的渲染流程中,其中SceneManager::_renderScene()調用太屢次,如Shadow Map一次,合成器中的render_scene一次,而後他們尚未重複使用剔除的數據,每次renderScene,都從新剔除了一次.特別是合成器中屢次調用render_scene,每次都會把渲染隊列裏的模型所有檢查剔除一次,這是無效的操做.
綜合這二點,渲染隊隊確定要大改,以下是做者綜合別的商業渲染引擎,給出的在Ogre2.0中新的實現建議,根據如今Ogre2.1我所看到的代碼,已經實現以下圖的功能.
這個圖後面會簡單說下其中的線程相關部分,這就是Ogre2.x的渲染流程了,從圖中,咱們能夠看到新的合成器是Ogre核心中的一部分,已經不是可選組件了,固然新的合成器也有至關大的更新,功能更強大,更好用.其中更詳細的部分,後面會專門寫一篇介紹Ogre2.x新的渲染流程與合成器.
在看以下內容時,先介紹一下什麼是基於DOD的設計.DOD(面向數據設計),以及咱們面向對象OOP經常使用的OOD(面向對象設計)
DOD與OOD之爭: Data oriented design vs Object oriented design
Data-Oriented Design Data-Oriented Design 二 什麼是DOD,爲何要使用DOD,什麼狀況下用DOD
[譯]基於數據的設計(Data-oriented design) 這是CSDN上針對第一篇的翻譯
有興趣你們仔細讀下,這裏總結下DOD相對OOP的優點.簡潔高效的並行化,緩存友好.
先看以下 http://stackoverflow.com/questions/12141626/data-oriented-design-in-oop 中提出的一個問題,二代碼以下:
//DOD void updateAims(float* aimDir, const AimingData* aim, vec3 target, uint count) { for(uint i = 0; i < count; i++) { aimDir[i] = dot3(aim->positions[i], target) * aim->mod[i]; } } //OOP class Bot { vec3 position; float mod; float aimDir; void UpdateAim(vec3 target) { aimDir = dot3(position, target) * mod; } }; void updateBots(Bots* pBots, uint count, vec3 target) { for(uint i = 0; i < count; i++) pBots[i]->UpdateAim(target); } };
下面有人解釋爲何第一段代碼要高效,在第二段代碼中,每次獲得一個結構域,浪費更多帶寬,以及更新無用數據到緩存中,緩存Miss高.第一種一次取一個float塊,提升緩存有效利用.
如在遊戲中最多見的操做,取得每一個模型的MVP矩陣,而OOP告訴咱們,要取的位置,先要取得模型.模型還包含許多其它的內容,可是是無用的,佔用緩存空間,緩存命中變低.而DOD是把全部的字段存放在一塊兒,一下取的全部位置,請看下面SoA.
下面再次提出二個概念,一個是SoA(Structure of Arrays,非你百度搜出來的SOA),一個是AoS(Arrays of Structure),暫時先說下,SoA是一種DOD裏經常使用的數據組織方式,對應OOP裏經常使用的AoS組織方法.
簡單說下,SoA的組織方式,是把一組元素的每一個字段連續保存,以下是我針對Ogre2.x裏的代碼改寫的.
struct Vector3 { float x = 0; float y = 0; float z = 0; Vector3() { } Vector3(float nx, float ny, float nz) { x = nx; y = ny; z = nz; } float& operator [] (const size_t i) { assert(i >= 0 && i < 3); return *(&x + i); } }; struct Quaternion { float x; float y; float z; float w; }; //OOD 面向對象設計 struct Transform { Vector3 pos; Vector3 scale; Quaternion orient; void move(Vector3 move) { for (int i = 0; i < 3; i++) { pos[i] += move[i]; } } }; //SIMD struct ArrayVector3 { float x[4]; float y[4]; float z[4]; ArrayVector3() { memset(x, 0, sizeof(float)* 4); memset(y, 0, sizeof(float)* 4); memset(z, 0, sizeof(float)* 4); } Vector3 getIndex(int index) { assert(index >= 0 && index < 4); return Vector3(x[index], y[index], z[index]); } void setIndex(int index, float fx, float fy, float fz) { assert(index >= 0 && index < 4); x[index] = fx; y[index] = fy; z[index] = fz; } }; struct ArrayQuaternion { float x[4]; float y[4]; float z[4]; float w[4]; }; //SoA(Structure of Arrays) struct ArrayTransformSoA { ArrayVector3* pos; ArrayVector3* scale; ArrayQuaternion* orient; int mIndex = 0; ArrayTransformSoA() { pos = new ArrayVector3(); scale = new ArrayVector3(); orient = new ArrayQuaternion(); } ~ArrayTransformSoA() { delete pos; delete scale; delete orient; } void move(Vector3 move) { //xxxxyyyyzzzz float *soa = reinterpret_cast<float*>(pos); for (int i = 0; i < 3; i++) { for (int j = 0; j < 4; j++) { soa[i * 4 + j] += move[i]; } } } void setPos(float x, float y, float z) { pos->setIndex(mIndex, x, y, z); } Vector3 getPos() { return pos->getIndex(mIndex); } }; void SoAVAoS() { //AoS(Arrays of Structure) Transform aosArray[4]; ArrayTransformSoA soaArray; Vector3 moveAdd(4.0f, 2.0f, 1.0f); for (int i = 0; i < 4; i++) { aosArray[i].move(moveAdd); } soaArray.move(moveAdd); for (int i = 0; i < 4; i++) { cout << aosArray[i].pos.x << endl; soaArray.mIndex = i; cout << soaArray.getPos().x << endl; } cout << "" << endl; }
下面的ArrayVector3和ArrayTransformSoA就是SoA相關組織方式與操做,這裏上面註釋寫的是SIMD,由於這個組織方式確實是用於SIMD的,SIMD在這很少說,若是有機會,專門研究這個後再來詳細說明,在這,咱們只須要知道SSM2能夠每下處理128位數據,在32位下,每下處理4個float數據,如上面的ArrayTransformSoA中的move方法中,第二個循環中對於使用SSM2指令來講,就是一個指令,簡單來講速度提升4倍,能夠說是一種最簡單安全的並行處理,不要你來設線程,關心同步啥的.更具體點的說,在遊戲中一下能夠處理四個頂點進行操做,如移動,縮放,以及矩陣運算(固然這四個頂點也有限制,並非全部都放一塊兒,請看後面).同時如上圖所示,這也是緩存友好的.
上面演示了常見SoA結構方法,能夠看到,對於對象來講,他的存放再也不是連續的了,如點的位置y和x相關了4*sizeof(float)個距離,在面向對象結構中,他們應該是相鄰的.可是對於SoA來講,對象列表中的每一個字段是相鄰的,如上圖所示應該是XXXXYYYYZZZZ這種內存佈局方式,而不是XYZXYZXYZXYZ這種.前面說過,DOD也經常使用SoA結構,那這是否是就是Ogre中的DOD核心設計,不算,由於這種四個一組的只是專門爲了SIMD的SoA結構,真正的DOD核心應該Ogre中的ArrayMemoryManager類,直接拖出這個方法可能看不明白,以下是我針對Ogre2.x中的ArrayMemoryManager改寫的,只保留核心幫助你們理解.
//DOD 面向數據設計 class ArrayTransformManager { private: enum ElementType { Pos, Scale, Orient, ElementCount }; int elements[ElementCount]; vector<char*> memoryPool; int totalSize = 0; int maxMemory = 32; //當前 int nextSlot = 0; public: ArrayTransformManager() { elements[Pos] = 3 * sizeof(float); totalSize += elements[Pos]; elements[Scale] = 3 * sizeof(float); totalSize += elements[Scale]; elements[Orient] = 4 * sizeof(float); totalSize += elements[Orient]; memoryPool.resize(ElementCount); } void initialize() { for (int i = 0; i < ElementCount; i++) { int byteCount = elements[0] * maxMemory; memoryPool[i] = new char[byteCount]; memset(memoryPool[i], 0, byteCount); } } void createArrayTransform(ArrayTransformSoA &outTransform) { int current = nextSlot++; //current = 0,nextSlotIdx = 0,nextSlotBase = 0 //current = 3,nextSlotIdx = 3,nextSlotBase = 0 //current = 4,nextSlotIdx = 0,nextSlotBase = 4 //current = 5,nextSlotIdx = 1,nextSlotBase = 4 //current = 7,nextSlotIdx = 3,nextSlotBase = 4 //current = 8,nextSlotIdx = 0,nextSlotBase = 8 int nextSlotIdx = current % 4; int nextSlotBase = current - nextSlotIdx; outTransform.mIndex = nextSlotIdx; outTransform.pos = reinterpret_cast<ArrayVector3*>( memoryPool[Pos] + nextSlotBase*elements[Pos]); outTransform.scale = reinterpret_cast<ArrayVector3*>( memoryPool[Scale] + nextSlotBase*elements[Scale]); outTransform.orient = reinterpret_cast<ArrayQuaternion*>( memoryPool[Orient] + nextSlotBase*elements[Orient]); outTransform.setPos(nextSlotIdx, nextSlotIdx, nextSlotIdx); } }; void TestDOD() { ArrayTransformManager transformDOD; transformDOD.initialize(); ArrayTransformSoA transform0; transformDOD.createArrayTransform(transform0); ArrayTransformSoA transform1; transformDOD.createArrayTransform(transform1); ArrayTransformSoA transform2; transformDOD.createArrayTransform(transform2); ArrayTransformSoA transform3; transformDOD.createArrayTransform(transform3); ArrayTransformSoA transform4; transformDOD.createArrayTransform(transform4); cout << transform0.getPos().x << endl; cout << transform1.getPos().x << endl; cout << transform2.getPos().x << endl; cout << transform3.getPos().x << endl; cout << transform4.getPos().x << endl; }
這個是結合了SMID的DOD設計,不看SMID部分,就看初始化部分,maxMemory表示最多存入多少個Transform,而elements表示Transform對應每一個字段佔多少位,memoryPool表示每一個字段(連續的)在一塊兒佔多少個字段,其中調用createArrayTransform生成一個ArrayTransformSoA數據,每四個連續的ArrayTransformSoA的裏的如Pos,Scale等地址同樣,如上面那種圖,不一樣的是對應mIndex,用於指明是當前在SoA中的索引.其實對比上面的ArrayVector3來看,組織數據應該算是同樣的,不一樣之處是一下並排放maxMemory個數據,而ArrayVector3一下放四個vector3數據.總的來講,這就是SoA數據結構,分別用於SIMD與DOD.
幻燈片文檔第一點與第二點主要包含二點,一是渲染流程(後文細說),二是SMID,DOD等基本數據格式與操做的改變.後面關於頂點格式與着色器先暫時不說了,你們有興趣能夠看下,對應代碼還沒查到,不知是否已經完成.
最後結束,還不忘指出Ogre中的設計模式被過分使用,說明OOD的多態太浪費,而且用宏來控制一些virtual_l0 ,virtual_l1 ,virtual_l2 來控制多態級別,默認已經不啓用虛函數,如SceneNode::getPosition(),SceneNode::setPosition默認不能重載,若是定義了SceneNode的子類,並重載瞭如上函數,你須要本身設定多態級別,並本身編譯.嗯,Ogre1.x最出名的多設計模式使用也隨着渲染流程的改變而去掉不少.相信你們看到相關更新後,都會說這改的太大了吧.
"We don't care really how long it takes" Ogre採用OOD帶來的好處,在客戶級別上的.
1.Ogre2.0後,Ogre不少對象去掉name,改用IdObejct,這個更多意義就是不少原來的聚合關係都是用的map來表示,name當key,如今爲IdObejct,且爲自動生成,因此相關id意思不大,相關聚合關係用的是Ogre開發人員本身寫的一個輕量級仿vector的類FastArray.
The Sorted Vector pattern Part I
The Sorted Vector pattern Part II
由於咱們場景中,更多如更新全部模型的位置,AABB等,能快速迭代是咱們最大的要求,而且vector更節省空間,內存塊連續(AoS,SMID,DOD).相反,map的優點如快速查找,隨機刪除與添加並不經常使用.因此像Ogre1.x中不少map的用法並不明智.
其中,Ogre內部更多聚合關係使用的是FastArray,FastArray是針對std::vector的輕量級實現,去掉了其中的大量邊界檢查與迭代器驗證.沿用大部分std::vector的功能,如std::for_each正常工做,也有同標準不同的,如FastArray<int> myArray(5),不會自動初始化這裏面的5個數據是0.註釋中默認不建議咱們用,由於如前面所說,和標準std::vector不一樣,這個類主打效率,針對邊界檢查與相關驗證所有去掉,除非咱們知道咱們應該怎麼用.
2.如何查看如MovableObject與Node裏的數據.
認真看過前面SoA部分的,這部分都不用細說了,Node裏的位置信息用Transform保存,MovableObject的信息用ObjectData保存,對應的信息是四個一組用於SIMD指令加速,因此一個Node對應的Transform其實有4個Transform信息,這4個Transform位置在內存中數據以下XXXXYYYYZZZZ,根據Transform的mIndex(0,4]找出對應數據.
避免SIMD不能正常計算,以及大量的非空判斷,Transform中若是隻有三個node,最後一個node不設爲null,爲虛擬指針代替,這也是DOD經常使用的一個方法.
3.在Ogre1.x中,MovableObject只有附加到SceneNode後,纔算是在場景中,而Ogre2.x就沒有這個概念了,Node只算是MovableObject用來操做與保存相關位置信息.其實提及來,應該是和渲染流程的改變有變,原來的渲染流程中,經過Node一級一級查找下去全部的MoveableObject是否在視截體範圍內,而如今生成一個MovableObject後,在對應的ObjectMemoryManager(同前面所講,分配SoA結構) 保留指針,而在場景進行剔除時,根據ObjectMemoryManager來的,因此說MovableObject一直在場景中.可是Node保留位置信息,沒有位置信號,同樣不能在場景中渲染.
在Attaching/Detaching操做後,會自動調用對應setVisible,Attaching後,自動設visible爲true,也能夠設爲flase,Detaching後,自動設visible爲false,若是手動設true,會出現錯誤.
若是你Attaching一個SceneNode後,你再次Attaching另外一個SceneNode時,須要先Detaching.不然斷言錯誤.
4.全部的MovableObject都須要SceneNode,包含燈光與攝像機,全部的都須要附加到SceneNode中才行,很簡單,原來如Light與Camera與通常的MovableObject有些區別,一是不渲染本身,二是有本身的位置信息,可是如今SceneNode不用於渲染通道,只是保存位置信息,天然和通常的MovableObject同樣用SceneNode來保存位置信息了,燈光與攝像機也都必須附加到SceneNode上纔有位置信息.
5. 改變Node的局部座標位置,並不能立刻獲得對應的全局位置.不一樣於Ogre1.x版本,如setPosition會設置一個flag表示父節點要更新,而調用getDerivedPosition後,檢查到flag就去更新父節點了.這是一個不友好的Cache設計.在Ogre2.x中,去掉了上面的一些flag,更新不會更新父節點,全部節點的更新都在每楨中的updateAllTransforms,就是說若是你setPosition後,你須要當前楨運行以後(調用updateAllTransforms)後才能獲得getDerivedPosition的正確值.固然若是你必定如今要,能夠用getDerivedPositionUpdated,固然這就是走老路了,若是可能,請更新你的設計.同時原Ogre1.x中的getDerivedPosition在Ogre2.x分紅了二個方法,也去掉了if判斷.
6.Node和MovableObject區分紅動態與靜態,靜態就是告訴Ogre,我不會每楨去更新位置.這樣Ogre能作的優化一是節省CPU,不用每楨去更新相應位置和相應AABB,二是告訴GPU,某些模型能夠合併批次渲染.其中動態模型只能附加到動態節點上,靜態模型只能附加到靜態模型上.動態節點能夠包含靜態子節點,而靜態節點不能夠包含動態子節點(根節點除外),緣由很簡單,靜態節點不常更新,你放個動態子節點在我裏面,是讓我更新了仍是不更新了.
7.在Ogre2.0之前,咱們知道最後用於渲染的只是Renderable與Pass,其中在場景可見性檢查模型時, MovableObject把當下的全部Renderable加入RenderQueue,用戶能夠不經過MovableObject也能把Renderable加入RenderQueue,在Ogre2.1後,必需把Renderable與對應的MovableObject一塊兒加入RenderQueue,由於新的渲染系統中的渲染模型,渲染中要求的Lod等級,骨骼動畫,MVP矩陣等都直接保存在MovableObject.就如Ogre2.1之後MVP矩陣是直接保存在對應MovableObject中,再也不是經過Renderable的getWorldTransforms獲取.詳細請查看相關QueuedRenderable引用相關信息.
此外去掉原Ogre2.0在場景可見性檢查時要獲得是否能夠接收陰影用到的訪問者模式,讓修改的人來講,這個模式花費太大,不值得.固然Ogre2.0之後的陰影相關所有改變,只支持shadow map,以及shadow map不少變種技術.模版陰影去掉支持,能夠看到MovableObject再也不是ShadowCaster的子類,這個類包裝模版陰影相關.
再一個就是新模型和VAO的引進,原來的VBO的類也改爲相應VaoManager.具體後文詳細分析.
SIMD與DOD設計前面有說,在這裏,只是簡單說幾個類.所在文件都在OgreMain/Math/Array/SSE2下.
以下這段代碼是前面我說的針對Ogre2.x中抽取的,主要是根據相關Ogre中SIMD與DOD設計中改寫的,幫助理解.
//SIMD struct ArrayVector3 { float x[4]; float y[4]; float z[4]; ArrayVector3() { memset(x, 0, sizeof(float)* 4); memset(y, 0, sizeof(float)* 4); memset(z, 0, sizeof(float)* 4); } Vector3 getIndex(int index) { assert(index >= 0 && index < 4); return Vector3(x[index], y[index], z[index]); } void setIndex(int index, float fx, float fy, float fz) { assert(index >= 0 && index < 4); x[index] = fx; y[index] = fy; z[index] = fz; } }; struct ArrayQuaternion { float x[4]; float y[4]; float z[4]; float w[4]; }; //SoA(Structure of Arrays) struct ArrayTransformSoA { ArrayVector3* pos; ArrayVector3* scale; ArrayQuaternion* orient; int mIndex = 0; ArrayTransformSoA() { pos = new ArrayVector3(); scale = new ArrayVector3(); orient = new ArrayQuaternion(); } ~ArrayTransformSoA() { delete pos; delete scale; delete orient; } void move(Vector3 move) { //xxxxyyyyzzzz float *soa = reinterpret_cast<float*>(pos); for (int i = 0; i < 3; i++) { for (int j = 0; j < 4; j++) { soa[i * 4 + j] += move[i]; } } } void setPos(float x, float y, float z) { pos->setIndex(mIndex, x, y, z); } Vector3 getPos() { return pos->getIndex(mIndex); } }; void SoAVAoS() { //AoS(Arrays of Structure) Transform aosArray[4]; ArrayTransformSoA soaArray; Vector3 moveAdd(4.0f, 2.0f, 1.0f); for (int i = 0; i < 4; i++) { aosArray[i].move(moveAdd); } soaArray.move(moveAdd); for (int i = 0; i < 4; i++) { cout << aosArray[i].pos.x << endl; soaArray.mIndex = i; cout << soaArray.getPos().x << endl; } cout << "" << endl; } //DOD 面向數據設計 class ArrayTransformManager { private: enum ElementType { Pos, Scale, Orient, ElementCount }; int elements[ElementCount]; vector<char*> memoryPool; int totalSize = 0; int maxMemory = 32; //當前 int nextSlot = 0; public: ArrayTransformManager() { elements[Pos] = 3 * sizeof(float); totalSize += elements[Pos]; elements[Scale] = 3 * sizeof(float); totalSize += elements[Scale]; elements[Orient] = 4 * sizeof(float); totalSize += elements[Orient]; memoryPool.resize(ElementCount); } void initialize() { for (int i = 0; i < ElementCount; i++) { int byteCount = elements[0] * maxMemory; memoryPool[i] = new char[byteCount]; memset(memoryPool[i], 0, byteCount); } } void createArrayTransform(ArrayTransformSoA &outTransform) { int current = nextSlot++; //current = 0,nextSlotIdx = 0,nextSlotBase = 0 //current = 3,nextSlotIdx = 3,nextSlotBase = 0 //current = 4,nextSlotIdx = 0,nextSlotBase = 4 //current = 5,nextSlotIdx = 1,nextSlotBase = 4 //current = 7,nextSlotIdx = 3,nextSlotBase = 4 //current = 8,nextSlotIdx = 0,nextSlotBase = 8 int nextSlotIdx = current % 4; int nextSlotBase = current - nextSlotIdx; outTransform.mIndex = nextSlotIdx; outTransform.pos = reinterpret_cast<ArrayVector3*>( memoryPool[Pos] + nextSlotBase*elements[Pos]); outTransform.scale = reinterpret_cast<ArrayVector3*>( memoryPool[Scale] + nextSlotBase*elements[Scale]); outTransform.orient = reinterpret_cast<ArrayQuaternion*>( memoryPool[Orient] + nextSlotBase*elements[Orient]); outTransform.setPos(nextSlotIdx, nextSlotIdx, nextSlotIdx); } }; void TestDOD() { ArrayTransformManager transformDOD; transformDOD.initialize(); ArrayTransformSoA transform0; transformDOD.createArrayTransform(transform0); ArrayTransformSoA transform1; transformDOD.createArrayTransform(transform1); ArrayTransformSoA transform2; transformDOD.createArrayTransform(transform2); ArrayTransformSoA transform3; transformDOD.createArrayTransform(transform3); ArrayTransformSoA transform4; transformDOD.createArrayTransform(transform4); cout << transform0.getPos().x << endl; cout << transform1.getPos().x << endl; cout << transform2.getPos().x << endl; cout << transform3.getPos().x << endl; cout << transform4.getPos().x << endl; }
1.ArrayVector3 對應ArrayVector3.是SIMD要求的SoA結構,用於使用SSE2指令.
2.Transform 對應ArrayTransformSoA.用於Node的位置信息.
3.ArrayMemoryManager對應ArrayTransformManager.用於生成DOD數據結構內存排列.
固然ArrayMemoryManager自己的功能要複雜的多,如刪除插槽,追蹤已被刪除插槽,添加時自動選擇刪除插槽,隊列中空白插槽太多後的自動清理.模擬的ArrayTransformManager都是沒有的.
線程在Ogre2.x中不再是一個無關緊要的功能,也不是一個玩具,也不是簡單的邏輯一個線程,渲染一個線程這種簡單用法.由於Ogre2.x中使用位置太多,以下只列出SceneManager::updateSceneGraph()中關於新的線程的使用,咱們來看下基本狀況.
先看以下代碼.
void SceneManager::startWorkerThreads() { #if OGRE_PLATFORM != OGRE_PLATFORM_EMSCRIPTEN mWorkerThreadsBarrier = new Barrier( mNumWorkerThreads+1 ); mWorkerThreads.reserve( mNumWorkerThreads ); for( size_t i=0; i<mNumWorkerThreads; ++i ) { ThreadHandlePtr th = Threads::CreateThread( THREAD_GET( updateWorkerThread ), i, this ); mWorkerThreads.push_back( th ); } #endif } unsigned long updateWorkerThread( ThreadHandle *threadHandle ) { SceneManager *sceneManager = reinterpret_cast<SceneManager*>( threadHandle->getUserParam() ); return sceneManager->_updateWorkerThread( threadHandle ); } THREAD_DECLARE( updateWorkerThread ); unsigned long SceneManager::_updateWorkerThread( ThreadHandle *threadHandle ) { #if OGRE_PLATFORM != OGRE_PLATFORM_EMSCRIPTEN size_t threadIdx = threadHandle->getThreadIdx(); while( !mExitWorkerThreads ) { mWorkerThreadsBarrier->sync(); if( !mExitWorkerThreads ) { #else size_t threadIdx = 0; #endif switch( mRequestType ) { case CULL_FRUSTUM: cullFrustum( mCurrentCullFrustumRequest, threadIdx ); break; case UPDATE_ALL_ANIMATIONS: updateAllAnimationsThread( threadIdx ); break; case UPDATE_ALL_TRANSFORMS: updateAllTransformsThread( mUpdateTransformRequest, threadIdx ); break; case UPDATE_ALL_BOUNDS: updateAllBoundsThread( *mUpdateBoundsRequest, threadIdx ); break; case UPDATE_ALL_LODS: updateAllLodsThread( mUpdateLodRequest, threadIdx ); break; case UPDATE_INSTANCE_MANAGERS: updateInstanceManagersThread( threadIdx ); break; case BUILD_LIGHT_LIST01: buildLightListThread01( mBuildLightListRequestPerThread[threadIdx], threadIdx ); break; case BUILD_LIGHT_LIST02: buildLightListThread02( threadIdx ); break; case USER_UNIFORM_SCALABLE_TASK: mUserTask->execute( threadIdx, mNumWorkerThreads ); break; default: break; } #if OGRE_PLATFORM != OGRE_PLATFORM_EMSCRIPTEN mWorkerThreadsBarrier->sync(); } } #endif return 0; } void SceneManager::fireWorkerThreadsAndWait(void) { #if OGRE_PLATFORM == OGRE_PLATFORM_EMSCRIPTEN _updateWorkerThread( NULL ); #else mWorkerThreadsBarrier->sync(); //Fire threads mWorkerThreadsBarrier->sync(); //Wait them to complete #endif } void SceneManager::updateSceneGraph() { //TODO: Enable auto tracking again, first manually update the tracked scene nodes for correct math. (dark_sylinc) // Update scene graph for this camera (can happen multiple times per frame) /*{ // Auto-track nodes AutoTrackingSceneNodes::iterator atsni, atsniend; atsniend = mAutoTrackingSceneNodes.end(); for (atsni = mAutoTrackingSceneNodes.begin(); atsni != atsniend; ++atsni) { (*atsni)->_autoTrack(); } // Auto-track camera if required camera->_autoTrack(); }*/ OgreProfileGroup("updateSceneGraph", OGREPROF_GENERAL); // Update controllers ControllerManager::getSingleton().updateAllControllers(); highLevelCull(); _applySceneAnimations(); updateAllTransforms(); updateAllAnimations(); #ifdef OGRE_LEGACY_ANIMATIONS updateInstanceManagerAnimations(); #endif updateInstanceManagers(); updateAllBounds( mEntitiesMemoryManagerUpdateList ); updateAllBounds( mLightsMemoryManagerCulledList ); { // Auto-track nodes AutoTrackingSceneNodeVec::const_iterator itor = mAutoTrackingSceneNodes.begin(); AutoTrackingSceneNodeVec::const_iterator end = mAutoTrackingSceneNodes.end(); while( itor != end ) { itor->source->lookAt( itor->target->_getDerivedPosition() + itor->offset, Node::TS_WORLD, itor->localDirection ); itor->source->_getDerivedPositionUpdated(); ++itor; } } { // Auto-track camera if required CameraList::const_iterator itor = mCameras.begin(); CameraList::const_iterator end = mCameras.end(); while( itor != end ) { (*itor)->_autoTrack(); ++itor; } } buildLightList(); //Reset the list of render RQs for all cameras that are in a PASS_SCENE (except shadow passes) uint8 numRqs = 0; { ObjectMemoryManagerVec::const_iterator itor = mEntitiesMemoryManagerCulledList.begin(); ObjectMemoryManagerVec::const_iterator end = mEntitiesMemoryManagerCulledList.end(); while( itor != end ) { numRqs = std::max<uint8>( numRqs, (*itor)->_getTotalRenderQueues() ); ++itor; } } CameraList::const_iterator itor = mCameras.begin(); CameraList::const_iterator end = mCameras.end(); while( itor != end ) { (*itor)->_resetRenderedRqs( numRqs ); ++itor; } // Reset these mStaticMinDepthLevelDirty = std::numeric_limits<uint16>::max(); mStaticEntitiesDirty = false; for( size_t i=0; i<OGRE_MAX_SIMULTANEOUS_LIGHTS; ++i ) mAutoParamDataSource->setTextureProjector( 0, i ); } void SceneManager::updateAllTransformsThread( const UpdateTransformRequest &request, size_t threadIdx ) { Transform t( request.t ); const size_t toAdvance = std::min( threadIdx * request.numNodesPerThread, request.numTotalNodes ); //Prevent going out of bounds (usually in the last threadIdx, or //when there are less nodes than ARRAY_PACKED_REALS const size_t numNodes = std::min( request.numNodesPerThread, request.numTotalNodes - toAdvance ); t.advancePack( toAdvance / ARRAY_PACKED_REALS ); Node::updateAllTransforms( numNodes, t ); } //----------------------------------------------------------------------- void SceneManager::updateAllTransforms() { mRequestType = UPDATE_ALL_TRANSFORMS; NodeMemoryManagerVec::const_iterator it = mNodeMemoryManagerUpdateList.begin(); NodeMemoryManagerVec::const_iterator en = mNodeMemoryManagerUpdateList.end(); while( it != en ) { NodeMemoryManager *nodeMemoryManager = *it; const size_t numDepths = nodeMemoryManager->getNumDepths(); size_t start = nodeMemoryManager->getMemoryManagerType() == SCENE_STATIC ? mStaticMinDepthLevelDirty : 1; //Start from the first level (not root) unless static (start from first dirty) for( size_t i=start; i<numDepths; ++i ) { Transform t; const size_t numNodes = nodeMemoryManager->getFirstNode( t, i ); //nodesPerThread must be multiple of ARRAY_PACKED_REALS size_t nodesPerThread = ( numNodes + (mNumWorkerThreads-1) ) / mNumWorkerThreads; nodesPerThread = ( (nodesPerThread + ARRAY_PACKED_REALS - 1) / ARRAY_PACKED_REALS ) * ARRAY_PACKED_REALS; //Send them to worker threads (dark_sylinc). We need to go depth by depth because //we may depend on parents which could be processed by different threads. mUpdateTransformRequest = UpdateTransformRequest( t, nodesPerThread, numNodes ); fireWorkerThreadsAndWait(); //Node::updateAllTransforms( numNodes, t ); } ++it; } //Call all listeners SceneNodeList::const_iterator itor = mSceneNodesWithListeners.begin(); SceneNodeList::const_iterator end = mSceneNodesWithListeners.end(); while( itor != end ) { (*itor)->getListener()->nodeUpdated( *itor ); ++itor; } }
咱們先假設有n個工做線程.以下是針對這段代碼的分析:
Barrier類型mWorkerThreadsBarrier對象:同步主線程與工做線程,用二個信號量來模擬.( 咱們用來互換信號量。不然,若是多個工做線程中若是一個,形成這一困在當前線程的同步點,最終會死鎖)。
mNumThreads:工做線程與主線程之和,n+1
mLockCount:當前鎖定線程數.
工做線程執行方法updateWorkerThread -> _updateWorkerThread(死循環)
主線程裏fireWorkerThreadsAndWait與工做線程中的_updateWorkerThread都會調用二次mWorkerThreadsBarrier->sync()方法.以下咱們簡化mWorkerThreadsBarrier->sync()爲sync方法,如沒作特殊說明,sync都是指mWorkerThreadsBarrier->sync().
建立Barrier對象mWorkerThreadsBarrier,信號量爲0.
建立n個工程線程,由於當前信號量的值爲0,因此在工做線程_updateWorkerThread循環中第一次的sync方法會引發當前信號量WaitForSingleObject堵塞.最終鎖定線程數mLockCount爲n.
主線程更新場景時,假設到了如上面的updateSceneGraph->updateAllTransforms後,更新標識爲CULL_FRUSTUM,調用fireWorkerThreadsAndWait中的第一次sync方法後,達到mWorkerThreadsBarrier中的條件mLockCount== mNumThreads,此時重置當前信號量爲工做線程數n(而後切換成下一個信號量,下一個信號量值爲0).這樣當前信號量下的工做線程就能夠執行工做(_updateWorkerThread->updateAllAnimationsThread).
當主線程和n個工做線程紛紛經過第一個sync方法,執行任務,各達到線程中第二次sync方法前,前n個線程(主線程可能也在裏面了)來到下一個信號量前面,這個信號量的值爲0,因此你們都等着,等到最後一個線程也執行完了,到二次sync方法,此時和第三步差很少,由於mLockCount== mNumThreads, 此時重置當前信號量爲工做線程數n(而後切換成下一個信號量,下一個信號量值爲0).這樣當前信號量下全部線程都紛紛跨過第二次sync方法.工做線程就是執行完當前循環,進到下一個循環裏的第一次sync這裏.這樣又到當前信號量這裏來了,因信號量爲0,因此WaitForSingleObject堵塞.和第2步狀態.
而後重複這個過程,一些平等關係的更新就能夠用工做線程更新,等主線程調用fireWorkerThreadsAndWait的第一個sync方法.而後重複下去.不過要指出的是,線程的順序是不肯定的,不只僅是說第四步最後到達的是不肯定的,也有可能在第三步中,由於主線程執行fireWorkerThreadsAndWait後,接着執行fireWorkerThreadsAndWait的第一個sync可能還在工做線程第二個sync到時第下一個循環的第一個sync以前完成.可是這個實際上是沒有關係,由於咱們要求的同步也不麻煩,只有一齊開始,而後等到一齊結束,這個是知足的.
這只是Ogre中新線程方案中的一例,這個過程咱們能夠看到Ogre中的渲染過程當中,全部節點更新,全部動畫,全部模型AABB所有是多線程開動的,這些方法內部數據的組合也可能是DOD對應的SOA結構,前面DOD連接中說明DOD優點就有更容易的並行化以及更好的緩存命中.前面的幻燈片文檔裏有專門針對DOD與OOD緩存命中的比較.
Ogre2.0已經放棄FFP了,不過原本就是一個應該早放棄的東東,在Ogre1.9就能用RTSS組件替換FFP了,不過在Ogre2.0是真真徹底沒有,相關API都沒有了,那是否是說要簡單渲染一個模型都要寫着色器代碼了,或是必定要用到RTSS,這都不是,咱們須要用到最新的高級材質系統HLMS.HLMS能夠說是組合原來的material和RTSS的新的核心功能,使用更方便與靈活,高效.
在說明新的HLMS時,我認爲有必要先講解一下渲染流水線,這是博友亮亮的園子OpenGL管線(用經典管線代說着色器內部),本文FFP與可編程管線都有說明,對好比上,HLMS採用分塊方案,這樣有不少好處,第一每塊狀態能夠重複使用,減小內存和帶寬,提升cache命中.第二D3D和OpenGL都是狀態機模式,使用塊模式,能夠組合相同塊一塊兒渲染,減小狀態切換,提升渲染效率.這也是爲何做者說原來的Material是低效的,不建議使用,還有做者特地說明,這種分塊模式看起來像是D3D11中的,可是做者自己是一個OpenGL fan,他開發這個HLMS一直都是在OpenGL下,只能說,做者說D3D11開發者想到一塊兒了.
Macroblocks是光柵化狀態,它們包含深度的讀/寫設置,剔除模式。Blendblocks就像D3D11混合狀態,含混合模式及其影響因素。Samplerblocks就像D3D11在GL3 +或採樣狀態採樣對象,包含過濾信息,紋理尋址模式(包,卡,等),紋理的設置,等等。
Macroblocks塊: 包含逐片段處理中的深度檢查,還有剔除模型,顯示模式,相似如D3D11中的ID3D11RasterizerState.
Blendblocks塊: 逐片段處理中的Alpha混合操做.相似ID3D11BlendState.
Samplerblocks塊:紋理塊的屬性集合.相似D3D11_SAMPLER_DESC.
Datablocks塊:這個OgreMain裏沒怎麼體現出來,應該去看OgreHlmsPbs,對應文件夾Media\Hlms\Pbs中,能夠看下是怎麼回事. Datablocks與Renderable結合一塊兒填充着色器代碼,如RTSS同樣.
包含上面全部塊,承載着至關於原Material項.舉例如原來Ogre1.x老模型Renderable原來是setMaterial,如今新模型Renderable使用setDatablock.
原來Material中,如表面顏色等影響頂點着色器與片段着色器之間屬性分被分配到Datablocks,剛開始看到alaph_test等相關設置在裏面還疑惑了下,後面直接在Media\Hlms\Pbs裏查看,如glsl中的PixelShader_ps.glsl能夠直接根據相應alaph_test設置已經能夠丟棄片段,和逐片段處理中的AlphaTest同樣,這裏有點仍是沒搞清楚,是逐片段處理放棄AlphaTest了仍是提早到片段着色器中處理了,畢竟逐片段處理是在片段着色器以後. 逐片段處理別的處理如上也是單獨分塊的.
這些都只是對應二份文檔的一小部分翻譯,只是簡單介紹Ogre2.x中一部分新功能,從這一小部分,咱們已經能夠看到,這是一個徹底不一樣的Ogre引擎,以下幾點後面會具體分析.
1.新的渲染流程.
2.新模型格式以及VAO的引進
3.HLMS詳解.
4.新合成器詳解.
5.新線程詳解.
固然Ogre2.1事實仍是半完成狀態,相關文章會盡可能使用最新版本進行分析.最後,不得不說句,TMD果真只能用C++或C來設計遊戲引擎,如上優化若是用C#來作,確定比用C++都來的麻煩.