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);
+ }
+ }
+}