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 267920 85ca98c22bdc7216afbe6b20d50c129622f3c590
parent 267919 ff75c7e0b5d4a0bfc78bb8df12f25716b12b3481
child 267921 8376812621b2732e6014d7a90929ff81cb341eff
push id8157
push userjlund@mozilla.com
push dateMon, 29 Jun 2015 20:36:23 +0000
treeherdermozilla-aurora@d480e05bd276 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerslsalzman
bugs1168527
milestone41.0a1
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);
       }