从本章开始进入代码实践阶段,主要是动手写和分析代码来熟悉一些渲染管线和各种基础功能实现的核心逻辑。因为本人阅读本书的主要目的是能更加深入的理解上层开源引擎的一些思路,所以对于具体功能可能涉及的数学模型和算法只了解其大致特点,主要精力放在从开始到渲染最后图像的实现思路上面,话不多说开始第一篇。
代码准备阶段
DX12龙书本身提供了一些代码,Github地址。依据书中一步一步来最后就能够得到 Box 示例。
如果和我一样使用VS2019编译,可能会遇到以下报错:
error C2664: “HANDLE CreateEventExW(LPSECURITY_ATTRIBUTES,LPCWSTR,DWORD,DWORD)”: 无法将参数 2 从“bool”转换为“LPCWSTR”
这是因为项目中有可能带来移植性风险的代码。
解决方法有两种:
一种是把这个检查代码可移植性的功能关闭。在项目上右键->属性->C/C++->语言->符合模式设置为否。
另一种就是去修改报错的代码让其能通过编译。去微软官方提供的文档中找到对应的 CreateEventExW文档。根据错误提示,是在参数 lpName 处填了 false 进去导致的。结合代码和文档分析,代码作者应该是不想指定事件的具体名称。顺着这个思路,在文档中我们能够看到这么一句:
If lpName is NULL, the event object is created without a name.
结合 MSDN 关于文档中 NULL 的解释(文章传送门)。
所以,将代码对应处的 false 改为 NULL 或者 nullptr,再次编译就能看到上面的 Box 示例了,详情可以看我的提交历史。通过测试 0 ,NULL 或者 nullptr 都行,考虑到 lpName 参数是一个指针所以最后选择了 nullptr。
DirectX3D初始化
关于 DirectX3D 的初始化代码都在 D3DApp::InitDirect3D 中。
DirectX 图形基础设施(DXGI)API的创建:
ThrowIfFailed(CreateDXGIFactory1(IID_PPV_ARGS(&mdxgiFactory)));
CreateDXGIFactory1 表示创建的 1.1 的DXGI,CreateDXGIFactory创建的是 1.0 的DXGI。二者在同一个程序里创建一个就行,不能混用。
IID_PPV_ARGS 主要为了方便传参(绑定的 REFIID 和 void **),之后使用 DirectX3D 的 API 时会经常用到。
创建设备时先使用默认的显示适配器。如果创建失败就拿微软自己的 WARP 应付一下:
HRESULT hardwareResult = D3D12CreateDevice(
nullptr, // default adapterD3D_FEATURE_LEVEL_11_0,
IID_PPV_ARGS(&md3dDevice));
// Fallback to WARP device.if(FAILED(hardwareResult))
{
ComPtr pWarpAdapter;
ThrowIfFailed(mdxgiFactory->EnumWarpAdapter(IID_PPV_ARGS(&pWarpAdapter)));
ThrowIfFailed(D3D12CreateDevice(
pWarpAdapter.Get(),
D3D_FEATURE_LEVEL_11_0,
IID_PPV_ARGS(&md3dDevice)));
}
创建用于 CPU 于 GPU 同步的围栏(fence):
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);
mCbvSrvUavDescriptorSize = md3dDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
作为功能检测的示例代码里面检测了对 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 && "Unexpected MSAA quality level.");
功能检测能检测多种功能,以后用到的话再具体讲。
创建我们的绘制命令三件套:
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(), // Associated command allocatornullptr, // Initial PipelineStateObjectIID_PPV_ARGS(mCommandList.GetAddressOf())));
mCommandList->Close();
因为每次绘制的时候我们都会先将 CommandList 重置,所以初始化以后就直接将它关闭就可以了。
创建默认的交换链用于渲染:
// Release the previous swapchain we will be recreating. 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;
// Note: Swap chain uses queue to perform flush. ThrowIfFailed(mdxgiFactory->CreateSwapChain(
mCommandQueue.Get(),
&sd,
mSwapChain.GetAddressOf()));
创建 RTV 和 DSV (关于 RTV 和 DSV 参见之前的理论部分)描述符堆:
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())));
至此,一些必要的参数我们就初始化完成了。下篇就开始我们就可以愉快的玩耍了=。=