D3D12: Improve robustness of command allocator and fence tracking

This commit is contained in:
Stenzek 2016-02-17 02:44:34 +10:00
parent 2f7870b046
commit efbb85da43
2 changed files with 96 additions and 70 deletions

View File

@ -2,6 +2,7 @@
// Licensed under GPLv2+ // Licensed under GPLv2+
// Refer to the license.txt file included. // Refer to the license.txt file included.
#include <algorithm>
#include <queue> #include <queue>
#include <vector> #include <vector>
@ -69,6 +70,9 @@ D3DCommandListManager::D3DCommandListManager(
} }
m_current_deferred_destruction_list = 0; m_current_deferred_destruction_list = 0;
std::fill(m_command_allocator_list_fences.begin(), m_command_allocator_list_fences.end(), 0);
std::fill(m_deferred_destruction_list_fences.begin(), m_deferred_destruction_list_fences.end(), 0);
} }
void D3DCommandListManager::SetInitialCommandListState() void D3DCommandListManager::SetInitialCommandListState()
@ -114,37 +118,26 @@ void D3DCommandListManager::ExecuteQueuedWork(bool wait_for_gpu_completion)
m_queued_command_list->QueueFenceGpuSignal(m_queue_fence, m_queue_fence_value); m_queued_command_list->QueueFenceGpuSignal(m_queue_fence, m_queue_fence_value);
ResetCommandListWithIdleCommandAllocator(); m_queued_command_list->ProcessQueuedItems(wait_for_gpu_completion);
m_queued_command_list->ProcessQueuedItems();
#else #else
CheckHR(m_backing_command_list->Close()); CheckHR(m_backing_command_list->Close());
ID3D12CommandList* const commandListsToExecute[1] = { m_backing_command_list }; ID3D12CommandList* const execute_list[1] = { m_backing_command_list };
m_command_queue->ExecuteCommandLists(1, commandListsToExecute); m_command_queue->ExecuteCommandLists(1, execute_list);
if (wait_for_gpu_completion)
{
CheckHR(m_command_queue->Signal(m_queue_fence, m_queue_fence_value)); CheckHR(m_command_queue->Signal(m_queue_fence, m_queue_fence_value));
}
if (m_current_command_allocator == 0)
{
PerformGpuRolloverChecks();
}
ResetCommandListWithIdleCommandAllocator();
#endif #endif
// Notify observers of the fence value for the current work to finish.
for (auto it : m_queue_fence_callbacks) for (auto it : m_queue_fence_callbacks)
it.second(it.first, m_queue_fence_value); it.second(it.first, m_queue_fence_value);
SetInitialCommandListState();
if (wait_for_gpu_completion) if (wait_for_gpu_completion)
{ WaitForGPUCompletion();
WaitOnCPUForFence(m_queue_fence, m_queue_fence_value);
} // Re-open the command list, using the current allocator.
ResetCommandList();
SetInitialCommandListState();
} }
void D3DCommandListManager::ExecuteQueuedWorkAndPresent(IDXGISwapChain* swap_chain, UINT sync_interval, UINT flags) void D3DCommandListManager::ExecuteQueuedWorkAndPresent(IDXGISwapChain* swap_chain, UINT sync_interval, UINT flags)
@ -154,60 +147,67 @@ void D3DCommandListManager::ExecuteQueuedWorkAndPresent(IDXGISwapChain* swap_cha
#ifdef USE_D3D12_QUEUED_COMMAND_LISTS #ifdef USE_D3D12_QUEUED_COMMAND_LISTS
CheckHR(m_queued_command_list->Close()); CheckHR(m_queued_command_list->Close());
m_queued_command_list->QueueExecute(); m_queued_command_list->QueueExecute();
m_queued_command_list->QueueFenceGpuSignal(m_queue_fence, m_queue_fence_value);
m_queued_command_list->QueuePresent(swap_chain, sync_interval, flags); m_queued_command_list->QueuePresent(swap_chain, sync_interval, flags);
m_queued_command_list->ProcessQueuedItems(true);
if (m_current_command_allocator == 0)
{
PerformGpuRolloverChecks();
}
m_current_command_allocator = (m_current_command_allocator + 1) % m_command_allocator_lists[m_current_command_allocator_list].size();
ResetCommandListWithIdleCommandAllocator();
SetInitialCommandListState();
#else
ExecuteQueuedWork();
m_command_queue->Signal(m_queue_fence, m_queue_fence_value);
CheckHR(swap_chain->Present(sync_interval, flags));
#endif
for (auto it : m_queue_fence_callbacks)
it.second(it.first, m_queue_fence_value);
}
void D3DCommandListManager::WaitForQueuedWorkToBeExecutedOnGPU()
{
// Wait for GPU to finish all outstanding work.
m_queue_fence_value++;
#ifdef USE_D3D12_QUEUED_COMMAND_LISTS
m_queued_command_list->QueueExecute();
m_queued_command_list->QueueFenceGpuSignal(m_queue_fence, m_queue_fence_value); m_queued_command_list->QueueFenceGpuSignal(m_queue_fence, m_queue_fence_value);
m_queued_command_list->ProcessQueuedItems(true); m_queued_command_list->ProcessQueuedItems(true);
#else #else
CheckHR(m_backing_command_list->Close());
ID3D12CommandList* const execute_list[1] = { m_backing_command_list };
m_command_queue->ExecuteCommandLists(1, execute_list);
CheckHR(swap_chain->Present(sync_interval, flags));
CheckHR(m_command_queue->Signal(m_queue_fence, m_queue_fence_value)); CheckHR(m_command_queue->Signal(m_queue_fence, m_queue_fence_value));
#endif #endif
WaitOnCPUForFence(m_queue_fence, m_queue_fence_value); // Notify observers of the fence value for the current work to finish.
for (auto it : m_queue_fence_callbacks)
it.second(it.first, m_queue_fence_value);
// Move to the next command allocator, this may mean switching allocator lists.
MoveToNextCommandAllocator();
ResetCommandList();
SetInitialCommandListState();
}
void D3DCommandListManager::WaitForGPUCompletion()
{
// Wait for GPU to finish all outstanding work.
// This method assumes that no command lists are open.
m_queue_frame_fence_value++;
#ifdef USE_D3D12_QUEUED_COMMAND_LISTS
m_queued_command_list->QueueFenceGpuSignal(m_queue_frame_fence, m_queue_frame_fence_value);
m_queued_command_list->ProcessQueuedItems(true);
#else
CheckHR(m_command_queue->Signal(m_queue_frame_fence, m_queue_frame_fence_value));
#endif
WaitOnCPUForFence(m_queue_frame_fence, m_queue_frame_fence_value);
// GPU is up to date with us. Therefore, it has finished with any pending resources.
ImmediatelyDestroyAllResourcesScheduledForDestruction();
// Command allocators are also up-to-date, so reset these.
ResetAllCommandAllocators();
} }
void D3DCommandListManager::PerformGpuRolloverChecks() void D3DCommandListManager::PerformGpuRolloverChecks()
{ {
// Insert fence to measure GPU progress, ensure we aren't using in-use command allocators. m_queue_frame_fence_value++;
if (m_queue_frame_fence->GetCompletedValue() < m_queue_frame_fence_value)
{ #ifdef USE_D3D12_QUEUED_COMMAND_LISTS
WaitOnCPUForFence(m_queue_frame_fence, m_queue_frame_fence_value); m_queued_command_list->QueueFenceGpuSignal(m_queue_frame_fence, m_queue_frame_fence_value);
} #else
CheckHR(m_command_queue->Signal(m_queue_frame_fence, m_queue_frame_fence_value));
#endif
// We now know that the previous 'set' of command lists has completed on GPU, and it is safe to // We now know that the previous 'set' of command lists has completed on GPU, and it is safe to
// release resources / start back at beginning of command allocator list. // release resources / start back at beginning of command allocator list.
// Begin Deferred Resource Destruction // Begin Deferred Resource Destruction
UINT safe_to_delete_deferred_destruction_list = (m_current_deferred_destruction_list - 1) % m_deferred_destruction_lists.size(); UINT safe_to_delete_deferred_destruction_list = (m_current_deferred_destruction_list - 1) % m_deferred_destruction_lists.size();
WaitOnCPUForFence(m_queue_frame_fence, m_deferred_destruction_list_fences[safe_to_delete_deferred_destruction_list]);
for (UINT i = 0; i < m_deferred_destruction_lists[safe_to_delete_deferred_destruction_list].size(); i++) for (UINT i = 0; i < m_deferred_destruction_lists[safe_to_delete_deferred_destruction_list].size(); i++)
{ {
@ -216,30 +216,37 @@ void D3DCommandListManager::PerformGpuRolloverChecks()
m_deferred_destruction_lists[safe_to_delete_deferred_destruction_list].clear(); m_deferred_destruction_lists[safe_to_delete_deferred_destruction_list].clear();
m_deferred_destruction_list_fences[m_current_deferred_destruction_list] = m_queue_frame_fence_value;
m_current_deferred_destruction_list = (m_current_deferred_destruction_list + 1) % m_deferred_destruction_lists.size(); m_current_deferred_destruction_list = (m_current_deferred_destruction_list + 1) % m_deferred_destruction_lists.size();
// End Deferred Resource Destruction // End Deferred Resource Destruction
// Begin Command Allocator Resets // Begin Command Allocator Resets
UINT safe_to_reset_command_allocator_list = (m_current_command_allocator_list - 1) % m_command_allocator_lists.size(); UINT safe_to_reset_command_allocator_list = (m_current_command_allocator_list - 1) % m_command_allocator_lists.size();
WaitOnCPUForFence(m_queue_frame_fence, m_command_allocator_list_fences[safe_to_reset_command_allocator_list]);
for (UINT i = 0; i < m_command_allocator_lists[safe_to_reset_command_allocator_list].size(); i++) for (UINT i = 0; i < m_command_allocator_lists[safe_to_reset_command_allocator_list].size(); i++)
{ {
CheckHR(m_command_allocator_lists[safe_to_reset_command_allocator_list][i]->Reset()); CheckHR(m_command_allocator_lists[safe_to_reset_command_allocator_list][i]->Reset());
} }
m_command_allocator_list_fences[m_current_command_allocator_list] = m_queue_frame_fence_value;
m_current_command_allocator_list = (m_current_command_allocator_list + 1) % m_command_allocator_lists.size(); m_current_command_allocator_list = (m_current_command_allocator_list + 1) % m_command_allocator_lists.size();
m_current_command_allocator = 0;
// End Command Allocator Resets // End Command Allocator Resets
m_queue_frame_fence_value++;
#ifdef USE_D3D12_QUEUED_COMMAND_LISTS
m_queued_command_list->QueueFenceGpuSignal(m_queue_frame_fence, m_queue_frame_fence_value);
#else
CheckHR(m_command_queue->Signal(m_queue_frame_fence, m_queue_frame_fence_value));
#endif
} }
void D3DCommandListManager::ResetCommandListWithIdleCommandAllocator() void D3DCommandListManager::MoveToNextCommandAllocator()
{
// Move to the next allocator in the current allocator list.
m_current_command_allocator = (m_current_command_allocator + 1) % m_command_allocator_lists[m_current_command_allocator_list].size();
// Did we wrap around? Move to the next set of allocators.
if (m_current_command_allocator == 0)
PerformGpuRolloverChecks();
}
void D3DCommandListManager::ResetCommandList()
{ {
#ifdef USE_D3D12_QUEUED_COMMAND_LISTS #ifdef USE_D3D12_QUEUED_COMMAND_LISTS
ID3D12QueuedCommandList* command_list = m_queued_command_list; ID3D12QueuedCommandList* command_list = m_queued_command_list;
@ -268,6 +275,20 @@ void D3DCommandListManager::ImmediatelyDestroyAllResourcesScheduledForDestructio
} }
} }
void D3DCommandListManager::ResetAllCommandAllocators()
{
for (auto& allocator_list : m_command_allocator_lists)
{
for (auto& allocator : allocator_list)
allocator->Reset();
}
// Move back to the start, using the first allocator of first list.
m_current_command_allocator = 0;
m_current_command_allocator_list = 0;
m_current_deferred_destruction_list = 0;
}
void D3DCommandListManager::ClearQueueAndWaitForCompletionOfInflightWork() void D3DCommandListManager::ClearQueueAndWaitForCompletionOfInflightWork()
{ {
// Wait for GPU to finish all outstanding work. // Wait for GPU to finish all outstanding work.
@ -305,8 +326,10 @@ D3DCommandListManager::~D3DCommandListManager()
void D3DCommandListManager::WaitOnCPUForFence(ID3D12Fence* fence, UINT64 fence_value) void D3DCommandListManager::WaitOnCPUForFence(ID3D12Fence* fence, UINT64 fence_value)
{ {
CheckHR(fence->SetEventOnCompletion(fence_value, m_wait_on_cpu_fence_event)); if (fence->GetCompletedValue() >= fence_value)
return;
CheckHR(fence->SetEventOnCompletion(fence_value, m_wait_on_cpu_fence_event));
WaitForSingleObject(m_wait_on_cpu_fence_event, INFINITE); WaitForSingleObject(m_wait_on_cpu_fence_event, INFINITE);
} }

View File

@ -38,11 +38,10 @@ public:
void ExecuteQueuedWork(bool wait_for_gpu_completion = false); void ExecuteQueuedWork(bool wait_for_gpu_completion = false);
void ExecuteQueuedWorkAndPresent(IDXGISwapChain* swap_chain, UINT sync_interval, UINT flags); void ExecuteQueuedWorkAndPresent(IDXGISwapChain* swap_chain, UINT sync_interval, UINT flags);
void WaitForQueuedWorkToBeExecutedOnGPU();
void ClearQueueAndWaitForCompletionOfInflightWork(); void ClearQueueAndWaitForCompletionOfInflightWork();
void DestroyResourceAfterCurrentCommandListExecuted(ID3D12Resource* resource); void DestroyResourceAfterCurrentCommandListExecuted(ID3D12Resource* resource);
void ImmediatelyDestroyAllResourcesScheduledForDestruction(); void ImmediatelyDestroyAllResourcesScheduledForDestruction();
void ResetAllCommandAllocators();
void SetCommandListDirtyState(unsigned int command_list_state, bool dirty); void SetCommandListDirtyState(unsigned int command_list_state, bool dirty);
bool GetCommandListDirtyState(COMMAND_LIST_STATE command_list_state) const; bool GetCommandListDirtyState(COMMAND_LIST_STATE command_list_state) const;
@ -65,8 +64,10 @@ public:
private: private:
void WaitForGPUCompletion();
void PerformGpuRolloverChecks(); void PerformGpuRolloverChecks();
void ResetCommandListWithIdleCommandAllocator(); void MoveToNextCommandAllocator();
void ResetCommandList();
unsigned int m_command_list_dirty_state = UINT_MAX; unsigned int m_command_list_dirty_state = UINT_MAX;
D3D_PRIMITIVE_TOPOLOGY m_command_list_current_topology = D3D_PRIMITIVE_TOPOLOGY_UNDEFINED; D3D_PRIMITIVE_TOPOLOGY m_command_list_current_topology = D3D_PRIMITIVE_TOPOLOGY_UNDEFINED;
@ -85,6 +86,7 @@ private:
UINT m_current_command_allocator; UINT m_current_command_allocator;
UINT m_current_command_allocator_list; UINT m_current_command_allocator_list;
std::array<std::vector<ID3D12CommandAllocator*>, 2> m_command_allocator_lists; std::array<std::vector<ID3D12CommandAllocator*>, 2> m_command_allocator_lists;
std::array<UINT64, 2> m_command_allocator_list_fences;
ID3D12GraphicsCommandList* m_backing_command_list; ID3D12GraphicsCommandList* m_backing_command_list;
ID3D12QueuedCommandList* m_queued_command_list; ID3D12QueuedCommandList* m_queued_command_list;
@ -93,6 +95,7 @@ private:
UINT m_current_deferred_destruction_list; UINT m_current_deferred_destruction_list;
std::array<std::vector<ID3D12Resource*>, 2> m_deferred_destruction_lists; std::array<std::vector<ID3D12Resource*>, 2> m_deferred_destruction_lists;
std::array<UINT64, 2> m_deferred_destruction_list_fences;
}; };
} // namespace } // namespace