UE4作为一款3D游戏引擎,当然也离不开显卡的支持。目前,主流的3D API主要包括DirectX、OpenGL、Vankul三种。其中,OpenGL和Vankul是支持跨平台的,而DirectX只能在windows平台上使用。UE4在windows平台上可以选择使用DX12或DX11等directX设备作为渲染设备。而D3D设备在使用之前是必须先创建的。
那么,今天让我们一起来探寻windows下使用DirectX作为渲染API时是在什么时候创建的D3D设备。
在上一篇中,我们知道UE4启动之后经过了PreInit,init,tick,exit四个过程。那么,我们首先从PreInit开始寻找。
EnginePreInit
函数实现在Engine\Source\Runtime\Launch\Private
中进行实现,实现过程只是调用在Engine\Source\Runtime\Launch\Private\LaunchEngineLoop.cpp
中实现的FEngineLoop::PreInit
函数,由此可见,重点在FEngineLoop::PreInit
函数。
我们查看FEngineLoop::PreInit
的源代码,发现该函数是一个很长的函数,从934行一直到2401行,总行数1468行。在这个长长的函数中,我们需要寻找我们关心的内容。
在1780行,调用了RHIInit
函数。这里,出现了RHI这个名词。RHI是Render Hardware Interface(渲染硬件接口)的意思。由于UE4的跨平台的需求,不同平台上需要使用不同的3D API来进行渲染。为了对上层程序员隐藏底层渲染的实现,UE4提出了RHI的概念,将底层渲染相关工作全部通过RHI来进行抽象,使上层程序员不用关系底层到底是用的是什么3D API,只需要关心实现的效果即可。
在RHIInit
中,判断了GDynamicRHI
指针的有效性,从变量命名来看,这是一个全部的DynamicRHI指针,极有可能是指向渲染设备的。因此,本指针内容的创建极有可能包含3D设备的创建过程。函数中进行了如下调用GDynamicRHI = PlatformCreateDynamicRHI();
,可见,这是在创建GDynamicRHI的内容,咱们需要继续深入。
在PlatformCreateDynamicRHI
中,经过一系列对平台所支持的API的判断以及本程序所选择的API的判断之后,最终来到了以下调用:
if (DynamicRHIModule)
{
// Create the dynamic RHI.
DynamicRHI = DynamicRHIModule->CreateRHI(RequestedFeatureLevel);
}
return DynamicRHI;
创建了一个DynamicRHI对象并将指针返回给了GDynamicRHI指针。可见,我们需要重点关注CreateRHI接口。此时,DynamicRHIModule
是一个windows D3D11 的RHI Module。因此,我们可以继续向下。
在Engine\Source\Runtime\Windows\D3D11RHI\Private\Windows\WindowsD3D11Device.cpp
中,定义了该module的CreateRHI接口。
TRefCountPtr DXGIFactory1;
SafeCreateDXGIFactory(DXGIFactory1.GetInitReference());
check(DXGIFactory1);
return new FD3D11DynamicRHI(DXGIFactory1,ChosenAdapter.MaxSupportedFeatureLevel,ChosenAdapter.AdapterIndex,ChosenDescription);
这里貌似就要达到目标了,可当我们进入FD3D11DynamicRHI
的构造函数的时候,在里面却并没有发现熟悉的创建D3D11设备的过程。但,在FD3D11DynamicRHI
类中,我们发现了一下两个成员函数
/** If it hasn't been initialized yet, initializes the D3D device. */
virtual void InitD3DDevice();
// FDynamicRHI interface.
virtual void Init() override;
其中Init
函数内部调用了InitD3DDevice
函数。InitD3DDevice
的实现如下:
void FD3D11DynamicRHI::InitD3DDevice()
{
// If we don't have a device yet, either because this is the first viewport, or the old device was removed, create a device.
if(!Direct3DDevice)
{
// Determine the adapter and device type to use.
TRefCountPtr<IDXGIAdapter> Adapter;
// In Direct3D 11, if you are trying to create a hardware or a software device, set pAdapter != NULL which constrains the other inputs to be:
// DriverType must be D3D_DRIVER_TYPE_UNKNOWN
// Software must be NULL.
D3D_DRIVER_TYPE DriverType = D3D_DRIVER_TYPE_UNKNOWN;
uint32 DeviceFlags = D3D11RHI_ShouldAllowAsyncResourceCreation() ? 0 : D3D11_CREATE_DEVICE_SINGLETHREADED;
// Use a debug device if specified on the command line.
const bool bWithD3DDebug = D3D11RHI_ShouldCreateWithD3DDebug();
if (bWithD3DDebug)
{
DeviceFlags |= D3D11_CREATE_DEVICE_DEBUG;
UE_LOG(LogD3D11RHI, Log, TEXT("InitD3DDevice: -D3DDebug = %s"), bWithD3DDebug ? TEXT("on") : TEXT("off"));
}
GTexturePoolSize = 0;
TRefCountPtr<IDXGIAdapter> EnumAdapter;
//枚举显卡设备
if(DXGIFactory1->EnumAdapters(ChosenAdapter,EnumAdapter.GetInitReference()) != DXGI_ERROR_NOT_FOUND)
{
if (EnumAdapter)
{
// 枚举成功
}
}
else
{
check(!"Internal error, EnumAdapters() failed but before it worked")
}
//创建D3D11设备
if (!bDeviceCreated)
{
// Creating the Direct3D device.
VERIFYD3D11RESULT(D3D11CreateDevice(
Adapter,
DriverType,
NULL,
DeviceFlags,
&FeatureLevel,
1,
D3D11_SDK_VERSION,
Direct3DDevice.GetInitReference(),
&ActualFeatureLevel,
Direct3DDeviceIMContext.GetInitReference()
));
}
// Set the RHI initialized flag.
GIsRHIInitialized = true;
}
}
从中可以看出,UE4中D3D11设备的创建时在InitD3DDevice
函数中进行的,那是在什么时候调用的这个函数呢?
前面我们已经发现Init函数会调用它,那么FD3D11DynamicRHI::Init
是在什么时候被调用呢?
我们回到RHIInit
函数中,
GDynamicRHI = PlatformCreateDynamicRHI();
if (GDynamicRHI)
{
GDynamicRHI->Init();
}
发现通过PlatformCreateDynamicRHI
函数创建了FD3D11DynamicRHI
对象之后会紧接着就进行该RHI对象的Init操作,从而调用InitD3DDevice
函数完成第一个D3D11设备的创建。
至此,D3D11设备就被创建出来并用于相关资源的初始化工作,待渲染进行开始之后就可以用于渲染。
RHI: Render hardware interface 渲染硬件层接口, 本人理解RHI是一套硬件无关,平台无关的图形渲染API.
它是如何做到与平台无关,与硬件无关的呢?
每个RHI接口都对应着多个图形API的实现版本. 对于每个RHI接口,其实都有针对DX11,DX12,OpenGL等版本的实现。对于不同平台,引擎初始化的时候就已经确定要用哪一套图形API了。 之后,调用的RHI其实就是对应初始化时候确定用的那套图形API实现的版本.
比如RHIDrawIndexedPrimitive接口,对于DX,OPENGL,其实都实现了RHIDrawIndexedPrimitive接口。
当引擎初始化的时候,判断是在windows平台上的,并决定使用DX11。之后,当调用RHIDrawIndexedPrimitive接口时,其实调用的是DX11版本的RHIDrawIndexedPrimitive。
对于RHI使用者而已,不需要关心是调用了哪套图形API,反正能正确运行,从而造成跨平台的假象;而从开发角度而言,RHI并不是平台无关的,它需要开发人员呕心沥血地开发和维护,以保证RHI在不同平台下运行结果都一样。
DynamicRHI.h里 FDynamicRHI, IRHIComputeContext两个虚基类里定义了所有的RHI接口。
实现RHI接口的子类有:
1. class D3D11RHI_API FD3D11DynamicRHI : public FDynamicRHI, public IRHICommandContext
2. class FD3D12DynamicRHI : public FDynamicRHI
3. class OPENGLDRV_API FOpenGLDynamicRHI : public FDynamicRHI, public IRHICommandContext
Vulkan: 新一代跨平台,充分利用多核多线程的图形API
1. class FVulkanDynamicRHI : public FDynamicRHI
Metal:苹果系统的图形API
5. class FMetalDynamicRHI : public FDynamicRHI, public FMetalRHICommandContext