Explorar o código

Fixes handling of blend equations and factors in GLRenderer (#848)

* Stronger separation of BlendMode.Custom from the other blend modes
* Custom blend equations and factors now only gets used on BlendMode.Custom
* Updated some JavaDocs in RenderState class
theMinka %!s(int64=7) %!d(string=hai) anos
pai
achega
cd7c60cc59

+ 81 - 102
jme3-core/src/main/java/com/jme3/material/RenderState.java

@@ -331,9 +331,17 @@ public class RenderState implements Cloneable, Savable {
          */
         Exclusion,
         /**
-         * Allows for custom blending by using glBlendFuncSeparate.
+         * Uses the blend equations and blend factors defined by the render state.
          * <p>
-         * 
+         * These attributes can be set by using the following methods:
+         * <ul>
+         * <li>{@link RenderState#setBlendEquation(BlendEquation)}<br/>
+         * <li>{@link RenderState#setBlendEquationAlpha(BlendEquationAlpha)}<br/>
+         * <li>{@link RenderState#setCustomBlendFactors(BlendFunc, BlendFunc, BlendFunc, BlendFunc)}<br/>
+         * </ul>
+         * <p>
+         * Result.RGB = BlendEquation( sfactorRGB * Source.RGB , dfactorRGB * Destination.RGB )<br/>
+         * Result.A = BlendEquationAlpha( sfactorAlpha * Source.A , dfactorAlpha * Destination.A )
          */
         Custom
     }
@@ -425,8 +433,6 @@ public class RenderState implements Cloneable, Savable {
         ADDITIONAL.applyDepthWrite = false;
         ADDITIONAL.applyDepthTest = false;
         ADDITIONAL.applyColorWrite = false;
-        ADDITIONAL.applyBlendEquation = false;
-        ADDITIONAL.applyBlendEquationAlpha = false;
         ADDITIONAL.applyBlendMode = false;
         ADDITIONAL.applyPolyOffset = false;
     }
@@ -441,9 +447,7 @@ public class RenderState implements Cloneable, Savable {
     boolean colorWrite = true;
     boolean applyColorWrite = true;
     BlendEquation blendEquation = BlendEquation.Add;
-    boolean applyBlendEquation = true;
     BlendEquationAlpha blendEquationAlpha = BlendEquationAlpha.InheritColor;
-    boolean applyBlendEquationAlpha = true;
     BlendMode blendMode = BlendMode.Off;
     boolean applyBlendMode = true;
     float offsetFactor = 0;
@@ -466,10 +470,10 @@ public class RenderState implements Cloneable, Savable {
     TestFunction frontStencilFunction = TestFunction.Always;
     TestFunction backStencilFunction = TestFunction.Always;
     int cachedHashCode = -1;
-    BlendFunc sfactorRGB=BlendFunc.One;
-    BlendFunc dfactorRGB=BlendFunc.Zero;
-    BlendFunc sfactorAlpha=BlendFunc.One;
-    BlendFunc dfactorAlpha=BlendFunc.Zero;
+    BlendFunc sfactorRGB = BlendFunc.One;
+    BlendFunc dfactorRGB = BlendFunc.One;
+    BlendFunc sfactorAlpha = BlendFunc.One;
+    BlendFunc dfactorAlpha = BlendFunc.One;
             
     public void write(JmeExporter ex) throws IOException {
         OutputCapsule oc = ex.getCapsule(this);
@@ -507,8 +511,6 @@ public class RenderState implements Cloneable, Savable {
         oc.write(applyDepthWrite, "applyDepthWrite", true);
         oc.write(applyDepthTest, "applyDepthTest", true);
         oc.write(applyColorWrite, "applyColorWrite", true);
-        oc.write(applyBlendEquation, "applyBlendEquation", true);
-        oc.write(applyBlendEquationAlpha, "applyBlendEquationAlpha", true);
         oc.write(applyBlendMode, "applyBlendMode", true);
         oc.write(applyPolyOffset, "applyPolyOffset", true);
         oc.write(applyDepthFunc, "applyDepthFunc", true);
@@ -541,9 +543,9 @@ public class RenderState implements Cloneable, Savable {
         depthFunc = ic.readEnum("depthFunc", TestFunction.class, TestFunction.LessOrEqual);
         lineWidth = ic.readFloat("lineWidth", 1);
         sfactorRGB = ic.readEnum("sfactorRGB", BlendFunc.class, BlendFunc.One);
-        dfactorAlpha = ic.readEnum("dfactorRGB", BlendFunc.class, BlendFunc.Zero);
+        dfactorAlpha = ic.readEnum("dfactorRGB", BlendFunc.class, BlendFunc.One);
         sfactorRGB = ic.readEnum("sfactorAlpha", BlendFunc.class, BlendFunc.One);
-        dfactorAlpha = ic.readEnum("dfactorAlpha", BlendFunc.class, BlendFunc.Zero);
+        dfactorAlpha = ic.readEnum("dfactorAlpha", BlendFunc.class, BlendFunc.One);
 
 
         applyWireFrame = ic.readBoolean("applyWireFrame", true);
@@ -551,14 +553,11 @@ public class RenderState implements Cloneable, Savable {
         applyDepthWrite = ic.readBoolean("applyDepthWrite", true);
         applyDepthTest = ic.readBoolean("applyDepthTest", true);
         applyColorWrite = ic.readBoolean("applyColorWrite", true);
-        applyBlendEquation = ic.readBoolean("applyBlendEquation", true);
-        applyBlendEquationAlpha = ic.readBoolean("applyBlendEquationAlpha", true);
         applyBlendMode = ic.readBoolean("applyBlendMode", true);
         applyPolyOffset = ic.readBoolean("applyPolyOffset", true);
         applyDepthFunc = ic.readBoolean("applyDepthFunc", true);
         applyLineWidth = ic.readBoolean("applyLineWidth", true);
 
-        
     }
 
     /**
@@ -615,19 +614,32 @@ public class RenderState implements Cloneable, Savable {
             return false;
         }
 
-        if (blendEquation != rs.blendEquation) {
+        if (blendMode != rs.blendMode) {
             return false;
         }
 
-        if (blendEquationAlpha != rs.blendEquationAlpha) {
-            return false;
-        }
+        if (blendMode == BlendMode.Custom) {
+            if (blendEquation != rs.blendEquation) {
+                return false;
+            }
+            if (blendEquationAlpha != rs.blendEquationAlpha) {
+                return false;
+            }
 
-        if (blendMode != rs.blendMode) {
-            return false;
+            if (sfactorRGB != rs.sfactorRGB) {
+                return false;
+            }
+            if (dfactorRGB != rs.dfactorRGB) {
+                return false;
+            }
+            if (sfactorAlpha != rs.sfactorAlpha) {
+                return false;
+            }
+            if (dfactorAlpha != rs.dfactorAlpha) {
+                return false;
+            }
         }
 
-
         if (offsetEnabled != rs.offsetEnabled) {
             return false;
         }
@@ -675,14 +687,6 @@ public class RenderState implements Cloneable, Savable {
         if(lineWidth != rs.lineWidth){
             return false;
         }
-        
-        if (blendMode.equals(BlendMode.Custom)) {
-           return sfactorRGB==rs.getCustomSfactorRGB()
-               && dfactorRGB==rs.getCustomDfactorRGB()
-               && sfactorAlpha==rs.getCustomSfactorAlpha()
-               && dfactorAlpha==rs.getCustomDfactorAlpha();
-           
-        }
 
         return true;
     }
@@ -768,80 +772,68 @@ public class RenderState implements Cloneable, Savable {
     }
 
     /**
-     * Set the blending equation.
+     * Set the blending equation for the color component (RGB).
      * <p>
-     * When blending is enabled, (<code>blendMode</code> is not
-     * {@link BlendMode#Off}) the input pixel will be blended with the pixel
-     * already in the color buffer. The blending equation is determined by the
-     * {@link BlendEquation}. For example, the mode {@link BlendMode#Additive}
-     * and {@link BlendEquation#Add} will add the input pixel's color to the
-     * color already in the color buffer:
+     * The blending equation determines, how the RGB values of the input pixel
+     * will be blended with the RGB values of the pixel already in the color buffer.<br/>
+     * For example, {@link BlendEquation#Add} will add the input pixel's color
+     * to the color already in the color buffer:
      * <br/>
      * <code>Result = Source Color + Destination Color</code>
-     * <br/>
-     * However, the mode {@link BlendMode#Additive}
-     * and {@link BlendEquation#Subtract} will subtract the input pixel's color to the
-     * color already in the color buffer:
-     * <br/>
-     * <code>Result = Source Color - Destination Color</code>
+     * <p>
+     * <b>Note:</b> This gets only used in {@link BlendMode#Custom} mode.
+     * All other blend modes will ignore this setting.
      *
-     * @param blendEquation The blend equation to use. 
+     * @param blendEquation The {@link BlendEquation} to use.
      */
     public void setBlendEquation(BlendEquation blendEquation) {
-        applyBlendEquation = true;
         this.blendEquation = blendEquation;
         cachedHashCode = -1;
     }
-    
+
     /**
      * Set the blending equation for the alpha component.
      * <p>
-     * When blending is enabled, (<code>blendMode</code> is not
-     * {@link BlendMode#Off}) the input pixel will be blended with the pixel
-     * already in the color buffer. The blending equation is determined by the
-     * {@link BlendEquation} and can be overrode for the alpha component using
-     * the {@link BlendEquationAlpha} . For example, the mode
-     * {@link BlendMode#Additive} and {@link BlendEquationAlpha#Add} will add
-     * the input pixel's alpha to the alpha component already in the color
-     * buffer:
+     * The alpha blending equation determines, how the alpha values of the input pixel
+     * will be blended with the alpha values of the pixel already in the color buffer.<br/>
+     * For example, {@link BlendEquationAlpha#Add} will add the input pixel's color
+     * to the color already in the color buffer:
      * <br/>
-     * <code>Result = Source Alpha + Destination Alpha</code>
-     * <br/>
-     * However, the mode {@link BlendMode#Additive} and
-     * {@link BlendEquationAlpha#Subtract} will subtract the input pixel's alpha
-     * to the alpha component already in the color buffer:
-     * <br/>
-     * <code>Result = Source Alpha - Destination Alpha</code>
+     * <code>Result = Source Color + Destination Color</code>
+     * <p>
+     * <b>Note:</b> This gets only used in {@link BlendMode#Custom} mode.
+     * All other blend modes will ignore this setting.
      *
-     * @param blendEquationAlpha The blend equation to use for the alpha
-     *                           component.
+     * @param blendEquationAlpha The {@link BlendEquationAlpha} to use.
      */
     public void setBlendEquationAlpha(BlendEquationAlpha blendEquationAlpha) {
-        applyBlendEquationAlpha = true;
         this.blendEquationAlpha = blendEquationAlpha;
         cachedHashCode = -1;
     }
 
-    
     /**
-     * Sets the custom blend factors for <code>BlendMode.Custom</code> as 
-     * defined by the appropriate <code>BlendFunc</code>.
-     * 
+     * Sets the blend factors used for the source and destination color.
+     * <p>
+     * These factors will be multiplied with the color values of the input pixel
+     * and the pixel already in the color buffer, before both colors gets combined by the {@link BlendEquation}.
+     * <p>
+     * <b>Note:</b> This gets only used in {@link BlendMode#Custom} mode.
+     * All other blend modes will ignore this setting.
+     *
      * @param sfactorRGB   The source blend factor for RGB components.
      * @param dfactorRGB   The destination blend factor for RGB components.
      * @param sfactorAlpha The source blend factor for the alpha component.
      * @param dfactorAlpha The destination blend factor for the alpha component.
      */
-    public void setCustomBlendFactors(BlendFunc sfactorRGB, BlendFunc dfactorRGB, BlendFunc sfactorAlpha, BlendFunc dfactorAlpha)
-    {
+    public void setCustomBlendFactors(BlendFunc sfactorRGB, BlendFunc dfactorRGB, BlendFunc sfactorAlpha, BlendFunc dfactorAlpha) {
        this.sfactorRGB = sfactorRGB;
        this.dfactorRGB = dfactorRGB;
        this.sfactorAlpha = sfactorAlpha;
        this.dfactorAlpha = dfactorAlpha;
        cachedHashCode = -1;
     }
-    
-    
+
+
     /**
      * Enable depth testing.
      *
@@ -1374,14 +1366,6 @@ public class RenderState implements Cloneable, Savable {
         return applyBlendMode;
     }
 
-    public boolean isApplyBlendEquation() {
-        return applyBlendEquation;
-    }
-
-    public boolean isApplyBlendEquationAlpha() {
-        return applyBlendEquationAlpha;
-    }
-
     public boolean isApplyColorWrite() {
         return applyColorWrite;
     }
@@ -1511,27 +1495,26 @@ public class RenderState implements Cloneable, Savable {
         } else {
             state.colorWrite = colorWrite;
         }
-        if (additionalState.applyBlendEquation) {
-            state.blendEquation = additionalState.blendEquation;
-        } else {
-            state.blendEquation = blendEquation;
-        }
-        if (additionalState.applyBlendEquationAlpha) {
-            state.blendEquationAlpha = additionalState.blendEquationAlpha;
-        } else {
-            state.blendEquationAlpha = blendEquationAlpha;
-        }        
         if (additionalState.applyBlendMode) {
             state.blendMode = additionalState.blendMode;
-            if (additionalState.getBlendMode().equals(BlendMode.Custom)) {
-               state.setCustomBlendFactors(
-                additionalState.getCustomSfactorRGB(),
-                additionalState.getCustomDfactorRGB(),
-                additionalState.getCustomSfactorAlpha(),
-                additionalState.getCustomDfactorAlpha());
+            if (additionalState.blendMode == BlendMode.Custom) {
+                state.blendEquation = additionalState.blendEquation;
+                state.blendEquationAlpha = additionalState.blendEquationAlpha;
+                state.sfactorRGB = additionalState.sfactorRGB;
+                state.dfactorRGB = additionalState.dfactorRGB;
+                state.sfactorAlpha = additionalState.sfactorAlpha;
+                state.dfactorAlpha = additionalState.dfactorAlpha;
             }
         } else {
             state.blendMode = blendMode;
+            if (blendMode == BlendMode.Custom) {
+                state.blendEquation = blendEquation;
+                state.blendEquationAlpha = blendEquationAlpha;
+                state.sfactorRGB = sfactorRGB;
+                state.dfactorRGB = dfactorRGB;
+                state.sfactorAlpha = sfactorAlpha;
+                state.dfactorAlpha = dfactorAlpha;
+            }
         }
 
         if (additionalState.applyPolyOffset) {
@@ -1608,8 +1591,6 @@ public class RenderState implements Cloneable, Savable {
         applyDepthWrite =  true;
         applyDepthTest =  true;
         applyColorWrite = true;
-        applyBlendEquation =  true;
-        applyBlendEquationAlpha =  true;
         applyBlendMode = true;
         applyPolyOffset =  true;
         applyDepthFunc = true;
@@ -1636,8 +1617,6 @@ public class RenderState implements Cloneable, Savable {
                 + "\ncolorWrite=" + colorWrite
                 + "\napplyColorWrite=" + applyColorWrite
                 + "\nblendEquation=" + blendEquation
-                + "\napplyBlendEquation=" + applyBlendEquation
-                + "\napplyBlendEquationAlpha=" + applyBlendEquationAlpha
                 + "\nblendMode=" + blendMode
                 + "\napplyBlendMode=" + applyBlendMode
                 + "\noffsetEnabled=" + offsetEnabled

+ 29 - 0
jme3-core/src/main/java/com/jme3/renderer/RenderContext.java

@@ -32,6 +32,7 @@
 package com.jme3.renderer;
 
 import com.jme3.material.RenderState;
+import com.jme3.material.RenderState.BlendFunc;
 import com.jme3.math.ColorRGBA;
 import com.jme3.scene.Mesh;
 import com.jme3.scene.VertexBuffer;
@@ -110,6 +111,30 @@ public class RenderContext {
      */
     public RenderState.BlendEquationAlpha blendEquationAlpha = RenderState.BlendEquationAlpha.InheritColor;
 
+    /**
+     * @see RenderState#setCustomBlendFactors(com.jme3.material.RenderState.BlendFunc, com.jme3.material.RenderState.BlendFunc,
+     *      com.jme3.material.RenderState.BlendFunc, com.jme3.material.RenderState.BlendFunc)
+     */
+    public RenderState.BlendFunc sfactorRGB = RenderState.BlendFunc.One;
+
+    /**
+     * @see RenderState#setCustomBlendFactors(com.jme3.material.RenderState.BlendFunc, com.jme3.material.RenderState.BlendFunc,
+     *      com.jme3.material.RenderState.BlendFunc, com.jme3.material.RenderState.BlendFunc)
+     */
+    public RenderState.BlendFunc dfactorRGB = RenderState.BlendFunc.One;
+
+    /**
+     * @see RenderState#setCustomBlendFactors(com.jme3.material.RenderState.BlendFunc, com.jme3.material.RenderState.BlendFunc,
+     *      com.jme3.material.RenderState.BlendFunc, com.jme3.material.RenderState.BlendFunc)
+     */
+    public RenderState.BlendFunc sfactorAlpha = RenderState.BlendFunc.One;
+
+    /**
+     * @see RenderState#setCustomBlendFactors(com.jme3.material.RenderState.BlendFunc, com.jme3.material.RenderState.BlendFunc,
+     *      com.jme3.material.RenderState.BlendFunc, com.jme3.material.RenderState.BlendFunc)
+     */
+    public RenderState.BlendFunc dfactorAlpha = RenderState.BlendFunc.One;
+
     /**
      * @see RenderState#setWireframe(boolean) 
      */
@@ -266,6 +291,10 @@ public class RenderContext {
         blendMode = RenderState.BlendMode.Off;
         blendEquation = RenderState.BlendEquation.Add;
         blendEquationAlpha = RenderState.BlendEquationAlpha.InheritColor;
+        sfactorRGB = BlendFunc.One;
+        dfactorRGB = BlendFunc.One;
+        sfactorAlpha = BlendFunc.One;
+        dfactorAlpha = BlendFunc.One;
         wireframe = false;
         boundShaderProgram = 0;
         boundShader = null;

+ 109 - 60
jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java

@@ -33,6 +33,7 @@ package com.jme3.renderer.opengl;
 
 import com.jme3.material.RenderState;
 import com.jme3.material.RenderState.BlendFunc;
+import com.jme3.material.RenderState.BlendMode;
 import com.jme3.material.RenderState.StencilOperation;
 import com.jme3.material.RenderState.TestFunction;
 import com.jme3.math.*;
@@ -743,68 +744,57 @@ public final class GLRenderer implements Renderer {
             context.cullMode = state.getFaceCullMode();
         }
 
-        if (state.getBlendMode() != context.blendMode) {
-            if (state.getBlendMode() == RenderState.BlendMode.Off) {
-                gl.glDisable(GL.GL_BLEND);
-            } else {
-                if (context.blendMode == RenderState.BlendMode.Off) {
-                    gl.glEnable(GL.GL_BLEND);
-                }
-                switch (state.getBlendMode()) {
-                    case Off:
-                        break;
-                    case Additive:
-                        gl.glBlendFunc(GL.GL_ONE, GL.GL_ONE);
-                        break;
-                    case AlphaAdditive:
-                        gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE);
-                        break;
-                    case Alpha:
-                        gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA);
-                        break;
-                    case PremultAlpha:
-                        gl.glBlendFunc(GL.GL_ONE, GL.GL_ONE_MINUS_SRC_ALPHA);
-                        break;
-                    case Modulate:
-                        gl.glBlendFunc(GL.GL_DST_COLOR, GL.GL_ZERO);
-                        break;
-                    case ModulateX2:
-                        gl.glBlendFunc(GL.GL_DST_COLOR, GL.GL_SRC_COLOR);
-                        break;
-                    case Color:
-                    case Screen:
-                        gl.glBlendFunc(GL.GL_ONE, GL.GL_ONE_MINUS_SRC_COLOR);
-                        break;
-                    case Exclusion:
-                        gl.glBlendFunc(GL.GL_ONE_MINUS_DST_COLOR, GL.GL_ONE_MINUS_SRC_COLOR);
-                        break;
-                    case Custom:
-                        gl.glBlendFuncSeparate(
-                            convertBlendFunc(state.getCustomSfactorRGB()),
-                            convertBlendFunc(state.getCustomDfactorRGB()),
-                            convertBlendFunc(state.getCustomSfactorAlpha()),
-                            convertBlendFunc(state.getCustomDfactorAlpha()));
-                        break;
-                    default:
-                        throw new UnsupportedOperationException("Unrecognized blend mode: "
-                                + state.getBlendMode());
-                }
-                
-                if (state.getBlendEquation() != context.blendEquation || state.getBlendEquationAlpha() != context.blendEquationAlpha) {
-                    int colorMode = convertBlendEquation(state.getBlendEquation());
-                    int alphaMode;
-                    if (state.getBlendEquationAlpha() == RenderState.BlendEquationAlpha.InheritColor) {
-                        alphaMode = colorMode;
-                    } else {
-                        alphaMode = convertBlendEquationAlpha(state.getBlendEquationAlpha());
-                    }
-                    gl.glBlendEquationSeparate(colorMode, alphaMode);
-                    context.blendEquation = state.getBlendEquation();
-                    context.blendEquationAlpha = state.getBlendEquationAlpha();
-                }
+        // Always update the blend equations and factors when using custom blend mode.
+        if (state.getBlendMode() == BlendMode.Custom) {
+            changeBlendMode(BlendMode.Custom);
+
+            blendFuncSeparate(
+                    state.getCustomSfactorRGB(),
+                    state.getCustomDfactorRGB(),
+                    state.getCustomSfactorAlpha(),
+                    state.getCustomDfactorAlpha());
+            blendEquationSeparate(state.getBlendEquation(), state.getBlendEquationAlpha());
+
+        // Update the blend equations and factors only on a mode change for all the other (common) blend modes.
+        } else if (state.getBlendMode() != context.blendMode) {
+            changeBlendMode(state.getBlendMode());
+
+            switch (state.getBlendMode()) {
+                case Off:
+                    break;
+                case Additive:
+                    blendFunc(RenderState.BlendFunc.One, RenderState.BlendFunc.One);
+                    break;
+                case AlphaAdditive:
+                    blendFunc(RenderState.BlendFunc.Src_Alpha, RenderState.BlendFunc.One);
+                    break;
+                case Alpha:
+                    blendFunc(RenderState.BlendFunc.Src_Alpha, RenderState.BlendFunc.One_Minus_Src_Alpha);
+                    break;
+                case PremultAlpha:
+                    blendFunc(RenderState.BlendFunc.One, RenderState.BlendFunc.One_Minus_Src_Alpha);
+                    break;
+                case Modulate:
+                    blendFunc(RenderState.BlendFunc.Dst_Color, RenderState.BlendFunc.Zero);
+                    break;
+                case ModulateX2:
+                    blendFunc(RenderState.BlendFunc.Dst_Color, RenderState.BlendFunc.Src_Color);
+                    break;
+                case Color:
+                case Screen:
+                    blendFunc(RenderState.BlendFunc.One, RenderState.BlendFunc.One_Minus_Src_Color);
+                    break;
+                case Exclusion:
+                    blendFunc(RenderState.BlendFunc.One_Minus_Dst_Color, RenderState.BlendFunc.One_Minus_Src_Color);
+                    break;
+                default:
+                    throw new UnsupportedOperationException("Unrecognized blend mode: "
+                            + state.getBlendMode());
             }
 
-            context.blendMode = state.getBlendMode();
+            // All of the common modes requires the ADD equation.
+            // (This might change in the future?)
+            blendEquationSeparate(RenderState.BlendEquation.Add, RenderState.BlendEquationAlpha.InheritColor);
         }
 
         if (context.stencilTest != state.isStencilTest()
@@ -852,6 +842,65 @@ public final class GLRenderer implements Renderer {
         }
     }
 
+    private void changeBlendMode(RenderState.BlendMode blendMode) {
+        if (blendMode != context.blendMode) {
+            if (blendMode == RenderState.BlendMode.Off) {
+                gl.glDisable(GL.GL_BLEND);
+            } else if (context.blendMode == RenderState.BlendMode.Off) {
+                gl.glEnable(GL.GL_BLEND);
+            }
+
+            context.blendMode = blendMode;
+        }
+    }
+
+    private void blendEquationSeparate(RenderState.BlendEquation blendEquation, RenderState.BlendEquationAlpha blendEquationAlpha) {
+        if (blendEquation != context.blendEquation || blendEquationAlpha != context.blendEquationAlpha) {
+            int glBlendEquation = convertBlendEquation(blendEquation);
+            int glBlendEquationAlpha = blendEquationAlpha == RenderState.BlendEquationAlpha.InheritColor
+                    ? glBlendEquation
+                    : convertBlendEquationAlpha(blendEquationAlpha);
+            gl.glBlendEquationSeparate(glBlendEquation, glBlendEquationAlpha);
+            context.blendEquation = blendEquation;
+            context.blendEquationAlpha = blendEquationAlpha;
+        }
+    }
+
+    private void blendFunc(RenderState.BlendFunc sfactor, RenderState.BlendFunc dfactor) {
+        if (sfactor != context.sfactorRGB
+                || dfactor != context.dfactorRGB
+                || sfactor != context.sfactorAlpha
+                || dfactor != context.dfactorAlpha) {
+
+            gl.glBlendFunc(
+                    convertBlendFunc(sfactor),
+                    convertBlendFunc(dfactor));
+            context.sfactorRGB = sfactor;
+            context.dfactorRGB = dfactor;
+            context.sfactorAlpha = sfactor;
+            context.dfactorAlpha = dfactor;
+        }
+    }
+
+    private void blendFuncSeparate(RenderState.BlendFunc sfactorRGB, RenderState.BlendFunc dfactorRGB,
+            RenderState.BlendFunc sfactorAlpha, RenderState.BlendFunc dfactorAlpha) {
+        if (sfactorRGB != context.sfactorRGB
+                || dfactorRGB != context.dfactorRGB
+                || sfactorAlpha != context.sfactorAlpha
+                || dfactorAlpha != context.dfactorAlpha) {
+
+            gl.glBlendFuncSeparate(
+                    convertBlendFunc(sfactorRGB),
+                    convertBlendFunc(dfactorRGB),
+                    convertBlendFunc(sfactorAlpha),
+                    convertBlendFunc(dfactorAlpha));
+            context.sfactorRGB = sfactorRGB;
+            context.dfactorRGB = dfactorRGB;
+            context.sfactorAlpha = sfactorAlpha;
+            context.dfactorAlpha = dfactorAlpha;
+        }
+    }
+
     private int convertBlendEquation(RenderState.BlendEquation blendEquation) {
         switch (blendEquation) {
             case Add: