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

Minimize GDI redraws on window position changes

When the display mode changes in full screen mode, DirectDraw resets
the main window to topmost after a short delay, generating a
WM_WINDOWPOSCHANGED message. This could cause an unnecessary repainting
of the window, erasing the background even if nothing needed to be updated.

Now only the regions that change during window repositioning are redrawn.

Fixes the black flashing in Rogue Spear's menus after display mode changes,
mentioned in issue #2.
This commit is contained in:
narzoul 2017-03-12 17:00:16 +01:00
parent d1cd72b304
commit 5195a5e4b4
8 changed files with 212 additions and 124 deletions

View File

@ -185,7 +185,7 @@ namespace DDraw
result = m_impl.Restore(This); result = m_impl.Restore(This);
if (SUCCEEDED(result)) if (SUCCEEDED(result))
{ {
Gdi::invalidate(nullptr); Gdi::redraw(nullptr);
} }
} }
} }

View File

@ -23,12 +23,6 @@ namespace
typedef std::unordered_map<HDC, CompatDc> CompatDcMap; typedef std::unordered_map<HDC, CompatDc> CompatDcMap;
CompatDcMap g_origDcToCompatDc; CompatDcMap g_origDcToCompatDc;
struct ExcludeClipRectsData
{
HDC compatDc;
HWND rootWnd;
};
void copyDcAttributes(CompatDc& compatDc, HDC origDc, POINT& origin) void copyDcAttributes(CompatDc& compatDc, HDC origDc, POINT& origin)
{ {
SelectObject(compatDc.dc, GetCurrentObject(origDc, OBJ_FONT)); SelectObject(compatDc.dc, GetCurrentObject(origDc, OBJ_FONT));
@ -81,52 +75,17 @@ namespace
MoveToEx(compatDc.dc, currentPos.x, currentPos.y, nullptr); MoveToEx(compatDc.dc, currentPos.x, currentPos.y, nullptr);
} }
BOOL CALLBACK excludeClipRectForOverlappingWindow(HWND hwnd, LPARAM lParam) void setClippingRegion(HDC compatDc, HDC origDc, HWND hwnd, const POINT& origin)
{ {
auto excludeClipRectsData = reinterpret_cast<ExcludeClipRectsData*>(lParam); if (hwnd)
if (hwnd == excludeClipRectsData->rootWnd)
{ {
return FALSE; HRGN sysRgn = CreateRectRgn(0, 0, 0, 0);
} if (1 == GetRandomRgn(origDc, sysRgn, SYSRGN))
{
if (!IsWindowVisible(hwnd)) SelectClipRgn(compatDc, sysRgn);
{ SetMetaRgn(compatDc);
return TRUE; }
} DeleteObject(sysRgn);
RECT windowRect = {};
GetWindowRect(hwnd, &windowRect);
HRGN windowRgn = CreateRectRgnIndirect(&windowRect);
ExtSelectClipRgn(excludeClipRectsData->compatDc, windowRgn, RGN_DIFF);
DeleteObject(windowRgn);
return TRUE;
}
void excludeClipRectsForOverlappingWindows(HWND hwnd, bool isMenuWindow, HDC compatDc)
{
ExcludeClipRectsData excludeClipRectsData = { compatDc, GetAncestor(hwnd, GA_ROOT) };
if (!isMenuWindow)
{
EnumWindows(&excludeClipRectForOverlappingWindow,
reinterpret_cast<LPARAM>(&excludeClipRectsData));
}
HWND menuWindow = FindWindow(reinterpret_cast<LPCSTR>(0x8000), nullptr);
while (menuWindow && menuWindow != hwnd)
{
excludeClipRectForOverlappingWindow(
menuWindow, reinterpret_cast<LPARAM>(&excludeClipRectsData));
menuWindow = FindWindowEx(nullptr, menuWindow, reinterpret_cast<LPCSTR>(0x8000), nullptr);
}
}
void setClippingRegion(HDC compatDc, HDC origDc, HWND hwnd, bool isMenuWindow, const POINT& origin)
{
if (GetDesktopWindow() == hwnd)
{
return;
} }
HRGN clipRgn = CreateRectRgn(0, 0, 0, 0); HRGN clipRgn = CreateRectRgn(0, 0, 0, 0);
@ -135,22 +94,8 @@ namespace
OffsetRgn(clipRgn, origin.x, origin.y); OffsetRgn(clipRgn, origin.x, origin.y);
SelectClipRgn(compatDc, clipRgn); SelectClipRgn(compatDc, clipRgn);
} }
if (hwnd)
{
if (isMenuWindow || 1 != GetRandomRgn(origDc, clipRgn, SYSRGN))
{
RECT rect = {};
GetWindowRect(hwnd, &rect);
SetRectRgn(clipRgn, rect.left, rect.top, rect.right, rect.bottom);
}
excludeClipRectsForOverlappingWindows(hwnd, isMenuWindow, compatDc);
ExtSelectClipRgn(compatDc, clipRgn, RGN_AND);
}
DeleteObject(clipRgn); DeleteObject(clipRgn);
SetMetaRgn(compatDc);
} }
} }
@ -158,7 +103,7 @@ namespace Gdi
{ {
namespace Dc namespace Dc
{ {
HDC getDc(HDC origDc, bool isMenuPaintDc) HDC getDc(HDC origDc)
{ {
if (!origDc || OBJ_DC != GetObjectType(origDc) || DT_RASDISPLAY != GetDeviceCaps(origDc, TECHNOLOGY)) if (!origDc || OBJ_DC != GetObjectType(origDc) || DT_RASDISPLAY != GetDeviceCaps(origDc, TECHNOLOGY))
{ {
@ -174,13 +119,6 @@ namespace Gdi
return it->second.dc; return it->second.dc;
} }
const HWND hwnd = CALL_ORIG_FUNC(WindowFromDC)(origDc);
const bool isMenuWindow = hwnd && 0x8000 == GetClassLongPtr(hwnd, GCW_ATOM);
if (isMenuWindow && !isMenuPaintDc)
{
return nullptr;
}
CompatDc compatDc(Gdi::DcCache::getDc()); CompatDc compatDc(Gdi::DcCache::getDc());
if (!compatDc.dc) if (!compatDc.dc)
{ {
@ -192,7 +130,7 @@ namespace Gdi
compatDc.savedState = SaveDC(compatDc.dc); compatDc.savedState = SaveDC(compatDc.dc);
copyDcAttributes(compatDc, origDc, origin); copyDcAttributes(compatDc, origDc, origin);
setClippingRegion(compatDc.dc, origDc, hwnd, isMenuWindow, origin); setClippingRegion(compatDc.dc, origDc, CALL_ORIG_FUNC(WindowFromDC)(origDc), origin);
compatDc.refCount = 1; compatDc.refCount = 1;
compatDc.origDc = origDc; compatDc.origDc = origDc;

View File

@ -8,7 +8,7 @@ namespace Gdi
{ {
namespace Dc namespace Dc
{ {
HDC getDc(HDC origDc, bool isMenuPaintDc = false); HDC getDc(HDC origDc);
HDC getOrigDc(HDC dc); HDC getOrigDc(HDC dc);
void releaseDc(HDC origDc); void releaseDc(HDC origDc);
} }

View File

@ -9,6 +9,12 @@
namespace namespace
{ {
struct ExcludeRectContext
{
HRGN rgn;
HWND rootWnd;
};
std::unordered_map<void*, const char*> g_funcNames; std::unordered_map<void*, const char*> g_funcNames;
template <typename OrigFuncPtr, OrigFuncPtr origFunc, typename... Params> template <typename OrigFuncPtr, OrigFuncPtr origFunc, typename... Params>
@ -106,6 +112,53 @@ namespace
return result; return result;
} }
void enumTopLevelThreadWindows(WNDENUMPROC enumFunc, LPARAM lParam)
{
const DWORD currentThreadId = GetCurrentThreadId();
const char* MENU_ATOM = reinterpret_cast<LPCSTR>(0x8000);
HWND menuWindow = FindWindow(MENU_ATOM, nullptr);
BOOL cont = TRUE;
while (menuWindow && cont)
{
if (currentThreadId == GetWindowThreadProcessId(menuWindow, nullptr))
{
cont = enumFunc(menuWindow, lParam);
}
if (cont)
{
menuWindow = FindWindowEx(nullptr, menuWindow, MENU_ATOM, nullptr);
}
}
if (cont)
{
EnumThreadWindows(currentThreadId, enumFunc, lParam);
}
}
BOOL CALLBACK excludeRectForOverlappingWindow(HWND hwnd, LPARAM lParam)
{
auto excludeRectContext = reinterpret_cast<ExcludeRectContext*>(lParam);
if (hwnd == excludeRectContext->rootWnd)
{
return FALSE;
}
if (!IsWindowVisible(hwnd) || (GetWindowLongPtr(hwnd, GWL_EXSTYLE) & WS_EX_TRANSPARENT))
{
return TRUE;
}
RECT windowRect = {};
GetWindowRect(hwnd, &windowRect);
HRGN windowRgn = CreateRectRgnIndirect(&windowRect);
CombineRgn(excludeRectContext->rgn, excludeRectContext->rgn, windowRgn, RGN_DIFF);
DeleteObject(windowRgn);
return TRUE;
}
template <typename OrigFuncPtr, OrigFuncPtr origFunc, typename Result, typename... Params> template <typename OrigFuncPtr, OrigFuncPtr origFunc, typename Result, typename... Params>
OrigFuncPtr getCompatGdiDcFuncPtr(FuncPtr<Result, Params...>) OrigFuncPtr getCompatGdiDcFuncPtr(FuncPtr<Result, Params...>)
{ {
@ -203,6 +256,35 @@ namespace
moduleName, funcName, getCompatGdiDcFuncPtr<OrigFuncPtr, origFunc>(origFunc)); moduleName, funcName, getCompatGdiDcFuncPtr<OrigFuncPtr, origFunc>(origFunc));
} }
int WINAPI getRandomRgn(HDC hdc, HRGN hrgn, INT iNum)
{
int result = CALL_ORIG_FUNC(GetRandomRgn)(hdc, hrgn, iNum);
if (1 != result)
{
return result;
}
HWND hwnd = WindowFromDC(hdc);
if (!hwnd)
{
return 1;
}
if ((GetWindowLongPtr(hwnd, GWL_EXSTYLE) & WS_EX_LAYERED) &&
!GetLayeredWindowAttributes(hwnd, nullptr, nullptr, nullptr))
{
RECT rect = {};
GetWindowRect(hwnd, &rect);
SetRectRgn(hrgn, rect.left, rect.top, rect.right, rect.bottom);
}
ExcludeRectContext excludeRectContext = { hrgn, GetAncestor(hwnd, GA_ROOT) };
enumTopLevelThreadWindows(excludeRectForOverlappingWindow,
reinterpret_cast<LPARAM>(&excludeRectContext));
return 1;
}
HWND WINAPI windowFromDc(HDC dc) HWND WINAPI windowFromDc(HDC dc)
{ {
return CALL_ORIG_FUNC(WindowFromDC)(Gdi::Dc::getOrigDc(dc)); return CALL_ORIG_FUNC(WindowFromDC)(Gdi::Dc::getOrigDc(dc));
@ -248,6 +330,9 @@ namespace Gdi
// Brush functions // Brush functions
HOOK_GDI_DC_FUNCTION(gdi32, PatBlt); HOOK_GDI_DC_FUNCTION(gdi32, PatBlt);
// Clipping functions
HOOK_FUNCTION(gdi32, GetRandomRgn, getRandomRgn);
// Device context functions // Device context functions
HOOK_GDI_DC_FUNCTION(gdi32, DrawEscape); HOOK_GDI_DC_FUNCTION(gdi32, DrawEscape);
HOOK_FUNCTION(user32, WindowFromDC, windowFromDc); HOOK_FUNCTION(user32, WindowFromDC, windowFromDc);

View File

@ -23,36 +23,6 @@ namespace
HANDLE g_ddUnlockEndEvent = nullptr; HANDLE g_ddUnlockEndEvent = nullptr;
bool g_isDelayedUnlockPending = false; bool g_isDelayedUnlockPending = false;
BOOL CALLBACK invalidateWindow(HWND hwnd, LPARAM lParam)
{
if (!IsWindowVisible(hwnd))
{
return TRUE;
}
DWORD processId = 0;
GetWindowThreadProcessId(hwnd, &processId);
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
{
RedrawWindow(hwnd, nullptr, nullptr, RDW_ERASE | RDW_FRAME | RDW_INVALIDATE | RDW_ALLCHILDREN);
}
return TRUE;
}
bool lockGdiSurface(DWORD lockFlags) bool lockGdiSurface(DWORD lockFlags)
{ {
DDSURFACEDESC2 desc = {}; DDSURFACEDESC2 desc = {};
@ -76,6 +46,12 @@ namespace
return true; return true;
} }
BOOL CALLBACK redrawWindowCallback(HWND hwnd, LPARAM lParam)
{
Gdi::redrawWindow(hwnd, reinterpret_cast<HRGN>(lParam));
return TRUE;
}
void unlockGdiSurface() void unlockGdiSurface()
{ {
GdiFlush(); GdiFlush();
@ -213,14 +189,36 @@ namespace Gdi
} }
} }
void invalidate(const RECT* rect) void redraw(HRGN rgn)
{ {
if (isEmulationEnabled()) if (isEmulationEnabled())
{ {
EnumWindows(&invalidateWindow, reinterpret_cast<LPARAM>(rect)); EnumThreadWindows(GetCurrentThreadId(), &redrawWindowCallback, reinterpret_cast<LPARAM>(rgn));
} }
} }
void redrawWindow(HWND hwnd, HRGN rgn)
{
if (!IsWindowVisible(hwnd))
{
return;
}
if (!rgn)
{
RedrawWindow(hwnd, nullptr, nullptr,
RDW_ERASE | RDW_FRAME | RDW_INVALIDATE | RDW_UPDATENOW | RDW_ALLCHILDREN);
return;
}
POINT origin = {};
ClientToScreen(hwnd, &origin);
OffsetRgn(rgn, -origin.x, -origin.y);
RedrawWindow(hwnd, nullptr, rgn,
RDW_ERASE | RDW_FRAME | RDW_INVALIDATE | RDW_UPDATENOW | RDW_ALLCHILDREN);
OffsetRgn(rgn, origin.x, origin.y);
}
bool isEmulationEnabled() bool isEmulationEnabled()
{ {
return g_disableEmulationCount <= 0 && DDraw::RealPrimarySurface::isFullScreen(); return g_disableEmulationCount <= 0 && DDraw::RealPrimarySurface::isFullScreen();

View File

@ -14,8 +14,9 @@ namespace Gdi
void hookWndProc(LPCSTR className, WNDPROC &oldWndProc, WNDPROC newWndProc); void hookWndProc(LPCSTR className, WNDPROC &oldWndProc, WNDPROC newWndProc);
void installHooks(); void installHooks();
void invalidate(const RECT* rect);
bool isEmulationEnabled(); bool isEmulationEnabled();
void redraw(HRGN rgn);
void redrawWindow(HWND hwnd, HRGN rgn);
void unhookWndProc(LPCSTR className, WNDPROC oldWndProc); void unhookWndProc(LPCSTR className, WNDPROC oldWndProc);
void uninstallHooks(); void uninstallHooks();
void updatePalette(DWORD startingEntry, DWORD count); void updatePalette(DWORD startingEntry, DWORD count);

View File

@ -196,8 +196,7 @@ namespace
} }
HDC dc = GetWindowDC(hwnd); HDC dc = GetWindowDC(hwnd);
const bool isMenuPaintDc = true; HDC compatDc = Gdi::Dc::getDc(dc);
HDC compatDc = Gdi::Dc::getDc(dc, isMenuPaintDc);
if (compatDc) if (compatDc)
{ {
CallWindowProc(origWndProc, hwnd, WM_PRINT, reinterpret_cast<WPARAM>(compatDc), CallWindowProc(origWndProc, hwnd, WM_PRINT, reinterpret_cast<WPARAM>(compatDc),

View File

@ -1,5 +1,6 @@
#define WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN
#include <memory>
#include <unordered_map> #include <unordered_map>
#include <dwmapi.h> #include <dwmapi.h>
@ -16,14 +17,22 @@
namespace namespace
{ {
struct WindowData
{
RECT wndRect;
std::shared_ptr<HRGN__> sysClipRgn;
};
HHOOK g_callWndRetProcHook = nullptr; HHOOK g_callWndRetProcHook = nullptr;
HWINEVENTHOOK g_objectStateChangeEventHook = nullptr; HWINEVENTHOOK g_objectStateChangeEventHook = nullptr;
std::unordered_map<HWND, RECT> g_prevWindowRect; std::unordered_map<HWND, WindowData> g_windowData;
void disableDwmAttributes(HWND hwnd); void disableDwmAttributes(HWND hwnd);
void onActivate(HWND hwnd); void onActivate(HWND hwnd);
void onMenuSelect(); void onMenuSelect();
void onWindowPosChanged(HWND hwnd); void onWindowPosChanged(HWND hwnd);
void redrawChangedWindowRegion(HWND hwnd, const WindowData& prevData, const WindowData& data);
void redrawUncoveredRegion(const WindowData& prevData, const WindowData& data);
void removeDropShadow(HWND hwnd); void removeDropShadow(HWND hwnd);
LRESULT CALLBACK callWndRetProc(int nCode, WPARAM wParam, LPARAM lParam) LRESULT CALLBACK callWndRetProc(int nCode, WPARAM wParam, LPARAM lParam)
@ -41,7 +50,7 @@ namespace
else if (WM_DESTROY == ret->message) else if (WM_DESTROY == ret->message)
{ {
Compat::ScopedCriticalSection lock(Gdi::g_gdiCriticalSection); Compat::ScopedCriticalSection lock(Gdi::g_gdiCriticalSection);
g_prevWindowRect.erase(ret->hwnd); g_windowData.erase(ret->hwnd);
} }
else if (WM_WINDOWPOSCHANGED == ret->message) else if (WM_WINDOWPOSCHANGED == ret->message)
{ {
@ -81,6 +90,21 @@ namespace
&disableTransitions, sizeof(disableTransitions)); &disableTransitions, sizeof(disableTransitions));
} }
WindowData getWindowData(HWND hwnd)
{
WindowData data;
if (IsWindowVisible(hwnd))
{
GetWindowRect(hwnd, &data.wndRect);
data.sysClipRgn.reset(CreateRectRgnIndirect(&data.wndRect), DeleteObject);
HDC dc = GetWindowDC(hwnd);
GetRandomRgn(dc, data.sysClipRgn.get(), SYSRGN);
ReleaseDC(hwnd, dc);
}
return data;
}
void CALLBACK objectStateChangeEvent( void CALLBACK objectStateChangeEvent(
HWINEVENTHOOK /*hWinEventHook*/, HWINEVENTHOOK /*hWinEventHook*/,
DWORD /*event*/, DWORD /*event*/,
@ -161,26 +185,69 @@ namespace
void onWindowPosChanged(HWND hwnd) void onWindowPosChanged(HWND hwnd)
{ {
if (GetAncestor(hwnd, GA_ROOT) != hwnd)
{
return;
}
Compat::ScopedCriticalSection lock(Gdi::g_gdiCriticalSection); Compat::ScopedCriticalSection lock(Gdi::g_gdiCriticalSection);
const auto it = g_prevWindowRect.find(hwnd); WindowData prevData = g_windowData[hwnd];
if (it != g_prevWindowRect.end()) WindowData data = getWindowData(hwnd);
g_windowData[hwnd] = data;
if (!prevData.sysClipRgn && !data.sysClipRgn || !Gdi::isEmulationEnabled())
{ {
Gdi::invalidate(&it->second); return;
} }
if (IsWindowVisible(hwnd)) redrawUncoveredRegion(prevData, data);
redrawChangedWindowRegion(hwnd, prevData, data);
}
void redrawChangedWindowRegion(HWND hwnd, const WindowData& prevData, const WindowData& data)
{
if (!data.sysClipRgn)
{ {
if (Gdi::isEmulationEnabled()) return;
{
GetWindowRect(hwnd, it != g_prevWindowRect.end() ? &it->second : &g_prevWindowRect[hwnd]);
RedrawWindow(hwnd, nullptr, nullptr, RDW_ERASE | RDW_FRAME | RDW_INVALIDATE | RDW_ALLCHILDREN);
}
} }
else if (it != g_prevWindowRect.end())
if (!prevData.sysClipRgn)
{ {
g_prevWindowRect.erase(it); Gdi::redrawWindow(hwnd, data.sysClipRgn.get());
return;
} }
if (EqualRect(&prevData.wndRect, &data.wndRect))
{
HRGN rgn = CreateRectRgn(0, 0, 0, 0);
CombineRgn(rgn, data.sysClipRgn.get(), prevData.sysClipRgn.get(), RGN_DIFF);
Gdi::redrawWindow(hwnd, rgn);
DeleteObject(rgn);
}
else
{
Gdi::redrawWindow(hwnd, data.sysClipRgn.get());
}
}
void redrawUncoveredRegion(const WindowData& prevData, const WindowData& data)
{
if (!prevData.sysClipRgn)
{
return;
}
if (!data.sysClipRgn)
{
Gdi::redraw(prevData.sysClipRgn.get());
return;
}
HRGN rgn = CreateRectRgn(0, 0, 0, 0);
CombineRgn(rgn, prevData.sysClipRgn.get(), data.sysClipRgn.get(), RGN_DIFF);
Gdi::redraw(rgn);
DeleteObject(rgn);
} }
void removeDropShadow(HWND hwnd) void removeDropShadow(HWND hwnd)