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.
diff --git a/README.md b/README.md
index a05f62c..948b321 100644
--- a/README.md
+++ b/README.md
@@ -182,6 +182,7 @@
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 0000000..350bfc0
--- /dev/null
+++ b/deps/wayland/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/docs/compat.md b/docs/compat.md
index 202b8a4..ef64b0c 100644
--- a/docs/compat.md
+++ b/docs/compat.md
@@ -150,6 +150,14 @@
[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 66edf4f..6423d61 100644
--- a/include/GLFW/glfw3.h
+++ b/include/GLFW/glfw3.h
@@ -2705,6 +2705,9 @@
* @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 4594164..1057a6f 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -101,6 +101,7 @@
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 2644e89..3aff476 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 @@
&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 @@
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 cb9b170..76d7c9c 100644
--- a/src/wl_platform.h
+++ b/src/wl_platform.h
@@ -131,6 +131,8 @@
#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 @@
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 @@
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 0a1554d..8a402a4 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 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 @@
{
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 @@
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 @@
}
}
+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 @@
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 @@
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 _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)