I looked back over the sample code, and I see the issue. I'll write a ticket to improve the sample, but I can't say for sure when we'll get around to it.
In the meantime, we have a couple of other samples that are more robust and use the PipelineState helper class we use for internal plugins like the VR and the air to ground radar. The OpenGLTexture and OpenCVStereoCamera, both contain a util folder with helpful dx12 utility classes including RenderHelper.h which contains the PipelineState class. This class handles the logic for waiting on your command allocator to be done working on the GPU before reusing it on the CPU. The two functions of interest here are Execute and Reset. Execute signals a fence after the command list is executed and reset, waits for that fence before resetting.
These should work without flicker, but may not perform as well under high load as some of our internal use add-ons. The version of RenderUtil we use internally has been updated to use a pool allocators so that you don't get CPU stalls waiting for the GPU while under heavy load. Below is the newer version of this utility used by our VR add-on. It includes a basic CommandAllocatorQueue that is essentially just a small tripple-buffered ring buffer. Double buffering would be enough in general, but if you plan to execute/reset more than once per frame, you may need to increase the size of the queue to prevent stalls.
Code: Select all
//==========================================================================================================
struct TrackedAllocator
{
CComPtr<ID3D12CommandAllocator> m_spCommandAllocator = nullptr;
UINT m_uFenceValue = 0;
};
#define ALLOCATOR_COUNT 3
struct CommandAllocatorQueue
{
CommandAllocatorQueue() : m_uCurrent(0) {}
TrackedAllocator& GetCurrent() {
return m_aAllocators[m_uCurrent];
}
TrackedAllocator& GetNext() {
m_uCurrent = (m_uCurrent + 1) % ALLOCATOR_COUNT;
return m_aAllocators[m_uCurrent];
}
TrackedAllocator m_aAllocators[ALLOCATOR_COUNT];
UINT m_uCurrent = 0;
};
class PipelineState
{
public:
PipelineState() noexcept;
HRESULT Initialize(ID3D12Device* pDevice, ID3D12CommandQueue* pQueue);
HRESULT DeInitialize();
HRESULT Apply(bool bClearRTV = true);
HRESULT Clear();
HRESULT Execute();
HRESULT Reset();
void CopySubresourceRegion(P3D::IRenderDataResourceV500* pDestination, UINT DstX, UINT DstY, UINT DstZ,
P3D::IRenderDataResourceV500* pSource, const D3D12_BOX* pSourceBox);
void CopyResource(P3D::IRenderDataResourceV500* pDestination, P3D::IRenderDataResourceV500* pSource);
static const unsigned int MAX_RESOURCES = 4;
CComPtr<P3D::IRenderDataResourceV500> m_spResources[MAX_RESOURCES];
CComPtr<P3D::IRenderDataResourceV500> m_spRenderTarget;
CComPtr<P3D::IRenderDataResourceV500> m_spDepthStencil;
CComPtr<P3D::IRenderDataResourceV500> m_spVertexBuffer;
D3D12_VERTEX_BUFFER_VIEW m_sVertexBufferView;
CComPtr<P3D::IRenderDataResourceV500> m_spConstantBuffer;
CComPtr<ID3D12Device> m_spDevice;
CComPtr<ID3D12GraphicsCommandList> m_spCommandList;
CComPtr<ID3D12CommandQueue> m_spCommandQueue;
CComPtr<ID3D12PipelineState> m_spPipelineState;
CComPtr<ID3D12RootSignature> m_spRootSignature;
CComPtr<ID3D12DescriptorHeap> m_spDescriptorHeapSRV;
CComPtr<ID3D12DescriptorHeap> m_spDescriptorHeapRTV;
CComPtr<ID3D12DescriptorHeap> m_spDescriptorHeapDSV;
CComPtr<ID3D12Fence> m_spFence;
D3D_PRIMITIVE_TOPOLOGY m_ePrimitiveTopology;
D3D12_VIEWPORT m_ViewPort;
HANDLE m_hFenceEvent;
UINT m_uDepthStencilRef;
UINT m_uSRVIncrement;
UINT m_uRTVIncrement;
UINT m_uDSVIncrement;
CommandAllocatorQueue m_AllocatorQueue;
UINT m_uFenceValue = 0;
};
//==========================================================================================================
PipelineState::PipelineState() noexcept
:m_ViewPort()
{
m_ePrimitiveTopology = D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP;
m_uDepthStencilRef = 0;
}
HRESULT PipelineState::Initialize(ID3D12Device* pDevice, ID3D12CommandQueue* pQueue)
{
HRESULT hr = S_OK;
if (m_spDevice != pDevice || m_spCommandQueue != pQueue)
{
m_spDevice = pDevice;
m_spCommandQueue = pQueue;
for (int i = 0; i < ALLOCATOR_COUNT; i++)
{
hr = m_spDevice->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&m_AllocatorQueue.m_aAllocators[i].m_spCommandAllocator));
if (FAILED(hr))
{
return hr;
}
}
hr = m_spDevice->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, m_AllocatorQueue.m_aAllocators[0].m_spCommandAllocator, nullptr, IID_PPV_ARGS(&m_spCommandList));
if (FAILED(hr))
{
return hr;
}
//Create Fence and Event
m_uFenceValue = 0;
hr = pDevice->CreateFence(m_uFenceValue, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&m_spFence));
if (FAILED(hr))
{
return hr;
}
m_hFenceEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
}
return hr;
}
HRESULT PipelineState::DeInitialize()
{
for (unsigned int i = 0; i < MAX_RESOURCES; i++)
{
m_spResources[i] = nullptr;
}
ZeroMemory(&m_sVertexBufferView, sizeof(m_sVertexBufferView));
ZeroMemory(&m_ViewPort, sizeof(m_ViewPort));
m_ePrimitiveTopology = D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP;
m_spRenderTarget = nullptr;
m_spDepthStencil = nullptr;
m_spVertexBuffer = nullptr;
m_spConstantBuffer = nullptr;
m_spDevice = nullptr;
m_spCommandList = nullptr;
m_spCommandQueue = nullptr;
m_spPipelineState = nullptr;
m_spRootSignature = nullptr;
m_spDescriptorHeapSRV = nullptr;
m_spDescriptorHeapRTV = nullptr;
m_spDescriptorHeapDSV = nullptr;
m_spFence = nullptr;
m_hFenceEvent = nullptr;
m_uDepthStencilRef = 0;
m_uSRVIncrement = 0;
m_uRTVIncrement = 0;
m_uDSVIncrement = 0;
m_uFenceValue = 0;
for (int i = 0; i < ALLOCATOR_COUNT; i++)
{
m_AllocatorQueue.m_aAllocators[i].m_spCommandAllocator = nullptr;
}
return S_OK;
}
//==========================================================================================================
HRESULT PipelineState::Apply(bool bClearRTV)
{
static float ClearColor[4] = { 0.0f, 0.0f, 0.0f, 1.0f }; // red,green,blue,alpha
ID3D12DescriptorHeap* ppHeaps[] = { m_spDescriptorHeapSRV };
m_spCommandList->SetDescriptorHeaps(_countof(ppHeaps), ppHeaps);
m_spCommandList->SetGraphicsRootSignature(m_spRootSignature);
if (bClearRTV && m_spRenderTarget != nullptr)
{
D3D12_CPU_DESCRIPTOR_HANDLE hRenderTargetHandle = m_spDescriptorHeapRTV->GetCPUDescriptorHandleForHeapStart();
m_spCommandList->ClearRenderTargetView(hRenderTargetHandle, ClearColor, 0, nullptr);
}
if (m_spDepthStencil != nullptr && m_spRenderTarget != nullptr)
{
D3D12_CPU_DESCRIPTOR_HANDLE hDepthStencilHandle = m_spDescriptorHeapDSV->GetCPUDescriptorHandleForHeapStart();
D3D12_CPU_DESCRIPTOR_HANDLE hRenderTargetHandle = m_spDescriptorHeapRTV->GetCPUDescriptorHandleForHeapStart();
m_spCommandList->OMSetStencilRef(m_uDepthStencilRef);
m_spCommandList->OMSetRenderTargets(1, &hRenderTargetHandle, FALSE, &hDepthStencilHandle);
}
else if(m_spRenderTarget)
{
D3D12_CPU_DESCRIPTOR_HANDLE hRenderTargetHandle = m_spDescriptorHeapRTV->GetCPUDescriptorHandleForHeapStart();
m_spCommandList->OMSetRenderTargets(1, &hRenderTargetHandle, FALSE, nullptr);
}
else
{
D3D12_CPU_DESCRIPTOR_HANDLE hDepthStencilHandle = m_spDescriptorHeapDSV->GetCPUDescriptorHandleForHeapStart();
m_spCommandList->OMSetStencilRef(m_uDepthStencilRef);
m_spCommandList->OMSetRenderTargets(0, nullptr, FALSE, &hDepthStencilHandle);
}
m_spCommandList->SetPipelineState(m_spPipelineState);
D3D12_RECT sScissorRectFull;
sScissorRectFull.left = 0;
sScissorRectFull.top = 0;
sScissorRectFull.right = static_cast<LONG>(m_ViewPort.Width);
sScissorRectFull.bottom = static_cast<LONG>(m_ViewPort.Height);
//Setup the viewport.
m_spCommandList->RSSetViewports(1, &m_ViewPort);
m_spCommandList->RSSetScissorRects(1, &sScissorRectFull);
if (m_spConstantBuffer)
{
m_spCommandList->SetGraphicsRootConstantBufferView(0, m_spConstantBuffer->GetD3D12Resource()->GetGPUVirtualAddress());
}
m_spCommandList->IASetPrimitiveTopology(m_ePrimitiveTopology);
m_spCommandList->IASetVertexBuffers(0, 1, &m_sVertexBufferView);
for (int i = 0; i < MAX_RESOURCES; i++)
{
if (m_spResources[i] != nullptr)
{
D3D12_GPU_DESCRIPTOR_HANDLE hGpuHandle = m_spDescriptorHeapSRV->GetGPUDescriptorHandleForHeapStart();
hGpuHandle.ptr += m_uSRVIncrement * i;
m_spCommandList->SetGraphicsRootDescriptorTable(i + 1, hGpuHandle);
}
}
return S_OK;
}
//==========================================================================================================
HRESULT PipelineState::Clear()
{
m_spCommandList->ClearState(m_spPipelineState);
m_spCommandList->OMSetRenderTargets(0, nullptr, false, nullptr);
return S_OK;
}
//==========================================================================================================
HRESULT PipelineState::Execute()
{
HRESULT hr = S_OK;
hr = m_spCommandList->Close();
if (FAILED(hr))
{
return hr;
}
ID3D12CommandList* ppCommandLists[] = { m_spCommandList };
m_spCommandQueue->ExecuteCommandLists(_countof(ppCommandLists), ppCommandLists);
hr = m_spCommandQueue->Signal(m_spFence, m_uFenceValue);
if (FAILED(hr))
{
return hr;
}
return hr;
}
//==========================================================================================================
HRESULT PipelineState::Reset()
{
HRESULT hr = S_OK;
TrackedAllocator& alloc = m_AllocatorQueue.GetNext();
if (m_spFence->GetCompletedValue() < (alloc.m_uFenceValue))
{
hr = m_spFence->SetEventOnCompletion(alloc.m_uFenceValue, m_hFenceEvent);
if (FAILED(hr))
{
return hr;
}
WaitForSingleObjectEx(m_hFenceEvent, INFINITE, FALSE);
}
m_uFenceValue++;
alloc.m_uFenceValue = m_uFenceValue;
hr = alloc.m_spCommandAllocator->Reset();
if (FAILED(hr))
{
return hr;
}
hr = m_spCommandList->Reset(alloc.m_spCommandAllocator, nullptr);
return hr;
}
void PipelineState::CopySubresourceRegion(P3D::IRenderDataResourceV500* pDestination, UINT DstX, UINT DstY, UINT DstZ,
P3D::IRenderDataResourceV500* pSource, const D3D12_BOX* pSourceBox)
{
if (pDestination == nullptr || pSource == nullptr || m_spCommandList == nullptr)
return;
D3D12_RESOURCE_STATES destState = pDestination->GetResourceState();
D3D12_RESOURCE_STATES srcState = pSource->GetResourceState();
D3D12_RESOURCE_BARRIER sBarriersBefore[2];
sBarriersBefore[0] = CD3DX12_RESOURCE_BARRIER::Transition(pDestination->GetD3D12Resource(), destState, D3D12_RESOURCE_STATE_COPY_DEST);
sBarriersBefore[1] = CD3DX12_RESOURCE_BARRIER::Transition(pSource->GetD3D12Resource(), srcState, D3D12_RESOURCE_STATE_COPY_SOURCE);
pDestination->SetResourceState(D3D12_RESOURCE_STATE_COPY_DEST);
pSource->SetResourceState(D3D12_RESOURCE_STATE_COPY_SOURCE);
m_spCommandList->ResourceBarrier(2, sBarriersBefore);
if (pDestination->GetD3D12Resource()->GetDesc().Format == D3D12_RESOURCE_DIMENSION_BUFFER)
{
D3D12_PLACED_SUBRESOURCE_FOOTPRINT PlacedFootprint;
m_spDevice->GetCopyableFootprints(&pDestination->GetD3D12Resource()->GetDesc(), 0, 1, 0, &PlacedFootprint, nullptr, nullptr, nullptr);
m_spCommandList->CopyTextureRegion(&CD3DX12_TEXTURE_COPY_LOCATION(pDestination->GetD3D12Resource(), PlacedFootprint), DstX, DstY, DstZ,
&CD3DX12_TEXTURE_COPY_LOCATION(pSource->GetD3D12Resource()), pSourceBox);
}
else
{
m_spCommandList->CopyTextureRegion(&CD3DX12_TEXTURE_COPY_LOCATION(pDestination->GetD3D12Resource()), DstX, DstY, DstZ,
&CD3DX12_TEXTURE_COPY_LOCATION(pSource->GetD3D12Resource()), pSourceBox);
}
D3D12_RESOURCE_BARRIER sBarriersAfter[2];
sBarriersAfter[0] = CD3DX12_RESOURCE_BARRIER::Transition(pDestination->GetD3D12Resource(), D3D12_RESOURCE_STATE_COPY_DEST, destState);
sBarriersAfter[1] = CD3DX12_RESOURCE_BARRIER::Transition(pSource->GetD3D12Resource(), D3D12_RESOURCE_STATE_COPY_SOURCE, srcState);
pDestination->SetResourceState(destState);
pSource->SetResourceState(srcState);
m_spCommandList->ResourceBarrier(2, sBarriersAfter);
}
void PipelineState::CopyResource(P3D::IRenderDataResourceV500* pDestination, P3D::IRenderDataResourceV500* pSource)
{
if (pDestination == nullptr || pSource == nullptr || m_spCommandList == nullptr)
return;
D3D12_RESOURCE_STATES destState = pDestination->GetResourceState();
D3D12_RESOURCE_STATES srcState = pSource->GetResourceState();
D3D12_PLACED_SUBRESOURCE_FOOTPRINT PlacedFootprint;
m_spDevice->GetCopyableFootprints(&pDestination->GetD3D12Resource()->GetDesc(), 0, 1, 0, &PlacedFootprint, nullptr, nullptr, nullptr);
D3D12_RESOURCE_BARRIER sBarriersBefore[2];
sBarriersBefore[0] = CD3DX12_RESOURCE_BARRIER::Transition(pDestination->GetD3D12Resource(), destState, D3D12_RESOURCE_STATE_COPY_DEST);
sBarriersBefore[1] = CD3DX12_RESOURCE_BARRIER::Transition(pSource->GetD3D12Resource(), srcState, D3D12_RESOURCE_STATE_COPY_SOURCE);
pDestination->SetResourceState(D3D12_RESOURCE_STATE_COPY_DEST);
pSource->SetResourceState(D3D12_RESOURCE_STATE_COPY_SOURCE);
m_spCommandList->ResourceBarrier(2, sBarriersBefore);
m_spCommandList->CopyResource(pDestination->GetD3D12Resource(), pSource->GetD3D12Resource());
D3D12_RESOURCE_BARRIER sBarriersAfter[2];
sBarriersAfter[0] = CD3DX12_RESOURCE_BARRIER::Transition(pDestination->GetD3D12Resource(), D3D12_RESOURCE_STATE_COPY_DEST, destState);
sBarriersAfter[1] = CD3DX12_RESOURCE_BARRIER::Transition(pSource->GetD3D12Resource(), D3D12_RESOURCE_STATE_COPY_SOURCE, srcState);
pDestination->SetResourceState(destState);
pSource->SetResourceState(srcState);
m_spCommandList->ResourceBarrier(2, sBarriersAfter);
}