diff options
Diffstat (limited to 'Source/Core/VideoBackends/D3D12/PerfQuery.cpp')
| -rw-r--r-- | Source/Core/VideoBackends/D3D12/PerfQuery.cpp | 235 |
1 files changed, 235 insertions, 0 deletions
diff --git a/Source/Core/VideoBackends/D3D12/PerfQuery.cpp b/Source/Core/VideoBackends/D3D12/PerfQuery.cpp new file mode 100644 index 0000000000..98e24d934c --- /dev/null +++ b/Source/Core/VideoBackends/D3D12/PerfQuery.cpp @@ -0,0 +1,235 @@ +// Copyright 2019 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include <algorithm> + +#include "Common/Assert.h" +#include "Common/Logging/Log.h" +#include "VideoBackends/D3D12/Common.h" +#include "VideoBackends/D3D12/DXContext.h" +#include "VideoBackends/D3D12/PerfQuery.h" +#include "VideoBackends/D3D12/Renderer.h" + +namespace DX12 +{ +PerfQuery::PerfQuery() = default; + +PerfQuery::~PerfQuery() = default; + +bool PerfQuery::Initialize() +{ + constexpr D3D12_QUERY_HEAP_DESC desc = {D3D12_QUERY_HEAP_TYPE_OCCLUSION, PERF_QUERY_BUFFER_SIZE}; + HRESULT hr = g_dx_context->GetDevice()->CreateQueryHeap(&desc, IID_PPV_ARGS(&m_query_heap)); + CHECK(SUCCEEDED(hr), "Failed to create query heap"); + if (FAILED(hr)) + return false; + + constexpr D3D12_HEAP_PROPERTIES heap_properties = {D3D12_HEAP_TYPE_READBACK}; + constexpr D3D12_RESOURCE_DESC resource_desc = {D3D12_RESOURCE_DIMENSION_BUFFER, + 0, + PERF_QUERY_BUFFER_SIZE * sizeof(PerfQueryDataType), + 1, + 1, + 1, + DXGI_FORMAT_UNKNOWN, + {1, 0}, + D3D12_TEXTURE_LAYOUT_ROW_MAJOR, + D3D12_RESOURCE_FLAG_NONE}; + hr = g_dx_context->GetDevice()->CreateCommittedResource( + &heap_properties, D3D12_HEAP_FLAG_NONE, &resource_desc, D3D12_RESOURCE_STATE_COPY_DEST, + nullptr, IID_PPV_ARGS(&m_query_readback_buffer)); + CHECK(SUCCEEDED(hr), "Failed to create query buffer"); + if (FAILED(hr)) + return false; + + return true; +} + +void PerfQuery::EnableQuery(PerfQueryGroup type) +{ + // Block if there are no free slots. + // Otherwise, try to keep half of them available. + if (m_query_count > m_query_buffer.size() / 2) + { + const bool do_resolve = m_unresolved_queries > m_query_buffer.size() / 2; + const bool blocking = m_query_count == PERF_QUERY_BUFFER_SIZE; + PartialFlush(do_resolve, blocking); + } + + if (type == PQG_ZCOMP_ZCOMPLOC || type == PQG_ZCOMP) + { + ActiveQuery& entry = m_query_buffer[m_query_next_pos]; + ASSERT(!entry.has_value && !entry.resolved); + entry.has_value = true; + + g_dx_context->GetCommandList()->BeginQuery(m_query_heap.Get(), D3D12_QUERY_TYPE_OCCLUSION, + m_query_next_pos); + } +} + +void PerfQuery::DisableQuery(PerfQueryGroup type) +{ + if (type == PQG_ZCOMP_ZCOMPLOC || type == PQG_ZCOMP) + { + g_dx_context->GetCommandList()->EndQuery(m_query_heap.Get(), D3D12_QUERY_TYPE_OCCLUSION, + m_query_next_pos); + m_query_next_pos = (m_query_next_pos + 1) % PERF_QUERY_BUFFER_SIZE; + m_query_count++; + m_unresolved_queries++; + } +} + +void PerfQuery::ResetQuery() +{ + m_query_count = 0; + m_unresolved_queries = 0; + m_query_resolve_pos = 0; + m_query_readback_pos = 0; + m_query_next_pos = 0; + std::fill_n(m_results, ArraySize(m_results), 0); + for (auto& entry : m_query_buffer) + { + entry.fence_value = 0; + entry.resolved = false; + entry.has_value = false; + } +} + +u32 PerfQuery::GetQueryResult(PerfQueryType type) +{ + u32 result = 0; + if (type == PQ_ZCOMP_INPUT_ZCOMPLOC || type == PQ_ZCOMP_OUTPUT_ZCOMPLOC) + result = m_results[PQG_ZCOMP_ZCOMPLOC]; + else if (type == PQ_ZCOMP_INPUT || type == PQ_ZCOMP_OUTPUT) + result = m_results[PQG_ZCOMP]; + else if (type == PQ_BLEND_INPUT) + result = m_results[PQG_ZCOMP] + m_results[PQG_ZCOMP_ZCOMPLOC]; + else if (type == PQ_EFB_COPY_CLOCKS) + result = m_results[PQG_EFB_COPY_CLOCKS]; + + return result / 4; +} + +void PerfQuery::FlushResults() +{ + while (!IsFlushed()) + PartialFlush(true, true); +} + +bool PerfQuery::IsFlushed() const +{ + return m_query_count == 0; +} + +void PerfQuery::ResolveQueries() +{ + // Do we need to split the resolve as it's wrapping around? + if ((m_query_resolve_pos + m_unresolved_queries) > PERF_QUERY_BUFFER_SIZE) + ResolveQueries(PERF_QUERY_BUFFER_SIZE - m_query_resolve_pos); + + ResolveQueries(m_unresolved_queries); +} + +void PerfQuery::ResolveQueries(u32 query_count) +{ + DEBUG_ASSERT(m_unresolved_queries >= query_count && + (m_query_resolve_pos + query_count) <= PERF_QUERY_BUFFER_SIZE); + + g_dx_context->GetCommandList()->ResolveQueryData( + m_query_heap.Get(), D3D12_QUERY_TYPE_OCCLUSION, m_query_resolve_pos, query_count, + m_query_readback_buffer.Get(), m_query_resolve_pos * sizeof(PerfQueryDataType)); + + // Flag all queries as available, but with a fence that has to be completed first + for (u32 i = 0; i < query_count; i++) + { + ActiveQuery& entry = m_query_buffer[m_query_resolve_pos + i]; + DEBUG_ASSERT(entry.has_value && !entry.resolved); + entry.fence_value = g_dx_context->GetCurrentFenceValue(); + entry.resolved = true; + } + m_query_resolve_pos = (m_query_resolve_pos + query_count) % PERF_QUERY_BUFFER_SIZE; + m_unresolved_queries -= query_count; +} + +void PerfQuery::ReadbackQueries() +{ + const u64 completed_fence_counter = g_dx_context->GetCompletedFenceValue(); + + // Need to save these since ProcessResults will modify them. + const u32 outstanding_queries = m_query_count; + u32 readback_count = 0; + for (u32 i = 0; i < outstanding_queries; i++) + { + u32 index = (m_query_readback_pos + readback_count) % PERF_QUERY_BUFFER_SIZE; + const ActiveQuery& entry = m_query_buffer[index]; + if (!entry.resolved || entry.fence_value > completed_fence_counter) + break; + + // If this wrapped around, we need to flush the entries before the end of the buffer. + if (index < m_query_readback_pos) + { + ReadbackQueries(readback_count); + DEBUG_ASSERT(m_query_readback_pos == 0); + readback_count = 0; + } + + readback_count++; + } + + if (readback_count > 0) + ReadbackQueries(readback_count); +} + +void PerfQuery::ReadbackQueries(u32 query_count) +{ + // Should be at maximum query_count queries pending. + ASSERT(query_count <= m_query_count && + (m_query_readback_pos + query_count) <= PERF_QUERY_BUFFER_SIZE); + + const D3D12_RANGE read_range = {m_query_readback_pos * sizeof(PerfQueryDataType), + (m_query_readback_pos + query_count) * sizeof(PerfQueryDataType)}; + u8* mapped_ptr; + HRESULT hr = m_query_readback_buffer->Map(0, &read_range, reinterpret_cast<void**>(&mapped_ptr)); + CHECK(SUCCEEDED(hr), "Failed to map query readback buffer"); + if (FAILED(hr)) + return; + + // Remove pending queries. + for (u32 i = 0; i < query_count; i++) + { + u32 index = (m_query_readback_pos + i) % PERF_QUERY_BUFFER_SIZE; + ActiveQuery& entry = m_query_buffer[index]; + + // Should have a fence associated with it (waiting for a result). + ASSERT(entry.fence_value != 0); + entry.fence_value = 0; + entry.resolved = false; + entry.has_value = false; + + // Grab result from readback buffer, it will already have been invalidated. + PerfQueryDataType result; + std::memcpy(&result, mapped_ptr + (index * sizeof(PerfQueryDataType)), sizeof(result)); + + // NOTE: Reported pixel metrics should be referenced to native resolution + m_results[entry.query_type] += + static_cast<u32>(static_cast<u64>(result) * EFB_WIDTH / g_renderer->GetTargetWidth() * + EFB_HEIGHT / g_renderer->GetTargetHeight()); + } + + constexpr D3D12_RANGE write_range = {0, 0}; + m_query_readback_buffer->Unmap(0, &write_range); + + m_query_readback_pos = (m_query_readback_pos + query_count) % PERF_QUERY_BUFFER_SIZE; + m_query_count -= query_count; +} + +void PerfQuery::PartialFlush(bool resolve, bool blocking) +{ + // Submit a command buffer in the background if the front query is not bound to one. + if ((resolve || blocking) && !m_query_buffer[m_query_resolve_pos].resolved) + Renderer::GetInstance()->ExecuteCommandList(blocking); + + ReadbackQueries(); +} +} // namespace DX12 |
