diff options
| author | Mat M <mathew1800@gmail.com> | 2016-09-30 19:15:50 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2016-09-30 19:15:50 -0400 |
| commit | 46b9383280a623753686ab6df6c479e784e416ae (patch) | |
| tree | 9ffb2cbdba6d39a29d6fed9df4db03166755a20d /Source/Core/VideoBackends/Vulkan/PerfQuery.cpp | |
| parent | f3cdc6433cba253fa4485cd7e27025c31410ed0c (diff) | |
| parent | a8194cff3ca8f9b85a779da6b8214b58ba3fa7d0 (diff) | |
Merge pull request #3935 from stenzek/vulkan-pr
Vulkan Backend
Diffstat (limited to 'Source/Core/VideoBackends/Vulkan/PerfQuery.cpp')
| -rw-r--r-- | Source/Core/VideoBackends/Vulkan/PerfQuery.cpp | 370 |
1 files changed, 370 insertions, 0 deletions
diff --git a/Source/Core/VideoBackends/Vulkan/PerfQuery.cpp b/Source/Core/VideoBackends/Vulkan/PerfQuery.cpp new file mode 100644 index 0000000000..0ee31e3d00 --- /dev/null +++ b/Source/Core/VideoBackends/Vulkan/PerfQuery.cpp @@ -0,0 +1,370 @@ +// Copyright 2016 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include <algorithm> +#include <cstring> +#include <functional> + +#include "Common/Assert.h" + +#include "VideoBackends/Vulkan/CommandBufferManager.h" +#include "VideoBackends/Vulkan/PerfQuery.h" +#include "VideoBackends/Vulkan/Renderer.h" +#include "VideoBackends/Vulkan/StagingBuffer.h" +#include "VideoBackends/Vulkan/StateTracker.h" +#include "VideoBackends/Vulkan/Util.h" +#include "VideoBackends/Vulkan/VulkanContext.h" + +namespace Vulkan +{ +PerfQuery::PerfQuery() +{ +} + +PerfQuery::~PerfQuery() +{ + g_command_buffer_mgr->RemoveFencePointCallback(this); + + if (m_query_pool != VK_NULL_HANDLE) + vkDestroyQueryPool(g_vulkan_context->GetDevice(), m_query_pool, nullptr); +} + +bool PerfQuery::Initialize(StateTracker* state_tracker) +{ + m_state_tracker = state_tracker; + + if (!CreateQueryPool()) + { + PanicAlert("Failed to create query pool"); + return false; + } + + if (!CreateReadbackBuffer()) + { + PanicAlert("Failed to create readback buffer"); + return false; + } + + g_command_buffer_mgr->AddFencePointCallback( + this, std::bind(&PerfQuery::OnCommandBufferQueued, this, std::placeholders::_1, + std::placeholders::_2), + std::bind(&PerfQuery::OnCommandBufferExecuted, this, std::placeholders::_1)); + + return true; +} + +void PerfQuery::EnableQuery(PerfQueryGroup type) +{ + // Have we used half of the query buffer already? + if (m_query_count > m_query_buffer.size() / 2) + NonBlockingPartialFlush(); + + // Block if there are no free slots. + if (m_query_count == PERF_QUERY_BUFFER_SIZE) + { + // ERROR_LOG(VIDEO, "Flushed query buffer early!"); + BlockingPartialFlush(); + } + + if (type == PQG_ZCOMP_ZCOMPLOC || type == PQG_ZCOMP) + { + u32 index = (m_query_read_pos + m_query_count) % PERF_QUERY_BUFFER_SIZE; + ActiveQuery& entry = m_query_buffer[index]; + _assert_(!entry.active && !entry.available); + entry.active = true; + m_query_count++; + + DEBUG_LOG(VIDEO, "start query %u", index); + + // Use precise queries if supported, otherwise boolean (which will be incorrect). + VkQueryControlFlags flags = 0; + if (g_vulkan_context->SupportsPreciseOcclusionQueries()) + flags = VK_QUERY_CONTROL_PRECISE_BIT; + + // Ensure the query starts within a render pass. + // TODO: Is this needed? + m_state_tracker->BeginRenderPass(); + vkCmdBeginQuery(g_command_buffer_mgr->GetCurrentCommandBuffer(), m_query_pool, index, flags); + + // Prevent background command buffer submission while the query is active. + m_state_tracker->SetBackgroundCommandBufferExecution(false); + } +} + +void PerfQuery::DisableQuery(PerfQueryGroup type) +{ + if (type == PQG_ZCOMP_ZCOMPLOC || type == PQG_ZCOMP) + { + // DisableQuery should be called for each EnableQuery, so subtract one to get the previous one. + u32 index = (m_query_read_pos + m_query_count - 1) % PERF_QUERY_BUFFER_SIZE; + vkCmdEndQuery(g_command_buffer_mgr->GetCurrentCommandBuffer(), m_query_pool, index); + m_state_tracker->SetBackgroundCommandBufferExecution(true); + DEBUG_LOG(VIDEO, "end query %u", index); + } +} + +void PerfQuery::ResetQuery() +{ + m_query_count = 0; + m_query_read_pos = 0; + std::fill_n(m_results, ArraySize(m_results), 0); + + // Reset entire query pool, ensuring all queries are ready to write to. + m_state_tracker->EndRenderPass(); + vkCmdResetQueryPool(g_command_buffer_mgr->GetCurrentCommandBuffer(), m_query_pool, 0, + PERF_QUERY_BUFFER_SIZE); + + for (auto& entry : m_query_buffer) + { + entry.pending_fence = VK_NULL_HANDLE; + entry.available = false; + entry.active = 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()) + BlockingPartialFlush(); +} + +bool PerfQuery::IsFlushed() const +{ + return m_query_count == 0; +} + +bool PerfQuery::CreateQueryPool() +{ + VkQueryPoolCreateInfo info = { + VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO, // VkStructureType sType + nullptr, // const void* pNext + 0, // VkQueryPoolCreateFlags flags + VK_QUERY_TYPE_OCCLUSION, // VkQueryType queryType + PERF_QUERY_BUFFER_SIZE, // uint32_t queryCount + 0 // VkQueryPipelineStatisticFlags pipelineStatistics; + }; + + VkResult res = vkCreateQueryPool(g_vulkan_context->GetDevice(), &info, nullptr, &m_query_pool); + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkCreateQueryPool failed: "); + return false; + } + + return true; +} + +bool PerfQuery::CreateReadbackBuffer() +{ + m_readback_buffer = StagingBuffer::Create(STAGING_BUFFER_TYPE_READBACK, + PERF_QUERY_BUFFER_SIZE * sizeof(PerfQueryDataType), + VK_BUFFER_USAGE_TRANSFER_DST_BIT); + + // Leave the buffer persistently mapped, we invalidate it when we need to read. + if (!m_readback_buffer || !m_readback_buffer->Map()) + return false; + + return true; +} + +void PerfQuery::QueueCopyQueryResults(VkCommandBuffer command_buffer, VkFence fence, + u32 start_index, u32 query_count) +{ + DEBUG_LOG(VIDEO, "queue copy of queries %u-%u", start_index, start_index + query_count - 1); + + // Transition buffer for GPU write + // TODO: Is this needed? + m_readback_buffer->PrepareForGPUWrite(command_buffer, VK_ACCESS_TRANSFER_WRITE_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT); + + // Copy from queries -> buffer + vkCmdCopyQueryPoolResults(command_buffer, m_query_pool, start_index, query_count, + m_readback_buffer->GetBuffer(), start_index * sizeof(PerfQueryDataType), + sizeof(PerfQueryDataType), VK_QUERY_RESULT_WAIT_BIT); + + // Prepare for host readback + m_readback_buffer->FlushGPUCache(command_buffer, VK_ACCESS_TRANSFER_WRITE_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT); + + // Reset queries so they're ready to use again + vkCmdResetQueryPool(command_buffer, m_query_pool, start_index, query_count); + + // Flag all queries as available, but with a fence that has to be completed first + for (u32 i = 0; i < query_count; i++) + { + u32 index = start_index + i; + ActiveQuery& entry = m_query_buffer[index]; + entry.pending_fence = fence; + entry.available = true; + entry.active = false; + } +} + +void PerfQuery::OnCommandBufferQueued(VkCommandBuffer command_buffer, VkFence fence) +{ + // Flag all pending queries that aren't available as available after execution. + u32 copy_start_index = 0; + u32 copy_count = 0; + for (u32 i = 0; i < m_query_count; i++) + { + u32 index = (m_query_read_pos + i) % PERF_QUERY_BUFFER_SIZE; + ActiveQuery& entry = m_query_buffer[index]; + + // Skip already-copied queries (will happen if a flush hasn't occurred and + // a command buffer hasn't finished executing). + if (entry.available) + { + // These should be grouped together, and at the start. + _assert_(copy_count == 0); + continue; + } + + // If this wrapped around, we need to flush the entries before the end of the buffer. + _assert_(entry.active); + if (index < copy_start_index) + { + QueueCopyQueryResults(command_buffer, fence, copy_start_index, copy_count); + copy_start_index = index; + copy_count = 0; + } + else if (copy_count == 0) + { + copy_start_index = index; + } + copy_count++; + } + + if (copy_count > 0) + QueueCopyQueryResults(command_buffer, fence, copy_start_index, copy_count); +} + +void PerfQuery::OnCommandBufferExecuted(VkFence fence) +{ + // Need to save these since ProcessResults will modify them. + u32 query_read_pos = m_query_read_pos; + u32 query_count = m_query_count; + + // Flush as many queries as are bound to this fence. + u32 flush_start_index = 0; + u32 flush_count = 0; + for (u32 i = 0; i < query_count; i++) + { + u32 index = (query_read_pos + i) % PERF_QUERY_BUFFER_SIZE; + if (m_query_buffer[index].pending_fence != fence) + { + // These should be grouped together, at the end. + break; + } + + // If this wrapped around, we need to flush the entries before the end of the buffer. + if (index < flush_start_index) + { + ProcessResults(flush_start_index, flush_count); + flush_start_index = index; + flush_count = 0; + } + else if (flush_count == 0) + { + flush_start_index = index; + } + flush_count++; + } + + if (flush_count > 0) + ProcessResults(flush_start_index, flush_count); +} + +void PerfQuery::ProcessResults(u32 start_index, u32 query_count) +{ + // Invalidate CPU caches before reading back. + m_readback_buffer->InvalidateCPUCache(start_index * sizeof(PerfQueryDataType), + query_count * sizeof(PerfQueryDataType)); + + // Should be at maximum query_count queries pending. + _assert_(query_count <= m_query_count); + DEBUG_LOG(VIDEO, "process queries %u-%u", start_index, start_index + query_count - 1); + + // Remove pending queries. + for (u32 i = 0; i < query_count; i++) + { + u32 index = (m_query_read_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.pending_fence != VK_NULL_HANDLE); + entry.pending_fence = VK_NULL_HANDLE; + entry.available = false; + entry.active = false; + + // Grab result from readback buffer, it will already have been invalidated. + u32 result; + m_readback_buffer->Read(index * sizeof(PerfQueryDataType), &result, sizeof(result), false); + DEBUG_LOG(VIDEO, " query result %u", 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()); + } + + m_query_read_pos = (m_query_read_pos + query_count) % PERF_QUERY_BUFFER_SIZE; + m_query_count -= query_count; +} + +void PerfQuery::NonBlockingPartialFlush() +{ + if (IsFlushed()) + return; + + // Submit a command buffer in the background if the front query is not bound to one. + // Ideally this will complete before the buffer fills. + if (m_query_buffer[m_query_read_pos].pending_fence == VK_NULL_HANDLE) + Util::ExecuteCurrentCommandsAndRestoreState(m_state_tracker, true, false); +} + +void PerfQuery::BlockingPartialFlush() +{ + if (IsFlushed()) + return; + + // If the first pending query is needing command buffer execution, do that. + ActiveQuery& entry = m_query_buffer[m_query_read_pos]; + if (entry.pending_fence == VK_NULL_HANDLE) + { + // This will callback OnCommandBufferQueued which will set the fence on the entry. + // We wait for completion, which will also call OnCommandBufferExecuted, and clear the fence. + Util::ExecuteCurrentCommandsAndRestoreState(m_state_tracker, false, true); + } + else + { + // The command buffer has been submitted, but is awaiting completion. + // Wait for the fence to complete, which will call OnCommandBufferExecuted. + g_command_buffer_mgr->WaitForFence(entry.pending_fence); + } +} +} |
