DirectX12的初始化

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。

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

posted @ 2020-07-15 20:55  异次元的归来  阅读(654)  评论(0编辑  收藏  举报