From 3d04b86ad2e52cbb6e577ed2bc4885e9a8373d18 Mon Sep 17 00:00:00 2001 From: PabloMK7 Date: Fri, 20 Jun 2025 19:26:12 +0200 Subject: [PATCH] Add "SWP" frame time information (#1173) --- .../citra_emu/fragments/EmulationFragment.kt | 6 +++-- src/android/app/src/main/jni/native.cpp | 9 ++++--- src/citra_qt/citra_qt.cpp | 3 ++- src/core/perf_stats.cpp | 26 ++++++++++++++----- src/core/perf_stats.h | 16 +++++++----- .../renderer_opengl/renderer_opengl.cpp | 2 ++ .../renderer_software/renderer_software.cpp | 4 ++- .../renderer_vulkan/renderer_vulkan.cpp | 2 ++ 8 files changed, 46 insertions(+), 22 deletions(-) diff --git a/src/android/app/src/main/java/org/citra/citra_emu/fragments/EmulationFragment.kt b/src/android/app/src/main/java/org/citra/citra_emu/fragments/EmulationFragment.kt index 027c73b28..18cf2c4ed 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/fragments/EmulationFragment.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/fragments/EmulationFragment.kt @@ -1183,7 +1183,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram val TIME_SVC = 4 val TIME_IPC = 5 val TIME_GPU = 6 - val TIME_REM = 7 + val TIME_SWAP = 7 + val TIME_REM = 8 perfStatsUpdater = Runnable { val sb = StringBuilder() val perfStats = NativeLibrary.getPerfStats() @@ -1197,9 +1198,10 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram if (sb.isNotEmpty()) sb.append(dividerString) sb.append( String.format( - "Frame:\u00A0%.1fms (GPU:\u00A0%.1fms IPC:\u00A0%.1fms SVC:\u00A0%.1fms Rem:\u00A0%.1fms)", + "Frame:\u00A0%.1fms (GPU: [CMD:\u00A0%.1fms SWP:\u00A0%.1fms] IPC:\u00A0%.1fms SVC:\u00A0%.1fms Rem:\u00A0%.1fms)", (perfStats[FRAMETIME] * 1000.0f).toFloat(), (perfStats[TIME_GPU] * 1000.0f).toFloat(), + (perfStats[TIME_SWAP] * 1000.0f).toFloat(), (perfStats[TIME_IPC] * 1000.0f).toFloat(), (perfStats[TIME_SVC] * 1000.0f).toFloat(), (perfStats[TIME_REM] * 1000.0f).toFloat(), diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp index ac018bea6..71c40a1fa 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp @@ -644,18 +644,19 @@ void Java_org_citra_citra_1emu_NativeLibrary_reloadSettings([[maybe_unused]] JNI jdoubleArray Java_org_citra_citra_1emu_NativeLibrary_getPerfStats(JNIEnv* env, [[maybe_unused]] jobject obj) { auto& core = Core::System::GetInstance(); - jdoubleArray j_stats = env->NewDoubleArray(8); + jdoubleArray j_stats = env->NewDoubleArray(9); if (core.IsPoweredOn()) { auto results = core.GetAndResetPerfStats(); // Converting the structure into an array makes it easier to pass it to the frontend - double stats[8] = {results.system_fps, results.game_fps, + double stats[9] = {results.system_fps, results.game_fps, results.emulation_speed, results.time_vblank_interval, results.time_hle_svc, results.time_hle_ipc, - results.time_gpu, results.time_remaining}; + results.time_gpu, results.time_swap, + results.time_remaining}; - env->SetDoubleArrayRegion(j_stats, 0, 8, stats); + env->SetDoubleArrayRegion(j_stats, 0, 9, stats); } return j_stats; diff --git a/src/citra_qt/citra_qt.cpp b/src/citra_qt/citra_qt.cpp index 02d766b84..5ff931ed9 100644 --- a/src/citra_qt/citra_qt.cpp +++ b/src/citra_qt/citra_qt.cpp @@ -3232,9 +3232,10 @@ void GMainWindow::UpdateStatusBar() { game_fps_label->setText(tr("App: %1 FPS").arg(results.game_fps, 0, 'f', 0)); if (UISettings::values.show_advanced_frametime_info) { emu_frametime_label->setText( - tr("Frame: %1 ms (GPU: %2 ms, IPC: %3 ms, SVC: %4 ms, Rem: %5 ms)") + tr("Frame: %1 ms (GPU: [CMD: %2 ms, SWP: %3 ms], IPC: %4 ms, SVC: %5 ms, Rem: %6 ms)") .arg(results.time_vblank_interval * 1000.0, 2, 'f', 2) .arg(results.time_gpu * 1000.0, 2, 'f', 2) + .arg(results.time_swap * 1000.0, 2, 'f', 2) .arg(results.time_hle_ipc * 1000.0, 2, 'f', 2) .arg(results.time_hle_svc * 1000.0, 2, 'f', 2) .arg(results.time_remaining * 1000.0, 2, 'f', 2)); diff --git a/src/core/perf_stats.cpp b/src/core/perf_stats.cpp index 1a14b620d..8d7724003 100644 --- a/src/core/perf_stats.cpp +++ b/src/core/perf_stats.cpp @@ -72,6 +72,14 @@ void PerfStats::EndGPUProcessing() { accumulated_gpu_time += (Clock::now() - start_gpu_time); } +void PerfStats::StartSwap() { + start_swap_time = Clock::now(); +} + +void PerfStats::EndSwap() { + accumulated_swap_time += (Clock::now() - start_swap_time); +} + void PerfStats::BeginSystemFrame() { std::scoped_lock lock{object_mutex}; @@ -143,11 +151,17 @@ PerfStats::Results PerfStats::GetAndResetStats(microseconds current_system_time_ last_stats.time_gpu = system_frames ? (duration_cast(accumulated_gpu_time).count() / static_cast(system_frames)) : 0; + last_stats.time_swap = system_frames + ? (duration_cast(accumulated_swap_time).count() / + static_cast(system_frames)) + : 0; + last_stats.time_remaining = - system_frames - ? (duration_cast(accumulated_frametime - accumulated_svc_time).count() / - static_cast(system_frames)) - : 0; + system_frames ? (duration_cast((accumulated_frametime - accumulated_svc_time) - + accumulated_swap_time) + .count() / + static_cast(system_frames)) + : 0; last_stats.emulation_speed = system_us_per_second.count() / 1'000'000.0; last_stats.artic_transmitted = static_cast(artic_transmitted) / interval; last_stats.artic_events.raw = artic_events.raw | prev_artic_event.raw; @@ -158,11 +172,9 @@ PerfStats::Results PerfStats::GetAndResetStats(microseconds current_system_time_ accumulated_frametime = Clock::duration::zero(); system_frames = 0; accumulated_svc_time = Clock::duration::zero(); - svc_processing_times = 0; accumulated_ipc_time = Clock::duration::zero(); - ipc_processing_times = 0; accumulated_gpu_time = Clock::duration::zero(); - gpu_processing_times = 0; + accumulated_swap_time = Clock::duration::zero(); game_frames = 0; artic_transmitted = 0; prev_artic_event.raw &= artic_events.raw; diff --git a/src/core/perf_stats.h b/src/core/perf_stats.h index 93c7603ae..de358de66 100644 --- a/src/core/perf_stats.h +++ b/src/core/perf_stats.h @@ -62,7 +62,10 @@ public: double time_hle_ipc; // Walltime in seconds of the vblank interval spent in GPU command processing double time_gpu; - // Walltime in seconds of teh vblank interval spent in other operations + // Walltime in seconds of the vblank interval spent in Renderer::SwapBuffers (includes + // waiting for host GPU to finish) + double time_swap; + // Walltime in seconds of the vblank interval spent in other operations double time_remaining; /// Ratio of walltime / emulated time elapsed double emulation_speed; @@ -78,6 +81,8 @@ public: void EndIPCProcessing(); void BeginGPUProcessing(); void EndGPUProcessing(); + void StartSwap(); + void EndSwap(); void BeginSystemFrame(); void EndSystemFrame(); void EndGameFrame(); @@ -155,19 +160,16 @@ private: Clock::duration previous_previous_frame_length = Clock::duration::zero(); Clock::time_point start_svc_time = reset_point; - Clock::duration accumulated_svc_time = Clock::duration::zero(); - u32 svc_processing_times = 0; Clock::time_point start_ipc_time = reset_point; - Clock::duration accumulated_ipc_time = Clock::duration::zero(); - u32 ipc_processing_times = 0; Clock::time_point start_gpu_time = reset_point; - Clock::duration accumulated_gpu_time = Clock::duration::zero(); - u32 gpu_processing_times = 0; + + Clock::time_point start_swap_time = reset_point; + Clock::duration accumulated_swap_time = Clock::duration::zero(); /// Last recorded performance statistics. Results last_stats; diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp index 03c4ce3e3..24e946740 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp @@ -89,6 +89,7 @@ RendererOpenGL::RendererOpenGL(Core::System& system, Pica::PicaCore& pica_, RendererOpenGL::~RendererOpenGL() = default; void RendererOpenGL::SwapBuffers() { + system.perf_stats->StartSwap(); // Maintain the rasterizer's state as a priority OpenGLState prev_state = OpenGLState::GetCurState(); state.Apply(); @@ -115,6 +116,7 @@ void RendererOpenGL::SwapBuffers() { } } + system.perf_stats->EndSwap(); EndFrame(); prev_state.Apply(); rasterizer.TickFrame(); diff --git a/src/video_core/renderer_software/renderer_software.cpp b/src/video_core/renderer_software/renderer_software.cpp index 226de33d5..ce8c6ca86 100644 --- a/src/video_core/renderer_software/renderer_software.cpp +++ b/src/video_core/renderer_software/renderer_software.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Citra Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -18,7 +18,9 @@ RendererSoftware::RendererSoftware(Core::System& system, Pica::PicaCore& pica_, RendererSoftware::~RendererSoftware() = default; void RendererSoftware::SwapBuffers() { + system.perf_stats->StartSwap(); PrepareRenderTarget(); + system.perf_stats->EndSwap(); EndFrame(); } diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp index 12bbf7df3..5e9f0e94f 100644 --- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp +++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp @@ -832,6 +832,7 @@ void RendererVulkan::DrawScreens(Frame* frame, const Layout::FramebufferLayout& } void RendererVulkan::SwapBuffers() { + system.perf_stats->StartSwap(); const Layout::FramebufferLayout& layout = render_window.GetFramebufferLayout(); PrepareRendertarget(); RenderScreenshot(); @@ -847,6 +848,7 @@ void RendererVulkan::SwapBuffers() { secondary_window->PollEvents(); } #endif + system.perf_stats->EndSwap(); rasterizer.TickFrame(); EndFrame(); }