首先是文件结构:
其中能够暴露给程序使用的只有头文件Effects.h,里面可以存放多套不同的特效框架类的声明,而关于每个框架类的实现部分都应当用一个独立的源文件存放。而EffectHelper.h则是用来帮助管理常量缓冲区的,服务于各种框架类的实现部分以及所属的源文件,因此不应该直接使用。
理论上它也是可以做成静态库使用的,然后着色器代码稳定后也不应当变动。在使用的时候只需要包含头文件Effects.h即可。
EffectHelper.h该头文件包含了一些有用的东西,但它需要在包含特效类实现的源文件中使用,且必须晚于Effects.h和d3dUtil.h包含。
在堆上进行类的内存对齐有些类型需要在堆上按16字节对齐,比如XMVECTOR和XMMATRIX,虽然说拿这些对象作为类的成员不太合适,毕竟分配在堆上的话基本上无法保证内存按16字节对齐了,但还是希望能够做到。在VS的corecrt_malloc.h(只要有包含stdlib.h, malloc.h之一的头文件都可以)中有这样的一个函数:_aligned_malloc,它可以指定需要分配的内存字节大小以及按多少字节对齐。其中对齐值必须为2的整数次幂的字节数。
void * _aligned_malloc( size_t size, // [In]分配内存字节数 size_t alignment // [In]按多少字节内存来对齐 );若一个类中包含有已经指定内存对齐的成员,则需要优先把这些成员放到最前。
然后与之对应的就是_aligned_free函数了,它可以释放之前由_aligned_malloc分配得到的内存。
下面是类模板AlignedType的实现,让需要内存对齐的类去继承该类即可。它重载了operator new和operator delete的实现:
// 若类需要内存对齐,从该类派生 template<class DerivedType> struct AlignedType { static void* operator new(size_t size) { const size_t alignedSize = __alignof(DerivedType); static_assert(alignedSize > 8, "AlignedNew is only useful for types with > 8 byte alignment! Did you forget a __declspec(align) on DerivedType?"); void* ptr = _aligned_malloc(size, alignedSize); if (!ptr) throw std::bad_alloc(); return ptr; } static void operator delete(void * ptr) { _aligned_free(ptr); } };需要注意的是,继承AlignedType的类或者其成员必须本身有__declspec(align)的标识。若是内部成员,在所有包含该标识的值中最大的align值 必须是2的整数次幂且必须大于8。
下面演示了正确的和错误的行为:
// 错误!VertexPosColor按4字节对齐! struct VertexPosColor : AlignedType<VertexPos> { XMFLOAT3 pos; XMFLOAT4 color; }; // 正确!Data按16字节对齐,因为pos本身是按16字节对齐的。 struct Data : AlignedType<VertexPos> { XMVECTOR pos; int val; }; // 正确!Vector类按16字节对齐 __declspec(align(16)) struct Vector : AlignedType<Vector> { float x; float y; float z; float w; };这里AlignedType<T>主要是用于BasicEffect::Impl类,因为其内部包含了XMVECTOR和XMMATRIX类型的成员,且该类需要分配在堆上。
常量缓冲区管理一个常量缓冲区可能会被创建、更新或者绑定到管线。若常量缓冲区的值没有发生变化,我们不希望它进行无意义的更新。这里可以使用一个dirty标记,确认它是否被修改过。在Effects调用Apply后,如果常量缓冲区的任一内部成员发生修改的话,我们就将数据更新到常量缓冲区并恢复该标记。
首先是抽象基类CBufferBase:
struct CBufferBase { template<class T> using ComPtr = Microsoft::WRL::ComPtr<T>; CBufferBase() : isDirty() {} ~CBufferBase() = default; BOOL isDirty; ComPtr<ID3D11Buffer> cBuffer; virtual HRESULT CreateBuffer(ID3D11Device * device) = 0; virtual void UpdateBuffer(ID3D11DeviceContext * deviceContext) = 0; virtual void BindVS(ID3D11DeviceContext * deviceContext) = 0; virtual void BindHS(ID3D11DeviceContext * deviceContext) = 0; virtual void BindDS(ID3D11DeviceContext * deviceContext) = 0; virtual void BindGS(ID3D11DeviceContext * deviceContext) = 0; virtual void BindCS(ID3D11DeviceContext * deviceContext) = 0; virtual void BindPS(ID3D11DeviceContext * deviceContext) = 0; };这么做是为了方便我们放入数组进行遍历。