This completes the implementation of display setting virtualization using DPI scaling, ie: Proton fshack done right.
There's various things to tweak still, and this will stay as an experimental feature with X11 (Wayland actually needs it to implement display mode change so it's as experimental as the driver):
- Fullscreen windows aren't padded when the display mode aspect ratio doesn't match the physical one. - Dragging a window over another monitor with a different raw DPI is broken, and window dimensions may flicker/break, this will need the winex11 window configure refactoring (ie: https://gitlab.winehq.org/wine/wine/-/merge_requests/6731 and the other changes in https://gitlab.winehq.org/wine/wine/-/merge_requests/6569) to be fixed. - DPI scaling is implemented with NtGdiStretchBlt, which is suboptimal, may not be performing well, and present sync is reportedly broken with GL/VK. - A couple of other more minor tweaks.
From: Rémi Bernon rbernon@codeweavers.com
--- dlls/win32u/sysparams.c | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-)
diff --git a/dlls/win32u/sysparams.c b/dlls/win32u/sysparams.c index f069f495620..93f8bc09c04 100644 --- a/dlls/win32u/sysparams.c +++ b/dlls/win32u/sysparams.c @@ -3648,13 +3648,25 @@ static POINT get_placement_offset( const DEVMODEW *displays, const DEVMODEW *pla return min_offset; }
-static void place_all_displays( DEVMODEW *displays ) +static void place_all_displays( DEVMODEW *displays, const WCHAR *primary_name ) { - POINT min_offset, offset; + POINT min_offset, offset = {0}; DEVMODEW *mode, *placing;
for (mode = displays; mode->dmSize; mode = NEXT_DEVMODEW(mode)) + { + if (wcsicmp( mode->dmDeviceName, primary_name )) continue; + offset.x = -mode->dmPosition.x; + offset.y = -mode->dmPosition.y; + break; + } + + for (mode = displays; mode->dmSize; mode = NEXT_DEVMODEW(mode)) + { + mode->dmPosition.x += offset.x; + mode->dmPosition.y += offset.y; mode->dmFields &= ~DM_POSITION; + }
/* Place all displays with no extra space between them and no overlapping */ while (1) @@ -3763,8 +3775,6 @@ static LONG apply_display_settings( struct source *target, const DEVMODEW *devmo return DISP_CHANGE_SUCCESSFUL; }
- place_all_displays( displays ); - if (!(primary = find_primary_source())) primary_name[0] = 0; else { @@ -3773,6 +3783,8 @@ static LONG apply_display_settings( struct source *target, const DEVMODEW *devmo asciiz_to_unicode( primary_name, device_name ); }
+ place_all_displays( displays, primary_name ); + /* use the default implementation in virtual desktop mode */ if (is_virtual_desktop()) ret = DISP_CHANGE_SUCCESSFUL; else ret = user_driver->pChangeDisplaySettings( displays, primary_name, hwnd, flags, lparam );
From: Rémi Bernon rbernon@codeweavers.com
--- server/window.c | 73 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+)
diff --git a/server/window.c b/server/window.c index 7b2a146f406..46377513719 100644 --- a/server/window.c +++ b/server/window.c @@ -255,6 +255,75 @@ static rectangle_t monitors_get_union_rect( struct winstation *winstation, int i return rect; }
+/* returns the largest intersecting or nearest monitor, keep in sync with win32u/sysparams.c */ +static struct monitor_info *get_monitor_from_rect( struct winstation *winstation, const rectangle_t *rect, int is_raw ) +{ + struct monitor_info *monitor, *nearest = NULL, *found = NULL, *end; + unsigned int max_area = 0, min_distance = -1; + + for (monitor = winstation->monitors, end = monitor + winstation->monitor_count; monitor < end; monitor++) + { + rectangle_t intersect, target = is_raw ? monitor->raw : monitor->virt; + + if (monitor->flags & (MONITOR_FLAG_CLONE | MONITOR_FLAG_INACTIVE)) continue; + + if (intersect_rect( &intersect, &target, rect )) + { + /* check for larger intersecting area */ + unsigned int area = (intersect.right - intersect.left) * (intersect.bottom - intersect.top); + + if (area > max_area) + { + max_area = area; + found = monitor; + } + } + + if (!found) /* if not intersecting, check for min distance */ + { + unsigned int distance, x, y; + + if (rect->right <= target.left) x = target.left - rect->right; + else if (target.right <= rect->left) x = rect->left - target.right; + else x = 0; + + if (rect->bottom <= target.top) y = target.top - rect->bottom; + else if (target.bottom <= rect->top) y = rect->top - target.bottom; + else y = 0; + + distance = x * x + y * y; + if (distance < min_distance) + { + min_distance = distance; + nearest = monitor; + } + } + } + + return found ? found : nearest; +} + +static void map_point_raw_to_virt( struct desktop *desktop, int *x, int *y ) +{ + int width_from, height_from, width_to, height_to; + rectangle_t rect = {*x, *y, *x + 1, *y + 1}; + struct monitor_info *monitor; + + if (!(monitor = get_monitor_from_rect( desktop->winstation, &rect, 1 ))) return; + width_to = monitor->virt.right - monitor->virt.left; + height_to = monitor->virt.bottom - monitor->virt.top; + width_from = monitor->raw.right - monitor->raw.left; + height_from = monitor->raw.bottom - monitor->raw.top; + + *x = *x * 2 - (monitor->raw.left * 2 + width_from); + *x = (*x * width_to * 2 + width_from) / (width_from * 2); + *x = (*x + monitor->virt.left * 2 + width_to) / 2; + + *y = *y * 2 - (monitor->raw.top * 2 + height_from); + *y = (*y * height_to * 2 + height_from) / (height_from * 2); + *y = (*y + monitor->virt.top * 2 + height_to) / 2; +} + /* get the per-monitor DPI for a window */ static unsigned int get_monitor_dpi( struct window *win ) { @@ -914,6 +983,8 @@ user_handle_t shallow_window_from_point( struct desktop *desktop, int x, int y )
if (!desktop->top_window) return 0;
+ map_point_raw_to_virt( desktop, &x, &y ); + LIST_FOR_EACH_ENTRY( ptr, &desktop->top_window->children, struct window, entry ) { int x_child = x, y_child = y; @@ -931,6 +1002,8 @@ struct thread *window_thread_from_point( user_handle_t scope, int x, int y )
if (!win) return NULL;
+ map_point_raw_to_virt( win->desktop, &x, &y ); + screen_to_client( win, &x, &y, 0 ); win = child_window_from_point( win, x, y ); if (!win->thread) return NULL;
From: Rémi Bernon rbernon@codeweavers.com
--- dlls/win32u/sysparams.c | 71 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 68 insertions(+), 3 deletions(-)
diff --git a/dlls/win32u/sysparams.c b/dlls/win32u/sysparams.c index 93f8bc09c04..fefd836d4f5 100644 --- a/dlls/win32u/sysparams.c +++ b/dlls/win32u/sysparams.c @@ -1761,6 +1761,27 @@ static BOOL is_monitor_primary( struct monitor *monitor ) return !!(source->state_flags & DISPLAY_DEVICE_PRIMARY_DEVICE); }
+/* display_lock must be held */ +static void monitor_virt_to_raw_ratio( struct monitor *monitor, UINT *num, UINT *den ) +{ + struct source *source = monitor->source; + + *num = *den = 1; + if (!source) return; + + if (source->physical.dmPelsWidth * source->current.dmPelsHeight <= + source->physical.dmPelsHeight * source->current.dmPelsWidth) + { + *num = source->physical.dmPelsWidth; + *den = source->current.dmPelsWidth; + } + else + { + *num = source->physical.dmPelsHeight; + *den = source->current.dmPelsHeight; + } +} + /* display_lock must be held */ static UINT monitor_get_dpi( struct monitor *monitor, MONITOR_DPI_TYPE type, UINT *dpi_x, UINT *dpi_y ) { @@ -1769,6 +1790,12 @@ static UINT monitor_get_dpi( struct monitor *monitor, MONITOR_DPI_TYPE type, UIN UINT dpi;
if (!source || !(dpi = source->dpi)) dpi = system_dpi; + if (source && type != MDT_EFFECTIVE_DPI) + { + scale_x = source->physical.dmPelsWidth / (float)source->current.dmPelsWidth; + scale_y = source->physical.dmPelsHeight / (float)source->current.dmPelsHeight; + } + *dpi_x = round( dpi * scale_x ); *dpi_y = round( dpi * scale_y ); return min( *dpi_x, *dpi_y ); @@ -1781,6 +1808,7 @@ static RECT monitor_get_rect( struct monitor *monitor, UINT dpi, MONITOR_DPI_TYP RECT rect = {0, 0, 1024, 768}; struct source *source; UINT dpi_from, x, y; + DEVMODEW *mode;
/* services do not have any adapters, only a virtual monitor */ if (!(source = monitor->source)) return rect; @@ -1789,9 +1817,10 @@ static RECT monitor_get_rect( struct monitor *monitor, UINT dpi, MONITOR_DPI_TYP if (!(source->state_flags & DISPLAY_DEVICE_ATTACHED_TO_DESKTOP)) return rect; source_get_current_settings( source, ¤t_mode );
- SetRect( &rect, current_mode.dmPosition.x, current_mode.dmPosition.y, - current_mode.dmPosition.x + current_mode.dmPelsWidth, - current_mode.dmPosition.y + current_mode.dmPelsHeight ); + mode = type != MDT_EFFECTIVE_DPI ? &source->physical : ¤t_mode; + SetRect( &rect, mode->dmPosition.x, mode->dmPosition.y, + mode->dmPosition.x + mode->dmPelsWidth, + mode->dmPosition.y + mode->dmPelsHeight );
dpi_from = monitor_get_dpi( monitor, type, &x, &y ); return map_dpi_rect( rect, dpi_from, dpi ); @@ -2403,6 +2432,42 @@ static RECT map_monitor_rect( struct monitor *monitor, RECT rect, UINT dpi_from, UINT dpi_to, MONITOR_DPI_TYPE type_to ) { UINT x, y; + + assert( type_from != type_to ); + + if (monitor->source) + { + DEVMODEW current_mode = {.dmSize = sizeof(DEVMODEW)}, *mode_from, *mode_to; + UINT num, den, dpi; + + source_get_current_settings( monitor->source, ¤t_mode ); + + dpi = monitor_get_dpi( monitor, MDT_DEFAULT, &x, &y ); + if (!dpi_from) dpi_from = dpi; + if (!dpi_to) dpi_to = dpi; + + if (type_from == MDT_RAW_DPI) + { + monitor_virt_to_raw_ratio( monitor, &den, &num ); + mode_from = &monitor->source->physical; + mode_to = ¤t_mode; + } + else + { + monitor_virt_to_raw_ratio( monitor, &num, &den ); + mode_from = ¤t_mode; + mode_to = &monitor->source->physical; + } + + rect = map_dpi_rect( rect, dpi_from, dpi * 2 ); + OffsetRect( &rect, -mode_from->dmPosition.x * 2 - mode_from->dmPelsWidth, + -mode_from->dmPosition.y * 2 - mode_from->dmPelsHeight ); + rect = map_dpi_rect( rect, den, num ); + OffsetRect( &rect, mode_to->dmPosition.x * 2 + mode_to->dmPelsWidth, + mode_to->dmPosition.y * 2 + mode_to->dmPelsHeight ); + return map_dpi_rect( rect, dpi * 2, dpi_to ); + } + if (!dpi_from) dpi_from = monitor_get_dpi( monitor, type_from, &x, &y ); if (!dpi_to) dpi_to = monitor_get_dpi( monitor, type_to, &x, &y ); return map_dpi_rect( rect, dpi_from, dpi_to );
From: Rémi Bernon rbernon@codeweavers.com
--- dlls/win32u/sysparams.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-)
diff --git a/dlls/win32u/sysparams.c b/dlls/win32u/sysparams.c index fefd836d4f5..cc27f3dc7f2 100644 --- a/dlls/win32u/sysparams.c +++ b/dlls/win32u/sysparams.c @@ -158,6 +158,7 @@ static struct list monitors = LIST_INIT(monitors); static INT64 last_query_display_time; static pthread_mutex_t display_lock = PTHREAD_MUTEX_INITIALIZER;
+static BOOL emulate_modeset; BOOL decorated_mode = TRUE; UINT64 thunk_lock_callback = 0;
@@ -1655,8 +1656,7 @@ static DEVMODEW *get_virtual_modes( const DEVMODEW *current, const DEVMODEW *ini static void add_modes( const DEVMODEW *current, UINT modes_count, const DEVMODEW *modes, void *param ) { struct device_manager_ctx *ctx = param; - DEVMODEW dummy, detached = *current, virtual, *virtual_modes = NULL; - const DEVMODEW physical = modes_count == 1 ? *modes : *current; + DEVMODEW dummy, physical, detached = *current, virtual, *virtual_modes = NULL; struct source *source; UINT virtual_count;
@@ -1665,6 +1665,13 @@ static void add_modes( const DEVMODEW *current, UINT modes_count, const DEVMODEW assert( !list_empty( &sources ) ); source = LIST_ENTRY( list_tail( &sources ), struct source, entry );
+ if (emulate_modeset) + { + modes = current; + modes_count = 1; + } + + physical = modes_count == 1 ? *modes : *current; if (ctx->is_primary) ctx->primary = *current;
detached.dmPelsWidth = 0; @@ -3851,7 +3858,7 @@ static LONG apply_display_settings( struct source *target, const DEVMODEW *devmo place_all_displays( displays, primary_name );
/* use the default implementation in virtual desktop mode */ - if (is_virtual_desktop()) ret = DISP_CHANGE_SUCCESSFUL; + if (is_virtual_desktop() || emulate_modeset) ret = DISP_CHANGE_SUCCESSFUL; else ret = user_driver->pChangeDisplaySettings( displays, primary_name, hwnd, flags, lparam );
if (ret == DISP_CHANGE_SUCCESSFUL) @@ -5423,6 +5430,8 @@ void sysparams_init(void) grab_fullscreen = IS_OPTION_TRUE( buffer[0] ); if (!get_config_key( hkey, appkey, "Decorated", buffer, sizeof(buffer) )) decorated_mode = IS_OPTION_TRUE( buffer[0] ); + if (!get_config_key( hkey, appkey, "EmulateModeset", buffer, sizeof(buffer) )) + emulate_modeset = IS_OPTION_TRUE( buffer[0] );
#undef IS_OPTION_TRUE
From: Rémi Bernon rbernon@codeweavers.com
--- dlls/desk.cpl/Makefile.in | 2 +- dlls/desk.cpl/desk.rc | 1 + dlls/desk.cpl/main.c | 38 ++++++++++++++++++++++++++++++++++++++ dlls/desk.cpl/resource.h | 1 + 4 files changed, 41 insertions(+), 1 deletion(-)
diff --git a/dlls/desk.cpl/Makefile.in b/dlls/desk.cpl/Makefile.in index 50ddb9b0125..fd267ac51af 100644 --- a/dlls/desk.cpl/Makefile.in +++ b/dlls/desk.cpl/Makefile.in @@ -1,5 +1,5 @@ MODULE = desk.cpl -IMPORTS = ole32 comctl32 user32 gdi32 +IMPORTS = advapi32 ole32 comctl32 user32 gdi32
SOURCES = \ desk.rc \ diff --git a/dlls/desk.cpl/desk.rc b/dlls/desk.cpl/desk.rc index dfffed3466c..da7deec665b 100644 --- a/dlls/desk.cpl/desk.rc +++ b/dlls/desk.cpl/desk.rc @@ -38,6 +38,7 @@ FONT 8, "Ms Shell Dlg" COMBOBOX IDC_DISPLAY_SETTINGS_LIST, 10, 136, 160, 60, CBS_DROPDOWNLIST | CBS_HASSTRINGS PUSHBUTTON "&Reset", IDC_DISPLAY_SETTINGS_RESET, 180, 135, 60, 15 PUSHBUTTON "&Apply", IDC_DISPLAY_SETTINGS_APPLY, 250, 135, 60, 15 + AUTOCHECKBOX "Emulate display mode changes (requires restart)", IDC_EMULATE_MODESET, 10, 155, 300, 10 }
LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL diff --git a/dlls/desk.cpl/main.c b/dlls/desk.cpl/main.c index 7dad563be16..15ebbfde94f 100644 --- a/dlls/desk.cpl/main.c +++ b/dlls/desk.cpl/main.c @@ -242,6 +242,39 @@ static void handle_display_settings_apply(void) ChangeDisplaySettingsExW( NULL, NULL, 0, 0, NULL ); }
+static void handle_emulate_modeset_change( HWND hwnd ) +{ + const WCHAR *value = L"N"; + HKEY hkey; + + /* Registry key can be found in HKCU\Software\Wine\X11 Driver */ + if (!RegCreateKeyExW( HKEY_CURRENT_USER, L"Software\Wine\X11 Driver", 0, NULL, 0, + KEY_SET_VALUE, NULL, &hkey, NULL )) + { + if (IsDlgButtonChecked( hwnd, IDC_EMULATE_MODESET ) == BST_CHECKED) value = L"Y"; + RegSetValueExW( hkey, L"EmulateModeset", 0, REG_SZ, (BYTE *)value, (wcslen( value ) + 1) * sizeof(WCHAR) ); + RegCloseKey( hkey ); + } +} + +static BOOL get_option( const WCHAR *option, BOOL default_value ) +{ + BOOL ret = default_value; + WCHAR buffer[MAX_PATH]; + DWORD size = sizeof(buffer); + +#define IS_OPTION_TRUE(ch) \ + ((ch) == 'y' || (ch) == 'Y' || (ch) == 't' || (ch) == 'T' || (ch) == '1') + + /* Registry key can be found in HKCU\Software\Wine\X11 Driver */ + if (!RegGetValueW( HKEY_CURRENT_USER, L"Software\Wine\X11 Driver", option, RRF_RT_REG_SZ, NULL, + (BYTE *)buffer, &size )) + ret = IS_OPTION_TRUE(buffer[0]); + +#undef IS_OPTION_TRUE + return ret; +} + static RECT map_virtual_client_rect( RECT rect, RECT client_rect, RECT virtual_rect, float scale ) { OffsetRect( &rect, -(virtual_rect.left + virtual_rect.right) / 2, -(virtual_rect.top + virtual_rect.bottom) / 2 ); @@ -424,6 +457,8 @@ static INT_PTR CALLBACK desktop_dialog_proc( HWND hwnd, UINT msg, WPARAM wparam, case WM_INITDIALOG: refresh_device_list( hwnd ); create_desktop_view( hwnd ); + SendMessageW( GetDlgItem( hwnd, IDC_EMULATE_MODESET ), BM_SETCHECK, + get_option( L"EmulateModeset", FALSE ), 0 ); return TRUE;
case WM_COMMAND: @@ -432,6 +467,9 @@ static INT_PTR CALLBACK desktop_dialog_proc( HWND hwnd, UINT msg, WPARAM wparam, case MAKEWPARAM( IDC_DISPLAY_SETTINGS_LIST, CBN_SELCHANGE ): handle_display_settings_change( hwnd ); break; + case IDC_EMULATE_MODESET: + handle_emulate_modeset_change( hwnd ); + break; case IDC_DISPLAY_SETTINGS_APPLY: handle_display_settings_apply(); break; diff --git a/dlls/desk.cpl/resource.h b/dlls/desk.cpl/resource.h index 03ff9c1e0b2..f16b9c724bd 100644 --- a/dlls/desk.cpl/resource.h +++ b/dlls/desk.cpl/resource.h @@ -40,5 +40,6 @@ #define IDC_DISPLAY_SETTINGS_LIST 2001 #define IDC_DISPLAY_SETTINGS_RESET 2002 #define IDC_DISPLAY_SETTINGS_APPLY 2003 +#define IDC_EMULATE_MODESET 2004
#define ICO_MAIN 100
1) Will gamma emulation be added too (because I don't see stuff for that)? 2) Is it possible to add a faster scaling path (with FSR for example)? 3) Could higher-than-native resolutions be supported with this (which after downscaling could provide a Dynamic Super Resolution-like effect)?