Bug 693521 - Improve preserve-3d sorting behaviour by using line intersection points. r=roc
authorMatt Woodrow <mwoodrow@mozilla.com>
Sat, 15 Oct 2011 09:45:01 +1300
changeset 78796 fa9aa291f91a8c5c7546678ae8d0ce1cb2030358
parent 78795 16bd56ed52ee57b180520f9db21c888aa2238ddd
child 78797 9a6b5bf1a10d4ebcb4a2150f16ecaee40f3d056e
push id2690
push usermwoodrow@mozilla.com
push dateFri, 14 Oct 2011 20:45:56 +0000
treeherdermozilla-inbound@fa9aa291f91a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersroc
bugs693521
milestone10.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 693521 - Improve preserve-3d sorting behaviour by using line intersection points. r=roc
gfx/layers/LayerSorter.cpp
gfx/thebes/Makefile.in
gfx/thebes/gfx3DMatrix.cpp
gfx/thebes/gfxLineSegment.h
gfx/thebes/gfxQuad.h
--- a/gfx/layers/LayerSorter.cpp
+++ b/gfx/layers/LayerSorter.cpp
@@ -33,16 +33,17 @@
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "LayerSorter.h"
 #include "DirectedGraph.h"
 #include "limits.h"
+#include "gfxLineSegment.h"
 
 namespace mozilla {
 namespace layers {
 
 enum LayerSortOrder {
   Undefined,
   ABeforeB,
   BBeforeA,
@@ -73,65 +74,98 @@ static gfxFloat RecoverZDepth(const gfx3
     return n/d;
 }
 
 /**
  * Determine if this transform layer should be drawn before another when they 
  * are both preserve-3d children.
  *
  * We want to find the relative z depths of the 2 layers at points where they
- * intersect when projected onto the 2d screen plane.
+ * intersect when projected onto the 2d screen plane. Intersections are defined
+ * as corners that are positioned within the other quad, as well as intersections
+ * of the lines.
  *
- * If the ordering is consistent at all intersection points, then we have
- * a definitive order, otherwise the 2 layers must actually intersect in 3d
- * space, and we just order these arbitrarily.
+ * We then choose the intersection point with the greatest difference in Z
+ * depths and use this point to determine an ordering for the two layers.
+ * For layers that are intersecting in 3d space, this essentially guesses an 
+ * order. In a lot of cases we only intersect right at the edge point (3d cubes
+ * in particular) and this generates the 'correct' looking ordering. For planes
+ * that truely intersect, then there is no correct ordering and this remains
+ * unsolved without changing our rendering code.
  */
 static LayerSortOrder CompareDepth(Layer* aOne, Layer* aTwo) {
   gfxRect ourRect = aOne->GetEffectiveVisibleRegion().GetBounds();
   gfxRect otherRect = aTwo->GetEffectiveVisibleRegion().GetBounds();
 
   gfx3DMatrix ourTransform = aOne->GetTransform();
   gfx3DMatrix otherTransform = aTwo->GetTransform();
 
   // Transform both rectangles and project into 2d space.
   gfxQuad ourTransformedRect = ourTransform.TransformRect(ourRect);
   gfxQuad otherTransformedRect = otherTransform.TransformRect(otherRect);
 
+  gfxRect ourBounds = ourTransformedRect.GetBounds();
+  gfxRect otherBounds = otherTransformedRect.GetBounds();
+
+  if (!ourBounds.Intersects(otherBounds)) {
+    return Undefined;
+  }
+
   // Make a list of all points that are within the other rect.
+  // Could we just check Contains() on the bounds rects. ie, is it possible
+  // for layers to overlap without intersections (in 2d space) and yet still
+  // have their bounds rects not completely enclose each other?
   nsTArray<gfxPoint> points;
-  for (PRUint32 i=0; i<4; i++) {
+  for (PRUint32 i = 0; i < 4; i++) {
     if (ourTransformedRect.Contains(otherTransformedRect.mPoints[i])) {
       points.AppendElement(otherTransformedRect.mPoints[i]);
     }
     if (otherTransformedRect.Contains(ourTransformedRect.mPoints[i])) {
       points.AppendElement(ourTransformedRect.mPoints[i]);
     }
   }
+  
+  // Look for intersections between lines (in 2d space) and use these as
+  // depth testing points.
+  for (PRUint32 i = 0; i < 4; i++) {
+    for (PRUint32 j = 0; j < 4; j++) {
+      gfxPoint intersection;
+      gfxLineSegment one(ourTransformedRect.mPoints[i],
+                         ourTransformedRect.mPoints[(i + 1) % 4]);
+      gfxLineSegment two(otherTransformedRect.mPoints[j],
+                         otherTransformedRect.mPoints[(j + 1) % 4]);
+      if (one.Intersects(two, intersection)) {
+        points.AppendElement(intersection);
+      }
+    }
+  }
 
   // No intersections, no defined order between these layers.
   if (points.IsEmpty()) {
     return Undefined;
   }
 
   // Find the relative Z depths of each intersection point and check that the layers are in the same order.
-  bool drawBefore = false;
+  gfxFloat highest = 0;
   for (PRUint32 i = 0; i < points.Length(); i++) {
-    bool temp = RecoverZDepth(ourTransform, points.ElementAt(i)) <= RecoverZDepth(otherTransform, points.ElementAt(i));
-    if (i == 0) {
-      drawBefore = temp; 
-    } else if (drawBefore != temp) {
-      // Mixed ordering means an intersection in 3d space that we can't resolve without plane splitting
-      // or depth buffering. Store this as having no defined order for now.
-      return Undefined;
+    gfxFloat ourDepth = RecoverZDepth(ourTransform, points.ElementAt(i));
+    gfxFloat otherDepth = RecoverZDepth(otherTransform, points.ElementAt(i));
+
+    gfxFloat difference = otherDepth - ourDepth;
+
+    if (fabs(difference) > fabs(highest)) {
+      highest = difference;
     }
   }
-  if (drawBefore) {
+  // If layers have the same depth keep the original order
+  if (highest >= 0) {
     return ABeforeB;
+  } else {
+    return BBeforeA;
   }
-  return BBeforeA;
 }
 
 #ifdef DEBUG
 static bool gDumpLayerSortList = getenv("MOZ_DUMP_LAYER_SORT_LIST") != 0;
 
 static void DumpLayerList(nsTArray<Layer*>& aLayers)
 {
   for (PRUint32 i = 0; i < aLayers.Length(); i++) {
@@ -199,54 +233,59 @@ void SortLayersBy3DZOrder(nsTArray<Layer
   noIncoming.AppendElements(aLayers);
   const nsTArray<DirectedGraph<Layer*>::Edge>& edges = graph.GetEdgeList();
   for (PRUint32 i = 0; i < edges.Length(); i++) {
     noIncoming.RemoveElement(edges.ElementAt(i).mTo);
   }
 
   // Move each item without incoming edges into the sorted list,
   // and remove edges from it.
-  while (!noIncoming.IsEmpty()) {
-    PRUint32 last = noIncoming.Length() - 1;
+  do {
+    if (!noIncoming.IsEmpty()) {
+      PRUint32 last = noIncoming.Length() - 1;
 
-    Layer* layer = noIncoming.ElementAt(last);
+      Layer* layer = noIncoming.ElementAt(last);
 
-    noIncoming.RemoveElementAt(last);
-    sortedList.AppendElement(layer);
+      noIncoming.RemoveElementAt(last);
+      sortedList.AppendElement(layer);
 
-    nsTArray<DirectedGraph<Layer*>::Edge> outgoing;
-    graph.GetEdgesFrom(layer, outgoing);
-    for (PRUint32 i = 0; i < outgoing.Length(); i++) {
-      DirectedGraph<Layer*>::Edge edge = outgoing.ElementAt(i);
-      graph.RemoveEdge(edge);
-      if (!graph.NumEdgesTo(edge.mTo)) {
-        // If this node also has no edges now, add it to the list
-        noIncoming.AppendElement(edge.mTo);
+      nsTArray<DirectedGraph<Layer*>::Edge> outgoing;
+      graph.GetEdgesFrom(layer, outgoing);
+      for (PRUint32 i = 0; i < outgoing.Length(); i++) {
+        DirectedGraph<Layer*>::Edge edge = outgoing.ElementAt(i);
+        graph.RemoveEdge(edge);
+        if (!graph.NumEdgesTo(edge.mTo)) {
+          // If this node also has no edges now, add it to the list
+          noIncoming.AppendElement(edge.mTo);
+        }
       }
     }
 
     // If there are no nodes without incoming edges, but there
     // are still edges, then we have a cycle.
     if (noIncoming.IsEmpty() && graph.GetEdgeCount()) {
       // Find the node with the least incoming edges.
       PRUint32 minEdges = UINT_MAX;
       Layer* minNode = nsnull;
       for (PRUint32 i = 0; i < aLayers.Length(); i++) {
         PRUint32 edgeCount = graph.NumEdgesTo(aLayers.ElementAt(i));
         if (edgeCount && edgeCount < minEdges) {
           minEdges = edgeCount;
           minNode = aLayers.ElementAt(i);
+          if (minEdges == 1) {
+            break;
+          }
         }
       }
 
       // Remove all of them!
       graph.RemoveEdgesTo(minNode);
       noIncoming.AppendElement(minNode);
     }
-  }
+  } while (!noIncoming.IsEmpty());
   NS_ASSERTION(!graph.GetEdgeCount(), "Cycles detected!");
 #ifdef DEBUG
   if (gDumpLayerSortList) {
     fprintf(stderr, " --- Layers after sorting: --- \n");
     DumpLayerList(sortedList);
   }
 #endif
 
--- a/gfx/thebes/Makefile.in
+++ b/gfx/thebes/Makefile.in
@@ -22,16 +22,17 @@ EXPORTS	= \
 	gfxContext.h \
 	gfxDrawable.h \
 	gfxFailure.h \
 	gfxFont.h \
 	gfxFontConstants.h \
 	gfxFontUtils.h \
 	gfxFontTest.h \
 	gfxImageSurface.h \
+	gfxLineSegment.h \
 	gfxMatrix.h \
 	gfxPath.h \
 	gfxPattern.h \
 	gfxPlatform.h \
 	gfxPoint.h \
 	gfxPoint3D.h \
 	gfxPointH3D.h \
 	gfxQuad.h \
--- a/gfx/thebes/gfx3DMatrix.cpp
+++ b/gfx/thebes/gfx3DMatrix.cpp
@@ -672,18 +672,17 @@ gfx3DMatrix::TransformRect(const gfxRect
 {
   gfxPoint points[4];
 
   points[0] = Transform(aRect.TopLeft());
   points[1] = Transform(gfxPoint(aRect.X() + aRect.Width(), aRect.Y()));
   points[2] = Transform(gfxPoint(aRect.X() + aRect.Width(),
                                  aRect.Y() + aRect.Height()));
   points[3] = Transform(gfxPoint(aRect.X(), aRect.Y() + aRect.Height()));
-
-
+  
   // Could this ever result in lines that intersect? I don't think so.
   return gfxQuad(points[0], points[1], points[2], points[3]);
 }
 
 bool
 gfx3DMatrix::Is2D() const
 {
   if (_13 != 0.0f || _14 != 0.0f ||
new file mode 100644
--- /dev/null
+++ b/gfx/thebes/gfxLineSegment.h
@@ -0,0 +1,109 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla Corporation code.
+ *
+ * The Initial Developer of the Original Code is Oracle Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2005
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Matt Woodrow <mwoodrow@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#ifndef GFX_LINESEGMENT_H
+#define GFX_LINESEGMENT_H
+
+#include "gfxTypes.h"
+#include "gfxPoint.h"
+
+struct THEBES_API gfxLineSegment {
+  gfxLineSegment(const gfxPoint &aStart, const gfxPoint &aEnd) 
+    : mStart(aStart)
+    , mEnd(aEnd)
+  {}
+
+  bool PointsOnSameSide(const gfxPoint& aOne, const gfxPoint& aTwo)
+  {
+    // Solve the equation y - mStart.y - ((mEnd.y - mStart.y)/(mEnd.x - mStart.x))(x - mStart.x) for both points 
+  
+    gfxFloat deltaY = (mEnd.y - mStart.y);
+    gfxFloat deltaX = (mEnd.x - mStart.x);
+  
+    gfxFloat one = deltaX * (aOne.y - mStart.y) - deltaY * (aOne.x - mStart.x);
+    gfxFloat two = deltaX * (aTwo.y - mStart.y) - deltaY * (aTwo.x - mStart.x);
+
+    // If both results have the same sign, then we're on the correct side of the line.
+    // 0 (on the line) is always considered in.
+
+    if ((one >= 0 && two >= 0) || (one <= 0 && two <= 0))
+      return true;
+    return false;
+  }
+
+  /**
+   * Determines if two line segments intersect, and returns the intersection
+   * point in aIntersection if they do.
+   *
+   * Coincident lines are considered not intersecting as they don't have an
+   * intersection point.
+   */
+  bool Intersects(const gfxLineSegment& aOther, gfxPoint& aIntersection)
+  {
+    gfxFloat denominator = (aOther.mEnd.y - aOther.mStart.y) * (mEnd.x - mStart.x ) - 
+                           (aOther.mEnd.x - aOther.mStart.x ) * (mEnd.y - mStart.y);
+
+    // Parallel or coincident. We treat coincident as not intersecting since
+    // these lines are guaranteed to have corners that intersect instead.
+    if (!denominator) {
+      return false;
+    }
+
+    gfxFloat anumerator = (aOther.mEnd.x - aOther.mStart.x) * (mStart.y - aOther.mStart.y) -
+                         (aOther.mEnd.y - aOther.mStart.y) * (mStart.x - aOther.mStart.x);
+  
+    gfxFloat bnumerator = (mEnd.x - mStart.x) * (mStart.y - aOther.mStart.y) -
+                         (mEnd.y - mStart.y) * (mStart.x - aOther.mStart.x);
+
+    gfxFloat ua = anumerator / denominator;
+    gfxFloat ub = bnumerator / denominator;
+
+    if (ua <= 0.0 || ua >= 1.0 ||
+        ub <= 0.0 || ub >= 1.0) {
+      //Intersection is outside of the segment
+      return false;
+    }
+
+    aIntersection = mStart + (mEnd - mStart) * ua;  
+    return true;
+  }
+
+  gfxPoint mStart;
+  gfxPoint mEnd;
+};
+
+#endif /* GFX_LINESEGMENT_H */
--- a/gfx/thebes/gfxQuad.h
+++ b/gfx/thebes/gfxQuad.h
@@ -33,55 +33,49 @@
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef GFX_QUAD_H
 #define GFX_QUAD_H
 
-#include "nsMathUtils.h"
-#include "mozilla/gfx/BaseSize.h"
-#include "mozilla/gfx/BasePoint.h"
-#include "nsSize.h"
-#include "nsPoint.h"
-
 #include "gfxTypes.h"
-
-static PRBool SameSideOfLine(const gfxPoint& aPoint1, const gfxPoint& aPoint2, const gfxPoint& aTest, const gfxPoint& aRef)
-{
-  // Solve the equation y - aPoint1.y - ((aPoint2.y - aPoint1.y)/(aPoint2.x - aPoint1.x))(x - aPoint1.x) for both test and ref
-  
-  gfxFloat deltaY = (aPoint2.y - aPoint1.y);
-  gfxFloat deltaX = (aPoint2.x - aPoint1.x);
-  
-  gfxFloat test = deltaX * (aTest.y - aPoint1.y) - deltaY * (aTest.x - aPoint1.x);
-  gfxFloat ref = deltaX * (aRef.y - aPoint1.y) - deltaY * (aRef.x - aPoint1.x);
-
-  // If both results have the same sign, then we're on the correct side of the line.
-  // 0 (on the line) is always considered in.
-
-  if ((test >= 0 && ref >= 0) || (test <= 0 && ref <= 0))
-    return PR_TRUE;
-  return PR_FALSE;
-}
+#include "gfxLineSegment.h"
 
 struct THEBES_API gfxQuad {
     gfxQuad(const gfxPoint& aOne, const gfxPoint& aTwo, const gfxPoint& aThree, const gfxPoint& aFour)
     {
         mPoints[0] = aOne;
         mPoints[1] = aTwo;
         mPoints[2] = aThree;
         mPoints[3] = aFour;
     }
 
     PRBool Contains(const gfxPoint& aPoint)
     {
-        return (SameSideOfLine(mPoints[0], mPoints[1], aPoint, mPoints[2]) &&
-                SameSideOfLine(mPoints[1], mPoints[2], aPoint, mPoints[3]) &&
-                SameSideOfLine(mPoints[2], mPoints[3], aPoint, mPoints[0]) &&
-                SameSideOfLine(mPoints[3], mPoints[0], aPoint, mPoints[1]));
+        return (gfxLineSegment(mPoints[0], mPoints[1]).PointsOnSameSide(aPoint, mPoints[2]) &&
+                gfxLineSegment(mPoints[1], mPoints[2]).PointsOnSameSide(aPoint, mPoints[3]) &&
+                gfxLineSegment(mPoints[2], mPoints[3]).PointsOnSameSide(aPoint, mPoints[0]) &&
+                gfxLineSegment(mPoints[3], mPoints[0]).PointsOnSameSide(aPoint, mPoints[1]));
+    }
+
+    gfxRect GetBounds()
+    {
+        gfxFloat min_x, max_x;
+        gfxFloat min_y, max_y;
+
+        min_x = max_x = mPoints[0].x;
+        min_y = max_y = mPoints[0].y;
+
+        for (int i=1; i<4; i++) {
+          min_x = NS_MIN(mPoints[i].x, min_x);
+          max_x = NS_MAX(mPoints[i].x, max_x);
+          min_y = NS_MIN(mPoints[i].y, min_y);
+          max_y = NS_MAX(mPoints[i].y, max_y);
+        }
+        return gfxRect(min_x, min_y, max_x - min_x, max_y - min_y);
     }
 
     gfxPoint mPoints[4];
 };
 
 #endif /* GFX_QUAD_H */