mirror of
https://github.com/PCSX2/pcsx2.git
synced 2026-07-11 01:34:17 +02:00
GS: Add GPU pipeline stats to OSD.
Adds vertex shader and pixel shader invocations.
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -774,6 +774,7 @@ struct Pcsx2Config
|
||||
OsdShowCPU : 1,
|
||||
OsdShowGPU : 1,
|
||||
OsdShowGPUDebug : 1,
|
||||
OsdShowGPUStats : 1,
|
||||
OsdShowIndicators : 1,
|
||||
OsdShowFrameTimes : 1,
|
||||
OsdShowHardwareInfo : 1,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user