diff --git a/src/Makefile.am b/src/Makefile.am index b6ff5a17b..bfc2a7fa0 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -105,6 +105,8 @@ metacity_SOURCES= \ if WITH_CLUTTER metacity_SOURCES += compositor/compositor-clutter.c \ compositor/compositor-clutter.h \ + compositor/shaped-texture.c \ + compositor/shaped-texture.h \ include/compositor-clutter-plugin.h \ compositor/compositor-clutter-plugin-manager.c \ compositor/compositor-clutter-plugin-manager.h diff --git a/src/compositor/shaped-texture.c b/src/compositor/shaped-texture.c new file mode 100644 index 000000000..ecce94005 --- /dev/null +++ b/src/compositor/shaped-texture.c @@ -0,0 +1,495 @@ +/* + * shaped texture + * + * An actor to draw a texture clipped to a list of rectangles + * + * Authored By Neil Roberts + * + * Copyright (C) 2008 Intel Corporation + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#include +#include +#include + +#include "shaped-texture.h" + +static void meta_shaped_texture_dispose (GObject *object); +static void meta_shaped_texture_finalize (GObject *object); + +static void meta_shaped_texture_paint (ClutterActor *actor); +static void meta_shaped_texture_pick (ClutterActor *actor, + const ClutterColor *color); + +static void meta_shaped_texture_dirty_mask (MetaShapedTexture *stex); + +G_DEFINE_TYPE (MetaShapedTexture, meta_shaped_texture, + CLUTTER_TYPE_TEXTURE); + +#define META_SHAPED_TEXTURE_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((obj), META_TYPE_SHAPED_TEXTURE, \ + MetaShapedTexturePrivate)) + +enum TstMultiTexSupport + { + TST_MULTI_TEX_SUPPORT_UNKNOWN = 0, + TST_MULTI_TEX_SUPPORT_YES, + TST_MULTI_TEX_SUPPORT_NO + }; + +static enum TstMultiTexSupport +tst_multi_tex_support = TST_MULTI_TEX_SUPPORT_UNKNOWN; + +typedef void (* TstActiveTextureFunc) (GLenum texture); +typedef void (* TstClientActiveTextureFunc) (GLenum texture); + +static TstActiveTextureFunc tst_active_texture; +static TstClientActiveTextureFunc tst_client_active_texture; + +struct _MetaShapedTexturePrivate +{ + CoglHandle mask_texture; + + guint mask_width, mask_height; + GLint mask_gl_width, mask_gl_height; + GLfloat mask_tex_coords[8]; + + GArray *rectangles; +}; + +static void +meta_shaped_texture_class_init (MetaShapedTextureClass *klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + ClutterActorClass *actor_class = (ClutterActorClass *) klass; + + gobject_class->dispose = meta_shaped_texture_dispose; + gobject_class->finalize = meta_shaped_texture_finalize; + + actor_class->paint = meta_shaped_texture_paint; + actor_class->pick = meta_shaped_texture_pick; + + g_type_class_add_private (klass, sizeof (MetaShapedTexturePrivate)); +} + +static void +meta_shaped_texture_init (MetaShapedTexture *self) +{ + MetaShapedTexturePrivate *priv; + + priv = self->priv = META_SHAPED_TEXTURE_GET_PRIVATE (self); + + priv->rectangles = g_array_new (FALSE, FALSE, sizeof (ClutterGeometry)); + + priv->mask_texture = COGL_INVALID_HANDLE; +} + +static void +meta_shaped_texture_dispose (GObject *object) +{ + MetaShapedTexture *self = (MetaShapedTexture *) object; + + meta_shaped_texture_dirty_mask (self); + + G_OBJECT_CLASS (meta_shaped_texture_parent_class)->dispose (object); +} + +static void +meta_shaped_texture_finalize (GObject *object) +{ + MetaShapedTexture *self = (MetaShapedTexture *) object; + MetaShapedTexturePrivate *priv = self->priv; + + g_array_free (priv->rectangles, TRUE); + + G_OBJECT_CLASS (meta_shaped_texture_parent_class)->finalize (object); +} + +static void +meta_shaped_texture_dirty_mask (MetaShapedTexture *stex) +{ + MetaShapedTexturePrivate *priv = stex->priv; + + if (priv->mask_texture != COGL_INVALID_HANDLE) + { + cogl_texture_unref (priv->mask_texture); + priv->mask_texture = COGL_INVALID_HANDLE; + } +} + +static gboolean +meta_shaped_texture_is_multi_tex_supported (void) +{ + const gchar *extensions; + GLint max_tex_units = 0; + + if (tst_multi_tex_support != TST_MULTI_TEX_SUPPORT_UNKNOWN) + return tst_multi_tex_support == TST_MULTI_TEX_SUPPORT_YES; + + extensions = (const gchar *) glGetString (GL_EXTENSIONS); + + tst_active_texture = (TstActiveTextureFunc) + cogl_get_proc_address ("glActiveTextureARB"); + tst_client_active_texture = (TstClientActiveTextureFunc) + cogl_get_proc_address ("glClientActiveTextureARB"); + + glGetIntegerv (GL_MAX_TEXTURE_UNITS, &max_tex_units); + + if (extensions + && cogl_check_extension ("GL_ARB_multitexture", extensions) + && tst_active_texture + && tst_client_active_texture + && max_tex_units > 1) + { + tst_multi_tex_support = TST_MULTI_TEX_SUPPORT_YES; + return TRUE; + } + else + { + g_warning ("multi texturing not supported"); + tst_multi_tex_support = TST_MULTI_TEX_SUPPORT_NO; + return FALSE; + } +} + +static void +meta_shaped_texture_set_coord_array (GLfloat x1, GLfloat y1, + GLfloat x2, GLfloat y2, + GLfloat *coords) +{ + coords[0] = x1; + coords[1] = y2; + coords[2] = x2; + coords[3] = y2; + coords[4] = x1; + coords[5] = y1; + coords[6] = x2; + coords[7] = y1; +} + +static void +meta_shaped_texture_ensure_mask (MetaShapedTexture *stex) +{ + MetaShapedTexturePrivate *priv = stex->priv; + CoglHandle paint_tex; + guint tex_width, tex_height; + GLuint mask_gl_tex; + + paint_tex = clutter_texture_get_cogl_texture (CLUTTER_TEXTURE (stex)); + + if (paint_tex == COGL_INVALID_HANDLE) + return; + + tex_width = cogl_texture_get_width (paint_tex); + tex_height = cogl_texture_get_height (paint_tex); + + /* If the mask texture we have was created for a different size then + recreate it */ + if (priv->mask_texture != COGL_INVALID_HANDLE + && (priv->mask_width != tex_width || priv->mask_height != tex_height)) + meta_shaped_texture_dirty_mask (stex); + + /* If we don't have a mask texture yet then create one */ + if (priv->mask_texture == COGL_INVALID_HANDLE) + { + guchar *mask_data; + const ClutterGeometry *rect; + + /* Create data for an empty image */ + mask_data = g_malloc0 (tex_width * tex_height); + + /* Cut out a hole for each rectangle */ + for (rect = (ClutterGeometry *) priv->rectangles->data + + priv->rectangles->len; + rect-- > (ClutterGeometry *) priv->rectangles->data;) + { + gint x1 = rect->x, x2 = x1 + rect->width; + gint y1 = rect->y, y2 = y1 + rect->height; + guchar *p; + + /* Clip the rectangle to the size of the texture */ + x1 = CLAMP (x1, 0, (gint) tex_width - 1); + x2 = CLAMP (x2, x1, (gint) tex_width); + y1 = CLAMP (y1, 0, (gint) tex_height - 1); + y2 = CLAMP (y2, y1, (gint) tex_height); + + /* Fill the rectangle */ + for (p = mask_data + y1 * tex_width + x1; + y1 < y2; + y1++, p += tex_width) + memset (p, 255, x2 - x1); + } + + priv->mask_texture = cogl_texture_new_from_data (tex_width, tex_height, + -1, FALSE, + COGL_PIXEL_FORMAT_A_8, + COGL_PIXEL_FORMAT_ANY, + tex_width, + mask_data); + + g_free (mask_data); + + priv->mask_width = tex_width; + priv->mask_height = tex_height; + + cogl_texture_get_gl_texture (priv->mask_texture, &mask_gl_tex, NULL); + + glBindTexture (GL_TEXTURE_2D, mask_gl_tex); + + glGetTexLevelParameteriv (GL_TEXTURE_2D, 0, + GL_TEXTURE_WIDTH, + &priv->mask_gl_width); + glGetTexLevelParameteriv (GL_TEXTURE_2D, 0, + GL_TEXTURE_HEIGHT, + &priv->mask_gl_height); + + if (priv->mask_gl_width == tex_width + && priv->mask_gl_height == tex_height) + meta_shaped_texture_set_coord_array (0.0f, 0.0f, 1.0f, 1.0f, + priv->mask_tex_coords); + else + meta_shaped_texture_set_coord_array (0.0f, 0.0f, + tex_width + / (GLfloat) priv->mask_gl_width, + tex_height + / (GLfloat) priv->mask_gl_height, + priv->mask_tex_coords); + } +} + +static void +meta_shaped_texture_paint (ClutterActor *actor) +{ + MetaShapedTexture *stex = (MetaShapedTexture *) actor; + MetaShapedTexturePrivate *priv = stex->priv; + CoglHandle paint_tex; + guint tex_width, tex_height; + GLboolean texture_was_enabled, blend_was_enabled; + GLboolean vertex_array_was_enabled, tex_coord_array_was_enabled; + GLboolean color_array_was_enabled; + GLuint paint_gl_tex, mask_gl_tex; + GLint paint_gl_width, paint_gl_height; + GLfloat vertex_coords[8], paint_tex_coords[8]; + ClutterActorBox alloc; + static const ClutterColor white = { 0xff, 0xff, 0xff, 0xff }; + + /* If there are no rectangles or multi-texturing isn't supported, + fallback to the regular paint method */ + if (priv->rectangles->len < 1 + || !meta_shaped_texture_is_multi_tex_supported ()) + { + CLUTTER_ACTOR_CLASS (meta_shaped_texture_parent_class) + ->paint (actor); + return; + } + + if (!CLUTTER_ACTOR_IS_REALIZED (CLUTTER_ACTOR (stex))) + clutter_actor_realize (CLUTTER_ACTOR (stex)); + + paint_tex = clutter_texture_get_cogl_texture (CLUTTER_TEXTURE (stex)); + + if (paint_tex == COGL_INVALID_HANDLE) + return; + + tex_width = cogl_texture_get_width (paint_tex); + tex_height = cogl_texture_get_height (paint_tex); + + meta_shaped_texture_ensure_mask (stex); + + cogl_texture_get_gl_texture (paint_tex, &paint_gl_tex, NULL); + cogl_texture_get_gl_texture (priv->mask_texture, &mask_gl_tex, NULL); + + /* We need to keep track of the some of the old state so that we + don't confuse Cogl */ + texture_was_enabled = glIsEnabled (GL_TEXTURE_2D); + blend_was_enabled = glIsEnabled (GL_BLEND); + vertex_array_was_enabled = glIsEnabled (GL_VERTEX_ARRAY); + tex_coord_array_was_enabled = glIsEnabled (GL_TEXTURE_COORD_ARRAY); + color_array_was_enabled = glIsEnabled (GL_COLOR_ARRAY); + + glEnable (GL_TEXTURE_2D); + glEnable (GL_BLEND); + glEnableClientState (GL_VERTEX_ARRAY); + glEnableClientState (GL_TEXTURE_COORD_ARRAY); + glDisableClientState (GL_COLOR_ARRAY); + glVertexPointer (2, GL_FLOAT, 0, vertex_coords); + glTexCoordPointer (2, GL_FLOAT, 0, paint_tex_coords); + cogl_color (&white); + + /* Put the main painting texture in the first texture unit */ + glBindTexture (GL_TEXTURE_2D, paint_gl_tex); + + /* We need the actual size of the texture so that we can calculate + the right texture coordinates if NPOTs textures are not supported + and Cogl has oversized the texture */ + glGetTexLevelParameteriv (GL_TEXTURE_2D, 0, + GL_TEXTURE_WIDTH, + &paint_gl_width); + glGetTexLevelParameteriv (GL_TEXTURE_2D, 0, + GL_TEXTURE_HEIGHT, + &paint_gl_height); + + /* Put the mask texture in the second texture unit */ + tst_active_texture (GL_TEXTURE1); + tst_client_active_texture (GL_TEXTURE1); + glBindTexture (GL_TEXTURE_2D, mask_gl_tex); + + glEnable (GL_TEXTURE_2D); + + glEnableClientState (GL_TEXTURE_COORD_ARRAY); + glTexCoordPointer (2, GL_FLOAT, 0, priv->mask_tex_coords); + + glTexEnvi (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE); + + /* Multiply the alpha by the alpha in the second texture */ + glTexEnvi (GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_MODULATE); + glTexEnvi (GL_TEXTURE_ENV, GL_SRC0_ALPHA, GL_TEXTURE); + glTexEnvi (GL_TEXTURE_ENV, GL_SRC1_ALPHA, GL_PREVIOUS); + + /* Replace the RGB in the second texture with that of the first + texture */ + glTexEnvi (GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_REPLACE); + glTexEnvi (GL_TEXTURE_ENV, GL_SRC0_RGB, GL_PREVIOUS); + + clutter_actor_get_allocation_box (actor, &alloc); + + meta_shaped_texture_set_coord_array (0, 0, + CLUTTER_UNITS_TO_FLOAT (alloc.x2 + - alloc.x1), + CLUTTER_UNITS_TO_FLOAT (alloc.y2 + - alloc.y1), + vertex_coords); + + if (paint_gl_width == tex_width && paint_gl_height == tex_height) + meta_shaped_texture_set_coord_array (0.0f, 0.0f, 1.0f, 1.0f, + paint_tex_coords); + else + meta_shaped_texture_set_coord_array (0.0f, 0.0f, + tex_width + / (GLfloat) paint_gl_width, + tex_height + / (GLfloat) paint_gl_height, + paint_tex_coords); + + glDrawArrays (GL_TRIANGLE_STRIP, 0, 4); + + /* Disable the second texture unit and coord array */ + glDisable (GL_TEXTURE_2D); + glDisableClientState (GL_TEXTURE_COORD_ARRAY); + + /* Go back to operating on the first texture unit */ + tst_active_texture (GL_TEXTURE0); + tst_client_active_texture (GL_TEXTURE0); + + /* Restore the old state */ + if (!texture_was_enabled) + glDisable (GL_TEXTURE_2D); + if (!blend_was_enabled) + glDisable (GL_BLEND); + if (!vertex_array_was_enabled) + glDisableClientState (GL_VERTEX_ARRAY); + if (!tex_coord_array_was_enabled) + glDisableClientState (GL_TEXTURE_COORD_ARRAY); + if (color_array_was_enabled) + glEnableClientState (GL_COLOR_ARRAY); +} + +static void +meta_shaped_texture_pick (ClutterActor *actor, + const ClutterColor *color) +{ + MetaShapedTexture *stex = (MetaShapedTexture *) actor; + MetaShapedTexturePrivate *priv = stex->priv; + + /* If there are no rectangles then use the regular pick */ + if (priv->rectangles->len < 1 + || !meta_shaped_texture_is_multi_tex_supported ()) + CLUTTER_ACTOR_CLASS (meta_shaped_texture_parent_class) + ->pick (actor, color); + else if (clutter_actor_should_pick_paint (actor)) + { + CoglHandle paint_tex; + ClutterActorBox alloc; + + paint_tex = clutter_texture_get_cogl_texture (CLUTTER_TEXTURE (stex)); + + if (paint_tex == COGL_INVALID_HANDLE) + return; + + meta_shaped_texture_ensure_mask (stex); + + cogl_color (color); + + clutter_actor_get_allocation_box (actor, &alloc); + + /* Paint the mask rectangle in the given color */ + cogl_texture_rectangle (priv->mask_texture, + 0, 0, + CLUTTER_UNITS_TO_FIXED (alloc.x2 - alloc.x1), + CLUTTER_UNITS_TO_FIXED (alloc.y2 - alloc.y1), + 0, 0, CFX_ONE, CFX_ONE); + } +} + +ClutterActor * +meta_shaped_texture_new (void) +{ + ClutterActor *self = g_object_new (META_TYPE_SHAPED_TEXTURE, NULL); + + return self; +} + +void +meta_shaped_texture_clear_rectangles (MetaShapedTexture *stex) +{ + MetaShapedTexturePrivate *priv; + + g_return_if_fail (META_IS_SHAPED_TEXTURE (stex)); + + priv = stex->priv; + + g_array_set_size (priv->rectangles, 0); + meta_shaped_texture_dirty_mask (stex); + clutter_actor_queue_redraw (CLUTTER_ACTOR (stex)); +} + +void +meta_shaped_texture_add_rectangle (MetaShapedTexture *stex, + const ClutterGeometry *rect) +{ + g_return_if_fail (META_IS_SHAPED_TEXTURE (stex)); + + meta_shaped_texture_add_rectangles (stex, 1, rect); +} + +void +meta_shaped_texture_add_rectangles (MetaShapedTexture *stex, + size_t num_rects, + const ClutterGeometry *rects) +{ + MetaShapedTexturePrivate *priv; + + g_return_if_fail (META_IS_SHAPED_TEXTURE (stex)); + + priv = stex->priv; + + g_array_append_vals (priv->rectangles, rects, num_rects); + + meta_shaped_texture_dirty_mask (stex); + clutter_actor_queue_redraw (CLUTTER_ACTOR (stex)); +} diff --git a/src/compositor/shaped-texture.h b/src/compositor/shaped-texture.h new file mode 100644 index 000000000..0b92b5696 --- /dev/null +++ b/src/compositor/shaped-texture.h @@ -0,0 +1,84 @@ +/* + * shaped texture + * + * An actor to draw a texture clipped to a list of rectangles + * + * Authored By Neil Roberts + * + * Copyright (C) 2008 Intel Corporation + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#ifndef __META_SHAPED_TEXTURE_H__ +#define __META_SHAPED_TEXTURE_H__ + +#include + +G_BEGIN_DECLS + +#define META_TYPE_SHAPED_TEXTURE \ + (meta_shaped_texture_get_type()) +#define META_SHAPED_TEXTURE(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), \ + META_TYPE_SHAPED_TEXTURE, \ + MetaShapedTexture)) +#define META_SHAPED_TEXTURE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), \ + META_TYPE_SHAPED_TEXTURE, \ + MetaShapedTextureClass)) +#define META_IS_SHAPED_TEXTURE(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \ + META_TYPE_SHAPED_TEXTURE)) +#define META_IS_SHAPED_TEXTURE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), \ + META_TYPE_SHAPED_TEXTURE)) +#define META_SHAPED_TEXTURE_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), \ + META_TYPE_SHAPED_TEXTURE, \ + MetaShapedTextureClass)) + +typedef struct _MetaShapedTexture MetaShapedTexture; +typedef struct _MetaShapedTextureClass MetaShapedTextureClass; +typedef struct _MetaShapedTexturePrivate MetaShapedTexturePrivate; + +struct _MetaShapedTextureClass +{ + ClutterTextureClass parent_class; +}; + +struct _MetaShapedTexture +{ + ClutterTexture parent; + + MetaShapedTexturePrivate *priv; +}; + +GType meta_shaped_texture_get_type (void) G_GNUC_CONST; + +ClutterActor *meta_shaped_texture_new (void); + +void meta_shaped_texture_clear_rectangles (MetaShapedTexture *stex); + +void meta_shaped_texture_add_rectangle (MetaShapedTexture *stex, + const ClutterGeometry *rect); +void meta_shaped_texture_add_rectangles (MetaShapedTexture *stex, + size_t num_rects, + const ClutterGeometry *rects); + +G_END_DECLS + +#endif /* __META_SHAPED_TEXTURE_H__ */