mutter/clutter/clutter-deform-effect.c
2010-10-18 11:26:45 +01:00

745 lines
22 KiB
C

/*
* 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 <http://www.gnu.org/licenses/>.
*
* Author:
* Emmanuele Bassi <ebassi@linux.intel.com>
*
* Based on the MxDeformTexture class, written by:
* Chris Lord <chris@linux.intel.com>
*/
/**
* 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
* geomtry to the GPU.
*
* <refsect2>
* <title>Implementing ClutterDeformEffect</title>
* <para>Sub-classes of #ClutterDeformEffect should override the
* <function>deform_vertex()</function> 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.</para>
* </refsect2>
*
* #ClutterDeformEffect is available since Clutter 1.4
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "clutter-deform-effect.h"
#include <cogl/cogl.h>
#include "clutter-debug.h"
#include "clutter-enum-types.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);
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);
}