[Chromium] New CCOcclusionTracker class with tests
authorcommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 22 Feb 2012 07:59:51 +0000 (07:59 +0000)
committercommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 22 Feb 2012 07:59:51 +0000 (07:59 +0000)
https://bugs.webkit.org/show_bug.cgi?id=78549

Patch by Dana Jansens <danakj@chromium.org> on 2012-02-21
Reviewed by James Robinson.

Source/WebCore:

Adds a CCOcclusionTrackerBase template class that is able to track occlusion
of layers while traversing the layer tree from front-to-back, with typedefed
versions for the main and impl threads.

At each step of the front-to-back traversal, the class should be notified of
changes to the current render target, and when done working with a layer, the
layer is added the tracked occlusion.

The class provides tests for checking if a rect in content space
for a layer/surface is occluded by others in front of it.

Unit tests: CCOcclusionTrackerTest.cpp

* WebCore.gypi:
* platform/graphics/chromium/cc/CCOcclusionTracker.cpp: Added.
(WebCore):
(WebCore::::enterTargetRenderSurface):
(WebCore::::finishedTargetRenderSurface):
(WebCore::transformSurfaceOpaqueRegion):
(WebCore::::leaveToTargetRenderSurface):
(WebCore::contentToScreenSpaceTransform):
(WebCore::contentToTargetSurfaceTransform):
(WebCore::computeOcclusionBehindLayer):
(WebCore::::markOccludedBehindLayer):
(WebCore::testContentRectOccluded):
(WebCore::::occluded):
(WebCore::::surfaceOccluded):
(WebCore::rectSubtractRegion):
(WebCore::computeUnoccludedContentRect):
(WebCore::::unoccludedContentRect):
(WebCore::::surfaceUnoccludedContentRect):
(WebCore::::currentOcclusionInScreenSpace):
(WebCore::::currentOcclusionInTargetSurface):
* platform/graphics/chromium/cc/CCOcclusionTracker.h: Added.
(WebCore):
(CCOcclusionTrackerBase):
(WebCore::CCOcclusionTrackerBase::CCOcclusionTrackerBase):
(StackObject):

Source/WebKit/chromium:

* WebKit.gypi:
* tests/CCLayerTreeHostCommonTest.cpp:
(WebCore):
* tests/CCOcclusionTrackerTest.cpp: Added.
(WebCore):
(WebCore::setLayerPropertiesForTesting):
(LayerChromiumWithForcedDrawsContent):
(WebCore::LayerChromiumWithForcedDrawsContent::LayerChromiumWithForcedDrawsContent):
(WebCore::LayerChromiumWithForcedDrawsContent::drawsContent):
(TestCCOcclusionTracker):
(WebCore::TestCCOcclusionTracker::occlusionInScreenSpace):
(WebCore::TestCCOcclusionTracker::occlusionInTargetSurface):
(WebCore::TestCCOcclusionTracker::setOcclusionInScreenSpace):
(WebCore::TestCCOcclusionTracker::setOcclusionInTargetSurface):
(WebCore::TEST):

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@108453 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Source/WebCore/ChangeLog
Source/WebCore/WebCore.gypi
Source/WebCore/platform/graphics/chromium/cc/CCOcclusionTracker.cpp [new file with mode: 0644]
Source/WebCore/platform/graphics/chromium/cc/CCOcclusionTracker.h [new file with mode: 0644]
Source/WebKit/chromium/ChangeLog
Source/WebKit/chromium/WebKit.gypi
Source/WebKit/chromium/tests/CCLayerTreeHostCommonTest.cpp
Source/WebKit/chromium/tests/CCOcclusionTrackerTest.cpp [new file with mode: 0644]

index d1f8a16f850e40a62f1bc1e496ccb85d849c26d9..c5beeadd207440c697d0af9201be674f0e679e65 100644 (file)
@@ -1,3 +1,49 @@
+2012-02-21  Dana Jansens  <danakj@chromium.org>
+
+        [Chromium] New CCOcclusionTracker class with tests
+        https://bugs.webkit.org/show_bug.cgi?id=78549
+
+        Reviewed by James Robinson.
+
+        Adds a CCOcclusionTrackerBase template class that is able to track occlusion
+        of layers while traversing the layer tree from front-to-back, with typedefed
+        versions for the main and impl threads.
+
+        At each step of the front-to-back traversal, the class should be notified of
+        changes to the current render target, and when done working with a layer, the
+        layer is added the tracked occlusion.
+
+        The class provides tests for checking if a rect in content space
+        for a layer/surface is occluded by others in front of it.
+
+        Unit tests: CCOcclusionTrackerTest.cpp
+
+        * WebCore.gypi:
+        * platform/graphics/chromium/cc/CCOcclusionTracker.cpp: Added.
+        (WebCore):
+        (WebCore::::enterTargetRenderSurface):
+        (WebCore::::finishedTargetRenderSurface):
+        (WebCore::transformSurfaceOpaqueRegion):
+        (WebCore::::leaveToTargetRenderSurface):
+        (WebCore::contentToScreenSpaceTransform):
+        (WebCore::contentToTargetSurfaceTransform):
+        (WebCore::computeOcclusionBehindLayer):
+        (WebCore::::markOccludedBehindLayer):
+        (WebCore::testContentRectOccluded):
+        (WebCore::::occluded):
+        (WebCore::::surfaceOccluded):
+        (WebCore::rectSubtractRegion):
+        (WebCore::computeUnoccludedContentRect):
+        (WebCore::::unoccludedContentRect):
+        (WebCore::::surfaceUnoccludedContentRect):
+        (WebCore::::currentOcclusionInScreenSpace):
+        (WebCore::::currentOcclusionInTargetSurface):
+        * platform/graphics/chromium/cc/CCOcclusionTracker.h: Added.
+        (WebCore):
+        (CCOcclusionTrackerBase):
+        (WebCore::CCOcclusionTrackerBase::CCOcclusionTrackerBase):
+        (StackObject):
+
 2012-02-21  Andreas Kling  <awesomekling@apple.com>
 
         Cache <font face> family lists in CSSValuePool.
index f850fa8cb322ba547f0e6a5d2308804e2351148e..7420cc5f1ea926972bb33222e38ad9dbafa0388e 100644 (file)
             'platform/graphics/chromium/cc/CCLayerTreeHost.h',
             'platform/graphics/chromium/cc/CCLayerTreeHostImpl.cpp',
             'platform/graphics/chromium/cc/CCLayerTreeHostImpl.h',
+            'platform/graphics/chromium/cc/CCOcclusionTracker.cpp',
+            'platform/graphics/chromium/cc/CCOcclusionTracker.h',
             'platform/graphics/chromium/cc/CCPageScaleAnimation.cpp',
             'platform/graphics/chromium/cc/CCPageScaleAnimation.h',
             'platform/graphics/chromium/cc/CCPluginDrawQuad.cpp',
diff --git a/Source/WebCore/platform/graphics/chromium/cc/CCOcclusionTracker.cpp b/Source/WebCore/platform/graphics/chromium/cc/CCOcclusionTracker.cpp
new file mode 100644 (file)
index 0000000..c5611ac
--- /dev/null
@@ -0,0 +1,351 @@
+/*
+ * 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 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 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"
+
+#if USE(ACCELERATED_COMPOSITING)
+
+#include "cc/CCOcclusionTracker.h"
+
+#include "LayerChromium.h"
+#include "cc/CCLayerImpl.h"
+
+#include <algorithm>
+
+using namespace std;
+
+namespace WebCore {
+
+template<typename LayerType, typename RenderSurfaceType>
+void CCOcclusionTrackerBase<LayerType, RenderSurfaceType>::enterTargetRenderSurface(const RenderSurfaceType* newTarget)
+{
+    if (!m_stack.isEmpty() && m_stack.last().surface == newTarget)
+        return;
+
+    const RenderSurfaceType* oldTarget = m_stack.isEmpty() ? 0 : m_stack.last().surface;
+    const RenderSurfaceType* oldAncestorThatMovesPixels = !oldTarget ? 0 : oldTarget->nearestAncestorThatMovesPixels();
+    const RenderSurfaceType* newAncestorThatMovesPixels = newTarget->nearestAncestorThatMovesPixels();
+
+    m_stack.append(StackObject());
+    m_stack.last().surface = newTarget;
+
+    // We copy the screen occlusion into the new RenderSurface subtree, but we never copy in the
+    // target occlusion, since we are looking at a new RenderSurface target.
+
+    // If we are entering a subtree that is going to move pixels around, then the occlusion we've computed
+    // so far won't apply to the pixels we're drawing here in the same way. We discard the occlusion thus
+    // far to be safe, and ensure we don't cull any pixels that are moved such that they become visible.
+    bool enteringSubtreeThatMovesPixels = newAncestorThatMovesPixels && newAncestorThatMovesPixels != oldAncestorThatMovesPixels;
+
+    bool copyScreenOcclusionForward = m_stack.size() > 1 && !enteringSubtreeThatMovesPixels;
+    if (copyScreenOcclusionForward) {
+        int lastIndex = m_stack.size() - 1;
+        m_stack[lastIndex].occlusionInScreen = m_stack[lastIndex - 1].occlusionInScreen;
+    }
+}
+
+template<typename LayerType, typename RenderSurfaceType>
+void CCOcclusionTrackerBase<LayerType, RenderSurfaceType>::finishedTargetRenderSurface(const LayerType* owningLayer, const RenderSurfaceType* finishedTarget)
+{
+    // FIXME: Remove the owningLayer parameter when we can get all the info from the surface.
+    ASSERT(owningLayer->renderSurface() == finishedTarget);
+
+    // Make sure we know about the target surface.
+    enterTargetRenderSurface(finishedTarget);
+
+    if (owningLayer->maskLayer() || finishedTarget->drawOpacity() < 1 || finishedTarget->filters().hasFilterThatAffectsOpacity()) {
+        m_stack.last().occlusionInScreen = Region();
+        m_stack.last().occlusionInTarget = Region();
+    }
+}
+
+template<typename RenderSurfaceType>
+static inline Region transformSurfaceOpaqueRegion(const RenderSurfaceType* surface, const Region& region, const TransformationMatrix& transform)
+{
+    // Verify that rects within the |surface| will remain rects in its target surface after applying |transform|. If this is true, then
+    // apply |transform| to each rect within |region| in order to transform the entire Region.
+
+    IntRect bounds = region.bounds();
+    FloatRect centeredBounds(-bounds.width() / 2.0, -bounds.height() / 2.0, bounds.width(), bounds.height());
+    FloatQuad transformedBoundsQuad = transform.mapQuad(FloatQuad(centeredBounds));
+    if (!transformedBoundsQuad.isRectilinear())
+        return Region();
+
+    Region transformedRegion;
+
+    IntRect surfaceBounds = surface->contentRect();
+    Vector<IntRect> rects = region.rects();
+    Vector<IntRect>::const_iterator end = rects.end();
+    for (Vector<IntRect>::const_iterator i = rects.begin(); i != end; ++i) {
+        FloatRect centeredOriginRect(-i->width() / 2.0 + i->x() - surfaceBounds.x(), -i->height() / 2.0 + i->y() - surfaceBounds.y(), i->width(), i->height());
+        FloatRect transformedRect = transform.mapRect(FloatRect(centeredOriginRect));
+        transformedRegion.unite(enclosedIntRect(transformedRect));
+    }
+    return transformedRegion;
+}
+
+template<typename LayerType, typename RenderSurfaceType>
+void CCOcclusionTrackerBase<LayerType, RenderSurfaceType>::leaveToTargetRenderSurface(const RenderSurfaceType* newTarget)
+{
+    int lastIndex = m_stack.size() - 1;
+    bool surfaceWillBeAtTopAfterPop = m_stack.size() > 1 && m_stack[lastIndex - 1].surface == newTarget;
+
+    // We merge the screen occlusion from the current RenderSurface subtree out to its parent target RenderSurface.
+    // The target occlusion can be merged out as well but needs to be transformed to the new target.
+
+    const RenderSurfaceType* oldTarget = m_stack[lastIndex].surface;
+    Region oldTargetOcclusionInNewTarget = transformSurfaceOpaqueRegion<RenderSurfaceType>(oldTarget, m_stack[lastIndex].occlusionInTarget, oldTarget->drawTransform());
+
+    if (surfaceWillBeAtTopAfterPop) {
+        // Merge the top of the stack down.
+
+        m_stack[lastIndex - 1].occlusionInScreen.unite(m_stack[lastIndex].occlusionInScreen);
+        m_stack[lastIndex - 1].occlusionInTarget.unite(oldTargetOcclusionInNewTarget);
+        m_stack.removeLast();
+    } else {
+        // Replace the top of the stack with the new pushed surface. Copy the occluded screen region to the top.
+        m_stack.last().surface = newTarget;
+        m_stack.last().occlusionInTarget = oldTargetOcclusionInNewTarget;
+    }
+}
+
+template<typename LayerType>
+static inline TransformationMatrix contentToScreenSpaceTransform(const LayerType* layer)
+{
+    IntSize boundsInLayerSpace = layer->bounds();
+    IntSize boundsInContentSpace = layer->contentBounds();
+
+    TransformationMatrix transform = layer->screenSpaceTransform();
+
+    if (boundsInContentSpace.isEmpty())
+        return transform;
+
+    // Scale from content space to layer space
+    transform.scaleNonUniform(boundsInLayerSpace.width() / static_cast<double>(boundsInContentSpace.width()),
+                              boundsInLayerSpace.height() / static_cast<double>(boundsInContentSpace.height()));
+
+    return transform;
+}
+
+template<typename LayerType>
+static inline TransformationMatrix contentToTargetSurfaceTransform(const LayerType* layer)
+{
+    IntSize boundsInLayerSpace = layer->bounds();
+    IntSize boundsInContentSpace = layer->contentBounds();
+
+    TransformationMatrix transform = layer->drawTransform();
+
+    if (boundsInContentSpace.isEmpty())
+        return transform;
+
+    // Scale from content space to layer space
+    transform.scaleNonUniform(boundsInLayerSpace.width() / static_cast<double>(boundsInContentSpace.width()),
+                              boundsInLayerSpace.height() / static_cast<double>(boundsInContentSpace.height()));
+
+    // The draw transform expects the origin to be in the middle of the layer.
+    transform.translate(-boundsInContentSpace.width() / 2.0, -boundsInContentSpace.height() / 2.0);
+
+    return transform;
+}
+
+template<typename LayerType>
+static inline Region computeOcclusionBehindLayer(const LayerType* layer, const TransformationMatrix& transform)
+{
+    Region opaqueRegion;
+
+    FloatQuad unoccludedQuad = transform.mapQuad(FloatQuad(layer->visibleLayerRect()));
+    bool isPaintedAxisAligned = unoccludedQuad.isRectilinear();
+    if (!isPaintedAxisAligned)
+        return opaqueRegion;
+
+    if (layer->opaque())
+        opaqueRegion = enclosedIntRect(unoccludedQuad.boundingBox());
+    // FIXME: Capture opaque paints: else opaqueRegion = layer->opaqueContentsRegion(transform);
+    return opaqueRegion;
+}
+
+template<typename LayerType, typename RenderSurfaceType>
+void CCOcclusionTrackerBase<LayerType, RenderSurfaceType>::markOccludedBehindLayer(const LayerType* layer)
+{
+    ASSERT(!m_stack.isEmpty());
+    ASSERT(layer->targetRenderSurface() == m_stack.last().surface);
+
+    if (layer->drawOpacity() != 1)
+        return;
+
+    TransformationMatrix contentToScreenSpace = contentToScreenSpaceTransform<LayerType>(layer);
+    TransformationMatrix contentToTargetSurface = contentToTargetSurfaceTransform<LayerType>(layer);
+
+    m_stack.last().occlusionInScreen.unite(computeOcclusionBehindLayer<LayerType>(layer, contentToScreenSpace));
+    m_stack.last().occlusionInTarget.unite(computeOcclusionBehindLayer<LayerType>(layer, contentToTargetSurface));
+}
+
+static inline bool testContentRectOccluded(const IntRect& contentRect, const TransformationMatrix& contentSpaceTransform, const Region& occlusion)
+{
+    FloatQuad transformedQuad = contentSpaceTransform.mapQuad(FloatQuad(contentRect));
+    return occlusion.contains(transformedQuad.enclosingBoundingBox());
+}
+
+template<typename LayerType, typename RenderSurfaceType>
+bool CCOcclusionTrackerBase<LayerType, RenderSurfaceType>::occluded(const LayerType* layer, const IntRect& contentRect) const
+{
+    if (m_stack.isEmpty())
+        return false;
+
+    ASSERT(layer->targetRenderSurface() == m_stack.last().surface);
+
+    if (testContentRectOccluded(contentRect, contentToScreenSpaceTransform<LayerType>(layer), m_stack.last().occlusionInScreen))
+        return true;
+    if (testContentRectOccluded(contentRect, contentToTargetSurfaceTransform<LayerType>(layer), m_stack.last().occlusionInTarget))
+        return true;
+    return false;
+}
+
+template<typename LayerType, typename RenderSurfaceType>
+bool CCOcclusionTrackerBase<LayerType, RenderSurfaceType>::surfaceOccluded(const LayerType* layer, const IntRect& surfaceContentRect) const
+{
+    // A surface is not occluded by layers drawing into itself, so we need to use occlusion from one spot down on the stack.
+    if (m_stack.size() < 2)
+        return false;
+
+    ASSERT(layer->renderSurface());
+    ASSERT(layer->renderSurface() == m_stack.last().surface);
+
+    TransformationMatrix surfaceContentToScreenSpace = contentToScreenSpaceTransform<LayerType>(layer);
+
+    const StackObject& secondLast = m_stack[m_stack.size()-2];
+    if (testContentRectOccluded(surfaceContentRect, surfaceContentToScreenSpace, secondLast.occlusionInScreen))
+        return true;
+    return false;
+}
+
+// Determines what portion of rect, if any, is unoccluded (not occluded by region). If
+// the resulting unoccluded region is not rectangular, we return a rect containing it.
+static inline IntRect rectSubtractRegion(const IntRect& rect, const Region& region)
+{
+    Region rectRegion(rect);
+    Region intersectRegion(intersect(region, rectRegion));
+
+    if (intersectRegion.isEmpty())
+        return rect;
+
+    rectRegion.subtract(intersectRegion);
+    IntRect boundsRect = rectRegion.bounds();
+    return boundsRect;
+}
+
+static IntRect computeUnoccludedContentRect(const IntRect& contentRect, const TransformationMatrix& contentSpaceTransform, const Region& occlusion)
+{
+    FloatQuad transformedQuad = contentSpaceTransform.mapQuad(FloatQuad(contentRect));
+    if (!transformedQuad.isRectilinear())
+        return contentRect;
+    // Take the enclosingIntRect at each step here, as we want to contain any unoccluded partial pixels in the resulting IntRect.
+    IntRect shrunkRect = rectSubtractRegion(enclosingIntRect(transformedQuad.boundingBox()), occlusion);
+    IntRect unoccludedRect = enclosingIntRect(contentSpaceTransform.inverse().mapRect(FloatRect(shrunkRect)));
+    // The use of enclosingIntRect, with floating point rounding, can give us a result that is not a sub-rect of contentRect, but our
+    // return value should be a sub-rect.
+    return intersection(unoccludedRect, contentRect);
+}
+
+template<typename LayerType, typename RenderSurfaceType>
+IntRect CCOcclusionTrackerBase<LayerType, RenderSurfaceType>::unoccludedContentRect(const LayerType* layer, const IntRect& contentRect) const
+{
+    if (m_stack.isEmpty())
+        return contentRect;
+
+    // We want to return a rect that contains all the visible parts of |contentRect| in both screen space and in the target surface.
+    // So we find the visible parts of |contentRect| in each space, and take the intersection.
+
+    TransformationMatrix contentToScreenSpace = contentToScreenSpaceTransform<LayerType>(layer);
+    TransformationMatrix contentToTargetSurface = contentToTargetSurfaceTransform<LayerType>(layer);
+
+    IntRect unoccludedInScreen = computeUnoccludedContentRect(contentRect, contentToScreenSpace, m_stack.last().occlusionInScreen);
+    IntRect unoccludedInTarget = computeUnoccludedContentRect(contentRect, contentToTargetSurface, m_stack.last().occlusionInTarget);
+
+    return intersection(unoccludedInScreen, unoccludedInTarget);
+}
+
+template<typename LayerType, typename RenderSurfaceType>
+IntRect CCOcclusionTrackerBase<LayerType, RenderSurfaceType>::surfaceUnoccludedContentRect(const LayerType* layer, const IntRect& surfaceContentRect) const
+{
+    // A surface is not occluded by layers drawing into itself, so we need to use occlusion from one spot down on the stack.
+    if (m_stack.size() < 2)
+        return surfaceContentRect;
+
+    ASSERT(layer->renderSurface());
+    ASSERT(layer->renderSurface() == m_stack.last().surface);
+
+    // We want to return a rect that contains all the visible parts of |contentRect| in both screen space and in the target surface.
+    // So we find the visible parts of |contentRect| in each space, and take the intersection.
+
+    TransformationMatrix contentToScreenSpace = contentToScreenSpaceTransform<LayerType>(layer);
+
+    const StackObject& secondLast = m_stack[m_stack.size()-2];
+    IntRect unoccludedInScreen = computeUnoccludedContentRect(surfaceContentRect, contentToScreenSpace, secondLast.occlusionInScreen);
+
+    return unoccludedInScreen;
+}
+
+template<typename LayerType, typename RenderSurfaceType>
+const Region& CCOcclusionTrackerBase<LayerType, RenderSurfaceType>::currentOcclusionInScreenSpace() const
+{
+    ASSERT(!m_stack.isEmpty());
+    return m_stack.last().occlusionInScreen;
+}
+
+template<typename LayerType, typename RenderSurfaceType>
+const Region& CCOcclusionTrackerBase<LayerType, RenderSurfaceType>::currentOcclusionInTargetSurface() const
+{
+    ASSERT(!m_stack.isEmpty());
+    return m_stack.last().occlusionInTarget;
+}
+
+
+// Declare the possible functions here for the linker.
+template void CCOcclusionTrackerBase<LayerChromium, RenderSurfaceChromium>::enterTargetRenderSurface(const RenderSurfaceChromium* newTarget);
+template void CCOcclusionTrackerBase<LayerChromium, RenderSurfaceChromium>::finishedTargetRenderSurface(const LayerChromium* owningLayer, const RenderSurfaceChromium* finishedTarget);
+template void CCOcclusionTrackerBase<LayerChromium, RenderSurfaceChromium>::leaveToTargetRenderSurface(const RenderSurfaceChromium* newTarget);
+template void CCOcclusionTrackerBase<LayerChromium, RenderSurfaceChromium>::markOccludedBehindLayer(const LayerChromium*);
+template bool CCOcclusionTrackerBase<LayerChromium, RenderSurfaceChromium>::occluded(const LayerChromium*, const IntRect& contentRect) const;
+template bool CCOcclusionTrackerBase<LayerChromium, RenderSurfaceChromium>::surfaceOccluded(const LayerChromium*, const IntRect& surfaceContentRect) const;
+template IntRect CCOcclusionTrackerBase<LayerChromium, RenderSurfaceChromium>::unoccludedContentRect(const LayerChromium*, const IntRect& contentRect) const;
+template IntRect CCOcclusionTrackerBase<LayerChromium, RenderSurfaceChromium>::surfaceUnoccludedContentRect(const LayerChromium*, const IntRect& surfaceContentRect) const;
+template const Region& CCOcclusionTrackerBase<LayerChromium, RenderSurfaceChromium>::currentOcclusionInScreenSpace() const;
+template const Region& CCOcclusionTrackerBase<LayerChromium, RenderSurfaceChromium>::currentOcclusionInTargetSurface() const;
+
+template void CCOcclusionTrackerBase<CCLayerImpl, CCRenderSurface>::enterTargetRenderSurface(const CCRenderSurface* newTarget);
+template void CCOcclusionTrackerBase<CCLayerImpl, CCRenderSurface>::finishedTargetRenderSurface(const CCLayerImpl* owningLayer, const CCRenderSurface* finishedTarget);
+template void CCOcclusionTrackerBase<CCLayerImpl, CCRenderSurface>::leaveToTargetRenderSurface(const CCRenderSurface* newTarget);
+template void CCOcclusionTrackerBase<CCLayerImpl, CCRenderSurface>::markOccludedBehindLayer(const CCLayerImpl*);
+template bool CCOcclusionTrackerBase<CCLayerImpl, CCRenderSurface>::occluded(const CCLayerImpl*, const IntRect& contentRect) const;
+template bool CCOcclusionTrackerBase<CCLayerImpl, CCRenderSurface>::surfaceOccluded(const CCLayerImpl*, const IntRect& surfaceContentRect) const;
+template IntRect CCOcclusionTrackerBase<CCLayerImpl, CCRenderSurface>::unoccludedContentRect(const CCLayerImpl*, const IntRect& contentRect) const;
+template IntRect CCOcclusionTrackerBase<CCLayerImpl, CCRenderSurface>::surfaceUnoccludedContentRect(const CCLayerImpl*, const IntRect& surfaceContentRect) const;
+template const Region& CCOcclusionTrackerBase<CCLayerImpl, CCRenderSurface>::currentOcclusionInScreenSpace() const;
+template const Region& CCOcclusionTrackerBase<CCLayerImpl, CCRenderSurface>::currentOcclusionInTargetSurface() const;
+
+
+} // namespace WebCore
+#endif // USE(ACCELERATED_COMPOSITING)
diff --git a/Source/WebCore/platform/graphics/chromium/cc/CCOcclusionTracker.h b/Source/WebCore/platform/graphics/chromium/cc/CCOcclusionTracker.h
new file mode 100644 (file)
index 0000000..d1c833e
--- /dev/null
@@ -0,0 +1,101 @@
+/*
+ * 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 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 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 CCOcclusionTracker_h
+#define CCOcclusionTracker_h
+
+#include "FloatQuad.h"
+#include "Region.h"
+#include "TransformationMatrix.h"
+
+namespace WebCore {
+class CCLayerImpl;
+class CCRenderSurface;
+class LayerChromium;
+class RenderSurfaceChromium;
+
+// This class is used to track occlusion of layers while traversing them in a front-to-back order. As each layer is visited, one of the
+// methods in this class is called to notify it about the current target surface.
+// Then, occlusion in the content space of the current layer may be queried, via methods such as occluded() and unoccludedContentRect().
+// If the current layer owns a RenderSurface, then occlusion on that RenderSurface may also be queried via surfaceOccluded() and surfaceUnoccludedContentRect().
+// Finally, once finished with the layer, occlusion behind the layer should be marked by calling markOccludedBehindLayer().
+template<typename LayerType, typename RenderSurfaceType>
+class CCOcclusionTrackerBase {
+public:
+    CCOcclusionTrackerBase() { }
+
+    // Called when visiting a layer representing itself. If the target was not already current, then this indicates we have entered a new surface subtree.
+    void enterTargetRenderSurface(const RenderSurfaceType* newTarget);
+
+    // Called when visiting a layer representing a target surface. This indicates we have visited all the layers within the surface, and we may
+    // perform any surface-wide operations.
+    void finishedTargetRenderSurface(const LayerType*, const RenderSurfaceType* finishedTarget);
+
+    // Called when visiting a layer representing a contributing surface. This  indicates that we are leaving our current surface, and
+    // entering the new one. We then perform any operations required for merging results from the child subtree into its parent.
+    void leaveToTargetRenderSurface(const RenderSurfaceType* newTarget);
+
+    // Add the layer's occlusion to the tracked state.
+    void markOccludedBehindLayer(const LayerType*);
+
+    // Returns true if the given rect in content space for the layer is fully occluded in either screen space or the layer's target surface.
+    bool occluded(const LayerType*, const IntRect& contentRect) const;
+    // Gives an unoccluded sub-rect of |contentRect| in the content space of the layer. Used when considering occlusion for a layer that paints/draws something.
+    IntRect unoccludedContentRect(const LayerType*, const IntRect& contentRect) const;
+
+    // Returns true if the given rect in content space for the RenderSurface owned by the layer is fully occluded in either screen space or the layer's target surface.
+    bool surfaceOccluded(const LayerType*, const IntRect& contentRect) const;
+    // Gives an unoccluded sub-rect of |contentRect| in the content space of the RenderSurface owned by the layer. Used when considering occlusion for a target surface.
+    IntRect surfaceUnoccludedContentRect(const LayerType*, const IntRect& contentRect) const;
+
+    // FIXME: Remove these in future, they are to make CLs for transitioning to this easier.
+    const Region& currentOcclusionInScreenSpace() const;
+    const Region& currentOcclusionInTargetSurface() const;
+
+protected:
+    struct StackObject {
+        const RenderSurfaceType* surface;
+        Region occlusionInScreen;
+        Region occlusionInTarget;
+    };
+
+    // The stack holds occluded regions for subtrees in the RenderSurface-Layer tree, so that when we leave a subtree we may
+    // apply a mask to it, but not to the parts outside the subtree.
+    // - The first time we see a new subtree under a target, we add that target to the top of the stack. This can happen as a layer representing itself, or as a target surface.
+    // - When we visit a target surface, we apply its mask to its subtree, which is at the top of the stack.
+    // - When we visit a layer representing itself, we add its occlusion to the current subtree, which is at the top of the stack.
+    // - When we visit a layer representing a contributing surface, the current target will never be the top of the stack since we just came from the contributing surface.
+    // We merge the occlusion at the top of the stack with the new current subtree. This new target is pushed onto the stack if not already there.
+    Vector<StackObject, 1> m_stack;
+
+private:
+    WTF_MAKE_NONCOPYABLE(CCOcclusionTrackerBase);
+};
+
+typedef CCOcclusionTrackerBase<LayerChromium, RenderSurfaceChromium> CCOcclusionTracker;
+typedef CCOcclusionTrackerBase<CCLayerImpl, CCRenderSurface> CCOcclusionTrackerImpl;
+
+}
+#endif // CCOcclusionTracker_h
index 0f8275af904bf675bfa7edc238a9ffe4f7bf95c8..d3a16dc85bbd4b2621f6b6c2dfa2bb8fb802d81d 100644 (file)
@@ -1,3 +1,26 @@
+2012-02-21  Dana Jansens  <danakj@chromium.org>
+
+        [Chromium] New CCOcclusionTracker class with tests
+        https://bugs.webkit.org/show_bug.cgi?id=78549
+
+        Reviewed by James Robinson.
+
+        * WebKit.gypi:
+        * tests/CCLayerTreeHostCommonTest.cpp:
+        (WebCore):
+        * tests/CCOcclusionTrackerTest.cpp: Added.
+        (WebCore):
+        (WebCore::setLayerPropertiesForTesting):
+        (LayerChromiumWithForcedDrawsContent):
+        (WebCore::LayerChromiumWithForcedDrawsContent::LayerChromiumWithForcedDrawsContent):
+        (WebCore::LayerChromiumWithForcedDrawsContent::drawsContent):
+        (TestCCOcclusionTracker):
+        (WebCore::TestCCOcclusionTracker::occlusionInScreenSpace):
+        (WebCore::TestCCOcclusionTracker::occlusionInTargetSurface):
+        (WebCore::TestCCOcclusionTracker::setOcclusionInScreenSpace):
+        (WebCore::TestCCOcclusionTracker::setOcclusionInTargetSurface):
+        (WebCore::TEST):
+
 2012-02-21  MORITA Hajime  <morrita@google.com>
 
         INPUT_SPEECH should be implemented as a PageSupplement.
index 6e9a4d0dfa95fb74871e5ff39fb28adffdaad9fb..9aa5d2ca65bd1bf607558e3089e99610a0f4660b 100644 (file)
@@ -75,6 +75,7 @@
             'tests/CCLayerTreeHostImplTest.cpp',
             'tests/CCLayerTreeHostTest.cpp',
             'tests/CCLayerTreeTestCommon.h',
+            'tests/CCOcclusionTrackerTest.cpp',
             'tests/CCQuadCullerTest.cpp',
             'tests/CCRenderSurfaceTest.cpp',
             'tests/CCSchedulerStateMachineTest.cpp',
index b1fa72918401e3240e41d13d2e0c27d0c56ad7c5..d2e1cfef93e282166da851256c95c0e6512c69ba 100644 (file)
@@ -28,7 +28,6 @@
 
 #include "CCLayerTreeTestCommon.h"
 #include "LayerChromium.h"
-#include "Region.h"
 #include "TransformationMatrix.h"
 
 #include <gmock/gmock.h>
 
 using namespace WebCore;
 
-#define EXPECT_EQ_RECT(a, b) \
-    EXPECT_EQ(a.x(), b.x()); \
-    EXPECT_EQ(a.y(), b.y()); \
-    EXPECT_EQ(a.width(), b.width()); \
-    EXPECT_EQ(a.height(), b.height());
-
 namespace {
 
 void setLayerPropertiesForTesting(LayerChromium* layer, const TransformationMatrix& transform, const TransformationMatrix& sublayerTransform, const FloatPoint& anchor, const FloatPoint& position, const IntSize& bounds, bool preserves3D)
@@ -602,174 +595,4 @@ TEST(CCLayerTreeHostCommonTest, verifyClipRectCullsRenderSurfaces)
 //  - test the other functions in CCLayerTreeHostCommon
 //
 
-TEST(CCLayerTreeHostCommonTest, layerAddsSelfToOccludedRegion)
-{
-    // This tests that the right transforms are being used.
-    Region occluded;
-    const TransformationMatrix identityMatrix;
-    RefPtr<LayerChromium> parent = LayerChromium::create();
-    RefPtr<LayerChromiumWithForcedDrawsContent> layer = adoptRef(new LayerChromiumWithForcedDrawsContent());
-    parent->createRenderSurface();
-    parent->addChild(layer);
-
-    setLayerPropertiesForTesting(parent.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(100, 100), false);
-    setLayerPropertiesForTesting(layer.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(30, 30), IntSize(500, 500), false);
-
-    layer->setOpaque(true);
-
-    Vector<RefPtr<LayerChromium> > renderSurfaceLayerList;
-    Vector<RefPtr<LayerChromium> > dummyLayerList;
-    int dummyMaxTextureSize = 512;
-
-    // FIXME: when we fix this "root-layer special case" behavior in CCLayerTreeHost, we will have to fix it here, too.
-    parent->renderSurface()->setContentRect(IntRect(IntPoint::zero(), parent->bounds()));
-    parent->setClipRect(IntRect(IntPoint::zero(), parent->bounds()));
-    renderSurfaceLayerList.append(parent);
-
-    CCLayerTreeHostCommon::calculateDrawTransformsAndVisibility(parent.get(), parent.get(), identityMatrix, identityMatrix, renderSurfaceLayerList, dummyLayerList, dummyMaxTextureSize);
-
-    occluded = Region();
-    layer->addSelfToOccludedScreenSpace(occluded);
-    EXPECT_EQ_RECT(IntRect(30, 30, 70, 70), occluded.bounds());
-    EXPECT_EQ(1u, occluded.rects().size());
-}
-
-TEST(CCLayerTreeHostCommonTest, layerAddsSelfToOccludedRegionWithRotation)
-{
-    // This tests that the right transforms are being used.
-    Region occluded;
-    const TransformationMatrix identityMatrix;
-    RefPtr<LayerChromium> parent = LayerChromium::create();
-    RefPtr<LayerChromiumWithForcedDrawsContent> layer = adoptRef(new LayerChromiumWithForcedDrawsContent());
-    parent->createRenderSurface();
-    parent->addChild(layer);
-
-    TransformationMatrix layerTransform;
-    layerTransform.translate(250, 250);
-    layerTransform.rotate(90);
-    layerTransform.translate(-250, -250);
-
-    setLayerPropertiesForTesting(parent.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(100, 100), false);
-    setLayerPropertiesForTesting(layer.get(), layerTransform, identityMatrix, FloatPoint(0, 0), FloatPoint(30, 30), IntSize(500, 500), false);
-
-    layer->setOpaque(true);
-
-    Vector<RefPtr<LayerChromium> > renderSurfaceLayerList;
-    Vector<RefPtr<LayerChromium> > dummyLayerList;
-    int dummyMaxTextureSize = 512;
-
-    // FIXME: when we fix this "root-layer special case" behavior in CCLayerTreeHost, we will have to fix it here, too.
-    parent->renderSurface()->setContentRect(IntRect(IntPoint::zero(), parent->bounds()));
-    parent->setClipRect(IntRect(IntPoint::zero(), parent->bounds()));
-    renderSurfaceLayerList.append(parent);
-
-    CCLayerTreeHostCommon::calculateDrawTransformsAndVisibility(parent.get(), parent.get(), identityMatrix, identityMatrix, renderSurfaceLayerList, dummyLayerList, dummyMaxTextureSize);
-
-    occluded = Region();
-    layer->addSelfToOccludedScreenSpace(occluded);
-    EXPECT_EQ_RECT(IntRect(30, 30, 70, 70), occluded.bounds());
-    EXPECT_EQ(1u, occluded.rects().size());
-}
-
-TEST(CCLayerTreeHostCommonTest, layerAddsSelfToOccludedRegionWithTranslation)
-{
-    // This tests that the right transforms are being used.
-    Region occluded;
-    const TransformationMatrix identityMatrix;
-    RefPtr<LayerChromium> parent = LayerChromium::create();
-    RefPtr<LayerChromiumWithForcedDrawsContent> layer = adoptRef(new LayerChromiumWithForcedDrawsContent());
-    parent->createRenderSurface();
-    parent->addChild(layer);
-
-    TransformationMatrix layerTransform;
-    layerTransform.translate(20, 20);
-
-    setLayerPropertiesForTesting(parent.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(100, 100), false);
-    setLayerPropertiesForTesting(layer.get(), layerTransform, identityMatrix, FloatPoint(0, 0), FloatPoint(30, 30), IntSize(500, 500), false);
-
-    layer->setOpaque(true);
-
-    Vector<RefPtr<LayerChromium> > renderSurfaceLayerList;
-    Vector<RefPtr<LayerChromium> > dummyLayerList;
-    int dummyMaxTextureSize = 512;
-
-    // FIXME: when we fix this "root-layer special case" behavior in CCLayerTreeHost, we will have to fix it here, too.
-    parent->renderSurface()->setContentRect(IntRect(IntPoint::zero(), parent->bounds()));
-    parent->setClipRect(IntRect(IntPoint::zero(), parent->bounds()));
-    renderSurfaceLayerList.append(parent);
-
-    CCLayerTreeHostCommon::calculateDrawTransformsAndVisibility(parent.get(), parent.get(), identityMatrix, identityMatrix, renderSurfaceLayerList, dummyLayerList, dummyMaxTextureSize);
-
-    occluded = Region();
-    layer->addSelfToOccludedScreenSpace(occluded);
-    EXPECT_EQ_RECT(IntRect(50, 50, 50, 50), occluded.bounds());
-    EXPECT_EQ(1u, occluded.rects().size());
-}
-
-TEST(CCLayerTreeHostCommonTest, layerAddsSelfToOccludedRegionWithRotatedSurface)
-{
-    // This tests that the right transforms are being used.
-    Region occluded;
-    const TransformationMatrix identityMatrix;
-    RefPtr<LayerChromium> parent = LayerChromium::create();
-    RefPtr<LayerChromium> child = LayerChromium::create();
-    RefPtr<LayerChromiumWithForcedDrawsContent> layer = adoptRef(new LayerChromiumWithForcedDrawsContent());
-    parent->createRenderSurface();
-    parent->addChild(child);
-    child->addChild(layer);
-
-    TransformationMatrix childTransform;
-    childTransform.translate(250, 250);
-    childTransform.rotate(90);
-    childTransform.translate(-250, -250);
-
-    setLayerPropertiesForTesting(parent.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(100, 100), false);
-    setLayerPropertiesForTesting(child.get(), childTransform, identityMatrix, FloatPoint(0, 0), FloatPoint(30, 30), IntSize(500, 500), false);
-    setLayerPropertiesForTesting(layer.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(10, 10), IntSize(500, 500), false);
-
-    child->setMasksToBounds(true);
-    layer->setOpaque(true);
-
-    Vector<RefPtr<LayerChromium> > renderSurfaceLayerList;
-    Vector<RefPtr<LayerChromium> > dummyLayerList;
-    int dummyMaxTextureSize = 512;
-
-    // FIXME: when we fix this "root-layer special case" behavior in CCLayerTreeHost, we will have to fix it here, too.
-    parent->renderSurface()->setContentRect(IntRect(IntPoint::zero(), parent->bounds()));
-    parent->setClipRect(IntRect(IntPoint::zero(), parent->bounds()));
-    renderSurfaceLayerList.append(parent);
-
-    CCLayerTreeHostCommon::calculateDrawTransformsAndVisibility(parent.get(), parent.get(), identityMatrix, identityMatrix, renderSurfaceLayerList, dummyLayerList, dummyMaxTextureSize);
-
-    occluded = Region();
-    layer->addSelfToOccludedScreenSpace(occluded);
-    EXPECT_EQ_RECT(IntRect(30, 40, 70, 60), occluded.bounds());
-    EXPECT_EQ(1u, occluded.rects().size());
-
-    /* Justification for the above opaque rect from |layer|:
-               100
-      +---------------------+                                      +---------------------+
-      |                     |                                      |                     |30  Visible region of |layer|: /////
-      |    30               |           rotate(90)                 |                     |
-      | 30 + ---------------------------------+                    |     +---------------------------------+
-  100 |    |  10            |                 |            ==>     |     |               |10               |
-      |    |10+---------------------------------+                  |  +---------------------------------+  |
-      |    |  |             |                 | |                  |  |  |///////////////|     420      |  |
-      |    |  |             |                 | |                  |  |  |///////////////|60            |  |
-      |    |  |             |                 | |                  |  |  |///////////////|              |  |
-      +----|--|-------------+                 | |                  +--|--|---------------+              |  |
-           |  |                               | |                   20|10|     70                       |  |
-           |  |                               | |                     |  |                              |  |
-           |  |                               | |500                  |  |                              |  |
-           |  |                               | |                     |  |                              |  |
-           |  |                               | |                     |  |                              |  |
-           |  |                               | |                     |  |                              |  |
-           |  |                               | |                     |  |                              |10|
-           +--|-------------------------------+ |                     |  +------------------------------|--+
-              |                                 |                     |                 490             |
-              +---------------------------------+                     +---------------------------------+
-                             500                                                     500
-     */
-}
-
 } // namespace
diff --git a/Source/WebKit/chromium/tests/CCOcclusionTrackerTest.cpp b/Source/WebKit/chromium/tests/CCOcclusionTrackerTest.cpp
new file mode 100644 (file)
index 0000000..7e7310b
--- /dev/null
@@ -0,0 +1,1028 @@
+/*
+ * 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/CCOcclusionTracker.h"
+
+#include "FilterOperations.h"
+#include "LayerChromium.h"
+#include "Region.h"
+#include "TransformationMatrix.h"
+#include "cc/CCLayerTreeHostCommon.h"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+using namespace WebCore;
+
+#define EXPECT_EQ_RECT(a, b) \
+    EXPECT_EQ(a.x(), b.x()); \
+    EXPECT_EQ(a.y(), b.y()); \
+    EXPECT_EQ(a.width(), b.width()); \
+    EXPECT_EQ(a.height(), b.height());
+
+namespace {
+
+void setLayerPropertiesForTesting(LayerChromium* layer, const TransformationMatrix& transform, const TransformationMatrix& sublayerTransform, const FloatPoint& anchor, const FloatPoint& position, const IntSize& bounds, bool opaque)
+{
+    layer->setTransform(transform);
+    layer->setSublayerTransform(sublayerTransform);
+    layer->setAnchorPoint(anchor);
+    layer->setPosition(position);
+    layer->setBounds(bounds);
+    layer->setOpaque(opaque);
+}
+
+class LayerChromiumWithForcedDrawsContent : public LayerChromium {
+public:
+    LayerChromiumWithForcedDrawsContent()
+        : LayerChromium()
+    {
+    }
+
+    virtual bool drawsContent() const { return true; }
+};
+
+// A subclass to expose the total current occlusion.
+class TestCCOcclusionTracker : public CCOcclusionTracker {
+public:
+    Region occlusionInScreenSpace() const { return CCOcclusionTracker::m_stack.last().occlusionInScreen; }
+    Region occlusionInTargetSurface() const { return CCOcclusionTracker::m_stack.last().occlusionInTarget; }
+
+    void setOcclusionInScreenSpace(const Region& region) { CCOcclusionTracker::m_stack.last().occlusionInScreen = region; }
+    void setOcclusionInTargetSurface(const Region& region) { CCOcclusionTracker::m_stack.last().occlusionInTarget = region; }
+};
+
+TEST(CCOcclusionTrackerTest, layerAddedToOccludedRegion)
+{
+    // This tests that the right transforms are being used.
+    TestCCOcclusionTracker occlusion;
+    const TransformationMatrix identityMatrix;
+    RefPtr<LayerChromium> parent = LayerChromium::create();
+    RefPtr<LayerChromiumWithForcedDrawsContent> layer = adoptRef(new LayerChromiumWithForcedDrawsContent());
+    parent->createRenderSurface();
+    parent->addChild(layer);
+
+    setLayerPropertiesForTesting(parent.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(100, 100), false);
+    setLayerPropertiesForTesting(layer.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(30, 30), IntSize(500, 500), true);
+
+    Vector<RefPtr<LayerChromium> > renderSurfaceLayerList;
+    Vector<RefPtr<LayerChromium> > dummyLayerList;
+    int dummyMaxTextureSize = 512;
+
+    parent->renderSurface()->setContentRect(IntRect(IntPoint::zero(), parent->bounds()));
+    parent->setClipRect(IntRect(IntPoint::zero(), parent->bounds()));
+    renderSurfaceLayerList.append(parent);
+
+    CCLayerTreeHostCommon::calculateDrawTransformsAndVisibility(parent.get(), parent.get(), identityMatrix, identityMatrix, renderSurfaceLayerList, dummyLayerList, dummyMaxTextureSize);
+
+    occlusion.enterTargetRenderSurface(parent->renderSurface());
+    occlusion.markOccludedBehindLayer(layer.get());
+    EXPECT_EQ_RECT(IntRect(30, 30, 70, 70), occlusion.occlusionInScreenSpace().bounds());
+    EXPECT_EQ(1u, occlusion.occlusionInScreenSpace().rects().size());
+    EXPECT_EQ_RECT(IntRect(30, 30, 70, 70), occlusion.occlusionInTargetSurface().bounds());
+    EXPECT_EQ(1u, occlusion.occlusionInTargetSurface().rects().size());
+
+    EXPECT_EQ(true, occlusion.occluded(parent.get(), IntRect(30, 30, 70, 70)));
+    EXPECT_EQ(false, occlusion.occluded(parent.get(), IntRect(29, 30, 70, 70)));
+    EXPECT_EQ(false, occlusion.occluded(parent.get(), IntRect(30, 29, 70, 70)));
+    EXPECT_EQ(false, occlusion.occluded(parent.get(), IntRect(31, 30, 70, 70)));
+    EXPECT_EQ(false, occlusion.occluded(parent.get(), IntRect(30, 31, 70, 70)));
+
+    EXPECT_TRUE(occlusion.unoccludedContentRect(parent.get(), IntRect(30, 30, 70, 70)).isEmpty());
+    EXPECT_EQ_RECT(IntRect(29, 30, 1, 70), occlusion.unoccludedContentRect(parent.get(), IntRect(29, 30, 70, 70)));
+    EXPECT_EQ_RECT(IntRect(29, 29, 70, 70), occlusion.unoccludedContentRect(parent.get(), IntRect(29, 29, 70, 70)));
+    EXPECT_EQ_RECT(IntRect(30, 29, 70, 1), occlusion.unoccludedContentRect(parent.get(), IntRect(30, 29, 70, 70)));
+    EXPECT_EQ_RECT(IntRect(31, 29, 70, 70), occlusion.unoccludedContentRect(parent.get(), IntRect(31, 29, 70, 70)));
+    EXPECT_EQ_RECT(IntRect(100, 30, 1, 70), occlusion.unoccludedContentRect(parent.get(), IntRect(31, 30, 70, 70)));
+    EXPECT_EQ_RECT(IntRect(31, 31, 70, 70), occlusion.unoccludedContentRect(parent.get(), IntRect(31, 31, 70, 70)));
+    EXPECT_EQ_RECT(IntRect(30, 100, 70, 1), occlusion.unoccludedContentRect(parent.get(), IntRect(30, 31, 70, 70)));
+    EXPECT_EQ_RECT(IntRect(29, 31, 70, 70), occlusion.unoccludedContentRect(parent.get(), IntRect(29, 31, 70, 70)));
+}
+
+TEST(CCOcclusionTrackerTest, layerAddedToOccludedRegionWithRotation)
+{
+    // This tests that the right transforms are being used.
+    TestCCOcclusionTracker occlusion;
+    const TransformationMatrix identityMatrix;
+    RefPtr<LayerChromium> parent = LayerChromium::create();
+    RefPtr<LayerChromiumWithForcedDrawsContent> layer = adoptRef(new LayerChromiumWithForcedDrawsContent());
+    parent->createRenderSurface();
+    parent->addChild(layer);
+
+    TransformationMatrix layerTransform;
+    layerTransform.translate(250, 250);
+    layerTransform.rotate(90);
+    layerTransform.translate(-250, -250);
+
+    setLayerPropertiesForTesting(parent.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(100, 100), false);
+    setLayerPropertiesForTesting(layer.get(), layerTransform, identityMatrix, FloatPoint(0, 0), FloatPoint(30, 30), IntSize(500, 500), true);
+
+    Vector<RefPtr<LayerChromium> > renderSurfaceLayerList;
+    Vector<RefPtr<LayerChromium> > dummyLayerList;
+    int dummyMaxTextureSize = 512;
+
+    parent->renderSurface()->setContentRect(IntRect(IntPoint::zero(), parent->bounds()));
+    parent->setClipRect(IntRect(IntPoint::zero(), parent->bounds()));
+    renderSurfaceLayerList.append(parent);
+
+    CCLayerTreeHostCommon::calculateDrawTransformsAndVisibility(parent.get(), parent.get(), identityMatrix, identityMatrix, renderSurfaceLayerList, dummyLayerList, dummyMaxTextureSize);
+
+    occlusion.enterTargetRenderSurface(parent->renderSurface());
+    occlusion.markOccludedBehindLayer(layer.get());
+    EXPECT_EQ_RECT(IntRect(30, 30, 70, 70), occlusion.occlusionInScreenSpace().bounds());
+    EXPECT_EQ(1u, occlusion.occlusionInScreenSpace().rects().size());
+    EXPECT_EQ_RECT(IntRect(30, 30, 70, 70), occlusion.occlusionInTargetSurface().bounds());
+    EXPECT_EQ(1u, occlusion.occlusionInTargetSurface().rects().size());
+
+    EXPECT_EQ(true, occlusion.occluded(parent.get(), IntRect(30, 30, 70, 70)));
+    EXPECT_EQ(false, occlusion.occluded(parent.get(), IntRect(29, 30, 70, 70)));
+    EXPECT_EQ(false, occlusion.occluded(parent.get(), IntRect(30, 29, 70, 70)));
+    EXPECT_EQ(false, occlusion.occluded(parent.get(), IntRect(31, 30, 70, 70)));
+    EXPECT_EQ(false, occlusion.occluded(parent.get(), IntRect(30, 31, 70, 70)));
+
+    EXPECT_TRUE(occlusion.unoccludedContentRect(parent.get(), IntRect(30, 30, 70, 70)).isEmpty());
+    EXPECT_EQ_RECT(IntRect(29, 30, 1, 70), occlusion.unoccludedContentRect(parent.get(), IntRect(29, 30, 70, 70)));
+    EXPECT_EQ_RECT(IntRect(29, 29, 70, 70), occlusion.unoccludedContentRect(parent.get(), IntRect(29, 29, 70, 70)));
+    EXPECT_EQ_RECT(IntRect(30, 29, 70, 1), occlusion.unoccludedContentRect(parent.get(), IntRect(30, 29, 70, 70)));
+    EXPECT_EQ_RECT(IntRect(31, 29, 70, 70), occlusion.unoccludedContentRect(parent.get(), IntRect(31, 29, 70, 70)));
+    EXPECT_EQ_RECT(IntRect(100, 30, 1, 70), occlusion.unoccludedContentRect(parent.get(), IntRect(31, 30, 70, 70)));
+    EXPECT_EQ_RECT(IntRect(31, 31, 70, 70), occlusion.unoccludedContentRect(parent.get(), IntRect(31, 31, 70, 70)));
+    EXPECT_EQ_RECT(IntRect(30, 100, 70, 1), occlusion.unoccludedContentRect(parent.get(), IntRect(30, 31, 70, 70)));
+    EXPECT_EQ_RECT(IntRect(29, 31, 70, 70), occlusion.unoccludedContentRect(parent.get(), IntRect(29, 31, 70, 70)));
+}
+
+TEST(CCOcclusionTrackerTest, layerAddedToOccludedRegionWithTranslation)
+{
+    // This tests that the right transforms are being used.
+    TestCCOcclusionTracker occlusion;
+    const TransformationMatrix identityMatrix;
+    RefPtr<LayerChromium> parent = LayerChromium::create();
+    RefPtr<LayerChromiumWithForcedDrawsContent> layer = adoptRef(new LayerChromiumWithForcedDrawsContent());
+    parent->createRenderSurface();
+    parent->addChild(layer);
+
+    TransformationMatrix layerTransform;
+    layerTransform.translate(20, 20);
+
+    setLayerPropertiesForTesting(parent.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(100, 100), false);
+    setLayerPropertiesForTesting(layer.get(), layerTransform, identityMatrix, FloatPoint(0, 0), FloatPoint(30, 30), IntSize(500, 500), true);
+
+    Vector<RefPtr<LayerChromium> > renderSurfaceLayerList;
+    Vector<RefPtr<LayerChromium> > dummyLayerList;
+    int dummyMaxTextureSize = 512;
+
+    parent->renderSurface()->setContentRect(IntRect(IntPoint::zero(), parent->bounds()));
+    parent->setClipRect(IntRect(IntPoint::zero(), parent->bounds()));
+    renderSurfaceLayerList.append(parent);
+
+    CCLayerTreeHostCommon::calculateDrawTransformsAndVisibility(parent.get(), parent.get(), identityMatrix, identityMatrix, renderSurfaceLayerList, dummyLayerList, dummyMaxTextureSize);
+
+    occlusion.enterTargetRenderSurface(parent->renderSurface());
+    occlusion.markOccludedBehindLayer(layer.get());
+    EXPECT_EQ_RECT(IntRect(50, 50, 50, 50), occlusion.occlusionInScreenSpace().bounds());
+    EXPECT_EQ(1u, occlusion.occlusionInScreenSpace().rects().size());
+    EXPECT_EQ_RECT(IntRect(50, 50, 50, 50), occlusion.occlusionInTargetSurface().bounds());
+    EXPECT_EQ(1u, occlusion.occlusionInTargetSurface().rects().size());
+
+    EXPECT_EQ(true, occlusion.occluded(parent.get(), IntRect(50, 50, 50, 50)));
+    EXPECT_EQ(false, occlusion.occluded(parent.get(), IntRect(49, 50, 50, 50)));
+    EXPECT_EQ(false, occlusion.occluded(parent.get(), IntRect(50, 49, 50, 50)));
+    EXPECT_EQ(false, occlusion.occluded(parent.get(), IntRect(51, 50, 50, 50)));
+    EXPECT_EQ(false, occlusion.occluded(parent.get(), IntRect(50, 51, 50, 50)));
+
+    EXPECT_TRUE(occlusion.unoccludedContentRect(parent.get(), IntRect(50, 50, 50, 50)).isEmpty());
+    EXPECT_EQ_RECT(IntRect(49, 50, 1, 50), occlusion.unoccludedContentRect(parent.get(), IntRect(49, 50, 50, 50)));
+    EXPECT_EQ_RECT(IntRect(49, 49, 50, 50), occlusion.unoccludedContentRect(parent.get(), IntRect(49, 49, 50, 50)));
+    EXPECT_EQ_RECT(IntRect(50, 49, 50, 1), occlusion.unoccludedContentRect(parent.get(), IntRect(50, 49, 50, 50)));
+    EXPECT_EQ_RECT(IntRect(51, 49, 50, 50), occlusion.unoccludedContentRect(parent.get(), IntRect(51, 49, 50, 50)));
+    EXPECT_EQ_RECT(IntRect(100, 50, 1, 50), occlusion.unoccludedContentRect(parent.get(), IntRect(51, 50, 50, 50)));
+    EXPECT_EQ_RECT(IntRect(51, 51, 50, 50), occlusion.unoccludedContentRect(parent.get(), IntRect(51, 51, 50, 50)));
+    EXPECT_EQ_RECT(IntRect(50, 100, 50, 1), occlusion.unoccludedContentRect(parent.get(), IntRect(50, 51, 50, 50)));
+    EXPECT_EQ_RECT(IntRect(49, 51, 50, 50), occlusion.unoccludedContentRect(parent.get(), IntRect(49, 51, 50, 50)));
+}
+
+TEST(CCOcclusionTrackerTest, layerAddedToOccludedRegionWithRotatedSurface)
+{
+    // This tests that the right transforms are being used.
+    TestCCOcclusionTracker occlusion;
+    const TransformationMatrix identityMatrix;
+    RefPtr<LayerChromium> parent = LayerChromium::create();
+    RefPtr<LayerChromium> child = LayerChromium::create();
+    RefPtr<LayerChromiumWithForcedDrawsContent> layer = adoptRef(new LayerChromiumWithForcedDrawsContent());
+    parent->createRenderSurface();
+    parent->addChild(child);
+    child->addChild(layer);
+
+    TransformationMatrix childTransform;
+    childTransform.translate(250, 250);
+    childTransform.rotate(90);
+    childTransform.translate(-250, -250);
+
+    setLayerPropertiesForTesting(parent.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(100, 100), false);
+    setLayerPropertiesForTesting(child.get(), childTransform, identityMatrix, FloatPoint(0, 0), FloatPoint(30, 30), IntSize(500, 500), false);
+    setLayerPropertiesForTesting(layer.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(10, 10), IntSize(500, 500), true);
+
+    child->setMasksToBounds(true);
+
+    Vector<RefPtr<LayerChromium> > renderSurfaceLayerList;
+    Vector<RefPtr<LayerChromium> > dummyLayerList;
+    int dummyMaxTextureSize = 512;
+
+    parent->renderSurface()->setContentRect(IntRect(IntPoint::zero(), parent->bounds()));
+    parent->setClipRect(IntRect(IntPoint::zero(), parent->bounds()));
+    renderSurfaceLayerList.append(parent);
+
+    CCLayerTreeHostCommon::calculateDrawTransformsAndVisibility(parent.get(), parent.get(), identityMatrix, identityMatrix, renderSurfaceLayerList, dummyLayerList, dummyMaxTextureSize);
+
+    occlusion.enterTargetRenderSurface(child->renderSurface());
+    occlusion.markOccludedBehindLayer(layer.get());
+
+    EXPECT_EQ_RECT(IntRect(30, 40, 70, 60), occlusion.occlusionInScreenSpace().bounds());
+    EXPECT_EQ(1u, occlusion.occlusionInScreenSpace().rects().size());
+    EXPECT_EQ_RECT(IntRect(10, 430, 60, 70), occlusion.occlusionInTargetSurface().bounds());
+    EXPECT_EQ(1u, occlusion.occlusionInTargetSurface().rects().size());
+
+    EXPECT_EQ(true, occlusion.occluded(child.get(), IntRect(10, 430, 60, 70)));
+    EXPECT_EQ(false, occlusion.occluded(child.get(), IntRect(9, 430, 60, 70)));
+    EXPECT_EQ(false, occlusion.occluded(child.get(), IntRect(10, 429, 60, 70)));
+    EXPECT_EQ(false, occlusion.occluded(child.get(), IntRect(10, 430, 61, 70)));
+    EXPECT_EQ(false, occlusion.occluded(child.get(), IntRect(10, 430, 60, 71)));
+
+    occlusion.markOccludedBehindLayer(child.get());
+    occlusion.finishedTargetRenderSurface(child.get(), child->renderSurface());
+    occlusion.leaveToTargetRenderSurface(parent->renderSurface());
+
+    EXPECT_EQ_RECT(IntRect(30, 40, 70, 60), occlusion.occlusionInScreenSpace().bounds());
+    EXPECT_EQ(1u, occlusion.occlusionInScreenSpace().rects().size());
+    EXPECT_EQ_RECT(IntRect(30, 40, 70, 60), occlusion.occlusionInTargetSurface().bounds());
+    EXPECT_EQ(1u, occlusion.occlusionInTargetSurface().rects().size());
+
+    EXPECT_EQ(true, occlusion.occluded(parent.get(), IntRect(30, 40, 70, 60)));
+    EXPECT_EQ(false, occlusion.occluded(parent.get(), IntRect(29, 40, 70, 60)));
+    EXPECT_EQ(false, occlusion.occluded(parent.get(), IntRect(30, 39, 70, 60)));
+
+
+    /* Justification for the above occlusion from |layer|:
+               100
+      +---------------------+                                      +---------------------+
+      |                     |                                      |                     |30  Visible region of |layer|: /////
+      |    30               |           rotate(90)                 |                     |
+      | 30 + ---------------------------------+                    |     +---------------------------------+
+  100 |    |  10            |                 |            ==>     |     |               |10               |
+      |    |10+---------------------------------+                  |  +---------------------------------+  |
+      |    |  |             |                 | |                  |  |  |///////////////|     420      |  |
+      |    |  |             |                 | |                  |  |  |///////////////|60            |  |
+      |    |  |             |                 | |                  |  |  |///////////////|              |  |
+      +----|--|-------------+                 | |                  +--|--|---------------+              |  |
+           |  |                               | |                   20|10|     70                       |  |
+           |  |                               | |                     |  |                              |  |
+           |  |                               | |500                  |  |                              |  |
+           |  |                               | |                     |  |                              |  |
+           |  |                               | |                     |  |                              |  |
+           |  |                               | |                     |  |                              |  |
+           |  |                               | |                     |  |                              |10|
+           +--|-------------------------------+ |                     |  +------------------------------|--+
+              |                                 |                     |                 490             |
+              +---------------------------------+                     +---------------------------------+
+                             500                                                     500
+     */
+}
+
+TEST(CCOcclusionTrackerTest, layerAddedToOccludedRegionWithSurfaceAlreadyOnStack)
+{
+    // This tests that the right transforms are being used.
+    TestCCOcclusionTracker occlusion;
+    const TransformationMatrix identityMatrix;
+    RefPtr<LayerChromium> parent = LayerChromium::create();
+    RefPtr<LayerChromium> child = LayerChromium::create();
+    RefPtr<LayerChromiumWithForcedDrawsContent> child2 = adoptRef(new LayerChromiumWithForcedDrawsContent());
+    RefPtr<LayerChromiumWithForcedDrawsContent> layer = adoptRef(new LayerChromiumWithForcedDrawsContent());
+    parent->createRenderSurface();
+    parent->addChild(child);
+    child->addChild(layer);
+    parent->addChild(child2);
+
+    TransformationMatrix childTransform;
+    childTransform.translate(250, 250);
+    childTransform.rotate(90);
+    childTransform.translate(-250, -250);
+
+    setLayerPropertiesForTesting(parent.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(100, 100), false);
+    setLayerPropertiesForTesting(child.get(), childTransform, identityMatrix, FloatPoint(0, 0), FloatPoint(30, 30), IntSize(500, 500), false);
+    setLayerPropertiesForTesting(layer.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(10, 10), IntSize(500, 500), true);
+
+    // |child2| makes |parent|'s surface get considered by CCOcclusionTracker first, instead of |child|'s. This exercises different code in
+    // leaveToTargetRenderSurface, as the target surface has already been seen.
+    setLayerPropertiesForTesting(child2.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(30, 30), IntSize(60, 20), true);
+
+    child->setMasksToBounds(true);
+
+    Vector<RefPtr<LayerChromium> > renderSurfaceLayerList;
+    Vector<RefPtr<LayerChromium> > dummyLayerList;
+    int dummyMaxTextureSize = 512;
+
+    parent->renderSurface()->setContentRect(IntRect(IntPoint::zero(), parent->bounds()));
+    parent->setClipRect(IntRect(IntPoint::zero(), parent->bounds()));
+    renderSurfaceLayerList.append(parent);
+
+    CCLayerTreeHostCommon::calculateDrawTransformsAndVisibility(parent.get(), parent.get(), identityMatrix, identityMatrix, renderSurfaceLayerList, dummyLayerList, dummyMaxTextureSize);
+
+    occlusion.enterTargetRenderSurface(parent->renderSurface());
+    occlusion.markOccludedBehindLayer(child2.get());
+
+    EXPECT_EQ_RECT(IntRect(30, 30, 60, 20), occlusion.occlusionInScreenSpace().bounds());
+    EXPECT_EQ(1u, occlusion.occlusionInScreenSpace().rects().size());
+    EXPECT_EQ_RECT(IntRect(30, 30, 60, 20), occlusion.occlusionInTargetSurface().bounds());
+    EXPECT_EQ(1u, occlusion.occlusionInTargetSurface().rects().size());
+
+    occlusion.enterTargetRenderSurface(child->renderSurface());
+    occlusion.markOccludedBehindLayer(layer.get());
+
+    EXPECT_EQ_RECT(IntRect(30, 30, 70, 70), occlusion.occlusionInScreenSpace().bounds());
+    EXPECT_EQ(2u, occlusion.occlusionInScreenSpace().rects().size());
+    EXPECT_EQ_RECT(IntRect(10, 430, 60, 70), occlusion.occlusionInTargetSurface().bounds());
+    EXPECT_EQ(1u, occlusion.occlusionInTargetSurface().rects().size());
+
+    EXPECT_EQ(true, occlusion.occluded(child.get(), IntRect(10, 430, 60, 70)));
+    EXPECT_EQ(false, occlusion.occluded(child.get(), IntRect(9, 430, 60, 70)));
+    EXPECT_EQ(false, occlusion.occluded(child.get(), IntRect(10, 429, 60, 70)));
+    EXPECT_EQ(false, occlusion.occluded(child.get(), IntRect(11, 430, 60, 70)));
+    EXPECT_EQ(false, occlusion.occluded(child.get(), IntRect(10, 431, 60, 70)));
+
+    EXPECT_TRUE(occlusion.unoccludedContentRect(child.get(), IntRect(10, 430, 60, 70)).isEmpty());
+    // This is the little piece not occluded by child2
+    EXPECT_EQ_RECT(IntRect(9, 430, 1, 10), occlusion.unoccludedContentRect(child.get(), IntRect(9, 430, 60, 70)));
+    // This extends past both sides of child2, so it will be the original rect.
+    EXPECT_EQ_RECT(IntRect(9, 430, 60, 80), occlusion.unoccludedContentRect(child.get(), IntRect(9, 430, 60, 80)));
+    // This extends past two adjacent sides of child2, and should included the unoccluded parts of each side.
+    // This also demonstrates that the rect can be arbitrary and does not get clipped to the layer's visibleLayerRect().
+    EXPECT_EQ_RECT(IntRect(-10, 430, 20, 70), occlusion.unoccludedContentRect(child.get(), IntRect(-10, 430, 60, 70)));
+    // This extends past three adjacent sides of child2, so it should contain the unoccluded parts of each side. The left
+    // and bottom edges are completely unoccluded for some row/column so we get back the original query rect.
+    EXPECT_EQ_RECT(IntRect(-10, 430, 60, 80), occlusion.unoccludedContentRect(child.get(), IntRect(-10, 430, 60, 80)));
+    EXPECT_EQ_RECT(IntRect(10, 429, 60, 1), occlusion.unoccludedContentRect(child.get(), IntRect(10, 429, 60, 70)));
+    EXPECT_EQ_RECT(IntRect(70, 430, 1, 70), occlusion.unoccludedContentRect(child.get(), IntRect(11, 430, 60, 70)));
+    EXPECT_EQ_RECT(IntRect(10, 500, 60, 1), occlusion.unoccludedContentRect(child.get(), IntRect(10, 431, 60, 70)));
+
+    // Surface is not occluded by things that draw into itself.
+    EXPECT_EQ_RECT(IntRect(10, 430, 60, 70), occlusion.surfaceUnoccludedContentRect(child.get(), IntRect(10, 430, 60, 70)));
+
+    occlusion.markOccludedBehindLayer(child.get());
+    // |child2| should get merged with the surface we are leaving now
+    occlusion.finishedTargetRenderSurface(child.get(), child->renderSurface());
+    occlusion.leaveToTargetRenderSurface(parent->renderSurface());
+
+    EXPECT_EQ_RECT(IntRect(30, 30, 70, 70), occlusion.occlusionInScreenSpace().bounds());
+    EXPECT_EQ(2u, occlusion.occlusionInScreenSpace().rects().size());
+    EXPECT_EQ_RECT(IntRect(30, 30, 70, 70), occlusion.occlusionInTargetSurface().bounds());
+    EXPECT_EQ(2u, occlusion.occlusionInTargetSurface().rects().size());
+
+    Vector<IntRect> screen = occlusion.occlusionInScreenSpace().rects();
+    Vector<IntRect> target = occlusion.occlusionInTargetSurface().rects();
+
+    EXPECT_EQ(false, occlusion.occluded(parent.get(), IntRect(30, 30, 70, 70)));
+    EXPECT_EQ_RECT(IntRect(90, 30, 10, 10), occlusion.unoccludedContentRect(parent.get(), IntRect(30, 30, 70, 70)));
+
+    EXPECT_EQ(true, occlusion.occluded(parent.get(), IntRect(30, 30, 60, 10)));
+    EXPECT_EQ(false, occlusion.occluded(parent.get(), IntRect(29, 30, 60, 10)));
+    EXPECT_EQ(false, occlusion.occluded(parent.get(), IntRect(30, 29, 60, 10)));
+    EXPECT_EQ(false, occlusion.occluded(parent.get(), IntRect(31, 30, 60, 10)));
+    EXPECT_EQ(true, occlusion.occluded(parent.get(), IntRect(30, 31, 60, 10)));
+
+    EXPECT_EQ(true, occlusion.occluded(parent.get(), IntRect(30, 40, 70, 60)));
+    EXPECT_EQ(false, occlusion.occluded(parent.get(), IntRect(29, 40, 70, 60)));
+    EXPECT_EQ(false, occlusion.occluded(parent.get(), IntRect(30, 39, 70, 60)));
+
+    EXPECT_TRUE(occlusion.unoccludedContentRect(parent.get(), IntRect(30, 30, 60, 10)).isEmpty());
+    EXPECT_EQ_RECT(IntRect(29, 30, 1, 10), occlusion.unoccludedContentRect(parent.get(), IntRect(29, 30, 60, 10)));
+    EXPECT_EQ_RECT(IntRect(30, 29, 60, 1), occlusion.unoccludedContentRect(parent.get(), IntRect(30, 29, 60, 10)));
+    EXPECT_EQ_RECT(IntRect(90, 30, 1, 10), occlusion.unoccludedContentRect(parent.get(), IntRect(31, 30, 60, 10)));
+    EXPECT_TRUE(occlusion.unoccludedContentRect(parent.get(), IntRect(30, 31, 60, 10)).isEmpty());
+
+    EXPECT_TRUE(occlusion.unoccludedContentRect(parent.get(), IntRect(30, 40, 70, 60)).isEmpty());
+    EXPECT_EQ_RECT(IntRect(29, 40, 1, 60), occlusion.unoccludedContentRect(parent.get(), IntRect(29, 40, 70, 60)));
+    // This rect is mostly occluded by |child2|.
+    EXPECT_EQ_RECT(IntRect(90, 39, 10, 1), occlusion.unoccludedContentRect(parent.get(), IntRect(30, 39, 70, 60)));
+    // This rect extends past top/right ends of |child2|.
+    EXPECT_EQ_RECT(IntRect(30, 29, 70, 11), occlusion.unoccludedContentRect(parent.get(), IntRect(30, 29, 70, 70)));
+    // This rect extends past left/right ends of |child2|.
+    EXPECT_EQ_RECT(IntRect(20, 39, 80, 60), occlusion.unoccludedContentRect(parent.get(), IntRect(20, 39, 80, 60)));
+    EXPECT_EQ_RECT(IntRect(100, 40, 1, 60), occlusion.unoccludedContentRect(parent.get(), IntRect(31, 40, 70, 60)));
+    EXPECT_EQ_RECT(IntRect(30, 100, 70, 1), occlusion.unoccludedContentRect(parent.get(), IntRect(30, 41, 70, 60)));
+
+    // Surface is not occluded by things that draw into itself.
+    EXPECT_EQ_RECT(IntRect(30, 40, 70, 60), occlusion.surfaceUnoccludedContentRect(parent.get(), IntRect(30, 40, 70, 60)));
+
+
+    /* Justification for the above occlusion from |layer|:
+               100
+      +---------------------+                                      +---------------------+
+      |                     |                                      |                     |30  Visible region of |layer|: /////
+      |    30               |           rotate(90)                 |     30   60         |    |child2|: \\\\\
+      | 30 + ------------+--------------------+                    |  30 +------------+--------------------+
+  100 |    |  10         |  |                 |            ==>     |     |\\\\\\\\\\\\|  |10               |
+      |    |10+----------|----------------------+                  |  +--|\\\\\\\\\\\\|-----------------+  |
+      |    + ------------+  |                 | |                  |  |  +------------+//|     420      |  |
+      |    |  |             |                 | |                  |  |  |///////////////|60            |  |
+      |    |  |             |                 | |                  |  |  |///////////////|              |  |
+      +----|--|-------------+                 | |                  +--|--|---------------+              |  |
+           |  |                               | |                   20|10|     70                       |  |
+           |  |                               | |                     |  |                              |  |
+           |  |                               | |500                  |  |                              |  |
+           |  |                               | |                     |  |                              |  |
+           |  |                               | |                     |  |                              |  |
+           |  |                               | |                     |  |                              |  |
+           |  |                               | |                     |  |                              |10|
+           +--|-------------------------------+ |                     |  +------------------------------|--+
+              |                                 |                     |                 490             |
+              +---------------------------------+                     +---------------------------------+
+                             500                                                     500
+     */
+}
+
+TEST(CCOcclusionTrackerTest, layerAddedToOccludedRegionWithRotatedOffAxisSurface)
+{
+    TestCCOcclusionTracker occlusion;
+    const TransformationMatrix identityMatrix;
+    RefPtr<LayerChromium> parent = LayerChromium::create();
+    RefPtr<LayerChromium> child = LayerChromium::create();
+    RefPtr<LayerChromiumWithForcedDrawsContent> layer = adoptRef(new LayerChromiumWithForcedDrawsContent());
+    parent->createRenderSurface();
+    parent->addChild(child);
+    child->addChild(layer);
+
+    // Now rotate the child a little more so it is not axis-aligned. The parent will no longer be occluded by |layer|, but
+    // the child's occlusion should be unchanged.
+
+    TransformationMatrix childTransform;
+    childTransform.translate(250, 250);
+    childTransform.rotate(95);
+    childTransform.translate(-250, -250);
+
+    TransformationMatrix layerTransform;
+    layerTransform.translate(10, 10);
+
+    setLayerPropertiesForTesting(parent.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(100, 100), false);
+    setLayerPropertiesForTesting(child.get(), childTransform, identityMatrix, FloatPoint(0, 0), FloatPoint(30, 30), IntSize(500, 500), false);
+    setLayerPropertiesForTesting(layer.get(), layerTransform, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(500, 500), true);
+
+    child->setMasksToBounds(true);
+
+    Vector<RefPtr<LayerChromium> > renderSurfaceLayerList;
+    Vector<RefPtr<LayerChromium> > dummyLayerList;
+    int dummyMaxTextureSize = 512;
+
+    parent->renderSurface()->setContentRect(IntRect(IntPoint::zero(), parent->bounds()));
+    parent->setClipRect(IntRect(IntPoint::zero(), parent->bounds()));
+    renderSurfaceLayerList.append(parent);
+
+    CCLayerTreeHostCommon::calculateDrawTransformsAndVisibility(parent.get(), parent.get(), identityMatrix, identityMatrix, renderSurfaceLayerList, dummyLayerList, dummyMaxTextureSize);
+
+    IntRect clippedLayerInChild = layerTransform.mapRect(layer->visibleLayerRect());
+
+    occlusion.enterTargetRenderSurface(child->renderSurface());
+    occlusion.markOccludedBehindLayer(layer.get());
+
+    EXPECT_EQ_RECT(IntRect(), occlusion.occlusionInScreenSpace().bounds());
+    EXPECT_EQ(0u, occlusion.occlusionInScreenSpace().rects().size());
+    EXPECT_EQ_RECT(clippedLayerInChild, occlusion.occlusionInTargetSurface().bounds());
+    EXPECT_EQ(1u, occlusion.occlusionInTargetSurface().rects().size());
+
+    EXPECT_EQ(true, occlusion.occluded(child.get(), clippedLayerInChild));
+    EXPECT_EQ(true, occlusion.unoccludedContentRect(parent.get(), clippedLayerInChild).isEmpty());
+    clippedLayerInChild.move(-1, 0);
+    EXPECT_EQ(false, occlusion.occluded(child.get(), clippedLayerInChild));
+    EXPECT_EQ(false, occlusion.unoccludedContentRect(parent.get(), clippedLayerInChild).isEmpty());
+    clippedLayerInChild.move(1, 0);
+    clippedLayerInChild.move(1, 0);
+    EXPECT_EQ(false, occlusion.occluded(child.get(), clippedLayerInChild));
+    EXPECT_EQ(false, occlusion.unoccludedContentRect(parent.get(), clippedLayerInChild).isEmpty());
+    clippedLayerInChild.move(-1, 0);
+    clippedLayerInChild.move(0, -1);
+    EXPECT_EQ(false, occlusion.occluded(child.get(), clippedLayerInChild));
+    EXPECT_EQ(false, occlusion.unoccludedContentRect(parent.get(), clippedLayerInChild).isEmpty());
+    clippedLayerInChild.move(0, 1);
+    clippedLayerInChild.move(0, 1);
+    EXPECT_EQ(false, occlusion.occluded(child.get(), clippedLayerInChild));
+    EXPECT_EQ(false, occlusion.unoccludedContentRect(parent.get(), clippedLayerInChild).isEmpty());
+    clippedLayerInChild.move(0, -1);
+
+    // Surface is not occluded by things that draw into itself.
+    EXPECT_EQ_RECT(IntRect(0, 0, 500, 500), occlusion.surfaceUnoccludedContentRect(child.get(), IntRect(0, 0, 500, 500)));
+
+    occlusion.markOccludedBehindLayer(child.get());
+    occlusion.finishedTargetRenderSurface(child.get(), child->renderSurface());
+    occlusion.leaveToTargetRenderSurface(parent->renderSurface());
+
+    EXPECT_EQ_RECT(IntRect(), occlusion.occlusionInScreenSpace().bounds());
+    EXPECT_EQ(0u, occlusion.occlusionInScreenSpace().rects().size());
+    EXPECT_EQ_RECT(IntRect(), occlusion.occlusionInTargetSurface().bounds());
+    EXPECT_EQ(0u, occlusion.occlusionInTargetSurface().rects().size());
+
+    EXPECT_EQ(false, occlusion.occluded(parent.get(), IntRect(75, 55, 1, 1)));
+    EXPECT_EQ_RECT(IntRect(75, 55, 1, 1), occlusion.unoccludedContentRect(parent.get(), IntRect(75, 55, 1, 1)));
+
+    // Surface is not occluded by things that draw into itself.
+    EXPECT_EQ_RECT(IntRect(0, 0, 100, 100), occlusion.surfaceUnoccludedContentRect(parent.get(), IntRect(0, 0, 100, 100)));
+}
+
+TEST(CCOcclusionTrackerTest, layerAddedToOccludedRegionWithMultipleOpaqueLayers)
+{
+    // This is similar to the previous test but now we make a few opaque layers inside of |child| so that the occluded parts of child are not a simple rect.
+    TestCCOcclusionTracker occlusion;
+    const TransformationMatrix identityMatrix;
+    RefPtr<LayerChromium> parent = LayerChromium::create();
+    RefPtr<LayerChromium> child = LayerChromium::create();
+    RefPtr<LayerChromiumWithForcedDrawsContent> layer1 = adoptRef(new LayerChromiumWithForcedDrawsContent());
+    RefPtr<LayerChromiumWithForcedDrawsContent> layer2 = adoptRef(new LayerChromiumWithForcedDrawsContent());
+    parent->createRenderSurface();
+    parent->addChild(child);
+    child->addChild(layer1);
+    child->addChild(layer2);
+
+    TransformationMatrix childTransform;
+    childTransform.translate(250, 250);
+    childTransform.rotate(90);
+    childTransform.translate(-250, -250);
+
+    setLayerPropertiesForTesting(parent.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(100, 100), false);
+    setLayerPropertiesForTesting(child.get(), childTransform, identityMatrix, FloatPoint(0, 0), FloatPoint(30, 30), IntSize(500, 500), false);
+    setLayerPropertiesForTesting(layer1.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(10, 10), IntSize(500, 440), true);
+    setLayerPropertiesForTesting(layer2.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(10, 450), IntSize(500, 60), true);
+
+    child->setMasksToBounds(true);
+
+    Vector<RefPtr<LayerChromium> > renderSurfaceLayerList;
+    Vector<RefPtr<LayerChromium> > dummyLayerList;
+    int dummyMaxTextureSize = 512;
+
+    parent->renderSurface()->setContentRect(IntRect(IntPoint::zero(), parent->bounds()));
+    parent->setClipRect(IntRect(IntPoint::zero(), parent->bounds()));
+    renderSurfaceLayerList.append(parent);
+
+    CCLayerTreeHostCommon::calculateDrawTransformsAndVisibility(parent.get(), parent.get(), identityMatrix, identityMatrix, renderSurfaceLayerList, dummyLayerList, dummyMaxTextureSize);
+
+    occlusion.enterTargetRenderSurface(child->renderSurface());
+    occlusion.markOccludedBehindLayer(layer2.get());
+    occlusion.markOccludedBehindLayer(layer1.get());
+
+    EXPECT_EQ_RECT(IntRect(30, 40, 70, 60), occlusion.occlusionInScreenSpace().bounds());
+    EXPECT_EQ(1u, occlusion.occlusionInScreenSpace().rects().size());
+    EXPECT_EQ_RECT(IntRect(10, 430, 60, 70), occlusion.occlusionInTargetSurface().bounds());
+    EXPECT_EQ(1u, occlusion.occlusionInTargetSurface().rects().size());
+
+    EXPECT_EQ(true, occlusion.occluded(child.get(), IntRect(10, 430, 60, 70)));
+    EXPECT_EQ(false, occlusion.occluded(child.get(), IntRect(9, 430, 60, 70)));
+    EXPECT_EQ(false, occlusion.occluded(child.get(), IntRect(10, 429, 60, 70)));
+    EXPECT_EQ(false, occlusion.occluded(child.get(), IntRect(11, 430, 60, 70)));
+    EXPECT_EQ(false, occlusion.occluded(child.get(), IntRect(10, 431, 60, 70)));
+
+    EXPECT_TRUE(occlusion.unoccludedContentRect(child.get(), IntRect(10, 430, 60, 70)).isEmpty());
+    EXPECT_EQ_RECT(IntRect(9, 430, 1, 70), occlusion.unoccludedContentRect(child.get(), IntRect(9, 430, 60, 70)));
+    EXPECT_EQ_RECT(IntRect(10, 429, 60, 1), occlusion.unoccludedContentRect(child.get(), IntRect(10, 429, 60, 70)));
+    EXPECT_EQ_RECT(IntRect(70, 430, 1, 70), occlusion.unoccludedContentRect(child.get(), IntRect(11, 430, 60, 70)));
+    EXPECT_EQ_RECT(IntRect(10, 500, 60, 1), occlusion.unoccludedContentRect(child.get(), IntRect(10, 431, 60, 70)));
+
+    // Surface is not occluded by things that draw into itself.
+    EXPECT_EQ_RECT(IntRect(10, 430, 60, 70), occlusion.surfaceUnoccludedContentRect(child.get(), IntRect(10, 430, 60, 70)));
+
+    occlusion.markOccludedBehindLayer(child.get());
+    occlusion.finishedTargetRenderSurface(child.get(), child->renderSurface());
+    occlusion.leaveToTargetRenderSurface(parent->renderSurface());
+
+    EXPECT_EQ_RECT(IntRect(30, 40, 70, 60), occlusion.occlusionInScreenSpace().bounds());
+    EXPECT_EQ(1u, occlusion.occlusionInScreenSpace().rects().size());
+    EXPECT_EQ_RECT(IntRect(30, 40, 70, 60), occlusion.occlusionInTargetSurface().bounds());
+    EXPECT_EQ(1u, occlusion.occlusionInTargetSurface().rects().size());
+
+    EXPECT_EQ(true, occlusion.occluded(parent.get(), IntRect(30, 40, 70, 60)));
+    EXPECT_EQ(false, occlusion.occluded(parent.get(), IntRect(29, 40, 70, 60)));
+    EXPECT_EQ(false, occlusion.occluded(parent.get(), IntRect(30, 39, 70, 60)));
+
+    EXPECT_TRUE(occlusion.unoccludedContentRect(parent.get(), IntRect(30, 40, 70, 60)).isEmpty());
+    EXPECT_EQ_RECT(IntRect(29, 40, 1, 60), occlusion.unoccludedContentRect(parent.get(), IntRect(29, 40, 70, 60)));
+    EXPECT_EQ_RECT(IntRect(30, 39, 70, 1), occlusion.unoccludedContentRect(parent.get(), IntRect(30, 39, 70, 60)));
+    EXPECT_EQ_RECT(IntRect(100, 40, 1, 60), occlusion.unoccludedContentRect(parent.get(), IntRect(31, 40, 70, 60)));
+    EXPECT_EQ_RECT(IntRect(30, 100, 70, 1), occlusion.unoccludedContentRect(parent.get(), IntRect(30, 41, 70, 60)));
+
+    // Surface is not occluded by things that draw into itself.
+    EXPECT_EQ_RECT(IntRect(30, 40, 70, 60), occlusion.surfaceUnoccludedContentRect(parent.get(), IntRect(30, 40, 70, 60)));
+
+
+    /* Justification for the above occlusion from |layer1| and |layer2|:
+
+       +---------------------+
+       |                     |30  Visible region of |layer1|: /////
+       |                     |    Visible region of |layer2|: \\\\\
+       |     +---------------------------------+
+       |     |               |10               |
+       |  +---------------+-----------------+  |
+       |  |  |\\\\\\\\\\\\|//|     420      |  |
+       |  |  |\\\\\\\\\\\\|//|60            |  |
+       |  |  |\\\\\\\\\\\\|//|              |  |
+       +--|--|------------|--+              |  |
+        20|10|     70     |                 |  |
+          |  |            |                 |  |
+          |  |            |                 |  |
+          |  |            |                 |  |
+          |  |            |                 |  |
+          |  |            |                 |  |
+          |  |            |                 |10|
+          |  +------------|-----------------|--+
+          |               | 490             |
+          +---------------+-----------------+
+                60               440
+     */
+}
+
+TEST(CCOcclusionTrackerTest, surfaceOcclusionWithOverlappingSiblingSurfaces)
+{
+    // This tests that the right transforms are being used.
+    TestCCOcclusionTracker occlusion;
+    const TransformationMatrix identityMatrix;
+    RefPtr<LayerChromium> parent = LayerChromium::create();
+    RefPtr<LayerChromium> child1 = LayerChromium::create();
+    RefPtr<LayerChromium> child2 = LayerChromium::create();
+    RefPtr<LayerChromiumWithForcedDrawsContent> layer1 = adoptRef(new LayerChromiumWithForcedDrawsContent());
+    RefPtr<LayerChromiumWithForcedDrawsContent> layer2 = adoptRef(new LayerChromiumWithForcedDrawsContent());
+    parent->createRenderSurface();
+    parent->addChild(child1);
+    parent->addChild(child2);
+    child1->addChild(layer1);
+    child2->addChild(layer2);
+
+    TransformationMatrix childTransform;
+    childTransform.translate(250, 250);
+    childTransform.rotate(90);
+    childTransform.translate(-250, -250);
+
+    setLayerPropertiesForTesting(parent.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(100, 100), false);
+    setLayerPropertiesForTesting(child1.get(), childTransform, identityMatrix, FloatPoint(0, 0), FloatPoint(30, 30), IntSize(500, 500), false);
+    setLayerPropertiesForTesting(layer1.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(500, 500), true);
+    setLayerPropertiesForTesting(child2.get(), childTransform, identityMatrix, FloatPoint(0, 0), FloatPoint(20, 40), IntSize(500, 500), false);
+    setLayerPropertiesForTesting(layer2.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(500, 500), true);
+
+    child1->setMasksToBounds(true);
+    child2->setMasksToBounds(true);
+
+    Vector<RefPtr<LayerChromium> > renderSurfaceLayerList;
+    Vector<RefPtr<LayerChromium> > dummyLayerList;
+    int dummyMaxTextureSize = 512;
+
+    parent->renderSurface()->setContentRect(IntRect(IntPoint::zero(), parent->bounds()));
+    parent->setClipRect(IntRect(IntPoint::zero(), parent->bounds()));
+    renderSurfaceLayerList.append(parent);
+
+    CCLayerTreeHostCommon::calculateDrawTransformsAndVisibility(parent.get(), parent.get(), identityMatrix, identityMatrix, renderSurfaceLayerList, dummyLayerList, dummyMaxTextureSize);
+
+    occlusion.enterTargetRenderSurface(child2->renderSurface());
+    occlusion.markOccludedBehindLayer(layer2.get());
+
+    EXPECT_EQ_RECT(IntRect(20, 40, 80, 60), occlusion.occlusionInScreenSpace().bounds());
+    EXPECT_EQ(1u, occlusion.occlusionInScreenSpace().rects().size());
+    EXPECT_EQ_RECT(IntRect(0, 420, 60, 80), occlusion.occlusionInTargetSurface().bounds());
+    EXPECT_EQ(1u, occlusion.occlusionInTargetSurface().rects().size());
+
+    EXPECT_EQ(true, occlusion.occluded(child2.get(), IntRect(0, 420, 60, 80)));
+    EXPECT_EQ(false, occlusion.occluded(child2.get(), IntRect(-1, 420, 60, 80)));
+    EXPECT_EQ(false, occlusion.occluded(child2.get(), IntRect(0, 419, 60, 80)));
+    EXPECT_EQ(false, occlusion.occluded(child2.get(), IntRect(0, 420, 61, 80)));
+    EXPECT_EQ(false, occlusion.occluded(child2.get(), IntRect(0, 420, 60, 81)));
+
+    // Surface is not occluded by things that draw into itself.
+    EXPECT_EQ_RECT(IntRect(0, 420, 60, 80), occlusion.surfaceUnoccludedContentRect(child2.get(), IntRect(0, 420, 60, 80)));
+
+    occlusion.markOccludedBehindLayer(child2.get());
+    occlusion.finishedTargetRenderSurface(child2.get(), child2->renderSurface());
+    occlusion.leaveToTargetRenderSurface(parent->renderSurface());
+    occlusion.enterTargetRenderSurface(child1->renderSurface());
+    occlusion.markOccludedBehindLayer(layer1.get());
+
+    EXPECT_EQ_RECT(IntRect(20, 30, 80, 70), occlusion.occlusionInScreenSpace().bounds());
+    EXPECT_EQ(2u, occlusion.occlusionInScreenSpace().rects().size());
+    EXPECT_EQ_RECT(IntRect(0, 430, 70, 70), occlusion.occlusionInTargetSurface().bounds());
+    EXPECT_EQ(1u, occlusion.occlusionInTargetSurface().rects().size());
+
+    EXPECT_EQ(true, occlusion.occluded(child2.get(), IntRect(0, 430, 70, 70)));
+    EXPECT_EQ(false, occlusion.occluded(child2.get(), IntRect(-1, 430, 70, 70)));
+    EXPECT_EQ(false, occlusion.occluded(child2.get(), IntRect(0, 429, 70, 70)));
+    EXPECT_EQ(false, occlusion.occluded(child2.get(), IntRect(0, 430, 71, 70)));
+    EXPECT_EQ(false, occlusion.occluded(child2.get(), IntRect(0, 430, 70, 71)));
+
+    // Surface is not occluded by things that draw into itself, but the |child1| surface should be occluded by the |child2| surface.
+    EXPECT_EQ_RECT(IntRect(0, 430, 10, 70), occlusion.surfaceUnoccludedContentRect(child1.get(), IntRect(0, 430, 70, 70)));
+
+    occlusion.markOccludedBehindLayer(child1.get());
+    occlusion.finishedTargetRenderSurface(child1.get(), child1->renderSurface());
+    occlusion.leaveToTargetRenderSurface(parent->renderSurface());
+
+    EXPECT_EQ_RECT(IntRect(20, 30, 80, 70), occlusion.occlusionInScreenSpace().bounds());
+    EXPECT_EQ(2u, occlusion.occlusionInScreenSpace().rects().size());
+    EXPECT_EQ_RECT(IntRect(20, 30, 80, 70), occlusion.occlusionInTargetSurface().bounds());
+    EXPECT_EQ(2u, occlusion.occlusionInTargetSurface().rects().size());
+
+    EXPECT_EQ(false, occlusion.occluded(parent.get(), IntRect(20, 30, 80, 70)));
+
+    EXPECT_EQ(true, occlusion.occluded(parent.get(), IntRect(30, 30, 70, 70)));
+    EXPECT_EQ(false, occlusion.occluded(parent.get(), IntRect(29, 30, 70, 70)));
+    EXPECT_EQ(false, occlusion.occluded(parent.get(), IntRect(30, 29, 70, 70)));
+
+    EXPECT_EQ(true, occlusion.occluded(parent.get(), IntRect(20, 40, 80, 60)));
+    EXPECT_EQ(false, occlusion.occluded(parent.get(), IntRect(19, 40, 80, 60)));
+    EXPECT_EQ(false, occlusion.occluded(parent.get(), IntRect(20, 39, 80, 60)));
+
+    // |child1| and |child2| both draw into parent so they should not occlude it.
+    EXPECT_EQ_RECT(IntRect(20, 30, 80, 70), occlusion.surfaceUnoccludedContentRect(parent.get(), IntRect(20, 30, 80, 70)));
+
+
+    /* Justification for the above occlusion:
+               100
+      +---------------------+
+      |                     |
+      |    30               |       child1
+      |  30+ ---------------------------------+
+  100 |  40|                |     child2      |
+      |20+----------------------------------+ |
+      |  | |                |               | |
+      |  | |                |               | |
+      |  | |                |               | |
+      +--|-|----------------+               | |
+         | |                                | | 500
+         | |                                | |
+         | |                                | |
+         | |                                | |
+         | |                                | |
+         | |                                | |
+         | |                                | |
+         | +--------------------------------|-+
+         |                                  |
+         +----------------------------------+
+                         500
+     */
+}
+
+TEST(CCOcclusionTrackerTest, surfaceOcclusionInScreenSpace)
+{
+    // This tests that the right transforms are being used.
+    TestCCOcclusionTracker occlusion;
+    const TransformationMatrix identityMatrix;
+    RefPtr<LayerChromium> parent = LayerChromium::create();
+    RefPtr<LayerChromium> child1 = LayerChromium::create();
+    RefPtr<LayerChromium> child2 = LayerChromium::create();
+    RefPtr<LayerChromiumWithForcedDrawsContent> layer1 = adoptRef(new LayerChromiumWithForcedDrawsContent());
+    RefPtr<LayerChromiumWithForcedDrawsContent> layer2 = adoptRef(new LayerChromiumWithForcedDrawsContent());
+    parent->createRenderSurface();
+    parent->addChild(child1);
+    parent->addChild(child2);
+    child1->addChild(layer1);
+    child2->addChild(layer2);
+
+    TransformationMatrix childTransform;
+    childTransform.translate(250, 250);
+    childTransform.rotate(90);
+    childTransform.translate(-250, -250);
+
+    // The owning layers have very different bounds from the surfaces that they own.
+    setLayerPropertiesForTesting(parent.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(100, 100), false);
+    setLayerPropertiesForTesting(child1.get(), childTransform, identityMatrix, FloatPoint(0, 0), FloatPoint(30, 30), IntSize(10, 10), false);
+    setLayerPropertiesForTesting(layer1.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(-10, -10), IntSize(510, 510), true);
+    setLayerPropertiesForTesting(child2.get(), childTransform, identityMatrix, FloatPoint(0, 0), FloatPoint(20, 40), IntSize(10, 10), false);
+    setLayerPropertiesForTesting(layer2.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(-10, -10), IntSize(510, 510), true);
+
+    // Make them both render surfaces
+    FilterOperations filters;
+    filters.operations().append(BasicComponentTransferFilterOperation::create(0.5, FilterOperation::GRAYSCALE));
+    child1->setFilters(filters);
+    child2->setFilters(filters);
+
+    child1->setMasksToBounds(false);
+    child2->setMasksToBounds(false);
+
+    Vector<RefPtr<LayerChromium> > renderSurfaceLayerList;
+    Vector<RefPtr<LayerChromium> > dummyLayerList;
+    int dummyMaxTextureSize = 512;
+
+    parent->renderSurface()->setContentRect(IntRect(IntPoint::zero(), parent->bounds()));
+    parent->setClipRect(IntRect(IntPoint::zero(), parent->bounds()));
+    renderSurfaceLayerList.append(parent);
+
+    CCLayerTreeHostCommon::calculateDrawTransformsAndVisibility(parent.get(), parent.get(), identityMatrix, identityMatrix, renderSurfaceLayerList, dummyLayerList, dummyMaxTextureSize);
+
+    occlusion.enterTargetRenderSurface(child2->renderSurface());
+    occlusion.markOccludedBehindLayer(layer2.get());
+
+    EXPECT_EQ_RECT(IntRect(20, 30, 80, 70), occlusion.occlusionInScreenSpace().bounds());
+    EXPECT_EQ(1u, occlusion.occlusionInScreenSpace().rects().size());
+    EXPECT_EQ_RECT(IntRect(-10, 420, 70, 80), occlusion.occlusionInTargetSurface().bounds());
+    EXPECT_EQ(1u, occlusion.occlusionInTargetSurface().rects().size());
+
+    EXPECT_EQ(true, occlusion.occluded(child2.get(), IntRect(-10, 420, 70, 80)));
+    EXPECT_EQ(false, occlusion.occluded(child2.get(), IntRect(-11, 420, 70, 80)));
+    EXPECT_EQ(false, occlusion.occluded(child2.get(), IntRect(-10, 419, 70, 80)));
+    EXPECT_EQ(false, occlusion.occluded(child2.get(), IntRect(-10, 420, 71, 80)));
+    EXPECT_EQ(false, occlusion.occluded(child2.get(), IntRect(-10, 420, 70, 81)));
+
+    // Surface is not occluded by things that draw into itself.
+    EXPECT_EQ_RECT(IntRect(-10, 420, 70, 80), occlusion.surfaceUnoccludedContentRect(child2.get(), IntRect(-10, 420, 70, 80)));
+    EXPECT_EQ(false, occlusion.surfaceOccluded(child2.get(), IntRect(30, 250, 1, 1)));
+
+    occlusion.markOccludedBehindLayer(child2.get());
+    occlusion.finishedTargetRenderSurface(child2.get(), child2->renderSurface());
+    occlusion.leaveToTargetRenderSurface(parent->renderSurface());
+    occlusion.enterTargetRenderSurface(child1->renderSurface());
+    occlusion.markOccludedBehindLayer(layer1.get());
+
+    EXPECT_EQ_RECT(IntRect(20, 20, 80, 80), occlusion.occlusionInScreenSpace().bounds());
+    EXPECT_EQ(2u, occlusion.occlusionInScreenSpace().rects().size());
+    EXPECT_EQ_RECT(IntRect(-10, 430, 80, 70), occlusion.occlusionInTargetSurface().bounds());
+    EXPECT_EQ(1u, occlusion.occlusionInTargetSurface().rects().size());
+
+    EXPECT_EQ(true, occlusion.occluded(child2.get(), IntRect(-10, 430, 80, 70)));
+    EXPECT_EQ(false, occlusion.occluded(child2.get(), IntRect(-11, 430, 80, 70)));
+    EXPECT_EQ(false, occlusion.occluded(child2.get(), IntRect(-10, 429, 80, 70)));
+    EXPECT_EQ(false, occlusion.occluded(child2.get(), IntRect(-10, 430, 81, 70)));
+    EXPECT_EQ(false, occlusion.occluded(child2.get(), IntRect(-10, 430, 80, 71)));
+
+    // Surface is not occluded by things that draw into itself, but the |child1| surface should be occluded by the |child2| surface.
+    EXPECT_EQ_RECT(IntRect(-10, 430, 10, 80), occlusion.surfaceUnoccludedContentRect(child1.get(), IntRect(-10, 430, 70, 80)));
+    EXPECT_EQ(true, occlusion.surfaceOccluded(child1.get(), IntRect(0, 430, 70, 80)));
+    EXPECT_EQ(false, occlusion.surfaceOccluded(child1.get(), IntRect(-1, 430, 70, 80)));
+    EXPECT_EQ(false, occlusion.surfaceOccluded(child1.get(), IntRect(0, 429, 70, 80)));
+    EXPECT_EQ(false, occlusion.surfaceOccluded(child1.get(), IntRect(1, 430, 70, 80)));
+    EXPECT_EQ(false, occlusion.surfaceOccluded(child1.get(), IntRect(0, 431, 70, 80)));
+
+    occlusion.markOccludedBehindLayer(child1.get());
+    occlusion.finishedTargetRenderSurface(child1.get(), child1->renderSurface());
+    occlusion.leaveToTargetRenderSurface(parent->renderSurface());
+
+    EXPECT_EQ_RECT(IntRect(20, 20, 80, 80), occlusion.occlusionInScreenSpace().bounds());
+    EXPECT_EQ(2u, occlusion.occlusionInScreenSpace().rects().size());
+    EXPECT_EQ_RECT(IntRect(20, 20, 80, 80), occlusion.occlusionInTargetSurface().bounds());
+    EXPECT_EQ(2u, occlusion.occlusionInTargetSurface().rects().size());
+
+    EXPECT_EQ(false, occlusion.occluded(parent.get(), IntRect(20, 20, 80, 80)));
+
+    EXPECT_EQ(true, occlusion.occluded(parent.get(), IntRect(30, 20, 70, 80)));
+    EXPECT_EQ(false, occlusion.occluded(parent.get(), IntRect(29, 20, 70, 80)));
+    EXPECT_EQ(false, occlusion.occluded(parent.get(), IntRect(30, 19, 70, 80)));
+
+    EXPECT_EQ(true, occlusion.occluded(parent.get(), IntRect(20, 30, 80, 70)));
+    EXPECT_EQ(false, occlusion.occluded(parent.get(), IntRect(19, 30, 80, 70)));
+    EXPECT_EQ(false, occlusion.occluded(parent.get(), IntRect(20, 29, 80, 70)));
+
+    // |child1| and |child2| both draw into parent so they should not occlude it.
+    EXPECT_EQ_RECT(IntRect(20, 20, 80, 80), occlusion.surfaceUnoccludedContentRect(parent.get(), IntRect(20, 20, 80, 80)));
+    EXPECT_EQ(false, occlusion.surfaceOccluded(parent.get(), IntRect(50, 50, 1, 1)));
+
+
+    /* Justification for the above occlusion:
+               100
+      +---------------------+
+      |    20               |       layer1
+      |  30+ ---------------------------------+
+  100 |  30|                |     layer2      |
+      |20+----------------------------------+ |
+      |  | |                |               | |
+      |  | |                |               | |
+      |  | |                |               | |
+      +--|-|----------------+               | |
+         | |                                | | 500
+         | |                                | |
+         | |                                | |
+         | |                                | |
+         | |                                | |
+         | |                                | |
+         | |                                | |
+         | +--------------------------------|-+
+         |                                  |
+         +----------------------------------+
+                         500
+     */
+}
+
+TEST(CCOcclusionTrackerTest, occlusionInteractionWithFilters)
+{
+    // This tests that the right transforms are being used.
+    TestCCOcclusionTracker occlusion;
+    const TransformationMatrix identityMatrix;
+    RefPtr<LayerChromium> parent = LayerChromium::create();
+    RefPtr<LayerChromiumWithForcedDrawsContent> blurLayer = adoptRef(new LayerChromiumWithForcedDrawsContent());
+    RefPtr<LayerChromiumWithForcedDrawsContent> opacityLayer = adoptRef(new LayerChromiumWithForcedDrawsContent());
+    RefPtr<LayerChromiumWithForcedDrawsContent> opaqueLayer = adoptRef(new LayerChromiumWithForcedDrawsContent());
+    parent->createRenderSurface();
+    parent->addChild(blurLayer);
+    parent->addChild(opacityLayer);
+    parent->addChild(opaqueLayer);
+
+    TransformationMatrix layerTransform;
+    layerTransform.translate(250, 250);
+    layerTransform.rotate(90);
+    layerTransform.translate(-250, -250);
+
+    setLayerPropertiesForTesting(parent.get(), identityMatrix, identityMatrix, FloatPoint(0, 0), FloatPoint(0, 0), IntSize(100, 100), false);
+    setLayerPropertiesForTesting(blurLayer.get(), layerTransform, identityMatrix, FloatPoint(0, 0), FloatPoint(30, 30), IntSize(500, 500), true);
+    setLayerPropertiesForTesting(opaqueLayer.get(), layerTransform, identityMatrix, FloatPoint(0, 0), FloatPoint(30, 30), IntSize(500, 500), true);
+    setLayerPropertiesForTesting(opacityLayer.get(), layerTransform, identityMatrix, FloatPoint(0, 0), FloatPoint(30, 30), IntSize(500, 500), true);
+
+    {
+        FilterOperations filters;
+        filters.operations().append(BlurFilterOperation::create(Length(10, WebCore::Percent), FilterOperation::BLUR));
+        blurLayer->setFilters(filters);
+    }
+
+    {
+        FilterOperations filters;
+        filters.operations().append(BasicComponentTransferFilterOperation::create(0.5, FilterOperation::GRAYSCALE));
+        opaqueLayer->setFilters(filters);
+    }
+
+    {
+        FilterOperations filters;
+        filters.operations().append(BasicComponentTransferFilterOperation::create(0.5, FilterOperation::OPACITY));
+        opacityLayer->setFilters(filters);
+    }
+
+
+    Vector<RefPtr<LayerChromium> > renderSurfaceLayerList;
+    Vector<RefPtr<LayerChromium> > dummyLayerList;
+    int dummyMaxTextureSize = 512;
+
+    parent->renderSurface()->setContentRect(IntRect(IntPoint::zero(), parent->bounds()));
+    parent->setClipRect(IntRect(IntPoint::zero(), parent->bounds()));
+    renderSurfaceLayerList.append(parent);
+
+    CCLayerTreeHostCommon::calculateDrawTransformsAndVisibility(parent.get(), parent.get(), identityMatrix, identityMatrix, renderSurfaceLayerList, dummyLayerList, dummyMaxTextureSize);
+
+    // Opacity layer won't contribute to occlusion.
+    occlusion.enterTargetRenderSurface(opacityLayer->renderSurface());
+    occlusion.markOccludedBehindLayer(opacityLayer.get());
+    occlusion.finishedTargetRenderSurface(opacityLayer.get(), opacityLayer->renderSurface());
+
+    EXPECT_TRUE(occlusion.occlusionInScreenSpace().isEmpty());
+    EXPECT_TRUE(occlusion.occlusionInTargetSurface().isEmpty());
+
+    // And has nothing to contribute to its parent surface.
+    occlusion.leaveToTargetRenderSurface(parent->renderSurface());
+    EXPECT_TRUE(occlusion.occlusionInScreenSpace().isEmpty());
+    EXPECT_TRUE(occlusion.occlusionInTargetSurface().isEmpty());
+
+    // Opaque layer will contribute to occlusion.
+    occlusion.enterTargetRenderSurface(opaqueLayer->renderSurface());
+    occlusion.markOccludedBehindLayer(opaqueLayer.get());
+    occlusion.finishedTargetRenderSurface(opaqueLayer.get(), opaqueLayer->renderSurface());
+
+    EXPECT_EQ_RECT(IntRect(30, 30, 70, 70), occlusion.occlusionInScreenSpace().bounds());
+    EXPECT_EQ(1u, occlusion.occlusionInScreenSpace().rects().size());
+    EXPECT_EQ_RECT(IntRect(0, 430, 70, 70), occlusion.occlusionInTargetSurface().bounds());
+    EXPECT_EQ(1u, occlusion.occlusionInTargetSurface().rects().size());
+
+    // And it gets translated to the parent surface.
+    occlusion.leaveToTargetRenderSurface(parent->renderSurface());
+    EXPECT_EQ_RECT(IntRect(30, 30, 70, 70), occlusion.occlusionInScreenSpace().bounds());
+    EXPECT_EQ(1u, occlusion.occlusionInScreenSpace().rects().size());
+    EXPECT_EQ_RECT(IntRect(30, 30, 70, 70), occlusion.occlusionInTargetSurface().bounds());
+    EXPECT_EQ(1u, occlusion.occlusionInTargetSurface().rects().size());
+
+    // The blur layer needs to throw away any occlusion from outside its subtree.
+    occlusion.enterTargetRenderSurface(blurLayer->renderSurface());
+    EXPECT_TRUE(occlusion.occlusionInScreenSpace().isEmpty());
+    EXPECT_TRUE(occlusion.occlusionInTargetSurface().isEmpty());
+
+    // And it won't contribute to occlusion.
+    occlusion.markOccludedBehindLayer(blurLayer.get());
+    occlusion.finishedTargetRenderSurface(blurLayer.get(), blurLayer->renderSurface());
+    EXPECT_TRUE(occlusion.occlusionInScreenSpace().isEmpty());
+    EXPECT_TRUE(occlusion.occlusionInTargetSurface().isEmpty());
+
+    // But the opaque layer's occlusion is preserved on the parent.
+    occlusion.leaveToTargetRenderSurface(parent->renderSurface());
+    EXPECT_EQ_RECT(IntRect(30, 30, 70, 70), occlusion.occlusionInScreenSpace().bounds());
+    EXPECT_EQ(1u, occlusion.occlusionInScreenSpace().rects().size());
+    EXPECT_EQ_RECT(IntRect(30, 30, 70, 70), occlusion.occlusionInTargetSurface().bounds());
+    EXPECT_EQ(1u, occlusion.occlusionInTargetSurface().rects().size());
+}
+
+} // namespace