Batch 9-patches in a single mesh whenever possible

This change also fixes the way batched bitmaps were handled
inside a layer. The layer is now correctly dirtied to minimize
the amount of pixels to blend.

Fix alpha, mode and opaque computations for DrawPatchOp.

Change-Id: I1b6cd581c0f0db66c1002bb4fb1a9811e55bfa78
diff --git a/libs/hwui/DisplayListOp.h b/libs/hwui/DisplayListOp.h
index f678bfd..6c6eabb 100644
--- a/libs/hwui/DisplayListOp.h
+++ b/libs/hwui/DisplayListOp.h
@@ -751,12 +751,18 @@
     TextureVertex::set(ptr++, posRect.xDim - offsetRect.left, posRect.yDim - offsetRect.top, \
             texCoordsRect.xDim, texCoordsRect.yDim)
 
+    /**
+     * This multi-draw operation builds a mesh on the stack by generating a quad
+     * for each bitmap in the batch. This method is also responsible for dirtying
+     * the current layer, if any.
+     */
     virtual status_t multiDraw(OpenGLRenderer& renderer, Rect& dirty,
             const Vector<DrawOp*>& ops, const Rect& bounds) {
         renderer.restoreDisplayState(state, true); // restore all but the clip
         TextureVertex vertices[6 * ops.size()];
         TextureVertex* vertex = &vertices[0];
 
+        const bool hasLayer = renderer.hasLayer();
         bool transformed = false;
 
         // TODO: manually handle rect clip for bitmaps by adjusting texCoords per op,
@@ -778,6 +784,11 @@
             SET_TEXTURE(vertex, opBounds, bounds, texCoords, left, bottom);
             SET_TEXTURE(vertex, opBounds, bounds, texCoords, right, top);
             SET_TEXTURE(vertex, opBounds, bounds, texCoords, right, bottom);
+
+            if (hasLayer) {
+                const Rect& dirty = ops[i]->state.mBounds;
+                renderer.dirtyLayer(dirty.left, dirty.top, dirty.right, dirty.bottom);
+            }
         }
 
         return renderer.drawBitmaps(mBitmap, ops.size(), &vertices[0],
@@ -921,25 +932,104 @@
 class DrawPatchOp : public DrawBoundedOp {
 public:
     DrawPatchOp(SkBitmap* bitmap, Res_png_9patch* patch,
-            float left, float top, float right, float bottom, int alpha, SkXfermode::Mode mode)
-            : DrawBoundedOp(left, top, right, bottom, 0),
-            mBitmap(bitmap), mPatch(patch), mAlpha(alpha), mMode(mode),
-            mGenerationId(0), mMesh(NULL) {
+            float left, float top, float right, float bottom, SkPaint* paint)
+            : DrawBoundedOp(left, top, right, bottom, paint),
+            mBitmap(bitmap), mPatch(patch), mGenerationId(0), mMesh(NULL) {
         mEntry = Caches::getInstance().assetAtlas.getEntry(bitmap);
     };
 
-    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty) {
+    const Patch* getMesh(OpenGLRenderer& renderer) {
         if (!mMesh || renderer.getCaches().patchCache.getGenerationId() != mGenerationId) {
             PatchCache& cache = renderer.getCaches().patchCache;
             mMesh = cache.get(mEntry, mBitmap->width(), mBitmap->height(),
-                    mLocalBounds.right - mLocalBounds.left, mLocalBounds.bottom - mLocalBounds.top,
-                    mPatch);
+                    mLocalBounds.getWidth(), mLocalBounds.getHeight(), mPatch);
             mGenerationId = cache.getGenerationId();
         }
+        return mMesh;
+    }
+
+    /**
+     * This multi-draw operation builds an indexed mesh on the stack by copying
+     * and transforming the vertices of each 9-patch in the batch. This method
+     * is also responsible for dirtying the current layer, if any.
+     */
+    virtual status_t multiDraw(OpenGLRenderer& renderer, Rect& dirty,
+            const Vector<DrawOp*>& ops, const Rect& bounds) {
+        renderer.restoreDisplayState(state, true);
+
+        // Batches will usually contain a small number of items so it's
+        // worth performing a first iteration to count the exact number
+        // of vertices we need in the new mesh
+        uint32_t totalVertices = 0;
+        for (unsigned int i = 0; i < ops.size(); i++) {
+            totalVertices += ((DrawPatchOp*) ops[i])->getMesh(renderer)->verticesCount;
+        }
+
+        const bool hasLayer = renderer.hasLayer();
+
+        uint32_t indexCount = 0;
+
+        TextureVertex vertices[totalVertices];
+        TextureVertex* vertex = &vertices[0];
+
+        // Create a mesh that contains the transformed vertices for all the
+        // 9-patch objects that are part of the batch. Note that onDefer()
+        // enforces ops drawn by this function to have a pure translate or
+        // identity matrix
+        for (unsigned int i = 0; i < ops.size(); i++) {
+            DrawPatchOp* patchOp = (DrawPatchOp*) ops[i];
+            const Patch* opMesh = patchOp->getMesh(renderer);
+            uint32_t vertexCount = opMesh->verticesCount;
+            if (vertexCount == 0) continue;
+
+            // We use the bounds to know where to translate our vertices
+            // Using patchOp->state.mBounds wouldn't work because these
+            // bounds are clipped
+            const float tx = (int) floorf(patchOp->state.mMatrix.getTranslateX() +
+                    patchOp->mLocalBounds.left + 0.5f);
+            const float ty = (int) floorf(patchOp->state.mMatrix.getTranslateY() +
+                    patchOp->mLocalBounds.top + 0.5f);
+
+            // Copy & transform all the vertices for the current operation
+            TextureVertex* opVertices = opMesh->vertices;
+            for (uint32_t j = 0; j < vertexCount; j++, opVertices++) {
+                TextureVertex::set(vertex++,
+                        opVertices->position[0] + tx, opVertices->position[1] + ty,
+                        opVertices->texture[0], opVertices->texture[1]);
+            }
+
+            // Dirty the current layer if possible. When the 9-patch does not
+            // contain empty quads we can take a shortcut and simply set the
+            // dirty rect to the object's bounds.
+            if (hasLayer) {
+                if (!opMesh->hasEmptyQuads) {
+                    renderer.dirtyLayer(tx, ty,
+                            tx + patchOp->mLocalBounds.getWidth(),
+                            ty + patchOp->mLocalBounds.getHeight());
+                } else {
+                    const size_t count = opMesh->quads.size();
+                    for (size_t i = 0; i < count; i++) {
+                        const Rect& quadBounds = opMesh->quads[i];
+                        const float x = tx + quadBounds.left;
+                        const float y = ty + quadBounds.top;
+                        renderer.dirtyLayer(x, y,
+                                x + quadBounds.getWidth(), y + quadBounds.getHeight());
+                    }
+                }
+            }
+
+            indexCount += opMesh->indexCount;
+        }
+
+        return renderer.drawPatches(mBitmap, mEntry, &vertices[0], indexCount, getPaint(renderer));
+    }
+
+    virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty) {
         // We're not calling the public variant of drawPatch() here
         // This method won't perform the quickReject() since we've already done it at this point
-        return renderer.drawPatch(mBitmap, mMesh, mEntry, mLocalBounds.left, mLocalBounds.top,
-                mLocalBounds.right, mLocalBounds.bottom, mAlpha, mMode);
+        return renderer.drawPatch(mBitmap, getMesh(renderer), mEntry,
+                mLocalBounds.left, mLocalBounds.top, mLocalBounds.right, mLocalBounds.bottom,
+                getPaint(renderer));
     }
 
     virtual void output(int level, uint32_t logFlags) {
@@ -951,7 +1041,8 @@
     virtual void onDefer(OpenGLRenderer& renderer, DeferInfo& deferInfo) {
         deferInfo.batchId = DeferredDisplayList::kOpBatch_Patch;
         deferInfo.mergeId = mEntry ? (mergeid_t) &mEntry->atlas : (mergeid_t) mBitmap;
-        deferInfo.mergeable = true;
+        deferInfo.mergeable = state.mMatrix.isPureTranslate() &&
+                OpenGLRenderer::getXfermodeDirect(mPaint) == SkXfermode::kSrcOver_Mode;
         deferInfo.opaqueOverBounds = isOpaqueOverBounds() && mBitmap->isOpaque();
     }
 
@@ -959,11 +1050,9 @@
     SkBitmap* mBitmap;
     Res_png_9patch* mPatch;
 
-    int mAlpha;
-    SkXfermode::Mode mMode;
-
     uint32_t mGenerationId;
     const Patch* mMesh;
+
     AssetAtlas::Entry* mEntry;
 };
 
diff --git a/libs/hwui/DisplayListRenderer.cpp b/libs/hwui/DisplayListRenderer.cpp
index 6d85a16..41b1507 100644
--- a/libs/hwui/DisplayListRenderer.cpp
+++ b/libs/hwui/DisplayListRenderer.cpp
@@ -317,13 +317,9 @@
 
 status_t DisplayListRenderer::drawPatch(SkBitmap* bitmap, Res_png_9patch* patch,
         float left, float top, float right, float bottom, SkPaint* paint) {
-    int alpha;
-    SkXfermode::Mode mode;
-    OpenGLRenderer::getAlphaAndModeDirect(paint, &alpha, &mode);
-
     bitmap = refBitmap(bitmap);
 
-    addDrawOp(new (alloc()) DrawPatchOp(bitmap, patch, left, top, right, bottom, alpha, mode));
+    addDrawOp(new (alloc()) DrawPatchOp(bitmap, patch, left, top, right, bottom, paint));
     return DrawGlInfo::kStatusDone;
 }
 
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index 1dcf21a..d6d7b7f 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -1191,7 +1191,7 @@
         // after we setup drawing in case we need to mess with the
         // stencil buffer in setupDraw()
         TextureVertex* mesh = mCaches.getRegionMesh();
-        GLsizei numQuads = 0;
+        uint32_t numQuads = 0;
 
         setupDrawWithTexture();
         setupDrawColor(alpha, alpha, alpha, alpha);
@@ -2049,6 +2049,11 @@
             GL_TRIANGLE_STRIP, gMeshCount, ignoreTransform);
 }
 
+/**
+ * Important note: this method is intended to draw batches of bitmaps and
+ * will not set the scissor enable or dirty the current layer, if any.
+ * The caller is responsible for properly dirtying the current layer.
+ */
 status_t OpenGLRenderer::drawBitmaps(SkBitmap* bitmap, int bitmapCount, TextureVertex* vertices,
         bool transformed, const Rect& bounds, SkPaint* paint) {
     mCaches.activeTexture(0);
@@ -2071,12 +2076,12 @@
         drawAlpha8TextureMesh(x, y, x + bounds.getWidth(), y + bounds.getHeight(),
                 texture->id, paint != NULL, color, alpha, mode,
                 &vertices[0].position[0], &vertices[0].texture[0],
-                GL_TRIANGLES, bitmapCount * 6, true, true);
+                GL_TRIANGLES, bitmapCount * 6, true, true, false);
     } else {
         drawTextureMesh(x, y, x + bounds.getWidth(), y + bounds.getHeight(),
                 texture->id, alpha / 255.0f, mode, texture->blend,
                 &vertices[0].position[0], &vertices[0].texture[0],
-                GL_TRIANGLES, bitmapCount * 6, false, true, 0, true);
+                GL_TRIANGLES, bitmapCount * 6, false, true, 0, true, false);
     }
 
     return DrawGlInfo::kStatusDrew;
@@ -2367,10 +2372,6 @@
 
 status_t OpenGLRenderer::drawPatch(SkBitmap* bitmap, Res_png_9patch* patch,
         float left, float top, float right, float bottom, SkPaint* paint) {
-    int alpha;
-    SkXfermode::Mode mode;
-    getAlphaAndMode(paint, &alpha, &mode);
-
     if (quickReject(left, top, right, bottom)) {
         return DrawGlInfo::kStatusDone;
     }
@@ -2379,13 +2380,11 @@
     const Patch* mesh = mCaches.patchCache.get(entry, bitmap->width(), bitmap->height(),
             right - left, bottom - top, patch);
 
-    return drawPatch(bitmap, mesh, entry, left, top, right, bottom, alpha, mode);
+    return drawPatch(bitmap, mesh, entry, left, top, right, bottom, paint);
 }
 
-status_t OpenGLRenderer::drawPatch(SkBitmap* bitmap, const Patch* mesh,
-        AssetAtlas::Entry* entry, float left, float top, float right, float bottom,
-        int alpha, SkXfermode::Mode mode) {
-
+status_t OpenGLRenderer::drawPatch(SkBitmap* bitmap, const Patch* mesh, AssetAtlas::Entry* entry,
+        float left, float top, float right, float bottom, SkPaint* paint) {
     if (quickReject(left, top, right, bottom)) {
         return DrawGlInfo::kStatusDone;
     }
@@ -2399,6 +2398,10 @@
         texture->setWrap(GL_CLAMP_TO_EDGE, true);
         texture->setFilter(GL_LINEAR, true);
 
+        int alpha;
+        SkXfermode::Mode mode;
+        getAlphaAndMode(paint, &alpha, &mode);
+
         const bool pureTranslate = currentTransform().isPureTranslate();
         // Mark the current layer dirty where we are going to draw the patch
         if (hasLayer() && mesh->hasEmptyQuads) {
@@ -2418,8 +2421,6 @@
             }
         }
 
-        alpha *= mSnapshot->alpha;
-
         if (CC_LIKELY(pureTranslate)) {
             const float x = (int) floorf(left + currentTransform().getTranslateX() + 0.5f);
             const float y = (int) floorf(top + currentTransform().getTranslateY() + 0.5f);
@@ -2441,6 +2442,32 @@
     return DrawGlInfo::kStatusDrew;
 }
 
+/**
+ * Important note: this method is intended to draw batches of 9-patch objects and
+ * will not set the scissor enable or dirty the current layer, if any.
+ * The caller is responsible for properly dirtying the current layer.
+ */
+status_t OpenGLRenderer::drawPatches(SkBitmap* bitmap, AssetAtlas::Entry* entry,
+        TextureVertex* vertices, uint32_t indexCount, SkPaint* paint) {
+    mCaches.activeTexture(0);
+    Texture* texture = entry ? entry->texture : mCaches.textureCache.get(bitmap);
+    if (!texture) return DrawGlInfo::kStatusDone;
+    const AutoTexture autoCleanup(texture);
+
+    texture->setWrap(GL_CLAMP_TO_EDGE, true);
+    texture->setFilter(GL_LINEAR, true);
+
+    int alpha;
+    SkXfermode::Mode mode;
+    getAlphaAndMode(paint, &alpha, &mode);
+
+    drawIndexedTextureMesh(0.0f, 0.0f, 1.0f, 1.0f, texture->id, alpha / 255.0f,
+            mode, texture->blend, &vertices[0].position[0], &vertices[0].texture[0],
+            GL_TRIANGLES, indexCount, false, true, 0, true, false);
+
+    return DrawGlInfo::kStatusDrew;
+}
+
 status_t OpenGLRenderer::drawVertexBuffer(const VertexBuffer& vertexBuffer, SkPaint* paint,
         bool useOffset) {
     if (!vertexBuffer.getVertexCount()) {
diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h
index 0760f9c..548501d 100644
--- a/libs/hwui/OpenGLRenderer.h
+++ b/libs/hwui/OpenGLRenderer.h
@@ -297,10 +297,12 @@
     virtual status_t drawBitmapData(SkBitmap* bitmap, float left, float top, SkPaint* paint);
     virtual status_t drawBitmapMesh(SkBitmap* bitmap, int meshWidth, int meshHeight,
             float* vertices, int* colors, SkPaint* paint);
+    status_t drawPatches(SkBitmap* bitmap, AssetAtlas::Entry* entry,
+            TextureVertex* vertices, uint32_t indexCount, SkPaint* paint);
     virtual status_t drawPatch(SkBitmap* bitmap, Res_png_9patch* patch,
             float left, float top, float right, float bottom, SkPaint* paint);
     status_t drawPatch(SkBitmap* bitmap, const Patch* mesh, AssetAtlas::Entry* entry,
-            float left, float top, float right, float bottom, int alpha, SkXfermode::Mode mode);
+            float left, float top, float right, float bottom, SkPaint* paint);
     virtual status_t drawColor(int color, SkXfermode::Mode mode);
     virtual status_t drawRect(float left, float top, float right, float bottom, SkPaint* paint);
     virtual status_t drawRoundRect(float left, float top, float right, float bottom,
@@ -1115,6 +1117,8 @@
     friend class DisplayListRenderer;
     friend class Layer;
     friend class TextSetupFunctor;
+    friend class DrawBitmapOp;
+    friend class DrawPatchOp;
 
 }; // class OpenGLRenderer
 
diff --git a/libs/hwui/Patch.cpp b/libs/hwui/Patch.cpp
index 6b0734a..9e3e701 100644
--- a/libs/hwui/Patch.cpp
+++ b/libs/hwui/Patch.cpp
@@ -32,7 +32,7 @@
 // Constructors/destructor
 ///////////////////////////////////////////////////////////////////////////////
 
-Patch::Patch(): verticesCount(0), indexCount(0), hasEmptyQuads(false) {
+Patch::Patch(): vertices(NULL), verticesCount(0), indexCount(0), hasEmptyQuads(false) {
 }
 
 Patch::~Patch() {
@@ -47,14 +47,14 @@
 }
 
 TextureVertex* Patch::createMesh(const float bitmapWidth, const float bitmapHeight,
-        float left, float top, float right, float bottom, const Res_png_9patch* patch) {
+        float width, float height, const Res_png_9patch* patch) {
     UvMapper mapper;
-    return createMesh(bitmapWidth, bitmapHeight, left, top, right, bottom, mapper, patch);
+    return createMesh(bitmapWidth, bitmapHeight, width, height, mapper, patch);
 }
 
 TextureVertex* Patch::createMesh(const float bitmapWidth, const float bitmapHeight,
-        float left, float top, float right, float bottom,
-        const UvMapper& mapper, const Res_png_9patch* patch) {
+        float width, float height, const UvMapper& mapper, const Res_png_9patch* patch) {
+    if (vertices) return vertices;
 
     const uint32_t* colors = &patch->colors[0];
     const int8_t numColors = patch->numColors;
@@ -79,7 +79,7 @@
     uint32_t maxVertices = ((xCount + 1) * (yCount + 1) - emptyQuads) * 4;
     if (maxVertices == 0) return NULL;
 
-    TextureVertex* vertices = new TextureVertex[maxVertices];
+    vertices = new TextureVertex[maxVertices];
     TextureVertex* vertex = vertices;
 
     const int32_t* xDivs = patch->xDivs;
@@ -101,9 +101,9 @@
         }
         const float xStretchTex = stretchSize;
         const float fixed = bitmapWidth - stretchSize;
-        const float xStretch = fmaxf(right - left - fixed, 0.0f);
+        const float xStretch = fmaxf(width - fixed, 0.0f);
         stretchX = xStretch / xStretchTex;
-        rescaleX = fixed == 0.0f ? 0.0f : fminf(fmaxf(right - left, 0.0f) / fixed, 1.0f);
+        rescaleX = fixed == 0.0f ? 0.0f : fminf(fmaxf(width, 0.0f) / fixed, 1.0f);
     }
 
     if (yStretchCount > 0) {
@@ -113,9 +113,9 @@
         }
         const float yStretchTex = stretchSize;
         const float fixed = bitmapHeight - stretchSize;
-        const float yStretch = fmaxf(bottom - top - fixed, 0.0f);
+        const float yStretch = fmaxf(height - fixed, 0.0f);
         stretchY = yStretch / yStretchTex;
-        rescaleY = fixed == 0.0f ? 0.0f : fminf(fmaxf(bottom - top, 0.0f) / fixed, 1.0f);
+        rescaleY = fixed == 0.0f ? 0.0f : fminf(fmaxf(height, 0.0f) / fixed, 1.0f);
     }
 
     uint32_t quadCount = 0;
@@ -144,7 +144,7 @@
 
         if (stepY > 0.0f) {
             generateRow(xDivs, xCount, vertex, y1, y2, v1, v2, stretchX, rescaleX,
-                    right - left, bitmapWidth, quadCount);
+                    width, bitmapWidth, quadCount);
         }
 
         y1 = y2;
@@ -154,9 +154,9 @@
     }
 
     if (previousStepY != bitmapHeight) {
-        y2 = bottom - top;
+        y2 = height;
         generateRow(xDivs, xCount, vertex, y1, y2, v1, 1.0f, stretchX, rescaleX,
-                right - left, bitmapWidth, quadCount);
+                width, bitmapWidth, quadCount);
     }
 
     return vertices;
diff --git a/libs/hwui/Patch.h b/libs/hwui/Patch.h
index 448cf60..246ba66 100644
--- a/libs/hwui/Patch.h
+++ b/libs/hwui/Patch.h
@@ -45,6 +45,7 @@
      */
     uint32_t getSize() const;
 
+    TextureVertex* vertices;
     uint32_t verticesCount;
     uint32_t indexCount;
     bool hasEmptyQuads;
@@ -54,11 +55,9 @@
     GLintptr textureOffset;
 
     TextureVertex* createMesh(const float bitmapWidth, const float bitmapHeight,
-            float left, float top, float right, float bottom,
-            const Res_png_9patch* patch);
+            float width, float height, const Res_png_9patch* patch);
     TextureVertex* createMesh(const float bitmapWidth, const float bitmapHeight,
-            float left, float top, float right, float bottom,
-            const UvMapper& mapper, const Res_png_9patch* patch);
+            float width, float height, const UvMapper& mapper, const Res_png_9patch* patch);
 
 private:
     void generateRow(const int32_t* xDivs, uint32_t xCount, TextureVertex*& vertex,
diff --git a/libs/hwui/PatchCache.cpp b/libs/hwui/PatchCache.cpp
index c23e991..dc69d7f 100644
--- a/libs/hwui/PatchCache.cpp
+++ b/libs/hwui/PatchCache.cpp
@@ -118,10 +118,10 @@
 
         if (entry) {
             vertices = newMesh->createMesh(bitmapWidth, bitmapHeight,
-                    0.0f, 0.0f, pixelWidth, pixelHeight, entry->uvMapper, patch);
+                    pixelWidth, pixelHeight, entry->uvMapper, patch);
         } else {
             vertices = newMesh->createMesh(bitmapWidth, bitmapHeight,
-                    0.0f, 0.0f, pixelWidth, pixelHeight, patch);
+                    pixelWidth, pixelHeight, patch);
         }
 
         if (vertices) {
@@ -141,8 +141,6 @@
             mSize += size;
 
             glBufferSubData(GL_ARRAY_BUFFER, newMesh->offset, size, vertices);
-
-            delete[] vertices;
         }
 
         mCache.put(description, newMesh);
diff --git a/libs/hwui/PixelBuffer.cpp b/libs/hwui/PixelBuffer.cpp
index 74b628a..36e89c6 100644
--- a/libs/hwui/PixelBuffer.cpp
+++ b/libs/hwui/PixelBuffer.cpp
@@ -132,7 +132,10 @@
     if (mAccessMode != kAccessMode_None) {
         if (mMappedPointer) {
             mCaches.bindPixelBuffer(mBuffer);
-            glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
+            GLboolean status = glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
+            if (status == GL_FALSE) {
+                ALOGE("Corrupted GPU pixel buffer");
+            }
         }
         mAccessMode = kAccessMode_None;
         mMappedPointer = NULL;