summaryrefslogtreecommitdiff
path: root/Source/Core/VideoBackends/Vulkan/PerfQuery.cpp
diff options
context:
space:
mode:
authorMat M <mathew1800@gmail.com>2016-09-30 19:15:50 -0400
committerGitHub <noreply@github.com>2016-09-30 19:15:50 -0400
commit46b9383280a623753686ab6df6c479e784e416ae (patch)
tree9ffb2cbdba6d39a29d6fed9df4db03166755a20d /Source/Core/VideoBackends/Vulkan/PerfQuery.cpp
parentf3cdc6433cba253fa4485cd7e27025c31410ed0c (diff)
parenta8194cff3ca8f9b85a779da6b8214b58ba3fa7d0 (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.cpp370
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);
+ }
+}
+}