diff --git a/hw/xwayland/xwayland-cursor.c b/hw/xwayland/xwayland-cursor.c
index e3c1aaa50..eba29b5ba 100644
--- a/hw/xwayland/xwayland-cursor.c
+++ b/hw/xwayland/xwayland-cursor.c
@@ -164,6 +164,8 @@ xwl_cursor_attach_pixmap(struct xwl_seat *xwl_seat,
     }
 
     wl_surface_attach(xwl_cursor->surface, buffer, 0, 0);
+    wl_surface_set_buffer_scale(xwl_cursor->surface,
+                                xwl_seat->xwl_screen->global_output_scale);
     xwl_surface_damage(xwl_seat->xwl_screen, xwl_cursor->surface, 0, 0,
                        xwl_seat->x_cursor->bits->width,
                        xwl_seat->x_cursor->bits->height);
@@ -195,6 +197,7 @@ xwl_cursor_clear_frame_cb(struct xwl_cursor *xwl_cursor)
 void
 xwl_seat_set_cursor(struct xwl_seat *xwl_seat)
 {
+    struct xwl_screen *xwl_screen = xwl_seat->xwl_screen;
     struct xwl_cursor *xwl_cursor = &xwl_seat->cursor;
     PixmapPtr pixmap;
     CursorPtr cursor;
@@ -225,8 +228,8 @@ xwl_seat_set_cursor(struct xwl_seat *xwl_seat)
     wl_pointer_set_cursor(xwl_seat->wl_pointer,
                           xwl_seat->pointer_enter_serial,
                           xwl_cursor->surface,
-                          xwl_seat->x_cursor->bits->xhot,
-                          xwl_seat->x_cursor->bits->yhot);
+                          xwl_scale_to(xwl_screen, xwl_seat->x_cursor->bits->xhot),
+                          xwl_scale_to(xwl_screen, xwl_seat->x_cursor->bits->yhot));
 
     xwl_cursor_attach_pixmap(xwl_seat, xwl_cursor, pixmap);
 }
@@ -235,6 +238,7 @@ void
 xwl_tablet_tool_set_cursor(struct xwl_tablet_tool *xwl_tablet_tool)
 {
     struct xwl_seat *xwl_seat = xwl_tablet_tool->seat;
+    struct xwl_screen *xwl_screen = xwl_seat->xwl_screen;
     struct xwl_cursor *xwl_cursor = &xwl_tablet_tool->cursor;
     PixmapPtr pixmap;
     CursorPtr cursor;
@@ -263,8 +267,9 @@ xwl_tablet_tool_set_cursor(struct xwl_tablet_tool *xwl_tablet_tool)
     zwp_tablet_tool_v2_set_cursor(xwl_tablet_tool->tool,
                                   xwl_tablet_tool->proximity_in_serial,
                                   xwl_cursor->surface,
-                                  xwl_seat->x_cursor->bits->xhot,
-                                  xwl_seat->x_cursor->bits->yhot);
+                                  xwl_scale_to(xwl_screen, xwl_seat->x_cursor->bits->xhot),
+                                  xwl_scale_to(xwl_screen, xwl_seat->x_cursor->bits->yhot));
+    wl_surface_set_buffer_scale(xwl_cursor->surface, xwl_screen->global_output_scale);
 
     xwl_cursor_attach_pixmap(xwl_seat, xwl_cursor, pixmap);
 }
diff --git a/hw/xwayland/xwayland-input.c b/hw/xwayland/xwayland-input.c
index 6e0600e4e..4a22ebff0 100644
--- a/hw/xwayland/xwayland-input.c
+++ b/hw/xwayland/xwayland-input.c
@@ -507,8 +507,8 @@ pointer_handle_enter(void *data, struct wl_pointer *pointer,
     DeviceIntPtr dev = get_pointer_device(xwl_seat);
     DeviceIntPtr master;
     int i;
-    int sx = wl_fixed_to_int(sx_w);
-    int sy = wl_fixed_to_int(sy_w);
+    int sx = wl_fixed_to_int(sx_w) * xwl_seat->xwl_screen->global_output_scale;
+    int sy = wl_fixed_to_int(sy_w) * xwl_seat->xwl_screen->global_output_scale;
     int dx, dy;
     ScreenPtr pScreen = xwl_seat->xwl_screen->screen;
     ValuatorMask mask;
@@ -731,13 +731,14 @@ pointer_handle_motion(void *data, struct wl_pointer *pointer,
                       uint32_t time, wl_fixed_t sx_w, wl_fixed_t sy_w)
 {
     struct xwl_seat *xwl_seat = data;
+    int32_t scale = xwl_seat->xwl_screen->global_output_scale;
 
     if (!xwl_seat->focus_window)
         return;
 
     xwl_seat->pending_pointer_event.has_absolute = TRUE;
-    xwl_seat->pending_pointer_event.x = sx_w;
-    xwl_seat->pending_pointer_event.y = sy_w;
+    xwl_seat->pending_pointer_event.x = sx_w * scale;
+    xwl_seat->pending_pointer_event.y = sy_w * scale;
 
     if (wl_proxy_get_version((struct wl_proxy *) xwl_seat->wl_pointer) < 5)
         dispatch_pointer_motion_event(xwl_seat);
@@ -887,12 +888,13 @@ relative_pointer_handle_relative_motion(void *data,
                                         wl_fixed_t dy_unaccelf)
 {
     struct xwl_seat *xwl_seat = data;
+    int32_t scale = xwl_seat->xwl_screen->global_output_scale;
 
     xwl_seat->pending_pointer_event.has_relative = TRUE;
-    xwl_seat->pending_pointer_event.dx = wl_fixed_to_double(dxf);
-    xwl_seat->pending_pointer_event.dy = wl_fixed_to_double(dyf);
-    xwl_seat->pending_pointer_event.dx_unaccel = wl_fixed_to_double(dx_unaccelf);
-    xwl_seat->pending_pointer_event.dy_unaccel = wl_fixed_to_double(dy_unaccelf);
+    xwl_seat->pending_pointer_event.dx = wl_fixed_to_double(dxf) * scale;
+    xwl_seat->pending_pointer_event.dy = wl_fixed_to_double(dyf) * scale;
+    xwl_seat->pending_pointer_event.dx_unaccel = wl_fixed_to_double(dx_unaccelf) * scale;
+    xwl_seat->pending_pointer_event.dy_unaccel = wl_fixed_to_double(dy_unaccelf) * scale;
 
     if (!xwl_seat->focus_window)
         return;
@@ -1382,8 +1384,8 @@ touch_handle_down(void *data, struct wl_touch *wl_touch,
 
     xwl_touch->window = wl_surface_get_user_data(surface);
     xwl_touch->id = id;
-    xwl_touch->x = wl_fixed_to_int(sx_w);
-    xwl_touch->y = wl_fixed_to_int(sy_w);
+    xwl_touch->x = wl_fixed_to_int(sx_w) * xwl_seat->xwl_screen->global_output_scale;
+    xwl_touch->y = wl_fixed_to_int(sy_w) * xwl_seat->xwl_screen->global_output_scale;
     xorg_list_add(&xwl_touch->link_touch, &xwl_seat->touches);
 
     xwl_touch_send_event(xwl_touch, xwl_seat, XI_TouchBegin);
@@ -1419,8 +1421,8 @@ touch_handle_motion(void *data, struct wl_touch *wl_touch,
     if (!xwl_touch)
         return;
 
-    xwl_touch->x = wl_fixed_to_int(sx_w);
-    xwl_touch->y = wl_fixed_to_int(sy_w);
+    xwl_touch->x = wl_fixed_to_int(sx_w) * xwl_seat->xwl_screen->global_output_scale;
+    xwl_touch->y = wl_fixed_to_int(sy_w) * xwl_seat->xwl_screen->global_output_scale;
     xwl_touch_send_event(xwl_touch, xwl_seat, XI_TouchUpdate);
 }
 
@@ -2110,8 +2112,8 @@ tablet_tool_motion(void *data, struct zwp_tablet_tool_v2 *tool,
     struct xwl_tablet_tool *xwl_tablet_tool = data;
     struct xwl_seat *xwl_seat = xwl_tablet_tool->seat;
     int32_t dx, dy;
-    double sx = wl_fixed_to_double(x);
-    double sy = wl_fixed_to_double(y);
+    double sx = wl_fixed_to_double(x) * xwl_seat->xwl_screen->global_output_scale;
+    double sy = wl_fixed_to_double(y) * xwl_seat->xwl_screen->global_output_scale;
 
     if (!xwl_seat->tablet_focus_window)
         return;
@@ -3152,6 +3154,7 @@ xwl_pointer_warp_emulator_set_fake_pos(struct xwl_pointer_warp_emulator *warp_em
                                        int x,
                                        int y)
 {
+    struct xwl_screen *xwl_screen;
     struct zwp_locked_pointer_v1 *locked_pointer =
         warp_emulator->locked_pointer;
     WindowPtr window;
@@ -3163,6 +3166,7 @@ xwl_pointer_warp_emulator_set_fake_pos(struct xwl_pointer_warp_emulator *warp_em
     if (!warp_emulator->xwl_seat->focus_window)
         return;
 
+    xwl_screen = warp_emulator->xwl_seat->xwl_screen;
     window = warp_emulator->xwl_seat->focus_window->window;
     if (x >= window->drawable.x ||
         y >= window->drawable.y ||
@@ -3171,8 +3175,8 @@ xwl_pointer_warp_emulator_set_fake_pos(struct xwl_pointer_warp_emulator *warp_em
         sx = x - window->drawable.x;
         sy = y - window->drawable.y;
         zwp_locked_pointer_v1_set_cursor_position_hint(locked_pointer,
-                                                       wl_fixed_from_int(sx),
-                                                       wl_fixed_from_int(sy));
+                                                       wl_fixed_from_int(xwl_scale_to(xwl_screen, sx)),
+                                                       wl_fixed_from_int(xwl_scale_to(xwl_screen, sy)));
         wl_surface_commit(warp_emulator->xwl_seat->focus_window->surface);
     }
 }
diff --git a/hw/xwayland/xwayland-output.c b/hw/xwayland/xwayland-output.c
index 661e1828d..6c60aba34 100644
--- a/hw/xwayland/xwayland-output.c
+++ b/hw/xwayland/xwayland-output.c
@@ -186,6 +186,9 @@ update_backing_pixmaps(struct xwl_screen *xwl_screen, int width, int height)
 static void
 update_screen_size(struct xwl_screen *xwl_screen, int width, int height)
 {
+    width *= xwl_screen->global_output_scale;
+    height *= xwl_screen->global_output_scale;
+
     xwl_screen->width = width;
     xwl_screen->height = height;
 
@@ -597,14 +600,15 @@ xwl_output_set_emulated_mode(struct xwl_output *xwl_output, ClientPtr client,
                                              new_emulated_height);
 }
 
-static void
-apply_output_change(struct xwl_output *xwl_output)
+void
+xwl_output_apply_changes(struct xwl_output *xwl_output)
 {
     struct xwl_screen *xwl_screen = xwl_output->xwl_screen;
     struct xwl_output *it;
     int mode_width, mode_height, count;
     int width = 0, height = 0, has_this_output = 0;
     RRModePtr *randr_modes;
+    int32_t scale = xwl_screen->global_output_scale;
 
     /* Clear out the "done" received flags */
     xwl_output->wl_output_done = FALSE;
@@ -623,10 +627,10 @@ apply_output_change(struct xwl_output *xwl_output)
     }
     if (xwl_output->randr_output) {
         /* Build a fresh modes array using the current refresh rate */
-        randr_modes = output_get_rr_modes(xwl_output, mode_width, mode_height, &count);
+        randr_modes = output_get_rr_modes(xwl_output, mode_width * scale, mode_height * scale, &count);
         RROutputSetModes(xwl_output->randr_output, randr_modes, count, 1);
         RRCrtcNotify(xwl_output->randr_crtc, randr_modes[0],
-                     xwl_output->x, xwl_output->y,
+                     xwl_output->x * scale, xwl_output->y * scale,
                      xwl_output->rotation, NULL, 1, &xwl_output->randr_output);
         /* RROutputSetModes takes ownership of the passed in modes, so we only
          * have to free the pointer array.
@@ -686,7 +690,7 @@ output_handle_done(void *data, struct wl_output *wl_output)
      */
     if (xwl_output->xdg_output_done || !xwl_output->xdg_output ||
         zxdg_output_v1_get_version(xwl_output->xdg_output) >= 3)
-        apply_output_change(xwl_output);
+        xwl_output_apply_changes(xwl_output);
 }
 
 static void
@@ -746,7 +750,7 @@ xdg_output_handle_done(void *data, struct zxdg_output_v1 *xdg_output)
     xwl_output->xdg_output_done = TRUE;
     if (xwl_output->wl_output_done &&
         zxdg_output_v1_get_version(xdg_output) < 3)
-        apply_output_change(xwl_output);
+        xwl_output_apply_changes(xwl_output);
 }
 
 static void
@@ -857,6 +861,8 @@ xwl_output_create(struct xwl_screen *xwl_screen, uint32_t id,
         RRCrtcGammaSetSize(xwl_output->randr_crtc, 256);
         RROutputSetCrtcs(xwl_output->randr_output, &xwl_output->randr_crtc, 1);
         RROutputSetConnection(xwl_output->randr_output, RR_Connected);
+
+        xwl_output->scale = 1;
     }
     /* We want the output to be in the list as soon as created so we can
      * use it when binding to the xdg-output protocol...
diff --git a/hw/xwayland/xwayland-output.h b/hw/xwayland/xwayland-output.h
index a95288e4f..46d1ead2a 100644
--- a/hw/xwayland/xwayland-output.h
+++ b/hw/xwayland/xwayland-output.h
@@ -53,7 +53,7 @@ struct xwl_output {
     struct wl_output *output;
     struct zxdg_output_v1 *xdg_output;
     uint32_t server_output_id;
-    int32_t x, y, width, height, refresh;
+    int32_t x, y, width, height, refresh, scale;
     Rotation rotation;
     Bool wl_output_done;
     Bool xdg_output_done;
@@ -102,6 +102,8 @@ void xwl_output_set_emulated_mode(struct xwl_output *xwl_output,
 void xwl_output_set_window_randr_emu_props(struct xwl_screen *xwl_screen,
                                            WindowPtr window);
 
+void xwl_output_apply_changes(struct xwl_output *xwl_output);
+
 void xwl_screen_init_xdg_output(struct xwl_screen *xwl_screen);
 
 #endif /* XWAYLAND_OUTPUT_H */
diff --git a/hw/xwayland/xwayland-present.c b/hw/xwayland/xwayland-present.c
index 189e7cfd6..555434031 100644
--- a/hw/xwayland/xwayland-present.c
+++ b/hw/xwayland/xwayland-present.c
@@ -764,6 +764,8 @@ xwl_present_flip(WindowPtr present_window,
 
     /* We can flip directly to the main surface (full screen window without clips) */
     wl_surface_attach(xwl_window->surface, buffer, 0, 0);
+    wl_surface_set_buffer_scale(xwl_window->surface,
+                                xwl_window->xwl_screen->global_output_scale);
 
     if (xorg_list_is_empty(&xwl_present_window->frame_callback_list)) {
         xorg_list_add(&xwl_present_window->frame_callback_list,
diff --git a/hw/xwayland/xwayland-screen.c b/hw/xwayland/xwayland-screen.c
index 46ab4fed7..b2d7022e6 100644
--- a/hw/xwayland/xwayland-screen.c
+++ b/hw/xwayland/xwayland-screen.c
@@ -51,6 +51,7 @@
 #include "xwayland-pixmap.h"
 #include "xwayland-present.h"
 #include "xwayland-shm.h"
+#include "xwayland-window-buffers.h"
 
 #ifdef MITSHM
 #include "shmint.h"
@@ -111,6 +112,12 @@ xwl_screen_has_resolution_change_emulation(struct xwl_screen *xwl_screen)
     return xwl_screen->rootless && xwl_screen_has_viewport_support(xwl_screen);
 }
 
+int
+xwl_scale_to(struct xwl_screen *xwl_screen, int value)
+{
+    return value / (double)xwl_screen->global_output_scale + 0.5;
+}
+
 /* Return the output @ 0x0, falling back to the first output in the list */
 struct xwl_output *
 xwl_screen_get_first_output(struct xwl_screen *xwl_screen)
@@ -128,6 +135,38 @@ xwl_screen_get_first_output(struct xwl_screen *xwl_screen)
     return xorg_list_first_entry(&xwl_screen->output_list, struct xwl_output, link);
 }
 
+static void
+xwl_screen_set_global_scale_from_property(struct xwl_screen *screen,
+                                          PropertyPtr prop)
+{
+    CARD32 *propdata;
+
+    if (prop->type != XA_CARDINAL || prop->format != 32 || prop->size != 1) {
+        // TODO: handle warnings more cleanly.
+        LogMessageVerb(X_WARNING, 0, "Bad value for property %s.\n",
+                        NameForAtom(prop->propertyName));
+        return;
+    }
+
+    propdata = prop->data;
+    xwl_screen_set_global_scale(screen, propdata[0]);
+}
+
+static void
+xwl_screen_update_property(struct xwl_screen *screen,
+                           PropertyStateRec *propstate)
+{
+    switch (propstate->state) {
+    case PropertyNewValue:
+        xwl_screen_set_global_scale_from_property(screen, propstate->prop);
+        break;
+    case PropertyDelete:
+        xwl_screen_set_global_scale(screen, 1);
+        break;
+    }
+}
+
+
 struct xwl_output *
 xwl_screen_get_fixed_or_first_output(struct xwl_screen *xwl_screen)
 {
@@ -144,19 +183,24 @@ xwl_property_callback(CallbackListPtr *pcbl, void *closure,
     ScreenPtr screen = closure;
     PropertyStateRec *rec = calldata;
     struct xwl_screen *xwl_screen;
-    struct xwl_window *xwl_window;
 
     if (rec->win->drawable.pScreen != screen)
         return;
 
-    xwl_window = xwl_window_get(rec->win);
-    if (!xwl_window)
-        return;
-
     xwl_screen = xwl_screen_get(screen);
 
-    if (rec->prop->propertyName == xwl_screen->allow_commits_prop)
-        xwl_window_update_property(xwl_window, rec);
+    if (rec->prop->propertyName == xwl_screen->allow_commits_prop) {
+        struct xwl_window *xwl_window;
+
+        xwl_window = xwl_window_get(rec->win);
+        if (!xwl_window)
+            return;
+
+         xwl_window_update_property(xwl_window, rec);
+    }
+    else if (rec->prop->propertyName == xwl_screen->global_output_scale_prop) {
+        xwl_screen_update_property(xwl_screen, rec);
+    }
 }
 
 static void
@@ -638,8 +682,14 @@ void xwl_surface_damage(struct xwl_screen *xwl_screen,
 {
     if (wl_surface_get_version(surface) >= WL_SURFACE_DAMAGE_BUFFER_SINCE_VERSION)
         wl_surface_damage_buffer(surface, x, y, width, height);
-    else
+    else {
+        x = xwl_scale_to(xwl_screen, x);
+        y = xwl_scale_to(xwl_screen, y);
+        width = xwl_scale_to(xwl_screen, width);
+        height = xwl_scale_to(xwl_screen, height);
+
         wl_surface_damage(surface, x, y, width, height);
+    }
 }
 
 void
@@ -655,6 +705,30 @@ xwl_screen_roundtrip(struct xwl_screen *xwl_screen)
         xwl_give_up("could not connect to wayland server\n");
 }
 
+void
+xwl_screen_set_global_scale(struct xwl_screen *xwl_screen, int32_t scale)
+{
+    struct xwl_output *it;
+    struct xwl_window *xwl_window;
+
+    xwl_screen->global_output_scale = scale;
+
+    /* change randr resolutions and positions */
+    xorg_list_for_each_entry(it, &xwl_screen->output_list, link) {
+        xwl_output_apply_changes(it);
+    }
+
+    if (!xwl_screen->rootless && xwl_screen->screen->root) {
+        /* Clear all the buffers, so that they'll be remade with the new sizes
+         * (this doesn't occur automatically because as far as Xorg is
+         *  concerned, the window's size is the same) */
+        xorg_list_for_each_entry(xwl_window, &xwl_screen->window_list, link_window) {
+            xwl_window_buffers_recycle(xwl_window);
+        }
+    }
+}
+
+
 static int
 xwl_server_grab(ClientPtr client)
 {
@@ -712,6 +786,7 @@ Bool
 xwl_screen_init(ScreenPtr pScreen, int argc, char **argv)
 {
     static const char allow_commits[] = "_XWAYLAND_ALLOW_COMMITS";
+    static const char global_output_scale[] = "_XWAYLAND_GLOBAL_OUTPUT_SCALE";
     struct xwl_screen *xwl_screen;
     Pixel red_mask, blue_mask, green_mask;
     int ret, bpc, green_bpc, i;
@@ -746,6 +821,7 @@ xwl_screen_init(ScreenPtr pScreen, int argc, char **argv)
 #ifdef XWL_HAS_GLAMOR
     xwl_screen->glamor = 1;
 #endif
+    xwl_screen->global_output_scale = 1;
 
     for (i = 1; i < argc; i++) {
         if (strcmp(argv[i], "-rootless") == 0) {
@@ -988,6 +1064,13 @@ xwl_screen_init(ScreenPtr pScreen, int argc, char **argv)
     if (xwl_screen->allow_commits_prop == BAD_RESOURCE)
         return FALSE;
 
+    xwl_screen->global_output_scale_prop = MakeAtom(global_output_scale,
+                                                    strlen(global_output_scale),
+                                                    TRUE);
+    if (xwl_screen->global_output_scale_prop == BAD_RESOURCE)
+        return FALSE;
+
+
     AddCallback(&PropertyStateCallback, xwl_property_callback, pScreen);
     AddCallback(&RootWindowFinalizeCallback, xwl_root_window_finalized_callback, pScreen);
 
diff --git a/hw/xwayland/xwayland-screen.h b/hw/xwayland/xwayland-screen.h
index fadd0526e..2ce6ce5ab 100644
--- a/hw/xwayland/xwayland-screen.h
+++ b/hw/xwayland/xwayland-screen.h
@@ -87,6 +87,7 @@ struct xwl_screen {
     struct xorg_list damage_window_list;
     struct xorg_list window_list;
 
+    int32_t global_output_scale;
     int wayland_fd;
     struct wl_display *display;
     struct wl_registry *registry;
@@ -134,6 +135,7 @@ struct xwl_screen {
     struct glamor_context *glamor_ctx;
 
     Atom allow_commits_prop;
+    Atom global_output_scale_prop;
 
     /* The preferred GLVND vendor. If NULL, "mesa" is assumed. */
     const char *glvnd_vendor;
@@ -166,6 +168,8 @@ void xwl_screen_roundtrip (struct xwl_screen *xwl_screen);
 void xwl_surface_damage(struct xwl_screen *xwl_screen,
                         struct wl_surface *surface,
                         int32_t x, int32_t y, int32_t width, int32_t height);
+int xwl_scale_to(struct xwl_screen *xwl_screen, int value);
+void xwl_screen_set_global_scale(struct xwl_screen *xwl_screen, int32_t scale);
 int xwl_screen_get_next_output_serial(struct xwl_screen * xwl_screen);
 
 #endif /* XWAYLAND_SCREEN_H */
diff --git a/hw/xwayland/xwayland-window.c b/hw/xwayland/xwayland-window.c
index 6b7f38605..2f1e0dee1 100644
--- a/hw/xwayland/xwayland-window.c
+++ b/hw/xwayland/xwayland-window.c
@@ -788,7 +788,8 @@ xwl_create_root_surface(struct xwl_window *xwl_window)
     }
 
     wl_region_add(region, 0, 0,
-                  window->drawable.width, window->drawable.height);
+                 xwl_scale_to(xwl_screen, window->drawable.width),
+                 xwl_scale_to(xwl_screen, window->drawable.height));
     wl_surface_set_opaque_region(xwl_window->surface, region);
     wl_region_destroy(region);
 
@@ -1322,6 +1323,7 @@ xwl_window_post_damage(struct xwl_window *xwl_window)
 #endif
 
     wl_surface_attach(xwl_window->surface, buffer, 0, 0);
+    wl_surface_set_buffer_scale(xwl_window->surface, xwl_screen->global_output_scale);
 
     /* Arbitrary limit to try to avoid flooding the Wayland
      * connection. If we flood it too much anyway, this could