主要信息来自于 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.h
、RHI.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
本身相应的FRHICommandBase
与FRHICommandBaseList
都是存放在渲染线程中,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*>