This MR improves driver support for scaled HiDPI displays, by giving the application access to the full native resolution of the displays, rather than the logical resolution which is the Wayland default for client ignores scale hints. The application (and/or user through Wine settings) can then control the scale it renders at.
If the compositor doesn't support the required extensions to support application scaling, we fall back to compositor scaling. Note that compositor scaling may be sometimes desirable anyway, so in an upcoming MR I will introduce a driver registry option to force it if needed.
Here is how things look with the various options on a 2x scaled output:
1. Application scaling at 96dpi (i.e, 1x scaling) ![wine-wayland-app-scaling-96dpi](/uploads/b42e671e434059928b977e22ee4510dc/wine-wayland-app-scaling-96dpi.png)
2. Application scaling at 192dpi (i.e., 2x scaling) ![wine-wayland-app-scaling-192dpi](/uploads/eb752866a474af6dba8388a279e07ab1/wine-wayland-app-scaling-192dpi.png)
3. Compositor scaling 2x (with app scaling at 1x) ![wine-wayland-compositor-scaling](/uploads/d4a4a54dabcb750b6b63854d8fc2144f/wine-wayland-compositor-scaling.png)
Thanks!
From: Alexandros Frantzis alexandros.frantzis@collabora.com
Introduce and use functions to convert between the Wine and Wayland surface coordinate spaces. At the moment the two are the same but this will change with the introduction of scaling support. --- dlls/winewayland.drv/wayland_pointer.c | 24 +++++--- dlls/winewayland.drv/wayland_surface.c | 79 ++++++++++++++++++++++---- dlls/winewayland.drv/waylanddrv.h | 4 ++ dlls/winewayland.drv/window.c | 24 +++++--- 4 files changed, 106 insertions(+), 25 deletions(-)
diff --git a/dlls/winewayland.drv/wayland_pointer.c b/dlls/winewayland.drv/wayland_pointer.c index ff376882d73..bbe61824561 100644 --- a/dlls/winewayland.drv/wayland_pointer.c +++ b/dlls/winewayland.drv/wayland_pointer.c @@ -50,21 +50,29 @@ static void pointer_handle_motion(void *data, struct wl_pointer *wl_pointer, uint32_t time, wl_fixed_t sx, wl_fixed_t sy) { INPUT input = {0}; - RECT window_rect; + RECT *window_rect; HWND hwnd; int screen_x, screen_y; + struct wayland_surface *surface;
if (!(hwnd = wayland_pointer_get_focused_hwnd())) return; - if (!NtUserGetWindowRect(hwnd, &window_rect)) return; + if (!(surface = wayland_surface_lock_hwnd(hwnd))) return;
- screen_x = round(wl_fixed_to_double(sx)) + window_rect.left; - screen_y = round(wl_fixed_to_double(sy)) + window_rect.top; + window_rect = &surface->window.rect; + + wayland_surface_coords_to_wine(surface, 2, + wl_fixed_to_double(sx), &screen_x, + wl_fixed_to_double(sy), &screen_y); + screen_x += window_rect->left; + screen_y += window_rect->top; /* Sometimes, due to rounding, we may end up with pointer coordinates * slightly outside the target window, so bring them within bounds. */ - if (screen_x >= window_rect.right) screen_x = window_rect.right - 1; - else if (screen_x < window_rect.left) screen_x = window_rect.left; - if (screen_y >= window_rect.bottom) screen_y = window_rect.bottom - 1; - else if (screen_y < window_rect.top) screen_y = window_rect.top; + if (screen_x >= window_rect->right) screen_x = window_rect->right - 1; + else if (screen_x < window_rect->left) screen_x = window_rect->left; + if (screen_y >= window_rect->bottom) screen_y = window_rect->bottom - 1; + else if (screen_y < window_rect->top) screen_y = window_rect->top; + + pthread_mutex_unlock(&surface->mutex);
input.type = INPUT_MOUSE; input.mi.dx = screen_x; diff --git a/dlls/winewayland.drv/wayland_surface.c b/dlls/winewayland.drv/wayland_surface.c index ae4812ebb08..0ace11e3a0b 100644 --- a/dlls/winewayland.drv/wayland_surface.c +++ b/dlls/winewayland.drv/wayland_surface.c @@ -24,6 +24,7 @@
#include "config.h"
+#include <stdarg.h> #include <stdlib.h> #include <unistd.h>
@@ -380,14 +381,11 @@ static void wayland_surface_get_rect_in_monitor(struct wayland_surface *surface, * * Sets the xdg_surface geometry */ -static void wayland_surface_reconfigure_geometry(struct wayland_surface *surface) +static void wayland_surface_reconfigure_geometry(struct wayland_surface *surface, + int width, int height) { - int width, height; RECT rect;
- width = surface->window.rect.right - surface->window.rect.left; - height = surface->window.rect.bottom - surface->window.rect.top; - /* If the window size is bigger than the current state accepts, use the * largest visible (from Windows' perspective) subregion of the window. */ if ((surface->current.state & (WAYLAND_SURFACE_CONFIG_STATE_MAXIMIZED | @@ -396,6 +394,12 @@ static void wayland_surface_reconfigure_geometry(struct wayland_surface *surface { wayland_surface_get_rect_in_monitor(surface, &rect);
+ wayland_surface_coords_from_wine_rounded(surface, 4, + rect.left, &rect.left, + rect.top, &rect.top, + rect.right, &rect.right, + rect.bottom, &rect.bottom); + /* If the window rect in the monitor is smaller than required, * fall back to an appropriately sized rect at the top-left. */ if ((surface->current.state & WAYLAND_SURFACE_CONFIG_STATE_MAXIMIZED) && @@ -436,15 +440,19 @@ static void wayland_surface_reconfigure_geometry(struct wayland_surface *surface BOOL wayland_surface_reconfigure(struct wayland_surface *surface) { struct wayland_window_config *window = &surface->window; - int width, height; + int wine_width, wine_height, width, height;
if (!surface->xdg_toplevel) return TRUE;
- width = surface->window.rect.right - surface->window.rect.left; - height = surface->window.rect.bottom - surface->window.rect.top; + wine_width = surface->window.rect.right - surface->window.rect.left; + wine_height = surface->window.rect.bottom - surface->window.rect.top; + + wayland_surface_coords_from_wine_rounded(surface, 2, + wine_width, &width, + wine_height, &height);
TRACE("hwnd=%p window=%dx%d,%#x processing=%dx%d,%#x current=%dx%d,%#x\n", - surface->hwnd, width, height, window->state, + surface->hwnd, wine_width, wine_height, window->state, surface->processing.width, surface->processing.height, surface->processing.state, surface->current.width, surface->current.height, surface->current.state); @@ -479,7 +487,7 @@ BOOL wayland_surface_reconfigure(struct wayland_surface *surface) return FALSE; }
- wayland_surface_reconfigure_geometry(surface); + wayland_surface_reconfigure_geometry(surface, width, height);
return TRUE; } @@ -616,3 +624,54 @@ err: if (shm_buffer) wayland_shm_buffer_unref(shm_buffer); return NULL; } + +/********************************************************************** + * wayland_surface_coords_from_wine_rounded + * + * Converts the window-local wine coordinates to wayland surface-local coordinates + * rounding to the closest integer value. + * + * Expects 'count' pairs of (int wine_coord, int *wl_coord) parameters. + */ +void wayland_surface_coords_from_wine_rounded(struct wayland_surface *surface, + int count, ...) +{ + va_list ap; + int i; + + va_start(ap, count); + + for (i = 0; i < count; i++) + { + int wine_coord = va_arg(ap, int); + int *wl_coord = va_arg(ap, int *); + *wl_coord = wine_coord; + } + + va_end(ap); +} + +/********************************************************************** + * wayland_surface_coords_to_wine + * + * Converts the surface-local coordinates to wine windows-local coordinates. + * + * Expects 'count' pairs of (double wl_coord, int *wine_coord) parameters. + */ +void wayland_surface_coords_to_wine(struct wayland_surface *surface, + int count, ...) +{ + va_list ap; + int i; + + va_start(ap, count); + + for (i = 0; i < count; i++) + { + double wl_coord = va_arg(ap, double); + int *wine_coord = va_arg(ap, int *); + *wine_coord = round(wl_coord); + } + + va_end(ap); +} diff --git a/dlls/winewayland.drv/waylanddrv.h b/dlls/winewayland.drv/waylanddrv.h index 4bcd9e6706e..340b792ba11 100644 --- a/dlls/winewayland.drv/waylanddrv.h +++ b/dlls/winewayland.drv/waylanddrv.h @@ -204,6 +204,10 @@ BOOL wayland_surface_reconfigure(struct wayland_surface *surface) DECLSPEC_HIDDE BOOL wayland_surface_config_is_compatible(struct wayland_surface_config *conf, int width, int height, enum wayland_surface_config_state state) DECLSPEC_HIDDEN; +void wayland_surface_coords_from_wine_rounded(struct wayland_surface *surface, + int count, ...) DECLSPEC_HIDDEN; +void wayland_surface_coords_to_wine(struct wayland_surface *surface, + int count, ...) DECLSPEC_HIDDEN;
/********************************************************************** * Wayland SHM buffer diff --git a/dlls/winewayland.drv/window.c b/dlls/winewayland.drv/window.c index 57d2f15a06b..35b9ab258a3 100644 --- a/dlls/winewayland.drv/window.c +++ b/dlls/winewayland.drv/window.c @@ -384,7 +384,8 @@ static void wayland_resize_desktop(void) static void wayland_configure_window(HWND hwnd) { struct wayland_surface *surface; - INT width, height; + INT width, height, wine_width, wine_height; + INT window_wl_width, window_wl_height; UINT flags = 0; uint32_t state; DWORD style; @@ -434,21 +435,30 @@ static void wayland_configure_window(HWND hwnd) flags |= SWP_FRAMECHANGED; }
+ wayland_surface_coords_from_wine_rounded(surface, 2, + surface->window.rect.right - + surface->window.rect.left, + &window_wl_width, + surface->window.rect.bottom - + surface->window.rect.top, + &window_wl_height); + /* If the window is already fullscreen and its size is compatible with what * the compositor is requesting, don't force a resize, since some applications * are very insistent on a particular fullscreen size (which may not match * the monitor size). */ if ((surface->window.state & WAYLAND_SURFACE_CONFIG_STATE_FULLSCREEN) && wayland_surface_config_is_compatible(&surface->processing, - surface->window.rect.right - - surface->window.rect.left, - surface->window.rect.bottom - - surface->window.rect.top, + window_wl_width, window_wl_height, surface->window.state)) { flags |= SWP_NOSIZE; }
+ wayland_surface_coords_to_wine(surface, 2, + (double)width, &wine_width, + (double)height, &wine_height); + pthread_mutex_unlock(&surface->mutex);
TRACE("processing=%dx%d,%#x\n", width, height, state); @@ -457,7 +467,7 @@ static void wayland_configure_window(HWND hwnd) if (needs_exit_size_move) send_message(hwnd, WM_EXITSIZEMOVE, 0, 0);
flags |= SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_NOMOVE; - if (width == 0 || height == 0) flags |= SWP_NOSIZE; + if (wine_width == 0 || wine_height == 0) flags |= SWP_NOSIZE;
style = NtUserGetWindowLongW(hwnd, GWL_STYLE); if (!(state & WAYLAND_SURFACE_CONFIG_STATE_MAXIMIZED) != !(style & WS_MAXIMIZE)) @@ -474,7 +484,7 @@ static void wayland_configure_window(HWND hwnd) flags |= SWP_NOSENDCHANGING; }
- NtUserSetWindowPos(hwnd, 0, 0, 0, width, height, flags); + NtUserSetWindowPos(hwnd, 0, 0, 0, wine_width, wine_height, flags); }
/**********************************************************************
From: Alexandros Frantzis alexandros.frantzis@collabora.com
Use the fractional-scale-v1 protocol to keep track of the preferred scale the compositor would like for each surface. --- dlls/winewayland.drv/Makefile.in | 1 + dlls/winewayland.drv/fractional-scale-v1.xml | 102 +++++++++++++++++++ dlls/winewayland.drv/wayland.c | 5 + dlls/winewayland.drv/wayland_surface.c | 46 +++++++++ dlls/winewayland.drv/waylanddrv.h | 4 + 5 files changed, 158 insertions(+) create mode 100644 dlls/winewayland.drv/fractional-scale-v1.xml
diff --git a/dlls/winewayland.drv/Makefile.in b/dlls/winewayland.drv/Makefile.in index e1019ad8348..b450f80dd1a 100644 --- a/dlls/winewayland.drv/Makefile.in +++ b/dlls/winewayland.drv/Makefile.in @@ -6,6 +6,7 @@ UNIX_LIBS = -lwin32u $(WAYLAND_CLIENT_LIBS) $(PTHREAD_LIBS) -lm SOURCES = \ display.c \ dllmain.c \ + fractional-scale-v1.xml \ version.rc \ wayland.c \ wayland_output.c \ diff --git a/dlls/winewayland.drv/fractional-scale-v1.xml b/dlls/winewayland.drv/fractional-scale-v1.xml new file mode 100644 index 00000000000..350bfc01eaf --- /dev/null +++ b/dlls/winewayland.drv/fractional-scale-v1.xml @@ -0,0 +1,102 @@ +<?xml version="1.0" encoding="UTF-8"?> +<protocol name="fractional_scale_v1"> + <copyright> + Copyright © 2022 Kenny Levinsen + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + </copyright> + + <description summary="Protocol for requesting fractional surface scales"> + This protocol allows a compositor to suggest for surfaces to render at + fractional scales. + + A client can submit scaled content by utilizing wp_viewport. This is done by + creating a wp_viewport object for the surface and setting the destination + rectangle to the surface size before the scale factor is applied. + + The buffer size is calculated by multiplying the surface size by the + intended scale. + + The wl_surface buffer scale should remain set to 1. + + If a surface has a surface-local size of 100 px by 50 px and wishes to + submit buffers with a scale of 1.5, then a buffer of 150px by 75 px should + be used and the wp_viewport destination rectangle should be 100 px by 50 px. + + For toplevel surfaces, the size is rounded halfway away from zero. The + rounding algorithm for subsurface position and size is not defined. + </description> + + <interface name="wp_fractional_scale_manager_v1" version="1"> + <description summary="fractional surface scale information"> + A global interface for requesting surfaces to use fractional scales. + </description> + + <request name="destroy" type="destructor"> + <description summary="unbind the fractional surface scale interface"> + Informs the server that the client will not be using this protocol + object anymore. This does not affect any other objects, + wp_fractional_scale_v1 objects included. + </description> + </request> + + <enum name="error"> + <entry name="fractional_scale_exists" value="0" + summary="the surface already has a fractional_scale object associated"/> + </enum> + + <request name="get_fractional_scale"> + <description summary="extend surface interface for scale information"> + Create an add-on object for the the wl_surface to let the compositor + request fractional scales. If the given wl_surface already has a + wp_fractional_scale_v1 object associated, the fractional_scale_exists + protocol error is raised. + </description> + <arg name="id" type="new_id" interface="wp_fractional_scale_v1" + summary="the new surface scale info interface id"/> + <arg name="surface" type="object" interface="wl_surface" + summary="the surface"/> + </request> + </interface> + + <interface name="wp_fractional_scale_v1" version="1"> + <description summary="fractional scale interface to a wl_surface"> + An additional interface to a wl_surface object which allows the compositor + to inform the client of the preferred scale. + </description> + + <request name="destroy" type="destructor"> + <description summary="remove surface scale information for surface"> + Destroy the fractional scale object. When this object is destroyed, + preferred_scale events will no longer be sent. + </description> + </request> + + <event name="preferred_scale"> + <description summary="notify of new preferred scale"> + Notification of a new preferred scale for this surface that the + compositor suggests that the client should use. + + The sent scale is the numerator of a fraction with a denominator of 120. + </description> + <arg name="scale" type="uint" summary="the new preferred scale"/> + </event> + </interface> +</protocol> diff --git a/dlls/winewayland.drv/wayland.c b/dlls/winewayland.drv/wayland.c index b8c69a105cb..cf3fd4748e9 100644 --- a/dlls/winewayland.drv/wayland.c +++ b/dlls/winewayland.drv/wayland.c @@ -138,6 +138,11 @@ static void registry_handle_global(void *data, struct wl_registry *registry, wl_seat_add_listener(seat->wl_seat, &seat_listener, NULL); pthread_mutex_unlock(&seat->mutex); } + else if (strcmp(interface, "wp_fractional_scale_manager_v1") == 0) + { + process_wayland.wp_fractional_scale_manager_v1 = + wl_registry_bind(registry, id, &wp_fractional_scale_manager_v1_interface, 1); + } }
static void registry_handle_global_remove(void *data, struct wl_registry *registry, diff --git a/dlls/winewayland.drv/wayland_surface.c b/dlls/winewayland.drv/wayland_surface.c index 0ace11e3a0b..c7d81c0b967 100644 --- a/dlls/winewayland.drv/wayland_surface.c +++ b/dlls/winewayland.drv/wayland_surface.c @@ -131,6 +131,25 @@ static const struct xdg_toplevel_listener xdg_toplevel_listener = xdg_toplevel_handle_close };
+static void wp_fractional_scale_v1_handle_preferred_scale( + void *data, + struct wp_fractional_scale_v1 *wp_fractional_scale_v1, + uint32_t scale) +{ + HWND hwnd = data; + struct wayland_surface *surface; + + if (!(surface = wayland_surface_lock_hwnd(hwnd))) return; + TRACE("hwnd=%p preferred_scale=%u\n", hwnd, scale); + surface->preferred_scale = scale / 120.0; + pthread_mutex_unlock(&surface->mutex); +} + +static const struct wp_fractional_scale_v1_listener wp_fractional_scale_v1_listener = +{ + wp_fractional_scale_v1_handle_preferred_scale +}; + /********************************************************************** * wayland_surface_create * @@ -160,6 +179,27 @@ struct wayland_surface *wayland_surface_create(HWND hwnd) } wl_surface_set_user_data(surface->wl_surface, hwnd);
+ surface->preferred_scale = 1.0; + if (process_wayland.wp_fractional_scale_manager_v1) + { + surface->wp_fractional_scale_v1 = + wp_fractional_scale_manager_v1_get_fractional_scale( + process_wayland.wp_fractional_scale_manager_v1, + surface->wl_surface); + /* Note: Since we are dispatching events from a different thread, it + * is possible (although not likely) that we receive a preferred + * scale event before we get the chance to add the listener, and + * thus miss the event. Fixing this requires synchronization with + * the dispatch thread, so for now we accept the risk and will + * revisit as needed. */ + if (surface->wp_fractional_scale_v1) + { + wp_fractional_scale_v1_add_listener(surface->wp_fractional_scale_v1, + &wp_fractional_scale_v1_listener, + hwnd); + } + } + return surface;
err: @@ -184,6 +224,12 @@ void wayland_surface_destroy(struct wayland_surface *surface)
pthread_mutex_lock(&surface->mutex);
+ if (surface->wp_fractional_scale_v1) + { + wp_fractional_scale_v1_destroy(surface->wp_fractional_scale_v1); + surface->wp_fractional_scale_v1 = NULL; + } + if (surface->xdg_toplevel) { xdg_toplevel_destroy(surface->xdg_toplevel); diff --git a/dlls/winewayland.drv/waylanddrv.h b/dlls/winewayland.drv/waylanddrv.h index 340b792ba11..0fdb06922b3 100644 --- a/dlls/winewayland.drv/waylanddrv.h +++ b/dlls/winewayland.drv/waylanddrv.h @@ -27,6 +27,7 @@
#include <pthread.h> #include <wayland-client.h> +#include "fractional-scale-v1-client-protocol.h" #include "xdg-output-unstable-v1-client-protocol.h" #include "xdg-shell-client-protocol.h"
@@ -99,6 +100,7 @@ struct wayland struct wl_compositor *wl_compositor; struct xdg_wm_base *xdg_wm_base; struct wl_shm *wl_shm; + struct wp_fractional_scale_manager_v1 *wp_fractional_scale_manager_v1; struct wayland_seat seat; struct wayland_pointer pointer; struct wl_list output_list; @@ -154,11 +156,13 @@ struct wayland_surface struct wl_surface *wl_surface; struct xdg_surface *xdg_surface; struct xdg_toplevel *xdg_toplevel; + struct wp_fractional_scale_v1 *wp_fractional_scale_v1; pthread_mutex_t mutex; struct wayland_surface_config pending, requested, processing, current; struct wayland_shm_buffer *latest_window_buffer; BOOL resizing; struct wayland_window_config window; + double preferred_scale; };
struct wayland_shm_buffer
From: Alexandros Frantzis alexandros.frantzis@collabora.com
Take into account the preferred scale factor and use wp_viewport to instruct the compositor to present surfaces at their native pixel sizes. This effectively disables compositor side scaling. --- dlls/winewayland.drv/Makefile.in | 1 + dlls/winewayland.drv/viewporter.xml | 180 +++++++++++++++++++++++++ dlls/winewayland.drv/wayland.c | 5 + dlls/winewayland.drv/wayland_surface.c | 40 +++++- dlls/winewayland.drv/waylanddrv.h | 3 + 5 files changed, 227 insertions(+), 2 deletions(-) create mode 100644 dlls/winewayland.drv/viewporter.xml
diff --git a/dlls/winewayland.drv/Makefile.in b/dlls/winewayland.drv/Makefile.in index b450f80dd1a..e00fb866f3f 100644 --- a/dlls/winewayland.drv/Makefile.in +++ b/dlls/winewayland.drv/Makefile.in @@ -8,6 +8,7 @@ SOURCES = \ dllmain.c \ fractional-scale-v1.xml \ version.rc \ + viewporter.xml \ wayland.c \ wayland_output.c \ wayland_pointer.c \ diff --git a/dlls/winewayland.drv/viewporter.xml b/dlls/winewayland.drv/viewporter.xml new file mode 100644 index 00000000000..d1048d1f332 --- /dev/null +++ b/dlls/winewayland.drv/viewporter.xml @@ -0,0 +1,180 @@ +<?xml version="1.0" encoding="UTF-8"?> +<protocol name="viewporter"> + + <copyright> + Copyright © 2013-2016 Collabora, Ltd. + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + </copyright> + + <interface name="wp_viewporter" version="1"> + <description summary="surface cropping and scaling"> + The global interface exposing surface cropping and scaling + capabilities is used to instantiate an interface extension for a + wl_surface object. This extended interface will then allow + cropping and scaling the surface contents, effectively + disconnecting the direct relationship between the buffer and the + surface size. + </description> + + <request name="destroy" type="destructor"> + <description summary="unbind from the cropping and scaling interface"> + Informs the server that the client will not be using this + protocol object anymore. This does not affect any other objects, + wp_viewport objects included. + </description> + </request> + + <enum name="error"> + <entry name="viewport_exists" value="0" + summary="the surface already has a viewport object associated"/> + </enum> + + <request name="get_viewport"> + <description summary="extend surface interface for crop and scale"> + Instantiate an interface extension for the given wl_surface to + crop and scale its content. If the given wl_surface already has + a wp_viewport object associated, the viewport_exists + protocol error is raised. + </description> + <arg name="id" type="new_id" interface="wp_viewport" + summary="the new viewport interface id"/> + <arg name="surface" type="object" interface="wl_surface" + summary="the surface"/> + </request> + </interface> + + <interface name="wp_viewport" version="1"> + <description summary="crop and scale interface to a wl_surface"> + An additional interface to a wl_surface object, which allows the + client to specify the cropping and scaling of the surface + contents. + + This interface works with two concepts: the source rectangle (src_x, + src_y, src_width, src_height), and the destination size (dst_width, + dst_height). The contents of the source rectangle are scaled to the + destination size, and content outside the source rectangle is ignored. + This state is double-buffered, and is applied on the next + wl_surface.commit. + + The two parts of crop and scale state are independent: the source + rectangle, and the destination size. Initially both are unset, that + is, no scaling is applied. The whole of the current wl_buffer is + used as the source, and the surface size is as defined in + wl_surface.attach. + + If the destination size is set, it causes the surface size to become + dst_width, dst_height. The source (rectangle) is scaled to exactly + this size. This overrides whatever the attached wl_buffer size is, + unless the wl_buffer is NULL. If the wl_buffer is NULL, the surface + has no content and therefore no size. Otherwise, the size is always + at least 1x1 in surface local coordinates. + + If the source rectangle is set, it defines what area of the wl_buffer is + taken as the source. If the source rectangle is set and the destination + size is not set, then src_width and src_height must be integers, and the + surface size becomes the source rectangle size. This results in cropping + without scaling. If src_width or src_height are not integers and + destination size is not set, the bad_size protocol error is raised when + the surface state is applied. + + The coordinate transformations from buffer pixel coordinates up to + the surface-local coordinates happen in the following order: + 1. buffer_transform (wl_surface.set_buffer_transform) + 2. buffer_scale (wl_surface.set_buffer_scale) + 3. crop and scale (wp_viewport.set*) + This means, that the source rectangle coordinates of crop and scale + are given in the coordinates after the buffer transform and scale, + i.e. in the coordinates that would be the surface-local coordinates + if the crop and scale was not applied. + + If src_x or src_y are negative, the bad_value protocol error is raised. + Otherwise, if the source rectangle is partially or completely outside of + the non-NULL wl_buffer, then the out_of_buffer protocol error is raised + when the surface state is applied. A NULL wl_buffer does not raise the + out_of_buffer error. + + If the wl_surface associated with the wp_viewport is destroyed, + all wp_viewport requests except 'destroy' raise the protocol error + no_surface. + + If the wp_viewport object is destroyed, the crop and scale + state is removed from the wl_surface. The change will be applied + on the next wl_surface.commit. + </description> + + <request name="destroy" type="destructor"> + <description summary="remove scaling and cropping from the surface"> + The associated wl_surface's crop and scale state is removed. + The change is applied on the next wl_surface.commit. + </description> + </request> + + <enum name="error"> + <entry name="bad_value" value="0" + summary="negative or zero values in width or height"/> + <entry name="bad_size" value="1" + summary="destination size is not integer"/> + <entry name="out_of_buffer" value="2" + summary="source rectangle extends outside of the content area"/> + <entry name="no_surface" value="3" + summary="the wl_surface was destroyed"/> + </enum> + + <request name="set_source"> + <description summary="set the source rectangle for cropping"> + Set the source rectangle of the associated wl_surface. See + wp_viewport for the description, and relation to the wl_buffer + size. + + If all of x, y, width and height are -1.0, the source rectangle is + unset instead. Any other set of values where width or height are zero + or negative, or x or y are negative, raise the bad_value protocol + error. + + The crop and scale state is double-buffered state, and will be + applied on the next wl_surface.commit. + </description> + <arg name="x" type="fixed" summary="source rectangle x"/> + <arg name="y" type="fixed" summary="source rectangle y"/> + <arg name="width" type="fixed" summary="source rectangle width"/> + <arg name="height" type="fixed" summary="source rectangle height"/> + </request> + + <request name="set_destination"> + <description summary="set the surface size for scaling"> + Set the destination size of the associated wl_surface. See + wp_viewport for the description, and relation to the wl_buffer + size. + + If width is -1 and height is -1, the destination size is unset + instead. Any other pair of values for width and height that + contains zero or negative values raises the bad_value protocol + error. + + The crop and scale state is double-buffered state, and will be + applied on the next wl_surface.commit. + </description> + <arg name="width" type="int" summary="surface width"/> + <arg name="height" type="int" summary="surface height"/> + </request> + </interface> + +</protocol> diff --git a/dlls/winewayland.drv/wayland.c b/dlls/winewayland.drv/wayland.c index cf3fd4748e9..4a09567ba0c 100644 --- a/dlls/winewayland.drv/wayland.c +++ b/dlls/winewayland.drv/wayland.c @@ -143,6 +143,11 @@ static void registry_handle_global(void *data, struct wl_registry *registry, process_wayland.wp_fractional_scale_manager_v1 = wl_registry_bind(registry, id, &wp_fractional_scale_manager_v1_interface, 1); } + else if (strcmp(interface, "wp_viewporter") == 0) + { + process_wayland.wp_viewporter = + wl_registry_bind(registry, id, &wp_viewporter_interface, 1); + } }
static void registry_handle_global_remove(void *data, struct wl_registry *registry, diff --git a/dlls/winewayland.drv/wayland_surface.c b/dlls/winewayland.drv/wayland_surface.c index c7d81c0b967..72c60959a1b 100644 --- a/dlls/winewayland.drv/wayland_surface.c +++ b/dlls/winewayland.drv/wayland_surface.c @@ -143,6 +143,9 @@ static void wp_fractional_scale_v1_handle_preferred_scale( TRACE("hwnd=%p preferred_scale=%u\n", hwnd, scale); surface->preferred_scale = scale / 120.0; pthread_mutex_unlock(&surface->mutex); + + /* Force a redraw so the new scale is applied. */ + NtUserRedrawWindow(hwnd, NULL, NULL, RDW_INVALIDATE | RDW_FRAME); }
static const struct wp_fractional_scale_v1_listener wp_fractional_scale_v1_listener = @@ -200,6 +203,13 @@ struct wayland_surface *wayland_surface_create(HWND hwnd) } }
+ if (process_wayland.wp_viewporter) + { + surface->wp_viewport = + wp_viewporter_get_viewport(process_wayland.wp_viewporter, + surface->wl_surface); + } + return surface;
err: @@ -230,6 +240,12 @@ void wayland_surface_destroy(struct wayland_surface *surface) surface->wp_fractional_scale_v1 = NULL; }
+ if (surface->wp_viewport) + { + wp_viewport_destroy(surface->wp_viewport); + surface->wp_viewport = NULL; + } + if (surface->xdg_toplevel) { xdg_toplevel_destroy(surface->xdg_toplevel); @@ -477,6 +493,25 @@ static void wayland_surface_reconfigure_geometry(struct wayland_surface *surface } }
+/********************************************************************** + * wayland_surface_reconfigure_size + * + * Sets the surface size with viewporter + */ +static void wayland_surface_reconfigure_size(struct wayland_surface *surface, + int width, int height) +{ + TRACE("hwnd=%p size=%dx%d\n", surface->hwnd, width, height); + + if (surface->wp_viewport) + { + if (width != 0 && height != 0) + wp_viewport_set_destination(surface->wp_viewport, width, height); + else + wp_viewport_set_destination(surface->wp_viewport, -1, -1); + } +} + /********************************************************************** * wayland_surface_reconfigure * @@ -534,6 +569,7 @@ BOOL wayland_surface_reconfigure(struct wayland_surface *surface) }
wayland_surface_reconfigure_geometry(surface, width, height); + wayland_surface_reconfigure_size(surface, width, height);
return TRUE; } @@ -691,7 +727,7 @@ void wayland_surface_coords_from_wine_rounded(struct wayland_surface *surface, { int wine_coord = va_arg(ap, int); int *wl_coord = va_arg(ap, int *); - *wl_coord = wine_coord; + *wl_coord = round(wine_coord / surface->preferred_scale); }
va_end(ap); @@ -716,7 +752,7 @@ void wayland_surface_coords_to_wine(struct wayland_surface *surface, { double wl_coord = va_arg(ap, double); int *wine_coord = va_arg(ap, int *); - *wine_coord = round(wl_coord); + *wine_coord = round(wl_coord * surface->preferred_scale); }
va_end(ap); diff --git a/dlls/winewayland.drv/waylanddrv.h b/dlls/winewayland.drv/waylanddrv.h index 0fdb06922b3..0340fb85685 100644 --- a/dlls/winewayland.drv/waylanddrv.h +++ b/dlls/winewayland.drv/waylanddrv.h @@ -28,6 +28,7 @@ #include <pthread.h> #include <wayland-client.h> #include "fractional-scale-v1-client-protocol.h" +#include "viewporter-client-protocol.h" #include "xdg-output-unstable-v1-client-protocol.h" #include "xdg-shell-client-protocol.h"
@@ -101,6 +102,7 @@ struct wayland struct xdg_wm_base *xdg_wm_base; struct wl_shm *wl_shm; struct wp_fractional_scale_manager_v1 *wp_fractional_scale_manager_v1; + struct wp_viewporter *wp_viewporter; struct wayland_seat seat; struct wayland_pointer pointer; struct wl_list output_list; @@ -157,6 +159,7 @@ struct wayland_surface struct xdg_surface *xdg_surface; struct xdg_toplevel *xdg_toplevel; struct wp_fractional_scale_v1 *wp_fractional_scale_v1; + struct wp_viewport *wp_viewport; pthread_mutex_t mutex; struct wayland_surface_config pending, requested, processing, current; struct wayland_shm_buffer *latest_window_buffer;
From: Alexandros Frantzis alexandros.frantzis@collabora.com
--- dlls/winewayland.drv/wayland_pointer.c | 49 ++++++++++++++++++-------- 1 file changed, 35 insertions(+), 14 deletions(-)
diff --git a/dlls/winewayland.drv/wayland_pointer.c b/dlls/winewayland.drv/wayland_pointer.c index bbe61824561..6a976c2ea31 100644 --- a/dlls/winewayland.drv/wayland_pointer.c +++ b/dlls/winewayland.drv/wayland_pointer.c @@ -86,6 +86,8 @@ static void pointer_handle_motion(void *data, struct wl_pointer *wl_pointer, __wine_send_input(hwnd, &input, NULL); }
+static void wayland_set_cursor(HWND hwnd, HCURSOR hcursor, BOOL use_hcursor); + static void pointer_handle_enter(void *data, struct wl_pointer *wl_pointer, uint32_t serial, struct wl_surface *wl_surface, wl_fixed_t sx, wl_fixed_t sy) @@ -103,14 +105,11 @@ static void pointer_handle_enter(void *data, struct wl_pointer *wl_pointer, pthread_mutex_lock(&pointer->mutex); pointer->focused_hwnd = hwnd; pointer->enter_serial = serial; + pthread_mutex_unlock(&pointer->mutex); + /* The cursor is undefined at every enter, so we set it again with * the latest information we have. */ - wl_pointer_set_cursor(pointer->wl_pointer, - pointer->enter_serial, - pointer->cursor.wl_surface, - pointer->cursor.hotspot_x, - pointer->cursor.hotspot_y); - pthread_mutex_unlock(&pointer->mutex); + wayland_set_cursor(hwnd, NULL, FALSE);
/* Handle the enter as a motion, to account for cases where the * window first appears beneath the pointer and won't get a separate @@ -414,7 +413,7 @@ static BOOL get_icon_info(HICON handle, ICONINFOEXW *ret) return TRUE; }
-static void wayland_pointer_update_cursor(HCURSOR hcursor) +static void wayland_pointer_update_cursor_buffer(HCURSOR hcursor) { struct wayland_cursor *cursor = &process_wayland.pointer.cursor; ICONINFOEXW info = {0}; @@ -466,6 +465,22 @@ static void wayland_pointer_update_cursor(HCURSOR hcursor) cursor->hotspot_y = cursor->shm_buffer->height / 2; }
+ return; + +clear_cursor: + if (cursor->shm_buffer) + { + wayland_shm_buffer_unref(cursor->shm_buffer); + cursor->shm_buffer = NULL; + } +} + +static void wayland_pointer_update_cursor_surface(void) +{ + struct wayland_cursor *cursor = &process_wayland.pointer.cursor; + + if (!cursor->shm_buffer) goto clear_cursor; + if (!cursor->wl_surface) { cursor->wl_surface = @@ -500,19 +515,15 @@ clear_cursor: } }
-/*********************************************************************** - * WAYLAND_SetCursor - */ -void WAYLAND_SetCursor(HWND hwnd, HCURSOR hcursor) +static void wayland_set_cursor(HWND hwnd, HCURSOR hcursor, BOOL use_hcursor) { struct wayland_pointer *pointer = &process_wayland.pointer;
- TRACE("hwnd=%p hcursor=%p\n", hwnd, hcursor); - pthread_mutex_lock(&pointer->mutex); if (pointer->focused_hwnd == hwnd) { - wayland_pointer_update_cursor(hcursor); + if (use_hcursor) wayland_pointer_update_cursor_buffer(hcursor); + wayland_pointer_update_cursor_surface(); wl_pointer_set_cursor(pointer->wl_pointer, pointer->enter_serial, pointer->cursor.wl_surface, @@ -522,3 +533,13 @@ void WAYLAND_SetCursor(HWND hwnd, HCURSOR hcursor) } pthread_mutex_unlock(&pointer->mutex); } + +/*********************************************************************** + * WAYLAND_SetCursor + */ +void WAYLAND_SetCursor(HWND hwnd, HCURSOR hcursor) +{ + TRACE("hwnd=%p hcursor=%p\n", hwnd, hcursor); + + wayland_set_cursor(hwnd, hcursor, TRUE); +}
From: Alexandros Frantzis alexandros.frantzis@collabora.com
Take into account the preferred scale factor and use wp_viewport to instruct the compositor to present cursors at their native pixel sizes. This effectively disables compositor side scaling for cursors. --- dlls/winewayland.drv/wayland_pointer.c | 56 ++++++++++++++++++++++++-- dlls/winewayland.drv/wayland_surface.c | 1 + dlls/winewayland.drv/waylanddrv.h | 2 + 3 files changed, 55 insertions(+), 4 deletions(-)
diff --git a/dlls/winewayland.drv/wayland_pointer.c b/dlls/winewayland.drv/wayland_pointer.c index 6a976c2ea31..4679d124499 100644 --- a/dlls/winewayland.drv/wayland_pointer.c +++ b/dlls/winewayland.drv/wayland_pointer.c @@ -413,7 +413,7 @@ static BOOL get_icon_info(HICON handle, ICONINFOEXW *ret) return TRUE; }
-static void wayland_pointer_update_cursor_buffer(HCURSOR hcursor) +static void wayland_pointer_update_cursor_buffer(HCURSOR hcursor, double scale) { struct wayland_cursor *cursor = &process_wayland.pointer.cursor; ICONINFOEXW info = {0}; @@ -465,6 +465,9 @@ static void wayland_pointer_update_cursor_buffer(HCURSOR hcursor) cursor->hotspot_y = cursor->shm_buffer->height / 2; }
+ cursor->hotspot_x = round(cursor->hotspot_x / scale); + cursor->hotspot_y = round(cursor->hotspot_y / scale); + return;
clear_cursor: @@ -475,7 +478,7 @@ clear_cursor: } }
-static void wayland_pointer_update_cursor_surface(void) +static void wayland_pointer_update_cursor_surface(double scale) { struct wayland_cursor *cursor = &process_wayland.pointer.cursor;
@@ -492,12 +495,32 @@ static void wayland_pointer_update_cursor_surface(void) } }
+ if (!cursor->wp_viewport && process_wayland.wp_viewporter) + { + cursor->wp_viewport = + wp_viewporter_get_viewport(process_wayland.wp_viewporter, + cursor->wl_surface); + if (!cursor->wp_viewport) + WARN("Failed to create wp_viewport for cursor\n"); + } + /* Commit the cursor buffer to the cursor surface. */ wl_surface_attach(cursor->wl_surface, cursor->shm_buffer->wl_buffer, 0, 0); wl_surface_damage_buffer(cursor->wl_surface, 0, 0, cursor->shm_buffer->width, cursor->shm_buffer->height); + /* Setting only the viewport is enough, but some compositors don't + * support wp_viewport for cursor surfaces, so also set the buffer + * scale. Note that setting the viewport destination overrides + * the buffer scale, so it's fine to set both. */ + wl_surface_set_buffer_scale(cursor->wl_surface, round(scale)); + if (cursor->wp_viewport) + { + wp_viewport_set_destination(cursor->wp_viewport, + round(cursor->shm_buffer->width / scale), + round(cursor->shm_buffer->height / scale)); + } wl_surface_commit(cursor->wl_surface);
return; @@ -508,6 +531,11 @@ clear_cursor: wayland_shm_buffer_unref(cursor->shm_buffer); cursor->shm_buffer = NULL; } + if (cursor->wp_viewport) + { + wp_viewport_destroy(cursor->wp_viewport); + cursor->wp_viewport = NULL; + } if (cursor->wl_surface) { wl_surface_destroy(cursor->wl_surface); @@ -518,12 +546,24 @@ clear_cursor: static void wayland_set_cursor(HWND hwnd, HCURSOR hcursor, BOOL use_hcursor) { struct wayland_pointer *pointer = &process_wayland.pointer; + struct wayland_surface *surface; + double scale; + + if ((surface = wayland_surface_lock_hwnd(hwnd))) + { + scale = surface->preferred_scale; + pthread_mutex_unlock(&surface->mutex); + } + else + { + scale = 1.0; + }
pthread_mutex_lock(&pointer->mutex); if (pointer->focused_hwnd == hwnd) { - if (use_hcursor) wayland_pointer_update_cursor_buffer(hcursor); - wayland_pointer_update_cursor_surface(); + if (use_hcursor) wayland_pointer_update_cursor_buffer(hcursor, scale); + wayland_pointer_update_cursor_surface(scale); wl_pointer_set_cursor(pointer->wl_pointer, pointer->enter_serial, pointer->cursor.wl_surface, @@ -543,3 +583,11 @@ void WAYLAND_SetCursor(HWND hwnd, HCURSOR hcursor)
wayland_set_cursor(hwnd, hcursor, TRUE); } + +/*********************************************************************** + * wayland_reapply_cursor + */ +void wayland_reapply_cursor(HWND hwnd) +{ + wayland_set_cursor(hwnd, NULL, FALSE); +} diff --git a/dlls/winewayland.drv/wayland_surface.c b/dlls/winewayland.drv/wayland_surface.c index 72c60959a1b..354a7990245 100644 --- a/dlls/winewayland.drv/wayland_surface.c +++ b/dlls/winewayland.drv/wayland_surface.c @@ -146,6 +146,7 @@ static void wp_fractional_scale_v1_handle_preferred_scale(
/* Force a redraw so the new scale is applied. */ NtUserRedrawWindow(hwnd, NULL, NULL, RDW_INVALIDATE | RDW_FRAME); + wayland_reapply_cursor(hwnd); }
static const struct wp_fractional_scale_v1_listener wp_fractional_scale_v1_listener = diff --git a/dlls/winewayland.drv/waylanddrv.h b/dlls/winewayland.drv/waylanddrv.h index 0340fb85685..284d42ef8eb 100644 --- a/dlls/winewayland.drv/waylanddrv.h +++ b/dlls/winewayland.drv/waylanddrv.h @@ -71,6 +71,7 @@ struct wayland_cursor { struct wayland_shm_buffer *shm_buffer; struct wl_surface *wl_surface; + struct wp_viewport *wp_viewport; int hotspot_x, hotspot_y; };
@@ -240,6 +241,7 @@ void wayland_window_flush(HWND hwnd) DECLSPEC_HIDDEN;
void wayland_pointer_init(struct wl_pointer *wl_pointer) DECLSPEC_HIDDEN; void wayland_pointer_deinit(void) DECLSPEC_HIDDEN; +void wayland_reapply_cursor(HWND hwnd) DECLSPEC_HIDDEN;
/********************************************************************** * Helpers
From: Alexandros Frantzis alexandros.frantzis@collabora.com
We need the fractional-scale-v1 and viewporter protocols to enable the application to access the native output resolution and support application side scaling. If this protocol is not available, fall back to compositor scaling which requires that we deal with all coordinates and sizes as if they were expressed in the logical compositor space. --- dlls/winewayland.drv/wayland.c | 9 ++++ dlls/winewayland.drv/wayland_output.c | 65 ++++++++++++++++++++++++++ dlls/winewayland.drv/wayland_surface.c | 3 +- dlls/winewayland.drv/waylanddrv.h | 3 ++ dlls/winewayland.drv/waylanddrv_main.c | 2 + 5 files changed, 81 insertions(+), 1 deletion(-)
diff --git a/dlls/winewayland.drv/wayland.c b/dlls/winewayland.drv/wayland.c index 4a09567ba0c..846ebbd8940 100644 --- a/dlls/winewayland.drv/wayland.c +++ b/dlls/winewayland.drv/wayland.c @@ -230,6 +230,15 @@ BOOL wayland_process_init(void) /* We need two roundtrips. One to get and bind globals, one to handle all * initial events produced from registering the globals. */ wl_display_roundtrip_queue(process_wayland.wl_display, process_wayland.wl_event_queue); + /* Check for optional protocol globals after the first roundtrip, since + * subsequent events may rely on options we set here. */ + if (!process_wayland.wp_fractional_scale_manager_v1 || + !process_wayland.wp_viewporter) + { + WARN("Wayland compositor doesn't support required protocols for" + " application scaling, falling back to compositor scaling\n"); + option_use_compositor_scaling = TRUE; + } wl_display_roundtrip_queue(process_wayland.wl_display, process_wayland.wl_event_queue);
/* Check for required protocol globals. */ diff --git a/dlls/winewayland.drv/wayland_output.c b/dlls/winewayland.drv/wayland_output.c index f5941c10f6f..a72e37de1a5 100644 --- a/dlls/winewayland.drv/wayland_output.c +++ b/dlls/winewayland.drv/wayland_output.c @@ -39,6 +39,7 @@ static uint32_t next_output_id = 0; #define WAYLAND_OUTPUT_CHANGED_NAME 0x02 #define WAYLAND_OUTPUT_CHANGED_LOGICAL_XY 0x04 #define WAYLAND_OUTPUT_CHANGED_LOGICAL_WH 0x08 +#define WAYLAND_OUTPUT_CHANGED_SCALE 0x10
/********************************************************************** * Output handling @@ -102,6 +103,25 @@ static void wayland_output_state_add_mode(struct wayland_output_state *state, if (current) state->current_mode = mode; }
+static void wayland_output_set_native_mode(struct wayland_output *output, + struct wayland_output_mode *mode) +{ + free(output->native_mode); + output->native_mode = NULL; + if (mode) + { + output->native_mode = calloc(1, sizeof(*output->native_mode)); + if (!output->native_mode) + { + ERR("Failed to allocate space for native wayland_output_mode\n"); + return; + } + output->native_mode->width = mode->width; + output->native_mode->height = mode->height; + output->native_mode->refresh = mode->refresh; + } +} + static void maybe_init_display_devices(void) { DWORD desktop_pid = 0; @@ -144,6 +164,7 @@ static void wayland_output_done(struct wayland_output *output) } rb_destroy(&output->pending.modes, wayland_output_mode_free_rb, NULL); rb_init(&output->pending.modes, wayland_output_mode_cmp_rb); + wayland_output_set_native_mode(output, output->current.current_mode); }
if (output->pending_flags & WAYLAND_OUTPUT_CHANGED_NAME) @@ -165,8 +186,45 @@ static void wayland_output_done(struct wayland_output *output) output->current.logical_h = output->pending.logical_h; }
+ if (output->pending_flags & WAYLAND_OUTPUT_CHANGED_SCALE) + output->current.scale = output->pending.scale; + output->pending_flags = 0;
+ /* When compositor scaling is used, the current and only mode + * corresponds to the logical width and height. */ + if (option_use_compositor_scaling) + { + int refresh = default_refresh; + int width = output->current.logical_w; + int height = output->current.logical_h; + + if (output->native_mode) + { + refresh = output->native_mode->refresh; + /* Some compositors report logical dimensions that match + * the native ones even when the output is scaled. We + * can't use these, so infer the actual logical dimensions + * on our own. */ + if (output->current.scale != 1 && + width == output->native_mode->width && + height == output->native_mode->height) + { + width = output->native_mode->width / output->current.scale; + height = output->native_mode->height / output->current.scale; + } + } + + output->current.current_mode = NULL; + rb_destroy(&output->current.modes, wayland_output_mode_free_rb, NULL); + + if (width && height) + { + wayland_output_state_add_mode(&output->current, width, height, + refresh, TRUE); + } + } + /* Ensure the logical dimensions have sane values. */ if ((!output->current.logical_w || !output->current.logical_h) && output->current.current_mode) @@ -229,6 +287,10 @@ static void output_handle_done(void *data, struct wl_output *wl_output) static void output_handle_scale(void *data, struct wl_output *wl_output, int32_t scale) { + struct wayland_output *output = data; + + output->pending.scale = scale; + output->pending_flags |= WAYLAND_OUTPUT_CHANGED_SCALE; }
static const struct wl_output_listener output_listener = { @@ -336,6 +398,8 @@ BOOL wayland_output_create(uint32_t id, uint32_t version) goto err; }
+ output->current.scale = 1; + if (process_wayland.zxdg_output_manager_v1) wayland_output_use_xdg_extension(output);
@@ -369,6 +433,7 @@ void wayland_output_destroy(struct wayland_output *output)
wayland_output_state_deinit(&output->pending); wayland_output_state_deinit(&output->current); + free(output->native_mode); if (output->zxdg_output_v1) zxdg_output_v1_destroy(output->zxdg_output_v1); wl_output_destroy(output->wl_output); diff --git a/dlls/winewayland.drv/wayland_surface.c b/dlls/winewayland.drv/wayland_surface.c index 354a7990245..8ed18745e2b 100644 --- a/dlls/winewayland.drv/wayland_surface.c +++ b/dlls/winewayland.drv/wayland_surface.c @@ -184,7 +184,8 @@ struct wayland_surface *wayland_surface_create(HWND hwnd) wl_surface_set_user_data(surface->wl_surface, hwnd);
surface->preferred_scale = 1.0; - if (process_wayland.wp_fractional_scale_manager_v1) + if (process_wayland.wp_fractional_scale_manager_v1 && + !option_use_compositor_scaling) { surface->wp_fractional_scale_v1 = wp_fractional_scale_manager_v1_get_fractional_scale( diff --git a/dlls/winewayland.drv/waylanddrv.h b/dlls/winewayland.drv/waylanddrv.h index 284d42ef8eb..5a85137ff85 100644 --- a/dlls/winewayland.drv/waylanddrv.h +++ b/dlls/winewayland.drv/waylanddrv.h @@ -48,6 +48,7 @@ */
extern struct wayland process_wayland DECLSPEC_HIDDEN; +extern BOOL option_use_compositor_scaling DECLSPEC_HIDDEN;
/********************************************************************** * Definitions for wayland types @@ -126,6 +127,7 @@ struct wayland_output_state char *name; int logical_x, logical_y; int logical_w, logical_h; + int scale; };
struct wayland_output @@ -137,6 +139,7 @@ struct wayland_output unsigned int pending_flags; struct wayland_output_state pending; struct wayland_output_state current; + struct wayland_output_mode *native_mode; };
struct wayland_surface_config diff --git a/dlls/winewayland.drv/waylanddrv_main.c b/dlls/winewayland.drv/waylanddrv_main.c index 7151d7b931a..12b81fd1f0f 100644 --- a/dlls/winewayland.drv/waylanddrv_main.c +++ b/dlls/winewayland.drv/waylanddrv_main.c @@ -29,6 +29,8 @@
#include "waylanddrv.h"
+BOOL option_use_compositor_scaling = FALSE; + static const struct user_driver_funcs waylanddrv_funcs = { .pDesktopWindowProc = WAYLAND_DesktopWindowProc,
From: Alexandros Frantzis alexandros.frantzis@collabora.com
Due to the asynchronous nature of Wayland events, and the design of the xdg_toplevel protocol, an xdg configure event may be a reaction to a request in the application's configuration past, and the size hint may be out of date. For example:
1. The client commits a 100x100 buffer to a surface. 2.1 The compositor sends xdg configure(100x100, state=activated). 2.2 In the meantime, the client resizes and commits a 50x50 buffer. 3. The client receives the event from (2.1). If we respect the size hint, we will resize back to 100x100, although this was neither the client's nor the compositor's intention.
To mitigate this we ignore size hints for states that do not require strict size adherence. --- dlls/winewayland.drv/window.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-)
diff --git a/dlls/winewayland.drv/window.c b/dlls/winewayland.drv/window.c index 35b9ab258a3..86267cbc989 100644 --- a/dlls/winewayland.drv/window.c +++ b/dlls/winewayland.drv/window.c @@ -411,9 +411,18 @@ static void wayland_configure_window(HWND hwnd) surface->processing = surface->requested; memset(&surface->requested, 0, sizeof(surface->requested));
- width = surface->processing.width; - height = surface->processing.height; state = surface->processing.state; + /* Ignore size hints if we don't have a state that requires strict + * size adherence, in order to avoid spurious resizes. */ + if (state) + { + width = surface->processing.width; + height = surface->processing.height; + } + else + { + width = height = 0; + }
if ((state & WAYLAND_SURFACE_CONFIG_STATE_RESIZING) && !surface->resizing) {
Do I understand correctly, that with this change and on HiDPI displays, Wine windows (defaulting at 96dpi) will appear as small windows by default? This doesn't look very desirable to me?
Also, I don't know how well our DPI code is, but I haven't seen much activity on that side and I would not bet that it's doing super well (the look of winecfg with non-default DPI already gives me a hunch that it's not perfect). So IMO you're going into troubled waters if you want to rely on it.
Still, I don't mind looking at this, but fwiw it feels a bit premature to do this now.
On Mon Oct 30 09:50:45 2023 +0000, Rémi Bernon wrote:
Do I understand correctly, that with this change and on HiDPI displays, Wine windows (defaulting at 96dpi) will appear as small windows by default? This doesn't look very desirable to me? Also, I don't know how well our DPI code is, but I haven't seen much activity on that side and I would not bet that it's doing super well (the look of winecfg with non-default DPI already gives me a hunch that it's not perfect). So IMO you're going into troubled waters if you want to rely on it. Still, I don't mind looking at this, but fwiw it feels a bit premature to do this now.
Hi! The purpose of this branch is to allow applications to render at the physical resolution of a display (in the default "application" scaling mode). That is, when a Wine application draws 10 pixels, these will end up as 10 physical pixels on screen.
The main reason I want this at this point, is to allow applications, typically games, to use the full physical resolution even on scaled outputs (including when becoming fullscreen). Without this change the compositor will unconditionally scale surfaces contents, so (depending on the Wine/Wayland size transformation details) the contents will be either presented as larger than expected or blurrier than expected.
As the code stands now (before this MR), scaled HiDPI displays are not properly supported, even with unconditional compositor scaling, since we are mixing up physical and logical coordinates at various points (for scale=1 these match so this hasn't been a problem there).
Do I understand correctly, that with this change and on HiDPI displays, Wine windows (defaulting at 96dpi) will appear as small windows by default? This doesn't look very desirable to me?
Yes, but to be clear, that's just because we are rendering at physical pixel sizes, it's not that we are making them artificially smaller in some way. This matches current WineX11 behavior (both with native X11 and Xwayland). Not that matching WineX11 is always the best way to go, but at least people won't be surprised.
As mentioned in the description, the plan is to have a driver registry option to set the scaling mode (app vs compositor) globally or per app, so users will be able to select the mode that suits their use case best, which is already more flexible than what WineX11 provides (if I have not missed something).
We could make compositor scaling the default, although then I expect people with scaled outputs will be confused about why their favorite game doesn't render as expected in fullscreen.
Ideally we would properly integrate Wayland support for HiDPI with the win32 DPI awareness modes, but that will require some more thought and not all modes are fully (or at all) implemented in Wine anyway.
Also, I don't know how well our DPI code is, but I haven't seen much activity on that side and I would not bet that it's doing super well (the look of winecfg with non-default DPI already gives me a hunch that it's not perfect). So IMO you're going into troubled waters if you want to rely on it.
I don't want to rely on this mechanism in particular, but I would like to allow applications to perform their own scaling (or no scaling), which requires that I avoid forcing compositor side scaling.
Still, I don't mind looking at this, but fwiw it feels a bit premature to do this now.
To sum up, here are my reasons for proposing this now:
1. Allow apps/games to use the full resolution on scaled outputs, which are quite common these days (and it personally annoys me since I use such scaling :))
(Secondary benefits)
2. It gives us an opportunity to remove the assumption that Wine coords == Wayland surface coords, sooner rather than later, before that assumption has sneakily permeated even more of the codebase. 3. The scaling infrastructure (incl. coordinate transformations) used here will be also used for emulating display mode changes, so it's more generally useful.
On Mon Oct 30 09:50:45 2023 +0000, Alexandros Frantzis wrote:
Hi! The purpose of this branch is to allow applications to render at the physical resolution of a display (in the default "application" scaling mode). That is, when a Wine application draws 10 pixels, these will end up as 10 physical pixels on screen. The main reason I want this at this point, is to allow applications, typically games, to use the full physical resolution even on scaled outputs (including when becoming fullscreen). Without this change the compositor will unconditionally scale surfaces contents, so (depending on the Wine/Wayland size transformation details) the contents will be either presented as larger than expected or blurrier than expected. As the code stands now (before this MR), scaled HiDPI displays are not properly supported, even with unconditional compositor scaling, since we are mixing up physical and logical coordinates at various points (for scale=1 these match so this hasn't been a problem there).
Do I understand correctly, that with this change and on HiDPI
displays, Wine
windows (defaulting at 96dpi) will appear as small windows by default? This doesn't look very desirable to me?
Yes, but to be clear, that's just because we are rendering at physical pixel sizes, it's not that we are making them artificially smaller in some way. This matches current WineX11 behavior (both with native X11 and Xwayland). Not that matching WineX11 is always the best way to go, but at least people won't be surprised. As mentioned in the description, the plan is to have a driver registry option to set the scaling mode (app vs compositor) globally or per app, so users will be able to select the mode that suits their use case best, which is already more flexible than what WineX11 provides (if I have not missed something). We could make compositor scaling the default, although then I expect people with scaled outputs will be confused about why their favorite game doesn't render as expected in fullscreen. Ideally we would properly integrate Wayland support for HiDPI with the win32 DPI awareness modes, but that will require some more thought and not all modes are fully (or at all) implemented in Wine anyway.
Also, I don't know how well our DPI code is, but I haven't seen much activity on that side and I would not bet that it's doing super well (the look of winecfg with non-default DPI already gives me a hunch that it's not perfect). So IMO you're going into troubled waters if you want to rely on it.
I don't want to rely on this mechanism in particular, but I would like to allow applications to perform their own scaling (or no scaling), which requires that I avoid forcing compositor side scaling.
Still, I don't mind looking at this, but fwiw it feels a bit premature
to do this now. To sum up, here are my reasons for proposing this now:
- Allow apps/games to use the full resolution on scaled outputs, which are quite common these days (and it personally annoys me since I use such
scaling :)) (Secondary benefits) 2. It gives us an opportunity to remove the assumption that Wine coords == Wayland surface coords, sooner rather than later, before that assumption has sneakily permeated even more of the codebase. 3. The scaling infrastructure (incl. coordinate transformations) used here will be also used for emulating display mode changes, so it's more generally useful.
I'd like to support the argument that this would be great for gaming. I use a 4k display with 2x scaling and every time I start a game I have to disable scaling in sway and reposition all my screens because of this. HiDPI support for wine wayland would fix one of my main annoyances while gaming on wayland.
Ideally we would properly integrate Wayland support for HiDPI with the win32 DPI awareness modes, but that will require some more thought and not all modes are fully (or at all) implemented in Wine anyway.
Yes, that's the way to go. If some things are missing in user32 please let's fix that instead of adding another DPI support layer in the driver.