Bug 1277306 - Fallback to a cairo image surface when SHM and XRender are unavailable when drawing to a GDK window. r=lsalzman
authorAndrew Comminos <andrew@comminos.com>
Wed, 01 Jun 2016 14:52:36 -0400
changeset 341488 867a9bdc20665a9826cdd9399163f2b0a0f011bf
parent 341487 1ec243446e6d3afe0c60842b5aaddc699bf7a069
child 341489 13bcb1c7d3b3595f855609536e91040f97bded2c
push id1183
push userraliiev@mozilla.com
push dateMon, 05 Sep 2016 20:01:49 +0000
treeherdermozilla-release@3148731bed45 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerslsalzman
bugs1277306
milestone49.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 1277306 - Fallback to a cairo image surface when SHM and XRender are unavailable when drawing to a GDK window. r=lsalzman MozReview-Commit-ID: 4VPrplGM8uK
widget/gtk/mozgtk/mozgtk.c
widget/gtk/nsWindow.cpp
widget/gtk/nsWindow.h
--- a/widget/gtk/mozgtk/mozgtk.c
+++ b/widget/gtk/mozgtk/mozgtk.c
@@ -2,16 +2,17 @@
 #include "mozilla/Assertions.h"
 
 #define STUB(symbol) MOZ_EXPORT void symbol (void) { MOZ_CRASH(); }
 
 #ifdef COMMON_SYMBOLS
 STUB(gdk_atom_intern)
 STUB(gdk_atom_name)
 STUB(gdk_beep)
+STUB(gdk_cairo_create)
 STUB(gdk_color_free)
 STUB(gdk_cursor_new_for_display)
 STUB(gdk_cursor_new_from_name)
 STUB(gdk_cursor_new_from_pixbuf)
 STUB(gdk_display_close)
 STUB(gdk_display_get_default)
 STUB(gdk_display_get_default_screen)
 STUB(gdk_display_get_pointer)
--- a/widget/gtk/nsWindow.cpp
+++ b/widget/gtk/nsWindow.cpp
@@ -107,16 +107,17 @@ using namespace mozilla::widget;
 
 #include "gfxPlatformGtk.h"
 #include "gfxContext.h"
 #include "gfxImageSurface.h"
 #include "gfxUtils.h"
 #include "Layers.h"
 #include "GLContextProvider.h"
 #include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/HelpersCairo.h"
 #include "mozilla/layers/CompositorBridgeParent.h"
 
 #ifdef MOZ_X11
 #include "gfxXlibSurface.h"
 #endif
   
 #include "nsShmImage.h"
 
@@ -466,16 +467,18 @@ nsWindow::nsWindow()
     mTransparencyBitmap = nullptr;
 
     mTransparencyBitmapWidth  = 0;
     mTransparencyBitmapHeight = 0;
 
 #if GTK_CHECK_VERSION(3,4,0)
     mLastScrollEventTime = GDK_CURRENT_TIME;
 #endif
+
+    mFallbackSurface = nullptr;
 }
 
 nsWindow::~nsWindow()
 {
     LOG(("nsWindow::~nsWindow() [%p]\n", (void *)this));
 
     delete[] mTransparencyBitmap;
     mTransparencyBitmap = nullptr;
@@ -787,16 +790,21 @@ nsWindow::Destroy(void)
     }
 
 #ifdef ACCESSIBILITY
      if (mRootAccessible) {
          mRootAccessible = nullptr;
      }
 #endif
 
+    if (mFallbackSurface) {
+        cairo_surface_destroy(mFallbackSurface);
+        mFallbackSurface = nullptr;
+    }
+
     // Save until last because OnDestroy() may cause us to be deleted.
     OnDestroy();
 
     return NS_OK;
 }
 
 nsIWidget *
 nsWindow::GetParent(void)
@@ -2235,17 +2243,17 @@ nsWindow::OnExposeEvent(cairo_t *cr)
         if (!listener)
             return TRUE;
 
         listener->DidPaintWindow();
         return TRUE;
     }
 
     BufferMode layerBuffering = BufferMode::BUFFERED;
-    RefPtr<DrawTarget> dt = GetDrawTarget(region, &layerBuffering);
+    RefPtr<DrawTarget> dt = StartRemoteDrawingInRegion(region, &layerBuffering);
     if (!dt || !dt->IsValid()) {
         return FALSE;
     }
     RefPtr<gfxContext> ctx;
     IntRect boundsRect = region.GetBounds().ToUnknownRect();
     IntPoint offset(0, 0);
     if (dt->GetSize() == boundsRect.Size()) {
       offset = boundsRect.TopLeft();
@@ -2330,23 +2338,20 @@ nsWindow::OnExposeEvent(cairo_t *cr)
                                 DrawOptions(1.0f, CompositionOp::OP_SOURCE));
             }
         }
     }
 
     ctx = nullptr;
     dt->PopClip();
 
-#  ifdef MOZ_HAVE_SHMIMAGE
-    if (mBackShmImage && MOZ_LIKELY(!mIsDestroyed)) {
-      mBackShmImage->Put(region);
-    }
-#  endif  // MOZ_HAVE_SHMIMAGE
 #endif // MOZ_X11
 
+    EndRemoteDrawingInRegion(dt, region);
+
     listener->DidPaintWindow();
 
     // Synchronously flush any new dirty areas
 #if (MOZ_WIDGET_GTK == 2)
     GdkRegion* dirtyArea = gdk_window_get_update_area(mGdkWindow);
 #else
     cairo_region_t* dirtyArea = gdk_window_get_update_area(mGdkWindow);
 #endif
@@ -6504,65 +6509,134 @@ nsWindow::GetDrawTarget(const LayoutDevi
 {
   if (!mGdkWindow || aRegion.IsEmpty()) {
     return nullptr;
   }
 
   RefPtr<DrawTarget> dt;
 
 #ifdef MOZ_X11
+  bool useXRender = false;
+#ifdef MOZ_WIDGET_GTK
+  useXRender = gfxPlatformGtk::GetPlatform()->UseXRender();
+#endif
+
+  if (useXRender) {
+    LayoutDeviceIntRect bounds = aRegion.GetBounds();
+    LayoutDeviceIntSize size(bounds.XMost(), bounds.YMost());
+    RefPtr<gfxXlibSurface> surf = new gfxXlibSurface(mXDisplay, mXWindow, mXVisual, size.ToUnknownSize());
+    if (!surf->CairoStatus()) {
+      dt = gfxPlatform::GetPlatform()->CreateDrawTargetForSurface(surf.get(), surf->GetSize());
+      *aBufferMode = BufferMode::BUFFERED;
+    }
+  }
+
 #  ifdef MOZ_HAVE_SHMIMAGE
-#    ifdef MOZ_WIDGET_GTK
-  if (!gfxPlatformGtk::GetPlatform()->UseXRender())
-#    endif
-  if (nsShmImage::UseShm()) {
+  if (!dt && nsShmImage::UseShm()) {
     mBackShmImage.swap(mFrontShmImage);
     if (!mBackShmImage) {
       mBackShmImage = new nsShmImage(mXDisplay, mXWindow, mXVisual, mXDepth);
     }
     dt = mBackShmImage->CreateDrawTarget(aRegion);
     *aBufferMode = BufferMode::BUFFER_NONE;
     if (!dt) {
       mBackShmImage = nullptr;
     }
   }
 #  endif  // MOZ_HAVE_SHMIMAGE
+#endif // MOZ_X11
+
+  // If MIT-SHM and XRender are unavailable, buffer to an image surface.
   if (!dt) {
-    LayoutDeviceIntRect bounds = aRegion.GetBounds();
-    LayoutDeviceIntSize size(bounds.XMost(), bounds.YMost());
-    RefPtr<gfxXlibSurface> surf = new gfxXlibSurface(mXDisplay, mXWindow, mXVisual, size.ToUnknownSize());
-    if (!surf->CairoStatus()) {
-      dt = gfxPlatform::GetPlatform()->CreateDrawTargetForSurface(surf.get(), surf->GetSize());
-      *aBufferMode = BufferMode::BUFFERED;
-    }
+    IntRect bounds = aRegion.GetBounds().ToUnknownRect();
+    IntSize size(bounds.XMost(), bounds.YMost());
+
+    // Recreate the fallback surface if there is unsufficient space to render.
+    if (!mFallbackSurface ||
+        cairo_image_surface_get_width(mFallbackSurface) < size.width ||
+        cairo_image_surface_get_height(mFallbackSurface) < size.height)
+    {
+      if (mFallbackSurface)
+        cairo_surface_destroy(mFallbackSurface);
+
+      GdkScreen* screen = gdk_screen_get_default();
+      bool argb = gdk_window_get_visual(mGdkWindow) ==
+                  gdk_screen_get_rgba_visual(screen);
+      cairo_format_t cairo_format = argb ? CAIRO_FORMAT_ARGB32
+                                         : CAIRO_FORMAT_RGB24;
+      mFallbackSurface = cairo_image_surface_create(cairo_format,
+                                                    bounds.XMost(),
+                                                    bounds.YMost());
+
+      // Set the appropriate device scale so that our surface can be used
+      // as a source without transforming to the GDK window's scale.
+      static auto sCairoSurfaceSetDeviceScale =
+          (void (*)(cairo_surface_t*, double, double))
+          dlsym(RTLD_DEFAULT, "cairo_surface_set_device_scale");
+      if (sCairoSurfaceSetDeviceScale) {
+        gint scale = GdkScaleFactor();
+        sCairoSurfaceSetDeviceScale(mFallbackSurface, scale, scale);
+      }
+
+      cairo_surface_flush(mFallbackSurface);
+    }
+
+    SurfaceFormat format = CairoFormatToGfxFormat(
+            cairo_image_surface_get_format(mFallbackSurface));
+    dt = gfxPlatform::GetPlatform()->CreateDrawTargetForData(
+            cairo_image_surface_get_data(mFallbackSurface)
+            + bounds.x * BytesPerPixel(format)
+            + bounds.y * cairo_image_surface_get_stride(mFallbackSurface),
+            bounds.Size(),
+            cairo_image_surface_get_stride(mFallbackSurface),
+            format);
+    *aBufferMode = BufferMode::BUFFER_NONE;
   }
-#endif // MOZ_X11
 
   return dt.forget();
 }
 
 already_AddRefed<DrawTarget>
 nsWindow::StartRemoteDrawingInRegion(LayoutDeviceIntRegion& aInvalidRegion, BufferMode* aBufferMode)
 {
   return GetDrawTarget(aInvalidRegion, aBufferMode);
 }
 
 void
 nsWindow::EndRemoteDrawingInRegion(DrawTarget* aDrawTarget,
                                    LayoutDeviceIntRegion& aInvalidRegion)
 {
 #ifdef MOZ_X11
 #  ifdef MOZ_HAVE_SHMIMAGE
-  if (!mGdkWindow || !mBackShmImage) {
+  if (!mGdkWindow) {
     return;
   }
 
-  mBackShmImage->Put(aInvalidRegion);
+  if (mBackShmImage) {
+    mBackShmImage->Put(aInvalidRegion);
+  }
 #  endif // MOZ_HAVE_SHMIMAGE
 #endif // MOZ_X11
+
+  if (mFallbackSurface) {
+    cairo_t* cr = gdk_cairo_create(mGdkWindow);
+    cairo_surface_mark_dirty(mFallbackSurface);
+    cairo_set_source_surface(cr, mFallbackSurface, 0, 0);
+
+    for (auto iter = aInvalidRegion.RectIter(); !iter.Done(); iter.Next()) {
+      // Transform our path into the window's coordinate system.
+      const IntRectTyped<LayoutDevicePixel> & r = iter.Get();
+      cairo_rectangle(cr, DevicePixelsToGdkCoordRoundDown(r.x),
+                          DevicePixelsToGdkCoordRoundDown(r.y),
+                          DevicePixelsToGdkCoordRoundUp(r.width),
+                          DevicePixelsToGdkCoordRoundUp(r.height));
+    }
+    cairo_fill(cr);
+    cairo_destroy(cr);
+  }
 }
 
 // Code shared begin BeginMoveDrag and BeginResizeDrag
 bool
 nsWindow::GetDragInfo(WidgetMouseEvent* aMouseEvent,
                       GdkWindow** aWindow, gint* aButton,
                       gint* aRootX, gint* aRootY)
 {
--- a/widget/gtk/nsWindow.h
+++ b/widget/gtk/nsWindow.h
@@ -463,16 +463,19 @@ private:
 #endif
 
 #ifdef MOZ_HAVE_SHMIMAGE
     // If we're using xshm rendering
     RefPtr<nsShmImage>  mFrontShmImage;
     RefPtr<nsShmImage>  mBackShmImage;
 #endif
 
+    // A fallback image surface when a SHM surface is unavailable.
+    cairo_surface_t* mFallbackSurface;
+
 #ifdef ACCESSIBILITY
     RefPtr<mozilla::a11y::Accessible> mRootAccessible;
 
     /**
      * Request to create the accessible for this window if it is top level.
      */
     void                CreateRootAccessible();