2016-01-10 00:23:32 +01:00
|
|
|
#include <algorithm>
|
|
|
|
#include <unordered_map>
|
|
|
|
|
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
|
|
|
#include "CompatGdi.h"
|
2016-01-04 23:37:58 +01:00
|
|
|
#include "CompatGdiDc.h"
|
|
|
|
#include "CompatGdiDcCache.h"
|
2016-01-10 00:23:32 +01:00
|
|
|
#include "DDrawLog.h"
|
2016-03-28 17:14:47 +02:00
|
|
|
#include "Hook.h"
|
2016-01-04 23:37:58 +01:00
|
|
|
|
|
|
|
namespace
|
|
|
|
{
|
2016-01-10 00:23:32 +01:00
|
|
|
using CompatGdiDcCache::CachedDc;
|
|
|
|
|
|
|
|
struct CompatDc : CachedDc
|
|
|
|
{
|
|
|
|
CompatDc(const CachedDc& cachedDc = {}) : CachedDc(cachedDc) {}
|
|
|
|
DWORD refCount;
|
|
|
|
HDC origDc;
|
|
|
|
HGDIOBJ origFont;
|
|
|
|
HGDIOBJ origBrush;
|
|
|
|
HGDIOBJ origPen;
|
|
|
|
};
|
|
|
|
|
|
|
|
typedef std::unordered_map<HDC, CompatDc> CompatDcMap;
|
|
|
|
CompatDcMap g_origDcToCompatDc;
|
|
|
|
|
2016-01-04 23:37:58 +01:00
|
|
|
struct ExcludeClipRectsData
|
|
|
|
{
|
|
|
|
HDC compatDc;
|
|
|
|
POINT origin;
|
|
|
|
HWND rootWnd;
|
|
|
|
};
|
|
|
|
|
2016-01-10 00:23:32 +01:00
|
|
|
void copyDcAttributes(CompatDc& compatDc, HDC origDc, POINT& origin)
|
|
|
|
{
|
|
|
|
compatDc.origFont = SelectObject(compatDc.dc, GetCurrentObject(origDc, OBJ_FONT));
|
|
|
|
compatDc.origBrush = SelectObject(compatDc.dc, GetCurrentObject(origDc, OBJ_BRUSH));
|
|
|
|
compatDc.origPen = SelectObject(compatDc.dc, GetCurrentObject(origDc, OBJ_PEN));
|
|
|
|
|
2016-03-15 11:03:58 +01:00
|
|
|
if (GM_ADVANCED == GetGraphicsMode(origDc))
|
|
|
|
{
|
|
|
|
SetGraphicsMode(compatDc.dc, GM_ADVANCED);
|
|
|
|
XFORM transform = {};
|
|
|
|
GetWorldTransform(origDc, &transform);
|
|
|
|
SetWorldTransform(compatDc.dc, &transform);
|
|
|
|
}
|
|
|
|
else if (GM_COMPATIBLE != GetGraphicsMode(compatDc.dc))
|
|
|
|
{
|
|
|
|
ModifyWorldTransform(compatDc.dc, nullptr, MWT_IDENTITY);
|
|
|
|
SetGraphicsMode(compatDc.dc, GM_COMPATIBLE);
|
|
|
|
}
|
|
|
|
|
|
|
|
SetMapMode(compatDc.dc, GetMapMode(origDc));
|
|
|
|
|
|
|
|
POINT viewportOrg = {};
|
|
|
|
GetViewportOrgEx(origDc, &viewportOrg);
|
|
|
|
SetViewportOrgEx(compatDc.dc, viewportOrg.x + origin.x, viewportOrg.y + origin.y, nullptr);
|
|
|
|
SIZE viewportExt = {};
|
|
|
|
GetViewportExtEx(origDc, &viewportExt);
|
|
|
|
SetViewportExtEx(compatDc.dc, viewportExt.cx, viewportExt.cy, nullptr);
|
|
|
|
|
|
|
|
POINT windowOrg = {};
|
|
|
|
GetWindowOrgEx(origDc, &windowOrg);
|
|
|
|
SetWindowOrgEx(compatDc.dc, windowOrg.x, windowOrg.y, nullptr);
|
|
|
|
SIZE windowExt = {};
|
|
|
|
GetWindowExtEx(origDc, &windowExt);
|
|
|
|
SetWindowExtEx(compatDc.dc, windowExt.cx, windowExt.cy, nullptr);
|
|
|
|
|
2016-01-10 00:23:32 +01:00
|
|
|
SetArcDirection(compatDc.dc, GetArcDirection(origDc));
|
|
|
|
SetBkColor(compatDc.dc, GetBkColor(origDc));
|
|
|
|
SetBkMode(compatDc.dc, GetBkMode(origDc));
|
|
|
|
SetDCBrushColor(compatDc.dc, GetDCBrushColor(origDc));
|
|
|
|
SetDCPenColor(compatDc.dc, GetDCPenColor(origDc));
|
2016-03-15 11:03:58 +01:00
|
|
|
SetLayout(compatDc.dc, GetLayout(origDc));
|
2016-01-10 00:23:32 +01:00
|
|
|
SetPolyFillMode(compatDc.dc, GetPolyFillMode(origDc));
|
|
|
|
SetROP2(compatDc.dc, GetROP2(origDc));
|
|
|
|
SetStretchBltMode(compatDc.dc, GetStretchBltMode(origDc));
|
|
|
|
SetTextAlign(compatDc.dc, GetTextAlign(origDc));
|
|
|
|
SetTextCharacterExtra(compatDc.dc, GetTextCharacterExtra(origDc));
|
|
|
|
SetTextColor(compatDc.dc, GetTextColor(origDc));
|
|
|
|
|
|
|
|
POINT brushOrg = {};
|
|
|
|
GetBrushOrgEx(origDc, &brushOrg);
|
|
|
|
SetBrushOrgEx(compatDc.dc, brushOrg.x, brushOrg.y, nullptr);
|
|
|
|
|
|
|
|
POINT currentPos = {};
|
2016-03-15 11:03:58 +01:00
|
|
|
GetCurrentPositionEx(origDc, ¤tPos);
|
2016-01-10 00:23:32 +01:00
|
|
|
MoveToEx(compatDc.dc, currentPos.x, currentPos.y, nullptr);
|
|
|
|
}
|
|
|
|
|
2016-03-20 22:58:51 +01:00
|
|
|
BOOL CALLBACK excludeClipRectForOverlappingWindow(HWND hwnd, LPARAM lParam)
|
2016-01-04 23:37:58 +01:00
|
|
|
{
|
|
|
|
auto excludeClipRectsData = reinterpret_cast<ExcludeClipRectsData*>(lParam);
|
|
|
|
if (hwnd == excludeClipRectsData->rootWnd)
|
|
|
|
{
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
2016-01-10 00:23:32 +01:00
|
|
|
if (!IsWindowVisible(hwnd))
|
|
|
|
{
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
2016-01-04 23:37:58 +01:00
|
|
|
RECT rect = {};
|
|
|
|
GetWindowRect(hwnd, &rect);
|
|
|
|
OffsetRect(&rect, -excludeClipRectsData->origin.x, -excludeClipRectsData->origin.y);
|
|
|
|
ExcludeClipRect(excludeClipRectsData->compatDc, rect.left, rect.top, rect.right, rect.bottom);
|
|
|
|
return TRUE;
|
|
|
|
}
|
2016-01-10 00:23:32 +01:00
|
|
|
|
2016-03-20 22:58:51 +01:00
|
|
|
void excludeClipRectsForOverlappingWindows(
|
|
|
|
HWND hwnd, bool isMenuWindow, HDC compatDc, const POINT& origin)
|
2016-01-10 00:23:32 +01:00
|
|
|
{
|
2016-03-20 22:58:51 +01:00
|
|
|
ExcludeClipRectsData excludeClipRectsData = { compatDc, origin, GetAncestor(hwnd, GA_ROOT) };
|
|
|
|
if (!isMenuWindow)
|
|
|
|
{
|
|
|
|
EnumWindows(&excludeClipRectForOverlappingWindow,
|
|
|
|
reinterpret_cast<LPARAM>(&excludeClipRectsData));
|
|
|
|
}
|
2016-01-10 00:23:32 +01:00
|
|
|
|
2016-03-20 22:58:51 +01:00
|
|
|
HWND menuWindow = FindWindow(reinterpret_cast<LPCSTR>(0x8000), nullptr);
|
|
|
|
while (menuWindow && menuWindow != hwnd)
|
2016-01-10 00:23:32 +01:00
|
|
|
{
|
2016-03-20 22:58:51 +01:00
|
|
|
excludeClipRectForOverlappingWindow(
|
|
|
|
menuWindow, reinterpret_cast<LPARAM>(&excludeClipRectsData));
|
|
|
|
menuWindow = FindWindowEx(nullptr, menuWindow, reinterpret_cast<LPCSTR>(0x8000), nullptr);
|
2016-01-10 00:23:32 +01:00
|
|
|
}
|
2016-03-20 22:58:51 +01:00
|
|
|
}
|
|
|
|
|
2016-03-28 17:14:47 +02:00
|
|
|
void setClippingRegion(HDC compatDc, HDC origDc, HWND hwnd, bool isMenuWindow, const POINT& origin)
|
2016-03-20 22:58:51 +01:00
|
|
|
{
|
|
|
|
if (isMenuWindow)
|
|
|
|
{
|
|
|
|
RECT windowRect = {};
|
|
|
|
GetWindowRect(hwnd, &windowRect);
|
2016-01-10 00:23:32 +01:00
|
|
|
|
2016-03-20 22:58:51 +01:00
|
|
|
HRGN windowRgn = CreateRectRgnIndirect(&windowRect);
|
|
|
|
SelectClipRgn(compatDc, windowRgn);
|
|
|
|
DeleteObject(windowRgn);
|
|
|
|
}
|
|
|
|
else
|
2016-01-10 00:23:32 +01:00
|
|
|
{
|
2016-03-20 22:58:51 +01:00
|
|
|
HRGN clipRgn = CreateRectRgn(0, 0, 0, 0);
|
|
|
|
const bool isEmptyClipRgn = 1 != GetRandomRgn(origDc, clipRgn, SYSRGN);
|
|
|
|
SelectClipRgn(compatDc, isEmptyClipRgn ? nullptr : clipRgn);
|
|
|
|
DeleteObject(clipRgn);
|
|
|
|
|
|
|
|
HRGN origClipRgn = CreateRectRgn(0, 0, 0, 0);
|
|
|
|
if (1 == GetClipRgn(origDc, origClipRgn))
|
2016-01-10 00:23:32 +01:00
|
|
|
{
|
2016-03-20 22:58:51 +01:00
|
|
|
OffsetRgn(origClipRgn, origin.x, origin.y);
|
|
|
|
ExtSelectClipRgn(compatDc, origClipRgn, RGN_AND);
|
2016-01-10 00:23:32 +01:00
|
|
|
}
|
2016-03-20 22:58:51 +01:00
|
|
|
DeleteObject(origClipRgn);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (hwnd)
|
|
|
|
{
|
|
|
|
excludeClipRectsForOverlappingWindows(hwnd, isMenuWindow, compatDc, origin);
|
2016-01-10 00:23:32 +01:00
|
|
|
}
|
|
|
|
}
|
2016-01-04 23:37:58 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
namespace CompatGdiDc
|
|
|
|
{
|
2016-03-28 17:14:47 +02:00
|
|
|
HDC getDc(HDC origDc, bool isMenuPaintDc)
|
2016-01-04 23:37:58 +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 (!origDc || OBJ_DC != GetObjectType(origDc) || DT_RASDISPLAY != GetDeviceCaps(origDc, TECHNOLOGY))
|
|
|
|
{
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
CompatGdi::GdiScopedThreadLock gdiLock;
|
|
|
|
|
2016-01-10 00:23:32 +01:00
|
|
|
auto it = g_origDcToCompatDc.find(origDc);
|
|
|
|
if (it != g_origDcToCompatDc.end())
|
2016-01-04 23:37:58 +01:00
|
|
|
{
|
2016-01-10 00:23:32 +01:00
|
|
|
++it->second.refCount;
|
|
|
|
return it->second.dc;
|
2016-01-04 23:37:58 +01:00
|
|
|
}
|
|
|
|
|
2016-03-28 17:14:47 +02:00
|
|
|
const HWND hwnd = CALL_ORIG_FUNC(WindowFromDC)(origDc);
|
|
|
|
const bool isMenuWindow = hwnd && 0x8000 == GetClassLongPtr(hwnd, GCW_ATOM);
|
|
|
|
if (isMenuWindow && !isMenuPaintDc)
|
|
|
|
{
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2016-01-10 00:23:32 +01:00
|
|
|
CompatDc compatDc(CompatGdiDcCache::getDc());
|
|
|
|
if (!compatDc.dc)
|
2016-01-04 23:37:58 +01:00
|
|
|
{
|
2016-01-10 00:23:32 +01:00
|
|
|
return nullptr;
|
2016-01-04 23:37:58 +01:00
|
|
|
}
|
|
|
|
|
2016-01-10 00:23:32 +01:00
|
|
|
POINT origin = {};
|
|
|
|
GetDCOrgEx(origDc, &origin);
|
|
|
|
|
|
|
|
copyDcAttributes(compatDc, origDc, origin);
|
2016-03-28 17:14:47 +02:00
|
|
|
setClippingRegion(compatDc.dc, origDc, hwnd, isMenuWindow, origin);
|
2016-01-10 00:23:32 +01:00
|
|
|
|
|
|
|
compatDc.refCount = 1;
|
|
|
|
compatDc.origDc = origDc;
|
|
|
|
g_origDcToCompatDc.insert(CompatDcMap::value_type(origDc, compatDc));
|
|
|
|
|
|
|
|
return compatDc.dc;
|
2016-01-04 23:37:58 +01:00
|
|
|
}
|
|
|
|
|
2016-03-19 16:16:13 +01:00
|
|
|
HDC getOrigDc(HDC dc)
|
|
|
|
{
|
|
|
|
const auto it = std::find_if(g_origDcToCompatDc.begin(), g_origDcToCompatDc.end(),
|
|
|
|
[dc](const CompatDcMap::value_type& compatDc) { return compatDc.second.dc == dc; });
|
|
|
|
return it != g_origDcToCompatDc.end() ? it->first : dc;
|
|
|
|
}
|
|
|
|
|
2016-01-10 00:23:32 +01:00
|
|
|
void releaseDc(HDC origDc)
|
2016-01-04 23:37:58 +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
|
|
|
CompatGdi::GdiScopedThreadLock gdiLock;
|
|
|
|
|
2016-01-10 00:23:32 +01:00
|
|
|
auto it = g_origDcToCompatDc.find(origDc);
|
|
|
|
if (it == g_origDcToCompatDc.end())
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
CompatDc& compatDc = it->second;
|
|
|
|
--compatDc.refCount;
|
|
|
|
if (0 == compatDc.refCount)
|
|
|
|
{
|
|
|
|
SelectObject(compatDc.dc, compatDc.origFont);
|
|
|
|
SelectObject(compatDc.dc, compatDc.origBrush);
|
|
|
|
SelectObject(compatDc.dc, compatDc.origPen);
|
|
|
|
CompatGdiDcCache::releaseDc(compatDc);
|
|
|
|
g_origDcToCompatDc.erase(origDc);
|
|
|
|
}
|
2016-01-04 23:37:58 +01:00
|
|
|
}
|
|
|
|
}
|