From 5195a5e4b4803f53d5b7ac11421b5d5477b2d9ef Mon Sep 17 00:00:00 2001 From: narzoul Date: Sun, 12 Mar 2017 17:00:16 +0100 Subject: [PATCH] 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. --- .../DDraw/Surfaces/PrimarySurfaceImpl.cpp | 2 +- DDrawCompat/Gdi/Dc.cpp | 86 +++-------------- DDrawCompat/Gdi/Dc.h | 2 +- DDrawCompat/Gdi/DcFunctions.cpp | 85 +++++++++++++++++ DDrawCompat/Gdi/Gdi.cpp | 62 ++++++------- DDrawCompat/Gdi/Gdi.h | 3 +- DDrawCompat/Gdi/PaintHandlers.cpp | 3 +- DDrawCompat/Gdi/WinProc.cpp | 93 ++++++++++++++++--- 8 files changed, 212 insertions(+), 124 deletions(-) diff --git a/DDrawCompat/DDraw/Surfaces/PrimarySurfaceImpl.cpp b/DDrawCompat/DDraw/Surfaces/PrimarySurfaceImpl.cpp index 066e17f..37eddd3 100644 --- a/DDrawCompat/DDraw/Surfaces/PrimarySurfaceImpl.cpp +++ b/DDrawCompat/DDraw/Surfaces/PrimarySurfaceImpl.cpp @@ -185,7 +185,7 @@ namespace DDraw result = m_impl.Restore(This); if (SUCCEEDED(result)) { - Gdi::invalidate(nullptr); + Gdi::redraw(nullptr); } } } diff --git a/DDrawCompat/Gdi/Dc.cpp b/DDrawCompat/Gdi/Dc.cpp index 4b4df91..00345cf 100644 --- a/DDrawCompat/Gdi/Dc.cpp +++ b/DDrawCompat/Gdi/Dc.cpp @@ -23,12 +23,6 @@ namespace typedef std::unordered_map CompatDcMap; CompatDcMap g_origDcToCompatDc; - struct ExcludeClipRectsData - { - HDC compatDc; - HWND rootWnd; - }; - void copyDcAttributes(CompatDc& compatDc, HDC origDc, POINT& origin) { SelectObject(compatDc.dc, GetCurrentObject(origDc, OBJ_FONT)); @@ -81,52 +75,17 @@ namespace 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(lParam); - if (hwnd == excludeClipRectsData->rootWnd) + if (hwnd) { - return FALSE; - } - - if (!IsWindowVisible(hwnd)) - { - return TRUE; - } - - 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(&excludeClipRectsData)); - } - - HWND menuWindow = FindWindow(reinterpret_cast(0x8000), nullptr); - while (menuWindow && menuWindow != hwnd) - { - excludeClipRectForOverlappingWindow( - menuWindow, reinterpret_cast(&excludeClipRectsData)); - menuWindow = FindWindowEx(nullptr, menuWindow, reinterpret_cast(0x8000), nullptr); - } - } - - void setClippingRegion(HDC compatDc, HDC origDc, HWND hwnd, bool isMenuWindow, const POINT& origin) - { - if (GetDesktopWindow() == hwnd) - { - return; + HRGN sysRgn = CreateRectRgn(0, 0, 0, 0); + if (1 == GetRandomRgn(origDc, sysRgn, SYSRGN)) + { + SelectClipRgn(compatDc, sysRgn); + SetMetaRgn(compatDc); + } + DeleteObject(sysRgn); } HRGN clipRgn = CreateRectRgn(0, 0, 0, 0); @@ -135,22 +94,8 @@ namespace OffsetRgn(clipRgn, origin.x, origin.y); 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); - SetMetaRgn(compatDc); + } } @@ -158,7 +103,7 @@ namespace Gdi { namespace Dc { - HDC getDc(HDC origDc, bool isMenuPaintDc) + HDC getDc(HDC origDc) { if (!origDc || OBJ_DC != GetObjectType(origDc) || DT_RASDISPLAY != GetDeviceCaps(origDc, TECHNOLOGY)) { @@ -174,13 +119,6 @@ namespace Gdi 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()); if (!compatDc.dc) { @@ -192,7 +130,7 @@ namespace Gdi compatDc.savedState = SaveDC(compatDc.dc); 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.origDc = origDc; diff --git a/DDrawCompat/Gdi/Dc.h b/DDrawCompat/Gdi/Dc.h index 2cc4ad7..a3a78e1 100644 --- a/DDrawCompat/Gdi/Dc.h +++ b/DDrawCompat/Gdi/Dc.h @@ -8,7 +8,7 @@ namespace Gdi { namespace Dc { - HDC getDc(HDC origDc, bool isMenuPaintDc = false); + HDC getDc(HDC origDc); HDC getOrigDc(HDC dc); void releaseDc(HDC origDc); } diff --git a/DDrawCompat/Gdi/DcFunctions.cpp b/DDrawCompat/Gdi/DcFunctions.cpp index e030e31..c381bb7 100644 --- a/DDrawCompat/Gdi/DcFunctions.cpp +++ b/DDrawCompat/Gdi/DcFunctions.cpp @@ -9,6 +9,12 @@ namespace { + struct ExcludeRectContext + { + HRGN rgn; + HWND rootWnd; + }; + std::unordered_map g_funcNames; template @@ -106,6 +112,53 @@ namespace return result; } + void enumTopLevelThreadWindows(WNDENUMPROC enumFunc, LPARAM lParam) + { + const DWORD currentThreadId = GetCurrentThreadId(); + const char* MENU_ATOM = reinterpret_cast(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(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 OrigFuncPtr getCompatGdiDcFuncPtr(FuncPtr) { @@ -203,6 +256,35 @@ namespace moduleName, funcName, getCompatGdiDcFuncPtr(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(&excludeRectContext)); + + return 1; + } + HWND WINAPI windowFromDc(HDC dc) { return CALL_ORIG_FUNC(WindowFromDC)(Gdi::Dc::getOrigDc(dc)); @@ -248,6 +330,9 @@ namespace Gdi // Brush functions HOOK_GDI_DC_FUNCTION(gdi32, PatBlt); + // Clipping functions + HOOK_FUNCTION(gdi32, GetRandomRgn, getRandomRgn); + // Device context functions HOOK_GDI_DC_FUNCTION(gdi32, DrawEscape); HOOK_FUNCTION(user32, WindowFromDC, windowFromDc); diff --git a/DDrawCompat/Gdi/Gdi.cpp b/DDrawCompat/Gdi/Gdi.cpp index 750e771..27977c2 100644 --- a/DDrawCompat/Gdi/Gdi.cpp +++ b/DDrawCompat/Gdi/Gdi.cpp @@ -23,36 +23,6 @@ namespace HANDLE g_ddUnlockEndEvent = nullptr; 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(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) { DDSURFACEDESC2 desc = {}; @@ -76,6 +46,12 @@ namespace return true; } + BOOL CALLBACK redrawWindowCallback(HWND hwnd, LPARAM lParam) + { + Gdi::redrawWindow(hwnd, reinterpret_cast(lParam)); + return TRUE; + } + void unlockGdiSurface() { GdiFlush(); @@ -213,14 +189,36 @@ namespace Gdi } } - void invalidate(const RECT* rect) + void redraw(HRGN rgn) { if (isEmulationEnabled()) { - EnumWindows(&invalidateWindow, reinterpret_cast(rect)); + EnumThreadWindows(GetCurrentThreadId(), &redrawWindowCallback, reinterpret_cast(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() { return g_disableEmulationCount <= 0 && DDraw::RealPrimarySurface::isFullScreen(); diff --git a/DDrawCompat/Gdi/Gdi.h b/DDrawCompat/Gdi/Gdi.h index e3b8276..6ba16cb 100644 --- a/DDrawCompat/Gdi/Gdi.h +++ b/DDrawCompat/Gdi/Gdi.h @@ -14,8 +14,9 @@ namespace Gdi void hookWndProc(LPCSTR className, WNDPROC &oldWndProc, WNDPROC newWndProc); void installHooks(); - void invalidate(const RECT* rect); bool isEmulationEnabled(); + void redraw(HRGN rgn); + void redrawWindow(HWND hwnd, HRGN rgn); void unhookWndProc(LPCSTR className, WNDPROC oldWndProc); void uninstallHooks(); void updatePalette(DWORD startingEntry, DWORD count); diff --git a/DDrawCompat/Gdi/PaintHandlers.cpp b/DDrawCompat/Gdi/PaintHandlers.cpp index fcdfa71..cc8ff6d 100644 --- a/DDrawCompat/Gdi/PaintHandlers.cpp +++ b/DDrawCompat/Gdi/PaintHandlers.cpp @@ -196,8 +196,7 @@ namespace } HDC dc = GetWindowDC(hwnd); - const bool isMenuPaintDc = true; - HDC compatDc = Gdi::Dc::getDc(dc, isMenuPaintDc); + HDC compatDc = Gdi::Dc::getDc(dc); if (compatDc) { CallWindowProc(origWndProc, hwnd, WM_PRINT, reinterpret_cast(compatDc), diff --git a/DDrawCompat/Gdi/WinProc.cpp b/DDrawCompat/Gdi/WinProc.cpp index 27d1c97..95b9f18 100644 --- a/DDrawCompat/Gdi/WinProc.cpp +++ b/DDrawCompat/Gdi/WinProc.cpp @@ -1,5 +1,6 @@ #define WIN32_LEAN_AND_MEAN +#include #include #include @@ -16,14 +17,22 @@ namespace { + struct WindowData + { + RECT wndRect; + std::shared_ptr sysClipRgn; + }; + HHOOK g_callWndRetProcHook = nullptr; HWINEVENTHOOK g_objectStateChangeEventHook = nullptr; - std::unordered_map g_prevWindowRect; + std::unordered_map g_windowData; void disableDwmAttributes(HWND hwnd); void onActivate(HWND hwnd); void onMenuSelect(); 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); LRESULT CALLBACK callWndRetProc(int nCode, WPARAM wParam, LPARAM lParam) @@ -41,7 +50,7 @@ namespace else if (WM_DESTROY == ret->message) { Compat::ScopedCriticalSection lock(Gdi::g_gdiCriticalSection); - g_prevWindowRect.erase(ret->hwnd); + g_windowData.erase(ret->hwnd); } else if (WM_WINDOWPOSCHANGED == ret->message) { @@ -81,6 +90,21 @@ namespace &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( HWINEVENTHOOK /*hWinEventHook*/, DWORD /*event*/, @@ -161,26 +185,69 @@ namespace void onWindowPosChanged(HWND hwnd) { + if (GetAncestor(hwnd, GA_ROOT) != hwnd) + { + return; + } + Compat::ScopedCriticalSection lock(Gdi::g_gdiCriticalSection); - const auto it = g_prevWindowRect.find(hwnd); - if (it != g_prevWindowRect.end()) + WindowData prevData = g_windowData[hwnd]; + 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()) - { - GetWindowRect(hwnd, it != g_prevWindowRect.end() ? &it->second : &g_prevWindowRect[hwnd]); - RedrawWindow(hwnd, nullptr, nullptr, RDW_ERASE | RDW_FRAME | RDW_INVALIDATE | RDW_ALLCHILDREN); - } + return; } - 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)