Release Clutter 1.11.4 (snapshot)
[profile/ivi/clutter.git] / clutter / clutter-deform-effect.c
1 /*
2  * Clutter.
3  *
4  * An OpenGL based 'interactive canvas' library.
5  *
6  * Copyright (C) 2010  Intel Corporation.
7  *
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.
12  *
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.
17  *
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/>.
20  *
21  * Author:
22  *   Emmanuele Bassi <ebassi@linux.intel.com>
23  *
24  * Based on the MxDeformTexture class, written by:
25  *   Chris Lord <chris@linux.intel.com>
26  */
27
28 /**
29  * SECTION:clutter-deform-effect
30  * @Title: ClutterDeformEffect
31  * @Short_Description: A base class for effects deforming the geometry
32  *   of an actor
33  *
34  * #ClutterDeformEffect is an abstract class providing all the plumbing
35  * for creating effects that result in the deformation of an actor's
36  * geometry.
37  *
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.
41  *
42  * <refsect2>
43  *   <title>Implementing ClutterDeformEffect</title>
44  *   <para>Sub-classes of #ClutterDeformEffect should override the
45  *   #ClutterDeformEffectClass.deform_vertex() 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>
50  * </refsect2>
51  *
52  * #ClutterDeformEffect is available since Clutter 1.4
53  */
54
55 #ifdef HAVE_CONFIG_H
56 #include "config.h"
57 #endif
58
59 #define CLUTTER_ENABLE_EXPERIMENTAL_API
60 #include "clutter-deform-effect.h"
61
62 #include <cogl/cogl.h>
63
64 #include "clutter-debug.h"
65 #include "clutter-enum-types.h"
66 #include "clutter-offscreen-effect-private.h"
67 #include "clutter-private.h"
68
69 #define DEFAULT_N_TILES         32
70
71 struct _ClutterDeformEffectPrivate
72 {
73   CoglPipeline *back_pipeline;
74
75   gint x_tiles;
76   gint y_tiles;
77
78   CoglAttributeBuffer *buffer;
79
80   CoglPrimitive *primitive;
81
82   CoglPrimitive *lines_primitive;
83
84   gint n_vertices;
85
86   gulong allocation_id;
87
88   guint is_dirty : 1;
89 };
90
91 enum
92 {
93   PROP_0,
94
95   PROP_X_TILES,
96   PROP_Y_TILES,
97
98   PROP_BACK_MATERIAL,
99
100   PROP_LAST
101 };
102
103 static GParamSpec *obj_props[PROP_LAST];
104
105 G_DEFINE_ABSTRACT_TYPE (ClutterDeformEffect,
106                         clutter_deform_effect,
107                         CLUTTER_TYPE_OFFSCREEN_EFFECT);
108
109 static void
110 clutter_deform_effect_real_deform_vertex (ClutterDeformEffect *effect,
111                                           gfloat               width,
112                                           gfloat               height,
113                                           CoglTextureVertex   *vertex)
114 {
115   g_warning ("%s: Deformation effect of type '%s' does not implement "
116              "the required ClutterDeformEffect::deform_vertex virtual "
117              "function.",
118              G_STRLOC,
119              G_OBJECT_TYPE_NAME (effect));
120 }
121
122 static void
123 clutter_deform_effect_deform_vertex (ClutterDeformEffect *effect,
124                                      gfloat               width,
125                                      gfloat               height,
126                                      CoglTextureVertex   *vertex)
127 {
128   CLUTTER_DEFORM_EFFECT_GET_CLASS (effect)->deform_vertex (effect,
129                                                            width, height,
130                                                            vertex);
131 }
132
133 static void
134 vbo_invalidate (ClutterActor           *actor,
135                 const ClutterActorBox  *allocation,
136                 ClutterAllocationFlags  flags,
137                 ClutterDeformEffect    *effect)
138 {
139   effect->priv->is_dirty = TRUE;
140 }
141
142 static void
143 clutter_deform_effect_set_actor (ClutterActorMeta *meta,
144                                  ClutterActor     *actor)
145 {
146   ClutterDeformEffectPrivate *priv = CLUTTER_DEFORM_EFFECT (meta)->priv;
147
148   if (priv->allocation_id != 0)
149     {
150       ClutterActor *old_actor = clutter_actor_meta_get_actor (meta);
151
152       if (old_actor != NULL)
153         g_signal_handler_disconnect (old_actor, priv->allocation_id);
154
155       priv->allocation_id = 0;
156     }
157
158   /* we need to invalidate the VBO whenever the allocation of the actor
159    * changes
160    */
161   if (actor != NULL)
162     priv->allocation_id = g_signal_connect (actor, "allocation-changed",
163                                             G_CALLBACK (vbo_invalidate),
164                                             meta);
165
166   priv->is_dirty = TRUE;
167
168   CLUTTER_ACTOR_META_CLASS (clutter_deform_effect_parent_class)->set_actor (meta, actor);
169 }
170
171 static void
172 clutter_deform_effect_paint_target (ClutterOffscreenEffect *effect)
173 {
174   ClutterDeformEffect *self= CLUTTER_DEFORM_EFFECT (effect);
175   ClutterDeformEffectPrivate *priv = self->priv;
176   CoglHandle material;
177   CoglPipeline *pipeline;
178   CoglDepthState depth_state;
179   CoglFramebuffer *fb = cogl_get_draw_framebuffer ();
180
181   if (priv->is_dirty)
182     {
183       gboolean mapped_buffer;
184       CoglVertexP3T2C4 *verts;
185       ClutterActor *actor;
186       gfloat width, height;
187       guint opacity;
188       gint i, j;
189
190       actor = clutter_actor_meta_get_actor (CLUTTER_ACTOR_META (effect));
191       opacity = clutter_actor_get_paint_opacity (actor);
192
193       /* if we don't have a target size, fall back to the actor's
194        * allocation, though wrong it might be
195        */
196       if (!clutter_offscreen_effect_get_target_size (effect, &width, &height))
197         clutter_actor_get_size (actor, &width, &height);
198
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
203        */
204       verts = cogl_buffer_map (COGL_BUFFER (priv->buffer),
205                                COGL_BUFFER_ACCESS_WRITE,
206                                COGL_BUFFER_MAP_HINT_DISCARD);
207
208       /* If the map failed then we'll resort to allocating a temporary
209          buffer */
210       if (verts == NULL)
211         {
212           mapped_buffer = FALSE;
213           verts = g_malloc (sizeof (*verts) * priv->n_vertices);
214         }
215       else
216         mapped_buffer = TRUE;
217
218       for (i = 0; i < priv->y_tiles + 1; i++)
219         {
220           for (j = 0; j < priv->x_tiles + 1; j++)
221             {
222               CoglVertexP3T2C4 *vertex_out;
223               CoglTextureVertex vertex;
224
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 */
234
235               vertex.tx = (float) j / priv->x_tiles;
236               vertex.ty = (float) i / priv->y_tiles;
237
238               vertex.x = width * vertex.tx;
239               vertex.y = height * vertex.ty;
240               vertex.z = 0.0f;
241
242               cogl_color_init_from_4ub (&vertex.color, 255, 255, 255, opacity);
243
244               clutter_deform_effect_deform_vertex (self,
245                                                    width, height,
246                                                    &vertex);
247
248               vertex_out = verts + i * (priv->x_tiles + 1) + j;
249
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);
259             }
260         }
261
262       if (mapped_buffer)
263         cogl_buffer_unmap (COGL_BUFFER (priv->buffer));
264       else
265         {
266           cogl_buffer_set_data (COGL_BUFFER (priv->buffer),
267                                 0, /* offset */
268                                 verts,
269                                 sizeof (*verts) * priv->n_vertices);
270           g_free (verts);
271         }
272
273       priv->is_dirty = FALSE;
274     }
275
276   material = clutter_offscreen_effect_get_target (effect);
277   pipeline = COGL_PIPELINE (material);
278
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);
283
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);
288
289   /* draw the front */
290   if (material != NULL)
291     cogl_framebuffer_draw_primitive (fb, pipeline, priv->primitive);
292
293   /* draw the back */
294   if (priv->back_pipeline != NULL)
295     {
296       CoglPipeline *back_pipeline;
297
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);
304
305       cogl_framebuffer_draw_primitive (fb, back_pipeline, priv->primitive);
306
307       cogl_object_unref (back_pipeline);
308     }
309
310   if (G_UNLIKELY (priv->lines_primitive != NULL))
311     {
312       CoglContext *ctx =
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);
319     }
320 }
321
322 static inline void
323 clutter_deform_effect_free_arrays (ClutterDeformEffect *self)
324 {
325   ClutterDeformEffectPrivate *priv = self->priv;
326
327   if (priv->buffer)
328     {
329       cogl_object_unref (priv->buffer);
330       priv->buffer = NULL;
331     }
332
333   if (priv->primitive)
334     {
335       cogl_object_unref (priv->primitive);
336       priv->primitive = NULL;
337     }
338
339   if (priv->lines_primitive)
340     {
341       cogl_object_unref (priv->lines_primitive);
342       priv->lines_primitive = NULL;
343     }
344 }
345
346 static void
347 clutter_deform_effect_init_arrays (ClutterDeformEffect *self)
348 {
349   ClutterDeformEffectPrivate *priv = self->priv;
350   gint x, y, direction, n_indices;
351   CoglAttribute *attributes[3];
352   guint16 *static_indices;
353   CoglContext *ctx =
354     clutter_backend_get_cogl_context (clutter_get_default_backend ());
355   CoglIndices *indices;
356   guint16 *idx;
357   int i;
358
359   clutter_deform_effect_free_arrays (self);
360
361   n_indices = ((2 + 2 * priv->x_tiles)
362                * priv->y_tiles
363                + (priv->y_tiles - 1));
364
365   static_indices = g_new (guint16, n_indices);
366
367 #define MESH_INDEX(x,y) ((y) * (priv->x_tiles + 1) + (x))
368
369   /* compute all the triangles from the various tiles */
370   direction = 1;
371
372   idx = static_indices;
373   idx[0] = MESH_INDEX (0, 0);
374   idx[1] = MESH_INDEX (0, 1);
375   idx += 2;
376
377   for (y = 0; y < priv->y_tiles; y++)
378     {
379       for (x = 0; x < priv->x_tiles; x++)
380         {
381           if (direction)
382             {
383               idx[0] = MESH_INDEX (x + 1, y);
384               idx[1] = MESH_INDEX (x + 1, y + 1);
385             }
386           else
387             {
388               idx[0] = MESH_INDEX (priv->x_tiles - x - 1, y);
389               idx[1] = MESH_INDEX (priv->x_tiles - x - 1, y + 1);
390             }
391
392           idx += 2;
393         }
394
395       if (y == (priv->y_tiles - 1))
396         break;
397
398       if (direction)
399         {
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);
403         }
404       else
405         {
406           idx[0] = MESH_INDEX (0, y + 1);
407           idx[1] = MESH_INDEX (0, y + 1);
408           idx[2] = MESH_INDEX (0, y + 2);
409         }
410
411       idx += 3;
412
413       direction = !direction;
414     }
415
416 #undef MESH_INDEX
417
418   indices = cogl_indices_new (ctx,
419                               COGL_INDICES_TYPE_UNSIGNED_SHORT,
420                               static_indices,
421                               n_indices);
422
423   g_free (static_indices);
424
425   priv->n_vertices = (priv->x_tiles + 1) * (priv->y_tiles + 1);
426
427   priv->buffer =
428     cogl_attribute_buffer_new (ctx,
429                                sizeof (CoglVertexP3T2C4) *
430                                priv->n_vertices,
431                                NULL);
432
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);
437
438   attributes[0] = cogl_attribute_new (priv->buffer,
439                                       "cogl_position_in",
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,
451                                       "cogl_color_in",
452                                       sizeof (CoglVertexP3T2C4),
453                                       G_STRUCT_OFFSET (CoglVertexP3T2C4, r),
454                                       4, /* n_components */
455                                       COGL_ATTRIBUTE_TYPE_UNSIGNED_BYTE);
456
457   priv->primitive =
458     cogl_primitive_new_with_attributes (COGL_VERTICES_MODE_TRIANGLE_STRIP,
459                                         priv->n_vertices,
460                                         attributes,
461                                         3 /* n_attributes */);
462   cogl_primitive_set_indices (priv->primitive,
463                               indices,
464                               n_indices);
465
466   if (G_UNLIKELY (clutter_paint_debug_flags & CLUTTER_DEBUG_PAINT_DEFORM_TILES))
467     {
468       priv->lines_primitive =
469         cogl_primitive_new_with_attributes (COGL_VERTICES_MODE_LINE_STRIP,
470                                             priv->n_vertices,
471                                             attributes,
472                                             2 /* n_attributes */);
473       cogl_primitive_set_indices (priv->lines_primitive,
474                                   indices,
475                                   n_indices);
476     }
477
478   cogl_object_unref (indices);
479
480   for (i = 0; i < 3; i++)
481     cogl_object_unref (attributes[i]);
482
483   priv->is_dirty = TRUE;
484 }
485
486 static inline void
487 clutter_deform_effect_free_back_pipeline (ClutterDeformEffect *self)
488 {
489   ClutterDeformEffectPrivate *priv = self->priv;
490
491   if (priv->back_pipeline != NULL)
492     {
493       cogl_object_unref (priv->back_pipeline);
494       priv->back_pipeline = NULL;
495     }
496 }
497
498 static void
499 clutter_deform_effect_finalize (GObject *gobject)
500 {
501   ClutterDeformEffect *self = CLUTTER_DEFORM_EFFECT (gobject);
502
503   clutter_deform_effect_free_arrays (self);
504   clutter_deform_effect_free_back_pipeline (self);
505
506   G_OBJECT_CLASS (clutter_deform_effect_parent_class)->finalize (gobject);
507 }
508
509 static void
510 clutter_deform_effect_set_property (GObject      *gobject,
511                                     guint         prop_id,
512                                     const GValue *value,
513                                     GParamSpec   *pspec)
514 {
515   ClutterDeformEffect *self = CLUTTER_DEFORM_EFFECT (gobject);
516
517   switch (prop_id)
518     {
519     case PROP_X_TILES:
520       clutter_deform_effect_set_n_tiles (self, g_value_get_uint (value),
521                                          self->priv->y_tiles);
522       break;
523
524     case PROP_Y_TILES:
525       clutter_deform_effect_set_n_tiles (self, self->priv->x_tiles,
526                                          g_value_get_uint (value));
527       break;
528
529     case PROP_BACK_MATERIAL:
530       clutter_deform_effect_set_back_material (self, g_value_get_boxed (value));
531       break;
532
533     default:
534       G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
535       break;
536     }
537 }
538
539 static void
540 clutter_deform_effect_get_property (GObject    *gobject,
541                                     guint       prop_id,
542                                     GValue     *value,
543                                     GParamSpec *pspec)
544 {
545   ClutterDeformEffectPrivate *priv = CLUTTER_DEFORM_EFFECT (gobject)->priv;
546
547   switch (prop_id)
548     {
549     case PROP_X_TILES:
550       g_value_set_uint (value, priv->x_tiles);
551       break;
552
553     case PROP_Y_TILES:
554       g_value_set_uint (value, priv->y_tiles);
555       break;
556
557     case PROP_BACK_MATERIAL:
558       g_value_set_boxed (value, priv->back_pipeline);
559       break;
560
561     default:
562       G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
563       break;
564     }
565 }
566
567 static void
568 clutter_deform_effect_class_init (ClutterDeformEffectClass *klass)
569 {
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);
573
574   g_type_class_add_private (klass, sizeof (ClutterDeformEffectPrivate));
575
576   klass->deform_vertex = clutter_deform_effect_real_deform_vertex;
577
578   /**
579    * ClutterDeformEffect:x-tiles:
580    *
581    * The number of horizontal tiles. The bigger the number, the
582    * smaller the tiles
583    *
584    * Since: 1.4
585    */
586   obj_props[PROP_X_TILES] =
587     g_param_spec_uint ("x-tiles",
588                        P_("Horizontal Tiles"),
589                        P_("The number of horizontal tiles"),
590                        1, G_MAXUINT,
591                        DEFAULT_N_TILES,
592                        CLUTTER_PARAM_READWRITE);
593
594   /**
595    * ClutterDeformEffect:y-tiles:
596    *
597    * The number of vertical tiles. The bigger the number, the
598    * smaller the tiles
599    *
600    * Since: 1.4
601    */
602   obj_props[PROP_Y_TILES] =
603     g_param_spec_uint ("y-tiles",
604                        P_("Vertical Tiles"),
605                        P_("The number of vertical tiles"),
606                        1, G_MAXUINT,
607                        DEFAULT_N_TILES,
608                        CLUTTER_PARAM_READWRITE);
609
610   /**
611    * ClutterDeformEffect:back-material:
612    *
613    * A material to be used when painting the back of the actor
614    * to which this effect has been applied
615    *
616    * By default, no material will be used
617    *
618    * Since: 1.4
619    */
620   obj_props[PROP_BACK_MATERIAL] =
621     g_param_spec_boxed ("back-material",
622                         P_("Back Material"),
623                         P_("The material to be used when painting the back of the actor"),
624                         COGL_TYPE_HANDLE,
625                         CLUTTER_PARAM_READWRITE);
626
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,
631                                      PROP_LAST,
632                                      obj_props);
633
634   meta_class->set_actor = clutter_deform_effect_set_actor;
635
636   offscreen_class->paint_target = clutter_deform_effect_paint_target;
637 }
638
639 static void
640 clutter_deform_effect_init (ClutterDeformEffect *self)
641 {
642   self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, CLUTTER_TYPE_DEFORM_EFFECT,
643                                             ClutterDeformEffectPrivate);
644
645   self->priv->x_tiles = self->priv->y_tiles = DEFAULT_N_TILES;
646   self->priv->back_pipeline = NULL;
647
648   clutter_deform_effect_init_arrays (self);
649 }
650
651 /**
652  * clutter_deform_effect_set_back_material:
653  * @effect: a #ClutterDeformEffect
654  * @material: (allow-none): a handle to a Cogl material
655  *
656  * Sets the material that should be used when drawing the back face
657  * of the actor during a deformation
658  *
659  * The #ClutterDeformEffect will take a reference on the material's
660  * handle
661  *
662  * Since: 1.4
663  */
664 void
665 clutter_deform_effect_set_back_material (ClutterDeformEffect *effect,
666                                          CoglHandle           material)
667 {
668   ClutterDeformEffectPrivate *priv;
669   CoglPipeline *pipeline = COGL_PIPELINE (material);
670
671   g_return_if_fail (CLUTTER_IS_DEFORM_EFFECT (effect));
672   g_return_if_fail (pipeline == NULL || cogl_is_pipeline (pipeline));
673
674   priv = effect->priv;
675
676   clutter_deform_effect_free_back_pipeline (effect);
677
678   priv->back_pipeline = material;
679   if (priv->back_pipeline != NULL)
680     cogl_object_ref (priv->back_pipeline);
681
682   clutter_deform_effect_invalidate (effect);
683 }
684
685 /**
686  * clutter_deform_effect_get_back_material:
687  * @effect: a #ClutterDeformEffect
688  *
689  * Retrieves the handle to the back face material used by @effect
690  *
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
694  *
695  * Since: 1.4
696  */
697 CoglHandle
698 clutter_deform_effect_get_back_material (ClutterDeformEffect *effect)
699 {
700   g_return_val_if_fail (CLUTTER_IS_DEFORM_EFFECT (effect), NULL);
701
702   return effect->priv->back_pipeline;
703 }
704
705 /**
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
710  *
711  * Sets the number of horizontal and vertical tiles to be used
712  * when applying the effect
713  *
714  * More tiles allow a finer grained deformation at the expenses
715  * of computation
716  *
717  * Since: 1.4
718  */
719 void
720 clutter_deform_effect_set_n_tiles (ClutterDeformEffect *effect,
721                                    guint                x_tiles,
722                                    guint                y_tiles)
723 {
724   ClutterDeformEffectPrivate *priv;
725   gboolean tiles_changed = FALSE;
726
727   g_return_if_fail (CLUTTER_IS_DEFORM_EFFECT (effect));
728   g_return_if_fail (x_tiles > 0 && y_tiles > 0);
729
730   priv = effect->priv;
731
732   g_object_freeze_notify (G_OBJECT (effect));
733
734   if (priv->x_tiles != x_tiles)
735     {
736       priv->x_tiles = x_tiles;
737
738       g_object_notify_by_pspec (G_OBJECT (effect), obj_props[PROP_X_TILES]);
739
740       tiles_changed = TRUE;
741     }
742
743   if (priv->y_tiles != y_tiles)
744     {
745       priv->y_tiles = y_tiles;
746
747       g_object_notify_by_pspec (G_OBJECT (effect), obj_props[PROP_Y_TILES]);
748
749       tiles_changed = TRUE;
750     }
751
752   if (tiles_changed)
753     {
754       clutter_deform_effect_init_arrays (effect);
755       clutter_deform_effect_invalidate (effect);
756     }
757
758   g_object_thaw_notify (G_OBJECT (effect));
759 }
760
761 /**
762  * clutter_deform_effect_get_n_tiles:
763  * @effect: a #ClutterDeformEffect
764  * @x_tiles: (out): return location for the number of horizontal tiles,
765  *   or %NULL
766  * @y_tiles: (out): return location for the number of vertical tiles,
767  *   or %NULL
768  *
769  * Retrieves the number of horizontal and vertical tiles used to sub-divide
770  * the actor's geometry during the effect
771  *
772  * Since: 1.4
773  */
774 void
775 clutter_deform_effect_get_n_tiles (ClutterDeformEffect *effect,
776                                    guint               *x_tiles,
777                                    guint               *y_tiles)
778 {
779   g_return_if_fail (CLUTTER_IS_DEFORM_EFFECT (effect));
780
781   if (x_tiles != NULL)
782     *x_tiles = effect->priv->x_tiles;
783
784   if (y_tiles != NULL)
785     *y_tiles = effect->priv->y_tiles;
786 }
787
788 /**
789  * clutter_deform_effect_invalidate:
790  * @effect: a #ClutterDeformEffect
791  *
792  * Invalidates the @effect<!-- -->'s vertices and, if it is associated
793  * to an actor, it will queue a redraw
794  *
795  * Since: 1.4
796  */
797 void
798 clutter_deform_effect_invalidate (ClutterDeformEffect *effect)
799 {
800   ClutterActor *actor;
801
802   g_return_if_fail (CLUTTER_IS_DEFORM_EFFECT (effect));
803
804   if (effect->priv->is_dirty)
805     return;
806
807   effect->priv->is_dirty = TRUE;
808
809   actor = clutter_actor_meta_get_actor (CLUTTER_ACTOR_META (effect));
810   if (actor != NULL)
811     clutter_effect_queue_repaint (CLUTTER_EFFECT (effect));
812 }