1
0
mirror of https://github.com/narzoul/DDrawCompat synced 2024-12-30 08:55:36 +01:00

Fixed black screen issue with Windows 10 Creators Update

Also added v-sync to direct primary surface updates (i.e. those that are
not using a back buffer and flip).

Fixes issues #3 and #15.
This commit is contained in:
narzoul 2017-05-17 20:41:05 +02:00
parent 640f746633
commit 5c91706b03
8 changed files with 124 additions and 100 deletions

View File

@ -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;
}

View File

@ -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<void*&>(g_origD3dKmtSetVidPnSourceOwner1), setVidPnSourceOwner1);
}
void overrideFlipInterval(D3DDDI_FLIPINTERVAL_TYPE flipInterval)
{
g_overrideFlipInterval = flipInterval;
}
void releaseVidPnSources()
{
for (auto it : g_devices)

View File

@ -2,11 +2,15 @@
#include "D3dDdi/Log/KernelModeThunksLog.h"
static const auto D3DDDI_FLIPINTERVAL_NOOVERRIDE = static_cast<D3DDDI_FLIPINTERVAL_TYPE>(5);
namespace D3dDdi
{
namespace KernelModeThunks
{
void installHooks();
bool isPresentReady();
void overrideFlipInterval(D3DDDI_FLIPINTERVAL_TYPE flipInterval);
void releaseVidPnSources();
}
}

View File

@ -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<IDirectDrawSurface7> g_frontBuffer;
@ -30,10 +30,11 @@ namespace
bool g_stopUpdateThread = false;
HANDLE g_updateThread = nullptr;
HANDLE g_updateEvent = nullptr;
RECT g_updateRect = {};
std::atomic<int> g_disableUpdateCount = 0;
long long g_qpcMinUpdateInterval = 0;
std::atomic<long long> 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<bool> 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<IDirectDrawSurface7> surface)
template <typename DirectDraw>
HRESULT init(CompatRef<DirectDraw> dd, CompatPtr<IDirectDrawSurface7> 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<DirectDraw>::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<IDirectDraw>);
@ -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();
}
}

View File

@ -19,7 +19,6 @@ namespace DDraw
static void enableUpdates();
static HRESULT flip(DWORD flags);
static CompatWeakPtr<IDirectDrawSurface7> getSurface();
static void invalidate(const RECT* rect);
static bool isFullScreen();
static bool isLost();
static void release();

View File

@ -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;

View File

@ -3,6 +3,7 @@
#include <string>
#include <Windows.h>
#include <timeapi.h>
#include <Uxtheme.h>
#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";
}

View File

@ -61,7 +61,6 @@ namespace
gdiSurface.get()->lpVtbl->Unlock(gdiSurface, nullptr);
if (DDLOCK_READONLY != g_ddLockFlags)
{
DDraw::RealPrimarySurface::invalidate(nullptr);
DDraw::RealPrimarySurface::update();
}
}