From ab81a559f43abc0858c96788f8e00bbb352287e8 Mon Sep 17 00:00:00 2001 From: Eric Wasylishen 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 | 219 +++++++++++++++-- src/video/windows/SDL_windowswindow.h | 8 + 16 files changed, 624 insertions(+), 34 deletions(-) diff --git a/include/SDL_hints.h b/include/SDL_hints.h index 57417292c..a45614b8b 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 8c68d3e69..2708d2d00 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 02f4c258f..72b9ee3a1 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 58f9d7466..084ece803 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 6daaf451f..69f6d14a2 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 9080cc895..03c2d4aa5 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 0f2cdf489..6bf62e586 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 bc4ba6326..b13b75faf 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 0c99ca9a6..a35d9728d 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 2dec0023a..36779aa4a 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 eedc0e6ab..493f1aff3 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_GetDrawableSize = WIN_GL_GetDrawableSize; _this->GL_SetSwapInterval = WIN_GL_SetSwapInterval; _this->GL_GetSwapInterval = WIN_GL_GetSwapInterval; _this->GL_SwapWindow = WIN_GL_SwapWindow; @@ -99,6 +102,12 @@ WIN_GLES_DeleteContext(_THIS, SDL_GLContext context) SDL_EGL_SwapWindow_impl(WIN) SDL_EGL_MakeCurrent_impl(WIN) +void +WIN_GLES_GetDrawableSize(_THIS, SDL_Window* window, int* w, int* h) +{ + WIN_GetDrawableSize(window, w, h); +} + int WIN_GLES_SetupWindow(_THIS, SDL_Window * window) { diff --git a/src/video/windows/SDL_windowsopengles.h b/src/video/windows/SDL_windowsopengles.h index b21c56f4f..e59c02ebd 100644 --- a/src/video/windows/SDL_windowsopengles.h +++ b/src/video/windows/SDL_windowsopengles.h @@ -39,6 +39,7 @@ extern int WIN_GLES_LoadLibrary(_THIS, const char *path); extern SDL_GLContext WIN_GLES_CreateContext(_THIS, SDL_Window * window); extern int WIN_GLES_SwapWindow(_THIS, SDL_Window * window); extern int WIN_GLES_MakeCurrent(_THIS, SDL_Window * window, SDL_GLContext context); +extern void WIN_GLES_GetDrawableSize(_THIS, SDL_Window* window, int* w, int* h); extern void WIN_GLES_DeleteContext(_THIS, SDL_GLContext context); extern int WIN_GLES_SetupWindow(_THIS, SDL_Window * window); diff --git a/src/video/windows/SDL_windowsvideo.c b/src/video/windows/SDL_windowsvideo.c index 203a08b0b..5358e1d8a 100644 --- a/src/video/windows/SDL_windowsvideo.c +++ b/src/video/windows/SDL_windowsvideo.c @@ -203,6 +203,7 @@ WIN_CreateDevice(int devindex) device->GL_UnloadLibrary = WIN_GL_UnloadLibrary; device->GL_CreateContext = WIN_GL_CreateContext; device->GL_MakeCurrent = WIN_GL_MakeCurrent; + device->GL_GetDrawableSize = WIN_GL_GetDrawableSize; device->GL_SetSwapInterval = WIN_GL_SetSwapInterval; device->GL_GetSwapInterval = WIN_GL_GetSwapInterval; device->GL_SwapWindow = WIN_GL_SwapWindow; @@ -214,6 +215,7 @@ WIN_CreateDevice(int devindex) device->GL_UnloadLibrary = WIN_GLES_UnloadLibrary; device->GL_CreateContext = WIN_GLES_CreateContext; device->GL_MakeCurrent = WIN_GLES_MakeCurrent; + device->GL_GetDrawableSize = WIN_GLES_GetDrawableSize; device->GL_SetSwapInterval = WIN_GLES_SetSwapInterval; device->GL_GetSwapInterval = WIN_GLES_GetSwapInterval; device->GL_SwapWindow = WIN_GLES_SwapWindow; @@ -224,6 +226,7 @@ WIN_CreateDevice(int devindex) device->Vulkan_UnloadLibrary = WIN_Vulkan_UnloadLibrary; device->Vulkan_GetInstanceExtensions = WIN_Vulkan_GetInstanceExtensions; device->Vulkan_CreateSurface = WIN_Vulkan_CreateSurface; + device->Vulkan_GetDrawableSize = WIN_GL_GetDrawableSize; #endif device->StartTextInput = WIN_StartTextInput; @@ -373,12 +376,25 @@ WIN_InitDPIAwareness(_THIS) } } +static void +WIN_InitDPIScaling(_THIS) +{ + SDL_VideoData* data = (SDL_VideoData*)_this->driverdata; + + if (SDL_GetHintBoolean(SDL_HINT_WINDOWS_DPI_SCALING, SDL_FALSE)) { + WIN_DeclareDPIAwarePerMonitorV2(_this); + + data->dpi_scaling_enabled = SDL_TRUE; + } +} + int WIN_VideoInit(_THIS) { SDL_VideoData *data = (SDL_VideoData *) _this->driverdata; WIN_InitDPIAwareness(_this); + WIN_InitDPIScaling(_this); #ifdef HIGHDPI_DEBUG SDL_Log("DPI awareness: %s", WIN_GetDPIAwareness(_this)); diff --git a/src/video/windows/SDL_windowsvideo.h b/src/video/windows/SDL_windowsvideo.h index 74c620709..f80ffde8e 100644 --- a/src/video/windows/SDL_windowsvideo.h +++ b/src/video/windows/SDL_windowsvideo.h @@ -188,6 +188,8 @@ typedef struct SDL_VideoData UINT *dpiY ); HRESULT (WINAPI *SetProcessDpiAwareness)(PROCESS_DPI_AWARENESS dpiAwareness); + SDL_bool dpi_scaling_enabled; + SDL_bool ime_com_initialized; struct ITfThreadMgr *ime_threadmgr; SDL_bool ime_initialized; diff --git a/src/video/windows/SDL_windowswindow.c b/src/video/windows/SDL_windowswindow.c index 50420dcbf..00fe3e9f5 100644 --- a/src/video/windows/SDL_windowswindow.c +++ b/src/video/windows/SDL_windowswindow.c @@ -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) { diff --git a/src/video/windows/SDL_windowswindow.h b/src/video/windows/SDL_windowswindow.h index 659366a9f..2a3653380 100644 --- a/src/video/windows/SDL_windowswindow.h +++ b/src/video/windows/SDL_windowswindow.h @@ -59,6 +59,11 @@ typedef struct #if SDL_VIDEO_OPENGL_EGL EGLSurface egl_surface; #endif + /** + * Cached value of GetDpiForWindow, for use for scaling points in the client area + * between dpi-scaled points and pixels. Only used if videodata->dpi_scaling_enabled. + */ + int scaling_dpi; } SDL_WindowData; extern int WIN_CreateWindow(_THIS, SDL_Window * window); @@ -91,6 +96,9 @@ extern SDL_bool WIN_GetWindowWMInfo(_THIS, SDL_Window * window, extern void WIN_OnWindowEnter(_THIS, SDL_Window * window); extern void WIN_UpdateClipCursor(SDL_Window *window); extern int WIN_SetWindowHitTest(SDL_Window *window, SDL_bool enabled); +extern void WIN_GetDrawableSize(const SDL_Window *window, int *w, int *h); +extern void WIN_ClientPointToSDL(const SDL_Window *window, int *w, int *h); +extern void WIN_ClientPointFromSDL(const SDL_Window *window, int *w, int *h); extern void WIN_AcceptDragAndDrop(SDL_Window * window, SDL_bool accept); extern int WIN_FlashWindow(_THIS, SDL_Window * window, SDL_FlashOperation operation);