[clutter/wip/correct-opacity: 2/9] clutter-stage: Simplify queue redraws



commit 6679d928bbb15008df4ae796f7b1d9fddeafd1c0
Author: Neil Roberts <neil linux intel com>
Date:   Thu Feb 24 14:53:18 2011 +0000

    clutter-stage: Simplify queue redraws
    
    This patch separates out the queue redraw signal from Clutter's
    tracking of dirty actors. Previously queuing a redraw with a clip
    would store the clip volume in a list so that it could be passed back
    to the actor as an out-of-band paramater when the queue-redraw signal
    actually gets fired. Instead of this the stage now handles the clip
    volume to update the dirty region for the actor immediately without
    worrying about waiting until the queue-redraw signal is actually
    fired. This avoids the need to pass all of the data out-of-band. There
    is still a single out-of-band boolean to mark when the signal is being
    emitted from a delayed queue redraw. This is needed to clear the clip
    if the application directly emits the signal or if the signal is
    emitted by chaining up from a child actor.
    
    The stage maintains a list of dirty actors which is now directly a
    GList of ClutterActor pointers rather than having a separate struct to
    contain the clip volume and a pointer to the actor. There is now a
    ClutterActorRedrawState struct which is directly stored inline in the
    priv struct of the ClutterActor. The clip volume for the actor is
    stored here. The intention is that ClutterActor could also store more
    information about the redraw state here too.
    
    Before painting, the stage emits the queue-redraw signal for all of
    the dirty actors without trying to pass the out-of-band data. Clutter
    itself no longer does any processing during the queue-redraw signal
    and instead it's intended just for applications and to implement
    ClutterClones.
    
    The stage separately calculates the clip region in stage coordinates
    by walking the list of dirty actors after the queue-redraw signal is
    emitted. At this point it will transform the actor's clip region into
    stage coordinates.

 clutter/clutter-actor-private.h |   31 +++-
 clutter/clutter-actor.c         |  310 ++++++++++-----------------
 clutter/clutter-stage-private.h |   12 +-
 clutter/clutter-stage.c         |  449 ++++++++++++++++++++++-----------------
 4 files changed, 406 insertions(+), 396 deletions(-)
---
diff --git a/clutter/clutter-actor-private.h b/clutter/clutter-actor-private.h
index b598008..4d79589 100644
--- a/clutter/clutter-actor-private.h
+++ b/clutter/clutter-actor-private.h
@@ -23,6 +23,7 @@
 #define __CLUTTER_ACTOR_PRIVATE_H__
 
 #include <clutter/clutter-actor.h>
+#include <clutter/clutter-paint-volume-private.h>
 
 G_BEGIN_DECLS
 
@@ -108,6 +109,25 @@ typedef ClutterActorTraverseVisitFlags (*ClutterTraverseCallback) (ClutterActor
 typedef gboolean (*ClutterForeachCallback) (ClutterActor *actor,
                                             gpointer      user_data);
 
+/* State that describes what needs to be redrawn for this actor */
+typedef struct _ClutterActorRedrawState
+{
+  /* Whether any this actor has already been added to its stage's
+     dirty list. If this is FALSE then the other fields are invalid */
+  gboolean is_dirty;
+
+  /* Whether the queue redraw signal has been emitted for this
+     actor. The Clutter stage emits queue redraws for all of the dirty
+     actors just before painting. This flag is used to avoid emitting
+     the signal twice */
+  gboolean queue_redraw_emitted;
+
+  /* If has_clip == FALSE then the full actor should be redrawn and
+     clip is invalid */
+  gboolean has_clip;
+  ClutterPaintVolume clip;
+} ClutterActorRedrawState;
+
 gint          _clutter_actor_get_n_children             (ClutterActor *self);
 gboolean      _clutter_actor_foreach_child              (ClutterActor *self,
                                                          ClutterForeachCallback callback,
@@ -147,11 +167,12 @@ void _clutter_actor_set_has_pointer (ClutterActor *self,
 void _clutter_actor_queue_redraw_with_clip   (ClutterActor              *self,
                                               ClutterRedrawFlags         flags,
                                               ClutterPaintVolume        *clip_volume);
-const ClutterPaintVolume *_clutter_actor_get_queue_redraw_clip (ClutterActor *self);
-void _clutter_actor_set_queue_redraw_clip     (ClutterActor             *self,
-                                               const ClutterPaintVolume *clip_volume);
-void _clutter_actor_finish_queue_redraw       (ClutterActor             *self,
-                                               ClutterPaintVolume       *clip);
+
+void _clutter_actor_finish_queue_redraw       (ClutterActor             *self);
+
+ClutterActorRedrawState *_clutter_actor_get_redraw_state (ClutterActor *self);
+
+const ClutterActorBox *_clutter_actor_get_last_paint_box (ClutterActor *self);
 
 gboolean           _clutter_actor_set_default_paint_volume (ClutterActor *self,
                                                             GType         check_gtype,
diff --git a/clutter/clutter-actor.c b/clutter/clutter-actor.c
index cff890f..26be357 100644
--- a/clutter/clutter-actor.c
+++ b/clutter/clutter-actor.c
@@ -456,12 +456,6 @@ struct _ClutterActorPrivate
 
   gint internal_child;
 
-  /* XXX: This is a workaround for not being able to break the ABI
-   * of the QUEUE_REDRAW signal. It's an out-of-band argument.
-   * See clutter_actor_queue_clipped_redraw() for details.
-   */
-  const ClutterPaintVolume *oob_queue_redraw_clip;
-
   ClutterMetaGroup *actions;
   ClutterMetaGroup *constraints;
   ClutterMetaGroup *effects;
@@ -478,7 +472,15 @@ struct _ClutterActorPrivate
 
   ClutterActorBox last_paint_box;
 
-  ClutterStageQueueRedrawEntry *queue_redraw_entry;
+  /* State used by ClutterStage to record what the dirty region of the
+     actor is */
+  ClutterActorRedrawState redraw_state;
+
+  /* This is used as an out-of-band argument passed to the
+     queue-redraw signal so that it can detect when an application
+     directly emits the queue-redraw signal without going through the
+     delayed mechanism from clutter_actor_queue_redraw() */
+  gboolean from_delayed_queue_redraw;
 };
 
 enum
@@ -1825,26 +1827,10 @@ clutter_actor_real_allocate (ClutterActor           *self,
 }
 
 static void
-_clutter_actor_signal_queue_redraw (ClutterActor *self,
-                                    ClutterActor *origin)
-{
-  /* no point in queuing a redraw on a destroyed actor */
-  if (CLUTTER_ACTOR_IN_DESTRUCTION (self))
-    return;
-
-  /* NB: We can't bail out early here if the actor is hidden in case
-   * the actor bas been cloned. In this case the clone will need to
-   * receive the signal so it can queue its own redraw.
-   */
-
-  /* calls klass->queue_redraw in default handler */
-  g_signal_emit (self, actor_signals[QUEUE_REDRAW], 0, origin);
-}
-
-static void
 clutter_actor_real_queue_redraw (ClutterActor *self,
                                  ClutterActor *origin)
 {
+  ClutterActorPrivate *priv = self->priv;
   ClutterActor *parent;
 
   CLUTTER_NOTE (PAINT, "Redraw queued on '%s' (from: '%s')",
@@ -1856,6 +1842,19 @@ clutter_actor_real_queue_redraw (ClutterActor *self,
   if (CLUTTER_ACTOR_IN_DESTRUCTION (self))
     return;
 
+  /* If the queue-redraw signal was directly emitted by an application
+     or by chaining up from a child actor then we need to clear the
+     clip */
+  if (!priv->from_delayed_queue_redraw)
+    {
+      ClutterActor *stage = _clutter_actor_get_stage_internal (self);
+
+      if (stage)
+        _clutter_stage_add_dirty_actor (CLUTTER_STAGE (stage),
+                                        self,
+                                        NULL);
+    }
+
   /* If the actor isn't visible, we still had to emit the signal
    * to allow for a ClutterClone, but the appearance of the parent
    * won't change so we don't have to propagate up the hierarchy.
@@ -1870,7 +1869,7 @@ clutter_actor_real_queue_redraw (ClutterActor *self,
    * container that tracks which of its children have queued a
    * redraw.
    */
-  if (self->priv->propagated_one_redraw)
+  if (priv->propagated_one_redraw)
     {
       ClutterActor *stage = _clutter_actor_get_stage_internal (self);
       if (stage != NULL &&
@@ -1878,17 +1877,13 @@ clutter_actor_real_queue_redraw (ClutterActor *self,
         return;
     }
 
-  self->priv->propagated_one_redraw = TRUE;
+  priv->propagated_one_redraw = TRUE;
 
-  /* notify parents, if they are all visible eventually we'll
-   * queue redraw on the stage, which queues the redraw idle.
-   */
+  /* recursively notify parents */
   parent = clutter_actor_get_parent (self);
   if (parent != NULL)
-    {
-      /* this will go up recursively */
-      _clutter_actor_signal_queue_redraw (parent, origin);
-    }
+    /* calls klass->queue_redraw in default handler */
+    g_signal_emit (parent, actor_signals[QUEUE_REDRAW], 0, origin);
 }
 
 void
@@ -4961,86 +4956,18 @@ clutter_actor_destroy (ClutterActor *self)
 }
 
 void
-_clutter_actor_finish_queue_redraw (ClutterActor *self,
-                                    ClutterPaintVolume *clip)
+_clutter_actor_finish_queue_redraw (ClutterActor *self)
 {
   ClutterActorPrivate *priv = self->priv;
-  const ClutterPaintVolume *pv;
-  gboolean clipped;
-
-  /* If we've been explicitly passed a clip volume then there's
-   * nothing more to calculate, but otherwhise the only thing we know
-   * is that the change is constrained to the given actor.
-   *
-   * The idea is that if we know the paint box for where the actor was
-   * last drawn and we also have the paint volume for where it will be
-   * drawn next then if we queue a redraw for both these regions that
-   * will cover everything that needs to be redrawn to clear the old
-   * view and show the latest view of the actor.
-   *
-   * Don't clip this redraw if we don't know what position we had for
-   * the previous redraw since we don't know where to set the clip so
-   * it will clear the actor as it is currently.
-   */
-  if (clip)
-    {
-      _clutter_actor_set_queue_redraw_clip (self, clip);
-      clipped = TRUE;
-    }
-  else if (G_LIKELY (priv->last_paint_box_valid))
-    {
-      pv = clutter_actor_get_paint_volume (self);
-      if (pv)
-        {
-          ClutterActor *stage = _clutter_actor_get_stage_internal (self);
-          ClutterPaintVolume stage_pv;
-          ClutterActorBox *box = &priv->last_paint_box;
-          ClutterVertex origin;
-
-          _clutter_paint_volume_init_static (stage, &stage_pv);
 
-          origin.x = box->x1;
-          origin.y = box->y1;
-          origin.z = 0;
-          clutter_paint_volume_set_origin (&stage_pv, &origin);
-          clutter_paint_volume_set_width (&stage_pv, box->x2 - box->x1);
-          clutter_paint_volume_set_height (&stage_pv, box->y2 - box->y1);
+  /* Mark the redraw is coming from a delayed queue redraw so that the
+     handler can detect when this signal is being directly by an
+     application. In this case it needs to remove any clip */
+  priv->from_delayed_queue_redraw = TRUE;
 
-          /* make sure we redraw the actors old position... */
-          _clutter_actor_set_queue_redraw_clip (stage, &stage_pv);
-          _clutter_actor_signal_queue_redraw (stage, stage);
-          _clutter_actor_set_queue_redraw_clip (stage, NULL);
+  g_signal_emit (self, actor_signals[QUEUE_REDRAW], 0, self);
 
-          clutter_paint_volume_free (&stage_pv);
-
-          /* XXX: Ideally the redraw signal would take a clip volume
-           * argument, but that would be an ABI break. Until we can
-           * break the ABI we pass the argument out-of-band via an
-           * actor->priv member...
-           */
-
-          /* setup the clip for the actors new position... */
-          _clutter_actor_set_queue_redraw_clip (self, pv);
-          clipped = TRUE;
-        }
-      else
-        clipped = FALSE;
-    }
-  else
-    clipped = FALSE;
-
-  _clutter_actor_signal_queue_redraw (self, self);
-
-  /* Just in case anyone is manually firing redraw signals without
-   * using the public queue_redraw() API we are careful to ensure that
-   * our out-of-band clip member is cleared before returning...
-   *
-   * Note: A NULL clip denotes a full-stage, un-clipped redraw
-   */
-  if (G_LIKELY (clipped))
-    _clutter_actor_set_queue_redraw_clip (self, NULL);
-
-  priv->queue_redraw_entry = NULL;
+  priv->from_delayed_queue_redraw = FALSE;
 }
 
 /**
@@ -5067,8 +4994,6 @@ _clutter_actor_finish_queue_redraw (ClutterActor *self,
 void
 clutter_actor_queue_redraw (ClutterActor *self)
 {
-  ClutterActor *stage;
-
   /* Here's an outline of the actor queue redraw mechanism:
    *
    * The process starts either here or in
@@ -5077,7 +5002,9 @@ clutter_actor_queue_redraw (ClutterActor *self)
    * These functions queue an entry in a list associated with the
    * stage which is a list of actors that queued a redraw while
    * updating the timelines, performing layouting and processing other
-   * mainloop sources before the next paint starts.
+   * mainloop sources before the next paint starts. The clipped region
+   * of the redraw is marked in actor space (so no transformation is
+   * applied) and stored in the 'redraw state' of the actor.
    *
    * We aim to minimize the processing done at this point because
    * there is a good chance other events will happen while updating
@@ -5088,10 +5015,17 @@ clutter_actor_queue_redraw (ClutterActor *self)
    * something else will happen which will force a full redraw anyway.
    *
    * When all updates are complete and we come to paint the stage then
-   * we iterate this list and actually emit the "queue-redraw" signals
-   * for each of the listed actors which will bubble up to the stage
-   * for each actor and at that point we will transform the actors
-   * paint volume into screen coordinates to determine the clip region
+   * we iterate this list and actually emit the "queue-redraw"
+   * signals. This gives applications a chance to notice dirty actors
+   * before the paint and it may cause more actors to be marked
+   * dirty. Clutter itself does very little work in the queue redraw
+   * signal. We don't want to emit the queue-redraw signal for an
+   * actor more than once per frame because the cost of signal
+   * emission can be quite high and the application itself may be
+   * doing expensive work when handling the signal.
+   *
+   * After emitting all of the queue-redraw signals the stage will
+   * walk the list of dirty actors to calculate the clip region for
    * for what needs to be redrawn in the next paint.
    *
    * Besides minimizing redundant work another reason for this
@@ -5106,44 +5040,29 @@ clutter_actor_queue_redraw (ClutterActor *self)
    * clutter_actor_queue_redraw and
    * _clutter_actor_queue_redraw_with_clip
    *
-   * then control moves to:
-   *   _clutter_stage_queue_actor_redraw
+   * these both call _clutter_stage_add_dirty_actor to record the
+   * actor in a list of actors that need to be painted.
    *
    * later during _clutter_stage_do_update, once relayouting is done
-   * and the scenegraph has been updated we will call:
-   * _clutter_stage_finish_queue_redraws
+   * the stage will call _clutter_actor_finish_queue_redraw on
+   * every dirty actor exactly once.
    *
-   * _clutter_stage_finish_queue_redraws will call
-   * _clutter_actor_finish_queue_redraw for each listed actor.
    * Note: actors *are* allowed to queue further redraws during this
    * process (considering clone actors or texture_new_from_actor which
    * respond to their source queueing a redraw by queuing a redraw
    * themselves). We repeat the process until the list is empty.
    *
-   * This will result in the "queue-redraw" signal being fired for
-   * each actor which will pass control to the default signal handler:
-   * clutter_actor_real_queue_redraw
-   *
-   * This will bubble up to the stages handler:
-   * clutter_stage_real_queue_redraw
-   *
-   * clutter_stage_real_queue_redraw will transform the actors paint
-   * volume into screen space and add it as a clip region for the next
-   * paint.
+   * After the signals are emitted, the stage will call
+   * clutter_stage_calculate_clip to calculate a list of dirty screen
+   * rectangles by projecting the dirty regions of the actors into
+   * screen space.
    */
 
   g_return_if_fail (CLUTTER_IS_ACTOR (self));
 
-  /* Ignore queuing a redraw for actors not descended from a stage */
-  stage = _clutter_actor_get_stage_internal (self);
-  if (stage == NULL)
-    return;
-
-  self->priv->queue_redraw_entry =
-    _clutter_stage_queue_actor_redraw (CLUTTER_STAGE (stage),
-                                       self->priv->queue_redraw_entry,
-                                       self,
-                                       NULL);
+  _clutter_actor_queue_redraw_with_clip (self,
+                                         0, /* flags */
+                                         NULL /* clip volume (no clip) */);
 }
 
 static void
@@ -5221,6 +5140,10 @@ _clutter_actor_queue_redraw_with_clip (ClutterActor       *self,
 
   g_return_if_fail (CLUTTER_IS_ACTOR (self));
 
+  /* There's no point in queueing a redraw on a destroyed actor */
+  if (CLUTTER_ACTOR_IN_DESTRUCTION (self))
+    return;
+
   if (flags & CLUTTER_REDRAW_CLIPPED_TO_ALLOCATION)
     {
       ClutterActorBox allocation_clip;
@@ -5232,26 +5155,28 @@ _clutter_actor_queue_redraw_with_clip (ClutterActor       *self,
         {
           /* NB: NULL denotes an undefined clip which will result in a
            * full redraw... */
-          _clutter_actor_set_queue_redraw_clip (self, NULL);
-          _clutter_actor_signal_queue_redraw (self, self);
-          return;
+          pv = NULL;
+          should_free_pv = FALSE;
         }
+      else
+        {
+          _clutter_paint_volume_init_static (self, &allocation_pv);
+          pv = &allocation_pv;
 
-      _clutter_paint_volume_init_static (self, &allocation_pv);
-      pv = &allocation_pv;
-
-      _clutter_actor_get_allocation_clip (self, &allocation_clip);
+          _clutter_actor_get_allocation_clip (self, &allocation_clip);
 
-      origin.x = allocation_clip.x1;
-      origin.y = allocation_clip.y1;
-      origin.z = 0;
-      clutter_paint_volume_set_origin (pv, &origin);
-      clutter_paint_volume_set_width (pv,
-                                      allocation_clip.x2 - allocation_clip.x1);
-      clutter_paint_volume_set_height (pv,
-                                       allocation_clip.y2 -
-                                       allocation_clip.y1);
-      should_free_pv = TRUE;
+          origin.x = allocation_clip.x1;
+          origin.y = allocation_clip.y1;
+          origin.z = 0;
+          clutter_paint_volume_set_origin (pv, &origin);
+          clutter_paint_volume_set_width (pv,
+                                          allocation_clip.x2 -
+                                          allocation_clip.x1);
+          clutter_paint_volume_set_height (pv,
+                                           allocation_clip.y2 -
+                                           allocation_clip.y1);
+          should_free_pv = TRUE;
+        }
     }
   else
     {
@@ -5263,15 +5188,34 @@ _clutter_actor_queue_redraw_with_clip (ClutterActor       *self,
   stage = _clutter_actor_get_stage_internal (self);
 
   if (stage != NULL)
-    _clutter_stage_queue_actor_redraw (CLUTTER_STAGE (stage),
-                                       self->priv->queue_redraw_entry,
-                                       self,
-                                       pv);
+    /* Add the actor to the list of dirty actors from the stage. */
+    _clutter_stage_add_dirty_actor (CLUTTER_STAGE (stage),
+                                    self,
+                                    pv);
 
   if (should_free_pv)
     clutter_paint_volume_free (pv);
 }
 
+const ClutterActorBox *
+_clutter_actor_get_last_paint_box (ClutterActor *self)
+{
+  ClutterActorPrivate *priv = self->priv;
+
+  if (priv->last_paint_box_valid)
+    return &priv->last_paint_box;
+  else
+    return NULL;
+}
+
+ClutterActorRedrawState *
+_clutter_actor_get_redraw_state (ClutterActor *self)
+{
+  ClutterActorPrivate *priv = self->priv;
+
+  return &priv->redraw_state;
+}
+
 static void
 _clutter_actor_queue_only_relayout (ClutterActor *self)
 {
@@ -7701,17 +7645,16 @@ clutter_actor_get_paint_visibility (ClutterActor *actor)
 }
 
 static ClutterActorTraverseVisitFlags
-invalidate_queue_redraw_entry (ClutterActor *self,
-                               int           depth,
-                               gpointer      user_data)
+remove_dirty_actor (ClutterActor *self,
+                    int           depth,
+                    gpointer      user_data)
 {
-  ClutterActorPrivate *priv = self->priv;
+  ClutterActor *stage;
 
-  if (priv->queue_redraw_entry != NULL)
-    {
-      _clutter_stage_queue_redraw_entry_invalidate (priv->queue_redraw_entry);
-      priv->queue_redraw_entry = NULL;
-    }
+  stage = clutter_actor_get_stage (self);
+
+  if (stage)
+    _clutter_stage_remove_dirty_actor (CLUTTER_STAGE (stage), self);
 
   return CLUTTER_ACTOR_TRAVERSE_VISIT_CONTINUE;
 }
@@ -7745,12 +7688,12 @@ clutter_actor_unparent (ClutterActor *self)
   if (priv->parent_actor == NULL)
     return;
 
-   /* We take this opportunity to invalidate any queue redraw entry
-    * associated with the actor and descendants since we won't be able to
-    * determine the appropriate stage after this. */
+   /* We take this opportunity to remove any dirtiness associated with
+    * the actor and descendants since we won't be able to determine
+    * the appropriate stage after this. */
   _clutter_actor_traverse (self,
                            0,
-                           invalidate_queue_redraw_entry,
+                           remove_dirty_actor,
                            NULL,
                            NULL);
 
@@ -10819,23 +10762,6 @@ clutter_actor_has_pointer (ClutterActor *self)
   return self->priv->has_pointer;
 }
 
-/* XXX: This is a workaround for not being able to break the ABI of
- * the QUEUE_REDRAW signal. It is an out-of-band argument.  See
- * clutter_actor_queue_clipped_redraw() for details.
- */
-const ClutterPaintVolume *
-_clutter_actor_get_queue_redraw_clip (ClutterActor *self)
-{
-  return self->priv->oob_queue_redraw_clip;
-}
-
-void
-_clutter_actor_set_queue_redraw_clip (ClutterActor *self,
-                                      const ClutterPaintVolume *clip)
-{
-  self->priv->oob_queue_redraw_clip = clip;
-}
-
 /**
  * clutter_actor_has_allocation:
  * @self: a #ClutterActor
diff --git a/clutter/clutter-stage-private.h b/clutter/clutter-stage-private.h
index 3217a82..f9858c3 100644
--- a/clutter/clutter-stage-private.h
+++ b/clutter/clutter-stage-private.h
@@ -28,8 +28,6 @@
 
 G_BEGIN_DECLS
 
-typedef struct _ClutterStageQueueRedrawEntry ClutterStageQueueRedrawEntry;
-
 /* stage */
 ClutterStageWindow *_clutter_stage_get_default_window    (void);
 void                _clutter_stage_do_paint              (ClutterStage          *stage,
@@ -77,11 +75,11 @@ void                _clutter_stage_paint_volume_stack_free_all (ClutterStage *st
 
 const ClutterGeometry *_clutter_stage_get_clip (ClutterStage *stage);
 
-ClutterStageQueueRedrawEntry *_clutter_stage_queue_actor_redraw            (ClutterStage                 *stage,
-                                                                            ClutterStageQueueRedrawEntry *entry,
-                                                                            ClutterActor                 *actor,
-                                                                            ClutterPaintVolume           *clip);
-void                          _clutter_stage_queue_redraw_entry_invalidate (ClutterStageQueueRedrawEntry *entry);
+void            _clutter_stage_add_dirty_actor    (ClutterStage       *stage,
+                                                   ClutterActor       *actor,
+                                                   const ClutterPaintVolume *clip);
+void            _clutter_stage_remove_dirty_actor (ClutterStage       *stage,
+                                                   ClutterActor       *actor);
 
 void            _clutter_stage_add_device       (ClutterStage       *stage,
                                                  ClutterInputDevice *device);
diff --git a/clutter/clutter-stage.c b/clutter/clutter-stage.c
index 5832b72..ddab834 100644
--- a/clutter/clutter-stage.c
+++ b/clutter/clutter-stage.c
@@ -100,13 +100,6 @@ typedef enum { /*< prefix=CLUTTER_STAGE >*/
 
 #define STAGE_NO_CLEAR_ON_PAINT(s)      ((((ClutterStage *) (s))->priv->stage_hints & CLUTTER_STAGE_NO_CLEAR_ON_PAINT) != 0)
 
-struct _ClutterStageQueueRedrawEntry
-{
-  ClutterActor *actor;
-  gboolean has_clip;
-  ClutterPaintVolume clip;
-};
-
 struct _ClutterStagePrivate
 {
   /* the stage implementation */
@@ -131,7 +124,7 @@ struct _ClutterStagePrivate
 
   const ClutterGeometry *current_paint_clip;
 
-  GList              *pending_queue_redraws;
+  GList              *dirty_actors;
 
   ClutterPickMode     pick_buffer_mode;
 
@@ -194,11 +187,6 @@ static guint stage_signals[LAST_SIGNAL] = { 0, };
 
 static const ClutterColor default_stage_color = { 255, 255, 255, 255 };
 
-static void _clutter_stage_maybe_finish_queue_redraws (ClutterStage *stage);
-
-static void
-_clutter_stage_maybe_finish_queue_redraws (ClutterStage *stage);
-
 static void
 clutter_stage_get_preferred_width (ClutterActor *self,
                                    gfloat        for_height,
@@ -852,6 +840,180 @@ clutter_stage_do_redraw (ClutterStage *stage)
                      stage);
 }
 
+static void
+clutter_stage_calculate_clip (ClutterStage *stage)
+{
+  ClutterStagePrivate *priv = stage->priv;
+  GList *l;
+  int i;
+  ClutterStageWindow *stage_window;
+  CoglMatrix stage_modelview;
+  struct
+  {
+    const ClutterActorBox *last_box;
+    const ClutterPaintVolume *current;
+  } *actor_data;
+
+  stage_window = _clutter_stage_get_window (stage);
+  if (stage_window == NULL)
+    return;
+
+  /* If the stage window is ignoring redraw clips then there's no
+     point in calculating anything */
+  if (_clutter_stage_window_ignoring_redraw_clips (stage_window))
+    return;
+
+  /* If there are no dirty actors then something has probably called
+     clutter_stage_ensure_redraw even though nothing has been
+     queued. In this case we'll just redraw the whole stage */
+  if (priv->dirty_actors == NULL)
+    {
+      _clutter_stage_window_add_redraw_clip (stage_window, NULL);
+      return;
+    }
+
+  /* If any actors don't have a clip and we can't determine their
+     paint volume then we need to queue a full redraw and there's no
+     point in calculating any further so we check for this
+     first. Calculating the paint volume can be expensive so we'll
+     only do it once and store the results in a temporary array */
+
+  actor_data = g_alloca (sizeof (*actor_data) *
+                         g_list_length (priv->dirty_actors));
+
+  for (i = 0, l = priv->dirty_actors; l; i++, l = l->next)
+    {
+      ClutterActor *actor = l->data;
+      ClutterActorRedrawState *redraw_state =
+        _clutter_actor_get_redraw_state (actor);
+
+      if (redraw_state->has_clip)
+        {
+          actor_data[i].last_box = NULL;
+          actor_data[i].current = &redraw_state->clip;
+        }
+      else
+        {
+          actor_data[i].last_box =
+            _clutter_actor_get_last_paint_box (actor);
+
+          if (G_UNLIKELY (actor_data[i].last_box == NULL))
+            {
+              _clutter_stage_window_add_redraw_clip (stage_window, NULL);
+              return;
+            }
+
+          actor_data[i].current =
+            clutter_actor_get_paint_volume (actor);
+
+          if (actor_data[i].current == NULL)
+            {
+              _clutter_stage_window_add_redraw_clip (stage_window, NULL);
+              return;
+            }
+        }
+    }
+
+  /* Once we get here then we know we can calculate the clips for the
+     redraw */
+
+  /* NB: _clutter_actor_apply_modelview_transform_recursive will never
+   * include the transformation between stage coordinates and OpenGL
+   * window coordinates, we have to explicitly use the
+   * stage->apply_transform to get that... */
+  cogl_matrix_init_identity (&stage_modelview);
+  _clutter_actor_apply_modelview_transform (CLUTTER_ACTOR (stage),
+                                            &stage_modelview);
+
+  for (i = 0, l = priv->dirty_actors; l; i++, l = l->next)
+    {
+      ClutterActor *actor = l->data;
+      ClutterPaintVolume projected_clip;
+      CoglMatrix modelview;
+      ClutterActorBox bounding_box;
+      ClutterGeometry stage_clip;
+
+      /* Add the clip for the last position of the actor */
+      if (actor_data[i].last_box)
+        {
+          ClutterGeometry last_geometry;
+          const ClutterActorBox *last_box = actor_data[i].last_box;
+          last_geometry.x = last_box->x1;
+          last_geometry.y = last_box->y1;
+          last_geometry.width = last_box->x2 - last_box->x1;
+          last_geometry.height = last_box->y2 - last_box->y1;
+          _clutter_stage_window_add_redraw_clip (stage_window, &last_geometry);
+        }
+
+      _clutter_paint_volume_copy_static (actor_data[i].current,
+                                         &projected_clip);
+
+      modelview = stage_modelview;
+      _clutter_actor_apply_modelview_transform_recursive (actor, NULL,
+                                                          &modelview);
+
+      _clutter_paint_volume_project (&projected_clip,
+                                     &modelview,
+                                     &priv->projection,
+                                     priv->viewport);
+
+      _clutter_paint_volume_get_bounding_box (&projected_clip, &bounding_box);
+      clutter_paint_volume_free (&projected_clip);
+
+      clutter_actor_box_clamp_to_pixel (&bounding_box);
+
+      /* when converting to integer coordinates make sure we round the
+       * edges of the clip rectangle outwards... */
+      stage_clip.x = bounding_box.x1;
+      stage_clip.y = bounding_box.y1;
+      stage_clip.width = bounding_box.x2 - stage_clip.x;
+      stage_clip.height = bounding_box.y2 - stage_clip.y;
+
+      _clutter_stage_window_add_redraw_clip (stage_window, &stage_clip);
+
+      /* Adding a new clip might make the stage start ignoring
+         subsequent clips if the clip contained the entire stage so we
+         should check for this and stop calculating any further if
+         so */
+      if (_clutter_stage_window_ignoring_redraw_clips (stage_window))
+        break;
+    }
+}
+
+static void
+_clutter_stage_emit_queue_redraw_signals (ClutterStage *stage)
+{
+  ClutterStagePrivate *priv = stage->priv;
+  GList *handled_actors = NULL;
+
+  /* We need to emit a queue redraw signal for every actor that has
+     been marked as dirty exactly once. The queue redraw signal may
+     also cause other actors to become dirty so we need to process the
+     list from the head. We'll build up a separate list of the actors
+     for which we've already emitted a signal and steal them from the
+     list to avoid emitting the signal again. If the same actor is
+     queued again during an emission then it won't get added to the
+     list again because the is_dirty member of the redraw state will
+     already be TRUE */
+  while (priv->dirty_actors)
+    {
+      GList *node = priv->dirty_actors;
+
+      /* Move the node to the handled actors list */
+      priv->dirty_actors = g_list_remove_link (priv->dirty_actors,
+                                               priv->dirty_actors);
+      /* There is no g_list_add_link but this does the equivalent */
+      node->next = handled_actors;
+      node->prev = NULL;
+      handled_actors = node;
+
+      _clutter_actor_finish_queue_redraw (node->data);
+    }
+
+  /* Put the list of dirty actors back in place */
+  priv->dirty_actors = handled_actors;
+}
+
 /**
  * _clutter_stage_do_update:
  * @stage: A #ClutterStage
@@ -881,7 +1043,19 @@ _clutter_stage_do_update (ClutterStage *stage)
   if (!priv->redraw_pending)
     return FALSE;
 
-  _clutter_stage_maybe_finish_queue_redraws (stage);
+  /* We notify the all of the queue redraws for the dirty actors only
+     once right before the paint. This is to avoid redundantly
+     emitting the signal multiple times per frame */
+  _clutter_stage_emit_queue_redraw_signals (stage);
+
+  /* If all of the dirty actors have a clip then we can also clip the
+     redraw. This will add the clip redraw boxes to the stage
+     window */
+  clutter_stage_calculate_clip (stage);
+
+  /* Clear the dirty actor list */
+  while (priv->dirty_actors)
+    _clutter_stage_remove_dirty_actor (stage, priv->dirty_actors->data);
 
   clutter_stage_do_redraw (stage);
 
@@ -891,7 +1065,7 @@ _clutter_stage_do_update (ClutterStage *stage)
 #ifdef CLUTTER_ENABLE_DEBUG
   if (priv->redraw_count > 0)
     {
-      CLUTTER_NOTE (SCHEDULER, "Queued %lu redraws during the last cycle",
+      CLUTTER_NOTE (SCHEDULER, "Queued %lu dirty actors during the last cycle",
                     priv->redraw_count);
 
       priv->redraw_count = 0;
@@ -915,78 +1089,6 @@ clutter_stage_real_queue_relayout (ClutterActor *self)
   parent_class->queue_relayout (self);
 }
 
-static void
-clutter_stage_real_queue_redraw (ClutterActor *actor,
-                                 ClutterActor *leaf)
-{
-  ClutterStage *stage = CLUTTER_STAGE (actor);
-  ClutterStagePrivate *priv = stage->priv;
-  ClutterStageWindow *stage_window;
-  ClutterGeometry stage_clip;
-  const ClutterPaintVolume *redraw_clip;
-  ClutterPaintVolume projected_clip;
-  CoglMatrix modelview;
-  ClutterActorBox bounding_box;
-
-  if (CLUTTER_ACTOR_IN_DESTRUCTION (actor))
-    return;
-
-  /* If the backend can't do anything with redraw clips (e.g. it already knows
-   * it needs to redraw everything anyway) then don't spend time transforming
-   * any clip volume into stage coordinates... */
-  stage_window = _clutter_stage_get_window (stage);
-  if (stage_window == NULL)
-    return;
-
-  if (_clutter_stage_window_ignoring_redraw_clips (stage_window))
-    {
-      _clutter_stage_window_add_redraw_clip (stage_window, NULL);
-      return;
-    }
-
-  /* Convert the clip volume (which is in leaf actor coordinates) into stage
-   * coordinates and then into an axis aligned stage coordinates bounding
-   * box...
-   */
-
-  if (!_clutter_actor_get_queue_redraw_clip (leaf))
-    {
-      _clutter_stage_window_add_redraw_clip (stage_window, NULL);
-      return;
-    }
-
-  redraw_clip = _clutter_actor_get_queue_redraw_clip (leaf);
-
-  _clutter_paint_volume_copy_static (redraw_clip, &projected_clip);
-
-  /* NB: _clutter_actor_apply_modelview_transform_recursive will never
-   * include the transformation between stage coordinates and OpenGL
-   * window coordinates, we have to explicitly use the
-   * stage->apply_transform to get that... */
-  cogl_matrix_init_identity (&modelview);
-  _clutter_actor_apply_modelview_transform (CLUTTER_ACTOR (stage), &modelview);
-  _clutter_actor_apply_modelview_transform_recursive (leaf, NULL, &modelview);
-
-  _clutter_paint_volume_project (&projected_clip,
-                                 &modelview,
-                                 &priv->projection,
-                                 priv->viewport);
-
-  _clutter_paint_volume_get_bounding_box (&projected_clip, &bounding_box);
-  clutter_paint_volume_free (&projected_clip);
-
-  clutter_actor_box_clamp_to_pixel (&bounding_box);
-
-  /* when converting to integer coordinates make sure we round the edges of the
-   * clip rectangle outwards... */
-  stage_clip.x = bounding_box.x1;
-  stage_clip.y = bounding_box.y1;
-  stage_clip.width = bounding_box.x2 - stage_clip.x;
-  stage_clip.height = bounding_box.y2 - stage_clip.y;
-
-  _clutter_stage_window_add_redraw_clip (stage_window, &stage_clip);
-}
-
 gboolean
 _clutter_stage_has_full_redraw_queued (ClutterStage *stage)
 {
@@ -1283,7 +1385,6 @@ clutter_stage_class_init (ClutterStageClass *klass)
   actor_class->show = clutter_stage_show;
   actor_class->hide = clutter_stage_hide;
   actor_class->queue_relayout = clutter_stage_real_queue_relayout;
-  actor_class->queue_redraw = clutter_stage_real_queue_redraw;
   actor_class->apply_transform = clutter_stage_real_apply_transform;
 
   /**
@@ -3229,24 +3330,37 @@ _clutter_stage_get_clip (ClutterStage *stage)
   return stage->priv->current_paint_clip;
 }
 
-/* When an actor queues a redraw we add it to a list on the stage that
- * gets processed once all updates to the stage have been finished.
+/* When an actor queues a redraw we add it to a list of dirty actors
+ * on the stage that gets processed once all updates to the stage have
+ * been finished.
  *
- * This deferred approach to processing queue_redraw requests means
- * that we can avoid redundant transformations of clip volumes if
- * something later triggers a full stage redraw anyway. It also means
- * we can be more sure that all the referenced actors will have valid
- * allocations improving the chance that we can determine the actors
- * paint volume so we can clip the redraw request even if the user
- * didn't explicitly do so.
+ * The dirty list is used in this way so that we can defer calculating
+ * the region that needs to be redrawn until we actually come to
+ * paint. This means that we can avoid redundant transformations of
+ * clip volumes if something later triggers a full stage redraw
+ * anyway. It also means we can be more sure that all the referenced
+ * actors will have valid allocations improving the chance that we can
+ * determine the actors paint volume so we can clip the redraw even if
+ * the user didn't explicitly do so.
  */
-ClutterStageQueueRedrawEntry *
-_clutter_stage_queue_actor_redraw (ClutterStage *stage,
-                                   ClutterStageQueueRedrawEntry *entry,
-                                   ClutterActor *actor,
-                                   ClutterPaintVolume *clip)
+void
+_clutter_stage_add_dirty_actor (ClutterStage *stage,
+                                ClutterActor *actor,
+                                const ClutterPaintVolume *clip)
 {
   ClutterStagePrivate *priv = stage->priv;
+  ClutterActorRedrawState *redraw_state;
+  ClutterStageWindow *stage_window;
+
+  /* We have an optimization in _clutter_do_pick to detect when the
+   * scene is static so we can cache a full, un-clipped pick buffer to
+   * avoid continuous pick renders.
+   *
+   * Currently the assumption is that actors queue a redraw when some
+   * state changes that affects painting *or* picking so we can use
+   * this point to invalidate any currently cached pick buffer.
+   */
+  _clutter_stage_set_pick_buffer_valid (stage, FALSE, -1);
 
   if (!priv->redraw_pending)
     {
@@ -3269,120 +3383,71 @@ _clutter_stage_queue_actor_redraw (ClutterStage *stage,
     }
 #endif /* CLUTTER_ENABLE_DEBUG */
 
-  /* We have an optimization in _clutter_do_pick to detect when the
-   * scene is static so we can cache a full, un-clipped pick buffer to
-   * avoid continuous pick renders.
-   *
-   * Currently the assumption is that actors queue a redraw when some
-   * state changes that affects painting *or* picking so we can use
-   * this point to invalidate any currently cached pick buffer.
-   */
-  _clutter_stage_set_pick_buffer_valid (stage, FALSE, -1);
+  redraw_state = _clutter_actor_get_redraw_state (actor);
 
-  if (entry)
+  /* If the backend can't actually handle clipped redraws then we'll
+     ignore the clip */
+  stage_window = _clutter_stage_get_window (stage);
+  if (stage_window == NULL ||
+      !_clutter_stage_window_ignoring_redraw_clips (stage_window))
+    clip = NULL;
+
+  if (redraw_state->is_dirty)
     {
-      /* Ignore all requests to queue a redraw for an actor if a full
-       * (non-clipped) redraw of the actor has already been queued. */
-      if (!entry->has_clip)
-        return entry;
-
-      /* If queuing a clipped redraw and a clipped redraw has
-       * previously been queued for this actor then combine the latest
-       * clip together with the existing clip */
-      if (clip)
-        clutter_paint_volume_union (&entry->clip, clip);
-      else
+      /* We don't need to do anything extra to the redraw state if the
+         dirty region already includes the full actor (ie, not
+         clipped) */
+      if (redraw_state->has_clip)
         {
-          clutter_paint_volume_free (&entry->clip);
-          entry->has_clip = FALSE;
+          /* Otherwise we'll combine the latest clip together with the
+           * existing clip */
+          if (clip)
+            clutter_paint_volume_union (&redraw_state->clip, clip);
+          else
+            {
+              clutter_paint_volume_free (&redraw_state->clip);
+              redraw_state->has_clip = FALSE;
+            }
         }
-      return entry;
     }
   else
     {
-      entry = g_slice_new (ClutterStageQueueRedrawEntry);
-      entry->actor = g_object_ref (actor);
+      stage->priv->dirty_actors =
+        g_list_prepend (stage->priv->dirty_actors, g_object_ref (actor));
 
       if (clip)
         {
-          entry->has_clip = TRUE;
-          _clutter_paint_volume_init_static (actor, &entry->clip);
-          _clutter_paint_volume_set_from_volume (&entry->clip, clip);
+          redraw_state->has_clip = TRUE;
+          _clutter_paint_volume_init_static (actor, &redraw_state->clip);
+          _clutter_paint_volume_set_from_volume (&redraw_state->clip, clip);
         }
       else
-        entry->has_clip = FALSE;
-
-      stage->priv->pending_queue_redraws =
-        g_list_prepend (stage->priv->pending_queue_redraws, entry);
+        redraw_state->has_clip = FALSE;
 
-      return entry;
+      redraw_state->is_dirty = TRUE;
+      redraw_state->queue_redraw_emitted = FALSE;
     }
 }
 
-static void
-free_queue_redraw_entry (ClutterStageQueueRedrawEntry *entry)
-{
-  if (entry->actor)
-    g_object_unref (entry->actor);
-  if (entry->has_clip)
-    clutter_paint_volume_free (&entry->clip);
-  g_slice_free (ClutterStageQueueRedrawEntry, entry);
-}
-
 void
-_clutter_stage_queue_redraw_entry_invalidate (ClutterStageQueueRedrawEntry *entry)
+_clutter_stage_remove_dirty_actor (ClutterStage *stage,
+                                   ClutterActor *actor)
 {
-  if (entry == NULL)
-    return;
-
-  if (entry->actor != NULL)
-    {
-      g_object_unref (entry->actor);
-      entry->actor = NULL;
-    }
+  ClutterStagePrivate *priv = stage->priv;
+  ClutterActorRedrawState *redraw_state;
 
-  if (entry->has_clip)
-    {
-      clutter_paint_volume_free (&entry->clip);
-      entry->has_clip = FALSE;
-    }
-}
+  redraw_state = _clutter_actor_get_redraw_state (actor);
 
-static void
-_clutter_stage_maybe_finish_queue_redraws (ClutterStage *stage)
-{
-  /* Note: we have to repeat until the pending_queue_redraws list is
-   * empty because actors are allowed to queue redraws in response to
-   * the queue-redraw signal. For example Clone actors or
-   * texture_new_from_actor actors will have to queue a redraw if
-   * their source queues a redraw.
-   */
-  while (stage->priv->pending_queue_redraws)
+  if (redraw_state->is_dirty)
     {
-      GList *l;
-      /* XXX: we need to allow stage->priv->pending_queue_redraws to
-       * be updated while we process the current entries in the list
-       * so we steal the list pointer and then reset it to an empty
-       * list before processing... */
-      GList *stolen_list = stage->priv->pending_queue_redraws;
-      stage->priv->pending_queue_redraws = NULL;
-
-      for (l = stolen_list; l; l = l->next)
-        {
-          ClutterStageQueueRedrawEntry *entry = l->data;
-          ClutterPaintVolume *clip;
+      if (redraw_state->has_clip)
+        clutter_paint_volume_free (&redraw_state->clip);
 
-          /* NB: Entries may be invalidated if the actor gets destroyed */
-          if (G_LIKELY (entry->actor != NULL))
-	    {
-	      clip = entry->has_clip ? &entry->clip : NULL;
+      redraw_state->is_dirty = FALSE;
 
-	      _clutter_actor_finish_queue_redraw (entry->actor, clip);
-	    }
+      priv->dirty_actors = g_list_remove (priv->dirty_actors, actor);
 
-          free_queue_redraw_entry (entry);
-        }
-      g_list_free (stolen_list);
+      g_object_unref (actor);
     }
 }
 



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