effect: Rework the OffscreenEffect painting

The OffscreenEffect should set up the off screen draw buffer so that it
has the same projection and modelview as if it where on screen; we
achieve that by setting up the viewport to be the same size of the stage
but with an initial offset given by the left-most vertex of the actor.

When we paint the texture attached to the FBO we then set up the
modelview matrix of the on screen draw buffer so that it's the same as
the stage one: this way, the texture will be painted in screen
coordinates and it will occupy the same area as the actor would have
had.
This commit is contained in:
Emmanuele Bassi 2010-04-16 12:33:20 +01:00
parent b4f8ba0f73
commit a3e8e0025a
2 changed files with 230 additions and 88 deletions

View File

@ -18,8 +18,9 @@
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Author:
* Authors:
* Emmanuele Bassi <ebassi@linux.intel.com>
* Robert Bragg <robert@linux.intel.com>
*/
/**
@ -48,6 +49,13 @@
* #ClutterOffscreenEffect also provides a <function>paint_target()</function>
* function, which encapsulates the effective painting of the texture that
* contains the result of the offscreen redirection.</para>
* <para>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
* <function>create_target()</function> virtual function; no chain up
* to the #ClutterOffscreenEffect implementation is required in this
* case.</para>
* </refsect2>
*
* #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);
}

View File

@ -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