Optimize blending state changes.

Change-Id: I7c22a8aecccb8b5abfcf7243f049a4ef3cf3979a
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index 34da572..4423b7e 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -95,6 +95,7 @@
 ///////////////////////////////////////////////////////////////////////////////
 
 OpenGLRenderer::OpenGLRenderer():
+        mBlend(false), mLastSrcMode(GL_ZERO), mLastDstMode(GL_ZERO),
         mTextureCache(MB(DEFAULT_TEXTURE_CACHE_SIZE)),
         mLayerCache(MB(DEFAULT_LAYER_CACHE_SIZE)),
         mPatchCache(DEFAULT_PATCH_CACHE_SIZE) {
@@ -242,7 +243,7 @@
     const Rect& rect = layer->layer;
 
     drawTextureRect(rect.left, rect.top, rect.right, rect.bottom,
-            layer->texture, layer->alpha, layer->mode, layer->blend, true);
+            layer->texture, layer->alpha, layer->mode, layer->blend);
 
     LayerSize size(rect.getWidth(), rect.getHeight());
     // Failing to add the layer to the cache should happen only if the
@@ -421,13 +422,7 @@
 
 void OpenGLRenderer::drawBitmap(SkBitmap* bitmap, float left, float top, const SkPaint* paint) {
     const Texture* texture = mTextureCache.get(bitmap);
-
-    int alpha;
-    SkXfermode::Mode mode;
-    getAlphaAndMode(paint, &alpha, &mode);
-
-    drawTextureRect(left, top, left + texture->width, top + texture->height, texture->id,
-            alpha / 255.0f, mode, texture->blend, true);
+    drawTextureRect(left, top, left + texture->width, top + texture->height, texture, paint);
 }
 
 void OpenGLRenderer::drawBitmap(SkBitmap* bitmap, const SkMatrix* matrix, const SkPaint* paint) {
@@ -436,13 +431,7 @@
     transform.mapRect(r);
 
     const Texture* texture = mTextureCache.get(bitmap);
-
-    int alpha;
-    SkXfermode::Mode mode;
-    getAlphaAndMode(paint, &alpha, &mode);
-
-    drawTextureRect(r.left, r.top, r.right, r.bottom, texture->id,
-            alpha / 255.0f, mode, texture->blend, true);
+    drawTextureRect(r.left, r.top, r.right, r.bottom, texture, paint);
 }
 
 void OpenGLRenderer::drawBitmap(SkBitmap* bitmap,
@@ -451,10 +440,6 @@
          const SkPaint* paint) {
     const Texture* texture = mTextureCache.get(bitmap);
 
-    int alpha;
-    SkXfermode::Mode mode;
-    getAlphaAndMode(paint, &alpha, &mode);
-
     const float width = texture->width;
     const float height = texture->height;
 
@@ -465,8 +450,7 @@
 
     resetDrawTextureTexCoords(u1, v1, u2, v2);
 
-    drawTextureRect(dstLeft, dstTop, dstRight, dstBottom, texture->id,
-            alpha / 255.0f, mode, texture->blend, true);
+    drawTextureRect(dstLeft, dstTop, dstRight, dstBottom, texture, paint);
 
     resetDrawTextureTexCoords(0.0f, 0.0f, 1.0f, 1.0f);
 }
@@ -606,17 +590,12 @@
 void OpenGLRenderer::drawColorRect(float left, float top, float right, float bottom,
         int color, SkXfermode::Mode mode) {
     const int alpha = (color >> 24) & 0xFF;
-    const bool blend = alpha < 255 || mode != SkXfermode::kSrcOver_Mode;
-
     const GLfloat a = alpha                  / 255.0f;
     const GLfloat r = ((color >> 16) & 0xFF) / 255.0f;
     const GLfloat g = ((color >>  8) & 0xFF) / 255.0f;
     const GLfloat b = ((color      ) & 0xFF) / 255.0f;
 
-    if (blend) {
-        glEnable(GL_BLEND);
-        glBlendFunc(gBlends[mode].src, gBlends[mode].dst);
-    }
+    chooseBlending(alpha < 255, mode, true);
 
     mModelView.loadTranslate(left, top, 0.0f);
     mModelView.scale(right - left, bottom - top, 1.0f);
@@ -633,10 +612,17 @@
     glDrawArrays(GL_TRIANGLE_STRIP, 0, gDrawColorVertexCount);
 
     glDisableVertexAttribArray(mDrawColorShader->position);
+}
 
-    if (blend) {
-        glDisable(GL_BLEND);
-    }
+void OpenGLRenderer::drawTextureRect(float left, float top, float right, float bottom,
+        const Texture* texture, const SkPaint* paint, bool isPremultiplied) {
+    int alpha;
+    SkXfermode::Mode mode;
+    getAlphaAndMode(paint, &alpha, &mode);
+
+    drawTextureMesh(left, top, right, bottom, texture->id, alpha / 255.0f, mode, texture->blend,
+            isPremultiplied, &mDrawTextureVertices[0].position[0],
+            &mDrawTextureVertices[0].texture[0], NULL);
 }
 
 void OpenGLRenderer::drawTextureRect(float left, float top, float right, float bottom,
@@ -653,15 +639,7 @@
 
     mDrawTextureShader->use(&mOrthoMatrix[0], &mModelView.data[0], &mSnapshot->transform.data[0]);
 
-    if (blend || alpha < 1.0f || mode != SkXfermode::kSrcOver_Mode) {
-        GLenum sourceMode = gBlends[mode].src;
-        if (!isPremultiplied && sourceMode == GL_ONE) {
-            sourceMode = GL_SRC_ALPHA;
-        }
-
-        glEnable(GL_BLEND);
-        glBlendFunc(sourceMode, gBlends[mode].dst);
-    }
+    chooseBlending(blend || alpha < 1.0f, mode, isPremultiplied);
 
     glBindTexture(GL_TEXTURE_2D, texture);
 
@@ -690,18 +668,40 @@
     glDisableVertexAttribArray(mDrawTextureShader->texCoords);
 
     glBindTexture(GL_TEXTURE_2D, 0);
-    glDisable(GL_BLEND);
+}
+
+void OpenGLRenderer::chooseBlending(bool blend, SkXfermode::Mode mode, bool isPremultiplied) {
+    // In theory we should not blend if the mode is Src, but it's rare enough
+    // that it's not worth it
+    blend = blend || mode != SkXfermode::kSrcOver_Mode;
+    if (blend) {
+        if (!mBlend) {
+            glEnable(GL_BLEND);
+        }
+
+        GLenum sourceMode = gBlends[mode].src;
+        GLenum destMode = gBlends[mode].dst;
+        if (!isPremultiplied && sourceMode == GL_ONE) {
+            sourceMode = GL_SRC_ALPHA;
+        }
+
+        if (sourceMode != mLastSrcMode || destMode != mLastDstMode) {
+            glBlendFunc(sourceMode, destMode);
+            mLastSrcMode = sourceMode;
+            mLastDstMode = destMode;
+        }
+    } else if (mBlend) {
+        glDisable(GL_BLEND);
+    }
+    mBlend = blend;
 }
 
 void OpenGLRenderer::resetDrawTextureTexCoords(float u1, float v1, float u2, float v2) {
-    mDrawTextureVertices[0].texture[0] = u1;
-    mDrawTextureVertices[0].texture[1] = v1;
-    mDrawTextureVertices[1].texture[0] = u2;
-    mDrawTextureVertices[1].texture[1] = v1;
-    mDrawTextureVertices[2].texture[0] = u1;
-    mDrawTextureVertices[2].texture[1] = v2;
-    mDrawTextureVertices[3].texture[0] = u2;
-    mDrawTextureVertices[3].texture[1] = v2;
+    TextureVertex* v = &mDrawTextureVertices[0];
+    TextureVertex::setUV(v++, u1, v1);
+    TextureVertex::setUV(v++, u2, v1);
+    TextureVertex::setUV(v++, u1, v2);
+    TextureVertex::setUV(v++, u2, v2);
 }
 
 void OpenGLRenderer::getAlphaAndMode(const SkPaint* paint, int* alpha, SkXfermode::Mode* mode) {
diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h
index b46e9d3..6527cae 100644
--- a/libs/hwui/OpenGLRenderer.h
+++ b/libs/hwui/OpenGLRenderer.h
@@ -179,10 +179,40 @@
      * @param isPremultiplied Indicates whether the texture has premultiplied alpha
      */
     void drawTextureRect(float left, float top, float right, float bottom, GLuint texture,
-            float alpha, SkXfermode::Mode mode, bool blend, bool isPremultiplied = false);
+            float alpha, SkXfermode::Mode mode, bool blend, bool isPremultiplied = true);
 
     /**
-     * TODO: documentation
+     * Draws a textured rectangle with the specified texture. The specified coordinates
+     * are transformed by the current snapshot's transform matrix.
+     *
+     * @param left The left coordinate of the rectangle
+     * @param top The top coordinate of the rectangle
+     * @param right The right coordinate of the rectangle
+     * @param bottom The bottom coordinate of the rectangle
+     * @param texture The texture to use
+     * @param paint The paint containing the alpha, blending mode, etc.
+     * @param isPremultiplied Indicates whether the texture has premultiplied alpha
+     */
+    void drawTextureRect(float left, float top, float right, float bottom, const Texture* texture,
+            const SkPaint* paint, bool isPremultiplied = true);
+
+    /**
+     * Draws a textured mesh with the specified texture. If the indices are omitted, the
+     * mesh is drawn as a simple quad.
+     *
+     * @param left The left coordinate of the rectangle
+     * @param top The top coordinate of the rectangle
+     * @param right The right coordinate of the rectangle
+     * @param bottom The bottom coordinate of the rectangle
+     * @param texture The texture name to map onto the rectangle
+     * @param alpha An additional translucency parameter, between 0.0f and 1.0f
+     * @param mode The blending mode
+     * @param blend True if the texture contains an alpha channel
+     * @param isPremultiplied Indicates whether the texture has premultiplied alpha
+     * @param vertices The vertices that define the mesh
+     * @param texCoords The texture coordinates of each vertex
+     * @param indices The indices of the vertices, can be NULL
+     * @param elementsCount The number of elements in the mesh, required by indices
      */
     void drawTextureMesh(float left, float top, float right, float bottom, GLuint texture,
             float alpha, SkXfermode::Mode mode, bool blend, bool isPremultiplied,
@@ -217,6 +247,12 @@
     inline void generateVertices(TextureVertex* vertex, float y, float v, const int32_t xDivs[],
             uint32_t xCount, float xStretch, float xStretchTex, float width, float widthTex);
 
+    /**
+     * Enable or disable blending as necessary. This function sets the appropriate
+     * blend function based on the specified xfermode.
+     */
+    inline void chooseBlending(bool blend, SkXfermode::Mode mode, bool isPremultiplied);
+
     // Dimensions of the drawing surface
     int mWidth, mHeight;
 
@@ -240,6 +276,12 @@
     // Used to draw textured quads
     TextureVertex mDrawTextureVertices[4];
 
+    // Last known blend state
+    bool mBlend;
+    GLenum mLastSrcMode;
+    GLenum mLastDstMode;
+
+    // Various caches
     TextureCache mTextureCache;
     LayerCache mLayerCache;
     PatchCache mPatchCache;
diff --git a/libs/hwui/Vertex.h b/libs/hwui/Vertex.h
index 208d2a8..ffd0633 100644
--- a/libs/hwui/Vertex.h
+++ b/libs/hwui/Vertex.h
@@ -41,6 +41,11 @@
         vertex[0].texture[0] = u;
         vertex[0].texture[1] = v;
     }
+
+    static inline void setUV(TextureVertex* vertex, float u, float v) {
+        vertex[0].texture[0] = u;
+        vertex[0].texture[1] = v;
+    }
 }; // struct TextureVertex
 
 }; // namespace uirenderer