Title: [113364] trunk/Source
Revision
113364
Author
[email protected]
Date
2012-04-05 13:14:18 -0700 (Thu, 05 Apr 2012)

Log Message

[chromium] Need to clip to homogeneous w=0 plane when applying transforms.
https://bugs.webkit.org/show_bug.cgi?id=80806

Reviewed by Adrienne Walker.

Source/WebCore:

Unit tests added to CCLayerTreeHostCommon. This change is also
covered by other existing unit tests and layout tests.

WebCore TransformationMatrix mapRect / mapQuad / projectQuad do
not properly handle the case where a surface is oriented partially
behind the camera, with a perspective projection. In this case,
projected points may appear to be valid in cartesian coordinates,
but they are indeed not valid, and this problem can only be
detected in homogeneous coordinates after applying the transform,
before the divide-by-w step.

The correct solution is to clip geometry where w < 0. This patch
makes this change local to chromium only, to fix rendering bugs
that arise from this problem. The primary fix is to correct
calculateVisibleLayerRect(), but other ancillary locations are
also fixed, in particular, the antialiasing code path is simply
skipped when this case arises.

Eventually this math needs to be merged into TransformationMatrix,
to fix hit-testing bugs that occur in both Chromium and Safari.

* WebCore.gypi:
* platform/graphics/chromium/LayerRendererChromium.cpp:
(WebCore::findTileProgramUniforms):
(WebCore::LayerRendererChromium::drawTileQuad):
* platform/graphics/chromium/cc/CCLayerTreeHostCommon.cpp:
(WebCore::CCLayerTreeHostCommon::calculateVisibleRect):
(WebCore::isScaleOrTranslation):
(WebCore::calculateDrawTransformsAndVisibilityInternal):
* platform/graphics/chromium/cc/CCMathUtil.cpp: Added.
(WebCore):
(WebCore::HomogeneousCoordinate::HomogeneousCoordinate):
(HomogeneousCoordinate):
(WebCore::HomogeneousCoordinate::shouldBeClipped):
(WebCore::HomogeneousCoordinate::cartesianPoint2d):
(WebCore::projectPoint):
(WebCore::mapPoint):
(WebCore::computeClippedPointForEdge):
(WebCore::expandBoundsToIncludePoint):
(WebCore::computeEnclosingRectOfClippedQuad):
(WebCore::computeEnclosingRect):
(WebCore::CCMathUtil::mapClippedRect):
(WebCore::CCMathUtil::projectClippedRect):
(WebCore::CCMathUtil::mapQuad):
(WebCore::CCMathUtil::projectQuad):
* platform/graphics/chromium/cc/CCMathUtil.h: Added.
(WebCore):
(CCMathUtil):
* platform/graphics/chromium/cc/CCOcclusionTracker.cpp:
(WebCore::computeUnoccludedContentRect):

Source/WebKit/chromium:

* tests/CCLayerTreeHostCommonTest.cpp:
(WebKitTests::TEST):
(WebKitTests):

Modified Paths

Added Paths

Diff

Modified: trunk/Source/WebCore/ChangeLog (113363 => 113364)


--- trunk/Source/WebCore/ChangeLog	2012-04-05 20:09:33 UTC (rev 113363)
+++ trunk/Source/WebCore/ChangeLog	2012-04-05 20:14:18 UTC (rev 113364)
@@ -1,3 +1,61 @@
+2012-04-05  Shawn Singh  <[email protected]>
+
+        [chromium] Need to clip to homogeneous w=0 plane when applying transforms.
+        https://bugs.webkit.org/show_bug.cgi?id=80806
+
+        Reviewed by Adrienne Walker.
+
+        Unit tests added to CCLayerTreeHostCommon. This change is also
+        covered by other existing unit tests and layout tests.
+
+        WebCore TransformationMatrix mapRect / mapQuad / projectQuad do
+        not properly handle the case where a surface is oriented partially
+        behind the camera, with a perspective projection. In this case,
+        projected points may appear to be valid in cartesian coordinates,
+        but they are indeed not valid, and this problem can only be
+        detected in homogeneous coordinates after applying the transform,
+        before the divide-by-w step.
+
+        The correct solution is to clip geometry where w < 0. This patch
+        makes this change local to chromium only, to fix rendering bugs
+        that arise from this problem. The primary fix is to correct
+        calculateVisibleLayerRect(), but other ancillary locations are
+        also fixed, in particular, the antialiasing code path is simply
+        skipped when this case arises.
+
+        Eventually this math needs to be merged into TransformationMatrix,
+        to fix hit-testing bugs that occur in both Chromium and Safari.
+
+        * WebCore.gypi:
+        * platform/graphics/chromium/LayerRendererChromium.cpp:
+        (WebCore::findTileProgramUniforms):
+        (WebCore::LayerRendererChromium::drawTileQuad):
+        * platform/graphics/chromium/cc/CCLayerTreeHostCommon.cpp:
+        (WebCore::CCLayerTreeHostCommon::calculateVisibleRect):
+        (WebCore::isScaleOrTranslation):
+        (WebCore::calculateDrawTransformsAndVisibilityInternal):
+        * platform/graphics/chromium/cc/CCMathUtil.cpp: Added.
+        (WebCore):
+        (WebCore::HomogeneousCoordinate::HomogeneousCoordinate):
+        (HomogeneousCoordinate):
+        (WebCore::HomogeneousCoordinate::shouldBeClipped):
+        (WebCore::HomogeneousCoordinate::cartesianPoint2d):
+        (WebCore::projectPoint):
+        (WebCore::mapPoint):
+        (WebCore::computeClippedPointForEdge):
+        (WebCore::expandBoundsToIncludePoint):
+        (WebCore::computeEnclosingRectOfClippedQuad):
+        (WebCore::computeEnclosingRect):
+        (WebCore::CCMathUtil::mapClippedRect):
+        (WebCore::CCMathUtil::projectClippedRect):
+        (WebCore::CCMathUtil::mapQuad):
+        (WebCore::CCMathUtil::projectQuad):
+        * platform/graphics/chromium/cc/CCMathUtil.h: Added.
+        (WebCore):
+        (CCMathUtil):
+        * platform/graphics/chromium/cc/CCOcclusionTracker.cpp:
+        (WebCore::computeUnoccludedContentRect):
+
 2012-04-05  Patrick Gansterer  <[email protected]>
 
         [Qt] Correct <wtf/*.h> include paths.

Modified: trunk/Source/WebCore/WebCore.gypi (113363 => 113364)


--- trunk/Source/WebCore/WebCore.gypi	2012-04-05 20:09:33 UTC (rev 113363)
+++ trunk/Source/WebCore/WebCore.gypi	2012-04-05 20:14:18 UTC (rev 113364)
@@ -3607,6 +3607,8 @@
             'platform/graphics/chromium/cc/CCLayerTreeHostCommon.h',
             'platform/graphics/chromium/cc/CCLayerTreeHostImpl.cpp',
             'platform/graphics/chromium/cc/CCLayerTreeHostImpl.h',
+            'platform/graphics/chromium/cc/CCMathUtil.cpp',
+            'platform/graphics/chromium/cc/CCMathUtil.h',
             'platform/graphics/chromium/cc/CCOcclusionTracker.cpp',
             'platform/graphics/chromium/cc/CCOcclusionTracker.h',
             'platform/graphics/chromium/cc/CCOverdrawMetrics.cpp',

Modified: trunk/Source/WebCore/platform/graphics/chromium/LayerRendererChromium.cpp (113363 => 113364)


--- trunk/Source/WebCore/platform/graphics/chromium/LayerRendererChromium.cpp	2012-04-05 20:09:33 UTC (rev 113363)
+++ trunk/Source/WebCore/platform/graphics/chromium/LayerRendererChromium.cpp	2012-04-05 20:14:18 UTC (rev 113364)
@@ -55,6 +55,7 @@
 #include "cc/CCDebugBorderDrawQuad.h"
 #include "cc/CCLayerImpl.h"
 #include "cc/CCLayerTreeHostCommon.h"
+#include "cc/CCMathUtil.h"
 #include "cc/CCProxy.h"
 #include "cc/CCRenderPass.h"
 #include "cc/CCRenderSurfaceDrawQuad.h"
@@ -564,9 +565,11 @@
     uniforms.edgeLocation = program->fragmentShader().edgeLocation();
 }
 
-static void findTileProgramUniforms(LayerRendererChromium* layerRenderer, const CCTileDrawQuad* quad, TileProgramUniforms& uniforms)
+static void findTileProgramUniforms(LayerRendererChromium* layerRenderer, const CCTileDrawQuad* quad, TileProgramUniforms& uniforms, bool quadIsClipped)
 {
-    if (quad->isAntialiased()) {
+    // For now, we simply skip anti-aliasing with the quad is clipped. This only happens
+    // on perspective transformed layers that go partially behind the camera.
+    if (quad->isAntialiased() && !quadIsClipped) {
         if (quad->swizzleContents()) {
             const CCTiledLayerImpl::ProgramSwizzleAA* program = layerRenderer->tilerProgramSwizzleAA();
             tileUniformLocation(program, uniforms);
@@ -629,8 +632,17 @@
     float fragmentTexScaleX = clampRect.width() / textureSize.width();
     float fragmentTexScaleY = clampRect.height() / textureSize.height();
 
+
+    FloatQuad localQuad;
+    TransformationMatrix deviceTransform = TransformationMatrix(windowMatrix() * projectionMatrix() * quad->quadTransform()).to2dTransform();
+    if (!deviceTransform.isInvertible())
+        return;
+
+    bool clipped = false;
+    FloatQuad deviceLayerQuad = CCMathUtil::mapQuad(deviceTransform, FloatQuad(quad->layerRect()), clipped);
+
     TileProgramUniforms uniforms;
-    findTileProgramUniforms(this, quad, uniforms);
+    findTileProgramUniforms(this, quad, uniforms, clipped);
 
     GLC(context(), context()->useProgram(uniforms.program));
     GLC(context(), context()->uniform1i(uniforms.samplerLocation, 0));
@@ -639,13 +651,8 @@
     GLC(context(), context()->texParameteri(GraphicsContext3D::TEXTURE_2D, GraphicsContext3D::TEXTURE_MIN_FILTER, quad->textureFilter()));
     GLC(context(), context()->texParameteri(GraphicsContext3D::TEXTURE_2D, GraphicsContext3D::TEXTURE_MAG_FILTER, quad->textureFilter()));
 
-    FloatQuad localQuad;
-    if (quad->isAntialiased()) {
-        TransformationMatrix deviceTransform = TransformationMatrix(windowMatrix() * projectionMatrix() * quad->quadTransform()).to2dTransform();
-        if (!deviceTransform.isInvertible())
-            return;
 
-        FloatQuad deviceLayerQuad = deviceTransform.mapQuad(FloatQuad(quad->layerRect()));
+    if (!clipped && quad->isAntialiased()) {
 
         CCLayerQuad deviceLayerBounds = CCLayerQuad(FloatQuad(deviceLayerQuad.boundingBox()));
         deviceLayerBounds.inflateAntiAliasingDistance();

Modified: trunk/Source/WebCore/platform/graphics/chromium/cc/CCLayerTreeHostCommon.cpp (113363 => 113364)


--- trunk/Source/WebCore/platform/graphics/chromium/cc/CCLayerTreeHostCommon.cpp	2012-04-05 20:09:33 UTC (rev 113363)
+++ trunk/Source/WebCore/platform/graphics/chromium/cc/CCLayerTreeHostCommon.cpp	2012-04-05 20:14:18 UTC (rev 113364)
@@ -37,6 +37,7 @@
 #include "cc/CCLayerImpl.h"
 #include "cc/CCLayerIterator.h"
 #include "cc/CCLayerSorter.h"
+#include "cc/CCMathUtil.h"
 #include "cc/CCRenderSurface.h"
 
 namespace WebCore {
@@ -44,7 +45,7 @@
 IntRect CCLayerTreeHostCommon::calculateVisibleRect(const IntRect& targetSurfaceRect, const IntRect& layerBoundRect, const TransformationMatrix& transform)
 {
     // Is this layer fully contained within the target surface?
-    IntRect layerInSurfaceSpace = transform.mapRect(layerBoundRect);
+    IntRect layerInSurfaceSpace = CCMathUtil::mapClippedRect(transform, layerBoundRect);
     if (targetSurfaceRect.contains(layerInSurfaceSpace))
         return layerBoundRect;
 
@@ -59,7 +60,7 @@
     // axis-aligned), but is a reasonable filter on the space to consider.
     // Non-invertible transforms will create an empty rect here.
     const TransformationMatrix surfaceToLayer = transform.inverse();
-    IntRect layerRect = surfaceToLayer.projectQuad(FloatQuad(FloatRect(minimalSurfaceRect))).enclosingBoundingBox();
+    IntRect layerRect = enclosingIntRect(CCMathUtil::projectClippedRect(surfaceToLayer, FloatRect(minimalSurfaceRect)));
     layerRect.intersect(layerBoundRect);
     return layerRect;
 }
@@ -70,7 +71,6 @@
            && !m.m21() && !m.m23() && !m.m24()
            && !m.m31() && !m.m32() && !m.m43()
            && m.m44();
-
 }
 
 template<typename LayerType>
@@ -409,7 +409,7 @@
         layer->setDrawTransform(combinedTransform);
         layer->setDrawTransformIsAnimating(animatingTransformToTarget);
         layer->setScreenSpaceTransformIsAnimating(animatingTransformToScreen);
-        transformedLayerRect = enclosingIntRect(layer->drawTransform().mapRect(layerRect));
+        transformedLayerRect = enclosingIntRect(CCMathUtil::mapClippedRect(layer->drawTransform(), layerRect));
 
         layer->setDrawOpacity(drawOpacity);
         layer->setDrawOpacityIsAnimating(drawOpacityIsAnimating);

Added: trunk/Source/WebCore/platform/graphics/chromium/cc/CCMathUtil.cpp (0 => 113364)


--- trunk/Source/WebCore/platform/graphics/chromium/cc/CCMathUtil.cpp	                        (rev 0)
+++ trunk/Source/WebCore/platform/graphics/chromium/cc/CCMathUtil.cpp	2012-04-05 20:14:18 UTC (rev 113364)
@@ -0,0 +1,254 @@
+/*
+ * Copyright (C) 2012 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1.  Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ * 2.  Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+
+#include "cc/CCMathUtil.h"
+
+#include "FloatPoint.h"
+#include "FloatQuad.h"
+#include "IntRect.h"
+#include "TransformationMatrix.h"
+
+namespace WebCore {
+
+struct HomogeneousCoordinate {
+    HomogeneousCoordinate(double newX, double newY, double newZ, double newW)
+        : x(newX)
+        , y(newY)
+        , z(newZ)
+        , w(newW)
+    {
+    }
+
+    bool shouldBeClipped() const
+    {
+        return w <= 0;
+    }
+
+    FloatPoint cartesianPoint2d() const
+    {
+        if (w == 1)
+            return FloatPoint(x, y);
+
+        // For now, because this code is used privately only by CCMathUtil, it should never be called when w == 0, and we do not yet need to handle that case.
+        ASSERT(w);
+        double invW = 1.0 / w;
+        return FloatPoint(x * invW, y * invW);
+    }
+
+    double x;
+    double y;
+    double z;
+    double w;
+};
+
+static HomogeneousCoordinate projectPoint(const TransformationMatrix& transform, const FloatPoint& p)
+{
+    double x = p.x();
+    double y = p.y();
+    double z = -(transform.m13() * x + transform.m23() * y + transform.m43()) / transform.m33();
+    // implicit definition of w = 1;
+
+    double outX = x * transform.m11() + y * transform.m21() + z * transform.m31() + transform.m41();
+    double outY = x * transform.m12() + y * transform.m22() + z * transform.m32() + transform.m42();
+    double outZ = x * transform.m13() + y * transform.m23() + z * transform.m33() + transform.m43();
+    double outW = x * transform.m14() + y * transform.m24() + z * transform.m34() + transform.m44();
+
+    return HomogeneousCoordinate(outX, outY, outZ, outW);
+}
+
+static HomogeneousCoordinate mapPoint(const TransformationMatrix& transform, const FloatPoint& p)
+{
+    double x = p.x();
+    double y = p.y();
+    // implicit definition of z = 0;
+    // implicit definition of w = 1;
+
+    double outX = x * transform.m11() + y * transform.m21() + transform.m41();
+    double outY = x * transform.m12() + y * transform.m22() + transform.m42();
+    double outZ = x * transform.m13() + y * transform.m23() + transform.m43();
+    double outW = x * transform.m14() + y * transform.m24() + transform.m44();
+
+    return HomogeneousCoordinate(outX, outY, outZ, outW);
+}
+
+static HomogeneousCoordinate computeClippedPointForEdge(const HomogeneousCoordinate& h1, const HomogeneousCoordinate& h2)
+{
+    // Points h1 and h2 form a line in 4d, and any point on that line can be represented
+    // as an interpolation between h1 and h2:
+    //    p = (1-t) h1 + (t) h2
+    //
+    // We want to compute point p such that p.w == epsilon, where epsilon is a small
+    // non-zero number. (but the smaller the number is, the higher the risk of overflow)
+    // To do this, we solve for t in the following equation:
+    //    p.w = epsilon = (1-t) * h1.w + (t) * h2.w
+    //
+    // Once paramter t is known, the rest of p can be computed via p = (1-t) h1 + (t) h2.
+
+    // Technically this is a special case of the following assertion, but its a good idea to keep it an explicit sanity check here.
+    ASSERT(h2.w != h1.w);
+    // Exactly one of h1 or h2 (but not both) must be on the negative side of the w plane when this is called.
+    ASSERT(h1.shouldBeClipped() ^ h2.shouldBeClipped());
+
+    double w = 0.00001; // or any positive non-zero small epsilon
+
+    double t = (w - h1.w) / (h2.w - h1.w);
+
+    double x = (1-t) * h1.x + t * h2.x;
+    double y = (1-t) * h1.y + t * h2.y;
+    double z = (1-t) * h1.z + t * h2.z;
+
+    return HomogeneousCoordinate(x, y, z, w);
+}
+
+static inline void expandBoundsToIncludePoint(float& xmin, float& xmax, float& ymin, float& ymax, const FloatPoint& p)
+{
+    xmin = std::min(p.x(), xmin);
+    xmax = std::max(p.x(), xmax);
+    ymin = std::min(p.y(), ymin);
+    ymax = std::max(p.y(), ymax);
+}
+
+static FloatRect computeEnclosingRectOfClippedQuad(const HomogeneousCoordinate& h1, const HomogeneousCoordinate& h2, const HomogeneousCoordinate& h3, const HomogeneousCoordinate& h4)
+{
+    // This function performs clipping as necessary and computes the enclosing 2d
+    // FloatRect of the vertices. Doing these two steps simultaneously allows us to avoid
+    // the overhead of storing an unknown number of clipped vertices.
+
+    float xmin = std::numeric_limits<float>::max();
+    float xmax = std::numeric_limits<float>::min();
+    float ymin = std::numeric_limits<float>::max();
+    float ymax = std::numeric_limits<float>::min();
+
+    if (!h1.shouldBeClipped())
+        expandBoundsToIncludePoint(xmin, xmax, ymin, ymax, h1.cartesianPoint2d());
+
+    if (h1.shouldBeClipped() ^ h2.shouldBeClipped())
+        expandBoundsToIncludePoint(xmin, xmax, ymin, ymax, computeClippedPointForEdge(h1, h2).cartesianPoint2d());
+
+    if (!h2.shouldBeClipped())
+        expandBoundsToIncludePoint(xmin, xmax, ymin, ymax, h2.cartesianPoint2d());
+
+    if (h2.shouldBeClipped() ^ h3.shouldBeClipped())
+        expandBoundsToIncludePoint(xmin, xmax, ymin, ymax, computeClippedPointForEdge(h2, h3).cartesianPoint2d());
+
+    if (!h3.shouldBeClipped())
+        expandBoundsToIncludePoint(xmin, xmax, ymin, ymax, h3.cartesianPoint2d());
+
+    if (h3.shouldBeClipped() ^ h4.shouldBeClipped())
+        expandBoundsToIncludePoint(xmin, xmax, ymin, ymax, computeClippedPointForEdge(h3, h4).cartesianPoint2d());
+
+    if (!h4.shouldBeClipped())
+        expandBoundsToIncludePoint(xmin, xmax, ymin, ymax, h4.cartesianPoint2d());
+
+    if (h4.shouldBeClipped() ^ h1.shouldBeClipped())
+        expandBoundsToIncludePoint(xmin, xmax, ymin, ymax, computeClippedPointForEdge(h4, h1).cartesianPoint2d());
+
+    return FloatRect(FloatPoint(xmin, ymin), FloatSize(xmax - xmin, ymax - ymin));
+}
+
+static FloatRect computeEnclosingRect(const HomogeneousCoordinate& h1, const HomogeneousCoordinate& h2, const HomogeneousCoordinate& h3, const HomogeneousCoordinate& h4)
+{
+    // If no vertices on the quad are clipped, then we can simply return the enclosing rect directly.
+    bool clipped = h1.shouldBeClipped() || h2.shouldBeClipped() || h3.shouldBeClipped() || h4.shouldBeClipped();
+    if (!clipped) {
+        FloatQuad mappedQuad = FloatQuad(h1.cartesianPoint2d(), h2.cartesianPoint2d(), h3.cartesianPoint2d(), h4.cartesianPoint2d());
+        return mappedQuad.boundingBox();
+    }
+
+    return computeEnclosingRectOfClippedQuad(h1, h2, h3, h4);
+}
+
+IntRect CCMathUtil::mapClippedRect(const TransformationMatrix& transform, const IntRect& srcRect)
+{
+    return enclosingIntRect(mapClippedRect(transform, FloatRect(srcRect)));
+}
+
+FloatRect CCMathUtil::mapClippedRect(const TransformationMatrix& transform, const FloatRect& srcRect)
+{
+    if (transform.isIdentityOrTranslation()) {
+        FloatRect mappedRect(srcRect);
+        mappedRect.move(static_cast<float>(transform.m41()), static_cast<float>(transform.m42()));
+        return mappedRect;
+    }
+
+    // Apply the transform, but retain the result in homogeneous coordinates.
+    FloatQuad q = FloatQuad(FloatRect(srcRect));
+    HomogeneousCoordinate h1 = mapPoint(transform, q.p1());
+    HomogeneousCoordinate h2 = mapPoint(transform, q.p2());
+    HomogeneousCoordinate h3 = mapPoint(transform, q.p3());
+    HomogeneousCoordinate h4 = mapPoint(transform, q.p4());
+
+    return computeEnclosingRect(h1, h2, h3, h4);
+}
+
+FloatRect CCMathUtil::projectClippedRect(const TransformationMatrix& transform, const FloatRect& srcRect)
+{
+    // Perform the projection, but retain the result in homogeneous coordinates.
+    FloatQuad q = FloatQuad(FloatRect(srcRect));
+    HomogeneousCoordinate h1 = projectPoint(transform, q.p1());
+    HomogeneousCoordinate h2 = projectPoint(transform, q.p2());
+    HomogeneousCoordinate h3 = projectPoint(transform, q.p3());
+    HomogeneousCoordinate h4 = projectPoint(transform, q.p4());
+
+    return computeEnclosingRect(h1, h2, h3, h4);
+}
+
+FloatQuad CCMathUtil::mapQuad(const TransformationMatrix& transform, const FloatQuad& q, bool& clipped)
+{
+    if (transform.isIdentityOrTranslation()) {
+        FloatQuad mappedQuad(q);
+        mappedQuad.move(static_cast<float>(transform.m41()), static_cast<float>(transform.m42()));
+        return mappedQuad;
+    }
+
+    HomogeneousCoordinate h1 = mapPoint(transform, q.p1());
+    HomogeneousCoordinate h2 = mapPoint(transform, q.p2());
+    HomogeneousCoordinate h3 = mapPoint(transform, q.p3());
+    HomogeneousCoordinate h4 = mapPoint(transform, q.p4());
+
+    clipped = h1.shouldBeClipped() || h2.shouldBeClipped() || h3.shouldBeClipped() || h4.shouldBeClipped();
+
+    // Result will be invalid if clipped == true. But, compute it anyway just in case, to emulate existing behavior.
+    return FloatQuad(h1.cartesianPoint2d(), h2.cartesianPoint2d(), h3.cartesianPoint2d(), h4.cartesianPoint2d());
+}
+
+FloatQuad CCMathUtil::projectQuad(const TransformationMatrix& transform, const FloatQuad& q, bool& clipped)
+{
+    FloatQuad projectedQuad;
+    bool clippedPoint;
+    projectedQuad.setP1(transform.projectPoint(q.p1(), &clippedPoint));
+    clipped = clippedPoint;
+    projectedQuad.setP2(transform.projectPoint(q.p2(), &clippedPoint));
+    clipped |= clippedPoint;
+    projectedQuad.setP3(transform.projectPoint(q.p3(), &clippedPoint));
+    clipped |= clippedPoint;
+    projectedQuad.setP4(transform.projectPoint(q.p4(), &clippedPoint));
+    clipped |= clippedPoint;
+
+    return projectedQuad;
+}
+
+} // namespace WebCore

Added: trunk/Source/WebCore/platform/graphics/chromium/cc/CCMathUtil.h (0 => 113364)


--- trunk/Source/WebCore/platform/graphics/chromium/cc/CCMathUtil.h	                        (rev 0)
+++ trunk/Source/WebCore/platform/graphics/chromium/cc/CCMathUtil.h	2012-04-05 20:14:18 UTC (rev 113364)
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2012 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1.  Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ * 2.  Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef CCMathUtil_h
+#define CCMathUtil_h
+
+namespace WebCore {
+
+class IntRect;
+class FloatRect;
+class FloatQuad;
+class TransformationMatrix;
+
+// This class contains math helper functionality that does not belong in WebCore.
+// It is possible that this functionality should be migrated to WebCore eventually.
+class CCMathUtil {
+public:
+
+    // Background: TransformationMatrix code in WebCore does not do the right thing in
+    // mapRect / mapQuad / projectQuad when there is a perspective projection that causes
+    // one of the transformed vertices to go to w < 0. In those cases, it is necessary to
+    // perform clipping in homogeneous coordinates, after applying the transform, before
+    // dividing-by-w to convert to cartesian coordinates.
+    //
+    // These functions return the axis-aligned rect that encloses the correctly clipped,
+    // transformed polygon.
+    static IntRect mapClippedRect(const TransformationMatrix&, const IntRect&);
+    static FloatRect mapClippedRect(const TransformationMatrix&, const FloatRect&);
+    static FloatRect projectClippedRect(const TransformationMatrix&, const FloatRect&);
+
+    // NOTE: This function does not do correct clipping against w = 0 plane, but it
+    // correctly detects the clipped condition via the boolean clipped.
+    static FloatQuad mapQuad(const TransformationMatrix&, const FloatQuad&, bool& clipped);
+    static FloatQuad projectQuad(const TransformationMatrix&, const FloatQuad&, bool& clipped);
+};
+
+} // namespace WebCore
+
+#endif // #define CCMathUtil_h

Modified: trunk/Source/WebCore/platform/graphics/chromium/cc/CCOcclusionTracker.cpp (113363 => 113364)


--- trunk/Source/WebCore/platform/graphics/chromium/cc/CCOcclusionTracker.cpp	2012-04-05 20:09:33 UTC (rev 113363)
+++ trunk/Source/WebCore/platform/graphics/chromium/cc/CCOcclusionTracker.cpp	2012-04-05 20:14:18 UTC (rev 113364)
@@ -31,6 +31,7 @@
 
 #include "LayerChromium.h"
 #include "cc/CCLayerImpl.h"
+#include "cc/CCMathUtil.h"
 
 #include <algorithm>
 
@@ -275,22 +276,6 @@
     return boundsRect;
 }
 
-static FloatQuad projectQuad(const TransformationMatrix& transform, const FloatQuad& q, bool& clamped)
-{
-    FloatQuad projectedQuad;
-    bool clampedPoint;
-    projectedQuad.setP1(transform.projectPoint(q.p1(), &clampedPoint));
-    clamped = clampedPoint;
-    projectedQuad.setP2(transform.projectPoint(q.p2(), &clampedPoint));
-    clamped |= clampedPoint;
-    projectedQuad.setP3(transform.projectPoint(q.p3(), &clampedPoint));
-    clamped |= clampedPoint;
-    projectedQuad.setP4(transform.projectPoint(q.p4(), &clampedPoint));
-    clamped |= clampedPoint;
-
-    return projectedQuad;
-}
-
 static inline IntRect computeUnoccludedContentRect(const IntRect& contentRect, const TransformationMatrix& contentSpaceTransform, const IntRect& scissorRect, const Region& occlusion)
 {
     if (!contentSpaceTransform.isInvertible())
@@ -299,9 +284,9 @@
     FloatRect transformedRect = contentSpaceTransform.mapRect(FloatRect(contentRect));
     // Take the enclosingIntRect at each step, as we want to contain any unoccluded partial pixels in the resulting IntRect.
     IntRect shrunkRect = rectSubtractRegion(intersection(enclosingIntRect(transformedRect), scissorRect), occlusion);
-    bool clamped; // FIXME: projectQuad returns invalid results when a point gets clamped. To be fixed in bug https://bugs.webkit.org/show_bug.cgi?id=80806.
-    IntRect unoccludedRect = enclosingIntRect(projectQuad(contentSpaceTransform.inverse(), FloatQuad(FloatRect(shrunkRect)), clamped).boundingBox());
-    if (clamped)
+    bool clipped; // FIXME: We should be able to use projectClippedQuad instead of forcing everything to be unoccluded. https://bugs.webkit.org/show_bug.cgi?id=83217.
+    IntRect unoccludedRect = enclosingIntRect(CCMathUtil::projectQuad(contentSpaceTransform.inverse(), FloatQuad(FloatRect(shrunkRect)), clipped).boundingBox());
+    if (clipped)
         return contentRect;
     // The rect back in content space is a bounding box and may extend outside of the original contentRect, so clamp it to the contentRectBounds.
     return intersection(unoccludedRect, contentRect);

Modified: trunk/Source/WebKit/chromium/ChangeLog (113363 => 113364)


--- trunk/Source/WebKit/chromium/ChangeLog	2012-04-05 20:09:33 UTC (rev 113363)
+++ trunk/Source/WebKit/chromium/ChangeLog	2012-04-05 20:14:18 UTC (rev 113364)
@@ -1,3 +1,14 @@
+2012-04-05  Shawn Singh  <[email protected]>
+
+        [chromium] Need to clip to homogeneous w=0 plane when applying transforms.
+        https://bugs.webkit.org/show_bug.cgi?id=80806
+
+        Reviewed by Adrienne Walker.
+
+        * tests/CCLayerTreeHostCommonTest.cpp:
+        (WebKitTests::TEST):
+        (WebKitTests):
+
 2012-04-05  Dana Jansens  <[email protected]>
 
         [chromium] Cleanup test, redundant code in CCSchedulerTest.NoBeginFrameWhenDrawFails

Modified: trunk/Source/WebKit/chromium/tests/CCLayerTreeHostCommonTest.cpp (113363 => 113364)


--- trunk/Source/WebKit/chromium/tests/CCLayerTreeHostCommonTest.cpp	2012-04-05 20:09:33 UTC (rev 113363)
+++ trunk/Source/WebKit/chromium/tests/CCLayerTreeHostCommonTest.cpp	2012-04-05 20:14:18 UTC (rev 113364)
@@ -32,6 +32,7 @@
 #include "TransformationMatrix.h"
 #include "TranslateTransformOperation.h"
 #include "cc/CCLayerAnimationController.h"
+#include "cc/CCMathUtil.h"
 
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
@@ -979,6 +980,264 @@
     EXPECT_FLOAT_EQ(5.0, grandChildOfRS2->screenSpaceTransform().m42());
 }
 
+TEST(CCLayerTreeHostCommonTest, verifyVisibleRectForIdentityTransform)
+{
+    // Test the calculateVisibleRect() function works correctly for identity transforms.
+
+    IntRect targetSurfaceRect = IntRect(IntPoint(0, 0), IntSize(100, 100));
+    TransformationMatrix layerToSurfaceTransform;
+
+    // Case 1: Layer is contained within the surface.
+    IntRect layerContentRect = IntRect(IntPoint(10, 10), IntSize(30, 30));
+    IntRect expected = IntRect(IntPoint(10, 10), IntSize(30, 30));
+    IntRect actual = CCLayerTreeHostCommon::calculateVisibleRect(targetSurfaceRect, layerContentRect, layerToSurfaceTransform);
+    EXPECT_INT_RECT_EQ(expected, actual);
+
+    // Case 2: Layer is outside the surface rect.
+    layerContentRect = IntRect(IntPoint(120, 120), IntSize(30, 30));
+    actual = CCLayerTreeHostCommon::calculateVisibleRect(targetSurfaceRect, layerContentRect, layerToSurfaceTransform);
+    EXPECT_TRUE(actual.isEmpty());
+
+    // Case 3: Layer is partially overlapping the surface rect.
+    layerContentRect = IntRect(IntPoint(80, 80), IntSize(30, 30));
+    expected = IntRect(IntPoint(80, 80), IntSize(20, 20));
+    actual = CCLayerTreeHostCommon::calculateVisibleRect(targetSurfaceRect, layerContentRect, layerToSurfaceTransform);
+    EXPECT_INT_RECT_EQ(expected, actual);
+}
+
+TEST(CCLayerTreeHostCommonTest, verifyVisibleRectForTranslations)
+{
+    // Test the calculateVisibleRect() function works correctly for scaling transforms.
+
+    IntRect targetSurfaceRect = IntRect(IntPoint(0, 0), IntSize(100, 100));
+    IntRect layerContentRect = IntRect(IntPoint(0, 0), IntSize(30, 30));
+    TransformationMatrix layerToSurfaceTransform;
+
+    // Case 1: Layer is contained within the surface.
+    layerToSurfaceTransform.makeIdentity();
+    layerToSurfaceTransform.translate(10, 10);
+    IntRect expected = IntRect(IntPoint(0, 0), IntSize(30, 30));
+    IntRect actual = CCLayerTreeHostCommon::calculateVisibleRect(targetSurfaceRect, layerContentRect, layerToSurfaceTransform);
+    EXPECT_INT_RECT_EQ(expected, actual);
+
+    // Case 2: Layer is outside the surface rect.
+    layerToSurfaceTransform.makeIdentity();
+    layerToSurfaceTransform.translate(120, 120);
+    actual = CCLayerTreeHostCommon::calculateVisibleRect(targetSurfaceRect, layerContentRect, layerToSurfaceTransform);
+    EXPECT_TRUE(actual.isEmpty());
+
+    // Case 3: Layer is partially overlapping the surface rect.
+    layerToSurfaceTransform.makeIdentity();
+    layerToSurfaceTransform.translate(80, 80);
+    expected = IntRect(IntPoint(0, 0), IntSize(20, 20));
+    actual = CCLayerTreeHostCommon::calculateVisibleRect(targetSurfaceRect, layerContentRect, layerToSurfaceTransform);
+    EXPECT_INT_RECT_EQ(expected, actual);
+}
+
+TEST(CCLayerTreeHostCommonTest, verifyVisibleRectFor2DRotations)
+{
+    // Test the calculateVisibleRect() function works correctly for rotations about z-axis (i.e. 2D rotations).
+    // Remember that calculateVisibleRect() should return the visible rect in the layer's space.
+
+    IntRect targetSurfaceRect = IntRect(IntPoint(0, 0), IntSize(100, 100));
+    IntRect layerContentRect = IntRect(IntPoint(0, 0), IntSize(30, 30));
+    TransformationMatrix layerToSurfaceTransform;
+
+    // Case 1: Layer is contained within the surface.
+    layerToSurfaceTransform.makeIdentity();
+    layerToSurfaceTransform.translate(50, 50);
+    layerToSurfaceTransform.rotate(45);
+    IntRect expected = IntRect(IntPoint(0, 0), IntSize(30, 30));
+    IntRect actual = CCLayerTreeHostCommon::calculateVisibleRect(targetSurfaceRect, layerContentRect, layerToSurfaceTransform);
+    EXPECT_INT_RECT_EQ(expected, actual);
+
+    // Case 2: Layer is outside the surface rect.
+    layerToSurfaceTransform.makeIdentity();
+    layerToSurfaceTransform.translate(-50, 0);
+    layerToSurfaceTransform.rotate(45);
+    actual = CCLayerTreeHostCommon::calculateVisibleRect(targetSurfaceRect, layerContentRect, layerToSurfaceTransform);
+    EXPECT_TRUE(actual.isEmpty());
+
+    // Case 3: The layer is rotated about its top-left corner. In surface space, the layer
+    //         is oriented diagonally, with the left half outside of the renderSurface. In
+    //         this case, the visible rect should still be the entire layer (remember the
+    //         visible rect is computed in layer space); both the top-left and
+    //         bottom-right corners of the layer are still visible.
+    layerToSurfaceTransform.makeIdentity();
+    layerToSurfaceTransform.rotate(45);
+    expected = IntRect(IntPoint(0, 0), IntSize(30, 30));
+    actual = CCLayerTreeHostCommon::calculateVisibleRect(targetSurfaceRect, layerContentRect, layerToSurfaceTransform);
+    EXPECT_INT_RECT_EQ(expected, actual);
+
+    // Case 4: The layer is rotated about its top-left corner, and translated upwards. In
+    //         surface space, the layer is oriented diagonally, with only the top corner
+    //         of the surface overlapping the layer. In layer space, the render surface
+    //         overlaps the right side of the layer. The visible rect should be the
+    //         layer's right half.
+    layerToSurfaceTransform.makeIdentity();
+    layerToSurfaceTransform.translate(0, -sqrt(2.0) * 15);
+    layerToSurfaceTransform.rotate(45);
+    expected = IntRect(IntPoint(15, 0), IntSize(15, 30)); // right half of layer bounds.
+    actual = CCLayerTreeHostCommon::calculateVisibleRect(targetSurfaceRect, layerContentRect, layerToSurfaceTransform);
+    EXPECT_INT_RECT_EQ(expected, actual);
+}
+
+TEST(CCLayerTreeHostCommonTest, verifyVisibleRectFor3dOrthographicTransform)
+{
+    // Test that the calculateVisibleRect() function works correctly for 3d transforms.
+
+    IntRect targetSurfaceRect = IntRect(IntPoint(0, 0), IntSize(100, 100));
+    IntRect layerContentRect = IntRect(IntPoint(0, 0), IntSize(100, 100));
+    TransformationMatrix layerToSurfaceTransform;
+
+    // Case 1: Orthographic projection of a layer rotated about y-axis by 45 degrees, should be fully contained in the renderSurface.
+    layerToSurfaceTransform.makeIdentity();
+    layerToSurfaceTransform.rotate3d(0, 45, 0);
+    IntRect expected = IntRect(IntPoint(0, 0), IntSize(100, 100));
+    IntRect actual = CCLayerTreeHostCommon::calculateVisibleRect(targetSurfaceRect, layerContentRect, layerToSurfaceTransform);
+    EXPECT_INT_RECT_EQ(expected, actual);
+
+    // Case 2: Orthographic projection of a layer rotated about y-axis by 45 degrees, but
+    //         shifted to the side so only the right-half the layer would be visible on
+    //         the surface.
+    double halfWidthOfRotatedLayer = (100.0 / sqrt(2.0)) * 0.5; // 100.0 is the un-rotated layer width; divided by sqrt(2.0) is the rotated width.
+    layerToSurfaceTransform.makeIdentity();
+    layerToSurfaceTransform.translate(-halfWidthOfRotatedLayer, 0);
+    layerToSurfaceTransform.rotate3d(0, 45, 0); // rotates about the left edge of the layer
+    expected = IntRect(IntPoint(50, 0), IntSize(50, 100)); // right half of the layer.
+    actual = CCLayerTreeHostCommon::calculateVisibleRect(targetSurfaceRect, layerContentRect, layerToSurfaceTransform);
+    EXPECT_INT_RECT_EQ(expected, actual);
+}
+
+TEST(CCLayerTreeHostCommonTest, verifyVisibleRectFor3dPerspectiveTransform)
+{
+    // Test the calculateVisibleRect() function works correctly when the layer has a
+    // perspective projection onto the target surface.
+
+    IntRect targetSurfaceRect = IntRect(IntPoint(0, 0), IntSize(100, 100));
+    IntRect layerContentRect = IntRect(IntPoint(-50, -50), IntSize(200, 200));
+    TransformationMatrix layerToSurfaceTransform;
+
+    // Case 1: Even though the layer is twice as large as the surface, due to perspective
+    //         foreshortening, the layer will fit fully in the surface when its translated
+    //         more than the perspective amount.
+    layerToSurfaceTransform.makeIdentity();
+
+    // The following sequence of transforms applies the perspective about the center of the surface.
+    layerToSurfaceTransform.translate(50, 50);
+    layerToSurfaceTransform.applyPerspective(9);
+    layerToSurfaceTransform.translate(-50, -50);
+
+    // This translate places the layer in front of the surface's projection plane.
+    layerToSurfaceTransform.translate3d(0, 0, -27);
+
+    IntRect expected = IntRect(IntPoint(-50, -50), IntSize(200, 200));
+    IntRect actual = CCLayerTreeHostCommon::calculateVisibleRect(targetSurfaceRect, layerContentRect, layerToSurfaceTransform);
+    EXPECT_INT_RECT_EQ(expected, actual);
+
+    // Case 2: same projection as before, except that the layer is also translated to the
+    //         side, so that only the right half of the layer should be visible.
+    //
+    // Explanation of expected result:
+    // The perspective ratio is (z distance between layer and camera origin) / (z distance between projection plane and camera origin) == ((-27 - 9) / 9)
+    // Then, by similar triangles, if we want to move a layer by translating -50 units in projected surface units (so that only half of it is
+    // visible), then we would need to translate by (-36 / 9) * -50 == -200 in the layer's units.
+    //
+    layerToSurfaceTransform.translate3d(-200, 0, 0);
+    expected = IntRect(IntPoint(50, -50), IntSize(100, 200)); // The right half of the layer's bounding rect.
+    actual = CCLayerTreeHostCommon::calculateVisibleRect(targetSurfaceRect, layerContentRect, layerToSurfaceTransform);
+    EXPECT_INT_RECT_EQ(expected, actual);
+}
+
+TEST(CCLayerTreeHostCommonTest, verifyVisibleRectFor3dOrthographicIsNotClippedBehindSurface)
+{
+    // There is currently no explicit concept of an orthographic projection plane in our
+    // code (nor in the CSS spec to my knowledge). Therefore, layers that are technically
+    // behind the surface in an orthographic world should not be clipped when they are
+    // flattened to the surface.
+
+    IntRect targetSurfaceRect = IntRect(IntPoint(0, 0), IntSize(100, 100));
+    IntRect layerContentRect = IntRect(IntPoint(0, 0), IntSize(100, 100));
+    TransformationMatrix layerToSurfaceTransform;
+
+    // This sequence of transforms effectively rotates the layer about the y-axis at the
+    // center of the layer.
+    layerToSurfaceTransform.makeIdentity();
+    layerToSurfaceTransform.translate(50, 0);
+    layerToSurfaceTransform.rotate3d(0, 45, 0);
+    layerToSurfaceTransform.translate(-50, 0);
+
+    IntRect expected = IntRect(IntPoint(0, 0), IntSize(100, 100));
+    IntRect actual = CCLayerTreeHostCommon::calculateVisibleRect(targetSurfaceRect, layerContentRect, layerToSurfaceTransform);
+    EXPECT_INT_RECT_EQ(expected, actual);
+}
+
+TEST(CCLayerTreeHostCommonTest, verifyVisibleRectFor3dPerspectiveIsClipped)
+{
+    // Test the calculateVisibleRect() function works correctly when projecting a surface
+    // onto a layer, but the layer is partially behind the camera (not just behind the
+    // projection plane). In this case, the cartesian coordinates may seem to be valid,
+    // but actually they are not. The visibleRect needs to be properly clipped by the
+    // w = 0 plane in homogeneous coordinates before converting to cartesian coordinates.
+
+    IntRect targetSurfaceRect = IntRect(IntPoint(-50, -50), IntSize(100, 100));
+    IntRect layerContentRect = IntRect(IntPoint(-10, -1), IntSize(20, 2));
+    TransformationMatrix layerToSurfaceTransform;
+
+    // The layer is positioned so that the right half of the layer should be in front of
+    // the camera, while the other half is behind the surface's projection plane. The
+    // following sequence of transforms applies the perspective and rotation about the
+    // center of the layer.
+    layerToSurfaceTransform.makeIdentity();
+    layerToSurfaceTransform.applyPerspective(1);
+    layerToSurfaceTransform.translate3d(0, 0, 1);
+    layerToSurfaceTransform.rotate3d(0, 45, 0);
+
+    // Sanity check that this transform does indeed cause w < 0 when applying the
+    // transform, otherwise this code is not testing the intended scenario.
+    bool clipped = false;
+    CCMathUtil::mapQuad(layerToSurfaceTransform, FloatQuad(FloatRect(layerContentRect)), clipped);
+    ASSERT_TRUE(clipped);
+
+    int expectedXPosition = -10;
+    int expectedWidth = 10;
+    IntRect actual = CCLayerTreeHostCommon::calculateVisibleRect(targetSurfaceRect, layerContentRect, layerToSurfaceTransform);
+    EXPECT_EQ(expectedXPosition, actual.x());
+    EXPECT_EQ(expectedWidth, actual.width());
+}
+
+TEST(CCLayerTreeHostCommonTest, verifyVisibleRectForPerspectiveUnprojection)
+{
+    // To determine visibleRect in layer space, there needs to be an un-projection from
+    // surface space to layer space. When the original transform was a perspective
+    // projection that was clipped, it returns a rect that encloses the clipped bounds.
+    // Un-projecting this new rect may require clipping again.
+
+    // This sequence of transforms causes one corner of the layer to protrude across the w = 0 plane, and should be clipped.
+    IntRect targetSurfaceRect = IntRect(IntPoint(-50, -50), IntSize(100, 100));
+    IntRect layerContentRect = IntRect(IntPoint(-10, -10), IntSize(20, 20));
+    TransformationMatrix layerToSurfaceTransform;
+    layerToSurfaceTransform.makeIdentity();
+    layerToSurfaceTransform.applyPerspective(1);
+    layerToSurfaceTransform.translate3d(0, 0, -5);
+    layerToSurfaceTransform.rotate3d(0, 45, 0);
+    layerToSurfaceTransform.rotate3d(80, 0, 0);
+
+    // Sanity check that un-projection does indeed cause w < 0, otherwise this code is not
+    // testing the intended scenario.
+    bool clipped = false;
+    FloatRect clippedRect = CCMathUtil::mapClippedRect(layerToSurfaceTransform, layerContentRect);
+    CCMathUtil::projectQuad(layerToSurfaceTransform.inverse(), FloatQuad(clippedRect), clipped);
+    ASSERT_TRUE(clipped);
+
+    // Only the corner of the layer is not visible on the surface because of being
+    // clipped. But, the net result of rounding visible region to an axis-aligned rect is
+    // that the entire layer should still be considered visible.
+    IntRect expected = IntRect(IntPoint(-10, -10), IntSize(20, 20));
+    IntRect actual = CCLayerTreeHostCommon::calculateVisibleRect(targetSurfaceRect, layerContentRect, layerToSurfaceTransform);
+    EXPECT_INT_RECT_EQ(expected, actual);
+}
+
 // FIXME:
 // continue working on https://bugs.webkit.org/show_bug.cgi?id=68942
 //  - add a test to verify clipping that changes the "center point"
_______________________________________________
webkit-changes mailing list
[email protected]
http://lists.webkit.org/mailman/listinfo.cgi/webkit-changes

Reply via email to