Implement support for PorterDuff's blending modes.

The blending modes are currently hooked up only when drawing filled rects but the
code is reusable for other primitives. This will allow implementation of saveLayer().
This method is required to support the fade effects used throughout the standard
Android UI.

Change-Id: I396023d123436f16cdafc606e358e4eb80c9df2c
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index e764778..305c8c5 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -40,34 +40,42 @@
 // Globals
 ///////////////////////////////////////////////////////////////////////////////
 
-const SimpleVertex gDrawColorVertices[] = {
+static const SimpleVertex gDrawColorVertices[] = {
         SV(0.0f, 0.0f),
         SV(1.0f, 0.0f),
         SV(0.0f, 1.0f),
         SV(1.0f, 1.0f)
 };
-const GLsizei gDrawColorVertexStride = sizeof(SimpleVertex);
-const GLsizei gDrawColorVertexCount = 4;
+static const GLsizei gDrawColorVertexStride = sizeof(SimpleVertex);
+static const GLsizei gDrawColorVertexCount = 4;
 
-TextureVertex gDrawTextureVertices[] = {
+// This array is never used directly but used as a memcpy source in the
+// OpenGLRenderer constructor
+static const TextureVertex gDrawTextureVertices[] = {
         FV(0.0f, 0.0f, 0.0f, 1.0f),
         FV(1.0f, 0.0f, 1.0f, 1.0f),
         FV(0.0f, 1.0f, 0.0f, 0.0f),
         FV(1.0f, 1.0f, 1.0f, 0.0f)
 };
-const GLsizei gDrawTextureVertexStride = sizeof(TextureVertex);
-const GLsizei gDrawTextureVertexCount = 4;
+static const GLsizei gDrawTextureVertexStride = sizeof(TextureVertex);
+static const GLsizei gDrawTextureVertexCount = 4;
 
-static inline void resetDrawTextureTexCoords(float u1, float v1, float u2, float v2) {
-    gDrawTextureVertices[0].texture[0] = u1;
-    gDrawTextureVertices[0].texture[1] = v2;
-    gDrawTextureVertices[1].texture[0] = u2;
-    gDrawTextureVertices[1].texture[1] = v2;
-    gDrawTextureVertices[2].texture[0] = u1;
-    gDrawTextureVertices[2].texture[1] = v1;
-    gDrawTextureVertices[3].texture[0] = u2;
-    gDrawTextureVertices[3].texture[1] = v1;
-}
+// In this array, the index of each Blender equals the value of the first
+// entry. For instance, gBlends[1] == gBlends[SkXfermode::kSrc_Mode]
+static const Blender gBlends[] = {
+        { SkXfermode::kClear_Mode,   GL_ZERO,                 GL_ZERO },
+        { SkXfermode::kSrc_Mode,     GL_ONE,                  GL_ZERO },
+        { SkXfermode::kDst_Mode,     GL_ZERO,                 GL_ONE },
+        { SkXfermode::kSrcOver_Mode, GL_ONE,                  GL_ONE_MINUS_SRC_ALPHA },
+        { SkXfermode::kDstOver_Mode, GL_ONE_MINUS_DST_ALPHA,  GL_ONE },
+        { SkXfermode::kSrcIn_Mode,   GL_DST_ALPHA,            GL_ZERO },
+        { SkXfermode::kDstIn_Mode,   GL_ZERO,                 GL_SRC_ALPHA },
+        { SkXfermode::kSrcOut_Mode,  GL_ONE_MINUS_DST_ALPHA,  GL_ZERO },
+        { SkXfermode::kDstOut_Mode,  GL_ZERO,                 GL_ONE_MINUS_SRC_ALPHA },
+        { SkXfermode::kSrcATop_Mode, GL_DST_ALPHA,            GL_ONE_MINUS_SRC_ALPHA },
+        { SkXfermode::kDstATop_Mode, GL_ONE_MINUS_DST_ALPHA,  GL_SRC_ALPHA },
+        { SkXfermode::kXor_Mode,     GL_ONE_MINUS_DST_ALPHA,  GL_ONE_MINUS_SRC_ALPHA }
+};
 
 ///////////////////////////////////////////////////////////////////////////////
 // Constructors/destructor
@@ -78,6 +86,8 @@
 
     mDrawColorShader = new DrawColorProgram;
     mDrawTextureShader = new DrawTextureProgram;
+
+    memcpy(mDrawTextureVertices, gDrawTextureVertices, sizeof(gDrawTextureVertices));
 }
 
 OpenGLRenderer::~OpenGLRenderer() {
@@ -336,22 +346,43 @@
 ///////////////////////////////////////////////////////////////////////////////
 
 void OpenGLRenderer::drawColor(int color, SkXfermode::Mode mode) {
-    // TODO: Set the transfer mode
     const Rect& clip = mSnapshot->clipRect;
-    drawColorRect(clip.left, clip.top, clip.right, clip.bottom, color);
+    drawColorRect(clip.left, clip.top, clip.right, clip.bottom, color, mode);
 }
 
 void OpenGLRenderer::drawRect(float left, float top, float right, float bottom, const SkPaint* p) {
-    // TODO Support more than  just color
-    // TODO: Set the transfer mode
-    drawColorRect(left, top, right, bottom, p->getColor());
+    SkXfermode::Mode mode;
+
+    const bool isMode = SkXfermode::IsMode(p->getXfermode(), &mode);
+    if (!isMode) {
+        // Assume SRC_OVER
+        mode = SkXfermode::kSrcOver_Mode;
+    }
+
+    // Skia draws using the color's alpha channel if < 255
+    // Otherwise, it uses the paint's alpha
+    int color = p->getColor();
+    if (((color >> 24) & 0xFF) == 255) {
+        color |= p->getAlpha() << 24;
+    }
+
+    drawColorRect(left, top, right, bottom, color, mode);
 }
 
-void OpenGLRenderer::drawColorRect(float left, float top, float right, float bottom, int color) {
-    GLfloat a = ((color >> 24) & 0xFF) / 255.0f;
-    GLfloat r = ((color >> 16) & 0xFF) / 255.0f;
-    GLfloat g = ((color >>  8) & 0xFF) / 255.0f;
-    GLfloat b = ((color      ) & 0xFF) / 255.0f;
+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);
+    }
 
     mModelView.loadTranslate(left, top, 0.0f);
     mModelView.scale(right - left, bottom - top, 1.0f);
@@ -368,6 +399,10 @@
     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,
@@ -379,15 +414,16 @@
 
     // TODO Correctly set the blend function, based on texture format and xfermode
     glEnable(GL_BLEND);
-    glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
+    // For not pre-multiplied sources
+    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
 
     glBindTexture(GL_TEXTURE_2D, texture);
 
     glActiveTexture(GL_TEXTURE0);
     glUniform1i(mDrawTextureShader->sampler, 0);
 
-    const GLvoid* p = &gDrawTextureVertices[0].position[0];
-    const GLvoid* t = &gDrawTextureVertices[0].texture[0];
+    const GLvoid* p = &mDrawTextureVertices[0].position[0];
+    const GLvoid* t = &mDrawTextureVertices[0].texture[0];
 
     glEnableVertexAttribArray(mDrawTextureShader->position);
     glVertexAttribPointer(mDrawTextureShader->position, 2, GL_FLOAT, GL_FALSE,
@@ -408,5 +444,16 @@
     glDisable(GL_BLEND);
 }
 
+void OpenGLRenderer::resetDrawTextureTexCoords(float u1, float v1, float u2, float v2) {
+    mDrawTextureVertices[0].texture[0] = u1;
+    mDrawTextureVertices[0].texture[1] = v2;
+    mDrawTextureVertices[1].texture[0] = u2;
+    mDrawTextureVertices[1].texture[1] = v2;
+    mDrawTextureVertices[2].texture[0] = u1;
+    mDrawTextureVertices[2].texture[1] = v1;
+    mDrawTextureVertices[3].texture[0] = u2;
+    mDrawTextureVertices[3].texture[1] = v1;
+}
+
 }; // namespace uirenderer
 }; // namespace android
diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h
index 1e031b8..85fa541 100644
--- a/libs/hwui/OpenGLRenderer.h
+++ b/libs/hwui/OpenGLRenderer.h
@@ -53,6 +53,15 @@
     float texture[2];
 }; // struct TextureVertex
 
+/**
+ * Structure mapping Skia xfermodes to OpenGL blending factors.
+ */
+struct Blender {
+    SkXfermode::Mode mode;
+    GLenum src;
+    GLenum dst;
+}; // struct Blender
+
 ///////////////////////////////////////////////////////////////////////////////
 // Renderer
 ///////////////////////////////////////////////////////////////////////////////
@@ -125,8 +134,10 @@
      * @param right The right coordinate of the rectangle
      * @param bottom The bottom coordinate of the rectangle
      * @param color The rectangle's ARGB color, defined as a packed 32 bits word
+     * @param mode The Skia xfermode to use
      */
-    void drawColorRect(float left, float top, float right, float bottom, int color);
+    void drawColorRect(float left, float top, float right, float bottom,
+    		int color, SkXfermode::Mode mode);
 
     /**
      * Draws a textured rectangle with the specified texture. The specified coordinates
@@ -142,6 +153,19 @@
     void drawTextureRect(float left, float top, float right, float bottom, GLuint texture,
             float alpha);
 
+    /**
+     * Resets the texture coordinates stored in mDrawTextureVertices. Setting the values
+     * back to default is achieved by calling:
+     *
+     * resetDrawTextureTexCoords(0.0f, 1.0f, 1.0f, 0.0f);
+     *
+     * @param u1 The left coordinate of the texture
+     * @param v1 The bottom coordinate of the texture
+     * @param u2 The right coordinate of the texture
+     * @param v2 The top coordinate of the texture
+     */
+    void resetDrawTextureTexCoords(float u1, float v1, float u2, float v2);
+
     // Dimensions of the drawing surface
     int mWidth, mHeight;
 
@@ -161,6 +185,9 @@
     // Shaders
     sp<DrawColorProgram> mDrawColorShader;
     sp<DrawTextureProgram> mDrawTextureShader;
+
+    // Used to draw textured quads
+    TextureVertex mDrawTextureVertices[4];
 }; // class OpenGLRenderer
 
 }; // namespace uirenderer
diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml
index 2ff0b2c..7dfa672 100644
--- a/tests/HwAccelerationTest/AndroidManifest.xml
+++ b/tests/HwAccelerationTest/AndroidManifest.xml
@@ -21,12 +21,24 @@
         android:label="HwUi"
         android:hardwareAccelerated="true">
 
-        <activity android:name="HwUiActivity">
+        <activity
+                android:name="HwUiActivity"
+                android:label="_Layers">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity>
 
+        <activity
+                android:name="XfermodeActivity"
+                android:label="_Xfermodes"
+                android:theme="@android:style/Theme.Translucent">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+        
     </application>
 </manifest>
diff --git a/tests/HwAccelerationTest/src/com/google/android/test/hwui/XfermodeActivity.java b/tests/HwAccelerationTest/src/com/google/android/test/hwui/XfermodeActivity.java
new file mode 100644
index 0000000..440d622
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/google/android/test/hwui/XfermodeActivity.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.test.hwui;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.os.Bundle;
+import android.view.View;
+
+import static android.graphics.PorterDuff.Mode;
+
+@SuppressWarnings({"UnusedDeclaration"})
+public class XfermodeActivity extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        
+        setContentView(new XfermodesView(this));
+    }
+    
+    @SuppressWarnings({"UnusedDeclaration"})
+    static int dipToPx(Context c, int dip) {
+        return (int) (c.getResources().getDisplayMetrics().density * dip + 0.5f);
+    }
+
+    static class XfermodesView extends View {
+        private final Paint mBluePaint;
+        private final Paint mRedPaint;
+
+        XfermodesView(Context c) {
+            super(c);
+
+            mBluePaint = new Paint();
+            mBluePaint.setColor(0xff0000ff);
+            
+            mRedPaint = new Paint();
+            mRedPaint.setColor(0x7fff0000);
+        }
+
+        @Override
+        protected void onDraw(Canvas canvas) {
+            super.onDraw(canvas);
+            //canvas.drawRGB(255, 255, 255);
+
+            canvas.translate(100.0f, 100.0f);
+            
+            // SRC modes
+            canvas.save();
+
+            drawRects(canvas, Mode.SRC_OVER);
+            canvas.translate(0.0f, 100.0f);
+
+            drawRects(canvas, Mode.SRC_IN);
+            canvas.translate(0.0f, 100.0f);            
+
+            drawRects(canvas, Mode.SRC_OUT);
+            canvas.translate(0.0f, 100.0f);
+
+            drawRects(canvas, Mode.SRC_ATOP);
+            canvas.translate(0.0f, 100.0f);
+            
+            drawRects(canvas, Mode.SRC);
+
+            canvas.restore();
+            
+            canvas.translate(100.0f, 0.0f);
+            
+            // DST modes
+            canvas.save();
+
+            drawRects(canvas, Mode.DST_OVER);
+            canvas.translate(0.0f, 100.0f);
+
+            drawRects(canvas, Mode.DST_IN);
+            canvas.translate(0.0f, 100.0f);            
+
+            drawRects(canvas, Mode.DST_OUT);
+            canvas.translate(0.0f, 100.0f);
+
+            drawRects(canvas, Mode.DST_ATOP);
+            canvas.translate(0.0f, 100.0f);
+            
+            drawRects(canvas, Mode.DST);
+
+            canvas.restore();
+            
+            canvas.translate(100.0f, 0.0f);
+            
+            // Other modes
+            canvas.save();
+
+            drawRects(canvas, Mode.CLEAR);
+            canvas.translate(0.0f, 100.0f);
+
+            drawRects(canvas, Mode.XOR);
+            
+            canvas.translate(0.0f, 100.0f);
+            
+            mBluePaint.setAlpha(127);
+            canvas.drawRect(0.0f, 0.0f, 50.0f, 50.0f, mBluePaint);
+            
+            canvas.translate(0.0f, 100.0f);
+            
+            mBluePaint.setAlpha(10);
+            mBluePaint.setColor(0x7f0000ff);
+            canvas.drawRect(0.0f, 0.0f, 50.0f, 50.0f, mBluePaint);
+            
+            mBluePaint.setColor(0xff0000ff);
+            mBluePaint.setAlpha(255);
+
+            canvas.restore();
+        }
+
+        private void drawRects(Canvas canvas, PorterDuff.Mode mode) {
+            canvas.drawRect(0.0f, 0.0f, 50.0f, 50.0f, mBluePaint);
+
+            canvas.save();
+            canvas.translate(25.0f, 25.0f);
+            mRedPaint.setXfermode(new PorterDuffXfermode(mode));
+            canvas.drawRect(0.0f, 0.0f, 50.0f, 50.0f, mRedPaint);
+            canvas.restore();
+        }
+    }
+}