From f90017ab4ef69d1275f726f4a8a272bfae3cb5a7 Mon Sep 17 00:00:00 2001 From: "Owen W. Taylor" Date: Sat, 9 May 2009 14:39:01 -0400 Subject: [PATCH 01/13] Add cogl_color_premultiply() Add a convenience function to convert an ARGB color from non-premultiplied to premultiplied form. http://bugzilla.openedhand.com/show_bug.cgi?id=1406 Signed-off-by: Robert Bragg --- clutter/cogl/cogl-color.h | 12 ++++++++++++ clutter/cogl/common/cogl-color.c | 8 ++++++++ 2 files changed, 20 insertions(+) diff --git a/clutter/cogl/cogl-color.h b/clutter/cogl/cogl-color.h index 0cbc479a4..73de891de 100644 --- a/clutter/cogl/cogl-color.h +++ b/clutter/cogl/cogl-color.h @@ -228,6 +228,18 @@ float cogl_color_get_blue (const CoglColor *color); */ float cogl_color_get_alpha (const CoglColor *color); +/** + * cogl_color_premultiply: + * @color: the color to premultiply + * + * Converts a non-premultiplied color to a pre-multiplied color. For + * example, semi-transparent red is (1.0, 0, 0, 0.5) when non-premultiplied + * and (0.5, 0, 0, 0.5) when premultiplied. + * + * Since: 1.0 + */ +void cogl_color_premultiply (CoglColor *color); + G_END_DECLS #endif /* __COGL_COLOR_H__ */ diff --git a/clutter/cogl/common/cogl-color.c b/clutter/cogl/common/cogl-color.c index 306005f2f..df338fcc4 100644 --- a/clutter/cogl/common/cogl-color.c +++ b/clutter/cogl/common/cogl-color.c @@ -153,6 +153,14 @@ cogl_color_get_alpha (const CoglColor *color) return ((float) color->alpha / 255.0); } +void +cogl_color_premultiply (CoglColor *color) +{ + color->red = (color->red * color->alpha + 128) / 255; + color->green = (color->green * color->alpha + 128) / 255; + color->blue = (color->blue * color->alpha + 128) / 255; +} + void cogl_set_source_color4ub (guint8 red, guint8 green, From 4dcd5f6134f62bf1d5988cea7479b14c896190fd Mon Sep 17 00:00:00 2001 From: "Owen W. Taylor" Date: Sat, 9 May 2009 14:39:01 -0400 Subject: [PATCH 02/13] Use correct texture format for pixmap textures and FBO's RGBA data in X pixmaps and in FBOs is already premultiplied; use the right format when creating cogl textures. http://bugzilla.openedhand.com/show_bug.cgi?id=1406 Signed-off-by: Robert Bragg --- clutter/clutter-texture.c | 4 ++-- clutter/glx/clutter-glx-texture-pixmap.c | 6 ++++-- clutter/x11/clutter-x11-texture-pixmap.c | 6 ++++-- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/clutter/clutter-texture.c b/clutter/clutter-texture.c index c619cbabb..5a31cffa4 100644 --- a/clutter/clutter-texture.c +++ b/clutter/clutter-texture.c @@ -296,7 +296,7 @@ clutter_texture_realize (ClutterActor *actor) tex = cogl_texture_new_with_size (priv->width, priv->height, flags, - COGL_PIXEL_FORMAT_RGBA_8888); + COGL_PIXEL_FORMAT_RGBA_8888_PRE); cogl_material_set_layer (priv->material, 0, tex); @@ -2243,7 +2243,7 @@ on_fbo_source_size_change (GObject *object, tex = cogl_texture_new_with_size (MAX (priv->width, 1), MAX (priv->height, 1), flags, - COGL_PIXEL_FORMAT_RGBA_8888); + COGL_PIXEL_FORMAT_RGBA_8888_PRE); cogl_material_set_layer (priv->material, 0, tex); diff --git a/clutter/glx/clutter-glx-texture-pixmap.c b/clutter/glx/clutter-glx-texture-pixmap.c index fb174fd6b..aab75f658 100644 --- a/clutter/glx/clutter-glx-texture-pixmap.c +++ b/clutter/glx/clutter-glx-texture-pixmap.c @@ -359,14 +359,16 @@ create_cogl_texture (ClutterTexture *texture, handle = cogl_texture_new_from_foreign (tex, CGL_TEXTURE_RECTANGLE_ARB, width, height, 0, 0, - cogl_format | COGL_BGR_BIT); + cogl_format | COGL_BGR_BIT | + COGL_PREMULT_BIT); } else { handle = cogl_texture_new_with_size (width, height, COGL_TEXTURE_NO_SLICING, - cogl_format | COGL_BGR_BIT); + cogl_format | COGL_BGR_BIT | + COGL_PREMULT_BIT); using_rectangle = FALSE; } diff --git a/clutter/x11/clutter-x11-texture-pixmap.c b/clutter/x11/clutter-x11-texture-pixmap.c index cb0c38529..a09ab1f9d 100644 --- a/clutter/x11/clutter-x11-texture-pixmap.c +++ b/clutter/x11/clutter-x11-texture-pixmap.c @@ -871,7 +871,8 @@ clutter_x11_texture_pixmap_update_area_real (ClutterX11TexturePixmap *texture, width, height, bytes_per_line, 4, - CLUTTER_TEXTURE_RGB_FLAG_BGR, + CLUTTER_TEXTURE_RGB_FLAG_BGR | + CLUTTER_TEXTURE_RGB_FLAG_PREMULT, &error); else clutter_texture_set_from_rgb_data (CLUTTER_TEXTURE (texture), @@ -880,7 +881,8 @@ clutter_x11_texture_pixmap_update_area_real (ClutterX11TexturePixmap *texture, width, height, bytes_per_line, 4, - CLUTTER_TEXTURE_RGB_FLAG_BGR, + CLUTTER_TEXTURE_RGB_FLAG_BGR | + CLUTTER_TEXTURE_RGB_FLAG_PREMULT, &error); From 888a26199918f84d6f444910f49db8cc321296f3 Mon Sep 17 00:00:00 2001 From: "Owen W. Taylor" Date: Sat, 9 May 2009 14:39:01 -0400 Subject: [PATCH 03/13] Implement premultiplication for CoglBitmap cogl-bitmap.c cogl-bitmap-pixbuf.c cogl-bitmap-fallback.c cogl-bitmap-private.h: Add _cogl_bitmap_can_premult(), _cogl_bitmap_premult() and implement a reasonably fast implementation in the "fallback" code. http://bugzilla.openedhand.com/show_bug.cgi?id=1406 Signed-off-by: Robert Bragg --- clutter/cogl/common/cogl-bitmap-fallback.c | 93 ++++++++++++++++++++++ clutter/cogl/common/cogl-bitmap-pixbuf.c | 13 +++ clutter/cogl/common/cogl-bitmap-private.h | 14 ++++ clutter/cogl/common/cogl-bitmap.c | 26 ++++-- 4 files changed, 140 insertions(+), 6 deletions(-) diff --git a/clutter/cogl/common/cogl-bitmap-fallback.c b/clutter/cogl/common/cogl-bitmap-fallback.c index e118459e1..a7e9704d9 100644 --- a/clutter/cogl/common/cogl-bitmap-fallback.c +++ b/clutter/cogl/common/cogl-bitmap-fallback.c @@ -180,6 +180,41 @@ _cogl_unpremult_alpha_first (const guchar *src, guchar *dst) dst[3] = ((((gulong) src[3] >> 0) & 0xff) * 255 ) / alpha; } +/* No division form of floor((c*a + 128)/255) (I first encountered + * this in the RENDER implementation in the X server.) Being exact + * is important for a == 255 - we want to get exactly c. + */ +#define MULT(d,c,a,t) G_STMT_START { t = c * a + 128; d = ((t >> 8) + t) >> 8; } G_STMT_END + +inline static void +_cogl_premult_alpha_last (const guchar *src, guchar *dst) +{ + guchar alpha = src[3]; + /* Using a separate temporary per component has given slightly better + * code generation with GCC in the past; it shouldn't do any worse in + * any case. + */ + guint t1, t2, t3; + MULT(dst[0], src[0], alpha, t1); + MULT(dst[1], src[1], alpha, t2); + MULT(dst[2], src[2], alpha, t3); + dst[3] = alpha; +} + +inline static void +_cogl_premult_alpha_first (const guchar *src, guchar *dst) +{ + guchar alpha = src[0]; + guint t1, t2, t3; + + dst[0] = alpha; + MULT(dst[1], src[1], alpha, t1); + MULT(dst[2], src[2], alpha, t2); + MULT(dst[3], src[3], alpha, t3); +} + +#undef MULT + gboolean _cogl_bitmap_fallback_can_convert (CoglPixelFormat src, CoglPixelFormat dst) { @@ -211,6 +246,12 @@ _cogl_bitmap_fallback_can_unpremult (CoglPixelFormat format) return ((format & COGL_UNORDERED_MASK) == COGL_PIXEL_FORMAT_32); } +gboolean +_cogl_bitmap_fallback_can_premult (CoglPixelFormat format) +{ + return ((format & COGL_UNORDERED_MASK) == COGL_PIXEL_FORMAT_32); +} + gboolean _cogl_bitmap_fallback_convert (const CoglBitmap *bmp, CoglBitmap *dst_bmp, @@ -358,6 +399,58 @@ _cogl_bitmap_fallback_unpremult (const CoglBitmap *bmp, return TRUE; } +gboolean +_cogl_bitmap_fallback_premult (const CoglBitmap *bmp, + CoglBitmap *dst_bmp) +{ + guchar *src; + guchar *dst; + gint bpp; + gint x,y; + + /* Make sure format supported for un-premultiplication */ + if (!_cogl_bitmap_fallback_can_premult (bmp->format)) + return FALSE; + + bpp = _cogl_get_format_bpp (bmp->format); + + /* Initialize destination bitmap */ + *dst_bmp = *bmp; + dst_bmp->format = (bmp->format & COGL_UNPREMULT_MASK); + + /* Allocate a new buffer to hold converted data */ + dst_bmp->data = g_malloc (sizeof(guchar) + * dst_bmp->height + * dst_bmp->rowstride); + + for (y = 0; y < bmp->height; y++) + { + src = (guchar*)bmp->data + y * bmp->rowstride; + dst = (guchar*)dst_bmp->data + y * dst_bmp->rowstride; + + if (bmp->format & COGL_AFIRST_BIT) + { + for (x = 0; x < bmp->width; x++) + { + _cogl_premult_alpha_first (src, dst); + src += bpp; + dst += bpp; + } + } + else + { + for (x = 0; x < bmp->width; x++) + { + _cogl_premult_alpha_last (src, dst); + src += bpp; + dst += bpp; + } + } + } + + return TRUE; +} + gboolean _cogl_bitmap_fallback_from_file (CoglBitmap *bmp, const gchar *filename) diff --git a/clutter/cogl/common/cogl-bitmap-pixbuf.c b/clutter/cogl/common/cogl-bitmap-pixbuf.c index f1e500534..1b75cd0ba 100644 --- a/clutter/cogl/common/cogl-bitmap-pixbuf.c +++ b/clutter/cogl/common/cogl-bitmap-pixbuf.c @@ -49,6 +49,12 @@ _cogl_bitmap_can_unpremult (CoglPixelFormat format) return FALSE; } +gboolean +_cogl_bitmap_can_premult (CoglPixelFormat format) +{ + return FALSE; +} + gboolean _cogl_bitmap_convert (const CoglBitmap *bmp, CoglBitmap *dst_bmp, @@ -64,6 +70,13 @@ _cogl_bitmap_unpremult (const CoglBitmap *bmp, return FALSE; } +gboolean +_cogl_bitmap_premult (const CoglBitmap *bmp, + CoglBitmap *dst_bmp) +{ + return FALSE; +} + #ifdef USE_QUARTZ /* lacking GdkPixbuf and other useful GError domains, define one of our own */ diff --git a/clutter/cogl/common/cogl-bitmap-private.h b/clutter/cogl/common/cogl-bitmap-private.h index f54961bd0..b6e425733 100644 --- a/clutter/cogl/common/cogl-bitmap-private.h +++ b/clutter/cogl/common/cogl-bitmap-private.h @@ -52,6 +52,12 @@ _cogl_bitmap_can_unpremult (CoglPixelFormat format); gboolean _cogl_bitmap_fallback_can_unpremult (CoglPixelFormat format); +gboolean +_cogl_bitmap_can_premult (CoglPixelFormat format); + +gboolean +_cogl_bitmap_fallback_can_premult (CoglPixelFormat format); + gboolean _cogl_bitmap_convert (const CoglBitmap *bmp, CoglBitmap *dst_bmp, @@ -69,6 +75,14 @@ gboolean _cogl_bitmap_fallback_unpremult (const CoglBitmap *bmp, CoglBitmap *dst_bmp); +gboolean +_cogl_bitmap_premult (const CoglBitmap *bmp, + CoglBitmap *dst_bmp); + +gboolean +_cogl_bitmap_fallback_premult (const CoglBitmap *bmp, + CoglBitmap *dst_bmp); + gboolean _cogl_bitmap_from_file (CoglBitmap *bmp, const gchar *filename, diff --git a/clutter/cogl/common/cogl-bitmap.c b/clutter/cogl/common/cogl-bitmap.c index f4c322abb..3dc7fd534 100644 --- a/clutter/cogl/common/cogl-bitmap.c +++ b/clutter/cogl/common/cogl-bitmap.c @@ -87,8 +87,8 @@ _cogl_bitmap_convert_and_premult (const CoglBitmap *bmp, } /* Do we need to unpremultiply */ - if ((bmp->format & COGL_PREMULT_BIT) == 0 && - (dst_format & COGL_PREMULT_BIT) > 0) + if ((bmp->format & COGL_PREMULT_BIT) > 0 && + (dst_format & COGL_PREMULT_BIT) == 0) { /* Try unpremultiplying using imaging library */ if (!_cogl_bitmap_unpremult (&new_bmp, &tmp_bmp)) @@ -112,14 +112,28 @@ _cogl_bitmap_convert_and_premult (const CoglBitmap *bmp, } /* Do we need to premultiply */ - if ((bmp->format & COGL_PREMULT_BIT) > 0 && - (dst_format & COGL_PREMULT_BIT) == 0) + if ((bmp->format & COGL_PREMULT_BIT) == 0 && + (dst_format & COGL_PREMULT_BIT) > 0) { - /* FIXME: implement premultiplication */ + /* Try premultiplying using imaging library */ + if (!_cogl_bitmap_premult (&new_bmp, &tmp_bmp)) + { + /* ... or try fallback */ + if (!_cogl_bitmap_fallback_premult (&new_bmp, &tmp_bmp)) + { + if (new_bmp_owner) + g_free (new_bmp.data); + + return FALSE; + } + } + + /* Update bitmap with new data */ if (new_bmp_owner) g_free (new_bmp.data); - return FALSE; + new_bmp = tmp_bmp; + new_bmp_owner = TRUE; } /* Output new bitmap info */ From c3448314d59a855a7dfaa47d68c3fa3531eee4ce Mon Sep 17 00:00:00 2001 From: "Owen W. Taylor" Date: Sat, 9 May 2009 14:39:01 -0400 Subject: [PATCH 04/13] Default to a blend function that expects premultiplied colors Many operations, like mixing two textures together or alpha-blending onto a destination with alpha, are done most logically if texture data is in premultiplied form. We also have many sources of premultiplied texture data, like X pixmaps, FBOs, cairo surfaces. Rather than trying to work with two different types of texture data, simplify things by always premultiplying texture data before uploading to GL. Because the default blend function is changed to accommodate this, uses of pure-color CoglMaterial need to be adapted to add premultiplication. gl/cogl-texture.c gles/cogl-texture.c: Always premultiply non-premultiplied texture data before uploading to GL. cogl-material.c cogl-material.h: Switch the default blend functions to ONE, ONE_MINUS_SRC_ALPHA so they work correctly with premultiplied data. cogl.c: Make cogl_set_source_color() premultiply the color. cogl.h.in color-material.h: Add some documentation about premultiplication and its interaction with color values. cogl-pango-render.c clutter-texture.c tests/interactive/test-cogl-offscreen.c: Use premultiplied colors. http://bugzilla.openedhand.com/show_bug.cgi?id=1406 Signed-off-by: Robert Bragg --- clutter/clutter-texture.c | 3 ++- clutter/cogl/cogl-material.h | 10 ++++++++++ clutter/cogl/cogl.h.in | 9 +++++++-- clutter/cogl/common/cogl-material.c | 2 +- clutter/cogl/common/cogl.c | 7 ++++++- clutter/cogl/gl/cogl-texture.c | 22 ++++++++++++---------- clutter/cogl/gles/cogl-texture.c | 23 +++++++++++++---------- clutter/pango/cogl-pango-display-list.c | 5 ++++- clutter/pango/cogl-pango-render.c | 18 +++++++++++------- tests/interactive/test-cogl-offscreen.c | 2 +- 10 files changed, 67 insertions(+), 34 deletions(-) diff --git a/clutter/clutter-texture.c b/clutter/clutter-texture.c index 5a31cffa4..b8fb002cf 100644 --- a/clutter/clutter-texture.c +++ b/clutter/clutter-texture.c @@ -608,7 +608,8 @@ clutter_texture_paint (ClutterActor *self) clutter_actor_get_name (self) ? clutter_actor_get_name (self) : "unknown"); - cogl_material_set_color4ub (priv->material, 0xff, 0xff, 0xff, paint_opacity); + cogl_material_set_color4ub (priv->material, + paint_opacity, paint_opacity, paint_opacity, paint_opacity); clutter_actor_get_allocation_box (self, &box); diff --git a/clutter/cogl/cogl-material.h b/clutter/cogl/cogl-material.h index 510ce8238..44a837564 100644 --- a/clutter/cogl/cogl-material.h +++ b/clutter/cogl/cogl-material.h @@ -138,6 +138,11 @@ gboolean cogl_is_material (CoglHandle handle); * * This is the basic color of the material, used when no lighting is enabled. * + * Note that if you don't add any layers to the material then the color + * will be blended unmodified with the destination; the default blend + * expects premultiplied colors: for example, use (0.5, 0.0, 0.0, 0.5) for + * semi-transparent red. See cogl_color_premultiply(). + * * The default value is (1.0, 1.0, 1.0, 1.0) * * Since 1.0 @@ -475,6 +480,11 @@ void cogl_material_set_alpha_test_function (CoglHandle material, * * * + * The default blend string is: + * "RGBA = ADD (SRC_COLOR, DST_COLOR*(1-SRC_COLOR[A]))" + * That gives normal alpha-blending when the calculated color for the material + * is in premultiplied form. + * * Returns: TRUE if the blend string was successfully parsed, and the described * blending is supported by the underlying driver/hardware. If there * was an error, it returns FALSE. diff --git a/clutter/cogl/cogl.h.in b/clutter/cogl/cogl.h.in index a468cf717..64d57fc4f 100644 --- a/clutter/cogl/cogl.h.in +++ b/clutter/cogl/cogl.h.in @@ -455,8 +455,13 @@ void cogl_set_source (CoglHandle material); * cogl_set_source_color: * @color: a #CoglColor * - * Sets the source color using normalized values for each component. - * This color will be used for any subsequent drawing operation. + * This is a convenience function for creating a solid fill source material + * from the given color. This color will be used for any subsequent drawing + * operation. + * + * The color will be premultiplied by Cogl, so the color should be + * non-premultiplied. For example: use (1.0, 0.0, 0.0, 0.5) for + * semi-transparent red. * * See also cogl_set_source_color4ub() and cogl_set_source_color4f() * if you already have the color components. diff --git a/clutter/cogl/common/cogl-material.c b/clutter/cogl/common/cogl-material.c index 831fa30f1..7e3935bff 100644 --- a/clutter/cogl/common/cogl-material.c +++ b/clutter/cogl/common/cogl-material.c @@ -109,7 +109,7 @@ cogl_material_new (void) material->blend_constant[2] = 0; material->blend_constant[3] = 0; #endif - material->blend_src_factor_rgb = GL_SRC_ALPHA; + material->blend_src_factor_rgb = GL_ONE; material->blend_dst_factor_rgb = GL_ONE_MINUS_SRC_ALPHA; material->flags |= COGL_MATERIAL_FLAG_DEFAULT_BLEND_FUNC; diff --git a/clutter/cogl/common/cogl.c b/clutter/cogl/common/cogl.c index 336303917..5857db2d3 100644 --- a/clutter/cogl/common/cogl.c +++ b/clutter/cogl/common/cogl.c @@ -250,12 +250,17 @@ cogl_get_backface_culling_enabled (void) void cogl_set_source_color (const CoglColor *color) { + CoglColor premultiplied; + _COGL_GET_CONTEXT (ctx, NO_RETVAL); /* In case cogl_set_source_texture was previously used... */ cogl_material_remove_layer (ctx->default_material, 0); - cogl_material_set_color (ctx->default_material, color); + premultiplied = *color; + cogl_color_premultiply (&premultiplied); + cogl_material_set_color (ctx->default_material, &premultiplied); + cogl_set_source (ctx->default_material); } diff --git a/clutter/cogl/gl/cogl-texture.c b/clutter/cogl/gl/cogl-texture.c index 3af78f9a0..2ee50617c 100644 --- a/clutter/cogl/gl/cogl-texture.c +++ b/clutter/cogl/gl/cogl-texture.c @@ -1085,18 +1085,20 @@ _cogl_pixel_format_to_gl (CoglPixelFormat format, GLenum glformat = 0; GLenum gltype = 0; - /* No premultiplied formats accepted by GL - * (FIXME: latest hardware?) */ - - if (format & COGL_PREMULT_BIT) - format = (format & COGL_UNPREMULT_MASK); - - /* Everything else accepted - * (FIXME: check YUV support) */ - required_format = format; + /* If PREMULT_BIT isn't specified, that means that we premultiply + * textures with alpha before uploading to GL; once we are in GL land, + * everything is premultiplied. + * + * Everything else accepted (FIXME: check YUV support) + */ + if ((format & COGL_A_BIT) != 0 && + format != COGL_PIXEL_FORMAT_A_8) + required_format = format | COGL_PREMULT_BIT; + else + required_format = format; /* Find GL equivalents */ - switch (format) + switch (format & COGL_UNPREMULT_MASK) { case COGL_PIXEL_FORMAT_A_8: glintformat = GL_ALPHA; diff --git a/clutter/cogl/gles/cogl-texture.c b/clutter/cogl/gles/cogl-texture.c index ae3e437b8..f9b6e0b78 100644 --- a/clutter/cogl/gles/cogl-texture.c +++ b/clutter/cogl/gles/cogl-texture.c @@ -1184,18 +1184,20 @@ _cogl_pixel_format_to_gl (CoglPixelFormat format, GLenum glformat = 0; GLenum gltype = 0; - /* No premultiplied formats accepted by GL - * (FIXME: latest hardware?) */ - - if (format & COGL_PREMULT_BIT) - format = (format & COGL_UNPREMULT_MASK); - - /* Everything else accepted - * (FIXME: check YUV support) */ - required_format = format; + /* If PREMULT_BIT isn't specified, that means that we premultiply + * textures with alpha before uploading to GL; once we are in GL land, + * everything is premultiplied. + * + * Everything else accepted (FIXME: check YUV support) + */ + if ((format & COGL_A_BIT) != 0 && + format != COGL_PIXEL_FORMAT_A_8) + required_format = format | COGL_PREMULT_BIT; + else + required_format = format; /* Find GL equivalents */ - switch (format) + switch (format & COGL_UNPREMULT_MASK) { case COGL_PIXEL_FORMAT_A_8: glintformat = GL_ALPHA; @@ -1226,6 +1228,7 @@ _cogl_pixel_format_to_gl (CoglPixelFormat format, glformat = GL_RGBA; gltype = GL_UNSIGNED_BYTE; required_format = COGL_PIXEL_FORMAT_RGBA_8888; + required_format |= (format & COGL_PREMULT_BIT); break; /* The following three types of channel ordering diff --git a/clutter/pango/cogl-pango-display-list.c b/clutter/pango/cogl-pango-display-list.c index 1e16a76f3..6a3b29c60 100644 --- a/clutter/pango/cogl-pango-display-list.c +++ b/clutter/pango/cogl-pango-display-list.c @@ -239,8 +239,10 @@ _cogl_pango_display_list_render_texture (CoglHandle material, const CoglColor *color, CoglPangoDisplayListNode *node) { + CoglColor premult_color = *color; cogl_material_set_layer (material, 0, node->d.texture.texture); - cogl_material_set_color (material, color); + cogl_color_premultiply (&premult_color); + cogl_material_set_color (material, &premult_color); cogl_set_source (material); if (node->d.texture.vertex_buffer == COGL_INVALID_HANDLE) @@ -311,6 +313,7 @@ _cogl_pango_display_list_render (CoglPangoDisplayList *dl, cogl_color_get_alpha_byte (color)); else draw_color = *color; + cogl_color_premultiply (&draw_color); switch (node->type) { diff --git a/clutter/pango/cogl-pango-render.c b/clutter/pango/cogl-pango-render.c index ffa224e65..c98ddca1f 100644 --- a/clutter/pango/cogl-pango-render.c +++ b/clutter/pango/cogl-pango-render.c @@ -124,16 +124,20 @@ cogl_pango_renderer_init (CoglPangoRenderer *priv) /* The default combine mode of materials is to modulate (A x B) the texture * RGBA channels with the RGBA channels of the previous layer (which in our - * case is just the solid font color) + * case is just the font color) * - * Since our glyph cache textures are component alpha textures, and so the - * RGB channels are defined as (0, 0, 0) we don't want to modulate the RGB - * channels, instead we want to simply replace them with our solid font - * color... + * Since the RGB for an alpha texture is defined as 0, this gives us: + * + * result.rgb = color.rgb * 0 + * result.a = color.a * texture.a + * + * What we want is premultiplied rgba values: + * + * result.rgba = color.rgb * texture.a + * result.a = color.a * texture.a */ cogl_material_set_layer_combine (priv->glyph_material, 0, /* layer */ - "RGB = REPLACE (PREVIOUS)" - "A = MODULATE (PREVIOUS, TEXTURE)", + "RGBA = MODULATE (PREVIOUS, TEXTURE[A])", NULL); priv->solid_material = cogl_material_new (); diff --git a/tests/interactive/test-cogl-offscreen.c b/tests/interactive/test-cogl-offscreen.c index 8b9d8e8f7..2aa5e79f6 100644 --- a/tests/interactive/test-cogl-offscreen.c +++ b/tests/interactive/test-cogl-offscreen.c @@ -106,7 +106,7 @@ test_coglbox_paint(ClutterActor *self) cogl_set_draw_buffer (COGL_WINDOW_BUFFER, 0); material = cogl_material_new (); - cogl_material_set_color4ub (material, 0xff, 0xff, 0xff, 0x88); + cogl_material_set_color4ub (material, 0x88, 0x88, 0x88, 0x88); cogl_material_set_layer (material, 0, priv->texture_id); cogl_set_source (material); cogl_rectangle_with_texture_coords (100, 100, From 1e5b5d10c5608efa411afcb8e032c5e956325249 Mon Sep 17 00:00:00 2001 From: "Owen W. Taylor" Date: Thu, 4 Jun 2009 13:45:43 -0400 Subject: [PATCH 05/13] Don't unpremultiply Cairo data Instead of unpremultiplying the Cairo data, pass it directly to Cogl in premultiplied form; we now *prefer* premultiplied data. http://bugzilla.openedhand.com/show_bug.cgi?id=1406 Signed-off-by: Robert Bragg --- clutter/clutter-cairo-texture.c | 58 +++++---------------------------- 1 file changed, 9 insertions(+), 49 deletions(-) diff --git a/clutter/clutter-cairo-texture.c b/clutter/clutter-cairo-texture.c index 82c614cb5..f3884c6bb 100644 --- a/clutter/clutter-cairo-texture.c +++ b/clutter/clutter-cairo-texture.c @@ -292,14 +292,13 @@ clutter_cairo_texture_surface_resize_internal (ClutterCairoTexture *cairo) cairo, clutter_cairo_texture_surface_destroy); - /* The texture data will be all zeroes so we can use it to create a - * blank Cogl texture even though its in a different format + /* Create a blank Cogl texture */ clutter_texture_set_from_rgb_data (CLUTTER_TEXTURE (cairo), priv->cr_surface_data, TRUE, priv->width, priv->height, priv->rowstride, - 4, 0, NULL); + 4, CLUTTER_TEXTURE_RGB_FLAG_PREMULT, NULL); } static void @@ -451,9 +450,7 @@ clutter_cairo_texture_context_destroy (void *data) gint cairo_width, cairo_height, cairo_rowstride; gint surface_width, surface_height; - guchar *pixbuf_data, *dst, *cairo_data; - guint *src, pixbuf_rowstride; - gint x, y; + guchar *cairo_data; priv = CLUTTER_CAIRO_TEXTURE_GET_PRIVATE (cairo); @@ -473,56 +470,19 @@ clutter_cairo_texture_context_destroy (void *data) } cairo_rowstride = priv->rowstride; - cairo_data = priv->cr_surface_data; - pixbuf_data = g_malloc (cairo_width * cairo_height * 4); - pixbuf_rowstride = cairo_width * 4; - - /* BAH BAH BAH ! un-pre-multiply alpha... - * - * FIXME: Need to figure out if GL has a premult texture - * format, or we need to change the order of the - * paint sequence in Clutter. or go back to battling - * glitz (ugh). - * - * in theory, this could be moved to a shader, but apparently - * the performance gain is not really worth it. - */ - for (y = 0; y < cairo_height; y++) - { - src = (unsigned int *) (cairo_data - + ((y + ctxt->rect.y) * cairo_rowstride) - + (ctxt->rect.x * 4)); - dst = pixbuf_data + y * pixbuf_rowstride; - - for (x = 0; x < cairo_width; x++) - { - guchar alpha = (*src >> 24) & 0xff; - - if (alpha == 0) - dst[0] = dst[1] = dst[2] = dst[3] = alpha; - else - { - dst[0] = (((*src >> 16) & 0xff) * 255 ) / alpha; - dst[1] = (((*src >> 8) & 0xff) * 255 ) / alpha; - dst[2] = (((*src >> 0) & 0xff) * 255 ) / alpha; - dst[3] = alpha; - } - - dst += 4; - src++; - } - } + cairo_data = (priv->cr_surface_data + + ctxt->rect.y * cairo_rowstride + + ctxt->rect.x * 4); clutter_texture_set_area_from_rgb_data (CLUTTER_TEXTURE (cairo), - pixbuf_data, + cairo_data, TRUE, ctxt->rect.x, ctxt->rect.y, cairo_width, cairo_height, - pixbuf_rowstride, - 4, 0, NULL); + cairo_rowstride, + 4, CLUTTER_TEXTURE_RGB_FLAG_PREMULT, NULL); - g_free (pixbuf_data); g_free (ctxt); if (CLUTTER_ACTOR_IS_VISIBLE (cairo)) From 5e18cee77ab0167a000bb3af8a7c047357141177 Mon Sep 17 00:00:00 2001 From: "Owen W. Taylor" Date: Sat, 9 May 2009 14:39:01 -0400 Subject: [PATCH 06/13] Avoid unnecesary unpremultiplication when saving to local data Now that we typically have premultiplied data stored in Cogl textures, when fetching a texture into local data for temporary storage, use a premultiplied format to avoid an unpremultiply/ premultiply roundtrip. http://bugzilla.openedhand.com/show_bug.cgi?id=1406 Signed-off-by: Robert Bragg --- clutter/clutter-texture.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clutter/clutter-texture.c b/clutter/clutter-texture.c index b8fb002cf..c5a2d7b81 100644 --- a/clutter/clutter-texture.c +++ b/clutter/clutter-texture.c @@ -1225,7 +1225,7 @@ clutter_texture_save_to_local_data (ClutterTexture *texture) if (cogl_texture_get_data (cogl_texture, priv->local_data_has_alpha - ? COGL_PIXEL_FORMAT_RGBA_8888 + ? COGL_PIXEL_FORMAT_RGBA_8888_PRE : COGL_PIXEL_FORMAT_RGB_888, priv->local_data_rowstride, priv->local_data) == 0) @@ -1252,7 +1252,7 @@ clutter_texture_load_from_local_data (ClutterTexture *texture) priv->local_data_height, priv->local_data_rowstride, priv->local_data_has_alpha ? 4: 3, - 0, NULL); + CLUTTER_TEXTURE_RGB_FLAG_PREMULT, NULL); g_free (priv->local_data); priv->local_data = NULL; From cb959ef457a670a5e827963d4a85f6c732993295 Mon Sep 17 00:00:00 2001 From: Robert Bragg Date: Sun, 7 Jun 2009 20:29:13 +0100 Subject: [PATCH 07/13] [cogl-bitmap] Fix minor copy and paste error in _cogl_bitmap_fallback_premult The returned bitmap format should include the COGL_PREMULT_BIT flag not have it explicitly removed as for _cogl_bitmap_fallback_unpremult. --- clutter/cogl/common/cogl-bitmap-fallback.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clutter/cogl/common/cogl-bitmap-fallback.c b/clutter/cogl/common/cogl-bitmap-fallback.c index a7e9704d9..e750a7ccf 100644 --- a/clutter/cogl/common/cogl-bitmap-fallback.c +++ b/clutter/cogl/common/cogl-bitmap-fallback.c @@ -416,7 +416,7 @@ _cogl_bitmap_fallback_premult (const CoglBitmap *bmp, /* Initialize destination bitmap */ *dst_bmp = *bmp; - dst_bmp->format = (bmp->format & COGL_UNPREMULT_MASK); + dst_bmp->format |= COGL_PREMULT_BIT; /* Allocate a new buffer to hold converted data */ dst_bmp->data = g_malloc (sizeof(guchar) From 9d3aa576045ce0aea51ef767b74596bb4582570f Mon Sep 17 00:00:00 2001 From: Robert Bragg Date: Sun, 7 Jun 2009 15:58:56 +0100 Subject: [PATCH 08/13] [bitmap] Fixes _cogl_bitmap_fallback_unpremult The _cogl_unpremult_alpha_{first,last} functions which _cogl_bitmap_fallback_unpremult depends on were incorrectly casting each of the byte components of a texel to a gulong and performing shifts as if it were dealing with the whole texel. It now just uses array indexing to access the byte components without needing to cast or manually shift any bits around. Even though we used to depend on unpremult whenever we used a ClutterCairoTexture, clutter_cairo_texture_context_destroy had it's own unpremult code which worked which is why this bug wouldn't have been noticed before. --- clutter/cogl/common/cogl-bitmap-fallback.c | 60 +++++++++++----------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/clutter/cogl/common/cogl-bitmap-fallback.c b/clutter/cogl/common/cogl-bitmap-fallback.c index e750a7ccf..ddff6ea40 100644 --- a/clutter/cogl/common/cogl-bitmap-fallback.c +++ b/clutter/cogl/common/cogl-bitmap-fallback.c @@ -163,9 +163,9 @@ _cogl_unpremult_alpha_last (const guchar *src, guchar *dst) { guchar alpha = src[3]; - dst[0] = ((((gulong) src[0] >> 16) & 0xff) * 255 ) / alpha; - dst[1] = ((((gulong) src[1] >> 8) & 0xff) * 255 ) / alpha; - dst[2] = ((((gulong) src[2] >> 0) & 0xff) * 255 ) / alpha; + dst[0] = (src[0] * 255) / alpha; + dst[1] = (src[1] * 255) / alpha; + dst[2] = (src[2] * 255) / alpha; dst[3] = alpha; } @@ -175,9 +175,9 @@ _cogl_unpremult_alpha_first (const guchar *src, guchar *dst) guchar alpha = src[0]; dst[0] = alpha; - dst[1] = ((((gulong) src[1] >> 16) & 0xff) * 255 ) / alpha; - dst[2] = ((((gulong) src[2] >> 8) & 0xff) * 255 ) / alpha; - dst[3] = ((((gulong) src[3] >> 0) & 0xff) * 255 ) / alpha; + dst[1] = (src[1] * 255) / alpha; + dst[2] = (src[2] * 255) / alpha; + dst[3] = (src[3] * 255) / alpha; } /* No division form of floor((c*a + 128)/255) (I first encountered @@ -365,35 +365,35 @@ _cogl_bitmap_fallback_unpremult (const CoglBitmap *bmp, * dst_bmp->height * dst_bmp->rowstride); - /* FIXME: Optimize */ for (y = 0; y < bmp->height; y++) { src = (guchar*)bmp->data + y * bmp->rowstride; dst = (guchar*)dst_bmp->data + y * dst_bmp->rowstride; - for (x = 0; x < bmp->width; x++) - { - /* FIXME: Would be nice to at least remove this inner - * branching, but not sure it can be done without - * rewriting of the whole loop */ - if (bmp->format & COGL_AFIRST_BIT) - { - if (src[0] == 0) - _cogl_unpremult_alpha_0 (src, dst); - else - _cogl_unpremult_alpha_first (src, dst); - } - else - { - if (src[3] == 0) - _cogl_unpremult_alpha_0 (src, dst); - else - _cogl_unpremult_alpha_last (src, dst); - } - - src += bpp; - dst += bpp; - } + if (bmp->format & COGL_AFIRST_BIT) + { + for (x = 0; x < bmp->width; x++) + { + if (src[0] == 0) + _cogl_unpremult_alpha_0 (src, dst); + else + _cogl_unpremult_alpha_first (src, dst); + src += bpp; + dst += bpp; + } + } + else + { + for (x = 0; x < bmp->width; x++) + { + if (src[0] == 0) + _cogl_unpremult_alpha_0 (src, dst); + else + _cogl_unpremult_alpha_last (src, dst); + src += bpp; + dst += bpp; + } + } } return TRUE; From a9b011f3edc9730f60280e38bf2164f1c2dd95a9 Mon Sep 17 00:00:00 2001 From: Robert Bragg Date: Sat, 6 Jun 2009 21:45:05 +0100 Subject: [PATCH 09/13] [premultiplication] Be more conservative with what data gets premultiplied We don't want to force texture data to be premultipled if the user explicitly specifies a non premultiplied internal_format such as COGL_PIXEL_FORMAT_RGBA_8888. So now Cogl will only automatically premultiply data when COGL_PIXEL_FORMAT_ANY is given for the internal_format, or a premultiplied internal format such as COGL_PIXEL_FORMAT_RGBA_8888_PRE is requested but non-premultiplied source data is given. This approach is consistent with OpenVG image formats which have already influenced Cogl's pixel format semantics. --- clutter/cogl/gl/cogl-texture.c | 24 +++++++++++------------- clutter/cogl/gles/cogl-texture.c | 24 +++++++++++------------- 2 files changed, 22 insertions(+), 26 deletions(-) diff --git a/clutter/cogl/gl/cogl-texture.c b/clutter/cogl/gl/cogl-texture.c index 2ee50617c..877107d3c 100644 --- a/clutter/cogl/gl/cogl-texture.c +++ b/clutter/cogl/gl/cogl-texture.c @@ -1085,17 +1085,9 @@ _cogl_pixel_format_to_gl (CoglPixelFormat format, GLenum glformat = 0; GLenum gltype = 0; - /* If PREMULT_BIT isn't specified, that means that we premultiply - * textures with alpha before uploading to GL; once we are in GL land, - * everything is premultiplied. - * - * Everything else accepted (FIXME: check YUV support) - */ - if ((format & COGL_A_BIT) != 0 && - format != COGL_PIXEL_FORMAT_A_8) - required_format = format | COGL_PREMULT_BIT; - else - required_format = format; + /* FIXME: check YUV support */ + + required_format = format; /* Find GL equivalents */ switch (format & COGL_UNPREMULT_MASK) @@ -1197,9 +1189,15 @@ _cogl_texture_bitmap_prepare (CoglTexture *tex, CoglPixelFormat new_data_format; gboolean success; - /* Was there any internal conversion requested? */ + /* Was there any internal conversion requested? + * By default Cogl will use a premultiplied internal format. Later we will + * add control over this. */ if (internal_format == COGL_PIXEL_FORMAT_ANY) - internal_format = tex->bitmap.format; + { + if ((tex->bitmap.format & COGL_A_BIT) && + tex->bitmap.format != COGL_PIXEL_FORMAT_A_8) + internal_format = tex->bitmap.format | COGL_PREMULT_BIT; + } /* Find closest format accepted by GL */ new_data_format = _cogl_pixel_format_to_gl (internal_format, diff --git a/clutter/cogl/gles/cogl-texture.c b/clutter/cogl/gles/cogl-texture.c index f9b6e0b78..ab1653e07 100644 --- a/clutter/cogl/gles/cogl-texture.c +++ b/clutter/cogl/gles/cogl-texture.c @@ -1184,17 +1184,9 @@ _cogl_pixel_format_to_gl (CoglPixelFormat format, GLenum glformat = 0; GLenum gltype = 0; - /* If PREMULT_BIT isn't specified, that means that we premultiply - * textures with alpha before uploading to GL; once we are in GL land, - * everything is premultiplied. - * - * Everything else accepted (FIXME: check YUV support) - */ - if ((format & COGL_A_BIT) != 0 && - format != COGL_PIXEL_FORMAT_A_8) - required_format = format | COGL_PREMULT_BIT; - else - required_format = format; + /* FIXME: check YUV support */ + + required_format = format; /* Find GL equivalents */ switch (format & COGL_UNPREMULT_MASK) @@ -1273,9 +1265,15 @@ _cogl_texture_bitmap_prepare (CoglTexture *tex, CoglPixelFormat new_data_format; gboolean success; - /* Was there any internal conversion requested? */ + /* Was there any internal conversion requested? + * By default Cogl will use a premultiplied internal format. Later we will + * add control over this. */ if (internal_format == COGL_PIXEL_FORMAT_ANY) - internal_format = tex->bitmap.format; + { + if ((tex->bitmap.format & COGL_A_BIT) && + tex->bitmap.format != COGL_PIXEL_FORMAT_A_8) + internal_format = tex->bitmap.format | COGL_PREMULT_BIT; + } /* Find closest format accepted by GL */ new_data_format = _cogl_pixel_format_to_gl (internal_format, From 80aceda9bb3fde41827cb35bec98ba523a095b0f Mon Sep 17 00:00:00 2001 From: Robert Bragg Date: Sat, 6 Jun 2009 21:55:05 +0100 Subject: [PATCH 10/13] [test-blend-strings] Explicitly request RGBA_888 tex format for test textures This test assumes that the textures will be stored internally with exactly the color given so that specific texture combining arithmetic can be tested. Using COGL_PIXEL_FORMAT_ANY allows Cogl to internally premultiply the textures, so we have to explicitly request an unmultiplied format. --- tests/conform/test-blend-strings.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/conform/test-blend-strings.c b/tests/conform/test-blend-strings.c index 12ada130f..14b7fbcc0 100644 --- a/tests/conform/test-blend-strings.c +++ b/tests/conform/test-blend-strings.c @@ -172,11 +172,13 @@ make_texture (guint32 color) *(--p) = r; } + /* Note: we don't use COGL_PIXEL_FORMAT_ANY for the internal format here + * since we don't want to allow Cogl to premultiply our data. */ tex = cogl_texture_new_from_data (QUAD_WIDTH, QUAD_WIDTH, COGL_TEXTURE_NONE, COGL_PIXEL_FORMAT_RGBA_8888, - COGL_PIXEL_FORMAT_ANY, + COGL_PIXEL_FORMAT_RGBA_8888, QUAD_WIDTH * 4, tex_data); From 70636b4815283136ca5d878b6e39e12337c4f9ed Mon Sep 17 00:00:00 2001 From: Robert Bragg Date: Sun, 7 Jun 2009 11:54:05 +0100 Subject: [PATCH 11/13] [fog] Document that fogging only works with opaque or unmultipled colors The fixed function fogging provided by OpenGL only works with unmultiplied colors (or if the color has an alpha of 1.0) so since we now premultiply textures and colors by default a note to this affect has been added to clutter_stage_set_fog and cogl_set_fog. test-depth.c no longer uses clutter_stage_set_fog for this reason. In the future when we can depend on fragment shaders we should also be able to support fogging of premultiplied primitives. --- clutter/clutter-stage.c | 10 ++++++++++ clutter/cogl/cogl.h.in | 23 ++++++++++++++++------- tests/interactive/test-depth.c | 3 --- 3 files changed, 26 insertions(+), 10 deletions(-) diff --git a/clutter/clutter-stage.c b/clutter/clutter-stage.c index 63f33d72a..6598b9c65 100644 --- a/clutter/clutter-stage.c +++ b/clutter/clutter-stage.c @@ -1668,6 +1668,16 @@ clutter_stage_set_use_fog (ClutterStage *stage, * } * ]| * + * Note: The fogging functions only work correctly when the visible actors use + * unmultiplied alpha colors. By default Cogl will premultiply textures + * and cogl_set_source_color will premultiply colors, so unless you + * explicitly load your textures requesting an unmultiplied + * internal_format and use cogl_material_set_color you can only use + * fogging with fully opaque actors. + * + * We can look to improve this in the future when we can depend on + * fragment shaders. + * * Since: 0.6 */ void diff --git a/clutter/cogl/cogl.h.in b/clutter/cogl/cogl.h.in index 64d57fc4f..26bcf25c2 100644 --- a/clutter/cogl/cogl.h.in +++ b/clutter/cogl/cogl.h.in @@ -386,13 +386,22 @@ gboolean cogl_get_backface_culling_enabled (void); * @z_near: Position along z-axis where no fogging should be applied * @z_far: Position along z-axes where full fogging should be applied * - * Enables fogging. Fogging causes vertices that are further away from - * the eye to be rendered with a different color. The color is determined - * according to the chosen fog mode; at it's simplest the color is - * linearly interpolated so that vertices at @z_near are drawn fully - * with their original color and vertices at @z_far are drawn fully - * with @fog_color. Fogging will remain enabled until you call - * cogl_disable_fog(). + * Enables fogging. Fogging causes vertices that are further away from the eye + * to be rendered with a different color. The color is determined according to + * the chosen fog mode; at it's simplest the color is linearly interpolated so + * that vertices at @z_near are drawn fully with their original color and + * vertices at @z_far are drawn fully with @fog_color. Fogging will remain + * enabled until you call cogl_disable_fog(). + * + * Note: The fogging functions only work correctly when primitives use + * unmultiplied alpha colors. By default Cogl will premultiply textures + * and cogl_set_source_color will premultiply colors, so unless you + * explicitly load your textures requesting an unmultiplied + * internal_format and use cogl_material_set_color you can only use + * fogging with fully opaque primitives. + * + * We can look to improve this in the future when we can depend on + * fragment shaders. */ void cogl_set_fog (const CoglColor *fog_color, CoglFogMode mode, diff --git a/tests/interactive/test-depth.c b/tests/interactive/test-depth.c index 7f5d1edd0..c06337c58 100644 --- a/tests/interactive/test-depth.c +++ b/tests/interactive/test-depth.c @@ -119,15 +119,12 @@ test_depth_main (int argc, char *argv[]) ClutterActor *group, *hand, *label, *rect, *janus, *box; ClutterColor stage_color = { 0xcc, 0xcc, 0xcc, 0xff }; ClutterColor rect_color = { 0, 0, 0, 0x88 }; - ClutterFog stage_fog = { 10.0, -50.0 }; GError *error; clutter_init (&argc, &argv); stage = clutter_stage_get_default (); clutter_stage_set_color (CLUTTER_STAGE (stage), &stage_color); - clutter_stage_set_fog (CLUTTER_STAGE (stage), &stage_fog); - clutter_stage_set_use_fog (CLUTTER_STAGE (stage), TRUE); g_signal_connect (stage, "button-press-event", G_CALLBACK (clutter_main_quit), From 41579eb3c9d7243d41754e38d30f1d8ad20958fd Mon Sep 17 00:00:00 2001 From: Robert Bragg Date: Sun, 7 Jun 2009 16:31:12 +0100 Subject: [PATCH 12/13] [test-premult] Adds a unit test for texture upload premultiplication semantics cogl_texture_new_from_data lets you specify a source format for the users given data, and an internal format which the user wants the GPU to see. This unit test verifies that the users data is premultiplied, un-premultiplied or left alone for a number of (source format, internal format) pairs. cogl_texture_set_region allows specifying a source format, and the internal format is determined from the texture being updated. As above we test a number of format pairs and check Cogl is converting data correctly. The test verifies that if the user allows COGL_FORMAT_ANY for the internal_format then by default Cogl will choose a premultipled format for RGBA textures. Note: Currently this only tests cogl_texture_new_from_data and cogl_texture_set_region, we should also test cogl_texture_new_from_file, cogl_texture_new_from_bitmap and cogl_texture_new_from_foreign. --- tests/conform/Makefile.am | 1 + tests/conform/test-conform-main.c | 1 + tests/conform/test-premult.c | 388 ++++++++++++++++++++++++++++++ 3 files changed, 390 insertions(+) create mode 100644 tests/conform/test-premult.c diff --git a/tests/conform/Makefile.am b/tests/conform/Makefile.am index ecbcb1f8f..e44680238 100644 --- a/tests/conform/Makefile.am +++ b/tests/conform/Makefile.am @@ -29,6 +29,7 @@ test_conformance_SOURCES = \ test-blend-strings.c \ test-color.c \ test-clutter-units.c \ + test-premult.c \ $(NULL) # For convenience, this provides a way to easily run individual unit tests: diff --git a/tests/conform/test-conform-main.c b/tests/conform/test-conform-main.c index b2aa07077..da23d5f15 100644 --- a/tests/conform/test-conform-main.c +++ b/tests/conform/test-conform-main.c @@ -129,6 +129,7 @@ main (int argc, char **argv) TEST_CONFORM_SIMPLE ("/texture", test_backface_culling); TEST_CONFORM_SIMPLE ("/texture", test_npot_texture); + TEST_CONFORM_SIMPLE ("/texture", test_premult); TEST_CONFORM_SIMPLE ("/path", test_path); diff --git a/tests/conform/test-premult.c b/tests/conform/test-premult.c new file mode 100644 index 000000000..93b28784a --- /dev/null +++ b/tests/conform/test-premult.c @@ -0,0 +1,388 @@ + +#include +#include +#include + +#include "test-conform-common.h" + +static const ClutterColor stage_color = { 0x0, 0x0, 0x0, 0xff }; + +#define QUAD_WIDTH 20 + +#define RED 0 +#define GREEN 1 +#define BLUE 2 +#define ALPHA 3 + +#define MASK_RED(COLOR) ((COLOR & 0xff000000) >> 24); +#define MASK_GREEN(COLOR) ((COLOR & 0xff0000) >> 16); +#define MASK_BLUE(COLOR) ((COLOR & 0xff00) >> 8); +#define MASK_ALPHA(COLOR) (COLOR & 0xff); + +#define SKIP_FRAMES 2 + +typedef struct _TestState +{ + guint frame; + ClutterGeometry stage_geom; + CoglHandle passthrough_material; +} TestState; + + +static void +check_pixel (GLubyte *pixel, guint32 color) +{ + guint8 r = MASK_RED (color); + guint8 g = MASK_GREEN (color); + guint8 b = MASK_BLUE (color); + guint8 a = MASK_ALPHA (color); + + if (g_test_verbose ()) + g_print (" expected = %x, %x, %x, %x\n", + r, g, b, a); + /* FIXME - allow for hardware in-precision */ + g_assert (pixel[RED] == r); + g_assert (pixel[GREEN] == g); + g_assert (pixel[BLUE] == b); + + /* FIXME + * We ignore the alpha, since we don't know if our render target is + * RGB or RGBA */ + /* g_assert (pixel[ALPHA] == a); */ +} + +static guchar * +gen_tex_data (guint32 color) +{ + guchar *tex_data, *p; + guint8 r = MASK_RED (color); + guint8 g = MASK_GREEN (color); + guint8 b = MASK_BLUE (color); + guint8 a = MASK_ALPHA (color); + + tex_data = g_malloc (QUAD_WIDTH * QUAD_WIDTH * 4); + + for (p = tex_data + QUAD_WIDTH * QUAD_WIDTH * 4; p > tex_data;) + { + *(--p) = a; + *(--p) = b; + *(--p) = g; + *(--p) = r; + } + + return tex_data; +} + +static CoglHandle +make_texture (guint32 color, + CoglPixelFormat src_format, + CoglPixelFormat internal_format) +{ + CoglHandle tex; + guchar *tex_data = gen_tex_data (color); + + tex = cogl_texture_new_from_data (QUAD_WIDTH, + QUAD_WIDTH, + COGL_TEXTURE_NONE, + src_format, + internal_format, + QUAD_WIDTH * 4, + tex_data); + + g_free (tex_data); + + return tex; +} + +static void +check_texture (TestState *state, + int x, + int y, + CoglHandle tex, + guint32 expected_result) +{ + GLubyte pixel[4]; + GLint y_off; + GLint x_off; + + cogl_material_set_layer (state->passthrough_material, 0, tex); + + cogl_set_source (state->passthrough_material); + cogl_rectangle (x * QUAD_WIDTH, + y * QUAD_WIDTH, + x * QUAD_WIDTH + QUAD_WIDTH, + y * QUAD_WIDTH + QUAD_WIDTH); + + /* See what we got... */ + + /* NB: glReadPixels is done in GL screen space so y = 0 is at the bottom */ + y_off = state->stage_geom.height - y * QUAD_WIDTH - (QUAD_WIDTH / 2); + x_off = x * QUAD_WIDTH + (QUAD_WIDTH / 2); + + /* XXX: + * We haven't always had good luck with GL drivers implementing glReadPixels + * reliably and skipping the first two frames improves our chances... */ + if (state->frame <= SKIP_FRAMES) + return; + + glReadPixels (x_off, y_off, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &pixel); + if (g_test_verbose ()) + { + g_print ("check texture (%d, %d):\n", x, y); + g_print (" result = %02x, %02x, %02x, %02x\n", + pixel[RED], pixel[GREEN], pixel[BLUE], pixel[ALPHA]); + } + + check_pixel (pixel, expected_result); +} + +static void +on_paint (ClutterActor *actor, TestState *state) +{ + int frame_num; + CoglHandle tex; + guchar *tex_data; + + /* If the user explicitly specifies an unmultiplied internal format then + * Cogl shouldn't automatically premultiply the given texture data... */ + if (state->frame > SKIP_FRAMES && g_test_verbose ()) + g_print ("make_texture (0xff00ff80, " + "src = RGBA_8888, internal = RGBA_8888)\n"); + tex = make_texture (0xff00ff80, + COGL_PIXEL_FORMAT_RGBA_8888, /* src format */ + COGL_PIXEL_FORMAT_RGBA_8888); /* internal format */ + check_texture (state, 0, 0, /* position */ + tex, + 0xff00ff80); /* expected */ + + /* If the user explicitly requests a premultiplied internal format and + * gives unmultiplied src data then Cogl should always premultiply that + * for us */ + if (state->frame > SKIP_FRAMES && g_test_verbose ()) + g_print ("make_texture (0xff00ff80, " + "src = RGBA_8888, internal = RGBA_8888_PRE)\n"); + tex = make_texture (0xff00ff80, + COGL_PIXEL_FORMAT_RGBA_8888, /* src format */ + COGL_PIXEL_FORMAT_RGBA_8888_PRE); /* internal format */ + check_texture (state, 1, 0, /* position */ + tex, + 0x80008080); /* expected */ + + /* If the user gives COGL_PIXEL_FORMAT_ANY for the internal format then + * by default Cogl should premultiply the given texture data... + * (In the future there will be additional Cogl API to control this + * behaviour) */ + if (state->frame > SKIP_FRAMES && g_test_verbose ()) + g_print ("make_texture (0xff00ff80, " + "src = RGBA_8888, internal = ANY)\n"); + tex = make_texture (0xff00ff80, + COGL_PIXEL_FORMAT_RGBA_8888, /* src format */ + COGL_PIXEL_FORMAT_ANY); /* internal format */ + check_texture (state, 2, 0, /* position */ + tex, + 0x80008080); /* expected */ + + /* If the user requests a premultiplied internal texture format and supplies + * premultiplied source data, Cogl should never modify that source data... + */ + if (state->frame > SKIP_FRAMES && g_test_verbose ()) + g_print ("make_texture (0x80008080, " + "src = RGBA_8888_PRE, " + "internal = RGBA_8888_PRE)\n"); + tex = make_texture (0x80008080, + COGL_PIXEL_FORMAT_RGBA_8888_PRE, /* src format */ + COGL_PIXEL_FORMAT_RGBA_8888_PRE); /* internal format */ + check_texture (state, 3, 0, /* position */ + tex, + 0x80008080); /* expected */ + + /* If the user requests an unmultiplied internal texture format, but + * supplies premultiplied source data, then Cogl should always + * un-premultiply the source data... */ + if (state->frame > SKIP_FRAMES && g_test_verbose ()) + g_print ("make_texture (0x80008080, " + "src = RGBA_8888_PRE, internal = RGBA_8888)\n"); + tex = make_texture (0x80008080, + COGL_PIXEL_FORMAT_RGBA_8888_PRE, /* src format */ + COGL_PIXEL_FORMAT_RGBA_8888); /* internal format */ + check_texture (state, 4, 0, /* position */ + tex, + 0xff00ff80); /* expected */ + + /* If the user allows any internal texture format and provides premultipled + * source data then by default Cogl shouldn't modify the source data... + * (In the future there will be additional Cogl API to control this + * behaviour) */ + if (state->frame > SKIP_FRAMES && g_test_verbose ()) + g_print ("make_texture (0x80008080, " + "src = RGBA_8888_PRE, internal = ANY)\n"); + tex = make_texture (0x80008080, + COGL_PIXEL_FORMAT_RGBA_8888_PRE, /* src format */ + COGL_PIXEL_FORMAT_ANY); /* internal format */ + check_texture (state, 5, 0, /* position */ + tex, + 0x80008080); /* expected */ + + /* + * Test cogl_texture_set_region() .... + */ + + if (state->frame > SKIP_FRAMES && g_test_verbose ()) + g_print ("make_texture (0xDEADBEEF, " + "src = RGBA_8888, internal = RGBA_8888)\n"); + tex = make_texture (0xDEADBEEF, + COGL_PIXEL_FORMAT_RGBA_8888, /* src format */ + COGL_PIXEL_FORMAT_RGBA_8888); /* internal format */ + if (state->frame > SKIP_FRAMES && g_test_verbose ()) + g_print ("set_region (0xff00ff80, RGBA_8888)\n"); + tex_data = gen_tex_data (0xff00ff80); + cogl_texture_set_region (tex, + 0, 0, /* src x, y */ + 0, 0, /* dst x, y */ + QUAD_WIDTH, QUAD_WIDTH, /* dst width, height */ + QUAD_WIDTH, QUAD_WIDTH, /* src width, height */ + COGL_PIXEL_FORMAT_RGBA_8888, + 0, /* auto compute row stride */ + tex_data); + check_texture (state, 6, 0, /* position */ + tex, + 0xff00ff80); /* expected */ + + /* Updating a texture region for an unmultiplied texture using premultiplied + * region data should result in Cogl unmultiplying the given region data... + */ + if (state->frame > SKIP_FRAMES && g_test_verbose ()) + g_print ("make_texture (0xDEADBEEF, " + "src = RGBA_8888, internal = RGBA_8888)\n"); + tex = make_texture (0xDEADBEEF, + COGL_PIXEL_FORMAT_RGBA_8888, /* src format */ + COGL_PIXEL_FORMAT_RGBA_8888); /* internal format */ + if (state->frame > SKIP_FRAMES && g_test_verbose ()) + g_print ("set_region (0x80008080, RGBA_8888_PRE)\n"); + tex_data = gen_tex_data (0x80008080); + cogl_texture_set_region (tex, + 0, 0, /* src x, y */ + 0, 0, /* dst x, y */ + QUAD_WIDTH, QUAD_WIDTH, /* dst width, height */ + QUAD_WIDTH, QUAD_WIDTH, /* src width, height */ + COGL_PIXEL_FORMAT_RGBA_8888_PRE, + 0, /* auto compute row stride */ + tex_data); + check_texture (state, 7, 0, /* position */ + tex, + 0xff00ff80); /* expected */ + + + if (state->frame > SKIP_FRAMES && g_test_verbose ()) + g_print ("make_texture (0xDEADBEEF, " + "src = RGBA_8888_PRE, " + "internal = RGBA_8888_PRE)\n"); + tex = make_texture (0xDEADBEEF, + COGL_PIXEL_FORMAT_RGBA_8888_PRE, /* src format */ + COGL_PIXEL_FORMAT_RGBA_8888_PRE); /* internal format */ + if (state->frame > SKIP_FRAMES && g_test_verbose ()) + g_print ("set_region (0x80008080, RGBA_8888_PRE)\n"); + tex_data = gen_tex_data (0x80008080); + cogl_texture_set_region (tex, + 0, 0, /* src x, y */ + 0, 0, /* dst x, y */ + QUAD_WIDTH, QUAD_WIDTH, /* dst width, height */ + QUAD_WIDTH, QUAD_WIDTH, /* src width, height */ + COGL_PIXEL_FORMAT_RGBA_8888_PRE, + 0, /* auto compute row stride */ + tex_data); + check_texture (state, 8, 0, /* position */ + tex, + 0x80008080); /* expected */ + + + /* Updating a texture region for a premultiplied texture using unmultiplied + * region data should result in Cogl premultiplying the given region data... + */ + if (state->frame > SKIP_FRAMES && g_test_verbose ()) + g_print ("make_texture (0xDEADBEEF, " + "src = RGBA_8888_PRE, " + "internal = RGBA_8888_PRE)\n"); + tex = make_texture (0xDEADBEEF, + COGL_PIXEL_FORMAT_RGBA_8888_PRE, /* src format */ + COGL_PIXEL_FORMAT_RGBA_8888_PRE); /* internal format */ + if (state->frame > SKIP_FRAMES && g_test_verbose ()) + g_print ("set_region (0xff00ff80, RGBA_8888)\n"); + tex_data = gen_tex_data (0xff00ff80); + cogl_texture_set_region (tex, + 0, 0, /* src x, y */ + 0, 0, /* dst x, y */ + QUAD_WIDTH, QUAD_WIDTH, /* dst width, height */ + QUAD_WIDTH, QUAD_WIDTH, /* src width, height */ + COGL_PIXEL_FORMAT_RGBA_8888, + 0, /* auto compute row stride */ + tex_data); + check_texture (state, 9, 0, /* position */ + tex, + 0x80008080); /* expected */ + + + /* XXX: Experiments have shown that for some buggy drivers, when using + * glReadPixels there is some kind of race, so we delay our test for a + * few frames and a few seconds: + */ + frame_num = state->frame++; + if (frame_num < SKIP_FRAMES) + g_usleep (G_USEC_PER_SEC); + + /* Comment this out if you want visual feedback for what this test paints */ +#if 1 + if (frame_num > SKIP_FRAMES) + clutter_main_quit (); +#endif +} + +static gboolean +queue_redraw (gpointer stage) +{ + clutter_actor_queue_redraw (CLUTTER_ACTOR (stage)); + + return TRUE; +} + +void +test_premult (TestConformSimpleFixture *fixture, + gconstpointer data) +{ + TestState state; + ClutterActor *stage; + ClutterActor *group; + guint idle_source; + + state.frame = 0; + state.passthrough_material = cogl_material_new (); + cogl_material_set_blend (state.passthrough_material, + "RGBA = ADD (SRC_COLOR, 0)", NULL); + cogl_material_set_layer_combine (state.passthrough_material, 0, + "RGBA = REPLACE (TEXTURE)", NULL); + + stage = clutter_stage_get_default (); + + clutter_stage_set_color (CLUTTER_STAGE (stage), &stage_color); + clutter_actor_get_geometry (stage, &state.stage_geom); + + group = clutter_group_new (); + clutter_container_add_actor (CLUTTER_CONTAINER (stage), group); + + /* We force continuous redrawing of the stage, since we need to skip + * the first few frames, and we wont be doing anything else that + * will trigger redrawing. */ + idle_source = g_idle_add (queue_redraw, stage); + + g_signal_connect (group, "paint", G_CALLBACK (on_paint), &state); + + clutter_actor_show_all (stage); + + clutter_main (); + + g_source_remove (idle_source); + + if (g_test_verbose ()) + g_print ("OK\n"); +} + From ec77b9139820207962ba38b3cd2ecc491144592f Mon Sep 17 00:00:00 2001 From: Robert Bragg Date: Thu, 11 Jun 2009 14:31:01 +0100 Subject: [PATCH 13/13] [cogl-texture docs] Improves the documentation of the internal_format args Clarifies that if you give COGL_PIXEL_FORMAT_ANY as the internal format for cogl_texture_new_from_file or cogl_texture_new_from_data then Cogl will choose a premultiplied internal format. --- clutter/cogl/cogl-texture.h | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/clutter/cogl/cogl-texture.h b/clutter/cogl/cogl-texture.h index 15cadc879..e3a2264a1 100644 --- a/clutter/cogl/cogl-texture.h +++ b/clutter/cogl/cogl-texture.h @@ -68,7 +68,13 @@ CoglHandle cogl_texture_new_with_size (guint width, * @filename: the file to load * @flags: Optional flags for the texture, or %COGL_TEXTURE_NONE * @internal_format: the #CoglPixelFormat to use for the GPU storage of the - * texture + * texture. If COGL_PIXEL_FORMAT_ANY is given then a premultiplied + * format similar to the format of the source data will be used. The + * default blending equations of Cogl expect premultiplied color data; + * the main use of passing a non-premultiplied format here is if you + * have non-premultiplied source data and are going to adjust the blend + * mode (see cogl_material_set_blend()) or use the data for something + * other than straight blending. * @error: return location for a #GError or %NULL * * Creates a COGL texture from an image file. @@ -90,7 +96,13 @@ CoglHandle cogl_texture_new_from_file (const gchar *filename, * @flags: Optional flags for the texture, or %COGL_TEXTURE_NONE * @format: the #CoglPixelFormat the buffer is stored in in RAM * @internal_format: the #CoglPixelFormat that will be used for storing - * the buffer on the GPU + * the buffer on the GPU. If COGL_PIXEL_FORMAT_ANY is given then a + * premultiplied format similar to the format of the source data will + * be used. The default blending equations of Cogl expect premultiplied + * color data; the main use of passing a non-premultiplied format here + * is if you have non-premultiplied source data and are going to adjust + * the blend mode (see cogl_material_set_blend()) or use the data for + * something other than straight blending. * @rowstride: the memory offset in bytes between the starts of * scanlines in @data * @data: pointer the memory region where the source buffer resides