ImGuiOverlays: Add frame time graph

This commit is contained in:
Connor McLaughlin 2022-12-01 00:25:16 +10:00 committed by refractionpcsx2
parent bb7ab5690c
commit 4bf6b1df5e
9 changed files with 152 additions and 11 deletions

View File

@ -144,6 +144,7 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsDialog* dialog, QWidget*
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.osdShowIndicators, "EmuCore/GS", "OsdShowIndicators", true);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.osdShowSettings, "EmuCore/GS", "OsdShowSettings", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.osdShowInputs, "EmuCore/GS", "OsdShowInputs", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.osdShowFrameTimes, "EmuCore/GS", "OsdShowFrameTimes", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.warnAboutUnsafeSettings, "EmuCore", "WarnAboutUnsafeSettings", true);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.fxaa, "EmuCore/GS", "fxaa", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.shadeBoost, "EmuCore/GS", "ShadeBoost", false);

View File

@ -1552,13 +1552,20 @@
</property>
</widget>
</item>
<item row="5" column="0">
<item row="5" column="1">
<widget class="QCheckBox" name="warnAboutUnsafeSettings">
<property name="text">
<string>Warn About Unsafe Settings</string>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QCheckBox" name="osdShowFrameTimes">
<property name="text">
<string>Show Frame Times</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>

View File

@ -508,7 +508,8 @@ struct Pcsx2Config
OsdShowGSStats : 1,
OsdShowIndicators : 1,
OsdShowSettings : 1,
OsdShowInputs : 1;
OsdShowInputs : 1,
OsdShowFrameTimes : 1;
bool
HWSpinGPUForReadbacks : 1,

View File

@ -2495,6 +2495,8 @@ void FullscreenUI::DrawInterfaceSettingsPage()
"EmuCore/GS", "OsdShowSettings", false);
DrawToggleSetting(bsi, ICON_FA_GAMEPAD " Show Inputs",
"Shows the current controller state of the system in the bottom-left corner of the display.", "EmuCore/GS", "OsdShowInputs", false);
DrawToggleSetting(bsi, ICON_FA_RULER_HORIZONTAL " Show Frame Times",
"Shows a visual history of frame times in the upper-left corner of the display.", "EmuCore/GS", "OsdShowFrameTimes", false);
DrawToggleSetting(bsi, ICON_FA_EXCLAMATION_CIRCLE " Warn About Unsafe Settings",
"Displays warnings when settings are enabled which may break games.", "EmuCore", "WarnAboutUnsafeSettings", true);

View File

@ -15,14 +15,18 @@
#include "PrecompiledHeader.h"
#include <array>
#include <chrono>
#include <cmath>
#include <deque>
#include <mutex>
#include <tuple>
#include <unordered_map>
#include "gsl/span"
#include "fmt/core.h"
#include "common/Align.h"
#include "common/StringUtil.h"
#include "common/Timer.h"
#include "imgui.h"
@ -33,6 +37,7 @@
#include "Frontend/ImGuiOverlays.h"
#include "GS.h"
#include "GS/GS.h"
#include "GS/GSVector.h"
#include "Host.h"
#include "HostDisplay.h"
#include "IconsFontAwesome5.h"
@ -60,6 +65,32 @@ namespace ImGuiManager
#endif
} // namespace ImGuiManager
static std::tuple<float, float> GetMinMax(gsl::span<const float> values)
{
GSVector4 vmin(GSVector4::load<false>(values.data()));
GSVector4 vmax(vmin);
const u32 count = static_cast<u32>(values.size());
const u32 aligned_count = Common::AlignDownPow2(count, 4);
u32 i = 4;
for (; i < aligned_count; i += 4)
{
const GSVector4 v(GSVector4::load<false>(&values[i]));
vmin = vmin.min(v);
vmax = vmax.max(v);
}
float min = std::min(vmin.x, std::min(vmin.y, std::min(vmin.z, vmin.w)));
float max = std::max(vmax.x, std::max(vmax.y, std::max(vmax.z, vmax.w)));
for (; i < count; i++)
{
min = std::min(min, values[i]);
max = std::max(max, values[i]);
}
return std::tie(min, max);
}
void ImGuiManager::FormatProcessorStat(std::string& text, double usage, double time)
{
// Some values, such as GPU (and even CPU to some extent) can be out of phase with the wall clock,
@ -224,6 +255,69 @@ void ImGuiManager::DrawPerformanceOverlay()
DRAW_LINE(standard_font, is_slowmo ? ICON_FA_FORWARD : ICON_FA_FAST_FORWARD, IM_COL32(255, 255, 255, 255));
}
}
#ifdef PCSX2_CORE
if (GSConfig.OsdShowFrameTimes)
{
const ImVec2 history_size(200.0f * scale, 50.0f * scale);
ImGui::SetNextWindowSize(ImVec2(history_size.x, history_size.y));
ImGui::SetNextWindowPos(ImVec2(ImGui::GetIO().DisplaySize.x - margin - history_size.x, position_y));
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.0f, 0.0f, 0.0f, 0.25f));
ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.0f, 0.0f, 0.0f, 0.0f));
ImGui::PushStyleColor(ImGuiCol_PlotLines, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.0f, 0.0f));
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f);
ImGui::PushFont(fixed_font);
if (ImGui::Begin("##frame_times", nullptr, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoInputs))
{
auto [min, max] = GetMinMax(PerformanceMetrics::GetFrameTimeHistory());
// add a little bit of space either side, so we're not constantly resizing
if ((max - min) < 4.0f)
{
min = min - std::fmod(min, 1.0f);
max = max - std::fmod(max, 1.0f) + 1.0f;
min = std::max(min - 2.0f, 0.0f);
max += 2.0f;
}
ImGui::PlotEx(
ImGuiPlotType_Lines, "##frame_times",
[](void*, int idx) -> float {
return PerformanceMetrics::GetFrameTimeHistory()[(
(PerformanceMetrics::GetFrameTimeHistoryPos() + idx) % PerformanceMetrics::NUM_FRAME_TIME_SAMPLES)];
},
nullptr, PerformanceMetrics::NUM_FRAME_TIME_SAMPLES, 0, nullptr, min, max, history_size);
ImDrawList* win_dl = ImGui::GetCurrentWindow()->DrawList;
const ImVec2 wpos(ImGui::GetCurrentWindow()->Pos);
text.clear();
fmt::format_to(std::back_inserter(text), "{:.1f} ms", max);
text_size = fixed_font->CalcTextSizeA(fixed_font->FontSize, FLT_MAX, 0.0f, text.c_str(), text.c_str() + text.length());
win_dl->AddText(ImVec2(wpos.x + history_size.x - text_size.x - spacing + shadow_offset, wpos.y + shadow_offset),
IM_COL32(0, 0, 0, 100), text.c_str(), text.c_str() + text.length());
win_dl->AddText(ImVec2(wpos.x + history_size.x - text_size.x - spacing, wpos.y), IM_COL32(255, 255, 255, 255), text.c_str(),
text.c_str() + text.length());
text.clear();
fmt::format_to(std::back_inserter(text), "{:.1f} ms", min);
text_size = fixed_font->CalcTextSizeA(fixed_font->FontSize, FLT_MAX, 0.0f, text.c_str(), text.c_str() + text.length());
win_dl->AddText(ImVec2(wpos.x + history_size.x - text_size.x - spacing + shadow_offset,
wpos.y + history_size.y - fixed_font->FontSize + shadow_offset),
IM_COL32(0, 0, 0, 100), text.c_str(), text.c_str() + text.length());
win_dl->AddText(ImVec2(wpos.x + history_size.x - text_size.x - spacing, wpos.y + history_size.y - fixed_font->FontSize),
IM_COL32(255, 255, 255, 255), text.c_str(), text.c_str() + text.length());
}
ImGui::End();
ImGui::PopFont();
ImGui::PopStyleVar(5);
ImGui::PopStyleColor(3);
}
#endif
}
else if (!fsui_active)
{

View File

@ -605,7 +605,7 @@ void GSRenderer::VSync(u32 field, bool registers_written)
if (Host::BeginPresentFrame(true))
Host::EndPresentFrame();
g_gs_device->RestoreAPIState();
PerformanceMetrics::Update(registers_written, fb_sprite_frame);
PerformanceMetrics::Update(registers_written, fb_sprite_frame, true);
return;
}
@ -655,7 +655,7 @@ void GSRenderer::VSync(u32 field, bool registers_written)
PerformanceMetrics::OnGPUPresent(g_host_display->GetAndResetAccumulatedGPUTime());
}
g_gs_device->RestoreAPIState();
PerformanceMetrics::Update(registers_written, fb_sprite_frame);
PerformanceMetrics::Update(registers_written, fb_sprite_frame, false);
// snapshot
// wx is dumb and call this from the UI thread...

View File

@ -321,6 +321,7 @@ Pcsx2Config::GSOptions::GSOptions()
OsdShowIndicators = true;
OsdShowSettings = false;
OsdShowInputs = false;
OsdShowFrameTimes = false;
HWDownloadMode = GSHardwareDownloadMode::Enabled;
HWSpinGPUForReadbacks = false;
@ -544,6 +545,7 @@ void Pcsx2Config::GSOptions::ReloadIniSettings()
GSSettingBool(OsdShowIndicators);
GSSettingBool(OsdShowSettings);
GSSettingBool(OsdShowInputs);
GSSettingBool(OsdShowFrameTimes);
GSSettingBool(HWSpinGPUForReadbacks);
GSSettingBool(HWSpinCPUForReadbacks);

View File

@ -41,6 +41,7 @@ static float s_average_frame_time = 0.0f;
static float s_average_frame_time_accumulator = 0.0f;
static float s_worst_frame_time_accumulator = 0.0f;
static u32 s_frames_since_last_update = 0;
static u32 s_unskipped_frames_since_last_update = 0;
static Common::Timer s_last_update_time;
static Common::Timer s_last_frame_time;
@ -65,6 +66,9 @@ static float s_gs_thread_time = 0.0f;
static float s_vu_thread_usage = 0.0f;
static float s_vu_thread_time = 0.0f;
static PerformanceMetrics::FrameTimeHistory s_frame_time_history;
static u32 s_frame_time_history_pos = 0;
struct GSSWThreadStats
{
Threading::ThreadHandle handle;
@ -100,11 +104,15 @@ void PerformanceMetrics::Clear()
s_gpu_usage = 0.0f;
s_frame_number = 0;
s_frame_time_history.fill(0.0f);
s_frame_time_history_pos = 0;
}
void PerformanceMetrics::Reset()
{
s_frames_since_last_update = 0;
s_unskipped_frames_since_last_update = 0;
s_gs_framebuffer_blits_since_last_update = 0;
s_gs_privileged_register_writes_since_last_update = 0;
s_average_frame_time_accumulator = 0.0f;
@ -125,11 +133,18 @@ void PerformanceMetrics::Reset()
stat.last_cpu_time = stat.handle.GetCPUTime();
}
void PerformanceMetrics::Update(bool gs_register_write, bool fb_blit)
void PerformanceMetrics::Update(bool gs_register_write, bool fb_blit, bool is_skipping_present)
{
const float frame_time = s_last_frame_time.GetTimeMillisecondsAndReset();
s_average_frame_time_accumulator += frame_time;
s_worst_frame_time_accumulator = std::max(s_worst_frame_time_accumulator, frame_time);
if (!is_skipping_present)
{
const float frame_time = s_last_frame_time.GetTimeMillisecondsAndReset();
s_average_frame_time_accumulator += frame_time;
s_worst_frame_time_accumulator = std::max(s_worst_frame_time_accumulator, frame_time);
s_frame_time_history[s_frame_time_history_pos] = frame_time;
s_frame_time_history_pos = (s_frame_time_history_pos + 1) % NUM_FRAME_TIME_SAMPLES;
s_unskipped_frames_since_last_update++;
}
s_frames_since_last_update++;
s_gs_privileged_register_writes_since_last_update += static_cast<u32>(gs_register_write);
s_gs_framebuffer_blits_since_last_update += static_cast<u32>(fb_blit);
@ -144,10 +159,10 @@ void PerformanceMetrics::Update(bool gs_register_write, bool fb_blit)
s_last_update_time.ResetTo(now_ticks);
s_worst_frame_time = s_worst_frame_time_accumulator;
s_worst_frame_time_accumulator = 0.0f;
s_average_frame_time = s_average_frame_time_accumulator / static_cast<float>(s_frames_since_last_update);
s_average_frame_time = s_average_frame_time_accumulator / static_cast<float>(s_unskipped_frames_since_last_update);
s_average_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_frames_since_last_update);
s_average_gpu_time = s_accumulated_gpu_time / static_cast<float>(s_unskipped_frames_since_last_update);
s_gpu_usage = s_accumulated_gpu_time / (time * 10.0f);
s_accumulated_gpu_time = 0.0f;
@ -209,6 +224,7 @@ void PerformanceMetrics::Update(bool gs_register_write, bool fb_blit)
}
s_frames_since_last_update = 0;
s_unskipped_frames_since_last_update = 0;
s_presents_since_last_update = 0;
#ifdef PCSX2_CORE
@ -339,3 +355,13 @@ float PerformanceMetrics::GetGPUAverageTime()
{
return s_average_gpu_time;
}
const PerformanceMetrics::FrameTimeHistory& PerformanceMetrics::GetFrameTimeHistory()
{
return s_frame_time_history;
}
u32 PerformanceMetrics::GetFrameTimeHistoryPos()
{
return s_frame_time_history_pos;
}

View File

@ -14,6 +14,8 @@
*/
#pragma once
#include <array>
#include "common/Threading.h"
namespace PerformanceMetrics
@ -25,9 +27,12 @@ namespace PerformanceMetrics
DISPFBBlit
};
static constexpr u32 NUM_FRAME_TIME_SAMPLES = 150;
using FrameTimeHistory = std::array<float, NUM_FRAME_TIME_SAMPLES>;
void Clear();
void Reset();
void Update(bool gs_register_write, bool fb_blit);
void Update(bool gs_register_write, bool fb_blit, bool is_skipping_present);
void OnGPUPresent(float gpu_time);
/// Sets the EE thread for CPU usage calculations.
@ -64,4 +69,7 @@ namespace PerformanceMetrics
float GetGPUUsage();
float GetGPUAverageTime();
const FrameTimeHistory& GetFrameTimeHistory();
u32 GetFrameTimeHistoryPos();
} // namespace PerformanceMetrics