diff --git a/DDrawCompat/Common/ScopedThreadPriority.h b/DDrawCompat/Common/ScopedThreadPriority.h new file mode 100644 index 0000000..7e62986 --- /dev/null +++ b/DDrawCompat/Common/ScopedThreadPriority.h @@ -0,0 +1,24 @@ +#pragma once + +#include + +namespace Compat +{ + class ScopedThreadPriority + { + public: + ScopedThreadPriority(int priority) + : m_prevPriority(GetThreadPriority(GetCurrentThread())) + { + SetThreadPriority(GetCurrentThread(), priority); + } + + ~ScopedThreadPriority() + { + SetThreadPriority(GetCurrentThread(), m_prevPriority); + } + + private: + int m_prevPriority; + }; +} diff --git a/DDrawCompat/Common/Time.cpp b/DDrawCompat/Common/Time.cpp index 4c8f628..42642af 100644 --- a/DDrawCompat/Common/Time.cpp +++ b/DDrawCompat/Common/Time.cpp @@ -5,24 +5,23 @@ namespace Time { long long g_qpcFrequency = 0; - HANDLE g_waitableTimer = nullptr; void init() { LARGE_INTEGER qpc; QueryPerformanceFrequency(&qpc); g_qpcFrequency = qpc.QuadPart; - - g_waitableTimer = CreateWaitableTimer(nullptr, FALSE, nullptr); } void waitForNextTick() { + thread_local HANDLE waitableTimer = CreateWaitableTimer(nullptr, FALSE, nullptr); + LARGE_INTEGER due = {}; due.QuadPart = -1; - if (!g_waitableTimer || - !SetWaitableTimer(g_waitableTimer, &due, 0, nullptr, nullptr, FALSE) || - WAIT_OBJECT_0 != WaitForSingleObject(g_waitableTimer, INFINITE)) + if (!waitableTimer || + !SetWaitableTimer(waitableTimer, &due, 0, nullptr, nullptr, FALSE) || + WAIT_OBJECT_0 != WaitForSingleObject(waitableTimer, INFINITE)) { Sleep(1); } diff --git a/DDrawCompat/Config/Config.cpp b/DDrawCompat/Config/Config.cpp index 21d0c98..7ba390d 100644 --- a/DDrawCompat/Config/Config.cpp +++ b/DDrawCompat/Config/Config.cpp @@ -15,6 +15,7 @@ namespace Config Settings::DisplayResolution displayResolution; Settings::DpiAwareness dpiAwareness; Settings::ForceD3D9On12 forceD3D9On12; + Settings::FpsLimiter fpsLimiter; Settings::FullscreenMode fullscreenMode; Settings::LogLevel logLevel; Settings::RemoveBorders removeBorders; diff --git a/DDrawCompat/Config/Config.h b/DDrawCompat/Config/Config.h index abd7802..63bc596 100644 --- a/DDrawCompat/Config/Config.h +++ b/DDrawCompat/Config/Config.h @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -42,6 +43,7 @@ namespace Config extern Settings::DisplayResolution displayResolution; extern Settings::DpiAwareness dpiAwareness; extern Settings::ForceD3D9On12 forceD3D9On12; + extern Settings::FpsLimiter fpsLimiter; extern Settings::FullscreenMode fullscreenMode; extern Settings::LogLevel logLevel; extern Settings::RemoveBorders removeBorders; diff --git a/DDrawCompat/Config/Settings/FpsLimiter.cpp b/DDrawCompat/Config/Settings/FpsLimiter.cpp new file mode 100644 index 0000000..88e9428 --- /dev/null +++ b/DDrawCompat/Config/Settings/FpsLimiter.cpp @@ -0,0 +1,26 @@ +#include + +namespace Config +{ + namespace Settings + { + FpsLimiter::FpsLimiter() + : MappedSetting("FpsLimiter", "off", { + {"off", OFF}, + {"flipstart", FLIPSTART}, + {"flipend", FLIPEND}, + {"msgloop", MSGLOOP} + }) + { + } + + Setting::ParamInfo FpsLimiter::getParamInfo() const + { + if (OFF != m_value) + { + return { "MaxFPS", 10, 200, 60, m_param }; + } + return {}; + } + } +} diff --git a/DDrawCompat/Config/Settings/FpsLimiter.h b/DDrawCompat/Config/Settings/FpsLimiter.h new file mode 100644 index 0000000..c4ab5bf --- /dev/null +++ b/DDrawCompat/Config/Settings/FpsLimiter.h @@ -0,0 +1,22 @@ +#pragma once + +#include + +namespace Config +{ + namespace Settings + { + class FpsLimiter : public MappedSetting + { + public: + static const UINT OFF = 0; + static const UINT FLIPSTART = 1; + static const UINT FLIPEND = 2; + static const UINT MSGLOOP = 3; + + FpsLimiter(); + + virtual ParamInfo getParamInfo() const override; + }; + } +} diff --git a/DDrawCompat/DDraw/RealPrimarySurface.cpp b/DDrawCompat/DDraw/RealPrimarySurface.cpp index c5076bd..ccf1688 100644 --- a/DDrawCompat/DDraw/RealPrimarySurface.cpp +++ b/DDrawCompat/DDraw/RealPrimarySurface.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -748,4 +749,33 @@ namespace DDraw } return true; } + + void RealPrimarySurface::waitForFlipFpsLimit() + { + static long long g_qpcPrevWaitEnd = Time::queryPerformanceCounter() - Time::g_qpcFrequency; + auto qpcNow = Time::queryPerformanceCounter(); + auto qpcWaitEnd = g_qpcPrevWaitEnd + Time::g_qpcFrequency / Config::fpsLimiter.getParam(); + if (qpcNow - qpcWaitEnd >= 0) + { + g_qpcPrevWaitEnd = qpcNow; + g_qpcDelayedFlipEnd = qpcNow; + return; + } + g_qpcPrevWaitEnd = qpcWaitEnd; + g_qpcDelayedFlipEnd = qpcWaitEnd; + + Compat::ScopedThreadPriority prio(THREAD_PRIORITY_TIME_CRITICAL); + while (Time::qpcToMs(qpcWaitEnd - qpcNow) > 0) + { + Time::waitForNextTick(); + flush(); + qpcNow = Time::queryPerformanceCounter(); + } + + while (qpcWaitEnd - qpcNow > 0) + { + qpcNow = Time::queryPerformanceCounter(); + } + g_qpcDelayedFlipEnd = Time::queryPerformanceCounter(); + } } diff --git a/DDrawCompat/DDraw/RealPrimarySurface.h b/DDrawCompat/DDraw/RealPrimarySurface.h index ba0e83d..a1f4f1f 100644 --- a/DDrawCompat/DDraw/RealPrimarySurface.h +++ b/DDrawCompat/DDraw/RealPrimarySurface.h @@ -34,5 +34,6 @@ namespace DDraw static void setUpdateReady(); static void updateDevicePresentationWindowPos(); static bool waitForFlip(CompatWeakPtr surface); + static void waitForFlipFpsLimit(); }; } diff --git a/DDrawCompat/DDraw/Surfaces/PrimarySurfaceImpl.cpp b/DDrawCompat/DDraw/Surfaces/PrimarySurfaceImpl.cpp index e8d6f57..3492147 100644 --- a/DDrawCompat/DDraw/Surfaces/PrimarySurfaceImpl.cpp +++ b/DDrawCompat/DDraw/Surfaces/PrimarySurfaceImpl.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -127,6 +128,12 @@ namespace DDraw RealPrimarySurface::setUpdateReady(); RealPrimarySurface::flush(); RealPrimarySurface::waitForFlip(m_data->getDDS()); + + if (Config::Settings::FpsLimiter::FLIPSTART == Config::fpsLimiter.get()) + { + RealPrimarySurface::waitForFlipFpsLimit(); + } + auto surfaceTargetOverride(CompatPtr::from(lpDDSurfaceTargetOverride)); const bool isFlipEmulated = 0 != (PrimarySurface::getOrigCaps() & DDSCAPS_SYSTEMMEMORY); if (isFlipEmulated) @@ -137,7 +144,12 @@ namespace DDraw caps.dwCaps = DDSCAPS_BACKBUFFER; getOrigVtable(This).GetAttachedSurface(This, &caps, &surfaceTargetOverride.getRef()); } - return Blt(This, nullptr, surfaceTargetOverride.get(), nullptr, DDBLT_WAIT, nullptr); + HRESULT result = Blt(This, nullptr, surfaceTargetOverride.get(), nullptr, DDBLT_WAIT, nullptr); + if (SUCCEEDED(result) && Config::Settings::FpsLimiter::FLIPEND == Config::fpsLimiter.get()) + { + RealPrimarySurface::waitForFlipFpsLimit(); + } + return result; } HRESULT result = SurfaceImpl::Flip(This, surfaceTargetOverride, DDFLIP_WAIT); @@ -147,7 +159,13 @@ namespace DDraw } PrimarySurface::updateFrontResource(); - return RealPrimarySurface::flip(surfaceTargetOverride, dwFlags); + result = RealPrimarySurface::flip(surfaceTargetOverride, dwFlags); + if (SUCCEEDED(result) && Config::Settings::FpsLimiter::FLIPEND == Config::fpsLimiter.get()) + { + DDraw::RealPrimarySurface::waitForFlip(m_data->getDDS()); + RealPrimarySurface::waitForFlipFpsLimit(); + } + return result; } template diff --git a/DDrawCompat/DDraw/Surfaces/SurfaceImpl.cpp b/DDrawCompat/DDraw/Surfaces/SurfaceImpl.cpp index dd5f8bf..8e1328d 100644 --- a/DDrawCompat/DDraw/Surfaces/SurfaceImpl.cpp +++ b/DDrawCompat/DDraw/Surfaces/SurfaceImpl.cpp @@ -14,6 +14,7 @@ #include #include #include +#include namespace { @@ -110,6 +111,7 @@ namespace DDraw TSurface* This, LPRECT lpDestRect, TSurface* lpDDSrcSurface, LPRECT lpSrcRect, DWORD dwFlags, LPDDBLTFX lpDDBltFx) { + Gdi::WinProc::startFrame(); RealPrimarySurface::waitForFlip(m_data->getDDS()); DirectDrawClipper::update(); return blt(This, lpDDSrcSurface, lpSrcRect, [=](TSurface* This, TSurface* lpDDSrcSurface, LPRECT lpSrcRect) @@ -120,6 +122,7 @@ namespace DDraw HRESULT SurfaceImpl::BltFast( TSurface* This, DWORD dwX, DWORD dwY, TSurface* lpDDSrcSurface, LPRECT lpSrcRect, DWORD dwTrans) { + Gdi::WinProc::startFrame(); RealPrimarySurface::waitForFlip(m_data->getDDS()); return blt(This, lpDDSrcSurface, lpSrcRect, [=](TSurface* This, TSurface* lpDDSrcSurface, LPRECT lpSrcRect) { return getOrigVtable(This).BltFast(This, dwX, dwY, lpDDSrcSurface, lpSrcRect, dwTrans); }); @@ -145,6 +148,7 @@ namespace DDraw template HRESULT SurfaceImpl::GetDC(TSurface* This, HDC* lphDC) { + Gdi::WinProc::startFrame(); RealPrimarySurface::waitForFlip(m_data->getDDS()); HRESULT result = getOrigVtable(This).GetDC(This, lphDC); if (SUCCEEDED(result)) @@ -182,6 +186,7 @@ namespace DDraw TSurface* This, LPRECT lpDestRect, TSurfaceDesc* lpDDSurfaceDesc, DWORD dwFlags, HANDLE hEvent) { + Gdi::WinProc::startFrame(); RealPrimarySurface::waitForFlip(m_data->getDDS()); HRESULT result = getOrigVtable(This).Lock(This, lpDestRect, lpDDSurfaceDesc, dwFlags, hEvent); if (SUCCEEDED(result)) diff --git a/DDrawCompat/DDrawCompat.vcxproj b/DDrawCompat/DDrawCompat.vcxproj index 29d54df..6ba12af 100644 --- a/DDrawCompat/DDrawCompat.vcxproj +++ b/DDrawCompat/DDrawCompat.vcxproj @@ -145,6 +145,7 @@ + @@ -171,6 +172,7 @@ + @@ -307,6 +309,7 @@ + diff --git a/DDrawCompat/DDrawCompat.vcxproj.filters b/DDrawCompat/DDrawCompat.vcxproj.filters index 1c4d807..84fa43d 100644 --- a/DDrawCompat/DDrawCompat.vcxproj.filters +++ b/DDrawCompat/DDrawCompat.vcxproj.filters @@ -576,6 +576,12 @@ Header Files\Config\Settings + + Header Files\Config\Settings + + + Header Files\Common + @@ -905,6 +911,9 @@ Source Files\Overlay + + Source Files\Config\Settings + diff --git a/DDrawCompat/Gdi/WinProc.cpp b/DDrawCompat/Gdi/WinProc.cpp index 1626b60..7674a6f 100644 --- a/DDrawCompat/Gdi/WinProc.cpp +++ b/DDrawCompat/Gdi/WinProc.cpp @@ -6,6 +6,8 @@ #include #include #include +#include +#include #include #include #include @@ -28,6 +30,16 @@ namespace { + class ScopedIncrement + { + public: + ScopedIncrement(unsigned& num) : m_num(num) { ++m_num; } + ~ScopedIncrement() { --m_num; } + + private: + unsigned& m_num; + }; + struct WindowProc { WNDPROC wndProcA; @@ -42,6 +54,10 @@ namespace thread_local unsigned g_inCreateDialog = 0; thread_local unsigned g_inMessageBox = 0; + thread_local unsigned g_inWindowProc = 0; + thread_local long long g_qpcWaitEnd = 0; + thread_local bool g_isFrameStarted = false; + thread_local bool g_waiting = false; WindowProc getWindowProc(HWND hwnd); bool isUser32ScrollBar(HWND hwnd); @@ -68,6 +84,7 @@ namespace decltype(&CallWindowProcA) callWindowProc, WNDPROC wndProc) { LOG_FUNC("ddcWindowProc", Compat::WindowMessageStruct(hwnd, uMsg, wParam, lParam)); + ScopedIncrement inc(g_inWindowProc); switch (uMsg) { @@ -388,7 +405,53 @@ namespace decltype(&PeekMessageA) origPeekMessage) { DDraw::RealPrimarySurface::setUpdateReady(); - return origPeekMessage(lpMsg, hWnd, wMsgFilterMin, wMsgFilterMax, wRemoveMsg); + BOOL result = origPeekMessage(lpMsg, hWnd, wMsgFilterMin, wMsgFilterMax, wRemoveMsg); + if (!g_isFrameStarted || Config::Settings::FpsLimiter::MSGLOOP != Config::fpsLimiter.get()) + { + return result; + } + + auto qpcNow = Time::queryPerformanceCounter(); + if (qpcNow - g_qpcWaitEnd >= 0) + { + if (!g_waiting) + { + g_qpcWaitEnd = qpcNow; + } + g_isFrameStarted = false; + g_waiting = false; + return result; + } + + g_waiting = true; + if (result) + { + return result; + } + + Compat::ScopedThreadPriority prio(THREAD_PRIORITY_TIME_CRITICAL); + while (Time::qpcToMs(g_qpcWaitEnd - qpcNow) > 0) + { + Time::waitForNextTick(); + if (origPeekMessage(lpMsg, hWnd, wMsgFilterMin, wMsgFilterMax, wRemoveMsg)) + { + return TRUE; + } + qpcNow = Time::queryPerformanceCounter(); + } + + while (g_qpcWaitEnd - qpcNow > 0) + { + if (origPeekMessage(lpMsg, hWnd, wMsgFilterMin, wMsgFilterMax, wRemoveMsg)) + { + return TRUE; + } + qpcNow = Time::queryPerformanceCounter(); + } + + g_isFrameStarted = false; + g_waiting = false; + return result; } BOOL WINAPI peekMessageA(LPMSG lpMsg, HWND hWnd, UINT wMsgFilterMin, UINT wMsgFilterMax, UINT wRemoveMsg) @@ -654,6 +717,54 @@ namespace Gdi Gdi::Window::updateAll(); } + + void startFrame() + { + if (Config::Settings::FpsLimiter::MSGLOOP != Config::fpsLimiter.get() || g_inWindowProc) + { + return; + } + + auto fps = Config::fpsLimiter.getParam(); + if (0 == fps) + { + fps = 1000; + } + + if (!g_isFrameStarted) + { + g_qpcWaitEnd += Time::g_qpcFrequency / fps; + g_isFrameStarted = true; + return; + } + + if (!g_waiting) + { + return; + } + + g_qpcWaitEnd += Time::g_qpcFrequency / fps; + g_waiting = false; + + auto qpcNow = Time::queryPerformanceCounter(); + if (qpcNow - g_qpcWaitEnd >= 0) + { + return; + } + + Compat::ScopedThreadPriority prio(THREAD_PRIORITY_TIME_CRITICAL); + while (Time::qpcToMs(g_qpcWaitEnd - qpcNow) > 0) + { + Time::waitForNextTick(); + DDraw::RealPrimarySurface::flush(); + qpcNow = Time::queryPerformanceCounter(); + } + + while (g_qpcWaitEnd - qpcNow > 0) + { + qpcNow = Time::queryPerformanceCounter(); + } + } void watchWindowPosChanges(WindowPosChangeNotifyFunc notifyFunc) { diff --git a/DDrawCompat/Gdi/WinProc.h b/DDrawCompat/Gdi/WinProc.h index c841cb7..9507ec3 100644 --- a/DDrawCompat/Gdi/WinProc.h +++ b/DDrawCompat/Gdi/WinProc.h @@ -9,6 +9,7 @@ namespace Gdi void dllThreadDetach(); void installHooks(); void onCreateWindow(HWND hwnd); + void startFrame(); void watchWindowPosChanges(WindowPosChangeNotifyFunc notifyFunc); } } diff --git a/DDrawCompat/Overlay/ConfigWindow.cpp b/DDrawCompat/Overlay/ConfigWindow.cpp index b541880..5f12923 100644 --- a/DDrawCompat/Overlay/ConfigWindow.cpp +++ b/DDrawCompat/Overlay/ConfigWindow.cpp @@ -17,7 +17,7 @@ namespace namespace Overlay { ConfigWindow::ConfigWindow() - : Window(nullptr, { 0, 0, SettingControl::TOTAL_WIDTH, 350 }, Config::configHotKey.get()) + : Window(nullptr, { 0, 0, SettingControl::TOTAL_WIDTH, 380 }, Config::configHotKey.get()) , m_buttonCount(0) , m_focus(nullptr) { @@ -31,6 +31,7 @@ namespace Overlay addControl(Config::bltFilter); addControl(Config::antialiasing); addControl(Config::displayFilter); + addControl(Config::fpsLimiter); addControl(Config::renderColorDepth); addControl(Config::resolutionScale); addControl(Config::spriteDetection); diff --git a/DDrawCompat/Overlay/SettingControl.h b/DDrawCompat/Overlay/SettingControl.h index d37459e..a4471bc 100644 --- a/DDrawCompat/Overlay/SettingControl.h +++ b/DDrawCompat/Overlay/SettingControl.h @@ -19,7 +19,7 @@ namespace Overlay { public: static const int PARAM_LABEL_WIDTH = 70; - static const int PARAM_CONTROL_WIDTH = 151; + static const int PARAM_CONTROL_WIDTH = 241; static const int SETTING_LABEL_WIDTH = 120; static const int SETTING_CONTROL_WIDTH = 151; static const int TOTAL_WIDTH =