[client] wayland: improve fractional scale handling

Currently, we scale the desktop up to the next largest integer, and rely on
the wayland compositor to scale it back down to the correct size.
This is obviously undesirable.

In this commit, we attempt to detect the actual fractional scaling by finding
the current active mode in wl_output, and dividing it by the logical screen
size reported by xdg_output, taking into consideration screen rotation.

We then use wp_viewporter to set the exact buffer and viewport sizes if
fractional scaling is needed.
This commit is contained in:
Quantum 2021-07-31 21:56:47 -04:00 committed by Geoffrey McRae
parent 3baed05728
commit b3ca872cef
6 changed files with 152 additions and 19 deletions

View File

@ -74,6 +74,9 @@ wayland_generate(
wayland_generate( wayland_generate(
"${WAYLAND_PROTOCOLS_BASE}/stable/presentation-time/presentation-time.xml" "${WAYLAND_PROTOCOLS_BASE}/stable/presentation-time/presentation-time.xml"
"${CMAKE_BINARY_DIR}/wayland/wayland-presentation-time-client-protocol") "${CMAKE_BINARY_DIR}/wayland/wayland-presentation-time-client-protocol")
wayland_generate(
"${WAYLAND_PROTOCOLS_BASE}/stable/viewporter/viewporter.xml"
"${CMAKE_BINARY_DIR}/wayland/wayland-viewporter-client-protocol")
wayland_generate( wayland_generate(
"${WAYLAND_PROTOCOLS_BASE}/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml" "${WAYLAND_PROTOCOLS_BASE}/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml"
"${CMAKE_BINARY_DIR}/wayland/wayland-xdg-decoration-unstable-v1-client-protocol") "${CMAKE_BINARY_DIR}/wayland/wayland-xdg-decoration-unstable-v1-client-protocol")
@ -89,3 +92,6 @@ wayland_generate(
wayland_generate( wayland_generate(
"${WAYLAND_PROTOCOLS_BASE}/unstable/idle-inhibit/idle-inhibit-unstable-v1.xml" "${WAYLAND_PROTOCOLS_BASE}/unstable/idle-inhibit/idle-inhibit-unstable-v1.xml"
"${CMAKE_BINARY_DIR}/wayland/wayland-idle-inhibit-unstable-v1-client-protocol") "${CMAKE_BINARY_DIR}/wayland/wayland-idle-inhibit-unstable-v1-client-protocol")
wayland_generate(
"${WAYLAND_PROTOCOLS_BASE}/unstable/xdg-output/xdg-output-unstable-v1.xml"
"${CMAKE_BINARY_DIR}/wayland/wayland-xdg-output-unstable-v1-client-protocol")

View File

@ -88,15 +88,37 @@ void waylandEGLSwapBuffers(EGLDisplay display, EGLSurface surface, const struct
if (wlWm.needsResize) if (wlWm.needsResize)
{ {
wl_egl_window_resize(wlWm.eglWindow, wlWm.width * wlWm.scale, wlWm.height * wlWm.scale, 0, 0); wl_egl_window_resize(wlWm.eglWindow, wl_fixed_to_int(wlWm.width * wlWm.scale),
wl_surface_set_buffer_scale(wlWm.surface, wlWm.scale); wl_fixed_to_int(wlWm.height * wlWm.scale), 0, 0);
if (wlWm.fractionalScale)
{
wl_surface_set_buffer_scale(wlWm.surface, 1);
if (!wlWm.viewport)
wlWm.viewport = wp_viewporter_get_viewport(wlWm.viewporter, wlWm.surface);
wp_viewport_set_source(wlWm.viewport, 0, 0, wlWm.width * wlWm.scale, wlWm.height * wlWm.scale);
wp_viewport_set_destination(wlWm.viewport, wlWm.width, wlWm.height);
}
else
{
if (wlWm.viewport)
{
wl_fixed_t clear = wl_fixed_from_int(-1);
wp_viewport_set_source(wlWm.viewport, clear, clear, clear, clear);
wp_viewport_set_destination(wlWm.viewport, -1, -1);
wp_viewport_destroy(wlWm.viewport);
wlWm.viewport = NULL;
}
wl_surface_set_buffer_scale(wlWm.surface, wl_fixed_to_int(wlWm.scale));
}
struct wl_region * region = wl_compositor_create_region(wlWm.compositor); struct wl_region * region = wl_compositor_create_region(wlWm.compositor);
wl_region_add(region, 0, 0, wlWm.width, wlWm.height); wl_region_add(region, 0, 0, wlWm.width, wlWm.height);
wl_surface_set_opaque_region(wlWm.surface, region); wl_surface_set_opaque_region(wlWm.surface, region);
wl_region_destroy(region); wl_region_destroy(region);
app_handleResizeEvent(wlWm.width, wlWm.height, wlWm.scale, (struct Border) {0, 0, 0, 0}); app_handleResizeEvent(wlWm.width, wlWm.height, wl_fixed_to_double(wlWm.scale),
(struct Border) {0, 0, 0, 0});
wlWm.needsResize = false; wlWm.needsResize = false;
} }

View File

@ -27,29 +27,63 @@
#include "common/debug.h" #include "common/debug.h"
static void outputGeometryHandler(void * data, struct wl_output * output, int32_t x, int32_t y, static void outputUpdateScale(struct WaylandOutput * node)
{
wl_fixed_t original = node->scale;
if (!wlWm.viewporter || !node->logicalWidth || !node->logicalHeight)
node->scale = wl_fixed_from_int(node->scaleInt);
else
{
int32_t modeWidth = node->modeRotate ? node->modeHeight : node->modeWidth;
node->scale = wl_fixed_from_double(1.0 * modeWidth / node->logicalWidth);
}
if (original != node->scale)
waylandWindowUpdateScale();
}
static void outputGeometryHandler(void * opaque, struct wl_output * output, int32_t x, int32_t y,
int32_t physical_width, int32_t physical_height, int32_t subpixel, const char * make, int32_t physical_width, int32_t physical_height, int32_t subpixel, const char * make,
const char * model, int32_t output_transform) const char * model, int32_t output_transform)
{ {
// Do nothing. struct WaylandOutput * node = opaque;
switch (output_transform)
{
case WL_OUTPUT_TRANSFORM_90:
case WL_OUTPUT_TRANSFORM_270:
case WL_OUTPUT_TRANSFORM_FLIPPED_90:
case WL_OUTPUT_TRANSFORM_FLIPPED_270:
node->modeRotate = true;
break;
default:
node->modeRotate = false;
}
} }
static void outputModeHandler(void * data, struct wl_output * wl_output, uint32_t flags, static void outputModeHandler(void * opaque, struct wl_output * wl_output, uint32_t flags,
int32_t width, int32_t height, int32_t refresh) int32_t width, int32_t height, int32_t refresh)
{ {
// Do nothing. if (!(flags & WL_OUTPUT_MODE_CURRENT))
return;
struct WaylandOutput * node = opaque;
node->modeWidth = width;
node->modeHeight = height;
} }
static void outputDoneHandler(void * data, struct wl_output * output) static void outputDoneHandler(void * opaque, struct wl_output * output)
{ {
// Do nothing. struct WaylandOutput * node = opaque;
outputUpdateScale(node);
} }
static void outputScaleHandler(void * opaque, struct wl_output * output, int32_t scale) static void outputScaleHandler(void * opaque, struct wl_output * output, int32_t scale)
{ {
struct WaylandOutput * node = opaque; struct WaylandOutput * node = opaque;
node->scale = scale; node->scaleInt = scale;
waylandWindowUpdateScale();
} }
static const struct wl_output_listener outputListener = { static const struct wl_output_listener outputListener = {
@ -59,6 +93,45 @@ static const struct wl_output_listener outputListener = {
.scale = outputScaleHandler, .scale = outputScaleHandler,
}; };
static void xdgOutputLogicalPositionHandler(void * opaque, struct zxdg_output_v1 * xdgOutput,
int32_t x, int32_t y)
{
// Do nothing.
}
static void xdgOutputLogicalSizeHandler(void * opaque, struct zxdg_output_v1 * xdgOutput,
int32_t width, int32_t height)
{
struct WaylandOutput * node = opaque;
node->logicalWidth = width;
node->logicalHeight = height;
}
static void xdgOutputDoneHandler(void * opaque, struct zxdg_output_v1 * xdgOutput)
{
struct WaylandOutput * node = opaque;
outputUpdateScale(node);
}
static void xdgOutputNameHandler(void * opaque, struct zxdg_output_v1 * xdgOutput, const char * name)
{
// Do nothing.
}
static void xdgOutputDescriptionHandler(void * opaque, struct zxdg_output_v1 * xdgOutput,
const char * description)
{
// Do nothing.
}
static const struct zxdg_output_v1_listener xdgOutputListener = {
.logical_position = xdgOutputLogicalPositionHandler,
.logical_size = xdgOutputLogicalSizeHandler,
.done = xdgOutputDoneHandler,
.name = xdgOutputNameHandler,
.description = xdgOutputDescriptionHandler,
};
bool waylandOutputInit(void) bool waylandOutputInit(void)
{ {
wl_list_init(&wlWm.outputs); wl_list_init(&wlWm.outputs);
@ -73,6 +146,8 @@ void waylandOutputFree(void)
{ {
if (node->version >= 3) if (node->version >= 3)
wl_output_release(node->output); wl_output_release(node->output);
if (node->xdgOutput)
zxdg_output_v1_destroy(node->xdgOutput);
wl_list_remove(&node->link); wl_list_remove(&node->link);
free(node); free(node);
} }
@ -80,7 +155,7 @@ void waylandOutputFree(void)
void waylandOutputBind(uint32_t name, uint32_t version) void waylandOutputBind(uint32_t name, uint32_t version)
{ {
struct WaylandOutput * node = malloc(sizeof(struct WaylandOutput)); struct WaylandOutput * node = calloc(1, sizeof(struct WaylandOutput));
if (!node) if (!node)
return; return;
@ -103,6 +178,13 @@ void waylandOutputBind(uint32_t name, uint32_t version)
return; return;
} }
if (wlWm.xdgOutputManager)
{
node->xdgOutput = zxdg_output_manager_v1_get_xdg_output(wlWm.xdgOutputManager, node->output);
if (node->xdgOutput)
zxdg_output_v1_add_listener(node->xdgOutput, &xdgOutputListener, node);
}
wl_output_add_listener(node->output, &outputListener, node); wl_output_add_listener(node->output, &outputListener, node);
wl_list_insert(&wlWm.outputs, &node->link); wl_list_insert(&wlWm.outputs, &node->link);
} }
@ -117,6 +199,8 @@ void waylandOutputTryUnbind(uint32_t name)
{ {
if (node->version >= 3) if (node->version >= 3)
wl_output_release(node->output); wl_output_release(node->output);
if (node->xdgOutput)
zxdg_output_v1_destroy(node->xdgOutput);
wl_list_remove(&node->link); wl_list_remove(&node->link);
free(node); free(node);
break; break;
@ -124,7 +208,7 @@ void waylandOutputTryUnbind(uint32_t name)
} }
} }
int32_t waylandOutputGetScale(struct wl_output * output) wl_fixed_t waylandOutputGetScale(struct wl_output * output)
{ {
struct WaylandOutput * node; struct WaylandOutput * node;

View File

@ -50,6 +50,9 @@ static void registryGlobalHandler(void * data, struct wl_registry * registry,
else if (!strcmp(interface, wp_presentation_interface.name)) else if (!strcmp(interface, wp_presentation_interface.name))
wlWm.presentation = wl_registry_bind(wlWm.registry, name, wlWm.presentation = wl_registry_bind(wlWm.registry, name,
&wp_presentation_interface, 1); &wp_presentation_interface, 1);
else if (!strcmp(interface, wp_viewporter_interface.name))
wlWm.viewporter = wl_registry_bind(wlWm.registry, name,
&wp_viewporter_interface, 1);
else if (!strcmp(interface, zwp_relative_pointer_manager_v1_interface.name)) else if (!strcmp(interface, zwp_relative_pointer_manager_v1_interface.name))
wlWm.relativePointerManager = wl_registry_bind(wlWm.registry, name, wlWm.relativePointerManager = wl_registry_bind(wlWm.registry, name,
&zwp_relative_pointer_manager_v1_interface, 1); &zwp_relative_pointer_manager_v1_interface, 1);
@ -65,6 +68,9 @@ static void registryGlobalHandler(void * data, struct wl_registry * registry,
else if (!strcmp(interface, zwp_idle_inhibit_manager_v1_interface.name)) else if (!strcmp(interface, zwp_idle_inhibit_manager_v1_interface.name))
wlWm.idleInhibitManager = wl_registry_bind(wlWm.registry, name, wlWm.idleInhibitManager = wl_registry_bind(wlWm.registry, name,
&zwp_idle_inhibit_manager_v1_interface, 1); &zwp_idle_inhibit_manager_v1_interface, 1);
else if (!strcmp(interface, zxdg_output_manager_v1_interface.name) && version >= 2)
wlWm.xdgOutputManager = wl_registry_bind(wlWm.registry, name,
&zxdg_output_manager_v1_interface, 2);
} }
static void registryGlobalRemoveHandler(void * data, static void registryGlobalRemoveHandler(void * data,

View File

@ -40,11 +40,13 @@
#include "wayland-xdg-shell-client-protocol.h" #include "wayland-xdg-shell-client-protocol.h"
#include "wayland-presentation-time-client-protocol.h" #include "wayland-presentation-time-client-protocol.h"
#include "wayland-viewporter-client-protocol.h"
#include "wayland-xdg-decoration-unstable-v1-client-protocol.h" #include "wayland-xdg-decoration-unstable-v1-client-protocol.h"
#include "wayland-keyboard-shortcuts-inhibit-unstable-v1-client-protocol.h" #include "wayland-keyboard-shortcuts-inhibit-unstable-v1-client-protocol.h"
#include "wayland-pointer-constraints-unstable-v1-client-protocol.h" #include "wayland-pointer-constraints-unstable-v1-client-protocol.h"
#include "wayland-relative-pointer-unstable-v1-client-protocol.h" #include "wayland-relative-pointer-unstable-v1-client-protocol.h"
#include "wayland-idle-inhibit-unstable-v1-client-protocol.h" #include "wayland-idle-inhibit-unstable-v1-client-protocol.h"
#include "wayland-xdg-output-unstable-v1-client-protocol.h"
typedef void (*WaylandPollCallback)(uint32_t events, void * opaque); typedef void (*WaylandPollCallback)(uint32_t events, void * opaque);
@ -60,8 +62,15 @@ struct WaylandPoll
struct WaylandOutput struct WaylandOutput
{ {
uint32_t name; uint32_t name;
int32_t scale; wl_fixed_t scale;
int32_t scaleInt;
int32_t logicalWidth;
int32_t logicalHeight;
int32_t modeWidth;
int32_t modeHeight;
bool modeRotate;
struct wl_output * output; struct wl_output * output;
struct zxdg_output_v1 * xdgOutput;
uint32_t version; uint32_t version;
struct wl_list link; struct wl_list link;
}; };
@ -93,7 +102,9 @@ struct WaylandDSState
struct wl_shm * shm; struct wl_shm * shm;
struct wl_compositor * compositor; struct wl_compositor * compositor;
int32_t width, height, scale; int32_t width, height;
wl_fixed_t scale;
bool fractionalScale;
bool needsResize; bool needsResize;
bool fullscreen; bool fullscreen;
uint32_t resizeSerial; uint32_t resizeSerial;
@ -158,6 +169,9 @@ struct WaylandDSState
struct zwp_idle_inhibit_manager_v1 * idleInhibitManager; struct zwp_idle_inhibit_manager_v1 * idleInhibitManager;
struct zwp_idle_inhibitor_v1 * idleInhibitor; struct zwp_idle_inhibitor_v1 * idleInhibitor;
struct wp_viewporter * viewporter;
struct wp_viewport * viewport;
struct zxdg_output_manager_v1 * xdgOutputManager;
struct wl_list outputs; // WaylandOutput::link struct wl_list outputs; // WaylandOutput::link
struct wl_list surfaceOutputs; // SurfaceOutput::link struct wl_list surfaceOutputs; // SurfaceOutput::link
@ -259,7 +273,7 @@ bool waylandOutputInit(void);
void waylandOutputFree(void); void waylandOutputFree(void);
void waylandOutputBind(uint32_t name, uint32_t version); void waylandOutputBind(uint32_t name, uint32_t version);
void waylandOutputTryUnbind(uint32_t name); void waylandOutputTryUnbind(uint32_t name);
int32_t waylandOutputGetScale(struct wl_output * output); wl_fixed_t waylandOutputGetScale(struct wl_output * output);
// poll module // poll module
bool waylandPollInit(void); bool waylandPollInit(void);

View File

@ -33,12 +33,12 @@
void waylandWindowUpdateScale(void) void waylandWindowUpdateScale(void)
{ {
int32_t maxScale = 0; wl_fixed_t maxScale = 0;
struct SurfaceOutput * node; struct SurfaceOutput * node;
wl_list_for_each(node, &wlWm.surfaceOutputs, link) wl_list_for_each(node, &wlWm.surfaceOutputs, link)
{ {
int32_t scale = waylandOutputGetScale(node->output); wl_fixed_t scale = waylandOutputGetScale(node->output);
if (scale > maxScale) if (scale > maxScale)
maxScale = scale; maxScale = scale;
} }
@ -46,6 +46,7 @@ void waylandWindowUpdateScale(void)
if (maxScale) if (maxScale)
{ {
wlWm.scale = maxScale; wlWm.scale = maxScale;
wlWm.fractionalScale = wl_fixed_from_int(wl_fixed_to_int(maxScale)) != maxScale;
wlWm.needsResize = true; wlWm.needsResize = true;
} }
} }
@ -77,7 +78,7 @@ static const struct wl_surface_listener wlSurfaceListener = {
bool waylandWindowInit(const char * title, bool fullscreen, bool maximize, bool borderless) bool waylandWindowInit(const char * title, bool fullscreen, bool maximize, bool borderless)
{ {
wlWm.scale = 1; wlWm.scale = wl_fixed_from_int(1);
if (!wlWm.compositor) if (!wlWm.compositor)
{ {