diff --git a/Common/Settings.h b/Common/Settings.h index 17cee19d..075951c3 100644 --- a/Common/Settings.h +++ b/Common/Settings.h @@ -192,6 +192,7 @@ visit(fog_layer2_complexity, 0.055f) \ visit(fog_layer2_density_add, 100.0f) \ visit(fog_layer2_density_mult, 1.4f) \ + visit(LimitPerFrameFPS, 0.0f) \ visit(water_spec_mult_apt_staircase, 0.035f) \ visit(water_spec_mult_strange_area, 0.017f) \ visit(water_spec_mult_labyrinth, 0.017f) \ @@ -254,6 +255,7 @@ visit(HookDirectSound) \ visit(HookWndProc) \ visit(LetterSpacing) \ + visit(LimitPerFrameFPS) \ visit(LoadModulesFromMemory) \ visit(LockResolution) \ visit(NormalFontHeight) \ diff --git a/Common/Utils.cpp b/Common/Utils.cpp index 7cd6d42e..ce3044fd 100644 --- a/Common/Utils.cpp +++ b/Common/Utils.cpp @@ -16,6 +16,7 @@ #define WIN32_LEAN_AND_MEAN #include +#include #ifdef ISLAUNCHER #undef NTDDI_VERSION #define NTDDI_VERSION NTDDI_WINXPSP3 @@ -420,6 +421,32 @@ bool GetCoreCount(DWORD& pBits, DWORD& sBits) return false; } +void BusyWaitYield(DWORD RemainingMS) +{ + static bool supports_pause = []() { + int cpu_info[4] = { 0 }; + __cpuid(cpu_info, 1); // Query CPU features + return (cpu_info[3] & (1 << 26)) != 0; // Check for SSE2 support + }(); + + // If remaining time is very small (e.g., 1 ms or less), use busy-wait with no operations + if (RemainingMS < 3 && supports_pause) + { + // Use _mm_pause or __asm { nop } to prevent unnecessary CPU cycles +#ifdef YieldProcessor + YieldProcessor(); +#else + _mm_pause(); +#endif + } + else + { + // For larger remaining times, we can relax by yielding to the OS + // Sleep(0) yields without consuming CPU excessively + Sleep(0); // Let the OS schedule other tasks if there's significant time left + } +} + // Log process affinity void LogAffinity() { diff --git a/Common/Utils.h b/Common/Utils.h index 91af3eba..df97d41b 100644 --- a/Common/Utils.h +++ b/Common/Utils.h @@ -17,6 +17,7 @@ bool UpdateMemoryAddress(void *dataAddr, const void *dataBytes, size_t dataSize) bool WriteCalltoMemory(BYTE *dataAddr, const void *JMPAddr, DWORD count = 5); bool WriteJMPtoMemory(BYTE *dataAddr, const void *JMPAddr, DWORD count = 5); DWORD ReplaceMemoryBytes(void *dataSrc, void *dataDest, size_t size, DWORD start, DWORD distance, DWORD count = 0); +void BusyWaitYield(DWORD RemainingMS); void LogAffinity(); DWORD_PTR GetProcessMask(); void SetSingleCoreAffinity(); diff --git a/Resources/BuildNo.rc b/Resources/BuildNo.rc index c2f3f101..97e15f18 100644 --- a/Resources/BuildNo.rc +++ b/Resources/BuildNo.rc @@ -1 +1 @@ -#define BUILD_NUMBER 2218 +#define BUILD_NUMBER 2219 diff --git a/Wrappers/d3d8/IDirect3D8.cpp b/Wrappers/d3d8/IDirect3D8.cpp index aa9536d2..c398594d 100644 --- a/Wrappers/d3d8/IDirect3D8.cpp +++ b/Wrappers/d3d8/IDirect3D8.cpp @@ -417,6 +417,10 @@ void SetScreenAndWindowSize() { Logging::Log() << __FUNCTION__ << " Setting display mode: " << (ScreenMode == WINDOWED ? "Windowed" : ScreenMode == WINDOWED_FULLSCREEN ? "Windowed Fullscreen" : "Exclusive Fullscreen"); + if (ScreenMode != EXCLUSIVE_FULLSCREEN) + { + ShowCursor(FALSE); + } } LastScreenMode = ScreenMode; diff --git a/Wrappers/d3d8/IDirect3DDevice8.cpp b/Wrappers/d3d8/IDirect3DDevice8.cpp index d6eb4e8e..e287232b 100644 --- a/Wrappers/d3d8/IDirect3DDevice8.cpp +++ b/Wrappers/d3d8/IDirect3DDevice8.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include "Common\Utils.h" #include "stb_image.h" #include "stb_image_dds.h" @@ -1374,6 +1375,58 @@ bool m_IDirect3DDevice8::FixPauseMenuOnPresent() return PauseMenuFlag; } +void m_IDirect3DDevice8::LimitFrameRate() +{ + // Count the number of frames + Counter.FrameCounter++; + + // Get performance frequency if not already cached + static LARGE_INTEGER Frequency = {}; + if (!Frequency.QuadPart) + { + QueryPerformanceFrequency(&Frequency); + } + static LONGLONG TicksPerMS = Frequency.QuadPart / 1000; + + // Calculate the delay time in ticks + static long double PerFrameFPS = LimitPerFrameFPS ? LimitPerFrameFPS : (SetSixtyFPS ? 59.94 : 29.97); + static LONGLONG PreFrameTicks = static_cast(static_cast(Frequency.QuadPart) / PerFrameFPS); + + // Get next tick time + LARGE_INTEGER ClickTime = {}; + QueryPerformanceCounter(&ClickTime); + LONGLONG TargetEndTicks = Counter.LastPresentTime.QuadPart; + LONGLONG FramesSinceLastCall = ((ClickTime.QuadPart - Counter.LastPresentTime.QuadPart - 1) / PreFrameTicks) + 1; + if (Counter.LastPresentTime.QuadPart == 0 || FramesSinceLastCall > 2) + { + QueryPerformanceCounter(&Counter.LastPresentTime); + TargetEndTicks = Counter.LastPresentTime.QuadPart; + } + else + { + TargetEndTicks += FramesSinceLastCall * PreFrameTicks; + } + + // Wait for time to expire + bool DoLoop; + do { + QueryPerformanceCounter(&ClickTime); + LONGLONG RemainingTicks = TargetEndTicks - ClickTime.QuadPart; + + // Check if we still need to wait + DoLoop = RemainingTicks > 0; + + if (DoLoop) + { + // Busy wait until we reach the target time + BusyWaitYield(static_cast(RemainingTicks / TicksPerMS)); + } + } while (DoLoop); + + // Update the last present time + Counter.LastPresentTime.QuadPart = TargetEndTicks; +} + HRESULT m_IDirect3DDevice8::Present(CONST RECT* pSourceRect, CONST RECT* pDestRect, HWND hDestWindowOverride, CONST RGNDATA* pDirtyRegion) { Logging::LogDebug() << __FUNCTION__; @@ -1438,6 +1491,11 @@ HRESULT m_IDirect3DDevice8::Present(CONST RECT* pSourceRect, CONST RECT* pDestRe if (ClearScreen) { ClearScreen = false; + if (ScreenMode != EXCLUSIVE_FULLSCREEN) + { + LimitFrameRate(); + } + HRESULT hr = ProxyInterface->Present(pSourceRect, pDestRect, hDestWindowOverride, pDirtyRegion); if (SUCCEEDED(hr)) @@ -1476,6 +1534,11 @@ HRESULT m_IDirect3DDevice8::Present(CONST RECT* pSourceRect, CONST RECT* pDestRe // Present screen if (!PauseMenuFlag) { + if (ScreenMode != EXCLUSIVE_FULLSCREEN) + { + LimitFrameRate(); + } + hr = ProxyInterface->Present(pSourceRect, pDestRect, hDestWindowOverride, pDirtyRegion); if (SUCCEEDED(hr)) diff --git a/Wrappers/d3d8/IDirect3DDevice8.h b/Wrappers/d3d8/IDirect3DDevice8.h index 35f0b55e..1f5d0808 100644 --- a/Wrappers/d3d8/IDirect3DDevice8.h +++ b/Wrappers/d3d8/IDirect3DDevice8.h @@ -208,6 +208,12 @@ class m_IDirect3DDevice8 : public IDirect3DDevice8 UINT stream0Stride; }; + // Limit frame rate + struct { + DWORD FrameCounter = 0; + LARGE_INTEGER LastPresentTime = {}; + } Counter; + // Helper functions void EnableAntiAliasing(); void DisableAntiAliasing(); @@ -224,6 +230,7 @@ class m_IDirect3DDevice8 : public IDirect3DDevice8 void CaptureScreenShot(); HRESULT CreateDCSurface(EMUSURFACE& surface, LONG Width, LONG Height); void ReleaseDCSurface(EMUSURFACE& surface); + void LimitFrameRate(); public: m_IDirect3DDevice8(LPDIRECT3DDEVICE8 pDevice, m_IDirect3D8* pD3D) : ProxyInterface(pDevice), m_pD3D(pD3D)