主要信息來自於 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*>