Ogre2.0 全新功能打造新3D引擎

  不知當初是在那看到,說是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版本),本文不會特別說明.編程

Ogre1.x中問題與建議 

Cache末命中

  看看做者的幻燈片,哈哈,圖片特別形象生動. 設計模式

  這個是函數是判斷模型是否在當前攝像機可見,若是可見,加入渲染通道.不過你去看如今的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新的渲染流程與合成器.

SIMD,DOD,SoA

  在看以下內容時,先介紹一下什麼是基於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);
  }
};
DOD VS OOP

  下面有人解釋爲何第一段代碼要高效,在第二段代碼中,每次獲得一個結構域,浪費更多帶寬,以及更新無用數據到緩存中,緩存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;
}
SIMD

  下面的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;

}
ArrayTransformManager//對應Ogre中的ArrayMemoryManager

  這個是結合了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帶來的好處,在客戶級別上的.

Ogre2.x移植手冊:

模型,場景和節點:

  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,Thread:

  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;

}
幫助理解Ogre中的SIMD,DOD

  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;
    }
}
SceneManager::updateSceneGraph

   咱們先假設有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緩存命中的比較.

HLMS:只是簡介

  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++都來的麻煩.

相關文章
相關標籤/搜索