DX12与以前的版本相比,更接近于底层。
主要是考虑了两个同步:GPU与CPU的同步,和GPU与GPU的同步
1,GPU与CPU的同步
加入了命令队列和命令列表,命令管理器。
CPU好像列车长秘书,GPU好像列车。奇葩的是,这个列车有N个车头,也有N个驾驶员。列车可以上天入地。
列车长秘书有一个笔记本,他把列车长每天的命令写入活页,放入笔记本。这个笔记本就是命令队列,命令列表就是接收的列车长的当天一系列命令,命令管理器就是各个活页。当然,可能不止一个列车长秘书,但是,只有一只笔,必须等一个活页放入笔记本后,才能写下一条命令。这里暂时认为只有一个秘书。简化之。命令列表可以忘记,只要活页还在就行。好,先生成笔记本和活页和列车长今天的命令列表。另外,怕完不成任务,造成命令堆积。则放个围栏,等到完成当天任务时,秘书才能进入放笔记本的屋子,否则只能在围栏外等待。
md3dDevice->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&mFence));
md3dDevice->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&mCommandQueue));
md3dDevice->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(mDirectCmdListAlloc.GetAddressOf()));
md3dDevice->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, mDirectCmdListAlloc.Get(), nullptr, IID_PPV_ARGS(mCommandList.GetAddressOf()));
开始时命令队列是关着的。
mCommandList->Close();
由于有多个车头(交换链mSwapChain),造几个车头
mdxgiFactory->CreateSwapChain(mCommandQueue.Get(), &sd, mSwapChain.GetAddressOf());
接下来创建列车的上天入地功能,
md3dDevice->CreateDescriptorHeap(&rtvHeapDesc, IID_PPV_ARGS(mRTVHeap.GetAddressOf()));
md3dDevice->CreateDescriptorHeap(&dsvHeapDesc, IID_PPV_ARGS(mDsvHeap.GetAddressOf()));
这时候命令发的可能快了,先歇会,等执行完了再开始
FlushCommandQueue();
接下来继续,调整火车头
mSwapChain->ResizeBuffers(SwapChainBufferCount, mClientWidth, mClientHeight, mBackBufferFormat, DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH);
//根据不同的车头,换不同的风景,即渲染目标视图和深度/模板缓冲区
CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHeapHandle(mRTVHeap->GetCPUDescriptorHandleForHeapStart());
for (UINT i = 0; i < SwapChainBufferCount; i++)
{
//获得交换链内的第i个缓冲区
mSwapChain->GetBuffer(i, IID_PPV_ARGS(&mSwapChainBuffer[i]));
//为此缓冲区创建一个RTV
md3dDevice->CreateRenderTargetView(mSwapChainBuffer[i].Get(), nullptr, rtvHeapHandle);
//偏移到描述符堆中的下一个缓冲区
rtvHeapHandle.Offset(1, mRtvDescriptorSize);
}
md3dDevice->CreateCommittedResource(&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT),
D3D12_HEAP_FLAG_NONE,
&depthStencilDesc,
D3D12_RESOURCE_STATE_COMMON,
&optClear,
IID_PPV_ARGS(mDepthStencilBuffer.GetAddressOf()));
//列车长今天的任务写完了,放下笔,把活页放入笔记本,开始执行。
mCommandList->Close();
mCommandQueue->ExecuteCommandLists(_countof(cmdsLists), cmdsLists);
2,GPU与GPU同步(资源冒险)
这个笔记本,放着列车长的命令,将会送到驾驶员组里。由于只有一个列车,这些驾驶员都要开车执行任务,咋办呢?其他的好说,一个列车不能多个驾驶员同时开。
没事,在陆地行驶时,转换到陆地行驶模式,在这个模式下,只有陆地驾驶员开。到天空时,换为飞行模式,由飞行员开。但是,怎么才能知道目前列车占着没有?当然是驾驶员室门把手上放着请勿进入牌子了。
mCommandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(currentBackBuffer(),
D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_RENDER_TARGET));
mCommandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(currentBackBuffer(),
D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_PRESENT));
现在看看列车长每个命令都是怎么发布的.
秘书,换个活页记录.
ThrowIfFailed(mDirectCmdListAlloc->Reset());
我开始要说命令了,记住.
mCommandList->Reset(mDirectCmdListAlloc.Get(), nullptr);
先让驾驶员A到火星上
mCommandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(currentBackBuffer(),
D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_RENDER_TARGET));
//去火星有些注意事项,及要执行的任务。
mCommandList->RSSetViewports(1, &mScreenViewport);
mCommandList->RSSetScissorRects(1, &mScissorRect);
mCommandList->ClearRenderTargetView(currentBackBufferView(), Colors::LightSteelBlue, 0, nullptr);
mCommandList->ClearDepthStencilView(depthStencilView(), D3D12_CLEAR_FLAG_DEPTH | D3D12_CLEAR_FLAG_STENCIL, 1.0f, 0, 0, nullptr);
mCommandList->OMSetRenderTargets(1, ¤tBackBufferView(), true, &depthStencilView());
火星任务执行后,返回地球,开启陆地模式
mCommandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(currentBackBuffer(),
D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_PRESENT));
我的命令完了。
mCommandList->Close();
开始执行吧
ID3D12CommandList* cmdsLists[] = { mCommandList.Get() };
mCommandQueue->ExecuteCommandLists(_countof(cmdsLists), cmdsLists);
顺便请把电视换到我喜欢看的喜羊羊。
ThrowIfFailed(mSwapChain->Present(0, 0));
mCurrBackBuffer = (mCurrBackBuffer + 1) % SwapChainBufferCount;
任务完成前不要烦我,我要睡觉了
FlushCommandQueue();
然后把门一关,看喜羊羊了。可以确保能看到任务完成。
//增加围栏值,接下来命令标记到此围栏点
mCurrentFence++;
//向命令队列添加一条用来设置新围栏点的命令,这条命令由GPU处理,即由GPU端来修改围栏值,所以再GPU处理完命令队列中此signal()的所有命令前,并不会设置新的围栏点
mCommandQueue->Signal(mFence.Get(), mCurrentFence);
//在cpu段等待GPU,直到后者执行完这个围栏点之前的所有命令
if (mFence->GetCompletedValue() < mCurrentFence)
{
HANDLE eventHandle = CreateEventEx(nullptr, nullptr, false, EVENT_ALL_ACCESS);
//若GPU命中当前的围栏,即执行到SIGNAL()
mFence->SetEventOnCompletion(mCurrentFence, eventHandle);
//等待GPU命中围栏,激发事件
WaitForSingleObject(eventHandle, INFINITE);
CloseHandle(eventHandle);
}