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