Clipping performance improvements

Create a ClipArea class to handle tracking clip regions. This class can
select the most efficient implementation depending on the types of
clipping presented.

ClipArea re-used the rectangle and region-based clipping
implementations as well as adding a "list of rotated rectangles"
approach that is more efficient for rotated views with children.

Change-Id: I2133761a2462ebc0852b394220e265974b3086f0
diff --git a/libs/hwui/Android.common.mk b/libs/hwui/Android.common.mk
index fba7254..b044e02 100644
--- a/libs/hwui/Android.common.mk
+++ b/libs/hwui/Android.common.mk
@@ -18,6 +18,7 @@
     AssetAtlas.cpp \
     Caches.cpp \
     CanvasState.cpp \
+    ClipArea.cpp \
     DamageAccumulator.cpp \
     DisplayList.cpp \
     DeferredDisplayList.cpp \
diff --git a/libs/hwui/CanvasState.cpp b/libs/hwui/CanvasState.cpp
index 9d2ccf1..85f860d 100644
--- a/libs/hwui/CanvasState.cpp
+++ b/libs/hwui/CanvasState.cpp
@@ -155,47 +155,18 @@
 ///////////////////////////////////////////////////////////////////////////////
 
 bool CanvasState::clipRect(float left, float top, float right, float bottom, SkRegion::Op op) {
-    if (CC_LIKELY(currentTransform()->rectToRect())) {
-        mDirtyClip |= mSnapshot->clip(left, top, right, bottom, op);
-        return !mSnapshot->clipRect->isEmpty();
-    }
-
-    SkPath path;
-    path.addRect(left, top, right, bottom);
-
-    return CanvasState::clipPath(&path, op);
+    mDirtyClip |= mSnapshot->clip(left, top, right, bottom, op);
+    return !mSnapshot->clipIsEmpty();
 }
 
 bool CanvasState::clipPath(const SkPath* path, SkRegion::Op op) {
-    SkMatrix transform;
-    currentTransform()->copyTo(transform);
-
-    SkPath transformed;
-    path->transform(transform, &transformed);
-
-    SkRegion clip;
-    if (!mSnapshot->previous->clipRegion->isEmpty()) {
-        clip.setRegion(*mSnapshot->previous->clipRegion);
-    } else {
-        if (mSnapshot->previous == firstSnapshot()) {
-            clip.setRect(0, 0, getWidth(), getHeight());
-        } else {
-            Rect* bounds = mSnapshot->previous->clipRect;
-            clip.setRect(bounds->left, bounds->top, bounds->right, bounds->bottom);
-        }
-    }
-
-    SkRegion region;
-    region.setPath(transformed, clip);
-
-    // region is the transformed input path, masked by the previous clip
-    mDirtyClip |= mSnapshot->clipRegionTransformed(region, op);
-    return !mSnapshot->clipRect->isEmpty();
+    mDirtyClip |= mSnapshot->clipPath(*path, op);
+    return !mSnapshot->clipIsEmpty();
 }
 
 bool CanvasState::clipRegion(const SkRegion* region, SkRegion::Op op) {
     mDirtyClip |= mSnapshot->clipRegionTransformed(*region, op);
-    return !mSnapshot->clipRect->isEmpty();
+    return !mSnapshot->clipIsEmpty();
 }
 
 void CanvasState::setClippingOutline(LinearAllocator& allocator, const Outline* outline) {
@@ -245,7 +216,7 @@
     currentTransform()->mapRect(r);
     r.snapGeometryToPixelBoundaries(snapOut);
 
-    Rect clipRect(*currentClipRect());
+    Rect clipRect(currentClipRect());
     clipRect.snapToPixelBoundaries();
 
     if (!clipRect.intersects(r)) return true;
@@ -273,7 +244,7 @@
     currentTransform()->mapRect(r);
     r.roundOut(); // rounded out to be conservative
 
-    Rect clipRect(*currentClipRect());
+    Rect clipRect(currentClipRect());
     clipRect.snapToPixelBoundaries();
 
     if (!clipRect.intersects(r)) return true;
diff --git a/libs/hwui/CanvasState.h b/libs/hwui/CanvasState.h
index 9e6a9c3..121112b 100644
--- a/libs/hwui/CanvasState.h
+++ b/libs/hwui/CanvasState.h
@@ -120,10 +120,6 @@
     bool clipPath(const SkPath* path, SkRegion::Op op);
     bool clipRegion(const SkRegion* region, SkRegion::Op op);
 
-    bool isCurrentClipSimple() const {
-        return currentSnapshot()->clipRegion->isEmpty();
-    }
-
     /**
      * Sets a "clipping outline", which is independent from the regular clip.
      * Currently only supports rectangles or rounded rectangles; passing in a
@@ -150,7 +146,7 @@
     void setInvisible(bool value) { mSnapshot->invisible = value; }
 
     inline const mat4* currentTransform() const { return currentSnapshot()->transform; }
-    inline const Rect* currentClipRect() const { return currentSnapshot()->clipRect; }
+    inline const Rect& currentClipRect() const { return currentSnapshot()->getClipRect(); }
     inline Region* currentRegion() const { return currentSnapshot()->region; }
     inline int currentFlags() const { return currentSnapshot()->flags; }
     const Vector3& currentLightCenter() const { return currentSnapshot()->getRelativeLightCenter(); }
diff --git a/libs/hwui/ClipArea.cpp b/libs/hwui/ClipArea.cpp
new file mode 100644
index 0000000..852204a
--- /dev/null
+++ b/libs/hwui/ClipArea.cpp
@@ -0,0 +1,368 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "ClipArea.h"
+
+#include <SkPath.h>
+#include <limits>
+
+#include "Rect.h"
+
+namespace android {
+namespace uirenderer {
+
+static bool intersect(Rect& r, const Rect& r2) {
+    bool hasIntersection = r.intersect(r2);
+    if (!hasIntersection) {
+        r.setEmpty();
+    }
+    return hasIntersection;
+}
+
+static void handlePoint(Rect& transformedBounds, const Matrix4& transform, float x, float y) {
+    Vertex v;
+    v.x = x;
+    v.y = y;
+    transform.mapPoint(v.x, v.y);
+    transformedBounds.expandToCoverVertex(v.x, v.y);
+}
+
+Rect transformAndCalculateBounds(const Rect& r, const Matrix4& transform) {
+    const float kMinFloat = std::numeric_limits<float>::lowest();
+    const float kMaxFloat = std::numeric_limits<float>::max();
+    Rect transformedBounds = { kMaxFloat, kMaxFloat, kMinFloat, kMinFloat };
+    handlePoint(transformedBounds, transform, r.left, r.top);
+    handlePoint(transformedBounds, transform, r.right, r.top);
+    handlePoint(transformedBounds, transform, r.left, r.bottom);
+    handlePoint(transformedBounds, transform, r.right, r.bottom);
+    return transformedBounds;
+}
+
+/*
+ * TransformedRectangle
+ */
+
+TransformedRectangle::TransformedRectangle() {
+}
+
+TransformedRectangle::TransformedRectangle(const Rect& bounds,
+        const Matrix4& transform)
+        : mBounds(bounds)
+        , mTransform(transform) {
+}
+
+bool TransformedRectangle::canSimplyIntersectWith(
+        const TransformedRectangle& other) const {
+
+    return mTransform == other.mTransform;
+}
+
+bool TransformedRectangle::intersectWith(const TransformedRectangle& other) {
+    Rect translatedBounds(other.mBounds);
+    return intersect(mBounds, translatedBounds);
+}
+
+bool TransformedRectangle::isEmpty() const {
+    return mBounds.isEmpty();
+}
+
+/*
+ * RectangleList
+ */
+
+RectangleList::RectangleList()
+        : mTransformedRectanglesCount(0) {
+}
+
+bool RectangleList::isEmpty() const {
+    if (mTransformedRectanglesCount < 1) {
+        return true;
+    }
+
+    for (int i = 0; i < mTransformedRectanglesCount; i++) {
+        if (mTransformedRectangles[i].isEmpty()) {
+            return true;
+        }
+    }
+    return false;
+}
+
+int RectangleList::getTransformedRectanglesCount() const {
+    return mTransformedRectanglesCount;
+}
+
+const TransformedRectangle& RectangleList::getTransformedRectangle(int i) const {
+    return mTransformedRectangles[i];
+}
+
+void RectangleList::setEmpty() {
+    mTransformedRectanglesCount = 0;
+}
+
+void RectangleList::set(const Rect& bounds, const Matrix4& transform) {
+    mTransformedRectanglesCount = 1;
+    mTransformedRectangles[0] = TransformedRectangle(bounds, transform);
+}
+
+bool RectangleList::intersectWith(const Rect& bounds,
+        const Matrix4& transform) {
+    TransformedRectangle newRectangle(bounds, transform);
+
+    // Try to find a rectangle with a compatible transformation
+    int index = 0;
+    for (; index < mTransformedRectanglesCount; index++) {
+        TransformedRectangle& tr(mTransformedRectangles[index]);
+        if (tr.canSimplyIntersectWith(newRectangle)) {
+            tr.intersectWith(newRectangle);
+            return true;
+        }
+    }
+
+    // Add it to the list if there is room
+    if (index < kMaxTransformedRectangles) {
+        mTransformedRectangles[index] = newRectangle;
+        mTransformedRectanglesCount += 1;
+        return true;
+    }
+
+    // This rectangle list is full
+    return false;
+}
+
+Rect RectangleList::calculateBounds() const {
+    Rect bounds;
+    for (int index = 0; index < mTransformedRectanglesCount; index++) {
+        const TransformedRectangle& tr(mTransformedRectangles[index]);
+        if (index == 0) {
+            bounds = tr.transformedBounds();
+        } else {
+            bounds.intersect(tr.transformedBounds());
+        }
+    }
+    return bounds;
+}
+
+static SkPath pathFromTransformedRectangle(const Rect& bounds,
+        const Matrix4& transform) {
+    SkPath rectPath;
+    SkPath rectPathTransformed;
+    rectPath.addRect(bounds.left, bounds.top, bounds.right, bounds.bottom);
+    SkMatrix skTransform;
+    transform.copyTo(skTransform);
+    rectPath.transform(skTransform, &rectPathTransformed);
+    return rectPathTransformed;
+}
+
+SkRegion RectangleList::convertToRegion(const SkRegion& clip) const {
+    SkRegion rectangleListAsRegion;
+    for (int index = 0; index < mTransformedRectanglesCount; index++) {
+        const TransformedRectangle& tr(mTransformedRectangles[index]);
+        SkPath rectPathTransformed = pathFromTransformedRectangle(
+                tr.getBounds(), tr.getTransform());
+        if (index == 0) {
+            rectangleListAsRegion.setPath(rectPathTransformed, clip);
+        } else {
+            SkRegion rectRegion;
+            rectRegion.setPath(rectPathTransformed, clip);
+            rectangleListAsRegion.op(rectRegion, SkRegion::kIntersect_Op);
+        }
+    }
+    return rectangleListAsRegion;
+}
+
+/*
+ * ClipArea
+ */
+
+ClipArea::ClipArea()
+        : mMode(kModeRectangle) {
+}
+
+/*
+ * Interface
+ */
+
+void ClipArea::setViewportDimensions(int width, int height) {
+    mViewportBounds.set(0, 0, width, height);
+    mClipRect = mViewportBounds;
+}
+
+void ClipArea::setEmpty() {
+    mMode = kModeRectangle;
+    mClipRect.setEmpty();
+    mClipRegion.setEmpty();
+    mRectangleList.setEmpty();
+}
+
+void ClipArea::setClip(float left, float top, float right, float bottom) {
+    mMode = kModeRectangle;
+    mClipRect.set(left, top, right, bottom);
+    mClipRegion.setEmpty();
+}
+
+bool ClipArea::clipRectWithTransform(float left, float top, float right,
+        float bottom, const mat4* transform, SkRegion::Op op) {
+    Rect r(left, top, right, bottom);
+    return clipRectWithTransform(r, transform, op);
+}
+
+bool ClipArea::clipRectWithTransform(const Rect& r, const mat4* transform,
+        SkRegion::Op op) {
+    switch (mMode) {
+    case kModeRectangle:
+        return rectangleModeClipRectWithTransform(r, transform, op);
+    case kModeRectangleList:
+        return rectangleListModeClipRectWithTransform(r, transform, op);
+    case kModeRegion:
+        return regionModeClipRectWithTransform(r, transform, op);
+    }
+    return false;
+}
+
+bool ClipArea::clipRegion(const SkRegion& region, SkRegion::Op op) {
+    enterRegionMode();
+    mClipRegion.op(region, op);
+    setClipRectToRegionBounds();
+    return true;
+}
+
+bool ClipArea::clipPathWithTransform(const SkPath& path, const mat4* transform,
+        SkRegion::Op op) {
+    SkMatrix skTransform;
+    transform->copyTo(skTransform);
+    SkPath transformed;
+    path.transform(skTransform, &transformed);
+    SkRegion region;
+    regionFromPath(transformed, region);
+    return clipRegion(region, op);
+}
+
+/*
+ * Rectangle mode
+ */
+
+void ClipArea::enterRectangleMode() {
+    // Entering rectangle mode discards any
+    // existing clipping information from the other modes.
+    // The only way this occurs is by a clip setting operation.
+    mMode = kModeRectangle;
+}
+
+bool ClipArea::rectangleModeClipRectWithTransform(const Rect& r,
+        const mat4* transform, SkRegion::Op op) {
+
+    if (op != SkRegion::kIntersect_Op) {
+        enterRegionMode();
+        return regionModeClipRectWithTransform(r, transform, op);
+    }
+
+    if (transform->rectToRect()) {
+        Rect transformed(r);
+        transform->mapRect(transformed);
+        bool hasIntersection = mClipRect.intersect(transformed);
+        if (!hasIntersection) {
+            mClipRect.setEmpty();
+        }
+        return true;
+    }
+
+    enterRectangleListMode();
+    return rectangleListModeClipRectWithTransform(r, transform, op);
+}
+
+bool ClipArea::rectangleModeClipRectWithTransform(float left, float top,
+        float right, float bottom, const mat4* transform, SkRegion::Op op) {
+    Rect r(left, top, right, bottom);
+    bool result = rectangleModeClipRectWithTransform(r, transform, op);
+    mClipRect = mRectangleList.calculateBounds();
+    return result;
+}
+
+/*
+ * RectangleList mode implementation
+ */
+
+void ClipArea::enterRectangleListMode() {
+    // Is is only legal to enter rectangle list mode from
+    // rectangle mode, since rectangle list mode cannot represent
+    // all clip areas that can be represented by a region.
+    ALOG_ASSERT(mMode == kModeRectangle);
+    mMode = kModeRectangleList;
+    mRectangleList.set(mClipRect, Matrix4::identity());
+}
+
+bool ClipArea::rectangleListModeClipRectWithTransform(const Rect& r,
+        const mat4* transform, SkRegion::Op op) {
+    if (op != SkRegion::kIntersect_Op
+            || !mRectangleList.intersectWith(r, *transform)) {
+        enterRegionMode();
+        return regionModeClipRectWithTransform(r, transform, op);
+    }
+    return true;
+}
+
+bool ClipArea::rectangleListModeClipRectWithTransform(float left, float top,
+        float right, float bottom, const mat4* transform, SkRegion::Op op) {
+    Rect r(left, top, right, bottom);
+    return rectangleListModeClipRectWithTransform(r, transform, op);
+}
+
+/*
+ * Region mode implementation
+ */
+
+void ClipArea::enterRegionMode() {
+    if (mMode != kModeRegion) {
+        if (mMode == kModeRectangle) {
+            mClipRegion.setRect(mClipRect.left, mClipRect.top,
+                    mClipRect.right, mClipRect.bottom);
+        } else {
+            mClipRegion = mRectangleList.convertToRegion(createViewportRegion());
+            setClipRectToRegionBounds();
+        }
+        mMode = kModeRegion;
+    }
+}
+
+bool ClipArea::regionModeClipRectWithTransform(const Rect& r,
+        const mat4* transform, SkRegion::Op op) {
+    SkPath transformedRect = pathFromTransformedRectangle(r, *transform);
+    SkRegion transformedRectRegion;
+    regionFromPath(transformedRect, transformedRectRegion);
+    mClipRegion.op(transformedRectRegion, op);
+    setClipRectToRegionBounds();
+    return true;
+}
+
+bool ClipArea::regionModeClipRectWithTransform(float left, float top,
+        float right, float bottom, const mat4* transform, SkRegion::Op op) {
+    return regionModeClipRectWithTransform(Rect(left, top, right, bottom),
+            transform, op);
+}
+
+void ClipArea::setClipRectToRegionBounds() {
+    if (!mClipRegion.isEmpty()) {
+        mClipRect.set(mClipRegion.getBounds());
+
+        if (mClipRegion.isRect()) {
+            mClipRegion.setEmpty();
+        }
+    } else {
+        mClipRect.setEmpty();
+    }
+}
+
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/ClipArea.h b/libs/hwui/ClipArea.h
new file mode 100644
index 0000000..16e6df9
--- /dev/null
+++ b/libs/hwui/ClipArea.h
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef CLIPAREA_H
+#define CLIPAREA_H
+
+#include <SkRegion.h>
+
+#include "Matrix.h"
+#include "Rect.h"
+#include "utils/Pair.h"
+
+namespace android {
+namespace uirenderer {
+
+Rect transformAndCalculateBounds(const Rect& r, const Matrix4& transform);
+
+class TransformedRectangle {
+public:
+    TransformedRectangle();
+    TransformedRectangle(const Rect& bounds, const Matrix4& transform);
+
+    bool canSimplyIntersectWith(const TransformedRectangle& other) const;
+    bool intersectWith(const TransformedRectangle& other);
+
+    bool isEmpty() const;
+
+    const Rect& getBounds() const {
+        return mBounds;
+    }
+
+    Rect transformedBounds() const {
+        Rect transformedBounds(transformAndCalculateBounds(mBounds, mTransform));
+        return transformedBounds;
+    }
+
+    const Matrix4& getTransform() const {
+        return mTransform;
+    }
+
+private:
+    Rect mBounds;
+    Matrix4 mTransform;
+};
+
+class RectangleList {
+public:
+    RectangleList();
+
+    bool isEmpty() const;
+    int getTransformedRectanglesCount() const;
+    const TransformedRectangle& getTransformedRectangle(int i) const;
+
+    void setEmpty();
+    void set(const Rect& bounds, const Matrix4& transform);
+    bool intersectWith(const Rect& bounds, const Matrix4& transform);
+
+    SkRegion convertToRegion(const SkRegion& clip) const;
+    Rect calculateBounds() const;
+
+private:
+    enum {
+        kMaxTransformedRectangles = 5
+    };
+
+    int mTransformedRectanglesCount;
+    TransformedRectangle mTransformedRectangles[kMaxTransformedRectangles];
+};
+
+class ClipArea {
+public:
+    ClipArea();
+
+    void setViewportDimensions(int width, int height);
+
+    bool isEmpty() const {
+        return mClipRect.isEmpty();
+    }
+
+    void setEmpty();
+    void setClip(float left, float top, float right, float bottom);
+    bool clipRectWithTransform(float left, float top, float right, float bottom,
+            const mat4* transform, SkRegion::Op op = SkRegion::kIntersect_Op);
+    bool clipRectWithTransform(const Rect& r, const mat4* transform,
+            SkRegion::Op op = SkRegion::kIntersect_Op);
+    bool clipRegion(const SkRegion& region, SkRegion::Op op = SkRegion::kIntersect_Op);
+    bool clipPathWithTransform(const SkPath& path, const mat4* transform,
+            SkRegion::Op op);
+
+    const Rect& getClipRect() const {
+        return mClipRect;
+    }
+
+    const SkRegion& getClipRegion() const {
+        return mClipRegion;
+    }
+
+    const RectangleList& getRectangleList() const {
+        return mRectangleList;
+    }
+
+    bool isRegion() const {
+        return kModeRegion == mMode;
+    }
+
+    bool isSimple() const {
+        return mMode == kModeRectangle;
+    }
+
+    bool isRectangleList() const {
+        return mMode == kModeRectangleList;
+    }
+
+private:
+    void enterRectangleMode();
+    bool rectangleModeClipRectWithTransform(const Rect& r, const mat4* transform, SkRegion::Op op);
+    bool rectangleModeClipRectWithTransform(float left, float top, float right,
+            float bottom, const mat4* transform, SkRegion::Op op);
+
+    void enterRectangleListMode();
+    bool rectangleListModeClipRectWithTransform(float left, float top,
+            float right, float bottom, const mat4* transform, SkRegion::Op op);
+    bool rectangleListModeClipRectWithTransform(const Rect& r,
+            const mat4* transform, SkRegion::Op op);
+
+    void enterRegionModeFromRectangleMode();
+    void enterRegionModeFromRectangleListMode();
+    void enterRegionMode();
+    bool regionModeClipRectWithTransform(const Rect& r, const mat4* transform,
+            SkRegion::Op op);
+    bool regionModeClipRectWithTransform(float left, float top, float right,
+            float bottom, const mat4* transform, SkRegion::Op op);
+
+    void ensureClipRegion();
+    void setClipRectToRegionBounds();
+    bool clipRegionOp(float left, float top, float right, float bottom,
+            SkRegion::Op op);
+
+    SkRegion createViewportRegion() {
+        return SkRegion(mViewportBounds.toSkIRect());
+    }
+
+    void regionFromPath(const SkPath& path, SkRegion& pathAsRegion) {
+        pathAsRegion.setPath(path, createViewportRegion());
+    }
+
+    enum Mode {
+        kModeRectangle,
+        kModeRegion,
+        kModeRectangleList
+    };
+
+    Mode mMode;
+    Rect mViewportBounds;
+    Rect mClipRect;
+    SkRegion mClipRegion;
+    RectangleList mRectangleList;
+};
+
+} /* namespace uirenderer */
+} /* namespace android */
+
+#endif /* CLIPAREA_H_ */
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
old mode 100755
new mode 100644
index 3c8fb8b..9ee3704
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -354,7 +354,7 @@
 void OpenGLRenderer::callDrawGLFunction(Functor* functor, Rect& dirty) {
     if (mState.currentlyIgnored()) return;
 
-    Rect clip(*mState.currentClipRect());
+    Rect clip(mState.currentClipRect());
     clip.snapToPixelBoundaries();
 
     // Since we don't know what the functor will draw, let's dirty
@@ -619,7 +619,7 @@
     currentTransform()->mapRect(bounds);
 
     // Layers only make sense if they are in the framebuffer's bounds
-    if (bounds.intersect(*mState.currentClipRect())) {
+    if (bounds.intersect(mState.currentClipRect())) {
         // We cannot work with sub-pixels in this case
         bounds.snapToPixelBoundaries();
 
@@ -1249,7 +1249,7 @@
 }
 
 void OpenGLRenderer::dirtyLayerUnchecked(Rect& bounds, Region* region) {
-    if (bounds.intersect(*mState.currentClipRect())) {
+    if (bounds.intersect(mState.currentClipRect())) {
         bounds.snapToPixelBoundaries();
         android::Rect dirty(bounds.left, bounds.top, bounds.right, bounds.bottom);
         if (!dirty.isEmpty()) {
@@ -1328,7 +1328,7 @@
 ///////////////////////////////////////////////////////////////////////////////
 
 bool OpenGLRenderer::storeDisplayState(DeferredDisplayState& state, int stateDeferFlags) {
-    const Rect* currentClip = mState.currentClipRect();
+    const Rect& currentClip = mState.currentClipRect();
     const mat4* currentMatrix = currentTransform();
 
     if (stateDeferFlags & kStateDeferFlag_Draw) {
@@ -1340,32 +1340,32 @@
             // is used, it should more closely duplicate the quickReject logic (in how it uses
             // snapToPixelBoundaries)
 
-            if(!clippedBounds.intersect(*currentClip)) {
+            if (!clippedBounds.intersect(currentClip)) {
                 // quick rejected
                 return true;
             }
 
             state.mClipSideFlags = kClipSide_None;
-            if (!currentClip->contains(state.mBounds)) {
+            if (!currentClip.contains(state.mBounds)) {
                 int& flags = state.mClipSideFlags;
                 // op partially clipped, so record which sides are clipped for clip-aware merging
-                if (currentClip->left > state.mBounds.left) flags |= kClipSide_Left;
-                if (currentClip->top > state.mBounds.top) flags |= kClipSide_Top;
-                if (currentClip->right < state.mBounds.right) flags |= kClipSide_Right;
-                if (currentClip->bottom < state.mBounds.bottom) flags |= kClipSide_Bottom;
+                if (currentClip.left > state.mBounds.left) flags |= kClipSide_Left;
+                if (currentClip.top > state.mBounds.top) flags |= kClipSide_Top;
+                if (currentClip.right < state.mBounds.right) flags |= kClipSide_Right;
+                if (currentClip.bottom < state.mBounds.bottom) flags |= kClipSide_Bottom;
             }
             state.mBounds.set(clippedBounds);
         } else {
             // Empty bounds implies size unknown. Label op as conservatively clipped to disable
             // overdraw avoidance (since we don't know what it overlaps)
             state.mClipSideFlags = kClipSide_ConservativeFull;
-            state.mBounds.set(*currentClip);
+            state.mBounds.set(currentClip);
         }
     }
 
     state.mClipValid = (stateDeferFlags & kStateDeferFlag_Clip);
     if (state.mClipValid) {
-        state.mClip.set(*currentClip);
+        state.mClip.set(currentClip);
     }
 
     // Transform, drawModifiers, and alpha always deferred, since they are used by state operations
@@ -1414,7 +1414,7 @@
 ///////////////////////////////////////////////////////////////////////////////
 
 void OpenGLRenderer::setScissorFromClip() {
-    Rect clip(*mState.currentClipRect());
+    Rect clip(mState.currentClipRect());
     clip.snapToPixelBoundaries();
 
     if (mCaches.setScissor(clip.left, getViewportHeight() - clip.bottom,
@@ -1448,9 +1448,74 @@
     }
 }
 
+static void handlePoint(std::vector<Vertex>& rectangleVertices, const Matrix4& transform,
+        float x, float y) {
+    Vertex v;
+    v.x = x;
+    v.y = y;
+    transform.mapPoint(v.x, v.y);
+    rectangleVertices.push_back(v);
+}
+
+static void handlePointNoTransform(std::vector<Vertex>& rectangleVertices, float x, float y) {
+    Vertex v;
+    v.x = x;
+    v.y = y;
+    rectangleVertices.push_back(v);
+}
+
+void OpenGLRenderer::drawRectangleList(const RectangleList& rectangleList) {
+    int count = rectangleList.getTransformedRectanglesCount();
+    std::vector<Vertex> rectangleVertices(count * 4);
+    Rect scissorBox = rectangleList.calculateBounds();
+    scissorBox.snapToPixelBoundaries();
+    for (int i = 0; i < count; ++i) {
+        const TransformedRectangle& tr(rectangleList.getTransformedRectangle(i));
+        const Matrix4& transform = tr.getTransform();
+        Rect bounds = tr.getBounds();
+        if (transform.rectToRect()) {
+            transform.mapRect(bounds);
+            if (!bounds.intersect(scissorBox)) {
+                bounds.setEmpty();
+            } else {
+                handlePointNoTransform(rectangleVertices, bounds.left, bounds.top);
+                handlePointNoTransform(rectangleVertices, bounds.right, bounds.top);
+                handlePointNoTransform(rectangleVertices, bounds.left, bounds.bottom);
+                handlePointNoTransform(rectangleVertices, bounds.right, bounds.bottom);
+            }
+        } else {
+            handlePoint(rectangleVertices, transform, bounds.left, bounds.top);
+            handlePoint(rectangleVertices, transform, bounds.right, bounds.top);
+            handlePoint(rectangleVertices, transform, bounds.left, bounds.bottom);
+            handlePoint(rectangleVertices, transform, bounds.right, bounds.bottom);
+        }
+    }
+
+    mCaches.setScissor(scissorBox.left, getViewportHeight() - scissorBox.bottom,
+            scissorBox.getWidth(), scissorBox.getHeight());
+
+    const SkPaint* paint = nullptr;
+    setupDraw();
+    setupDrawNoTexture();
+    setupDrawColor(0, 0xff * currentSnapshot()->alpha);
+    setupDrawShader(getShader(paint));
+    setupDrawColorFilter(getColorFilter(paint));
+    setupDrawBlending(paint);
+    setupDrawProgram();
+    setupDrawDirtyRegionsDisabled();
+    setupDrawModelView(kModelViewMode_Translate, false,
+            0.0f, 0.0f, 0.0f, 0.0f, true);
+    setupDrawColorUniforms(getShader(paint));
+    setupDrawShaderUniforms(getShader(paint));
+    setupDrawColorFilterUniforms(getColorFilter(paint));
+
+    issueIndexedQuadDraw(&rectangleVertices[0], rectangleVertices.size() / 4);
+}
+
 void OpenGLRenderer::setStencilFromClip() {
     if (!mCaches.debugOverdraw) {
-        if (!currentSnapshot()->clipRegion->isEmpty()) {
+        if (!currentSnapshot()->clipIsSimple()) {
+            int incrementThreshold;
             EVENT_LOGD("setStencilFromClip - enabling");
 
             // NOTE: The order here is important, we must set dirtyClip to false
@@ -1459,15 +1524,25 @@
 
             ensureStencilBuffer();
 
-            mCaches.stencil.enableWrite();
+            const ClipArea& clipArea = currentSnapshot()->getClipArea();
 
-            // Clear and update the stencil, but first make sure we restrict drawing
+            bool isRectangleList = clipArea.isRectangleList();
+            if (isRectangleList) {
+                incrementThreshold = clipArea.getRectangleList().getTransformedRectanglesCount();
+            } else {
+                incrementThreshold = 0;
+            }
+
+            mCaches.stencil.enableWrite(incrementThreshold);
+
+            // Clean and update the stencil, but first make sure we restrict drawing
             // to the region's bounds
             bool resetScissor = mCaches.enableScissor();
             if (resetScissor) {
                 // The scissor was not set so we now need to update it
                 setScissorFromClip();
             }
+
             mCaches.stencil.clear();
 
             // stash and disable the outline clip state, since stencil doesn't account for outline
@@ -1478,24 +1553,30 @@
             paint.setColor(SK_ColorBLACK);
             paint.setXfermodeMode(SkXfermode::kSrc_Mode);
 
-            // NOTE: We could use the region contour path to generate a smaller mesh
-            //       Since we are using the stencil we could use the red book path
-            //       drawing technique. It might increase bandwidth usage though.
+            if (isRectangleList) {
+                drawRectangleList(clipArea.getRectangleList());
+            } else {
+                // NOTE: We could use the region contour path to generate a smaller mesh
+                //       Since we are using the stencil we could use the red book path
+                //       drawing technique. It might increase bandwidth usage though.
 
-            // The last parameter is important: we are not drawing in the color buffer
-            // so we don't want to dirty the current layer, if any
-            drawRegionRects(*(currentSnapshot()->clipRegion), paint, false);
+                // The last parameter is important: we are not drawing in the color buffer
+                // so we don't want to dirty the current layer, if any
+                drawRegionRects(clipArea.getClipRegion(), paint, false);
+            }
             if (resetScissor) mCaches.disableScissor();
             mSkipOutlineClip = storedSkipOutlineClip;
 
-            mCaches.stencil.enableTest();
+            mCaches.stencil.enableTest(incrementThreshold);
 
             // Draw the region used to generate the stencil if the appropriate debug
             // mode is enabled
-            if (mCaches.debugStencilClip == Caches::kStencilShowRegion) {
+            // TODO: Implement for rectangle list clip areas
+            if (mCaches.debugStencilClip == Caches::kStencilShowRegion &&
+                    !clipArea.isRectangleList()) {
                 paint.setColor(0x7f0000ff);
                 paint.setXfermodeMode(SkXfermode::kSrcOver_Mode);
-                drawRegionRects(*(currentSnapshot()->clipRegion), paint);
+                drawRegionRects(currentSnapshot()->getClipRegion(), paint);
             }
         } else {
             EVENT_LOGD("setStencilFromClip - disabling");
@@ -1913,7 +1994,7 @@
         // Don't avoid overdraw when visualizing, since that makes it harder to
         // debug where it's coming from, and when the problem occurs.
         bool avoidOverdraw = !mCaches.debugOverdraw;
-        DeferredDisplayList deferredList(*mState.currentClipRect(), avoidOverdraw);
+        DeferredDisplayList deferredList(mState.currentClipRect(), avoidOverdraw);
         DeferStateStruct deferStruct(deferredList, *this, replayFlags);
         renderNode->defer(deferStruct, 0);
 
@@ -2450,7 +2531,7 @@
     // No need to check against the clip, we fill the clip region
     if (mState.currentlyIgnored()) return;
 
-    Rect clip(*mState.currentClipRect());
+    Rect clip(mState.currentClipRect());
     clip.snapToPixelBoundaries();
 
     SkPaint paint;
@@ -2704,13 +2785,13 @@
     }
     fontRenderer.setTextureFiltering(linearFilter);
 
-    const Rect* clip = pureTranslate ? writableSnapshot()->clipRect : &writableSnapshot()->getLocalClip();
+    const Rect& clip(pureTranslate ? writableSnapshot()->getClipRect() : writableSnapshot()->getLocalClip());
     Rect bounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f);
 
     const bool hasActiveLayer = hasLayer();
 
     TextSetupFunctor functor(this, x, y, pureTranslate, alpha, mode, paint);
-    if (fontRenderer.renderPosText(paint, clip, text, 0, bytesCount, count, x, y,
+    if (fontRenderer.renderPosText(paint, &clip, text, 0, bytesCount, count, x, y,
             positions, hasActiveLayer ? &bounds : nullptr, &functor)) {
         if (hasActiveLayer) {
             if (!pureTranslate) {
@@ -2864,7 +2945,7 @@
     fontRenderer.setTextureFiltering(linearFilter);
 
     // TODO: Implement better clipping for scaled/rotated text
-    const Rect* clip = !pureTranslate ? nullptr : mState.currentClipRect();
+    const Rect* clip = !pureTranslate ? nullptr : &mState.currentClipRect();
     Rect layerBounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f);
 
     bool status;
diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h
index 9d9b3d2..f613183 100755
--- a/libs/hwui/OpenGLRenderer.h
+++ b/libs/hwui/OpenGLRenderer.h
@@ -223,9 +223,6 @@
         return mCaches;
     }
 
-    // simple rect clip
-    bool isCurrentClipSimple() { return mState.isCurrentClipSimple(); }
-
     int getViewportWidth() { return mState.getViewportWidth(); }
     int getViewportHeight() { return mState.getViewportHeight(); }
 
@@ -433,6 +430,13 @@
      */
     void attachStencilBufferToLayer(Layer* layer);
 
+    /**
+     * Draw a rectangle list. Currently only used for the the stencil buffer so that the stencil
+     * will have a value of 'n' in every unclipped pixel, where 'n' is the number of rectangles
+     * in the list.
+     */
+    void drawRectangleList(const RectangleList& rectangleList);
+
     bool quickRejectSetupScissor(float left, float top, float right, float bottom,
             const SkPaint* paint = nullptr);
     bool quickRejectSetupScissor(const Rect& bounds, const SkPaint* paint = nullptr) {
diff --git a/libs/hwui/Rect.h b/libs/hwui/Rect.h
index b046b6f..1716cf0 100644
--- a/libs/hwui/Rect.h
+++ b/libs/hwui/Rect.h
@@ -111,6 +111,10 @@
         set(r.left, r.top, r.right, r.bottom);
     }
 
+    inline void set(const SkIRect& r) {
+        set(r.left(), r.top(), r.right(), r.bottom());
+    }
+
     inline float getWidth() const {
         return right - left;
     }
@@ -248,6 +252,14 @@
         bottom = fmaxf(bottom, y);
     }
 
+    SkRect toSkRect() const {
+        return SkRect::MakeLTRB(left, top, right, bottom);
+    }
+
+    SkIRect toSkIRect() const {
+        return SkIRect::MakeLTRB(left, top, right, bottom);
+    }
+
     void dump(const char* label = nullptr) const {
         ALOGD("%s[l=%f t=%f r=%f b=%f]", label ? label : "Rect", left, top, right, bottom);
     }
diff --git a/libs/hwui/Snapshot.cpp b/libs/hwui/Snapshot.cpp
index d4ca99e..49fb4ba 100644
--- a/libs/hwui/Snapshot.cpp
+++ b/libs/hwui/Snapshot.cpp
@@ -35,11 +35,10 @@
         , invisible(false)
         , empty(false)
         , alpha(1.0f)
-        , roundRectClipState(nullptr) {
+        , roundRectClipState(nullptr)
+        , mClipArea(&mClipAreaRoot) {
     transform = &mTransformRoot;
-    clipRect = &mClipRectRoot;
     region = nullptr;
-    clipRegion = &mClipRegionRoot;
 }
 
 /**
@@ -55,6 +54,7 @@
         , empty(false)
         , alpha(s->alpha)
         , roundRectClipState(s->roundRectClipState)
+        , mClipArea(nullptr)
         , mViewportData(s->mViewportData)
         , mRelativeLightCenter(s->mRelativeLightCenter) {
     if (saveFlags & SkCanvas::kMatrix_SaveFlag) {
@@ -65,15 +65,10 @@
     }
 
     if (saveFlags & SkCanvas::kClip_SaveFlag) {
-        mClipRectRoot.set(*s->clipRect);
-        clipRect = &mClipRectRoot;
-        if (!s->clipRegion->isEmpty()) {
-            mClipRegionRoot.op(*s->clipRegion, SkRegion::kUnion_Op);
-        }
-        clipRegion = &mClipRegionRoot;
+        mClipAreaRoot = s->getClipArea();
+        mClipArea = &mClipAreaRoot;
     } else {
-        clipRect = s->clipRect;
-        clipRegion = s->clipRegion;
+        mClipArea = s->mClipArea;
     }
 
     if (s->flags & Snapshot::kFlagFboTarget) {
@@ -88,88 +83,23 @@
 // Clipping
 ///////////////////////////////////////////////////////////////////////////////
 
-void Snapshot::ensureClipRegion() {
-    if (clipRegion->isEmpty()) {
-        clipRegion->setRect(clipRect->left, clipRect->top, clipRect->right, clipRect->bottom);
-    }
-}
-
-void Snapshot::copyClipRectFromRegion() {
-    if (!clipRegion->isEmpty()) {
-        const SkIRect& bounds = clipRegion->getBounds();
-        clipRect->set(bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom);
-
-        if (clipRegion->isRect()) {
-            clipRegion->setEmpty();
-        }
-    } else {
-        clipRect->setEmpty();
-    }
-}
-
-bool Snapshot::clipRegionOp(float left, float top, float right, float bottom, SkRegion::Op op) {
-    SkIRect tmp;
-    tmp.set(left, top, right, bottom);
-    clipRegion->op(tmp, op);
-    copyClipRectFromRegion();
-    return true;
-}
-
 bool Snapshot::clipRegionTransformed(const SkRegion& region, SkRegion::Op op) {
-    ensureClipRegion();
-    clipRegion->op(region, op);
-    copyClipRectFromRegion();
     flags |= Snapshot::kFlagClipSet;
-    return true;
+    return mClipArea->clipRegion(region, op);
 }
 
 bool Snapshot::clip(float left, float top, float right, float bottom, SkRegion::Op op) {
-    Rect r(left, top, right, bottom);
-    transform->mapRect(r);
-    return clipTransformed(r, op);
+    flags |= Snapshot::kFlagClipSet;
+    return mClipArea->clipRectWithTransform(left, top, right, bottom, transform, op);
 }
 
-bool Snapshot::clipTransformed(const Rect& r, SkRegion::Op op) {
-    bool clipped = false;
-
-    switch (op) {
-        case SkRegion::kIntersect_Op: {
-            if (CC_UNLIKELY(!clipRegion->isEmpty())) {
-                ensureClipRegion();
-                clipped = clipRegionOp(r.left, r.top, r.right, r.bottom, SkRegion::kIntersect_Op);
-            } else {
-                clipped = clipRect->intersect(r);
-                if (!clipped) {
-                    clipRect->setEmpty();
-                    clipped = true;
-                }
-            }
-            break;
-        }
-        case SkRegion::kReplace_Op: {
-            setClip(r.left, r.top, r.right, r.bottom);
-            clipped = true;
-            break;
-        }
-        default: {
-            ensureClipRegion();
-            clipped = clipRegionOp(r.left, r.top, r.right, r.bottom, op);
-            break;
-        }
-    }
-
-    if (clipped) {
-        flags |= Snapshot::kFlagClipSet;
-    }
-
-    return clipped;
+bool Snapshot::clipPath(const SkPath& path, SkRegion::Op op) {
+    flags |= Snapshot::kFlagClipSet;
+    return mClipArea->clipPathWithTransform(path, transform, op);
 }
 
 void Snapshot::setClip(float left, float top, float right, float bottom) {
-    clipRect->set(left, top, right, bottom);
-    if (!clipRegion->isEmpty()) {
-        clipRegion->setEmpty();
-    }
+    mClipArea->setClip(left, top, right, bottom);
     flags |= Snapshot::kFlagClipSet;
 }
 
@@ -181,7 +111,7 @@
     mat4 inverse;
     inverse.loadInverse(*transform);
 
-    mLocalClip.set(*clipRect);
+    mLocalClip.set(mClipArea->getClipRect());
     inverse.mapRect(mLocalClip);
 
     return mLocalClip;
@@ -191,8 +121,7 @@
     // TODO: This is incorrect, when we start rendering into a new layer,
     // we may have to modify the previous snapshot's clip rect and clip
     // region if the previous restore() call did not restore the clip
-    clipRect = &mClipRectRoot;
-    clipRegion = &mClipRegionRoot;
+    mClipArea = &mClipAreaRoot;
     setClip(left, top, right, bottom);
 }
 
@@ -219,7 +148,7 @@
 void Snapshot::setClippingRoundRect(LinearAllocator& allocator, const Rect& bounds,
         float radius, bool highPriority) {
     if (bounds.isEmpty()) {
-        clipRect->setEmpty();
+        mClipArea->setEmpty();
         return;
     }
 
@@ -272,9 +201,10 @@
 
 void Snapshot::dump() const {
     ALOGD("Snapshot %p, flags %x, prev %p, height %d, ignored %d, hasComplexClip %d",
-            this, flags, previous.get(), getViewportHeight(), isIgnored(), clipRegion && !clipRegion->isEmpty());
+            this, flags, previous.get(), getViewportHeight(), isIgnored(), !mClipArea->isSimple());
+    const Rect& clipRect(mClipArea->getClipRect());
     ALOGD("  ClipRect (at %p) %.1f %.1f %.1f %.1f",
-            clipRect, clipRect->left, clipRect->top, clipRect->right, clipRect->bottom);
+            clipRect, clipRect.left, clipRect.top, clipRect.right, clipRect.bottom);
     ALOGD("  Transform (at %p):", transform);
     transform->dump();
 }
diff --git a/libs/hwui/Snapshot.h b/libs/hwui/Snapshot.h
index 549de9b..4d704ab 100644
--- a/libs/hwui/Snapshot.h
+++ b/libs/hwui/Snapshot.h
@@ -26,6 +26,7 @@
 
 #include <SkRegion.h>
 
+#include "ClipArea.h"
 #include "Layer.h"
 #include "Matrix.h"
 #include "Outline.h"
@@ -129,6 +130,11 @@
     bool clipRegionTransformed(const SkRegion& region, SkRegion::Op op);
 
     /**
+     * Modifies the current clip with the specified path and operation.
+     */
+    bool clipPath(const SkPath& path, SkRegion::Op op);
+
+    /**
      * Sets the current clip.
      */
     void setClip(float left, float top, float right, float bottom);
@@ -142,7 +148,16 @@
     /**
      * Returns the current clip in render target coordinates.
      */
-    const Rect& getRenderTargetClip() { return *clipRect; }
+    const Rect& getRenderTargetClip() { return mClipArea->getClipRect(); }
+
+    /*
+     * Accessor functions so that the clip area can stay private
+     */
+    bool clipIsEmpty() const { return mClipArea->isEmpty(); }
+    const Rect& getClipRect() const { return mClipArea->getClipRect(); }
+    const SkRegion& getClipRegion() const { return mClipArea->getClipRegion(); }
+    bool clipIsSimple() const { return mClipArea->isSimple(); }
+    const ClipArea& getClipArea() const { return *mClipArea; }
 
     /**
      * Resets the clip to the specified rect.
@@ -156,6 +171,7 @@
 
     void initializeViewport(int width, int height) {
         mViewportData.initialize(width, height);
+        mClipAreaRoot.setViewportDimensions(width, height);
     }
 
     int getViewportWidth() const { return mViewportData.mWidth; }
@@ -175,7 +191,7 @@
 
     /**
      * Indicates whether this snapshot should be ignored. A snapshot
-     * is typicalled ignored if its layer is invisible or empty.
+     * is typically ignored if its layer is invisible or empty.
      */
     bool isIgnored() const;
 
@@ -230,24 +246,6 @@
     mat4* transform;
 
     /**
-     * Current clip rect. The clip is stored in canvas-space coordinates,
-     * (screen-space coordinates in the regular case.)
-     *
-     * This is a reference to a rect owned by this snapshot or another
-     * snapshot. This pointer must not be freed. See ::mClipRectRoot.
-     */
-    Rect* clipRect;
-
-    /**
-     * Current clip region. The clip is stored in canvas-space coordinates,
-     * (screen-space coordinates in the regular case.)
-     *
-     * This is a reference to a region owned by this snapshot or another
-     * snapshot. This pointer must not be freed. See ::mClipRegionRoot.
-     */
-    SkRegion* clipRegion;
-
-    /**
      * The ancestor layer's dirty region.
      *
      * This is a reference to a region owned by a layer. This pointer must
@@ -260,7 +258,7 @@
      * has translucent rendering in a non-overlapping View. This value will be used by
      * the renderer to set the alpha in the current color being used for ensuing drawing
      * operations. The value is inherited by child snapshots because the same value should
-     * be applied to descendents of the current DisplayList (for example, a TextView contains
+     * be applied to descendants of the current DisplayList (for example, a TextView contains
      * the base alpha value which should be applied to the child DisplayLists used for drawing
      * the actual text).
      */
@@ -298,16 +296,12 @@
         mat4 mOrthoMatrix;
     };
 
-    void ensureClipRegion();
-    void copyClipRectFromRegion();
-
-    bool clipRegionOp(float left, float top, float right, float bottom, SkRegion::Op op);
-
     mat4 mTransformRoot;
-    Rect mClipRectRoot;
-    Rect mLocalClip; // don't use directly, call getLocalClip() which initializes this
 
-    SkRegion mClipRegionRoot;
+    ClipArea mClipAreaRoot;
+    ClipArea* mClipArea;
+    Rect mLocalClip;
+
     ViewportData mViewportData;
     Vector3 mRelativeLightCenter;
 
diff --git a/libs/hwui/Stencil.cpp b/libs/hwui/Stencil.cpp
index 8ce57db..f56a02e 100644
--- a/libs/hwui/Stencil.cpp
+++ b/libs/hwui/Stencil.cpp
@@ -32,7 +32,8 @@
 #define STENCIL_MASK_VALUE 0x1
 #endif
 
-Stencil::Stencil(): mState(kDisabled) {
+Stencil::Stencil()
+        : mState(kDisabled) {
 }
 
 uint8_t Stencil::getStencilSize() {
@@ -56,24 +57,36 @@
     glClear(GL_STENCIL_BUFFER_BIT);
 }
 
-void Stencil::enableTest() {
+void Stencil::enableTest(int incrementThreshold) {
     if (mState != kTest) {
         enable();
-        glStencilFunc(GL_EQUAL, STENCIL_WRITE_VALUE, STENCIL_MASK_VALUE);
+        if (incrementThreshold > 0) {
+            glStencilFunc(GL_EQUAL, incrementThreshold, 0xff);
+        } else {
+            glStencilFunc(GL_EQUAL, STENCIL_WRITE_VALUE, STENCIL_MASK_VALUE);
+        }
         // We only want to test, let's keep everything
         glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
         glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
+        glStencilMask(0);
         mState = kTest;
     }
 }
 
-void Stencil::enableWrite() {
+void Stencil::enableWrite(int incrementThreshold) {
     if (mState != kWrite) {
         enable();
-        glStencilFunc(GL_ALWAYS, STENCIL_WRITE_VALUE, STENCIL_MASK_VALUE);
-        // The test always passes so the first two values are meaningless
-        glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
+        if (incrementThreshold > 0) {
+            glStencilFunc(GL_ALWAYS, 1, 0xff);
+            // The test always passes so the first two values are meaningless
+            glStencilOp(GL_INCR, GL_INCR, GL_INCR);
+        } else {
+            glStencilFunc(GL_ALWAYS, STENCIL_WRITE_VALUE, STENCIL_MASK_VALUE);
+            // The test always passes so the first two values are meaningless
+            glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
+        }
         glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
+        glStencilMask(0xff);
         mState = kWrite;
     }
 }
diff --git a/libs/hwui/Stencil.h b/libs/hwui/Stencil.h
index c5e5186..20bb955 100644
--- a/libs/hwui/Stencil.h
+++ b/libs/hwui/Stencil.h
@@ -53,17 +53,21 @@
     void clear();
 
     /**
-     * Enables stencil test. When the stencil test is enabled the stencil
-     * buffer is not written into.
+     * Enables stencil test. When the stencil test is enabled the stencil buffer is not written
+     * into. An increment threshold of zero causes the stencil to use a constant reference value
+     * and GL_EQUAL for the test. A non-zero increment threshold causes the stencil to use that
+     * value as the reference value and GL_EQUAL for the test.
      */
-    void enableTest();
+    void enableTest(int incrementThreshold);
 
     /**
      * Enables stencil write. When stencil write is enabled, the stencil
      * test always succeeds and the value 0x1 is written in the stencil
-     * buffer for each fragment.
+     * buffer for each fragment. An increment threshold of zero causes the stencil to use a constant
+     * reference value and GL_EQUAL for the test. A non-zero increment threshold causes the stencil
+     * to use that value as the reference value and GL_EQUAL for the test.
      */
-    void enableWrite();
+    void enableWrite(int incrementThreshold);
 
     /**
      * The test passes only when equal to the specified value.
diff --git a/libs/hwui/tests/Android.mk b/libs/hwui/tests/Android.mk
index 2fff07d..a69f3fb 100644
--- a/libs/hwui/tests/Android.mk
+++ b/libs/hwui/tests/Android.mk
@@ -32,3 +32,16 @@
 	tests/main.cpp
 
 include $(BUILD_EXECUTABLE)
+
+include $(CLEAR_VARS)
+
+LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.common.mk
+LOCAL_MODULE := hwui_unit_tests
+LOCAL_MODULE_TAGS := tests
+
+include $(LOCAL_PATH)/Android.common.mk
+
+LOCAL_SRC_FILES += \
+	tests/ClipAreaTests.cpp \
+
+include $(BUILD_NATIVE_TEST)
diff --git a/libs/hwui/tests/ClipAreaTests.cpp b/libs/hwui/tests/ClipAreaTests.cpp
new file mode 100644
index 0000000..166d5b6
--- /dev/null
+++ b/libs/hwui/tests/ClipAreaTests.cpp
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gtest/gtest.h>
+#include <SkPath.h>
+#include <SkRegion.h>
+
+#include "ClipArea.h"
+
+#include "Matrix.h"
+#include "Rect.h"
+#include "utils/LinearAllocator.h"
+
+namespace android {
+namespace uirenderer {
+
+static Rect kViewportBounds(0, 0, 2048, 2048);
+
+static ClipArea createClipArea() {
+    ClipArea area;
+    area.setViewportDimensions(kViewportBounds.getWidth(), kViewportBounds.getHeight());
+    return area;
+}
+
+TEST(TransformedRectangle, basics) {
+    Rect r(0, 0, 100, 100);
+    Matrix4 minus90;
+    minus90.loadRotate(-90);
+    minus90.mapRect(r);
+    Rect r2(20, 40, 120, 60);
+
+    Matrix4 m90;
+    m90.loadRotate(90);
+    TransformedRectangle tr(r, m90);
+    EXPECT_TRUE(tr.canSimplyIntersectWith(tr));
+
+    Matrix4 m0;
+    TransformedRectangle tr0(r2, m0);
+    EXPECT_FALSE(tr.canSimplyIntersectWith(tr0));
+
+    Matrix4 m45;
+    m45.loadRotate(45);
+    TransformedRectangle tr2(r, m45);
+    EXPECT_FALSE(tr2.canSimplyIntersectWith(tr));
+}
+
+TEST(RectangleList, basics) {
+    RectangleList list;
+    EXPECT_TRUE(list.isEmpty());
+
+    Rect r(0, 0, 100, 100);
+    Matrix4 m45;
+    m45.loadRotate(45);
+    list.set(r, m45);
+    EXPECT_FALSE(list.isEmpty());
+
+    Rect r2(20, 20, 200, 200);
+    list.intersectWith(r2, m45);
+    EXPECT_FALSE(list.isEmpty());
+    EXPECT_EQ(1, list.getTransformedRectanglesCount());
+
+    Rect r3(20, 20, 200, 200);
+    Matrix4 m30;
+    m30.loadRotate(30);
+    list.intersectWith(r2, m30);
+    EXPECT_FALSE(list.isEmpty());
+    EXPECT_EQ(2, list.getTransformedRectanglesCount());
+
+    SkRegion clip;
+    clip.setRect(0, 0, 2000, 2000);
+    SkRegion rgn(list.convertToRegion(clip));
+    EXPECT_FALSE(rgn.isEmpty());
+}
+
+TEST(ClipArea, basics) {
+    ClipArea area(createClipArea());
+    EXPECT_FALSE(area.isEmpty());
+}
+
+TEST(ClipArea, paths) {
+    ClipArea area(createClipArea());
+    Matrix4 transform;
+    transform.loadIdentity();
+    SkPath path;
+    SkScalar r = 100;
+    path.addCircle(r, r, r);
+    area.clipPathWithTransform(path, &transform, SkRegion::kIntersect_Op);
+    EXPECT_FALSE(area.isEmpty());
+    EXPECT_FALSE(area.isSimple());
+    EXPECT_FALSE(area.isRectangleList());
+    Rect clipRect(area.getClipRect());
+    clipRect.dump("clipRect");
+    Rect expected(0, 0, r * 2, r * 2);
+    expected.dump("expected");
+    EXPECT_EQ(expected, clipRect);
+    SkRegion clipRegion(area.getClipRegion());
+    auto skRect(clipRegion.getBounds());
+    Rect regionBounds;
+    regionBounds.set(skRect);
+    EXPECT_EQ(expected, regionBounds);
+}
+}
+}
diff --git a/libs/hwui/utils/Pair.h b/libs/hwui/utils/Pair.h
index 172606a..0db3aa3 100644
--- a/libs/hwui/utils/Pair.h
+++ b/libs/hwui/utils/Pair.h
@@ -17,6 +17,8 @@
 #ifndef ANDROID_HWUI_PAIR_H
 #define ANDROID_HWUI_PAIR_H
 
+#include <utils/TypeHelpers.h>
+
 namespace android {
 namespace uirenderer {