[cairo] Question about cairo_paint

Antoine Azar cairo at antoineazar.com
Sat Feb 16 00:26:15 PST 2008


Hey all,
I've got a working optimization that speeds up paint operations for 
opaque sources. The optimization replaces an OVER operator by a 
SOURCE operator, and in the case of EXTEND_NONE, creates a path 
around the source's extents and calls a fill operation instead. The 
speedups can be huge, espescially in the case of copying a small 
surface onto a much larger one.

I'd like feedback from people with more experience with the inner 
workings of Cairo. I've tried to make sense of the different matrices 
stored and the logic behind the coord transforms, but I'm sure 
there's a better way to do this. I ran the most relevant tests and 
they're all still passing (some were failing even before this 
optimization. Is this normal?)

The optimization is right now straight in _cairo_gstate_paint, but I 
think it could be a good idea to create a layer of functions before 
the gstate calls to determine which call really is best considering 
the current gstate and to convert it if necessary.

I'm attaching my optimized _cairo_gstate_paint function. I'm also 
attaching a summary of the speedups seen with perf (using only the 
cases affected by the optim). The best speedup is 11X, many are 
3X-5X. The solid/gradients using SOURCE should stay at 1X as they're 
not affected. There's one test at 0.68X but I think it's an anomaly 
as it had a 20% Std dev.

Looking forward to your feedback.
Thanks,
Antoine


At 05:31 PM 2/7/2008, Carl Worth wrote:
>On Thu, 07 Feb 2008 16:51:03 -0500, Antoine Azar wrote:
> > >Uhm... how exactly are you determining what sources are classified
> > >as opaque here?
> >
> > There is a _cairo_pattern_is_opaque convenience function in
> > cairo-pattern.c.
>
>Right, OK. I had forgotten this function existed. This looks just
>fine.
>
>What I was wondering is whether the decision of "is_opaque"
>considered the extend mode at all, (and it does not).
>
> > There is a caveat listed concerning patterns and
> > gradients:
>...
> > If I understand this correctly a "deep channel"
>
>No need to worry about that now. That's just a future-proofing warning
>that if we add such a backend in the future we'll have to tweak
>CAIRO_COLOR_IS_OPAQUE.
>
>That's really just one of many things that will have to change for
>such a backend to be added in the future. And for you, for now, it's
>nothing you need to worry about.
>
> > You're absolutely right. I'll look more into this.
>
>Good luck!
>
>The easiest thing for now would be to just not do the OVER->SOURCE
>optimization in the face of EXTEND_NONE. Doing better than that will
>require reasoning about the extents of any given operation. I don't
>think we currently compute those extents in advance in any convenient
>form, (but see cairo-analysis-surface.c for code that *does* do all of
>the necessary extents computation).
>
>If we could compute all of that without doing any additional work,
>then it might be worth it, (and we'd likely end up wanting to pass
>such details down to the backends as well).
>
>-Carl
-------------- next part --------------
backend-content	test-size	Speedup (old median / new median)
image-rgba	paint_solid_rgb_over-256	97%
image-rgba	paint_solid_rgb_source-256	100%
image-rgba	paint_image_rgb_over-256	503%
image-rgba	paint_image_rgb_source-256	85%
image-rgba	paint_linear_rgb_over-256	154%
image-rgba	paint_linear_rgb_source-256	101%
image-rgba	paint_radial_rgb_over-256	114%
image-rgba	paint_radial_rgb_source-256	100%
image-rgba	paint_solid_rgb_over-512	63%
image-rgba	paint_solid_rgb_source-512	88%
image-rgba	paint_image_rgb_over-512	313%
image-rgba	paint_image_rgb_source-512	84%
image-rgba	paint_linear_rgb_over-512	147%
image-rgba	paint_linear_rgb_source-512	96%
image-rgba	paint_radial_rgb_over-512	112%
image-rgba	paint_radial_rgb_source-512	100%
image-rgb	paint_solid_rgb_over-256	100%
image-rgb	paint_solid_rgb_source-256	100%
image-rgb	paint_image_rgb_over-256	1137%
image-rgb	paint_image_rgb_source-256	96%
image-rgb	paint_linear_rgb_over-256	154%
image-rgb	paint_linear_rgb_source-256	101%
image-rgb	paint_radial_rgb_over-256	114%
image-rgb	paint_radial_rgb_source-256	102%
image-rgb	paint_solid_rgb_over-512	101%
image-rgb	paint_solid_rgb_source-512	98%
image-rgb	paint_image_rgb_over-512	454%
image-rgb	paint_image_rgb_source-512	106%
image-rgb	paint_linear_rgb_over-512	148%
image-rgb	paint_linear_rgb_source-512	97%
image-rgb	paint_radial_rgb_over-512	113%
image-rgb	paint_radial_rgb_source-512	99%
win32-rgb	paint_solid_rgb_over-256	100%
win32-rgb&	paint_solid_rgb_over-256	99%
win32-rgb	paint_solid_rgb_source-256	100%
win32-rgb&	paint_solid_rgb_source-256	100%
win32-rgb	paint_image_rgb_over-256	99%
win32-rgb&	paint_image_rgb_over-256	101%
win32-rgb	paint_image_rgb_source-256	98%
win32-rgb&	paint_image_rgb_source-256	100%
win32-rgb	paint_linear_rgb_over-256	153%
win32-rgb&	paint_linear_rgb_over-256	151%
win32-rgb	paint_linear_rgb_source-256	99%
win32-rgb&	paint_linear_rgb_source-256	100%
win32-rgb	paint_radial_rgb_over-256	114%
win32-rgb&	paint_radial_rgb_over-256	114%
win32-rgb	paint_radial_rgb_source-256	99%
win32-rgb&	paint_radial_rgb_source-256	100%
win32-rgb	paint_solid_rgb_over-512	120%
win32-rgb&	paint_solid_rgb_over-512	98%
win32-rgb	paint_solid_rgb_source-512	121%
win32-rgb&	paint_solid_rgb_source-512	99%
win32-rgb	paint_image_rgb_over-512	97%
win32-rgb&	paint_image_rgb_over-512	99%
win32-rgb	paint_image_rgb_source-512	96%
win32-rgb&	paint_image_rgb_source-512	98%
win32-rgb	paint_linear_rgb_over-512	149%
win32-rgb&	paint_linear_rgb_over-512	149%
win32-rgb	paint_linear_rgb_source-512	104%
win32-rgb&	paint_linear_rgb_source-512	104%
win32-rgb	paint_radial_rgb_over-512	112%
win32-rgb&	paint_radial_rgb_over-512	112%
win32-rgb	paint_radial_rgb_source-512	104%
win32-rgb&	paint_radial_rgb_source-512	96%
win32-rgba	paint_solid_rgb_over-256	114%
win32-rgba&	paint_solid_rgb_over-256	94%
win32-rgba	paint_solid_rgb_source-256	101%
win32-rgba&	paint_solid_rgb_source-256	96%
win32-rgba	paint_image_rgb_over-256	513%
win32-rgba&	paint_image_rgb_over-256	360%
win32-rgba	paint_image_rgb_source-256	68%
win32-rgba&	paint_image_rgb_source-256	83%
win32-rgba	paint_linear_rgb_over-256	151%
win32-rgba&	paint_linear_rgb_over-256	152%
win32-rgba	paint_linear_rgb_source-256	99%
win32-rgba&	paint_linear_rgb_source-256	100%
win32-rgba	paint_radial_rgb_over-256	114%
win32-rgba&	paint_radial_rgb_over-256	114%
win32-rgba	paint_radial_rgb_source-256	99%
win32-rgba&	paint_radial_rgb_source-256	99%
win32-rgba	paint_solid_rgb_over-512	61%
win32-rgba&	paint_solid_rgb_over-512	103%
win32-rgba	paint_solid_rgb_source-512	100%
win32-rgba&	paint_solid_rgb_source-512	101%
win32-rgba	paint_image_rgb_over-512	312%
win32-rgba&	paint_image_rgb_over-512	309%
win32-rgba	paint_image_rgb_source-512	98%
win32-rgba&	paint_image_rgb_source-512	96%
win32-rgba	paint_linear_rgb_over-512	155%
win32-rgba&	paint_linear_rgb_over-512	153%
win32-rgba	paint_linear_rgb_source-512	104%
win32-rgba&	paint_linear_rgb_source-512	104%
win32-rgba	paint_radial_rgb_over-512	104%
win32-rgba&	paint_radial_rgb_over-512	108%
win32-rgba	paint_radial_rgb_source-512	104%
win32-rgba&	paint_radial_rgb_source-512	99%
-------------- next part --------------
cairo_status_t
_cairo_gstate_paint (cairo_gstate_t *gstate)
{
    cairo_status_t status;
    cairo_pattern_union_t pattern;
    cairo_operator_t op = gstate->op;
    
    //optimization related declarations
    cairo_rectangle_int_t extents;
    const cairo_pattern_union_t *pattern_union;
    cairo_fixed_t x_fixed, y_fixed;
    cairo_fixed_t dx_fixed, dy_fixed;
    cairo_rectangle_t surface_rect;
    cairo_path_fixed_t *path;
    cairo_matrix_t matrix_invert;

    if (gstate->source->status)
	return gstate->source->status;

    status = _cairo_surface_set_clip (gstate->target, &gstate->clip);
    if (status)
	return status;

    status = _cairo_gstate_copy_transformed_source (gstate, &pattern.base);
    if (status)
	return status;

    //AAZAR - BEGIN OPTIMIZATION //////////////////////////////////////////////////////////////////////////////

    //Optimize a very common case of calling paint with an OVER operator for opaque surfaces.
    //Replace it with a more efficient SOURCE operator, and constrain the operation to the source's extents.
    if ( _cairo_pattern_is_opaque(&pattern.base) && (op == CAIRO_OPERATOR_OVER || op == CAIRO_OPERATOR_SOURCE))
    {
        pattern_union = (cairo_pattern_union_t *) &pattern;
        switch (pattern_union->base.type) 
        {
            case CAIRO_PATTERN_TYPE_SOLID:
            case CAIRO_PATTERN_TYPE_LINEAR:
            case CAIRO_PATTERN_TYPE_RADIAL:
	        op = CAIRO_OPERATOR_SOURCE;
                break;
            case CAIRO_PATTERN_TYPE_SURFACE:
                //in all cases set the operator to source
                op = CAIRO_OPERATOR_SOURCE;
                
                if (pattern.surface.base.extend != CAIRO_EXTEND_NONE)
                {
                    //We'll need to fill the whole destination anyways, so go on with paint
                    break;
                }
                else
                {
                    //create a path around the source's extents and call fill with that
                    path = _cairo_path_fixed_create();

                    //extents of the source
                    status = _cairo_surface_get_extents(pattern.surface.surface, &extents);
                    if (status) {
                        printf("ERROR\n");
	                return status; //instead continue without optimization?
                    }

                    //multiply by the source's inverse matrix and by the dest context transformation matrix
                    surface_rect.x = (double)extents.x;
                    surface_rect.y = (double)extents.y;
                    surface_rect.width = surface_rect.x+(double)extents.width; //convert width and height to point coords for now
                    surface_rect.height = surface_rect.y+(double)extents.height;

                    //FIXME: isn't the inverse already available somewhere?
                    matrix_invert = pattern.surface.base.matrix;
                    cairo_matrix_invert(&matrix_invert);
                    _cairo_matrix_transform_bounding_box (&matrix_invert,
                            &surface_rect.x, &surface_rect.y, &surface_rect.width, &surface_rect.height, NULL);

                    surface_rect.x = floor(surface_rect.x);
                    surface_rect.y = floor(surface_rect.y);
                    surface_rect.width = ceil(surface_rect.width);
                    surface_rect.height = ceil(surface_rect.height);

                    if (surface_rect.width <= 0 || surface_rect.height <= 0)
	                return CAIRO_STATUS_SUCCESS;

                    cairo_matrix_transform_point (&gstate->target->device_transform, &surface_rect.x, &surface_rect.y);
                    cairo_matrix_transform_point (&gstate->target->device_transform, &surface_rect.width, &surface_rect.height);
                    
                    //FIXME: why doesn't user_to_backend implement the floor and ceil as done above (and why do we need them in the first place anyways?)?
                    //       this is apparent in the filter-nearest-offset test
//                    _cairo_gstate_user_to_backend (gstate, &surface_rect.x, &surface_rect.y);
//                    _cairo_gstate_user_to_backend (gstate, &surface_rect.width, &surface_rect.height);

                    //Go back to width and height instead of point coords
                    surface_rect.width -= surface_rect.x;
                    surface_rect.height -= surface_rect.y;

                    x_fixed = _cairo_fixed_from_double (surface_rect.x);
                    y_fixed = _cairo_fixed_from_double (surface_rect.y);

                    dx_fixed = _cairo_fixed_from_double (surface_rect.width);
                    dy_fixed = _cairo_fixed_from_double (surface_rect.height);

                    status = _cairo_path_fixed_move_to (path, x_fixed, y_fixed);
                    status |= _cairo_path_fixed_rel_line_to (path, dx_fixed, 0);
                    status |= _cairo_path_fixed_rel_line_to (path, 0, dy_fixed);
                    status |= _cairo_path_fixed_rel_line_to (path, -dx_fixed, 0);
                    status |= _cairo_path_fixed_rel_line_to (path, 0, -dy_fixed);
                    status |= _cairo_path_fixed_close_path (path);

                    if (status) {
                        printf("ERROR\n");
	                return status; //instead continue without optimization?
                    }

                    _cairo_gstate_fill(gstate, path);
                    _cairo_path_fixed_destroy(path);
                    return status;
                }
        }
    }
    
    //AAZAR - END OPTIMIZATION //////////////////////////////////////////////////////////////////////////////

    status = _cairo_surface_paint (gstate->target,
				   op,
				   &pattern.base);

    _cairo_pattern_fini (&pattern.base);

    return status;
}


More information about the cairo mailing list