GHOST/Wayland: add fractional-scale interface support

Previously, fractional scaling was detected but set an integer buffer
scale which the compositor would down-scale causing blurry output.

Now the fractional scaling interface is used when available to set the
DPI and set the internal buffers size & viewport transformation to
ensure 1:1 pixels from Blender to the Wayland output.

Tested to work with multiple monitors with mixed
fractional/non-fractional scale.

Note that this change causes a regression for when fractional scaling
is set on a compositor without support for fractional-scale-v1.
Supporting fractional scaling in both cases is possible but overly
complicated. This case already wasn't working so well - with blurry
output due to image scaling, now the DPI wont be accurate in this case
although Blender is still usable.
This commit is contained in:
Campbell Barton 2023-03-31 13:02:56 +11:00
parent 75127f9b09
commit cde99075e8
5 changed files with 563 additions and 103 deletions

View File

@ -378,6 +378,14 @@ elseif(WITH_GHOST_X11 OR WITH_GHOST_WAYLAND)
unset(_name)
endmacro()
macro(generate_protocol_bindings_when_found PROT_DEF FOUND_VAR)
if(EXISTS ${PROT_DEF})
generate_protocol_bindings(${PROT_DEF})
set(${FOUND_VAR} TRUE)
else()
set(${FOUND_VAR} FALSE)
endif()
endmacro()
list(APPEND INC_SYS
${INC_DST}
@ -402,6 +410,17 @@ elseif(WITH_GHOST_X11 OR WITH_GHOST_WAYLAND)
generate_protocol_bindings(
"${WAYLAND_PROTOCOLS_DIR}/staging/xdg-activation/xdg-activation-v1.xml"
)
# Fractional scale.
generate_protocol_bindings_when_found(
"${WAYLAND_PROTOCOLS_DIR}/staging/fractional-scale/fractional-scale-v1.xml"
_has_fractional_scale
)
if(_has_fractional_scale)
# Viewport (only required when fractional scale is in use).
generate_protocol_bindings(
"${WAYLAND_PROTOCOLS_DIR}/stable/viewporter/viewporter.xml"
)
endif()
# Pointer-constraints.
generate_protocol_bindings(
"${WAYLAND_PROTOCOLS_DIR}/unstable/pointer-constraints/pointer-constraints-unstable-v1.xml"
@ -427,6 +446,11 @@ elseif(WITH_GHOST_X11 OR WITH_GHOST_WAYLAND)
add_definitions(-DWITH_GHOST_WAYLAND)
if(_has_fractional_scale)
add_definitions(-DWITH_GHOST_WAYLAND_FRACTIONAL_SCALE)
endif()
unset(_has_fractional_scale)
if(NOT WITH_GHOST_WAYLAND_APP_ID STREQUAL "")
add_definitions(-DWITH_GHOST_WAYLAND_APP_ID=${WITH_GHOST_WAYLAND_APP_ID})
endif()

View File

@ -61,6 +61,10 @@
#include <tablet-unstable-v2-client-protocol.h>
#include <xdg-activation-v1-client-protocol.h>
#include <xdg-output-unstable-v1-client-protocol.h>
#ifdef WITH_GHOST_WAYLAND_FRACTIONAL_SCALE
# include <fractional-scale-v1-client-protocol.h>
# include <viewporter-client-protocol.h>
#endif
/* Decorations `xdg_decor`. */
#include <xdg-decoration-unstable-v1-client-protocol.h>
@ -944,6 +948,10 @@ struct GWL_Display {
struct zwp_relative_pointer_manager_v1 *wp_relative_pointer_manager = nullptr;
struct zwp_primary_selection_device_manager_v1 *wp_primary_selection_device_manager = nullptr;
struct xdg_activation_v1 *xdg_activation_manager = nullptr;
#ifdef WITH_GHOST_WAYLAND_FRACTIONAL_SCALE
struct wp_fractional_scale_manager_v1 *wp_fractional_scale_manager = nullptr;
struct wp_viewporter *wp_viewporter = nullptr;
#endif
struct zwp_pointer_constraints_v1 *wp_pointer_constraints = nullptr;
struct zwp_pointer_gestures_v1 *wp_pointer_gestures = nullptr;
@ -3006,19 +3014,6 @@ static void gesture_pinch_handle_begin(void *data,
if (wl_surface *wl_surface_focus = seat->pointer.wl_surface_window) {
win = ghost_wl_surface_user_data(wl_surface_focus);
}
/* NOTE(@ideasman42): Blender's use of track-pad coordinates is inconsistent and needs work.
* This isn't specific to WAYLAND, in practice they tend to work well enough in most cases.
* Some operators scale by the UI scale, some don't.
* Even this window scale is not correct because it doesn't account for:
* 1) Fractional window scale.
* 2) Blender's UI scale preference (which GHOST doesn't know about).
*
* If support for this were all that was needed it could be handled in GHOST,
* however as the operators are not even using coordinates compatible with each other,
* it would be better to resolve this by passing rotation & zoom levels directly,
* instead of attempting to handle them as cursor coordinates.
*/
const wl_fixed_t win_scale = win ? win->scale() : 1;
/* NOTE(@ideasman42): Scale factors match Blender's operators & default preferences.
* For these values to work correctly, operator logic will need to be changed not to scale input
@ -3033,10 +3028,30 @@ static void gesture_pinch_handle_begin(void *data,
seat->pointer_gesture_pinch.scale.value = wl_fixed_from_int(1);
/* The value 300 matches a value used in clip & image zoom operators.
* It seems OK for the 3D view too. */
seat->pointer_gesture_pinch.scale.factor = 300 * win_scale;
seat->pointer_gesture_pinch.scale.factor = 300;
/* The value 5 is used on macOS and roughly maps 1:1 with turntable rotation,
* although preferences can scale the sensitivity (which would be skipped ideally). */
seat->pointer_gesture_pinch.rotation.factor = 5 * win_scale;
seat->pointer_gesture_pinch.rotation.factor = 5;
if (win) {
/* NOTE(@ideasman42): Blender's use of track-pad coordinates is inconsistent and needs work.
* This isn't specific to WAYLAND, in practice they tend to work well enough in most cases.
* Some operators scale by the UI scale, some don't.
* Even this window scale is not correct because it doesn't account for:
* 1) Fractional window scale.
* 2) Blender's UI scale preference (which GHOST doesn't know about).
*
* If support for this were all that was needed it could be handled in GHOST,
* however as the operators are not even using coordinates compatible with each other,
* it would be better to resolve this by passing rotation & zoom levels directly,
* instead of attempting to handle them as cursor coordinates.
*/
const struct GWL_WindowScaleParams &scale_params = win->scale_params();
seat->pointer_gesture_pinch.scale.factor = gwl_window_scale_int_to(
scale_params, seat->pointer_gesture_pinch.scale.factor);
seat->pointer_gesture_pinch.rotation.factor = gwl_window_scale_int_to(
scale_params, seat->pointer_gesture_pinch.rotation.factor);
}
}
static void gesture_pinch_handle_update(void *data,
@ -5167,6 +5182,47 @@ static void gwl_registry_xdg_activation_remove(GWL_Display *display,
*value_p = nullptr;
}
/* #GWL_Display.wp_fractional_scale_manger */
#ifdef WITH_GHOST_WAYLAND_FRACTIONAL_SCALE
static void gwl_registry_wp_fractional_scale_manager_add(GWL_Display *display,
const GWL_RegisteryAdd_Params *params)
{
display->wp_fractional_scale_manager = static_cast<wp_fractional_scale_manager_v1 *>(
wl_registry_bind(
display->wl_registry, params->name, &wp_fractional_scale_manager_v1_interface, 1));
gwl_registry_entry_add(display, params, nullptr);
}
static void gwl_registry_wp_fractional_scale_manager_remove(GWL_Display *display,
void * /*user_data*/,
const bool /*on_exit*/)
{
struct wp_fractional_scale_manager_v1 **value_p = &display->wp_fractional_scale_manager;
wp_fractional_scale_manager_v1_destroy(*value_p);
*value_p = nullptr;
}
/* #GWL_Display.wl_viewport */
static void gwl_registry_wp_viewporter_add(GWL_Display *display,
const GWL_RegisteryAdd_Params *params)
{
display->wp_viewporter = static_cast<wp_viewporter *>(
wl_registry_bind(display->wl_registry, params->name, &wp_viewporter_interface, 1));
gwl_registry_entry_add(display, params, nullptr);
}
static void gwl_registry_wp_viewporter_remove(GWL_Display *display,
void * /*user_data*/,
const bool /*on_exit*/)
{
struct wp_viewporter **value_p = &display->wp_viewporter;
wp_viewporter_destroy(*value_p);
*value_p = nullptr;
}
#endif /* WITH_GHOST_WAYLAND_FRACTIONAL_SCALE */
/* #GWL_Display.wp_primary_selection_device_manager */
static void gwl_registry_wp_primary_selection_device_manager_add(
@ -5276,6 +5332,20 @@ static const GWL_RegistryHandler gwl_registry_handlers[] = {
/*update_fn*/ nullptr,
/*remove_fn*/ gwl_registry_xdg_activation_remove,
},
#ifdef WITH_GHOST_WAYLAND_FRACTIONAL_SCALE
{
/*interface_p*/ &wp_fractional_scale_manager_v1_interface.name,
/*add_fn*/ gwl_registry_wp_fractional_scale_manager_add,
/*update_fn*/ nullptr,
/*remove_fn*/ gwl_registry_wp_fractional_scale_manager_remove,
},
{
/*interface_p*/ &wp_viewporter_interface.name,
/*add_fn*/ gwl_registry_wp_viewporter_add,
/*update_fn*/ nullptr,
/*remove_fn*/ gwl_registry_wp_viewporter_remove,
},
#endif /* WITH_GHOST_WAYLAND_FRACTIONAL_SCALE */
/* Display outputs. */
{
/*interface_p*/ &wl_output_interface.name,
@ -6840,6 +6910,17 @@ struct xdg_activation_v1 *GHOST_SystemWayland::xdg_activation_manager()
return display_->xdg_activation_manager;
}
#ifdef WITH_GHOST_WAYLAND_FRACTIONAL_SCALE
struct wp_fractional_scale_manager_v1 *GHOST_SystemWayland::wp_fractional_scale_manager()
{
return display_->wp_fractional_scale_manager;
}
struct wp_viewporter *GHOST_SystemWayland::wp_viewporter()
{
return display_->wp_viewporter;
}
#endif /* WITH_GHOST_WAYLAND_FRACTIONAL_SCALE */
struct zwp_pointer_gestures_v1 *GHOST_SystemWayland::wp_pointer_gestures()
{
return display_->wp_pointer_gestures;
@ -7055,7 +7136,7 @@ bool GHOST_SystemWayland::window_cursor_grab_set(const GHOST_TGrabCursorMode mod
const GHOST_Rect *wrap_bounds,
const GHOST_TAxisFlag wrap_axis,
wl_surface *wl_surface,
const int scale)
const struct GWL_WindowScaleParams &scale_params)
{
/* Caller must lock `server_mutex`. */
@ -7115,10 +7196,14 @@ bool GHOST_SystemWayland::window_cursor_grab_set(const GHOST_TGrabCursorMode mod
GHOST_Rect bounds_scale;
bounds_scale.m_l = wl_fixed_from_int(wrap_bounds->m_l) / scale;
bounds_scale.m_t = wl_fixed_from_int(wrap_bounds->m_t) / scale;
bounds_scale.m_r = wl_fixed_from_int(wrap_bounds->m_r) / scale;
bounds_scale.m_b = wl_fixed_from_int(wrap_bounds->m_b) / scale;
bounds_scale.m_l = gwl_window_scale_wl_fixed_from(scale_params,
wl_fixed_from_int(wrap_bounds->m_l));
bounds_scale.m_t = gwl_window_scale_wl_fixed_from(scale_params,
wl_fixed_from_int(wrap_bounds->m_t));
bounds_scale.m_r = gwl_window_scale_wl_fixed_from(scale_params,
wl_fixed_from_int(wrap_bounds->m_r));
bounds_scale.m_b = gwl_window_scale_wl_fixed_from(scale_params,
wl_fixed_from_int(wrap_bounds->m_b));
bounds_scale.wrapPoint(UNPACK2(xy_next), 0, wrap_axis);
@ -7138,8 +7223,8 @@ bool GHOST_SystemWayland::window_cursor_grab_set(const GHOST_TGrabCursorMode mod
if ((init_grab_xy[0] != seat->grab_lock_xy[0]) ||
(init_grab_xy[1] != seat->grab_lock_xy[1])) {
const wl_fixed_t xy_next[2] = {
wl_fixed_from_int(init_grab_xy[0]) / scale,
wl_fixed_from_int(init_grab_xy[1]) / scale,
gwl_window_scale_wl_fixed_from(scale_params, wl_fixed_from_int(init_grab_xy[0])),
gwl_window_scale_wl_fixed_from(scale_params, wl_fixed_from_int(init_grab_xy[1])),
};
zwp_locked_pointer_v1_set_cursor_position_hint(seat->wp_locked_pointer,
UNPACK2(xy_next));
@ -7164,13 +7249,13 @@ bool GHOST_SystemWayland::window_cursor_grab_set(const GHOST_TGrabCursorMode mod
#endif
if (xy_motion_create_event) {
seat->system->pushEvent_maybe_pending(
new GHOST_EventCursor(seat->system->getMilliSeconds(),
GHOST_kEventCursorMove,
ghost_wl_surface_user_data(wl_surface),
wl_fixed_to_int(scale * xy_motion[0]),
wl_fixed_to_int(scale * xy_motion[1]),
GHOST_TABLET_DATA_NONE));
seat->system->pushEvent_maybe_pending(new GHOST_EventCursor(
seat->system->getMilliSeconds(),
GHOST_kEventCursorMove,
ghost_wl_surface_user_data(wl_surface),
wl_fixed_to_int(gwl_window_scale_wl_fixed_to(scale_params, xy_motion[0])),
wl_fixed_to_int(gwl_window_scale_wl_fixed_to(scale_params, xy_motion[1])),
GHOST_TABLET_DATA_NONE));
}
zwp_locked_pointer_v1_destroy(seat->wp_locked_pointer);
@ -7206,8 +7291,10 @@ bool GHOST_SystemWayland::window_cursor_grab_set(const GHOST_TGrabCursorMode mod
if (mode == GHOST_kGrabHide) {
/* Set the initial position to detect any changes when un-grabbing,
* otherwise the unlocked cursor defaults to un-locking in-place. */
init_grab_xy[0] = wl_fixed_to_int(scale * seat->pointer.xy[0]);
init_grab_xy[1] = wl_fixed_to_int(scale * seat->pointer.xy[1]);
init_grab_xy[0] = wl_fixed_to_int(
gwl_window_scale_wl_fixed_to(scale_params, seat->pointer.xy[0]));
init_grab_xy[1] = wl_fixed_to_int(
gwl_window_scale_wl_fixed_to(scale_params, seat->pointer.xy[1]));
seat->grab_lock_xy[0] = init_grab_xy[0];
seat->grab_lock_xy[1] = init_grab_xy[1];
}

View File

@ -47,6 +47,26 @@ void ghost_wl_surface_tag_cursor_pointer(struct wl_surface *surface);
bool ghost_wl_surface_own_cursor_tablet(const struct wl_surface *surface);
void ghost_wl_surface_tag_cursor_tablet(struct wl_surface *surface);
/* Scaling to: translates from WAYLAND into GHOST (viewport local) coordinates.
* Scaling from: performs the reverse translation.
*
* Scaling "to" is used to map WAYLAND location cursor coordinates to GHOST coordinates.
* Scaling "from" is used to clamp cursor coordinates in WAYLAND local coordinates. */
struct GWL_WindowScaleParams;
wl_fixed_t gwl_window_scale_wl_fixed_to(const GWL_WindowScaleParams &scale_params,
wl_fixed_t value);
wl_fixed_t gwl_window_scale_wl_fixed_from(const GWL_WindowScaleParams &scale_params,
wl_fixed_t value);
/* Avoid this where possible as scaling integers often incurs rounding errors.
* Scale #wl_fixed_t where possible.
*
* In general scale by large values where this is less likely to be a problem. */
int gwl_window_scale_int_to(const GWL_WindowScaleParams &scale_params, int value);
int gwl_window_scale_int_from(const GWL_WindowScaleParams &scale_params, int value);
#ifdef WITH_GHOST_WAYLAND_DYNLOAD
/**
* Return true when all required WAYLAND libraries are present,
@ -181,6 +201,10 @@ class GHOST_SystemWayland : public GHOST_System {
struct zwp_primary_selection_device_manager_v1 *wp_primary_selection_manager();
struct xdg_activation_v1 *xdg_activation_manager();
struct zwp_pointer_gestures_v1 *wp_pointer_gestures();
#ifdef WITH_GHOST_WAYLAND_FRACTIONAL_SCALE
struct wp_fractional_scale_manager_v1 *wp_fractional_scale_manager();
struct wp_viewporter *wp_viewporter();
#endif
#ifdef WITH_GHOST_WAYLAND_LIBDECOR
libdecor *libdecor_context();
@ -234,7 +258,7 @@ class GHOST_SystemWayland : public GHOST_System {
const GHOST_Rect *wrap_bounds,
GHOST_TAxisFlag wrap_axis,
wl_surface *wl_surface,
int scale);
const struct GWL_WindowScaleParams &scale_params);
#ifdef WITH_GHOST_WAYLAND_LIBDECOR
static bool use_libdecor_runtime();

View File

@ -38,6 +38,11 @@
#include <xdg-activation-v1-client-protocol.h>
#include <xdg-decoration-unstable-v1-client-protocol.h>
#include <xdg-shell-client-protocol.h>
#ifdef WITH_GHOST_WAYLAND_FRACTIONAL_SCALE
# include <fractional-scale-v1-client-protocol.h>
# include <viewporter-client-protocol.h>
# define FRACTIONAL_DENOMINATOR 120
#endif
#include <atomic>
@ -83,6 +88,52 @@ static void gwl_xdg_decor_window_destroy(WGL_XDG_Decor_Window *decor)
delete decor;
}
/* -------------------------------------------------------------------- */
/** \name Window-Viewport/Wayland to/from Scale Conversion
* \{ */
struct GWL_WindowScaleParams {
bool is_fractional = false;
/**
* When fractional:
* Scale is multiplied by #FRACTIONAL_DENOMINATOR.
* Otherwise scale is an integer.
*/
wl_fixed_t scale = 0;
};
wl_fixed_t gwl_window_scale_wl_fixed_to(const GWL_WindowScaleParams &scale_params,
wl_fixed_t value)
{
#ifdef WITH_GHOST_WAYLAND_FRACTIONAL_SCALE
if (scale_params.is_fractional) {
return (value * scale_params.scale) / FRACTIONAL_DENOMINATOR;
}
#endif
return value * scale_params.scale;
}
wl_fixed_t gwl_window_scale_wl_fixed_from(const GWL_WindowScaleParams &scale_params,
wl_fixed_t value)
{
#ifdef WITH_GHOST_WAYLAND_FRACTIONAL_SCALE
if (scale_params.is_fractional) {
return (value * FRACTIONAL_DENOMINATOR) / scale_params.scale;
}
#endif
return value / scale_params.scale;
}
int gwl_window_scale_int_to(const GWL_WindowScaleParams &scale_params, int value)
{
return wl_fixed_to_int(gwl_window_scale_wl_fixed_to(scale_params, wl_fixed_from_int(value)));
}
int gwl_window_scale_int_from(const GWL_WindowScaleParams &scale_params, int value)
{
return wl_fixed_to_int(gwl_window_scale_wl_fixed_from(scale_params, wl_fixed_from_int(value)));
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Internal #GWL_Window
* \{ */
@ -111,10 +162,18 @@ enum eGWL_PendingWindowActions {
#endif /* USE_EVENT_BACKGROUND_THREAD */
struct GWL_WindowFrame {
/**
* The frame size (in GHOST window coordinates).
*
* These must be converted to WAYLADN relative coordinates when the window is scaled
* by Hi-DPI/fractional scaling.
*/
int32_t size[2] = {0, 0};
bool is_maximised = false;
bool is_fullscreen = false;
bool is_active = false;
/** Disable when the fractional scale is a whole number. */
int fractional_scale = 0;
};
struct GWL_Window {
@ -132,14 +191,20 @@ struct GWL_Window {
/** The scale value written to #wl_surface_set_buffer_scale. */
int scale = 0;
/**
* The fractional scale used to calculate the DPI.
* (always set, even when scaling is rounded to whole units).
* The scale value to be used in the case fractional scale is disable.
* In general this should only be used when changing states
* (when disabling fractional scale).
*/
wl_fixed_t scale_fractional = 0;
int scale_on_output = 0;
/** A temporary token used for the window to be notified of it's activation. */
struct xdg_activation_token_v1 *xdg_activation_token = nullptr;
#ifdef WITH_GHOST_WAYLAND_FRACTIONAL_SCALE
struct wp_viewport *viewport = nullptr;
struct wp_fractional_scale_v1 *fractional_scale_handle = nullptr;
#endif
#ifdef WITH_GHOST_WAYLAND_LIBDECOR
WGL_LibDecor_Window *libdecor = nullptr;
#endif
@ -166,6 +231,9 @@ struct GWL_Window {
bool is_dialog = false;
/** Currently only initialized on access (avoids allocations & allows to keep private). */
GWL_WindowScaleParams scale_params;
#ifdef USE_EVENT_BACKGROUND_THREAD
/**
* These pending actions can't be performed when WAYLAND handlers are running from a thread.
@ -310,6 +378,124 @@ static bool gwl_window_state_set(GWL_Window *win, const GHOST_TWindowState state
/** \} */
/* -------------------------------------------------------------------- */
/** \name Internal #GWL_Window Viewport Setup/Teardown
*
* A viewport is needed to implement fractional scale,
* as the outputs scale may change at runtime, support creating & clearing the viewport as needed.
* \{ */
#ifdef WITH_GHOST_WAYLAND_FRACTIONAL_SCALE
/**
* Scale a value from a viewport value to Wayland windowing.
* Scale down or not at all.
*/
static int gwl_window_fractional_to_viewport(const GWL_WindowFrame &frame, int value)
{
GHOST_ASSERT(frame.fractional_scale != 0, "Not fractional or called before initiazlized!");
return (value * frame.fractional_scale) / FRACTIONAL_DENOMINATOR;
}
/**
* Scale a value from a Wayland windowing value to the viewport.
* Scales up or not at all.
*/
static int gwl_window_fractional_from_viewport(const GWL_WindowFrame &frame, int value)
{
GHOST_ASSERT(frame.fractional_scale != 0, "Not fractional or called before initiazlized!");
return (value * FRACTIONAL_DENOMINATOR) / frame.fractional_scale;
}
/* NOTE: rounded versions are needed for window-frame dimensions conversions.
* (rounding is part of the WAYLAND spec). All other conversions such as cursor coordinates
* can used simple integer division as rounding is not defined in this case. */
static int gwl_window_fractional_to_viewport_round(const GWL_WindowFrame &frame, int value)
{
GHOST_ASSERT(frame.fractional_scale != 0, "Not fractional or called before initiazlized!");
return lroundf(double(value * frame.fractional_scale) / double(FRACTIONAL_DENOMINATOR));
}
static int gwl_window_fractional_from_viewport_round(const GWL_WindowFrame &frame, int value)
{
GHOST_ASSERT(frame.fractional_scale != 0, "Not fractional or called before initiazlized!");
return lroundf(double(value * FRACTIONAL_DENOMINATOR) / double(frame.fractional_scale));
}
static bool gwl_window_viewport_set(GWL_Window *win, bool *r_surface_needs_commit)
{
if (win->viewport != nullptr) {
return false;
}
struct wp_viewporter *viewporter = win->ghost_system->wp_viewporter();
if (viewporter == nullptr) {
return false;
}
win->viewport = wp_viewporter_get_viewport(viewporter, win->wl_surface);
if (win->viewport == nullptr) {
return false;
}
/* Set the buffer scale to 1 since a viewport will be used. */
if (win->scale != 1) {
win->scale = 1;
wl_surface_set_buffer_scale(win->wl_surface, win->scale);
if (r_surface_needs_commit) {
*r_surface_needs_commit = true;
}
else {
wl_surface_commit(win->wl_surface);
}
}
return true;
}
static bool gwl_window_viewport_unset(GWL_Window *win, bool *r_surface_needs_commit)
{
if (win->viewport == nullptr) {
return false;
}
wp_viewport_destroy(win->viewport);
win->viewport = nullptr;
GHOST_ASSERT(win->scale == 1, "Unexpected scale!");
if (win->scale != win->scale_on_output) {
win->scale = win->scale_on_output;
wl_surface_set_buffer_scale(win->wl_surface, win->scale);
if (r_surface_needs_commit) {
*r_surface_needs_commit = true;
}
else {
wl_surface_commit(win->wl_surface);
}
}
return true;
}
static bool gwl_window_viewport_size_update(GWL_Window *win)
{
if (win->viewport == nullptr) {
return false;
}
wp_viewport_set_source(win->viewport,
wl_fixed_from_int(0),
wl_fixed_from_int(0),
wl_fixed_from_int(win->frame.size[0]),
wl_fixed_from_int(win->frame.size[1]));
wp_viewport_set_destination(
win->viewport,
gwl_window_fractional_from_viewport_round(win->frame, win->frame.size[0]),
gwl_window_fractional_from_viewport_round(win->frame, win->frame.size[1]));
return true;
}
#endif /* WITH_GHOST_WAYLAND_FRACTIONAL_SCALE */
/** \} */
/* -------------------------------------------------------------------- */
/** \name Internal #GWL_Window Activation
* \{ */
@ -369,7 +555,26 @@ static void gwl_window_activate(GWL_Window *win)
/** \name Internal #GWL_Window Pending Actions
* \{ */
static void gwl_window_frame_pending_size_set(GWL_Window *win)
#ifdef WITH_GHOST_WAYLAND_FRACTIONAL_SCALE
static void gwl_window_frame_pending_fractional_scale_set(GWL_Window *win,
bool *r_surface_needs_commit)
{
if (win->frame_pending.fractional_scale == win->frame.fractional_scale) {
return;
}
win->frame.fractional_scale = win->frame_pending.fractional_scale;
if (win->frame_pending.fractional_scale) {
gwl_window_viewport_set(win, r_surface_needs_commit);
gwl_window_viewport_size_update(win);
}
else {
gwl_window_viewport_unset(win, r_surface_needs_commit);
}
}
#endif /* WITH_GHOST_WAYLAND_FRACTIONAL_SCALE */
static void gwl_window_frame_pending_size_set(GWL_Window *win, bool *r_surface_needs_commit)
{
if (win->frame_pending.size[0] == 0 || win->frame_pending.size[1] == 0) {
return;
@ -378,7 +583,19 @@ static void gwl_window_frame_pending_size_set(GWL_Window *win)
win->frame.size[0] = win->frame_pending.size[0];
win->frame.size[1] = win->frame_pending.size[1];
#ifdef WITH_GHOST_WAYLAND_FRACTIONAL_SCALE
if (win->frame_pending.fractional_scale != win->frame.fractional_scale) {
gwl_window_frame_pending_fractional_scale_set(win, r_surface_needs_commit);
}
else {
gwl_window_viewport_size_update(win);
}
#else
(void)r_surface_needs_commit;
#endif
wl_egl_window_resize(win->egl_window, UNPACK2(win->frame.size), 0, 0);
win->ghost_window->notify_size();
win->frame_pending.size[0] = 0;
@ -401,6 +618,9 @@ static void gwl_window_pending_actions_handle(GWL_Window *win)
gwl_window_frame_update_from_pending(win);
}
if (win->pending_actions[PENDING_EGL_WINDOW_RESIZE].exchange(false)) {
# ifdef WITH_GHOST_WAYLAND_FRACTIONAL_SCALE
gwl_window_viewport_size_update(win);
# endif
wl_egl_window_resize(win->egl_window, UNPACK2(win->frame.size), 0, 0);
}
# ifdef GHOST_OPENGL_ALPHA
@ -427,13 +647,32 @@ static void gwl_window_frame_update_from_pending_no_lock(GWL_Window *win)
#endif
const bool dpi_changed = win->frame_pending.fractional_scale != win->frame.fractional_scale;
bool surface_needs_commit = false;
if (win->frame_pending.size[0] != 0 && win->frame_pending.size[1] != 0) {
if ((win->frame.size[0] != win->frame_pending.size[0]) ||
(win->frame.size[1] != win->frame_pending.size[1])) {
gwl_window_frame_pending_size_set(win);
gwl_window_frame_pending_size_set(win, &surface_needs_commit);
}
}
#ifdef WITH_GHOST_WAYLAND_FRACTIONAL_SCALE
if (win->frame_pending.fractional_scale != win->frame.fractional_scale) {
gwl_window_frame_pending_fractional_scale_set(win, &surface_needs_commit);
}
#endif
if (surface_needs_commit) {
wl_surface_commit(win->wl_surface);
}
if (dpi_changed) {
GHOST_SystemWayland *system = win->ghost_system;
system->pushEvent(new GHOST_Event(
system->getMilliSeconds(), GHOST_kEventWindowDPIHintChanged, win->ghost_window));
}
if (win->frame_pending.is_active) {
win->ghost_window->activate();
}
@ -476,26 +715,11 @@ static int output_scale_cmp(const GWL_Output *output_a, const GWL_Output *output
if (output_a->scale > output_b->scale) {
return 1;
}
if (output_a->has_scale_fractional || output_b->has_scale_fractional) {
const wl_fixed_t scale_fractional_a = output_a->has_scale_fractional ?
output_a->scale_fractional :
wl_fixed_from_int(output_a->scale);
const wl_fixed_t scale_fractional_b = output_b->has_scale_fractional ?
output_b->scale_fractional :
wl_fixed_from_int(output_b->scale);
if (scale_fractional_a < scale_fractional_b) {
return -1;
}
if (scale_fractional_a > scale_fractional_b) {
return 1;
}
}
return 0;
}
static int outputs_max_scale_or_default(const std::vector<GWL_Output *> &outputs,
const int32_t scale_default,
wl_fixed_t *r_scale_fractional)
const int32_t scale_default)
{
const GWL_Output *output_max = nullptr;
for (const GWL_Output *reg_output : outputs) {
@ -505,17 +729,9 @@ static int outputs_max_scale_or_default(const std::vector<GWL_Output *> &outputs
}
if (output_max) {
if (r_scale_fractional) {
*r_scale_fractional = output_max->has_scale_fractional ?
output_max->scale_fractional :
wl_fixed_from_int(output_max->scale);
}
return output_max->scale;
}
if (r_scale_fractional) {
*r_scale_fractional = wl_fixed_from_int(scale_default);
}
return scale_default;
}
@ -543,8 +759,17 @@ static void xdg_toplevel_handle_configure(void *data,
std::lock_guard lock_frame_guard{win->frame_pending_mutex};
#endif
win->frame_pending.size[0] = win->scale * width;
win->frame_pending.size[1] = win->scale * height;
#ifdef WITH_GHOST_WAYLAND_FRACTIONAL_SCALE
if (win->frame.fractional_scale) {
win->frame_pending.size[0] = gwl_window_fractional_to_viewport_round(win->frame, width);
win->frame_pending.size[1] = gwl_window_fractional_to_viewport_round(win->frame, height);
}
else
#endif /* WITH_GHOST_WAYLAND_FRACTIONAL_SCALE */
{
win->frame_pending.size[0] = width * win->scale;
win->frame_pending.size[1] = height * win->scale;
}
win->frame_pending.is_maximised = false;
win->frame_pending.is_fullscreen = false;
@ -619,6 +844,63 @@ static const struct xdg_activation_token_v1_listener *xdg_activation_listener_ge
/** \} */
/* -------------------------------------------------------------------- */
/** \name Listener (Fractional Scale), #wp_fractional_scale_manager_v1_interface
*
* Used by #gwl_window_activate.
* \{ */
#ifdef WITH_GHOST_WAYLAND_FRACTIONAL_SCALE
static CLG_LogRef LOG_WL_FRACTIONAL_SCALE = {"ghost.wl.handle.fractional_scale"};
# define LOG (&LOG_WL_FRACTIONAL_SCALE)
static void wp_fractional_scale_handle_preferred_scale(
void *data, struct wp_fractional_scale_v1 * /*wp_fractional_scale_v1*/, uint preferred_scale)
{
CLOG_INFO(LOG,
2,
"preferred_scale (preferred_scale=%.6f)",
double(preferred_scale) / FRACTIONAL_DENOMINATOR);
GWL_Window *win = static_cast<GWL_Window *>(data);
const bool is_fractional = (preferred_scale % FRACTIONAL_DENOMINATOR) != 0;
/* When non-fractional, never use fractional scaling! */
win->frame_pending.fractional_scale = is_fractional ? preferred_scale : 0;
if (win->frame.fractional_scale != win->frame_pending.fractional_scale) {
/* Resize the window failing to do so results in severe flickering with a
* multi-monitor setup when multiple monitors have different scales.
*
* NOTE: some flickering is still possible even when resizing this
* happens when dragging the right hand side of the title-bar in KDE
* as expanding changed the size on the RHS, this may be up to the compositor to fix. */
const int scale_prev = win->frame.fractional_scale ?
win->frame.fractional_scale :
win->scale_on_output * FRACTIONAL_DENOMINATOR;
const int scale_next = win->frame_pending.fractional_scale ?
win->frame_pending.fractional_scale :
win->scale_on_output * FRACTIONAL_DENOMINATOR;
for (size_t i = 0; i < ARRAY_SIZE(win->frame_pending.size); i++) {
const int value = win->frame_pending.size[i] ? win->frame_pending.size[i] :
win->frame.size[i];
win->frame_pending.size[i] = lroundf(value * (double(scale_next) / double(scale_prev)));
}
gwl_window_pending_actions_tag(win, PENDING_WINDOW_FRAME_CONFIGURE);
}
}
static const struct wp_fractional_scale_v1_listener wp_fractional_scale_listener = {
/*preferred_scale*/ wp_fractional_scale_handle_preferred_scale,
};
# undef LOG
#endif /* WITH_GHOST_WAYLAND_FRACTIONAL_SCALE */
/** \} */
/* -------------------------------------------------------------------- */
/** \name Listener (LibDecor Frame), #libdecor_frame_interface
* \{ */
@ -643,16 +925,27 @@ static void frame_handle_configure(struct libdecor_frame *frame,
/* Set the size. */
int size_next[2];
{
const int scale = static_cast<GWL_Window *>(data)->scale;
GWL_Window *win = static_cast<GWL_Window *>(data);
const int scale = win->scale;
if (!libdecor_configuration_get_content_size(
configuration, frame, &size_next[0], &size_next[1])) {
GWL_Window *win = static_cast<GWL_Window *>(data);
size_next[0] = win->frame.size[0] / scale;
size_next[1] = win->frame.size[1] / scale;
}
frame_pending->size[0] = scale * size_next[0];
frame_pending->size[1] = scale * size_next[1];
# ifdef WITH_GHOST_WAYLAND_FRACTIONAL_SCALE
if (win->frame.fractional_scale) {
win->frame_pending.size[0] = gwl_window_fractional_to_viewport_round(win->frame,
size_next[0]);
win->frame_pending.size[1] = gwl_window_fractional_to_viewport_round(win->frame,
size_next[1]);
}
else
# endif /* WITH_GHOST_WAYLAND_FRACTIONAL_SCALE */
{
frame_pending->size[0] = size_next[0] * scale;
frame_pending->size[1] = size_next[1] * scale;
}
}
/* Set the state. */
@ -892,7 +1185,8 @@ GHOST_WindowWayland::GHOST_WindowWayland(GHOST_SystemWayland *system,
*
* Using the maximum scale is best as it results in the window first being smaller,
* avoiding a large window flashing before it's made smaller. */
window_->scale = outputs_max_scale_or_default(system_->outputs(), 1, &window_->scale_fractional);
window_->scale = outputs_max_scale_or_default(system_->outputs(), 1);
window_->scale_on_output = window_->scale;
window_->frame.size[0] = int32_t(width);
window_->frame.size[1] = int32_t(height);
@ -915,6 +1209,17 @@ GHOST_WindowWayland::GHOST_WindowWayland(GHOST_SystemWayland *system,
window_->egl_window = wl_egl_window_create(
window_->wl_surface, int(window_->frame.size[0]), int(window_->frame.size[1]));
#ifdef WITH_GHOST_WAYLAND_FRACTIONAL_SCALE
struct wp_fractional_scale_manager_v1 *fractional_scale_manager =
system->wp_fractional_scale_manager();
if (fractional_scale_manager) {
window_->fractional_scale_handle = wp_fractional_scale_manager_v1_get_fractional_scale(
fractional_scale_manager, window_->wl_surface);
wp_fractional_scale_v1_add_listener(
window_->fractional_scale_handle, &wp_fractional_scale_listener, window_);
}
#endif /* WITH_GHOST_WAYLAND_FRACTIONAL_SCALE */
/* NOTE: The limit is in points (not pixels) so Hi-DPI will limit to larger number of pixels.
* This has the advantage that the size limit is the same when moving the window between monitors
* with different scales set. If it was important to limit in pixels it could be re-calculated
@ -1033,13 +1338,14 @@ GHOST_TSuccess GHOST_WindowWayland::setWindowCursorGrab(GHOST_TGrabCursorMode mo
}
bounds = &bounds_buf;
}
if (system_->window_cursor_grab_set(mode,
m_cursorGrab,
m_cursorGrabInitPos,
bounds,
m_cursorGrabAxis,
window_->wl_surface,
window_->scale)) {
this->scale_params())) {
return GHOST_kSuccess;
}
return GHOST_kFailure;
@ -1125,7 +1431,7 @@ GHOST_TSuccess GHOST_WindowWayland::setClientSize(const uint32_t width, const ui
window_->frame_pending.size[0] = width;
window_->frame_pending.size[1] = height;
gwl_window_frame_pending_size_set(window_);
gwl_window_frame_pending_size_set(window_, nullptr);
return GHOST_kSuccess;
}
@ -1163,6 +1469,18 @@ GHOST_WindowWayland::~GHOST_WindowWayland()
window_->xdg_activation_token = nullptr;
}
#ifdef WITH_GHOST_WAYLAND_FRACTIONAL_SCALE
if (window_->fractional_scale_handle) {
wp_fractional_scale_v1_destroy(window_->fractional_scale_handle);
window_->fractional_scale_handle = nullptr;
}
if (window_->viewport) {
wp_viewport_destroy(window_->viewport);
window_->viewport = nullptr;
}
#endif /* WITH_GHOST_WAYLAND_FRACTIONAL_SCALE */
#ifdef WITH_GHOST_WAYLAND_LIBDECOR
if (use_libdecor) {
gwl_libdecor_window_destroy(window_->libdecor);
@ -1192,25 +1510,13 @@ uint16_t GHOST_WindowWayland::getDPIHint()
/* No need to lock `server_mutex`
* (`outputs_changed_update_scale` never changes values in a non-main thread). */
const wl_fixed_t scale_fractional = window_->scale_fractional;
GHOST_ASSERT(wl_fixed_from_int(window_->scale) >= scale_fractional,
"Fractional scale should always be less than the fixed scale.");
/* Using the physical DPI will cause wrong scaling of the UI
* use a multiplier for the default DPI as a workaround. */
if (wl_fixed_from_int(window_->scale) == scale_fractional || window_->scale <= 1) {
/* No fractional scaling. */
return window_->scale * base_dpi;
#ifdef WITH_GHOST_WAYLAND_FRACTIONAL_SCALE
if (window_->frame.fractional_scale) {
return gwl_window_fractional_to_viewport(window_->frame, base_dpi);
}
const int scale_ceil = window_->scale;
const int scale_floor = scale_ceil - 1;
const wl_fixed_t scale_fractional_final = wl_fixed_to_int(
scale_fractional *
/* Compensate for the buffer being rendered at `window_->scale`,
* then scaled down fractionally. */
(wl_fixed_from_int(1) + ((wl_fixed_from_int(scale_ceil) - scale_fractional) / scale_floor)));
#endif /* WITH_GHOST_WAYLAND_FRACTIONAL_SCALE */
return wl_fixed_to_int(scale_fractional_final * base_dpi);
return window_->scale * base_dpi;
}
GHOST_TSuccess GHOST_WindowWayland::setWindowCursorVisibility(bool visible)
@ -1383,18 +1689,34 @@ int GHOST_WindowWayland::scale() const
return window_->scale;
}
wl_fixed_t GHOST_WindowWayland::scale_fractional() const
const struct GWL_WindowScaleParams &GHOST_WindowWayland::scale_params() const
{
return window_->scale_fractional;
/* NOTE(@ideasman42): This could be kept initialized,
* since it's such a small struct it's not so important. */
GWL_WindowScaleParams *scale_params = &window_->scale_params;
scale_params->is_fractional = (window_->frame.fractional_scale != 0);
scale_params->scale = scale_params->is_fractional ? window_->frame.fractional_scale :
window_->scale;
return *scale_params;
}
wl_fixed_t GHOST_WindowWayland::wl_fixed_from_window(wl_fixed_t value) const
{
#ifdef WITH_GHOST_WAYLAND_FRACTIONAL_SCALE
if (window_->frame.fractional_scale) {
return gwl_window_fractional_from_viewport(window_->frame, value);
}
#endif
return value / window_->scale;
}
wl_fixed_t GHOST_WindowWayland::wl_fixed_to_window(wl_fixed_t value) const
{
#ifdef WITH_GHOST_WAYLAND_FRACTIONAL_SCALE
if (window_->frame.fractional_scale) {
return gwl_window_fractional_to_viewport(window_->frame, value);
}
#endif
return value * window_->scale;
}
@ -1513,13 +1835,19 @@ bool GHOST_WindowWayland::outputs_changed_update_scale()
}
#endif
wl_fixed_t scale_fractional_next = 0;
const int scale_next = outputs_max_scale_or_default(outputs(), 0, &scale_fractional_next);
const int scale_next = outputs_max_scale_or_default(outputs(), 0);
if (UNLIKELY(scale_next == 0)) {
return false;
}
const wl_fixed_t scale_fractional_curr = window_->scale_fractional;
#ifdef WITH_GHOST_WAYLAND_FRACTIONAL_SCALE
if (window_->frame.fractional_scale) {
GHOST_ASSERT(window_->scale == 1, "Scale is expected to be 1 for fractional scaling!");
window_->scale_on_output = scale_next;
return false;
}
#endif /* WITH_GHOST_WAYLAND_FRACTIONAL_SCALE */
const int scale_curr = window_->scale;
bool changed = false;
@ -1544,22 +1872,18 @@ bool GHOST_WindowWayland::outputs_changed_update_scale()
window_->frame_pending.size[0] = (window_->frame.size[0] / scale_curr) * scale_next;
window_->frame_pending.size[1] = (window_->frame.size[1] / scale_curr) * scale_next;
gwl_window_frame_pending_size_set(window_);
gwl_window_frame_pending_size_set(window_, nullptr);
changed = true;
}
if (scale_fractional_next != scale_fractional_curr) {
window_->scale_fractional = scale_fractional_next;
changed = true;
/* As this is a low-level function, we might want adding this event to be optional,
* always add the event unless it causes issues. */
GHOST_SystemWayland *system = window_->ghost_system;
system->pushEvent(
new GHOST_Event(system->getMilliSeconds(), GHOST_kEventWindowDPIHintChanged, this));
changed = true;
}
/* Ensure both are always set. */
window_->scale_on_output = window_->scale;
return changed;
}

View File

@ -140,7 +140,8 @@ class GHOST_WindowWayland : public GHOST_Window {
/* WAYLAND direct-data access. */
int scale() const;
wl_fixed_t scale_fractional() const;
const struct GWL_WindowScaleParams &scale_params() const;
struct wl_surface *wl_surface() const;
const std::vector<GWL_Output *> &outputs();