diff --git a/pcsx2-qt/Settings/OSDSettingsWidget.cpp b/pcsx2-qt/Settings/OSDSettingsWidget.cpp index 610822f49d..9c3005fcde 100644 --- a/pcsx2-qt/Settings/OSDSettingsWidget.cpp +++ b/pcsx2-qt/Settings/OSDSettingsWidget.cpp @@ -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, diff --git a/pcsx2-qt/Settings/OSDSettingsWidget.ui b/pcsx2-qt/Settings/OSDSettingsWidget.ui index 1fc86aacde..472b4de17d 100644 --- a/pcsx2-qt/Settings/OSDSettingsWidget.ui +++ b/pcsx2-qt/Settings/OSDSettingsWidget.ui @@ -492,6 +492,13 @@ + + + + Show GPU Pipeline Statistics + + + @@ -530,6 +537,7 @@ showGSStats showUsageCPU showUsageGPU + showStatsGPU showStatusIndicators showFrameTimes diff --git a/pcsx2/Config.h b/pcsx2/Config.h index 615c7ce26b..93bbb89e62 100644 --- a/pcsx2/Config.h +++ b/pcsx2/Config.h @@ -774,6 +774,7 @@ struct Pcsx2Config OsdShowCPU : 1, OsdShowGPU : 1, OsdShowGPUDebug : 1, + OsdShowGPUStats : 1, OsdShowIndicators : 1, OsdShowFrameTimes : 1, OsdShowHardwareInfo : 1, diff --git a/pcsx2/GS/GS.cpp b/pcsx2/GS/GS.cpp index 6a541794d9..fec4df9f1b 100644 --- a/pcsx2/GS/GS.cpp +++ b/pcsx2/GS/GS.cpp @@ -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) diff --git a/pcsx2/GS/Renderers/Common/GSDevice.h b/pcsx2/GS/Renderers/Common/GSDevice.h index 6dfb6ab287..4621115e5f 100644 --- a/pcsx2/GS/Renderers/Common/GSDevice.h +++ b/pcsx2/GS/Renderers/Common/GSDevice.h @@ -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(); diff --git a/pcsx2/GS/Renderers/Common/GSRenderer.cpp b/pcsx2/GS/Renderers/Common/GSRenderer.cpp index 71708ade6e..1d77040963 100644 --- a/pcsx2/GS/Renderers/Common/GSRenderer.cpp +++ b/pcsx2/GS/Renderers/Common/GSRenderer.cpp @@ -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); diff --git a/pcsx2/GS/Renderers/DX11/GSDevice11.cpp b/pcsx2/GS/Renderers/DX11/GSDevice11.cpp index 1848b8efd0..9ce212193e 100644 --- a/pcsx2/GS/Renderers/DX11/GSDevice11.cpp +++ b/pcsx2/GS/Renderers/DX11/GSDevice11.cpp @@ -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); diff --git a/pcsx2/GS/Renderers/DX11/GSDevice11.h b/pcsx2/GS/Renderers/DX11/GSDevice11.h index 6c6e80f362..3da31414cf 100644 --- a/pcsx2/GS/Renderers/DX11/GSDevice11.h +++ b/pcsx2/GS/Renderers/DX11/GSDevice11.h @@ -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, 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 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(); diff --git a/pcsx2/GS/Renderers/DX12/GSDevice12.cpp b/pcsx2/GS/Renderers/DX12/GSDevice12.cpp index 37f9721208..eb7fcc285c 100644 --- a/pcsx2/GS/Renderers/DX12/GSDevice12.cpp +++ b/pcsx2/GS/Renderers/DX12/GSDevice12.cpp @@ -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(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& 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(); diff --git a/pcsx2/GS/Renderers/DX12/GSDevice12.h b/pcsx2/GS/Renderers/DX12/GSDevice12.h index 938cfcb2bc..691e176afb 100644 --- a/pcsx2/GS/Renderers/DX12/GSDevice12.h +++ b/pcsx2/GS/Renderers/DX12/GSDevice12.h @@ -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, 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 m_pipeline_statistics_query_heap; + ComPtr m_pipeline_statistics_query_buffer; + ComPtr 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; diff --git a/pcsx2/GS/Renderers/Metal/GSDeviceMTL.h b/pcsx2/GS/Renderers/Metal/GSDeviceMTL.h index dfa0e5ecfc..dae95ae80c 100644 --- a/pcsx2/GS/Renderers/Metal/GSDeviceMTL.h +++ b/pcsx2/GS/Renderers/Metal/GSDeviceMTL.h @@ -414,6 +414,9 @@ public: float GetAndResetAccumulatedGPUTime() override; void AccumulateCommandBufferTime(id buffer); + bool SetGPUPipelineStatisticsEnabled(bool enabled) override { return false; } + GPUPipelineStatistics GetAndResetAccumulatedGPUPipelineStatistics() override { return {}; } + std::unique_ptr CreateDownloadTexture(u32 width, u32 height, GSTexture::Format format) override; void ClearSamplerCache() override; diff --git a/pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.cpp b/pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.cpp index 127108b7fd..4d96d13b59 100644 --- a/pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.cpp +++ b/pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.cpp @@ -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); diff --git a/pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.h b/pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.h index aff062881b..80a88c154f 100644 --- a/pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.h +++ b/pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.h @@ -147,6 +147,7 @@ public: private: static constexpr u8 NUM_TIMESTAMP_QUERIES = 5; + static constexpr u8 NUM_PIPELINE_STATISTICS_QUERIES = 5; std::unique_ptr m_gl_context; @@ -253,6 +254,15 @@ private: bool m_timestamp_query_started = false; bool m_gpu_timing_enabled = false; + std::array, 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(); diff --git a/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp b/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp index 2d55f76e60..2e1cccc0b9 100644 --- a/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp +++ b/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp @@ -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); diff --git a/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.h b/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.h index 19d4f4fb7c..c245f34a92 100644 --- a/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.h +++ b/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.h @@ -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> 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 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; diff --git a/pcsx2/ImGui/FullscreenUI_Settings.cpp b/pcsx2/ImGui/FullscreenUI_Settings.cpp index 253af71a8c..8edec72252 100644 --- a/pcsx2/ImGui/FullscreenUI_Settings.cpp +++ b/pcsx2/ImGui/FullscreenUI_Settings.cpp @@ -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"), diff --git a/pcsx2/ImGui/ImGuiOverlays.cpp b/pcsx2/ImGui/ImGuiOverlays.cpp index a54c4725d0..f49f121e19 100644 --- a/pcsx2/ImGui/ImGuiOverlays.cpp +++ b/pcsx2/ImGui/ImGuiOverlays.cpp @@ -71,6 +71,7 @@ std::vector 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. diff --git a/pcsx2/Pcsx2Config.cpp b/pcsx2/Pcsx2Config.cpp index df57ca9341..6dba18bbc1 100644 --- a/pcsx2/Pcsx2Config.cpp +++ b/pcsx2/Pcsx2Config.cpp @@ -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); diff --git a/pcsx2/PerformanceMetrics.cpp b/pcsx2/PerformanceMetrics.cpp index 42a8409bfd..c1f3c543f2 100644 --- a/pcsx2/PerformanceMetrics.cpp +++ b/pcsx2/PerformanceMetrics.cpp @@ -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(s_frames_since_last_update) / time; s_average_gpu_time = s_accumulated_gpu_time / static_cast(s_unskipped_frames_since_last_update); + s_average_gpu_vs_invocations = static_cast(s_accumulated_gpu_vs_invocations) / static_cast(s_unskipped_frames_since_last_update); + s_average_gpu_ps_invocations = static_cast(s_accumulated_gpu_ps_invocations) / static_cast(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; diff --git a/pcsx2/PerformanceMetrics.h b/pcsx2/PerformanceMetrics.h index 80c83e5d15..0efc488432 100644 --- a/pcsx2/PerformanceMetrics.h +++ b/pcsx2/PerformanceMetrics.h @@ -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();