UE4 LAYOUT_FIELD 分析
近期的需求需要對UE的Mesh Pipline進行分析,花了一些時間去閱讀相關的源碼,在此記錄。
在源碼閱讀的過程中,發現很多Shader Parameter相關的類成員都要求使用LAYOUT_FIELD
對其進行包裝,直覺猜測應該和反射有關系,於是深入的了解了一下其內部原理。
首先我拿FShaderUniformBufferParameter
進行實例分析
// \Engine\Source\Runtime\RenderCore\Public\ShaderParameters.h
class FShaderUniformBufferParameter
{
DECLARE_EXPORTED_TYPE_LAYOUT(FShaderUniformBufferParameter, RENDERCORE_API, NonVirtual);
public:
FShaderUniformBufferParameter()
: BaseIndex(0xffff)
{}
static RENDERCORE_API void ModifyCompilationEnvironment(const TCHAR* ParameterName,const FShaderParametersMetadata& Struct,EShaderPlatform Platform,FShaderCompilerEnvironment& OutEnvironment);
RENDERCORE_API void Bind(const FShaderParameterMap& ParameterMap,const TCHAR* ParameterName,EShaderParameterFlags Flags = SPF_Optional);
friend FArchive& operator<<(FArchive& Ar,FShaderUniformBufferParameter& P)
{
P.Serialize(Ar);
return Ar;
}
bool IsBound() const { return BaseIndex != 0xffff; }
void Serialize(FArchive& Ar)
{
Ar << BaseIndex;
}
inline bool IsInitialized() const
{
return true;
}
uint32 GetBaseIndex() const { ensure(IsBound()); return BaseIndex; }
private:
LAYOUT_FIELD(uint16, BaseIndex);
};
-
從
FShaderUniformBufferParameter
的聲明中我們可以看到,在聲明頭部有一個DECLARE_EXPORTED_TYPE_LAYOUT
,對成員變量BaseIndex
使用LAYOUT_FIELD
進行了封裝 -
我們對這兩個宏進行展開,觀察其展開之后的代碼,使用 Rider For Unreal Engine 可以比較方便的對宏進行展開
-
展開之后的代碼:
class FShaderUniformBufferParameter
{
private:
using InternalBaseType = typename TGetBaseTypeHelper<FShaderUniformBufferParameter>::Type;
template <typename InternalType>
static void InternalInitializeBases(FTypeLayoutDesc& TypeDesc)
{
TInitializeBaseHelper<InternalType, InternalBaseType>::Initialize(TypeDesc);
};
private:
static void InternalDestroy(void* Object, const FTypeLayoutDesc&, const FPointerTableBase* PtrTable);
public:
static FTypeLayoutDesc& StaticGetTypeLayout();
public:
const FTypeLayoutDesc& GetTypeLayout() const;
static const int CounterBase = 10;
public:
using DerivedType = FShaderUniformBufferParameter;
static const ETypeLayoutInterface::Type InterfaceType = ETypeLayoutInterface::NonVirtual;
template <int Counter>
struct InternalLinkType
{
;
static __forceinline void Initialize(FTypeLayoutDesc& TypeDesc)
{
}
};
public:
FShaderUniformBufferParameter()
: BaseIndex(0xffff)
{
}
static RENDERCORE_API void ModifyCompilationEnvironment(const TCHAR* ParameterName,
const FShaderParametersMetadata& Struct,
EShaderPlatform Platform,
FShaderCompilerEnvironment& OutEnvironment);
RENDERCORE_API void Bind(const FShaderParameterMap& ParameterMap, const TCHAR* ParameterName,
EShaderParameterFlags Flags = SPF_Optional);
friend FArchive& operator<<(FArchive& Ar, FShaderUniformBufferParameter& P)
{
P.Serialize(Ar);
return Ar;
}
bool IsBound() const { return BaseIndex != 0xffff; }
void Serialize(FArchive& Ar)
{
Ar << BaseIndex;
}
inline bool IsInitialized() const
{
return true;
}
uint32 GetBaseIndex() const
{
ensure(IsBound());
return BaseIndex;
}
private:
uint16 BaseIndex;
__pragma (warning(push))
__pragma (warning(disable: 4995))
__pragma (warning(disable: 4996))
template <>
struct InternalLinkType<11 - CounterBase>
{
;
static void Initialize(FTypeLayoutDesc& TypeDesc)
{
InternalLinkType<11 - CounterBase + 1>::Initialize(TypeDesc);
alignas(FFieldLayoutDesc) static uint8 FieldBuffer[sizeof(FFieldLayoutDesc)] = {0};
FFieldLayoutDesc& FieldDesc = *(FFieldLayoutDesc*)FieldBuffer;
FieldDesc.Name = L"BaseIndex";
FieldDesc.UFieldNameLength = Freeze::FindFieldNameLength(FieldDesc.Name);
FieldDesc.Type = &StaticGetTypeLayoutDesc<uint16>();
FieldDesc.WriteFrozenMemoryImageFunc = TGetFreezeImageFieldHelper<uint16>::Do();
FieldDesc.Offset = ((::size_t)&reinterpret_cast<char const volatile&>((((DerivedType*)0)->BaseIndex)));
FieldDesc.NumArray = 1u;
FieldDesc.Flags = EFieldLayoutFlags::MakeFlags();
FieldDesc.BitFieldSize = 0u;
FieldDesc.Next = TypeDesc.Fields;
TypeDesc.Fields = &FieldDesc;
}
};
__pragma (warning(pop));
};
-
上面這段代碼中可以觀察到
BaseIndex
成員變量被展開成了uint16 BaseIndex;
和下面的一段代碼,下面的代碼是對InternalLinkType
結構的一個特化,它的靜態函數InternalLinkType::Initialize()
實現了該字段的類型反射 -
目前觀察的
FShaderUniformBufferParameter
類中只存在一個字段,如果存在其他更多的字段,會生成相應的InternalLinkType
的特化 -
在該
InternalLinkType::Initialize()
中第一行中會調用InternalLinkType<11 - CounterBase + 1>::Initialize(TypeDesc);
,遞歸調用類中的字段發射類型生成函數 -InternalLinkType::Initialize()
。 -
遞歸結束的位置在
DECLARE_EXPORTED_TYPE_LAYOUT
生成代碼中template <int Counter> struct InternalLinkType { ; static __forceinline void Initialize(FTypeLayoutDesc& TypeDesc) { } };
-
Shader 變量成員信息反射的調用
using InternalBaseType = typename TGetBaseTypeHelper<FShaderUniformBufferParameter>::Type; template <typename InternalType> static void InternalInitializeBases(FTypeLayoutDesc& TypeDesc) { TInitializeBaseHelper<InternalType, InternalBaseType>::Initialize(TypeDesc); };
內部通過
InternalInitializeBases()
調用InternalLinkType::Initialize()
函數,然后在FShaderUniformBufferParameter::StaticGetTypeLayout()
中調用InternalInitializeBases()
實現成員信息反射其中包含了一些模板操作,在此不做詳細解釋
-
再次觀察
DECLARE_EXPORTED_TYPE_LAYOUT
生成的代碼中存在兩個函數public: static FTypeLayoutDesc& StaticGetTypeLayout(); public: const FTypeLayoutDesc& GetTypeLayout() const;
這兩個函數聲明未定義,從函數的名稱可以猜測到其作用應該是返回保存反射信息的類型,應該是分為在運行期和編譯期調用
找到這兩個函數的實現位置:
// \Engine\Source\Runtime\RenderCore\Private\ShaderParameters.cpp IMPLEMENT_TYPE_LAYOUT(FShaderParameter); IMPLEMENT_TYPE_LAYOUT(FShaderResourceParameter); IMPLEMENT_TYPE_LAYOUT(FRWShaderParameter); IMPLEMENT_TYPE_LAYOUT(FShaderUniformBufferParameter);
-
上面的的
IMPLEMENT_TYPE_LAYOUT(FShaderUniformBufferParameter);
就是上述兩個函數的實現位置(通過Rider的全局搜索和GoTo尋得) -
對
IMPLEMENT_TYPE_LAYOUT(FShaderUniformBufferParameter);
展開得:
// \Engine\Source\Runtime\RenderCore\Private\ShaderParameters.cpp
FTypeLayoutDesc& FShaderUniformBufferParameter::StaticGetTypeLayout()
{
static_assert(TValidateInterfaceHelper<FShaderUniformBufferParameter, InterfaceType>::Value,
"Invalid interface for " "FShaderUniformBufferParameter");
alignas(FTypeLayoutDesc) static uint8 TypeBuffer[sizeof(FTypeLayoutDesc)] = {0};
FTypeLayoutDesc& TypeDesc = *(FTypeLayoutDesc*)TypeBuffer;
if (!TypeDesc.IsInitialized)
{
TypeDesc.IsInitialized = true;
TypeDesc.Name = L"FShaderUniformBufferParameter";
TypeDesc.WriteFrozenMemoryImageFunc = TGetFreezeImageHelper<FShaderUniformBufferParameter>::Do();
TypeDesc.UnfrozenCopyFunc = &Freeze::DefaultUnfrozenCopy;
TypeDesc.AppendHashFunc = &Freeze::DefaultAppendHash;
TypeDesc.GetTargetAlignmentFunc = &Freeze::DefaultGetTargetAlignment;
TypeDesc.ToStringFunc = &Freeze::DefaultToString;
TypeDesc.DestroyFunc = &InternalDestroy;
TypeDesc.Size = sizeof(FShaderUniformBufferParameter);
TypeDesc.Alignment = alignof(FShaderUniformBufferParameter);
TypeDesc.Interface = InterfaceType;
TypeDesc.SizeFromFields = ~0u;
TypeDesc.GetDefaultObjectFunc = &TGetDefaultObjectHelper<FShaderUniformBufferParameter, InterfaceType>::Do;
InternalLinkType<1>::Initialize(TypeDesc);
InternalInitializeBases<FShaderUniformBufferParameter>(TypeDesc);
FTypeLayoutDesc::Initialize(TypeDesc);
}
return TypeDesc;
};
const FTypeLayoutDesc& FShaderUniformBufferParameter::GetTypeLayout() const { return StaticGetTypeLayout(); };
static const FDelayedAutoRegisterHelper DelayedAutoRegisterHelper3(EDelayedRegisterRunPhase::ShaderTypesReady, []
{
FTypeLayoutDesc::Register(
FShaderUniformBufferParameter::StaticGetTypeLayout());
});;
-
上述代碼生成了一個
DelayedAutoRegisterHelper3
的Static
變量去構造一個FDelayedAutoRegisterHelper
,FDelayedAutoRegisterHelper
在構造過程中會將反射信息保存,在引擎初始化時進行延時自動注冊, -
DelayedAutoRegisterHelper3 變量構造即初始化,調用時會產生輸入一個
EDelayedRegisterRunPhase::ShaderTypesReady
,該枚舉定義為// \Engine\Source\Runtime\Core\Public\Misc\DelayedAutoRegister.h enum class EDelayedRegisterRunPhase : uint8 { StartOfEnginePreInit, FileSystemReady, StatSystemReady, IniSystemReady, TaskGraphSystemReady, ShaderTypesReady, PreObjectSystemReady, ObjectSystemReady, EndOfEngineInit, NumPhases, };
不同的枚舉類型有不同的加載策略
-
至於
DelayedAutoRegisterHelper3
中為什么是3,不知道 -
再具體的延遲加載可以查看FDelayedAutoRegisterHelper相關的調用,和深入了解UE的反射系統
相關資料
- 剖析虛幻渲染體系(08)- Shader體系 - 0向往0 - 博客園 (cnblogs.com)
- Creating a Custom Mesh Component in UE4 | Part 2: Implementing the Vertex Factory | by khammassi ayoub | Realities.io | Medium
- UE4.25傳統渲染方式的代碼遷移指南 - 知乎 (zhihu.com)
- UE4中反射信息收集 - 菜雞的博客 | WTCL (bbkgl.github.io)
- UE4反射基礎一:揭秘UBT生成代碼、UObject注冊、UClass及CDO生成 - 知乎 (zhihu.com)
- UE4 UObject反射系列(一) Class相關 - 知乎 (zhihu.com)
- 虛幻4反射系統(一) - 知乎 (zhihu.com)
- 《InsideUE4》UObject(一)開篇 - 知乎 (zhihu.com)
- UE4 獲取預編譯中間文件 - 知乎 (zhihu.com)