DirectX12 初始化
一、Dx12初始化全流程
渲染,可以简单理解为一张指定效果的图片生成,而这项生成看作一个任务,由CPU自己处理比较耗时。
因此就采用了一个方案,由CPU负责发布生成任务计划,转交给更擅长生成任务的GPU进行具体生成任务的执行,最后将结果转交到CPU,最终放到显示器上;
正好Dx12就是个好帮手,它提供了一个通用的解决方案,大部分繁杂而相同的工作都由它做好了基础。
因此在初始化阶段,我们重点在于CPU到GPU的任务转交流程,因为Dx12,我们只要做好 理解通用方案 和 部分本地化1 就好,其过程如下:
1、引入Dx12的通用方案的基础支持(CreateFactory,CreateDevice);
2、本地化上传模型 - CMD三件套(CreateCMDQueueListAlloctor);
3、本地化下载模型 - 交换链&MASS(SetMSAA,CreateSwapChain);
4、准备基础资源和空间 - Heap&Descriptor(CreateHeap,CreateRTV,CreateDSV);
5、做好CPU和GPU同步(CreateFence);
6、确定显示结果(CreateViewPortAndScissorRect);
BOOL cDx12Rendering::Init(cDx12RenderConfig* io_cDx12RenderConfig)
{
CreateFactory();
CreateDevice();
CreateCMDQueueListAlloctor();
SetMSAA();
CreateSwapChain(io_cDx12RenderConfig);
CreateHeap(io_cDx12RenderConfig->iSwapChainCount);
CreateRTV(io_cDx12RenderConfig->iSwapChainCount);
CreateDSV(io_cDx12RenderConfig);
CreateFence();
CreateViewPortAndScissorRect(io_cDx12RenderConfig);
return TRUE;
}
二、引入Dx12的通用方案的基础支持
首要,在引入Dx12给出的通用方案之前,就好准备好一些后续的工具;
1、头文件
我们用Dx12去渲染,其本质是运用别人写好的工具和通用方案,结合我们本地化进行使用,因此要先把别人的工具拿到手,因此,我们先和windows说:”兄弟,搭把手,把以下这些工具给我用用?“。
首先是,Dx12的主要工具,这些工具是直接包含在Windows的工具包中的,因此不需要专门去下载,直接包含头文件和链接Dx12库即可。
#pragma comment(lib, "d3d12.lib")
#include <d3d12.h>
其次微软还提供了很多辅助类,这些类封装了一些函数可以帮助我们快速的构建一些D3D12对象。这些类在头文件 d3dx12.h 中,属于开源资源,可以在GitHub上下载到。
#include "d3dx12.h"
还有,随着图形学的发展,DirectX有很多版本,很多模块都在不断的进化和演变。然而也会有些公共模块,它独立于DirectX,并且每个DirectX版本都需要使用到。例如枚举硬件设备,管理全屏模式的转换,控制gamma等。DirectX Graphics Infrastructure (DXGI) 的作用就是将这些独立于DirectX的公共功能封装起来,供不同版本的DirextX使用。
#pragma comment(lib, "dxgi.lib")
#include <dxgi1_4.h>
同时,也要强调,DirectX使用的是COM(Component Object Model)技术,d3d12相关的头文件中为我们提供了很多的Direct3D12的接口(全是纯虚函数的类),我们可以使用Windows Runtime Library (WRL) 提供的智能指针(Microsoft::WRL::ComPtr)来管理Direct3D12对象,因此需要包含:
#include <wrl.h>
using Microsoft::WRL::ComPtr;
2、创建DXGI工厂(DXGIFactory)
DirectX为了处理与硬件进行交互,命令硬件完成一些工作2,封装了两个基础的接口,即DXGI工厂(DXGIFactory)和设备(Device)。
其中DXGI工厂的接口就是用于创建其它的所有DXGI对象;
void cDx12Rendering::CreateFactory()
{
//创建DXGI工厂
CreateDXGIFactory1(IID_PPV_ARGS(&m_spDXGIFactory));
}
其具体的创建方法是通过CreateDXGIFactory1函数创建:
HRESULT CreateDXGIFactory1(
REFIID riid,
[out] void **ppFactory
);
a、riid:设备接口的全局唯一标识符 (GUID) ,此参数和 ppDevice 可以使用单个宏 IID_PPV_ARGS 进行寻址。
b、ppFactory:指向接收指向 DXGI 1.1 工厂的指针的内存块的指针,com的经典二级指针用法,可为空。
3、创建设备(Device)
设备接口用于检测显示适配器功能和分配资源;
void cDx12Rendering::CreateDevice()
{
//创建设备
HRESULT D3dDeviceResult = D3D12CreateDevice(NULL, D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&m_spD3dDevice));
if (FAILED(D3dDeviceResult))
{
ComPtr<IDXGIAdapter> spWARPAdapter;
m_spDXGIFactory->EnumWarpAdapter(IID_PPV_ARGS(&spWARPAdapter));
D3D12CreateDevice(spWARPAdapter.Get(), D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&m_spD3dDevice));
}
}
其具体的创建方法是通过D3D12CreateDevice函数创建:
HRESULT D3D12CreateDevice(
[in, optional] IUnknown *pAdapter,
D3D_FEATURE_LEVEL MinimumFeatureLevel,
[in] REFIID riid,
[out, optional] void **ppDevice
);
a、pAdapter :指向创建设备时要使用的视频适配器的指针。
b、MinimumFeatureLevel :指要求该显示适配器最低支持的 DirectX 的版本,如果显示适配器无法支持该版本,该函数会返回创建失败的异常报告。
c、riid :设备接口的全局唯一标识符 (GUID) 。
d、ppDevice :指向接收指向设备的指针的内存块的指针。
e、HRESULT:windows御用函数返回值,相较于bool类型,可以详细地说明函数调用过程中的各种情况,而不仅仅标明函数调用成功或失败。
三、本地化上传模型 - CMD三件套
在此阶段要根据Dx12的通用方案,本地化CMD提交模型,即从CPU到GPU的任务提交过程,其过程类似于快递,分为三个部分:
命令队列:接收和送入GPU的流水线队列;
命令分配器:能快速打包的高手;
命令列表:含带基础资源的一个任务包裹;
官方给出的机制设计图如下:
void cDx12Rendering::CreateCMDQueueListAlloctor()
{
//命令队列的创建
D3D12_COMMAND_QUEUE_DESC cQueueDesc = {};
cQueueDesc.Type = D3D12_COMMAND_LIST_TYPE::D3D12_COMMAND_LIST_TYPE_DIRECT;
cQueueDesc.Flags = D3D12_COMMAND_QUEUE_FLAGS::D3D12_COMMAND_QUEUE_FLAG_NONE;
m_spD3dDevice->CreateCommandQueue(&cQueueDesc, IID_PPV_ARGS(&m_spCommandQueue));
//命令分配器的创建
m_spD3dDevice->CreateCommandAllocator(
D3D12_COMMAND_LIST_TYPE::D3D12_COMMAND_LIST_TYPE_DIRECT,
IID_PPV_ARGS(m_spCommandAllocator.GetAddressOf()));
//命令列表的创建
m_spD3dDevice->CreateCommandList(
0, //默认单个Gpu
D3D12_COMMAND_LIST_TYPE::D3D12_COMMAND_LIST_TYPE_DIRECT,
m_spCommandAllocator.Get(),//将Commandlist关联到Allocator
NULL,//ID3D12PipelineState
IID_PPV_ARGS(m_spGraphicsCommandList.GetAddressOf()));
m_spGraphicsCommandList->Close();
}
1、命令队列(CommandQueue)的创建
CommandQueue :接口用于接受命令放在GPU等待处理,这个队列是由GPU所维护,该接口的提出与CPU和GPU同步处理相关。
D3D12_COMMAND_QUEUE_DESC cQueueDesc = {};
cQueueDesc.Type = D3D12_COMMAND_LIST_TYPE::D3D12_COMMAND_LIST_TYPE_DIRECT;
cQueueDesc.Flags = D3D12_COMMAND_QUEUE_FLAGS::D3D12_COMMAND_QUEUE_FLAG_NONE;
m_spD3dDevice->CreateCommandQueue(&cQueueDesc, IID_PPV_ARGS(&m_spCommandQueue));
其具体的创建方法是通过Device的CreateCommandQueue函数创建:
HRESULT CreateCommandQueue(
const D3D12_COMMAND_QUEUE_DESC *pDesc,
REFIID riid,
void **ppCommandQueue
);
a、pDesc:指定描述命令队列 的D3D12_COMMAND_QUEUE_DESC 。
b、riid :接口的全局唯一标识符 (GUID) 。
c、ppCommandAllocator:指向接收指向设备的指针的内存块的指针。
2、命令分配器(CommandAllocator)的创建
CommandAllocator :接口用于图形处理单元 (GPU) 命令的存储分配,即实际上命令存储的内存空间;
m_spD3dDevice->CreateCommandAllocator(
D3D12_COMMAND_LIST_TYPE::D3D12_COMMAND_LIST_TYPE_DIRECT,
IID_PPV_ARGS(m_spCommandAllocator.GetAddressOf()));
其具体的创建方法是通过Device的CreateCommandAllocator函数创建:
HRESULT CreateCommandAllocator(
[in] D3D12_COMMAND_LIST_TYPE type,
REFIID riid,
[out] void **ppCommandAllocator
);
a、type:一个 D3D12_COMMAND_LIST_TYPE 类型的值,它指定要创建的命令分配器的类型。 命令分配器的类型可以是记录直接命令列表或捆绑包的类型。
b、riid :设备接口的全局唯一标识符 (GUID) 。
c、ppCommandAllocator:指向接收指向设备的指针的内存块的指针。
3、命令列表(GraphicsCommandList)的创建
GraphicsCommandList:接口用于对命令进行操作,将多个操作合并,类似于数据库的事务性操作 ;
m_spD3dDevice->CreateCommandList(
0, //默认单个Gpu
D3D12_COMMAND_LIST_TYPE::D3D12_COMMAND_LIST_TYPE_DIRECT,
m_spCommandAllocator.Get(),//将Commandlist关联到Allocator
NULL,//ID3D12PipelineState
IID_PPV_ARGS(m_spGraphicsCommandList.GetAddressOf()));
m_spGraphicsCommandList->Close();
其具体的创建方法是通过Device的CreateCommandList函数创建:
HRESULT CreateCommandList(
[in] UINT nodeMask,
[in] D3D12_COMMAND_LIST_TYPE type,
[in] ID3D12CommandAllocator *pCommandAllocator,
[in, optional] ID3D12PipelineState *pInitialState,
[in] REFIID riid,
[out] void **ppCommandList
);
a、nodeMask:对于单 GPU 操作,请将此项设置为零。 如果有多个 GPU 节点,则设置一个位来标识要为其创建命令列表的设备物理适配器 (节点) 。 掩码中的每个位都对应一个节点。 必须仅设置一位。 另请参阅多适配器系统。
b、type:指定要创建的命令列表的类型,同CreateCommandAllocator的type参数。
c、pCommandAllocator:指向设备在其中创建命令列表的命令分配器对象的指针。
d、pInitialState:指向包含命令列表的初始管道状态的管道状态对象的可选指针,暂为nullptr即可【未明确待后续补充】。
e、riid:设备接口的全局唯一标识符 (GUID)。
f、ppCommandList:指向接收指向设备的指针的内存块的指针,com的经典二级指针用法,可为空。
四、本地化下载模型 - MASS&交换链
在此阶段,本意是处理GPU渲染处理后的画面接收到CPU去显示,只需要一个缓冲区存放送显示即可。
但在这个阶段还存在两个要解决的问题,撕裂和 锯齿 。
1、抗锯齿的MASS
画面锯齿,即画面上的图形边缘等地方存在锯齿的问题,其本质是采样不足。
画面想要一个这样的边缘光滑三角形;
但实际得到的画面是一个这样的边缘充满锯齿的三角形(已经不能算是三角形了);
针对这个问题,Dx12提供了MASS(多重采样)的解决方案;
void cDx12Rendering::SetMSAA()
{
D3D12_FEATURE_DATA_MULTISAMPLE_QUALITY_LEVELS cQualityLevels;
cQualityLevels.Format = m_dfBackBufferFormat;
cQualityLevels.SampleCount = 4;
cQualityLevels.Flags = D3D12_MULTISAMPLE_QUALITY_LEVEL_FLAGS::D3D12_MULTISAMPLE_QUALITY_LEVELS_FLAG_NONE;
cQualityLevels.NumQualityLevels = 0;
m_spD3dDevice->CheckFeatureSupport(
D3D12_FEATURE_MULTISAMPLE_QUALITY_LEVELS,
&cQualityLevels,
sizeof(cQualityLevels));
m_iM4XQualityLevels = cQualityLevels.NumQualityLevels;
}
其具体的创建方法是通过D3dDevice的CheckFeatureSupport函数创建:
HRESULT CheckFeatureSupport(
[in] D3D12_FEATURE Feature,
[in, out] void *pFeatureSupportData,
[out] UINT FeatureSupportDataSize
);
a、Feature:D3D12_FEATURE 枚举中的常量。
b、pFeatureSupportData:指向对应于 Feature 参数值的数据结构的指针。 若要确定每个常量对应的数据结构,详见 D3D12_FEATURE。
c、FeatureSupportDataSize:参数指向的结构的大小。
2、抗撕裂的交换链
画面撕裂,其表现为出现第N帧画面一半是第N-1帧画面,另一半是第n帧画面的问题,因此Dx12提供了一个 交换链模型 。
这个模型大致是从GPU拿到渲染结果的时候,第N帧画面会被存储在缓冲区A中,此时下一帧的画面渲染结果会存放在缓冲区B中,经过一个渲染周期的时间,缓冲区B内第N+1帧画面渲染完全,此时只需要交换指针,让缓冲区B来到前台,缓冲区A去后台继续清除数据,渲染第N+2帧画面的数据,如此往复即可;
前台缓冲区:存储屏幕上当前展示的图像数据;
后台缓冲区:存储绘制计算中得到的数据;
void cDx12Rendering::CreateSwapChain(cDx12RenderConfig* io_cDx12RenderConfig)
{
m_spSwapChain.Reset();
DXGI_SWAP_CHAIN_DESC cSwapChainDesc;
cSwapChainDesc.BufferDesc.Width = io_cDx12RenderConfig->iResolutionWidth;//分辨率宽度
cSwapChainDesc.BufferDesc.Height = io_cDx12RenderConfig->iResolutionHight;//分辨率高
cSwapChainDesc.BufferDesc.RefreshRate.Numerator = io_cDx12RenderConfig->iRefreshRate;//刷新率
cSwapChainDesc.BufferDesc.RefreshRate.Denominator = 1;
cSwapChainDesc.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER::DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
cSwapChainDesc.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;
cSwapChainDesc.BufferCount = io_cDx12RenderConfig->iSwapChainCount;
cSwapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;//使用表面或资源作为输出渲染目标。
cSwapChainDesc.OutputWindow = io_cDx12RenderConfig->hHandle;//指定windows句柄
cSwapChainDesc.Windowed = true;//以窗口运行
cSwapChainDesc.SwapEffect = DXGI_SWAP_EFFECT::DXGI_SWAP_EFFECT_FLIP_DISCARD;
cSwapChainDesc.Flags = DXGI_SWAP_CHAIN_FLAG::DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;//IDXGISwapChain::ResizeTarget
cSwapChainDesc.BufferDesc.Format = m_dfBackBufferFormat;//纹理格式
//多重采样设置
cSwapChainDesc.SampleDesc.Count = m_bMSAA4XEnabled ? 4 : 1;
cSwapChainDesc.SampleDesc.Quality = m_bMSAA4XEnabled ? (m_iM4XQualityLevels - 1) : 0;
m_spDXGIFactory->CreateSwapChain(
m_spCommandQueue.Get(),
&cSwapChainDesc, m_spSwapChain.GetAddressOf());
m_spSwapChain->ResizeBuffers(
io_cDx12RenderConfig->iSwapChainCount,
io_cDx12RenderConfig->iScrrenWidth,
io_cDx12RenderConfig->iScrrenHight,
m_dfBackBufferFormat, DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH);
}
其具体的创建方法是通过DXGIFactory的CreateSwapChain函数创建:
HRESULT CreateSwapChain(
[in] IUnknown *pDevice,
[in] DXGI_SWAP_CHAIN_DESC *pDesc,
[out] IDXGISwapChain **ppSwapChain
);
a、pDevice:对于 Direct3D 11 和早期版本的 Direct3D,这是指向交换链的 Direct3D 设备的指针。 对于 Direct3D 12,这是指向直接命令队列的指针,(引用 ID3D12CommandQueue),此参数不能为 NULL。
b、pDesc:指向交换链说明 DXGI_SWAP_CHAIN_DESC 结构的指针。 此参数不能为 NULL 。
c、ppSwapChain:指向接收指向交换链的指针的内存块的指针 。
五、准备基础资源和空间 - Heap&Descriptor
上传,下载,都处理好了,接下来就要处理支持操作这些的资源了。
在这一阶段,我们就要涉及到Dx12对于GPU显存的管理规划了,其主要是对于描述符堆,描述符,资源的理解;
如上图,CPU传入的资源,并不直接绑定到(PSO)渲染流水线3上,而是通过名为描述符(Descriptor)的中间对象来绑定,也称视图(view),我目前理解为类似指针。
这些描述符都会根据类型,存放在相关的描述符堆(Descriptor Heap)中,我目前理解为类似指针数组,其目标是以便后续渲染流水线在对应阶段进行使用,例如,流水线存在ABC三个阶段,A阶段就去遍历A描述符堆,依次对里面的描述符进行处理。
每个描述符都有一种具体的类型,这个类型指定了资源的具体作用,常见的有:
- CBV:常量缓冲区视图(constant buffer view),
- SRV:着色资源视图(shader resource view)
- UAV:无序访问视图(unordered access view)
- sampler:采样器资源
- RTV:渲染目标视图(render targe view)
- DSV:深度/模板视图(depth/stencil view)
这里面每种视图对应的都是一种资源,但一种资源可以被多个视图对应。
1、创建描述符堆(初始化只用做RTV和DSV的)
void cDx12Rendering::CreateHeap(int i_iSwapChainCount)
{
//创建RTV描述符堆
D3D12_DESCRIPTOR_HEAP_DESC cRTVDescriptorHeapDesc;
cRTVDescriptorHeapDesc.NumDescriptors = i_iSwapChainCount;
cRTVDescriptorHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
cRTVDescriptorHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
cRTVDescriptorHeapDesc.NodeMask = 0;
m_spD3dDevice->CreateDescriptorHeap(
&cRTVDescriptorHeapDesc,
IID_PPV_ARGS(m_spRTVHeap.GetAddressOf()));
//创建DSV描述符堆
D3D12_DESCRIPTOR_HEAP_DESC cDSVDescriptorHeapDesc;
cDSVDescriptorHeapDesc.NumDescriptors = 1;
cDSVDescriptorHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_DSV;
cDSVDescriptorHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
cDSVDescriptorHeapDesc.NodeMask = 0;
m_spD3dDevice->CreateDescriptorHeap(
&cDSVDescriptorHeapDesc,
IID_PPV_ARGS(m_spDSVHeap.GetAddressOf()));
}
其具体的创建方法是通过D3dDevice的CreateDescriptorHeap函数创建:
HRESULT CreateDescriptorHeap(
[in] const D3D12_DESCRIPTOR_HEAP_DESC *pDescriptorHeapDesc,
REFIID riid,
[out] void **ppvHeap
);
a、pDescriptorHeapDesc:指向描述堆的D3D12_DESCRIPTOR_HEAP_DESC 结构的指针。
b、riid:接口的全局唯一标识符 (GUID) 。
c、ppvHeap:指向接收指向资源描述符的指针的内存块的指针 。
2、创建RTV资源和描述符
void cDx12Rendering::CreateRTV(int i_iSwapChainCount)
{
for (int i = 0; i < i_iSwapChainCount; i++)
{
vecRTVBuffer.push_back(ComPtr<ID3D12Resource>());
}
//拿到描述size
m_iRTVDescriptorSize = m_spD3dDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
CD3DX12_CPU_DESCRIPTOR_HANDLE cRTVHeapHandle(m_spRTVHeap->GetCPUDescriptorHandleForHeapStart());
for (int i = 0; i < i_iSwapChainCount; i++)
{
m_spSwapChain->GetBuffer(i, IID_PPV_ARGS(&vecRTVBuffer[i]));
//RTV描述符放入RTV描述符堆中
m_spD3dDevice->CreateRenderTargetView(vecRTVBuffer[i].Get(), nullptr, cRTVHeapHandle);
cRTVHeapHandle.Offset(1, m_iRTVDescriptorSize);
}
}
其具体的创建方法是通过D3dDevice的CreateRenderTargetView函数创建:
void CreateRenderTargetView(
[in, optional] ID3D12Resource *pResource,
[in, optional] const D3D12_RENDER_TARGET_VIEW_DESC *pDesc,
[in] D3D12_CPU_DESCRIPTOR_HANDLE DestDescriptor
);
a、pResource:指向将用作呈现目标 的资源 的指针。 此资源必须已使用 D3D10_BIND_RENDER_TARGET 标志创建。
b、pDesc:指向 render-target-view 说明的指针, (请参阅 D3D10_RENDER_TARGET_VIEW_DESC) 。 将此参数设置为 NULL 可创建一个视图,该视图使用) 创建资源的格式访问整个资源 (的 mipmap 级别 0。
c、DestDescriptor:指向 ID3D10RenderTargetView 的指针的地址。 将此参数设置为 NULL 以验证其他输入参数, (如果其他输入参数通过验证) ,该方法将返回S_FALSE。
3、创建DSV资源和描述符
void cDx12Rendering::CreateDSV(cDx12RenderConfig* io_cDx12RenderConfig)
{
//创建DSV描述符
D3D12_RESOURCE_DESC cDSVDesc;
cDSVDesc.Width = io_cDx12RenderConfig->iScrrenWidth;
cDSVDesc.Height = io_cDx12RenderConfig->iScrrenHight;
cDSVDesc.Alignment = 0;
cDSVDesc.MipLevels = 1;
cDSVDesc.DepthOrArraySize = 1;
cDSVDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
cDSVDesc.SampleDesc.Count = m_bMSAA4XEnabled ? 4 : 1;
cDSVDesc.SampleDesc.Quality = m_iM4XQualityLevels ? (m_iM4XQualityLevels - 1) : 0;
cDSVDesc.Format = DXGI_FORMAT_R24G8_TYPELESS;
cDSVDesc.Flags = D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL;
cDSVDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;
D3D12_CLEAR_VALUE cOptClear;
cOptClear.DepthStencil.Depth = 1.f;
cOptClear.DepthStencil.Stencil = 0;
cOptClear.Format = m_dfDepthStencilFormat;
CD3DX12_HEAP_PROPERTIES Properties = CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT);
m_spD3dDevice->CreateCommittedResource(
&Properties,
D3D12_HEAP_FLAG_NONE, &cDSVDesc,
D3D12_RESOURCE_STATE_COMMON, &cOptClear,
IID_PPV_ARGS(m_spDepthStencilBuffer.GetAddressOf()));
//DSV描述符放入DSV描述符堆中
D3D12_DEPTH_STENCIL_VIEW_DESC DSVDesc;
DSVDesc.Format = m_dfDepthStencilFormat;
DSVDesc.Texture2D.MipSlice = 0;
DSVDesc.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2D;
DSVDesc.Flags = D3D12_DSV_FLAG_NONE;
m_spD3dDevice->CreateDepthStencilView(m_spDepthStencilBuffer.Get(),
&DSVDesc,
m_spDSVHeap->GetCPUDescriptorHandleForHeapStart());
CD3DX12_RESOURCE_BARRIER Barrier = CD3DX12_RESOURCE_BARRIER::Transition(
m_spDepthStencilBuffer.Get(),
D3D12_RESOURCE_STATE_COMMON,
D3D12_RESOURCE_STATE_DEPTH_WRITE);
m_spGraphicsCommandList->Reset(m_spCommandAllocator.Get(), NULL);
m_spGraphicsCommandList->ResourceBarrier(1, &Barrier);
m_spGraphicsCommandList->Close();
ID3D12CommandList* CommandList[] = { m_spGraphicsCommandList.Get() };
m_spCommandQueue->ExecuteCommandLists(_countof(CommandList), CommandList);
}
其具体的创建方法是通过D3dDevice的CreateDescriptorHeap函数创建:
HRESULT CreateDepthStencilView(
[in] ID3D10Resource *pResource,
[in] const D3D10_DEPTH_STENCIL_VIEW_DESC *pDesc,
[out] ID3D10DepthStencilView **ppDepthStencilView
);
a、pResource:指向将用作深度模具图面 的资源 的指针。 此资源必须已使用 D3D10_BIND_DEPTH_STENCIL 标志创建。
b、pDesc:指向深度模具视图说明的指针, (查看 D3D10_DEPTH_STENCIL_VIEW_DESC) 。 将此参数设置为 NULL 可创建一个视图,该视图使用) 创建资源的格式访问整个资源 (的 mipmap 级别 0。
c、ppDepthStencilView:指向 ID3D10DepthStencilView 的指针的地址。 将此参数设置为 NULL 以验证其他输入参数, (如果其他输入参数通过验证) ,该方法将返回S_FALSE。
六、做好CPU和GPU同步
DirectX在渲染过程中,CPU与GPU之间需要相互沟通,也就出现了很多CPU和GPU之间工作的同步问题,例如在执行CPU传来的命令时,GPU一时无法处理完成,在持续引用和占用相关的资源,而CPU执行快了,在GPU完成处理前就对这些被占用资源进行修改,这会导致很多预料之外的错误,因此为了解决这样的问题,提出了围栏的概念;
1、围栏(Fence)的创建
void cDx12Rendering::CreateFence()
{
//创建围栏
m_spD3dDevice->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&m_spFence));
}
围栏(Fence),是 Device 所创建用于同步的工具,它维护着一个围栏值,而这个值我们可以在命令队列以及命令列表中调用相应方法对其进行更新,GPU完成处理修改此值,CPU对比此值判断是否可以进行下一步。
其具体的创建方法是通过Device的CreateFence函数创建:
HRESULT CreateFence(
UINT64 InitialValue,
D3D11_FENCE_FLAG Flags,
REFIID ReturnedInterface,
[out] void **ppFence
);
a、InitialValue:围栏初始化时候的围栏值,初始化时将其设置为0即可。
b、Flags:使用按位 OR 操作2 组合 的D3D11_FENCE_FLAG类型化值的组合,生成的值指定围栏的选项 。
c、ReturnedInterface:围栏接口 (ID3D11Fence) 的全局唯一标识符 (GUID) 。
d、ppFence:指向接收指向围栏的指针的内存块的指针。
2、围栏(Fence)的使用
创建围栏之后,就要用围栏来保证CPU和GPU之间工作的同步问题,即CPU要在GPU处理的时候,不能等待,而是处理其他,等到GPU处理完成之后,CPU再通过接收到结束信号,处理GPU的处理结果,放置到显示上;
void cDx12Rendering::WaitGPUComplete()
{
m_iCurFenceIndex++;
//向GUP设置新的隔离点 等待GPU处理玩信号
m_spCommandQueue->Signal(m_spFence.Get(), m_iCurFenceIndex);
if (m_spFence->GetCompletedValue() < m_iCurFenceIndex)
{
HANDLE hEventEX = CreateEventEx(NULL, NULL, 0, EVENT_ALL_ACCESS);
//GPU完成后会通知我们的Handle
m_spFence->SetEventOnCompletion(m_iCurFenceIndex, hEventEX);
//等待GPU,阻塞主线程
WaitForSingleObject(hEventEX, INFINITE);
CloseHandle(hEventEX);
}
}
CommandQueue的成员函数Signal,用于发信号调整围栏值。
HRESULT STDMETHODCALLTYPE Signal(
ID3D12Fence *pFence,
UINT64 Value
);
a、pFence:指向围栏对象的指针。
b、Value:要设置围栏的值。
Fence的成员函数GetCompletedValue,可以返回围栏的当前值。
UINT64 GetCompletedValue();
如果设备已删除,则返回值将 UINT64_MAX。
Fence的成员函数SetEventOnCompletion,用于当指定当栅栏达到特定值时,引发的指定事件。
HRESULT SetEventOnCompletion(
UINT64 Value,
HANDLE hEvent
);
a、Value:事件发出信号时的栅栏值。
b、hEvent:事件对象的句柄(Handle)。
七、确定显示结果
一张图片,并不是必要的全部显示的,因此可以用视口来确定,有多少用于显示,这里只是先设定视口,视口的使用还要等到绘制的时候。
void cDx12Rendering::CreateViewPortAndScissorRect(cDx12RenderConfig* io_cDx12RenderConfig)
{
//描述视口尺寸
m_dvViewprotInfo.TopLeftX = 0;
m_dvViewprotInfo.TopLeftY = 0;
m_dvViewprotInfo.Width = 100;
m_dvViewprotInfo.Height = 100;
m_dvViewprotInfo.MinDepth = 0.f;
m_dvViewprotInfo.MaxDepth = 1.f;
//矩形
m_drViewprotRect.left = 0;
m_drViewprotRect.top = 0;
m_drViewprotRect.right = io_cDx12RenderConfig->iScrrenWidth;
m_drViewprotRect.bottom = io_cDx12RenderConfig->iScrrenHight;
}
八、Dx12资源准备部分的全部代码
在总体代码实现的时候,考虑到还有上下文,因此,我们把Dx12渲染直接做成一个类,因此Dx12资源准备指代的就是这个类的Init ( ) 方法。
Dx12Render.h
#include"Core.h"
#include"Dx12RenderConfig.h"
#include<vector>
class cDx12Rendering
{
public:
cDx12Rendering();
~cDx12Rendering();
public:
BOOL Init(cDx12RenderConfig* io_cDx12RenderConfig);
private:
void CreateFactory();
void CreateDevice();
void CreateCMDQueueListAlloctor();
void CreateFence();
void SetMSAA();
void CreateSwapChain(cDx12RenderConfig* io_cDx12RenderConfig);
void CreateHeap(int i_iSwapChainCount);
void CreateRTV(int i_iSwapChainCount);
void CreateDSV(cDx12RenderConfig* io_cDx12RenderConfig);
void CreateViewPortAndScissorRect(cDx12RenderConfig* io_cDx12RenderConfig);
void WaitGPUComplete();
public:
private:
cDx12RenderConfig cMyDx12RenderConfig;
ComPtr<IDXGIFactory4> m_spDXGIFactory;//创建 DirectX 图形基础结构 (DXGI) 对象
ComPtr<ID3D12Device> m_spD3dDevice;//创建命令分配器、命令列表、命令队列、Fence、资源、管道状态对象、堆、根签名、采样器和许多资源视图
ComPtr<ID3D12CommandQueue> m_spCommandQueue;//队列
ComPtr<ID3D12CommandAllocator> m_spCommandAllocator; //存储
ComPtr<ID3D12GraphicsCommandList> m_spGraphicsCommandList;//命令列表
ComPtr<IDXGISwapChain> m_spSwapChain;
ComPtr<ID3D12DescriptorHeap> m_spRTVHeap;
ComPtr<ID3D12DescriptorHeap> m_spDSVHeap;
ComPtr<ID3D12Fence> m_spFence;//一个用于同步 CPU 和一个或多个 GPU 的对象。
std::vector<ComPtr<ID3D12Resource>> vecRTVBuffer;
ComPtr<ID3D12Resource> m_spDepthStencilBuffer;
//和屏幕的视口有关
D3D12_VIEWPORT m_dvViewprotInfo;
D3D12_RECT m_drViewprotRect;
private:
UINT m_iM4XQualityLevels;
bool m_bMSAA4XEnabled;
DXGI_FORMAT m_dfBackBufferFormat;
DXGI_FORMAT m_dfDepthStencilFormat;
UINT m_iRTVDescriptorSize;
UINT64 m_iCurFenceIndex;
int m_iCurrentSwapBuffIndex;
};
Dx12Render.cpp
#include"Dx12Render.h"
#include <DirectXColors.h>
cDx12Rendering::cDx12Rendering()
: m_iM4XQualityLevels(0)
, m_bMSAA4XEnabled(false)
, m_dfBackBufferFormat(DXGI_FORMAT::DXGI_FORMAT_R8G8B8A8_UNORM)
, m_dfDepthStencilFormat(DXGI_FORMAT::DXGI_FORMAT_D24_UNORM_S8_UINT)
, m_iCurrentSwapBuffIndex(0)
{
}
cDx12Rendering::~cDx12Rendering()
{
}
BOOL cDx12Rendering::Init(cDx12RenderConfig* io_cDx12RenderConfig)
{
//Debug
ComPtr<ID3D12Debug> D3D12Debug;
if (SUCCEEDED(D3D12GetDebugInterface(IID_PPV_ARGS(&D3D12Debug))))
{
D3D12Debug->EnableDebugLayer();
}
CreateFactory();
CreateDevice();
CreateCMDQueueListAlloctor();
SetMSAA();
CreateSwapChain(io_cDx12RenderConfig);
CreateHeap(io_cDx12RenderConfig->iSwapChainCount);
CreateRTV(io_cDx12RenderConfig->iSwapChainCount);
CreateDSV(io_cDx12RenderConfig);
CreateFence();
CreateViewPortAndScissorRect(io_cDx12RenderConfig);
return TRUE;
}
void cDx12Rendering::CreateFactory()
{
//创建DXGI工厂
CreateDXGIFactory1(IID_PPV_ARGS(&m_spDXGIFactory));
}
void cDx12Rendering::CreateDevice()
{
//创建设备
HRESULT D3dDeviceResult = D3D12CreateDevice(NULL, D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&m_spD3dDevice));
if (FAILED(D3dDeviceResult))
{
ComPtr<IDXGIAdapter> spWARPAdapter;
m_spDXGIFactory->EnumWarpAdapter(IID_PPV_ARGS(&spWARPAdapter));
D3D12CreateDevice(spWARPAdapter.Get(), D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&m_spD3dDevice));
}
}
void cDx12Rendering::CreateCMDQueueListAlloctor()
{
//命令队列的创建
D3D12_COMMAND_QUEUE_DESC cQueueDesc = {};
cQueueDesc.Type = D3D12_COMMAND_LIST_TYPE::D3D12_COMMAND_LIST_TYPE_DIRECT;
cQueueDesc.Flags = D3D12_COMMAND_QUEUE_FLAGS::D3D12_COMMAND_QUEUE_FLAG_NONE;
m_spD3dDevice->CreateCommandQueue(&cQueueDesc, IID_PPV_ARGS(&m_spCommandQueue));
//命令分配器的创建
m_spD3dDevice->CreateCommandAllocator(
D3D12_COMMAND_LIST_TYPE::D3D12_COMMAND_LIST_TYPE_DIRECT,
IID_PPV_ARGS(m_spCommandAllocator.GetAddressOf()));
//命令列表的创建
m_spD3dDevice->CreateCommandList(
0, //默认单个Gpu
D3D12_COMMAND_LIST_TYPE::D3D12_COMMAND_LIST_TYPE_DIRECT,
m_spCommandAllocator.Get(),//将Commandlist关联到Allocator
NULL,//ID3D12PipelineState
IID_PPV_ARGS(m_spGraphicsCommandList.GetAddressOf()));
m_spGraphicsCommandList->Close();
}
void cDx12Rendering::CreateFence()
{
//创建围栏
m_spD3dDevice->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&m_spFence));
}
void cDx12Rendering::SetMSAA()
{
D3D12_FEATURE_DATA_MULTISAMPLE_QUALITY_LEVELS cQualityLevels;
cQualityLevels.Format = m_dfBackBufferFormat;
cQualityLevels.SampleCount = 4;
cQualityLevels.Flags = D3D12_MULTISAMPLE_QUALITY_LEVEL_FLAGS::D3D12_MULTISAMPLE_QUALITY_LEVELS_FLAG_NONE;
cQualityLevels.NumQualityLevels = 0;
m_spD3dDevice->CheckFeatureSupport(
D3D12_FEATURE_MULTISAMPLE_QUALITY_LEVELS,
&cQualityLevels,
sizeof(cQualityLevels));
m_iM4XQualityLevels = cQualityLevels.NumQualityLevels;
}
void cDx12Rendering::CreateSwapChain(cDx12RenderConfig* io_cDx12RenderConfig)
{
m_spSwapChain.Reset();
DXGI_SWAP_CHAIN_DESC cSwapChainDesc;
cSwapChainDesc.BufferDesc.Width = io_cDx12RenderConfig->iResolutionWidth;//分辨率宽度
cSwapChainDesc.BufferDesc.Height = io_cDx12RenderConfig->iResolutionHight;//分辨率高
cSwapChainDesc.BufferDesc.RefreshRate.Numerator = io_cDx12RenderConfig->iRefreshRate;//刷新率
cSwapChainDesc.BufferDesc.RefreshRate.Denominator = 1;
cSwapChainDesc.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER::DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
cSwapChainDesc.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;
cSwapChainDesc.BufferCount = io_cDx12RenderConfig->iSwapChainCount;
cSwapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;//使用表面或资源作为输出渲染目标。
cSwapChainDesc.OutputWindow = io_cDx12RenderConfig->hHandle;//指定windows句柄
cSwapChainDesc.Windowed = true;//以窗口运行
cSwapChainDesc.SwapEffect = DXGI_SWAP_EFFECT::DXGI_SWAP_EFFECT_FLIP_DISCARD;
cSwapChainDesc.Flags = DXGI_SWAP_CHAIN_FLAG::DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;//IDXGISwapChain::ResizeTarget
cSwapChainDesc.BufferDesc.Format = m_dfBackBufferFormat;//纹理格式
//多重采样设置
cSwapChainDesc.SampleDesc.Count = m_bMSAA4XEnabled ? 4 : 1;
cSwapChainDesc.SampleDesc.Quality = m_bMSAA4XEnabled ? (m_iM4XQualityLevels - 1) : 0;
m_spDXGIFactory->CreateSwapChain(
m_spCommandQueue.Get(),
&cSwapChainDesc, m_spSwapChain.GetAddressOf());
m_spSwapChain->ResizeBuffers(
io_cDx12RenderConfig->iSwapChainCount,
io_cDx12RenderConfig->iScrrenWidth,
io_cDx12RenderConfig->iScrrenHight,
m_dfBackBufferFormat, DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH);
}
void cDx12Rendering::CreateHeap(int i_iSwapChainCount)
{
//创建RTV描述符堆
D3D12_DESCRIPTOR_HEAP_DESC cRTVDescriptorHeapDesc;
cRTVDescriptorHeapDesc.NumDescriptors = i_iSwapChainCount;
cRTVDescriptorHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
cRTVDescriptorHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
cRTVDescriptorHeapDesc.NodeMask = 0;
m_spD3dDevice->CreateDescriptorHeap(
&cRTVDescriptorHeapDesc,
IID_PPV_ARGS(m_spRTVHeap.GetAddressOf()));
//创建DSV描述符堆
D3D12_DESCRIPTOR_HEAP_DESC cDSVDescriptorHeapDesc;
cDSVDescriptorHeapDesc.NumDescriptors = 1;
cDSVDescriptorHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_DSV;
cDSVDescriptorHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
cDSVDescriptorHeapDesc.NodeMask = 0;
m_spD3dDevice->CreateDescriptorHeap(
&cDSVDescriptorHeapDesc,
IID_PPV_ARGS(m_spDSVHeap.GetAddressOf()));
}
void cDx12Rendering::CreateRTV(int i_iSwapChainCount)
{
for (int i = 0; i < i_iSwapChainCount; i++)
{
vecRTVBuffer.push_back(ComPtr<ID3D12Resource>());
}
//拿到描述size
m_iRTVDescriptorSize = m_spD3dDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
CD3DX12_CPU_DESCRIPTOR_HANDLE cRTVHeapHandle(m_spRTVHeap->GetCPUDescriptorHandleForHeapStart());
for (int i = 0; i < i_iSwapChainCount; i++)
{
m_spSwapChain->GetBuffer(i, IID_PPV_ARGS(&vecRTVBuffer[i]));
//RTV描述符放入RTV描述符堆中
m_spD3dDevice->CreateRenderTargetView(vecRTVBuffer[i].Get(), nullptr, cRTVHeapHandle);
cRTVHeapHandle.Offset(1, m_iRTVDescriptorSize);
}
}
void cDx12Rendering::CreateDSV(cDx12RenderConfig* io_cDx12RenderConfig)
{
//创建DSV描述符
D3D12_RESOURCE_DESC cDSVDesc;
cDSVDesc.Width = io_cDx12RenderConfig->iScrrenWidth;
cDSVDesc.Height = io_cDx12RenderConfig->iScrrenHight;
cDSVDesc.Alignment = 0;
cDSVDesc.MipLevels = 1;
cDSVDesc.DepthOrArraySize = 1;
cDSVDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
cDSVDesc.SampleDesc.Count = m_bMSAA4XEnabled ? 4 : 1;
cDSVDesc.SampleDesc.Quality = m_iM4XQualityLevels ? (m_iM4XQualityLevels - 1) : 0;
cDSVDesc.Format = DXGI_FORMAT_R24G8_TYPELESS;
cDSVDesc.Flags = D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL;
cDSVDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;
D3D12_CLEAR_VALUE cOptClear;
cOptClear.DepthStencil.Depth = 1.f;
cOptClear.DepthStencil.Stencil = 0;
cOptClear.Format = m_dfDepthStencilFormat;
CD3DX12_HEAP_PROPERTIES Properties = CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT);
m_spD3dDevice->CreateCommittedResource(
&Properties,
D3D12_HEAP_FLAG_NONE, &cDSVDesc,
D3D12_RESOURCE_STATE_COMMON, &cOptClear,
IID_PPV_ARGS(m_spDepthStencilBuffer.GetAddressOf()));
//DSV描述符放入DSV描述符堆中
D3D12_DEPTH_STENCIL_VIEW_DESC DSVDesc;
DSVDesc.Format = m_dfDepthStencilFormat;
DSVDesc.Texture2D.MipSlice = 0;
DSVDesc.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2D;
DSVDesc.Flags = D3D12_DSV_FLAG_NONE;
m_spD3dDevice->CreateDepthStencilView(m_spDepthStencilBuffer.Get(),
&DSVDesc,
m_spDSVHeap->GetCPUDescriptorHandleForHeapStart());
CD3DX12_RESOURCE_BARRIER Barrier = CD3DX12_RESOURCE_BARRIER::Transition(
m_spDepthStencilBuffer.Get(),
D3D12_RESOURCE_STATE_COMMON,
D3D12_RESOURCE_STATE_DEPTH_WRITE);
m_spGraphicsCommandList->Reset(m_spCommandAllocator.Get(), NULL);
m_spGraphicsCommandList->ResourceBarrier(1, &Barrier);
m_spGraphicsCommandList->Close();
ID3D12CommandList* CommandList[] = { m_spGraphicsCommandList.Get() };
m_spCommandQueue->ExecuteCommandLists(_countof(CommandList), CommandList);
}
void cDx12Rendering::CreateViewPortAndScissorRect(cDx12RenderConfig* io_cDx12RenderConfig)
{
//描述视口尺寸
m_dvViewprotInfo.TopLeftX = 0;
m_dvViewprotInfo.TopLeftY = 0;
m_dvViewprotInfo.Width = 100;
m_dvViewprotInfo.Height = 100;
m_dvViewprotInfo.MinDepth = 0.f;
m_dvViewprotInfo.MaxDepth = 1.f;
//矩形
m_drViewprotRect.left = 0;
m_drViewprotRect.top = 0;
m_drViewprotRect.right = io_cDx12RenderConfig->iScrrenWidth;
m_drViewprotRect.bottom = io_cDx12RenderConfig->iScrrenHight;
}
void cDx12Rendering::WaitGPUComplete()
{
m_iCurFenceIndex++;
//向GUP设置新的隔离点 等待GPU处理玩信号
m_spCommandQueue->Signal(m_spFence.Get(), m_iCurFenceIndex);
if (m_spFence->GetCompletedValue() < m_iCurFenceIndex)
{
HANDLE hEventEX = CreateEventEx(NULL, NULL, 0, EVENT_ALL_ACCESS);
//GPU完成后会通知我们的Handle
m_spFence->SetEventOnCompletion(m_iCurFenceIndex, hEventEX);
//等待GPU,阻塞主线程
WaitForSingleObject(hEventEX, INFINITE);
CloseHandle(hEventEX);
}
}
Dx12Text\Core.h
#pragma once
#include "d3dx12.h"
#pragma comment(lib, "d3d12.lib")
#include <d3d12.h>
#pragma comment(lib, "dxgi.lib")
#include <dxgi1_4.h>
#include <wrl.h>
using namespace Microsoft::WRL;
Dx12TestMian.cpp
#include "Dx12Window.h"
#include "Dx12Render.h"
int WINAPI WinMain(
HINSTANCE i_hInstance, //应用程序当前运行的实例的句柄,这是系统给该实例的一个唯一标识符,是一个数值
HINSTANCE i_PrevInstance, //当前实例的前一个实例的句柄。在Win32环境下,这个参数总是NULL
PSTR i_lpCmdLine, //一个以空终止的字符串,指定传递给应用程序的命令行参数,可在工程设置中调整
int i_nCmdShow //窗口如何显示
)
{
cDx12RenderConfig cMyDx12RenderConfig;
cMyDx12RenderConfig.iScrrenLeftX = 250;
cMyDx12RenderConfig.iScrrenLeftY = 150;
cMyDx12RenderConfig.iScrrenWidth = 1000;
cMyDx12RenderConfig.iScrrenHight = 600;
cMyDx12RenderConfig.iRefreshRate = 60;
cMyDx12RenderConfig.iSwapChainCount = 2;
cMyDx12RenderConfig.iResolutionWidth = 1920;
cMyDx12RenderConfig.iResolutionHight = 1080;
if (!CreateDx12Window(i_hInstance, &cMyDx12RenderConfig))
{
return 1;
}
cDx12Rendering cDx12Render;
if (!cDx12Render.Init(&cMyDx12RenderConfig))
{
return 1;
}
system("pause");//暂停一下
return 1;
}
渲染初始化阶段,没有直接的阶段效果展示,只要能正确编译通过即可。