[texture] Support picking textures according to their alpha channel
authorRobert Bragg <robert@linux.intel.com>
Fri, 25 Sep 2009 18:14:40 +0000 (19:14 +0100)
committerRobert Bragg <robert@linux.intel.com>
Fri, 28 May 2010 15:07:17 +0000 (16:07 +0100)
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.

clutter/clutter-texture.c
clutter/clutter-texture.h
tests/conform/Makefile.am
tests/conform/test-clutter-texture.c [new file with mode: 0644]
tests/conform/test-conform-main.c

index 72709b3..d52cba2 100644 (file)
@@ -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
@@ -517,50 +522,132 @@ update_fbo (ClutterActor *self)
 }
 
 static void
-clutter_texture_paint (ClutterActor *self)
+gen_texcoords_and_draw_cogl_rectangle (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);
-
-  CLUTTER_NOTE (PAINT,
-                "painting texture '%s'",
-               clutter_actor_get_name (self) ? clutter_actor_get_name (self)
-                                              : "unknown");
-
-  cogl_material_set_color4ub (priv->material,
-                             paint_opacity, paint_opacity, paint_opacity, paint_opacity);
+  ClutterActorBox box;
+  float t_w, t_h;
 
   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;
+    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) / (gfloat) priv->image_height;
+    t_h = (box.y2 - box.y1) / (float) priv->image_height;
   else
     t_h = 1.0;
 
-  /* Paint will have translated us */
-  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);
 }
 
+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;
+  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);
+  cogl_set_source (priv->material);
+
+  gen_texcoords_and_draw_cogl_rectangle (self);
+}
+
 static void
 clutter_texture_async_data_free (ClutterTextureAsyncData *data)
 {
@@ -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;
+}
+
index 40f84e8..6e17af3 100644 (file)
@@ -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__ */
index 59ddea7..28f6074 100644 (file)
@@ -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:
diff --git a/tests/conform/test-clutter-texture.c b/tests/conform/test-clutter-texture.c
new file mode 100644 (file)
index 0000000..df0162c
--- /dev/null
@@ -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");
+}
+
index 939220d..9a10a4c 100644 (file)
@@ -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);