2016-02-27 19:26:50 +01:00
|
|
|
#include "CompatDirectDrawPalette.h"
|
2016-01-10 00:23:32 +01:00
|
|
|
#include "CompatDirectDrawSurface.h"
|
2016-01-04 23:37:58 +01:00
|
|
|
#include "CompatGdi.h"
|
2016-01-10 00:23:32 +01:00
|
|
|
#include "CompatGdiCaret.h"
|
2016-01-04 23:37:58 +01:00
|
|
|
#include "CompatGdiDcCache.h"
|
2016-03-19 13:15:22 +01:00
|
|
|
#include "CompatGdiDcFunctions.h"
|
2016-03-19 13:33:07 +01:00
|
|
|
#include "CompatGdiScrollFunctions.h"
|
2016-01-10 00:23:32 +01:00
|
|
|
#include "CompatGdiWinProc.h"
|
|
|
|
#include "CompatPrimarySurface.h"
|
|
|
|
#include "DDrawProcs.h"
|
|
|
|
#include "RealPrimarySurface.h"
|
2016-01-04 23:37:58 +01:00
|
|
|
|
|
|
|
namespace
|
|
|
|
{
|
Fixed GDI interworking deadlocks
Because both the DirectDraw and GDI thread locks are held by the thread that
initially calls beginGdiRendering, a deadlock can occur if a complex rendering
operation uses synchronized worker threads for some subtasks and they also need
access to the resources locked by the initial thread.
To resolve this, the GDI thread lock is no longer held after beginGdiRendering
returns, and the DirectDraw thread lock is only taken during the initial entry.
However, this introduces another problem, because now the final endGdiRendering
might not be called by the same thread that initially called beginGdiRendering.
If this happens, a deadlock will occur because the initial thread is still
holding the DirectDraw thread lock while the other thread is trying to acquire
it to unlock the primary surface.
To resolve this, the initial thread will always be the one to release the lock
on the primary surface, waiting for other threads to finish using GDI DCs if
necessary. This also means that other threads won't be able to create new
cached DCs (as they would need the DD thread lock), so to prevent yet another
deadlock, the initial thread always preallocates a number of DCs in the cache,
and only the initial thread is allowed to extend the cache.
2016-01-18 23:23:50 +01:00
|
|
|
DWORD g_renderingRefCount = 0;
|
|
|
|
DWORD g_ddLockThreadRenderingRefCount = 0;
|
|
|
|
DWORD g_ddLockThreadId = 0;
|
|
|
|
HANDLE g_ddUnlockBeginEvent = nullptr;
|
|
|
|
HANDLE g_ddUnlockEndEvent = nullptr;
|
|
|
|
bool g_isDelayedUnlockPending = false;
|
2016-01-10 00:23:32 +01:00
|
|
|
|
2016-02-14 17:11:34 +01:00
|
|
|
bool g_isPaletteUsed = false;
|
|
|
|
PALETTEENTRY g_usedPaletteEntries[256] = {};
|
|
|
|
|
2016-01-10 00:23:32 +01:00
|
|
|
FARPROC getProcAddress(HMODULE module, const char* procName)
|
|
|
|
{
|
|
|
|
if (!module || !procName)
|
|
|
|
{
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
PIMAGE_DOS_HEADER dosHeader = reinterpret_cast<PIMAGE_DOS_HEADER>(module);
|
|
|
|
if (IMAGE_DOS_SIGNATURE != dosHeader->e_magic) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
char* moduleBase = reinterpret_cast<char*>(module);
|
|
|
|
|
|
|
|
PIMAGE_NT_HEADERS ntHeader = reinterpret_cast<PIMAGE_NT_HEADERS>(
|
|
|
|
reinterpret_cast<char*>(dosHeader) + dosHeader->e_lfanew);
|
|
|
|
if (IMAGE_NT_SIGNATURE != ntHeader->Signature)
|
|
|
|
{
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
PIMAGE_EXPORT_DIRECTORY exportDir = reinterpret_cast<PIMAGE_EXPORT_DIRECTORY>(
|
|
|
|
moduleBase + ntHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
|
|
|
|
|
|
|
|
DWORD* rvaOfNames = reinterpret_cast<DWORD*>(moduleBase + exportDir->AddressOfNames);
|
|
|
|
|
|
|
|
for (DWORD i = 0; i < exportDir->NumberOfNames; ++i)
|
|
|
|
{
|
|
|
|
if (0 == strcmp(procName, moduleBase + rvaOfNames[i]))
|
|
|
|
{
|
|
|
|
WORD* nameOrds = reinterpret_cast<WORD*>(moduleBase + exportDir->AddressOfNameOrdinals);
|
|
|
|
DWORD* rvaOfFunctions = reinterpret_cast<DWORD*>(moduleBase + exportDir->AddressOfFunctions);
|
|
|
|
return reinterpret_cast<FARPROC>(moduleBase + rvaOfFunctions[nameOrds[i]]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2016-02-21 12:03:31 +01:00
|
|
|
BOOL CALLBACK invalidateWindow(HWND hwnd, LPARAM lParam)
|
2016-01-10 00:23:32 +01:00
|
|
|
{
|
2016-02-21 12:03:31 +01:00
|
|
|
if (!IsWindowVisible(hwnd))
|
|
|
|
{
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
2016-01-10 00:23:32 +01:00
|
|
|
DWORD processId = 0;
|
|
|
|
GetWindowThreadProcessId(hwnd, &processId);
|
2016-02-21 12:03:31 +01:00
|
|
|
if (processId != GetCurrentProcessId())
|
|
|
|
{
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (lParam)
|
|
|
|
{
|
|
|
|
POINT origin = {};
|
|
|
|
ClientToScreen(hwnd, &origin);
|
|
|
|
RECT rect = *reinterpret_cast<const RECT*>(lParam);
|
|
|
|
OffsetRect(&rect, -origin.x, -origin.y);
|
|
|
|
RedrawWindow(hwnd, &rect, nullptr, RDW_ERASE | RDW_FRAME | RDW_INVALIDATE | RDW_ALLCHILDREN);
|
|
|
|
}
|
|
|
|
else
|
2016-01-10 00:23:32 +01:00
|
|
|
{
|
|
|
|
RedrawWindow(hwnd, nullptr, nullptr, RDW_ERASE | RDW_FRAME | RDW_INVALIDATE | RDW_ALLCHILDREN);
|
|
|
|
}
|
2016-02-21 12:03:31 +01:00
|
|
|
|
2016-01-10 00:23:32 +01:00
|
|
|
return TRUE;
|
|
|
|
}
|
Fixed GDI interworking deadlocks
Because both the DirectDraw and GDI thread locks are held by the thread that
initially calls beginGdiRendering, a deadlock can occur if a complex rendering
operation uses synchronized worker threads for some subtasks and they also need
access to the resources locked by the initial thread.
To resolve this, the GDI thread lock is no longer held after beginGdiRendering
returns, and the DirectDraw thread lock is only taken during the initial entry.
However, this introduces another problem, because now the final endGdiRendering
might not be called by the same thread that initially called beginGdiRendering.
If this happens, a deadlock will occur because the initial thread is still
holding the DirectDraw thread lock while the other thread is trying to acquire
it to unlock the primary surface.
To resolve this, the initial thread will always be the one to release the lock
on the primary surface, waiting for other threads to finish using GDI DCs if
necessary. This also means that other threads won't be able to create new
cached DCs (as they would need the DD thread lock), so to prevent yet another
deadlock, the initial thread always preallocates a number of DCs in the cache,
and only the initial thread is allowed to extend the cache.
2016-01-18 23:23:50 +01:00
|
|
|
|
|
|
|
bool lockPrimarySurface()
|
|
|
|
{
|
|
|
|
Compat::origProcs.AcquireDDThreadLock();
|
|
|
|
|
|
|
|
DDSURFACEDESC2 desc = {};
|
|
|
|
desc.dwSize = sizeof(desc);
|
|
|
|
if (FAILED(CompatDirectDrawSurface<IDirectDrawSurface7>::s_origVtable.Lock(
|
|
|
|
CompatPrimarySurface::surface, nullptr, &desc, DDLOCK_WAIT, nullptr)))
|
|
|
|
{
|
|
|
|
Compat::origProcs.ReleaseDDThreadLock();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
g_ddLockThreadId = GetCurrentThreadId();
|
|
|
|
CompatGdiDcCache::setDdLockThreadId(g_ddLockThreadId);
|
|
|
|
CompatGdiDcCache::setSurfaceMemory(desc.lpSurface, desc.lPitch);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2016-02-27 19:26:50 +01:00
|
|
|
/*
|
|
|
|
Workaround for correctly drawing icons with a transparent background using BitBlt and a monochrome
|
|
|
|
bitmap mask. Normally black (index 0) and white (index 255) are selected as foreground and background
|
|
|
|
colors on the target DC so that a BitBlt with the SRCAND ROP would preserve the background pixels and
|
|
|
|
set the foreground pixels to 0. (Logical AND with 0xFF preserves all bits.)
|
|
|
|
But if the physical palette contains another, earlier white entry, SRCAND will be performed with the
|
|
|
|
wrong index (less than 0xFF), erasing some of the bits from all background pixels.
|
|
|
|
This workaround replaces all unwanted white entries with a similar color.
|
|
|
|
*/
|
|
|
|
void replaceDuplicateWhitePaletteEntries(const PALETTEENTRY (&entries)[256])
|
|
|
|
{
|
|
|
|
PALETTEENTRY newEntries[256] = {};
|
|
|
|
memcpy(newEntries, entries, sizeof(entries));
|
|
|
|
|
|
|
|
bool isReplacementDone = false;
|
|
|
|
for (int i = 0; i < 255; ++i)
|
|
|
|
{
|
|
|
|
if (0xFF == entries[i].peRed && 0xFF == entries[i].peGreen && 0xFF == entries[i].peBlue)
|
|
|
|
{
|
|
|
|
newEntries[i].peRed = 0xFE;
|
|
|
|
newEntries[i].peGreen = 0xFE;
|
|
|
|
newEntries[i].peBlue = 0xFE;
|
|
|
|
isReplacementDone = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isReplacementDone)
|
|
|
|
{
|
|
|
|
CompatDirectDrawPalette::s_origVtable.SetEntries(
|
|
|
|
CompatPrimarySurface::palette, 0, 0, 256, newEntries);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Fixed GDI interworking deadlocks
Because both the DirectDraw and GDI thread locks are held by the thread that
initially calls beginGdiRendering, a deadlock can occur if a complex rendering
operation uses synchronized worker threads for some subtasks and they also need
access to the resources locked by the initial thread.
To resolve this, the GDI thread lock is no longer held after beginGdiRendering
returns, and the DirectDraw thread lock is only taken during the initial entry.
However, this introduces another problem, because now the final endGdiRendering
might not be called by the same thread that initially called beginGdiRendering.
If this happens, a deadlock will occur because the initial thread is still
holding the DirectDraw thread lock while the other thread is trying to acquire
it to unlock the primary surface.
To resolve this, the initial thread will always be the one to release the lock
on the primary surface, waiting for other threads to finish using GDI DCs if
necessary. This also means that other threads won't be able to create new
cached DCs (as they would need the DD thread lock), so to prevent yet another
deadlock, the initial thread always preallocates a number of DCs in the cache,
and only the initial thread is allowed to extend the cache.
2016-01-18 23:23:50 +01:00
|
|
|
void unlockPrimarySurface()
|
|
|
|
{
|
|
|
|
GdiFlush();
|
|
|
|
CompatDirectDrawSurface<IDirectDrawSurface7>::s_origVtable.Unlock(
|
|
|
|
CompatPrimarySurface::surface, nullptr);
|
|
|
|
RealPrimarySurface::update();
|
|
|
|
|
|
|
|
Compat::origProcs.ReleaseDDThreadLock();
|
|
|
|
}
|
2016-01-04 23:37:58 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
namespace CompatGdi
|
|
|
|
{
|
2016-01-10 00:23:32 +01:00
|
|
|
CRITICAL_SECTION g_gdiCriticalSection;
|
|
|
|
std::unordered_map<void*, const char*> g_funcNames;
|
|
|
|
|
Fixed GDI interworking deadlocks
Because both the DirectDraw and GDI thread locks are held by the thread that
initially calls beginGdiRendering, a deadlock can occur if a complex rendering
operation uses synchronized worker threads for some subtasks and they also need
access to the resources locked by the initial thread.
To resolve this, the GDI thread lock is no longer held after beginGdiRendering
returns, and the DirectDraw thread lock is only taken during the initial entry.
However, this introduces another problem, because now the final endGdiRendering
might not be called by the same thread that initially called beginGdiRendering.
If this happens, a deadlock will occur because the initial thread is still
holding the DirectDraw thread lock while the other thread is trying to acquire
it to unlock the primary surface.
To resolve this, the initial thread will always be the one to release the lock
on the primary surface, waiting for other threads to finish using GDI DCs if
necessary. This also means that other threads won't be able to create new
cached DCs (as they would need the DD thread lock), so to prevent yet another
deadlock, the initial thread always preallocates a number of DCs in the cache,
and only the initial thread is allowed to extend the cache.
2016-01-18 23:23:50 +01:00
|
|
|
GdiScopedThreadLock::GdiScopedThreadLock() : m_isLocked(true)
|
2016-01-04 23:37:58 +01:00
|
|
|
{
|
|
|
|
EnterCriticalSection(&g_gdiCriticalSection);
|
|
|
|
}
|
|
|
|
|
|
|
|
GdiScopedThreadLock::~GdiScopedThreadLock()
|
|
|
|
{
|
Fixed GDI interworking deadlocks
Because both the DirectDraw and GDI thread locks are held by the thread that
initially calls beginGdiRendering, a deadlock can occur if a complex rendering
operation uses synchronized worker threads for some subtasks and they also need
access to the resources locked by the initial thread.
To resolve this, the GDI thread lock is no longer held after beginGdiRendering
returns, and the DirectDraw thread lock is only taken during the initial entry.
However, this introduces another problem, because now the final endGdiRendering
might not be called by the same thread that initially called beginGdiRendering.
If this happens, a deadlock will occur because the initial thread is still
holding the DirectDraw thread lock while the other thread is trying to acquire
it to unlock the primary surface.
To resolve this, the initial thread will always be the one to release the lock
on the primary surface, waiting for other threads to finish using GDI DCs if
necessary. This also means that other threads won't be able to create new
cached DCs (as they would need the DD thread lock), so to prevent yet another
deadlock, the initial thread always preallocates a number of DCs in the cache,
and only the initial thread is allowed to extend the cache.
2016-01-18 23:23:50 +01:00
|
|
|
unlock();
|
|
|
|
}
|
|
|
|
|
|
|
|
void GdiScopedThreadLock::unlock()
|
|
|
|
{
|
|
|
|
if (m_isLocked)
|
|
|
|
{
|
|
|
|
LeaveCriticalSection(&g_gdiCriticalSection);
|
|
|
|
m_isLocked = false;
|
|
|
|
}
|
2016-01-04 23:37:58 +01:00
|
|
|
}
|
|
|
|
|
2016-01-10 00:23:32 +01:00
|
|
|
bool beginGdiRendering()
|
|
|
|
{
|
|
|
|
if (!RealPrimarySurface::isFullScreen())
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
Fixed GDI interworking deadlocks
Because both the DirectDraw and GDI thread locks are held by the thread that
initially calls beginGdiRendering, a deadlock can occur if a complex rendering
operation uses synchronized worker threads for some subtasks and they also need
access to the resources locked by the initial thread.
To resolve this, the GDI thread lock is no longer held after beginGdiRendering
returns, and the DirectDraw thread lock is only taken during the initial entry.
However, this introduces another problem, because now the final endGdiRendering
might not be called by the same thread that initially called beginGdiRendering.
If this happens, a deadlock will occur because the initial thread is still
holding the DirectDraw thread lock while the other thread is trying to acquire
it to unlock the primary surface.
To resolve this, the initial thread will always be the one to release the lock
on the primary surface, waiting for other threads to finish using GDI DCs if
necessary. This also means that other threads won't be able to create new
cached DCs (as they would need the DD thread lock), so to prevent yet another
deadlock, the initial thread always preallocates a number of DCs in the cache,
and only the initial thread is allowed to extend the cache.
2016-01-18 23:23:50 +01:00
|
|
|
GdiScopedThreadLock gdiLock;
|
2016-01-10 00:23:32 +01:00
|
|
|
|
Fixed GDI interworking deadlocks
Because both the DirectDraw and GDI thread locks are held by the thread that
initially calls beginGdiRendering, a deadlock can occur if a complex rendering
operation uses synchronized worker threads for some subtasks and they also need
access to the resources locked by the initial thread.
To resolve this, the GDI thread lock is no longer held after beginGdiRendering
returns, and the DirectDraw thread lock is only taken during the initial entry.
However, this introduces another problem, because now the final endGdiRendering
might not be called by the same thread that initially called beginGdiRendering.
If this happens, a deadlock will occur because the initial thread is still
holding the DirectDraw thread lock while the other thread is trying to acquire
it to unlock the primary surface.
To resolve this, the initial thread will always be the one to release the lock
on the primary surface, waiting for other threads to finish using GDI DCs if
necessary. This also means that other threads won't be able to create new
cached DCs (as they would need the DD thread lock), so to prevent yet another
deadlock, the initial thread always preallocates a number of DCs in the cache,
and only the initial thread is allowed to extend the cache.
2016-01-18 23:23:50 +01:00
|
|
|
if (0 == g_renderingRefCount)
|
2016-01-10 00:23:32 +01:00
|
|
|
{
|
Fixed GDI interworking deadlocks
Because both the DirectDraw and GDI thread locks are held by the thread that
initially calls beginGdiRendering, a deadlock can occur if a complex rendering
operation uses synchronized worker threads for some subtasks and they also need
access to the resources locked by the initial thread.
To resolve this, the GDI thread lock is no longer held after beginGdiRendering
returns, and the DirectDraw thread lock is only taken during the initial entry.
However, this introduces another problem, because now the final endGdiRendering
might not be called by the same thread that initially called beginGdiRendering.
If this happens, a deadlock will occur because the initial thread is still
holding the DirectDraw thread lock while the other thread is trying to acquire
it to unlock the primary surface.
To resolve this, the initial thread will always be the one to release the lock
on the primary surface, waiting for other threads to finish using GDI DCs if
necessary. This also means that other threads won't be able to create new
cached DCs (as they would need the DD thread lock), so to prevent yet another
deadlock, the initial thread always preallocates a number of DCs in the cache,
and only the initial thread is allowed to extend the cache.
2016-01-18 23:23:50 +01:00
|
|
|
if (!lockPrimarySurface())
|
2016-01-10 00:23:32 +01:00
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
Fixed GDI interworking deadlocks
Because both the DirectDraw and GDI thread locks are held by the thread that
initially calls beginGdiRendering, a deadlock can occur if a complex rendering
operation uses synchronized worker threads for some subtasks and they also need
access to the resources locked by the initial thread.
To resolve this, the GDI thread lock is no longer held after beginGdiRendering
returns, and the DirectDraw thread lock is only taken during the initial entry.
However, this introduces another problem, because now the final endGdiRendering
might not be called by the same thread that initially called beginGdiRendering.
If this happens, a deadlock will occur because the initial thread is still
holding the DirectDraw thread lock while the other thread is trying to acquire
it to unlock the primary surface.
To resolve this, the initial thread will always be the one to release the lock
on the primary surface, waiting for other threads to finish using GDI DCs if
necessary. This also means that other threads won't be able to create new
cached DCs (as they would need the DD thread lock), so to prevent yet another
deadlock, the initial thread always preallocates a number of DCs in the cache,
and only the initial thread is allowed to extend the cache.
2016-01-18 23:23:50 +01:00
|
|
|
++g_ddLockThreadRenderingRefCount;
|
|
|
|
}
|
|
|
|
else if (GetCurrentThreadId() == g_ddLockThreadId)
|
|
|
|
{
|
|
|
|
++g_ddLockThreadRenderingRefCount;
|
2016-01-10 00:23:32 +01:00
|
|
|
}
|
|
|
|
|
2016-02-14 17:11:34 +01:00
|
|
|
if (!g_isPaletteUsed && CompatPrimarySurface::palette)
|
|
|
|
{
|
|
|
|
g_isPaletteUsed = true;
|
|
|
|
ZeroMemory(g_usedPaletteEntries, sizeof(g_usedPaletteEntries));
|
|
|
|
CompatPrimarySurface::palette->lpVtbl->GetEntries(
|
|
|
|
CompatPrimarySurface::palette, 0, 0, 256, g_usedPaletteEntries);
|
|
|
|
}
|
|
|
|
|
Fixed GDI interworking deadlocks
Because both the DirectDraw and GDI thread locks are held by the thread that
initially calls beginGdiRendering, a deadlock can occur if a complex rendering
operation uses synchronized worker threads for some subtasks and they also need
access to the resources locked by the initial thread.
To resolve this, the GDI thread lock is no longer held after beginGdiRendering
returns, and the DirectDraw thread lock is only taken during the initial entry.
However, this introduces another problem, because now the final endGdiRendering
might not be called by the same thread that initially called beginGdiRendering.
If this happens, a deadlock will occur because the initial thread is still
holding the DirectDraw thread lock while the other thread is trying to acquire
it to unlock the primary surface.
To resolve this, the initial thread will always be the one to release the lock
on the primary surface, waiting for other threads to finish using GDI DCs if
necessary. This also means that other threads won't be able to create new
cached DCs (as they would need the DD thread lock), so to prevent yet another
deadlock, the initial thread always preallocates a number of DCs in the cache,
and only the initial thread is allowed to extend the cache.
2016-01-18 23:23:50 +01:00
|
|
|
++g_renderingRefCount;
|
2016-01-10 00:23:32 +01:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void endGdiRendering()
|
|
|
|
{
|
Fixed GDI interworking deadlocks
Because both the DirectDraw and GDI thread locks are held by the thread that
initially calls beginGdiRendering, a deadlock can occur if a complex rendering
operation uses synchronized worker threads for some subtasks and they also need
access to the resources locked by the initial thread.
To resolve this, the GDI thread lock is no longer held after beginGdiRendering
returns, and the DirectDraw thread lock is only taken during the initial entry.
However, this introduces another problem, because now the final endGdiRendering
might not be called by the same thread that initially called beginGdiRendering.
If this happens, a deadlock will occur because the initial thread is still
holding the DirectDraw thread lock while the other thread is trying to acquire
it to unlock the primary surface.
To resolve this, the initial thread will always be the one to release the lock
on the primary surface, waiting for other threads to finish using GDI DCs if
necessary. This also means that other threads won't be able to create new
cached DCs (as they would need the DD thread lock), so to prevent yet another
deadlock, the initial thread always preallocates a number of DCs in the cache,
and only the initial thread is allowed to extend the cache.
2016-01-18 23:23:50 +01:00
|
|
|
CompatGdi::GdiScopedThreadLock gdiLock;
|
|
|
|
|
|
|
|
if (GetCurrentThreadId() == g_ddLockThreadId)
|
|
|
|
{
|
|
|
|
if (1 == g_renderingRefCount)
|
|
|
|
{
|
|
|
|
unlockPrimarySurface();
|
|
|
|
g_ddLockThreadRenderingRefCount = 0;
|
|
|
|
g_renderingRefCount = 0;
|
|
|
|
}
|
|
|
|
else if (1 == g_ddLockThreadRenderingRefCount)
|
|
|
|
{
|
|
|
|
g_isDelayedUnlockPending = true;
|
|
|
|
gdiLock.unlock();
|
|
|
|
WaitForSingleObject(g_ddUnlockBeginEvent, INFINITE);
|
|
|
|
unlockPrimarySurface();
|
|
|
|
g_ddLockThreadRenderingRefCount = 0;
|
|
|
|
g_renderingRefCount = 0;
|
|
|
|
SetEvent(g_ddUnlockEndEvent);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
--g_ddLockThreadRenderingRefCount;
|
|
|
|
--g_renderingRefCount;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
2016-01-10 00:23:32 +01:00
|
|
|
{
|
Fixed GDI interworking deadlocks
Because both the DirectDraw and GDI thread locks are held by the thread that
initially calls beginGdiRendering, a deadlock can occur if a complex rendering
operation uses synchronized worker threads for some subtasks and they also need
access to the resources locked by the initial thread.
To resolve this, the GDI thread lock is no longer held after beginGdiRendering
returns, and the DirectDraw thread lock is only taken during the initial entry.
However, this introduces another problem, because now the final endGdiRendering
might not be called by the same thread that initially called beginGdiRendering.
If this happens, a deadlock will occur because the initial thread is still
holding the DirectDraw thread lock while the other thread is trying to acquire
it to unlock the primary surface.
To resolve this, the initial thread will always be the one to release the lock
on the primary surface, waiting for other threads to finish using GDI DCs if
necessary. This also means that other threads won't be able to create new
cached DCs (as they would need the DD thread lock), so to prevent yet another
deadlock, the initial thread always preallocates a number of DCs in the cache,
and only the initial thread is allowed to extend the cache.
2016-01-18 23:23:50 +01:00
|
|
|
--g_renderingRefCount;
|
|
|
|
if (1 == g_renderingRefCount && g_isDelayedUnlockPending)
|
|
|
|
{
|
|
|
|
SetEvent(g_ddUnlockBeginEvent);
|
|
|
|
WaitForSingleObject(g_ddUnlockEndEvent, INFINITE);
|
|
|
|
g_isDelayedUnlockPending = false;
|
|
|
|
}
|
2016-01-10 00:23:32 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void hookGdiFunction(const char* moduleName, const char* funcName, void*& origFuncPtr, void* newFuncPtr)
|
|
|
|
{
|
|
|
|
#ifdef _DEBUG
|
|
|
|
g_funcNames[origFuncPtr] = funcName;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
FARPROC procAddr = getProcAddress(GetModuleHandle(moduleName), funcName);
|
|
|
|
if (!procAddr)
|
|
|
|
{
|
|
|
|
Compat::Log() << "Failed to load the address of a GDI function: " << funcName;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
origFuncPtr = procAddr;
|
|
|
|
if (NO_ERROR != DetourAttach(&origFuncPtr, newFuncPtr))
|
|
|
|
{
|
|
|
|
Compat::Log() << "Failed to hook a GDI function: " << funcName;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-01-04 23:37:58 +01:00
|
|
|
void installHooks()
|
|
|
|
{
|
|
|
|
InitializeCriticalSection(&g_gdiCriticalSection);
|
|
|
|
if (CompatGdiDcCache::init())
|
|
|
|
{
|
Fixed GDI interworking deadlocks
Because both the DirectDraw and GDI thread locks are held by the thread that
initially calls beginGdiRendering, a deadlock can occur if a complex rendering
operation uses synchronized worker threads for some subtasks and they also need
access to the resources locked by the initial thread.
To resolve this, the GDI thread lock is no longer held after beginGdiRendering
returns, and the DirectDraw thread lock is only taken during the initial entry.
However, this introduces another problem, because now the final endGdiRendering
might not be called by the same thread that initially called beginGdiRendering.
If this happens, a deadlock will occur because the initial thread is still
holding the DirectDraw thread lock while the other thread is trying to acquire
it to unlock the primary surface.
To resolve this, the initial thread will always be the one to release the lock
on the primary surface, waiting for other threads to finish using GDI DCs if
necessary. This also means that other threads won't be able to create new
cached DCs (as they would need the DD thread lock), so to prevent yet another
deadlock, the initial thread always preallocates a number of DCs in the cache,
and only the initial thread is allowed to extend the cache.
2016-01-18 23:23:50 +01:00
|
|
|
g_ddUnlockBeginEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
|
|
|
|
g_ddUnlockEndEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
|
|
|
|
if (!g_ddUnlockBeginEvent || !g_ddUnlockEndEvent)
|
|
|
|
{
|
|
|
|
Compat::Log() << "Failed to create the unlock events for GDI";
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-03-19 13:22:30 +01:00
|
|
|
CompatGdiDcFunctions::installHooks();
|
2016-03-19 13:33:07 +01:00
|
|
|
CompatGdiScrollFunctions::installHooks();
|
2016-01-10 00:23:32 +01:00
|
|
|
CompatGdiWinProc::installHooks();
|
|
|
|
CompatGdiCaret::installHooks();
|
2016-01-04 23:37:58 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-02-21 12:03:31 +01:00
|
|
|
void invalidate(const RECT* rect)
|
2016-01-04 23:37:58 +01:00
|
|
|
{
|
2016-02-21 12:03:31 +01:00
|
|
|
EnumWindows(&invalidateWindow, reinterpret_cast<LPARAM>(rect));
|
2016-01-04 23:37:58 +01:00
|
|
|
}
|
|
|
|
|
2016-01-10 00:23:32 +01:00
|
|
|
void updatePalette()
|
2016-01-04 23:37:58 +01:00
|
|
|
{
|
|
|
|
GdiScopedThreadLock gdiLock;
|
2016-01-10 00:23:32 +01:00
|
|
|
CompatGdiDcCache::clear();
|
2016-02-14 17:11:34 +01:00
|
|
|
|
|
|
|
if (g_isPaletteUsed && CompatPrimarySurface::palette)
|
|
|
|
{
|
|
|
|
g_isPaletteUsed = false;
|
|
|
|
|
|
|
|
PALETTEENTRY usedPaletteEntries[256] = {};
|
|
|
|
CompatPrimarySurface::palette->lpVtbl->GetEntries(
|
|
|
|
CompatPrimarySurface::palette, 0, 0, 256, usedPaletteEntries);
|
|
|
|
|
|
|
|
if (0 != memcmp(usedPaletteEntries, g_usedPaletteEntries, sizeof(usedPaletteEntries)))
|
|
|
|
{
|
2016-02-27 19:26:50 +01:00
|
|
|
replaceDuplicateWhitePaletteEntries(usedPaletteEntries);
|
2016-02-21 12:03:31 +01:00
|
|
|
invalidate(nullptr);
|
2016-02-14 17:11:34 +01:00
|
|
|
}
|
|
|
|
}
|
2016-01-04 23:37:58 +01:00
|
|
|
}
|
|
|
|
}
|