Add support for advanced blend modes with the framebuffer.

This adds the ability to blend with the framebuffer using Darken,
Lighten, Add, Multiply, Overlay and Screen.

Change-Id: Iae01a53797d4ad39c373cba6ff2a42293129da1a
diff --git a/libs/hwui/Extensions.h b/libs/hwui/Extensions.h
index 7778290..d50d36e 100644
--- a/libs/hwui/Extensions.h
+++ b/libs/hwui/Extensions.h
@@ -26,17 +26,33 @@
 namespace android {
 namespace uirenderer {
 
+///////////////////////////////////////////////////////////////////////////////
+// Defines
+///////////////////////////////////////////////////////////////////////////////
+
+// Debug
+#define DEBUG_EXTENSIONS 0
+
+// Debug
+#if DEBUG_EXTENSIONS
+    #define EXT_LOGD(...) LOGD(__VA_ARGS__)
+#else
+    #define EXT_LOGD(...)
+#endif
+
 class Extensions {
 public:
     Extensions() {
         const char* buffer = (const char*) glGetString(GL_EXTENSIONS);
         const char* current = buffer;
         const char* head = current;
+        EXT_LOGD("Available GL extensions:");
         do {
             head = strchr(current, ' ');
             String8 s(current, head ? head - current : strlen(current));
             if (s.length()) {
                 mExtensionList.add(s);
+                EXT_LOGD("  %s", s.string());
             }
             current = head + 1;
         } while (head);
@@ -44,6 +60,7 @@
         mHasNPot = hasExtension("GL_OES_texture_npot");
         mHasDrawPath = hasExtension("GL_NV_draw_path");
         mHasCoverageSample = hasExtension("GL_NV_coverage_sample");
+        mHasFramebufferFetch = hasExtension("GL_NV_shader_framebuffer_fetch");
 
         mExtensions = buffer;
     }
@@ -51,6 +68,7 @@
     inline bool hasNPot() const { return mHasNPot; }
     inline bool hasDrawPath() const { return mHasDrawPath; }
     inline bool hasCoverageSample() const { return mHasCoverageSample; }
+    inline bool hasFramebufferFetch() const { return mHasFramebufferFetch; }
 
     bool hasExtension(const char* extension) const {
         const String8 s(extension);
@@ -69,6 +87,7 @@
     bool mHasNPot;
     bool mHasDrawPath;
     bool mHasCoverageSample;
+    bool mHasFramebufferFetch;
 }; // class Extensions
 
 }; // namespace uirenderer
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index f70bca7..6c90704 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -240,10 +240,14 @@
 
     if (p) {
         alpha = p->getAlpha();
-        const bool isMode = SkXfermode::IsMode(p->getXfermode(), &mode);
-        if (!isMode) {
-            // Assume SRC_OVER
-            mode = SkXfermode::kSrcOver_Mode;
+        if (!mExtensions.hasFramebufferFetch()) {
+            const bool isMode = SkXfermode::IsMode(p->getXfermode(), &mode);
+            if (!isMode) {
+                // Assume SRC_OVER
+                mode = SkXfermode::kSrcOver_Mode;
+            }
+        } else {
+            mode = getXfermode(p->getXfermode());
         }
     } else {
         mode = SkXfermode::kSrcOver_Mode;
@@ -519,11 +523,14 @@
     }
 
     SkXfermode::Mode mode;
-
-    const bool isMode = SkXfermode::IsMode(p->getXfermode(), &mode);
-    if (!isMode) {
-        // Assume SRC_OVER
-        mode = SkXfermode::kSrcOver_Mode;
+    if (!mExtensions.hasFramebufferFetch()) {
+        const bool isMode = SkXfermode::IsMode(p->getXfermode(), &mode);
+        if (!isMode) {
+            // Assume SRC_OVER
+            mode = SkXfermode::kSrcOver_Mode;
+        }
+    } else {
+        mode = getXfermode(p->getXfermode());
     }
 
     // Skia draws using the color's alpha channel if < 255
@@ -731,11 +738,12 @@
          }
      }
 
+     // Setup the blending mode
+     chooseBlending(true, mode, description);
+
      // Build and use the appropriate shader
      useProgram(mCaches.programCache.get(description));
 
-     // Setup the blending mode
-     chooseBlending(true, mode);
      bindTexture(texture, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE, textureUnit);
      glUniform1i(mCaches.currentProgram->getUniform("sampler"), textureUnit);
 
@@ -837,9 +845,6 @@
 
     GLuint textureUnit = 0;
 
-    // Setup the blending mode
-    chooseBlending(alpha < 255 || (mShader && mShader->blend()), mode);
-
     // Describe the required shaders
     ProgramDescription description;
     if (mShader) {
@@ -849,6 +854,9 @@
         mColorFilter->describe(description, mExtensions);
     }
 
+    // Setup the blending mode
+    chooseBlending(alpha < 255 || (mShader && mShader->blend()), mode, description);
+
     // Build and use the appropriate shader
     useProgram(mCaches.programCache.get(description));
 
@@ -907,11 +915,11 @@
     mModelView.loadTranslate(left, top, 0.0f);
     mModelView.scale(right - left, bottom - top, 1.0f);
 
+    chooseBlending(blend || alpha < 1.0f, mode, description);
+
     useProgram(mCaches.programCache.get(description));
     mCaches.currentProgram->set(mOrthoMatrix, mModelView, *mSnapshot->transform);
 
-    chooseBlending(blend || alpha < 1.0f, mode);
-
     // Texture
     bindTexture(texture, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE, 0);
     glUniform1i(mCaches.currentProgram->getUniform("sampler"), 0);
@@ -939,23 +947,35 @@
     glDisableVertexAttribArray(texCoordsSlot);
 }
 
-void OpenGLRenderer::chooseBlending(bool blend, SkXfermode::Mode mode, bool isPremultiplied) {
+void OpenGLRenderer::chooseBlending(bool blend, SkXfermode::Mode mode,
+        ProgramDescription& description) {
     blend = blend || mode != SkXfermode::kSrcOver_Mode;
     if (blend) {
-        if (!mCaches.blend) {
-            glEnable(GL_BLEND);
-        }
+        if (mode < SkXfermode::kPlus_Mode) {
+            if (!mCaches.blend) {
+                glEnable(GL_BLEND);
+            }
 
-        GLenum sourceMode = gBlends[mode].src;
-        GLenum destMode = gBlends[mode].dst;
-        if (!isPremultiplied && sourceMode == GL_ONE) {
-            sourceMode = GL_SRC_ALPHA;
-        }
+            GLenum sourceMode = gBlends[mode].src;
+            GLenum destMode = gBlends[mode].dst;
 
-        if (sourceMode != mCaches.lastSrcMode || destMode != mCaches.lastDstMode) {
-            glBlendFunc(sourceMode, destMode);
-            mCaches.lastSrcMode = sourceMode;
-            mCaches.lastDstMode = destMode;
+            if (sourceMode != mCaches.lastSrcMode || destMode != mCaches.lastDstMode) {
+                glBlendFunc(sourceMode, destMode);
+                mCaches.lastSrcMode = sourceMode;
+                mCaches.lastDstMode = destMode;
+            }
+        } else {
+            // These blend modes are not supported by OpenGL directly and have
+            // to be implemented using shaders. Since the shader will perform
+            // the blending, turn blending off here
+            if (mExtensions.hasFramebufferFetch()) {
+                description.framebufferMode = mode;
+            }
+
+            if (mCaches.blend) {
+                glDisable(GL_BLEND);
+            }
+            blend = false;
         }
     } else if (mCaches.blend) {
         glDisable(GL_BLEND);
@@ -983,10 +1003,14 @@
 
 void OpenGLRenderer::getAlphaAndMode(const SkPaint* paint, int* alpha, SkXfermode::Mode* mode) {
     if (paint) {
-        const bool isMode = SkXfermode::IsMode(paint->getXfermode(), mode);
-        if (!isMode) {
-            // Assume SRC_OVER
-            *mode = SkXfermode::kSrcOver_Mode;
+        if (!mExtensions.hasFramebufferFetch()) {
+            const bool isMode = SkXfermode::IsMode(paint->getXfermode(), mode);
+            if (!isMode) {
+                // Assume SRC_OVER
+                *mode = SkXfermode::kSrcOver_Mode;
+            }
+        } else {
+            *mode = getXfermode(paint->getXfermode());
         }
 
         // Skia draws using the color's alpha channel if < 255
@@ -1002,6 +1026,13 @@
     }
 }
 
+SkXfermode::Mode OpenGLRenderer::getXfermode(SkXfermode* mode) {
+    if (mode == NULL) {
+        return SkXfermode::kSrcOver_Mode;
+    }
+    return mode->fMode;
+}
+
 void OpenGLRenderer::bindTexture(GLuint texture, GLenum wrapS, GLenum wrapT, GLuint textureUnit) {
     glActiveTexture(gTextureUnits[textureUnit]);
     glBindTexture(GL_TEXTURE_2D, texture);
diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h
index 0e90d20..50f42c2 100644
--- a/libs/hwui/OpenGLRenderer.h
+++ b/libs/hwui/OpenGLRenderer.h
@@ -322,7 +322,9 @@
      * 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 = true);
+    inline void chooseBlending(bool blend, SkXfermode::Mode mode, ProgramDescription& description);
+
+    inline SkXfermode::Mode getXfermode(SkXfermode* mode);
 
     /**
      * Use the specified program with the current GL context. If the program is already
diff --git a/libs/hwui/ProgramCache.cpp b/libs/hwui/ProgramCache.cpp
index 39fe85a..ff65c1b 100644
--- a/libs/hwui/ProgramCache.cpp
+++ b/libs/hwui/ProgramCache.cpp
@@ -66,6 +66,8 @@
 // Fragment shaders snippets
 ///////////////////////////////////////////////////////////////////////////////
 
+const char* gFS_Header_Extension_FramebufferFetch =
+        "#extension GL_NV_shader_framebuffer_fetch : enable\n\n";
 const char* gFS_Header =
         "precision mediump float;\n\n";
 const char* gFS_Uniforms_Color =
@@ -115,6 +117,8 @@
         "    fragColor = bitmapColor * fragColor.a;\n";
 const char* gFS_Main_FragColor =
         "    gl_FragColor = fragColor;\n";
+const char* gFS_Main_FragColor_Blend =
+        "    gl_FragColor = blendFramebuffer(fragColor, gl_LastFragColor);\n";
 const char* gFS_Main_ApplyColorOp[4] = {
         // None
         "",
@@ -281,7 +285,14 @@
 
 String8 ProgramCache::generateFragmentShader(const ProgramDescription& description) {
     // Set the default precision
-    String8 shader(gFS_Header);
+    String8 shader;
+
+    bool blendFramebuffer = description.framebufferMode >= SkXfermode::kPlus_Mode;
+    if (blendFramebuffer) {
+        shader.append(gFS_Header_Extension_FramebufferFetch);
+    }
+
+    shader.append(gFS_Header);
 
     // Varyings
     if (description.hasTexture) {
@@ -315,6 +326,9 @@
     if (description.colorOp == ProgramDescription::kColorBlend) {
         generateBlend(shader, "blendColors", description.colorMode);
     }
+    if (blendFramebuffer) {
+        generateBlend(shader, "blendFramebuffer", description.framebufferMode);
+    }
     if (description.isBitmapNpot) {
         generateTextureWrap(shader, description.bitmapWrapS, description.bitmapWrapT);
     }
@@ -359,7 +373,11 @@
         // Apply the color op if needed
         shader.append(gFS_Main_ApplyColorOp[description.colorOp]);
         // Output the fragment
-        shader.append(gFS_Main_FragColor);
+        if (!blendFramebuffer) {
+            shader.append(gFS_Main_FragColor);
+        } else {
+            shader.append(gFS_Main_FragColor_Blend);
+        }
     }
     // End the shader
     shader.append(gFS_Footer);
diff --git a/libs/hwui/ProgramCache.h b/libs/hwui/ProgramCache.h
index f211ab6..8f5304d 100644
--- a/libs/hwui/ProgramCache.h
+++ b/libs/hwui/ProgramCache.h
@@ -35,7 +35,7 @@
 ///////////////////////////////////////////////////////////////////////////////
 
 // Debug
-#define DEBUG_PROGRAM_CACHE 0
+#define DEBUG_PROGRAM_CACHE 1
 
 // Debug
 #if DEBUG_PROGRAM_CACHE
@@ -61,6 +61,7 @@
 #define PROGRAM_MAX_XFERMODE 0x1f
 #define PROGRAM_XFERMODE_SHADER_SHIFT 26
 #define PROGRAM_XFERMODE_COLOR_OP_SHIFT 20
+#define PROGRAM_XFERMODE_FRAMEBUFFER_SHIFT 14
 
 #define PROGRAM_BITMAP_WRAPS_SHIFT 9
 #define PROGRAM_BITMAP_WRAPT_SHIFT 11
@@ -93,7 +94,8 @@
         hasBitmap(false), isBitmapNpot(false), hasGradient(false),
         shadersMode(SkXfermode::kClear_Mode), isBitmapFirst(false),
         bitmapWrapS(GL_CLAMP_TO_EDGE), bitmapWrapT(GL_CLAMP_TO_EDGE),
-        colorOp(kColorNone), colorMode(SkXfermode::kClear_Mode) {
+        colorOp(kColorNone), colorMode(SkXfermode::kClear_Mode),
+        framebufferMode(SkXfermode::kClear_Mode) {
     }
 
     // Texturing
@@ -113,6 +115,10 @@
     int colorOp;
     SkXfermode::Mode colorMode;
 
+    // Framebuffer blending (requires Extensions.hasFramebufferFetch())
+    // Ignored for all values < SkXfermode::kPlus_Mode
+    SkXfermode::Mode framebufferMode;
+
     inline uint32_t getEnumForWrap(GLenum wrap) const {
         switch (wrap) {
             case GL_CLAMP_TO_EDGE:
@@ -156,6 +162,7 @@
             case kColorNone:
                 break;
         }
+        key |= (framebufferMode & PROGRAM_MAX_XFERMODE) << PROGRAM_XFERMODE_FRAMEBUFFER_SHIFT;
         return key;
     }
 }; // struct ProgramDescription
diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml
index db1e5ef..ec1cbf1 100644
--- a/tests/HwAccelerationTest/AndroidManifest.xml
+++ b/tests/HwAccelerationTest/AndroidManifest.xml
@@ -230,6 +230,16 @@
         </activity>
 
         <activity
+                android:name="FramebufferBlendActivity"
+                android:label="_FramebufferBlend">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+        
+
+        <activity
                 android:name="StackActivity"
                 android:label="_Stacks">
             <intent-filter>
diff --git a/tests/HwAccelerationTest/src/com/google/android/test/hwui/FramebufferBlendActivity.java b/tests/HwAccelerationTest/src/com/google/android/test/hwui/FramebufferBlendActivity.java
new file mode 100644
index 0000000..ef84b67
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/google/android/test/hwui/FramebufferBlendActivity.java
@@ -0,0 +1,111 @@
+/*
+ * 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.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.LinearGradient;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.Shader;
+import android.os.Bundle;
+import android.view.View;
+
+@SuppressWarnings({"UnusedDeclaration"})
+public class FramebufferBlendActivity extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(new BlendView(this));
+    }
+
+    static class BlendView extends View {
+        private int mTexWidth;
+        private int mTexHeight;
+        private Paint mPaint;
+        private LinearGradient mHorGradient;
+        private Bitmap mTexture;
+
+        BlendView(Context c) {
+            super(c);
+
+            mTexture = BitmapFactory.decodeResource(c.getResources(), R.drawable.sunset1);
+            mTexWidth = mTexture.getWidth();
+            mTexHeight = mTexture.getHeight();
+
+            mHorGradient = new LinearGradient(0.0f, 0.0f, mTexWidth, 0.0f,
+                    Color.BLACK, Color.WHITE, Shader.TileMode.CLAMP);
+
+            mPaint = new Paint();
+        }
+
+        @Override
+        protected void onDraw(Canvas canvas) {
+            super.onDraw(canvas);
+            canvas.drawRGB(255, 255, 255);
+
+            canvas.save();
+            canvas.translate(40.0f, 40.0f);
+
+            drawBlendedBitmap(canvas, PorterDuff.Mode.DARKEN);
+            drawBlendedBitmap(canvas, PorterDuff.Mode.LIGHTEN);
+            drawBlendedBitmap(canvas, PorterDuff.Mode.MULTIPLY);
+
+            canvas.restore();
+
+            canvas.save();
+            canvas.translate(40.0f + mTexWidth + 40.0f, 40.0f);
+
+            drawBlendedBitmap(canvas, PorterDuff.Mode.SCREEN);
+            drawBlendedBitmap(canvas, PorterDuff.Mode.ADD);
+            drawBlendedBitmapInverse(canvas, PorterDuff.Mode.OVERLAY);
+
+            canvas.restore();
+        }
+
+        private void drawBlendedBitmap(Canvas canvas, PorterDuff.Mode mode) {
+            mPaint.setShader(null);
+            mPaint.setXfermode(null);
+            canvas.drawBitmap(mTexture, 0.0f, 0.0f, mPaint);
+
+            mPaint.setShader(mHorGradient);
+            mPaint.setXfermode(new PorterDuffXfermode(mode));
+            canvas.drawRect(0.0f, 0.0f, mTexWidth, mTexHeight, mPaint);
+
+            canvas.translate(0.0f, 40.0f + mTexHeight);
+        }
+
+        private void drawBlendedBitmapInverse(Canvas canvas, PorterDuff.Mode mode) {
+            mPaint.setXfermode(null);
+            mPaint.setShader(mHorGradient);
+            canvas.drawRect(0.0f, 0.0f, mTexWidth, mTexHeight, mPaint);
+
+            mPaint.setXfermode(new PorterDuffXfermode(mode));
+            mPaint.setShader(null);
+            canvas.drawBitmap(mTexture, 0.0f, 0.0f, mPaint);
+
+            canvas.translate(0.0f, 40.0f + mTexHeight);
+        }
+    }
+}