[clutter/wip/stereo] Adds initial support for stereoscopic rendering
- From: Robert Bragg <rbragg src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [clutter/wip/stereo] Adds initial support for stereoscopic rendering
- Date: Wed, 7 Mar 2012 03:32:05 +0000 (UTC)
commit dff7660d5da96a87e25a34f5a8212e6aaea22d57
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 | 687 +++++++++++++++++------
clutter/clutter-backend.c | 4 +-
clutter/clutter-enums.h | 25 +
clutter/clutter-offscreen-effect.c | 480 ++++++++++++----
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 | 18 +-
clutter/clutter-stage.c | 953 +++++++++++++++++++++++---------
clutter/clutter-stage.h | 5 +
clutter/clutter-texture.c | 54 ++-
clutter/cogl/clutter-stage-cogl.c | 214 +++++---
15 files changed, 1915 insertions(+), 669 deletions(-)
---
diff --git a/clutter/clutter-actor-private.h b/clutter/clutter-actor-private.h
index 6b91405..4cb3e71 100644
--- a/clutter/clutter-actor-private.h
+++ b/clutter/clutter-actor-private.h
@@ -251,8 +251,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 f84d7cf..92aa1b9 100644
--- a/clutter/clutter-actor.c
+++ b/clutter/clutter-actor.c
@@ -373,6 +373,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 eye_volume;
+ gboolean eye_volume_valid;
+
+ /* If this doesn't match camera->age then the above paint-volume
+ * is invalid. */
+ int valid_for_age;
+} PerCameraState;
+
/* 3 entries should be a good compromise, few layout managers
* will ask for 3 different preferred size in each allocation cycle */
#define N_CACHED_SIZE_REQUESTS 3
@@ -396,6 +411,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;
+
/* depth */
gfloat z;
@@ -415,6 +436,7 @@ struct _ClutterActorPrivate
ClutterEffect *flatten_effect;
/* scene graph */
+ ClutterStage *stage_cache;
ClutterActor *parent;
ClutterActor *prev_sibling;
ClutterActor *next_sibling;
@@ -449,6 +471,13 @@ struct _ClutterActorPrivate
/* a counter used to toggle the CLUTTER_INTERNAL_CHILD flag */
gint internal_child;
+ /* 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;
+
/* meta classes */
ClutterMetaGroup *actions;
ClutterMetaGroup *constraints;
@@ -476,11 +505,6 @@ struct _ClutterActorPrivate
ClutterPaintVolume paint_volume;
- /* 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;
ClutterColor bg_color;
@@ -1096,11 +1120,40 @@ 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;
+
+ /* 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, *iter;
+ ClutterActor *iter;
g_assert (!CLUTTER_ACTOR_IS_MAPPED (self));
@@ -1109,9 +1162,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));
@@ -1165,6 +1216,7 @@ clutter_actor_real_unmap (ClutterActor *self)
{
ClutterActorPrivate *priv = self->priv;
ClutterActor *iter;
+ int i;
g_assert (CLUTTER_ACTOR_IS_MAPPED (self));
@@ -1180,11 +1232,29 @@ 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 - eye coordinate paint
+ * volumes, so that if we later show the actor again we won't
+ * redundantly also redraw the old location of the actor.
+ *
+ * Note: We only do this if the actor doesn't already have redraw
+ * queued for it that may depend on the last eye_volume to clear
+ * its old location. For example if you were to hide, move and
+ * re-show an actor in preparing for a single frame then in that
+ * case we would have queued a redraw for the hide and and do
+ * need to make sure that the actors old location is redrawn.
*/
- _clutter_paint_volume_init_static (&priv->last_paint_volume, NULL);
- priv->last_paint_volume_valid = TRUE;
+ if (priv->queue_redraw_entry != NULL)
+ {
+ for (i = 0; i < priv->n_cameras; i++)
+ {
+ PerCameraState *camera_state = &priv->camera_state[i];
+
+ if (!camera_state->eye_volume_valid)
+ clutter_paint_volume_free (&camera_state->eye_volume);
+ _clutter_paint_volume_init_static (&camera_state->eye_volume, NULL);
+ camera_state->eye_volume_valid = TRUE;
+ }
+ }
/* notify on parent mapped after potentially unmapping
* children, so apps see a bottom-up notification.
@@ -1194,10 +1264,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);
@@ -1340,6 +1407,14 @@ 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.
+ */
if (priv->parent != NULL)
clutter_actor_queue_redraw (priv->parent);
@@ -1435,6 +1510,14 @@ 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.
+ */
if (priv->parent != NULL)
clutter_actor_queue_redraw (priv->parent);
@@ -2176,9 +2259,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;
}
@@ -2256,7 +2338,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)
{
@@ -2270,40 +2352,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);
@@ -2321,6 +2395,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
@@ -2330,14 +2407,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
@@ -2347,13 +2423,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()
@@ -2367,6 +2436,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);
@@ -2376,6 +2447,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[])
{
@@ -2395,7 +2467,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);
}
/**
@@ -2432,25 +2505,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
@@ -2505,6 +2579,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
@@ -2524,7 +2601,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;
@@ -2539,6 +2616,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);
}
@@ -2630,8 +2708,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
@@ -2641,13 +2718,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
@@ -2661,6 +2731,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
@@ -2754,9 +2826,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);
@@ -2780,6 +2852,7 @@ _clutter_actor_draw_paint_volume (ClutterActor *self)
static void
_clutter_actor_paint_cull_result (ClutterActor *self,
+ const ClutterCamera *camera,
gboolean success,
ClutterCullResult result)
{
@@ -2851,32 +2924,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->eye_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",
@@ -2885,37 +2962,107 @@ 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->eye_volume, stage_clip);
return TRUE;
}
static void
-_clutter_actor_update_last_paint_volume (ClutterActor *self)
+invalidate_per_camera_eye_volume (PerCameraState *camera_state)
+{
+ if (camera_state->eye_volume_valid)
+ {
+ clutter_paint_volume_free (&camera_state->eye_volume);
+ camera_state->eye_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_eye_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->eye_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_eye_volume (camera_state);
+ camera_state->valid_for_age = camera_state->camera->age;
+ }
+
+ return camera_state;
+}
+
+/* NB: This updates the eye coordinates paint volume ("eye_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_eye_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->eye_volume_valid)
{
- clutter_paint_volume_free (&priv->last_paint_volume);
- priv->last_paint_volume_valid = FALSE;
+ clutter_paint_volume_free (&camera_state->eye_volume);
+ camera_state->eye_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->eye_volume);
- _clutter_paint_volume_transform_relative (&priv->last_paint_volume,
- NULL); /* eye coordinates */
+ _clutter_paint_volume_transform_relative_to_camera (&camera_state->eye_volume,
+ camera);
- priv->last_paint_volume_valid = TRUE;
+ camera_state->eye_volume_valid = TRUE;
}
static inline gboolean
@@ -3095,6 +3242,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",
@@ -3137,6 +3287,39 @@ clutter_actor_paint (ClutterActor *self)
/* mark that we are in the paint process */
CLUTTER_SET_PRIVATE_FLAGS (self, CLUTTER_IN_PAINT);
+ 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.
+ *
+ * XXX: This code should stay very near the beginning of
+ * clutter_actor_paint() to ensure that we do have a valid camera for
+ * subsequent code.
+ */
+ 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;
+
cogl_push_matrix();
if (priv->enable_model_view_transform)
@@ -3249,7 +3432,9 @@ clutter_actor_paint (ClutterActor *self)
* paint then the last-paint-volume would likely represent the new
* actor position not the old.
*/
- if (!in_clone_paint () && pick_mode == CLUTTER_PICK_NONE)
+ if (!set_current_camera &&
+ !in_clone_paint () &&
+ pick_mode == CLUTTER_PICK_NONE)
{
gboolean success;
/* annoyingly gcc warns if uninitialized even though
@@ -3261,12 +3446,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_eye_volume (self);
success = cull_actor (self, &result);
if (G_UNLIKELY (clutter_paint_debug_flags & CLUTTER_DEBUG_REDRAWS))
- _clutter_actor_paint_cull_result (self, success, result);
+ _clutter_actor_paint_cull_result (self, camera, success, result);
else if (result == CLUTTER_CULL_RESULT_OUT && success)
goto done;
}
@@ -3301,6 +3486,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();
@@ -3439,6 +3627,7 @@ remove_child (ClutterActor *self,
if (self->priv->last_child == child)
self->priv->last_child = prev_sibling;
+ child->priv->stage_cache = NULL;
child->priv->parent = NULL;
child->priv->prev_sibling = NULL;
child->priv->next_sibling = NULL;
@@ -4611,6 +4800,21 @@ clutter_actor_dispose (GObject *object)
priv->layout_manager = 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->eye_volume_valid)
+ clutter_paint_volume_free (&camera_state->eye_volume);
+ }
+ g_slice_free1 (sizeof (PerCameraState) * priv->n_cameras,
+ priv->camera_state);
+ priv->camera_state = NULL;
+ priv->n_cameras = -1;
+ }
+
G_OBJECT_CLASS (clutter_actor_parent_class)->dispose (object);
}
@@ -6455,6 +6659,8 @@ clutter_actor_init (ClutterActor *self)
self->priv = priv = CLUTTER_ACTOR_GET_PRIVATE (self);
+ priv->stage_cache = NULL;
+
priv->id = _clutter_context_acquire_id (self);
priv->pick_id = -1;
@@ -6471,9 +6677,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;
}
@@ -6529,13 +6742,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;
/* Remove queue entry early in the process, otherwise a new
queue_redraw() during signal handling could put back this
@@ -6563,46 +6807,65 @@ _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);
+ PerCameraState *camera_state =
+ _clutter_actor_get_per_camera_state (self, i);
+ ClutterPaintVolume *pv;
- /* 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);
+ if (G_LIKELY (camera_state->eye_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->eye_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 a clip volume
- * argument, but that would be an ABI break. Until we can
- * break the ABI we pass the argument out-of-band
+ /* 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);
}
static void
@@ -6638,10 +6901,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:
*
@@ -6716,9 +6979,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;
@@ -6726,6 +6988,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;
@@ -6736,9 +7000,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;
}
@@ -6764,8 +7031,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);
@@ -7557,8 +7824,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
@@ -7572,16 +7843,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;
@@ -7780,9 +8050,13 @@ clutter_actor_allocate (ClutterActor *self,
ClutterActorBox old_allocation, real_allocation;
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",
@@ -7790,8 +8064,6 @@ clutter_actor_allocate (ClutterActor *self,
return;
}
- priv = self->priv;
-
old_allocation = priv->allocation;
real_allocation = *box;
@@ -8633,6 +8905,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
@@ -8673,7 +8948,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);
@@ -9892,6 +10167,53 @@ clutter_actor_get_clip (ClutterActor *self,
*height = priv->clip.height;
}
+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->eye_volume, NULL);
+ camera_state->eye_volume_valid = TRUE;
+ }
+
+ return CLUTTER_ACTOR_TRAVERSE_VISIT_CONTINUE;
+}
+
/**
* clutter_actor_get_children:
* @self: a #ClutterActor
@@ -10185,6 +10507,7 @@ clutter_actor_add_child_internal (ClutterActor *self,
gboolean check_state;
gboolean notify_first_last;
ClutterActor *old_first_child, *old_last_child;
+ ClutterStage *stage;
if (child->priv->parent != NULL)
{
@@ -10289,6 +10612,25 @@ clutter_actor_add_child_internal (ClutterActor *self,
if (self->priv->internal_child)
CLUTTER_SET_PRIVATE_FLAGS (child, 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 (emit_parent_set && !CLUTTER_ACTOR_IN_REPARENT (child))
g_signal_emit (child, actor_signals[PARENT_SET], 0, NULL);
@@ -12628,15 +12970,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;
-
- return actor;
-}
-
/**
* clutter_actor_get_stage:
* @actor: a #ClutterActor
@@ -12653,7 +12986,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));
}
/**
@@ -12966,13 +13299,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);
}
/**
@@ -13710,26 +14043,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 g_object_get_data (G_OBJECT (self),
- "-clutter-actor-queue-redraw-clip");
-}
-
-void
-_clutter_actor_set_queue_redraw_clip (ClutterActor *self,
- ClutterPaintVolume *clip)
-{
- g_object_set_data (G_OBJECT (self),
- "-clutter-actor-queue-redraw-clip",
- clip);
-}
-
/**
* clutter_actor_has_allocation:
* @self: a #ClutterActor
@@ -14425,15 +14738,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
@@ -14561,10 +14874,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);
@@ -14639,29 +14954,30 @@ clutter_actor_get_paint_volume (ClutterActor *self)
* not guaranteed to be valid across multiple frames; if you wish to
* keep it, you will have to copy it using clutter_paint_volume_copy().
*
+ * <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 *
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);
@@ -14689,30 +15005,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 b50154a..b56b56a 100644
--- a/clutter/clutter-backend.c
+++ b/clutter/clutter-backend.c
@@ -804,8 +804,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-enums.h b/clutter/clutter-enums.h
index 40bdb6a..644ac68 100644
--- a/clutter/clutter-enums.h
+++ b/clutter/clutter-enums.h
@@ -1094,6 +1094,31 @@ typedef enum {
CLUTTER_ACTOR_ALIGN_END
} ClutterActorAlign;
+/**
+ * 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 and the right 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;
+
G_END_DECLS
#endif /* __CLUTTER_ENUMS_H__ */
diff --git a/clutter/clutter-offscreen-effect.c b/clutter/clutter-offscreen-effect.c
index 09443dc..99130a0 100644
--- a/clutter/clutter-offscreen-effect.c
+++ b/clutter/clutter-offscreen-effect.c
@@ -76,26 +76,32 @@
#include "clutter-private.h"
#include "clutter-stage-private.h"
-struct _ClutterOffscreenEffectPrivate
+typedef struct _PerCameraState
{
+ const ClutterCamera *camera;
+ int valid_for_age;
+
CoglHandle offscreen;
- CoglPipeline *target;
- CoglHandle texture;
- ClutterActor *actor;
- ClutterActor *stage;
+ /* aka "target" for legacy reasons */
+ CoglPipeline *pipeline;
+ CoglHandle texture;
- gfloat x_offset;
- gfloat y_offset;
+ gfloat viewport_x_offset;
+ gfloat viewport_y_offset;
/* This is the calculated size of the fbo before being passed
- through create_texture(). This needs to be tracked separately so
- that we can detect when a different size is calculated and
- regenerate the fbo */
- int fbo_width;
- int fbo_height;
-
- gint old_opacity_override;
+ * through update_fbo() and create_texture(). This needs to be
+ * tracked separately from the final fbo_width/height so that we can
+ * detect when a different size is calculated and regenerate the
+ * fbo.
+ *
+ * NB: We can't just compare the fbo_width/height because some
+ * sub-classes may return a texture from create_texture() that has
+ * a different size from the calculated request size.
+ */
+ int request_width;
+ int request_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
@@ -106,6 +112,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,
@@ -119,15 +137,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 != NULL)
+ for (i = 0; i < priv->n_cameras; i++)
{
- cogl_handle_unref (priv->offscreen);
- priv->offscreen = NULL;
+ PerCameraState *camera_state = &priv->camera_state[i];
+ if (camera_state->offscreen != NULL)
+ {
+ cogl_object_unref (camera_state->offscreen);
+ camera_state->offscreen = NULL;
+ }
}
/* we keep a back pointer here, to avoid going through the ActorMeta */
@@ -144,72 +167,139 @@ 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->pipeline)
+ {
+ cogl_object_unref (camera_state->pipeline);
+ camera_state->pipeline = NULL;
+ }
+
+ if (camera_state->offscreen)
+ {
+ cogl_object_unref (camera_state->offscreen);
+ camera_state->offscreen = NULL;
+ }
+}
+
+static PerCameraState *
+get_per_camera_state (ClutterOffscreenEffect *self, int camera_index)
{
- ClutterOffscreenEffect *self = CLUTTER_OFFSCREEN_EFFECT (effect);
ClutterOffscreenEffectPrivate *priv = self->priv;
+ ClutterStage *stage =
+ CLUTTER_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->pipeline = NULL;
+ camera_state->texture = NULL;
+ camera_state->offscreen = NULL;
+ }
+
+ priv->n_cameras = n_cameras;
+ priv->cameras_age = cameras_age;
}
- if (priv->fbo_width == fbo_width &&
- priv->fbo_height == fbo_height &&
- priv->offscreen != NULL)
+ 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;
+ }
+
+ return camera_state;
+}
+
+static gboolean
+update_fbo (ClutterEffect *effect,
+ int camera_index,
+ int request_width,
+ int request_height)
+{
+ ClutterOffscreenEffect *self = CLUTTER_OFFSCREEN_EFFECT (effect);
+ PerCameraState *camera_state;
+
+ camera_state = get_per_camera_state (self, camera_index);
+ if (camera_state->request_width == request_width &&
+ camera_state->request_height == request_height &&
+ camera_state->offscreen != NULL)
return TRUE;
- if (priv->target == NULL)
+ if (camera_state->pipeline == NULL)
{
CoglContext *ctx =
clutter_backend_get_cogl_context (clutter_get_default_backend ());
- priv->target = cogl_pipeline_new (ctx);
+ camera_state->pipeline = cogl_pipeline_new (ctx);
/* 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_pipeline_set_layer_filters (priv->target,
+ cogl_pipeline_set_layer_filters (camera_state->pipeline,
0, /* layer_index */
COGL_PIPELINE_FILTER_NEAREST,
COGL_PIPELINE_FILTER_NEAREST);
}
- if (priv->texture != NULL)
+ if (camera_state->texture != NULL)
{
- cogl_handle_unref (priv->texture);
- priv->texture = NULL;
+ cogl_object_unref (camera_state->texture);
+ camera_state->texture = NULL;
}
- priv->texture =
- clutter_offscreen_effect_create_texture (self, fbo_width, fbo_height);
- if (priv->texture == NULL)
+ camera_state->texture =
+ clutter_offscreen_effect_create_texture (self,
+ request_width,
+ request_height);
+ if (camera_state->texture == NULL)
return FALSE;
- cogl_pipeline_set_layer_texture (priv->target, 0, priv->texture);
+ cogl_pipeline_set_layer_texture (camera_state->pipeline, 0, camera_state->texture);
- priv->fbo_width = fbo_width;
- priv->fbo_height = fbo_height;
+ camera_state->request_width = request_width;
+ camera_state->request_height = request_height;
- if (priv->offscreen != NULL)
- cogl_handle_unref (priv->offscreen);
+ if (camera_state->offscreen != NULL)
+ cogl_object_unref (camera_state->offscreen);
- priv->offscreen = cogl_offscreen_new_to_texture (priv->texture);
- if (priv->offscreen == NULL)
+ camera_state->offscreen = cogl_offscreen_new_to_texture (camera_state->texture);
+ if (camera_state->offscreen == NULL)
{
g_warning ("%s: Unable to create an Offscreen buffer", G_STRLOC);
- cogl_handle_unref (priv->target);
- priv->target = NULL;
+ cogl_object_unref (camera_state->pipeline);
+ camera_state->pipeline = NULL;
- priv->fbo_width = 0;
- priv->fbo_height = 0;
+ camera_state->request_width = 0;
+ camera_state->request_height = 0;
return FALSE;
}
@@ -225,10 +315,14 @@ clutter_offscreen_effect_pre_paint (ClutterEffect *effect)
ClutterActorBox box;
CoglMatrix projection;
CoglColor transparent;
- gfloat fbo_width, fbo_height;
- gfloat width, height;
+ gfloat fbo_request_width, fbo_request_height;
+ gfloat stage_viewport_x, stage_viewport_y;
+ gfloat stage_viewport_width, stage_viewport_height;
gfloat xexpand, yexpand;
int texture_width, texture_height;
+ const ClutterCamera *camera;
+ PerCameraState *camera_state;
+ ClutterStage *stage;
if (!clutter_actor_meta_get_enabled (CLUTTER_ACTOR_META (effect)))
return FALSE;
@@ -236,71 +330,108 @@ clutter_offscreen_effect_pre_paint (ClutterEffect *effect)
if (priv->actor == NULL)
return FALSE;
+ stage = CLUTTER_STAGE (_clutter_actor_get_stage_internal (priv->actor));
+
+ camera = _clutter_stage_get_current_camera (stage);
+ camera_state = get_per_camera_state (self, camera->index);
+
+ stage_viewport_x = camera->viewport[0];
+ stage_viewport_y = camera->viewport[1];
+ stage_viewport_width = camera->viewport[2];
+ stage_viewport_height = camera->viewport[3];
+
/* 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
- * be used to setup an offset viewport */
+ * screen coordinates (i.e. stage coordinates that have been
+ * projected by the current camera and had the camera's viewport
+ * transform applied). This will give us the size for the
+ * framebuffer we need to redirect its rendering offscreen.
+ *
+ * The position will be used to setup an offset viewport so we need
+ * an offset that is relative to the top-left of the stage's
+ * viewport rectangle not relative to the screen.
+ *
+ * NB: We can't assume the stage viewport has an origin of (0,0) or
+ * that the viewport size matches the geometry of the stage because
+ * for example when we are running in stereoscopic rendering mode
+ * the stage size might map to half the width or height in screen
+ * coordinates if a horizonal or vertical split screen is being
+ * used.
+ */
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_size (&box,
+ &fbo_request_width, &fbo_request_height);
+ clutter_actor_box_get_origin (&box,
+ &camera_state->viewport_x_offset,
+ &camera_state->viewport_y_offset);
+ camera_state->viewport_x_offset -= stage_viewport_x;
+ camera_state->viewport_y_offset -= stage_viewport_y;
}
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;
+ * creating a full stage size fbo.
+ *
+ * Note: as mentioned above the stage's viewport might not
+ * match the stage's geometry so if we want to know the stages
+ * size in screen coordinates we should look at the viewport
+ * geometry.
+ *
+ * Note: we may need to change how we determine the screen-space
+ * size of the stage if we add support for sliced stages in the
+ * future.
+ */
+ fbo_request_width = stage_viewport_width;
+ fbo_request_height = stage_viewport_height;
+ camera_state->viewport_x_offset = 0;
+ camera_state->viewport_y_offset = 0;
}
/* First assert that the framebuffer is the right size... */
- if (!update_fbo (effect, fbo_width, fbo_height))
+ if (!update_fbo (effect, camera->index,
+ fbo_request_width, fbo_request_height))
return FALSE;
- texture_width = cogl_texture_get_width (priv->texture);
- texture_height = cogl_texture_get_height (priv->texture);
+ texture_width = cogl_texture_get_width (camera_state->texture);
+ texture_height = cogl_texture_get_height (camera_state->texture);
/* get the current modelview matrix so that we can copy it to the
* framebuffer. We also 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 */
- cogl_get_modelview_matrix (&priv->last_matrix_drawn);
+ cogl_get_modelview_matrix (&camera_state->last_matrix_drawn);
/* let's draw offscreen */
- cogl_push_framebuffer (priv->offscreen);
+ cogl_push_framebuffer (camera_state->offscreen);
/* Copy the modelview that would have been used if rendering onscreen */
- cogl_set_modelview_matrix (&priv->last_matrix_drawn);
-
- /* 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);
+ cogl_set_modelview_matrix (&camera_state->last_matrix_drawn);
/* 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 + texture_width > width)
- xexpand = MAX (xexpand, (priv->x_offset + texture_width) - width);
+ if (camera_state->viewport_x_offset < 0.f)
+ xexpand = -camera_state->viewport_x_offset;
+ if (camera_state->viewport_x_offset + texture_width > stage_viewport_width)
+ xexpand = MAX (xexpand, (camera_state->viewport_x_offset +
+ texture_width) - stage_viewport_width);
yexpand = 0.f;
- if (priv->y_offset < 0.f)
- yexpand = -priv->y_offset;
- if (priv->y_offset + texture_height > height)
- yexpand = MAX (yexpand, (priv->y_offset + texture_height) - height);
+ if (camera_state->viewport_y_offset < 0.f)
+ yexpand = -camera_state->viewport_y_offset;
+ if (camera_state->viewport_y_offset + texture_height > stage_viewport_height)
+ yexpand = MAX (yexpand, (camera_state->viewport_y_offset +
+ texture_height) - stage_viewport_height);
/* Set the viewport */
- cogl_set_viewport (-(priv->x_offset + xexpand), -(priv->y_offset + yexpand),
- width + (2 * xexpand), height + (2 * yexpand));
+ cogl_set_viewport (-(camera_state->viewport_x_offset + xexpand),
+ -(camera_state->viewport_y_offset + yexpand),
+ stage_viewport_width + (2 * xexpand),
+ stage_viewport_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);
/* If we've expanded the viewport, make sure to scale the projection
* matrix accordingly (as it's been initialised to work with the
@@ -310,12 +441,12 @@ clutter_offscreen_effect_pre_paint (ClutterEffect *effect)
{
gfloat new_width, new_height;
- new_width = width + (2 * xexpand);
- new_height = height + (2 * yexpand);
+ new_width = stage_viewport_width + (2 * xexpand);
+ new_height = stage_viewport_height + (2 * yexpand);
cogl_matrix_scale (&projection,
- width / new_width,
- height / new_height,
+ stage_viewport_width / new_width,
+ stage_viewport_height / new_height,
1);
}
@@ -344,24 +475,32 @@ clutter_offscreen_effect_real_paint_target (ClutterOffscreenEffect *effect)
{
ClutterOffscreenEffectPrivate *priv = effect->priv;
guint8 paint_opacity;
+ ClutterStage *stage =
+ CLUTTER_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);
+ int texture_width;
+ int texture_height;
paint_opacity = clutter_actor_get_paint_opacity (priv->actor);
- cogl_pipeline_set_color4ub (priv->target,
+ cogl_pipeline_set_color4ub (camera_state->pipeline,
paint_opacity,
paint_opacity,
paint_opacity,
paint_opacity);
- cogl_set_source (priv->target);
+ cogl_set_source (camera_state->pipeline);
/* At this point we are in stage coordinates translated so if
* we draw our texture using a textured quad the size of the paint
* box then we will overlay where the actor would have drawn if it
* hadn't been redirected offscreen.
*/
+ texture_width = cogl_texture_get_width (camera_state->texture);
+ texture_height = cogl_texture_get_height (camera_state->texture);
cogl_rectangle_with_texture_coords (0, 0,
- cogl_texture_get_width (priv->texture),
- cogl_texture_get_height (priv->texture),
+ texture_width,
+ texture_height,
0.0, 0.0,
1.0, 1.0);
}
@@ -371,16 +510,62 @@ clutter_offscreen_effect_paint_texture (ClutterOffscreenEffect *effect)
{
ClutterOffscreenEffectPrivate *priv = effect->priv;
CoglMatrix modelview;
+ ClutterStage *stage =
+ CLUTTER_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);
+ CoglMatrix saved_projection;
+ gfloat scale_x, scale_y;
+ gfloat stage_x, stage_y;
+ gfloat stage_width, stage_height;
+
+ /* Now reset the modelview/projection to put us in orthographic
+ * stage coordinates so we can draw the result of our offscreen render as
+ * a textured quad...
+ *
+ * XXX: Note we don't use _apply_transform (stage, &modelview) to
+ * put is in regular stage coordinates because that might include a
+ * stereoscopic camera view transform and we don't want our
+ * rectangle to be affected by that.
+ *
+ * XXX: since clutter-stage.c should be free to play tricks with the
+ * viewport and projection matrix to support different forms of
+ * stereoscopic rendering it might make sense at some point to add
+ * some internal _clutter_stage api something like
+ * _clutter_stage_push/pop_orthographic() that can handle the
+ * details of giving us an orthographic projection without
+ * clobbering any of the transforms in place for stereo rendering.
+ *
+ * XXX: For now we are assuming that clutter-stage.c only plays with
+ * the viewport for vertical and horizontal split stereo rendering
+ * but at some point if we start using the projection matrix instead
+ * then this code will conflict with that!
+ */
+
+ cogl_get_projection_matrix (&saved_projection);
+ clutter_actor_get_size (CLUTTER_ACTOR (stage), &stage_width, &stage_height);
+ cogl_ortho (0, stage_width, /* left, right */
+ stage_height, 0, /* bottom, top */
+ -1, 100 /* z near, far */);
cogl_push_matrix ();
- /* Now reset the modelview to put us in stage coordinates so
- * we can drawn the result of our offscreen render as a textured
- * quad... */
+ /* NB: camera_state->viewport_x/y_offset are in screen coordinates
+ * relative to the stage's viewport rectangle but here we need a
+ * position in stage coordinates.
+ *
+ * Also our texture size was measured in screen coordinates but we
+ * want to paint the texture in actor coordinates.
+ */
+ scale_x = (stage_width / camera->viewport[2]);
+ scale_y = (stage_height / camera->viewport[3]);
+
+ stage_x = camera_state->viewport_x_offset * scale_x;
+ stage_y = camera_state->viewport_y_offset * scale_y;
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);
+ cogl_matrix_translate (&modelview, stage_x, stage_y, 0.0f);
+ cogl_matrix_scale (&modelview, scale_x, scale_y, 1);
cogl_set_modelview_matrix (&modelview);
/* paint the target material; this is virtualized for
@@ -389,6 +574,7 @@ clutter_offscreen_effect_paint_texture (ClutterOffscreenEffect *effect)
clutter_offscreen_effect_paint_target (effect);
cogl_pop_matrix ();
+ cogl_set_projection_matrix (&saved_projection);
}
static void
@@ -396,9 +582,13 @@ clutter_offscreen_effect_post_paint (ClutterEffect *effect)
{
ClutterOffscreenEffect *self = CLUTTER_OFFSCREEN_EFFECT (effect);
ClutterOffscreenEffectPrivate *priv = self->priv;
+ ClutterStage *stage =
+ CLUTTER_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 == NULL ||
- priv->target == NULL ||
+ if (camera_state->offscreen == NULL ||
+ camera_state->pipeline == NULL ||
priv->actor == NULL)
return;
@@ -418,20 +608,27 @@ clutter_offscreen_effect_paint (ClutterEffect *effect,
ClutterOffscreenEffect *self = CLUTTER_OFFSCREEN_EFFECT (effect);
ClutterOffscreenEffectPrivate *priv = self->priv;
CoglMatrix matrix;
+ ClutterStage *stage =
+ CLUTTER_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);
@@ -442,15 +639,29 @@ 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_object_unref (camera_state->offscreen);
- if (priv->offscreen)
- cogl_handle_unref (priv->offscreen);
+ if (camera_state->pipeline)
+ cogl_object_unref (camera_state->pipeline);
- if (priv->target)
- cogl_handle_unref (priv->target);
+ if (camera_state->texture)
+ cogl_object_unref (camera_state->texture);
+ }
- if (priv->texture)
- cogl_handle_unref (priv->texture);
+ 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);
}
@@ -482,6 +693,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;
}
/**
@@ -507,10 +720,23 @@ clutter_offscreen_effect_init (ClutterOffscreenEffect *self)
CoglHandle
clutter_offscreen_effect_get_texture (ClutterOffscreenEffect *effect)
{
+ ClutterOffscreenEffect *self;
+ ClutterOffscreenEffectPrivate *priv;
+ ClutterStage *stage;
+ const ClutterCamera *camera;
+ PerCameraState *camera_state;
+
g_return_val_if_fail (CLUTTER_IS_OFFSCREEN_EFFECT (effect),
NULL);
- return effect->priv->texture;
+ self = CLUTTER_OFFSCREEN_EFFECT (effect);
+ priv = self->priv;
+
+ stage = CLUTTER_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->texture;
}
/**
@@ -532,10 +758,23 @@ clutter_offscreen_effect_get_texture (ClutterOffscreenEffect *effect)
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),
NULL);
- return (CoglMaterial *)effect->priv->target;
+ self = CLUTTER_OFFSCREEN_EFFECT (effect);
+ priv = self->priv;
+
+ stage = CLUTTER_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 (CoglMaterial *)camera_state->pipeline;
}
/**
@@ -594,6 +833,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
*
@@ -605,19 +848,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->texture == NULL)
+ stage = CLUTTER_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->texture == NULL)
return FALSE;
if (width)
- *width = cogl_texture_get_width (priv->texture);
+ *width = cogl_texture_get_width (camera_state->texture);
if (height)
- *height = cogl_texture_get_height (priv->texture);
+ *height = cogl_texture_get_height (camera_state->texture);
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 725be79..7103aed 100644
--- a/clutter/clutter-paint-volume.c
+++ b/clutter/clutter-paint-volume.c
@@ -1128,14 +1128,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;
@@ -1145,20 +1143,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);
@@ -1220,13 +1216,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 df17d6a..9aa6b84 100644
--- a/clutter/clutter-private.h
+++ b/clutter/clutter-private.h
@@ -265,6 +265,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 d2c7f78..81da8a7 100644
--- a/clutter/clutter-stage-private.h
+++ b/clutter/clutter-stage-private.h
@@ -44,7 +44,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,
@@ -55,9 +55,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);
@@ -116,6 +116,14 @@ gboolean _clutter_stage_update_state (ClutterStage *stag
ClutterStageState unset_state,
ClutterStageState set_state);
+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 3ba3c99..b6383a0 100644
--- a/clutter/clutter-stage-window.c
+++ b/clutter/clutter-stage-window.c
@@ -38,6 +38,21 @@ clutter_stage_window_default_init (ClutterStageWindowInterface *iface)
g_object_interface_install_property (iface, pspec);
}
+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)
{
@@ -236,6 +251,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 041a255..6f938d2 100644
--- a/clutter/clutter-stage-window.h
+++ b/clutter/clutter-stage-window.h
@@ -22,7 +22,13 @@ G_BEGIN_DECLS
typedef struct _ClutterStageWindow ClutterStageWindow; /* dummy */
typedef struct _ClutterStageWindowIface ClutterStageWindowIface;
-/*
+typedef enum
+{
+ /*< private >*/
+ CLUTTER_STAGE_WINDOW_FEATURE_SWAP_BUFFERS
+} ClutterStageWindowFeature;
+
+/**
* ClutterStageWindowIface: (skip)
*
* The interface implemented by backends for stage windows
@@ -34,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,
@@ -72,6 +81,8 @@ 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);
@@ -80,6 +91,9 @@ struct _ClutterStageWindowIface
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,
@@ -116,6 +130,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 123caeb..bf2d0ca 100644
--- a/clutter/clutter-stage.c
+++ b/clutter/clutter-stage.c
@@ -50,6 +50,7 @@
#include <math.h>
#include <cairo.h>
+#include <stdlib.h>
#define CLUTTER_DISABLE_DEPRECATION_WARNINGS
@@ -121,11 +122,27 @@ struct _ClutterStagePrivate
/* the stage implementation */
ClutterStageWindow *impl;
- 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;
@@ -171,11 +188,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
@@ -495,7 +516,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)
@@ -657,9 +678,9 @@ _clutter_stage_do_paint (ClutterStage *stage,
_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);
@@ -753,6 +774,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);
@@ -814,7 +836,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);
@@ -1046,9 +1068,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, };
@@ -1115,12 +1136,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_STATIC_COUNTER (redraw_counter,
"clutter_stage_do_redraw counter",
@@ -1153,14 +1538,68 @@ clutter_stage_do_redraw (ClutterStage *stage)
priv->fps_timer = g_timer_new ();
}
- _clutter_stage_maybe_setup_viewport (stage);
+ _clutter_stage_update_modelview_projection (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_COUNTER_INC (_clutter_uprof_context, redraw_counter);
- CLUTTER_TIMER_START (_clutter_uprof_context, redraw_timer);
+ _clutter_stage_set_current_camera (stage, &priv->cameras[0]);
+ _clutter_stage_flush_modelview_projection (stage);
- _clutter_stage_window_redraw (priv->impl);
+ fb = cogl_get_draw_framebuffer ();
- CLUTTER_TIMER_STOP (_clutter_uprof_context, redraw_timer);
+ 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_COUNTER_INC (_clutter_uprof_context, redraw_counter);
+ CLUTTER_TIMER_START (_clutter_uprof_context, redraw_timer);
+
+ _clutter_stage_window_redraw (priv->impl);
+
+ CLUTTER_TIMER_STOP (_clutter_uprof_context, redraw_timer);
+
+ 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_COUNTER_INC (_clutter_uprof_context, redraw_counter);
+ CLUTTER_TIMER_START (_clutter_uprof_context, redraw_timer);
+
+ _clutter_stage_window_redraw (priv->impl);
+
+ CLUTTER_TIMER_STOP (_clutter_uprof_context, redraw_timer);
+ }
+
+ _clutter_stage_set_current_camera (stage, NULL);
if (_clutter_context_get_show_fps ())
{
@@ -1209,7 +1648,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;
@@ -1255,6 +1694,8 @@ clutter_stage_real_queue_redraw (ClutterActor *actor,
ClutterStage *stage = CLUTTER_STAGE (actor);
ClutterStageWindow *stage_window;
ClutterPaintVolume *redraw_clip;
+ int camera_index;
+ ClutterCamera *camera;
ClutterActorBox bounding_box;
ClutterActorBox intersection_box;
cairo_rectangle_int_t geom, stage_clip;
@@ -1285,9 +1726,11 @@ clutter_stage_real_queue_redraw (ClutterActor *actor,
return;
}
- _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);
_clutter_stage_window_get_geometry (stage_window, &geom);
@@ -1305,9 +1748,14 @@ clutter_stage_real_queue_redraw (ClutterActor *actor,
* clip rectangle outwards... */
stage_clip.x = intersection_box.x1;
stage_clip.y = intersection_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 = intersection_box.x2 - stage_clip.x;
stage_clip.height = intersection_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);
}
@@ -1473,8 +1921,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
@@ -1514,6 +1967,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 &
@@ -1601,7 +2056,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
@@ -2203,6 +2658,17 @@ clutter_stage_init (ClutterStage *self)
ClutterStageWindow *impl;
ClutterBackend *backend;
GError *error;
+ 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);
@@ -2232,6 +2698,32 @@ clutter_stage_init (ClutterStage *self)
g_critical ("Unable to create a new stage implementation.");
}
+ 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;
@@ -2257,16 +2749,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,
@@ -2274,6 +2772,9 @@ clutter_stage_init (ClutterStage *self)
geom.width,
geom.height);
+ priv->dirty_viewport = TRUE;
+ priv->dirty_projection = TRUE;
+ priv->dirty_view = TRUE;
/* FIXME - remove for 2.0 */
priv->fog.z_near = 1.0;
@@ -2371,47 +2872,20 @@ clutter_stage_set_color (ClutterStage *stage,
g_object_notify (G_OBJECT (stage), "color");
}
-/**
- * clutter_stage_get_color:
- * @stage: A #ClutterStage
- * @color: (out caller-allocates): return location for a #ClutterColor
- *
- * Retrieves the stage color.
- *
- * Deprecated: 1.10: Use clutter_actor_get_background_color() instead.
- */
-void
-clutter_stage_get_color (ClutterStage *stage,
- ClutterColor *color)
-{
- clutter_actor_get_background_color (CLUTTER_ACTOR (stage), 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;
-
- priv->perspective = *perspective;
-
- 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->dirty_projection = TRUE;
- clutter_actor_queue_redraw (CLUTTER_ACTOR (stage));
+/**
+ * clutter_stage_get_color:
+ * @stage: A #ClutterStage
+ * @color: (out caller-allocates): return location for a #ClutterColor
+ *
+ * Retrieves the stage color.
+ *
+ * Deprecated: 1.10: Use clutter_actor_get_background_color() instead.
+ */
+void
+clutter_stage_get_color (ClutterStage *stage,
+ ClutterColor *color)
+{
+ clutter_actor_get_background_color (CLUTTER_ACTOR (stage), color);
}
/**
@@ -2471,7 +2945,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,
@@ -2480,7 +2957,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
@@ -2488,9 +2965,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;
}
/*
@@ -2543,7 +3020,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] &&
@@ -2555,6 +3034,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);
@@ -2565,9 +3046,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;
}
/*
@@ -2829,15 +3310,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)
@@ -3338,194 +3846,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);
}
/**
@@ -4424,3 +4755,111 @@ _clutter_stage_update_state (ClutterStage *stage,
return TRUE;
}
+
+/**
+ * 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 e3c1d7e..7e91a39 100644
--- a/clutter/clutter-stage.h
+++ b/clutter/clutter-stage.h
@@ -202,6 +202,11 @@ void clutter_stage_ensure_current (ClutterStage
void clutter_stage_ensure_viewport (ClutterStage *stage);
void clutter_stage_ensure_redraw (ClutterStage *stage);
+void clutter_stage_set_stereo_enabled (ClutterStage *stage,
+ gboolean enabled);
+void clutter_stage_set_stereo_mode (ClutterStage *stage,
+ ClutterStereoMode mode);
+
G_END_DECLS
#endif /* __CLUTTER_STAGE_H__ */
diff --git a/clutter/clutter-texture.c b/clutter/clutter-texture.c
index c521c80..c5257bf 100644
--- a/clutter/clutter-texture.c
+++ b/clutter/clutter-texture.c
@@ -541,9 +541,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);
}
@@ -2363,14 +2365,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:
@@ -2532,6 +2550,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
@@ -2543,6 +2565,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;
@@ -2562,9 +2585,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 618723a..2b17bae 100644
--- a/clutter/cogl/clutter-stage-cogl.c
+++ b/clutter/cogl/clutter-stage-cogl.c
@@ -63,6 +63,16 @@ enum {
PROP_LAST
};
+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)
{
@@ -312,9 +322,42 @@ 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);
+ gboolean can_blit_sub_buffer =
+ cogl_clutter_winsys_has_feature (COGL_WINSYS_FEATURE_SWAP_REGION);
+
+ if (G_LIKELY (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)
+ /* While resizing a window clipped redraws are disabled to avoid
+ * artefacts. See clutter-event-x11.c:event_translate for a
+ * detailed explanation */
+ && _clutter_stage_window_can_clip_redraws (stage_window)
+ )
+ {
+ *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);
gboolean may_use_clipped_redraw;
@@ -322,11 +365,6 @@ clutter_stage_cogl_redraw (ClutterStageWindow *stage_window)
gboolean can_blit_sub_buffer;
ClutterActor *wrapper;
- 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",
@@ -338,34 +376,93 @@ clutter_stage_cogl_redraw (ClutterStageWindow *stage_window)
"The time spent in blit_sub_buffer",
0 /* no application private data */);
- wrapper = CLUTTER_ACTOR (stage_cogl->wrapper);
-
if (!stage_cogl->onscreen)
return;
- CLUTTER_TIMER_START (_clutter_uprof_context, painting_timer);
+ get_clipped_redraw_status (stage_window,
+ &may_use_clipped_redraw,
+ &use_clipped_redraw);
- can_blit_sub_buffer =
- cogl_clutter_winsys_has_feature (COGL_WINSYS_FEATURE_SWAP_REGION);
+ /* push on the screen */
+ if (use_clipped_redraw)
+ {
+ cairo_rectangle_int_t *clip = &stage_cogl->bounding_redraw_clip;
+ int copy_area[4];
- may_use_clipped_redraw = FALSE;
- if (_clutter_stage_window_can_clip_redraws (stage_window) &&
- 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... */
- stage_cogl->frame_count > 3)
+ /* XXX: It seems there will be a race here in that the stage
+ * window may be resized before the cogl_onscreen_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_onscreen_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_onscreen_swap_region (stage_cogl->onscreen, copy_area, 1);
+
+ CLUTTER_TIMER_STOP (_clutter_uprof_context, blit_sub_buffer_timer);
+ }
+ else
{
- may_use_clipped_redraw = TRUE;
+ CLUTTER_NOTE (BACKEND, "cogl_onscreen_swap_buffers (onscreen: %p)",
+ stage_cogl->onscreen);
+
+ /* If we have swap buffer events then
+ * cogl_onscreen_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_onscreen_swap_buffers (stage_cogl->onscreen);
+ CLUTTER_TIMER_STOP (_clutter_uprof_context, swapbuffers_timer);
}
- 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;
+ /* 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 */);
+
+ wrapper = CLUTTER_ACTOR (stage_cogl->wrapper);
+
+ if (!stage_cogl->onscreen)
+ return;
+
+ CLUTTER_TIMER_START (_clutter_uprof_context, painting_timer);
+
+ get_clipped_redraw_status (stage_window,
+ &may_use_clipped_redraw,
+ &use_clipped_redraw);
if (use_clipped_redraw)
{
@@ -454,61 +551,13 @@ clutter_stage_cogl_redraw (ClutterStageWindow *stage_window)
}
CLUTTER_TIMER_STOP (_clutter_uprof_context, painting_timer);
+}
- /* push on the screen */
- if (use_clipped_redraw)
- {
- cairo_rectangle_int_t *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_onscreen_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_onscreen_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_onscreen_swap_region (stage_cogl->onscreen, copy_area, 1);
-
- CLUTTER_TIMER_STOP (_clutter_uprof_context, blit_sub_buffer_timer);
- }
- else
- {
- CLUTTER_NOTE (BACKEND, "cogl_onscreen_swap_buffers (onscreen: %p)",
- stage_cogl->onscreen);
-
- /* If we have swap buffer events then cogl_onscreen_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_onscreen_swap_buffers (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 *
@@ -522,6 +571,7 @@ clutter_stage_cogl_get_active_framebuffer (ClutterStageWindow *stage_window)
static void
clutter_stage_window_iface_init (ClutterStageWindowIface *iface)
{
+ iface->has_feature = clutter_stage_cogl_has_feature;
iface->realize = clutter_stage_cogl_realize;
iface->unrealize = clutter_stage_cogl_unrealize;
iface->get_wrapper = clutter_stage_cogl_get_wrapper;
@@ -535,6 +585,8 @@ clutter_stage_window_iface_init (ClutterStageWindowIface *iface)
iface->ignoring_redraw_clips = clutter_stage_cogl_ignoring_redraw_clips;
iface->get_redraw_clip_bounds = clutter_stage_cogl_get_redraw_clip_bounds;
iface->redraw = clutter_stage_cogl_redraw;
+ iface->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;
}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]