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

主要调用如下两个接口:

  1. HRESULT D3D12CreateDevice(
  2. IUnknown *pAdapter,
  3. D3D_FEATURE_LEVEL MinimumFeatureLevel,
  4. REFIID riid,
  5. void **ppDevice
  6. );
  1. HRESULT CreateDXGIFactory1(
  2. REFIID riid,
  3. void **ppFactory
  4. );

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

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

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

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

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

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

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

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

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

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

  1. HRESULT CreateFence(
  2. UINT64 InitialValue,
  3. D3D12_FENCE_FLAGS Flags,
  4. REFIID riid,
  5. void **ppFence
  6. );

创建swap chain

  1. HRESULT CreateSwapChain(
  2. IUnknown *pDevice,
  3. DXGI_SWAP_CHAIN_DESC *pDesc,
  4. IDXGISwapChain **ppSwapChain
  5. );

值得一提的是,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的结构,相关接口如下:

  1. UINT GetDescriptorHandleIncrementSize(
  2. D3D12_DESCRIPTOR_HEAP_TYPE DescriptorHeapType
  3. );
  1. HRESULT CreateDescriptorHeap(
  2. const D3D12_DESCRIPTOR_HEAP_DESC *pDescriptorHeapDesc,
  3. REFIID riid,
  4. void **ppvHeap
  5. );

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

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

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

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

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

  1. ThrowIfFailed(mDevice->CreateCommittedResource(&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT),
  2. D3D12_HEAP_FLAG_NONE, &desc, D3D12_RESOURCE_STATE_COMMON, &clearValue, IID_PPV_ARGS(&mDepthStencilBuffer)));
  3. handle = CD3DX12_CPU_DESCRIPTOR_HANDLE(mDsvHeap->GetCPUDescriptorHandleForHeapStart());
  4. 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进行便捷操作:

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

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

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

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

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

  1. mViewport.TopLeftX = 0;
  2. mViewport.TopLeftY = 0;
  3. mViewport.Width = windowWidth;
  4. mViewport.Height = windowHeight;
  5. mViewport.MinDepth = 0.0f;
  6. mViewport.MaxDepth = 1.0f;
  7. mScissorRect.left = 0;
  8. mScissorRect.right = windowWidth;
  9. mScissorRect.top = 0;
  10. mScissorRect.bottom = windowHeight;

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

  1. mCommandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(mBackBuffer[mCurBackBuffer].Get(),
  2. D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_RENDER_TARGET));
  3. mCommandList->RSSetScissorRects(1, &mScissorRect);
  4. mCommandList->RSSetViewports(1, &mViewport);
  5. CD3DX12_CPU_DESCRIPTOR_HANDLE dsv = CD3DX12_CPU_DESCRIPTOR_HANDLE(
  6. mDsvHeap->GetCPUDescriptorHandleForHeapStart());
  7. mCommandList->ClearDepthStencilView(dsv, D3D12_CLEAR_FLAG_DEPTH | D3D12_CLEAR_FLAG_STENCIL, 1.0f, 0, 0,
  8. nullptr);
  9. CD3DX12_CPU_DESCRIPTOR_HANDLE rtv = CD3DX12_CPU_DESCRIPTOR_HANDLE(
  10. mRtvHeap->GetCPUDescriptorHandleForHeapStart(), mCurBackBuffer, mRtvHeapIncSize);
  11. mCommandList->ClearRenderTargetView(rtv, LightSteelBlue, 0, nullptr);
  12. 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

  1. mCommandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(mBackBuffer[mCurBackBuffer].Get(),
  2. D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_PRESENT));
  3. ThrowIfFailed(mCommandList->Close());
  4. ID3D12CommandList *cmdList[] = { mCommandList.Get() };
  5. mCommandQueue->ExecuteCommandLists(_countof(cmdList), cmdList);
  6. ThrowIfFailed(mSwapChain->Present(0, 0));
  7. 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. JUC并发工具包之CyclicBarrier

    1.简介 CyclicBarrier是一个同步器,允许多个线程等待彼此直到达一个执行点(barrier). CyclicBarrier都是在多个线程必须等到彼此都到达同一个执行点后才执行一段逻辑时才被 ...

  2. vim进入粘贴模式

    最近使用linux的vim编辑器编写程序时,遇到一些繁琐的模板想要复制粘贴进去,直接进入插入模式点复制,复制出来的格式不对没办法运行 解决办法: 这是因为 Vim 自动缩进了,按照如下设置可以解决该问 ...

  3. P1633 二进制

    首先将 \(A,B,C\) 二进制中 \(1\) 的个数和最大长度 \(L\) 算出来. 接着考虑二进制位相加的情况: 低位不进上来. \(X\) 和 \(Y\) 中的两个 \(1\) 合成 \(Z\ ...

  4. Android自带图标库

    Java Usage example: myMenuItem.setIcon(android.R.drawable.ic_menu_save); Resource Usage example: and ...

  5. Robot Framework接口自动化案例分享⑦——Jenkins持续集成

    一.RobotFramework插件安装 1.Jenkins首页->系统管理->插件管理->可选插件-> 2.搜索robot,点击直接安装 二.任务参数配置 1.新建任务 Je ...

  6. 初学者值得拥有【Hadoop伪分布式模式安装部署】

    目录 1.了解单机模式与伪分布模式有何区别 2.安装好单机模式的Hadoop 3.修改Hadoop配置文件---五个核心配置文件 (1)hadoop-env.sh 1.到hadoop目录中 ​ 2.修 ...

  7. 老猿学5G:3GPP 5G规范中的URI资源概念

    ☞ ░ 前往老猿Python博文目录 ░ 说明: 本文参考3GPP29.501<Principles and Guidelines for Services Definition>结合笔者 ...

  8. PyQt(Python+Qt)学习随笔:QTableWidget的构造方法

    老猿Python博文目录 专栏:使用PyQt开发图形界面Python应用 老猿Python博客地址 QTableWidget有2个构造方法: QTableWidget(QWidget parent = ...

  9. 《30天自制操作系统》软盘 -> VMware虚拟机

    书名叫做30天自制操作系统,按照学校课设答辩的时间来看,估计得把书名改成<一周自制操作系统>,太卷了哈哈哈 我们可以使用qemu来模拟物理机 make run第二天制作的操作系统 可以看到 ...

  10. anaconda 配置虚拟环境

    工作时有时候会遇到不同版本的问题,比如深度学习tensorflow 1.14版本 和 2.0版本,或者cpu版本和gpu版本,那么这个时候建立虚拟环境就很方便了 anaconda命令行下 1) act ...