1 /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 * This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "gfxXlibNativeRenderer.h"
8 #include "gfxXlibSurface.h"
9 #include "gfxImageSurface.h"
10 #include "gfxContext.h"
11 #include "gfxPlatform.h"
12 #include "gfxAlphaRecovery.h"
13 #include "cairo-xlib.h"
14 #include "cairo-xlib-xrender.h"
15 #include "mozilla/gfx/BorrowedContext.h"
16 #include "gfx2DGlue.h"
18 using namespace mozilla
;
19 using namespace mozilla::gfx
;
23 #define NATIVE_DRAWING_NOTE(m) fprintf(stderr, m)
25 #define NATIVE_DRAWING_NOTE(m) do {} while (0)
28 /* We have four basic strategies available:
30 1) 'direct': If the target is an xlib surface, and other conditions are met,
31 we can pass the underlying drawable directly to the callback.
33 2) 'simple': If the drawing is opaque, or we can draw to a surface with an
34 alpha channel, then we can create a temporary xlib surface, pass its
35 underlying drawable to the callback, and composite the result using
38 3) 'copy-background': If the drawing is not opaque but the target is
39 opaque, and we can draw to a surface with format such that pixel
40 conversion to and from the target format is exact, we can create a
41 temporary xlib surface, copy the background from the target, pass the
42 underlying drawable to the callback, and copy back to the target.
44 This strategy is not used if the pixel format conversion is not exact,
45 because that would mean that drawing intended to be very transparent
46 messes with other content.
48 The strategy is prefered over simple for non-opaque drawing and opaque
49 targets on the same screen as compositing without alpha is a simpler
52 4) 'alpha-extraction': create a temporary xlib surface, fill with black,
53 pass its underlying drawable to the callback, copy the results to a
54 cairo image surface, repeat with a white background, update the on-black
55 image alpha values by comparing the two images, then paint the on-black
58 Sure would be nice to have an X extension or GL to do this for us on the
63 _convert_coord_to_int (double coord
, int32_t *v
)
66 /* XXX allow some tolerance here? */
71 _get_rectangular_clip (cairo_t
*cr
,
72 const nsIntRect
& bounds
,
74 nsIntRect
*rectangles
, int max_rectangles
,
77 cairo_rectangle_list_t
*cliplist
;
78 cairo_rectangle_t
*clips
;
82 cliplist
= cairo_copy_clip_rectangle_list (cr
);
83 if (cliplist
->status
!= CAIRO_STATUS_SUCCESS
) {
85 NATIVE_DRAWING_NOTE("FALLBACK: non-rectangular clip");
89 /* the clip is always in surface backend coordinates (i.e. native backend coords) */
90 clips
= cliplist
->rectangles
;
92 for (i
= 0; i
< cliplist
->num_rectangles
; ++i
) {
95 if (!_convert_coord_to_int (clips
[i
].x
, &rect
.x
) ||
96 !_convert_coord_to_int (clips
[i
].y
, &rect
.y
) ||
97 !_convert_coord_to_int (clips
[i
].width
, &rect
.width
) ||
98 !_convert_coord_to_int (clips
[i
].height
, &rect
.height
))
101 NATIVE_DRAWING_NOTE("FALLBACK: non-integer clip");
105 if (rect
.IsEqualInterior(bounds
)) {
106 /* the bounds are entirely inside the clip region so we don't need to clip. */
111 NS_ASSERTION(bounds
.Contains(rect
),
112 "Was expecting to be clipped to bounds");
114 if (i
>= max_rectangles
) {
116 NATIVE_DRAWING_NOTE("FALLBACK: unsupported clip rectangle count");
120 rectangles
[i
] = rect
;
124 *num_rectangles
= cliplist
->num_rectangles
;
127 cairo_rectangle_list_destroy (cliplist
);
132 #define MAX_STATIC_CLIP_RECTANGLES 50
135 * Try the direct path.
136 * @return True if we took the direct path
139 gfxXlibNativeRenderer::DrawDirect(gfxContext
*ctx
, nsIntSize size
,
141 Screen
*screen
, Visual
*visual
)
143 // We need to actually borrow the context because we want to read out the
145 BorrowedCairoContext
borrowed(ctx
->GetDrawTarget());
146 if (!borrowed
.mCairo
) {
150 bool direct
= DrawCairo(borrowed
.mCairo
, size
, flags
, screen
, visual
);
157 gfxXlibNativeRenderer::DrawCairo(cairo_t
* cr
, nsIntSize size
,
159 Screen
*screen
, Visual
*visual
)
161 /* Check that the target surface is an xlib surface. */
162 cairo_surface_t
*target
= cairo_get_group_target (cr
);
163 if (cairo_surface_get_type (target
) != CAIRO_SURFACE_TYPE_XLIB
) {
164 NATIVE_DRAWING_NOTE("FALLBACK: non-X surface");
168 cairo_matrix_t matrix
;
169 cairo_get_matrix (cr
, &matrix
);
170 double device_offset_x
, device_offset_y
;
171 cairo_surface_get_device_offset (target
, &device_offset_x
, &device_offset_y
);
173 /* Draw() checked that the matrix contained only a very-close-to-integer
174 translation. Here (and in several other places and thebes) device
175 offsets are assumed to be integer. */
176 NS_ASSERTION(int32_t(device_offset_x
) == device_offset_x
&&
177 int32_t(device_offset_y
) == device_offset_y
,
178 "Expected integer device offsets");
179 nsIntPoint
offset(NS_lroundf(matrix
.x0
+ device_offset_x
),
180 NS_lroundf(matrix
.y0
+ device_offset_y
));
182 int max_rectangles
= 0;
183 if (flags
& DRAW_SUPPORTS_CLIP_RECT
) {
186 if (flags
& DRAW_SUPPORTS_CLIP_LIST
) {
187 max_rectangles
= MAX_STATIC_CLIP_RECTANGLES
;
190 /* The client won't draw outside the surface so consider this when
191 analysing clip rectangles. */
192 nsIntRect
bounds(offset
, size
);
193 bounds
.IntersectRect(bounds
,
195 cairo_xlib_surface_get_width(target
),
196 cairo_xlib_surface_get_height(target
)));
198 bool needs_clip
= true;
199 nsIntRect rectangles
[MAX_STATIC_CLIP_RECTANGLES
];
202 /* Check that the clip is rectangular and aligned on unit boundaries. */
203 /* Temporarily set the matrix for _get_rectangular_clip. It's basically
204 the identity matrix, but we must adjust for the fact that our
205 offset-rect is in device coordinates. */
206 cairo_identity_matrix (cr
);
207 cairo_translate (cr
, -device_offset_x
, -device_offset_y
);
208 bool have_rectangular_clip
=
209 _get_rectangular_clip (cr
, bounds
, &needs_clip
,
210 rectangles
, max_rectangles
, &rect_count
);
211 cairo_set_matrix (cr
, &matrix
);
212 if (!have_rectangular_clip
)
215 /* Stop now if everything is clipped out */
216 if (needs_clip
&& rect_count
== 0)
219 /* Check that the screen is supported.
220 Visuals belong to screens, so, if alternate visuals are not supported,
221 then alternate screens cannot be supported. */
222 bool supports_alternate_visual
=
223 (flags
& DRAW_SUPPORTS_ALTERNATE_VISUAL
) != 0;
224 bool supports_alternate_screen
= supports_alternate_visual
&&
225 (flags
& DRAW_SUPPORTS_ALTERNATE_SCREEN
);
226 if (!supports_alternate_screen
&&
227 cairo_xlib_surface_get_screen (target
) != screen
) {
228 NATIVE_DRAWING_NOTE("FALLBACK: non-default screen");
232 /* Check that there is a visual */
233 Visual
*target_visual
= cairo_xlib_surface_get_visual (target
);
234 if (!target_visual
) {
235 NATIVE_DRAWING_NOTE("FALLBACK: no Visual for surface");
238 /* Check that the visual is supported */
239 if (!supports_alternate_visual
&& target_visual
!= visual
) {
240 // Only the format of the visual is important (not the GLX properties)
241 // for Xlib or XRender drawing.
242 XRenderPictFormat
*target_format
=
243 cairo_xlib_surface_get_xrender_format (target
);
244 if (!target_format
||
246 XRenderFindVisualFormat (DisplayOfScreen(screen
), visual
))) {
247 NATIVE_DRAWING_NOTE("FALLBACK: unsupported Visual");
252 /* we're good to go! */
253 NATIVE_DRAWING_NOTE("TAKING FAST PATH\n");
254 cairo_surface_flush (target
);
255 nsresult rv
= DrawWithXlib(target
,
257 needs_clip
? rect_count
: 0);
258 if (NS_SUCCEEDED(rv
)) {
259 cairo_surface_mark_dirty (target
);
266 VisualHasAlpha(Screen
*screen
, Visual
*visual
) {
267 // There may be some other visuals format with alpha but usually this is
268 // the only one we care about.
269 return visual
->c_class
== TrueColor
&&
270 visual
->bits_per_rgb
== 8 &&
271 visual
->red_mask
== 0xff0000 &&
272 visual
->green_mask
== 0xff00 &&
273 visual
->blue_mask
== 0xff &&
274 gfxXlibSurface::DepthOfVisual(screen
, visual
) == 32;
277 // Returns whether pixel conversion between visual and format is exact (in
280 FormatConversionIsExact(Screen
*screen
, Visual
*visual
, XRenderPictFormat
*format
) {
282 visual
->c_class
!= TrueColor
||
283 format
->type
!= PictTypeDirect
||
284 gfxXlibSurface::DepthOfVisual(screen
, visual
) != format
->depth
)
287 XRenderPictFormat
*visualFormat
=
288 XRenderFindVisualFormat(DisplayOfScreen(screen
), visual
);
290 if (visualFormat
->type
!= PictTypeDirect
)
293 const XRenderDirectFormat
& a
= visualFormat
->direct
;
294 const XRenderDirectFormat
& b
= format
->direct
;
295 return a
.redMask
== b
.redMask
&&
296 a
.greenMask
== b
.greenMask
&&
297 a
.blueMask
== b
.blueMask
;
300 // The 3 non-direct strategies described above.
301 // The surface format and strategy are inter-dependent.
308 static cairo_surface_t
*
309 CreateTempXlibSurface (cairo_surface_t
* cairoTarget
,
310 DrawTarget
* drawTarget
,
312 bool canDrawOverBackground
,
313 uint32_t flags
, Screen
*screen
, Visual
*visual
,
314 DrawingMethod
*method
)
316 NS_ASSERTION(cairoTarget
|| drawTarget
, "Must have some type");
318 bool drawIsOpaque
= (flags
& gfxXlibNativeRenderer::DRAW_IS_OPAQUE
) != 0;
319 bool supportsAlternateVisual
=
320 (flags
& gfxXlibNativeRenderer::DRAW_SUPPORTS_ALTERNATE_VISUAL
) != 0;
321 bool supportsAlternateScreen
= supportsAlternateVisual
&&
322 (flags
& gfxXlibNativeRenderer::DRAW_SUPPORTS_ALTERNATE_SCREEN
);
324 cairo_surface_type_t cairoTargetType
=
325 cairoTarget
? cairo_surface_get_type (cairoTarget
) : (cairo_surface_type_t
)0xFF;
327 Screen
*target_screen
= cairoTargetType
== CAIRO_SURFACE_TYPE_XLIB
?
328 cairo_xlib_surface_get_screen (cairoTarget
) : screen
;
330 // When the background has an alpha channel, we need to draw with an alpha
331 // channel anyway, so there is no need to copy the background. If
332 // doCopyBackground is set here, we'll also need to check below that the
333 // background can copied without any loss in format conversions.
334 bool doCopyBackground
= !drawIsOpaque
&& canDrawOverBackground
&&
335 cairoTarget
&& cairo_surface_get_content (cairoTarget
) == CAIRO_CONTENT_COLOR
;
337 if (supportsAlternateScreen
&& screen
!= target_screen
&& drawIsOpaque
) {
338 // Prefer a visual on the target screen.
339 // (If !drawIsOpaque, we'll need doCopyBackground or an alpha channel.)
340 visual
= DefaultVisualOfScreen(target_screen
);
341 screen
= target_screen
;
343 } else if (doCopyBackground
|| (supportsAlternateVisual
&& drawIsOpaque
)) {
344 // Analyse the pixel formats either to check whether we can
345 // doCopyBackground or to see if we can find a better visual for
347 Visual
*target_visual
= nullptr;
348 XRenderPictFormat
*target_format
= nullptr;
349 if (cairoTargetType
== CAIRO_SURFACE_TYPE_XLIB
) {
350 target_visual
= cairo_xlib_surface_get_visual (cairoTarget
);
351 target_format
= cairo_xlib_surface_get_xrender_format (cairoTarget
);
352 } else if (cairoTargetType
== CAIRO_SURFACE_TYPE_IMAGE
|| drawTarget
) {
353 gfxImageFormat imageFormat
=
354 drawTarget
? SurfaceFormatToImageFormat(drawTarget
->GetFormat()) :
355 (gfxImageFormat
)cairo_image_surface_get_format(cairoTarget
);
356 target_visual
= gfxXlibSurface::FindVisual(screen
, imageFormat
);
357 Display
*dpy
= DisplayOfScreen(screen
);
359 target_format
= XRenderFindVisualFormat(dpy
, target_visual
);
362 gfxXlibSurface::FindRenderFormat(dpy
, imageFormat
);
366 if (supportsAlternateVisual
&&
367 (supportsAlternateScreen
|| screen
== target_screen
)) {
369 visual
= target_visual
;
370 screen
= target_screen
;
373 // Could try harder to match formats across screens for background
374 // copying when !supportsAlternateScreen, if we cared. Preferably
375 // we'll find a visual below with an alpha channel anyway; if so, the
376 // background won't need to be copied.
378 if (doCopyBackground
&& visual
!= target_visual
&&
379 !FormatConversionIsExact(screen
, visual
, target_format
)) {
380 doCopyBackground
= false;
384 if (supportsAlternateVisual
&& !drawIsOpaque
&&
385 (screen
!= target_screen
||
386 !(doCopyBackground
|| VisualHasAlpha(screen
, visual
)))) {
387 // Try to find a visual with an alpha channel.
388 Screen
*visualScreen
=
389 supportsAlternateScreen
? target_screen
: screen
;
391 gfxXlibSurface::FindVisual(visualScreen
,
392 gfxImageFormat::ARGB32
);
395 screen
= visualScreen
;
396 } else if (!doCopyBackground
&&
397 gfxXlibSurface::DepthOfVisual(screen
, visual
) != 24) {
398 // Will need to do alpha extraction; prefer a 24-bit visual.
399 // No advantage in using the target screen.
400 Visual
*rgb24Visual
=
401 gfxXlibSurface::FindVisual(screen
,
402 gfxImageFormat::RGB24
);
404 visual
= rgb24Visual
;
410 (screen
== target_screen
&& cairoTargetType
== CAIRO_SURFACE_TYPE_XLIB
) ?
411 cairo_xlib_surface_get_drawable (cairoTarget
) : RootWindowOfScreen(screen
);
413 cairo_surface_t
*surface
=
414 gfxXlibSurface::CreateCairoSurface(screen
, visual
,
415 gfxIntSize(size
.width
, size
.height
),
422 cairo_surface_get_content(surface
) == CAIRO_CONTENT_COLOR_ALPHA
) {
423 NATIVE_DRAWING_NOTE(drawIsOpaque
?
424 ", SIMPLE OPAQUE\n" : ", SIMPLE WITH ALPHA");
426 } else if (doCopyBackground
) {
427 NATIVE_DRAWING_NOTE(", COPY BACKGROUND\n");
428 *method
= eCopyBackground
;
430 NATIVE_DRAWING_NOTE(", SLOW ALPHA EXTRACTION\n");
431 *method
= eAlphaExtraction
;
438 gfxXlibNativeRenderer::DrawOntoTempSurface(cairo_surface_t
*tempXlibSurface
,
441 cairo_surface_flush(tempXlibSurface
);
442 /* no clipping is needed because the callback can't draw outside the native
444 nsresult rv
= DrawWithXlib(tempXlibSurface
, offset
, nullptr, 0);
445 cairo_surface_mark_dirty(tempXlibSurface
);
446 return NS_SUCCEEDED(rv
);
449 static already_AddRefed
<gfxImageSurface
>
450 CopyXlibSurfaceToImage(cairo_surface_t
*tempXlibSurface
,
452 gfxImageFormat format
)
454 nsRefPtr
<gfxImageSurface
> result
= new gfxImageSurface(size
, format
);
456 cairo_t
* copyCtx
= cairo_create(result
->CairoSurface());
457 cairo_set_source_surface(copyCtx
, tempXlibSurface
, 0, 0);
458 cairo_set_operator(copyCtx
, CAIRO_OPERATOR_SOURCE
);
459 cairo_paint(copyCtx
);
460 cairo_destroy(copyCtx
);
462 return result
.forget();
466 gfxXlibNativeRenderer::Draw(gfxContext
* ctx
, nsIntSize size
,
467 uint32_t flags
, Screen
*screen
, Visual
*visual
)
469 gfxMatrix matrix
= ctx
->CurrentMatrix();
471 // We can only draw direct or onto a copied background if pixels align and
472 // native drawing is compatible with the current operator. (The matrix is
473 // actually also pixel-exact for flips and right-angle rotations, which
474 // would permit copying the background but not drawing direct.)
475 bool matrixIsIntegerTranslation
= !matrix
.HasNonIntegerTranslation();
476 bool canDrawOverBackground
= matrixIsIntegerTranslation
&&
477 ctx
->CurrentOperator() == gfxContext::OPERATOR_OVER
;
479 // The padding of 0.5 for non-pixel-exact transformations used here is
480 // the same as what _cairo_pattern_analyze_filter uses.
481 const gfxFloat filterRadius
= 0.5;
482 gfxRect
affectedRect(0.0, 0.0, size
.width
, size
.height
);
483 if (!matrixIsIntegerTranslation
) {
484 // The filter footprint means that the affected rectangle is a
485 // little larger than the drawingRect;
486 affectedRect
.Inflate(filterRadius
);
488 NATIVE_DRAWING_NOTE("FALLBACK: matrix not integer translation");
489 } else if (!canDrawOverBackground
) {
490 NATIVE_DRAWING_NOTE("FALLBACK: unsupported operator");
493 // Clipping to the region affected by drawing allows us to consider only
494 // the portions of the clip region that will be affected by drawing.
497 gfxContextAutoSaveRestore
autoSR(ctx
);
498 ctx
->Clip(affectedRect
);
500 clipExtents
= ctx
->GetClipExtents();
501 if (clipExtents
.IsEmpty())
502 return; // nothing to do
504 if (canDrawOverBackground
&&
505 DrawDirect(ctx
, size
, flags
, screen
, visual
))
509 nsIntRect
drawingRect(nsIntPoint(0, 0), size
);
510 // Drawing need only be performed within the clip extents
511 // (and padding for the filter).
512 if (!matrixIsIntegerTranslation
) {
513 // The source surface may need to be a little larger than the clip
514 // extents due to the filter footprint.
515 clipExtents
.Inflate(filterRadius
);
517 clipExtents
.RoundOut();
519 nsIntRect
intExtents(int32_t(clipExtents
.X()),
520 int32_t(clipExtents
.Y()),
521 int32_t(clipExtents
.Width()),
522 int32_t(clipExtents
.Height()));
523 drawingRect
.IntersectRect(drawingRect
, intExtents
);
525 gfxPoint
offset(drawingRect
.x
, drawingRect
.y
);
527 DrawingMethod method
;
528 DrawTarget
* drawTarget
= ctx
->GetDrawTarget();
529 Matrix dtTransform
= drawTarget
->GetTransform();
530 gfxPoint deviceTranslation
= gfxPoint(dtTransform
._31
, dtTransform
._32
);
531 cairo_surface_t
* cairoTarget
= static_cast<cairo_surface_t
*>
532 (drawTarget
->GetNativeSurface(NativeSurfaceType::CAIRO_SURFACE
));
534 cairo_surface_t
* tempXlibSurface
=
535 CreateTempXlibSurface(cairoTarget
, drawTarget
, size
,
536 canDrawOverBackground
, flags
, screen
, visual
,
538 if (!tempXlibSurface
)
541 bool drawIsOpaque
= (flags
& DRAW_IS_OPAQUE
) != 0;
543 cairo_t
* tmpCtx
= cairo_create(tempXlibSurface
);
544 if (method
== eCopyBackground
) {
545 NS_ASSERTION(cairoTarget
, "eCopyBackground only used when there's a cairoTarget");
546 cairo_set_operator(tmpCtx
, CAIRO_OPERATOR_SOURCE
);
547 gfxPoint pt
= -(offset
+ deviceTranslation
);
548 cairo_set_source_surface(tmpCtx
, cairoTarget
, pt
.x
, pt
.y
);
549 // The copy from the tempXlibSurface to the target context should
550 // use operator SOURCE, but that would need a mask to bound the
551 // operation. Here we only copy opaque backgrounds so operator
552 // OVER will behave like SOURCE masked by the surface.
553 NS_ASSERTION(cairo_surface_get_content(tempXlibSurface
) == CAIRO_CONTENT_COLOR
,
554 "Don't copy background with a transparent surface");
556 cairo_set_operator(tmpCtx
, CAIRO_OPERATOR_CLEAR
);
559 cairo_destroy(tmpCtx
);
562 if (!DrawOntoTempSurface(tempXlibSurface
, -drawingRect
.TopLeft())) {
563 cairo_surface_destroy(tempXlibSurface
);
567 SurfaceFormat moz2DFormat
=
568 cairo_surface_get_content(tempXlibSurface
) == CAIRO_CONTENT_COLOR
?
569 SurfaceFormat::B8G8R8A8
: SurfaceFormat::B8G8R8X8
;
570 if (method
!= eAlphaExtraction
) {
572 NativeSurface native
;
573 native
.mFormat
= moz2DFormat
;
574 native
.mType
= NativeSurfaceType::CAIRO_SURFACE
;
575 native
.mSurface
= tempXlibSurface
;
576 native
.mSize
= ToIntSize(size
);
577 RefPtr
<SourceSurface
> sourceSurface
=
578 drawTarget
->CreateSourceSurfaceFromNativeSurface(native
);
580 drawTarget
->DrawSurface(sourceSurface
,
581 Rect(offset
.x
, offset
.y
, size
.width
, size
.height
),
582 Rect(0, 0, size
.width
, size
.height
));
585 nsRefPtr
<gfxASurface
> tmpSurf
= gfxASurface::Wrap(tempXlibSurface
);
586 ctx
->SetSource(tmpSurf
, offset
);
589 cairo_surface_destroy(tempXlibSurface
);
593 nsRefPtr
<gfxImageSurface
> blackImage
=
594 CopyXlibSurfaceToImage(tempXlibSurface
, size
, gfxImageFormat::ARGB32
);
596 cairo_t
* tmpCtx
= cairo_create(tempXlibSurface
);
597 cairo_set_source_rgba(tmpCtx
, 1.0, 1.0, 1.0, 1.0);
598 cairo_set_operator(tmpCtx
, CAIRO_OPERATOR_SOURCE
);
600 cairo_destroy(tmpCtx
);
601 DrawOntoTempSurface(tempXlibSurface
, -drawingRect
.TopLeft());
602 nsRefPtr
<gfxImageSurface
> whiteImage
=
603 CopyXlibSurfaceToImage(tempXlibSurface
, size
, gfxImageFormat::RGB24
);
605 if (blackImage
->CairoStatus() == CAIRO_STATUS_SUCCESS
&&
606 whiteImage
->CairoStatus() == CAIRO_STATUS_SUCCESS
) {
607 if (!gfxAlphaRecovery::RecoverAlpha(blackImage
, whiteImage
)) {
608 cairo_surface_destroy(tempXlibSurface
);
612 gfxASurface
* paintSurface
= blackImage
;
614 NativeSurface native
;
615 native
.mFormat
= moz2DFormat
;
616 native
.mType
= NativeSurfaceType::CAIRO_SURFACE
;
617 native
.mSurface
= paintSurface
->CairoSurface();
618 native
.mSize
= ToIntSize(size
);
619 RefPtr
<SourceSurface
> sourceSurface
=
620 drawTarget
->CreateSourceSurfaceFromNativeSurface(native
);
622 drawTarget
->DrawSurface(sourceSurface
,
623 Rect(offset
.x
, offset
.y
, size
.width
, size
.height
),
624 Rect(0, 0, size
.width
, size
.height
));
627 ctx
->SetSource(paintSurface
, offset
);
631 cairo_surface_destroy(tempXlibSurface
);