From 125bded81455df73b37ed459f360ced0135db65a Mon Sep 17 00:00:00 2001
From: Havoc Pennington <hp@pobox.com>
Date: Thu, 2 Apr 2009 09:16:43 -0400
Subject: [PATCH] Enforce invariants on mapped, realized, visibility states

Bug 1138 - No trackable "mapped" state

* Add a VISIBLE flag tracking application programmer's
  expected showing-state for the actor, allowing us to
  always ensure we keep what the app wants while tracking
  internal implementation state separately.

* Make MAPPED reflect whether the actor will be painted;
  add notification on a ClutterActor::mapped property.
  Keep MAPPED state updated as the actor is shown,
  ancestors are shown, actor is reparented, etc.

* Require a stage and realized parents to realize; this means
  at realization time the correct window system and GL resources
  are known. But unparented actors can no longer be realized.

* Allow children to be unrealized even if parent is realized.
  Otherwise in effect either all actors or no actors are realized,
  i.e. it becomes a stage-global flag.

* Allow clutter_actor_realize() to "fail" if not inside a toplevel

* Rework clutter_actor_unrealize() so internally we have
  a flavor that does not mess with visibility flag

* Add _clutter_actor_rerealize() to encapsulate a somewhat
  tricky operation we were doing in a couple of places

* Do not realize/unrealize children in ClutterGroup,
  ClutterActor already does it

* Do not realize impl by hand in clutter_stage_show(),
  since showing impl already does that

* Do not unrealize in various dispose() methods, since
  ClutterActor dispose implementation already does it
  and chaining up is mandatory

* ClutterTexture uses COGL while unrealizable (before it's
  added to a stage). Previously this breakage was affecting
  ClutterActor because we had to allow realize outside
  a stage. Move the breakage to ClutterTexture, by making
  ClutterTexture just use COGL while not realized.

* Unrealize before we set parent to NULL in clutter_actor_unparent().
  This means unrealize() implementations can get to the stage.
  Because actors need the stage in order to detach from stage.

* Update clutter-actor-invariants.txt to reflect latest changes

* Remove explicit hide/unrealize from ClutterActor::dispose since
  unparent already forces those
  Instead just assert that unparent() occurred and did the right thing.

* Check whether parent implements unrealize before chaining up
  Needed because ClutterGroup no longer has to implement unrealize.

* Perform unrealize in the default handler for the signal.
  This allows non-containers that have children to work properly,
  and allows containers to override how it's done.

* Add map/unmap virtual methods and set MAPPED flag on self and
  children in there. This allows subclasses to hook map/unmap.
  These are not signals, because notify::mapped is better for
  anything it's legitimate for a non-subclass to do.

Signed-off-by: Emmanuele Bassi <ebassi@linux.intel.com>
---
 clutter/clutter-actor.c               | 819 +++++++++++++++++++++++---
 clutter/clutter-actor.h               |  12 +-
 clutter/clutter-group.c               |  18 -
 clutter/clutter-private.h             |   4 +
 clutter/clutter-stage.c               |  56 +-
 clutter/clutter-texture.c             |  81 ++-
 clutter/eglnative/clutter-stage-egl.c |   5 +-
 clutter/eglx/clutter-stage-egl.c      |   6 +-
 clutter/fruity/clutter-fruity.c       |   4 +
 clutter/fruity/clutter-stage-fruity.c |   5 +-
 clutter/glx/clutter-stage-glx.c       |  12 +-
 clutter/sdl/clutter-stage-sdl.c       |   2 -
 clutter/win32/clutter-stage-win32.c   |  15 +-
 clutter/x11/clutter-stage-x11.c       |  69 ++-
 doc/clutter-actor-invariants.txt      | 180 ++++--
 tests/conform/test-actor-invariants.c | 133 ++++-
 tests/conform/test-conform-main.c     |   3 +
 17 files changed, 1145 insertions(+), 279 deletions(-)

diff --git a/clutter/clutter-actor.c b/clutter/clutter-actor.c
index 5d9d95d6a..3c9890a5f 100644
--- a/clutter/clutter-actor.c
+++ b/clutter/clutter-actor.c
@@ -136,6 +136,14 @@
  *
  * Evaluates to %TRUE if the %CLUTTER_ACTOR_MAPPED flag is set.
  *
+ * Means "the actor will be painted if the stage is mapped."
+ *
+ * %TRUE if the actor is visible; and all parents with possible exception
+ * of the stage are visible; and an ancestor of the actor is a toplevel.
+ *
+ * Clutter auto-maintains the mapped flag whenever actors are
+ * reparented or shown/hidden.
+ *
  * Since: 0.2
  */
 
@@ -145,6 +153,15 @@
  *
  * Evaluates to %TRUE if the %CLUTTER_ACTOR_REALIZED flag is set.
  *
+ * Whether GL resources such as textures are allocated;
+ * if an actor is mapped it must also be realized, but an actor
+ * can be realized and unmapped (this is so hiding an actor temporarily
+ * doesn't do an expensive unrealize/realize).
+ *
+ * To be realized an actor must be inside a stage, and all its parents
+ * must be realized. The stage is required so the actor knows the
+ * correct GL context and window system resources to use.
+ *
  * Since: 0.2
  */
 
@@ -152,7 +169,12 @@
  * CLUTTER_ACTOR_IS_VISIBLE:
  * @e: a #ClutterActor
  *
- * Evaluates to %TRUE if the actor is both realized and mapped.
+ * Evaluates to %TRUE if the actor has been shown, %FALSE if it's hidden.
+ * Equivalent to the ClutterActor::visible object property.
+ *
+ * Note that an actor is only painted onscreen if it's mapped, which
+ * means it's visible, and all its parents are visible, and one of the
+ * parents is a toplevel stage.
  *
  * Since: 0.2
  */
@@ -210,6 +232,23 @@ struct _AnchorCoord
   } v;
 };
 
+/* Internal enum used to control mapped state update.  This is a hint
+ * which indicates when to do something other than just enforce
+ * invariants.
+ */
+typedef enum {
+  MAP_STATE_CHECK,           /* just enforce invariants. */
+  MAP_STATE_MAKE_UNREALIZED, /* force unrealize, ignoring invariants,
+                              * used when about to unparent.
+                              */
+  MAP_STATE_MAKE_MAPPED,     /* set mapped, error if invariants not met;
+                              * used to set mapped on toplevels.
+                              */
+  MAP_STATE_MAKE_UNMAPPED    /* set unmapped, even if parent is mapped,
+                              * used just before unmapping parent.
+                              */
+} MapStateChange;
+
 struct _ClutterActorPrivate
 {
   /* fixed_x, fixed_y, and the allocation box are all in parent
@@ -343,6 +382,7 @@ enum
 
   PROP_OPACITY,
   PROP_VISIBLE,
+  PROP_MAPPED,
   PROP_REACTIVE,
 
   PROP_SCALE_X,
@@ -430,6 +470,9 @@ static void clutter_actor_set_natural_height_set (ClutterActor *self,
                                                   gboolean  use_natural_height);
 static void clutter_actor_set_request_mode       (ClutterActor *self,
                                                   ClutterRequestMode mode);
+static void clutter_actor_update_map_state       (ClutterActor  *self,
+                                                  MapStateChange change);
+static void clutter_actor_unrealize_not_hiding   (ClutterActor *self);
 
 /* Helper routines for managing anchor coords */
 static void clutter_anchor_coord_get_units (ClutterActor *self,
@@ -471,22 +514,404 @@ G_DEFINE_ABSTRACT_TYPE_WITH_CODE (ClutterActor,
                                                          clutter_scriptable_iface_init));
 
 
+/* FIXME this is for debugging only, remove once working (or leave in
+ * only in some debug mode). Should leave it for a little while
+ * until we're confident in the new map/realize/visible handling.
+ */
+static void
+clutter_actor_verify_map_state (ClutterActor *self)
+{
+  if (CLUTTER_ACTOR_IS_REALIZED (self))
+    {
+      /* all bets are off during reparent when we're potentially realized,
+       * but should not be according to invariants
+       */
+      if (!(CLUTTER_PRIVATE_FLAGS (self) & CLUTTER_ACTOR_IN_REPARENT))
+        {
+          if (self->priv->parent_actor == NULL)
+            {
+              if (CLUTTER_PRIVATE_FLAGS (self) & CLUTTER_ACTOR_IS_TOPLEVEL)
+                {
+                }
+              else
+                {
+                  g_warning ("Realized non-toplevel actor should have a parent");
+                }
+            }
+          else if (!CLUTTER_ACTOR_IS_REALIZED (self->priv->parent_actor))
+            {
+              g_warning ("Realized actor %s %p has an unrealized parent %s %p",
+                         G_OBJECT_TYPE_NAME (self), self,
+                         G_OBJECT_TYPE_NAME (self->priv->parent_actor),
+                         self->priv->parent_actor);
+            }
+        }
+    }
+
+  if (CLUTTER_ACTOR_IS_MAPPED (self))
+    {
+      if (!CLUTTER_ACTOR_IS_REALIZED (self))
+        g_warning ("Actor is mapped but not realized");
+
+      /* remaining bets are off during reparent when we're potentially
+       * mapped, but should not be according to invariants
+       */
+      if (!(CLUTTER_PRIVATE_FLAGS (self) & CLUTTER_ACTOR_IN_REPARENT))
+        {
+          if (self->priv->parent_actor == NULL)
+            {
+              if (CLUTTER_PRIVATE_FLAGS (self) & CLUTTER_ACTOR_IS_TOPLEVEL)
+                {
+                  if (!CLUTTER_ACTOR_IS_VISIBLE (self))
+                    g_warning ("Toplevel actor is mapped but not visible");
+                }
+              else
+                {
+                  g_warning ("Mapped actor %s %p should have a parent",
+                             G_OBJECT_TYPE_NAME (self), self);
+                }
+            }
+          else
+            {
+              if (!CLUTTER_ACTOR_IS_VISIBLE (self->priv->parent_actor))
+                {
+                  g_warning ("Actor should not be mapped if parent is not visible");
+                }
+
+              if (!CLUTTER_ACTOR_IS_REALIZED (self->priv->parent_actor))
+                {
+                  g_warning ("Actor should not be mapped if parent is not realized");
+                }
+
+              if (!(CLUTTER_PRIVATE_FLAGS (self->priv->parent_actor) &
+                    CLUTTER_ACTOR_IS_TOPLEVEL))
+                {
+                  if (!CLUTTER_ACTOR_IS_MAPPED (self->priv->parent_actor))
+                    g_warning ("Actor is mapped but its non-toplevel parent is not mapped");
+                }
+            }
+        }
+    }
+}
+
+static void
+clutter_actor_set_mapped (ClutterActor *self,
+                          gboolean      mapped)
+{
+  if (CLUTTER_ACTOR_IS_MAPPED (self) == mapped)
+    return;
+
+  if (mapped)
+    {
+      CLUTTER_ACTOR_GET_CLASS (self)->map (self);
+      g_assert (CLUTTER_ACTOR_IS_MAPPED (self));
+    }
+  else
+    {
+      CLUTTER_ACTOR_GET_CLASS (self)->unmap (self);
+      g_assert (!CLUTTER_ACTOR_IS_MAPPED (self));
+    }
+}
+
+/* this function updates the mapped and realized states according to
+ * invariants, in the appropriate order.
+ */
+static void
+clutter_actor_update_map_state (ClutterActor  *self,
+                                MapStateChange change)
+{
+  gboolean was_mapped;
+  gboolean was_realized;
+
+  was_mapped = CLUTTER_ACTOR_IS_MAPPED (self);
+  was_realized = CLUTTER_ACTOR_IS_REALIZED (self);
+
+  if (CLUTTER_PRIVATE_FLAGS (self) & CLUTTER_ACTOR_IS_TOPLEVEL)
+    {
+      /* the mapped flag on top-level actors must be set by the
+       * per-backend implementation because it might be asynchronous.
+       *
+       * That is, the MAPPED flag on toplevels currently tracks the X
+       * server mapped-ness of the window, while the expected behavior
+       * (if used to GTK) may be to track WM_STATE!=WithdrawnState.
+       * This creates some weird complexity by breaking the invariant
+       * that if we're visible and all ancestors shown then we are
+       * also mapped - instead, we are mapped if all ancestors
+       * _possibly excepting_ the stage are mapped. The stage
+       * will map/unmap for example when it is minimized or
+       * moved to another workspace.
+       *
+       * So, the only invariant on the stage is that if visible it
+       * should be realized, and that it has to be visible to be
+       * mapped.
+       */
+      if (CLUTTER_ACTOR_IS_VISIBLE (self))
+        clutter_actor_realize (self);
+
+      switch (change)
+        {
+        case MAP_STATE_CHECK:
+          break;
+        case MAP_STATE_MAKE_MAPPED:
+          g_assert (!was_mapped);
+          clutter_actor_set_mapped (self, TRUE);
+          break;
+        case MAP_STATE_MAKE_UNMAPPED:
+          g_assert (was_mapped);
+          clutter_actor_set_mapped (self, FALSE);
+          break;
+        case MAP_STATE_MAKE_UNREALIZED:
+          /* we only use MAKE_UNREALIZED in unparent,
+           * and unparenting a stage isn't possible.
+           * If someone wants to just unrealize a stage
+           * then clutter_actor_unrealize() doesn't
+           * go through this codepath.
+           */
+          g_warning ("Trying to force unrealize stage is not allowed");
+          break;
+        }
+
+      if (CLUTTER_ACTOR_IS_MAPPED (self) &&
+          !CLUTTER_ACTOR_IS_VISIBLE (self))
+        g_warning ("Clutter toplevel is not visible, but is somehow still mapped");
+    }
+  else
+    {
+      ClutterActorPrivate *priv;
+      gboolean should_be_mapped;
+      gboolean may_be_realized;
+      gboolean must_be_realized;
+      ClutterActor *parent;
+
+      priv = self->priv;
+
+      should_be_mapped = FALSE;
+      may_be_realized = TRUE;
+      must_be_realized = FALSE;
+
+      parent = priv->parent_actor;
+
+      if (parent == NULL ||
+          change == MAP_STATE_MAKE_UNREALIZED)
+        {
+          may_be_realized = FALSE;
+        }
+      else
+        {
+          /* Maintain invariant that if parent is mapped, and we are
+           * visible, then we are mapped ...  unless parent is a
+           * stage, in which case we map regardless of parent's map
+           * state but do require stage to be visible and realized.
+           *
+           * If parent is realized, that does not force us to be
+           * realized; but if parent is unrealized, that does force
+           * us to be unrealized.
+           *
+           * The reason we don't force children to realize with
+           * parents is _clutter_actor_rerealize(); if we require that
+           * a realized parent means children are realized, then to
+           * unrealize an actor we would have to unrealize its
+           * parents, which would end up meaning unrealizing and
+           * hiding the entire stage. So we allow unrealizing a
+           * child (as long as that child is not mapped) while that
+           * child still has a realized parent.
+           *
+           * Also, if we unrealize from leaf nodes to root, and
+           * realize from root to leaf, the invariants are never
+           * violated if we allow children to be unrealized
+           * while parents are realized.
+           *
+           * When unmapping, MAP_STATE_MAKE_UNMAPPED is specified
+           * to force us to unmap, even though parent is still
+           * mapped. This is because we're unmapping from leaf nodes
+           * up to root nodes.
+           */
+          if (CLUTTER_ACTOR_IS_VISIBLE (self) &&
+              change != MAP_STATE_MAKE_UNMAPPED)
+            {
+              gboolean parent_is_visible_realized_toplevel;
+
+              parent_is_visible_realized_toplevel =
+                (((CLUTTER_PRIVATE_FLAGS (parent) &
+                   CLUTTER_ACTOR_IS_TOPLEVEL) != 0) &&
+                 CLUTTER_ACTOR_IS_VISIBLE (parent) &&
+                 CLUTTER_ACTOR_IS_REALIZED (parent));
+
+              if (CLUTTER_ACTOR_IS_MAPPED (parent) ||
+                  parent_is_visible_realized_toplevel)
+                {
+                  must_be_realized = TRUE;
+                  should_be_mapped = TRUE;
+                }
+            }
+
+          if (!CLUTTER_ACTOR_IS_REALIZED (parent))
+            may_be_realized = FALSE;
+        }
+
+      if (change == MAP_STATE_MAKE_MAPPED && !should_be_mapped)
+        {
+          g_warning ("Attempting to map a child that does not meet the necessary invariants");
+        }
+
+      /* If in reparent, we temporarily suspend unmap and unrealize.
+       *
+       * We want to go in the order "realize, map" and "unmap, unrealize"
+       */
+
+      /* Unmap */
+      if (!should_be_mapped &&
+          !(CLUTTER_PRIVATE_FLAGS (self) & CLUTTER_ACTOR_IN_REPARENT))
+        {
+          clutter_actor_set_mapped (self, FALSE);
+        }
+
+      /* Realize */
+      if (must_be_realized)
+        clutter_actor_realize (self);
+
+      /* if we must be realized then we may be, presumably */
+      g_assert (!(must_be_realized && !may_be_realized));
+
+      /* Unrealize */
+      if (!may_be_realized &&
+          !(CLUTTER_PRIVATE_FLAGS (self) & CLUTTER_ACTOR_IN_REPARENT))
+        clutter_actor_unrealize_not_hiding (self);
+
+      /* Map */
+      if (should_be_mapped)
+        {
+          if (!must_be_realized)
+            g_warning ("Somehow we think an actor should be mapped but not realized, which isn't allowed");
+
+          /* realization is allowed to fail (though I don't know what
+           * an app is supposed to do about that - shouldn't it just
+           * be a g_error? anyway, we have to avoid mapping if this
+           * happens)
+           */
+          if (CLUTTER_ACTOR_IS_REALIZED (self))
+            {
+              clutter_actor_set_mapped (self, TRUE);
+            }
+        }
+    }
+
+  /* check all invariants were kept */
+  clutter_actor_verify_map_state (self);
+}
+
+static void
+clutter_actor_real_map (ClutterActor *self)
+{
+  g_assert (!CLUTTER_ACTOR_IS_MAPPED (self));
+
+  CLUTTER_ACTOR_SET_FLAGS (self, CLUTTER_ACTOR_MAPPED);
+  /* notify on parent mapped before potentially mapping
+   * children, so apps see a top-down notification.
+   */
+  g_object_notify (G_OBJECT (self), "mapped");
+
+  clutter_actor_queue_redraw (self);
+
+  if (CLUTTER_IS_CONTAINER (self))
+    clutter_container_foreach (CLUTTER_CONTAINER (self),
+                               CLUTTER_CALLBACK (clutter_actor_map),
+                               NULL);
+}
+
+/**
+ * clutter_actor_map:
+ * @self: A #ClutterActor
+ *
+ * Sets the #CLUTTER_ACTOR_MAPPED flag on the actor
+ * and possibly maps and realizes its children
+ * if they are visible. Does nothing if the
+ * actor is not visible.
+ *
+ * Calling this is allowed in only one case:
+ * you are implementing the "map" virtual function
+ * in an actor and you need to map the children of
+ * that actor. It is not necessary to call this
+ * if you implement #ClutterContainer because the
+ * default implementation will automatically map
+ * children of containers.
+ *
+ * When overriding map, it is mandatory to chain up to the parent
+ * implementation.
+ **/
+void
+clutter_actor_map (ClutterActor *self)
+{
+  g_return_if_fail (CLUTTER_IS_ACTOR (self));
+
+  if (CLUTTER_ACTOR_IS_MAPPED (self))
+    return;
+
+  if (!CLUTTER_ACTOR_IS_VISIBLE (self))
+    return;
+
+  clutter_actor_update_map_state (self, MAP_STATE_MAKE_MAPPED);
+}
+
+static void
+clutter_actor_real_unmap (ClutterActor *self)
+{
+  g_assert (CLUTTER_ACTOR_IS_MAPPED (self));
+
+  if (CLUTTER_IS_CONTAINER (self))
+    clutter_container_foreach (CLUTTER_CONTAINER (self),
+                               CLUTTER_CALLBACK (clutter_actor_unmap),
+                               NULL);
+
+  CLUTTER_ACTOR_UNSET_FLAGS (self, CLUTTER_ACTOR_MAPPED);
+  /* notify on parent mapped after potentially unmapping
+   * children, so apps see a bottom-up notification.
+   */
+  g_object_notify (G_OBJECT (self), "mapped");
+
+  clutter_actor_queue_redraw (self);
+}
+
+/**
+ * clutter_actor_unmap:
+ * @self: A #ClutterActor
+ *
+ * Unsets the #CLUTTER_ACTOR_MAPPED flag on the actor and possibly
+ * unmaps its children if they were mapped.
+ *
+ * Calling this is allowed in only one case:
+ * you are implementing the "unmap" virtual function
+ * in an actor and you need to unmap the children of
+ * that actor. It is not necessary to call this
+ * if you implement #ClutterContainer because the
+ * default implementation will automatically unmap
+ * children of containers.
+ *
+ * When overriding unmap, it is mandatory to chain up to the parent
+ * implementation.
+ **/
+void
+clutter_actor_unmap (ClutterActor *self)
+{
+  g_return_if_fail (CLUTTER_IS_ACTOR (self));
+
+  if (!CLUTTER_ACTOR_IS_MAPPED (self))
+    return;
+
+  clutter_actor_update_map_state (self, MAP_STATE_MAKE_UNMAPPED);
+}
+
 static void
 clutter_actor_real_show (ClutterActor *self)
 {
   if (!CLUTTER_ACTOR_IS_VISIBLE (self))
     {
-      if (!CLUTTER_ACTOR_IS_REALIZED (self))
-        clutter_actor_realize (self);
-
-      /* the mapped flag on the top-level actors must be set by the
-       * per-backend implementation because it might be asynchronous
+      CLUTTER_ACTOR_SET_FLAGS (self, CLUTTER_ACTOR_VISIBLE);
+      /* we notify on the "visible" flag in the clutter_actor_show()
+       * wrapper so the entire show signal emission completes first
+       * (?)
        */
-      if (!(CLUTTER_PRIVATE_FLAGS (self) & CLUTTER_ACTOR_IS_TOPLEVEL))
-        CLUTTER_ACTOR_SET_FLAGS (self, CLUTTER_ACTOR_MAPPED);
-
-      if (CLUTTER_ACTOR_IS_VISIBLE (self))
-        clutter_actor_queue_redraw (self);
+      clutter_actor_update_map_state (self, MAP_STATE_CHECK);
 
       clutter_actor_queue_relayout (self);
     }
@@ -512,6 +937,8 @@ clutter_actor_show (ClutterActor *self)
 
   g_return_if_fail (CLUTTER_IS_ACTOR (self));
 
+  clutter_actor_verify_map_state (self); /* FIXME leave this for debugging only */
+
   priv = self->priv;
 
   g_object_freeze_notify (G_OBJECT (self));
@@ -556,11 +983,12 @@ clutter_actor_real_hide (ClutterActor *self)
 {
   if (CLUTTER_ACTOR_IS_VISIBLE (self))
     {
-      /* see comment in clutter_actor_real_show() on why we don't set
-       * the mapped flag on the top-level actors
+      CLUTTER_ACTOR_UNSET_FLAGS (self, CLUTTER_ACTOR_VISIBLE);
+      /* we notify on the "visible" flag in the clutter_actor_hide()
+       * wrapper so the entire hide signal emission completes first
+       * (?)
        */
-      if (!(CLUTTER_PRIVATE_FLAGS (self) & CLUTTER_ACTOR_IS_TOPLEVEL))
-        CLUTTER_ACTOR_UNSET_FLAGS (self, CLUTTER_ACTOR_MAPPED);
+      clutter_actor_update_map_state (self, MAP_STATE_CHECK);
 
       clutter_actor_queue_relayout (self);
     }
@@ -586,6 +1014,8 @@ clutter_actor_hide (ClutterActor *self)
 
   g_return_if_fail (CLUTTER_IS_ACTOR (self));
 
+  clutter_actor_verify_map_state (self); /* FIXME leave this for debugging only */
+
   priv = self->priv;
 
   g_object_freeze_notify (G_OBJECT (self));
@@ -596,7 +1026,7 @@ clutter_actor_hide (ClutterActor *self)
       g_object_notify (G_OBJECT (self), "show-on-set-parent");
     }
 
-  if (CLUTTER_ACTOR_IS_MAPPED (self))
+  if (CLUTTER_ACTOR_IS_VISIBLE (self))
     {
       g_signal_emit (self, actor_signals[HIDE], 0);
       g_object_notify (G_OBJECT (self), "visible");
@@ -631,44 +1061,225 @@ clutter_actor_hide_all (ClutterActor *self)
  *
  * Creates any underlying graphics resources needed by the actor to be
  * displayed.
+ *
+ * Realization means the actor is now tied to a specific rendering context
+ * (that is, a specific toplevel stage).
+ *
+ * This function does nothing if the actor is already realized.
+ *
+ * Because a realized actor must have realized parent actors, calling
+ * clutter_actor_realize() will also realize all parents of the actor.
+ *
+ * This function does not realize child actors, except in the special
+ * case that realizing the stage, when the stage is visible, will
+ * suddenly map (and thus realize) the children of the stage.
  **/
 void
 clutter_actor_realize (ClutterActor *self)
 {
   g_return_if_fail (CLUTTER_IS_ACTOR (self));
 
+  clutter_actor_verify_map_state (self); /* FIXME leave this for debugging only */
+
   if (CLUTTER_ACTOR_IS_REALIZED (self))
     return;
 
+  /* To be realized, our parent actors must be realized first.
+   * This will only succeed if we're inside a toplevel.
+   */
+  if (self->priv->parent_actor)
+    clutter_actor_realize (self->priv->parent_actor);
+
+  if (CLUTTER_PRIVATE_FLAGS (self) & CLUTTER_ACTOR_IS_TOPLEVEL)
+    {
+      /* toplevels can be realized at any time */
+    }
+  else
+    {
+      /* "Fail" the realization if parent is missing or unrealized;
+       * this should really be a g_warning() not some kind of runtime
+       * failure; how can an app possibly recover? Instead it's a bug
+       * in the app and the app should get an explanatory warning so
+       * someone can fix it. But for now it's too hard to fix this
+       * because e.g. ClutterTexture needs reworking.
+       */
+      if (self->priv->parent_actor == NULL ||
+          !CLUTTER_ACTOR_IS_REALIZED (self->priv->parent_actor))
+        return;
+    }
+
   CLUTTER_ACTOR_SET_FLAGS (self, CLUTTER_ACTOR_REALIZED);
 
   g_signal_emit (self, actor_signals[REALIZE], 0);
+
+  /* Stage actor is allowed to unset the realized flag again in its
+   * default signal handler, though that is a pathological situation.
+   */
+
+  /* If realization "failed" we'll have to update child state. */
+  clutter_actor_update_map_state (self, MAP_STATE_CHECK);
+}
+
+void
+clutter_actor_real_unrealize (ClutterActor *self)
+{
+  /* we must be unmapped (implying our children are also unmapped) */
+  g_assert (!CLUTTER_ACTOR_IS_MAPPED (self));
+
+  if (CLUTTER_IS_CONTAINER (self))
+    clutter_container_foreach (CLUTTER_CONTAINER (self),
+                               CLUTTER_CALLBACK (clutter_actor_unrealize_not_hiding),
+                               NULL);
 }
 
 /**
  * clutter_actor_unrealize:
  * @self: A #ClutterActor
  *
- * Frees up any underlying graphics resources needed by the actor to be
- * displayed.
- **/
+ * Frees up any underlying graphics resources needed by the actor to
+ * be displayed.
+ *
+ * Unrealization means the actor is now independent of any specific
+ * rendering context (is not attached to a specific toplevel stage).
+ *
+ * Because mapped actors must be realized, actors may not be
+ * unrealized if they are mapped. This function hides the actor to be
+ * sure it isn't mapped, an application-visible side effect that you
+ * may not be expecting.
+ *
+ * This function should not really be in the public API, because
+ * there isn't a good reason to call it. ClutterActor will already
+ * unrealize things for you when it's important to do so.
+ *
+ * If you were using clutter_actor_unrealize() in a dispose
+ * implementation, then don't, just chain up to ClutterActor's
+ * dispose.
+ *
+ * If you were using clutter_actor_unrealize() to implement
+ * unrealizing children of your container, then don't, ClutterActor
+ * will already take care of that.
+ *
+ * If you were using clutter_actor_unrealize() to re-realize to
+ * create your resources in a different way, then use
+ * _clutter_actor_rerealize() (inside Clutter) or just call your
+ * code that recreates your resources directly (outside Clutter).
+ */
 void
 clutter_actor_unrealize (ClutterActor *self)
 {
   g_return_if_fail (CLUTTER_IS_ACTOR (self));
+  g_return_if_fail (!CLUTTER_ACTOR_IS_MAPPED (self));
+
+  clutter_actor_verify_map_state (self); /* FIXME leave this for debugging only */
+
+  clutter_actor_hide (self);
+
+  clutter_actor_unrealize_not_hiding (self);
+}
+
+/**
+ * clutter_actor_unrealize_not_hiding:
+ * @self: A #ClutterActor
+ *
+ * Frees up any underlying graphics resources needed by the actor to
+ * be displayed.
+ *
+ * Unrealization means the actor is now independent of any specific
+ * rendering context (is not attached to a specific toplevel stage).
+ *
+ * Because mapped actors must be realized, actors may not be
+ * unrealized if they are mapped. You must hide the actor or one of
+ * its parents before attempting to unrealize.
+ *
+ * This function is separate from clutter_actor_unrealize() because it
+ * does not automatically hide the actor.
+ * Actors need not be hidden to be unrealized, they just need to
+ * be unmapped. In fact we don't want to mess up the application's
+ * setting of the "visible" flag, so hiding is very undesirable.
+ *
+ * clutter_actor_unrealize() does a clutter_actor_hide() just for
+ * backward compatibility.
+ */
+static void
+clutter_actor_unrealize_not_hiding (ClutterActor *self)
+{
+  /* All callers of clutter_actor_unrealize_not_hiding() should have
+   * taken care of unmapping the actor first. This means
+   * all our children should also be unmapped.
+   */
+  g_assert (!CLUTTER_ACTOR_IS_MAPPED (self));
 
   if (!CLUTTER_ACTOR_IS_REALIZED (self))
     return;
 
-  /* unrealizing also means hiding a visible actor, exactly
-   * like showing implies realization if called on an unrealized
-   * actor. this keeps the flags in sync.
+  /* The default handler for the signal should recursively unrealize
+   * child actors. We want to unset the realized flag only _after_
+   * child actors are unrealized, to maintain invariants.
    */
-  clutter_actor_hide (self);
-
-  CLUTTER_ACTOR_UNSET_FLAGS (self, CLUTTER_ACTOR_REALIZED);
 
   g_signal_emit (self, actor_signals[UNREALIZE], 0);
+
+  CLUTTER_ACTOR_UNSET_FLAGS (self, CLUTTER_ACTOR_REALIZED);
+}
+
+/**
+ * _clutter_actor_rerealize:
+ * @self: A #ClutterActor
+ * @callback: Function to call while unrealized
+ * @data: data for callback
+ *
+ * If an actor is already unrealized, this just calls the callback.
+ *
+ * If it is realized, it unrealizes temporarily, calls the callback,
+ * and then re-realizes the actor.
+ *
+ * As a side effect, leaves all children of the actor unrealized if
+ * the actor was realized but not showing.  This is because when we
+ * unrealize the actor temporarily we must unrealize its children
+ * (e.g. children of a stage can't be realized if stage window is
+ * gone). And we aren't clever enough to save the realization state of
+ * all children. In most cases this should not matter, because
+ * the children will automatically realize when they next become mapped.
+ */
+void
+_clutter_actor_rerealize (ClutterActor    *self,
+                          ClutterCallback  callback,
+                          void            *data)
+{
+  gboolean was_mapped;
+  gboolean was_showing;
+  gboolean was_realized;
+
+  g_return_if_fail (CLUTTER_IS_ACTOR (self));
+
+  clutter_actor_verify_map_state (self); /* FIXME leave this for debugging only */
+
+  was_realized = CLUTTER_ACTOR_IS_REALIZED (self);
+  was_mapped = CLUTTER_ACTOR_IS_MAPPED (self);
+  was_showing = CLUTTER_ACTOR_IS_VISIBLE (self);
+
+  /* Must be unmapped to unrealize. Note we only have to hide this
+   * actor if it was mapped (if all parents were showing).  If actor
+   * is merely visible (but not mapped), then that's fine, we can
+   * leave it visible.
+   */
+  if (was_mapped)
+    clutter_actor_hide (self);
+
+  g_assert (!CLUTTER_ACTOR_IS_MAPPED (self));
+
+  /* unrealize self and all children */
+  clutter_actor_unrealize_not_hiding (self);
+
+  if (callback != NULL)
+    {
+      (* callback) (self, data);
+    }
+
+  if (was_showing)
+    clutter_actor_show (self); /* will realize only if mapping implies it */
+  else if (was_realized)
+    clutter_actor_realize (self); /* realize self and all parents */
 }
 
 static void
@@ -892,7 +1503,7 @@ clutter_actor_queue_redraw_with_origin (ClutterActor *self,
                                         ClutterActor *origin)
 {
   /* short-circuit the trivial case */
-  if (!CLUTTER_ACTOR_IS_VISIBLE(self))
+  if (!CLUTTER_ACTOR_IS_MAPPED(self))
     return;
 
   /* already queued since last paint() */
@@ -910,7 +1521,7 @@ clutter_actor_real_queue_redraw (ClutterActor *self,
   ClutterActor *parent;
 
   /* short-circuit the trivial case */
-  if (!CLUTTER_ACTOR_IS_VISIBLE (self))
+  if (!CLUTTER_ACTOR_IS_MAPPED (self))
     return;
 
   /* already queued since last paint() */
@@ -1562,17 +2173,11 @@ clutter_actor_paint (ClutterActor *self)
 
   priv = self->priv;
 
-  if (!CLUTTER_ACTOR_IS_REALIZED (self))
-    {
-      CLUTTER_NOTE (PAINT, "Attempting realize via paint()");
-      clutter_actor_realize(self);
-
-      if (!CLUTTER_ACTOR_IS_REALIZED (self))
-	{
-	  CLUTTER_NOTE (PAINT, "Attempt failed, aborting paint");
-	  return;
-	}
-    }
+  /* if we aren't paintable (not in a toplevel with all
+   * parents paintable) then do nothing.
+   */
+  if (!CLUTTER_ACTOR_IS_MAPPED (self))
+    return;
 
   /* mark that we are in the paint process */
   CLUTTER_SET_PRIVATE_FLAGS (self, CLUTTER_ACTOR_IN_PAINT);
@@ -1985,7 +2590,11 @@ clutter_actor_get_property (GObject    *object,
       break;
     case PROP_VISIBLE:
       g_value_set_boolean (value,
-		           (CLUTTER_ACTOR_IS_VISIBLE (actor) != FALSE));
+		           CLUTTER_ACTOR_IS_VISIBLE (actor));
+      break;
+    case PROP_MAPPED:
+      g_value_set_boolean (value,
+		           CLUTTER_ACTOR_IS_MAPPED (actor));
       break;
     case PROP_HAS_CLIP:
       g_value_set_boolean (value, priv->has_clip);
@@ -2123,7 +2732,11 @@ clutter_actor_dispose (GObject *object)
         priv->parent_actor = NULL;
     }
 
-  clutter_actor_unrealize (self);
+  /* parent should be gone */
+  g_assert (priv->parent_actor == NULL);
+  /* can't be mapped or realized with no parent */
+  g_assert (!CLUTTER_ACTOR_IS_MAPPED (self));
+  g_assert (!CLUTTER_ACTOR_IS_REALIZED (self));
 
   destroy_shader_data (self);
 
@@ -2527,6 +3140,19 @@ clutter_actor_class_init (ClutterActorClass *klass)
                                                          "Whether the actor is visible or not",
                                                          FALSE,
                                                          CLUTTER_PARAM_READWRITE));
+
+  /**
+   * ClutterActor:mapped:
+   *
+   * Whether the actor is mapped (will be painted when stage is mapped).
+   */
+  g_object_class_install_property (object_class,
+                                   PROP_MAPPED,
+                                   g_param_spec_boolean ("mapped",
+                                                         "Mapped",
+                                                         "Whether the actor will be painted",
+                                                         FALSE,
+                                                         G_PARAM_READABLE));
   /**
    * ClutterActor:reactive:
    *
@@ -3321,6 +3947,30 @@ clutter_actor_class_init (ClutterActorClass *klass)
                   clutter_marshal_VOID__VOID,
                   G_TYPE_NONE, 0);
 
+  /**
+   * ClutterActor::map:
+   * @actor: the #ClutterActor to map
+   *
+   * The ::map virtual functon must be overridden in order to call
+   * clutter_actor_map() on any child actors if the actor is not a
+   * #ClutterContainer.  When overriding, it is mandatory to chain up
+   * to the parent implementation.
+   *
+   * Since: 1.0
+   */
+
+  /**
+   * ClutterActor::unmap:
+   * @actor: the #ClutterActor to unmap
+   *
+   * The ::unmap virtual functon must be overridden in order to call
+   * clutter_actor_unmap() on any child actors if the actor is not a
+   * #ClutterContainer.  When overriding, it is mandatory to chain up
+   * to the parent implementation.
+   *
+   * Since: 1.0
+   */
+
   /**
    * ClutterActor::pick:
    * @actor: the #ClutterActor that received the signal
@@ -3353,6 +4003,9 @@ clutter_actor_class_init (ClutterActorClass *klass)
   klass->show_all = clutter_actor_show;
   klass->hide = clutter_actor_real_hide;
   klass->hide_all = clutter_actor_hide;
+  klass->map = clutter_actor_real_map;
+  klass->unmap = clutter_actor_real_unmap;
+  klass->unrealize = clutter_actor_real_unrealize;
   klass->pick = clutter_actor_real_pick;
   klass->get_preferred_width = clutter_actor_real_get_preferred_width;
   klass->get_preferred_height = clutter_actor_real_get_preferred_height;
@@ -3473,9 +4126,8 @@ clutter_actor_queue_relayout (ClutterActor *self)
   priv->needs_height_request = TRUE;
   priv->needs_allocation     = TRUE;
 
-  /* always repaint also */
-  if (CLUTTER_ACTOR_IS_VISIBLE (self))
-    clutter_actor_queue_redraw (self);
+  /* always repaint also (no-op if not mapped) */
+  clutter_actor_queue_redraw (self);
 
   /* We need to go all the way up the hierarchy */
   if (priv->parent_actor)
@@ -6225,19 +6877,15 @@ clutter_actor_set_parent (ClutterActor *self,
   if (!(CLUTTER_PRIVATE_FLAGS (self) & CLUTTER_ACTOR_IN_REPARENT))
     g_signal_emit (self, actor_signals[PARENT_SET], 0, NULL);
 
-  /* the invariant is: if the parent is realized, the we must be
-   * realized after set_parent(). the call to clutter_actor_show()
-   * will cause this anyway, but we need to maintain the invariant
-   * even for actors that have :show-on-set-parent set to FALSE
+  /* If parent is mapped or realized, we need to also be mapped or
+   * realized once we're inside the parent.
    */
-  if (CLUTTER_ACTOR_IS_REALIZED (priv->parent_actor))
-    clutter_actor_realize (self);
+  clutter_actor_update_map_state (self, MAP_STATE_CHECK);
 
   if (priv->show_on_set_parent)
     clutter_actor_show (self);
 
-  if (CLUTTER_ACTOR_IS_VISIBLE (priv->parent_actor) &&
-      CLUTTER_ACTOR_IS_VISIBLE (self))
+  if (CLUTTER_ACTOR_IS_MAPPED (self))
     {
       clutter_actor_queue_redraw (self);
     }
@@ -6284,6 +6932,8 @@ clutter_actor_get_parent (ClutterActor *self)
  * Retrieves the 'paint' visibility of an actor recursively checking for non
  * visible parents.
  *
+ * This is by definition the same as CLUTTER_ACTOR_IS_MAPPED().
+ *
  * Return Value: TRUE if the actor is visibile and will be painted.
  *
  * Since: 0.8.4
@@ -6293,17 +6943,7 @@ clutter_actor_get_paint_visibility (ClutterActor *actor)
 {
   g_return_val_if_fail (CLUTTER_IS_ACTOR (actor), FALSE);
 
-  do
-    {
-      if (!CLUTTER_ACTOR_IS_VISIBLE (actor))
-        return FALSE;
-
-      if (CLUTTER_PRIVATE_FLAGS (actor) & CLUTTER_ACTOR_IS_TOPLEVEL)
-        return TRUE;
-    }
-  while ((actor = clutter_actor_get_parent (actor)) != NULL);
-
-  return FALSE;
+  return CLUTTER_ACTOR_IS_MAPPED (actor);
 }
 
 /**
@@ -6323,8 +6963,7 @@ clutter_actor_unparent (ClutterActor *self)
 {
   ClutterActorPrivate *priv;
   ClutterActor *old_parent;
-
-  gboolean show_on_set_parent_enabled = TRUE;
+  gboolean was_mapped;
 
   g_return_if_fail (CLUTTER_IS_ACTOR (self));
 
@@ -6333,47 +6972,29 @@ clutter_actor_unparent (ClutterActor *self)
   if (priv->parent_actor == NULL)
     return;
 
-  show_on_set_parent_enabled = priv->show_on_set_parent;
+  was_mapped = CLUTTER_ACTOR_IS_MAPPED (self);
+
+  /* we need to unrealize *before* we set parent_actor to NULL,
+   * because in an unrealize method actors are dissociating from the
+   * stage, which means they need to be able to
+   * clutter_actor_get_stage(). This should unmap and unrealize,
+   * unless we're reparenting.
+   */
+  clutter_actor_update_map_state (self, MAP_STATE_MAKE_UNREALIZED);
 
   old_parent = priv->parent_actor;
   priv->parent_actor = NULL;
 
-  /* if we are uparenting we hide ourselves; if we are just reparenting
-   * there's no need to do that, as the paint is fast enough.
-   */
-  if (CLUTTER_ACTOR_IS_REALIZED (self))
-    {
-      if (!(CLUTTER_PRIVATE_FLAGS (self) & CLUTTER_ACTOR_IN_REPARENT))
-        clutter_actor_hide (self);
-    }
-
-  /* clutter_actor_hide() will set the :show-on-set-parent property
-   * to FALSE because the actor doesn't have a parent anymore; but
-   * we need to return the actor to its initial state, so we force
-   * the state of the :show-on-set-parent property to its value
-   * previous the unparenting
-   */
-  priv->show_on_set_parent = show_on_set_parent_enabled;
-
-  if (CLUTTER_ACTOR_IS_VISIBLE (self))
-    clutter_actor_queue_redraw (self);
-
   /* clutter_actor_reparent() will emit ::parent-set for us */
   if (!(CLUTTER_PRIVATE_FLAGS (self) & CLUTTER_ACTOR_IN_REPARENT))
     g_signal_emit (self, actor_signals[PARENT_SET], 0, old_parent);
 
-  /* Queue a redraw on old_parent */
-  if (CLUTTER_ACTOR_IS_VISIBLE (old_parent))
+  /* Queue a redraw on old_parent only if we were painted in the first
+   * place. Will be no-op if old parent is not shown.
+   */
+  if (was_mapped && !CLUTTER_ACTOR_IS_MAPPED (self))
     clutter_actor_queue_redraw (old_parent);
 
-  /* Could also need to relayout */
-  if (old_parent->priv->needs_width_request ||
-      old_parent->priv->needs_height_request ||
-      old_parent->priv->needs_allocation)
-    {
-      clutter_actor_queue_relayout (old_parent);
-    }
-
   /* remove the reference we acquired in clutter_actor_set_parent() */
   g_object_unref (self);
 }
@@ -6385,7 +7006,10 @@ clutter_actor_unparent (ClutterActor *self)
  *
  * This function resets the parent actor of @self.  It is
  * logically equivalent to calling clutter_actor_unparent()
- * and clutter_actor_set_parent().
+ * and clutter_actor_set_parent(), but more efficiently
+ * implemented, ensures the child is not finalized
+ * when unparented, and emits the parent-set signal only
+ * one time.
  *
  * Since: 0.2
  */
@@ -6438,6 +7062,9 @@ clutter_actor_reparent (ClutterActor *self,
       g_object_unref (self);
 
       CLUTTER_UNSET_PRIVATE_FLAGS (self, CLUTTER_ACTOR_IN_REPARENT);
+
+      /* the IN_REPARENT flag suspends state updates */
+      clutter_actor_update_map_state (self, MAP_STATE_CHECK);
    }
 }
 /**
diff --git a/clutter/clutter-actor.h b/clutter/clutter-actor.h
index 6ff80c3e5..c4941ecdd 100644
--- a/clutter/clutter-actor.h
+++ b/clutter/clutter-actor.h
@@ -76,7 +76,7 @@ G_BEGIN_DECLS
 
 #define CLUTTER_ACTOR_IS_MAPPED(e)      ((((ClutterActor*)(e))->flags & CLUTTER_ACTOR_MAPPED) != FALSE)
 #define CLUTTER_ACTOR_IS_REALIZED(e)    ((((ClutterActor*)(e))->flags & CLUTTER_ACTOR_REALIZED) != FALSE)
-#define CLUTTER_ACTOR_IS_VISIBLE(e)     (CLUTTER_ACTOR_IS_MAPPED (e) && CLUTTER_ACTOR_IS_REALIZED (e))
+#define CLUTTER_ACTOR_IS_VISIBLE(e)     ((((ClutterActor*)(e))->flags & CLUTTER_ACTOR_VISIBLE) != FALSE)
 #define CLUTTER_ACTOR_IS_REACTIVE(e)    ((((ClutterActor*)(e))->flags & CLUTTER_ACTOR_REACTIVE) != FALSE)
 
 typedef struct _ClutterActorClass    ClutterActorClass;
@@ -102,11 +102,12 @@ typedef void (*ClutterCallback) (ClutterActor *actor, gpointer data);
 
 /**
  * ClutterActorFlags:
- * @CLUTTER_ACTOR_MAPPED: the actor has been painted
+ * @CLUTTER_ACTOR_MAPPED: the actor will be painted (is visible, and inside a toplevel, and all parents visible)
  * @CLUTTER_ACTOR_REALIZED: the resources associated to the actor have been
  *   allocated
  * @CLUTTER_ACTOR_REACTIVE: the actor 'reacts' to mouse events emmitting event
  *   signals
+ * @CLUTTER_ACTOR_VISIBLE: the actor has been shown by the application program
  *
  * Flags used to signal the state of an actor.
  */
@@ -114,7 +115,8 @@ typedef enum
 {
   CLUTTER_ACTOR_MAPPED   = 1 << 1,
   CLUTTER_ACTOR_REALIZED = 1 << 2,
-  CLUTTER_ACTOR_REACTIVE = 1 << 3
+  CLUTTER_ACTOR_REACTIVE = 1 << 3,
+  CLUTTER_ACTOR_VISIBLE  = 1 << 4
 } ClutterActorFlags;
 
 /**
@@ -220,6 +222,8 @@ struct _ClutterActorClass
   void (* hide_all)             (ClutterActor          *actor);
   void (* realize)              (ClutterActor          *actor);
   void (* unrealize)            (ClutterActor          *actor);
+  void (* map)                  (ClutterActor          *actor);
+  void (* unmap)                (ClutterActor          *actor);
   void (* paint)                (ClutterActor          *actor);
   void (* parent_set)           (ClutterActor          *actor,
                                  ClutterActor          *old_parent);
@@ -280,6 +284,8 @@ void                  clutter_actor_hide                      (ClutterActor
 void                  clutter_actor_hide_all                  (ClutterActor          *self);
 void                  clutter_actor_realize                   (ClutterActor          *self);
 void                  clutter_actor_unrealize                 (ClutterActor          *self);
+void                  clutter_actor_map                       (ClutterActor          *self);
+void                  clutter_actor_unmap                     (ClutterActor          *self);
 void                  clutter_actor_paint                     (ClutterActor          *self);
 void                  clutter_actor_pick                      (ClutterActor          *self,
                                                                const ClutterColor    *color);
diff --git a/clutter/clutter-group.c b/clutter/clutter-group.c
index 712acb464..a6a15d4ee 100644
--- a/clutter/clutter-group.c
+++ b/clutter/clutter-group.c
@@ -105,22 +105,6 @@ clutter_group_paint (ClutterActor *actor)
                                                : "unknown");
 }
 
-static void
-clutter_group_realize (ClutterActor *actor)
-{
-  clutter_container_foreach (CLUTTER_CONTAINER (actor),
-                             CLUTTER_CALLBACK (clutter_actor_realize),
-                             NULL);
-}
-
-static void
-clutter_group_unrealize (ClutterActor *actor)
-{
-  clutter_container_foreach (CLUTTER_CONTAINER (actor),
-                             CLUTTER_CALLBACK (clutter_actor_unrealize),
-                             NULL);
-}
-
 static void
 clutter_group_pick (ClutterActor       *actor,
 		    const ClutterColor *color)
@@ -587,8 +571,6 @@ clutter_group_class_init (ClutterGroupClass *klass)
   actor_class->pick            = clutter_group_pick;
   actor_class->show_all        = clutter_group_real_show_all;
   actor_class->hide_all        = clutter_group_real_hide_all;
-  actor_class->realize         = clutter_group_realize;
-  actor_class->unrealize       = clutter_group_unrealize;
 
   actor_class->get_preferred_width  = clutter_group_get_preferred_width;
   actor_class->get_preferred_height = clutter_group_get_preferred_height;
diff --git a/clutter/clutter-private.h b/clutter/clutter-private.h
index 912a05c01..f102a0b90 100644
--- a/clutter/clutter-private.h
+++ b/clutter/clutter-private.h
@@ -217,6 +217,10 @@ gboolean _clutter_boolean_handled_accumulator (GSignalInvocationHint *ihint,
 void _clutter_actor_apply_modelview_transform_recursive (ClutterActor *self,
 						       ClutterActor *ancestor);
 
+void _clutter_actor_rerealize           (ClutterActor    *self,
+                                         ClutterCallback  callback,
+                                         void            *data);
+
 void _clutter_actor_set_opacity_parent (ClutterActor *self,
                                         ClutterActor *parent);
 
diff --git a/clutter/clutter-stage.c b/clutter/clutter-stage.c
index 14b9505c3..9e3576264 100644
--- a/clutter/clutter-stage.c
+++ b/clutter/clutter-stage.c
@@ -263,15 +263,13 @@ clutter_stage_realize (ClutterActor *self)
 {
   ClutterStagePrivate *priv = CLUTTER_STAGE (self)->priv;
 
-  CLUTTER_ACTOR_SET_FLAGS (self, CLUTTER_ACTOR_REALIZED);
-
   /* Make sure the viewport and projection matrix are valid for the
      first paint (which will likely occur before the ConfigureNotify
      is received) */
   CLUTTER_SET_PRIVATE_FLAGS (self, CLUTTER_ACTOR_SYNC_MATRICES);
 
   g_assert (priv->impl != NULL);
-  CLUTTER_ACTOR_GET_CLASS (priv->impl)->realize (priv->impl);
+  clutter_actor_realize (priv->impl);
 
   /* ensure that the stage is using the context if the
    * realization sequence was successful
@@ -287,12 +285,9 @@ clutter_stage_unrealize (ClutterActor *self)
 {
   ClutterStagePrivate *priv = CLUTTER_STAGE (self)->priv;
 
-  /* unset the flag */
-  CLUTTER_ACTOR_UNSET_FLAGS (self, CLUTTER_ACTOR_REALIZED);
-
   /* and then unrealize the implementation */
   g_assert (priv->impl != NULL);
-  CLUTTER_ACTOR_GET_CLASS (priv->impl)->unrealize (priv->impl);
+  clutter_actor_unrealize (priv->impl);
 
   clutter_stage_ensure_current (CLUTTER_STAGE (self));
 }
@@ -304,9 +299,6 @@ clutter_stage_show (ClutterActor *self)
 
   g_assert (priv->impl != NULL);
 
-  if (!CLUTTER_ACTOR_IS_REALIZED (priv->impl))
-    clutter_actor_realize (priv->impl);
-
   clutter_actor_show (priv->impl);
 
   CLUTTER_ACTOR_CLASS (clutter_stage_parent_class)->show (self);
@@ -394,6 +386,14 @@ clutter_stage_real_queue_redraw (ClutterActor *actor,
     }
 }
 
+static void
+set_offscreen_while_unrealized (ClutterActor *actor,
+                                void         *data)
+{
+  CLUTTER_STAGE (actor)->priv->is_offscreen =
+    GPOINTER_TO_INT (data);
+}
+
 static void
 clutter_stage_set_property (GObject      *object,
 			    guint         prop_id,
@@ -415,24 +415,26 @@ clutter_stage_set_property (GObject      *object,
       break;
 
     case PROP_OFFSCREEN:
-      if (priv->is_offscreen == g_value_get_boolean (value))
-	return;
+      {
+        gboolean was_showing;
 
-      if (CLUTTER_ACTOR_IS_REALIZED (actor))
-        {
-          /* Backend needs to check this prop and handle accordingly
-           * in realise.
-           * FIXME: More 'obvious' implementation needed?
-          */
-          clutter_actor_unrealize (actor);
-          priv->is_offscreen = g_value_get_boolean (value);
-          clutter_actor_realize (actor);
+        if (priv->is_offscreen == g_value_get_boolean (value))
+          return;
 
-	  if (!CLUTTER_ACTOR_IS_REALIZED (actor))
-	    priv->is_offscreen = ~g_value_get_boolean (value);
-        }
-      else
-        priv->is_offscreen = g_value_get_boolean (value);
+        was_showing = CLUTTER_ACTOR_IS_VISIBLE (actor);
+
+        /* Backend needs to check this prop and handle accordingly
+         * in realise.
+         * FIXME: More 'obvious' implementation needed?
+         */
+        _clutter_actor_rerealize (actor,
+                                  set_offscreen_while_unrealized,
+                                  GINT_TO_POINTER (g_value_get_boolean (value)));
+
+        if (was_showing &&
+            !CLUTTER_ACTOR_IS_REALIZED (actor))
+          priv->is_offscreen = ~g_value_get_boolean (value);
+      }
       break;
 
     case PROP_FULLSCREEN:
@@ -534,7 +536,7 @@ clutter_stage_dispose (GObject *object)
   ClutterStagePrivate *priv = stage->priv;
   ClutterStageManager *stage_manager = clutter_stage_manager_get_default ();
 
-  clutter_actor_unrealize (CLUTTER_ACTOR (object));
+  clutter_actor_hide (CLUTTER_ACTOR (object));
 
   if (priv->update_idle)
     {
diff --git a/clutter/clutter-texture.c b/clutter/clutter-texture.c
index 52dc01c98..50c2e5de0 100644
--- a/clutter/clutter-texture.c
+++ b/clutter/clutter-texture.c
@@ -335,19 +335,15 @@ clutter_texture_realize (ClutterActor *actor)
     }
   else
     {
-      if (clutter_feature_available (CLUTTER_FEATURE_TEXTURE_READ_PIXELS))
-	{
-	  /* Dont allow realization with no data - note set_data
-	   * will set realize flags.
-	   */
-	  CLUTTER_NOTE (TEXTURE,
-			"Texture has no image data cannot realize");
-
-	  CLUTTER_NOTE (TEXTURE, "flags %i", actor->flags);
-	  CLUTTER_ACTOR_UNSET_FLAGS (actor, CLUTTER_ACTOR_REALIZED);
-	  CLUTTER_NOTE (TEXTURE, "flags %i", actor->flags);
-	  return;
-	}
+      /* If we have no data, then realization is a no-op but
+       * we still want to be in REALIZED state to maintain
+       * invariants. We may have already created the texture
+       * if someone set some data earlier, or we may create it
+       * later if someone sets some data later. The fact that
+       * we may have created it earlier is really a bug, since
+       * it means ClutterTexture can have GL resources without
+       * being realized.
+       */
     }
 
   CLUTTER_NOTE (TEXTURE, "Texture realized");
@@ -539,9 +535,6 @@ clutter_texture_paint (ClutterActor *self)
       return;
     }
 
-  if (!CLUTTER_ACTOR_IS_REALIZED (CLUTTER_ACTOR(texture)))
-    clutter_actor_realize (CLUTTER_ACTOR(texture));
-
   if (priv->fbo_handle != COGL_INVALID_HANDLE)
     {
       ClutterMainContext *context;
@@ -1392,6 +1385,12 @@ clutter_texture_get_cogl_texture (ClutterTexture *texture)
  * @cogl_tex. A reference to the texture is taken so if the handle is
  * no longer needed it should be deref'd with cogl_handle_unref.
  *
+ * This should not be called on an unrealizable texture (one that
+ * isn't inside a stage). (Currently the ClutterTexture
+ * implementation relies on being able to have a GL texture while
+ * unrealized, which means you can get away with it, but it's
+ * not correct and may change in the future.)
+ *
  * Since: 0.8
  */
 void
@@ -1405,6 +1404,12 @@ clutter_texture_set_cogl_texture (ClutterTexture  *texture,
   g_return_if_fail (CLUTTER_IS_TEXTURE (texture));
   g_return_if_fail (cogl_is_texture (cogl_tex));
 
+  /* FIXME this implementation should realize the actor if it's in a
+   * stage, and warn and return if not in a stage yet. However, right
+   * now everything would break if we did that, so we just fudge it
+   * and we're broken: we can have a texture without being realized.
+   */
+
   priv = texture->priv;
 
   width = cogl_texture_get_width (cogl_tex);
@@ -1420,6 +1425,14 @@ clutter_texture_set_cogl_texture (ClutterTexture  *texture,
 
   /* Remove old texture */
   texture_free_gl_resources (texture);
+
+  /* Free any saved data so realization doesn't resend it to GL */
+  if (priv->local_data)
+    {
+      g_free (priv->local_data);
+      priv->local_data = NULL;
+    }
+
   /* Use the new texture */
 
   cogl_material_set_layer (priv->material, 0, cogl_tex);
@@ -1436,8 +1449,6 @@ clutter_texture_set_cogl_texture (ClutterTexture  *texture,
 		priv->width,
 		priv->height);
 
-  CLUTTER_ACTOR_SET_FLAGS (CLUTTER_ACTOR (texture), CLUTTER_ACTOR_REALIZED);
-
   if (size_change)
     {
       g_signal_emit (texture, texture_signals[SIZE_CHANGE], 0,
@@ -1480,6 +1491,10 @@ clutter_texture_set_from_data (ClutterTexture     *texture,
   if (priv->filter_quality == CLUTTER_TEXTURE_QUALITY_HIGH)
     flags |= COGL_TEXTURE_AUTO_MIPMAP;
 
+  /* FIXME if we are not realized, we should store the data
+   * for future use, instead of creating the texture.
+   */
+
   new_texture = cogl_texture_new_from_data (width, height,
                                             max_waste, flags,
                                             source_format,
@@ -2004,15 +2019,8 @@ clutter_texture_set_filter_quality (ClutterTexture        *texture,
            filter_quality == CLUTTER_TEXTURE_QUALITY_HIGH) &&
            CLUTTER_ACTOR_IS_REALIZED (texture))
         {
-          gboolean was_visible;
-
-          was_visible = CLUTTER_ACTOR_IS_VISIBLE (CLUTTER_ACTOR (texture));
-
-          clutter_actor_unrealize (CLUTTER_ACTOR (texture));
-          clutter_actor_realize (CLUTTER_ACTOR (texture));
-
-          if (was_visible)
-            clutter_actor_show (CLUTTER_ACTOR (texture));
+          _clutter_actor_rerealize (CLUTTER_ACTOR (texture),
+                                    NULL, NULL);
         }
 
       g_object_notify (G_OBJECT (texture), "filter-quality");
@@ -2172,9 +2180,13 @@ clutter_texture_get_base_size (ClutterTexture *texture,
 
   /* Attempt to realize, mainly for subclasses ( such as labels )
    * which may not create pixbuf data and thus base size until
-   * realization happens.
+   * realization happens. If we aren't in a stage we can't realize
+   * though. Doing this here is probably just broken; instead
+   * we could virtualize get_base_size, or have the subclasses
+   * create the pixbufs sooner, or something better.
    */
-  if (!CLUTTER_ACTOR_IS_REALIZED (texture))
+  if (!CLUTTER_ACTOR_IS_REALIZED (texture) &&
+      clutter_actor_get_stage (CLUTTER_ACTOR (texture)) != NULL)
     clutter_actor_realize (CLUTTER_ACTOR (texture));
 
   if (width)
@@ -2251,8 +2263,15 @@ clutter_texture_set_area_from_rgb_data (ClutterTexture     *texture,
   if ((flags & CLUTTER_TEXTURE_RGB_FLAG_PREMULT))
     source_format |= COGL_PREMULT_BIT;
 
-  clutter_actor_realize (CLUTTER_ACTOR (texture));
+  /* attempt to realize ... */
+  if (!CLUTTER_ACTOR_IS_REALIZED (texture) &&
+      clutter_actor_get_stage (CLUTTER_ACTOR (texture)) != NULL)
+    clutter_actor_realize (CLUTTER_ACTOR (texture));
 
+  /* due to the fudging of clutter_texture_set_cogl_texture()
+   * which allows setting a texture pre-realize, we may end
+   * up having a texture even if we couldn't realize yet.
+   */
   cogl_texture = clutter_texture_get_cogl_texture (texture);
   if (cogl_texture == COGL_INVALID_HANDLE)
     {
@@ -2329,8 +2348,6 @@ on_fbo_source_size_change (GObject          *object,
       if (priv->fbo_handle == COGL_INVALID_HANDLE)
         {
           g_warning ("%s: Offscreen texture creation failed", G_STRLOC);
-	  CLUTTER_ACTOR_UNSET_FLAGS (CLUTTER_ACTOR (texture),
-				     CLUTTER_ACTOR_REALIZED);
           return;
         }
 
diff --git a/clutter/eglnative/clutter-stage-egl.c b/clutter/eglnative/clutter-stage-egl.c
index 1d7f59603..7e766ed71 100644
--- a/clutter/eglnative/clutter-stage-egl.c
+++ b/clutter/eglnative/clutter-stage-egl.c
@@ -49,7 +49,8 @@ clutter_stage_egl_unrealize (ClutterActor *actor)
 
   CLUTTER_MARK();
 
-  CLUTTER_ACTOR_CLASS (clutter_stage_egl_parent_class)->unrealize (actor);
+  if (CLUTTER_ACTOR_CLASS (clutter_stage_egl_parent_class)->unrealize != NULL)
+    CLUTTER_ACTOR_CLASS (clutter_stage_egl_parent_class)->unrealize (actor);
 
   if (stage_egl->egl_surface)
     {
@@ -253,8 +254,6 @@ clutter_stage_egl_dispose (GObject *gobject)
 {
   ClutterStageEGL *stage_egl = CLUTTER_STAGE_EGL (gobject);
 
-  clutter_actor_unrealize (CLUTTER_ACTOR (stage_egl));
-
   G_OBJECT_CLASS (clutter_stage_egl_parent_class)->dispose (gobject);
 }
 
diff --git a/clutter/eglx/clutter-stage-egl.c b/clutter/eglx/clutter-stage-egl.c
index 5add2a862..97a8631ca 100644
--- a/clutter/eglx/clutter-stage-egl.c
+++ b/clutter/eglx/clutter-stage-egl.c
@@ -38,7 +38,8 @@ clutter_stage_egl_unrealize (ClutterActor *actor)
 
   g_object_get (stage_x11->wrapper, "offscreen", &was_offscreen, NULL);
 
-  CLUTTER_ACTOR_CLASS (clutter_stage_egl_parent_class)->unrealize (actor);
+  if (CLUTTER_ACTOR_CLASS (clutter_stage_egl_parent_class)->unrealize != NULL)
+    CLUTTER_ACTOR_CLASS (clutter_stage_egl_parent_class)->unrealize (actor);
 
   clutter_x11_trap_x_errors ();
 
@@ -275,9 +276,6 @@ clutter_stage_egl_dispose (GObject *gobject)
   ClutterStageEGL *stage_egl = CLUTTER_STAGE_EGL (gobject);
   ClutterStageX11 *stage_x11 = CLUTTER_STAGE_X11 (gobject);
 
-  if (stage_x11->xwin)
-    clutter_actor_unrealize (CLUTTER_ACTOR (stage_egl));
-
   G_OBJECT_CLASS (clutter_stage_egl_parent_class)->dispose (gobject);
 }
 
diff --git a/clutter/fruity/clutter-fruity.c b/clutter/fruity/clutter-fruity.c
index 1198c5903..75cbcf1a3 100644
--- a/clutter/fruity/clutter-fruity.c
+++ b/clutter/fruity/clutter-fruity.c
@@ -393,6 +393,10 @@ typedef struct {
   stage_fruity = CLUTTER_STAGE_EGL(backend_fruity->stage);
 
   alive = FALSE;
+  /* FIXME why is this unrealize here? is the intent to destroy the stage?
+   * or hide it? Trying to clean up all manual unrealization so
+   * clutter_actor_unrealize() can be made private to clutter-actor.c
+   */
   clutter_actor_unrealize (CLUTTER_ACTOR (stage_fruity));
   clutter_main_quit ();
 }
diff --git a/clutter/fruity/clutter-stage-fruity.c b/clutter/fruity/clutter-stage-fruity.c
index 1f6b192fb..ea1603a58 100644
--- a/clutter/fruity/clutter-stage-fruity.c
+++ b/clutter/fruity/clutter-stage-fruity.c
@@ -45,7 +45,8 @@ clutter_stage_egl_unrealize (ClutterActor *actor)
 
   CLUTTER_MARK();
 
-  CLUTTER_ACTOR_CLASS (clutter_stage_egl_parent_class)->unrealize (actor);
+  if (CLUTTER_ACTOR_CLASS (clutter_stage_egl_parent_class)->unrealize != NULL)
+    CLUTTER_ACTOR_CLASS (clutter_stage_egl_parent_class)->unrealize (actor);
 
   if (stage_egl->egl_surface)
     {
@@ -222,8 +223,6 @@ clutter_stage_egl_dispose (GObject *gobject)
 {
   ClutterStageEGL *stage_egl = CLUTTER_STAGE_EGL (gobject);
 
-  clutter_actor_unrealize (CLUTTER_ACTOR (stage_egl));
-
   G_OBJECT_CLASS (clutter_stage_egl_parent_class)->dispose (gobject);
 }
 
diff --git a/clutter/glx/clutter-stage-glx.c b/clutter/glx/clutter-stage-glx.c
index 12f54cb38..650cf9445 100644
--- a/clutter/glx/clutter-stage-glx.c
+++ b/clutter/glx/clutter-stage-glx.c
@@ -67,10 +67,8 @@ clutter_stage_glx_unrealize (ClutterActor *actor)
 
   g_object_get (stage_x11->wrapper, "offscreen", &was_offscreen, NULL);
 
-  /* Chain up so all children get unrealized, needed to move texture data
-   * across contexts
-  */
-  CLUTTER_ACTOR_CLASS (clutter_stage_glx_parent_class)->unrealize (actor);
+  if (CLUTTER_ACTOR_CLASS (clutter_stage_glx_parent_class)->unrealize != NULL)
+    CLUTTER_ACTOR_CLASS (clutter_stage_glx_parent_class)->unrealize (actor);
 
   clutter_x11_trap_x_errors ();
 
@@ -310,12 +308,6 @@ fail:
 static void
 clutter_stage_glx_dispose (GObject *gobject)
 {
-  ClutterStageGLX *stage_glx = CLUTTER_STAGE_GLX (gobject);
-  ClutterStageX11 *stage_x11 = CLUTTER_STAGE_X11 (gobject);
-
-  if (stage_x11->xwin)
-    clutter_actor_unrealize (CLUTTER_ACTOR (stage_glx));
-
   G_OBJECT_CLASS (clutter_stage_glx_parent_class)->dispose (gobject);
 }
 
diff --git a/clutter/sdl/clutter-stage-sdl.c b/clutter/sdl/clutter-stage-sdl.c
index 17a8bd0eb..215c28307 100644
--- a/clutter/sdl/clutter-stage-sdl.c
+++ b/clutter/sdl/clutter-stage-sdl.c
@@ -199,8 +199,6 @@ clutter_stage_sdl_dispose (GObject *gobject)
 {
   ClutterStageSDL *stage_sdl = CLUTTER_STAGE_SDL (gobject);
 
-  clutter_actor_unrealize (CLUTTER_ACTOR (stage_sdl));
-
   G_OBJECT_CLASS (clutter_stage_sdl_parent_class)->dispose (gobject);
 }
 
diff --git a/clutter/win32/clutter-stage-win32.c b/clutter/win32/clutter-stage-win32.c
index 5ce8a16c6..7d233c6c2 100644
--- a/clutter/win32/clutter-stage-win32.c
+++ b/clutter/win32/clutter-stage-win32.c
@@ -574,10 +574,8 @@ clutter_stage_win32_unrealize (ClutterActor *actor)
 
   CLUTTER_NOTE (BACKEND, "Unrealizing stage");
 
-  /* Chain up so all children get unrealized, needed to move texture
-   * data across contexts
-   */
-  CLUTTER_ACTOR_CLASS (clutter_stage_win32_parent_class)->unrealize (actor);
+  if (CLUTTER_ACTOR_CLASS (clutter_stage_win32_parent_class)->unrealize != NULL)
+    CLUTTER_ACTOR_CLASS (clutter_stage_win32_parent_class)->unrealize (actor);
 
   if (stage_win32->client_dc)
     {
@@ -602,9 +600,6 @@ clutter_stage_win32_dispose (GObject *gobject)
 {
   ClutterStageWin32 *stage_win32 = CLUTTER_STAGE_WIN32 (gobject);
 
-  if (stage_win32->hwnd)
-    clutter_actor_unrealize (CLUTTER_ACTOR (gobject));
-
   G_OBJECT_CLASS (clutter_stage_win32_parent_class)->dispose (gobject);
 }
 
@@ -749,6 +744,12 @@ clutter_win32_set_stage_foreign (ClutterStage *stage,
   impl = _clutter_stage_get_window (stage);
   stage_win32 = CLUTTER_STAGE_WIN32 (impl);
 
+  /* FIXME this needs updating to use _clutter_actor_rerealize(),
+   * see the analogous code in x11 backend. Probably best if
+   * win32 maintainer does it so they can be sure it compiles
+   * and works.
+   */
+
   clutter_actor_unrealize (actor);
 
   if (!GetClientRect (hwnd, &client_rect))
diff --git a/clutter/x11/clutter-stage-x11.c b/clutter/x11/clutter-stage-x11.c
index 2c4592ace..56a33d9cf 100644
--- a/clutter/x11/clutter-stage-x11.c
+++ b/clutter/x11/clutter-stage-x11.c
@@ -297,8 +297,7 @@ clutter_stage_x11_allocate (ClutterActor          *self,
       if (stage_x11->xpixmap != None)
         {
           /* Need to recreate to resize */
-          clutter_actor_unrealize (self);
-          clutter_actor_realize (self);
+          _clutter_actor_rerealize (self, NULL, NULL);
         }
     }
 
@@ -529,11 +528,6 @@ clutter_stage_x11_finalize (GObject *gobject)
 static void
 clutter_stage_x11_dispose (GObject *gobject)
 {
-  ClutterStageX11 *stage_x11 = CLUTTER_STAGE_X11 (gobject);
-
-  if (stage_x11->xwin)
-    clutter_actor_unrealize (CLUTTER_ACTOR (stage_x11));
-
   G_OBJECT_CLASS (clutter_stage_x11_parent_class)->dispose (gobject);
 }
 
@@ -671,6 +665,29 @@ clutter_x11_get_stage_visual (ClutterStage *stage)
   return CLUTTER_STAGE_X11 (impl)->xvisinfo;
 }
 
+typedef struct {
+  ClutterStageX11 *stage_x11;
+  ClutterGeometry geom;
+  Window xwindow;
+} ForeignWindowData;
+
+static void
+set_foreign_window_callback (ClutterActor *actor,
+                             void         *data)
+{
+  ForeignWindowData *fwd = data;
+
+  CLUTTER_NOTE (BACKEND, "Setting foreign window (0x%x)", (int) fwd->xwindow);
+
+  fwd->stage_x11->xwin = fwd->xwindow;
+  fwd->stage_x11->is_foreign_xwin = TRUE;
+
+  fwd->stage_x11->xwin_width = fwd->geom.width;
+  fwd->stage_x11->xwin_height = fwd->geom.height;
+
+  clutter_actor_set_geometry (actor, &fwd->geom);
+}
+
 /**
  * clutter_x11_set_stage_foreign:
  * @stage: a #ClutterStage
@@ -693,7 +710,7 @@ clutter_x11_set_stage_foreign (ClutterStage *stage,
   guint width, height, border, depth;
   Window root_return;
   Status status;
-  ClutterGeometry geom;
+  ForeignWindowData fwd;
 
   g_return_val_if_fail (CLUTTER_IS_STAGE (stage), FALSE);
   g_return_val_if_fail (xwindow != None, FALSE);
@@ -721,20 +738,16 @@ clutter_x11_set_stage_foreign (ClutterStage *stage,
       return FALSE;
     }
 
-  clutter_actor_unrealize (actor);
+  fwd.stage_x11 = stage_x11;
+  fwd.xwindow = xwindow;
+  fwd.geom.x = x;
+  fwd.geom.y = y;
+  fwd.geom.width = width;
+  fwd.geom.height = height;
 
-  CLUTTER_NOTE (BACKEND, "Setting foreign window (0x%x)", (int) xwindow);
-
-  stage_x11->xwin = xwindow;
-  stage_x11->is_foreign_xwin = TRUE;
-
-  geom.x = x;
-  geom.y = y;
-  geom.width = stage_x11->xwin_width = width;
-  geom.height = stage_x11->xwin_height = height;
-
-  clutter_actor_set_geometry (actor, &geom);
-  clutter_actor_realize (actor);
+  _clutter_actor_rerealize (actor,
+                            set_foreign_window_callback,
+                            &fwd);
 
   CLUTTER_SET_PRIVATE_FLAGS (actor, CLUTTER_ACTOR_SYNC_MATRICES);
 
@@ -744,11 +757,11 @@ clutter_x11_set_stage_foreign (ClutterStage *stage,
 void
 clutter_stage_x11_map (ClutterStageX11 *stage_x11)
 {
-  /* set the mapped flag on the implementation */
-  CLUTTER_ACTOR_SET_FLAGS (stage_x11, CLUTTER_ACTOR_MAPPED);
+  /* set the mapped state on the wrapper */
+  clutter_actor_map (CLUTTER_ACTOR (stage_x11->wrapper));
 
-  /* and on the wrapper itself */
-  CLUTTER_ACTOR_SET_FLAGS (stage_x11->wrapper, CLUTTER_ACTOR_MAPPED);
+  /* and on the implementation second */
+  clutter_actor_map (CLUTTER_ACTOR (stage_x11));
 
   if (stage_x11->fullscreen_on_map)
     clutter_stage_fullscreen (CLUTTER_STAGE (stage_x11->wrapper));
@@ -762,8 +775,8 @@ void
 clutter_stage_x11_unmap (ClutterStageX11 *stage_x11)
 {
   /* like above, unset the MAPPED stage on both the implementation and
-   * the wrapper
+   * the wrapper, but unmap in reverse order from map
    */
-  CLUTTER_ACTOR_UNSET_FLAGS (stage_x11, CLUTTER_ACTOR_MAPPED);
-  CLUTTER_ACTOR_UNSET_FLAGS (stage_x11->wrapper, CLUTTER_ACTOR_MAPPED);
+  clutter_actor_unmap (CLUTTER_ACTOR (stage_x11));
+  clutter_actor_unmap (CLUTTER_ACTOR (stage_x11->wrapper));
 }
diff --git a/doc/clutter-actor-invariants.txt b/doc/clutter-actor-invariants.txt
index c64f84cc6..b7c74659f 100644
--- a/doc/clutter-actor-invariants.txt
+++ b/doc/clutter-actor-invariants.txt
@@ -28,20 +28,56 @@ ClutterActor.
  a. Public ClutterActor Flags
 
 CLUTTER_ACTOR_REALIZED
-        Set by clutter_actor_realize(), unset by clutter_actor_unrealize().
-        Means: the actor has GPU resources associated to its paint cycle.
-        Once realized an actor needs to be explicitly unrealized unless
-        being destroyed. Hide, reparent etc will not unrealize.
+        Means: the actor has GPU resources associated to its paint
+        cycle.
+
+        Set by clutter_actor_realize(), unset by
+        clutter_actor_unrealize(). Generally set implicitly when the
+        actor becomes MAPPED (see below).
+
+        May only be set if one of the actor's ancestors is a toplevel.
+        May only be set if all of the actor's ancestors are realized.
+
+        Once realized an actor remains realized until removed from the
+        toplevel. Hide, reparent will not unrealize; but unparent or
+        destroy will unrealize since they remove the actor from the
+        toplevel.
 
 CLUTTER_ACTOR_MAPPED
-        Set by clutter_actor_show(), unset by clutter_actor_hide()
-        May only be set if CLUTTER_ACTOR_IS_REALIZED (actor).
-        Means: the actor has been set as visible
+        Means: the actor will be painted if the stage is mapped.
+
+        On non-toplevels, will be set if all of the following are
+        true, and unset otherwise:
+        * the actor's VISIBLE flag is set
+        * all of the actor's non-toplevel ancestors have the MAPPED
+          flag set
+        * the actor has a toplevel ancestor
+        * the toplevel ancestor's VISIBLE flag is set
+        * the toplevel ancestor's REALIZED flag is set
+
+        On toplevels, MAPPED is set asynchronously when the window
+        system notifies Clutter that the toplevel has been made
+        visible on the screen.
+
+        The MAPPED flag requires that an actor is REALIZED. When
+        Clutter sets the MAPPED flag, it forces realization; this is
+        the "normal" way for realization to occur, though explicit
+        realization with clutter_actor_realize() is permitted.
 
 CLUTTER_ACTOR_VISIBLE
-        Implies: CLUTTER_ACTOR_IS_REALIZED (actor)
-                 &&
-                 CLUTTER_ACTOR_IS_MAPPED (actor)
+        Means: the actor's "visible" property was set to true by
+        the application programmer.
+
+        Set by clutter_actor_show(), unset by clutter_actor_hide().
+
+        This is an application-controlled property, while MAPPED and
+        REALIZED are usually managed by Clutter (with the exception
+        that applications can "realize early" with
+        clutter_actor_realize()).
+
+        If VISIBLE is unset, the actor (and any children) must
+        be immediately unmapped, to maintain the invariants for
+        the MAPPED flag.
 
 CLUTTER_ACTOR_REACTIVE
         Set and unset by clutter_actor_set_reactive()
@@ -51,7 +87,7 @@ CLUTTER_ACTOR_REACTIVE
             parent implementation. In the case of ClutterGroup it
             being marked unreactive does not mark all children unreactive.
           * Clutter stage is always reactive.
-                  
+
 
  b. Private ClutterActor flags
 
@@ -63,7 +99,18 @@ CLUTTER_ACTOR_IS_TOPLEVEL
         Set internally by the initialization of ClutterStage
 
 CLUTTER_ACTOR_IN_REPARENT
-        Set internally by clutter_actor_reparent()
+        Set internally by clutter_actor_reparent(). This flag
+        optimizes the reparent process by avoiding the need
+        to pass through an unrealized state when the actor is
+        removed from the old parent.
+
+CLUTTER_ACTOR_ABOUT_TO_UNPARENT
+        Set internally during part of clutter_actor_unparent().
+        Causes the actor to pretend it has no parent, then
+        update invariants; which effectively forces the actor
+        to unrealize. The purpose of this is to unrealize _before_ the
+        actor is removed from the stage, so unrealize implementations
+        can use clutter_actor_get_stage().
 
 CLUTTER_ACTOR_SYNC_MATRICES
         Set internally by ClutterStage implementations
@@ -101,68 +148,118 @@ In the following
 1) CLUTTER_ACTOR_IN_DESTRUCTION => !CLUTTER_ACTOR_IS_MAPPED (actor) &&
                                    !CLUTTER_ACTOR_IS_REALIZED (actor)
 
-        clutter_actor_destroy() will cause an actor to be hidden
-        and unrealized.
+        clutter_actor_destroy() will cause an actor to be unparented,
+        which means the actor must be unmapped and unrealized as
+        well.
 
 2) CLUTTER_ACTOR_IS_MAPPED (actor) => CLUTTER_ACTOR_IS_REALIZED (actor)
 
-        calling clutter_actor_show() on an unrealized actor will cause
-        a realization to happen.
+        when an actor is mapped, it must first be realized.
+
+        This is the most common way an actor becomes realized.
 
 3) if clutter_actor_set_parent (actor, parent):
-     CLUTTER_ACTOR_IS_REALIZED (parent) => CLUTTER_ACTOR_IS_REALIZED (actor)
+     ((parent_is_not_toplevel && CLUTTER_ACTOR_IS_MAPPED (parent)) ||
+      (parent_is_toplevel && CLUTTER_ACTOR_IS_VISIBLE(parent))) &&
+     CLUTTER_ACTOR_IS_VISIBLE (actor)
+                                  => CLUTTER_ACTOR_IS_MAPPED (actor)
 
-        calling clutter_actor_set_parent() on an actor and a realized
-        parent will cause a realization on the actor.
+        calling clutter_actor_set_parent() on an actor and a mapped
+        parent will map the actor if it has been shown.
 
 4) if clutter_actor_unparent (actor):
      CLUTTER_ACTOR_IS_MAPPED (actor) <=> CLUTTER_ACTOR_IN_REPARENT
 
-        calling clutter_actor_unparent() on an actor will hide the actor;
-        calling clutter_actor_reparent() on an actor will leave the actor
-        in the same state.
+        calling clutter_actor_unparent() on an actor will unmap and
+        unrealize the actor since it no longer has a toplevel.
 
-        Neither will unrealize.
+        calling clutter_actor_reparent() on an actor will leave the
+        actor mapped and realized (if it was before) until it has a
+        new parent, at which point the invariants implied by the new
+        parent's state are applied.
+
+5) CLUTTER_ACTOR_IS_REALIZED(actor) => CLUTTER_ACTOR_IS_REALIZED(parent)
+
+        Actors may only be realized if their parent is realized.
+        However, they may be unrealized even though their parent
+        is realized.
+
+        This implies that an actor may not be realized unless
+        it has a parent, or is a toplevel.
+
+        Since only toplevels can realize without a parent, no actor
+        can be realized unless it either is a toplevel or has a
+        toplevel ancestor.
+
+        As long as they are unmapped, actors may be unrealized. This
+        will force all children of the actor to be unrealized, since
+        children may not be realized while parents are unrealized.
+
+6) CLUTTER_ACTOR_IS_MAPPED(actor) <=>
+      (  (  (CLUTTER_ACTOR_IS_VISIBLE(toplevel_parent) &&
+             CLUTTER_ACTOR_IS_REALIZED(toplevel_parent)) ||
+             CLUTTER_ACTOR_IS_MAPPED(non_toplevel_parent)  )  ) &&
+       CLUTTER_ACTOR_IS_VISIBLE(actor)
+
+       Actors _must_ be mapped if and only if they are visible and
+       their parent is mapped, or they are visible and their
+       parent is a toplevel that's realized and visible.
+
+       This invariant enables us to track whether an actor will
+       be painted (whether it's MAPPED) without ever traversing
+       the actor graph.
 
  iii. State changes
 -------------------------------------------------------------------------------
 
 clutter_actor_show:
-        1. if !CLUTTER_ACTOR_REALIZED calls clutter_actor_realize
-        2. sets CLUTTER_ACTOR_MAPPED
+        1. sets VISIBLE
+        2. sets MAPPED if invariants are met; mapping in turn sets
+           REALIZED
 
 clutter_actor_hide:
-        1. sets !CLUTTER_ACTOR_MAPPED
+        1. sets !VISIBLE
+        2. unsets MAPPED if actor was mapped previously
+        3. does not affect REALIZED
 
 clutter_actor_destroy:
         1. sets CLUTTER_ACTOR_IN_DESTRUCTION
+        2. unparents the actor, which in turn implies unmap and unrealize
 
 clutter_actor_realize:
-        sets CLUTTER_ACTOR_REALIZED
+        1. attempts to set REALIZED on all parents, failing if
+           invariants are not met, e.g. not in a toplevel yet
+        2. sets REALIZED on actor if parent was successfully realized
 
-clutter_actor_unrealized:
-        1. if CLUTTER_ACTOR_MAPPED calls clutter_actor_hide
-        2. sets !CLUTTER_ACTOR_REALIZED
+clutter_actor_unrealize:
+        1. sets !VISIBLE which forces !MAPPED
+        2. sets !REALIZED
+        3. !MAPPED and !REALIZED forces unmap and unrealize of all
+           children
 
 clutter_actor_set_parent:
         1. sets actor->parent
-        2. if parent is CLUTTER_ACTOR_REALIZED calls clutter_actor_realize
-        3. if actor->show_on_set_parent is TRUE calls clutter_actor_show
+        2. if actor->show_on_set_parent is TRUE calls clutter_actor_show
+        3. sets MAPPED if all prerequisites are now met for map
         4. if !CLUTTER_ACTOR_IN_REPARENT emits ::parent-set with
            old_parent set to NULL
 
-clutter_actor_unset_parent:
+clutter_actor_unparent:
         1. unsets actor->parent
-        2. if !CLUTTER_ACTOR_IN_REPARENT calls clutter_actor_hide
+        2. if !CLUTTER_ACTOR_IN_REPARENT, sets !MAPPED and !REALIZED
+           since the invariants for those flags are no longer met
         3. if !CLUTTER_ACTOR_IN_REPARENT emits ::parent-set with
            old_parent set to the previous parent
 
 clutter_actor_reparent:
         1. sets CLUTTER_ACTOR_IN_REPARENT
         2. emits ::parent-set with old_parent set to the previous parent
-        equivalent to:
+           equivalent to:
                 clutter_actor_unparent
                 clutter_actor_set_parent
+        3. updates state of the actor to match invariants
+           (may change MAPPED or REALIZED in either direction,
+           depending on state of the new parent)
 
 
  iv. Responsibilities of a ClutterActor
@@ -173,24 +270,27 @@ clutter_actor_reparent:
 When adding an actor to a container, the container must:
 
  1. call clutter_actor_set_parent (actor, container)
- 2. call clutter_actor_queue_relayout (container)
+ 2. call clutter_actor_queue_relayout (container) if
+    adding the actor changes the container's preferred
+    size
 
  b. Removing from a container
 
 When removing an actor from a container, the container must:
 
  1. call clutter_actor_unparent (actor)
- 2. call clutter_actor_queue_relayout (container)
+ 2. call clutter_actor_queue_relayout (container) if removing
+    the actor changes the container's preferred size
 
 Notes:
 
 * here a container actor is any actor that contains children actors; it
   does not imply the implementation of the ClutterContainer interface.
 
-* clutter_actor_unparent() will hide the actor except in the special case
-  when CLUTTER_ACTOR_IN_REPARENT is set.
+* clutter_actor_unparent() will unmap and unrealize the actor except
+  in the special case when CLUTTER_ACTOR_IN_REPARENT is set.
 
-* 'Composite' Clutter actors need to pass down any allocations to children. 
+* 'Composite' Clutter actors need to pass down any allocations to children.
 
  c. Initial state
 
diff --git a/tests/conform/test-actor-invariants.c b/tests/conform/test-actor-invariants.c
index 315abeed6..77eea9ad2 100644
--- a/tests/conform/test-actor-invariants.c
+++ b/tests/conform/test-actor-invariants.c
@@ -21,15 +21,38 @@ test_initial_state (TestConformSimpleFixture *fixture,
 }
 
 void
-test_realized (TestConformSimpleFixture *fixture,
-               gconstpointer             data)
+test_shown_not_parented (TestConformSimpleFixture *fixture,
+                         gconstpointer             data)
 {
   ClutterActor *actor;
 
   actor = clutter_rectangle_new ();
 
+  clutter_actor_show (actor);
+
+  g_assert (!CLUTTER_ACTOR_IS_REALIZED (actor));
+  g_assert (!CLUTTER_ACTOR_IS_MAPPED (actor));
+  g_assert (CLUTTER_ACTOR_IS_VISIBLE (actor));
+
+  clutter_actor_destroy (actor);
+}
+
+void
+test_realized (TestConformSimpleFixture *fixture,
+               gconstpointer             data)
+{
+  ClutterActor *actor;
+  ClutterActor *stage;
+
+  stage = clutter_stage_get_default ();
+
+  actor = clutter_rectangle_new ();
+
   g_assert (!(CLUTTER_ACTOR_IS_REALIZED (actor)));
 
+  clutter_actor_hide (actor); /* don't show, so won't map */
+  clutter_container_add_actor (CLUTTER_CONTAINER (stage),
+                               actor);
   clutter_actor_realize (actor);
 
   g_assert (CLUTTER_ACTOR_IS_REALIZED (actor));
@@ -45,33 +68,130 @@ test_mapped (TestConformSimpleFixture *fixture,
              gconstpointer             data)
 {
   ClutterActor *actor;
+  ClutterActor *stage;
+
+  stage = clutter_stage_get_default ();
 
   actor = clutter_rectangle_new ();
 
   g_assert (!(CLUTTER_ACTOR_IS_REALIZED (actor)));
   g_assert (!(CLUTTER_ACTOR_IS_MAPPED (actor)));
 
-  clutter_actor_show (actor);
+  clutter_container_add_actor (CLUTTER_CONTAINER (stage),
+                               actor);
 
   g_assert (CLUTTER_ACTOR_IS_REALIZED (actor));
   g_assert (CLUTTER_ACTOR_IS_MAPPED (actor));
-
   g_assert (CLUTTER_ACTOR_IS_VISIBLE (actor));
 
   clutter_actor_destroy (actor);
 }
 
+void
+test_realize_not_recursive (TestConformSimpleFixture *fixture,
+                            gconstpointer             data)
+{
+  ClutterActor *actor, *group;
+  ClutterActor *stage;
+
+  stage = clutter_stage_get_default ();
+
+  group = clutter_group_new ();
+
+  actor = clutter_rectangle_new ();
+
+  clutter_actor_hide (group); /* don't show, so won't map */
+  clutter_actor_hide (actor); /* don't show, so won't map */
+
+  g_assert (!(CLUTTER_ACTOR_IS_REALIZED (group)));
+  g_assert (!(CLUTTER_ACTOR_IS_REALIZED (actor)));
+
+  clutter_container_add_actor (CLUTTER_CONTAINER (stage),
+                               group);
+  clutter_container_add_actor (CLUTTER_CONTAINER (group),
+                               actor);
+
+  clutter_actor_realize (group);
+
+  g_assert (CLUTTER_ACTOR_IS_REALIZED (group));
+
+  g_assert (!(CLUTTER_ACTOR_IS_MAPPED (group)));
+  g_assert (!(CLUTTER_ACTOR_IS_VISIBLE (group)));
+
+  /* realizing group did not realize the child */
+  g_assert (!CLUTTER_ACTOR_IS_REALIZED (actor));
+  g_assert (!(CLUTTER_ACTOR_IS_MAPPED (actor)));
+  g_assert (!(CLUTTER_ACTOR_IS_VISIBLE (actor)));
+
+  clutter_actor_destroy (group);
+}
+
+void
+test_map_recursive (TestConformSimpleFixture *fixture,
+                    gconstpointer             data)
+{
+  ClutterActor *actor, *group;
+  ClutterActor *stage;
+
+  stage = clutter_stage_get_default ();
+
+  group = clutter_group_new ();
+
+  actor = clutter_rectangle_new ();
+
+  clutter_actor_hide (group); /* hide at first */
+  clutter_actor_show (actor); /* show at first */
+
+  g_assert (!(CLUTTER_ACTOR_IS_REALIZED (group)));
+  g_assert (!(CLUTTER_ACTOR_IS_REALIZED (actor)));
+  g_assert (!(CLUTTER_ACTOR_IS_MAPPED (group)));
+  g_assert (!(CLUTTER_ACTOR_IS_MAPPED (actor)));
+  g_assert (!(CLUTTER_ACTOR_IS_VISIBLE (group)));
+  g_assert ((CLUTTER_ACTOR_IS_VISIBLE (actor)));
+
+  clutter_container_add_actor (CLUTTER_CONTAINER (stage),
+                               group);
+  clutter_container_add_actor (CLUTTER_CONTAINER (group),
+                               actor);
+
+  g_assert (!(CLUTTER_ACTOR_IS_REALIZED (group)));
+  g_assert (!(CLUTTER_ACTOR_IS_REALIZED (actor)));
+  g_assert (!(CLUTTER_ACTOR_IS_MAPPED (group)));
+  g_assert (!(CLUTTER_ACTOR_IS_MAPPED (actor)));
+  g_assert (!(CLUTTER_ACTOR_IS_VISIBLE (group)));
+  g_assert ((CLUTTER_ACTOR_IS_VISIBLE (actor)));
+
+  /* show group, which should map and realize both
+   * group and child.
+   */
+  clutter_actor_show (group);
+  g_assert (CLUTTER_ACTOR_IS_REALIZED (group));
+  g_assert (CLUTTER_ACTOR_IS_REALIZED (actor));
+  g_assert (CLUTTER_ACTOR_IS_MAPPED (group));
+  g_assert (CLUTTER_ACTOR_IS_MAPPED (actor));
+  g_assert (CLUTTER_ACTOR_IS_VISIBLE (group));
+  g_assert (CLUTTER_ACTOR_IS_VISIBLE (actor));
+
+  clutter_actor_destroy (group);
+}
+
 void
 test_show_on_set_parent (TestConformSimpleFixture *fixture,
                          gconstpointer             data)
 {
   ClutterActor *actor, *group;
   gboolean show_on_set_parent;
+  ClutterActor *stage;
+
+  stage = clutter_stage_get_default ();
 
   group = clutter_group_new ();
 
   g_assert (!(CLUTTER_ACTOR_IS_VISIBLE (group)));
 
+  clutter_container_add_actor (CLUTTER_CONTAINER (stage),
+                               group);
+
   actor = clutter_rectangle_new ();
   g_object_get (G_OBJECT (actor),
                 "show-on-set-parent", &show_on_set_parent,
@@ -94,8 +214,9 @@ test_show_on_set_parent (TestConformSimpleFixture *fixture,
                 "show-on-set-parent", &show_on_set_parent,
                 NULL);
 
-  g_assert (CLUTTER_ACTOR_IS_REALIZED (actor));
-  g_assert (!(CLUTTER_ACTOR_IS_VISIBLE (actor)));
+  g_assert (!CLUTTER_ACTOR_IS_REALIZED (actor));
+  g_assert (!CLUTTER_ACTOR_IS_MAPPED (actor));
+  g_assert (CLUTTER_ACTOR_IS_VISIBLE (actor));
   g_assert (show_on_set_parent == TRUE);
 
   clutter_actor_destroy (actor);
diff --git a/tests/conform/test-conform-main.c b/tests/conform/test-conform-main.c
index d4ea35dfc..8a75b8868 100644
--- a/tests/conform/test-conform-main.c
+++ b/tests/conform/test-conform-main.c
@@ -116,7 +116,10 @@ main (int argc, char **argv)
   TEST_CONFORM_SIMPLE ("/fixed", test_fixed_constants);
 
   TEST_CONFORM_SIMPLE ("/invariants", test_initial_state);
+  TEST_CONFORM_SIMPLE ("/invariants", test_shown_not_parented);
   TEST_CONFORM_SIMPLE ("/invariants", test_realized);
+  TEST_CONFORM_SIMPLE ("/invariants", test_realize_not_recursive);
+  TEST_CONFORM_SIMPLE ("/invariants", test_map_recursive);
   TEST_CONFORM_SIMPLE ("/invariants", test_mapped);
   TEST_CONFORM_SIMPLE ("/invariants", test_show_on_set_parent);