GS: Add GPU pipeline stats to OSD.

Adds vertex shader and pixel shader invocations.
This commit is contained in:
TJnotJT
2026-04-30 23:48:39 -04:00
committed by Ty
parent c3ace64205
commit 99a78f9a55
20 changed files with 542 additions and 4 deletions
+5
View File
@@ -62,6 +62,7 @@ OSDSettingsWidget::OSDSettingsWidget(SettingsWindow* settings_dialog, QWidget* p
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.showUsageCPU, "EmuCore/GS", "OsdShowCPU", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.showUsageGPU, "EmuCore/GS", "OsdShowGPU", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.showDebugGPU, "EmuCore/GS", "OsdShowGPUDebug", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.showStatsGPU, "EmuCore/GS", "OsdShowGPUStats", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.showStatusIndicators, "EmuCore/GS", "OsdShowIndicators", true);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.showFrameTimes, "EmuCore/GS", "OsdShowFrameTimes", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.showHardwareInfo, "EmuCore/GS", "OsdShowHardwareInfo", false);
@@ -127,6 +128,8 @@ OSDSettingsWidget::OSDSettingsWidget(SettingsWindow* settings_dialog, QWidget* p
dialog()->registerWidgetHelp(m_ui.showDebugGPU, tr("Show GPU Debug Info"),
tr("Unchecked"), tr("Shows debug information about the renderer."));
#endif
dialog()->registerWidgetHelp(m_ui.showDebugGPU, tr("Show GPU Pipeline Statistics"),
tr("Unchecked"), tr("Shows GPU vertex shader and pixels shader invocations."));
dialog()->registerWidgetHelp(m_ui.showFrameTimes, tr("Show Frame Times"), tr("Unchecked"),
tr("Displays a graph showing the average frametimes."));
@@ -243,6 +246,7 @@ void OSDSettingsWidget::onPerformancePosChanged()
m_ui.showGSStats->setEnabled(enabled);
m_ui.showUsageCPU->setEnabled(enabled);
m_ui.showUsageGPU->setEnabled(enabled);
m_ui.showStatsGPU->setEnabled(enabled);
m_ui.showStatusIndicators->setEnabled(enabled);
m_ui.showFrameTimes->setEnabled(enabled);
m_ui.showHardwareInfo->setEnabled(enabled);
@@ -265,6 +269,7 @@ void OSDSettingsWidget::setAllCheckboxes(bool checked)
m_ui.showGSStats,
m_ui.showUsageCPU,
m_ui.showUsageGPU,
m_ui.showStatsGPU,
m_ui.showFrameTimes,
m_ui.showHardwareInfo,
m_ui.showVersion,
+8
View File
@@ -492,6 +492,13 @@
</property>
</widget>
</item>
<item row="11" column="0">
<widget class="QCheckBox" name="showStatsGPU">
<property name="text">
<string>Show GPU Pipeline Statistics</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
@@ -530,6 +537,7 @@
<tabstop>showGSStats</tabstop>
<tabstop>showUsageCPU</tabstop>
<tabstop>showUsageGPU</tabstop>
<tabstop>showStatsGPU</tabstop>
<tabstop>showStatusIndicators</tabstop>
<tabstop>showFrameTimes</tabstop>
</tabstops>
+1
View File
@@ -774,6 +774,7 @@ struct Pcsx2Config
OsdShowCPU : 1,
OsdShowGPU : 1,
OsdShowGPUDebug : 1,
OsdShowGPUStats : 1,
OsdShowIndicators : 1,
OsdShowFrameTimes : 1,
OsdShowHardwareInfo : 1,
+8
View File
@@ -163,6 +163,8 @@ static bool OpenGSDevice(GSRendererType renderer, bool clear_state_on_fail, bool
if (!g_gs_device->SetGPUTimingEnabled(true))
GSConfig.OsdShowGPU = false;
if (!g_gs_device->SetGPUPipelineStatisticsEnabled(true))
GSConfig.OsdShowGPUStats = false;
Console.WriteLn(Color_StrongGreen, "%s Graphics Driver Info:", GSDevice::RenderAPIToString(new_api));
Console.WriteLn(g_gs_device->GetDriverInfo());
@@ -884,6 +886,12 @@ void GSUpdateConfig(const Pcsx2Config::GSOptions& new_config)
if (!g_gs_device->SetGPUTimingEnabled(true))
GSConfig.OsdShowGPU = false;
}
if (GSConfig.OsdShowGPUStats != old_config.OsdShowGPUStats)
{
if (!g_gs_device->SetGPUPipelineStatisticsEnabled(GSConfig.OsdShowGPUStats))
GSConfig.OsdShowGPUStats = false;
}
}
void GSSetSoftwareRendering(bool software_renderer, GSInterlaceMode new_interlace)
+12
View File
@@ -29,6 +29,12 @@ static inline constexpr Filter BilnIf(bool biln)
return biln ? Biln : Nearest;
}
struct GPUPipelineStatistics
{
u64 vs_invocations;
u64 ps_invocations;
};
enum class ShaderConvert
{
COPY = 0,
@@ -1610,6 +1616,12 @@ public:
/// Returns the amount of GPU time utilized since the last time this method was called.
virtual float GetAndResetAccumulatedGPUTime() = 0;
/// Enables/disables GPU pipeline statistics.
virtual bool SetGPUPipelineStatisticsEnabled(bool enabled) = 0;
/// Get the pipeline statistics for the last frame.
virtual GPUPipelineStatistics GetAndResetAccumulatedGPUPipelineStatistics() = 0;
/// Returns true if not enough time has passed for present to not block.
bool ShouldSkipPresentingFrame();
+3 -1
View File
@@ -700,7 +700,9 @@ void GSRenderer::VSync(u32 field, bool registers_written, bool idle_frame)
EndPresentFrame();
PerformanceMetrics::OnGPUPresent(g_gs_device->GetAndResetAccumulatedGPUTime());
const float gpu_time = g_gs_device->GetAndResetAccumulatedGPUTime();
GPUPipelineStatistics gpu_stats = g_gs_device->GetAndResetAccumulatedGPUPipelineStatistics();
PerformanceMetrics::OnGPUPresent(gpu_time, gpu_stats.vs_invocations, gpu_stats.ps_invocations);
}
PerformanceMetrics::Update(registers_written, fb_sprite_frame, false);
+100
View File
@@ -618,6 +618,7 @@ void GSDevice11::Destroy()
GSDevice::Destroy();
DestroySwapChain();
DestroyTimestampQueries();
DestroyPipelineStatisticsQueries();
m_convert = {};
m_present = {};
@@ -1082,6 +1083,10 @@ GSDevice::PresentResult GSDevice11::BeginPresent(bool frame_skip)
if (m_vsync_mode == GSVSyncMode::FIFO && m_gpu_timing_enabled)
PopTimestampQuery();
// Get the pipeline statistics for this frame before postprocessing.
if (m_gpu_pipeline_statistics_enabled)
PopPipelineStatisticsQuery();
m_ctx->ClearRenderTargetView(m_swap_chain_rtv.get(), s_present_clear_color.data());
m_ctx->OMSetRenderTargets(1, m_swap_chain_rtv.addressof(), nullptr);
if (m_state.rtv)
@@ -1113,6 +1118,9 @@ void GSDevice11::EndPresent()
if (m_vsync_mode != GSVSyncMode::FIFO && m_gpu_timing_enabled)
PopTimestampQuery();
if (m_gpu_pipeline_statistics_enabled)
PopPipelineStatisticsQuery();
// clear out the swap chain view, it might get resized..
OMSetRenderTargets(nullptr, nullptr, nullptr);
@@ -1122,6 +1130,9 @@ void GSDevice11::EndPresent()
if (m_gpu_timing_enabled)
KickTimestampQuery();
if (m_gpu_pipeline_statistics_enabled)
KickPipelineStatisticsQuery();
}
bool GSDevice11::CreateTimestampQueries()
@@ -1242,6 +1253,95 @@ float GSDevice11::GetAndResetAccumulatedGPUTime()
return value;
}
bool GSDevice11::CreatePipelineStatisticsQueries()
{
for (u32 i = 0; i < NUM_PIPELINE_STATISTICS_QUERIES; i++)
{
const CD3D11_QUERY_DESC qdesc(D3D11_QUERY_PIPELINE_STATISTICS);
const HRESULT hr = m_dev->CreateQuery(&qdesc, m_pipeline_statistics_queries[i].put());
if (FAILED(hr))
{
m_pipeline_statistics_queries = {};
return false;
}
}
KickPipelineStatisticsQuery();
return true;
}
void GSDevice11::KickPipelineStatisticsQuery()
{
if (m_pipeline_statistics_query_started || !m_pipeline_statistics_queries[0] ||
m_waiting_pipeline_statistics_queries == NUM_PIPELINE_STATISTICS_QUERIES)
return;
m_ctx->Begin(m_pipeline_statistics_queries[m_write_pipeline_statistics_query].get());
m_pipeline_statistics_query_started = true;
}
void GSDevice11::DestroyPipelineStatisticsQueries()
{
if (!m_pipeline_statistics_queries[0])
return;
if (m_pipeline_statistics_query_started)
m_ctx->End(m_pipeline_statistics_queries[m_write_timestamp_query].get());
m_timestamp_queries = {};
m_read_timestamp_query = 0;
m_write_timestamp_query = 0;
m_waiting_timestamp_queries = 0;
m_timestamp_query_started = 0;
}
void GSDevice11::PopPipelineStatisticsQuery()
{
while (m_waiting_pipeline_statistics_queries > 0)
{
D3D11_QUERY_DATA_PIPELINE_STATISTICS stats{};
const HRESULT stats_hr = m_ctx->GetData(
m_pipeline_statistics_queries[m_read_pipeline_statistics_query].get(), &stats, sizeof(stats), 0);
if (stats_hr != S_OK)
break;
m_accumulated_gpu_pipeline_statistics.vs_invocations += stats.VSInvocations;
m_accumulated_gpu_pipeline_statistics.ps_invocations += stats.PSInvocations;
m_read_pipeline_statistics_query = (m_read_pipeline_statistics_query + 1) % NUM_PIPELINE_STATISTICS_QUERIES;
m_waiting_pipeline_statistics_queries--;
}
if (m_pipeline_statistics_query_started)
{
m_ctx->End(m_pipeline_statistics_queries[m_write_pipeline_statistics_query].get());
m_write_pipeline_statistics_query = (m_write_pipeline_statistics_query + 1) % NUM_PIPELINE_STATISTICS_QUERIES;
m_pipeline_statistics_query_started = false;
m_waiting_pipeline_statistics_queries++;
}
}
GPUPipelineStatistics GSDevice11::GetAndResetAccumulatedGPUPipelineStatistics()
{
GPUPipelineStatistics stats = m_accumulated_gpu_pipeline_statistics;
m_accumulated_gpu_pipeline_statistics = {};
return stats;
}
bool GSDevice11::SetGPUPipelineStatisticsEnabled(bool enabled)
{
m_gpu_pipeline_statistics_enabled = enabled;
if (m_gpu_pipeline_statistics_enabled)
{
return CreatePipelineStatisticsQueries();
}
else
{
DestroyPipelineStatisticsQueries();
return true;
}
}
void GSDevice11::DrawPrimitive()
{
g_perfmon.Put(GSPerfMon::DrawCalls, 1);
+17
View File
@@ -93,6 +93,7 @@ private:
VERTEX_BUFFER_SIZE = 32 * 1024 * 1024,
INDEX_BUFFER_SIZE = 16 * 1024 * 1024,
NUM_TIMESTAMP_QUERIES = 5,
NUM_PIPELINE_STATISTICS_QUERIES = 5,
};
void SetFeatures(IDXGIAdapter1* adapter);
@@ -107,6 +108,11 @@ private:
void PopTimestampQuery();
void KickTimestampQuery();
bool CreatePipelineStatisticsQueries();
void DestroyPipelineStatisticsQueries();
void PopPipelineStatisticsQuery();
void KickPipelineStatisticsQuery();
void DoMerge(GSTexture* sTex[3], GSVector4* sRect, GSTexture* dTex, GSVector4* dRect, const GSRegPMODE& PMODE, const GSRegEXTBUF& EXTBUF, u32 c, const Filter filter) override;
void DoInterlace(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, ShaderInterlace shader, Filter filter, const InterlaceConstantBuffer& cb) override;
void DoFXAA(GSTexture* sTex, GSTexture* dTex) override;
@@ -192,6 +198,14 @@ private:
bool m_timestamp_query_started = false;
bool m_gpu_timing_enabled = false;
std::array<wil::com_ptr_nothrow<ID3D11Query>, NUM_PIPELINE_STATISTICS_QUERIES> m_pipeline_statistics_queries = {};
GPUPipelineStatistics m_accumulated_gpu_pipeline_statistics{};
u8 m_read_pipeline_statistics_query = 0;
u8 m_write_pipeline_statistics_query = 0;
u8 m_waiting_pipeline_statistics_queries = 0;
bool m_gpu_pipeline_statistics_enabled = false;
bool m_pipeline_statistics_query_started = false;
struct
{
wil::com_ptr_nothrow<ID3D11InputLayout> il;
@@ -325,6 +339,9 @@ public:
bool SetGPUTimingEnabled(bool enabled) override;
float GetAndResetAccumulatedGPUTime() override;
bool SetGPUPipelineStatisticsEnabled(bool enabled) override;
GPUPipelineStatistics GetAndResetAccumulatedGPUPipelineStatistics() override;
// Helpers and utility draws.
void DrawPrimitive();
void DrawIndexedPrimitive();
+99 -1
View File
@@ -519,6 +519,39 @@ void GSDevice12::MoveToNextCommandList()
m_current_command_list * NUM_TIMESTAMP_QUERIES_PER_CMDLIST);
}
if (res.pipeline_statistics_query == QueryState::Ready)
{
// Collect the pipeline statistics from the last time this cmdlist was used.
res.pipeline_statistics_query = QueryState::None;
const u32 offset = (m_current_command_list * sizeof(D3D12_QUERY_DATA_PIPELINE_STATISTICS));
const D3D12_RANGE read_range = { offset, offset + sizeof(D3D12_QUERY_DATA_PIPELINE_STATISTICS) };
void* map;
HRESULT hr = m_pipeline_statistics_query_buffer->Map(0, &read_range, &map);
if (SUCCEEDED(hr))
{
D3D12_QUERY_DATA_PIPELINE_STATISTICS stats;
std::memcpy(&stats, static_cast<const u8*>(map) + offset, sizeof(stats));
m_accumulated_gpu_pipeline_statistics.vs_invocations += stats.VSInvocations;
m_accumulated_gpu_pipeline_statistics.ps_invocations += stats.PSInvocations;
const D3D12_RANGE write_range = {};
m_pipeline_statistics_query_buffer->Unmap(0, &write_range);
}
else
{
Console.Warning("D3D12: Map() for pipeline statistics query failed: %08X", hr);
}
}
if (m_gpu_pipeline_statistics_enabled)
{
pxAssert(res.pipeline_statistics_query == QueryState::None);
res.pipeline_statistics_query = QueryState::Querying;
res.command_lists[1].list4->BeginQuery(
m_pipeline_statistics_query_heap.get(), D3D12_QUERY_TYPE_PIPELINE_STATISTICS, m_current_command_list);
}
ID3D12DescriptorHeap* heaps[2] = {
res.descriptor_allocator.GetDescriptorHeap(), res.sampler_allocator.GetDescriptorHeap()};
res.command_lists[1].list4->SetDescriptorHeaps(std::size(heaps), heaps);
@@ -564,6 +597,18 @@ bool GSDevice12::ExecuteCommandList(WaitType wait_for_completion)
m_timestamp_query_buffer.get(), m_current_command_list * (sizeof(u64) * NUM_TIMESTAMP_QUERIES_PER_CMDLIST));
}
if ((res.pipeline_statistics_query == QueryState::Querying) || (res.pipeline_statistics_query == QueryState::Ready))
{
if (res.pipeline_statistics_query == QueryState::Querying)
{
// Didn't end query in BeginPresent() so end it here.
res.pipeline_statistics_query = QueryState::Ready;
res.command_lists[1].list4->EndQuery(m_pipeline_statistics_query_heap.get(), D3D12_QUERY_TYPE_PIPELINE_STATISTICS, m_current_command_list);
}
res.command_lists[1].list4->ResolveQueryData(m_pipeline_statistics_query_heap.get(), D3D12_QUERY_TYPE_PIPELINE_STATISTICS,
m_current_command_list, 1, m_pipeline_statistics_query_buffer.get(), m_current_command_list * sizeof(D3D11_QUERY_DATA_PIPELINE_STATISTICS));
}
if (res.init_command_list_used)
{
hr = res.command_lists[0].list4->Close();
@@ -716,6 +761,36 @@ void GSDevice12::WaitForGPUIdle()
}
}
bool GSDevice12::CreatePipelineStatisticsQuery()
{
constexpr u32 QUERY_COUNT = NUM_COMMAND_LISTS;
constexpr u32 BUFFER_SIZE = sizeof(D3D12_QUERY_DATA_PIPELINE_STATISTICS) * QUERY_COUNT;
const D3D12_QUERY_HEAP_DESC desc = { D3D12_QUERY_HEAP_TYPE_PIPELINE_STATISTICS, QUERY_COUNT };
HRESULT hr = m_device->CreateQueryHeap(&desc, IID_PPV_ARGS(m_pipeline_statistics_query_heap.put()));
if (FAILED(hr))
{
Console.Error("D3D12: CreateQueryHeap() for pipeline statistics failed with %08X", hr);
return false;
}
const D3D12MA::ALLOCATION_DESC allocation_desc = { D3D12MA::ALLOCATION_FLAG_NONE, D3D12_HEAP_TYPE_READBACK };
const D3D12_RESOURCE_DESCU resource_desc = { {D3D12_RESOURCE_DIMENSION_BUFFER, 0, BUFFER_SIZE, 1, 1, 1,
DXGI_FORMAT_UNKNOWN, {1, 0}, D3D12_TEXTURE_LAYOUT_ROW_MAJOR, D3D12_RESOURCE_FLAG_NONE} };
if (m_enhanced_barriers)
hr = m_allocator->CreateResource3(&allocation_desc, &resource_desc.desc1, D3D12_BARRIER_LAYOUT_UNDEFINED, nullptr,
0, nullptr, m_pipeline_statistics_query_allocation.put(), IID_PPV_ARGS(m_pipeline_statistics_query_buffer.put()));
else
hr = m_allocator->CreateResource(&allocation_desc, &resource_desc.desc, D3D12_RESOURCE_STATE_COPY_DEST, nullptr,
m_pipeline_statistics_query_allocation.put(), IID_PPV_ARGS(m_pipeline_statistics_query_buffer.put()));
if (FAILED(hr))
{
Console.Error("D3D12: CreateResource() for pipeline statistics failed with %08X", hr);
return false;
}
return true;
}
bool GSDevice12::CreateTimestampQuery()
{
constexpr u32 QUERY_COUNT = NUM_TIMESTAMP_QUERIES_PER_CMDLIST * NUM_COMMAND_LISTS;
@@ -769,6 +844,19 @@ bool GSDevice12::SetGPUTimingEnabled(bool enabled)
return true;
}
GPUPipelineStatistics GSDevice12::GetAndResetAccumulatedGPUPipelineStatistics()
{
GPUPipelineStatistics stats = m_accumulated_gpu_pipeline_statistics;
m_accumulated_gpu_pipeline_statistics = {};
return stats;
}
bool GSDevice12::SetGPUPipelineStatisticsEnabled(bool enabled)
{
m_gpu_pipeline_statistics_enabled = enabled;
return true;
}
bool GSDevice12::AllocatePreinitializedGPUBuffer(u32 size, ID3D12Resource** gpu_buffer,
D3D12MA::Allocation** gpu_allocation, const std::function<void(void*)>& fill_callback)
{
@@ -863,7 +951,7 @@ bool GSDevice12::Create(GSVSyncMode vsync_mode, bool allow_present_throttle)
m_name = D3D::GetAdapterName(m_adapter.get());
if (!CreateDescriptorHeaps() || !CreateCommandLists() || !CreateTimestampQuery())
if (!CreateDescriptorHeaps() || !CreateCommandLists() || !CreateTimestampQuery() || !CreatePipelineStatisticsQuery())
return false;
if (!AcquireWindow(true) || (m_window_info.type != WindowInfo::Type::Surfaceless && !CreateSwapChain()))
@@ -1275,6 +1363,14 @@ GSDevice::PresentResult GSDevice12::BeginPresent(bool frame_skip)
return PresentResult::FrameSkipped;
}
// End the pipeline statistics for this cmdlist before postprocessing.
CommandListResources& res = m_command_lists[m_current_command_list];
if (res.pipeline_statistics_query == QueryState::Querying)
{
res.pipeline_statistics_query = QueryState::Ready;
res.command_lists[1].list4->EndQuery(m_pipeline_statistics_query_heap.get(), D3D12_QUERY_TYPE_PIPELINE_STATISTICS, m_current_command_list);
}
GSTexture12* swap_chain_buf = m_swap_chain_buffers[m_current_swap_chain_buffer].get();
const D3D12CommandList& cmdlist = GetCommandList();
@@ -3091,6 +3187,8 @@ void GSDevice12::DestroyResources()
m_timestamp_query_buffer.reset();
m_timestamp_query_allocation.reset();
m_pipeline_statistics_query_buffer.reset();
m_pipeline_statistics_query_allocation.reset();
m_sampler_heap_manager.Destroy();
m_dsv_heap_manager.Destroy();
m_rtv_heap_manager.Destroy();
+19
View File
@@ -191,6 +191,14 @@ public:
void UploadIndices(D3D12StreamBuffer& buffer, const void* index, size_t count);
private:
// For pipeline statistics
enum class QueryState
{
None,
Querying,
Ready,
};
struct CommandListResources
{
std::array<ComPtr<ID3D12CommandAllocator>, 2> command_allocators;
@@ -202,6 +210,7 @@ private:
u64 ready_fence_value = 0;
bool init_command_list_used = false;
bool has_timestamp_query = false;
QueryState pipeline_statistics_query = QueryState::None;
};
void LoadAgilitySDK();
@@ -210,6 +219,7 @@ private:
bool CreateDescriptorHeaps();
bool CreateCommandLists();
bool CreateTimestampQuery();
bool CreatePipelineStatisticsQuery();
void MoveToNextCommandList();
void DestroyPendingResources(CommandListResources& cmdlist);
@@ -234,6 +244,12 @@ private:
bool m_gpu_timing_enabled = false;
bool m_programmable_sample_positions = false;
ComPtr<ID3D12QueryHeap> m_pipeline_statistics_query_heap;
ComPtr<ID3D12Resource> m_pipeline_statistics_query_buffer;
ComPtr<D3D12MA::Allocation> m_pipeline_statistics_query_allocation;
GPUPipelineStatistics m_accumulated_gpu_pipeline_statistics{};
bool m_gpu_pipeline_statistics_enabled = false;
D3D12DescriptorHeapManager m_descriptor_heap_manager;
D3D12DescriptorHeapManager m_rtv_heap_manager;
D3D12DescriptorHeapManager m_dsv_heap_manager;
@@ -510,6 +526,9 @@ public:
bool SetGPUTimingEnabled(bool enabled) override;
float GetAndResetAccumulatedGPUTime() override;
bool SetGPUPipelineStatisticsEnabled(bool enabled) override;
GPUPipelineStatistics GetAndResetAccumulatedGPUPipelineStatistics() override;
void PushDebugGroup(const char* fmt, ...) override;
void PopDebugGroup() override;
void InsertDebugMessage(DebugMessageCategory category, const char* fmt, ...) override;
+3
View File
@@ -414,6 +414,9 @@ public:
float GetAndResetAccumulatedGPUTime() override;
void AccumulateCommandBufferTime(id<MTLCommandBuffer> buffer);
bool SetGPUPipelineStatisticsEnabled(bool enabled) override { return false; }
GPUPipelineStatistics GetAndResetAccumulatedGPUPipelineStatistics() override { return {}; }
std::unique_ptr<GSDownloadTexture> CreateDownloadTexture(u32 width, u32 height, GSTexture::Format format) override;
void ClearSamplerCache() override;
+104
View File
@@ -657,6 +657,8 @@ bool GSDeviceOGL::Create(GSVSyncMode vsync_mode, bool allow_present_throttle)
if (!CreateImGuiProgram())
return false;
m_gpu_pipeline_statistics_supported = (GLAD_GL_ARB_pipeline_statistics_query != 0);
// Basic to ensure structures are correctly packed
static_assert(sizeof(VSSelector) == 1, "Wrong VSSelector size");
static_assert(sizeof(PSSelector) == 16, "Wrong PSSelector size");
@@ -674,6 +676,7 @@ void GSDeviceOGL::Destroy()
if (m_gl_context)
{
DestroyTimestampQueries();
DestroyPipelineStatisticsQueries();
DestroyResources();
m_gl_context->DoneCurrent();
@@ -1084,6 +1087,10 @@ GSDevice::PresentResult GSDeviceOGL::BeginPresent(bool frame_skip)
if (frame_skip || m_window_info.type == WindowInfo::Type::Surfaceless)
return PresentResult::FrameSkipped;
// Get the pipeline statistics for this frame before postprocessing.
if (m_gpu_pipeline_statistics_enabled)
PopPipelineStatisticsQuery();
OMSetFBO(0);
OMSetColorMaskState();
@@ -1110,6 +1117,9 @@ void GSDeviceOGL::EndPresent()
if (m_gpu_timing_enabled)
KickTimestampQuery();
if (m_gpu_pipeline_statistics_enabled)
KickPipelineStatisticsQuery();
}
void GSDeviceOGL::CreateTimestampQueries()
@@ -1191,6 +1201,100 @@ float GSDeviceOGL::GetAndResetAccumulatedGPUTime()
return value;
}
void GSDeviceOGL::PopPipelineStatisticsQuery()
{
while (m_waiting_pipeline_statistics_queries > 0)
{
GLint available[2] = {};
glGetQueryObjectiv(m_pipeline_statistics_queries[m_read_pipeline_statistics_query][0], GL_QUERY_RESULT_AVAILABLE, &available[0]);
glGetQueryObjectiv(m_pipeline_statistics_queries[m_read_pipeline_statistics_query][1], GL_QUERY_RESULT_AVAILABLE, &available[1]);
if (!(available[0] && available[1]))
break;
GPUPipelineStatistics stats = {};
glGetQueryObjectui64v(m_pipeline_statistics_queries[m_read_pipeline_statistics_query][0], GL_QUERY_RESULT, &stats.vs_invocations);
glGetQueryObjectui64v(m_pipeline_statistics_queries[m_read_pipeline_statistics_query][1], GL_QUERY_RESULT, &stats.ps_invocations);
m_accumulated_gpu_pipeline_statistics.vs_invocations += stats.vs_invocations;
m_accumulated_gpu_pipeline_statistics.ps_invocations += stats.ps_invocations;
m_read_pipeline_statistics_query = (m_read_pipeline_statistics_query + 1) % NUM_PIPELINE_STATISTICS_QUERIES;
m_waiting_pipeline_statistics_queries--;
}
if (m_pipeline_statistics_query_started)
{
glEndQuery(GL_VERTEX_SHADER_INVOCATIONS_ARB);
glEndQuery(GL_FRAGMENT_SHADER_INVOCATIONS_ARB);
m_write_pipeline_statistics_query = (m_write_pipeline_statistics_query + 1) % NUM_TIMESTAMP_QUERIES;
m_pipeline_statistics_query_started = false;
m_waiting_pipeline_statistics_queries++;
}
}
void GSDeviceOGL::KickPipelineStatisticsQuery()
{
if (m_pipeline_statistics_query_started || m_waiting_pipeline_statistics_queries == NUM_PIPELINE_STATISTICS_QUERIES)
return;
glBeginQuery(GL_VERTEX_SHADER_INVOCATIONS_ARB, m_pipeline_statistics_queries[m_write_pipeline_statistics_query][0]);
glBeginQuery(GL_FRAGMENT_SHADER_INVOCATIONS_ARB, m_pipeline_statistics_queries[m_write_pipeline_statistics_query][1]);
m_pipeline_statistics_query_started = true;
}
void GSDeviceOGL::CreatePipelineStatisticsQueries()
{
for (int i = 0; i < NUM_PIPELINE_STATISTICS_QUERIES; i++)
{
glGenQueries(2, m_pipeline_statistics_queries[i].data());
}
KickPipelineStatisticsQuery();
}
void GSDeviceOGL::DestroyPipelineStatisticsQueries()
{
if (m_pipeline_statistics_queries[0][0] == 0)
return;
if (m_pipeline_statistics_query_started)
{
glEndQuery(GL_VERTEX_SHADER_INVOCATIONS_ARB);
glEndQuery(GL_FRAGMENT_SHADER_INVOCATIONS_ARB);
}
for (int i = 0; i < m_pipeline_statistics_queries.size(); i++)
{
glDeleteQueries(2, m_pipeline_statistics_queries[i].data());
m_pipeline_statistics_queries[i].fill(0);
}
m_read_pipeline_statistics_query = 0;
m_write_pipeline_statistics_query = 0;
m_waiting_pipeline_statistics_queries = 0;
m_pipeline_statistics_query_started = false;
}
GPUPipelineStatistics GSDeviceOGL::GetAndResetAccumulatedGPUPipelineStatistics()
{
GPUPipelineStatistics stats = m_accumulated_gpu_pipeline_statistics;
m_accumulated_gpu_pipeline_statistics = {};
return stats;
}
bool GSDeviceOGL::SetGPUPipelineStatisticsEnabled(bool enabled)
{
if (m_gpu_pipeline_statistics_enabled == enabled)
return true;
m_gpu_pipeline_statistics_enabled = enabled && m_gpu_pipeline_statistics_supported;
if (m_gpu_pipeline_statistics_enabled)
CreatePipelineStatisticsQueries();
else
DestroyPipelineStatisticsQueries();
return true;
}
void GSDeviceOGL::DrawPrimitive()
{
g_perfmon.Put(GSPerfMon::DrawCalls, 1);
+18
View File
@@ -147,6 +147,7 @@ public:
private:
static constexpr u8 NUM_TIMESTAMP_QUERIES = 5;
static constexpr u8 NUM_PIPELINE_STATISTICS_QUERIES = 5;
std::unique_ptr<GLContext> m_gl_context;
@@ -253,6 +254,15 @@ private:
bool m_timestamp_query_started = false;
bool m_gpu_timing_enabled = false;
std::array<std::array<GLuint, 2>, NUM_PIPELINE_STATISTICS_QUERIES> m_pipeline_statistics_queries = {};
GPUPipelineStatistics m_accumulated_gpu_pipeline_statistics = {};
u8 m_read_pipeline_statistics_query = 0;
u8 m_write_pipeline_statistics_query = 0;
u8 m_waiting_pipeline_statistics_queries = 0;
bool m_pipeline_statistics_query_started = false;
bool m_gpu_pipeline_statistics_enabled = false;
bool m_gpu_pipeline_statistics_supported = false;
GSHWDrawConfig::VSConstantBuffer m_vs_cb_cache;
GSHWDrawConfig::PSConstantBuffer m_ps_cb_cache;
GSHWDrawConfig::VSPushConstants m_vs_pc_cache;
@@ -272,6 +282,11 @@ private:
GSTexture* CreateSurface(GSTexture::Usage usage, int width, int height, int levels, GSTexture::Format format) override;
void CreatePipelineStatisticsQueries();
void DestroyPipelineStatisticsQueries();
void PopPipelineStatisticsQuery();
void KickPipelineStatisticsQuery();
void DoMerge(GSTexture* sTex[3], GSVector4* sRect, GSTexture* dTex, GSVector4* dRect, const GSRegPMODE& PMODE, const GSRegEXTBUF& EXTBUF, u32 c, const Filter filter) override;
void DoInterlace(GSTexture* sTex, const GSVector4& sRect, GSTexture* dTex, const GSVector4& dRect, ShaderInterlace shader, Filter filter, const InterlaceConstantBuffer& cb) override;
@@ -337,6 +352,9 @@ public:
bool SetGPUTimingEnabled(bool enabled) override;
float GetAndResetAccumulatedGPUTime() override;
bool SetGPUPipelineStatisticsEnabled(bool enabled) override;
GPUPipelineStatistics GetAndResetAccumulatedGPUPipelineStatistics() override;
// Helpers and utility draws.
void DrawPrimitive();
void DrawIndexedPrimitive();
+76
View File
@@ -462,6 +462,7 @@ bool GSDeviceVK::SelectDeviceFeatures()
m_device_features.textureCompressionBC = available_features.textureCompressionBC;
m_device_features.geometryShader = available_features.geometryShader;
m_device_features.fragmentStoresAndAtomics = available_features.fragmentStoresAndAtomics;
m_device_features.pipelineStatisticsQuery = available_features.pipelineStatisticsQuery;
return true;
}
@@ -697,6 +698,9 @@ bool GSDeviceVK::CreateDevice(VkSurfaceKHR surface, bool enable_validation_layer
queue_family_properties[m_graphics_queue_family_index].timestampValidBits,
m_device_properties.limits.timestampPeriod);
m_gpu_pipeline_statistics_supported = (m_device_features.pipelineStatisticsQuery != 0);
DevCon.WriteLn("GPU pipeline statistics is %s", m_gpu_pipeline_statistics_supported ? "supported" : "not supported");
if (!ProcessDeviceExtensions())
return false;
@@ -997,6 +1001,20 @@ bool GSDeviceVK::CreateGlobalDescriptorPool()
}
}
if (m_gpu_pipeline_statistics_supported)
{
const VkQueryPoolCreateInfo query_create_info = {
VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO, nullptr, 0, VK_QUERY_TYPE_PIPELINE_STATISTICS, NUM_COMMAND_BUFFERS,
VK_QUERY_PIPELINE_STATISTIC_VERTEX_SHADER_INVOCATIONS_BIT | VK_QUERY_PIPELINE_STATISTIC_FRAGMENT_SHADER_INVOCATIONS_BIT };
res = vkCreateQueryPool(m_device, &query_create_info, nullptr, &m_pipeline_statistics_query_pool);
if (res != VK_SUCCESS)
{
LOG_VULKAN_ERROR(res, "vkCreateQueryPool failed: ");
m_gpu_pipeline_statistics_supported = false;
return false;
}
}
return true;
}
@@ -1122,6 +1140,19 @@ bool GSDeviceVK::SetGPUTimingEnabled(bool enabled)
return (enabled == m_gpu_timing_enabled);
}
GPUPipelineStatistics GSDeviceVK::GetAndResetAccumulatedGPUPipelineStatistics()
{
GPUPipelineStatistics stats = m_accumulated_gpu_pipeline_statistics;
m_accumulated_gpu_pipeline_statistics = {};
return stats;
}
bool GSDeviceVK::SetGPUPipelineStatisticsEnabled(bool enabled)
{
m_gpu_pipeline_statistics_enabled = enabled && m_gpu_pipeline_statistics_supported;
return true;
}
void GSDeviceVK::ScanForCommandBufferCompletion()
{
for (u32 check_index = (m_current_frame + 1) % NUM_COMMAND_BUFFERS; check_index != m_current_frame;
@@ -1198,6 +1229,13 @@ void GSDeviceVK::SubmitCommandBuffer(VKSwapChain* present_swap_chain)
m_current_frame * 2 + 1);
}
if (resources.pipeline_statistics_query == QueryState::Querying)
{
// Didn't end query in BeginPresent() so end it here.
resources.pipeline_statistics_query = QueryState::Ready;
vkCmdEndQuery(m_current_command_buffer, m_pipeline_statistics_query_pool, m_current_frame);
}
res = vkEndCommandBuffer(resources.command_buffers[1]);
if (res != VK_SUCCESS)
{
@@ -1404,6 +1442,33 @@ void GSDeviceVK::ActivateCommandBuffer(u32 index)
resources.command_buffers[1], VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, m_timestamp_query_pool, index * 2);
}
if (resources.pipeline_statistics_query == QueryState::Ready)
{
// Collect the pipeline statistics from the last time this cmdbuffer was used.
resources.pipeline_statistics_query = QueryState::None;
GPUPipelineStatistics stats{};
VkResult res =
vkGetQueryPoolResults(m_device, m_pipeline_statistics_query_pool, index, 1,
sizeof(stats), &stats, sizeof(u64), VK_QUERY_RESULT_64_BIT | VK_QUERY_RESULT_WAIT_BIT);
if (res == VK_SUCCESS)
{
m_accumulated_gpu_pipeline_statistics.vs_invocations += stats.vs_invocations;
m_accumulated_gpu_pipeline_statistics.ps_invocations += stats.ps_invocations;
}
else
{
LOG_VULKAN_ERROR(res, "vkGetQueryPoolResults failed: ");
}
}
if (m_gpu_pipeline_statistics_enabled)
{
pxAssert(resources.pipeline_statistics_query == QueryState::None);
resources.pipeline_statistics_query = QueryState::Querying;
vkCmdResetQueryPool(resources.command_buffers[1], m_pipeline_statistics_query_pool, index, 1);
vkCmdBeginQuery(resources.command_buffers[1], m_pipeline_statistics_query_pool, index, 0);
}
resources.fence_counter = m_next_fence_counter++;
resources.init_buffer_used = false;
resources.timestamp_written = wants_timestamp;
@@ -2367,6 +2432,14 @@ GSDevice::PresentResult GSDeviceVK::BeginPresent(bool frame_skip)
return PresentResult::FrameSkipped;
}
// End the pipeline statistics for this cmdbuffer before postprocessing.
FrameResources& resources = m_frame_resources[m_current_frame];
if (resources.pipeline_statistics_query == QueryState::Querying)
{
resources.pipeline_statistics_query = QueryState::Ready;
vkCmdEndQuery(m_current_command_buffer, m_pipeline_statistics_query_pool, m_current_frame);
}
VkResult res = m_resize_requested ? VK_ERROR_OUT_OF_DATE_KHR : m_swap_chain->AcquireNextImage();
if (res != VK_SUCCESS)
{
@@ -4819,6 +4892,9 @@ void GSDeviceVK::DestroyResources()
if (m_timestamp_query_pool != VK_NULL_HANDLE)
vkDestroyQueryPool(m_device, m_timestamp_query_pool, nullptr);
if (m_pipeline_statistics_query_pool != VK_NULL_HANDLE)
vkDestroyQueryPool(m_device, m_pipeline_statistics_query_pool, nullptr);
if (m_global_descriptor_pool != VK_NULL_HANDLE)
vkDestroyDescriptorPool(m_device, m_global_descriptor_pool, nullptr);
+17
View File
@@ -209,6 +209,14 @@ private:
void CalibrateSpinTimestamp();
u64 GetCPUTimestamp();
// For pipeline statistics
enum class QueryState
{
None,
Querying,
Ready,
};
struct FrameResources
{
// [0] - Init (upload) command buffer, [1] - draw command buffer
@@ -221,6 +229,7 @@ private:
bool init_buffer_used = false;
bool needs_fence_wait = false;
bool timestamp_written = false;
QueryState pipeline_statistics_query = QueryState::None;
std::vector<std::function<void()>> cleanup_resources;
};
@@ -277,6 +286,11 @@ private:
bool m_wants_new_timestamp_calibration = false;
VkTimeDomainEXT m_calibrated_timestamp_type = VK_TIME_DOMAIN_DEVICE_EXT;
VkQueryPool m_pipeline_statistics_query_pool = VK_NULL_HANDLE;
GPUPipelineStatistics m_accumulated_gpu_pipeline_statistics{};
bool m_gpu_pipeline_statistics_enabled = false;
bool m_gpu_pipeline_statistics_supported = false;
std::array<FrameResources, NUM_COMMAND_BUFFERS> m_frame_resources;
u64 m_next_fence_counter = 1;
u64 m_completed_fence_counter = 0;
@@ -563,6 +577,9 @@ public:
bool SetGPUTimingEnabled(bool enabled) override;
float GetAndResetAccumulatedGPUTime() override;
bool SetGPUPipelineStatisticsEnabled(bool enabled) override;
GPUPipelineStatistics GetAndResetAccumulatedGPUPipelineStatistics() override;
void PushDebugGroup(const char* fmt, ...) override;
void PopDebugGroup() override;
void InsertDebugMessage(DebugMessageCategory category, const char* fmt, ...) override;
+2
View File
@@ -3461,6 +3461,8 @@ void FullscreenUI::DrawOSDSettingsPage()
DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_BUG, "Show GPU Debug Info"),
FSUI_CSTR("Shows debug information about the renderer."), "EmuCore/GS", "OsdShowGPUDebug", false);
#endif
DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_IMAGE, "Show GPU Stats"),
FSUI_CSTR("Shows the host's GPU pipeline statistics."), "EmuCore/GS", "OsdShowGPUStats", false);
DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_PF_HEARTBEAT_ALT, "Show Frame Times"),
FSUI_CSTR("Shows a visual history of frame times."), "EmuCore/GS", "OsdShowFrameTimes", false);
DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_SLIDERS, "Show Settings"),
+24
View File
@@ -71,6 +71,7 @@ std::vector<SmallString> s_software_thread_lines;
SmallString s_capture_line;
SmallString s_gpu_usage_line;
SmallString s_gpu_debug_info_line;
SmallString s_gpu_stats_line;
SmallString s_speed_icon;
constexpr ImU32 white_color = IM_COL32(255, 255, 255, 255);
@@ -516,6 +517,24 @@ __ri void ImGuiManager::DrawPerformanceOverlay(float& position_y, float scale, f
}
#endif
}
if (GSConfig.OsdShowGPUStats)
{
const auto FormatUnits = [](double val) {
if (val >= 1e9)
return fmt::format("{:.5}B", val / 1e9);
if (val >= 1e6)
return fmt::format("{:.5}M", val / 1e6);
if (val >= 1e3)
return fmt::format("{:.5}K", val / 1e3);
return fmt::format("{:.5}", val);
};
s_gpu_stats_line.format("VSI: {} | PSI: {}",
FormatUnits(PerformanceMetrics::GetGPUAverageVSInvocations()),
FormatUnits(PerformanceMetrics::GetGPUAveragePSInvocations()));
DRAW_LINE(osd_font, font_size, s_gpu_stats_line.c_str(), white_color);
}
}
// No refresh yet. Display cached lines.
else
@@ -568,6 +587,11 @@ __ri void ImGuiManager::DrawPerformanceOverlay(float& position_y, float scale, f
DRAW_LINE(osd_font, font_size, s_gpu_debug_info_line.c_str(), white_color);
#endif
}
if (GSConfig.OsdShowGPUStats)
{
DRAW_LINE(osd_font, font_size, s_gpu_stats_line.c_str(), white_color);
}
}
// Check every OSD frame because this is an animation.
+2
View File
@@ -733,6 +733,7 @@ Pcsx2Config::GSOptions::GSOptions()
OsdShowCPU = false;
OsdShowGPU = false;
OsdShowGPUDebug = false;
OsdShowGPUStats = false;
OsdShowIndicators = true;
OsdShowFrameTimes = false;
OsdShowHardwareInfo = false;
@@ -966,6 +967,7 @@ void Pcsx2Config::GSOptions::LoadSave(SettingsWrapper& wrap)
SettingsWrapBitBool(OsdShowCPU);
SettingsWrapBitBool(OsdShowGPU);
SettingsWrapBitBool(OsdShowGPUDebug);
SettingsWrapBitBool(OsdShowGPUStats);
SettingsWrapBitBool(OsdShowResolution);
SettingsWrapBitBool(OsdShowGSStats);
SettingsWrapBitBool(OsdShowIndicators);
+21 -1
View File
@@ -70,6 +70,10 @@ static float s_average_gpu_time = 0.0f;
static float s_accumulated_gpu_time = 0.0f;
static float s_gpu_usage = 0.0f;
static u32 s_presents_since_last_update = 0;
static double s_average_gpu_vs_invocations = 0.0;
static double s_average_gpu_ps_invocations = 0.0;
static u64 s_accumulated_gpu_vs_invocations = 0;
static u64 s_accumulated_gpu_ps_invocations = 0;
void PerformanceMetrics::Clear()
{
@@ -156,8 +160,12 @@ void PerformanceMetrics::Update(bool gs_register_write, bool fb_blit, bool is_sk
s_maximum_frame_time = std::exchange(s_maximum_frame_time_accumulator, 0.0f);
s_fps = static_cast<float>(s_frames_since_last_update) / time;
s_average_gpu_time = s_accumulated_gpu_time / static_cast<float>(s_unskipped_frames_since_last_update);
s_average_gpu_vs_invocations = static_cast<double>(s_accumulated_gpu_vs_invocations) / static_cast<double>(s_unskipped_frames_since_last_update);
s_average_gpu_ps_invocations = static_cast<double>(s_accumulated_gpu_ps_invocations) / static_cast<double>(s_unskipped_frames_since_last_update);
s_gpu_usage = s_accumulated_gpu_time / (time * 10.0f);
s_accumulated_gpu_time = 0.0f;
s_accumulated_gpu_vs_invocations = 0;
s_accumulated_gpu_ps_invocations = 0;
// prefer privileged register write based framerate detection, it's less likely to have false positives
if (s_gs_privileged_register_writes_since_last_update > 0 && !EmuConfig.Gamefixes.BlitInternalFPSHack)
@@ -228,9 +236,11 @@ void PerformanceMetrics::Update(bool gs_register_write, bool fb_blit, bool is_sk
Host::OnPerformanceMetricsUpdated();
}
void PerformanceMetrics::OnGPUPresent(float gpu_time)
void PerformanceMetrics::OnGPUPresent(float gpu_time, u64 vs_invocations, u64 ps_invocations)
{
s_accumulated_gpu_time += gpu_time;
s_accumulated_gpu_vs_invocations += vs_invocations;
s_accumulated_gpu_ps_invocations += ps_invocations;
s_presents_since_last_update++;
}
@@ -362,6 +372,16 @@ float PerformanceMetrics::GetGPUAverageTime()
return s_average_gpu_time;
}
double PerformanceMetrics::GetGPUAverageVSInvocations()
{
return s_average_gpu_vs_invocations;
}
double PerformanceMetrics::GetGPUAveragePSInvocations()
{
return s_average_gpu_ps_invocations;
}
const PerformanceMetrics::FrameTimeHistory& PerformanceMetrics::GetFrameTimeHistory()
{
return s_frame_time_history;
+3 -1
View File
@@ -21,7 +21,7 @@ namespace PerformanceMetrics
void Clear();
void Reset();
void Update(bool gs_register_write, bool fb_blit, bool is_skipping_present);
void OnGPUPresent(float gpu_time);
void OnGPUPresent(float gpu_time, u64 vs_invocations, u64 ps_invocations);
/// Sets the EE thread for CPU usage calculations.
void SetCPUThread(Threading::ThreadHandle thread);
@@ -57,6 +57,8 @@ namespace PerformanceMetrics
float GetGPUUsage();
float GetGPUAverageTime();
double GetGPUAverageVSInvocations();
double GetGPUAveragePSInvocations();
const FrameTimeHistory& GetFrameTimeHistory();
u32 GetFrameTimeHistoryPos();