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 id27483
push usercbook@mozilla.com
push dateMon, 15 Sep 2014 12:07:08 +0000
treeherdermozilla-central@77127f875559 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjrmuizel
bugs1060960
milestone35.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 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;