Commit 51ebefeeee2228fe3a20e6985c4901d4e6a160d8

Eric Wasylishen 2022-02-28T00:43:43

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
diff --git a/include/SDL_hints.h b/include/SDL_hints.h
index b5665b9..5741729 100644
--- a/include/SDL_hints.h
+++ b/include/SDL_hints.h
@@ -1809,6 +1809,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 
  *
  *  This variable can be set to the following values:
diff --git a/src/video/windows/SDL_windowsevents.c b/src/video/windows/SDL_windowsevents.c
index 084bb9a..22780ad 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 62ad1c9..7d916bd 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 d4208c4..1a42b80 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 6608a5f..e2289f3 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;