mirror of
https://github.com/narzoul/DDrawCompat
synced 2024-12-30 08:55:36 +01:00
Fixes stuttering in Midtown Madness 2 Trial (and probably other games) when V-sync is enabled.
385 lines
9.3 KiB
C++
385 lines
9.3 KiB
C++
#include <atomic>
|
|
|
|
#include "CompatDirectDrawSurface.h"
|
|
#include "CompatGdi.h"
|
|
#include "CompatPaletteConverter.h"
|
|
#include "CompatPrimarySurface.h"
|
|
#include "CompatPtr.h"
|
|
#include "Config.h"
|
|
#include "DDrawScopedThreadLock.h"
|
|
#include "DDrawProcs.h"
|
|
#include "DDrawTypes.h"
|
|
#include "Hook.h"
|
|
#include "IReleaseNotifier.h"
|
|
#include "RealPrimarySurface.h"
|
|
#include "Time.h"
|
|
|
|
namespace
|
|
{
|
|
void onRelease();
|
|
void updateNow(long long qpcNow);
|
|
DWORD WINAPI updateThreadProc(LPVOID lpParameter);
|
|
|
|
CompatWeakPtr<IDirectDrawSurface7> g_frontBuffer;
|
|
CompatWeakPtr<IDirectDrawSurface7> g_backBuffer;
|
|
CompatWeakPtr<IDirectDrawClipper> g_clipper;
|
|
DDSURFACEDESC2 g_surfaceDesc = {};
|
|
IReleaseNotifier g_releaseNotifier(onRelease);
|
|
|
|
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;
|
|
|
|
std::atomic<bool> g_isFullScreen(false);
|
|
|
|
bool compatBlt(CompatRef<IDirectDrawSurface7> dest)
|
|
{
|
|
Compat::LogEnter("RealPrimarySurface::compatBlt", dest);
|
|
|
|
if (g_disableUpdateCount > 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool result = false;
|
|
|
|
auto primary(CompatPrimarySurface::getPrimary());
|
|
if (CompatPrimarySurface::getDesc().ddpfPixelFormat.dwRGBBitCount <= 8)
|
|
{
|
|
auto paletteConverter(CompatPaletteConverter::getSurface());
|
|
paletteConverter->Blt(paletteConverter, &g_updateRect,
|
|
primary, &g_updateRect, DDBLT_WAIT, nullptr);
|
|
|
|
HDC destDc = nullptr;
|
|
dest->GetDC(&dest, &destDc);
|
|
result = TRUE == CALL_ORIG_FUNC(BitBlt)(destDc, g_updateRect.left, g_updateRect.top,
|
|
g_updateRect.right - g_updateRect.left, g_updateRect.bottom - g_updateRect.top,
|
|
CompatPaletteConverter::getDc(), g_updateRect.left, g_updateRect.top, SRCCOPY);
|
|
dest->ReleaseDC(&dest, destDc);
|
|
|
|
if (&dest == g_frontBuffer)
|
|
{
|
|
// Force the screen to be updated. It won't refresh from BitBlt alone.
|
|
RECT r = { 0, 0, 1, 1 };
|
|
g_frontBuffer->BltFast(g_frontBuffer, 0, 0, g_frontBuffer, &r, DDBLTFAST_WAIT);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
result = SUCCEEDED(dest->Blt(&dest, &g_updateRect,
|
|
primary, &g_updateRect, DDBLT_WAIT, nullptr));
|
|
}
|
|
|
|
if (result)
|
|
{
|
|
SetRectEmpty(&g_updateRect);
|
|
}
|
|
|
|
Compat::LogLeave("RealPrimarySurface::compatBlt", dest);
|
|
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)
|
|
{
|
|
if (!CompatPaletteConverter::create())
|
|
{
|
|
return DDERR_GENERIC;
|
|
}
|
|
|
|
DDSURFACEDESC2 desc = {};
|
|
desc.dwSize = sizeof(desc);
|
|
surface->GetSurfaceDesc(surface, &desc);
|
|
|
|
const bool isFlippable = 0 != (desc.ddsCaps.dwCaps & DDSCAPS_FLIP);
|
|
CompatPtr<IDirectDrawSurface7> backBuffer;
|
|
if (isFlippable)
|
|
{
|
|
DDSCAPS2 backBufferCaps = {};
|
|
backBufferCaps.dwCaps = DDSCAPS_BACKBUFFER;
|
|
surface->GetAttachedSurface(surface, &backBufferCaps, &backBuffer.getRef());
|
|
}
|
|
|
|
g_qpcMinUpdateInterval = Time::g_qpcFrequency / Config::maxPrimaryUpdateRate;
|
|
g_qpcNextUpdate = Time::queryPerformanceCounter();
|
|
|
|
if (!g_updateEvent)
|
|
{
|
|
g_updateEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr);
|
|
}
|
|
|
|
if (!g_updateThread)
|
|
{
|
|
g_updateThread = CreateThread(nullptr, 0, &updateThreadProc, nullptr, 0, nullptr);
|
|
SetThreadPriority(g_updateThread, THREAD_PRIORITY_TIME_CRITICAL);
|
|
}
|
|
|
|
surface->SetPrivateData(surface, IID_IReleaseNotifier,
|
|
&g_releaseNotifier, sizeof(&g_releaseNotifier), DDSPD_IUNKNOWNPOINTER);
|
|
|
|
timeBeginPeriod(1);
|
|
|
|
g_frontBuffer = surface.detach();
|
|
g_backBuffer = backBuffer.detach();
|
|
g_surfaceDesc = desc;
|
|
g_isFullScreen = isFlippable;
|
|
|
|
return DD_OK;
|
|
}
|
|
|
|
bool isNextUpdateSignaledAndReady(long long qpcNow)
|
|
{
|
|
return Time::qpcToMs(qpcNow - g_qpcNextUpdate) >= 0 &&
|
|
WAIT_OBJECT_0 == WaitForSingleObject(g_updateEvent, 0);
|
|
}
|
|
|
|
void onRelease()
|
|
{
|
|
Compat::LogEnter("RealPrimarySurface::onRelease");
|
|
|
|
ResetEvent(g_updateEvent);
|
|
timeEndPeriod(1);
|
|
g_frontBuffer = nullptr;
|
|
g_backBuffer.release();
|
|
g_clipper = nullptr;
|
|
g_isFullScreen = false;
|
|
CompatPaletteConverter::release();
|
|
|
|
ZeroMemory(&g_surfaceDesc, sizeof(g_surfaceDesc));
|
|
|
|
Compat::LogLeave("RealPrimarySurface::onRelease");
|
|
}
|
|
|
|
void updateNow(long long qpcNow)
|
|
{
|
|
ResetEvent(g_updateEvent);
|
|
|
|
if (compatBlt(*g_frontBuffer))
|
|
{
|
|
long long qpcNextUpdate = getNextUpdateQpc(qpcNow);
|
|
if (Time::qpcToMs(qpcNow - qpcNextUpdate) >= 0)
|
|
{
|
|
qpcNextUpdate += g_qpcMinUpdateInterval;
|
|
}
|
|
g_qpcNextUpdate = qpcNextUpdate;
|
|
}
|
|
}
|
|
|
|
DWORD WINAPI updateThreadProc(LPVOID /*lpParameter*/)
|
|
{
|
|
while (true)
|
|
{
|
|
WaitForSingleObject(g_updateEvent, INFINITE);
|
|
|
|
if (g_stopUpdateThread)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
const long long qpcTargetNextUpdate = g_qpcNextUpdate;
|
|
const int msUntilNextUpdate =
|
|
Time::qpcToMs(qpcTargetNextUpdate - Time::queryPerformanceCounter());
|
|
if (msUntilNextUpdate > 0)
|
|
{
|
|
Sleep(msUntilNextUpdate);
|
|
}
|
|
|
|
Compat::DDrawScopedThreadLock lock;
|
|
const long long qpcNow = Time::queryPerformanceCounter();
|
|
const bool isTargetUpdateStillNeeded = qpcTargetNextUpdate == g_qpcNextUpdate;
|
|
if (g_frontBuffer && (isTargetUpdateStillNeeded || isNextUpdateSignaledAndReady(qpcNow)))
|
|
{
|
|
updateNow(qpcNow);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
template <typename DirectDraw>
|
|
HRESULT RealPrimarySurface::create(CompatRef<DirectDraw> dd)
|
|
{
|
|
typename Types<DirectDraw>::TSurfaceDesc desc = {};
|
|
desc.dwSize = sizeof(desc);
|
|
desc.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT;
|
|
desc.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE | DDSCAPS_COMPLEX | DDSCAPS_FLIP;
|
|
desc.dwBackBufferCount = 2;
|
|
|
|
CompatPtr<typename Types<DirectDraw>::TCreatedSurface> surface;
|
|
HRESULT result = dd->CreateSurface(&dd, &desc, &surface.getRef(), nullptr);
|
|
|
|
bool isFlippable = true;
|
|
if (DDERR_NOEXCLUSIVEMODE == result)
|
|
{
|
|
desc.dwFlags = DDSD_CAPS;
|
|
desc.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;
|
|
desc.dwBackBufferCount = 0;
|
|
isFlippable = false;
|
|
result = dd->CreateSurface(&dd, &desc, &surface.getRef(), nullptr);
|
|
}
|
|
|
|
if (FAILED(result))
|
|
{
|
|
Compat::Log() << "Failed to create the real primary surface";
|
|
return result;
|
|
}
|
|
|
|
return init(surface);
|
|
}
|
|
|
|
template HRESULT RealPrimarySurface::create(CompatRef<IDirectDraw>);
|
|
template HRESULT RealPrimarySurface::create(CompatRef<IDirectDraw2>);
|
|
template HRESULT RealPrimarySurface::create(CompatRef<IDirectDraw4>);
|
|
template HRESULT RealPrimarySurface::create(CompatRef<IDirectDraw7>);
|
|
|
|
void RealPrimarySurface::disableUpdates()
|
|
{
|
|
++g_disableUpdateCount;
|
|
ResetEvent(g_updateEvent);
|
|
}
|
|
|
|
void RealPrimarySurface::enableUpdates()
|
|
{
|
|
if (0 == --g_disableUpdateCount)
|
|
{
|
|
update();
|
|
}
|
|
}
|
|
|
|
HRESULT RealPrimarySurface::flip(DWORD flags)
|
|
{
|
|
if (!g_isFullScreen)
|
|
{
|
|
return DDERR_NOTFLIPPABLE;
|
|
}
|
|
|
|
ResetEvent(g_updateEvent);
|
|
|
|
invalidate(nullptr);
|
|
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);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
CompatWeakPtr<IDirectDrawSurface7> RealPrimarySurface::getSurface()
|
|
{
|
|
return g_frontBuffer;
|
|
}
|
|
|
|
void RealPrimarySurface::invalidate(const RECT* rect)
|
|
{
|
|
if (rect)
|
|
{
|
|
UnionRect(&g_updateRect, &g_updateRect, rect);
|
|
}
|
|
else
|
|
{
|
|
auto primaryDesc = CompatPrimarySurface::getDesc();
|
|
SetRect(&g_updateRect, 0, 0, primaryDesc.dwWidth, primaryDesc.dwHeight);
|
|
}
|
|
}
|
|
|
|
bool RealPrimarySurface::isFullScreen()
|
|
{
|
|
return g_isFullScreen;
|
|
}
|
|
|
|
bool RealPrimarySurface::isLost()
|
|
{
|
|
return g_frontBuffer && DDERR_SURFACELOST == g_frontBuffer->IsLost(g_frontBuffer);
|
|
}
|
|
|
|
void RealPrimarySurface::release()
|
|
{
|
|
g_frontBuffer.release();
|
|
}
|
|
|
|
void RealPrimarySurface::removeUpdateThread()
|
|
{
|
|
if (!g_updateThread)
|
|
{
|
|
return;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
void RealPrimarySurface::setClipper(CompatWeakPtr<IDirectDrawClipper> clipper)
|
|
{
|
|
HRESULT result = g_frontBuffer->SetClipper(g_frontBuffer, clipper);
|
|
if (FAILED(result))
|
|
{
|
|
LOG_ONCE("Failed to set clipper on the real primary surface: " << result);
|
|
return;
|
|
}
|
|
CompatPaletteConverter::setClipper(clipper);
|
|
g_clipper = clipper;
|
|
}
|
|
|
|
void RealPrimarySurface::setPalette()
|
|
{
|
|
if (g_surfaceDesc.ddpfPixelFormat.dwRGBBitCount <= 8)
|
|
{
|
|
g_frontBuffer->SetPalette(g_frontBuffer, CompatPrimarySurface::palette);
|
|
}
|
|
|
|
updatePalette(0, 256);
|
|
}
|
|
|
|
void RealPrimarySurface::update()
|
|
{
|
|
if (!IsRectEmpty(&g_updateRect) && 0 == g_disableUpdateCount && (g_isFullScreen || g_clipper))
|
|
{
|
|
const long long qpcNow = Time::queryPerformanceCounter();
|
|
if (Time::qpcToMs(qpcNow - g_qpcNextUpdate) >= 0)
|
|
{
|
|
updateNow(qpcNow);
|
|
}
|
|
else
|
|
{
|
|
SetEvent(g_updateEvent);
|
|
}
|
|
}
|
|
}
|
|
|
|
void RealPrimarySurface::updatePalette(DWORD startingEntry, DWORD count)
|
|
{
|
|
CompatPaletteConverter::updatePalette(startingEntry, count);
|
|
CompatGdi::updatePalette(startingEntry, count);
|
|
if (CompatPrimarySurface::palette)
|
|
{
|
|
invalidate(nullptr);
|
|
update();
|
|
}
|
|
}
|