[cairo-commit] 2 commits - src/cairo-gl-glyphs.c src/cairo-gl-private.h src/cairo-gl-surface.c

Eric Anholt anholt at kemper.freedesktop.org
Thu Jan 21 13:24:04 PST 2010


 src/cairo-gl-glyphs.c  |   60 +++++-
 src/cairo-gl-private.h |    3 
 src/cairo-gl-surface.c |  457 ++++++++++++++++++++++++++++++++++++++++++++-----
 3 files changed, 470 insertions(+), 50 deletions(-)

New commits:
commit 12d521df8acc483b2daa844d4f05dc2fe2765ba6
Author: Eric Anholt <eric at anholt.net>
Date:   Thu Jan 21 13:19:17 2010 -0800

    [gl] Use GL_RGBA textures even for CAIRO_CONTENT_COLOR.
    
    When the texture is GL_RGB, GL_CLAMP_TO_BORDER (EXTEND_NONE) fills the
    border color alpha channel with 1, when the whole reason we were using
    the border color was to get a color and alpha of 0.  We're forced to
    use GL_RGBA textures and do extra work to fill in the alpha channel of
    them to 1 to get cairo's desired behavior.
    
    This fixes a failure in rotate-image-surface-paint and 4 other
    testcases.  No performance difference in firefox-talos-gfx.

diff --git a/src/cairo-gl-surface.c b/src/cairo-gl-surface.c
index d191bb4..2acc8b5 100644
--- a/src/cairo-gl-surface.c
+++ b/src/cairo-gl-surface.c
@@ -42,6 +42,13 @@
 slim_hidden_proto (cairo_gl_context_reference);
 slim_hidden_proto (cairo_gl_context_destroy);
 
+static cairo_int_status_t
+_cairo_gl_surface_fill_rectangles (void			   *abstract_surface,
+				   cairo_operator_t	    op,
+				   const cairo_color_t     *color,
+				   cairo_rectangle_int_t   *rects,
+				   int			    num_rects);
+
 #define BIAS .375
 
 static inline float
@@ -379,8 +386,9 @@ _cairo_gl_set_operator (cairo_gl_surface_t *dst, cairo_operator_t op,
     src_factor = blend_factors[op].src;
     dst_factor = blend_factors[op].dst;
 
-    /* We may have a visual with alpha bits despite the user requesting
-     * CAIRO_CONTENT_COLOR.  So clear out those bits in that case.
+    /* Even when the user requests CAIRO_CONTENT_COLOR, we use GL_RGBA
+     * due to texture filtering of GL_CLAMP_TO_BORDER.  So fix those
+     * bits in that case.
      */
     if (dst->base.content == CAIRO_CONTENT_COLOR) {
 	if (src_factor == GL_ONE_MINUS_DST_ALPHA)
@@ -486,10 +494,19 @@ _cairo_gl_surface_create_scratch (cairo_gl_context_t   *ctx,
 	format = GL_RGBA;
 	break;
     case CAIRO_CONTENT_ALPHA:
+	/* We want to be trying GL_ALPHA framebuffer objects here. */
 	format = GL_RGBA;
 	break;
     case CAIRO_CONTENT_COLOR:
-	format = GL_RGB;
+	/* GL_RGB is almost what we want here -- sampling 1 alpha when
+	 * texturing, using 1 as destination alpha factor in blending,
+	 * etc.  However, when filtering with GL_CLAMP_TO_BORDER, the
+	 * alpha channel of the border color will also be clamped to
+	 * 1, when we actually want the border color we explicitly
+	 * specified.  So, we have to store RGBA, and fill the alpha
+	 * channel with 1 when blending.
+	 */
+	format = GL_RGBA;
 	break;
     }
 
@@ -524,6 +541,24 @@ _cairo_gl_surface_create_scratch (cairo_gl_context_t   *ctx,
     return &surface->base;
 }
 
+static void
+_cairo_gl_surface_clear (cairo_gl_surface_t *surface)
+{
+    cairo_gl_context_t *ctx;
+
+    ctx = _cairo_gl_context_acquire (surface->ctx);
+    _cairo_gl_set_destination (surface);
+    if (surface->base.content == CAIRO_CONTENT_COLOR)
+	glClearColor (0.0, 0.0, 0.0, 1.0);
+    else
+	glClearColor (0.0, 0.0, 0.0, 0.0);
+    glClear (GL_COLOR_BUFFER_BIT);
+    _cairo_gl_context_release (ctx);
+
+    surface->base.is_clear = TRUE;
+
+}
+
 cairo_surface_t *
 cairo_gl_surface_create (cairo_gl_context_t   *ctx,
 			 cairo_content_t	content,
@@ -546,13 +581,7 @@ cairo_gl_surface_create (cairo_gl_context_t   *ctx,
 	return &surface->base;
 
     /* Cairo surfaces start out initialized to transparent (black) */
-    ctx = _cairo_gl_context_acquire (surface->ctx);
-    _cairo_gl_set_destination (surface);
-    glClearColor (0.0, 0.0, 0.0, 0.0);
-    glClear (GL_COLOR_BUFFER_BIT);
-    _cairo_gl_context_release (ctx);
-
-    surface->base.is_clear = TRUE;
+    _cairo_gl_surface_clear (surface);
 
     return &surface->base;
 }
@@ -689,6 +718,32 @@ _cairo_gl_surface_draw_image (cairo_gl_surface_t *dst,
 
     cairo_surface_destroy (&clone->base);
 
+    /* If we just treated some rgb-only data as rgba, then we have to
+     * go back and fix up the alpha channel where we filled in this
+     * texture data.
+     */
+    if (!has_alpha) {
+	cairo_rectangle_int_t rect;
+	cairo_color_t color;
+
+	rect.x = dst_x;
+	rect.y = dst_y;
+	rect.width = width;
+	rect.height = height;
+
+	color.red = 0.0;
+	color.green = 0.0;
+	color.blue = 0.0;
+	color.alpha = 1.0;
+
+	glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_TRUE);
+	_cairo_gl_surface_fill_rectangles (dst,
+					   CAIRO_OPERATOR_SOURCE,
+					   &color,
+					   &rect, 1);
+	glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
+    }
+
     return CAIRO_STATUS_SUCCESS;
 }
 
@@ -1122,7 +1177,7 @@ _cairo_gl_set_src_operand (cairo_gl_context_t *ctx,
 			   cairo_gl_composite_setup_t *setup)
 {
     cairo_surface_attributes_t *src_attributes;
-    GLfloat constant_color[4] = {0.0, 0.0, 0.0, 1.0};
+    GLfloat constant_color[4] = {0.0, 0.0, 0.0, 0.0};
 
     src_attributes = &setup->src.operand.texture.attributes;
 
@@ -1134,7 +1189,7 @@ _cairo_gl_set_src_operand (cairo_gl_context_t *ctx,
     case OPERAND_TEXTURE:
 	_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. */
+	/* Set up the constant color we use to set color to 0 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);
@@ -1150,16 +1205,7 @@ _cairo_gl_set_src_operand (cairo_gl_context_t *ctx,
 	    glTexEnvi (GL_TEXTURE_ENV, GL_SRC0_RGB, GL_TEXTURE0);
 	else
 	    glTexEnvi (GL_TEXTURE_ENV, GL_SRC0_RGB, GL_CONSTANT);
-	/* 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_ALPHA, GL_TEXTURE0);
-	else
-	    glTexEnvi (GL_TEXTURE_ENV, GL_SRC0_ALPHA, GL_CONSTANT);
+	glTexEnvi (GL_TEXTURE_ENV, GL_SRC0_ALPHA, GL_TEXTURE0);
 	glTexEnvi (GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR);
 	glTexEnvi (GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA);
 	break;
@@ -1174,8 +1220,8 @@ static void
 _cairo_gl_set_src_alpha_operand (cairo_gl_context_t *ctx,
 				 cairo_gl_composite_setup_t *setup)
 {
+    GLfloat constant_color[4] = {0.0, 0.0, 0.0, 0.0};
     cairo_surface_attributes_t *src_attributes;
-    GLfloat constant_color[4];
 
     src_attributes = &setup->src.operand.texture.attributes;
 
@@ -1194,25 +1240,13 @@ _cairo_gl_set_src_alpha_operand (cairo_gl_context_t *ctx,
 	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_SRC0_RGB, GL_TEXTURE0);
+	glTexEnvi (GL_TEXTURE_ENV, GL_SRC0_ALPHA, GL_TEXTURE0);
 	glTexEnvi (GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_ALPHA);
 	glTexEnvi (GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA);
 	break;
@@ -1227,7 +1261,7 @@ _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};
+    GLfloat constant_color[4] = {0.0, 0.0, 0.0, 0.0};
 
     mask_attributes = &setup->mask.operand.texture.attributes;
 
@@ -1259,7 +1293,7 @@ _cairo_gl_set_component_alpha_mask_operand (cairo_gl_context_t *ctx,
     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. */
+	/* Set up the constant color we use to set color to 0 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.
@@ -1271,16 +1305,7 @@ _cairo_gl_set_component_alpha_mask_operand (cairo_gl_context_t *ctx,
 	    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_SRC0_ALPHA, GL_TEXTURE1);
 	glTexEnvi (GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR);
 	glTexEnvi (GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA);
 	break;
@@ -2344,13 +2369,7 @@ _cairo_gl_surface_paint (void *abstract_surface,
 {
     /* simplify the common case of clearing the surface */
     if (op == CAIRO_OPERATOR_CLEAR && clip == NULL) {
-	cairo_gl_surface_t *surface = abstract_surface;
-	cairo_gl_context_t *ctx;
-
-	ctx = _cairo_gl_context_acquire (surface->ctx);
-	_cairo_gl_set_destination (surface);
-	glClear (GL_COLOR_BUFFER_BIT);
-	_cairo_gl_context_release (ctx);
+	_cairo_gl_surface_clear (abstract_surface);
 
 	return CAIRO_STATUS_SUCCESS;
     }
commit e316cb9db57094359c28f6b740e73674e8dcc134
Author: Eric Anholt <eric at anholt.net>
Date:   Wed Jan 20 13:33:56 2010 -0800

    [gl] Add support for component-alpha glyph rendering.
    
    This is done using a mask and the two-pass trick I did for EXA.  Fixes
    text-antialias-subpixel.

diff --git a/src/cairo-gl-glyphs.c b/src/cairo-gl-glyphs.c
index fb3bdd4..dbc7100 100644
--- a/src/cairo-gl-glyphs.c
+++ b/src/cairo-gl-glyphs.c
@@ -366,27 +366,35 @@ _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);
-    _cairo_gl_set_operator (dst, op);
+    /* If we're doing some CA glyphs, we're only doing it for ADD,
+     * which doesn't require the source alpha value in blending.
+     */
+    _cairo_gl_set_operator (dst, op, FALSE);
     _cairo_gl_set_src_operand (ctx, &composite_setup);
 
     _cairo_scaled_font_freeze_cache (scaled_font);
@@ -470,8 +478,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 +571,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 +631,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 +642,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-private.h b/src/cairo-gl-private.h
index a3f2dc9..490bc5c 100644
--- a/src/cairo-gl-private.h
+++ b/src/cairo-gl-private.h
@@ -169,7 +169,8 @@ cairo_private cairo_bool_t
 _cairo_gl_operator_is_supported (cairo_operator_t op);
 
 cairo_private void
-_cairo_gl_set_operator (cairo_gl_surface_t *dst, cairo_operator_t op);
+_cairo_gl_set_operator (cairo_gl_surface_t *dst, cairo_operator_t op,
+			cairo_bool_t component_alpha);
 
 cairo_private void
 _cairo_gl_set_src_operand (cairo_gl_context_t *ctx,
diff --git a/src/cairo-gl-surface.c b/src/cairo-gl-surface.c
index 63bbb6c..d191bb4 100644
--- a/src/cairo-gl-surface.c
+++ b/src/cairo-gl-surface.c
@@ -349,7 +349,8 @@ _cairo_gl_operator_is_supported (cairo_operator_t op)
 }
 
 void
-_cairo_gl_set_operator (cairo_gl_surface_t *dst, cairo_operator_t op)
+_cairo_gl_set_operator (cairo_gl_surface_t *dst, cairo_operator_t op,
+			cairo_bool_t component_alpha)
 {
     struct {
 	GLenum src;
@@ -388,6 +389,13 @@ _cairo_gl_set_operator (cairo_gl_surface_t *dst, cairo_operator_t op)
 	    src_factor = GL_ONE;
     }
 
+    if (component_alpha) {
+	if (dst_factor == GL_ONE_MINUS_SRC_ALPHA)
+	    dst_factor = GL_ONE_MINUS_SRC_COLOR;
+	if (dst_factor == GL_SRC_ALPHA)
+	    dst_factor = GL_SRC_COLOR;
+    }
+
     glEnable (GL_BLEND);
     glBlendFunc (src_factor, dst_factor);
 }
@@ -1158,6 +1166,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, TRUE);
+    _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, TRUE);
+    _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 +1542,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));
 
@@ -1221,7 +1583,7 @@ _cairo_gl_surface_composite (cairo_operator_t		  op,
 
     ctx = _cairo_gl_context_acquire (dst->ctx);
     _cairo_gl_set_destination (dst);
-    _cairo_gl_set_operator (dst, op);
+    _cairo_gl_set_operator (dst, op, FALSE);
 
     _cairo_gl_set_src_operand (ctx, &setup);
 
@@ -1443,7 +1805,7 @@ _cairo_gl_surface_fill_rectangles_fixed (void			 *abstract_surface,
     ctx = _cairo_gl_context_acquire (surface->ctx);
 
     _cairo_gl_set_destination (surface);
-    _cairo_gl_set_operator (surface, op);
+    _cairo_gl_set_operator (surface, op, FALSE);
 
     if (num_rects > N_STACK_RECTS) {
 	vertices = _cairo_malloc_ab (num_rects, sizeof (GLfloat) * 4 * 2);
@@ -1561,7 +1923,7 @@ _cairo_gl_surface_fill_rectangles_glsl (void                  *abstract_surface,
     glUseProgramObjectARB (ctx->fill_rectangles_shader);
 
     _cairo_gl_set_destination (surface);
-    _cairo_gl_set_operator (surface, op);
+    _cairo_gl_set_operator (surface, op, FALSE);
 
     gl_color[0] = color->red * color->alpha;
     gl_color[1] = color->green * color->alpha;
@@ -1922,7 +2284,7 @@ _cairo_gl_surface_create_span_renderer (cairo_operator_t	 op,
 
     src_attributes = &renderer->setup.src.operand.texture.attributes;
 
-    _cairo_gl_set_operator (dst, op);
+    _cairo_gl_set_operator (dst, op, FALSE);
     _cairo_gl_set_src_operand (dst->ctx, &renderer->setup);
 
     /* Set up the mask to source from the incoming vertex color. */


More information about the cairo-commit mailing list