Add dual source blending support for proper blending with coverage.

Review URL: http://codereview.appspot.com/4535088/



git-svn-id: http://skia.googlecode.com/svn/trunk@1390 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/gpu/src/GrGLProgram.cpp b/gpu/src/GrGLProgram.cpp
index 9a9e3c2..516bc98 100644
--- a/gpu/src/GrGLProgram.cpp
+++ b/gpu/src/GrGLProgram.cpp
@@ -99,6 +99,9 @@
     return ZEROSVEC[count];
 }
 
+static inline const char* declared_color_output_name() { return "fsColorOut"; }
+static inline const char* dual_source_output_name() { return "dualSourceOut"; }
+
 static void tex_matrix_name(int stage, GrStringBuilder* s) {
 #if GR_GL_ATTRIBUTE_MATRICES
     *s = "aTexM";
@@ -144,12 +147,32 @@
 GrGLProgram::~GrGLProgram() {
 }
 
+void GrGLProgram::overrideBlend(GrBlendCoeff* srcCoeff, 
+                                GrBlendCoeff* dstCoeff) const {
+    switch (fProgramDesc.fDualSrcOutput) {
+        case ProgramDesc::kNone_DualSrcOutput:
+            break;
+        // the prog will write a coverage value to the secondary
+        // output and the dst is blended by one minus that value.
+        case ProgramDesc::kCoverage_DualSrcOutput:
+        case ProgramDesc::kCoverageISA_DualSrcOutput:
+        case ProgramDesc::kCoverageISC_DualSrcOutput:
+        *dstCoeff = (GrBlendCoeff)GrGpu::kIS2C_BlendCoeff;
+        break;
+        default:
+            GrCrash("Unexpected dual source blend output");
+            break;
+    }
+}
+
 void GrGLProgram::buildKey(GrBinHashKeyBuilder& key) const {
     // Add stage configuration to the key
     key.keyData(reinterpret_cast<const uint8_t*>(&fProgramDesc), sizeof(ProgramDesc));
 }
 
 // assigns modulation of two vars to an output var
+// vars can be vec4s or floats (or one of each)
+// result is always vec4
 // if either var is "" then assign to the other var
 // if both are "" then assign all ones
 static inline void modulate_helper(const char* outputVar,
@@ -167,15 +190,17 @@
     if (!has0 && !has1) {
         code->appendf("\t%s = %s;\n", outputVar, all_ones_vec(4));
     } else if (!has0) {
-        code->appendf("\t%s = %s;\n", outputVar, var1);
+        code->appendf("\t%s = vec4(%s);\n", outputVar, var1);
     } else if (!has1) {
-        code->appendf("\t%s = %s;\n", outputVar, var0);
+        code->appendf("\t%s = vec4(%s);\n", outputVar, var0);
     } else {
-        code->appendf("\t%s = %s * %s;\n", outputVar, var0, var1);
+        code->appendf("\t%s = vec4(%s * %s);\n", outputVar, var0, var1);
     }
 }
 
 // assigns addition of two vars to an output var
+// vars can be vec4s or floats (or one of each)
+// result is always vec4
 // if either var is "" then assign to the other var
 // if both are "" then assign all zeros
 static inline void add_helper(const char* outputVar,
@@ -193,11 +218,11 @@
     if (!has0 && !has1) {
         code->appendf("\t%s = %s;\n", outputVar, all_zeros_vec(4));
     } else if (!has0) {
-        code->appendf("\t%s = %s;\n", outputVar, var1);
+        code->appendf("\t%s = vec4(%s);\n", outputVar, var1);
     } else if (!has1) {
-        code->appendf("\t%s = %s;\n", outputVar, var0);
+        code->appendf("\t%s = vec4(%s);\n", outputVar, var0);
     } else {
-        code->appendf("\t%s = %s + %s;\n", outputVar, var0, var1);
+        code->appendf("\t%s = vec4(%s + %s);\n", outputVar, var0, var1);
     }
 }
 
@@ -325,6 +350,21 @@
     needBlendInputs(uniformCoeff, colorCoeff,
                     &needColorFilterUniform, &needComputedColor);
 
+    // the dual source output has no canonical var name, have to
+    // declare an output, which is incompatible with gl_FragColor/gl_FragData.
+    const char* fsColorOutput;
+    bool dualSourceOutputWritten = false;
+    bool usingDeclaredOutputs = ProgramDesc::kNone_DualSrcOutput != 
+                                fProgramDesc.fDualSrcOutput;
+    if (usingDeclaredOutputs) {
+        GrAssert(0 == segments.fHeader.size());
+        segments.fHeader.printf("#version 150\n");
+        fsColorOutput = declared_color_output_name();
+        segments.fFSOutputs.appendf("out vec4 %s;\n", fsColorOutput);
+    } else {
+        fsColorOutput = "gl_FragColor";
+    }
+
 #if GR_GL_ATTRIBUTE_MATRICES
     segments.fVSAttrs += "attribute mat3 " VIEW_MATRIX_NAME ";\n";
     programData->fUniLocations.fViewMatrixUni = kSetAsAttribute;
@@ -433,7 +473,9 @@
     bool wroteFragColorZero = false;
     if (SkXfermode::kZero_Coeff == uniformCoeff && 
         SkXfermode::kZero_Coeff == colorCoeff) {
-        segments.fFSCode.appendf("\tgl_FragColor = %s;\n", all_zeros_vec(4));
+        segments.fFSCode.appendf("\t%s = %s;\n", 
+                                 fsColorOutput,
+                                 all_zeros_vec(4));
         wroteFragColorZero = true;
     } else if (SkXfermode::kDst_Mode != fProgramDesc.fColorFilterXfermode) {
         segments.fFSCode.appendf("\tvec4 filteredColor;\n");
@@ -447,11 +489,11 @@
     // compute the partial coverage (coverage stages and edge aa)
 
     GrStringBuilder inCoverage;
-    bool coverageIsScalar = false;
 
-    // we will want to compute coverage for some blend when there is no
-    // color (when dual source blending is enabled). But for now we have this if
-    if (!wroteFragColorZero) {
+    // we don't need to compute coverage at all if we know the final shader
+    // output will be zero and we don't have a dual src blend output.
+    if (!wroteFragColorZero ||
+        ProgramDesc::kNone_DualSrcOutput != fProgramDesc.fDualSrcOutput) {
         if (fProgramDesc.fEdgeAANumEdges > 0) {
             segments.fFSUnis.append("uniform vec3 " EDGES_UNI_NAME "[");
             segments.fFSUnis.appendS32(fProgramDesc.fEdgeAANumEdges);
@@ -483,7 +525,6 @@
             }
             segments.fFSCode.append(";\n");
             inCoverage = "edgeAlpha";
-            coverageIsScalar = true;
         }
 
         GrStringBuilder outCoverage;
@@ -516,26 +557,48 @@
                              &segments,
                              &programData->fUniLocations.fStages[s]);
                 inCoverage = outCoverage;
-                coverageIsScalar = false;
             }
         }
+        if (ProgramDesc::kNone_DualSrcOutput != fProgramDesc.fDualSrcOutput) {
+            segments.fFSOutputs.appendf("out vec4 %s;\n", 
+                                        dual_source_output_name());
+            bool outputIsZero = false;
+            GrStringBuilder coeff;
+            if (ProgramDesc::kCoverage_DualSrcOutput != 
+                fProgramDesc.fDualSrcOutput && !wroteFragColorZero) {
+                if (!inColor.size()) {
+                    outputIsZero = true;
+                } else {
+                    if (fProgramDesc.fDualSrcOutput == 
+                        ProgramDesc::kCoverageISA_DualSrcOutput) {
+                        coeff.printf("(1 - %s.a)", inColor.c_str());
+                    } else {
+                        coeff.printf("(vec4(1,1,1,1) - %s)", inColor.c_str());
+                    }
+                }
+            }
+            if (outputIsZero) {
+                segments.fFSCode.appendf("\t%s = %s;\n", 
+                                         dual_source_output_name(),
+                                         all_zeros_vec(4));
+            } else {
+                modulate_helper(dual_source_output_name(),
+                                coeff.c_str(),
+                                inCoverage.c_str(),
+                                &segments.fFSCode);
+            }
+            dualSourceOutputWritten = true;
+        }
     }
 
-    // TODO: ADD dual source blend output based on coverage here
-
     ///////////////////////////////////////////////////////////////////////////
     // combine color and coverage as frag color
 
     if (!wroteFragColorZero) {
-        if (coverageIsScalar && !inColor.size()) {
-            GrStringBuilder oldCoverage = inCoverage;
-            inCoverage.swap(oldCoverage);
-            inCoverage.printf("vec4(%s,%s,%s,%s)", oldCoverage.c_str(), 
-                              oldCoverage.c_str(), oldCoverage.c_str(), 
-                              oldCoverage.c_str());
-        }
-        modulate_helper("gl_FragColor", inColor.c_str(),
-                        inCoverage.c_str(), &segments.fFSCode);
+        modulate_helper(fsColorOutput,
+                         inColor.c_str(),
+                         inCoverage.c_str(),
+                         &segments.fFSCode);
     }
 
     segments.fVSCode.append("}\n");
@@ -548,7 +611,10 @@
         return false;
     }
 
-    if (!this->bindAttribsAndLinkProgram(texCoordAttrs, programData)) {
+    if (!this->bindOutputsAttribsAndLinkProgram(texCoordAttrs,
+                                                usingDeclaredOutputs,
+                                                dualSourceOutputWritten,
+                                                programData)) {
         return false;
     }
 
@@ -560,10 +626,16 @@
 bool GrGLProgram::CompileFSAndVS(const ShaderCodeSegments& segments,
                                  CachedData* programData) {
 
-    const char* strings[4];
-    int lengths[4];
+    static const int MAX_STRINGS = 6;
+    const char* strings[MAX_STRINGS];
+    int lengths[MAX_STRINGS];
     int stringCnt = 0;
 
+    if (segments.fHeader.size()) {
+        strings[stringCnt] = segments.fHeader.c_str();
+        lengths[stringCnt] = segments.fHeader.size();
+        ++stringCnt;
+    }
     if (segments.fVSUnis.size()) {
         strings[stringCnt] = segments.fVSUnis.c_str();
         lengths[stringCnt] = segments.fVSUnis.size();
@@ -586,12 +658,14 @@
     ++stringCnt;
 
 #if PRINT_SHADERS
+    GrPrintf(segments.fHeader.c_str());
     GrPrintf(segments.fVSUnis.c_str());
     GrPrintf(segments.fVSAttrs.c_str());
     GrPrintf(segments.fVaryings.c_str());
     GrPrintf(segments.fVSCode.c_str());
     GrPrintf("\n");
 #endif
+    GrAssert(stringCnt <= MAX_STRINGS);
     programData->fVShaderID = CompileShader(GR_GL_VERTEX_SHADER,
                                         stringCnt,
                                         strings,
@@ -603,6 +677,11 @@
 
     stringCnt = 0;
 
+    if (segments.fHeader.size()) {
+        strings[stringCnt] = segments.fHeader.c_str();
+        lengths[stringCnt] = segments.fHeader.size();
+        ++stringCnt;
+    }
     if (strlen(GrShaderPrecision()) > 1) {
         strings[stringCnt] = GrShaderPrecision();
         lengths[stringCnt] = strlen(GrShaderPrecision());
@@ -618,6 +697,11 @@
         lengths[stringCnt] = segments.fVaryings.size();
         ++stringCnt;
     }
+    if (segments.fFSOutputs.size()) {
+        strings[stringCnt] = segments.fFSOutputs.c_str();
+        lengths[stringCnt] = segments.fFSOutputs.size();
+        ++stringCnt;
+    }
 
     GrAssert(segments.fFSCode.size());
     strings[stringCnt] = segments.fFSCode.c_str();
@@ -625,12 +709,15 @@
     ++stringCnt;
 
 #if PRINT_SHADERS
+    GrPrintf(segments.fHeader.c_str());
     GrPrintf(GrShaderPrecision());
     GrPrintf(segments.fFSUnis.c_str());
     GrPrintf(segments.fVaryings.c_str());
+    GrPrintf(segments.fFSOutputs.c_str());
     GrPrintf(segments.fFSCode.c_str());
     GrPrintf("\n");
 #endif
+    GrAssert(stringCnt <= MAX_STRINGS);
     programData->fFShaderID = CompileShader(GR_GL_FRAGMENT_SHADER,
                                             stringCnt,
                                             strings,
@@ -639,6 +726,7 @@
     if (!programData->fFShaderID) {
         return false;
     }
+
     return true;
 }
 
@@ -678,8 +766,11 @@
     return shader;
 }
 
-bool GrGLProgram::bindAttribsAndLinkProgram(GrStringBuilder texCoordAttrNames[],
-                                            CachedData* programData) const {
+bool GrGLProgram::bindOutputsAttribsAndLinkProgram(
+                                        GrStringBuilder texCoordAttrNames[],
+                                        bool bindColorOut,
+                                        bool bindDualSrcOut, 
+                                        CachedData* programData) const {
     programData->fProgramID = GR_GL(CreateProgram());
     if (!programData->fProgramID) {
         return false;
@@ -689,6 +780,15 @@
     GR_GL(AttachShader(progID, programData->fVShaderID));
     GR_GL(AttachShader(progID, programData->fFShaderID));
 
+    if (bindColorOut) {
+        GR_GL(BindFragDataLocationIndexed(programData->fProgramID, 
+                                          0, 0, declared_color_output_name()));
+    }
+    if (bindDualSrcOut) {
+        GR_GL(BindFragDataLocationIndexed(programData->fProgramID,
+                                          0, 1, dual_source_output_name()));
+    }
+
     // Bind the attrib locations to same values for all shaders
     GR_GL(BindAttribLocation(progID, PositionAttributeIdx(), POS_ATTR_NAME));
     for (int t = 0; t < GrDrawTarget::kMaxTexCoords; ++t) {
@@ -699,7 +799,6 @@
         }
     }
 
-
     if (kSetAsAttribute == programData->fUniLocations.fViewMatrixUni) {
         GR_GL(BindAttribLocation(progID,
                              ViewMatrixAttributeIdx(),
@@ -1072,7 +1171,7 @@
         segments->fFSCode.appendf("\t%s += %s(%s, %s + vec2(+%s.x,+%s.y))%s;\n", accumVar.c_str(), texFunc.c_str(), samplerName.c_str(), sampleCoords.c_str(), texelSizeName.c_str(), texelSizeName.c_str(), smear);
         segments->fFSCode.appendf("\t%s = .25 * %s%s;\n", fsOutColor, accumVar.c_str(), modulate.c_str());
     } else {
-        segments->fFSCode.appendf("\t%s = %s(%s, %s)%s %s;\n", fsOutColor, texFunc.c_str(), samplerName.c_str(), sampleCoords.c_str(), smear, modulate.c_str());
+        segments->fFSCode.appendf("\t%s = %s(%s, %s)%s%s;\n", fsOutColor, texFunc.c_str(), samplerName.c_str(), sampleCoords.c_str(), smear, modulate.c_str());
     }
 }