Blob Blame History Raw
From ac946bf95ce3e4dc900f72dcb4189dd49bdb3155 Mon Sep 17 00:00:00 2001
From: Daniel van Vugt <daniel.van.vugt@canonical.com>
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 <daniel.van.vugt@canonical.com>
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 <clutter/clutter.h>
-#include <string.h>
-
-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 <laney@debian.org>
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?= <mail@3v1n0.net>
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