我想了解D3D11是如何被UE4的RHI封装的。我知道这牵扯到很多复杂的内容,但通过观察一些最基础的API被谁在哪调用,可以对RHI是如何封装的有一个大致了解。在之前的博客《创建一个最小的D3D11实例》中,有一些最基础的D3D11的API调用,我选他们作为观察的对象。
ID3D11Device 是怎么被创建的?
首先,FD3D11Device
被定义为了 ID3D11Device
,这在 D3D11RHIBasePrivate.h可找到:
typedef ID3D11Device FD3D11Device;
而FD3D11DynamicRHI
中有一个成员:
/** The global D3D device's immediate context */
TRefCountPtr<FD3D11Device> Direct3DDevice;
FD3D11DynamicRHI
是一个继承FDynamicRHI
与IRHICommandContext
的类:
class D3D11RHI_API FD3D11DynamicRHI : public FDynamicRHI, public IRHICommandContextPSOFallback
它有个全局的指针:
extern FD3D11DynamicRHI* GD3D11RHI;
最终,通过调用D3D11的APID3D11CreateDevice
完成创建。
堆栈如下(主线程中):
ID3D11DeviceContext 是怎么得到的?
与前者很类似。
FD3D11DeviceContext
被定义为了 ID3D11DeviceContext
typedef ID3D11DeviceContext FD3D11DeviceContext;
而FD3D11DynamicRHI
中有一个成员:
/** The global D3D device's immediate context */
TRefCountPtr<FD3D11DeviceContext> Direct3DDeviceIMContext;
在前者ID3D11Device
被D3D11CreateDevice
函数创建时得到。
IDXGISwapChain 是怎么创建的?
首先,FD3D11Viewport
中有一个成员:
TRefCountPtr<IDXGISwapChain> SwapChain;
在FD3D11Viewport
的构造函数中调用IDXGIFactory2::CreateSwapChainForHwnd
函数来创建:
IDXGISwapChain1* SwapChain1 = nullptr;
IDXGIFactory2* Factory2 = (IDXGIFactory2*)D3DRHI->GetFactory();
if(!FAILED(Factory2->CreateSwapChainForHwnd(D3DRHI->GetDevice(), WindowHandle, &SwapChainDesc, &FSSwapChainDesc, nullptr, &SwapChain1)))
{
SwapChain1->QueryInterface(__uuidof(IDXGISwapChain1), (void**)SwapChain.GetInitReference());
// See if we are running on a HDR monitor
CheckHDRMonitorStatus();
}
堆栈如下(主线程中):
值得一提的是,创建SwapChain是需要一个窗口的Handle,我感兴趣它是怎么得到的。
这里我调试时是调到了FSlateRHIRenderer
来调用RHICreateViewport
:
NewInfo->ViewportRHI = RHICreateViewport( NewInfo->OSWindow, Width, Height, bFullscreen, NewInfo->PixelFormat );
其中NewInfo->OSWindow
获得方式是:
TSharedRef<FGenericWindow> NativeWindow = Window->GetNativeWindow().ToSharedRef();
NewInfo->OSWindow = NativeWindow->GetOSWindowHandle();
SwapChain 的 BackBuffer 的 RenderTargetView如何被创建
堆栈如下(主线程):
值得一提的是,GetSwapChainSurface
最后会返回一个FD3D11Texture2D*
对象,他将作为FD3D11Viewport
的成员BackBuffer
的值。
ImmediateContext->ClearRenderTargetView 在哪调用
调用堆栈如下(渲染线程)
SwapChain->Present(0, 0) 在哪调用
调用堆栈如下(渲染线程)
如何设置Viewport
调用堆栈如下(渲染线程)
着色器
在引擎的\Engine\Shaders\
目录中有大量的 .ush
和 .usf
,他们如何被编译成各个图形接口对应的着色器文件,相信UE4有复杂的系统,讨论它们应该需要另开篇章了。
这里对一些基础API打上断点简单看下,ID3D11Device::CreateVertexShader
:
看起来和RenderCore
模块有很大关系。
而设置顶点着色器的堆栈如下(渲染线程):
输入布局如何设置
和前者一样,输入布局也在FD3D11DynamicRHI::RHISetBoundShaderState
中:
void FD3D11DynamicRHI::RHISetBoundShaderState(FRHIBoundShaderState* BoundShaderStateRHI)
{
FD3D11BoundShaderState* BoundShaderState = ResourceCast(BoundShaderStateRHI);
StateCache.SetStreamStrides(BoundShaderState->StreamStrides);
StateCache.SetInputLayout(BoundShaderState->InputLayout);
StateCache.SetVertexShader(BoundShaderState->VertexShader);
StateCache.SetPixelShader(BoundShaderState->PixelShader);
StateCache.SetHullShader(BoundShaderState->HullShader);
StateCache.SetDomainShader(BoundShaderState->DomainShader);
StateCache.SetGeometryShader(BoundShaderState->GeometryShader);
而SetInputLayout
函数中调用了IASetInputLayout
D3D11_STATE_CACHE_INLINE void SetInputLayout(ID3D11InputLayout* InputLayout)
{
#if D3D11_ALLOW_STATE_CACHE
D3D11_STATE_CACHE_VERIFY_PRE();
if ((CurrentInputLayout != InputLayout) || GD3D11SkipStateCaching)
{
CurrentInputLayout = InputLayout;
Direct3DDeviceIMContext->IASetInputLayout(InputLayout);
}
D3D11_STATE_CACHE_VERIFY_POST();
#else
Direct3DDeviceIMContext->IASetInputLayout(InputLayout);
#endif
}
顶点缓冲如何被设置
而设置顶点着色器的堆栈如下(渲染线程):
Draw调用
Draw调用的堆栈如下(渲染线程):