// Copyright 2015 Dolphin Emulator Project // Licensed under GPLv2+ // Refer to the license.txt file included. #include #include #include #include "VideoBackends/D3D12/D3DBase.h" #include "VideoBackends/D3D12/D3DCommandListManager.h" #include "VideoBackends/D3D12/D3DDescriptorHeapManager.h" #include "VideoBackends/D3D12/D3DQueuedCommandList.h" #include "VideoBackends/D3D12/D3DState.h" #include "VideoBackends/D3D12/D3DTexture.h" #include "VideoBackends/D3D12/Render.h" #include "VideoBackends/D3D12/ShaderConstantsManager.h" #include "VideoBackends/D3D12/VertexManager.h" static constexpr unsigned int COMMAND_ALLOCATORS_PER_LIST = 2; namespace DX12 { extern StateCache gx_state_cache; D3DCommandListManager::D3DCommandListManager( D3D12_COMMAND_LIST_TYPE command_list_type, ID3D12Device* device, ID3D12CommandQueue* command_queue ) : m_device(device), m_command_queue(command_queue) { // Create two lists, with two command allocators each. This corresponds to up to two frames in flight at once. m_current_command_allocator = 0; m_current_command_allocator_list = 0; for (UINT i = 0; i < COMMAND_ALLOCATORS_PER_LIST; i++) { for (UINT j = 0; j < m_command_allocator_lists.size(); j++) { ID3D12CommandAllocator* command_allocator = nullptr; CheckHR(m_device->CreateCommandAllocator(command_list_type, IID_PPV_ARGS(&command_allocator))); m_command_allocator_lists[j].push_back(command_allocator); } } // Create backing command list. CheckHR(m_device->CreateCommandList(0, command_list_type, m_command_allocator_lists[m_current_command_allocator_list][0], nullptr, IID_PPV_ARGS(&m_backing_command_list))); #ifdef USE_D3D12_QUEUED_COMMAND_LISTS m_queued_command_list = new ID3D12QueuedCommandList(m_backing_command_list, m_command_queue); #endif // Create fence that will be used to measure GPU progress of app rendering requests (e.g. CPU readback of GPU data). m_queue_fence_value = 0; CheckHR(m_device->CreateFence(m_queue_fence_value, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&m_queue_fence))); // Create fence that will be used internally by D3DCommandListManager for frame-level resource tracking. m_queue_frame_fence_value = 0; CheckHR(m_device->CreateFence(m_queue_frame_fence_value, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&m_queue_frame_fence))); // Create event that will be used for waiting on CPU until a fence is signaled by GPU. m_wait_on_cpu_fence_event = CreateEvent(nullptr, FALSE, FALSE, nullptr); // Pre-size the deferred destruction lists. for (UINT i = 0; i < m_deferred_destruction_lists.size(); i++) { m_deferred_destruction_lists[i].reserve(200); } 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() { ID3D12GraphicsCommandList* command_list = nullptr; GetCommandList(&command_list); command_list->SetDescriptorHeaps(static_cast(D3D::gpu_descriptor_heaps.size()), D3D::gpu_descriptor_heaps.data()); command_list->SetGraphicsRootSignature(D3D::default_root_signature); if (g_renderer) { // It is possible that we change command lists in the middle of the frame. In that case, restore // the viewport/scissor to the current console GPU state. g_renderer->RestoreAPIState(); } m_command_list_dirty_state = UINT_MAX; command_list->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP); m_command_list_current_topology = D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP; if (g_vertex_manager) reinterpret_cast(g_vertex_manager.get())->SetIndexBuffer(); } void D3DCommandListManager::GetCommandList(ID3D12GraphicsCommandList** command_list) const { #ifdef USE_D3D12_QUEUED_COMMAND_LISTS *command_list = this->m_queued_command_list; #else *command_list = this->m_backing_command_list; #endif } void D3DCommandListManager::ExecuteQueuedWork(bool wait_for_gpu_completion) { m_queue_fence_value++; #ifdef USE_D3D12_QUEUED_COMMAND_LISTS m_queued_command_list->Close(); m_queued_command_list->QueueExecute(); m_queued_command_list->QueueFenceGpuSignal(m_queue_fence, m_queue_fence_value); m_queued_command_list->ProcessQueuedItems(wait_for_gpu_completion, wait_for_gpu_completion); #else CheckHR(m_backing_command_list->Close()); ID3D12CommandList* const execute_list[1] = { m_backing_command_list }; m_command_queue->ExecuteCommandLists(1, execute_list); CheckHR(m_command_queue->Signal(m_queue_fence, m_queue_fence_value)); #endif // 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); if (wait_for_gpu_completion) WaitForGPUCompletion(); // Re-open the command list, using the current allocator. ResetCommandList(); SetInitialCommandListState(); } void D3DCommandListManager::ExecuteQueuedWorkAndPresent(IDXGISwapChain* swap_chain, UINT sync_interval, UINT flags) { m_queue_fence_value++; #ifdef USE_D3D12_QUEUED_COMMAND_LISTS m_queued_command_list->Close(); m_queued_command_list->QueueExecute(); m_queued_command_list->QueuePresent(swap_chain, sync_interval, flags); m_queued_command_list->QueueFenceGpuSignal(m_queue_fence, m_queue_fence_value); m_queued_command_list->ProcessQueuedItems(true); #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)); #endif // 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::DestroyAllPendingResources() { for (auto& destruction_list : m_deferred_destruction_lists) { for (auto& resource : destruction_list) resource->Release(); destruction_list.clear(); } } 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::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. DestroyAllPendingResources(); // Command allocators are also up-to-date, so reset these. ResetAllCommandAllocators(); } void D3DCommandListManager::PerformGPURolloverChecks() { 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 // 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. // Begin Deferred Resource Destruction 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++) { CHECK(m_deferred_destruction_lists[safe_to_delete_deferred_destruction_list][i]->Release() == 0, "Resource leak."); } 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(); // End Deferred Resource Destruction // Begin Command Allocator Resets 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++) { 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 = 0; // End Command Allocator Resets } 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 ID3D12QueuedCommandList* command_list = m_queued_command_list; #else ID3D12GraphicsCommandList* command_list = m_backing_command_list; #endif CheckHR(command_list->Reset(m_command_allocator_lists[m_current_command_allocator_list][m_current_command_allocator], nullptr)); } void D3DCommandListManager::DestroyResourceAfterCurrentCommandListExecuted(ID3D12Resource* resource) { CHECK(resource, "Null resource being inserted!"); m_deferred_destruction_lists[m_current_deferred_destruction_list].push_back(resource); } D3DCommandListManager::~D3DCommandListManager() { #ifdef USE_D3D12_QUEUED_COMMAND_LISTS // Wait for background thread to exit. m_queued_command_list->Release(); #endif // The command list will still be open, close it before destroying. m_backing_command_list->Close(); DestroyAllPendingResources(); m_backing_command_list->Release(); for (auto& allocator_list : m_command_allocator_lists) { for (auto& resource : allocator_list) resource->Release(); } m_queue_fence->Release(); m_queue_frame_fence->Release(); CloseHandle(m_wait_on_cpu_fence_event); } void D3DCommandListManager::WaitOnCPUForFence(ID3D12Fence* fence, UINT64 fence_value) { 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); } void D3DCommandListManager::SetCommandListDirtyState(unsigned int command_list_state, bool dirty) { if (dirty) m_command_list_dirty_state |= command_list_state; else m_command_list_dirty_state &= ~command_list_state; } bool D3DCommandListManager::GetCommandListDirtyState(COMMAND_LIST_STATE command_list_state) const { return ((m_command_list_dirty_state & command_list_state) != 0); } void D3DCommandListManager::SetCommandListPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY primitive_topology) { m_command_list_current_topology = primitive_topology; } D3D_PRIMITIVE_TOPOLOGY D3DCommandListManager::GetCommandListPrimitiveTopology() const { return m_command_list_current_topology; } void D3DCommandListManager::CPUAccessNotify() { m_cpu_access_last_frame = true; m_cpu_access_this_frame = true; m_draws_since_last_execution = 0; }; ID3D12Fence* D3DCommandListManager::RegisterQueueFenceCallback(void* owning_object, PFN_QUEUE_FENCE_CALLBACK* callback_function) { m_queue_fence_callbacks[owning_object] = callback_function; return m_queue_fence; } void D3DCommandListManager::RemoveQueueFenceCallback(void* owning_object) { m_queue_fence_callbacks.erase(owning_object); } } // namespace DX12