Skip to content

Commit 5a5c3c1

Browse files
committed
Fix UI being cropped + other issues and fix for AMD Vega GPUs
1 parent 0ec7b5c commit 5a5c3c1

File tree

5 files changed

+289
-299
lines changed

5 files changed

+289
-299
lines changed

src/hooking/entity_debugger.cpp

Lines changed: 138 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -553,12 +553,6 @@ std::array<bool, ImGuiKey_NamedKey_COUNT> s_pressedNamedKeyState = {};
553553
}
554554

555555
void EntityDebugger::UpdateKeyboardControls() {
556-
// update mouse state depending on if the window is focused
557-
ImGui::GetIO().AddMouseButtonEvent(0, GetAsyncKeyState(VK_LBUTTON) & 0x8000);
558-
ImGui::GetIO().AddMouseButtonEvent(1, GetAsyncKeyState(VK_RBUTTON) & 0x8000);
559-
ImGui::GetIO().AddMouseButtonEvent(2, GetAsyncKeyState(VK_MBUTTON) & 0x8000);
560-
561-
562556
// capture keyboard input
563557
ImGui::GetIO().KeyAlt = GetAsyncKeyState(VK_MENU) & 0x8000;
564558
ImGui::GetIO().KeyCtrl = GetAsyncKeyState(VK_CONTROL) & 0x8000;
@@ -623,4 +617,142 @@ void EntityDebugger::UpdateKeyboardControls() {
623617
else if (!isBackspaceKeyDown && wasBackspaceKeyDown) {
624618
ImGui::GetIO().AddKeyEvent(ImGuiKey_Backspace, false);
625619
}
620+
}
621+
622+
623+
void EntityDebugger::DrawFPSOverlay(RND_Renderer* renderer) {
624+
ImGui::SetNextWindowBgAlpha(0.6f);
625+
626+
// Use DisplaySize/FramebufferScale so positioning matches the same coordinate space as the overlay.
627+
ImVec2 windowSize = ImGui::GetIO().DisplaySize;
628+
windowSize.x = windowSize.x / ImGui::GetIO().DisplayFramebufferScale.x;
629+
windowSize.y = windowSize.y / ImGui::GetIO().DisplayFramebufferScale.y;
630+
631+
const ImVec2 pad(10.0f, 10.0f);
632+
ImGui::SetNextWindowPos(ImVec2(windowSize.x - pad.x, pad.y), ImGuiCond_Always, ImVec2(1.0f, 0.0f));
633+
634+
if (ImGui::Begin("AppMS Overlay", nullptr, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoMove)) {
635+
const float predictedDisplayPeriodMs = (float)renderer->GetPredictedDisplayPeriodMs();
636+
const float predictedHz = predictedDisplayPeriodMs > 0.0f ? (1000.0f / predictedDisplayPeriodMs) : 0.0f;
637+
638+
const float appMs = (float)renderer->GetLastFrameTimeMs(); // Total frame time (includes wait)
639+
const float workMs = (float)renderer->GetLastFrameWorkTimeMs(); // GPU Work time only (excludes wait)
640+
const float waitMs = (float)renderer->GetLastWaitTimeMs();
641+
const float overheadMs = (float)renderer->GetLastOverheadMs();
642+
643+
// --- 2. Convert to FPS ---
644+
const float appFps = appMs > 0.0000001f ? (1000.0f / appMs) : 0.0f;
645+
646+
// "Theoretical FPS": How fast you COULD run if you didn't have to wait for V-Sync/OpenXR
647+
const float workFps = workMs >= 0.0000001f ? (1000.0f / workMs) : 0.0f;
648+
649+
// Calculate percentage of the frame budget used (still useful in % terms)
650+
const float workPct = predictedDisplayPeriodMs > 0.0f ? (workMs / predictedDisplayPeriodMs) * 100.0f : 0.0f;
651+
652+
// --- 3. Text Summary ---
653+
ImGui::Text("Your headset is %.0f Hz", predictedHz);
654+
ImGui::Text("Currently Running At %.1f FPS", appFps);
655+
ImGui::Text("");
656+
ImGui::Text("OpenXR waited %.1f ms so that it can interpolate/have low latency.", waitMs);
657+
ImGui::Text("Theoretically, it'd run at %.1f FPS if that didn't matter", workFps);
658+
659+
if (predictedHz > 0.0f && workFps > 0.0f) {
660+
auto rateForDivisor = [predictedHz](int divisor) -> double {
661+
return divisor > 0 ? (predictedHz / (double)divisor) : 0.0;
662+
};
663+
664+
auto chooseBestDivisor = [&](double fps) -> int {
665+
// Pick the closest *supported* refresh divisor (1x, 1/2x, 1/3x, 1/4x).
666+
int bestDiv = 1;
667+
double bestErr = std::abs(fps - rateForDivisor(1));
668+
for (int div = 2; div <= 4; ++div) {
669+
const double err = std::abs(fps - rateForDivisor(div));
670+
if (err < bestErr) {
671+
bestErr = err;
672+
bestDiv = div;
673+
}
674+
}
675+
return bestDiv;
676+
};
677+
678+
// Use theoretical FPS (GPU work time) as the basis for which step we're closest to.
679+
const int currentDiv = chooseBestDivisor(workFps);
680+
const double currentTarget = rateForDivisor(currentDiv);
681+
const int nextDiv = std::max(1, currentDiv - 1);
682+
const double nextTarget = rateForDivisor(nextDiv);
683+
const double missingNext = std::max(0.0, nextTarget - (double)workFps);
684+
685+
if (currentDiv == 1) {
686+
ImGui::Text("Its reaching the full refresh rate you've set (%.0f hz)", currentTarget);
687+
ImGui::Text("You've got ~%.1f FPS of headroom to spare", (float)std::max(0.0, (double)workFps - nextTarget));
688+
}
689+
else {
690+
ImGui::Text("It is however able to reliably reach %.0f FPS (%.0f Hz / %d)", currentTarget, predictedHz, currentDiv);
691+
ImGui::Text("You'd need to get %.1f FPS more to get it to switch to %.0f FPS", missingNext, nextTarget);
692+
}
693+
}
694+
695+
// --- 4. History Buffers (Storing FPS now) ---
696+
static float history_app_fps[120] = {};
697+
static float history_work_fps[120] = {};
698+
static int offset = 0;
699+
700+
history_app_fps[offset] = appFps;
701+
history_work_fps[offset] = workFps;
702+
offset = (offset + 1) % 120;
703+
704+
// --- 5. Plotting ---
705+
const double targetFps = predictedHz;
706+
const double halfFps = predictedHz / 2.0;
707+
const double thirdFps = predictedHz / 3.0;
708+
const double fourthFps = predictedHz / 4.0;
709+
if (ImPlot::BeginPlot("##Frametime", ImVec2(420, 150), ImPlotFlags_NoFrame | ImPlotFlags_NoTitle | ImPlotFlags_NoMouseText | ImPlotFlags_NoMenus | ImPlotFlags_NoBoxSelect | ImPlotFlags_NoInputs)) {
710+
ImPlot::SetupAxes(nullptr, "##FPS", ImPlotAxisFlags_NoDecorations, ImPlotAxisFlags_NoInitialFit);
711+
ImPlot::SetupAxisLimits(ImAxis_X1, 0, 120, ImPlotCond_Always);
712+
713+
if (targetFps >= 0.000000001f) {
714+
ImPlot::SetupAxisLimits(ImAxis_Y1, 0.0, predictedHz * 1.5f, ImPlotCond_Always);
715+
716+
// 1. Target Refresh Rate (Green)
717+
ImPlot::SetNextLineStyle(ImVec4(0, 1, 0, 0.5f));
718+
ImPlot::PlotInfLines("##Target", &targetFps, 1, ImPlotInfLinesFlags_Horizontal);
719+
ImPlot::TagY(targetFps, ImVec4(0, 1, 0, 0.5f), "%.0f Hz", targetFps);
720+
721+
// 2. Half Rate (ASW/Reprojection threshold) (Yellow)
722+
ImPlot::SetNextLineStyle(ImVec4(1, 1, 0, 0.5f));
723+
ImPlot::PlotInfLines("##1/2 Rate", &halfFps, 1, ImPlotInfLinesFlags_Horizontal);
724+
ImPlot::TagY(halfFps, ImVec4(1, 1, 0, 0.5f), "%.0f Hz", halfFps);
725+
726+
// 3. Third Rate (Red)
727+
ImPlot::SetNextLineStyle(ImVec4(1, 0, 0, 0.5f));
728+
ImPlot::PlotInfLines("##1/3 Rate", &thirdFps, 1, ImPlotInfLinesFlags_Horizontal);
729+
}
730+
else {
731+
ImPlot::SetupAxisLimits(ImAxis_Y1, 0.0, 144.0, ImPlotCond_Always);
732+
}
733+
734+
// --- Draw Graphs ---
735+
// 1. Theoretical Max FPS (Work Time) - Purple/Pink
736+
ImPlot::SetNextLineStyle(ImVec4(1.0f, 0.4f, 1.0f, 1.0f));
737+
ImPlot::PlotLine("Theoretical Max", history_work_fps, 120, 1.0, 0.0, 0, offset);
738+
739+
// 2. Actual FPS (App Time) - Blue
740+
// This represents what is actually hitting the screen (capped by Wait).
741+
ImPlot::SetNextFillStyle(ImVec4(0.4f, 0.4f, 1.0f, 0.50f));
742+
ImPlot::SetNextLineStyle(ImVec4(0.4f, 0.4f, 1.0f, 1.0f));
743+
ImPlot::PlotShaded("Actual", history_app_fps, 120, 0.0, 1.0, 0.0, 0, offset);
744+
ImPlot::PlotLine("##TotalLine", history_app_fps, 120, 1.0, 0.0, 0, offset);
745+
746+
// Current FPS Tag
747+
if (appFps > 0.0f) {
748+
const double currentFps = appFps;
749+
ImPlot::SetNextLineStyle(ImVec4(1, 1, 1, 0.65f));
750+
ImPlot::PlotInfLines("##Current", &currentFps, 1, ImPlotInfLinesFlags_Horizontal);
751+
ImPlot::TagY(currentFps, ImVec4(1, 1, 1, 0.8f), "%.1f FPS", currentFps);
752+
}
753+
754+
ImPlot::EndPlot();
755+
}
756+
}
757+
ImGui::End();
626758
}

src/hooking/entity_debugger.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ class EntityDebugger {
2323

2424
void UpdateKeyboardControls();
2525
void DrawEntityInspector();
26+
static void DrawFPSOverlay(class RND_Renderer* renderer);
2627

2728
struct EntityValue {
2829
std::string value_name;

src/hooking/framebuffer.cpp

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -80,13 +80,14 @@ void VkDeviceOverrides::CmdClearColorImage(const vkroots::VkCommandBufferDispatc
8080
if (const auto it = imageResolutions.find(image); it != imageResolutions.end()) {
8181
auto viewConfs = VRManager::instance().XR->GetViewConfigurations();
8282

83+
VkExtent2D renderRes = it->second.first;
8384
VkExtent2D swapchainRes = it->second.first;
8485
if (VRManager::instance().XR->m_capabilities.isMetaSimulator) {
85-
swapchainRes = VkExtent2D{ viewConfs[0].recommendedImageRectWidth, viewConfs[0].recommendedImageRectHeight };
86+
//swapchainRes = VkExtent2D{ viewConfs[0].recommendedImageRectWidth, viewConfs[0].recommendedImageRectHeight };
8687
}
8788

88-
layer3D = std::make_unique<RND_Renderer::Layer3D>(it->second.first, swapchainRes);
89-
layer2D = std::make_unique<RND_Renderer::Layer2D>(it->second.first, swapchainRes);
89+
layer3D = std::make_unique<RND_Renderer::Layer3D>(renderRes, swapchainRes);
90+
layer2D = std::make_unique<RND_Renderer::Layer2D>(renderRes, swapchainRes);
9091
for (auto& textures : layer3D->GetSharedTextures()) {
9192
for (auto& texture : textures) {
9293
texture->Init(commandBuffer);
@@ -101,8 +102,8 @@ void VkDeviceOverrides::CmdClearColorImage(const vkroots::VkCommandBufferDispatc
101102
texture->Init(commandBuffer);
102103
}
103104

104-
Log::print<INFO>("Found rendering resolution {}x{} @ {} using capture #{}", it->second.first.width, it->second.first.height, it->second.second, captureIdx);
105-
imguiOverlay = std::make_unique<RND_Renderer::ImGuiOverlay>(commandBuffer, it->second.first.width, it->second.first.height, VK_FORMAT_A2B10G10R10_UNORM_PACK32);
105+
Log::print<INFO>("Found rendering resolution {}x{} @ {} using capture #{}", renderRes.width, renderRes.height, it->second.second, captureIdx);
106+
imguiOverlay = std::make_unique<RND_Renderer::ImGuiOverlay>(commandBuffer, renderRes, VK_FORMAT_A2B10G10R10_UNORM_PACK32);
106107
if (CemuHooks::GetSettings().ShowDebugOverlay()) {
107108
VRManager::instance().Hooks->m_entityDebugger = std::make_unique<EntityDebugger>();
108109
}
@@ -214,9 +215,8 @@ void VkDeviceOverrides::CmdClearColorImage(const vkroots::VkCommandBufferDispatc
214215

215216
if (imguiOverlay && !hudCopied) {
216217
// render imgui, and then copy the framebuffer to the 2D layer
217-
imguiOverlay->BeginFrame(frameIdx, false);
218218
imguiOverlay->Update();
219-
imguiOverlay->Render();
219+
imguiOverlay->Render(frameIdx, false);
220220
imguiOverlay->DrawAndCopyToImage(commandBuffer, image, frameIdx);
221221
VulkanUtils::DebugPipelineBarrier(commandBuffer);
222222
}
@@ -238,9 +238,8 @@ void VkDeviceOverrides::CmdClearColorImage(const vkroots::VkCommandBufferDispatc
238238
// render the imgui overlay on the right side
239239
if (imguiOverlay) {
240240
// render imgui, and then copy the framebuffer to the 2D layer
241-
imguiOverlay->BeginFrame(frameIdx, true);
241+
imguiOverlay->Render(frameIdx, true);
242242
imguiOverlay->Update();
243-
imguiOverlay->Render();
244243
imguiOverlay->DrawAndCopyToImage(commandBuffer, image, frameIdx);
245244
returnToLayout();
246245
return;

src/rendering/renderer.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -169,23 +169,23 @@ class RND_Renderer {
169169

170170
class ImGuiOverlay {
171171
public:
172-
explicit ImGuiOverlay(VkCommandBuffer cb, uint32_t width, uint32_t height, VkFormat format);
172+
explicit ImGuiOverlay(VkCommandBuffer cb,VkExtent2D fbRes, VkFormat framebufferFormat);
173173
~ImGuiOverlay();
174174

175175
bool ShouldBlockGameInput() { return ImGui::GetIO().WantCaptureKeyboard; }
176176

177-
void BeginFrame(long frameIdx, bool renderBackground);
177+
void Update();
178178
static void Draw3DLayerAsBackground(VkCommandBuffer cb, VkImage srcImage, float aspectRatio, long frameIdx);
179179
static void DrawHUDLayerAsBackground(VkCommandBuffer cb, VkImage srcImage, long frameIdx);
180-
void Update();
181-
void Render();
180+
void Render(long frameIdx, bool renderBackground);
182181
void DrawAndCopyToImage(VkCommandBuffer cb, VkImage destImage, long frameIdx);
183182

184183
private:
185184
VkDescriptorPool m_descriptorPool;
186185
VkRenderPass m_renderPass;
187186

188187
VkSampler m_sampler = VK_NULL_HANDLE;
188+
VkExtent2D m_outputRes = {};
189189

190190
uint8_t m_showAppMS = 0;
191191
bool m_wasF3Pressed = false;

0 commit comments

Comments
 (0)