mutter/clutter/clutter-texture.c
Emmanuele Bassi c18e756267 [docs] Clarify the set_cogl_texture() documentation
When setting the COGL texture handle for a ClutterTexture the
texture will be set as the first layer of the material used
by the ClutterTexture. The documentation should clarify this
point.
2009-02-19 13:44:29 +00:00

2423 lines
71 KiB
C

/*
* 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 */
ClutterFixed ratio, height;
ratio = CLUTTER_FIXED_DIV ((float)(priv->width),
(float)(priv->height));
height = CLUTTER_UNITS_TO_FIXED (for_height);
*natural_width_p =
CLUTTER_UNITS_FROM_FIXED (CLUTTER_FIXED_MUL (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 */
ClutterFixed ratio, width;
ratio = CLUTTER_FIXED_DIV ((float)(priv->height),
(float)(priv->width));
width = CLUTTER_UNITS_TO_FIXED (for_width);
*natural_height_p =
CLUTTER_UNITS_FROM_FIXED (CLUTTER_FIXED_MUL (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];
ClutterFixed viewport[4];
ClutterUnit x_min, x_max, y_min, y_max;
ClutterFixed tx_min, tx_max, ty_min, ty_max;
ClutterPerspective perspective;
ClutterStage *stage;
ClutterFixed tan_angle, near_size;
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_perspectivex (stage, &perspective);
/* Convert the coordinates back to [-1,1] range */
cogl_get_viewport (viewport);
tx_min = CLUTTER_FIXED_DIV (CLUTTER_UNITS_TO_FIXED (x_min), viewport[2])
* 2 - 1.0;
tx_max = CLUTTER_FIXED_DIV (CLUTTER_UNITS_TO_FIXED (x_max), viewport[2])
* 2 - 1.0;
ty_min = CLUTTER_FIXED_DIV (CLUTTER_UNITS_TO_FIXED (y_min), viewport[3])
* 2 - 1.0;
ty_max = CLUTTER_FIXED_DIV (CLUTTER_UNITS_TO_FIXED (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 = CLUTTER_FIXED_MUL (perspective.z_near, tan_angle);
cogl_frustum (CLUTTER_FIXED_MUL (tx_min, near_size),
CLUTTER_FIXED_MUL (tx_max, near_size),
CLUTTER_FIXED_MUL (-ty_min, near_size),
CLUTTER_FIXED_MUL (-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;
ClutterFixed t_w, t_h;
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_perspectivex (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,
clutter_actor_get_paint_opacity (self));
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 = CLUTTER_FIXED_DIV ((float)(x_2 - x_1),
(float)(priv->width));
else
t_w = 1.0;
if (priv->repeat_y && priv->height > 0)
t_h = CLUTTER_FIXED_DIV ((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;
}
}