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

Optimize GDI redraw on window position changes

This commit is contained in:
narzoul 2018-06-07 23:07:58 +02:00
parent 7cbc65878a
commit 6183aed7da
14 changed files with 456 additions and 108 deletions

View File

@ -17,14 +17,11 @@ namespace
void updateWindowClipList(CompatRef<IDirectDrawClipper> clipper, ClipperData& data);
void onWindowPosChange(HWND /*hwnd*/, const RECT& oldWindowRect, const RECT& newWindowRect)
void onWindowPosChange()
{
for (auto& clipperData : g_clipperData)
{
if (!IsRectEmpty(&oldWindowRect) || !IsRectEmpty(&newWindowRect))
{
updateWindowClipList(*clipperData.first, clipperData.second);
}
updateWindowClipList(*clipperData.first, clipperData.second);
}
}

View File

@ -221,9 +221,11 @@
<ClInclude Include="Gdi\DcCache.h" />
<ClInclude Include="Gdi\DcFunctions.h" />
<ClInclude Include="Gdi\PaintHandlers.h" />
<ClInclude Include="Gdi\Region.h" />
<ClInclude Include="Gdi\ScrollBar.h" />
<ClInclude Include="Gdi\ScrollFunctions.h" />
<ClInclude Include="Gdi\TitleBar.h" />
<ClInclude Include="Gdi\Window.h" />
<ClInclude Include="Gdi\WinProc.h" />
<ClInclude Include="Win32\DisplayMode.h" />
<ClInclude Include="Win32\FontSmoothing.h" />
@ -278,9 +280,11 @@
<ClCompile Include="Gdi\DcCache.cpp" />
<ClCompile Include="Gdi\DcFunctions.cpp" />
<ClCompile Include="Gdi\PaintHandlers.cpp" />
<ClCompile Include="Gdi\Region.cpp" />
<ClCompile Include="Gdi\ScrollBar.cpp" />
<ClCompile Include="Gdi\ScrollFunctions.cpp" />
<ClCompile Include="Gdi\TitleBar.cpp" />
<ClCompile Include="Gdi\Window.cpp" />
<ClCompile Include="Gdi\WinProc.cpp" />
<ClCompile Include="Win32\DisplayMode.cpp" />
<ClCompile Include="Win32\FontSmoothing.cpp" />

View File

@ -315,6 +315,12 @@
<ClInclude Include="D3dDdi\Device.h">
<Filter>Header Files\D3dDdi</Filter>
</ClInclude>
<ClInclude Include="Gdi\Window.h">
<Filter>Header Files\Gdi</Filter>
</ClInclude>
<ClInclude Include="Gdi\Region.h">
<Filter>Header Files\Gdi</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="Gdi\Gdi.cpp">
@ -482,6 +488,12 @@
<ClCompile Include="D3dDdi\Device.cpp">
<Filter>Source Files\D3dDdi</Filter>
</ClCompile>
<ClCompile Include="Gdi\Window.cpp">
<Filter>Source Files\Gdi</Filter>
</ClCompile>
<ClCompile Include="Gdi\Region.cpp">
<Filter>Source Files\Gdi</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<None Include="Dll\DDrawCompat.def">

View File

@ -7,6 +7,7 @@
#include "Gdi/Dc.h"
#include "Gdi/DcCache.h"
#include "Gdi/Gdi.h"
#include "Gdi/Window.h"
namespace
{
@ -95,7 +96,20 @@ namespace
SelectClipRgn(compatDc, clipRgn);
}
DeleteObject(clipRgn);
}
void updateWindow(HWND wnd)
{
RECT windowRect = {};
GetWindowRect(wnd, &windowRect);
auto& window = Gdi::Window::get(wnd);
RECT cachedWindowRect = window.getWindowRect();
if (!EqualRect(&windowRect, &cachedWindowRect))
{
Gdi::Window::updateAll();
}
}
}
@ -119,6 +133,13 @@ namespace Gdi
return it->second.dc;
}
const HWND wnd = CALL_ORIG_FUNC(WindowFromDC)(origDc);
const HWND rootWnd = wnd ? GetAncestor(wnd, GA_ROOT) : nullptr;
if (rootWnd && GetDesktopWindow() != rootWnd)
{
updateWindow(rootWnd);
}
CompatDc compatDc(Gdi::DcCache::getDc());
if (!compatDc.dc)
{
@ -130,7 +151,7 @@ namespace Gdi
compatDc.savedState = SaveDC(compatDc.dc);
copyDcAttributes(compatDc, origDc, origin);
setClippingRegion(compatDc.dc, origDc, CALL_ORIG_FUNC(WindowFromDC)(origDc), origin);
setClippingRegion(compatDc.dc, origDc, wnd, origin);
compatDc.refCount = 1;
compatDc.origDc = origDc;

View File

@ -242,7 +242,7 @@ namespace
}
HWND hwnd = WindowFromDC(hdc);
if (!hwnd || (GetWindowLongPtr(hwnd, GWL_EXSTYLE) & WS_EX_LAYERED))
if (!hwnd || hwnd == GetDesktopWindow() || (GetWindowLongPtr(hwnd, GWL_EXSTYLE) & WS_EX_LAYERED))
{
return 1;
}

View File

@ -195,24 +195,25 @@ namespace Gdi
void redrawWindow(HWND hwnd, HRGN rgn)
{
if (!IsWindowVisible(hwnd))
if (!IsWindowVisible(hwnd) || IsIconic(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);
if (rgn)
{
ClientToScreen(hwnd, &origin);
OffsetRgn(rgn, -origin.x, -origin.y);
}
RedrawWindow(hwnd, nullptr, rgn, RDW_ERASE | RDW_FRAME | RDW_INVALIDATE | RDW_ALLCHILDREN);
RedrawWindow(hwnd, nullptr, rgn, RDW_ERASENOW);
if (rgn)
{
OffsetRgn(rgn, origin.x, origin.y);
}
}
void unhookWndProc(LPCSTR className, WNDPROC oldWndProc)

View File

@ -6,7 +6,7 @@
namespace Gdi
{
typedef void(*WindowPosChangeNotifyFunc)(HWND, const RECT&, const RECT&);
typedef void(*WindowPosChangeNotifyFunc)();
bool beginGdiRendering(DWORD lockFlags = 0);
void endGdiRendering();

148
DDrawCompat/Gdi/Region.cpp Normal file
View File

@ -0,0 +1,148 @@
#include <utility>
#include "Gdi/Region.h"
#include "Win32/DisplayMode.h"
namespace
{
BOOL CALLBACK addMonitorRectToRegion(
HMONITOR /*hMonitor*/, HDC /*hdcMonitor*/, LPRECT lprcMonitor, LPARAM dwData)
{
Gdi::Region& virtualScreenRegion = *reinterpret_cast<Gdi::Region*>(dwData);
Gdi::Region monitorRegion(*lprcMonitor);
virtualScreenRegion |= monitorRegion;
return TRUE;
}
Gdi::Region calculateVirtualScreenRegion()
{
Gdi::Region region;
EnumDisplayMonitors(nullptr, nullptr, addMonitorRectToRegion, reinterpret_cast<LPARAM>(&region));
return region;
}
Gdi::Region combineRegions(const Gdi::Region& rgn1, const Gdi::Region& rgn2, int mode)
{
Gdi::Region region;
CombineRgn(region, rgn1, rgn2, mode);
return region;
}
}
namespace Gdi
{
Region::Region(const RECT& rect)
: m_region(CreateRectRgnIndirect(&rect))
{
}
Region::~Region()
{
if (m_region)
{
DeleteObject(m_region);
}
}
Region::Region(const Region& other)
: Region()
{
CombineRgn(m_region, other, nullptr, RGN_COPY);
}
Region::Region(Region&& other)
: m_region(other.m_region)
{
other.m_region = nullptr;
}
Region& Region::operator=(Region other)
{
swap(*this, other);
return *this;
}
bool Region::isEmpty() const
{
return sizeof(RGNDATAHEADER) == GetRegionData(m_region, 0, nullptr);
}
void Region::offset(int x, int y)
{
OffsetRgn(m_region, x, y);
}
Region::operator HRGN() const
{
return m_region;
}
Region Region::operator&(const Region& other) const
{
return combineRegions(*this, other, RGN_AND);
}
Region Region::operator|(const Region& other) const
{
return combineRegions(*this, other, RGN_OR);
}
Region Region::operator-(const Region& other) const
{
return combineRegions(*this, other, RGN_DIFF);
}
Region Region::operator&=(const Region& other)
{
return combine(other, RGN_AND);
}
Region Region::operator|=(const Region& other)
{
return combine(other, RGN_OR);
}
Region Region::operator-=(const Region& other)
{
return combine(other, RGN_DIFF);
}
void swap(Region& rgn1, Region& rgn2)
{
std::swap(rgn1.m_region, rgn2.m_region);
}
Region operator&(const Region& rgn1, const Region& rgn2)
{
return combineRegions(rgn1, rgn2, RGN_AND);
}
Region operator|(const Region& rgn1, const Region& rgn2)
{
return combineRegions(rgn1, rgn2, RGN_OR);
}
Region operator-(const Region& rgn1, const Region& rgn2)
{
return combineRegions(rgn1, rgn2, RGN_DIFF);
}
Region& Region::combine(const Region& other, int mode)
{
CombineRgn(m_region, m_region, other, mode);
return *this;
}
const Region& getVirtualScreenRegion()
{
static Region virtualScreenRegion;
static ULONG displaySettingsUniqueness = Win32::DisplayMode::queryDisplaySettingsUniqueness() - 1;
const ULONG currentDisplaySettingsUniqueness = Win32::DisplayMode::queryDisplaySettingsUniqueness();
if (currentDisplaySettingsUniqueness != displaySettingsUniqueness)
{
virtualScreenRegion = calculateVirtualScreenRegion();
displaySettingsUniqueness = currentDisplaySettingsUniqueness;
}
return virtualScreenRegion;
}
}

40
DDrawCompat/Gdi/Region.h Normal file
View File

@ -0,0 +1,40 @@
#pragma once
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
namespace Gdi
{
class Region
{
public:
Region(const RECT& rect = RECT{ 0, 0, 0, 0 });
~Region();
Region(const Region& other);
Region(Region&& other);
Region& operator=(Region other);
bool isEmpty() const;
void offset(int x, int y);
operator HRGN() const;
Region operator&(const Region& other) const;
Region operator|(const Region& other) const;
Region operator-(const Region& other) const;
Region operator&=(const Region& other);
Region operator|=(const Region& other);
Region operator-=(const Region& other);
friend void swap(Region& rgn1, Region& rgn2);
private:
Region& combine(const Region& other, int mode);
HRGN m_region;
};
const Region& getVirtualScreenRegion();
}

View File

@ -1,8 +1,8 @@
#define WIN32_LEAN_AND_MEAN
#include <map>
#include <memory>
#include <set>
#include <unordered_map>
#include <dwmapi.h>
#include <Windows.h>
@ -13,29 +13,22 @@
#include "Gdi/ScrollBar.h"
#include "Gdi/ScrollFunctions.h"
#include "Gdi/TitleBar.h"
#include "Gdi/Window.h"
#include "Gdi/WinProc.h"
namespace
{
struct WindowData
{
RECT wndRect;
std::shared_ptr<HRGN__> sysClipRgn;
};
HHOOK g_callWndRetProcHook = nullptr;
HWINEVENTHOOK g_objectStateChangeEventHook = nullptr;
std::unordered_map<HWND, WindowData> g_windowData;
std::set<Gdi::WindowPosChangeNotifyFunc> g_windowPosChangeNotifyFuncs;
void disableDwmAttributes(HWND hwnd);
void onActivate(HWND hwnd);
void onCreateWindow(HWND hwnd);
void onDestroyWindow(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);
BOOL CALLBACK updateWindowData(HWND hwnd, LPARAM lParam);
LRESULT CALLBACK callWndRetProc(int nCode, WPARAM wParam, LPARAM lParam)
{
@ -46,13 +39,11 @@ namespace
{
if (WM_CREATE == ret->message)
{
disableDwmAttributes(ret->hwnd);
removeDropShadow(ret->hwnd);
onCreateWindow(ret->hwnd);
}
else if (WM_DESTROY == ret->message)
{
Compat::ScopedCriticalSection lock(Gdi::g_gdiCriticalSection);
g_windowData.erase(ret->hwnd);
onDestroyWindow(ret->hwnd);
}
else if (WM_WINDOWPOSCHANGED == ret->message)
{
@ -92,18 +83,15 @@ namespace
&disableTransitions, sizeof(disableTransitions));
}
WindowData getWindowData(HWND hwnd)
BOOL CALLBACK initTopLevelWindow(HWND hwnd, LPARAM /*lParam*/)
{
WindowData data;
if (IsWindowVisible(hwnd) && !(GetWindowLongPtr(hwnd, GWL_EXSTYLE) & WS_EX_LAYERED))
onCreateWindow(hwnd);
if (!(GetWindowLongPtr(hwnd, GWL_EXSTYLE) & WS_EX_LAYERED))
{
GetWindowRect(hwnd, &data.wndRect);
data.sysClipRgn.reset(CreateRectRgnIndirect(&data.wndRect), DeleteObject);
HDC dc = GetWindowDC(hwnd);
GetRandomRgn(dc, data.sysClipRgn.get(), SYSRGN);
ReleaseDC(hwnd, dc);
RedrawWindow(hwnd, nullptr, nullptr,
RDW_INVALIDATE | RDW_ERASE | RDW_FRAME | RDW_ALLCHILDREN | RDW_UPDATENOW);
}
return data;
return TRUE;
}
void CALLBACK objectStateChangeEvent(
@ -164,6 +152,26 @@ namespace
DeleteObject(ncRgn);
}
void onCreateWindow(HWND hwnd)
{
if (Gdi::isTopLevelWindow(hwnd))
{
disableDwmAttributes(hwnd);
removeDropShadow(hwnd);
Compat::ScopedCriticalSection lock(Gdi::g_gdiCriticalSection);
Gdi::Window::add(hwnd);
}
}
void onDestroyWindow(HWND hwnd)
{
if (Gdi::isTopLevelWindow(hwnd))
{
Compat::ScopedCriticalSection lock(Gdi::g_gdiCriticalSection);
Gdi::Window::remove(hwnd);
}
}
void onMenuSelect()
{
HWND menuWindow = FindWindow(reinterpret_cast<LPCSTR>(0x8000), nullptr);
@ -189,67 +197,12 @@ namespace
Compat::ScopedCriticalSection lock(Gdi::g_gdiCriticalSection);
WindowData prevData = g_windowData[hwnd];
EnumThreadWindows(Gdi::getGdiThreadId(), updateWindowData, 0);
WindowData& data = g_windowData[hwnd];
for (auto notifyFunc : g_windowPosChangeNotifyFuncs)
{
notifyFunc(hwnd, prevData.wndRect, data.wndRect);
notifyFunc();
}
if (!prevData.sysClipRgn && !data.sysClipRgn)
{
return;
}
redrawUncoveredRegion(prevData, data);
redrawChangedWindowRegion(hwnd, prevData, data);
}
void redrawChangedWindowRegion(HWND hwnd, const WindowData& prevData, const WindowData& data)
{
if (!data.sysClipRgn)
{
return;
}
if (!prevData.sysClipRgn)
{
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);
Gdi::Window::updateAll();
}
void removeDropShadow(HWND hwnd)
@ -260,12 +213,6 @@ namespace
SetClassLongPtr(hwnd, GCL_STYLE, style ^ CS_DROPSHADOW);
}
}
BOOL CALLBACK updateWindowData(HWND hwnd, LPARAM /*lParam*/)
{
g_windowData[hwnd] = getWindowData(hwnd);
return TRUE;
}
}
namespace Gdi
@ -278,6 +225,8 @@ namespace Gdi
g_callWndRetProcHook = SetWindowsHookEx(WH_CALLWNDPROCRET, callWndRetProc, nullptr, threadId);
g_objectStateChangeEventHook = SetWinEventHook(EVENT_OBJECT_STATECHANGE, EVENT_OBJECT_STATECHANGE,
nullptr, &objectStateChangeEvent, 0, threadId, WINEVENT_OUTOFCONTEXT);
EnumThreadWindows(threadId, initTopLevelWindow, 0);
}
void watchWindowPosChanges(WindowPosChangeNotifyFunc notifyFunc)

128
DDrawCompat/Gdi/Window.cpp Normal file
View File

@ -0,0 +1,128 @@
#include "Gdi/Gdi.h"
#include "Gdi/Window.h"
namespace Gdi
{
Window::Window(HWND hwnd)
: m_hwnd(hwnd)
, m_windowRect{ 0, 0, 0, 0 }
, m_isUpdating(false)
{
update();
}
Window& Window::add(HWND hwnd)
{
auto it = s_windows.find(hwnd);
if (it != s_windows.end())
{
return it->second;
}
return s_windows.emplace(hwnd, hwnd).first->second;
}
void Window::calcInvalidatedRegion(const RECT& oldWindowRect, const Region& oldVisibleRegion)
{
if (IsRectEmpty(&m_windowRect) || m_visibleRegion.isEmpty())
{
m_invalidatedRegion = Region();
return;
}
m_invalidatedRegion = m_visibleRegion;
if (m_windowRect.right - m_windowRect.left == oldWindowRect.right - oldWindowRect.left &&
m_windowRect.bottom - m_windowRect.top == oldWindowRect.bottom - oldWindowRect.top)
{
Region preservedRegion(oldVisibleRegion);
preservedRegion.offset(m_windowRect.left - oldWindowRect.left, m_windowRect.top - oldWindowRect.top);
preservedRegion &= m_visibleRegion;
if (!preservedRegion.isEmpty())
{
HDC screenDc = GetDC(nullptr);
SelectClipRgn(screenDc, preservedRegion);
BitBlt(screenDc, m_windowRect.left, m_windowRect.top,
oldWindowRect.right - oldWindowRect.left, oldWindowRect.bottom - oldWindowRect.top,
screenDc, oldWindowRect.left, oldWindowRect.top, SRCCOPY);
ReleaseDC(nullptr, screenDc);
m_invalidatedRegion -= preservedRegion;
}
}
}
Window& Window::get(HWND hwnd)
{
return add(hwnd);
}
Region Window::getVisibleRegion() const
{
return m_visibleRegion;
}
RECT Window::getWindowRect() const
{
return m_windowRect;
}
void Window::remove(HWND hwnd)
{
s_windows.erase(hwnd);
}
void Window::update()
{
if (m_isUpdating)
{
return;
}
m_isUpdating = true;
RECT newWindowRect = {};
Region newVisibleRegion;
if (IsWindowVisible(m_hwnd) && !IsIconic(m_hwnd))
{
GetWindowRect(m_hwnd, &newWindowRect);
if (!IsRectEmpty(&newWindowRect))
{
HDC windowDc = GetWindowDC(m_hwnd);
GetRandomRgn(windowDc, newVisibleRegion, SYSRGN);
ReleaseDC(m_hwnd, windowDc);
newVisibleRegion &= getVirtualScreenRegion();
}
}
std::swap(m_windowRect, newWindowRect);
swap(m_visibleRegion, newVisibleRegion);
calcInvalidatedRegion(newWindowRect, newVisibleRegion);
m_isUpdating = false;
}
void Window::updateAll()
{
for (auto& windowPair : s_windows)
{
windowPair.second.update();
}
for (auto& windowPair : s_windows)
{
if (!windowPair.second.m_invalidatedRegion.isEmpty())
{
POINT clientOrigin = {};
ClientToScreen(windowPair.first, &clientOrigin);
windowPair.second.m_invalidatedRegion.offset(-clientOrigin.x, -clientOrigin.y);
RedrawWindow(windowPair.first, nullptr, windowPair.second.m_invalidatedRegion,
RDW_INVALIDATE | RDW_ERASE | RDW_FRAME | RDW_ALLCHILDREN | RDW_ERASENOW);
}
}
}
std::map<HWND, Window> Window::s_windows;
}

40
DDrawCompat/Gdi/Window.h Normal file
View File

@ -0,0 +1,40 @@
#pragma once
#define WIN32_LEAN_AND_MEAN
#include <map>
#include <Windows.h>
#include "Gdi/Region.h"
namespace Gdi
{
class Window
{
public:
Window(HWND hwnd);
Window(const Window&) = delete;
Window& operator=(const Window&) = delete;
Region getVisibleRegion() const;
RECT getWindowRect() const;
static Window& add(HWND hwnd);
static Window& get(HWND hwnd);
static void remove(HWND hwnd);
static void updateAll();
private:
void calcInvalidatedRegion(const RECT& oldWindowRect, const Region& oldVisibleRegion);
void update();
HWND m_hwnd;
RECT m_windowRect;
Region m_visibleRegion;
Region m_invalidatedRegion;
bool m_isUpdating;
static std::map<HWND, Window> s_windows;
};
}

View File

@ -372,6 +372,13 @@ namespace Win32
return result;
}
ULONG queryDisplaySettingsUniqueness()
{
static auto ddQueryDisplaySettingsUniqueness = reinterpret_cast<ULONG(APIENTRY*)()>(
GetProcAddress(GetModuleHandle("gdi32"), "GdiEntry13"));
return ddQueryDisplaySettingsUniqueness();
}
void setDDrawBpp(DWORD bpp)
{
g_ddrawBpp = bpp;

View File

@ -13,6 +13,7 @@ namespace Win32
const void* lpbInit, const BITMAPINFO* lpbmi, UINT fuUsage);
HBITMAP WINAPI createDiscardableBitmap(HDC hdc, int nWidth, int nHeight);
ULONG queryDisplaySettingsUniqueness();
void setDDrawBpp(DWORD bpp);
void disableDwm8And16BitMitigation();