4 * An OpenGL based 'interactive canvas' library.
6 * Copyright (C) 2010 Intel Corporation.
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public
10 * License as published by the Free Software Foundation; either
11 * version 2 of the License, or (at your option) any later version.
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Lesser General Public License for more details.
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
22 * Emmanuele Bassi <ebassi@linux.intel.com>
24 * Based on the MxDeformTexture class, written by:
25 * Chris Lord <chris@linux.intel.com>
29 * SECTION:clutter-deform-effect
30 * @Title: ClutterDeformEffect
31 * @Short_Description: A base class for effects deforming the geometry
34 * #ClutterDeformEffect is an abstract class providing all the plumbing
35 * for creating effects that result in the deformation of an actor's
38 * #ClutterDeformEffect uses offscreen buffers to render the contents of
39 * a #ClutterActor and then the Cogl vertex buffers API to submit the
40 * geometry to the GPU.
43 * <title>Implementing ClutterDeformEffect</title>
44 * <para>Sub-classes of #ClutterDeformEffect should override the
45 * <function>deform_vertex()</function> virtual function; this function
46 * is called on every vertex that needs to be deformed by the effect.
47 * Each passed vertex is an in-out parameter that initially contains the
48 * position of the vertex and should be modified according to a specific
49 * deformation algorithm.</para>
52 * #ClutterDeformEffect is available since Clutter 1.4
59 #define CLUTTER_ENABLE_EXPERIMENTAL_API
60 #include "clutter-deform-effect.h"
62 #include <cogl/cogl.h>
64 #include "clutter-debug.h"
65 #include "clutter-enum-types.h"
66 #include "clutter-offscreen-effect-private.h"
67 #include "clutter-private.h"
69 #define DEFAULT_N_TILES 32
71 struct _ClutterDeformEffectPrivate
73 CoglPipeline *back_pipeline;
78 CoglAttributeBuffer *buffer;
80 CoglPrimitive *primitive;
82 CoglPrimitive *lines_primitive;
103 static GParamSpec *obj_props[PROP_LAST];
105 G_DEFINE_ABSTRACT_TYPE (ClutterDeformEffect,
106 clutter_deform_effect,
107 CLUTTER_TYPE_OFFSCREEN_EFFECT);
110 clutter_deform_effect_real_deform_vertex (ClutterDeformEffect *effect,
113 CoglTextureVertex *vertex)
115 g_warning ("%s: Deformation effect of type '%s' does not implement "
116 "the required ClutterDeformEffect::deform_vertex virtual "
119 G_OBJECT_TYPE_NAME (effect));
123 clutter_deform_effect_deform_vertex (ClutterDeformEffect *effect,
126 CoglTextureVertex *vertex)
128 CLUTTER_DEFORM_EFFECT_GET_CLASS (effect)->deform_vertex (effect,
134 vbo_invalidate (ClutterActor *actor,
135 const ClutterActorBox *allocation,
136 ClutterAllocationFlags flags,
137 ClutterDeformEffect *effect)
139 effect->priv->is_dirty = TRUE;
143 clutter_deform_effect_set_actor (ClutterActorMeta *meta,
146 ClutterDeformEffectPrivate *priv = CLUTTER_DEFORM_EFFECT (meta)->priv;
148 if (priv->allocation_id != 0)
150 ClutterActor *old_actor = clutter_actor_meta_get_actor (meta);
152 if (old_actor != NULL)
153 g_signal_handler_disconnect (old_actor, priv->allocation_id);
155 priv->allocation_id = 0;
158 /* we need to invalidate the VBO whenever the allocation of the actor
162 priv->allocation_id = g_signal_connect (actor, "allocation-changed",
163 G_CALLBACK (vbo_invalidate),
166 priv->is_dirty = TRUE;
168 CLUTTER_ACTOR_META_CLASS (clutter_deform_effect_parent_class)->set_actor (meta, actor);
172 clutter_deform_effect_paint_target (ClutterOffscreenEffect *effect)
174 ClutterDeformEffect *self= CLUTTER_DEFORM_EFFECT (effect);
175 ClutterDeformEffectPrivate *priv = self->priv;
177 CoglPipeline *pipeline;
178 CoglDepthState depth_state;
179 CoglFramebuffer *fb = cogl_get_draw_framebuffer ();
183 gboolean mapped_buffer;
184 CoglVertexP3T2C4 *verts;
186 gfloat width, height;
190 actor = clutter_actor_meta_get_actor (CLUTTER_ACTOR_META (effect));
191 opacity = clutter_actor_get_paint_opacity (actor);
193 /* if we don't have a target size, fall back to the actor's
194 * allocation, though wrong it might be
196 if (!clutter_offscreen_effect_get_target_size (effect, &width, &height))
197 clutter_actor_get_size (actor, &width, &height);
199 /* XXX ideally, the sub-classes should tell us what they
200 * changed in the texture vertices; we then would be able to
201 * avoid resubmitting the same data, if it did not change. for
202 * the time being, we resubmit everything
204 verts = cogl_buffer_map (COGL_BUFFER (priv->buffer),
205 COGL_BUFFER_ACCESS_WRITE,
206 COGL_BUFFER_MAP_HINT_DISCARD);
208 /* If the map failed then we'll resort to allocating a temporary
212 mapped_buffer = FALSE;
213 verts = g_malloc (sizeof (*verts) * priv->n_vertices);
216 mapped_buffer = TRUE;
218 for (i = 0; i < priv->y_tiles + 1; i++)
220 for (j = 0; j < priv->x_tiles + 1; j++)
222 CoglVertexP3T2C4 *vertex_out;
223 CoglTextureVertex vertex;
225 /* CoglTextureVertex isn't an ideal structure to use for
226 this because it contains a CoglColor. The internal
227 layout of CoglColor is mean to be private so Clutter
228 can not pass a pointer to it as a vertex
229 attribute. Also it contains padding so we end up
230 storing more data in the vertex buffer than we need
231 to. Instead we let the application modify a dummy
232 vertex and then copy the details back out to a more
233 well-defined struct */
235 vertex.tx = (float) j / priv->x_tiles;
236 vertex.ty = (float) i / priv->y_tiles;
238 vertex.x = width * vertex.tx;
239 vertex.y = height * vertex.ty;
242 cogl_color_init_from_4ub (&vertex.color, 255, 255, 255, opacity);
244 clutter_deform_effect_deform_vertex (self,
248 vertex_out = verts + i * (priv->x_tiles + 1) + j;
250 vertex_out->x = vertex.x;
251 vertex_out->y = vertex.y;
252 vertex_out->z = vertex.z;
253 vertex_out->s = vertex.tx;
254 vertex_out->t = vertex.ty;
255 vertex_out->r = cogl_color_get_red_byte (&vertex.color);
256 vertex_out->g = cogl_color_get_green_byte (&vertex.color);
257 vertex_out->b = cogl_color_get_blue_byte (&vertex.color);
258 vertex_out->a = cogl_color_get_alpha_byte (&vertex.color);
263 cogl_buffer_unmap (COGL_BUFFER (priv->buffer));
266 cogl_buffer_set_data (COGL_BUFFER (priv->buffer),
269 sizeof (*verts) * priv->n_vertices);
273 priv->is_dirty = FALSE;
276 material = clutter_offscreen_effect_get_target (effect);
277 pipeline = COGL_PIPELINE (material);
279 /* enable depth testing */
280 cogl_depth_state_init (&depth_state);
281 cogl_depth_state_set_test_enabled (&depth_state, TRUE);
282 cogl_pipeline_set_depth_state (pipeline, &depth_state, NULL);
284 /* enable backface culling if we have a back material */
285 if (priv->back_pipeline != NULL)
286 cogl_pipeline_set_cull_face_mode (pipeline,
287 COGL_PIPELINE_CULL_FACE_MODE_BACK);
290 if (material != NULL)
291 cogl_framebuffer_draw_primitive (fb, pipeline, priv->primitive);
294 if (priv->back_pipeline != NULL)
296 CoglPipeline *back_pipeline;
298 /* We probably shouldn't be modifying the user's material so
299 instead we make a temporary copy */
300 back_pipeline = cogl_pipeline_copy (priv->back_pipeline);
301 cogl_pipeline_set_depth_state (back_pipeline, &depth_state, NULL);
302 cogl_pipeline_set_cull_face_mode (pipeline,
303 COGL_PIPELINE_CULL_FACE_MODE_FRONT);
305 cogl_framebuffer_draw_primitive (fb, back_pipeline, priv->primitive);
307 cogl_object_unref (back_pipeline);
310 if (G_UNLIKELY (priv->lines_primitive != NULL))
313 clutter_backend_get_cogl_context (clutter_get_default_backend ());
314 CoglPipeline *lines_pipeline = cogl_pipeline_new (ctx);
315 cogl_pipeline_set_color4f (lines_pipeline, 1.0, 0, 0, 1.0);
316 cogl_framebuffer_draw_primitive (fb, lines_pipeline,
317 priv->lines_primitive);
318 cogl_object_unref (lines_pipeline);
323 clutter_deform_effect_free_arrays (ClutterDeformEffect *self)
325 ClutterDeformEffectPrivate *priv = self->priv;
329 cogl_object_unref (priv->buffer);
335 cogl_object_unref (priv->primitive);
336 priv->primitive = NULL;
339 if (priv->lines_primitive)
341 cogl_object_unref (priv->lines_primitive);
342 priv->lines_primitive = NULL;
347 clutter_deform_effect_init_arrays (ClutterDeformEffect *self)
349 ClutterDeformEffectPrivate *priv = self->priv;
350 gint x, y, direction, n_indices;
351 CoglAttribute *attributes[3];
352 guint16 *static_indices;
354 clutter_backend_get_cogl_context (clutter_get_default_backend ());
355 CoglIndices *indices;
359 clutter_deform_effect_free_arrays (self);
361 n_indices = ((2 + 2 * priv->x_tiles)
363 + (priv->y_tiles - 1));
365 static_indices = g_new (guint16, n_indices);
367 #define MESH_INDEX(x,y) ((y) * (priv->x_tiles + 1) + (x))
369 /* compute all the triangles from the various tiles */
372 idx = static_indices;
373 idx[0] = MESH_INDEX (0, 0);
374 idx[1] = MESH_INDEX (0, 1);
377 for (y = 0; y < priv->y_tiles; y++)
379 for (x = 0; x < priv->x_tiles; x++)
383 idx[0] = MESH_INDEX (x + 1, y);
384 idx[1] = MESH_INDEX (x + 1, y + 1);
388 idx[0] = MESH_INDEX (priv->x_tiles - x - 1, y);
389 idx[1] = MESH_INDEX (priv->x_tiles - x - 1, y + 1);
395 if (y == (priv->y_tiles - 1))
400 idx[0] = MESH_INDEX (priv->x_tiles, y + 1);
401 idx[1] = MESH_INDEX (priv->x_tiles, y + 1);
402 idx[2] = MESH_INDEX (priv->x_tiles, y + 2);
406 idx[0] = MESH_INDEX (0, y + 1);
407 idx[1] = MESH_INDEX (0, y + 1);
408 idx[2] = MESH_INDEX (0, y + 2);
413 direction = !direction;
418 indices = cogl_indices_new (ctx,
419 COGL_INDICES_TYPE_UNSIGNED_SHORT,
423 g_free (static_indices);
425 priv->n_vertices = (priv->x_tiles + 1) * (priv->y_tiles + 1);
428 cogl_attribute_buffer_new (ctx,
429 sizeof (CoglVertexP3T2C4) *
433 /* The application is expected to continuously modify the vertices
434 so we should give a hint to Cogl about that */
435 cogl_buffer_set_update_hint (COGL_BUFFER (priv->buffer),
436 COGL_BUFFER_UPDATE_HINT_DYNAMIC);
438 attributes[0] = cogl_attribute_new (priv->buffer,
440 sizeof (CoglVertexP3T2C4),
441 G_STRUCT_OFFSET (CoglVertexP3T2C4, x),
442 3, /* n_components */
443 COGL_ATTRIBUTE_TYPE_FLOAT);
444 attributes[1] = cogl_attribute_new (priv->buffer,
445 "cogl_tex_coord0_in",
446 sizeof (CoglVertexP3T2C4),
447 G_STRUCT_OFFSET (CoglVertexP3T2C4, s),
448 2, /* n_components */
449 COGL_ATTRIBUTE_TYPE_FLOAT);
450 attributes[2] = cogl_attribute_new (priv->buffer,
452 sizeof (CoglVertexP3T2C4),
453 G_STRUCT_OFFSET (CoglVertexP3T2C4, r),
454 4, /* n_components */
455 COGL_ATTRIBUTE_TYPE_UNSIGNED_BYTE);
458 cogl_primitive_new_with_attributes (COGL_VERTICES_MODE_TRIANGLE_STRIP,
461 3 /* n_attributes */);
462 cogl_primitive_set_indices (priv->primitive,
466 if (G_UNLIKELY (clutter_paint_debug_flags & CLUTTER_DEBUG_PAINT_DEFORM_TILES))
468 priv->lines_primitive =
469 cogl_primitive_new_with_attributes (COGL_VERTICES_MODE_LINE_STRIP,
472 2 /* n_attributes */);
473 cogl_primitive_set_indices (priv->lines_primitive,
478 cogl_object_unref (indices);
480 for (i = 0; i < 3; i++)
481 cogl_object_unref (attributes[i]);
483 priv->is_dirty = TRUE;
487 clutter_deform_effect_free_back_pipeline (ClutterDeformEffect *self)
489 ClutterDeformEffectPrivate *priv = self->priv;
491 if (priv->back_pipeline != NULL)
493 cogl_object_unref (priv->back_pipeline);
494 priv->back_pipeline = NULL;
499 clutter_deform_effect_finalize (GObject *gobject)
501 ClutterDeformEffect *self = CLUTTER_DEFORM_EFFECT (gobject);
503 clutter_deform_effect_free_arrays (self);
504 clutter_deform_effect_free_back_pipeline (self);
506 G_OBJECT_CLASS (clutter_deform_effect_parent_class)->finalize (gobject);
510 clutter_deform_effect_set_property (GObject *gobject,
515 ClutterDeformEffect *self = CLUTTER_DEFORM_EFFECT (gobject);
520 clutter_deform_effect_set_n_tiles (self, g_value_get_uint (value),
521 self->priv->y_tiles);
525 clutter_deform_effect_set_n_tiles (self, self->priv->x_tiles,
526 g_value_get_uint (value));
529 case PROP_BACK_MATERIAL:
530 clutter_deform_effect_set_back_material (self, g_value_get_boxed (value));
534 G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
540 clutter_deform_effect_get_property (GObject *gobject,
545 ClutterDeformEffectPrivate *priv = CLUTTER_DEFORM_EFFECT (gobject)->priv;
550 g_value_set_uint (value, priv->x_tiles);
554 g_value_set_uint (value, priv->y_tiles);
557 case PROP_BACK_MATERIAL:
558 g_value_set_boxed (value, priv->back_pipeline);
562 G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
568 clutter_deform_effect_class_init (ClutterDeformEffectClass *klass)
570 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
571 ClutterActorMetaClass *meta_class = CLUTTER_ACTOR_META_CLASS (klass);
572 ClutterOffscreenEffectClass *offscreen_class = CLUTTER_OFFSCREEN_EFFECT_CLASS (klass);
574 g_type_class_add_private (klass, sizeof (ClutterDeformEffectPrivate));
576 klass->deform_vertex = clutter_deform_effect_real_deform_vertex;
579 * ClutterDeformEffect:x-tiles:
581 * The number of horizontal tiles. The bigger the number, the
586 obj_props[PROP_X_TILES] =
587 g_param_spec_uint ("x-tiles",
588 P_("Horizontal Tiles"),
589 P_("The number of horizontal tiles"),
592 CLUTTER_PARAM_READWRITE);
595 * ClutterDeformEffect:y-tiles:
597 * The number of vertical tiles. The bigger the number, the
602 obj_props[PROP_Y_TILES] =
603 g_param_spec_uint ("y-tiles",
604 P_("Vertical Tiles"),
605 P_("The number of vertical tiles"),
608 CLUTTER_PARAM_READWRITE);
611 * ClutterDeformEffect:back-material:
613 * A material to be used when painting the back of the actor
614 * to which this effect has been applied
616 * By default, no material will be used
620 obj_props[PROP_BACK_MATERIAL] =
621 g_param_spec_boxed ("back-material",
623 P_("The material to be used when painting the back of the actor"),
625 CLUTTER_PARAM_READWRITE);
627 gobject_class->finalize = clutter_deform_effect_finalize;
628 gobject_class->set_property = clutter_deform_effect_set_property;
629 gobject_class->get_property = clutter_deform_effect_get_property;
630 g_object_class_install_properties (gobject_class,
634 meta_class->set_actor = clutter_deform_effect_set_actor;
636 offscreen_class->paint_target = clutter_deform_effect_paint_target;
640 clutter_deform_effect_init (ClutterDeformEffect *self)
642 self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, CLUTTER_TYPE_DEFORM_EFFECT,
643 ClutterDeformEffectPrivate);
645 self->priv->x_tiles = self->priv->y_tiles = DEFAULT_N_TILES;
646 self->priv->back_pipeline = NULL;
648 clutter_deform_effect_init_arrays (self);
652 * clutter_deform_effect_set_back_material:
653 * @effect: a #ClutterDeformEffect
654 * @material: (allow-none): a handle to a Cogl material
656 * Sets the material that should be used when drawing the back face
657 * of the actor during a deformation
659 * The #ClutterDeformEffect will take a reference on the material's
665 clutter_deform_effect_set_back_material (ClutterDeformEffect *effect,
668 ClutterDeformEffectPrivate *priv;
669 CoglPipeline *pipeline = COGL_PIPELINE (material);
671 g_return_if_fail (CLUTTER_IS_DEFORM_EFFECT (effect));
672 g_return_if_fail (pipeline == NULL || cogl_is_pipeline (pipeline));
676 clutter_deform_effect_free_back_pipeline (effect);
678 priv->back_pipeline = material;
679 if (priv->back_pipeline != NULL)
680 cogl_object_ref (priv->back_pipeline);
682 clutter_deform_effect_invalidate (effect);
686 * clutter_deform_effect_get_back_material:
687 * @effect: a #ClutterDeformEffect
689 * Retrieves the handle to the back face material used by @effect
691 * Return value: (transfer none): a handle for the material, or %NULL.
692 * The returned material is owned by the #ClutterDeformEffect and it
693 * should not be freed directly
698 clutter_deform_effect_get_back_material (ClutterDeformEffect *effect)
700 g_return_val_if_fail (CLUTTER_IS_DEFORM_EFFECT (effect), NULL);
702 return effect->priv->back_pipeline;
706 * clutter_deform_effect_set_n_tiles:
707 * @effect: a #ClutterDeformEffect
708 * @x_tiles: number of horizontal tiles
709 * @y_tiles: number of vertical tiles
711 * Sets the number of horizontal and vertical tiles to be used
712 * when applying the effect
714 * More tiles allow a finer grained deformation at the expenses
720 clutter_deform_effect_set_n_tiles (ClutterDeformEffect *effect,
724 ClutterDeformEffectPrivate *priv;
725 gboolean tiles_changed = FALSE;
727 g_return_if_fail (CLUTTER_IS_DEFORM_EFFECT (effect));
728 g_return_if_fail (x_tiles > 0 && y_tiles > 0);
732 g_object_freeze_notify (G_OBJECT (effect));
734 if (priv->x_tiles != x_tiles)
736 priv->x_tiles = x_tiles;
738 g_object_notify_by_pspec (G_OBJECT (effect), obj_props[PROP_X_TILES]);
740 tiles_changed = TRUE;
743 if (priv->y_tiles != y_tiles)
745 priv->y_tiles = y_tiles;
747 g_object_notify_by_pspec (G_OBJECT (effect), obj_props[PROP_Y_TILES]);
749 tiles_changed = TRUE;
754 clutter_deform_effect_init_arrays (effect);
755 clutter_deform_effect_invalidate (effect);
758 g_object_thaw_notify (G_OBJECT (effect));
762 * clutter_deform_effect_get_n_tiles:
763 * @effect: a #ClutterDeformEffect
764 * @x_tiles: (out): return location for the number of horizontal tiles,
766 * @y_tiles: (out): return location for the number of vertical tiles,
769 * Retrieves the number of horizontal and vertical tiles used to sub-divide
770 * the actor's geometry during the effect
775 clutter_deform_effect_get_n_tiles (ClutterDeformEffect *effect,
779 g_return_if_fail (CLUTTER_IS_DEFORM_EFFECT (effect));
782 *x_tiles = effect->priv->x_tiles;
785 *y_tiles = effect->priv->y_tiles;
789 * clutter_deform_effect_invalidate:
790 * @effect: a #ClutterDeformEffect
792 * Invalidates the @effect<!-- -->'s vertices and, if it is associated
793 * to an actor, it will queue a redraw
798 clutter_deform_effect_invalidate (ClutterDeformEffect *effect)
802 g_return_if_fail (CLUTTER_IS_DEFORM_EFFECT (effect));
804 if (effect->priv->is_dirty)
807 effect->priv->is_dirty = TRUE;
809 actor = clutter_actor_meta_get_actor (CLUTTER_ACTOR_META (effect));
811 clutter_effect_queue_repaint (CLUTTER_EFFECT (effect));