diff --git a/clutter/clutter-offscreen-effect.c b/clutter/clutter-offscreen-effect.c index f3bdc336d..b55d8b227 100644 --- a/clutter/clutter-offscreen-effect.c +++ b/clutter/clutter-offscreen-effect.c @@ -18,8 +18,9 @@ * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * - * Author: + * Authors: * Emmanuele Bassi + * Robert Bragg */ /** @@ -48,6 +49,13 @@ * #ClutterOffscreenEffect also provides a paint_target() * function, which encapsulates the effective painting of the texture that * contains the result of the offscreen redirection. + * The size of the target material is defined to be as big as the + * transformed size of the #ClutterActor using the offscreen effect. + * Sub-classes of #ClutterOffscreenEffect can change the texture creation + * code to provide bigger textures by overriding the + * create_target() virtual function; no chain up + * to the #ClutterOffscreenEffect implementation is required in this + * case. * * * #ClutterOffscreenEffect is available since Clutter 1.4 @@ -70,6 +78,13 @@ struct _ClutterOffscreenEffectPrivate CoglHandle target; ClutterActor *actor; + ClutterActor *stage; + + gfloat x_offset; + gfloat y_offset; + + gfloat target_width; + gfloat target_height; }; G_DEFINE_ABSTRACT_TYPE (ClutterOffscreenEffect, @@ -83,13 +98,6 @@ clutter_offscreen_effect_set_actor (ClutterActorMeta *meta, ClutterOffscreenEffect *self = CLUTTER_OFFSCREEN_EFFECT (meta); ClutterOffscreenEffectPrivate *priv = self->priv; ClutterActorMetaClass *meta_class; - ClutterPerspective perspective; - gfloat width, height, z_camera; - ClutterActorBox allocation; - gfloat fb_width, fb_height; - ClutterActor *stage; - CoglHandle texture; - CoglMatrix matrix; meta_class = CLUTTER_ACTOR_META_CLASS (clutter_offscreen_effect_parent_class); meta_class->set_actor (meta, actor); @@ -109,78 +117,120 @@ clutter_offscreen_effect_set_actor (ClutterActorMeta *meta, /* we keep a back pointer here, to avoid going through the ActorMeta */ priv->actor = clutter_actor_meta_get_actor (meta); - if (priv->actor == NULL) - return; +} - stage = clutter_actor_get_stage (priv->actor); - if (stage == NULL) +static CoglHandle +clutter_offscreen_effect_real_create_target (ClutterOffscreenEffect *effect, + gfloat width, + gfloat height) +{ + return cogl_texture_new_with_size (MAX (width, 1), MAX (height, 1), + COGL_TEXTURE_NO_SLICING, + COGL_PIXEL_FORMAT_RGBA_8888_PRE); +} + +static gboolean +update_fbo (ClutterEffect *effect) +{ + ClutterOffscreenEffect *self = CLUTTER_OFFSCREEN_EFFECT (effect); + ClutterOffscreenEffectPrivate *priv = self->priv; + gfloat width, height; + CoglHandle texture; + + priv->stage = clutter_actor_get_stage (priv->actor); + if (priv->stage == NULL) { CLUTTER_NOTE (MISC, "The actor '%s' is not part of a stage", - clutter_actor_get_name (actor) == NULL - ? G_OBJECT_TYPE_NAME (actor) - : clutter_actor_get_name (actor)); - - /* we forcibly disable the effect here */ - clutter_actor_meta_set_enabled (meta, FALSE); - return; + clutter_actor_get_name (priv->actor) == NULL + ? G_OBJECT_TYPE_NAME (priv->actor) + : clutter_actor_get_name (priv->actor)); + return FALSE; } - clutter_stage_get_perspective (CLUTTER_STAGE (stage), &perspective); - clutter_actor_get_allocation_box (stage, &allocation); - clutter_actor_box_get_size (&allocation, &fb_width, &fb_height); + /* 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 + */ + clutter_actor_get_transformed_size (priv->actor, &width, &height); + if (fabsf (priv->target_width - width) < 0.00001f && + fabsf (priv->target_height - height) < 0.0001f) + return TRUE; - clutter_actor_get_allocation_box (priv->actor, &allocation); - clutter_actor_box_get_size (&allocation, &width, &height); + if (priv->target != COGL_INVALID_HANDLE) + { + cogl_handle_unref (priv->target); + cogl_handle_unref (priv->offscreen); + } priv->target = cogl_material_new (); - texture = cogl_texture_new_with_size (MAX (width, 1), MAX (height, 1), - COGL_TEXTURE_NO_SLICING, - COGL_PIXEL_FORMAT_RGBA_8888_PRE); + texture = clutter_offscreen_effect_create_target (self, width, height); + if (texture == COGL_INVALID_HANDLE) + return FALSE; + cogl_material_set_layer (priv->target, 0, texture); cogl_handle_unref (texture); - cogl_material_set_layer_filters (priv->target, 0, - COGL_MATERIAL_FILTER_LINEAR, - COGL_MATERIAL_FILTER_LINEAR); + /* we need to use the size of the texture target and not the minimum + * size we passed to the create_target() vfunc, as any sub-class might + * give use a bigger texture + */ + priv->target_width = cogl_texture_get_width (texture); + priv->target_height = cogl_texture_get_height (texture); priv->offscreen = cogl_offscreen_new_to_texture (texture); - if (priv->offscreen == COGL_INVALID_HANDLE) { g_warning ("%s: Unable to create an Offscreen buffer", G_STRLOC); - /* we forcibly disable the effect here */ - clutter_actor_meta_set_enabled (meta, FALSE); - return; + cogl_handle_unref (priv->target); + priv->target = COGL_INVALID_HANDLE; + + priv->target_width = 0; + priv->target_height = 0; + + return FALSE; } - cogl_push_framebuffer (priv->offscreen); + return TRUE; +} - width = cogl_texture_get_width (texture); - height = cogl_texture_get_height (texture); - fb_width /= fb_width / width; - fb_height /= fb_height / height; +static void +get_screen_offsets (ClutterActor *actor, + gfloat *x_offset, + gfloat *y_offset) +{ + ClutterVertex verts[4]; + gfloat x_min = G_MAXFLOAT, y_min = G_MAXFLOAT; + gint i; - cogl_set_viewport (0, 0, width, height); - cogl_perspective (perspective.fovy, - perspective.aspect, - perspective.z_near, - perspective.z_far); + /* 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 (actor, verts); - cogl_get_projection_matrix (&matrix); - z_camera = 0.5 * matrix.xx; + for (i = 0; i < G_N_ELEMENTS (verts); ++i) + { + if (verts[i].x < x_min) + x_min = verts[i].x; - cogl_matrix_init_identity (&matrix); - cogl_matrix_translate (&matrix, -0.5f, -0.5f, -z_camera); - cogl_matrix_scale (&matrix, - 1.0f / fb_width, - -1.0f / fb_height, - 1.0f / fb_width); - cogl_matrix_translate (&matrix, 0.0f, -1.0f * fb_height, 0.0f); - cogl_set_modelview_matrix (&matrix); + if (verts[i].y < y_min) + y_min = verts[i].y; + } - cogl_pop_framebuffer (); + /* 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 } static gboolean @@ -188,49 +238,78 @@ clutter_offscreen_effect_pre_paint (ClutterEffect *effect) { ClutterOffscreenEffect *self = CLUTTER_OFFSCREEN_EFFECT (effect); ClutterOffscreenEffectPrivate *priv = self->priv; + ClutterPerspective perspective; + CoglColor transparent; + CoglMatrix modelview; + gfloat width, height; if (!clutter_actor_meta_get_enabled (CLUTTER_ACTOR_META (effect))) return FALSE; - if (priv->offscreen != COGL_INVALID_HANDLE) - { - CoglColor transparent; + if (priv->actor == NULL) + return FALSE; - cogl_push_framebuffer (priv->offscreen); - cogl_push_matrix (); + if (!update_fbo (effect)) + return FALSE; - cogl_color_set_from_4ub (&transparent, 0, 0, 0, 0); - cogl_clear (&transparent, - COGL_BUFFER_BIT_COLOR | - COGL_BUFFER_BIT_STENCIL | - COGL_BUFFER_BIT_DEPTH); + /* get the current modelview matrix so that we can copy it + * on the new framebuffer + */ + cogl_get_modelview_matrix (&modelview); - return TRUE; - } + clutter_stage_get_perspective (CLUTTER_STAGE (priv->stage), &perspective); + clutter_actor_get_size (priv->stage, &width, &height); - return FALSE; + get_screen_offsets (priv->actor, &priv->x_offset, &priv->y_offset); + + /* 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 + */ + cogl_set_viewport (-priv->x_offset, -priv->y_offset, width, height); + cogl_perspective (perspective.fovy, + perspective.aspect, + perspective.z_near, + perspective.z_far); + + cogl_color_set_from_4ub (&transparent, 0, 0, 0, 0); + cogl_clear (&transparent, + COGL_BUFFER_BIT_COLOR | + COGL_BUFFER_BIT_DEPTH); + + cogl_push_matrix (); + + cogl_set_modelview_matrix (&modelview); + + return TRUE; } static void clutter_offscreen_effect_real_paint_target (ClutterOffscreenEffect *effect) { ClutterOffscreenEffectPrivate *priv = effect->priv; - ClutterActorBox allocation; - gfloat width, height; guint8 paint_opacity; paint_opacity = clutter_actor_get_paint_opacity (priv->actor); - clutter_actor_get_allocation_box (priv->actor, &allocation); - clutter_actor_box_get_size (&allocation, &width, &height); - cogl_material_set_color4ub (priv->target, paint_opacity, paint_opacity, paint_opacity, paint_opacity); cogl_set_source (priv->target); - cogl_rectangle_with_texture_coords (0, 0, width, height, + + /* 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 + */ + cogl_rectangle_with_texture_coords (priv->x_offset, + priv->y_offset, + priv->x_offset + priv->target_width, + priv->y_offset + priv->target_height, 0.0, 0.0, 1.0, 1.0); } @@ -240,19 +319,48 @@ clutter_offscreen_effect_post_paint (ClutterEffect *effect) { ClutterOffscreenEffect *self = CLUTTER_OFFSCREEN_EFFECT (effect); ClutterOffscreenEffectPrivate *priv = self->priv; + ClutterPerspective perspective; + CoglMatrix modelview, matrix; + gfloat width, height; + gfloat z_camera; - if (priv->offscreen != COGL_INVALID_HANDLE && - priv->target != COGL_INVALID_HANDLE && - priv->actor != NULL) - { - cogl_pop_matrix (); - cogl_pop_framebuffer (); + if (priv->offscreen == COGL_INVALID_HANDLE || + priv->target == COGL_INVALID_HANDLE || + priv->actor == NULL) + return; - /* paint the target material; this is virtualized for - * sub-classes that require special hand-holding - */ - clutter_offscreen_effect_paint_target (self); - } + cogl_pop_matrix (); + cogl_pop_framebuffer (); + + clutter_stage_get_perspective (CLUTTER_STAGE (priv->stage), &perspective); + clutter_actor_get_size (priv->stage, &width, &height); + + cogl_get_modelview_matrix (&modelview); + cogl_get_projection_matrix (&matrix); + z_camera = 0.5f * matrix.xx; + + /* obliterate the current modelview matrix and reset it to be + * the same as the stage's at the beginning of a paint run; this + * is done to paint the target material in screen coordinates at + * the same place as the actor would have been + */ + cogl_matrix_init_identity (&matrix); + cogl_matrix_translate (&matrix, -0.5f, -0.5f, -z_camera); + cogl_matrix_scale (&matrix, 1.0f / width, -1.0f / height, 1.0f / width); + cogl_matrix_translate (&matrix, 0.0f, -1.0f * height, 0.0f); + cogl_set_modelview_matrix (&matrix); + + cogl_push_matrix (); + + /* paint the target material; this is virtualized for + * sub-classes that require special hand-holding + */ + clutter_offscreen_effect_paint_target (self); + + cogl_pop_matrix (); + + /* reset the modelview matrix */ + cogl_set_modelview_matrix (&modelview); } static void @@ -279,6 +387,7 @@ clutter_offscreen_effect_class_init (ClutterOffscreenEffectClass *klass) g_type_class_add_private (klass, sizeof (ClutterOffscreenEffectPrivate)); + klass->create_target = clutter_offscreen_effect_real_create_target; klass->paint_target = clutter_offscreen_effect_real_paint_target; meta_class->set_actor = clutter_offscreen_effect_set_actor; @@ -334,3 +443,28 @@ clutter_offscreen_effect_paint_target (ClutterOffscreenEffect *effect) CLUTTER_OFFSCREEN_EFFECT_GET_CLASS (effect)->paint_target (effect); } + +/** + * clutter_offscreen_effect_create_target: + * @effect: a #ClutterOffscreenEffect + * @width: the minimum width of the target texture + * @height: the minimum height of the target texture + * + * Calls the create_target() virtual function of the @effect + * + * Return value: a handle to the target texture + * + * Since: 1.4 + */ +CoglHandle +clutter_offscreen_effect_create_target (ClutterOffscreenEffect *effect, + gfloat width, + gfloat height) +{ + g_return_val_if_fail (CLUTTER_IS_OFFSCREEN_EFFECT (effect), + COGL_INVALID_HANDLE); + + return CLUTTER_OFFSCREEN_EFFECT_GET_CLASS (effect)->create_target (effect, + width, + height); +} diff --git a/clutter/clutter-offscreen-effect.h b/clutter/clutter-offscreen-effect.h index df6026f2a..a1fffd579 100644 --- a/clutter/clutter-offscreen-effect.h +++ b/clutter/clutter-offscreen-effect.h @@ -62,6 +62,7 @@ struct _ClutterOffscreenEffect /** * ClutterOffscreenEffectClass: + * @create_target: virtual function * @paint_target: virtual function * * The #ClutterOffscreenEffectClass structure contains only private data @@ -74,7 +75,10 @@ struct _ClutterOffscreenEffectClass ClutterEffectClass parent_class; /*< public >*/ - void (* paint_target) (ClutterOffscreenEffect *effect); + CoglHandle (* create_target) (ClutterOffscreenEffect *effect, + gfloat min_width, + gfloat min_height); + void (* paint_target) (ClutterOffscreenEffect *effect); /*< private >*/ void (* _clutter_offscreen1) (void); @@ -88,8 +92,12 @@ struct _ClutterOffscreenEffectClass GType clutter_offscreen_effect_get_type (void) G_GNUC_CONST; -CoglHandle clutter_offscreen_effect_get_target (ClutterOffscreenEffect *effect); -void clutter_offscreen_effect_paint_target (ClutterOffscreenEffect *effect); +CoglHandle clutter_offscreen_effect_get_target (ClutterOffscreenEffect *effect); + +void clutter_offscreen_effect_paint_target (ClutterOffscreenEffect *effect); +CoglHandle clutter_offscreen_effect_create_target (ClutterOffscreenEffect *effect, + gfloat width, + gfloat height); G_END_DECLS