Merge pull request #11348 from Sam-Belliveau/improved-pacing
CoreTiming: Throttle Before Every Event Using std::chrono
This commit is contained in:
commit
8a1cac9be1
|
@ -162,7 +162,7 @@ unsigned int Mixer::Mix(short* samples, unsigned int num_samples)
|
||||||
memset(samples, 0, num_samples * 2 * sizeof(short));
|
memset(samples, 0, num_samples * 2 * sizeof(short));
|
||||||
|
|
||||||
// TODO: Determine how emulation speed will be used in audio
|
// TODO: Determine how emulation speed will be used in audio
|
||||||
// const float emulation_speed = std::roundf(g_perf_metrics.GetSpeed()) / 100.f;
|
// const float emulation_speed = g_perf_metrics.GetSpeed();
|
||||||
const float emulation_speed = m_config_emulation_speed;
|
const float emulation_speed = m_config_emulation_speed;
|
||||||
const int timing_variance = m_config_timing_variance;
|
const int timing_variance = m_config_timing_variance;
|
||||||
if (m_config_audio_stretch)
|
if (m_config_audio_stretch)
|
||||||
|
|
|
@ -891,9 +891,9 @@ void Callback_NewField()
|
||||||
|
|
||||||
void UpdateTitle()
|
void UpdateTitle()
|
||||||
{
|
{
|
||||||
float FPS = g_perf_metrics.GetFPS();
|
const double FPS = g_perf_metrics.GetFPS();
|
||||||
float VPS = g_perf_metrics.GetVPS();
|
const double VPS = g_perf_metrics.GetVPS();
|
||||||
float Speed = g_perf_metrics.GetSpeed();
|
const double Speed = 100.0 * g_perf_metrics.GetSpeed();
|
||||||
|
|
||||||
// Settings are shown the same for both extended and summary info
|
// Settings are shown the same for both extended and summary info
|
||||||
const std::string SSettings = fmt::format(
|
const std::string SSettings = fmt::format(
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
#include "Core/System.h"
|
#include "Core/System.h"
|
||||||
|
|
||||||
#include "VideoCommon/Fifo.h"
|
#include "VideoCommon/Fifo.h"
|
||||||
|
#include "VideoCommon/PerformanceMetrics.h"
|
||||||
#include "VideoCommon/VideoBackendBase.h"
|
#include "VideoCommon/VideoBackendBase.h"
|
||||||
|
|
||||||
namespace CoreTiming
|
namespace CoreTiming
|
||||||
|
@ -99,6 +100,10 @@ void CoreTimingManager::Init()
|
||||||
// that slice.
|
// that slice.
|
||||||
m_is_global_timer_sane = true;
|
m_is_global_timer_sane = true;
|
||||||
|
|
||||||
|
// Reset data used by the throttling system
|
||||||
|
m_throttle_last_cycle = 0;
|
||||||
|
m_throttle_deadline = Clock::now();
|
||||||
|
|
||||||
m_event_fifo_id = 0;
|
m_event_fifo_id = 0;
|
||||||
m_ev_lost = RegisterEvent("_lost_event", &EmptyTimedCallback);
|
m_ev_lost = RegisterEvent("_lost_event", &EmptyTimedCallback);
|
||||||
}
|
}
|
||||||
|
@ -305,6 +310,8 @@ void CoreTimingManager::Advance()
|
||||||
Event evt = std::move(m_event_queue.front());
|
Event evt = std::move(m_event_queue.front());
|
||||||
std::pop_heap(m_event_queue.begin(), m_event_queue.end(), std::greater<Event>());
|
std::pop_heap(m_event_queue.begin(), m_event_queue.end(), std::greater<Event>());
|
||||||
m_event_queue.pop_back();
|
m_event_queue.pop_back();
|
||||||
|
|
||||||
|
Throttle(evt.time);
|
||||||
evt.type->callback(system, evt.userdata, m_globals.global_timer - evt.time);
|
evt.type->callback(system, evt.userdata, m_globals.global_timer - evt.time);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -326,6 +333,61 @@ void CoreTimingManager::Advance()
|
||||||
PowerPC::CheckExternalExceptions();
|
PowerPC::CheckExternalExceptions();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CoreTimingManager::Throttle(const s64 target_cycle)
|
||||||
|
{
|
||||||
|
// Based on number of cycles and emulation speed, increase the target deadline
|
||||||
|
const s64 cycles = target_cycle - m_throttle_last_cycle;
|
||||||
|
|
||||||
|
// Prevent any throttling code if the amount of time passed is < ~0.122ms
|
||||||
|
if (cycles < m_throttle_min_clock_per_sleep)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_throttle_last_cycle = target_cycle;
|
||||||
|
|
||||||
|
const double speed =
|
||||||
|
Core::GetIsThrottlerTempDisabled() ? 0.0 : Config::Get(Config::MAIN_EMULATION_SPEED);
|
||||||
|
|
||||||
|
if (0.0 < speed)
|
||||||
|
m_throttle_deadline +=
|
||||||
|
std::chrono::duration_cast<DT>(DT_s(cycles) / (speed * m_throttle_clock_per_sec));
|
||||||
|
|
||||||
|
// A maximum fallback is used to prevent the system from sleeping for
|
||||||
|
// too long or going full speed in an attempt to catch up to timings.
|
||||||
|
const DT max_fallback =
|
||||||
|
std::chrono::duration_cast<DT>(DT_ms(Config::Get(Config::MAIN_TIMING_VARIANCE)));
|
||||||
|
|
||||||
|
const TimePoint time = Clock::now();
|
||||||
|
const TimePoint min_deadline = time - max_fallback;
|
||||||
|
const TimePoint max_deadline = time + max_fallback;
|
||||||
|
|
||||||
|
if (m_throttle_deadline > max_deadline)
|
||||||
|
{
|
||||||
|
m_throttle_deadline = max_deadline;
|
||||||
|
}
|
||||||
|
else if (m_throttle_deadline < min_deadline)
|
||||||
|
{
|
||||||
|
DEBUG_LOG_FMT(COMMON, "System can not to keep up with timings! [relaxing timings by {} us]",
|
||||||
|
DT_us(min_deadline - m_throttle_deadline).count());
|
||||||
|
m_throttle_deadline = min_deadline;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only sleep if we are behind the deadline
|
||||||
|
if (time < m_throttle_deadline)
|
||||||
|
{
|
||||||
|
std::this_thread::sleep_until(m_throttle_deadline);
|
||||||
|
|
||||||
|
// Count amount of time sleeping for analytics
|
||||||
|
const TimePoint time_after_sleep = Clock::now();
|
||||||
|
g_perf_metrics.CountThrottleSleep(time_after_sleep - time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TimePoint CoreTimingManager::GetCPUTimePoint(s64 cyclesLate) const
|
||||||
|
{
|
||||||
|
return TimePoint(std::chrono::duration_cast<DT>(DT_s(m_globals.global_timer - cyclesLate) /
|
||||||
|
m_throttle_clock_per_sec));
|
||||||
|
}
|
||||||
|
|
||||||
void CoreTimingManager::LogPendingEvents() const
|
void CoreTimingManager::LogPendingEvents() const
|
||||||
{
|
{
|
||||||
auto clone = m_event_queue;
|
auto clone = m_event_queue;
|
||||||
|
@ -340,6 +402,9 @@ void CoreTimingManager::LogPendingEvents() const
|
||||||
// Should only be called from the CPU thread after the PPC clock has changed
|
// Should only be called from the CPU thread after the PPC clock has changed
|
||||||
void CoreTimingManager::AdjustEventQueueTimes(u32 new_ppc_clock, u32 old_ppc_clock)
|
void CoreTimingManager::AdjustEventQueueTimes(u32 new_ppc_clock, u32 old_ppc_clock)
|
||||||
{
|
{
|
||||||
|
m_throttle_clock_per_sec = new_ppc_clock;
|
||||||
|
m_throttle_min_clock_per_sleep = new_ppc_clock / 1200;
|
||||||
|
|
||||||
for (Event& ev : m_event_queue)
|
for (Event& ev : m_event_queue)
|
||||||
{
|
{
|
||||||
const s64 ticks = (ev.time - m_globals.global_timer) * new_ppc_clock / old_ppc_clock;
|
const s64 ticks = (ev.time - m_globals.global_timer) * new_ppc_clock / old_ppc_clock;
|
||||||
|
|
|
@ -140,6 +140,13 @@ public:
|
||||||
// Directly accessed by the JIT.
|
// Directly accessed by the JIT.
|
||||||
Globals& GetGlobals() { return m_globals; }
|
Globals& GetGlobals() { return m_globals; }
|
||||||
|
|
||||||
|
// Throttle the CPU to the specified target cycle.
|
||||||
|
// Never used outside of CoreTiming, however it remains public
|
||||||
|
// in order to allow custom throttling implementations to be tested.
|
||||||
|
void Throttle(const s64 target_cycle);
|
||||||
|
|
||||||
|
TimePoint GetCPUTimePoint(s64 cyclesLate) const; // Used by Dolphin Analytics
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Globals m_globals;
|
Globals m_globals;
|
||||||
|
|
||||||
|
@ -173,6 +180,11 @@ private:
|
||||||
float m_config_oc_inv_factor = 0.0f;
|
float m_config_oc_inv_factor = 0.0f;
|
||||||
bool m_config_sync_on_skip_idle = false;
|
bool m_config_sync_on_skip_idle = false;
|
||||||
|
|
||||||
|
s64 m_throttle_last_cycle = 0;
|
||||||
|
TimePoint m_throttle_deadline = Clock::now();
|
||||||
|
s64 m_throttle_clock_per_sec;
|
||||||
|
s64 m_throttle_min_clock_per_sleep;
|
||||||
|
|
||||||
int DowncountToCycles(int downcount) const;
|
int DowncountToCycles(int downcount) const;
|
||||||
int CyclesToDowncount(int cycles) const;
|
int CyclesToDowncount(int cycles) const;
|
||||||
};
|
};
|
||||||
|
|
|
@ -67,6 +67,7 @@ IPC_HLE_PERIOD: For the Wii Remote this is the call schedule:
|
||||||
#include "Core/PowerPC/PowerPC.h"
|
#include "Core/PowerPC/PowerPC.h"
|
||||||
#include "Core/System.h"
|
#include "Core/System.h"
|
||||||
#include "VideoCommon/Fifo.h"
|
#include "VideoCommon/Fifo.h"
|
||||||
|
#include "VideoCommon/PerformanceMetrics.h"
|
||||||
|
|
||||||
namespace SystemTimers
|
namespace SystemTimers
|
||||||
{
|
{
|
||||||
|
@ -77,9 +78,10 @@ CoreTiming::EventType* et_VI;
|
||||||
CoreTiming::EventType* et_AudioDMA;
|
CoreTiming::EventType* et_AudioDMA;
|
||||||
CoreTiming::EventType* et_DSP;
|
CoreTiming::EventType* et_DSP;
|
||||||
CoreTiming::EventType* et_IPC_HLE;
|
CoreTiming::EventType* et_IPC_HLE;
|
||||||
|
CoreTiming::EventType* et_GPU_sleeper;
|
||||||
|
CoreTiming::EventType* et_perf_tracker;
|
||||||
// PatchEngine updates every 1/60th of a second by default
|
// PatchEngine updates every 1/60th of a second by default
|
||||||
CoreTiming::EventType* et_PatchEngine;
|
CoreTiming::EventType* et_PatchEngine;
|
||||||
CoreTiming::EventType* et_Throttle;
|
|
||||||
|
|
||||||
u32 s_cpu_core_clock = 486000000u; // 486 mhz (its not 485, stop bugging me!)
|
u32 s_cpu_core_clock = 486000000u; // 486 mhz (its not 485, stop bugging me!)
|
||||||
|
|
||||||
|
@ -90,16 +92,6 @@ int s_ipc_hle_period;
|
||||||
// Custom RTC
|
// Custom RTC
|
||||||
s64 s_localtime_rtc_offset = 0;
|
s64 s_localtime_rtc_offset = 0;
|
||||||
|
|
||||||
// For each emulated milliseconds, what was the real time timestamp (excluding sleep time). This is
|
|
||||||
// a "special" ring buffer where we only need to read the first and last value.
|
|
||||||
std::array<u64, 1000> s_emu_to_real_time_ring_buffer;
|
|
||||||
size_t s_emu_to_real_time_index;
|
|
||||||
std::mutex s_emu_to_real_time_mutex;
|
|
||||||
|
|
||||||
// How much time was spent sleeping since the emulator started. Note: this does not need to be reset
|
|
||||||
// at initialization (or ever), since only the "derivative" of that value really matters.
|
|
||||||
u64 s_time_spent_sleeping;
|
|
||||||
|
|
||||||
// DSP/CPU timeslicing.
|
// DSP/CPU timeslicing.
|
||||||
void DSPCallback(Core::System& system, u64 userdata, s64 cyclesLate)
|
void DSPCallback(Core::System& system, u64 userdata, s64 cyclesLate)
|
||||||
{
|
{
|
||||||
|
@ -132,6 +124,26 @@ void IPC_HLE_UpdateCallback(Core::System& system, u64 userdata, s64 cyclesLate)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GPUSleepCallback(Core::System& system, u64 userdata, s64 cyclesLate)
|
||||||
|
{
|
||||||
|
auto& core_timing = system.GetCoreTiming();
|
||||||
|
system.GetFifo().GpuMaySleep();
|
||||||
|
|
||||||
|
// We want to call GpuMaySleep at about 1000hz so
|
||||||
|
// that the thread can sleep while not doing anything.
|
||||||
|
core_timing.ScheduleEvent(GetTicksPerSecond() / 1000 - cyclesLate, et_GPU_sleeper);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PerfTrackerCallback(Core::System& system, u64 userdata, s64 cyclesLate)
|
||||||
|
{
|
||||||
|
auto& core_timing = system.GetCoreTiming();
|
||||||
|
g_perf_metrics.CountPerformanceMarker(system, cyclesLate);
|
||||||
|
|
||||||
|
// Call this performance tracker again in 1/64th of a second.
|
||||||
|
// The tracker stores 256 values so this will let us summarize the last 4 seconds.
|
||||||
|
core_timing.ScheduleEvent(GetTicksPerSecond() / 64 - cyclesLate, et_perf_tracker);
|
||||||
|
}
|
||||||
|
|
||||||
void VICallback(Core::System& system, u64 userdata, s64 cyclesLate)
|
void VICallback(Core::System& system, u64 userdata, s64 cyclesLate)
|
||||||
{
|
{
|
||||||
auto& core_timing = system.GetCoreTiming();
|
auto& core_timing = system.GetCoreTiming();
|
||||||
|
@ -169,50 +181,6 @@ void PatchEngineCallback(Core::System& system, u64 userdata, s64 cycles_late)
|
||||||
|
|
||||||
system.GetCoreTiming().ScheduleEvent(next_schedule, et_PatchEngine, cycles_pruned);
|
system.GetCoreTiming().ScheduleEvent(next_schedule, et_PatchEngine, cycles_pruned);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ThrottleCallback(Core::System& system, u64 deadline, s64 cyclesLate)
|
|
||||||
{
|
|
||||||
// Allow the GPU thread to sleep. Setting this flag here limits the wakeups to 1 kHz.
|
|
||||||
system.GetFifo().GpuMaySleep();
|
|
||||||
|
|
||||||
const u64 time = Common::Timer::NowUs();
|
|
||||||
|
|
||||||
if (deadline == 0)
|
|
||||||
deadline = time;
|
|
||||||
|
|
||||||
const s64 diff = deadline - time;
|
|
||||||
const float emulation_speed = Config::Get(Config::MAIN_EMULATION_SPEED);
|
|
||||||
const bool frame_limiter = emulation_speed > 0.0f && !Core::GetIsThrottlerTempDisabled();
|
|
||||||
u32 next_event = GetTicksPerSecond() / 1000;
|
|
||||||
|
|
||||||
{
|
|
||||||
std::lock_guard lk(s_emu_to_real_time_mutex);
|
|
||||||
s_emu_to_real_time_ring_buffer[s_emu_to_real_time_index] = time - s_time_spent_sleeping;
|
|
||||||
s_emu_to_real_time_index =
|
|
||||||
(s_emu_to_real_time_index + 1) % s_emu_to_real_time_ring_buffer.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (frame_limiter)
|
|
||||||
{
|
|
||||||
if (emulation_speed != 1.0f)
|
|
||||||
next_event = u32(next_event * emulation_speed);
|
|
||||||
const s64 max_fallback = Config::Get(Config::MAIN_TIMING_VARIANCE) * 1000;
|
|
||||||
if (std::abs(diff) > max_fallback)
|
|
||||||
{
|
|
||||||
DEBUG_LOG_FMT(COMMON, "system too {}, {} us skipped", diff < 0 ? "slow" : "fast",
|
|
||||||
std::abs(diff) - max_fallback);
|
|
||||||
deadline = time - max_fallback;
|
|
||||||
}
|
|
||||||
else if (diff > 1000)
|
|
||||||
{
|
|
||||||
Common::SleepCurrentThread(diff / 1000);
|
|
||||||
s_time_spent_sleeping += Common::Timer::NowUs() - time;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// reschedule 1ms (possibly scaled by emulation_speed) into future on ppc
|
|
||||||
// add 1ms to the deadline
|
|
||||||
system.GetCoreTiming().ScheduleEvent(next_event - cyclesLate, et_Throttle, deadline + 1000);
|
|
||||||
}
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
u32 GetTicksPerSecond()
|
u32 GetTicksPerSecond()
|
||||||
|
@ -268,27 +236,7 @@ s64 GetLocalTimeRTCOffset()
|
||||||
|
|
||||||
double GetEstimatedEmulationPerformance()
|
double GetEstimatedEmulationPerformance()
|
||||||
{
|
{
|
||||||
u64 ts_now, ts_before; // In microseconds
|
return g_perf_metrics.GetMaxSpeed();
|
||||||
{
|
|
||||||
std::lock_guard lk(s_emu_to_real_time_mutex);
|
|
||||||
size_t index_now = s_emu_to_real_time_index == 0 ? s_emu_to_real_time_ring_buffer.size() - 1 :
|
|
||||||
s_emu_to_real_time_index - 1;
|
|
||||||
size_t index_before = s_emu_to_real_time_index;
|
|
||||||
|
|
||||||
ts_now = s_emu_to_real_time_ring_buffer[index_now];
|
|
||||||
ts_before = s_emu_to_real_time_ring_buffer[index_before];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ts_before == 0)
|
|
||||||
{
|
|
||||||
// Not enough data yet to estimate. We could technically provide an estimate based on a shorter
|
|
||||||
// time horizon, but it's not really worth it.
|
|
||||||
return 1.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
u64 delta_us = ts_now - ts_before;
|
|
||||||
double emulated_us = s_emu_to_real_time_ring_buffer.size() * 1000.0; // For each emulated ms.
|
|
||||||
return delta_us == 0 ? DBL_MAX : emulated_us / delta_us;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// split from Init to break a circular dependency between VideoInterface::Init and
|
// split from Init to break a circular dependency between VideoInterface::Init and
|
||||||
|
@ -345,20 +293,20 @@ void Init()
|
||||||
et_DSP = core_timing.RegisterEvent("DSPCallback", DSPCallback);
|
et_DSP = core_timing.RegisterEvent("DSPCallback", DSPCallback);
|
||||||
et_AudioDMA = core_timing.RegisterEvent("AudioDMACallback", AudioDMACallback);
|
et_AudioDMA = core_timing.RegisterEvent("AudioDMACallback", AudioDMACallback);
|
||||||
et_IPC_HLE = core_timing.RegisterEvent("IPC_HLE_UpdateCallback", IPC_HLE_UpdateCallback);
|
et_IPC_HLE = core_timing.RegisterEvent("IPC_HLE_UpdateCallback", IPC_HLE_UpdateCallback);
|
||||||
|
et_GPU_sleeper = core_timing.RegisterEvent("GPUSleeper", GPUSleepCallback);
|
||||||
|
et_perf_tracker = core_timing.RegisterEvent("PerfTracker", PerfTrackerCallback);
|
||||||
et_PatchEngine = core_timing.RegisterEvent("PatchEngine", PatchEngineCallback);
|
et_PatchEngine = core_timing.RegisterEvent("PatchEngine", PatchEngineCallback);
|
||||||
et_Throttle = core_timing.RegisterEvent("Throttle", ThrottleCallback);
|
|
||||||
|
|
||||||
|
core_timing.ScheduleEvent(0, et_perf_tracker);
|
||||||
|
core_timing.ScheduleEvent(0, et_GPU_sleeper);
|
||||||
core_timing.ScheduleEvent(VideoInterface::GetTicksPerHalfLine(), et_VI);
|
core_timing.ScheduleEvent(VideoInterface::GetTicksPerHalfLine(), et_VI);
|
||||||
core_timing.ScheduleEvent(0, et_DSP);
|
core_timing.ScheduleEvent(0, et_DSP);
|
||||||
core_timing.ScheduleEvent(GetAudioDMACallbackPeriod(), et_AudioDMA);
|
core_timing.ScheduleEvent(GetAudioDMACallbackPeriod(), et_AudioDMA);
|
||||||
core_timing.ScheduleEvent(0, et_Throttle, 0);
|
|
||||||
|
|
||||||
core_timing.ScheduleEvent(VideoInterface::GetTicksPerField(), et_PatchEngine);
|
core_timing.ScheduleEvent(VideoInterface::GetTicksPerField(), et_PatchEngine);
|
||||||
|
|
||||||
if (SConfig::GetInstance().bWii)
|
if (SConfig::GetInstance().bWii)
|
||||||
core_timing.ScheduleEvent(s_ipc_hle_period, et_IPC_HLE);
|
core_timing.ScheduleEvent(s_ipc_hle_period, et_IPC_HLE);
|
||||||
|
|
||||||
s_emu_to_real_time_ring_buffer.fill(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Shutdown()
|
void Shutdown()
|
||||||
|
|
|
@ -3,10 +3,14 @@
|
||||||
|
|
||||||
#include "VideoCommon/PerformanceMetrics.h"
|
#include "VideoCommon/PerformanceMetrics.h"
|
||||||
|
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
#include <implot.h>
|
#include <implot.h>
|
||||||
|
|
||||||
|
#include "Core/CoreTiming.h"
|
||||||
#include "Core/HW/VideoInterface.h"
|
#include "Core/HW/VideoInterface.h"
|
||||||
|
#include "Core/System.h"
|
||||||
#include "VideoCommon/VideoConfig.h"
|
#include "VideoCommon/VideoConfig.h"
|
||||||
|
|
||||||
PerformanceMetrics g_perf_metrics;
|
PerformanceMetrics g_perf_metrics;
|
||||||
|
@ -16,6 +20,10 @@ void PerformanceMetrics::Reset()
|
||||||
m_fps_counter.Reset();
|
m_fps_counter.Reset();
|
||||||
m_vps_counter.Reset();
|
m_vps_counter.Reset();
|
||||||
m_speed_counter.Reset();
|
m_speed_counter.Reset();
|
||||||
|
|
||||||
|
m_time_sleeping = DT::zero();
|
||||||
|
m_real_times.fill(Clock::now());
|
||||||
|
m_cpu_times.fill(Core::System::GetInstance().GetCoreTiming().GetCPUTimePoint(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
void PerformanceMetrics::CountFrame()
|
void PerformanceMetrics::CountFrame()
|
||||||
|
@ -29,6 +37,20 @@ void PerformanceMetrics::CountVBlank()
|
||||||
m_speed_counter.Count();
|
m_speed_counter.Count();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PerformanceMetrics::CountThrottleSleep(DT sleep)
|
||||||
|
{
|
||||||
|
std::unique_lock lock(m_time_lock);
|
||||||
|
m_time_sleeping += sleep;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PerformanceMetrics::CountPerformanceMarker(Core::System& system, s64 cyclesLate)
|
||||||
|
{
|
||||||
|
std::unique_lock lock(m_time_lock);
|
||||||
|
m_real_times[m_time_index] = Clock::now() - m_time_sleeping;
|
||||||
|
m_cpu_times[m_time_index] = system.GetCoreTiming().GetCPUTimePoint(cyclesLate);
|
||||||
|
m_time_index += 1;
|
||||||
|
}
|
||||||
|
|
||||||
double PerformanceMetrics::GetFPS() const
|
double PerformanceMetrics::GetFPS() const
|
||||||
{
|
{
|
||||||
return m_fps_counter.GetHzAvg();
|
return m_fps_counter.GetHzAvg();
|
||||||
|
@ -41,7 +63,14 @@ double PerformanceMetrics::GetVPS() const
|
||||||
|
|
||||||
double PerformanceMetrics::GetSpeed() const
|
double PerformanceMetrics::GetSpeed() const
|
||||||
{
|
{
|
||||||
return 100.0 * m_speed_counter.GetHzAvg() / VideoInterface::GetTargetRefreshRate();
|
return m_speed_counter.GetHzAvg() / VideoInterface::GetTargetRefreshRate();
|
||||||
|
}
|
||||||
|
|
||||||
|
double PerformanceMetrics::GetMaxSpeed() const
|
||||||
|
{
|
||||||
|
std::shared_lock lock(m_time_lock);
|
||||||
|
return DT_s(m_cpu_times[u8(m_time_index - 1)] - m_cpu_times[m_time_index]) /
|
||||||
|
DT_s(m_real_times[u8(m_time_index - 1)] - m_real_times[m_time_index]);
|
||||||
}
|
}
|
||||||
|
|
||||||
double PerformanceMetrics::GetLastSpeedDenominator() const
|
double PerformanceMetrics::GetLastSpeedDenominator() const
|
||||||
|
@ -49,7 +78,7 @@ double PerformanceMetrics::GetLastSpeedDenominator() const
|
||||||
return DT_s(m_speed_counter.GetLastRawDt()).count() * VideoInterface::GetTargetRefreshRate();
|
return DT_s(m_speed_counter.GetLastRawDt()).count() * VideoInterface::GetTargetRefreshRate();
|
||||||
}
|
}
|
||||||
|
|
||||||
void PerformanceMetrics::DrawImGuiStats(const float backbuffer_scale) const
|
void PerformanceMetrics::DrawImGuiStats(const float backbuffer_scale)
|
||||||
{
|
{
|
||||||
const float bg_alpha = 0.7f;
|
const float bg_alpha = 0.7f;
|
||||||
const auto imgui_flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoInputs |
|
const auto imgui_flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoInputs |
|
||||||
|
@ -65,9 +94,9 @@ void PerformanceMetrics::DrawImGuiStats(const float backbuffer_scale) const
|
||||||
float r = 0.0f, g = 1.0f, b = 1.0f;
|
float r = 0.0f, g = 1.0f, b = 1.0f;
|
||||||
if (g_ActiveConfig.bShowSpeedColors)
|
if (g_ActiveConfig.bShowSpeedColors)
|
||||||
{
|
{
|
||||||
r = 1.0 - (speed - 80.0) / 20.0;
|
r = 1.0 - (speed - 0.8) / 0.2;
|
||||||
g = speed / 80.0;
|
g = speed / 0.8;
|
||||||
b = (speed - 90.0) / 10.0;
|
b = (speed - 0.9) / 0.1;
|
||||||
}
|
}
|
||||||
|
|
||||||
const float window_padding = 8.f * backbuffer_scale;
|
const float window_padding = 8.f * backbuffer_scale;
|
||||||
|
@ -79,6 +108,8 @@ void PerformanceMetrics::DrawImGuiStats(const float backbuffer_scale) const
|
||||||
const float graph_height =
|
const float graph_height =
|
||||||
std::min(200.f * backbuffer_scale, ImGui::GetIO().DisplaySize.y - 85.f * backbuffer_scale);
|
std::min(200.f * backbuffer_scale, ImGui::GetIO().DisplaySize.y - 85.f * backbuffer_scale);
|
||||||
|
|
||||||
|
const bool stack_vertically = !g_ActiveConfig.bShowGraphs;
|
||||||
|
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.f);
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.f);
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 14.f * backbuffer_scale);
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 14.f * backbuffer_scale);
|
||||||
if (g_ActiveConfig.bShowGraphs)
|
if (g_ActiveConfig.bShowGraphs)
|
||||||
|
@ -93,40 +124,39 @@ void PerformanceMetrics::DrawImGuiStats(const float backbuffer_scale) const
|
||||||
|
|
||||||
if (ImGui::Begin("PerformanceGraphs", nullptr, imgui_flags))
|
if (ImGui::Begin("PerformanceGraphs", nullptr, imgui_flags))
|
||||||
{
|
{
|
||||||
const static int num_ticks = 17;
|
static constexpr std::size_t num_ticks = 17;
|
||||||
const static double tick_marks[num_ticks] = {0.0,
|
static constexpr std::array<double, num_ticks> tick_marks = {0.0,
|
||||||
1000.0 / 360.0,
|
1000.0 / 360.0,
|
||||||
1000.0 / 240.0,
|
1000.0 / 240.0,
|
||||||
1000.0 / 180.0,
|
1000.0 / 180.0,
|
||||||
1000.0 / 120.0,
|
1000.0 / 120.0,
|
||||||
1000.0 / 90.00,
|
1000.0 / 90.00,
|
||||||
1000.0 / 59.94,
|
1000.0 / 59.94,
|
||||||
1000.0 / 40.00,
|
1000.0 / 40.00,
|
||||||
1000.0 / 29.97,
|
1000.0 / 29.97,
|
||||||
1000.0 / 24.00,
|
1000.0 / 24.00,
|
||||||
1000.0 / 20.00,
|
1000.0 / 20.00,
|
||||||
1000.0 / 15.00,
|
1000.0 / 15.00,
|
||||||
1000.0 / 10.00,
|
1000.0 / 10.00,
|
||||||
1000.0 / 5.000,
|
1000.0 / 5.000,
|
||||||
1000.0 / 2.000,
|
1000.0 / 2.000,
|
||||||
1000.0,
|
1000.0,
|
||||||
2000.0};
|
2000.0};
|
||||||
|
|
||||||
const DT vblank_time = m_vps_counter.GetDtAvg() + m_vps_counter.GetDtStd();
|
const DT vblank_time = m_vps_counter.GetDtAvg() + 2 * m_vps_counter.GetDtStd();
|
||||||
const DT frame_time = m_fps_counter.GetDtAvg() + m_fps_counter.GetDtStd();
|
const DT frame_time = m_fps_counter.GetDtAvg() + 2 * m_fps_counter.GetDtStd();
|
||||||
const double target_max_time = DT_ms(vblank_time + frame_time).count();
|
const double target_max_time = DT_ms(vblank_time + frame_time).count();
|
||||||
const double a =
|
const double a =
|
||||||
std::max(0.0, 1.0 - std::exp(-4000.0 * m_vps_counter.GetLastRawDt().count() /
|
std::max(0.0, 1.0 - std::exp(-4.0 * (DT_s(m_vps_counter.GetLastRawDt()) /
|
||||||
DT_ms(m_vps_counter.GetSampleWindow()).count()));
|
DT_s(m_vps_counter.GetSampleWindow()))));
|
||||||
|
|
||||||
static double max_time = 0.0;
|
if (std::isfinite(m_graph_max_time))
|
||||||
if (std::isfinite(max_time))
|
m_graph_max_time += a * (target_max_time - m_graph_max_time);
|
||||||
max_time += a * (target_max_time - max_time);
|
|
||||||
else
|
else
|
||||||
max_time = target_max_time;
|
m_graph_max_time = target_max_time;
|
||||||
|
|
||||||
const double total_frame_time = std::max(DT_ms(m_fps_counter.GetSampleWindow()).count(),
|
const double total_frame_time =
|
||||||
DT_ms(m_vps_counter.GetSampleWindow()).count());
|
DT_ms(std::max(m_fps_counter.GetSampleWindow(), m_vps_counter.GetSampleWindow())).count();
|
||||||
|
|
||||||
if (ImPlot::BeginPlot("PerformanceGraphs", ImVec2(-1.0, -1.0),
|
if (ImPlot::BeginPlot("PerformanceGraphs", ImVec2(-1.0, -1.0),
|
||||||
ImPlotFlags_NoFrame | ImPlotFlags_NoTitle | ImPlotFlags_NoMenus))
|
ImPlotFlags_NoFrame | ImPlotFlags_NoTitle | ImPlotFlags_NoMenus))
|
||||||
|
@ -141,8 +171,8 @@ void PerformanceMetrics::DrawImGuiStats(const float backbuffer_scale) const
|
||||||
ImPlotAxisFlags_Lock | ImPlotAxisFlags_Invert | ImPlotAxisFlags_NoLabel |
|
ImPlotAxisFlags_Lock | ImPlotAxisFlags_Invert | ImPlotAxisFlags_NoLabel |
|
||||||
ImPlotAxisFlags_NoHighlight);
|
ImPlotAxisFlags_NoHighlight);
|
||||||
ImPlot::SetupAxisFormat(ImAxis_Y1, "%.1f");
|
ImPlot::SetupAxisFormat(ImAxis_Y1, "%.1f");
|
||||||
ImPlot::SetupAxisTicks(ImAxis_Y1, tick_marks, num_ticks);
|
ImPlot::SetupAxisTicks(ImAxis_Y1, tick_marks.data(), num_ticks);
|
||||||
ImPlot::SetupAxesLimits(0, total_frame_time, 0, max_time, ImGuiCond_Always);
|
ImPlot::SetupAxesLimits(0, total_frame_time, 0, m_graph_max_time, ImGuiCond_Always);
|
||||||
ImPlot::SetupLegend(ImPlotLocation_SouthEast, ImPlotLegendFlags_None);
|
ImPlot::SetupLegend(ImPlotLocation_SouthEast, ImPlotLegendFlags_None);
|
||||||
m_vps_counter.ImPlotPlotLines("V-Blank (ms)");
|
m_vps_counter.ImPlotPlotLines("V-Blank (ms)");
|
||||||
m_fps_counter.ImPlotPlotLines("Frame (ms)");
|
m_fps_counter.ImPlotPlotLines("Frame (ms)");
|
||||||
|
@ -155,14 +185,42 @@ void PerformanceMetrics::DrawImGuiStats(const float backbuffer_scale) const
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (g_ActiveConfig.bShowFPS || g_ActiveConfig.bShowFTimes)
|
if (g_ActiveConfig.bShowSpeed)
|
||||||
{
|
{
|
||||||
// Position in the top-right corner of the screen.
|
// Position in the top-right corner of the screen.
|
||||||
int count = g_ActiveConfig.bShowFPS + 2 * g_ActiveConfig.bShowFTimes;
|
float window_height = 47.f * backbuffer_scale;
|
||||||
|
|
||||||
ImGui::SetNextWindowPos(ImVec2(window_x, window_y), ImGuiCond_Always, ImVec2(1.0f, 0.0f));
|
ImGui::SetNextWindowPos(ImVec2(window_x, window_y), ImGuiCond_Always, ImVec2(1.0f, 0.0f));
|
||||||
ImGui::SetNextWindowSize(ImVec2(window_width, (12.f + 17.f * count) * backbuffer_scale));
|
ImGui::SetNextWindowSize(ImVec2(window_width, window_height));
|
||||||
ImGui::SetNextWindowBgAlpha(bg_alpha);
|
ImGui::SetNextWindowBgAlpha(bg_alpha);
|
||||||
window_x -= window_width + window_padding;
|
|
||||||
|
if (stack_vertically)
|
||||||
|
window_y += window_height + window_padding;
|
||||||
|
else
|
||||||
|
window_x -= window_width + window_padding;
|
||||||
|
|
||||||
|
if (ImGui::Begin("SpeedStats", nullptr, imgui_flags))
|
||||||
|
{
|
||||||
|
ImGui::TextColored(ImVec4(r, g, b, 1.0f), "Speed:%4.0lf%%", 100.0 * speed);
|
||||||
|
ImGui::TextColored(ImVec4(r, g, b, 1.0f), "Max:%6.0lf%%", 100.0 * GetMaxSpeed());
|
||||||
|
ImGui::End();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (g_ActiveConfig.bShowFPS || g_ActiveConfig.bShowFTimes)
|
||||||
|
{
|
||||||
|
int count = g_ActiveConfig.bShowFPS + 2 * g_ActiveConfig.bShowFTimes;
|
||||||
|
float window_height = (12.f + 17.f * count) * backbuffer_scale;
|
||||||
|
|
||||||
|
// Position in the top-right corner of the screen.
|
||||||
|
ImGui::SetNextWindowPos(ImVec2(window_x, window_y), ImGuiCond_Always, ImVec2(1.0f, 0.0f));
|
||||||
|
ImGui::SetNextWindowSize(ImVec2(window_width, window_height));
|
||||||
|
ImGui::SetNextWindowBgAlpha(bg_alpha);
|
||||||
|
|
||||||
|
if (stack_vertically)
|
||||||
|
window_y += window_height + window_padding;
|
||||||
|
else
|
||||||
|
window_x -= window_width + window_padding;
|
||||||
|
|
||||||
if (ImGui::Begin("FPSStats", nullptr, imgui_flags))
|
if (ImGui::Begin("FPSStats", nullptr, imgui_flags))
|
||||||
{
|
{
|
||||||
|
@ -181,12 +239,18 @@ void PerformanceMetrics::DrawImGuiStats(const float backbuffer_scale) const
|
||||||
|
|
||||||
if (g_ActiveConfig.bShowVPS || g_ActiveConfig.bShowVTimes)
|
if (g_ActiveConfig.bShowVPS || g_ActiveConfig.bShowVTimes)
|
||||||
{
|
{
|
||||||
// Position in the top-right corner of the screen.
|
|
||||||
int count = g_ActiveConfig.bShowVPS + 2 * g_ActiveConfig.bShowVTimes;
|
int count = g_ActiveConfig.bShowVPS + 2 * g_ActiveConfig.bShowVTimes;
|
||||||
|
float window_height = (12.f + 17.f * count) * backbuffer_scale;
|
||||||
|
|
||||||
|
// Position in the top-right corner of the screen.
|
||||||
ImGui::SetNextWindowPos(ImVec2(window_x, window_y), ImGuiCond_Always, ImVec2(1.0f, 0.0f));
|
ImGui::SetNextWindowPos(ImVec2(window_x, window_y), ImGuiCond_Always, ImVec2(1.0f, 0.0f));
|
||||||
ImGui::SetNextWindowSize(ImVec2(window_width, (12.f + 17.f * count) * backbuffer_scale));
|
ImGui::SetNextWindowSize(ImVec2(window_width, (12.f + 17.f * count) * backbuffer_scale));
|
||||||
ImGui::SetNextWindowBgAlpha(bg_alpha);
|
ImGui::SetNextWindowBgAlpha(bg_alpha);
|
||||||
window_x -= window_width + window_padding;
|
|
||||||
|
if (stack_vertically)
|
||||||
|
window_y += window_height + window_padding;
|
||||||
|
else
|
||||||
|
window_x -= window_width + window_padding;
|
||||||
|
|
||||||
if (ImGui::Begin("VPSStats", nullptr, imgui_flags))
|
if (ImGui::Begin("VPSStats", nullptr, imgui_flags))
|
||||||
{
|
{
|
||||||
|
@ -203,19 +267,5 @@ void PerformanceMetrics::DrawImGuiStats(const float backbuffer_scale) const
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (g_ActiveConfig.bShowSpeed)
|
|
||||||
{
|
|
||||||
// Position in the top-right corner of the screen.
|
|
||||||
ImGui::SetNextWindowPos(ImVec2(window_x, window_y), ImGuiCond_Always, ImVec2(1.0f, 0.0f));
|
|
||||||
ImGui::SetNextWindowSize(ImVec2(window_width, 29.f * backbuffer_scale));
|
|
||||||
ImGui::SetNextWindowBgAlpha(bg_alpha);
|
|
||||||
|
|
||||||
if (ImGui::Begin("SpeedStats", nullptr, imgui_flags))
|
|
||||||
{
|
|
||||||
ImGui::TextColored(ImVec4(r, g, b, 1.0f), "Speed:%4.0lf%%", speed);
|
|
||||||
ImGui::End();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::PopStyleVar(2);
|
ImGui::PopStyleVar(2);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,8 +3,17 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <shared_mutex>
|
||||||
|
|
||||||
|
#include "Common/CommonTypes.h"
|
||||||
#include "VideoCommon/PerformanceTracker.h"
|
#include "VideoCommon/PerformanceTracker.h"
|
||||||
|
|
||||||
|
namespace Core
|
||||||
|
{
|
||||||
|
class System;
|
||||||
|
}
|
||||||
|
|
||||||
class PerformanceMetrics
|
class PerformanceMetrics
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
@ -21,20 +30,33 @@ public:
|
||||||
void CountFrame();
|
void CountFrame();
|
||||||
void CountVBlank();
|
void CountVBlank();
|
||||||
|
|
||||||
|
void CountThrottleSleep(DT sleep);
|
||||||
|
void CountPerformanceMarker(Core::System& system, s64 cyclesLate);
|
||||||
|
|
||||||
// Getter Functions
|
// Getter Functions
|
||||||
double GetFPS() const;
|
double GetFPS() const;
|
||||||
double GetVPS() const;
|
double GetVPS() const;
|
||||||
double GetSpeed() const;
|
double GetSpeed() const;
|
||||||
|
double GetMaxSpeed() const;
|
||||||
|
|
||||||
double GetLastSpeedDenominator() const;
|
double GetLastSpeedDenominator() const;
|
||||||
|
|
||||||
// ImGui Functions
|
// ImGui Functions
|
||||||
void DrawImGuiStats(const float backbuffer_scale) const;
|
void DrawImGuiStats(const float backbuffer_scale);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
PerformanceTracker m_fps_counter{"render_times.txt"};
|
PerformanceTracker m_fps_counter{"render_times.txt"};
|
||||||
PerformanceTracker m_vps_counter{"vblank_times.txt"};
|
PerformanceTracker m_vps_counter{"vblank_times.txt"};
|
||||||
PerformanceTracker m_speed_counter{std::nullopt, 500000};
|
PerformanceTracker m_speed_counter{std::nullopt, 500000};
|
||||||
|
|
||||||
|
double m_graph_max_time = 0.0;
|
||||||
|
|
||||||
|
mutable std::shared_mutex m_time_lock;
|
||||||
|
|
||||||
|
u8 m_time_index = 0;
|
||||||
|
std::array<TimePoint, 256> m_real_times;
|
||||||
|
std::array<TimePoint, 256> m_cpu_times;
|
||||||
|
DT m_time_sleeping;
|
||||||
};
|
};
|
||||||
|
|
||||||
extern PerformanceMetrics g_perf_metrics;
|
extern PerformanceMetrics g_perf_metrics;
|
||||||
|
|
Loading…
Reference in New Issue