Round rect outline clipping

Change-Id: Iee9cf4f719f6f1917507b69189ad114fa365917b
diff --git a/libs/hwui/DeferredDisplayList.cpp b/libs/hwui/DeferredDisplayList.cpp
index 45b6624..3016814 100644
--- a/libs/hwui/DeferredDisplayList.cpp
+++ b/libs/hwui/DeferredDisplayList.cpp
@@ -28,6 +28,7 @@
 #include "DeferredDisplayList.h"
 #include "DisplayListOp.h"
 #include "OpenGLRenderer.h"
+#include "utils/MathUtils.h"
 
 #if DEBUG_DEFER
     #define DEFER_LOGD(...) ALOGD(__VA_ARGS__)
@@ -146,10 +147,6 @@
     mergeid_t mMergeId;
 };
 
-// compare alphas approximately, with a small margin
-#define NEQ_FALPHA(lhs, rhs) \
-        fabs((float)lhs - (float)rhs) > 0.001f
-
 class MergingDrawBatch : public DrawBatch {
 public:
     MergingDrawBatch(DeferInfo& deferInfo, int width, int height) :
@@ -196,7 +193,11 @@
         const DeferredDisplayState* lhs = state;
         const DeferredDisplayState* rhs = mOps[0].state;
 
-        if (NEQ_FALPHA(lhs->mAlpha, rhs->mAlpha)) return false;
+        if (!MathUtils::areEqual(lhs->mAlpha, rhs->mAlpha)) return false;
+
+        // Identical round rect clip state means both ops will clip in the same way, or not at all.
+        // As the state objects are const, we can compare their pointers to determine mergeability
+        if (lhs->mRoundRectClipState != rhs->mRoundRectClipState) return false;
 
         /* Clipping compatibility check
          *
diff --git a/libs/hwui/DeferredDisplayList.h b/libs/hwui/DeferredDisplayList.h
index fca3588..48489c2 100644
--- a/libs/hwui/DeferredDisplayList.h
+++ b/libs/hwui/DeferredDisplayList.h
@@ -64,6 +64,7 @@
     mat4 mMatrix;
     DrawModifiers mDrawModifiers;
     float mAlpha;
+    const RoundRectClipState* mRoundRectClipState;
 };
 
 class OpStatePair {
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index 4df97e6..7993c0f 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -711,6 +711,7 @@
             mSnapshot->resetTransform(-bounds.left, -bounds.top, 0.0f);
             mSnapshot->resetClip(clip.left, clip.top, clip.right, clip.bottom);
             mSnapshot->initializeViewport(bounds.getWidth(), bounds.getHeight());
+            mSnapshot->roundRectClipState = NULL;
         }
     }
 
@@ -844,6 +845,7 @@
     mSnapshot->resetTransform(-bounds.left, -bounds.top, 0.0f);
     mSnapshot->resetClip(clip.left, clip.top, clip.right, clip.bottom);
     mSnapshot->initializeViewport(bounds.getWidth(), bounds.getHeight());
+    mSnapshot->roundRectClipState = NULL;
 
     endTiling();
     debugOverdraw(false, false);
@@ -872,8 +874,6 @@
 
     // Change the ortho projection
     glViewport(0, 0, bounds.getWidth(), bounds.getHeight());
-
-
     return true;
 }
 
@@ -892,7 +892,7 @@
 
     bool clipRequired = false;
     calculateQuickRejectForScissor(rect.left, rect.top, rect.right, rect.bottom,
-            &clipRequired, false); // safely ignore return, should never be rejected
+            &clipRequired, NULL, false); // safely ignore return, should never be rejected
     mCaches.setScissorEnabled(mScissorOptimizationDisabled || clipRequired);
 
     if (fboLayer) {
@@ -1367,6 +1367,9 @@
     state.mMatrix.load(*currentMatrix);
     state.mDrawModifiers = mDrawModifiers;
     state.mAlpha = currentSnapshot()->alpha;
+
+    // always store/restore, since it's just a pointer
+    state.mRoundRectClipState = currentSnapshot()->roundRectClipState;
     return false;
 }
 
@@ -1374,6 +1377,7 @@
     setMatrix(state.mMatrix);
     mSnapshot->alpha = state.mAlpha;
     mDrawModifiers = state.mDrawModifiers;
+    mSnapshot->roundRectClipState = state.mRoundRectClipState;
 
     if (state.mClipValid && !skipClipRestore) {
         mSnapshot->setClip(state.mClip.left, state.mClip.top,
@@ -1449,7 +1453,7 @@
 
             mCaches.stencil.enableWrite();
 
-            // Clear the stencil but first make sure we restrict drawing
+            // Clear and update the stencil, but first make sure we restrict drawing
             // to the region's bounds
             bool resetScissor = mCaches.enableScissor();
             if (resetScissor) {
@@ -1457,7 +1461,10 @@
                 setScissorFromClip();
             }
             mCaches.stencil.clear();
-            if (resetScissor) mCaches.disableScissor();
+
+            // stash and disable the outline clip state, since stencil doesn't account for outline
+            bool storedSkipOutlineClip = mSkipOutlineClip;
+            mSkipOutlineClip = true;
 
             SkPaint paint;
             paint.setColor(0xff000000);
@@ -1470,6 +1477,8 @@
             // 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);
+            if (resetScissor) mCaches.disableScissor();
+            mSkipOutlineClip = storedSkipOutlineClip;
 
             mCaches.stencil.enableTest();
 
@@ -1494,7 +1503,6 @@
  */
 bool OpenGLRenderer::quickRejectSetupScissor(float left, float top, float right, float bottom,
         const SkPaint* paint) {
-    bool clipRequired = false;
     bool snapOut = paint && paint->isAntiAlias();
 
     if (paint && paint->getStyle() != SkPaint::kFill_Style) {
@@ -1505,13 +1513,17 @@
         bottom += outset;
     }
 
-    if (calculateQuickRejectForScissor(left, top, right, bottom, &clipRequired, snapOut)) {
+    bool clipRequired = false;
+    bool roundRectClipRequired = false;
+    if (calculateQuickRejectForScissor(left, top, right, bottom,
+            &clipRequired, &roundRectClipRequired, snapOut)) {
         return true;
     }
 
     if (!isRecording()) {
         // not quick rejected, so enable the scissor if clipRequired
         mCaches.setScissorEnabled(mScissorOptimizationDisabled || clipRequired);
+        mSkipOutlineClip = !roundRectClipRequired;
     }
     return false;
 }
@@ -1668,6 +1680,18 @@
 
 void OpenGLRenderer::setupDrawProgram() {
     useProgram(mCaches.programCache.get(mDescription));
+    if (mDescription.hasRoundRectClip) {
+        // TODO: avoid doing this repeatedly, stashing state pointer in program
+        const RoundRectClipState* state = mSnapshot->roundRectClipState;
+        const Rect& innerRect = state->outlineInnerRect;
+        glUniform4f(mCaches.currentProgram->getUniform("roundRectInnerRectLTRB"),
+                innerRect.left,  innerRect.top,
+                innerRect.right,  innerRect.bottom);
+        glUniform1f(mCaches.currentProgram->getUniform("roundRectRadius"),
+                state->outlineRadius);
+        glUniformMatrix4fv(mCaches.currentProgram->getUniform("roundRectInvTransform"),
+                1, GL_FALSE, &state->matrix.data[0]);
+    }
 }
 
 void OpenGLRenderer::setupDrawDirtyRegionsDisabled() {
@@ -2902,7 +2926,7 @@
 
     bool clipRequired = false;
     const bool rejected = calculateQuickRejectForScissor(x, y,
-            x + layer->layer.getWidth(), y + layer->layer.getHeight(), &clipRequired, false);
+            x + layer->layer.getWidth(), y + layer->layer.getHeight(), &clipRequired, NULL, false);
 
     if (rejected) {
         if (transform && !transform->isIdentity()) {
@@ -3433,6 +3457,13 @@
 
 void OpenGLRenderer::chooseBlending(bool blend, SkXfermode::Mode mode,
         ProgramDescription& description, bool swapSrcDst) {
+
+    if (mSnapshot->roundRectClipState != NULL /*&& !mSkipOutlineClip*/) {
+        blend = true;
+        mDescription.hasRoundRectClip = true;
+    }
+    mSkipOutlineClip = true;
+
     if (mCountOverdraw) {
         if (!mCaches.blend) glEnable(GL_BLEND);
         if (mCaches.lastSrcMode != GL_ONE || mCaches.lastDstMode != GL_ONE) {
diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h
index b58b817..f70ae58 100644
--- a/libs/hwui/OpenGLRenderer.h
+++ b/libs/hwui/OpenGLRenderer.h
@@ -993,6 +993,8 @@
     bool mCountOverdraw;
     float mOverdraw;
 
+    bool mSkipOutlineClip;
+
     friend class DisplayListRenderer;
     friend class Layer;
     friend class TextSetupFunctor;
diff --git a/libs/hwui/Outline.h b/libs/hwui/Outline.h
index 530be30..5c24335 100644
--- a/libs/hwui/Outline.h
+++ b/libs/hwui/Outline.h
@@ -58,11 +58,24 @@
         mShouldClip = clip;
     }
 
+    bool getShouldClip() const {
+        return mShouldClip;
+    }
+
     bool willClip() const {
         // only round rect outlines can be used for clipping
         return mShouldClip && (mType == kOutlineType_RoundRect);
     }
 
+    bool getAsRoundRect(Rect* outRect, float* outRadius) const {
+        if (mType == kOutlineType_RoundRect) {
+            outRect->set(mBounds);
+            *outRadius = mRadius;
+            return true;
+        }
+        return false;
+    }
+
     const SkPath* getPath() const {
         if (mType == kOutlineType_None) return NULL;
 
diff --git a/libs/hwui/Program.h b/libs/hwui/Program.h
index 33c91b3..3e191d0 100644
--- a/libs/hwui/Program.h
+++ b/libs/hwui/Program.h
@@ -45,17 +45,18 @@
 #define COLOR_COMPONENT_THRESHOLD 1.0f
 #define COLOR_COMPONENT_INV_THRESHOLD 0.0f
 
-#define PROGRAM_KEY_TEXTURE 0x1
-#define PROGRAM_KEY_A8_TEXTURE 0x2
-#define PROGRAM_KEY_BITMAP 0x4
-#define PROGRAM_KEY_GRADIENT 0x8
-#define PROGRAM_KEY_BITMAP_FIRST 0x10
-#define PROGRAM_KEY_COLOR_MATRIX 0x20
-#define PROGRAM_KEY_COLOR_BLEND 0x40
-#define PROGRAM_KEY_BITMAP_NPOT 0x80
-#define PROGRAM_KEY_SWAP_SRC_DST 0x2000
+#define PROGRAM_KEY_TEXTURE             0x01
+#define PROGRAM_KEY_A8_TEXTURE          0x02
+#define PROGRAM_KEY_BITMAP              0x04
+#define PROGRAM_KEY_GRADIENT            0x08
+#define PROGRAM_KEY_BITMAP_FIRST        0x10
+#define PROGRAM_KEY_COLOR_MATRIX        0x20
+#define PROGRAM_KEY_COLOR_BLEND         0x40
+#define PROGRAM_KEY_BITMAP_NPOT         0x80
 
-#define PROGRAM_KEY_BITMAP_WRAPS_MASK 0x600
+#define PROGRAM_KEY_SWAP_SRC_DST      0x2000
+
+#define PROGRAM_KEY_BITMAP_WRAPS_MASK  0x600
 #define PROGRAM_KEY_BITMAP_WRAPT_MASK 0x1800
 
 // Encode the xfermodes on 6 bits
@@ -83,6 +84,7 @@
 
 #define PROGRAM_HAS_DEBUG_HIGHLIGHT 42
 #define PROGRAM_EMULATE_STENCIL 43
+#define PROGRAM_HAS_ROUND_RECT_CLIP 44
 
 ///////////////////////////////////////////////////////////////////////////////
 // Types
@@ -158,6 +160,7 @@
 
     bool hasDebugHighlight;
     bool emulateStencil;
+    bool hasRoundRectClip;
 
     /**
      * Resets this description. All fields are reset back to the default
@@ -198,6 +201,8 @@
         gamma = 2.2f;
 
         hasDebugHighlight = false;
+        emulateStencil = false;
+        hasRoundRectClip = false;
     }
 
     /**
@@ -264,6 +269,7 @@
         if (hasColors) key |= programid(0x1) << PROGRAM_HAS_COLORS;
         if (hasDebugHighlight) key |= programid(0x1) << PROGRAM_HAS_DEBUG_HIGHLIGHT;
         if (emulateStencil) key |= programid(0x1) << PROGRAM_EMULATE_STENCIL;
+        if (hasRoundRectClip) key |= programid(0x1) << PROGRAM_HAS_ROUND_RECT_CLIP;
         return key;
     }
 
diff --git a/libs/hwui/ProgramCache.cpp b/libs/hwui/ProgramCache.cpp
index 6d50410..f451690 100644
--- a/libs/hwui/ProgramCache.cpp
+++ b/libs/hwui/ProgramCache.cpp
@@ -58,6 +58,8 @@
 const char* gVS_Header_Uniforms_HasBitmap =
         "uniform mat4 textureTransform;\n"
         "uniform mediump vec2 textureDimension;\n";
+const char* gVS_Header_Uniforms_HasRoundRectClip =
+        "uniform mat4 roundRectInvTransform;\n";
 const char* gVS_Header_Varyings_HasTexture =
         "varying vec2 outTexCoords;\n";
 const char* gVS_Header_Varyings_HasColors =
@@ -85,6 +87,8 @@
         "varying highp vec2 sweep;\n"
         "varying vec2 ditherTexCoords;\n",
 };
+const char* gVS_Header_Varyings_HasRoundRectClip =
+        "varying vec2 roundRectPos;\n";
 const char* gVS_Main =
         "\nvoid main(void) {\n";
 const char* gVS_Main_OutTexCoords =
@@ -115,9 +119,12 @@
 const char* gVS_Main_OutBitmapTexCoords =
         "    outBitmapTexCoords = (textureTransform * position).xy * textureDimension;\n";
 const char* gVS_Main_Position =
-        "    gl_Position = projection * transform * position;\n";
+        "    vec4 transformedPosition = projection * transform * position;\n"
+        "    gl_Position = transformedPosition;\n";
 const char* gVS_Main_AAVertexShape =
         "    alpha = vtxAlpha;\n";
+const char* gVS_Main_HasRoundRectClip =
+        "    roundRectPos = (roundRectInvTransform * transformedPosition).xy;\n";
 const char* gVS_Footer =
         "}\n\n";
 
@@ -160,6 +167,10 @@
 const char* gFS_Uniforms_Gamma =
         "uniform float gamma;\n";
 
+const char* gFS_Uniforms_HasRoundRectClip =
+        "uniform vec4 roundRectInnerRectLTRB;\n"
+        "uniform float roundRectRadius;\n";
+
 const char* gFS_Main =
         "\nvoid main(void) {\n"
         "    lowp vec4 fragColor;\n";
@@ -318,6 +329,15 @@
         // PorterDuff
         "    fragColor = blendColors(colorBlend, fragColor);\n"
 };
+
+// Note: LTRB -> xyzw
+const char* gFS_Main_FragColor_HasRoundRectClip =
+        "    mediump vec2 fragToLT = roundRectInnerRectLTRB.xy - roundRectPos;\n"
+        "    mediump vec2 fragFromRB = roundRectPos - roundRectInnerRectLTRB.zw;\n"
+        "    mediump vec2 dist = max(max(fragToLT, fragFromRB), vec2(0.0, 0.0));\n"
+        "    mediump float linearDist = roundRectRadius - length(dist);\n"
+        "    gl_FragColor *= clamp(linearDist, 0.0, 1.0);\n";
+
 const char* gFS_Main_DebugHighlight =
         "    gl_FragColor.rgb = vec3(0.0, gl_FragColor.a, 0.0);\n";
 const char* gFS_Main_EmulateStencil =
@@ -462,6 +482,9 @@
     if (description.hasBitmap) {
         shader.append(gVS_Header_Uniforms_HasBitmap);
     }
+    if (description.hasRoundRectClip) {
+        shader.append(gVS_Header_Uniforms_HasRoundRectClip);
+    }
     // Varyings
     if (description.hasTexture || description.hasExternalTexture) {
         shader.append(gVS_Header_Varyings_HasTexture);
@@ -478,6 +501,9 @@
     if (description.hasBitmap) {
         shader.append(gVS_Header_Varyings_HasBitmap);
     }
+    if (description.hasRoundRectClip) {
+        shader.append(gVS_Header_Varyings_HasRoundRectClip);
+    }
 
     // Begin the shader
     shader.append(gVS_Main); {
@@ -500,6 +526,9 @@
         if (description.hasGradient) {
             shader.append(gVS_Main_OutGradient[gradientIndex(description)]);
         }
+        if (description.hasRoundRectClip) {
+            shader.append(gVS_Main_HasRoundRectClip);
+        }
     }
     // End the shader
     shader.append(gVS_Footer);
@@ -546,6 +575,9 @@
     if (description.hasBitmap) {
         shader.append(gVS_Header_Varyings_HasBitmap);
     }
+    if (description.hasRoundRectClip) {
+        shader.append(gVS_Header_Varyings_HasRoundRectClip);
+    }
 
     // Uniforms
     int modulateOp = MODULATE_OP_NO_MODULATE;
@@ -568,11 +600,18 @@
     if (description.hasGammaCorrection) {
         shader.append(gFS_Uniforms_Gamma);
     }
+    if (description.hasRoundRectClip) {
+        shader.append(gFS_Uniforms_HasRoundRectClip);
+    }
 
     // Optimization for common cases
-    if (!description.isAA && !blendFramebuffer && !description.hasColors &&
-            description.colorOp == ProgramDescription::kColorNone &&
-            !description.hasDebugHighlight && !description.emulateStencil) {
+    if (!description.isAA
+            && !blendFramebuffer
+            && !description.hasColors
+            && description.colorOp == ProgramDescription::kColorNone
+            && !description.hasDebugHighlight
+            && !description.emulateStencil
+            && !description.hasRoundRectClip) {
         bool fast = false;
 
         const bool noShader = !description.hasGradient && !description.hasBitmap;
@@ -722,6 +761,9 @@
         if (description.hasColors) {
             shader.append(gFS_Main_FragColor_HasColors);
         }
+        if (description.hasRoundRectClip) {
+            shader.append(gFS_Main_FragColor_HasRoundRectClip);
+        }
         if (description.hasDebugHighlight) {
             shader.append(gFS_Main_DebugHighlight);
         }
diff --git a/libs/hwui/Rect.h b/libs/hwui/Rect.h
index f38d8b7..2ddbbd7 100644
--- a/libs/hwui/Rect.h
+++ b/libs/hwui/Rect.h
@@ -234,7 +234,7 @@
         bottom = ceilf(bottom);
     }
 
-    void dump(const char* label) const {
+    void dump(const char* label = NULL) const {
         ALOGD("%s[l=%f t=%f r=%f b=%f]", label ? label : "Rect", left, top, right, bottom);
     }
 
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index fba482d..ad48f44 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -653,6 +653,10 @@
     bool quickRejected = properties().getClipToBounds()
             && renderer.quickRejectConservative(0, 0, properties().getWidth(), properties().getHeight());
     if (!quickRejected) {
+        if (mProperties.getOutline().willClip()) {
+            renderer.setClippingOutline(alloc, &(mProperties.getOutline()));
+        }
+
         Vector<ZDrawDisplayListOpPair> zTranslatedNodes;
         buildZSortedChildList(zTranslatedNodes);
 
diff --git a/libs/hwui/Snapshot.cpp b/libs/hwui/Snapshot.cpp
index 029b56d..80f7eca 100644
--- a/libs/hwui/Snapshot.cpp
+++ b/libs/hwui/Snapshot.cpp
@@ -20,6 +20,8 @@
 
 #include <SkCanvas.h>
 
+#include "utils/MathUtils.h"
+
 namespace android {
 namespace uirenderer {
 
@@ -34,7 +36,8 @@
         , fbo(0)
         , invisible(false)
         , empty(false)
-        , alpha(1.0f) {
+        , alpha(1.0f)
+        , roundRectClipState(NULL) {
     transform = &mTransformRoot;
     clipRect = &mClipRectRoot;
     region = NULL;
@@ -53,8 +56,8 @@
         , invisible(s->invisible)
         , empty(false)
         , alpha(s->alpha)
+        , roundRectClipState(s->roundRectClipState)
         , mViewportData(s->mViewportData) {
-
     if (saveFlags & SkCanvas::kMatrix_SaveFlag) {
         mTransformRoot.load(*s->transform);
         transform = &mTransformRoot;
@@ -204,6 +207,49 @@
 }
 
 ///////////////////////////////////////////////////////////////////////////////
+// Clipping outline
+///////////////////////////////////////////////////////////////////////////////
+
+void Snapshot::setClippingOutline(LinearAllocator& allocator, const Outline* outline) {
+    Rect bounds;
+    float radius;
+    if (!outline->getAsRoundRect(&bounds, &radius)) return; // only RR supported
+
+    if (!MathUtils::isPositive(radius)) return; // leave clipping up to rect clipping
+
+    RoundRectClipState* state = new (allocator) RoundRectClipState;
+
+    // store the inverse drawing matrix
+    Matrix4 outlineDrawingMatrix;
+    outlineDrawingMatrix.load(getOrthoMatrix());
+    outlineDrawingMatrix.multiply(*transform);
+    state->matrix.loadInverse(outlineDrawingMatrix);
+
+    // compute area under rounded corners - only draws overlapping these rects need to be clipped
+    for (int i = 0 ; i < 4; i++) {
+        state->dangerRects[i] = bounds;
+    }
+    state->dangerRects[0].bottom = state->dangerRects[1].bottom = bounds.top + radius;
+    state->dangerRects[0].right = state->dangerRects[2].right = bounds.left + radius;
+    state->dangerRects[1].left = state->dangerRects[3].left = bounds.right - radius;
+    state->dangerRects[2].top = state->dangerRects[3].top = bounds.bottom - radius;
+    for (int i = 0; i < 4; i++) {
+        transform->mapRect(state->dangerRects[i]);
+
+        // round danger rects out as though they are AA geometry (since they essentially are)
+        state->dangerRects[i].snapGeometryToPixelBoundaries(true);
+    }
+
+    // store RR area
+    bounds.inset(radius);
+    state->outlineInnerRect = bounds;
+    state->outlineRadius = radius;
+
+    // store as immutable so, for this frame, pointer uniquely identifies this bundle of shader info
+    roundRectClipState = state;
+}
+
+///////////////////////////////////////////////////////////////////////////////
 // Queries
 ///////////////////////////////////////////////////////////////////////////////
 
diff --git a/libs/hwui/Snapshot.h b/libs/hwui/Snapshot.h
index e9ab1ff..435736c 100644
--- a/libs/hwui/Snapshot.h
+++ b/libs/hwui/Snapshot.h
@@ -20,6 +20,7 @@
 #include <GLES2/gl2.h>
 #include <GLES2/gl2ext.h>
 
+#include <utils/LinearAllocator.h>
 #include <utils/RefBase.h>
 #include <ui/Region.h>
 
@@ -27,12 +28,40 @@
 
 #include "Layer.h"
 #include "Matrix.h"
+#include "Outline.h"
 #include "Rect.h"
+#include "utils/Macros.h"
 
 namespace android {
 namespace uirenderer {
 
 /**
+ * Temporary structure holding information for a single outline clip.
+ *
+ * These structures are treated as immutable once created, and only exist for a single frame, which
+ * is why they may only be allocated with a LinearAllocator.
+ */
+class RoundRectClipState {
+public:
+    /** static void* operator new(size_t size); PURPOSELY OMITTED, allocator only **/
+    static void* operator new(size_t size, LinearAllocator& allocator) {
+        return allocator.alloc(size);
+    }
+
+    bool areaRequiresRoundRectClip(const Rect& rect) const {
+        return rect.intersects(dangerRects[0])
+                || rect.intersects(dangerRects[1])
+                || rect.intersects(dangerRects[2])
+                || rect.intersects(dangerRects[3]);
+    }
+
+    Matrix4 matrix;
+    Rect dangerRects[4];
+    Rect outlineInnerRect;
+    float outlineRadius;
+};
+
+/**
  * A snapshot holds information about the current state of the rendering
  * surface. A snapshot is usually created whenever the user calls save()
  * and discarded when the user calls restore(). Once a snapshot is created,
@@ -133,6 +162,11 @@
     const Matrix4& getOrthoMatrix() const { return mViewportData.mOrthoMatrix; }
 
     /**
+     * Sets (and replaces) the current clipping outline
+     */
+    void setClippingOutline(LinearAllocator& allocator, const Outline* outline);
+
+    /**
      * Indicates whether this snapshot should be ignored. A snapshot
      * is typicalled ignored if its layer is invisible or empty.
      */
@@ -225,6 +259,14 @@
      */
     float alpha;
 
+    /**
+     * Current clipping round rect.
+     *
+     * Points to data not owned by the snapshot, and may only be replaced by subsequent RR clips,
+     * never modified.
+     */
+    const RoundRectClipState* roundRectClipState;
+
     void dump() const;
 
 private:
diff --git a/libs/hwui/StatefulBaseRenderer.cpp b/libs/hwui/StatefulBaseRenderer.cpp
index aa83e20..7d299f0 100644
--- a/libs/hwui/StatefulBaseRenderer.cpp
+++ b/libs/hwui/StatefulBaseRenderer.cpp
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+#define LOG_TAG "OpenGLRenderer"
+
 #include <SkCanvas.h>
 
 #include "StatefulBaseRenderer.h"
@@ -180,6 +182,10 @@
     return !mSnapshot->clipRect->isEmpty();
 }
 
+void StatefulBaseRenderer::setClippingOutline(LinearAllocator& allocator, const Outline* outline) {
+    mSnapshot->setClippingOutline(allocator, outline);
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 // Quick Rejection
 ///////////////////////////////////////////////////////////////////////////////
@@ -195,7 +201,9 @@
  *         See Rect::snapGeometryToPixelBoundaries()
  */
 bool StatefulBaseRenderer::calculateQuickRejectForScissor(float left, float top,
-        float right, float bottom, bool* clipRequired, bool snapOut) const {
+        float right, float bottom,
+        bool* clipRequired, bool* roundRectClipRequired,
+        bool snapOut) const {
     if (mSnapshot->isIgnored() || bottom <= top || right <= left) {
         return true;
     }
@@ -210,7 +218,15 @@
     if (!clipRect.intersects(r)) return true;
 
     // clip is required if geometry intersects clip rect
-    if (clipRequired) *clipRequired = !clipRect.contains(r);
+    if (clipRequired) {
+        *clipRequired = !clipRect.contains(r);
+    }
+
+    // round rect clip is required if RR clip exists, and geometry intersects its corners
+    if (roundRectClipRequired) {
+        *roundRectClipRequired = mSnapshot->roundRectClipState != NULL
+                && mSnapshot->roundRectClipState->areaRequiresRoundRectClip(r);
+    }
     return false;
 }
 
diff --git a/libs/hwui/StatefulBaseRenderer.h b/libs/hwui/StatefulBaseRenderer.h
index 9fbf2ca..2e7f279 100644
--- a/libs/hwui/StatefulBaseRenderer.h
+++ b/libs/hwui/StatefulBaseRenderer.h
@@ -88,6 +88,14 @@
     virtual bool clipPath(const SkPath* path, SkRegion::Op op);
     virtual bool clipRegion(const SkRegion* region, SkRegion::Op op);
 
+    /**
+     * Does not support different clipping Ops (that is, every call to setClippingOutline is
+     * effectively using SkRegion::kReplaceOp)
+     *
+     * The clipping outline is independent from the regular clip.
+     */
+    void setClippingOutline(LinearAllocator& allocator, const Outline* outline);
+
 protected:
     const Rect& getRenderTargetClipBounds() const { return mSnapshot->getRenderTargetClip(); }
 
@@ -106,7 +114,7 @@
 
     // Clip
     bool calculateQuickRejectForScissor(float left, float top, float right, float bottom,
-            bool* clipRequired, bool snapOut) const;
+            bool* clipRequired, bool* roundRectClipRequired, bool snapOut) const;
 
     /**
      * Called just after a restore has occurred. The 'removed' snapshot popped from the stack,
diff --git a/libs/hwui/utils/MathUtils.h b/libs/hwui/utils/MathUtils.h
index 1a7082b..997acde2 100644
--- a/libs/hwui/utils/MathUtils.h
+++ b/libs/hwui/utils/MathUtils.h
@@ -34,6 +34,10 @@
         return value >= gNonZeroEpsilon;
     }
 
+    inline static bool areEqual(float valueA, float valueB) {
+        return isZero(valueA - valueB);
+    }
+
     inline static int min(int a, int b) {
         return a < b ? a : b;
     }