Bug 1224976. Recover from singular-matrix cairo errors. r=mattwoodrow
authorRobert O'Callahan <robert@ocallahan.org>
Mon, 16 Nov 2015 17:35:23 +1300
changeset 309296 8684df98e6495ab2898b4a9da13ecb3777953952
parent 309295 d34e6e7a22d1aa638bfb902193f78cd446ce3f1e
child 309297 ef68dc6a289da306e5afa73f5a51d0ba41ec9e2f
push id5513
push userraliiev@mozilla.com
push dateMon, 25 Jan 2016 13:55:34 +0000
treeherdermozilla-beta@5ee97dd05b5c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmattwoodrow
bugs1224976
milestone45.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 1224976. Recover from singular-matrix cairo errors. r=mattwoodrow
gfx/2d/DrawTargetCairo.cpp
gfx/2d/DrawTargetCairo.h
layout/reftests/canvas/1224976-1-ref.html
layout/reftests/canvas/1224976-1.html
layout/reftests/canvas/reftest.list
--- a/gfx/2d/DrawTargetCairo.cpp
+++ b/gfx/2d/DrawTargetCairo.cpp
@@ -585,16 +585,17 @@ NeedIntermediateSurface(const Pattern& a
     return false;
 
   return true;
 }
 
 DrawTargetCairo::DrawTargetCairo()
   : mContext(nullptr)
   , mSurface(nullptr)
+  , mTransformSingular(false)
   , mLockedBits(nullptr)
 {
 }
 
 DrawTargetCairo::~DrawTargetCairo()
 {
   cairo_destroy(mContext);
   if (mSurface) {
@@ -770,16 +771,20 @@ PaintWithAlpha(cairo_t* aContext, const 
 
 void
 DrawTargetCairo::DrawSurface(SourceSurface *aSurface,
                              const Rect &aDest,
                              const Rect &aSource,
                              const DrawSurfaceOptions &aSurfOptions,
                              const DrawOptions &aOptions)
 {
+  if (mTransformSingular) {
+    return;
+  }
+
   AutoPrepareForDrawing prep(this, mContext);
   AutoClearDeviceOffset clear(aSurface);
 
   float sx = aSource.Width() / aDest.Width();
   float sy = aSource.Height() / aDest.Height();
 
   cairo_matrix_t src_mat;
   cairo_matrix_init_translate(&src_mat, aSource.X(), aSource.Y());
@@ -963,16 +968,20 @@ DrawTargetCairo::DrawPattern(const Patte
   cairo_pattern_destroy(pat);
 }
 
 void
 DrawTargetCairo::FillRect(const Rect &aRect,
                           const Pattern &aPattern,
                           const DrawOptions &aOptions)
 {
+  if (mTransformSingular) {
+    return;
+  }
+
   AutoPrepareForDrawing prep(this, mContext);
 
   bool restoreTransform = false;
   Matrix mat;
   Rect r = aRect;
 
   /* Clamp coordinates to work around a design bug in cairo */
   if (r.width > CAIRO_COORD_MAX ||
@@ -1038,16 +1047,20 @@ DrawTargetCairo::CopySurfaceInternal(cai
   cairo_fill(mContext);
 }
 
 void
 DrawTargetCairo::CopySurface(SourceSurface *aSurface,
                              const IntRect &aSource,
                              const IntPoint &aDest)
 {
+  if (mTransformSingular) {
+    return;
+  }
+
   AutoPrepareForDrawing prep(this, mContext);
   AutoClearDeviceOffset clear(aSurface);
 
   if (!aSurface) {
     gfxWarning() << "Unsupported surface type specified";
     return;
   }
 
@@ -1060,16 +1073,20 @@ DrawTargetCairo::CopySurface(SourceSurfa
   CopySurfaceInternal(surf, aSource, aDest);
   cairo_surface_destroy(surf);
 }
 
 void
 DrawTargetCairo::CopyRect(const IntRect &aSource,
                           const IntPoint &aDest)
 {
+  if (mTransformSingular) {
+    return;
+  }
+
   AutoPrepareForDrawing prep(this, mContext);
 
   IntRect source = aSource;
   cairo_surface_t* surf = mSurface;
 
   if (!SupportsSelfCopy(mSurface) &&
       aDest.y >= aSource.y &&
       aDest.y < aSource.YMost()) {
@@ -1092,16 +1109,20 @@ DrawTargetCairo::CopyRect(const IntRect 
   if (surf != mSurface) {
     cairo_surface_destroy(surf);
   }
 }
 
 void
 DrawTargetCairo::ClearRect(const Rect& aRect)
 {
+  if (mTransformSingular) {
+    return;
+  }
+
   AutoPrepareForDrawing prep(this, mContext);
 
   if (!mContext || aRect.Width() <= 0 || aRect.Height() <= 0 ||
       !IsFinite(aRect.X()) || !IsFinite(aRect.Width()) ||
       !IsFinite(aRect.Y()) || !IsFinite(aRect.Height())) {
     gfxCriticalNote << "ClearRect with invalid argument " << gfx::hexa(mContext) << " with " << aRect.Width() << "x" << aRect.Height() << " [" << aRect.X() << ", " << aRect.Y() << "]";
   }
 
@@ -1114,62 +1135,78 @@ DrawTargetCairo::ClearRect(const Rect& a
 }
 
 void
 DrawTargetCairo::StrokeRect(const Rect &aRect,
                             const Pattern &aPattern,
                             const StrokeOptions &aStrokeOptions /* = StrokeOptions() */,
                             const DrawOptions &aOptions /* = DrawOptions() */)
 {
+  if (mTransformSingular) {
+    return;
+  }
+
   AutoPrepareForDrawing prep(this, mContext);
 
   cairo_new_path(mContext);
   cairo_rectangle(mContext, aRect.x, aRect.y, aRect.Width(), aRect.Height());
 
   DrawPattern(aPattern, aStrokeOptions, aOptions, DRAW_STROKE);
 }
 
 void
 DrawTargetCairo::StrokeLine(const Point &aStart,
                             const Point &aEnd,
                             const Pattern &aPattern,
                             const StrokeOptions &aStrokeOptions /* = StrokeOptions() */,
                             const DrawOptions &aOptions /* = DrawOptions() */)
 {
+  if (mTransformSingular) {
+    return;
+  }
+
   AutoPrepareForDrawing prep(this, mContext);
 
   cairo_new_path(mContext);
   cairo_move_to(mContext, aStart.x, aStart.y);
   cairo_line_to(mContext, aEnd.x, aEnd.y);
 
   DrawPattern(aPattern, aStrokeOptions, aOptions, DRAW_STROKE);
 }
 
 void
 DrawTargetCairo::Stroke(const Path *aPath,
                         const Pattern &aPattern,
                         const StrokeOptions &aStrokeOptions /* = StrokeOptions() */,
                         const DrawOptions &aOptions /* = DrawOptions() */)
 {
+  if (mTransformSingular) {
+    return;
+  }
+
   AutoPrepareForDrawing prep(this, mContext, aPath);
 
   if (aPath->GetBackendType() != BackendType::CAIRO)
     return;
 
   PathCairo* path = const_cast<PathCairo*>(static_cast<const PathCairo*>(aPath));
   path->SetPathOnContext(mContext);
 
   DrawPattern(aPattern, aStrokeOptions, aOptions, DRAW_STROKE);
 }
 
 void
 DrawTargetCairo::Fill(const Path *aPath,
                       const Pattern &aPattern,
                       const DrawOptions &aOptions /* = DrawOptions() */)
 {
+  if (mTransformSingular) {
+    return;
+  }
+
   AutoPrepareForDrawing prep(this, mContext, aPath);
 
   if (aPath->GetBackendType() != BackendType::CAIRO)
     return;
 
   PathCairo* path = const_cast<PathCairo*>(static_cast<const PathCairo*>(aPath));
   path->SetPathOnContext(mContext);
 
@@ -1188,16 +1225,20 @@ DrawTargetCairo::SetPermitSubpixelAA(boo
 
 void
 DrawTargetCairo::FillGlyphs(ScaledFont *aFont,
                             const GlyphBuffer &aBuffer,
                             const Pattern &aPattern,
                             const DrawOptions &aOptions,
                             const GlyphRenderingOptions*)
 {
+  if (mTransformSingular) {
+    return;
+  }
+
   AutoPrepareForDrawing prep(this, mContext);
   AutoClearDeviceOffset clear(aPattern);
 
   ScaledFontBase* scaledFont = static_cast<ScaledFontBase*>(aFont);
   cairo_set_scaled_font(mContext, scaledFont->GetCairoScaledFont());
 
   cairo_pattern_t* pat = GfxPatternToCairoPattern(aPattern, aOptions.mAlpha, GetTransform());
   if (!pat)
@@ -1227,16 +1268,20 @@ DrawTargetCairo::FillGlyphs(ScaledFont *
   cairo_show_glyphs(mContext, &glyphs[0], aBuffer.mNumGlyphs);
 }
 
 void
 DrawTargetCairo::Mask(const Pattern &aSource,
                       const Pattern &aMask,
                       const DrawOptions &aOptions /* = DrawOptions() */)
 {
+  if (mTransformSingular) {
+    return;
+  }
+
   AutoPrepareForDrawing prep(this, mContext);
   AutoClearDeviceOffset clearSource(aSource);
   AutoClearDeviceOffset clearMask(aMask);
 
   cairo_set_antialias(mContext, GfxAntialiasToCairoAntialias(aOptions.mAntialiasMode));
 
   cairo_pattern_t* source = GfxPatternToCairoPattern(aSource, aOptions.mAlpha, GetTransform());
   if (!source) {
@@ -1265,16 +1310,20 @@ DrawTargetCairo::Mask(const Pattern &aSo
 }
 
 void
 DrawTargetCairo::MaskSurface(const Pattern &aSource,
                              SourceSurface *aMask,
                              Point aOffset,
                              const DrawOptions &aOptions)
 {
+  if (mTransformSingular) {
+    return;
+  }
+
   AutoPrepareForDrawing prep(this, mContext);
   AutoClearDeviceOffset clearSource(aSource);
   AutoClearDeviceOffset clearMask(aMask);
 
   if (!PatternIsCompatible(aSource)) {
     return;
   }
 
@@ -1331,47 +1380,54 @@ DrawTargetCairo::PushClip(const Path *aP
   if (aPath->GetBackendType() != BackendType::CAIRO) {
     return;
   }
 
   WillChange(aPath);
   cairo_save(mContext);
 
   PathCairo* path = const_cast<PathCairo*>(static_cast<const PathCairo*>(aPath));
-  path->SetPathOnContext(mContext);
+
+  if (mTransformSingular) {
+    cairo_new_path(mContext);
+    cairo_rectangle(mContext, 0, 0, 0, 0);
+  } else {
+    path->SetPathOnContext(mContext);
+  }
   cairo_clip_preserve(mContext);
 }
 
 void
 DrawTargetCairo::PushClipRect(const Rect& aRect)
 {
   WillChange();
   cairo_save(mContext);
 
   cairo_new_path(mContext);
-  cairo_rectangle(mContext, aRect.X(), aRect.Y(), aRect.Width(), aRect.Height());
+  if (mTransformSingular) {
+    cairo_rectangle(mContext, 0, 0, 0, 0);
+  } else {
+    cairo_rectangle(mContext, aRect.X(), aRect.Y(), aRect.Width(), aRect.Height());
+  }
   cairo_clip_preserve(mContext);
 }
 
 void
 DrawTargetCairo::PopClip()
 {
   // save/restore does not affect the path, so no need to call WillChange()
 
   // cairo_restore will restore the transform too and we don't want to do that
   // so we'll save it now and restore it after the cairo_restore
   cairo_matrix_t mat;
   cairo_get_matrix(mContext, &mat);
 
   cairo_restore(mContext);
 
   cairo_set_matrix(mContext, &mat);
-
-  MOZ_ASSERT(cairo_status(mContext) || GetTransform() == Matrix(mat.xx, mat.yx, mat.xy, mat.yy, mat.x0, mat.y0),
-             "Transforms are out of sync");
 }
 
 already_AddRefed<PathBuilder>
 DrawTargetCairo::CreatePathBuilder(FillRule aFillRule /* = FillRule::FILL_WINDING */) const
 {
   return MakeAndAddRef<PathBuilderCairo>(aFillRule);
 }
 
@@ -1704,21 +1760,24 @@ DrawTargetCairo::WillChange(const Path* 
 {
   MarkSnapshotIndependent();
   MOZ_ASSERT(!mLockedBits);
 }
 
 void
 DrawTargetCairo::SetTransform(const Matrix& aTransform)
 {
-  mTransform = aTransform;
+  DrawTarget::SetTransform(aTransform);
 
-  cairo_matrix_t mat;
-  GfxMatrixToCairoMatrix(mTransform, mat);
-  cairo_set_matrix(mContext, &mat);
+  mTransformSingular = aTransform.IsSingular();
+  if (!mTransformSingular) {
+    cairo_matrix_t mat;
+    GfxMatrixToCairoMatrix(mTransform, mat);
+    cairo_set_matrix(mContext, &mat);
+  }
 }
 
 Rect
 DrawTargetCairo::GetUserSpaceClip()
 {
   double clipX1, clipY1, clipX2, clipY2;
   cairo_clip_extents(mContext, &clipX1, &clipY1, &clipX2, &clipY2);
   return Rect(clipX1, clipY1, clipX2 - clipX1, clipY2 - clipY1); // Narrowing of doubles to floats
--- a/gfx/2d/DrawTargetCairo.h
+++ b/gfx/2d/DrawTargetCairo.h
@@ -204,16 +204,17 @@ private: // methods
   // If the current operator is "source" then clear the destination before we
   // draw into it, to simulate the effect of an unbounded source operator.
   void ClearSurfaceForUnboundedSource(const CompositionOp &aOperator);
 
 private: // data
   cairo_t* mContext;
   cairo_surface_t* mSurface;
   IntSize mSize;
+  bool mTransformSingular;
 
   uint8_t* mLockedBits;
 
   // The latest snapshot of this surface. This needs to be told when this
   // target is modified. We keep it alive as a cache.
   RefPtr<SourceSurfaceCairo> mSnapshot;
   static cairo_surface_t *mDummySurface;
 };
new file mode 100644
--- /dev/null
+++ b/layout/reftests/canvas/1224976-1-ref.html
@@ -0,0 +1,2 @@
+<!DOCTYPE HTML>
+<div style="background:black; width:10px; height:10px"></div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/canvas/1224976-1.html
@@ -0,0 +1,10 @@
+<!DOCTYPE HTML>
+<canvas id="c"></canvas>
+<script>
+var ctx = c.getContext('2d');
+ctx.scale(0,1);
+ctx.fillStyle = "black";
+ctx.fillRect(0, 0, 10, 10);
+ctx.setTransform(1, 0, 0, 1, 0, 0);
+ctx.fillRect(0, 0, 10, 10);
+</script>
--- a/layout/reftests/canvas/reftest.list
+++ b/layout/reftests/canvas/reftest.list
@@ -102,8 +102,9 @@ fails-if(azureQuartz&&OSX==1006) == 6726
 # You get a little bit of rounding fuzz on OSX from transforming the paths between user space and device space
 fuzzy-if(azureQuartz,2,128) fuzzy-if(d2d,12,21) fuzzy-if(d2d&&/^Windows\x20NT\x2010\.0/.test(http.oscpu),2,141) == 784573-1.html 784573-1-ref.html
 
 == 802658-1.html 802658-1-ref.html
 == 1074733-1.html 1074733-1-ref.html
 fuzzy-if(Mulet,45,2) == 1107096-invisibles.html 1107096-invisibles-ref.html
 == 1151821-1.html 1151821-1-ref.html
 == 1201272-1.html 1201272-1-ref.html
+== 1224976-1.html 1224976-1-ref.html