Bug 1072898: Part 1 - Correct rendering of layers that are both in front and behind the w=0 plane. r=mattwoodrow
☠☠ backed out by ab4eee1c6d7e ☠ ☠
authorKearwood (Kip) Gilbert <kgilbert@mozilla.com>
Tue, 21 Apr 2015 16:57:18 -0700
changeset 273970 61bd6e2c6e723fa9eb5fc5a6f1b4fe6a0de88032
parent 273969 7141b7088a8933bfaaefd9e46318ed4ebb2dd021
child 273971 230adc57e016d687dcd5b0899ed16c5d6c78638f
push id863
push userraliiev@mozilla.com
push dateMon, 03 Aug 2015 13:22:43 +0000
treeherdermozilla-release@f6321b14228d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmattwoodrow
bugs1072898
milestone40.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 1072898: Part 1 - Correct rendering of layers that are both in front and behind the w=0 plane. r=mattwoodrow - The BasicCompositor now transforms vertices prior to rendering in Skia, using a function that clips against frustum clipping planes in homogenous coordinate space.
gfx/2d/Matrix.cpp
gfx/2d/Matrix.h
gfx/layers/basic/BasicCompositor.cpp
--- a/gfx/2d/Matrix.cpp
+++ b/gfx/2d/Matrix.cpp
@@ -222,16 +222,91 @@ Rect Matrix4x4::ProjectRectBounds(const 
 
   if (max_x < min_x || max_y < min_y) {
     return Rect(0, 0, 0, 0);
   }
 
   return Rect(min_x, min_y, max_x - min_x, max_y - min_y);
 }
 
+
+size_t
+Matrix4x4::TransformAndClipRect(const Rect& aRect, const Rect& aClip, Point* aVerts) const
+{
+  // Initialize a double-buffered array of points in homogenous space with
+  // the input rectangle, aRect.
+  Point4D points[2][kTransformAndClipRectMaxVerts];
+  Point4D* dstPoint = points[0];
+  *dstPoint++ = *this * Point4D(aRect.x, aRect.y, 0, 1);
+  *dstPoint++ = *this * Point4D(aRect.XMost(), aRect.y, 0, 1);
+  *dstPoint++ = *this * Point4D(aRect.XMost(), aRect.YMost(), 0, 1);
+  *dstPoint++ = *this * Point4D(aRect.x, aRect.YMost(), 0, 1);
+
+  // View frustum clipping planes are described as normals originating from
+  // the 0,0,0,0 origin.
+  Point4D planeNormals[4];
+  planeNormals[0] = Point4D(1.0, 0.0, 0.0, -aClip.x);
+  planeNormals[1] = Point4D(-1.0, 0.0, 0.0, aClip.XMost());
+  planeNormals[2] = Point4D(0.0, 1.0, 0.0, -aClip.y);
+  planeNormals[3] = Point4D(0.0, -1.0, 0.0, aClip.YMost());
+
+  // Iterate through each clipping plane and clip the polygon.
+  // In each pass, we double buffer, alternating between points[0] and
+  // points[1].
+  for (int plane=0; plane < 4; plane++) {
+    planeNormals[plane].Normalize();
+
+    Point4D* srcPoint = points[plane & 1];
+    Point4D* srcPointEnd = dstPoint;
+    dstPoint = points[~plane & 1];
+
+    Point4D* prevPoint = srcPointEnd - 1;
+    float prevDot = planeNormals[plane].DotProduct(*prevPoint);
+    while (srcPoint < srcPointEnd) {
+      float nextDot = planeNormals[plane].DotProduct(*srcPoint);
+
+      if ((nextDot >= 0.0) != (prevDot >= 0.0)) {
+        // An intersection with the clipping plane has been detected.
+        // Interpolate to find the intersecting point and emit it.
+        float t = -prevDot / (nextDot - prevDot);
+        *dstPoint++ = *srcPoint * t + *prevPoint * (1.0 - t);
+      }
+
+      if (nextDot >= 0.0) {
+        // Emit any source points that are on the positive side of the
+        // clipping plane.
+        *dstPoint++ = *srcPoint;
+      }
+
+      prevPoint = srcPoint++;
+      prevDot = nextDot;
+    }
+  }
+
+  size_t dstPointCount = 0;
+  size_t srcPointCount = dstPoint - points[0];
+  for (Point4D* srcPoint = points[0]; srcPoint < points[0] + srcPointCount; srcPoint++) {
+
+    Point p;
+    if (srcPoint->w == 0.0) {
+      // If a point lies on the intersection of the clipping planes at
+      // (0,0,0,0), we must avoid a division by zero w component.
+      p = Point(0.0, 0.0);
+    } else {
+      p = srcPoint->As2DPoint();
+    }
+    // Emit only unique points
+    if (dstPointCount == 0 || p != aVerts[dstPointCount - 1]) {
+      aVerts[dstPointCount++] = p;
+    }
+  }
+
+  return dstPointCount;
+}
+
 bool
 Matrix4x4::Invert()
 {
   Float det = Determinant();
   if (!det) {
     return false;
   }
 
--- a/gfx/2d/Matrix.h
+++ b/gfx/2d/Matrix.h
@@ -477,17 +477,30 @@ public:
 
     // Solving for z when z' = 0 gives us:
     float z = -(aPoint.x * _13 + aPoint.y * _23 + _43) / _33;
 
     // Compute the transformed point
     return *this * Point4D(aPoint.x, aPoint.y, z, 1);
   }
 
-  Rect ProjectRectBounds(const Rect& aRect, const Rect &aClip) const;
+  Rect ProjectRectBounds(const Rect& aRect, const Rect& aClip) const;
+
+  /**
+   * TransformAndClipRect projects a rectangle and clips against view frustum
+   * clipping planes in homogenous space so that its projected vertices are
+   * constrained within the 2d rectangle passed in aClip.
+   * The resulting vertices are populated in aVerts.  aVerts must be
+   * pre-allocated to hold at least kTransformAndClipRectMaxVerts Points.
+   * The vertex count is returned by TransformAndClipRect.  It is possible to
+   * emit fewer that 3 vertices, indicating that aRect will not be visible
+   * within aClip.
+   */
+  size_t TransformAndClipRect(const Rect& aRect, const Rect& aClip, Point* aVerts) const;
+  static const size_t kTransformAndClipRectMaxVerts = 32;
 
   static Matrix4x4 From2D(const Matrix &aMatrix) {
     Matrix4x4 matrix;
     matrix._11 = aMatrix._11;
     matrix._12 = aMatrix._12;
     matrix._21 = aMatrix._21;
     matrix._22 = aMatrix._22;
     matrix._41 = aMatrix._31;
--- a/gfx/layers/basic/BasicCompositor.cpp
+++ b/gfx/layers/basic/BasicCompositor.cpp
@@ -15,16 +15,17 @@
 #include "gfxUtils.h"
 #include "YCbCrUtils.h"
 #include <algorithm>
 #include "ImageContainer.h"
 #include "gfxPrefs.h"
 #ifdef MOZ_ENABLE_SKIA
 #include "skia/SkCanvas.h"              // for SkCanvas
 #include "skia/SkBitmapDevice.h"        // for SkBitmapDevice
+#include "skia/SkShader.h"              // for SkShader
 #else
 #define PIXMAN_DONT_DEFINE_STDINT
 #include "pixman.h"                     // for pixman_f_transform, etc
 #endif
 
 namespace mozilla {
 using namespace mozilla::gfx;
 
@@ -177,17 +178,17 @@ DrawSurfaceWithTextureCoords(DrawTarget 
   ExtendMode mode = unitRect.Contains(aTextureCoords) ? ExtendMode::CLAMP : ExtendMode::REPEAT;
 
   FillRectWithMask(aDest, aDestRect, aSource, aFilter, DrawOptions(aOpacity),
                    mode, aMask, aMaskTransform, &matrix);
 }
 
 #ifdef MOZ_ENABLE_SKIA
 static SkMatrix
-Matrix3DToSkia(const gfx3DMatrix& aMatrix)
+MatrixToSkia(const Matrix4x4& aMatrix)
 {
   SkMatrix transform;
   transform.setAll(aMatrix._11,
                    aMatrix._21,
                    aMatrix._41,
                    aMatrix._12,
                    aMatrix._22,
                    aMatrix._42,
@@ -196,52 +197,81 @@ Matrix3DToSkia(const gfx3DMatrix& aMatri
                    aMatrix._44);
 
   return transform;
 }
 
 static void
 Transform(DataSourceSurface* aDest,
           DataSourceSurface* aSource,
-          const gfx3DMatrix& aTransform,
+          const Matrix4x4& aTransform,
           const Point& aDestOffset)
 {
+  // Skia does not correctly transform vertices that are behind the camera.
+  // To work around this, we clip the geometry against the view frustum
+  // clipping planes in homogenous space before passing them to Skia.
+  // A Skia bitmap shader is initialized with a transformation matrix to
+  // transform the texture contents of the polygon.
+
   if (aTransform.IsSingular()) {
     return;
   }
 
+  Matrix4x4 transform = aTransform;
+  transform.PostTranslate(Point3D(-aDestOffset.x, -aDestOffset.y, 0));
+
+  IntSize srcSize = aSource->GetSize();
   IntSize destSize = aDest->GetSize();
+  Point verts[Matrix4x4::kTransformAndClipRectMaxVerts];
+  Rect srcRect = Rect(0, 0, srcSize.width, srcSize.height);
+  Rect destRect = Rect(0, 0, destSize.width, destSize.height);
+  size_t vertCount = transform.TransformAndClipRect(srcRect, destRect, verts);
+  if (vertCount < 3) {
+    // If we have fewer than 3 vertices, then the polygon has been completely
+    // clipped out and there is nothing to render.
+    return;
+  }
+
   SkImageInfo destInfo = SkImageInfo::Make(destSize.width,
                                            destSize.height,
                                            kBGRA_8888_SkColorType,
                                            kPremul_SkAlphaType);
   SkBitmap destBitmap;
   destBitmap.setInfo(destInfo, aDest->Stride());
   destBitmap.setPixels((uint32_t*)aDest->GetData());
   SkCanvas destCanvas(destBitmap);
 
-  IntSize srcSize = aSource->GetSize();
   SkImageInfo srcInfo = SkImageInfo::Make(srcSize.width,
                                           srcSize.height,
                                           kBGRA_8888_SkColorType,
                                           kPremul_SkAlphaType);
   SkBitmap src;
   src.setInfo(srcInfo, aSource->Stride());
   src.setPixels((uint32_t*)aSource->GetData());
 
-  gfx3DMatrix transform = aTransform;
-  transform.TranslatePost(Point3D(-aDestOffset.x, -aDestOffset.y, 0));
-  destCanvas.setMatrix(Matrix3DToSkia(transform));
-
   SkPaint paint;
   paint.setXfermodeMode(SkXfermode::kSrc_Mode);
   paint.setAntiAlias(true);
   paint.setFilterLevel(SkPaint::kLow_FilterLevel);
-  SkRect destRect = SkRect::MakeXYWH(0, 0, srcSize.width, srcSize.height);
-  destCanvas.drawBitmapRectToRect(src, nullptr, destRect, &paint);
+  SkMatrix localMatrix = MatrixToSkia(transform);
+  SkShader* shader = SkShader::CreateBitmapShader(src,
+                                                  SkShader::kClamp_TileMode,
+                                                  SkShader::kClamp_TileMode,
+                                                  &localMatrix);
+  paint.setShader(shader);
+  shader->unref();
+
+  SkPath path;
+  path.moveTo(verts[0].x, verts[0].y);
+  for (size_t i=1; i<vertCount; i++) {
+    path.lineTo(verts[i].x, verts[i].y);
+  }
+  path.close();
+
+  destCanvas.drawPath(path, paint);
 }
 #else
 static pixman_transform
 Matrix3DToPixman(const gfx3DMatrix& aMatrix)
 {
   pixman_f_transform transform;
 
   transform.m[0][0] = aMatrix._11;
@@ -331,44 +361,48 @@ BasicCompositor::DrawQuad(const gfx::Rec
   // |dest| is a temporary surface.
   RefPtr<DrawTarget> dest = buffer;
 
   buffer->PushClipRect(aClipRect);
   AutoRestoreTransform autoRestoreTransform(dest);
 
   Matrix newTransform;
   Rect transformBounds;
-  gfx3DMatrix new3DTransform;
+  Matrix4x4 new3DTransform;
   IntPoint offset = mRenderTarget->GetOrigin();
 
   if (aTransform.Is2D()) {
     newTransform = aTransform.As2D();
   } else {
+    // Get the bounds post-transform.
+    new3DTransform = aTransform;
+    Rect bounds = new3DTransform.TransformBounds(aRect);
+
+    transformBounds = bounds;
+    transformBounds.RoundOut();
+
+    if (transformBounds.IsEmpty()) {
+      // The quad has been completely clipped, nothing to draw.
+      return;
+    }
+
     // Create a temporary surface for the transform.
     dest = gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(RoundOut(aRect).Size(), SurfaceFormat::B8G8R8A8);
     if (!dest) {
       return;
     }
 
     dest->SetTransform(Matrix::Translation(-aRect.x, -aRect.y));
 
-    // Get the bounds post-transform.
-    new3DTransform = To3DMatrix(aTransform);
-    gfxRect bounds = new3DTransform.TransformBounds(ThebesRect(aRect));
-    bounds.IntersectRect(bounds, gfxRect(offset.x, offset.y, buffer->GetSize().width, buffer->GetSize().height));
-
-    transformBounds = ToRect(bounds);
-    transformBounds.RoundOut();
-
     // Propagate the coordinate offset to our 2D draw target.
     newTransform = Matrix::Translation(transformBounds.x, transformBounds.y);
 
     // When we apply the 3D transformation, we do it against a temporary
     // surface, so undo the coordinate offset.
-    new3DTransform = gfx3DMatrix::Translation(aRect.x, aRect.y, 0) * new3DTransform;
+    new3DTransform = Matrix4x4::Translation(aRect.x, aRect.y, 0) * new3DTransform;
   }
 
   newTransform.PostTranslate(-offset.x, -offset.y);
   buffer->SetTransform(newTransform);
 
   RefPtr<SourceSurface> sourceMask;
   Matrix maskTransform;
   if (aEffectChain.mSecondaryEffects[EffectTypes::MASK]) {