wayland: avoid potential floating point errors while scaling

Described in more detail in the upstream MR*. mpv naively rounds which
makes us susceptible to the mentioned error. Fix this by keeping
wl->scaling and wl->pending_scaling in the base 120 units. Use the
simple rounding algorithm when needed for calculating widths/heights.
Create a wl->scaling_factor as convenient shorthand for scale / 120
which is what wl->scaling used to previously be.

*: https://gitlab.freedesktop.org/wayland/wayland-protocols/-/merge_requests/309
This commit is contained in:
Dudemanguy 2024-07-05 15:53:05 -05:00
parent 0aebcbcc19
commit d15660f4ed
3 changed files with 66 additions and 49 deletions

View File

@ -525,19 +525,19 @@ static void resize(struct vo *vo)
vo_get_src_dst_rects(vo, &src, &dst, &p->screen_osd_res);
int window_w = MPMAX(0, p->screen_osd_res.ml + p->screen_osd_res.mr) + mp_rect_w(dst);
int window_h = MPMAX(0, p->screen_osd_res.mt + p->screen_osd_res.mb) + mp_rect_h(dst);
wp_viewport_set_destination(wl->viewport, lround(window_w / wl->scaling),
lround(window_h / wl->scaling));
wp_viewport_set_destination(wl->viewport, lround(window_w / wl->scaling_factor),
lround(window_h / wl->scaling_factor));
//now we restore pan for video viewport calculation
vo->opts->pan_x = vo_opts->pan_x;
vo->opts->pan_y = vo_opts->pan_y;
vo_get_src_dst_rects(vo, &src, &dst, &p->screen_osd_res);
wp_viewport_set_destination(wl->video_viewport, lround(mp_rect_w(dst) / wl->scaling),
lround(mp_rect_h(dst) / wl->scaling));
wl_subsurface_set_position(wl->video_subsurface, lround(dst.x0 / wl->scaling), lround(dst.y0 / wl->scaling));
wp_viewport_set_destination(wl->osd_viewport, lround(vo->dwidth / wl->scaling),
lround(vo->dheight / wl->scaling));
wl_subsurface_set_position(wl->osd_subsurface, lround((0 - dst.x0) / wl->scaling), lround((0 - dst.y0) / wl->scaling));
wp_viewport_set_destination(wl->video_viewport, lround(mp_rect_w(dst) / wl->scaling_factor),
lround(mp_rect_h(dst) / wl->scaling_factor));
wl_subsurface_set_position(wl->video_subsurface, lround(dst.x0 / wl->scaling_factor), lround(dst.y0 / wl->scaling_factor));
wp_viewport_set_destination(wl->osd_viewport, lround(vo->dwidth / wl->scaling_factor),
lround(vo->dheight / wl->scaling_factor));
wl_subsurface_set_position(wl->osd_subsurface, lround((0 - dst.x0) / wl->scaling_factor), lround((0 - dst.y0) / wl->scaling_factor));
set_viewport_source(vo, src);
}

View File

@ -62,6 +62,9 @@
#define XDG_TOPLEVEL_STATE_SUSPENDED 9
#endif
// From the fractional scale protocol
#define WAYLAND_SCALE_FACTOR 120.0
static const struct mp_keymap keymap[] = {
/* Special keys */
@ -214,7 +217,7 @@ static int check_for_resize(struct vo_wayland_state *wl, int edge_pixels,
enum xdg_toplevel_resize_edge *edge);
static int get_mods(struct vo_wayland_seat *seat);
static int greatest_common_divisor(int a, int b);
static void handle_key_input(struct vo_wayland_seat *s, uint32_t key, uint32_t state);
static int handle_round(int scale, int n);
static int set_cursor_visibility(struct vo_wayland_seat *s, bool on);
static int spawn_cursor(struct vo_wayland_state *wl);
@ -223,6 +226,7 @@ static void add_feedback(struct vo_wayland_feedback_pool *fback_pool,
static void apply_keepaspect(struct vo_wayland_state *wl, int *width, int *height);
static void get_shape_device(struct vo_wayland_state *wl, struct vo_wayland_seat *s);
static void guess_focus(struct vo_wayland_state *wl);
static void handle_key_input(struct vo_wayland_seat *s, uint32_t key, uint32_t state);
static void prepare_resize(struct vo_wayland_state *wl);
static void remove_feedback(struct vo_wayland_feedback_pool *fback_pool,
struct wp_presentation_feedback *fback);
@ -248,8 +252,8 @@ static void pointer_handle_enter(void *data, struct wl_pointer *pointer,
set_cursor_visibility(s, wl->cursor_visible);
mp_input_put_key(wl->vo->input_ctx, MP_KEY_MOUSE_ENTER);
wl->mouse_x = wl_fixed_to_int(sx) * wl->scaling;
wl->mouse_y = wl_fixed_to_int(sy) * wl->scaling;
wl->mouse_x = handle_round(wl->scaling, wl_fixed_to_int(sx));
wl->mouse_y = handle_round(wl->scaling, wl_fixed_to_int(sy));
if (!wl->toplevel_configured)
mp_input_set_mouse_pos(wl->vo->input_ctx, wl->mouse_x, wl->mouse_y);
@ -270,8 +274,8 @@ static void pointer_handle_motion(void *data, struct wl_pointer *pointer,
struct vo_wayland_seat *s = data;
struct vo_wayland_state *wl = s->wl;
wl->mouse_x = wl_fixed_to_int(sx) * wl->scaling;
wl->mouse_y = wl_fixed_to_int(sy) * wl->scaling;
wl->mouse_x = handle_round(wl->scaling, wl_fixed_to_int(sx));
wl->mouse_y = handle_round(wl->scaling, wl_fixed_to_int(sy));
if (!wl->toplevel_configured)
mp_input_set_mouse_pos(wl->vo->input_ctx, wl->mouse_x, wl->mouse_y);
@ -432,8 +436,8 @@ static void touch_handle_down(void *data, struct wl_touch *wl_touch,
struct vo_wayland_seat *s = data;
struct vo_wayland_state *wl = s->wl;
// Note: the position should still be saved here for VO dragging handling.
wl->mouse_x = wl_fixed_to_int(x_w) * wl->scaling;
wl->mouse_y = wl_fixed_to_int(y_w) * wl->scaling;
wl->mouse_x = handle_round(wl->scaling, wl_fixed_to_int(x_w));
wl->mouse_y = handle_round(wl->scaling, wl_fixed_to_int(y_w));
mp_input_add_touch_point(wl->vo->input_ctx, id, wl->mouse_x, wl->mouse_y);
@ -464,8 +468,8 @@ static void touch_handle_motion(void *data, struct wl_touch *wl_touch,
struct vo_wayland_seat *s = data;
struct vo_wayland_state *wl = s->wl;
wl->mouse_x = wl_fixed_to_int(x_w) * wl->scaling;
wl->mouse_y = wl_fixed_to_int(y_w) * wl->scaling;
wl->mouse_x = handle_round(wl->scaling, wl_fixed_to_int(x_w));
wl->mouse_y = handle_round(wl->scaling, wl_fixed_to_int(y_w));
mp_input_update_touch_point(wl->vo->input_ctx, id, wl->mouse_x, wl->mouse_y);
}
@ -857,10 +861,11 @@ static void output_handle_done(void *data, struct wl_output *wl_output)
MP_VERBOSE(o->wl, "Registered output %s %s (0x%x):\n"
"\tx: %dpx, y: %dpx\n"
"\tw: %dpx (%dmm), h: %dpx (%dmm)\n"
"\tscale: %d\n"
"\tscale: %f\n"
"\tHz: %f\n", o->make, o->model, o->id, o->geometry.x0,
o->geometry.y0, mp_rect_w(o->geometry), o->phys_width,
mp_rect_h(o->geometry), o->phys_height, o->scale, o->refresh_rate);
mp_rect_h(o->geometry), o->phys_height,
o->scale / WAYLAND_SCALE_FACTOR, o->refresh_rate);
/* If we satisfy this conditional, something about the current
* output must have changed (resolution, scale, etc). All window
@ -882,7 +887,7 @@ static void output_handle_scale(void *data, struct wl_output *wl_output,
MP_ERR(output->wl, "Invalid output scale given by the compositor!\n");
return;
}
output->scale = factor;
output->scale = factor * WAYLAND_SCALE_FACTOR;
}
static void output_handle_name(void *data, struct wl_output *wl_output,
@ -933,7 +938,7 @@ static void surface_handle_enter(void *data, struct wl_surface *wl_surface,
MP_VERBOSE(wl, "Surface entered output %s %s (0x%x), scale = %f, refresh rate = %f Hz\n",
wl->current_output->make, wl->current_output->model,
wl->current_output->id, wl->scaling, wl->current_output->refresh_rate);
wl->current_output->id, wl->scaling_factor, wl->current_output->refresh_rate);
wl->pending_vo_events |= VO_EVENT_WIN_STATE;
}
@ -972,13 +977,13 @@ static void surface_handle_preferred_buffer_scale(void *data,
{
struct vo_wayland_state *wl = data;
if (wl->fractional_scale_manager || wl->scaling == scale)
if (wl->fractional_scale_manager || wl->scaling == scale * WAYLAND_SCALE_FACTOR)
return;
wl->pending_scaling = scale;
wl->pending_scaling = scale * WAYLAND_SCALE_FACTOR;
wl->scale_configured = true;
MP_VERBOSE(wl, "Obtained preferred scale, %f, from the compositor.\n",
wl->scaling);
wl->scaling / WAYLAND_SCALE_FACTOR);
wl->pending_vo_events |= VO_EVENT_DPI;
wl->need_rescale = true;
@ -986,8 +991,10 @@ static void surface_handle_preferred_buffer_scale(void *data,
if (single_output_spanned(wl))
update_output_scaling(wl);
if (!wl->current_output)
if (!wl->current_output) {
wl->scaling = wl->pending_scaling;
wl->scaling_factor = scale;
}
}
static void surface_handle_preferred_buffer_transform(void *data,
@ -1144,13 +1151,13 @@ static void handle_toplevel_config(void *data, struct xdg_toplevel *toplevel,
apply_keepaspect(wl, &width, &height);
wl->window_size.x0 = 0;
wl->window_size.y0 = 0;
wl->window_size.x1 = lround(width * wl->scaling);
wl->window_size.y1 = lround(height * wl->scaling);
wl->window_size.x1 = handle_round(wl->scaling, width);
wl->window_size.y1 = handle_round(wl->scaling, height);
}
wl->geometry.x0 = 0;
wl->geometry.y0 = 0;
wl->geometry.x1 = lround(width * wl->scaling);
wl->geometry.y1 = lround(height * wl->scaling);
wl->geometry.x1 = handle_round(wl->scaling, width);
wl->geometry.y1 = handle_round(wl->scaling, height);
if (mp_rect_equals(&old_geometry, &wl->geometry))
return;
@ -1174,8 +1181,8 @@ static void handle_configure_bounds(void *data, struct xdg_toplevel *xdg_topleve
int32_t width, int32_t height)
{
struct vo_wayland_state *wl = data;
wl->bounded_width = width * wl->scaling;
wl->bounded_height = height * wl->scaling;
wl->bounded_width = handle_round(wl->scaling, width);
wl->bounded_height = handle_round(wl->scaling, height);
}
static void handle_wm_capabilities(void *data, struct xdg_toplevel *xdg_toplevel,
@ -1195,22 +1202,23 @@ static void preferred_scale(void *data,
uint32_t scale)
{
struct vo_wayland_state *wl = data;
double new_scale = (double)scale / 120;
if (wl->scaling == new_scale)
if (wl->scaling == scale)
return;
wl->pending_scaling = new_scale;
wl->pending_scaling = scale;
wl->scale_configured = true;
MP_VERBOSE(wl, "Obtained preferred scale, %f, from the compositor.\n",
wl->pending_scaling);
wl->pending_scaling / WAYLAND_SCALE_FACTOR);
wl->need_rescale = true;
// Update scaling now.
if (single_output_spanned(wl))
update_output_scaling(wl);
if (!wl->current_output)
if (!wl->current_output) {
wl->scaling = wl->pending_scaling;
wl->scaling_factor = wl->scaling / WAYLAND_SCALE_FACTOR;
}
}
static const struct wp_fractional_scale_v1_listener fractional_scale_listener = {
@ -1906,10 +1914,16 @@ static void handle_key_input(struct vo_wayland_seat *s, uint32_t key,
s->mpkey = 0;
}
// Avoid possible floating point errors.
static int handle_round(int scale, int n)
{
return (scale * n + WAYLAND_SCALE_FACTOR / 2) / WAYLAND_SCALE_FACTOR;
}
static void prepare_resize(struct vo_wayland_state *wl)
{
int32_t width = mp_rect_w(wl->geometry) / wl->scaling;
int32_t height = mp_rect_h(wl->geometry) / wl->scaling;
int32_t width = mp_rect_w(wl->geometry) / wl->scaling_factor;
int32_t height = mp_rect_h(wl->geometry) / wl->scaling_factor;
xdg_surface_set_window_geometry(wl->xdg_surface, 0, 0, width, height);
wl->pending_vo_events |= VO_EVENT_RESIZE;
}
@ -2036,7 +2050,7 @@ static int set_cursor_visibility(struct vo_wayland_seat *s, bool on)
struct wl_buffer *buffer = wl_cursor_image_get_buffer(img);
if (!buffer)
return VO_FALSE;
double scale = MPMAX(wl->scaling, 1);
double scale = MPMAX(wl->scaling_factor, 1);
wl_pointer_set_cursor(s->pointer, s->pointer_enter_serial, wl->cursor_surface,
img->hotspot_x / scale, img->hotspot_y / scale);
wp_viewport_set_destination(wl->cursor_viewport, lround(img->width / scale),
@ -2080,7 +2094,7 @@ static void set_geometry(struct vo_wayland_state *wl, bool resize)
struct vo_win_geometry geo;
struct mp_rect screenrc = wl->current_output->geometry;
vo_calc_window_geometry2(vo, &screenrc, wl->scaling, &geo);
vo_calc_window_geometry2(vo, &screenrc, wl->scaling_factor, &geo);
vo_apply_window_geometry(vo, &geo);
int gcd = greatest_common_divisor(vo->dwidth, vo->dheight);
@ -2137,6 +2151,7 @@ static void set_surface_scaling(struct vo_wayland_state *wl)
double old_scale = wl->scaling;
wl->scaling = wl->current_output->scale;
wl->scaling_factor = wl->scaling / WAYLAND_SCALE_FACTOR;
rescale_geometry(wl, old_scale);
wl->pending_vo_events |= VO_EVENT_DPI;
}
@ -2192,7 +2207,8 @@ static int spawn_cursor(struct vo_wayland_state *wl)
size = (int)size_long;
}
wl->cursor_theme = wl_cursor_theme_load(xcursor_theme, size*wl->scaling, wl->shm);
wl->cursor_theme = wl_cursor_theme_load(xcursor_theme, handle_round(wl->scaling, size),
wl->shm);
if (!wl->cursor_theme) {
MP_ERR(wl, "Unable to load cursor theme!\n");
return 1;
@ -2245,6 +2261,7 @@ static void update_output_scaling(struct vo_wayland_state *wl)
{
double old_scale = wl->scaling;
wl->scaling = wl->pending_scaling;
wl->scaling_factor = wl->scaling / WAYLAND_SCALE_FACTOR;
rescale_geometry(wl, old_scale);
set_geometry(wl, false);
prepare_resize(wl);
@ -2500,9 +2517,9 @@ int vo_wayland_control(struct vo *vo, int *events, int request, void *arg)
return VO_TRUE;
}
case VOCTRL_GET_HIDPI_SCALE: {
if (!wl->scaling)
if (!wl->scaling_factor)
return VO_NOTAVAIL;
*(double *)arg = wl->scaling;
*(double *)arg = wl->scaling_factor;
return VO_TRUE;
}
case VOCTRL_BEGIN_DRAGGING:
@ -2523,9 +2540,8 @@ int vo_wayland_control(struct vo *vo, int *events, int request, void *arg)
void vo_wayland_handle_scale(struct vo_wayland_state *wl)
{
wp_viewport_set_destination(wl->viewport,
lround(mp_rect_w(wl->geometry) / wl->scaling),
lround(mp_rect_h(wl->geometry) / wl->scaling));
wp_viewport_set_destination(wl->viewport, lround(mp_rect_w(wl->geometry) / wl->scaling_factor),
lround(mp_rect_h(wl->geometry) / wl->scaling_factor));
}
bool vo_wayland_init(struct vo *vo)
@ -2543,7 +2559,7 @@ bool vo_wayland_init(struct vo *vo)
.bounded_width = 0,
.bounded_height = 0,
.refresh_interval = 0,
.scaling = 1,
.scaling = WAYLAND_SCALE_FACTOR,
.wakeup_pipe = {-1, -1},
.display_fd = -1,
.dnd_fd = -1,

View File

@ -86,8 +86,9 @@ struct vo_wayland_state {
int mouse_x;
int mouse_y;
int pending_vo_events;
double pending_scaling;
double scaling;
int pending_scaling; // base 120
int scaling; // base 120
double scaling_factor; // wl->scaling divided by 120
int timeout_count;
int wakeup_pipe[2];