perf_stats: Rework FPS counter to be more accurate
The FPS counter was based on metrics in the nvdisp swapbuffers call. This metric would be accurate if the gpu thread/renderer were synchronous with the nvdisp service, but that's no longer the case. This commit moves the frame counting responsibility onto the concrete renderers after their frame draw calls. Resulting in more meaningful metrics. The displayed FPS is now made up of the average framerate between the previous and most recent update, in order to avoid distracting FPS counter updates when framerate is oscillating between close values. The status bar update frequency was also changed from 2 seconds to 500ms.
This commit is contained in:
parent
904584e4ba
commit
5bef54618a
10 changed files with 26 additions and 14 deletions
|
@ -289,7 +289,8 @@ struct System::Impl {
|
||||||
|
|
||||||
telemetry_session->AddField(performance, "Shutdown_EmulationSpeed",
|
telemetry_session->AddField(performance, "Shutdown_EmulationSpeed",
|
||||||
perf_results.emulation_speed * 100.0);
|
perf_results.emulation_speed * 100.0);
|
||||||
telemetry_session->AddField(performance, "Shutdown_Framerate", perf_results.game_fps);
|
telemetry_session->AddField(performance, "Shutdown_Framerate",
|
||||||
|
perf_results.average_game_fps);
|
||||||
telemetry_session->AddField(performance, "Shutdown_Frametime",
|
telemetry_session->AddField(performance, "Shutdown_Frametime",
|
||||||
perf_results.frametime * 1000.0);
|
perf_results.frametime * 1000.0);
|
||||||
telemetry_session->AddField(performance, "Mean_Frametime_MS",
|
telemetry_session->AddField(performance, "Mean_Frametime_MS",
|
||||||
|
|
|
@ -52,7 +52,6 @@ void nvdisp_disp0::flip(u32 buffer_handle, u32 offset, u32 format, u32 width, u3
|
||||||
addr, offset, width, height, stride, static_cast<PixelFormat>(format),
|
addr, offset, width, height, stride, static_cast<PixelFormat>(format),
|
||||||
transform, crop_rect};
|
transform, crop_rect};
|
||||||
|
|
||||||
system.GetPerfStats().EndGameFrame();
|
|
||||||
system.GetPerfStats().EndSystemFrame();
|
system.GetPerfStats().EndSystemFrame();
|
||||||
system.GPU().SwapBuffers(&framebuffer);
|
system.GPU().SwapBuffers(&framebuffer);
|
||||||
system.FrameLimiter().DoFrameLimiting(system.CoreTiming().GetGlobalTimeUs());
|
system.FrameLimiter().DoFrameLimiting(system.CoreTiming().GetGlobalTimeUs());
|
||||||
|
|
|
@ -69,9 +69,7 @@ void PerfStats::EndSystemFrame() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void PerfStats::EndGameFrame() {
|
void PerfStats::EndGameFrame() {
|
||||||
std::lock_guard lock{object_mutex};
|
game_frames.fetch_add(1, std::memory_order_relaxed);
|
||||||
|
|
||||||
game_frames += 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
double PerfStats::GetMeanFrametime() const {
|
double PerfStats::GetMeanFrametime() const {
|
||||||
|
@ -94,10 +92,11 @@ PerfStatsResults PerfStats::GetAndResetStats(microseconds current_system_time_us
|
||||||
const auto interval = duration_cast<DoubleSecs>(now - reset_point).count();
|
const auto interval = duration_cast<DoubleSecs>(now - reset_point).count();
|
||||||
|
|
||||||
const auto system_us_per_second = (current_system_time_us - reset_point_system_us) / interval;
|
const auto system_us_per_second = (current_system_time_us - reset_point_system_us) / interval;
|
||||||
|
const auto current_frames = static_cast<double>(game_frames.load(std::memory_order_relaxed));
|
||||||
|
const auto current_fps = current_frames / interval;
|
||||||
const PerfStatsResults results{
|
const PerfStatsResults results{
|
||||||
.system_fps = static_cast<double>(system_frames) / interval,
|
.system_fps = static_cast<double>(system_frames) / interval,
|
||||||
.game_fps = static_cast<double>(game_frames) / interval,
|
.average_game_fps = (current_fps + previous_fps) / 2.0,
|
||||||
.frametime = duration_cast<DoubleSecs>(accumulated_frametime).count() /
|
.frametime = duration_cast<DoubleSecs>(accumulated_frametime).count() /
|
||||||
static_cast<double>(system_frames),
|
static_cast<double>(system_frames),
|
||||||
.emulation_speed = system_us_per_second.count() / 1'000'000.0,
|
.emulation_speed = system_us_per_second.count() / 1'000'000.0,
|
||||||
|
@ -108,7 +107,8 @@ PerfStatsResults PerfStats::GetAndResetStats(microseconds current_system_time_us
|
||||||
reset_point_system_us = current_system_time_us;
|
reset_point_system_us = current_system_time_us;
|
||||||
accumulated_frametime = Clock::duration::zero();
|
accumulated_frametime = Clock::duration::zero();
|
||||||
system_frames = 0;
|
system_frames = 0;
|
||||||
game_frames = 0;
|
game_frames.store(0, std::memory_order_relaxed);
|
||||||
|
previous_fps = current_fps;
|
||||||
|
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
|
#include <atomic>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
@ -15,8 +16,8 @@ namespace Core {
|
||||||
struct PerfStatsResults {
|
struct PerfStatsResults {
|
||||||
/// System FPS (LCD VBlanks) in Hz
|
/// System FPS (LCD VBlanks) in Hz
|
||||||
double system_fps;
|
double system_fps;
|
||||||
/// Game FPS (GSP frame submissions) in Hz
|
/// Average game FPS (GPU frame renders) in Hz
|
||||||
double game_fps;
|
double average_game_fps;
|
||||||
/// Walltime per system frame, in seconds, excluding any waits
|
/// Walltime per system frame, in seconds, excluding any waits
|
||||||
double frametime;
|
double frametime;
|
||||||
/// Ratio of walltime / emulated time elapsed
|
/// Ratio of walltime / emulated time elapsed
|
||||||
|
@ -72,7 +73,7 @@ private:
|
||||||
/// Cumulative number of system frames (LCD VBlanks) presented since last reset
|
/// Cumulative number of system frames (LCD VBlanks) presented since last reset
|
||||||
u32 system_frames = 0;
|
u32 system_frames = 0;
|
||||||
/// Cumulative number of game frames (GSP frame submissions) since last reset
|
/// Cumulative number of game frames (GSP frame submissions) since last reset
|
||||||
u32 game_frames = 0;
|
std::atomic<u32> game_frames = 0;
|
||||||
|
|
||||||
/// Point when the previous system frame ended
|
/// Point when the previous system frame ended
|
||||||
Clock::time_point previous_frame_end = reset_point;
|
Clock::time_point previous_frame_end = reset_point;
|
||||||
|
@ -80,6 +81,8 @@ private:
|
||||||
Clock::time_point frame_begin = reset_point;
|
Clock::time_point frame_begin = reset_point;
|
||||||
/// Total visible duration (including frame-limiting, etc.) of the previous system frame
|
/// Total visible duration (including frame-limiting, etc.) of the previous system frame
|
||||||
Clock::duration previous_frame_length = Clock::duration::zero();
|
Clock::duration previous_frame_length = Clock::duration::zero();
|
||||||
|
/// Previously computed fps
|
||||||
|
double previous_fps = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
class FrameLimiter {
|
class FrameLimiter {
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
#include "core/frontend/emu_window.h"
|
#include "core/frontend/emu_window.h"
|
||||||
#include "core/hardware_interrupt_manager.h"
|
#include "core/hardware_interrupt_manager.h"
|
||||||
#include "core/memory.h"
|
#include "core/memory.h"
|
||||||
|
#include "core/perf_stats.h"
|
||||||
#include "video_core/engines/fermi_2d.h"
|
#include "video_core/engines/fermi_2d.h"
|
||||||
#include "video_core/engines/kepler_compute.h"
|
#include "video_core/engines/kepler_compute.h"
|
||||||
#include "video_core/engines/kepler_memory.h"
|
#include "video_core/engines/kepler_memory.h"
|
||||||
|
@ -191,6 +192,10 @@ u64 GPU::GetTicks() const {
|
||||||
return nanoseconds_num * gpu_ticks_num + (nanoseconds_rem * gpu_ticks_num) / gpu_ticks_den;
|
return nanoseconds_num * gpu_ticks_num + (nanoseconds_rem * gpu_ticks_num) / gpu_ticks_den;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GPU::RendererFrameEndNotify() {
|
||||||
|
system.GetPerfStats().EndGameFrame();
|
||||||
|
}
|
||||||
|
|
||||||
void GPU::FlushCommands() {
|
void GPU::FlushCommands() {
|
||||||
rasterizer->FlushCommands();
|
rasterizer->FlushCommands();
|
||||||
}
|
}
|
||||||
|
|
|
@ -247,6 +247,8 @@ public:
|
||||||
return use_nvdec;
|
return use_nvdec;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void RendererFrameEndNotify();
|
||||||
|
|
||||||
enum class FenceOperation : u32 {
|
enum class FenceOperation : u32 {
|
||||||
Acquire = 0,
|
Acquire = 0,
|
||||||
Increment = 1,
|
Increment = 1,
|
||||||
|
|
|
@ -155,6 +155,7 @@ void RendererOpenGL::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) {
|
||||||
|
|
||||||
++m_current_frame;
|
++m_current_frame;
|
||||||
|
|
||||||
|
gpu.RendererFrameEndNotify();
|
||||||
rasterizer.TickFrame();
|
rasterizer.TickFrame();
|
||||||
|
|
||||||
context->SwapBuffers();
|
context->SwapBuffers();
|
||||||
|
|
|
@ -154,6 +154,7 @@ void RendererVulkan::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) {
|
||||||
if (swapchain.Present(render_semaphore)) {
|
if (swapchain.Present(render_semaphore)) {
|
||||||
blit_screen.Recreate();
|
blit_screen.Recreate();
|
||||||
}
|
}
|
||||||
|
gpu.RendererFrameEndNotify();
|
||||||
rasterizer.TickFrame();
|
rasterizer.TickFrame();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1377,7 +1377,7 @@ void GMainWindow::BootGame(const QString& filename, std::size_t program_index) {
|
||||||
game_list->hide();
|
game_list->hide();
|
||||||
game_list_placeholder->hide();
|
game_list_placeholder->hide();
|
||||||
}
|
}
|
||||||
status_bar_update_timer.start(2000);
|
status_bar_update_timer.start(500);
|
||||||
async_status_button->setDisabled(true);
|
async_status_button->setDisabled(true);
|
||||||
multicore_status_button->setDisabled(true);
|
multicore_status_button->setDisabled(true);
|
||||||
renderer_status_button->setDisabled(true);
|
renderer_status_button->setDisabled(true);
|
||||||
|
@ -2797,7 +2797,7 @@ void GMainWindow::UpdateStatusBar() {
|
||||||
} else {
|
} else {
|
||||||
emu_speed_label->setText(tr("Speed: %1%").arg(results.emulation_speed * 100.0, 0, 'f', 0));
|
emu_speed_label->setText(tr("Speed: %1%").arg(results.emulation_speed * 100.0, 0, 'f', 0));
|
||||||
}
|
}
|
||||||
game_fps_label->setText(tr("Game: %1 FPS").arg(results.game_fps, 0, 'f', 0));
|
game_fps_label->setText(tr("Game: %1 FPS").arg(results.average_game_fps, 0, 'f', 0));
|
||||||
emu_frametime_label->setText(tr("Frame: %1 ms").arg(results.frametime * 1000.0, 0, 'f', 2));
|
emu_frametime_label->setText(tr("Frame: %1 ms").arg(results.frametime * 1000.0, 0, 'f', 2));
|
||||||
|
|
||||||
emu_speed_label->setVisible(!Settings::values.use_multi_core.GetValue());
|
emu_speed_label->setVisible(!Settings::values.use_multi_core.GetValue());
|
||||||
|
|
|
@ -215,7 +215,7 @@ void EmuWindow_SDL2::WaitEvent() {
|
||||||
const auto results = Core::System::GetInstance().GetAndResetPerfStats();
|
const auto results = Core::System::GetInstance().GetAndResetPerfStats();
|
||||||
const auto title =
|
const auto title =
|
||||||
fmt::format("yuzu {} | {}-{} | FPS: {:.0f} ({:.0f}%)", Common::g_build_fullname,
|
fmt::format("yuzu {} | {}-{} | FPS: {:.0f} ({:.0f}%)", Common::g_build_fullname,
|
||||||
Common::g_scm_branch, Common::g_scm_desc, results.game_fps,
|
Common::g_scm_branch, Common::g_scm_desc, results.average_game_fps,
|
||||||
results.emulation_speed * 100.0);
|
results.emulation_speed * 100.0);
|
||||||
SDL_SetWindowTitle(render_window, title.c_str());
|
SDL_SetWindowTitle(render_window, title.c_str());
|
||||||
last_time = current_time;
|
last_time = current_time;
|
||||||
|
|
Loading…
Reference in a new issue