/*
 * Clutter.
 *
 * An OpenGL based 'interactive canvas' library.
 *
 * Authored By Matthew Allum  <mallum@openedhand.com>
 *
 * Copyright (C) 2006 OpenedHand
 *
 * 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, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

/**
 * SECTION:clutter-texture
 * @short_description: An actor for displaying and manipulating images.
 *
 * #ClutterTexture is a base class for displaying and manipulating pixel
 * buffer type data.
 *
 * The clutter_texture_set_from_rgb_data() and clutter_texture_set_pixbuf()
 * functions are used to copy image data into texture memory and subsequently
 * realize the texture.
 *
 * If texture reads are supported by underlying GL implementation,
 * unrealizing/hiding frees image data from texture memory moving to main
 * system memory. Re-realizing then performs the opposite operation.
 * This process allows basic management of commonly limited available texture
 * memory.
 *
 * Note: a ClutterTexture will scale its contents to fit the bounding box
 * requested using clutter_actor_request_coords() and its wrappers. To
 * display an area of a texture without scaling, you should set the clip
 * area using clutter_actor_set_clip().
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "clutter-texture.h"
#include "clutter-main.h"
#include "clutter-marshal.h"
#include "clutter-feature.h"
#include "clutter-util.h"
#include "clutter-private.h"
#include "clutter-debug.h"
#include "clutter-fixed.h"

#include "cogl.h"

G_DEFINE_TYPE (ClutterTexture, clutter_texture, CLUTTER_TYPE_ACTOR);

#if G_BYTE_ORDER == G_LITTLE_ENDIAN
#define PIXEL_TYPE CGL_UNSIGNED_BYTE
#else
#define PIXEL_TYPE CGL_UNSIGNED_INT_8_8_8_8_REV
#endif

typedef struct {
  gint pos;
  gint size;
  gint waste;
} ClutterTextureTileDimension;

#define CLUTTER_TEXTURE_GET_PRIVATE(obj)        (G_TYPE_INSTANCE_GET_PRIVATE ((obj), CLUTTER_TYPE_TEXTURE, ClutterTexturePrivate))

struct _ClutterTexturePrivate
{
  gint                         width;
  gint                         height;
  COGLenum                     pixel_format;
  COGLenum                     pixel_type;
  COGLenum                     target_type;
  GdkPixbuf                   *local_pixbuf; /* non video memory copy */
  guint                        sync_actor_size : 1;
  gint                         max_tile_waste;
  guint                        filter_quality;
  guint                        repeat_x : 1; /* non working */
  guint                        repeat_y : 1; /* non working */
  guint                        is_tiled : 1;
  ClutterTextureTileDimension *x_tiles;
  ClutterTextureTileDimension *y_tiles;
  gint                         n_x_tiles;
  gint                         n_y_tiles;
  COGLuint                    *tiles;

  ClutterActor                *fbo_source;
  COGLuint                     fbo_handle;
};

enum
{
  PROP_0,
  PROP_PIXBUF,
  PROP_USE_TILES,
  PROP_MAX_TILE_WASTE,
  PROP_PIXEL_TYPE, 		/* Texture type */
  PROP_PIXEL_FORMAT,		/* Texture format */
  PROP_SYNC_SIZE,
  PROP_REPEAT_Y,
  PROP_REPEAT_X,
  PROP_FILTER_QUALITY
};

enum
{
  SIZE_CHANGE,
  PIXBUF_CHANGE,
  LAST_SIGNAL
};

static int texture_signals[LAST_SIGNAL] = { 0 };

static void
texture_fbo_free_resources (ClutterTexture *texture);

GQuark
clutter_texture_error_quark (void)
{
  return g_quark_from_static_string ("clutter-texture-error-quark");
}

static guchar*
un_pre_multiply_alpha (const guchar   *data,
		       gint            width,
		       gint            height,
		       gint            rowstride)
{
  gint           x,y;
  unsigned char *ret, *dst, *src;

  ret = dst = g_malloc(sizeof(guchar) * height * rowstride);

  /* FIXME: Optimise */
  for (y = 0; y < height; y++)
    {
      src = (guchar*)data + y * rowstride;
      for (x = 0; x < width; x++)
	{
	  guchar alpha = src[3];
	  if (alpha == 0)
	    {
	      src[0] = src[1] = src[2] = src[3] = alpha;
	    }
	  else
	    {
	      dst[0] = (((src[0] >> 16) & 0xff) * 255 ) / alpha;
	      dst[1] = (((src[1] >> 8) & 0xff) * 255 ) / alpha;
	      dst[2] = (((src[2] >> 0) & 0xff) * 255 ) / alpha;
	      dst[3] = alpha;
	    }
	  dst += 4;
	  src += 4;
	}
    }

  return ret;
}

#ifndef HAVE_COGL_GL
static guchar *
rgb_to_bgr (const guchar   *data,
	    gboolean        has_alpha,
	    gint            width,
	    gint            height,
	    gint            rowstride)
{
  gint           x,y, bpp = 4;
  unsigned char *ret, *dst, *src;

  ret = dst = g_malloc(sizeof(guchar) * height * rowstride);

  if (!has_alpha)
    bpp = 3;

  /* FIXME: Optimise */
  for (y = 0; y < height; y++)
    {
      src = (guchar*)data + y * rowstride;
      for (x = 0; x < width; x++)
	{
	  dst[0] = src[2];
	  dst[1] = src[1];
	  dst[2] = src[0];
	  if (has_alpha)
	    dst[3] = src[3];
	  dst += bpp;
	  src += bpp;
	}
    }

  return ret;
}
#endif /* !HAVE_COGL_GL */

static int
tile_dimension (int                          to_fill,
		int                          start_size,
		int                          waste,
		ClutterTextureTileDimension *tiles)
{
  int pos     = 0;
  int n_tiles = 0;
  int size    = start_size;

  while (TRUE)
    {
      if (tiles)
	{
	  tiles[n_tiles].pos = pos;
	  tiles[n_tiles].size = size;
	  tiles[n_tiles].waste = 0;
	}

      n_tiles++;

      if (to_fill <= size)
	{
	  if (tiles)
	    tiles[n_tiles-1].waste = size - to_fill;
	  break;
	}
      else
	{
	  to_fill -= size; pos += size;
	  while (size >= 2 * to_fill || size - to_fill > waste)
	    size /= 2;
	}
    }

  return n_tiles;
}

static void
texture_init_tiles (ClutterTexture *texture)
{
  ClutterTexturePrivate *priv;
  gint                   x_pot, y_pot;

  priv = texture->priv;

  x_pot = clutter_util_next_p2 (priv->width);
  y_pot = clutter_util_next_p2 (priv->height);

  while (!(cogl_texture_can_size (CGL_TEXTURE_2D,
				  priv->pixel_format,
				  priv->pixel_type,
				  x_pot, y_pot)
	   && (x_pot - priv->width < priv->max_tile_waste)
	   && (y_pot - priv->height < priv->max_tile_waste)))
    {
      CLUTTER_NOTE (TEXTURE, "x_pot:%i - width:%i < max_waste:%i",
		    x_pot,
		    priv->width,
		    priv->max_tile_waste);

      CLUTTER_NOTE (TEXTURE, "y_pot:%i - height:%i < max_waste:%i",
		    y_pot,
		    priv->height,
		    priv->max_tile_waste);

      if (x_pot > y_pot)
	x_pot /= 2;
      else
	y_pot /= 2;

      g_return_if_fail (x_pot != 0 || y_pot != 0);
    }

  if (priv->x_tiles)
    g_free (priv->x_tiles);

  priv->n_x_tiles = tile_dimension (priv->width, x_pot,
				    priv->max_tile_waste, NULL);
  if (priv->y_tiles)
    g_free (priv->y_tiles);
  priv->n_y_tiles = tile_dimension (priv->height, y_pot,
				    priv->max_tile_waste, NULL);

  if (priv->n_x_tiles == 1 && priv->n_y_tiles == 1)
    {
      /* So were not actually tiled... */
      priv->n_x_tiles = priv->n_y_tiles = 0;
      priv->is_tiled = FALSE;
      return;
    }

  priv->x_tiles = g_new (ClutterTextureTileDimension, priv->n_x_tiles);
  tile_dimension (priv->width, x_pot, priv->max_tile_waste, priv->x_tiles);

  priv->y_tiles = g_new (ClutterTextureTileDimension, priv->n_y_tiles);
  tile_dimension (priv->height, y_pot, priv->max_tile_waste, priv->y_tiles);

  CLUTTER_NOTE (TEXTURE,
                "x_pot:%i, width:%i, y_pot:%i, height: %i "
		"max_waste:%i, n_x_tiles: %i, n_y_tiles: %i",
		x_pot, priv->width, y_pot, priv->height,
		priv->max_tile_waste,
		priv->n_x_tiles, priv->n_y_tiles);
}

static void
texture_render_to_gl_quad (ClutterTexture *texture,
			   int             x_1,
			   int             y_1,
			   int             x_2,
			   int             y_2)
{
  int   qx1 = 0, qx2 = 0, qy1 = 0, qy2 = 0;
  int   qwidth = 0, qheight = 0;
  int   x, y, i =0, lastx = 0, lasty = 0;
  float tx, ty;

  ClutterTexturePrivate *priv;

  priv = texture->priv;

  qwidth  = x_2 - x_1;
  qheight = y_2 - y_1;

  if (!priv->is_tiled)
    {
      cogl_texture_bind (priv->target_type, priv->tiles[0]);

      if (priv->target_type == CGL_TEXTURE_2D) /* POT */
	{
	  tx = (float) priv->width / clutter_util_next_p2 (priv->width);
	  ty = (float) priv->height / clutter_util_next_p2 (priv->height);
	}
      else
	{
	  tx = (float) priv->width;
	  ty = (float) priv->height;

	}

      qx1 = x_1; qx2 = x_2;
      qy1 = y_1; qy2 = y_2;

      cogl_texture_quad (x_1, x_2, y_1, y_2,
			 0,
			 0,
			 CLUTTER_FLOAT_TO_FIXED (tx),
			 CLUTTER_FLOAT_TO_FIXED (ty));

      return;
    }

  for (x = 0; x < priv->n_x_tiles; x++)
    {
      lasty = 0;

      for (y=0; y < priv->n_y_tiles; y++)
	{
	  int actual_w, actual_h;

	  cogl_texture_bind (priv->target_type, priv->tiles[i]);

	  actual_w = priv->x_tiles[x].size - priv->x_tiles[x].waste;
	  actual_h = priv->y_tiles[y].size - priv->y_tiles[y].waste;

	  CLUTTER_NOTE (TEXTURE,
                        "rendering text tile x: %i, y: %i - %ix%i",
			x, y, actual_w, actual_h);

	  tx = (float) actual_w / priv->x_tiles[x].size;
	  ty = (float) actual_h / priv->y_tiles[y].size;

	  qx1 = x_1 + lastx;
	  qx2 = qx1 + ((qwidth * actual_w ) / priv->width );

	  qy1 = y_1 + lasty;
	  qy2 = qy1 + ((qheight * actual_h) / priv->height );

	  cogl_texture_quad (qx1, qx2, qy1, qy2,
			     0,
			     0,
			     CLUTTER_FLOAT_TO_FIXED (tx),
			     CLUTTER_FLOAT_TO_FIXED (ty));

	  lasty += (qy2 - qy1) ;

	  i++;
	}
      lastx += (qx2 - qx1);
    }
}

static void
texture_free_gl_resources (ClutterTexture *texture)
{
  ClutterTexturePrivate *priv;

  priv = texture->priv;

  CLUTTER_MARK();

  if (priv->tiles)
    {
      if (!priv->is_tiled)
	cogl_textures_destroy (1, priv->tiles);
      else
	cogl_textures_destroy (priv->n_x_tiles * priv->n_y_tiles, priv->tiles);

      g_free (priv->tiles);
      priv->tiles = NULL;
    }

  if (priv->x_tiles)
    {
      g_free (priv->x_tiles);
      priv->x_tiles = NULL;
    }

  if (priv->y_tiles)
    {
      g_free (priv->y_tiles);
      priv->y_tiles = NULL;
    }
}

static void inline
texture_upload_data (ClutterTexture *texture,
		             const guchar   *data,
                     gboolean        has_alpha,
                     gint            width,
                     gint            height,
                     gint            rowstride,
                     gint            bpp)
{
  ClutterTexturePrivate *priv;
  gint x, y;
  gint i = 0;
  gboolean create_textures = FALSE;
  GdkPixbuf *master_pixbuf = NULL;

  priv = texture->priv;

  g_return_if_fail (data != NULL);

  CLUTTER_MARK();

  if (!priv->is_tiled)
    {
      /* Single Texture */
      if (!priv->tiles)
	{
	  priv->tiles = g_new (COGLuint, 1);
	  glGenTextures (1, priv->tiles);
	  create_textures = TRUE;
	}

      CLUTTER_NOTE (TEXTURE, "syncing for single tile");

      cogl_texture_bind (priv->target_type, priv->tiles[0]);
      cogl_texture_set_alignment (priv->target_type, 4, priv->width);

      cogl_texture_set_filters
	(priv->target_type,
	 priv->filter_quality ? CGL_LINEAR : CGL_NEAREST,
	 priv->filter_quality ? CGL_LINEAR : CGL_NEAREST);

      cogl_texture_set_wrap (priv->target_type,
			     priv->repeat_x ? CGL_REPEAT : CGL_CLAMP_TO_EDGE,
			     priv->repeat_y ? CGL_REPEAT : CGL_CLAMP_TO_EDGE);

      if (create_textures)
	{
	  gint tex_width, tex_height;

	  tex_width  = priv->width;
	  tex_height = priv->height;

	  if (priv->target_type == CGL_TEXTURE_2D) /* POT */
	    {
	      tex_width  = clutter_util_next_p2 (priv->width);
	      tex_height = clutter_util_next_p2 (priv->height);
	    }

	  cogl_texture_image_2d (priv->target_type,
				 CGL_RGBA,
				 tex_width,
				 tex_height,
				 priv->pixel_format,
				 priv->pixel_type,
				 NULL);
	}

      cogl_texture_sub_image_2d (priv->target_type,
				 0,
				 0,
				 width,
				 height,
				 priv->pixel_format,
				 priv->pixel_type,
				 data);
      return;
    }

  /* Multiple tiled texture */

  CLUTTER_NOTE (TEXTURE,
                "syncing for multiple tiles for %ix%i pixbuf",
		priv->width, priv->height);

  g_return_if_fail (priv->x_tiles != NULL && priv->y_tiles != NULL);

  master_pixbuf = gdk_pixbuf_new_from_data (data,
                                            GDK_COLORSPACE_RGB,
                                            has_alpha,
                                            8,
                                            width, height, rowstride,
                                            NULL, NULL);

  if (priv->tiles == NULL)
    {
      priv->tiles = g_new (COGLuint, priv->n_x_tiles * priv->n_y_tiles);
      glGenTextures (priv->n_x_tiles * priv->n_y_tiles, priv->tiles);
      create_textures = TRUE;
    }

  for (x = 0; x < priv->n_x_tiles; x++)
    for (y = 0; y < priv->n_y_tiles; y++)
      {
        GdkPixbuf *pixtmp;
	gint src_h, src_w;

	src_w = priv->x_tiles[x].size;
	src_h = priv->y_tiles[y].size;

        pixtmp = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
                                 has_alpha,
                                 8,
                                 src_w, src_h);

	/* clip */
	if (priv->x_tiles[x].pos + src_w > priv->width)
	  src_w = priv->width - priv->x_tiles[x].pos;

	if (priv->y_tiles[y].pos + src_h > priv->height)
	  src_h = priv->height - priv->y_tiles[y].pos;

        gdk_pixbuf_copy_area (master_pixbuf,
                              priv->x_tiles[x].pos,
                              priv->y_tiles[y].pos,
                              src_w,
                              src_h,
                              pixtmp,
                              0, 0);
#ifdef CLUTTER_DUMP_TILES
	{
	  gchar  *filename;

	  filename =
            g_strdup_printf("/tmp/%i-%i-%i.png",
                            clutter_actor_get_gid (CLUTTER_ACTOR (texture)),
                            x, y);

	  printf ("saving %s\n", filename);

	  gdk_pixbuf_save (pixtmp, filename , "png", NULL, NULL);
          g_free (filename);
	}
#endif

	cogl_texture_bind (priv->target_type, priv->tiles[i]);

	cogl_texture_set_alignment (priv->target_type,
				    4, priv->x_tiles[x].size);

	cogl_texture_set_filters
	  (priv->target_type,
	   priv->filter_quality ? CGL_LINEAR : CGL_NEAREST,
	   priv->filter_quality ? CGL_LINEAR : CGL_NEAREST);

	cogl_texture_set_wrap (priv->target_type,
			       priv->repeat_x ? CGL_REPEAT : CGL_CLAMP_TO_EDGE,
			       priv->repeat_y ? CGL_REPEAT : CGL_CLAMP_TO_EDGE);
	if (create_textures)
	  {
	    cogl_texture_image_2d (priv->target_type,
				   CGL_RGBA,
				   gdk_pixbuf_get_width (pixtmp),
				   gdk_pixbuf_get_height (pixtmp),
				   priv->pixel_format,
				   priv->pixel_type,
				   gdk_pixbuf_get_pixels (pixtmp));
	  }
	else
	  {
	    /* Textures already created, so just update whats inside
	    */
	    cogl_texture_sub_image_2d (priv->target_type,
				       0,
				       0,
				       gdk_pixbuf_get_width (pixtmp),
				       gdk_pixbuf_get_height (pixtmp),
				       priv->pixel_format,
				       priv->pixel_type,
				       gdk_pixbuf_get_pixels (pixtmp));
	  }

	g_object_unref (pixtmp);

	i++;
      }

  g_object_unref (master_pixbuf);
}

static void
clutter_texture_unrealize (ClutterActor *actor)
{
  ClutterTexture        *texture;
  ClutterTexturePrivate *priv;

  texture = CLUTTER_TEXTURE(actor);
  priv = texture->priv;

  if (priv->tiles == NULL)
    return;

  /* there's no need to read the pixels back when unrealizing inside
   * a dispose run, and the dispose() call will release the GL
   * texture data as well, so we can safely bail out now
   */
  if (CLUTTER_PRIVATE_FLAGS (actor) & CLUTTER_ACTOR_IN_DESTRUCTION)
    return;

  CLUTTER_MARK();

  if (priv->fbo_source)
    {
      /* Free up our fbo handle and texture resources, realize will recreate */
      cogl_offscreen_destroy (priv->fbo_handle);
      priv->fbo_handle = 0;
      texture_free_gl_resources (texture);
      return;
    }

  if (clutter_feature_available (CLUTTER_FEATURE_TEXTURE_READ_PIXELS))
    {
      /* Move image data from video to main memory.
       * GL/ES cant do this - it probably makes sense
       * to move this kind of thing into a ClutterProxyTexture
       * where this behaviour can be better controlled.
       *
       * Or make it controllable via a property.
       */
      if (priv->local_pixbuf == NULL)
	{
	  priv->local_pixbuf = clutter_texture_get_pixbuf (texture);
	  CLUTTER_NOTE (TEXTURE, "moved pixels into system (pixbuf) mem");
	}

      texture_free_gl_resources (texture);
    }

  CLUTTER_NOTE (TEXTURE, "Texture unrealized");
}

static void
clutter_texture_realize (ClutterActor *actor)
{
  ClutterTexture       *texture;
  ClutterTexturePrivate *priv;

  texture = CLUTTER_TEXTURE(actor);
  priv = texture->priv;

  CLUTTER_MARK();

  if (priv->fbo_source)
    {
      /* Handle FBO's */

      priv->tiles = g_new (COGLuint, 1);

      /* FIXME: needs a cogl wrapper */
      glGenTextures (1, priv->tiles);

      cogl_texture_bind (priv->target_type, priv->tiles[0]);

      cogl_texture_image_2d (priv->target_type,
                             CGL_RGBA,
                             priv->width,
                             priv->height,
                             priv->pixel_format,
                             priv->pixel_type,
                             NULL);

      priv->fbo_handle = cogl_offscreen_create (priv->tiles[0]);

      if (priv->fbo_handle == 0)
        {
          g_warning ("%s: Offscreen texture creation failed", G_STRLOC);
	  CLUTTER_ACTOR_UNSET_FLAGS (actor, CLUTTER_ACTOR_REALIZED);
          return;
        }

      clutter_actor_set_size (actor, priv->width, priv->height);
      return;
    }

  if (priv->local_pixbuf != NULL)
    {
      /* Move any local image data we have from unrealization
       * back into video memory.
      */
      if (priv->is_tiled)
	texture_init_tiles (texture);
      clutter_texture_set_pixbuf (texture, priv->local_pixbuf, NULL);
      g_object_unref (priv->local_pixbuf);
      priv->local_pixbuf = NULL;
    }
  else
    {
      if (clutter_feature_available (CLUTTER_FEATURE_TEXTURE_READ_PIXELS))
	{
	  /* Dont allow realization with no pixbuf - note set_pixbuf/data
	   * will set realize flags.
	  */
	  CLUTTER_NOTE (TEXTURE,
			"Texture has no image data cannot realize");

	  CLUTTER_NOTE (TEXTURE, "flags %i", actor->flags);
	  CLUTTER_ACTOR_UNSET_FLAGS (actor, CLUTTER_ACTOR_REALIZED);
	  CLUTTER_NOTE (TEXTURE, "flags %i", actor->flags);
	  return;
	}
    }

  CLUTTER_NOTE (TEXTURE, "Texture realized");
}

static void
clutter_texture_show (ClutterActor *self)
{
  ClutterActorClass *parent_class;

  /* chain up parent show */
  parent_class = CLUTTER_ACTOR_CLASS (clutter_texture_parent_class);
  if (parent_class->show)
    parent_class->show (self);

  clutter_actor_realize (self);
}

static void
clutter_texture_hide (ClutterActor *self)
{
  ClutterActorClass *parent_class;

  /* chain up parent hide */
  parent_class = CLUTTER_ACTOR_CLASS (clutter_texture_parent_class);
  if (parent_class->hide)
    parent_class->hide (self);

  clutter_actor_unrealize (self);
}

static void
clutter_texture_paint (ClutterActor *self)
{
  ClutterTexture *texture = CLUTTER_TEXTURE (self);
  ClutterTexturePrivate *priv = texture->priv;
  gint            x_1, y_1, x_2, y_2;
  ClutterColor    col = { 0xff, 0xff, 0xff, 0xff };

  if (!CLUTTER_ACTOR_IS_REALIZED (CLUTTER_ACTOR(texture)))
    clutter_actor_realize (CLUTTER_ACTOR(texture));

  if (priv->tiles == NULL)
    {
      /* We just need do debug this state, it doesn't really need to
       * throw a an error as what previously happened. Sub classes
       * quite likely may not be able to realize.
      */
      CLUTTER_NOTE (PAINT, "unable to paint texture '%s', contains no tiles",
		    clutter_actor_get_name (self)
                           ? clutter_actor_get_name (self)
		           : "unknown");
      return;
    }

  if (priv->fbo_handle)
    {
      ClutterMainContext *context;
      ClutterShader      *shader = NULL;

      context = clutter_context_get_default ();

      if (context->shaders)
        shader = clutter_actor_get_shader (context->shaders->data);

      /* Temporarily turn of the shader on the top of the context's
       * shader stack, to restore the GL pipeline to it's natural state.
       */
      if (shader)
        clutter_shader_set_is_enabled (shader, FALSE);

      cogl_offscreen_redirect_start (priv->fbo_handle,
                                     priv->width, priv->height);

      /* Render out actor scene to fbo */
      clutter_actor_paint (priv->fbo_source);

      cogl_offscreen_redirect_end (priv->fbo_handle,
                                   CLUTTER_STAGE_WIDTH(),
                                   CLUTTER_STAGE_HEIGHT());

      /* If there is a shader on top of the shader stack, turn it back on. */
      if (shader)
        clutter_shader_set_is_enabled (shader, TRUE);

      glBindTexture(CGL_TEXTURE_RECTANGLE_ARB, priv->tiles[0]);
    }

  CLUTTER_NOTE (PAINT,
                "painting texture '%s'",
		clutter_actor_get_name (self) ? clutter_actor_get_name (self)
                                              : "unknown");
  cogl_push_matrix ();

  switch (priv->target_type)
    {
    case CGL_TEXTURE_2D:
      cogl_enable (CGL_ENABLE_TEXTURE_2D|CGL_ENABLE_BLEND);
      break;
    case CGL_TEXTURE_RECTANGLE_ARB:
      cogl_enable (CGL_ENABLE_TEXTURE_RECT|CGL_ENABLE_BLEND);
      break;
    default:
      break;
    }

  col.alpha = clutter_actor_get_abs_opacity (self);
  cogl_color (&col);

  clutter_actor_get_coords (self, &x_1, &y_1, &x_2, &y_2);

  CLUTTER_NOTE (PAINT, "paint to x1: %i, y1: %i x2: %i, y2: %i "
		       "opacity: %i",
		x_1, y_1, x_2, y_2,
		clutter_actor_get_opacity (self));

  /* Paint will of translated us */
  texture_render_to_gl_quad (texture, 0, 0, x_2 - x_1, y_2 - y_1);

  cogl_pop_matrix ();
}

static void
clutter_texture_request_coords (ClutterActor    *self,
                                ClutterActorBox *box)
{
  ClutterTexture *texture = CLUTTER_TEXTURE (self);
  ClutterActorBox old_request;

  clutter_actor_query_coords (self, &old_request);

  if (((box->x2 - box->x1) != (old_request.x2 - old_request.x1)) ||
      ((box->y2 - box->y1) != (old_request.y2 - old_request.y1)))
    texture->priv->sync_actor_size = FALSE;

  CLUTTER_ACTOR_CLASS (clutter_texture_parent_class)->request_coords (self, box);
}

static void
clutter_texture_dispose (GObject *object)
{
  ClutterTexture *texture = CLUTTER_TEXTURE (object);
  ClutterTexturePrivate *priv;

  priv = texture->priv;

  texture_free_gl_resources (texture);
  texture_fbo_free_resources (texture);

  if (priv->local_pixbuf != NULL)
    {
      g_object_unref (priv->local_pixbuf);
      priv->local_pixbuf = NULL;
    }

  G_OBJECT_CLASS (clutter_texture_parent_class)->dispose (object);
}

static void
clutter_texture_set_property (GObject      *object,
			      guint         prop_id,
			      const GValue *value,
			      GParamSpec   *pspec)
{
  ClutterTexture        *texture;
  ClutterTexturePrivate *priv;

  texture = CLUTTER_TEXTURE(object);
  priv = texture->priv;

  switch (prop_id)
    {
    case PROP_PIXBUF:
      if (g_value_get_object (value))
        clutter_texture_set_pixbuf (texture,
                                    GDK_PIXBUF (g_value_get_object (value)),
				    NULL);
      break;
    case PROP_USE_TILES:
      priv->is_tiled = g_value_get_boolean (value);

      if (priv->target_type == CGL_TEXTURE_RECTANGLE_ARB && priv->is_tiled)
	priv->target_type = CGL_TEXTURE_2D;

      CLUTTER_NOTE (TEXTURE, "Texture is tiled ? %s",
		    priv->is_tiled ? "yes" : "no");
      break;
    case PROP_MAX_TILE_WASTE:
      priv->max_tile_waste = g_value_get_int (value);
      break;
    case PROP_SYNC_SIZE:
      priv->sync_actor_size = g_value_get_boolean (value);
      break;
    case PROP_REPEAT_X:
      priv->repeat_x = g_value_get_boolean (value);
      break;
    case PROP_REPEAT_Y:
      priv->repeat_y = g_value_get_boolean (value);
      break;
    case PROP_FILTER_QUALITY:
      priv->filter_quality = g_value_get_int (value);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
  }
}

static void
clutter_texture_get_property (GObject    *object,
			      guint       prop_id,
			      GValue     *value,
			      GParamSpec *pspec)
{
  ClutterTexture        *texture;
  ClutterTexturePrivate *priv;

  texture = CLUTTER_TEXTURE(object);
  priv = texture->priv;

  switch (prop_id)
    {
    case PROP_PIXBUF:
      {
	GdkPixbuf *pixb;
	pixb = clutter_texture_get_pixbuf (texture);
	g_value_take_object (value, pixb);
      }
      break;
    case PROP_USE_TILES:
      g_value_set_boolean (value, priv->is_tiled);
      break;
    case PROP_MAX_TILE_WASTE:
      g_value_set_int (value, priv->max_tile_waste);
      break;
    case PROP_PIXEL_TYPE:
      g_value_set_int (value, priv->pixel_type);
      break;
    case PROP_PIXEL_FORMAT:
      g_value_set_int (value, priv->pixel_format);
      break;
    case PROP_SYNC_SIZE:
      g_value_set_boolean (value, priv->sync_actor_size);
      break;
    case PROP_REPEAT_X:
      g_value_set_boolean (value, priv->repeat_x);
      break;
    case PROP_REPEAT_Y:
      g_value_set_boolean (value, priv->repeat_y);
      break;
    case PROP_FILTER_QUALITY:
      g_value_set_int (value, priv->filter_quality);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
    }
}


static void
clutter_texture_class_init (ClutterTextureClass *klass)
{
  GObjectClass        *gobject_class;
  ClutterActorClass *actor_class;

  gobject_class = (GObjectClass*) klass;
  actor_class = (ClutterActorClass*) klass;

  g_type_class_add_private (klass, sizeof (ClutterTexturePrivate));

  actor_class->paint          = clutter_texture_paint;
  actor_class->realize        = clutter_texture_realize;
  actor_class->unrealize      = clutter_texture_unrealize;
  actor_class->show           = clutter_texture_show;
  actor_class->hide           = clutter_texture_hide;
  actor_class->request_coords = clutter_texture_request_coords;

  gobject_class->dispose      = clutter_texture_dispose;
  gobject_class->set_property = clutter_texture_set_property;
  gobject_class->get_property = clutter_texture_get_property;

  g_object_class_install_property
    (gobject_class, PROP_PIXBUF,
     g_param_spec_object ("pixbuf",
			  "Pixbuf source for Texture.",
			  "Pixbuf source for Texture.",
			  GDK_TYPE_PIXBUF,
			  CLUTTER_PARAM_READWRITE));

  g_object_class_install_property
    (gobject_class, PROP_USE_TILES,
     g_param_spec_boolean ("tiled",
			   "Enable use of tiled textures",
			   "Enables the use of tiled GL textures to more "
			   "efficiently use available texture memory",
			   /* FIXME: This default set at runtime :/
                            * As tiling depends on what GL features available.
                            * Need to figure out better solution
			   */
			   (clutter_feature_available
			     (CLUTTER_FEATURE_TEXTURE_RECTANGLE) == FALSE),
			   G_PARAM_CONSTRUCT_ONLY | CLUTTER_PARAM_READWRITE));

  g_object_class_install_property
    (gobject_class, PROP_SYNC_SIZE,
     g_param_spec_boolean ("sync-size",
			   "Sync size of actor",
			   "Auto sync size of actor to underlying pixbuf "
			   "dimensions",
			   TRUE,
			   CLUTTER_PARAM_READWRITE));

  g_object_class_install_property
    (gobject_class, PROP_REPEAT_X,
     g_param_spec_boolean ("repeat-x",
			   "Tile underlying pixbuf in x direction",
			   "Reapeat underlying pixbuf rather than scale "
			   "in x direction. Currently ignored",
			   FALSE,
			   CLUTTER_PARAM_READWRITE));

  g_object_class_install_property
    (gobject_class, PROP_REPEAT_Y,
     g_param_spec_boolean ("repeat-y",
			   "Tile underlying pixbuf in y direction",
			   "Reapeat underlying pixbuf rather than scale "
			   "in y direction. Currently ignored",
			   FALSE,
			   CLUTTER_PARAM_READWRITE));

  /* FIXME: Ideally this option needs to have some kind of global
   *        overide as to imporve performance.
  */
  g_object_class_install_property
    (gobject_class, PROP_FILTER_QUALITY,
     g_param_spec_int ("filter-quality",
		       "Quality of filter used when scaling a texture",
		       "Values 0 and 1 current only supported, with 0 "
		       "being lower quality but fast, 1 being better "
		       "quality but slower. ( Currently just maps to "
		       "GL_NEAREST / GL_LINEAR )",
		       0,
		       G_MAXINT,
		       1,
		       CLUTTER_PARAM_READWRITE));

  g_object_class_install_property
    (gobject_class, PROP_MAX_TILE_WASTE,
     g_param_spec_int ("tile-waste",
		       "Tile dimension to waste",
		       "Max wastage dimension of a texture when using "
		       "tiled textures. Bigger values use less textures, "
		       "smaller values less texture memory.",
		       0,
		       G_MAXINT,
		       64,
		       G_PARAM_CONSTRUCT_ONLY | CLUTTER_PARAM_READWRITE));

  g_object_class_install_property
    (gobject_class, PROP_PIXEL_TYPE,
     g_param_spec_int ("pixel-type",
		       "Texture Pixel Type",
		       "GL texture pixel type used",
		       0,
		       G_MAXINT,
		       PIXEL_TYPE,
		       G_PARAM_READABLE));

  g_object_class_install_property
    (gobject_class, PROP_PIXEL_FORMAT,
     g_param_spec_int ("pixel-format",
		       "Texture pixel format",
		       "GL texture pixel format used",
		       0,
		       G_MAXINT,
		       CGL_RGBA,
		       G_PARAM_READABLE));

  /**
   * ClutterTexture::size-change:
   * @texture: the texture which received the signal
   * @width: the width of the new texture
   * @height: the height of the new texture
   *
   * The ::size-change signal is emitted each time the size of the
   * pixbuf used by @texture changes.  The new size is given as
   * argument to the callback.
   */
  texture_signals[SIZE_CHANGE] =
    g_signal_new ("size-change",
		  G_TYPE_FROM_CLASS (gobject_class),
		  G_SIGNAL_RUN_LAST,
		  G_STRUCT_OFFSET (ClutterTextureClass, size_change),
		  NULL, NULL,
		  clutter_marshal_VOID__INT_INT,
		  G_TYPE_NONE,
		  2, G_TYPE_INT, G_TYPE_INT);
  /**
   * ClutterTexture::pixbuf-change:
   * @texture: the texture which received the signal
   *
   * The ::pixbuf-change signal is emitted each time the pixbuf
   * used by @texture changes.
   */
  texture_signals[PIXBUF_CHANGE] =
    g_signal_new ("pixbuf-change",
		  G_TYPE_FROM_CLASS (gobject_class),
		  G_SIGNAL_RUN_LAST,
		  G_STRUCT_OFFSET (ClutterTextureClass, pixbuf_change),
		  NULL, NULL,
		  g_cclosure_marshal_VOID__VOID,
		  G_TYPE_NONE,
		  0);
}

static void
clutter_texture_init (ClutterTexture *self)
{
  ClutterTexturePrivate *priv;

  self->priv = priv = CLUTTER_TEXTURE_GET_PRIVATE (self);

  priv->max_tile_waste  = 64;
  priv->filter_quality  = 1;
  priv->is_tiled        = TRUE;
  priv->pixel_type      = PIXEL_TYPE;
  priv->pixel_format    = CGL_RGBA;
  priv->repeat_x        = FALSE;
  priv->repeat_y        = FALSE;
  priv->sync_actor_size = TRUE;

  if (clutter_feature_available (CLUTTER_FEATURE_TEXTURE_RECTANGLE))
    {
      priv->target_type = CGL_TEXTURE_RECTANGLE_ARB;
      priv->is_tiled    = FALSE;
    }
  else
    priv->target_type = CGL_TEXTURE_2D;
}

static void
pixbuf_destroy_notify (guchar *pixels, gpointer data)
{
  g_free (pixels);
}

static GdkPixbuf *
texture_get_tile_pixbuf (ClutterTexture *texture,
                         COGLuint        texture_id,
                         gint            bpp,
                         gint            ix,
                         gint            iy)
{
#ifdef HAVE_COGL_GL
  ClutterTexturePrivate *priv;
  guchar                *pixels = NULL;
  guint                  tex_width, tex_height;

  priv = texture->priv;
  cogl_texture_bind (priv->target_type, texture_id);

  if (!priv->is_tiled)
    {
      tex_width = priv->width;
      tex_height = priv->height;
    }
  else
    {
      tex_width = priv->x_tiles[ix].size;
      tex_height = priv->y_tiles[iy].size;
    }

  /* Make sure if we aren't using rectangular textures that we increase the
   * texture size accordingly.
   */
  if (priv->target_type == CGL_TEXTURE_2D) /* POT */
    {
      tex_width = clutter_util_next_p2 (tex_width);
      tex_height = clutter_util_next_p2 (tex_height);
    }

  cogl_texture_set_alignment (priv->target_type, 4, tex_width);

  if ((pixels = g_malloc (((tex_width * bpp + 3) &~ 3) * tex_height)) == NULL)
    return NULL;

  /* Read data from GL texture and return as pixbuf */
  /* No such func in GLES... */
  glGetTexImage (priv->target_type,
                 0,
                 (priv->pixel_format == CGL_RGBA
                  || priv->pixel_format == CGL_BGRA) ?
                 CGL_RGBA : CGL_RGB,
                 PIXEL_TYPE,
                 (GLvoid *)pixels);


  return gdk_pixbuf_new_from_data ((const guchar *)pixels,
                                   GDK_COLORSPACE_RGB,
                                   (priv->pixel_format == CGL_RGBA
                                    || priv->pixel_format == CGL_BGRA),
                                   8,
                                   tex_width,
                                   tex_height,
                                   ((tex_width * bpp + 3) &~ 3),
                                   pixbuf_destroy_notify,
                                   NULL);
#else
  return NULL;
#endif
}

/**
 * clutter_texture_get_pixbuf:
 * @texture: A #ClutterTexture
 *
 * Gets a #GdkPixbuf representation of the #ClutterTexture data.
 * The created #GdkPixbuf is not owned by the texture but the caller.
 *
 * Note: %NULL is always returned with OpenGL ES.
 *
 * Return value: A #GdkPixbuf, or %NULL on fail.
 **/
GdkPixbuf *
clutter_texture_get_pixbuf (ClutterTexture *texture)
{
#if HAVE_COGL_GL
  ClutterTexturePrivate *priv;
  GdkPixbuf             *pixbuf;
  GdkPixbuf             *pixtmp;
  int                    bpp = 4;

  priv = texture->priv;

  if (priv->tiles == NULL)
    return NULL;

  if (priv->pixel_format == CGL_YCBCR_MESA)
    return NULL;                 /* FIXME: convert YUV */

  if (priv->pixel_format == CGL_RGB || priv->pixel_format == CGL_BGR)
    bpp = 3;

  pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
                           priv->pixel_format == CGL_RGBA ||
                           priv->pixel_format == CGL_BGRA,
                           8, priv->width, priv->height);

  if (pixbuf == NULL)
    return NULL;

  if (!priv->is_tiled)
    {
      pixtmp = texture_get_tile_pixbuf (texture, priv->tiles[0], bpp, 0, 0);

      if (pixtmp == NULL)
        {
          g_object_unref (pixbuf);
          return NULL;
        }

      /* Avoid a copy if the texture has the same size as the pixbuf */
      if ((gdk_pixbuf_get_width (pixtmp)  == priv->width &&
           gdk_pixbuf_get_height (pixtmp) == priv->height))
        {
          g_object_unref (pixbuf);
          pixbuf = pixtmp;
        }
      else
        {
          gdk_pixbuf_copy_area (pixtmp,
                                0, 0,
                                priv->width, priv->height,
                                pixbuf,
                                0, 0);
          g_object_unref (pixtmp);
        }
    }
  else
    {
      int x,y,i;

      i = 0;

      for (x = 0; x < priv->n_x_tiles; x++)
        for (y = 0; y < priv->n_y_tiles; y++)
          {
            gint src_w, src_h;

            pixtmp = texture_get_tile_pixbuf (texture, priv->tiles[i], bpp, x, y);

            if (pixtmp == NULL)
              {
                g_object_unref (pixbuf);
                return NULL;
              }

            src_w = priv->x_tiles[x].size;
            src_h = priv->y_tiles[y].size;

            /* Clip */
            if (priv->x_tiles[x].pos + src_w > priv->width)
              src_w = priv->width - priv->x_tiles[x].pos;

            if (priv->y_tiles[y].pos + src_h > priv->height)
              src_h = priv->height - priv->y_tiles[y].pos;

            gdk_pixbuf_copy_area (pixtmp,
                                  0, 0, src_w, src_h,
                                  pixbuf,
                                  priv->x_tiles[x].pos, priv->y_tiles[y].pos);

            g_object_unref (pixtmp);

            i++;
          }

    }

  return pixbuf;
#else

  /* FIXME: func call wont work for GLES...
   *        features need to reflect this.
   */
  return NULL;
#endif
}

/* internal helper function for initialization of
 * clutter_texture_set_from_rgb_data and clutter_texture_set_area_from_rgb_data.
 */
static inline gboolean
texture_prepare_upload (gboolean            initialize,
                        ClutterTexture     *texture,
                        const guchar       *data,
                        gboolean            has_alpha,
                        gint                width,
                        gint                height,
                        gint                rowstride,
                        gint                bpp,
                        ClutterTextureFlags flags,
                        guchar            **copy_data,
                        gboolean           *texture_dirty,
                        gboolean           *size_change)
{
  ClutterTexturePrivate *priv;
  COGLenum               prev_format;
  priv = texture->priv;

  g_return_val_if_fail (data != NULL, FALSE);
  /* Needed for GL_RGBA (internal format) and gdk pixbuf usage */
  g_return_val_if_fail (bpp == 4, FALSE);

  if (initialize)
    {
      *texture_dirty = *size_change =
        (width != priv->width || height != priv->height) ;
    }

  prev_format = priv->pixel_format;

  if (has_alpha)
    priv->pixel_format = CGL_RGBA;
  else
    priv->pixel_format = CGL_RGB;

  if (flags & CLUTTER_TEXTURE_RGB_FLAG_BGR)
    {
#if HAVE_COGL_GL
      if (has_alpha)
	priv->pixel_format = CGL_BGRA;
      else
	priv->pixel_format = CGL_BGR;
#else
      /* GLES has no BGR format*/
      *copy_data = rgb_to_bgr (data, has_alpha, width, height, rowstride);
#endif /* HAVE_COGL_GL */
    }

  if (flags & CLUTTER_TEXTURE_RGB_FLAG_PREMULT)
    *copy_data = un_pre_multiply_alpha (data, width, height, rowstride);

  if (initialize)
    {
      if (prev_format != priv->pixel_format || priv->pixel_type != PIXEL_TYPE)
        *texture_dirty = TRUE;
    }
  else
    {
      if (prev_format != priv->pixel_format || priv->pixel_type != PIXEL_TYPE)
        {
          g_warning ("%s: pixel format or type mismatch", G_STRFUNC);
          return FALSE;
        }
    }

  priv->pixel_type = PIXEL_TYPE;

  return TRUE;
}

/**
 * clutter_texture_set_from_rgb_data:
 * @texture: A #ClutterTexture
 * @data: Image data in RGBA type colorspace.
 * @has_alpha: Set to TRUE if image data has an alpha channel.
 * @width: Width in pixels of image data.
 * @height: Height in pixels of image data
 * @rowstride: Distance in bytes between row starts.
 * @bpp: bytes per pixel ( Currently only 4 supported )
 * @flags: #ClutterTextureFlags
 * @error: return location for a #GError, or %NULL.
 *
 * Sets #ClutterTexture image data.
 *
 * Note: This function is likely to change in future versions.
 *
 * Return value: %TRUE on success, %FALSE on failure.
 *
 * Since: 0.4.
 **/
gboolean
clutter_texture_set_from_rgb_data   (ClutterTexture     *texture,
				     const guchar       *data,
				     gboolean            has_alpha,
				     gint                width,
				     gint                height,
				     gint                rowstride,
				     gint                bpp,
				     ClutterTextureFlags flags,
				     GError            **error)
{
  ClutterTexturePrivate *priv;
  guchar                *copy_data = NULL;
  gboolean               texture_dirty = TRUE, size_change = FALSE;

  priv = texture->priv;

  /* Remove FBO if exisiting */
  if (priv->fbo_source)
    texture_fbo_free_resources (texture);

  if (!texture_prepare_upload (TRUE, texture, data, has_alpha,
			       width, height, rowstride, bpp, flags,
			       &copy_data, &texture_dirty, &size_change))
    {
      return FALSE;
    }

  priv->width      = width;
  priv->height     = height;

  if (texture_dirty)
    {
      texture_free_gl_resources (texture);

      if (priv->is_tiled == FALSE)
	{
	  if (priv->target_type == CGL_TEXTURE_RECTANGLE_ARB &&
              !cogl_texture_can_size (CGL_TEXTURE_RECTANGLE_ARB,
				      priv->pixel_format,
				      priv->pixel_type,
				      priv->width,
				      priv->height))
	    {
	      /* If we cant create NPOT tex of this size fall back to tiles */
	      CLUTTER_NOTE (TEXTURE,
			    "Cannot make npots of size %ix%i "
			    "falling back to tiled",
			    priv->width,
			    priv->height);

	      priv->target_type = CGL_TEXTURE_2D;
	    }

	  if (priv->target_type == CGL_TEXTURE_2D &&
	      !cogl_texture_can_size (CGL_TEXTURE_2D,
				      priv->pixel_format,
				      priv->pixel_type,
				      clutter_util_next_p2 (priv->width),
				      clutter_util_next_p2 (priv->height)))
	    {
	      priv->is_tiled = TRUE;
	    }
	}

      /* Figure our tiling etc */
      if (priv->is_tiled)
	texture_init_tiles (texture);
    }

  CLUTTER_NOTE (TEXTURE, "set size %ix%i\n",
		priv->width,
		priv->height);

  /* Set Error from this */
  texture_upload_data (texture,
		       copy_data != NULL ? copy_data : data,
		       has_alpha,
		       width,
		       height,
		       rowstride,
		       bpp);

  CLUTTER_ACTOR_SET_FLAGS (CLUTTER_ACTOR (texture), CLUTTER_ACTOR_REALIZED);

  if (size_change)
    {
      g_signal_emit (texture, texture_signals[SIZE_CHANGE],
		     0, priv->width, priv->height);

      if (priv->sync_actor_size)
	clutter_actor_set_size (CLUTTER_ACTOR(texture),
				priv->width,
				priv->height);
    }

  /* rename signal */
  g_signal_emit (texture, texture_signals[PIXBUF_CHANGE], 0);

  /* If resized actor may need resizing but paint() will do this */
  if (CLUTTER_ACTOR_IS_VISIBLE (texture))
    clutter_actor_queue_redraw (CLUTTER_ACTOR (texture));

  if (copy_data != NULL)
    g_free (copy_data);

  return TRUE;
}

/**
 * clutter_texture_set_from_yuv_data:
 * @texture: A #ClutterTexture
 * @data: Image data in YUV type colorspace.
 * @width: Width in pixels of image data.
 * @height: Height in pixels of image data
 * @flags: #ClutterTextureFlags
 * @error: Return location for a #GError, or %NULL.
 *
 * Sets a #ClutterTexture from YUV image data. If an error occurred,
 * %FALSE is returned and @error is set.
 *
 * This function is likely to change in future versions.
 *
 * Return value: %TRUE if the texture was successfully updated
 *
 * Since 0.4.
 **/
gboolean
clutter_texture_set_from_yuv_data   (ClutterTexture     *texture,
				     const guchar       *data,
				     gint                width,
				     gint                height,
				     ClutterTextureFlags flags,
				     GError            **error)
{
  ClutterTexturePrivate *priv;
  gboolean               texture_dirty = TRUE, size_change = FALSE;

  g_return_val_if_fail (CLUTTER_IS_TEXTURE (texture), FALSE);

  if (!clutter_feature_available (CLUTTER_FEATURE_TEXTURE_YUV))
    {
      g_set_error (error, CLUTTER_TEXTURE_ERROR,
                   CLUTTER_TEXTURE_ERROR_NO_YUV,
                   "YUV textures are not supported");
      return FALSE;
    }

  priv = texture->priv;

  if (priv->fbo_source)
    texture_fbo_free_resources (texture);

  /* FIXME: check other image props */
  size_change = (width != priv->width || height != priv->height);
  texture_dirty = size_change || (priv->pixel_format != CGL_YCBCR_MESA);

  priv->width  = width;
  priv->height = height;
  priv->pixel_type = (flags & CLUTTER_TEXTURE_YUV_FLAG_YUV2) ?
                                CGL_UNSIGNED_SHORT_8_8_REV_MESA :
                                CGL_UNSIGNED_SHORT_8_8_MESA;
  priv->pixel_format = CGL_YCBCR_MESA;
  priv->target_type = CGL_TEXTURE_2D;

  if (texture_dirty)
    texture_free_gl_resources (texture);

  if (!priv->tiles)
    {
      priv->tiles = g_new (COGLuint, 1);
      glGenTextures (1, priv->tiles);
    }

  cogl_texture_bind (priv->target_type, priv->tiles[0]);

  cogl_texture_set_filters (priv->target_type,
			    priv->filter_quality ? CGL_LINEAR : CGL_NEAREST,
			    priv->filter_quality ? CGL_LINEAR : CGL_NEAREST);

  if (texture_dirty)
    {
      gint new_width, new_height;

      new_width = clutter_util_next_p2 (priv->width);
      new_height = clutter_util_next_p2 (priv->height);

      /* FIXME: need to check size limits correctly - does not
       * seem to work if correct format and type are used so
       * this is really a guess...
      */
      if (cogl_texture_can_size (CGL_TEXTURE_2D,
				 CGL_RGBA,
				 CGL_UNSIGNED_BYTE,
                                 new_width, new_height))
	{
	  cogl_texture_image_2d (priv->target_type,
				 priv->pixel_format,
				 new_width, new_height,
                                 priv->pixel_format,
				 priv->pixel_type,
				 NULL);
	}
      else
        {
          g_set_error (error, CLUTTER_TEXTURE_ERROR,
                       CLUTTER_TEXTURE_ERROR_OUT_OF_MEMORY,
                       "Unable to allocate a texture of %d by %d pixels",
                       new_width,
                       new_height);

	  return FALSE; 		/* FIXME: add tiling */
       }
    }

  cogl_texture_sub_image_2d (priv->target_type,
			     0,
			     0,
			     priv->width,
			     priv->height,
			     priv->pixel_format,
			     priv->pixel_type,
			     data);

  CLUTTER_ACTOR_SET_FLAGS (CLUTTER_ACTOR (texture), CLUTTER_ACTOR_REALIZED);

  if (size_change)
    {
      g_signal_emit (texture, texture_signals[SIZE_CHANGE],
		     0, priv->width, priv->height);

      if (priv->sync_actor_size)
	clutter_actor_set_size (CLUTTER_ACTOR (texture),
				priv->width,
				priv->height);
    }

  g_signal_emit (texture, texture_signals[PIXBUF_CHANGE], 0);

  if (CLUTTER_ACTOR_IS_VISIBLE (texture))
    clutter_actor_queue_redraw (CLUTTER_ACTOR(texture));

  return TRUE;
}

/**
 * clutter_texture_set_pixbuf:
 * @texture: A #ClutterTexture
 * @pixbuf: A #GdkPixbuf
 * @error: Return location for a #GError, or %NULL
 *
 * Sets a  #ClutterTexture image data from a #GdkPixbuf. In case of
 * failure, %FALSE is returned and @error is set.
 *
 * Return value: %TRUE if the pixbuf was successfully set
 *
 * Since: 0.4
 */
gboolean
clutter_texture_set_pixbuf (ClutterTexture *texture,
                            GdkPixbuf      *pixbuf,
			    GError        **error)
{
  ClutterTexturePrivate *priv;

  g_return_val_if_fail (pixbuf != NULL, FALSE);

  priv = texture->priv;

  return clutter_texture_set_from_rgb_data (texture,
					    gdk_pixbuf_get_pixels (pixbuf),
					    gdk_pixbuf_get_has_alpha (pixbuf),
					    gdk_pixbuf_get_width (pixbuf),
					    gdk_pixbuf_get_height (pixbuf),
					    gdk_pixbuf_get_rowstride (pixbuf),
					    4,
					    0,
					    error);
}

/**
 * clutter_texture_new_from_pixbuf:
 * @pixbuf: A #GdkPixbuf
 *
 * Creates a new #ClutterTexture object.
 *
 * Return value: A newly created #ClutterTexture object.
 **/
ClutterActor*
clutter_texture_new_from_pixbuf (GdkPixbuf *pixbuf)
{
  return g_object_new (CLUTTER_TYPE_TEXTURE, "pixbuf", pixbuf, NULL);
}

/**
 * clutter_texture_new:
 *
 * Creates a new empty #ClutterTexture object.
 *
 * Return value: A newly created #ClutterTexture object.
 **/
ClutterActor *
clutter_texture_new (void)
{
  return g_object_new (CLUTTER_TYPE_TEXTURE, NULL);
}

/**
 * clutter_texture_get_base_size:
 * @texture: A #ClutterTexture
 * @width:   Pointer to gint to be populated with width value if non NULL.
 * @height:  Pointer to gint to be populated with height value if non NULL.
 *
 * Gets the size in pixels of the untransformed underlying texture pixbuf data.
 *
 **/
void 				/* FIXME: rename to get_image_size */
clutter_texture_get_base_size (ClutterTexture *texture,
			       gint           *width,
			       gint           *height)
{
  g_return_if_fail (CLUTTER_IS_TEXTURE (texture));

  /* Attempt to realize, mainly for subclasses ( such as labels )
   * which may not create pixbuf data and thus base size until
   * realization happens.
   */
  if (!CLUTTER_ACTOR_IS_REALIZED (texture))
    clutter_actor_realize (CLUTTER_ACTOR (texture));

  if (width)
    *width = texture->priv->width;

  if (height)
    *height = texture->priv->height;

}

/**
 * clutter_texture_bind_tile:
 * @texture: A #ClutterTexture
 * @index_: Tile index to bind
 *
 * Proxies a call to glBindTexture to bind an internal 'tile'.
 *
 * This function is only useful for #ClutterTexture sub-classes
 * and should never be called by an application.
 **/
void
clutter_texture_bind_tile (ClutterTexture *texture,
                           gint            index_)
{
  ClutterTexturePrivate *priv;

  g_return_if_fail (CLUTTER_IS_TEXTURE (texture));

  priv = texture->priv;

  cogl_texture_bind (priv->target_type, priv->tiles[index_]);
}

/**
 * clutter_texture_get_n_tiles:
 * @texture: A #ClutterTexture
 * @n_x_tiles: Location to store number of tiles in horizonally axis
 * @n_y_tiles: Location to store number of tiles in vertical axis
 *
 * Retreives internal tile dimensioning.
 *
 * This function is only useful for #ClutterTexture sub-classes
 * and should never be called by an application.
 **/
void
clutter_texture_get_n_tiles (ClutterTexture *texture,
			     gint           *n_x_tiles,
			     gint           *n_y_tiles)
{
  g_return_if_fail (CLUTTER_IS_TEXTURE (texture));

  if (n_x_tiles)
    *n_x_tiles = texture->priv->n_x_tiles;

  if (n_y_tiles)
    *n_y_tiles = texture->priv->n_y_tiles;

}

/**
 * clutter_texture_get_x_tile_detail:
 * @texture: A #ClutterTexture
 * @x_index: X index of tile to query
 * @pos: Location to store tile's X position
 * @size: Location to store tile's horizontal size in pixels
 * @waste: Location to store tile's horizontal wastage in pixels
 *
 * Retreives details of a tile on the X axis.
 *
 * This function is only useful for #ClutterTexture sub-classes
 * and should never be called by an application.
 **/
void
clutter_texture_get_x_tile_detail (ClutterTexture *texture,
				   gint            x_index,
				   gint           *pos,
				   gint           *size,
				   gint           *waste)
{
  ClutterTexturePrivate *priv;

  g_return_if_fail (CLUTTER_IS_TEXTURE (texture));
  g_return_if_fail (x_index < texture->priv->n_x_tiles);

  priv = texture->priv;

  if (pos)
    *pos = priv->x_tiles[x_index].pos;

  if (size)
    *size = priv->x_tiles[x_index].size;

  if (waste)
    *waste = priv->x_tiles[x_index].waste;
}

/**
 * clutter_texture_get_y_tile_detail:
 * @texture: A #ClutterTexture
 * @y_index: Y index of tile to query
 * @pos: Location to store tile's Y position
 * @size: Location to store tile's vertical size in pixels
 * @waste: Location to store tile's vertical wastage in pixels
 *
 * Retreives details of a tile on the Y axis.
 *
 * This function is only useful for #ClutterTexture sub-classes
 * and should never be called by an application.
 **/
void
clutter_texture_get_y_tile_detail (ClutterTexture *texture,
				   gint            y_index,
				   gint           *pos,
				   gint           *size,
				   gint           *waste)
{
  ClutterTexturePrivate *priv;

  g_return_if_fail (CLUTTER_IS_TEXTURE (texture));

  priv = texture->priv;

  g_return_if_fail (y_index < priv->n_y_tiles);

  if (pos)
    *pos = priv->y_tiles[y_index].pos;

  if (size)
    *size = priv->y_tiles[y_index].size;

  if (waste)
    *waste = priv->y_tiles[y_index].waste;
}

/**
 * clutter_texture_has_generated_tiles:
 * @texture: A #ClutterTexture
 *
 * Checks if #ClutterTexture has generated underlying GL texture tiles.
 *
 * This function is only useful for #ClutterTexture sub-classes
 * and should never be called by an application.
 *
 * Return value: %TRUE if texture has pre-generated GL tiles.
 **/
gboolean
clutter_texture_has_generated_tiles (ClutterTexture *texture)
{
  g_return_val_if_fail (CLUTTER_IS_TEXTURE (texture), FALSE);

  return texture->priv->tiles != NULL;
}

/**
 * clutter_texture_is_tiled:
 * @texture: A #ClutterTexture
 *
 * Checks if #ClutterTexture is tiled.
 *
 * This function is only useful for #ClutterTexture sub-classes
 * and should never be called by an application.
 *
 * Return value: %TRUE if texture is tiled
 **/
gboolean
clutter_texture_is_tiled (ClutterTexture *texture)
{
  g_return_val_if_fail (CLUTTER_IS_TEXTURE (texture), FALSE);

  return texture->priv->is_tiled;
}


static void inline
texture_update_data (ClutterTexture *texture,
		     const guchar   *data,
		     gboolean        has_alpha,
                     gint            x_0,
                     gint            y_0,
		     gint            width,
		     gint            height,
		     gint            rowstride,
		     gint            bpp)
{
  ClutterTexturePrivate *priv;
  gint x, y;
  gint i = 0;
  gboolean create_textures = FALSE;

  priv = texture->priv;

  g_return_if_fail (data != NULL);

  CLUTTER_MARK();

  if (!priv->is_tiled)
    {
      g_assert (priv->tiles);

      CLUTTER_NOTE (TEXTURE, "syncing for single tile");

      cogl_texture_bind (priv->target_type, priv->tiles[0]);
      cogl_texture_set_alignment (priv->target_type, 4, rowstride/4);

      cogl_texture_set_filters
	(priv->target_type,
	 priv->filter_quality ? CGL_LINEAR : CGL_NEAREST,
	 priv->filter_quality ? CGL_LINEAR : CGL_NEAREST);

      cogl_texture_set_wrap (priv->target_type,
			     priv->repeat_x ? CGL_REPEAT : CGL_CLAMP_TO_EDGE,
			     priv->repeat_y ? CGL_REPEAT : CGL_CLAMP_TO_EDGE);

      priv->filter_quality = 1;

      cogl_texture_sub_image_2d (priv->target_type,
				 x_0,
				 y_0,
				 width,
				 height,
				 priv->pixel_format,
				 priv->pixel_type,
				 data);
      return;
    }

  /* Multiple tiled texture */

  CLUTTER_NOTE (TEXTURE,
                "syncing for multiple tiles for %ix%i pixbuf",
		priv->width, priv->height);

  g_return_if_fail (priv->x_tiles != NULL && priv->y_tiles != NULL);

  if (priv->tiles == NULL)
    {
      g_assert (0);
      priv->tiles = g_new (COGLuint, priv->n_x_tiles * priv->n_y_tiles);
      glGenTextures (priv->n_x_tiles * priv->n_y_tiles, priv->tiles);
      create_textures = TRUE;
    }

  for (x = 0; x < priv->n_x_tiles; x++)
    for (y = 0; y < priv->n_y_tiles; y++)
      {
        gint master_offset_x;
        gint effective_x;
        gint effective_width;

        gint master_offset_y;
        gint effective_y;
        gint effective_height;

/*
 *  -- first tile --
 *  |--------------------- priv->width ------------------------------|
 *  | <- priv->x_tiles[x].pos
 *  |-----------| <- priv->x_tiles[x].size
 *  |-------| <- x_0
 *          |------------| <- width
 *  |--------------------| <- x_0 + width
 *  |-------| <- master_offset = -8
 *  |-------| <- effective_x = 8
 *          |---| <- effective_width
 *
 *  -- second tile ---
 *
 *  |--------------------- priv->width ------------------------------|
 *  |-----------|  <- priv->x_tiles[x].pos
 *              |-----------| <- priv->x_tiles[x].size (src_w)
 *  |-------| <- x_0
 *          |------------| <- width
 *  |--------------------| <- x_0 + width
 *          |---| <- master_offset = 4
 *              | <- effective_x (0 in between)
 *              |--------| <- effective_width
 *
 *         XXXXXXXXXXXXXX    <- master
 *  |___________|___________|___________|___________|___________|_____%%%%%%|
 */

        gint src_w, src_h;

	src_w = priv->x_tiles[x].size;
	src_h = priv->y_tiles[y].size;

        /* skip tiles that do not intersect the updated region */
        if ((priv->x_tiles[x].pos + src_w < x_0 ||
             priv->y_tiles[y].pos + src_h < y_0 ||
             priv->x_tiles[x].pos >= x_0 + width ||
             priv->y_tiles[y].pos >= y_0 + height))
          {
            i++;
            continue;
          }

        master_offset_x = priv->x_tiles[x].pos - x_0;

        if (priv->x_tiles[x].pos > x_0)
          effective_x = 0;
        else
          effective_x = x_0 - priv->x_tiles[x].pos;

        effective_width = (x_0 + width) - priv->x_tiles[x].pos;

        if (effective_width > src_w - effective_x)
          effective_width = src_w - effective_x;

        master_offset_y = priv->y_tiles[y].pos - y_0;

        if (priv->y_tiles[y].pos > y_0)
          effective_y = 0;
        else
          effective_y = y_0 - priv->y_tiles[y].pos;

        effective_height = (y_0 + height) - priv->y_tiles[y].pos;
        if (effective_height > src_h - effective_y)
          effective_height = src_h - effective_y;

        if (master_offset_x < 0)
          master_offset_x = 0;
        if (master_offset_y < 0)
          master_offset_y = 0;

        if (master_offset_x + effective_width > width)
          effective_width = width - master_offset_x;
        if (master_offset_y + effective_height > height)
          effective_height = height - master_offset_y;

	cogl_texture_bind (priv->target_type, priv->tiles[i]);

	cogl_texture_set_alignment (priv->target_type, 4, rowstride/4);

	cogl_texture_set_filters
	  (priv->target_type,
	   priv->filter_quality ? CGL_LINEAR : CGL_NEAREST,
	   priv->filter_quality ? CGL_LINEAR : CGL_NEAREST);

	cogl_texture_set_wrap (priv->target_type,
			       priv->repeat_x ? CGL_REPEAT : CGL_CLAMP_TO_EDGE,
			       priv->repeat_y ? CGL_REPEAT : CGL_CLAMP_TO_EDGE);
	cogl_texture_sub_image_2d (priv->target_type,
				   effective_x,
				   effective_y,
				   effective_width,
				   effective_height,
				   priv->pixel_format,
				   priv->pixel_type,
                                   data + (master_offset_y * priv->width + master_offset_x) * 4);

	i++;
      }
}

/**
 * clutter_texture_set_area_from_rgb_data:
 * @texture: A #ClutterTexture
 * @data: Image data in RGB type colorspace.
 * @has_alpha: Set to TRUE if image data has an alpha channel.
 * @x: X coordinate of upper left corner of region to update.
 * @y: Y coordinate of upper left corner of region to update.
 * @width: Width in pixels of region to update.
 * @height: Height in pixels of region to update.
 * @rowstride: Distance in bytes between row starts on source buffer.
 * @bpp: bytes per pixel ( Currently only 4 supported )
 * @flags: #ClutterTextureFlags
 * @error: return location for a #GError, or %NULL
 *
 * Updates a sub-region of the pixel data in a #ClutterTexture.
 *
 * Return value: %TRUE on success, %FALSE on failure.
 *
 * Since 0.6.
 */
gboolean
clutter_texture_set_area_from_rgb_data (ClutterTexture     *texture,
                                        const guchar       *data,
                                        gboolean            has_alpha,
                                        gint                x,
                                        gint                y,
                                        gint                width,
                                        gint                height,
                                        gint                rowstride,
                                        gint                bpp,
                                        ClutterTextureFlags flags,
                                        GError            **error)
{
  ClutterTexturePrivate *priv;
  guchar                *copy_data = NULL;

  priv = texture->priv;

  if (!texture_prepare_upload (FALSE, texture, data, has_alpha,
			       width, height, rowstride,
                               bpp, flags, &copy_data, NULL, NULL))
    {
      return FALSE;
    }

  texture_update_data (texture,
                       copy_data != NULL ? copy_data : data,
                       has_alpha,
                       x, y,
                       width, height,
                       rowstride,
                       bpp);

  CLUTTER_ACTOR_SET_FLAGS (CLUTTER_ACTOR (texture), CLUTTER_ACTOR_REALIZED);

  /* rename signal */
  g_signal_emit (texture, texture_signals[PIXBUF_CHANGE], 0);

  if (CLUTTER_ACTOR_IS_VISIBLE (texture))
    clutter_actor_queue_redraw (CLUTTER_ACTOR (texture));

  if (copy_data != NULL)
    g_free (copy_data);

  return TRUE;
}

static void
on_fbo_source_size_change (GObject          *object,
                           GParamSpec       *param_spec,
                           ClutterTexture   *texture)
{
  ClutterTexturePrivate *priv = texture->priv;
  guint                  w, h;

  clutter_actor_get_abs_size (priv->fbo_source, &w, &h);

  if (w != priv->width || h != priv->height)
    {
      if (!cogl_texture_can_size (CGL_TEXTURE_RECTANGLE_ARB,
                                  CGL_RGBA, PIXEL_TYPE, w, h))
        {
          g_warning ("%s: Offscreen source too large for texture", G_STRLOC);
          return;
        }

      /* tear down the FBO */
      cogl_offscreen_destroy (priv->fbo_handle);

      texture_free_gl_resources (texture);

      priv->width        = w;
      priv->height       = h;

      priv->tiles = g_new (COGLuint, 1);

      /* FIXME: needs a cogl wrapper */
      glGenTextures (1, priv->tiles);

      cogl_texture_bind (priv->target_type, priv->tiles[0]);
      cogl_texture_image_2d (priv->target_type,
                             CGL_RGBA,
                             w,
                             h,
                             priv->pixel_format,
                             priv->pixel_type,
                             NULL);

      priv->fbo_handle = cogl_offscreen_create (priv->tiles[0]);

      clutter_actor_set_size (CLUTTER_ACTOR(texture), w, h);
    }
}

static void
on_fbo_parent_change (ClutterActor        *actor,
                      ClutterActor        *old_parent,
                      ClutterTexture      *texture)
{
  ClutterActor        *parent = CLUTTER_ACTOR(texture);

  while ((parent = clutter_actor_get_parent (parent)) != NULL)
    if (parent == actor)
      {
        g_warning ("Offscreen texture is ancestor of source!");
        /* Desperate but will avoid infinite loops */
        clutter_actor_unparent (actor);
      }
}


/**
 * clutter_texture_new_from_actor:
 * @actor: A source #ClutterActor
 *
 * Creates a new #ClutterTexture object with its source a prexisting
 * actor (and associated children). The textures content will contain
 * 'live' redirected output of the actors scene.
 *
 * Note this function is intented as a utility call for uniformly applying
 * shaders to groups and other potential visual effects. It requires that
 * both %CLUTTER_FEATURE_TEXTURE_RECTANGLE and %CLUTTER_FEATURE_OFFSCREEN
 * features are supported by the current backend and the target system.
 *
 * Some tips on usage:
 *
 * <itemizedlist>
 *   <listitem>
 *     <para>The source actor must be made visible (i.e by calling
 *     #clutter_actor_show). The source actor does not however have to
 *     have a parent.</para>
 *   </listitem>
 *   <listitem>
 *     <para>Avoid reparenting the source with the created texture.</para>
 *   </listitem>
 *   <listitem>
 *     <para>A group can be padded with a transparent rectangle as to
 *     provide a border to contents for shader output (blurring text
 *     for example).</para>
 *   </listitem>
 *   <listitem>
 *     <para>The texture will automatically resize to contain a further
 *     transformed source. However, this involves overhead and can be
 *     avoided by placing the source actor in a bounding group
 *     sized large enough to contain any child tranformations.</para>
 *   </listitem>
 *   <listitem>
 *     <para>Uploading pixel data to the texture (e.g by using
 *     clutter_actor_set_pixbuf()) will destroy the offscreen texture data
 *     and end redirection.</para>
 *   </listitem>
 *   <listitem>
 *     <para>clutter_texture_get_pixbuf() can be used to read the offscreen
 *     texture pixels into a pixbuf.</para>
 *   </listitem>
 * </itemizedlist>
 *
 * Return value: A newly created #ClutterTexture object, or %NULL on failure.
 *
 * Since: 0.6
 */
ClutterActor *
clutter_texture_new_from_actor (ClutterActor *actor)
{
  ClutterTexture        *texture;
  ClutterTexturePrivate *priv;
  guint                  w, h;

  g_return_val_if_fail (CLUTTER_IS_ACTOR (actor), NULL);

  /* FIXME: Some error result on below could be useful */
  if (clutter_feature_available (CLUTTER_FEATURE_TEXTURE_RECTANGLE) == FALSE)
    return NULL;

  if (clutter_feature_available (CLUTTER_FEATURE_OFFSCREEN) == FALSE)
    return NULL;

  if (!CLUTTER_ACTOR_IS_REALIZED (actor))
    {
      clutter_actor_realize (actor);

      if (!CLUTTER_ACTOR_IS_REALIZED (actor))
	return NULL;
    }

  clutter_actor_get_abs_size (actor, &w, &h);

  if (w == 0 || h == 0)
    return NULL;

  if (!cogl_texture_can_size (CGL_TEXTURE_RECTANGLE_ARB,
			      CGL_RGBA, PIXEL_TYPE, w, h))
    return NULL;

  /* Hopefully now were good.. */
  texture = g_object_new (CLUTTER_TYPE_TEXTURE, NULL);

  priv = texture->priv;

  priv->fbo_source = g_object_ref(actor);

  /* Connect up any signals which could change our underlying size */
  g_signal_connect (actor,
                    "notify::width",
                    G_CALLBACK(on_fbo_source_size_change),
                    texture);
  g_signal_connect (actor,
                    "notify::height",
                    G_CALLBACK(on_fbo_source_size_change),
                    texture);
  g_signal_connect (actor,
                    "notify::scale-x",
                    G_CALLBACK(on_fbo_source_size_change),
                    texture);
  g_signal_connect (actor,
                    "notify::scale-y",
                    G_CALLBACK(on_fbo_source_size_change),
                    texture);
  g_signal_connect (actor,
                    "notify::rotation-angle-x",
                    G_CALLBACK(on_fbo_source_size_change),
                    texture);
  g_signal_connect (actor,
                    "notify::rotation-angle-y",
                    G_CALLBACK(on_fbo_source_size_change),
                    texture);
  g_signal_connect (actor,
                    "notify::rotation-angle-z",
                    G_CALLBACK(on_fbo_source_size_change),
                    texture);

  /* And a warning if the source becomes a child of the texture */
  g_signal_connect (actor,
                    "parent-set",
                    G_CALLBACK(on_fbo_parent_change),
                    texture);

  priv->width        = w;
  priv->height       = h;
  priv->target_type  = CGL_TEXTURE_RECTANGLE_ARB;
  priv->pixel_format = CGL_RGBA;
  priv->pixel_type   = PIXEL_TYPE;
  priv->is_tiled     = 0;

  clutter_actor_set_size (CLUTTER_ACTOR(texture), priv->width, priv->height);

  return CLUTTER_ACTOR(texture);
}

static void
texture_fbo_free_resources (ClutterTexture *texture)
{
  ClutterTexturePrivate *priv;

  priv = texture->priv;

  CLUTTER_MARK();

  if (priv->fbo_source != NULL)
    {
      g_signal_handlers_disconnect_by_func
                            (priv->fbo_source,
                             G_CALLBACK(on_fbo_parent_change),
                             texture);

      g_signal_handlers_disconnect_by_func
                            (priv->fbo_source,
                             G_CALLBACK(on_fbo_source_size_change),
                             texture);

      g_object_unref (priv->fbo_source);

      priv->fbo_source = NULL;
    }

  if (priv->fbo_handle != 0)
    {
      cogl_offscreen_destroy (priv->fbo_handle);
      priv->fbo_handle = 0;
    }
}