From 51ebefeeee2228fe3a20e6985c4901d4e6a160d8 Mon Sep 17 00:00:00 2001
From: Eric Wasylishen <[EMAIL REDACTED]>
Date: Mon, 28 Feb 2022 00:43:43 -0700
Subject: [PATCH] Support PMv2 DPI awareness, add
SDL_HINT_WINDOWS_DPI_AWARENESS
The hint allows setting a specific DPI awareness ("unaware", "system", "permonitor", "permonitorv2").
This is the first part of High-DPI support on Windows ( https://github.com/libsdl-org/SDL/issues/2119 ).
It doesn't implement a virtualized SDL coordinate system, which will be
addressed in a later commit. (This hint could be useful for SDL apps
that want 1 SDL unit = 1 pixel, though.)
Detecting and behaving correctly under per-monitor V2
(calling AdjustWindowRectExForDpi where needed) should fix the
following issues:
https://github.com/libsdl-org/SDL/issues/3286
https://github.com/libsdl-org/SDL/issues/4712
---
include/SDL_hints.h | 30 ++++++
src/video/windows/SDL_windowsevents.c | 136 +++++++++++++++++++++++++-
src/video/windows/SDL_windowsvideo.c | 116 ++++++++++++++++++++++
src/video/windows/SDL_windowsvideo.h | 45 ++++++++-
src/video/windows/SDL_windowswindow.c | 57 +++++++++--
5 files changed, 376 insertions(+), 8 deletions(-)
diff --git a/include/SDL_hints.h b/include/SDL_hints.h
index b5665b91d9f..57417292caa 100644
--- a/include/SDL_hints.h
+++ b/include/SDL_hints.h
@@ -1808,6 +1808,36 @@ extern "C" {
*/
#define SDL_HINT_WINDOWS_USE_D3D9EX "SDL_WINDOWS_USE_D3D9EX"
+/**
+ * \brief Controls whether SDL will declare the process to be DPI aware.
+ *
+ * This hint must be set before initializing the video subsystem.
+ *
+ * The main purpose of declaring DPI awareness is to disable OS bitmap scaling of SDL windows on monitors with
+ * a DPI scale factor.
+ *
+ * This hint is equivalent to requesting DPI awareness via external means (e.g. calling SetProcessDpiAwarenessContext)
+ * and does not cause SDL to use a virtualized coordinate system, so it will generally give you 1 SDL coordinate = 1 pixel
+ * even on high-DPI displays.
+ *
+ * For more information, see:
+ * https://docs.microsoft.com/en-us/windows/win32/hidpi/high-dpi-desktop-application-development-on-windows
+ *
+ * This variable can be set to the following values:
+ * "" - Do not change the DPI awareness (default).
+ * "unaware" - Declare the process as DPI unaware. (Windows 8.1 and later).
+ * "system" - Request system DPI awareness. (Vista and later).
+ * "permonitor" - Request per-monitor DPI awareness. (Windows 8.1 and later).
+ * "permonitorv2" - Request per-monitor V2 DPI awareness. (Windows 10, version 1607 and later).
+ * The most visible difference from "permonitor" is that window title bar will be scaled
+ * to the visually correct size when dragging between monitors with different scale factors.
+ * This is the preferred DPI awareness level.
+ *
+ * If the requested DPI awareness is not available on the currently running OS, SDL will try to request the best
+ * available match.
+ */
+#define SDL_HINT_WINDOWS_DPI_AWARENESS "SDL_WINDOWS_DPI_AWARENESS"
+
/**
* \brief A variable controlling whether the window frame and title bar are interactive when the cursor is hidden
*
diff --git a/src/video/windows/SDL_windowsevents.c b/src/video/windows/SDL_windowsevents.c
index 084bb9a961b..22780ad8489 100644
--- a/src/video/windows/SDL_windowsevents.c
+++ b/src/video/windows/SDL_windowsevents.c
@@ -34,6 +34,7 @@
#include "../../events/SDL_touch_c.h"
#include "../../events/scancodes_windows.h"
#include "SDL_hints.h"
+#include "SDL_log.h"
/* Dropfile support */
#include <shellapi.h>
@@ -52,6 +53,8 @@
#include "wmmsg.h"
#endif
+/* #define HIGHDPI_DEBUG */
+
/* Masks for processing the windows KEYDOWN and KEYUP messages */
#define REPEATED_KEYMASK (1<<30)
#define EXTENDED_KEYMASK (1<<24)
@@ -86,6 +89,12 @@
#ifndef WM_UNICHAR
#define WM_UNICHAR 0x0109
#endif
+#ifndef WM_DPICHANGED
+#define WM_DPICHANGED 0x02E0
+#endif
+#ifndef WM_GETDPISCALEDSIZE
+#define WM_GETDPISCALEDSIZE 0x02E4
+#endif
#ifndef IS_HIGH_SURROGATE
#define IS_HIGH_SURROGATE(x) (((x) >= 0xd800) && ((x) <= 0xdbff))
@@ -1084,7 +1093,12 @@ WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
size.bottom = h;
size.right = w;
- AdjustWindowRectEx(&size, style, menu, 0);
+ if (WIN_IsPerMonitorV2DPIAware(SDL_GetVideoDevice())) {
+ UINT dpi = data->videodata->GetDpiForWindow(hwnd);
+ data->videodata->AdjustWindowRectExForDpi(&size, style, menu, 0, dpi);
+ } else {
+ AdjustWindowRectEx(&size, style, menu, 0);
+ }
w = size.right - size.left;
h = size.bottom - size.top;
}
@@ -1154,6 +1168,12 @@ WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
h = rect.bottom - rect.top;
SDL_SendWindowEvent(data->window, SDL_WINDOWEVENT_RESIZED, w, h);
+#ifdef HIGHDPI_DEBUG
+ SDL_Log("WM_WINDOWPOSCHANGED: Windows client rect (pixels): (%d, %d) (%d x %d)\tSDL client rect: (%d, %d) (%d x %d)\tGetDpiForWindow: %d",
+ rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top,
+ x, y, w, h, data->videodata->GetDpiForWindow ? (int)data->videodata->GetDpiForWindow(data->hwnd) : 0);
+#endif
+
/* Forces a WM_PAINT event */
InvalidateRect(hwnd, NULL, FALSE);
@@ -1399,6 +1419,120 @@ WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
}
}
break;
+
+ case WM_GETDPISCALEDSIZE:
+ /* Windows 10 Creators Update+ */
+ /* Documented as only being sent to windows that are per-monitor V2 DPI aware. */
+ if (data->videodata->GetDpiForWindow && data->videodata->AdjustWindowRectExForDpi) {
+ /* Windows expects applications to scale their window rects linearly
+ when dragging between monitors with different DPI's.
+ e.g. a 100x100 window dragged to a 200% scaled monitor
+ becomes 200x200.
+
+ For SDL, we instead want the client size to scale linearly.
+ This is not the same as the window rect scaling linearly,
+ because Windows doesn't scale the non-client area (titlebar etc.)
+ linearly. So, we need to handle this message to request custom
+ scaling. */
+
+ const int nextDPI = (int)wParam;
+ const int prevDPI = (int)data->videodata->GetDpiForWindow(hwnd);
+ SIZE *sizeInOut = (SIZE *)lParam;
+
+ int frame_w, frame_h;
+ int query_client_w_win, query_client_h_win;
+
+ const DWORD style = GetWindowLong(hwnd, GWL_STYLE);
+ const BOOL menu = (style & WS_CHILDWINDOW) ? FALSE : (GetMenu(hwnd) != NULL);
+
+#ifdef HIGHDPI_DEBUG
+ SDL_Log("WM_GETDPISCALEDSIZE: current DPI: %d potential DPI: %d input size: (%dx%d)",
+ prevDPI, nextDPI, sizeInOut->cx, sizeInOut->cy);
+#endif
+
+ /* Subtract the window frame size that would have been used at prevDPI */
+ {
+ RECT rect = {0};
+
+ if (!(data->window->flags & SDL_WINDOW_BORDERLESS)) {
+ data->videodata->AdjustWindowRectExForDpi(&rect, style, menu, 0, prevDPI);
+ }
+
+ frame_w = -rect.left + rect.right;
+ frame_h = -rect.top + rect.bottom;
+
+ query_client_w_win = sizeInOut->cx - frame_w;
+ query_client_h_win = sizeInOut->cy - frame_h;
+ }
+
+ /* Add the window frame size that would be used at nextDPI */
+ {
+ RECT rect = { 0, 0, query_client_w_win, query_client_h_win };
+
+ if (!(data->window->flags & SDL_WINDOW_BORDERLESS)) {
+ data->videodata->AdjustWindowRectExForDpi(&rect, style, menu, 0, nextDPI);
+ }
+
+ /* This is supposed to control the suggested rect param of WM_DPICHANGED */
+ sizeInOut->cx = rect.right - rect.left;
+ sizeInOut->cy = rect.bottom - rect.top;
+ }
+
+#ifdef HIGHDPI_DEBUG
+ SDL_Log("WM_GETDPISCALEDSIZE: output size: (%dx%d)", sizeInOut->cx, sizeInOut->cy);
+#endif
+ return TRUE;
+ }
+ break;
+
+ case WM_DPICHANGED:
+ /* Windows 8.1+ */
+ {
+ const int newDPI = HIWORD(wParam);
+ RECT* const suggestedRect = (RECT*)lParam;
+ int w, h;
+
+#ifdef HIGHDPI_DEBUG
+ SDL_Log("WM_DPICHANGED: to %d\tsuggested rect: (%d, %d), (%dx%d)\n", newDPI,
+ suggestedRect->left, suggestedRect->top, suggestedRect->right - suggestedRect->left, suggestedRect->bottom - suggestedRect->top);
+#endif
+
+ /* DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 means that
+ WM_GETDPISCALEDSIZE will have been called, so we can use suggestedRect. */
+ if (WIN_IsPerMonitorV2DPIAware(SDL_GetVideoDevice())) {
+ w = suggestedRect->right - suggestedRect->left;
+ h = suggestedRect->bottom - suggestedRect->top;
+ } else {
+ RECT rect = { 0, 0, data->window->w, data->window->h };
+ const DWORD style = GetWindowLong(hwnd, GWL_STYLE);
+ const BOOL menu = (style & WS_CHILDWINDOW) ? FALSE : (GetMenu(hwnd) != NULL);
+
+ if (!(data->window->flags & SDL_WINDOW_BORDERLESS)) {
+ AdjustWindowRectEx(&rect, style, menu, 0);
+ }
+
+ w = rect.right - rect.left;
+ h = rect.bottom - rect.top;
+ }
+
+#ifdef HIGHDPI_DEBUG
+ SDL_Log("WM_DPICHANGED: current SDL window size: (%dx%d)\tcalling SetWindowPos: (%d, %d), (%dx%d)\n",
+ data->window->w, data->window->h,
+ suggestedRect->left, suggestedRect->top, w, h);
+#endif
+
+ data->expected_resize = SDL_TRUE;
+ SetWindowPos(hwnd,
+ NULL,
+ suggestedRect->left,
+ suggestedRect->top,
+ w,
+ h,
+ SWP_NOZORDER | SWP_NOACTIVATE);
+ data->expected_resize = SDL_FALSE;
+ return 0;
+ }
+ break;
}
/* If there's a window proc, assume it's going to handle messages */
diff --git a/src/video/windows/SDL_windowsvideo.c b/src/video/windows/SDL_windowsvideo.c
index 62ad1c96450..7d916bd7a65 100644
--- a/src/video/windows/SDL_windowsvideo.c
+++ b/src/video/windows/SDL_windowsvideo.c
@@ -122,6 +122,16 @@ WIN_CreateDevice(int devindex)
data->CloseTouchInputHandle = (BOOL (WINAPI *)(HTOUCHINPUT)) SDL_LoadFunction(data->userDLL, "CloseTouchInputHandle");
data->GetTouchInputInfo = (BOOL (WINAPI *)(HTOUCHINPUT, UINT, PTOUCHINPUT, int)) SDL_LoadFunction(data->userDLL, "GetTouchInputInfo");
data->RegisterTouchWindow = (BOOL (WINAPI *)(HWND, ULONG)) SDL_LoadFunction(data->userDLL, "RegisterTouchWindow");
+ data->SetProcessDPIAware = (BOOL (WINAPI *)(void)) SDL_LoadFunction(data->userDLL, "SetProcessDPIAware");
+ data->SetProcessDpiAwarenessContext = (BOOL (WINAPI *)(DPI_AWARENESS_CONTEXT)) SDL_LoadFunction(data->userDLL, "SetProcessDpiAwarenessContext");
+ data->SetThreadDpiAwarenessContext = (DPI_AWARENESS_CONTEXT (WINAPI *)(DPI_AWARENESS_CONTEXT)) SDL_LoadFunction(data->userDLL, "SetThreadDpiAwarenessContext");
+ data->GetThreadDpiAwarenessContext = (DPI_AWARENESS_CONTEXT (WINAPI *)(void)) SDL_LoadFunction(data->userDLL, "GetThreadDpiAwarenessContext");
+ data->GetAwarenessFromDpiAwarenessContext = (DPI_AWARENESS (WINAPI *)(DPI_AWARENESS_CONTEXT)) SDL_LoadFunction(data->userDLL, "GetAwarenessFromDpiAwarenessContext");
+ data->EnableNonClientDpiScaling = (BOOL (WINAPI *)(HWND)) SDL_LoadFunction(data->userDLL, "EnableNonClientDpiScaling");
+ data->AdjustWindowRectExForDpi = (BOOL (WINAPI *)(LPRECT, DWORD, BOOL, DWORD, UINT)) SDL_LoadFunction(data->userDLL, "AdjustWindowRectExForDpi");
+ data->GetDpiForWindow = (UINT (WINAPI *)(HWND)) SDL_LoadFunction(data->userDLL, "GetDpiForWindow");
+ data->AreDpiAwarenessContextsEqual = (BOOL (WINAPI *)(DPI_AWARENESS_CONTEXT, DPI_AWARENESS_CONTEXT)) SDL_LoadFunction(data->userDLL, "AreDpiAwarenessContextsEqual");
+ data->IsValidDpiAwarenessContext = (BOOL (WINAPI *)(DPI_AWARENESS_CONTEXT)) SDL_LoadFunction(data->userDLL, "IsValidDpiAwarenessContext");
} else {
SDL_ClearError();
}
@@ -129,6 +139,7 @@ WIN_CreateDevice(int devindex)
data->shcoreDLL = SDL_LoadObject("SHCORE.DLL");
if (data->shcoreDLL) {
data->GetDpiForMonitor = (HRESULT (WINAPI *)(HMONITOR, MONITOR_DPI_TYPE, UINT *, UINT *)) SDL_LoadFunction(data->shcoreDLL, "GetDpiForMonitor");
+ data->SetProcessDpiAwareness = (HRESULT (WINAPI *)(PROCESS_DPI_AWARENESS)) SDL_LoadFunction(data->shcoreDLL, "SetProcessDpiAwareness");
} else {
SDL_ClearError();
}
@@ -233,11 +244,103 @@ VideoBootStrap WINDOWS_bootstrap = {
"windows", "SDL Windows video driver", WIN_CreateDevice
};
+static BOOL
+WIN_DeclareDPIAwareUnaware(_THIS)
+{
+ SDL_VideoData* data = (SDL_VideoData*)_this->driverdata;
+
+ if (data->SetProcessDpiAwarenessContext) {
+ return data->SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_UNAWARE);
+ } else if (data->SetProcessDpiAwareness) {
+ /* Windows 8.1 */
+ return SUCCEEDED(data->SetProcessDpiAwareness(PROCESS_DPI_UNAWARE));
+ }
+ return FALSE;
+}
+
+static BOOL
+WIN_DeclareDPIAwareSystem(_THIS)
+{
+ SDL_VideoData* data = (SDL_VideoData*)_this->driverdata;
+
+ if (data->SetProcessDpiAwarenessContext) {
+ /* Windows 10, version 1607 */
+ return data->SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_SYSTEM_AWARE);
+ } else if (data->SetProcessDpiAwareness) {
+ /* Windows 8.1 */
+ return SUCCEEDED(data->SetProcessDpiAwareness(PROCESS_SYSTEM_DPI_AWARE));
+ } else if (data->SetProcessDPIAware) {
+ /* Windows Vista */
+ return data->SetProcessDPIAware();
+ }
+ return FALSE;
+}
+
+static BOOL
+WIN_DeclareDPIAwarePerMonitor(_THIS)
+{
+ SDL_VideoData *data = (SDL_VideoData *) _this->driverdata;
+
+ if (data->SetProcessDpiAwarenessContext) {
+ /* Windows 10, version 1607 */
+ return data->SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE);
+ } else if (data->SetProcessDpiAwareness) {
+ /* Windows 8.1 */
+ return SUCCEEDED(data->SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE));
+ } else {
+ /* Older OS: fall back to system DPI aware */
+ return WIN_DeclareDPIAwareSystem(_this);
+ }
+ return FALSE;
+}
+
+static BOOL
+WIN_DeclareDPIAwarePerMonitorV2(_THIS)
+{
+ SDL_VideoData* data = (SDL_VideoData*)_this->driverdata;
+
+ /* Declare DPI aware(may have been done in external code or a manifest, as well) */
+ if (data->SetProcessDpiAwarenessContext) {
+ /* Windows 10, version 1607 */
+
+ /* NOTE: SetThreadDpiAwarenessContext doesn't work here with OpenGL - the OpenGL contents
+ end up still getting OS scaled. (tested on Windows 10 21H1 19043.1348, NVIDIA 496.49) */
+ if (data->SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)) {
+ return TRUE;
+ } else {
+ return WIN_DeclareDPIAwarePerMonitor(_this);
+ }
+ } else {
+ /* Older OS: fall back to per-monitor (or system) */
+ return WIN_DeclareDPIAwarePerMonitor(_this);
+ }
+}
+
+static void
+WIN_InitDPIAwareness(_THIS)
+{
+ const char* hint = SDL_GetHint(SDL_HINT_WINDOWS_DPI_AWARENESS);
+
+ if (hint != NULL) {
+ if (SDL_strcmp(hint, "permonitorv2") == 0) {
+ WIN_DeclareDPIAwarePerMonitorV2(_this);
+ } else if (SDL_strcmp(hint, "permonitor") == 0) {
+ WIN_DeclareDPIAwarePerMonitor(_this);
+ } else if (SDL_strcmp(hint, "system") == 0) {
+ WIN_DeclareDPIAwareSystem(_this);
+ } else if (SDL_strcmp(hint, "unaware") == 0) {
+ WIN_DeclareDPIAwareUnaware(_this);
+ }
+ }
+}
+
int
WIN_VideoInit(_THIS)
{
SDL_VideoData *data = (SDL_VideoData *) _this->driverdata;
+ WIN_InitDPIAwareness(_this);
+
if (WIN_InitModes(_this) < 0) {
return -1;
}
@@ -473,6 +576,19 @@ SDL_DXGIGetOutputInfo(int displayIndex, int *adapterIndex, int *outputIndex)
#endif
}
+SDL_bool
+WIN_IsPerMonitorV2DPIAware(_THIS)
+{
+ SDL_VideoData* data = (SDL_VideoData*) _this->driverdata;
+
+ if (data->AreDpiAwarenessContextsEqual && data->GetThreadDpiAwarenessContext) {
+ /* Windows 10, version 1607 */
+ return (SDL_bool)data->AreDpiAwarenessContextsEqual(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2,
+ data->GetThreadDpiAwarenessContext());
+ }
+ return SDL_FALSE;
+}
+
#endif /* SDL_VIDEO_DRIVER_WINDOWS */
/* vim: set ts=4 sw=4 expandtab: */
diff --git a/src/video/windows/SDL_windowsvideo.h b/src/video/windows/SDL_windowsvideo.h
index d4208c40500..1a42b8010ac 100644
--- a/src/video/windows/SDL_windowsvideo.h
+++ b/src/video/windows/SDL_windowsvideo.h
@@ -86,10 +86,40 @@ typedef enum MONITOR_DPI_TYPE {
MDT_DEFAULT = MDT_EFFECTIVE_DPI
} MONITOR_DPI_TYPE;
+typedef enum PROCESS_DPI_AWARENESS {
+ PROCESS_DPI_UNAWARE = 0,
+ PROCESS_SYSTEM_DPI_AWARE = 1,
+ PROCESS_PER_MONITOR_DPI_AWARE = 2
+} PROCESS_DPI_AWARENESS;
+
#else
#include <shellscalingapi.h>
#endif /* WINVER < 0x0603 */
+#ifndef _DPI_AWARENESS_CONTEXTS_
+
+typedef enum DPI_AWARENESS {
+ DPI_AWARENESS_INVALID = -1,
+ DPI_AWARENESS_UNAWARE = 0,
+ DPI_AWARENESS_SYSTEM_AWARE = 1,
+ DPI_AWARENESS_PER_MONITOR_AWARE = 2
+} DPI_AWARENESS;
+
+DECLARE_HANDLE(DPI_AWARENESS_CONTEXT);
+
+#define DPI_AWARENESS_CONTEXT_UNAWARE ((DPI_AWARENESS_CONTEXT)-1)
+#define DPI_AWARENESS_CONTEXT_SYSTEM_AWARE ((DPI_AWARENESS_CONTEXT)-2)
+#define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE ((DPI_AWARENESS_CONTEXT)-3)
+
+#endif /* _DPI_AWARENESS_CONTEXTS_ */
+
+/* Windows 10 Creators Update */
+#if NTDDI_VERSION < 0x0A000003
+
+#define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 ((DPI_AWARENESS_CONTEXT)-4)
+
+#endif /* NTDDI_VERSION < 0x0A000003 */
+
typedef BOOL (*PFNSHFullScreen)(HWND, DWORD);
typedef void (*PFCoordTransform)(SDL_Window*, POINT*);
@@ -137,13 +167,24 @@ typedef struct SDL_VideoData
BOOL (WINAPI *CloseTouchInputHandle)( HTOUCHINPUT );
BOOL (WINAPI *GetTouchInputInfo)( HTOUCHINPUT, UINT, PTOUCHINPUT, int );
BOOL (WINAPI *RegisterTouchWindow)( HWND, ULONG );
+ BOOL (WINAPI *SetProcessDPIAware)( void );
+ BOOL (WINAPI *SetProcessDpiAwarenessContext)( DPI_AWARENESS_CONTEXT );
+ DPI_AWARENESS_CONTEXT (WINAPI *SetThreadDpiAwarenessContext)( DPI_AWARENESS_CONTEXT );
+ DPI_AWARENESS_CONTEXT (WINAPI *GetThreadDpiAwarenessContext)( void );
+ DPI_AWARENESS (WINAPI *GetAwarenessFromDpiAwarenessContext)( DPI_AWARENESS_CONTEXT );
+ BOOL (WINAPI *EnableNonClientDpiScaling)( HWND );
+ BOOL (WINAPI *AdjustWindowRectExForDpi)( LPRECT, DWORD, BOOL, DWORD, UINT );
+ UINT (WINAPI *GetDpiForWindow)( HWND );
+ BOOL (WINAPI *AreDpiAwarenessContextsEqual)(DPI_AWARENESS_CONTEXT, DPI_AWARENESS_CONTEXT);
+ BOOL (WINAPI *IsValidDpiAwarenessContext)(DPI_AWARENESS_CONTEXT);
void* shcoreDLL;
HRESULT (WINAPI *GetDpiForMonitor)( HMONITOR hmonitor,
MONITOR_DPI_TYPE dpiType,
UINT *dpiX,
UINT *dpiY );
-
+ HRESULT (WINAPI *SetProcessDpiAwareness)(PROCESS_DPI_AWARENESS dpiAwareness);
+
SDL_bool ime_com_initialized;
struct ITfThreadMgr *ime_threadmgr;
SDL_bool ime_initialized;
@@ -203,6 +244,8 @@ extern SDL_bool g_WindowFrameUsableWhileCursorHidden;
typedef struct IDirect3D9 IDirect3D9;
extern SDL_bool D3D_LoadDLL( void **pD3DDLL, IDirect3D9 **pDirect3D9Interface );
+extern SDL_bool WIN_IsPerMonitorV2DPIAware(_THIS);
+
#endif /* SDL_windowsvideo_h_ */
/* vi: set ts=4 sw=4 expandtab: */
diff --git a/src/video/windows/SDL_windowswindow.c b/src/video/windows/SDL_windowswindow.c
index 6608a5f9838..e2289f3adeb 100644
--- a/src/video/windows/SDL_windowswindow.c
+++ b/src/video/windows/SDL_windowswindow.c
@@ -114,8 +114,11 @@ GetWindowStyle(SDL_Window * window)
}
static void
-WIN_AdjustWindowRectWithStyle(SDL_Window *window, DWORD style, BOOL menu, int *x, int *y, int *width, int *height, SDL_bool use_current)
+WIN_AdjustWindowRectWithStyle(SDL_Window *window, DWORD style, BOOL menu, int *x, int *y, int *width, int *height, SDL_bool use_current,
+ SDL_bool force_ignore_window_dpi)
{
+ SDL_WindowData *data = (SDL_WindowData *)window->driverdata;
+ SDL_VideoData* videodata = SDL_GetVideoDevice() ? SDL_GetVideoDevice()->driverdata : NULL;
RECT rect;
rect.left = 0;
@@ -126,8 +129,44 @@ WIN_AdjustWindowRectWithStyle(SDL_Window *window, DWORD style, BOOL menu, int *x
/* borderless windows will have WM_NCCALCSIZE return 0 for the non-client area. When this happens, it looks like windows will send a resize message
expanding the window client area to the previous window + chrome size, so shouldn't need to adjust the window size for the set styles.
*/
- if (!(window->flags & SDL_WINDOW_BORDERLESS))
- AdjustWindowRectEx(&rect, style, menu, 0);
+ if (!(window->flags & SDL_WINDOW_BORDERLESS)) {
+ if (WIN_IsPerMonitorV2DPIAware(SDL_GetVideoDevice())) {
+ /* With per-monitor v2, the window border/titlebar size depend on the DPI, so we need to call AdjustWindowRectExForDpi instead of
+ AdjustWindowRectEx. */
+ UINT dpi;
+
+ if (data && !force_ignore_window_dpi) {
+ /* The usual case - we have a HWND, so we can look up the DPI to use. */
+ dpi = videodata->GetDpiForWindow(data->hwnd);
+ } else {
+ /* In this case we guess the window DPI based on its rectangle on the screen.
+
+ This happens at creation time of an SDL window, before we have a HWND,
+ and also in a bug workaround (when force_ignore_window_dpi is SDL_TRUE
+ - see WIN_SetWindowFullscreen).
+ */
+ UINT unused;
+ RECT screen_rect;
+ HMONITOR mon;
+
+ screen_rect.left = (use_current ? window->x : window->windowed.x);
+ screen_rect.top = (use_current ? window->y : window->windowed.y);
+ screen_rect.right = screen_rect.left + (use_current ? window->w : window->windowed.w);
+ screen_rect.bottom = screen_rect.top + (use_current ? window->h : window->windowed.h);
+
+ mon = MonitorFromRect(&screen_rect, MONITOR_DEFAULTTONEAREST);
+
+ /* GetDpiForMonitor docs promise to return the same hdpi / vdpi */
+ if (videodata->GetDpiForMonitor(mon, MDT_EFFECTIVE_DPI, &dpi, &unused) != S_OK) {
+ dpi = 96;
+ }
+ }
+
+ videodata->AdjustWindowRectExForDpi(&rect, style, menu, 0, dpi);
+ } else {
+ AdjustWindowRectEx(&rect, style, menu, 0);
+ }
+ }
*x = (use_current ? window->x : window->windowed.x) + rect.left;
*y = (use_current ? window->y : window->windowed.y) + rect.top;
@@ -145,7 +184,7 @@ WIN_AdjustWindowRect(SDL_Window *window, int *x, int *y, int *width, int *height
style = GetWindowLong(hwnd, GWL_STYLE);
menu = (style & WS_CHILDWINDOW) ? FALSE : (GetMenu(hwnd) != NULL);
- WIN_AdjustWindowRectWithStyle(window, style, menu, x, y, width, height, use_current);
+ WIN_AdjustWindowRectWithStyle(window, style, menu, x, y, width, height, use_current, SDL_FALSE);
}
static void
@@ -356,7 +395,7 @@ WIN_CreateWindow(_THIS, SDL_Window * window)
style |= GetWindowStyle(window);
/* Figure out what the window area will be */
- WIN_AdjustWindowRectWithStyle(window, style, FALSE, &x, &y, &w, &h, SDL_FALSE);
+ WIN_AdjustWindowRectWithStyle(window, style, FALSE, &x, &y, &w, &h, SDL_FALSE, SDL_FALSE);
hwnd =
CreateWindow(SDL_Appname, TEXT(""), style, x, y, w, h, parent, NULL,
@@ -781,7 +820,13 @@ WIN_SetWindowFullscreen(_THIS, SDL_Window * window, SDL_VideoDisplay * display,
}
menu = (style & WS_CHILDWINDOW) ? FALSE : (GetMenu(hwnd) != NULL);
- WIN_AdjustWindowRectWithStyle(window, style, menu, &x, &y, &w, &h, SDL_FALSE);
+ /* HighDPI bug workaround - when leaving exclusive fullscreen, the window DPI reported
+ by GetDpiForWindow will be wrong. Pass SDL_TRUE for `force_ignore_window_dpi`
+ makes us recompute the DPI based on the monitor we are restoring onto.
+ Fixes windows shrinking slightly when going from exclusive fullscreen to windowed
+ on a HighDPI monitor with scaling.
+ */
+ WIN_AdjustWindowRectWithStyle(window, style, menu, &x, &y, &w, &h, SDL_FALSE, SDL_TRUE);
}
SetWindowLong(hwnd, GWL_STYLE, style);
data->expected_resize = SDL_TRUE;