最近在寫D3D9模擬D3D10接口的渲染系統中碰到大量的渲染狀態對象,不僅成員多,枚舉也多的要命。
struct CORE_API RasterizerState : ResourceHandle { eFillMode mFillMode; eCullMode mCullMode; bool mFrontFaceCCW; float mDepthBias; float mSlopeScaledDepthBias; bool mDepthClipEnable; bool mScissorEnable; bool mMultisampleEnable; RasterizerState(); };
而要從配置文件中讀取數據并填充到這個結構體,對于C++來說完全就是吃力不討好的,寫出來的代碼也是極為過程,修改和擴展極為麻煩的。
因此決定使用反射的方法來填充數據,先總結一下我的C++反射系統
class RTTIObject // 動態類型識別對象基類,對象通過一些宏后可以很方便的通過字符串創建出類實例,并且可以查詢注冊時的類型和其他綁定信息 class NameRef // 名字表,類似于虛幻中的FName,可以定義Const和普通Name,比較和拷貝只是一個dword耗費的時間 value_parse,value_tostring,value_typename // 一系列類型模板函數,提供對類型的ToString,Parse及類型名查詢
首先需要處理的是枚舉查詢,這里將枚舉通過宏做成一個個枚舉對象,并可以通過名字創建實例
#define DECLARE_ENUMOBJECT( TEnum ) \ struct EnumObject_##TEnum : EnumObject\ {\ DECLARE_RTTIOBJECT( EnumObject_##TEnum );\ EnumObject_##TEnum( );\ }; #define IMPLEMENT_ENUMOBJECT_BEGIN( TEnum, TEnum_prefixoffset, TMember_prefixoffset ) \ IMPLEMENT_RTTIOBJECT_STRING( EnumObject_##TEnum, #TEnum + TEnum_prefixoffset, #TEnum + TEnum_prefixoffset, "EnumObject" )\ EnumObject_##TEnum::EnumObject_##TEnum(){ const int member_prefixoffset = TMember_prefixoffset; #define ENUMOBJECT_ADD( enumkey ) AddMember( #enumkey + member_prefixoffset, (dword)enumkey ); #define IMPLEMENT_ENUMOBJECT_END } #define ENUMOBJECT_STATICINIT( TEnum ) EnumObject_##TEnum::StaticInit();
EnumObject 中通過宏將枚舉的名稱和值保存在這個對象中
IMPLEMENT_ENUMOBJECT_BEGIN( eFillMode, 1, 3 ) // 這里的1,3是將eFillMode及FM_Point轉成字符串后去掉前綴 ENUMOBJECT_ADD( FM_Point ) ENUMOBJECT_ADD( FM_Line ) ENUMOBJECT_ADD( FM_Fill ) IMPLEMENT_ENUMOBJECT_END // 注冊到RTTIObject系統 ENUMOBJECT_STATICINIT( eFillMode )
// 通過枚舉對象可以查找到字符串對應的值 dword v; EnumObject::GetEnumValue( "FillMode", "Point", v )
下一步是將結構體成員信息記錄
void SettingObject::BindMember( const NameRef& objname, void* instancePtr, void* dataPtr, SettingProxy* proxy ) { proxy->mOffset = dword(dataPtr) - dword(instancePtr); MemberList& memberlist = mSettingMap[ objname ]; memberlist[ proxy->mName ] = proxy; }
這里記錄的是結構體成員的內存偏移
使用大量的宏,可以讓結構體綁定變得漂亮
#define BIND_SETTINGOBJECT_BEGIN( TClass ) \ { const NameRef& soname = TClass::StaticGetClassInfo()->mClassName;TClass soobj; #define BIND_SO_MEMBER( TMemberType, TMember ) \ so.BindMember( soname, &soobj, &soobj.TMember, new TSettingElement<TMemberType>(#TMember + 1 ) ); #define BIND_SO_MEMBER_NAME( TMemberType, TMember, TName ) \ so.BindMember( soname, &soobj, &soobj.TMember, new TSettingElement<TMemberType>(TName) ); #define BIND_SO_ENUM( TEnumType, TMember ) \ so.BindMember( soname, &soobj, &soobj.TMember, new TSettingEnum(#TMember + 1, #TEnumType + 1) ); #define BIND_SO_ENUM_NAME( TEnumType, TMember, TName ) \ so.BindMember( soname, &soobj, &soobj.TMember, new TSettingEnum(TName, #TEnumType + 1) ); #define BIND_SETTINGOBJECT_END }
綁定代碼如下
BIND_SETTINGOBJECT_BEGIN( RasterizerState ) BIND_SO_ENUM ( eFillMode , mFillMode ) BIND_SO_ENUM ( eCullMode , mCullMode ) BIND_SO_MEMBER ( bool , mFrontFaceCCW ) BIND_SO_MEMBER ( float , mDepthBias ) BIND_SO_MEMBER ( float , mSlopeScaledDepthBias) BIND_SO_MEMBER ( bool , mDepthClipEnable) BIND_SO_MEMBER ( bool , mScissorEnable) BIND_SO_MEMBER ( bool , mMultisampleEnable) BIND_SETTINGOBJECT_END
所有結構體的信息被記錄在SettingObject中,讀取配置文件填充結構體的任務就變得異常的簡單了
SettingObject settings; // 將所有的結構體信息記錄 InitRenderStateObjectSetting( settings ); const NameRef& rzname = DepthStencilState::StaticGetClassInfo()->mClassName; DepthStencilState a; // 這里就是將配置文件的信息填充到結構體 settings.SetMember( rzname, &a, "BackFace.StencilFunc", "Equal" );