Bug 1151821 - Make globalCompositeOperator work correctly when a complex clip is pushed. r=jrmuizel, a=sledru
authorBas Schouten <bschouten@mozilla.com>
Fri, 10 Apr 2015 07:09:31 +0200
changeset 260298 987c18b686eb
parent 260297 cb2725c612b2
child 260299 1bbb50c6a494
push id741
push userryanvm@gmail.com
push date2015-04-27 20:01 +0000
treeherdermozilla-release@d10817faa571 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjrmuizel, sledru
bugs1151821
milestone38.0
Bug 1151821 - Make globalCompositeOperator work correctly when a complex clip is pushed. r=jrmuizel, a=sledru
gfx/2d/DrawTargetD2D1.cpp
gfx/2d/DrawTargetD2D1.h
--- a/gfx/2d/DrawTargetD2D1.cpp
+++ b/gfx/2d/DrawTargetD2D1.cpp
@@ -910,53 +910,87 @@ DrawTargetD2D1::MarkChanged()
   }
 }
 
 void
 DrawTargetD2D1::PrepareForDrawing(CompositionOp aOp, const Pattern &aPattern)
 {
   MarkChanged();
 
-  // It's important to do this before FlushTransformToDC! As this will cause
-  // the transform to become dirty.
-  if (!mClipsArePushed) {
-    mClipsArePushed = true;
-    PushClipsToDC(mDC);
-  }
+  if (aOp == CompositionOp::OP_OVER && IsPatternSupportedByD2D(aPattern)) {
+    // It's important to do this before FlushTransformToDC! As this will cause
+    // the transform to become dirty.
+    PushAllClips();
 
-  FlushTransformToDC();
-
-  if (aOp == CompositionOp::OP_OVER && IsPatternSupportedByD2D(aPattern)) {
+    FlushTransformToDC();
     return;
   }
 
+  PopAllClips();
+
   mDC->SetTarget(mTempBitmap);
   mDC->Clear(D2D1::ColorF(0, 0));
+
+  PushAllClips();
+  FlushTransformToDC();
 }
 
 void
 DrawTargetD2D1::FinalizeDrawing(CompositionOp aOp, const Pattern &aPattern)
 {
   bool patternSupported = IsPatternSupportedByD2D(aPattern);
 
   if (aOp == CompositionOp::OP_OVER && patternSupported) {
     return;
   }
 
+  PopAllClips();
+
   RefPtr<ID2D1Image> image;
   mDC->GetTarget(byRef(image));
 
   mDC->SetTarget(mBitmap);
 
   mDC->SetTransform(D2D1::IdentityMatrix());
   mTransformDirty = true;
 
   if (patternSupported) {
     if (D2DSupportsCompositeMode(aOp)) {
+      D2D1_RECT_F rect;
+      bool isAligned;
+      RefPtr<ID2D1Bitmap> tmpBitmap;
+      bool clipIsComplex = mPushedClips.size() && !GetDeviceSpaceClipRect(rect, isAligned);
+
+      if (clipIsComplex) {
+        if (!IsOperatorBoundByMask(aOp)) {
+          HRESULT hr = mDC->CreateBitmap(D2DIntSize(mSize), D2D1::BitmapProperties(D2DPixelFormat(mFormat)), byRef(tmpBitmap));
+          if (FAILED(hr)) {
+            gfxCriticalError(CriticalLog::DefaultOptions(Factory::ReasonableSurfaceSize(mSize))) << "[D2D1.1] 6CreateBitmap failure " << mSize << " Code: " << hexa(hr);
+            // For now, crash in this scenario; this should happen because tmpBitmap is
+            // null and CopyFromBitmap call below dereferences it.
+            // return;
+          }
+          mDC->Flush();
+
+          tmpBitmap->CopyFromBitmap(nullptr, mBitmap, nullptr);
+        }
+      } else {
+        PushAllClips();
+      }
       mDC->DrawImage(image, D2D1_INTERPOLATION_MODE_NEAREST_NEIGHBOR, D2DCompositionMode(aOp));
+
+      if (tmpBitmap) {
+        RefPtr<ID2D1BitmapBrush> brush;
+        RefPtr<ID2D1Geometry> inverseGeom = GetInverseClippedGeometry();
+        mDC->CreateBitmapBrush(tmpBitmap, byRef(brush));
+
+        mDC->SetPrimitiveBlend(D2D1_PRIMITIVE_BLEND_COPY);
+        mDC->FillGeometry(inverseGeom, brush);
+        mDC->SetPrimitiveBlend(D2D1_PRIMITIVE_BLEND_SOURCE_OVER);
+      }
       return;
     }
 
     if (!mBlendEffect) {
       mDC->CreateEffect(CLSID_D2D1Blend, byRef(mBlendEffect));
 
       if (!mBlendEffect) {
         gfxWarning() << "Failed to create blend effect!";
@@ -964,40 +998,40 @@ DrawTargetD2D1::FinalizeDrawing(Composit
       }
     }
 
     RefPtr<ID2D1Bitmap> tmpBitmap;
     mDC->CreateBitmap(D2DIntSize(mSize), D2D1::BitmapProperties(D2DPixelFormat(mFormat)), byRef(tmpBitmap));
 
     // This flush is important since the copy method will not know about the context drawing to the surface.
     // We also need to pop all the clips to make sure any drawn content will have made it to the final bitmap.
-    PopAllClips();
     mDC->Flush();
 
     // We need to use a copy here because affects don't accept a surface on
     // both their in- and outputs.
     tmpBitmap->CopyFromBitmap(nullptr, mBitmap, nullptr);
 
     mBlendEffect->SetInput(0, tmpBitmap);
     mBlendEffect->SetInput(1, mTempBitmap);
     mBlendEffect->SetValue(D2D1_BLEND_PROP_MODE, D2DBlendMode(aOp));
 
-    PushClipsToDC(mDC);
-    mClipsArePushed = true;
+    PushAllClips();
 
     mDC->DrawImage(mBlendEffect, D2D1_INTERPOLATION_MODE_NEAREST_NEIGHBOR, D2D1_COMPOSITE_MODE_BOUNDED_SOURCE_COPY);
     return;
   }
 
   const RadialGradientPattern *pat = static_cast<const RadialGradientPattern*>(&aPattern);
   if (pat->mCenter1 == pat->mCenter2 && pat->mRadius1 == pat->mRadius2) {
     // Draw nothing!
     return;
   }
 
+  PushAllClips();
+
   RefPtr<ID2D1Effect> radialGradientEffect;
 
   mDC->CreateEffect(CLSID_RadialGradientEffect, byRef(radialGradientEffect));
 
   radialGradientEffect->SetValue(RADIAL_PROP_STOP_COLLECTION,
                                  static_cast<const GradientStopsD2D*>(pat->mStops.get())->mStopCollection);
   radialGradientEffect->SetValue(RADIAL_PROP_CENTER_1, D2D1::Vector2F(pat->mCenter1.x, pat->mCenter1.y));
   radialGradientEffect->SetValue(RADIAL_PROP_CENTER_2, D2D1::Vector2F(pat->mCenter2.x, pat->mCenter2.y));
@@ -1057,16 +1091,18 @@ DrawTargetD2D1::GetDeviceSpaceClipRect(D
 TemporaryRef<ID2D1Geometry>
 DrawTargetD2D1::GetClippedGeometry(IntRect *aClipBounds)
 {
   if (mCurrentClippedGeometry) {
     *aClipBounds = mCurrentClipBounds;
     return mCurrentClippedGeometry;
   }
 
+  MOZ_ASSERT(mPushedClips.size());
+
   mCurrentClipBounds = IntRect(IntPoint(0, 0), mSize);
 
   // if pathGeom is null then pathRect represents the path.
   RefPtr<ID2D1Geometry> pathGeom;
   D2D1_RECT_F pathRect;
   bool pathRectIsAxisAligned = false;
   auto iter = mPushedClips.begin();
 
@@ -1136,27 +1172,55 @@ DrawTargetD2D1::GetClippedGeometry(IntRe
   if (!pathGeom) {
     pathGeom = ConvertRectToGeometry(pathRect);
   }
   mCurrentClippedGeometry = pathGeom.forget();
   *aClipBounds = mCurrentClipBounds;
   return mCurrentClippedGeometry;
 }
 
+TemporaryRef<ID2D1Geometry>
+DrawTargetD2D1::GetInverseClippedGeometry()
+{
+  IntRect bounds;
+  RefPtr<ID2D1Geometry> geom = GetClippedGeometry(&bounds);
+  RefPtr<ID2D1RectangleGeometry> rectGeom;
+  RefPtr<ID2D1PathGeometry> inverseGeom;
+
+  factory()->CreateRectangleGeometry(D2D1::RectF(0, 0, mSize.width, mSize.height), byRef(rectGeom));
+  factory()->CreatePathGeometry(byRef(inverseGeom));
+  RefPtr<ID2D1GeometrySink> sink;
+  inverseGeom->Open(byRef(sink));
+  rectGeom->CombineWithGeometry(geom, D2D1_COMBINE_MODE_EXCLUDE, D2D1::IdentityMatrix(), sink);
+  sink->Close();
+
+  return inverseGeom;
+}
+
 void
 DrawTargetD2D1::PopAllClips()
 {
   if (mClipsArePushed) {
     PopClipsFromDC(mDC);
   
     mClipsArePushed = false;
   }
 }
 
 void
+DrawTargetD2D1::PushAllClips()
+{
+  if (!mClipsArePushed) {
+    PushClipsToDC(mDC);
+  
+    mClipsArePushed = true;
+  }
+}
+
+void
 DrawTargetD2D1::PushClipsToDC(ID2D1DeviceContext *aDC)
 {
   mDC->SetTransform(D2D1::IdentityMatrix());
   mTransformDirty = true;
 
   for (std::vector<PushedClip>::iterator iter = mPushedClips.begin();
         iter != mPushedClips.end(); iter++) {
     if (iter->mPath) {
--- a/gfx/2d/DrawTargetD2D1.h
+++ b/gfx/2d/DrawTargetD2D1.h
@@ -170,19 +170,22 @@ private:
   void AddDependencyOnSource(SourceSurfaceD2D1* aSource);
 
   // This returns the clipped geometry, in addition it returns aClipBounds which
   // represents the intersection of all pixel-aligned rectangular clips that
   // are currently set. The returned clipped geometry must be clipped by these
   // bounds to correctly reflect the total clip. This is in device space.
   TemporaryRef<ID2D1Geometry> GetClippedGeometry(IntRect *aClipBounds);
 
+  TemporaryRef<ID2D1Geometry> GetInverseClippedGeometry();
+
   bool GetDeviceSpaceClipRect(D2D1_RECT_F& aClipRect, bool& aIsPixelAligned);
 
   void PopAllClips();
+  void PushAllClips();
   void PushClipsToDC(ID2D1DeviceContext *aDC);
   void PopClipsFromDC(ID2D1DeviceContext *aDC);
 
   TemporaryRef<ID2D1Brush> CreateTransparentBlackBrush();
   TemporaryRef<ID2D1Brush> CreateBrushForPattern(const Pattern &aPattern, Float aAlpha = 1.0f);
 
   void PushD2DLayer(ID2D1DeviceContext *aDC, ID2D1Geometry *aGeometry, const D2D1_MATRIX_3X2_F &aTransform);