Bug 1277306 - Fallback to a cairo image surface when SHM and XRender are unavailable when drawing to a GDK window. r?lsalzman draft
authorAndrew Comminos <andrew@comminos.com>
Wed, 01 Jun 2016 14:52:36 -0400
changeset 375157 fa769b2002a46e9775928088af82c042276ad623
parent 375054 e27fe24a746fa839f1cabe198faf1bad42c7dc4b
child 522775 4022f0bcb22a5b77a8143ae389355d7633fcdc28
push id20178
push userbmo:andrew@comminos.com
push dateFri, 03 Jun 2016 15:24:11 +0000
reviewerslsalzman
bugs1277306
milestone49.0a1
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();
@@ -2329,23 +2337,20 @@ nsWindow::OnExposeEvent(cairo_t *cr)
                                 DrawSurfaceOptions(Filter::POINT), 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
@@ -6502,65 +6507,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();