From a0b1e3f858423078986337279fbaadc49e513abd Mon Sep 17 00:00:00 2001 From: CentOS Sources Date: Thu, 18 Mar 2021 04:13:43 +0000 Subject: [PATCH] import mutter-3.32.2-57.el8 --- .../0001-clutter-Backport-of-touch-mode.patch | 357 +++ SOURCES/geometric-picking.patch | 2091 +++++++++++++++++ SPECS/mutter.spec | 16 +- 3 files changed, 2463 insertions(+), 1 deletion(-) create mode 100644 SOURCES/0001-clutter-Backport-of-touch-mode.patch create mode 100644 SOURCES/geometric-picking.patch diff --git a/SOURCES/0001-clutter-Backport-of-touch-mode.patch b/SOURCES/0001-clutter-Backport-of-touch-mode.patch new file mode 100644 index 0000000..dff32fa --- /dev/null +++ b/SOURCES/0001-clutter-Backport-of-touch-mode.patch @@ -0,0 +1,357 @@ +From 2a2e870c139e2130b00d582546616269bca27458 Mon Sep 17 00:00:00 2001 +From: Carlos Garnacho +Date: Fri, 4 Sep 2020 17:11:36 +0200 +Subject: [PATCH] clutter: Backport of ::touch-mode + +In upstream/master this is a ClutterSeat readonly property. Add it to +ClutterDeviceManager here, the mechanism and triggering is the same +though. +--- + clutter/clutter/clutter-device-manager.c | 24 +++ + clutter/clutter/clutter-device-manager.h | 2 + + .../evdev/clutter-device-manager-evdev.c | 179 ++++++++++++++++++ + 3 files changed, 205 insertions(+) + +diff --git a/clutter/clutter/clutter-device-manager.c b/clutter/clutter/clutter-device-manager.c +index c676384..e1cc455 100644 +--- a/clutter/clutter/clutter-device-manager.c ++++ b/clutter/clutter/clutter-device-manager.c +@@ -62,6 +62,7 @@ enum + PROP_0, + + PROP_BACKEND, ++ PROP_TOUCH_MODE, + + PROP_LAST + }; +@@ -108,6 +109,7 @@ clutter_device_manager_set_property (GObject *gobject, + priv->backend = g_value_get_object (value); + break; + ++ case PROP_TOUCH_MODE: + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); + } +@@ -127,6 +129,10 @@ clutter_device_manager_get_property (GObject *gobject, + g_value_set_object (value, priv->backend); + break; + ++ case PROP_TOUCH_MODE: ++ g_value_set_boolean (value, FALSE); ++ break; ++ + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); + } +@@ -143,6 +149,12 @@ clutter_device_manager_class_init (ClutterDeviceManagerClass *klass) + P_("The ClutterBackend of the device manager"), + CLUTTER_TYPE_BACKEND, + CLUTTER_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); ++ obj_props[PROP_TOUCH_MODE] = ++ g_param_spec_boolean ("touch-mode", ++ P_("Touch mode"), ++ P_("Touch mode"), ++ FALSE, ++ CLUTTER_PARAM_READABLE); + + gobject_class->set_property = clutter_device_manager_set_property; + gobject_class->get_property = clutter_device_manager_get_property; +@@ -579,3 +591,15 @@ clutter_device_manager_get_kbd_a11y_settings (ClutterDeviceManager *device_man + + *settings = device_manager->priv->kbd_a11y_settings; + } ++ ++gboolean ++clutter_device_manager_get_touch_mode (ClutterDeviceManager *device_manager) ++{ ++ gboolean touch_mode; ++ ++ g_return_val_if_fail (CLUTTER_IS_DEVICE_MANAGER (device_manager), FALSE); ++ ++ g_object_get (G_OBJECT (device_manager), "touch-mode", &touch_mode, NULL); ++ ++ return touch_mode; ++} +diff --git a/clutter/clutter/clutter-device-manager.h b/clutter/clutter/clutter-device-manager.h +index 1cbf030..a4a6271 100644 +--- a/clutter/clutter/clutter-device-manager.h ++++ b/clutter/clutter/clutter-device-manager.h +@@ -155,6 +155,8 @@ void clutter_device_manager_set_kbd_a11y_settings (ClutterDeviceManager *devic + CLUTTER_EXPORT + void clutter_device_manager_get_kbd_a11y_settings (ClutterDeviceManager *device_manager, + ClutterKbdA11ySettings *settings); ++CLUTTER_EXPORT ++gboolean clutter_device_manager_get_touch_mode (ClutterDeviceManager *device_manager); + + G_END_DECLS + +diff --git a/clutter/clutter/evdev/clutter-device-manager-evdev.c b/clutter/clutter/evdev/clutter-device-manager-evdev.c +index 84b0aad..78b5b64 100644 +--- a/clutter/clutter/evdev/clutter-device-manager-evdev.c ++++ b/clutter/clutter/evdev/clutter-device-manager-evdev.c +@@ -108,6 +108,19 @@ struct _ClutterDeviceManagerEvdevPrivate + + gint device_id_next; + GList *free_device_ids; ++ ++ guint tablet_mode_switch_state : 1; ++ guint has_touchscreen : 1; ++ guint has_tablet_switch : 1; ++ guint has_pointer : 1; ++ guint touch_mode : 1; ++}; ++ ++enum ++{ ++ PROP_0, ++ PROP_TOUCH_MODE, ++ N_PROPS + }; + + static void clutter_device_manager_evdev_event_extender_init (ClutterEventExtenderInterface *iface); +@@ -765,6 +778,34 @@ clutter_event_source_free (ClutterEventSource *source) + g_source_unref (g_source); + } + ++static void ++update_touch_mode (ClutterDeviceManagerEvdev *manager_evdev) ++{ ++ ClutterDeviceManagerEvdevPrivate *priv = manager_evdev->priv; ++ gboolean touch_mode; ++ ++ /* No touch mode if we don't have a touchscreen, easy */ ++ if (!priv->has_touchscreen) ++ touch_mode = FALSE; ++ /* If we have a tablet mode switch, honor it being unset */ ++ else if (priv->has_tablet_switch && !priv->tablet_mode_switch_state) ++ touch_mode = FALSE; ++ /* If tablet mode is enabled, go for it */ ++ else if (priv->has_tablet_switch && priv->tablet_mode_switch_state) ++ touch_mode = TRUE; ++ /* If there is no tablet mode switch (eg. kiosk machines), ++ * assume touch-mode is mutually exclusive with pointers. ++ */ ++ else ++ touch_mode = !priv->has_pointer; ++ ++ if (priv->touch_mode != touch_mode) ++ { ++ priv->touch_mode = touch_mode; ++ g_object_notify (G_OBJECT (manager_evdev), "touch-mode"); ++ } ++} ++ + static void + evdev_add_device (ClutterDeviceManagerEvdev *manager_evdev, + struct libinput_device *libinput_device) +@@ -942,19 +983,81 @@ flush_event_queue (void) + } + } + ++static gboolean ++has_touchscreen (ClutterDeviceManagerEvdev *manager_evdev) ++{ ++ ClutterDeviceManagerEvdevPrivate *priv = manager_evdev->priv; ++ GSList *l; ++ ++ for (l = priv->devices; l; l = l->next) ++ { ++ ClutterInputDeviceType device_type; ++ ++ device_type = clutter_input_device_get_device_type (l->data); ++ ++ if (device_type == CLUTTER_TOUCHSCREEN_DEVICE) ++ return TRUE; ++ } ++ ++ return FALSE; ++} ++ ++static gboolean ++device_type_is_pointer (ClutterInputDeviceType device_type) ++{ ++ return (device_type == CLUTTER_POINTER_DEVICE || ++ device_type == CLUTTER_TOUCHPAD_DEVICE); ++} ++ ++static gboolean ++has_pointer (ClutterDeviceManagerEvdev *manager_evdev) ++{ ++ ClutterDeviceManagerEvdevPrivate *priv = manager_evdev->priv; ++ GSList *l; ++ ++ for (l = priv->devices; l; l = l->next) ++ { ++ ClutterInputDeviceType device_type; ++ ++ device_type = clutter_input_device_get_device_type (l->data); ++ ++ if (device_type_is_pointer (device_type)) ++ return TRUE; ++ } ++ ++ return FALSE; ++} ++ + static gboolean + process_base_event (ClutterDeviceManagerEvdev *manager_evdev, + struct libinput_event *event) + { ++ ClutterDeviceManagerEvdevPrivate *priv = manager_evdev->priv; + ClutterInputDevice *device; + struct libinput_device *libinput_device; + gboolean handled = TRUE; ++ gboolean check_touch_mode; + + switch (libinput_event_get_type (event)) + { + case LIBINPUT_EVENT_DEVICE_ADDED: + libinput_device = libinput_event_get_device (event); + ++ priv->has_touchscreen |= ++ libinput_device_has_capability (libinput_device, LIBINPUT_DEVICE_CAP_TOUCH); ++ priv->has_pointer |= ++ libinput_device_has_capability (libinput_device, LIBINPUT_DEVICE_CAP_POINTER); ++ check_touch_mode = priv->has_touchscreen | priv->has_pointer; ++ ++ if (libinput_device_has_capability (libinput_device, ++ LIBINPUT_DEVICE_CAP_SWITCH) && ++ libinput_device_switch_has_switch (libinput_device, ++ LIBINPUT_SWITCH_TABLET_MODE)) ++ { ++ priv->has_tablet_switch = TRUE; ++ check_touch_mode = TRUE; ++ } ++ + evdev_add_device (manager_evdev, libinput_device); + break; + +@@ -966,7 +1069,17 @@ process_base_event (ClutterDeviceManagerEvdev *manager_evdev, + + libinput_device = libinput_event_get_device (event); + ++ check_touch_mode = ++ libinput_device_has_capability (libinput_device, LIBINPUT_DEVICE_CAP_TOUCH); + device = libinput_device_get_user_data (libinput_device); ++ if (check_touch_mode) ++ priv->has_touchscreen = has_touchscreen (manager_evdev); ++ if (device_type_is_pointer (clutter_input_device_get_device_type (device))) ++ { ++ priv->has_pointer = has_pointer (manager_evdev); ++ check_touch_mode = TRUE; ++ } ++ + evdev_remove_device (manager_evdev, + CLUTTER_INPUT_DEVICE_EVDEV (device)); + break; +@@ -975,6 +1088,9 @@ process_base_event (ClutterDeviceManagerEvdev *manager_evdev, + handled = FALSE; + } + ++ if (check_touch_mode) ++ update_touch_mode (manager_evdev); ++ + return handled; + } + +@@ -1752,6 +1868,23 @@ process_device_event (ClutterDeviceManagerEvdev *manager_evdev, + notify_pad_ring (device, time, number, source, group, mode, angle); + break; + } ++ case LIBINPUT_EVENT_SWITCH_TOGGLE: ++ { ++ ClutterDeviceManagerEvdevPrivate *priv = manager_evdev->priv; ++ struct libinput_event_switch *switch_event = ++ libinput_event_get_switch_event (event); ++ enum libinput_switch sw = ++ libinput_event_switch_get_switch (switch_event); ++ enum libinput_switch_state state = ++ libinput_event_switch_get_switch_state (switch_event); ++ ++ if (sw == LIBINPUT_SWITCH_TABLET_MODE) ++ { ++ priv->tablet_mode_switch_state = (state == LIBINPUT_SWITCH_STATE_ON); ++ update_touch_mode (manager_evdev); ++ } ++ break; ++ } + default: + handled = FALSE; + } +@@ -1967,6 +2100,10 @@ clutter_device_manager_evdev_constructed (GObject *gobject) + + source = clutter_event_source_new (manager_evdev); + priv->event_source = source; ++ ++ priv->has_touchscreen = has_touchscreen (manager_evdev); ++ priv->has_pointer = has_pointer (manager_evdev); ++ update_touch_mode (manager_evdev); + } + + static void +@@ -2001,6 +2138,43 @@ clutter_device_manager_evdev_dispose (GObject *object) + G_OBJECT_CLASS (clutter_device_manager_evdev_parent_class)->dispose (object); + } + ++static void ++clutter_device_manager_evdev_set_property (GObject *object, ++ guint prop_id, ++ const GValue *value, ++ GParamSpec *pspec) ++{ ++ switch (prop_id) ++ { ++ case PROP_TOUCH_MODE: ++ default: ++ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); ++ } ++} ++ ++static void ++clutter_device_manager_evdev_get_property (GObject *object, ++ guint prop_id, ++ GValue *value, ++ GParamSpec *pspec) ++{ ++ ClutterDeviceManagerEvdev *manager_evdev; ++ ClutterDeviceManagerEvdevPrivate *priv; ++ ++ manager_evdev = CLUTTER_DEVICE_MANAGER_EVDEV (object); ++ priv = manager_evdev->priv; ++ ++ switch (prop_id) ++ { ++ case PROP_TOUCH_MODE: ++ g_value_set_boolean (value, priv->touch_mode); ++ break; ++ default: ++ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); ++ } ++} ++ ++ + static void + clutter_device_manager_evdev_finalize (GObject *object) + { +@@ -2036,6 +2210,8 @@ clutter_device_manager_evdev_class_init (ClutterDeviceManagerEvdevClass *klass) + gobject_class->constructed = clutter_device_manager_evdev_constructed; + gobject_class->finalize = clutter_device_manager_evdev_finalize; + gobject_class->dispose = clutter_device_manager_evdev_dispose; ++ gobject_class->set_property = clutter_device_manager_evdev_set_property; ++ gobject_class->get_property = clutter_device_manager_evdev_get_property; + + manager_class = CLUTTER_DEVICE_MANAGER_CLASS (klass); + manager_class->add_device = clutter_device_manager_evdev_add_device; +@@ -2047,6 +2223,9 @@ clutter_device_manager_evdev_class_init (ClutterDeviceManagerEvdevClass *klass) + manager_class->get_supported_virtual_device_types = clutter_device_manager_evdev_get_supported_virtual_device_types; + manager_class->compress_motion = clutter_device_manager_evdev_compress_motion; + manager_class->apply_kbd_a11y_settings = clutter_device_manager_evdev_apply_kbd_a11y_settings; ++ ++ g_object_class_override_property (gobject_class, PROP_TOUCH_MODE, ++ "touch-mode"); + } + + static void +-- +2.29.2 + diff --git a/SOURCES/geometric-picking.patch b/SOURCES/geometric-picking.patch new file mode 100644 index 0000000..6892dca --- /dev/null +++ b/SOURCES/geometric-picking.patch @@ -0,0 +1,2091 @@ +From ac946bf95ce3e4dc900f72dcb4189dd49bdb3155 Mon Sep 17 00:00:00 2001 +From: Daniel van Vugt +Date: Thu, 18 Jul 2019 16:56:41 +0800 +Subject: [PATCH 1/4] clutter/point: Add ClutterPoint quarilateral testing API + +Add a function to check whether a point is inside a quadrilateral +by checking the cross product of vectors with the quadrilateral +points, and the point being checked. + +If the passed quadrilateral is zero-sized, no point is ever reported +to be inside it. + +This will be used by the next commit when comparing the transformed +actor vertices. + +[feaneron: add a commit message and remove unecessary code] + +https://gitlab.gnome.org/GNOME/mutter/merge_requests/189 +--- + clutter/clutter/clutter-base-types.c | 62 ++++++++++++++++++++++++++++ + clutter/clutter/clutter-types.h | 3 ++ + 2 files changed, 65 insertions(+) + +diff --git a/clutter/clutter/clutter-base-types.c b/clutter/clutter/clutter-base-types.c +index aeb25c90ef..c84f9aa64b 100644 +--- a/clutter/clutter/clutter-base-types.c ++++ b/clutter/clutter/clutter-base-types.c +@@ -570,6 +570,68 @@ G_DEFINE_BOXED_TYPE_WITH_CODE (ClutterPoint, clutter_point, + clutter_point_free, + CLUTTER_REGISTER_INTERVAL_PROGRESS (clutter_point_progress)) + ++static int ++clutter_point_compare_line (const ClutterPoint *p, ++ const ClutterPoint *a, ++ const ClutterPoint *b) ++{ ++ float x1 = b->x - a->x; ++ float y1 = b->y - a->y; ++ float x2 = p->x - a->x; ++ float y2 = p->y - a->y; ++ float cross_z = x1 * y2 - y1 * x2; ++ ++ if (cross_z > 0.f) ++ return 1; ++ else if (cross_z < 0.f) ++ return -1; ++ else ++ return 0; ++} ++ ++/** ++ * clutter_point_inside_quadrilateral: ++ * @point: a #ClutterPoint to test ++ * @vertices: array of vertices of the quadrilateral, in clockwise order, ++ * from top-left to bottom-left ++ * ++ * Determines whether a point is inside the convex quadrilateral provided, ++ * and not on any of its edges or vertices. ++ * ++ * Returns: %TRUE if @point is inside the quadrilateral ++ */ ++gboolean ++clutter_point_inside_quadrilateral (const ClutterPoint *point, ++ const ClutterPoint *vertices) ++{ ++ unsigned int i; ++ int first_side; ++ ++ first_side = 0; ++ ++ for (i = 0; i < 4; i++) ++ { ++ int side; ++ ++ side = clutter_point_compare_line (point, ++ &vertices[i], ++ &vertices[(i + 1) % 4]); ++ ++ if (side) ++ { ++ if (first_side == 0) ++ first_side = side; ++ else if (side != first_side) ++ return FALSE; ++ } ++ } ++ ++ if (first_side == 0) ++ return FALSE; ++ ++ return TRUE; ++} ++ + + + /* +diff --git a/clutter/clutter/clutter-types.h b/clutter/clutter/clutter-types.h +index 0f0fb1c2ac..0508028273 100644 +--- a/clutter/clutter/clutter-types.h ++++ b/clutter/clutter/clutter-types.h +@@ -200,6 +200,9 @@ float clutter_point_distance (const ClutterPoint *a, + const ClutterPoint *b, + float *x_distance, + float *y_distance); ++CLUTTER_EXPORT ++gboolean clutter_point_inside_quadrilateral (const ClutterPoint *point, ++ const ClutterPoint *vertices); + + /** + * ClutterSize: +-- +2.29.2 + + +From 8abac81711cfef8317bb675349d6b5b0a79eb05d Mon Sep 17 00:00:00 2001 +From: Daniel van Vugt +Date: Thu, 2 Aug 2018 19:03:30 +0800 +Subject: [PATCH 2/4] clutter: Introduce geometric picking + +Currently, Clutter does picking by drawing with Cogl and reading +the pixel that's beneath the given point. Since Cogl has a journal +that records drawing operations, and has optimizations to read a +single pixel from a list of rectangle, it would be expected that +we would hit this fast path and not flush the journal while picking. + +However, that's not the case: dithering, clipping with scissors, etc, +can all flush the journal, issuing commands to the GPU and making +picking slow. On NVidia-based systems, this glReadPixels() call is +extremely costly. + +Introduce geometric picking, and avoid using the Cogl journal entirely. +Do this by introducing a stack of actors in ClutterStage. This stack +is cached, but for now, don't use the cache as much as possible. + +The picking routines are still tied to painting. + +When projecting the actor vertexes, do it manually and take the modelview +matrix of the framebuffer into account as well. + +CPU usage on an Intel i7-7700, tested with two different GPUs/drivers: + + | | Intel | Nvidia | + | ------: | --------: | -----: | + | Moving the mouse: | + | Before | 10% | 10% | + | After | 6% | 6% | + | Moving a window: | + | Before | 23% | 81% | + | After | 19% | 40% | + +Closes: https://gitlab.gnome.org/GNOME/mutter/issues/154, + https://gitlab.gnome.org/GNOME/mutter/issues/691 + +Helps significantly with: https://gitlab.gnome.org/GNOME/mutter/issues/283, + https://gitlab.gnome.org/GNOME/mutter/issues/590, + https://gitlab.gnome.org/GNOME/mutter/issues/700 + +v2: Fix code style issues + Simplify quadrilateral checks + Remove the 0.5f hack + Differentiate axis-aligned rectangles + +https://gitlab.gnome.org/GNOME/mutter/merge_requests/189 +--- + clutter/clutter/clutter-actor-private.h | 2 - + clutter/clutter/clutter-actor.c | 232 ++++++---- + clutter/clutter/clutter-actor.h | 4 + + clutter/clutter/clutter-debug.h | 1 - + clutter/clutter/clutter-main.c | 120 ----- + clutter/clutter/clutter-private.h | 5 - + clutter/clutter/clutter-stage-private.h | 16 +- + clutter/clutter/clutter-stage-window.c | 18 - + clutter/clutter/clutter-stage-window.h | 8 - + clutter/clutter/clutter-stage.c | 459 +++++++++++-------- + clutter/clutter/cogl/clutter-stage-cogl.c | 52 --- + clutter/clutter/deprecated/clutter-texture.c | 93 +--- + clutter/tests/conform/actor-pick.c | 99 +--- + clutter/tests/conform/meson.build | 1 - + clutter/tests/conform/texture.c | 84 ---- + src/compositor/meta-surface-actor.c | 27 +- + 16 files changed, 439 insertions(+), 782 deletions(-) + delete mode 100644 clutter/tests/conform/texture.c + +diff --git a/clutter/clutter/clutter-actor-private.h b/clutter/clutter/clutter-actor-private.h +index c44f6342fd..9bf1a30493 100644 +--- a/clutter/clutter/clutter-actor-private.h ++++ b/clutter/clutter/clutter-actor-private.h +@@ -297,8 +297,6 @@ const gchar * _clutter_actor_get_debug_name + void _clutter_actor_push_clone_paint (void); + void _clutter_actor_pop_clone_paint (void); + +-guint32 _clutter_actor_get_pick_id (ClutterActor *self); +- + void _clutter_actor_shader_pre_paint (ClutterActor *actor, + gboolean repeat); + void _clutter_actor_shader_post_paint (ClutterActor *actor); +diff --git a/clutter/clutter/clutter-actor.c b/clutter/clutter/clutter-actor.c +index 43093fe79d..01ffa51caa 100644 +--- a/clutter/clutter/clutter-actor.c ++++ b/clutter/clutter/clutter-actor.c +@@ -730,8 +730,6 @@ struct _ClutterActorPrivate + + gchar *name; /* a non-unique name, used for debugging */ + +- gint32 pick_id; /* per-stage unique id, used for picking */ +- + /* a back-pointer to the Pango context that we can use + * to create pre-configured PangoLayout + */ +@@ -1281,6 +1279,105 @@ clutter_actor_verify_map_state (ClutterActor *self) + + #endif /* CLUTTER_ENABLE_DEBUG */ + ++static gboolean ++_clutter_actor_transform_local_box_to_stage (ClutterActor *self, ++ ClutterStage *stage, ++ const ClutterActorBox *box, ++ ClutterPoint vertices[4]) ++{ ++ CoglFramebuffer *fb = cogl_get_draw_framebuffer (); ++ CoglMatrix stage_transform, inv_stage_transform; ++ CoglMatrix modelview, transform_to_stage; ++ int v; ++ ++ clutter_actor_get_transform (CLUTTER_ACTOR (stage), &stage_transform); ++ if (!cogl_matrix_get_inverse (&stage_transform, &inv_stage_transform)) ++ return FALSE; ++ cogl_framebuffer_get_modelview_matrix (fb, &modelview); ++ cogl_matrix_multiply (&transform_to_stage, &inv_stage_transform, &modelview); ++ ++ vertices[0].x = box->x1; ++ vertices[0].y = box->y1; ++ ++ vertices[1].x = box->x2; ++ vertices[1].y = box->y1; ++ ++ vertices[2].x = box->x2; ++ vertices[2].y = box->y2; ++ ++ vertices[3].x = box->x1; ++ vertices[3].y = box->y2; ++ ++ for (v = 0; v < 4; v++) ++ { ++ float z = 0.f; ++ float w = 1.f; ++ ++ cogl_matrix_transform_point (&transform_to_stage, ++ &vertices[v].x, ++ &vertices[v].y, ++ &z, ++ &w); ++ } ++ ++ return TRUE; ++} ++ ++/** ++ * clutter_actor_pick_box: ++ * @self: The #ClutterActor being "pick" painted. ++ * @box: A rectangle in the actor's own local coordinates. ++ * ++ * Logs (does a virtual paint of) a rectangle for picking. Note that @box is ++ * in the actor's own local coordinates, so is usually {0,0,width,height} ++ * to include the whole actor. That is unless the actor has a shaped input ++ * region in which case you may wish to log the (multiple) smaller rectangles ++ * that make up the input region. ++ */ ++void ++clutter_actor_pick_box (ClutterActor *self, ++ const ClutterActorBox *box) ++{ ++ ClutterStage *stage; ++ ClutterPoint vertices[4]; ++ ++ g_return_if_fail (CLUTTER_IS_ACTOR (self)); ++ g_return_if_fail (box != NULL); ++ ++ if (box->x1 >= box->x2 || box->y1 >= box->y2) ++ return; ++ ++ stage = CLUTTER_STAGE (_clutter_actor_get_stage_internal (self)); ++ ++ if (_clutter_actor_transform_local_box_to_stage (self, stage, box, vertices)) ++ clutter_stage_log_pick (stage, vertices, self); ++} ++ ++static gboolean ++_clutter_actor_push_pick_clip (ClutterActor *self, ++ const ClutterActorBox *clip) ++{ ++ ClutterStage *stage; ++ ClutterPoint vertices[4]; ++ ++ stage = CLUTTER_STAGE (_clutter_actor_get_stage_internal (self)); ++ ++ if (!_clutter_actor_transform_local_box_to_stage (self, stage, clip, vertices)) ++ return FALSE; ++ ++ clutter_stage_push_pick_clip (stage, vertices); ++ return TRUE; ++} ++ ++static void ++_clutter_actor_pop_pick_clip (ClutterActor *self) ++{ ++ ClutterActor *stage; ++ ++ stage = _clutter_actor_get_stage_internal (self); ++ clutter_stage_pop_pick_clip (CLUTTER_STAGE (stage)); ++} ++ + static void + clutter_actor_set_mapped (ClutterActor *self, + gboolean mapped) +@@ -1509,8 +1606,7 @@ clutter_actor_update_map_state (ClutterActor *self, + static void + clutter_actor_real_map (ClutterActor *self) + { +- ClutterActorPrivate *priv = self->priv; +- ClutterActor *stage, *iter; ++ ClutterActor *iter; + + g_assert (!CLUTTER_ACTOR_IS_MAPPED (self)); + +@@ -1521,13 +1617,6 @@ clutter_actor_real_map (ClutterActor *self) + + self->priv->needs_paint_volume_update = TRUE; + +- stage = _clutter_actor_get_stage_internal (self); +- priv->pick_id = _clutter_stage_acquire_pick_id (CLUTTER_STAGE (stage), self); +- +- CLUTTER_NOTE (ACTOR, "Pick id '%d' for actor '%s'", +- priv->pick_id, +- _clutter_actor_get_debug_name (self)); +- + clutter_actor_ensure_resource_scale (self); + + /* notify on parent mapped before potentially mapping +@@ -1632,11 +1721,6 @@ clutter_actor_real_unmap (ClutterActor *self) + + stage = CLUTTER_STAGE (_clutter_actor_get_stage_internal (self)); + +- if (stage != NULL) +- _clutter_stage_release_pick_id (stage, priv->pick_id); +- +- priv->pick_id = -1; +- + if (stage != NULL && + clutter_stage_get_key_focus (stage) == self) + { +@@ -2255,46 +2339,16 @@ static void + clutter_actor_real_pick (ClutterActor *self, + const ClutterColor *color) + { +- CoglFramebuffer *framebuffer = cogl_get_draw_framebuffer (); +- +- /* the default implementation is just to paint a rectangle +- * with the same size of the actor using the passed color +- */ + if (clutter_actor_should_pick_paint (self)) + { +- static CoglPipeline *default_pick_pipeline = NULL; +- ClutterActorBox box = { 0, }; +- CoglPipeline *pick_pipeline; +- float width, height; +- +- if (G_UNLIKELY (default_pick_pipeline == NULL)) +- { +- CoglContext *ctx = +- clutter_backend_get_cogl_context (clutter_get_default_backend ()); +- +- default_pick_pipeline = cogl_pipeline_new (ctx); +- } +- +- g_assert (default_pick_pipeline != NULL); +- pick_pipeline = cogl_pipeline_copy (default_pick_pipeline); ++ ClutterActorBox box = { ++ .x1 = 0, ++ .y1 = 0, ++ .x2 = clutter_actor_get_width (self), ++ .y2 = clutter_actor_get_height (self), ++ }; + +- clutter_actor_get_allocation_box (self, &box); +- +- width = box.x2 - box.x1; +- height = box.y2 - box.y1; +- +- cogl_pipeline_set_color4ub (pick_pipeline, +- color->red, +- color->green, +- color->blue, +- color->alpha); +- +- cogl_framebuffer_draw_rectangle (framebuffer, +- pick_pipeline, +- 0, 0, +- width, height); +- +- cogl_object_unref (pick_pipeline); ++ clutter_actor_pick_box (self, &box); + } + + /* XXX - this thoroughly sucks, but we need to maintain compatibility +@@ -3585,15 +3639,6 @@ _clutter_actor_update_last_paint_volume (ClutterActor *self) + priv->last_paint_volume_valid = TRUE; + } + +-guint32 +-_clutter_actor_get_pick_id (ClutterActor *self) +-{ +- if (self->priv->pick_id < 0) +- return 0; +- +- return self->priv->pick_id; +-} +- + /* This is the same as clutter_actor_add_effect except that it doesn't + queue a redraw and it doesn't notify on the effect property */ + static void +@@ -3826,6 +3871,7 @@ clutter_actor_paint (ClutterActor *self) + ClutterActorPrivate *priv; + ClutterPickMode pick_mode; + gboolean culling_inhibited; ++ ClutterActorBox clip; + gboolean clip_set = FALSE; + ClutterStage *stage; + +@@ -3919,24 +3965,38 @@ clutter_actor_paint (ClutterActor *self) + + if (priv->has_clip) + { +- CoglFramebuffer *fb = _clutter_stage_get_active_framebuffer (stage); +- cogl_framebuffer_push_rectangle_clip (fb, +- priv->clip.origin.x, +- priv->clip.origin.y, +- priv->clip.origin.x + priv->clip.size.width, +- priv->clip.origin.y + priv->clip.size.height); ++ clip.x1 = priv->clip.origin.x; ++ clip.y1 = priv->clip.origin.y; ++ clip.x2 = priv->clip.origin.x + priv->clip.size.width; ++ clip.y2 = priv->clip.origin.y + priv->clip.size.height; + clip_set = TRUE; + } + else if (priv->clip_to_allocation) + { +- CoglFramebuffer *fb = _clutter_stage_get_active_framebuffer (stage); +- gfloat width, height; ++ clip.x1 = 0.f; ++ clip.y1 = 0.f; ++ clip.x2 = priv->allocation.x2 - priv->allocation.x1; ++ clip.y2 = priv->allocation.y2 - priv->allocation.y1; ++ clip_set = TRUE; ++ } + +- width = priv->allocation.x2 - priv->allocation.x1; +- height = priv->allocation.y2 - priv->allocation.y1; ++ if (clip_set) ++ { ++ if (pick_mode == CLUTTER_PICK_NONE) ++ { ++ CoglFramebuffer *fb = _clutter_stage_get_active_framebuffer (stage); + +- cogl_framebuffer_push_rectangle_clip (fb, 0, 0, width, height); +- clip_set = TRUE; ++ cogl_framebuffer_push_rectangle_clip (fb, ++ clip.x1, ++ clip.y1, ++ clip.x2, ++ clip.y2); ++ } ++ else ++ { ++ if (!_clutter_actor_push_pick_clip (self, &clip)) ++ clip_set = FALSE; ++ } + } + + if (pick_mode == CLUTTER_PICK_NONE) +@@ -4020,9 +4080,16 @@ clutter_actor_paint (ClutterActor *self) + done: + if (clip_set) + { +- CoglFramebuffer *fb = _clutter_stage_get_active_framebuffer (stage); ++ if (pick_mode == CLUTTER_PICK_NONE) ++ { ++ CoglFramebuffer *fb = _clutter_stage_get_active_framebuffer (stage); + +- cogl_framebuffer_pop_clip (fb); ++ cogl_framebuffer_pop_clip (fb); ++ } ++ else ++ { ++ _clutter_actor_pop_pick_clip (self); ++ } + } + + cogl_pop_matrix (); +@@ -4093,11 +4160,12 @@ clutter_actor_continue_paint (ClutterActor *self) + { + ClutterColor col = { 0, }; + +- _clutter_id_to_color (_clutter_actor_get_pick_id (self), &col); +- +- /* Actor will then paint silhouette of itself in supplied +- * color. See clutter_stage_get_actor_at_pos() for where +- * picking is enabled. ++ /* The actor will log a silhouette of itself to the stage pick log. ++ * Note that the picking color is no longer used as the "log" instead ++ * keeps a weak pointer to the actor itself. But we keep the color ++ * parameter for now so as to maintain ABI compatibility. The color ++ * parameter can be removed when someone feels like breaking the ABI ++ * along with gnome-shell. + * + * XXX:2.0 - Call the pick() virtual directly + */ +@@ -8603,8 +8671,6 @@ clutter_actor_init (ClutterActor *self) + + self->priv = priv = clutter_actor_get_instance_private (self); + +- priv->pick_id = -1; +- + priv->opacity = 0xff; + priv->show_on_set_parent = TRUE; + priv->resource_scale = -1.0f; +diff --git a/clutter/clutter/clutter-actor.h b/clutter/clutter/clutter-actor.h +index 3e7a59ac0c..16b438ba64 100644 +--- a/clutter/clutter/clutter-actor.h ++++ b/clutter/clutter/clutter-actor.h +@@ -910,6 +910,10 @@ void clutter_actor_bind_model_with_properties + const char *first_model_property, + ...); + ++CLUTTER_EXPORT ++void clutter_actor_pick_box (ClutterActor *self, ++ const ClutterActorBox *box); ++ + G_END_DECLS + + #endif /* __CLUTTER_ACTOR_H__ */ +diff --git a/clutter/clutter/clutter-debug.h b/clutter/clutter/clutter-debug.h +index 2462385f65..7d170d2d54 100644 +--- a/clutter/clutter/clutter-debug.h ++++ b/clutter/clutter/clutter-debug.h +@@ -30,7 +30,6 @@ typedef enum + typedef enum + { + CLUTTER_DEBUG_NOP_PICKING = 1 << 0, +- CLUTTER_DEBUG_DUMP_PICK_BUFFERS = 1 << 1 + } ClutterPickDebugFlag; + + typedef enum +diff --git a/clutter/clutter/clutter-main.c b/clutter/clutter/clutter-main.c +index 645c8bceb6..11c221a65b 100644 +--- a/clutter/clutter/clutter-main.c ++++ b/clutter/clutter/clutter-main.c +@@ -131,7 +131,6 @@ static const GDebugKey clutter_debug_keys[] = { + + static const GDebugKey clutter_pick_debug_keys[] = { + { "nop-picking", CLUTTER_DEBUG_NOP_PICKING }, +- { "dump-pick-buffers", CLUTTER_DEBUG_DUMP_PICK_BUFFERS }, + }; + + static const GDebugKey clutter_paint_debug_keys[] = { +@@ -533,125 +532,6 @@ clutter_get_motion_events_enabled (void) + return _clutter_context_get_motion_events_enabled (); + } + +-void +-_clutter_id_to_color (guint id_, +- ClutterColor *col) +-{ +- ClutterMainContext *ctx; +- gint red, green, blue; +- +- ctx = _clutter_context_get_default (); +- +- if (ctx->fb_g_mask == 0) +- { +- /* Figure out framebuffer masks used for pick */ +- cogl_get_bitmasks (&ctx->fb_r_mask, +- &ctx->fb_g_mask, +- &ctx->fb_b_mask, NULL); +- +- ctx->fb_r_mask_used = ctx->fb_r_mask; +- ctx->fb_g_mask_used = ctx->fb_g_mask; +- ctx->fb_b_mask_used = ctx->fb_b_mask; +- +- /* XXX - describe what "fuzzy picking" is */ +- if (clutter_use_fuzzy_picking) +- { +- ctx->fb_r_mask_used--; +- ctx->fb_g_mask_used--; +- ctx->fb_b_mask_used--; +- } +- } +- +- /* compute the numbers we'll store in the components */ +- red = (id_ >> (ctx->fb_g_mask_used+ctx->fb_b_mask_used)) +- & (0xff >> (8-ctx->fb_r_mask_used)); +- green = (id_ >> ctx->fb_b_mask_used) +- & (0xff >> (8-ctx->fb_g_mask_used)); +- blue = (id_) +- & (0xff >> (8-ctx->fb_b_mask_used)); +- +- /* shift left bits a bit and add one, this circumvents +- * at least some potential rounding errors in GL/GLES +- * driver / hw implementation. +- */ +- if (ctx->fb_r_mask_used != ctx->fb_r_mask) +- red = red * 2; +- if (ctx->fb_g_mask_used != ctx->fb_g_mask) +- green = green * 2; +- if (ctx->fb_b_mask_used != ctx->fb_b_mask) +- blue = blue * 2; +- +- /* shift up to be full 8bit values */ +- red = (red << (8 - ctx->fb_r_mask)) | (0x7f >> (ctx->fb_r_mask_used)); +- green = (green << (8 - ctx->fb_g_mask)) | (0x7f >> (ctx->fb_g_mask_used)); +- blue = (blue << (8 - ctx->fb_b_mask)) | (0x7f >> (ctx->fb_b_mask_used)); +- +- col->red = red; +- col->green = green; +- col->blue = blue; +- col->alpha = 0xff; +- +- /* XXX: We rotate the nibbles of the colors here so that there is a +- * visible variation between colors of sequential actor identifiers; +- * otherwise pick buffers dumped to an image will pretty much just look +- * black. +- */ +- if (G_UNLIKELY (clutter_pick_debug_flags & CLUTTER_DEBUG_DUMP_PICK_BUFFERS)) +- { +- col->red = (col->red << 4) | (col->red >> 4); +- col->green = (col->green << 4) | (col->green >> 4); +- col->blue = (col->blue << 4) | (col->blue >> 4); +- } +-} +- +-guint +-_clutter_pixel_to_id (guchar pixel[4]) +-{ +- ClutterMainContext *ctx; +- gint red, green, blue; +- guint retval; +- +- ctx = _clutter_context_get_default (); +- +- /* reduce the pixel components to the number of bits actually used of the +- * 8bits. +- */ +- if (G_UNLIKELY (clutter_pick_debug_flags & CLUTTER_DEBUG_DUMP_PICK_BUFFERS)) +- { +- guchar tmp; +- +- /* XXX: In _clutter_id_to_color we rotated the nibbles of the colors so +- * that there is a visible variation between colors of sequential actor +- * identifiers (otherwise pick buffers dumped to an image will pretty +- * much just look black.) Here we reverse that rotation. +- */ +- tmp = ((pixel[0] << 4) | (pixel[0] >> 4)); +- red = tmp >> (8 - ctx->fb_r_mask); +- tmp = ((pixel[1] << 4) | (pixel[1] >> 4)); +- green = tmp >> (8 - ctx->fb_g_mask); +- tmp = ((pixel[2] << 4) | (pixel[2] >> 4)); +- blue = tmp >> (8 - ctx->fb_b_mask); +- } +- else +- { +- red = pixel[0] >> (8 - ctx->fb_r_mask); +- green = pixel[1] >> (8 - ctx->fb_g_mask); +- blue = pixel[2] >> (8 - ctx->fb_b_mask); +- } +- +- /* divide potentially by two if 'fuzzy' */ +- red = red >> (ctx->fb_r_mask - ctx->fb_r_mask_used); +- green = green >> (ctx->fb_g_mask - ctx->fb_g_mask_used); +- blue = blue >> (ctx->fb_b_mask - ctx->fb_b_mask_used); +- +- /* combine the correct per component values into the final id */ +- retval = blue +- + (green << ctx->fb_b_mask_used) +- + (red << (ctx->fb_b_mask_used + ctx->fb_g_mask_used)); +- +- return retval; +-} +- + static CoglPangoFontMap * + clutter_context_get_pango_fontmap (void) + { +diff --git a/clutter/clutter/clutter-private.h b/clutter/clutter/clutter-private.h +index 5a0fed85c9..f2f870b014 100644 +--- a/clutter/clutter/clutter-private.h ++++ b/clutter/clutter/clutter-private.h +@@ -210,11 +210,6 @@ gboolean _clutter_feature_init (GError **error); + gboolean _clutter_diagnostic_enabled (void); + void _clutter_diagnostic_message (const char *fmt, ...) G_GNUC_PRINTF (1, 2); + +-/* Picking code */ +-guint _clutter_pixel_to_id (guchar pixel[4]); +-void _clutter_id_to_color (guint id, +- ClutterColor *col); +- + void _clutter_set_sync_to_vblank (gboolean sync_to_vblank); + + /* use this function as the accumulator if you have a signal with +diff --git a/clutter/clutter/clutter-stage-private.h b/clutter/clutter/clutter-stage-private.h +index 42474687ad..51ae47af1d 100644 +--- a/clutter/clutter/clutter-stage-private.h ++++ b/clutter/clutter/clutter-stage-private.h +@@ -75,6 +75,15 @@ gint64 _clutter_stage_get_update_time (ClutterStage *stage); + void _clutter_stage_clear_update_time (ClutterStage *stage); + gboolean _clutter_stage_has_full_redraw_queued (ClutterStage *stage); + ++void clutter_stage_log_pick (ClutterStage *stage, ++ const ClutterPoint *vertices, ++ ClutterActor *actor); ++ ++void clutter_stage_push_pick_clip (ClutterStage *stage, ++ const ClutterPoint *vertices); ++ ++void clutter_stage_pop_pick_clip (ClutterStage *stage); ++ + ClutterActor *_clutter_stage_do_pick (ClutterStage *stage, + gint x, + gint y, +@@ -93,13 +102,6 @@ void _clutter_stage_queue_redraw_entry_invalidate (Clut + + CoglFramebuffer *_clutter_stage_get_active_framebuffer (ClutterStage *stage); + +-gint32 _clutter_stage_acquire_pick_id (ClutterStage *stage, +- ClutterActor *actor); +-void _clutter_stage_release_pick_id (ClutterStage *stage, +- gint32 pick_id); +-ClutterActor * _clutter_stage_get_actor_by_pick_id (ClutterStage *stage, +- gint32 pick_id); +- + void _clutter_stage_add_pointer_drag_actor (ClutterStage *stage, + ClutterInputDevice *device, + ClutterActor *actor); +diff --git a/clutter/clutter/clutter-stage-window.c b/clutter/clutter/clutter-stage-window.c +index e8fa976a7d..4c4ef9d643 100644 +--- a/clutter/clutter/clutter-stage-window.c ++++ b/clutter/clutter/clutter-stage-window.c +@@ -293,24 +293,6 @@ _clutter_stage_window_redraw (ClutterStageWindow *window) + iface->redraw (window); + } + +- +-void +-_clutter_stage_window_get_dirty_pixel (ClutterStageWindow *window, +- ClutterStageView *view, +- int *x, int *y) +-{ +- ClutterStageWindowInterface *iface; +- +- *x = 0; +- *y = 0; +- +- g_return_if_fail (CLUTTER_IS_STAGE_WINDOW (window)); +- +- iface = CLUTTER_STAGE_WINDOW_GET_IFACE (window); +- if (iface->get_dirty_pixel) +- iface->get_dirty_pixel (window, view, x, y); +-} +- + gboolean + _clutter_stage_window_can_clip_redraws (ClutterStageWindow *window) + { +diff --git a/clutter/clutter/clutter-stage-window.h b/clutter/clutter/clutter-stage-window.h +index 6c3601745f..aa0c5f71cc 100644 +--- a/clutter/clutter/clutter-stage-window.h ++++ b/clutter/clutter/clutter-stage-window.h +@@ -68,10 +68,6 @@ struct _ClutterStageWindowInterface + + void (* redraw) (ClutterStageWindow *stage_window); + +- void (* get_dirty_pixel) (ClutterStageWindow *stage_window, +- ClutterStageView *view, +- int *x, int *y); +- + gboolean (* can_clip_redraws) (ClutterStageWindow *stage_window); + + GList *(* get_views) (ClutterStageWindow *stage_window); +@@ -119,10 +115,6 @@ void _clutter_stage_window_set_accept_focus (ClutterStageWin + + void _clutter_stage_window_redraw (ClutterStageWindow *window); + +-void _clutter_stage_window_get_dirty_pixel (ClutterStageWindow *window, +- ClutterStageView *view, +- int *x, int *y); +- + gboolean _clutter_stage_window_can_clip_redraws (ClutterStageWindow *window); + + GList * _clutter_stage_window_get_views (ClutterStageWindow *window); +diff --git a/clutter/clutter/clutter-stage.c b/clutter/clutter/clutter-stage.c +index aaa77d9ede..7d88d5752f 100644 +--- a/clutter/clutter/clutter-stage.c ++++ b/clutter/clutter/clutter-stage.c +@@ -97,6 +97,11 @@ typedef enum /*< prefix=CLUTTER_STAGE >*/ + + #define STAGE_NO_CLEAR_ON_PAINT(s) ((((ClutterStage *) (s))->priv->stage_hints & CLUTTER_STAGE_NO_CLEAR_ON_PAINT) != 0) + ++#ifndef G_APPROX_VALUE ++#define G_APPROX_VALUE(a, b, epsilon) \ ++ (((a) > (b) ? (a) - (b) : (b) - (a)) < (epsilon)) ++#endif ++ + struct _ClutterStageQueueRedrawEntry + { + ClutterActor *actor; +@@ -104,6 +109,19 @@ struct _ClutterStageQueueRedrawEntry + ClutterPaintVolume clip; + }; + ++typedef struct _PickRecord ++{ ++ ClutterPoint vertex[4]; ++ ClutterActor *actor; ++ int clip_stack_top; ++} PickRecord; ++ ++typedef struct _PickClipRecord ++{ ++ int prev; ++ ClutterPoint vertex[4]; ++} PickClipRecord; ++ + struct _ClutterStagePrivate + { + /* the stage implementation */ +@@ -137,7 +155,11 @@ struct _ClutterStagePrivate + GTimer *fps_timer; + gint32 timer_n_frames; + +- ClutterIDPool *pick_id_pool; ++ GArray *pick_stack; ++ GArray *pick_clip_stack; ++ int pick_clip_stack_top; ++ gboolean pick_stack_frozen; ++ ClutterPickMode cached_pick_mode; + + #ifdef CLUTTER_ENABLE_DEBUG + gulong redraw_count; +@@ -326,6 +348,211 @@ clutter_stage_get_preferred_height (ClutterActor *self, + *natural_height_p = geom.height; + } + ++static void ++add_pick_stack_weak_refs (ClutterStage *stage) ++{ ++ ClutterStagePrivate *priv = stage->priv; ++ int i; ++ ++ if (priv->pick_stack_frozen) ++ return; ++ ++ for (i = 0; i < priv->pick_stack->len; i++) ++ { ++ PickRecord *rec = &g_array_index (priv->pick_stack, PickRecord, i); ++ ++ if (rec->actor) ++ g_object_add_weak_pointer (G_OBJECT (rec->actor), ++ (gpointer) &rec->actor); ++ } ++ ++ priv->pick_stack_frozen = TRUE; ++} ++ ++static void ++remove_pick_stack_weak_refs (ClutterStage *stage) ++{ ++ ClutterStagePrivate *priv = stage->priv; ++ int i; ++ ++ if (!priv->pick_stack_frozen) ++ return; ++ ++ for (i = 0; i < priv->pick_stack->len; i++) ++ { ++ PickRecord *rec = &g_array_index (priv->pick_stack, PickRecord, i); ++ ++ if (rec->actor) ++ g_object_remove_weak_pointer (G_OBJECT (rec->actor), ++ (gpointer) &rec->actor); ++ } ++ ++ priv->pick_stack_frozen = FALSE; ++} ++ ++static void ++_clutter_stage_clear_pick_stack (ClutterStage *stage) ++{ ++ ClutterStagePrivate *priv = stage->priv; ++ ++ remove_pick_stack_weak_refs (stage); ++ g_array_set_size (priv->pick_stack, 0); ++ g_array_set_size (priv->pick_clip_stack, 0); ++ priv->pick_clip_stack_top = -1; ++ priv->cached_pick_mode = CLUTTER_PICK_NONE; ++} ++ ++void ++clutter_stage_log_pick (ClutterStage *stage, ++ const ClutterPoint *vertices, ++ ClutterActor *actor) ++{ ++ ClutterStagePrivate *priv; ++ PickRecord rec; ++ ++ g_return_if_fail (CLUTTER_IS_STAGE (stage)); ++ g_return_if_fail (actor != NULL); ++ ++ priv = stage->priv; ++ ++ g_assert (!priv->pick_stack_frozen); ++ ++ memcpy (rec.vertex, vertices, 4 * sizeof (ClutterPoint)); ++ rec.actor = actor; ++ rec.clip_stack_top = priv->pick_clip_stack_top; ++ ++ g_array_append_val (priv->pick_stack, rec); ++} ++ ++void ++clutter_stage_push_pick_clip (ClutterStage *stage, ++ const ClutterPoint *vertices) ++{ ++ ClutterStagePrivate *priv; ++ PickClipRecord clip; ++ ++ g_return_if_fail (CLUTTER_IS_STAGE (stage)); ++ ++ priv = stage->priv; ++ ++ g_assert (!priv->pick_stack_frozen); ++ ++ clip.prev = priv->pick_clip_stack_top; ++ memcpy (clip.vertex, vertices, 4 * sizeof (ClutterPoint)); ++ ++ g_array_append_val (priv->pick_clip_stack, clip); ++ priv->pick_clip_stack_top = priv->pick_clip_stack->len - 1; ++} ++ ++void ++clutter_stage_pop_pick_clip (ClutterStage *stage) ++{ ++ ClutterStagePrivate *priv; ++ const PickClipRecord *top; ++ ++ g_return_if_fail (CLUTTER_IS_STAGE (stage)); ++ ++ priv = stage->priv; ++ ++ g_assert (!priv->pick_stack_frozen); ++ g_assert (priv->pick_clip_stack_top >= 0); ++ ++ /* Individual elements of pick_clip_stack are not freed. This is so they ++ * can be shared as part of a tree of different stacks used by different ++ * actors in the pick_stack. The whole pick_clip_stack does however get ++ * freed later in _clutter_stage_clear_pick_stack. ++ */ ++ ++ top = &g_array_index (priv->pick_clip_stack, ++ PickClipRecord, ++ priv->pick_clip_stack_top); ++ ++ priv->pick_clip_stack_top = top->prev; ++} ++ ++static gboolean ++is_quadrilateral_axis_aligned_rectangle (const ClutterPoint *vertices) ++{ ++ int i; ++ ++ for (i = 0; i < 4; i++) ++ { ++ if (!G_APPROX_VALUE (vertices[i].x, ++ vertices[(i + 1) % 4].x, ++ FLT_EPSILON) && ++ !G_APPROX_VALUE (vertices[i].y, ++ vertices[(i + 1) % 4].y, ++ FLT_EPSILON)) ++ return FALSE; ++ } ++ return TRUE; ++} ++ ++static gboolean ++is_inside_axis_aligned_rectangle (const ClutterPoint *point, ++ const ClutterPoint *vertices) ++{ ++ float min_x = FLT_MAX; ++ float max_x = FLT_MIN; ++ float min_y = FLT_MAX; ++ float max_y = FLT_MIN; ++ int i; ++ ++ for (i = 0; i < 3; i++) ++ { ++ min_x = MIN (min_x, vertices[i].x); ++ min_y = MIN (min_y, vertices[i].y); ++ max_x = MAX (max_x, vertices[i].x); ++ max_y = MAX (max_y, vertices[i].y); ++ } ++ ++ return (point->x >= min_x && ++ point->y >= min_y && ++ point->x < max_x && ++ point->y < max_y); ++} ++ ++static gboolean ++is_inside_input_region (const ClutterPoint *point, ++ const ClutterPoint *vertices) ++{ ++ ++ if (is_quadrilateral_axis_aligned_rectangle (vertices)) ++ return is_inside_axis_aligned_rectangle (point, vertices); ++ else ++ return clutter_point_inside_quadrilateral (point, vertices); ++} ++ ++static gboolean ++pick_record_contains_pixel (ClutterStage *stage, ++ const PickRecord *rec, ++ int x, ++ int y) ++{ ++ const ClutterPoint point = CLUTTER_POINT_INIT (x, y); ++ ClutterStagePrivate *priv; ++ int clip_index; ++ ++ if (!is_inside_input_region (&point, rec->vertex)) ++ return FALSE; ++ ++ priv = stage->priv; ++ clip_index = rec->clip_stack_top; ++ while (clip_index >= 0) ++ { ++ const PickClipRecord *clip = &g_array_index (priv->pick_clip_stack, ++ PickClipRecord, ++ clip_index); ++ ++ if (!is_inside_input_region (&point, clip->vertex)) ++ return FALSE; ++ ++ clip_index = clip->prev; ++ } ++ ++ return TRUE; ++} ++ + static inline void + queue_full_redraw (ClutterStage *stage) + { +@@ -636,6 +863,12 @@ clutter_stage_do_paint_view (ClutterStage *stage, + float viewport[4]; + cairo_rectangle_int_t geom; + ++ /* Any mode of painting/picking invalidates the pick cache, unless we're ++ * in the middle of building it. So we reset the cached flag but don't ++ * completely clear the pick stack. ++ */ ++ priv->cached_pick_mode = CLUTTER_PICK_NONE; ++ + _clutter_stage_window_get_geometry (priv->impl, &geom); + + viewport[0] = priv->viewport[0]; +@@ -1414,40 +1647,6 @@ clutter_stage_get_redraw_clip_bounds (ClutterStage *stage, + } + } + +-static void +-read_pixels_to_file (CoglFramebuffer *fb, +- char *filename_stem, +- int x, +- int y, +- int width, +- int height) +-{ +- guint8 *data; +- cairo_surface_t *surface; +- static int read_count = 0; +- char *filename = g_strdup_printf ("%s-%05d.png", +- filename_stem, +- read_count); +- +- data = g_malloc (4 * width * height); +- cogl_framebuffer_read_pixels (fb, +- x, y, width, height, +- CLUTTER_CAIRO_FORMAT_ARGB32, +- data); +- +- surface = cairo_image_surface_create_for_data (data, CAIRO_FORMAT_RGB24, +- width, height, +- width * 4); +- +- cairo_surface_write_to_png (surface, filename); +- cairo_surface_destroy (surface); +- +- g_free (data); +- g_free (filename); +- +- read_count++; +-} +- + static ClutterActor * + _clutter_stage_do_pick_on_view (ClutterStage *stage, + gint x, +@@ -1455,140 +1654,42 @@ _clutter_stage_do_pick_on_view (ClutterStage *stage, + ClutterPickMode mode, + ClutterStageView *view) + { +- ClutterActor *actor = CLUTTER_ACTOR (stage); ++ ClutterMainContext *context = _clutter_context_get_default (); + ClutterStagePrivate *priv = stage->priv; + CoglFramebuffer *fb = clutter_stage_view_get_framebuffer (view); +- cairo_rectangle_int_t view_layout; +- ClutterMainContext *context; +- guchar pixel[4] = { 0xff, 0xff, 0xff, 0xff }; +- CoglColor stage_pick_id; +- gboolean dither_enabled_save; +- ClutterActor *retval; +- gint dirty_x; +- gint dirty_y; +- gint read_x; +- gint read_y; +- float fb_width, fb_height; +- float fb_scale; +- float viewport_offset_x; +- float viewport_offset_y; +- +- priv = stage->priv; +- +- context = _clutter_context_get_default (); +- fb_scale = clutter_stage_view_get_scale (view); +- clutter_stage_view_get_layout (view, &view_layout); +- +- fb_width = view_layout.width * fb_scale; +- fb_height = view_layout.height * fb_scale; +- cogl_push_framebuffer (fb); +- +- /* needed for when a context switch happens */ +- _clutter_stage_maybe_setup_viewport (stage, view); +- +- /* FIXME: For some reason leaving the cogl clip stack empty causes the +- * picking to not work at all, so setting it the whole framebuffer content +- * for now. */ +- cogl_framebuffer_push_scissor_clip (fb, 0, 0, +- view_layout.width * fb_scale, +- view_layout.height * fb_scale); +- +- _clutter_stage_window_get_dirty_pixel (priv->impl, view, &dirty_x, &dirty_y); ++ int i; + +- if (G_LIKELY (!(clutter_pick_debug_flags & CLUTTER_DEBUG_DUMP_PICK_BUFFERS))) +- { +- CLUTTER_NOTE (PICK, "Pushing pick scissor clip x: %d, y: %d, 1x1", +- (int) (dirty_x * fb_scale), +- (int) (dirty_y * fb_scale)); +- cogl_framebuffer_push_scissor_clip (fb, dirty_x * fb_scale, dirty_y * fb_scale, 1, 1); +- } ++ g_assert (context->pick_mode == CLUTTER_PICK_NONE); + +- viewport_offset_x = x * fb_scale - dirty_x * fb_scale; +- viewport_offset_y = y * fb_scale - dirty_y * fb_scale; +- CLUTTER_NOTE (PICK, "Setting viewport to %f, %f, %f, %f", +- priv->viewport[0] * fb_scale - viewport_offset_x, +- priv->viewport[1] * fb_scale - viewport_offset_y, +- priv->viewport[2] * fb_scale, +- priv->viewport[3] * fb_scale); +- cogl_framebuffer_set_viewport (fb, +- priv->viewport[0] * fb_scale - viewport_offset_x, +- priv->viewport[1] * fb_scale - viewport_offset_y, +- priv->viewport[2] * fb_scale, +- priv->viewport[3] * fb_scale); +- +- read_x = dirty_x * fb_scale; +- read_y = dirty_y * fb_scale; +- +- CLUTTER_NOTE (PICK, "Performing pick at %i,%i on view %dx%d+%d+%d s: %f", +- x, y, +- view_layout.width, view_layout.height, +- view_layout.x, view_layout.y, fb_scale); +- +- cogl_color_init_from_4ub (&stage_pick_id, 255, 255, 255, 255); +- cogl_framebuffer_clear (fb, COGL_BUFFER_BIT_COLOR | COGL_BUFFER_BIT_DEPTH, &stage_pick_id); +- +- /* Disable dithering (if any) when doing the painting in pick mode */ +- dither_enabled_save = cogl_framebuffer_get_dither_enabled (fb); +- cogl_framebuffer_set_dither_enabled (fb, FALSE); +- +- /* Render the entire scence in pick mode - just single colored silhouette's +- * are drawn offscreen (as we never swap buffers) +- */ +- context->pick_mode = mode; +- +- clutter_stage_do_paint_view (stage, view, NULL); +- context->pick_mode = CLUTTER_PICK_NONE; +- +- /* Read the color of the screen co-ords pixel. RGBA_8888_PRE is used +- even though we don't care about the alpha component because under +- GLES this is the only format that is guaranteed to work so Cogl +- will end up having to do a conversion if any other format is +- used. The format is requested as pre-multiplied because Cogl +- assumes that all pixels in the framebuffer are premultiplied so +- it avoids a conversion. */ +- cogl_framebuffer_read_pixels (fb, +- read_x, read_y, 1, 1, +- COGL_PIXEL_FORMAT_RGBA_8888_PRE, +- pixel); +- +- if (G_UNLIKELY (clutter_pick_debug_flags & CLUTTER_DEBUG_DUMP_PICK_BUFFERS)) ++ if (mode != priv->cached_pick_mode) + { +- char *file_name = +- g_strdup_printf ("pick-buffer-%s-view-x-%d", +- _clutter_actor_get_debug_name (actor), +- view_layout.x); ++ _clutter_stage_clear_pick_stack (stage); + +- read_pixels_to_file (fb, file_name, 0, 0, fb_width, fb_height); ++ cogl_push_framebuffer (fb); + +- g_free (file_name); +- } +- +- /* Restore whether GL_DITHER was enabled */ +- cogl_framebuffer_set_dither_enabled (fb, dither_enabled_save); +- +- if (G_LIKELY (!(clutter_pick_debug_flags & CLUTTER_DEBUG_DUMP_PICK_BUFFERS))) +- cogl_framebuffer_pop_clip (fb); ++ context->pick_mode = mode; ++ clutter_stage_do_paint_view (stage, view, NULL); ++ context->pick_mode = CLUTTER_PICK_NONE; ++ priv->cached_pick_mode = mode; + +- cogl_framebuffer_pop_clip (fb); ++ cogl_pop_framebuffer (); + +- _clutter_stage_dirty_viewport (stage); ++ add_pick_stack_weak_refs (stage); ++ } + +- if (pixel[0] == 0xff && pixel[1] == 0xff && pixel[2] == 0xff) +- retval = actor; +- else ++ /* Search all "painted" pickable actors from front to back. A linear search ++ * is required, and also performs fine since there is typically only ++ * on the order of dozens of actors in the list (on screen) at a time. ++ */ ++ for (i = priv->pick_stack->len - 1; i >= 0; i--) + { +- guint32 id_ = _clutter_pixel_to_id (pixel); ++ const PickRecord *rec = &g_array_index (priv->pick_stack, PickRecord, i); + +- retval = _clutter_stage_get_actor_by_pick_id (stage, id_); +- CLUTTER_NOTE (PICK, "Picking actor %s with id %u (pixel: 0x%x%x%x%x", +- G_OBJECT_TYPE_NAME (retval), +- id_, +- pixel[0], pixel[1], pixel[2], pixel[3]); ++ if (rec->actor && pick_record_contains_pixel (stage, rec, x, y)) ++ return rec->actor; + } + +- cogl_pop_framebuffer (); +- +- return retval; ++ return CLUTTER_ACTOR (stage); + } + + static ClutterStageView * +@@ -1901,7 +2002,9 @@ clutter_stage_finalize (GObject *object) + + g_array_free (priv->paint_volume_stack, TRUE); + +- _clutter_id_pool_free (priv->pick_id_pool); ++ _clutter_stage_clear_pick_stack (stage); ++ g_array_free (priv->pick_clip_stack, TRUE); ++ g_array_free (priv->pick_stack, TRUE); + + if (priv->fps_timer != NULL) + g_timer_destroy (priv->fps_timer); +@@ -2435,7 +2538,10 @@ clutter_stage_init (ClutterStage *self) + priv->paint_volume_stack = + g_array_new (FALSE, FALSE, sizeof (ClutterPaintVolume)); + +- priv->pick_id_pool = _clutter_id_pool_new (256); ++ priv->pick_stack = g_array_new (FALSE, FALSE, sizeof (PickRecord)); ++ priv->pick_clip_stack = g_array_new (FALSE, FALSE, sizeof (PickClipRecord)); ++ priv->pick_clip_stack_top = -1; ++ priv->cached_pick_mode = CLUTTER_PICK_NONE; + } + + /** +@@ -4253,6 +4359,12 @@ _clutter_stage_queue_actor_redraw (ClutterStage *stage, + CLUTTER_NOTE (CLIPPING, "stage_queue_actor_redraw (actor=%s, clip=%p): ", + _clutter_actor_get_debug_name (actor), clip); + ++ /* Queuing a redraw or clip change invalidates the pick cache, unless we're ++ * in the middle of building it. So we reset the cached flag but don't ++ * completely clear the pick stack... ++ */ ++ priv->cached_pick_mode = CLUTTER_PICK_NONE; ++ + if (!priv->redraw_pending) + { + ClutterMasterClock *master_clock; +@@ -4513,39 +4625,6 @@ _clutter_stage_get_active_framebuffer (ClutterStage *stage) + return stage->priv->active_framebuffer; + } + +-gint32 +-_clutter_stage_acquire_pick_id (ClutterStage *stage, +- ClutterActor *actor) +-{ +- ClutterStagePrivate *priv = stage->priv; +- +- g_assert (priv->pick_id_pool != NULL); +- +- return _clutter_id_pool_add (priv->pick_id_pool, actor); +-} +- +-void +-_clutter_stage_release_pick_id (ClutterStage *stage, +- gint32 pick_id) +-{ +- ClutterStagePrivate *priv = stage->priv; +- +- g_assert (priv->pick_id_pool != NULL); +- +- _clutter_id_pool_remove (priv->pick_id_pool, pick_id); +-} +- +-ClutterActor * +-_clutter_stage_get_actor_by_pick_id (ClutterStage *stage, +- gint32 pick_id) +-{ +- ClutterStagePrivate *priv = stage->priv; +- +- g_assert (priv->pick_id_pool != NULL); +- +- return _clutter_id_pool_lookup (priv->pick_id_pool, pick_id); +-} +- + void + _clutter_stage_add_pointer_drag_actor (ClutterStage *stage, + ClutterInputDevice *device, +diff --git a/clutter/clutter/cogl/clutter-stage-cogl.c b/clutter/clutter/cogl/clutter-stage-cogl.c +index 3f1f609c4e..effed79759 100644 +--- a/clutter/clutter/cogl/clutter-stage-cogl.c ++++ b/clutter/clutter/cogl/clutter-stage-cogl.c +@@ -926,57 +926,6 @@ clutter_stage_cogl_redraw (ClutterStageWindow *stage_window) + stage_cogl->frame_count++; + } + +-static void +-clutter_stage_cogl_get_dirty_pixel (ClutterStageWindow *stage_window, +- ClutterStageView *view, +- int *x, +- int *y) +-{ +- CoglFramebuffer *onscreen = clutter_stage_view_get_onscreen (view); +- ClutterStageViewCogl *view_cogl = CLUTTER_STAGE_VIEW_COGL (view); +- ClutterStageViewCoglPrivate *view_priv = +- clutter_stage_view_cogl_get_instance_private (view_cogl); +- gboolean has_buffer_age = +- cogl_is_onscreen (onscreen) && +- is_buffer_age_enabled (); +- float fb_scale; +- gboolean scale_is_fractional; +- +- fb_scale = clutter_stage_view_get_scale (view); +- if (fb_scale != floorf (fb_scale)) +- scale_is_fractional = TRUE; +- else +- scale_is_fractional = FALSE; +- +- /* +- * Buffer damage is tracked in the framebuffer coordinate space +- * using the damage history. When fractional scaling is used, a +- * coordinate on the stage might not correspond to the exact position of any +- * physical pixel, which causes issues when painting using the pick mode. +- * +- * For now, always use the (0, 0) pixel for picking when using fractional +- * framebuffer scaling. +- */ +- if (!has_buffer_age || +- scale_is_fractional || +- !clutter_damage_history_is_age_valid (view_priv->damage_history, 0)) +- { +- *x = 0; +- *y = 0; +- } +- else +- { +- cairo_rectangle_int_t view_layout; +- const cairo_rectangle_int_t *fb_damage; +- +- clutter_stage_view_get_layout (view, &view_layout); +- +- fb_damage = clutter_damage_history_lookup (view_priv->damage_history, 0); +- *x = fb_damage->x / fb_scale; +- *y = fb_damage->y / fb_scale; +- } +-} +- + static void + clutter_stage_window_iface_init (ClutterStageWindowInterface *iface) + { +@@ -994,7 +943,6 @@ clutter_stage_window_iface_init (ClutterStageWindowInterface *iface) + iface->ignoring_redraw_clips = clutter_stage_cogl_ignoring_redraw_clips; + iface->get_redraw_clip_bounds = clutter_stage_cogl_get_redraw_clip_bounds; + iface->redraw = clutter_stage_cogl_redraw; +- iface->get_dirty_pixel = clutter_stage_cogl_get_dirty_pixel; + } + + static void +diff --git a/clutter/clutter/deprecated/clutter-texture.c b/clutter/clutter/deprecated/clutter-texture.c +index bea239f454..2c677b8a44 100644 +--- a/clutter/clutter/deprecated/clutter-texture.c ++++ b/clutter/clutter/deprecated/clutter-texture.c +@@ -572,83 +572,6 @@ gen_texcoords_and_draw_cogl_rectangle (ClutterActor *self, + 0, 0, t_w, t_h); + } + +-static CoglPipeline * +-create_pick_pipeline (ClutterActor *self) +-{ +- ClutterTexture *texture = CLUTTER_TEXTURE (self); +- ClutterTexturePrivate *priv = texture->priv; +- CoglPipeline *pick_pipeline = cogl_pipeline_copy (texture_template_pipeline); +- GError *error = NULL; +- +- if (!cogl_pipeline_set_layer_combine (pick_pipeline, 0, +- "RGBA = " +- " MODULATE (CONSTANT, TEXTURE[A])", +- &error)) +- { +- if (!priv->seen_create_pick_pipeline_warning) +- g_warning ("Error setting up texture combine for shaped " +- "texture picking: %s", error->message); +- priv->seen_create_pick_pipeline_warning = TRUE; +- g_error_free (error); +- cogl_object_unref (pick_pipeline); +- return NULL; +- } +- +- cogl_pipeline_set_blend (pick_pipeline, +- "RGBA = ADD (SRC_COLOR[RGBA], 0)", +- NULL); +- +- cogl_pipeline_set_alpha_test_function (pick_pipeline, +- COGL_PIPELINE_ALPHA_FUNC_EQUAL, +- 1.0); +- +- return pick_pipeline; +-} +- +-static void +-clutter_texture_pick (ClutterActor *self, +- const ClutterColor *color) +-{ +- ClutterTexture *texture = CLUTTER_TEXTURE (self); +- ClutterTexturePrivate *priv = texture->priv; +- CoglFramebuffer *framebuffer = cogl_get_draw_framebuffer (); +- +- if (!clutter_actor_should_pick_paint (self)) +- return; +- +- if (G_LIKELY (priv->pick_with_alpha_supported) && priv->pick_with_alpha) +- { +- CoglColor pick_color; +- +- if (priv->pick_pipeline == NULL) +- priv->pick_pipeline = create_pick_pipeline (self); +- +- if (priv->pick_pipeline == NULL) +- { +- priv->pick_with_alpha_supported = FALSE; +- CLUTTER_ACTOR_CLASS (clutter_texture_parent_class)->pick (self, +- color); +- return; +- } +- +- if (priv->fbo_handle != NULL) +- update_fbo (self); +- +- cogl_color_init_from_4ub (&pick_color, +- color->red, +- color->green, +- color->blue, +- 0xff); +- cogl_pipeline_set_layer_combine_constant (priv->pick_pipeline, +- 0, &pick_color); +- cogl_pipeline_set_layer_texture (priv->pick_pipeline, 0, +- clutter_texture_get_cogl_texture (texture)); +- gen_texcoords_and_draw_cogl_rectangle (self, priv->pick_pipeline, framebuffer); +- } +- else +- CLUTTER_ACTOR_CLASS (clutter_texture_parent_class)->pick (self, color); +-} +- + static void + clutter_texture_paint (ClutterActor *self) + { +@@ -767,12 +690,6 @@ clutter_texture_dispose (GObject *object) + priv->pipeline = NULL; + } + +- if (priv->pick_pipeline != NULL) +- { +- cogl_object_unref (priv->pick_pipeline); +- priv->pick_pipeline = NULL; +- } +- + G_OBJECT_CLASS (clutter_texture_parent_class)->dispose (object); + } + +@@ -944,7 +861,6 @@ clutter_texture_class_init (ClutterTextureClass *klass) + GParamSpec *pspec; + + actor_class->paint = clutter_texture_paint; +- actor_class->pick = clutter_texture_pick; + actor_class->get_paint_volume = clutter_texture_get_paint_volume; + actor_class->realize = clutter_texture_realize; + actor_class->unrealize = clutter_texture_unrealize; +@@ -1263,11 +1179,9 @@ clutter_texture_init (ClutterTexture *self) + priv->repeat_y = FALSE; + priv->sync_actor_size = TRUE; + priv->fbo_handle = NULL; +- priv->pick_pipeline = NULL; + priv->keep_aspect_ratio = FALSE; + priv->pick_with_alpha = FALSE; + priv->pick_with_alpha_supported = TRUE; +- priv->seen_create_pick_pipeline_warning = FALSE; + + if (G_UNLIKELY (texture_template_pipeline == NULL)) + { +@@ -3052,13 +2966,8 @@ clutter_texture_set_pick_with_alpha (ClutterTexture *texture, + if (priv->pick_with_alpha == pick_with_alpha) + return; + +- if (!pick_with_alpha && priv->pick_pipeline != NULL) +- { +- cogl_object_unref (priv->pick_pipeline); +- priv->pick_pipeline = NULL; +- } ++ g_assert (!pick_with_alpha); /* No longer supported */ + +- /* NB: the pick pipeline is created lazily when we first pick */ + priv->pick_with_alpha = pick_with_alpha; + + /* NB: actors are expected to call clutter_actor_queue_redraw when +diff --git a/clutter/tests/conform/actor-pick.c b/clutter/tests/conform/actor-pick.c +index 969b4920ac..2bf5954c73 100644 +--- a/clutter/tests/conform/actor-pick.c ++++ b/clutter/tests/conform/actor-pick.c +@@ -5,7 +5,6 @@ + #define STAGE_HEIGHT 480 + #define ACTORS_X 12 + #define ACTORS_Y 16 +-#define SHIFT_STEP STAGE_WIDTH / ACTORS_X + + typedef struct _State State; + +@@ -20,84 +19,11 @@ struct _State + gboolean pass; + }; + +-struct _ShiftEffect +-{ +- ClutterShaderEffect parent_instance; +-}; +- +-struct _ShiftEffectClass +-{ +- ClutterShaderEffectClass parent_class; +-}; +- +-typedef struct _ShiftEffect ShiftEffect; +-typedef struct _ShiftEffectClass ShiftEffectClass; +- +-#define TYPE_SHIFT_EFFECT (shift_effect_get_type ()) +- +-GType shift_effect_get_type (void); +- +-G_DEFINE_TYPE (ShiftEffect, +- shift_effect, +- CLUTTER_TYPE_SHADER_EFFECT); +- +-static void +-shader_paint (ClutterEffect *effect, +- ClutterEffectPaintFlags flags) +-{ +- ClutterShaderEffect *shader = CLUTTER_SHADER_EFFECT (effect); +- float tex_width; +- ClutterActor *actor = +- clutter_actor_meta_get_actor (CLUTTER_ACTOR_META (effect)); +- +- if (g_test_verbose ()) +- g_debug ("shader_paint"); +- +- clutter_shader_effect_set_shader_source (shader, +- "uniform sampler2D tex;\n" +- "uniform float step;\n" +- "void main (void)\n" +- "{\n" +- " cogl_color_out = texture2D(tex, vec2 (cogl_tex_coord_in[0].s + step,\n" +- " cogl_tex_coord_in[0].t));\n" +- "}\n"); +- +- tex_width = clutter_actor_get_width (actor); +- +- clutter_shader_effect_set_uniform (shader, "tex", G_TYPE_INT, 1, 0); +- clutter_shader_effect_set_uniform (shader, "step", G_TYPE_FLOAT, 1, +- SHIFT_STEP / tex_width); +- +- CLUTTER_EFFECT_CLASS (shift_effect_parent_class)->paint (effect, flags); +-} +- +-static void +-shader_pick (ClutterEffect *effect, +- ClutterEffectPaintFlags flags) +-{ +- shader_paint (effect, flags); +-} +- +-static void +-shift_effect_class_init (ShiftEffectClass *klass) +-{ +- ClutterEffectClass *shader_class = CLUTTER_EFFECT_CLASS (klass); +- +- shader_class->paint = shader_paint; +- shader_class->pick = shader_pick; +-} +- +-static void +-shift_effect_init (ShiftEffect *self) +-{ +-} +- + static const char *test_passes[] = { + "No covering actor", + "Invisible covering actor", + "Clipped covering actor", + "Blur effect", +- "Shift effect", + }; + + static gboolean +@@ -165,30 +91,10 @@ on_timeout (gpointer data) + if (g_test_verbose ()) + g_print ("With blur effect:\n"); + } +- else if (test_num == 4) +- { +- if (!clutter_feature_available (CLUTTER_FEATURE_SHADERS_GLSL)) +- continue; +- +- clutter_actor_hide (over_actor); +- clutter_actor_remove_effect_by_name (CLUTTER_ACTOR (state->stage), +- "blur"); +- +- clutter_actor_add_effect_with_name (CLUTTER_ACTOR (state->stage), +- "shift", +- g_object_new (TYPE_SHIFT_EFFECT, +- NULL)); +- +- if (g_test_verbose ()) +- g_print ("With shift effect:\n"); +- } + + for (y = 0; y < ACTORS_Y; y++) + { +- if (test_num == 4) +- x = 1; +- else +- x = 0; ++ x = 0; + + for (; x < ACTORS_X; x++) + { +@@ -198,9 +104,6 @@ on_timeout (gpointer data) + + pick_x = x * state->actor_width + state->actor_width / 2; + +- if (test_num == 4) +- pick_x -= SHIFT_STEP; +- + actor = + clutter_stage_get_actor_at_pos (CLUTTER_STAGE (state->stage), + CLUTTER_PICK_ALL, +diff --git a/clutter/tests/conform/meson.build b/clutter/tests/conform/meson.build +index a9f2d7e20c..fffc9014c4 100644 +--- a/clutter/tests/conform/meson.build ++++ b/clutter/tests/conform/meson.build +@@ -42,7 +42,6 @@ clutter_conform_tests_deprecated_tests = [ + 'behaviours', + 'group', + 'rectangle', +- 'texture', + ] + + clutter_conform_tests = [] +diff --git a/clutter/tests/conform/texture.c b/clutter/tests/conform/texture.c +deleted file mode 100644 +index 392fd5c47e..0000000000 +--- a/clutter/tests/conform/texture.c ++++ /dev/null +@@ -1,84 +0,0 @@ +-#define CLUTTER_DISABLE_DEPRECATION_WARNINGS +-#include +-#include +- +-static CoglHandle +-make_texture (void) +-{ +- guint32 *data = g_malloc (100 * 100 * 4); +- int x; +- int y; +- +- for (y = 0; y < 100; y ++) +- for (x = 0; x < 100; x++) +- { +- if (x < 50 && y < 50) +- data[y * 100 + x] = 0xff00ff00; +- else +- data[y * 100 + x] = 0xff00ffff; +- } +- return cogl_texture_new_from_data (100, +- 100, +- COGL_TEXTURE_NONE, +- COGL_PIXEL_FORMAT_ARGB_8888, +- COGL_PIXEL_FORMAT_ARGB_8888, +- 400, +- (guchar *)data); +-} +- +-static void +-texture_pick_with_alpha (void) +-{ +- ClutterTexture *tex = CLUTTER_TEXTURE (clutter_texture_new ()); +- ClutterStage *stage = CLUTTER_STAGE (clutter_test_get_stage ()); +- ClutterActor *actor; +- +- clutter_texture_set_cogl_texture (tex, make_texture ()); +- +- clutter_actor_add_child (CLUTTER_ACTOR (stage), CLUTTER_ACTOR (tex)); +- +- clutter_actor_show (CLUTTER_ACTOR (stage)); +- +- if (g_test_verbose ()) +- { +- g_print ("\nstage = %p\n", stage); +- g_print ("texture = %p\n\n", tex); +- } +- +- clutter_texture_set_pick_with_alpha (tex, TRUE); +- if (g_test_verbose ()) +- g_print ("Testing with pick-with-alpha enabled:\n"); +- +- /* This should fall through and hit the stage: */ +- actor = clutter_stage_get_actor_at_pos (stage, CLUTTER_PICK_ALL, 10, 10); +- if (g_test_verbose ()) +- g_print ("actor @ (10, 10) = %p\n", actor); +- g_assert (actor == CLUTTER_ACTOR (stage)); +- +- /* The rest should hit the texture */ +- actor = clutter_stage_get_actor_at_pos (stage, CLUTTER_PICK_ALL, 90, 10); +- if (g_test_verbose ()) +- g_print ("actor @ (90, 10) = %p\n", actor); +- g_assert (actor == CLUTTER_ACTOR (tex)); +- actor = clutter_stage_get_actor_at_pos (stage, CLUTTER_PICK_ALL, 90, 90); +- if (g_test_verbose ()) +- g_print ("actor @ (90, 90) = %p\n", actor); +- g_assert (actor == CLUTTER_ACTOR (tex)); +- actor = clutter_stage_get_actor_at_pos (stage, CLUTTER_PICK_ALL, 10, 90); +- if (g_test_verbose ()) +- g_print ("actor @ (10, 90) = %p\n", actor); +- g_assert (actor == CLUTTER_ACTOR (tex)); +- +- clutter_texture_set_pick_with_alpha (tex, FALSE); +- if (g_test_verbose ()) +- g_print ("Testing with pick-with-alpha disabled:\n"); +- +- actor = clutter_stage_get_actor_at_pos (stage, CLUTTER_PICK_ALL, 10, 10); +- if (g_test_verbose ()) +- g_print ("actor @ (10, 10) = %p\n", actor); +- g_assert (actor == CLUTTER_ACTOR (tex)); +-} +- +-CLUTTER_TEST_SUITE ( +- CLUTTER_TEST_UNIT ("/texture/pick-with-alpha", texture_pick_with_alpha) +-) +diff --git a/src/compositor/meta-surface-actor.c b/src/compositor/meta-surface-actor.c +index ca4ca19a99..814199145a 100644 +--- a/src/compositor/meta-surface-actor.c ++++ b/src/compositor/meta-surface-actor.c +@@ -70,38 +70,23 @@ meta_surface_actor_pick (ClutterActor *actor, + else + { + int n_rects; +- float *rectangles; + int i; +- CoglPipeline *pipeline; +- CoglContext *ctx; +- CoglFramebuffer *fb; +- CoglColor cogl_color; + + n_rects = cairo_region_num_rectangles (priv->input_region); +- rectangles = g_alloca (sizeof (float) * 4 * n_rects); + + for (i = 0; i < n_rects; i++) + { + cairo_rectangle_int_t rect; +- int pos = i * 4; ++ ClutterActorBox box; + + cairo_region_get_rectangle (priv->input_region, i, &rect); + +- rectangles[pos + 0] = rect.x; +- rectangles[pos + 1] = rect.y; +- rectangles[pos + 2] = rect.x + rect.width; +- rectangles[pos + 3] = rect.y + rect.height; ++ box.x1 = rect.x; ++ box.y1 = rect.y; ++ box.x2 = rect.x + rect.width; ++ box.y2 = rect.y + rect.height; ++ clutter_actor_pick_box (actor, &box); + } +- +- ctx = clutter_backend_get_cogl_context (clutter_get_default_backend ()); +- fb = cogl_get_draw_framebuffer (); +- +- cogl_color_init_from_4ub (&cogl_color, color->red, color->green, color->blue, color->alpha); +- +- pipeline = cogl_pipeline_new (ctx); +- cogl_pipeline_set_color (pipeline, &cogl_color); +- cogl_framebuffer_draw_rectangles (fb, pipeline, rectangles, n_rects); +- cogl_object_unref (pipeline); + } + + clutter_actor_iter_init (&iter, actor); +-- +2.29.2 + + +From 254e93de8d60393ca94fa430c0acc6f6a7b9516e Mon Sep 17 00:00:00 2001 +From: Iain Lane +Date: Mon, 9 Sep 2019 10:17:22 +0100 +Subject: [PATCH 3/4] build: Compile with `-ffloat-store` on x86 (32 bit) + +GCC's manpage says that this flag does the following: + + Do not store floating-point variables in registers, and inhibit other + options that might change whether a floating-point value is taken from + a register or memory. + + This option prevents undesirable excess precision on machines such as + the 68000 where the floating registers (of the 68881) keep more + precision than a "double" is supposed to have. Similarly for the x86 + architecture. For most programs, the excess precision does only good, + but a few programs rely on the precise definition of IEEE floating + point. + +We rely on this behaviour in our fork of clutter. When performing +floating point computations on x86, we are getting the wrong results +because of this architecture's use of the CPU's extended (x87, non-IEEE +confirming) precision by default. If we enable `-ffloat-store` here, +then we'll get the same results everywhere by storing into variables +instead of registers. This does not remove the need to be correct when +handling floats, but it does mean we don't need to be more correct than +the IEEE spec requires. + +https://gitlab.gnome.org/GNOME/mutter/merge_requests/785 +--- + meson.build | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/meson.build b/meson.build +index 8ef592bc58..e1edb78ba7 100644 +--- a/meson.build ++++ b/meson.build +@@ -267,6 +267,9 @@ foreach function : required_functions + endif + endforeach + ++if host_machine.cpu_family() == 'x86' ++ add_project_arguments('-ffloat-store', language: 'c') ++endif + add_project_arguments('-D_GNU_SOURCE', language: 'c') + + all_warnings = [ +-- +2.29.2 + + +From 2d42caef14772984344e62ce40957d3b40e1f2b6 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= +Date: Thu, 19 Sep 2019 11:27:50 +0200 +Subject: [PATCH 4/4] stage: Compute view perspective when parameters changed + +Clutter stage used to compute the initial projection using a fixed z +translation which wasn't matching the one we computed in +calculate_z_translation(). +This caused to have a wrong initial projection on startup which was then +correctly recomputed only at the first paint. + +However, since this calculation doesn't depend on view, but only on viewport +size, perspective's fovy and z_near we can safely do this at startup and +only when any of those parameters change. + +Then we can move the computation out _clutter_stage_maybe_setup_viewport() +since the cogl framebuffer viewport sizes aren't affecting this. + +Fixes https://gitlab.gnome.org/GNOME/gnome-shell/issues/1639 +https://gitlab.gnome.org/GNOME/mutter/merge_requests/803 +--- + clutter/clutter/clutter-stage.c | 104 +++++++++++++++----------------- + 1 file changed, 47 insertions(+), 57 deletions(-) + +diff --git a/clutter/clutter/clutter-stage.c b/clutter/clutter/clutter-stage.c +index 7d88d5752f..0cfa87486e 100644 +--- a/clutter/clutter/clutter-stage.c ++++ b/clutter/clutter/clutter-stage.c +@@ -234,6 +234,7 @@ static void capture_view_into (ClutterStage *stage, + cairo_rectangle_int_t *rect, + uint8_t *data, + int stride); ++static void clutter_stage_update_view_perspective (ClutterStage *stage); + + static void clutter_container_iface_init (ClutterContainerIface *iface); + +@@ -2492,29 +2493,6 @@ clutter_stage_init (ClutterStage *self) + clutter_actor_set_background_color (CLUTTER_ACTOR (self), + &default_stage_color); + +- priv->perspective.fovy = 60.0; /* 60 Degrees */ +- priv->perspective.aspect = (float) geom.width / (float) geom.height; +- priv->perspective.z_near = 0.1; +- priv->perspective.z_far = 100.0; +- +- cogl_matrix_init_identity (&priv->projection); +- cogl_matrix_perspective (&priv->projection, +- priv->perspective.fovy, +- priv->perspective.aspect, +- priv->perspective.z_near, +- priv->perspective.z_far); +- cogl_matrix_get_inverse (&priv->projection, +- &priv->inverse_projection); +- cogl_matrix_init_identity (&priv->view); +- cogl_matrix_view_2d_in_perspective (&priv->view, +- priv->perspective.fovy, +- priv->perspective.aspect, +- priv->perspective.z_near, +- 50, /* distance to 2d plane */ +- geom.width, +- geom.height); +- +- + /* FIXME - remove for 2.0 */ + priv->fog.z_near = 1.0; + priv->fog.z_far = 2.0; +@@ -2682,6 +2660,7 @@ clutter_stage_set_perspective (ClutterStage *stage, + priv->has_custom_perspective = TRUE; + + clutter_stage_set_perspective_internal (stage, perspective); ++ clutter_stage_update_view_perspective (stage); + } + + /** +@@ -2808,6 +2787,7 @@ _clutter_stage_set_viewport (ClutterStage *stage, + priv->viewport[2] = width; + priv->viewport[3] = height; + ++ clutter_stage_update_view_perspective (stage); + _clutter_stage_dirty_viewport (stage); + + queue_full_redraw (stage); +@@ -3788,6 +3768,50 @@ calculate_z_translation (float z_near) + + z_near; + } + ++static void ++clutter_stage_update_view_perspective (ClutterStage *stage) ++{ ++ ClutterStagePrivate *priv = stage->priv; ++ ClutterPerspective perspective; ++ float z_2d; ++ ++ perspective = priv->perspective; ++ ++ /* Ideally we want to regenerate the perspective matrix whenever ++ * the size changes but if the user has provided a custom matrix ++ * then we don't want to override it */ ++ if (!priv->has_custom_perspective) ++ { ++ perspective.fovy = 60.0; /* 60 Degrees */ ++ perspective.z_near = 0.1; ++ perspective.aspect = priv->viewport[2] / priv->viewport[3]; ++ z_2d = calculate_z_translation (perspective.z_near); ++ ++ /* NB: z_2d is only enough room for 85% of the stage_height between ++ * the stage and the z_near plane. For behind the stage plane we ++ * want a more consistent gap of 10 times the stage_height before ++ * hitting the far plane so we calculate that relative to the final ++ * height of the stage plane at the z_2d_distance we got... */ ++ perspective.z_far = z_2d + ++ tanf (_DEG_TO_RAD (perspective.fovy / 2.0f)) * z_2d * 20.0f; ++ ++ clutter_stage_set_perspective_internal (stage, &perspective); ++ } ++ else ++ { ++ z_2d = calculate_z_translation (perspective.z_near); ++ } ++ ++ cogl_matrix_init_identity (&priv->view); ++ cogl_matrix_view_2d_in_perspective (&priv->view, ++ perspective.fovy, ++ perspective.aspect, ++ perspective.z_near, ++ z_2d, ++ priv->viewport[2], ++ priv->viewport[3]); ++} ++ + void + _clutter_stage_maybe_setup_viewport (ClutterStage *stage, + ClutterStageView *view) +@@ -3797,7 +3821,6 @@ _clutter_stage_maybe_setup_viewport (ClutterStage *stage, + if (clutter_stage_view_is_dirty_viewport (view)) + { + cairo_rectangle_int_t view_layout; +- ClutterPerspective perspective; + float fb_scale; + float viewport_offset_x; + float viewport_offset_y; +@@ -3805,7 +3828,6 @@ _clutter_stage_maybe_setup_viewport (ClutterStage *stage, + float viewport_y; + float viewport_width; + float viewport_height; +- float z_2d; + + CLUTTER_NOTE (PAINT, + "Setting up the viewport { w:%f, h:%f }", +@@ -3825,38 +3847,6 @@ _clutter_stage_maybe_setup_viewport (ClutterStage *stage, + clutter_stage_view_set_viewport (view, + viewport_x, viewport_y, + viewport_width, viewport_height); +- +- perspective = priv->perspective; +- +- /* Ideally we want to regenerate the perspective matrix whenever +- * the size changes but if the user has provided a custom matrix +- * then we don't want to override it */ +- if (!priv->has_custom_perspective) +- { +- perspective.aspect = priv->viewport[2] / priv->viewport[3]; +- z_2d = calculate_z_translation (perspective.z_near); +- +- /* NB: z_2d is only enough room for 85% of the stage_height between +- * the stage and the z_near plane. For behind the stage plane we +- * want a more consistent gap of 10 times the stage_height before +- * hitting the far plane so we calculate that relative to the final +- * height of the stage plane at the z_2d_distance we got... */ +- perspective.z_far = z_2d + +- tanf (_DEG_TO_RAD (perspective.fovy / 2.0f)) * z_2d * 20.0f; +- +- clutter_stage_set_perspective_internal (stage, &perspective); +- } +- else +- z_2d = calculate_z_translation (perspective.z_near); +- +- cogl_matrix_init_identity (&priv->view); +- cogl_matrix_view_2d_in_perspective (&priv->view, +- perspective.fovy, +- perspective.aspect, +- perspective.z_near, +- z_2d, +- priv->viewport[2], +- priv->viewport[3]); + } + + if (clutter_stage_view_is_dirty_projection (view)) +-- +2.29.2 + diff --git a/SPECS/mutter.spec b/SPECS/mutter.spec index 45e87b0..940675a 100644 --- a/SPECS/mutter.spec +++ b/SPECS/mutter.spec @@ -8,7 +8,7 @@ Name: mutter Version: 3.32.2 -Release: 55%{?dist} +Release: 57%{?dist} Summary: Window and compositing manager based on Clutter License: GPLv2+ @@ -177,6 +177,12 @@ Patch508: 0001-monitor-config-manager-Handle-multiple-builtin-panel.patch Patch509: 0001-clutter-stage-view-Hide-double-buffered-shadowfb-beh.patch Patch510: 0002-cogl-gpu-info-Fix-software-acceleration-detection.patch +# Backport of geometric picking, improving performance and fixing picking +# 10bpc pixel formats (#1919467) +Patch511: geometric-picking.patch + +Patch520: 0001-clutter-Backport-of-touch-mode.patch + BuildRequires: chrpath BuildRequires: pango-devel BuildRequires: startup-notification-devel @@ -318,6 +324,14 @@ desktop-file-validate %{buildroot}/%{_datadir}/applications/%{name}.desktop %{_datadir}/mutter-%{mutter_api_version}/tests %changelog +* Mon Feb 22 2021 Carlos Garnacho - 3.32.2-57 +- Backport touch-mode + Resolves: #1833787 + +* Tue Feb 09 2021 Jonas Ådahl - 3.32.2-56 +- Backport geometric picking patches + Resolves: #1919467 + * Tue Feb 09 2021 Jonas Ådahl - 3.32.2-55 - Fix slow nouveau with llvmpipe Resolves: #1921151