RHI、RDG相关记录


在这里插入图片描述

主要信息来自于 RDG101、UE Doc、一些博客 和 UE4.26的源码。文章中的图片主要来自于链接和源码截图。

https://epicgames.ent.box.com/s/ul1h44ozs0t2850ug0hrohlzm53kxwrz
https://docs.unrealengine.com/4.26/en-US/ProgrammingAndScripting/Rendering/Overview/
https://zhuanlan.zhihu.com/p/72086470
https://www.cnblogs.com/Saeru-Hikari/p/10898119.html
https://blog.csdn.net/leonwei/article/details/95527109
https://www.cnblogs.com/zhouxin/p/6418301.html
https://www.bilibili.com/read/cv5082163

1. RHI

RHI即Render Hardware Interface, 即渲染硬件接口, 是UE为实现跨平台而实现的一套API. 每个RHI接口都为OpenGL, Vulkan, DX11/12、Metal做了不同的实现. 在引擎初始化时使用的绘图接口就已经确定, 引擎就可以确定RHI所使用接口的版本。

1.1 RHI实例

创建RHI实例的方法的声明在DynamicRHI.h
在这里插入图片描述
而这个方法的实现分散在对应平台的CPP文件中
在这里插入图片描述
编译时只保留目标平台的的源文件。

1.2 主要功能

1.2.1 渲染特性查询

对应在RHI.hRHI.cpp中,包括当前显卡硬件的代号以及驱动版本,深度测试时获取深度, 体纹理, 硬件合并渲染, 对MSAA的支持, 对各种RenderTarget格式的支持程度, 乃至于光栅化等。

比如查询是否支持细分的方法:

// helper to check that the shader platform supports tessellation.
RHI_API bool RHISupportsTessellation(const FStaticShaderPlatform Platform);
...
RHI_API bool RHISupportsTessellation(const FStaticShaderPlatform Platform)
{
	if (IsFeatureLevelSupported(Platform, ERHIFeatureLevel::SM5))
	{
		return (Platform == SP_PCD3D_SM5) || (Platform == SP_XBOXONE_D3D12) || (Platform == SP_METAL_SM5) || (IsVulkanSM5Platform(Platform));
	}
	return false;
}

在使用Vulkan时,其依据就来自于创建物理设备之后,逻辑设备创建之前,Vulkan对Feature的一次查询

void FVulkanDevice::InitGPU(int32 DeviceIndex)
{
	LLM_SCOPE_VULKAN(ELLMTagVulkan::VulkanMisc);

	// Query features
	VulkanRHI::vkGetPhysicalDeviceFeatures(Gpu, &PhysicalFeatures);
	
	...
	CreateDevice();
	...
}

实际上,RHI模块大多数实现都在对应的 XXXRHI模块中。

1.2.2 API基础功能

对应在DynamicRHI中,基本上用API写渲染器时所用到的功能都有,创建 Shader、Buffer、SamplerState,Mips生成等等。
在这里插入图片描述

1.2.3 渲染资源封装

对应在RHIResources中。
基类FRHIResource封装了一些基础方法,主要有释放、延时释放、提交状态、引用计数、可用检测等等。

class RHI_API FRHIResource
{
public:
	...
	FORCEINLINE_DEBUGGABLE uint32 AddRef() const;
	FORCEINLINE_DEBUGGABLE uint32 Release() const;
	FORCEINLINE_DEBUGGABLE uint32 GetRefCount() const;
	void DoNoDeferDelete();
	static void FlushPendingDeletes(bool bFlushDeferredDeletes = false);
	bool IsValid() const;
	void SetCommitted(bool bInCommitted);
	bool IsCommitted() const;
	...
private:
	mutable FThreadSafeCounter NumRefs;
	mutable int32 MarkedForDelete;
	bool bDoNotDeferDelete;
	bool bCommitted;
	static uint32 CurrentFrame;
};

所有DynamicRHI.h中能创建的资源都有一个对应的FRHIResource子类:

typedef TRefCountPtr<frhisamplerstate> FSamplerStateRHIRef;
typedef TRefCountPtr<frhirasterizerstate> FRasterizerStateRHIRef;
typedef TRefCountPtr<frhidepthstencilstate> FDepthStencilStateRHIRef;
typedef TRefCountPtr<frhiblendstate> FBlendStateRHIRef;
typedef TRefCountPtr<frhivertexdeclaration> FVertexDeclarationRHIRef;
typedef TRefCountPtr<frhivertexshader> FVertexShaderRHIRef;
typedef TRefCountPtr<frhihullshader> FHullShaderRHIRef;
typedef TRefCountPtr<frhidomainshader> FDomainShaderRHIRef;
typedef TRefCountPtr<frhipixelshader> FPixelShaderRHIRef;
typedef TRefCountPtr<frhigeometryshader> FGeometryShaderRHIRef;
typedef TRefCountPtr<frhicomputeshader> FComputeShaderRHIRef;
typedef TRefCountPtr<frhiraytracingshader>          FRayTracingShaderRHIRef;
typedef TRefCountPtr<frhicomputefence>	FComputeFenceRHIRef;
typedef TRefCountPtr<frhiboundshaderstate> FBoundShaderStateRHIRef;
typedef TRefCountPtr<frhiuniformbuffer> FUniformBufferRHIRef;
typedef TRefCountPtr<frhiindexbuffer> FIndexBufferRHIRef;
typedef TRefCountPtr<frhivertexbuffer> FVertexBufferRHIRef;
typedef TRefCountPtr<frhistructuredbuffer> FStructuredBufferRHIRef;
typedef TRefCountPtr<frhitexture> FTextureRHIRef;
typedef TRefCountPtr<frhitexture2d> FTexture2DRHIRef;
typedef TRefCountPtr<frhitexture2darray> FTexture2DArrayRHIRef;
typedef TRefCountPtr<frhitexture3d> FTexture3DRHIRef;
typedef TRefCountPtr<frhitexturecube> FTextureCubeRHIRef;
typedef TRefCountPtr<frhitexturereference> FTextureReferenceRHIRef;
typedef TRefCountPtr<frhirenderquery> FRenderQueryRHIRef;
typedef TRefCountPtr<frhirenderquerypool> FRenderQueryPoolRHIRef;
typedef TRefCountPtr<frhitimestampcalibrationquery> FTimestampCalibrationQueryRHIRef;
typedef TRefCountPtr<frhigpufence>	FGPUFenceRHIRef;
typedef TRefCountPtr<frhiviewport> FViewportRHIRef;
typedef TRefCountPtr<frhiunorderedaccessview> FUnorderedAccessViewRHIRef;
typedef TRefCountPtr<frhishaderresourceview> FShaderResourceViewRHIRef;
typedef TRefCountPtr<frhigraphicspipelinestate> FGraphicsPipelineStateRHIRef;
typedef TRefCountPtr<frhiraytracingpipelinestate> FRayTracingPipelineStateRHIRef;

每个都根据自身需要额外添加了一些方法和成员变量,比如有关Layout,Usage,SizeXY的等等。

1.2.4 渲染指令

1.2.4.1 FRHICommandBase 与 FRHICommand

FRHICommandBase CommanList链表的一个元素,ExecuteAndDestruct用于执行当前指令,而Next 指向了下一条指令。

struct FRHICommandBase
{
	FRHICommandBase* Next = nullptr;
	virtual void ExecuteAndDestruct(FRHICommandListBase& CmdList, FRHICommandListDebugContext& DebugContext) = 0;
};

FRHICommand是一个模板类,需要模板TCmd实现方法Execute. 而此子类在执行ExecuteAndDestruct时会退化到参数类型TCmd, 执行TCmd下的Execute方法后自动析构, 这个过程也就实现了所谓"ExecuteAndDestruct"

template<typename tcmd,="" typename="" nametype="FUnnamedRhiCommand">
struct FRHICommand : public FRHICommandBase
{
#if RHICOMMAND_CALLSTACK
	uint64 StackFrames[16];

	FRHICommand()
	{
		FPlatformStackWalk::CaptureStackBackTrace(StackFrames, 16);
	}
#endif

	void ExecuteAndDestruct(FRHICommandListBase& CmdList, FRHICommandListDebugContext& Context) override final
	{
		TRACE_CPUPROFILER_EVENT_SCOPE_ON_CHANNEL_STR(NameType::TStr(), RHICommandsChannel);
		
		TCmd *ThisCmd = static_cast<tcmd*>(this);
#if RHI_COMMAND_LIST_DEBUG_TRACES
		ThisCmd->StoreDebugInfo(Context);
#endif
		ThisCmd->Execute(CmdList);
		ThisCmd->~TCmd();
	}

	virtual void StoreDebugInfo(FRHICommandListDebugContext& Context) {};
};

1.2.4.2 FRHICommandListBase与 FRHICommandList

FRHICommandListBase中定义了相应FRHICommandBase的链表实现,以及定义一些上下文如IRHICommandContext , IRHIComputeContext
还定义了渲染线程和RHI线程交互的API:

...
void QueueAsyncCommandListSubmit(FGraphEventRef& AnyThreadCompletionEvent, class FRHICommandList* CmdList);
void QueueParallelAsyncCommandListSubmit(FGraphEventRef* AnyThreadCompletionEvents, bool bIsPrepass, class FRHICommandList** CmdLists, int32* NumDrawsIfKnown, int32 Num, int32 MinDrawsPerTranslate, bool bSpewMerge);
void QueueRenderThreadCommandListSubmit(FGraphEventRef& RenderThreadCompletionEvent, class FRHICommandList* CmdList);
void QueueCommandListSubmit(class FRHICommandList* CmdList);
void AddDispatchPrerequisite(const FGraphEventRef& Prereq);
void WaitForTasks(bool bKnownToBeComplete = false);
void WaitForDispatch();
void WaitForRHIThreadTasks();
void HandleRTThreadTaskCompletion(const FGraphEventRef& MyCompletionGraphEvent);
...

RHI本身相应的FRHICommandBaseFRHICommandBaseList都是存放在渲染线程中,RHI线程可以用于在渲染线程中同步执行异步的复杂操作。比如:压入很多FRHICommandBase到渲染线程中执行;有些操作可以放入RHI线程中与渲染线程一起执行;在某段FRHICommandBase前,调用WaitForTasks等同步渲染线程与RHI线程 等等操作。

FRHICommandList中有所有用于渲染的API指令封装,一般有二种方法,一种是插入FRHICommandListBase链表,一种是直接调用相应渲染平台对应FDynamicRHI中的实现。
比如:

...
FORCEINLINE_DEBUGGABLE void SetShaderTexture(FRHIComputeShader* Shader, uint32 TextureIndex, FRHITexture* Texture)
{
	ValidateBoundShader(Shader);
	if (Bypass())
	{
		GetComputeContext().RHISetShaderTexture(Shader, TextureIndex, Texture);
		return;
	}
	ALLOC_COMMAND(FRHICommandSetShaderTexture<frhicomputeshader>)(Shader, TextureIndex, Texture);
}
...

2. RDG

PPT上写的挺详细,这里主要总结一下。

2.1 Shader Parameters

Shader参数设置方法,大体上分两种,第一种是只有某个shader能访问的局部shader parmeters,另一种是能够全局访问的uniformbuffer,这两种是使用不同的宏来进行实现的。

2.1.1 如何用宏设置Shader Parameter:

在这里插入图片描述

2.1.2 自动对齐规则

每个成员都是按照其大小的下一个幂进行对齐的,但前提是大于4个字节:
指针是8字节对齐的(即使在32位平台上也是如此);
浮点、uint32、int32是四字节对齐的;
FVector2D,FIntPoint是8字节对齐的;
FVector和FVector 4是16字节对齐的。
每个成员的自动对齐将不可避免地创建填充,如下面的注释所示:
在这里插入图片描述有自动合并对齐功能,但数组没有
在这里插入图片描述
可以手动通过调整顺序来减少自动填充的空间浪费
在这里插入图片描述

2.1.3 Parameter与Shader绑定

SHADER_USE_PARAMETER_STRUCT
在这里插入图片描述
直接把宏放在类里,就不用码using那行了

2.1.4 如何设置参数

在这里插入图片描述

2.1.5 Uniform Buffer

在这里插入图片描述
UE会自动生成.ush文件,上图中IMPLEMENT_GLOBAL_SHADER_PARAMETER_STRUCT的第二个参数,是UBO在Shader里的名字。
然后在上面的Shader Parameters里引用UBO:
在这里插入图片描述

2.2 Render Graph Basics

2.2.1 创建和设置RenderGraph

下图中是创建FRDGBuilder与执行的代码。中间需要填充的便是具体执行内容。
在这里插入图片描述

2.2.2 创建和设置Texture

2.2.2.1 创建Texture

在这里插入图片描述
注意这里是渲染线程,Texture实际上是异步在RHI线程创建的,所以现在拿到的是个句柄。

2.2.2.2 创建SRV,UAV

在这里插入图片描述在这里插入图片描述
着色器资源视图 (SRV) 和无序的访问视图 (UAV)是DX中的概念,DX没有碰过不是很了解,不过看概念映射到Vulkan应该是对应的Image View、Usage标志。

2.2.2.3 将Texture绑Shader上

在这里插入图片描述

2.2.3 在RDG上添加Pass

在这里插入图片描述分配参数→设置参数→管理参数到Pass

2.2.4 建立和绑定 Color、DepthStencil Target

首先要做的是将RENDER_TARGET_BINDING_SLOTS()添加到Shader Parameters里
在这里插入图片描述
然后设置参数时设置到RenderTargets数组里
在这里插入图片描述
DepthStencil是单独的
在这里插入图片描述

除了上面这种方法之外,还可以用之前UAV的方式:
在这里插入图片描述

2.2.5 IPooledRenderTarget的使用

控制纹理的分配的接口,有时需要将现有资源导入到RDG中(特别是在RDG转换过程中)。Builder公开RealStices外部纹理,它返回由现有渲染目标支持的RDG纹理实例:
在这里插入图片描述
还可以从FRDGTexture中提取rt指针。这允许您跨RDG调用保留资源的内容。但是,提取会延迟到图形执行完毕;这是因为在执行期间可能会根据图形中资源的生存期来分配资源。因此,API公开了QueueTextureExtraction,它允许您提供一个指针,该指针将在graph执行时填充:
在这里插入图片描述

2.2.6 创建和设置Buffer

2.2.6.1 普通Buffer

在这里插入图片描述
如果一个buffer使用了SHADER_PARAMETER_RDG_BUFFER_SRV().着色器只能通过SRV读取:
在这里插入图片描述

2.2.6.1 Indirect Draw/Dispatch Buffer

间接的draw / dispatch buffer比较特殊独特,因为它们不是由着色器直接使用的。相反,将它们声明为pass参数上的RDG缓冲区,然后直接在pass中使用RHI间接绘制缓冲区:
在这里插入图片描述

2.3 Pass Debugging and Methodology

未完待续

</tcmd*>


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM