Bug 1224207 - Part 7. (Main) Pass frame transform info down to nsFilterInstance. r=mstange
authorcku <cku@mozilla.com>
Tue, 07 Feb 2017 11:51:34 +0800
changeset 341437 f4f6790e39265fa14d95449fffd05b001a89e0ac
parent 341436 b1fb2a174fbf045a8ca8fe00eb80117a2cb6d441
child 341438 655021ab4e0729879f20ad05979e9dad7fb0b24e
push id86727
push userkwierso@gmail.com
push dateThu, 09 Feb 2017 00:21:26 +0000
treeherdermozilla-inbound@55a4f5189115 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmstange
bugs1224207
milestone54.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 1224207 - Part 7. (Main) Pass frame transform info down to nsFilterInstance. r=mstange The biggest change is located in nsFilterInstance::ComputeUserSpaceToFilterSpaceScale. Originally, nsSVGUtils::GetCanvasTM is used. This function returns combination of svg-transform, e.g. <rect transform="translate(30,40)" />, and css-to-dev-scale-transform. That why we do not see this bug in a transformed svg element. For example, the following svg can be rendered correctly on FF: <svg xmlns="http://www.w3.org/2000/svg"> <defs> <filter id="blurMe"> <feGaussianBlur in="SourceGraphic" stdDeviation="1"/> </filter> </defs> <!-- nsSVGUtils::GetCanvasTM return transform="scale(3)" correctly --> <text x="0" y="35" font-size="35" transform="scale(3)" filter="url(#blurMe)"> Hello, out there </text> </svg> Unfortunately, this function does not report css-transform at all. So, I replaced it by mPaintTransfom, which is passed from the caller. So now it's the caller's responsibility to pass a UserSpace-To-DeviceSpace transform into nsFilterInstance. And, we actually change the meaning of mPaintTransform in this patch. Before this patch, mPaintTransform means css-to-dev-px scaling transform; After this patch it means "userspace-to-filterspace-scaling * css-to-dev-scaling" transform. All the other modifictions are to respect the change in nsFilterInstance::ComputeUserSpaceToFilterSpaceScale. MozReview-Commit-ID: LwNUAMo98M
layout/svg/nsFilterInstance.cpp
layout/svg/nsFilterInstance.h
layout/svg/nsSVGIntegrationUtils.cpp
--- a/layout/svg/nsFilterInstance.cpp
+++ b/layout/svg/nsFilterInstance.cpp
@@ -34,20 +34,20 @@ using namespace mozilla::image;
 FilterDescription
 nsFilterInstance::GetFilterDescription(nsIContent* aFilteredElement,
                                        const nsTArray<nsStyleFilter>& aFilterChain,
                                        bool aFilterInputIsTainted,
                                        const UserSpaceMetrics& aMetrics,
                                        const gfxRect& aBBox,
                                        nsTArray<RefPtr<SourceSurface>>& aOutAdditionalImages)
 {
-  gfxMatrix unused; // aPaintTransform arg not used since we're not painting
+  gfxMatrix identity;
   nsFilterInstance instance(nullptr, aFilteredElement, aMetrics,
                             aFilterChain, aFilterInputIsTainted, nullptr,
-                            unused, nullptr, nullptr, nullptr, &aBBox);
+                            identity, nullptr, nullptr, nullptr, &aBBox);
   if (!instance.IsInitialized()) {
     return FilterDescription();
   }
   return instance.ExtractDescriptionAndAdditionalImages(aOutAdditionalImages);
 }
 
 static UniquePtr<UserSpaceMetrics>
 UserSpaceMetricsForFrame(nsIFrame* aFrame)
@@ -65,64 +65,65 @@ nsFilterInstance::PaintFilteredFrame(nsI
                                      const gfxMatrix& aTransform,
                                      nsSVGFilterPaintCallback *aPaintCallback,
                                      const nsRegion *aDirtyArea)
 {
   auto& filterChain = aFilteredFrame->StyleEffects()->mFilters;
   UniquePtr<UserSpaceMetrics> metrics = UserSpaceMetricsForFrame(aFilteredFrame);
   // Hardcode InputIsTainted to true because we don't want JS to be able to
   // read the rendered contents of aFilteredFrame.
-  nsFilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(), *metrics,
-                            filterChain, /* InputIsTainted */ true, aPaintCallback,
-                            aTransform, aDirtyArea, nullptr, nullptr, nullptr);
+  nsFilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(),
+                            *metrics, filterChain, /* InputIsTainted */ true,
+                            aPaintCallback, aTransform, aDirtyArea, nullptr,
+                            nullptr, nullptr);
   if (!instance.IsInitialized()) {
     return DrawResult::BAD_IMAGE;
   }
 
   return instance.Render(aDrawTarget);
 }
 
 nsRegion
 nsFilterInstance::GetPostFilterDirtyArea(nsIFrame *aFilteredFrame,
                                          const nsRegion& aPreFilterDirtyRegion)
 {
   if (aPreFilterDirtyRegion.IsEmpty()) {
     return nsRegion();
   }
 
-  gfxMatrix unused; // aPaintTransform arg not used since we're not painting
+  gfxMatrix tm = nsSVGUtils::GetCanvasTM(aFilteredFrame);
   auto& filterChain = aFilteredFrame->StyleEffects()->mFilters;
   UniquePtr<UserSpaceMetrics> metrics = UserSpaceMetricsForFrame(aFilteredFrame);
   // Hardcode InputIsTainted to true because we don't want JS to be able to
   // read the rendered contents of aFilteredFrame.
-  nsFilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(), *metrics,
-                            filterChain, /* InputIsTainted */ true, nullptr, unused,
-                            nullptr, &aPreFilterDirtyRegion);
+  nsFilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(),
+                            *metrics, filterChain, /* InputIsTainted */ true,
+                            nullptr, tm, nullptr, &aPreFilterDirtyRegion);
   if (!instance.IsInitialized()) {
     return nsRegion();
   }
 
   // We've passed in the source's dirty area so the instance knows about it.
   // Now we can ask the instance to compute the area of the filter output
   // that's dirty.
   return instance.ComputePostFilterDirtyRegion();
 }
 
 nsRegion
 nsFilterInstance::GetPreFilterNeededArea(nsIFrame *aFilteredFrame,
                                          const nsRegion& aPostFilterDirtyRegion)
 {
-  gfxMatrix unused; // aPaintTransform arg not used since we're not painting
+  gfxMatrix tm = nsSVGUtils::GetCanvasTM(aFilteredFrame);
   auto& filterChain = aFilteredFrame->StyleEffects()->mFilters;
   UniquePtr<UserSpaceMetrics> metrics = UserSpaceMetricsForFrame(aFilteredFrame);
   // Hardcode InputIsTainted to true because we don't want JS to be able to
   // read the rendered contents of aFilteredFrame.
-  nsFilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(), *metrics,
-                            filterChain, /* InputIsTainted */ true, nullptr, unused,
-                            &aPostFilterDirtyRegion);
+  nsFilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(),
+                            *metrics, filterChain, /* InputIsTainted */ true,
+                            nullptr, tm, &aPostFilterDirtyRegion);
   if (!instance.IsInitialized()) {
     return nsRect();
   }
 
   // Now we can ask the instance to compute the area of the source
   // that's needed.
   return instance.ComputeSourceNeededRect();
 }
@@ -138,25 +139,25 @@ nsFilterInstance::GetPostFilterBounds(ns
 
   nsRegion preFilterRegion;
   nsRegion* preFilterRegionPtr = nullptr;
   if (aPreFilterBounds) {
     preFilterRegion = *aPreFilterBounds;
     preFilterRegionPtr = &preFilterRegion;
   }
 
-  gfxMatrix unused; // aPaintTransform arg not used since we're not painting
+  gfxMatrix tm = nsSVGUtils::GetCanvasTM(aFilteredFrame);
   auto& filterChain = aFilteredFrame->StyleEffects()->mFilters;
   UniquePtr<UserSpaceMetrics> metrics = UserSpaceMetricsForFrame(aFilteredFrame);
   // Hardcode InputIsTainted to true because we don't want JS to be able to
   // read the rendered contents of aFilteredFrame.
-  nsFilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(), *metrics,
-                            filterChain, /* InputIsTainted */ true, nullptr, unused,
-                            nullptr, preFilterRegionPtr, aPreFilterBounds,
-                            aOverrideBBox);
+  nsFilterInstance instance(aFilteredFrame, aFilteredFrame->GetContent(),
+                            *metrics, filterChain, /* InputIsTainted */ true,
+                            nullptr, tm, nullptr, preFilterRegionPtr,
+                            aPreFilterBounds, aOverrideBBox);
   if (!instance.IsInitialized()) {
     return nsRect();
   }
 
   return instance.ComputePostFilterExtents();
 }
 
 nsFilterInstance::nsFilterInstance(nsIFrame *aTargetFrame,
@@ -198,21 +199,16 @@ nsFilterInstance::nsFilterInstance(nsIFr
   }
 
   // Get various transforms:
 
   gfxMatrix filterToUserSpace(mFilterSpaceToUserSpaceScale.width, 0.0f,
                               0.0f, mFilterSpaceToUserSpaceScale.height,
                               0.0f, 0.0f);
 
-  // Only used (so only set) when we paint:
-  if (mPaintCallback) {
-    mFilterSpaceToDeviceSpaceTransform = filterToUserSpace * mPaintTransform;
-  }
-
   mFilterSpaceToFrameSpaceInCSSPxTransform =
     filterToUserSpace * GetUserSpaceToFrameSpaceInCSSPxTransform();
   // mFilterSpaceToFrameSpaceInCSSPxTransform is always invertible
   mFrameSpaceInCSSPxToFilterSpaceTransform =
     mFilterSpaceToFrameSpaceInCSSPxTransform;
   mFrameSpaceInCSSPxToFilterSpaceTransform.Invert();
 
   nsIntRect targetBounds;
@@ -238,32 +234,29 @@ nsFilterInstance::nsFilterInstance(nsIFr
   mInitialized = true;
 }
 
 nsresult
 nsFilterInstance::ComputeUserSpaceToFilterSpaceScale()
 {
   gfxMatrix canvasTransform;
   if (mTargetFrame) {
-    canvasTransform = nsSVGUtils::GetCanvasTM(mTargetFrame);
-    if (canvasTransform.IsSingular()) {
+    mUserSpaceToFilterSpaceScale = mPaintTransform.ScaleFactors(true);
+    if (mUserSpaceToFilterSpaceScale.width <= 0.0f ||
+        mUserSpaceToFilterSpaceScale.height <= 0.0f) {
       // Nothing should be rendered.
       return NS_ERROR_FAILURE;
     }
+  } else {
+    mUserSpaceToFilterSpaceScale = gfxSize(1.0, 1.0);
   }
 
-  mUserSpaceToFilterSpaceScale = canvasTransform.ScaleFactors(true);
-  if (mUserSpaceToFilterSpaceScale.width <= 0.0f ||
-      mUserSpaceToFilterSpaceScale.height <= 0.0f) {
-    // Nothing should be rendered.
-    return NS_ERROR_FAILURE;
-  }
-
-  mFilterSpaceToUserSpaceScale = gfxSize(1.0f / mUserSpaceToFilterSpaceScale.width,
-                                         1.0f / mUserSpaceToFilterSpaceScale.height);
+  mFilterSpaceToUserSpaceScale =
+    gfxSize(1.0f / mUserSpaceToFilterSpaceScale.width,
+            1.0f / mUserSpaceToFilterSpaceScale.height);
   return NS_OK;
 }
 
 gfxRect
 nsFilterInstance::UserSpaceToFilterSpace(const gfxRect& aUserSpaceRect) const
 {
   gfxRect filterSpaceRect = aUserSpaceRect;
   filterSpaceRect.Scale(mUserSpaceToFilterSpaceScale.width,
@@ -369,40 +362,32 @@ nsFilterInstance::BuildSourcePaint(Sourc
 
   RefPtr<DrawTarget> offscreenDT =
     gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(
       neededRect.Size(), SurfaceFormat::B8G8R8A8);
   if (!offscreenDT || !offscreenDT->IsValid()) {
     return DrawResult::TEMPORARY_ERROR;
   }
 
-  gfxMatrix deviceToFilterSpace = GetFilterSpaceToDeviceSpaceTransform();
-  DebugOnly<bool> invertible = deviceToFilterSpace.Invert();
-  MOZ_ASSERT(invertible,
-             "The returning matix of GetFilterSpaceToDeviceSpaceTransform must"
-             "be an invertible matrix(not a singular one), since we already"
-             "checked it and early return if it's not from the caller side"
-             "(nsFilterInstance::Render)");
+  RefPtr<gfxContext> ctx = gfxContext::CreateOrNull(offscreenDT);
+  MOZ_ASSERT(ctx); // already checked the draw target above
+  gfxContextAutoSaveRestore saver(ctx);
 
-  if (!mPaintTransform.IsSingular()) {
-    RefPtr<gfxContext> sourceCtx = gfxContext::CreateOrNull(offscreenDT);
-    MOZ_ASSERT(sourceCtx); // already checked the draw target above
-    sourceCtx->Multiply(mPaintTransform *
-                  deviceToFilterSpace *
-                  gfxMatrix::Translation(-neededRect.TopLeft()));
-    GeneralPattern pattern;
-    if (aSource == &mFillPaint) {
-      nsSVGUtils::MakeFillPatternFor(mTargetFrame, sourceCtx, &pattern);
-    } else if (aSource == &mStrokePaint) {
-      nsSVGUtils::MakeStrokePatternFor(mTargetFrame, sourceCtx, &pattern);
-    }
-    if (pattern.GetPattern()) {
-      offscreenDT->FillRect(ToRect(FilterSpaceToUserSpace(ThebesRect(neededRect))),
-                            pattern);
-    }
+  ctx->SetMatrix(mPaintTransform *
+                 gfxMatrix::Translation(-neededRect.TopLeft()));
+  GeneralPattern pattern;
+  if (aSource == &mFillPaint) {
+    nsSVGUtils::MakeFillPatternFor(mTargetFrame, ctx, &pattern);
+  } else if (aSource == &mStrokePaint) {
+    nsSVGUtils::MakeStrokePatternFor(mTargetFrame, ctx, &pattern);
+  }
+
+  if (pattern.GetPattern()) {
+    offscreenDT->FillRect(ToRect(FilterSpaceToUserSpace(ThebesRect(neededRect))),
+                          pattern);
   }
 
   aSource->mSourceSurface = offscreenDT->Snapshot();
   aSource->mSurfaceRect = neededRect;
 
   return DrawResult::SUCCESS;
 }
 
@@ -456,29 +441,23 @@ nsFilterInstance::BuildSourceImage()
   //
   // (In theory it would be better to minimize error by having filtered SVG
   // graphics temporarily paint to user space when painting the sources and
   // only set a user space to filter space transform on the gfxContext
   // (since that would eliminate the transform multiplications from user
   // space to device space and back again). However, that would make the
   // code more complex while being hard to get right without introducing
   // subtle bugs, and in practice it probably makes no real difference.)
-  gfxMatrix deviceToFilterSpace = GetFilterSpaceToDeviceSpaceTransform();
-  DebugOnly<bool> invertible = deviceToFilterSpace.Invert();
-  MOZ_ASSERT(invertible,
-             "The returning matix of GetFilterSpaceToDeviceSpaceTransform must"
-             "be an invertible matrix(not a singular one), since we already"
-             "checked it and early return if it's not from the caller side"
-             "(nsFilterInstance::Render)");
-
   RefPtr<gfxContext> ctx = gfxContext::CreateOrNull(offscreenDT);
   MOZ_ASSERT(ctx); // already checked the draw target above
-  ctx->SetMatrix(
-    ctx->CurrentMatrix().Translate(-neededRect.TopLeft()).
-                         PreMultiply(deviceToFilterSpace));
+  gfxMatrix devPxToCssPxTM = nsSVGUtils::GetCSSPxToDevPxMatrix(mTargetFrame);
+  DebugOnly<bool> invertible = devPxToCssPxTM.Invert();
+  MOZ_ASSERT(invertible);
+  ctx->SetMatrix(devPxToCssPxTM * mPaintTransform *
+                 gfxMatrix::Translation(-neededRect.TopLeft()));
 
   DrawResult result =
     mPaintCallback->Paint(*ctx, mTargetFrame, mPaintTransform, &dirty);
 
   mSourceGraphic.mSourceSurface = offscreenDT->Snapshot();
   mSourceGraphic.mSurfaceRect = neededRect;
 
   return result;
@@ -491,28 +470,32 @@ nsFilterInstance::Render(DrawTarget* aDr
 
   if (mPrimitiveDescriptions.IsEmpty()) {
     // An filter without any primitive. Treat it as success and paint nothing.
     return DrawResult::SUCCESS;
   }
 
   nsIntRect filterRect =
     mPostFilterDirtyRegion.GetBounds().Intersect(OutputFilterSpaceBounds());
-  gfxMatrix ctm = GetFilterSpaceToDeviceSpaceTransform();
-
-  if (filterRect.IsEmpty() || ctm.IsSingular()) {
+  if (filterRect.IsEmpty() || mPaintTransform.IsSingular()) {
     return DrawResult::SUCCESS;
   }
 
   AutoRestoreTransform autoRestoreTransform(aDrawTarget);
-  Matrix newTM = ToMatrix(ctm).PreTranslate(filterRect.x, filterRect.y) *
-                 aDrawTarget->GetTransform();
-  aDrawTarget->SetTransform(newTM);
+  gfxMatrix filterSpaceToUserSpace = mPaintTransform;
+  DebugOnly<bool> invertible = filterSpaceToUserSpace.Invert();
+  MOZ_ASSERT(invertible);
+  filterSpaceToUserSpace *= nsSVGUtils::GetCSSPxToDevPxMatrix(mTargetFrame);
+
+  aDrawTarget->SetTransform(ToMatrix(filterSpaceToUserSpace) *
+                            aDrawTarget->GetTransform() *
+                            Matrix::Translation(filterRect.TopLeft()));
 
   ComputeNeededBoxes();
+
   DrawResult result = BuildSourceImage();
   if (result != DrawResult::SUCCESS){
     return result;
   }
   result = BuildSourcePaints();
   if (result != DrawResult::SUCCESS){
     return result;
   }
--- a/layout/svg/nsFilterInstance.h
+++ b/layout/svg/nsFilterInstance.h
@@ -197,23 +197,16 @@ private:
    * Sets the aDirty outparam to the pre-filter bounds in frame space of the
    * area of mTargetFrame that is needed in order to paint the filtered output
    * for a given post-filter dirtied area. The post-filter area must have been
    * specified before calling this method by passing it as the aPostFilterDirtyRegion
    * argument to the nsFilterInstance constructor.
    */
   nsRect ComputeSourceNeededRect();
 
-  /**
-   * Returns the transform from filter space to outer-<svg> device space.
-   */
-  gfxMatrix GetFilterSpaceToDeviceSpaceTransform() const {
-    return mFilterSpaceToDeviceSpaceTransform;
-  }
-
   struct SourceInfo {
     // Specifies which parts of the source need to be rendered.
     // Set by ComputeNeededBoxes().
     nsIntRect mNeededBounds;
 
     // The surface that contains the input rendering.
     // Set by BuildSourceImage / BuildSourcePaint.
     RefPtr<SourceSurface> mSourceSurface;
@@ -333,21 +326,16 @@ private:
   gfxRect mTargetBBox;
 
   /**
    * The SVG bbox of the element that is being filtered, in filter space.
    */
   nsIntRect mTargetBBoxInFilterSpace;
 
   /**
-   * The transform from filter space to outer-<svg> device space.
-   */
-  gfxMatrix mFilterSpaceToDeviceSpaceTransform;
-
-  /**
    * Transform rects between filter space and frame space in CSS pixels.
    */
   gfxMatrix mFilterSpaceToFrameSpaceInCSSPxTransform;
   gfxMatrix mFrameSpaceInCSSPxToFilterSpaceTransform;
 
   /**
    * The scale factors between user space and filter space.
    */
--- a/layout/svg/nsSVGIntegrationUtils.cpp
+++ b/layout/svg/nsSVGIntegrationUtils.cpp
@@ -1118,17 +1118,22 @@ nsSVGIntegrationUtils::PaintFilter(const
     context.PushGroupForBlendBack(gfxContentType::COLOR_ALPHA, opacity,
                                   nullptr, Matrix());
   }
 
   /* Paint the child and apply filters */
   RegularFramePaintCallback callback(aParams.builder, aParams.layerManager,
                                      offsets.offsetToUserSpaceInDevPx);
   nsRegion dirtyRegion = aParams.dirtyRect - offsets.offsetToBoundingBox;
-  gfxMatrix tm = nsSVGUtils::GetCSSPxToDevPxMatrix(frame);
+  gfxSize scaleFactors = context.CurrentMatrix().ScaleFactors(true);
+  gfxMatrix scaleMatrix(scaleFactors.width, 0.0f,
+                        0.0f, scaleFactors.height,
+                        0.0f, 0.0f);
+  gfxMatrix tm =
+    scaleMatrix * nsSVGUtils::GetCSSPxToDevPxMatrix(frame);
   DrawResult result =
     nsFilterInstance::PaintFilteredFrame(frame, context.GetDrawTarget(),
                                          tm, &callback, &dirtyRegion);
 
   if (opacity != 1.0f) {
     context.PopGroupAndBlend();
   }