Bug 1168527 - Replay clips into the system cairo on GTK3. r=lsalzman
authorAndrew Comminos <acomminos@mozilla.com>
Tue, 09 Jun 2015 13:46:09 -0400
changeset 248382 85ca98c22bdc7216afbe6b20d50c129622f3c590
parent 248381 ff75c7e0b5d4a0bfc78bb8df12f25716b12b3481
child 248383 8376812621b2732e6014d7a90929ff81cb341eff
push id28893
push userkwierso@gmail.com
push dateFri, 12 Jun 2015 00:02:58 +0000
treeherdermozilla-central@8cf9d3e497f9 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerslsalzman
bugs1168527
milestone41.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1168527 - Replay clips into the system cairo on GTK3. r=lsalzman
widget/gtk/nsNativeThemeGTK.cpp
--- a/widget/gtk/nsNativeThemeGTK.cpp
+++ b/widget/gtk/nsNativeThemeGTK.cpp
@@ -30,16 +30,17 @@
 #include <gdk/gdkprivate.h>
 #include <gtk/gtk.h>
 
 #include "gfxContext.h"
 #include "gfxPlatformGtk.h"
 #include "gfxGdkNativeRenderer.h"
 #include "mozilla/gfx/BorrowedContext.h"
 #include "mozilla/gfx/HelpersCairo.h"
+#include "mozilla/gfx/PathHelpers.h"
 
 #ifdef MOZ_X11
 #  ifdef CAIRO_HAS_XLIB_SURFACE
 #    include "cairo-xlib.h"
 #  endif
 #  ifdef CAIRO_HAS_XLIB_XRENDER_SURFACE
 #    include "cairo-xlib-xrender.h"
 #  endif
@@ -710,110 +711,191 @@ ThemeRenderer::DrawWithGDK(GdkDrawable *
   
   NS_ASSERTION(numClipRects == 0, "We don't support clipping!!!");
   moz_gtk_widget_paint(mGTKWidgetType, drawable, &gdk_rect, &gdk_clip,
                        &mState, mFlags, mDirection);
 
   return NS_OK;
 }
 #else
+class SystemCairoClipper : public ClipExporter {
+public:
+  SystemCairoClipper(cairo_t* aContext) : mContext(aContext)
+  {
+  }
+
+  void
+  BeginClip(const Matrix& aTransform) override
+  {
+    cairo_matrix_t mat;
+    GfxMatrixToCairoMatrix(aTransform, mat);
+    cairo_set_matrix(mContext, &mat);
+
+    cairo_new_path(mContext);
+  }
+
+  void
+  MoveTo(const Point &aPoint) override
+  {
+    cairo_move_to(mContext, aPoint.x, aPoint.y);
+    mCurrentPoint = aPoint;
+  }
+
+  void
+  LineTo(const Point &aPoint) override
+  {
+    cairo_line_to(mContext, aPoint.x, aPoint.y);
+    mCurrentPoint = aPoint;
+  }
+
+  void
+  BezierTo(const Point &aCP1, const Point &aCP2, const Point &aCP3) override
+  {
+    cairo_curve_to(mContext, aCP1.x, aCP1.y, aCP2.x, aCP2.y, aCP3.x, aCP3.y);
+    mCurrentPoint = aCP3;
+  }
+
+  void
+  QuadraticBezierTo(const Point &aCP1, const Point &aCP2) override
+  {
+    Point CP0 = CurrentPoint();
+    Point CP1 = (CP0 + aCP1 * 2.0) / 3.0;
+    Point CP2 = (aCP2 + aCP1 * 2.0) / 3.0;
+    Point CP3 = aCP2;
+    cairo_curve_to(mContext, CP1.x, CP1.y, CP2.x, CP2.y, CP3.x, CP3.y);
+    mCurrentPoint = aCP2;
+  }
+
+  void
+  Arc(const Point &aOrigin, float aRadius, float aStartAngle, float aEndAngle,
+      bool aAntiClockwise) override
+  {
+    ArcToBezier(this, aOrigin, Size(aRadius, aRadius), aStartAngle, aEndAngle,
+                aAntiClockwise);
+  }
+
+  void
+  Close() override
+  {
+    cairo_close_path(mContext);
+  }
+
+  void
+  EndClip() override
+  {
+    cairo_clip(mContext);
+  }
+
+  Point
+  CurrentPoint() const override
+  {
+    return mCurrentPoint;
+  }
+
+private:
+  cairo_t* mContext;
+  Point mCurrentPoint;
+};
+
 static void
 DrawThemeWithCairo(gfxContext* aContext, DrawTarget* aDrawTarget,
                    GtkWidgetState aState, GtkThemeWidgetType aGTKWidgetType,
                    gint aFlags, GtkTextDirection aDirection, gint aScaleFactor,
                    bool aSnapped, const Point& aDrawOrigin, const nsIntSize& aDrawSize,
                    GdkRectangle& aGDKRect, nsITheme::Transparency aTransparency)
 {
+  Point drawOffset;
+  Matrix transform;
+  if (!aSnapped) {
+    // If we are not snapped, we depend on the DT for translation.
+    drawOffset = aDrawOrigin;
+    transform = aDrawTarget->GetTransform().PreTranslate(aDrawOrigin);
+  } else {
+    // Otherwise, we only need to take the device offset into account.
+    drawOffset = aDrawOrigin - aContext->GetDeviceOffset();
+    transform = Matrix::Translation(drawOffset);
+  }
+
+  if (aScaleFactor != 1)
+    transform.PreScale(aScaleFactor, aScaleFactor);
+
+  cairo_matrix_t mat;
+  GfxMatrixToCairoMatrix(transform, mat);
+
 #ifndef MOZ_TREE_CAIRO
   // Directly use the Cairo draw target to render the widget if using system Cairo everywhere.
   BorrowedCairoContext borrow(aDrawTarget);
   if (borrow.mCairo) {
-    if (aSnapped) {
-      cairo_identity_matrix(borrow.mCairo);
-    }
-    if (aDrawOrigin != Point(0, 0)) {
-      cairo_translate(borrow.mCairo, aDrawOrigin.x, aDrawOrigin.y);
-    }
-    if (aScaleFactor != 1) {
-      cairo_scale(borrow.mCairo, aScaleFactor, aScaleFactor);
-    }
+    cairo_set_matrix(borrow.mCairo, &mat);
 
     moz_gtk_widget_paint(aGTKWidgetType, borrow.mCairo, &aGDKRect, &aState, aFlags, aDirection);
 
     borrow.Finish();
     return;
   }
 #endif
 
   // A direct Cairo draw target is not available, so we need to create a temporary one.
-  bool needClip = !aSnapped || aContext->HasComplexClip();
 #if defined(MOZ_X11) && defined(CAIRO_HAS_XLIB_SURFACE)
-  if (!needClip) {
-    // If using a Cairo xlib surface, then try to reuse it.
-    BorrowedXlibDrawable borrow(aDrawTarget);
-    if (borrow.GetDrawable()) {
-      nsIntSize size = aDrawTarget->GetSize();
-      cairo_surface_t* surf = nullptr;
-      // Check if the surface is using XRender.
+  // If using a Cairo xlib surface, then try to reuse it.
+  BorrowedXlibDrawable borrow(aDrawTarget);
+  if (borrow.GetDrawable()) {
+    nsIntSize size = aDrawTarget->GetSize();
+    cairo_surface_t* surf = nullptr;
+    // Check if the surface is using XRender.
 #ifdef CAIRO_HAS_XLIB_XRENDER_SURFACE
-      if (borrow.GetXRenderFormat()) {
-        surf = cairo_xlib_surface_create_with_xrender_format(
+    if (borrow.GetXRenderFormat()) {
+      surf = cairo_xlib_surface_create_with_xrender_format(
           borrow.GetDisplay(), borrow.GetDrawable(), borrow.GetScreen(),
           borrow.GetXRenderFormat(), size.width, size.height);
-      } else {
+    } else {
 #else
       if (! borrow.GetXRenderFormat()) {
 #endif
         surf = cairo_xlib_surface_create(
-          borrow.GetDisplay(), borrow.GetDrawable(), borrow.GetVisual(),
-          size.width, size.height);
+            borrow.GetDisplay(), borrow.GetDrawable(), borrow.GetVisual(),
+            size.width, size.height);
       }
       if (!NS_WARN_IF(!surf)) {
         cairo_t* cr = cairo_create(surf);
         if (!NS_WARN_IF(!cr)) {
-          cairo_new_path(cr);
-          cairo_rectangle(cr, aDrawOrigin.x, aDrawOrigin.y, aDrawSize.width, aDrawSize.height);
-          cairo_clip(cr);
-          if (aDrawOrigin != Point(0, 0)) {
-            cairo_translate(cr, aDrawOrigin.x, aDrawOrigin.y);
-          }
-          if (aScaleFactor != 1) {
-            cairo_scale(cr, aScaleFactor, aScaleFactor);
-          }
+          RefPtr<SystemCairoClipper> clipper = new SystemCairoClipper(cr);
+          aContext->ExportClip(*clipper);
+
+          cairo_set_matrix(cr, &mat);
 
           moz_gtk_widget_paint(aGTKWidgetType, cr, &aGDKRect, &aState, aFlags, aDirection);
 
           cairo_destroy(cr);
         }
         cairo_surface_destroy(surf);
       }
       borrow.Finish();
       return;
     }
-  }
 #endif
 
   // Check if the widget requires complex masking that must be composited.
   // Try to directly write to the draw target's pixels if possible.
   uint8_t* data;
   nsIntSize size;
   int32_t stride;
   SurfaceFormat format;
-  if (!needClip && aDrawTarget->LockBits(&data, &size, &stride, &format)) {
+  if (aDrawTarget->LockBits(&data, &size, &stride, &format)) {
     // Create a Cairo image surface context the device rectangle.
     cairo_surface_t* surf =
       cairo_image_surface_create_for_data(
-        data + int32_t(aDrawOrigin.y) * stride + int32_t(aDrawOrigin.x) * BytesPerPixel(format),
-        GfxFormatToCairoFormat(format), aDrawSize.width, aDrawSize.height, stride);
+        data, GfxFormatToCairoFormat(format), size.width, size.height, stride);
     if (!NS_WARN_IF(!surf)) {
       cairo_t* cr = cairo_create(surf);
       if (!NS_WARN_IF(!cr)) {
-        if (aScaleFactor != 1) {
-          cairo_scale(cr, aScaleFactor, aScaleFactor);
-        }
+        RefPtr<SystemCairoClipper> clipper = new SystemCairoClipper(cr);
+        aContext->ExportClip(*clipper);
+
+        cairo_set_matrix(cr, &mat);
 
         moz_gtk_widget_paint(aGTKWidgetType, cr, &aGDKRect, &aState, aFlags, aDirection);
 
         cairo_destroy(cr);
       }
       cairo_surface_destroy(surf);
     }
     aDrawTarget->ReleaseBits(data);
@@ -840,27 +922,27 @@ DrawThemeWithCairo(gfxContext* aContext,
           moz_gtk_widget_paint(aGTKWidgetType, cr, &aGDKRect, &aState, aFlags, aDirection);
         }
       }
 
       // Unmap the surface before using it as a source
       dataSurface->Unmap();
 
       if (cr) {
-        if (needClip || aTransparency != nsITheme::eOpaque) {
+        if (!aSnapped || aTransparency != nsITheme::eOpaque) {
           // The widget either needs to be masked or has transparency, so use the slower drawing path.
           aDrawTarget->DrawSurface(dataSurface,
-                                   Rect(aSnapped ? aDrawOrigin - aDrawTarget->GetTransform().GetTranslation() : aDrawOrigin,
+                                   Rect(aSnapped ? drawOffset - aDrawTarget->GetTransform().GetTranslation() : drawOffset,
                                         Size(aDrawSize)),
                                    Rect(0, 0, aDrawSize.width, aDrawSize.height));
         } else {
           // The widget is a simple opaque rectangle, so just copy it out.
           aDrawTarget->CopySurface(dataSurface,
                                    IntRect(0, 0, aDrawSize.width, aDrawSize.height),
-                                   TruncatedToInt(aDrawOrigin));
+                                   TruncatedToInt(drawOffset));
         }
 
         cairo_destroy(cr);
       }
 
       if (surf) {
         cairo_surface_destroy(surf);
       }