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

Improved presentation scheduling

Fixes cursor flickering issues in Siege of Avalon (issue #34)
and multimon flip issues on Windows 8.1 (issue #28).
This commit is contained in:
narzoul 2018-10-03 20:49:50 +02:00
parent e69b5e312c
commit 537ef9c595
16 changed files with 708 additions and 463 deletions

View File

@ -10,14 +10,14 @@ namespace Time
void init();
inline long long msToQpc(int ms)
inline long long msToQpc(long long ms)
{
return static_cast<long long>(ms) * g_qpcFrequency / 1000;
}
inline int qpcToMs(long long qpc)
inline long long qpcToMs(long long qpc)
{
return static_cast<int>(qpc * 1000 / g_qpcFrequency);
return qpc * 1000 / g_qpcFrequency;
}
inline long long queryPerformanceCounter()

View File

@ -4,6 +4,7 @@ typedef unsigned long DWORD;
namespace Config
{
const int delayedFlipModeTimeout = 200;
const int maxPaletteUpdatesPerMs = 5;
const int minExpectedFlipsPerSec = 5;
const DWORD primarySurfaceExtraRows = 2;

View File

@ -1,3 +1,6 @@
#include <d3d.h>
#include <../km/d3dkmthk.h>
#include "D3dDdi/AdapterFuncs.h"
#include "D3dDdi/Device.h"
#include "D3dDdi/DeviceFuncs.h"
@ -208,7 +211,7 @@ namespace D3dDdi
{
if (resource == m_sharedPrimary)
{
KernelModeThunks::releaseVidPnSources();
D3DKMTReleaseProcessVidPnSourceOwners(GetCurrentProcess());
}
HRESULT result = m_origVtable->pfnDestroyResource(m_device, resource);

View File

@ -1,3 +1,4 @@
#include <atomic>
#include <map>
#include <d3d.h>
@ -6,12 +7,23 @@
#include "Common/Log.h"
#include "Common/Hook.h"
#include "Common/ScopedCriticalSection.h"
#include "Common/Time.h"
#include "D3dDdi/Hooks.h"
#include "D3dDdi/KernelModeThunks.h"
#include "D3dDdi/Log/KernelModeThunksLog.h"
#include "DDraw/Surfaces/PrimarySurface.h"
#include "Win32/DisplayMode.h"
namespace
{
struct AdapterInfo
{
UINT adapter;
UINT vidPnSourceId;
RECT monitorRect;
};
struct ContextInfo
{
D3DKMT_HANDLE device;
@ -19,40 +31,26 @@ namespace
ContextInfo() : device(0) {}
};
struct DeviceInfo
{
D3DKMT_HANDLE adapter;
D3DDDI_VIDEO_PRESENT_SOURCE_ID vidPnSourceId;
DeviceInfo() : adapter(0), vidPnSourceId(D3DDDI_ID_UNINITIALIZED) {}
};
std::map<D3DKMT_HANDLE, ContextInfo> g_contexts;
std::map<D3DKMT_HANDLE, DeviceInfo> g_devices;
HMONITOR g_lastOpenAdapterMonitor = nullptr;
AdapterInfo g_gdiAdapterInfo = {};
AdapterInfo g_lastOpenAdapterInfo = {};
UINT g_lastFlipInterval = 0;
UINT g_flipIntervalOverride = 0;
D3DKMT_HANDLE g_lastPresentContext = 0;
UINT g_presentCount = 0;
std::atomic<long long> g_qpcLastVerticalBlank = 0;
CRITICAL_SECTION g_vblankCs = {};
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)
NTSTATUS APIENTRY closeAdapter(const D3DKMT_CLOSEADAPTER* pData)
{
Compat::LogEnter("D3DKMTCreateDevice", pData);
NTSTATUS result = CALL_ORIG_FUNC(D3DKMTCreateDevice)(pData);
if (SUCCEEDED(result))
Compat::ScopedCriticalSection lock(g_vblankCs);
if (pData && pData->hAdapter == g_lastOpenAdapterInfo.adapter)
{
g_devices[pData->hDevice].adapter = pData->hAdapter;
D3DKMT_SETQUEUEDLIMIT limit = {};
limit.hDevice = pData->hDevice;
limit.Type = D3DKMT_SET_QUEUEDLIMIT_PRESENT;
limit.QueuedPresentLimit = 1;
CALL_ORIG_FUNC(D3DKMTSetQueuedLimit)(&limit);
g_lastOpenAdapterInfo = {};
}
Compat::LogLeave("D3DKMTCreateDevice", pData) << result;
return result;
return CALL_ORIG_FUNC(D3DKMTCloseAdapter)(pData);
}
NTSTATUS APIENTRY createContext(D3DKMT_CREATECONTEXT* pData)
@ -98,6 +96,22 @@ namespace
return result;
}
NTSTATUS APIENTRY createDevice(D3DKMT_CREATEDEVICE* pData)
{
Compat::LogEnter("D3DKMTCreateDevice", pData);
NTSTATUS result = CALL_ORIG_FUNC(D3DKMTCreateDevice)(pData);
if (SUCCEEDED(result))
{
D3DKMT_SETQUEUEDLIMIT limit = {};
limit.hDevice = pData->hDevice;
limit.Type = D3DKMT_SET_QUEUEDLIMIT_PRESENT;
limit.QueuedPresentLimit = 2;
CALL_ORIG_FUNC(D3DKMTSetQueuedLimit)(&limit);
}
Compat::LogLeave("D3DKMTCreateDevice", pData) << result;
return result;
}
NTSTATUS APIENTRY destroyContext(const D3DKMT_DESTROYCONTEXT* pData)
{
Compat::LogEnter("D3DKMTDestroyContext", pData);
@ -105,89 +119,71 @@ namespace
if (SUCCEEDED(result))
{
g_contexts.erase(pData->hContext);
if (g_lastPresentContext == pData->hContext)
{
g_lastPresentContext = 0;
}
}
Compat::LogLeave("D3DKMTDestroyContext", pData) << result;
return result;
}
NTSTATUS APIENTRY destroyDevice(const D3DKMT_DESTROYDEVICE* pData)
AdapterInfo getAdapterInfo(const D3DKMT_OPENADAPTERFROMHDC& data)
{
Compat::LogEnter("D3DKMTDestroyDevice", pData);
NTSTATUS result = CALL_ORIG_FUNC(D3DKMTDestroyDevice)(pData);
if (SUCCEEDED(result))
{
g_devices.erase(pData->hDevice);
}
Compat::LogLeave("D3DKMTDestroyDevice", pData) << result;
return result;
AdapterInfo adapterInfo = {};
adapterInfo.adapter = data.hAdapter;
adapterInfo.vidPnSourceId = data.VidPnSourceId;
POINT p = {};
GetDCOrgEx(data.hDc, &p);
MONITORINFO mi = {};
mi.cbSize = sizeof(mi);
GetMonitorInfo(MonitorFromPoint(p, MONITOR_DEFAULTTOPRIMARY), &mi);
adapterInfo.monitorRect = mi.rcMonitor;
return adapterInfo;
}
NTSTATUS APIENTRY openAdapterFromHdc(D3DKMT_OPENADAPTERFROMHDC* pData)
{
Compat::LogEnter("D3DKMTOpenAdapterFromHdc", pData);
NTSTATUS result = CALL_ORIG_FUNC(D3DKMTOpenAdapterFromHdc)(pData);
if (pData)
if (SUCCEEDED(result))
{
POINT p = {};
GetDCOrgEx(pData->hDc, &p);
g_lastOpenAdapterMonitor = MonitorFromPoint(p, MONITOR_DEFAULTTOPRIMARY);
Compat::ScopedCriticalSection lock(g_vblankCs);
g_lastOpenAdapterInfo = getAdapterInfo(*pData);
}
Compat::LogLeave("D3DKMTOpenAdapterFromHdc", pData) << result;
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);
if (pData->Flags.Flip && D3DDDI_FLIPINTERVAL_NOOVERRIDE != g_overrideFlipInterval)
if (pData->Flags.Flip)
{
pData->FlipInterval = g_overrideFlipInterval;
}
g_lastFlipInterval = pData->FlipInterval;
g_lastPresentContext = pData->hContext;
++g_presentCount;
pData->Flags.PresentCountValid = 1;
pData->PresentCount = g_presentCount;
if (UINT_MAX == g_flipIntervalOverride)
{
Compat::LogLeave("D3DKMTPresent", pData) << S_OK;
return S_OK;
}
++g_presentCount;
if (0 == g_presentCount)
{
g_presentCount = 1;
}
pData->PresentCount = g_presentCount;
pData->Flags.PresentCountValid = 1;
pData->FlipInterval = static_cast<D3DDDI_FLIPINTERVAL_TYPE>(g_flipIntervalOverride);
}
NTSTATUS result = CALL_ORIG_FUNC(D3DKMTPresent)(pData);
if (SUCCEEDED(result) &&
1 == DDraw::PrimarySurface::getDesc().dwBackBufferCount &&
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())
? g_devices.find(contextIt->second.device)
: g_devices.find(pData->hDevice);
if (deviceIt != g_devices.end())
{
D3DKMT_WAITFORVERTICALBLANKEVENT vbEvent = {};
vbEvent.hAdapter = deviceIt->second.adapter;
vbEvent.hDevice = deviceIt->first;
vbEvent.VidPnSourceId = deviceIt->second.vidPnSourceId;
while (!isPresentReady(deviceIt->first, deviceIt->second.vidPnSourceId))
{
if (FAILED(D3DKMTWaitForVerticalBlankEvent(&vbEvent)))
{
Sleep(1);
}
}
}
}
Compat::LogLeave("D3DKMTPresent", pData) << result;
return result;
@ -212,7 +208,7 @@ namespace
if (D3DKMT_SET_QUEUEDLIMIT_PRESENT == pData->Type)
{
const UINT origLimit = pData->QueuedPresentLimit;
const_cast<D3DKMT_SETQUEUEDLIMIT*>(pData)->QueuedPresentLimit = 1;
const_cast<D3DKMT_SETQUEUEDLIMIT*>(pData)->QueuedPresentLimit = 2;
NTSTATUS result = CALL_ORIG_FUNC(D3DKMTSetQueuedLimit)(pData);
const_cast<D3DKMT_SETQUEUEDLIMIT*>(pData)->QueuedPresentLimit = origLimit;
Compat::LogLeave("D3DKMTSetQueuedLimit", pData) << result;
@ -223,42 +219,30 @@ namespace
return result;
}
void processSetVidPnSourceOwner(const D3DKMT_SETVIDPNSOURCEOWNER* pData)
void updateGdiAdapterInfo()
{
auto& vidPnSourceId = g_devices[pData->hDevice].vidPnSourceId;
for (UINT i = 0; i < pData->VidPnSourceCount; ++i)
static auto lastDisplaySettingsUniqueness = Win32::DisplayMode::queryDisplaySettingsUniqueness() - 1;
const auto currentDisplaySettingsUniqueness = Win32::DisplayMode::queryDisplaySettingsUniqueness();
if (currentDisplaySettingsUniqueness != lastDisplaySettingsUniqueness)
{
if (D3DKMT_VIDPNSOURCEOWNER_UNOWNED != pData->pType[i])
if (g_gdiAdapterInfo.adapter)
{
vidPnSourceId = pData->pVidPnSourceId[i];
return;
D3DKMT_CLOSEADAPTER data = {};
data.hAdapter = g_gdiAdapterInfo.adapter;
CALL_ORIG_FUNC(D3DKMTCloseAdapter)(&data);
g_gdiAdapterInfo = {};
}
}
vidPnSourceId = D3DDDI_ID_UNINITIALIZED;
}
NTSTATUS APIENTRY setVidPnSourceOwner(const D3DKMT_SETVIDPNSOURCEOWNER* pData)
{
Compat::LogEnter("D3DKMTSetVidPnSourceOwner", pData);
NTSTATUS result = CALL_ORIG_FUNC(D3DKMTSetVidPnSourceOwner)(pData);
if (SUCCEEDED(result))
{
processSetVidPnSourceOwner(pData);
}
Compat::LogLeave("D3DKMTSetVidPnSourceOwner", pData) << result;
return result;
}
D3DKMT_OPENADAPTERFROMHDC data = {};
data.hDc = GetDC(nullptr);
if (SUCCEEDED(CALL_ORIG_FUNC(D3DKMTOpenAdapterFromHdc)(&data)))
{
g_gdiAdapterInfo = getAdapterInfo(data);
}
ReleaseDC(nullptr, data.hDc);
NTSTATUS APIENTRY setVidPnSourceOwner1(const D3DKMT_SETVIDPNSOURCEOWNER1* pData)
{
Compat::LogEnter("D3DKMTSetVidPnSourceOwner1", pData);
NTSTATUS result = g_origD3dKmtSetVidPnSourceOwner1(pData);
if (SUCCEEDED(result))
{
processSetVidPnSourceOwner(&pData->Version0);
lastDisplaySettingsUniqueness = currentDisplaySettingsUniqueness;
}
Compat::LogLeave("D3DKMTSetVidPnSourceOwner1", pData) << result;
return result;
}
}
@ -266,58 +250,113 @@ namespace D3dDdi
{
namespace KernelModeThunks
{
HMONITOR getLastOpenAdapterMonitor()
UINT getLastFlipInterval()
{
return g_lastOpenAdapterMonitor;
return g_lastFlipInterval;
}
bool isPresentReady()
UINT getLastDisplayedFrameCount()
{
for (auto it : g_devices)
auto contextIter = g_contexts.find(g_lastPresentContext);
if (contextIter == g_contexts.end())
{
if (D3DDDI_ID_UNINITIALIZED != it.second.vidPnSourceId)
{
return ::isPresentReady(it.first, it.second.vidPnSourceId);
}
return g_presentCount;
}
return true;
D3DKMT_GETDEVICESTATE data = {};
data.hDevice = contextIter->second.device;
data.StateType = D3DKMT_DEVICESTATE_PRESENT;
data.PresentState.VidPnSourceId = g_lastOpenAdapterInfo.vidPnSourceId;
D3DKMTGetDeviceState(&data);
if (0 == data.PresentState.PresentStats.PresentCount)
{
return g_presentCount;
}
return data.PresentState.PresentStats.PresentCount;
}
UINT getLastSubmittedFrameCount()
{
return g_presentCount;
}
RECT getMonitorRect()
{
auto primary(DDraw::PrimarySurface::getPrimary());
if (!primary)
{
return {};
}
static auto lastDisplaySettingsUniqueness = Win32::DisplayMode::queryDisplaySettingsUniqueness() - 1;
const auto currentDisplaySettingsUniqueness = Win32::DisplayMode::queryDisplaySettingsUniqueness();
if (currentDisplaySettingsUniqueness != lastDisplaySettingsUniqueness)
{
lastDisplaySettingsUniqueness = currentDisplaySettingsUniqueness;
CompatPtr<IUnknown> ddUnk;
primary->GetDDInterface(primary, reinterpret_cast<void**>(&ddUnk.getRef()));
CompatPtr<IDirectDraw7> dd7(ddUnk);
DDDEVICEIDENTIFIER2 di = {};
dd7->GetDeviceIdentifier(dd7, &di, 0);
}
return g_lastOpenAdapterInfo.monitorRect;
}
long long getQpcLastVerticalBlank()
{
return g_qpcLastVerticalBlank;
}
void installHooks()
{
InitializeCriticalSection(&g_vblankCs);
HOOK_FUNCTION(gdi32, D3DKMTCloseAdapter, closeAdapter);
HOOK_FUNCTION(gdi32, D3DKMTCreateContext, createContext);
HOOK_FUNCTION(gdi32, D3DKMTCreateDevice, createDevice);
HOOK_FUNCTION(gdi32, D3DKMTCreateDCFromMemory, createDcFromMemory);
HOOK_FUNCTION(gdi32, D3DKMTDestroyContext, destroyContext);
HOOK_FUNCTION(gdi32, D3DKMTDestroyDevice, destroyDevice);
HOOK_FUNCTION(gdi32, D3DKMTOpenAdapterFromHdc, openAdapterFromHdc);
HOOK_FUNCTION(gdi32, D3DKMTQueryAdapterInfo, queryAdapterInfo);
HOOK_FUNCTION(gdi32, D3DKMTPresent, present);
HOOK_FUNCTION(gdi32, D3DKMTSetQueuedLimit, setQueuedLimit);
HOOK_FUNCTION(gdi32, D3DKMTSetVidPnSourceOwner, setVidPnSourceOwner);
// Functions not available in Windows Vista
Compat::hookFunction("gdi32", "D3DKMTCreateContextVirtual",
reinterpret_cast<void*&>(g_origD3dKmtCreateContextVirtual), createContextVirtual);
Compat::hookFunction("gdi32", "D3DKMTSetVidPnSourceOwner1",
reinterpret_cast<void*&>(g_origD3dKmtSetVidPnSourceOwner1), setVidPnSourceOwner1);
}
void overrideFlipInterval(D3DDDI_FLIPINTERVAL_TYPE flipInterval)
void setFlipIntervalOverride(UINT flipInterval)
{
g_overrideFlipInterval = flipInterval;
g_flipIntervalOverride = flipInterval;
}
void releaseVidPnSources()
void waitForVerticalBlank()
{
for (auto it : g_devices)
D3DKMT_WAITFORVERTICALBLANKEVENT data = {};
{
if (D3DDDI_ID_UNINITIALIZED != it.second.vidPnSourceId)
Compat::ScopedCriticalSection lock(g_vblankCs);
if (g_lastOpenAdapterInfo.adapter)
{
D3DKMT_SETVIDPNSOURCEOWNER vidPnSourceOwner = {};
vidPnSourceOwner.hDevice = it.first;
D3DKMTSetVidPnSourceOwner(&vidPnSourceOwner);
data.hAdapter = g_lastOpenAdapterInfo.adapter;
data.VidPnSourceId = g_lastOpenAdapterInfo.vidPnSourceId;
}
else
{
updateGdiAdapterInfo();
data.hAdapter = g_gdiAdapterInfo.adapter;
data.VidPnSourceId = g_gdiAdapterInfo.vidPnSourceId;
}
}
if (data.hAdapter)
{
D3DKMTWaitForVerticalBlankEvent(&data);
g_qpcLastVerticalBlank = Time::queryPerformanceCounter();
}
}
}

View File

@ -1,22 +1,20 @@
#pragma once
#include <d3d.h>
#include <d3dumddi.h>
#include <../km/d3dkmthk.h>
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include "D3dDdi/Log/KernelModeThunksLog.h"
static const auto D3DDDI_FLIPINTERVAL_NOOVERRIDE = static_cast<D3DDDI_FLIPINTERVAL_TYPE>(5);
namespace D3dDdi
{
namespace KernelModeThunks
{
HMONITOR getLastOpenAdapterMonitor();
UINT getLastFlipInterval();
UINT getLastDisplayedFrameCount();
UINT getLastSubmittedFrameCount();
RECT getMonitorRect();
long long getQpcLastVerticalBlank();
void installHooks();
bool isPresentReady();
void overrideFlipInterval(D3DDDI_FLIPINTERVAL_TYPE flipInterval);
void releaseVidPnSources();
void setFlipIntervalOverride(UINT flipInterval);
void waitForVerticalBlank();
}
}

View File

@ -1,4 +1,5 @@
#include "Common/CompatPtr.h"
#include "D3dDdi/KernelModeThunks.h"
#include "DDraw/ActivateAppHandler.h"
#include "DDraw/DirectDraw.h"
#include "DDraw/Repository.h"
@ -117,6 +118,7 @@ namespace DDraw
vtable.GetGDISurface = &GetGDISurface;
vtable.SetCooperativeLevel = &SetCooperativeLevel;
vtable.SetDisplayMode = &SetDisplayMode;
vtable.WaitForVerticalBlank = &WaitForVerticalBlank;
}
template <typename TDirectDraw>
@ -206,6 +208,30 @@ namespace DDraw
return setDisplayMode(This, dwWidth, dwHeight, dwBPP, params...);
}
template <typename TDirectDraw>
HRESULT STDMETHODCALLTYPE DirectDraw<TDirectDraw>::WaitForVerticalBlank(
TDirectDraw* This, DWORD dwFlags, HANDLE hEvent)
{
if (!This || (DDWAITVB_BLOCKBEGIN != dwFlags && DDWAITVB_BLOCKEND != dwFlags))
{
return s_origVtable.WaitForVerticalBlank(This, dwFlags, hEvent);
}
DWORD scanLine = 0;
if (DDERR_VERTICALBLANKINPROGRESS != s_origVtable.GetScanLine(This, &scanLine))
{
D3dDdi::KernelModeThunks::waitForVerticalBlank();
}
if (DDWAITVB_BLOCKEND == dwFlags)
{
while (DDERR_VERTICALBLANKINPROGRESS == s_origVtable.GetScanLine(This, &scanLine));
}
return DD_OK;
}
template DirectDraw<IDirectDraw>;
template DirectDraw<IDirectDraw2>;
template DirectDraw<IDirectDraw4>;

View File

@ -46,6 +46,8 @@ namespace DDraw
DWORD dwHeight,
DWORD dwBPP,
Params... params);
static HRESULT STDMETHODCALLTYPE WaitForVerticalBlank(TDirectDraw* This, DWORD dwFlags, HANDLE hEvent);
};
}

View File

@ -32,8 +32,10 @@ namespace DDraw
SET_COMPAT_METHOD(Blt);
SET_COMPAT_METHOD(BltFast);
SET_COMPAT_METHOD(Flip);
SET_COMPAT_METHOD(GetBltStatus);
SET_COMPAT_METHOD(GetCaps);
SET_COMPAT_METHOD(GetDC);
SET_COMPAT_METHOD(GetFlipStatus);
SET_COMPAT_METHOD(GetSurfaceDesc);
SET_COMPAT_METHOD(IsLost);
SET_COMPAT_METHOD(Lock);

View File

@ -1,4 +1,3 @@
#include <atomic>
#include <memory>
#include <vector>
@ -39,7 +38,6 @@ namespace
DWORD WINAPI updateThreadProc(LPVOID lpParameter);
CompatWeakPtr<IDirectDrawSurface7> g_frontBuffer;
CompatWeakPtr<IDirectDrawSurface7> g_backBuffer;
CompatWeakPtr<IDirectDrawSurface7> g_paletteConverter;
CompatWeakPtr<IDirectDrawClipper> g_clipper;
DDSURFACEDESC2 g_surfaceDesc = {};
@ -47,15 +45,19 @@ namespace
bool g_stopUpdateThread = false;
HANDLE g_updateThread = nullptr;
HANDLE g_updateEvent = nullptr;
std::atomic<int> g_disableUpdateCount = 0;
bool g_isUpdateSuspended = false;
long long g_qpcFlipModeTimeout = 0;
long long g_qpcLastFlip = 0;
long long g_qpcNextUpdate = 0;
long long g_qpcUpdateInterval = 0;
unsigned int g_disableUpdateCount = 0;
bool g_isFlipPending = false;
bool g_isPresentPending = false;
bool g_isUpdatePending = false;
bool g_isFullScreen = false;
bool g_waitingForPrimaryUnlock = false;
UINT g_flipIntervalDriverOverride = UINT_MAX;
UINT g_lastFlipFrameCount = 0;
DDraw::Surface* g_lastFlipSurface = nullptr;
long long g_qpcLastUpdate = 0;
std::atomic<bool> g_isFullScreen(false);
CompatPtr<IDirectDrawSurface7> getBackBuffer();
CompatPtr<IDirectDrawSurface7> getLastSurface();
BOOL CALLBACK addVisibleLayeredWindowToVector(HWND hwnd, LPARAM lParam)
{
@ -140,8 +142,14 @@ namespace
return;
}
auto backBuffer(getBackBuffer());
if (!backBuffer)
{
return;
}
HDC backBufferDc = nullptr;
g_backBuffer->GetDC(g_backBuffer, &backBufferDc);
backBuffer->GetDC(backBuffer, &backBufferDc);
for (auto it = visibleLayeredWindows.rbegin(); it != visibleLayeredWindows.rend(); ++it)
{
@ -159,76 +167,22 @@ namespace
ReleaseDC(*it, windowDc);
}
g_backBuffer->ReleaseDC(g_backBuffer, backBufferDc);
backBuffer->ReleaseDC(backBuffer, backBufferDc);
}
HRESULT bltToPrimaryChain(CompatRef<IDirectDrawSurface7> src)
void bltToPrimaryChain(CompatRef<IDirectDrawSurface7> src)
{
if (g_isFullScreen)
if (!g_isFullScreen)
{
return g_backBuffer->Blt(g_backBuffer, nullptr, &src, nullptr, DDBLT_WAIT, nullptr);
EnumThreadWindows(Gdi::getGdiThreadId(), bltToWindow, reinterpret_cast<LPARAM>(&src));
return;
}
EnumThreadWindows(Gdi::getGdiThreadId(), bltToWindow, reinterpret_cast<LPARAM>(&src));
return DD_OK;
}
bool compatBlt()
{
Compat::LogEnter("RealPrimarySurface::compatBlt");
BltToWindowViaGdiArgs bltToWindowViaGdiArgs;
if (!g_frontBuffer || (!g_isFullScreen && DDraw::RealPrimarySurface::isLost()))
auto backBuffer(getBackBuffer());
if (backBuffer)
{
EnumThreadWindows(Gdi::getGdiThreadId(), bltToWindowViaGdi,
reinterpret_cast<LPARAM>(&bltToWindowViaGdiArgs));
Compat::LogLeave("RealPrimarySurface::compatBlt") << false;
return false;
backBuffer->Blt(backBuffer, nullptr, &src, nullptr, DDBLT_WAIT, nullptr);
}
Gdi::Region primaryRegion(DDraw::PrimarySurface::getMonitorRect());
bltToWindowViaGdiArgs.primaryRegion = &primaryRegion;
EnumThreadWindows(Gdi::getGdiThreadId(), bltToWindowViaGdi,
reinterpret_cast<LPARAM>(&bltToWindowViaGdiArgs));
bool result = false;
auto primary(DDraw::PrimarySurface::getPrimary());
Gdi::DDrawAccessGuard accessGuard(Gdi::ACCESS_READ, DDraw::PrimarySurface::isGdiSurface(primary.get()));
if (DDraw::PrimarySurface::getDesc().ddpfPixelFormat.dwRGBBitCount <= 8)
{
HDC paletteConverterDc = nullptr;
g_paletteConverter->GetDC(g_paletteConverter, &paletteConverterDc);
HDC primaryDc = nullptr;
D3dDdi::Device::setReadOnlyGdiLock(true);
primary->GetDC(primary, &primaryDc);
D3dDdi::Device::setReadOnlyGdiLock(false);
if (paletteConverterDc && primaryDc)
{
result = TRUE == CALL_ORIG_FUNC(BitBlt)(paletteConverterDc,
0, 0, g_surfaceDesc.dwWidth, g_surfaceDesc.dwHeight, primaryDc, 0, 0, SRCCOPY);
}
primary->ReleaseDC(primary, primaryDc);
g_paletteConverter->ReleaseDC(g_paletteConverter, paletteConverterDc);
if (result)
{
result = SUCCEEDED(bltToPrimaryChain(*g_paletteConverter));
}
}
else
{
result = SUCCEEDED(bltToPrimaryChain(*primary));
}
if (result && g_isFullScreen && primary == DDraw::PrimarySurface::getGdiSurface())
{
bltVisibleLayeredWindowsToBackBuffer();
}
Compat::LogLeave("RealPrimarySurface::compatBlt") << result;
return result;
}
template <typename TDirectDraw>
@ -263,124 +217,278 @@ namespace
return result;
}
template <typename DirectDraw>
HRESULT init(CompatRef<DirectDraw> dd, CompatPtr<IDirectDrawSurface7> surface)
CompatPtr<IDirectDrawSurface7> getBackBuffer()
{
DDSURFACEDESC2 desc = {};
desc.dwSize = sizeof(desc);
surface->GetSurfaceDesc(surface, &desc);
const bool isFlippable = 0 != (desc.ddsCaps.dwCaps & DDSCAPS_FLIP);
DDSCAPS2 caps = {};
caps.dwCaps = DDSCAPS_BACKBUFFER;
CompatPtr<IDirectDrawSurface7> backBuffer;
if (isFlippable)
if (g_frontBuffer)
{
DDSCAPS2 backBufferCaps = {};
backBufferCaps.dwCaps = DDSCAPS_BACKBUFFER;
surface->GetAttachedSurface(surface, &backBufferCaps, &backBuffer.getRef());
g_frontBuffer->GetAttachedSurface(g_frontBuffer, &caps, &backBuffer.getRef());
}
else
{
CALL_ORIG_PROC(DirectDrawCreateClipper, 0, &g_clipper.getRef(), nullptr);
surface->SetClipper(surface, g_clipper);
}
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;
surface->SetPrivateData(surface, IID_IReleaseNotifier,
&g_releaseNotifier, sizeof(&g_releaseNotifier), DDSPD_IUNKNOWNPOINTER);
g_frontBuffer = surface.detach();
g_backBuffer = backBuffer;
g_surfaceDesc = desc;
g_isFullScreen = isFlippable;
return DD_OK;
return backBuffer;
}
bool isUpdateScheduled()
CompatPtr<IDirectDrawSurface7> getLastSurface()
{
return WAIT_OBJECT_0 == WaitForSingleObject(g_updateEvent, 0);
DDSCAPS2 caps = {};
caps.dwCaps = DDSCAPS_FLIP;;
CompatPtr<IDirectDrawSurface7> backBuffer(getBackBuffer());
CompatPtr<IDirectDrawSurface7> lastSurface;
if (backBuffer)
{
backBuffer->GetAttachedSurface(backBuffer, &caps, &lastSurface.getRef());
}
return lastSurface;
}
int msUntilNextUpdate()
UINT getFlipIntervalFromFlags(DWORD flags)
{
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)
if (flags & DDFLIP_NOVSYNC)
{
return D3dDdi::KernelModeThunks::isPresentReady() ? 0 : 2;
return 0;
}
return result;
if (flags & (DDFLIP_INTERVAL2 | DDFLIP_INTERVAL3 | DDFLIP_INTERVAL4))
{
UINT flipInterval = (flags & (DDFLIP_INTERVAL2 | DDFLIP_INTERVAL3 | DDFLIP_INTERVAL4)) >> 24;
if (flipInterval < 2 || flipInterval > 4)
{
flipInterval = 1;
}
return flipInterval;
}
return 1;
}
UINT getFlipInterval(DWORD flags)
{
if (0 == g_flipIntervalDriverOverride)
{
return 0;
}
UINT flipInterval = getFlipIntervalFromFlags(flags);
if (UINT_MAX != g_flipIntervalDriverOverride)
{
return max(flipInterval, g_flipIntervalDriverOverride);
}
return flipInterval;
}
bool isFlipPending()
{
if (g_isFlipPending)
{
g_isFlipPending = static_cast<int>(D3dDdi::KernelModeThunks::getLastDisplayedFrameCount() -
g_lastFlipFrameCount) < 0;
}
return g_isFlipPending;
}
bool isPresentPending()
{
if (g_isPresentPending)
{
g_isPresentPending = static_cast<int>(D3dDdi::KernelModeThunks::getLastDisplayedFrameCount() -
D3dDdi::KernelModeThunks::getLastSubmittedFrameCount()) < 0;
}
return g_isPresentPending;
}
void onRelease()
{
Compat::LogEnter("RealPrimarySurface::onRelease");
ResetEvent(g_updateEvent);
g_frontBuffer = nullptr;
g_backBuffer = nullptr;
g_clipper.release();
g_isFullScreen = false;
g_isFlipPending = false;
g_isPresentPending = false;
g_waitingForPrimaryUnlock = false;
g_paletteConverter.release();
g_qpcUpdateInterval = Time::g_qpcFrequency / 60;
ZeroMemory(&g_surfaceDesc, sizeof(g_surfaceDesc));
g_surfaceDesc = {};
Compat::LogLeave("RealPrimarySurface::onRelease");
}
void updateNow()
void onRestore()
{
ResetEvent(g_updateEvent);
DDSURFACEDESC2 desc = {};
desc.dwSize = sizeof(desc);
g_frontBuffer->GetSurfaceDesc(g_frontBuffer, &desc);
g_clipper.release();
const bool isFlippable = 0 != (desc.ddsCaps.dwCaps & DDSCAPS_FLIP);
if (!isFlippable)
{
CALL_ORIG_PROC(DirectDrawCreateClipper, 0, &g_clipper.getRef(), nullptr);
g_frontBuffer->SetClipper(g_frontBuffer, g_clipper);
}
g_surfaceDesc = desc;
g_isFullScreen = isFlippable;
g_flipIntervalDriverOverride = UINT_MAX;
g_isFlipPending = false;
g_isPresentPending = false;
g_isUpdatePending = false;
g_qpcLastUpdate = Time::queryPerformanceCounter() - Time::msToQpc(Config::delayedFlipModeTimeout);
if (isFlippable)
{
D3dDdi::KernelModeThunks::setFlipIntervalOverride(UINT_MAX);
g_frontBuffer->Flip(g_frontBuffer, nullptr, DDFLIP_WAIT | DDFLIP_NOVSYNC);
g_flipIntervalDriverOverride = D3dDdi::KernelModeThunks::getLastFlipInterval();
g_frontBuffer->Flip(g_frontBuffer, nullptr, DDFLIP_WAIT);
if (0 == g_flipIntervalDriverOverride)
{
g_flipIntervalDriverOverride = D3dDdi::KernelModeThunks::getLastFlipInterval();
if (1 == g_flipIntervalDriverOverride)
{
g_flipIntervalDriverOverride = UINT_MAX;
}
}
g_frontBuffer->Flip(g_frontBuffer, nullptr, DDFLIP_WAIT);
D3dDdi::KernelModeThunks::setFlipIntervalOverride(0);
g_lastFlipFrameCount = D3dDdi::KernelModeThunks::getLastSubmittedFrameCount();
}
D3dDdi::KernelModeThunks::waitForVerticalBlank();
}
void presentToPrimaryChain(CompatWeakPtr<IDirectDrawSurface7> src)
{
Compat::LogEnter("RealPrimarySurface::presentToPrimaryChain", src.get());
Gdi::VirtualScreen::update();
if (compatBlt() && g_isFullScreen)
BltToWindowViaGdiArgs bltToWindowViaGdiArgs;
if (!g_frontBuffer || !src || DDraw::RealPrimarySurface::isLost())
{
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);
EnumThreadWindows(Gdi::getGdiThreadId(), bltToWindowViaGdi,
reinterpret_cast<LPARAM>(&bltToWindowViaGdiArgs));
Compat::LogLeave("RealPrimarySurface::presentToPrimaryChain", src.get()) << false;
return;
}
Gdi::Region primaryRegion(D3dDdi::KernelModeThunks::getMonitorRect());
bltToWindowViaGdiArgs.primaryRegion = &primaryRegion;
EnumThreadWindows(Gdi::getGdiThreadId(), bltToWindowViaGdi,
reinterpret_cast<LPARAM>(&bltToWindowViaGdiArgs));
Gdi::DDrawAccessGuard accessGuard(Gdi::ACCESS_READ, DDraw::PrimarySurface::isGdiSurface(src.get()));
if (DDraw::PrimarySurface::getDesc().ddpfPixelFormat.dwRGBBitCount <= 8)
{
HDC paletteConverterDc = nullptr;
g_paletteConverter->GetDC(g_paletteConverter, &paletteConverterDc);
HDC srcDc = nullptr;
D3dDdi::Device::setReadOnlyGdiLock(true);
src->GetDC(src, &srcDc);
D3dDdi::Device::setReadOnlyGdiLock(false);
if (paletteConverterDc && srcDc)
{
CALL_ORIG_FUNC(BitBlt)(paletteConverterDc,
0, 0, g_surfaceDesc.dwWidth, g_surfaceDesc.dwHeight, srcDc, 0, 0, SRCCOPY);
}
src->ReleaseDC(src, srcDc);
g_paletteConverter->ReleaseDC(g_paletteConverter, paletteConverterDc);
bltToPrimaryChain(*g_paletteConverter);
}
else
{
bltToPrimaryChain(*src);
}
if (g_isFullScreen && src == DDraw::PrimarySurface::getGdiSurface())
{
bltVisibleLayeredWindowsToBackBuffer();
}
Compat::LogLeave("RealPrimarySurface::presentToPrimaryChain", src.get());
}
void updateNow(CompatWeakPtr<IDirectDrawSurface7> src, UINT flipInterval)
{
DDraw::ScopedThreadLock lock;
if (flipInterval <= 1 && isPresentPending())
{
g_isUpdatePending = true;
return;
}
presentToPrimaryChain(src);
g_isUpdatePending = false;
if (!g_isFullScreen)
{
g_isPresentPending = true;
return;
}
if (flipInterval > 1 && isPresentPending())
{
--flipInterval;
}
D3dDdi::KernelModeThunks::setFlipIntervalOverride(flipInterval);
g_frontBuffer->Flip(g_frontBuffer, nullptr, DDFLIP_WAIT);
// Workaround for Windows 8 multimon display glitches when presenting from GDI shared primary surface
D3dDdi::KernelModeThunks::setFlipIntervalOverride(UINT_MAX);
g_frontBuffer->Flip(g_frontBuffer, getLastSurface(), DDFLIP_WAIT);
D3dDdi::KernelModeThunks::setFlipIntervalOverride(0);
g_isPresentPending = 0 != flipInterval;
}
void updateNowIfNotBusy()
{
auto primary(DDraw::PrimarySurface::getPrimary());
RECT emptyRect = {};
HRESULT result = primary ? primary->BltFast(primary, 0, 0, primary, &emptyRect, DDBLTFAST_WAIT) : DD_OK;
g_waitingForPrimaryUnlock = DDERR_SURFACEBUSY == result || DDERR_LOCKEDSURFACES == result;
if (!g_waitingForPrimaryUnlock && DDERR_SURFACELOST != result)
{
const auto msSinceLastUpdate = Time::qpcToMs(Time::queryPerformanceCounter() - g_qpcLastUpdate);
updateNow(primary, msSinceLastUpdate > Config::delayedFlipModeTimeout ? 0 : 1);
}
}
DWORD WINAPI updateThreadProc(LPVOID /*lpParameter*/)
{
while (true)
const int msPresentDelayAfterVBlank = 1;
bool waitForVBlank = true;
while (!g_stopUpdateThread)
{
WaitForSingleObject(g_updateEvent, INFINITE);
if (g_stopUpdateThread)
if (waitForVBlank)
{
return 0;
D3dDdi::KernelModeThunks::waitForVerticalBlank();
if (!g_isFullScreen)
{
g_isPresentPending = false;
}
}
const int waitTime = msUntilNextUpdate();
if (waitTime > 0)
{
Sleep(waitTime);
continue;
}
Sleep(msPresentDelayAfterVBlank);
DDraw::ScopedThreadLock lock;
if (isUpdateScheduled() && msUntilNextUpdate() <= 0)
waitForVBlank = Time::qpcToMs(Time::queryPerformanceCounter() -
D3dDdi::KernelModeThunks::getQpcLastVerticalBlank()) >= msPresentDelayAfterVBlank;
if (waitForVBlank && g_isUpdatePending && 0 == g_disableUpdateCount && !isPresentPending())
{
updateNow();
updateNowIfNotBusy();
}
}
return 0;
}
}
@ -389,6 +497,7 @@ namespace DDraw
template <typename DirectDraw>
HRESULT RealPrimarySurface::create(CompatRef<DirectDraw> dd)
{
DDraw::ScopedThreadLock lock;
HRESULT result = createPaletteConverter(dd);
if (FAILED(result))
{
@ -400,7 +509,7 @@ namespace DDraw
desc.dwSize = sizeof(desc);
desc.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT;
desc.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE | DDSCAPS_COMPLEX | DDSCAPS_FLIP;
desc.dwBackBufferCount = 1;
desc.dwBackBufferCount = 2;
CompatPtr<typename Types<DirectDraw>::TCreatedSurface> surface;
result = dd->CreateSurface(&dd, &desc, &surface.getRef(), nullptr);
@ -420,7 +529,12 @@ namespace DDraw
return result;
}
return ::init(dd, surface);
g_frontBuffer = CompatPtr<IDirectDrawSurface7>::from(surface.get()).detach();
g_frontBuffer->SetPrivateData(g_frontBuffer, IID_IReleaseNotifier,
&g_releaseNotifier, sizeof(&g_releaseNotifier), DDSPD_IUNKNOWNPOINTER);
onRestore();
return DD_OK;
}
template HRESULT RealPrimarySurface::create(CompatRef<IDirectDraw>);
@ -430,41 +544,73 @@ namespace DDraw
void RealPrimarySurface::disableUpdates()
{
if (0 == g_disableUpdateCount++ && isUpdateScheduled())
{
ResetEvent(g_updateEvent);
g_isUpdateSuspended = true;
}
DDraw::ScopedThreadLock lock;
--g_disableUpdateCount;
}
void RealPrimarySurface::enableUpdates()
{
if (0 == --g_disableUpdateCount && g_isUpdateSuspended)
{
SetEvent(g_updateEvent);
g_isUpdateSuspended = false;
}
DDraw::ScopedThreadLock lock;
++g_disableUpdateCount;
}
HRESULT RealPrimarySurface::flip(DWORD flags)
HRESULT RealPrimarySurface::flip(CompatPtr<IDirectDrawSurface7> surfaceTargetOverride, DWORD flags)
{
if (!g_isFullScreen)
DDraw::ScopedThreadLock lock;
auto primary(PrimarySurface::getPrimary());
const bool isFlipEmulated = 0 != (PrimarySurface::getOrigCaps() & DDSCAPS_SYSTEMMEMORY);
if (isFlipEmulated && !surfaceTargetOverride)
{
return DDERR_NOTFLIPPABLE;
surfaceTargetOverride = PrimarySurface::getBackBuffer();
}
ResetEvent(g_updateEvent);
g_isUpdateSuspended = false;
HRESULT result = primary->Flip(primary, surfaceTargetOverride, DDFLIP_WAIT);
if (FAILED(result))
{
return result;
}
g_qpcLastFlip = Time::queryPerformanceCounter();
compatBlt();
HRESULT result = g_frontBuffer->Flip(g_frontBuffer, nullptr, flags);
g_qpcNextUpdate = Time::queryPerformanceCounter();
return result;
DWORD flipInterval = getFlipInterval(flags);
const auto msSinceLastUpdate = Time::qpcToMs(Time::queryPerformanceCounter() - g_qpcLastUpdate);
const bool isFlipDelayed = msSinceLastUpdate >= 0 && msSinceLastUpdate <= Config::delayedFlipModeTimeout;
if (isFlipDelayed)
{
CompatPtr<IDirectDrawSurface7> prevPrimarySurface(
surfaceTargetOverride ? surfaceTargetOverride : PrimarySurface::getLastSurface());
updateNow(prevPrimarySurface, flipInterval);
g_isUpdatePending = true;
}
if (isFlipEmulated)
{
surfaceTargetOverride->Blt(surfaceTargetOverride, nullptr, primary, nullptr, DDBLT_WAIT, nullptr);
}
if (!isFlipDelayed)
{
updateNow(primary, flipInterval);
}
if (0 != flipInterval)
{
g_isFlipPending = true;
g_lastFlipFrameCount = D3dDdi::KernelModeThunks::getLastSubmittedFrameCount();
}
g_lastFlipSurface = nullptr;
if (g_isFlipPending && !isFlipEmulated)
{
g_lastFlipSurface = Surface::getSurface(
surfaceTargetOverride ? *surfaceTargetOverride : *PrimarySurface::getLastSurface());
}
return DD_OK;
}
HRESULT RealPrimarySurface::getGammaRamp(DDGAMMARAMP* rampData)
{
DDraw::ScopedThreadLock lock;
auto gammaControl(CompatPtr<IDirectDrawGammaControl>::from(g_frontBuffer.get()));
if (!gammaControl)
{
@ -481,11 +627,6 @@ namespace DDraw
void RealPrimarySurface::init()
{
g_qpcNextUpdate = Time::queryPerformanceCounter();
g_qpcUpdateInterval = Time::g_qpcFrequency / 60;
g_qpcFlipModeTimeout = Time::g_qpcFrequency / Config::minExpectedFlipsPerSec;
g_updateEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr);
g_updateThread = CreateThread(nullptr, 0, &updateThreadProc, nullptr, 0, nullptr);
SetThreadPriority(g_updateThread, THREAD_PRIORITY_TIME_CRITICAL);
}
@ -497,11 +638,13 @@ namespace DDraw
bool RealPrimarySurface::isLost()
{
DDraw::ScopedThreadLock lock;
return g_frontBuffer && DDERR_SURFACELOST == g_frontBuffer->IsLost(g_frontBuffer);
}
void RealPrimarySurface::release()
{
DDraw::ScopedThreadLock lock;
g_frontBuffer.release();
}
@ -513,24 +656,28 @@ namespace DDraw
}
g_stopUpdateThread = true;
SetEvent(g_updateEvent);
if (WAIT_OBJECT_0 != WaitForSingleObject(g_updateThread, 1000))
{
TerminateThread(g_updateThread, 0);
Compat::Log() << "The update thread was terminated forcefully";
}
ResetEvent(g_updateEvent);
g_stopUpdateThread = false;
g_updateThread = nullptr;
}
HRESULT RealPrimarySurface::restore()
{
return g_frontBuffer->Restore(g_frontBuffer);
DDraw::ScopedThreadLock lock;
HRESULT result = g_frontBuffer->Restore(g_frontBuffer);
if (SUCCEEDED(result))
{
onRestore();
}
return result;
}
HRESULT RealPrimarySurface::setGammaRamp(DDGAMMARAMP* rampData)
{
DDraw::ScopedThreadLock lock;
auto gammaControl(CompatPtr<IDirectDrawGammaControl>::from(g_frontBuffer.get()));
if (!gammaControl)
{
@ -542,6 +689,7 @@ namespace DDraw
void RealPrimarySurface::setPalette()
{
DDraw::ScopedThreadLock lock;
if (g_surfaceDesc.ddpfPixelFormat.dwRGBBitCount <= 8)
{
g_frontBuffer->SetPalette(g_frontBuffer, PrimarySurface::s_palette);
@ -552,41 +700,44 @@ namespace DDraw
void RealPrimarySurface::update()
{
if (g_isUpdateSuspended)
DDraw::ScopedThreadLock lock;
g_qpcLastUpdate = Time::queryPerformanceCounter();
g_isUpdatePending = true;
if (g_waitingForPrimaryUnlock)
{
return;
}
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;
}
if (g_disableUpdateCount <= 0)
{
SetEvent(g_updateEvent);
}
else
{
g_isUpdateSuspended = true;
}
}
else if (msUntilNextUpdate() <= 0)
{
updateNow();
updateNowIfNotBusy();
}
}
void RealPrimarySurface::updatePalette()
{
DDraw::ScopedThreadLock lock;
if (PrimarySurface::s_palette)
{
update();
}
}
bool RealPrimarySurface::waitForFlip(Surface* surface, bool wait)
{
auto primary(DDraw::PrimarySurface::getPrimary());
if (!surface || !primary ||
surface != g_lastFlipSurface &&
surface != Surface::getSurface(*DDraw::PrimarySurface::getPrimary()))
{
return true;
}
if (!wait)
{
return !isFlipPending();
}
while (isFlipPending())
{
D3dDdi::KernelModeThunks::waitForVerticalBlank();
}
return true;
}
}

View File

@ -2,11 +2,13 @@
#include <ddraw.h>
#include "Common/CompatWeakPtr.h"
#include "Common/CompatPtr.h"
#include "Common/CompatRef.h"
namespace DDraw
{
class Surface;
class RealPrimarySurface
{
public:
@ -15,7 +17,7 @@ namespace DDraw
static void disableUpdates();
static void enableUpdates();
static HRESULT flip(DWORD flags);
static HRESULT flip(CompatPtr<IDirectDrawSurface7> surfaceTargetOverride, DWORD flags);
static HRESULT getGammaRamp(DDGAMMARAMP* rampData);
static CompatWeakPtr<IDirectDrawSurface7> getSurface();
static void init();
@ -28,5 +30,6 @@ namespace DDraw
static void setPalette();
static void update();
static void updatePalette();
static bool waitForFlip(Surface* surface, bool wait = true);
};
}

View File

@ -1,5 +1,3 @@
#include <ddraw.h>
#include "Common/CompatPtr.h"
#include "Common/CompatRef.h"
#include "Config/Config.h"
@ -16,24 +14,6 @@ namespace
CompatWeakPtr<IDirectDrawSurface7> g_primarySurface;
HANDLE g_gdiResourceHandle = nullptr;
DWORD g_origCaps = 0;
RECT g_monitorRect = {};
RECT getDdMonitorRect(CompatRef<IDirectDraw7> dd)
{
DDDEVICEIDENTIFIER2 di = {};
dd->GetDeviceIdentifier(&dd, &di, 0); // Calls D3DKMTOpenAdapterFromHdc, which updates last monitor below
HMONITOR monitor = D3dDdi::KernelModeThunks::getLastOpenAdapterMonitor();
if (!monitor)
{
monitor = MonitorFromPoint({}, MONITOR_DEFAULTTOPRIMARY);
}
MONITORINFO mi = {};
mi.cbSize = sizeof(mi);
GetMonitorInfo(monitor, &mi);
return mi.rcMonitor;
}
template <typename TSurface>
HANDLE getResourceHandle(TSurface& surface)
@ -56,7 +36,6 @@ namespace DDraw
g_gdiResourceHandle = nullptr;
g_primarySurface = nullptr;
g_origCaps = 0;
g_monitorRect = {};
s_palette = nullptr;
s_surfaceBuffers.clear();
ZeroMemory(&s_paletteEntries, sizeof(s_paletteEntries));
@ -100,19 +79,8 @@ namespace DDraw
g_primarySurface = surface7;
g_origCaps = origCaps;
g_monitorRect = getDdMonitorRect(*CompatPtr<IDirectDraw7>::from(&dd));
ZeroMemory(&g_primarySurfaceDesc, sizeof(g_primarySurfaceDesc));
g_primarySurfaceDesc.dwSize = sizeof(g_primarySurfaceDesc);
CompatVtable<IDirectDrawSurface7Vtbl>::s_origVtable.GetSurfaceDesc(surface7, &g_primarySurfaceDesc);
if (g_primarySurfaceDesc.ddsCaps.dwCaps & DDSCAPS_SYSTEMMEMORY)
{
resizeBuffers(*surface7);
}
g_gdiResourceHandle = getResourceHandle(*surface7);
D3dDdi::Device::setGdiResourceHandle(*reinterpret_cast<HANDLE*>(g_gdiResourceHandle));
onRestore();
return DD_OK;
}
@ -178,9 +146,29 @@ namespace DDraw
return nullptr;
}
RECT PrimarySurface::getMonitorRect()
CompatPtr<IDirectDrawSurface7> PrimarySurface::getBackBuffer()
{
return g_monitorRect;
DDSCAPS2 caps = {};
caps.dwCaps = DDSCAPS_BACKBUFFER;
CompatPtr<IDirectDrawSurface7> backBuffer;
g_primarySurface->GetAttachedSurface(g_primarySurface, &caps, &backBuffer.getRef());
return backBuffer;
}
CompatPtr<IDirectDrawSurface7> PrimarySurface::getLastSurface()
{
DDSCAPS2 caps = {};
caps.dwCaps = DDSCAPS_FLIP;
auto surface(CompatPtr<IDirectDrawSurface7>::from(g_primarySurface.get()));
CompatPtr<IDirectDrawSurface7> nextSurface;
while (SUCCEEDED(surface->GetAttachedSurface(surface, &caps, &nextSurface.getRef())) &&
nextSurface != g_primarySurface)
{
surface = nextSurface;
}
return surface;
}
CompatWeakPtr<IDirectDrawSurface7> PrimarySurface::getPrimary()
@ -207,14 +195,20 @@ namespace DDraw
void PrimarySurface::onRestore()
{
CompatPtr<IUnknown> ddUnk;
g_primarySurface.get()->lpVtbl->GetDDInterface(
g_primarySurface, reinterpret_cast<void**>(&ddUnk.getRef()));
CompatPtr<IDirectDraw7> dd7(ddUnk);
g_monitorRect = getDdMonitorRect(*dd7);
g_primarySurfaceDesc = {};
g_primarySurfaceDesc.dwSize = sizeof(g_primarySurfaceDesc);
g_primarySurface->GetSurfaceDesc(g_primarySurface, &g_primarySurfaceDesc);
if (g_primarySurfaceDesc.ddsCaps.dwCaps & DDSCAPS_SYSTEMMEMORY)
{
resizeBuffers();
}
g_gdiResourceHandle = getResourceHandle(*g_primarySurface);
D3dDdi::Device::setGdiResourceHandle(*reinterpret_cast<HANDLE*>(g_gdiResourceHandle));
}
void PrimarySurface::resizeBuffers(CompatRef<IDirectDrawSurface7> surface)
void PrimarySurface::resizeBuffers()
{
DDSCAPS2 flipCaps = {};
flipCaps.dwCaps = DDSCAPS_FLIP;
@ -226,7 +220,7 @@ namespace DDraw
const DWORD newBufferSize = g_primarySurfaceDesc.lPitch *
(g_primarySurfaceDesc.dwHeight + Config::primarySurfaceExtraRows);
auto surfacePtr(CompatPtr<IDirectDrawSurface7>::from(&surface));
auto surfacePtr(CompatPtr<IDirectDrawSurface7>::from(g_primarySurface.get()));
do
{
s_surfaceBuffers.push_back(std::vector<unsigned char>(newBufferSize));
@ -236,7 +230,7 @@ namespace DDraw
CompatPtr<IDirectDrawSurface7> nextSurface;
surfacePtr->GetAttachedSurface(surfacePtr, &flipCaps, &nextSurface.getRef());
surfacePtr.swap(nextSurface);
} while (surfacePtr && surfacePtr != &surface);
} while (surfacePtr && surfacePtr != g_primarySurface.get());
}
CompatWeakPtr<IDirectDrawPalette> PrimarySurface::s_palette;

View File

@ -2,6 +2,8 @@
#include <vector>
#include <ddraw.h>
#include "Common/CompatPtr.h"
#include "Common/CompatRef.h"
#include "DDraw/Surfaces/Surface.h"
@ -19,7 +21,8 @@ namespace DDraw
static HRESULT flipToGdiSurface();
static const DDSURFACEDESC2& getDesc();
static CompatPtr<IDirectDrawSurface7> getGdiSurface();
static RECT getMonitorRect();
static CompatPtr<IDirectDrawSurface7> getBackBuffer();
static CompatPtr<IDirectDrawSurface7> getLastSurface();
static CompatWeakPtr<IDirectDrawSurface7> getPrimary();
static DWORD getOrigCaps();
static void onRestore();
@ -35,7 +38,7 @@ namespace DDraw
virtual void createImpl() override;
static void resizeBuffers(CompatRef<IDirectDrawSurface7> surface);
static void resizeBuffers();
std::unique_ptr<Surface> m_surface;

View File

@ -1,4 +1,5 @@
#include "DDraw/DirectDrawPalette.h"
#include "DDraw/DirectDrawSurface.h"
#include "DDraw/RealPrimarySurface.h"
#include "DDraw/Surfaces/PrimarySurface.h"
#include "DDraw/Surfaces/PrimarySurfaceImpl.h"
@ -65,44 +66,14 @@ namespace DDraw
template <typename TSurface>
HRESULT PrimarySurfaceImpl<TSurface>::Flip(TSurface* This, TSurface* lpDDSurfaceTargetOverride, DWORD dwFlags)
{
if (RealPrimarySurface::isLost())
const bool wait = (dwFlags & DDFLIP_WAIT) || !(dwFlags & DDFLIP_DONOTWAIT) &&
CompatVtable<IDirectDrawSurface7Vtbl>::s_origVtablePtr == static_cast<void*>(This->lpVtbl);
if (!DDraw::RealPrimarySurface::waitForFlip(Surface::getSurface(*This), wait))
{
return DDERR_SURFACELOST;
return DDERR_WASSTILLDRAWING;
}
HRESULT result = m_impl.Flip(This, lpDDSurfaceTargetOverride, dwFlags);
if (FAILED(result))
{
return result;
}
const bool isFlipEmulated = 0 != (PrimarySurface::getOrigCaps() & DDSCAPS_SYSTEMMEMORY);
result = RealPrimarySurface::flip(dwFlags);
if (SUCCEEDED(result) && !isFlipEmulated)
{
return DD_OK;
}
undoFlip(This, lpDDSurfaceTargetOverride);
if (SUCCEEDED(result) && isFlipEmulated)
{
if (lpDDSurfaceTargetOverride)
{
s_origVtable.BltFast(This, 0, 0, lpDDSurfaceTargetOverride, nullptr, DDBLTFAST_WAIT);
}
else
{
TDdsCaps caps = {};
caps.dwCaps = DDSCAPS_BACKBUFFER;
CompatPtr<TSurface> backBuffer;
s_origVtable.GetAttachedSurface(This, &caps, &backBuffer.getRef());
s_origVtable.BltFast(This, 0, 0, backBuffer, nullptr, DDBLTFAST_WAIT);
}
}
return result;
return RealPrimarySurface::flip(CompatPtr<IDirectDrawSurface7>::from(lpDDSurfaceTargetOverride), dwFlags);
}
template <typename TSurface>

View File

@ -1,6 +1,7 @@
#include <set>
#include "Common/CompatRef.h"
#include "DDraw/RealPrimarySurface.h"
#include "DDraw/Repository.h"
#include "DDraw/Surfaces/PrimarySurface.h"
#include "DDraw/Surfaces/Surface.h"
@ -28,6 +29,20 @@ namespace
dst->SetColorKey(&dst, ckFlag, &ck);
}
}
template <typename TSurface>
bool waitForFlip(TSurface* This, DWORD flags, DWORD waitFlag, DWORD doNotWaitFlag)
{
if (!This)
{
return true;
}
const bool wait = (flags & waitFlag) || !(flags & doNotWaitFlag) &&
CompatVtable<IDirectDrawSurface7Vtbl>::s_origVtablePtr == static_cast<void*>(This->lpVtbl);
return DDraw::RealPrimarySurface::waitForFlip(DDraw::Surface::getSurface(*This), wait);
}
}
namespace DDraw
@ -136,31 +151,16 @@ namespace DDraw
}
}
template <typename TSurface>
void SurfaceImpl<TSurface>::undoFlip(TSurface* This, TSurface* targetOverride)
{
if (targetOverride)
{
SurfaceImpl::Flip(This, targetOverride, DDFLIP_WAIT);
}
else
{
TSurfaceDesc desc = {};
desc.dwSize = sizeof(desc);
s_origVtable.GetSurfaceDesc(This, &desc);
for (DWORD i = 0; i < desc.dwBackBufferCount; ++i)
{
SurfaceImpl::Flip(This, nullptr, DDFLIP_WAIT);
}
}
}
template <typename TSurface>
HRESULT SurfaceImpl<TSurface>::Blt(
TSurface* This, LPRECT lpDestRect, TSurface* lpDDSrcSurface, LPRECT lpSrcRect,
DWORD dwFlags, LPDDBLTFX lpDDBltFx)
{
if (!waitForFlip(This, dwFlags, DDBLT_WAIT, DDBLT_DONOTWAIT))
{
return DDERR_WASSTILLDRAWING;
}
Gdi::DDrawAccessGuard dstAccessGuard(Gdi::ACCESS_WRITE, PrimarySurface::isGdiSurface(This));
Gdi::DDrawAccessGuard srcAccessGuard(Gdi::ACCESS_READ, PrimarySurface::isGdiSurface(lpDDSrcSurface));
HRESULT result = s_origVtable.Blt(This, lpDestRect, lpDDSrcSurface, lpSrcRect, dwFlags, lpDDBltFx);
@ -182,6 +182,11 @@ namespace DDraw
HRESULT SurfaceImpl<TSurface>::BltFast(
TSurface* This, DWORD dwX, DWORD dwY, TSurface* lpDDSrcSurface, LPRECT lpSrcRect, DWORD dwTrans)
{
if (!waitForFlip(This, dwTrans, DDBLTFAST_WAIT, DDBLTFAST_DONOTWAIT))
{
return DDERR_WASSTILLDRAWING;
}
Gdi::DDrawAccessGuard dstAccessGuard(Gdi::ACCESS_WRITE, PrimarySurface::isGdiSurface(This));
Gdi::DDrawAccessGuard srcAccessGuard(Gdi::ACCESS_READ, PrimarySurface::isGdiSurface(lpDDSrcSurface));
HRESULT result = s_origVtable.BltFast(This, dwX, dwY, lpDDSrcSurface, lpSrcRect, dwTrans);
@ -221,6 +226,21 @@ namespace DDraw
return s_origVtable.Flip(This, lpDDSurfaceTargetOverride, dwFlags);
}
template <typename TSurface>
HRESULT SurfaceImpl<TSurface>::GetBltStatus(TSurface* This, DWORD dwFlags)
{
HRESULT result = s_origVtable.GetBltStatus(This, dwFlags);
if (SUCCEEDED(result) && (dwFlags & DDGBS_CANBLT))
{
const bool wait = false;
if (!RealPrimarySurface::waitForFlip(Surface::getSurface(*This), wait))
{
return DDERR_WASSTILLDRAWING;
}
}
return result;
}
template <typename TSurface>
HRESULT SurfaceImpl<TSurface>::GetCaps(TSurface* This, TDdsCaps* lpDDSCaps)
{
@ -230,8 +250,19 @@ namespace DDraw
template <typename TSurface>
HRESULT SurfaceImpl<TSurface>::GetDC(TSurface* This, HDC* lphDC)
{
Gdi::DDrawAccessGuard accessGuard(Gdi::ACCESS_WRITE);
return s_origVtable.GetDC(This, lphDC);
HRESULT result = DD_OK;
{
Gdi::DDrawAccessGuard accessGuard(Gdi::ACCESS_WRITE);
result = s_origVtable.GetDC(This, lphDC);
}
if (SUCCEEDED(result))
{
RealPrimarySurface::waitForFlip(Surface::getSurface(*This));
}
return result;
}
template <typename TSurface>
@ -246,6 +277,21 @@ namespace DDraw
reinterpret_cast<IDirectDraw*>(&dd), m_data->m_ddId, lplpDD);
}
template <typename TSurface>
HRESULT SurfaceImpl<TSurface>::GetFlipStatus(TSurface* This, DWORD dwFlags)
{
HRESULT result = s_origVtable.GetFlipStatus(This, dwFlags);
if (SUCCEEDED(result))
{
const bool wait = false;
if (!RealPrimarySurface::waitForFlip(Surface::getSurface(*This), wait))
{
return DDERR_WASSTILLDRAWING;
}
}
return result;
}
template <typename TSurface>
HRESULT SurfaceImpl<TSurface>::GetSurfaceDesc(TSurface* This, TSurfaceDesc* lpDDSurfaceDesc)
{
@ -263,6 +309,11 @@ namespace DDraw
TSurface* This, LPRECT lpDestRect, TSurfaceDesc* lpDDSurfaceDesc,
DWORD dwFlags, HANDLE hEvent)
{
if (!waitForFlip(This, dwFlags, DDLOCK_WAIT, DDLOCK_DONOTWAIT))
{
return DDERR_WASSTILLDRAWING;
}
Gdi::DDrawAccessGuard accessGuard((dwFlags & DDLOCK_READONLY) ? Gdi::ACCESS_READ : Gdi::ACCESS_WRITE,
PrimarySurface::isGdiSurface(This));
HRESULT result = s_origVtable.Lock(This, lpDestRect, lpDDSurfaceDesc, dwFlags, hEvent);

View File

@ -40,8 +40,10 @@ namespace DDraw
virtual HRESULT BltFast(TSurface* This, DWORD dwX, DWORD dwY,
TSurface* lpDDSrcSurface, LPRECT lpSrcRect, DWORD dwTrans);
virtual HRESULT Flip(TSurface* This, TSurface* lpDDSurfaceTargetOverride, DWORD dwFlags);
virtual HRESULT GetBltStatus(TSurface* This, DWORD dwFlags);
virtual HRESULT GetCaps(TSurface* This, TDdsCaps* lpDDSCaps);
virtual HRESULT GetDC(TSurface* This, HDC* lphDC);
virtual HRESULT GetFlipStatus(TSurface* This, DWORD dwFlags);
virtual HRESULT GetSurfaceDesc(TSurface* This, TSurfaceDesc* lpDDSurfaceDesc);
virtual HRESULT IsLost(TSurface* This);
virtual HRESULT Lock(TSurface* This, LPRECT lpDestRect, TSurfaceDesc* lpDDSurfaceDesc,
@ -52,8 +54,6 @@ namespace DDraw
virtual HRESULT Unlock(TSurface* This, TUnlockParam lpRect);
protected:
void undoFlip(TSurface* This, TSurface* targetOverride);
static const Vtable<TSurface>& s_origVtable;
private:

View File

@ -2,6 +2,7 @@
#include <Windows.h>
#include "D3dDdi/KernelModeThunks.h"
#include "DDraw/RealPrimarySurface.h"
#include "DDraw/ScopedThreadLock.h"
#include "DDraw/Surfaces/PrimarySurface.h"
@ -83,7 +84,7 @@ namespace
return false;
}
auto gdiSurface(Gdi::VirtualScreen::createSurface(DDraw::PrimarySurface::getMonitorRect()));
auto gdiSurface(Gdi::VirtualScreen::createSurface(D3dDdi::KernelModeThunks::getMonitorRect()));
if (!gdiSurface)
{
return false;
@ -96,8 +97,8 @@ namespace
CompatPtr<IDirectDrawClipper> clipper;
ddrawSurface->GetClipper(ddrawSurface, &clipper.getRef());
ddrawSurface->SetClipper(ddrawSurface, nullptr);
result = SUCCEEDED(ddrawSurface->BltFast(
ddrawSurface, 0, 0, gdiSurface, nullptr, DDBLTFAST_WAIT));
result = SUCCEEDED(ddrawSurface->Blt(
ddrawSurface, nullptr, gdiSurface, nullptr, DDBLT_WAIT, nullptr));
ddrawSurface->SetClipper(ddrawSurface, clipper);
}
else