From ab81a559f43abc0858c96788f8e00bbb352287e8 Mon Sep 17 00:00:00 2001
From: Eric Wasylishen <[EMAIL REDACTED]>
Date: Tue, 7 Jun 2022 02:01:27 -0600
Subject: [PATCH] Windows DPI scaling/highdpi support
Adds hint "SDL_WINDOWS_DPI_SCALING" which can be set to "1" to
change the SDL coordinate system units to be DPI-scaled points, rather
than pixels everywhere.
This means windows will be appropriately sized, even when created on
high-DPI displays with scaling.
e.g. requesting a 640x480 window from SDL, on a display with 125%
scaling in Windows display settings, will create a window with an
800x600 client area (in pixels).
Setting this to "1" implicitly requests process DPI awareness
(setting SDL_WINDOWS_DPI_AWARENESS is unnecessary),
and forces SDL_WINDOW_ALLOW_HIGHDPI on all windows.
---
include/SDL_hints.h | 21 ++
src/render/direct3d/SDL_render_d3d.c | 12 +-
src/render/direct3d11/SDL_render_d3d11.c | 11 +-
src/video/SDL_video.c | 7 +-
src/video/windows/SDL_windowsevents.c | 49 +++-
src/video/windows/SDL_windowsmodes.c | 291 +++++++++++++++++++++++
src/video/windows/SDL_windowsmodes.h | 2 +
src/video/windows/SDL_windowsmouse.c | 2 +
src/video/windows/SDL_windowsopengl.c | 7 +
src/video/windows/SDL_windowsopengl.h | 1 +
src/video/windows/SDL_windowsopengles.c | 9 +
src/video/windows/SDL_windowsopengles.h | 1 +
src/video/windows/SDL_windowsvideo.c | 16 ++
src/video/windows/SDL_windowsvideo.h | 2 +
src/video/windows/SDL_windowswindow.c | 221 ++++++++++++++---
src/video/windows/SDL_windowswindow.h | 8 +
16 files changed, 625 insertions(+), 35 deletions(-)
diff --git a/include/SDL_hints.h b/include/SDL_hints.h
index 57417292caa..a45614b8bf7 100644
--- a/include/SDL_hints.h
+++ b/include/SDL_hints.h
@@ -1838,6 +1838,27 @@ extern "C" {
*/
#define SDL_HINT_WINDOWS_DPI_AWARENESS "SDL_WINDOWS_DPI_AWARENESS"
+ /**
+ * \brief Uses DPI-scaled points as the SDL coordinate system on Windows.
+ *
+ * This changes the SDL coordinate system units to be DPI-scaled points, rather than pixels everywhere.
+ * This means windows will be appropriately sized, even when created on high-DPI displays with scaling.
+ *
+ * e.g. requesting a 640x480 window from SDL, on a display with 125% scaling in Windows display settings,
+ * will create a window with an 800x600 client area (in pixels).
+ *
+ * Setting this to "1" implicitly requests process DPI awareness (setting SDL_WINDOWS_DPI_AWARENESS is unnecessary),
+ * and forces SDL_WINDOW_ALLOW_HIGHDPI on all windows.
+ *
+ * This variable can be set to the following values:
+ * "0" - SDL coordinates equal Windows coordinates. No automatic window resizing when dragging
+ * between monitors with different scale factors (unless this is performed by
+ * Windows itself, which is the case when the process is DPI unaware).
+ * "1" - SDL coordinates are in DPI-scaled points. Automatically resize windows as needed on
+ * displays with non-100% scale factors.
+ */
+#define SDL_HINT_WINDOWS_DPI_SCALING "SDL_WINDOWS_DPI_SCALING"
+
/**
* \brief A variable controlling whether the window frame and title bar are interactive when the cursor is hidden
*
diff --git a/src/render/direct3d/SDL_render_d3d.c b/src/render/direct3d/SDL_render_d3d.c
index 8c68d3e69a4..2708d2d0036 100644
--- a/src/render/direct3d/SDL_render_d3d.c
+++ b/src/render/direct3d/SDL_render_d3d.c
@@ -308,7 +308,7 @@ D3D_ActivateRenderer(SDL_Renderer * renderer)
int w, h;
Uint32 window_flags = SDL_GetWindowFlags(window);
- SDL_GetWindowSize(window, &w, &h);
+ WIN_GetDrawableSize(window, &w, &h);
data->pparams.BackBufferWidth = w;
data->pparams.BackBufferHeight = h;
if (window_flags & SDL_WINDOW_FULLSCREEN && (window_flags & SDL_WINDOW_FULLSCREEN_DESKTOP) != SDL_WINDOW_FULLSCREEN_DESKTOP) {
@@ -354,6 +354,13 @@ D3D_WindowEvent(SDL_Renderer * renderer, const SDL_WindowEvent *event)
}
}
+static int
+D3D_GetOutputSize(SDL_Renderer * renderer, int *w, int *h)
+{
+ WIN_GetDrawableSize(renderer->window, w, h);
+ return 0;
+}
+
static D3DBLEND
GetBlendFunc(SDL_BlendFactor factor)
{
@@ -1616,6 +1623,7 @@ D3D_CreateRenderer(SDL_Window * window, Uint32 flags)
}
renderer->WindowEvent = D3D_WindowEvent;
+ renderer->GetOutputSize = D3D_GetOutputSize;
renderer->SupportsBlendMode = D3D_SupportsBlendMode;
renderer->CreateTexture = D3D_CreateTexture;
renderer->UpdateTexture = D3D_UpdateTexture;
@@ -1645,7 +1653,7 @@ D3D_CreateRenderer(SDL_Window * window, Uint32 flags)
SDL_GetWindowWMInfo(window, &windowinfo);
window_flags = SDL_GetWindowFlags(window);
- SDL_GetWindowSize(window, &w, &h);
+ WIN_GetDrawableSize(window, &w, &h);
SDL_GetWindowDisplayMode(window, &fullscreen_mode);
SDL_zero(pparams);
diff --git a/src/render/direct3d11/SDL_render_d3d11.c b/src/render/direct3d11/SDL_render_d3d11.c
index 02f4c258f03..72b9ee3a109 100644
--- a/src/render/direct3d11/SDL_render_d3d11.c
+++ b/src/render/direct3d11/SDL_render_d3d11.c
@@ -27,6 +27,7 @@
#define COBJMACROS
#include "../../core/windows/SDL_windows.h"
+#include "../../video/windows/SDL_windowswindow.h"
#include "SDL_hints.h"
#include "SDL_loadso.h"
#include "SDL_syswm.h"
@@ -910,7 +911,7 @@ D3D11_CreateWindowSizeDependentResources(SDL_Renderer * renderer)
/* The width and height of the swap chain must be based on the display's
* non-rotated size.
*/
- SDL_GetWindowSize(renderer->window, &w, &h);
+ WIN_GetDrawableSize(renderer->window, &w, &h);
data->rotation = D3D11_GetCurrentRotation();
/* SDL_Log("%s: windowSize={%d,%d}, orientation=%d\n", __FUNCTION__, w, h, (int)data->rotation); */
if (D3D11_IsDisplayRotated90Degrees(data->rotation)) {
@@ -1051,6 +1052,13 @@ D3D11_WindowEvent(SDL_Renderer * renderer, const SDL_WindowEvent *event)
}
}
+static int
+D3D11_GetOutputSize(SDL_Renderer * renderer, int *w, int *h)
+{
+ WIN_GetDrawableSize(renderer->window, w, h);
+ return 0;
+}
+
static SDL_bool
D3D11_SupportsBlendMode(SDL_Renderer * renderer, SDL_BlendMode blendMode)
{
@@ -2366,6 +2374,7 @@ D3D11_CreateRenderer(SDL_Window * window, Uint32 flags)
data->identity = MatrixIdentity();
renderer->WindowEvent = D3D11_WindowEvent;
+ renderer->GetOutputSize = D3D11_GetOutputSize;
renderer->SupportsBlendMode = D3D11_SupportsBlendMode;
renderer->CreateTexture = D3D11_CreateTexture;
renderer->UpdateTexture = D3D11_UpdateTexture;
diff --git a/src/video/SDL_video.c b/src/video/SDL_video.c
index 58f9d746659..084ece803a6 100644
--- a/src/video/SDL_video.c
+++ b/src/video/SDL_video.c
@@ -1374,12 +1374,17 @@ SDL_UpdateFullscreenMode(SDL_Window * window, SDL_bool fullscreen)
/* Generate a mode change event here */
if (resized) {
-#ifndef ANDROID
+#if !defined(ANDROID) && !defined(WIN32)
/* Android may not resize the window to exactly what our fullscreen mode is, especially on
* windowed Android environments like the Chromebook or Samsung DeX. Given this, we shouldn't
* use fullscreen_mode.w and fullscreen_mode.h, but rather get our current native size. As such,
* Android's SetWindowFullscreen will generate the window event for us with the proper final size.
*/
+
+ /* This is also unnecessary on Win32 (WIN_SetWindowFullscreen calls SetWindowPos,
+ * WM_WINDOWPOSCHANGED will send SDL_WINDOWEVENT_RESIZED). Also, on Windows with DPI scaling enabled,
+ * we're keeping modes in pixels, but window sizes in dpi-scaled points, so this would be a unit mismatch.
+ */
SDL_SendWindowEvent(other, SDL_WINDOWEVENT_RESIZED,
fullscreen_mode.w, fullscreen_mode.h);
#endif
diff --git a/src/video/windows/SDL_windowsevents.c b/src/video/windows/SDL_windowsevents.c
index 6daaf451f24..69f6d14a24f 100644
--- a/src/video/windows/SDL_windowsevents.c
+++ b/src/video/windows/SDL_windowsevents.c
@@ -749,7 +749,12 @@ WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
/* Only generate mouse events for real mouse */
if (GetMouseMessageSource() != SDL_MOUSE_EVENT_SOURCE_TOUCH &&
lParam != data->last_pointer_update) {
- SDL_SendMouseMotion(data->window, 0, 0, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
+ int x = GET_X_LPARAM(lParam);
+ int y = GET_Y_LPARAM(lParam);
+
+ WIN_ClientPointToSDL(data->window, &x, &y);
+
+ SDL_SendMouseMotion(data->window, 0, 0, x, y);
}
}
}
@@ -1068,6 +1073,12 @@ WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
SDL_GetWindowMinimumSize(data->window, &min_w, &min_h);
SDL_GetWindowMaximumSize(data->window, &max_w, &max_h);
+ /* Convert w, h, min_w, min_h, max_w, max_h from dpi-scaled points to pixels,
+ treating them as coordinates within the client area. */
+ WIN_ClientPointFromSDL(data->window, &w, &h);
+ WIN_ClientPointFromSDL(data->window, &min_w, &min_h);
+ WIN_ClientPointFromSDL(data->window, &max_w, &max_h);
+
/* Store in min_w and min_h difference between current size and minimal
size so we don't need to call AdjustWindowRectEx twice */
min_w -= w;
@@ -1168,16 +1179,21 @@ WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
x = rect.left;
y = rect.top;
+ WIN_ScreenPointToSDL(&x, &y);
+
SDL_SendWindowEvent(data->window, SDL_WINDOWEVENT_MOVED, x, y);
+ /* Convert client area width/height from pixels to dpi-scaled points */
w = rect.right - rect.left;
h = rect.bottom - rect.top;
+ WIN_ClientPointToSDL(data->window, &w, &h);
+
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);
+ SDL_Log("WM_WINDOWPOSCHANGED: Windows client rect (pixels): (%d, %d) (%d x %d)\tSDL client rect (points): (%d, %d) (%d x %d) cached dpi %d, windows reported dpi %d",
+ rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top,
+ x, y, w, h, data->scaling_dpi, data->videodata->GetDpiForWindow ? data->videodata->GetDpiForWindow(data->hwnd) : 0);
#endif
/* Forces a WM_PAINT event */
@@ -1385,6 +1401,7 @@ WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
NCCALCSIZE_PARAMS *params = (NCCALCSIZE_PARAMS *)lParam;
w = data->window->windowed.w;
h = data->window->windowed.h;
+ WIN_ClientPointFromSDL(data->window, &w, &h);
params->rgrc[0].right = params->rgrc[0].left + w;
params->rgrc[0].bottom = params->rgrc[0].top + h;
}
@@ -1405,6 +1422,7 @@ WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
SDL_HitTestResult rc;
point.x = winpoint.x;
point.y = winpoint.y;
+ WIN_ClientPointToSDL(data->window, &point.x, &point.y);
rc = window->hit_test(window, &point, window->hit_test_data);
switch (rc) {
#define POST_HIT_TEST(ret) { SDL_SendWindowEvent(data->window, SDL_WINDOWEVENT_HIT_TEST, 0, 0); return ret; }
@@ -1474,6 +1492,14 @@ WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
query_client_h_win = sizeInOut->cy - frame_h;
}
+ /* Convert to new dpi if we are using scaling.
+ * Otherwise leave as pixels.
+ */
+ if (data->videodata->dpi_scaling_enabled) {
+ query_client_w_win = MulDiv(query_client_w_win, nextDPI, prevDPI);
+ query_client_h_win = MulDiv(query_client_h_win, nextDPI, prevDPI);
+ }
+
/* Add the window frame size that would be used at nextDPI */
{
RECT rect = {0};
@@ -1508,6 +1534,15 @@ WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
suggestedRect->left, suggestedRect->top, suggestedRect->right - suggestedRect->left, suggestedRect->bottom - suggestedRect->top);
#endif
+ if (data->videodata->dpi_scaling_enabled) {
+ /* Update the cached DPI value for this window */
+ data->scaling_dpi = newDPI;
+
+ /* Send a SDL_WINDOWEVENT_SIZE_CHANGED saying that the client size (in dpi-scaled points) is unchanged.
+ Renderers need to get this to know that the framebuffer size changed. */
+ SDL_SendWindowEvent(data->window, SDL_WINDOWEVENT_SIZE_CHANGED, data->window->w, data->window->h);
+ }
+
if (data->expected_resize) {
/* This DPI change is coming from an explicit SetWindowPos call within SDL.
Assume all call sites are calculating the DPI-aware frame correctly, so
@@ -1539,6 +1574,12 @@ WIN_WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
rect.right = data->window->w;
rect.bottom = data->window->h;
+ if (data->videodata->dpi_scaling_enabled) {
+ /* scale client size to from points to the new DPI */
+ rect.right = MulDiv(rect.right, newDPI, 96);
+ rect.bottom = MulDiv(rect.bottom, newDPI, 96);
+ }
+
if (!(data->window->flags & SDL_WINDOW_BORDERLESS)) {
AdjustWindowRectEx(&rect, style, menu, 0);
}
diff --git a/src/video/windows/SDL_windowsmodes.c b/src/video/windows/SDL_windowsmodes.c
index 9080cc89552..03c2d4aa59b 100644
--- a/src/video/windows/SDL_windowsmodes.c
+++ b/src/video/windows/SDL_windowsmodes.c
@@ -31,6 +31,7 @@
#endif
/* #define DEBUG_MODES */
+/* #define HIGHDPI_DEBUG_VERBOSE */
static void
WIN_UpdateDisplayMode(_THIS, LPCWSTR deviceName, DWORD index, SDL_DisplayMode * mode)
@@ -50,6 +51,15 @@ WIN_UpdateDisplayMode(_THIS, LPCWSTR deviceName, DWORD index, SDL_DisplayMode *
int logical_width = GetDeviceCaps( hdc, HORZRES );
int logical_height = GetDeviceCaps( hdc, VERTRES );
+ /* High-DPI notes:
+
+ If DPI-unaware:
+ - GetDeviceCaps( hdc, HORZRES ) will return the monitor width in points.
+ - DeviceMode.dmPelsWidth is actual pixels (unlike almost all other Windows API's,
+ it's not virtualized when DPI unaware).
+
+ If DPI-aware:
+ - GetDeviceCaps( hdc, HORZRES ) will return pixels, same as DeviceMode.dmPelsWidth */
mode->w = logical_width;
mode->h = logical_height;
@@ -301,10 +311,46 @@ WIN_InitModes(_THIS)
return 0;
}
+/**
+ * Convert the monitor rect and work rect from pixels to the SDL coordinate system (monitor origins are in pixels,
+ * monitor size in DPI-scaled points).
+ *
+ * No-op if DPI scaling is not enabled.
+ */
+static void
+WIN_MonitorInfoToSDL(const SDL_VideoData *videodata, HMONITOR monitor, MONITORINFO *info)
+{
+ UINT xdpi, ydpi;
+
+ if (!videodata->dpi_scaling_enabled) {
+ return;
+ }
+
+ /* Check for Windows < 8.1*/
+ if (!videodata->GetDpiForMonitor) {
+ return;
+ }
+ if (videodata->GetDpiForMonitor(monitor, MDT_EFFECTIVE_DPI, &xdpi, &ydpi) != S_OK) {
+ /* Shouldn't happen? */
+ return;
+ }
+
+ /* Convert monitor size to points, leaving the monitor position in pixels */
+ info->rcMonitor.right = info->rcMonitor.left + MulDiv(info->rcMonitor.right - info->rcMonitor.left, 96, xdpi);
+ info->rcMonitor.bottom = info->rcMonitor.top + MulDiv(info->rcMonitor.bottom - info->rcMonitor.top, 96, ydpi);
+
+ /* Convert monitor work rect to points */
+ info->rcWork.left = info->rcMonitor.left + MulDiv(info->rcWork.left - info->rcMonitor.left, 96, xdpi);
+ info->rcWork.right = info->rcMonitor.left + MulDiv(info->rcWork.right - info->rcMonitor.left, 96, xdpi);
+ info->rcWork.top = info->rcMonitor.top + MulDiv(info->rcWork.top - info->rcMonitor.top, 96, ydpi);
+ info->rcWork.bottom = info->rcMonitor.top + MulDiv(info->rcWork.bottom - info->rcMonitor.top, 96, ydpi);
+}
+
int
WIN_GetDisplayBounds(_THIS, SDL_VideoDisplay * display, SDL_Rect * rect)
{
const SDL_DisplayData *data = (const SDL_DisplayData *)display->driverdata;
+ const SDL_VideoData *videodata = (SDL_VideoData *)display->device->driverdata;
MONITORINFO minfo;
BOOL rc;
@@ -316,6 +362,7 @@ WIN_GetDisplayBounds(_THIS, SDL_VideoDisplay * display, SDL_Rect * rect)
return SDL_SetError("Couldn't find monitor data");
}
+ WIN_MonitorInfoToSDL(videodata, data->MonitorHandle, &minfo);
rect->x = minfo.rcMonitor.left;
rect->y = minfo.rcMonitor.top;
rect->w = minfo.rcMonitor.right - minfo.rcMonitor.left;
@@ -387,6 +434,7 @@ int
WIN_GetDisplayUsableBounds(_THIS, SDL_VideoDisplay * display, SDL_Rect * rect)
{
const SDL_DisplayData *data = (const SDL_DisplayData *)display->driverdata;
+ const SDL_VideoData *videodata = (SDL_VideoData *)display->device->driverdata;
MONITORINFO minfo;
BOOL rc;
@@ -398,6 +446,7 @@ WIN_GetDisplayUsableBounds(_THIS, SDL_VideoDisplay * display, SDL_Rect * rect)
return SDL_SetError("Couldn't find monitor data");
}
+ WIN_MonitorInfoToSDL(videodata, data->MonitorHandle, &minfo);
rect->x = minfo.rcWork.left;
rect->y = minfo.rcWork.top;
rect->w = minfo.rcWork.right - minfo.rcWork.left;
@@ -406,6 +455,190 @@ WIN_GetDisplayUsableBounds(_THIS, SDL_VideoDisplay * display, SDL_Rect * rect)
return 0;
}
+/**
+ * If x, y are outside of rect, snaps them to the closest point inside rect
+ * (between rect->x, rect->y, inclusive, and rect->x + w, rect->y + h, exclusive)
+ */
+static void
+SDL_GetClosestPointOnRect(const SDL_Rect *rect, int *x, int *y)
+{
+ const int right = rect->x + rect->w - 1;
+ const int bottom = rect->y + rect->h - 1;
+
+ if (*x < rect->x) {
+ *x = rect->x;
+ } else if (*x > right) {
+ *x = right;
+ }
+
+ if (*y < rect->y) {
+ *y = rect->y;
+ } else if (*y > bottom) {
+ *y = bottom;
+ }
+}
+
+/**
+ * Returns the display index of the display which either encloses the given point
+ * or is closest to it. The point is in SDL screen coordinates.
+ */
+static int
+SDL_GetPointDisplayIndex(int x, int y)
+{
+ int i, dist;
+ int closest = -1;
+ int closest_dist = 0x7FFFFFFF;
+ SDL_Point closest_point_on_display;
+ SDL_Point delta;
+ SDL_Rect rect;
+ SDL_VideoDevice *_this = SDL_GetVideoDevice();
+ SDL_Point point;
+ point.x = x;
+ point.y = y;
+
+ for (i = 0; i < _this->num_displays; ++i) {
+ /* Check for an exact match */
+ SDL_GetDisplayBounds(i, &rect);
+ if (SDL_EnclosePoints(&point, 1, &rect, NULL)) {
+ return i;
+ }
+
+ /* Snap x, y to the display rect */
+ closest_point_on_display = point;
+ SDL_GetClosestPointOnRect(&rect, &closest_point_on_display.x, &closest_point_on_display.y);
+
+ delta.x = point.x - closest_point_on_display.x;
+ delta.y = point.y - closest_point_on_display.y;
+ dist = (delta.x*delta.x + delta.y*delta.y);
+ if (dist < closest_dist) {
+ closest = i;
+ closest_dist = dist;
+ }
+ }
+ if (closest < 0) {
+ SDL_SetError("Couldn't find any displays");
+ }
+ return closest;
+}
+
+/**
+ * Convert a point from the SDL coordinate system (monitor origins are in pixels,
+ * offset within a monitor in DPI-scaled points) to Windows virtual screen coordinates (pixels).
+ *
+ * No-op if DPI scaling is not enabled (returns 96 dpi).
+ *
+ * Returns the DPI of the monitor that was closest to x, y and used for the conversion.
+ */
+void WIN_ScreenPointFromSDL(int *x, int *y, int *dpiOut)
+{
+ const SDL_VideoDevice *videodevice = SDL_GetVideoDevice();
+ const SDL_VideoData *videodata;
+ int displayIndex;
+ SDL_Rect bounds;
+ float ddpi, hdpi, vdpi;
+ int x_sdl, y_sdl;
+
+ if (dpiOut) {
+ *dpiOut = 96;
+ }
+
+ if (!videodevice || !videodevice->driverdata) {
+ return;
+ }
+
+ videodata = (SDL_VideoData *)videodevice->driverdata;
+ if (!videodata->dpi_scaling_enabled) {
+ return;
+ }
+
+ /* Can't use MonitorFromPoint for this because we currently have SDL coordinates, not pixels */
+ displayIndex = SDL_GetPointDisplayIndex(*x, *y);
+
+ if (displayIndex < 0) {
+ return;
+ }
+
+ if (SDL_GetDisplayBounds(displayIndex, &bounds) < 0
+ || SDL_GetDisplayDPI(displayIndex, &ddpi, &hdpi, &vdpi) < 0) {
+ return;
+ }
+
+ if (dpiOut) {
+ *dpiOut = (int) ddpi;
+ }
+
+ /* Undo the DPI-scaling within the monitor bounds to convert back to pixels */
+ x_sdl = *x;
+ y_sdl = *y;
+ *x = bounds.x + MulDiv(x_sdl - bounds.x, (int)ddpi, 96);
+ *y = bounds.y + MulDiv(y_sdl - bounds.y, (int)ddpi, 96);
+
+#ifdef HIGHDPI_DEBUG_VERBOSE
+ SDL_Log("WIN_ScreenPointFromSDL: (%d, %d) points -> (%d x %d) pixels, using %d DPI monitor",
+ x_sdl, y_sdl, *x, *y, (int)ddpi);
+#endif
+}
+
+/**
+ * Convert a point from Windows virtual screen coordinates (pixels) to the SDL
+ * coordinate system (monitor origins are in pixels, offset within a monitor in DPI-scaled points).
+ *
+ * No-op if DPI scaling is not enabled.
+ */
+void WIN_ScreenPointToSDL(int *x, int *y)
+{
+ const SDL_VideoDevice *videodevice = SDL_GetVideoDevice();
+ const SDL_VideoData *videodata;
+ POINT point;
+ HMONITOR monitor;
+ int i, displayIndex;
+ SDL_Rect bounds;
+ float ddpi, hdpi, vdpi;
+ int x_pixels, y_pixels;
+
+ if (!videodevice || !videodevice->driverdata) {
+ return;
+ }
+
+ videodata = (SDL_VideoData *)videodevice->driverdata;
+ if (!videodata->dpi_scaling_enabled) {
+ return;
+ }
+
+ point.x = *x;
+ point.y = *y;
+ monitor = MonitorFromPoint(point, MONITOR_DEFAULTTONEAREST);
+
+ /* Search for the corresponding SDL monitor */
+ displayIndex = -1;
+ for (i = 0; i < videodevice->num_displays; ++i) {
+ SDL_DisplayData *driverdata = (SDL_DisplayData *)videodevice->displays[i].driverdata;
+ if (driverdata->MonitorHandle == monitor) {
+ displayIndex = i;
+ }
+ }
+ if (displayIndex == -1) {
+ return;
+ }
+
+ /* Get SDL display properties */
+ if (SDL_GetDisplayBounds(displayIndex, &bounds) < 0
+ || SDL_GetDisplayDPI(displayIndex, &ddpi, &hdpi, &vdpi) < 0) {
+ return;
+ }
+
+ /* Convert the point's offset within the monitor from pixels to DPI-scaled points */
+ x_pixels = *x;
+ y_pixels = *y;
+ *x = bounds.x + MulDiv(x_pixels - bounds.x, 96, (int)ddpi);
+ *y = bounds.y + MulDiv(y_pixels - bounds.y, 96, (int)ddpi);
+
+#ifdef HIGHDPI_DEBUG_VERBOSE
+ SDL_Log("WIN_ScreenPointToSDL: (%d, %d) pixels -> (%d x %d) points, using %d DPI monitor",
+ x_pixels, y_pixels, *x, *y, (int)ddpi);
+#endif
+}
+
void
WIN_GetDisplayModes(_THIS, SDL_VideoDisplay * display)
{
@@ -432,6 +665,37 @@ WIN_GetDisplayModes(_THIS, SDL_VideoDisplay * display)
}
}
+#ifdef DEBUG_MODES
+static void
+WIN_LogMonitor(_THIS, HMONITOR mon)
+{
+ const SDL_VideoData *vid_data = (const SDL_VideoData *)_this->driverdata;
+ MONITORINFOEX minfo;
+ UINT xdpi = 0, ydpi = 0;
+ char *name_utf8;
+
+ if (vid_data->GetDpiForMonitor) {
+ vid_data->GetDpiForMonitor(mon, MDT_EFFECTIVE_DPI, &xdpi, &ydpi);
+ }
+
+ SDL_zero(minfo);
+ minfo.cbSize = sizeof(minfo);
+ GetMonitorInfo(mon, (LPMONITORINFO)&minfo);
+
+ name_utf8 = WIN_StringToUTF8(minfo.szDevice);
+
+ SDL_Log("WIN_LogMonitor: monitor \"%s\": dpi: %d windows screen coordinates: %d, %d, %dx%d",
+ name_utf8,
+ xdpi,
+ minfo.rcMonitor.left,
+ minfo.rcMonitor.top,
+ minfo.rcMonitor.right - minfo.rcMonitor.left,
+ minfo.rcMonitor.bottom - minfo.rcMonitor.top);
+
+ SDL_free(name_utf8);
+}
+#endif
+
int
WIN_SetDisplayMode(_THIS, SDL_VideoDisplay * display, SDL_DisplayMode * mode)
{
@@ -439,9 +703,30 @@ WIN_SetDisplayMode(_THIS, SDL_VideoDisplay * display, SDL_DisplayMode * mode)
SDL_DisplayModeData *data = (SDL_DisplayModeData *) mode->driverdata;
LONG status;
+#ifdef DEBUG_MODES
+ SDL_Log("WIN_SetDisplayMode: monitor state before mode change:");
+ WIN_LogMonitor(_this, displaydata->MonitorHandle);
+#endif
+
+ /* High-DPI notes:
+
+ - ChangeDisplaySettingsEx always takes pixels.
+ - e.g. if the display is set to 2880x1800 with 200% scaling in Display Settings
+ - calling ChangeDisplaySettingsEx with a dmPelsWidth/Height other than 2880x1800 will
+ change the monitor DPI to 96. (100% scaling)
+ - calling ChangeDisplaySettingsEx with a dmPelsWidth/Height of 2880x1800 (or a NULL DEVMODE*) will
+ reset the monitor DPI to 192. (200% scaling)
+
+ NOTE: these are temporary changes in DPI, not modifications to the Control Panel setting. */
if (mode->driverdata == display->desktop_mode.driverdata) {
+#ifdef DEBUG_MODES
+ SDL_Log("WIN_SetDisplayMode: resetting to original resolution");
+#endif
status = ChangeDisplaySettingsExW(displaydata->DeviceName, NULL, NULL, CDS_FULLSCREEN, NULL);
} else {
+#ifdef DEBUG_MODES
+ SDL_Log("WIN_SetDisplayMode: changing to %dx%d pixels", data->DeviceMode.dmPelsWidth, data->DeviceMode.dmPelsHeight);
+#endif
status = ChangeDisplaySettingsExW(displaydata->DeviceName, &data->DeviceMode, NULL, CDS_FULLSCREEN, NULL);
}
if (status != DISP_CHANGE_SUCCESSFUL) {
@@ -462,6 +747,12 @@ WIN_SetDisplayMode(_THIS, SDL_VideoDisplay * display, SDL_DisplayMode * mode)
}
return SDL_SetError("ChangeDisplaySettingsEx() failed: %s", reason);
}
+
+#ifdef DEBUG_MODES
+ SDL_Log("WIN_SetDisplayMode: monitor state after mode change:");
+ WIN_LogMonitor(_this, displaydata->MonitorHandle);
+#endif
+
EnumDisplaySettingsW(displaydata->DeviceName, ENUM_CURRENT_SETTINGS, &data->DeviceMode);
WIN_UpdateDisplayMode(_this, displaydata->DeviceName, ENUM_CURRENT_SETTINGS, mode);
return 0;
diff --git a/src/video/windows/SDL_windowsmodes.h b/src/video/windows/SDL_windowsmodes.h
index 0f2cdf4891d..6bf62e58693 100644
--- a/src/video/windows/SDL_windowsmodes.h
+++ b/src/video/windows/SDL_windowsmodes.h
@@ -38,6 +38,8 @@ typedef struct
extern int WIN_InitModes(_THIS);
extern int WIN_GetDisplayBounds(_THIS, SDL_VideoDisplay * display, SDL_Rect * rect);
extern int WIN_GetDisplayUsableBounds(_THIS, SDL_VideoDisplay * display, SDL_Rect * rect);
+extern void WIN_ScreenPointFromSDL(int *x, int *y, int *dpiOut);
+extern void WIN_ScreenPointToSDL(int *x, int *y);
extern int WIN_GetDisplayDPI(_THIS, SDL_VideoDisplay * display, float * ddpi, float * hdpi, float * vdpi);
extern void WIN_GetDisplayModes(_THIS, SDL_VideoDisplay * display);
extern int WIN_SetDisplayMode(_THIS, SDL_VideoDisplay * display, SDL_DisplayMode * mode);
diff --git a/src/video/windows/SDL_windowsmouse.c b/src/video/windows/SDL_windowsmouse.c
index bc4ba6326f7..b13b75faf76 100644
--- a/src/video/windows/SDL_windowsmouse.c
+++ b/src/video/windows/SDL_windowsmouse.c
@@ -290,6 +290,7 @@ WIN_WarpMouseGlobal(int x, int y)
{
POINT pt;
+ WIN_ScreenPointFromSDL(&x, &y, NULL);
pt.x = x;
pt.y = y;
SetCursorPos(pt.x, pt.y);
@@ -333,6 +334,7 @@ WIN_GetGlobalMouseState(int *x, int *y)
GetCursorPos(&pt);
*x = (int) pt.x;
*y = (int) pt.y;
+ WIN_ScreenPointToSDL(x, y);
retval |= GetAsyncKeyState(!swapButtons ? VK_LBUTTON : VK_RBUTTON) & 0x8000 ? SDL_BUTTON_LMASK : 0;
retval |= GetAsyncKeyState(!swapButtons ? VK_RBUTTON : VK_LBUTTON) & 0x8000 ? SDL_BUTTON_RMASK : 0;
diff --git a/src/video/windows/SDL_windowsopengl.c b/src/video/windows/SDL_windowsopengl.c
index 0c99ca9a61e..a35d9728d51 100644
--- a/src/video/windows/SDL_windowsopengl.c
+++ b/src/video/windows/SDL_windowsopengl.c
@@ -676,6 +676,7 @@ WIN_GL_CreateContext(_THIS, SDL_Window * window)
_this->GL_UnloadLibrary = WIN_GLES_UnloadLibrary;
_this->GL_CreateContext = WIN_GLES_CreateContext;
_this->GL_MakeCurrent = WIN_GLES_MakeCurrent;
+ _this->GL_GetDrawableSize = WIN_GLES_GetDrawableSize;
_this->GL_SetSwapInterval = WIN_GLES_SetSwapInterval;
_this->GL_GetSwapInterval = WIN_GLES_GetSwapInterval;
_this->GL_SwapWindow = WIN_GLES_SwapWindow;
@@ -822,6 +823,12 @@ WIN_GL_MakeCurrent(_THIS, SDL_Window * window, SDL_GLContext context)
return 0;
}
+void
+WIN_GL_GetDrawableSize(_THIS, SDL_Window *window, int *w, int *h)
+{
+ WIN_GetDrawableSize(window, w, h);
+}
+
int
WIN_GL_SetSwapInterval(_THIS, int interval)
{
diff --git a/src/video/windows/SDL_windowsopengl.h b/src/video/windows/SDL_windowsopengl.h
index 2dec0023a42..36779aa4a3a 100644
--- a/src/video/windows/SDL_windowsopengl.h
+++ b/src/video/windows/SDL_windowsopengl.h
@@ -71,6 +71,7 @@ extern int WIN_GL_SetupWindow(_THIS, SDL_Window * window);
extern SDL_GLContext WIN_GL_CreateContext(_THIS, SDL_Window * window);
extern int WIN_GL_MakeCurrent(_THIS, SDL_Window * window,
SDL_GLContext context);
+extern void WIN_GL_GetDrawableSize(_THIS, SDL_Window *window, int *w, int *h);
extern int WIN_GL_SetSwapInterval(_THIS, int interval);
extern int WIN_GL_GetSwapInterval(_THIS);
extern int WIN_GL_SwapWindow(_THIS, SDL_Window * window);
diff --git a/src/video/windows/SDL_windowsopengles.c b/src/video/windows/SDL_windowsopengles.c
index eedc0e6abde..493f1aff3f7 100644
--- a/src/video/windows/SDL_windowsopengles.c
+++ b/src/video/windows/SDL_windowsopengles.c
@@ -25,6 +25,7 @@
#include "SDL_windowsvideo.h"
#include "SDL_windowsopengles.h"
#include "SDL_windowsopengl.h"
+#include "SDL_windowswindow.h"
/* EGL implementation of SDL OpenGL support */
@@ -40,6 +41,7 @@ WIN_GLES_LoadLibrary(_THIS, const char *path) {
_this->GL_UnloadLibrary = WIN_GL_UnloadLibrary;
_this->GL_CreateContext = WIN_GL_CreateContext;
_this->GL_MakeCurrent = WIN_GL_MakeCurrent;
+ _this->GL_GetDrawableSize = WIN_GL_GetDrawableSize;
_this->GL_SetSwapInterval = WIN_GL_SetSwapInterval;
_this->GL_GetSwapInterval = WIN_GL_GetSwapInterval;
_this->GL_SwapWindow = WIN_GL_SwapWindow;
@@ -72,6 +74,7 @@ WIN_GLES_CreateContext(_THIS, SDL_Window * window)
_this->GL_UnloadLibrary = WIN_GL_UnloadLibrary;
_this->GL_CreateContext = WIN_GL_CreateContext;
_this->GL_MakeCurrent = WIN_GL_MakeCurrent;
+ _this->GL_GetDraw
(Patch may be truncated, please check the link at the top of this post.)