diff --git a/.gitmodules b/.gitmodules index 6981e9f4..0784b50c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -57,4 +57,7 @@ url = https://github.com/martinus/unordered_dense.git [submodule "thirdparty/SDL_mixer"] path = thirdparty/SDL_mixer - url = https://github.com/libsdl-org/SDL_mixer \ No newline at end of file + url = https://github.com/libsdl-org/SDL_mixer +[submodule "thirdparty/implot"] + path = thirdparty/implot + url = https://github.com/epezent/implot.git diff --git a/UnleashedRecomp/CMakeLists.txt b/UnleashedRecomp/CMakeLists.txt index 395523fc..01ac1139 100644 --- a/UnleashedRecomp/CMakeLists.txt +++ b/UnleashedRecomp/CMakeLists.txt @@ -196,11 +196,14 @@ set(SWA_USER_CXX_SOURCES set(SWA_THIRDPARTY_SOURCES "${SWA_THIRDPARTY_ROOT}/imgui/backends/imgui_impl_sdl2.cpp" - "${SWA_THIRDPARTY_ROOT}/imgui/imgui.cpp" + "${SWA_THIRDPARTY_ROOT}/imgui/imgui.cpp" "${SWA_THIRDPARTY_ROOT}/imgui/imgui_demo.cpp" "${SWA_THIRDPARTY_ROOT}/imgui/imgui_draw.cpp" "${SWA_THIRDPARTY_ROOT}/imgui/imgui_tables.cpp" "${SWA_THIRDPARTY_ROOT}/imgui/imgui_widgets.cpp" + "${SWA_THIRDPARTY_ROOT}/implot/implot.cpp" + "${SWA_THIRDPARTY_ROOT}/implot/implot_demo.cpp" + "${SWA_THIRDPARTY_ROOT}/implot/implot_items.cpp" "${SWA_THIRDPARTY_ROOT}/libmspack/libmspack/mspack/lzxd.c" "${SWA_THIRDPARTY_ROOT}/tiny-AES-c/aes.c" "${SWA_TOOLS_ROOT}/ShaderRecomp/thirdparty/smol-v/source/smolv.cpp" @@ -209,7 +212,8 @@ set(SWA_THIRDPARTY_SOURCES set(SWA_THIRDPARTY_INCLUDES "${SWA_THIRDPARTY_ROOT}/concurrentqueue" "${SWA_THIRDPARTY_ROOT}/ddspp" - "${SWA_THIRDPARTY_ROOT}/imgui" + "${SWA_THIRDPARTY_ROOT}/imgui" + "${SWA_THIRDPARTY_ROOT}/implot" "${SWA_THIRDPARTY_ROOT}/libmspack/libmspack/mspack" "${SWA_THIRDPARTY_ROOT}/magic_enum/include" "${SWA_THIRDPARTY_ROOT}/stb" diff --git a/UnleashedRecomp/app.cpp b/UnleashedRecomp/app.cpp index fa8c0982..306b7f9d 100644 --- a/UnleashedRecomp/app.cpp +++ b/UnleashedRecomp/app.cpp @@ -38,6 +38,14 @@ PPC_FUNC(sub_824EB490) PPC_FUNC_IMPL(__imp__sub_822C1130); PPC_FUNC(sub_822C1130) { + // Correct small delta time errors. + if (Config::FPS >= FPS_MIN && Config::FPS < FPS_MAX) + { + double targetDeltaTime = 1.0 / Config::FPS; + if (abs(ctx.f1.f64 - targetDeltaTime) < 0.00001) + ctx.f1.f64 = targetDeltaTime; + } + App::s_deltaTime = ctx.f1.f64; SDL_PumpEvents(); @@ -48,3 +56,4 @@ PPC_FUNC(sub_822C1130) __imp__sub_822C1130(ctx, base); } + diff --git a/UnleashedRecomp/app.h b/UnleashedRecomp/app.h index b6541358..54794207 100644 --- a/UnleashedRecomp/app.h +++ b/UnleashedRecomp/app.h @@ -15,3 +15,4 @@ public: static void Restart(std::vector restartArgs = {}); static void Exit(); }; + diff --git a/UnleashedRecomp/apu/driver/sdl2_driver.cpp b/UnleashedRecomp/apu/driver/sdl2_driver.cpp index 7c8777e7..30156083 100644 --- a/UnleashedRecomp/apu/driver/sdl2_driver.cpp +++ b/UnleashedRecomp/apu/driver/sdl2_driver.cpp @@ -41,10 +41,6 @@ static void AudioThread() size_t channels = g_downMixToStereo ? 2 : XAUDIO_NUM_CHANNELS; - constexpr double INTERVAL = double(XAUDIO_NUM_SAMPLES) / double(XAUDIO_SAMPLES_HZ); - auto start = std::chrono::steady_clock::now(); - size_t iteration = 1; - while (true) { uint32_t queuedAudioSize = SDL_GetQueuedAudioSize(g_audioDevice); @@ -57,15 +53,14 @@ static void AudioThread() g_clientCallback(ctx.ppcContext, reinterpret_cast(g_memory.base)); } - auto next = start + std::chrono::duration(iteration * INTERVAL); auto now = std::chrono::steady_clock::now(); + constexpr auto INTERVAL = 1000000000ns * XAUDIO_NUM_SAMPLES / XAUDIO_SAMPLES_HZ; + auto next = now + (INTERVAL - now.time_since_epoch() % INTERVAL); - if ((next - now) > 1s) - next = now; + std::this_thread::sleep_for(std::chrono::floor(next - now)); - std::this_thread::sleep_until(next); - - iteration = std::chrono::duration(std::chrono::steady_clock::now() - start).count() / INTERVAL + 1; + while (std::chrono::steady_clock::now() < next) + std::this_thread::yield(); } } diff --git a/UnleashedRecomp/gpu/rhi/plume_d3d12.cpp b/UnleashedRecomp/gpu/rhi/plume_d3d12.cpp index 7bef562f..6c28cdf6 100644 --- a/UnleashedRecomp/gpu/rhi/plume_d3d12.cpp +++ b/UnleashedRecomp/gpu/rhi/plume_d3d12.cpp @@ -1265,9 +1265,7 @@ namespace plume { bool D3D12SwapChain::present(uint32_t textureIndex, RenderCommandSemaphore **waitSemaphores, uint32_t waitSemaphoreCount) { if (waitableObject != NULL) { - while (WaitForSingleObjectEx(waitableObject, 0, FALSE)) { - std::this_thread::yield(); - } + WaitForSingleObject(waitableObject, INFINITE); } UINT syncInterval = vsyncEnabled ? 1 : 0; diff --git a/UnleashedRecomp/gpu/video.cpp b/UnleashedRecomp/gpu/video.cpp index 887aaa2f..376d10cb 100644 --- a/UnleashedRecomp/gpu/video.cpp +++ b/UnleashedRecomp/gpu/video.cpp @@ -4,6 +4,7 @@ #include "imgui/imgui_snapshot.h" #include "imgui/imgui_font_builder.h" +#include #include #include #include @@ -1317,6 +1318,7 @@ void Video::CreateHostDevice(bool sdlVideoDefault) IMGUI_CHECKVERSION(); ImGui::CreateContext(); + ImPlot::CreateContext(); GameWindow::Init(sdlVideoDefault); @@ -1885,6 +1887,100 @@ static uint32_t HashVertexDeclaration(uint32_t vertexDeclaration) return vertexDeclaration; } +static constexpr size_t PROFILER_VALUE_COUNT = 1024; +static size_t g_profilerValueIndex; + +struct Profiler +{ + std::atomic value; + double values[PROFILER_VALUE_COUNT]; + std::chrono::steady_clock::time_point start; + + void Begin() + { + start = std::chrono::steady_clock::now(); + } + + void End() + { + value = std::chrono::duration(std::chrono::steady_clock::now() - start).count(); + } + + double UpdateAndReturnAverage() + { + values[g_profilerValueIndex] = value; + return std::accumulate(values, values + PROFILER_VALUE_COUNT, 0.0) / PROFILER_VALUE_COUNT; + } +}; + +static double g_applicationValues[PROFILER_VALUE_COUNT]; +static Profiler g_renderDirectorProfiler; + +static bool g_profilerVisible; +static bool g_profilerWasToggled; + +static void DrawProfiler() +{ + bool toggleProfiler = SDL_GetKeyboardState(nullptr)[SDL_SCANCODE_F1] != 0; + + if (!g_profilerWasToggled && toggleProfiler) + g_profilerVisible = !g_profilerVisible; + + g_profilerWasToggled = toggleProfiler; + + if (!g_profilerVisible) + return; + + ImFont* font = ImFontAtlasSnapshot::GetFont("FOT-SeuratPro-M.otf"); + float defaultScale = font->Scale; + font->Scale = ImGui::GetDefaultFont()->FontSize / font->FontSize; + ImGui::PushFont(font); + + if (ImGui::Begin("Profiler", &g_profilerVisible)) + { + g_applicationValues[g_profilerValueIndex] = App::s_deltaTime * 1000.0; + double renderDirectorAvg = g_renderDirectorProfiler.UpdateAndReturnAverage(); + + if (ImPlot::BeginPlot("Frame Time")) + { + ImPlot::SetupAxisLimits(ImAxis_Y1, 0.0, 20.0); + ImPlot::SetupAxis(ImAxis_Y1, "ms", ImPlotAxisFlags_None); + ImPlot::PlotLine("Application", g_applicationValues, PROFILER_VALUE_COUNT, 1.0, 0.0, ImPlotLineFlags_None, g_profilerValueIndex); + ImPlot::PlotLine("Render Director", g_renderDirectorProfiler.values, PROFILER_VALUE_COUNT, 1.0, 0.0, ImPlotLineFlags_None, g_profilerValueIndex); + + ImPlot::EndPlot(); + } + + g_profilerValueIndex = (g_profilerValueIndex + 1) % PROFILER_VALUE_COUNT; + + const double applicationAvg = std::accumulate(g_applicationValues, g_applicationValues + PROFILER_VALUE_COUNT, 0.0) / PROFILER_VALUE_COUNT; + + ImGui::Text("Average Application: %g ms (%g FPS)", applicationAvg, 1000.0 / applicationAvg); + ImGui::Text("Average Render Director: %g ms (%g FPS)", renderDirectorAvg, 1000.0 / renderDirectorAvg); + + O1HeapDiagnostics diagnostics, physicalDiagnostics; + { + std::lock_guard lock(g_userHeap.mutex); + diagnostics = o1heapGetDiagnostics(g_userHeap.heap); + } + { + std::lock_guard lock(g_userHeap.physicalMutex); + physicalDiagnostics = o1heapGetDiagnostics(g_userHeap.physicalHeap); + } + + ImGui::Text("Heap Allocated: %d MB", int32_t(diagnostics.allocated / (1024 * 1024))); + ImGui::Text("Physical Heap Allocated: %d MB", int32_t(physicalDiagnostics.allocated / (1024 * 1024))); + + auto capabilities = g_device->getCapabilities(); + ImGui::Text("Present Wait: %s", capabilities.presentWait ? "Supported" : "Unsupported"); + ImGui::Text("Triangle Fan: %s", capabilities.triangleFan ? "Supported" : "Unsupported"); + } + ImGui::End(); + + ImGui::PopFont(); + font->Scale = defaultScale; +} + static void DrawImGui() { ImGui_ImplSDL2_NewFrame(); @@ -1916,6 +2012,8 @@ static void DrawImGui() MessageWindow::Draw(); ButtonGuide::Draw(); + DrawProfiler(); + ImGui::Render(); auto drawData = ImGui::GetDrawData(); @@ -4077,6 +4175,7 @@ static std::thread g_renderThread([] while (true) { size_t count = g_renderQueue.wait_dequeue_bulk(commands, std::size(commands)); + for (size_t i = 0; i < count; i++) { auto& cmd = commands[i]; @@ -4750,6 +4849,8 @@ PPC_FUNC(sub_8258C8A0) PPC_FUNC_IMPL(__imp__sub_8258CAE0); PPC_FUNC(sub_8258CAE0) { + g_renderDirectorProfiler.Begin(); + if (g_needsResize) { auto r3 = ctx.r3; @@ -4762,6 +4863,8 @@ PPC_FUNC(sub_8258CAE0) } __imp__sub_8258CAE0(ctx, base); + + g_renderDirectorProfiler.End(); } void PostProcessResolutionFix(PPCRegister& r4, PPCRegister& f1, PPCRegister& f2) diff --git a/UnleashedRecomp/patches/fps_patches.cpp b/UnleashedRecomp/patches/fps_patches.cpp index 684421cb..1e466485 100644 --- a/UnleashedRecomp/patches/fps_patches.cpp +++ b/UnleashedRecomp/patches/fps_patches.cpp @@ -1,12 +1,10 @@ +#include #include #include #include #include #include -float m_lastLoadingFrameDelta = 0.0f; -std::chrono::high_resolution_clock::time_point m_lastLoadingFrameTime; - void DownForceDeltaTimeFixMidAsmHook(PPCRegister& f0) { f0.f64 = 30.0; @@ -79,14 +77,71 @@ void Camera2DSlopeLerpFixMidAsmHook(PPCRegister& t, PPCRegister& deltaTime) t.f64 = ComputeLerpFactor(t.f64, deltaTime.f64 / 60.0); } -void LoadingScreenSpeedFixMidAsmHook(PPCRegister& r4) +using namespace std::chrono_literals; + +static std::chrono::steady_clock::time_point g_next; + +void ApplicationUpdateMidAsmHook() { - auto now = std::chrono::high_resolution_clock::now(); + if (Config::FPS >= FPS_MIN && Config::FPS < FPS_MAX) + { + auto now = std::chrono::steady_clock::now(); - m_lastLoadingFrameDelta = std::min(std::chrono::duration(now - m_lastLoadingFrameTime).count(), 1.0f / 15.0f); - m_lastLoadingFrameTime = now; + if (now < g_next) + { + std::this_thread::sleep_for(std::chrono::floor(g_next - now - 2ms)); - auto pDeltaTime = (be*)g_memory.Translate(r4.u32); + while ((now = std::chrono::steady_clock::now()) < g_next) + std::this_thread::yield(); + } + else + { + g_next = now; + } - *pDeltaTime = m_lastLoadingFrameDelta; + g_next += 1000000000ns / Config::FPS; + } +} + +static std::chrono::steady_clock::time_point g_prev; + +bool LoadingUpdateMidAsmHook(PPCRegister& r31) +{ + auto now = std::chrono::steady_clock::now(); + double deltaTime = std::min(std::chrono::duration(now - g_prev).count(), 1.0 / 15.0); + g_prev = now; + + uint8_t* base = reinterpret_cast(g_memory.base); + uint32_t application = PPC_LOAD_U32(PPC_LOAD_U32(r31.u32 + 4)); + uint32_t update = PPC_LOAD_U32(PPC_LOAD_U32(application) + 20); + + g_ppcContext->r3.u32 = application; + g_ppcContext->f1.f64 = deltaTime; + reinterpret_cast(g_codeCache.Find(update))(*g_ppcContext, base); + + bool loading = PPC_LOAD_U8(0x83367A4C); + if (loading) + { + now = std::chrono::steady_clock::now(); + constexpr auto INTERVAL = 1000000000ns / 30; + auto next = now + (INTERVAL - now.time_since_epoch() % INTERVAL); + + std::this_thread::sleep_until(next); + } + + return loading; +} + +// ADXM_WaitVsync +PPC_FUNC_IMPL(__imp__sub_8312DBF8); +PPC_FUNC(sub_8312DBF8) +{ + auto now = std::chrono::steady_clock::now(); + constexpr auto INTERVAL = 1000000000ns / 60; + auto next = now + (INTERVAL - now.time_since_epoch() % INTERVAL); + + std::this_thread::sleep_for(std::chrono::floor(next - now - 1ms)); + + while (std::chrono::steady_clock::now() < next) + std::this_thread::yield(); } diff --git a/UnleashedRecomp/stdafx.h b/UnleashedRecomp/stdafx.h index 929f0f58..40590b8f 100644 --- a/UnleashedRecomp/stdafx.h +++ b/UnleashedRecomp/stdafx.h @@ -39,6 +39,7 @@ using Microsoft::WRL::ComPtr; #include #include #include +#include #include #include #include @@ -47,6 +48,7 @@ using Microsoft::WRL::ComPtr; #include #include #include +#include #include "framework.h" #include "mutex.h" diff --git a/UnleashedRecomp/ui/options_menu.cpp b/UnleashedRecomp/ui/options_menu.cpp index 50e737c0..e37017a4 100644 --- a/UnleashedRecomp/ui/options_menu.cpp +++ b/UnleashedRecomp/ui/options_menu.cpp @@ -850,7 +850,7 @@ static void DrawConfigOptions() DrawConfigOption(rowCount++, yOffset, &Config::ResolutionScale, true, nullptr, 0.25f, 1.0f, 2.0f); DrawConfigOption(rowCount++, yOffset, &Config::Fullscreen, true); DrawConfigOption(rowCount++, yOffset, &Config::VSync, true); - DrawConfigOption(rowCount++, yOffset, &Config::FPS, true, nullptr, 15, 120, 240); + DrawConfigOption(rowCount++, yOffset, &Config::FPS, true, nullptr, FPS_MIN, 120, FPS_MAX); DrawConfigOption(rowCount++, yOffset, &Config::Brightness, true); DrawConfigOption(rowCount++, yOffset, &Config::AntiAliasing, true); DrawConfigOption(rowCount++, yOffset, &Config::TransparencyAntiAliasing, Config::AntiAliasing != EAntiAliasing::None, &Localise("Options_Desc_NotAvailableMSAA")); diff --git a/UnleashedRecomp/user/config.h b/UnleashedRecomp/user/config.h index 685c9e06..f2c173ef 100644 --- a/UnleashedRecomp/user/config.h +++ b/UnleashedRecomp/user/config.h @@ -169,6 +169,9 @@ CONFIG_DEFINE_ENUM_TEMPLATE(ETripleBuffering) { "Off", ETripleBuffering::Off } }; +static constexpr int32_t FPS_MIN = 15; +static constexpr int32_t FPS_MAX = 240; + enum class EAntiAliasing : uint32_t { None = 0, diff --git a/UnleashedRecompLib/config/SWA.toml b/UnleashedRecompLib/config/SWA.toml index cc13d6a1..6436a26b 100644 --- a/UnleashedRecompLib/config/SWA.toml +++ b/UnleashedRecompLib/config/SWA.toml @@ -425,11 +425,6 @@ name = "ParticleTestDrawIndexedPrimitiveMidAsmHook" address = 0x827D25AC registers = ["r7"] -[[midasm_hook]] -name = "LoadingScreenSpeedFixMidAsmHook" -address = 0x824DAB60 -registers = ["r4"] - [[midasm_hook]] name = "MotionBlurPrevInvViewProjectionMidAsmHook" address = 0x82BA9E7C @@ -588,3 +583,14 @@ registers = ["r3"] name = "PostureDPadSupportMidAsmHook" address = 0x823CDA2C registers = ["r3"] + +[[midasm_hook]] +name = "ApplicationUpdateMidAsmHook" +address = 0x822C0EC8 + +[[midasm_hook]] +name = "LoadingUpdateMidAsmHook" +address = 0x825360C8 +registers = ["r31"] +jump_address_on_true = 0x825360C8 +jump_address_on_false = 0x82536140 \ No newline at end of file diff --git a/thirdparty/implot b/thirdparty/implot new file mode 160000 index 00000000..77674d27 --- /dev/null +++ b/thirdparty/implot @@ -0,0 +1 @@ +Subproject commit 77674d270e851d3f3718aad00234201af2b76ac9