【UE4源代码分析】-009 寻找UE4的D3D设备

【UE4源代码分析】-009 寻找UE4的D3D设备

  UE4作为一款3D游戏引擎,当然也离不开显卡的支持。目前,主流的3D API主要包括DirectX、OpenGL、Vankul三种。其中,OpenGL和Vankul是支持跨平台的,而DirectX只能在windows平台上使用。UE4在windows平台上可以选择使用DX12或DX11等directX设备作为渲染设备。而D3D设备在使用之前是必须先创建的。
  那么,今天让我们一起来探寻windows下使用DirectX作为渲染API时是在什么时候创建的D3D设备。

1 创建过程

  在上一篇中,我们知道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<IDXGIFactory1> 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设备就被创建出来并用于相关资源的初始化工作,待渲染进行开始之后就可以用于渲染。

2 RHI

  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

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值