9b780c2716
* clutter/clutter-group.c: * clutter/clutter-label.c: * clutter/clutter-rectangle.c: * clutter/clutter-texture.c: More documentation fixes
2447 lines
66 KiB
C
2447 lines
66 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_pixbuf()
|
|
* functions are used to copy image data into texture memory and subsequently
|
|
* realize the texture.
|
|
*
|
|
* If texture reads are supported by underlying GL implementation,
|
|
* unrealizing/hiding frees image data from texture memory moving to main
|
|
* system memory. Re-realizing then performs the opposite operation.
|
|
* This process allows basic management of commonly limited available texture
|
|
* memory.
|
|
*
|
|
* Note: a ClutterTexture will scale its contents to fit the bounding box
|
|
* requested using clutter_actor_request_coords() and its wrappers. To
|
|
* display an area of a texture without scaling, you should set the clip
|
|
* area using clutter_actor_set_clip().
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include "clutter-texture.h"
|
|
#include "clutter-main.h"
|
|
#include "clutter-marshal.h"
|
|
#include "clutter-feature.h"
|
|
#include "clutter-util.h"
|
|
#include "clutter-private.h"
|
|
#include "clutter-debug.h"
|
|
#include "clutter-fixed.h"
|
|
|
|
#include "cogl.h"
|
|
|
|
G_DEFINE_TYPE (ClutterTexture, clutter_texture, CLUTTER_TYPE_ACTOR);
|
|
|
|
#if G_BYTE_ORDER == G_LITTLE_ENDIAN
|
|
#define PIXEL_TYPE CGL_UNSIGNED_BYTE
|
|
#else
|
|
#define PIXEL_TYPE CGL_UNSIGNED_INT_8_8_8_8_REV
|
|
#endif
|
|
|
|
typedef struct {
|
|
gint pos;
|
|
gint size;
|
|
gint waste;
|
|
} ClutterTextureTileDimension;
|
|
|
|
#define CLUTTER_TEXTURE_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), CLUTTER_TYPE_TEXTURE, ClutterTexturePrivate))
|
|
|
|
struct _ClutterTexturePrivate
|
|
{
|
|
gint width;
|
|
gint height;
|
|
COGLenum pixel_format;
|
|
COGLenum pixel_type;
|
|
COGLenum target_type;
|
|
GdkPixbuf *local_pixbuf; /* non video memory copy */
|
|
guint sync_actor_size : 1;
|
|
gint max_tile_waste;
|
|
guint filter_quality;
|
|
guint repeat_x : 1; /* non working */
|
|
guint repeat_y : 1; /* non working */
|
|
guint is_tiled : 1;
|
|
ClutterTextureTileDimension *x_tiles;
|
|
ClutterTextureTileDimension *y_tiles;
|
|
gint n_x_tiles;
|
|
gint n_y_tiles;
|
|
COGLuint *tiles;
|
|
|
|
ClutterActor *fbo_source;
|
|
COGLuint fbo_handle;
|
|
};
|
|
|
|
enum
|
|
{
|
|
PROP_0,
|
|
PROP_PIXBUF,
|
|
PROP_USE_TILES,
|
|
PROP_MAX_TILE_WASTE,
|
|
PROP_PIXEL_TYPE, /* Texture type */
|
|
PROP_PIXEL_FORMAT, /* Texture format */
|
|
PROP_SYNC_SIZE,
|
|
PROP_REPEAT_Y,
|
|
PROP_REPEAT_X,
|
|
PROP_FILTER_QUALITY
|
|
};
|
|
|
|
enum
|
|
{
|
|
SIZE_CHANGE,
|
|
PIXBUF_CHANGE,
|
|
LAST_SIGNAL
|
|
};
|
|
|
|
static int texture_signals[LAST_SIGNAL] = { 0 };
|
|
|
|
static void
|
|
texture_fbo_free_resources (ClutterTexture *texture);
|
|
|
|
GQuark
|
|
clutter_texture_error_quark (void)
|
|
{
|
|
return g_quark_from_static_string ("clutter-texture-error-quark");
|
|
}
|
|
|
|
static guchar*
|
|
un_pre_multiply_alpha (const guchar *data,
|
|
gint width,
|
|
gint height,
|
|
gint rowstride)
|
|
{
|
|
gint x,y;
|
|
unsigned char *ret, *dst, *src;
|
|
|
|
ret = dst = g_malloc(sizeof(guchar) * height * rowstride);
|
|
|
|
/* FIXME: Optimise */
|
|
for (y = 0; y < height; y++)
|
|
{
|
|
src = (guchar*)data + y * rowstride;
|
|
for (x = 0; x < width; x++)
|
|
{
|
|
guchar alpha = src[3];
|
|
if (alpha == 0)
|
|
{
|
|
src[0] = src[1] = src[2] = src[3] = alpha;
|
|
}
|
|
else
|
|
{
|
|
dst[0] = (((src[0] >> 16) & 0xff) * 255 ) / alpha;
|
|
dst[1] = (((src[1] >> 8) & 0xff) * 255 ) / alpha;
|
|
dst[2] = (((src[2] >> 0) & 0xff) * 255 ) / alpha;
|
|
dst[3] = alpha;
|
|
}
|
|
dst += 4;
|
|
src += 4;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
#ifndef HAVE_COGL_GL
|
|
static guchar *
|
|
rgb_to_bgr (const guchar *data,
|
|
gboolean has_alpha,
|
|
gint width,
|
|
gint height,
|
|
gint rowstride)
|
|
{
|
|
gint x,y, bpp = 4;
|
|
unsigned char *ret, *dst, *src;
|
|
|
|
ret = dst = g_malloc(sizeof(guchar) * height * rowstride);
|
|
|
|
if (!has_alpha)
|
|
bpp = 3;
|
|
|
|
/* FIXME: Optimise */
|
|
for (y = 0; y < height; y++)
|
|
{
|
|
src = (guchar*)data + y * rowstride;
|
|
for (x = 0; x < width; x++)
|
|
{
|
|
dst[0] = src[2];
|
|
dst[1] = src[1];
|
|
dst[2] = src[0];
|
|
if (has_alpha)
|
|
dst[3] = src[3];
|
|
dst += bpp;
|
|
src += bpp;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
#endif /* !HAVE_COGL_GL */
|
|
|
|
static int
|
|
tile_dimension (int to_fill,
|
|
int start_size,
|
|
int waste,
|
|
ClutterTextureTileDimension *tiles)
|
|
{
|
|
int pos = 0;
|
|
int n_tiles = 0;
|
|
int size = start_size;
|
|
|
|
while (TRUE)
|
|
{
|
|
if (tiles)
|
|
{
|
|
tiles[n_tiles].pos = pos;
|
|
tiles[n_tiles].size = size;
|
|
tiles[n_tiles].waste = 0;
|
|
}
|
|
|
|
n_tiles++;
|
|
|
|
if (to_fill <= size)
|
|
{
|
|
if (tiles)
|
|
tiles[n_tiles-1].waste = size - to_fill;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
to_fill -= size; pos += size;
|
|
while (size >= 2 * to_fill || size - to_fill > waste)
|
|
size /= 2;
|
|
}
|
|
}
|
|
|
|
return n_tiles;
|
|
}
|
|
|
|
static void
|
|
texture_init_tiles (ClutterTexture *texture)
|
|
{
|
|
ClutterTexturePrivate *priv;
|
|
gint x_pot, y_pot;
|
|
|
|
priv = texture->priv;
|
|
|
|
x_pot = clutter_util_next_p2 (priv->width);
|
|
y_pot = clutter_util_next_p2 (priv->height);
|
|
|
|
while (!(cogl_texture_can_size (CGL_TEXTURE_2D,
|
|
priv->pixel_format,
|
|
priv->pixel_type,
|
|
x_pot, y_pot)
|
|
&& (x_pot - priv->width < priv->max_tile_waste)
|
|
&& (y_pot - priv->height < priv->max_tile_waste)))
|
|
{
|
|
CLUTTER_NOTE (TEXTURE, "x_pot:%i - width:%i < max_waste:%i",
|
|
x_pot,
|
|
priv->width,
|
|
priv->max_tile_waste);
|
|
|
|
CLUTTER_NOTE (TEXTURE, "y_pot:%i - height:%i < max_waste:%i",
|
|
y_pot,
|
|
priv->height,
|
|
priv->max_tile_waste);
|
|
|
|
if (x_pot > y_pot)
|
|
x_pot /= 2;
|
|
else
|
|
y_pot /= 2;
|
|
|
|
g_return_if_fail (x_pot != 0 || y_pot != 0);
|
|
}
|
|
|
|
if (priv->x_tiles)
|
|
g_free (priv->x_tiles);
|
|
|
|
priv->n_x_tiles = tile_dimension (priv->width, x_pot,
|
|
priv->max_tile_waste, NULL);
|
|
if (priv->y_tiles)
|
|
g_free (priv->y_tiles);
|
|
priv->n_y_tiles = tile_dimension (priv->height, y_pot,
|
|
priv->max_tile_waste, NULL);
|
|
|
|
if (priv->n_x_tiles == 1 && priv->n_y_tiles == 1)
|
|
{
|
|
/* So were not actually tiled... */
|
|
priv->n_x_tiles = priv->n_y_tiles = 0;
|
|
priv->is_tiled = FALSE;
|
|
return;
|
|
}
|
|
|
|
priv->x_tiles = g_new (ClutterTextureTileDimension, priv->n_x_tiles);
|
|
tile_dimension (priv->width, x_pot, priv->max_tile_waste, priv->x_tiles);
|
|
|
|
priv->y_tiles = g_new (ClutterTextureTileDimension, priv->n_y_tiles);
|
|
tile_dimension (priv->height, y_pot, priv->max_tile_waste, priv->y_tiles);
|
|
|
|
CLUTTER_NOTE (TEXTURE,
|
|
"x_pot:%i, width:%i, y_pot:%i, height: %i "
|
|
"max_waste:%i, n_x_tiles: %i, n_y_tiles: %i",
|
|
x_pot, priv->width, y_pot, priv->height,
|
|
priv->max_tile_waste,
|
|
priv->n_x_tiles, priv->n_y_tiles);
|
|
}
|
|
|
|
static void
|
|
texture_render_to_gl_quad (ClutterTexture *texture,
|
|
int x_1,
|
|
int y_1,
|
|
int x_2,
|
|
int y_2)
|
|
{
|
|
int qx1 = 0, qx2 = 0, qy1 = 0, qy2 = 0;
|
|
int qwidth = 0, qheight = 0;
|
|
int x, y, i =0, lastx = 0, lasty = 0;
|
|
float tx, ty;
|
|
|
|
ClutterTexturePrivate *priv;
|
|
|
|
priv = texture->priv;
|
|
|
|
qwidth = x_2 - x_1;
|
|
qheight = y_2 - y_1;
|
|
|
|
if (!priv->is_tiled)
|
|
{
|
|
cogl_texture_bind (priv->target_type, priv->tiles[0]);
|
|
|
|
if (priv->target_type == CGL_TEXTURE_2D) /* POT */
|
|
{
|
|
tx = (float) priv->width / clutter_util_next_p2 (priv->width);
|
|
ty = (float) priv->height / clutter_util_next_p2 (priv->height);
|
|
}
|
|
else
|
|
{
|
|
tx = (float) priv->width;
|
|
ty = (float) priv->height;
|
|
|
|
}
|
|
|
|
qx1 = x_1; qx2 = x_2;
|
|
qy1 = y_1; qy2 = y_2;
|
|
|
|
cogl_texture_quad (x_1, x_2, y_1, y_2,
|
|
0,
|
|
0,
|
|
CLUTTER_FLOAT_TO_FIXED (tx),
|
|
CLUTTER_FLOAT_TO_FIXED (ty));
|
|
|
|
return;
|
|
}
|
|
|
|
for (x = 0; x < priv->n_x_tiles; x++)
|
|
{
|
|
lasty = 0;
|
|
|
|
for (y=0; y < priv->n_y_tiles; y++)
|
|
{
|
|
int actual_w, actual_h;
|
|
|
|
cogl_texture_bind (priv->target_type, priv->tiles[i]);
|
|
|
|
actual_w = priv->x_tiles[x].size - priv->x_tiles[x].waste;
|
|
actual_h = priv->y_tiles[y].size - priv->y_tiles[y].waste;
|
|
|
|
CLUTTER_NOTE (TEXTURE,
|
|
"rendering text tile x: %i, y: %i - %ix%i",
|
|
x, y, actual_w, actual_h);
|
|
|
|
tx = (float) actual_w / priv->x_tiles[x].size;
|
|
ty = (float) actual_h / priv->y_tiles[y].size;
|
|
|
|
qx1 = x_1 + lastx;
|
|
qx2 = qx1 + ((qwidth * actual_w ) / priv->width );
|
|
|
|
qy1 = y_1 + lasty;
|
|
qy2 = qy1 + ((qheight * actual_h) / priv->height );
|
|
|
|
cogl_texture_quad (qx1, qx2, qy1, qy2,
|
|
0,
|
|
0,
|
|
CLUTTER_FLOAT_TO_FIXED (tx),
|
|
CLUTTER_FLOAT_TO_FIXED (ty));
|
|
|
|
lasty += (qy2 - qy1) ;
|
|
|
|
i++;
|
|
}
|
|
lastx += (qx2 - qx1);
|
|
}
|
|
}
|
|
|
|
static void
|
|
texture_free_gl_resources (ClutterTexture *texture)
|
|
{
|
|
ClutterTexturePrivate *priv;
|
|
|
|
priv = texture->priv;
|
|
|
|
CLUTTER_MARK();
|
|
|
|
if (priv->tiles)
|
|
{
|
|
if (!priv->is_tiled)
|
|
cogl_textures_destroy (1, priv->tiles);
|
|
else
|
|
cogl_textures_destroy (priv->n_x_tiles * priv->n_y_tiles, priv->tiles);
|
|
|
|
g_free (priv->tiles);
|
|
priv->tiles = NULL;
|
|
}
|
|
|
|
if (priv->x_tiles)
|
|
{
|
|
g_free (priv->x_tiles);
|
|
priv->x_tiles = NULL;
|
|
}
|
|
|
|
if (priv->y_tiles)
|
|
{
|
|
g_free (priv->y_tiles);
|
|
priv->y_tiles = NULL;
|
|
}
|
|
}
|
|
|
|
static void inline
|
|
texture_upload_data (ClutterTexture *texture,
|
|
const guchar *data,
|
|
gboolean has_alpha,
|
|
gint width,
|
|
gint height,
|
|
gint rowstride,
|
|
gint bpp)
|
|
{
|
|
ClutterTexturePrivate *priv;
|
|
gint x, y;
|
|
gint i = 0;
|
|
gboolean create_textures = FALSE;
|
|
GdkPixbuf *master_pixbuf = NULL;
|
|
|
|
priv = texture->priv;
|
|
|
|
g_return_if_fail (data != NULL);
|
|
|
|
CLUTTER_MARK();
|
|
|
|
if (!priv->is_tiled)
|
|
{
|
|
/* Single Texture */
|
|
if (!priv->tiles)
|
|
{
|
|
priv->tiles = g_new (COGLuint, 1);
|
|
glGenTextures (1, priv->tiles);
|
|
create_textures = TRUE;
|
|
}
|
|
|
|
CLUTTER_NOTE (TEXTURE, "syncing for single tile");
|
|
|
|
cogl_texture_bind (priv->target_type, priv->tiles[0]);
|
|
cogl_texture_set_alignment (priv->target_type, 4, priv->width);
|
|
|
|
cogl_texture_set_filters
|
|
(priv->target_type,
|
|
priv->filter_quality ? CGL_LINEAR : CGL_NEAREST,
|
|
priv->filter_quality ? CGL_LINEAR : CGL_NEAREST);
|
|
|
|
cogl_texture_set_wrap (priv->target_type,
|
|
priv->repeat_x ? CGL_REPEAT : CGL_CLAMP_TO_EDGE,
|
|
priv->repeat_y ? CGL_REPEAT : CGL_CLAMP_TO_EDGE);
|
|
|
|
if (create_textures)
|
|
{
|
|
gint tex_width, tex_height;
|
|
|
|
tex_width = priv->width;
|
|
tex_height = priv->height;
|
|
|
|
if (priv->target_type == CGL_TEXTURE_2D) /* POT */
|
|
{
|
|
tex_width = clutter_util_next_p2 (priv->width);
|
|
tex_height = clutter_util_next_p2 (priv->height);
|
|
}
|
|
|
|
cogl_texture_image_2d (priv->target_type,
|
|
CGL_RGBA,
|
|
tex_width,
|
|
tex_height,
|
|
priv->pixel_format,
|
|
priv->pixel_type,
|
|
NULL);
|
|
}
|
|
|
|
cogl_texture_sub_image_2d (priv->target_type,
|
|
0,
|
|
0,
|
|
width,
|
|
height,
|
|
priv->pixel_format,
|
|
priv->pixel_type,
|
|
data);
|
|
return;
|
|
}
|
|
|
|
/* Multiple tiled texture */
|
|
|
|
CLUTTER_NOTE (TEXTURE,
|
|
"syncing for multiple tiles for %ix%i pixbuf",
|
|
priv->width, priv->height);
|
|
|
|
g_return_if_fail (priv->x_tiles != NULL && priv->y_tiles != NULL);
|
|
|
|
master_pixbuf = gdk_pixbuf_new_from_data (data,
|
|
GDK_COLORSPACE_RGB,
|
|
has_alpha,
|
|
8,
|
|
width, height, rowstride,
|
|
NULL, NULL);
|
|
|
|
if (priv->tiles == NULL)
|
|
{
|
|
priv->tiles = g_new (COGLuint, priv->n_x_tiles * priv->n_y_tiles);
|
|
glGenTextures (priv->n_x_tiles * priv->n_y_tiles, priv->tiles);
|
|
create_textures = TRUE;
|
|
}
|
|
|
|
for (x = 0; x < priv->n_x_tiles; x++)
|
|
for (y = 0; y < priv->n_y_tiles; y++)
|
|
{
|
|
GdkPixbuf *pixtmp;
|
|
gint src_h, src_w;
|
|
|
|
src_w = priv->x_tiles[x].size;
|
|
src_h = priv->y_tiles[y].size;
|
|
|
|
pixtmp = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
|
|
has_alpha,
|
|
8,
|
|
src_w, src_h);
|
|
|
|
/* clip */
|
|
if (priv->x_tiles[x].pos + src_w > priv->width)
|
|
src_w = priv->width - priv->x_tiles[x].pos;
|
|
|
|
if (priv->y_tiles[y].pos + src_h > priv->height)
|
|
src_h = priv->height - priv->y_tiles[y].pos;
|
|
|
|
gdk_pixbuf_copy_area (master_pixbuf,
|
|
priv->x_tiles[x].pos,
|
|
priv->y_tiles[y].pos,
|
|
src_w,
|
|
src_h,
|
|
pixtmp,
|
|
0, 0);
|
|
#ifdef CLUTTER_DUMP_TILES
|
|
{
|
|
gchar *filename;
|
|
|
|
filename =
|
|
g_strdup_printf("/tmp/%i-%i-%i.png",
|
|
clutter_actor_get_gid (CLUTTER_ACTOR (texture)),
|
|
x, y);
|
|
|
|
printf ("saving %s\n", filename);
|
|
|
|
gdk_pixbuf_save (pixtmp, filename , "png", NULL, NULL);
|
|
g_free (filename);
|
|
}
|
|
#endif
|
|
|
|
cogl_texture_bind (priv->target_type, priv->tiles[i]);
|
|
|
|
cogl_texture_set_alignment (priv->target_type,
|
|
4, priv->x_tiles[x].size);
|
|
|
|
cogl_texture_set_filters
|
|
(priv->target_type,
|
|
priv->filter_quality ? CGL_LINEAR : CGL_NEAREST,
|
|
priv->filter_quality ? CGL_LINEAR : CGL_NEAREST);
|
|
|
|
cogl_texture_set_wrap (priv->target_type,
|
|
priv->repeat_x ? CGL_REPEAT : CGL_CLAMP_TO_EDGE,
|
|
priv->repeat_y ? CGL_REPEAT : CGL_CLAMP_TO_EDGE);
|
|
if (create_textures)
|
|
{
|
|
cogl_texture_image_2d (priv->target_type,
|
|
CGL_RGBA,
|
|
gdk_pixbuf_get_width (pixtmp),
|
|
gdk_pixbuf_get_height (pixtmp),
|
|
priv->pixel_format,
|
|
priv->pixel_type,
|
|
gdk_pixbuf_get_pixels (pixtmp));
|
|
}
|
|
else
|
|
{
|
|
/* Textures already created, so just update whats inside
|
|
*/
|
|
cogl_texture_sub_image_2d (priv->target_type,
|
|
0,
|
|
0,
|
|
gdk_pixbuf_get_width (pixtmp),
|
|
gdk_pixbuf_get_height (pixtmp),
|
|
priv->pixel_format,
|
|
priv->pixel_type,
|
|
gdk_pixbuf_get_pixels (pixtmp));
|
|
}
|
|
|
|
g_object_unref (pixtmp);
|
|
|
|
i++;
|
|
}
|
|
|
|
g_object_unref (master_pixbuf);
|
|
}
|
|
|
|
static void
|
|
clutter_texture_unrealize (ClutterActor *actor)
|
|
{
|
|
ClutterTexture *texture;
|
|
ClutterTexturePrivate *priv;
|
|
|
|
texture = CLUTTER_TEXTURE(actor);
|
|
priv = texture->priv;
|
|
|
|
if (priv->tiles == NULL)
|
|
return;
|
|
|
|
/* there's no need to read the pixels back when unrealizing inside
|
|
* a dispose run, and the dispose() call will release the GL
|
|
* texture data as well, so we can safely bail out now
|
|
*/
|
|
if (CLUTTER_PRIVATE_FLAGS (actor) & CLUTTER_ACTOR_IN_DESTRUCTION)
|
|
return;
|
|
|
|
CLUTTER_MARK();
|
|
|
|
if (priv->fbo_source)
|
|
{
|
|
/* Free up our fbo handle and texture resources, realize will recreate */
|
|
cogl_offscreen_destroy (priv->fbo_handle);
|
|
priv->fbo_handle = 0;
|
|
texture_free_gl_resources (texture);
|
|
return;
|
|
}
|
|
|
|
if (clutter_feature_available (CLUTTER_FEATURE_TEXTURE_READ_PIXELS))
|
|
{
|
|
/* Move image data from video to main memory.
|
|
* GL/ES cant do this - it probably makes sense
|
|
* to move this kind of thing into a ClutterProxyTexture
|
|
* where this behaviour can be better controlled.
|
|
*
|
|
* Or make it controllable via a property.
|
|
*/
|
|
if (priv->local_pixbuf == NULL)
|
|
{
|
|
priv->local_pixbuf = clutter_texture_get_pixbuf (texture);
|
|
CLUTTER_NOTE (TEXTURE, "moved pixels into system (pixbuf) mem");
|
|
}
|
|
|
|
texture_free_gl_resources (texture);
|
|
}
|
|
|
|
CLUTTER_NOTE (TEXTURE, "Texture unrealized");
|
|
}
|
|
|
|
static void
|
|
clutter_texture_realize (ClutterActor *actor)
|
|
{
|
|
ClutterTexture *texture;
|
|
ClutterTexturePrivate *priv;
|
|
|
|
texture = CLUTTER_TEXTURE(actor);
|
|
priv = texture->priv;
|
|
|
|
CLUTTER_MARK();
|
|
|
|
if (priv->fbo_source)
|
|
{
|
|
/* Handle FBO's */
|
|
|
|
priv->tiles = g_new (COGLuint, 1);
|
|
|
|
/* FIXME: needs a cogl wrapper */
|
|
glGenTextures (1, priv->tiles);
|
|
|
|
cogl_texture_bind (priv->target_type, priv->tiles[0]);
|
|
|
|
cogl_texture_image_2d (priv->target_type,
|
|
CGL_RGBA,
|
|
priv->width,
|
|
priv->height,
|
|
priv->pixel_format,
|
|
priv->pixel_type,
|
|
NULL);
|
|
|
|
priv->fbo_handle = cogl_offscreen_create (priv->tiles[0]);
|
|
|
|
if (priv->fbo_handle == 0)
|
|
{
|
|
g_warning ("%s: Offscreen texture creation failed", G_STRLOC);
|
|
CLUTTER_ACTOR_UNSET_FLAGS (actor, CLUTTER_ACTOR_REALIZED);
|
|
return;
|
|
}
|
|
|
|
clutter_actor_set_size (actor, priv->width, priv->height);
|
|
return;
|
|
}
|
|
|
|
if (priv->local_pixbuf != NULL)
|
|
{
|
|
/* Move any local image data we have from unrealization
|
|
* back into video memory.
|
|
*/
|
|
if (priv->is_tiled)
|
|
texture_init_tiles (texture);
|
|
clutter_texture_set_pixbuf (texture, priv->local_pixbuf, NULL);
|
|
g_object_unref (priv->local_pixbuf);
|
|
priv->local_pixbuf = NULL;
|
|
}
|
|
else
|
|
{
|
|
if (clutter_feature_available (CLUTTER_FEATURE_TEXTURE_READ_PIXELS))
|
|
{
|
|
/* Dont allow realization with no pixbuf - note set_pixbuf/data
|
|
* will set realize flags.
|
|
*/
|
|
CLUTTER_NOTE (TEXTURE,
|
|
"Texture has no image data cannot realize");
|
|
|
|
CLUTTER_NOTE (TEXTURE, "flags %i", actor->flags);
|
|
CLUTTER_ACTOR_UNSET_FLAGS (actor, CLUTTER_ACTOR_REALIZED);
|
|
CLUTTER_NOTE (TEXTURE, "flags %i", actor->flags);
|
|
return;
|
|
}
|
|
}
|
|
|
|
CLUTTER_NOTE (TEXTURE, "Texture realized");
|
|
}
|
|
|
|
static void
|
|
clutter_texture_show (ClutterActor *self)
|
|
{
|
|
ClutterActorClass *parent_class;
|
|
|
|
/* chain up parent show */
|
|
parent_class = CLUTTER_ACTOR_CLASS (clutter_texture_parent_class);
|
|
if (parent_class->show)
|
|
parent_class->show (self);
|
|
|
|
clutter_actor_realize (self);
|
|
}
|
|
|
|
static void
|
|
clutter_texture_hide (ClutterActor *self)
|
|
{
|
|
ClutterActorClass *parent_class;
|
|
|
|
/* chain up parent hide */
|
|
parent_class = CLUTTER_ACTOR_CLASS (clutter_texture_parent_class);
|
|
if (parent_class->hide)
|
|
parent_class->hide (self);
|
|
|
|
clutter_actor_unrealize (self);
|
|
}
|
|
|
|
static void
|
|
clutter_texture_paint (ClutterActor *self)
|
|
{
|
|
ClutterTexture *texture = CLUTTER_TEXTURE (self);
|
|
ClutterTexturePrivate *priv = texture->priv;
|
|
gint x_1, y_1, x_2, y_2;
|
|
ClutterColor col = { 0xff, 0xff, 0xff, 0xff };
|
|
|
|
if (!CLUTTER_ACTOR_IS_REALIZED (CLUTTER_ACTOR(texture)))
|
|
clutter_actor_realize (CLUTTER_ACTOR(texture));
|
|
|
|
if (priv->tiles == NULL)
|
|
{
|
|
/* We just need do debug this state, it doesn't really need to
|
|
* throw a an error as what previously happened. Sub classes
|
|
* quite likely may not be able to realize.
|
|
*/
|
|
CLUTTER_NOTE (PAINT, "unable to paint texture '%s', contains no tiles",
|
|
clutter_actor_get_name (self)
|
|
? clutter_actor_get_name (self)
|
|
: "unknown");
|
|
return;
|
|
}
|
|
|
|
if (priv->fbo_handle)
|
|
{
|
|
ClutterMainContext *context;
|
|
ClutterShader *shader = NULL;
|
|
|
|
context = clutter_context_get_default ();
|
|
|
|
if (context->shaders)
|
|
shader = clutter_actor_get_shader (context->shaders->data);
|
|
|
|
/* Temporarily turn of the shader on the top of the context's
|
|
* shader stack, to restore the GL pipeline to it's natural state.
|
|
*/
|
|
if (shader)
|
|
clutter_shader_set_is_enabled (shader, FALSE);
|
|
|
|
cogl_offscreen_redirect_start (priv->fbo_handle,
|
|
priv->width, priv->height);
|
|
|
|
/* Render out actor scene to fbo */
|
|
clutter_actor_paint (priv->fbo_source);
|
|
|
|
cogl_offscreen_redirect_end (priv->fbo_handle,
|
|
CLUTTER_STAGE_WIDTH(),
|
|
CLUTTER_STAGE_HEIGHT());
|
|
|
|
/* If there is a shader on top of the shader stack, turn it back on. */
|
|
if (shader)
|
|
clutter_shader_set_is_enabled (shader, TRUE);
|
|
|
|
glBindTexture(CGL_TEXTURE_RECTANGLE_ARB, priv->tiles[0]);
|
|
}
|
|
|
|
CLUTTER_NOTE (PAINT,
|
|
"painting texture '%s'",
|
|
clutter_actor_get_name (self) ? clutter_actor_get_name (self)
|
|
: "unknown");
|
|
cogl_push_matrix ();
|
|
|
|
switch (priv->target_type)
|
|
{
|
|
case CGL_TEXTURE_2D:
|
|
cogl_enable (CGL_ENABLE_TEXTURE_2D|CGL_ENABLE_BLEND);
|
|
break;
|
|
case CGL_TEXTURE_RECTANGLE_ARB:
|
|
cogl_enable (CGL_ENABLE_TEXTURE_RECT|CGL_ENABLE_BLEND);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
col.alpha = clutter_actor_get_opacity (self);
|
|
|
|
cogl_color (&col);
|
|
|
|
clutter_actor_get_coords (self, &x_1, &y_1, &x_2, &y_2);
|
|
|
|
CLUTTER_NOTE (PAINT, "paint to x1: %i, y1: %i x2: %i, y2: %i "
|
|
"opacity: %i",
|
|
x_1, y_1, x_2, y_2,
|
|
clutter_actor_get_opacity (self));
|
|
|
|
/* Paint will of translated us */
|
|
texture_render_to_gl_quad (texture, 0, 0, x_2 - x_1, y_2 - y_1);
|
|
|
|
cogl_pop_matrix ();
|
|
}
|
|
|
|
static void
|
|
clutter_texture_request_coords (ClutterActor *self,
|
|
ClutterActorBox *box)
|
|
{
|
|
ClutterTexture *texture = CLUTTER_TEXTURE (self);
|
|
ClutterActorBox old_request;
|
|
|
|
clutter_actor_query_coords (self, &old_request);
|
|
|
|
if (((box->x2 - box->x1) != (old_request.x2 - old_request.x1)) ||
|
|
((box->y2 - box->y1) != (old_request.y2 - old_request.y1)))
|
|
texture->priv->sync_actor_size = FALSE;
|
|
|
|
CLUTTER_ACTOR_CLASS (clutter_texture_parent_class)->request_coords (self, box);
|
|
}
|
|
|
|
static void
|
|
clutter_texture_dispose (GObject *object)
|
|
{
|
|
ClutterTexture *texture = CLUTTER_TEXTURE (object);
|
|
ClutterTexturePrivate *priv;
|
|
|
|
priv = texture->priv;
|
|
|
|
texture_free_gl_resources (texture);
|
|
texture_fbo_free_resources (texture);
|
|
|
|
if (priv->local_pixbuf != NULL)
|
|
{
|
|
g_object_unref (priv->local_pixbuf);
|
|
priv->local_pixbuf = NULL;
|
|
}
|
|
|
|
G_OBJECT_CLASS (clutter_texture_parent_class)->dispose (object);
|
|
}
|
|
|
|
static void
|
|
clutter_texture_set_property (GObject *object,
|
|
guint prop_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
ClutterTexture *texture;
|
|
ClutterTexturePrivate *priv;
|
|
|
|
texture = CLUTTER_TEXTURE(object);
|
|
priv = texture->priv;
|
|
|
|
switch (prop_id)
|
|
{
|
|
case PROP_PIXBUF:
|
|
if (g_value_get_object (value))
|
|
clutter_texture_set_pixbuf (texture,
|
|
GDK_PIXBUF (g_value_get_object (value)),
|
|
NULL);
|
|
break;
|
|
case PROP_USE_TILES:
|
|
priv->is_tiled = g_value_get_boolean (value);
|
|
|
|
if (priv->target_type == CGL_TEXTURE_RECTANGLE_ARB && priv->is_tiled)
|
|
priv->target_type = CGL_TEXTURE_2D;
|
|
|
|
CLUTTER_NOTE (TEXTURE, "Texture is tiled ? %s",
|
|
priv->is_tiled ? "yes" : "no");
|
|
break;
|
|
case PROP_MAX_TILE_WASTE:
|
|
priv->max_tile_waste = g_value_get_int (value);
|
|
break;
|
|
case PROP_SYNC_SIZE:
|
|
priv->sync_actor_size = g_value_get_boolean (value);
|
|
break;
|
|
case PROP_REPEAT_X:
|
|
priv->repeat_x = g_value_get_boolean (value);
|
|
break;
|
|
case PROP_REPEAT_Y:
|
|
priv->repeat_y = g_value_get_boolean (value);
|
|
break;
|
|
case PROP_FILTER_QUALITY:
|
|
priv->filter_quality = g_value_get_int (value);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
clutter_texture_get_property (GObject *object,
|
|
guint prop_id,
|
|
GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
ClutterTexture *texture;
|
|
ClutterTexturePrivate *priv;
|
|
|
|
texture = CLUTTER_TEXTURE(object);
|
|
priv = texture->priv;
|
|
|
|
switch (prop_id)
|
|
{
|
|
case PROP_PIXBUF:
|
|
{
|
|
GdkPixbuf *pixb;
|
|
pixb = clutter_texture_get_pixbuf (texture);
|
|
g_value_take_object (value, pixb);
|
|
}
|
|
break;
|
|
case PROP_USE_TILES:
|
|
g_value_set_boolean (value, priv->is_tiled);
|
|
break;
|
|
case PROP_MAX_TILE_WASTE:
|
|
g_value_set_int (value, priv->max_tile_waste);
|
|
break;
|
|
case PROP_PIXEL_TYPE:
|
|
g_value_set_int (value, priv->pixel_type);
|
|
break;
|
|
case PROP_PIXEL_FORMAT:
|
|
g_value_set_int (value, priv->pixel_format);
|
|
break;
|
|
case PROP_SYNC_SIZE:
|
|
g_value_set_boolean (value, priv->sync_actor_size);
|
|
break;
|
|
case PROP_REPEAT_X:
|
|
g_value_set_boolean (value, priv->repeat_x);
|
|
break;
|
|
case PROP_REPEAT_Y:
|
|
g_value_set_boolean (value, priv->repeat_y);
|
|
break;
|
|
case PROP_FILTER_QUALITY:
|
|
g_value_set_int (value, priv->filter_quality);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
clutter_texture_class_init (ClutterTextureClass *klass)
|
|
{
|
|
GObjectClass *gobject_class;
|
|
ClutterActorClass *actor_class;
|
|
|
|
gobject_class = (GObjectClass*) klass;
|
|
actor_class = (ClutterActorClass*) klass;
|
|
|
|
g_type_class_add_private (klass, sizeof (ClutterTexturePrivate));
|
|
|
|
actor_class->paint = clutter_texture_paint;
|
|
actor_class->realize = clutter_texture_realize;
|
|
actor_class->unrealize = clutter_texture_unrealize;
|
|
actor_class->show = clutter_texture_show;
|
|
actor_class->hide = clutter_texture_hide;
|
|
actor_class->request_coords = clutter_texture_request_coords;
|
|
|
|
gobject_class->dispose = clutter_texture_dispose;
|
|
gobject_class->set_property = clutter_texture_set_property;
|
|
gobject_class->get_property = clutter_texture_get_property;
|
|
|
|
g_object_class_install_property
|
|
(gobject_class, PROP_PIXBUF,
|
|
g_param_spec_object ("pixbuf",
|
|
"Pixbuf source for Texture.",
|
|
"Pixbuf source for Texture.",
|
|
GDK_TYPE_PIXBUF,
|
|
CLUTTER_PARAM_READWRITE));
|
|
|
|
g_object_class_install_property
|
|
(gobject_class, PROP_USE_TILES,
|
|
g_param_spec_boolean ("tiled",
|
|
"Enable use of tiled textures",
|
|
"Enables the use of tiled GL textures to more "
|
|
"efficiently use available texture memory",
|
|
/* FIXME: This default set at runtime :/
|
|
* As tiling depends on what GL features available.
|
|
* Need to figure out better solution
|
|
*/
|
|
(clutter_feature_available
|
|
(CLUTTER_FEATURE_TEXTURE_RECTANGLE) == FALSE),
|
|
G_PARAM_CONSTRUCT_ONLY | CLUTTER_PARAM_READWRITE));
|
|
|
|
g_object_class_install_property
|
|
(gobject_class, PROP_SYNC_SIZE,
|
|
g_param_spec_boolean ("sync-size",
|
|
"Sync size of actor",
|
|
"Auto sync size of actor to underlying pixbuf "
|
|
"dimensions",
|
|
TRUE,
|
|
CLUTTER_PARAM_READWRITE));
|
|
|
|
g_object_class_install_property
|
|
(gobject_class, PROP_REPEAT_X,
|
|
g_param_spec_boolean ("repeat-x",
|
|
"Tile underlying pixbuf in x direction",
|
|
"Reapeat underlying pixbuf rather than scale "
|
|
"in x direction. Currently ignored",
|
|
FALSE,
|
|
CLUTTER_PARAM_READWRITE));
|
|
|
|
g_object_class_install_property
|
|
(gobject_class, PROP_REPEAT_Y,
|
|
g_param_spec_boolean ("repeat-y",
|
|
"Tile underlying pixbuf in y direction",
|
|
"Reapeat underlying pixbuf rather than scale "
|
|
"in y direction. Currently ignored",
|
|
FALSE,
|
|
CLUTTER_PARAM_READWRITE));
|
|
|
|
/* FIXME: Ideally this option needs to have some kind of global
|
|
* overide as to imporve performance.
|
|
*/
|
|
g_object_class_install_property
|
|
(gobject_class, PROP_FILTER_QUALITY,
|
|
g_param_spec_int ("filter-quality",
|
|
"Quality of filter used when scaling a texture",
|
|
"Values 0 and 1 current only supported, with 0 "
|
|
"being lower quality but fast, 1 being better "
|
|
"quality but slower. ( Currently just maps to "
|
|
"GL_NEAREST / GL_LINEAR )",
|
|
0,
|
|
G_MAXINT,
|
|
1,
|
|
CLUTTER_PARAM_READWRITE));
|
|
|
|
g_object_class_install_property
|
|
(gobject_class, PROP_MAX_TILE_WASTE,
|
|
g_param_spec_int ("tile-waste",
|
|
"Tile dimension to waste",
|
|
"Max wastage dimension of a texture when using "
|
|
"tiled textures. Bigger values use less textures, "
|
|
"smaller values less texture memory.",
|
|
0,
|
|
G_MAXINT,
|
|
64,
|
|
G_PARAM_CONSTRUCT_ONLY | CLUTTER_PARAM_READWRITE));
|
|
|
|
g_object_class_install_property
|
|
(gobject_class, PROP_PIXEL_TYPE,
|
|
g_param_spec_int ("pixel-type",
|
|
"Texture Pixel Type",
|
|
"GL texture pixel type used",
|
|
0,
|
|
G_MAXINT,
|
|
PIXEL_TYPE,
|
|
G_PARAM_READABLE));
|
|
|
|
g_object_class_install_property
|
|
(gobject_class, PROP_PIXEL_FORMAT,
|
|
g_param_spec_int ("pixel-format",
|
|
"Texture pixel format",
|
|
"GL texture pixel format used",
|
|
0,
|
|
G_MAXINT,
|
|
CGL_RGBA,
|
|
G_PARAM_READABLE));
|
|
|
|
/**
|
|
* ClutterTexture::size-change:
|
|
* @texture: the texture which received the signal
|
|
* @width: the width of the new texture
|
|
* @height: the height of the new texture
|
|
*
|
|
* The ::size-change signal is emitted each time the size of the
|
|
* pixbuf used by @texture changes. The new size is given as
|
|
* argument to the callback.
|
|
*/
|
|
texture_signals[SIZE_CHANGE] =
|
|
g_signal_new ("size-change",
|
|
G_TYPE_FROM_CLASS (gobject_class),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (ClutterTextureClass, size_change),
|
|
NULL, NULL,
|
|
clutter_marshal_VOID__INT_INT,
|
|
G_TYPE_NONE,
|
|
2, G_TYPE_INT, G_TYPE_INT);
|
|
/**
|
|
* ClutterTexture::pixbuf-change:
|
|
* @texture: the texture which received the signal
|
|
*
|
|
* The ::pixbuf-change signal is emitted each time the pixbuf
|
|
* used by @texture changes.
|
|
*/
|
|
texture_signals[PIXBUF_CHANGE] =
|
|
g_signal_new ("pixbuf-change",
|
|
G_TYPE_FROM_CLASS (gobject_class),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (ClutterTextureClass, pixbuf_change),
|
|
NULL, NULL,
|
|
g_cclosure_marshal_VOID__VOID,
|
|
G_TYPE_NONE,
|
|
0);
|
|
}
|
|
|
|
static void
|
|
clutter_texture_init (ClutterTexture *self)
|
|
{
|
|
ClutterTexturePrivate *priv;
|
|
|
|
self->priv = priv = CLUTTER_TEXTURE_GET_PRIVATE (self);
|
|
|
|
priv->max_tile_waste = 64;
|
|
priv->filter_quality = 1;
|
|
priv->is_tiled = TRUE;
|
|
priv->pixel_type = PIXEL_TYPE;
|
|
priv->pixel_format = CGL_RGBA;
|
|
priv->repeat_x = FALSE;
|
|
priv->repeat_y = FALSE;
|
|
priv->sync_actor_size = TRUE;
|
|
|
|
if (clutter_feature_available (CLUTTER_FEATURE_TEXTURE_RECTANGLE))
|
|
{
|
|
priv->target_type = CGL_TEXTURE_RECTANGLE_ARB;
|
|
priv->is_tiled = FALSE;
|
|
}
|
|
else
|
|
priv->target_type = CGL_TEXTURE_2D;
|
|
}
|
|
|
|
static void
|
|
pixbuf_destroy_notify (guchar *pixels, gpointer data)
|
|
{
|
|
g_free (pixels);
|
|
}
|
|
|
|
static GdkPixbuf *
|
|
texture_get_tile_pixbuf (ClutterTexture *texture,
|
|
COGLuint texture_id,
|
|
gint bpp,
|
|
gint ix,
|
|
gint iy)
|
|
{
|
|
#ifdef HAVE_COGL_GL
|
|
ClutterTexturePrivate *priv;
|
|
guchar *pixels = NULL;
|
|
guint tex_width, tex_height;
|
|
|
|
priv = texture->priv;
|
|
cogl_texture_bind (priv->target_type, texture_id);
|
|
|
|
if (!priv->is_tiled)
|
|
{
|
|
tex_width = priv->width;
|
|
tex_height = priv->height;
|
|
}
|
|
else
|
|
{
|
|
tex_width = priv->x_tiles[ix].size;
|
|
tex_height = priv->y_tiles[iy].size;
|
|
}
|
|
|
|
/* Make sure if we aren't using rectangular textures that we increase the
|
|
* texture size accordingly.
|
|
*/
|
|
if (priv->target_type == CGL_TEXTURE_2D) /* POT */
|
|
{
|
|
tex_width = clutter_util_next_p2 (tex_width);
|
|
tex_height = clutter_util_next_p2 (tex_height);
|
|
}
|
|
|
|
cogl_texture_set_alignment (priv->target_type, 4, tex_width);
|
|
|
|
if ((pixels = g_malloc (((tex_width * bpp + 3) &~ 3) * tex_height)) == NULL)
|
|
return NULL;
|
|
|
|
/* Read data from GL texture and return as pixbuf */
|
|
/* No such func in GLES... */
|
|
glGetTexImage (priv->target_type,
|
|
0,
|
|
(priv->pixel_format == CGL_RGBA
|
|
|| priv->pixel_format == CGL_BGRA) ?
|
|
CGL_RGBA : CGL_RGB,
|
|
PIXEL_TYPE,
|
|
(GLvoid *)pixels);
|
|
|
|
|
|
return gdk_pixbuf_new_from_data ((const guchar *)pixels,
|
|
GDK_COLORSPACE_RGB,
|
|
(priv->pixel_format == CGL_RGBA
|
|
|| priv->pixel_format == CGL_BGRA),
|
|
8,
|
|
tex_width,
|
|
tex_height,
|
|
((tex_width * bpp + 3) &~ 3),
|
|
pixbuf_destroy_notify,
|
|
NULL);
|
|
#else
|
|
return NULL;
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
* clutter_texture_get_pixbuf:
|
|
* @texture: A #ClutterTexture
|
|
*
|
|
* Gets a #GdkPixbuf representation of the #ClutterTexture data.
|
|
* The created #GdkPixbuf is not owned by the texture but the caller.
|
|
*
|
|
* Note: %NULL is always returned with OpenGL ES.
|
|
*
|
|
* Return value: A #GdkPixbuf, or %NULL on fail.
|
|
**/
|
|
GdkPixbuf *
|
|
clutter_texture_get_pixbuf (ClutterTexture *texture)
|
|
{
|
|
#if HAVE_COGL_GL
|
|
ClutterTexturePrivate *priv;
|
|
GdkPixbuf *pixbuf;
|
|
GdkPixbuf *pixtmp;
|
|
int bpp = 4;
|
|
|
|
priv = texture->priv;
|
|
|
|
if (priv->tiles == NULL)
|
|
return NULL;
|
|
|
|
if (priv->pixel_format == CGL_YCBCR_MESA)
|
|
return NULL; /* FIXME: convert YUV */
|
|
|
|
if (priv->pixel_format == CGL_RGB || priv->pixel_format == CGL_BGR)
|
|
bpp = 3;
|
|
|
|
pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
|
|
priv->pixel_format == CGL_RGBA ||
|
|
priv->pixel_format == CGL_BGRA,
|
|
8, priv->width, priv->height);
|
|
|
|
if (pixbuf == NULL)
|
|
return NULL;
|
|
|
|
if (!priv->is_tiled)
|
|
{
|
|
pixtmp = texture_get_tile_pixbuf (texture, priv->tiles[0], bpp, 0, 0);
|
|
|
|
if (pixtmp == NULL)
|
|
{
|
|
g_object_unref (pixbuf);
|
|
return NULL;
|
|
}
|
|
|
|
/* Avoid a copy if the texture has the same size as the pixbuf */
|
|
if ((gdk_pixbuf_get_width (pixtmp) == priv->width &&
|
|
gdk_pixbuf_get_height (pixtmp) == priv->height))
|
|
{
|
|
g_object_unref (pixbuf);
|
|
pixbuf = pixtmp;
|
|
}
|
|
else
|
|
{
|
|
gdk_pixbuf_copy_area (pixtmp,
|
|
0, 0,
|
|
priv->width, priv->height,
|
|
pixbuf,
|
|
0, 0);
|
|
g_object_unref (pixtmp);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int x,y,i;
|
|
|
|
i = 0;
|
|
|
|
for (x = 0; x < priv->n_x_tiles; x++)
|
|
for (y = 0; y < priv->n_y_tiles; y++)
|
|
{
|
|
gint src_w, src_h;
|
|
|
|
pixtmp = texture_get_tile_pixbuf (texture, priv->tiles[i], bpp, x, y);
|
|
|
|
if (pixtmp == NULL)
|
|
{
|
|
g_object_unref (pixbuf);
|
|
return NULL;
|
|
}
|
|
|
|
src_w = priv->x_tiles[x].size;
|
|
src_h = priv->y_tiles[y].size;
|
|
|
|
/* Clip */
|
|
if (priv->x_tiles[x].pos + src_w > priv->width)
|
|
src_w = priv->width - priv->x_tiles[x].pos;
|
|
|
|
if (priv->y_tiles[y].pos + src_h > priv->height)
|
|
src_h = priv->height - priv->y_tiles[y].pos;
|
|
|
|
gdk_pixbuf_copy_area (pixtmp,
|
|
0, 0, src_w, src_h,
|
|
pixbuf,
|
|
priv->x_tiles[x].pos, priv->y_tiles[y].pos);
|
|
|
|
g_object_unref (pixtmp);
|
|
|
|
i++;
|
|
}
|
|
|
|
}
|
|
|
|
return pixbuf;
|
|
#else
|
|
|
|
/* FIXME: func call wont work for GLES...
|
|
* features need to reflect this.
|
|
*/
|
|
return NULL;
|
|
#endif
|
|
}
|
|
|
|
/* internal helper function for initialization of
|
|
* clutter_texture_set_from_rgb_data and clutter_texture_set_area_from_rgb_data.
|
|
*/
|
|
static inline gboolean
|
|
texture_prepare_upload (gboolean initialize,
|
|
ClutterTexture *texture,
|
|
const guchar *data,
|
|
gboolean has_alpha,
|
|
gint width,
|
|
gint height,
|
|
gint rowstride,
|
|
gint bpp,
|
|
ClutterTextureFlags flags,
|
|
guchar **copy_data,
|
|
gboolean *texture_dirty,
|
|
gboolean *size_change)
|
|
{
|
|
ClutterTexturePrivate *priv;
|
|
COGLenum prev_format;
|
|
priv = texture->priv;
|
|
|
|
g_return_val_if_fail (data != NULL, FALSE);
|
|
/* Needed for GL_RGBA (internal format) and gdk pixbuf usage */
|
|
g_return_val_if_fail (bpp == 4, FALSE);
|
|
|
|
if (initialize)
|
|
{
|
|
*texture_dirty = *size_change =
|
|
(width != priv->width || height != priv->height) ;
|
|
}
|
|
|
|
prev_format = priv->pixel_format;
|
|
|
|
if (has_alpha)
|
|
priv->pixel_format = CGL_RGBA;
|
|
else
|
|
priv->pixel_format = CGL_RGB;
|
|
|
|
if (flags & CLUTTER_TEXTURE_RGB_FLAG_BGR)
|
|
{
|
|
#if HAVE_COGL_GL
|
|
if (has_alpha)
|
|
priv->pixel_format = CGL_BGRA;
|
|
else
|
|
priv->pixel_format = CGL_BGR;
|
|
#else
|
|
/* GLES has no BGR format*/
|
|
*copy_data = rgb_to_bgr (data, has_alpha, width, height, rowstride);
|
|
#endif /* HAVE_COGL_GL */
|
|
}
|
|
|
|
if (flags & CLUTTER_TEXTURE_RGB_FLAG_PREMULT)
|
|
*copy_data = un_pre_multiply_alpha (data, width, height, rowstride);
|
|
|
|
if (initialize)
|
|
{
|
|
if (prev_format != priv->pixel_format || priv->pixel_type != PIXEL_TYPE)
|
|
*texture_dirty = TRUE;
|
|
}
|
|
else
|
|
{
|
|
if (prev_format != priv->pixel_format || priv->pixel_type != PIXEL_TYPE)
|
|
{
|
|
g_warning ("%s: pixel format or type mismatch", G_STRFUNC);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
priv->pixel_type = PIXEL_TYPE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* clutter_texture_set_from_rgb_data:
|
|
* @texture: A #ClutterTexture
|
|
* @data: Image data in RGBA type colorspace.
|
|
* @has_alpha: Set to TRUE if image data has an alpha channel.
|
|
* @width: Width in pixels of image data.
|
|
* @height: Height in pixels of image data
|
|
* @rowstride: Distance in bytes between row starts.
|
|
* @bpp: bytes per pixel ( Currently only 4 supported )
|
|
* @flags: #ClutterTextureFlags
|
|
* @error: return location for a #GError, or %NULL.
|
|
*
|
|
* Sets #ClutterTexture image data.
|
|
*
|
|
* Note: This function is likely to change in future versions.
|
|
*
|
|
* Return value: %TRUE on success, %FALSE on failure.
|
|
*
|
|
* Since: 0.4.
|
|
**/
|
|
gboolean
|
|
clutter_texture_set_from_rgb_data (ClutterTexture *texture,
|
|
const guchar *data,
|
|
gboolean has_alpha,
|
|
gint width,
|
|
gint height,
|
|
gint rowstride,
|
|
gint bpp,
|
|
ClutterTextureFlags flags,
|
|
GError **error)
|
|
{
|
|
ClutterTexturePrivate *priv;
|
|
guchar *copy_data = NULL;
|
|
gboolean texture_dirty = TRUE, size_change = FALSE;
|
|
|
|
priv = texture->priv;
|
|
|
|
/* Remove FBO if exisiting */
|
|
if (priv->fbo_source)
|
|
texture_fbo_free_resources (texture);
|
|
|
|
if (!texture_prepare_upload (TRUE, texture, data, has_alpha,
|
|
width, height, rowstride, bpp, flags,
|
|
©_data, &texture_dirty, &size_change))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
priv->width = width;
|
|
priv->height = height;
|
|
|
|
if (texture_dirty)
|
|
{
|
|
texture_free_gl_resources (texture);
|
|
|
|
if (priv->is_tiled == FALSE)
|
|
{
|
|
if (priv->target_type == CGL_TEXTURE_RECTANGLE_ARB &&
|
|
!cogl_texture_can_size (CGL_TEXTURE_RECTANGLE_ARB,
|
|
priv->pixel_format,
|
|
priv->pixel_type,
|
|
priv->width,
|
|
priv->height))
|
|
{
|
|
/* If we cant create NPOT tex of this size fall back to tiles */
|
|
CLUTTER_NOTE (TEXTURE,
|
|
"Cannot make npots of size %ix%i "
|
|
"falling back to tiled",
|
|
priv->width,
|
|
priv->height);
|
|
|
|
priv->target_type = CGL_TEXTURE_2D;
|
|
}
|
|
|
|
if (priv->target_type == CGL_TEXTURE_2D &&
|
|
!cogl_texture_can_size (CGL_TEXTURE_2D,
|
|
priv->pixel_format,
|
|
priv->pixel_type,
|
|
clutter_util_next_p2 (priv->width),
|
|
clutter_util_next_p2 (priv->height)))
|
|
{
|
|
priv->is_tiled = TRUE;
|
|
}
|
|
}
|
|
|
|
/* Figure our tiling etc */
|
|
if (priv->is_tiled)
|
|
texture_init_tiles (texture);
|
|
}
|
|
|
|
CLUTTER_NOTE (TEXTURE, "set size %ix%i\n",
|
|
priv->width,
|
|
priv->height);
|
|
|
|
/* Set Error from this */
|
|
texture_upload_data (texture,
|
|
copy_data != NULL ? copy_data : data,
|
|
has_alpha,
|
|
width,
|
|
height,
|
|
rowstride,
|
|
bpp);
|
|
|
|
CLUTTER_ACTOR_SET_FLAGS (CLUTTER_ACTOR (texture), CLUTTER_ACTOR_REALIZED);
|
|
|
|
if (size_change)
|
|
{
|
|
g_signal_emit (texture, texture_signals[SIZE_CHANGE],
|
|
0, priv->width, priv->height);
|
|
|
|
if (priv->sync_actor_size)
|
|
clutter_actor_set_size (CLUTTER_ACTOR(texture),
|
|
priv->width,
|
|
priv->height);
|
|
}
|
|
|
|
/* rename signal */
|
|
g_signal_emit (texture, texture_signals[PIXBUF_CHANGE], 0);
|
|
|
|
/* If resized actor may need resizing but paint() will do this */
|
|
if (CLUTTER_ACTOR_IS_MAPPED (CLUTTER_ACTOR(texture)))
|
|
clutter_actor_queue_redraw (CLUTTER_ACTOR(texture));
|
|
|
|
if (copy_data != NULL)
|
|
g_free (copy_data);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* clutter_texture_set_from_yuv_data:
|
|
* @texture: A #ClutterTexture
|
|
* @data: Image data in YUV type colorspace.
|
|
* @width: Width in pixels of image data.
|
|
* @height: Height in pixels of image data
|
|
* @flags: #ClutterTextureFlags
|
|
* @error: Return location for a #GError, or %NULL.
|
|
*
|
|
* Sets a #ClutterTexture from YUV image data. If an error occurred,
|
|
* %FALSE is returned and @error is set.
|
|
*
|
|
* This function is likely to change in future versions.
|
|
*
|
|
* Return value: %TRUE if the texture was successfully updated
|
|
*
|
|
* Since 0.4.
|
|
**/
|
|
gboolean
|
|
clutter_texture_set_from_yuv_data (ClutterTexture *texture,
|
|
const guchar *data,
|
|
gint width,
|
|
gint height,
|
|
ClutterTextureFlags flags,
|
|
GError **error)
|
|
{
|
|
ClutterTexturePrivate *priv;
|
|
gboolean texture_dirty = TRUE, size_change = FALSE;
|
|
|
|
g_return_val_if_fail (CLUTTER_IS_TEXTURE (texture), FALSE);
|
|
|
|
if (!clutter_feature_available (CLUTTER_FEATURE_TEXTURE_YUV))
|
|
{
|
|
g_set_error (error, CLUTTER_TEXTURE_ERROR,
|
|
CLUTTER_TEXTURE_ERROR_NO_YUV,
|
|
"YUV textures are not supported");
|
|
return FALSE;
|
|
}
|
|
|
|
priv = texture->priv;
|
|
|
|
if (priv->fbo_source)
|
|
texture_fbo_free_resources (texture);
|
|
|
|
/* FIXME: check other image props */
|
|
size_change = (width != priv->width || height != priv->height);
|
|
texture_dirty = size_change || (priv->pixel_format != CGL_YCBCR_MESA);
|
|
|
|
priv->width = width;
|
|
priv->height = height;
|
|
priv->pixel_type = (flags & CLUTTER_TEXTURE_YUV_FLAG_YUV2) ?
|
|
CGL_UNSIGNED_SHORT_8_8_REV_MESA :
|
|
CGL_UNSIGNED_SHORT_8_8_MESA;
|
|
priv->pixel_format = CGL_YCBCR_MESA;
|
|
priv->target_type = CGL_TEXTURE_2D;
|
|
|
|
if (texture_dirty)
|
|
texture_free_gl_resources (texture);
|
|
|
|
if (!priv->tiles)
|
|
{
|
|
priv->tiles = g_new (COGLuint, 1);
|
|
glGenTextures (1, priv->tiles);
|
|
}
|
|
|
|
cogl_texture_bind (priv->target_type, priv->tiles[0]);
|
|
|
|
cogl_texture_set_filters (priv->target_type,
|
|
priv->filter_quality ? CGL_LINEAR : CGL_NEAREST,
|
|
priv->filter_quality ? CGL_LINEAR : CGL_NEAREST);
|
|
|
|
if (texture_dirty)
|
|
{
|
|
gint new_width, new_height;
|
|
|
|
new_width = clutter_util_next_p2 (priv->width);
|
|
new_height = clutter_util_next_p2 (priv->height);
|
|
|
|
/* FIXME: need to check size limits correctly - does not
|
|
* seem to work if correct format and type are used so
|
|
* this is really a guess...
|
|
*/
|
|
if (cogl_texture_can_size (CGL_TEXTURE_2D,
|
|
CGL_RGBA,
|
|
CGL_UNSIGNED_BYTE,
|
|
new_width, new_height))
|
|
{
|
|
cogl_texture_image_2d (priv->target_type,
|
|
priv->pixel_format,
|
|
new_width, new_height,
|
|
priv->pixel_format,
|
|
priv->pixel_type,
|
|
NULL);
|
|
}
|
|
else
|
|
{
|
|
g_set_error (error, CLUTTER_TEXTURE_ERROR,
|
|
CLUTTER_TEXTURE_ERROR_OUT_OF_MEMORY,
|
|
"Unable to allocate a texture of %d by %d pixels",
|
|
new_width,
|
|
new_height);
|
|
|
|
return FALSE; /* FIXME: add tiling */
|
|
}
|
|
}
|
|
|
|
cogl_texture_sub_image_2d (priv->target_type,
|
|
0,
|
|
0,
|
|
priv->width,
|
|
priv->height,
|
|
priv->pixel_format,
|
|
priv->pixel_type,
|
|
data);
|
|
|
|
CLUTTER_ACTOR_SET_FLAGS (CLUTTER_ACTOR (texture), CLUTTER_ACTOR_REALIZED);
|
|
|
|
if (size_change)
|
|
{
|
|
g_signal_emit (texture, texture_signals[SIZE_CHANGE],
|
|
0, priv->width, priv->height);
|
|
|
|
if (priv->sync_actor_size)
|
|
clutter_actor_set_size (CLUTTER_ACTOR(texture),
|
|
priv->width,
|
|
priv->height);
|
|
}
|
|
|
|
g_signal_emit (texture, texture_signals[PIXBUF_CHANGE], 0);
|
|
|
|
if (CLUTTER_ACTOR_IS_MAPPED (CLUTTER_ACTOR(texture)))
|
|
clutter_actor_queue_redraw (CLUTTER_ACTOR(texture));
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* clutter_texture_set_pixbuf:
|
|
* @texture: A #ClutterTexture
|
|
* @pixbuf: A #GdkPixbuf
|
|
* @error: Return location for a #GError, or %NULL
|
|
*
|
|
* Sets a #ClutterTexture image data from a #GdkPixbuf. In case of
|
|
* failure, %FALSE is returned and @error is set.
|
|
*
|
|
* Return value: %TRUE if the pixbuf was successfully set
|
|
*
|
|
* Since: 0.4
|
|
*/
|
|
gboolean
|
|
clutter_texture_set_pixbuf (ClutterTexture *texture,
|
|
GdkPixbuf *pixbuf,
|
|
GError **error)
|
|
{
|
|
ClutterTexturePrivate *priv;
|
|
|
|
priv = texture->priv;
|
|
|
|
g_return_val_if_fail (pixbuf != NULL, FALSE);
|
|
|
|
return clutter_texture_set_from_rgb_data (texture,
|
|
gdk_pixbuf_get_pixels (pixbuf),
|
|
gdk_pixbuf_get_has_alpha (pixbuf),
|
|
gdk_pixbuf_get_width (pixbuf),
|
|
gdk_pixbuf_get_height (pixbuf),
|
|
gdk_pixbuf_get_rowstride (pixbuf),
|
|
4,
|
|
0,
|
|
error);
|
|
}
|
|
|
|
/**
|
|
* clutter_texture_new_from_pixbuf:
|
|
* @pixbuf: A #GdkPixbuf
|
|
*
|
|
* Creates a new #ClutterTexture object.
|
|
*
|
|
* Return value: A newly created #ClutterTexture object.
|
|
**/
|
|
ClutterActor*
|
|
clutter_texture_new_from_pixbuf (GdkPixbuf *pixbuf)
|
|
{
|
|
ClutterTexture *texture;
|
|
|
|
texture = g_object_new (CLUTTER_TYPE_TEXTURE, "pixbuf", pixbuf, NULL);
|
|
|
|
return CLUTTER_ACTOR(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)
|
|
{
|
|
/* Attempt to realize, mainly for subclasses ( such as labels )
|
|
* which maynot create pixbuf data and thus base size until
|
|
* realization happens.
|
|
*/
|
|
if (!CLUTTER_ACTOR_IS_REALIZED(CLUTTER_ACTOR(texture)))
|
|
clutter_actor_realize (CLUTTER_ACTOR(texture));
|
|
|
|
if (width)
|
|
*width = texture->priv->width;
|
|
|
|
if (height)
|
|
*height = texture->priv->height;
|
|
|
|
}
|
|
|
|
/**
|
|
* clutter_texture_bind_tile:
|
|
* @texture: A #ClutterTexture
|
|
* @index_: Tile index to bind
|
|
*
|
|
* Proxies a call to glBindTexture to bind an internal 'tile'.
|
|
*
|
|
* This function is only useful for #ClutterTexture sub-classes
|
|
* and should never be called by an application.
|
|
**/
|
|
void
|
|
clutter_texture_bind_tile (ClutterTexture *texture,
|
|
gint index_)
|
|
{
|
|
ClutterTexturePrivate *priv;
|
|
|
|
g_return_if_fail (CLUTTER_IS_TEXTURE (texture));
|
|
|
|
priv = texture->priv;
|
|
|
|
cogl_texture_bind (priv->target_type, priv->tiles[index_]);
|
|
}
|
|
|
|
/**
|
|
* clutter_texture_get_n_tiles:
|
|
* @texture: A #ClutterTexture
|
|
* @n_x_tiles: Location to store number of tiles in horizonally axis
|
|
* @n_y_tiles: Location to store number of tiles in vertical axis
|
|
*
|
|
* Retreives internal tile dimensioning.
|
|
*
|
|
* This function is only useful for #ClutterTexture sub-classes
|
|
* and should never be called by an application.
|
|
**/
|
|
void
|
|
clutter_texture_get_n_tiles (ClutterTexture *texture,
|
|
gint *n_x_tiles,
|
|
gint *n_y_tiles)
|
|
{
|
|
if (n_x_tiles)
|
|
*n_x_tiles = texture->priv->n_x_tiles;
|
|
|
|
if (n_y_tiles)
|
|
*n_y_tiles = texture->priv->n_y_tiles;
|
|
|
|
}
|
|
|
|
/**
|
|
* clutter_texture_get_x_tile_detail:
|
|
* @texture: A #ClutterTexture
|
|
* @x_index: X index of tile to query
|
|
* @pos: Location to store tile's X position
|
|
* @size: Location to store tile's horizontal size in pixels
|
|
* @waste: Location to store tile's horizontal wastage in pixels
|
|
*
|
|
* Retreives details of a tile on the X axis.
|
|
*
|
|
* This function is only useful for #ClutterTexture sub-classes
|
|
* and should never be called by an application.
|
|
**/
|
|
void
|
|
clutter_texture_get_x_tile_detail (ClutterTexture *texture,
|
|
gint x_index,
|
|
gint *pos,
|
|
gint *size,
|
|
gint *waste)
|
|
{
|
|
g_return_if_fail(x_index < texture->priv->n_x_tiles);
|
|
|
|
if (pos)
|
|
*pos = texture->priv->x_tiles[x_index].pos;
|
|
|
|
if (size)
|
|
*size = texture->priv->x_tiles[x_index].size;
|
|
|
|
if (waste)
|
|
*waste = texture->priv->x_tiles[x_index].waste;
|
|
}
|
|
|
|
/**
|
|
* clutter_texture_get_y_tile_detail:
|
|
* @texture: A #ClutterTexture
|
|
* @y_index: Y index of tile to query
|
|
* @pos: Location to store tile's Y position
|
|
* @size: Location to store tile's vertical size in pixels
|
|
* @waste: Location to store tile's vertical wastage in pixels
|
|
*
|
|
* Retreives details of a tile on the Y axis.
|
|
*
|
|
* This function is only useful for #ClutterTexture sub-classes
|
|
* and should never be called by an application.
|
|
**/
|
|
void
|
|
clutter_texture_get_y_tile_detail (ClutterTexture *texture,
|
|
gint y_index,
|
|
gint *pos,
|
|
gint *size,
|
|
gint *waste)
|
|
{
|
|
ClutterTexturePrivate *priv;
|
|
|
|
g_return_if_fail (CLUTTER_IS_TEXTURE (texture));
|
|
|
|
priv = texture->priv;
|
|
|
|
g_return_if_fail (y_index < priv->n_y_tiles);
|
|
|
|
if (pos)
|
|
*pos = priv->y_tiles[y_index].pos;
|
|
|
|
if (size)
|
|
*size = priv->y_tiles[y_index].size;
|
|
|
|
if (waste)
|
|
*waste = priv->y_tiles[y_index].waste;
|
|
}
|
|
|
|
/**
|
|
* clutter_texture_has_generated_tiles:
|
|
* @texture: A #ClutterTexture
|
|
*
|
|
* Checks if #ClutterTexture has generated underlying GL texture tiles.
|
|
*
|
|
* This function is only useful for #ClutterTexture sub-classes
|
|
* and should never be called by an application.
|
|
*
|
|
* Return value: %TRUE if texture has pre-generated GL tiles.
|
|
**/
|
|
gboolean
|
|
clutter_texture_has_generated_tiles (ClutterTexture *texture)
|
|
{
|
|
g_return_val_if_fail (CLUTTER_IS_TEXTURE (texture), FALSE);
|
|
|
|
return (texture->priv->tiles != NULL);
|
|
}
|
|
|
|
/**
|
|
* clutter_texture_is_tiled:
|
|
* @texture: A #ClutterTexture
|
|
*
|
|
* Checks if #ClutterTexture is tiled.
|
|
*
|
|
* This function is only useful for #ClutterTexture sub-classes
|
|
* and should never be called by an application.
|
|
*
|
|
* Return value: %TRUE if texture is tiled
|
|
**/
|
|
gboolean
|
|
clutter_texture_is_tiled (ClutterTexture *texture)
|
|
{
|
|
g_return_val_if_fail (CLUTTER_IS_TEXTURE (texture), FALSE);
|
|
|
|
return texture->priv->is_tiled;
|
|
}
|
|
|
|
|
|
static void inline
|
|
texture_update_data (ClutterTexture *texture,
|
|
const guchar *data,
|
|
gboolean has_alpha,
|
|
gint x_0,
|
|
gint y_0,
|
|
gint width,
|
|
gint height,
|
|
gint rowstride,
|
|
gint bpp)
|
|
{
|
|
ClutterTexturePrivate *priv;
|
|
gint x, y;
|
|
gint i = 0;
|
|
gboolean create_textures = FALSE;
|
|
|
|
priv = texture->priv;
|
|
|
|
g_return_if_fail (data != NULL);
|
|
|
|
CLUTTER_MARK();
|
|
|
|
if (!priv->is_tiled)
|
|
{
|
|
g_assert (priv->tiles);
|
|
|
|
CLUTTER_NOTE (TEXTURE, "syncing for single tile");
|
|
|
|
cogl_texture_bind (priv->target_type, priv->tiles[0]);
|
|
cogl_texture_set_alignment (priv->target_type, 4, priv->width);
|
|
|
|
cogl_texture_set_filters
|
|
(priv->target_type,
|
|
priv->filter_quality ? CGL_LINEAR : CGL_NEAREST,
|
|
priv->filter_quality ? CGL_LINEAR : CGL_NEAREST);
|
|
|
|
cogl_texture_set_wrap (priv->target_type,
|
|
priv->repeat_x ? CGL_REPEAT : CGL_CLAMP_TO_EDGE,
|
|
priv->repeat_y ? CGL_REPEAT : CGL_CLAMP_TO_EDGE);
|
|
|
|
priv->filter_quality = 1;
|
|
|
|
cogl_texture_sub_image_2d (priv->target_type,
|
|
x_0,
|
|
y_0,
|
|
width,
|
|
height,
|
|
priv->pixel_format,
|
|
priv->pixel_type,
|
|
data);
|
|
return;
|
|
}
|
|
|
|
/* Multiple tiled texture */
|
|
|
|
CLUTTER_NOTE (TEXTURE,
|
|
"syncing for multiple tiles for %ix%i pixbuf",
|
|
priv->width, priv->height);
|
|
|
|
g_return_if_fail (priv->x_tiles != NULL && priv->y_tiles != NULL);
|
|
|
|
if (priv->tiles == NULL)
|
|
{
|
|
g_assert (0);
|
|
priv->tiles = g_new (COGLuint, priv->n_x_tiles * priv->n_y_tiles);
|
|
glGenTextures (priv->n_x_tiles * priv->n_y_tiles, priv->tiles);
|
|
create_textures = TRUE;
|
|
}
|
|
|
|
for (x = 0; x < priv->n_x_tiles; x++)
|
|
for (y = 0; y < priv->n_y_tiles; y++)
|
|
{
|
|
gint master_offset_x;
|
|
gint effective_x;
|
|
gint effective_width;
|
|
|
|
gint master_offset_y;
|
|
gint effective_y;
|
|
gint effective_height;
|
|
|
|
/*
|
|
* -- first tile --
|
|
* |--------------------- priv->width ------------------------------|
|
|
* | <- priv->x_tiles[x].pos
|
|
* |-----------| <- priv->x_tiles[x].size
|
|
* |-------| <- x_0
|
|
* |------------| <- width
|
|
* |--------------------| <- x_0 + width
|
|
* |-------| <- master_offset = -8
|
|
* |-------| <- effective_x = 8
|
|
* |---| <- effective_width
|
|
*
|
|
* -- second tile ---
|
|
*
|
|
* |--------------------- priv->width ------------------------------|
|
|
* |-----------| <- priv->x_tiles[x].pos
|
|
* |-----------| <- priv->x_tiles[x].size (src_w)
|
|
* |-------| <- x_0
|
|
* |------------| <- width
|
|
* |--------------------| <- x_0 + width
|
|
* |---| <- master_offset = 4
|
|
* | <- effective_x (0 in between)
|
|
* |--------| <- effective_width
|
|
*
|
|
* XXXXXXXXXXXXXX <- master
|
|
* |___________|___________|___________|___________|___________|_____%%%%%%|
|
|
*/
|
|
|
|
gint src_w, src_h;
|
|
|
|
src_w = priv->x_tiles[x].size;
|
|
src_h = priv->y_tiles[y].size;
|
|
|
|
/* skip tiles that do not intersect the updated region */
|
|
if ((priv->x_tiles[x].pos + src_w < x_0 ||
|
|
priv->y_tiles[y].pos + src_h < y_0 ||
|
|
priv->x_tiles[x].pos >= x_0 + width ||
|
|
priv->y_tiles[y].pos >= y_0 + height))
|
|
{
|
|
i++;
|
|
continue;
|
|
}
|
|
|
|
master_offset_x = priv->x_tiles[x].pos - x_0;
|
|
|
|
if (priv->x_tiles[x].pos > x_0)
|
|
effective_x = 0;
|
|
else
|
|
effective_x = x_0 - priv->x_tiles[x].pos;
|
|
|
|
effective_width = (x_0 + width) - priv->x_tiles[x].pos;
|
|
|
|
if (effective_width > src_w - effective_x)
|
|
effective_width = src_w - effective_x;
|
|
|
|
master_offset_y = priv->y_tiles[y].pos - y_0;
|
|
|
|
if (priv->y_tiles[y].pos > y_0)
|
|
effective_y = 0;
|
|
else
|
|
effective_y = y_0 - priv->y_tiles[y].pos;
|
|
|
|
effective_height = (y_0 + height) - priv->y_tiles[y].pos;
|
|
if (effective_height > src_h - effective_y)
|
|
effective_height = src_h - effective_y;
|
|
|
|
if (master_offset_x < 0)
|
|
master_offset_x = 0;
|
|
if (master_offset_y < 0)
|
|
master_offset_y = 0;
|
|
|
|
if (master_offset_x + effective_width > width)
|
|
effective_width = width - master_offset_x;
|
|
if (master_offset_y + effective_height > height)
|
|
effective_height = height - master_offset_y;
|
|
|
|
cogl_texture_bind (priv->target_type, priv->tiles[i]);
|
|
|
|
cogl_texture_set_alignment (priv->target_type, 4, rowstride/4);
|
|
|
|
cogl_texture_set_filters
|
|
(priv->target_type,
|
|
priv->filter_quality ? CGL_LINEAR : CGL_NEAREST,
|
|
priv->filter_quality ? CGL_LINEAR : CGL_NEAREST);
|
|
|
|
cogl_texture_set_wrap (priv->target_type,
|
|
priv->repeat_x ? CGL_REPEAT : CGL_CLAMP_TO_EDGE,
|
|
priv->repeat_y ? CGL_REPEAT : CGL_CLAMP_TO_EDGE);
|
|
cogl_texture_sub_image_2d (priv->target_type,
|
|
effective_x,
|
|
effective_y,
|
|
effective_width,
|
|
effective_height,
|
|
priv->pixel_format,
|
|
priv->pixel_type,
|
|
data + (master_offset_y * priv->width + master_offset_x) * 4);
|
|
|
|
i++;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* clutter_texture_set_area_from_rgb_data:
|
|
* @texture: A #ClutterTexture
|
|
* @data: Image data in RGB type colorspace.
|
|
* @has_alpha: Set to TRUE if image data has an alpha channel.
|
|
* @x: X coordinate of upper left corner of region to update.
|
|
* @y: Y coordinate of upper left corner of region to update.
|
|
* @width: Width in pixels of region to update.
|
|
* @height: Height in pixels of region to update.
|
|
* @rowstride: Distance in bytes between row starts on source buffer.
|
|
* @bpp: bytes per pixel ( Currently only 4 supported )
|
|
* @flags: #ClutterTextureFlags
|
|
* @error: return location for a #GError, or %NULL
|
|
*
|
|
* Updates a sub-region of the pixel data in a #ClutterTexture.
|
|
*
|
|
* Return value: %TRUE on success, %FALSE on failure.
|
|
*
|
|
* Since 0.6.
|
|
*/
|
|
gboolean
|
|
clutter_texture_set_area_from_rgb_data (ClutterTexture *texture,
|
|
const guchar *data,
|
|
gboolean has_alpha,
|
|
gint x,
|
|
gint y,
|
|
gint width,
|
|
gint height,
|
|
gint rowstride,
|
|
gint bpp,
|
|
ClutterTextureFlags flags,
|
|
GError **error)
|
|
{
|
|
ClutterTexturePrivate *priv;
|
|
guchar *copy_data = NULL;
|
|
|
|
priv = texture->priv;
|
|
|
|
if (!texture_prepare_upload (FALSE, texture, data, has_alpha,
|
|
width, height, rowstride,
|
|
bpp, flags, ©_data, NULL, NULL))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
texture_update_data (texture,
|
|
copy_data != NULL ? copy_data : data,
|
|
has_alpha,
|
|
x, y,
|
|
width, height,
|
|
rowstride,
|
|
bpp);
|
|
|
|
CLUTTER_ACTOR_SET_FLAGS (CLUTTER_ACTOR (texture), CLUTTER_ACTOR_REALIZED);
|
|
|
|
/* rename signal */
|
|
g_signal_emit (texture, texture_signals[PIXBUF_CHANGE], 0);
|
|
|
|
if (CLUTTER_ACTOR_IS_MAPPED (CLUTTER_ACTOR(texture)))
|
|
clutter_actor_queue_redraw (CLUTTER_ACTOR(texture));
|
|
|
|
if (copy_data != NULL)
|
|
g_free (copy_data);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
on_fbo_source_size_change (GObject *object,
|
|
GParamSpec *param_spec,
|
|
ClutterTexture *texture)
|
|
{
|
|
ClutterTexturePrivate *priv = texture->priv;
|
|
guint w, h;
|
|
|
|
clutter_actor_get_abs_size (priv->fbo_source, &w, &h);
|
|
|
|
if (w != priv->width || h != priv->height)
|
|
{
|
|
if (!cogl_texture_can_size (CGL_TEXTURE_RECTANGLE_ARB,
|
|
CGL_RGBA, PIXEL_TYPE, w, h))
|
|
{
|
|
g_warning ("%s: Offscreen source too large for texture", G_STRLOC);
|
|
return;
|
|
}
|
|
|
|
/* tear down the FBO */
|
|
cogl_offscreen_destroy (priv->fbo_handle);
|
|
|
|
texture_free_gl_resources (texture);
|
|
|
|
priv->width = w;
|
|
priv->height = h;
|
|
|
|
priv->tiles = g_new (COGLuint, 1);
|
|
|
|
/* FIXME: needs a cogl wrapper */
|
|
glGenTextures (1, priv->tiles);
|
|
|
|
cogl_texture_bind (priv->target_type, priv->tiles[0]);
|
|
cogl_texture_image_2d (priv->target_type,
|
|
CGL_RGBA,
|
|
w,
|
|
h,
|
|
priv->pixel_format,
|
|
priv->pixel_type,
|
|
NULL);
|
|
|
|
priv->fbo_handle = cogl_offscreen_create (priv->tiles[0]);
|
|
|
|
clutter_actor_set_size (CLUTTER_ACTOR(texture), w, h);
|
|
}
|
|
}
|
|
|
|
static void
|
|
on_fbo_parent_change (ClutterActor *actor,
|
|
ClutterActor *old_parent,
|
|
ClutterTexture *texture)
|
|
{
|
|
ClutterActor *parent = CLUTTER_ACTOR(texture);
|
|
|
|
while ((parent = clutter_actor_get_parent (parent)) != NULL)
|
|
if (parent == actor)
|
|
{
|
|
g_warning ("Offscreen texture is ancestor of source!");
|
|
/* Desperate but will avoid infinite loops */
|
|
clutter_actor_unparent (actor);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* clutter_texture_new_from_actor:
|
|
* @actor: A source #ClutterActor
|
|
*
|
|
* Creates a new #ClutterTexture object with its source a prexisting
|
|
* actor (and associated children). The textures content will contain
|
|
* 'live' redirected output of the actors scene.
|
|
*
|
|
* Note this function is intented as a utility call for uniformly applying
|
|
* shaders to groups and other potential visual effects. It requires that
|
|
* both %CLUTTER_FEATURE_TEXTURE_RECTANGLE and %CLUTTER_FEATURE_OFFSCREEN
|
|
* features are supported by the current backend and the target system.
|
|
*
|
|
* Some tips on usage:
|
|
*
|
|
* <itemizedlist>
|
|
* <listitem>
|
|
* <para>The source actor must be made visible (i.e by calling
|
|
* #clutter_actor_show). The source actor does not however have to
|
|
* have a parent.</para>
|
|
* </listitem>
|
|
* <listitem>
|
|
* <para>Avoid reparenting the source with the created texture.</para>
|
|
* </listitem>
|
|
* <listitem>
|
|
* <para>A group can be padded with a transparent rectangle as to
|
|
* provide a border to contents for shader output (blurring text
|
|
* for example).</para>
|
|
* </listitem>
|
|
* <listitem>
|
|
* <para>The texture will automatically resize to contain a further
|
|
* transformed source. However, this involves overhead and can be
|
|
* avoided by placing the source actor in a bounding group
|
|
* sized large enough to contain any child tranformations.</para>
|
|
* </listitem>
|
|
* <listitem>
|
|
* <para>Uploading pixel data to the texture (e.g by
|
|
* #clutter_actor_set_pixbuf) will destroy the offscreen texture data
|
|
* and end redirection.</para>
|
|
* </listitem>
|
|
* <listitem>
|
|
* <para>#clutter_texture_get_pixbuf can be used to read the offscreen
|
|
* texture pixels into a pixbuf.</para>
|
|
* </listitem>
|
|
* </itemizedlist>
|
|
*
|
|
* Return value: A newly created #ClutterTexture object, or %NULL on failure.
|
|
*
|
|
* Since: 0.6
|
|
*/
|
|
ClutterActor *
|
|
clutter_texture_new_from_actor (ClutterActor *actor)
|
|
{
|
|
ClutterTexture *texture;
|
|
ClutterTexturePrivate *priv;
|
|
guint w, h;
|
|
|
|
g_return_val_if_fail (CLUTTER_IS_ACTOR (actor), NULL);
|
|
|
|
/* FIXME: Some error result on below could be useful */
|
|
if (clutter_feature_available (CLUTTER_FEATURE_TEXTURE_RECTANGLE) == FALSE)
|
|
return NULL;
|
|
|
|
if (clutter_feature_available (CLUTTER_FEATURE_OFFSCREEN) == FALSE)
|
|
return NULL;
|
|
|
|
if (!CLUTTER_ACTOR_IS_REALIZED (actor))
|
|
{
|
|
clutter_actor_realize (actor);
|
|
|
|
if (!CLUTTER_ACTOR_IS_REALIZED (actor))
|
|
return NULL;
|
|
}
|
|
|
|
clutter_actor_get_abs_size (actor, &w, &h);
|
|
|
|
if (w == 0 || h == 0)
|
|
return NULL;
|
|
|
|
if (!cogl_texture_can_size (CGL_TEXTURE_RECTANGLE_ARB,
|
|
CGL_RGBA, PIXEL_TYPE, w, h))
|
|
return NULL;
|
|
|
|
/* Hopefully now were good.. */
|
|
texture = g_object_new (CLUTTER_TYPE_TEXTURE, NULL);
|
|
|
|
priv = texture->priv;
|
|
|
|
priv->fbo_source = g_object_ref(actor);
|
|
|
|
/* Connect up any signals which could change our underlying size */
|
|
g_signal_connect (actor,
|
|
"notify::width",
|
|
G_CALLBACK(on_fbo_source_size_change),
|
|
texture);
|
|
g_signal_connect (actor,
|
|
"notify::height",
|
|
G_CALLBACK(on_fbo_source_size_change),
|
|
texture);
|
|
g_signal_connect (actor,
|
|
"notify::scale-x",
|
|
G_CALLBACK(on_fbo_source_size_change),
|
|
texture);
|
|
g_signal_connect (actor,
|
|
"notify::scale-y",
|
|
G_CALLBACK(on_fbo_source_size_change),
|
|
texture);
|
|
g_signal_connect (actor,
|
|
"notify::rotation-angle-x",
|
|
G_CALLBACK(on_fbo_source_size_change),
|
|
texture);
|
|
g_signal_connect (actor,
|
|
"notify::rotation-angle-y",
|
|
G_CALLBACK(on_fbo_source_size_change),
|
|
texture);
|
|
g_signal_connect (actor,
|
|
"notify::rotation-angle-z",
|
|
G_CALLBACK(on_fbo_source_size_change),
|
|
texture);
|
|
|
|
/* And a warning if the source becomes a child of the texture */
|
|
g_signal_connect (actor,
|
|
"parent-set",
|
|
G_CALLBACK(on_fbo_parent_change),
|
|
texture);
|
|
|
|
priv->width = w;
|
|
priv->height = h;
|
|
priv->target_type = CGL_TEXTURE_RECTANGLE_ARB;
|
|
priv->pixel_format = CGL_RGBA;
|
|
priv->pixel_type = PIXEL_TYPE;
|
|
priv->is_tiled = 0;
|
|
|
|
clutter_actor_set_size (CLUTTER_ACTOR(texture), priv->width, priv->height);
|
|
|
|
return CLUTTER_ACTOR(texture);
|
|
}
|
|
|
|
static void
|
|
texture_fbo_free_resources (ClutterTexture *texture)
|
|
{
|
|
ClutterTexturePrivate *priv;
|
|
|
|
priv = texture->priv;
|
|
|
|
CLUTTER_MARK();
|
|
|
|
if (priv->fbo_source != NULL)
|
|
{
|
|
g_signal_handlers_disconnect_by_func
|
|
(priv->fbo_source,
|
|
G_CALLBACK(on_fbo_parent_change),
|
|
texture);
|
|
|
|
g_signal_handlers_disconnect_by_func
|
|
(priv->fbo_source,
|
|
G_CALLBACK(on_fbo_source_size_change),
|
|
texture);
|
|
|
|
g_object_unref (priv->fbo_source);
|
|
|
|
priv->fbo_source = NULL;
|
|
}
|
|
|
|
if (priv->fbo_handle != 0)
|
|
{
|
|
cogl_offscreen_destroy (priv->fbo_handle);
|
|
priv->fbo_handle = 0;
|
|
}
|
|
}
|
|
|