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. 如何在Visio 中插入各种数学公式?

    在Visio 2007老版本中,插入公式可以直接在插入图片中选择,但是在后来的Visio2013中却无法直接通过插入图片的方法插入,那么该如何在visio 2013中插入公式呢? 具体的操作步骤如下: ...

  2. mac实用软件推荐 mac好用的软件

    终于入手了梦寐以求的苹果电脑,但却发现其操作系统与Windows大相径庭!不会使用怎么办?不用担心,我们可以借助软件的力量.一款实用的Mac软件不仅能够使你的工作效率显著提高,同时它还能帮助你更快地熟 ...

  3. jQuery 第五章 实例方法 详解内置队列queue() dequeue() 方法

    .queue() .dequeue() .clearQueue() ------------------------------------------------------------------ ...

  4. 【爬虫】基于PUPPETEER页面爬虫

    一.简介 本文简单介绍一下如何用puppeteer抓取页面数据. 二.下载 npm install puppeteer --save-dev npm install typescrip --save- ...

  5. ECS服务器快速迁移

    ECS服务器快速迁移 前提 一.停机 二.创建镜像 三.复制镜像 前提 服务器都在同一个区域,举例:华南1(深圳) 同一个账号 具体迁移步骤如下: 一.停机 先从阿里云ECS控制台,将要迁移的两台EC ...

  6. Java数组作业

    1 //输入一组数(10个),找出最大值,并给出最大值的位置 2 package test; 3 4 public class test1_1 { 5 public static double max ...

  7. .NET使用AutoResetEvent实现多线程打印奇偶数

    AutoResetEvent 类 (System.Threading) | Microsoft Docs 定义 命名空间: System.Threading 程序集: mscorlib.dll, Sy ...

  8. 我与PHP和git不得不说的故事(梦开始的地方,从入门到放弃记录第一章)

    ·关于下载 阿瑶瑶跟wampsever官网搏斗了一下午,其实我觉得教材可能在PUA我.谷歌说它给的网址安全证书过期,然后下载以断网收场.(阿瑶的第一战,以失败告终) [经过我玲姐指点,下载路径变为迅雷 ...

  9. 适合 JS 新手学习的开源项目——在 GitHub 学编程

    作者:HelloGitHub-小鱼干 这里是 HelloGitHub 的<GitHub 上适合新手的开源项目>系列的最后一篇,系列文章: C++ 篇 Python 篇 Go 篇 Java ...

  10. 在Python中使用moviepy进行视频剪辑时输出文件报错 ‘NoneType‘ object has no attribute ‘stdout‘问题

    专栏:Python基础教程目录 专栏:使用PyQt开发图形界面Python应用 专栏:PyQt入门学习 老猿Python博文目录 老猿学5G博文目录 movipy输出文件时报错 'NoneType' ...