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++都来的麻烦.

相关文章
相关标签/搜索