There are several approaches to getting a basic render loop up and running for fast semantics. Sample code demonstrates each approach.
The basic process for a fast render loop goes through the following stages.
With fast semantics, serious problems can occur if code is not changed from the normal D3D11 approach. In particular:
| Fast Render Loop Approach | Description |
|---|---|
| Fast Render Loop 1 (Save pointers to back buffer textures) | For titles that are performance-bound and cannot spare any cycles. The preferred way of setting things up and doing a fast semantics render loop. When initializing resources, save off pointers to the back buffer textures from the swap chain, and then create the render target view. |
| Fast Render Loop 2 (Maintain list of render target views and GPU fences) | Requires less synchronization than Approach 1. Maintains a list of render target views and GPU fences that are used to synchronize deletion of the render target. To synchronize without blocking, utilize one more render target view than the number of back buffers. This ensures that drawing to all render targets has completed before you release the view. Verify that the GPU is done by calling IsFencePending. |
| Fast Render Loop 3 (Call PlaceSwapChainView) | Requires less synchronization than Approach 1. Easiest. Calls PlaceSwapChainView to set a pre-allocated view to point to the back-buffer. |
These approaches are described more below.
This approach is the preferred way of setting things up and doing a fast semantics render loop. When we initialize resources, we save off pointers to the back buffer textures from the swap chain, and then create the render target view.
DXGI_SWAP_CHAIN_DESC swap_desc = { 0 };
m_swapChain->GetDesc(&swap_desc);
m_uNumBackBuffers = swap_desc.BufferCount;
for(unsigned int x = 0; x < m_uNumBackBuffers; x++)
{
Microsoft::WRL::ComPtr<ID3D11Texture2D> backBuffer;
Microsoft::WRL::ComPtr<ID3D11RenderTargetView> renderTargetView;
DX::ThrowIfFailed(m_swapChain->GetBuffer(x, __uuidof(ID3D11Texture2D), &backBuffer));
m_BackBuffers.push_back(backBuffer);
DX::ThrowIfFailed(m_d3dDevice->CreateRenderTargetView(backBuffer.Get(), nullptr, &renderTargetView));
m_RenderTargetViews.push_back(renderTargetView);
}
When we draw the scene, we just cycle through the back buffer textures and views. This is very efficient because we allocate everything up front and just hand off the appropriate pointers to D3D.
There are some issues here, though. The call to DXGIXPresentArray will present the current back-buffer at index 0 and then cycle a new back-buffer into index 0 (for double buffering, it will cycle in the back buffer at index1). There is no way for the title to control which back-buffer is at index 0, but the title code still needs to be perfectly synchronized. In other words, when this code sets a render-target to the pipeline, it must make sure it specified the render target at index 0 for that draw.
In general, the two will stay in sync as long as the title starts with the buffer at index 0 and then increments once per frame. If something goes wrong and things get out of sync, the recommended solution is to call DXGIXGetFrameStatistics to get the number of swaps that have occurred, and then do the math to figure out which back buffer is actually at index 0.
// Draws the scene
void Game::Render()
{
// Add a PIX marker.
PIXBeginEvent(EVT_COLOR_RENDER, L"Render");
//
// Setup the back buffer for binding and clearing.
//
// Call InsertWaitOnPresent before we draw the scene to the current back buffer.
m_d3dContext->InsertWaitOnPresent(0, m_BackBuffers[m_uActiveBackBuffer].Get());
// Clear the views
const float clearColor[] = { 0.39f, 0.58f, 0.93f, 1.0f };
m_d3dContext->ClearRenderTargetView(m_RenderTargetViews[m_uActiveBackBuffer].Get(), clearColor);
m_d3dContext->ClearDepthStencilView(m_depthStencilView.Get(), D3D11_CLEAR_DEPTH, 1.0f, 0);
// Bind the views to the pipeline after clear is called.
m_d3dContext->OMSetRenderTargets(1, m_RenderTargetViews[m_uActiveBackBuffer].GetAddressOf(), m_depthStencilView.Get());
// Reset viewport state each frame.
m_d3dContext->RSSetViewports(1, &m_viewPort);
//
// TODO: Add drawing here.
//
//
// Decompress the buffers and present.
//
// Decompress the back buffer.
m_d3dContext->DecompressResource(pBackBuffer.Get(), 0, nullptr, pBackBuffer.Get(), 0, nullptr, DXGI_FORMAT_B8G8R8A8_UNORM, D3D11X_DECOMPRESS_PROPAGATE_COLOR_CLEAR);
// Present the scene.
HRESULT hr = DXGIXPresentArray( 1, 0, 0, 1, m_swapChain.GetAddressOf(), &m_PresentArrayParams);
// Cycle to the next active back buffer.
m_uActiveBackBuffer ++;
if(m_uActiveBackBuffer >= m_uNumBackBuffers )
{
m_uActiveBackBuffer = 0;
}
PIXEndEvent();
}
This second technique is pretty simple and does not require the title to synchronize the back buffers like the previous approach.
This technique maintains a list of render target views and GPU fences that are used to synchronize deletion of the render target. In order to synchronize without blocking (for example, blocking by calling the IsFencePending method until the fence is no longer pending), you can utilize one more render target view than the number of back buffers. If there are two back buffers, you should have three render target view references. This ensures that drawing to all render targets has completed before you release the view. You can verify that the GPU is done by calling the IsFencePending method once and asserting on the result.
This example uses smart COM pointers that automatically release the interfaces when the Game::Render method goes out of scope.
// Draws the scene
void Game::Render()
{
PIXBeginEvent(EVT_COLOR_RENDER, L"Render");
//
// Set up the back buffer for binding and clearing.
//
Microsoft::WRL::ComPtr<ID3D11Texture2D> pBackBuffer;
Microsoft::WRL::ComPtr<ID3D11RenderTargetView> pRenderTargetView;
// Get the back buffer currently at index 0.
DX::ThrowIfFailed(m_swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), &pBackBuffer));
// Create a view to the back buffer in order to bind.
DX::ThrowIfFailed(m_d3dDevice->CreateRenderTargetView(pBackBuffer.Get(), nullptr, &pRenderTargetView));
// Call InsertWaitOnPresent before we draw the scene to the current back buffer.
m_d3dContext->InsertWaitOnPresent(0, pBackBuffer.Get());
// Clear the views
const float clearColor[] = { 0.39f, 0.58f, 0.93f, 1.0f };
m_d3dContext->OMSetRenderTargets(1, pRenderTargetView.GetAddressOf(), m_depthStencilView.Get());
m_d3dContext->ClearRenderTargetView(pRenderTargetView.Get(), clearColor);
m_d3dContext->ClearDepthStencilView(m_depthStencilView.Get(), D3D11_CLEAR_DEPTH, 1.0f, 0);
// Re-bind the views to the pipeline after clear is called.
m_d3dContext->OMSetRenderTargets(1, pRenderTargetView.GetAddressOf(), m_depthStencilView.Get());
// Reset viewport state each frame.
m_d3dContext->RSSetViewports(1, &m_viewPort);
//
// TODO: Add drawing here.
//
//
// Decompress the buffers and present.
//
// Decompress the back buffer.
m_d3dContext->DecompressResource(m_BackBuffers[m_uActiveBackBuffer].Get(), 0, nullptr, m_BackBuffers[m_uActiveBackBuffer].Get(), 0, nullptr, DXGI_FORMAT_B8G8R8A8_UNORM, D3D11X_DECOMPRESS_PROPAGATE_COLOR_CLEAR);
// Re-bind the views to the pipeline after DecompressResource is called.
m_d3dContext->OMSetRenderTargets(1, pRenderTargetView.GetAddressOf(), m_depthStencilView.Get());
// Present the scene.
HRESULT hr = DXGIXPresentArray( 1, 0, 0, 1, m_swapChain.GetAddressOf(), &m_PresentArrayParams);
// Make sure the GPU is done with the render target view
// in the next slot before we release it.
if (m_pFencedRenderTargetViews[m_dwCurFencedRTView] != nullptr)
{
// We are triple buffering the views, but have only double buffered the
// back buffers. This means that the GPU should be done with the
// render target view by the time we get to it here. Do a check to verify.
assert(!m_d3dDevice->IsFencePending(m_pRenderTargetViewFences[m_dwCurFencedRTView]));
// m_pRenderTargetViewFences[m_dwCurFencedRTView] is not pending. The view is no longer in use by the GPU.
// release the old render target view.
m_pFencedRenderTargetViews[m_dwCurFencedRTView] = nullptr;
}
// Insert a fence to track when the GPU is done with this RT view.
m_pRenderTargetViewFences[m_dwCurFencedRTView] = m_d3dContext->InsertFence(0);
// Save a reference to the RT view so we can release it when the GPU is done.
m_pFencedRenderTargetViews[m_dwCurFencedRTView] = pRenderTargetView;
m_dwCurFencedRTView ++;
if(m_dwCurFencedRTView >= m_dwNumFencedRTViews)
{
m_dwCurFencedRTView = 0;
}
PIXEndEvent();
}
This is another quick technique that uses the PlaceSwapChainView method to set a pre-allocated view to point to the specified back-buffer.
// Draws the scene
void Game::Render()
{
PIXBeginEvent(EVT_COLOR_RENDER, L"Render");
//
// Setup the back buffer for binding and clearing.
//
Microsoft::WRL::ComPtr<ID3D11Texture2D> pBackBuffer;
// Get the back buffer currently at index 0.
DX::ThrowIfFailed(m_swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), &pBackBuffer));
// Fixup the default render target view to point to the back buffer currently at index 0.
m_d3dDevice->PlaceSwapChainView(pBackBuffer.Get(), m_renderTargetView.Get());
// Call InsertWaitOnPresent before we draw the scene to the current back buffer.
m_d3dContext->InsertWaitOnPresent(0, pBackBuffer.Get());
// Clear the views
const float clearColor[] = { 0.39f, 0.58f, 0.93f, 1.000f };
m_d3dContext->ClearRenderTargetView(m_renderTargetView.Get(), clearColor);
m_d3dContext->ClearDepthStencilView(m_depthStencilView.Get(), D3D11_CLEAR_DEPTH, 1.0f, 0);
// Bind the views to the pipeline after clear is called.
m_d3dContext->OMSetRenderTargets(1, m_renderTargetView.GetAddressOf(), m_depthStencilView.Get());
// Reset viewport state each frame.
m_d3dContext->RSSetViewports(1, &m_viewPort);
//
// TODO: Add drawing here.
//
//
// Decompress the buffers and present.
//
// Decompress the back buffer.
m_d3dContext->DecompressResource(m_BackBuffers[m_uActiveBackBuffer].Get(), 0, nullptr, m_BackBuffers[m_uActiveBackBuffer].Get(), 0, nullptr, DXGI_FORMAT_B8G8R8A8_UNORM, D3D11X_DECOMPRESS_PROPAGATE_COLOR_CLEAR);
// Present the scene.
HRESULT hr = DXGIXPresentArray( 1, 0, 0, 1, m_swapChain.GetAddressOf(), &m_PresentArrayParams);
PIXEndEvent();
}