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.
This commit is contained in:
Eric Wasylishen
2022-06-07 02:01:27 -06:00
committed by Sam Lantinga
parent df36f926fc
commit ab81a559f4
16 changed files with 624 additions and 34 deletions

View File

@@ -24,6 +24,7 @@
#include "../../core/windows/SDL_windows.h"
#include "SDL_log.h"
#include "../SDL_sysvideo.h"
#include "../SDL_pixels_c.h"
#include "../../events/SDL_keyboard_c.h"
@@ -115,19 +116,45 @@ GetWindowStyle(SDL_Window * window)
return style;
}
/**
* Returns arguments to pass to SetWindowPos - the window rect, including frame, in Windows coordinates.
*
* Can be called before we have a HWND.
*/
static void
WIN_AdjustWindowRectWithStyle(SDL_Window *window, DWORD style, BOOL menu, int *x, int *y, int *width, int *height, SDL_bool use_current)
{
SDL_WindowData *data = (SDL_WindowData *)window->driverdata;
SDL_VideoData* videodata = SDL_GetVideoDevice() ? SDL_GetVideoDevice()->driverdata : NULL;
RECT rect;
UINT dpi;
int dpi;
UINT frame_dpi;
dpi = 96;
/* Client rect, in SDL screen coordinates */
*x = (use_current ? window->x : window->windowed.x);
*y = (use_current ? window->y : window->windowed.y);
*width = (use_current ? window->w : window->windowed.w);
*height = (use_current ? window->h : window->windowed.h);
/* Convert client rect from SDL coordinates to pixels (no-op if DPI scaling not enabled) */
WIN_ScreenPointFromSDL(x, y, &dpi);
/* Note, use the guessed DPI returned from WIN_ScreenPointFromSDL rather than the cached one in
data->scaling_dpi.
- This is called before the window is created, so we can't rely on data->scaling_dpi
- Bug workaround: when leaving exclusive fullscreen, the cached DPI and window DPI reported
by GetDpiForWindow will be wrong, and would cause windows shrinking slightly when
going from exclusive fullscreen to windowed on a HighDPI monitor with scaling if we used them.
*/
*width = MulDiv(*width, dpi, 96);
*height = MulDiv(*height, dpi, 96);
/* Copy the client size in pixels into this rect structure,
which we'll then adjust with AdjustWindowRectEx */
rect.left = 0;
rect.top = 0;
rect.right = (use_current ? window->w : window->windowed.w);
rect.bottom = (use_current ? window->h : window->windowed.h);
rect.right = *width;
rect.bottom = *height;
/* 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.
@@ -141,26 +168,27 @@ WIN_AdjustWindowRectWithStyle(SDL_Window *window, DWORD style, BOOL menu, int *x
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);
screen_rect.left = *x;
screen_rect.top = *y;
screen_rect.right = *x + *width;
screen_rect.bottom = *y + *height;
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;
if (videodata->GetDpiForMonitor(mon, MDT_EFFECTIVE_DPI, &frame_dpi, &unused) != S_OK) {
frame_dpi = 96;
}
videodata->AdjustWindowRectExForDpi(&rect, style, menu, 0, dpi);
videodata->AdjustWindowRectExForDpi(&rect, style, menu, 0, frame_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;
/* Final rect in Windows screen space, including the frame */
*x += rect.left;
*y += rect.top;
*width = (rect.right - rect.left);
*height = (rect.bottom - rect.top);
@@ -170,7 +198,7 @@ WIN_AdjustWindowRectWithStyle(SDL_Window *window, DWORD style, BOOL menu, int *x
(use_current ? window->y : window->windowed.y),
(use_current ? window->w : window->windowed.w),
(use_current ? window->h : window->windowed.h),
*x, *y, *width, *height, dpi);
*x, *y, *width, *height, frame_dpi);
#endif
}
@@ -217,6 +245,43 @@ WIN_MouseRelativeModeCenterChanged(void *userdata, const char *name, const char
data->mouse_relative_mode_center = SDL_GetStringBoolean(hint, SDL_TRUE);
}
static int
WIN_GetScalingDPIForHWND(const SDL_VideoData *videodata, HWND hwnd)
{
/* DPI scaling not requested? */
if (!videodata->dpi_scaling_enabled) {
return 96;
}
/* Window 10+ */
if (videodata->GetDpiForWindow) {
return videodata->GetDpiForWindow(hwnd);
}
/* Window 8.1+ */
if (videodata->GetDpiForMonitor) {
HMONITOR monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
if (monitor) {
UINT dpi_uint, unused;
if (S_OK == videodata->GetDpiForMonitor(monitor, MDT_EFFECTIVE_DPI, &dpi_uint, &unused)) {
return (int)dpi_uint;
}
}
return 96;
}
/* Windows Vista-8.0 */
{
HDC hdc = GetDC(NULL);
if (hdc) {
int dpi = GetDeviceCaps(hdc, LOGPIXELSX);
ReleaseDC(NULL, hdc);
return dpi;
}
return 96;
}
}
static int
SetupWindowData(_THIS, SDL_Window * window, HWND hwnd, HWND parent, SDL_bool created)
{
@@ -239,6 +304,11 @@ SetupWindowData(_THIS, SDL_Window * window, HWND hwnd, HWND parent, SDL_bool cre
data->last_pointer_update = (LPARAM)-1;
data->videodata = videodata;
data->initializing = SDL_TRUE;
data->scaling_dpi = WIN_GetScalingDPIForHWND(videodata, hwnd);
#ifdef HIGHDPI_DEBUG
SDL_Log("SetupWindowData: initialized data->scaling_dpi to %d", data->scaling_dpi);
#endif
SDL_AddHintCallback(SDL_HINT_MOUSE_RELATIVE_MODE_CENTER, WIN_MouseRelativeModeCenterChanged, data);
@@ -274,6 +344,8 @@ SetupWindowData(_THIS, SDL_Window * window, HWND hwnd, HWND parent, SDL_bool cre
if (GetClientRect(hwnd, &rect)) {
int w = rect.right;
int h = rect.bottom;
WIN_ClientPointToSDL(window, &w, &h);
if ((window->windowed.w && window->windowed.w != w) || (window->windowed.h && window->windowed.h != h)) {
/* We tried to create a window larger than the desktop and Windows didn't allow it. Override! */
int x, y;
@@ -293,8 +365,11 @@ SetupWindowData(_THIS, SDL_Window * window, HWND hwnd, HWND parent, SDL_bool cre
point.x = 0;
point.y = 0;
if (ClientToScreen(hwnd, &point)) {
window->x = point.x;
window->y = point.y;
int x = point.x;
int y = point.y;
WIN_ScreenPointToSDL(&x, &y);
window->x = x;
window->y = y;
}
}
{
@@ -342,6 +417,11 @@ SetupWindowData(_THIS, SDL_Window * window, HWND hwnd, HWND parent, SDL_bool cre
videodata->RegisterTouchWindow(hwnd, (TWF_FINETOUCH|TWF_WANTPALM));
}
/* Force the SDL_WINDOW_ALLOW_HIGHDPI window flag if we are doing DPI scaling */
if (videodata->dpi_scaling_enabled) {
window->flags |= SDL_WINDOW_ALLOW_HIGHDPI;
}
data->initializing = SDL_FALSE;
/* All done! */
@@ -767,12 +847,17 @@ WIN_RestoreWindow(_THIS, SDL_Window * window)
data->expected_resize = SDL_FALSE;
}
/**
* Reconfigures the window to fill the given display, if fullscreen is true, otherwise restores the window.
*/
void
WIN_SetWindowFullscreen(_THIS, SDL_Window * window, SDL_VideoDisplay * display, SDL_bool fullscreen)
{
SDL_DisplayData *displaydata = (SDL_DisplayData *) display->driverdata;
SDL_WindowData *data = (SDL_WindowData *) window->driverdata;
SDL_VideoData *videodata = data->videodata;
HWND hwnd = data->hwnd;
SDL_Rect bounds;
MONITORINFO minfo;
DWORD style;
HWND top;
int x, y;
@@ -785,6 +870,15 @@ WIN_SetWindowFullscreen(_THIS, SDL_Window * window, SDL_VideoDisplay * display,
return;
}
#ifdef HIGHDPI_DEBUG
SDL_Log("WIN_SetWindowFullscreen: %d", (int)fullscreen);
#endif
/* Clear the window size, to force SDL_SendWindowEvent to send a SDL_WINDOWEVENT_RESIZED
event in WM_WINDOWPOSCHANGED. */
data->window->w = 0;
data->window->h = 0;
if (SDL_ShouldAllowTopmost() && (window->flags & SDL_WINDOW_ALWAYS_ON_TOP)) {
top = HWND_TOPMOST;
} else {
@@ -795,13 +889,20 @@ WIN_SetWindowFullscreen(_THIS, SDL_Window * window, SDL_VideoDisplay * display,
style &= ~STYLE_MASK;
style |= GetWindowStyle(window);
WIN_GetDisplayBounds(_this, display, &bounds);
/* Use GetMonitorInfo instead of WIN_GetDisplayBounds because we want the
monitor bounds in Windows coordinates (pixels) rather than SDL coordinates (points). */
SDL_zero(minfo);
minfo.cbSize = sizeof(MONITORINFO);
if (!GetMonitorInfo(displaydata->MonitorHandle, &minfo)) {
SDL_SetError("GetMonitorInfo failed");
return;
}
if (fullscreen) {
x = bounds.x;
y = bounds.y;
w = bounds.w;
h = bounds.h;
x = minfo.rcMonitor.left;
y = minfo.rcMonitor.top;
w = minfo.rcMonitor.right - minfo.rcMonitor.left;
h = minfo.rcMonitor.bottom - minfo.rcMonitor.top;
/* Unset the maximized flag. This fixes
https://bugzilla.libsdl.org/show_bug.cgi?id=3215
@@ -831,6 +932,10 @@ WIN_SetWindowFullscreen(_THIS, SDL_Window * window, SDL_VideoDisplay * display,
data->expected_resize = SDL_TRUE;
SetWindowPos(hwnd, top, x, y, w, h, SWP_NOCOPYBITS | SWP_NOACTIVATE);
data->expected_resize = SDL_FALSE;
#ifdef HIGHDPI_DEBUG
SDL_Log("WIN_SetWindowFullscreen: %d finished. Set window to %d,%d, %dx%d", (int)fullscreen, x, y, w, h);
#endif
}
int
@@ -1122,12 +1227,19 @@ WIN_UpdateClipCursor(SDL_Window *window)
ClientToScreen(data->hwnd, (LPPOINT) & rect);
ClientToScreen(data->hwnd, (LPPOINT) & rect + 1);
if (window->mouse_rect.w > 0 && window->mouse_rect.h > 0) {
SDL_Rect mouse_rect_win_client;
RECT mouse_rect, intersection;
mouse_rect.left = rect.left + window->mouse_rect.x;
mouse_rect.top = rect.top + window->mouse_rect.y;
mouse_rect.right = mouse_rect.left + window->mouse_rect.w - 1;
mouse_rect.bottom = mouse_rect.top + window->mouse_rect.h - 1;
/* mouse_rect_win_client is the mouse rect in Windows client space */
mouse_rect_win_client = window->mouse_rect;
WIN_ClientPointFromSDL(window, &mouse_rect_win_client.x, &mouse_rect_win_client.y);
WIN_ClientPointFromSDL(window, &mouse_rect_win_client.w, &mouse_rect_win_client.h);
/* mouse_rect is the rect in Windows screen space */
mouse_rect.left = rect.left + mouse_rect_win_client.x;
mouse_rect.top = rect.top + mouse_rect_win_client.y;
mouse_rect.right = mouse_rect.left + mouse_rect_win_client.w - 1;
mouse_rect.bottom = mouse_rect.top + mouse_rect_win_client.h - 1;
if (IntersectRect(&intersection, &rect, &mouse_rect)) {
SDL_memcpy(&rect, &intersection, sizeof(rect));
} else if ((window->flags & SDL_WINDOW_MOUSE_GRABBED) != 0) {
@@ -1203,6 +1315,61 @@ WIN_SetWindowOpacity(_THIS, SDL_Window * window, float opacity)
return 0;
}
/**
* Returns the drawable size in pixels (GetClientRect).
*/
void
WIN_GetDrawableSize(const SDL_Window *window, int *w, int *h)
{
const SDL_WindowData *data = ((SDL_WindowData *)window->driverdata);
HWND hwnd = data->hwnd;
RECT rect;
if (GetClientRect(hwnd, &rect)) {
*w = rect.right;
*h = rect.bottom;
} else {
*w = 0;
*h = 0;
}
}
/**
* Convert a point in the client area from pixels to DPI-scaled points.
*
* No-op if DPI scaling is not enabled.
*/
void
WIN_ClientPointToSDL(const SDL_Window *window, int *x, int *y)
{
const SDL_WindowData *data = ((SDL_WindowData *)window->driverdata);
const SDL_VideoData *videodata = data->videodata;
if (!videodata->dpi_scaling_enabled)
return;
*x = MulDiv(*x, 96, data->scaling_dpi);
*y = MulDiv(*y, 96, data->scaling_dpi);
}
/**
* Convert a point in the client area from DPI-scaled points to pixels.
*
* No-op if DPI scaling is not enabled.
*/
void
WIN_ClientPointFromSDL(const SDL_Window *window, int *x, int *y)
{
const SDL_WindowData *data = ((SDL_WindowData *)window->driverdata);
const SDL_VideoData *videodata = data->videodata;
if (!videodata->dpi_scaling_enabled)
return;
*x = MulDiv(*x, data->scaling_dpi, 96);
*y = MulDiv(*y, data->scaling_dpi, 96);
}
void
WIN_AcceptDragAndDrop(SDL_Window * window, SDL_bool accept)
{