summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRobert Bragg <robert@linux.intel.com>2011-08-01 01:11:57 +0100
committerRobert Bragg <robert@linux.intel.com>2012-03-07 03:27:55 +0000
commitdff7660d5da96a87e25a34f5a8212e6aaea22d57 (patch)
tree14198bb30271cccf0c4b950bcca959f33ef247f4
parent762e0de2c60dff77b36e9fefefc3ad263c33ce90 (diff)
downloadclutter-wip/stereo.tar.gz
Adds initial support for stereoscopic renderingwip/stereo
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.
-rw-r--r--clutter/clutter-actor-private.h3
-rw-r--r--clutter/clutter-actor.c687
-rw-r--r--clutter/clutter-backend.c4
-rw-r--r--clutter/clutter-enums.h25
-rw-r--r--clutter/clutter-offscreen-effect.c480
-rw-r--r--clutter/clutter-paint-volume-private.h7
-rw-r--r--clutter/clutter-paint-volume.c55
-rw-r--r--clutter/clutter-private.h26
-rw-r--r--clutter/clutter-stage-private.h14
-rw-r--r--clutter/clutter-stage-window.c39
-rw-r--r--clutter/clutter-stage-window.h18
-rw-r--r--clutter/clutter-stage.c925
-rw-r--r--clutter/clutter-stage.h5
-rw-r--r--clutter/clutter-texture.c54
-rw-r--r--clutter/cogl/clutter-stage-cogl.c214
15 files changed, 1901 insertions, 655 deletions
diff --git a/clutter/clutter-actor-private.h b/clutter/clutter-actor-private.h
index 6b91405c1..4cb3e71b8 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 f84d7cf9d..92aa1b907 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 b50154a57..b56b56a47 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 40bdb6aac..644ac6865 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 09443dc35..99130a0ed 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 72bc7aee3..e2c1b7827 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 725be7917..7103aedec 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 df17d6a00..9aa6b844e 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 d2c7f7845..81da8a781 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 3ba3c9983..b6383a00c 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 041a2557c..6f938d2a5 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 123caeb09..bf2d0cafe 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_stage_set_current_camera (stage, &priv->cameras[0]);
+ _clutter_stage_flush_modelview_projection (stage);
+
+ fb = cogl_get_draw_framebuffer ();
+
+ if (anaglyph)
+ cogl_framebuffer_set_color_mask (fb, COGL_COLOR_MASK_RED);
+
+ _clutter_stage_window_redraw_without_swap (priv->impl);
+
+ _clutter_stage_set_current_camera (stage, &priv->cameras[1]);
+ _clutter_stage_flush_modelview_projection (stage);
+ if (anaglyph)
+ cogl_framebuffer_set_color_mask (fb,
+ COGL_COLOR_MASK_GREEN |
+ COGL_COLOR_MASK_BLUE);
+ else
+ {
+ /* XXX: For now we scissor the right-eye render so that the stage
+ * clear doesn't clobber the rendering of the left-eye, but later it
+ * might be better to instead add a mechanism for painting the stage
+ * except without clearing the color buffer. */
+ cogl_framebuffer_get_viewport4fv (fb, viewport);
+ cogl_clip_push_window_rectangle (viewport[0], viewport[1],
+ viewport[2], viewport[3]);
+ }
+
+ CLUTTER_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_COUNTER_INC (_clutter_uprof_context, redraw_counter);
- CLUTTER_TIMER_START (_clutter_uprof_context, redraw_timer);
+ _clutter_stage_window_redraw (priv->impl);
- _clutter_stage_window_redraw (priv->impl);
+ CLUTTER_TIMER_STOP (_clutter_uprof_context, redraw_timer);
+ }
- 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;
@@ -2387,33 +2888,6 @@ clutter_stage_get_color (ClutterStage *stage,
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_set_perspective:
* @stage: A #ClutterStage
@@ -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 e3c1d7e9c..7e91a392d 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 c521c807f..c5257bfb6 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 618723ac1..2b17baecb 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;
}