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)