Skip to content

Commit 7c4ba17

Browse files
committed
Add profiling to application
1 parent 30e87e8 commit 7c4ba17

19 files changed

Lines changed: 549 additions & 190 deletions

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,9 @@ While more integrated solutions are being found out, there's probably ways to se
7575
- `FPS++`: Set the FPS limit to at least 120 or 144. Your headset/runtime will still control the actual VR framerate.
7676
- `Enhancements`: Set anisotropic filtering to 16x, and optionally set a Clarity preset to make the colors less washed out.
7777

78-
7. Double click the game in Cemu's game list while your VR headset is turned on and connected. If things went correctly, you should now be playing the game in VR!
78+
7. [Optional] To avoid some heavy stutters when you first start the game, download the shader caches from https://chriztr.github.io/cemu_shader_and_pipeline_caches/ and follow the `How to install the caches` section on that page.
79+
80+
8. Double click the game in Cemu's game list while your VR headset is turned on and connected. If things went correctly, you should now be playing the game in VR!
7981

8082
Use the in-game BetterVR menu to see the BetterVR settings and controls guide (hold the X or equivalent button on your left VR controller, or long press the Menu button on your Xbox controller).
8183
In the settings you can also enable an option to immediately start the game when you start the `BetterVR_Launcher.exe`. You can also add this executable to Virtual Desktop, SteamVR etc. as a non-official Steam game.

include/pch.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ using Microsoft::WRL::ComPtr;
6363
#include <imgui.h>
6464
#include <imgui_impl_vulkan.h>
6565
#include <implot.h>
66+
#include <imgui_memory_editor.h>
6667

6768
// glm includes
6869
#define GLM_FORCE_XYZW_ONLY
@@ -533,3 +534,4 @@ struct data_VRProjectionMatrixOut {
533534
#include "game_structs.h"
534535
#include "cemu.h"
535536
#include "utils/logger.h"
537+
#include "utils/profiler.h"

src/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ target_sources(BetterVR_Layer PUBLIC
1717
${CMAKE_SOURCE_DIR}/src/utils/logger.cpp
1818
${CMAKE_SOURCE_DIR}/src/utils/logger.h
1919
${CMAKE_SOURCE_DIR}/src/utils/mod_settings.h
20+
${CMAKE_SOURCE_DIR}/src/utils/profiler.h
2021
${CMAKE_SOURCE_DIR}/src/utils/debug_draw.h
2122
${CMAKE_SOURCE_DIR}/src/utils/debug_draw.cpp
2223
${CMAKE_SOURCE_DIR}/src/hooking/framebuffer.cpp

src/hooking/entity_controller.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -497,6 +497,8 @@ static glm::fmat3 GetRoomscaleWorldToRoomRotation() {
497497
}
498498

499499
static void ClearRoomscaleResolveState() {
500+
BetterVRProfiler::EndSpan(BetterVRProfiler::Section::RoomscaleResolve);
501+
500502
s_roomscaleState.isResolving = false;
501503
s_roomscaleState.isProbeOnly = false;
502504
s_roomscaleState.remainingRawDelta = glm::fvec3(0.0f);
@@ -755,6 +757,8 @@ float CemuHooks::GetRoomscaleFadeAmount() {
755757

756758

757759
void CemuHooks::hook_BeginRoomscaleMovement(PPCInterpreter_t* hCPU) {
760+
BetterVRProfiler::Scope profile(BetterVRProfiler::Section::RoomscaleBeginHook);
761+
758762
hCPU->instructionPointer = hCPU->sprNew.LR;
759763

760764
uint32_t controller = hCPU->gpr[3];
@@ -793,6 +797,7 @@ void CemuHooks::hook_BeginRoomscaleMovement(PPCInterpreter_t* hCPU) {
793797
s_roomscaleState.isResolving = true;
794798
s_roomscaleState.isProbeOnly = true;
795799
hCPU->gpr[3] = RoomscaleHookResult_Cast;
800+
BetterVRProfiler::BeginSpan(BetterVRProfiler::Section::RoomscaleResolve);
796801
return;
797802
}
798803
(void)currentHeadPos;
@@ -807,6 +812,7 @@ void CemuHooks::hook_BeginRoomscaleMovement(PPCInterpreter_t* hCPU) {
807812
s_roomscaleState.isResolving = true;
808813
s_roomscaleState.isProbeOnly = false;
809814
hCPU->gpr[3] = RoomscaleHookResult_Cast;
815+
BetterVRProfiler::BeginSpan(BetterVRProfiler::Section::RoomscaleResolve);
810816
}
811817

812818
void CemuHooks::hook_PrepareRoomscaleRaycast(PPCInterpreter_t* hCPU) {

src/hooking/entity_debugger.cpp

Lines changed: 0 additions & 146 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
11
#include "pch.h"
22
#include "entity_debugger.h"
33
#include "instance.h"
4-
#include "rendering/vulkan.h"
54
#include "utils/mod_settings.h"
65

7-
#include <imgui_memory_editor.h>
8-
96
#include "utils/debug_draw.h"
107

118
std::mutex g_actorListMutex;
@@ -65,7 +62,6 @@ void CemuHooks::hook_UpdateActorList(PPCInterpreter_t* hCPU) {
6562
s_cameraMtxAddress = actorMtxPtr;
6663
}
6764
}
68-
6965
// ksys::phys::RigidBodyFromShape::create to create a RigidBody from a shape
7066
// use Actor::getRigidBodyByName
7167

@@ -483,8 +479,6 @@ void EntityDebugger::RemoveEntityValue(uint32_t actorId, const std::string& valu
483479
}).begin(), it->second.values.end());
484480
}
485481
}
486-
487-
488482
std::array<bool, 256> s_pressedKeyState = {};
489483
std::array<bool, ImGuiKey_NamedKey_COUNT> s_pressedNamedKeyState = {};
490484

@@ -575,143 +569,3 @@ void EntityDebugger::UpdateKeyboardControls() {
575569
ImGui::GetIO().AddKeyEvent(ImGuiKey_Backspace, false);
576570
}
577571
}
578-
579-
580-
void EntityDebugger::DrawFPSOverlay(RND_Renderer* renderer) {
581-
ImGui::SetNextWindowBgAlpha(0.4f);
582-
583-
ImVec2 windowSize = ImGui::GetIO().DisplaySize;
584-
585-
const ImVec2 pad(10.0f, 10.0f);
586-
ImGui::SetNextWindowPos(ImVec2(windowSize.x - pad.x, pad.y), ImGuiCond_Always, ImVec2(1.0f, 0.0f));
587-
588-
if (ImGui::Begin("AppMS Overlay", nullptr, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoMove)) {
589-
DrawFPSOverlayContent(renderer, false);
590-
}
591-
ImGui::End();
592-
}
593-
594-
void EntityDebugger::DrawFPSOverlayContent(RND_Renderer* renderer, bool renderText) {
595-
const float predictedDisplayPeriodMs = (float)renderer->GetPredictedDisplayPeriodMs();
596-
const float predictedHz = GetSettings().performanceOverlayFrequency;
597-
598-
const float appMs = (float)renderer->GetLastFrameTimeMs(); // Total frame time (includes wait)
599-
const float workMs = (float)renderer->GetLastFrameWorkTimeMs(); // GPU Work time only (excludes wait)
600-
const float waitMs = (float)renderer->GetLastWaitTimeMs();
601-
const float overheadMs = (float)renderer->GetLastOverheadMs();
602-
603-
// --- 2. Convert to FPS ---
604-
const float appFps = appMs > 0.0000001f ? (1000.0f / appMs) : 0.0f;
605-
606-
// "Theoretical FPS": How fast you COULD run if you didn't have to wait for V-Sync/OpenXR
607-
const float workFps = workMs >= 0.0000001f ? (1000.0f / workMs) : 0.0f;
608-
609-
// Calculate percentage of the frame budget used (still useful in % terms)
610-
const float workPct = predictedDisplayPeriodMs > 0.0f ? (workMs / predictedDisplayPeriodMs) * 100.0f : 0.0f;
611-
612-
// --- 3. Text Summary ---
613-
if (renderText) {
614-
ImGui::Text("The visualization refresh rate of your headset is set to %.0f Hz", predictedHz);
615-
ImGui::Text("Currently Running At %.1f FPS", appFps);
616-
ImGui::Text("");
617-
ImGui::Text("OpenXR waited %.1f ms so that it can interpolate/have low latency.", waitMs);
618-
ImGui::Text("Theoretically, it'd run at %.1f FPS if that didn't matter", workFps);
619-
}
620-
621-
if (predictedHz > 0.0f && workFps >= 0.0f) {
622-
auto rateForDivisor = [predictedHz](int divisor) -> double {
623-
return divisor > 0 ? (predictedHz / (double)divisor) : 0.0;
624-
};
625-
626-
auto chooseBestDivisor = [&](double fps) -> int {
627-
// Pick the refresh divisor (1x, 1/2x, 1/3x, 1/4x) that is able to be achieved using the workFPS (frame time minus OpenXR wait time)
628-
for (int div = 1; div <= 4; ++div) {
629-
if (fps >= rateForDivisor(div)) {
630-
return div;
631-
}
632-
}
633-
return 4;
634-
};
635-
636-
// Use theoretical FPS (GPU work time) as the basis for which step we're closest to.
637-
const int currentDiv = chooseBestDivisor(workFps);
638-
const double currentTarget = rateForDivisor(currentDiv);
639-
const int nextDiv = std::max(1, currentDiv - 1);
640-
const double nextTarget = rateForDivisor(nextDiv);
641-
const double missingNext = std::max(0.0, nextTarget - (double)workFps);
642-
643-
if (renderText) {
644-
if (currentDiv == 1) {
645-
ImGui::Text("Its reaching the full refresh rate you've set (%.0f hz)", currentTarget);
646-
ImGui::Text("You've got ~%.1f FPS of headroom to spare", (float)std::max(0.0, (double)workFps - nextTarget));
647-
}
648-
else {
649-
ImGui::Text("It is however able to reliably reach %.0f FPS (%.0f Hz / %d)", currentTarget, predictedHz, currentDiv);
650-
ImGui::Text("You'd need to get %.1f FPS more to get it to switch to %.0f FPS", missingNext, nextTarget);
651-
}
652-
}
653-
}
654-
655-
// --- 4. History Buffers (Storing FPS now) ---
656-
static float history_app_fps[60] = {};
657-
static float history_work_fps[60] = {};
658-
static int offset = 0;
659-
660-
history_app_fps[offset] = appFps;
661-
history_work_fps[offset] = workFps;
662-
offset = (offset + 1) % 60;
663-
664-
// --- 5. Plotting ---
665-
const double targetFps = predictedHz;
666-
const double halfFps = predictedHz / 2.0;
667-
const double thirdFps = predictedHz / 3.0;
668-
const double fourthFps = predictedHz / 4.0;
669-
670-
if (ImPlot::BeginPlot("##Frametime", ImVec2(300, 150), ImPlotFlags_NoFrame | ImPlotFlags_NoTitle | ImPlotFlags_NoMouseText | ImPlotFlags_NoMenus | ImPlotFlags_NoBoxSelect | ImPlotFlags_NoInputs)) {
671-
ImPlot::SetupAxes(nullptr, "##FPS", ImPlotAxisFlags_NoDecorations, ImPlotAxisFlags_NoInitialFit);
672-
ImPlot::SetupAxisLimits(ImAxis_X1, 0, 60, ImPlotCond_Always);
673-
674-
if (targetFps >= 0.000000001f) {
675-
ImPlot::SetupAxisLimits(ImAxis_Y1, 0.0, predictedHz * 1.5f, ImPlotCond_Always);
676-
677-
// 1. Target Refresh Rate (Green)
678-
ImPlot::SetNextLineStyle(ImVec4(0, 1, 0, 0.5f));
679-
ImPlot::PlotInfLines("##Target", &targetFps, 1, ImPlotInfLinesFlags_Horizontal);
680-
ImPlot::TagY(targetFps, ImVec4(0, 1, 0, 0.5f), "%.0f Hz", targetFps);
681-
682-
// 2. Half Rate (ASW/Reprojection threshold) (Yellow)
683-
ImPlot::SetNextLineStyle(ImVec4(1, 1, 0, 0.5f));
684-
ImPlot::PlotInfLines("##1/2 Rate", &halfFps, 1, ImPlotInfLinesFlags_Horizontal);
685-
ImPlot::TagY(halfFps, ImVec4(1, 1, 0, 0.5f), "%.0f Hz", halfFps);
686-
687-
// 3. Third Rate (Red)
688-
ImPlot::SetNextLineStyle(ImVec4(1, 0, 0, 0.5f));
689-
ImPlot::PlotInfLines("##1/3 Rate", &thirdFps, 1, ImPlotInfLinesFlags_Horizontal);
690-
}
691-
else {
692-
ImPlot::SetupAxisLimits(ImAxis_Y1, 0.0, 144.0, ImPlotCond_Always);
693-
}
694-
695-
// --- Draw Graphs ---
696-
// 1. Theoretical Max FPS (Work Time) - Purple/Pink
697-
ImPlot::SetNextLineStyle(ImVec4(1.0f, 0.4f, 1.0f, 1.0f));
698-
ImPlot::PlotLine("Theoretical Max", history_work_fps, 60, 1.0, 0.0, 0, offset);
699-
700-
// 2. Actual FPS (App Time) - Blue
701-
// This represents what is actually hitting the screen (capped by Wait).
702-
ImPlot::SetNextFillStyle(ImVec4(0.4f, 0.4f, 1.0f, 0.50f));
703-
ImPlot::SetNextLineStyle(ImVec4(0.4f, 0.4f, 1.0f, 1.0f));
704-
ImPlot::PlotShaded("Actual", history_app_fps, 60, 0.0, 1.0, 0.0, 0, offset);
705-
ImPlot::PlotLine("##TotalLine", history_app_fps, 60, 1.0, 0.0, 0, offset);
706-
707-
// Current FPS Tag
708-
if (appFps > 0.0f) {
709-
const double currentFps = appFps;
710-
ImPlot::SetNextLineStyle(ImVec4(1, 1, 1, 0.65f));
711-
ImPlot::PlotInfLines("##Current", &currentFps, 1, ImPlotInfLinesFlags_Horizontal);
712-
ImPlot::TagY(currentFps, ImVec4(1, 1, 1, 0.8f), "%.1f FPS", currentFps);
713-
}
714-
715-
ImPlot::EndPlot();
716-
}
717-
}

src/hooking/entity_debugger.h

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
#pragma once
22

3-
#include <imgui_memory_editor.h>
4-
53
struct MemoryRange {
64
uint32_t start;
75
uint32_t end;
@@ -24,8 +22,6 @@ class EntityDebugger {
2422
void UpdateKeyboardControls();
2523
void DrawWorldSpaceOverlaySettings(bool* changed);
2624
void DrawEntityInspectorContent();
27-
static void DrawFPSOverlay(class RND_Renderer* renderer);
28-
static void DrawFPSOverlayContent(class RND_Renderer* renderer, bool renderText);
2925

3026
struct EntityValue {
3127
std::string value_name;

0 commit comments

Comments
 (0)