Bug 1173521 - Properly handle unapplying 3D projective transforms to rectangles in APZ code. r=botond
authorKevin Wern <kevin.m.wern@gmail.com>
Fri, 28 Aug 2015 12:00:29 -0400
changeset 292499 fb5a9ebbca96d2c9eca67bdaca0fea389abda1c4
parent 292498 2d89bf9f70ee66f82a482c28070ae2d8b8bacdcb
child 292500 9a074478c2d3498a7c5eec0cd732ba4cc4ee0e14
push id5245
push userraliiev@mozilla.com
push dateThu, 29 Oct 2015 11:30:51 +0000
treeherdermozilla-beta@dac831dc1bd0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbotond
bugs1173521
milestone43.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 1173521 - Properly handle unapplying 3D projective transforms to rectangles in APZ code. r=botond The untransformation is done using the newly added UntransformTo() functions which call Matrix4x4::ProjectRectBounds(), which returns an empty rectangle if the result is not at least partially on the positive side of the w = 0 plane. UntransformTo() returnis the transformed rectangle if it's not empty, and Nothing() otherwise, making callers check for this case. The patch also adds some assertions to places where we apply transforms we know should be 2D (and thus didn't switch to use UntransformTo()), checking that the transforms are in fact 2D.
gfx/layers/client/ClientTiledPaintedLayer.cpp
gfx/layers/client/TiledContentClient.cpp
gfx/layers/client/TiledContentClient.h
gfx/layers/composite/AsyncCompositionManager.cpp
layout/base/UnitTransforms.h
--- a/gfx/layers/client/ClientTiledPaintedLayer.cpp
+++ b/gfx/layers/client/ClientTiledPaintedLayer.cpp
@@ -50,20 +50,20 @@ ClientTiledPaintedLayer::ClearCachedReso
 }
 
 void
 ClientTiledPaintedLayer::FillSpecificAttributes(SpecificLayerAttributes& aAttrs)
 {
   aAttrs = PaintedLayerAttributes(GetValidRegion());
 }
 
-static LayerRect
-ApplyParentLayerToLayerTransform(const gfx::Matrix4x4& aTransform, const ParentLayerRect& aParentLayerRect)
+static Maybe<LayerRect>
+ApplyParentLayerToLayerTransform(const gfx::Matrix4x4& aTransform, const ParentLayerRect& aParentLayerRect, const LayerRect& aClip)
 {
-  return TransformTo<LayerPixel>(aTransform, aParentLayerRect);
+  return UntransformTo<LayerPixel>(aTransform, aParentLayerRect, aClip);
 }
 
 static gfx::Matrix4x4
 GetTransformToAncestorsParentLayer(Layer* aStart, const LayerMetricsWrapper& aAncestor)
 {
   gfx::Matrix4x4 transform;
   const LayerMetricsWrapper& ancestorParent = aAncestor.GetParent();
   for (LayerMetricsWrapper iter(aStart, LayerMetricsWrapper::StartAt::BOTTOM);
@@ -119,20 +119,17 @@ ClientTiledPaintedLayer::GetAncestorLaye
   if (aOutHasTransformAnimation) {
     *aOutHasTransformAnimation = hasTransformAnimation;
   }
 }
 
 void
 ClientTiledPaintedLayer::BeginPaint()
 {
-  mPaintData.mLowPrecisionPaintCount = 0;
-  mPaintData.mPaintFinished = false;
-  mPaintData.mCompositionBounds.SetEmpty();
-  mPaintData.mCriticalDisplayPort.SetEmpty();
+  mPaintData.ResetPaintData();
 
   if (!GetBaseTransform().Is2D()) {
     // Give up if there is a complex CSS transform on the layer. We might
     // eventually support these but for now it's too complicated to handle
     // given that it's a pretty rare scenario.
     return;
   }
 
@@ -160,43 +157,55 @@ ClientTiledPaintedLayer::BeginPaint()
   const FrameMetrics& displayportMetrics = displayPortAncestor.Metrics();
 
   // Calculate the transform required to convert ParentLayer space of our
   // display port ancestor to the Layer space of this layer.
   gfx::Matrix4x4 transformDisplayPortToLayer =
     GetTransformToAncestorsParentLayer(this, displayPortAncestor);
   transformDisplayPortToLayer.Invert();
 
+  LayerRect layerBounds = ViewAs<LayerPixel>(Rect(GetLayerBounds()));
+
   // Compute the critical display port that applies to this layer in the
   // LayoutDevice space of this layer, but only if there is no OMT animation
   // on this layer. If there is an OMT animation then we need to draw the whole
   // visible region of this layer as determined by layout, because we don't know
   // what parts of it might move into view in the compositor.
   if (!hasTransformAnimation &&
       mContentClient->GetLowPrecisionTiledBuffer()) {
     ParentLayerRect criticalDisplayPort =
       (displayportMetrics.GetCriticalDisplayPort() * displayportMetrics.GetZoom())
       + displayportMetrics.GetCompositionBounds().TopLeft();
-    mPaintData.mCriticalDisplayPort = RoundedToInt(
-      ApplyParentLayerToLayerTransform(transformDisplayPortToLayer, criticalDisplayPort));
+    Maybe<LayerRect> criticalDisplayPortTransformed =
+      ApplyParentLayerToLayerTransform(transformDisplayPortToLayer, criticalDisplayPort, layerBounds);
+    if (!criticalDisplayPortTransformed) {
+      mPaintData.ResetPaintData();
+      return;
+    }
+    mPaintData.mCriticalDisplayPort = RoundedToInt(*criticalDisplayPortTransformed);
   }
   TILING_LOG("TILING %p: Critical displayport %s\n", this, Stringify(mPaintData.mCriticalDisplayPort).c_str());
 
   // Store the resolution from the displayport ancestor layer. Because this is Gecko-side,
   // before any async transforms have occurred, we can use the zoom for this.
   mPaintData.mResolution = displayportMetrics.GetZoom();
   TILING_LOG("TILING %p: Resolution %s\n", this, Stringify(mPaintData.mResolution).c_str());
 
   // Store the applicable composition bounds in this layer's Layer units.
   mPaintData.mTransformToCompBounds =
     GetTransformToAncestorsParentLayer(this, scrollAncestor);
   gfx::Matrix4x4 transformToBounds = mPaintData.mTransformToCompBounds;
   transformToBounds.Invert();
-  mPaintData.mCompositionBounds = ApplyParentLayerToLayerTransform(
-    transformToBounds, scrollMetrics.GetCompositionBounds());
+  Maybe<LayerRect> compositionBoundsTransformed = ApplyParentLayerToLayerTransform(
+    transformToBounds, scrollMetrics.GetCompositionBounds(), layerBounds);
+  if (!compositionBoundsTransformed) {
+    mPaintData.ResetPaintData();
+    return;
+  }
+  mPaintData.mCompositionBounds = *compositionBoundsTransformed;
   TILING_LOG("TILING %p: Composition bounds %s\n", this, Stringify(mPaintData.mCompositionBounds).c_str());
 
   // Calculate the scroll offset since the last transaction
   mPaintData.mScrollOffset = displayportMetrics.GetScrollOffset() * displayportMetrics.GetZoom();
   TILING_LOG("TILING %p: Scroll offset %s\n", this, Stringify(mPaintData.mScrollOffset).c_str());
 }
 
 bool
--- a/gfx/layers/client/TiledContentClient.cpp
+++ b/gfx/layers/client/TiledContentClient.cpp
@@ -1393,24 +1393,26 @@ ClientMultiTiledLayerBuffer::ValidateTil
  * (which was generated in GetTransformToAncestorsParentLayer), and
  * modifies it with the ViewTransform from the compositor side so that
  * it reflects what the compositor is actually rendering. This operation
  * basically adds in the layer's async transform.
  * This function then returns the scroll ancestor's composition bounds,
  * transformed into the painted layer's LayerPixel coordinates, accounting
  * for the compositor state.
  */
-static LayerRect
+static Maybe<LayerRect>
 GetCompositorSideCompositionBounds(const LayerMetricsWrapper& aScrollAncestor,
                                    const Matrix4x4& aTransformToCompBounds,
-                                   const ViewTransform& aAPZTransform)
+                                   const ViewTransform& aAPZTransform,
+                                   const LayerRect& aClip)
 {
   Matrix4x4 transform = aTransformToCompBounds * Matrix4x4(aAPZTransform);
-  return TransformTo<LayerPixel>(transform.Inverse(),
-            aScrollAncestor.Metrics().GetCompositionBounds());
+
+  return UntransformTo<LayerPixel>(transform.Inverse(),
+    aScrollAncestor.Metrics().GetCompositionBounds(), aClip);
 }
 
 bool
 ClientMultiTiledLayerBuffer::ComputeProgressiveUpdateRegion(const nsIntRegion& aInvalidRegion,
                                                             const nsIntRegion& aOldValidRegion,
                                                             nsIntRegion& aRegionToPaint,
                                                             BasicTiledLayerPaintData* aPaintData,
                                                             bool aIsRepeated)
@@ -1479,37 +1481,43 @@ ClientMultiTiledLayerBuffer::ComputeProg
       PROFILER_LABEL("ClientMultiTiledLayerBuffer", "ComputeProgressiveUpdateRegion",
         js::ProfileEntry::Category::GRAPHICS);
 
       aRegionToPaint.SetEmpty();
       return aIsRepeated;
     }
   }
 
-  LayerRect transformedCompositionBounds =
+  Maybe<LayerRect> transformedCompositionBounds =
     GetCompositorSideCompositionBounds(scrollAncestor,
                                        aPaintData->mTransformToCompBounds,
-                                       viewTransform);
+                                       viewTransform,
+                                       ViewAs<LayerPixel>(Rect(mPaintedLayer->GetLayerBounds())));
 
-  TILING_LOG("TILING %p: Progressive update transformed compositor bounds %s\n", mPaintedLayer, Stringify(transformedCompositionBounds).c_str());
+  if (!transformedCompositionBounds) {
+    aPaintData->mPaintFinished = true;
+    return false;
+  }
+
+  TILING_LOG("TILING %p: Progressive update transformed compositor bounds %s\n", mPaintedLayer, Stringify(*transformedCompositionBounds).c_str());
 
   // Compute a "coherent update rect" that we should paint all at once in a
   // single transaction. This is to avoid rendering glitches on animated
   // page content, and when layers change size/shape.
   // On Fennec uploads are more expensive because we're not using gralloc, so
   // we use a coherent update rect that is intersected with the screen at the
   // time of issuing the draw command. This will paint faster but also potentially
   // make the progressive paint more visible to the user while scrolling.
   // On B2G uploads are cheaper and we value coherency more, especially outside
   // the browser, so we always use the entire user-visible area.
   IntRect coherentUpdateRect(LayerIntRect::ToUntyped(RoundedOut(
 #ifdef MOZ_WIDGET_ANDROID
-    transformedCompositionBounds.Intersect(aPaintData->mCompositionBounds)
+    transformedCompositionBounds->Intersect(aPaintData->mCompositionBounds)
 #else
-    transformedCompositionBounds
+    *transformedCompositionBounds
 #endif
   )));
 
   TILING_LOG("TILING %p: Progressive update final coherency rect %s\n", mPaintedLayer, Stringify(coherentUpdateRect).c_str());
 
   aRegionToPaint.And(aInvalidRegion, coherentUpdateRect);
   aRegionToPaint.Or(aRegionToPaint, staleRegion);
   bool drawingStale = !aRegionToPaint.IsEmpty();
@@ -1674,10 +1682,19 @@ TiledContentClient::PrintInfo(std::strin
 void
 TiledContentClient::Dump(std::stringstream& aStream,
                          const char* aPrefix,
                          bool aDumpHtml)
 {
   GetTiledBuffer()->Dump(aStream, aPrefix, aDumpHtml);
 }
 
+void
+BasicTiledLayerPaintData::ResetPaintData()
+{
+  mLowPrecisionPaintCount = 0;
+  mPaintFinished = false;
+  mCompositionBounds.SetEmpty();
+  mCriticalDisplayPort.SetEmpty();
+}
+
 } // namespace layers
 } // namespace mozilla
--- a/gfx/layers/client/TiledContentClient.h
+++ b/gfx/layers/client/TiledContentClient.h
@@ -347,16 +347,21 @@ struct BasicTiledLayerPaintData {
   bool mFirstPaint : 1;
 
   /*
    * Whether there is further work to complete this paint. This is used to
    * determine whether or not to repeat the transaction when painting
    * progressively.
    */
   bool mPaintFinished : 1;
+
+  /*
+   * Initializes/clears data to prepare for paint action.
+   */
+  void ResetPaintData();
 };
 
 class SharedFrameMetricsHelper
 {
 public:
   SharedFrameMetricsHelper();
   ~SharedFrameMetricsHelper();
 
--- a/gfx/layers/composite/AsyncCompositionManager.cpp
+++ b/gfx/layers/composite/AsyncCompositionManager.cpp
@@ -153,16 +153,17 @@ GetBaseTransform(Layer* aLayer, Matrix4x
         ? aLayer->GetLocalTransform()
         : aLayer->GetTransform());
 }
 
 static void
 TransformClipRect(Layer* aLayer,
                   const Matrix4x4& aTransform)
 {
+  MOZ_ASSERT(aTransform.Is2D());
   const Maybe<ParentLayerIntRect>& clipRect = aLayer->AsLayerComposite()->GetShadowClipRect();
   if (clipRect) {
     ParentLayerIntRect transformed = TransformTo<ParentLayerPixel>(aTransform, *clipRect);
     aLayer->AsLayerComposite()->SetShadowClipRect(Some(transformed));
   }
 }
 
 /**
@@ -658,16 +659,17 @@ AsyncCompositionManager::ApplyAsyncConte
 
     mIsFirstPaint = false;
     mLayersUpdated = false;
 
     // Transform the current local clip by this APZC's async transform. If we're
     // using containerful scrolling, then the clip is not part of the scrolled
     // frame and should not be transformed.
     if (asyncClip && !metrics.UsesContainerScrolling()) {
+      MOZ_ASSERT(asyncTransform.Is2D());
       asyncClip = Some(TransformTo<ParentLayerPixel>(asyncTransform, *asyncClip));
     }
 
     // Combine the local clip with the ancestor scrollframe clip. This is not
     // included in the async transform above, since the ancestor clip should not
     // move with this APZC.
     if (metrics.HasClipRect()) {
       ParentLayerIntRect clip = metrics.ClipRect();
--- a/layout/base/UnitTransforms.h
+++ b/layout/base/UnitTransforms.h
@@ -175,16 +175,44 @@ static Maybe<gfx::IntPointTyped<TargetUn
                                                 const gfx::IntPointTyped<SourceUnits>& aPoint)
 {
   gfx::Point4D point = aTransform.ProjectPoint(aPoint.ToUnknownPoint());
   if (!point.HasPositiveWCoord()) {
     return Nothing();
   }
   return Some(RoundedToInt(ViewAs<TargetUnits>(point.As2DPoint())));
 }
+
+// The versions of UntransformTo() that take a rectangle also take a clip,
+// which represents the bounds within which the target must fall. The
+// result of the transform is intersected with this clip, and is considered
+// meaningful if the intersection is not empty.
+template <typename TargetUnits, typename SourceUnits>
+static Maybe<gfx::RectTyped<TargetUnits>> UntransformTo(const gfx::Matrix4x4& aTransform,
+                                                const gfx::RectTyped<SourceUnits>& aRect,
+                                                const gfx::RectTyped<TargetUnits>& aClip)
+{
+  gfx::Rect rect = aTransform.ProjectRectBounds(aRect.ToUnknownRect(), aClip.ToUnknownRect());
+  if (rect.IsEmpty()) {
+    return Nothing();
+  }
+  return Some(ViewAs<TargetUnits>(rect));
+}
+template <typename TargetUnits, typename SourceUnits>
+static Maybe<gfx::IntRectTyped<TargetUnits>> UntransformTo(const gfx::Matrix4x4& aTransform,
+                                                const gfx::IntRectTyped<SourceUnits>& aRect,
+                                                const gfx::IntRectTyped<TargetUnits>& aClip)
+{
+  gfx::Rect rect = aTransform.ProjectRectBounds(aRect.ToUnknownRect(), aClip.ToUnknownRect());
+  if (rect.IsEmpty()) {
+    return Nothing();
+  }
+  return Some(RoundedToInt(ViewAs<TargetUnits>(rect)));
+}
+
 template <typename TargetUnits, typename SourceUnits>
 static Maybe<gfx::PointTyped<TargetUnits>> UntransformVector(const gfx::Matrix4x4& aTransform,
                                                     const gfx::PointTyped<SourceUnits>& aVector,
                                                     const gfx::PointTyped<SourceUnits>& aAnchor) {
   gfx::Point4D projectedAnchor = aTransform.ProjectPoint(aAnchor.ToUnknownPoint());
   gfx::Point4D projectedTarget = aTransform.ProjectPoint(aAnchor.ToUnknownPoint() + aVector.ToUnknownPoint());
   if (!projectedAnchor.HasPositiveWCoord() || !projectedTarget.HasPositiveWCoord()){
     return Nothing();