[mutter/wip/carlosg/input-thread: 149/185] backends: Split kms cursor renderer from MetaCursorRendererNative




commit dbce4738d824a7ebfe767103edd7cc31acd6e1bd
Author: Carlos Garnacho <carlosg gnome org>
Date:   Tue Jul 14 13:34:13 2020 +0200

    backends: Split kms cursor renderer from MetaCursorRendererNative
    
    Move the KMS updating pieces to a distinct MetaKmsCursorRenderer whose
    cursor sprite will get updated from the driving MetaCursorRenderer.
    Since there could be multiple MetaCursorRenderers, also add the
    necessary pieces to move the KMS cursor renderer between renderers.
    
    https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1403

 src/backends/native/meta-cursor-renderer-native.c | 1593 +------------------
 src/backends/native/meta-cursor-renderer-native.h |    4 +
 src/backends/native/meta-kms-cursor-renderer.c    | 1700 +++++++++++++++++++++
 src/backends/native/meta-kms-cursor-renderer.h    |   43 +
 src/backends/native/meta-seat-native.c            |    5 +
 src/backends/native/meta-seat-native.h            |    1 +
 src/meson.build                                   |    2 +
 7 files changed, 1802 insertions(+), 1546 deletions(-)
---
diff --git a/src/backends/native/meta-cursor-renderer-native.c 
b/src/backends/native/meta-cursor-renderer-native.c
index ccf0434ee7..65a5315c8b 100644
--- a/src/backends/native/meta-cursor-renderer-native.c
+++ b/src/backends/native/meta-cursor-renderer-native.c
@@ -28,53 +28,17 @@
 #include "backends/native/meta-cursor-renderer-native.h"
 
 #include <string.h>
-#include <gbm.h>
-#include <xf86drm.h>
 #include <errno.h>
 
 #include "backends/meta-backend-private.h"
-#include "backends/meta-cursor-sprite-xcursor.h"
-#include "backends/meta-logical-monitor.h"
 #include "backends/meta-monitor.h"
 #include "backends/meta-monitor-manager-private.h"
-#include "backends/meta-output.h"
-#include "backends/native/meta-crtc-kms.h"
-#include "backends/native/meta-kms-device.h"
-#include "backends/native/meta-kms-update.h"
-#include "backends/native/meta-kms.h"
 #include "backends/native/meta-renderer-native.h"
 #include "core/boxes-private.h"
 #include "meta/boxes.h"
 #include "meta/meta-backend.h"
 #include "meta/util.h"
 
-#ifdef HAVE_WAYLAND
-#include "wayland/meta-cursor-sprite-wayland.h"
-#include "wayland/meta-wayland-buffer.h"
-#endif
-
-#ifndef DRM_CAP_CURSOR_WIDTH
-#define DRM_CAP_CURSOR_WIDTH 0x8
-#endif
-#ifndef DRM_CAP_CURSOR_HEIGHT
-#define DRM_CAP_CURSOR_HEIGHT 0x9
-#endif
-
-/* When animating a cursor, we usually call drmModeSetCursor2 once per frame.
- * Though, testing shows that we need to triple buffer the cursor buffer in
- * order to avoid glitches when animating the cursor, at least when running on
- * Intel. The reason for this might be (but is not confirmed to be) due to
- * the user space gbm_bo cache, making us reuse and overwrite the kernel side
- * buffer content before it was scanned out. To avoid this, we keep a user space
- * reference to each buffer we set until at least one frame after it was drawn.
- * In effect, this means we three active cursor gbm_bo's: one that that just has
- * been set, one that was previously set and may or may not have been scanned
- * out, and one pending that will be replaced if the cursor sprite changes.
- */
-#define HW_CURSOR_BUFFER_COUNT 3
-
-static GQuark quark_cursor_sprite = 0;
-
 struct _MetaCursorRendererNative
 {
   MetaCursorRenderer parent;
@@ -83,96 +47,17 @@ struct _MetaCursorRendererNative
 struct _MetaCursorRendererNativePrivate
 {
   MetaBackend *backend;
+  MetaKmsCursorRenderer *kms_cursor_renderer;
 
-  gboolean hw_state_invalidated;
-  gboolean has_hw_cursor;
+  gboolean use_kms_cursor;
 
   MetaCursorSprite *last_cursor;
   guint animation_timeout_id;
 };
 typedef struct _MetaCursorRendererNativePrivate MetaCursorRendererNativePrivate;
 
-typedef struct _MetaCursorRendererNativeGpuData
-{
-  gboolean hw_cursor_broken;
-
-  uint64_t cursor_width;
-  uint64_t cursor_height;
-} MetaCursorRendererNativeGpuData;
-
-typedef enum _MetaCursorGbmBoState
-{
-  META_CURSOR_GBM_BO_STATE_NONE,
-  META_CURSOR_GBM_BO_STATE_SET,
-  META_CURSOR_GBM_BO_STATE_INVALIDATED,
-} MetaCursorGbmBoState;
-
-typedef struct _MetaCursorNativeGpuState
-{
-  MetaGpu *gpu;
-  guint active_bo;
-  MetaCursorGbmBoState pending_bo_state;
-  struct gbm_bo *bos[HW_CURSOR_BUFFER_COUNT];
-} MetaCursorNativeGpuState;
-
-typedef struct _MetaCursorNativePrivate
-{
-  GHashTable *gpu_states;
-
-  struct {
-    gboolean can_preprocess;
-    float current_relative_scale;
-    MetaMonitorTransform current_relative_transform;
-  } preprocess_state;
-} MetaCursorNativePrivate;
-
-static GQuark quark_cursor_renderer_native_gpu_data = 0;
-
 G_DEFINE_TYPE_WITH_PRIVATE (MetaCursorRendererNative, meta_cursor_renderer_native, 
META_TYPE_CURSOR_RENDERER);
 
-static void
-realize_cursor_sprite (MetaCursorRenderer *renderer,
-                       MetaCursorSprite   *cursor_sprite,
-                       GList              *gpus);
-
-static MetaCursorNativeGpuState *
-get_cursor_gpu_state (MetaCursorNativePrivate *cursor_priv,
-                      MetaGpuKms              *gpu_kms);
-
-static MetaCursorNativeGpuState *
-ensure_cursor_gpu_state (MetaCursorNativePrivate *cursor_priv,
-                         MetaGpuKms              *gpu_kms);
-
-static void
-invalidate_cursor_gpu_state (MetaCursorSprite *cursor_sprite);
-
-static MetaCursorNativePrivate *
-ensure_cursor_priv (MetaCursorSprite *cursor_sprite);
-
-static MetaCursorNativePrivate *
-get_cursor_priv (MetaCursorSprite *cursor_sprite);
-
-static MetaCursorRendererNativeGpuData *
-meta_cursor_renderer_native_gpu_data_from_gpu (MetaGpuKms *gpu_kms)
-{
-  return g_object_get_qdata (G_OBJECT (gpu_kms),
-                             quark_cursor_renderer_native_gpu_data);
-}
-
-static MetaCursorRendererNativeGpuData *
-meta_create_cursor_renderer_native_gpu_data (MetaGpuKms *gpu_kms)
-{
-  MetaCursorRendererNativeGpuData *cursor_renderer_gpu_data;
-
-  cursor_renderer_gpu_data = g_new0 (MetaCursorRendererNativeGpuData, 1);
-  g_object_set_qdata_full (G_OBJECT (gpu_kms),
-                           quark_cursor_renderer_native_gpu_data,
-                           cursor_renderer_gpu_data,
-                           g_free);
-
-  return cursor_renderer_gpu_data;
-}
-
 static void
 meta_cursor_renderer_native_finalize (GObject *object)
 {
@@ -181,752 +66,34 @@ meta_cursor_renderer_native_finalize (GObject *object)
     meta_cursor_renderer_native_get_instance_private (renderer);
 
   g_clear_handle_id (&priv->animation_timeout_id, g_source_remove);
+  g_clear_object (&priv->kms_cursor_renderer);
 
   G_OBJECT_CLASS (meta_cursor_renderer_native_parent_class)->finalize (object);
 }
 
-static guint
-get_pending_cursor_sprite_gbm_bo_index (MetaCursorNativeGpuState *cursor_gpu_state)
-{
-  return (cursor_gpu_state->active_bo + 1) % HW_CURSOR_BUFFER_COUNT;
-}
-
-static struct gbm_bo *
-get_pending_cursor_sprite_gbm_bo (MetaCursorNativeGpuState *cursor_gpu_state)
-{
-  guint pending_bo;
-
-  pending_bo = get_pending_cursor_sprite_gbm_bo_index (cursor_gpu_state);
-  return cursor_gpu_state->bos[pending_bo];
-}
-
-static struct gbm_bo *
-get_active_cursor_sprite_gbm_bo (MetaCursorNativeGpuState *cursor_gpu_state)
-{
-  return cursor_gpu_state->bos[cursor_gpu_state->active_bo];
-}
-
-static void
-set_pending_cursor_sprite_gbm_bo (MetaCursorSprite *cursor_sprite,
-                                  MetaGpuKms       *gpu_kms,
-                                  struct gbm_bo    *bo)
-{
-  MetaCursorNativePrivate *cursor_priv;
-  MetaCursorNativeGpuState *cursor_gpu_state;
-  guint pending_bo;
-
-  cursor_priv = ensure_cursor_priv (cursor_sprite);
-  cursor_gpu_state = ensure_cursor_gpu_state (cursor_priv, gpu_kms);
-
-  pending_bo = get_pending_cursor_sprite_gbm_bo_index (cursor_gpu_state);
-  cursor_gpu_state->bos[pending_bo] = bo;
-  cursor_gpu_state->pending_bo_state = META_CURSOR_GBM_BO_STATE_SET;
-}
-
-static void
-calculate_crtc_cursor_hotspot (MetaCursorSprite *cursor_sprite,
-                               int              *cursor_hotspot_x,
-                               int              *cursor_hotspot_y)
-{
-  MetaCursorNativePrivate *cursor_priv = get_cursor_priv (cursor_sprite);
-  int hot_x, hot_y;
-  int width, height;
-  float scale;
-  MetaMonitorTransform transform;
-
-  scale = cursor_priv->preprocess_state.current_relative_scale;
-  transform = cursor_priv->preprocess_state.current_relative_transform;
-
-  meta_cursor_sprite_get_hotspot (cursor_sprite, &hot_x, &hot_y);
-  width = meta_cursor_sprite_get_width (cursor_sprite);
-  height = meta_cursor_sprite_get_height (cursor_sprite);
-  meta_monitor_transform_transform_point (transform,
-                                          width, height,
-                                          hot_x, hot_y,
-                                          &hot_x, &hot_y);
-  *cursor_hotspot_x = (int) roundf (hot_x * scale);
-  *cursor_hotspot_y = (int) roundf (hot_y * scale);
-}
-
-static void
-set_crtc_cursor (MetaCursorRendererNative *native,
-                 MetaKmsUpdate            *kms_update,
-                 MetaCrtcKms              *crtc_kms,
-                 int                       x,
-                 int                       y,
-                 MetaCursorSprite         *cursor_sprite)
-{
-  MetaCursorRendererNativePrivate *priv =
-    meta_cursor_renderer_native_get_instance_private (native);
-  MetaCrtc *crtc = META_CRTC (crtc_kms);
-  MetaCursorNativePrivate *cursor_priv = get_cursor_priv (cursor_sprite);
-  MetaGpuKms *gpu_kms = META_GPU_KMS (meta_crtc_get_gpu (crtc));
-  MetaCursorRendererNativeGpuData *cursor_renderer_gpu_data =
-    meta_cursor_renderer_native_gpu_data_from_gpu (gpu_kms);
-  MetaCursorNativeGpuState *cursor_gpu_state =
-    get_cursor_gpu_state (cursor_priv, gpu_kms);
-  MetaKmsCrtc *kms_crtc;
-  MetaKmsDevice *kms_device;
-  MetaKmsPlane *cursor_plane;
-  struct gbm_bo *bo;
-  union gbm_bo_handle handle;
-  int cursor_width, cursor_height;
-  MetaFixed16Rectangle src_rect;
-  MetaFixed16Rectangle dst_rect;
-  struct gbm_bo *crtc_bo;
-  MetaKmsAssignPlaneFlag flags;
-  int cursor_hotspot_x;
-  int cursor_hotspot_y;
-  MetaKmsPlaneAssignment *plane_assignment;
-
-  if (cursor_gpu_state->pending_bo_state == META_CURSOR_GBM_BO_STATE_SET)
-    bo = get_pending_cursor_sprite_gbm_bo (cursor_gpu_state);
-  else
-    bo = get_active_cursor_sprite_gbm_bo (cursor_gpu_state);
-
-  kms_crtc = meta_crtc_kms_get_kms_crtc (crtc_kms);
-  kms_device = meta_kms_crtc_get_device (kms_crtc);
-  cursor_plane = meta_kms_device_get_cursor_plane_for (kms_device, kms_crtc);
-  g_return_if_fail (cursor_plane);
-
-  handle = gbm_bo_get_handle (bo);
-
-  cursor_width = cursor_renderer_gpu_data->cursor_width;
-  cursor_height = cursor_renderer_gpu_data->cursor_height;
-  src_rect = (MetaFixed16Rectangle) {
-    .x = meta_fixed_16_from_int (0),
-    .y = meta_fixed_16_from_int (0),
-    .width = meta_fixed_16_from_int (cursor_width),
-    .height = meta_fixed_16_from_int (cursor_height),
-  };
-  dst_rect = (MetaFixed16Rectangle) {
-    .x = meta_fixed_16_from_int (x),
-    .y = meta_fixed_16_from_int (y),
-    .width = meta_fixed_16_from_int (cursor_width),
-    .height = meta_fixed_16_from_int (cursor_height),
-  };
-
-  flags = META_KMS_ASSIGN_PLANE_FLAG_NONE;
-  crtc_bo = meta_crtc_kms_get_cursor_renderer_private (crtc_kms);
-  if (!priv->hw_state_invalidated && bo == crtc_bo)
-    flags |= META_KMS_ASSIGN_PLANE_FLAG_FB_UNCHANGED;
-
-  plane_assignment = meta_kms_update_assign_plane (kms_update,
-                                                   kms_crtc,
-                                                   cursor_plane,
-                                                   handle.u32,
-                                                   src_rect,
-                                                   dst_rect,
-                                                   flags);
-
-  calculate_crtc_cursor_hotspot (cursor_sprite,
-                                 &cursor_hotspot_x,
-                                 &cursor_hotspot_y);
-  meta_kms_plane_assignment_set_cursor_hotspot (plane_assignment,
-                                                cursor_hotspot_x,
-                                                cursor_hotspot_y);
-
-  meta_crtc_kms_set_cursor_renderer_private (crtc_kms, bo);
-
-  if (cursor_gpu_state->pending_bo_state == META_CURSOR_GBM_BO_STATE_SET)
-    {
-      cursor_gpu_state->active_bo =
-        (cursor_gpu_state->active_bo + 1) % HW_CURSOR_BUFFER_COUNT;
-      cursor_gpu_state->pending_bo_state = META_CURSOR_GBM_BO_STATE_NONE;
-    }
-}
-
-static void
-unset_crtc_cursor (MetaCursorRendererNative *native,
-                   MetaKmsUpdate            *kms_update,
-                   MetaCrtcKms              *crtc_kms)
-{
-  MetaCursorRendererNativePrivate *priv =
-    meta_cursor_renderer_native_get_instance_private (native);
-  MetaKmsCrtc *kms_crtc;
-  MetaKmsDevice *kms_device;
-  MetaKmsPlane *cursor_plane;
-  struct gbm_bo *crtc_bo;
-
-  crtc_bo = meta_crtc_kms_get_cursor_renderer_private (crtc_kms);
-  if (!priv->hw_state_invalidated && !crtc_bo)
-    return;
-
-  kms_crtc = meta_crtc_kms_get_kms_crtc (crtc_kms);
-  kms_device = meta_kms_crtc_get_device (kms_crtc);
-  cursor_plane = meta_kms_device_get_cursor_plane_for (kms_device, kms_crtc);
-
-  if (cursor_plane)
-    meta_kms_update_unassign_plane (kms_update, kms_crtc, cursor_plane);
-
-  meta_crtc_kms_set_cursor_renderer_private (crtc_kms, NULL);
-}
-
-static float
-calculate_cursor_crtc_sprite_scale (MetaCursorSprite   *cursor_sprite,
-                                    MetaLogicalMonitor *logical_monitor)
-{
-  if (meta_is_stage_views_scaled ())
-    {
-      return (meta_logical_monitor_get_scale (logical_monitor) *
-              meta_cursor_sprite_get_texture_scale (cursor_sprite));
-    }
-  else
-    {
-      return 1.0;
-    }
-}
-
-typedef struct
-{
-  MetaCursorRendererNative *in_cursor_renderer_native;
-  MetaLogicalMonitor *in_logical_monitor;
-  graphene_rect_t in_local_cursor_rect;
-  MetaCursorSprite *in_cursor_sprite;
-  MetaKmsUpdate *in_kms_update;
-
-  gboolean out_painted;
-} UpdateCrtcCursorData;
-
 static gboolean
-update_monitor_crtc_cursor (MetaMonitor         *monitor,
-                            MetaMonitorMode     *monitor_mode,
-                            MetaMonitorCrtcMode *monitor_crtc_mode,
-                            gpointer             user_data,
-                            GError             **error)
-{
-  UpdateCrtcCursorData *data = user_data;
-  MetaCursorRendererNative *cursor_renderer_native =
-    data->in_cursor_renderer_native;
-  MetaCursorRendererNativePrivate *priv =
-    meta_cursor_renderer_native_get_instance_private (cursor_renderer_native);
-  MetaCrtc *crtc;
-  MetaMonitorTransform transform;
-  const MetaCrtcModeInfo *crtc_mode_info;
-  graphene_rect_t scaled_crtc_rect;
-  float scale;
-  int crtc_x, crtc_y;
-  int crtc_width, crtc_height;
-
-  if (meta_is_stage_views_scaled ())
-    scale = meta_logical_monitor_get_scale (data->in_logical_monitor);
-  else
-    scale = 1.0;
-
-  transform = meta_logical_monitor_get_transform (data->in_logical_monitor);
-  transform = meta_monitor_logical_to_crtc_transform (monitor, transform);
-
-  meta_monitor_calculate_crtc_pos (monitor, monitor_mode,
-                                   monitor_crtc_mode->output,
-                                   transform,
-                                   &crtc_x, &crtc_y);
-
-  crtc_mode_info = meta_crtc_mode_get_info (monitor_crtc_mode->crtc_mode);
-
-  if (meta_monitor_transform_is_rotated (transform))
-    {
-      crtc_width = crtc_mode_info->height;
-      crtc_height = crtc_mode_info->width;
-    }
-  else
-    {
-      crtc_width = crtc_mode_info->width;
-      crtc_height = crtc_mode_info->height;
-    }
-
-  scaled_crtc_rect = (graphene_rect_t) {
-    .origin = {
-      .x = crtc_x / scale,
-      .y = crtc_y / scale
-    },
-    .size = {
-      .width = crtc_width / scale,
-      .height = crtc_height / scale
-    },
-  };
-
-  crtc = meta_output_get_assigned_crtc (monitor_crtc_mode->output);
-
-  if (priv->has_hw_cursor &&
-      graphene_rect_intersection (&scaled_crtc_rect,
-                                  &data->in_local_cursor_rect,
-                                  NULL))
-    {
-      MetaMonitorTransform inverted_transform;
-      MetaRectangle cursor_rect;
-      CoglTexture *texture;
-      float crtc_cursor_x, crtc_cursor_y;
-      float cursor_crtc_scale;
-      int tex_width, tex_height;
-
-      crtc_cursor_x = (data->in_local_cursor_rect.origin.x -
-                       scaled_crtc_rect.origin.x) * scale;
-      crtc_cursor_y = (data->in_local_cursor_rect.origin.y -
-                       scaled_crtc_rect.origin.y) * scale;
-
-      texture = meta_cursor_sprite_get_cogl_texture (data->in_cursor_sprite);
-      tex_width = cogl_texture_get_width (texture);
-      tex_height = cogl_texture_get_height (texture);
-
-      cursor_crtc_scale =
-        calculate_cursor_crtc_sprite_scale (data->in_cursor_sprite,
-                                            data->in_logical_monitor);
-
-      cursor_rect = (MetaRectangle) {
-        .x = floorf (crtc_cursor_x),
-        .y = floorf (crtc_cursor_y),
-        .width = roundf (tex_width * cursor_crtc_scale),
-        .height = roundf (tex_height * cursor_crtc_scale)
-      };
-
-      inverted_transform = meta_monitor_transform_invert (transform);
-      meta_rectangle_transform (&cursor_rect,
-                                inverted_transform,
-                                crtc_mode_info->width,
-                                crtc_mode_info->height,
-                                &cursor_rect);
-
-      set_crtc_cursor (data->in_cursor_renderer_native,
-                       data->in_kms_update,
-                       META_CRTC_KMS (crtc),
-                       cursor_rect.x,
-                       cursor_rect.y,
-                       data->in_cursor_sprite);
-
-      data->out_painted = data->out_painted || TRUE;
-    }
-  else
-    {
-      unset_crtc_cursor (data->in_cursor_renderer_native,
-                         data->in_kms_update,
-                         META_CRTC_KMS (crtc));
-    }
-
-  return TRUE;
-}
-
-static void
-disable_hw_cursor_for_crtc (MetaKmsCrtc  *kms_crtc,
-                            const GError *error)
-{
-  MetaCrtcKms *crtc_kms = meta_crtc_kms_from_kms_crtc (kms_crtc);
-  MetaCrtc *crtc = META_CRTC (crtc_kms);
-  MetaGpuKms *gpu_kms = META_GPU_KMS (meta_crtc_get_gpu (crtc));
-  MetaCursorRendererNativeGpuData *cursor_renderer_gpu_data =
-    meta_cursor_renderer_native_gpu_data_from_gpu (gpu_kms);
-
-  g_warning ("Failed to set hardware cursor (%s), "
-             "using OpenGL from now on",
-             error->message);
-  cursor_renderer_gpu_data->hw_cursor_broken = TRUE;
-}
-
-static void
-update_hw_cursor (MetaCursorRendererNative *native,
-                  MetaCursorSprite         *cursor_sprite)
-{
-  MetaCursorRendererNativePrivate *priv =
-    meta_cursor_renderer_native_get_instance_private (native);
-  MetaCursorRenderer *renderer = META_CURSOR_RENDERER (native);
-  MetaBackend *backend = priv->backend;
-  MetaBackendNative *backend_native = META_BACKEND_NATIVE (priv->backend);
-  MetaKms *kms = meta_backend_native_get_kms (backend_native);
-  MetaMonitorManager *monitor_manager =
-    meta_backend_get_monitor_manager (backend);
-  MetaKmsUpdate *kms_update;
-  GList *logical_monitors;
-  GList *l;
-  graphene_rect_t rect;
-  gboolean painted = FALSE;
-  g_autoptr (MetaKmsFeedback) feedback = NULL;
-
-  kms_update = meta_kms_ensure_pending_update (kms);
-
-  if (cursor_sprite)
-    rect = meta_cursor_renderer_calculate_rect (renderer, cursor_sprite);
-  else
-    rect = GRAPHENE_RECT_INIT_ZERO;
-
-  logical_monitors =
-    meta_monitor_manager_get_logical_monitors (monitor_manager);
-  for (l = logical_monitors; l; l = l->next)
-    {
-      MetaLogicalMonitor *logical_monitor = l->data;
-      UpdateCrtcCursorData data;
-      GList *monitors;
-      GList *k;
-
-      data = (UpdateCrtcCursorData) {
-        .in_cursor_renderer_native = native,
-        .in_logical_monitor = logical_monitor,
-        .in_local_cursor_rect = (graphene_rect_t) {
-          .origin = {
-            .x = rect.origin.x - logical_monitor->rect.x,
-            .y = rect.origin.y - logical_monitor->rect.y
-          },
-          .size = rect.size
-        },
-        .in_cursor_sprite = cursor_sprite,
-        .in_kms_update = kms_update,
-      };
-
-      monitors = meta_logical_monitor_get_monitors (logical_monitor);
-      for (k = monitors; k; k = k->next)
-        {
-          MetaMonitor *monitor = k->data;
-          MetaMonitorMode *monitor_mode;
-
-          monitor_mode = meta_monitor_get_current_mode (monitor);
-          meta_monitor_mode_foreach_crtc (monitor, monitor_mode,
-                                          update_monitor_crtc_cursor,
-                                          &data,
-                                          NULL);
-        }
-
-      painted = painted || data.out_painted;
-    }
-
-  feedback = meta_kms_post_pending_update_sync (kms);
-  if (meta_kms_feedback_get_result (feedback) != META_KMS_FEEDBACK_PASSED)
-    {
-      for (l = meta_kms_feedback_get_failed_planes (feedback); l; l = l->next)
-        {
-          MetaKmsPlaneFeedback *plane_feedback = l->data;
-
-          if (!g_error_matches (plane_feedback->error,
-                                G_IO_ERROR,
-                                G_IO_ERROR_PERMISSION_DENIED))
-            {
-              disable_hw_cursor_for_crtc (plane_feedback->crtc,
-                                          plane_feedback->error);
-            }
-        }
-
-      priv->has_hw_cursor = FALSE;
-    }
-
-  priv->hw_state_invalidated = FALSE;
-
-  if (painted)
-    meta_cursor_renderer_emit_painted (renderer, cursor_sprite);
-}
-
-static gboolean
-has_valid_cursor_sprite_gbm_bo (MetaCursorSprite *cursor_sprite,
-                                MetaGpuKms       *gpu_kms)
-{
-  MetaCursorNativePrivate *cursor_priv;
-  MetaCursorNativeGpuState *cursor_gpu_state;
-
-  cursor_priv = get_cursor_priv (cursor_sprite);
-  if (!cursor_priv)
-    return FALSE;
-
-  cursor_gpu_state = get_cursor_gpu_state (cursor_priv, gpu_kms);
-  if (!cursor_gpu_state)
-    return FALSE;
-
-  switch (cursor_gpu_state->pending_bo_state)
-    {
-    case META_CURSOR_GBM_BO_STATE_NONE:
-      return get_active_cursor_sprite_gbm_bo (cursor_gpu_state) != NULL;
-    case META_CURSOR_GBM_BO_STATE_SET:
-      return TRUE;
-    case META_CURSOR_GBM_BO_STATE_INVALIDATED:
-      return FALSE;
-    }
-
-  g_assert_not_reached ();
-
-  return FALSE;
-}
-
-static void
-set_can_preprocess (MetaCursorSprite     *cursor_sprite,
-                    float                 scale,
-                    MetaMonitorTransform  transform)
-{
-  MetaCursorNativePrivate *cursor_priv = get_cursor_priv (cursor_sprite);
-
-  cursor_priv->preprocess_state.current_relative_scale = scale;
-  cursor_priv->preprocess_state.current_relative_transform = transform;
-  cursor_priv->preprocess_state.can_preprocess = TRUE;
-
-  invalidate_cursor_gpu_state (cursor_sprite);
-}
-
-static void
-unset_can_preprocess (MetaCursorSprite *cursor_sprite)
-{
-  MetaCursorNativePrivate *cursor_priv = get_cursor_priv (cursor_sprite);
-
-  memset (&cursor_priv->preprocess_state,
-          0,
-          sizeof (cursor_priv->preprocess_state));
-  cursor_priv->preprocess_state.can_preprocess = FALSE;
-
-  invalidate_cursor_gpu_state (cursor_sprite);
-}
-
-static gboolean
-get_can_preprocess (MetaCursorSprite *cursor_sprite)
-{
-  MetaCursorNativePrivate *cursor_priv = get_cursor_priv (cursor_sprite);
-
-  return cursor_priv->preprocess_state.can_preprocess;
-}
-
-static float
-get_current_relative_scale (MetaCursorSprite *cursor_sprite)
-{
-  MetaCursorNativePrivate *cursor_priv = get_cursor_priv (cursor_sprite);
-
-  return cursor_priv->preprocess_state.current_relative_scale;
-}
-
-static MetaMonitorTransform
-get_current_relative_transform (MetaCursorSprite *cursor_sprite)
-{
-  MetaCursorNativePrivate *cursor_priv = get_cursor_priv (cursor_sprite);
-
-  return cursor_priv->preprocess_state.current_relative_transform;
-}
-
-static void
-has_cursor_plane (MetaLogicalMonitor *logical_monitor,
-                  MetaMonitor        *monitor,
-                  MetaOutput         *output,
-                  MetaCrtc           *crtc,
-                  gpointer            user_data)
-{
-  gboolean *has_cursor_planes = user_data;
-  MetaCrtcKms *crtc_kms = META_CRTC_KMS (crtc);
-  MetaKmsCrtc *kms_crtc = meta_crtc_kms_get_kms_crtc (crtc_kms);
-  MetaKmsDevice *kms_device = meta_kms_crtc_get_device (kms_crtc);
-
-  *has_cursor_planes &= !!meta_kms_device_get_cursor_plane_for (kms_device,
-                                                                kms_crtc);
-}
-
-static gboolean
-crtcs_has_cursor_planes (MetaCursorRenderer *renderer,
-                         MetaCursorSprite   *cursor_sprite)
-{
-  MetaCursorRendererNative *cursor_renderer_native =
-    META_CURSOR_RENDERER_NATIVE (renderer);
-  MetaCursorRendererNativePrivate *priv =
-    meta_cursor_renderer_native_get_instance_private (cursor_renderer_native);
-  MetaBackend *backend = priv->backend;
-  MetaMonitorManager *monitor_manager =
-    meta_backend_get_monitor_manager (backend);
-  GList *logical_monitors;
-  GList *l;
-  graphene_rect_t cursor_rect;
-
-  cursor_rect = meta_cursor_renderer_calculate_rect (renderer, cursor_sprite);
-
-  logical_monitors =
-    meta_monitor_manager_get_logical_monitors (monitor_manager);
-  for (l = logical_monitors; l; l = l->next)
-    {
-      MetaLogicalMonitor *logical_monitor = l->data;
-      MetaRectangle logical_monitor_layout;
-      graphene_rect_t logical_monitor_rect;
-      gboolean has_cursor_planes;
-
-      logical_monitor_layout =
-        meta_logical_monitor_get_layout (logical_monitor);
-      logical_monitor_rect =
-        meta_rectangle_to_graphene_rect (&logical_monitor_layout);
-
-      if (!graphene_rect_intersection (&cursor_rect, &logical_monitor_rect,
-                                       NULL))
-        continue;
-
-      has_cursor_planes = TRUE;
-      meta_logical_monitor_foreach_crtc (logical_monitor,
-                                         has_cursor_plane,
-                                         &has_cursor_planes);
-      if (!has_cursor_planes)
-        return FALSE;
-    }
-
-  return TRUE;
-}
-
-static gboolean
-get_common_crtc_sprite_scale_for_logical_monitors (MetaCursorRenderer *renderer,
-                                                   MetaCursorSprite   *cursor_sprite,
-                                                   float              *out_scale)
-{
-  MetaCursorRendererNative *cursor_renderer_native =
-    META_CURSOR_RENDERER_NATIVE (renderer);
-  MetaCursorRendererNativePrivate *priv =
-    meta_cursor_renderer_native_get_instance_private (cursor_renderer_native);
-  MetaBackend *backend = priv->backend;
-  MetaMonitorManager *monitor_manager =
-    meta_backend_get_monitor_manager (backend);
-  graphene_rect_t cursor_rect;
-  float scale = 1.0;
-  gboolean has_visible_crtc_sprite = FALSE;
-  GList *logical_monitors;
-  GList *l;
-
-  cursor_rect = meta_cursor_renderer_calculate_rect (renderer, cursor_sprite);
-
-  logical_monitors =
-    meta_monitor_manager_get_logical_monitors (monitor_manager);
-
-  for (l = logical_monitors; l; l = l->next)
-    {
-      MetaLogicalMonitor *logical_monitor = l->data;
-      graphene_rect_t logical_monitor_rect =
-        meta_rectangle_to_graphene_rect (&logical_monitor->rect);
-      float tmp_scale;
-
-      if (!graphene_rect_intersection (&cursor_rect,
-                                       &logical_monitor_rect,
-                                       NULL))
-        continue;
-
-      tmp_scale =
-        calculate_cursor_crtc_sprite_scale (cursor_sprite, logical_monitor);
-
-      if (has_visible_crtc_sprite && scale != tmp_scale)
-        return FALSE;
-
-      has_visible_crtc_sprite = TRUE;
-      scale = tmp_scale;
-    }
-
-  if (!has_visible_crtc_sprite)
-    return FALSE;
-
-  *out_scale = scale;
-  return TRUE;
-}
-
-static gboolean
-get_common_crtc_sprite_transform_for_logical_monitors (MetaCursorRenderer   *renderer,
-                                                       MetaCursorSprite     *cursor_sprite,
-                                                       MetaMonitorTransform *out_transform)
-{
-  MetaCursorRendererNative *cursor_renderer_native =
-    META_CURSOR_RENDERER_NATIVE (renderer);
-  MetaCursorRendererNativePrivate *priv =
-    meta_cursor_renderer_native_get_instance_private (cursor_renderer_native);
-  MetaBackend *backend = priv->backend;
-  MetaMonitorManager *monitor_manager =
-    meta_backend_get_monitor_manager (backend);
-  graphene_rect_t cursor_rect;
-  MetaMonitorTransform transform = META_MONITOR_TRANSFORM_NORMAL;
-  gboolean has_visible_crtc_sprite = FALSE;
-  GList *logical_monitors;
-  GList *l;
-
-  cursor_rect = meta_cursor_renderer_calculate_rect (renderer, cursor_sprite);
-
-  logical_monitors =
-    meta_monitor_manager_get_logical_monitors (monitor_manager);
-
-  for (l = logical_monitors; l; l = l->next)
-    {
-      MetaLogicalMonitor *logical_monitor = l->data;
-      graphene_rect_t logical_monitor_rect =
-        meta_rectangle_to_graphene_rect (&logical_monitor->rect);
-      MetaMonitorTransform logical_transform, tmp_transform;
-      GList *monitors, *l_mon;
-
-      if (!graphene_rect_intersection (&cursor_rect,
-                                       &logical_monitor_rect,
-                                       NULL))
-        continue;
-
-      logical_transform = meta_logical_monitor_get_transform (logical_monitor);
-      monitors = meta_logical_monitor_get_monitors (logical_monitor);
-      for (l_mon = monitors; l_mon; l_mon = l_mon->next)
-        {
-          MetaMonitor *monitor = l_mon->data;
-
-          tmp_transform = meta_monitor_transform_relative_transform (
-            meta_cursor_sprite_get_texture_transform (cursor_sprite),
-            meta_monitor_logical_to_crtc_transform (monitor, logical_transform));
-
-          if (has_visible_crtc_sprite && transform != tmp_transform)
-            return FALSE;
-
-          has_visible_crtc_sprite = TRUE;
-          transform = tmp_transform;
-        }
-    }
-
-  if (!has_visible_crtc_sprite)
-    return FALSE;
-
-  *out_transform = transform;
-  return TRUE;
-}
-
-static gboolean
-should_have_hw_cursor (MetaCursorRenderer *renderer,
-                       MetaCursorSprite   *cursor_sprite,
-                       GList              *gpus)
+should_have_kms_cursor (MetaCursorRenderer *renderer,
+                        MetaCursorSprite   *cursor_sprite)
 {
   MetaCursorRendererNative *cursor_renderer_native =
     META_CURSOR_RENDERER_NATIVE (renderer);
   MetaCursorRendererNativePrivate *priv =
     meta_cursor_renderer_native_get_instance_private (cursor_renderer_native);
   CoglTexture *texture;
-  MetaMonitorTransform transform;
-  float scale;
-  GList *l;
 
   if (!cursor_sprite)
     return FALSE;
 
-  if (meta_backend_is_hw_cursors_inhibited (priv->backend))
+  if (!priv->kms_cursor_renderer)
     return FALSE;
 
-  for (l = gpus; l; l = l->next)
-    {
-      MetaGpuKms *gpu_kms = l->data;
-      MetaCursorRendererNativeGpuData *cursor_renderer_gpu_data;
-
-      cursor_renderer_gpu_data =
-        meta_cursor_renderer_native_gpu_data_from_gpu (gpu_kms);
-      if (!cursor_renderer_gpu_data)
-        return FALSE;
-
-      if (cursor_renderer_gpu_data->hw_cursor_broken)
-        return FALSE;
-
-      if (!has_valid_cursor_sprite_gbm_bo (cursor_sprite, gpu_kms))
-        return FALSE;
-    }
-
-  if (!crtcs_has_cursor_planes (renderer, cursor_sprite))
+  if (meta_backend_is_hw_cursors_inhibited (priv->backend))
     return FALSE;
 
   texture = meta_cursor_sprite_get_cogl_texture (cursor_sprite);
   if (!texture)
     return FALSE;
 
-  if (!get_common_crtc_sprite_scale_for_logical_monitors (renderer,
-                                                          cursor_sprite,
-                                                          &scale))
-    return FALSE;
-
-  if (!get_common_crtc_sprite_transform_for_logical_monitors (renderer,
-                                                              cursor_sprite,
-                                                              &transform))
-    return FALSE;
-
-  if (G_APPROX_VALUE (scale, 1.f, FLT_EPSILON) &&
-      transform == META_MONITOR_TRANSFORM_NORMAL)
-    return TRUE;
-  else
-    return get_can_preprocess (cursor_sprite);
-
   return TRUE;
 }
 
@@ -978,56 +145,6 @@ maybe_schedule_cursor_sprite_animation_frame (MetaCursorRendererNative *native,
     }
 }
 
-static GList *
-calculate_cursor_sprite_gpus (MetaCursorRenderer *renderer,
-                              MetaCursorSprite   *cursor_sprite)
-{
-  MetaCursorRendererNative *native = META_CURSOR_RENDERER_NATIVE (renderer);
-  MetaCursorRendererNativePrivate *priv =
-    meta_cursor_renderer_native_get_instance_private (native);
-  MetaBackend *backend = priv->backend;
-  MetaMonitorManager *monitor_manager =
-    meta_backend_get_monitor_manager (backend);
-  GList *gpus = NULL;
-  GList *logical_monitors;
-  GList *l;
-  graphene_rect_t cursor_rect;
-
-  cursor_rect = meta_cursor_renderer_calculate_rect (renderer, cursor_sprite);
-
-  logical_monitors =
-    meta_monitor_manager_get_logical_monitors (monitor_manager);
-  for (l = logical_monitors; l; l = l->next)
-    {
-      MetaLogicalMonitor *logical_monitor = l->data;
-      MetaRectangle logical_monitor_layout;
-      graphene_rect_t logical_monitor_rect;
-      GList *monitors, *l_mon;
-
-      logical_monitor_layout =
-        meta_logical_monitor_get_layout (logical_monitor);
-      logical_monitor_rect =
-        meta_rectangle_to_graphene_rect (&logical_monitor_layout);
-
-      if (!graphene_rect_intersection (&cursor_rect, &logical_monitor_rect,
-                                       NULL))
-        continue;
-
-      monitors = meta_logical_monitor_get_monitors (logical_monitor);
-      for (l_mon = monitors; l_mon; l_mon = l_mon->next)
-        {
-          MetaMonitor *monitor = l_mon->data;
-          MetaGpu *gpu;
-
-          gpu = meta_monitor_get_gpu (monitor);
-          if (!g_list_find (gpus, gpu))
-            gpus = g_list_prepend (gpus, gpu);
-        }
-    }
-
-  return gpus;
-}
-
 static gboolean
 meta_cursor_renderer_native_update_cursor (MetaCursorRenderer *renderer,
                                            MetaCursorSprite   *cursor_sprite)
@@ -1035,614 +152,32 @@ meta_cursor_renderer_native_update_cursor (MetaCursorRenderer *renderer,
   MetaCursorRendererNative *native = META_CURSOR_RENDERER_NATIVE (renderer);
   MetaCursorRendererNativePrivate *priv =
     meta_cursor_renderer_native_get_instance_private (native);
-  g_autoptr (GList) gpus = NULL;
+  gboolean kms_cursor_drawn = FALSE;
 
   if (cursor_sprite)
-    {
-      meta_cursor_sprite_realize_texture (cursor_sprite);
-      gpus = calculate_cursor_sprite_gpus (renderer, cursor_sprite);
-      realize_cursor_sprite (renderer, cursor_sprite, gpus);
-    }
+    meta_cursor_sprite_realize_texture (cursor_sprite);
 
   maybe_schedule_cursor_sprite_animation_frame (native, cursor_sprite);
 
-  priv->has_hw_cursor = should_have_hw_cursor (renderer, cursor_sprite, gpus);
-  update_hw_cursor (native, cursor_sprite);
-
-  return (priv->has_hw_cursor ||
-          !cursor_sprite ||
-          !meta_cursor_sprite_get_cogl_texture (cursor_sprite));
-}
-
-static void
-unset_crtc_cursor_renderer_privates (MetaGpu       *gpu,
-                                     struct gbm_bo *bo)
-{
-  GList *l;
-
-  for (l = meta_gpu_get_crtcs (gpu); l; l = l->next)
-    {
-      MetaCrtcKms *crtc_kms = META_CRTC_KMS (l->data);
-      struct gbm_bo *crtc_bo;
-
-      crtc_bo = meta_crtc_kms_get_cursor_renderer_private (crtc_kms);
-      if (bo == crtc_bo)
-        meta_crtc_kms_set_cursor_renderer_private (crtc_kms, NULL);
-    }
-}
-
-static void
-cursor_gpu_state_free (MetaCursorNativeGpuState *cursor_gpu_state)
-{
-  int i;
-  struct gbm_bo *active_bo;
-
-  active_bo = get_active_cursor_sprite_gbm_bo (cursor_gpu_state);
-  if (active_bo)
-    unset_crtc_cursor_renderer_privates (cursor_gpu_state->gpu, active_bo);
-
-  for (i = 0; i < HW_CURSOR_BUFFER_COUNT; i++)
-    g_clear_pointer (&cursor_gpu_state->bos[i], gbm_bo_destroy);
-  g_free (cursor_gpu_state);
-}
-
-static MetaCursorNativeGpuState *
-get_cursor_gpu_state (MetaCursorNativePrivate *cursor_priv,
-                      MetaGpuKms              *gpu_kms)
-{
-  return g_hash_table_lookup (cursor_priv->gpu_states, gpu_kms);
-}
-
-static MetaCursorNativeGpuState *
-ensure_cursor_gpu_state (MetaCursorNativePrivate *cursor_priv,
-                         MetaGpuKms              *gpu_kms)
-{
-  MetaCursorNativeGpuState *cursor_gpu_state;
-
-  cursor_gpu_state = get_cursor_gpu_state (cursor_priv, gpu_kms);
-  if (cursor_gpu_state)
-    return cursor_gpu_state;
-
-  cursor_gpu_state = g_new0 (MetaCursorNativeGpuState, 1);
-  cursor_gpu_state->gpu = META_GPU (gpu_kms);
-  g_hash_table_insert (cursor_priv->gpu_states, gpu_kms, cursor_gpu_state);
-
-  return cursor_gpu_state;
-}
-
-static void
-invalidate_cursor_gpu_state (MetaCursorSprite *cursor_sprite)
-{
-  MetaCursorNativePrivate *cursor_priv = get_cursor_priv (cursor_sprite);
-  GHashTableIter iter;
-  MetaCursorNativeGpuState *cursor_gpu_state;
-
-  g_hash_table_iter_init (&iter, cursor_priv->gpu_states);
-  while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &cursor_gpu_state))
-    {
-      guint pending_bo;
-      pending_bo = get_pending_cursor_sprite_gbm_bo_index (cursor_gpu_state);
-      g_clear_pointer (&cursor_gpu_state->bos[pending_bo],
-                       gbm_bo_destroy);
-      cursor_gpu_state->pending_bo_state = META_CURSOR_GBM_BO_STATE_INVALIDATED;
-    }
-}
-
-static void
-on_cursor_sprite_texture_changed (MetaCursorSprite *cursor_sprite)
-{
-  invalidate_cursor_gpu_state (cursor_sprite);
-}
-
-static void
-cursor_priv_free (MetaCursorNativePrivate *cursor_priv)
-{
-  g_hash_table_destroy (cursor_priv->gpu_states);
-  g_free (cursor_priv);
-}
-
-static MetaCursorNativePrivate *
-get_cursor_priv (MetaCursorSprite *cursor_sprite)
-{
-  return g_object_get_qdata (G_OBJECT (cursor_sprite), quark_cursor_sprite);
-}
-
-static MetaCursorNativePrivate *
-ensure_cursor_priv (MetaCursorSprite *cursor_sprite)
-{
-  MetaCursorNativePrivate *cursor_priv;
-
-  cursor_priv = get_cursor_priv (cursor_sprite);
-  if (cursor_priv)
-    return cursor_priv;
-
-  cursor_priv = g_new0 (MetaCursorNativePrivate, 1);
-  cursor_priv->gpu_states =
-    g_hash_table_new_full (g_direct_hash,
-                           g_direct_equal,
-                           NULL,
-                           (GDestroyNotify) cursor_gpu_state_free);
-  g_object_set_qdata_full (G_OBJECT (cursor_sprite),
-                           quark_cursor_sprite,
-                           cursor_priv,
-                           (GDestroyNotify) cursor_priv_free);
-
-  g_signal_connect (cursor_sprite, "texture-changed",
-                    G_CALLBACK (on_cursor_sprite_texture_changed), NULL);
-
-  unset_can_preprocess (cursor_sprite);
-
-  return cursor_priv;
-}
-
-static void
-load_cursor_sprite_gbm_buffer_for_gpu (MetaCursorRendererNative *native,
-                                       MetaGpuKms               *gpu_kms,
-                                       MetaCursorSprite         *cursor_sprite,
-                                       uint8_t                  *pixels,
-                                       uint                      width,
-                                       uint                      height,
-                                       int                       rowstride,
-                                       uint32_t                  gbm_format)
-{
-  uint64_t cursor_width, cursor_height;
-  MetaCursorRendererNativeGpuData *cursor_renderer_gpu_data;
-  struct gbm_device *gbm_device;
-
-  cursor_renderer_gpu_data =
-    meta_cursor_renderer_native_gpu_data_from_gpu (gpu_kms);
-  if (!cursor_renderer_gpu_data)
-    return;
-
-  cursor_width = (uint64_t) cursor_renderer_gpu_data->cursor_width;
-  cursor_height = (uint64_t) cursor_renderer_gpu_data->cursor_height;
-
-  if (width > cursor_width || height > cursor_height)
-    {
-      meta_warning ("Invalid theme cursor size (must be at most %ux%u)",
-                    (unsigned int)cursor_width, (unsigned int)cursor_height);
-      return;
-    }
-
-  gbm_device = meta_gbm_device_from_gpu (gpu_kms);
-  if (gbm_device_is_format_supported (gbm_device, gbm_format,
-                                      GBM_BO_USE_CURSOR | GBM_BO_USE_WRITE))
-    {
-      struct gbm_bo *bo;
-      uint8_t buf[4 * cursor_width * cursor_height];
-      uint i;
-
-      bo = gbm_bo_create (gbm_device, cursor_width, cursor_height,
-                          gbm_format, GBM_BO_USE_CURSOR | GBM_BO_USE_WRITE);
-      if (!bo)
-        {
-          meta_warning ("Failed to allocate HW cursor buffer");
-          return;
-        }
-
-      memset (buf, 0, sizeof(buf));
-      for (i = 0; i < height; i++)
-        memcpy (buf + i * 4 * cursor_width, pixels + i * rowstride, width * 4);
-      if (gbm_bo_write (bo, buf, cursor_width * cursor_height * 4) != 0)
-        {
-          meta_warning ("Failed to write cursors buffer data: %s",
-                        g_strerror (errno));
-          gbm_bo_destroy (bo);
-          return;
-        }
-
-      set_pending_cursor_sprite_gbm_bo (cursor_sprite, gpu_kms, bo);
-    }
-  else
-    {
-      meta_warning ("HW cursor for format %d not supported", gbm_format);
-    }
-}
-
-static gboolean
-is_cursor_hw_state_valid (MetaCursorSprite *cursor_sprite,
-                          MetaGpuKms       *gpu_kms)
-{
-  MetaCursorNativePrivate *cursor_priv;
-  MetaCursorNativeGpuState *cursor_gpu_state;
-
-  cursor_priv = get_cursor_priv (cursor_sprite);
-  if (!cursor_priv)
-    return FALSE;
-
-  cursor_gpu_state = get_cursor_gpu_state (cursor_priv, gpu_kms);
-  if (!cursor_gpu_state)
-    return FALSE;
-
-  switch (cursor_gpu_state->pending_bo_state)
-    {
-    case META_CURSOR_GBM_BO_STATE_SET:
-    case META_CURSOR_GBM_BO_STATE_NONE:
-      return TRUE;
-    case META_CURSOR_GBM_BO_STATE_INVALIDATED:
-      return FALSE;
-    }
-
-  g_assert_not_reached ();
-  return FALSE;
-}
-
-static gboolean
-is_cursor_scale_and_transform_valid (MetaCursorRenderer *renderer,
-                                     MetaCursorSprite   *cursor_sprite)
-{
-  MetaMonitorTransform transform;
-  float scale;
-
-  if (!get_common_crtc_sprite_scale_for_logical_monitors (renderer,
-                                                          cursor_sprite,
-                                                          &scale))
-    return FALSE;
-
-  if (!get_common_crtc_sprite_transform_for_logical_monitors (renderer,
-                                                              cursor_sprite,
-                                                              &transform))
-    return FALSE;
-
-  return (scale == get_current_relative_scale (cursor_sprite) &&
-          transform == get_current_relative_transform (cursor_sprite));
-}
-
-static cairo_surface_t *
-scale_and_transform_cursor_sprite_cpu (uint8_t              *pixels,
-                                       int                   width,
-                                       int                   height,
-                                       int                   rowstride,
-                                       float                 scale,
-                                       MetaMonitorTransform  transform)
-{
-  cairo_t *cr;
-  cairo_surface_t *source_surface;
-  cairo_surface_t *target_surface;
-  int image_width;
-  int image_height;
-
-  image_width = ceilf (width * scale);
-  image_height = ceilf (height * scale);
-
-  target_surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
-                                               image_width,
-                                               image_height);
-
-  cr = cairo_create (target_surface);
-  if (transform != META_MONITOR_TRANSFORM_NORMAL)
-    {
-      cairo_translate (cr, 0.5 * image_width, 0.5 * image_height);
-      switch (transform)
-        {
-        case META_MONITOR_TRANSFORM_90:
-          cairo_rotate (cr, M_PI * 1.5);
-          break;
-        case META_MONITOR_TRANSFORM_180:
-          cairo_rotate (cr, M_PI);
-          break;
-        case META_MONITOR_TRANSFORM_270:
-          cairo_rotate (cr, M_PI * 0.5);
-          break;
-        case META_MONITOR_TRANSFORM_FLIPPED:
-          cairo_scale (cr, 1, -1);
-          break;
-        case META_MONITOR_TRANSFORM_FLIPPED_90:
-          cairo_rotate (cr, M_PI * 1.5);
-          cairo_scale (cr, -1, 1);
-          break;
-        case META_MONITOR_TRANSFORM_FLIPPED_180:
-          cairo_rotate (cr, M_PI);
-          cairo_scale (cr, 1, -1);
-          break;
-        case META_MONITOR_TRANSFORM_FLIPPED_270:
-          cairo_rotate (cr, M_PI * 0.5);
-          cairo_scale (cr, -1, 1);
-          break;
-        case META_MONITOR_TRANSFORM_NORMAL:
-          g_assert_not_reached ();
-        }
-      cairo_translate (cr, -0.5 * image_width, -0.5 * image_height);
-    }
-  cairo_scale (cr, scale, scale);
-
-  source_surface = cairo_image_surface_create_for_data (pixels,
-                                                        CAIRO_FORMAT_ARGB32,
-                                                        width,
-                                                        height,
-                                                        rowstride);
-
-  cairo_set_source_surface (cr, source_surface, 0, 0);
-  cairo_paint (cr);
-  cairo_destroy (cr);
-  cairo_surface_destroy (source_surface);
+  priv->use_kms_cursor = should_have_kms_cursor (renderer, cursor_sprite);
 
-  return target_surface;
-}
-
-static void
-load_scaled_and_transformed_cursor_sprite (MetaCursorRendererNative *native,
-                                           MetaGpuKms               *gpu_kms,
-                                           MetaCursorSprite         *cursor_sprite,
-                                           float                     relative_scale,
-                                           MetaMonitorTransform      relative_transform,
-                                           uint8_t                  *data,
-                                           int                       width,
-                                           int                       height,
-                                           int                       rowstride,
-                                           uint32_t                  gbm_format)
-{
-  if (!G_APPROX_VALUE (relative_scale, 1.f, FLT_EPSILON) ||
-      relative_transform != META_MONITOR_TRANSFORM_NORMAL)
-    {
-      cairo_surface_t *surface;
-
-      surface = scale_and_transform_cursor_sprite_cpu (data,
-                                                       width,
-                                                       height,
-                                                       rowstride,
-                                                       relative_scale,
-                                                       relative_transform);
-
-      load_cursor_sprite_gbm_buffer_for_gpu (native,
-                                             gpu_kms,
-                                             cursor_sprite,
-                                             cairo_image_surface_get_data (surface),
-                                             cairo_image_surface_get_width (surface),
-                                             cairo_image_surface_get_width (surface),
-                                             cairo_image_surface_get_stride (surface),
-                                             gbm_format);
-
-      cairo_surface_destroy (surface);
-    }
-  else
-    {
-      load_cursor_sprite_gbm_buffer_for_gpu (native,
-                                             gpu_kms,
-                                             cursor_sprite,
-                                             data,
-                                             width,
-                                             height,
-                                             rowstride,
-                                             gbm_format);
-    }
-}
-
-#ifdef HAVE_WAYLAND
-static void
-realize_cursor_sprite_from_wl_buffer_for_gpu (MetaCursorRenderer      *renderer,
-                                              MetaGpuKms              *gpu_kms,
-                                              MetaCursorSpriteWayland *sprite_wayland)
-{
-  MetaCursorRendererNative *native = META_CURSOR_RENDERER_NATIVE (renderer);
-  MetaCursorSprite *cursor_sprite = META_CURSOR_SPRITE (sprite_wayland);
-  MetaCursorRendererNativeGpuData *cursor_renderer_gpu_data;
-  uint64_t cursor_width, cursor_height;
-  CoglTexture *texture;
-  uint width, height;
-  MetaWaylandBuffer *buffer;
-  struct wl_resource *buffer_resource;
-  struct wl_shm_buffer *shm_buffer;
-
-  cursor_renderer_gpu_data =
-    meta_cursor_renderer_native_gpu_data_from_gpu (gpu_kms);
-  if (!cursor_renderer_gpu_data || cursor_renderer_gpu_data->hw_cursor_broken)
-    return;
-
-  if (is_cursor_hw_state_valid (cursor_sprite, gpu_kms) &&
-      is_cursor_scale_and_transform_valid (renderer, cursor_sprite))
-    return;
-
-  buffer = meta_cursor_sprite_wayland_get_buffer (sprite_wayland);
-  if (!buffer)
-    return;
-
-  buffer_resource = meta_wayland_buffer_get_resource (buffer);
-  if (!buffer_resource)
-    return;
-
-  ensure_cursor_priv (cursor_sprite);
-
-  shm_buffer = wl_shm_buffer_get (buffer_resource);
-  if (shm_buffer)
-    {
-      int rowstride = wl_shm_buffer_get_stride (shm_buffer);
-      uint8_t *buffer_data;
-      float relative_scale;
-      MetaMonitorTransform relative_transform;
-      uint32_t gbm_format;
-
-      if (!get_common_crtc_sprite_scale_for_logical_monitors (renderer,
-                                                              cursor_sprite,
-                                                              &relative_scale))
-        {
-          unset_can_preprocess (cursor_sprite);
-          return;
-        }
-
-      if (!get_common_crtc_sprite_transform_for_logical_monitors (renderer,
-                                                                  cursor_sprite,
-                                                                  &relative_transform))
-        {
-          unset_can_preprocess (cursor_sprite);
-          return;
-        }
-
-      set_can_preprocess (cursor_sprite,
-                          relative_scale,
-                          relative_transform);
-
-      wl_shm_buffer_begin_access (shm_buffer);
-      buffer_data = wl_shm_buffer_get_data (shm_buffer);
-
-      width = wl_shm_buffer_get_width (shm_buffer);
-      height = wl_shm_buffer_get_height (shm_buffer);
-
-      switch (wl_shm_buffer_get_format (shm_buffer))
-        {
-        case WL_SHM_FORMAT_ARGB8888:
-          gbm_format = GBM_FORMAT_ARGB8888;
-          break;
-        case WL_SHM_FORMAT_XRGB8888:
-          gbm_format = GBM_FORMAT_XRGB8888;
-          break;
-        default:
-          g_warn_if_reached ();
-          gbm_format = GBM_FORMAT_ARGB8888;
-        }
-
-      load_scaled_and_transformed_cursor_sprite (native,
-                                                 gpu_kms,
-                                                 cursor_sprite,
-                                                 relative_scale,
-                                                 relative_transform,
-                                                 buffer_data,
-                                                 width,
-                                                 height,
-                                                 rowstride,
-                                                 gbm_format);
-
-      wl_shm_buffer_end_access (shm_buffer);
-    }
-  else
+  if (priv->kms_cursor_renderer)
     {
-      struct gbm_device *gbm_device;
-      struct gbm_bo *bo;
-
-      /* HW cursors have a predefined size (at least 64x64), which usually is
-       * bigger than cursor theme size, so themed cursors must be padded with
-       * transparent pixels to fill the overlay. This is trivial if we have CPU
-       * access to the data, but it's not possible if the buffer is in GPU
-       * memory (and possibly tiled too), so if we don't get the right size, we
-       * fallback to GL. */
-      cursor_width = (uint64_t) cursor_renderer_gpu_data->cursor_width;
-      cursor_height = (uint64_t) cursor_renderer_gpu_data->cursor_height;
-
-      texture = meta_cursor_sprite_get_cogl_texture (cursor_sprite);
-      width = cogl_texture_get_width (texture);
-      height = cogl_texture_get_height (texture);
-
-      if (width != cursor_width || height != cursor_height)
+      if (priv->use_kms_cursor)
         {
-          meta_warning ("Invalid cursor size (must be 64x64), falling back to software (GL) cursors");
-          return;
+          kms_cursor_drawn =
+            meta_kms_cursor_renderer_update_cursor (priv->kms_cursor_renderer,
+                                                    cursor_sprite);
         }
-
-      gbm_device = meta_gbm_device_from_gpu (gpu_kms);
-      bo = gbm_bo_import (gbm_device,
-                          GBM_BO_IMPORT_WL_BUFFER,
-                          buffer,
-                          GBM_BO_USE_CURSOR);
-      if (!bo)
+      else
         {
-          meta_warning ("Importing HW cursor from wl_buffer failed");
-          return;
+          meta_kms_cursor_renderer_update_cursor (priv->kms_cursor_renderer, NULL);
         }
-
-      unset_can_preprocess (cursor_sprite);
-
-      set_pending_cursor_sprite_gbm_bo (cursor_sprite, gpu_kms, bo);
-    }
-}
-#endif
-
-static void
-realize_cursor_sprite_from_xcursor_for_gpu (MetaCursorRenderer      *renderer,
-                                            MetaGpuKms              *gpu_kms,
-                                            MetaCursorSpriteXcursor *sprite_xcursor)
-{
-  MetaCursorRendererNative *native = META_CURSOR_RENDERER_NATIVE (renderer);
-  MetaCursorRendererNativeGpuData *cursor_renderer_gpu_data;
-  MetaCursorSprite *cursor_sprite = META_CURSOR_SPRITE (sprite_xcursor);
-  XcursorImage *xc_image;
-  float relative_scale;
-  MetaMonitorTransform relative_transform;
-
-  ensure_cursor_priv (cursor_sprite);
-
-  cursor_renderer_gpu_data =
-    meta_cursor_renderer_native_gpu_data_from_gpu (gpu_kms);
-  if (!cursor_renderer_gpu_data || cursor_renderer_gpu_data->hw_cursor_broken)
-    return;
-
-  if (is_cursor_hw_state_valid (cursor_sprite, gpu_kms) &&
-      is_cursor_scale_and_transform_valid (renderer, cursor_sprite))
-    return;
-
-  if (!get_common_crtc_sprite_scale_for_logical_monitors (renderer,
-                                                          cursor_sprite,
-                                                          &relative_scale))
-    {
-      unset_can_preprocess (cursor_sprite);
-      return;
-    }
-
-  if (!get_common_crtc_sprite_transform_for_logical_monitors (renderer,
-                                                              cursor_sprite,
-                                                              &relative_transform))
-    {
-      unset_can_preprocess (cursor_sprite);
-      return;
-    }
-
-  set_can_preprocess (cursor_sprite,
-                      relative_scale,
-                      relative_transform);
-
-  xc_image = meta_cursor_sprite_xcursor_get_current_image (sprite_xcursor);
-
-  load_scaled_and_transformed_cursor_sprite (native,
-                                             gpu_kms,
-                                             cursor_sprite,
-                                             relative_scale,
-                                             relative_transform,
-                                             (uint8_t *) xc_image->pixels,
-                                             xc_image->width,
-                                             xc_image->height,
-                                             xc_image->width * 4,
-                                             GBM_FORMAT_ARGB8888);
-}
-
-static void
-realize_cursor_sprite_for_gpu (MetaCursorRenderer *renderer,
-                               MetaGpuKms         *gpu_kms,
-                               MetaCursorSprite   *cursor_sprite)
-{
-  if (META_IS_CURSOR_SPRITE_XCURSOR (cursor_sprite))
-    {
-      MetaCursorSpriteXcursor *sprite_xcursor =
-        META_CURSOR_SPRITE_XCURSOR (cursor_sprite);
-
-      realize_cursor_sprite_from_xcursor_for_gpu (renderer,
-                                                  gpu_kms,
-                                                  sprite_xcursor);
-    }
-#ifdef HAVE_WAYLAND
-  else if (META_IS_CURSOR_SPRITE_WAYLAND (cursor_sprite))
-    {
-      MetaCursorSpriteWayland *sprite_wayland =
-        META_CURSOR_SPRITE_WAYLAND (cursor_sprite);
-
-      realize_cursor_sprite_from_wl_buffer_for_gpu (renderer,
-                                                    gpu_kms,
-                                                    sprite_wayland);
     }
-#endif
-}
-
-static void
-realize_cursor_sprite (MetaCursorRenderer *renderer,
-                       MetaCursorSprite   *cursor_sprite,
-                       GList              *gpus)
-{
-  GList *l;
-
-  for (l = gpus; l; l = l->next)
-    {
-      MetaGpuKms *gpu_kms = l->data;
 
-      realize_cursor_sprite_for_gpu (renderer, gpu_kms, cursor_sprite);
-    }
+  return (kms_cursor_drawn ||
+          !cursor_sprite ||
+          !meta_cursor_sprite_get_cogl_texture (cursor_sprite));
 }
 
 static void
@@ -1653,20 +188,17 @@ meta_cursor_renderer_native_class_init (MetaCursorRendererNativeClass *klass)
 
   object_class->finalize = meta_cursor_renderer_native_finalize;
   renderer_class->update_cursor = meta_cursor_renderer_native_update_cursor;
-
-  quark_cursor_sprite = g_quark_from_static_string ("-meta-cursor-native");
-  quark_cursor_renderer_native_gpu_data =
-    g_quark_from_static_string ("-meta-cursor-renderer-native-gpu-data");
 }
 
 static void
-force_update_hw_cursor (MetaCursorRendererNative *native)
+force_update_kms_cursor (MetaCursorRendererNative *native)
 {
   MetaCursorRenderer *renderer = META_CURSOR_RENDERER (native);
   MetaCursorRendererNativePrivate *priv =
     meta_cursor_renderer_native_get_instance_private (native);
 
-  priv->hw_state_invalidated = TRUE;
+  if (priv->kms_cursor_renderer)
+    meta_kms_cursor_renderer_invalidate_state (priv->kms_cursor_renderer);
   meta_cursor_renderer_force_update (renderer);
 }
 
@@ -1674,56 +206,7 @@ static void
 on_monitors_changed (MetaMonitorManager       *monitors,
                      MetaCursorRendererNative *native)
 {
-  force_update_hw_cursor (native);
-}
-
-static void
-init_hw_cursor_support_for_gpu (MetaGpuKms *gpu_kms)
-{
-  MetaKmsDevice *kms_device = meta_gpu_kms_get_kms_device (gpu_kms);
-  MetaCursorRendererNativeGpuData *cursor_renderer_gpu_data;
-  struct gbm_device *gbm_device;
-  uint64_t width, height;
-
-  gbm_device = meta_gbm_device_from_gpu (gpu_kms);
-  if (!gbm_device)
-    return;
-
-  cursor_renderer_gpu_data =
-    meta_create_cursor_renderer_native_gpu_data (gpu_kms);
-
-  if (!meta_kms_device_get_cursor_size (kms_device, &width, &height))
-    {
-      width = 64;
-      height = 64;
-    }
-
-  cursor_renderer_gpu_data->cursor_width = width;
-  cursor_renderer_gpu_data->cursor_height = height;
-}
-
-static void
-on_gpu_added_for_cursor (MetaBackend *backend,
-                         MetaGpuKms  *gpu_kms)
-{
-  init_hw_cursor_support_for_gpu (gpu_kms);
-}
-
-static void
-init_hw_cursor_support (MetaCursorRendererNative *cursor_renderer_native)
-{
-  MetaCursorRendererNativePrivate *priv =
-    meta_cursor_renderer_native_get_instance_private (cursor_renderer_native);
-  GList *gpus;
-  GList *l;
-
-  gpus = meta_backend_get_gpus (priv->backend);
-  for (l = gpus; l; l = l->next)
-    {
-      MetaGpuKms *gpu_kms = l->data;
-
-      init_hw_cursor_support_for_gpu (gpu_kms);
-    }
+  force_update_kms_cursor (native);
 }
 
 MetaCursorRendererNative *
@@ -1745,13 +228,8 @@ meta_cursor_renderer_native_new (MetaBackend        *backend,
   g_signal_connect_object (monitor_manager, "monitors-changed-internal",
                            G_CALLBACK (on_monitors_changed),
                            cursor_renderer_native, 0);
-  g_signal_connect (backend, "gpu-added",
-                    G_CALLBACK (on_gpu_added_for_cursor), NULL);
 
   priv->backend = backend;
-  priv->hw_state_invalidated = TRUE;
-
-  init_hw_cursor_support (cursor_renderer_native);
 
   return cursor_renderer_native;
 }
@@ -1760,3 +238,26 @@ static void
 meta_cursor_renderer_native_init (MetaCursorRendererNative *native)
 {
 }
+
+void
+meta_cursor_renderer_native_set_kms_cursor_renderer (MetaCursorRendererNative *native,
+                                                     MetaKmsCursorRenderer    *kms_cursor_renderer)
+{
+  MetaCursorRenderer *renderer = META_CURSOR_RENDERER (native);
+  MetaCursorRendererNativePrivate *priv =
+    meta_cursor_renderer_native_get_instance_private (native);
+
+  if (priv->kms_cursor_renderer == kms_cursor_renderer)
+    return;
+
+  if (g_set_object (&priv->kms_cursor_renderer, kms_cursor_renderer))
+    {
+      if (priv->kms_cursor_renderer)
+        {
+          meta_kms_cursor_renderer_set_cursor_renderer (priv->kms_cursor_renderer,
+                                                        renderer);
+        }
+
+      meta_cursor_renderer_force_update (renderer);
+    }
+}
diff --git a/src/backends/native/meta-cursor-renderer-native.h 
b/src/backends/native/meta-cursor-renderer-native.h
index 26c698cda5..62e612dade 100644
--- a/src/backends/native/meta-cursor-renderer-native.h
+++ b/src/backends/native/meta-cursor-renderer-native.h
@@ -26,6 +26,7 @@
 #define META_CURSOR_RENDERER_NATIVE_H
 
 #include "backends/meta-cursor-renderer.h"
+#include "backends/native/meta-kms-cursor-renderer.h"
 #include "meta/meta-backend.h"
 
 #define META_TYPE_CURSOR_RENDERER_NATIVE (meta_cursor_renderer_native_get_type ())
@@ -36,4 +37,7 @@ G_DECLARE_FINAL_TYPE (MetaCursorRendererNative, meta_cursor_renderer_native,
 MetaCursorRendererNative * meta_cursor_renderer_native_new (MetaBackend        *backend,
                                                             ClutterInputDevice *device);
 
+void meta_cursor_renderer_native_set_kms_cursor_renderer (MetaCursorRendererNative *native,
+                                                          MetaKmsCursorRenderer    *kms_cursor_renderer);
+
 #endif /* META_CURSOR_RENDERER_NATIVE_H */
diff --git a/src/backends/native/meta-kms-cursor-renderer.c b/src/backends/native/meta-kms-cursor-renderer.c
new file mode 100644
index 0000000000..a86b3d8e8f
--- /dev/null
+++ b/src/backends/native/meta-kms-cursor-renderer.c
@@ -0,0 +1,1700 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/*
+ * Copyright (C) 2014 Red Hat
+ * Copyright 2020 DisplayLink (UK) Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ * Written by:
+ *     Jasper St. Pierre <jstpierre mecheye net>
+ */
+
+#include "config.h"
+
+#include "backends/native/meta-kms-cursor-renderer.h"
+
+#include <string.h>
+#include <gbm.h>
+#include <xf86drm.h>
+#include <errno.h>
+
+#include "backends/meta-backend-private.h"
+#include "backends/meta-cursor-sprite-xcursor.h"
+#include "backends/meta-logical-monitor.h"
+#include "backends/meta-monitor.h"
+#include "backends/meta-monitor-manager-private.h"
+#include "backends/meta-output.h"
+#include "backends/native/meta-crtc-kms.h"
+#include "backends/native/meta-cursor-renderer-native.h"
+#include "backends/native/meta-kms-device.h"
+#include "backends/native/meta-kms-update.h"
+#include "backends/native/meta-kms.h"
+#include "backends/native/meta-renderer-native.h"
+#include "core/boxes-private.h"
+#include "meta/boxes.h"
+#include "meta/meta-backend.h"
+#include "meta/util.h"
+
+#ifdef HAVE_WAYLAND
+#include "wayland/meta-cursor-sprite-wayland.h"
+#include "wayland/meta-wayland-buffer.h"
+#endif
+
+#ifndef DRM_CAP_CURSOR_WIDTH
+#define DRM_CAP_CURSOR_WIDTH 0x8
+#endif
+#ifndef DRM_CAP_CURSOR_HEIGHT
+#define DRM_CAP_CURSOR_HEIGHT 0x9
+#endif
+
+/* When animating a cursor, we usually call drmModeSetCursor2 once per frame.
+ * Though, testing shows that we need to triple buffer the cursor buffer in
+ * order to avoid glitches when animating the cursor, at least when running on
+ * Intel. The reason for this might be (but is not confirmed to be) due to
+ * the user space gbm_bo cache, making us reuse and overwrite the kernel side
+ * buffer content before it was scanned out. To avoid this, we keep a user space
+ * reference to each buffer we set until at least one frame after it was drawn.
+ * In effect, this means we three active cursor gbm_bo's: one that that just has
+ * been set, one that was previously set and may or may not have been scanned
+ * out, and one pending that will be replaced if the cursor sprite changes.
+ */
+#define HW_CURSOR_BUFFER_COUNT 3
+
+static GQuark quark_cursor_sprite = 0;
+
+typedef struct _MetaKmsCursorRendererPrivate MetaKmsCursorRendererPrivate;
+typedef struct _MetaKmsCursorRendererGpuData MetaKmsCursorRendererGpuData;
+typedef struct _MetaKmsCursorGpuState MetaKmsCursorGpuState;
+typedef struct _MetaKmsCursorPrivate MetaKmsCursorPrivate;
+
+struct _MetaKmsCursorRenderer
+{
+  MetaCursorRenderer parent;
+};
+
+struct _MetaKmsCursorRendererPrivate
+{
+  MetaBackend *backend;
+
+  gboolean hw_state_invalidated;
+  gboolean has_hw_cursor;
+
+  MetaCursorRenderer *cursor_renderer;
+  MetaCursorSprite *last_cursor;
+  guint animation_timeout_id;
+};
+
+struct _MetaKmsCursorRendererGpuData
+{
+  gboolean hw_cursor_broken;
+
+  uint64_t cursor_width;
+  uint64_t cursor_height;
+};
+
+typedef enum
+{
+  META_CURSOR_GBM_BO_STATE_NONE,
+  META_CURSOR_GBM_BO_STATE_SET,
+  META_CURSOR_GBM_BO_STATE_INVALIDATED,
+} MetaCursorGbmBoState;
+
+struct _MetaKmsCursorGpuState
+{
+  MetaGpu *gpu;
+  guint active_bo;
+  MetaCursorGbmBoState pending_bo_state;
+  struct gbm_bo *bos[HW_CURSOR_BUFFER_COUNT];
+};
+
+struct _MetaKmsCursorPrivate
+{
+  GHashTable *gpu_states;
+
+  struct {
+    gboolean can_preprocess;
+    float current_relative_scale;
+    MetaMonitorTransform current_relative_transform;
+  } preprocess_state;
+};
+
+static GQuark quark_kms_cursor_renderer_gpu_data = 0;
+
+G_DEFINE_TYPE_WITH_PRIVATE (MetaKmsCursorRenderer, meta_kms_cursor_renderer, G_TYPE_OBJECT);
+
+static void
+realize_cursor_sprite (MetaKmsCursorRenderer *kms_cursor_renderer,
+                       MetaCursorSprite      *cursor_sprite,
+                       GList                 *gpus);
+
+static MetaKmsCursorGpuState *
+get_cursor_gpu_state (MetaKmsCursorPrivate *cursor_priv,
+                      MetaGpuKms           *gpu_kms);
+
+static MetaKmsCursorGpuState *
+ensure_cursor_gpu_state (MetaKmsCursorPrivate *cursor_priv,
+                         MetaGpuKms           *gpu_kms);
+
+static void
+invalidate_cursor_gpu_state (MetaCursorSprite *cursor_sprite);
+
+static MetaKmsCursorPrivate *
+ensure_cursor_priv (MetaCursorSprite *cursor_sprite);
+
+static MetaKmsCursorPrivate *
+get_cursor_priv (MetaCursorSprite *cursor_sprite);
+
+static MetaKmsCursorRendererGpuData *
+meta_kms_cursor_renderer_gpu_data_from_gpu (MetaGpuKms *gpu_kms)
+{
+  return g_object_get_qdata (G_OBJECT (gpu_kms),
+                             quark_kms_cursor_renderer_gpu_data);
+}
+
+static MetaKmsCursorRendererGpuData *
+meta_create_kms_cursor_renderer_gpu_data (MetaGpuKms *gpu_kms)
+{
+  MetaKmsCursorRendererGpuData *cursor_renderer_gpu_data;
+
+  cursor_renderer_gpu_data = g_new0 (MetaKmsCursorRendererGpuData, 1);
+  g_object_set_qdata_full (G_OBJECT (gpu_kms),
+                           quark_kms_cursor_renderer_gpu_data,
+                           cursor_renderer_gpu_data,
+                           g_free);
+
+  return cursor_renderer_gpu_data;
+}
+
+static void
+meta_kms_cursor_renderer_finalize (GObject *object)
+{
+  MetaKmsCursorRenderer *renderer = META_KMS_CURSOR_RENDERER (object);
+  MetaKmsCursorRendererPrivate *priv =
+    meta_kms_cursor_renderer_get_instance_private (renderer);
+
+  g_clear_handle_id (&priv->animation_timeout_id, g_source_remove);
+
+  G_OBJECT_CLASS (meta_kms_cursor_renderer_parent_class)->finalize (object);
+}
+
+static guint
+get_pending_cursor_sprite_gbm_bo_index (MetaKmsCursorGpuState *cursor_gpu_state)
+{
+  return (cursor_gpu_state->active_bo + 1) % HW_CURSOR_BUFFER_COUNT;
+}
+
+static struct gbm_bo *
+get_pending_cursor_sprite_gbm_bo (MetaKmsCursorGpuState *cursor_gpu_state)
+{
+  guint pending_bo;
+
+  pending_bo = get_pending_cursor_sprite_gbm_bo_index (cursor_gpu_state);
+  return cursor_gpu_state->bos[pending_bo];
+}
+
+static struct gbm_bo *
+get_active_cursor_sprite_gbm_bo (MetaKmsCursorGpuState *cursor_gpu_state)
+{
+  return cursor_gpu_state->bos[cursor_gpu_state->active_bo];
+}
+
+static void
+set_pending_cursor_sprite_gbm_bo (MetaCursorSprite *cursor_sprite,
+                                  MetaGpuKms       *gpu_kms,
+                                  struct gbm_bo    *bo)
+{
+  MetaKmsCursorPrivate *cursor_priv;
+  MetaKmsCursorGpuState *cursor_gpu_state;
+  guint pending_bo;
+
+  cursor_priv = ensure_cursor_priv (cursor_sprite);
+  cursor_gpu_state = ensure_cursor_gpu_state (cursor_priv, gpu_kms);
+
+  pending_bo = get_pending_cursor_sprite_gbm_bo_index (cursor_gpu_state);
+  cursor_gpu_state->bos[pending_bo] = bo;
+  cursor_gpu_state->pending_bo_state = META_CURSOR_GBM_BO_STATE_SET;
+}
+
+static void
+calculate_crtc_cursor_hotspot (MetaCursorSprite *cursor_sprite,
+                               int              *cursor_hotspot_x,
+                               int              *cursor_hotspot_y)
+{
+  MetaKmsCursorPrivate *cursor_priv = get_cursor_priv (cursor_sprite);
+  int hot_x, hot_y;
+  int width, height;
+  float scale;
+  MetaMonitorTransform transform;
+
+  scale = cursor_priv->preprocess_state.current_relative_scale;
+  transform = cursor_priv->preprocess_state.current_relative_transform;
+
+  meta_cursor_sprite_get_hotspot (cursor_sprite, &hot_x, &hot_y);
+  width = meta_cursor_sprite_get_width (cursor_sprite);
+  height = meta_cursor_sprite_get_height (cursor_sprite);
+  meta_monitor_transform_transform_point (transform,
+                                          width, height,
+                                          hot_x, hot_y,
+                                          &hot_x, &hot_y);
+  *cursor_hotspot_x = (int) roundf (hot_x * scale);
+  *cursor_hotspot_y = (int) roundf (hot_y * scale);
+}
+
+static void
+set_crtc_cursor (MetaKmsCursorRenderer *kms_renderer,
+                 MetaKmsUpdate         *kms_update,
+                 MetaCrtcKms           *crtc_kms,
+                 int                    x,
+                 int                    y,
+                 MetaCursorSprite      *cursor_sprite)
+{
+  MetaKmsCursorRendererPrivate *priv =
+    meta_kms_cursor_renderer_get_instance_private (kms_renderer);
+  MetaCrtc *crtc = META_CRTC (crtc_kms);
+  MetaKmsCursorPrivate *cursor_priv = get_cursor_priv (cursor_sprite);
+  MetaGpuKms *gpu_kms = META_GPU_KMS (meta_crtc_get_gpu (crtc));
+  MetaKmsCursorRendererGpuData *cursor_renderer_gpu_data =
+    meta_kms_cursor_renderer_gpu_data_from_gpu (gpu_kms);
+  MetaKmsCursorGpuState *cursor_gpu_state =
+    get_cursor_gpu_state (cursor_priv, gpu_kms);
+  MetaKmsCrtc *kms_crtc;
+  MetaKmsDevice *kms_device;
+  MetaKmsPlane *cursor_plane;
+  struct gbm_bo *bo;
+  union gbm_bo_handle handle;
+  int cursor_width, cursor_height;
+  MetaFixed16Rectangle src_rect;
+  MetaFixed16Rectangle dst_rect;
+  struct gbm_bo *crtc_bo;
+  MetaKmsAssignPlaneFlag flags;
+  int cursor_hotspot_x;
+  int cursor_hotspot_y;
+  MetaKmsPlaneAssignment *plane_assignment;
+
+  if (cursor_gpu_state->pending_bo_state == META_CURSOR_GBM_BO_STATE_SET)
+    bo = get_pending_cursor_sprite_gbm_bo (cursor_gpu_state);
+  else
+    bo = get_active_cursor_sprite_gbm_bo (cursor_gpu_state);
+
+  kms_crtc = meta_crtc_kms_get_kms_crtc (crtc_kms);
+  kms_device = meta_kms_crtc_get_device (kms_crtc);
+  cursor_plane = meta_kms_device_get_cursor_plane_for (kms_device, kms_crtc);
+  g_return_if_fail (cursor_plane);
+
+  handle = gbm_bo_get_handle (bo);
+
+  cursor_width = cursor_renderer_gpu_data->cursor_width;
+  cursor_height = cursor_renderer_gpu_data->cursor_height;
+  src_rect = (MetaFixed16Rectangle) {
+    .x = meta_fixed_16_from_int (0),
+    .y = meta_fixed_16_from_int (0),
+    .width = meta_fixed_16_from_int (cursor_width),
+    .height = meta_fixed_16_from_int (cursor_height),
+  };
+  dst_rect = (MetaFixed16Rectangle) {
+    .x = meta_fixed_16_from_int (x),
+    .y = meta_fixed_16_from_int (y),
+    .width = meta_fixed_16_from_int (cursor_width),
+    .height = meta_fixed_16_from_int (cursor_height),
+  };
+
+  flags = META_KMS_ASSIGN_PLANE_FLAG_NONE;
+  crtc_bo = meta_crtc_kms_get_cursor_renderer_private (crtc_kms);
+  if (!priv->hw_state_invalidated && bo == crtc_bo)
+    flags |= META_KMS_ASSIGN_PLANE_FLAG_FB_UNCHANGED;
+
+  plane_assignment = meta_kms_update_assign_plane (kms_update,
+                                                   kms_crtc,
+                                                   cursor_plane,
+                                                   handle.u32,
+                                                   src_rect,
+                                                   dst_rect,
+                                                   flags);
+
+  calculate_crtc_cursor_hotspot (cursor_sprite,
+                                 &cursor_hotspot_x,
+                                 &cursor_hotspot_y);
+  meta_kms_plane_assignment_set_cursor_hotspot (plane_assignment,
+                                                cursor_hotspot_x,
+                                                cursor_hotspot_y);
+
+  meta_crtc_kms_set_cursor_renderer_private (crtc_kms, bo);
+
+  if (cursor_gpu_state->pending_bo_state == META_CURSOR_GBM_BO_STATE_SET)
+    {
+      cursor_gpu_state->active_bo =
+        (cursor_gpu_state->active_bo + 1) % HW_CURSOR_BUFFER_COUNT;
+      cursor_gpu_state->pending_bo_state = META_CURSOR_GBM_BO_STATE_NONE;
+    }
+}
+
+static void
+unset_crtc_cursor (MetaKmsCursorRenderer *kms_renderer,
+                   MetaKmsUpdate         *kms_update,
+                   MetaCrtcKms           *crtc_kms)
+{
+  MetaKmsCursorRendererPrivate *priv =
+    meta_kms_cursor_renderer_get_instance_private (kms_renderer);
+  MetaKmsCrtc *kms_crtc;
+  MetaKmsDevice *kms_device;
+  MetaKmsPlane *cursor_plane;
+  struct gbm_bo *crtc_bo;
+
+  crtc_bo = meta_crtc_kms_get_cursor_renderer_private (crtc_kms);
+  if (!priv->hw_state_invalidated && !crtc_bo)
+    return;
+
+  kms_crtc = meta_crtc_kms_get_kms_crtc (crtc_kms);
+  kms_device = meta_kms_crtc_get_device (kms_crtc);
+  cursor_plane = meta_kms_device_get_cursor_plane_for (kms_device, kms_crtc);
+
+  if (cursor_plane)
+    meta_kms_update_unassign_plane (kms_update, kms_crtc, cursor_plane);
+
+  meta_crtc_kms_set_cursor_renderer_private (crtc_kms, NULL);
+}
+
+static float
+calculate_cursor_crtc_sprite_scale (MetaCursorSprite   *cursor_sprite,
+                                    MetaLogicalMonitor *logical_monitor)
+{
+  if (meta_is_stage_views_scaled ())
+    {
+      return (meta_logical_monitor_get_scale (logical_monitor) *
+              meta_cursor_sprite_get_texture_scale (cursor_sprite));
+    }
+  else
+    {
+      return 1.0;
+    }
+}
+
+typedef struct
+{
+  MetaKmsCursorRenderer *in_kms_cursor_renderer;
+  MetaLogicalMonitor *in_logical_monitor;
+  graphene_rect_t in_local_cursor_rect;
+  MetaCursorSprite *in_cursor_sprite;
+  MetaKmsUpdate *in_kms_update;
+
+  gboolean out_painted;
+} UpdateCrtcCursorData;
+
+static gboolean
+update_monitor_crtc_cursor (MetaMonitor         *monitor,
+                            MetaMonitorMode     *monitor_mode,
+                            MetaMonitorCrtcMode *monitor_crtc_mode,
+                            gpointer             user_data,
+                            GError             **error)
+{
+  UpdateCrtcCursorData *data = user_data;
+  MetaKmsCursorRenderer *kms_cursor_renderer =
+    data->in_kms_cursor_renderer;
+  MetaKmsCursorRendererPrivate *priv =
+    meta_kms_cursor_renderer_get_instance_private (kms_cursor_renderer);
+  MetaCrtc *crtc;
+  MetaMonitorTransform transform;
+  const MetaCrtcModeInfo *crtc_mode_info;
+  graphene_rect_t scaled_crtc_rect;
+  float scale;
+  int crtc_x, crtc_y;
+  int crtc_width, crtc_height;
+
+  if (meta_is_stage_views_scaled ())
+    scale = meta_logical_monitor_get_scale (data->in_logical_monitor);
+  else
+    scale = 1.0;
+
+  transform = meta_logical_monitor_get_transform (data->in_logical_monitor);
+  transform = meta_monitor_logical_to_crtc_transform (monitor, transform);
+
+  meta_monitor_calculate_crtc_pos (monitor, monitor_mode,
+                                   monitor_crtc_mode->output,
+                                   transform,
+                                   &crtc_x, &crtc_y);
+
+  crtc_mode_info = meta_crtc_mode_get_info (monitor_crtc_mode->crtc_mode);
+
+  if (meta_monitor_transform_is_rotated (transform))
+    {
+      crtc_width = crtc_mode_info->height;
+      crtc_height = crtc_mode_info->width;
+    }
+  else
+    {
+      crtc_width = crtc_mode_info->width;
+      crtc_height = crtc_mode_info->height;
+    }
+
+  scaled_crtc_rect = (graphene_rect_t) {
+    .origin = {
+      .x = crtc_x / scale,
+      .y = crtc_y / scale
+    },
+    .size = {
+      .width = crtc_width / scale,
+      .height = crtc_height / scale
+    },
+  };
+
+  crtc = meta_output_get_assigned_crtc (monitor_crtc_mode->output);
+
+  if (priv->has_hw_cursor &&
+      graphene_rect_intersection (&scaled_crtc_rect,
+                                  &data->in_local_cursor_rect,
+                                  NULL))
+    {
+      MetaMonitorTransform inverted_transform;
+      MetaRectangle cursor_rect;
+      CoglTexture *texture;
+      float crtc_cursor_x, crtc_cursor_y;
+      float cursor_crtc_scale;
+      int tex_width, tex_height;
+
+      crtc_cursor_x = (data->in_local_cursor_rect.origin.x -
+                       scaled_crtc_rect.origin.x) * scale;
+      crtc_cursor_y = (data->in_local_cursor_rect.origin.y -
+                       scaled_crtc_rect.origin.y) * scale;
+
+      texture = meta_cursor_sprite_get_cogl_texture (data->in_cursor_sprite);
+      tex_width = cogl_texture_get_width (texture);
+      tex_height = cogl_texture_get_height (texture);
+
+      cursor_crtc_scale =
+        calculate_cursor_crtc_sprite_scale (data->in_cursor_sprite,
+                                            data->in_logical_monitor);
+
+      cursor_rect = (MetaRectangle) {
+        .x = floorf (crtc_cursor_x),
+        .y = floorf (crtc_cursor_y),
+        .width = roundf (tex_width * cursor_crtc_scale),
+        .height = roundf (tex_height * cursor_crtc_scale)
+      };
+
+      inverted_transform = meta_monitor_transform_invert (transform);
+      meta_rectangle_transform (&cursor_rect,
+                                inverted_transform,
+                                crtc_mode_info->width,
+                                crtc_mode_info->height,
+                                &cursor_rect);
+
+      set_crtc_cursor (data->in_kms_cursor_renderer,
+                       data->in_kms_update,
+                       META_CRTC_KMS (crtc),
+                       cursor_rect.x,
+                       cursor_rect.y,
+                       data->in_cursor_sprite);
+
+      data->out_painted = data->out_painted || TRUE;
+    }
+  else
+    {
+      unset_crtc_cursor (data->in_kms_cursor_renderer,
+                         data->in_kms_update,
+                         META_CRTC_KMS (crtc));
+    }
+
+  return TRUE;
+}
+
+static void
+disable_hw_cursor_for_crtc (MetaKmsCrtc  *kms_crtc,
+                            const GError *error)
+{
+  MetaCrtcKms *crtc_kms = meta_crtc_kms_from_kms_crtc (kms_crtc);
+  MetaCrtc *crtc = META_CRTC (crtc_kms);
+  MetaGpuKms *gpu_kms = META_GPU_KMS (meta_crtc_get_gpu (crtc));
+  MetaKmsCursorRendererGpuData *cursor_renderer_gpu_data =
+    meta_kms_cursor_renderer_gpu_data_from_gpu (gpu_kms);
+
+  g_warning ("Failed to set hardware cursor (%s), "
+             "using OpenGL from now on",
+             error->message);
+  cursor_renderer_gpu_data->hw_cursor_broken = TRUE;
+}
+
+static void
+update_hw_cursor (MetaKmsCursorRenderer *kms_renderer,
+                  MetaCursorSprite      *cursor_sprite)
+{
+  MetaKmsCursorRendererPrivate *priv =
+    meta_kms_cursor_renderer_get_instance_private (kms_renderer);
+  MetaCursorRenderer *renderer = META_CURSOR_RENDERER (priv->cursor_renderer);
+  MetaBackend *backend = priv->backend;
+  MetaBackendNative *backend_native = META_BACKEND_NATIVE (priv->backend);
+  MetaKms *kms = meta_backend_native_get_kms (backend_native);
+  MetaMonitorManager *monitor_manager =
+    meta_backend_get_monitor_manager (backend);
+  MetaKmsUpdate *kms_update;
+  GList *logical_monitors;
+  GList *l;
+  graphene_rect_t rect;
+  gboolean painted = FALSE;
+  g_autoptr (MetaKmsFeedback) feedback = NULL;
+
+  kms_update = meta_kms_ensure_pending_update (kms);
+
+  if (cursor_sprite)
+    rect = meta_cursor_renderer_calculate_rect (renderer, cursor_sprite);
+  else
+    rect = GRAPHENE_RECT_INIT_ZERO;
+
+  logical_monitors =
+    meta_monitor_manager_get_logical_monitors (monitor_manager);
+  for (l = logical_monitors; l; l = l->next)
+    {
+      MetaLogicalMonitor *logical_monitor = l->data;
+      UpdateCrtcCursorData data;
+      GList *monitors;
+      GList *k;
+
+      data = (UpdateCrtcCursorData) {
+        .in_kms_cursor_renderer = kms_renderer,
+        .in_logical_monitor = logical_monitor,
+        .in_local_cursor_rect = (graphene_rect_t) {
+          .origin = {
+            .x = rect.origin.x - logical_monitor->rect.x,
+            .y = rect.origin.y - logical_monitor->rect.y
+          },
+          .size = rect.size
+        },
+        .in_cursor_sprite = cursor_sprite,
+        .in_kms_update = kms_update,
+      };
+
+      monitors = meta_logical_monitor_get_monitors (logical_monitor);
+      for (k = monitors; k; k = k->next)
+        {
+          MetaMonitor *monitor = k->data;
+          MetaMonitorMode *monitor_mode;
+
+          monitor_mode = meta_monitor_get_current_mode (monitor);
+          meta_monitor_mode_foreach_crtc (monitor, monitor_mode,
+                                          update_monitor_crtc_cursor,
+                                          &data,
+                                          NULL);
+        }
+
+      painted = painted || data.out_painted;
+    }
+
+  feedback = meta_kms_post_pending_update_sync (kms);
+  if (meta_kms_feedback_get_result (feedback) != META_KMS_FEEDBACK_PASSED)
+    {
+      for (l = meta_kms_feedback_get_failed_planes (feedback); l; l = l->next)
+        {
+          MetaKmsPlaneFeedback *plane_feedback = l->data;
+
+          if (!g_error_matches (plane_feedback->error,
+                                G_IO_ERROR,
+                                G_IO_ERROR_PERMISSION_DENIED))
+            {
+              disable_hw_cursor_for_crtc (plane_feedback->crtc,
+                                          plane_feedback->error);
+            }
+        }
+
+      priv->has_hw_cursor = FALSE;
+    }
+
+  priv->hw_state_invalidated = FALSE;
+
+  if (painted)
+    meta_cursor_renderer_emit_painted (renderer, cursor_sprite);
+}
+
+static gboolean
+has_valid_cursor_sprite_gbm_bo (MetaCursorSprite *cursor_sprite,
+                                MetaGpuKms       *gpu_kms)
+{
+  MetaKmsCursorPrivate *cursor_priv;
+  MetaKmsCursorGpuState *cursor_gpu_state;
+
+  cursor_priv = get_cursor_priv (cursor_sprite);
+  if (!cursor_priv)
+    return FALSE;
+
+  cursor_gpu_state = get_cursor_gpu_state (cursor_priv, gpu_kms);
+  if (!cursor_gpu_state)
+    return FALSE;
+
+  switch (cursor_gpu_state->pending_bo_state)
+    {
+    case META_CURSOR_GBM_BO_STATE_NONE:
+      return get_active_cursor_sprite_gbm_bo (cursor_gpu_state) != NULL;
+    case META_CURSOR_GBM_BO_STATE_SET:
+      return TRUE;
+    case META_CURSOR_GBM_BO_STATE_INVALIDATED:
+      return FALSE;
+    }
+
+  g_assert_not_reached ();
+
+  return FALSE;
+}
+
+static void
+set_can_preprocess (MetaCursorSprite     *cursor_sprite,
+                    float                 scale,
+                    MetaMonitorTransform  transform)
+{
+  MetaKmsCursorPrivate *cursor_priv = get_cursor_priv (cursor_sprite);
+
+  cursor_priv->preprocess_state.current_relative_scale = scale;
+  cursor_priv->preprocess_state.current_relative_transform = transform;
+  cursor_priv->preprocess_state.can_preprocess = TRUE;
+
+  invalidate_cursor_gpu_state (cursor_sprite);
+}
+
+static void
+unset_can_preprocess (MetaCursorSprite *cursor_sprite)
+{
+  MetaKmsCursorPrivate *cursor_priv = get_cursor_priv (cursor_sprite);
+
+  memset (&cursor_priv->preprocess_state,
+          0,
+          sizeof (cursor_priv->preprocess_state));
+  cursor_priv->preprocess_state.can_preprocess = FALSE;
+
+  invalidate_cursor_gpu_state (cursor_sprite);
+}
+
+static gboolean
+get_can_preprocess (MetaCursorSprite *cursor_sprite)
+{
+  MetaKmsCursorPrivate *cursor_priv = get_cursor_priv (cursor_sprite);
+
+  return cursor_priv->preprocess_state.can_preprocess;
+}
+
+static float
+get_current_relative_scale (MetaCursorSprite *cursor_sprite)
+{
+  MetaKmsCursorPrivate *cursor_priv = get_cursor_priv (cursor_sprite);
+
+  return cursor_priv->preprocess_state.current_relative_scale;
+}
+
+static MetaMonitorTransform
+get_current_relative_transform (MetaCursorSprite *cursor_sprite)
+{
+  MetaKmsCursorPrivate *cursor_priv = get_cursor_priv (cursor_sprite);
+
+  return cursor_priv->preprocess_state.current_relative_transform;
+}
+
+static void
+has_cursor_plane (MetaLogicalMonitor *logical_monitor,
+                  MetaMonitor        *monitor,
+                  MetaOutput         *output,
+                  MetaCrtc           *crtc,
+                  gpointer            user_data)
+{
+  gboolean *has_cursor_planes = user_data;
+  MetaCrtcKms *crtc_kms = META_CRTC_KMS (crtc);
+  MetaKmsCrtc *kms_crtc = meta_crtc_kms_get_kms_crtc (crtc_kms);
+  MetaKmsDevice *kms_device = meta_kms_crtc_get_device (kms_crtc);
+
+  *has_cursor_planes &= !!meta_kms_device_get_cursor_plane_for (kms_device,
+                                                                kms_crtc);
+}
+
+static gboolean
+crtcs_has_cursor_planes (MetaKmsCursorRenderer *kms_cursor_renderer,
+                         MetaCursorSprite      *cursor_sprite)
+{
+  MetaKmsCursorRendererPrivate *priv =
+    meta_kms_cursor_renderer_get_instance_private (kms_cursor_renderer);
+  MetaCursorRenderer *cursor_renderer = priv->cursor_renderer;
+  MetaBackend *backend = priv->backend;
+  MetaMonitorManager *monitor_manager =
+    meta_backend_get_monitor_manager (backend);
+  GList *logical_monitors;
+  GList *l;
+  graphene_rect_t cursor_rect;
+
+  cursor_rect = meta_cursor_renderer_calculate_rect (cursor_renderer, cursor_sprite);
+
+  logical_monitors =
+    meta_monitor_manager_get_logical_monitors (monitor_manager);
+  for (l = logical_monitors; l; l = l->next)
+    {
+      MetaLogicalMonitor *logical_monitor = l->data;
+      MetaRectangle logical_monitor_layout;
+      graphene_rect_t logical_monitor_rect;
+      gboolean has_cursor_planes;
+
+      logical_monitor_layout =
+        meta_logical_monitor_get_layout (logical_monitor);
+      logical_monitor_rect =
+        meta_rectangle_to_graphene_rect (&logical_monitor_layout);
+
+      if (!graphene_rect_intersection (&cursor_rect, &logical_monitor_rect,
+                                       NULL))
+        continue;
+
+      has_cursor_planes = TRUE;
+      meta_logical_monitor_foreach_crtc (logical_monitor,
+                                         has_cursor_plane,
+                                         &has_cursor_planes);
+      if (!has_cursor_planes)
+        return FALSE;
+    }
+
+  return TRUE;
+}
+
+static gboolean
+get_common_crtc_sprite_scale_for_logical_monitors (MetaKmsCursorRenderer *kms_cursor_renderer,
+                                                   MetaCursorSprite      *cursor_sprite,
+                                                   float                 *out_scale)
+{
+  MetaKmsCursorRendererPrivate *priv =
+    meta_kms_cursor_renderer_get_instance_private (kms_cursor_renderer);
+  MetaCursorRenderer *renderer = priv->cursor_renderer;
+  MetaBackend *backend = priv->backend;
+  MetaMonitorManager *monitor_manager =
+    meta_backend_get_monitor_manager (backend);
+  graphene_rect_t cursor_rect;
+  float scale = 1.0;
+  gboolean has_visible_crtc_sprite = FALSE;
+  GList *logical_monitors;
+  GList *l;
+
+  cursor_rect = meta_cursor_renderer_calculate_rect (renderer, cursor_sprite);
+
+  logical_monitors =
+    meta_monitor_manager_get_logical_monitors (monitor_manager);
+
+  for (l = logical_monitors; l; l = l->next)
+    {
+      MetaLogicalMonitor *logical_monitor = l->data;
+      graphene_rect_t logical_monitor_rect =
+        meta_rectangle_to_graphene_rect (&logical_monitor->rect);
+      float tmp_scale;
+
+      if (!graphene_rect_intersection (&cursor_rect,
+                                       &logical_monitor_rect,
+                                       NULL))
+        continue;
+
+      tmp_scale =
+        calculate_cursor_crtc_sprite_scale (cursor_sprite, logical_monitor);
+
+      if (has_visible_crtc_sprite && scale != tmp_scale)
+        return FALSE;
+
+      has_visible_crtc_sprite = TRUE;
+      scale = tmp_scale;
+    }
+
+  if (!has_visible_crtc_sprite)
+    return FALSE;
+
+  *out_scale = scale;
+  return TRUE;
+}
+
+static gboolean
+get_common_crtc_sprite_transform_for_logical_monitors (MetaKmsCursorRenderer *kms_cursor_renderer,
+                                                       MetaCursorSprite      *cursor_sprite,
+                                                       MetaMonitorTransform  *out_transform)
+{
+  MetaKmsCursorRendererPrivate *priv =
+    meta_kms_cursor_renderer_get_instance_private (kms_cursor_renderer);
+  MetaCursorRenderer *renderer = priv->cursor_renderer;
+  MetaBackend *backend = priv->backend;
+  MetaMonitorManager *monitor_manager =
+    meta_backend_get_monitor_manager (backend);
+  graphene_rect_t cursor_rect;
+  MetaMonitorTransform transform = META_MONITOR_TRANSFORM_NORMAL;
+  gboolean has_visible_crtc_sprite = FALSE;
+  GList *logical_monitors;
+  GList *l;
+
+  cursor_rect = meta_cursor_renderer_calculate_rect (renderer, cursor_sprite);
+
+  logical_monitors =
+    meta_monitor_manager_get_logical_monitors (monitor_manager);
+
+  for (l = logical_monitors; l; l = l->next)
+    {
+      MetaLogicalMonitor *logical_monitor = l->data;
+      graphene_rect_t logical_monitor_rect =
+        meta_rectangle_to_graphene_rect (&logical_monitor->rect);
+      MetaMonitorTransform logical_transform, tmp_transform;
+      GList *monitors, *l_mon;
+
+      if (!graphene_rect_intersection (&cursor_rect,
+                                       &logical_monitor_rect,
+                                       NULL))
+        continue;
+
+      logical_transform = meta_logical_monitor_get_transform (logical_monitor);
+      monitors = meta_logical_monitor_get_monitors (logical_monitor);
+      for (l_mon = monitors; l_mon; l_mon = l_mon->next)
+        {
+          MetaMonitor *monitor = l_mon->data;
+
+          tmp_transform = meta_monitor_transform_relative_transform (
+            meta_cursor_sprite_get_texture_transform (cursor_sprite),
+            meta_monitor_logical_to_crtc_transform (monitor, logical_transform));
+
+          if (has_visible_crtc_sprite && transform != tmp_transform)
+            return FALSE;
+
+          has_visible_crtc_sprite = TRUE;
+          transform = tmp_transform;
+        }
+    }
+
+  if (!has_visible_crtc_sprite)
+    return FALSE;
+
+  *out_transform = transform;
+  return TRUE;
+}
+
+static gboolean
+should_have_hw_cursor (MetaKmsCursorRenderer *kms_cursor_renderer,
+                       MetaCursorSprite      *cursor_sprite,
+                       GList                 *gpus)
+{
+  MetaMonitorTransform transform;
+  float scale;
+  GList *l;
+
+  if (!cursor_sprite)
+    return FALSE;
+
+  for (l = gpus; l; l = l->next)
+    {
+      MetaGpuKms *gpu_kms = l->data;
+      MetaKmsCursorRendererGpuData *cursor_renderer_gpu_data;
+
+      cursor_renderer_gpu_data =
+        meta_kms_cursor_renderer_gpu_data_from_gpu (gpu_kms);
+      if (!cursor_renderer_gpu_data)
+        return FALSE;
+
+      if (cursor_renderer_gpu_data->hw_cursor_broken)
+        return FALSE;
+
+      if (!has_valid_cursor_sprite_gbm_bo (cursor_sprite, gpu_kms))
+        return FALSE;
+    }
+
+  if (!crtcs_has_cursor_planes (kms_cursor_renderer, cursor_sprite))
+    return FALSE;
+
+  if (!get_common_crtc_sprite_scale_for_logical_monitors (kms_cursor_renderer,
+                                                          cursor_sprite,
+                                                          &scale))
+    return FALSE;
+
+  if (!get_common_crtc_sprite_transform_for_logical_monitors (kms_cursor_renderer,
+                                                              cursor_sprite,
+                                                              &transform))
+    return FALSE;
+
+  if (G_APPROX_VALUE (scale, 1.f, FLT_EPSILON) &&
+      transform == META_MONITOR_TRANSFORM_NORMAL)
+    return TRUE;
+  else
+    return get_can_preprocess (cursor_sprite);
+
+  return TRUE;
+}
+
+static GList *
+calculate_cursor_sprite_gpus (MetaKmsCursorRenderer *kms_cursor_renderer,
+                              MetaCursorSprite      *cursor_sprite)
+{
+  MetaKmsCursorRendererPrivate *priv =
+    meta_kms_cursor_renderer_get_instance_private (kms_cursor_renderer);
+  MetaCursorRenderer *renderer = priv->cursor_renderer;
+  MetaBackend *backend = priv->backend;
+  MetaMonitorManager *monitor_manager =
+    meta_backend_get_monitor_manager (backend);
+  GList *gpus = NULL;
+  GList *logical_monitors;
+  GList *l;
+  graphene_rect_t cursor_rect;
+
+  cursor_rect = meta_cursor_renderer_calculate_rect (renderer, cursor_sprite);
+
+  logical_monitors =
+    meta_monitor_manager_get_logical_monitors (monitor_manager);
+  for (l = logical_monitors; l; l = l->next)
+    {
+      MetaLogicalMonitor *logical_monitor = l->data;
+      MetaRectangle logical_monitor_layout;
+      graphene_rect_t logical_monitor_rect;
+      GList *monitors, *l_mon;
+
+      logical_monitor_layout =
+        meta_logical_monitor_get_layout (logical_monitor);
+      logical_monitor_rect =
+        meta_rectangle_to_graphene_rect (&logical_monitor_layout);
+
+      if (!graphene_rect_intersection (&cursor_rect, &logical_monitor_rect,
+                                       NULL))
+        continue;
+
+      monitors = meta_logical_monitor_get_monitors (logical_monitor);
+      for (l_mon = monitors; l_mon; l_mon = l_mon->next)
+        {
+          MetaMonitor *monitor = l_mon->data;
+          MetaGpu *gpu;
+
+          gpu = meta_monitor_get_gpu (monitor);
+          if (!g_list_find (gpus, gpu))
+            gpus = g_list_prepend (gpus, gpu);
+        }
+    }
+
+  return gpus;
+}
+
+gboolean
+meta_kms_cursor_renderer_update_cursor (MetaKmsCursorRenderer *kms_cursor_renderer,
+                                        MetaCursorSprite      *cursor_sprite)
+{
+  MetaKmsCursorRendererPrivate *priv =
+    meta_kms_cursor_renderer_get_instance_private (kms_cursor_renderer);
+  g_autoptr (GList) gpus = NULL;
+
+  if (cursor_sprite)
+    {
+      gpus = calculate_cursor_sprite_gpus (kms_cursor_renderer, cursor_sprite);
+      realize_cursor_sprite (kms_cursor_renderer, cursor_sprite, gpus);
+    }
+
+  priv->has_hw_cursor = should_have_hw_cursor (kms_cursor_renderer,
+                                               cursor_sprite, gpus);
+  update_hw_cursor (kms_cursor_renderer, cursor_sprite);
+
+  return priv->has_hw_cursor;
+}
+
+static void
+unset_crtc_cursor_renderer_privates (MetaGpu       *gpu,
+                                     struct gbm_bo *bo)
+{
+  GList *l;
+
+  for (l = meta_gpu_get_crtcs (gpu); l; l = l->next)
+    {
+      MetaCrtcKms *crtc_kms = META_CRTC_KMS (l->data);
+      struct gbm_bo *crtc_bo;
+
+      crtc_bo = meta_crtc_kms_get_cursor_renderer_private (crtc_kms);
+      if (bo == crtc_bo)
+        meta_crtc_kms_set_cursor_renderer_private (crtc_kms, NULL);
+    }
+}
+
+static void
+cursor_gpu_state_free (MetaKmsCursorGpuState *cursor_gpu_state)
+{
+  int i;
+  struct gbm_bo *active_bo;
+
+  active_bo = get_active_cursor_sprite_gbm_bo (cursor_gpu_state);
+  if (active_bo)
+    unset_crtc_cursor_renderer_privates (cursor_gpu_state->gpu, active_bo);
+
+  for (i = 0; i < HW_CURSOR_BUFFER_COUNT; i++)
+    g_clear_pointer (&cursor_gpu_state->bos[i], gbm_bo_destroy);
+  g_free (cursor_gpu_state);
+}
+
+static MetaKmsCursorGpuState *
+get_cursor_gpu_state (MetaKmsCursorPrivate *cursor_priv,
+                      MetaGpuKms           *gpu_kms)
+{
+  return g_hash_table_lookup (cursor_priv->gpu_states, gpu_kms);
+}
+
+static MetaKmsCursorGpuState *
+ensure_cursor_gpu_state (MetaKmsCursorPrivate *cursor_priv,
+                         MetaGpuKms           *gpu_kms)
+{
+  MetaKmsCursorGpuState *cursor_gpu_state;
+
+  cursor_gpu_state = get_cursor_gpu_state (cursor_priv, gpu_kms);
+  if (cursor_gpu_state)
+    return cursor_gpu_state;
+
+  cursor_gpu_state = g_new0 (MetaKmsCursorGpuState, 1);
+  cursor_gpu_state->gpu = META_GPU (gpu_kms);
+  g_hash_table_insert (cursor_priv->gpu_states, gpu_kms, cursor_gpu_state);
+
+  return cursor_gpu_state;
+}
+
+static void
+invalidate_cursor_gpu_state (MetaCursorSprite *cursor_sprite)
+{
+  MetaKmsCursorPrivate *cursor_priv = get_cursor_priv (cursor_sprite);
+  GHashTableIter iter;
+  MetaKmsCursorGpuState *cursor_gpu_state;
+
+  g_hash_table_iter_init (&iter, cursor_priv->gpu_states);
+  while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &cursor_gpu_state))
+    {
+      guint pending_bo;
+      pending_bo = get_pending_cursor_sprite_gbm_bo_index (cursor_gpu_state);
+      g_clear_pointer (&cursor_gpu_state->bos[pending_bo],
+                       gbm_bo_destroy);
+      cursor_gpu_state->pending_bo_state = META_CURSOR_GBM_BO_STATE_INVALIDATED;
+    }
+}
+
+static void
+on_cursor_sprite_texture_changed (MetaCursorSprite *cursor_sprite)
+{
+  invalidate_cursor_gpu_state (cursor_sprite);
+}
+
+static void
+cursor_priv_free (MetaKmsCursorPrivate *cursor_priv)
+{
+  g_hash_table_destroy (cursor_priv->gpu_states);
+  g_free (cursor_priv);
+}
+
+static MetaKmsCursorPrivate *
+get_cursor_priv (MetaCursorSprite *cursor_sprite)
+{
+  return g_object_get_qdata (G_OBJECT (cursor_sprite), quark_cursor_sprite);
+}
+
+static MetaKmsCursorPrivate *
+ensure_cursor_priv (MetaCursorSprite *cursor_sprite)
+{
+  MetaKmsCursorPrivate *cursor_priv;
+
+  cursor_priv = get_cursor_priv (cursor_sprite);
+  if (cursor_priv)
+    return cursor_priv;
+
+  cursor_priv = g_new0 (MetaKmsCursorPrivate, 1);
+  cursor_priv->gpu_states =
+    g_hash_table_new_full (g_direct_hash,
+                           g_direct_equal,
+                           NULL,
+                           (GDestroyNotify) cursor_gpu_state_free);
+  g_object_set_qdata_full (G_OBJECT (cursor_sprite),
+                           quark_cursor_sprite,
+                           cursor_priv,
+                           (GDestroyNotify) cursor_priv_free);
+
+  g_signal_connect (cursor_sprite, "texture-changed",
+                    G_CALLBACK (on_cursor_sprite_texture_changed), NULL);
+
+  unset_can_preprocess (cursor_sprite);
+
+  return cursor_priv;
+}
+
+static void
+load_cursor_sprite_gbm_buffer_for_gpu (MetaKmsCursorRenderer *kms_renderer,
+                                       MetaGpuKms            *gpu_kms,
+                                       MetaCursorSprite      *cursor_sprite,
+                                       uint8_t               *pixels,
+                                       uint                   width,
+                                       uint                   height,
+                                       int                    rowstride,
+                                       uint32_t               gbm_format)
+{
+  uint64_t cursor_width, cursor_height;
+  MetaKmsCursorRendererGpuData *cursor_renderer_gpu_data;
+  struct gbm_device *gbm_device;
+
+  cursor_renderer_gpu_data =
+    meta_kms_cursor_renderer_gpu_data_from_gpu (gpu_kms);
+  if (!cursor_renderer_gpu_data)
+    return;
+
+  cursor_width = (uint64_t) cursor_renderer_gpu_data->cursor_width;
+  cursor_height = (uint64_t) cursor_renderer_gpu_data->cursor_height;
+
+  if (width > cursor_width || height > cursor_height)
+    {
+      meta_warning ("Invalid theme cursor size (must be at most %ux%u)",
+                    (unsigned int)cursor_width, (unsigned int)cursor_height);
+      return;
+    }
+
+  gbm_device = meta_gbm_device_from_gpu (gpu_kms);
+  if (gbm_device_is_format_supported (gbm_device, gbm_format,
+                                      GBM_BO_USE_CURSOR | GBM_BO_USE_WRITE))
+    {
+      struct gbm_bo *bo;
+      uint8_t buf[4 * cursor_width * cursor_height];
+      uint i;
+
+      bo = gbm_bo_create (gbm_device, cursor_width, cursor_height,
+                          gbm_format, GBM_BO_USE_CURSOR | GBM_BO_USE_WRITE);
+      if (!bo)
+        {
+          meta_warning ("Failed to allocate HW cursor buffer");
+          return;
+        }
+
+      memset (buf, 0, sizeof(buf));
+      for (i = 0; i < height; i++)
+        memcpy (buf + i * 4 * cursor_width, pixels + i * rowstride, width * 4);
+      if (gbm_bo_write (bo, buf, cursor_width * cursor_height * 4) != 0)
+        {
+          meta_warning ("Failed to write cursors buffer data: %s",
+                        g_strerror (errno));
+          gbm_bo_destroy (bo);
+          return;
+        }
+
+      set_pending_cursor_sprite_gbm_bo (cursor_sprite, gpu_kms, bo);
+    }
+  else
+    {
+      meta_warning ("HW cursor for format %d not supported", gbm_format);
+    }
+}
+
+static gboolean
+is_cursor_hw_state_valid (MetaCursorSprite *cursor_sprite,
+                          MetaGpuKms       *gpu_kms)
+{
+  MetaKmsCursorPrivate *cursor_priv;
+  MetaKmsCursorGpuState *cursor_gpu_state;
+
+  cursor_priv = get_cursor_priv (cursor_sprite);
+  if (!cursor_priv)
+    return FALSE;
+
+  cursor_gpu_state = get_cursor_gpu_state (cursor_priv, gpu_kms);
+  if (!cursor_gpu_state)
+    return FALSE;
+
+  switch (cursor_gpu_state->pending_bo_state)
+    {
+    case META_CURSOR_GBM_BO_STATE_SET:
+    case META_CURSOR_GBM_BO_STATE_NONE:
+      return TRUE;
+    case META_CURSOR_GBM_BO_STATE_INVALIDATED:
+      return FALSE;
+    }
+
+  g_assert_not_reached ();
+  return FALSE;
+}
+
+static gboolean
+is_cursor_scale_and_transform_valid (MetaKmsCursorRenderer *kms_cursor_renderer,
+                                     MetaCursorSprite      *cursor_sprite)
+{
+  MetaMonitorTransform transform;
+  float scale;
+
+  if (!get_common_crtc_sprite_scale_for_logical_monitors (kms_cursor_renderer,
+                                                          cursor_sprite,
+                                                          &scale))
+    return FALSE;
+
+  if (!get_common_crtc_sprite_transform_for_logical_monitors (kms_cursor_renderer,
+                                                              cursor_sprite,
+                                                              &transform))
+    return FALSE;
+
+  return (scale == get_current_relative_scale (cursor_sprite) &&
+          transform == get_current_relative_transform (cursor_sprite));
+}
+
+static cairo_surface_t *
+scale_and_transform_cursor_sprite_cpu (uint8_t              *pixels,
+                                       int                   width,
+                                       int                   height,
+                                       int                   rowstride,
+                                       float                 scale,
+                                       MetaMonitorTransform  transform)
+{
+  cairo_t *cr;
+  cairo_surface_t *source_surface;
+  cairo_surface_t *target_surface;
+  int image_width;
+  int image_height;
+
+  image_width = ceilf (width * scale);
+  image_height = ceilf (height * scale);
+
+  target_surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
+                                               image_width,
+                                               image_height);
+
+  cr = cairo_create (target_surface);
+  if (transform != META_MONITOR_TRANSFORM_NORMAL)
+    {
+      cairo_translate (cr, 0.5 * image_width, 0.5 * image_height);
+      switch (transform)
+        {
+        case META_MONITOR_TRANSFORM_90:
+          cairo_rotate (cr, M_PI * 1.5);
+          break;
+        case META_MONITOR_TRANSFORM_180:
+          cairo_rotate (cr, M_PI);
+          break;
+        case META_MONITOR_TRANSFORM_270:
+          cairo_rotate (cr, M_PI * 0.5);
+          break;
+        case META_MONITOR_TRANSFORM_FLIPPED:
+          cairo_scale (cr, 1, -1);
+          break;
+        case META_MONITOR_TRANSFORM_FLIPPED_90:
+          cairo_rotate (cr, M_PI * 1.5);
+          cairo_scale (cr, -1, 1);
+          break;
+        case META_MONITOR_TRANSFORM_FLIPPED_180:
+          cairo_rotate (cr, M_PI);
+          cairo_scale (cr, 1, -1);
+          break;
+        case META_MONITOR_TRANSFORM_FLIPPED_270:
+          cairo_rotate (cr, M_PI * 0.5);
+          cairo_scale (cr, -1, 1);
+          break;
+        case META_MONITOR_TRANSFORM_NORMAL:
+          g_assert_not_reached ();
+        }
+      cairo_translate (cr, -0.5 * image_width, -0.5 * image_height);
+    }
+  cairo_scale (cr, scale, scale);
+
+  source_surface = cairo_image_surface_create_for_data (pixels,
+                                                        CAIRO_FORMAT_ARGB32,
+                                                        width,
+                                                        height,
+                                                        rowstride);
+
+  cairo_set_source_surface (cr, source_surface, 0, 0);
+  cairo_paint (cr);
+  cairo_destroy (cr);
+  cairo_surface_destroy (source_surface);
+
+  return target_surface;
+}
+
+static void
+load_scaled_and_transformed_cursor_sprite (MetaKmsCursorRenderer *kms_renderer,
+                                           MetaGpuKms            *gpu_kms,
+                                           MetaCursorSprite      *cursor_sprite,
+                                           float                  relative_scale,
+                                           MetaMonitorTransform   relative_transform,
+                                           uint8_t               *data,
+                                           int                    width,
+                                           int                    height,
+                                           int                    rowstride,
+                                           uint32_t               gbm_format)
+{
+  if (!G_APPROX_VALUE (relative_scale, 1.f, FLT_EPSILON) ||
+      relative_transform != META_MONITOR_TRANSFORM_NORMAL)
+    {
+      cairo_surface_t *surface;
+
+      surface = scale_and_transform_cursor_sprite_cpu (data,
+                                                       width,
+                                                       height,
+                                                       rowstride,
+                                                       relative_scale,
+                                                       relative_transform);
+
+      load_cursor_sprite_gbm_buffer_for_gpu (kms_renderer,
+                                             gpu_kms,
+                                             cursor_sprite,
+                                             cairo_image_surface_get_data (surface),
+                                             cairo_image_surface_get_width (surface),
+                                             cairo_image_surface_get_width (surface),
+                                             cairo_image_surface_get_stride (surface),
+                                             gbm_format);
+
+      cairo_surface_destroy (surface);
+    }
+  else
+    {
+      load_cursor_sprite_gbm_buffer_for_gpu (kms_renderer,
+                                             gpu_kms,
+                                             cursor_sprite,
+                                             data,
+                                             width,
+                                             height,
+                                             rowstride,
+                                             gbm_format);
+    }
+}
+
+#ifdef HAVE_WAYLAND
+static void
+realize_cursor_sprite_from_wl_buffer_for_gpu (MetaKmsCursorRenderer   *kms_cursor_renderer,
+                                              MetaGpuKms              *gpu_kms,
+                                              MetaCursorSpriteWayland *sprite_wayland)
+{
+  MetaCursorSprite *cursor_sprite = META_CURSOR_SPRITE (sprite_wayland);
+  MetaKmsCursorRendererGpuData *cursor_renderer_gpu_data;
+  uint64_t cursor_width, cursor_height;
+  CoglTexture *texture;
+  uint width, height;
+  MetaWaylandBuffer *buffer;
+  struct wl_resource *buffer_resource;
+  struct wl_shm_buffer *shm_buffer;
+
+  cursor_renderer_gpu_data =
+    meta_kms_cursor_renderer_gpu_data_from_gpu (gpu_kms);
+  if (!cursor_renderer_gpu_data || cursor_renderer_gpu_data->hw_cursor_broken)
+    return;
+
+  if (is_cursor_hw_state_valid (cursor_sprite, gpu_kms) &&
+      is_cursor_scale_and_transform_valid (kms_cursor_renderer, cursor_sprite))
+    return;
+
+  buffer = meta_cursor_sprite_wayland_get_buffer (sprite_wayland);
+  if (!buffer)
+    return;
+
+  buffer_resource = meta_wayland_buffer_get_resource (buffer);
+  if (!buffer_resource)
+    return;
+
+  ensure_cursor_priv (cursor_sprite);
+
+  shm_buffer = wl_shm_buffer_get (buffer_resource);
+  if (shm_buffer)
+    {
+      int rowstride = wl_shm_buffer_get_stride (shm_buffer);
+      uint8_t *buffer_data;
+      float relative_scale;
+      MetaMonitorTransform relative_transform;
+      uint32_t gbm_format;
+
+      if (!get_common_crtc_sprite_scale_for_logical_monitors (kms_cursor_renderer,
+                                                              cursor_sprite,
+                                                              &relative_scale))
+        {
+          unset_can_preprocess (cursor_sprite);
+          return;
+        }
+
+      if (!get_common_crtc_sprite_transform_for_logical_monitors (kms_cursor_renderer,
+                                                                  cursor_sprite,
+                                                                  &relative_transform))
+        {
+          unset_can_preprocess (cursor_sprite);
+          return;
+        }
+
+      set_can_preprocess (cursor_sprite,
+                          relative_scale,
+                          relative_transform);
+
+      wl_shm_buffer_begin_access (shm_buffer);
+      buffer_data = wl_shm_buffer_get_data (shm_buffer);
+
+      width = wl_shm_buffer_get_width (shm_buffer);
+      height = wl_shm_buffer_get_height (shm_buffer);
+
+      switch (wl_shm_buffer_get_format (shm_buffer))
+        {
+        case WL_SHM_FORMAT_ARGB8888:
+          gbm_format = GBM_FORMAT_ARGB8888;
+          break;
+        case WL_SHM_FORMAT_XRGB8888:
+          gbm_format = GBM_FORMAT_XRGB8888;
+          break;
+        default:
+          g_warn_if_reached ();
+          gbm_format = GBM_FORMAT_ARGB8888;
+        }
+
+      load_scaled_and_transformed_cursor_sprite (kms_cursor_renderer,
+                                                 gpu_kms,
+                                                 cursor_sprite,
+                                                 relative_scale,
+                                                 relative_transform,
+                                                 buffer_data,
+                                                 width,
+                                                 height,
+                                                 rowstride,
+                                                 gbm_format);
+
+      wl_shm_buffer_end_access (shm_buffer);
+    }
+  else
+    {
+      struct gbm_device *gbm_device;
+      struct gbm_bo *bo;
+
+      /* HW cursors have a predefined size (at least 64x64), which usually is
+       * bigger than cursor theme size, so themed cursors must be padded with
+       * transparent pixels to fill the overlay. This is trivial if we have CPU
+       * access to the data, but it's not possible if the buffer is in GPU
+       * memory (and possibly tiled too), so if we don't get the right size, we
+       * fallback to GL. */
+      cursor_width = (uint64_t) cursor_renderer_gpu_data->cursor_width;
+      cursor_height = (uint64_t) cursor_renderer_gpu_data->cursor_height;
+
+      texture = meta_cursor_sprite_get_cogl_texture (cursor_sprite);
+      width = cogl_texture_get_width (texture);
+      height = cogl_texture_get_height (texture);
+
+      if (width != cursor_width || height != cursor_height)
+        {
+          meta_warning ("Invalid cursor size (must be 64x64), falling back to software (GL) cursors");
+          return;
+        }
+
+      gbm_device = meta_gbm_device_from_gpu (gpu_kms);
+      bo = gbm_bo_import (gbm_device,
+                          GBM_BO_IMPORT_WL_BUFFER,
+                          buffer,
+                          GBM_BO_USE_CURSOR);
+      if (!bo)
+        {
+          meta_warning ("Importing HW cursor from wl_buffer failed");
+          return;
+        }
+
+      unset_can_preprocess (cursor_sprite);
+
+      set_pending_cursor_sprite_gbm_bo (cursor_sprite, gpu_kms, bo);
+    }
+}
+#endif
+
+static void
+realize_cursor_sprite_from_xcursor_for_gpu (MetaKmsCursorRenderer   *kms_cursor_renderer,
+                                            MetaGpuKms              *gpu_kms,
+                                            MetaCursorSpriteXcursor *sprite_xcursor)
+{
+  MetaKmsCursorRendererGpuData *cursor_renderer_gpu_data;
+  MetaCursorSprite *cursor_sprite = META_CURSOR_SPRITE (sprite_xcursor);
+  XcursorImage *xc_image;
+  float relative_scale;
+  MetaMonitorTransform relative_transform;
+
+  ensure_cursor_priv (cursor_sprite);
+
+  cursor_renderer_gpu_data =
+    meta_kms_cursor_renderer_gpu_data_from_gpu (gpu_kms);
+  if (!cursor_renderer_gpu_data || cursor_renderer_gpu_data->hw_cursor_broken)
+    return;
+
+  if (is_cursor_hw_state_valid (cursor_sprite, gpu_kms) &&
+      is_cursor_scale_and_transform_valid (kms_cursor_renderer, cursor_sprite))
+    return;
+
+  if (!get_common_crtc_sprite_scale_for_logical_monitors (kms_cursor_renderer,
+                                                          cursor_sprite,
+                                                          &relative_scale))
+    {
+      unset_can_preprocess (cursor_sprite);
+      return;
+    }
+
+  if (!get_common_crtc_sprite_transform_for_logical_monitors (kms_cursor_renderer,
+                                                              cursor_sprite,
+                                                              &relative_transform))
+    {
+      unset_can_preprocess (cursor_sprite);
+      return;
+    }
+
+  set_can_preprocess (cursor_sprite,
+                      relative_scale,
+                      relative_transform);
+
+  xc_image = meta_cursor_sprite_xcursor_get_current_image (sprite_xcursor);
+
+  load_scaled_and_transformed_cursor_sprite (kms_cursor_renderer,
+                                             gpu_kms,
+                                             cursor_sprite,
+                                             relative_scale,
+                                             relative_transform,
+                                             (uint8_t *) xc_image->pixels,
+                                             xc_image->width,
+                                             xc_image->height,
+                                             xc_image->width * 4,
+                                             GBM_FORMAT_ARGB8888);
+}
+
+static void
+realize_cursor_sprite_for_gpu (MetaKmsCursorRenderer *kms_cursor_renderer,
+                               MetaGpuKms            *gpu_kms,
+                               MetaCursorSprite      *cursor_sprite)
+{
+  if (META_IS_CURSOR_SPRITE_XCURSOR (cursor_sprite))
+    {
+      MetaCursorSpriteXcursor *sprite_xcursor =
+        META_CURSOR_SPRITE_XCURSOR (cursor_sprite);
+
+      realize_cursor_sprite_from_xcursor_for_gpu (kms_cursor_renderer,
+                                                  gpu_kms,
+                                                  sprite_xcursor);
+    }
+#ifdef HAVE_WAYLAND
+  else if (META_IS_CURSOR_SPRITE_WAYLAND (cursor_sprite))
+    {
+      MetaCursorSpriteWayland *sprite_wayland =
+        META_CURSOR_SPRITE_WAYLAND (cursor_sprite);
+
+      realize_cursor_sprite_from_wl_buffer_for_gpu (kms_cursor_renderer,
+                                                    gpu_kms,
+                                                    sprite_wayland);
+    }
+#endif
+}
+
+static void
+realize_cursor_sprite (MetaKmsCursorRenderer *kms_cursor_renderer,
+                       MetaCursorSprite      *cursor_sprite,
+                       GList                 *gpus)
+{
+  GList *l;
+
+  for (l = gpus; l; l = l->next)
+    {
+      MetaGpuKms *gpu_kms = l->data;
+
+      realize_cursor_sprite_for_gpu (kms_cursor_renderer, gpu_kms,
+                                     cursor_sprite);
+    }
+}
+
+static void
+meta_kms_cursor_renderer_class_init (MetaKmsCursorRendererClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = meta_kms_cursor_renderer_finalize;
+
+  quark_cursor_sprite = g_quark_from_static_string ("-meta-kms-cursor");
+  quark_kms_cursor_renderer_gpu_data =
+    g_quark_from_static_string ("-meta-kms-cursor-renderer-gpu-data");
+}
+
+static void
+init_hw_cursor_support_for_gpu (MetaGpuKms *gpu_kms)
+{
+  MetaKmsDevice *kms_device = meta_gpu_kms_get_kms_device (gpu_kms);
+  MetaKmsCursorRendererGpuData *cursor_renderer_gpu_data;
+  struct gbm_device *gbm_device;
+  uint64_t width, height;
+
+  gbm_device = meta_gbm_device_from_gpu (gpu_kms);
+  if (!gbm_device)
+    return;
+
+  cursor_renderer_gpu_data =
+    meta_create_kms_cursor_renderer_gpu_data (gpu_kms);
+
+  if (!meta_kms_device_get_cursor_size (kms_device, &width, &height))
+    {
+      width = 64;
+      height = 64;
+    }
+
+  cursor_renderer_gpu_data->cursor_width = width;
+  cursor_renderer_gpu_data->cursor_height = height;
+}
+
+static void
+on_gpu_added_for_cursor (MetaBackend *backend,
+                         MetaGpuKms  *gpu_kms)
+{
+  init_hw_cursor_support_for_gpu (gpu_kms);
+}
+
+static void
+init_hw_cursor_support (MetaKmsCursorRenderer *kms_cursor_renderer)
+{
+  MetaKmsCursorRendererPrivate *priv =
+    meta_kms_cursor_renderer_get_instance_private (kms_cursor_renderer);
+  GList *gpus;
+  GList *l;
+
+  gpus = meta_backend_get_gpus (priv->backend);
+  for (l = gpus; l; l = l->next)
+    {
+      MetaGpuKms *gpu_kms = l->data;
+
+      init_hw_cursor_support_for_gpu (gpu_kms);
+    }
+}
+
+MetaKmsCursorRenderer *
+meta_kms_cursor_renderer_new (MetaBackend *backend)
+{
+  MetaKmsCursorRenderer *kms_cursor_renderer;
+  MetaKmsCursorRendererPrivate *priv;
+
+  kms_cursor_renderer = g_object_new (META_TYPE_KMS_CURSOR_RENDERER, NULL);
+  priv = meta_kms_cursor_renderer_get_instance_private (kms_cursor_renderer);
+
+  g_signal_connect (backend, "gpu-added",
+                    G_CALLBACK (on_gpu_added_for_cursor), NULL);
+
+  priv->backend = backend;
+  priv->hw_state_invalidated = TRUE;
+
+  init_hw_cursor_support (kms_cursor_renderer);
+
+  return kms_cursor_renderer;
+}
+
+static void
+meta_kms_cursor_renderer_init (MetaKmsCursorRenderer *kms_renderer)
+{
+}
+
+void
+meta_kms_cursor_renderer_invalidate_state (MetaKmsCursorRenderer *kms_renderer)
+{
+  MetaKmsCursorRendererPrivate *priv =
+    meta_kms_cursor_renderer_get_instance_private (kms_renderer);
+
+  priv->hw_state_invalidated = TRUE;
+}
+
+void
+meta_kms_cursor_renderer_set_cursor_renderer (MetaKmsCursorRenderer *kms_renderer,
+                                              MetaCursorRenderer    *renderer)
+{
+  MetaKmsCursorRendererPrivate *priv =
+    meta_kms_cursor_renderer_get_instance_private (kms_renderer);
+  MetaCursorRenderer *old_renderer =
+    priv->cursor_renderer ? g_object_ref (priv->cursor_renderer) : NULL;
+
+  if (g_set_object (&priv->cursor_renderer, renderer))
+    {
+      if (old_renderer)
+        {
+          meta_cursor_renderer_native_set_kms_cursor_renderer (META_CURSOR_RENDERER_NATIVE (old_renderer),
+                                                               NULL);
+        }
+    }
+
+  if (old_renderer)
+    g_object_unref (old_renderer);
+}
diff --git a/src/backends/native/meta-kms-cursor-renderer.h b/src/backends/native/meta-kms-cursor-renderer.h
new file mode 100644
index 0000000000..d12589a97e
--- /dev/null
+++ b/src/backends/native/meta-kms-cursor-renderer.h
@@ -0,0 +1,43 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/*
+ * Copyright (C) 2014 Red Hat
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ * Written by:
+ *     Jasper St. Pierre <jstpierre mecheye net>
+ */
+
+#ifndef META_KMS_CURSOR_RENDERER_H
+#define META_KMS_CURSOR_RENDERER_H
+
+#include "backends/meta-cursor-renderer.h"
+#include "meta/meta-backend.h"
+
+#define META_TYPE_KMS_CURSOR_RENDERER (meta_kms_cursor_renderer_get_type ())
+G_DECLARE_FINAL_TYPE (MetaKmsCursorRenderer, meta_kms_cursor_renderer,
+                      META, KMS_CURSOR_RENDERER,
+                      GObject)
+
+MetaKmsCursorRenderer * meta_kms_cursor_renderer_new (MetaBackend *backend);
+void meta_kms_cursor_renderer_invalidate_state (MetaKmsCursorRenderer *kms_renderer);
+gboolean meta_kms_cursor_renderer_update_cursor (MetaKmsCursorRenderer *kms_cursor_renderer,
+                                                 MetaCursorSprite      *sprite);
+void meta_kms_cursor_renderer_set_cursor_renderer (MetaKmsCursorRenderer *kms_renderer,
+                                                   MetaCursorRenderer    *renderer);
+
+#endif /* META_KMS_CURSOR_RENDERER_H */
diff --git a/src/backends/native/meta-seat-native.c b/src/backends/native/meta-seat-native.c
index 9d7069ec5c..1eeb9d7251 100644
--- a/src/backends/native/meta-seat-native.c
+++ b/src/backends/native/meta-seat-native.c
@@ -2530,6 +2530,9 @@ meta_seat_native_constructed (GObject *object)
       CLUTTER_INPUT_MODE_LOGICAL);
   seat->core_keyboard = device;
 
+  seat->kms_cursor_renderer =
+    meta_kms_cursor_renderer_new (meta_get_backend ());
+
   udev = udev_new ();
   if (G_UNLIKELY (udev == NULL))
     {
@@ -3384,6 +3387,8 @@ meta_seat_native_get_cursor_renderer (MetaSeatNative     *seat,
             meta_cursor_renderer_native_new (meta_get_backend (),
                                              seat->core_pointer);
           seat->cursor_renderer = META_CURSOR_RENDERER (renderer_native);
+          meta_cursor_renderer_native_set_kms_cursor_renderer (renderer_native,
+                                                               seat->kms_cursor_renderer);
         }
 
       return seat->cursor_renderer;
diff --git a/src/backends/native/meta-seat-native.h b/src/backends/native/meta-seat-native.h
index 3d05973580..0200ad0a48 100644
--- a/src/backends/native/meta-seat-native.h
+++ b/src/backends/native/meta-seat-native.h
@@ -87,6 +87,7 @@ struct _MetaSeatNative
 
   MetaKeymapNative *keymap;
   MetaCursorRenderer *cursor_renderer;
+  MetaKmsCursorRenderer *kms_cursor_renderer;
   GHashTable *tablet_cursors;
 
   GUdevClient *udev_client;
diff --git a/src/meson.build b/src/meson.build
index 84cdabcb6a..3da557f55e 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -674,6 +674,8 @@ if have_native_backend
     'backends/native/meta-kms-crtc-private.h',
     'backends/native/meta-kms-crtc.c',
     'backends/native/meta-kms-crtc.h',
+    'backends/native/meta-kms-cursor-renderer.c',
+    'backends/native/meta-kms-cursor-renderer.h',
     'backends/native/meta-kms-device-private.h',
     'backends/native/meta-kms-device.c',
     'backends/native/meta-kms-device.h',


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]