perf_stats.cpp
1 // Copyright 2017 Citra Emulator Project 2 // Licensed under GPLv2 or any later version 3 // Refer to the license.txt file included. 4 5 #include <algorithm> 6 #include <chrono> 7 #include <iterator> 8 #include <mutex> 9 #include <numeric> 10 #include <sstream> 11 #include <thread> 12 #include <fmt/chrono.h> 13 #include <fmt/format.h> 14 #include "common/file_util.h" 15 #include "common/settings.h" 16 #include "core/core_timing.h" 17 #include "core/perf_stats.h" 18 #include "video_core/gpu.h" 19 20 using namespace std::chrono_literals; 21 using DoubleSecs = std::chrono::duration<double, std::chrono::seconds::period>; 22 using std::chrono::duration_cast; 23 using std::chrono::microseconds; 24 25 // Purposefully ignore the first five frames, as there's a significant amount of overhead in 26 // booting that we shouldn't account for 27 constexpr std::size_t IgnoreFrames = 5; 28 29 namespace Core { 30 31 PerfStats::PerfStats(u64 title_id) : title_id(title_id) {} 32 33 PerfStats::~PerfStats() { 34 if (!Settings::values.record_frame_times || title_id == 0) { 35 return; 36 } 37 38 const std::time_t t = std::time(nullptr); 39 std::ostringstream stream; 40 std::copy(perf_history.begin() + IgnoreFrames, perf_history.begin() + current_index, 41 std::ostream_iterator<double>(stream, "\n")); 42 const std::string& path = FileUtil::GetUserPath(FileUtil::UserPath::LogDir); 43 // %F Date format expanded is "%Y-%m-%d" 44 const std::string filename = 45 fmt::format("{}/{:%F-%H-%M}_{:016X}.csv", path, *std::localtime(&t), title_id); 46 FileUtil::IOFile file(filename, "w"); 47 file.WriteString(stream.str()); 48 } 49 50 void PerfStats::BeginSystemFrame() { 51 std::scoped_lock lock{object_mutex}; 52 53 frame_begin = Clock::now(); 54 } 55 56 void PerfStats::EndSystemFrame() { 57 std::scoped_lock lock{object_mutex}; 58 59 auto frame_end = Clock::now(); 60 const auto frame_time = frame_end - frame_begin; 61 if (current_index < perf_history.size()) { 62 perf_history[current_index++] = 63 std::chrono::duration<double, std::milli>(frame_time).count(); 64 } 65 accumulated_frametime += frame_time; 66 system_frames += 1; 67 68 previous_frame_length = frame_end - previous_frame_end; 69 previous_frame_end = frame_end; 70 } 71 72 void PerfStats::EndGameFrame() { 73 std::scoped_lock lock{object_mutex}; 74 75 game_frames += 1; 76 } 77 78 double PerfStats::GetMeanFrametime() const { 79 std::scoped_lock lock{object_mutex}; 80 81 if (current_index <= IgnoreFrames) { 82 return 0; 83 } 84 85 const double sum = std::accumulate(perf_history.begin() + IgnoreFrames, 86 perf_history.begin() + current_index, 0.0); 87 return sum / static_cast<double>(current_index - IgnoreFrames); 88 } 89 90 PerfStats::Results PerfStats::GetAndResetStats(microseconds current_system_time_us) { 91 std::scoped_lock lock{object_mutex}; 92 93 const auto now = Clock::now(); 94 // Walltime elapsed since stats were reset 95 const auto interval = duration_cast<DoubleSecs>(now - reset_point).count(); 96 97 const auto system_us_per_second = (current_system_time_us - reset_point_system_us) / interval; 98 99 last_stats.system_fps = static_cast<double>(system_frames) / interval; 100 last_stats.game_fps = static_cast<double>(game_frames) / interval; 101 last_stats.frametime = duration_cast<DoubleSecs>(accumulated_frametime).count() / 102 static_cast<double>(system_frames); 103 last_stats.emulation_speed = system_us_per_second.count() / 1'000'000.0; 104 105 // Reset counters 106 reset_point = now; 107 reset_point_system_us = current_system_time_us; 108 accumulated_frametime = Clock::duration::zero(); 109 system_frames = 0; 110 game_frames = 0; 111 112 return last_stats; 113 } 114 115 PerfStats::Results PerfStats::GetLastStats() { 116 std::scoped_lock lock{object_mutex}; 117 118 return last_stats; 119 } 120 121 double PerfStats::GetLastFrameTimeScale() const { 122 std::scoped_lock lock{object_mutex}; 123 124 constexpr double FRAME_LENGTH = 1.0 / SCREEN_REFRESH_RATE; 125 return duration_cast<DoubleSecs>(previous_frame_length).count() / FRAME_LENGTH; 126 } 127 128 void FrameLimiter::WaitOnce() { 129 if (frame_advancing_enabled) { 130 // Frame advancing is enabled: wait on event instead of doing framelimiting 131 frame_advance_event.Wait(); 132 frame_advance_event.Reset(); 133 } 134 } 135 136 void FrameLimiter::DoFrameLimiting(microseconds current_system_time_us) { 137 if (frame_advancing_enabled) { 138 // Frame advancing is enabled: wait on event instead of doing framelimiting 139 frame_advance_event.Wait(); 140 frame_advance_event.Reset(); 141 return; 142 } 143 144 auto now = Clock::now(); 145 double sleep_scale = Settings::values.frame_limit.GetValue() / 100.0; 146 147 if (Settings::values.frame_limit.GetValue() == 0) { 148 return; 149 } 150 151 // Max lag caused by slow frames. Shouldn't be more than the length of a frame at the current 152 // speed percent or it will clamp too much and prevent this from properly limiting to that 153 // percent. High values means it'll take longer after a slow frame to recover and start limiting 154 const microseconds max_lag_time_us = duration_cast<microseconds>( 155 std::chrono::duration<double, std::chrono::microseconds::period>(25ms / sleep_scale)); 156 frame_limiting_delta_err += duration_cast<microseconds>( 157 std::chrono::duration<double, std::chrono::microseconds::period>( 158 (current_system_time_us - previous_system_time_us) / sleep_scale)); 159 frame_limiting_delta_err -= duration_cast<microseconds>(now - previous_walltime); 160 frame_limiting_delta_err = 161 std::clamp(frame_limiting_delta_err, -max_lag_time_us, max_lag_time_us); 162 163 if (frame_limiting_delta_err > microseconds::zero()) { 164 std::this_thread::sleep_for(frame_limiting_delta_err); 165 auto now_after_sleep = Clock::now(); 166 frame_limiting_delta_err -= duration_cast<microseconds>(now_after_sleep - now); 167 now = now_after_sleep; 168 } 169 170 previous_system_time_us = current_system_time_us; 171 previous_walltime = now; 172 } 173 174 bool FrameLimiter::IsFrameAdvancing() const { 175 return frame_advancing_enabled; 176 } 177 178 void FrameLimiter::SetFrameAdvancing(bool value) { 179 const bool was_enabled = frame_advancing_enabled.exchange(value); 180 if (was_enabled && !value) { 181 // Set the event to let emulation continue 182 frame_advance_event.Set(); 183 } 184 } 185 186 void FrameLimiter::AdvanceFrame() { 187 frame_advance_event.Set(); 188 } 189 190 } // namespace Core