Implement clipRect with a transform, clipRegion & clipPath
Bug #7146141

When non-rectangular clipping occurs in a layer the render buffer
used as the stencil buffer is not cached. If this happens on a
View's hardware layer the render buffer will live for as long
as the layer is bound to the view. When a stencil buffer is
required because of a call to Canvas.saveLayer() it will be allocated
on every frame. A future change will address this problem.

If "show GPU overdraw" is enabled, non-rectangular clips are not
supported anymore and we fall back to rectangular clips instead.
This is a limitation imposed by OpenGL ES that cannot be worked
around at this time.

This change also improves the Matrix4 implementation to easily
detect when a rect remains a rect after transform.

Change-Id: I0e69fb901792d38bc0c4ca1bf9fdb02d7db415b9
diff --git a/libs/hwui/Caches.h b/libs/hwui/Caches.h
index 628d8a0..ae188be 100644
--- a/libs/hwui/Caches.h
+++ b/libs/hwui/Caches.h
@@ -270,9 +270,7 @@
     GammaFontRenderer* fontRenderer;
 
     Dither dither;
-#if STENCIL_BUFFER_SIZE
     Stencil stencil;
-#endif
 
     // Debug methods
     PFNGLINSERTEVENTMARKEREXTPROC eventMark;
diff --git a/libs/hwui/Layer.cpp b/libs/hwui/Layer.cpp
index ee1d391..1d85b70 100644
--- a/libs/hwui/Layer.cpp
+++ b/libs/hwui/Layer.cpp
@@ -41,6 +41,7 @@
     renderer = NULL;
     displayList = NULL;
     fbo = 0;
+    stencil = 0;
     debugDrawUpdate = false;
     Caches::getInstance().resourceCache.incrementRefcount(this);
 }
@@ -53,9 +54,22 @@
     deleteTexture();
 }
 
-void Layer::removeFbo() {
+void Layer::removeFbo(bool flush) {
+    if (stencil) {
+        // TODO: recycle & cache instead of simply deleting
+        GLuint previousFbo;
+        glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint*) &previousFbo);
+        if (fbo != previousFbo) glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+        glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, 0);
+        if (fbo != previousFbo) glBindFramebuffer(GL_FRAMEBUFFER, previousFbo);
+
+        glDeleteRenderbuffers(1, &stencil);
+        stencil = 0;
+    }
+
     if (fbo) {
-        LayerRenderer::flushLayer(this);
+        if (flush) LayerRenderer::flushLayer(this);
+        // If put fails the cache will delete the FBO
         Caches::getInstance().fboCache.put(fbo);
         fbo = 0;
     }
@@ -75,7 +89,5 @@
     }
 }
 
-
-
 }; // namespace uirenderer
 }; // namespace android
diff --git a/libs/hwui/Layer.h b/libs/hwui/Layer.h
index 181eb6c..9ef4894 100644
--- a/libs/hwui/Layer.h
+++ b/libs/hwui/Layer.h
@@ -48,7 +48,12 @@
     Layer(const uint32_t layerWidth, const uint32_t layerHeight);
     ~Layer();
 
-    void removeFbo();
+    /**
+     * Calling this method will remove (either by recycling or
+     * destroying) the associated FBO, if present, and any render
+     * buffer (stencil for instance.)
+     */
+    void removeFbo(bool flush = true);
 
     /**
      * Sets this layer's region to a rectangle. Computes the appropriate
@@ -134,6 +139,14 @@
         return fbo;
     }
 
+    inline void setStencilRenderBuffer(GLuint renderBuffer) {
+        this->stencil = renderBuffer;
+    }
+
+    inline GLuint getStencilRenderBuffer() {
+        return stencil;
+    }
+
     inline GLuint getTexture() {
         return texture.id;
     }
@@ -212,10 +225,6 @@
         texture.id = 0;
     }
 
-    inline void deleteFbo() {
-        if (fbo) glDeleteFramebuffers(1, &fbo);
-    }
-
     inline void allocateTexture(GLenum format, GLenum storage) {
 #if DEBUG_LAYERS
         ALOGD("  Allocate layer: %dx%d", getWidth(), getHeight());
@@ -275,6 +284,12 @@
     GLuint fbo;
 
     /**
+     * Name of the render buffer used as the stencil buffer. If the
+     * name is 0, this layer does not have a stencil buffer.
+     */
+    GLuint stencil;
+
+    /**
      * Indicates whether this layer has been used already.
      */
     bool empty;
diff --git a/libs/hwui/LayerRenderer.cpp b/libs/hwui/LayerRenderer.cpp
index 3484d41..ba59bb39 100644
--- a/libs/hwui/LayerRenderer.cpp
+++ b/libs/hwui/LayerRenderer.cpp
@@ -100,13 +100,21 @@
 }
 
 ///////////////////////////////////////////////////////////////////////////////
-// Dirty region tracking
+// Layer support
 ///////////////////////////////////////////////////////////////////////////////
 
 bool LayerRenderer::hasLayer() {
     return true;
 }
 
+void LayerRenderer::ensureStencilBuffer() {
+    attachStencilBufferToLayer(mLayer);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Dirty region tracking
+///////////////////////////////////////////////////////////////////////////////
+
 Region* LayerRenderer::getRegion() {
     if (getSnapshot()->flags & Snapshot::kFlagFboTarget) {
         return OpenGLRenderer::getRegion();
diff --git a/libs/hwui/LayerRenderer.h b/libs/hwui/LayerRenderer.h
index c44abce..7a8bdc5 100644
--- a/libs/hwui/LayerRenderer.h
+++ b/libs/hwui/LayerRenderer.h
@@ -64,6 +64,7 @@
     static void flushLayer(Layer* layer);
 
 protected:
+    virtual void ensureStencilBuffer();
     virtual bool hasLayer();
     virtual Region* getRegion();
     virtual GLint getTargetFbo();
diff --git a/libs/hwui/Matrix.cpp b/libs/hwui/Matrix.cpp
index a924362..79fae2b 100644
--- a/libs/hwui/Matrix.cpp
+++ b/libs/hwui/Matrix.cpp
@@ -30,6 +30,16 @@
 namespace android {
 namespace uirenderer {
 
+///////////////////////////////////////////////////////////////////////////////
+// Defines
+///////////////////////////////////////////////////////////////////////////////
+
+static const float EPSILON = 0.0000001f;
+
+///////////////////////////////////////////////////////////////////////////////
+// Matrix
+///////////////////////////////////////////////////////////////////////////////
+
 void Matrix4::loadIdentity() {
     data[kScaleX]       = 1.0f;
     data[kSkewY]        = 0.0f;
@@ -51,44 +61,91 @@
     data[kTranslateZ]   = 0.0f;
     data[kPerspective2] = 1.0f;
 
-    mIsIdentity = true;
-    mSimpleMatrix = true;
+    mType = kTypeIdentity | kTypeRectToRect;
+}
+
+static bool isZero(float f) {
+    return fabs(f) <= EPSILON;
+}
+
+uint32_t Matrix4::getType() const {
+    if (mType & kTypeUnknown) {
+        mType = kTypeIdentity;
+
+        if (data[kPerspective0] != 0.0f || data[kPerspective1] != 0.0f ||
+                data[kPerspective2] != 1.0f) {
+            mType |= kTypePerspective;
+        }
+
+        if (data[kTranslateX] != 0.0f || data[kTranslateY] != 0.0f) {
+            mType |= kTypeTranslate;
+        }
+
+        float m00 = data[kScaleX];
+        float m01 = data[kSkewX];
+        float m10 = data[kSkewY];
+        float m11 = data[kScaleY];
+
+        if (m01 != 0.0f || m10 != 0.0f) {
+            mType |= kTypeAffine;
+        }
+
+        if (m00 != 1.0f || m11 != 1.0f) {
+            mType |= kTypeScale;
+        }
+
+        // The following section determines whether the matrix will preserve
+        // rectangles. For instance, a rectangle transformed by a pure
+        // translation matrix will result in a rectangle. A rectangle
+        // transformed by a 45 degrees rotation matrix is not a rectangle.
+        // If the matrix has a perspective component then we already know
+        // it doesn't preserve rectangles.
+        if (!(mType & kTypePerspective)) {
+            if ((isZero(m00) && isZero(m11) && !isZero(m01) && !isZero(m10)) ||
+                    (isZero(m01) && isZero(m10) && !isZero(m00) && !isZero(m11))) {
+                mType |= kTypeRectToRect;
+            }
+        }
+    }
+    return mType;
+}
+
+uint32_t Matrix4::getGeometryType() const {
+    return getType() & sGeometryMask;
+}
+
+bool Matrix4::rectToRect() const {
+    return getType() & kTypeRectToRect;
 }
 
 bool Matrix4::changesBounds() const {
-    return !(data[0] == 1.0f && data[1] == 0.0f && data[2] == 0.0f && data[4] == 0.0f &&
-             data[5] == 1.0f && data[6] == 0.0f && data[8] == 0.0f && data[9] == 0.0f &&
-             data[10] == 1.0f);
+    return getType() & (kTypeScale | kTypeAffine | kTypePerspective);
 }
 
 bool Matrix4::isPureTranslate() const {
-    return mSimpleMatrix && data[kScaleX] == 1.0f && data[kScaleY] == 1.0f;
+    return getGeometryType() == kTypeTranslate;
 }
 
 bool Matrix4::isSimple() const {
-    return mSimpleMatrix;
+    return getGeometryType() <= (kTypeScale | kTypeTranslate);
 }
 
 bool Matrix4::isIdentity() const {
-    return mIsIdentity;
+    return getGeometryType() == kTypeIdentity;
 }
 
 bool Matrix4::isPerspective() const {
-    return data[kPerspective0] != 0.0f || data[kPerspective1] != 0.0f ||
-            data[kPerspective2] != 1.0f;
+    return getType() & kTypePerspective;
 }
 
 void Matrix4::load(const float* v) {
     memcpy(data, v, sizeof(data));
-    // TODO: Do something smarter here
-    mSimpleMatrix = false;
-    mIsIdentity = false;
+    mType = kTypeUnknown;
 }
 
 void Matrix4::load(const Matrix4& v) {
     memcpy(data, v.data, sizeof(data));
-    mSimpleMatrix = v.mSimpleMatrix;
-    mIsIdentity = v.mIsIdentity;
+    mType = v.getType();
 }
 
 void Matrix4::load(const SkMatrix& v) {
@@ -108,8 +165,14 @@
 
     data[kScaleZ] = 1.0f;
 
-    mSimpleMatrix = (v.getType() <= (SkMatrix::kScale_Mask | SkMatrix::kTranslate_Mask));
-    mIsIdentity = v.isIdentity();
+    // NOTE: The flags are compatible between SkMatrix and this class.
+    //       However, SkMatrix::getType() does not return the flag
+    //       kRectStaysRect. The return value is masked with 0xF
+    //       so we need the extra rectStaysRect() check
+    mType = v.getType();
+    if (v.rectStaysRect()) {
+        mType |= kTypeRectToRect;
+    }
 }
 
 void Matrix4::copyTo(SkMatrix& v) const {
@@ -158,8 +221,7 @@
     data[kPerspective2] = (v.data[kScaleX] * v.data[kScaleY] -
             v.data[kSkewX] * v.data[kSkewY]) * scale;
 
-    mSimpleMatrix = v.mSimpleMatrix;
-    mIsIdentity = v.mIsIdentity;
+    mType = kTypeUnknown;
 }
 
 void Matrix4::copyTo(float* v) const {
@@ -178,7 +240,7 @@
     for (int i = 0; i < 16; i++) {
         data[i] *= v;
     }
-    mIsIdentity = false;
+    mType = kTypeUnknown;
 }
 
 void Matrix4::loadTranslate(float x, float y, float z) {
@@ -188,7 +250,7 @@
     data[kTranslateY] = y;
     data[kTranslateZ] = z;
 
-    mIsIdentity = false;
+    mType = kTypeTranslate | kTypeRectToRect;
 }
 
 void Matrix4::loadScale(float sx, float sy, float sz) {
@@ -198,7 +260,7 @@
     data[kScaleY] = sy;
     data[kScaleZ] = sz;
 
-    mIsIdentity = false;
+    mType = kTypeScale | kTypeRectToRect;
 }
 
 void Matrix4::loadSkew(float sx, float sy) {
@@ -216,8 +278,23 @@
     data[kPerspective1] = 0.0f;
     data[kPerspective2] = 1.0f;
 
-    mSimpleMatrix = false;
-    mIsIdentity = false;
+    mType = kTypeUnknown;
+}
+
+void Matrix4::loadRotate(float angle) {
+    angle *= float(M_PI / 180.0f);
+    float c = cosf(angle);
+    float s = sinf(angle);
+
+    loadIdentity();
+
+    data[kScaleX]     = c;
+    data[kSkewX]      = -s;
+
+    data[kSkewY]      = s;
+    data[kScaleY]     = c;
+
+    mType = kTypeUnknown;
 }
 
 void Matrix4::loadRotate(float angle, float x, float y, float z) {
@@ -257,8 +334,7 @@
     data[6]       =    yz * nc + xs;
     data[kScaleZ] = z * z * nc +  c;
 
-    mSimpleMatrix = false;
-    mIsIdentity = false;
+    mType = kTypeUnknown;
 }
 
 void Matrix4::loadMultiply(const Matrix4& u, const Matrix4& v) {
@@ -282,8 +358,7 @@
         set(i, 3, w);
     }
 
-    mSimpleMatrix = u.mSimpleMatrix && v.mSimpleMatrix;
-    mIsIdentity = false;
+    mType = kTypeUnknown;
 }
 
 void Matrix4::loadOrtho(float left, float right, float bottom, float top, float near, float far) {
@@ -296,13 +371,13 @@
     data[kTranslateY] = -(top + bottom) / (top - bottom);
     data[kTranslateZ] = -(far + near) / (far - near);
 
-    mIsIdentity = false;
+    mType = kTypeTranslate | kTypeScale | kTypeRectToRect;
 }
 
 #define MUL_ADD_STORE(a, b, c) a = (a) * (b) + (c)
 
 void Matrix4::mapPoint(float& x, float& y) const {
-    if (mSimpleMatrix) {
+    if (isSimple()) {
         MUL_ADD_STORE(x, data[kScaleX], data[kTranslateX]);
         MUL_ADD_STORE(y, data[kScaleY], data[kTranslateY]);
         return;
@@ -318,7 +393,7 @@
 }
 
 void Matrix4::mapRect(Rect& r) const {
-    if (mSimpleMatrix) {
+    if (isSimple()) {
         MUL_ADD_STORE(r.left, data[kScaleX], data[kTranslateX]);
         MUL_ADD_STORE(r.right, data[kScaleX], data[kTranslateX]);
         MUL_ADD_STORE(r.top, data[kScaleY], data[kTranslateY]);
@@ -376,7 +451,7 @@
 }
 
 void Matrix4::dump() const {
-    ALOGD("Matrix4[simple=%d", mSimpleMatrix);
+    ALOGD("Matrix4[simple=%d, type=0x%x", isSimple(), getType());
     ALOGD("  %f %f %f %f", data[kScaleX], data[kSkewX], data[8], data[kTranslateX]);
     ALOGD("  %f %f %f %f", data[kSkewY], data[kScaleY], data[9], data[kTranslateY]);
     ALOGD("  %f %f %f %f", data[2], data[6], data[kScaleZ], data[kTranslateZ]);
diff --git a/libs/hwui/Matrix.h b/libs/hwui/Matrix.h
index f86823d..46a5597 100644
--- a/libs/hwui/Matrix.h
+++ b/libs/hwui/Matrix.h
@@ -48,6 +48,21 @@
         kPerspective2 = 15
     };
 
+    // NOTE: The flags from kTypeIdentity to kTypePerspective
+    //       must be kept in sync with the type flags found
+    //       in SkMatrix
+    enum Type {
+        kTypeIdentity = 0,
+        kTypeTranslate = 0x1,
+        kTypeScale = 0x2,
+        kTypeAffine = 0x4,
+        kTypePerspective = 0x8,
+        kTypeRectToRect = 0x10,
+        kTypeUnknown = 0x20,
+    };
+
+    static const int sGeometryMask = 0xf;
+
     Matrix4() {
         loadIdentity();
     }
@@ -75,11 +90,14 @@
     void loadTranslate(float x, float y, float z);
     void loadScale(float sx, float sy, float sz);
     void loadSkew(float sx, float sy);
+    void loadRotate(float angle);
     void loadRotate(float angle, float x, float y, float z);
     void loadMultiply(const Matrix4& u, const Matrix4& v);
 
     void loadOrtho(float left, float right, float bottom, float top, float near, float far);
 
+    uint32_t getType() const;
+
     void multiply(const Matrix4& v) {
         Matrix4 u;
         u.loadMultiply(*this, v);
@@ -112,10 +130,14 @@
         multiply(u);
     }
 
-    bool isPureTranslate() const;
+    /**
+     * If the matrix is identity or translate and/or scale.
+     */
     bool isSimple() const;
+    bool isPureTranslate() const;
     bool isIdentity() const;
     bool isPerspective() const;
+    bool rectToRect() const;
 
     bool changesBounds() const;
 
@@ -131,8 +153,7 @@
     void dump() const;
 
 private:
-    bool mSimpleMatrix;
-    bool mIsIdentity;
+    mutable uint32_t mType;
 
     inline float get(int i, int j) const {
         return data[i * 4 + j];
@@ -141,6 +162,9 @@
     inline void set(int i, int j, float v) {
         data[i * 4 + j] = v;
     }
+
+    uint32_t getGeometryType() const;
+
 }; // class Matrix4
 
 ///////////////////////////////////////////////////////////////////////////////
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index bb1edbb..be34b40 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -837,6 +837,8 @@
         return;
     }
 
+    Layer* layer = current->layer;
+    const Rect& rect = layer->layer;
     const bool fboLayer = current->flags & Snapshot::kFlagIsFboLayer;
 
     if (fboLayer) {
@@ -844,6 +846,9 @@
 
         // Detach the texture from the FBO
         glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
+
+        layer->removeFbo(false);
+
         // Unbind current FBO and restore previous one
         glBindFramebuffer(GL_FRAMEBUFFER, previous->fbo);
         debugOverdraw(true, false);
@@ -851,9 +856,6 @@
         startTiling(previous);
     }
 
-    Layer* layer = current->layer;
-    const Rect& rect = layer->layer;
-
     if (!fboLayer && layer->getAlpha() < 255) {
         drawColorRect(rect.left, rect.top, rect.right, rect.bottom,
                 layer->getAlpha() << 24, SkXfermode::kDstIn_Mode, true);
@@ -881,17 +883,6 @@
         composeLayerRect(layer, rect, true);
     }
 
-    if (fboLayer) {
-        // Note: No need to use glDiscardFramebufferEXT() since we never
-        //       create/compose layers that are not on screen with this
-        //       code path
-        // See LayerRenderer::destroyLayer(Layer*)
-
-        // Put the FBO name back in the cache, if it doesn't fit, it will be destroyed
-        mCaches.fboCache.put(current->fbo);
-        layer->setFbo(0);
-    }
-
     dirtyClip();
 
     // Failing to add the layer to the cache should happen only if the layer is too large
@@ -1001,10 +992,14 @@
         const float texY = 1.0f / float(layer->getHeight());
         const float height = rect.getHeight();
 
+        setupDraw();
+
+        // We must get (and therefore bind) the region mesh buffer
+        // after we setup drawing in case we need to mess with the
+        // stencil buffer in setupDraw()
         TextureVertex* mesh = mCaches.getRegionMesh();
         GLsizei numQuads = 0;
 
-        setupDraw();
         setupDrawWithTexture();
         setupDrawColor(alpha, alpha, alpha, alpha);
         setupDrawColorFilter();
@@ -1089,6 +1084,25 @@
 #endif
 }
 
+void OpenGLRenderer::drawRegionRects(const SkRegion& region, int color,
+        SkXfermode::Mode mode, bool dirty) {
+    int count = 0;
+    Vector<float> rects;
+
+    SkRegion::Iterator it(region);
+    while (!it.done()) {
+        const SkIRect& r = it.rect();
+        rects.push(r.fLeft);
+        rects.push(r.fTop);
+        rects.push(r.fRight);
+        rects.push(r.fBottom);
+        count++;
+        it.next();
+    }
+
+    drawColorRects(rects.array(), count, color, mode, true, dirty);
+}
+
 void OpenGLRenderer::dirtyLayer(const float left, const float top,
         const float right, const float bottom, const mat4 transform) {
     if (hasLayer()) {
@@ -1219,6 +1233,65 @@
     }
 }
 
+void OpenGLRenderer::ensureStencilBuffer() {
+    // Thanks to the mismatch between EGL and OpenGL ES FBO we
+    // cannot attach a stencil buffer to fbo0 dynamically. Let's
+    // just hope we have one when hasLayer() returns false.
+    if (hasLayer()) {
+        attachStencilBufferToLayer(mSnapshot->layer);
+    }
+}
+
+void OpenGLRenderer::attachStencilBufferToLayer(Layer* layer) {
+    // The layer's FBO is already bound when we reach this stage
+    if (!layer->getStencilRenderBuffer()) {
+        // TODO: See Layer::removeFbo(). The stencil renderbuffer should be cached
+        GLuint buffer;
+        glGenRenderbuffers(1, &buffer);
+        glBindRenderbuffer(GL_RENDERBUFFER, buffer);
+        glRenderbufferStorage(GL_RENDERBUFFER, GL_STENCIL_INDEX8,
+                layer->getWidth(), layer->getHeight());
+        glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, buffer);
+        layer->setStencilRenderBuffer(buffer);
+    }
+}
+
+void OpenGLRenderer::setStencilFromClip() {
+    if (!mCaches.debugOverdraw) {
+        if (!mSnapshot->clipRegion->isEmpty()) {
+            // NOTE: The order here is important, we must set dirtyClip to false
+            //       before any draw call to avoid calling back into this method
+            mDirtyClip = false;
+
+            ensureStencilBuffer();
+
+            mCaches.stencil.enableWrite();
+
+            // Clear 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();
+            if (resetScissor) mCaches.disableScissor();
+
+            // 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(*mSnapshot->clipRegion, 0xff000000, SkXfermode::kSrc_Mode, false);
+
+            mCaches.stencil.enableTest();
+        } else {
+            mCaches.stencil.disable();
+        }
+    }
+}
+
 const Rect& OpenGLRenderer::getClipBounds() {
     return mSnapshot->getLocalClip();
 }
@@ -1284,40 +1357,60 @@
     return rejected;
 }
 
+void OpenGLRenderer::debugClip() {
+#if DEBUG_CLIP_REGIONS
+    if (!isDeferred() && !mSnapshot->clipRegion->isEmpty()) {
+        drawRegionRects(*mSnapshot->clipRegion, 0x7f00ff00, SkXfermode::kSrcOver_Mode);
+    }
+#endif
+}
+
 bool OpenGLRenderer::clipRect(float left, float top, float right, float bottom, SkRegion::Op op) {
-    bool clipped = mSnapshot->clip(left, top, right, bottom, op);
+    if (CC_LIKELY(mSnapshot->transform->rectToRect())) {
+        bool clipped = mSnapshot->clip(left, top, right, bottom, op);
+        if (clipped) {
+            dirtyClip();
+        }
+        return !mSnapshot->clipRect->isEmpty();
+    }
+
+    SkPath path;
+    path.addRect(left, top, right, bottom);
+
+    return clipPath(&path, op);
+}
+
+bool OpenGLRenderer::clipPath(SkPath* path, SkRegion::Op op) {
+    SkMatrix transform;
+    mSnapshot->transform->copyTo(transform);
+
+    SkPath transformed;
+    path->transform(transform, &transformed);
+
+    SkRegion clip;
+    if (!mSnapshot->clipRegion->isEmpty()) {
+        clip.setRegion(*mSnapshot->clipRegion);
+    } else {
+        Rect* bounds = mSnapshot->clipRect;
+        clip.setRect(bounds->left, bounds->top, bounds->right, bounds->bottom);
+    }
+
+    SkRegion region;
+    region.setPath(transformed, clip);
+
+    bool clipped = mSnapshot->clipRegionTransformed(region, op);
     if (clipped) {
         dirtyClip();
-#if DEBUG_CLIP_REGIONS
-        if (!isDeferred() && mSnapshot->clipRegion && !mSnapshot->clipRegion->isRect()) {
-            int count = 0;
-            Vector<float> rects;
-            SkRegion::Iterator it(*mSnapshot->clipRegion);
-            while (!it.done()) {
-                const SkIRect& r = it.rect();
-                rects.push(r.fLeft);
-                rects.push(r.fTop);
-                rects.push(r.fRight);
-                rects.push(r.fBottom);
-                count++;
-                it.next();
-            }
-
-            drawColorRects(rects.array(), count, 0x7f00ff00, SkXfermode::kSrcOver_Mode, true);
-        }
-#endif
     }
     return !mSnapshot->clipRect->isEmpty();
 }
 
-bool OpenGLRenderer::clipPath(SkPath* path, SkRegion::Op op) {
-    const SkRect& bounds = path->getBounds();
-    return clipRect(bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom, op);
-}
-
 bool OpenGLRenderer::clipRegion(SkRegion* region, SkRegion::Op op) {
-    const SkIRect& bounds = region->getBounds();
-    return clipRect(bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom, op);
+    bool clipped = mSnapshot->clipRegionTransformed(*region, op);
+    if (clipped) {
+        dirtyClip();
+    }
+    return !mSnapshot->clipRect->isEmpty();
 }
 
 Rect* OpenGLRenderer::getClipRect() {
@@ -1332,8 +1425,11 @@
     // TODO: It would be best if we could do this before quickReject()
     //       changes the scissor test state
     if (clear) clearLayerRegions();
+    // Make sure setScissor & setStencil happen at the beginning of
+    // this method
     if (mDirtyClip) {
         setScissorFromClip();
+        setStencilFromClip();
     }
     mDescription.reset();
     mSetShaderColor = false;
@@ -3085,7 +3181,7 @@
 }
 
 status_t OpenGLRenderer::drawColorRects(const float* rects, int count, int color,
-        SkXfermode::Mode mode, bool ignoreTransform) {
+        SkXfermode::Mode mode, bool ignoreTransform, bool dirty) {
 
     float left = FLT_MAX;
     float top = FLT_MAX;
@@ -3103,7 +3199,7 @@
         float r = rects[index + 2];
         float b = rects[index + 3];
 
-        if (!quickRejectNoScissor(left, top, right, bottom)) {
+        if (ignoreTransform || !quickRejectNoScissor(left, top, right, bottom)) {
             Vertex::set(vertex++, l, b);
             Vertex::set(vertex++, l, t);
             Vertex::set(vertex++, r, t);
@@ -3136,7 +3232,7 @@
     setupDrawColorFilterUniforms();
     setupDrawVertices((GLvoid*) &mesh[0].position[0]);
 
-    if (hasLayer()) {
+    if (dirty && hasLayer()) {
         dirtyLayer(left, top, right, bottom, *mSnapshot->transform);
     }
 
diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h
index f07325f..d4e1eb5 100644
--- a/libs/hwui/OpenGLRenderer.h
+++ b/libs/hwui/OpenGLRenderer.h
@@ -287,6 +287,19 @@
     void resumeAfterLayer();
 
     /**
+     * This method is called whenever a stencil buffer is required. Subclasses
+     * should override this method and call attachStencilBufferToLayer() on the
+     * appropriate layer(s).
+     */
+    virtual void ensureStencilBuffer();
+
+    /**
+     * Obtains a stencil render buffer (allocating it if necessary) and
+     * attaches it to the specified layer.
+     */
+    void attachStencilBufferToLayer(Layer* layer);
+
+    /**
      * Compose the layer defined in the current snapshot with the layer
      * defined by the previous snapshot.
      *
@@ -423,6 +436,12 @@
     void setScissorFromClip();
 
     /**
+     * Sets the clipping region using the stencil buffer. The clip region
+     * is defined by the current snapshot's clipRegion member.
+     */
+    void setStencilFromClip();
+
+    /**
      * Performs a quick reject but does not affect the scissor. Returns
      * the transformed rect to test and the current clip.
      */
@@ -524,9 +543,10 @@
      * @param color The rectangles' ARGB color, defined as a packed 32 bits word
      * @param mode The Skia xfermode to use
      * @param ignoreTransform True if the current transform should be ignored
+     * @param dirty True if calling this method should dirty the current layer
      */
     status_t drawColorRects(const float* rects, int count, int color,
-            SkXfermode::Mode mode, bool ignoreTransform = false);
+            SkXfermode::Mode mode, bool ignoreTransform = false, bool dirty = true);
 
     /**
      * Draws the shape represented by the specified path texture.
@@ -774,6 +794,19 @@
      */
     void drawRegionRects(const Region& region);
 
+    /**
+     * Renders the specified region as a series of rectangles. The region
+     * must be in screen-space coordinates.
+     */
+    void drawRegionRects(const SkRegion& region, int color, SkXfermode::Mode mode,
+            bool dirty = false);
+
+    /**
+     * Draws the current clip region if any. Only when DEBUG_CLIP_REGIONS
+     * is turned on.
+     */
+    void debugClip();
+
     void debugOverdraw(bool enable, bool clear);
     void renderOverdraw();
 
diff --git a/libs/hwui/Snapshot.cpp b/libs/hwui/Snapshot.cpp
index d947299..22c7dde 100644
--- a/libs/hwui/Snapshot.cpp
+++ b/libs/hwui/Snapshot.cpp
@@ -31,7 +31,7 @@
     transform = &mTransformRoot;
     clipRect = &mClipRectRoot;
     region = NULL;
-    clipRegion = NULL;
+    clipRegion = &mClipRegionRoot;
 }
 
 /**
@@ -39,12 +39,10 @@
  * the previous snapshot.
  */
 Snapshot::Snapshot(const sp<Snapshot>& s, int saveFlags):
-        flags(0), previous(s), layer(NULL), fbo(s->fbo),
+        flags(0), previous(s), layer(s->layer), fbo(s->fbo),
         invisible(s->invisible), empty(false),
         viewport(s->viewport), height(s->height), alpha(s->alpha) {
 
-    clipRegion = NULL;
-
     if (saveFlags & SkCanvas::kMatrix_SaveFlag) {
         mTransformRoot.load(*s->transform);
         transform = &mTransformRoot;
@@ -55,17 +53,13 @@
     if (saveFlags & SkCanvas::kClip_SaveFlag) {
         mClipRectRoot.set(*s->clipRect);
         clipRect = &mClipRectRoot;
-#if STENCIL_BUFFER_SIZE
-        if (s->clipRegion) {
+        if (!s->clipRegion->isEmpty()) {
             mClipRegionRoot.op(*s->clipRegion, SkRegion::kUnion_Op);
-            clipRegion = &mClipRegionRoot;
         }
-#endif
+        clipRegion = &mClipRegionRoot;
     } else {
         clipRect = s->clipRect;
-#if STENCIL_BUFFER_SIZE
         clipRegion = s->clipRegion;
-#endif
     }
 
     if (s->flags & Snapshot::kFlagFboTarget) {
@@ -81,41 +75,38 @@
 ///////////////////////////////////////////////////////////////////////////////
 
 void Snapshot::ensureClipRegion() {
-#if STENCIL_BUFFER_SIZE
-    if (!clipRegion) {
-        clipRegion = &mClipRegionRoot;
+    if (clipRegion->isEmpty()) {
         clipRegion->setRect(clipRect->left, clipRect->top, clipRect->right, clipRect->bottom);
     }
-#endif
 }
 
 void Snapshot::copyClipRectFromRegion() {
-#if STENCIL_BUFFER_SIZE
     if (!clipRegion->isEmpty()) {
         const SkIRect& bounds = clipRegion->getBounds();
         clipRect->set(bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom);
 
         if (clipRegion->isRect()) {
             clipRegion->setEmpty();
-            clipRegion = NULL;
         }
     } else {
         clipRect->setEmpty();
-        clipRegion = NULL;
     }
-#endif
 }
 
 bool Snapshot::clipRegionOp(float left, float top, float right, float bottom, SkRegion::Op op) {
-#if STENCIL_BUFFER_SIZE
     SkIRect tmp;
     tmp.set(left, top, right, bottom);
     clipRegion->op(tmp, op);
     copyClipRectFromRegion();
     return true;
-#else
-    return false;
-#endif
+}
+
+bool Snapshot::clipRegionTransformed(const SkRegion& region, SkRegion::Op op) {
+    ensureClipRegion();
+    clipRegion->op(region, op);
+    copyClipRectFromRegion();
+    flags |= Snapshot::kFlagClipSet;
+    return true;
 }
 
 bool Snapshot::clip(float left, float top, float right, float bottom, SkRegion::Op op) {
@@ -129,7 +120,7 @@
 
     switch (op) {
         case SkRegion::kIntersect_Op: {
-            if (CC_UNLIKELY(clipRegion)) {
+            if (CC_UNLIKELY(!clipRegion->isEmpty())) {
                 ensureClipRegion();
                 clipped = clipRegionOp(r.left, r.top, r.right, r.bottom, SkRegion::kIntersect_Op);
             } else {
@@ -142,7 +133,7 @@
             break;
         }
         case SkRegion::kUnion_Op: {
-            if (CC_UNLIKELY(clipRegion)) {
+            if (CC_UNLIKELY(!clipRegion->isEmpty())) {
                 ensureClipRegion();
                 clipped = clipRegionOp(r.left, r.top, r.right, r.bottom, SkRegion::kUnion_Op);
             } else {
@@ -171,12 +162,9 @@
 
 void Snapshot::setClip(float left, float top, float right, float bottom) {
     clipRect->set(left, top, right, bottom);
-#if STENCIL_BUFFER_SIZE
-    if (clipRegion) {
+    if (!clipRegion->isEmpty()) {
         clipRegion->setEmpty();
-        clipRegion = NULL;
     }
-#endif
     flags |= Snapshot::kFlagClipSet;
 }
 
diff --git a/libs/hwui/Snapshot.h b/libs/hwui/Snapshot.h
index 9c612ff..ffd4729 100644
--- a/libs/hwui/Snapshot.h
+++ b/libs/hwui/Snapshot.h
@@ -94,6 +94,12 @@
     bool clipTransformed(const Rect& r, SkRegion::Op op = SkRegion::kIntersect_Op);
 
     /**
+     * Modifies the current clip with the specified region and operation.
+     * The specified region is considered already transformed.
+     */
+    bool clipRegionTransformed(const SkRegion& region, SkRegion::Op op);
+
+    /**
      * Sets the current clip.
      */
     void setClip(float left, float top, float right, float bottom);
@@ -136,7 +142,7 @@
     sp<Snapshot> previous;
 
     /**
-     * Only set when the flag kFlagIsLayer is set.
+     * A pointer to the currently active layer.
      *
      * This snapshot does not own the layer, this pointer must not be freed.
      */
@@ -200,8 +206,6 @@
      *
      * This is a reference to a region owned by this snapshot or another
      * snapshot. This pointer must not be freed. See ::mClipRegionRoot.
-     *
-     * This field is used only if STENCIL_BUFFER_SIZE is > 0.
      */
     SkRegion* clipRegion;
 
@@ -234,9 +238,7 @@
     Rect mClipRectRoot;
     Rect mLocalClip;
 
-#if STENCIL_BUFFER_SIZE
     SkRegion mClipRegionRoot;
-#endif
 
 }; // class Snapshot
 
diff --git a/libs/hwui/Stencil.cpp b/libs/hwui/Stencil.cpp
index 84df82b..4fcd51d 100644
--- a/libs/hwui/Stencil.cpp
+++ b/libs/hwui/Stencil.cpp
@@ -37,7 +37,7 @@
 void Stencil::enableTest() {
     if (mState != kTest) {
         enable();
-        glStencilFunc(GL_EQUAL, 0x1, 0x1);
+        glStencilFunc(GL_EQUAL, 0xff, 0xff);
         // 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);
@@ -48,7 +48,7 @@
 void Stencil::enableWrite() {
     if (mState != kWrite) {
         enable();
-        glStencilFunc(GL_ALWAYS, 0x1, 0x1);
+        glStencilFunc(GL_ALWAYS, 0xff, 0xff);
         // 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);