mutter/src/backends/native/meta-cursor-renderer-native.c
Daniel van Vugt 8d514095cb cursor-renderer-native: Floor the cursor position instead of rounding
Which eliminates the 1px jitter that was visible when dragging windows,
and eliminates the flickering that was visible when pushing the cursor
against the right/bottom edges of the screen.
2018-12-13 17:12:28 +08:00

1210 lines
38 KiB
C

/* -*- 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>
*/
#include "config.h"
#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-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;
};
struct _MetaCursorRendererNativePrivate
{
MetaMonitorManager *monitor_manager;
gboolean hw_state_invalidated;
gboolean has_hw_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
{
guint active_bo;
MetaCursorGbmBoState pending_bo_state;
struct gbm_bo *bos[HW_CURSOR_BUFFER_COUNT];
} MetaCursorNativeGpuState;
typedef struct _MetaCursorNativePrivate
{
GHashTable *gpu_states;
} 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 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)
{
MetaCursorRendererNative *renderer = META_CURSOR_RENDERER_NATIVE (object);
MetaCursorRendererNativePrivate *priv =
meta_cursor_renderer_native_get_instance_private (renderer);
if (priv->animation_timeout_id)
g_source_remove (priv->animation_timeout_id);
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
set_crtc_cursor (MetaCursorRendererNative *native,
MetaCrtc *crtc,
MetaCursorSprite *cursor_sprite)
{
MetaCursorRendererNativePrivate *priv =
meta_cursor_renderer_native_get_instance_private (native);
MetaCursorRendererNativeGpuData *cursor_renderer_gpu_data;
MetaGpuKms *gpu_kms;
int kms_fd;
gpu_kms = META_GPU_KMS (meta_crtc_get_gpu (crtc));
cursor_renderer_gpu_data =
meta_cursor_renderer_native_gpu_data_from_gpu (gpu_kms);
kms_fd = meta_gpu_kms_get_fd (gpu_kms);
if (cursor_sprite)
{
MetaCursorNativePrivate *cursor_priv;
MetaCursorNativeGpuState *cursor_gpu_state;
struct gbm_bo *bo;
union gbm_bo_handle handle;
int hot_x, hot_y;
cursor_priv = get_cursor_priv (cursor_sprite);
cursor_gpu_state = get_cursor_gpu_state (cursor_priv, gpu_kms);
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);
if (!priv->hw_state_invalidated && bo == crtc->cursor_renderer_private)
return;
crtc->cursor_renderer_private = bo;
handle = gbm_bo_get_handle (bo);
meta_cursor_sprite_get_hotspot (cursor_sprite, &hot_x, &hot_y);
if (drmModeSetCursor2 (kms_fd, crtc->crtc_id, handle.u32,
cursor_renderer_gpu_data->cursor_width,
cursor_renderer_gpu_data->cursor_height,
hot_x, hot_y) < 0)
{
if (errno != EACCES)
{
g_warning ("drmModeSetCursor2 failed with (%s), "
"drawing cursor with OpenGL from now on",
strerror (errno));
priv->has_hw_cursor = FALSE;
cursor_renderer_gpu_data->hw_cursor_broken = TRUE;
}
}
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;
}
}
else
{
if (priv->hw_state_invalidated || crtc->cursor_renderer_private != NULL)
{
drmModeSetCursor2 (kms_fd, crtc->crtc_id, 0, 0, 0, 0, 0);
crtc->cursor_renderer_private = NULL;
}
}
}
typedef struct
{
MetaCursorRendererNative *in_cursor_renderer_native;
MetaLogicalMonitor *in_logical_monitor;
ClutterRect in_local_cursor_rect;
MetaCursorSprite *in_cursor_sprite;
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;
ClutterRect 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;
meta_monitor_calculate_crtc_pos (monitor, monitor_mode,
monitor_crtc_mode->output,
META_MONITOR_TRANSFORM_NORMAL,
&crtc_x, &crtc_y);
transform = meta_logical_monitor_get_transform (data->in_logical_monitor);
if (meta_monitor_transform_is_rotated (transform))
{
crtc_width = monitor_crtc_mode->crtc_mode->height;
crtc_height = monitor_crtc_mode->crtc_mode->width;
}
else
{
crtc_width = monitor_crtc_mode->crtc_mode->width;
crtc_height = monitor_crtc_mode->crtc_mode->height;
}
scaled_crtc_rect = (ClutterRect) {
.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 &&
clutter_rect_intersection (&scaled_crtc_rect,
&data->in_local_cursor_rect,
NULL))
{
MetaGpuKms *gpu_kms;
int kms_fd;
float crtc_cursor_x, crtc_cursor_y;
set_crtc_cursor (data->in_cursor_renderer_native,
crtc,
data->in_cursor_sprite);
gpu_kms = META_GPU_KMS (meta_monitor_get_gpu (monitor));
kms_fd = meta_gpu_kms_get_fd (gpu_kms);
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;
drmModeMoveCursor (kms_fd,
crtc->crtc_id,
floorf (crtc_cursor_x),
floorf (crtc_cursor_y));
data->out_painted = data->out_painted || TRUE;
}
else
{
set_crtc_cursor (data->in_cursor_renderer_native, crtc, NULL);
}
return 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);
MetaMonitorManager *monitor_manager = priv->monitor_manager;
GList *logical_monitors;
GList *l;
ClutterRect rect;
gboolean painted = FALSE;
if (cursor_sprite)
rect = meta_cursor_renderer_calculate_rect (renderer, cursor_sprite);
else
rect = (ClutterRect) { 0 };
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 = (ClutterRect) {
.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
};
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;
}
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 gboolean
cursor_over_transformed_logical_monitor (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);
MetaMonitorManager *monitor_manager = priv->monitor_manager;
GList *logical_monitors;
GList *l;
ClutterRect 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;
ClutterRect logical_monitor_rect;
MetaMonitorTransform transform;
GList *monitors, *l_mon;
logical_monitor_layout =
meta_logical_monitor_get_layout (logical_monitor);
logical_monitor_rect =
meta_rectangle_to_clutter_rect (&logical_monitor_layout);
if (!clutter_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;
transform = meta_logical_monitor_get_transform (logical_monitor);
/* Get transform corrected for LCD panel-orientation. */
transform = meta_monitor_logical_to_crtc_transform (monitor, transform);
if (transform != META_MONITOR_TRANSFORM_NORMAL)
return TRUE;
}
}
return FALSE;
}
static float
calculate_cursor_crtc_sprite_scale (MetaCursorSprite *cursor_sprite,
MetaLogicalMonitor *logical_monitor)
{
return (meta_logical_monitor_get_scale (logical_monitor) *
meta_cursor_sprite_get_texture_scale (cursor_sprite));
}
static gboolean
can_draw_cursor_unscaled (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);
MetaMonitorManager *monitor_manager = priv->monitor_manager;
ClutterRect cursor_rect;
GList *logical_monitors;
GList *l;
gboolean has_visible_crtc_sprite = FALSE;
if (!meta_is_stage_views_scaled ())
return meta_cursor_sprite_get_texture_scale (cursor_sprite) == 1.0;
logical_monitors =
meta_monitor_manager_get_logical_monitors (monitor_manager);
if (!logical_monitors)
return FALSE;
cursor_rect = meta_cursor_renderer_calculate_rect (renderer, cursor_sprite);
for (l = logical_monitors; l; l = l->next)
{
MetaLogicalMonitor *logical_monitor = l->data;
ClutterRect logical_monitor_rect =
meta_rectangle_to_clutter_rect (&logical_monitor->rect);
if (!clutter_rect_intersection (&cursor_rect,
&logical_monitor_rect,
NULL))
continue;
if (calculate_cursor_crtc_sprite_scale (cursor_sprite,
logical_monitor) != 1.0)
return FALSE;
has_visible_crtc_sprite = TRUE;
}
return has_visible_crtc_sprite;
}
static gboolean
should_have_hw_cursor (MetaCursorRenderer *renderer,
MetaCursorSprite *cursor_sprite,
GList *gpus)
{
GList *l;
CoglTexture *texture;
if (!cursor_sprite)
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 (cursor_over_transformed_logical_monitor (renderer, cursor_sprite))
return FALSE;
texture = meta_cursor_sprite_get_cogl_texture (cursor_sprite);
if (!texture)
return FALSE;
if (!can_draw_cursor_unscaled (renderer, cursor_sprite))
return FALSE;
return TRUE;
}
static gboolean
meta_cursor_renderer_native_update_animation (MetaCursorRendererNative *native)
{
MetaCursorRendererNativePrivate *priv =
meta_cursor_renderer_native_get_instance_private (native);
MetaCursorRenderer *renderer = META_CURSOR_RENDERER (native);
MetaCursorSprite *cursor_sprite = meta_cursor_renderer_get_cursor (renderer);
priv->animation_timeout_id = 0;
meta_cursor_sprite_tick_frame (cursor_sprite);
meta_cursor_renderer_force_update (renderer);
return G_SOURCE_REMOVE;
}
static void
maybe_schedule_cursor_sprite_animation_frame (MetaCursorRendererNative *native,
MetaCursorSprite *cursor_sprite)
{
MetaCursorRendererNativePrivate *priv =
meta_cursor_renderer_native_get_instance_private (native);
gboolean cursor_change;
guint delay;
cursor_change = cursor_sprite != priv->last_cursor;
priv->last_cursor = cursor_sprite;
if (!cursor_change && priv->animation_timeout_id)
return;
if (priv->animation_timeout_id)
{
g_source_remove (priv->animation_timeout_id);
priv->animation_timeout_id = 0;
}
if (cursor_sprite && meta_cursor_sprite_is_animated (cursor_sprite))
{
delay = meta_cursor_sprite_get_current_frame_time (cursor_sprite);
if (delay == 0)
return;
priv->animation_timeout_id =
g_timeout_add (delay,
(GSourceFunc) meta_cursor_renderer_native_update_animation,
native);
g_source_set_name_by_id (priv->animation_timeout_id,
"[mutter] meta_cursor_renderer_native_update_animation");
}
}
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);
MetaMonitorManager *monitor_manager = priv->monitor_manager;
GList *gpus = NULL;
GList *logical_monitors;
GList *l;
ClutterRect 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;
ClutterRect logical_monitor_rect;
GList *monitors, *l_mon;
logical_monitor_layout =
meta_logical_monitor_get_layout (logical_monitor);
logical_monitor_rect =
meta_rectangle_to_clutter_rect (&logical_monitor_layout);
if (!clutter_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)
{
MetaCursorRendererNative *native = META_CURSOR_RENDERER_NATIVE (renderer);
MetaCursorRendererNativePrivate *priv =
meta_cursor_renderer_native_get_instance_private (native);
g_autoptr (GList) gpus = NULL;
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);
}
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
cursor_gpu_state_free (MetaCursorNativeGpuState *cursor_gpu_state)
{
int i;
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);
g_hash_table_insert (cursor_priv->gpu_states, gpu_kms, cursor_gpu_state);
return cursor_gpu_state;
}
static void
on_cursor_sprite_texture_changed (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
cursor_priv_free (MetaCursorNativePrivate *cursor_priv)
{
g_hash_table_destroy (cursor_priv->gpu_states);
}
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);
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)\n",
(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\n");
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\n", 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 ();
}
#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;
uint32_t gbm_format;
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))
return;
texture = meta_cursor_sprite_get_cogl_texture (cursor_sprite);
width = cogl_texture_get_width (texture);
height = cogl_texture_get_height (texture);
buffer = meta_cursor_sprite_wayland_get_buffer (sprite_wayland);
if (!buffer)
return;
buffer_resource = meta_wayland_buffer_get_resource (buffer);
if (!buffer_resource)
return;
shm_buffer = wl_shm_buffer_get (buffer_resource);
if (shm_buffer)
{
int rowstride = wl_shm_buffer_get_stride (shm_buffer);
uint8_t *buffer_data;
wl_shm_buffer_begin_access (shm_buffer);
switch (wl_shm_buffer_get_format (shm_buffer))
{
#if G_BYTE_ORDER == G_BIG_ENDIAN
case WL_SHM_FORMAT_ARGB8888:
gbm_format = GBM_FORMAT_ARGB8888;
break;
case WL_SHM_FORMAT_XRGB8888:
gbm_format = GBM_FORMAT_XRGB8888;
break;
#else
case WL_SHM_FORMAT_ARGB8888:
gbm_format = GBM_FORMAT_ARGB8888;
break;
case WL_SHM_FORMAT_XRGB8888:
gbm_format = GBM_FORMAT_XRGB8888;
break;
#endif
default:
g_warn_if_reached ();
gbm_format = GBM_FORMAT_ARGB8888;
}
buffer_data = wl_shm_buffer_get_data (shm_buffer);
load_cursor_sprite_gbm_buffer_for_gpu (native,
gpu_kms,
cursor_sprite,
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;
if (width != cursor_width || height != cursor_height)
{
meta_warning ("Invalid cursor size (must be 64x64), falling back to software (GL) cursors\n");
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\n");
return;
}
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;
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))
return;
xc_image = meta_cursor_sprite_xcursor_get_current_image (sprite_xcursor);
load_cursor_sprite_gbm_buffer_for_gpu (native,
gpu_kms,
cursor_sprite,
(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);
}
}
static void
meta_cursor_renderer_native_class_init (MetaCursorRendererNativeClass *klass)
{
MetaCursorRendererClass *renderer_class = META_CURSOR_RENDERER_CLASS (klass);
GObjectClass *object_class = G_OBJECT_CLASS (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)
{
MetaCursorRenderer *renderer = META_CURSOR_RENDERER (native);
MetaCursorRendererNativePrivate *priv =
meta_cursor_renderer_native_get_instance_private (native);
priv->hw_state_invalidated = TRUE;
meta_cursor_renderer_force_update (renderer);
}
static void
on_monitors_changed (MetaMonitorManager *monitors,
MetaCursorRendererNative *native)
{
force_update_hw_cursor (native);
}
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_monitor_manager_get_gpus (priv->monitor_manager);
for (l = gpus; l; l = l->next)
{
MetaGpuKms *gpu_kms = l->data;
MetaCursorRendererNativeGpuData *cursor_renderer_gpu_data;
int kms_fd;
struct gbm_device *gbm_device;
uint64_t width, height;
gbm_device = meta_gbm_device_from_gpu (gpu_kms);
if (!gbm_device)
continue;
cursor_renderer_gpu_data =
meta_create_cursor_renderer_native_gpu_data (gpu_kms);
kms_fd = meta_gpu_kms_get_fd (gpu_kms);
if (drmGetCap (kms_fd, DRM_CAP_CURSOR_WIDTH, &width) == 0 &&
drmGetCap (kms_fd, DRM_CAP_CURSOR_HEIGHT, &height) == 0)
{
cursor_renderer_gpu_data->cursor_width = width;
cursor_renderer_gpu_data->cursor_height = height;
}
else
{
cursor_renderer_gpu_data->cursor_width = 64;
cursor_renderer_gpu_data->cursor_height = 64;
}
}
}
MetaCursorRendererNative *
meta_cursor_renderer_native_new (MetaBackend *backend)
{
MetaMonitorManager *monitor_manager =
meta_backend_get_monitor_manager (backend);
MetaCursorRendererNative *cursor_renderer_native;
MetaCursorRendererNativePrivate *priv;
cursor_renderer_native =
g_object_new (META_TYPE_CURSOR_RENDERER_NATIVE, NULL);
priv =
meta_cursor_renderer_native_get_instance_private (cursor_renderer_native);
g_signal_connect_object (monitor_manager, "monitors-changed-internal",
G_CALLBACK (on_monitors_changed),
cursor_renderer_native, 0);
priv->monitor_manager = monitor_manager;
priv->hw_state_invalidated = TRUE;
init_hw_cursor_support (cursor_renderer_native);
return cursor_renderer_native;
}
static void
meta_cursor_renderer_native_init (MetaCursorRendererNative *native)
{
}