[clutter/wip/stereo: 6/6] Adds initial support for stereoscopic rendering



commit 7fd5cd23cc9359c74e09b93df01c9dcef88dc2c6
Author: Robert Bragg <robert linux intel com>
Date:   Mon Aug 1 01:11:57 2011 +0100

    Adds initial support for stereoscopic rendering
    
    This adds initial support for stereoscopic rendering of Clutter scenes.
    The major change here is that we've introduce the internal concept of a
    ClutterCamera which encapsulates a viewport, projection and view
    transform. In the future the camera will also encapsulate a reference to
    the destination framebuffer, but for now it's assumed that the backend
    framebuffer behind stage->priv->impl is associated with all cameras.
    
    If clutter_stage_set_stereo_enabled (stage, TRUE) is called then Clutter
    will now enable stereographic rendering, using a simple toe-in approach
    of modifying the view transform for each eye to model that there is a
    gap between the eyes and both eyes look towards the center of the z=0
    plane (This is the plane where most 2d content for a clutter application
    goes)
    
    A notable disadvantage to the simple toe-in approach is that 2D content
    on the z=0 plane will look different for each eye so straight lines
    for example wont appear straight to the user. A better approach we will
    implement later is to setup asymetric projections for each eye that
    can model the eye gap but the frustums cross at the z=0 plane such that
    both eyes would have the same view of 2D content.
    
    Currently there are three modes of output for stereoscopic content.
     Â There is anaglyph rendering (for use with filter glasses with a red
       filter for the left eye and cyan filter for the right)
     Â There is a vertical split mode which splits the stage and shows the
       left eye content on the left and the right eye on the right.
     Â There is a horizontal split mode which splits the stage and shows
       the left eye content on the top and right eye on the bottom.
    
    The mode can be selected using clutter_stage_set_stereo_mode().
    
    The mode can also be explicitly overridden using the CLUTTER_STEREO_MODE
    environment variable by setting it to "default", "anaglyph",
    "vertical-split" or "horizontal-split". Setting this environment
    variable also implicitly forces stereo rendering to be enabled.

 clutter/clutter-actor-private.h          |    3 +-
 clutter/clutter-actor.c                  |  691 ++++++++++++++++------
 clutter/clutter-backend.c                |    4 +-
 clutter/clutter-offscreen-effect.c       |  299 +++++++---
 clutter/clutter-paint-volume-private.h   |    7 +-
 clutter/clutter-paint-volume.c           |   55 ++-
 clutter/clutter-private.h                |   26 +
 clutter/clutter-stage-private.h          |   14 +-
 clutter/clutter-stage-window.c           |   39 ++
 clutter/clutter-stage-window.h           |   15 +
 clutter/clutter-stage.c                  |  934 ++++++++++++++++++++++--------
 clutter/clutter-stage.h                  |   29 +
 clutter/clutter-texture.c                |   54 ++-
 clutter/cogl/clutter-stage-cogl.c        |  233 +++++---
 tests/conform/test-cogl-path.c           |    1 +
 tests/conform/test-texture-fbo.c         |    1 +
 tests/interactive/test-actors.c          |   11 +-
 tests/interactive/test-behave.c          |   41 +-
 tests/interactive/test-cairo-flowers.c   |    2 +
 tests/interactive/test-clip.c            |    1 +
 tests/interactive/test-depth.c           |    2 +
 tests/interactive/test-easing.c          |    9 +-
 tests/interactive/test-shader-effects.c  |    3 +-
 tests/interactive/test-texture-quality.c |    7 +-
 24 files changed, 1818 insertions(+), 663 deletions(-)
---
diff --git a/clutter/clutter-actor-private.h b/clutter/clutter-actor-private.h
index 3af853f..abf00d2 100644
--- a/clutter/clutter-actor-private.h
+++ b/clutter/clutter-actor-private.h
@@ -153,8 +153,7 @@ void _clutter_actor_queue_redraw_full        (ClutterActor              *self,
                                               ClutterEffect             *effect);
 
 ClutterPaintVolume *_clutter_actor_get_queue_redraw_clip (ClutterActor *self);
-void _clutter_actor_set_queue_redraw_clip     (ClutterActor             *self,
-                                               ClutterPaintVolume *clip_volume);
+int _clutter_actor_get_queue_redraw_camera_index (ClutterActor *self);
 void _clutter_actor_finish_queue_redraw       (ClutterActor             *self,
                                                ClutterPaintVolume       *clip);
 
diff --git a/clutter/clutter-actor.c b/clutter/clutter-actor.c
index 732c5f4..50df1d0 100644
--- a/clutter/clutter-actor.c
+++ b/clutter/clutter-actor.c
@@ -366,6 +366,21 @@ typedef enum {
                               */
 } MapStateChange;
 
+typedef struct
+{
+  const ClutterCamera *camera;
+
+  /* NB: This volume isn't relative to this actor, it is in eye
+   * coordinates so that it can remain valid after the actor changes.
+   */
+  ClutterPaintVolume paint_volume;
+  guint paint_volume_valid: 1;
+
+  /* If this doesn't match camera->age then the above paint-volume
+   * is invalid. */
+  int valid_for_age;
+} PerCameraState;
+
 struct _ClutterActorPrivate
 {
   /* fixed_x, fixed_y, and the allocation box are all in parent
@@ -393,6 +408,12 @@ struct _ClutterActorPrivate
   ClutterActorBox allocation;
   ClutterAllocationFlags allocation_flags;
 
+  /* State we cache that's specific to a camera view. We only currently
+   * consider their may be two cameras for stereo rendering. */
+  PerCameraState *camera_state;
+  int n_cameras;
+  int cameras_age;
+
   guint position_set                : 1;
   guint min_width_set               : 1;
   guint min_height_set              : 1;
@@ -412,7 +433,6 @@ struct _ClutterActorPrivate
   guint has_pointer                 : 1;
   guint propagated_one_redraw       : 1;
   guint paint_volume_valid          : 1;
-  guint last_paint_volume_valid     : 1;
   guint in_clone_paint              : 1;
   guint transform_valid             : 1;
   /* This is TRUE if anything has queued a redraw since we were last
@@ -454,6 +474,7 @@ struct _ClutterActorPrivate
      offscreen-redirect property */
   ClutterEffect  *flatten_effect;
 
+  ClutterStage   *stage_cache;
   ClutterActor   *parent_actor;
   GList          *children;
   gint            n_children;
@@ -473,11 +494,12 @@ 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.
+  /* XXX: These are a workaround for not being able to break the ABI
+   * of the QUEUE_REDRAW signal. They are out-of-band arguments.
    * See clutter_actor_queue_clipped_redraw() for details.
    */
   ClutterPaintVolume *oob_queue_redraw_clip;
+  int oob_queue_redraw_camera_index;
 
   ClutterMetaGroup *actions;
   ClutterMetaGroup *constraints;
@@ -502,11 +524,6 @@ struct _ClutterActorPrivate
      the list of effects that is next in the chain */
   const GList *next_effect_to_paint;
 
-  /* NB: This volume isn't relative to this actor, it is in eye
-   * coordinates so that it can remain valid after the actor changes.
-   */
-  ClutterPaintVolume last_paint_volume;
-
   ClutterStageQueueRedrawEntry *queue_redraw_entry;
 };
 
@@ -1067,11 +1084,39 @@ clutter_actor_update_map_state (ClutterActor  *self,
 #endif
 }
 
+static ClutterStage *
+_clutter_actor_get_stage_real (ClutterActor *actor)
+{
+  ClutterActor *self;
+
+  if (G_LIKELY (actor->priv->stage_cache))
+    return actor->priv->stage_cache;
+
+  self = actor;
+
+  /* Check to see if the actor is associated with a stage yet... */
+  while (actor && !CLUTTER_ACTOR_IS_TOPLEVEL (actor))
+    actor = actor->priv->parent_actor;
+
+  /* Note: we never want to have a type check when we cast here
+   * since this is function can be used very heavily. */
+  self->priv->stage_cache = (ClutterStage *)actor;
+  return self->priv->stage_cache;
+}
+
+ClutterActor *
+_clutter_actor_get_stage_internal (ClutterActor *actor)
+{
+  /* Note: we never want to have a type check when we cast here
+   * since this is function can be used very heavily. */
+  return (ClutterActor *)_clutter_actor_get_stage_real (actor);
+}
+
 static void
 clutter_actor_real_map (ClutterActor *self)
 {
+  ClutterStage *stage = _clutter_actor_get_stage_real (self);
   ClutterActorPrivate *priv = self->priv;
-  ClutterActor *stage;
   GList *c;
 
   g_assert (!CLUTTER_ACTOR_IS_MAPPED (self));
@@ -1081,8 +1126,7 @@ clutter_actor_real_map (ClutterActor *self)
 
   CLUTTER_ACTOR_SET_FLAGS (self, CLUTTER_ACTOR_MAPPED);
 
-  stage = _clutter_actor_get_stage_internal (self);
-  priv->pick_id = _clutter_stage_acquire_pick_id (CLUTTER_STAGE (stage), self);
+  priv->pick_id = _clutter_stage_acquire_pick_id (stage, self);
   CLUTTER_NOTE (ACTOR, "Pick id '%d' for actor '%s'",
                 priv->pick_id,
                 _clutter_actor_get_debug_name (self));
@@ -1137,6 +1181,7 @@ clutter_actor_real_unmap (ClutterActor *self)
 {
   ClutterActorPrivate *priv = self->priv;
   GList *c;
+  int i;
 
   g_assert (CLUTTER_ACTOR_IS_MAPPED (self));
 
@@ -1151,11 +1196,31 @@ clutter_actor_real_unmap (ClutterActor *self)
 
   CLUTTER_ACTOR_UNSET_FLAGS (self, CLUTTER_ACTOR_MAPPED);
 
-  /* clear the contents of the last paint volume, so that hiding + moving +
-   * showing will not result in the wrong area being repainted
+  /* clear the contents of the - per camera - paint volumes, so that
+   * hiding + moving + showing will not result in the old visible area
+   * being needlessly repainted.
+   *
+   * XXX: I'm not sure this is right. We only call
+   * clutter_actor_queue_redraw in clutter_actor_hide which only adds
+   * a record of the queue_redraw request to the stage. Later when
+   * finishing the queue_redraw requests associated with the stage
+   * surely we will see that actor has a valid, but empty volume and
+   * so we won't redraw the old area of the actorâ
    */
-  _clutter_paint_volume_init_static (&priv->last_paint_volume, NULL);
-  priv->last_paint_volume_valid = TRUE;
+  for (i = 0; i < priv->n_cameras; i++)
+    {
+      PerCameraState *camera_state = &priv->camera_state[i];
+
+      /* FIXME: we should never allow modification of a cached paint-volume
+       * if we know we have a queue_redraw entry associated with the
+       * stage because it might be required during
+       * _clutter_actor_finish_queue_redraw().
+       */
+      if (!camera_state->paint_volume_valid)
+        clutter_paint_volume_free (&camera_state->paint_volume);
+      _clutter_paint_volume_init_static (&camera_state->paint_volume, NULL);
+      camera_state->paint_volume_valid = TRUE;
+    }
 
   /* notify on parent mapped after potentially unmapping
    * children, so apps see a bottom-up notification.
@@ -1165,10 +1230,7 @@ clutter_actor_real_unmap (ClutterActor *self)
   /* relinquish keyboard focus if we were unmapped while owning it */
   if (!CLUTTER_ACTOR_IS_TOPLEVEL (self))
     {
-      ClutterStage *stage;
-
-      stage = CLUTTER_STAGE (_clutter_actor_get_stage_internal (self));
-
+      ClutterStage *stage = _clutter_actor_get_stage_real (self);
       if (stage != NULL)
         _clutter_stage_release_pick_id (stage, priv->pick_id);
 
@@ -1307,6 +1369,16 @@ clutter_actor_show (ClutterActor *self)
   g_signal_emit (self, actor_signals[SHOW], 0);
   g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_VISIBLE]);
 
+  /* XXX: shouldn't this be:
+   * if (_clutter_actor_get_stage_real (self))
+   *   clutter_actor_queue_redraw (self);
+   *
+   * XXX: actually shouldn't we queue redraws from map/unmap changes
+   * instead since there's no point queueing a redraw for an actor if
+   * one of its ancestors is unmapped. The concern here could be that
+   * the queue_redraw might bail out if the actor isn't visible and
+   * mapped.
+   */
   if (priv->parent_actor)
     clutter_actor_queue_redraw (priv->parent_actor);
 
@@ -1400,6 +1472,16 @@ clutter_actor_hide (ClutterActor *self)
   g_signal_emit (self, actor_signals[HIDE], 0);
   g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_VISIBLE]);
 
+  /* XXX: shouldn't this be:
+   * if (_clutter_actor_get_stage_real (self))
+   *   clutter_actor_queue_redraw (self);
+   *
+   * XXX: actually shouldn't we queue redraws from map/unmap changes
+   * instead since there's no point queueing a redraw for an actor if
+   * one of its ancestors is unmapped. The concern here could be that
+   * the queue_redraw might bail out if the actor isn't visible and
+   * mapped.
+   */
   if (priv->parent_actor)
     clutter_actor_queue_redraw (priv->parent_actor);
 
@@ -1941,9 +2023,8 @@ clutter_actor_real_queue_redraw (ClutterActor *self,
    */
   if (self->priv->propagated_one_redraw)
     {
-      ClutterActor *stage = _clutter_actor_get_stage_internal (self);
-      if (stage != NULL &&
-          _clutter_stage_has_full_redraw_queued (CLUTTER_STAGE (stage)))
+      ClutterStage *stage = _clutter_actor_get_stage_real (self);
+      if (stage != NULL && _clutter_stage_has_full_redraw_queued (stage))
         return;
     }
 
@@ -2021,7 +2102,7 @@ clutter_actor_apply_relative_transform_to_point (ClutterActor        *self,
   w = 1.0;
 
   if (ancestor == NULL)
-    ancestor = _clutter_actor_get_stage_internal (self);
+    ancestor = CLUTTER_ACTOR (_clutter_actor_get_stage_real (self));
 
   if (ancestor == NULL)
     {
@@ -2035,40 +2116,32 @@ clutter_actor_apply_relative_transform_to_point (ClutterActor        *self,
 
 static gboolean
 _clutter_actor_fully_transform_vertices (ClutterActor *self,
+                                         int camera_index,
                                          const ClutterVertex *vertices_in,
                                          ClutterVertex *vertices_out,
                                          int n_vertices)
 {
   ClutterActor *stage;
+  const ClutterCamera *camera;
   CoglMatrix modelview;
-  CoglMatrix projection;
-  float viewport[4];
 
   g_return_val_if_fail (CLUTTER_IS_ACTOR (self), FALSE);
 
-  stage = _clutter_actor_get_stage_internal (self);
-
   /* We really can't do anything meaningful in this case so don't try
    * to do any transform */
+  stage = CLUTTER_ACTOR (_clutter_actor_get_stage_real (self));
   if (stage == NULL)
     return FALSE;
 
-  /* Note: we pass NULL as the ancestor because we don't just want the modelview
-   * that gets us to stage coordinates, we want to go all the way to eye
-   * coordinates */
-  _clutter_actor_apply_relative_transformation_matrix (self, NULL, &modelview);
+  camera = _clutter_stage_get_camera (CLUTTER_STAGE (stage), camera_index);
+  cogl_matrix_init_from_array (&modelview, (float *)&camera->view);
 
-  /* Fetch the projection and viewport */
-  _clutter_stage_get_projection_matrix (CLUTTER_STAGE (stage), &projection);
-  _clutter_stage_get_viewport (CLUTTER_STAGE (stage),
-                               &viewport[0],
-                               &viewport[1],
-                               &viewport[2],
-                               &viewport[3]);
+  _clutter_actor_apply_relative_transformation_matrix (self, stage, &modelview);
 
+  /* Fetch the projection and viewport */
   _clutter_util_fully_transform_vertices (&modelview,
-                                          &projection,
-                                          viewport,
+                                          &camera->projection,
+                                          camera->viewport,
                                           vertices_in,
                                           vertices_out,
                                           n_vertices);
@@ -2086,6 +2159,9 @@ _clutter_actor_fully_transform_vertices (ClutterActor *self,
  * into screen-relative coordinates with the current actor
  * transformation (i.e. scale, rotation, etc)
  *
+ * <note>If clutter is being used for stereo rendering then this will
+ * simply transform the point according the left eye's view</note>
+ *
  * Since: 0.4
  **/
 void
@@ -2095,14 +2171,13 @@ clutter_actor_apply_transform_to_point (ClutterActor        *self,
 {
   g_return_if_fail (point != NULL);
   g_return_if_fail (vertex != NULL);
-  _clutter_actor_fully_transform_vertices (self, point, vertex, 1);
+  _clutter_actor_fully_transform_vertices (self, 0, point, vertex, 1);
 }
 
 /*
  * _clutter_actor_get_relative_transformation_matrix:
  * @self: The actor whose coordinate space you want to transform from.
- * @ancestor: The ancestor actor whose coordinate space you want to transform too
- *            or %NULL if you want to transform all the way to eye coordinates.
+ * @ancestor: The ancestor actor whose coordinate space you want to transform too.
  * @matrix: A #CoglMatrix to store the transformation
  *
  * This gets a transformation @matrix that will transform coordinates from the
@@ -2112,13 +2187,6 @@ clutter_actor_apply_transform_to_point (ClutterActor        *self,
  * coordinates of @self into stage coordinates you would pass the actor's stage
  * pointer as the @ancestor.
  *
- * If you pass %NULL then the transformation will take you all the way through
- * to eye coordinates. This can be useful if you want to extract the entire
- * modelview transform that Clutter applies before applying the projection
- * transformation. If you want to explicitly set a modelview on a CoglFramebuffer
- * using cogl_set_modelview_matrix() for example then you would want a matrix
- * that transforms into eye coordinates.
- *
  * <note><para>This function explicitly initializes the given @matrix. If you just
  * want clutter to multiply a relative transformation with an existing matrix
  * you can use clutter_actor_apply_relative_transformation_matrix()
@@ -2132,6 +2200,8 @@ _clutter_actor_get_relative_transformation_matrix (ClutterActor *self,
                                                    ClutterActor *ancestor,
                                                    CoglMatrix *matrix)
 {
+  g_return_if_fail (ancestor != NULL);
+
   cogl_matrix_init_identity (matrix);
 
   _clutter_actor_apply_relative_transformation_matrix (self, ancestor, matrix);
@@ -2141,6 +2211,7 @@ _clutter_actor_get_relative_transformation_matrix (ClutterActor *self,
  * transformed vertices to @verts[]. */
 static gboolean
 _clutter_actor_transform_and_project_box (ClutterActor          *self,
+                                          int                    camera_index,
 					  const ClutterActorBox *box,
 					  ClutterVertex          verts[])
 {
@@ -2160,7 +2231,8 @@ _clutter_actor_transform_and_project_box (ClutterActor          *self,
   box_vertices[3].z = 0;
 
   return
-    _clutter_actor_fully_transform_vertices (self, box_vertices, verts, 4);
+    _clutter_actor_fully_transform_vertices (self, camera_index,
+                                             box_vertices, verts, 4);
 }
 
 /**
@@ -2197,25 +2269,26 @@ clutter_actor_get_allocation_vertices (ClutterActor  *self,
   ClutterActorBox box;
   ClutterVertex vertices[4];
   CoglMatrix modelview;
+  ClutterStage *stage;
 
   g_return_if_fail (CLUTTER_IS_ACTOR (self));
   g_return_if_fail (ancestor == NULL || CLUTTER_IS_ACTOR (ancestor));
 
+  priv = self->priv;
+  stage = _clutter_actor_get_stage_real (self);
+
   if (ancestor == NULL)
-    ancestor = _clutter_actor_get_stage_internal (self);
+    ancestor = CLUTTER_ACTOR (stage);
 
   /* Fallback to a NOP transform if the actor isn't parented under a
    * stage. */
   if (ancestor == NULL)
     ancestor = self;
 
-  priv = self->priv;
-
   /* if the actor needs to be allocated we force a relayout, so that
    * we will have valid values to use in the transformations */
   if (priv->needs_allocation)
     {
-      ClutterActor *stage = _clutter_actor_get_stage_internal (self);
       if (stage)
         _clutter_stage_maybe_relayout (stage);
       else
@@ -2270,6 +2343,9 @@ clutter_actor_get_allocation_vertices (ClutterActor  *self,
  *   <listitem><para>v[3] contains (x2, y2)</para></listitem>
  * </itemizedlist>
  *
+ * <note>If clutter is being used for stereo rendering then this will
+ * simply return a box according the left eye's view.</note>
+ *
  * Since: 0.4
  */
 void
@@ -2289,7 +2365,7 @@ clutter_actor_get_abs_allocation_vertices (ClutterActor  *self,
    */
   if (priv->needs_allocation)
     {
-      ClutterActor *stage = _clutter_actor_get_stage_internal (self);
+      ClutterStage *stage = _clutter_actor_get_stage_real (self);
       /* There's nothing meaningful we can do now */
       if (!stage)
         return;
@@ -2304,6 +2380,7 @@ clutter_actor_get_abs_allocation_vertices (ClutterActor  *self,
   actor_space_allocation.x2 = priv->allocation.x2 - priv->allocation.x1;
   actor_space_allocation.y2 = priv->allocation.y2 - priv->allocation.y1;
   _clutter_actor_transform_and_project_box (self,
+                                            0,
 					    &actor_space_allocation,
 					    verts);
 }
@@ -2392,8 +2469,7 @@ _clutter_actor_apply_modelview_transform (ClutterActor *self,
 /*
  * clutter_actor_apply_relative_transformation_matrix:
  * @self: The actor whose coordinate space you want to transform from.
- * @ancestor: The ancestor actor whose coordinate space you want to transform too
- *            or %NULL if you want to transform all the way to eye coordinates.
+ * @ancestor: The ancestor actor whose coordinate space you want to transform too.
  * @matrix: A #CoglMatrix to apply the transformation too.
  *
  * This multiplies a transform with @matrix that will transform coordinates
@@ -2403,13 +2479,6 @@ _clutter_actor_apply_modelview_transform (ClutterActor *self,
  * coordinates of @self into stage coordinates you would pass the actor's stage
  * pointer as the @ancestor.
  *
- * If you pass %NULL then the transformation will take you all the way through
- * to eye coordinates. This can be useful if you want to extract the entire
- * modelview transform that Clutter applies before applying the projection
- * transformation. If you want to explicitly set a modelview on a CoglFramebuffer
- * using cogl_set_modelview_matrix() for example then you would want a matrix
- * that transforms into eye coordinates.
- *
  * <note>This function doesn't initialize the given @matrix, it simply
  * multiplies the requested transformation matrix with the existing contents of
  * @matrix. You can use cogl_matrix_init_identity() to initialize the @matrix
@@ -2423,6 +2492,8 @@ _clutter_actor_apply_relative_transformation_matrix (ClutterActor *self,
 {
   ClutterActor *parent;
 
+  g_return_if_fail (ancestor != NULL);
+
   /* Note we terminate before ever calling stage->apply_transform()
    * since that would conceptually be relative to the underlying
    * window OpenGL coordinates so we'd need a special @ancestor
@@ -2511,9 +2582,9 @@ _clutter_actor_draw_paint_volume (ClutterActor *self)
     {
       gfloat width, height;
       ClutterPaintVolume fake_pv;
+      ClutterStage *stage = _clutter_actor_get_stage_real (self);
 
-      ClutterActor *stage = _clutter_actor_get_stage_internal (self);
-      _clutter_paint_volume_init_static (&fake_pv, stage);
+      _clutter_paint_volume_init_static (&fake_pv, CLUTTER_ACTOR (stage));
 
       clutter_actor_get_size (self, &width, &height);
       clutter_paint_volume_set_width (&fake_pv, width);
@@ -2537,6 +2608,7 @@ _clutter_actor_draw_paint_volume (ClutterActor *self)
 
 static void
 _clutter_actor_paint_cull_result (ClutterActor *self,
+                                  const ClutterCamera *camera,
                                   gboolean success,
                                   ClutterCullResult result)
 {
@@ -2608,32 +2680,36 @@ static gboolean
 cull_actor (ClutterActor *self, ClutterCullResult *result_out)
 {
   ClutterActorPrivate *priv = self->priv;
-  ClutterActor *stage;
   const ClutterPlane *stage_clip;
+  const ClutterCamera *camera;
+  PerCameraState *camera_state;
+  ClutterStage *stage = _clutter_actor_get_stage_real (self);
 
-  if (!priv->last_paint_volume_valid)
+  if (G_UNLIKELY (clutter_paint_debug_flags & CLUTTER_DEBUG_DISABLE_CULLING))
+    return FALSE;
+
+  stage_clip = _clutter_stage_get_clip (stage);
+  if (G_UNLIKELY (!stage_clip))
     {
       CLUTTER_NOTE (CLIPPING, "Bail from cull_actor without culling (%s): "
-                    "->last_paint_volume_valid == FALSE",
+                    "No stage clip set",
                     _clutter_actor_get_debug_name (self));
       return FALSE;
     }
 
-  if (G_UNLIKELY (clutter_paint_debug_flags & CLUTTER_DEBUG_DISABLE_CULLING))
-    return FALSE;
+  camera = _clutter_stage_get_current_camera (stage);
+  camera_state = &priv->camera_state[camera->index];
 
-  stage = _clutter_actor_get_stage_internal (self);
-  stage_clip = _clutter_stage_get_clip (CLUTTER_STAGE (stage));
-  if (G_UNLIKELY (!stage_clip))
+  if (!camera_state->paint_volume_valid)
     {
       CLUTTER_NOTE (CLIPPING, "Bail from cull_actor without culling (%s): "
-                    "No stage clip set",
+                    "->paint_volume_valid == FALSE",
                     _clutter_actor_get_debug_name (self));
       return FALSE;
     }
 
   if (cogl_get_draw_framebuffer () !=
-      _clutter_stage_get_active_framebuffer (CLUTTER_STAGE (stage)))
+      _clutter_stage_get_active_framebuffer (stage))
     {
       CLUTTER_NOTE (CLIPPING, "Bail from cull_actor without culling (%s): "
                     "Current framebuffer doesn't correspond to stage",
@@ -2642,37 +2718,106 @@ cull_actor (ClutterActor *self, ClutterCullResult *result_out)
     }
 
   *result_out =
-    _clutter_paint_volume_cull (&priv->last_paint_volume, stage_clip);
+    _clutter_paint_volume_cull (&camera_state->paint_volume, stage_clip);
   return TRUE;
 }
 
 static void
-_clutter_actor_update_last_paint_volume (ClutterActor *self)
+invalidate_per_camera_paint_volume (PerCameraState *camera_state)
+{
+  if (camera_state->paint_volume_valid)
+    {
+      clutter_paint_volume_free (&camera_state->paint_volume);
+      camera_state->paint_volume_valid = FALSE;
+    }
+}
+
+static PerCameraState *
+_clutter_actor_get_per_camera_state (ClutterActor *self,
+                                     int camera_index)
 {
   ClutterActorPrivate *priv = self->priv;
+  ClutterStage *stage = _clutter_actor_get_stage_real (self);
+  int cameras_age = _clutter_stage_get_cameras_age (stage);
+  PerCameraState *camera_state;
+
+  /* Whenever there are additions or removals of cameras associated with the
+   * stage then the stage's 'cameras_age' is bumped and we throw away any
+   * per-actor cached state associated with the old cameras. */
+
+  if (G_UNLIKELY (cameras_age != priv->cameras_age))
+    {
+      int i;
+      int n_cameras;
+
+      for (i = 0; i < priv->n_cameras; i++)
+        invalidate_per_camera_paint_volume (&priv->camera_state[i]);
+
+      if (priv->camera_state)
+        g_slice_free1 (sizeof (PerCameraState) * priv->n_cameras,
+                       priv->camera_state);
+
+      /* NB: We always allocate for the total number of cameras since
+       * we expect that each camera is likely going to be painted each
+       * frame so we should save having to re-allocate later. */
+      n_cameras = _clutter_stage_get_n_cameras (stage);
+      priv->camera_state = g_slice_alloc (sizeof (PerCameraState) * n_cameras);
+
+      for (i = 0; i < n_cameras; i++)
+        {
+          camera_state = &priv->camera_state[i];
+
+          camera_state->camera = _clutter_stage_get_camera (stage, i);
+          camera_state->paint_volume_valid = FALSE;
+          camera_state->valid_for_age = camera_state->camera->age;
+        }
+
+      priv->n_cameras = n_cameras;
+      priv->cameras_age = cameras_age;
+    }
+
+  camera_state = &priv->camera_state[camera_index];
+  if (camera_state->camera->age != camera_state->valid_for_age)
+    {
+      invalidate_per_camera_paint_volume (camera_state);
+      camera_state->valid_for_age = camera_state->camera->age;
+    }
+
+  return camera_state;
+}
+
+/* NB: This updates the paint volume for the current camera and it's assumed that
+ * this is only used during painting where the current camera is meaningful. */
+static void
+_clutter_actor_update_paint_volume (ClutterActor *self)
+{
   const ClutterPaintVolume *pv;
+  ClutterStage *stage = _clutter_actor_get_stage_real (self);
+  const ClutterCamera *camera = _clutter_stage_get_current_camera (stage);
+  PerCameraState *camera_state =
+    _clutter_actor_get_per_camera_state (self, camera->index);
 
-  if (priv->last_paint_volume_valid)
+  if (camera_state->paint_volume_valid)
     {
-      clutter_paint_volume_free (&priv->last_paint_volume);
-      priv->last_paint_volume_valid = FALSE;
+      clutter_paint_volume_free (&camera_state->paint_volume);
+      camera_state->paint_volume_valid = FALSE;
     }
 
   pv = clutter_actor_get_paint_volume (self);
   if (!pv)
     {
-      CLUTTER_NOTE (CLIPPING, "Bail from update_last_paint_volume (%s): "
+      CLUTTER_NOTE (CLIPPING, "Bail from update_paint_volume (%s): "
                     "Actor failed to report a paint volume",
                     _clutter_actor_get_debug_name (self));
       return;
     }
 
-  _clutter_paint_volume_copy_static (pv, &priv->last_paint_volume);
+  _clutter_paint_volume_copy_static (pv, &camera_state->paint_volume);
 
-  _clutter_paint_volume_transform_relative (&priv->last_paint_volume,
-                                            NULL); /* eye coordinates */
+  _clutter_paint_volume_transform_relative_to_camera (&camera_state->paint_volume,
+                                                      camera);
 
-  priv->last_paint_volume_valid = TRUE;
+  camera_state->paint_volume_valid = TRUE;
 }
 
 static inline gboolean
@@ -2814,6 +2959,9 @@ clutter_actor_paint (ClutterActor *self)
   ClutterPickMode pick_mode;
   gboolean clip_set = FALSE;
   gboolean shader_applied = FALSE;
+  ClutterStage *stage;
+  const ClutterCamera *camera;
+  gboolean set_current_camera;
 
   CLUTTER_STATIC_COUNTER (actor_paint_counter,
                           "Actor real-paint counter",
@@ -2901,6 +3049,34 @@ clutter_actor_paint (ClutterActor *self)
   else
     CLUTTER_COUNTER_INC (_clutter_uprof_context, actor_pick_counter);
 
+  stage = _clutter_actor_get_stage_real (self);
+  camera = _clutter_stage_get_current_camera (stage);
+
+  /* Although not ideal, we have to support toolkits that may
+   * manually paint actors outside of a standard paint-cycle
+   * (such as as MxOffscreen which may paint individual actors
+   * to an offscreen fbo)
+   *
+   * In this situation we won't have setup a current camera and so,
+   * for compatibility, we make the left_eye camera current so code
+   * relying on this capability won't simply crash.
+   *
+   * It should be noted though that code relying on this behaviour
+   * won't work with stereoscopic rendering. */
+  if (!camera)
+    {
+      camera = _clutter_stage_get_camera (stage, 0);
+
+      /* XXX: code relying on this really should be encourage to
+       * switch to a solution that works within the paint-cycle not
+       * least because the state of the current camera is basically
+       * un-defined and may change before the next paint. */
+      _clutter_stage_set_current_camera (stage, camera);
+      set_current_camera = TRUE;
+    }
+  else
+    set_current_camera = FALSE;
+
   /* We save the current paint volume so that the next time the
    * actor queues a redraw we can constrain the redraw to just
    * cover the union of the new bounding box and the old.
@@ -2917,7 +3093,9 @@ clutter_actor_paint (ClutterActor *self)
    * the CPU in a typical paint, so at some point we should
    * audit these and consider caching some things.
    */
-  if (!in_clone_paint ())
+  if (!set_current_camera &&
+      pick_mode == CLUTTER_PICK_NONE &&
+      !in_clone_paint ())
     {
       gboolean success;
       /* annoyingly gcc warns if uninitialized even though
@@ -2929,13 +3107,12 @@ clutter_actor_paint (ClutterActor *self)
                       CLUTTER_DEBUG_DISABLE_CLIPPED_REDRAWS)) !=
                     (CLUTTER_DEBUG_DISABLE_CULLING |
                      CLUTTER_DEBUG_DISABLE_CLIPPED_REDRAWS)))
-        _clutter_actor_update_last_paint_volume (self);
+        _clutter_actor_update_paint_volume (self);
 
       success = cull_actor (self, &result);
 
-      if (G_UNLIKELY (clutter_paint_debug_flags & CLUTTER_DEBUG_REDRAWS &&
-                      pick_mode == CLUTTER_PICK_NONE))
-        _clutter_actor_paint_cull_result (self, success, result);
+      if (G_UNLIKELY (clutter_paint_debug_flags & CLUTTER_DEBUG_REDRAWS))
+        _clutter_actor_paint_cull_result (self, camera, success, result);
       else if (result == CLUTTER_CULL_RESULT_OUT && success)
         goto done;
     }
@@ -2969,6 +3146,9 @@ done:
   if (pick_mode == CLUTTER_PICK_NONE)
     priv->is_dirty = FALSE;
 
+  if (set_current_camera)
+    _clutter_stage_set_current_camera (stage, NULL);
+
   if (clip_set)
     cogl_clip_pop();
 
@@ -3729,6 +3909,21 @@ clutter_actor_dispose (GObject *object)
       priv->flatten_effect = NULL;
     }
 
+  if (priv->n_cameras > 0)
+    {
+      int i;
+      for (i = 0; i < priv->n_cameras; i++)
+        {
+          PerCameraState *camera_state = &priv->camera_state[i];
+          if (camera_state->paint_volume_valid)
+            clutter_paint_volume_free (&camera_state->paint_volume);
+        }
+      g_slice_free1 (sizeof (PerCameraState) * priv->n_cameras,
+                     priv->camera_state);
+      priv->camera_state = NULL;
+      priv->n_cameras = -1;
+    }
+
   g_signal_emit (self, actor_signals[DESTROY], 0);
 
   G_OBJECT_CLASS (clutter_actor_parent_class)->dispose (object);
@@ -5275,6 +5470,8 @@ clutter_actor_init (ClutterActor *self)
 
   self->priv = priv = CLUTTER_ACTOR_GET_PRIVATE (self);
 
+  priv->stage_cache = NULL;
+
   priv->parent_actor = NULL;
   priv->has_clip = FALSE;
   priv->opacity = 0xff;
@@ -5295,9 +5492,16 @@ clutter_actor_init (ClutterActor *self)
   priv->opacity_override = -1;
   priv->enable_model_view_transform = TRUE;
 
-  /* Initialize an empty paint volume to start with */
-  _clutter_paint_volume_init_static (&priv->last_paint_volume, NULL);
-  priv->last_paint_volume_valid = TRUE;
+  priv->camera_state = NULL;
+  priv->n_cameras = 0;
+
+  /* When an actor first gets associated with a stage we make sure to
+   * initialize this to a value not matching the
+   * stage's->priv->cameras_age, but for the stage itself we need to
+   * make sure the age is initialized to a value other than 0 so that
+   * get_per_camera_state() will correctly initialize the per-camera
+   * state. */
+  priv->cameras_age = -1;
 
   priv->transform_valid = FALSE;
 
@@ -5343,13 +5547,44 @@ clutter_actor_destroy (ClutterActor *self)
   g_object_unref (self);
 }
 
+/* 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.
+ */
+ClutterPaintVolume *
+_clutter_actor_get_queue_redraw_clip (ClutterActor *self)
+{
+  return self->priv->oob_queue_redraw_clip;
+}
+
+static void
+_clutter_actor_set_queue_redraw_clip (ClutterActor *self,
+                                      ClutterPaintVolume *clip)
+{
+  self->priv->oob_queue_redraw_clip = clip;
+}
+
+int
+_clutter_actor_get_queue_redraw_camera_index (ClutterActor *self)
+{
+  return self->priv->oob_queue_redraw_camera_index;
+}
+
+static void
+_clutter_actor_set_queue_redraw_camera_index (ClutterActor *self,
+                                              int camera_index)
+{
+  self->priv->oob_queue_redraw_camera_index = camera_index;
+}
+
 void
 _clutter_actor_finish_queue_redraw (ClutterActor *self,
                                     ClutterPaintVolume *clip)
 {
   ClutterActorPrivate *priv = self->priv;
-  ClutterPaintVolume *pv;
-  gboolean clipped;
+  ClutterStage *stage = _clutter_actor_get_stage_real (self);
+  int n_cameras = _clutter_stage_get_n_cameras (stage);
+  int i;
 
   /* If we've been explicitly passed a clip volume then there's
    * nothing more to calculate, but otherwise the only thing we know
@@ -5369,47 +5604,66 @@ _clutter_actor_finish_queue_redraw (ClutterActor *self,
   if (clip)
     {
       _clutter_actor_set_queue_redraw_clip (self, clip);
-      clipped = TRUE;
+      for (i = 0; i < n_cameras; i++)
+        {
+          _clutter_actor_set_queue_redraw_camera_index (self, i);
+          _clutter_actor_signal_queue_redraw (self, self);
+        }
     }
-  else if (G_LIKELY (priv->last_paint_volume_valid))
+  else
     {
-      pv = _clutter_actor_get_paint_volume_mutable (self);
-      if (pv)
+      for (i = 0; i < n_cameras; i++)
         {
-          ClutterActor *stage = _clutter_actor_get_stage_internal (self);
-
-          /* make sure we redraw the actors old position... */
-          _clutter_actor_set_queue_redraw_clip (stage,
-                                                &priv->last_paint_volume);
-          _clutter_actor_signal_queue_redraw (stage, stage);
-          _clutter_actor_set_queue_redraw_clip (stage, NULL);
-
-          /* 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...
+          PerCameraState *camera_state =
+            _clutter_actor_get_per_camera_state (self, i);
+          ClutterPaintVolume *pv;
+
+          if (G_LIKELY (camera_state->paint_volume_valid))
+            {
+              pv = _clutter_actor_get_paint_volume_mutable (self);
+              if (pv)
+                {
+                  ClutterActor *stage_actor = CLUTTER_ACTOR (stage);
+
+                  /* make sure we redraw the actors old position... */
+                  _clutter_actor_set_queue_redraw_clip (stage_actor,
+                                                        &camera_state->paint_volume);
+                  _clutter_actor_signal_queue_redraw (stage_actor,
+                                                      stage_actor);
+                  _clutter_actor_set_queue_redraw_clip (stage_actor, NULL);
+                }
+            }
+          else
+            pv = NULL;
+
+          /* XXX: Ideally the redraw signal would take clip volume and
+           * camera arguments, but that would be an ABI break. Until
+           * we can break the ABI we pass these arguments out-of-band
+           * via actor->priv members...
            */
 
-          /* setup the clip for the actors new position... */
+          /* Setup the clip for the actor's new position.
+           * Note: pv could be NULL here which will result in a full
+           * redraw. */
           _clutter_actor_set_queue_redraw_clip (self, pv);
-          clipped = TRUE;
+          _clutter_actor_set_queue_redraw_camera_index (self, i);
+          _clutter_actor_signal_queue_redraw (self, self);
         }
-      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...
+   * our out-of-band clip member is cleared before returning and
+   * the out-of-band camera index reset to zero.
    *
-   * Note: A NULL clip denotes a full-stage, un-clipped redraw
+   * Note: A NULL clip denotes a full-stage, un-clipped redraw and
+   * camera index 0 corresponds to the main stage camera or the
+   * left eye camera while stereoscopic rendering is enabled.
    */
-  if (G_LIKELY (clipped))
-    _clutter_actor_set_queue_redraw_clip (self, NULL);
+
+  _clutter_actor_set_queue_redraw_clip (self, NULL);
+
+  _clutter_actor_set_queue_redraw_camera_index (self, 0);
 
   priv->queue_redraw_entry = NULL;
 }
@@ -5447,10 +5701,10 @@ _clutter_actor_queue_redraw_full (ClutterActor       *self,
                                   ClutterEffect      *effect)
 {
   ClutterActorPrivate *priv = self->priv;
+  ClutterStage *stage;
   ClutterPaintVolume allocation_pv;
   ClutterPaintVolume *pv;
   gboolean should_free_pv;
-  ClutterActor *stage;
 
   /* Here's an outline of the actor queue redraw mechanism:
    *
@@ -5525,9 +5779,8 @@ _clutter_actor_queue_redraw_full (ClutterActor       *self,
   if (CLUTTER_ACTOR_IN_DESTRUCTION (self))
     return;
 
-  stage = _clutter_actor_get_stage_internal (self);
-
   /* Ignore queueing a redraw for actors not descended from a stage */
+  stage = _clutter_actor_get_stage_real (self);
   if (stage == NULL)
     return;
 
@@ -5535,6 +5788,8 @@ _clutter_actor_queue_redraw_full (ClutterActor       *self,
   if (CLUTTER_ACTOR_IN_DESTRUCTION (stage))
     return;
 
+  /* FIXME: in this case if a clip was explicitly passed we should
+   * intersect the clip with the allocation. */
   if (flags & CLUTTER_REDRAW_CLIPPED_TO_ALLOCATION)
     {
       ClutterActorBox allocation_clip;
@@ -5545,9 +5800,12 @@ _clutter_actor_queue_redraw_full (ClutterActor       *self,
       if (priv->needs_allocation)
         {
           /* 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);
+           * full redraw according to the stage paint-volume. */
+          priv->queue_redraw_entry =
+            _clutter_stage_queue_actor_redraw (stage,
+                                               priv->queue_redraw_entry,
+                                               self,
+                                               NULL);
           return;
         }
 
@@ -5573,8 +5831,8 @@ _clutter_actor_queue_redraw_full (ClutterActor       *self,
       should_free_pv = FALSE;
     }
 
-  self->priv->queue_redraw_entry =
-    _clutter_stage_queue_actor_redraw (CLUTTER_STAGE (stage),
+  priv->queue_redraw_entry =
+    _clutter_stage_queue_actor_redraw (stage,
                                        priv->queue_redraw_entry,
                                        self,
                                        pv);
@@ -6065,8 +6323,12 @@ void
 clutter_actor_get_allocation_box (ClutterActor    *self,
                                   ClutterActorBox *box)
 {
+  ClutterActorPrivate *priv;
+
   g_return_if_fail (CLUTTER_IS_ACTOR (self));
 
+  priv = self->priv;
+
   /* XXX - if needs_allocation=TRUE, we can either 1) g_return_if_fail,
    * which limits calling get_allocation to inside paint() basically; or
    * we can 2) force a layout, which could be expensive if someone calls
@@ -6080,16 +6342,15 @@ clutter_actor_get_allocation_box (ClutterActor    *self,
    */
 
   /* this implements 2) */
-  if (G_UNLIKELY (self->priv->needs_allocation))
+  if (G_UNLIKELY (priv->needs_allocation))
     {
-      ClutterActor *stage = _clutter_actor_get_stage_internal (self);
-
+      ClutterStage *stage = _clutter_actor_get_stage_real (self);
       /* do not queue a relayout on an unparented actor */
       if (stage)
         _clutter_stage_maybe_relayout (stage);
     }
 
-  /* commenting out the code above and just keeping this assigment
+  /* commenting out the code above and just keeping this assignment
    * implements 3)
    */
   *box = self->priv->allocation;
@@ -6156,9 +6417,13 @@ clutter_actor_allocate (ClutterActor           *self,
   ClutterActorBox alloc;
   gboolean origin_changed, child_moved, size_changed;
   gboolean stage_allocation_changed;
+  ClutterStage *stage;
 
   g_return_if_fail (CLUTTER_IS_ACTOR (self));
-  if (G_UNLIKELY (_clutter_actor_get_stage_internal (self) == NULL))
+  priv = self->priv;
+  stage = _clutter_actor_get_stage_real (self);
+
+  if (G_UNLIKELY (stage == NULL))
     {
       g_warning ("Spurious clutter_actor_allocate called for actor %p/%s "
                  "which isn't a descendent of the stage!\n",
@@ -6166,8 +6431,6 @@ clutter_actor_allocate (ClutterActor           *self,
       return;
     }
 
-  priv = self->priv;
-
   alloc = *box;
 
   if (priv->constraints != NULL)
@@ -6866,6 +7129,9 @@ clutter_actor_get_transformed_position (ClutterActor *self,
  * information, you need to use clutter_actor_get_abs_allocation_vertices()
  * to get the coords of the actual quadrangle.</note>
  *
+ * <note>If clutter is being used for stereo rendering then this will
+ * simply return a size according the left eye's view.</note>
+ *
  * Since: 0.8
  */
 void
@@ -6906,7 +7172,7 @@ clutter_actor_get_transformed_size (ClutterActor *self,
       box.x2 = natural_width;
       box.y2 = natural_height;
 
-      _clutter_actor_transform_and_project_box (self, &box, v);
+      _clutter_actor_transform_and_project_box (self, 0, &box, v);
     }
   else
     clutter_actor_get_abs_allocation_vertices (self, v);
@@ -8127,6 +8393,53 @@ clutter_actor_get_clip (ClutterActor *self,
     *height = priv->clip[3];
 }
 
+typedef struct
+{
+  ClutterStage *stage;
+  int stage_n_cameras;
+  int stage_cameras_age;
+} InitPerCameraStateClosure;
+
+static ClutterActorTraverseVisitFlags
+init_per_camera_state_cb (ClutterActor *self,
+                          int           depth,
+                          gpointer      user_data)
+{
+  ClutterActorPrivate *priv = self->priv;
+  InitPerCameraStateClosure *closure = user_data;
+  int n_cameras = closure->stage_n_cameras;
+  int i;
+
+  /* This is the first point at which this actor has been
+   * associated with a specific stage and now that we have been
+   * associated with a set of cameras we need to initialize the
+   * actor's per-camera paint-volume to be empty so when it first
+   * gets shown we will only redraw the new area of the actor.
+   *
+   * XXX: it could be nice if re-parenting an actor within the
+   * same stage didn't hit this path too.
+   */
+
+  /* Make sure our camera state doesn't have the same age as the
+   * stage's camera state so we can be sure it will be invalidated
+   * during _clutter_actor_get_per_camera_state() */
+  priv->cameras_age = closure->stage_cameras_age - 1;
+
+  /* XXX: note we don't just rely on the initialization of per camera
+   * state by _clutter_actor_get_per_camera_state() since that will
+   * mark the initial paint_volume as invalid.
+   */
+  for (i = 0; i < n_cameras; i++)
+    {
+      PerCameraState *camera_state =
+        _clutter_actor_get_per_camera_state (self, i);
+      _clutter_paint_volume_init_static (&camera_state->paint_volume, NULL);
+      camera_state->paint_volume_valid = TRUE;
+    }
+
+  return CLUTTER_ACTOR_TRAVERSE_VISIT_CONTINUE;
+}
+
 /**
  * clutter_actor_set_parent:
  * @self: A #ClutterActor
@@ -8145,6 +8458,7 @@ clutter_actor_set_parent (ClutterActor *self,
   ClutterActorPrivate *priv;
   ClutterActorPrivate *parent_priv;
   ClutterTextDirection text_dir;
+  ClutterStage *stage;
 
   g_return_if_fail (CLUTTER_IS_ACTOR (self));
   g_return_if_fail (CLUTTER_IS_ACTOR (parent));
@@ -8186,6 +8500,25 @@ clutter_actor_set_parent (ClutterActor *self,
   if (parent->priv->internal_child)
     CLUTTER_SET_PRIVATE_FLAGS (self, CLUTTER_INTERNAL_CHILD);
 
+  /* Check to see if the actor is associated with a stage yet... */
+  stage = _clutter_actor_get_stage_real (self);
+
+  if (stage)
+    {
+      InitPerCameraStateClosure init_per_camera_state_closure;
+
+      init_per_camera_state_closure.stage_n_cameras =
+        _clutter_stage_get_n_cameras (stage);
+      init_per_camera_state_closure.stage_cameras_age =
+        _clutter_stage_get_cameras_age (stage);
+
+      _clutter_actor_traverse (self,
+                               CLUTTER_ACTOR_TRAVERSE_DEPTH_FIRST,
+                               init_per_camera_state_cb,
+                               NULL,
+                               &init_per_camera_state_closure);
+    }
+
   /* clutter_actor_reparent() will emit ::parent-set for us */
   if (!CLUTTER_ACTOR_IN_REPARENT (self))
     g_signal_emit (self, actor_signals[PARENT_SET], 0, NULL);
@@ -8307,6 +8640,8 @@ clutter_actor_unparent (ClutterActor *self)
   if (priv->parent_actor == NULL)
     return;
 
+  priv->stage_cache = NULL;
+
   was_mapped = CLUTTER_ACTOR_IS_MAPPED (self);
 
   /* we need to unrealize *before* we set parent_actor to NULL,
@@ -10304,15 +10639,6 @@ clutter_actor_is_scaled (ClutterActor *self)
   return FALSE;
 }
 
-ClutterActor *
-_clutter_actor_get_stage_internal (ClutterActor *actor)
-{
-  while (actor && !CLUTTER_ACTOR_IS_TOPLEVEL (actor))
-    actor = actor->priv->parent_actor;
-
-  return actor;
-}
-
 /**
  * clutter_actor_get_stage:
  * @actor: a #ClutterActor
@@ -10329,7 +10655,7 @@ clutter_actor_get_stage (ClutterActor *actor)
 {
   g_return_val_if_fail (CLUTTER_IS_ACTOR (actor), NULL);
 
-  return _clutter_actor_get_stage_internal (actor);
+  return CLUTTER_ACTOR (_clutter_actor_get_stage_real (actor));
 }
 
 /**
@@ -10642,13 +10968,13 @@ out:
 void
 clutter_actor_grab_key_focus (ClutterActor *self)
 {
-  ClutterActor *stage;
+  ClutterStage *stage;
 
   g_return_if_fail (CLUTTER_IS_ACTOR (self));
 
-  stage = _clutter_actor_get_stage_internal (self);
+  stage = _clutter_actor_get_stage_real (self);
   if (stage != NULL)
-    clutter_stage_set_key_focus (CLUTTER_STAGE (stage), self);
+    clutter_stage_set_key_focus (stage, self);
 }
 
 /**
@@ -11381,23 +11707,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.
- */
-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,
-                                      ClutterPaintVolume *clip)
-{
-  self->priv->oob_queue_redraw_clip = clip;
-}
-
 /**
  * clutter_actor_has_allocation:
  * @self: a #ClutterActor
@@ -12093,15 +12402,15 @@ clutter_actor_clear_effects (ClutterActor *self)
 gboolean
 clutter_actor_has_key_focus (ClutterActor *self)
 {
-  ClutterActor *stage;
+  ClutterStage *stage;
 
   g_return_val_if_fail (CLUTTER_IS_ACTOR (self), FALSE);
 
-  stage = _clutter_actor_get_stage_internal (self);
+  stage = _clutter_actor_get_stage_real (self);
   if (stage == NULL)
     return FALSE;
 
-  return clutter_stage_get_key_focus (CLUTTER_STAGE (stage)) == self;
+  return clutter_stage_get_key_focus (stage) == self;
 }
 
 static gboolean
@@ -12229,10 +12538,12 @@ _clutter_actor_get_paint_volume_real (ClutterActor *self,
 static ClutterPaintVolume *
 _clutter_actor_get_paint_volume_mutable (ClutterActor *self)
 {
-  ClutterActorPrivate *priv;
-
-  priv = self->priv;
+  ClutterActorPrivate *priv = self->priv;
 
+  /* NB: the paint volume isn't slice/heap allocated; it's actually
+   * just a member of priv, but we still have to call _free incase
+   * cogl-paint-volume.c associates object/memory references with
+   * the volume... */
   if (priv->paint_volume_valid)
     clutter_paint_volume_free (&priv->paint_volume);
 
@@ -12272,6 +12583,9 @@ _clutter_actor_get_paint_volume_mutable (ClutterActor *self)
  * Return value: (transfer none): a pointer to a #ClutterPaintVolume
  *   or %NULL if no volume could be determined.
  *
+ * <note>If stereoscopic rendering has been enabled then the paint
+ * volume is only valid for the eye currently being rendered.</note>
+ *
  * Since: 1.6
  */
 const ClutterPaintVolume *
@@ -12309,23 +12623,21 @@ const ClutterPaintVolume *
 clutter_actor_get_transformed_paint_volume (ClutterActor *self,
                                             ClutterActor *relative_to_ancestor)
 {
+  ClutterStage *stage = _clutter_actor_get_stage_real (self);
   const ClutterPaintVolume *volume;
-  ClutterActor *stage;
   ClutterPaintVolume *transformed_volume;
 
-  stage = _clutter_actor_get_stage_internal (self);
   if (G_UNLIKELY (stage == NULL))
     return NULL;
 
   if (relative_to_ancestor == NULL)
-    relative_to_ancestor = stage;
+    relative_to_ancestor = CLUTTER_ACTOR (stage);
 
   volume = clutter_actor_get_paint_volume (self);
   if (volume == NULL)
     return NULL;
 
-  transformed_volume =
-    _clutter_stage_paint_volume_stack_allocate (CLUTTER_STAGE (stage));
+  transformed_volume = _clutter_stage_paint_volume_stack_allocate (stage);
 
   _clutter_paint_volume_copy_static (volume, transformed_volume);
 
@@ -12353,30 +12665,39 @@ clutter_actor_get_transformed_paint_volume (ClutterActor *self,
  * because the actor isn't yet parented under a stage or because
  * the actor is unable to determine a paint volume.
  *
+ * This function may only be called during a paint cycle.
+ *
  * Return value: %TRUE if a 2D paint box could be determined, else
  * %FALSE.
  *
+ * <note>If stereoscopic rendering has been enabled then the paint box
+ * returned will only be valid for the current eye being
+ * rendered</note>
+ *
  * Since: 1.6
  */
 gboolean
 clutter_actor_get_paint_box (ClutterActor    *self,
                              ClutterActorBox *box)
 {
-  ClutterActor *stage;
   ClutterPaintVolume *pv;
+  const ClutterCamera *camera;
+  ClutterStage *stage;
 
   g_return_val_if_fail (CLUTTER_IS_ACTOR (self), FALSE);
   g_return_val_if_fail (box != NULL, FALSE);
 
-  stage = _clutter_actor_get_stage_internal (self);
+  stage = _clutter_actor_get_stage_real (self);
   if (G_UNLIKELY (!stage))
     return FALSE;
 
+  camera = _clutter_stage_get_current_camera (stage);
+
   pv = _clutter_actor_get_paint_volume_mutable (self);
   if (G_UNLIKELY (!pv))
     return FALSE;
 
-  _clutter_paint_volume_get_stage_paint_box (pv, CLUTTER_STAGE (stage), box);
+  _clutter_paint_volume_get_camera_paint_box (pv, camera, box);
 
   return TRUE;
 }
diff --git a/clutter/clutter-backend.c b/clutter/clutter-backend.c
index 80e4853..5a7c12c 100644
--- a/clutter/clutter-backend.c
+++ b/clutter/clutter-backend.c
@@ -503,8 +503,8 @@ _clutter_backend_ensure_context (ClutterBackend *backend,
            * This dirty mechanism will ensure they are asserted before
            * the next paint...
            */
-          _clutter_stage_dirty_viewport (stage);
-          _clutter_stage_dirty_projection (stage);
+          _clutter_stage_dirty_cogl_viewport (stage);
+          _clutter_stage_dirty_cogl_projection (stage);
         }
 
       /* FIXME: With a NULL stage and thus no active context it may make more
diff --git a/clutter/clutter-offscreen-effect.c b/clutter/clutter-offscreen-effect.c
index bb5268a..7515ea9 100644
--- a/clutter/clutter-offscreen-effect.c
+++ b/clutter/clutter-offscreen-effect.c
@@ -74,21 +74,21 @@
 #include "clutter-private.h"
 #include "clutter-stage-private.h"
 
-struct _ClutterOffscreenEffectPrivate
+typedef struct _PerCameraState
 {
+  const ClutterCamera *camera;
+  int valid_for_age;
+
   CoglHandle offscreen;
-  CoglMaterial *target;
 
-  ClutterActor *actor;
-  ClutterActor *stage;
+  /* aka "target" for legacy reasons */
+  CoglMaterial *material;
 
   gfloat x_offset;
   gfloat y_offset;
 
-  gfloat target_width;
-  gfloat target_height;
-
-  gint old_opacity_override;
+  gfloat fbo_width;
+  gfloat fbo_height;
 
   /* The matrix that was current the last time the fbo was updated. We
      need to keep track of this to detect when we can reuse the
@@ -99,6 +99,18 @@ struct _ClutterOffscreenEffectPrivate
      and it won't cause a redraw to be queued on the parent's
      children. */
   CoglMatrix last_matrix_drawn;
+
+} PerCameraState;
+
+struct _ClutterOffscreenEffectPrivate
+{
+  ClutterActor *actor;
+
+  PerCameraState *camera_state;
+  int n_cameras;
+  int cameras_age;
+
+  gint old_opacity_override;
 };
 
 G_DEFINE_ABSTRACT_TYPE (ClutterOffscreenEffect,
@@ -112,15 +124,20 @@ clutter_offscreen_effect_set_actor (ClutterActorMeta *meta,
   ClutterOffscreenEffect *self = CLUTTER_OFFSCREEN_EFFECT (meta);
   ClutterOffscreenEffectPrivate *priv = self->priv;
   ClutterActorMetaClass *meta_class;
+  int i;
 
   meta_class = CLUTTER_ACTOR_META_CLASS (clutter_offscreen_effect_parent_class);
   meta_class->set_actor (meta, actor);
 
   /* clear out the previous state */
-  if (priv->offscreen != COGL_INVALID_HANDLE)
+  for (i = 0; i < priv->n_cameras; i++)
     {
-      cogl_handle_unref (priv->offscreen);
-      priv->offscreen = COGL_INVALID_HANDLE;
+      PerCameraState *camera_state = &priv->camera_state[i];
+      if (camera_state->offscreen != COGL_INVALID_HANDLE)
+        {
+          cogl_handle_unref (camera_state->offscreen);
+          camera_state->offscreen = COGL_INVALID_HANDLE;
+        }
     }
 
   /* we keep a back pointer here, to avoid going through the ActorMeta */
@@ -137,36 +154,96 @@ clutter_offscreen_effect_real_create_texture (ClutterOffscreenEffect *effect,
                                      COGL_PIXEL_FORMAT_RGBA_8888_PRE);
 }
 
-static gboolean
-update_fbo (ClutterEffect *effect, int fbo_width, int fbo_height)
+static void
+invalidate_per_camera_state (PerCameraState *camera_state)
+{
+  if (camera_state->material)
+    {
+      cogl_handle_unref (camera_state->material);
+      camera_state->material = COGL_INVALID_HANDLE;
+    }
+
+  if (camera_state->offscreen)
+    {
+      cogl_handle_unref (camera_state->offscreen);
+      camera_state->offscreen = COGL_INVALID_HANDLE;
+    }
+}
+
+static PerCameraState *
+get_per_camera_state (ClutterOffscreenEffect *self, int camera_index)
 {
-  ClutterOffscreenEffect *self = CLUTTER_OFFSCREEN_EFFECT (effect);
   ClutterOffscreenEffectPrivate *priv = self->priv;
-  CoglHandle texture;
+  ClutterStage *stage = _clutter_actor_get_stage_internal (priv->actor);
+  int cameras_age = _clutter_stage_get_cameras_age (stage);
+  PerCameraState *camera_state;
 
-  priv->stage = clutter_actor_get_stage (priv->actor);
-  if (priv->stage == NULL)
+  /* Whenever there are additions or removals of cameras associated
+   * with the stage then the stage's 'cameras_age' is bumped and we
+   * throw away any cached state associated with the old cameras. */
+
+  if (G_UNLIKELY (cameras_age != priv->cameras_age))
     {
-      CLUTTER_NOTE (MISC, "The actor '%s' is not part of a stage",
-                    clutter_actor_get_name (priv->actor) == NULL
-                      ? G_OBJECT_TYPE_NAME (priv->actor)
-                      : clutter_actor_get_name (priv->actor));
-      return FALSE;
+      int i;
+      int n_cameras;
+
+      for (i = 0; i < priv->n_cameras; i++)
+        invalidate_per_camera_state (&priv->camera_state[i]);
+
+      if (priv->camera_state)
+        g_slice_free1 (sizeof (PerCameraState) * priv->n_cameras,
+                       priv->camera_state);
+
+      /* NB: We always allocate for the total number of cameras since
+       * we expect that each camera is likely going to be painted each
+       * frame so we should save having to re-allocate later. */
+      n_cameras = _clutter_stage_get_n_cameras (stage);
+      priv->camera_state = g_slice_alloc (sizeof (PerCameraState) * n_cameras);
+
+      for (i = 0; i < n_cameras; i++)
+        {
+          camera_state = &priv->camera_state[i];
+
+          camera_state->camera = _clutter_stage_get_camera (stage, i);
+          camera_state->material = COGL_INVALID_HANDLE;
+          camera_state->offscreen = COGL_INVALID_HANDLE;
+        }
+
+      priv->n_cameras = n_cameras;
+      priv->cameras_age = cameras_age;
+    }
+
+  camera_state = &priv->camera_state[camera_index];
+  if (camera_state->camera->age != camera_state->valid_for_age)
+    {
+      invalidate_per_camera_state (camera_state);
+      camera_state->valid_for_age = camera_state->camera->age;
     }
 
-  if (priv->target_width == fbo_width &&
-      priv->target_height == fbo_height &&
-      priv->offscreen != COGL_INVALID_HANDLE)
+  return camera_state;
+}
+
+static gboolean
+update_fbo (ClutterEffect *effect, int camera_index, int fbo_width, int fbo_height)
+{
+  ClutterOffscreenEffect *self = CLUTTER_OFFSCREEN_EFFECT (effect);
+  CoglHandle texture;
+  PerCameraState *camera_state;
+
+  camera_state = get_per_camera_state (self, camera_index);
+  if (camera_state->fbo_width == fbo_width &&
+      camera_state->fbo_height == fbo_height &&
+      camera_state->offscreen != COGL_INVALID_HANDLE)
     return TRUE;
 
-  if (priv->target == COGL_INVALID_HANDLE)
+  if (camera_state->material == COGL_INVALID_HANDLE)
     {
-      priv->target = cogl_material_new ();
+      camera_state->material = cogl_material_new ();
 
       /* We're always going to render the texture at a 1:1 texel:pixel
          ratio so we can use 'nearest' filtering to decrease the
          effects of rounding errors in the geometry calculation */
-      cogl_material_set_layer_filters (priv->target,
+      cogl_material_set_layer_filters (camera_state->material,
                                        0, /* layer_index */
                                        COGL_MATERIAL_FILTER_NEAREST,
                                        COGL_MATERIAL_FILTER_NEAREST);
@@ -177,29 +254,29 @@ update_fbo (ClutterEffect *effect, int fbo_width, int fbo_height)
   if (texture == COGL_INVALID_HANDLE)
      return FALSE;
 
-  cogl_material_set_layer (priv->target, 0, texture);
+  cogl_material_set_layer (camera_state->material, 0, texture);
   cogl_handle_unref (texture);
 
   /* we need to use the size of the texture target and not the minimum
    * size we passed to the create_texture() vfunc, as any sub-class might
    * give use a bigger texture
    */
-  priv->target_width = cogl_texture_get_width (texture);
-  priv->target_height = cogl_texture_get_height (texture);
+  camera_state->fbo_width = cogl_texture_get_width (texture);
+  camera_state->fbo_height = cogl_texture_get_height (texture);
 
-  if (priv->offscreen != COGL_INVALID_HANDLE)
-    cogl_handle_unref (priv->offscreen);
+  if (camera_state->offscreen != COGL_INVALID_HANDLE)
+    cogl_handle_unref (camera_state->offscreen);
 
-  priv->offscreen = cogl_offscreen_new_to_texture (texture);
-  if (priv->offscreen == COGL_INVALID_HANDLE)
+  camera_state->offscreen = cogl_offscreen_new_to_texture (texture);
+  if (camera_state->offscreen == COGL_INVALID_HANDLE)
     {
       g_warning ("%s: Unable to create an Offscreen buffer", G_STRLOC);
 
-      cogl_handle_unref (priv->target);
-      priv->target = COGL_INVALID_HANDLE;
+      cogl_handle_unref (camera_state->material);
+      camera_state->material = COGL_INVALID_HANDLE;
 
-      priv->target_width = 0;
-      priv->target_height = 0;
+      camera_state->fbo_width = 0;
+      camera_state->fbo_height = 0;
 
       return FALSE;
     }
@@ -219,6 +296,9 @@ clutter_offscreen_effect_pre_paint (ClutterEffect *effect)
   gfloat fbo_width, fbo_height;
   gfloat width, height;
   gfloat xexpand, yexpand;
+  const ClutterCamera *camera;
+  PerCameraState *camera_state;
+  ClutterStage *stage;
 
   if (!clutter_actor_meta_get_enabled (CLUTTER_ACTOR_META (effect)))
     return FALSE;
@@ -226,6 +306,11 @@ clutter_offscreen_effect_pre_paint (ClutterEffect *effect)
   if (priv->actor == NULL)
     return FALSE;
 
+  stage = _clutter_actor_get_stage_internal (priv->actor);
+
+  camera = _clutter_stage_get_current_camera (stage);
+  camera_state = get_per_camera_state (self, camera->index);
+
   /* The paint box is the bounding box of the actor's paint volume in
    * stage coordinates. This will give us the size for the framebuffer
    * we need to redirect its rendering offscreen and its position will
@@ -233,20 +318,19 @@ clutter_offscreen_effect_pre_paint (ClutterEffect *effect)
   if (clutter_actor_get_paint_box (priv->actor, &box))
     {
       clutter_actor_box_get_size (&box, &fbo_width, &fbo_height);
-      clutter_actor_box_get_origin (&box, &priv->x_offset, &priv->y_offset);
+      clutter_actor_box_get_origin (&box, &camera_state->x_offset, &camera_state->y_offset);
     }
   else
     {
       /* If we can't get a valid paint box then we fallback to
        * creating a full stage size fbo. */
-      ClutterActor *stage = _clutter_actor_get_stage_internal (priv->actor);
-      clutter_actor_get_size (stage, &fbo_width, &fbo_height);
-      priv->x_offset = 0.0f;
-      priv->y_offset = 0.0f;
+      clutter_actor_get_size (CLUTTER_ACTOR (stage), &fbo_width, &fbo_height);
+      camera_state->x_offset = 0.0f;
+      camera_state->y_offset = 0.0f;
     }
 
   /* First assert that the framebuffer is the right size... */
-  if (!update_fbo (effect, fbo_width, fbo_height))
+  if (!update_fbo (effect, camera->index, fbo_width, fbo_height))
     return FALSE;
 
   /* get the current modelview matrix so that we can copy it
@@ -257,38 +341,37 @@ clutter_offscreen_effect_pre_paint (ClutterEffect *effect)
   /* Store the matrix that was last used when we updated the FBO so
      that we can detect when we don't need to update the FBO to paint
      a second time */
-  priv->last_matrix_drawn = modelview;
+  camera_state->last_matrix_drawn = modelview;
 
   /* let's draw offscreen */
-  cogl_push_framebuffer (priv->offscreen);
+  cogl_push_framebuffer (camera_state->offscreen);
 
   /* Set up the viewport so that it has the same size as the stage,
    * but offset it so that the actor of interest lands on our
    * framebuffer. */
-  clutter_actor_get_size (priv->stage, &width, &height);
+  clutter_actor_get_size (CLUTTER_ACTOR (stage), &width, &height);
 
   /* Expand the viewport if the actor is partially off-stage,
    * otherwise the actor will end up clipped to the stage viewport
    */
   xexpand = 0.f;
-  if (priv->x_offset < 0.f)
-    xexpand = -priv->x_offset;
-  if (priv->x_offset + priv->target_width > width)
-    xexpand = MAX (xexpand, (priv->x_offset + priv->target_width) - width);
+  if (camera_state->x_offset < 0.f)
+    xexpand = -camera_state->x_offset;
+  if (camera_state->x_offset + camera_state->fbo_width > width)
+    xexpand = MAX (xexpand, (camera_state->x_offset + camera_state->fbo_width) - width);
 
   yexpand = 0.f;
-  if (priv->y_offset < 0.f)
-    yexpand = -priv->y_offset;
-  if (priv->y_offset + priv->target_height > height)
-    yexpand = MAX (yexpand, (priv->y_offset + priv->target_height) - height);
+  if (camera_state->y_offset < 0.f)
+    yexpand = -camera_state->y_offset;
+  if (camera_state->y_offset + camera_state->fbo_height > height)
+    yexpand = MAX (yexpand, (camera_state->y_offset + camera_state->fbo_height) - height);
 
   /* Set the viewport */
-  cogl_set_viewport (-(priv->x_offset + xexpand), -(priv->y_offset + yexpand),
+  cogl_set_viewport (-(camera_state->x_offset + xexpand), -(camera_state->y_offset + yexpand),
                      width + (2 * xexpand), height + (2 * yexpand));
 
   /* Copy the stage's projection matrix across to the framebuffer */
-  _clutter_stage_get_projection_matrix (CLUTTER_STAGE (priv->stage),
-                                        &projection);
+  _clutter_stage_get_projection_matrix (stage, &projection);
   cogl_set_projection_matrix (&projection);
 
   /* If we've expanded the viewport, make sure to scale the modelview
@@ -339,15 +422,18 @@ clutter_offscreen_effect_real_paint_target (ClutterOffscreenEffect *effect)
 {
   ClutterOffscreenEffectPrivate *priv = effect->priv;
   guint8 paint_opacity;
+  ClutterStage *stage = _clutter_actor_get_stage_internal (priv->actor);
+  const ClutterCamera *camera = _clutter_stage_get_current_camera (stage);
+  PerCameraState *camera_state = get_per_camera_state (effect, camera->index);
 
   paint_opacity = clutter_actor_get_paint_opacity (priv->actor);
 
-  cogl_material_set_color4ub (priv->target,
+  cogl_material_set_color4ub (camera_state->material,
                               paint_opacity,
                               paint_opacity,
                               paint_opacity,
                               paint_opacity);
-  cogl_set_source (priv->target);
+  cogl_set_source (camera_state->material);
 
   /* At this point we are in stage coordinates translated so if
    * we draw our texture using a textured quad the size of the paint
@@ -355,8 +441,8 @@ clutter_offscreen_effect_real_paint_target (ClutterOffscreenEffect *effect)
    * hadn't been redirected offscreen.
    */
   cogl_rectangle_with_texture_coords (0, 0,
-                                      priv->target_width,
-                                      priv->target_height,
+                                      camera_state->fbo_width,
+                                      camera_state->fbo_height,
                                       0.0, 0.0,
                                       1.0, 1.0);
 }
@@ -366,6 +452,9 @@ clutter_offscreen_effect_paint_texture (ClutterOffscreenEffect *effect)
 {
   ClutterOffscreenEffectPrivate *priv = effect->priv;
   CoglMatrix modelview;
+  ClutterStage *stage = _clutter_actor_get_stage_internal (priv->actor);
+  const ClutterCamera *camera = _clutter_stage_get_current_camera (stage);
+  PerCameraState *camera_state = get_per_camera_state (effect, camera->index);
 
   cogl_push_matrix ();
 
@@ -374,8 +463,9 @@ clutter_offscreen_effect_paint_texture (ClutterOffscreenEffect *effect)
    * quad... */
 
   cogl_matrix_init_identity (&modelview);
-  _clutter_actor_apply_modelview_transform (priv->stage, &modelview);
-  cogl_matrix_translate (&modelview, priv->x_offset, priv->y_offset, 0.0f);
+  _clutter_actor_apply_modelview_transform (CLUTTER_ACTOR (stage), &modelview);
+  cogl_matrix_translate (&modelview,
+                         camera_state->x_offset, camera_state->y_offset, 0.0f);
   cogl_set_modelview_matrix (&modelview);
 
   /* paint the target material; this is virtualized for
@@ -391,9 +481,12 @@ clutter_offscreen_effect_post_paint (ClutterEffect *effect)
 {
   ClutterOffscreenEffect *self = CLUTTER_OFFSCREEN_EFFECT (effect);
   ClutterOffscreenEffectPrivate *priv = self->priv;
+  ClutterStage *stage = _clutter_actor_get_stage_internal (priv->actor);
+  const ClutterCamera *camera = _clutter_stage_get_current_camera (stage);
+  PerCameraState *camera_state = get_per_camera_state (self, camera->index);
 
-  if (priv->offscreen == COGL_INVALID_HANDLE ||
-      priv->target == COGL_INVALID_HANDLE ||
+  if (camera_state->offscreen == COGL_INVALID_HANDLE ||
+      camera_state->material == COGL_INVALID_HANDLE ||
       priv->actor == NULL)
     return;
 
@@ -413,20 +506,26 @@ clutter_offscreen_effect_paint (ClutterEffect           *effect,
   ClutterOffscreenEffect *self = CLUTTER_OFFSCREEN_EFFECT (effect);
   ClutterOffscreenEffectPrivate *priv = self->priv;
   CoglMatrix matrix;
+  ClutterStage *stage = _clutter_actor_get_stage_internal (priv->actor);
+  const ClutterCamera *camera = _clutter_stage_get_current_camera (stage);
+  PerCameraState *camera_state = get_per_camera_state (self, camera->index);
 
   cogl_get_modelview_matrix (&matrix);
 
   /* If we've already got a cached image for the same matrix and the
-     actor hasn't been redrawn then we can just use the cached image
-     in the fbo */
-  if (priv->offscreen == NULL ||
+   * actor hasn't been redrawn then we can just use the cached image
+   * in the fbo
+   */
+  if (camera_state->offscreen == NULL ||
       (flags & CLUTTER_EFFECT_PAINT_ACTOR_DIRTY) ||
-      !cogl_matrix_equal (&matrix, &priv->last_matrix_drawn))
+      !cogl_matrix_equal (&matrix, &camera_state->last_matrix_drawn) ||
+      camera_state->valid_for_age != camera_state->camera->age)
     {
       /* Chain up to the parent paint method which will call the pre and
          post paint functions to update the image */
       CLUTTER_EFFECT_CLASS (clutter_offscreen_effect_parent_class)->
         paint (effect, flags);
+      camera_state->valid_for_age = camera_state->camera->age;
     }
   else
     clutter_offscreen_effect_paint_texture (self);
@@ -437,12 +536,26 @@ clutter_offscreen_effect_finalize (GObject *gobject)
 {
   ClutterOffscreenEffect *self = CLUTTER_OFFSCREEN_EFFECT (gobject);
   ClutterOffscreenEffectPrivate *priv = self->priv;
+  int i;
+
+  for (i = 0; i < priv->n_cameras; i++)
+    {
+      PerCameraState *camera_state = &priv->camera_state[i];
+
+      if (camera_state->offscreen)
+        cogl_handle_unref (camera_state->offscreen);
 
-  if (priv->offscreen)
-    cogl_handle_unref (priv->offscreen);
+      if (camera_state->material)
+        cogl_handle_unref (camera_state->material);
+    }
 
-  if (priv->target)
-    cogl_handle_unref (priv->target);
+  if (priv->camera_state)
+    {
+      g_slice_free1 (sizeof (PerCameraState) * priv->n_cameras,
+                     priv->camera_state);
+      priv->camera_state = NULL;
+      priv->n_cameras = 0;
+    }
 
   G_OBJECT_CLASS (clutter_offscreen_effect_parent_class)->finalize (gobject);
 }
@@ -474,6 +587,8 @@ clutter_offscreen_effect_init (ClutterOffscreenEffect *self)
   self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
                                             CLUTTER_TYPE_OFFSCREEN_EFFECT,
                                             ClutterOffscreenEffectPrivate);
+
+  self->priv->cameras_age = -1;
 }
 
 /**
@@ -495,10 +610,23 @@ clutter_offscreen_effect_init (ClutterOffscreenEffect *self)
 CoglMaterial *
 clutter_offscreen_effect_get_target (ClutterOffscreenEffect *effect)
 {
+  ClutterOffscreenEffect *self;
+  ClutterOffscreenEffectPrivate *priv;
+  ClutterStage *stage;
+  const ClutterCamera *camera;
+  PerCameraState *camera_state;
+
   g_return_val_if_fail (CLUTTER_IS_OFFSCREEN_EFFECT (effect),
                         COGL_INVALID_HANDLE);
 
-  return effect->priv->target;
+  self = CLUTTER_OFFSCREEN_EFFECT (effect);
+  priv = self->priv;
+
+  stage = _clutter_actor_get_stage_internal (priv->actor);
+  camera = _clutter_stage_get_current_camera (stage);
+  camera_state = get_per_camera_state (self, camera->index);
+
+  return camera_state->material;
 }
 
 /**
@@ -557,6 +685,10 @@ clutter_offscreen_effect_create_texture (ClutterOffscreenEffect *effect,
  * implementations, from within the <function>paint_target()</function>
  * virtual function.
  *
+ * <note>If stereoscopic rendering has been enabled then this function
+ * returns the size according to the eye currently being
+ * rendered.</note>
+ *
  * Return value: %TRUE if the offscreen buffer has a valid size,
  *   and %FALSE otherwise
  *
@@ -568,19 +700,26 @@ clutter_offscreen_effect_get_target_size (ClutterOffscreenEffect *effect,
                                           gfloat                 *height)
 {
   ClutterOffscreenEffectPrivate *priv;
+  ClutterStage *stage;
+  const ClutterCamera *camera;
+  PerCameraState *camera_state;
 
   g_return_val_if_fail (CLUTTER_IS_OFFSCREEN_EFFECT (effect), FALSE);
-  
+
   priv = effect->priv;
 
-  if (priv->target == NULL)
+  stage = _clutter_actor_get_stage_internal (priv->actor);
+  camera = _clutter_stage_get_current_camera (stage);
+  camera_state = get_per_camera_state (effect, camera->index);
+
+  if (camera_state->material == NULL)
     return FALSE;
 
   if (width)
-    *width = priv->target_width;
+    *width = camera_state->fbo_width;
 
   if (height)
-    *height = priv->target_height;
+    *height = camera_state->fbo_height;
 
   return TRUE;
 }
diff --git a/clutter/clutter-paint-volume-private.h b/clutter/clutter-paint-volume-private.h
index 72bc7ae..e2c1b78 100644
--- a/clutter/clutter-paint-volume-private.h
+++ b/clutter/clutter-paint-volume-private.h
@@ -126,13 +126,16 @@ void                _clutter_paint_volume_set_reference_actor  (ClutterPaintVolu
 ClutterCullResult   _clutter_paint_volume_cull                 (ClutterPaintVolume *pv,
                                                                 const ClutterPlane       *planes);
 
-void                _clutter_paint_volume_get_stage_paint_box  (ClutterPaintVolume *pv,
-                                                                ClutterStage *stage,
+void                _clutter_paint_volume_get_camera_paint_box (ClutterPaintVolume *pv,
+                                                                const ClutterCamera *camera,
                                                                 ClutterActorBox *box);
 
 void                _clutter_paint_volume_transform_relative   (ClutterPaintVolume *pv,
                                                                 ClutterActor *relative_to_ancestor);
 
+void                _clutter_paint_volume_transform_relative_to_camera (ClutterPaintVolume *pv,
+                                                                        const ClutterCamera *camera);
+
 G_END_DECLS
 
 #endif /* __CLUTTER_PAINT_VOLUME_PRIVATE_H__ */
diff --git a/clutter/clutter-paint-volume.c b/clutter/clutter-paint-volume.c
index 24ea8da..ef6c4b9 100644
--- a/clutter/clutter-paint-volume.c
+++ b/clutter/clutter-paint-volume.c
@@ -1088,14 +1088,12 @@ _clutter_paint_volume_cull (ClutterPaintVolume *pv,
 }
 
 void
-_clutter_paint_volume_get_stage_paint_box (ClutterPaintVolume *pv,
-                                           ClutterStage *stage,
-                                           ClutterActorBox *box)
+_clutter_paint_volume_get_camera_paint_box (ClutterPaintVolume *pv,
+                                            const ClutterCamera *camera,
+                                            ClutterActorBox *box)
 {
   ClutterPaintVolume projected_pv;
   CoglMatrix modelview;
-  CoglMatrix projection;
-  float viewport[4];
   float width;
   float height;
 
@@ -1105,20 +1103,18 @@ _clutter_paint_volume_get_stage_paint_box (ClutterPaintVolume *pv,
 
   /* If the paint volume isn't already in eye coordinates... */
   if (pv->actor)
-    _clutter_actor_apply_relative_transformation_matrix (pv->actor, NULL,
-                                                         &modelview);
-
-  _clutter_stage_get_projection_matrix (stage, &projection);
-  _clutter_stage_get_viewport (stage,
-                               &viewport[0],
-                               &viewport[1],
-                               &viewport[2],
-                               &viewport[3]);
+    {
+      ClutterActor *stage =
+        CLUTTER_ACTOR (_clutter_actor_get_stage_internal (pv->actor));
+      cogl_matrix_init_from_array (&modelview, (float *)&camera->view);
+      _clutter_actor_apply_relative_transformation_matrix (pv->actor, stage,
+                                                           &modelview);
+    }
 
   _clutter_paint_volume_project (&projected_pv,
                                  &modelview,
-                                 &projection,
-                                 viewport);
+                                 &camera->projection,
+                                 camera->viewport);
 
   _clutter_paint_volume_get_bounding_box (&projected_pv, box);
 
@@ -1180,13 +1176,38 @@ _clutter_paint_volume_transform_relative (ClutterPaintVolume *pv,
   actor = pv->actor;
 
   g_return_if_fail (actor != NULL);
+  g_return_if_fail (relative_to_ancestor != NULL);
 
   _clutter_paint_volume_set_reference_actor (pv, relative_to_ancestor);
 
   cogl_matrix_init_identity (&matrix);
   _clutter_actor_apply_relative_transformation_matrix (actor,
                                                        relative_to_ancestor,
-                                                      &matrix);
+                                                       &matrix);
 
   _clutter_paint_volume_transform (pv, &matrix);
 }
+
+void
+_clutter_paint_volume_transform_relative_to_camera (ClutterPaintVolume *pv,
+                                                    const ClutterCamera *camera)
+{
+  CoglMatrix modelview;
+  ClutterActor *stage;
+  ClutterActor *actor;
+
+  actor = pv->actor;
+
+  g_return_if_fail (actor != NULL);
+
+  stage = CLUTTER_ACTOR (_clutter_actor_get_stage_internal (actor));
+
+  _clutter_paint_volume_set_reference_actor (pv, NULL);
+
+  cogl_matrix_init_from_array (&modelview, (float *)&camera->view);
+  _clutter_actor_apply_relative_transformation_matrix (actor,
+                                                       CLUTTER_ACTOR (stage),
+                                                       &modelview);
+
+  _clutter_paint_volume_transform (pv, &modelview);
+}
diff --git a/clutter/clutter-private.h b/clutter/clutter-private.h
index e9d0bd2..98358ca 100644
--- a/clutter/clutter-private.h
+++ b/clutter/clutter-private.h
@@ -246,6 +246,32 @@ typedef enum _ClutterCullResult
   CLUTTER_CULL_RESULT_PARTIAL
 } ClutterCullResult;
 
+typedef struct _ClutterCamera
+{
+  /* Cameras are always maintained in a packed array associated with the stage
+   * and this is the index into that array. */
+  int       index;
+
+  /* TODO: It should be possible to associate cameras with CoglOffscreen
+   * framebuffers. Currently cameras are only used for stereoscopic rendering
+   * and it's assumed that all cameras are associated with the stage's
+   * ClutterStageWindow. */
+
+  CoglMatrix projection;
+  CoglMatrix inverse_projection;
+  float      viewport[4];
+  CoglMatrix view;
+
+  /* NB: This age is bumped when the viewport, view or projection change.
+   *
+   * For example clutter-actor.c can use this age to know if something cached
+   * in eye coordinates (which depends on a view transform) is still valid.
+   *
+   * NB: only do == or != comparisons with the age so wrapping should never be
+   * a problem. */
+  int        age;
+} ClutterCamera;
+
 G_END_DECLS
 
 #endif /* __CLUTTER_PRIVATE_H__ */
diff --git a/clutter/clutter-stage-private.h b/clutter/clutter-stage-private.h
index 9911f38..0946d08 100644
--- a/clutter/clutter-stage-private.h
+++ b/clutter/clutter-stage-private.h
@@ -42,7 +42,7 @@ void                _clutter_stage_set_window            (ClutterStage
 ClutterStageWindow *_clutter_stage_get_window            (ClutterStage          *stage);
 void                _clutter_stage_get_projection_matrix (ClutterStage          *stage,
                                                           CoglMatrix            *projection);
-void                _clutter_stage_dirty_projection      (ClutterStage          *stage);
+void                _clutter_stage_dirty_cogl_projection (ClutterStage          *stage);
 void                _clutter_stage_set_viewport          (ClutterStage          *stage,
                                                           float                  x,
                                                           float                  y,
@@ -53,9 +53,9 @@ void                _clutter_stage_get_viewport          (ClutterStage
                                                           float                 *y,
                                                           float                 *width,
                                                           float                 *height);
-void                _clutter_stage_dirty_viewport        (ClutterStage          *stage);
+void                _clutter_stage_dirty_cogl_viewport   (ClutterStage          *stage);
 void                _clutter_stage_maybe_setup_viewport  (ClutterStage          *stage);
-void                _clutter_stage_maybe_relayout        (ClutterActor          *stage);
+void                _clutter_stage_maybe_relayout        (ClutterStage          *stage);
 gboolean            _clutter_stage_needs_update          (ClutterStage          *stage);
 gboolean            _clutter_stage_do_update             (ClutterStage          *stage);
 
@@ -107,6 +107,14 @@ ClutterActor *  _clutter_stage_get_drag_actor           (ClutterStage       *sta
 void            _clutter_stage_remove_drag_actor        (ClutterStage       *stage,
                                                          ClutterInputDevice *device);
 
+const ClutterCamera  *_clutter_stage_get_camera         (ClutterStage        *stage,
+                                                         int                  camera_index);
+const ClutterCamera  *_clutter_stage_get_current_camera (ClutterStage        *stage);
+void                  _clutter_stage_set_current_camera (ClutterStage        *stage,
+                                                         const ClutterCamera *camera);
+int                   _clutter_stage_get_n_cameras      (ClutterStage        *stage);
+int                   _clutter_stage_get_cameras_age    (ClutterStage        *stage);
+
 G_END_DECLS
 
 #endif /* __CLUTTER_STAGE_PRIVATE_H__ */
diff --git a/clutter/clutter-stage-window.c b/clutter/clutter-stage-window.c
index 04cf786..d06c957 100644
--- a/clutter/clutter-stage-window.c
+++ b/clutter/clutter-stage-window.c
@@ -17,6 +17,21 @@ clutter_stage_window_default_init (ClutterStageWindowInterface *iface)
 {
 }
 
+gboolean
+_clutter_stage_window_has_feature (ClutterStageWindow       *window,
+                                   ClutterStageWindowFeature feature)
+{
+  ClutterStageWindowIface *iface;
+
+  g_return_val_if_fail (CLUTTER_IS_STAGE_WINDOW (window), FALSE);
+
+  iface = CLUTTER_STAGE_WINDOW_GET_IFACE (window);
+  if (iface->redraw_without_swap)
+    return iface->has_feature (window, feature);
+  else
+    return FALSE;
+}
+
 ClutterActor *
 _clutter_stage_window_get_wrapper (ClutterStageWindow *window)
 {
@@ -215,6 +230,30 @@ _clutter_stage_window_redraw (ClutterStageWindow *window)
     iface->redraw (window);
 }
 
+void
+_clutter_stage_window_redraw_without_swap (ClutterStageWindow *window)
+{
+  ClutterStageWindowIface *iface;
+
+  g_return_if_fail (CLUTTER_IS_STAGE_WINDOW (window));
+
+  iface = CLUTTER_STAGE_WINDOW_GET_IFACE (window);
+  if (iface->redraw_without_swap)
+    iface->redraw_without_swap (window);
+}
+
+void
+_clutter_stage_window_swap_buffers (ClutterStageWindow *window)
+{
+  ClutterStageWindowIface *iface;
+
+  g_return_if_fail (CLUTTER_IS_STAGE_WINDOW (window));
+
+  iface = CLUTTER_STAGE_WINDOW_GET_IFACE (window);
+  if (iface->swap_buffers)
+    iface->swap_buffers (window);
+}
+
 /* NB: The presumption shouldn't be that a stage can't be comprised of
  * multiple internal framebuffers, so instead of simply naming this
  * function _clutter_stage_window_get_framebuffer(), the "active"
diff --git a/clutter/clutter-stage-window.h b/clutter/clutter-stage-window.h
index de84e31..63a0c04 100644
--- a/clutter/clutter-stage-window.h
+++ b/clutter/clutter-stage-window.h
@@ -23,6 +23,11 @@ G_BEGIN_DECLS
 typedef struct _ClutterStageWindow      ClutterStageWindow; /* dummy */
 typedef struct _ClutterStageWindowIface ClutterStageWindowIface;
 
+typedef enum
+{
+  CLUTTER_STAGE_WINDOW_FEATURE_SWAP_BUFFERS
+} ClutterStageWindowFeature;
+
 /**
  * ClutterStageWindowIface: (skip)
  *
@@ -35,6 +40,9 @@ struct _ClutterStageWindowIface
   /*< private >*/
   GTypeInterface parent_iface;
 
+  gboolean          (* has_feature)             (ClutterStageWindow       *stage_window,
+                                                 ClutterStageWindowFeature feature);
+
   ClutterActor     *(* get_wrapper)             (ClutterStageWindow *stage_window);
 
   void              (* set_title)               (ClutterStageWindow *stage_window,
@@ -73,12 +81,17 @@ struct _ClutterStageWindowIface
                                                  gboolean            accept_focus);
 
   void              (* redraw)                  (ClutterStageWindow *stage_window);
+  void              (* redraw_without_swap)     (ClutterStageWindow *stage_window);
+  void              (* swap_buffers)            (ClutterStageWindow *stage_window);
 
   CoglFramebuffer  *(* get_active_framebuffer)  (ClutterStageWindow *stage_window);
 };
 
 GType clutter_stage_window_get_type (void) G_GNUC_CONST;
 
+gboolean          _clutter_stage_window_has_feature        (ClutterStageWindow       *window,
+                                                            ClutterStageWindowFeature feature);
+
 ClutterActor *    _clutter_stage_window_get_wrapper        (ClutterStageWindow *window);
 
 void              _clutter_stage_window_set_title          (ClutterStageWindow *window,
@@ -115,6 +128,8 @@ void              _clutter_stage_window_set_accept_focus        (ClutterStageWin
                                                            gboolean            accept_focus);
 
 void              _clutter_stage_window_redraw                  (ClutterStageWindow *window);
+void              _clutter_stage_window_redraw_without_swap     (ClutterStageWindow *window);
+void              _clutter_stage_window_swap_buffers            (ClutterStageWindow *window);
 
 CoglFramebuffer  *_clutter_stage_window_get_active_framebuffer  (ClutterStageWindow *window);
 
diff --git a/clutter/clutter-stage.c b/clutter/clutter-stage.c
index fe02984..fa58a32 100644
--- a/clutter/clutter-stage.c
+++ b/clutter/clutter-stage.c
@@ -82,6 +82,7 @@
 #include "cogl/cogl.h"
 
 #include <math.h>
+#include <stdlib.h>
 
 #ifdef USE_GDKPIXBUF
 #include <gdk-pixbuf/gdk-pixbuf.h>
@@ -125,11 +126,29 @@ struct _ClutterStagePrivate
   ClutterStageWindow *impl;
 
   ClutterColor        color;
-  ClutterPerspective  perspective;
-  CoglMatrix          projection;
-  CoglMatrix          inverse_projection;
-  CoglMatrix          view;
+
   float               viewport[4];
+  ClutterPerspective  perspective;
+
+  /* NB: When we start adding support for more cameras we'll need to ensure
+   * that we continue to maintain all valid cameras as a contiguous range of
+   * indices from 0 since we have various bits of code that expect to iterate
+   * through indices 0 to (n_cameras - 1) */
+  ClutterCamera       cameras[2];
+  int                 n_cameras;
+  /* NB: The age is bumped whenever cameras are added or removed. It
+   * isn't bumped when cameras are modified. To determine if an
+   * individual camera has been modified there is a per camera age.
+   *
+   * NB: only do == or != comparisons with the age so wrapping should never be
+   * a problem. */
+  int                 cameras_age;
+
+  const ClutterCamera *current_camera;
+  const ClutterCamera *last_flushed_camera;
+
+  ClutterStereoMode   stereo_mode;
+
   ClutterFog          fog;
 
   gchar              *title;
@@ -172,11 +191,15 @@ struct _ClutterStagePrivate
   guint use_alpha              : 1;
   guint min_size_changed       : 1;
   guint dirty_viewport         : 1;
+  guint dirty_cogl_viewport    : 1;
   guint dirty_projection       : 1;
+  guint dirty_cogl_projection  : 1;
+  guint dirty_view             : 1;
   guint have_valid_pick_buffer : 1;
   guint accept_focus           : 1;
   guint motion_events_enabled  : 1;
   guint has_custom_perspective : 1;
+  guint stereo_enabled         : 1;
 };
 
 enum
@@ -403,7 +426,7 @@ typedef struct _Vector4
 static void
 _cogl_util_get_eye_planes_for_screen_poly (float *polygon,
                                            int n_vertices,
-                                           float *viewport,
+                                           const float *viewport,
                                            const CoglMatrix *projection,
                                            const CoglMatrix *inverse_project,
                                            ClutterPlane *planes)
@@ -565,9 +588,9 @@ _clutter_stage_do_paint (ClutterStage *stage, const ClutterGeometry *clip)
 
   _cogl_util_get_eye_planes_for_screen_poly (clip_poly,
                                              4,
-                                             priv->viewport,
-                                             &priv->projection,
-                                             &priv->inverse_projection,
+                                             priv->current_camera->viewport,
+                                             &priv->current_camera->projection,
+                                             &priv->current_camera->inverse_projection,
                                              priv->current_clip_planes);
 
   _clutter_stage_paint_volume_stack_free_all (stage);
@@ -669,6 +692,7 @@ clutter_stage_realize (ClutterActor *self)
    */
   priv->dirty_viewport = TRUE;
   priv->dirty_projection = TRUE;
+  priv->dirty_view = TRUE;
 
   g_assert (priv->impl != NULL);
   is_realized = _clutter_stage_window_realize (priv->impl);
@@ -714,7 +738,7 @@ clutter_stage_show (ClutterActor *self)
 
   /* Possibly do an allocation run so that the stage will have the
      right size before we map it */
-  _clutter_stage_maybe_relayout (self);
+  _clutter_stage_maybe_relayout (CLUTTER_STAGE (self));
 
   g_assert (priv->impl != NULL);
   _clutter_stage_window_show (priv->impl, TRUE);
@@ -928,9 +952,8 @@ _clutter_stage_needs_update (ClutterStage *stage)
 }
 
 void
-_clutter_stage_maybe_relayout (ClutterActor *actor)
+_clutter_stage_maybe_relayout (ClutterStage *stage)
 {
-  ClutterStage *stage = CLUTTER_STAGE (actor);
   ClutterStagePrivate *priv = stage->priv;
   gfloat natural_width, natural_height;
   ClutterActorBox box = { 0, };
@@ -997,12 +1020,376 @@ _clutter_stage_set_pick_buffer_valid (ClutterStage   *stage,
   stage->priv->pick_buffer_mode = mode;
 }
 
+void
+_clutter_stage_set_current_camera (ClutterStage *stage,
+                                   const ClutterCamera *camera)
+{
+  ClutterStagePrivate *priv = stage->priv;
+
+  if (priv->current_camera == camera)
+    return;
+
+  priv->current_camera = camera;
+}
+
+/* This calculates a distance into the view frustum to position the
+ * stage so there is a decent amount of space to position geometry
+ * between the stage and the near clipping plane.
+ *
+ * Some awkward issues with this problem are:
+ * - It's not possible to have a gap as large as the stage size with
+ *   a fov > 53Â which is basically always the case since the default
+ *   fov is 60Â.
+ *    - This can be deduced if you consider that this requires a
+ *      triangle as wide as it is deep to fit in the frustum in front
+ *      of the z_near plane. That triangle will always have an angle
+ *      of 53.13Â at the point sitting on the z_near plane, but if the
+ *      frustum has a wider fov angle the left/right clipping planes
+ *      can never converge with the two corners of our triangle no
+ *      matter what size the triangle has.
+ * - With a fov > 53Â there is a trade off between maximizing the gap
+ *   size relative to the stage size but not loosing depth precision.
+ * - Perhaps ideally we wouldn't just consider the fov on the y-axis
+ *   that is usually used to define a perspective, we would consider
+ *   the fov of the axis with the largest stage size so the gap would
+ *   accommodate that size best.
+ *
+ * After going around in circles a few times with how to handle these
+ * issues, we decided in the end to go for the simplest solution to
+ * start with instead of an elaborate function that handles arbitrary
+ * fov angles that we currently have no use-case for.
+ *
+ * The solution assumes a fovy of 60Â and for that case gives a gap
+ * that's 85% of the stage height. We can consider more elaborate
+ * functions if necessary later.
+ *
+ * One guide we had to steer the gap size we support is the
+ * interactive test, test-texture-quality which expects to animate an
+ * actor to +400 on the z axis with a stage size of 640x480. A gap
+ * that's 85% of the stage height gives a gap of 408 in that case.
+ */
+static float
+calculate_z_translation (float z_near)
+{
+  /* This solution uses fairly basic trigonometry, but is seems worth
+   * clarifying the particular geometry we are looking at in-case
+   * anyone wants to develop this further later. Not sure how well an
+   * ascii diagram is going to work :-)
+   *
+   *    |--- stage_height ---|
+   *    |     stage line     |
+   *   âââââââââââââââââââââââ------------
+   *    â.  (2)   â        .â       |   |
+   *   C â .      â      . â     gap|   |
+   * =0.5Ââ  . a  â    .  â         |   |
+   *      bâ(1). Dâ  .   â          |   |
+   *        â   B.â.    ânear plane |   |
+   *      A= âââââââââââ-------------   |
+   *     120Â â c â   â  |            z_2d
+   *           â  â  â  z_near          |
+   *       left â â â    |              |
+   *       clip  60Âfovy |              |
+   *       plane  â----------------------
+   *              |
+   *              |
+   *         origin line
+   *
+   * The area of interest is the triangle labeled (1) at the top left
+   * marked with the ... line (a) from where the origin line crosses
+   * the near plane to the top left where the stage line cross the
+   * left clip plane.
+   *
+   * The sides of the triangle are a, b and c and the corresponding
+   * angles opposite those sides are A, B and C.
+   *
+   * The angle of C is what trades off the gap size we have relative
+   * to the stage size vs the depth precision we have.
+   *
+   * As mentioned above we arove at the angle for C is by working
+   * backwards from how much space we want for test-texture-quality.
+   * With a stage_height of 480 we want a gap > 400, ideally we also
+   * wanted a somewhat round number as a percentage of the height for
+   * documentation purposes. ~87% or a gap of ~416 is the limit
+   * because that's where we approach a C angle of 0Â and effectively
+   * loose all depth precision.
+   *
+   * So for our test app with a stage_height of 480 if we aim for a
+   * gap of 408 (85% of 480) we can get the angle D as
+   * atan (stage_height/2/408) = 30.5Â.
+   *
+   * That gives us the angle for B as 90Â - 30.5Â = 59.5Â
+   *
+   * We can already determine that A has an angle of (fovy/2 + 90Â) =
+   * 120Â
+   *
+   * Therefore C = 180 - A - B = 0.5Â
+   *
+   * The length of c = z_near * tan (30Â)
+   *
+   * Now we can use the rule a/SinA = c/SinC to calculate the
+   * length of a. After some rearranging that gives us:
+   *
+   *      a              c
+   *  ----------  =  ----------
+   *  sin (120Â)     sin (0.5Â)
+   *
+   *      c * sin (120Â)
+   *  a = --------------
+   *        sin (0.5Â)
+   *
+   * And with that we can determine z_2d = cos (D) * a =
+   * cos (30.5Â) * a + z_near:
+   *
+   *         c * sin (120Â) * cos (30.5Â)
+   *  z_2d = --------------------------- + z_near
+   *                 sin (0.5Â)
+   */
+#define _DEG_TO_RAD (G_PI / 180.0)
+  return z_near * tanf (30.0f * _DEG_TO_RAD) *
+         sinf (120.0f * _DEG_TO_RAD) * cosf (30.5f * _DEG_TO_RAD) /
+         sinf (0.5f * _DEG_TO_RAD) +
+         z_near;
+#undef _DEG_TO_RAD
+   /* We expect the compiler should boil this down to z_near * CONSTANT */
+}
+
+static void
+clutter_stage_set_perspective_internal (ClutterStage       *stage,
+                                        ClutterPerspective *perspective)
+{
+  ClutterStagePrivate *priv = stage->priv;
+
+  if (priv->perspective.fovy == perspective->fovy &&
+      priv->perspective.aspect == perspective->aspect &&
+      priv->perspective.z_near == perspective->z_near &&
+      priv->perspective.z_far == perspective->z_far)
+    return;
+
+  priv->perspective = *perspective;
+
+  priv->dirty_projection = TRUE;
+  clutter_actor_queue_redraw (CLUTTER_ACTOR (stage));
+}
+
+static void
+_clutter_stage_update_modelview_projection (ClutterStage *stage)
+{
+  ClutterStagePrivate *priv = stage->priv;
+
+  if (priv->dirty_viewport)
+    {
+      /* The Cogl viewport is implicitly dirty... */
+      priv->dirty_cogl_viewport = TRUE;
+
+      /* The view transform is based on the viewport width/height */
+      priv->dirty_view = TRUE;
+
+      if (priv->stereo_enabled)
+        {
+          priv->cameras[0].viewport[0] = priv->viewport[0];
+          priv->cameras[0].viewport[1] = priv->viewport[1];
+
+          if (priv->stereo_mode == CLUTTER_STEREO_MODE_VERTICAL_SPLIT)
+            {
+              priv->cameras[0].viewport[2] = priv->viewport[2] / 2;
+              priv->cameras[0].viewport[3] = priv->viewport[3];
+              priv->cameras[0].age++;
+
+              priv->cameras[1].viewport[0] =
+                (priv->viewport[0] / 2) + (priv->viewport[2] / 2);
+              priv->cameras[1].viewport[1] = priv->viewport[1];
+              priv->cameras[1].viewport[2] = priv->viewport[2] / 2;
+              priv->cameras[1].viewport[3] = priv->viewport[3];
+              priv->cameras[1].age++;
+            }
+          else if (priv->stereo_mode == CLUTTER_STEREO_MODE_HORIZONTAL_SPLIT)
+            {
+              priv->cameras[0].viewport[2] = priv->viewport[2];
+              priv->cameras[0].viewport[3] = priv->viewport[3] / 2;
+              priv->cameras[0].age++;
+
+              priv->cameras[1].viewport[0] = priv->viewport[0];
+              priv->cameras[1].viewport[1] =
+                (priv->viewport[1] / 2)  + (priv->viewport[3] / 2);
+              priv->cameras[1].viewport[2] = priv->viewport[2];
+              priv->cameras[1].viewport[3] = priv->viewport[3] / 2;
+              priv->cameras[1].age++;
+            }
+          else
+            {
+              memcpy (priv->cameras[0].viewport, priv->viewport,
+                      sizeof (float) * 4);
+              memcpy (priv->cameras[1].viewport, priv->viewport,
+                      sizeof (float) * 4);
+            }
+        }
+      else
+        {
+          memcpy (priv->cameras[0].viewport, priv->viewport,
+                  sizeof (float) * 4);
+          memcpy (priv->cameras[1].viewport, priv->viewport,
+                  sizeof (float) * 4);
+        }
+
+      priv->dirty_viewport = FALSE;
+    }
+
+  if (priv->dirty_view)
+    {
+      ClutterPerspective perspective;
+      float z_2d;
+
+      perspective = priv->perspective;
+
+      /* Ideally we want to regenerate the perspective matrix whenever
+       * the size changes but if the user has provided a custom matrix
+       * then we don't want to override it */
+      if (!priv->has_custom_perspective)
+        {
+          perspective.aspect = priv->viewport[2] / priv->viewport[3];
+          z_2d = calculate_z_translation (perspective.z_near);
+
+#define _DEG_TO_RAD (G_PI / 180.0)
+          /* NB: z_2d is only enough room for 85% of the stage_height between
+           * the stage and the z_near plane. For behind the stage plane we
+           * want a more consistent gap of 10 times the stage_height before
+           * hitting the far plane so we calculate that relative to the final
+           * height of the stage plane at the z_2d_distance we got... */
+          perspective.z_far = z_2d +
+            tanf ((perspective.fovy / 2.0f) * _DEG_TO_RAD) * z_2d * 20.0f;
+#undef _DEG_TO_RAD
+
+          clutter_stage_set_perspective_internal (stage, &perspective);
+        }
+      else
+        z_2d = calculate_z_translation (perspective.z_near);
+
+      cogl_matrix_init_identity (&priv->cameras[0].view);
+
+      /* For now we use the simple "toe-in" method for stereoscopic
+       * rendering whereby we simply offset two cameras to the left
+       * and to the right of the origin, both pointing diagonally into
+       * the center of our z_2d plane. A disadvantage of this approach
+       * is that straight lines on the z_2d plane will not be
+       * perceived as straight since the planes intersecting each
+       * camera frustum at the z_2d distance aren't equal. */
+      if (priv->stereo_enabled)
+        {
+          cogl_matrix_init_identity (&priv->cameras[1].view);
+
+#define EYE_OFFSET 0.1
+          /* FIXME: the 0.5 offsets are just hacky constants for now! */
+          cogl_matrix_look_at (&priv->cameras[0].view,
+                               -EYE_OFFSET, 0, 0,
+                               0, 0, -z_2d,
+                               0, 1, 0);
+          cogl_matrix_look_at (&priv->cameras[1].view,
+                               EYE_OFFSET, 0, 0,
+                               0, 0, -z_2d,
+                               0, 1, 0);
+        }
+
+      cogl_matrix_view_2d_in_perspective (&priv->cameras[0].view,
+                                          perspective.fovy,
+                                          perspective.aspect,
+                                          perspective.z_near,
+                                          z_2d,
+                                          priv->viewport[2],
+                                          priv->viewport[3]);
+      priv->cameras[0].age++;
+
+      if (priv->stereo_enabled)
+        {
+          cogl_matrix_view_2d_in_perspective (&priv->cameras[1].view,
+                                              perspective.fovy,
+                                              perspective.aspect,
+                                              perspective.z_near,
+                                              z_2d,
+                                              priv->viewport[2],
+                                              priv->viewport[3]);
+          priv->cameras[1].age++;
+        }
+
+      priv->dirty_view = FALSE;
+    }
+
+  /* XXX: This must be done after checking for a dirty view since updates to
+   * the view might also result in a corresponding update to the projection */
+  if (priv->dirty_projection)
+    {
+      /* The Cogl projection is implicitly dirty... */
+      priv->dirty_cogl_projection = TRUE;
+
+      cogl_matrix_init_identity (&priv->cameras[0].projection);
+      cogl_matrix_perspective (&priv->cameras[0].projection,
+                               priv->perspective.fovy,
+                               priv->perspective.aspect,
+                               priv->perspective.z_near,
+                               priv->perspective.z_far);
+      cogl_matrix_get_inverse (&priv->cameras[0].projection,
+                               &priv->cameras[0].inverse_projection);
+      priv->cameras[0].age++;
+
+      /* NB: For now, we simply use the toe in approach for handling
+       * stereoscopic rendering and so both eyes always have the same
+       * projection matrix. */
+      if (priv->stereo_enabled)
+        {
+          cogl_matrix_init_from_array (&priv->cameras[1].projection,
+                                       (float *)&priv->cameras[0].projection);
+          cogl_matrix_init_from_array (&priv->cameras[1].inverse_projection,
+                                       (float *)
+                                       &priv->cameras[0].inverse_projection);
+          priv->cameras[1].age++;
+        }
+
+      priv->dirty_projection = FALSE;
+    }
+}
+
+static void
+_clutter_stage_flush_modelview_projection (ClutterStage *stage)
+{
+  ClutterStagePrivate *priv = stage->priv;
+
+  g_return_if_fail (priv->current_camera != NULL);
+
+  if (priv->last_flushed_camera != priv->current_camera)
+    {
+      priv->dirty_cogl_viewport = TRUE;
+      priv->dirty_cogl_projection = TRUE;
+    }
+  priv->last_flushed_camera = priv->current_camera;
+
+  if (priv->dirty_cogl_viewport)
+    {
+      CLUTTER_NOTE (PAINT,
+                    "Setting up the viewport { w:%f, h:%f }",
+                    priv->current_camera->viewport[2], priv->current_camera->viewport[3]);
+      cogl_set_viewport (priv->current_camera->viewport[0],
+                         priv->current_camera->viewport[1],
+                         priv->current_camera->viewport[2],
+                         priv->current_camera->viewport[3]);
+
+      priv->dirty_cogl_viewport = FALSE;
+    }
+
+  if (priv->dirty_cogl_projection)
+    {
+      cogl_set_projection_matrix ((CoglMatrix *)
+                                  &priv->current_camera->projection);
+
+      priv->dirty_cogl_projection = FALSE;
+    }
+}
+
 static void
 clutter_stage_do_redraw (ClutterStage *stage)
 {
   ClutterBackend *backend = clutter_get_default_backend ();
   ClutterActor *actor = CLUTTER_ACTOR (stage);
   ClutterStagePrivate *priv = stage->priv;
+  CoglFramebuffer *fb;
 
   CLUTTER_TIMESTAMP (SCHEDULER, "Redraw started for %s[%p]",
                      _clutter_actor_get_debug_name (actor),
@@ -1019,9 +1406,57 @@ clutter_stage_do_redraw (ClutterStage *stage)
         priv->fps_timer = g_timer_new ();
     }
 
-  _clutter_stage_maybe_setup_viewport (stage);
+  _clutter_stage_update_modelview_projection (stage);
 
-  _clutter_backend_redraw (backend, stage);
+  if (priv->stereo_enabled)
+    {
+      gboolean anaglyph =
+        (priv->stereo_mode == CLUTTER_STEREO_MODE_DEFAULT) ||
+        (priv->stereo_mode == CLUTTER_STEREO_MODE_ANAGLYPH);
+      float viewport[4];
+
+      _clutter_stage_set_current_camera (stage, &priv->cameras[0]);
+      _clutter_stage_flush_modelview_projection (stage);
+
+      fb = cogl_get_draw_framebuffer ();
+
+      if (anaglyph)
+        cogl_framebuffer_set_color_mask (fb, COGL_COLOR_MASK_RED);
+
+      _clutter_stage_window_redraw_without_swap (priv->impl);
+
+      _clutter_stage_set_current_camera (stage, &priv->cameras[1]);
+      _clutter_stage_flush_modelview_projection (stage);
+      if (anaglyph)
+        cogl_framebuffer_set_color_mask (fb,
+                                         COGL_COLOR_MASK_GREEN |
+                                         COGL_COLOR_MASK_BLUE);
+      else
+        {
+          /* XXX: For now we scissor the right-eye render so that the stage
+           * clear doesn't clobber the rendering of the left-eye, but later it
+           * might be better to instead add a mechanism for painting the stage
+           * except without clearing the color buffer. */
+          cogl_framebuffer_get_viewport4fv (fb, viewport);
+          cogl_clip_push_window_rectangle (viewport[0], viewport[1],
+                                           viewport[2], viewport[3]);
+        }
+
+      _clutter_backend_redraw (backend, stage);
+
+      if (anaglyph)
+        cogl_framebuffer_set_color_mask (fb, COGL_COLOR_MASK_ALL);
+      else
+        cogl_clip_pop ();
+    }
+  else
+    {
+      _clutter_stage_set_current_camera (stage, &priv->cameras[0]);
+      _clutter_stage_flush_modelview_projection (stage);
+      _clutter_backend_redraw (backend, stage);
+    }
+
+  _clutter_stage_set_current_camera (stage, NULL);
 
   if (clutter_get_show_fps ())
     {
@@ -1070,7 +1505,7 @@ _clutter_stage_do_update (ClutterStage *stage)
    * check or clear the pending redraws flag since a relayout may
    * queue a redraw.
    */
-  _clutter_stage_maybe_relayout (CLUTTER_ACTOR (stage));
+  _clutter_stage_maybe_relayout (stage);
 
   if (!priv->redraw_pending)
     return FALSE;
@@ -1117,6 +1552,8 @@ clutter_stage_real_queue_redraw (ClutterActor *actor,
   ClutterStageWindow *stage_window;
   ClutterGeometry stage_clip;
   ClutterPaintVolume *redraw_clip;
+  int camera_index;
+  ClutterCamera *camera;
   ClutterActorBox bounding_box;
 
   if (CLUTTER_ACTOR_IN_DESTRUCTION (actor))
@@ -1147,17 +1584,25 @@ clutter_stage_real_queue_redraw (ClutterActor *actor,
 
   redraw_clip = _clutter_actor_get_queue_redraw_clip (leaf);
 
-  _clutter_paint_volume_get_stage_paint_box (redraw_clip,
-                                             stage,
-                                             &bounding_box);
+  camera_index = _clutter_actor_get_queue_redraw_camera_index (leaf);
+  camera = &stage->priv->cameras[camera_index];
+
+  _clutter_paint_volume_get_camera_paint_box (redraw_clip,
+                                              camera, &bounding_box);
 
-  /* when converting to integer coordinates make sure we round the edges of the
-   * clip rectangle outwards... */
+  /* 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;
+  /* XXX: This doesn't seem right - it seems like this will
+   * potentially round down to be smaller than it needs to
+   * beâ */
   stage_clip.width = bounding_box.x2 - stage_clip.x;
   stage_clip.height = bounding_box.y2 - stage_clip.y;
 
+  /* XXX: When we add support for cameras associated with
+   * CoglOffscreen framebuffers then we shouldn't assume that the
+   * backend needs to be notified of redraw clips. */
   _clutter_stage_window_add_redraw_clip (stage_window, &stage_clip);
 }
 
@@ -1342,8 +1787,13 @@ _clutter_stage_do_pick (ClutterStage   *stage,
 
   _clutter_backend_ensure_context (context->backend, stage);
 
+  /* Note: If stereoscopic rendering is enabled then we will currently
+   * just perform picking according to the left eye... */
+  _clutter_stage_set_current_camera (stage, &priv->cameras[0]);
+
   /* needed for when a context switch happens */
-  _clutter_stage_maybe_setup_viewport (stage);
+  _clutter_stage_update_modelview_projection (stage);
+  _clutter_stage_flush_modelview_projection (stage);
 
   /* If we are seeing multiple picks per frame that means the scene is static
    * so we promote to doing a non-scissored pick render so that all subsequent
@@ -1383,6 +1833,8 @@ _clutter_stage_do_pick (ClutterStage   *stage,
   context->pick_mode = CLUTTER_PICK_NONE;
   CLUTTER_TIMER_STOP (_clutter_uprof_context, pick_paint);
 
+  _clutter_stage_set_current_camera (stage, NULL);
+
   if (is_clipped)
     {
       if (G_LIKELY (!(clutter_pick_debug_flags &
@@ -1469,7 +1921,7 @@ clutter_stage_real_apply_transform (ClutterActor *stage,
   /* FIXME: we probably shouldn't be explicitly reseting the matrix
    * here... */
   cogl_matrix_init_identity (matrix);
-  cogl_matrix_multiply (matrix, matrix, &priv->view);
+  cogl_matrix_multiply (matrix, matrix, &priv->current_camera->view);
 }
 
 static void
@@ -2012,6 +2464,17 @@ clutter_stage_init (ClutterStage *self)
   ClutterStagePrivate *priv;
   ClutterBackend *backend;
   ClutterGeometry geom;
+  char *stereo_mode;
+  struct {
+      const char *name;
+      ClutterStereoMode mode;
+  } stereo_modes[] = {
+        { "default", CLUTTER_STEREO_MODE_DEFAULT },
+        { "anaglyph", CLUTTER_STEREO_MODE_ANAGLYPH },
+        { "vertical-split", CLUTTER_STEREO_MODE_VERTICAL_SPLIT },
+        { "horizontal-split", CLUTTER_STEREO_MODE_HORIZONTAL_SPLIT },
+        { 0 }
+  };
 
   /* a stage is a top-level object */
   CLUTTER_SET_PRIVATE_FLAGS (self, CLUTTER_IS_TOPLEVEL);
@@ -2031,6 +2494,32 @@ clutter_stage_init (ClutterStage *self)
       g_assert (priv->impl != NULL);
     }
 
+  stereo_mode = getenv ("CLUTTER_STEREO_MODE");
+  if (stereo_mode)
+    {
+      int i;
+      priv->stereo_enabled = TRUE;
+      priv->stereo_mode = CLUTTER_STEREO_MODE_DEFAULT;
+      for (i = 0; stereo_modes[i].name; i++)
+        {
+          if (strcmp (stereo_modes[i].name, stereo_mode) == 0)
+            priv->stereo_mode = stereo_modes[i].mode;
+        }
+    }
+  else
+    {
+      priv->stereo_enabled = FALSE;
+      priv->stereo_mode = CLUTTER_STEREO_MODE_DEFAULT;
+    }
+
+  priv->n_cameras = priv->stereo_enabled ? 2 : 1;
+  priv->cameras_age = 0;
+
+  priv->cameras[0].index = 0;
+  priv->cameras[1].index = 1;
+
+  priv->current_camera = NULL;
+
   priv->event_queue = g_queue_new ();
 
   priv->is_fullscreen          = FALSE;
@@ -2057,16 +2546,22 @@ clutter_stage_init (ClutterStage *self)
   priv->perspective.z_near = 0.1;
   priv->perspective.z_far  = 100.0;
 
-  cogl_matrix_init_identity (&priv->projection);
-  cogl_matrix_perspective (&priv->projection,
+  /* Ideally we would set the cameras up lazily when we first paint,
+   * but we have to consider for example that using
+   * clutter_texture_new_from_actor results in clutter trying to
+   * calculate the paint-box of the source-actor outside of the paint
+   * cycle and so it gets upset if we don't initialize the left-eye
+   * camera to something reasonable early... */
+  cogl_matrix_init_identity (&priv->cameras[0].projection);
+  cogl_matrix_perspective (&priv->cameras[0].projection,
                            priv->perspective.fovy,
                            priv->perspective.aspect,
                            priv->perspective.z_near,
                            priv->perspective.z_far);
-  cogl_matrix_get_inverse (&priv->projection,
-                           &priv->inverse_projection);
-  cogl_matrix_init_identity (&priv->view);
-  cogl_matrix_view_2d_in_perspective (&priv->view,
+  cogl_matrix_get_inverse (&priv->cameras[0].projection,
+                           &priv->cameras[0].inverse_projection);
+  cogl_matrix_init_identity (&priv->cameras[0].view);
+  cogl_matrix_view_2d_in_perspective (&priv->cameras[0].view,
                                       priv->perspective.fovy,
                                       priv->perspective.aspect,
                                       priv->perspective.z_near,
@@ -2074,6 +2569,9 @@ clutter_stage_init (ClutterStage *self)
                                       geom.width,
                                       geom.height);
 
+  priv->dirty_viewport = TRUE;
+  priv->dirty_projection = TRUE;
+  priv->dirty_view = TRUE;
 
   /* depth cueing */
   priv->fog.z_near = 1.0;
@@ -2174,43 +2672,16 @@ clutter_stage_set_color (ClutterStage       *stage,
  */
 void
 clutter_stage_get_color (ClutterStage *stage,
-			 ClutterColor *color)
-{
-  ClutterStagePrivate *priv;
-
-  g_return_if_fail (CLUTTER_IS_STAGE (stage));
-  g_return_if_fail (color != NULL);
-
-  priv = stage->priv;
-
-  *color = priv->color;
-}
-
-static void
-clutter_stage_set_perspective_internal (ClutterStage       *stage,
-                                        ClutterPerspective *perspective)
-{
-  ClutterStagePrivate *priv = stage->priv;
-
-  if (priv->perspective.fovy == perspective->fovy &&
-      priv->perspective.aspect == perspective->aspect &&
-      priv->perspective.z_near == perspective->z_near &&
-      priv->perspective.z_far == perspective->z_far)
-    return;
+			 ClutterColor *color)
+{
+  ClutterStagePrivate *priv;
 
-  priv->perspective = *perspective;
+  g_return_if_fail (CLUTTER_IS_STAGE (stage));
+  g_return_if_fail (color != NULL);
 
-  cogl_matrix_init_identity (&priv->projection);
-  cogl_matrix_perspective (&priv->projection,
-                           priv->perspective.fovy,
-                           priv->perspective.aspect,
-                           priv->perspective.z_near,
-                           priv->perspective.z_far);
-  cogl_matrix_get_inverse (&priv->projection,
-                           &priv->inverse_projection);
+  priv = stage->priv;
 
-  priv->dirty_projection = TRUE;
-  clutter_actor_queue_redraw (CLUTTER_ACTOR (stage));
+  *color = priv->color;
 }
 
 /**
@@ -2270,7 +2741,10 @@ clutter_stage_get_perspective (ClutterStage       *stage,
  * Retrieves the @stage's projection matrix. This is derived from the
  * current perspective set using clutter_stage_set_perspective().
  *
- * Since: 1.6
+ * XXX: it's not clear a.t.m what the right way to make something like
+ * this public would be, considering that the stage may be associated
+ * with multiple cameras. The limited internal use-cases we have
+ * currently will work if we just return the current camera.
  */
 void
 _clutter_stage_get_projection_matrix (ClutterStage *stage,
@@ -2279,7 +2753,7 @@ _clutter_stage_get_projection_matrix (ClutterStage *stage,
   g_return_if_fail (CLUTTER_IS_STAGE (stage));
   g_return_if_fail (projection != NULL);
 
-  *projection = stage->priv->projection;
+  *projection = stage->priv->current_camera->projection;
 }
 
 /* This simply provides a simple mechanism for us to ensure that
@@ -2287,9 +2761,9 @@ _clutter_stage_get_projection_matrix (ClutterStage *stage,
  *
  * This is used when switching between multiple stages */
 void
-_clutter_stage_dirty_projection (ClutterStage *stage)
+_clutter_stage_dirty_cogl_projection (ClutterStage *stage)
 {
-  stage->priv->dirty_projection = TRUE;
+  stage->priv->dirty_cogl_projection = TRUE;
 }
 
 /*
@@ -2342,7 +2816,9 @@ _clutter_stage_set_viewport (ClutterStage *stage,
 
   priv = stage->priv;
 
-
+  /* NB: in stereo mode we don't have to worry about checking against
+   * the second camera too since we can assume both cameras have the
+   * same viewport */
   if (x == priv->viewport[0] &&
       y == priv->viewport[1] &&
       width == priv->viewport[2] &&
@@ -2354,6 +2830,8 @@ _clutter_stage_set_viewport (ClutterStage *stage,
   priv->viewport[2] = width;
   priv->viewport[3] = height;
 
+  /* NB: we update the actual camera viewports lazily during
+   * _clutter_stage_update_modelview_projection () */
   priv->dirty_viewport = TRUE;
 
   queue_full_redraw (stage);
@@ -2364,9 +2842,9 @@ _clutter_stage_set_viewport (ClutterStage *stage,
  *
  * This is used when switching between multiple stages */
 void
-_clutter_stage_dirty_viewport (ClutterStage *stage)
+_clutter_stage_dirty_cogl_viewport (ClutterStage *stage)
 {
-  stage->priv->dirty_viewport = TRUE;
+  stage->priv->dirty_cogl_viewport = TRUE;
 }
 
 /*
@@ -2628,15 +3106,42 @@ clutter_stage_read_pixels (ClutterStage *stage,
                            gint          width,
                            gint          height)
 {
+  ClutterStagePrivate *priv;
+  gboolean set_camera = FALSE;
   ClutterGeometry geom;
   guchar *pixels;
 
   g_return_val_if_fail (CLUTTER_IS_STAGE (stage), NULL);
 
+  priv = stage->priv;
+
   /* Force a redraw of the stage before reading back pixels */
   clutter_stage_ensure_current (stage);
+
+  /* XXX: Ideally we'd just assert that priv->current_camera is NULL
+   * but I think there is already code in the wild that expects to be
+   * able to issue a read_pixels request mid-scene, so even though
+   * that's quite likely to break anyway we try not to make it
+   * any more likely than before.
+   *
+   * If a read_pixels request is made mid-scene then we will already
+   * have a current_camera which we don't want to disrupt but
+   * otherwise we make the left_eye camera current before issuing
+   * the stage paint.
+   */
+  if (!priv->current_camera)
+    {
+      _clutter_stage_update_modelview_projection (stage);
+      _clutter_stage_set_current_camera (stage, &priv->cameras[0]);
+      _clutter_stage_flush_modelview_projection (stage);
+      set_camera = TRUE;
+    }
+
   clutter_actor_paint (CLUTTER_ACTOR (stage));
 
+  if (set_camera)
+    _clutter_stage_set_current_camera (stage, NULL);
+
   clutter_actor_get_allocation_geometry (CLUTTER_ACTOR (stage), &geom);
 
   if (width < 0)
@@ -3176,194 +3681,17 @@ clutter_stage_ensure_viewport (ClutterStage *stage)
 {
   g_return_if_fail (CLUTTER_IS_STAGE (stage));
 
-  _clutter_stage_dirty_viewport (stage);
+  _clutter_stage_dirty_cogl_viewport (stage);
 
   clutter_actor_queue_redraw (CLUTTER_ACTOR (stage));
 }
 
-/* This calculates a distance into the view frustum to position the
- * stage so there is a decent amount of space to position geometry
- * between the stage and the near clipping plane.
- *
- * Some awkward issues with this problem are:
- * - It's not possible to have a gap as large as the stage size with
- *   a fov > 53Â which is basically always the case since the default
- *   fov is 60Â.
- *    - This can be deduced if you consider that this requires a
- *      triangle as wide as it is deep to fit in the frustum in front
- *      of the z_near plane. That triangle will always have an angle
- *      of 53.13Â at the point sitting on the z_near plane, but if the
- *      frustum has a wider fov angle the left/right clipping planes
- *      can never converge with the two corners of our triangle no
- *      matter what size the triangle has.
- * - With a fov > 53Â there is a trade off between maximizing the gap
- *   size relative to the stage size but not loosing depth precision.
- * - Perhaps ideally we wouldn't just consider the fov on the y-axis
- *   that is usually used to define a perspective, we would consider
- *   the fov of the axis with the largest stage size so the gap would
- *   accommodate that size best.
- *
- * After going around in circles a few times with how to handle these
- * issues, we decided in the end to go for the simplest solution to
- * start with instead of an elaborate function that handles arbitrary
- * fov angles that we currently have no use-case for.
- *
- * The solution assumes a fovy of 60Â and for that case gives a gap
- * that's 85% of the stage height. We can consider more elaborate
- * functions if necessary later.
- *
- * One guide we had to steer the gap size we support is the
- * interactive test, test-texture-quality which expects to animate an
- * actor to +400 on the z axis with a stage size of 640x480. A gap
- * that's 85% of the stage height gives a gap of 408 in that case.
- */
-static float
-calculate_z_translation (float z_near)
-{
-  /* This solution uses fairly basic trigonometry, but is seems worth
-   * clarifying the particular geometry we are looking at in-case
-   * anyone wants to develop this further later. Not sure how well an
-   * ascii diagram is going to work :-)
-   *
-   *    |--- stage_height ---|
-   *    |     stage line     |
-   *   âââââââââââââââââââââââ------------
-   *    â.  (2)   â        .â       |   |
-   *   C â .      â      . â     gap|   |
-   * =0.5Ââ  . a  â    .  â         |   |
-   *      bâ(1). Dâ  .   â          |   |
-   *        â   B.â.    ânear plane |   |
-   *      A= âââââââââââ-------------   |
-   *     120Â â c â   â  |            z_2d
-   *           â  â  â  z_near          |
-   *       left â â â    |              |
-   *       clip  60Âfovy |              |
-   *       plane  â----------------------
-   *              |
-   *              |
-   *         origin line
-   *
-   * The area of interest is the triangle labeled (1) at the top left
-   * marked with the ... line (a) from where the origin line crosses
-   * the near plane to the top left where the stage line cross the
-   * left clip plane.
-   *
-   * The sides of the triangle are a, b and c and the corresponding
-   * angles opposite those sides are A, B and C.
-   *
-   * The angle of C is what trades off the gap size we have relative
-   * to the stage size vs the depth precision we have.
-   *
-   * As mentioned above we arove at the angle for C is by working
-   * backwards from how much space we want for test-texture-quality.
-   * With a stage_height of 480 we want a gap > 400, ideally we also
-   * wanted a somewhat round number as a percentage of the height for
-   * documentation purposes. ~87% or a gap of ~416 is the limit
-   * because that's where we approach a C angle of 0Â and effectively
-   * loose all depth precision.
-   *
-   * So for our test app with a stage_height of 480 if we aim for a
-   * gap of 408 (85% of 480) we can get the angle D as
-   * atan (stage_height/2/408) = 30.5Â.
-   *
-   * That gives us the angle for B as 90Â - 30.5Â = 59.5Â
-   *
-   * We can already determine that A has an angle of (fovy/2 + 90Â) =
-   * 120Â
-   *
-   * Therefore C = 180 - A - B = 0.5Â
-   *
-   * The length of c = z_near * tan (30Â)
-   *
-   * Now we can use the rule a/SinA = c/SinC to calculate the
-   * length of a. After some rearranging that gives us:
-   *
-   *      a              c
-   *  ----------  =  ----------
-   *  sin (120Â)     sin (0.5Â)
-   *
-   *      c * sin (120Â)
-   *  a = --------------
-   *        sin (0.5Â)
-   *
-   * And with that we can determine z_2d = cos (D) * a =
-   * cos (30.5Â) * a + z_near:
-   *
-   *         c * sin (120Â) * cos (30.5Â)
-   *  z_2d = --------------------------- + z_near
-   *                 sin (0.5Â)
-   */
-#define _DEG_TO_RAD (G_PI / 180.0)
-  return z_near * tanf (30.0f * _DEG_TO_RAD) *
-         sinf (120.0f * _DEG_TO_RAD) * cosf (30.5f * _DEG_TO_RAD) /
-         sinf (0.5f * _DEG_TO_RAD) +
-         z_near;
-#undef _DEG_TO_RAD
-   /* We expect the compiler should boil this down to z_near * CONSTANT */
-}
-
+/* TODO: remove this badly named API */
 void
 _clutter_stage_maybe_setup_viewport (ClutterStage *stage)
 {
-  ClutterStagePrivate *priv = stage->priv;
-
-  if (priv->dirty_viewport)
-    {
-      ClutterPerspective perspective;
-      float z_2d;
-
-      CLUTTER_NOTE (PAINT,
-                    "Setting up the viewport { w:%f, h:%f }",
-                    priv->viewport[2], priv->viewport[3]);
-
-      cogl_set_viewport (priv->viewport[0],
-                         priv->viewport[1],
-                         priv->viewport[2],
-                         priv->viewport[3]);
-
-      perspective = priv->perspective;
-
-      /* Ideally we want to regenerate the perspective matrix whenever
-       * the size changes but if the user has provided a custom matrix
-       * then we don't want to override it */
-      if (!priv->has_custom_perspective)
-        {
-          perspective.aspect = priv->viewport[2] / priv->viewport[3];
-          z_2d = calculate_z_translation (perspective.z_near);
-
-#define _DEG_TO_RAD (G_PI / 180.0)
-          /* NB: z_2d is only enough room for 85% of the stage_height between
-           * the stage and the z_near plane. For behind the stage plane we
-           * want a more consistent gap of 10 times the stage_height before
-           * hitting the far plane so we calculate that relative to the final
-           * height of the stage plane at the z_2d_distance we got... */
-          perspective.z_far = z_2d +
-            tanf ((perspective.fovy / 2.0f) * _DEG_TO_RAD) * z_2d * 20.0f;
-#undef _DEG_TO_RAD
-
-          clutter_stage_set_perspective_internal (stage, &perspective);
-        }
-      else
-        z_2d = calculate_z_translation (perspective.z_near);
-
-      cogl_matrix_init_identity (&priv->view);
-      cogl_matrix_view_2d_in_perspective (&priv->view,
-                                          perspective.fovy,
-                                          perspective.aspect,
-                                          perspective.z_near,
-                                          z_2d,
-                                          priv->viewport[2],
-                                          priv->viewport[3]);
-
-      priv->dirty_viewport = FALSE;
-    }
-
-  if (priv->dirty_projection)
-    {
-      cogl_set_projection_matrix (&priv->projection);
-
-      priv->dirty_projection = FALSE;
-    }
+  _clutter_stage_update_modelview_projection (stage);
+  _clutter_stage_flush_modelview_projection (stage);
 }
 
 /**
@@ -4181,3 +4509,111 @@ _clutter_stage_remove_drag_actor (ClutterStage       *stage,
   if (g_hash_table_size (drag_actors) == 0)
     g_object_set_data (G_OBJECT (stage), "__clutter_stage_drag_actors", NULL);
 }
+
+/**
+ * clutter_stage_set_stereo_enabled:
+ * @stage: A #ClutterStage
+ * @enabled: Whether stereoscopic rendering should be enabled or not
+ *
+ * Enables stereoscopic rendering of the stage if @enabled = %TRUE or
+ * disables stereoscopic rendering if enabled = %FALSE;
+ *
+ * Since: 1.8
+ */
+void
+clutter_stage_set_stereo_enabled (ClutterStage *stage,
+                                  gboolean enabled)
+{
+  ClutterStagePrivate *priv;
+
+  g_return_if_fail (CLUTTER_IS_STAGE (stage));
+
+  priv = stage->priv;
+
+  if (priv->stereo_enabled == enabled)
+    return;
+
+  /* Currently we only support anaglyph based stereoscopic rendering
+   * and for that we compose each frame by asking the backend to
+   * redraw the scene from two eye positions per frame but that
+   * also means we don't want the backend to automatically present
+   * the frame until we have drawn for both eyes so we need
+   * the swap-buffers feature to enable stereo rendering... */
+  if (enabled &&
+      !_clutter_stage_window_has_feature (stage->priv->impl,
+                                          CLUTTER_STAGE_WINDOW_FEATURE_SWAP_BUFFERS))
+    return;
+
+  priv->stereo_enabled = enabled;
+
+  priv->n_cameras = priv->stereo_enabled ? 2 : 1;
+
+  priv->dirty_viewport = TRUE;
+  priv->dirty_projection = TRUE;
+  priv->dirty_view = TRUE;
+  priv->cameras_age++;
+}
+
+/**
+ * clutter_stage_set_stereo_mode:
+ * @stage: A #ClutterStage
+ * @mode: A #ClutterStereoMode selecting the mode of stereoscopic
+ *        output.
+ *
+ * Changes the mode of outputing stereoscopic content. The default
+ * mode will take advantage of any platform specific support for
+ * stereoscopic output but there are also some platform independent
+ * modes including anaglyph rendering for use with filter glasses with
+ * a red filter for the left eye and a cyan filter for the right, it's
+ * also possible to split the stage horizontally or vertically showing
+ * the left and right eye content on opposite sides of the stage. This
+ * can be used with a lot of 3D TVs.
+ *
+ * Since: 1.8
+ */
+void
+clutter_stage_set_stereo_mode (ClutterStage     *stage,
+                               ClutterStereoMode mode)
+{
+  ClutterStagePrivate *priv;
+
+  g_return_if_fail (CLUTTER_IS_STAGE (stage));
+
+  priv = stage->priv;
+
+  if (priv->stereo_mode == mode)
+    return;
+
+  priv->stereo_mode = mode;
+
+  if (priv->stereo_enabled)
+    {
+      priv->dirty_viewport = TRUE;
+      priv->dirty_projection = TRUE;
+      priv->dirty_view = TRUE;
+    }
+}
+
+const ClutterCamera *
+_clutter_stage_get_current_camera (ClutterStage *stage)
+{
+  return stage->priv->current_camera;
+}
+
+const ClutterCamera *
+_clutter_stage_get_camera (ClutterStage *stage, int camera_index)
+{
+  return &stage->priv->cameras[camera_index];
+}
+
+int
+_clutter_stage_get_n_cameras (ClutterStage *stage)
+{
+  return stage->priv->n_cameras;
+}
+
+int
+_clutter_stage_get_cameras_age (ClutterStage *stage)
+{
+  return stage->priv->cameras_age;
+}
diff --git a/clutter/clutter-stage.h b/clutter/clutter-stage.h
index cdd7797..8ee806c 100644
--- a/clutter/clutter-stage.h
+++ b/clutter/clutter-stage.h
@@ -88,6 +88,30 @@ typedef enum {
   CLUTTER_PICK_ALL
 } ClutterPickMode;
 
+/**
+ * ClutterStereoMode:
+ * @CLUTTER_STEREO_MODE_DEFAULT: Use the platform's default stereoscopic mode
+ * @CLUTTER_STEREO_MODE_ANAGLYPH: Use anaglyph based stereoscopic rendering; assuming
+ *    a red filter for the left eye and a cyan filter for the right eye.
+ * @CLUTTER_STEREO_MODE_VERTICAL_SPLIT: Split the scene vertically and show the left
+ *    eye contents on the left and the right eye contents on the right
+ * @CLUTTER_STEREO_MODE_HORIZONTAL_SPLIT: Split the scene horizontally and show the left
+ *    eye contents on the top eye contents on the bottom.
+ *
+ * Defines the mode of outputing stereoscopic content to the user.
+ * %CLUTTER_STEREO_MODE_DEFAULT should just do the right thing for any platform that
+ * has native support for stereoscopic hardware, but for platforms without native
+ * stereoscopic support there are several fallback methods including anaglpyh for
+ * using filter glasses and split screen, which many 3D TVs can handle.
+ */
+typedef enum
+{
+  CLUTTER_STEREO_MODE_DEFAULT,
+  CLUTTER_STEREO_MODE_ANAGLYPH,
+  CLUTTER_STEREO_MODE_VERTICAL_SPLIT,
+  CLUTTER_STEREO_MODE_HORIZONTAL_SPLIT
+} ClutterStereoMode;
+
 typedef struct _ClutterPerspective  ClutterPerspective;
 typedef struct _ClutterFog          ClutterFog;
 
@@ -271,6 +295,11 @@ void                  clutter_stage_set_motion_events_enabled (ClutterStage *sta
                                                                gboolean      enabled);
 gboolean              clutter_stage_get_motion_events_enabled (ClutterStage *stage);
 
+void                  clutter_stage_set_stereo_enabled        (ClutterStage     *stage,
+                                                               gboolean          enabled);
+void                  clutter_stage_set_stereo_mode           (ClutterStage     *stage,
+                                                               ClutterStereoMode  mode);
+
 #ifndef CLUTTER_DISABLE_DEPRECATED
 
 /* Commodity macro, for mallum only */
diff --git a/clutter/clutter-texture.c b/clutter/clutter-texture.c
index ff602f7..52d240f 100644
--- a/clutter/clutter-texture.c
+++ b/clutter/clutter-texture.c
@@ -530,9 +530,11 @@ update_fbo (ClutterActor *self)
       if ((source_parent = clutter_actor_get_parent (priv->fbo_source)))
         {
           CoglMatrix modelview;
-          cogl_matrix_init_identity (&modelview);
+          const ClutterCamera *camera =
+            _clutter_stage_get_camera (CLUTTER_STAGE (stage), 0);
+          cogl_matrix_init_from_array (&modelview, (float *)&camera->view);
           _clutter_actor_apply_relative_transformation_matrix (source_parent,
-                                                               NULL,
+                                                               stage,
                                                                &modelview);
           cogl_set_modelview_matrix (&modelview);
         }
@@ -2360,14 +2362,30 @@ on_fbo_source_size_change (GObject          *object,
                            GParamSpec       *param_spec,
                            ClutterTexture   *texture)
 {
+  ClutterStage *stage;
   ClutterTexturePrivate *priv = texture->priv;
   gfloat w, h;
   ClutterActorBox box;
   gboolean status;
 
-  status = clutter_actor_get_paint_box (priv->fbo_source, &box);
-  if (status)
-    clutter_actor_box_get_size (&box, &w, &h);
+  stage = CLUTTER_STAGE (_clutter_actor_get_stage_internal (priv->fbo_source));
+  if (stage)
+    {
+      const ClutterCamera *left_eye;
+
+      g_return_if_fail (_clutter_stage_get_current_camera (stage) == NULL);
+
+      left_eye = _clutter_stage_get_camera (stage, 0);
+      _clutter_stage_set_current_camera (stage, left_eye);
+
+      status = clutter_actor_get_paint_box (priv->fbo_source, &box);
+      if (status)
+        clutter_actor_box_get_size (&box, &w, &h);
+
+      _clutter_stage_set_current_camera (stage, NULL);
+    }
+  else
+    status = FALSE;
 
   /* In the end we will size the framebuffer according to the paint
    * box, but for code that does:
@@ -2527,6 +2545,10 @@ fbo_source_queue_relayout_cb (ClutterActor *source,
  *   </listitem>
  * </itemizedlist>
  *
+ * <note>If clutter is being used for stereo rendering then the
+ * texture represent the actor as it would be seen from the left
+ * eye.</note>
+ *
  * Return value: A newly created #ClutterTexture object, or %NULL on failure.
  *
  * Deprecated: 1.8: Use the #ClutterOffscreenEffect and #ClutterShaderEffect
@@ -2538,6 +2560,7 @@ fbo_source_queue_relayout_cb (ClutterActor *source,
 ClutterActor *
 clutter_texture_new_from_actor (ClutterActor *actor)
 {
+  ClutterStage *stage;
   ClutterTexture        *texture;
   ClutterTexturePrivate *priv;
   gfloat w, h;
@@ -2557,9 +2580,24 @@ clutter_texture_new_from_actor (ClutterActor *actor)
 	return NULL;
     }
 
-  status = clutter_actor_get_paint_box (actor, &box);
-  if (status)
-    clutter_actor_box_get_size (&box, &w, &h);
+  stage = CLUTTER_STAGE (_clutter_actor_get_stage_internal (actor));
+  if (stage)
+    {
+      const ClutterCamera *left_eye;
+
+      g_return_val_if_fail (_clutter_stage_get_current_camera (stage) == NULL, NULL);
+
+      left_eye = _clutter_stage_get_camera (stage, 0);
+      _clutter_stage_set_current_camera (stage, left_eye);
+
+      status = clutter_actor_get_paint_box (actor, &box);
+      if (status)
+        clutter_actor_box_get_size (&box, &w, &h);
+
+      _clutter_stage_set_current_camera (stage, NULL);
+    }
+  else
+    status = FALSE;
 
   /* In the end we will size the framebuffer according to the paint
    * box, but for code that does:
diff --git a/clutter/cogl/clutter-stage-cogl.c b/clutter/cogl/clutter-stage-cogl.c
index 40416f3..22ec407 100644
--- a/clutter/cogl/clutter-stage-cogl.c
+++ b/clutter/cogl/clutter-stage-cogl.c
@@ -59,6 +59,16 @@ G_DEFINE_TYPE_WITH_CODE (ClutterStageCogl,
                          G_IMPLEMENT_INTERFACE (CLUTTER_TYPE_STAGE_WINDOW,
                                                 clutter_stage_window_iface_init));
 
+static gboolean
+clutter_stage_cogl_has_feature (ClutterStageWindow       *window,
+                                ClutterStageWindowFeature feature)
+{
+  if (feature == CLUTTER_STAGE_WINDOW_FEATURE_SWAP_BUFFERS)
+    return TRUE;
+  else
+    return FALSE;
+}
+
 static void
 clutter_stage_cogl_unrealize (ClutterStageWindow *stage_window)
 {
@@ -343,22 +353,52 @@ clutter_stage_cogl_get_redraw_clip_bounds (ClutterStageWindow *stage_window,
   return FALSE;
 }
 
-/* XXX: This is basically identical to clutter_stage_glx_redraw */
 static void
-clutter_stage_cogl_redraw (ClutterStageWindow *stage_window)
+get_clipped_redraw_status (ClutterStageWindow *stage_window,
+                           gboolean *may_use_clipped_redraw,
+                           gboolean *use_clipped_redraw)
+{
+  ClutterStageCogl *stage_cogl = CLUTTER_STAGE_COGL (stage_window);
+#ifdef COGL_HAS_X11_SUPPORT
+  ClutterStageX11 *stage_x11 = CLUTTER_STAGE_X11 (stage_cogl);
+#endif
+  ClutterBackend *backend = clutter_get_default_backend ();
+  ClutterBackendCogl *backend_cogl = CLUTTER_BACKEND_COGL (backend);
+
+  if (G_LIKELY (backend_cogl->can_blit_sub_buffer) &&
+      /* NB: a zero width redraw clip == full stage redraw */
+      stage_cogl->bounding_redraw_clip.width != 0 &&
+      /* some drivers struggle to get going and produce some junk
+       * frames when starting up... */
+      G_LIKELY (stage_cogl->frame_count > 3)
+#ifdef COGL_HAS_X11_SUPPORT
+      /* While resizing a window clipped redraws are disabled to avoid
+       * artefacts. See clutter-event-x11.c:event_translate for a
+       * detailed explanation */
+      && G_LIKELY (stage_x11->clipped_redraws_cool_off == 0)
+#endif
+      )
+    {
+      *may_use_clipped_redraw = TRUE;
+    }
+  else
+    *may_use_clipped_redraw = FALSE;
+
+  if (*may_use_clipped_redraw &&
+      G_LIKELY (!(clutter_paint_debug_flags &
+                  CLUTTER_DEBUG_DISABLE_CLIPPED_REDRAWS)))
+    *use_clipped_redraw = TRUE;
+  else
+    *use_clipped_redraw = FALSE;
+}
+
+static void
+clutter_stage_cogl_swap_buffers (ClutterStageWindow *stage_window)
 {
   ClutterStageCogl *stage_cogl = CLUTTER_STAGE_COGL (stage_window);
-  ClutterActor *wrapper;
-  ClutterBackend *backend;
-  ClutterBackendCogl *backend_cogl;
   gboolean may_use_clipped_redraw;
   gboolean use_clipped_redraw;
 
-  CLUTTER_STATIC_TIMER (painting_timer,
-                        "Redrawing", /* parent */
-                        "Painting actors",
-                        "The time spent painting actors",
-                        0 /* no application private data */);
   CLUTTER_STATIC_TIMER (swapbuffers_timer,
                         "Redrawing", /* parent */
                         "SwapBuffers",
@@ -370,6 +410,84 @@ clutter_stage_cogl_redraw (ClutterStageWindow *stage_window)
                         "The time spent in blit_sub_buffer",
                         0 /* no application private data */);
 
+  if (!stage_cogl->onscreen)
+    return;
+
+  get_clipped_redraw_status (stage_window,
+                             &may_use_clipped_redraw,
+                             &use_clipped_redraw);
+
+  /* push on the screen */
+  if (use_clipped_redraw)
+    {
+      ClutterGeometry *clip = &stage_cogl->bounding_redraw_clip;
+      int copy_area[4];
+
+      /* XXX: It seems there will be a race here in that the stage
+       * window may be resized before the cogl_framebuffer_swap_region
+       * is handled and so we may copy the wrong region. I can't
+       * really see how we can handle this with the current state of X
+       * but at least in this case a full redraw should be queued by
+       * the resize anyway so it should only exhibit temporary
+       * artefacts.
+       */
+
+      copy_area[0] = clip->x;
+      copy_area[1] = clip->y;
+      copy_area[2] = clip->width;
+      copy_area[3] = clip->height;
+
+      CLUTTER_NOTE (BACKEND,
+                    "cogl_framebuffer_swap_region (onscreen: %p, "
+                                                  "x: %d, y: %d, "
+                                                  "width: %d, height: %d)",
+                    stage_cogl->onscreen,
+                    copy_area[0], copy_area[1], copy_area[2], copy_area[3]);
+
+
+      CLUTTER_TIMER_START (_clutter_uprof_context, blit_sub_buffer_timer);
+
+      cogl_framebuffer_swap_region (COGL_FRAMEBUFFER (stage_cogl->onscreen),
+                                    copy_area, 1);
+
+      CLUTTER_TIMER_STOP (_clutter_uprof_context, blit_sub_buffer_timer);
+    }
+  else
+    {
+      CLUTTER_NOTE (BACKEND, "cogl_framebuffer_swap_buffers (onscreen: %p)",
+                    stage_cogl->onscreen);
+
+      /* If we have swap buffer events then
+       * cogl_framebuffer_swap_buffers will return immediately and we
+       * need to track that there is a swap in progress... */
+      if (clutter_feature_available (CLUTTER_FEATURE_SWAP_EVENTS))
+        stage_cogl->pending_swaps++;
+
+      CLUTTER_TIMER_START (_clutter_uprof_context, swapbuffers_timer);
+      cogl_framebuffer_swap_buffers (COGL_FRAMEBUFFER (stage_cogl->onscreen));
+      CLUTTER_TIMER_STOP (_clutter_uprof_context, swapbuffers_timer);
+    }
+
+  /* reset the redraw clipping for the next paint... */
+  stage_cogl->initialized_redraw_clip = FALSE;
+
+  stage_cogl->frame_count++;
+}
+
+static void
+clutter_stage_cogl_redraw_without_swap (ClutterStageWindow *stage_window)
+{
+  ClutterStageCogl *stage_cogl = CLUTTER_STAGE_COGL (stage_window);
+  ClutterActor *wrapper;
+  gboolean may_use_clipped_redraw;
+  gboolean use_clipped_redraw;
+
+  CLUTTER_STATIC_TIMER (painting_timer,
+                        "Redrawing", /* parent */
+                        "Painting actors",
+                        "The time spent painting actors",
+                        0 /* no application private data */);
+
 #ifdef COGL_HAS_X11_SUPPORT
   ClutterStageX11 *stage_x11 = CLUTTER_STAGE_X11 (stage_cogl);
 
@@ -381,36 +499,11 @@ clutter_stage_cogl_redraw (ClutterStageWindow *stage_window)
   if (!stage_cogl->onscreen)
     return;
 
-  backend = clutter_get_default_backend ();
-  backend_cogl = CLUTTER_BACKEND_COGL (backend);
-
   CLUTTER_TIMER_START (_clutter_uprof_context, painting_timer);
 
-  if (G_LIKELY (backend_cogl->can_blit_sub_buffer) &&
-      /* NB: a zero width redraw clip == full stage redraw */
-      stage_cogl->bounding_redraw_clip.width != 0 &&
-      /* some drivers struggle to get going and produce some junk
-       * frames when starting up... */
-      G_LIKELY (stage_cogl->frame_count > 3)
-#ifdef COGL_HAS_X11_SUPPORT
-      /* While resizing a window clipped redraws are disabled to avoid
-       * artefacts. See clutter-event-x11.c:event_translate for a
-       * detailed explanation */
-      && G_LIKELY (stage_x11->clipped_redraws_cool_off == 0)
-#endif
-      )
-    {
-      may_use_clipped_redraw = TRUE;
-    }
-  else
-    may_use_clipped_redraw = FALSE;
-
-  if (may_use_clipped_redraw &&
-      G_LIKELY (!(clutter_paint_debug_flags &
-                  CLUTTER_DEBUG_DISABLE_CLIPPED_REDRAWS)))
-    use_clipped_redraw = TRUE;
-  else
-    use_clipped_redraw = FALSE;
+  get_clipped_redraw_status (stage_window,
+                             &may_use_clipped_redraw,
+                             &use_clipped_redraw);
 
   if (use_clipped_redraw)
     {
@@ -497,62 +590,13 @@ clutter_stage_cogl_redraw (ClutterStageWindow *stage_window)
     }
 
   CLUTTER_TIMER_STOP (_clutter_uprof_context, painting_timer);
+}
 
-  /* push on the screen */
-  if (use_clipped_redraw)
-    {
-      ClutterGeometry *clip = &stage_cogl->bounding_redraw_clip;
-      int copy_area[4];
-
-      /* XXX: It seems there will be a race here in that the stage
-       * window may be resized before the cogl_framebuffer_swap_region
-       * is handled and so we may copy the wrong region. I can't
-       * really see how we can handle this with the current state of X
-       * but at least in this case a full redraw should be queued by
-       * the resize anyway so it should only exhibit temporary
-       * artefacts.
-       */
-
-      copy_area[0] = clip->x;
-      copy_area[1] = clip->y;
-      copy_area[2] = clip->width;
-      copy_area[3] = clip->height;
-
-      CLUTTER_NOTE (BACKEND,
-                    "cogl_framebuffer_swap_region (onscreen: %p, "
-                                                  "x: %d, y: %d, "
-                                                  "width: %d, height: %d)",
-                    stage_cogl->onscreen,
-                    copy_area[0], copy_area[1], copy_area[2], copy_area[3]);
-
-
-      CLUTTER_TIMER_START (_clutter_uprof_context, blit_sub_buffer_timer);
-
-      cogl_framebuffer_swap_region (COGL_FRAMEBUFFER (stage_cogl->onscreen),
-                                    copy_area, 1);
-
-      CLUTTER_TIMER_STOP (_clutter_uprof_context, blit_sub_buffer_timer);
-    }
-  else
-    {
-      CLUTTER_NOTE (BACKEND, "cogl_framebuffer_swap_buffers (onscreen: %p)",
-                    stage_cogl->onscreen);
-
-      /* If we have swap buffer events then
-       * cogl_framebuffer_swap_buffers will return immediately and we
-       * need to track that there is a swap in progress... */
-      if (clutter_feature_available (CLUTTER_FEATURE_SWAP_EVENTS))
-        stage_cogl->pending_swaps++;
-
-      CLUTTER_TIMER_START (_clutter_uprof_context, swapbuffers_timer);
-      cogl_framebuffer_swap_buffers (COGL_FRAMEBUFFER (stage_cogl->onscreen));
-      CLUTTER_TIMER_STOP (_clutter_uprof_context, swapbuffers_timer);
-    }
-
-  /* reset the redraw clipping for the next paint... */
-  stage_cogl->initialized_redraw_clip = FALSE;
-
-  stage_cogl->frame_count++;
+static void
+clutter_stage_cogl_redraw (ClutterStageWindow *stage_window)
+{
+  clutter_stage_cogl_redraw_without_swap (stage_window);
+  clutter_stage_cogl_swap_buffers (stage_window);
 }
 
 static CoglFramebuffer *
@@ -586,12 +630,15 @@ clutter_stage_window_iface_init (ClutterStageWindowIface *iface)
 
 #endif /* COGL_HAS_X11_SUPPORT */
 
+  iface->has_feature = clutter_stage_cogl_has_feature;
   iface->get_pending_swaps = clutter_stage_cogl_get_pending_swaps;
   iface->add_redraw_clip = clutter_stage_cogl_add_redraw_clip;
   iface->has_redraw_clips = clutter_stage_cogl_has_redraw_clips;
   iface->ignoring_redraw_clips = clutter_stage_cogl_ignoring_redraw_clips;
   iface->get_redraw_clip_bounds = clutter_stage_cogl_get_redraw_clip_bounds;
   iface->redraw = clutter_stage_cogl_redraw;
+  iface->redraw_without_swap = clutter_stage_cogl_redraw_without_swap;
+  iface->swap_buffers = clutter_stage_cogl_swap_buffers;
   iface->get_active_framebuffer = clutter_stage_cogl_get_active_framebuffer;
 }
 
diff --git a/tests/conform/test-cogl-path.c b/tests/conform/test-cogl-path.c
index 1daa57f..79f8e8e 100644
--- a/tests/conform/test-cogl-path.c
+++ b/tests/conform/test-cogl-path.c
@@ -1,3 +1,4 @@
+#undef COGL_ENABLE_EXPERIMENTAL_2_0_API
 #include <clutter/clutter.h>
 #include <cogl/cogl.h>
 
diff --git a/tests/conform/test-texture-fbo.c b/tests/conform/test-texture-fbo.c
index 9ec82cb..d693a62 100644
--- a/tests/conform/test-texture-fbo.c
+++ b/tests/conform/test-texture-fbo.c
@@ -1,5 +1,6 @@
 #undef CLUTTER_DISABLE_DEPRECATED
 
+#undef COGL_ENABLE_EXPERIMENTAL_2_0_API
 #include <clutter/clutter.h>
 #include <cogl/cogl.h>
 
diff --git a/tests/interactive/test-actors.c b/tests/interactive/test-actors.c
index 3143494..f1edec2 100644
--- a/tests/interactive/test-actors.c
+++ b/tests/interactive/test-actors.c
@@ -122,6 +122,7 @@ frame_cb (ClutterTimeline *timeline,
   gint i;
   float rotation = clutter_timeline_get_progress (timeline) * 360.0f;
 
+#if 0
   /* Rotate everything clockwise about stage center*/
   if (oh->group != NULL)
     clutter_actor_set_rotation (oh->group,
@@ -130,6 +131,7 @@ frame_cb (ClutterTimeline *timeline,
                                 oh->stage_width / 2,
                                 oh->stage_height / 2,
                                 0);
+#endif
 
   for (i = 0; i < n_hands; i++)
     {
@@ -181,6 +183,7 @@ test_actors_main (int argc, char *argv[])
     }
 
   stage = clutter_stage_new ();
+  clutter_stage_set_stereo_enabled (CLUTTER_STAGE (stage), TRUE);
   clutter_actor_set_size (stage, 800, 600);
   clutter_actor_set_name (stage, "Default Stage");
   g_signal_connect (stage, "destroy", G_CALLBACK (clutter_main_quit), NULL);
@@ -202,8 +205,8 @@ test_actors_main (int argc, char *argv[])
   /* Set up some behaviours to handle scaling  */
   alpha = clutter_alpha_new_with_func (oh->timeline, my_sine_wave, NULL, NULL);
 
-  oh->scaler_1 = clutter_behaviour_scale_new (alpha, 0.5, 0.5, 1.0, 1.0);
-  oh->scaler_2 = clutter_behaviour_scale_new (alpha, 1.0, 1.0, 0.5, 0.5);
+  oh->scaler_1 = clutter_behaviour_scale_new (alpha, 0.3, 0.3, 0.7, 0.7);
+  oh->scaler_2 = clutter_behaviour_scale_new (alpha, 0.7, 0.7, 0.3, 0.3);
 
   file = g_build_filename (TESTS_DATADIR, "redhand.png", NULL);
   real_hand = clutter_texture_new_from_file (file, &error);
@@ -222,7 +225,7 @@ test_actors_main (int argc, char *argv[])
   oh->stage_width = clutter_actor_get_width (stage);
   oh->stage_height = clutter_actor_get_height (stage);
   oh->radius = (oh->stage_width + oh->stage_height)
-             / n_hands;
+             / 8;
 
   for (i = 0; i < n_hands; i++)
     {
@@ -258,6 +261,7 @@ test_actors_main (int argc, char *argv[])
 	- h / 2;
 
       clutter_actor_set_position (oh->hand[i], x, y);
+      clutter_actor_set_depth (oh->hand[i], x-150);
 
       clutter_actor_move_anchor_point_from_gravity (oh->hand[i],
 						   CLUTTER_GRAVITY_CENTER);
@@ -284,6 +288,7 @@ test_actors_main (int argc, char *argv[])
 
   /* Show everying */
   clutter_actor_show (stage);
+  //clutter_stage_set_fullscreen (CLUTTER_STAGE (stage), TRUE);
 
   g_signal_connect (stage, "key-release-event",
 		    G_CALLBACK (input_cb),
diff --git a/tests/interactive/test-behave.c b/tests/interactive/test-behave.c
index 0ec4e1e..9684037 100644
--- a/tests/interactive/test-behave.c
+++ b/tests/interactive/test-behave.c
@@ -51,6 +51,16 @@ typedef enum {
     PATH_BSPLINE
 } path_t;
 
+static void
+frame_cb (ClutterTimeline *timeline,
+	  gint             msecs,
+	  gpointer         data)
+{
+  ClutterActor *rect = data;
+
+  clutter_actor_set_position (rect, msecs/10, msecs/10);
+}
+
 G_MODULE_EXPORT int
 test_behave_main (int argc, char *argv[])
 {
@@ -58,7 +68,7 @@ test_behave_main (int argc, char *argv[])
   ClutterAlpha     *alpha;
   ClutterBehaviour *o_behave, *p_behave;
   ClutterActor     *stage;
-  ClutterActor     *group, *rect, *hand;
+  ClutterActor     *group;  // *rect, *hand;
   gchar            *file;
   int               i;
   path_t            path_type = PATH_POLY;
@@ -99,7 +109,7 @@ test_behave_main (int argc, char *argv[])
 	  exit (0);
 	}
     }
-  
+
   if (clutter_init (&argc, &argv) != CLUTTER_INIT_SUCCESS)
     return 1;
 
@@ -114,15 +124,17 @@ test_behave_main (int argc, char *argv[])
   g_signal_connect (stage, "scroll-event",
                     G_CALLBACK (scroll_event_cb),
                     NULL);
+#if 0
   g_signal_connect (stage, "key-press-event",
                     G_CALLBACK (clutter_main_quit),
                     NULL);
+#endif
 
   /* Make a hand */
-  group = clutter_group_new ();
+  group = clutter_rectangle_new ();
   clutter_container_add_actor (CLUTTER_CONTAINER (stage), group);
   clutter_actor_show (group);
-
+#if 0
   file = g_build_filename (TESTS_DATADIR, "redhand.png", NULL);
   hand = clutter_texture_new_from_file (file, NULL);
   if (hand == NULL)
@@ -134,31 +146,34 @@ test_behave_main (int argc, char *argv[])
   clutter_actor_show (hand);
 
   rect = clutter_rectangle_new ();
-  clutter_actor_set_position (rect, 0, 0);
-  clutter_actor_set_size (rect,
-                          clutter_actor_get_width (hand),
-			  clutter_actor_get_height (hand));
+#endif
+  clutter_actor_set_position (group, 0, 0);
+  clutter_actor_set_size (group, 200, 200);
+#if 0
   clutter_rectangle_set_color (CLUTTER_RECTANGLE (rect), CLUTTER_COLOR_Transparent);
   clutter_rectangle_set_border_width (CLUTTER_RECTANGLE (rect), 10);
   clutter_rectangle_set_border_color (CLUTTER_RECTANGLE (rect),
                                       CLUTTER_COLOR_Chameleon);
   clutter_actor_show (rect);
-  
+
   clutter_container_add (CLUTTER_CONTAINER (group), rect, hand, NULL);
-  
+#endif
+
   /* Make a timeline */
   timeline = clutter_timeline_new (4000);
   clutter_timeline_set_loop (timeline, TRUE);
   clutter_timeline_set_auto_reverse (timeline, TRUE);
 
+  g_signal_connect (timeline, "new-frame", G_CALLBACK (frame_cb), group);
+
   /* Set an alpha func to power behaviour - ramp is constant rise */
   alpha = clutter_alpha_new_full (timeline, CLUTTER_LINEAR);
 
   /* Create a behaviour for that alpha */
-  o_behave = clutter_behaviour_opacity_new (alpha, 0X33, 0xff); 
+  o_behave = clutter_behaviour_opacity_new (alpha, 0X33, 0xff);
 
   /* Apply it to our actor */
-  clutter_behaviour_apply (o_behave, group);
+  //clutter_behaviour_apply (o_behave, group);
 
   /* Make a path behaviour and apply that too */
   switch (path_type)
@@ -197,7 +212,7 @@ test_behave_main (int argc, char *argv[])
       break;
     }
 
-  clutter_behaviour_apply (p_behave, group);
+  //clutter_behaviour_apply (p_behave, group);
 
   /* start the timeline and thus the animations */
   clutter_timeline_start (timeline);
diff --git a/tests/interactive/test-cairo-flowers.c b/tests/interactive/test-cairo-flowers.c
index b2c0352..06fcbcd 100644
--- a/tests/interactive/test-cairo-flowers.c
+++ b/tests/interactive/test-cairo-flowers.c
@@ -154,6 +154,8 @@ tick (ClutterTimeline *timeline,
       flowers[i]->rot += flowers[i]->rv;
 
       stage = clutter_stage_get_default ();
+      clutter_stage_set_stereo_enabled (stage, TRUE);
+
       if (flowers[i]->y > (gint) clutter_actor_get_height (stage))
         flowers[i]->y = -clutter_actor_get_height (flowers[i]->ctex);
 
diff --git a/tests/interactive/test-clip.c b/tests/interactive/test-clip.c
index b2f2a8a..a6db75e 100644
--- a/tests/interactive/test-clip.c
+++ b/tests/interactive/test-clip.c
@@ -1,3 +1,4 @@
+#undef COGL_ENABLE_EXPERIMENTAL_2_0_API
 #include <clutter/clutter.h>
 #include <gmodule.h>
 #include <math.h>
diff --git a/tests/interactive/test-depth.c b/tests/interactive/test-depth.c
index 0f2ed76..4fbc677 100644
--- a/tests/interactive/test-depth.c
+++ b/tests/interactive/test-depth.c
@@ -122,6 +122,8 @@ test_depth_main (int argc, char *argv[])
     return 1;
 
   stage = clutter_stage_new ();
+  clutter_stage_set_stereo_enabled (CLUTTER_STAGE (stage), TRUE);
+
   clutter_stage_set_title (CLUTTER_STAGE (stage), "Depth Test");
   clutter_stage_set_color (CLUTTER_STAGE (stage), CLUTTER_COLOR_Aluminium2);
   g_signal_connect (stage,
diff --git a/tests/interactive/test-easing.c b/tests/interactive/test-easing.c
index f4b5b39..91fa86b 100644
--- a/tests/interactive/test-easing.c
+++ b/tests/interactive/test-easing.c
@@ -173,16 +173,19 @@ make_bouncer (gfloat width,
               gfloat height)
 {
   ClutterActor *retval;
+  ClutterColor red = {0xff, 0x00, 0x00, 0xff};
 
-  retval = clutter_cairo_texture_new (width, height);
-  g_signal_connect (retval, "draw", G_CALLBACK (draw_bouncer), NULL);
+  //retval = clutter_cairo_texture_new (width, height);
+  retval = clutter_rectangle_new_with_color (&red);
+
+  //g_signal_connect (retval, "draw", G_CALLBACK (draw_bouncer), NULL);
 
   clutter_actor_set_name (retval, "bouncer");
   clutter_actor_set_size (retval, width, height);
   clutter_actor_set_anchor_point (retval, width / 2, height / 2);
   clutter_actor_set_reactive (retval, TRUE);
 
-  clutter_cairo_texture_invalidate (CLUTTER_CAIRO_TEXTURE (retval));
+  //clutter_cairo_texture_invalidate (CLUTTER_CAIRO_TEXTURE (retval));
 
   return retval;
 }
diff --git a/tests/interactive/test-shader-effects.c b/tests/interactive/test-shader-effects.c
index 6c7d8a6..19e09f4 100644
--- a/tests/interactive/test-shader-effects.c
+++ b/tests/interactive/test-shader-effects.c
@@ -22,6 +22,7 @@ test_shader_effects_main (int argc, char *argv[])
   clutter_timeline_set_loop (timeline, TRUE);
 
   stage = clutter_stage_new ();
+  clutter_stage_set_stereo_enabled (CLUTTER_STAGE (stage), TRUE);
   clutter_stage_set_title (CLUTTER_STAGE (stage), "Rotations");
   clutter_stage_set_color (CLUTTER_STAGE (stage), CLUTTER_COLOR_Aluminium3);
   g_signal_connect (stage, "destroy", G_CALLBACK (clutter_main_quit), NULL);
@@ -69,7 +70,7 @@ test_shader_effects_main (int argc, char *argv[])
                                        NULL);
 
   clutter_container_add (CLUTTER_CONTAINER (stage), rect, hand, label, NULL);
-  
+
   /* start the timeline and thus the animations */
   clutter_timeline_start (timeline);
 
diff --git a/tests/interactive/test-texture-quality.c b/tests/interactive/test-texture-quality.c
index eb04630..5260631 100644
--- a/tests/interactive/test-texture-quality.c
+++ b/tests/interactive/test-texture-quality.c
@@ -58,8 +58,9 @@ test_texture_quality_main (int argc, char *argv[])
     return 1;
 
   stage = clutter_stage_get_default ();
+  clutter_stage_set_stereo_enabled (CLUTTER_STAGE (stage), TRUE);
   clutter_stage_set_color (CLUTTER_STAGE (stage), &stage_color);
-  clutter_stage_set_use_fog (CLUTTER_STAGE (stage), TRUE);
+  //clutter_stage_set_use_fog (CLUTTER_STAGE (stage), TRUE);
   clutter_stage_set_fog (CLUTTER_STAGE (stage), &stage_fog);
 
   g_signal_connect (stage,
@@ -82,9 +83,11 @@ test_texture_quality_main (int argc, char *argv[])
   g_free (file);
 
   /* center the image */
-  clutter_actor_set_position (image, 
+  clutter_actor_set_position (image,
     (clutter_actor_get_width (stage) - clutter_actor_get_width (image))/2,
     (clutter_actor_get_height (stage) - clutter_actor_get_height (image))/2);
+
+  clutter_actor_set_scale_with_gravity (image, 0.1, 0.1, CLUTTER_GRAVITY_CENTER);
   clutter_container_add (CLUTTER_CONTAINER (stage), image, NULL);
 
   timeline = clutter_timeline_new (5000);



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