From 478de8416527cd33b9f2323f2a30bc66655bfd18 Mon Sep 17 00:00:00 2001 From: somewhatlurker <52014015+somewhatlurker@users.noreply.github.com> Date: Sun, 1 Sep 2019 23:14:57 +1000 Subject: [PATCH] render: add lightmode for FPS limiter (reduces CPU usage... hopefully without impacting timing much) --- source-code/data/plugins/config.ini | 5 + .../source/plugins/Launcher/framework.h | 1 + source-code/source/plugins/Render/dllmain.cpp | 97 +++++++++++++++++-- source-code/source/plugins/Render/framework.h | 2 + 4 files changed, 96 insertions(+), 9 deletions(-) diff --git a/source-code/data/plugins/config.ini b/source-code/data/plugins/config.ini index a173c38..5e0fa94 100644 --- a/source-code/data/plugins/config.ini +++ b/source-code/data/plugins/config.ini @@ -42,6 +42,11 @@ Enhanced_Stage_Manager_Encore=1 [Graphics] # Set to -1 to unlock the frame rate. FPS.Limit=60 +# Reduces FPS Limiter CPU usage +# (may have less consistent performance) +FPS.Limit.LightMode=1 +# Enable debug warnings for lightweight limiter +FPS.Limit.Verbose=0 # Temporal Anti-Aliasing TAA=1 # Morphological Anti-Aliasing diff --git a/source-code/source/plugins/Launcher/framework.h b/source-code/source/plugins/Launcher/framework.h index 1579fc2..93448c0 100644 --- a/source-code/source/plugins/Launcher/framework.h +++ b/source-code/source/plugins/Launcher/framework.h @@ -170,6 +170,7 @@ ConfigOptionBase* optionsArray[] = { new DropdownOption(L"status_icons", PATCHES_SECTION, CONFIG_FILE, L"Status Icons:", L"Set the state of card reader and network status icons.", 3, std::vector({ L"Default", L"Hidden", L"Error", L"OK", L"Partial OK" })), new NumericOption(L"FPS.Limit", GRAPHICS_SECTION, CONFIG_FILE, L"FPS Limit:", L"Allows you to set a frame rate cap. Set to -1 to unlock the frame rate.", 60, -1, INT_MAX), + new BooleanOption(L"FPS.Limit.LightMode", GRAPHICS_SECTION, CONFIG_FILE, L"Use Lightweight Limiter", L"Makes the FPS limiter use less CPU.\nMay have less consistent performance.", true, false), new StringOption(L"command_line", LAUNCHER_SECTION, CONFIG_FILE, L"Command Line:", L"Allows setting command line parameters for the game when using the launcher.\nDisabling the launcher will bypass this.", L"", false), }; diff --git a/source-code/source/plugins/Render/dllmain.cpp b/source-code/source/plugins/Render/dllmain.cpp index 25a5fa0..85802d5 100644 --- a/source-code/source/plugins/Render/dllmain.cpp +++ b/source-code/source/plugins/Render/dllmain.cpp @@ -5,7 +5,9 @@ #include #include #include +#include #include +#include #include @@ -95,18 +97,76 @@ __int64 hookedParseParameters(int a1, __int64* a2) return divaParseParameters(a1, a2); } -time_point nextUpdate; -microseconds expectedFrameDuration(1000000 / nFpsLimit); +time_point sleepUntil = high_resolution_clock::now(); +time_point nextUpdate = high_resolution_clock::now(); +nanoseconds expectedFrameDuration(1000000000 / nFpsLimit); +nanoseconds sleepWindow(2500000); __int64 __fastcall hookedEngineUpdate(__int64 a1) { - const auto beforeUpdate = high_resolution_clock::now(); - if (beforeUpdate < nextUpdate) + auto timeNow = high_resolution_clock::now(); + + if (timeNow < nextUpdate) return 0; const auto result = divaEngineUpdate(a1); - nextUpdate = beforeUpdate + expectedFrameDuration; + timeNow = high_resolution_clock::now(); + + // increment nextUpdate by fixed interval to unsure timing accuracy between frames + // (ignore time spent outside the update) + nextUpdate += expectedFrameDuration; + + // if dropping frames, run as fast as possible + // and also change nextUpdate's timing to match current time + // (so frame n+2 will be at least 1/nFpsLimit seconds from frame n+1) + if (nextUpdate < timeNow) + nextUpdate = timeNow; + + return result; +} + +__int64 __fastcall hookedEngineUpdateLight(__int64 a1) +{ + auto timeNow = high_resolution_clock::now(); + + // sleep until the approximate correct time to save CPU usage, + // then change to instantly returning when timing is more critical + if (timeNow < sleepUntil) + { + std::this_thread::sleep_until(sleepUntil); + + if (nVerboseLimiter) + { + timeNow = high_resolution_clock::now(); + nanoseconds timeDifference = (timeNow - sleepUntil); + if (timeDifference > sleepWindow) // no need for abs because it doesn't matter if this is early + { + printf("FPS LIMITER SLEEP TIME OUTSIDE ALLOWED WINDOW\n", timeDifference.count()); + printf(" Target sleep until time: %lld, Actual time: %lld\n", sleepUntil.time_since_epoch().count(), timeNow.time_since_epoch().count()); + printf(" (Difference: %lld, Window: %lld)\n", timeDifference.count(), sleepWindow.count()); + } + } + } + + if (timeNow < nextUpdate) + return 0; + + const auto result = divaEngineUpdate(a1); + + timeNow = high_resolution_clock::now(); + + // increment nextUpdate by fixed interval to unsure timing accuracy between frames + // (ignore time spent outside the update) + nextUpdate += expectedFrameDuration; + + // if dropping frames, run as fast as possible + // and also change nextUpdate's timing to match current time + // (so frame n+2 will be at least 1/nFpsLimit seconds from frame n+1) + if (nextUpdate < timeNow) + nextUpdate = timeNow; + + sleepUntil = nextUpdate - sleepWindow; return result; } @@ -132,10 +192,29 @@ BOOL APIENTRY DllMain(HMODULE hModule, if (nFpsLimit > 0) { - DetourTransactionBegin(); - DetourUpdateThread(GetCurrentThread()); - DetourAttach(&(PVOID&)divaEngineUpdate, hookedEngineUpdate); - DetourTransactionCommit(); + if (nUseLightLimiter) + { + // set sleep time resolution to 2ms or device minimum + TIMECAPS tc; + + if (timeGetDevCaps(&tc, sizeof(TIMECAPS)) == MMSYSERR_NOERROR) + { + UINT wTimerRes = min(max(tc.wPeriodMin, 2), tc.wPeriodMax); + timeBeginPeriod(wTimerRes); + } + + DetourTransactionBegin(); + DetourUpdateThread(GetCurrentThread()); + DetourAttach(&(PVOID&)divaEngineUpdate, hookedEngineUpdateLight); + DetourTransactionCommit(); + } + else + { + DetourTransactionBegin(); + DetourUpdateThread(GetCurrentThread()); + DetourAttach(&(PVOID&)divaEngineUpdate, hookedEngineUpdate); + DetourTransactionCommit(); + } } } return TRUE; diff --git a/source-code/source/plugins/Render/framework.h b/source-code/source/plugins/Render/framework.h index e8cf93e..084cd0b 100644 --- a/source-code/source/plugins/Render/framework.h +++ b/source-code/source/plugins/Render/framework.h @@ -36,6 +36,8 @@ int nBitDepth = GetPrivateProfileIntW(L"resolution", L"bitdepth", 32, CONFIG_FIL int nRefreshRate = GetPrivateProfileIntW(L"resolution", L"refreshrate", 60, CONFIG_FILE); int nFpsLimit = GetPrivateProfileIntW(L"graphics", L"fps.limit", 60, CONFIG_FILE); +int nUseLightLimiter = GetPrivateProfileIntW(L"graphics", L"fps.limit.lightmode", TRUE, CONFIG_FILE); +int nVerboseLimiter = GetPrivateProfileIntW(L"graphics", L"fps.limit.verbose", FALSE, CONFIG_FILE); // used to trick Optimus into switching to the NVIDIA GPU HMODULE nvcudaModule = LoadLibraryW(L"nvcuda.dll");