cairo-texture: Allow overriding the surface creation

By using a new signal, ::create-surface (width, height), it should be
possible for third party code and sub-classes to override the default
surface creation code in CairoSurface.

This commit takes a bit of the patch from:

  http://bugzilla.clutter-project.org/show_bug.cgi?id=1878

which cleans up CairoTexture; the idea, mutuated from that bug, is that
the CairoTexture actor checks whether the surface it has it's an image
one, and in that case it uses a Cogl texture as the backing store. In
case the surface is not an image one we assume that the surface itself
has some way of updating the GL state and flush the surface.
This commit is contained in:
Emmanuele Bassi 2010-10-15 17:35:41 +01:00
parent 63e314b07e
commit 0f613ea134
5 changed files with 183 additions and 135 deletions

View File

@ -78,8 +78,12 @@
#include <string.h> #include <string.h>
#include <cairo-gobject.h>
#include "clutter-cairo-texture.h" #include "clutter-cairo-texture.h"
#include "clutter-debug.h" #include "clutter-debug.h"
#include "clutter-marshal.h"
#include "clutter-private.h" #include "clutter-private.h"
G_DEFINE_TYPE (ClutterCairoTexture, G_DEFINE_TYPE (ClutterCairoTexture,
@ -96,7 +100,16 @@ enum
PROP_LAST PROP_LAST
}; };
static GParamSpec *obj_props[PROP_LAST]; enum
{
CREATE_SURFACE,
LAST_SIGNAL
};
static GParamSpec *obj_props[PROP_LAST] = { NULL, };
static guint cairo_signals[LAST_SIGNAL] = { 0, };
#ifdef CLUTTER_ENABLE_DEBUG #ifdef CLUTTER_ENABLE_DEBUG
#define clutter_warn_if_paint_fail(obj) G_STMT_START { \ #define clutter_warn_if_paint_fail(obj) G_STMT_START { \
@ -122,41 +135,21 @@ static GParamSpec *obj_props[PROP_LAST];
struct _ClutterCairoTexturePrivate struct _ClutterCairoTexturePrivate
{ {
cairo_format_t format;
cairo_surface_t *cr_surface; cairo_surface_t *cr_surface;
guchar *cr_surface_data; gint surface_width;
gint surface_height;
guint width;
guint height;
guint rowstride;
};
typedef struct
{
gint x;
gint y;
guint width; guint width;
guint height; guint height;
} ClutterCairoTextureRectangle; };
typedef struct typedef struct {
{
ClutterCairoTexture *cairo; ClutterCairoTexture *cairo;
ClutterCairoTextureRectangle rect; cairo_rectangle_int_t rect;
} ClutterCairoTextureContext; } ClutterCairoTextureContext;
static const cairo_user_data_key_t clutter_cairo_texture_surface_key;
static const cairo_user_data_key_t clutter_cairo_texture_context_key; static const cairo_user_data_key_t clutter_cairo_texture_context_key;
static void
clutter_cairo_texture_surface_destroy (void *data)
{
ClutterCairoTexture *cairo = data;
cairo->priv->cr_surface = NULL;
}
static void static void
clutter_cairo_texture_set_property (GObject *object, clutter_cairo_texture_set_property (GObject *object,
guint prop_id, guint prop_id,
@ -214,105 +207,69 @@ clutter_cairo_texture_finalize (GObject *object)
{ {
ClutterCairoTexturePrivate *priv = CLUTTER_CAIRO_TEXTURE (object)->priv; ClutterCairoTexturePrivate *priv = CLUTTER_CAIRO_TEXTURE (object)->priv;
if (priv->cr_surface) if (priv->cr_surface != NULL)
{ {
cairo_surface_t *surface = priv->cr_surface; cairo_surface_t *surface = priv->cr_surface;
cairo_surface_finish (priv->cr_surface);
cairo_surface_set_user_data (priv->cr_surface,
&clutter_cairo_texture_surface_key,
NULL, NULL);
cairo_surface_destroy (surface);
priv->cr_surface = NULL; priv->cr_surface = NULL;
}
if (priv->cr_surface_data) cairo_surface_finish (surface);
{ cairo_surface_destroy (surface);
g_free (priv->cr_surface_data);
priv->cr_surface_data = NULL;
} }
G_OBJECT_CLASS (clutter_cairo_texture_parent_class)->finalize (object); G_OBJECT_CLASS (clutter_cairo_texture_parent_class)->finalize (object);
} }
static cairo_surface_t *
get_surface (ClutterCairoTexture *self)
{
ClutterCairoTexturePrivate *priv = self->priv;
if (priv->cr_surface == NULL)
{
g_signal_emit (self, cairo_signals[CREATE_SURFACE], 0,
priv->width,
priv->height,
&priv->cr_surface);
}
return priv->cr_surface;
}
static inline void static inline void
clutter_cairo_texture_surface_resize_internal (ClutterCairoTexture *cairo) clutter_cairo_texture_surface_resize_internal (ClutterCairoTexture *cairo)
{ {
ClutterCairoTexturePrivate *priv = cairo->priv; ClutterCairoTexturePrivate *priv = cairo->priv;
CoglHandle cogl_texture;
if (priv->cr_surface) if (priv->cr_surface != NULL)
{ {
cairo_surface_t *surface = priv->cr_surface; cairo_surface_t *surface = priv->cr_surface;
/* If the surface is already the right size then don't bother /* if the surface is an image one, and the size is already the
doing anything */ * same, then we don't need to do anything
if (priv->width == cairo_image_surface_get_width (priv->cr_surface) */
&& priv->height == cairo_image_surface_get_height (priv->cr_surface)) if (cairo_surface_get_type (surface) != CAIRO_SURFACE_TYPE_IMAGE)
return; {
gint surface_width = cairo_image_surface_get_width (surface);
gint surface_height = cairo_image_surface_get_height (surface);
if (priv->width == surface_width &&
priv->height == surface_height)
return;
}
cairo_surface_finish (surface); cairo_surface_finish (surface);
cairo_surface_set_user_data (surface,
&clutter_cairo_texture_surface_key,
NULL, NULL);
cairo_surface_destroy (surface); cairo_surface_destroy (surface);
priv->cr_surface = NULL; priv->cr_surface = NULL;
} }
if (priv->cr_surface_data)
{
g_free (priv->cr_surface_data);
priv->cr_surface_data = NULL;
}
if (priv->width == 0 || priv->height == 0) if (priv->width == 0 || priv->height == 0)
return; return;
#if CAIRO_VERSION > 106000 g_signal_emit (cairo, cairo_signals[CREATE_SURFACE], 0,
priv->rowstride = cairo_format_stride_for_width (priv->format, priv->width); priv->width,
#else priv->height,
/* poor man's version of cairo_format_stride_for_width() */ &priv->cr_surface);
switch (priv->format)
{
case CAIRO_FORMAT_ARGB32:
case CAIRO_FORMAT_RGB24:
priv->rowstride = priv->width * 4;
break;
case CAIRO_FORMAT_A8:
case CAIRO_FORMAT_A1:
priv->rowstride = priv->width;
break;
default:
g_assert_not_reached ();
break;
}
#endif /* CAIRO_VERSION > 106000 */
priv->cr_surface_data = g_malloc0 (priv->height * priv->rowstride);
priv->cr_surface =
cairo_image_surface_create_for_data (priv->cr_surface_data,
priv->format,
priv->width, priv->height,
priv->rowstride);
cairo_surface_set_user_data (priv->cr_surface,
&clutter_cairo_texture_surface_key,
cairo,
clutter_cairo_texture_surface_destroy);
/* Create a blank Cogl texture */
cogl_texture = cogl_texture_new_from_data (priv->width, priv->height,
COGL_TEXTURE_NONE,
CLUTTER_CAIRO_TEXTURE_PIXEL_FORMAT,
COGL_PIXEL_FORMAT_ANY,
priv->rowstride,
priv->cr_surface_data);
clutter_texture_set_cogl_texture (CLUTTER_TEXTURE (cairo), cogl_texture);
cogl_handle_unref (cogl_texture);
} }
static void static void
@ -375,6 +332,51 @@ clutter_cairo_texture_get_paint_volume (ClutterActor *self,
volume); volume);
} }
static cairo_surface_t *
clutter_cairo_texture_create_surface (ClutterCairoTexture *self,
guint width,
guint height)
{
cairo_surface_t *surface;
guint cairo_stride;
guint8 *cairo_data;
CoglHandle cogl_texture;
surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
width,
height);
cairo_stride = cairo_image_surface_get_stride (surface);
cairo_data = cairo_image_surface_get_data (surface);
self->priv->surface_width = width;
self->priv->surface_height = height;
/* create a backing Cogl texture */
cogl_texture = cogl_texture_new_from_data (width, height,
COGL_TEXTURE_NONE,
CLUTTER_CAIRO_TEXTURE_PIXEL_FORMAT,
COGL_PIXEL_FORMAT_ANY,
cairo_stride,
cairo_data);
clutter_texture_set_cogl_texture (CLUTTER_TEXTURE (self), cogl_texture);
cogl_handle_unref (cogl_texture);
return surface;
}
static gboolean
create_surface_accum (GSignalInvocationHint *ihint,
GValue *return_accu,
const GValue *handler_return,
gpointer data)
{
g_value_copy (handler_return, return_accu);
/* stop on the first non-NULL return value */
return g_value_get_boxed (handler_return) == NULL;
}
static void static void
clutter_cairo_texture_class_init (ClutterCairoTextureClass *klass) clutter_cairo_texture_class_init (ClutterCairoTextureClass *klass)
{ {
@ -388,12 +390,13 @@ clutter_cairo_texture_class_init (ClutterCairoTextureClass *klass)
actor_class->get_paint_volume = actor_class->get_paint_volume =
clutter_cairo_texture_get_paint_volume; clutter_cairo_texture_get_paint_volume;
actor_class->get_preferred_width = actor_class->get_preferred_width =
clutter_cairo_texture_get_preferred_width; clutter_cairo_texture_get_preferred_width;
actor_class->get_preferred_height = actor_class->get_preferred_height =
clutter_cairo_texture_get_preferred_height; clutter_cairo_texture_get_preferred_height;
klass->create_surface = clutter_cairo_texture_create_surface;
g_type_class_add_private (gobject_class, sizeof (ClutterCairoTexturePrivate)); g_type_class_add_private (gobject_class, sizeof (ClutterCairoTexturePrivate));
/** /**
@ -430,6 +433,37 @@ clutter_cairo_texture_class_init (ClutterCairoTextureClass *klass)
_clutter_object_class_install_properties (gobject_class, _clutter_object_class_install_properties (gobject_class,
PROP_LAST, PROP_LAST,
obj_props); obj_props);
/**
* ClutterCairoTexture::create_surface:
* @texture: the #ClutterCairoTexture that emitted the signal
* @width: the width of the surface to create
* @height: the height of the surface to create
*
* The ::create-surface signal is emitted when a #ClutterCairoTexture
* news its surface (re)created, which happens either when the Cairo
* context is created with clutter_cairo_texture_create() or
* clutter_cairo_texture_create_region(), or when the surface is resized
* through clutter_cairo_texture_set_surface_size().
*
* The first signal handler that returns a non-%NULL, valid surface will
* stop any further signal emission, and the returned surface will be
* the one used.
*
* Return value: the newly created #cairo_surface_t for the texture
*
* Since: 1.6
*/
cairo_signals[CREATE_SURFACE] =
g_signal_new (I_("create-surface"),
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (ClutterCairoTextureClass, create_surface),
create_surface_accum, NULL,
_clutter_marshal_BOXED__UINT_UINT,
CAIRO_GOBJECT_TYPE_SURFACE, 2,
G_TYPE_UINT,
G_TYPE_UINT);
} }
static void static void
@ -443,8 +477,9 @@ clutter_cairo_texture_init (ClutterCairoTexture *self)
* a :surface-format construct-only property for creating * a :surface-format construct-only property for creating
* textures with a different format and have the cairo surface * textures with a different format and have the cairo surface
* match that format * match that format
*
* priv->format = CAIRO_FORMAT_ARGB32;
*/ */
priv->format = CAIRO_FORMAT_ARGB32;
/* the Cairo surface is responsible for driving the size of /* the Cairo surface is responsible for driving the size of
* the texture; if we let sync_size to its default of TRUE, * the texture; if we let sync_size to its default of TRUE,
@ -483,14 +518,24 @@ clutter_cairo_texture_context_destroy (void *data)
ClutterCairoTextureContext *ctxt = data; ClutterCairoTextureContext *ctxt = data;
ClutterCairoTexture *cairo = ctxt->cairo; ClutterCairoTexture *cairo = ctxt->cairo;
ClutterCairoTexturePrivate *priv = cairo->priv; ClutterCairoTexturePrivate *priv = cairo->priv;
guchar *cairo_data; guint8 *cairo_data;
gint cairo_width, cairo_height; gint cairo_width, cairo_height, cairo_stride;
gint surface_width, surface_height; gint surface_width, surface_height;
CoglHandle cogl_texture; CoglHandle cogl_texture;
if (!priv->cr_surface) if (priv->cr_surface == NULL)
return; return;
/* for any other surface type, we presume that there exists a native
* communication between Cairo and GL that is triggered by cairo_destroy().
*
* for instance, cairo-drm will flush the outstanding modifications to the
* surface upon context destruction and so the texture is automatically
* updated.
*/
if (cairo_surface_get_type (priv->cr_surface) != CAIRO_SURFACE_TYPE_IMAGE)
goto out;
surface_width = cairo_image_surface_get_width (priv->cr_surface); surface_width = cairo_image_surface_get_width (priv->cr_surface);
surface_height = cairo_image_surface_get_height (priv->cr_surface); surface_height = cairo_image_surface_get_height (priv->cr_surface);
@ -498,17 +543,16 @@ clutter_cairo_texture_context_destroy (void *data)
cairo_height = MIN (ctxt->rect.height, surface_height); cairo_height = MIN (ctxt->rect.height, surface_height);
cogl_texture = clutter_texture_get_cogl_texture (CLUTTER_TEXTURE (cairo)); cogl_texture = clutter_texture_get_cogl_texture (CLUTTER_TEXTURE (cairo));
if (!cairo_width || !cairo_height || cogl_texture == COGL_INVALID_HANDLE) if (!cairo_width || !cairo_height || cogl_texture == COGL_INVALID_HANDLE)
{ {
g_free (ctxt); g_slice_free (ClutterCairoTextureContext, ctxt);
return; return;
} }
cairo_data = (priv->cr_surface_data cairo_stride = cairo_image_surface_get_stride (priv->cr_surface);
+ (ctxt->rect.y * priv->rowstride) cairo_data = cairo_image_surface_get_data (priv->cr_surface);
+ (ctxt->rect.x * 4)); cairo_data += cairo_stride * ctxt->rect.y;
cairo_data += 4 * ctxt->rect.x;
cogl_texture_set_region (cogl_texture, cogl_texture_set_region (cogl_texture,
0, 0, 0, 0,
@ -516,18 +560,18 @@ clutter_cairo_texture_context_destroy (void *data)
cairo_width, cairo_height, cairo_width, cairo_height,
cairo_width, cairo_height, cairo_width, cairo_height,
CLUTTER_CAIRO_TEXTURE_PIXEL_FORMAT, CLUTTER_CAIRO_TEXTURE_PIXEL_FORMAT,
priv->rowstride, cairo_stride,
cairo_data); cairo_data);
g_free (ctxt); out:
g_slice_free (ClutterCairoTextureContext, ctxt);
clutter_actor_queue_redraw (CLUTTER_ACTOR (cairo)); clutter_actor_queue_redraw (CLUTTER_ACTOR (cairo));
} }
static void static void
intersect_rectangles (ClutterCairoTextureRectangle *a, intersect_rectangles (cairo_rectangle_int_t *a,
ClutterCairoTextureRectangle *b, cairo_rectangle_int_t *b,
ClutterCairoTextureRectangle *inter) cairo_rectangle_int_t *inter)
{ {
gint dest_x, dest_y; gint dest_x, dest_y;
gint dest_width, dest_height; gint dest_width, dest_height;
@ -582,7 +626,8 @@ clutter_cairo_texture_create_region (ClutterCairoTexture *self,
{ {
ClutterCairoTexturePrivate *priv; ClutterCairoTexturePrivate *priv;
ClutterCairoTextureContext *ctxt; ClutterCairoTextureContext *ctxt;
ClutterCairoTextureRectangle region, area, inter; cairo_rectangle_int_t region, area, inter;
cairo_surface_t *surface;
cairo_t *cr; cairo_t *cr;
g_return_val_if_fail (CLUTTER_IS_CAIRO_TEXTURE (self), NULL); g_return_val_if_fail (CLUTTER_IS_CAIRO_TEXTURE (self), NULL);
@ -606,10 +651,9 @@ clutter_cairo_texture_create_region (ClutterCairoTexture *self,
return NULL; return NULL;
} }
if (!priv->cr_surface) surface = get_surface (self);
return NULL;
ctxt = g_new0 (ClutterCairoTextureContext, 1); ctxt = g_slice_new0 (ClutterCairoTextureContext);
ctxt->cairo = self; ctxt->cairo = self;
region.x = x_offset; region.x = x_offset;
@ -625,14 +669,12 @@ clutter_cairo_texture_create_region (ClutterCairoTexture *self,
/* Limit the region to the visible rectangle */ /* Limit the region to the visible rectangle */
intersect_rectangles (&area, &region, &inter); intersect_rectangles (&area, &region, &inter);
ctxt->rect.x = inter.x; ctxt->rect = inter;
ctxt->rect.y = inter.y;
ctxt->rect.width = inter.width;
ctxt->rect.height = inter.height;
cr = cairo_create (priv->cr_surface); cr = cairo_create (surface);
cairo_set_user_data (cr, &clutter_cairo_texture_context_key, cairo_set_user_data (cr, &clutter_cairo_texture_context_key,
ctxt, clutter_cairo_texture_context_destroy); ctxt,
clutter_cairo_texture_context_destroy);
return cr; return cr;
} }
@ -775,14 +817,15 @@ clutter_cairo_texture_get_surface_size (ClutterCairoTexture *self,
void void
clutter_cairo_texture_clear (ClutterCairoTexture *self) clutter_cairo_texture_clear (ClutterCairoTexture *self)
{ {
ClutterCairoTexturePrivate *priv; cairo_surface_t *surface;
cairo_t *cr;
g_return_if_fail (CLUTTER_IS_CAIRO_TEXTURE (self)); g_return_if_fail (CLUTTER_IS_CAIRO_TEXTURE (self));
priv = self->priv; surface = get_surface (self);
if (!priv->cr_surface_data) cr = cairo_create (surface);
return; cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR);
cairo_paint (cr);
memset (priv->cr_surface_data, 0, priv->height * priv->rowstride); cairo_destroy (cr);
} }

View File

@ -75,7 +75,12 @@ struct _ClutterCairoTextureClass
/*< private >*/ /*< private >*/
ClutterTextureClass parent_class; ClutterTextureClass parent_class;
void (*_clutter_cairo_1) (void); /*< public >*/
cairo_surface_t *(* create_surface) (ClutterCairoTexture *texture,
guint width,
guint height);
/*< private >*/
void (*_clutter_cairo_2) (void); void (*_clutter_cairo_2) (void);
void (*_clutter_cairo_3) (void); void (*_clutter_cairo_3) (void);
void (*_clutter_cairo_4) (void); void (*_clutter_cairo_4) (void);

View File

@ -1,5 +1,6 @@
BOOLEAN:BOXED BOOLEAN:BOXED
BOOLEAN:STRING,UINT,ENUM BOOLEAN:STRING,UINT,ENUM
BOXED:UINT,UINT
DOUBLE:VOID DOUBLE:VOID
UINT:VOID UINT:VOID
VOID:BOXED VOID:BOXED

View File

@ -46,7 +46,7 @@ UNIT_TESTS = \
test-binding-pool.c \ test-binding-pool.c \
test-text.c \ test-text.c \
test-text-field.c \ test-text-field.c \
test-clutter-cairo-flowers.c \ test-cairo-flowers.c \
test-cogl-vertex-buffer.c \ test-cogl-vertex-buffer.c \
test-bin-layout.c \ test-bin-layout.c \
test-flow-layout.c \ test-flow-layout.c \

View File

@ -169,7 +169,7 @@ tick (ClutterTimeline *timeline,
} }
int int
test_clutter_cairo_flowers_main (int argc, char **argv) test_cairo_flowers_main (int argc, char **argv)
{ {
int i; int i;
ClutterActor *stage; ClutterActor *stage;
@ -184,7 +184,6 @@ test_clutter_cairo_flowers_main (int argc, char **argv)
stage = clutter_stage_get_default (); stage = clutter_stage_get_default ();
clutter_stage_set_color (CLUTTER_STAGE (stage), &stage_color); clutter_stage_set_color (CLUTTER_STAGE (stage), &stage_color);
clutter_stage_set_fullscreen (CLUTTER_STAGE (stage), TRUE);
for (i=0; i< N_FLOWERS; i++) for (i=0; i< N_FLOWERS; i++)
{ {