Bug 1060960: Fix D2D 1.1 backend's ClearRect to deal with transforms and complex clips. r=jrmuizel
authorBas Schouten <bschouten@mozilla.com>
Sun, 14 Sep 2014 23:51:29 +0200
changeset 205255 3706401f8cc93082d68cc55db3a04a59086ba988
parent 205254 2c2c856713d2e56c71ea68da3b5c43bdb1179707
child 205256 7861be6bf0390ad3dfe4accc0a36a8e5ba345e78
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
reviewersjrmuizel
bugs1060960
milestone35.0a1
Bug 1060960: Fix D2D 1.1 backend's ClearRect to deal with transforms and complex clips. r=jrmuizel
gfx/2d/DrawTargetD2D1.cpp
gfx/2d/DrawTargetD2D1.h
--- a/gfx/2d/DrawTargetD2D1.cpp
+++ b/gfx/2d/DrawTargetD2D1.cpp
@@ -198,19 +198,56 @@ DrawTargetD2D1::DrawSurfaceWithShadow(So
   mDC->DrawImage(compositeEffect, &surfPoint, nullptr, D2D1_INTERPOLATION_MODE_LINEAR, D2DCompositionMode(aOperator));
 }
 
 void
 DrawTargetD2D1::ClearRect(const Rect &aRect)
 {
   MarkChanged();
 
-  mDC->PushAxisAlignedClip(D2DRect(aRect), D2D1_ANTIALIAS_MODE_PER_PRIMITIVE);
+  PopAllClips();
+
+  PushClipRect(aRect);
+
+  if (mTransformDirty ||
+      !mTransform.IsIdentity()) {
+    mDC->SetTransform(D2D1::IdentityMatrix());
+    mTransformDirty = true;
+  }
+
+  D2D1_RECT_F clipRect;
+  bool isPixelAligned;
+  if (mTransform.IsRectilinear() &&
+      GetDeviceSpaceClipRect(clipRect, isPixelAligned)) {
+    mDC->PushAxisAlignedClip(clipRect, isPixelAligned ? D2D1_ANTIALIAS_MODE_ALIASED : D2D1_ANTIALIAS_MODE_PER_PRIMITIVE);
+    mDC->Clear();
+    mDC->PopAxisAlignedClip();
+
+    PopClip();
+    return;
+  }
+
+  mDC->SetTarget(mTempBitmap);
   mDC->Clear();
+
+  IntRect addClipRect;
+  RefPtr<ID2D1Geometry> geom = GetClippedGeometry(&addClipRect);
+
+  RefPtr<ID2D1SolidColorBrush> brush;
+  mDC->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::White), byRef(brush));
+  mDC->PushAxisAlignedClip(D2D1::RectF(addClipRect.x, addClipRect.y, addClipRect.XMost(), addClipRect.YMost()), D2D1_ANTIALIAS_MODE_PER_PRIMITIVE);
+  mDC->FillGeometry(geom, brush);
   mDC->PopAxisAlignedClip();
+
+  mDC->SetTarget(mBitmap);
+  mDC->DrawImage(mTempBitmap, D2D1_INTERPOLATION_MODE_NEAREST_NEIGHBOR, D2D1_COMPOSITE_MODE_DESTINATION_OUT);
+
+  PopClip();
+
+  return;
 }
 
 void
 DrawTargetD2D1::MaskSurface(const Pattern &aSource,
                             SourceSurface *aMask,
                             Point aOffset,
                             const DrawOptions &aOptions)
 {
@@ -461,16 +498,18 @@ DrawTargetD2D1::Mask(const Pattern &aSou
 void
 DrawTargetD2D1::PushClip(const Path *aPath)
 {
   if (aPath->GetBackendType() != BackendType::DIRECT2D) {
     gfxDebug() << *this << ": Ignoring clipping call for incompatible path.";
     return;
   }
 
+  mCurrentClippedGeometry = nullptr;
+
   RefPtr<PathD2D> pathD2D = static_cast<PathD2D*>(const_cast<Path*>(aPath));
 
   PushedClip clip;
   clip.mTransform = D2DMatrix(mTransform);
   clip.mPath = pathD2D;
   
   pathD2D->mGeometry->GetBounds(clip.mTransform, &clip.mBounds);
 
@@ -499,16 +538,18 @@ DrawTargetD2D1::PushClipRect(const Rect 
     pathBuilder->LineTo(aRect.TopRight());
     pathBuilder->LineTo(aRect.BottomRight());
     pathBuilder->LineTo(aRect.BottomLeft());
     pathBuilder->Close();
     RefPtr<Path> path = pathBuilder->Finish();
     return PushClip(path);
   }
 
+  mCurrentClippedGeometry = nullptr;
+
   PushedClip clip;
   Rect rect = mTransform.TransformBounds(aRect);
   IntRect intRect;
   clip.mIsPixelAligned = rect.ToIntRect(&intRect);
 
   // Do not store the transform, just store the device space rectangle directly.
   clip.mBounds = D2DRect(rect);
 
@@ -520,16 +561,18 @@ DrawTargetD2D1::PushClipRect(const Rect 
   if (mClipsArePushed) {
     mDC->PushAxisAlignedClip(clip.mBounds, clip.mIsPixelAligned ? D2D1_ANTIALIAS_MODE_ALIASED : D2D1_ANTIALIAS_MODE_PER_PRIMITIVE);
   }
 }
 
 void
 DrawTargetD2D1::PopClip()
 {
+  mCurrentClippedGeometry = nullptr;
+
   if (mClipsArePushed) {
     if (mPushedClips.back().mPath) {
       mDC->PopLayer();
     } else {
       mDC->PopAxisAlignedClip();
     }
   }
   mPushedClips.pop_back();
@@ -824,16 +867,138 @@ void
 DrawTargetD2D1::AddDependencyOnSource(SourceSurfaceD2D1* aSource)
 {
   if (aSource->mDrawTarget && !mDependingOnTargets.count(aSource->mDrawTarget)) {
     aSource->mDrawTarget->mDependentTargets.insert(this);
     mDependingOnTargets.insert(aSource->mDrawTarget);
   }
 }
 
+static D2D1_RECT_F
+IntersectRect(const D2D1_RECT_F& aRect1, const D2D1_RECT_F& aRect2)
+{
+  D2D1_RECT_F result;
+  result.left = max(aRect1.left, aRect2.left);
+  result.top = max(aRect1.top, aRect2.top);
+  result.right = min(aRect1.right, aRect2.right);
+  result.bottom = min(aRect1.bottom, aRect2.bottom);
+
+  result.right = max(result.right, result.left);
+  result.bottom = max(result.bottom, result.top);
+
+  return result;
+}
+
+bool
+DrawTargetD2D1::GetDeviceSpaceClipRect(D2D1_RECT_F& aClipRect, bool& aIsPixelAligned)
+{
+  if (!mPushedClips.size()) {
+    return false;
+  }
+
+  aClipRect = D2D1::RectF(0, 0, mSize.width, mSize.height);
+  for (auto iter = mPushedClips.begin();iter != mPushedClips.end(); iter++) {
+    if (iter->mPath) {
+      return false;
+    }
+    aClipRect = IntersectRect(aClipRect, iter->mBounds);
+    if (!iter->mIsPixelAligned) {
+      aIsPixelAligned = false;
+    }
+  }
+  return true;
+}
+
+TemporaryRef<ID2D1Geometry>
+DrawTargetD2D1::GetClippedGeometry(IntRect *aClipBounds)
+{
+  if (mCurrentClippedGeometry) {
+    *aClipBounds = mCurrentClipBounds;
+    return mCurrentClippedGeometry;
+  }
+
+  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();
+
+  if (iter->mPath) {
+    pathGeom = GetTransformedGeometry(iter->mPath->GetGeometry(), iter->mTransform);
+  } else {
+    pathRect = iter->mBounds;
+    pathRectIsAxisAligned = iter->mIsPixelAligned;
+  }
+
+  iter++;
+  for (;iter != mPushedClips.end(); iter++) {
+    // Do nothing but add it to the current clip bounds.
+    if (!iter->mPath && iter->mIsPixelAligned) {
+      mCurrentClipBounds.IntersectRect(mCurrentClipBounds,
+        IntRect(int32_t(iter->mBounds.left), int32_t(iter->mBounds.top),
+                int32_t(iter->mBounds.right - iter->mBounds.left),
+                int32_t(iter->mBounds.bottom - iter->mBounds.top)));
+      continue;
+    }
+
+    if (!pathGeom) {
+      if (pathRectIsAxisAligned) {
+        mCurrentClipBounds.IntersectRect(mCurrentClipBounds,
+          IntRect(int32_t(pathRect.left), int32_t(pathRect.top),
+                  int32_t(pathRect.right - pathRect.left),
+                  int32_t(pathRect.bottom - pathRect.top)));
+      }
+      if (iter->mPath) {
+        // See if pathRect needs to go into the path geometry.
+        if (!pathRectIsAxisAligned) {
+          pathGeom = ConvertRectToGeometry(pathRect);
+        } else {
+          pathGeom = GetTransformedGeometry(iter->mPath->GetGeometry(), iter->mTransform);
+        }
+      } else {
+        pathRect = IntersectRect(pathRect, iter->mBounds);
+        pathRectIsAxisAligned = false;
+        continue;
+      }
+    }
+
+    RefPtr<ID2D1PathGeometry> newGeom;
+    factory()->CreatePathGeometry(byRef(newGeom));
+
+    RefPtr<ID2D1GeometrySink> currentSink;
+    newGeom->Open(byRef(currentSink));
+
+    if (iter->mPath) {
+      pathGeom->CombineWithGeometry(iter->mPath->GetGeometry(), D2D1_COMBINE_MODE_INTERSECT,
+                                    iter->mTransform, currentSink);
+    } else {
+      RefPtr<ID2D1Geometry> rectGeom = ConvertRectToGeometry(iter->mBounds);
+      pathGeom->CombineWithGeometry(rectGeom, D2D1_COMBINE_MODE_INTERSECT,
+                                    D2D1::IdentityMatrix(), currentSink);
+    }
+
+    currentSink->Close();
+
+    pathGeom = newGeom.forget();
+  }
+
+  // For now we need mCurrentClippedGeometry to always be non-nullptr. This
+  // method might seem a little strange but it is just fine, if pathGeom is
+  // nullptr pathRect will always still contain 1 clip unaccounted for
+  // regardless of mCurrentClipBounds.
+  if (!pathGeom) {
+    pathGeom = ConvertRectToGeometry(pathRect);
+  }
+  mCurrentClippedGeometry = pathGeom.forget();
+  *aClipBounds = mCurrentClipBounds;
+  return mCurrentClippedGeometry;
+}
+
 void
 DrawTargetD2D1::PopAllClips()
 {
   if (mClipsArePushed) {
     PopClipsFromDC(mDC);
   
     mClipsArePushed = false;
   }
--- a/gfx/2d/DrawTargetD2D1.h
+++ b/gfx/2d/DrawTargetD2D1.h
@@ -166,28 +166,37 @@ private:
   void FlushTransformToDC() {
     if (mTransformDirty) {
       mDC->SetTransform(D2DMatrix(mTransform));
       mTransformDirty = false;
     }
   }
   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);
+
+  bool GetDeviceSpaceClipRect(D2D1_RECT_F& aClipRect, bool& aIsPixelAligned);
+
   void PopAllClips();
   void PushClipsToDC(ID2D1DeviceContext *aDC);
   void PopClipsFromDC(ID2D1DeviceContext *aDC);
 
   TemporaryRef<ID2D1Brush> CreateBrushForPattern(const Pattern &aPattern, Float aAlpha = 1.0f);
 
   void PushD2DLayer(ID2D1DeviceContext *aDC, ID2D1Geometry *aGeometry, const D2D1_MATRIX_3X2_F &aTransform);
 
   IntSize mSize;
 
   RefPtr<ID3D11Device> mDevice;
   RefPtr<ID3D11Texture2D> mTexture;
+  RefPtr<ID2D1Geometry> mCurrentClippedGeometry;
   // This is only valid if mCurrentClippedGeometry is non-null. And will
   // only be the intersection of all pixel-aligned retangular clips. This is in
   // device space.
   IntRect mCurrentClipBounds;
   mutable RefPtr<ID2D1DeviceContext> mDC;
   RefPtr<ID2D1Bitmap1> mBitmap;
   RefPtr<ID2D1Bitmap1> mTempBitmap;