UE4 LAYOUT_FIELD 分析
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)