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/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;