1.用D3D12CreateDevice函数创建ID3D12Device接口实例。参考代码如下所示:
// 通过nullptr(代表默认适配器)来创建硬件设备
HRESULT hardwareResult = D3D12CreateDevice(nullptr, D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&md3dDevice));
// 创建硬件设备失败时就使用WARP适配器来创建硬件设备
if (FAILED(hardwareResult))
{
ComPtr<UDXGIAdapter> pWarpAdapter;
ThrowIfFailed(mdxgiFactory->EnumWarpAdapter(IID_PPV_ARGS(&pWarpAdapter)));
ThrowFailed(D3D12CreateDevice(pWarpAdapter->Get(), D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&md3dDevice)));
}
2.创建围栏并获取描述符大小。参考代码如下所示:
// 创建初始值为0的围栏对象,用于处理CPU/GPU之间的同步操作
ThrowIfFailed(md3dDevice->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&mFence)));
// 获取渲染目标视图大小
mRtvDescriptorSize = md3dDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
// 获取深度/模板视图大小
mDsvDescriptorSize = md3dDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_DSV);
// 获取常量缓冲区/着色器资源/无序访问视图大小
mCbvUavDescriptorSize = md3dDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
3.检测对4X MSAA质量级别的支持。参考代码如下所示:
// 在一切支持Direct3D 11的设备上,所有渲染目标格式就皆已支持4X MSAA了。
D3D12_FEATURE_DATA_MULTISAMPLE_QUALITY_LEVELS msQualityLevels;
msQualityLevels.Format = mBackBufferFormat;
msQualityLevels.SampleCount = 4;
msQualityLevels.Flags = D3D12_MULTISAMPLE_QUALITY_LEVELS_FLAG_NONE;
msQualityLevels.NumQualityLevels = 0; // 用来存储质量级别
ThrowIfFailed(md3dDevice->CheckFeatureSupport(D3D12_FEATURE_MULTISAMPLE_QUALITY_LEVELS, &msQualityLevels, sizeof(msQualityLevels)));
m4xMsaaQuality = msQualityLevels.NumQualityLevels;
assert(m4xMsaaQuality > 0 && "Unexcepted MSAA quality level.");
4.创建命令队列,命令列表分配器和主命令列表。参考代码如下所示:
ComPtr<ID3D12CommandQueue> mCommandQueue;
ComPtr<ID3D12CommandAllocator> mDirectCmdListAlloc;
ComPtr<ID3D12GraphicsCommandList> mCommandList;
void D3DApp::CreateCommandObjects()
{
D3D12_COMMAND_QUEUE_DESC queueDesc = {};
queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
ThrowIfFailed(md3dDevice->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&mCommandQueue)));
ThrowIfFailed(md3dDevice->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(mDirectCmdListAlloc.GetAddressOf())));
ThrowIfFailed(md3dDevice->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, mDirectCmdListAlloc.Get(), nullptr, IID_PPV_ARGS(mCommandList.GetAddressOf())));
// 首先要将命令列表处于关闭状态。这是因为在第一次引用命令列表时,我们要对它进行重置,而在调用重置方法之前又需要先将其关闭
mCommandList->Close();
}
5.描述并创建交换链。参考代码如下所示:
// 缓冲区的显示格式
DXGI_FORMAT mBackBufferFormat = DXGI_FORMAT_R8G8B8A8_UNORM;
// Direct3D 12中不支持创建MSAA交换链
void D3DApp::CreateSwapChain()
{
// 释放之前所创的交换链,随后再进行重建
mSwapChain.Reset();
// 创建交换链描述结构体对象
DXGI_SWAP_CHAIN_DESC sd;
sd.BufferDesc.Width = mClientWidth; // 缓冲区分辨率的宽度
sd.BufferDesc.Height = mClientHeight; // 缓冲区分辨率的高度
sd.BufferDesc.RefreshRate.Numerator = 60;
sd.BufferDesc.RefreshRate.Denominator = 1;
sd.BufferDesc.Format = mBackBufferFormat;
sd.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
sd.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED; // 图像如何相对于屏幕进行拉伸
sd.SampleDesc.Count = m4xMsaaState ? 4 : 1;
sd.SampleDesc.Quality = m4xMsaaState ? (m4xMsaaQuality - 1) : 0;
sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
sd.BufferCount = SwapChainBufferCount;
sd.OutputWindow = mhMainWnd;
sd.Windowed = true;
sd.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
sd.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;
// 注意,交换链需要通过命令队列对其进行刷新
ThrowIfFailed(mdxgiFactory->CreateSwapChain(mCommandQueue.Get(), &sd, mSwapChain.GetAddressOf()));
}
6.创建应用程序所需要的描述符堆。参考代码如下所示:
// 渲染目标视图描述符堆
ComPtr<ID3D12DescriptorHeap> mRtvHeap;
// 深度/模板视图描述符堆
ComPtr<ID3D12DescriptorHeap> mDsvHeap;
void D3DApp::CreateRtvAndDsvDescriptorHeaps()
{
// 创建渲染目标视图描述符堆
D3D12_DESCRIPTOR_HEAP_DESC rtvHeapDesc;
rtvHeapDesc.NumDescriptors = SwapChainBufferCount;
rtvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
rtvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
rtvHeapDesc.NodeMask = 0;
ThrowIfFailed(md3dDevice->CreateDescriptorHeap(&rtvHeapDesc, IID_PPV_ARGS(mRtvHeap.GetAddressOf())));
// 创建深度/模板视图描述符堆
D3D12_DESCRIPTOR_HEAP_DESC dsvHeapDesc;
dsvHeapDesc.NumDescriptors = 1;
dsvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_DSV;
dsvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
dsvHeapDesc.NodeMask = 0;
ThrowIfFailed(md3dDevice->CreateDescriptorHeap(&dsvHeapDesc, IID_PPV_ARGS(mDsvHeap.GetAddressOf())));
}
// 根据给定的偏移量获取当前后台缓冲区的RTV
D3D12_CPU_DESCRIPTOR_HANDLE D3DApp::CurrentBackBufferView() const
{
return CD3DX12_CPU_DESCRIPTOR_HANDLE(
mRtvHeap->GetCpuDescriptorHandleForHeapStart(), // 描述符堆中的首个句柄
mCurrBackBuffer, // 偏移至后台缓冲区描述符句柄索引
mRtvDescriptorSize); // 描述符所占用的字节大小
}
// 获取当前后台缓冲区的DSV
D3D12_CPU_DESCRIPTOR_HANDLE D3DApp::DepthStencilView() const
{
return mDsvHeap->GetCpuDescriptorHandleForHeapStart();
}
7.调整后台缓冲区大小,并为它创建渲染目标视图。参考代码如下所示:
ComPtr<ID3D12Resource> mSwapChainBuffer[SwapChainBufferCount];
CD3D12_CPU_DESCRIPTOR_HANDLE rtvHeapHandle(mRtvHeap->GetCpuDescriptorHandleForHeapStart());
for (UINT i = 0; i < SwapChainBufferCount; ++i)
{
// 获取交换链内的第i个缓冲区
ThrowIfFailed(mSwapChain->GetBuffer(i, IID_PPV_ARGS(&mSwapChainBuffer[i])));
// 为此缓冲区创建一个RTV
md3dDevice->CreateRenderTargetView(mSwapChainBuffer[i].Get(), nullptr, rtvHeapHandle);
// 偏移至描述符堆中的下一个缓冲区
rtvHeapHandle.Offset(1, mRtvDescriptorSize);
}
8.创建深度/模板缓冲区及其视图。
预备知识如下所示:
1>.D3D12_HEAP_TYPE枚举类型定义如下所示:
typedef enum D3D12_HEAP_TYPE
{
D3D12_HEAP_TYPE_DEFAULT = 1, // 默认堆。向这个堆里提交的资源,唯独GPU可以访问。
D3D12_HEAP_TYPE_UPLOAD = 2, // 上传堆。向这个堆里提交的都是需要经CPU上传至GPU的资源。
D3D12_HEAP_TYPE_READBACK = 3, // 回读堆。向这个堆里提交的都是需要由CPU读取的资源。
D3D12_HEAP_TYPE_CUSTOM = 4 // 自定义堆。
}
2>.ID3D12Device::CreateCommittedResource函数将根据我们所提供的属性创建一个资源与一个堆。并把该资源提交到这个堆中。
参考代码如下所示:
// 创建资源描述对象
D3D12_RESOURCE_DESC depthStencilDesc;
depthStencilDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
depthStencilDesc.Allignment = 0;
depthStencilDesc.Width = mClientWidth;
depthStencilDesc.Height = mClientHeight;
depthStencilDesc.DepthOrArraySize = 1;
depthStencilDesc.MipLevels = 1;
depthStencilDesc.Format = mDepthStencilFormat;
depthStencilDesc.SampleDesc.Count = m4xMsaaState ? 4 : 1;
depthStencilDesc.SampleDesc.Quality = m4xMsaaState ? (m4xMsaaQuality - 1) : 0;
depthStencilDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;
depthStencilDesc.Flags = D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL;
// 创建清除资源优化值对象
D3D12_CLEAR_VALUE optClear;
optClear.Format = mDepthStencilFormat;
optClear.DepthStencil.Depth = 1.0f;
optClear.DepthStencil.Stencil = 0;
// 创建深度/模板缓冲区与一个堆。并把深度/模板缓冲区提交到这个堆中。
ThrowIfFailed(md3dDevice->CreateCommittedResource(
&CD3D12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT),
D3D12_HEAP_FLAG_NONE,
&depthStencilDesc,
D3D12_RESOURCE_STATE_COMMON,
&optClear,
IID_PPV_ARGS(mDepthStencilBuffer.GetAddressOf())));
// 为深度/模板缓冲区的第0 mip层创建描述符
md3dDevice->CreateDepthStencilView(mDepthStencilBuffer.Get(), nullptr, DepthStencilView());
// 将深度/模板缓冲区从初始状态转换为深度缓冲区状态
mCommandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(
mDepthStencilBuffer.Get(),
D3D12_RESOURCE_STATE_COMMON,
D3D12_RESOURCE_STATE_DEPTH_WRITE));
9.设置视口。
预备知识如下:
1>.后台缓冲区中的矩形子区域叫做视口,用D3D12_VIEWPORT结构体来表示。其定义如下所示:
typedef struct D3D12_VIEWPORT
{
FLOAT TopLeftX;
FLOAT TopLeftY;
FLOAT Width;
FLOAT Height;
FLOAT MinDepth;
FLOAT MaxDepth;
} D3D12_VIEWPORT;
其中MinDepth和MaxDepth负责将深度值从区间[0, 1]转换到区间[MinDepth, MaxDepth]。视口所采用的坐标系为:缓冲区左上角为原点,x和y轴的正方向分别为水平向右与垂直向下。
2>.调用ID3D12GraphicsCommandList::RSSetViewports函数来设置视口。不能为同一个渲染目标指定多个视口。而多视口则是一种用于对多个渲染目标同时进行渲染的高级技术。
3>.命令列表一旦被重置,视口也就需要随之而重置。
参考代码如下所示:
D3D12_VIEWPORT vp;
vp.TopLeftX = 0.0f;
vp.TopLeftY = 0.0f;
vp.Width = static_cast<float>(mClientWidth);
vp.Height= static_cast<float>(mClientHeight);
vp.MinDepth = 0.0f;
vp.MaxDepth = 1.0f;
// 第一个参数为要绑定的视口数量;第二个参数为指向视口数组的指针
mCommandList->RSSetViewports(1, &vp);
10.设置裁剪矩形。
预备知识如下所示:
1>.裁剪矩形是一种相对于后台缓冲区定义的一个矩形区域,在此矩形区域外的像素都将被剔除。
2>.裁剪矩形用D3D12_RECT结构体来表示。
3>.调用ID3D12GraphicsCommandList::RSSetScissorRects函数来设置裁剪矩形。不能为同一个渲染目标指定多个裁剪矩形。而多裁剪矩形则是一种用于对多个渲染目标同时进行渲染的高级技术。
3>.命令列表一旦被重置,裁剪矩形也就需要随之而重置。
参考代码如下所示:
mScissorRect = {0, 0, mClientWidth / 2, mClientHeight / 2};
// 第一个参数为要绑定的裁剪矩形数量;第二个参数为指向裁剪矩形数组的指针
mCommandList->RSSetScissorRects(1, &mScissorRect);