From 2b3f919b6055e6837ca0fad193c7f96b323f1256 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Camilla=20L=C3=B6wy?= Date: Thu, 8 Feb 2024 22:08:31 +0100 Subject: [PATCH] Wayland: Add support for fractional scaling This adds basic support for fractional-scale-v1. Note that this introduces a potential discrepancy between window and monitor content scales. --- README.md | 1 + deps/wayland/fractional-scale-v1.xml | 102 +++++++++++++++++++++++++++ docs/compat.md | 8 +++ include/GLFW/glfw3.h | 3 + src/CMakeLists.txt | 1 + src/wl_init.c | 14 ++++ src/wl_platform.h | 7 ++ src/wl_window.c | 87 +++++++++++++++++++++-- 8 files changed, 216 insertions(+), 7 deletions(-) create mode 100644 deps/wayland/fractional-scale-v1.xml diff --git a/README.md b/README.md index a05f62c0..948b3212 100644 --- a/README.md +++ b/README.md @@ -182,6 +182,7 @@ information on what to include when reporting a bug. would abort (#1649) - [Wayland] Added support for `glfwRequestWindowAttention` (#2287) - [Wayland] Added support for `glfwFocusWindow` + - [Wayland] Added support for fractional scaling of window contents - [Wayland] Added dynamic loading of all Wayland libraries - [Wayland] Bugfix: `CLOCK_MONOTONIC` was not correctly enabled - [Wayland] Bugfix: `GLFW_HOVERED` was true when the cursor was over any diff --git a/deps/wayland/fractional-scale-v1.xml b/deps/wayland/fractional-scale-v1.xml new file mode 100644 index 00000000..350bfc01 --- /dev/null +++ b/deps/wayland/fractional-scale-v1.xml @@ -0,0 +1,102 @@ + + + + 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. + + + + 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. + + + + + A global interface for requesting surfaces to use fractional scales. + + + + + 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. + + + + + + + + + + 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. + + + + + + + + + An additional interface to a wl_surface object which allows the compositor + to inform the client of the preferred scale. + + + + + Destroy the fractional scale object. When this object is destroyed, + preferred_scale events will no longer be sent. + + + + + + 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. + + + + + diff --git a/docs/compat.md b/docs/compat.md index 202b8a4e..ef64b0cc 100644 --- a/docs/compat.md +++ b/docs/compat.md @@ -150,6 +150,14 @@ window focus and attention requests do nothing. [xdg-activation-v1]: https://wayland.app/protocols/xdg-activation-v1 +GLFW uses the [fractional-scale-v1][] protocol to implement fine-grained +framebuffer scaling. If the running compositor does not support this protocol, +the @ref GLFW_SCALE_FRAMEBUFFER window hint will only be able to scale the +framebuffer by integer scales. This will typically be the smallest integer not +less than the actual scale. + +[fractional-scale-v1]: https://wayland.app/protocols/fractional-scale-v1 + ## GLX extensions {#compat_glx} diff --git a/include/GLFW/glfw3.h b/include/GLFW/glfw3.h index 66edf4fc..6423d61f 100644 --- a/include/GLFW/glfw3.h +++ b/include/GLFW/glfw3.h @@ -2705,6 +2705,9 @@ GLFWAPI void glfwGetMonitorPhysicalSize(GLFWmonitor* monitor, int* widthMM, int* * @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref * GLFW_PLATFORM_ERROR. * + * @remark @wayland Fractional scaling information is not yet available for + * monitors, so this function only returns integer content scales. + * * @thread_safety This function must only be called from the main thread. * * @sa @ref monitor_scale diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 45941647..1057a6f9 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -101,6 +101,7 @@ if (GLFW_BUILD_WAYLAND) generate_wayland_protocol("idle-inhibit-unstable-v1.xml") generate_wayland_protocol("pointer-constraints-unstable-v1.xml") generate_wayland_protocol("relative-pointer-unstable-v1.xml") + generate_wayland_protocol("fractional-scale-v1.xml") generate_wayland_protocol("xdg-activation-v1.xml") generate_wayland_protocol("xdg-decoration-unstable-v1.xml") endif() diff --git a/src/wl_init.c b/src/wl_init.c index 2644e89a..3aff476d 100644 --- a/src/wl_init.c +++ b/src/wl_init.c @@ -46,6 +46,7 @@ #include "viewporter-client-protocol.h" #include "relative-pointer-unstable-v1-client-protocol.h" #include "pointer-constraints-unstable-v1-client-protocol.h" +#include "fractional-scale-v1-client-protocol.h" #include "xdg-activation-v1-client-protocol.h" #include "idle-inhibit-unstable-v1-client-protocol.h" @@ -78,6 +79,10 @@ #include "pointer-constraints-unstable-v1-client-protocol-code.h" #undef types +#define types _glfw_fractional_scale_types +#include "fractional-scale-v1-client-protocol-code.h" +#undef types + #define types _glfw_xdg_activation_types #include "xdg-activation-v1-client-protocol-code.h" #undef types @@ -189,6 +194,13 @@ static void registryHandleGlobal(void* userData, &xdg_activation_v1_interface, 1); } + else if (strcmp(interface, "wp_fractional_scale_manager_v1") == 0) + { + _glfw.wl.fractionalScaleManager = + wl_registry_bind(registry, name, + &wp_fractional_scale_manager_v1_interface, + 1); + } } static void registryHandleGlobalRemove(void* userData, @@ -969,6 +981,8 @@ void _glfwTerminateWayland(void) zwp_idle_inhibit_manager_v1_destroy(_glfw.wl.idleInhibitManager); if (_glfw.wl.activationManager) xdg_activation_v1_destroy(_glfw.wl.activationManager); + if (_glfw.wl.fractionalScaleManager) + wp_fractional_scale_manager_v1_destroy(_glfw.wl.fractionalScaleManager); if (_glfw.wl.registry) wl_registry_destroy(_glfw.wl.registry); if (_glfw.wl.display) diff --git a/src/wl_platform.h b/src/wl_platform.h index cb9b170f..76d7c9cb 100644 --- a/src/wl_platform.h +++ b/src/wl_platform.h @@ -131,6 +131,8 @@ struct wl_output; #define xdg_wm_base_interface _glfw_xdg_wm_base_interface #define xdg_activation_v1_interface _glfw_xdg_activation_v1_interface #define xdg_activation_token_v1_interface _glfw_xdg_activation_token_v1_interface +#define wl_surface_interface _glfw_wl_surface_interface +#define wp_fractional_scale_v1_interface _glfw_wp_fractional_scale_v1_interface #define GLFW_WAYLAND_WINDOW_STATE _GLFWwindowWayland wl; #define GLFW_WAYLAND_LIBRARY_WINDOW_STATE _GLFWlibraryWayland wl; @@ -395,6 +397,10 @@ typedef struct _GLFWwindowWayland size_t outputScaleCount; size_t outputScaleSize; + struct wp_viewport* scalingViewport; + uint32_t scalingNumerator; + struct wp_fractional_scale_v1* fractionalScale; + struct zwp_relative_pointer_v1* relativePointer; struct zwp_locked_pointer_v1* lockedPointer; struct zwp_confined_pointer_v1* confinedPointer; @@ -431,6 +437,7 @@ typedef struct _GLFWlibraryWayland struct zwp_pointer_constraints_v1* pointerConstraints; struct zwp_idle_inhibit_manager_v1* idleInhibitManager; struct xdg_activation_v1* activationManager; + struct wp_fractional_scale_manager_v1* fractionalScaleManager; _GLFWofferWayland* offers; unsigned int offerCount; diff --git a/src/wl_window.c b/src/wl_window.c index 0a1554d1..8a402a43 100644 --- a/src/wl_window.c +++ b/src/wl_window.c @@ -50,6 +50,7 @@ #include "pointer-constraints-unstable-v1-client-protocol.h" #include "xdg-activation-v1-client-protocol.h" #include "idle-inhibit-unstable-v1-client-protocol.h" +#include "fractional-scale-v1-client-protocol.h" #define GLFW_BORDER_SIZE 4 #define GLFW_CAPTION_HEIGHT 24 @@ -312,8 +313,16 @@ static void setContentAreaOpaque(_GLFWwindow* window) static void resizeFramebuffer(_GLFWwindow* window) { - window->wl.fbWidth = window->wl.width * window->wl.bufferScale; - window->wl.fbHeight = window->wl.height * window->wl.bufferScale; + if (window->wl.fractionalScale) + { + window->wl.fbWidth = (window->wl.width * window->wl.scalingNumerator) / 120; + window->wl.fbHeight = (window->wl.height * window->wl.scalingNumerator) / 120; + } + else + { + window->wl.fbWidth = window->wl.width * window->wl.bufferScale; + window->wl.fbHeight = window->wl.height * window->wl.bufferScale; + } if (window->wl.egl.window) { @@ -333,6 +342,13 @@ static void resizeWindow(_GLFWwindow* window) { resizeFramebuffer(window); + if (window->wl.scalingViewport) + { + wp_viewport_set_destination(window->wl.scalingViewport, + window->wl.width, + window->wl.height); + } + if (window->wl.fallback.decorations) { wp_viewport_set_destination(window->wl.fallback.top.viewport, @@ -372,6 +388,10 @@ void _glfwUpdateBufferScaleFromOutputsWayland(_GLFWwindow* window) if (!window->wl.scaleFramebuffer) return; + // When using fractional scaling, the buffer scale should remain at 1 + if (window->wl.fractionalScale) + return; + // Get the scale factor from the highest scale monitor. int32_t maxScale = 1; @@ -505,6 +525,25 @@ static void releaseMonitor(_GLFWwindow* window) } } +void fractionalScaleHandlePreferredScale(void* userData, + struct wp_fractional_scale_v1* fractionalScale, + uint32_t numerator) +{ + _GLFWwindow* window = userData; + + window->wl.scalingNumerator = numerator; + _glfwInputWindowContentScale(window, numerator / 120.f, numerator / 120.f); + resizeFramebuffer(window); + + if (window->wl.visible) + _glfwInputWindowDamage(window); +} + +const struct wp_fractional_scale_v1_listener fractionalScaleListener = +{ + fractionalScaleHandlePreferredScale, +}; + static void xdgToplevelHandleConfigure(void* userData, struct xdg_toplevel* toplevel, int32_t width, @@ -980,9 +1019,11 @@ static GLFWbool createNativeSurface(_GLFWwindow* window, window->wl.height = wndconfig->height; window->wl.fbWidth = wndconfig->width; window->wl.fbHeight = wndconfig->height; - window->wl.bufferScale = 1; window->wl.title = _glfw_strdup(wndconfig->title); window->wl.appId = _glfw_strdup(wndconfig->wl.appId); + + window->wl.bufferScale = 1; + window->wl.scalingNumerator = 120; window->wl.scaleFramebuffer = wndconfig->scaleFramebuffer; window->wl.maximized = wndconfig->maximized; @@ -991,6 +1032,28 @@ static GLFWbool createNativeSurface(_GLFWwindow* window, if (!window->wl.transparent) setContentAreaOpaque(window); + if (_glfw.wl.fractionalScaleManager) + { + if (window->wl.scaleFramebuffer) + { + window->wl.scalingViewport = + wp_viewporter_get_viewport(_glfw.wl.viewporter, window->wl.surface); + + wp_viewport_set_destination(window->wl.scalingViewport, + window->wl.width, + window->wl.height); + + window->wl.fractionalScale = + wp_fractional_scale_manager_v1_get_fractional_scale( + _glfw.wl.fractionalScaleManager, + window->wl.surface); + + wp_fractional_scale_v1_add_listener(window->wl.fractionalScale, + &fractionalScaleListener, + window); + } + } + return GLFW_TRUE; } @@ -2315,10 +2378,20 @@ void _glfwGetWindowFrameSizeWayland(_GLFWwindow* window, void _glfwGetWindowContentScaleWayland(_GLFWwindow* window, float* xscale, float* yscale) { - if (xscale) - *xscale = (float) window->wl.bufferScale; - if (yscale) - *yscale = (float) window->wl.bufferScale; + if (window->wl.fractionalScale) + { + if (xscale) + *xscale = (float) window->wl.scalingNumerator / 120.f; + if (yscale) + *yscale = (float) window->wl.scalingNumerator / 120.f; + } + else + { + if (xscale) + *xscale = (float) window->wl.bufferScale; + if (yscale) + *yscale = (float) window->wl.bufferScale; + } } void _glfwIconifyWindowWayland(_GLFWwindow* window)