/* * Copyright (C) 2021 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, see . */ #include "config.h" #include "backends/native/meta-kms-cursor-manager.h" #include #include "backends/native/meta-kms-crtc.h" #include "backends/native/meta-kms-impl.h" #include "backends/native/meta-kms-device-private.h" #include "backends/native/meta-kms-update-private.h" #include "backends/native/meta-thread.h" typedef struct _MetaKmsCursorManagerImpl { MetaKmsImpl *impl; GPtrArray *crtc_states; MetaKmsCursorQueryInImpl cursor_query_in_impl_func; gpointer cursor_query_in_impl_func_user_data; MetaKmsUpdateFilter *update_filter; } MetaKmsCursorManagerImpl; typedef struct _CrtcStateImpl { gatomicrefcount ref_count; MetaKmsCursorManagerImpl *cursor_manager_impl; MetaKmsCrtc *crtc; graphene_rect_t layout; float scale; MetaMonitorTransform transform; MetaDrmBuffer *buffer; graphene_point_t hotspot; gboolean cursor_invalidated; gboolean force_update; gboolean has_cursor; graphene_point_t pending_hotspot; MetaDrmBuffer *pending_buffer; MetaDrmBuffer *active_buffer; MetaDrmBuffer *presenting_buffer; } CrtcStateImpl; struct _MetaKmsCursorManager { GObject parent; MetaKms *kms; }; G_DEFINE_TYPE (MetaKmsCursorManager, meta_kms_cursor_manager, G_TYPE_OBJECT) static GQuark quark_cursor_manager_impl; static CrtcStateImpl * find_crtc_state (MetaKmsCursorManagerImpl *cursor_manager_impl, MetaKmsCrtc *crtc) { int i; if (!cursor_manager_impl->crtc_states) return NULL; for (i = 0; i < cursor_manager_impl->crtc_states->len; i++) { CrtcStateImpl *crtc_state_impl = g_ptr_array_index (cursor_manager_impl->crtc_states, i); if (crtc_state_impl->crtc == crtc) return crtc_state_impl; } return NULL; } static CrtcStateImpl * crtc_state_impl_new (MetaKmsCursorManagerImpl *cursor_manager_impl, MetaKmsCrtc *crtc, graphene_rect_t layout, float scale, MetaDrmBuffer *buffer) { CrtcStateImpl *crtc_state_impl; crtc_state_impl = g_new0 (CrtcStateImpl, 1); g_atomic_ref_count_init (&crtc_state_impl->ref_count); crtc_state_impl->cursor_manager_impl = cursor_manager_impl; crtc_state_impl->crtc = crtc; crtc_state_impl->layout = layout; crtc_state_impl->scale = scale; crtc_state_impl->buffer = buffer; return crtc_state_impl; } static CrtcStateImpl * crtc_state_impl_ref (CrtcStateImpl *crtc_state_impl) { g_atomic_ref_count_inc (&crtc_state_impl->ref_count); return crtc_state_impl; } static void crtc_state_impl_unref (CrtcStateImpl *crtc_state_impl) { if (g_atomic_ref_count_dec (&crtc_state_impl->ref_count)) { g_warn_if_fail (!crtc_state_impl->buffer); g_warn_if_fail (!crtc_state_impl->pending_buffer); g_warn_if_fail (!crtc_state_impl->active_buffer); g_warn_if_fail (!crtc_state_impl->presenting_buffer); g_free (crtc_state_impl); } } static void crtc_state_impl_swap_buffer (CrtcStateImpl *crtc_state_impl, MetaDrmBuffer **from_buffer_ref, MetaDrmBuffer **to_buffer_ref) { MetaDrmBuffer *buffer_to_release; if (*from_buffer_ref == *to_buffer_ref) { buffer_to_release = g_steal_pointer (from_buffer_ref); } else { buffer_to_release = g_steal_pointer (to_buffer_ref); *to_buffer_ref = g_steal_pointer (from_buffer_ref); } if (buffer_to_release) { MetaKmsDevice *device = meta_kms_crtc_get_device (crtc_state_impl->crtc); MetaKms *kms = meta_kms_device_get_kms (device); meta_thread_queue_callback (META_THREAD (kms), NULL, NULL, buffer_to_release, g_object_unref); } } static void crtc_state_impl_notify_presented (CrtcStateImpl *crtc_state_impl) { crtc_state_impl_swap_buffer (crtc_state_impl, &crtc_state_impl->active_buffer, &crtc_state_impl->presenting_buffer); } static void cursor_page_flip_feedback_flipped (MetaKmsCrtc *crtc, unsigned int sequence, unsigned int tv_sec, unsigned int tv_usec, gpointer user_data) { CrtcStateImpl *crtc_state_impl = user_data; crtc_state_impl_notify_presented (crtc_state_impl); } static void cursor_page_flip_feedback_ready (MetaKmsCrtc *crtc, gpointer user_data) { } static void cursor_page_flip_feedback_mode_set_fallback (MetaKmsCrtc *crtc, gpointer user_data) { CrtcStateImpl *crtc_state_impl = user_data; crtc_state_impl_notify_presented (crtc_state_impl); } static void cursor_page_flip_feedback_discarded (MetaKmsCrtc *crtc, gpointer user_data, const GError *error) { } static const MetaKmsPageFlipListenerVtable cursor_page_flip_listener_vtable = { .flipped = cursor_page_flip_feedback_flipped, .ready = cursor_page_flip_feedback_ready, .mode_set_fallback = cursor_page_flip_feedback_mode_set_fallback, .discarded = cursor_page_flip_feedback_discarded, }; static void cursor_result_feedback (const MetaKmsFeedback *feedback, gpointer user_data) { CrtcStateImpl *crtc_state_impl = user_data; switch (meta_kms_feedback_get_result (feedback)) { case META_KMS_FEEDBACK_PASSED: break; case META_KMS_FEEDBACK_FAILED: return; } crtc_state_impl->cursor_invalidated = FALSE; crtc_state_impl_swap_buffer (crtc_state_impl, &crtc_state_impl->pending_buffer, &crtc_state_impl->active_buffer); } static const MetaKmsResultListenerVtable cursor_result_listener_vtable = { .feedback = cursor_result_feedback, }; static gboolean get_current_cursor_position (MetaKmsCursorManagerImpl *cursor_manager_impl, float *x, float *y) { gpointer user_data; if (!cursor_manager_impl->cursor_query_in_impl_func) return FALSE; user_data = cursor_manager_impl->cursor_query_in_impl_func_user_data; cursor_manager_impl->cursor_query_in_impl_func (x, y, user_data); return TRUE; } static gboolean calculate_cursor_rect (CrtcStateImpl *crtc_state_impl, MetaDrmBuffer *buffer, const graphene_point_t *hotspot, float x, float y, graphene_rect_t *out_cursor_rect) { int crtc_x, crtc_y, crtc_width, crtc_height; int buffer_width, buffer_height; graphene_rect_t cursor_rect; crtc_x = (x - crtc_state_impl->layout.origin.x) * crtc_state_impl->scale; crtc_y = (y - crtc_state_impl->layout.origin.y) * crtc_state_impl->scale; crtc_width = roundf (crtc_state_impl->layout.size.width * crtc_state_impl->scale); crtc_height = roundf (crtc_state_impl->layout.size.height * crtc_state_impl->scale); meta_monitor_transform_transform_point (crtc_state_impl->transform, &crtc_width, &crtc_height, &crtc_x, &crtc_y); buffer_width = meta_drm_buffer_get_width (buffer); buffer_height = meta_drm_buffer_get_height (buffer); cursor_rect = (graphene_rect_t) { .origin = { .x = crtc_x - hotspot->x, .y = crtc_y - hotspot->y, }, .size = { .width = buffer_width, .height = buffer_height, }, }; if (cursor_rect.origin.x + cursor_rect.size.width > 0.0 && cursor_rect.origin.y + cursor_rect.size.height > 0.0 && cursor_rect.origin.x < crtc_width && cursor_rect.origin.y < crtc_height) { if (out_cursor_rect) *out_cursor_rect = cursor_rect; return TRUE; } else { return FALSE; } } static MetaKmsUpdate * maybe_update_cursor_plane (MetaKmsCursorManagerImpl *cursor_manager_impl, MetaKmsCrtc *crtc, MetaKmsUpdate *update, MetaDrmBuffer **old_buffer) { MetaKmsImpl *impl = cursor_manager_impl->impl; MetaThreadImpl *thread_impl = META_THREAD_IMPL (impl); MetaKmsDevice *device; CrtcStateImpl *crtc_state_impl; float x, y; MetaDrmBuffer *buffer; const graphene_point_t *hotspot; gboolean should_have_cursor; gboolean did_have_cursor; graphene_rect_t cursor_rect; MetaKmsPlane *cursor_plane; g_assert (old_buffer && !*old_buffer); if (!get_current_cursor_position (cursor_manager_impl, &x, &y)) return update; crtc_state_impl = find_crtc_state (cursor_manager_impl, crtc); g_return_val_if_fail (crtc_state_impl, update); if (!crtc_state_impl->cursor_invalidated) return update; device = meta_kms_crtc_get_device (crtc_state_impl->crtc); buffer = crtc_state_impl->buffer; hotspot = &crtc_state_impl->hotspot; if (buffer) { should_have_cursor = calculate_cursor_rect (crtc_state_impl, buffer, hotspot, x, y, &cursor_rect); } else { should_have_cursor = FALSE; } did_have_cursor = crtc_state_impl->has_cursor; crtc_state_impl->has_cursor = should_have_cursor; if (!should_have_cursor && !did_have_cursor) return update; if (!update) { MetaKmsImplDevice *impl_device = meta_kms_device_get_impl_device (device); update = meta_kms_update_new (device); meta_kms_update_realize (update, impl_device); } cursor_plane = meta_kms_device_get_cursor_plane_for (device, crtc); if (should_have_cursor) { int width, height; MetaFixed16Rectangle src_rect; MtkRectangle dst_rect; MetaKmsAssignPlaneFlag assign_plane_flags = META_KMS_ASSIGN_PLANE_FLAG_NONE; MetaKmsPlaneAssignment *plane_assignment; if (crtc_state_impl->pending_buffer != crtc_state_impl->buffer) { *old_buffer = g_steal_pointer (&crtc_state_impl->pending_buffer); crtc_state_impl->pending_buffer = g_object_ref (buffer); } else { assign_plane_flags |= META_KMS_ASSIGN_PLANE_FLAG_FB_UNCHANGED; } width = meta_drm_buffer_get_width (buffer); height = meta_drm_buffer_get_height (buffer); src_rect = (MetaFixed16Rectangle) { .x = meta_fixed_16_from_int (0), .y = meta_fixed_16_from_int (0), .width = meta_fixed_16_from_int (width), .height = meta_fixed_16_from_int (height), }; dst_rect = (MtkRectangle) { .x = round (cursor_rect.origin.x), .y = round (cursor_rect.origin.y), .width = round (cursor_rect.size.width), .height = round (cursor_rect.size.height), }; plane_assignment = meta_kms_update_assign_plane (update, crtc, cursor_plane, buffer, src_rect, dst_rect, assign_plane_flags); meta_kms_plane_assignment_set_cursor_hotspot (plane_assignment, (int) roundf (hotspot->x), (int) roundf (hotspot->y)); } else { *old_buffer = g_steal_pointer (&crtc_state_impl->pending_buffer); meta_kms_update_unassign_plane (update, crtc, cursor_plane); } meta_kms_update_add_page_flip_listener (update, crtc, &cursor_page_flip_listener_vtable, META_KMS_PAGE_FLIP_LISTENER_FLAG_NONE, meta_thread_impl_get_main_context (thread_impl), crtc_state_impl_ref (crtc_state_impl), (GDestroyNotify) crtc_state_impl_unref); meta_kms_update_add_result_listener (update, &cursor_result_listener_vtable, meta_thread_impl_get_main_context (thread_impl), crtc_state_impl_ref (crtc_state_impl), (GDestroyNotify) crtc_state_impl_unref); return update; } static void free_old_buffers (gpointer retval, const GError *error, gpointer user_data) { GList *old_buffers = retval; g_list_free_full (old_buffers, g_object_unref); } static MetaKmsUpdate * update_filter_cb (MetaKmsImpl *impl, MetaKmsCrtc *crtc, MetaKmsUpdate *update, MetaKmsUpdateFlag flags, gpointer user_data) { MetaThreadImpl *thread_impl = META_THREAD_IMPL (impl); MetaKmsCursorManagerImpl *cursor_manager_impl = user_data; if (flags & META_KMS_UPDATE_FLAG_TEST_ONLY) return update; if (flags & META_KMS_UPDATE_FLAG_MODE_SET) { GPtrArray *crtc_states = cursor_manager_impl->crtc_states; GList *old_buffers = NULL; int i; g_return_val_if_fail (crtc_states, update); for (i = 0; i < crtc_states->len; i++) { CrtcStateImpl *crtc_state_impl = g_ptr_array_index (crtc_states, i); MetaKmsCrtc *crtc = crtc_state_impl->crtc; MetaDrmBuffer *old_buffer = NULL; if (meta_kms_crtc_get_device (crtc) != meta_kms_update_get_device (update)) continue; update = maybe_update_cursor_plane (cursor_manager_impl, crtc, update, &old_buffer); if (old_buffer) old_buffers = g_list_prepend (old_buffers, old_buffer); } if (old_buffers) { meta_thread_queue_callback (meta_thread_impl_get_thread (thread_impl), g_main_context_default (), NULL, old_buffers, (GDestroyNotify) free_old_buffers); } } else { MetaDrmBuffer *old_buffer = NULL; update = maybe_update_cursor_plane (cursor_manager_impl, crtc, update, &old_buffer); if (old_buffer) { meta_thread_queue_callback (meta_thread_impl_get_thread (thread_impl), g_main_context_default (), NULL, old_buffer, g_object_unref); } } return update; } static void meta_kms_cursor_manager_impl_free (MetaKmsCursorManagerImpl *cursor_manager_impl) { g_warn_if_fail (!cursor_manager_impl->crtc_states); g_free (cursor_manager_impl); } static MetaKmsCursorManagerImpl * ensure_cursor_manager_impl (MetaKmsImpl *impl) { MetaKmsCursorManagerImpl *cursor_manager_impl; cursor_manager_impl = g_object_get_qdata (G_OBJECT (impl), quark_cursor_manager_impl); if (!cursor_manager_impl) { cursor_manager_impl = g_new0 (MetaKmsCursorManagerImpl, 1); cursor_manager_impl->impl = impl; g_object_set_qdata (G_OBJECT (impl), quark_cursor_manager_impl, cursor_manager_impl); cursor_manager_impl->update_filter = meta_kms_impl_add_update_filter (impl, update_filter_cb, cursor_manager_impl); } return cursor_manager_impl; } static gpointer finalize_in_impl (MetaThreadImpl *thread_impl, gpointer user_data, GError **error) { MetaKmsImpl *impl = META_KMS_IMPL (thread_impl); MetaKmsCursorManagerImpl *cursor_manager_impl; cursor_manager_impl = g_object_steal_qdata (G_OBJECT (impl), quark_cursor_manager_impl); if (cursor_manager_impl) { GPtrArray *crtc_states; meta_kms_impl_remove_update_filter (impl, cursor_manager_impl->update_filter); crtc_states = g_steal_pointer (&cursor_manager_impl->crtc_states); meta_kms_cursor_manager_impl_free (cursor_manager_impl); return crtc_states; } else { return NULL; } } static void meta_kms_cursor_manager_finalize (GObject *object) { MetaKmsCursorManager *cursor_manager = META_KMS_CURSOR_MANAGER (object); GPtrArray *crtc_states; crtc_states = meta_thread_run_impl_task_sync (META_THREAD (cursor_manager->kms), finalize_in_impl, NULL, NULL); g_clear_pointer (&crtc_states, g_ptr_array_unref); G_OBJECT_CLASS (meta_kms_cursor_manager_parent_class)->finalize (object); } static void meta_kms_cursor_manager_class_init (MetaKmsCursorManagerClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = meta_kms_cursor_manager_finalize; quark_cursor_manager_impl = g_quark_from_static_string ("-meta-kms-cursor-manager-quark"); } static void meta_kms_cursor_manager_init (MetaKmsCursorManager *cursor_manager) { } MetaKmsCursorManager * meta_kms_cursor_manager_new (MetaKms *kms) { MetaKmsCursorManager *cursor_manager; cursor_manager = g_object_new (META_TYPE_KMS_CURSOR_MANAGER, NULL); cursor_manager->kms = kms; return cursor_manager; } typedef struct { MetaKmsCursorQueryInImpl func; gpointer user_data; } SetQueryFuncData; static gpointer set_query_func_in_impl (MetaThreadImpl *thread_impl, gpointer user_data, GError **error) { SetQueryFuncData *data = user_data; MetaKmsCursorManagerImpl *cursor_manager_impl = ensure_cursor_manager_impl (META_KMS_IMPL (thread_impl)); cursor_manager_impl->cursor_query_in_impl_func = data->func; cursor_manager_impl->cursor_query_in_impl_func_user_data = data->user_data; return NULL; } void meta_kms_cursor_manager_set_query_func (MetaKmsCursorManager *cursor_manager, MetaKmsCursorQueryInImpl func, gpointer user_data) { SetQueryFuncData *data; data = g_new0 (SetQueryFuncData, 1); data->func = func; data->user_data = user_data; meta_thread_post_impl_task (META_THREAD (cursor_manager->kms), set_query_func_in_impl, data, g_free, NULL, NULL); } static gpointer position_changed_in_impl (MetaThreadImpl *thread_impl, gpointer user_data, GError **error) { MetaKmsCursorManagerImpl *cursor_manager_impl = ensure_cursor_manager_impl (META_KMS_IMPL (thread_impl)); const graphene_point_t *position = user_data; GPtrArray *crtc_states; int i; crtc_states = cursor_manager_impl->crtc_states; g_return_val_if_fail (crtc_states, NULL); for (i = 0; i < crtc_states->len; i++) { CrtcStateImpl *crtc_state_impl = g_ptr_array_index (crtc_states, i); MetaDrmBuffer *buffer; const graphene_point_t *hotspot; gboolean did_have_cursor; gboolean should_have_cursor; buffer = crtc_state_impl->buffer; hotspot = &crtc_state_impl->hotspot; if (buffer) { should_have_cursor = calculate_cursor_rect (crtc_state_impl, buffer, hotspot, position->x, position->y, NULL); } else { should_have_cursor = FALSE; } did_have_cursor = crtc_state_impl->has_cursor; if (did_have_cursor != should_have_cursor || should_have_cursor) { MetaKmsCrtc *crtc = crtc_state_impl->crtc; MetaKmsDevice *device = meta_kms_crtc_get_device (crtc); MetaKmsImplDevice *impl_device = meta_kms_device_get_impl_device (device); crtc_state_impl->cursor_invalidated = TRUE; meta_kms_impl_device_schedule_process (impl_device, crtc_state_impl->crtc); } } return NULL; } static graphene_point_t * copy_point (const graphene_point_t *point) { return graphene_point_init_from_point (graphene_point_alloc (), point); } void meta_kms_cursor_manager_position_changed_in_input_impl (MetaKmsCursorManager *cursor_manager, const graphene_point_t *position) { meta_thread_post_impl_task (META_THREAD (cursor_manager->kms), position_changed_in_impl, copy_point (position), (GDestroyNotify) graphene_point_free, NULL, NULL); } typedef struct { MetaKmsCrtc *crtc; MetaDrmBuffer *buffer; MetaMonitorTransform transform; graphene_point_t hotspot; } UpdateSpriteData; static gpointer update_sprite_in_impl (MetaThreadImpl *thread_impl, gpointer user_data, GError **error) { MetaKmsCursorManagerImpl *cursor_manager_impl = ensure_cursor_manager_impl (META_KMS_IMPL (thread_impl)); UpdateSpriteData *data = user_data; MetaKmsCrtc *crtc = data->crtc; MetaKmsDevice *device = meta_kms_crtc_get_device (crtc); MetaKmsImplDevice *impl_device = meta_kms_device_get_impl_device (device); CrtcStateImpl *crtc_state_impl; MetaDrmBuffer *old_buffer; crtc_state_impl = find_crtc_state (cursor_manager_impl, crtc); g_return_val_if_fail (crtc_state_impl, NULL); old_buffer = g_steal_pointer (&crtc_state_impl->buffer); crtc_state_impl->buffer = g_steal_pointer (&data->buffer); crtc_state_impl->transform = data->transform; crtc_state_impl->hotspot = data->hotspot; crtc_state_impl->cursor_invalidated = TRUE; meta_kms_impl_device_schedule_process (impl_device, crtc_state_impl->crtc); if (old_buffer) { MetaThread *thread = meta_thread_impl_get_thread (thread_impl); meta_thread_queue_callback (thread, NULL, NULL, old_buffer, g_object_unref); } return NULL; } void meta_kms_cursor_manager_update_sprite (MetaKmsCursorManager *cursor_manager, MetaKmsCrtc *crtc, MetaDrmBuffer *buffer, MetaMonitorTransform transform, const graphene_point_t *hotspot) { UpdateSpriteData *data; data = g_new0 (UpdateSpriteData, 1); data->crtc = crtc; data->buffer = buffer ? g_object_ref (buffer) : NULL; data->transform = transform; if (hotspot) data->hotspot = *hotspot; meta_thread_post_impl_task (META_THREAD (cursor_manager->kms), update_sprite_in_impl, data, g_free, NULL, NULL); } static void crtc_state_impl_clear_in_main (CrtcStateImpl *crtc_state_impl) { g_clear_object (&crtc_state_impl->buffer); g_clear_object (&crtc_state_impl->pending_buffer); g_clear_object (&crtc_state_impl->active_buffer); g_clear_object (&crtc_state_impl->presenting_buffer); crtc_state_impl_unref (crtc_state_impl); } static void clear_crtc_states_in_impl (MetaThreadImpl *thread_impl, GPtrArray *crtc_states) { MetaThread *thread = meta_thread_impl_get_thread (thread_impl); meta_thread_queue_callback (thread, NULL, NULL, crtc_states, (GDestroyNotify) g_ptr_array_unref); } static gpointer update_viewports_in_impl (MetaThreadImpl *thread_impl, gpointer user_data, GError **error) { MetaKmsCursorManagerImpl *cursor_manager_impl = ensure_cursor_manager_impl (META_KMS_IMPL (thread_impl)); GArray *layouts = user_data; GPtrArray *crtc_states; int i; crtc_states = g_ptr_array_new_full (layouts->len, (GDestroyNotify) crtc_state_impl_clear_in_main); for (i = 0; i < layouts->len; i++) { MetaKmsCrtcLayout *crtc_layout = &g_array_index (layouts, MetaKmsCrtcLayout, i); CrtcStateImpl *crtc_state_impl; CrtcStateImpl *old_crtc_state; old_crtc_state = find_crtc_state (cursor_manager_impl, crtc_layout->crtc); if (old_crtc_state) { crtc_state_impl = crtc_state_impl_new (cursor_manager_impl, crtc_layout->crtc, crtc_layout->layout, crtc_layout->scale, g_steal_pointer (&old_crtc_state->buffer)); } else { crtc_state_impl = crtc_state_impl_new (cursor_manager_impl, crtc_layout->crtc, crtc_layout->layout, crtc_layout->scale, NULL); } crtc_state_impl->cursor_invalidated = TRUE; g_ptr_array_add (crtc_states, crtc_state_impl); } if (cursor_manager_impl->crtc_states) clear_crtc_states_in_impl (thread_impl, cursor_manager_impl->crtc_states); cursor_manager_impl->crtc_states = crtc_states; return NULL; } void meta_kms_cursor_manager_update_crtc_layout (MetaKmsCursorManager *cursor_manager, GArray *layouts) { meta_thread_post_impl_task (META_THREAD (cursor_manager->kms), update_viewports_in_impl, g_array_ref (layouts), (GDestroyNotify) g_array_unref, NULL, NULL); }