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
This commit is contained in:
@@ -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 @@ public:
|
||||
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 @@ public:
|
||||
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 @@ private:
|
||||
bool mHasNPot;
|
||||
bool mHasDrawPath;
|
||||
bool mHasCoverageSample;
|
||||
bool mHasFramebufferFetch;
|
||||
}; // class Extensions
|
||||
|
||||
}; // namespace uirenderer
|
||||
|
||||
@@ -240,10 +240,14 @@ int OpenGLRenderer::saveLayer(float left, float top, float right, float bottom,
|
||||
|
||||
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 @@ void OpenGLRenderer::drawRect(float left, float top, float right, float bottom,
|
||||
}
|
||||
|
||||
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 @@ void OpenGLRenderer::setupTextureAlpha8(GLuint texture, uint32_t width, uint32_t
|
||||
}
|
||||
}
|
||||
|
||||
// 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 @@ void OpenGLRenderer::drawColorRect(float left, float top, float right, float bot
|
||||
|
||||
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 @@ void OpenGLRenderer::drawColorRect(float left, float top, float right, float bot
|
||||
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 @@ void OpenGLRenderer::drawTextureMesh(float left, float top, float right, float b
|
||||
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 @@ void OpenGLRenderer::drawTextureMesh(float left, float top, float right, float b
|
||||
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::resetDrawTextureTexCoords(float u1, float v1, float u2, flo
|
||||
|
||||
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 @@ void OpenGLRenderer::getAlphaAndMode(const SkPaint* paint, int* alpha, SkXfermod
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
@@ -322,7 +322,9 @@ private:
|
||||
* 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
|
||||
|
||||
@@ -66,6 +66,8 @@ const char* gVS_Footer =
|
||||
// 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 @@ const char* gFS_Main_BitmapShader_Modulate =
|
||||
" 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::generateVertexShader(const ProgramDescription& description
|
||||
|
||||
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 @@ String8 ProgramCache::generateFragmentShader(const ProgramDescription& descripti
|
||||
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 @@ String8 ProgramCache::generateFragmentShader(const ProgramDescription& descripti
|
||||
// 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);
|
||||
|
||||
@@ -35,7 +35,7 @@ namespace uirenderer {
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Debug
|
||||
#define DEBUG_PROGRAM_CACHE 0
|
||||
#define DEBUG_PROGRAM_CACHE 1
|
||||
|
||||
// Debug
|
||||
#if DEBUG_PROGRAM_CACHE
|
||||
@@ -61,6 +61,7 @@ namespace uirenderer {
|
||||
#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 @@ struct ProgramDescription {
|
||||
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 @@ struct ProgramDescription {
|
||||
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 @@ struct ProgramDescription {
|
||||
case kColorNone:
|
||||
break;
|
||||
}
|
||||
key |= (framebufferMode & PROGRAM_MAX_XFERMODE) << PROGRAM_XFERMODE_FRAMEBUFFER_SHIFT;
|
||||
return key;
|
||||
}
|
||||
}; // struct ProgramDescription
|
||||
|
||||
@@ -229,6 +229,16 @@
|
||||
</intent-filter>
|
||||
</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">
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user