wayland: Fix some incorrect buffer scale calculations

Use doubles and apply an offset to account for rounding errors due to Wayland scale increments being in units of 1/120. This fixes the backbuffer size calculations with certain combinations of size/scale values, and future-proofs the Wayland backend, as 32-bit floats become increasingly error-prone with larger dimensions and/or scale factors.

The conversion formula is now point->pixel->point round trip safe as well.
This commit is contained in:
Frank Praznik 2024-09-12 13:36:08 -04:00
parent 94436a938d
commit 9d9721cd4c
No known key found for this signature in database
6 changed files with 53 additions and 63 deletions

View File

@ -521,8 +521,8 @@ static void pointer_handle_motion(void *data, struct wl_pointer *pointer,
input->sx_w = sx_w;
input->sy_w = sy_w;
if (input->pointer_focus) {
float sx = (float)(wl_fixed_to_double(sx_w) * window_data->pointer_scale.x);
float sy = (float)(wl_fixed_to_double(sy_w) * window_data->pointer_scale.y);
const float sx = (float)(wl_fixed_to_double(sx_w) * window_data->pointer_scale.x);
const float sy = (float)(wl_fixed_to_double(sy_w) * window_data->pointer_scale.y);
SDL_SendMouseMotion(Wayland_GetPointerTimestamp(input, time), window_data->sdlwindow, input->pointer_id, false, sx, sy);
}
@ -2590,12 +2590,8 @@ static void tablet_tool_handle_motion(void *data, struct zwp_tablet_tool_v2 *too
SDL_Window *window = sdltool->tool_focus;
if (window) {
const SDL_WindowData *windowdata = window->internal;
const float sx_f = (float)wl_fixed_to_double(sx_w);
const float sy_f = (float)wl_fixed_to_double(sy_w);
const float sx = sx_f * windowdata->pointer_scale.x;
const float sy = sy_f * windowdata->pointer_scale.y;
sdltool->x = sx;
sdltool->y = sy;
sdltool->x = (float)(wl_fixed_to_double(sx_w) * windowdata->pointer_scale.x);
sdltool->y = (float)(wl_fixed_to_double(sy_w) * windowdata->pointer_scale.y);
sdltool->frame_motion_set = true;
}
}
@ -3178,10 +3174,10 @@ bool Wayland_input_confine_pointer(struct SDL_WaylandInput *input, SDL_Window *w
} else {
SDL_Rect scaled_mouse_rect;
scaled_mouse_rect.x = (int)SDL_floorf((float)window->mouse_rect.x / w->pointer_scale.x);
scaled_mouse_rect.y = (int)SDL_floorf((float)window->mouse_rect.y / w->pointer_scale.y);
scaled_mouse_rect.w = (int)SDL_ceilf((float)window->mouse_rect.w / w->pointer_scale.x);
scaled_mouse_rect.h = (int)SDL_ceilf((float)window->mouse_rect.h / w->pointer_scale.y);
scaled_mouse_rect.x = (int)SDL_floor(window->mouse_rect.x / w->pointer_scale.x);
scaled_mouse_rect.y = (int)SDL_floor(window->mouse_rect.y / w->pointer_scale.y);
scaled_mouse_rect.w = (int)SDL_ceil(window->mouse_rect.w / w->pointer_scale.x);
scaled_mouse_rect.h = (int)SDL_ceil(window->mouse_rect.h / w->pointer_scale.y);
confine_rect = wl_compositor_create_region(d->compositor);
wl_region_add(confine_rect,

View File

@ -51,7 +51,7 @@ typedef struct
int dst_width;
int dst_height;
float scale;
double scale;
struct wl_list node;
} Wayland_CachedCustomCursor;
@ -332,7 +332,7 @@ static void cursor_frame_done(void *data, struct wl_callback *cb, uint32_t time)
wl_surface_commit(c->surface);
}
static bool wayland_get_system_cursor(SDL_VideoData *vdata, SDL_CursorData *cdata, float *scale)
static bool wayland_get_system_cursor(SDL_VideoData *vdata, SDL_CursorData *cdata, double *scale)
{
struct wl_cursor_theme *theme = NULL;
struct wl_cursor *cursor;
@ -357,9 +357,9 @@ static bool wayland_get_system_cursor(SDL_VideoData *vdata, SDL_CursorData *cdat
focus = SDL_GetMouse()->focus;
if (focus) {
// TODO: Use the fractional scale once GNOME supports viewports on cursor surfaces.
*scale = SDL_ceilf(focus->internal->windowed_scale_factor);
*scale = SDL_ceil(focus->internal->scale_factor);
} else {
*scale = 1.0f;
*scale = 1.0;
}
size *= (int)*scale;
@ -435,15 +435,15 @@ static Wayland_CachedCustomCursor *Wayland_GetCachedCustomCursor(SDL_Cursor *cur
SDL_CursorData *data = cursor->internal;
Wayland_CachedCustomCursor *cache;
SDL_Window *focus = SDL_GetMouseFocus();
float scale = 1.0f;
double scale = 1.0;
if (focus && SDL_SurfaceHasAlternateImages(data->cursor_data.custom.sdl_cursor_surface)) {
scale = focus->internal->windowed_scale_factor;
scale = focus->internal->scale_factor;
}
// Only use fractional scale values if viewports are available.
if (!wd->viewporter) {
scale = SDL_ceilf(scale);
scale = SDL_ceil(scale);
}
// Is this cursor already cached at the target scale?
@ -458,7 +458,7 @@ static Wayland_CachedCustomCursor *Wayland_GetCachedCustomCursor(SDL_Cursor *cur
return NULL;
}
SDL_Surface *surface = SDL_GetSurfaceImage(data->cursor_data.custom.sdl_cursor_surface, scale);
SDL_Surface *surface = SDL_GetSurfaceImage(data->cursor_data.custom.sdl_cursor_surface, (float)scale);
if (!surface) {
SDL_free(cache);
return NULL;
@ -675,7 +675,7 @@ static bool Wayland_ShowCursor(SDL_Cursor *cursor)
SDL_VideoData *d = vd->internal;
struct SDL_WaylandInput *input = d->input;
struct wl_pointer *pointer = d->pointer;
float scale = 1.0f;
double scale = 1.0;
int dst_width = 0;
int dst_height = 0;
@ -728,7 +728,7 @@ static bool Wayland_ShowCursor(SDL_Cursor *cursor)
}
// TODO: Make the viewport path the default in all cases once GNOME finally supports viewports on cursor surfaces.
if (SDL_ceilf(scale) != scale && d->viewporter) {
if (SDL_ceil(scale) != scale && d->viewporter) {
if (!data->viewport) {
data->viewport = wp_viewporter_get_viewport(d->viewporter, data->surface);
}

View File

@ -895,7 +895,7 @@ static void display_handle_done(void *data,
// ...and the compositor scales the logical viewport...
if (video->viewporter) {
// ...and viewports are supported, calculate the true scale of the output.
internal->scale_factor = (float)native_mode.w / (float)internal->screen_width;
internal->scale_factor = (double)native_mode.w / (double)internal->screen_width;
} else {
// ...otherwise, the 'native' pixel values are a multiple of the logical screen size.
internal->pixel_width = internal->screen_width * (int)internal->scale_factor;
@ -923,7 +923,7 @@ static void display_handle_done(void *data,
if (!video->scale_to_display_enabled) {
desktop_mode.w = internal->screen_width;
desktop_mode.h = internal->screen_height;
desktop_mode.pixel_density = internal->scale_factor;
desktop_mode.pixel_density = (float)internal->scale_factor;
} else {
desktop_mode.w = native_mode.w;
desktop_mode.h = native_mode.h;
@ -940,14 +940,14 @@ static void display_handle_done(void *data,
}
if (video->scale_to_display_enabled) {
SDL_SetDisplayContentScale(dpy, internal->scale_factor);
SDL_SetDisplayContentScale(dpy, (float)internal->scale_factor);
}
// Set the desktop display mode.
SDL_SetDesktopDisplayMode(dpy, &desktop_mode);
// Expose the unscaled, native resolution if the scale is 1.0 or viewports are available...
if (internal->scale_factor == 1.0f || video->viewporter) {
if (internal->scale_factor == 1.0 || video->viewporter) {
SDL_AddFullscreenDisplayMode(dpy, &native_mode);
} else {
// ...otherwise expose the integer scaled variants of the desktop resolution down to 1.

View File

@ -105,8 +105,8 @@ struct SDL_DisplayData
struct wl_output *output;
struct zxdg_output_v1 *xdg_output;
char *wl_output_name;
double scale_factor;
uint32_t registry_id;
float scale_factor;
int pixel_width, pixel_height;
int x, y, screen_width, screen_height, refresh, transform;
SDL_DisplayOrientation orientation;

View File

@ -48,24 +48,19 @@
#include <libdecor.h>
#endif
/* These are *NOT* roundtrip safe! */
// These are point->pixel->point round trip safe; the inverse is not round trip safe due to rounding.
static int PointToPixel(SDL_Window *window, int point)
{
// Rounds halfway away from zero as per the Wayland fractional scaling protocol spec.
return (int)SDL_lroundf((float)point * window->internal->windowed_scale_factor);
/* Rounds halfway away from zero as per the Wayland fractional scaling protocol spec.
* Wayland scale units are in units of 1/120, so the offset is required to correct for
* rounding errors when using certain scale values.
*/
return SDL_max((int)SDL_lround((double)point * window->internal->scale_factor + 1e-6), 1);
}
static int PixelToPoint(SDL_Window *window, int pixel)
{
return (int)SDL_lroundf((float)pixel / window->internal->windowed_scale_factor);
}
static bool FloatEqual(float a, float b)
{
const float diff = SDL_fabsf(a - b);
const float largest = SDL_max(SDL_fabsf(a), SDL_fabsf(b));
return diff <= largest * SDL_FLT_EPSILON;
return SDL_max((int)SDL_lround((double)pixel / window->internal->scale_factor), 1);
}
/* According to the Wayland spec:
@ -372,8 +367,8 @@ static void ConfigureWindowGeometry(SDL_Window *window)
data->current.logical_height = window->current_fullscreen_mode.h;
}
data->pointer_scale.x = (float)window_width / (float)data->current.logical_width;
data->pointer_scale.y = (float)window_height / (float)data->current.logical_height;
data->pointer_scale.x = (double)window_width / (double)data->current.logical_width;
data->pointer_scale.y = (double)window_height / (double)data->current.logical_height;
}
} else {
window_width = data->requested.logical_width;
@ -386,7 +381,7 @@ static void ConfigureWindowGeometry(SDL_Window *window)
wp_viewport_set_destination(data->viewport, window_width, window_height);
} else if (window->flags & SDL_WINDOW_HIGH_PIXEL_DENSITY) {
// Don't change this if the DPI awareness flag is unset, as an application may have set this manually on a custom or external surface.
wl_surface_set_buffer_scale(data->surface, (int32_t)data->windowed_scale_factor);
wl_surface_set_buffer_scale(data->surface, (int32_t)data->scale_factor);
}
// Clamp the physical window size to the system minimum required size.
@ -394,11 +389,11 @@ static void ConfigureWindowGeometry(SDL_Window *window)
data->current.logical_height = SDL_max(window_height, data->system_limits.min_height);
if (!data->scale_to_display) {
data->pointer_scale.x = 1.0f;
data->pointer_scale.y = 1.0f;
data->pointer_scale.x = 1.0;
data->pointer_scale.y = 1.0;
} else {
data->pointer_scale.x = data->windowed_scale_factor;
data->pointer_scale.y = data->windowed_scale_factor;
data->pointer_scale.x = data->scale_factor;
data->pointer_scale.y = data->scale_factor;
}
}
}
@ -1346,9 +1341,9 @@ static struct libdecor_frame_interface libdecor_frame_interface = {
};
#endif
static void Wayland_HandlePreferredScaleChanged(SDL_WindowData *window_data, float factor)
static void Wayland_HandlePreferredScaleChanged(SDL_WindowData *window_data, double factor)
{
const float old_factor = window_data->windowed_scale_factor;
const double old_factor = window_data->scale_factor;
if (!(window_data->sdlwindow->flags & SDL_WINDOW_HIGH_PIXEL_DENSITY) && !window_data->scale_to_display) {
// Scale will always be 1, just ignore this
@ -1357,11 +1352,11 @@ static void Wayland_HandlePreferredScaleChanged(SDL_WindowData *window_data, flo
// Round the scale factor if viewports aren't available.
if (!window_data->viewport) {
factor = SDL_ceilf(factor);
factor = SDL_ceil(factor);
}
if (!FloatEqual(factor, old_factor)) {
window_data->windowed_scale_factor = factor;
if (factor != old_factor) {
window_data->scale_factor = factor;
if (window_data->scale_to_display) {
/* If the window is in the floating state with a user/application specified size, calculate the new
@ -1384,7 +1379,7 @@ static void Wayland_HandlePreferredScaleChanged(SDL_WindowData *window_data, flo
static void Wayland_MaybeUpdateScaleFactor(SDL_WindowData *window)
{
float factor;
double factor;
int i;
/* If the fractional scale protocol is present or the core protocol supports the
@ -1398,14 +1393,14 @@ static void Wayland_MaybeUpdateScaleFactor(SDL_WindowData *window)
if (window->num_outputs != 0) {
// Check every display's factor, use the highest
factor = 0.0f;
factor = 0.0;
for (i = 0; i < window->num_outputs; i++) {
SDL_DisplayData *internal = window->outputs[i];
factor = SDL_max(factor, internal->scale_factor);
}
} else {
// All outputs removed, just fall back.
factor = window->windowed_scale_factor;
factor = window->scale_factor;
}
Wayland_HandlePreferredScaleChanged(window, factor);
@ -1481,7 +1476,7 @@ static void handle_preferred_buffer_scale(void *data, struct wl_surface *wl_surf
* only listen to this event if the fractional scaling protocol is not present.
*/
if (!wind->fractional_scale) {
Wayland_HandlePreferredScaleChanged(data, (float)factor);
Wayland_HandlePreferredScaleChanged(data, (double)factor);
}
}
@ -1499,7 +1494,7 @@ static const struct wl_surface_listener surface_listener = {
static void handle_preferred_fractional_scale(void *data, struct wp_fractional_scale_v1 *wp_fractional_scale_v1, uint32_t scale)
{
const float factor = scale / 120.f; // 120 is a magic number defined in the spec as a common denominator
const double factor = (double)scale / 120.; // 120 is a magic number defined in the spec as a common denominator
Wayland_HandlePreferredScaleChanged(data, factor);
}
@ -2397,16 +2392,15 @@ bool Wayland_CreateWindow(SDL_VideoDevice *_this, SDL_Window *window, SDL_Proper
data->waylandData = c;
data->sdlwindow = window;
data->windowed_scale_factor = 1.0f;
data->scale_factor = 1.0;
if (SDL_WINDOW_IS_POPUP(window)) {
data->scale_to_display = window->parent->internal->scale_to_display;
data->windowed_scale_factor = window->parent->internal->windowed_scale_factor;
data->scale_factor = window->parent->internal->scale_factor;
EnsurePopupPositionIsValid(window, &window->x, &window->y);
} else if ((window->flags & SDL_WINDOW_HIGH_PIXEL_DENSITY) || c->scale_to_display_enabled) {
for (int i = 0; i < _this->num_displays; i++) {
float scale = _this->displays[i]->internal->scale_factor;
data->windowed_scale_factor = SDL_max(data->windowed_scale_factor, scale);
data->scale_factor = SDL_max(data->scale_factor, _this->displays[i]->internal->scale_factor);
}
}

View File

@ -110,14 +110,14 @@ struct SDL_WindowData
SDL_Window *keyboard_focus;
char *app_id;
float windowed_scale_factor;
double scale_factor;
struct Wayland_SHMBuffer icon;
struct
{
float x;
float y;
double x;
double y;
} pointer_scale;
// The in-flight window size request.