diff --git a/dllmain/DisplayTweaks.cpp b/dllmain/DisplayTweaks.cpp index 2411788..5f4011f 100644 --- a/dllmain/DisplayTweaks.cpp +++ b/dllmain/DisplayTweaks.cpp @@ -3,6 +3,7 @@ #include "Patches.h" #include "Game.h" #include "Settings.h" +#include int g_UserRefreshRate; @@ -76,8 +77,112 @@ BOOL __stdcall MoveWindow_Hook(HWND hWnd, int X, int Y, int nWidth, int nHeight, return MoveWindow(hWnd, windowX, windowY, nWidth, nHeight, bRepaint); } +double FramelimiterFrequency = 0; +double FramelimiterPrevTicks = 0; +void Framelimiter_Hook(uint8_t isAliveEvt_result) +{ + // Change thread to core 0 before running QueryPerformance* funcs, game does this, so guess we should too + HANDLE curThread = GetCurrentThread(); + DWORD_PTR prevAffinityMask = SetThreadAffinityMask(curThread, 0); + + static bool framelimiterInited = false; + if (!framelimiterInited) + { + LARGE_INTEGER result; + QueryPerformanceFrequency(&result); + FramelimiterFrequency = (double)result.QuadPart / 1000.0; + + QueryPerformanceCounter(&result); + FramelimiterPrevTicks = (double)result.QuadPart / FramelimiterFrequency; + + typedef MMRESULT(__stdcall* timeBeginPeriod_Fn) (UINT Period); + timeBeginPeriod_Fn timeBeginPeriod_actual = (timeBeginPeriod_Fn)GetProcAddress(LoadLibraryA("winmm.dll"), "timeBeginPeriod"); + timeBeginPeriod_actual(1); + + framelimiterInited = true; + } + + int gameFramerate = GameVariableFrameRate(); + + // The games IsAliveEvt check seems to (indirectly) result in framelimiter loop limiting to 60FPS + // maybe a remnant of some time when more framerate options were available? + if (isAliveEvt_result && gameFramerate != 30) + gameFramerate = 60; + + double TargetFrametime = 1000.0 / (double)gameFramerate; + + double timeElapsed = 0; + double timeCurrent = 0; + do + { + // Framelimiter based on FPS_ACCURATE code from + // https://github.com/ThirteenAG/d3d9-wrapper/blob/c1480b0c1b40e0ba7b55b8660bd67f911a967421/source/dllmain.cpp#L46 + + LARGE_INTEGER counter; + QueryPerformanceCounter(&counter); + timeCurrent = (double)counter.QuadPart / FramelimiterFrequency; + timeElapsed = timeCurrent - FramelimiterPrevTicks; + + if (TargetFrametime <= timeElapsed || pConfig->bDisableFramelimiting) + break; + else if (TargetFrametime - timeElapsed > 2.0) // > 2ms + Sleep(1); // Sleep for ~1ms + else + Sleep(0); // yield thread's time-slice (does not actually sleep) + } + while (TargetFrametime > timeElapsed); + + FramelimiterPrevTicks = timeCurrent; + + // Not really sure what the second part of IsAliveEvt check is doing + // Seems to skip setting timeElapsed to the fixed FramelimiterTargetFrametime at least + // Guess that means the true timeElapsed gets passed to the game? (after being limited to 60 like above) + if (isAliveEvt_result && gameFramerate != 30) + { + if (timeElapsed > 33.333333333333333) + timeElapsed = 33.333333333333333; + } + else + { + // TODO: using the actual time elapsed since last frame instead of FramelimiterTargetFrametime would solve slowdown issues + // (similar to https://github.com/nipkownix/re4_tweaks/pull/25) + // but it's not known how well the game works with values that aren't 0.5 (60fps) or 1 (30fps) + // so for now we'll just work pretty much the same as the game itself, unless UseDynamicFrametime is set + if (!pConfig->bUseDynamicFrametime) + timeElapsed = TargetFrametime; + } + + GlobalPtr()->deltaTime_70 = float((timeElapsed / 1000) * 30.0); + + SetThreadAffinityMask(curThread, prevAffinityMask); +} + void Init_DisplayTweaks() { + // Implements new reduced-CPU-usage limiter + if (pConfig->bReplaceFramelimiter) + { + // nop beginning of framelimiter code (sets up thread affinity to core 0) + auto pattern = hook::pattern("A3 ? ? ? ? 6A 00 FF 15 ? ? ? ? 50 FF"); + uint8_t* framelimiterStart = pattern.count(1).get(0).get(5); + pattern = hook::pattern("E8 ? ? ? ? 85 C0 75 ? D9 EE EB ?"); + uint8_t* framelimiterEnd = pattern.count(1).get(0).get(0); + Nop(framelimiterStart, framelimiterEnd - framelimiterStart); // 6549C3 to 6549D9 (1.1.0) + + pattern = hook::pattern("B9 ? ? ? ? E8 ? ? ? ? 84 C0 74 ? E8 ? ? ? ? 83 F8 1E"); + framelimiterStart = pattern.count(1).get(0).get(0xA); // 654A1E + pattern = hook::pattern("8D 85 ? ? ? ? 50 FF D3 DF AD ? ? ? ? 8D 8D"); + framelimiterEnd = pattern.count(1).get(0).get(0); // 654B0A + + Nop(framelimiterStart, framelimiterEnd - framelimiterStart); + + Patch(framelimiterStart, uint8_t(0x50)); // push eax (pass return value of EventMgr::IsAliveEvt) + InjectHook(framelimiterStart + 1, Framelimiter_Hook, PATCH_CALL); + Patch(framelimiterStart + 1 + 5, { 0x83, 0xc4, 0x04 }); // add esp, 4 (needed to allow WinMain to exit properly) + + spd::log()->info("Framelimiter replaced"); + } + // Fix broken effects Init_FilterXXFixes(); diff --git a/dllmain/Game.cpp b/dllmain/Game.cpp index 74b7235..3b20607 100644 --- a/dllmain/Game.cpp +++ b/dllmain/Game.cpp @@ -17,6 +17,12 @@ bool GameVersionIsDebug() return gameIsDebugBuild; } +uint32_t* ptrGameVariableFrameRate; +int GameVariableFrameRate() +{ + return *(int32_t*)(ptrGameVariableFrameRate); +} + uint32_t* ptrLastUsedDevice = nullptr; InputDevices LastUsedDevice() { @@ -166,6 +172,10 @@ bool Init_Game() con.AddConcatLog("Game version = ", GameVersion().data()); #endif + // Pointer to users variableframerate setting value + pattern = hook::pattern("89 0D ? ? ? ? 0F 95 ? 88 15 ? ? ? ? D9 1D ? ? ? ? A3 ? ? ? ? DB 46 ? D9 1D ? ? ? ? 8B 4E ? 89 0D ? ? ? ? 8B 4D ? 5E"); + ptrGameVariableFrameRate = *pattern.count(1).get(0).get(2); + // LastUsedDevice pointer pattern = hook::pattern("A1 ? ? ? ? 85 C0 74 ? 83 F8 ? 74 ? 81 F9"); ptrLastUsedDevice = *pattern.count(1).get(0).get(1); diff --git a/dllmain/Game.h b/dllmain/Game.h index 3aabaf1..cc268ed 100644 --- a/dllmain/Game.h +++ b/dllmain/Game.h @@ -24,6 +24,7 @@ extern SND_CTRL* Snd_ctrl_work; std::string GameVersion(); bool GameVersionIsDebug(); +int GameVariableFrameRate(); InputDevices LastUsedDevice(); bool isController(); bool isKeyboardMouse(); diff --git a/dllmain/Settings.cpp b/dllmain/Settings.cpp index 352d9f6..8e2748a 100644 --- a/dllmain/Settings.cpp +++ b/dllmain/Settings.cpp @@ -138,13 +138,15 @@ void Config::ReadSettings(std::string_view ini_path) else pConfig->bEnableFOV = false; + pConfig->bDisableVsync = iniReader.ReadBoolean("DISPLAY", "DisableVsync", pConfig->bDisableVsync); + pConfig->bUltraWideAspectSupport = iniReader.ReadBoolean("DISPLAY", "UltraWideAspectSupport", pConfig->bUltraWideAspectSupport); pConfig->bSideAlignHUD = iniReader.ReadBoolean("DISPLAY", "SideAlignHUD", pConfig->bSideAlignHUD); pConfig->bStretchFullscreenImages = iniReader.ReadBoolean("DISPLAY", "StretchFullscreenImages", pConfig->bStretchFullscreenImages); pConfig->bStretchVideos = iniReader.ReadBoolean("DISPLAY", "StretchVideos", pConfig->bStretchVideos); pConfig->bRemove16by10BlackBars = iniReader.ReadBoolean("DISPLAY", "Remove16by10BlackBars", pConfig->bRemove16by10BlackBars); - pConfig->bDisableVsync = iniReader.ReadBoolean("DISPLAY", "DisableVsync", pConfig->bDisableVsync); + pConfig->bReplaceFramelimiter = iniReader.ReadBoolean("DISPLAY", "ReplaceFramelimiter", pConfig->bReplaceFramelimiter); pConfig->bFixDPIScale = iniReader.ReadBoolean("DISPLAY", "FixDPIScale", pConfig->bFixDPIScale); pConfig->bFixDisplayMode = iniReader.ReadBoolean("DISPLAY", "FixDisplayMode", pConfig->bFixDisplayMode); pConfig->iCustomRefreshRate = iniReader.ReadInteger("DISPLAY", "CustomRefreshRate", pConfig->iCustomRefreshRate); @@ -360,6 +362,8 @@ void Config::ReadSettings(std::string_view ini_path) // DEBUG pConfig->bVerboseLog = iniReader.ReadBoolean("DEBUG", "VerboseLog", pConfig->bVerboseLog); pConfig->bNeverHideCursor = iniReader.ReadBoolean("DEBUG", "NeverHideCursor", pConfig->bNeverHideCursor); + pConfig->bUseDynamicFrametime = iniReader.ReadBoolean("DEBUG", "UseDynamicFrametime", pConfig->bUseDynamicFrametime); + pConfig->bDisableFramelimiting = iniReader.ReadBoolean("DEBUG", "DisableFramelimiting", pConfig->bDisableFramelimiting); } std::mutex settingsThreadRunningMutex; @@ -406,6 +410,7 @@ DWORD WINAPI WriteSettingsThread(LPVOID lpParameter) // DISPLAY iniReader.WriteFloat("DISPLAY", "FOVAdditional", pConfig->fFOVAdditional); + iniReader.WriteBoolean("DISPLAY", "DisableVsync", pConfig->bDisableVsync); iniReader.WriteBoolean("DISPLAY", "UltraWideAspectSupport", pConfig->bUltraWideAspectSupport); iniReader.WriteBoolean("DISPLAY", "SideAlignHUD", pConfig->bSideAlignHUD); @@ -413,7 +418,7 @@ DWORD WINAPI WriteSettingsThread(LPVOID lpParameter) iniReader.WriteBoolean("DISPLAY", "StretchVideos", pConfig->bStretchVideos); iniReader.WriteBoolean("DISPLAY", "Remove16by10BlackBars", pConfig->bRemove16by10BlackBars); - iniReader.WriteBoolean("DISPLAY", "DisableVsync", pConfig->bDisableVsync); + iniReader.WriteBoolean("DISPLAY", "ReplaceFramelimiter", pConfig->bReplaceFramelimiter); iniReader.WriteBoolean("DISPLAY", "FixDPIScale", pConfig->bFixDPIScale); iniReader.WriteBoolean("DISPLAY", "FixDisplayMode", pConfig->bFixDisplayMode); iniReader.WriteInteger("DISPLAY", "CustomRefreshRate", pConfig->iCustomRefreshRate); @@ -550,12 +555,13 @@ void Config::LogSettings() // DISPLAY spd::log()->info("+ DISPLAY------------------------+-----------------+"); spd::log()->info("| {:<30} | {:>15} |", "FOVAdditional", pConfig->fFOVAdditional); + spd::log()->info("| {:<30} | {:>15} |", "DisableVsync", pConfig->bDisableVsync ? "true" : "false"); spd::log()->info("| {:<30} | {:>15} |", "UltraWideAspectSupport", pConfig->bUltraWideAspectSupport ? "true" : "false"); spd::log()->info("| {:<30} | {:>15} |", "SideAlignHUD", pConfig->bSideAlignHUD ? "true" : "false"); spd::log()->info("| {:<30} | {:>15} |", "StretchFullscreenImages", pConfig->bStretchFullscreenImages ? "true" : "false"); spd::log()->info("| {:<30} | {:>15} |", "StretchVideos", pConfig->bStretchVideos ? "true" : "false"); spd::log()->info("| {:<30} | {:>15} |", "Remove16by10BlackBars", pConfig->bRemove16by10BlackBars ? "true" : "false"); - spd::log()->info("| {:<30} | {:>15} |", "DisableVsync", pConfig->bDisableVsync ? "true" : "false"); + spd::log()->info("| {:<30} | {:>15} |", "ReplaceFramelimiter", pConfig->bReplaceFramelimiter ? "true" : "false"); spd::log()->info("| {:<30} | {:>15} |", "FixDPIScale", pConfig->bFixDPIScale ? "true" : "false"); spd::log()->info("| {:<30} | {:>15} |", "FixDisplayMode", pConfig->bFixDisplayMode ? "true" : "false"); spd::log()->info("| {:<30} | {:>15} |", "CustomRefreshRate", pConfig->iCustomRefreshRate); @@ -698,5 +704,7 @@ void Config::LogSettings() spd::log()->info("+ DEBUG--------------------------+-----------------+"); spd::log()->info("| {:<30} | {:>15} |", "VerboseLog", pConfig->bVerboseLog ? "true" : "false"); spd::log()->info("| {:<30} | {:>15} |", "NeverHideCursor", pConfig->bNeverHideCursor ? "true" : "false"); + spd::log()->info("| {:<30} | {:>15} |", "UseDynamicFrametime", pConfig->bUseDynamicFrametime ? "true" : "false"); + spd::log()->info("| {:<30} | {:>15} |", "DisableFramelimiting", pConfig->bDisableFramelimiting ? "true" : "false"); spd::log()->info("+--------------------------------+-----------------+"); } \ No newline at end of file diff --git a/dllmain/Settings.h b/dllmain/Settings.h index 443bd5b..d2c257e 100644 --- a/dllmain/Settings.h +++ b/dllmain/Settings.h @@ -10,12 +10,13 @@ class Config // DISPLAY float fFOVAdditional = 0.0f; bool bEnableFOV = false; + bool bDisableVsync = false; bool bUltraWideAspectSupport = true; bool bSideAlignHUD = true; bool bStretchFullscreenImages = false; bool bStretchVideos = false; bool bRemove16by10BlackBars = true; - bool bDisableVsync = false; + bool bReplaceFramelimiter = true; bool bFixDPIScale = true; bool bFixDisplayMode = true; int iCustomRefreshRate = -1; @@ -131,6 +132,8 @@ class Config // DEBUG bool bVerboseLog = false; bool bNeverHideCursor = false; + bool bUseDynamicFrametime = false; + bool bDisableFramelimiting = false; bool HasUnsavedChanges = false; diff --git a/dllmain/cfgMenu.cpp b/dllmain/cfgMenu.cpp index b67b339..66fef7b 100644 --- a/dllmain/cfgMenu.cpp +++ b/dllmain/cfgMenu.cpp @@ -422,6 +422,22 @@ void cfgMenuRender() pConfig->fFOVAdditional = 0.0f; } + // DisableVsync + { + ImGui_ColumnSwitch(); + + if (ImGui::Checkbox("DisableVsync", &pConfig->bDisableVsync)) + { + pConfig->HasUnsavedChanges = true; + NeedsToRestart = true; + } + + ImGui_ItemSeparator(); + + ImGui::Dummy(ImVec2(10, 10)); + ImGui::TextWrapped("Force V-Sync to be disabled. For some reason the vanilla game doesn't provide a functional way to do this."); + } + // Aspect ratio tweaks { ImGui_ColumnSwitch(); @@ -463,11 +479,11 @@ void cfgMenuRender() ImGui::TextWrapped("(Change the resolution for this setting to take effect)"); } - // DisableVsync + // ReplaceFramelimiter { ImGui_ColumnSwitch(); - if (ImGui::Checkbox("DisableVsync", &pConfig->bDisableVsync)) + if (ImGui::Checkbox("ReplaceFramelimiter", &pConfig->bReplaceFramelimiter)) { pConfig->HasUnsavedChanges = true; NeedsToRestart = true; @@ -476,7 +492,8 @@ void cfgMenuRender() ImGui_ItemSeparator(); ImGui::Dummy(ImVec2(10, 10)); - ImGui::TextWrapped("Force V-Sync to be disabled. For some reason the vanilla game doesn't provide a functional way to do this."); + ImGui::TextWrapped("Replaces the games 60/30FPS framelimiter with our own version, which reduces CPU usage quite a lot."); + ImGui::TextWrapped("(experimental, not known if the new framelimiter performs the same as the old one yet)"); } // FixDPIScale diff --git a/settings/settings_string.h b/settings/settings_string.h index e42ca9b..a2081a2 100644 --- a/settings/settings_string.h +++ b/settings/settings_string.h @@ -4,6 +4,9 @@ const char* defaultSettings = R""""( ; Additional FOV value. 20 seems good for most cases. FOVAdditional = 0.0 +; Force V-Sync to be disabled. For some reason the vanilla game doesn't provide a functional way to do this. +DisableVsync = false + ; Fixes the incorrect aspect ratio when playing in ultrawide resolutions, ; preventing the image from being cut off and the HUD appearing off-screen. UltraWideAspectSupport = true @@ -21,10 +24,11 @@ StretchVideos = false ; Removes top and bottom black bars that are present when playing in 16:10. ; Will crop a few pixels from each side of the screen. -Remove16by10BlackBars = false +Remove16by10BlackBars = true -; Force V-Sync to be disabled. For some reason the vanilla game doesn't provide a functional way to do this. -DisableVsync = false +; Replaces the games 60/30FPS framelimiter with our own version, which reduces CPU usage quite a lot. +; (experimental, not known if the new framelimiter performs the same as the old one yet) +ReplaceFramelimiter = true ; Forces game to run at normal 100% DPI scaling, fixes resolution issues for players that have above 100% DPI scaling set. FixDPIScale = true @@ -346,4 +350,14 @@ DisableMenuTip = false ; Logs extra information. VerboseLog = false NeverHideCursor = false -)""""; \ No newline at end of file + +; Passes the actual elapsed frametime to the game instead of a fixed 30/60FPS frametime. +; Should help reduce slowdown in-game when FPS fails to reach the games framerate setting. +; (experimental, certain things may act strange when using non-fixed frametime, especially audio) +UseDynamicFrametime = false + +; Disables any kind of framelimiting. +; Useful for comparing "true" FPS/frametime when making performance-related changes. +; (requires ReplaceFramelimiter = true, recommend DisableVsync too) +DisableFramelimiting = false +)"""";