Merge GrGpuGLShaders into its parent class, GrGpuGL

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



git-svn-id: http://skia.googlecode.com/svn/trunk@4095 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/src/gpu/gl/GrGpuGL_program.cpp b/src/gpu/gl/GrGpuGL_program.cpp
new file mode 100644
index 0000000..2ac26d5
--- /dev/null
+++ b/src/gpu/gl/GrGpuGL_program.cpp
@@ -0,0 +1,1176 @@
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "GrGpuGL.h"
+
+#include "GrBinHashKey.h"
+#include "effects/GrConvolutionEffect.h"
+#include "GrCustomStage.h"
+#include "GrGLProgramStage.h"
+#include "GrGLSL.h"
+#include "GrGpuVertex.h"
+#include "GrNoncopyable.h"
+#include "GrProgramStageFactory.h"
+#include "GrRandom.h"
+#include "GrStringBuilder.h"
+
+#define SKIP_CACHE_CHECK    true
+#define GR_UINT32_MAX   static_cast<uint32_t>(-1)
+
+#include "../GrTHashCache.h"
+
+class GrGpuGL::ProgramCache : public ::GrNoncopyable {
+private:
+    class Entry;
+
+    typedef GrBinHashKey<Entry, GrGLProgram::kProgramKeySize> ProgramHashKey;
+
+    class Entry : public ::GrNoncopyable {
+    public:
+        Entry() {}
+        void copyAndTakeOwnership(Entry& entry) {
+            fProgramData.copyAndTakeOwnership(entry.fProgramData);
+            fKey = entry.fKey; // ownership transfer
+            fLRUStamp = entry.fLRUStamp;
+        }
+
+    public:
+        int compare(const ProgramHashKey& key) const { return fKey.compare(key); }
+
+    public:
+        GrGLProgram::CachedData fProgramData;
+        ProgramHashKey          fKey;
+        unsigned int            fLRUStamp;
+    };
+
+    GrTHashTable<Entry, ProgramHashKey, 8> fHashCache;
+
+    // We may have kMaxEntries+1 shaders in the GL context because
+    // we create a new shader before evicting from the cache.
+    enum {
+        kMaxEntries = 32
+    };
+    Entry                       fEntries[kMaxEntries];
+    int                         fCount;
+    unsigned int                fCurrLRUStamp;
+    const GrGLContextInfo&      fGL;
+
+public:
+    ProgramCache(const GrGLContextInfo& gl)
+        : fCount(0)
+        , fCurrLRUStamp(0)
+        , fGL(gl) {
+    }
+
+    ~ProgramCache() {
+        for (int i = 0; i < fCount; ++i) {
+            GrGpuGL::DeleteProgram(fGL.interface(),
+                                          &fEntries[i].fProgramData);
+        }
+    }
+
+    void abandon() {
+        fCount = 0;
+    }
+
+    void invalidateViewMatrices() {
+        for (int i = 0; i < fCount; ++i) {
+            // set to illegal matrix
+            fEntries[i].fProgramData.fViewMatrix = GrMatrix::InvalidMatrix();
+        }
+    }
+
+    GrGLProgram::CachedData* getProgramData(const GrGLProgram& desc,
+                                            GrCustomStage** stages) {
+        Entry newEntry;
+        newEntry.fKey.setKeyData(desc.keyData());
+        
+        Entry* entry = fHashCache.find(newEntry.fKey);
+        if (NULL == entry) {
+            if (!desc.genProgram(fGL, stages, &newEntry.fProgramData)) {
+                return NULL;
+            }
+            if (fCount < kMaxEntries) {
+                entry = fEntries + fCount;
+                ++fCount;
+            } else {
+                GrAssert(kMaxEntries == fCount);
+                entry = fEntries;
+                for (int i = 1; i < kMaxEntries; ++i) {
+                    if (fEntries[i].fLRUStamp < entry->fLRUStamp) {
+                        entry = fEntries + i;
+                    }
+                }
+                fHashCache.remove(entry->fKey, entry);
+                GrGpuGL::DeleteProgram(fGL.interface(),
+                                              &entry->fProgramData);
+            }
+            entry->copyAndTakeOwnership(newEntry);
+            fHashCache.insert(entry->fKey, entry);
+        }
+
+        entry->fLRUStamp = fCurrLRUStamp;
+        if (GR_UINT32_MAX == fCurrLRUStamp) {
+            // wrap around! just trash our LRU, one time hit.
+            for (int i = 0; i < fCount; ++i) {
+                fEntries[i].fLRUStamp = 0;
+            }
+        }
+        ++fCurrLRUStamp;
+        return &entry->fProgramData;
+    }
+};
+
+void GrGpuGL::DeleteProgram(const GrGLInterface* gl,
+                                   CachedData* programData) {
+    GR_GL_CALL(gl, DeleteShader(programData->fVShaderID));
+    if (programData->fGShaderID) {
+        GR_GL_CALL(gl, DeleteShader(programData->fGShaderID));
+    }
+    GR_GL_CALL(gl, DeleteShader(programData->fFShaderID));
+    GR_GL_CALL(gl, DeleteProgram(programData->fProgramID));
+    GR_DEBUGCODE(memset(programData, 0, sizeof(*programData));)
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void GrGpuGL::createProgramCache() {
+    fProgramData = NULL;
+    fProgramCache = new ProgramCache(this->glContextInfo());
+}
+
+void GrGpuGL::deleteProgramCache() {
+    delete fProgramCache;
+    fProgramCache = NULL;
+    fProgramData = NULL;
+}
+
+void GrGpuGL::abandonResources(){
+    INHERITED::abandonResources();
+    fProgramCache->abandon();
+    fHWProgramID = 0;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+#define GL_CALL(X) GR_GL_CALL(this->glInterface(), X)
+
+namespace {
+
+// GrRandoms nextU() values have patterns in the low bits
+// So using nextU() % array_count might never take some values.
+int random_int(GrRandom* r, int count) {
+    return (int)(r->nextF() * count);
+}
+
+// min is inclusive, max is exclusive
+int random_int(GrRandom* r, int min, int max) {
+    return (int)(r->nextF() * (max-min)) + min;
+}
+
+bool random_bool(GrRandom* r) {
+    return r->nextF() > .5f;
+}
+
+}
+
+bool GrGpuGL::programUnitTest() {
+
+    GrGLSLGeneration glslGeneration = 
+            GrGetGLSLGeneration(this->glBinding(), this->glInterface());
+    static const int STAGE_OPTS[] = {
+        0,
+        StageDesc::kNoPerspective_OptFlagBit,
+        StageDesc::kIdentity_CoordMapping
+    };
+    static const int IN_CONFIG_FLAGS[] = {
+        StageDesc::kNone_InConfigFlag,
+        StageDesc::kSwapRAndB_InConfigFlag,
+        StageDesc::kSwapRAndB_InConfigFlag |
+        StageDesc::kMulRGBByAlpha_RoundUp_InConfigFlag,
+        StageDesc::kMulRGBByAlpha_RoundDown_InConfigFlag,
+        StageDesc::kSmearAlpha_InConfigFlag,
+        StageDesc::kSmearRed_InConfigFlag,
+    };
+    GrGLProgram program;
+    ProgramDesc& pdesc = program.fProgramDesc;
+
+    static const int NUM_TESTS = 512;
+
+    GrRandom random;
+    for (int t = 0; t < NUM_TESTS; ++t) {
+
+#if 0
+        GrPrintf("\nTest Program %d\n-------------\n", t);
+        static const int stop = -1;
+        if (t == stop) {
+            int breakpointhere = 9;
+        }
+#endif
+
+        pdesc.fVertexLayout = 0;
+        pdesc.fEmitsPointSize = random.nextF() > .5f;
+        pdesc.fColorInput = random_int(&random, ProgramDesc::kColorInputCnt);
+        pdesc.fCoverageInput = random_int(&random, ProgramDesc::kColorInputCnt);
+
+        pdesc.fColorFilterXfermode = random_int(&random, SkXfermode::kCoeffModesCnt);
+
+        pdesc.fFirstCoverageStage = random_int(&random, GrDrawState::kNumStages);
+
+        pdesc.fVertexLayout |= random_bool(&random) ?
+                                    GrDrawTarget::kCoverage_VertexLayoutBit :
+                                    0;
+
+#if GR_GL_EXPERIMENTAL_GS
+        pdesc.fExperimentalGS = this->getCaps().fGeometryShaderSupport &&
+                                random_bool(&random);
+#endif
+        pdesc.fOutputConfig =  random_int(&random, ProgramDesc::kOutputConfigCnt);
+
+        bool edgeAA = random_bool(&random);
+        if (edgeAA) {
+            pdesc.fVertexLayout |= GrDrawTarget::kEdge_VertexLayoutBit;
+            if (this->getCaps().fShaderDerivativeSupport) {
+                pdesc.fVertexEdgeType = (GrDrawState::VertexEdgeType) random_int(&random, GrDrawState::kVertexEdgeTypeCnt);
+            } else {
+                pdesc.fVertexEdgeType = GrDrawState::kHairLine_EdgeType;
+            }
+        } else {
+        }
+
+        pdesc.fColorMatrixEnabled = random_bool(&random);
+
+        if (this->getCaps().fDualSourceBlendingSupport) {
+            pdesc.fDualSrcOutput = random_int(&random, ProgramDesc::kDualSrcOutputCnt);
+        } else {
+            pdesc.fDualSrcOutput = ProgramDesc::kNone_DualSrcOutput;
+        }
+
+        GrCustomStage* customStages[GrDrawState::kNumStages];
+
+        for (int s = 0; s < GrDrawState::kNumStages; ++s) {
+            // enable the stage?
+            if (random_bool(&random)) {
+                // use separate tex coords?
+                if (random_bool(&random)) {
+                    int t = random_int(&random, GrDrawState::kMaxTexCoords);
+                    pdesc.fVertexLayout |= StageTexCoordVertexLayoutBit(s, t);
+                } else {
+                    pdesc.fVertexLayout |= StagePosAsTexCoordVertexLayoutBit(s);
+                }
+            }
+            // use text-formatted verts?
+            if (random_bool(&random)) {
+                pdesc.fVertexLayout |= kTextFormat_VertexLayoutBit;
+            }
+            StageDesc& stage = pdesc.fStages[s];
+
+            stage.fCustomStageKey = 0;
+            customStages[s] = NULL;
+
+            stage.fOptFlags = STAGE_OPTS[random_int(&random, GR_ARRAY_COUNT(STAGE_OPTS))];
+            stage.fInConfigFlags = IN_CONFIG_FLAGS[random_int(&random, GR_ARRAY_COUNT(IN_CONFIG_FLAGS))];
+            stage.fCoordMapping =  random_int(&random, StageDesc::kCoordMappingCnt);
+            stage.fFetchMode = random_int(&random, StageDesc::kFetchModeCnt);
+            // convolution shaders don't work with persp tex matrix
+            if (stage.fFetchMode == StageDesc::kConvolution_FetchMode ||
+                stage.fFetchMode == StageDesc::kDilate_FetchMode ||
+                stage.fFetchMode == StageDesc::kErode_FetchMode) {
+                stage.fOptFlags |= StageDesc::kNoPerspective_OptFlagBit;
+            }
+            stage.setEnabled(VertexUsesStage(s, pdesc.fVertexLayout));
+            static const uint32_t kMulByAlphaMask =
+                StageDesc::kMulRGBByAlpha_RoundUp_InConfigFlag |
+                StageDesc::kMulRGBByAlpha_RoundDown_InConfigFlag;
+
+            switch (stage.fFetchMode) {
+                case StageDesc::kSingle_FetchMode:
+                    stage.fKernelWidth = 0;
+                    break;
+                case StageDesc::kConvolution_FetchMode:
+                case StageDesc::kDilate_FetchMode:
+                case StageDesc::kErode_FetchMode:
+                    stage.fKernelWidth = random_int(&random, 2, 8);
+                    stage.fInConfigFlags &= ~kMulByAlphaMask;
+                    break;
+                case StageDesc::k2x2_FetchMode:
+                    stage.fKernelWidth = 0;
+                    stage.fInConfigFlags &= ~kMulByAlphaMask;
+                    break;
+            }
+
+            // TODO: is there a more elegant way to express this?
+            if (stage.fFetchMode == StageDesc::kConvolution_FetchMode) {
+                int direction = random_int(&random, 2);
+                float kernel[MAX_KERNEL_WIDTH];
+                for (int i = 0; i < stage.fKernelWidth; i++) {
+                    kernel[i] = random.nextF();
+                }
+                customStages[s] = new GrConvolutionEffect(
+                    (GrSamplerState::FilterDirection)direction,
+                    stage.fKernelWidth, kernel);
+                stage.fCustomStageKey =
+                    customStages[s]->getFactory().glStageKey(customStages[s]);
+            }
+        }
+        CachedData cachedData;
+        if (!program.genProgram(this->glContextInfo(), customStages,
+                                &cachedData)) {
+            return false;
+        }
+        DeleteProgram(this->glInterface(), &cachedData);
+    }
+    return true;
+}
+
+void GrGpuGL::flushViewMatrix() {
+    const GrMatrix& vm = this->getDrawState().getViewMatrix();
+    if (!fProgramData->fViewMatrix.cheapEqualTo(vm)) {
+
+        const GrRenderTarget* rt = this->getDrawState().getRenderTarget();
+        GrAssert(NULL != rt);
+        GrMatrix m;
+        m.setAll(
+            GrIntToScalar(2) / rt->width(), 0, -GR_Scalar1,
+            0,-GrIntToScalar(2) / rt->height(), GR_Scalar1,
+            0, 0, GrMatrix::I()[8]);
+        m.setConcat(m, vm);
+
+        // ES doesn't allow you to pass true to the transpose param,
+        // so do our own transpose
+        GrGLfloat mt[]  = {
+            GrScalarToFloat(m[GrMatrix::kMScaleX]),
+            GrScalarToFloat(m[GrMatrix::kMSkewY]),
+            GrScalarToFloat(m[GrMatrix::kMPersp0]),
+            GrScalarToFloat(m[GrMatrix::kMSkewX]),
+            GrScalarToFloat(m[GrMatrix::kMScaleY]),
+            GrScalarToFloat(m[GrMatrix::kMPersp1]),
+            GrScalarToFloat(m[GrMatrix::kMTransX]),
+            GrScalarToFloat(m[GrMatrix::kMTransY]),
+            GrScalarToFloat(m[GrMatrix::kMPersp2])
+        };
+
+        GrAssert(GrGLProgram::kUnusedUniform != 
+                 fProgramData->fUniLocations.fViewMatrixUni);
+        GL_CALL(UniformMatrix3fv(fProgramData->fUniLocations.fViewMatrixUni,
+                                 1, false, mt));
+        fProgramData->fViewMatrix = vm;
+    }
+}
+
+void GrGpuGL::flushTextureDomain(int s) {
+    const GrGLint& uni = fProgramData->fUniLocations.fStages[s].fTexDomUni;
+    const GrDrawState& drawState = this->getDrawState();
+    if (GrGLProgram::kUnusedUniform != uni) {
+        const GrRect &texDom = drawState.getSampler(s).getTextureDomain();
+
+        if (((1 << s) & fDirtyFlags.fTextureChangedMask) ||
+            fProgramData->fTextureDomain[s] != texDom) {
+
+            fProgramData->fTextureDomain[s] = texDom;
+
+            float values[4] = {
+                GrScalarToFloat(texDom.left()),
+                GrScalarToFloat(texDom.top()),
+                GrScalarToFloat(texDom.right()),
+                GrScalarToFloat(texDom.bottom())
+            };
+
+            const GrGLTexture* texture =
+                static_cast<const GrGLTexture*>(drawState.getTexture(s));
+            GrGLTexture::Orientation orientation = texture->orientation();
+
+            // vertical flip if necessary
+            if (GrGLTexture::kBottomUp_Orientation == orientation) {
+                values[1] = 1.0f - values[1];
+                values[3] = 1.0f - values[3];
+                // The top and bottom were just flipped, so correct the ordering
+                // of elements so that values = (l, t, r, b).
+                SkTSwap(values[1], values[3]);
+            }
+
+            GL_CALL(Uniform4fv(uni, 1, values));
+        }
+    }
+}
+
+void GrGpuGL::flushTextureMatrix(int s) {
+    const GrGLint& uni = fProgramData->fUniLocations.fStages[s].fTextureMatrixUni;
+    const GrDrawState& drawState = this->getDrawState();
+    const GrGLTexture* texture =
+        static_cast<const GrGLTexture*>(drawState.getTexture(s));
+    if (NULL != texture) {
+        const GrMatrix& hwMatrix = fProgramData->fTextureMatrices[s];
+        const GrMatrix& samplerMatrix = drawState.getSampler(s).getMatrix();
+        if (GrGLProgram::kUnusedUniform != uni &&
+            (((1 << s) & fDirtyFlags.fTextureChangedMask) ||
+            !hwMatrix.cheapEqualTo(samplerMatrix))) {
+
+            GrMatrix m = samplerMatrix;
+            GrSamplerState::SampleMode mode =
+                drawState.getSampler(s).getSampleMode();
+            AdjustTextureMatrix(texture, mode, &m);
+
+            // ES doesn't allow you to pass true to the transpose param,
+            // so do our own transpose
+            GrGLfloat mt[]  = {
+                GrScalarToFloat(m[GrMatrix::kMScaleX]),
+                GrScalarToFloat(m[GrMatrix::kMSkewY]),
+                GrScalarToFloat(m[GrMatrix::kMPersp0]),
+                GrScalarToFloat(m[GrMatrix::kMSkewX]),
+                GrScalarToFloat(m[GrMatrix::kMScaleY]),
+                GrScalarToFloat(m[GrMatrix::kMPersp1]),
+                GrScalarToFloat(m[GrMatrix::kMTransX]),
+                GrScalarToFloat(m[GrMatrix::kMTransY]),
+                GrScalarToFloat(m[GrMatrix::kMPersp2])
+            };
+
+            GL_CALL(UniformMatrix3fv(uni, 1, false, mt));
+            fProgramData->fTextureMatrices[s] = samplerMatrix;
+        }
+    }
+}
+
+void GrGpuGL::flushRadial2(int s) {
+
+    const int &uni = fProgramData->fUniLocations.fStages[s].fRadial2Uni;
+    const GrSamplerState& sampler = this->getDrawState().getSampler(s);
+    if (GrGLProgram::kUnusedUniform != uni &&
+        (fProgramData->fRadial2CenterX1[s] != sampler.getRadial2CenterX1() ||
+         fProgramData->fRadial2Radius0[s]  != sampler.getRadial2Radius0()  ||
+         fProgramData->fRadial2PosRoot[s]  != sampler.isRadial2PosRoot())) {
+
+        GrScalar centerX1 = sampler.getRadial2CenterX1();
+        GrScalar radius0 = sampler.getRadial2Radius0();
+
+        GrScalar a = GrMul(centerX1, centerX1) - GR_Scalar1;
+
+        // when were in the degenerate (linear) case the second
+        // value will be INF but the program doesn't read it. (We
+        // use the same 6 uniforms even though we don't need them
+        // all in the linear case just to keep the code complexity
+        // down).
+        float values[6] = {
+            GrScalarToFloat(a),
+            1 / (2.f * GrScalarToFloat(a)),
+            GrScalarToFloat(centerX1),
+            GrScalarToFloat(radius0),
+            GrScalarToFloat(GrMul(radius0, radius0)),
+            sampler.isRadial2PosRoot() ? 1.f : -1.f
+        };
+        GL_CALL(Uniform1fv(uni, 6, values));
+        fProgramData->fRadial2CenterX1[s] = sampler.getRadial2CenterX1();
+        fProgramData->fRadial2Radius0[s]  = sampler.getRadial2Radius0();
+        fProgramData->fRadial2PosRoot[s]  = sampler.isRadial2PosRoot();
+    }
+}
+
+void GrGpuGL::flushConvolution(int s) {
+    const GrSamplerState& sampler = this->getDrawState().getSampler(s);
+    int kernelUni = fProgramData->fUniLocations.fStages[s].fKernelUni;
+    if (GrGLProgram::kUnusedUniform != kernelUni) {
+        GL_CALL(Uniform1fv(kernelUni, sampler.getKernelWidth(),
+                           sampler.getKernel()));
+    }
+    int imageIncrementUni = fProgramData->fUniLocations.fStages[s].fImageIncrementUni;
+    if (GrGLProgram::kUnusedUniform != imageIncrementUni) {
+        const GrGLTexture* texture =
+            static_cast<const GrGLTexture*>(this->getDrawState().getTexture(s));
+        float imageIncrement[2] = { 0 };
+        switch (sampler.getFilterDirection()) {
+            case GrSamplerState::kX_FilterDirection:
+                imageIncrement[0] = 1.0f / texture->width();
+                break;
+            case GrSamplerState::kY_FilterDirection:
+                imageIncrement[1] = 1.0f / texture->height();
+                break;
+            default:
+                GrCrash("Unknown filter direction.");
+        }
+        GL_CALL(Uniform2fv(imageIncrementUni, 1, imageIncrement));
+    }
+}
+
+void GrGpuGL::flushTexelSize(int s) {
+    const int& uni = fProgramData->fUniLocations.fStages[s].fNormalizedTexelSizeUni;
+    if (GrGLProgram::kUnusedUniform != uni) {
+        const GrGLTexture* texture =
+            static_cast<const GrGLTexture*>(this->getDrawState().getTexture(s));
+        if (texture->width() != fProgramData->fTextureWidth[s] ||
+            texture->height() != fProgramData->fTextureHeight[s]) {
+
+            float texelSize[] = {1.f / texture->width(),
+                                 1.f / texture->height()};
+            GL_CALL(Uniform2fv(uni, 1, texelSize));
+            fProgramData->fTextureWidth[s] = texture->width();
+            fProgramData->fTextureHeight[s] = texture->height();
+        }
+    }
+}
+
+void GrGpuGL::flushColorMatrix() {
+    const ProgramDesc& desc = fCurrentProgram.getDesc();
+    int matrixUni = fProgramData->fUniLocations.fColorMatrixUni;
+    int vecUni = fProgramData->fUniLocations.fColorMatrixVecUni;
+    if (GrGLProgram::kUnusedUniform != matrixUni
+     && GrGLProgram::kUnusedUniform != vecUni) {
+        const float* m = this->getDrawState().getColorMatrix();
+        GrGLfloat mt[]  = {
+            m[0], m[5], m[10], m[15],
+            m[1], m[6], m[11], m[16],
+            m[2], m[7], m[12], m[17],
+            m[3], m[8], m[13], m[18],
+        };
+        static float scale = 1.0f / 255.0f;
+        GrGLfloat vec[] = {
+            m[4] * scale, m[9] * scale, m[14] * scale, m[19] * scale,
+        };
+        GL_CALL(UniformMatrix4fv(matrixUni, 1, false, mt));
+        GL_CALL(Uniform4fv(vecUni, 1, vec));
+    }
+}
+
+static const float ONE_OVER_255 = 1.f / 255.f;
+
+#define GR_COLOR_TO_VEC4(color) {\
+    GrColorUnpackR(color) * ONE_OVER_255,\
+    GrColorUnpackG(color) * ONE_OVER_255,\
+    GrColorUnpackB(color) * ONE_OVER_255,\
+    GrColorUnpackA(color) * ONE_OVER_255 \
+}
+
+void GrGpuGL::flushColor(GrColor color) {
+    const ProgramDesc& desc = fCurrentProgram.getDesc();
+    const GrDrawState& drawState = this->getDrawState();
+
+    if (this->getVertexLayout() & kColor_VertexLayoutBit) {
+        // color will be specified per-vertex as an attribute
+        // invalidate the const vertex attrib color
+        fHWConstAttribColor = GrColor_ILLEGAL;
+    } else {
+        switch (desc.fColorInput) {
+            case ProgramDesc::kAttribute_ColorInput:
+                if (fHWConstAttribColor != color) {
+                    // OpenGL ES only supports the float varieties of
+                    // glVertexAttrib
+                    float c[] = GR_COLOR_TO_VEC4(color);
+                    GL_CALL(VertexAttrib4fv(GrGLProgram::ColorAttributeIdx(), 
+                                            c));
+                    fHWConstAttribColor = color;
+                }
+                break;
+            case ProgramDesc::kUniform_ColorInput:
+                if (fProgramData->fColor != color) {
+                    // OpenGL ES doesn't support unsigned byte varieties of
+                    // glUniform
+                    float c[] = GR_COLOR_TO_VEC4(color);
+                    GrAssert(GrGLProgram::kUnusedUniform != 
+                             fProgramData->fUniLocations.fColorUni);
+                    GL_CALL(Uniform4fv(fProgramData->fUniLocations.fColorUni,
+                                        1, c));
+                    fProgramData->fColor = color;
+                }
+                break;
+            case ProgramDesc::kSolidWhite_ColorInput:
+            case ProgramDesc::kTransBlack_ColorInput:
+                break;
+            default:
+                GrCrash("Unknown color type.");
+        }
+    }
+    if (fProgramData->fUniLocations.fColorFilterUni
+                != GrGLProgram::kUnusedUniform
+            && fProgramData->fColorFilterColor
+                != drawState.getColorFilterColor()) {
+        float c[] = GR_COLOR_TO_VEC4(drawState.getColorFilterColor());
+        GL_CALL(Uniform4fv(fProgramData->fUniLocations.fColorFilterUni, 1, c));
+        fProgramData->fColorFilterColor = drawState.getColorFilterColor();
+    }
+}
+
+void GrGpuGL::flushCoverage(GrColor coverage) {
+    const ProgramDesc& desc = fCurrentProgram.getDesc();
+    const GrDrawState& drawState = this->getDrawState();
+
+
+    if (this->getVertexLayout() & kCoverage_VertexLayoutBit) {
+        // coverage will be specified per-vertex as an attribute
+        // invalidate the const vertex attrib coverage
+        fHWConstAttribCoverage = GrColor_ILLEGAL;
+    } else {
+        switch (desc.fCoverageInput) {
+            case ProgramDesc::kAttribute_ColorInput:
+                if (fHWConstAttribCoverage != coverage) {
+                    // OpenGL ES only supports the float varieties of
+                    // glVertexAttrib
+                    float c[] = GR_COLOR_TO_VEC4(coverage);
+                    GL_CALL(VertexAttrib4fv(GrGLProgram::CoverageAttributeIdx(), 
+                                            c));
+                    fHWConstAttribCoverage = coverage;
+                }
+                break;
+            case ProgramDesc::kUniform_ColorInput:
+                if (fProgramData->fCoverage != coverage) {
+                    // OpenGL ES doesn't support unsigned byte varieties of
+                    // glUniform
+                    float c[] = GR_COLOR_TO_VEC4(coverage);
+                    GrAssert(GrGLProgram::kUnusedUniform != 
+                             fProgramData->fUniLocations.fCoverageUni);
+                    GL_CALL(Uniform4fv(fProgramData->fUniLocations.fCoverageUni,
+                                        1, c));
+                    fProgramData->fCoverage = coverage;
+                }
+                break;
+            case ProgramDesc::kSolidWhite_ColorInput:
+            case ProgramDesc::kTransBlack_ColorInput:
+                break;
+            default:
+                GrCrash("Unknown coverage type.");
+        }
+    }
+}
+
+bool GrGpuGL::flushGraphicsState(GrPrimitiveType type) {
+    if (!flushGLStateCommon(type)) {
+        return false;
+    }
+
+    const GrDrawState& drawState = this->getDrawState();
+
+    if (fDirtyFlags.fRenderTargetChanged) {
+        // we assume all shader matrices may be wrong after viewport changes
+        fProgramCache->invalidateViewMatrices();
+    }
+
+    GrBlendCoeff srcCoeff;
+    GrBlendCoeff dstCoeff;
+    BlendOptFlags blendOpts = this->getBlendOpts(false, &srcCoeff, &dstCoeff);
+    if (kSkipDraw_BlendOptFlag & blendOpts) {
+        return false;
+    }
+
+    GrCustomStage* customStages [GrDrawState::kNumStages];
+    this->buildProgram(type, blendOpts, dstCoeff, customStages);
+    fProgramData = fProgramCache->getProgramData(fCurrentProgram,
+                                                 customStages);
+    if (NULL == fProgramData) {
+        GrAssert(!"Failed to create program!");
+        return false;
+    }
+
+    if (fHWProgramID != fProgramData->fProgramID) {
+        GL_CALL(UseProgram(fProgramData->fProgramID));
+        fHWProgramID = fProgramData->fProgramID;
+    }
+    fCurrentProgram.overrideBlend(&srcCoeff, &dstCoeff);
+    this->flushBlend(type, srcCoeff, dstCoeff);
+
+    GrColor color;
+    GrColor coverage;
+    if (blendOpts & kEmitTransBlack_BlendOptFlag) {
+        color = 0;
+        coverage = 0;
+    } else if (blendOpts & kEmitCoverage_BlendOptFlag) {
+        color = 0xffffffff;
+        coverage = drawState.getCoverage();
+    } else {
+        color = drawState.getColor();
+        coverage = drawState.getCoverage();
+    }
+    this->flushColor(color);
+    this->flushCoverage(coverage);
+
+    this->flushViewMatrix();
+
+    for (int s = 0; s < GrDrawState::kNumStages; ++s) {
+        if (this->isStageEnabled(s)) {
+            this->flushTextureMatrix(s);
+
+            this->flushRadial2(s);
+
+            this->flushConvolution(s);
+
+            this->flushTexelSize(s);
+
+            this->flushTextureDomain(s);
+
+            if (NULL != fProgramData->fCustomStage[s]) {
+                const GrSamplerState& sampler =
+                    this->getDrawState().getSampler(s);
+                const GrGLTexture* texture =
+                    static_cast<const GrGLTexture*>(
+                        this->getDrawState().getTexture(s));
+                fProgramData->fCustomStage[s]->setData(
+                    this->glInterface(), *texture,
+                    sampler.getCustomStage(), s);
+            }
+        }
+    }
+    this->flushColorMatrix();
+    resetDirtyFlags();
+    return true;
+}
+
+#if GR_TEXT_SCALAR_IS_USHORT
+    #define TEXT_COORDS_GL_TYPE          GR_GL_UNSIGNED_SHORT
+    #define TEXT_COORDS_ARE_NORMALIZED   1
+#elif GR_TEXT_SCALAR_IS_FLOAT
+    #define TEXT_COORDS_GL_TYPE          GR_GL_FLOAT
+    #define TEXT_COORDS_ARE_NORMALIZED   0
+#elif GR_TEXT_SCALAR_IS_FIXED
+    #define TEXT_COORDS_GL_TYPE          GR_GL_FIXED
+    #define TEXT_COORDS_ARE_NORMALIZED   0
+#else
+    #error "unknown GR_TEXT_SCALAR type"
+#endif
+
+void GrGpuGL::setupGeometry(int* startVertex,
+                                    int* startIndex,
+                                    int vertexCount,
+                                    int indexCount) {
+
+    int newColorOffset;
+    int newCoverageOffset;
+    int newTexCoordOffsets[GrDrawState::kMaxTexCoords];
+    int newEdgeOffset;
+
+    GrVertexLayout currLayout = this->getVertexLayout();
+
+    GrGLsizei newStride = VertexSizeAndOffsetsByIdx(
+                                            currLayout,
+                                            newTexCoordOffsets,
+                                            &newColorOffset,
+                                            &newCoverageOffset,
+                                            &newEdgeOffset);
+    int oldColorOffset;
+    int oldCoverageOffset;
+    int oldTexCoordOffsets[GrDrawState::kMaxTexCoords];
+    int oldEdgeOffset;
+
+    GrGLsizei oldStride = VertexSizeAndOffsetsByIdx(
+                                            fHWGeometryState.fVertexLayout,
+                                            oldTexCoordOffsets,
+                                            &oldColorOffset,
+                                            &oldCoverageOffset,
+                                            &oldEdgeOffset);
+    bool indexed = NULL != startIndex;
+
+    int extraVertexOffset;
+    int extraIndexOffset;
+    this->setBuffers(indexed, &extraVertexOffset, &extraIndexOffset);
+
+    GrGLenum scalarType;
+    bool texCoordNorm;
+    if (currLayout & kTextFormat_VertexLayoutBit) {
+        scalarType = TEXT_COORDS_GL_TYPE;
+        texCoordNorm = SkToBool(TEXT_COORDS_ARE_NORMALIZED);
+    } else {
+        GR_STATIC_ASSERT(GR_SCALAR_IS_FLOAT);
+        scalarType = GR_GL_FLOAT;
+        texCoordNorm = false;
+    }
+
+    size_t vertexOffset = (*startVertex + extraVertexOffset) * newStride;
+    *startVertex = 0;
+    if (indexed) {
+        *startIndex += extraIndexOffset;
+    }
+
+    // all the Pointers must be set if any of these are true
+    bool allOffsetsChange =  fHWGeometryState.fArrayPtrsDirty ||
+                             vertexOffset != fHWGeometryState.fVertexOffset ||
+                             newStride != oldStride;
+
+    // position and tex coord offsets change if above conditions are true
+    // or the type/normalization changed based on text vs nontext type coords.
+    bool posAndTexChange = allOffsetsChange ||
+                           (((TEXT_COORDS_GL_TYPE != GR_GL_FLOAT) || TEXT_COORDS_ARE_NORMALIZED) &&
+                                (kTextFormat_VertexLayoutBit &
+                                  (fHWGeometryState.fVertexLayout ^ currLayout)));
+
+    if (posAndTexChange) {
+        int idx = GrGLProgram::PositionAttributeIdx();
+        GL_CALL(VertexAttribPointer(idx, 2, scalarType, false, newStride, 
+                                  (GrGLvoid*)vertexOffset));
+        fHWGeometryState.fVertexOffset = vertexOffset;
+    }
+
+    for (int t = 0; t < GrDrawState::kMaxTexCoords; ++t) {
+        if (newTexCoordOffsets[t] > 0) {
+            GrGLvoid* texCoordOffset = (GrGLvoid*)(vertexOffset + newTexCoordOffsets[t]);
+            int idx = GrGLProgram::TexCoordAttributeIdx(t);
+            if (oldTexCoordOffsets[t] <= 0) {
+                GL_CALL(EnableVertexAttribArray(idx));
+                GL_CALL(VertexAttribPointer(idx, 2, scalarType, texCoordNorm, 
+                                          newStride, texCoordOffset));
+            } else if (posAndTexChange ||
+                       newTexCoordOffsets[t] != oldTexCoordOffsets[t]) {
+                GL_CALL(VertexAttribPointer(idx, 2, scalarType, texCoordNorm, 
+                                          newStride, texCoordOffset));
+            }
+        } else if (oldTexCoordOffsets[t] > 0) {
+            GL_CALL(DisableVertexAttribArray(GrGLProgram::TexCoordAttributeIdx(t)));
+        }
+    }
+
+    if (newColorOffset > 0) {
+        GrGLvoid* colorOffset = (int8_t*)(vertexOffset + newColorOffset);
+        int idx = GrGLProgram::ColorAttributeIdx();
+        if (oldColorOffset <= 0) {
+            GL_CALL(EnableVertexAttribArray(idx));
+            GL_CALL(VertexAttribPointer(idx, 4, GR_GL_UNSIGNED_BYTE,
+                                      true, newStride, colorOffset));
+        } else if (allOffsetsChange || newColorOffset != oldColorOffset) {
+            GL_CALL(VertexAttribPointer(idx, 4, GR_GL_UNSIGNED_BYTE,
+                                      true, newStride, colorOffset));
+        }
+    } else if (oldColorOffset > 0) {
+        GL_CALL(DisableVertexAttribArray(GrGLProgram::ColorAttributeIdx()));
+    }
+
+    if (newCoverageOffset > 0) {
+        GrGLvoid* coverageOffset = (int8_t*)(vertexOffset + newCoverageOffset);
+        int idx = GrGLProgram::CoverageAttributeIdx();
+        if (oldCoverageOffset <= 0) {
+            GL_CALL(EnableVertexAttribArray(idx));
+            GL_CALL(VertexAttribPointer(idx, 4, GR_GL_UNSIGNED_BYTE,
+                                        true, newStride, coverageOffset));
+        } else if (allOffsetsChange || newCoverageOffset != oldCoverageOffset) {
+            GL_CALL(VertexAttribPointer(idx, 4, GR_GL_UNSIGNED_BYTE,
+                                        true, newStride, coverageOffset));
+        }
+    } else if (oldCoverageOffset > 0) {
+        GL_CALL(DisableVertexAttribArray(GrGLProgram::CoverageAttributeIdx()));
+    }
+
+    if (newEdgeOffset > 0) {
+        GrGLvoid* edgeOffset = (int8_t*)(vertexOffset + newEdgeOffset);
+        int idx = GrGLProgram::EdgeAttributeIdx();
+        if (oldEdgeOffset <= 0) {
+            GL_CALL(EnableVertexAttribArray(idx));
+            GL_CALL(VertexAttribPointer(idx, 4, scalarType,
+                                        false, newStride, edgeOffset));
+        } else if (allOffsetsChange || newEdgeOffset != oldEdgeOffset) {
+            GL_CALL(VertexAttribPointer(idx, 4, scalarType,
+                                        false, newStride, edgeOffset));
+        }
+    } else if (oldEdgeOffset > 0) {
+        GL_CALL(DisableVertexAttribArray(GrGLProgram::EdgeAttributeIdx()));
+    }
+
+    fHWGeometryState.fVertexLayout = currLayout;
+    fHWGeometryState.fArrayPtrsDirty = false;
+}
+
+namespace {
+
+void setup_custom_stage(GrGLProgram::ProgramDesc::StageDesc* stage,
+                        const GrSamplerState& sampler,
+                        GrCustomStage** customStages,
+                        GrGLProgram* program, int index) {
+    GrCustomStage* customStage = sampler.getCustomStage();
+    if (customStage) {
+        const GrProgramStageFactory& factory = customStage->getFactory();
+        stage->fCustomStageKey = factory.glStageKey(customStage);
+        customStages[index] = customStage;
+    } else {
+        stage->fCustomStageKey = 0;
+        customStages[index] = NULL;
+    }
+}
+
+}
+
+void GrGpuGL::buildProgram(GrPrimitiveType type,
+                                  BlendOptFlags blendOpts,
+                                  GrBlendCoeff dstCoeff,
+                                  GrCustomStage** customStages) {
+    ProgramDesc& desc = fCurrentProgram.fProgramDesc;
+    const GrDrawState& drawState = this->getDrawState();
+
+    // This should already have been caught
+    GrAssert(!(kSkipDraw_BlendOptFlag & blendOpts));
+
+    bool skipCoverage = SkToBool(blendOpts & kEmitTransBlack_BlendOptFlag);
+
+    bool skipColor = SkToBool(blendOpts & (kEmitTransBlack_BlendOptFlag |
+                                           kEmitCoverage_BlendOptFlag));
+
+    // The descriptor is used as a cache key. Thus when a field of the
+    // descriptor will not affect program generation (because of the vertex
+    // layout in use or other descriptor field settings) it should be set
+    // to a canonical value to avoid duplicate programs with different keys.
+
+    // Must initialize all fields or cache will have false negatives!
+    desc.fVertexLayout = this->getVertexLayout();
+
+    desc.fEmitsPointSize = kPoints_PrimitiveType == type;
+
+    bool requiresAttributeColors = 
+        !skipColor && SkToBool(desc.fVertexLayout & kColor_VertexLayoutBit);
+    bool requiresAttributeCoverage = 
+        !skipCoverage && SkToBool(desc.fVertexLayout &
+                                  kCoverage_VertexLayoutBit);
+
+    // fColorInput/fCoverageInput records how colors are specified for the.
+    // program. So we strip the bits from the layout to avoid false negatives
+    // when searching for an existing program in the cache.
+    desc.fVertexLayout &= ~(kColor_VertexLayoutBit | kCoverage_VertexLayoutBit);
+
+    desc.fColorFilterXfermode = skipColor ?
+                                SkXfermode::kDst_Mode :
+                                drawState.getColorFilterMode();
+
+    desc.fColorMatrixEnabled = drawState.isStateFlagEnabled(GrDrawState::kColorMatrix_StateBit);
+
+    // no reason to do edge aa or look at per-vertex coverage if coverage is
+    // ignored
+    if (skipCoverage) {
+        desc.fVertexLayout &= ~(kEdge_VertexLayoutBit |
+                                kCoverage_VertexLayoutBit);
+    }
+
+    bool colorIsTransBlack = SkToBool(blendOpts & kEmitTransBlack_BlendOptFlag);
+    bool colorIsSolidWhite = (blendOpts & kEmitCoverage_BlendOptFlag) ||
+                             (!requiresAttributeColors &&
+                              0xffffffff == drawState.getColor());
+    if (GR_AGGRESSIVE_SHADER_OPTS && colorIsTransBlack) {
+        desc.fColorInput = ProgramDesc::kTransBlack_ColorInput;
+    } else if (GR_AGGRESSIVE_SHADER_OPTS && colorIsSolidWhite) {
+        desc.fColorInput = ProgramDesc::kSolidWhite_ColorInput;
+    } else if (GR_GL_NO_CONSTANT_ATTRIBUTES && !requiresAttributeColors) {
+        desc.fColorInput = ProgramDesc::kUniform_ColorInput;
+    } else {
+        desc.fColorInput = ProgramDesc::kAttribute_ColorInput;
+    }
+    
+    bool covIsSolidWhite = !requiresAttributeCoverage &&
+                           0xffffffff == drawState.getCoverage();
+    
+    if (skipCoverage) {
+        desc.fCoverageInput = ProgramDesc::kTransBlack_ColorInput;
+    } else if (covIsSolidWhite) {
+        desc.fCoverageInput = ProgramDesc::kSolidWhite_ColorInput;
+    } else if (GR_GL_NO_CONSTANT_ATTRIBUTES && !requiresAttributeCoverage) {
+        desc.fCoverageInput = ProgramDesc::kUniform_ColorInput;
+    } else {
+        desc.fCoverageInput = ProgramDesc::kAttribute_ColorInput;
+    }
+
+    int lastEnabledStage = -1;
+
+    if (!skipCoverage && (desc.fVertexLayout &
+                          GrDrawTarget::kEdge_VertexLayoutBit)) {
+        desc.fVertexEdgeType = drawState.getVertexEdgeType();
+    } else {
+        // use canonical value when not set to avoid cache misses
+        desc.fVertexEdgeType = GrDrawState::kHairLine_EdgeType;
+    }
+
+    for (int s = 0; s < GrDrawState::kNumStages; ++s) {
+        StageDesc& stage = desc.fStages[s];
+
+        stage.fOptFlags = 0;
+        stage.setEnabled(this->isStageEnabled(s));
+
+        bool skip = s < drawState.getFirstCoverageStage() ? skipColor :
+                                                             skipCoverage;
+
+        if (!skip && stage.isEnabled()) {
+            lastEnabledStage = s;
+            const GrGLTexture* texture =
+                static_cast<const GrGLTexture*>(drawState.getTexture(s));
+            GrAssert(NULL != texture);
+            const GrSamplerState& sampler = drawState.getSampler(s);
+            // we matrix to invert when orientation is TopDown, so make sure
+            // we aren't in that case before flagging as identity.
+            if (TextureMatrixIsIdentity(texture, sampler)) {
+                stage.fOptFlags |= StageDesc::kIdentityMatrix_OptFlagBit;
+            } else if (!sampler.getMatrix().hasPerspective()) {
+                stage.fOptFlags |= StageDesc::kNoPerspective_OptFlagBit;
+            }
+            switch (sampler.getSampleMode()) {
+                case GrSamplerState::kNormal_SampleMode:
+                    stage.fCoordMapping = StageDesc::kIdentity_CoordMapping;
+                    break;
+                case GrSamplerState::kRadial_SampleMode:
+                    stage.fCoordMapping = StageDesc::kRadialGradient_CoordMapping;
+                    break;
+                case GrSamplerState::kRadial2_SampleMode:
+                    if (sampler.radial2IsDegenerate()) {
+                        stage.fCoordMapping =
+                            StageDesc::kRadial2GradientDegenerate_CoordMapping;
+                    } else {
+                        stage.fCoordMapping =
+                            StageDesc::kRadial2Gradient_CoordMapping;
+                    }
+                    break;
+                case GrSamplerState::kSweep_SampleMode:
+                    stage.fCoordMapping = StageDesc::kSweepGradient_CoordMapping;
+                    break;
+                default:
+                    GrCrash("Unexpected sample mode!");
+                    break;
+            }
+
+            switch (sampler.getFilter()) {
+                // these both can use a regular texture2D()
+                case GrSamplerState::kNearest_Filter:
+                case GrSamplerState::kBilinear_Filter:
+                    stage.fFetchMode = StageDesc::kSingle_FetchMode;
+                    break;
+                // performs 4 texture2D()s
+                case GrSamplerState::k4x4Downsample_Filter:
+                    stage.fFetchMode = StageDesc::k2x2_FetchMode;
+                    break;
+                // performs fKernelWidth texture2D()s
+                case GrSamplerState::kConvolution_Filter:
+                    stage.fFetchMode = StageDesc::kConvolution_FetchMode;
+                    break;
+                case GrSamplerState::kDilate_Filter:
+                    stage.fFetchMode = StageDesc::kDilate_FetchMode;
+                    break;
+                case GrSamplerState::kErode_Filter:
+                    stage.fFetchMode = StageDesc::kErode_FetchMode;
+                    break;
+                default:
+                    GrCrash("Unexpected filter!");
+                    break;
+            }
+
+            if (sampler.hasTextureDomain()) {
+                GrAssert(GrSamplerState::kClamp_WrapMode ==
+                            sampler.getWrapX() &&
+                         GrSamplerState::kClamp_WrapMode ==
+                            sampler.getWrapY());
+                stage.fOptFlags |= StageDesc::kCustomTextureDomain_OptFlagBit;
+            }
+
+            stage.fInConfigFlags = 0;
+            if (!this->glCaps().textureSwizzleSupport()) {
+                if (GrPixelConfigIsAlphaOnly(texture->config())) {
+                    // if we don't have texture swizzle support then
+                    // the shader must smear the single channel after
+                    // reading the texture
+                    if (this->glCaps().textureRedSupport()) {
+                        // we can use R8 textures so use kSmearRed
+                        stage.fInConfigFlags |= 
+                                        StageDesc::kSmearRed_InConfigFlag;
+                    } else {
+                        // we can use A8 textures so use kSmearAlpha
+                        stage.fInConfigFlags |= 
+                                        StageDesc::kSmearAlpha_InConfigFlag;
+                    }
+                } else if (sampler.swapsRAndB()) {
+                    stage.fInConfigFlags |= StageDesc::kSwapRAndB_InConfigFlag;
+                }
+            }
+            if (GrPixelConfigIsUnpremultiplied(texture->config())) {
+                // The shader generator assumes that color channels are bytes
+                // when rounding.
+                GrAssert(4 == GrBytesPerPixel(texture->config()));
+                if (kUpOnWrite_DownOnRead_UnpremulConversion ==
+                    fUnpremulConversion) {
+                    stage.fInConfigFlags |=
+                        StageDesc::kMulRGBByAlpha_RoundDown_InConfigFlag;
+                } else {
+                    stage.fInConfigFlags |=
+                        StageDesc::kMulRGBByAlpha_RoundUp_InConfigFlag;
+                }
+            }
+
+            if (sampler.getFilter() == GrSamplerState::kDilate_Filter ||
+                sampler.getFilter() == GrSamplerState::kErode_Filter) {
+                stage.fKernelWidth = sampler.getKernelWidth();
+            } else {
+                stage.fKernelWidth = 0;
+            }
+
+            setup_custom_stage(&stage, sampler, customStages,
+                               &fCurrentProgram, s);
+
+        } else {
+            stage.fOptFlags         = 0;
+            stage.fCoordMapping     = (StageDesc::CoordMapping) 0;
+            stage.fInConfigFlags    = 0;
+            stage.fFetchMode        = (StageDesc::FetchMode) 0;
+            stage.fKernelWidth      = 0;
+            stage.fCustomStageKey   = 0;
+            customStages[s] = NULL;
+        }
+    }
+
+    if (GrPixelConfigIsUnpremultiplied(drawState.getRenderTarget()->config())) {
+        // The shader generator assumes that color channels are bytes
+        // when rounding.
+        GrAssert(4 == GrBytesPerPixel(drawState.getRenderTarget()->config()));
+        if (kUpOnWrite_DownOnRead_UnpremulConversion == fUnpremulConversion) {
+            desc.fOutputConfig =
+                ProgramDesc::kUnpremultiplied_RoundUp_OutputConfig;
+        } else {
+            desc.fOutputConfig =
+                ProgramDesc::kUnpremultiplied_RoundDown_OutputConfig;
+        }
+    } else {
+        desc.fOutputConfig = ProgramDesc::kPremultiplied_OutputConfig;
+    }
+
+    desc.fDualSrcOutput = ProgramDesc::kNone_DualSrcOutput;
+
+    // currently the experimental GS will only work with triangle prims
+    // (and it doesn't do anything other than pass through values from
+    // the VS to the FS anyway).
+#if 0 && GR_GL_EXPERIMENTAL_GS
+    desc.fExperimentalGS = this->getCaps().fGeometryShaderSupport;
+#endif
+
+    // we want to avoid generating programs with different "first cov stage"
+    // values when they would compute the same result.
+    // We set field in the desc to kNumStages when either there are no 
+    // coverage stages or the distinction between coverage and color is
+    // immaterial.
+    int firstCoverageStage = GrDrawState::kNumStages;
+    desc.fFirstCoverageStage = GrDrawState::kNumStages;
+    bool hasCoverage = drawState.getFirstCoverageStage() <= lastEnabledStage;
+    if (hasCoverage) {
+        firstCoverageStage = drawState.getFirstCoverageStage();
+    }
+
+    // other coverage inputs
+    if (!hasCoverage) {
+        hasCoverage =
+               requiresAttributeCoverage ||
+               (desc.fVertexLayout & GrDrawTarget::kEdge_VertexLayoutBit);
+    }
+
+    if (hasCoverage) {
+        // color filter is applied between color/coverage computation
+        if (SkXfermode::kDst_Mode != desc.fColorFilterXfermode) {
+            desc.fFirstCoverageStage = firstCoverageStage;
+        }
+
+        if (this->getCaps().fDualSourceBlendingSupport &&
+            !(blendOpts & (kEmitCoverage_BlendOptFlag |
+                           kCoverageAsAlpha_BlendOptFlag))) {
+            if (kZero_BlendCoeff == dstCoeff) {
+                // write the coverage value to second color
+                desc.fDualSrcOutput =  ProgramDesc::kCoverage_DualSrcOutput;
+                desc.fFirstCoverageStage = firstCoverageStage;
+            } else if (kSA_BlendCoeff == dstCoeff) {
+                // SA dst coeff becomes 1-(1-SA)*coverage when dst is partially 
+                // cover
+                desc.fDualSrcOutput = ProgramDesc::kCoverageISA_DualSrcOutput;
+                desc.fFirstCoverageStage = firstCoverageStage;
+            } else if (kSC_BlendCoeff == dstCoeff) {
+                // SA dst coeff becomes 1-(1-SA)*coverage when dst is partially
+                // cover
+                desc.fDualSrcOutput = ProgramDesc::kCoverageISC_DualSrcOutput;
+                desc.fFirstCoverageStage = firstCoverageStage;
+            }
+        }
+    }
+}