mutter/clutter/clutter-texture.c
Matthew Allum 9d8bfaf997 2008-06-06 Matthew Allum <mallum@openedhand.com>
Bug #948 - Remove texture rectangle support

        * clutter/clutter-feature.c:
        * clutter/clutter-feature.h:
        * clutter/clutter-texture.c:
        * clutter/cogl/gl/cogl.c:
* clutter/glx/clutter-glx-texture-pixmap.c:
        Remove support for GL_TEXTURE_RECTANGLE_ARB (now using just regular
        2D textures, with optional npots extension). Simplifys code, + makes
        mipmap & shader support much more sane.
2008-06-06 13:44:22 +00:00

1699 lines
48 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/hiding frees image data from texture memory moving to main
* system memory. Re-realizing then performs the opposite operation.
* This process allows basic management of commonly limited available texture
* memory.
*
* Note: a ClutterTexture will scale its contents to fit the bounding box
* requested using clutter_actor_request_coords() and its wrappers. To
* display an area of a texture without scaling, you should set the clip
* area using clutter_actor_set_clip().
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "clutter-texture.h"
#include "clutter-main.h"
#include "clutter-marshal.h"
#include "clutter-feature.h"
#include "clutter-util.h"
#include "clutter-private.h"
#include "clutter-scriptable.h"
#include "clutter-debug.h"
#include "clutter-fixed.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;
guint sync_actor_size : 1;
gint max_tile_waste;
guint filter_quality;
guint repeat_x : 1;
guint repeat_y : 1;
CoglHandle 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;
};
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_FILENAME
};
enum
{
SIZE_CHANGE,
PIXBUF_CHANGE,
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 void
texture_free_gl_resources (ClutterTexture *texture)
{
ClutterTexturePrivate *priv;
priv = texture->priv;
CLUTTER_MARK();
if (priv->texture != COGL_INVALID_HANDLE)
{
cogl_texture_unref (priv->texture);
priv->texture = COGL_INVALID_HANDLE;
}
}
static void
clutter_texture_unrealize (ClutterActor *actor)
{
ClutterTexture *texture;
ClutterTexturePrivate *priv;
texture = CLUTTER_TEXTURE(actor);
priv = texture->priv;
if (priv->texture == 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)
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)
{
/* Handle FBO's */
if (priv->texture != COGL_INVALID_HANDLE)
cogl_texture_unref (priv->texture);
priv->texture = cogl_texture_new_with_size
(priv->width,
priv->height,
priv->no_slice ? -1 : priv->max_tile_waste,
FALSE,
COGL_PIXEL_FORMAT_RGBA_8888);
cogl_texture_set_filters (priv->texture,
priv->filter_quality
? CGL_LINEAR : CGL_NEAREST,
priv->filter_quality
? CGL_LINEAR : CGL_NEAREST);
priv->fbo_handle = cogl_offscreen_new_to_texture (priv->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_show (ClutterActor *self)
{
ClutterActorClass *parent_class;
/* chain up parent show */
parent_class = CLUTTER_ACTOR_CLASS (clutter_texture_parent_class);
if (parent_class->show)
parent_class->show (self);
clutter_actor_realize (self);
}
static void
clutter_texture_hide (ClutterActor *self)
{
ClutterActorClass *parent_class;
/* chain up parent hide */
parent_class = CLUTTER_ACTOR_CLASS (clutter_texture_parent_class);
if (parent_class->hide)
parent_class->hide (self);
clutter_actor_unrealize (self);
}
static void
clutter_texture_paint (ClutterActor *self)
{
ClutterTexture *texture = CLUTTER_TEXTURE (self);
ClutterTexturePrivate *priv = texture->priv;
gint x_1, y_1, x_2, y_2;
ClutterColor col = { 0xff, 0xff, 0xff, 0xff };
ClutterColor transparent_col = { 0, 0, 0, 0 };
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;
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);
/* cogl_paint_init is called to clear the buffers */
cogl_paint_init (&transparent_col);
/* Render out actor scene to fbo */
clutter_actor_paint (priv->fbo_source);
/* Restore drawing to the frame buffer */
cogl_draw_buffer (COGL_WINDOW_BUFFER, COGL_INVALID_HANDLE);
/* If there is a shader on top of the shader stack, turn it back on. */
if (shader)
clutter_shader_set_is_enabled (shader, TRUE);
}
CLUTTER_NOTE (PAINT,
"painting texture '%s'",
clutter_actor_get_name (self) ? clutter_actor_get_name (self)
: "unknown");
cogl_push_matrix ();
col.alpha = clutter_actor_get_abs_opacity (self);
cogl_color (&col);
clutter_actor_get_coords (self, &x_1, &y_1, &x_2, &y_2);
CLUTTER_NOTE (PAINT, "paint to x1: %i, y1: %i x2: %i, y2: %i "
"opacity: %i",
x_1, y_1, x_2, y_2,
clutter_actor_get_opacity (self));
if (priv->repeat_x && priv->width > 0)
t_w = CFX_QDIV (CLUTTER_INT_TO_FIXED (x_2 - x_1),
CLUTTER_INT_TO_FIXED (priv->width));
else
t_w = CFX_ONE;
if (priv->repeat_y && priv->height > 0)
t_h = CFX_QDIV (CLUTTER_INT_TO_FIXED (y_2 - y_1),
CLUTTER_INT_TO_FIXED (priv->height));
else
t_h = CFX_ONE;
/* Paint will have translated us */
cogl_texture_rectangle (priv->texture, 0, 0,
CLUTTER_INT_TO_FIXED (x_2 - x_1),
CLUTTER_INT_TO_FIXED (y_2 - y_1),
0, 0, t_w, t_h);
cogl_pop_matrix ();
}
static void
clutter_texture_request_coords (ClutterActor *self,
ClutterActorBox *box)
{
ClutterTexture *texture = CLUTTER_TEXTURE (self);
ClutterActorBox old_request;
clutter_actor_query_coords (self, &old_request);
if (((box->x2 - box->x1) != (old_request.x2 - old_request.x1)) ||
((box->y2 - box->y1) != (old_request.y2 - old_request.y1)))
texture->priv->sync_actor_size = FALSE;
CLUTTER_ACTOR_CLASS (clutter_texture_parent_class)
->request_coords (self, box);
}
static void
clutter_texture_dispose (GObject *object)
{
ClutterTexture *texture = CLUTTER_TEXTURE (object);
ClutterTexturePrivate *priv;
priv = texture->priv;
texture_free_gl_resources (texture);
texture_fbo_free_resources (texture);
if (priv->local_data != NULL)
{
g_free (priv->local_data);
priv->local_data = NULL;
}
G_OBJECT_CLASS (clutter_texture_parent_class)->dispose (object);
}
static void
clutter_texture_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
ClutterTexture *texture;
ClutterTexturePrivate *priv;
texture = CLUTTER_TEXTURE(object);
priv = texture->priv;
switch (prop_id)
{
case PROP_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);
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_int (value));
break;
case PROP_COGL_TEXTURE:
clutter_texture_set_cogl_texture
(texture, (CoglHandle) g_value_get_boxed (value));
break;
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;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
clutter_texture_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
ClutterTexture *texture;
ClutterTexturePrivate *priv;
texture = CLUTTER_TEXTURE(object);
priv = texture->priv;
switch (prop_id)
{
case PROP_MAX_TILE_WASTE:
g_value_set_int (value, clutter_texture_get_max_tile_waste (texture));
break;
case PROP_PIXEL_FORMAT:
if (priv->texture == COGL_INVALID_HANDLE)
g_value_set_int (value, COGL_PIXEL_FORMAT_ANY);
else
g_value_set_int (value, cogl_texture_get_format (priv->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_int (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_NO_SLICE:
g_value_set_boolean (value, priv->no_slice);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
clutter_texture_class_init (ClutterTextureClass *klass)
{
GObjectClass *gobject_class;
ClutterActorClass *actor_class;
gobject_class = (GObjectClass*) klass;
actor_class = (ClutterActorClass*) klass;
g_type_class_add_private (klass, sizeof (ClutterTexturePrivate));
actor_class->paint = clutter_texture_paint;
actor_class->realize = clutter_texture_realize;
actor_class->unrealize = clutter_texture_unrealize;
actor_class->show = clutter_texture_show;
actor_class->hide = clutter_texture_hide;
actor_class->request_coords = clutter_texture_request_coords;
gobject_class->dispose = clutter_texture_dispose;
gobject_class->set_property = clutter_texture_set_property;
gobject_class->get_property = clutter_texture_get_property;
g_object_class_install_property
(gobject_class, PROP_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));
/* FIXME: Ideally this option needs to have some kind of global
* overide as to imporve performance.
*/
g_object_class_install_property
(gobject_class, PROP_FILTER_QUALITY,
g_param_spec_int ("filter-quality",
"Quality of filter used when scaling a texture",
"Values 0 and 1 current only supported, with 0 "
"being lower quality but fast, 1 being better "
"quality but slower. ( Currently just maps to "
"GL_NEAREST / GL_LINEAR )",
0,
G_MAXINT,
1,
CLUTTER_PARAM_READWRITE));
g_object_class_install_property
(gobject_class, PROP_MAX_TILE_WASTE,
g_param_spec_int ("tile-waste",
"Tile dimension to waste",
"Max wastage dimension of a texture when using "
"sliced textures or -1 to disable slicing. "
"Bigger values use less textures, "
"smaller values less texture memory.",
-1,
G_MAXINT,
64,
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",
CLUTTER_TYPE_TEXTURE_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));
/**
* ClutterTexture::size-change:
* @texture: the texture which received the signal
* @width: the width of the new texture
* @height: the height of the new texture
*
* The ::size-change signal is emitted each time the size of the
* pixbuf used by @texture changes. The new size is given as
* argument to the callback.
*/
texture_signals[SIZE_CHANGE] =
g_signal_new ("size-change",
G_TYPE_FROM_CLASS (gobject_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (ClutterTextureClass, size_change),
NULL, NULL,
clutter_marshal_VOID__INT_INT,
G_TYPE_NONE,
2, G_TYPE_INT, G_TYPE_INT);
/**
* ClutterTexture::pixbuf-change:
* @texture: the texture which received the signal
*
* The ::pixbuf-change signal is emitted each time the pixbuf
* used by @texture changes.
*/
texture_signals[PIXBUF_CHANGE] =
g_signal_new ("pixbuf-change",
G_TYPE_FROM_CLASS (gobject_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (ClutterTextureClass, pixbuf_change),
NULL, NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE,
0);
}
static 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;
if (g_path_is_absolute (str))
path = g_strdup (str);
else
{
gchar *dirname = NULL;
gboolean is_filename = FALSE;
g_object_get (script, "filename-set", &is_filename, NULL);
if (is_filename)
{
gchar *filename = NULL;
g_object_get (script, "filename", &filename, NULL);
dirname = g_path_get_dirname (filename);
g_free (filename);
}
else
dirname = g_get_current_dir ();
path = g_build_filename (dirname, str, NULL);
g_free (dirname);
}
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 = 64;
priv->filter_quality = 1;
priv->repeat_x = FALSE;
priv->repeat_y = FALSE;
priv->sync_actor_size = TRUE;
priv->texture = COGL_INVALID_HANDLE;
priv->fbo_handle = COGL_INVALID_HANDLE;
priv->local_data = NULL;
}
static void
clutter_texture_save_to_local_data (ClutterTexture *texture)
{
ClutterTexturePrivate *priv;
int bpp;
CoglPixelFormat pixel_format;
priv = texture->priv;
if (priv->local_data)
{
g_free (priv->local_data);
priv->local_data = NULL;
}
if (priv->texture == COGL_INVALID_HANDLE)
return;
priv->local_data_width = cogl_texture_get_width (priv->texture);
priv->local_data_height = cogl_texture_get_height (priv->texture);
pixel_format = cogl_texture_get_format (priv->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 (priv->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_texture
* @texture: A #ClutterTexture
*
* Returns a 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.
*
* Since: 0.8
*
* Return value: COGL texture handle
**/
CoglHandle
clutter_texture_get_cogl_texture (ClutterTexture *texture)
{
g_return_val_if_fail (CLUTTER_IS_TEXTURE (texture), COGL_INVALID_HANDLE);
return texture->priv->texture;
}
/**
* 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 */
priv->texture = 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_set_size (CLUTTER_ACTOR(texture),
priv->width,
priv->height);
/* The above call will clear sync_actor_size because the
size has changed so we need to put it back */
priv->sync_actor_size = TRUE;
}
}
/* 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)
{
CoglHandle new_texture;
ClutterTexturePrivate *priv;
priv = texture->priv;
if ((new_texture = cogl_texture_new_from_data
(width, height,
priv->no_slice ? -1 : priv->max_tile_waste,
FALSE,
source_format,
COGL_PIXEL_FORMAT_ANY,
rowstride,
data)) == COGL_INVALID_HANDLE)
{
g_set_error (error, CLUTTER_TEXTURE_ERROR,
CLUTTER_TEXTURE_ERROR_BAD_FORMAT,
"Failed to create COGL texture");
return FALSE;
}
cogl_texture_set_filters (new_texture,
priv->filter_quality
? CGL_LINEAR : CGL_NEAREST,
priv->filter_quality
? CGL_LINEAR : CGL_NEAREST);
clutter_texture_set_cogl_texture (texture, new_texture);
cogl_texture_unref (new_texture);
return TRUE;
}
/**
* clutter_texture_set_from_rgb_data:
* @texture: A #ClutterTexture
* @data: Image data in RGBA type colorspace.
* @has_alpha: Set to TRUE if image data has an alpha channel.
* @width: Width in pixels of image data.
* @height: Height in pixels of image data
* @rowstride: Distance in bytes between row starts.
* @bpp: bytes per pixel ( Currently only 4 supported )
* @flags: #ClutterTextureFlags
* @error: return location for a #GError, or %NULL.
*
* Sets #ClutterTexture image data.
*
* Note: This function is likely to change in future versions.
*
* Return value: %TRUE on success, %FALSE on failure.
*
* Since: 0.4.
**/
gboolean
clutter_texture_set_from_rgb_data (ClutterTexture *texture,
const guchar *data,
gboolean has_alpha,
gint width,
gint height,
gint rowstride,
gint bpp,
ClutterTextureFlags flags,
GError **error)
{
ClutterTexturePrivate *priv;
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_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.
*
* 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)
{
CoglHandle new_texture;
ClutterTexturePrivate *priv;
priv = texture->priv;
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
if ((new_texture = cogl_texture_new_from_file
(filename,
priv->no_slice ? -1 : priv->max_tile_waste,
FALSE,
COGL_PIXEL_FORMAT_ANY,
error))
== COGL_INVALID_HANDLE)
{
/* If COGL didn't give an error then make one up */
if (error && *error == NULL)
{
g_set_error (error, CLUTTER_TEXTURE_ERROR,
CLUTTER_TEXTURE_ERROR_BAD_FORMAT,
"Failed to create COGL texture");
}
return FALSE;
}
cogl_texture_set_filters (new_texture,
priv->filter_quality
? CGL_LINEAR : CGL_NEAREST,
priv->filter_quality
? CGL_LINEAR : CGL_NEAREST);
clutter_texture_set_cogl_texture (texture, new_texture);
cogl_texture_unref (new_texture);
return TRUE;
}
/**
* clutter_texture_set_filter_quality
* @texture: A #ClutterTexture
* @filter_quality: A filter quality value
*
* Sets the filter quality when scaling a texture. Only values 0 and 1
* are currently supported, with 0 being lower quality but fast, 1
* being better quality but slower. ( Currently just maps to
* GL_NEAREST / GL_LINEAR ). The default is 1.
*
* Since: 0.8
*/
void
clutter_texture_set_filter_quality (ClutterTexture *texture,
guint filter_quality)
{
ClutterTexturePrivate *priv;
g_return_if_fail (CLUTTER_IS_TEXTURE (texture));
priv = texture->priv;
if (filter_quality != clutter_texture_get_filter_quality (texture))
{
priv->filter_quality = filter_quality;
if (priv->texture != COGL_INVALID_HANDLE)
cogl_texture_set_filters (priv->texture,
filter_quality ? CGL_LINEAR : CGL_NEAREST,
filter_quality ? CGL_LINEAR : CGL_NEAREST);
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
*/
guint
clutter_texture_get_filter_quality (ClutterTexture *texture)
{
ClutterTexturePrivate *priv;
g_return_val_if_fail (CLUTTER_IS_TEXTURE (texture), 0);
priv = texture->priv;
if (priv->texture == COGL_INVALID_HANDLE)
return texture->priv->max_tile_waste;
else
/* If we have a valid texture handle then use the filter quality
from that instead */
return cogl_texture_get_min_filter (texture->priv->texture)
== CGL_LINEAR ? 1 : 0;
}
/**
* 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;
g_return_if_fail (CLUTTER_IS_TEXTURE (texture));
priv = texture->priv;
/* 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 (priv->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;
g_return_val_if_fail (CLUTTER_IS_TEXTURE (texture), 0);
priv = texture->priv;
if (priv->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 (texture->priv->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 4 supported )
* @flags: #ClutterTextureFlags
* @error: return location for a #GError, or %NULL
*
* Updates a sub-region of the pixel data in a #ClutterTexture.
*
* Return value: %TRUE on success, %FALSE on failure.
*
* Since 0.6.
*/
gboolean
clutter_texture_set_area_from_rgb_data (ClutterTexture *texture,
const guchar *data,
gboolean has_alpha,
gint x,
gint y,
gint width,
gint height,
gint rowstride,
gint bpp,
ClutterTextureFlags flags,
GError **error)
{
ClutterTexturePrivate *priv;
CoglPixelFormat source_format;
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));
if (priv->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 (priv->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_abs_size (priv->fbo_source, &w, &h);
if (w != priv->width || h != priv->height)
{
/* tear down the FBO */
cogl_offscreen_unref (priv->fbo_handle);
texture_free_gl_resources (texture);
priv->width = w;
priv->height = h;
priv->texture = cogl_texture_new_with_size (priv->width,
priv->height,
-1,
FALSE,
COGL_PIXEL_FORMAT_RGBA_8888);
cogl_texture_set_filters (priv->texture,
priv->filter_quality
? CGL_LINEAR : CGL_NEAREST,
priv->filter_quality
? CGL_LINEAR : CGL_NEAREST);
priv->fbo_handle = cogl_offscreen_new_to_texture (priv->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). The source actor does not however have to
* have a parent.</para>
* </listitem>
* <listitem>
* <para>Avoid reparenting the source with the created texture.</para>
* </listitem>
* <listitem>
* <para>A group can be padded with a transparent rectangle as to
* provide a border to contents for shader output (blurring text
* for example).</para>
* </listitem>
* <listitem>
* <para>The texture will automatically resize to contain a further
* transformed source. However, this involves overhead and can be
* avoided by placing the source actor in a bounding group
* sized large enough to contain any child tranformations.</para>
* </listitem>
* <listitem>
* <para>Uploading pixel data to the texture (e.g by using
* clutter_actor_set_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_abs_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(actor);
/* Connect up any signals which could change our underlying size */
g_signal_connect (actor,
"notify::width",
G_CALLBACK(on_fbo_source_size_change),
texture);
g_signal_connect (actor,
"notify::height",
G_CALLBACK(on_fbo_source_size_change),
texture);
g_signal_connect (actor,
"notify::scale-x",
G_CALLBACK(on_fbo_source_size_change),
texture);
g_signal_connect (actor,
"notify::scale-y",
G_CALLBACK(on_fbo_source_size_change),
texture);
g_signal_connect (actor,
"notify::rotation-angle-x",
G_CALLBACK(on_fbo_source_size_change),
texture);
g_signal_connect (actor,
"notify::rotation-angle-y",
G_CALLBACK(on_fbo_source_size_change),
texture);
g_signal_connect (actor,
"notify::rotation-angle-z",
G_CALLBACK(on_fbo_source_size_change),
texture);
/* And a warning if the source becomes a child of the texture */
g_signal_connect (actor,
"parent-set",
G_CALLBACK(on_fbo_parent_change),
texture);
priv->width = w;
priv->height = h;
clutter_actor_set_size (CLUTTER_ACTOR(texture), priv->width, priv->height);
return CLUTTER_ACTOR(texture);
}
static void
texture_fbo_free_resources (ClutterTexture *texture)
{
ClutterTexturePrivate *priv;
priv = texture->priv;
CLUTTER_MARK();
if (priv->fbo_source != NULL)
{
g_signal_handlers_disconnect_by_func
(priv->fbo_source,
G_CALLBACK(on_fbo_parent_change),
texture);
g_signal_handlers_disconnect_by_func
(priv->fbo_source,
G_CALLBACK(on_fbo_source_size_change),
texture);
g_object_unref (priv->fbo_source);
priv->fbo_source = NULL;
}
if (priv->fbo_handle != COGL_INVALID_HANDLE)
{
cogl_offscreen_unref (priv->fbo_handle);
priv->fbo_handle = COGL_INVALID_HANDLE;
}
}
GType
clutter_texture_handle_get_type (void)
{
static GType our_type = 0;
if (G_UNLIKELY (!our_type))
{
our_type =
g_boxed_type_register_static (I_("ClutterTextureHandle"),
(GBoxedCopyFunc) cogl_texture_ref,
(GBoxedFreeFunc) cogl_texture_unref);
}
return our_type;
}