[texture] Support picking textures according to their alpha channel

This adds a boolean "pick-with-alpha" property to ClutterTexture and when
true, it will use the textures alpha channel to define the actors shape when
picking.

Users should be aware that it's a bit more expensive to pick textures like
this (so probably best not to blindly enable it on *all* your textures)
since it implies rasterizing the texture during picking whereas we would
otherwise just send a solid filled quad to the GPU.  It will also interrupt
the internal batching of geometry for pick renders which can otherwise often
be done in a single draw call.
This commit is contained in:
Robert Bragg 2009-09-25 19:14:40 +01:00
parent 8ded6663fb
commit 589c8d9579
5 changed files with 318 additions and 32 deletions

View File

@ -79,6 +79,8 @@ struct _ClutterTexturePrivate
ClutterActor *fbo_source; ClutterActor *fbo_source;
CoglHandle fbo_handle; CoglHandle fbo_handle;
CoglHandle pick_material;
ClutterTextureAsyncData *async_data; ClutterTextureAsyncData *async_data;
guint no_slice : 1; guint no_slice : 1;
@ -89,6 +91,8 @@ struct _ClutterTexturePrivate
guint load_size_async : 1; guint load_size_async : 1;
guint load_data_async : 1; guint load_data_async : 1;
guint load_async_set : 1; /* used to make load_async possible */ guint load_async_set : 1; /* used to make load_async possible */
guint pick_with_alpha : 1;
guint pick_with_alpha_supported : 1;
}; };
struct _ClutterTextureAsyncData struct _ClutterTextureAsyncData
@ -137,6 +141,7 @@ enum
PROP_KEEP_ASPECT_RATIO, PROP_KEEP_ASPECT_RATIO,
PROP_LOAD_ASYNC, PROP_LOAD_ASYNC,
PROP_LOAD_DATA_ASYNC, PROP_LOAD_DATA_ASYNC,
PROP_PICK_WITH_ALPHA
}; };
enum enum
@ -516,49 +521,131 @@ update_fbo (ClutterActor *self)
clutter_shader_set_is_enabled (shader, TRUE); clutter_shader_set_is_enabled (shader, TRUE);
} }
static void
gen_texcoords_and_draw_cogl_rectangle (ClutterActor *self)
{
ClutterTexture *texture = CLUTTER_TEXTURE (self);
ClutterTexturePrivate *priv = texture->priv;
ClutterActorBox box;
float t_w, t_h;
clutter_actor_get_allocation_box (self, &box);
if (priv->repeat_x && priv->image_width > 0)
t_w = (box.x2 - box.x1) / (float) priv->image_width;
else
t_w = 1.0;
if (priv->repeat_y && priv->image_height > 0)
t_h = (box.y2 - box.y1) / (float) priv->image_height;
else
t_h = 1.0;
cogl_rectangle_with_texture_coords (0, 0,
box.x2 - box.x1,
box.y2 - box.y1,
0, 0, t_w, t_h);
}
static CoglHandle
create_pick_material (ClutterActor *self)
{
CoglHandle pick_material = cogl_material_new ();
GError *error = NULL;
if (!cogl_material_set_layer_combine (pick_material, 0,
"RGBA = "
" MODULATE (CONSTANT, TEXTURE[A])",
&error))
{
static gboolean seen_warning = FALSE;
if (!seen_warning)
g_warning ("Error setting up texture combine for shaped "
"texture picking: %s", error->message);
seen_warning = TRUE;
g_error_free (error);
cogl_handle_unref (pick_material);
return COGL_INVALID_HANDLE;
}
cogl_material_set_blend (pick_material,
"RGBA = ADD (SRC_COLOR[RGBA], 0)",
NULL);
cogl_material_set_alpha_test_function (pick_material,
COGL_MATERIAL_ALPHA_FUNC_EQUAL,
1.0);
return pick_material;
}
static void
clutter_texture_pick (ClutterActor *self,
const ClutterColor *color)
{
ClutterTexture *texture = CLUTTER_TEXTURE (self);
ClutterTexturePrivate *priv = texture->priv;
if (!clutter_actor_should_pick_paint (self))
return;
if (G_LIKELY (priv->pick_with_alpha_supported) && priv->pick_with_alpha)
{
CoglColor pick_color;
if (priv->pick_material == COGL_INVALID_HANDLE)
priv->pick_material = create_pick_material (self);
if (priv->pick_material == COGL_INVALID_HANDLE)
{
priv->pick_with_alpha_supported = FALSE;
CLUTTER_ACTOR_CLASS (clutter_texture_parent_class)->pick (self,
color);
return;
}
if (priv->fbo_handle != COGL_INVALID_HANDLE)
update_fbo (self);
cogl_color_set_from_4ub (&pick_color,
color->red,
color->green,
color->blue,
0xff);
cogl_material_set_layer_combine_constant (priv->pick_material,
0, &pick_color);
cogl_material_set_layer (priv->pick_material, 0,
clutter_texture_get_cogl_texture (texture));
cogl_set_source (priv->pick_material);
gen_texcoords_and_draw_cogl_rectangle (self);
}
else
CLUTTER_ACTOR_CLASS (clutter_texture_parent_class)->pick (self, color);
}
static void static void
clutter_texture_paint (ClutterActor *self) clutter_texture_paint (ClutterActor *self)
{ {
ClutterTexture *texture = CLUTTER_TEXTURE (self); ClutterTexture *texture = CLUTTER_TEXTURE (self);
ClutterTexturePrivate *priv = texture->priv; ClutterTexturePrivate *priv = texture->priv;
ClutterActorBox box = { 0, };
gfloat t_w, t_h;
guint8 paint_opacity = clutter_actor_get_paint_opacity (self); guint8 paint_opacity = clutter_actor_get_paint_opacity (self);
if (priv->fbo_handle != COGL_INVALID_HANDLE)
update_fbo (self);
CLUTTER_NOTE (PAINT, CLUTTER_NOTE (PAINT,
"painting texture '%s'", "painting texture '%s'",
clutter_actor_get_name (self) ? clutter_actor_get_name (self) clutter_actor_get_name (self) ? clutter_actor_get_name (self)
: "unknown"); : "unknown");
if (priv->fbo_handle != COGL_INVALID_HANDLE)
update_fbo (self);
cogl_material_set_color4ub (priv->material, cogl_material_set_color4ub (priv->material,
paint_opacity, paint_opacity, paint_opacity, paint_opacity); paint_opacity,
paint_opacity,
clutter_actor_get_allocation_box (self, &box); paint_opacity,
paint_opacity);
CLUTTER_NOTE (PAINT, "paint to x1: %f, y1: %f x2: %f, y2: %f "
"opacity: %i",
box.x1, box.y1, box.x2, box.y2,
clutter_actor_get_opacity (self));
if (priv->repeat_x && priv->image_width > 0)
t_w = (box.x2 - box.x1) / (gfloat) priv->image_width;
else
t_w = 1.0;
if (priv->repeat_y && priv->image_height > 0)
t_h = (box.y2 - box.y1) / (gfloat) priv->image_height;
else
t_h = 1.0;
/* Paint will have translated us */
cogl_set_source (priv->material); cogl_set_source (priv->material);
cogl_rectangle_with_texture_coords (0, 0,
box.x2 - box.x1, gen_texcoords_and_draw_cogl_rectangle (self);
box.y2 - box.y1,
0, 0, t_w, t_h);
} }
static void static void
@ -658,6 +745,12 @@ clutter_texture_finalize (GObject *object)
priv->material = COGL_INVALID_HANDLE; priv->material = COGL_INVALID_HANDLE;
} }
if (priv->pick_material != COGL_INVALID_HANDLE)
{
cogl_handle_unref (priv->pick_material);
priv->pick_material = COGL_INVALID_HANDLE;
}
G_OBJECT_CLASS (clutter_texture_parent_class)->finalize (object); G_OBJECT_CLASS (clutter_texture_parent_class)->finalize (object);
} }
@ -736,6 +829,11 @@ clutter_texture_set_property (GObject *object,
clutter_texture_set_load_async (texture, g_value_get_boolean (value)); clutter_texture_set_load_async (texture, g_value_get_boolean (value));
break; break;
case PROP_PICK_WITH_ALPHA:
clutter_texture_set_pick_with_alpha (texture,
g_value_get_boolean (value));
break;
default: default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break; break;
@ -796,6 +894,10 @@ clutter_texture_get_property (GObject *object,
g_value_set_boolean (value, priv->keep_aspect_ratio); g_value_set_boolean (value, priv->keep_aspect_ratio);
break; break;
case PROP_PICK_WITH_ALPHA:
g_value_set_boolean (value, priv->pick_with_alpha);
break;
default: default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break; break;
@ -811,6 +913,7 @@ clutter_texture_class_init (ClutterTextureClass *klass)
g_type_class_add_private (klass, sizeof (ClutterTexturePrivate)); g_type_class_add_private (klass, sizeof (ClutterTexturePrivate));
actor_class->paint = clutter_texture_paint; actor_class->paint = clutter_texture_paint;
actor_class->pick = clutter_texture_pick;
actor_class->realize = clutter_texture_realize; actor_class->realize = clutter_texture_realize;
actor_class->unrealize = clutter_texture_unrealize; actor_class->unrealize = clutter_texture_unrealize;
@ -969,7 +1072,30 @@ clutter_texture_class_init (ClutterTextureClass *klass)
FALSE, FALSE,
CLUTTER_PARAM_WRITABLE)); CLUTTER_PARAM_WRITABLE));
/**
* ClutterTexture::pick-with-alpha:
*
* Determines whether a #ClutterTexture should have it's shape defined
* by its alpha channel when picking.
*
* Be aware that this is a bit more costly than the default picking
* due to the texture lookup, extra test against the alpha value and
* the fact that it will also interrupt the batching of geometry
* done internally.
*
* Also there is currently no control over the threshold used to
* determine what value of alpha is considered pickable, and so
* only fully opaque parts of the texture will react to picking.
*
* Since: 1.4
*/
g_object_class_install_property
(gobject_class, PROP_PICK_WITH_ALPHA,
g_param_spec_boolean ("pick-with-alpha",
"Pick With Alpha Channel",
"Shape actor with alpha channel when picking",
FALSE,
CLUTTER_PARAM_READWRITE));
/** /**
* ClutterTexture::size-change: * ClutterTexture::size-change:
@ -1096,7 +1222,10 @@ clutter_texture_init (ClutterTexture *self)
priv->sync_actor_size = TRUE; priv->sync_actor_size = TRUE;
priv->material = cogl_material_new (); priv->material = cogl_material_new ();
priv->fbo_handle = COGL_INVALID_HANDLE; priv->fbo_handle = COGL_INVALID_HANDLE;
priv->pick_material = COGL_INVALID_HANDLE;
priv->keep_aspect_ratio = FALSE; priv->keep_aspect_ratio = FALSE;
priv->pick_with_alpha = FALSE;
priv->pick_with_alpha_supported = TRUE;
} }
/** /**
@ -2690,3 +2819,65 @@ clutter_texture_get_load_data_async (ClutterTexture *texture)
return texture->priv->load_async_set && return texture->priv->load_async_set &&
texture->priv->load_data_async; texture->priv->load_data_async;
} }
/**
* clutter_texture_set_pick_with_alpha:
* @texture: a #ClutterTexture
*
* Sets whether @texture should have it's shape defined by the alpha
* channel when picking.
*
* Be aware that this is a bit more costly than the default picking
* due to the texture lookup, extra test against the alpha value and
* the fact that it will also interrupt the batching of geometry done
* internally.
*
* Also there is currently no control over the threshold used to
* determine what value of alpha is considered pickable, and so only
* fully opaque parts of the texture will react to picking.
*
* Since: 1.4
*/
void
clutter_texture_set_pick_with_alpha (ClutterTexture *texture,
gboolean pick_with_alpha)
{
ClutterTexturePrivate *priv;
g_return_if_fail (CLUTTER_IS_TEXTURE (texture));
priv = texture->priv;
if (priv->pick_with_alpha == pick_with_alpha)
return;
if (!pick_with_alpha && priv->pick_material != COGL_INVALID_HANDLE)
{
cogl_handle_unref (priv->pick_material);
priv->pick_material = COGL_INVALID_HANDLE;
}
/* NB: the pick material is created lazily when we first pick */
priv->pick_with_alpha = pick_with_alpha;
}
/**
* clutter_texture_get_pick_with_alpha:
* @texture: a #ClutterTexture
*
* Retrieves the value set by clutter_texture_set_load_data_async()
*
* Return value: %TRUE if the #ClutterTexture should define its shape
* using the alpha channel when picking.
*
* Since: 1.4
*/
gboolean
clutter_texture_get_pick_with_alpha (ClutterTexture *texture)
{
g_return_val_if_fail (CLUTTER_IS_TEXTURE (texture), FALSE);
return texture->priv->pick_with_alpha ? TRUE : FALSE;
}

View File

@ -229,6 +229,10 @@ void clutter_texture_set_load_data_async (ClutterTexture
gboolean load_async); gboolean load_async);
gboolean clutter_texture_get_load_data_async (ClutterTexture *texture); gboolean clutter_texture_get_load_data_async (ClutterTexture *texture);
void clutter_texture_set_pick_with_alpha (ClutterTexture *texture,
gboolean pick_with_alpha);
gboolean clutter_texture_get_pick_with_alpha (ClutterTexture *texture);
G_END_DECLS G_END_DECLS
#endif /* __CLUTTER_TEXTURE_H__ */ #endif /* __CLUTTER_TEXTURE_H__ */

View File

@ -52,6 +52,7 @@ test_conformance_SOURCES = \
test-behaviours.c \ test-behaviours.c \
test-animator.c \ test-animator.c \
test-state.c \ test-state.c \
test-clutter-texture.c \
$(NULL) $(NULL)
# For convenience, this provides a way to easily run individual unit tests: # For convenience, this provides a way to easily run individual unit tests:

View File

@ -0,0 +1,89 @@
#include <glib.h>
#include <clutter/clutter.h>
#include <string.h>
#include "test-conform-common.h"
static CoglHandle
make_texture (void)
{
guint32 *data = g_malloc (100 * 100 * 4);
int x;
int y;
for (y = 0; y < 100; y ++)
for (x = 0; x < 100; x++)
{
if (x < 50 && y < 50)
data[y * 100 + x] = 0xff00ff00;
else
data[y * 100 + x] = 0xff00ffff;
}
return cogl_texture_new_from_data (100,
100,
COGL_TEXTURE_NONE,
COGL_PIXEL_FORMAT_ARGB_8888,
COGL_PIXEL_FORMAT_ARGB_8888,
400,
(guchar *)data);
}
void
test_texture_pick_with_alpha (TestConformSimpleFixture *fixture,
gconstpointer data)
{
ClutterTexture *tex = CLUTTER_TEXTURE (clutter_texture_new ());
ClutterStage *stage = CLUTTER_STAGE (clutter_stage_get_default ());
ClutterActor *actor;
clutter_texture_set_cogl_texture (tex, make_texture ());
clutter_container_add_actor (CLUTTER_CONTAINER (stage), CLUTTER_ACTOR (tex));
clutter_actor_show (CLUTTER_ACTOR (stage));
if (g_test_verbose ())
{
g_print ("\nstage = %p\n", stage);
g_print ("texture = %p\n\n", tex);
}
clutter_texture_set_pick_with_alpha (tex, TRUE);
if (g_test_verbose ())
g_print ("Testing with pick-with-alpha enabled:\n");
/* This should fall through and hit the stage: */
actor = clutter_stage_get_actor_at_pos (stage, CLUTTER_PICK_ALL, 10, 10);
if (g_test_verbose ())
g_print ("actor @ (10, 10) = %p\n", actor);
g_assert (actor == CLUTTER_ACTOR (stage));
/* The rest should hit the texture */
actor = clutter_stage_get_actor_at_pos (stage, CLUTTER_PICK_ALL, 90, 10);
if (g_test_verbose ())
g_print ("actor @ (90, 10) = %p\n", actor);
g_assert (actor == CLUTTER_ACTOR (tex));
actor = clutter_stage_get_actor_at_pos (stage, CLUTTER_PICK_ALL, 90, 90);
if (g_test_verbose ())
g_print ("actor @ (90, 90) = %p\n", actor);
g_assert (actor == CLUTTER_ACTOR (tex));
actor = clutter_stage_get_actor_at_pos (stage, CLUTTER_PICK_ALL, 10, 90);
if (g_test_verbose ())
g_print ("actor @ (10, 90) = %p\n", actor);
g_assert (actor == CLUTTER_ACTOR (tex));
clutter_texture_set_pick_with_alpha (tex, FALSE);
if (g_test_verbose ())
g_print ("Testing with pick-with-alpha disabled:\n");
actor = clutter_stage_get_actor_at_pos (stage, CLUTTER_PICK_ALL, 10, 10);
if (g_test_verbose ())
g_print ("actor @ (10, 10) = %p\n", actor);
g_assert (actor == CLUTTER_ACTOR (tex));
clutter_actor_destroy (CLUTTER_ACTOR (tex));
if (g_test_verbose ())
g_print ("OK\n");
}

View File

@ -144,6 +144,7 @@ main (int argc, char **argv)
TEST_CONFORM_SIMPLE ("/opacity", test_rectangle_opacity); TEST_CONFORM_SIMPLE ("/opacity", test_rectangle_opacity);
TEST_CONFORM_SIMPLE ("/opacity", test_paint_opacity); TEST_CONFORM_SIMPLE ("/opacity", test_paint_opacity);
TEST_CONFORM_SIMPLE ("/texture", test_texture_pick_with_alpha);
TEST_CONFORM_SIMPLE ("/texture", test_texture_fbo); TEST_CONFORM_SIMPLE ("/texture", test_texture_fbo);
TEST_CONFORM_SIMPLE ("/texture/cairo", test_clutter_cairo_texture); TEST_CONFORM_SIMPLE ("/texture/cairo", test_clutter_cairo_texture);