diff --git a/DDrawCompat/Config/Config.h b/DDrawCompat/Config/Config.h index ebfcaaa..6df3d79 100644 --- a/DDrawCompat/Config/Config.h +++ b/DDrawCompat/Config/Config.h @@ -4,8 +4,7 @@ typedef unsigned long DWORD; namespace Config { - const int maxPaletteUpdatesPerMs = 10; - const int maxPrimaryUpdateRate = 60; - const int primaryUpdateDelayAfterFlip = 100; + const int maxPaletteUpdatesPerMs = 5; + const int minExpectedFlipsPerSec = 5; const DWORD preallocatedGdiDcCount = 4; } diff --git a/DDrawCompat/D3dDdi/KernelModeThunks.cpp b/DDrawCompat/D3dDdi/KernelModeThunks.cpp index 5784935..39f81a7 100644 --- a/DDrawCompat/D3dDdi/KernelModeThunks.cpp +++ b/DDrawCompat/D3dDdi/KernelModeThunks.cpp @@ -35,6 +35,9 @@ namespace decltype(D3DKMTCreateContextVirtual)* g_origD3dKmtCreateContextVirtual = nullptr; decltype(D3DKMTSetVidPnSourceOwner1)* g_origD3dKmtSetVidPnSourceOwner1 = nullptr; + D3DDDI_FLIPINTERVAL_TYPE g_overrideFlipInterval = D3DDDI_FLIPINTERVAL_NOOVERRIDE; + UINT g_presentCount = 0; + NTSTATUS APIENTRY createDevice(D3DKMT_CREATEDEVICE* pData) { Compat::LogEnter("D3DKMTCreateDevice", pData); @@ -101,19 +104,37 @@ namespace return result; } + bool isPresentReady(D3DKMT_HANDLE device, D3DDDI_VIDEO_PRESENT_SOURCE_ID vidPnSourceId) + { + D3DKMT_GETDEVICESTATE deviceState = {}; + deviceState.hDevice = device; + deviceState.StateType = D3DKMT_DEVICESTATE_PRESENT; + deviceState.PresentState.VidPnSourceId = vidPnSourceId; + NTSTATUS stateResult = D3DKMTGetDeviceState(&deviceState); + return FAILED(stateResult) || + g_presentCount == deviceState.PresentState.PresentStats.PresentCount || + 0 == deviceState.PresentState.PresentStats.PresentCount; + } + NTSTATUS APIENTRY present(D3DKMT_PRESENT* pData) { Compat::LogEnter("D3DKMTPresent", pData); - static UINT presentCount = 0; - ++presentCount; + if (pData->Flags.Flip && D3DDDI_FLIPINTERVAL_NOOVERRIDE != g_overrideFlipInterval) + { + pData->FlipInterval = g_overrideFlipInterval; + } + + ++g_presentCount; pData->Flags.PresentCountValid = 1; - pData->PresentCount = presentCount; + pData->PresentCount = g_presentCount; NTSTATUS result = CALL_ORIG_FUNC(D3DKMTPresent)(pData); if (SUCCEEDED(result) && 1 == DDraw::PrimarySurface::getDesc().dwBackBufferCount && - pData->Flags.Flip && pData->FlipInterval != D3DDDI_FLIPINTERVAL_IMMEDIATE) + pData->Flags.Flip && + D3DDDI_FLIPINTERVAL_IMMEDIATE != pData->FlipInterval && + D3DDDI_FLIPINTERVAL_NOOVERRIDE == g_overrideFlipInterval) { auto contextIt = g_contexts.find(pData->hContext); auto deviceIt = (contextIt != g_contexts.end()) @@ -126,19 +147,12 @@ namespace vbEvent.hDevice = deviceIt->first; vbEvent.VidPnSourceId = deviceIt->second.vidPnSourceId; - D3DKMT_GETDEVICESTATE deviceState = {}; - deviceState.hDevice = deviceIt->first; - deviceState.StateType = D3DKMT_DEVICESTATE_PRESENT; - deviceState.PresentState.VidPnSourceId = deviceIt->second.vidPnSourceId; - NTSTATUS stateResult = D3DKMTGetDeviceState(&deviceState); - while (SUCCEEDED(stateResult) && - presentCount != deviceState.PresentState.PresentStats.PresentCount) + while (!isPresentReady(deviceIt->first, deviceIt->second.vidPnSourceId)) { if (FAILED(D3DKMTWaitForVerticalBlankEvent(&vbEvent))) { Sleep(1); } - stateResult = D3DKMTGetDeviceState(&deviceState); } } } @@ -220,6 +234,18 @@ namespace D3dDdi { namespace KernelModeThunks { + bool isPresentReady() + { + for (auto it : g_devices) + { + if (D3DDDI_ID_UNINITIALIZED != it.second.vidPnSourceId) + { + return ::isPresentReady(it.first, it.second.vidPnSourceId); + } + } + return true; + } + void installHooks() { HOOK_FUNCTION(gdi32, D3DKMTCreateContext, createContext); @@ -238,6 +264,11 @@ namespace D3dDdi reinterpret_cast(g_origD3dKmtSetVidPnSourceOwner1), setVidPnSourceOwner1); } + void overrideFlipInterval(D3DDDI_FLIPINTERVAL_TYPE flipInterval) + { + g_overrideFlipInterval = flipInterval; + } + void releaseVidPnSources() { for (auto it : g_devices) diff --git a/DDrawCompat/D3dDdi/KernelModeThunks.h b/DDrawCompat/D3dDdi/KernelModeThunks.h index 607d4e2..b5be73d 100644 --- a/DDrawCompat/D3dDdi/KernelModeThunks.h +++ b/DDrawCompat/D3dDdi/KernelModeThunks.h @@ -2,11 +2,15 @@ #include "D3dDdi/Log/KernelModeThunksLog.h" +static const auto D3DDDI_FLIPINTERVAL_NOOVERRIDE = static_cast(5); + namespace D3dDdi { namespace KernelModeThunks { void installHooks(); + bool isPresentReady(); + void overrideFlipInterval(D3DDDI_FLIPINTERVAL_TYPE flipInterval); void releaseVidPnSources(); } } diff --git a/DDrawCompat/DDraw/RealPrimarySurface.cpp b/DDrawCompat/DDraw/RealPrimarySurface.cpp index 9eb6e7d..5644609 100644 --- a/DDrawCompat/DDraw/RealPrimarySurface.cpp +++ b/DDrawCompat/DDraw/RealPrimarySurface.cpp @@ -4,6 +4,7 @@ #include "Common/Hook.h" #include "Common/Time.h" #include "Config/Config.h" +#include "D3dDdi/KernelModeThunks.h" #include "DDraw/DirectDraw.h" #include "DDraw/DirectDrawSurface.h" #include "DDraw/IReleaseNotifier.h" @@ -17,7 +18,6 @@ namespace { void onRelease(); - void updateNow(long long qpcNow); DWORD WINAPI updateThreadProc(LPVOID lpParameter); CompatWeakPtr g_frontBuffer; @@ -30,10 +30,11 @@ namespace bool g_stopUpdateThread = false; HANDLE g_updateThread = nullptr; HANDLE g_updateEvent = nullptr; - RECT g_updateRect = {}; std::atomic g_disableUpdateCount = 0; - long long g_qpcMinUpdateInterval = 0; - std::atomic g_qpcNextUpdate = 0; + long long g_qpcFlipModeTimeout = 0; + long long g_qpcLastFlip = 0; + long long g_qpcNextUpdate = 0; + long long g_qpcUpdateInterval = 0; std::atomic g_isFullScreen(false); @@ -59,9 +60,7 @@ namespace if (paletteConverterDc && primaryDc) { result = TRUE == CALL_ORIG_FUNC(BitBlt)(paletteConverterDc, - g_updateRect.left, g_updateRect.top, - g_updateRect.right - g_updateRect.left, g_updateRect.bottom - g_updateRect.top, - primaryDc, g_updateRect.left, g_updateRect.top, SRCCOPY); + 0, 0, g_surfaceDesc.dwWidth, g_surfaceDesc.dwHeight, primaryDc, 0, 0, SRCCOPY); } primary->ReleaseDC(primary, primaryDc); @@ -69,19 +68,13 @@ namespace if (result) { - result = SUCCEEDED(dest->Blt(&dest, &g_updateRect, - g_paletteConverter, &g_updateRect, DDBLT_WAIT, nullptr)); + result = SUCCEEDED(dest->Blt( + &dest, nullptr, g_paletteConverter, nullptr, DDBLT_WAIT, nullptr)); } } else { - result = SUCCEEDED(dest->Blt(&dest, &g_updateRect, - primary, &g_updateRect, DDBLT_WAIT, nullptr)); - } - - if (result) - { - SetRectEmpty(&g_updateRect); + result = SUCCEEDED(dest->Blt(&dest, nullptr, primary, nullptr, DDBLT_WAIT, nullptr)); } Compat::LogLeave("RealPrimarySurface::compatBlt", dest) << result; @@ -120,14 +113,8 @@ namespace return result; } - long long getNextUpdateQpc(long long qpcNow) - { - long long qpcNextUpdate = g_qpcNextUpdate; - const long long missedIntervals = (qpcNow - qpcNextUpdate) / g_qpcMinUpdateInterval; - return qpcNextUpdate + g_qpcMinUpdateInterval * (missedIntervals + 1); - } - - HRESULT init(CompatPtr surface) + template + HRESULT init(CompatRef dd, CompatPtr surface) { DDSURFACEDESC2 desc = {}; desc.dwSize = sizeof(desc); @@ -142,9 +129,19 @@ namespace surface->GetAttachedSurface(surface, &backBufferCaps, &backBuffer.getRef()); } - g_qpcMinUpdateInterval = Time::g_qpcFrequency / Config::maxPrimaryUpdateRate; + g_qpcFlipModeTimeout = Time::g_qpcFrequency / Config::minExpectedFlipsPerSec; + g_qpcLastFlip = Time::queryPerformanceCounter() - g_qpcFlipModeTimeout; g_qpcNextUpdate = Time::queryPerformanceCounter(); + typename DDraw::Types::TSurfaceDesc dm = {}; + dm.dwSize = sizeof(dm); + dd->GetDisplayMode(&dd, &dm); + if (dm.dwRefreshRate <= 1 || dm.dwRefreshRate >= 1000) + { + dm.dwRefreshRate = 60; + } + g_qpcUpdateInterval = Time::g_qpcFrequency / dm.dwRefreshRate; + if (!g_updateEvent) { g_updateEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr); @@ -159,8 +156,6 @@ namespace surface->SetPrivateData(surface, IID_IReleaseNotifier, &g_releaseNotifier, sizeof(&g_releaseNotifier), DDSPD_IUNKNOWNPOINTER); - timeBeginPeriod(1); - g_frontBuffer = surface.detach(); g_backBuffer = backBuffer; g_surfaceDesc = desc; @@ -169,10 +164,21 @@ namespace return DD_OK; } - bool isNextUpdateSignaledAndReady(long long qpcNow) + bool isUpdateScheduled() { - return Time::qpcToMs(qpcNow - g_qpcNextUpdate) >= 0 && - WAIT_OBJECT_0 == WaitForSingleObject(g_updateEvent, 0); + return WAIT_OBJECT_0 == WaitForSingleObject(g_updateEvent, 0); + } + + int msUntilNextUpdate() + { + DDraw::ScopedThreadLock lock; + const auto qpcNow = Time::queryPerformanceCounter(); + const int result = max(0, Time::qpcToMs(g_qpcNextUpdate - qpcNow)); + if (0 == result && g_isFullScreen && qpcNow - g_qpcLastFlip >= g_qpcFlipModeTimeout) + { + return D3dDdi::KernelModeThunks::isPresentReady() ? 0 : 2; + } + return result; } void onRelease() @@ -180,7 +186,6 @@ namespace Compat::LogEnter("RealPrimarySurface::onRelease"); ResetEvent(g_updateEvent); - timeEndPeriod(1); g_frontBuffer = nullptr; g_backBuffer = nullptr; g_clipper = nullptr; @@ -192,18 +197,24 @@ namespace Compat::LogLeave("RealPrimarySurface::onRelease"); } - void updateNow(long long qpcNow) + void updateNow() { ResetEvent(g_updateEvent); - if (compatBlt(*g_frontBuffer)) + if (!g_isFullScreen) { - long long qpcNextUpdate = getNextUpdateQpc(qpcNow); - if (Time::qpcToMs(qpcNow - qpcNextUpdate) >= 0) - { - qpcNextUpdate += g_qpcMinUpdateInterval; - } - g_qpcNextUpdate = qpcNextUpdate; + compatBlt(*g_frontBuffer); + return; + } + + if (compatBlt(*g_backBuffer)) + { + D3dDdi::KernelModeThunks::overrideFlipInterval( + Time::queryPerformanceCounter() - g_qpcLastFlip >= g_qpcFlipModeTimeout + ? D3DDDI_FLIPINTERVAL_ONE + : D3DDDI_FLIPINTERVAL_IMMEDIATE); + g_frontBuffer->Flip(g_frontBuffer, nullptr, DDFLIP_WAIT); + D3dDdi::KernelModeThunks::overrideFlipInterval(D3DDDI_FLIPINTERVAL_NOOVERRIDE); } } @@ -218,20 +229,17 @@ namespace return 0; } - const long long qpcTargetNextUpdate = g_qpcNextUpdate; - const int msUntilNextUpdate = - Time::qpcToMs(qpcTargetNextUpdate - Time::queryPerformanceCounter()); - if (msUntilNextUpdate > 0) + const int waitTime = msUntilNextUpdate(); + if (waitTime > 0) { - Sleep(msUntilNextUpdate); + Sleep(waitTime); + continue; } DDraw::ScopedThreadLock lock; - const long long qpcNow = Time::queryPerformanceCounter(); - const bool isTargetUpdateStillNeeded = qpcTargetNextUpdate == g_qpcNextUpdate; - if (g_frontBuffer && (isTargetUpdateStillNeeded || isNextUpdateSignaledAndReady(qpcNow))) + if (isUpdateScheduled() && msUntilNextUpdate() <= 0) { - updateNow(qpcNow); + updateNow(); } } } @@ -275,7 +283,7 @@ namespace DDraw return result; } - return init(surface); + return init(dd, surface); } template HRESULT RealPrimarySurface::create(CompatRef); @@ -305,17 +313,10 @@ namespace DDraw } ResetEvent(g_updateEvent); - - invalidate(nullptr); + g_qpcLastFlip = Time::queryPerformanceCounter(); compatBlt(*g_backBuffer); - HRESULT result = g_frontBuffer->Flip(g_frontBuffer, nullptr, flags); - if (SUCCEEDED(result)) - { - g_qpcNextUpdate = getNextUpdateQpc( - Time::queryPerformanceCounter() + Time::msToQpc(Config::primaryUpdateDelayAfterFlip)); - SetRectEmpty(&g_updateRect); - } + g_qpcNextUpdate = Time::queryPerformanceCounter(); return result; } @@ -324,19 +325,6 @@ namespace DDraw return g_frontBuffer; } - void RealPrimarySurface::invalidate(const RECT* rect) - { - if (rect) - { - UnionRect(&g_updateRect, &g_updateRect, rect); - } - else - { - auto primaryDesc = PrimarySurface::getDesc(); - SetRect(&g_updateRect, 0, 0, primaryDesc.dwWidth, primaryDesc.dwHeight); - } - } - bool RealPrimarySurface::isFullScreen() { return g_isFullScreen; @@ -404,17 +392,23 @@ namespace DDraw void RealPrimarySurface::update() { - if (!IsRectEmpty(&g_updateRect) && 0 == g_disableUpdateCount && (g_isFullScreen || g_clipper)) + if (0 == g_disableUpdateCount && (g_isFullScreen || g_clipper)) { - const long long qpcNow = Time::queryPerformanceCounter(); - if (Time::qpcToMs(qpcNow - g_qpcNextUpdate) >= 0) - { - updateNow(qpcNow); - } - else + if (!isUpdateScheduled()) { + const auto qpcNow = Time::queryPerformanceCounter(); + const long long missedIntervals = (qpcNow - g_qpcNextUpdate) / g_qpcUpdateInterval; + g_qpcNextUpdate += g_qpcUpdateInterval * (missedIntervals + 1); + if (Time::qpcToMs(g_qpcNextUpdate - qpcNow) < 2) + { + g_qpcNextUpdate += g_qpcUpdateInterval; + } SetEvent(g_updateEvent); } + else if (msUntilNextUpdate() <= 0) + { + updateNow(); + } } } @@ -423,7 +417,6 @@ namespace DDraw Gdi::updatePalette(startingEntry, count); if (PrimarySurface::s_palette) { - invalidate(nullptr); update(); } } diff --git a/DDrawCompat/DDraw/RealPrimarySurface.h b/DDrawCompat/DDraw/RealPrimarySurface.h index f523232..7aa0666 100644 --- a/DDrawCompat/DDraw/RealPrimarySurface.h +++ b/DDrawCompat/DDraw/RealPrimarySurface.h @@ -19,7 +19,6 @@ namespace DDraw static void enableUpdates(); static HRESULT flip(DWORD flags); static CompatWeakPtr getSurface(); - static void invalidate(const RECT* rect); static bool isFullScreen(); static bool isLost(); static void release(); diff --git a/DDrawCompat/DDraw/Surfaces/PrimarySurfaceImpl.cpp b/DDrawCompat/DDraw/Surfaces/PrimarySurfaceImpl.cpp index 37eddd3..247f2f8 100644 --- a/DDrawCompat/DDraw/Surfaces/PrimarySurfaceImpl.cpp +++ b/DDrawCompat/DDraw/Surfaces/PrimarySurfaceImpl.cpp @@ -33,7 +33,6 @@ namespace DDraw HRESULT result = m_impl.Blt(This, lpDestRect, lpDDSrcSurface, lpSrcRect, dwFlags, lpDDBltFx); if (SUCCEEDED(result)) { - RealPrimarySurface::invalidate(lpDestRect); RealPrimarySurface::update(); } return result; @@ -67,7 +66,6 @@ namespace DDraw destRect.right += desc.dwWidth; destRect.bottom += desc.dwHeight; } - RealPrimarySurface::invalidate(&destRect); RealPrimarySurface::update(); } return result; @@ -144,7 +142,6 @@ namespace DDraw HRESULT result = m_impl.Lock(This, lpDestRect, lpDDSurfaceDesc, dwFlags, hEvent); if (SUCCEEDED(result)) { - RealPrimarySurface::invalidate(lpDestRect); restorePrimaryCaps(lpDDSurfaceDesc->ddsCaps.dwCaps); } return result; @@ -167,7 +164,6 @@ namespace DDraw HRESULT result = m_impl.ReleaseDC(This, hDC); if (SUCCEEDED(result)) { - RealPrimarySurface::invalidate(nullptr); RealPrimarySurface::update(); } return result; diff --git a/DDrawCompat/Dll/DllMain.cpp b/DDrawCompat/Dll/DllMain.cpp index d56783d..72be4d9 100644 --- a/DDrawCompat/Dll/DllMain.cpp +++ b/DDrawCompat/Dll/DllMain.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include "Common/Hook.h" @@ -125,6 +126,7 @@ BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID /*lpvReserved*/) const BOOL disablePriorityBoost = TRUE; SetProcessPriorityBoost(GetCurrentProcess(), disablePriorityBoost); SetProcessAffinityMask(GetCurrentProcess(), 1); + timeBeginPeriod(1); SetThemeAppProperties(0); Compat::redirectIatHooks("ddraw.dll", "DirectDrawCreate", @@ -150,6 +152,7 @@ BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID /*lpvReserved*/) FreeLibrary(g_origDInputModule); FreeLibrary(g_origDDrawModule); Win32::FontSmoothing::setSystemSettingsForced(Win32::FontSmoothing::g_origSystemSettings); + timeEndPeriod(1); Compat::Log() << "DDrawCompat detached successfully"; } diff --git a/DDrawCompat/Gdi/Gdi.cpp b/DDrawCompat/Gdi/Gdi.cpp index 4ab538d..d079a1a 100644 --- a/DDrawCompat/Gdi/Gdi.cpp +++ b/DDrawCompat/Gdi/Gdi.cpp @@ -61,7 +61,6 @@ namespace gdiSurface.get()->lpVtbl->Unlock(gdiSurface, nullptr); if (DDLOCK_READONLY != g_ddLockFlags) { - DDraw::RealPrimarySurface::invalidate(nullptr); DDraw::RealPrimarySurface::update(); } }