Loading

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());
});;
  • 上述代码生成了一个DelayedAutoRegisterHelper3Static 变量去构造一个FDelayedAutoRegisterHelperFDelayedAutoRegisterHelper在构造过程中会将反射信息保存,在引擎初始化时进行延时自动注册,

  • 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的反射系统

相关资料

posted @ 2022-02-10 14:59  straywriter  阅读(1189)  评论(0编辑  收藏  举报