diff --git a/clutter/clutter-actor.c b/clutter/clutter-actor.c
index 580e6f469..2e0be7df4 100644
--- a/clutter/clutter-actor.c
+++ b/clutter/clutter-actor.c
@@ -312,37 +312,6 @@ typedef struct _AnchorCoord AnchorCoord;
#define CLUTTER_ACTOR_GET_PRIVATE(obj) \
(G_TYPE_INSTANCE_GET_PRIVATE ((obj), CLUTTER_TYPE_ACTOR, ClutterActorPrivate))
-/**
- * ClutterPaintVolume:
- *
- * ClutterPaintVolume is an opaque structure whose
- * members cannot be directly accessed
- *
- * Since: 1.4
- */
-struct _ClutterPaintVolume
-{
- ClutterActor *actor;
-
- /* cuboid for the volume:
- *
- * 0: origin ┐
- * 1: width │→ plane[0], Z value = 0
- * 2: height ┘
- *
- * 3. depth → X = 0, Y = 0, Z = @actor:depth
- *
- * 4: anti-origin ┐
- * 5: width │→ plane[1], Z value = @actor:depth
- * 6: height ┘
- *
- * 7: depth → X = anti-origin.x, Y = anti-origin.y, Z = @actor:depth
- *
- * the first four elements are filled in by the PaintVolume setters
- */
- ClutterVertex vertices[8];
-};
-
/* Internal helper struct to represent a point that can be stored in
either direct pixel coordinates or as a fraction of the actor's
size. It is used for the anchor point, scale center and rotation
@@ -495,6 +464,11 @@ struct _ClutterActorPrivate
ClutterMetaGroup *effects;
ClutterActorMeta *current_effect;
+
+ gboolean paint_volume_valid;
+ ClutterPaintVolume paint_volume;
+
+ gboolean paint_volume_disabled;
};
enum
@@ -672,6 +646,9 @@ static void _clutter_actor_get_relative_modelview (ClutterActor *self,
ClutterActor *ancestor,
CoglMatrix *matrix);
+static ClutterPaintVolume *_clutter_actor_get_paint_volume_mutable (ClutterActor *self);
+static void _clutter_paint_volume_complete (ClutterPaintVolume *pv);
+
/* Helper macro which translates by the anchor coord, applies the
given transformation and then translates back */
#define TRANSFORM_ABOUT_ANCHOR_COORD(a,m,c,_transform) G_STMT_START { \
@@ -2509,6 +2486,36 @@ clutter_actor_paint (ClutterActor *self)
CLUTTER_COUNTER_INC (_clutter_uprof_context, actor_paint_counter);
+ /* Check if there are any handlers connected to the paint
+ * signal. If there are then all bets are off for what the paint
+ * volume for this actor might possibly be!
+ *
+ * Actually it's a bit late to find this out because the redraw
+ * may have already had a scissor set on the stage based on the
+ * projected paint volume of this actor, but since glib doesn't
+ * provide a mechanism to notify when signal handlers are
+ * connected and removed for a signal we don't have many options
+ * and being able to clip redraws is a crucial optimization.
+ *
+ * If we ever find that a handler is connected we simply mark
+ * self->priv->paint_volume_disabled = TRUE which will mean the
+ * actor will always report a NULL (un-determined) paint volume.
+ * We also queue another redraw of this actor which will result
+ * in a full stage redraw. The status is never reverted to FALSE
+ * because we don't want to end up in a situation where we see
+ * lots of artefacts caused by intermittent periods with no
+ * handlers followed by periods with handlers that paint outside
+ * the paint volume.
+ */
+ if (g_signal_has_handler_pending (self,
+ actor_signals[PAINT],
+ 0,
+ TRUE))
+ {
+ priv->paint_volume_disabled = TRUE;
+ clutter_actor_queue_redraw (self);
+ }
+
if (priv->effects != NULL)
effect_painted = _clutter_actor_effects_pre_paint (self);
else if (priv->shader_data != NULL)
@@ -3268,19 +3275,23 @@ atk_implementor_iface_init (AtkImplementorIface *iface)
iface->ref_accessible = _clutter_actor_ref_accessible;
}
-static void
+static gboolean
clutter_actor_real_get_paint_volume (ClutterActor *self,
ClutterPaintVolume *volume)
{
ClutterActorPrivate *priv = self->priv;
gfloat width, height;
+ if (G_UNLIKELY (priv->needs_allocation))
+ return FALSE;
+
/* the default origin is set to { 0, 0, 0 } */
clutter_actor_box_get_size (&priv->allocation, &width, &height);
clutter_paint_volume_set_width (volume, width);
clutter_paint_volume_set_height (volume, height);
+ /* the default depth will be 0 since most actors are 2D */
- clutter_paint_volume_set_depth (volume, priv->z);
+ return TRUE;
}
static void
@@ -11558,14 +11569,68 @@ _clutter_paint_volume_new (ClutterActor *actor)
{
ClutterPaintVolume *pv;
+ g_return_val_if_fail (actor != NULL, NULL);
+
pv = g_slice_new (ClutterPaintVolume);
- pv->actor = (actor != NULL) ? g_object_ref (actor) : NULL;
+
+ pv->actor = g_object_ref (actor);
memset (pv->vertices, 0, 8 * sizeof (ClutterVertex));
+ pv->is_static = FALSE;
+ pv->is_empty = TRUE;
+ pv->is_axis_aligned = TRUE;
+ pv->is_complete = TRUE;
+ pv->is_2d = TRUE;
+
return pv;
}
+/* Since paint volumes are used so heavily in a typical paint
+ * traversal of a Clutter scene graph and since paint volumes often
+ * have a very short life cycle that maps well to stack allocation we
+ * allow initializing a static ClutterPaintVolume variable to avoid
+ * hammering the slice allocator.
+ *
+ * We were seeing slice allocation take about 1% cumulative CPU time
+ * for some very simple clutter tests which although it isn't a *lot*
+ * this is an easy way to basically drop that to 0%.
+ *
+ * The PaintVolume will be internally marked as static and
+ * clutter_paint_volume_free should still be used to "free" static
+ * volumes. This allows us to potentially store dynamically allocated
+ * data inside paint volumes in the future since we would be able to
+ * free it during _paint_volume_free().
+ */
+void
+_clutter_paint_volume_init_static (ClutterActor *actor,
+ ClutterPaintVolume *pv)
+{
+ g_return_if_fail (actor != NULL);
+
+ pv->actor = g_object_ref (actor);
+
+ memset (pv->vertices, 0, 8 * sizeof (ClutterVertex));
+
+ pv->is_static = TRUE;
+ pv->is_empty = TRUE;
+ pv->is_axis_aligned = TRUE;
+ pv->is_complete = TRUE;
+ pv->is_2d = TRUE;
+}
+
+void
+_clutter_paint_volume_copy_static (const ClutterPaintVolume *src_pv,
+ ClutterPaintVolume *dst_pv)
+{
+
+ g_return_if_fail (src_pv != NULL && dst_pv != NULL);
+
+ memcpy (dst_pv, src_pv, sizeof (ClutterPaintVolume));
+ g_object_ref (dst_pv->actor);
+ dst_pv->is_static = TRUE;
+}
+
/**
* clutter_paint_volume_copy:
* @pv: a #ClutterPaintVolume
@@ -11581,11 +11646,15 @@ clutter_paint_volume_copy (const ClutterPaintVolume *pv)
{
ClutterPaintVolume *copy;
+ /* XXX: can we just g_return_val_if_fail instead‽ */
if (G_UNLIKELY (pv == NULL))
return NULL;
copy = g_slice_dup (ClutterPaintVolume, pv);
- copy->actor = g_object_ref (pv->actor);
+ if (copy->actor)
+ g_object_ref (copy->actor);
+
+ copy->is_static = FALSE;
return copy;
}
@@ -11601,13 +11670,14 @@ clutter_paint_volume_copy (const ClutterPaintVolume *pv)
void
clutter_paint_volume_free (ClutterPaintVolume *pv)
{
- if (G_UNLIKELY (pv == NULL))
- {
- if (pv->actor != NULL)
- g_object_unref (pv->actor);
+ g_return_if_fail (pv != NULL);
- g_slice_free (ClutterPaintVolume, pv);
- }
+ g_object_unref (pv->actor);
+
+ if (G_LIKELY (pv->is_static))
+ return;
+
+ g_slice_free (ClutterPaintVolume, pv);
}
/**
@@ -11617,8 +11687,8 @@ clutter_paint_volume_free (ClutterPaintVolume *pv)
*
* Sets the origin of the paint volume.
*
- * The origin is defined as the X and Y coordinates of the top-left corner
- * of an actor's paint volume, with a Z coordinate of 0.
+ * The origin is defined as the X, Y and Z coordinates of the top-left
+ * corner of an actor's paint volume, in actor coordinates.
*
* The default is origin is assumed at: (0, 0, 0)
*
@@ -11628,9 +11698,25 @@ void
clutter_paint_volume_set_origin (ClutterPaintVolume *pv,
const ClutterVertex *origin)
{
- g_return_if_fail (pv != NULL);
+ float dx = origin->x - pv->vertices[0].x;
+ float dy = origin->y - pv->vertices[0].y;
+ float dz = origin->z - pv->vertices[0].z;
+ int key_vertices[4] = {0, 1, 3, 4};
+ int i;
- pv->vertices[0] = *origin;
+ g_return_if_fail (pv != NULL);
+ g_return_if_fail (pv->is_axis_aligned);
+
+ /* If we change the origin then all the key vertices of the paint
+ * volume need to be shifted too... */
+ for (i = 0; i < 4; i++)
+ {
+ pv->vertices[key_vertices[i]].x += dx;
+ pv->vertices[key_vertices[i]].y += dy;
+ pv->vertices[key_vertices[i]].z += dz;
+ }
+
+ pv->is_complete = FALSE;
}
/**
@@ -11652,16 +11738,23 @@ clutter_paint_volume_get_origin (const ClutterPaintVolume *pv,
*vertex = pv->vertices[0];
}
+static void
+_clutter_paint_volume_update_is_empty (ClutterPaintVolume *pv)
+{
+ if (pv->vertices[0].x == pv->vertices[1].x &&
+ pv->vertices[0].y == pv->vertices[3].y &&
+ pv->vertices[0].z == pv->vertices[4].z)
+ pv->is_empty = TRUE;
+ else
+ pv->is_empty = FALSE;
+}
+
/**
* clutter_paint_volume_set_width:
* @pv: a #ClutterPaintVolume
- * @width: the width vector of the paint volume, in pixels
+ * @width: the width of the paint volume, in pixels
*
- * Sets the magnitude of the width vector of the paint volume.
- *
- * The width vector is defined as the vector that has the initial
- * point in the origin, the sense of the X axis and the magnitude
- * as the horizontal span of an actor.
+ * Sets the width of the paint volume.
*
* Since: 1.4
*/
@@ -11669,10 +11762,29 @@ void
clutter_paint_volume_set_width (ClutterPaintVolume *pv,
gfloat width)
{
+ gfloat right_xpos;
+
g_return_if_fail (pv != NULL);
+ g_return_if_fail (pv->is_axis_aligned);
g_return_if_fail (width >= 0.0f);
- pv->vertices[1].x = width;
+ /* If the volume is currently empty then only the origin is
+ * currently valid */
+ if (pv->is_empty)
+ pv->vertices[1] = pv->vertices[3] = pv->vertices[4] = pv->vertices[0];
+
+ right_xpos = pv->vertices[0].x + width;
+
+ /* Move the right vertices of the paint box relative to the
+ * origin... */
+ pv->vertices[1].x = right_xpos;
+ /* pv->vertices[2].x = right_xpos; NB: updated lazily */
+ /* pv->vertices[5].x = right_xpos; NB: updated lazily */
+ /* pv->vertices[6].x = right_xpos; NB: updated lazily */
+
+ pv->is_complete = FALSE;
+
+ _clutter_paint_volume_update_is_empty (pv);
}
/**
@@ -11689,20 +11801,20 @@ gfloat
clutter_paint_volume_get_width (const ClutterPaintVolume *pv)
{
g_return_val_if_fail (pv != NULL, 0.0);
+ g_return_val_if_fail (pv->is_axis_aligned, 0);
- return pv->vertices[1].x;
+ if (pv->is_empty)
+ return 0;
+ else
+ return pv->vertices[1].x - pv->vertices[0].x;
}
/**
* clutter_paint_volume_set_height:
* @pv: a #ClutterPaintVolume
- * @height: the magnitude of the height vector of the paint volume, in pixels
+ * @height: the height of the paint volume, in pixels
*
- * Sets the magnitude of the height vector of the paint volume.
- *
- * The height vector is defined as the vector that has the initial
- * point in the origin, the sense of the Y axis and the magnitude
- * as the vertical span of an actor.
+ * Sets the height of the paint volume.
*
* Since: 1.4
*/
@@ -11710,19 +11822,38 @@ void
clutter_paint_volume_set_height (ClutterPaintVolume *pv,
gfloat height)
{
+ gfloat height_ypos;
+
g_return_if_fail (pv != NULL);
+ g_return_if_fail (pv->is_axis_aligned);
g_return_if_fail (height >= 0.0f);
- pv->vertices[2].y = height;
+ /* If the volume is currently empty then only the origin is
+ * currently valid */
+ if (pv->is_empty)
+ pv->vertices[1] = pv->vertices[3] = pv->vertices[4] = pv->vertices[0];
+
+ height_ypos = pv->vertices[0].y + height;
+
+ /* Move the bottom vertices of the paint box relative to the
+ * origin... */
+ /* pv->vertices[2].y = height_ypos; NB: updated lazily */
+ pv->vertices[3].y = height_ypos;
+ /* pv->vertices[6].y = height_ypos; NB: updated lazily */
+ /* pv->vertices[7].y = height_ypos; NB: updated lazily */
+ pv->is_complete = FALSE;
+
+ _clutter_paint_volume_update_is_empty (pv);
}
/**
* clutter_paint_volume_get_height:
* @pv: a #ClutterPaintVolume
*
- * Retrieves the height set using clutter_paint_volume_get_height()
+ * Retrieves the height of the paint volume set using
+ * clutter_paint_volume_get_height()
*
- * Return value: the height, in pixels
+ * Return value: the height of the paint volume, in pixels
*
* Since: 1.4
*/
@@ -11730,20 +11861,20 @@ gfloat
clutter_paint_volume_get_height (const ClutterPaintVolume *pv)
{
g_return_val_if_fail (pv != NULL, 0.0);
+ g_return_val_if_fail (pv->is_axis_aligned, 0);
- return pv->vertices[2].y;
+ if (pv->is_empty)
+ return 0;
+ else
+ return pv->vertices[3].y - pv->vertices[0].y;
}
/**
* clutter_paint_volume_set_depth:
* @pv: a #ClutterPaintVolume
- * @depth: the magnitude of the depth vector of the paint volume
+ * @depth: the depth of the paint volume, in pixels
*
- * Sets the magnitude of the depth vector of the paint volume.
- *
- * The height vector is defined as the vector that has the initial
- * point in the origin, the sense of the Z axis and the magnitude
- * as the depth span of an actor.
+ * Sets the depth of the paint volume.
*
* Since: 1.4
*/
@@ -11751,16 +11882,37 @@ void
clutter_paint_volume_set_depth (ClutterPaintVolume *pv,
gfloat depth)
{
- g_return_if_fail (pv != NULL);
+ gfloat depth_zpos;
- pv->vertices[3].z = depth;
+ g_return_if_fail (pv != NULL);
+ g_return_if_fail (pv->is_axis_aligned);
+ g_return_if_fail (depth >= 0.0f);
+
+ /* If the volume is currently empty then only the origin is
+ * currently valid */
+ if (pv->is_empty)
+ pv->vertices[1] = pv->vertices[3] = pv->vertices[4] = pv->vertices[0];
+
+ depth_zpos = pv->vertices[0].z + depth;
+
+ /* Move the back vertices of the paint box relative to the
+ * origin... */
+ pv->vertices[4].z = depth_zpos;
+ /* pv->vertices[5].z = depth_zpos; NB: updated lazily */
+ /* pv->vertices[6].z = depth_zpos; NB: updated lazily */
+ /* pv->vertices[7].z = depth_zpos; NB: updated lazily */
+
+ pv->is_complete = FALSE;
+ pv->is_2d = depth ? FALSE : TRUE;
+ _clutter_paint_volume_update_is_empty (pv);
}
/**
* clutter_paint_volume_get_depth:
* @pv: a #ClutterPaintVolume
*
- * Retrieves the depth set using clutter_paint_volume_get_depth()
+ * Retrieves the depth of the paint volume set using
+ * clutter_paint_volume_get_depth()
*
* Return value: the depth
*
@@ -11770,125 +11922,442 @@ gfloat
clutter_paint_volume_get_depth (const ClutterPaintVolume *pv)
{
g_return_val_if_fail (pv != NULL, 0.0);
+ g_return_val_if_fail (pv->is_axis_aligned, 0);
- return pv->vertices[3].z;
+ if (pv->is_empty)
+ return 0;
+ else
+ return pv->vertices[4].z - pv->vertices[0].z;
+}
+
+/**
+ * clutter_paint_volume_union:
+ * @pv: The first #ClutterPaintVolume and destination for resulting
+ * union
+ * @another_pv: A second #ClutterPaintVolume to union with @pv
+ *
+ * Updates the geometry of @pv to be the union bounding box that
+ * encompases @pv and @another_pv.
+ *
+ * Since: 1.4
+ */
+void
+clutter_paint_volume_union (ClutterPaintVolume *pv,
+ const ClutterPaintVolume *another_pv)
+{
+ int key_vertices[4] = {0, 1, 3, 4};
+
+ g_return_if_fail (pv != NULL);
+ g_return_if_fail (pv->is_axis_aligned);
+ g_return_if_fail (another_pv != NULL);
+ g_return_if_fail (another_pv->is_axis_aligned);
+
+ /* NB: we only have to update vertices 0, 1, 3 and 4
+ * (See the ClutterPaintVolume typedef for more details) */
+
+ /* We special case empty volumes because otherwise we'd end up
+ * calculating a bounding box that would enclose the origin of
+ * the empty volume which isn't desired.
+ */
+ if (another_pv->is_empty)
+ return;
+
+ if (pv->is_empty)
+ {
+ int i;
+ for (i = 0; i < 4; i++)
+ pv->vertices[key_vertices[i]] = another_pv->vertices[key_vertices[i]];
+ pv->is_2d = another_pv->is_2d;
+ goto done;
+ }
+
+ /* grow left*/
+ /* left vertices 0, 3, 4, 7 */
+ if (another_pv->vertices[0].x < pv->vertices[0].x)
+ {
+ int min_x = another_pv->vertices[0].x;
+ pv->vertices[0].x = min_x;
+ pv->vertices[3].x = min_x;
+ pv->vertices[4].x = min_x;
+ /* pv->vertices[7].x = min_x; */
+ }
+
+ /* grow right */
+ /* right vertices 1, 2, 5, 6 */
+ if (another_pv->vertices[1].x > pv->vertices[1].x)
+ {
+ int max_x = another_pv->vertices[1].x;
+ pv->vertices[1].x = max_x;
+ /* pv->vertices[2].x = max_x; */
+ /* pv->vertices[5].x = max_x; */
+ /* pv->vertices[6].x = max_x; */
+ }
+
+ /* grow up */
+ /* top vertices 0, 1, 4, 5 */
+ if (another_pv->vertices[0].y < pv->vertices[0].y)
+ {
+ int min_y = another_pv->vertices[0].y;
+ pv->vertices[0].y = min_y;
+ pv->vertices[1].y = min_y;
+ pv->vertices[4].y = min_y;
+ /* pv->vertices[5].y = min_y; */
+ }
+
+ /* grow down */
+ /* bottom vertices 2, 3, 6, 7 */
+ if (another_pv->vertices[3].y > pv->vertices[3].y)
+ {
+ int may_y = another_pv->vertices[3].y;
+ /* pv->vertices[2].y = may_y; */
+ pv->vertices[3].y = may_y;
+ /* pv->vertices[6].y = may_y; */
+ /* pv->vertices[7].y = may_y; */
+ }
+
+ /* grow forward */
+ /* front vertices 0, 1, 2, 3 */
+ if (another_pv->vertices[0].z < pv->vertices[0].z)
+ {
+ int min_z = another_pv->vertices[0].z;
+ pv->vertices[0].z = min_z;
+ pv->vertices[1].z = min_z;
+ /* pv->vertices[2].z = min_z; */
+ pv->vertices[3].z = min_z;
+ }
+
+ /* grow backward */
+ /* back vertices 4, 5, 6, 7 */
+ if (another_pv->vertices[4].z > pv->vertices[4].z)
+ {
+ int maz_z = another_pv->vertices[4].z;
+ pv->vertices[4].z = maz_z;
+ /* pv->vertices[5].z = maz_z; */
+ /* pv->vertices[6].z = maz_z; */
+ /* pv->vertices[7].z = maz_z; */
+ }
+
+ if (pv->vertices[4].z == pv->vertices[0].z)
+ pv->is_2d = TRUE;
+ else
+ pv->is_2d = FALSE;
+
+done:
+ pv->is_empty = FALSE;
+ pv->is_complete = FALSE;
+}
+
+/* The paint_volume setters only update vertices 0, 1, 3 and
+ * 4 since the others can be drived from them.
+ *
+ * This will set pv->completed = TRUE;
+ */
+static void
+_clutter_paint_volume_complete (ClutterPaintVolume *pv)
+{
+ if (pv->is_complete || pv->is_empty)
+ return;
+
+ g_return_if_fail (pv->is_axis_aligned);
+
+ /* front-bottom-right */
+ pv->vertices[2].x = pv->vertices[1].x;
+ pv->vertices[2].y = pv->vertices[3].y;
+ pv->vertices[2].z = pv->vertices[0].z;
+
+ if (G_UNLIKELY (!pv->is_2d))
+ {
+ /* back-top-right */
+ pv->vertices[5].x = pv->vertices[1].x;
+ pv->vertices[5].y = pv->vertices[0].y;
+ pv->vertices[5].z = pv->vertices[4].z;
+
+ /* back-bottom-right */
+ pv->vertices[6].x = pv->vertices[1].x;
+ pv->vertices[6].y = pv->vertices[3].y;
+ pv->vertices[6].z = pv->vertices[4].z;
+
+ /* back-bottom-left */
+ pv->vertices[7].x = pv->vertices[0].x;
+ pv->vertices[7].y = pv->vertices[3].y;
+ pv->vertices[7].z = pv->vertices[4].z;
+ }
+
+ pv->is_complete = TRUE;
}
/*
* _clutter_paint_volume_get_box:
* @pv: a #ClutterPaintVolume
- * @box: a #ClutterActorBox
+ * @box: a pixel aligned #ClutterGeometry
*
- * Transforms a 3D paint volume into a 2D bounding box
+ * Transforms a 3D paint volume into a 2D bounding box in the
+ * same coordinate space as the 3D paint volume.
+ *
+ * To get an actors "paint box" you should first project
+ * the paint volume into window coordinates before getting
+ * the 2D bounding box.
+ *
+ * The coordinates of the returned box are not clamped to
+ * integer pixel values, if you need them to be clamped you can use
+ * clutter_actor_box_clamp_to_pixel()
*
* Since: 1.4
*/
void
-_clutter_paint_volume_get_box (ClutterPaintVolume *pv,
- ClutterActorBox *box)
+_clutter_paint_volume_get_bounding_box (ClutterPaintVolume *pv,
+ ClutterActorBox *box)
{
- ClutterVertex screen_v[8];
gfloat x_min, y_min, x_max, y_max;
+ ClutterVertex *vertices;
+ int count;
gint i;
g_return_if_fail (pv != NULL);
g_return_if_fail (box != NULL);
- if (pv->actor == NULL)
+ if (pv->is_empty)
{
- g_warning ("Paint volume created without a reference actor");
+ box->x1 = box->x2 = pv->vertices[0].x;
+ box->y1 = box->y2 = pv->vertices[0].y;
return;
}
- /* anti-origin */
- pv->vertices[4].x = pv->vertices[1].x;
- pv->vertices[4].y = pv->vertices[2].y;
- pv->vertices[4].z = 0.0f;
+ /* Updates the vertices we calculate lazily
+ * (See ClutterPaintVolume typedef for more details) */
+ _clutter_paint_volume_complete (pv);
- if (pv->vertices[3].z < 0.00001 || pv->vertices[3].z > 0.00001)
+ vertices = pv->vertices;
+
+ x_min = x_max = vertices[0].x;
+ y_min = y_max = vertices[0].y;
+
+ /* Most actors are 2D so we only have to look at the front 4
+ * vertices of the paint volume... */
+ if (G_LIKELY (pv->is_2d))
+ count = 4;
+ else
+ count = 8;
+
+ for (i = 1; i < count; i++)
{
- pv->vertices[5].x = pv->vertices[1].x;
- pv->vertices[5].y = pv->vertices[0].y;
- pv->vertices[5].z = pv->vertices[3].z;
+ if (vertices[i].x < x_min)
+ x_min = vertices[i].x;
+ else if (vertices[i].x > x_max)
+ x_max = vertices[i].x;
- pv->vertices[6].x = pv->vertices[0].x;
- pv->vertices[6].y = pv->vertices[2].y;
- pv->vertices[6].z = pv->vertices[3].z;
-
- pv->vertices[7].x = pv->vertices[1].x;
- pv->vertices[7].y = pv->vertices[2].y;
- pv->vertices[7].z = pv->vertices[3].z;
- }
-
- if (!_clutter_actor_fully_transform_vertices (pv->actor,
- pv->vertices,
- screen_v,
- G_N_ELEMENTS (screen_v)))
- return;
-
- box->x1 = box->y1 = box->x2 = box->y2 = 0.0f;
-
- x_min = y_min = G_MAXFLOAT;
- x_max = y_max = -G_MAXFLOAT;
-
- for (i = 0; i < G_N_ELEMENTS (screen_v); i++)
- {
- if (screen_v[i].x < x_min)
- x_min = screen_v[i].x;
-
- if (screen_v[i].x > x_max)
- x_max = screen_v[i].x;
-
- if (screen_v[i].y < y_min)
- y_min = screen_v[i].y;
-
- if (screen_v[i].y > y_max)
- y_max = screen_v[i].y;
+ if (vertices[i].y < y_min)
+ y_min = vertices[i].y;
+ else if (vertices[i].y > y_max)
+ y_max = vertices[i].y;
}
box->x1 = x_min;
box->y1 = y_min;
box->x2 = x_max;
box->y2 = y_max;
- clutter_actor_box_clamp_to_pixel (box);
}
-/**
- * clutter_actor_get_paint_volume:
- * @self: a #ClutterActor
- *
- * Retrieves the paint volume of the passed #ClutterActor.
- *
- * The paint volume is defined as the 3D space occupied by an actor
- * when being painted.
- *
- * This function will call the get_paint_volume()
- * virtual function of the #ClutterActor class. Sub-classes of #ClutterActor
- * should not usually care about overriding the default implementation,
- * unless they are, for instance, painting outside their allocation area.
- *
- * Overriding the get_paint_box() for 2D actors
- * should set a degenerate depth vector using the same value as the Z
- * coordinate of the origin.
- *
- * Return value: (transfer full): a newly allocated #ClutterPaintVolume.
- * Use clutter_paint_volume_free() to free the resources when done.
- *
- * Since: 1.4
+void
+_clutter_paint_volume_project (ClutterPaintVolume *pv,
+ const CoglMatrix *modelview,
+ const CoglMatrix *projection,
+ const int *viewport)
+{
+ int transform_count;
+
+ if (pv->is_empty)
+ {
+ /* Just transform the origin... */
+ _fully_transform_vertices (modelview,
+ projection,
+ viewport,
+ pv->vertices,
+ pv->vertices,
+ 1);
+ return;
+ }
+
+ /* All the vertices must be up to date, since after the projection
+ * it wont be trivial to derive the other vertices. */
+ _clutter_paint_volume_complete (pv);
+
+ /* Most actors are 2D so we only have to transform the front 4
+ * vertices of the paint volume... */
+ if (G_LIKELY (pv->is_2d))
+ transform_count = 4;
+ else
+ transform_count = 8;
+
+ _fully_transform_vertices (modelview,
+ projection,
+ viewport,
+ pv->vertices,
+ pv->vertices,
+ transform_count);
+
+ pv->is_axis_aligned = FALSE;
+}
+
+static void
+_clutter_paint_volume_transform (ClutterPaintVolume *pv,
+ const CoglMatrix *matrix)
+{
+ int transform_count;
+ int i;
+
+ if (pv->is_empty)
+ {
+ gfloat w = 1;
+ /* Just transform the origin */
+ cogl_matrix_transform_point (matrix,
+ &pv->vertices[0].x,
+ &pv->vertices[0].y,
+ &pv->vertices[0].z,
+ &w);
+ return;
+ }
+
+ /* All the vertices must be up to date, since after the transform
+ * it wont be trivial to derive the other vertices. */
+ _clutter_paint_volume_complete (pv);
+
+ /* Most actors are 2D so we only have to transform the front 4
+ * vertices of the paint volume... */
+ if (G_LIKELY (pv->is_2d))
+ transform_count = 4;
+ else
+ transform_count = 8;
+
+
+ for (i = 0; i < transform_count; i++)
+ {
+ gfloat w = 1;
+ cogl_matrix_transform_point (matrix,
+ &pv->vertices[i].x,
+ &pv->vertices[i].y,
+ &pv->vertices[i].z,
+ &w);
+ }
+
+ pv->is_axis_aligned = FALSE;
+}
+
+
+/* Given a paint volume that has been transformed by an arbitrary
+ * modelview and is no longer axis aligned, this derives a replacement
+ * that is axis aligned. */
+static void
+_clutter_paint_volume_axis_align (ClutterPaintVolume *pv)
+{
+ int count;
+ int i;
+ ClutterVertex origin;
+ float max_x;
+ float max_y;
+ float max_z;
+
+ if (pv->is_empty)
+ return;
+
+ g_return_if_fail (pv->is_complete);
+
+ if (G_LIKELY (pv->is_axis_aligned))
+ return;
+
+ if (G_LIKELY (pv->vertices[0].x == pv->vertices[1].x &&
+ pv->vertices[0].y == pv->vertices[3].y &&
+ pv->vertices[0].z == pv->vertices[4].y))
+ {
+ pv->is_axis_aligned = TRUE;
+ return;
+ }
+
+ origin = pv->vertices[0];
+ max_x = pv->vertices[0].x;
+ max_y = pv->vertices[0].y;
+ max_z = pv->vertices[0].z;
+
+ count = pv->is_2d ? 4 : 8;
+ for (i = 1; i < count; i++)
+ {
+ if (pv->vertices[i].x < origin.x)
+ origin.x = pv->vertices[i].x;
+ else if (pv->vertices[i].x > max_x)
+ max_x = pv->vertices[i].x;
+
+ if (pv->vertices[i].y < origin.y)
+ origin.y = pv->vertices[i].y;
+ else if (pv->vertices[i].y > max_y)
+ max_y = pv->vertices[i].y;
+
+ if (pv->vertices[i].z < origin.z)
+ origin.z = pv->vertices[i].z;
+ else if (pv->vertices[i].z > max_z)
+ max_z = pv->vertices[i].z;
+ }
+
+ pv->vertices[0] = origin;
+
+ pv->vertices[1].x = max_x;
+ pv->vertices[1].y = origin.y;
+ pv->vertices[1].z = origin.z;
+
+ pv->vertices[3].x = origin.x;
+ pv->vertices[3].y = max_y;
+ pv->vertices[3].z = origin.z;
+
+ pv->vertices[4].x = origin.x;
+ pv->vertices[4].y = origin.y;
+ pv->vertices[4].z = max_z;
+
+ pv->is_complete = FALSE;
+ pv->is_axis_aligned = TRUE;
+
+ if (pv->vertices[4].z == pv->vertices[0].z)
+ pv->is_2d = TRUE;
+ else
+ pv->is_2d = FALSE;
+}
+
+/* The public clutter_actor_get_paint_volume API returns a const
+ * pointer since we return a pointer directly to the cached
+ * PaintVolume associated with the actor and don't want the user to
+ * inadvertently modify it, but for internal uses we sometimes need
+ * access to the same PaintVolume but need to apply some book-keeping
+ * modifications to it so we don't want a const pointer.
*/
-ClutterPaintVolume *
-clutter_actor_get_paint_volume (ClutterActor *self)
+static ClutterPaintVolume *
+_clutter_actor_get_paint_volume_mutable (ClutterActor *self)
{
ClutterActorPrivate *priv;
ClutterPaintVolume *pv;
- g_return_val_if_fail (CLUTTER_IS_ACTOR (self), NULL);
-
- pv = _clutter_paint_volume_new (self);
- CLUTTER_ACTOR_GET_CLASS (self)->get_paint_volume (self, pv);
-
priv = self->priv;
+ if (priv->paint_volume_valid)
+ {
+ clutter_paint_volume_free (&priv->paint_volume);
+ priv->paint_volume_valid = FALSE;
+ }
+
+ if (G_UNLIKELY (priv->paint_volume_disabled))
+ return NULL;
+
+ /* Actors are only expected to report a valid paint volume
+ * while they have a valid allocation. */
+ if (G_UNLIKELY (priv->needs_allocation))
+ return NULL;
+
+ pv = &priv->paint_volume;
+ _clutter_paint_volume_init_static (self, pv);
+
+ if (!CLUTTER_ACTOR_GET_CLASS (self)->get_paint_volume (self, pv))
+ {
+ clutter_paint_volume_free (pv);
+ return NULL;
+ }
+
/* since effects can modify the paint volume, we allow them to actually
* do this by making get_paint_volume() "context sensitive"
*/
@@ -11906,7 +12375,11 @@ clutter_actor_get_paint_volume (ClutterActor *self)
l != NULL || (l != NULL && l->data != priv->current_effect);
l = l->next)
{
- _clutter_effect_get_paint_volume (l->data, pv);
+ if (!_clutter_effect_get_paint_volume (l->data, pv))
+ {
+ clutter_paint_volume_free (pv);
+ return NULL;
+ }
}
}
else
@@ -11916,38 +12389,179 @@ clutter_actor_get_paint_volume (ClutterActor *self)
/* otherwise, get the cumulative volume */
effects = _clutter_meta_group_peek_metas (priv->effects);
for (l = effects; l != NULL; l = l->next)
- _clutter_effect_get_paint_volume (l->data, pv);
+ if (!_clutter_effect_get_paint_volume (l->data, pv))
+ {
+ clutter_paint_volume_free (pv);
+ return NULL;
+ }
}
}
+ priv->paint_volume_valid = TRUE;
return pv;
}
+/**
+ * clutter_actor_get_paint_volume:
+ * @self: a #ClutterActor
+ *
+ * Retrieves the paint volume of the passed #ClutterActor, or %NULL
+ * when a paint volume can't be determined.
+ *
+ * The paint volume is defined as the 3D space occupied by an actor
+ * when being painted.
+ *
+ * This function will call the get_paint_volume()
+ * virtual function of the #ClutterActor class. Sub-classes of #ClutterActor
+ * should not usually care about overriding the default implementation,
+ * unless they are, for instance: painting outside their allocation, or
+ * actors with a depth factor (not in terms of #ClutterActor:depth but real
+ * 3D depth).
+ *
+ * 2D actors overriding get_paint_volume()
+ * ensure their volume has a depth of 0. (This will be true so long as
+ * you don't call clutter_paint_volume_set_depth().)
+ *
+ * Return value: (transfer none): a pointer to a #ClutterPaintVolume
+ * or %NULL if no volume could be determined.
+ *
+ * Since: 1.4
+ */
+const ClutterPaintVolume *
+clutter_actor_get_paint_volume (ClutterActor *self)
+{
+ g_return_val_if_fail (CLUTTER_IS_ACTOR (self), NULL);
+
+ return _clutter_actor_get_paint_volume_mutable (self);
+}
+
+/**
+ * clutter_actor_get_transformed_paint_volume:
+ * @self: a #ClutterActor
+ * @relative_to_ancestor: A #ClutterActor that is an ancestor of @self
+ * (or %NULL for the stage)
+ *
+ * Retrieves the 3D paint volume of an actor like
+ * clutter_actor_get_paint_volume() does (Please refer to the
+ * documentation of clutter_actor_get_paint_volume() for more
+ * details.) and it additionally transforms the paint volume into the
+ * coordinate space of @relative_to_ancestor. (Or the stage if %NULL
+ * is passed for @relative_to_ancestor)
+ *
+ * This can be used by containers that base their paint volume on
+ * the volume of their children. Such containers can query the
+ * transformed paint volume of all of its children and union them
+ * together using clutter_paint_volume_union().
+ *
+ * Return value: (transfer none): a pointer to a #ClutterPaintVolume
+ * or %NULL if no volume could be determined.
+ *
+ * Since: 1.4
+ */
+const ClutterPaintVolume *
+clutter_actor_get_transformed_paint_volume (ClutterActor *self,
+ ClutterActor *relative_to_ancestor)
+{
+ CoglMatrix matrix;
+ const ClutterPaintVolume *volume;
+ ClutterStage *stage;
+ ClutterPaintVolume *transformed_volume;
+
+ if (relative_to_ancestor == NULL)
+ relative_to_ancestor = _clutter_actor_get_stage_internal (self);
+
+ if (relative_to_ancestor == NULL)
+ return NULL;
+
+ volume = clutter_actor_get_paint_volume (self);
+ if (!volume)
+ return NULL;
+
+ _clutter_actor_get_relative_modelview (self, relative_to_ancestor, &matrix);
+
+ stage = CLUTTER_STAGE (_clutter_actor_get_stage_internal (self));
+ transformed_volume = _clutter_stage_paint_volume_stack_allocate (stage);
+ _clutter_paint_volume_copy_static (volume, transformed_volume);
+ _clutter_paint_volume_transform (transformed_volume, &matrix);
+ _clutter_paint_volume_axis_align (transformed_volume);
+ g_object_unref (transformed_volume->actor);
+ transformed_volume->actor = g_object_ref (relative_to_ancestor);
+
+ return transformed_volume;
+}
+
/**
* clutter_actor_get_paint_box:
* @self: a #ClutterActor
* @box: (out): return location for a #ClutterActorBox
*
- * Retrieves the paint volume of the passed #ClutterActor, and transforms it
- * into a 2D bounding box in stage coordinates.
+ * Retrieves the paint volume of the passed #ClutterActor, and
+ * transforms it into a 2D bounding box in stage coordinates.
*
- * This function is useful to determine the on screen area occupied by the
- * actor.
+ * This function is useful to determine the on screen area occupied by
+ * the actor. The box is only an approximation and may often be
+ * considerably larger due to the optimizations used to calculate the
+ * box. The box is never smaller though, so it can reliably be used
+ * for culling.
+ *
+ * There are times when a 2D paint box can't be determined, e.g.
+ * because the actor isn't yet parented under a stage or because
+ * the actor is unable to determine a paint volume.
+ *
+ * Return value: %TRUE if a 2D paint box could be determined, else
+ * %FALSE.
*
* Since: 1.4
*/
-void
+gboolean
clutter_actor_get_paint_box (ClutterActor *self,
ClutterActorBox *box)
{
- ClutterPaintVolume *pv;
+ ClutterActor *stage;
+ const ClutterPaintVolume *pv;
+ CoglMatrix modelview;
+ CoglMatrix projection;
+ int viewport[4];
+ ClutterPaintVolume projected_pv;
- g_return_if_fail (CLUTTER_IS_ACTOR (self));
- g_return_if_fail (box != NULL);
+ 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);
+ if (G_UNLIKELY (!stage))
+ return FALSE;
pv = clutter_actor_get_paint_volume (self);
+ if (G_UNLIKELY (!pv))
+ return FALSE;
- _clutter_paint_volume_get_box (pv, box);
+ /* NB: _clutter_actor_apply_modelview_transform_recursive will never
+ * include the transformation between stage coordinates and OpenGL
+ * window coordinates, we have to explicitly use the
+ * stage->apply_transform to get that... */
+ cogl_matrix_init_identity (&modelview);
+ _clutter_actor_apply_modelview_transform (stage, &modelview);
+ _clutter_actor_apply_modelview_transform_recursive (pv->actor,
+ stage, &modelview);
- clutter_paint_volume_free (pv);
+ _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_paint_volume_copy_static (pv, &projected_pv);
+ _clutter_paint_volume_project (&projected_pv,
+ &modelview,
+ &projection,
+ viewport);
+
+ _clutter_paint_volume_get_bounding_box (&projected_pv, box);
+ clutter_actor_box_clamp_to_pixel (box);
+
+ clutter_paint_volume_free (&projected_pv);
+
+ return TRUE;
}
+
diff --git a/clutter/clutter-actor.h b/clutter/clutter-actor.h
index ab9306159..cea8c4b10 100644
--- a/clutter/clutter-actor.h
+++ b/clutter/clutter-actor.h
@@ -306,7 +306,7 @@ struct _ClutterActorClass
/* accessibility support */
AtkObject * (* get_accessible) (ClutterActor *actor);
- void (* get_paint_volume) (ClutterActor *actor,
+ gboolean (* get_paint_volume) (ClutterActor *actor,
ClutterPaintVolume *volume);
/*< private >*/
@@ -589,8 +589,12 @@ gboolean clutter_actor_has_allocation (ClutterActor *sel
AtkObject * clutter_actor_get_accessible (ClutterActor *self);
gboolean clutter_actor_has_key_focus (ClutterActor *self);
-ClutterPaintVolume *clutter_actor_get_paint_volume (ClutterActor *self);
-void clutter_actor_get_paint_box (ClutterActor *self,
+
+const ClutterPaintVolume *clutter_actor_get_paint_volume (ClutterActor *self);
+const ClutterPaintVolume *clutter_actor_get_transformed_paint_volume (ClutterActor *self,
+ ClutterActor *relative_to_ancestor);
+
+gboolean clutter_actor_get_paint_box (ClutterActor *self,
ClutterActorBox *box);
G_END_DECLS
diff --git a/clutter/clutter-blur-effect.c b/clutter/clutter-blur-effect.c
index 839fbb264..7d2374f6a 100644
--- a/clutter/clutter-blur-effect.c
+++ b/clutter/clutter-blur-effect.c
@@ -255,7 +255,7 @@ out:
parent->paint_target (effect);
}
-static void
+static gboolean
clutter_blur_effect_get_paint_volume (ClutterEffect *effect,
ClutterPaintVolume *volume)
{
@@ -273,6 +273,8 @@ clutter_blur_effect_get_paint_volume (ClutterEffect *effect,
clutter_paint_volume_set_origin (volume, &origin);
clutter_paint_volume_set_width (volume, cur_width);
clutter_paint_volume_set_height (volume, cur_height);
+
+ return TRUE;
}
static void
diff --git a/clutter/clutter-effect.c b/clutter/clutter-effect.c
index f920a75f1..1a11e06a9 100644
--- a/clutter/clutter-effect.c
+++ b/clutter/clutter-effect.c
@@ -191,10 +191,11 @@ clutter_effect_real_post_paint (ClutterEffect *effect)
{
}
-static void
+static gboolean
clutter_effect_real_get_paint_volume (ClutterEffect *effect,
ClutterPaintVolume *volume)
{
+ return TRUE;
}
static void
@@ -226,12 +227,12 @@ _clutter_effect_post_paint (ClutterEffect *effect)
CLUTTER_EFFECT_GET_CLASS (effect)->post_paint (effect);
}
-void
+gboolean
_clutter_effect_get_paint_volume (ClutterEffect *effect,
ClutterPaintVolume *volume)
{
- g_return_if_fail (CLUTTER_IS_EFFECT (effect));
- g_return_if_fail (volume != NULL);
+ g_return_val_if_fail (CLUTTER_IS_EFFECT (effect), FALSE);
+ g_return_val_if_fail (volume != NULL, FALSE);
- CLUTTER_EFFECT_GET_CLASS (effect)->get_paint_volume (effect, volume);
+ return CLUTTER_EFFECT_GET_CLASS (effect)->get_paint_volume (effect, volume);
}
diff --git a/clutter/clutter-effect.h b/clutter/clutter-effect.h
index 6db925569..c69cdc9d5 100644
--- a/clutter/clutter-effect.h
+++ b/clutter/clutter-effect.h
@@ -75,7 +75,7 @@ struct _ClutterEffectClass
gboolean (* pre_paint) (ClutterEffect *effect);
void (* post_paint) (ClutterEffect *effect);
- void (* get_paint_volume) (ClutterEffect *effect,
+ gboolean (* get_paint_volume) (ClutterEffect *effect,
ClutterPaintVolume *volume);
/*< private >*/
diff --git a/clutter/clutter-group.c b/clutter/clutter-group.c
index 363dd1ccc..d6c98973e 100644
--- a/clutter/clutter-group.c
+++ b/clutter/clutter-group.c
@@ -386,8 +386,32 @@ clutter_group_real_hide_all (ClutterActor *actor)
NULL);
}
+static gboolean
+clutter_group_real_get_paint_volume (ClutterActor *actor,
+ ClutterPaintVolume *volume)
+{
+ ClutterGroupPrivate *priv = CLUTTER_GROUP (actor)->priv;
+ GList *l;
+ if (priv->children == NULL)
+ return TRUE;
+ for (l = priv->children; l != NULL; l = l->next)
+ {
+ ClutterActor *child = l->data;
+ const ClutterPaintVolume *child_volume;
+
+ /* This gets the paint volume of the child transformed into the
+ * group's coordinate space... */
+ child_volume = clutter_actor_get_transformed_paint_volume (child, actor);
+ if (!child_volume)
+ return FALSE;
+
+ clutter_paint_volume_union (volume, child_volume);
+ }
+
+ return TRUE;
+}
static void
clutter_group_class_init (ClutterGroupClass *klass)
@@ -404,6 +428,7 @@ clutter_group_class_init (ClutterGroupClass *klass)
actor_class->pick = clutter_group_real_pick;
actor_class->show_all = clutter_group_real_show_all;
actor_class->hide_all = clutter_group_real_hide_all;
+ actor_class->get_paint_volume = clutter_group_real_get_paint_volume;
gobject_class->dispose = clutter_group_dispose;
diff --git a/clutter/clutter-main.c b/clutter/clutter-main.c
index 7ed35d822..da62c2a1a 100644
--- a/clutter/clutter-main.c
+++ b/clutter/clutter-main.c
@@ -665,7 +665,7 @@ _clutter_do_pick (ClutterStage *stage,
*/
CLUTTER_TIMER_START (_clutter_uprof_context, pick_paint);
context->pick_mode = mode;
- clutter_actor_paint (CLUTTER_ACTOR (stage));
+ _clutter_stage_do_paint (stage);
context->pick_mode = CLUTTER_PICK_NONE;
CLUTTER_TIMER_STOP (_clutter_uprof_context, pick_paint);
diff --git a/clutter/clutter-offscreen-effect.c b/clutter/clutter-offscreen-effect.c
index 2e324256e..045698f3e 100644
--- a/clutter/clutter-offscreen-effect.c
+++ b/clutter/clutter-offscreen-effect.c
@@ -130,11 +130,10 @@ clutter_offscreen_effect_real_create_texture (ClutterOffscreenEffect *effect,
}
static gboolean
-update_fbo (ClutterEffect *effect)
+update_fbo (ClutterEffect *effect, int fbo_width, int fbo_height)
{
ClutterOffscreenEffect *self = CLUTTER_OFFSCREEN_EFFECT (effect);
ClutterOffscreenEffectPrivate *priv = self->priv;
- gfloat width, height;
CoglHandle texture;
priv->stage = clutter_actor_get_stage (priv->actor);
@@ -147,21 +146,8 @@ update_fbo (ClutterEffect *effect)
return FALSE;
}
- /* the target should at least be big enough to contain the
- * transformed allocation of the actor
- *
- * FIXME - this is actually not enough: we need the paint area
- * to make this work reliably
- */
- {
- ClutterActorBox box = { 0, };
-
- clutter_actor_get_paint_box (priv->actor, &box);
- clutter_actor_box_get_size (&box, &width, &height);
- }
-
- if (priv->target_width == width &&
- priv->target_height == height)
+ if (priv->target_width == fbo_width &&
+ priv->target_height == fbo_height)
return TRUE;
if (priv->target != COGL_INVALID_HANDLE)
@@ -172,7 +158,8 @@ update_fbo (ClutterEffect *effect)
priv->target = cogl_material_new ();
- texture = clutter_offscreen_effect_create_texture (self, width, height);
+ texture =
+ clutter_offscreen_effect_create_texture (self, fbo_width, fbo_height);
if (texture == COGL_INVALID_HANDLE)
return FALSE;
@@ -203,49 +190,16 @@ update_fbo (ClutterEffect *effect)
return TRUE;
}
-static void
-get_screen_offsets (ClutterActor *actor,
- gfloat *x_offset,
- gfloat *y_offset)
-{
- gfloat v[4] = { 0, };
-
- if (CLUTTER_ACTOR_IS_TOPLEVEL (actor))
- {
- /* trivial optimization: a top-level actor is always going to
- * have a (0, 0) offset
- */
- *x_offset = 0.0f;
- *y_offset = 0.0f;
- }
- else
- {
- ClutterActorBox box = { 0, };
-
- clutter_actor_get_paint_box (actor, &box);
- clutter_actor_box_get_origin (&box, x_offset, y_offset);
- }
-
- /* since we're setting up a viewport with a negative offset to paint
- * in an FBO with the same modelview and projection matrices as the
- * stage, we need to offset the computed absolute allocation vertices
- * with the current viewport's X and Y offsets. this works even with
- * the default case where the viewport is set up by Clutter to be
- * (0, 0, stage_width, stage_height)
- */
- cogl_get_viewport (v);
- *x_offset -= v[0];
- *y_offset -= v[1];
-}
-
static gboolean
clutter_offscreen_effect_pre_paint (ClutterEffect *effect)
{
ClutterOffscreenEffect *self = CLUTTER_OFFSCREEN_EFFECT (effect);
ClutterOffscreenEffectPrivate *priv = self->priv;
+ ClutterActorBox box;
CoglMatrix projection;
CoglColor transparent;
CoglMatrix modelview;
+ gfloat fbo_width, fbo_height;
gfloat width, height;
if (!clutter_actor_meta_get_enabled (CLUTTER_ACTOR_META (effect)))
@@ -254,7 +208,22 @@ clutter_offscreen_effect_pre_paint (ClutterEffect *effect)
if (priv->actor == NULL)
return FALSE;
- if (!update_fbo (effect))
+ /* The paint box is the bounding box of the actor's paint volume in
+ * stage coordinates. This will give us the size for the framebuffer
+ * we need to redirect its rendering offscreen and its position will
+ * be used to setup an offset viewport */
+ if (clutter_actor_get_paint_box (priv->actor, &box))
+ clutter_actor_box_get_size (&box, &fbo_width, &fbo_height);
+ 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);
+ }
+
+ /* First assert that the framebuffer is the right size... */
+ if (!update_fbo (effect, fbo_width, fbo_height))
return FALSE;
/* get the current modelview matrix so that we can copy it
@@ -265,12 +234,11 @@ clutter_offscreen_effect_pre_paint (ClutterEffect *effect)
/* let's draw offscreen */
cogl_push_framebuffer (priv->offscreen);
- /* set up the viewport so that it has the same size of the stage,
- * and it has its origin at the same position of the stage's; also
- * set up the perspective to be the same as the stage's
- */
+ /* 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);
- get_screen_offsets (priv->actor, &priv->x_offset, &priv->y_offset);
+ clutter_actor_box_get_origin (&box, &priv->x_offset, &priv->y_offset);
cogl_set_viewport (-priv->x_offset, -priv->y_offset, width, height);
/* Copy the stage's projection matrix across to the framebuffer */
@@ -306,10 +274,10 @@ clutter_offscreen_effect_real_paint_target (ClutterOffscreenEffect *effect)
paint_opacity);
cogl_set_source (priv->target);
- /* paint the texture at the same position as the actor would be,
- * in Stage coordinates, since we set up the modelview matrix to
- * be exactly as the stage sets it up, plus the eventual offsets
- * due to offscreen effects stacking
+ /* At this point we are in stage coordinates translated so if
+ * we draw our texture using a textured quad the size of the paint
+ * box then we will overlay where the actor would have drawn if it
+ * hadn't been redirected offscreen.
*/
cogl_rectangle_with_texture_coords (0, 0,
priv->target_width,
diff --git a/clutter/clutter-private.h b/clutter/clutter-private.h
index e5df30567..853c9058f 100644
--- a/clutter/clutter-private.h
+++ b/clutter/clutter-private.h
@@ -185,6 +185,79 @@ struct _ClutterMainContext
ClutterSettings *settings;
};
+struct _ClutterPaintVolume
+{
+ ClutterActor *actor;
+
+ /* cuboid for the volume:
+ *
+ * 4━━━━━━━┓5
+ * ┏━━━━━━━━┓╱┃
+ * ┃0 ┊7 1┃ ┃
+ * ┃ ┄┄┄┄┄┃┄┃6
+ * ┃3 2┃╱
+ * ┗━━━━━━━━┛
+ *
+ * 0: top, left (origin) : always valid
+ * 1: top, right : always valid
+ * 2: bottom, right : updated lazily
+ * 3: bottom, left : always valid
+ *
+ * 4: top, left, back : always valid
+ * 5: top, right, back : updated lazily
+ * 6: bottom, right, back : updated lazily
+ * 7: bottom, left, back : updated lazily
+ *
+ * Elements 0, 1, 3 and 4 are filled in by the PaintVolume setters
+ *
+ * Note: the reason for this ordering is that we can simply ignore
+ * elements 4, 5, 6 and 7 most of the time for 2D actors when
+ * calculating the projected paint box.
+ */
+ ClutterVertex vertices[8];
+
+ /* As an optimization for internally managed PaintVolumes we allow
+ * initializing ClutterPaintVolume variables allocated on the stack
+ * so we can avoid hammering the slice allocator. */
+ guint is_static:1;
+
+ /* A newly initialized PaintVolume is considered empty as it is
+ * degenerate on all three axis.
+ *
+ * We consider this carefully when we union an empty volume with
+ * another so that the union simply results in a copy of the other
+ * volume instead of also bounding the origin of the empty volume.
+ *
+ * For example this is a convenient property when calculating the
+ * volume of a container as the union of the volume of its children
+ * where the initial volume passed to the containers
+ * ->get_paint_volume method will be empty. */
+ guint is_empty:1;
+
+ /* TRUE when we've updated the values we calculate lazily */
+ guint is_complete:1;
+
+ /* TRUE if vertices 4-7 can be ignored. (Only valid if complete is
+ * TRUE) */
+ guint is_2d:1;
+
+ /* Set to TRUE initialy but cleared if the paint volume is
+ * transfomed by a matrix. */
+ guint is_axis_aligned:1;
+
+
+ /* Note: There is a precedence to the above bitfields that should be
+ * considered whenever we implement code that manipulates
+ * PaintVolumes...
+ *
+ * Firstly if ->is_empty == TRUE then the values for ->is_complete
+ * and ->is_2d are undefined, so you should typically check
+ * ->is_empty as the first priority.
+ *
+ * XXX: document other invariables...
+ */
+};
+
#define CLUTTER_CONTEXT() (_clutter_context_get_default ())
ClutterMainContext *_clutter_context_get_default (void);
gboolean _clutter_context_is_initialized (void);
@@ -239,6 +312,7 @@ void _clutter_stage_manager_set_default_stage (ClutterStageManager *stage_manage
ClutterStage *stage);
/* stage */
+void _clutter_stage_do_paint (ClutterStage *stage);
void _clutter_stage_set_window (ClutterStage *stage,
ClutterStageWindow *stage_window);
ClutterStageWindow *_clutter_stage_get_window (ClutterStage *stage);
@@ -283,6 +357,9 @@ void _clutter_stage_increment_picks_per_frame_counter (ClutterStage *stage);
void _clutter_stage_reset_picks_per_frame_counter (ClutterStage *stage);
guint _clutter_stage_get_picks_per_frame_counter (ClutterStage *stage);
+ClutterPaintVolume *_clutter_stage_paint_volume_stack_allocate (ClutterStage *stage);
+void _clutter_stage_paint_volume_stack_free_all (ClutterStage *stage);
+
/* vfuncs implemented by backend */
GType _clutter_backend_impl_get_type (void);
@@ -386,7 +463,7 @@ gint32 _clutter_backend_get_units_serial (ClutterBackend *backend);
gboolean _clutter_effect_pre_paint (ClutterEffect *effect);
void _clutter_effect_post_paint (ClutterEffect *effect);
-void _clutter_effect_get_paint_volume (ClutterEffect *effect,
+gboolean _clutter_effect_get_paint_volume (ClutterEffect *effect,
ClutterPaintVolume *volume);
void _clutter_constraint_update_allocation (ClutterConstraint *constraint,
@@ -411,9 +488,18 @@ gpointer _clutter_event_get_platform_data (const ClutterEvent *event);
#endif
-ClutterPaintVolume *_clutter_paint_volume_new (ClutterActor *actor);
-void _clutter_paint_volume_get_box (ClutterPaintVolume *pv,
- ClutterActorBox *box);
+void _clutter_paint_volume_init_static (ClutterActor *actor,
+ ClutterPaintVolume *pv);
+ClutterPaintVolume *_clutter_paint_volume_new (ClutterActor *actor);
+void _clutter_paint_volume_copy_static (const ClutterPaintVolume *src_pv,
+ ClutterPaintVolume *dst_pv);
+
+void _clutter_paint_volume_project (ClutterPaintVolume *pv,
+ const CoglMatrix *modelview,
+ const CoglMatrix *projection,
+ const int *viewport);
+void _clutter_paint_volume_get_bounding_box (ClutterPaintVolume *pv,
+ ClutterActorBox *box);
G_END_DECLS
diff --git a/clutter/clutter-stage.c b/clutter/clutter-stage.c
index 0a8efd0df..794a4fb09 100644
--- a/clutter/clutter-stage.c
+++ b/clutter/clutter-stage.c
@@ -118,6 +118,8 @@ struct _ClutterStagePrivate
gint picks_per_frame;
+ GArray *paint_volume_stack;
+
guint redraw_pending : 1;
guint is_fullscreen : 1;
guint is_cursor_visible : 1;
@@ -309,6 +311,16 @@ clutter_stage_allocate (ClutterActor *self,
clutter_actor_queue_redraw (self);
}
+/* This provides a common point of entry for painting the scenegraph
+ * for picking or painting...
+ */
+void
+_clutter_stage_do_paint (ClutterStage *stage)
+{
+ _clutter_stage_paint_volume_stack_free_all (stage);
+ clutter_actor_paint (CLUTTER_ACTOR (stage));
+}
+
static void
clutter_stage_paint (ClutterActor *self)
{
@@ -1071,6 +1083,8 @@ clutter_stage_finalize (GObject *object)
g_free (stage->priv->title);
+ g_array_free (priv->paint_volume_stack, TRUE);
+
G_OBJECT_CLASS (clutter_stage_parent_class)->finalize (object);
}
@@ -1468,6 +1482,9 @@ clutter_stage_init (ClutterStage *self)
_clutter_stage_set_pick_buffer_valid (self, FALSE);
_clutter_stage_reset_picks_per_frame_counter (self);
+
+ priv->paint_volume_stack =
+ g_array_new (FALSE, FALSE, sizeof (ClutterPaintVolume));
}
/**
@@ -2986,3 +3003,33 @@ _clutter_stage_get_picks_per_frame_counter (ClutterStage *stage)
return stage->priv->picks_per_frame;
}
+
+ClutterPaintVolume *
+_clutter_stage_paint_volume_stack_allocate (ClutterStage *stage)
+{
+ GArray *paint_volume_stack = stage->priv->paint_volume_stack;
+
+ g_array_set_size (paint_volume_stack,
+ paint_volume_stack->len+1);
+
+ return &g_array_index (paint_volume_stack,
+ ClutterPaintVolume,
+ paint_volume_stack->len - 1);
+}
+
+void
+_clutter_stage_paint_volume_stack_free_all (ClutterStage *stage)
+{
+ GArray *paint_volume_stack = stage->priv->paint_volume_stack;
+ int i;
+
+ for (i = 0; i < paint_volume_stack->len; i++)
+ {
+ ClutterPaintVolume *pv =
+ &g_array_index (paint_volume_stack, ClutterPaintVolume, i);
+ clutter_paint_volume_free (pv);
+ }
+
+ g_array_set_size (paint_volume_stack, 0);
+}
+
diff --git a/clutter/clutter-texture.c b/clutter/clutter-texture.c
index e5b121b82..fbf5d0504 100644
--- a/clutter/clutter-texture.c
+++ b/clutter/clutter-texture.c
@@ -416,8 +416,46 @@ set_viewport_with_buffer_under_fbo_source (ClutterActor *fbo_source,
ClutterActorBox box = { 0, };
float x_offset, y_offset;
- clutter_actor_get_paint_box (fbo_source, &box);
- clutter_actor_box_get_origin (&box, &x_offset, &y_offset);
+ if (clutter_actor_get_paint_box (fbo_source, &box))
+ clutter_actor_box_get_origin (&box, &x_offset, &y_offset);
+ else
+ {
+ /* As a fallback when the paint box can't be determined we use
+ * the transformed allocation to come up with an offset instead.
+ *
+ * FIXME: when we don't have a paint box we should instead be
+ * falling back to a stage sized fbo with an offset of (0,0)
+ */
+
+ ClutterVertex verts[4];
+ float x_min = G_MAXFLOAT, y_min = G_MAXFLOAT;
+ int i;
+
+ /* Get the actors allocation transformed into screen coordinates.
+ *
+ * XXX: Note: this may not be a bounding box for the actor, since an
+ * actor with depth may escape the box due to its perspective
+ * projection. */
+ clutter_actor_get_abs_allocation_vertices (fbo_source, verts);
+
+ for (i = 0; i < G_N_ELEMENTS (verts); ++i)
+ {
+ if (verts[i].x < x_min)
+ x_min = verts[i].x;
+ if (verts[i].y < y_min)
+ y_min = verts[i].y;
+ }
+
+ /* XXX: It's not good enough to round by simply truncating the fraction here
+ * via a cast, as it results in offscreen rendering being offset by 1 pixel
+ * in many cases... */
+#define ROUND(x) ((x) >= 0 ? (long)((x) + 0.5) : (long)((x) - 0.5))
+
+ x_offset = ROUND (x_min);
+ y_offset = ROUND (y_min);
+
+#undef ROUND
+ }
/* translate the viewport so that the source actor lands on the
* sub-region backed by the offscreen framebuffer... */
@@ -432,7 +470,7 @@ update_fbo (ClutterActor *self)
ClutterMainContext *context;
ClutterShader *shader = NULL;
ClutterActor *stage = NULL;
- ClutterPerspective perspective;
+ CoglMatrix projection;
CoglColor transparent_col;
context = _clutter_context_get_default ();
@@ -466,24 +504,21 @@ update_fbo (ClutterActor *self)
* under the actor.
*/
- clutter_stage_get_perspective (CLUTTER_STAGE (stage), &perspective);
+ _clutter_stage_get_projection_matrix (CLUTTER_STAGE (stage), &projection);
+
+ /* Set the projection matrix modelview matrix as it is for the
+ * stage... */
+ cogl_set_projection_matrix (&projection);
+
clutter_actor_get_size (stage, &stage_width, &stage_height);
- /* Set the projection matrix modelview matrix and viewport size as
- * they are for the stage... */
- _cogl_setup_viewport (stage_width, stage_height,
- perspective.fovy,
- perspective.aspect,
- perspective.z_near,
- perspective.z_far);
-
- /* Negatively offset the viewport so that the offscreen framebuffer is
- * position underneath the fbo_source actor... */
+ /* Set a negatively offset the viewport so that the offscreen
+ * framebuffer is position underneath the fbo_source actor... */
set_viewport_with_buffer_under_fbo_source (priv->fbo_source,
stage_width,
stage_height);
- /* Reapply the source's parent transformations */
+ /* Apply the source's parent transformations to the modelview */
if ((source_parent = clutter_actor_get_parent (priv->fbo_source)))
{
CoglMatrix modelview;
@@ -2315,9 +2350,26 @@ on_fbo_source_size_change (GObject *object,
ClutterTexturePrivate *priv = texture->priv;
gfloat w, h;
ClutterActorBox box;
+ gboolean status;
- clutter_actor_get_paint_box (priv->fbo_source, &box);
- clutter_actor_box_get_size (&box, &w, &h);
+ status = clutter_actor_get_paint_box (priv->fbo_source, &box);
+ if (status)
+ clutter_actor_box_get_size (&box, &w, &h);
+
+ /* In the end we will size the framebuffer according to the paint
+ * box, but for code that does:
+ * tex = clutter_texture_new_from_actor (src);
+ * clutter_actor_get_size (tex, &width, &height);
+ * it seems more helpfull to return the src actor size if it has a
+ * degenerate paint box. The most likely reason it will have a
+ * degenerate paint box is simply that the src currently has no
+ * parent. */
+ if (status == FALSE || w == 0 || h == 0)
+ clutter_actor_get_size (priv->fbo_source, &w, &h);
+
+ /* We can't create a texture with a width or height of 0... */
+ w = MAX (1, w);
+ h = MAX (1, h);
if (w != priv->image_width || h != priv->image_height)
{
@@ -2458,6 +2510,7 @@ clutter_texture_new_from_actor (ClutterActor *actor)
ClutterTexturePrivate *priv;
gfloat w, h;
ClutterActorBox box;
+ gboolean status;
g_return_val_if_fail (CLUTTER_IS_ACTOR (actor), NULL);
@@ -2472,13 +2525,25 @@ clutter_texture_new_from_actor (ClutterActor *actor)
return NULL;
}
- clutter_actor_get_paint_box (actor, &box);
- clutter_actor_box_get_size (&box, &w, &h);
+ status = clutter_actor_get_paint_box (actor, &box);
+ if (status)
+ clutter_actor_box_get_size (&box, &w, &h);
+
+ /* In the end we will size the framebuffer according to the paint
+ * box, but for code that does:
+ * tex = clutter_texture_new_from_actor (src);
+ * clutter_actor_get_size (tex, &width, &height);
+ * it seems more helpfull to return the src actor size if it has a
+ * degenerate paint box. The most likely reason it will have a
+ * degenerate paint box is simply that the src currently has no
+ * parent. */
+ if (status == FALSE || w == 0 || h == 0)
+ clutter_actor_get_size (actor, &w, &h);
/* We can't create a 0x0 fbo so always bump the size up to at least
* 1 */
- w = MAX(1,w);
- h = MAX(1,h);
+ w = MAX (1, w);
+ h = MAX (1, h);
/* Hopefully now were good.. */
texture = g_object_new (CLUTTER_TYPE_TEXTURE,
diff --git a/clutter/clutter-types.h b/clutter/clutter-types.h
index 65a771f10..32420dbee 100644
--- a/clutter/clutter-types.h
+++ b/clutter/clutter-types.h
@@ -91,6 +91,22 @@ typedef struct _ClutterActorBox ClutterActorBox;
typedef struct _ClutterGeometry ClutterGeometry;
typedef struct _ClutterKnot ClutterKnot;
typedef struct _ClutterVertex ClutterVertex;
+
+/**
+ * ClutterPaintVolume:
+ *
+ * ClutterPaintVolume is an opaque structure
+ * whose members cannot be directly accessed.
+ *
+ * A ClutterPaintVolume represents an
+ * a bounding volume whos internal representation isn't defined but
+ * can be set and queried in terms of an axis aligned bounding box.
+ *
+ * Other internal representation and methods for describing the
+ * bounding volume may be added in the future.
+ *
+ * Since: 1.4
+ */
typedef struct _ClutterPaintVolume ClutterPaintVolume;
/**
@@ -456,6 +472,8 @@ gfloat clutter_paint_volume_get_height (const ClutterPaintVolume *p
void clutter_paint_volume_set_depth (ClutterPaintVolume *pv,
gfloat depth);
gfloat clutter_paint_volume_get_depth (const ClutterPaintVolume *pv);
+void clutter_paint_volume_union (ClutterPaintVolume *pv,
+ const ClutterPaintVolume *another_pv);
G_END_DECLS
diff --git a/clutter/egl/clutter-stage-egl.c b/clutter/egl/clutter-stage-egl.c
index f339ab8c2..7d95c71ce 100644
--- a/clutter/egl/clutter-stage-egl.c
+++ b/clutter/egl/clutter-stage-egl.c
@@ -359,7 +359,7 @@ _clutter_stage_egl_redraw (ClutterStageEGL *stage_egl,
egl_surface = backend_egl->egl_surface;
#endif
- clutter_actor_paint (wrapper);
+ _clutter_stage_do_paint (CLUTTER_STAGE (wrapper));
cogl_flush ();
eglSwapBuffers (backend_egl->edpy, egl_surface);
diff --git a/clutter/fruity/clutter-backend-fruity.c b/clutter/fruity/clutter-backend-fruity.c
index ecc005c9f..68226548b 100644
--- a/clutter/fruity/clutter-backend-fruity.c
+++ b/clutter/fruity/clutter-backend-fruity.c
@@ -71,7 +71,7 @@ clutter_backend_egl_redraw (ClutterBackend *backend,
stage_egl = CLUTTER_STAGE_EGL (impl);
eglWaitNative (EGL_CORE_NATIVE_ENGINE);
- clutter_actor_paint (CLUTTER_ACTOR (stage));
+ _clutter_stage_do_paint (stage);
cogl_flush ();
eglWaitGL();
eglSwapBuffers (backend_egl->edpy, stage_egl->egl_surface);
diff --git a/clutter/glx/clutter-stage-glx.c b/clutter/glx/clutter-stage-glx.c
index 2e6291bbe..be1601d79 100644
--- a/clutter/glx/clutter-stage-glx.c
+++ b/clutter/glx/clutter-stage-glx.c
@@ -532,11 +532,11 @@ _clutter_stage_glx_redraw (ClutterStageGLX *stage_glx,
stage_glx->bounding_redraw_clip.y,
stage_glx->bounding_redraw_clip.width,
stage_glx->bounding_redraw_clip.height);
- clutter_actor_paint (CLUTTER_ACTOR (stage));
+ _clutter_stage_do_paint (stage);
cogl_clip_pop ();
}
else
- clutter_actor_paint (CLUTTER_ACTOR (stage));
+ _clutter_stage_do_paint (stage);
cogl_flush ();
CLUTTER_TIMER_STOP (_clutter_uprof_context, painting_timer);
diff --git a/clutter/osx/clutter-stage-osx.c b/clutter/osx/clutter-stage-osx.c
index 4bb4ca7ad..6b966a44d 100644
--- a/clutter/osx/clutter-stage-osx.c
+++ b/clutter/osx/clutter-stage-osx.c
@@ -147,7 +147,7 @@ clutter_stage_osx_get_wrapper (ClutterStageWindow *stage_window);
- (void) drawRect: (NSRect) bounds
{
- clutter_actor_paint (CLUTTER_ACTOR (self->stage_osx->wrapper));
+ _clutter_stage_do_paint (CLUTTER_STAGE (self->stage_osx->wrapper));
cogl_flush ();
[[self openGLContext] flushBuffer];
}
diff --git a/clutter/win32/clutter-backend-win32.c b/clutter/win32/clutter-backend-win32.c
index a8f7f8e41..cc5abe3c5 100644
--- a/clutter/win32/clutter-backend-win32.c
+++ b/clutter/win32/clutter-backend-win32.c
@@ -494,7 +494,7 @@ clutter_backend_win32_redraw (ClutterBackend *backend,
stage_win32 = CLUTTER_STAGE_WIN32 (impl);
/* this will cause the stage implementation to be painted */
- clutter_actor_paint (CLUTTER_ACTOR (stage));
+ _clutter_stage_do_paint (stage);
cogl_flush ();
if (stage_win32->client_dc)