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"