/*
 * 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_from_file() 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 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_set_size(). 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-scriptable.h"
#include "clutter-debug.h"
#include "clutter-fixed.h"
#include "clutter-enum-types.h"

#include "cogl/cogl.h"

static void clutter_scriptable_iface_init (ClutterScriptableIface *iface);

G_DEFINE_TYPE_WITH_CODE (ClutterTexture,
                         clutter_texture,
                         CLUTTER_TYPE_ACTOR,
                         G_IMPLEMENT_INTERFACE (CLUTTER_TYPE_SCRIPTABLE,
                                                clutter_scriptable_iface_init));

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

struct _ClutterTexturePrivate
{
  gint                         width;
  gint                         height;
  gint                         max_tile_waste;
  ClutterTextureQuality        filter_quality;
  CoglHandle                   material;
  CoglHandle                   fbo_texture;
  gboolean                     no_slice;

  ClutterActor                *fbo_source;
  CoglHandle                   fbo_handle;

  /* Non video memory copy of image data */
  guint                        local_data_width, local_data_height;
  guint                        local_data_rowstride;
  guint                        local_data_has_alpha;
  guchar                      *local_data;

  guint                        sync_actor_size : 1;
  guint                        repeat_x : 1;
  guint                        repeat_y : 1;
  guint                        in_dispose : 1;
  guint                        keep_aspect_ratio : 1;
  guint                        load_async : 1;
  
  GThread                     *load_thread;
  guint                        load_idle;
  gchar                       *load_filename;
  CoglBitmap                  *load_bitmap;
  GError                      *load_error;
  gboolean                     abort;
};

enum
{
  PROP_0,
  PROP_NO_SLICE,
  PROP_MAX_TILE_WASTE,
  PROP_PIXEL_FORMAT,		/* Texture format */
  PROP_SYNC_SIZE,
  PROP_REPEAT_Y,
  PROP_REPEAT_X,
  PROP_FILTER_QUALITY,
  PROP_COGL_TEXTURE,
  PROP_COGL_MATERIAL,
  PROP_FILENAME,
  PROP_KEEP_ASPECT_RATIO,
  PROP_LOAD_ASYNC
};

enum
{
  SIZE_CHANGE,
  PIXBUF_CHANGE,
  LOAD_SUCCESS,
  LOAD_FINISHED,
  
  LAST_SIGNAL
};

static int texture_signals[LAST_SIGNAL] = { 0 };

static void
texture_fbo_free_resources (ClutterTexture *texture);

static void
clutter_texture_save_to_local_data (ClutterTexture *texture);

static void
clutter_texture_load_from_local_data (ClutterTexture *texture);

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

static inline void
clutter_texture_quality_to_filters (ClutterTextureQuality  quality,
                                    gint                  *min_filter_p,
                                    gint                  *mag_filter_p)
{
  gint min_filter, mag_filter;

  switch (quality)
    {
    case CLUTTER_TEXTURE_QUALITY_LOW:
      min_filter = CGL_NEAREST;
      mag_filter = CGL_NEAREST;
      break;

    case CLUTTER_TEXTURE_QUALITY_MEDIUM:
      min_filter = CGL_LINEAR;
      mag_filter = CGL_LINEAR;
      break;

    case CLUTTER_TEXTURE_QUALITY_HIGH:
      min_filter = CGL_LINEAR_MIPMAP_LINEAR;
      mag_filter = CGL_LINEAR;
      break;
    }

  if (min_filter_p)
    *min_filter_p = min_filter;

  if (mag_filter_p)
    *mag_filter_p = mag_filter;
}

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

  CLUTTER_MARK();

  if (priv->material != COGL_INVALID_HANDLE)
    cogl_material_remove_layer (priv->material, 0);
}

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

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

  if (priv->material == COGL_INVALID_HANDLE)
    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) ||
      priv->in_dispose)
    return;

  CLUTTER_MARK();

  if (priv->fbo_source != COGL_INVALID_HANDLE)
    {
      /* Free up our fbo handle and texture resources, realize will recreate */
      cogl_offscreen_unref (priv->fbo_handle);
      priv->fbo_handle = COGL_INVALID_HANDLE;
      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_data == NULL)
	{
	  clutter_texture_save_to_local_data (texture);
	  CLUTTER_NOTE (TEXTURE, "moved pixels into system 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)
    {
      CoglTextureFlags flags = COGL_TEXTURE_NONE;
      gint min_filter, mag_filter;
      gint max_waste = -1;

      /* Handle FBO's */

      if (priv->fbo_texture != COGL_INVALID_HANDLE)
	cogl_texture_unref (priv->fbo_texture);

      if (!priv->no_slice)
        max_waste = priv->max_tile_waste;

      if (priv->filter_quality == CLUTTER_TEXTURE_QUALITY_HIGH)
        flags |= COGL_TEXTURE_AUTO_MIPMAP;

      priv->fbo_texture =
        cogl_texture_new_with_size (priv->width,
                                    priv->height,
                                    max_waste, flags,
                                    COGL_PIXEL_FORMAT_RGBA_8888);

      clutter_texture_quality_to_filters (priv->filter_quality,
                                          &min_filter,
                                          &mag_filter);

      cogl_texture_set_filters (priv->fbo_texture, min_filter, mag_filter);

      priv->fbo_handle = cogl_offscreen_new_to_texture (priv->fbo_texture);

      if (priv->fbo_handle == COGL_INVALID_HANDLE)
        {
          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_data != NULL)
    {
      /* Move any local image data we have from unrealization
       * back into video memory.
      */
      clutter_texture_load_from_local_data (texture);
    }
  else
    {
      if (clutter_feature_available (CLUTTER_FEATURE_TEXTURE_READ_PIXELS))
	{
	  /* Dont allow realization with no data - note set_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_get_preferred_width (ClutterActor *self,
                                     ClutterUnit   for_height,
                                     ClutterUnit  *min_width_p,
                                     ClutterUnit  *natural_width_p)
{
  ClutterTexture *texture = CLUTTER_TEXTURE (self);
  ClutterTexturePrivate *priv = texture->priv;

  /* Min request is always 0 since we can scale down or clip */
  if (min_width_p)
    *min_width_p = 0;

  if (priv->sync_actor_size)
    {
      if (natural_width_p)
        {
          if (!priv->keep_aspect_ratio ||
              for_height < 0 ||
              priv->height <= 0)
            {
              *natural_width_p = CLUTTER_UNITS_FROM_DEVICE (priv->width);
            }
          else
            {
              /* Set the natural width so as to preserve the aspect ratio */
              gfloat ratio, height;

              ratio = (float)(priv->width) / (float)(priv->height);

              height = CLUTTER_UNITS_TO_FLOAT (for_height);

              *natural_width_p =
                CLUTTER_UNITS_FROM_FLOAT (ratio * height);
            }
        }
    }
  else
    {
      if (natural_width_p)
        *natural_width_p = 0;
    }
}

static void
clutter_texture_get_preferred_height (ClutterActor *self,
                                      ClutterUnit   for_width,
                                      ClutterUnit  *min_height_p,
                                      ClutterUnit  *natural_height_p)
{
  ClutterTexture *texture = CLUTTER_TEXTURE (self);
  ClutterTexturePrivate *priv = texture->priv;

  /* Min request is always 0 since we can scale down or clip */
  if (min_height_p)
    *min_height_p = 0;

  if (priv->sync_actor_size)
    {
      if (natural_height_p)
        {
          if (!priv->keep_aspect_ratio ||
              for_width < 0 ||
              priv->width <= 0)
            {
              *natural_height_p = CLUTTER_UNITS_FROM_DEVICE (priv->height);
            }
          else
            {
              /* Set the natural height so as to preserve the aspect ratio */
              gfloat ratio, width;

              ratio = (float)(priv->height) / (float)(priv->width);

              width = CLUTTER_UNITS_TO_FLOAT (for_width);

              *natural_height_p =
                CLUTTER_UNITS_FROM_FLOAT (ratio * width);
            }
        }
    }
  else
    {
      if (natural_height_p)
        *natural_height_p = 0;
    }
}

static void
clutter_texture_allocate (ClutterActor          *self,
			  const ClutterActorBox *box,
			  gboolean               origin_changed)
{
  ClutterTexturePrivate *priv = CLUTTER_TEXTURE (self)->priv;

  /* chain up to set actor->allocation */
  CLUTTER_ACTOR_CLASS (clutter_texture_parent_class)->allocate (self, box,
								origin_changed);

  /* If we adopted the source fbo then allocate that at its preferred
     size */
  if (priv->fbo_source && clutter_actor_get_parent (priv->fbo_source) == self)
    clutter_actor_allocate_preferred_size (priv->fbo_source, origin_changed);
}

static void
clutter_texture_set_fbo_projection (ClutterActor *self)
{
  ClutterTexturePrivate *priv = CLUTTER_TEXTURE (self)->priv;
  ClutterVertex verts[4];
  gfloat viewport[4];
  ClutterUnit x_min, x_max, y_min, y_max;
  gfloat tx_min, tx_max, ty_min, ty_max;
  gfloat tan_angle, near_size;
  ClutterPerspective perspective;
  ClutterStage *stage;
  int i;

  /* Get the bounding rectangle of the source as drawn in screen
     coordinates */
  clutter_actor_get_abs_allocation_vertices (priv->fbo_source, verts);

  x_min = x_max = verts[0].x;
  y_min = y_max = verts[0].y;

  for (i = 1; i < G_N_ELEMENTS (verts); ++i)
    {
      if (verts[i].x < x_min)
	x_min = verts[i].x;

      if (verts[i].x > x_max)
	x_max = verts[i].x;

      if (verts[i].y < y_min)
	y_min = verts[i].y;

      if (verts[i].y > y_max)
	y_max = verts[i].y;
    }

  stage = CLUTTER_STAGE (clutter_actor_get_stage (self));
  clutter_stage_get_perspective (stage, &perspective);

  /* Convert the coordinates back to [-1,1] range */
  cogl_get_viewport (viewport);

  tx_min = (CLUTTER_UNITS_TO_FLOAT (x_min) / viewport[2])
         * 2 - 1.0;
  tx_max = (CLUTTER_UNITS_TO_FLOAT (x_max) / viewport[2])
         * 2 - 1.0;
  ty_min = (CLUTTER_UNITS_TO_FLOAT (y_min) / viewport[3])
         * 2 - 1.0;
  ty_max = (CLUTTER_UNITS_TO_FLOAT (y_max) / viewport[3])
         * 2 - 1.0;

  /* Set up a projection matrix so that the actor will be projected as
     if it was drawn at its original location */
  tan_angle = tanf ((perspective.fovy / 2) * (G_PI / 180.0));
  near_size = perspective.z_near * tan_angle;

  cogl_frustum ((tx_min * near_size),
                (tx_max * near_size),
                (-ty_min * near_size),
                (-ty_max * near_size),
                perspective.z_near,
                perspective.z_far);
}

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;
  CoglColor       transparent_col;
  gfloat          t_w, t_h;
  guint8          paint_opacity = clutter_actor_get_paint_opacity (self);

  if (paint_opacity == 0)
    {
      /* Bail early if painting the actor would be a no-op, custom actors that
       * might cause a lot of work/state changes should all do this.
       */
      return;
    }

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

  if (priv->fbo_handle != COGL_INVALID_HANDLE)
    {
      ClutterMainContext *context;
      ClutterShader      *shader = NULL;
      ClutterActor       *stage = NULL;
      ClutterPerspective  perspective;

      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);

      /* Redirect drawing to the fbo */
      cogl_draw_buffer (COGL_OFFSCREEN_BUFFER, priv->fbo_handle);

      if ((stage = clutter_actor_get_stage (self)))
	{
	  guint               stage_width, stage_height;
	  ClutterActor       *source_parent;

	  clutter_stage_get_perspective (CLUTTER_STAGE (stage), &perspective);
	  clutter_actor_get_size (stage, &stage_width, &stage_height);

	  /* Use below to set the modelview matrix as if the viewport
	     was still the same size as the stage */
	  cogl_setup_viewport (stage_width, stage_height,
			       perspective.fovy,
			       perspective.aspect,
			       perspective.z_near,
			       perspective.z_far);
	  /* Use a projection matrix that makes the actor appear as it
	     would if it was rendered at its normal screen location */
	  clutter_texture_set_fbo_projection (self);
	  /* Reset the viewport to the size of the FBO */
	  cogl_viewport (priv->width, priv->height);
	  /* Reapply the source's parent transformations */
	  if ((source_parent = clutter_actor_get_parent (priv->fbo_source)))
	    _clutter_actor_apply_modelview_transform_recursive (source_parent,
								NULL);
	}

      /* cogl_clear is called to clear the buffers */
      cogl_color_set_from_4ub (&transparent_col, 0, 0, 0, 0);
      cogl_clear (&transparent_col);
      cogl_disable_fog ();

      /* Clear the clipping stack so that if the FBO actor is being
	 clipped then it won't affect drawing the source */
      cogl_clip_stack_save ();

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

      cogl_clip_stack_restore ();

      /* Restore drawing to the frame buffer */
      cogl_draw_buffer (COGL_WINDOW_BUFFER, COGL_INVALID_HANDLE);

      /* Restore the perspective matrix using cogl_perspective so that
	 the inverse matrix will be right */
      cogl_perspective (perspective.fovy,
                        perspective.aspect,
                        perspective.z_near,
                        perspective.z_far);

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

  /* A clone may need to fire above if were a TFP/FBO but not visible.
   * Ultimatly needs some reworking with maybe an extra prepare_paint
   * method or some such.
  */
  if (CLUTTER_PRIVATE_FLAGS(self) & CLUTTER_TEXTURE_IN_CLONE_PAINT)
    return;

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

  cogl_material_set_color4ub (priv->material, 0xff, 0xff, 0xff, paint_opacity);

  clutter_actor_get_allocation_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));

  if (priv->repeat_x && priv->width > 0)
    t_w = (float) (x_2 - x_1) / (float) (priv->width);
  else
    t_w = 1.0;

  if (priv->repeat_y && priv->height > 0)
    t_h = (float) (y_2 - y_1) / (float) (priv->height);
  else
    t_h = 1.0;

  /* Paint will have translated us */
  cogl_set_source (priv->material);
  cogl_rectangle_with_texture_coords (0, 0,
			              (float) (x_2 - x_1),
			              (float) (y_2 - y_1),
			              0, 0, t_w, t_h);
}

/*
 * clutter_texture_async_load_cancel:
 * @texture: a #ClutterTexture
 *
 * Cancels an asynchronous loading operation, whether done
 * with threads enabled or just using the main loop
 */
static void
clutter_texture_async_load_cancel (ClutterTexture *texture)
{
  ClutterTexturePrivate *priv = texture->priv;

  if (priv->load_thread)
    {
      priv->abort = TRUE;
      g_thread_join (priv->load_thread);
      priv->load_thread = NULL;
    }

  if (priv->load_idle)
    {
      g_source_remove (priv->load_idle);
      priv->load_idle = 0;
    }

  if (priv->load_error)
    {
      g_error_free (priv->load_error);
      priv->load_error = NULL;
    }

  if (priv->load_bitmap)
    {
      cogl_bitmap_free (priv->load_bitmap);
      priv->load_bitmap = NULL;
    }

  g_free (priv->load_filename);
}

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

  priv = texture->priv;

  /* mark that we are in dispose, so when the parent class'
   * dispose implementation will call unrealize on us we'll
   * not try to copy back the resources from video memory
   * to system memory
   */
  if (!priv->in_dispose)
    priv->in_dispose = TRUE;

  texture_free_gl_resources (texture);
  texture_fbo_free_resources (texture);

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

  clutter_texture_async_load_cancel (texture);

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

static void
clutter_texture_finalize (GObject *object)
{
  ClutterTexture *texture = CLUTTER_TEXTURE (object);
  ClutterTexturePrivate *priv = texture->priv;

  if (priv->material != COGL_INVALID_HANDLE)
    {
      cogl_material_unref (priv->material);
      priv->material = COGL_INVALID_HANDLE;
    }

  G_OBJECT_CLASS (clutter_texture_parent_class)->finalize (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_MAX_TILE_WASTE:
      clutter_texture_set_max_tile_waste (texture,
					  g_value_get_int (value));
      break;
    case PROP_SYNC_SIZE:
      priv->sync_actor_size = g_value_get_boolean (value);
      clutter_actor_queue_relayout (CLUTTER_ACTOR (texture));
      break;
    case PROP_REPEAT_X:
      if (priv->repeat_x != g_value_get_boolean (value))
	{
	  priv->repeat_x = !priv->repeat_x;
	  if (CLUTTER_ACTOR_IS_VISIBLE (texture))
	    clutter_actor_queue_redraw (CLUTTER_ACTOR (texture));
	}
      break;
    case PROP_REPEAT_Y:
      if (priv->repeat_y != g_value_get_boolean (value))
	{
	  priv->repeat_y = !priv->repeat_y;
	  if (CLUTTER_ACTOR_IS_VISIBLE (texture))
	    clutter_actor_queue_redraw (CLUTTER_ACTOR (texture));
	}
      break;
    case PROP_FILTER_QUALITY:
      clutter_texture_set_filter_quality (texture,
					  g_value_get_enum (value));
      break;
    case PROP_COGL_TEXTURE:
      clutter_texture_set_cogl_texture
	(texture, (CoglHandle) g_value_get_boxed (value));
      break;
#if EXPOSE_COGL_MATERIAL_PROP
    case PROP_COGL_MATERIAL:
      clutter_texture_set_cogl_material
        (texture, (CoglHandle) g_value_get_boxed (value));
      break;
#endif
    case PROP_FILENAME:
      clutter_texture_set_from_file (texture,
                                     g_value_get_string (value),
                                     NULL);
      break;
    case PROP_NO_SLICE:
      priv->no_slice = g_value_get_boolean (value);
      break;
    case PROP_KEEP_ASPECT_RATIO:
      priv->keep_aspect_ratio = g_value_get_boolean (value);
      break;
    case PROP_LOAD_ASYNC:
      priv->load_async = g_value_get_boolean (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;
  CoglHandle             cogl_texture;

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

  switch (prop_id)
    {
    case PROP_MAX_TILE_WASTE:
      g_value_set_int (value, clutter_texture_get_max_tile_waste (texture));
      break;
    case PROP_PIXEL_FORMAT:
      cogl_texture = clutter_texture_get_cogl_texture (texture);
      if (cogl_texture == COGL_INVALID_HANDLE)
	g_value_set_int (value, COGL_PIXEL_FORMAT_ANY);
      else
	g_value_set_int (value, cogl_texture_get_format (cogl_texture));
      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_enum (value, clutter_texture_get_filter_quality (texture));
      break;
    case PROP_COGL_TEXTURE:
      g_value_set_boxed (value, clutter_texture_get_cogl_texture (texture));
      break;
    case PROP_COGL_MATERIAL:
      g_value_set_boxed (value, clutter_texture_get_cogl_material (texture));
      break;
    case PROP_NO_SLICE:
      g_value_set_boolean (value, priv->no_slice);
      break;
    case PROP_KEEP_ASPECT_RATIO:
      g_value_set_boolean (value, priv->keep_aspect_ratio);
      break;
    case PROP_LOAD_ASYNC:
      g_value_set_boolean (value, priv->load_async);
      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->get_preferred_width  = clutter_texture_get_preferred_width;
  actor_class->get_preferred_height = clutter_texture_get_preferred_height;
  actor_class->allocate             = clutter_texture_allocate;

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

  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_NO_SLICE,
     g_param_spec_boolean ("disable-slicing",
			   "Disable Slicing",
			   "Force the underlying texture to be singlular"
			   "and not made of of smaller space saving "
                           "inidivual textures.",
			   FALSE,
			   G_PARAM_CONSTRUCT_ONLY | 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",
			   "Repeat underlying pixbuf rather than scale "
			   "in x direction.",
			   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",
			   "Repeat underlying pixbuf rather than scale "
			   "in y direction.",
			   FALSE,
			   CLUTTER_PARAM_READWRITE));

  g_object_class_install_property
    (gobject_class, PROP_FILTER_QUALITY,
     g_param_spec_enum ("filter-quality",
                       "Filter Quality",
                       "Rendering quality used when drawing the texture.",
                       CLUTTER_TYPE_TEXTURE_QUALITY,
		       CLUTTER_TEXTURE_QUALITY_MEDIUM,
		       G_PARAM_CONSTRUCT | 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 "
		       "sliced textures or -1 to disable slicing. "
		       "Bigger values use less textures, "
		       "smaller values less texture memory.",
		       -1,
		       G_MAXINT,
		       63,
		       G_PARAM_CONSTRUCT_ONLY | CLUTTER_PARAM_READWRITE));

  g_object_class_install_property
    (gobject_class, PROP_PIXEL_FORMAT,
     g_param_spec_int ("pixel-format",
		       "Texture pixel format",
		       "CoglPixelFormat to use.",
		       0,
		       G_MAXINT,
		       COGL_PIXEL_FORMAT_RGBA_8888,
		       G_PARAM_READABLE));

  g_object_class_install_property
    (gobject_class, PROP_COGL_TEXTURE,
     g_param_spec_boxed ("cogl-texture",
			 "COGL Texture",
			 "The underlying COGL texture handle used to draw "
			 "this actor",
			 COGL_TYPE_HANDLE,
			 G_PARAM_READWRITE));

  g_object_class_install_property
    (gobject_class, PROP_COGL_MATERIAL,
     g_param_spec_boxed ("cogl-material",
			 "COGL Material",
			 "The underlying COGL material handle used to draw "
			 "this actor",
			 COGL_TYPE_HANDLE,
			 G_PARAM_READWRITE));

  g_object_class_install_property
    (gobject_class, PROP_FILENAME,
     g_param_spec_string ("filename",
                          "Filename",
                          "The full path of the file containing the texture",
                          NULL,
                          G_PARAM_WRITABLE));

  g_object_class_install_property
    (gobject_class, PROP_KEEP_ASPECT_RATIO,
     g_param_spec_boolean ("keep-aspect-ratio",
			   "Keep Aspect Ratio",
			   "Keep the aspect ratio of the texture when "
			   "requesting the preferred width or height",
			   FALSE,
			   CLUTTER_PARAM_READWRITE));

  /**
   * ClutterTexture:load-async:
   *
   * Tries to load a texture from a filename by using a local thread
   * to perform the read operations. Threading is only enabled if
   * g_thread_init() has been called prior to clutter_init(), otherwise
   * #ClutterTexture will use the main loop to load the image.
   *
   * The upload of the texture data on the GL pipeline is not
   * asynchronous, as it must be performed from within the same
   * thread that called clutter_main().
   *
   * Since: 1.0
   */
  g_object_class_install_property
    (gobject_class, PROP_LOAD_ASYNC,
     g_param_spec_boolean ("load-async",
			   "Load asynchronously",
			   "Load files inside a thread to avoid blocking when "
                           "loading images.",
			   FALSE,
			   CLUTTER_PARAM_READWRITE));

  /**
   * 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);
  /**
   * ClutterTexture::load-finished:
   * @texture: the texture which received the signal
   * @error: A set error, or %NULL
   *
   * The ::load-finished signal is emitted when a texture load has
   * completed. If there was an error during loading, @error will
   * be set, otherwise it will be %NULL
   *
   * Since: 1.0
   */
  texture_signals[LOAD_FINISHED] =
    g_signal_new (I_("load-finished"),
		  G_TYPE_FROM_CLASS (gobject_class),
		  G_SIGNAL_RUN_LAST,
		  G_STRUCT_OFFSET (ClutterTextureClass, load_finished),
		  NULL, NULL,
		  g_cclosure_marshal_VOID__POINTER,
		  G_TYPE_NONE,
		  1,
                  G_TYPE_POINTER);
}

static ClutterScriptableIface *parent_scriptable_iface = NULL;

static void
clutter_texture_set_custom_property (ClutterScriptable *scriptable,
                                     ClutterScript     *script,
                                     const gchar       *name,
                                     const GValue      *value)
{
  ClutterTexture *texture = CLUTTER_TEXTURE (scriptable);

  if (strcmp ("filename", name) == 0)
    {
      const gchar *str = g_value_get_string (value);
      gchar *path;
      GError *error;

      path = clutter_script_lookup_filename (script, str);
      if (G_UNLIKELY (!path))
        return;

      error = NULL;
      clutter_texture_set_from_file (texture, path, &error);
      if (error)
        {
          g_warning ("Unable to open image path at `%s': %s",
                     path,
                     error->message);
          g_error_free (error);
        }

      g_free (path);
    }
  else
    {
      /* chain up */
      if (parent_scriptable_iface->set_custom_property)
        parent_scriptable_iface->set_custom_property (scriptable, script,
                                                      name,
                                                      value);
    }
}

static void
clutter_scriptable_iface_init (ClutterScriptableIface *iface)
{
  parent_scriptable_iface = g_type_interface_peek_parent (iface);

  if (!parent_scriptable_iface)
    parent_scriptable_iface = g_type_default_interface_peek
                                          (CLUTTER_TYPE_SCRIPTABLE);

  iface->set_custom_property = clutter_texture_set_custom_property;
}

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

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

  priv->max_tile_waste    = 63;
  priv->filter_quality    = CLUTTER_TEXTURE_QUALITY_MEDIUM;
  priv->repeat_x          = FALSE;
  priv->repeat_y          = FALSE;
  priv->sync_actor_size   = TRUE;
  priv->material          = cogl_material_new ();
  priv->fbo_texture       = COGL_INVALID_HANDLE;
  priv->fbo_handle        = COGL_INVALID_HANDLE;
  priv->local_data        = NULL;
  priv->keep_aspect_ratio = FALSE;
}

static void
clutter_texture_save_to_local_data (ClutterTexture *texture)
{
  ClutterTexturePrivate *priv;
  int                    bpp;
  CoglPixelFormat        pixel_format;
  CoglHandle             cogl_texture;

  priv = texture->priv;

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

  if (priv->material == COGL_INVALID_HANDLE)
    return;

  cogl_texture = clutter_texture_get_cogl_texture (texture);

  priv->local_data_width = cogl_texture_get_width (cogl_texture);
  priv->local_data_height = cogl_texture_get_height (cogl_texture);
  pixel_format = cogl_texture_get_format (cogl_texture);
  priv->local_data_has_alpha = pixel_format & COGL_A_BIT;
  bpp = priv->local_data_has_alpha ? 4 : 3;

  /* Align to 4 bytes */
  priv->local_data_rowstride = (priv->local_data_width * bpp + 3) & ~3;

  /* Store the filter quality and max_tile_waste from the texture
     properties so that they will be restored the data is loaded
     again */
  priv->max_tile_waste = clutter_texture_get_max_tile_waste (texture);
  priv->filter_quality = clutter_texture_get_filter_quality (texture);

  priv->local_data = g_malloc (priv->local_data_rowstride
			       * priv->local_data_height);

  if (cogl_texture_get_data (cogl_texture,
			     priv->local_data_has_alpha
			     ? COGL_PIXEL_FORMAT_RGBA_8888
			     : COGL_PIXEL_FORMAT_RGB_888,
			     priv->local_data_rowstride,
			     priv->local_data) == 0)
    {
      g_free (priv->local_data);
      priv->local_data = NULL;
    }
}

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

  priv = texture->priv;

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

  clutter_texture_set_from_rgb_data (texture,
				     priv->local_data,
				     priv->local_data_has_alpha,
				     priv->local_data_width,
				     priv->local_data_height,
				     priv->local_data_rowstride,
				     priv->local_data_has_alpha ? 4: 3,
				     0, NULL);

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

/**
 * clutter_texture_get_cogl_material:
 * @texture: A #ClutterTexture
 *
 * Returns a handle to the underlying COGL material used for drawing
 * the actor. No extra reference is taken so if you need to keep the
 * handle then you should call cogl_material_ref() on it.
 *
 * Since: 1.0
 *
 * Return value: COGL material handle
 */
CoglHandle
clutter_texture_get_cogl_material (ClutterTexture *texture)
{
  g_return_val_if_fail (CLUTTER_IS_TEXTURE (texture), COGL_INVALID_HANDLE);

  return texture->priv->material;
}

/**
 * clutter_texture_set_cogl_material:
 * @texture: A #ClutterTexture
 * @cogl_material: A CoglHandle for a material
 *
 * Replaces the underlying COGL texture drawn by this actor with
 * @cogl_tex. A reference to the texture is taken so if the handle is
 * no longer needed it should be deref'd with cogl_texture_unref.
 *
 * Since: 0.8
 *
 */
void
clutter_texture_set_cogl_material (ClutterTexture *texture,
                                   CoglHandle cogl_material)
{
  CoglHandle cogl_texture;

  g_return_if_fail (CLUTTER_IS_TEXTURE (texture));

  /* This */
  if (texture->priv->material)
    cogl_material_unref (texture->priv->material);

  texture->priv->material = cogl_material;

  /* XXX: We are re-asserting the first layer of the new material to ensure the
   * priv state is in sync with the contents of the material. */
  cogl_texture = clutter_texture_get_cogl_texture (texture);
  clutter_texture_set_cogl_texture (texture, cogl_texture);
  /* XXX: If we add support for more material layers, this will need
   * extending */
}

/**
 * clutter_texture_get_cogl_texture
 * @texture: A #ClutterTexture
 *
 * Retrieves the handle to the underlying COGL texture used for drawing
 * the actor. No extra reference is taken so if you need to keep the
 * handle then you should call cogl_texture_ref() on it.
 *
 * The texture handle returned is the first layer of the material
 * handle used by the #ClutterTexture. If you need to access the other
 * layers you should use clutter_texture_get_cogl_material() instead
 * and use the #CoglMaterial API.
 *
 * Since: 0.8
 *
 * Return value: COGL texture handle
 */
CoglHandle
clutter_texture_get_cogl_texture (ClutterTexture *texture)
{
  const GList *layers;
  int n_layers;

  g_return_val_if_fail (CLUTTER_IS_TEXTURE (texture), COGL_INVALID_HANDLE);

  layers = cogl_material_get_layers (texture->priv->material);
  n_layers = g_list_length ((GList *)layers);
  if (n_layers == 0)
    return COGL_INVALID_HANDLE;

  return cogl_material_layer_get_texture (layers->data);
}

/**
 * clutter_texture_set_cogl_texture
 * @texture: A #ClutterTexture
 * @cogl_tex: A CoglHandle for a texture
 *
 * Replaces the underlying COGL texture drawn by this actor with
 * @cogl_tex. A reference to the texture is taken so if the handle is
 * no longer needed it should be deref'd with cogl_texture_unref.
 *
 * Since: 0.8
 */
void
clutter_texture_set_cogl_texture (ClutterTexture  *texture,
				  CoglHandle       cogl_tex)
{
  ClutterTexturePrivate  *priv;
  gboolean                size_change;
  guint                   width, height;

  g_return_if_fail (CLUTTER_IS_TEXTURE (texture));
  g_return_if_fail (cogl_is_texture (cogl_tex));

  priv = texture->priv;

  width = cogl_texture_get_width (cogl_tex);
  height = cogl_texture_get_height (cogl_tex);

  /* Reference the new texture now in case it is the same one we are
     already using */
  cogl_texture_ref (cogl_tex);

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

  /* Remove old texture */
  texture_free_gl_resources (texture);
  /* Use the new texture */

  cogl_material_set_layer (priv->material, 0, cogl_tex);

  /* The material now holds a reference to the texture so we can
     safely release the reference we claimed above */
  cogl_texture_unref (cogl_tex);

  size_change      = width != priv->width || height != priv->height;
  priv->width      = width;
  priv->height     = height;

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

  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_queue_relayout (CLUTTER_ACTOR (texture));
    }

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

  g_object_notify (G_OBJECT (texture), "cogl-texture");

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

static gboolean
clutter_texture_set_from_data (ClutterTexture     *texture,
			       const guchar       *data,
			       CoglPixelFormat     source_format,
			       gint                width,
			       gint                height,
			       gint                rowstride,
			       gint                bpp,
			       GError            **error)
{
  ClutterTexturePrivate *priv = texture->priv;
  CoglHandle new_texture = COGL_INVALID_HANDLE;
  CoglTextureFlags flags = COGL_TEXTURE_NONE;
  gint min_filter, mag_filter;
  gint max_waste = -1;

  if (!priv->no_slice)
    max_waste = priv->max_tile_waste;

  if (priv->filter_quality == CLUTTER_TEXTURE_QUALITY_HIGH)
    flags |= COGL_TEXTURE_AUTO_MIPMAP;

  new_texture = cogl_texture_new_from_data (width, height,
                                            max_waste, flags,
                                            source_format,
                                            COGL_PIXEL_FORMAT_ANY,
                                            rowstride,
                                            data);

  if (G_UNLIKELY (new_texture == COGL_INVALID_HANDLE))
    {
      g_set_error (error, CLUTTER_TEXTURE_ERROR,
                   CLUTTER_TEXTURE_ERROR_BAD_FORMAT,
                   "Failed to create COGL texture");

      return FALSE;
    }

  clutter_texture_quality_to_filters (priv->filter_quality,
                                      &min_filter,
                                      &mag_filter);

  cogl_texture_set_filters (new_texture, min_filter, mag_filter);

  clutter_texture_set_cogl_texture (texture, new_texture);

  cogl_texture_unref (new_texture);

  g_signal_emit (texture, texture_signals[LOAD_FINISHED], 0, error);

  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 3 and 4 supported,
 *                        depending on @has_alpha)
 * @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;
  CoglPixelFormat        source_format;

  g_return_val_if_fail (CLUTTER_IS_TEXTURE (texture), FALSE);

  priv = texture->priv;

  /* Convert the flags to a CoglPixelFormat */
  if (has_alpha)
    {
      if (bpp != 4)
	{
	  g_set_error (error, CLUTTER_TEXTURE_ERROR,
		       CLUTTER_TEXTURE_ERROR_BAD_FORMAT,
		       "Unsupported BPP");
	  return FALSE;
	}
      source_format = COGL_PIXEL_FORMAT_RGBA_8888;
    }
  else
    {
      if (bpp != 3)
	{
	  g_set_error (error, CLUTTER_TEXTURE_ERROR,
		       CLUTTER_TEXTURE_ERROR_BAD_FORMAT,
		       "Unsupported BPP");
	  return FALSE;
	}
      source_format = COGL_PIXEL_FORMAT_RGB_888;
    }
  if ((flags & CLUTTER_TEXTURE_RGB_FLAG_BGR))
    source_format |= COGL_BGR_BIT;
  if ((flags & CLUTTER_TEXTURE_RGB_FLAG_PREMULT))
    source_format |= COGL_PREMULT_BIT;

  return clutter_texture_set_from_data (texture, data,
					source_format,
					width, height,
					rowstride, bpp,
					error);
}

/**
 * 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;

  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;

  /* Convert the flags to a CoglPixelFormat */
  if ((flags & CLUTTER_TEXTURE_YUV_FLAG_YUV2))
    {
      g_set_error (error, CLUTTER_TEXTURE_ERROR,
		   CLUTTER_TEXTURE_ERROR_BAD_FORMAT,
		   "YUV2 not supported");
      return FALSE;
    }

  return clutter_texture_set_from_data (texture, data,
					COGL_PIXEL_FORMAT_YUV,
					width, height,
					width * 3, 3,
					error);
}

/*
 * clutter_texture_async_load_complete:
 * @self: a #ClutterTexture
 * @error: load error
 *
 * If @error is %NULL, loads the #CoglBitmap into a #CoglTexture.
 *
 * This function emits the ::load-finished signal on @self.
 */
static void
clutter_texture_async_load_complete (ClutterTexture *self,
                                     const GError   *error)
{
  ClutterTexturePrivate *priv = self->priv;
  CoglHandle handle;
  CoglTextureFlags flags = COGL_TEXTURE_NONE;
  gint waste = -1;

  if (error == NULL)
    {
      if (!priv->no_slice)
        waste = priv->max_tile_waste;

      if (priv->filter_quality == CLUTTER_TEXTURE_QUALITY_HIGH)
        flags |= COGL_TEXTURE_AUTO_MIPMAP;

      handle = cogl_texture_new_from_bitmap (priv->load_bitmap,
                                             waste, flags,
                                             COGL_PIXEL_FORMAT_ANY);
      clutter_texture_set_cogl_texture (self, handle);
      cogl_texture_unref (handle);
      
      cogl_bitmap_free (priv->load_bitmap);
      priv->load_bitmap = NULL;
    }

  g_signal_emit (self, texture_signals[LOAD_FINISHED], 0, error);

  clutter_actor_queue_relayout (CLUTTER_ACTOR (self));
}

static gboolean
clutter_texture_thread_cb (gpointer data)
{
  ClutterTexture *self = data;
  ClutterTexturePrivate *priv = self->priv;

  priv->load_idle = 0;

  if (priv->load_thread)
    {
      g_thread_join (priv->load_thread);
      priv->load_thread = NULL;
    }
  else
    return FALSE;

  clutter_texture_async_load_complete (self, priv->load_error);

  if (priv->load_error)
    {
      g_error_free (priv->load_error);
      priv->load_error = NULL;
    }
  
  return FALSE;
}

static gpointer
clutter_texture_thread_func (gpointer data)
{
  static GStaticMutex    thread_load_mutex = G_STATIC_MUTEX_INIT;
  ClutterTexture        *self = data;
  ClutterTexturePrivate *priv = self->priv;

  /* we aquire the shared lock, only one thread is allowed to
   * be loading at a time
   */
  g_static_mutex_lock (&thread_load_mutex);
  if (priv->abort)
    {
      g_static_mutex_unlock (&thread_load_mutex);
      g_free (priv->load_filename);
      priv->load_filename = NULL;
      return NULL;
    }
  /* Try loading with imaging backend */
  priv->load_bitmap = cogl_bitmap_new_from_file (priv->load_filename,
                                                 &priv->load_error);
  g_static_mutex_unlock (&thread_load_mutex);
  g_free (priv->load_filename);
  priv->load_filename = NULL;

  /* make sure we load the image in the main thread, where we
   * hold the main Clutter lock
   */
  priv->load_idle =
    clutter_threads_add_idle (clutter_texture_thread_cb, self);

  return NULL;
}

static gboolean
clutter_texture_idle_func (gpointer data)
{
  ClutterTexture *self = data;
  ClutterTexturePrivate *priv = self->priv;
  GError *internal_error;

  internal_error = NULL;
  priv->load_bitmap = cogl_bitmap_new_from_file (priv->load_filename,
                                                 &internal_error);

  clutter_texture_async_load_complete (self, internal_error);

  g_free (priv->load_filename);
  priv->load_filename = NULL;

  if (internal_error)
    g_error_free (internal_error);

  return FALSE;
}


/*
 * clutter_texture_async_load:
 * @self: a #ClutterTexture
 * @error: return location for a #GError
 *
 * Starts an asynchronous load of the file name stored inside
 * the load_filename private member.
 *
 * If threading is enabled we use a GThread to perform the actual
 * I/O; if threading is not enabled, we use an idle GSource.
 *
 * The I/O is the only bit done in a thread -- uploading the
 * texture data to the GL pipeline must be done from within the
 * same thread that called clutter_main(). Threaded upload should
 * be part of the GL implementation.
 *
 * This function will block until we get a size from the file
 * so that we can effectively get the size the texture actor after
 * clutter_texture_set_from_file().
 *
 * Return value: %TRUE if the asynchronous loading was successfully
 *   initiated, %FALSE otherwise
 */
static gboolean
clutter_texture_async_load (ClutterTexture  *self,
                            GError         **error)
{
  ClutterTexturePrivate *priv = self->priv;
  gint width, height;
  gboolean res;

  g_assert (priv->load_filename != NULL);

  /* ask the file for a size; if we cannot get the size then
   * there's no point in even continuing the asynchronous
   * loading, so we just stop there
   */
  res = cogl_bitmap_get_size_from_file (priv->load_filename,
                                        &width,
                                        &height);
  if (!res)
    {
      g_set_error (error, CLUTTER_TEXTURE_ERROR,
		   CLUTTER_TEXTURE_ERROR_BAD_FORMAT,
		   "Failed to create COGL texture");
      return FALSE;
    }
  else
    {
      priv->width = width;
      priv->height = height;
    }

  if (g_thread_supported ())
    {
      priv->load_thread =
        g_thread_create ((GThreadFunc) clutter_texture_thread_func,
                         self, TRUE,
                         error);

      return priv->load_thread != NULL? TRUE : FALSE;
    }
  else
    {
      priv->load_idle =
        clutter_threads_add_idle (clutter_texture_idle_func, self);

      return TRUE;
    }
}

/**
 * clutter_texture_set_from_file:
 * @texture: A #ClutterTexture
 * @filename: The filename of the image in GLib file name encoding
 * @error: Return location for a #GError, or %NULL
 *
 * Sets the #ClutterTexture image data from an image file. In case of
 * failure, %FALSE is returned and @error is set.
 *
 * If #ClutterTexture:load-async is set to %TRUE, this function
 * will return as soon as possible, and the actual image loading
 * from disk will be performed asynchronously. #ClutterTexture::load-finished
 * will be emitted when the image has been loaded or if an error
 * occurred.
 *
 * Return value: %TRUE if the image was successfully loaded and set
 *
 * Since: 0.8
 */
gboolean
clutter_texture_set_from_file (ClutterTexture *texture,
			       const gchar    *filename,
			       GError        **error)
{
  ClutterTexturePrivate *priv;
  CoglHandle new_texture = COGL_INVALID_HANDLE;
  GError *internal_error = NULL;
  CoglTextureFlags flags = COGL_TEXTURE_NONE;
  gint min_filter, mag_filter;
  gint max_waste = -1;

  priv = texture->priv;

  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);

  if (priv->load_async)
    {
      clutter_texture_async_load_cancel (texture);

      priv->load_filename = g_strdup (filename);

      return clutter_texture_async_load (texture, error);
    }

  if (!priv->no_slice)
    max_waste = priv->max_tile_waste;

  if (priv->filter_quality == CLUTTER_TEXTURE_QUALITY_HIGH)
    flags |= COGL_TEXTURE_AUTO_MIPMAP;

  new_texture = cogl_texture_new_from_file (filename,
                                            max_waste, flags,
                                            COGL_PIXEL_FORMAT_ANY,
                                            &internal_error);
  if (new_texture == COGL_INVALID_HANDLE)
    {
      /* If COGL didn't give an error then make one up */
      if (internal_error == NULL)
	{
	  g_set_error (error, CLUTTER_TEXTURE_ERROR,
		       CLUTTER_TEXTURE_ERROR_BAD_FORMAT,
		       "Failed to create COGL texture");
	}
      else
        g_propagate_error (error, internal_error);

      g_signal_emit (texture, texture_signals[LOAD_FINISHED], 0, error);

      return FALSE;
    }

  clutter_texture_quality_to_filters (priv->filter_quality,
                                      &min_filter,
                                      &mag_filter);

  cogl_texture_set_filters (new_texture, min_filter, mag_filter);

  clutter_texture_set_cogl_texture (texture, new_texture);

  cogl_texture_unref (new_texture);

  g_signal_emit (texture, texture_signals[LOAD_FINISHED], 0, error);

  return TRUE;
}

/**
 * clutter_texture_set_filter_quality:
 * @texture: a #ClutterTexture
 * @filter_quality: new filter quality value
 *
 * Sets the filter quality when scaling a texture. The quality is an
 * enumeration currently the following values are supported:
 * %CLUTTER_TEXTURE_QUALITY_LOW which is fast but only uses nearest neighbour
 * interpolation. %CLUTTER_TEXTURE_QUALITY_MEDIUM which is computationally a
 * bit more expensive (bilinear interpolation), and
 * %CLUTTER_TEXTURE_QUALITY_HIGH which uses extra texture memory resources to
 * improve scaled down rendering as well (by using mipmaps). The default value
 * is %CLUTTER_TEXTURE_QUALITY_MEDIUM.
 *
 * Since: 0.8
 */
void
clutter_texture_set_filter_quality (ClutterTexture        *texture,
				    ClutterTextureQuality  filter_quality)
{
  ClutterTexturePrivate *priv;
  ClutterTextureQuality  old_quality;

  g_return_if_fail (CLUTTER_IS_TEXTURE (texture));

  priv = texture->priv;

  old_quality = clutter_texture_get_filter_quality (texture);

  if (filter_quality != old_quality)
    {
      CoglHandle cogl_texture = clutter_texture_get_cogl_texture (texture);
      gint min_filter, mag_filter;

      priv->filter_quality = filter_quality;

      clutter_texture_quality_to_filters (priv->filter_quality,
                                          &min_filter,
                                          &mag_filter);

      /* Is this actually needed - causes problems with TFP mipmaps */
      if (cogl_texture != COGL_INVALID_HANDLE)
	cogl_texture_set_filters (cogl_texture, min_filter, mag_filter);

      if ((old_quality == CLUTTER_TEXTURE_QUALITY_HIGH ||
           filter_quality == CLUTTER_TEXTURE_QUALITY_HIGH) &&
           CLUTTER_ACTOR_IS_REALIZED (texture))
        {
          gboolean was_visible;

          was_visible = CLUTTER_ACTOR_IS_VISIBLE (CLUTTER_ACTOR (texture));

          clutter_actor_unrealize (CLUTTER_ACTOR (texture));
          clutter_actor_realize (CLUTTER_ACTOR (texture));

          if (was_visible)
            clutter_actor_show (CLUTTER_ACTOR (texture));
        }

      g_object_notify (G_OBJECT (texture), "filter-quality");

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

/**
 * clutter_texture_get_filter_quality
 * @texture: A #ClutterTexture
 *
 * Gets the filter quality used when scaling a texture.
 *
 * Return value: The filter quality value.
 *
 * Since: 0.8
 */
ClutterTextureQuality
clutter_texture_get_filter_quality (ClutterTexture *texture)
{
  ClutterTexturePrivate *priv;

  g_return_val_if_fail (CLUTTER_IS_TEXTURE (texture), 0);

  priv = texture->priv;

  return priv->filter_quality;
}

/**
 * clutter_texture_set_max_tile_waste
 * @texture: A #ClutterTexture
 * @max_tile_waste: Maximum amount of waste in pixels or -1
 *
 * Sets the maximum number of pixels in either axis that can be wasted
 * for an individual texture slice. If -1 is specified then the
 * texture is forced not to be sliced and the texture creation will
 * fail if the hardware can't create a texture large enough.
 *
 * The value is only used when first creating a texture so changing it
 * after the texture data has been set has no effect.
 *
 * Since: 0.8
 */
void
clutter_texture_set_max_tile_waste (ClutterTexture *texture,
				    gint            max_tile_waste)
{
  ClutterTexturePrivate *priv;
  CoglHandle cogl_texture;

  g_return_if_fail (CLUTTER_IS_TEXTURE (texture));

  priv = texture->priv;
  cogl_texture = clutter_texture_get_cogl_texture (texture);

  /* There's no point in changing the max_tile_waste if the texture
     has already been created because it will be overridden with the
     value from the texture handle */
  if (cogl_texture == COGL_INVALID_HANDLE)
    priv->max_tile_waste = max_tile_waste;
}

/**
 * clutter_texture_get_max_tile_waste
 * @texture: A #ClutterTexture
 *
 * Gets the maximum waste that will be used when creating a texture or
 * -1 if slicing is disabled.
 *
 * Return value: The maximum waste or -1 if the texture waste is
 * unlimited.
 *
 * Since: 0.8
 */
gint
clutter_texture_get_max_tile_waste (ClutterTexture *texture)
{
  ClutterTexturePrivate *priv;
  CoglHandle             cogl_texture;

  g_return_val_if_fail (CLUTTER_IS_TEXTURE (texture), 0);

  priv = texture->priv;
  cogl_texture = clutter_texture_get_cogl_texture (texture);

  if (cogl_texture == COGL_INVALID_HANDLE)
    return texture->priv->max_tile_waste;
  else
    /* If we have a valid texture handle then use the value from that
       instead */
    return cogl_texture_get_max_waste (cogl_texture);
}

/**
 * clutter_texture_new_from_file:
 * @filename: The name of an image file to load.
 * @error: Return locatoin for an error.
 *
 * Creates a new ClutterTexture actor to display the image contained a
 * file. If the image failed to load then NULL is returned and @error
 * is set.
 *
 * Return value: A newly created #ClutterTexture object or NULL on
 * error.
 *
 * Since: 0.8
 **/
ClutterActor*
clutter_texture_new_from_file (const gchar *filename,
			       GError     **error)
{
  ClutterActor *texture = clutter_texture_new ();

  if (!clutter_texture_set_from_file (CLUTTER_TEXTURE (texture),
				      filename, error))
    {
      g_object_ref_sink (texture);
      g_object_unref (texture);

      return NULL;
    }
  else
    return texture;
}

/**
 * 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_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 3 and 4 supported,
 *                        depending on @has_alpha)
 * @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;
  CoglPixelFormat        source_format;
  CoglHandle             cogl_texture;

  priv = texture->priv;

  if (has_alpha)
    {
      if (bpp != 4)
	{
	  g_set_error (error, CLUTTER_TEXTURE_ERROR,
		       CLUTTER_TEXTURE_ERROR_BAD_FORMAT,
		       "Unsupported BPP");
	  return FALSE;
	}
      source_format = COGL_PIXEL_FORMAT_RGBA_8888;
    }
  else
    {
      if (bpp != 3)
	{
	  g_set_error (error, CLUTTER_TEXTURE_ERROR,
		       CLUTTER_TEXTURE_ERROR_BAD_FORMAT,
		       "Unsupported BPP");
	  return FALSE;
	}
      source_format = COGL_PIXEL_FORMAT_RGB_888;
    }
  if ((flags & CLUTTER_TEXTURE_RGB_FLAG_BGR))
    source_format |= COGL_BGR_BIT;
  if ((flags & CLUTTER_TEXTURE_RGB_FLAG_PREMULT))
    source_format |= COGL_PREMULT_BIT;

  clutter_actor_realize (CLUTTER_ACTOR (texture));

  cogl_texture = clutter_texture_get_cogl_texture (texture);
  if (cogl_texture == COGL_INVALID_HANDLE)
    {
      g_set_error (error, CLUTTER_TEXTURE_ERROR,
		   CLUTTER_TEXTURE_ERROR_BAD_FORMAT,
		   "Failed to realize actor");
      return FALSE;
    }

  if (!cogl_texture_set_region (cogl_texture,
				0, 0,
				x, y, width, height,
				width, height,
				source_format,
				rowstride,
				data))
    {
      g_set_error (error, CLUTTER_TEXTURE_ERROR,
		   CLUTTER_TEXTURE_ERROR_BAD_FORMAT,
		   "Failed to upload COGL texture data");
      return FALSE;
    }

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

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

  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_transformed_size (priv->fbo_source, &w, &h);

  if (w != priv->width || h != priv->height)
    {
      CoglTextureFlags flags = COGL_TEXTURE_NONE;
      gint min_filter, mag_filter;

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

      texture_free_gl_resources (texture);

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

      if (priv->filter_quality == CLUTTER_TEXTURE_QUALITY_HIGH)
        flags |= COGL_TEXTURE_AUTO_MIPMAP;

      priv->fbo_texture =
        cogl_texture_new_with_size (MAX (priv->width, 1),
				    MAX (priv->height, 1),
				    -1,
                                    flags,
				    COGL_PIXEL_FORMAT_RGBA_8888);

      clutter_texture_quality_to_filters (priv->filter_quality,
                                          &min_filter,
                                          &mag_filter);

      cogl_texture_set_filters (priv->fbo_texture, min_filter, mag_filter);

      priv->fbo_handle = cogl_offscreen_new_to_texture (priv->fbo_texture);

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

      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
 * the %CLUTTER_FEATURE_OFFSCREEN feature is 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).</para>
 *   </listitem>
 *   <listitem>
 *     <para>The source actor must have a parent in order for it to be
 *     allocated a size from the layouting mechanism. If the source
 *     actor does not have a parent when this function is called then
 *     the ClutterTexture will adopt it and allocate it at its
 *     preferred size. Using this you can clone an actor that is
 *     otherwise not displayed. Because of this feature if you do
 *     intend to display the source actor then you must make sure that
 *     the actor is parented before calling
 *     clutter_texture_new_from_actor() or that you unparent it before
 *     adding it to a container.</para>
 *   </listitem>
 *   <listitem>
 *     <para>When getting the image for the clone texture, Clutter
 *     will attempt to render the source actor exactly as it would
 *     appear if it was rendered on screen. The source actor's parent
 *     transformations are taken into account. Therefore if your
 *     source actor is rotated along the X or Y axes so that it has
 *     some depth, the texture will appear differently depending on
 *     the on-screen location of the source actor. While painting the
 *     source actor, Clutter will set up a temporary asymmetric
 *     perspective matrix as the projection matrix so that the source
 *     actor will be projected as if a small section of the screen was
 *     being viewed. Before version 0.8.2, an orthogonal identity
 *     projection was used which meant that the source actor would be
 *     clipped if any part of it was not on the zero Z-plane.</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_from_file()) will destroy the offscreen texture data
 *     and end redirection.</para>
 *   </listitem>
 *   <listitem>
 *     <para>cogl_texture_get_data() with the handle returned by
 *     clutter_texture_get_cogl_texture() 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);

  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_transformed_size (actor, &w, &h);

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

  /* Hopefully now were good.. */
  texture = g_object_new (CLUTTER_TYPE_TEXTURE,
                          "disable-slicing", TRUE,
                          NULL);

  priv = texture->priv;

  priv->fbo_source = g_object_ref_sink (actor);

  /* If the actor doesn't have a parent then claim it so that it will
     get a size allocation during layout */
  if (clutter_actor_get_parent (actor) == NULL)
    clutter_actor_set_parent (actor, CLUTTER_ACTOR (texture));

  /* 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;

  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)
    {
      /* If we parented the texture then unparent it again so that it
	 will lose the reference */
      if (clutter_actor_get_parent (priv->fbo_source)
	  == CLUTTER_ACTOR (texture))
	clutter_actor_unparent (priv->fbo_source);

      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 != COGL_INVALID_HANDLE)
    {
      cogl_offscreen_unref (priv->fbo_handle);
      priv->fbo_handle = COGL_INVALID_HANDLE;
    }
}