[xlib] Use server-side gradients.

We can offload creation of gradients to server that support RENDER 0.10
and later. This greatly reduces the amount of traffic we need to send over
our display connection as the gradient patterns are much smaller than the
full image. Even if the server fallbacks to using pixman, performance
should be improved by the reduced transport overhead. Furthermore this is a
requisite to enable hardware accelerated gradients with the xlib backend.

Running cairo-perf-trace on tiny, Celeron/i915:

  before: firefox-20090601 211.585
   after: firefox-20090601 270.939

and on tiger, CoreDuo/nvidia:

  before: firefox-20090601 70.143
   after: firefox-20090601 87.326

where linear gradients are used extensively throughout the GTK+ theme.
Not quite the result I was expecting!

In particular, looking at tiny:

 xlib-rgba paint-with-alpha_linear-rgba_over-512   47.11 (47.16 0.05%) -> 123.42 (123.72 0.13%):  2.62x slowdown
█▋
 xlib-rgba paint-with-alpha_linear3-rgba_over-512   47.27 (47.32 0.04%) -> 123.78 (124.04 0.13%):  2.62x slowdown
█▋
 xlib-rgba paint-with-alpha_linear-rgb_over-512   47.19 (47.21 0.02%) -> 123.37 (123.70 0.13%):  2.61x slowdown
█▋
 xlib-rgba paint-with-alpha_linear3-rgb_over-512   47.30 (47.31 0.04%) -> 123.52 (123.62 0.09%):  2.61x slowdown
█▋
 xlib-rgba     paint_linear3-rgb_over-512   47.29 (47.32 0.05%) -> 118.95 (119.60 0.29%):  2.52x slowdown
█▌
 xlib-rgba     paint_linear-rgba_over-512   47.14 (47.17 0.06%) -> 116.76 (117.06 0.16%):  2.48x slowdown
█▌
 xlib-rgba    paint_linear3-rgba_over-512   47.32 (47.34 0.04%) -> 116.85 (116.98 0.05%):  2.47x slowdown
█▌
 xlib-rgba      paint_linear-rgb_over-512   47.15 (47.19 0.03%) -> 114.08 (114.55 0.20%):  2.42x slowdown
█▍
 xlib-rgba paint-with-alpha_radial-rgb_over-512  117.25 (119.43 1.21%) -> 194.36 (194.73 0.09%):  1.66x slowdown
▋
 xlib-rgba paint-with-alpha_radial-rgba_over-512  117.22 (117.26 0.02%) -> 193.81 (194.17 0.11%):  1.65x slowdown
▋
 xlib-rgba     paint_radial-rgba_over-512  117.23 (117.26 0.02%) -> 186.35 (186.41 0.03%):  1.59x slowdown
▋
 xlib-rgba      paint_radial-rgb_over-512  117.23 (117.27 0.02%) -> 184.14 (184.62 1.51%):  1.57x slowdown
▋

Before 1.10, we may choose to disable server-side gradients for the
current crop of Xorg servers, similar to the extended repeat modes.

[Updated by Chris Wilson. All bugs are his.]
This commit is contained in:
Chris Wilson 2009-07-31 13:17:24 +01:00
parent 53cbbad8fb
commit 786d1f962a
5 changed files with 318 additions and 33 deletions

28
NEWS
View file

@ -26,6 +26,34 @@ New utilities:
Further minimisation of the fail trace using "delta debugging".
More control over test/reference targets.
Backend improvements:
xlib
Server-side gradients. The theory is that we can offload computation
of gradients to the GPU and avoid pushing large images over the
connection. Even if the driver has to fallback and use pixman to render
a temporary source, it should be able to do so in a more efficient manner
than Cairo itself. However, cairo-perf suggests otherwise:
On tiny, Celeron/i915:
before: firefox-20090601 211.585
after: firefox-20090601 270.939
and on tiger, CoreDuo/nvidia:
before: firefox-20090601 70.143
after: firefox-20090601 87.326
In particular, looking at tiny:
xlib-rgba paint-with-alpha_linear-rgba_over-512 47.11 (47.16 0.05%) -> 123.42 (123.72 0.13%): 2.62x slowdown
█▋
xlib-rgba paint-with-alpha_linear3-rgba_over-512 47.27 (47.32 0.04%) -> 123.78 (124.04 0.13%): 2.62x slowdown
█▋
New experimental backends:
QT

View file

@ -284,8 +284,9 @@ _cairo_xlib_display_get (Display *dpy,
memset (display->cached_xrender_formats, 0,
sizeof (display->cached_xrender_formats));
display->buggy_repeat = FALSE;
display->buggy_gradients = FALSE;
display->buggy_pad_reflect = TRUE;
display->buggy_repeat = FALSE;
/* This buggy_repeat condition is very complicated because there
* are multiple X server code bases (with multiple versioning
@ -335,6 +336,12 @@ _cairo_xlib_display_get (Display *dpy,
if (VendorRelease (dpy) >= 60700000) {
if (VendorRelease (dpy) < 70000000)
display->buggy_repeat = TRUE;
/* We know that gradients simply do not work in eary Xorg servers */
if (VendorRelease (dpy) < 70200000)
{
display->buggy_gradients = TRUE;
}
} else {
if (VendorRelease (dpy) < 10400000)
display->buggy_repeat = TRUE;

View file

@ -72,8 +72,9 @@ struct _cairo_xlib_display {
cairo_freelist_t wq_freelist;
cairo_xlib_hook_t *close_display_hooks;
unsigned int buggy_repeat :1;
unsigned int buggy_gradients :1;
unsigned int buggy_pad_reflect :1;
unsigned int buggy_repeat :1;
unsigned int closed :1;
};

View file

@ -75,8 +75,9 @@ struct _cairo_xlib_surface {
* Both are fixed in xorg >= 6.9 and hopefully in > 6.8.2, so
* we can reuse the test for now.
*/
cairo_bool_t buggy_repeat;
cairo_bool_t buggy_pad_reflect;
unsigned int buggy_gradients : 1;
unsigned int buggy_pad_reflect : 1;
unsigned int buggy_repeat : 1;
int width;
int height;

View file

@ -153,6 +153,7 @@ static const XTransform identity = { {
#define CAIRO_SURFACE_RENDER_HAS_FILTERS(surface) CAIRO_SURFACE_RENDER_AT_LEAST((surface), 0, 6)
#define CAIRO_SURFACE_RENDER_HAS_EXTENDED_REPEAT(surface) CAIRO_SURFACE_RENDER_AT_LEAST((surface), 0, 10)
#define CAIRO_SURFACE_RENDER_HAS_GRADIENTS(surface) CAIRO_SURFACE_RENDER_AT_LEAST((surface), 0, 10)
#define CAIRO_SURFACE_RENDER_HAS_PDF_OPERATORS(surface) CAIRO_SURFACE_RENDER_AT_LEAST((surface), 0, 11)
@ -1886,6 +1887,254 @@ _render_operator (cairo_operator_t op)
}
}
static cairo_int_status_t
_cairo_xlib_surface_acquire_pattern_surface (cairo_xlib_surface_t *dst,
const cairo_pattern_t *pattern,
cairo_content_t content,
int x, int y,
int width, int height,
cairo_xlib_surface_t **surface_out,
cairo_surface_attributes_t *attributes)
{
switch (pattern->type) {
case CAIRO_PATTERN_TYPE_LINEAR:
case CAIRO_PATTERN_TYPE_RADIAL:
{
cairo_gradient_pattern_t *gradient =
(cairo_gradient_pattern_t *) pattern;
cairo_matrix_t matrix = pattern->matrix;
cairo_xlib_surface_t *surface;
char buf[CAIRO_STACK_BUFFER_SIZE];
XFixed *stops;
XRenderColor *colors;
XRenderPictFormat *format;
Picture picture;
unsigned int i;
if (dst->buggy_gradients)
break;
if (gradient->n_stops < 2) /* becomes a solid */
break;
if (gradient->n_stops < sizeof (buf) / (sizeof (XFixed) + sizeof (XRenderColor)))
{
stops = (XFixed *) buf;
}
else
{
stops =
_cairo_malloc_ab (gradient->n_stops,
sizeof (XFixed) + sizeof (XRenderColor));
if (unlikely (stops == NULL))
return _cairo_error (CAIRO_STATUS_NO_MEMORY);
}
colors = (XRenderColor *) (stops + gradient->n_stops);
for (i = 0; i < gradient->n_stops; i++) {
stops[i] =
_cairo_fixed_16_16_from_double (gradient->stops[i].offset);
colors[i].red = gradient->stops[i].color.red_short;
colors[i].green = gradient->stops[i].color.green_short;
colors[i].blue = gradient->stops[i].color.blue_short;
colors[i].alpha = gradient->stops[i].color.alpha_short;
}
#if 0
/* For some weird reason the X server is sometimes getting
* CreateGradient requests with bad length. So far I've only seen
* XRenderCreateLinearGradient request with 4 stops sometime end up
* with length field matching 0 stops at the server side. I've
* looked at the libXrender code and I can't see anything that
* could cause this behavior. However, for some reason having a
* XSync call here seems to avoid the issue so I'll keep it here
* until it's solved.
*/
XSync (dst->dpy, False);
#endif
if (pattern->type == CAIRO_PATTERN_TYPE_LINEAR) {
cairo_linear_pattern_t *linear = (cairo_linear_pattern_t *) pattern;
XLinearGradient grad;
cairo_fixed_t xdim, ydim;
xdim = linear->p2.x - linear->p1.x;
ydim = linear->p2.y - linear->p1.y;
/*
* Transform the matrix to avoid overflow when converting between
* cairo_fixed_t and pixman_fixed_t (without incurring performance
* loss when the transformation is unnecessary).
*
* XXX: Consider converting out-of-range co-ordinates and transforms.
* Having a function to compute the required transformation to
* "normalize" a given bounding box would be generally useful -
* cf linear patterns, gradient patterns, surface patterns...
*/
#define PIXMAN_MAX_INT ((pixman_fixed_1 >> 1) - pixman_fixed_e) /* need to ensure deltas also fit */
if (_cairo_fixed_integer_ceil (xdim) > PIXMAN_MAX_INT ||
_cairo_fixed_integer_ceil (ydim) > PIXMAN_MAX_INT)
{
double sf;
if (xdim > ydim)
sf = PIXMAN_MAX_INT / _cairo_fixed_to_double (xdim);
else
sf = PIXMAN_MAX_INT / _cairo_fixed_to_double (ydim);
grad.p1.x = _cairo_fixed_16_16_from_double (_cairo_fixed_to_double (linear->p1.x) * sf);
grad.p1.y = _cairo_fixed_16_16_from_double (_cairo_fixed_to_double (linear->p1.y) * sf);
grad.p2.x = _cairo_fixed_16_16_from_double (_cairo_fixed_to_double (linear->p2.x) * sf);
grad.p2.y = _cairo_fixed_16_16_from_double (_cairo_fixed_to_double (linear->p2.y) * sf);
cairo_matrix_scale (&matrix, sf, sf);
}
else
{
grad.p1.x = _cairo_fixed_to_16_16 (linear->p1.x);
grad.p1.y = _cairo_fixed_to_16_16 (linear->p1.y);
grad.p2.x = _cairo_fixed_to_16_16 (linear->p2.x);
grad.p2.y = _cairo_fixed_to_16_16 (linear->p2.y);
}
picture = XRenderCreateLinearGradient (dst->dpy, &grad,
stops, colors,
gradient->n_stops);
} else {
cairo_radial_pattern_t *radial = (cairo_radial_pattern_t *) pattern;
XRadialGradient grad;
grad.inner.x = _cairo_fixed_to_16_16 (radial->c1.x);
grad.inner.y = _cairo_fixed_to_16_16 (radial->c1.y);
grad.inner.radius = _cairo_fixed_to_16_16 (radial->r1);
grad.outer.x = _cairo_fixed_to_16_16 (radial->c2.x);
grad.outer.y = _cairo_fixed_to_16_16 (radial->c2.y);
grad.outer.radius = _cairo_fixed_to_16_16 (radial->r2);
picture = XRenderCreateRadialGradient (dst->dpy, &grad,
stops, colors,
gradient->n_stops);
}
if (stops != (XFixed *) buf)
free (stops);
if (unlikely (picture == None))
return _cairo_error (CAIRO_STATUS_NO_MEMORY);
/* Wrap the remote Picture in an xlib surface. */
format = _cairo_xlib_display_get_xrender_format (dst->display,
CAIRO_FORMAT_ARGB32);
surface = (cairo_xlib_surface_t *)
_cairo_xlib_surface_create_internal (dst->dpy, None,
dst->screen, NULL,
format, 0, 0, 32);
if (unlikely (surface->base.status)) {
XRenderFreePicture (dst->dpy, picture);
return surface->base.status;
}
surface->src_picture = picture;
attributes->matrix = matrix;
attributes->extend = pattern->extend;
attributes->filter = CAIRO_FILTER_NEAREST;
attributes->x_offset = 0;
attributes->y_offset = 0;
*surface_out = surface;
return CAIRO_STATUS_SUCCESS;
}
default:
ASSERT_NOT_REACHED;
case CAIRO_PATTERN_TYPE_SOLID:
case CAIRO_PATTERN_TYPE_SURFACE:
break;
}
return _cairo_pattern_acquire_surface (pattern, &dst->base,
content,
x, y, width, height,
dst->buggy_pad_reflect ?
CAIRO_PATTERN_ACQUIRE_NO_REFLECT :
CAIRO_PATTERN_ACQUIRE_NONE,
(cairo_surface_t **) surface_out,
attributes);
}
static cairo_int_status_t
_cairo_xlib_surface_acquire_pattern_surfaces (cairo_xlib_surface_t *dst,
const cairo_pattern_t *src,
const cairo_pattern_t *mask,
cairo_content_t src_content,
int src_x,
int src_y,
int mask_x,
int mask_y,
unsigned int width,
unsigned int height,
cairo_xlib_surface_t **src_out,
cairo_xlib_surface_t **mask_out,
cairo_surface_attributes_t *src_attr,
cairo_surface_attributes_t *mask_attr)
{
if (! dst->buggy_gradients &&
(src->type == CAIRO_PATTERN_TYPE_LINEAR ||
src->type == CAIRO_PATTERN_TYPE_RADIAL ||
(mask && (mask->type == CAIRO_PATTERN_TYPE_LINEAR ||
mask->type == CAIRO_PATTERN_TYPE_RADIAL))))
{
cairo_int_status_t status;
status = _cairo_xlib_surface_acquire_pattern_surface (dst, src,
src_content,
src_x, src_y,
width, height,
src_out,
src_attr);
if (unlikely (status))
return status;
if (mask) {
status = _cairo_xlib_surface_acquire_pattern_surface (dst, mask,
CAIRO_CONTENT_ALPHA,
mask_x,
mask_y,
width,
height,
mask_out,
mask_attr);
if (unlikely (status)) {
_cairo_pattern_release_surface (src, &(*src_out)->base,
src_attr);
return status;
}
} else {
*mask_out = NULL;
}
return CAIRO_STATUS_SUCCESS;
}
return _cairo_pattern_acquire_surfaces (src, mask,
&dst->base,
src_content,
src_x, src_y,
mask_x, mask_y,
width, height,
dst->buggy_pad_reflect ?
CAIRO_PATTERN_ACQUIRE_NO_REFLECT :
CAIRO_PATTERN_ACQUIRE_NONE,
(cairo_surface_t **) src_out,
(cairo_surface_t **) mask_out,
src_attr, mask_attr);
}
static cairo_int_status_t
_cairo_xlib_surface_composite (cairo_operator_t op,
const cairo_pattern_t *src_pattern,
@ -1930,18 +2179,15 @@ _cairo_xlib_surface_composite (cairo_operator_t op,
_cairo_xlib_display_notify (dst->display);
status = _cairo_pattern_acquire_surfaces (src_pattern, mask_pattern,
&dst->base,
src_content,
src_x, src_y,
mask_x, mask_y,
width, height,
dst->buggy_pad_reflect ?
CAIRO_PATTERN_ACQUIRE_NO_REFLECT :
CAIRO_PATTERN_ACQUIRE_NONE,
(cairo_surface_t **) &src,
(cairo_surface_t **) &mask,
&src_attr, &mask_attr);
status =
_cairo_xlib_surface_acquire_pattern_surfaces (dst,
src_pattern, mask_pattern,
src_content,
src_x, src_y,
mask_x, mask_y,
width, height,
&src, &mask,
&src_attr, &mask_attr);
if (unlikely (status))
return status;
@ -2083,6 +2329,7 @@ _cairo_xlib_surface_composite (cairo_operator_t op,
return status;
}
/* XXX move this out of core and into acquire_pattern_surface() above. */
static cairo_int_status_t
_cairo_xlib_surface_solid_fill_rectangles (cairo_xlib_surface_t *surface,
const cairo_color_t *color,
@ -2359,14 +2606,12 @@ _cairo_xlib_surface_composite_trapezoids (cairo_operator_t op,
if (operation == DO_UNSUPPORTED)
return UNSUPPORTED ("unsupported operation");
status = _cairo_pattern_acquire_surface (pattern, &dst->base,
CAIRO_CONTENT_COLOR_ALPHA,
src_x, src_y, width, height,
dst->buggy_pad_reflect ?
CAIRO_PATTERN_ACQUIRE_NO_REFLECT :
CAIRO_PATTERN_ACQUIRE_NONE,
(cairo_surface_t **) &src,
&attributes);
status = _cairo_xlib_surface_acquire_pattern_surface (dst,
pattern,
CAIRO_CONTENT_COLOR_ALPHA,
src_x, src_y,
width, height,
&src, &attributes);
if (unlikely (status))
return status;
@ -2752,10 +2997,15 @@ _cairo_xlib_surface_create_internal (Display *dpy,
/* so we can use the XTile fallback */
surface->buggy_repeat = TRUE;
}
surface->buggy_pad_reflect = screen_info->display->buggy_pad_reflect;
if (! CAIRO_SURFACE_RENDER_HAS_EXTENDED_REPEAT (surface))
surface->buggy_pad_reflect = TRUE;
surface->buggy_gradients = screen_info->display->buggy_gradients;
if (! CAIRO_SURFACE_RENDER_HAS_GRADIENTS (surface))
surface->buggy_gradients = TRUE;
surface->dst_picture = None;
surface->src_picture = None;
@ -4212,15 +4462,13 @@ _cairo_xlib_surface_show_glyphs (void *abstract_dst,
}
}
status = _cairo_pattern_acquire_surface (src_pattern, &dst->base,
CAIRO_CONTENT_COLOR_ALPHA,
glyph_extents.x, glyph_extents.y,
glyph_extents.width, glyph_extents.height,
dst->buggy_pad_reflect ?
CAIRO_PATTERN_ACQUIRE_NO_REFLECT :
CAIRO_PATTERN_ACQUIRE_NONE,
(cairo_surface_t **) &src,
&attributes);
status = _cairo_xlib_surface_acquire_pattern_surface (dst, src_pattern,
dst->base.content,
glyph_extents.x,
glyph_extents.y,
glyph_extents.width,
glyph_extents.height,
&src, &attributes);
if (unlikely (status))
goto BAIL0;
}