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

相關資料


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM