/* * Clutter. * * An OpenGL based 'interactive canvas' library. * * Copyright (C) 2010 Intel Corporation. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * * Authors: * Emmanuele Bassi * Robert Bragg */ /** * SECTION:clutter-offscreen-effect * @short_description: Base class for effects using offscreen buffers * @see_also: #ClutterBlurEffect, #ClutterEffect * * #ClutterOffscreenEffect is an abstract class that can be used by * #ClutterEffect sub-classes requiring access to an offscreen buffer. * * Some effects, like the fragment shader based effects, can only use GL * textures, and in order to apply those effects to any kind of actor they * require that all drawing operations are applied to an offscreen framebuffer * that gets redirected to a texture. * * #ClutterOffscreenEffect provides all the heavy-lifting for creating the * offscreen framebuffer, the redirection and the final paint of the texture on * the desired stage. * * * Implementing a ClutterOffscreenEffect * Creating a sub-class of #ClutterOffscreenEffect requires, in case * of overriding the #ClutterEffect virtual functions, to chain up to the * #ClutterOffscreenEffect's implementation. * On top of the #ClutterEffect's virtual functions, * #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_texture() virtual function; no chain up * to the #ClutterOffscreenEffect implementation is required in this * case. * * * #ClutterOffscreenEffect is available since Clutter 1.4 */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "clutter-offscreen-effect.h" #include "cogl/cogl.h" #include "clutter-actor-private.h" #include "clutter-debug.h" #include "clutter-private.h" #include "clutter-stage-private.h" struct _ClutterOffscreenEffectPrivate { CoglHandle offscreen; CoglMaterial *target; ClutterActor *actor; ClutterActor *stage; gfloat x_offset; gfloat y_offset; gfloat target_width; gfloat target_height; }; G_DEFINE_ABSTRACT_TYPE (ClutterOffscreenEffect, clutter_offscreen_effect, CLUTTER_TYPE_EFFECT); static void clutter_offscreen_effect_set_actor (ClutterActorMeta *meta, ClutterActor *actor) { ClutterOffscreenEffect *self = CLUTTER_OFFSCREEN_EFFECT (meta); ClutterOffscreenEffectPrivate *priv = self->priv; ClutterActorMetaClass *meta_class; meta_class = CLUTTER_ACTOR_META_CLASS (clutter_offscreen_effect_parent_class); meta_class->set_actor (meta, actor); /* clear out the previous state */ if (priv->offscreen != COGL_INVALID_HANDLE) { cogl_handle_unref (priv->offscreen); priv->offscreen = COGL_INVALID_HANDLE; } /* we keep a back pointer here, to avoid going through the ActorMeta */ priv->actor = clutter_actor_meta_get_actor (meta); } static CoglHandle clutter_offscreen_effect_real_create_texture (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, int fbo_width, int fbo_height) { ClutterOffscreenEffect *self = CLUTTER_OFFSCREEN_EFFECT (effect); ClutterOffscreenEffectPrivate *priv = self->priv; 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 (priv->actor) == NULL ? G_OBJECT_TYPE_NAME (priv->actor) : clutter_actor_get_name (priv->actor)); return FALSE; } if (priv->target_width == fbo_width && priv->target_height == fbo_height && priv->offscreen != COGL_INVALID_HANDLE) return TRUE; if (priv->target == COGL_INVALID_HANDLE) priv->target = cogl_material_new (); texture = clutter_offscreen_effect_create_texture (self, fbo_width, fbo_height); if (texture == COGL_INVALID_HANDLE) return FALSE; cogl_material_set_layer (priv->target, 0, texture); cogl_handle_unref (texture); /* we need to use the size of the texture target and not the minimum * size we passed to the create_texture() vfunc, as any sub-class might * give use a bigger texture */ priv->target_width = cogl_texture_get_width (texture); priv->target_height = cogl_texture_get_height (texture); if (priv->offscreen != COGL_INVALID_HANDLE) cogl_handle_unref (priv->offscreen); 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); cogl_handle_unref (priv->target); priv->target = COGL_INVALID_HANDLE; priv->target_width = 0; priv->target_height = 0; return FALSE; } return TRUE; } 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; gfloat xexpand, yexpand; if (!clutter_actor_meta_get_enabled (CLUTTER_ACTOR_META (effect))) return FALSE; if (priv->actor == NULL) return FALSE; /* 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 * to the framebuffer */ cogl_get_modelview_matrix (&modelview); /* let's draw offscreen */ cogl_push_framebuffer (priv->offscreen); /* Set up the viewport so that it has the same size as the stage, * but offset it so that the actor of interest lands on our * framebuffer. */ clutter_actor_get_size (priv->stage, &width, &height); clutter_actor_box_get_origin (&box, &priv->x_offset, &priv->y_offset); /* Expand the viewport if the actor is partially off-stage, * otherwise the actor will end up clipped to the stage viewport */ xexpand = 0.f; if (priv->x_offset < 0.f) xexpand = -priv->x_offset; if (priv->x_offset + fbo_width > width) xexpand = MAX (xexpand, (priv->x_offset + fbo_width) - width); yexpand = 0.f; if (priv->y_offset < 0.f) yexpand = -priv->y_offset; if (priv->y_offset + fbo_height > height) yexpand = MAX (yexpand, (priv->y_offset + fbo_height) - height); /* Set the viewport */ cogl_set_viewport (-(priv->x_offset + xexpand), -(priv->y_offset + yexpand), width + (2 * xexpand), height + (2 * yexpand)); /* Copy the stage's projection matrix across to the framebuffer */ _clutter_stage_get_projection_matrix (CLUTTER_STAGE (priv->stage), &projection); cogl_set_projection_matrix (&projection); /* If we've expanded the viewport, make sure to scale the modelview * matrix accordingly (as it's been initialised to work with the * original viewport and not our expanded one). */ if (xexpand > 0.f || yexpand > 0.f) { CoglMatrix correction; gfloat new_width, new_height; new_width = width + (2 * xexpand); new_height = height + (2 * yexpand); cogl_matrix_init_identity (&correction); cogl_matrix_scale (&correction, width / new_width, height / new_height, 1); cogl_matrix_multiply (&correction, &correction, &modelview); modelview = correction; } /* Copy the modelview that would have been used if rendering onscreen */ cogl_set_modelview_matrix (&modelview); cogl_color_init_from_4ub (&transparent, 0, 0, 0, 0); cogl_clear (&transparent, COGL_BUFFER_BIT_COLOR | COGL_BUFFER_BIT_DEPTH); cogl_push_matrix (); /* Override the actor's opacity to fully opaque - we paint the offscreen * texture with the actor's paint opacity, so we need to do this to avoid * multiplying the opacity twice. */ _clutter_actor_set_opacity_override (priv->actor, 0xff); return TRUE; } static void clutter_offscreen_effect_real_paint_target (ClutterOffscreenEffect *effect) { ClutterOffscreenEffectPrivate *priv = effect->priv; guint8 paint_opacity; paint_opacity = clutter_actor_get_paint_opacity (priv->actor); cogl_material_set_color4ub (priv->target, paint_opacity, paint_opacity, paint_opacity, paint_opacity); cogl_set_source (priv->target); /* 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, priv->target_height, 0.0, 0.0, 1.0, 1.0); } static void clutter_offscreen_effect_post_paint (ClutterEffect *effect) { ClutterOffscreenEffect *self = CLUTTER_OFFSCREEN_EFFECT (effect); ClutterOffscreenEffectPrivate *priv = self->priv; CoglMatrix modelview; if (priv->offscreen == COGL_INVALID_HANDLE || priv->target == COGL_INVALID_HANDLE || priv->actor == NULL) return; cogl_pop_matrix (); cogl_pop_framebuffer (); cogl_push_matrix (); /* Now reset the modelview to put us in stage coordinates so * we can drawn the result of our offscreen render as a textured * quad... */ cogl_matrix_init_identity (&modelview); _clutter_actor_apply_modelview_transform (priv->stage, &modelview); cogl_matrix_translate (&modelview, priv->x_offset, priv->y_offset, 0.0f); cogl_set_modelview_matrix (&modelview); /* Remove the opacity override */ _clutter_actor_set_opacity_override (priv->actor, -1); /* paint the target material; this is virtualized for * sub-classes that require special hand-holding */ clutter_offscreen_effect_paint_target (self); cogl_pop_matrix (); } static void clutter_offscreen_effect_finalize (GObject *gobject) { ClutterOffscreenEffect *self = CLUTTER_OFFSCREEN_EFFECT (gobject); ClutterOffscreenEffectPrivate *priv = self->priv; if (priv->offscreen) cogl_handle_unref (priv->offscreen); if (priv->target) cogl_handle_unref (priv->target); G_OBJECT_CLASS (clutter_offscreen_effect_parent_class)->finalize (gobject); } static void clutter_offscreen_effect_class_init (ClutterOffscreenEffectClass *klass) { ClutterActorMetaClass *meta_class = CLUTTER_ACTOR_META_CLASS (klass); ClutterEffectClass *effect_class = CLUTTER_EFFECT_CLASS (klass); GObjectClass *gobject_class = G_OBJECT_CLASS (klass); g_type_class_add_private (klass, sizeof (ClutterOffscreenEffectPrivate)); klass->create_texture = clutter_offscreen_effect_real_create_texture; klass->paint_target = clutter_offscreen_effect_real_paint_target; meta_class->set_actor = clutter_offscreen_effect_set_actor; effect_class->pre_paint = clutter_offscreen_effect_pre_paint; effect_class->post_paint = clutter_offscreen_effect_post_paint; gobject_class->finalize = clutter_offscreen_effect_finalize; } static void clutter_offscreen_effect_init (ClutterOffscreenEffect *self) { self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, CLUTTER_TYPE_OFFSCREEN_EFFECT, ClutterOffscreenEffectPrivate); } /** * clutter_offscreen_effect_get_target: * @effect: a #ClutterOffscreenEffect * * Retrieves the material used as a render target for the offscreen * buffer created by @effect * * You should only use the returned #CoglMaterial when painting. The * returned material might change between different frames. * * Return value: (transfer none): a #CoglMaterial or %NULL. The * returned material is owned by Clutter and it should not be * modified or freed * * Since: 1.4 */ CoglMaterial * clutter_offscreen_effect_get_target (ClutterOffscreenEffect *effect) { g_return_val_if_fail (CLUTTER_IS_OFFSCREEN_EFFECT (effect), COGL_INVALID_HANDLE); return effect->priv->target; } /** * clutter_offscreen_effect_paint_target: * @effect: a #ClutterOffscreenEffect * * Calls the paint_target() virtual function of the @effect * * Since: 1.4 */ void clutter_offscreen_effect_paint_target (ClutterOffscreenEffect *effect) { g_return_if_fail (CLUTTER_IS_OFFSCREEN_EFFECT (effect)); CLUTTER_OFFSCREEN_EFFECT_GET_CLASS (effect)->paint_target (effect); } /** * clutter_offscreen_effect_create_texture: * @effect: a #ClutterOffscreenEffect * @width: the minimum width of the target texture * @height: the minimum height of the target texture * * Calls the create_texture() virtual function of the @effect * * Return value: (transfer full): a handle to a Cogl texture, or * %COGL_INVALID_HANDLE. The returned handle has its reference * count increased. * * Since: 1.4 */ CoglHandle clutter_offscreen_effect_create_texture (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_texture (effect, width, height); }