DirectX12的初始化主要分为以下若干步骤:

  1. 创建device和gifactory
  2. 创建与GPU通信同步相关的objects,command和fence
  3. 创建swap chain
  4. 为render target view和depth stencil view创建descriptor heap
  5. 创建back buffer和与之绑定的render target view
  6. 创建depth stencil buffer和与之绑定的depth stencil view
  7. 填充viewport和屏幕剪裁区域结构
  8. 绘制前:设置buffer,设置view,设置viewport和屏幕剪裁区域
  9. 绘制后:提交绘制,设置buffer

创建device和gifactory

主要调用如下两个接口:

HRESULT D3D12CreateDevice(
IUnknown *pAdapter,
D3D_FEATURE_LEVEL MinimumFeatureLevel,
REFIID riid,
void **ppDevice
);
HRESULT CreateDXGIFactory1(
REFIID riid,
void **ppFactory
);

这里,我们可以借用微软提供的ComPtr来管理要创建的相关对象:

ComPtr提供了一些常用的接口,重载了一些常用的操作符:

  • Get():获取原始的指针
  • GetAddressOf():获取原始指针的地址
  • operator&释放原始指针指向的对象,然而再返回原始指针的地址
			ComPtr<ID3D12Device> mDevice = nullptr;
ComPtr<IDXGISwapChain> mSwapChain = nullptr;

同时,为了方便填充这些创建接口,微软提供了一个宏定义IID_PPV_ARGS

	ThrowIfFailed(D3D12CreateDevice(nullptr, D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&mDevice)));
ThrowIfFailed(CreateDXGIFactory1(IID_PPV_ARGS(&mDxGiFactory)));

创建与GPU通信同步相关的objects,command和fence

command是用来从CPU提交相关指令给GPU执行的。这里需要三个objects:queue,allocator,list。queue是用来最终提交给CPU的数据结构,allocator负责为list提供空间管理,list提供了若干CPU提交相关指令的接口。

ThrowIfFailed(mDevice->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&mCommandQueue)));
ThrowIfFailed(mDevice->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&mCommandAlloc)));
ThrowIfFailed(mDevice->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, mCommandAlloc.Get(), nullptr, IID_PPV_ARGS(&mCommandList)));
// must close otherwise call reset will raise a error
mCommandList->Close();

创建完之后记得调用Close()方法,否则后面调用Reset()时会因为状态未关闭而出错。

fence是用来管理CPU和GPU同步的对象,防止对同一资源进行冲突访问,创建接口如下:

HRESULT CreateFence(
UINT64 InitialValue,
D3D12_FENCE_FLAGS Flags,
REFIID riid,
void **ppFence
);

创建swap chain

HRESULT CreateSwapChain(
IUnknown *pDevice,
DXGI_SWAP_CHAIN_DESC *pDesc,
IDXGISwapChain **ppSwapChain
);

值得一提的是,DirectX12不支持用该API创建MSAA swap chain

Direct3D 12 don't support creating MSAA swap chains--attempts to create a swap chain with SampleDesc.Count > 1 will fail. Instead, you create your own MSAA render target and explicitly resolve to the DXGI back-buffer for presentation as shown here.

为render target view和depth stencil view创建descriptor heap

heap就是用来存放管理view的结构,相关接口如下:

UINT GetDescriptorHandleIncrementSize(
D3D12_DESCRIPTOR_HEAP_TYPE DescriptorHeapType
);
HRESULT CreateDescriptorHeap(
const D3D12_DESCRIPTOR_HEAP_DESC *pDescriptorHeapDesc,
REFIID riid,
void **ppvHeap
);

通过GetDescriptorHandleIncrementSize可以知道heap上每个handle的大小,方便在取一个heap上多个view进行偏移。

创建back buffer和与之绑定的render target view

	ThrowIfFailed(mSwapChain->ResizeBuffers(mBackBufferCount, windowWidth, windowHeight, mBackBufferFormat,
DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH));
CD3DX12_CPU_DESCRIPTOR_HANDLE handle = CD3DX12_CPU_DESCRIPTOR_HANDLE(
mRtvHeap->GetCPUDescriptorHandleForHeapStart()); for (int i = 0; i < mBackBufferCount; i++)
{
ThrowIfFailed(mSwapChain->GetBuffer(i, IID_PPV_ARGS(&mBackBuffer[i])));
mDevice->CreateRenderTargetView(mBackBuffer[i].Get(), nullptr, handle);
handle.Offset(1, mRtvHeapIncSize);
}

CD3DX12_CPU_DESCRIPTOR_HANDLE也是微软提供的便利数据结构,包含一些简单的接口。这里用了Offset方法来取得正确的heap位置。

创建depth stencil buffer和与之绑定的depth stencil view

	ThrowIfFailed(mDevice->CreateCommittedResource(&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT),
D3D12_HEAP_FLAG_NONE, &desc, D3D12_RESOURCE_STATE_COMMON, &clearValue, IID_PPV_ARGS(&mDepthStencilBuffer))); handle = CD3DX12_CPU_DESCRIPTOR_HANDLE(mDsvHeap->GetCPUDescriptorHandleForHeapStart());
mDevice->CreateDepthStencilView(mDepthStencilBuffer.Get(), &viewDesc, handle);

与back buffer的过程类似,只不过depth stencil buffer只有一个,而back buffer可能有多个。CD3DX12_HEAP_PROPERTIES同样也是便利数据结构。

注意到buffer创建完后处于D3D12_RESOURCE_STATE_COMMON状态,我们需要将其转为D3D12_RESOURCE_STATE_DEPTH_WRITE状态,使用CD3DX12_RESOURCE_BARRIER::Transition进行便捷操作:

	mCommandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(mDepthStencilBuffer.Get(),
D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_STATE_DEPTH_WRITE));
ThrowIfFailed(mCommandList->Close()); ID3D12CommandList *cmdList[] = { mCommandList.Get() };
mCommandQueue->ExecuteCommandLists(_countof(cmdList), cmdList); FlushCommandQueue();
void DirectX12::FlushCommandQueue()
{
mCurrentFence++;
ThrowIfFailed(mCommandQueue->Signal(mFence.Get(), mCurrentFence));
if (mFence->GetCompletedValue() < mCurrentFence)
{
HANDLE handle = CreateEventEx(nullptr, false, false, EVENT_ALL_ACCESS);
ThrowIfFailed(mFence->SetEventOnCompletion(mCurrentFence, handle)); WaitForSingleObject(handle, INFINITE);
CloseHandle(handle);
}
}

资源状态转换需要向GPU提交指令,因此涉及到同步问题,即CPU需要等待GPU完成指令之后才能继续执行其他操作。这里用了FlushCommandQueue来完成同步。这个方法的实现一目了然:

首先将当前fence计数值增加,signal提交到GPU,然后一直等待,直到获取到与计数值相同的值为止。

填充viewport和屏幕剪裁区域结构

这一步骤只需填充D3D12_VIEWPORTD3D12_RECT结构即可。

	mViewport.TopLeftX = 0;
mViewport.TopLeftY = 0;
mViewport.Width = windowWidth;
mViewport.Height = windowHeight;
mViewport.MinDepth = 0.0f;
mViewport.MaxDepth = 1.0f; mScissorRect.left = 0;
mScissorRect.right = windowWidth;
mScissorRect.top = 0;
mScissorRect.bottom = windowHeight;

绘制前:设置buffer,设置view,设置viewport和屏幕剪裁区域

	mCommandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(mBackBuffer[mCurBackBuffer].Get(),
D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_RENDER_TARGET));
mCommandList->RSSetScissorRects(1, &mScissorRect);
mCommandList->RSSetViewports(1, &mViewport);
CD3DX12_CPU_DESCRIPTOR_HANDLE dsv = CD3DX12_CPU_DESCRIPTOR_HANDLE(
mDsvHeap->GetCPUDescriptorHandleForHeapStart());
mCommandList->ClearDepthStencilView(dsv, D3D12_CLEAR_FLAG_DEPTH | D3D12_CLEAR_FLAG_STENCIL, 1.0f, 0, 0,
nullptr);
CD3DX12_CPU_DESCRIPTOR_HANDLE rtv = CD3DX12_CPU_DESCRIPTOR_HANDLE(
mRtvHeap->GetCPUDescriptorHandleForHeapStart(), mCurBackBuffer, mRtvHeapIncSize);
mCommandList->ClearRenderTargetView(rtv, LightSteelBlue, 0, nullptr);
mCommandList->OMSetRenderTargets(1, &rtv, true, &dsv);

这里将backBuffer的状态从D3D12_RESOURCE_STATE_PRESENT转移到了D3D12_RESOURCE_STATE_RENDER_TARGET,这样后面的设置和绘制工作才会有效,否则会出错。实际上,状态定义里,D3D12_RESOURCE_STATE_PRESENTD3D12_RESOURCE_STATE_COMMON是等价的,都为0,这里只是换了一种说法。

绘制后:提交绘制,设置buffer

	mCommandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(mBackBuffer[mCurBackBuffer].Get(),
D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_PRESENT));
ThrowIfFailed(mCommandList->Close()); ID3D12CommandList *cmdList[] = { mCommandList.Get() };
mCommandQueue->ExecuteCommandLists(_countof(cmdList), cmdList); ThrowIfFailed(mSwapChain->Present(0, 0));
mCurBackBuffer = (mCurBackBuffer + 1) % mBackBufferCount;

在调用swapChain的Present方法之前,必须将backbuffer的状态设置为D3D12_RESOURCE_STATE_PRESENT。如果使用了多个backbuffer,别忘记切换到下一个backbuffer。

如果你觉得我的文章有帮助,欢迎关注我的微信公众号(大龄社畜的游戏开发之路-

DirectX12的初始化的更多相关文章

  1. 用DirectX12绘制一个Cube

    之前一篇文章讲了DirectX12的初始化流程,现在来看看在此基础上如何绘制一个Cube. 首先,我们要为这个Cube准备一个shader,来告诉GPU绘制的具体流程,DirectX中的shader使 ...

  2. DirectX12 3D 游戏开发与实战第一章内容

    DirectX12 3D 第一章内容 学习目标 1.学习向量在几何学和数学中的表示方法 2.了解向量的运算定义以及它在几何学中的应用 3.熟悉DirectXMath库中与向量有关的类和方法 1.1 向 ...

  3. Java初始化过程

    这篇文章主要讲解Java在创建对象的时候,初始化的顺序.主要从以下几个例子中讲解: 继承关系中初始化顺序 初始化块与构造器的顺序 已经加载过的类的初始化顺序 加载父类,会不会加载子类 创建子类对象会不 ...

  4. nginx源码分析之模块初始化

    在nginx启动过程中,模块的初始化是整个启动过程中的重要部分,而且了解了模块初始化的过程对应后面具体分析各个模块会有事半功倍的效果.在我看来,分析源码来了解模块的初始化是最直接不过的了,所以下面主要 ...

  5. nginx源码分析之网络初始化

    nginx作为一个高性能的HTTP服务器,网络的处理是其核心,了解网络的初始化有助于加深对nginx网络处理的了解,本文主要通过nginx的源代码来分析其网络初始化. 从配置文件中读取初始化信息 与网 ...

  6. Java类变量和成员变量初始化过程

    一.类的初始化 对于类的初始化:类的初始化一般只初始化一次,类的初始化主要是初始化静态成员变量. 类的编译决定了类的初始化过程. 编译器生成的class文件主要对定义在源文件中的类进行了如下的更改: ...

  7. Git学习笔记一:新建本地仓库及初始化

    1.百度搜索Git下载安装,直接按默认选项安装即可. 例如:Git-2.7.2-32-bit_setup.1457942412.exe 2.配置Git信息,建立版本仓库 (Alt+PrintScerr ...

  8. Spring MVC初始化参数绑定

    初始化参数绑定与类型转换很类似,初始化绑定时,主要是参数类型 ---单日期 在处理器类中配置绑定方法  使用@InitBinder注解 在这里首先注册一个用户编辑器 参数一为目标类型   proper ...

  9. SpringMVC初始化参数绑定--日期格式

    一.初始化参数绑定[一种日期格式] 配置步骤: ①:在applicationcontext.xml中只需要配置一个包扫描器即可 <!-- 包扫描器 --> <context:comp ...

随机推荐

  1. css3系列之过渡transition

    transition 设置变换属性的过渡效果,举个例子,也就是说, 如果从width100px 变成 width200px,要以什么方式进行变换,速度,时间,类型等. transition trans ...

  2. Linux高可用之Keepalived

    1: 安装keepalived yum install -y keepalived ipvsadm 安装keepalived和LVS管理软件ipvsadm 主机与备机都需要安装 ######修改配置文 ...

  3. 加快alter table

    mysql的alter table操作的性能对打表来说是个大问题. mysql执行大部分修改表结构的方法是用新的结构创建一个空表,从旧表中查出所有的数据插入新表,然后删除旧表.这样操作就可能需要花费很 ...

  4. Free-Form Image Inpainting with Gated Convolution

    Free-Form Image Inpainting with Gated Convolution pytorch 引言 和Generative Image Inpainting with Conte ...

  5. 蒲公英 · JELLY技术周刊 Vol.32: 前端的自我进化之路

    蒲公英 · JELLY技术周刊 Vol.32 前端开发不过是切图仔在那儿瞎鼓捣,没啥门槛,不需要懂计算机原理,不需要懂算法,微服务.高并发这些都不需要懂,拖组件写个页面就好了,数据.逻辑.测试都可以交 ...

  6. 使用paho的MQTT时遇到的重连导致订阅无法收到问题和解决

    最近在使用MQTT来实现消息的传输,网上demo很多,这里就不在重复介绍了,直接上代码,百度就能出现一大堆 下面是MQTT实现订阅的主要代码部分 MqttClient client = new Mqt ...

  7. PyQt学习随笔:自定义Qt事件可以使用的事件类型的常量值范围

    除了<PyQt学习随笔:Qt事件QEvent.type类型常量及其含义资料速查>介绍的Qt已经定义的事件外,Qt还支持自定义事件. 为了方便起见,可以使用 registerEventTyp ...

  8. espcms代码审计(二次urldecode注入分析)

    这里提到的漏洞是二次urldecode注入 这里用到的还是espcms,不过版本应该跟之前的有所不同,在网上找到的espcms源代码都是已经修补了这个漏洞的,我们对比分析吧 先放上漏洞位置代码,也就是 ...

  9. 一台电脑上同时安装python2和python3

    被python2的编码问题整的快晕了,python3对编码问题解决的不错 所以想安装下python3,但由于目前企业大多还是用python2,所以不想卸载2,让python2和python3共存. 本 ...

  10. 5、Spring Cloud Ribbon

    1.Ribbon简介 (1).Ribbon介绍 Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡的工具. Ribbon是Netflix发布的开源项目,主 ...