Add private circular reveal API on View/RenderNode

Change-Id: I139c8e12b354083149a665f6768f3f6931a8dd15
diff --git a/libs/hwui/DisplayListOp.h b/libs/hwui/DisplayListOp.h
index 549b786..9e367fc 100644
--- a/libs/hwui/DisplayListOp.h
+++ b/libs/hwui/DisplayListOp.h
@@ -21,6 +21,8 @@
     #define LOG_TAG "OpenGLRenderer"
 #endif
 
+#include <SkPath.h>
+#include <SkPathOps.h>
 #include <SkXfermode.h>
 
 #include <private/hwui/DrawGlInfo.h>
@@ -1550,20 +1552,27 @@
  */
 class DrawShadowOp : public DrawOp {
 public:
-    DrawShadowOp(const mat4& transformXY, const mat4& transformZ, float alpha, const SkPath* outline,
-            float fallbackWidth, float fallbackHeight)
-            : DrawOp(NULL), mTransformXY(transformXY), mTransformZ(transformZ),
-            mAlpha(alpha), mOutline(outline),
-            mFallbackWidth(fallbackWidth), mFallbackHeight(fallbackHeight) {}
+    DrawShadowOp(const mat4& transformXY, const mat4& transformZ, float alpha,
+            float fallbackWidth, float fallbackHeight,
+            const SkPath* outline, const SkPath* revealClip)
+            : DrawOp(NULL), mTransformXY(transformXY), mTransformZ(transformZ), mAlpha(alpha),
+            mFallbackWidth(fallbackWidth), mFallbackHeight(fallbackHeight),
+            mOutline(outline), mRevealClip(revealClip) {}
 
     virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty) {
-        if (mOutline->isEmpty()) {
-            SkPath fakeOutline;
-            fakeOutline.addRect(0, 0, mFallbackWidth, mFallbackHeight);
-            return renderer.drawShadow(mTransformXY, mTransformZ, mAlpha, &fakeOutline);
+        SkPath casterPerimeter;
+        if (!mOutline || mOutline->isEmpty()) {
+            casterPerimeter.addRect(0, 0, mFallbackWidth, mFallbackHeight);
+        } else {
+            casterPerimeter = *mOutline;
         }
 
-        return renderer.drawShadow(mTransformXY, mTransformZ, mAlpha, mOutline);
+        if (mRevealClip) {
+            // intersect the outline with the convex reveal clip
+            Op(casterPerimeter, *mRevealClip, kIntersect_PathOp, &casterPerimeter);
+        }
+
+        return renderer.drawShadow(mTransformXY, mTransformZ, mAlpha, &casterPerimeter);
     }
 
     virtual void output(int level, uint32_t logFlags) const {
@@ -1576,9 +1585,12 @@
     const mat4 mTransformXY;
     const mat4 mTransformZ;
     const float mAlpha;
-    const SkPath* mOutline;
     const float mFallbackWidth;
     const float mFallbackHeight;
+
+    // these point at convex SkPaths owned by RenderProperties, or null
+    const SkPath* mOutline;
+    const SkPath* mRevealClip;
 };
 
 class DrawLayerOp : public DrawOp {
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index cdd789d..d808735 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -1919,6 +1919,7 @@
     // will be performed by the display list itself
     if (displayList && displayList->isRenderable()) {
         // compute 3d ordering
+        displayList->updateProperties();
         displayList->computeOrdering();
         if (CC_UNLIKELY(mCaches.drawDeferDisabled)) {
             status = startFrame();
@@ -3202,7 +3203,7 @@
 }
 
 status_t OpenGLRenderer::drawShadow(const mat4& casterTransformXY, const mat4& casterTransformZ,
-        float casterAlpha, const SkPath* casterOutline) {
+        float casterAlpha, const SkPath* casterPerimeter) {
     if (currentSnapshot()->isIgnored()) return DrawGlInfo::kStatusDone;
 
     // TODO: use quickRejectWithScissor. For now, always force enable scissor.
@@ -3214,7 +3215,7 @@
     // tessellate caster outline into a 2d polygon
     Vector<Vertex> casterVertices2d;
     const float casterRefinementThresholdSquared = 20.0f; // TODO: experiment with this value
-    PathTessellator::approximatePathOutlineVertices(*casterOutline,
+    PathTessellator::approximatePathOutlineVertices(*casterPerimeter,
             casterRefinementThresholdSquared, casterVertices2d);
 
     if (casterVertices2d.size() == 0) {
@@ -3256,7 +3257,7 @@
     // We only have ortho projection, so we can just ignore the Z in caster for
     // simple rejection calculation.
     Rect localClip = mSnapshot->getLocalClip();
-    Rect casterBounds(casterOutline->getBounds());
+    Rect casterBounds(casterPerimeter->getBounds());
     casterTransformXY.mapRect(casterBounds);
 
     bool isCasterOpaque = (casterAlpha == 1.0f);
diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h
index 059c64f..054767e 100644
--- a/libs/hwui/OpenGLRenderer.h
+++ b/libs/hwui/OpenGLRenderer.h
@@ -210,7 +210,7 @@
     virtual status_t drawRects(const float* rects, int count, const SkPaint* paint);
 
     status_t drawShadow(const mat4& casterTransformXY, const mat4& casterTransformZ,
-            float casterAlpha, const SkPath* casterOutline);
+            float casterAlpha, const SkPath* casterPerimeter);
 
     virtual void resetShader();
     virtual void setupShader(SkiaShader* shader);
diff --git a/libs/hwui/Outline.h b/libs/hwui/Outline.h
index 4e496e7..530be30 100644
--- a/libs/hwui/Outline.h
+++ b/libs/hwui/Outline.h
@@ -58,7 +58,6 @@
         mShouldClip = clip;
     }
 
-
     bool willClip() const {
         // only round rect outlines can be used for clipping
         return mShouldClip && (mType == kOutlineType_RoundRect);
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index d3daec8..e39e5ae 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -152,18 +152,21 @@
             }
 
             SaveLayerOp* op = new (handler.allocator()) SaveLayerOp(
-                    0, 0, properties().getWidth(), properties().getHeight(), properties().getAlpha() * 255, saveFlags);
+                    0, 0, properties().getWidth(), properties().getHeight(),
+                    properties().getAlpha() * 255, saveFlags);
             handler(op, PROPERTY_SAVECOUNT, properties().getClipToBounds());
         }
     }
     if (clipToBoundsNeeded) {
-        ClipRectOp* op = new (handler.allocator()) ClipRectOp(0, 0,
-                properties().getWidth(), properties().getHeight(), SkRegion::kIntersect_Op);
+        ClipRectOp* op = new (handler.allocator()) ClipRectOp(
+                0, 0, properties().getWidth(), properties().getHeight(), SkRegion::kIntersect_Op);
         handler(op, PROPERTY_SAVECOUNT, properties().getClipToBounds());
     }
-    if (CC_UNLIKELY(properties().getOutline().willClip())) {
-        ClipPathOp* op = new (handler.allocator()) ClipPathOp(properties().getOutline().getPath(),
-                SkRegion::kIntersect_Op);
+
+    if (CC_UNLIKELY(properties().hasClippingPath())) {
+        // TODO: optimize for round rect/circle clipping
+        const SkPath* path = properties().getClippingPath();
+        ClipPathOp* op = new (handler.allocator()) ClipPathOp(path, SkRegion::kIntersect_Op);
         handler(op, PROPERTY_SAVECOUNT, properties().getClipToBounds());
     }
 }
@@ -408,10 +411,15 @@
                     mat4 shadowMatrixZ(casterOp->mTransformFromParent);
                     caster->applyViewPropertyTransforms(shadowMatrixZ, true);
 
+                    const SkPath* outlinePath = caster->properties().getOutline().getPath();
+                    const RevealClip& revealClip = caster->properties().getRevealClip();
+                    const SkPath* revealClipPath = revealClip.hasConvexClip()
+                            ?  revealClip.getPath() : NULL; // only pass the reveal clip's path if it's convex
+
                     DisplayListOp* shadowOp  = new (alloc) DrawShadowOp(
-                            shadowMatrixXY, shadowMatrixZ,
-                            caster->properties().getAlpha(), caster->properties().getOutline().getPath(),
-                            caster->properties().getWidth(), caster->properties().getHeight());
+                            shadowMatrixXY, shadowMatrixZ, caster->properties().getAlpha(),
+                            caster->properties().getWidth(), caster->properties().getHeight(),
+                            outlinePath, revealClipPath);
                     handler(shadowOp, PROPERTY_SAVECOUNT, properties().getClipToBounds());
                 }
 
@@ -498,7 +506,8 @@
 
     setViewProperties<T>(renderer, handler, level + 1);
 
-    bool quickRejected = properties().getClipToBounds() && renderer.quickRejectConservative(0, 0, properties().getWidth(), properties().getHeight());
+    bool quickRejected = properties().getClipToBounds()
+            && renderer.quickRejectConservative(0, 0, properties().getWidth(), properties().getHeight());
     if (!quickRejected) {
         Vector<ZDrawDisplayListOpPair> zTranslatedNodes;
         buildZSortedChildList(zTranslatedNodes);
diff --git a/libs/hwui/RenderProperties.cpp b/libs/hwui/RenderProperties.cpp
index ca291a6..3975a76 100644
--- a/libs/hwui/RenderProperties.cpp
+++ b/libs/hwui/RenderProperties.cpp
@@ -22,6 +22,8 @@
 
 #include <SkCanvas.h>
 #include <SkMatrix.h>
+#include <SkPath.h>
+#include <SkPathOps.h>
 
 #include "Matrix.h"
 
@@ -51,13 +53,15 @@
 RenderProperties::ComputedFields::ComputedFields()
         : mTransformMatrix(NULL)
         , mTransformCamera(NULL)
-        , mTransformMatrix3D(NULL) {
+        , mTransformMatrix3D(NULL)
+        , mClipPath(NULL) {
 }
 
 RenderProperties::ComputedFields::~ComputedFields() {
     delete mTransformMatrix;
     delete mTransformCamera;
     delete mTransformMatrix3D;
+    delete mClipPath;
 }
 
 RenderProperties::RenderProperties()
@@ -80,6 +84,7 @@
 
         // Update the computed fields
         updateMatrix();
+        updateClipPath();
     }
     return *this;
 }
@@ -181,5 +186,39 @@
     }
 }
 
+void RenderProperties::updateClipPath() {
+    const SkPath* outlineClipPath = mPrimitiveFields.mOutline.willClip()
+            ? mPrimitiveFields.mOutline.getPath() : NULL;
+    const SkPath* revealClipPath = mPrimitiveFields.mRevealClip.getPath();
+
+    if (!outlineClipPath && !revealClipPath) {
+        // mComputedFields.mClipPath doesn't need to be updated, since it won't be used
+        return;
+    }
+
+    if (mComputedFields.mClipPath == NULL) {
+        mComputedFields.mClipPath = new SkPath();
+    }
+    SkPath* clipPath = mComputedFields.mClipPath;
+    mComputedFields.mClipPathOp = SkRegion::kIntersect_Op;
+
+    if (outlineClipPath && revealClipPath) {
+        SkPathOp op = kIntersect_PathOp;
+        if (mPrimitiveFields.mRevealClip.isInverseClip()) {
+            op = kDifference_PathOp; // apply difference step in the Op below, instead of draw time
+        }
+
+        Op(*outlineClipPath, *revealClipPath, op, clipPath);
+    } else if (outlineClipPath) {
+        *clipPath = *outlineClipPath;
+    } else {
+        *clipPath = *revealClipPath;
+        if (mPrimitiveFields.mRevealClip.isInverseClip()) {
+            // apply difference step at draw time
+            mComputedFields.mClipPathOp = SkRegion::kDifference_Op;
+        }
+    }
+}
+
 } /* namespace uirenderer */
 } /* namespace android */
diff --git a/libs/hwui/RenderProperties.h b/libs/hwui/RenderProperties.h
index 504196d..061e469 100644
--- a/libs/hwui/RenderProperties.h
+++ b/libs/hwui/RenderProperties.h
@@ -22,8 +22,10 @@
 
 #include <SkCamera.h>
 #include <SkMatrix.h>
+#include <SkRegion.h>
 
 #include "Rect.h"
+#include "RevealClip.h"
 #include "Outline.h"
 
 #define TRANSLATION 0x0001
@@ -34,7 +36,6 @@
 
 class SkBitmap;
 class SkPaint;
-class SkRegion;
 
 namespace android {
 namespace uirenderer {
@@ -415,6 +416,10 @@
         return mPrimitiveFields.mOutline;
     }
 
+    const RevealClip& getRevealClip() const {
+        return mPrimitiveFields.mRevealClip;
+    }
+
     bool getProjectBackwards() const {
         return mPrimitiveFields.mProjectBackwards;
     }
@@ -423,10 +428,29 @@
 
     ANDROID_API void updateMatrix();
 
+    ANDROID_API void updateClipPath();
+
+    // signals that mComputedFields.mClipPath is up to date, and should be used for clipping
+    bool hasClippingPath() const {
+        return mPrimitiveFields.mOutline.willClip() || mPrimitiveFields.mRevealClip.willClip();
+    }
+
+    const SkPath* getClippingPath() const {
+        return hasClippingPath() ? mComputedFields.mClipPath : NULL;
+    }
+
+    SkRegion::Op getClippingPathOp() const {
+        return mComputedFields.mClipPathOp;
+    }
+
     Outline& mutableOutline() {
         return mPrimitiveFields.mOutline;
     }
 
+    RevealClip& mutableRevealClip() {
+        return mPrimitiveFields.mRevealClip;
+    }
+
 private:
     void onTranslationUpdate() {
         mPrimitiveFields.mMatrixDirty = true;
@@ -442,6 +466,7 @@
         PrimitiveFields();
 
         Outline mOutline;
+        RevealClip mRevealClip;
         bool mClipToBounds;
         bool mProjectBackwards;
         bool mProjectionReceiver;
@@ -483,6 +508,8 @@
         Matrix4* mTransformMatrix;
         Sk3DView* mTransformCamera;
         SkMatrix* mTransformMatrix3D;
+        SkPath* mClipPath; // TODO: remove this, create new ops for efficient/special case clipping
+        SkRegion::Op mClipPathOp;
     } mComputedFields;
 };
 
diff --git a/libs/hwui/RevealClip.h b/libs/hwui/RevealClip.h
new file mode 100644
index 0000000..ece8498
--- /dev/null
+++ b/libs/hwui/RevealClip.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2014 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 REVEALCLIP_H
+#define REVEALCLIP_H
+
+#include <SkPath.h>
+
+#include "Rect.h"
+
+namespace android {
+namespace uirenderer {
+
+class RevealClip {
+public:
+    RevealClip()
+            : mShouldClip(false)
+            , mInverseClip(false)
+            , mX(0)
+            , mY(0)
+            , mRadius(0) {}
+
+    void set(bool shouldClip, bool inverseClip, float x, float y, float radius) {
+        mShouldClip = shouldClip;
+        mInverseClip = inverseClip;
+        mX = x;
+        mY = y;
+        mRadius = radius;
+
+        mPath.rewind();
+        if (mShouldClip) {
+            mPath.addCircle(x, y, radius);
+        }
+    }
+
+    bool hasConvexClip() const {
+        return mShouldClip && !mInverseClip;
+    }
+
+    bool isInverseClip() const {
+        return mInverseClip;
+    }
+
+    bool willClip() const {
+        return mShouldClip;
+    }
+
+    const SkPath* getPath() const {
+        if (!mShouldClip) return NULL;
+
+        return &mPath;
+    }
+
+private:
+    bool mShouldClip;
+    bool mInverseClip;
+    float mX;
+    float mY;
+    float mRadius;
+    SkPath mPath;
+};
+
+} /* namespace uirenderer */
+} /* namespace android */
+
+#endif /* REVEALCLIP_H */