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

Revamping GDI interworking - Part 2

Extended GDI redirection to most of the GDI rendering methods.
Readded handling of window messages and extended it to support scrolling.
Readded manual drawing of the caret.
Simplified the DC cache.
This commit is contained in:
narzoul 2016-01-10 00:23:32 +01:00
parent 5a30b072ba
commit 1999d1c56e
16 changed files with 625 additions and 320 deletions

View File

@ -66,10 +66,6 @@ HRESULT STDMETHODCALLTYPE CompatDirectDraw<TDirectDraw>::CreateSurface(
if (SUCCEEDED(result))
{
CompatDirectDrawSurface<TSurface>::fixSurfacePtrs(**lplpDDSurface);
if (isPrimary)
{
CompatDirectDrawSurface<TSurface>::updateSurfaceParams();
}
}
return result;

View File

@ -477,7 +477,6 @@ HRESULT STDMETHODCALLTYPE CompatDirectDrawSurface<TSurface>::Flip(
if (This == s_compatPrimarySurface && SUCCEEDED(result))
{
result = RealPrimarySurface::flip(dwFlags);
updateSurfaceParams();
}
return result;
}
@ -603,8 +602,7 @@ HRESULT STDMETHODCALLTYPE CompatDirectDrawSurface<TSurface>::Restore(TSurface* T
result = RealPrimarySurface::restore();
if (wasLost)
{
CompatGdi::releaseSurfaceMemory();
updateSurfaceParams();
CompatGdi::invalidate();
}
}
}
@ -682,20 +680,6 @@ void CompatDirectDrawSurface<TSurface>::restorePrimaryCaps(TDdsCaps& caps)
caps.dwCaps |= DDSCAPS_PRIMARYSURFACE | DDSCAPS_VISIBLE;
}
template <typename TSurface>
void CompatDirectDrawSurface<TSurface>::updateSurfaceParams()
{
TSurfaceDesc desc = {};
desc.dwSize = sizeof(desc);
g_lockingPrimary = true;
if (SUCCEEDED(s_origVtable.Lock(s_compatPrimarySurface, nullptr, &desc, DDLOCK_WAIT, nullptr)))
{
s_origVtable.Unlock(s_compatPrimarySurface, nullptr);
CompatGdi::setSurfaceMemory(desc.lpSurface, desc.lPitch);
}
g_lockingPrimary = false;
}
template <typename TSurface>
TSurface* CompatDirectDrawSurface<TSurface>::s_compatPrimarySurface = nullptr;

View File

@ -23,7 +23,6 @@ public:
static void fixSurfacePtrs(TSurface& surface);
static void initPrimarySurfacePtr(const GUID& guid, IUnknown& surface);
static void resetPrimarySurfacePtr();
static void updateSurfaceParams();
static HRESULT STDMETHODCALLTYPE Blt(
TSurface* This,

View File

@ -1,14 +1,72 @@
#include "CompatDirectDrawSurface.h"
#include "CompatGdi.h"
#include "CompatGdiCaret.h"
#include "CompatGdiDcCache.h"
#include "CompatGdiFunctions.h"
#include "CompatGdiWinProc.h"
#include "CompatPrimarySurface.h"
#include "DDrawProcs.h"
#include "RealPrimarySurface.h"
namespace
{
CRITICAL_SECTION g_gdiCriticalSection;
DWORD g_renderingDepth = 0;
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;
}
BOOL CALLBACK invalidateWindow(HWND hwnd, LPARAM /*lParam*/)
{
DWORD processId = 0;
GetWindowThreadProcessId(hwnd, &processId);
if (processId == GetCurrentProcessId())
{
RedrawWindow(hwnd, nullptr, nullptr, RDW_ERASE | RDW_FRAME | RDW_INVALIDATE | RDW_ALLCHILDREN);
}
return TRUE;
}
}
namespace CompatGdi
{
CRITICAL_SECTION g_gdiCriticalSection;
std::unordered_map<void*, const char*> g_funcNames;
GdiScopedThreadLock::GdiScopedThreadLock()
{
EnterCriticalSection(&g_gdiCriticalSection);
@ -19,29 +77,89 @@ namespace CompatGdi
LeaveCriticalSection(&g_gdiCriticalSection);
}
bool beginGdiRendering()
{
if (!RealPrimarySurface::isFullScreen())
{
return false;
}
Compat::origProcs.AcquireDDThreadLock();
EnterCriticalSection(&g_gdiCriticalSection);
if (0 == g_renderingDepth)
{
DDSURFACEDESC2 desc = {};
desc.dwSize = sizeof(desc);
if (FAILED(CompatDirectDrawSurface<IDirectDrawSurface7>::s_origVtable.Lock(
CompatPrimarySurface::surface, nullptr, &desc, DDLOCK_WAIT, nullptr)))
{
LeaveCriticalSection(&g_gdiCriticalSection);
Compat::origProcs.ReleaseDDThreadLock();
return false;
}
CompatGdiDcCache::setSurfaceMemory(desc.lpSurface, desc.lPitch);
}
++g_renderingDepth;
return true;
}
void endGdiRendering()
{
--g_renderingDepth;
if (0 == g_renderingDepth)
{
GdiFlush();
CompatDirectDrawSurface<IDirectDrawSurface7>::s_origVtable.Unlock(
CompatPrimarySurface::surface, nullptr);
RealPrimarySurface::update();
}
LeaveCriticalSection(&g_gdiCriticalSection);
Compat::origProcs.ReleaseDDThreadLock();
}
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;
}
}
void installHooks()
{
InitializeCriticalSection(&g_gdiCriticalSection);
if (CompatGdiDcCache::init())
{
CompatGdiFunctions::hookGdiFunctions();
CompatGdiFunctions::installHooks();
CompatGdiWinProc::installHooks();
CompatGdiCaret::installHooks();
}
}
void releaseSurfaceMemory()
void invalidate()
{
GdiScopedThreadLock gdiLock;
CompatGdiDcCache::release();
EnumWindows(&invalidateWindow, 0);
}
void setSurfaceMemory(void* surfaceMemory, int pitch)
void updatePalette()
{
GdiScopedThreadLock gdiLock;
const bool wasReleased = CompatGdiDcCache::isReleased();
CompatGdiDcCache::setSurfaceMemory(surfaceMemory, pitch);
if (wasReleased)
{
InvalidateRect(nullptr, nullptr, TRUE);
}
CompatGdiDcCache::clear();
}
}

View File

@ -1,5 +1,15 @@
#pragma once
#define WIN32_LEAN_AND_MEAN
#include <unordered_map>
#include <Windows.h>
#include "DDrawLog.h"
#define CALL_ORIG_GDI(func) CompatGdi::getOrigFuncPtr<decltype(&func), &func>()
namespace CompatGdi
{
class GdiScopedThreadLock
@ -9,7 +19,29 @@ namespace CompatGdi
~GdiScopedThreadLock();
};
extern CRITICAL_SECTION g_gdiCriticalSection;
extern std::unordered_map<void*, const char*> g_funcNames;
bool beginGdiRendering();
void endGdiRendering();
template <typename OrigFuncPtr, OrigFuncPtr origFunc>
OrigFuncPtr& getOrigFuncPtr()
{
static OrigFuncPtr origFuncPtr = origFunc;
return origFuncPtr;
}
void hookGdiFunction(const char* moduleName, const char* funcName, void*& origFuncPtr, void* newFuncPtr);
template <typename OrigFuncPtr, OrigFuncPtr origFunc>
void hookGdiFunction(const char* moduleName, const char* funcName, OrigFuncPtr newFuncPtr)
{
hookGdiFunction(moduleName, funcName,
reinterpret_cast<void*&>(getOrigFuncPtr<OrigFuncPtr, origFunc>()), newFuncPtr);
}
void installHooks();
void releaseSurfaceMemory();
void setSurfaceMemory(void* surfaceMemory, int pitch);
void invalidate();
void updatePalette();
};

View File

@ -0,0 +1,160 @@
#define CINTERFACE
#define WIN32_LEAN_AND_MEAN
#include <oleacc.h>
#include <Windows.h>
#include <detours.h>
#include "CompatGdi.h"
#include "CompatGdiCaret.h"
#include "CompatGdiDc.h"
namespace
{
struct CaretData
{
HWND hwnd;
long left;
long top;
long width;
long height;
bool isDrawn;
};
CaretData g_caret = {};
void drawCaret()
{
if (CompatGdi::beginGdiRendering())
{
HDC dc = GetDC(g_caret.hwnd);
HDC compatDc = CompatGdiDc::getDc(dc);
PatBlt(compatDc, g_caret.left, g_caret.top, g_caret.width, g_caret.height, PATINVERT);
CompatGdiDc::releaseDc(dc);
ReleaseDC(g_caret.hwnd, dc);
CompatGdi::endGdiRendering();
}
}
LONG getCaretState(IAccessible* accessible)
{
VARIANT varChild = {};
varChild.vt = VT_I4;
varChild.lVal = CHILDID_SELF;
VARIANT varState = {};
accessible->lpVtbl->get_accState(accessible, varChild, &varState);
return varState.lVal;
}
void CALLBACK caretDestroyEvent(
HWINEVENTHOOK /*hWinEventHook*/,
DWORD /*event*/,
HWND hwnd,
LONG idObject,
LONG idChild,
DWORD /*dwEventThread*/,
DWORD /*dwmsEventTime*/)
{
CompatGdi::GdiScopedThreadLock gdiLock;
if (OBJID_CARET != idObject || !g_caret.isDrawn || g_caret.hwnd != hwnd)
{
return;
}
IAccessible* accessible = nullptr;
VARIANT varChild = {};
AccessibleObjectFromEvent(hwnd, idObject, idChild, &accessible, &varChild);
if (accessible)
{
if (STATE_SYSTEM_INVISIBLE == getCaretState(accessible))
{
drawCaret();
g_caret.isDrawn = false;
}
accessible->lpVtbl->Release(accessible);
}
}
BOOL WINAPI createCaret(HWND hWnd, HBITMAP hBitmap, int nWidth, int nHeight)
{
BOOL result = CALL_ORIG_GDI(CreateCaret)(hWnd, hBitmap, nWidth, nHeight);
if (result)
{
CompatGdi::GdiScopedThreadLock gdiLock;
if (g_caret.isDrawn)
{
drawCaret();
g_caret.isDrawn = false;
}
g_caret.width = nWidth ? nWidth : GetSystemMetrics(SM_CXBORDER);
g_caret.height = nHeight ? nHeight : GetSystemMetrics(SM_CYBORDER);
}
return result;
}
BOOL WINAPI showCaret(HWND hWnd)
{
if (!CALL_ORIG_GDI(ShowCaret)(hWnd))
{
return FALSE;
}
CompatGdi::GdiScopedThreadLock gdiLock;
if (!g_caret.isDrawn)
{
IAccessible* accessible = nullptr;
AccessibleObjectFromWindow(hWnd, static_cast<DWORD>(OBJID_CARET), IID_IAccessible,
reinterpret_cast<void**>(&accessible));
if (accessible)
{
if (0 == getCaretState(accessible))
{
POINT caretPos = {};
GetCaretPos(&caretPos);
g_caret.left = caretPos.x;
g_caret.top = caretPos.y;
g_caret.hwnd = hWnd;
drawCaret();
g_caret.isDrawn = true;
}
accessible->lpVtbl->Release(accessible);
}
}
return TRUE;
}
BOOL WINAPI hideCaret(HWND hWnd)
{
BOOL result = CALL_ORIG_GDI(HideCaret)(hWnd);
if (result)
{
CompatGdi::GdiScopedThreadLock gdiLock;
if (g_caret.isDrawn)
{
drawCaret();
g_caret.isDrawn = false;
}
}
return result;
}
}
#define HOOK_GDI_FUNCTION(module, func, newFunc) \
CompatGdi::hookGdiFunction<decltype(&func), &func>(#module, #func, &newFunc);
namespace CompatGdiCaret
{
void installHooks()
{
DetourTransactionBegin();
HOOK_GDI_FUNCTION(user32, CreateCaret, createCaret);
HOOK_GDI_FUNCTION(user32, ShowCaret, showCaret);
HOOK_GDI_FUNCTION(user32, HideCaret, hideCaret);
DetourTransactionCommit();
const DWORD threadId = GetCurrentThreadId();
SetWinEventHook(EVENT_OBJECT_DESTROY, EVENT_OBJECT_DESTROY,
nullptr, &caretDestroyEvent, 0, threadId, WINEVENT_OUTOFCONTEXT);
}
}

View File

@ -1,9 +1,27 @@
#include <algorithm>
#include <unordered_map>
#include "CompatGdiDc.h"
#include "CompatGdiDcCache.h"
#include "RealPrimarySurface.h"
#include "DDrawLog.h"
namespace
{
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;
struct ExcludeClipRectsData
{
HDC compatDc;
@ -11,6 +29,35 @@ namespace
HWND rootWnd;
};
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));
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));
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));
SetWindowOrgEx(compatDc.dc, -origin.x, -origin.y, nullptr);
POINT brushOrg = {};
GetBrushOrgEx(origDc, &brushOrg);
SetBrushOrgEx(compatDc.dc, brushOrg.x, brushOrg.y, nullptr);
POINT currentPos = {};
MoveToEx(origDc, 0, 0, &currentPos);
MoveToEx(origDc, currentPos.x, currentPos.y, nullptr);
MoveToEx(compatDc.dc, currentPos.x, currentPos.y, nullptr);
}
BOOL CALLBACK excludeClipRectsForOverlappingWindows(HWND hwnd, LPARAM lParam)
{
auto excludeClipRectsData = reinterpret_cast<ExcludeClipRectsData*>(lParam);
@ -19,56 +66,102 @@ namespace
return FALSE;
}
if (!IsWindowVisible(hwnd))
{
return TRUE;
}
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;
}
void setClippingRegion(HDC compatDc, HDC origDc, POINT& origin)
{
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))
{
OffsetRgn(origClipRgn, origin.x, origin.y);
ExtSelectClipRgn(compatDc, origClipRgn, RGN_AND);
}
DeleteObject(origClipRgn);
if (!isEmptyClipRgn)
{
HWND hwnd = WindowFromDC(origDc);
if (hwnd)
{
ExcludeClipRectsData excludeClipRectsData = { compatDc, origin, GetAncestor(hwnd, GA_ROOT) };
EnumThreadWindows(GetCurrentThreadId(), &excludeClipRectsForOverlappingWindows,
reinterpret_cast<LPARAM>(&excludeClipRectsData));
RECT windowRect = {};
GetWindowRect(hwnd, &windowRect);
}
}
}
}
namespace CompatGdiDc
{
CachedDc getDc(HDC origDc)
HDC getDc(HDC origDc)
{
CachedDc cachedDc = {};
if (!origDc || !RealPrimarySurface::isFullScreen() ||
OBJ_DC != GetObjectType(origDc) ||
DT_RASDISPLAY != GetDeviceCaps(origDc, TECHNOLOGY))
if (!origDc || OBJ_DC != GetObjectType(origDc) || DT_RASDISPLAY != GetDeviceCaps(origDc, TECHNOLOGY) ||
g_origDcToCompatDc.end() != std::find_if(g_origDcToCompatDc.begin(), g_origDcToCompatDc.end(),
[=](const CompatDcMap::value_type& compatDc) { return compatDc.second.dc == origDc; }))
{
return cachedDc;
return nullptr;
}
cachedDc = CompatGdiDcCache::getDc();
if (!cachedDc.dc)
auto it = g_origDcToCompatDc.find(origDc);
if (it != g_origDcToCompatDc.end())
{
return cachedDc;
++it->second.refCount;
return it->second.dc;
}
HWND hwnd = WindowFromDC(origDc);
if (hwnd)
CompatDc compatDc(CompatGdiDcCache::getDc());
if (!compatDc.dc)
{
POINT origin = {};
GetDCOrgEx(origDc, &origin);
SetWindowOrgEx(cachedDc.dc, -origin.x, -origin.y, nullptr);
HRGN clipRgn = CreateRectRgn(0, 0, 0, 0);
GetRandomRgn(origDc, clipRgn, SYSRGN);
SelectClipRgn(cachedDc.dc, clipRgn);
RECT r = {};
GetRgnBox(clipRgn, &r);
DeleteObject(clipRgn);
ExcludeClipRectsData excludeClipRectsData = { cachedDc.dc, origin, GetAncestor(hwnd, GA_ROOT) };
EnumThreadWindows(GetCurrentThreadId(), &excludeClipRectsForOverlappingWindows,
reinterpret_cast<LPARAM>(&excludeClipRectsData));
return nullptr;
}
return cachedDc;
POINT origin = {};
GetDCOrgEx(origDc, &origin);
copyDcAttributes(compatDc, origDc, origin);
setClippingRegion(compatDc.dc, origDc, origin);
compatDc.refCount = 1;
compatDc.origDc = origDc;
g_origDcToCompatDc.insert(CompatDcMap::value_type(origDc, compatDc));
return compatDc.dc;
}
void releaseDc(const CachedDc& cachedDc)
void releaseDc(HDC origDc)
{
CompatGdiDcCache::releaseDc(cachedDc);
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);
}
}
}

View File

@ -4,12 +4,8 @@
#include <Windows.h>
#include "CompatGdiDcCache.h"
namespace CompatGdiDc
{
using CompatGdiDcCache::CachedDc;
CachedDc getDc(HDC origDc);
void releaseDc(const CachedDc& cachedDc);
HDC getDc(HDC origDc);
void releaseDc(HDC origDc);
}

View File

@ -1,4 +1,3 @@
#include <map>
#include <vector>
#include "CompatDirectDraw.h"
@ -8,41 +7,18 @@
#include "Config.h"
#include "DDrawLog.h"
#include "DDrawProcs.h"
#include "DDrawScopedThreadLock.h"
namespace CompatGdiDcCache
{
bool operator<(const SurfaceMemoryDesc& desc1, const SurfaceMemoryDesc& desc2)
{
return desc1.surfaceMemory < desc2.surfaceMemory ||
(desc1.surfaceMemory == desc2.surfaceMemory && desc1.pitch < desc2.pitch);
}
}
namespace
{
using CompatGdiDcCache::SurfaceMemoryDesc;
using CompatGdiDcCache::CachedDc;
std::map<SurfaceMemoryDesc, std::vector<CachedDc>> g_caches;
std::vector<CachedDc>* g_currentCache = nullptr;
std::vector<CachedDc> g_cache;
IDirectDraw7* g_directDraw = nullptr;
void* g_surfaceMemory = nullptr;
LONG g_pitch = 0;
IDirectDrawSurface7* createGdiSurface();
void releaseCachedDc(CachedDc cachedDc);
void releaseCache(std::vector<CachedDc>& cache);
void clearAllCaches()
{
for (auto& cache : g_caches)
{
releaseCache(cache.second);
}
g_caches.clear();
}
IDirectDraw7* createDirectDraw()
{
@ -86,17 +62,13 @@ namespace
// Release DD critical section acquired by IDirectDrawSurface7::GetDC to avoid deadlocks
Compat::origProcs.ReleaseDDThreadLock();
cachedDc.surfaceMemoryDesc.surfaceMemory = g_surfaceMemory;
cachedDc.surfaceMemoryDesc.pitch = g_pitch;
cachedDc.dc = dc;
cachedDc.surface = surface;
cachedDc.dc = dc;
return cachedDc;
}
IDirectDrawSurface7* createGdiSurface()
{
Compat::DDrawScopedThreadLock ddLock;
DDSURFACEDESC2 desc = {};
desc.dwSize = sizeof(desc);
desc.dwFlags = DDSD_WIDTH | DDSD_HEIGHT | DDSD_PIXELFORMAT | DDSD_CAPS | DDSD_PITCH | DDSD_LPSURFACE;
@ -125,66 +97,49 @@ namespace
return surface;
}
void fillCurrentCache()
{
for (DWORD i = 0; i < Config::gdiDcCacheSize; ++i)
{
CachedDc cachedDc = createCachedDc();
if (!cachedDc.dc)
{
return;
}
g_currentCache->push_back(cachedDc);
}
}
void releaseCachedDc(CachedDc cachedDc)
{
// Reacquire DD critical section that was temporarily released after IDirectDrawSurface7::GetDC
Compat::origProcs.AcquireDDThreadLock();
if (FAILED(CompatDirectDrawSurface<IDirectDrawSurface7>::s_origVtable.ReleaseDC(
cachedDc.surface, cachedDc.dc)))
HRESULT result = CompatDirectDrawSurface<IDirectDrawSurface7>::s_origVtable.ReleaseDC(
cachedDc.surface, cachedDc.dc);
if (FAILED(result))
{
LOG_ONCE("Failed to release a cached DC");
Compat::origProcs.ReleaseDDThreadLock();
LOG_ONCE("Failed to release a cached DC: " << result);
}
CompatDirectDrawSurface<IDirectDrawSurface7>::s_origVtable.Release(cachedDc.surface);
}
void releaseCache(std::vector<CachedDc>& cache)
{
for (auto& cachedDc : cache)
{
releaseCachedDc(cachedDc);
}
}
}
namespace CompatGdiDcCache
{
void clear()
{
for (auto& cachedDc : g_cache)
{
releaseCachedDc(cachedDc);
}
g_cache.clear();
}
CachedDc getDc()
{
CachedDc cachedDc = {};
if (!g_currentCache)
if (!g_surfaceMemory)
{
return cachedDc;
}
if (g_currentCache->empty())
if (g_cache.empty())
{
LOG_ONCE("Warning: GDI DC cache size is insufficient");
cachedDc = createCachedDc();
if (!cachedDc.dc)
{
return cachedDc;
}
}
else
{
cachedDc = g_currentCache->back();
g_currentCache->pop_back();
cachedDc = g_cache.back();
g_cache.pop_back();
}
return cachedDc;
@ -196,46 +151,20 @@ namespace CompatGdiDcCache
return nullptr != g_directDraw;
}
bool isReleased()
{
return g_caches.empty();
}
void release()
{
if (g_currentCache)
{
g_currentCache = nullptr;
clearAllCaches();
}
}
void releaseDc(const CachedDc& cachedDc)
{
g_caches[cachedDc.surfaceMemoryDesc].push_back(cachedDc);
g_cache.push_back(cachedDc);
}
void setSurfaceMemory(void* surfaceMemory, LONG pitch)
{
g_surfaceMemory = surfaceMemory;
g_pitch = pitch;
if (!surfaceMemory)
if (g_surfaceMemory == surfaceMemory && g_pitch == pitch)
{
g_currentCache = nullptr;
return;
}
SurfaceMemoryDesc surfaceMemoryDesc = { surfaceMemory, pitch };
auto it = g_caches.find(surfaceMemoryDesc);
if (it == g_caches.end())
{
g_currentCache = &g_caches[surfaceMemoryDesc];
fillCurrentCache();
}
else
{
g_currentCache = &it->second;
}
g_surfaceMemory = surfaceMemory;
g_pitch = pitch;
clear();
}
}

View File

@ -8,23 +8,15 @@
namespace CompatGdiDcCache
{
struct SurfaceMemoryDesc
{
void* surfaceMemory;
LONG pitch;
};
struct CachedDc
{
SurfaceMemoryDesc surfaceMemoryDesc;
IDirectDrawSurface7* surface;
HDC dc;
};
void clear();
CachedDc getDc();
bool init();
bool isReleased();
void release();
void releaseDc(const CachedDc& cachedDc);
void setSurfaceMemory(void* surfaceMemory, LONG pitch);
}

View File

@ -1,42 +1,30 @@
#include <algorithm>
#include <unordered_map>
#include <vector>
#include "CompatDirectDrawSurface.h"
#include "CompatGdi.h"
#include "CompatGdiDc.h"
#include "CompatGdiFunctions.h"
#include "CompatPrimarySurface.h"
#include "DDrawLog.h"
#include "DDrawScopedThreadLock.h"
#include "RealPrimarySurface.h"
#include <detours.h>
namespace
{
using CompatGdiDc::CachedDc;
struct CompatDc : CachedDc
{
CompatDc(const CachedDc& cachedDc) : CachedDc(cachedDc) {}
HGDIOBJ origFont;
HGDIOBJ origBrush;
HGDIOBJ origPen;
};
std::unordered_map<void*, const char*> g_funcNames;
std::vector<CompatDc> g_usedCompatDcs;
DWORD* g_usedCompatDcCount = nullptr;
template <typename Result, typename... Params>
using FuncPtr = Result(WINAPI *)(Params...);
template <typename OrigFuncPtr, OrigFuncPtr origFunc>
OrigFuncPtr& getOrigFuncPtr()
bool hasDisplayDcArg(HDC dc)
{
static OrigFuncPtr origFuncPtr = origFunc;
return origFuncPtr;
return dc && OBJ_DC == GetObjectType(dc) && DT_RASDISPLAY == GetDeviceCaps(dc, TECHNOLOGY);
}
template <typename T>
bool hasDisplayDcArg(T)
{
return false;
}
template <typename T, typename... Params>
bool hasDisplayDcArg(T t, Params... params)
{
return hasDisplayDcArg(t) || hasDisplayDcArg(params...);
}
template <typename T>
@ -47,140 +35,147 @@ namespace
HDC replaceDc(HDC dc)
{
auto it = std::find_if(g_usedCompatDcs.begin(), g_usedCompatDcs.end(),
[dc](const CompatDc& compatDc) { return compatDc.dc == dc; });
if (it != g_usedCompatDcs.end())
{
return it->dc;
}
HDC compatDc = CompatGdiDc::getDc(dc);
return compatDc ? compatDc : dc;
}
CompatDc compatDc = CompatGdiDc::getDc(dc);
if (!compatDc.dc)
{
return dc;
}
template <typename T>
void releaseDc(T) {}
compatDc.origFont = SelectObject(compatDc.dc, GetCurrentObject(dc, OBJ_FONT));
compatDc.origBrush = SelectObject(compatDc.dc, GetCurrentObject(dc, OBJ_BRUSH));
compatDc.origPen = SelectObject(compatDc.dc, GetCurrentObject(dc, OBJ_PEN));
SetTextColor(compatDc.dc, GetTextColor(dc));
SetBkColor(compatDc.dc, GetBkColor(dc));
SetBkMode(compatDc.dc, GetBkMode(dc));
void releaseDc(HDC dc)
{
CompatGdiDc::releaseDc(dc);
}
g_usedCompatDcs.push_back(compatDc);
++*g_usedCompatDcCount;
return compatDc.dc;
template <typename T, typename... Params>
void releaseDc(T t, Params... params)
{
releaseDc(params...);
releaseDc(t);
}
template <typename OrigFuncPtr, OrigFuncPtr origFunc, typename Result, typename... Params>
Result WINAPI compatGdiFunc(Params... params)
{
CompatGdi::GdiScopedThreadLock gdiLock;
Compat::DDrawScopedThreadLock ddLock;
DWORD usedCompatDcCount = 0;
g_usedCompatDcCount = &usedCompatDcCount;
DDSURFACEDESC2 desc = {};
desc.dwSize = sizeof(desc);
if (FAILED(CompatDirectDrawSurface<IDirectDrawSurface7>::s_origVtable.Lock(
CompatPrimarySurface::surface, nullptr, &desc, DDLOCK_WAIT, nullptr)))
if (!hasDisplayDcArg(params...) || !CompatGdi::beginGdiRendering())
{
return getOrigFuncPtr<OrigFuncPtr, origFunc>()(params...);
return CompatGdi::getOrigFuncPtr<OrigFuncPtr, origFunc>()(params...);
}
Result result = getOrigFuncPtr<OrigFuncPtr, origFunc>()(replaceDc(params)...);
GdiFlush();
#ifdef _DEBUG
Compat::LogEnter(CompatGdi::g_funcNames[origFunc], params...);
#endif
CompatDirectDrawSurface<IDirectDrawSurface7>::s_origVtable.Unlock(
CompatPrimarySurface::surface, nullptr);
Result result = CompatGdi::getOrigFuncPtr<OrigFuncPtr, origFunc>()(replaceDc(params)...);
releaseDc(params...);
CompatGdi::endGdiRendering();
if (0 != usedCompatDcCount)
{
RealPrimarySurface::update();
}
for (DWORD i = 0; i < usedCompatDcCount; ++i)
{
CompatDc& compatDc = g_usedCompatDcs.back();
SelectObject(compatDc.dc, compatDc.origFont);
SelectObject(compatDc.dc, compatDc.origBrush);
SelectObject(compatDc.dc, compatDc.origPen);
CompatGdiDc::releaseDc(compatDc);
g_usedCompatDcs.pop_back();
}
#ifdef _DEBUG
Compat::LogLeave(CompatGdi::g_funcNames[origFunc], params...) << result;
#endif
return result;
}
template <typename OrigFuncPtr, OrigFuncPtr origFunc, typename Result, typename... Params>
OrigFuncPtr getCompatGdiFuncPtr(FuncPtr<Result, Params...>&)
OrigFuncPtr getCompatGdiFuncPtr(FuncPtr<Result, Params...>)
{
return &compatGdiFunc<OrigFuncPtr, origFunc, Result, Params...>;
}
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;
}
template <typename OrigFuncPtr, OrigFuncPtr origFunc>
void hookGdiFunction(const char* moduleName, const char* funcName)
{
OrigFuncPtr& origFuncPtr = getOrigFuncPtr<OrigFuncPtr, origFunc>();
origFuncPtr = reinterpret_cast<OrigFuncPtr>(getProcAddress(GetModuleHandle(moduleName), funcName));
OrigFuncPtr newFuncPtr = getCompatGdiFuncPtr<OrigFuncPtr, origFunc>(origFuncPtr);
DetourAttach(reinterpret_cast<void**>(&origFuncPtr), newFuncPtr);
g_funcNames[origFunc] = funcName;
}
}
void CompatGdiFunctions::hookGdiFunctions()
{
#define HOOK_GDI_FUNCTION(module, func) hookGdiFunction<decltype(&func), &func>(#module, #func);
#define HOOK_GDI_FUNCTION(module, func) \
CompatGdi::hookGdiFunction<decltype(&func), &func>( \
#module, #func, getCompatGdiFuncPtr<decltype(&func), &func>(&func));
#define HOOK_GDI_TEXT_FUNCTION(module, func) \
HOOK_GDI_FUNCTION(module, func##A); \
HOOK_GDI_FUNCTION(module, func##W)
DetourTransactionBegin();
HOOK_GDI_FUNCTION(gdi32, BitBlt);
DetourTransactionCommit();
namespace CompatGdiFunctions
{
void installHooks()
{
DetourTransactionBegin();
#undef HOOK_GDI_TEXT_FUNCTION
#undef HOOK_GDI_FUNCTION
// Bitmap functions
HOOK_GDI_FUNCTION(msimg32, AlphaBlend);
HOOK_GDI_FUNCTION(gdi32, BitBlt);
HOOK_GDI_FUNCTION(gdi32, CreateCompatibleBitmap);
HOOK_GDI_FUNCTION(gdi32, CreateDIBitmap);
HOOK_GDI_FUNCTION(gdi32, CreateDIBSection);
HOOK_GDI_FUNCTION(gdi32, CreateDiscardableBitmap);
HOOK_GDI_FUNCTION(gdi32, ExtFloodFill);
HOOK_GDI_FUNCTION(gdi32, GetDIBits);
HOOK_GDI_FUNCTION(gdi32, GetPixel);
HOOK_GDI_FUNCTION(msimg32, GradientFill);
HOOK_GDI_FUNCTION(gdi32, MaskBlt);
HOOK_GDI_FUNCTION(gdi32, PlgBlt);
HOOK_GDI_FUNCTION(gdi32, SetDIBits);
HOOK_GDI_FUNCTION(gdi32, SetDIBitsToDevice);
HOOK_GDI_FUNCTION(gdi32, SetPixel);
HOOK_GDI_FUNCTION(gdi32, SetPixelV);
HOOK_GDI_FUNCTION(gdi32, StretchBlt);
HOOK_GDI_FUNCTION(gdi32, StretchDIBits);
HOOK_GDI_FUNCTION(msimg32, TransparentBlt);
// Brush functions
HOOK_GDI_FUNCTION(gdi32, PatBlt);
// Device context functions
HOOK_GDI_FUNCTION(gdi32, CreateCompatibleDC);
HOOK_GDI_FUNCTION(gdi32, DrawEscape);
// Filled shape functions
HOOK_GDI_FUNCTION(gdi32, Chord);
HOOK_GDI_FUNCTION(gdi32, Ellipse);
HOOK_GDI_FUNCTION(user32, FillRect);
HOOK_GDI_FUNCTION(user32, FrameRect);
HOOK_GDI_FUNCTION(user32, InvertRect);
HOOK_GDI_FUNCTION(gdi32, Pie);
HOOK_GDI_FUNCTION(gdi32, Polygon);
HOOK_GDI_FUNCTION(gdi32, PolyPolygon);
HOOK_GDI_FUNCTION(gdi32, Rectangle);
HOOK_GDI_FUNCTION(gdi32, RoundRect);
// Font and text functions
HOOK_GDI_TEXT_FUNCTION(user32, DrawText);
HOOK_GDI_TEXT_FUNCTION(user32, DrawTextEx);
HOOK_GDI_TEXT_FUNCTION(gdi32, ExtTextOut);
HOOK_GDI_TEXT_FUNCTION(gdi32, PolyTextOut);
HOOK_GDI_TEXT_FUNCTION(user32, TabbedTextOut);
HOOK_GDI_TEXT_FUNCTION(gdi32, TextOut);
// Line and curve functions
HOOK_GDI_FUNCTION(gdi32, AngleArc);
HOOK_GDI_FUNCTION(gdi32, Arc);
HOOK_GDI_FUNCTION(gdi32, ArcTo);
HOOK_GDI_FUNCTION(gdi32, LineTo);
HOOK_GDI_FUNCTION(gdi32, PolyBezier);
HOOK_GDI_FUNCTION(gdi32, PolyBezierTo);
HOOK_GDI_FUNCTION(gdi32, PolyDraw);
HOOK_GDI_FUNCTION(gdi32, Polyline);
HOOK_GDI_FUNCTION(gdi32, PolylineTo);
HOOK_GDI_FUNCTION(gdi32, PolyPolyline);
// Painting and drawing functions
HOOK_GDI_FUNCTION(user32, DrawCaption);
HOOK_GDI_FUNCTION(user32, DrawEdge);
HOOK_GDI_FUNCTION(user32, DrawFocusRect);
HOOK_GDI_FUNCTION(user32, DrawFrameControl);
HOOK_GDI_TEXT_FUNCTION(user32, DrawState);
HOOK_GDI_TEXT_FUNCTION(user32, GrayString);
HOOK_GDI_FUNCTION(user32, PaintDesktop);
// Region functions
HOOK_GDI_FUNCTION(gdi32, FillRgn);
HOOK_GDI_FUNCTION(gdi32, FrameRgn);
HOOK_GDI_FUNCTION(gdi32, InvertRgn);
HOOK_GDI_FUNCTION(gdi32, PaintRgn);
// Scroll bar functions
HOOK_GDI_FUNCTION(user32, ScrollDC);
DetourTransactionCommit();
}
}

View File

@ -1,7 +1,6 @@
#pragma once
class CompatGdiFunctions
namespace CompatGdiFunctions
{
public:
static void hookGdiFunctions();
void installHooks();
};

View File

@ -4,7 +4,6 @@ typedef unsigned long DWORD;
namespace Config
{
const DWORD gdiDcCacheSize = 10;
const DWORD minRefreshInterval = 1000 / 60;
const DWORD minRefreshIntervalAfterFlip = 1000 / 10;
const DWORD minPaletteUpdateInterval = 1000 / 60;

View File

@ -94,7 +94,7 @@
</ClCompile>
<Link>
<ModuleDefinitionFile>DDrawCompat.def</ModuleDefinitionFile>
<AdditionalDependencies>dxguid.lib;detours.lib;oleacc.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>dxguid.lib;detours.lib;msimg32.lib;oleacc.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
@ -121,7 +121,7 @@
</ClCompile>
<Link>
<ModuleDefinitionFile>DDrawCompat.def</ModuleDefinitionFile>
<AdditionalDependencies>dxguid.lib;detours.lib;oleacc.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>dxguid.lib;detours.lib;msimg32.lib;oleacc.lib;%(AdditionalDependencies)</AdditionalDependencies>
<GenerateDebugInformation>No</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
@ -146,9 +146,11 @@
<ItemGroup>
<ClInclude Include="CompatDirectDrawPalette.h" />
<ClInclude Include="CompatGdi.h" />
<ClInclude Include="CompatGdiCaret.h" />
<ClInclude Include="CompatGdiDc.h" />
<ClInclude Include="CompatGdiDcCache.h" />
<ClInclude Include="CompatGdiFunctions.h" />
<ClInclude Include="CompatGdiWinProc.h" />
<ClInclude Include="Config.h" />
<ClInclude Include="DDrawProcs.h" />
<ClInclude Include="CompatDirectDraw.h" />
@ -170,8 +172,10 @@
<ClCompile Include="CompatDirectDrawPalette.cpp" />
<ClCompile Include="CompatDirectDrawSurface.cpp" />
<ClCompile Include="CompatGdi.cpp" />
<ClCompile Include="CompatGdiCaret.cpp" />
<ClCompile Include="CompatGdiDcCache.cpp" />
<ClCompile Include="CompatGdiFunctions.cpp" />
<ClCompile Include="CompatGdiWinProc.cpp" />
<ClCompile Include="CompatVtable.cpp" />
<ClCompile Include="DDrawLog.cpp" />
<ClCompile Include="DllMain.cpp" />

View File

@ -75,6 +75,12 @@
<ClInclude Include="CompatGdi.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="CompatGdiWinProc.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="CompatGdiCaret.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="DllMain.cpp">
@ -122,6 +128,12 @@
<ClCompile Include="CompatGdi.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="CompatGdiWinProc.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="CompatGdiCaret.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<None Include="DDrawCompat.def">

View File

@ -302,13 +302,8 @@ bool RealPrimarySurface::isFullScreen()
bool RealPrimarySurface::isLost()
{
const bool isLost = g_frontBuffer &&
return g_frontBuffer &&
DDERR_SURFACELOST == CompatDirectDrawSurface<IDirectDrawSurface7>::s_origVtable.IsLost(g_frontBuffer);
if (isLost)
{
CompatGdi::releaseSurfaceMemory();
}
return isLost;
}
void RealPrimarySurface::release()
@ -380,6 +375,8 @@ void RealPrimarySurface::update()
void RealPrimarySurface::updatePalette()
{
CompatGdi::updatePalette();
if (isFullScreen())
{
flip(DDFLIP_WAIT);