[cairo] [PATCH] [gl] Attempt to support component-alpha rendering.

Eric Anholt eric at anholt.net
Wed Jan 20 13:37:21 PST 2010


Something is still going wrong with the text-antialias-subpixel test and
the rendering is as if the glyphs were non-subpixel.
---
 src/cairo-gl-glyphs.c  |   55 ++++++--
 src/cairo-gl-surface.c |  366 +++++++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 405 insertions(+), 16 deletions(-)

diff --git a/src/cairo-gl-glyphs.c b/src/cairo-gl-glyphs.c
index fb3bdd4..d6e5d28 100644
--- a/src/cairo-gl-glyphs.c
+++ b/src/cairo-gl-glyphs.c
@@ -366,23 +366,28 @@ _render_glyphs (cairo_gl_surface_t	*dst,
 
     ctx = _cairo_gl_context_acquire (dst->ctx);
 
-    /* Set up the mask to source from the incoming vertex color. */
+    /* Set up the IN operator for source IN mask.
+     *
+     * IN (normal, any op): dst.argb = src.argb * mask.aaaa
+     * IN (component, ADD): dst.argb = src.argb * mask.argb
+     *
+     * The mask channel selection for component alpha ADD will be updated in
+     * the loop over glyphs below.
+     */
     glActiveTexture (GL_TEXTURE1);
     glEnable (GL_TEXTURE_2D);
-    /* IN: dst.argb = src.argb * mask.aaaa */
     glTexEnvi (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
     glTexEnvi (GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_MODULATE);
     glTexEnvi (GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_MODULATE);
 
-    glTexEnvi (GL_TEXTURE_ENV, GL_SRC0_RGB, GL_PREVIOUS);
-    glTexEnvi (GL_TEXTURE_ENV, GL_SRC0_ALPHA, GL_PREVIOUS);
-    glTexEnvi (GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR);
+    glTexEnvi (GL_TEXTURE_ENV, GL_SRC0_RGB, GL_TEXTURE1);
+    glTexEnvi (GL_TEXTURE_ENV, GL_SRC0_ALPHA, GL_TEXTURE1);
+    glTexEnvi (GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_ALPHA);
     glTexEnvi (GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA);
 
-    /* XXX component alpha */
-    glTexEnvi (GL_TEXTURE_ENV, GL_SRC1_RGB, GL_TEXTURE1);
-    glTexEnvi (GL_TEXTURE_ENV, GL_SRC1_ALPHA, GL_TEXTURE1);
-    glTexEnvi (GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_ALPHA);
+    glTexEnvi (GL_TEXTURE_ENV, GL_SRC1_RGB, GL_PREVIOUS);
+    glTexEnvi (GL_TEXTURE_ENV, GL_SRC1_ALPHA, GL_PREVIOUS);
+    glTexEnvi (GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR);
     glTexEnvi (GL_TEXTURE_ENV, GL_OPERAND1_ALPHA, GL_SRC_ALPHA);
 
     _cairo_gl_set_destination (dst);
@@ -470,8 +475,17 @@ _render_glyphs (cairo_gl_surface_t	*dst,
 	    glBindTexture (GL_TEXTURE_2D, cache->tex);
 
 	    last_format = scaled_glyph->surface->format;
-	    if (last_format == CAIRO_FORMAT_ARGB32)
+	    /* If we're doing component alpha in this function, it should
+	     * only be in the case of CAIRO_OPERATOR_ADD.  In that case, we just
+	     * need to make sure we send the rgb bits down to the destination.
+	     */
+	    if (last_format == CAIRO_FORMAT_ARGB32) {
+		assert (op == CAIRO_OPERATOR_ADD);
+		glTexEnvi (GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR);
 		*has_component_alpha = TRUE;
+	    } else {
+		glTexEnvi (GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_ALPHA);
+	    }
 
 	    /* XXX component alpha */
 	}
@@ -554,6 +568,7 @@ _cairo_gl_surface_show_glyphs_via_mask (cairo_gl_surface_t	*dst,
     cairo_bool_t has_component_alpha;
     int i;
 
+    /* XXX: For non-CA, this should be CAIRO_CONTENT_ALPHA to save memory */
     mask = cairo_gl_surface_create (dst->ctx,
 	                            CAIRO_CONTENT_COLOR_ALPHA,
 				    glyph_extents->width,
@@ -613,6 +628,7 @@ _cairo_gl_surface_show_glyphs (void			*abstract_dst,
     cairo_bool_t overlap, use_mask = FALSE;
     cairo_bool_t has_component_alpha;
     cairo_status_t status;
+    int i;
 
     if (! GLEW_ARB_vertex_buffer_object)
 	return UNSUPPORTED ("requires ARB_vertex_buffer_object");
@@ -623,6 +639,25 @@ _cairo_gl_surface_show_glyphs (void			*abstract_dst,
     if (! _cairo_operator_bounded_by_mask (op))
 	use_mask |= TRUE;
 
+    /* If any of the glyphs are component alpha, we have to go through a mask,
+     * since only _cairo_gl_surface_composite() currently supports component
+     * alpha.
+     */
+    for (i = 0; i < num_glyphs; i++) {
+	cairo_scaled_glyph_t *scaled_glyph;
+
+	status = _cairo_scaled_glyph_lookup (scaled_font,
+					     glyphs[i].index,
+					     CAIRO_SCALED_GLYPH_INFO_SURFACE,
+					     &scaled_glyph);
+	if (!_cairo_status_is_error (status) &&
+	    scaled_glyph->surface->format == CAIRO_FORMAT_ARGB32)
+	{
+	    use_mask = TRUE;
+	    break;
+	}
+    }
+
     /* For CLEAR, cairo's rendering equation (quoting Owen's description in:
      * http://lists.cairographics.org/archives/cairo/2005-August/004992.html)
      * is:
diff --git a/src/cairo-gl-surface.c b/src/cairo-gl-surface.c
index 63bbb6c..e27c75d 100644
--- a/src/cairo-gl-surface.c
+++ b/src/cairo-gl-surface.c
@@ -1158,6 +1158,350 @@ _cairo_gl_set_src_operand (cairo_gl_context_t *ctx,
     }
 }
 
+/* This is like _cairo_gl_set_src_operand, but instead swizzles the source
+ * for creating the "source alpha" value (src.aaaa * mask.argb) required by
+ * component alpha rendering.
+ */
+static void
+_cairo_gl_set_src_alpha_operand (cairo_gl_context_t *ctx,
+				 cairo_gl_composite_setup_t *setup)
+{
+    cairo_surface_attributes_t *src_attributes;
+    GLfloat constant_color[4];
+
+    src_attributes = &setup->src.operand.texture.attributes;
+
+    switch (setup->src.type) {
+    case OPERAND_CONSTANT:
+	constant_color[0] = setup->src.operand.constant.color[3];
+	constant_color[1] = setup->src.operand.constant.color[3];
+	constant_color[2] = setup->src.operand.constant.color[3];
+	constant_color[3] = setup->src.operand.constant.color[3];
+       _cairo_gl_set_tex_combine_constant_color (ctx, 0, constant_color);
+	break;
+    case OPERAND_TEXTURE:
+	constant_color[0] = 0.0;
+	constant_color[1] = 0.0;
+	constant_color[2] = 0.0;
+	constant_color[3] = 1.0;
+	_cairo_gl_set_texture_surface (0, setup->src.operand.texture.tex,
+				       src_attributes);
+	/* Set up the constant color we use to set alpha to 1 if needed. */
+	glTexEnvfv (GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, constant_color);
+	/* Set up the combiner to just set color to the sampled texture. */
+	glTexEnvi (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
+	glTexEnvi (GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_REPLACE);
+	glTexEnvi (GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_REPLACE);
+
+	/* Wire the src alpha to 1 if the surface doesn't have it.
+	 * We may have a teximage with alpha bits even though we didn't ask
+	 * for it and we don't pay attention to setting alpha to 1 in a dest
+	 * that has inadvertent alpha.
+	 */
+	if (setup->src.operand.texture.surface->base.content != CAIRO_CONTENT_COLOR) {
+	    glTexEnvi (GL_TEXTURE_ENV, GL_SRC0_RGB, GL_TEXTURE0);
+	    glTexEnvi (GL_TEXTURE_ENV, GL_SRC0_ALPHA, GL_TEXTURE0);
+	} else {
+	    glTexEnvi (GL_TEXTURE_ENV, GL_SRC0_RGB, GL_CONSTANT);
+	    glTexEnvi (GL_TEXTURE_ENV, GL_SRC0_ALPHA, GL_CONSTANT);
+	}
+	glTexEnvi (GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_ALPHA);
+	glTexEnvi (GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA);
+	break;
+    }
+}
+
+/* This is like _cairo_gl_set_src_alpha_operand, for component alpha setup
+ * of the mask part of IN to produce a "source alpha" value.
+ */
+static void
+_cairo_gl_set_component_alpha_mask_operand (cairo_gl_context_t *ctx,
+					    cairo_gl_composite_setup_t *setup)
+{
+    cairo_surface_attributes_t *mask_attributes;
+    GLfloat constant_color[4] = {0.0, 0.0, 0.0, 1.0};
+
+    mask_attributes = &setup->mask.operand.texture.attributes;
+
+    glActiveTexture (GL_TEXTURE1);
+    glEnable (GL_TEXTURE_2D);
+    glTexEnvi (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
+    glTexEnvi (GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_MODULATE);
+    glTexEnvi (GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_MODULATE);
+
+    glTexEnvi (GL_TEXTURE_ENV, GL_SRC1_RGB, GL_PREVIOUS);
+    glTexEnvi (GL_TEXTURE_ENV, GL_SRC1_ALPHA, GL_PREVIOUS);
+    glTexEnvi (GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR);
+    glTexEnvi (GL_TEXTURE_ENV, GL_OPERAND1_ALPHA, GL_SRC_ALPHA);
+
+    switch (setup->mask.type) {
+    case OPERAND_CONSTANT:
+	/* Have to have a dummy texture bound in order to use the combiner unit. */
+	glBindTexture (GL_TEXTURE_2D, ctx->dummy_tex);
+
+	glTexEnvfv (GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR,
+		    setup->mask.operand.constant.color);
+
+	glTexEnvi (GL_TEXTURE_ENV, GL_SRC0_RGB, GL_CONSTANT);
+	glTexEnvi (GL_TEXTURE_ENV, GL_SRC0_ALPHA, GL_CONSTANT);
+	glTexEnvi (GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR);
+	glTexEnvi (GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA);
+
+	break;
+    case OPERAND_TEXTURE:
+	_cairo_gl_set_texture_surface (1, setup->mask.operand.texture.tex,
+				       mask_attributes);
+	/* Set up the constant color we use to set alpha to 1 if needed. */
+	glTexEnvfv (GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, constant_color);
+
+	/* Force the mask color to 0 if the surface should be alpha-only.
+	 * We may have a teximage with color bits if the implementation doesn't
+	 * support GL_ALPHA FBOs.
+	 */
+	if (setup->mask.operand.texture.surface->base.content !=
+	    CAIRO_CONTENT_ALPHA)
+	    glTexEnvi (GL_TEXTURE_ENV, GL_SRC0_RGB, GL_TEXTURE1);
+	else
+	    glTexEnvi (GL_TEXTURE_ENV, GL_SRC0_RGB, GL_CONSTANT);
+	/* Wire the mask alpha to 1 if the surface doesn't have it.
+	 * We may have a teximage with alpha bits even though we didn't ask
+	 * for it and we don't pay attention to setting alpha to 1 in a dest
+	 * that has inadvertent alpha.
+	 */
+	if (setup->mask.operand.texture.surface->base.content !=
+	    CAIRO_CONTENT_COLOR)
+	    glTexEnvi (GL_TEXTURE_ENV, GL_SRC0_ALPHA, GL_TEXTURE1);
+	else
+	    glTexEnvi (GL_TEXTURE_ENV, GL_SRC0_ALPHA, GL_CONSTANT);
+	glTexEnvi (GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR);
+	glTexEnvi (GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA);
+	break;
+    }
+}
+
+/**
+ * implements component-alpha CAIRO_OPERATOR_SOURCE using two passes of
+ * the simpler operations CAIRO_OPERATOR_DEST_OUT and CAIRO_OPERATOR_ADD.
+ *
+ * From http://anholt.livejournal.com/32058.html:
+ *
+ * The trouble is that component-alpha rendering requires two different sources
+ * for blending: one for the source value to the blender, which is the
+ * per-channel multiplication of source and mask, and one for the source alpha
+ * for multiplying with the destination channels, which is the multiplication
+ * of the source channels by the mask alpha. So the equation for Over is:
+ *
+ * dst.A = src.A * mask.A + (1 - (src.A * mask.A)) * dst.A
+ * dst.R = src.R * mask.R + (1 - (src.A * mask.R)) * dst.R
+ * dst.G = src.G * mask.G + (1 - (src.A * mask.G)) * dst.G
+ * dst.B = src.B * mask.B + (1 - (src.A * mask.B)) * dst.B
+ *
+ * But we can do some simpler operations, right? How about PictOpOutReverse,
+ * which has a source factor of 0 and dest factor of (1 - source alpha). We
+ * can get the source alpha value (srca.X = src.A * mask.X) out of the texture
+ * blenders pretty easily. So we can do a component-alpha OutReverse, which
+ * gets us:
+ *
+ * dst.A = 0 + (1 - (src.A * mask.A)) * dst.A
+ * dst.R = 0 + (1 - (src.A * mask.R)) * dst.R
+ * dst.G = 0 + (1 - (src.A * mask.G)) * dst.G
+ * dst.B = 0 + (1 - (src.A * mask.B)) * dst.B
+ *
+ * OK. And if an op doesn't use the source alpha value for the destination
+ * factor, then we can do the channel multiplication in the texture blenders
+ * to get the source value, and ignore the source alpha that we wouldn't use.
+ * We've supported this in the Radeon driver for a long time. An example would
+ * be PictOpAdd, which does:
+ *
+ * dst.A = src.A * mask.A + dst.A
+ * dst.R = src.R * mask.R + dst.R
+ * dst.G = src.G * mask.G + dst.G
+ * dst.B = src.B * mask.B + dst.B
+ *
+ * Hey, this looks good! If we do a PictOpOutReverse and then a PictOpAdd right
+ * after it, we get:
+ *
+ * dst.A = src.A * mask.A + ((1 - (src.A * mask.A)) * dst.A)
+ * dst.R = src.R * mask.R + ((1 - (src.A * mask.R)) * dst.R)
+ * dst.G = src.G * mask.G + ((1 - (src.A * mask.G)) * dst.G)
+ * dst.B = src.B * mask.B + ((1 - (src.A * mask.B)) * dst.B)
+ *
+ * This two-pass trickery could be avoided using a new GL extension that
+ * lets two values come out of the shader and into the blend unit.
+ */
+static cairo_int_status_t
+_cairo_gl_surface_composite_component_alpha (cairo_operator_t op,
+					     const cairo_pattern_t *src,
+					     const cairo_pattern_t *mask,
+					     void *abstract_dst,
+					     int src_x,
+					     int src_y,
+					     int mask_x,
+					     int mask_y,
+					     int dst_x,
+					     int dst_y,
+					     unsigned int width,
+					     unsigned int height,
+					     cairo_region_t *clip_region)
+{
+    cairo_gl_surface_t	*dst = abstract_dst;
+    cairo_surface_attributes_t *src_attributes, *mask_attributes = NULL;
+    cairo_gl_context_t *ctx;
+    struct gl_point {
+	GLfloat x, y;
+    } vertices_stack[8], texcoord_src_stack[8], texcoord_mask_stack[8];
+    struct gl_point *vertices = vertices_stack;
+    struct gl_point *texcoord_src = texcoord_src_stack;
+    struct gl_point *texcoord_mask = texcoord_mask_stack;
+    cairo_status_t status;
+    int num_vertices, i;
+    GLenum err;
+    cairo_gl_composite_setup_t setup;
+
+    if (op != CAIRO_OPERATOR_OVER)
+	return UNSUPPORTED ("unsupported component alpha operator");
+
+    memset (&setup, 0, sizeof (setup));
+
+    status = _cairo_gl_operand_init (&setup.src, src, dst,
+				     src_x, src_y,
+				     dst_x, dst_y,
+				     width, height);
+    if (unlikely (status))
+	return status;
+    src_attributes = &setup.src.operand.texture.attributes;
+
+    status = _cairo_gl_operand_init (&setup.mask, mask, dst,
+				     mask_x, mask_y,
+				     dst_x, dst_y,
+				     width, height);
+    if (unlikely (status)) {
+	_cairo_gl_operand_destroy (&setup.src);
+	return status;
+    }
+    mask_attributes = &setup.mask.operand.texture.attributes;
+
+    ctx = _cairo_gl_context_acquire (dst->ctx);
+    _cairo_gl_set_destination (dst);
+
+    if (clip_region != NULL) {
+	int num_rectangles;
+
+	num_rectangles = cairo_region_num_rectangles (clip_region);
+	if (num_rectangles * 4 > ARRAY_LENGTH (vertices_stack)) {
+	    vertices = _cairo_malloc_ab (num_rectangles,
+					 4*3*sizeof (vertices[0]));
+	    if (unlikely (vertices == NULL)) {
+		status = _cairo_error (CAIRO_STATUS_NO_MEMORY);
+		goto CONTEXT_RELEASE;
+	    }
+
+	    texcoord_src = vertices + num_rectangles * 4;
+	    texcoord_mask = texcoord_src + num_rectangles * 4;
+	}
+
+	for (i = 0; i < num_rectangles; i++) {
+	    cairo_rectangle_int_t rect;
+
+	    cairo_region_get_rectangle (clip_region, i, &rect);
+	    vertices[4*i + 0].x = rect.x;
+	    vertices[4*i + 0].y = rect.y;
+	    vertices[4*i + 1].x = rect.x + rect.width;
+	    vertices[4*i + 1].y = rect.y;
+	    vertices[4*i + 2].x = rect.x + rect.width;
+	    vertices[4*i + 2].y = rect.y + rect.height;
+	    vertices[4*i + 3].x = rect.x;
+	    vertices[4*i + 3].y = rect.y + rect.height;
+	}
+
+	num_vertices = 4 * num_rectangles;
+    } else {
+	vertices[0].x = dst_x;
+	vertices[0].y = dst_y;
+	vertices[1].x = dst_x + width;
+	vertices[1].y = dst_y;
+	vertices[2].x = dst_x + width;
+	vertices[2].y = dst_y + height;
+	vertices[3].x = dst_x;
+	vertices[3].y = dst_y + height;
+
+	num_vertices = 4;
+    }
+
+    glVertexPointer (2, GL_FLOAT, sizeof (GLfloat) * 2, vertices);
+    glEnableClientState (GL_VERTEX_ARRAY);
+
+    if (setup.src.type == OPERAND_TEXTURE) {
+	for (i = 0; i < num_vertices; i++) {
+	    double s, t;
+
+	    s = vertices[i].x;
+	    t = vertices[i].y;
+	    cairo_matrix_transform_point (&src_attributes->matrix, &s, &t);
+	    texcoord_src[i].x = s;
+	    texcoord_src[i].y = t;
+	}
+
+	glClientActiveTexture (GL_TEXTURE0);
+	glTexCoordPointer (2, GL_FLOAT, sizeof (GLfloat)*2, texcoord_src);
+	glEnableClientState (GL_TEXTURE_COORD_ARRAY);
+    }
+
+    if (setup.mask.type == OPERAND_TEXTURE) {
+	for (i = 0; i < num_vertices; i++) {
+	    double s, t;
+
+	    s = vertices[i].x;
+	    t = vertices[i].y;
+	    cairo_matrix_transform_point (&mask_attributes->matrix, &s, &t);
+	    texcoord_mask[i].x = s;
+	    texcoord_mask[i].y = t;
+	}
+
+	glClientActiveTexture (GL_TEXTURE1);
+	glTexCoordPointer (2, GL_FLOAT, sizeof (GLfloat)*2, texcoord_mask);
+	glEnableClientState (GL_TEXTURE_COORD_ARRAY);
+    }
+
+    _cairo_gl_set_operator (dst, CAIRO_OPERATOR_DEST_OUT);
+    _cairo_gl_set_src_alpha_operand (ctx, &setup);
+    _cairo_gl_set_component_alpha_mask_operand (ctx, &setup);
+    glDrawArrays (GL_QUADS, 0, num_vertices);
+
+    _cairo_gl_set_operator (dst, CAIRO_OPERATOR_ADD);
+    _cairo_gl_set_src_operand (ctx, &setup);
+    glDrawArrays (GL_QUADS, 0, num_vertices);
+
+    glDisable (GL_BLEND);
+
+    glDisableClientState (GL_VERTEX_ARRAY);
+
+    glClientActiveTexture (GL_TEXTURE0);
+    glDisableClientState (GL_TEXTURE_COORD_ARRAY);
+    glActiveTexture (GL_TEXTURE0);
+    glDisable (GL_TEXTURE_2D);
+
+    glClientActiveTexture (GL_TEXTURE1);
+    glDisableClientState (GL_TEXTURE_COORD_ARRAY);
+    glActiveTexture (GL_TEXTURE1);
+    glDisable (GL_TEXTURE_2D);
+
+    while ((err = glGetError ()))
+	fprintf (stderr, "GL error 0x%08x\n", (int) err);
+
+  CONTEXT_RELEASE:
+    _cairo_gl_context_release (ctx);
+
+    _cairo_gl_operand_destroy (&setup.src);
+    if (mask != NULL)
+	_cairo_gl_operand_destroy (&setup.mask);
+
+    if (vertices != vertices_stack)
+	free (vertices);
+
+    return status;
+}
+
 static cairo_int_status_t
 _cairo_gl_surface_composite (cairo_operator_t		  op,
 			     const cairo_pattern_t	 *src,
@@ -1190,12 +1534,22 @@ _cairo_gl_surface_composite (cairo_operator_t		  op,
     if (! _cairo_gl_operator_is_supported (op))
 	return UNSUPPORTED ("unsupported operator");
 
-    /* XXX: There is no sane way of expressing ComponentAlpha using
-     * fixed-function combiners and every possible operator. Look at the
-     * EXA drivers for the more appropriate fallback conditions.
-     */
-    if (mask && mask->has_component_alpha)
-	return UNSUPPORTED ("component alpha");
+    if (mask && mask->has_component_alpha) {
+	/* Try two-pass component alpha support, or bail. */
+	return _cairo_gl_surface_composite_component_alpha(op,
+							   src,
+							   mask,
+							   abstract_dst,
+							   src_x,
+							   src_y,
+							   mask_x,
+							   mask_y,
+							   dst_x,
+							   dst_y,
+							   width,
+							   height,
+							   clip_region);
+    }
 
     memset (&setup, 0, sizeof (setup));
 
-- 
1.6.5.7



More information about the cairo mailing list