From e32c220df6be1c33609f174cf11f121a7029b11c Mon Sep 17 00:00:00 2001 From: Karl Tomlinson Date: Fri, 23 Jul 2010 10:39:22 +1200 Subject: [PATCH] b=576143 copy and draw over background to avoid alpha extraction when possible r=roc --- gfx/thebes/gfxXlibNativeRenderer.cpp | 343 ++++++++++++++++++++------- 1 file changed, 252 insertions(+), 91 deletions(-) diff --git a/gfx/thebes/gfxXlibNativeRenderer.cpp b/gfx/thebes/gfxXlibNativeRenderer.cpp index c410543c99a0..aee13269cd57 100644 --- a/gfx/thebes/gfxXlibNativeRenderer.cpp +++ b/gfx/thebes/gfxXlibNativeRenderer.cpp @@ -40,6 +40,7 @@ #include "gfxXlibNativeRenderer.h" #include "gfxXlibSurface.h" +#include "gfxImageSurface.h" #include "gfxContext.h" #include "cairo-xlib.h" #include "cairo-xlib-xrender.h" @@ -60,18 +61,38 @@ #define NATIVE_DRAWING_NOTE(m) do {} while (0) #endif -/* We have three basic strategies available: - 1) 'direct': cr targets an xlib surface, and other conditions are met: we can - pass the underlying drawable directly to the callback - 2) 'opaque': the image is opaque: we can create a temporary cairo xlib surface, - pass its underlying drawable to the callback, and paint the result - using cairo - 3) 'default': create a temporary cairo xlib surface, fill with black, pass its - underlying drawable to the callback, copy the results to a cairo - image surface, repeat with a white background, update the on-black +/* We have four basic strategies available: + + 1) 'direct': If the target is an xlib surface, and other conditions are met, + we can pass the underlying drawable directly to the callback. + + 2) 'simple': If the drawing is opaque, or we can draw to a surface with an + alpha channel, then we can create a temporary xlib surface, pass its + underlying drawable to the callback, and composite the result using + cairo. + + 3) 'copy-background': If the drawing is not opaque but the target is + opaque, and we can draw to a surface with format such that pixel + conversion to and from the target format is exact, we can create a + temporary xlib surface, copy the background from the target, pass the + underlying drawable to the callback, and copy back to the target. + + This strategy is not used if the pixel format conversion is not exact, + because that would mean that drawing intended to be very transparent + messes with other content. + + The strategy is prefered over simple for non-opaque drawing and opaque + targets on the same screen as compositing without alpha is a simpler + operation. + + 4) 'alpha-extraction': create a temporary xlib surface, fill with black, + pass its underlying drawable to the callback, copy the results to a + cairo image surface, repeat with a white background, update the on-black image alpha values by comparing the two images, then paint the on-black - image using cairo - Sure would be nice to have an X extension to do 3 for us on the server... + image using cairo. + + Sure would be nice to have an X extension or GL to do this for us on the + server... */ static cairo_bool_t @@ -97,7 +118,7 @@ _get_rectangular_clip (cairo_t *cr, cliplist = cairo_copy_clip_rectangle_list (cr); if (cliplist->status != CAIRO_STATUS_SUCCESS) { retval = PR_FALSE; - NATIVE_DRAWING_NOTE("TAKING SLOW PATH: non-rectangular clip\n"); + NATIVE_DRAWING_NOTE("FALLBACK: non-rectangular clip"); goto FINISH; } @@ -113,7 +134,7 @@ _get_rectangular_clip (cairo_t *cr, !_convert_coord_to_int (clips[i].height, &rect.height)) { retval = PR_FALSE; - NATIVE_DRAWING_NOTE("TAKING SLOW PATH: non-integer clip\n"); + NATIVE_DRAWING_NOTE("FALLBACK: non-integer clip"); goto FINISH; } @@ -128,7 +149,7 @@ _get_rectangular_clip (cairo_t *cr, if (i >= max_rectangles) { retval = PR_FALSE; - NATIVE_DRAWING_NOTE("TAKING SLOW PATH: unsupported clip rectangle count\n"); + NATIVE_DRAWING_NOTE("FALLBACK: unsupported clip rectangle count"); goto FINISH; } @@ -157,16 +178,10 @@ gfxXlibNativeRenderer::DrawDirect(gfxContext *ctx, nsIntSize size, { cairo_t *cr = ctx->GetCairo(); - /* Check that the operator is OVER */ - if (cairo_get_operator (cr) != CAIRO_OPERATOR_OVER) { - NATIVE_DRAWING_NOTE("TAKING SLOW PATH: non-OVER operator\n"); - return PR_FALSE; - } - /* Check that the target surface is an xlib surface. */ cairo_surface_t *target = cairo_get_group_target (cr); if (cairo_surface_get_type (target) != CAIRO_SURFACE_TYPE_XLIB) { - NATIVE_DRAWING_NOTE("TAKING SLOW PATH: non-X surface\n"); + NATIVE_DRAWING_NOTE("FALLBACK: non-X surface"); return PR_FALSE; } @@ -175,18 +190,18 @@ gfxXlibNativeRenderer::DrawDirect(gfxContext *ctx, nsIntSize size, then alternate screens cannot be supported. */ PRBool supports_alternate_visual = (flags & DRAW_SUPPORTS_ALTERNATE_VISUAL) != 0; - PRBool supports_alternate_screen = supports_alternate_visual - && (flags & DRAW_SUPPORTS_ALTERNATE_SCREEN); + PRBool supports_alternate_screen = supports_alternate_visual && + (flags & DRAW_SUPPORTS_ALTERNATE_SCREEN); if (!supports_alternate_screen && cairo_xlib_surface_get_screen (target) != screen) { - NATIVE_DRAWING_NOTE("TAKING SLOW PATH: non-default screen\n"); + NATIVE_DRAWING_NOTE("FALLBACK: non-default screen"); return PR_FALSE; } /* Check that there is a visual */ Visual *target_visual = cairo_xlib_surface_get_visual (target); if (!target_visual) { - NATIVE_DRAWING_NOTE("TAKING SLOW PATH: no Visual for surface\n"); + NATIVE_DRAWING_NOTE("FALLBACK: no Visual for surface"); return PR_FALSE; } /* Check that the visual is supported */ @@ -198,7 +213,7 @@ gfxXlibNativeRenderer::DrawDirect(gfxContext *ctx, nsIntSize size, if (!target_format || (target_format != XRenderFindVisualFormat (DisplayOfScreen(screen), visual))) { - NATIVE_DRAWING_NOTE("TAKING SLOW PATH: unsupported Visual\n"); + NATIVE_DRAWING_NOTE("FALLBACK: unsupported Visual"); return PR_FALSE; } } @@ -269,57 +284,174 @@ gfxXlibNativeRenderer::DrawDirect(gfxContext *ctx, nsIntSize size, } static PRBool -FormatHasAlpha(const XRenderPictFormat *format) { - if (!format) - return false; - - if (format->type != PictTypeDirect) - return false; - - return format->direct.alphaMask != 0; +VisualHasAlpha(Screen *screen, Visual *visual) { + // There may be some other visuals format with alpha but usually this is + // the only one we care about. + return visual->c_class == TrueColor && + visual->bits_per_rgb == 8 && + visual->red_mask == 0xff0000 && + visual->green_mask == 0xff00 && + visual->blue_mask == 0xff && + gfxXlibSurface::DepthOfVisual(screen, visual) == 32; } +// Returns whether pixel conversion between visual and format is exact (in +// both directions). +static PRBool +FormatConversionIsExact(Screen *screen, Visual *visual, XRenderPictFormat *format) { + if (!format || + visual->c_class != TrueColor || + format->type != PictTypeDirect || + gfxXlibSurface::DepthOfVisual(screen, visual) != format->depth) + return PR_FALSE; + + XRenderPictFormat *visualFormat = + XRenderFindVisualFormat(DisplayOfScreen(screen), visual); + + if (visualFormat->type != PictTypeDirect ) + return PR_FALSE; + + const XRenderDirectFormat& a = visualFormat->direct; + const XRenderDirectFormat& b = format->direct; + return a.redMask == b.redMask && + a.greenMask == b.greenMask && + a.blueMask == b.blueMask; +} + +// The 3 non-direct strategies described above. +// The surface format and strategy are inter-dependent. +enum DrawingMethod { + eSimple, + eCopyBackground, + eAlphaExtraction +}; + static already_AddRefed -_create_temp_xlib_surface (cairo_t *cr, nsIntSize size, - PRUint32 flags, Screen *screen, Visual *visual) +CreateTempXlibSurface (gfxASurface *destination, nsIntSize size, + PRBool canDrawOverBackground, + PRUint32 flags, Screen *screen, Visual *visual, + DrawingMethod *method) { - Drawable drawable = None; + PRBool drawIsOpaque = (flags & gfxXlibNativeRenderer::DRAW_IS_OPAQUE) != 0; + PRBool supportsAlternateVisual = + (flags & gfxXlibNativeRenderer::DRAW_SUPPORTS_ALTERNATE_VISUAL) != 0; + PRBool supportsAlternateScreen = supportsAlternateVisual && + (flags & gfxXlibNativeRenderer::DRAW_SUPPORTS_ALTERNATE_SCREEN); - // For opaque drawing, set up the temp surface for copying to the target. - // For non-opaque drawing we read back anyway so just use the - // prefered screen and visual. - cairo_surface_t *target = cairo_get_group_target (cr); - if ((flags & gfxXlibNativeRenderer::DRAW_IS_OPAQUE) - && cairo_surface_get_type (target) == CAIRO_SURFACE_TYPE_XLIB) { + cairo_surface_t *target = destination->CairoSurface(); + cairo_surface_type_t target_type = cairo_surface_get_type (target); + cairo_content_t target_content = cairo_surface_get_content (target); - Screen *target_screen = cairo_xlib_surface_get_screen (target); - PRBool supports_alternate_visual = - (flags & gfxXlibNativeRenderer::DRAW_SUPPORTS_ALTERNATE_VISUAL) != 0; - PRBool supports_alternate_screen = supports_alternate_visual - && (flags & gfxXlibNativeRenderer::DRAW_SUPPORTS_ALTERNATE_SCREEN); - if (target_screen == screen || supports_alternate_screen) { + Screen *target_screen = target_type == CAIRO_SURFACE_TYPE_XLIB ? + cairo_xlib_surface_get_screen (target) : screen; - if (supports_alternate_visual) { - Visual *target_visual = cairo_xlib_surface_get_visual (target); - if (target_visual && - (!FormatHasAlpha(cairo_xlib_surface_get_xrender_format (target)))) { - visual = target_visual; - } else if (target_screen != screen) { - visual = DefaultVisualOfScreen (target_screen); - } + // When the background has an alpha channel, we need to draw with an alpha + // channel anyway, so there is no need to copy the background. If + // doCopyBackground is set here, we'll also need to check below that the + // background can copied without any loss in format conversions. + PRBool doCopyBackground = !drawIsOpaque && canDrawOverBackground && + target_content == CAIRO_CONTENT_COLOR; + + if (supportsAlternateScreen && screen != target_screen && drawIsOpaque) { + // Prefer a visual on the target screen. + // (If !drawIsOpaque, we'll need doCopyBackground or an alpha channel.) + visual = DefaultVisualOfScreen(target_screen); + screen = target_screen; + + } else if (doCopyBackground || (supportsAlternateVisual && drawIsOpaque)) { + // Analyse the pixel formats either to check whether we can + // doCopyBackground or to see if we can find a better visual for + // opaque drawing. + Visual *target_visual = NULL; + XRenderPictFormat *target_format = NULL; + switch (target_type) { + case CAIRO_SURFACE_TYPE_XLIB: + target_visual = cairo_xlib_surface_get_visual (target); + target_format = cairo_xlib_surface_get_xrender_format (target); + break; + case CAIRO_SURFACE_TYPE_IMAGE: { + gfxASurface::gfxImageFormat imageFormat = + static_cast(destination)->Format(); + target_visual = gfxXlibSurface::FindVisual(screen, imageFormat); + Display *dpy = DisplayOfScreen(screen); + if (target_visual) { + target_format = XRenderFindVisualFormat(dpy, visual); + } else { + target_format = + gfxXlibSurface::FindRenderFormat(dpy, imageFormat); + } + break; + } + default: + break; + } + + if (supportsAlternateVisual && + (supportsAlternateScreen || screen == target_screen)) { + if (target_visual) { + visual = target_visual; + screen = target_screen; } + } + // Could try harder to match formats across screens for background + // copying when !supportsAlternateScreen, if we cared. Preferably + // we'll find a visual below with an alpha channel anyway; if so, the + // background won't need to be copied. - drawable = cairo_xlib_surface_get_drawable (target); - screen = target_screen; + if (doCopyBackground && visual != target_visual && + !FormatConversionIsExact(screen, visual, target_format)) { + doCopyBackground = PR_FALSE; } } - if (!drawable) { - drawable = RootWindowOfScreen (screen); + if (supportsAlternateVisual && !drawIsOpaque && + (screen != target_screen || + !(doCopyBackground || VisualHasAlpha(screen, visual)))) { + // Try to find a visual with an alpha channel. + Screen *visualScreen = + supportsAlternateScreen ? target_screen : screen; + Visual *argbVisual = + gfxXlibSurface::FindVisual(visualScreen, + gfxASurface::ImageFormatARGB32); + if (argbVisual) { + visual = argbVisual; + screen = visualScreen; + } else if (!doCopyBackground && + gfxXlibSurface::DepthOfVisual(screen, visual) != 24) { + // Will need to do alpha extraction; prefer a 24-bit visual. + // No advantage in using the target screen. + Visual *rgb24Visual = + gfxXlibSurface::FindVisual(screen, + gfxASurface::ImageFormatRGB24); + if (rgb24Visual) { + visual = rgb24Visual; + } + } } - return gfxXlibSurface::Create(screen, visual, - gfxIntSize(size.width, size.height), - drawable); + + Drawable drawable = + (screen == target_screen && target_type == CAIRO_SURFACE_TYPE_XLIB) ? + cairo_xlib_surface_get_drawable (target) : RootWindowOfScreen(screen); + + nsRefPtr surface = + gfxXlibSurface::Create(screen, visual, + gfxIntSize(size.width, size.height), + drawable); + + if (drawIsOpaque || + surface->GetContentType() == gfxASurface::CONTENT_COLOR_ALPHA) { + NATIVE_DRAWING_NOTE(drawIsOpaque ? + ", SIMPLE OPAQUE\n" : ", SIMPLE WITH ALPHA"); + *method = eSimple; + } else if (doCopyBackground) { + NATIVE_DRAWING_NOTE(", COPY BACKGROUND\n"); + *method = eCopyBackground; + } else { + NATIVE_DRAWING_NOTE(", SLOW ALPHA EXTRACTION\n"); + *method = eAlphaExtraction; + } + + return surface.forget(); } PRBool @@ -456,9 +588,17 @@ gfxXlibNativeRenderer::Draw(gfxContext* ctx, nsIntSize size, result->mUniformAlpha = PR_FALSE; result->mUniformColor = PR_FALSE; } - - PRBool matrixIsIntegerTranslation = - !ctx->CurrentMatrix().HasNonIntegerTranslation(); + + PRBool drawIsOpaque = (flags & DRAW_IS_OPAQUE) != 0; + gfxMatrix matrix = ctx->CurrentMatrix(); + + // We can only draw direct or onto a copied background if pixels align and + // native drawing is compatible with the current operator. (The matrix is + // actually also pixel-exact for flips and right-angle rotations, which + // would permit copying the background but not drawing direct.) + PRBool matrixIsIntegerTranslation = !matrix.HasNonIntegerTranslation(); + PRBool canDrawOverBackground = matrixIsIntegerTranslation && + ctx->CurrentOperator() == gfxContext::OPERATOR_OVER; // The padding of 0.5 for non-pixel-exact transformations used here is // the same as what _cairo_pattern_analyze_filter uses. @@ -469,8 +609,11 @@ gfxXlibNativeRenderer::Draw(gfxContext* ctx, nsIntSize size, // little larger than the drawingRect; affectedRect.Outset(filterRadius); - NATIVE_DRAWING_NOTE("TAKING SLOW PATH: matrix not integer translation\n"); + NATIVE_DRAWING_NOTE("FALLBACK: matrix not integer translation"); + } else if (!canDrawOverBackground) { + NATIVE_DRAWING_NOTE("FALLBACK: unsupported operator"); } + // Clipping to the region affected by drawing allows us to consider only // the portions of the clip region that will be affected by drawing. gfxRect clipExtents; @@ -482,42 +625,59 @@ gfxXlibNativeRenderer::Draw(gfxContext* ctx, nsIntSize size, if (clipExtents.IsEmpty()) return; // nothing to do - if (matrixIsIntegerTranslation && + if (canDrawOverBackground && DrawDirect(ctx, size, flags, screen, visual)) return; } nsIntRect drawingRect(nsIntPoint(0, 0), size); - PRBool drawIsOpaque = (flags & DRAW_IS_OPAQUE) != 0; - if (drawIsOpaque || !result) { - // Drawing need only be performed within the clip extents - // (and padding for the filter). - if (!matrixIsIntegerTranslation) { - // The source surface may need to be a little larger than the clip - // extents due to the filter footprint. - clipExtents.Outset(filterRadius); - } - clipExtents.RoundOut(); - - nsIntRect intExtents(PRInt32(clipExtents.X()), - PRInt32(clipExtents.Y()), - PRInt32(clipExtents.Width()), - PRInt32(clipExtents.Height())); - drawingRect.IntersectRect(drawingRect, intExtents); + // Drawing need only be performed within the clip extents + // (and padding for the filter). + if (!matrixIsIntegerTranslation) { + // The source surface may need to be a little larger than the clip + // extents due to the filter footprint. + clipExtents.Outset(filterRadius); } + clipExtents.RoundOut(); + + nsIntRect intExtents(PRInt32(clipExtents.X()), + PRInt32(clipExtents.Y()), + PRInt32(clipExtents.Width()), + PRInt32(clipExtents.Height())); + drawingRect.IntersectRect(drawingRect, intExtents); gfxPoint offset(drawingRect.x, drawingRect.y); - cairo_t *cr = ctx->GetCairo(); + DrawingMethod method; + nsRefPtr target = ctx->CurrentSurface(); nsRefPtr tempXlibSurface = - _create_temp_xlib_surface (cr, drawingRect.Size(), - flags, screen, visual); - if (tempXlibSurface == NULL) + CreateTempXlibSurface(target, drawingRect.Size(), + canDrawOverBackground, flags, screen, visual, + &method); + if (!tempXlibSurface) return; + if (drawingRect.Size() != size || method == eCopyBackground) { + // Only drawing a portion, or copying background, + // so won't return a result. + result = NULL; + } + nsRefPtr tmpCtx; if (!drawIsOpaque) { tmpCtx = new gfxContext(tempXlibSurface); - tmpCtx->SetOperator(gfxContext::OPERATOR_CLEAR); + if (method == eCopyBackground) { + tmpCtx->SetOperator(gfxContext::OPERATOR_SOURCE); + tmpCtx->SetSource(target, -(offset + matrix.GetTranslation())); + // The copy from the tempXlibSurface to the target context should + // use operator SOURCE, but that would need a mask to bound the + // operation. Here we only copy opaque backgrounds so operator + // OVER will behave like SOURCE masked by the surface. + NS_ASSERTION(tempXlibSurface->GetContentType() + == gfxASurface::CONTENT_COLOR, + "Don't copy background with a transparent surface"); + } else { + tmpCtx->SetOperator(gfxContext::OPERATOR_CLEAR); + } tmpCtx->Paint(); } @@ -525,7 +685,7 @@ gfxXlibNativeRenderer::Draw(gfxContext* ctx, nsIntSize size, return; } - if (drawIsOpaque) { + if (method != eAlphaExtraction) { ctx->SetSource(tempXlibSurface, offset); ctx->Paint(); if (result) { @@ -561,6 +721,7 @@ gfxXlibNativeRenderer::Draw(gfxContext* ctx, nsIntSize size, _compute_alpha_values ((uint32_t*)black_data, (uint32_t*)white_data, width, height, result); cairo_surface_mark_dirty (black_image_surface); + cairo_t *cr = ctx->GetCairo(); cairo_set_source_surface (cr, black_image_surface, offset.x, offset.y); /* if the caller wants to retrieve the rendered image, put it into a 'similar' surface, and use that as the source for the drawing right