DirectX12的初始化主要分为以下若干步骤:
- 创建device和gifactory
- 创建与GPU通信同步相关的objects,command和fence
- 创建swap chain
- 为render target view和depth stencil view创建descriptor heap
- 创建back buffer和与之绑定的render target view
- 创建depth stencil buffer和与之绑定的depth stencil view
- 填充viewport和屏幕剪裁区域结构
- 绘制前:设置buffer,设置view,设置viewport和屏幕剪裁区域
- 绘制后:提交绘制,设置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_VIEWPORT
和D3D12_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_PRESENT
和D3D12_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。
如果你觉得我的文章有帮助,欢迎关注我的微信公众号(大龄社畜的游戏开发之路)-