• 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。

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

  • 相关阅读:
    python day 09 文件操作
    day python calss08 深浅copy
    python.day05
    pytonn04day
    python开发day03
    python开发day02
    python 变量名的规范
    设备描述表
    GDI基础
    windows编程-窗口
  • 原文地址:https://www.cnblogs.com/back-to-the-past/p/13307912.html
Copyright © 2020-2023  润新知