mirror of
https://github.com/brl/mutter.git
synced 2024-12-28 05:42:14 +00:00
[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:
parent
8ded6663fb
commit
589c8d9579
@ -79,6 +79,8 @@ struct _ClutterTexturePrivate
|
||||
ClutterActor *fbo_source;
|
||||
CoglHandle fbo_handle;
|
||||
|
||||
CoglHandle pick_material;
|
||||
|
||||
ClutterTextureAsyncData *async_data;
|
||||
|
||||
guint no_slice : 1;
|
||||
@ -89,6 +91,8 @@ struct _ClutterTexturePrivate
|
||||
guint load_size_async : 1;
|
||||
guint load_data_async : 1;
|
||||
guint load_async_set : 1; /* used to make load_async possible */
|
||||
guint pick_with_alpha : 1;
|
||||
guint pick_with_alpha_supported : 1;
|
||||
};
|
||||
|
||||
struct _ClutterTextureAsyncData
|
||||
@ -114,7 +118,7 @@ struct _ClutterTextureAsyncData
|
||||
/* Set when the texture is queued for GPU upload, used to determine
|
||||
* what to do with the texture data when load_idle is zero.
|
||||
*/
|
||||
gboolean upload_queued;
|
||||
gboolean upload_queued;
|
||||
|
||||
gchar *load_filename;
|
||||
CoglHandle load_bitmap;
|
||||
@ -137,6 +141,7 @@ enum
|
||||
PROP_KEEP_ASPECT_RATIO,
|
||||
PROP_LOAD_ASYNC,
|
||||
PROP_LOAD_DATA_ASYNC,
|
||||
PROP_PICK_WITH_ALPHA
|
||||
};
|
||||
|
||||
enum
|
||||
@ -516,49 +521,131 @@ update_fbo (ClutterActor *self)
|
||||
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
|
||||
clutter_texture_paint (ClutterActor *self)
|
||||
{
|
||||
ClutterTexture *texture = CLUTTER_TEXTURE (self);
|
||||
ClutterTexturePrivate *priv = texture->priv;
|
||||
ClutterActorBox box = { 0, };
|
||||
gfloat t_w, t_h;
|
||||
guint8 paint_opacity = clutter_actor_get_paint_opacity (self);
|
||||
|
||||
if (priv->fbo_handle != COGL_INVALID_HANDLE)
|
||||
update_fbo (self);
|
||||
guint8 paint_opacity = clutter_actor_get_paint_opacity (self);
|
||||
|
||||
CLUTTER_NOTE (PAINT,
|
||||
"painting texture '%s'",
|
||||
clutter_actor_get_name (self) ? clutter_actor_get_name (self)
|
||||
: "unknown");
|
||||
|
||||
if (priv->fbo_handle != COGL_INVALID_HANDLE)
|
||||
update_fbo (self);
|
||||
|
||||
cogl_material_set_color4ub (priv->material,
|
||||
paint_opacity, paint_opacity, paint_opacity, paint_opacity);
|
||||
|
||||
clutter_actor_get_allocation_box (self, &box);
|
||||
|
||||
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 */
|
||||
paint_opacity,
|
||||
paint_opacity,
|
||||
paint_opacity,
|
||||
paint_opacity);
|
||||
cogl_set_source (priv->material);
|
||||
cogl_rectangle_with_texture_coords (0, 0,
|
||||
box.x2 - box.x1,
|
||||
box.y2 - box.y1,
|
||||
0, 0, t_w, t_h);
|
||||
|
||||
gen_texcoords_and_draw_cogl_rectangle (self);
|
||||
}
|
||||
|
||||
static void
|
||||
@ -658,6 +745,12 @@ clutter_texture_finalize (GObject *object)
|
||||
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);
|
||||
}
|
||||
|
||||
@ -736,6 +829,11 @@ clutter_texture_set_property (GObject *object,
|
||||
clutter_texture_set_load_async (texture, g_value_get_boolean (value));
|
||||
break;
|
||||
|
||||
case PROP_PICK_WITH_ALPHA:
|
||||
clutter_texture_set_pick_with_alpha (texture,
|
||||
g_value_get_boolean (value));
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
@ -796,6 +894,10 @@ clutter_texture_get_property (GObject *object,
|
||||
g_value_set_boolean (value, priv->keep_aspect_ratio);
|
||||
break;
|
||||
|
||||
case PROP_PICK_WITH_ALPHA:
|
||||
g_value_set_boolean (value, priv->pick_with_alpha);
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
@ -811,6 +913,7 @@ clutter_texture_class_init (ClutterTextureClass *klass)
|
||||
g_type_class_add_private (klass, sizeof (ClutterTexturePrivate));
|
||||
|
||||
actor_class->paint = clutter_texture_paint;
|
||||
actor_class->pick = clutter_texture_pick;
|
||||
actor_class->realize = clutter_texture_realize;
|
||||
actor_class->unrealize = clutter_texture_unrealize;
|
||||
|
||||
@ -969,7 +1072,30 @@ clutter_texture_class_init (ClutterTextureClass *klass)
|
||||
FALSE,
|
||||
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:
|
||||
@ -1096,7 +1222,10 @@ clutter_texture_init (ClutterTexture *self)
|
||||
priv->sync_actor_size = TRUE;
|
||||
priv->material = cogl_material_new ();
|
||||
priv->fbo_handle = COGL_INVALID_HANDLE;
|
||||
priv->pick_material = COGL_INVALID_HANDLE;
|
||||
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 &&
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -229,6 +229,10 @@ void clutter_texture_set_load_data_async (ClutterTexture
|
||||
gboolean load_async);
|
||||
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
|
||||
|
||||
#endif /* __CLUTTER_TEXTURE_H__ */
|
||||
|
@ -52,6 +52,7 @@ test_conformance_SOURCES = \
|
||||
test-behaviours.c \
|
||||
test-animator.c \
|
||||
test-state.c \
|
||||
test-clutter-texture.c \
|
||||
$(NULL)
|
||||
|
||||
# For convenience, this provides a way to easily run individual unit tests:
|
||||
|
89
tests/conform/test-clutter-texture.c
Normal file
89
tests/conform/test-clutter-texture.c
Normal 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");
|
||||
}
|
||||
|
@ -144,6 +144,7 @@ main (int argc, char **argv)
|
||||
TEST_CONFORM_SIMPLE ("/opacity", test_rectangle_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/cairo", test_clutter_cairo_texture);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user