/* * 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 . * * Author: * Emmanuele Bassi * * Based on the MxDeformTexture class, written by: * Chris Lord */ /** * SECTION:clutter-deform-effect * @Title: ClutterDeformEffect * @Short_Description: A base class for effects deforming the geometry * of an actor * * #ClutterDeformEffect is an abstract class providing all the plumbing * for creating effects that result in the deformation of an actor's * geometry. * * #ClutterDeformEffect uses offscreen buffers to render the contents of * a #ClutterActor and then the Cogl vertex buffers API to submit the * geometry to the GPU. * * * Implementing ClutterDeformEffect * Sub-classes of #ClutterDeformEffect should override the * deform_vertex() virtual function; this function * is called on every vertex that needs to be deformed by the effect. * Each passed vertex is an in-out parameter that initially contains the * position of the vertex and should be modified according to a specific * deformation algorithm. * * * #ClutterDeformEffect is available since Clutter 1.4 */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "clutter-deform-effect.h" #include #include "clutter-debug.h" #include "clutter-enum-types.h" #include "clutter-offscreen-effect-private.h" #include "clutter-private.h" #define DEFAULT_N_TILES 32 struct _ClutterDeformEffectPrivate { CoglHandle back_material; gint x_tiles; gint y_tiles; CoglHandle vbo; CoglHandle indices; CoglHandle back_indices; gint n_indices; CoglTextureVertex *vertices; gulong allocation_id; guint is_dirty : 1; }; enum { PROP_0, PROP_X_TILES, PROP_Y_TILES, PROP_BACK_MATERIAL, PROP_LAST }; static GParamSpec *obj_props[PROP_LAST]; G_DEFINE_ABSTRACT_TYPE (ClutterDeformEffect, clutter_deform_effect, CLUTTER_TYPE_OFFSCREEN_EFFECT); static void clutter_deform_effect_real_deform_vertex (ClutterDeformEffect *effect, gfloat width, gfloat height, CoglTextureVertex *vertex) { g_warning ("%s: Deformation effect of type '%s' does not implement " "the required ClutterDeformEffect::deform_vertex virtual " "function.", G_STRLOC, G_OBJECT_TYPE_NAME (effect)); } void _clutter_deform_effect_deform_vertex (ClutterDeformEffect *effect, gfloat width, gfloat height, CoglTextureVertex *vertex) { CLUTTER_DEFORM_EFFECT_GET_CLASS (effect)->deform_vertex (effect, width, height, vertex); } static void vbo_invalidate (ClutterActor *actor, const ClutterActorBox *allocation, ClutterAllocationFlags flags, ClutterDeformEffect *effect) { effect->priv->is_dirty = TRUE; } static void clutter_deform_effect_set_actor (ClutterActorMeta *meta, ClutterActor *actor) { ClutterDeformEffectPrivate *priv = CLUTTER_DEFORM_EFFECT (meta)->priv; if (priv->allocation_id != 0) { ClutterActor *old_actor = clutter_actor_meta_get_actor (meta); g_signal_handler_disconnect (old_actor, priv->allocation_id); priv->allocation_id = 0; } /* we need to invalidate the VBO whenever the allocation of the actor * changes */ if (actor != NULL) priv->allocation_id = g_signal_connect (actor, "allocation-changed", G_CALLBACK (vbo_invalidate), meta); priv->is_dirty = TRUE; CLUTTER_ACTOR_META_CLASS (clutter_deform_effect_parent_class)->set_actor (meta, actor); } static void clutter_deform_effect_paint_target (ClutterOffscreenEffect *effect) { ClutterDeformEffect *self= CLUTTER_DEFORM_EFFECT (effect); ClutterDeformEffectPrivate *priv = self->priv; gboolean is_depth_enabled, is_cull_enabled; CoglHandle material; gint n_tiles; if (priv->is_dirty) { ClutterActor *actor; gfloat width, height; guint opacity; gint i, j; actor = clutter_actor_meta_get_actor (CLUTTER_ACTOR_META (effect)); opacity = clutter_actor_get_paint_opacity (actor); /* if we don't have a target size, fall back to the actor's * allocation, though wrong it might be */ if (!clutter_offscreen_effect_get_target_size (effect, &width, &height)) clutter_actor_get_size (actor, &width, &height); for (i = 0; i < priv->y_tiles + 1; i++) { for (j = 0; j < priv->x_tiles + 1; j++) { CoglTextureVertex *vertex; vertex = &priv->vertices[(i * (priv->x_tiles + 1)) + j]; vertex->tx = (float) j / priv->x_tiles; vertex->ty = (float) i / priv->y_tiles; vertex->x = width * vertex->tx; vertex->y = height * vertex->ty; vertex->z = 0.0f; cogl_color_init_from_4ub (&vertex->color, 255, 255, 255, opacity); _clutter_deform_effect_deform_vertex (self, width, height, vertex); } } /* XXX in theory, the sub-classes should tell us what they changed * in the texture vertices; we then would be able to avoid resubmitting * the same data, if it did not change. for the time being, we resubmit * everything */ cogl_vertex_buffer_add (priv->vbo, "gl_Vertex", 3, COGL_ATTRIBUTE_TYPE_FLOAT, FALSE, sizeof (CoglTextureVertex), &priv->vertices->x); cogl_vertex_buffer_add (priv->vbo, "gl_MultiTexCoord0", 2, COGL_ATTRIBUTE_TYPE_FLOAT, FALSE, sizeof (CoglTextureVertex), &priv->vertices->tx); cogl_vertex_buffer_add (priv->vbo, "gl_Color", 4, COGL_ATTRIBUTE_TYPE_UNSIGNED_BYTE, FALSE, sizeof (CoglTextureVertex), &priv->vertices->color); priv->is_dirty = FALSE; } /* enable depth test, if it's not already enabled */ is_depth_enabled = cogl_get_depth_test_enabled (); if (!is_depth_enabled) cogl_set_depth_test_enabled (TRUE); /* enable backface culling if it's not already enabled and if * we have a back material */ is_cull_enabled = cogl_get_backface_culling_enabled (); if (priv->back_material != COGL_INVALID_HANDLE && !is_cull_enabled) cogl_set_backface_culling_enabled (TRUE); else if (priv->back_material == COGL_INVALID_HANDLE && is_cull_enabled) cogl_set_backface_culling_enabled (FALSE); n_tiles = (priv->x_tiles + 1) * (priv->y_tiles + 1); /* draw the front */ material = clutter_offscreen_effect_get_target (effect); if (material != COGL_INVALID_HANDLE) { cogl_set_source (material); cogl_vertex_buffer_draw_elements (priv->vbo, COGL_VERTICES_MODE_TRIANGLE_STRIP, priv->indices, 0, n_tiles, 0, priv->n_indices); } /* draw the back */ material = priv->back_material; if (material != COGL_INVALID_HANDLE) { cogl_set_source (priv->back_material); cogl_vertex_buffer_draw_elements (priv->vbo, COGL_VERTICES_MODE_TRIANGLE_STRIP, priv->back_indices, 0, n_tiles, 0, priv->n_indices); } /* restore the previous state */ if (!is_depth_enabled) cogl_set_depth_test_enabled (FALSE); if (priv->back_material != COGL_INVALID_HANDLE && !is_cull_enabled) cogl_set_backface_culling_enabled (FALSE); else if (priv->back_material == COGL_INVALID_HANDLE && is_cull_enabled) cogl_set_backface_culling_enabled (TRUE); } static inline void clutter_deform_effect_free_arrays (ClutterDeformEffect *self) { ClutterDeformEffectPrivate *priv = self->priv; if (priv->vbo != COGL_INVALID_HANDLE) { cogl_handle_unref (priv->vbo); priv->vbo = COGL_INVALID_HANDLE; } if (priv->indices != COGL_INVALID_HANDLE) { cogl_handle_unref (priv->indices); priv->indices = COGL_INVALID_HANDLE; } g_free (priv->vertices); priv->vertices = NULL; } static void clutter_deform_effect_init_arrays (ClutterDeformEffect *self) { ClutterDeformEffectPrivate *priv = self->priv; GLushort *static_indices, *static_back_indices; GLushort *idx, *back_idx; gint x, y, direction; gint n_tiles; clutter_deform_effect_free_arrays (self); priv->n_indices = (2 + 2 * priv->x_tiles) * priv->y_tiles + (priv->y_tiles - 1); static_indices = g_new (GLushort, priv->n_indices); static_back_indices = g_new (GLushort, priv->n_indices); #define MESH_INDEX(x,y) ((y) * (priv->x_tiles + 1) + (x)) /* compute all the triangles from the various tiles */ direction = 1; idx = static_indices; idx[0] = MESH_INDEX (0, 0); idx[1] = MESH_INDEX (0, 1); idx += 2; back_idx = static_back_indices; back_idx[0] = MESH_INDEX (priv->x_tiles, 0); back_idx[1] = MESH_INDEX (priv->x_tiles, 1); back_idx += 2; for (y = 0; y < priv->y_tiles; y++) { for (x = 0; x < priv->x_tiles; x++) { if (direction) { idx[0] = MESH_INDEX (x + 1, y); idx[1] = MESH_INDEX (x + 1, y + 1); back_idx[0] = MESH_INDEX (priv->x_tiles - (x + 1), y); back_idx[1] = MESH_INDEX (priv->x_tiles - (x + 1), y + 1); } else { idx[0] = MESH_INDEX (priv->x_tiles - x - 1, y); idx[1] = MESH_INDEX (priv->x_tiles - x - 1, y + 1); back_idx[0] = MESH_INDEX (x + 1, y); back_idx[1] = MESH_INDEX (x + 1, y + 1); } idx += 2; back_idx += 2; } if (y == (priv->y_tiles - 1)) break; if (direction) { idx[0] = MESH_INDEX (priv->x_tiles, y + 1); idx[1] = MESH_INDEX (priv->x_tiles, y + 1); idx[2] = MESH_INDEX (priv->x_tiles, y + 2); back_idx[0] = MESH_INDEX (0, y + 1); back_idx[1] = MESH_INDEX (0, y + 1); back_idx[2] = MESH_INDEX (0, y + 2); } else { idx[0] = MESH_INDEX (0, y + 1); idx[1] = MESH_INDEX (0, y + 1); idx[2] = MESH_INDEX (0, y + 2); back_idx[0] = MESH_INDEX (priv->x_tiles, y + 1); back_idx[1] = MESH_INDEX (priv->x_tiles, y + 1); back_idx[2] = MESH_INDEX (priv->x_tiles, y + 2); } idx += 3; back_idx += 3; direction = !direction; } #undef MESH_INDEX priv->indices = cogl_vertex_buffer_indices_new (COGL_INDICES_TYPE_UNSIGNED_SHORT, static_indices, priv->n_indices); priv->back_indices = cogl_vertex_buffer_indices_new (COGL_INDICES_TYPE_UNSIGNED_SHORT, static_back_indices, priv->n_indices); g_free (static_indices); g_free (static_back_indices); n_tiles = (priv->x_tiles + 1) * (priv->y_tiles + 1); priv->vertices = g_new (CoglTextureVertex, n_tiles); priv->vbo = cogl_vertex_buffer_new (n_tiles); priv->is_dirty = TRUE; } static inline void clutter_deform_effect_free_back_material (ClutterDeformEffect *self) { ClutterDeformEffectPrivate *priv = self->priv; if (priv->back_material != NULL) { cogl_handle_unref (priv->back_material); priv->back_material = COGL_INVALID_HANDLE; } } static void clutter_deform_effect_finalize (GObject *gobject) { ClutterDeformEffect *self = CLUTTER_DEFORM_EFFECT (gobject); clutter_deform_effect_free_arrays (self); clutter_deform_effect_free_back_material (self); G_OBJECT_CLASS (clutter_deform_effect_parent_class)->finalize (gobject); } static void clutter_deform_effect_set_property (GObject *gobject, guint prop_id, const GValue *value, GParamSpec *pspec) { ClutterDeformEffect *self = CLUTTER_DEFORM_EFFECT (gobject); switch (prop_id) { case PROP_X_TILES: clutter_deform_effect_set_n_tiles (self, g_value_get_uint (value), self->priv->y_tiles); break; case PROP_Y_TILES: clutter_deform_effect_set_n_tiles (self, self->priv->x_tiles, g_value_get_uint (value)); break; case PROP_BACK_MATERIAL: clutter_deform_effect_set_back_material (self, g_value_get_boxed (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); break; } } static void clutter_deform_effect_get_property (GObject *gobject, guint prop_id, GValue *value, GParamSpec *pspec) { ClutterDeformEffectPrivate *priv = CLUTTER_DEFORM_EFFECT (gobject)->priv; switch (prop_id) { case PROP_X_TILES: g_value_set_uint (value, priv->x_tiles); break; case PROP_Y_TILES: g_value_set_uint (value, priv->y_tiles); break; case PROP_BACK_MATERIAL: g_value_set_boxed (value, priv->back_material); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); break; } } static void clutter_deform_effect_class_init (ClutterDeformEffectClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); ClutterActorMetaClass *meta_class = CLUTTER_ACTOR_META_CLASS (klass); ClutterOffscreenEffectClass *offscreen_class = CLUTTER_OFFSCREEN_EFFECT_CLASS (klass); g_type_class_add_private (klass, sizeof (ClutterDeformEffectPrivate)); klass->deform_vertex = clutter_deform_effect_real_deform_vertex; /** * ClutterDeformEffect:x-tiles: * * The number of horizontal tiles. The bigger the number, the * smaller the tiles * * Since: 1.4 */ obj_props[PROP_X_TILES] = g_param_spec_uint ("x-tiles", P_("Horizontal Tiles"), P_("The number of horizontal tiles"), 1, G_MAXUINT, DEFAULT_N_TILES, CLUTTER_PARAM_READWRITE); /** * ClutterDeformEffect:y-tiles: * * The number of vertical tiles. The bigger the number, the * smaller the tiles * * Since: 1.4 */ obj_props[PROP_Y_TILES] = g_param_spec_uint ("y-tiles", P_("Vertical Tiles"), P_("The number of vertical tiles"), 1, G_MAXUINT, DEFAULT_N_TILES, CLUTTER_PARAM_READWRITE); /** * ClutterDeformEffect:back-material: * * A material to be used when painting the back of the actor * to which this effect has been applied * * By default, no material will be used * * Since: 1.4 */ obj_props[PROP_BACK_MATERIAL] = g_param_spec_boxed ("back-material", P_("Back Material"), P_("The material to be used when painting the back of the actor"), COGL_TYPE_HANDLE, CLUTTER_PARAM_READWRITE); gobject_class->finalize = clutter_deform_effect_finalize; gobject_class->set_property = clutter_deform_effect_set_property; gobject_class->get_property = clutter_deform_effect_get_property; _clutter_object_class_install_properties (gobject_class, PROP_LAST, obj_props); meta_class->set_actor = clutter_deform_effect_set_actor; offscreen_class->paint_target = clutter_deform_effect_paint_target; } static void clutter_deform_effect_init (ClutterDeformEffect *self) { self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, CLUTTER_TYPE_DEFORM_EFFECT, ClutterDeformEffectPrivate); self->priv->x_tiles = self->priv->y_tiles = DEFAULT_N_TILES; self->priv->back_material = COGL_INVALID_HANDLE; clutter_deform_effect_init_arrays (self); } /** * clutter_deform_effect_set_back_material: * @effect: a #ClutterDeformEffect * @material: (allow-none): a handle to a Cogl material * * Sets the material that should be used when drawing the back face * of the actor during a deformation * * The #ClutterDeformEffect will take a reference on the material's * handle * * Since: 1.4 */ void clutter_deform_effect_set_back_material (ClutterDeformEffect *effect, CoglHandle material) { ClutterDeformEffectPrivate *priv; g_return_if_fail (CLUTTER_IS_DEFORM_EFFECT (effect)); g_return_if_fail (material == COGL_INVALID_HANDLE || cogl_is_material (material)); priv = effect->priv; clutter_deform_effect_free_back_material (effect); priv->back_material = material; if (priv->back_material != COGL_INVALID_HANDLE) cogl_handle_ref (priv->back_material); clutter_deform_effect_invalidate (effect); } /** * clutter_deform_effect_get_back_material: * @effect: a #ClutterDeformEffect * * Retrieves the handle to the back face material used by @effect * * Return value: (transfer none): a handle for the material, or %NULL. * The returned material is owned by the #ClutterDeformEffect and it * should not be freed directly * * Since: 1.4 */ CoglHandle clutter_deform_effect_get_back_material (ClutterDeformEffect *effect) { g_return_val_if_fail (CLUTTER_IS_DEFORM_EFFECT (effect), NULL); return effect->priv->back_material; } /** * clutter_deform_effect_set_n_tiles: * @effect: a #ClutterDeformEffect * @x_tiles: number of horizontal tiles * @y_tiles: number of vertical tiles * * Sets the number of horizontal and vertical tiles to be used * when applying the effect * * More tiles allow a finer grained deformation at the expenses * of computation * * Since: 1.4 */ void clutter_deform_effect_set_n_tiles (ClutterDeformEffect *effect, guint x_tiles, guint y_tiles) { ClutterDeformEffectPrivate *priv; gboolean tiles_changed = FALSE; g_return_if_fail (CLUTTER_IS_DEFORM_EFFECT (effect)); g_return_if_fail (x_tiles > 0 && y_tiles > 0); priv = effect->priv; g_object_freeze_notify (G_OBJECT (effect)); if (priv->x_tiles != x_tiles) { priv->x_tiles = x_tiles; _clutter_notify_by_pspec (G_OBJECT (effect), obj_props[PROP_X_TILES]); tiles_changed = TRUE; } if (priv->y_tiles != y_tiles) { priv->y_tiles = y_tiles; _clutter_notify_by_pspec (G_OBJECT (effect), obj_props[PROP_Y_TILES]); tiles_changed = TRUE; } if (tiles_changed) { clutter_deform_effect_init_arrays (effect); clutter_deform_effect_invalidate (effect); } g_object_thaw_notify (G_OBJECT (effect)); } /** * clutter_deform_effect_get_n_tiles: * @effect: a #ClutterDeformEffect * @x_tiles: (out): return location for the number of horizontal tiles, * or %NULL * @y_tiles: (out): return location for the number of vertical tiles, * or %NULL * * Retrieves the number of horizontal and vertical tiles used to sub-divide * the actor's geometry during the effect * * Since: 1.4 */ void clutter_deform_effect_get_n_tiles (ClutterDeformEffect *effect, guint *x_tiles, guint *y_tiles) { g_return_if_fail (CLUTTER_IS_DEFORM_EFFECT (effect)); if (x_tiles != NULL) *x_tiles = effect->priv->x_tiles; if (y_tiles != NULL) *y_tiles = effect->priv->y_tiles; } /** * clutter_deform_effect_invalidate: * @effect: a #ClutterDeformEffect * * Invalidates the @effect's vertices and, if it is associated * to an actor, it will queue a redraw * * Since: 1.4 */ void clutter_deform_effect_invalidate (ClutterDeformEffect *effect) { ClutterActor *actor; g_return_if_fail (CLUTTER_IS_DEFORM_EFFECT (effect)); if (effect->priv->is_dirty) return; effect->priv->is_dirty = TRUE; actor = clutter_actor_meta_get_actor (CLUTTER_ACTOR_META (effect)); if (actor != NULL) clutter_actor_queue_redraw (actor); }