コード例 #1
0
ファイル: GrGLShaderBuilder.cpp プロジェクト: xuwakao/skia
GrGLFragmentOnlyShaderBuilder::GrGLFragmentOnlyShaderBuilder(GrGpuGL* gpu,
                                                             const GrGLProgramDesc& desc)
    : INHERITED(gpu, desc) {
    SkASSERT(!desc.getHeader().fHasVertexCode);
    SkASSERT(gpu->glCaps().pathRenderingSupport());
    SkASSERT(GrGLProgramDesc::kAttribute_ColorInput != desc.getHeader().fColorInput);
    SkASSERT(GrGLProgramDesc::kAttribute_ColorInput != desc.getHeader().fCoverageInput);
}
コード例 #2
0
GrGLFragmentOnlyShaderBuilder::GrGLFragmentOnlyShaderBuilder(GrGpuGL* gpu,
        GrGLUniformManager& uniformManager,
        const GrGLProgramDesc& desc)
    : INHERITED(gpu, uniformManager, desc)
    , fNumTexCoordSets(0) {

    SkASSERT(!desc.getHeader().fHasVertexCode);
    SkASSERT(gpu->glCaps().fixedFunctionSupport());
    SkASSERT(gpu->glCaps().pathRenderingSupport());
    SkASSERT(GrGLProgramDesc::kAttribute_ColorInput != desc.getHeader().fColorInput);
    SkASSERT(GrGLProgramDesc::kAttribute_ColorInput != desc.getHeader().fCoverageInput);
}
コード例 #3
0
GrGLProgram* GrGLGpu::ProgramCache::refProgram(const GrGLGpu* gpu,
                                               const GrPipeline& pipeline,
                                               const GrPrimitiveProcessor& primProc) {
#ifdef PROGRAM_CACHE_STATS
    ++fTotalRequests;
#endif

    // Get GrGLProgramDesc
    GrGLProgramDesc desc;
    if (!GrGLProgramDescBuilder::Build(&desc, primProc, pipeline, *gpu->glCaps().glslCaps())) {
        GrCapsDebugf(gpu->caps(), "Failed to gl program descriptor!\n");
        return nullptr;
    }

    Entry* entry = nullptr;

    uint32_t hashIdx = desc.getChecksum();
    hashIdx ^= hashIdx >> 16;
    if (kHashBits <= 8) {
        hashIdx ^= hashIdx >> 8;
    }
コード例 #4
0
ファイル: GrGLShaderBuilder.cpp プロジェクト: cnh/skia
GrGLShaderBuilder::GrGLShaderBuilder(GrGpuGL* gpu,
                                     GrGLUniformManager* uniformManager,
                                     const GrGLProgramDesc& desc)
    : fDesc(desc)
    , fGpu(gpu)
    , fUniformManager(SkRef(uniformManager))
    , fFSFeaturesAddedMask(0)
    , fFSInputs(kVarsPerBlock)
    , fFSOutputs(kMaxFSOutputs)
    , fUniforms(kVarsPerBlock)
    , fSetupFragPosition(false)
    , fTopLeftFragPosRead(kTopLeftFragPosRead_FragPosKey == desc.getHeader().fFragPosKey)
    , fHasCustomColorOutput(false)
    , fHasSecondaryOutput(false) {
}
コード例 #5
0
ファイル: GrGLShaderBuilder.cpp プロジェクト: xuwakao/skia
GrGLShaderBuilder::GrGLShaderBuilder(GrGpuGL* gpu,
                                     const GrGLProgramDesc& desc)
    : fHasVertexShader(false)
    , fTexCoordSetCnt(0)
    , fProgramID(0)
    , fDesc(desc)
    , fGpu(gpu)
    , fFSFeaturesAddedMask(0)
    , fFSInputs(kVarsPerBlock)
    , fFSOutputs(kMaxFSOutputs)
    , fUniforms(kVarsPerBlock)
    , fSetupFragPosition(false)
    , fTopLeftFragPosRead(kTopLeftFragPosRead_FragPosKey == desc.getHeader().fFragPosKey)
    , fHasCustomColorOutput(false)
    , fHasSecondaryOutput(false) {
}
コード例 #6
0
ファイル: GrGLShaderBuilder.cpp プロジェクト: cnh/skia
bool GrGLShaderBuilder::GenProgram(GrGpuGL* gpu,
                                   GrGLUniformManager* uman,
                                   const GrGLProgramDesc& desc,
                                   const GrEffectStage* inColorStages[],
                                   const GrEffectStage* inCoverageStages[],
                                   GenProgramOutput* output) {
    SkAutoTDelete<GrGLShaderBuilder> builder;
    if (desc.getHeader().fHasVertexCode ||!gpu->shouldUseFixedFunctionTexturing()) {
        builder.reset(SkNEW_ARGS(GrGLFullShaderBuilder, (gpu, uman, desc)));
    } else {
        builder.reset(SkNEW_ARGS(GrGLFragmentOnlyShaderBuilder, (gpu, uman, desc)));
    }
    if (builder->genProgram(inColorStages, inCoverageStages)) {
        *output = builder->getOutput();
        return true;
    }
    return false;
}
コード例 #7
0
ファイル: GLProgramsTest.cpp プロジェクト: exploreword/skia
bool GrGpuGL::programUnitTest(int maxStages) {

    GrTextureDesc dummyDesc;
    dummyDesc.fFlags = kRenderTarget_GrTextureFlagBit;
    dummyDesc.fConfig = kSkia8888_GrPixelConfig;
    dummyDesc.fWidth = 34;
    dummyDesc.fHeight = 18;
    SkAutoTUnref<GrTexture> dummyTexture1(this->createTexture(dummyDesc, NULL, 0));
    dummyDesc.fFlags = kNone_GrTextureFlags;
    dummyDesc.fConfig = kAlpha_8_GrPixelConfig;
    dummyDesc.fWidth = 16;
    dummyDesc.fHeight = 22;
    SkAutoTUnref<GrTexture> dummyTexture2(this->createTexture(dummyDesc, NULL, 0));

    if (!dummyTexture1 || ! dummyTexture2) {
        return false;
    }

    static const int NUM_TESTS = 512;

    SkRandom 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

        GrGLProgramDesc pdesc;

        int currAttribIndex = 1;  // we need to always leave room for position
        int currTextureCoordSet = 0;
        GrTexture* dummyTextures[] = {dummyTexture1.get(), dummyTexture2.get()};

        int numStages = random.nextULessThan(maxStages + 1);
        int numColorStages = random.nextULessThan(numStages + 1);
        int numCoverageStages = numStages - numColorStages;

        SkAutoSTMalloc<8, const GrFragmentStage*> stages(numStages);

        bool usePathRendering = this->glCaps().pathRenderingSupport() && random.nextBool();

        GrGpu::DrawType drawType = usePathRendering ? GrGpu::kDrawPath_DrawType :
                                                      GrGpu::kDrawPoints_DrawType;

        SkAutoTDelete<GrGeometryStage> geometryProcessor;
        bool hasGeometryProcessor = usePathRendering ? false : random.nextBool();
        if (hasGeometryProcessor) {
            while (true) {
                SkAutoTUnref<const GrGeometryProcessor> effect(
                        GrProcessorTestFactory<GrGeometryProcessor>::CreateStage(&random, this->getContext(), *this->caps(),
                                                         dummyTextures));
                SkASSERT(effect);
                // Only geometryProcessor can use vertex shader
                GrGeometryStage* stage = SkNEW_ARGS(GrGeometryStage, (effect.get()));
                geometryProcessor.reset(stage);

                // we have to set dummy vertex attribs
                const GrGeometryProcessor::VertexAttribArray& v = effect->getVertexAttribs();
                int numVertexAttribs = v.count();

                SkASSERT(GrGeometryProcessor::kMaxVertexAttribs == 2 &&
                         GrGeometryProcessor::kMaxVertexAttribs >= numVertexAttribs);
                size_t runningStride = GrVertexAttribTypeSize(genericVertexAttribs[0].fType);
                for (int i = 0; i < numVertexAttribs; i++) {
                    genericVertexAttribs[i + 1].fOffset = runningStride;
                    genericVertexAttribs[i + 1].fType =
                            convert_sltype_to_attribtype(v[i].getType());
                    runningStride += GrVertexAttribTypeSize(genericVertexAttribs[i + 1].fType);
                }

                // update the vertex attributes with the ds
                GrDrawState* ds = this->drawState();
                ds->setVertexAttribs<genericVertexAttribs>(numVertexAttribs + 1, runningStride);
                currAttribIndex = numVertexAttribs + 1;
                break;
            }
        }
        for (int s = 0; s < numStages;) {
            SkAutoTUnref<const GrFragmentProcessor> effect(
                    GrProcessorTestFactory<GrFragmentProcessor>::CreateStage(
                                                                            &random,
                                                                            this->getContext(),
                                                                            *this->caps(),
                                                                            dummyTextures));
            SkASSERT(effect);

            // If adding this effect would exceed the max texture coord set count then generate a
            // new random effect.
            if (usePathRendering && this->glPathRendering()->texturingMode() ==
                                    GrGLPathRendering::FixedFunction_TexturingMode) {;
                int numTransforms = effect->numTransforms();
                if (currTextureCoordSet + numTransforms > this->glCaps().maxFixedFunctionTextureCoords()) {
                    continue;
                }
                currTextureCoordSet += numTransforms;
            }
            GrFragmentStage* stage = SkNEW_ARGS(GrFragmentStage, (effect.get()));

            stages[s] = stage;
            ++s;
        }
        const GrTexture* dstTexture = random.nextBool() ? dummyTextures[0] : dummyTextures[1];
        if (!pdesc.setRandom(&random,
                             this,
                             dummyTextures[0]->asRenderTarget(),
                             dstTexture,
                             geometryProcessor.get(),
                             stages.get(),
                             numColorStages,
                             numCoverageStages,
                             currAttribIndex,
                             drawType)) {
            return false;
        }

        SkAutoTUnref<GrOptDrawState> optState(GrOptDrawState::Create(this->getDrawState(),
                                                                     *this->caps(),
                                                                     drawType));
        SkAutoTUnref<GrGLProgram> program(
                        GrGLProgramBuilder::CreateProgram(*optState,
                                                          pdesc,
                                                          drawType,
                                                          geometryProcessor,
                                                          stages,
                                                          stages + numColorStages,
                                                          this));
        for (int s = 0; s < numStages; ++s) {
            SkDELETE(stages[s]);
        }
        if (NULL == program.get()) {
            return false;
        }

        // We have to reset the drawstate because we might have added a gp
        this->drawState()->reset();
    }
    return true;
}
コード例 #8
0
ファイル: GLProgramsTest.cpp プロジェクト: Adenilson/skia
bool GrGpuGL::programUnitTest(int maxStages) {

    GrTextureDesc dummyDesc;
    dummyDesc.fFlags = kRenderTarget_GrTextureFlagBit;
    dummyDesc.fConfig = kSkia8888_GrPixelConfig;
    dummyDesc.fWidth = 34;
    dummyDesc.fHeight = 18;
    SkAutoTUnref<GrTexture> dummyTexture1(this->createTexture(dummyDesc, NULL, 0));
    dummyDesc.fFlags = kNone_GrTextureFlags;
    dummyDesc.fConfig = kAlpha_8_GrPixelConfig;
    dummyDesc.fWidth = 16;
    dummyDesc.fHeight = 22;
    SkAutoTUnref<GrTexture> dummyTexture2(this->createTexture(dummyDesc, NULL, 0));

    static const int NUM_TESTS = 512;

    SkRandom 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

        GrGLProgramDesc pdesc;

        int currAttribIndex = 1;  // we need to always leave room for position
        int currTextureCoordSet = 0;
        int attribIndices[2] = { 0, 0 };
        GrTexture* dummyTextures[] = {dummyTexture1.get(), dummyTexture2.get()};

        int numStages = random.nextULessThan(maxStages + 1);
        int numColorStages = random.nextULessThan(numStages + 1);
        int numCoverageStages = numStages - numColorStages;

        SkAutoSTMalloc<8, const GrEffectStage*> stages(numStages);

        bool useFixedFunctionTexturing = this->shouldUseFixedFunctionTexturing();

        for (int s = 0; s < numStages;) {
            SkAutoTUnref<const GrEffectRef> effect(GrEffectTestFactory::CreateStage(
                                                                            &random,
                                                                            this->getContext(),
                                                                            *this->caps(),
                                                                            dummyTextures));
            SkASSERT(effect);
            int numAttribs = (*effect)->numVertexAttribs();

            // If adding this effect would exceed the max attrib count then generate a
            // new random effect.
            if (currAttribIndex + numAttribs > GrDrawState::kMaxVertexAttribCnt) {
                continue;
            }


            // If adding this effect would exceed the max texture coord set count then generate a
            // new random effect.
            if (useFixedFunctionTexturing && !(*effect)->hasVertexCode()) {
                int numTransforms = (*effect)->numTransforms();
                if (currTextureCoordSet + numTransforms > this->glCaps().maxFixedFunctionTextureCoords()) {
                    continue;
                }
                currTextureCoordSet += numTransforms;
            }

            useFixedFunctionTexturing = useFixedFunctionTexturing && !(*effect)->hasVertexCode();

            for (int i = 0; i < numAttribs; ++i) {
                attribIndices[i] = currAttribIndex++;
            }
            GrEffectStage* stage = SkNEW_ARGS(GrEffectStage,
                                              (effect.get(), attribIndices[0], attribIndices[1]));
            stages[s] = stage;
            ++s;
        }
        const GrTexture* dstTexture = random.nextBool() ? dummyTextures[0] : dummyTextures[1];
        pdesc.setRandom(&random,
                        this,
                        dummyTextures[0]->asRenderTarget(),
                        dstTexture,
                        stages.get(),
                        numColorStages,
                        numCoverageStages,
                        currAttribIndex);

        SkAutoTUnref<GrGLProgram> program(GrGLProgram::Create(this,
                                                              pdesc,
                                                              stages,
                                                              stages + numColorStages));
        for (int s = 0; s < numStages; ++s) {
            SkDELETE(stages[s]);
        }
        if (NULL == program.get()) {
            return false;
        }
    }
    return true;
}
コード例 #9
0
ファイル: GrGLProgramDesc.cpp プロジェクト: WangCrystal/skia
bool GrGLProgramDescBuilder::Build(GrProgramDesc* desc,
                                   const GrPrimitiveProcessor& primProc,
                                   const GrPipeline& pipeline,
                                   const GrGLGpu* gpu,
                                   const GrBatchTracker& batchTracker) {
    // The descriptor is used as a cache key. Thus when a field of the
    // descriptor will not affect program generation (because of the attribute
    // bindings in use or other descriptor field settings) it should be set
    // to a canonical value to avoid duplicate programs with different keys.

    GrGLProgramDesc* glDesc = (GrGLProgramDesc*) desc;

    GR_STATIC_ASSERT(0 == kProcessorKeysOffset % sizeof(uint32_t));
    // Make room for everything up to the effect keys.
    glDesc->key().reset();
    glDesc->key().push_back_n(kProcessorKeysOffset);

    GrProcessorKeyBuilder b(&glDesc->key());

    primProc.getGLProcessorKey(batchTracker, gpu->glCaps(), &b);
    if (!get_meta_key(primProc, gpu->glCaps(), 0, &b)) {
        glDesc->key().reset();
        return false;
    }

    for (int s = 0; s < pipeline.numFragmentStages(); ++s) {
        const GrPendingFragmentStage& fps = pipeline.getFragmentStage(s);
        const GrFragmentProcessor& fp = *fps.processor();
        fp.getGLProcessorKey(gpu->glCaps(), &b);
        if (!get_meta_key(fp, gpu->glCaps(), primProc.getTransformKey(fp.coordTransforms()), &b)) {
            glDesc->key().reset();
            return false;
        }
    }

    const GrXferProcessor& xp = *pipeline.getXferProcessor();
    xp.getGLProcessorKey(gpu->glCaps(), &b);
    if (!get_meta_key(xp, gpu->glCaps(), 0, &b)) {
        glDesc->key().reset();
        return false;
    }

    // --------DO NOT MOVE HEADER ABOVE THIS LINE--------------------------------------------------
    // Because header is a pointer into the dynamic array, we can't push any new data into the key
    // below here.
    KeyHeader* header = glDesc->atOffset<KeyHeader, kHeaderOffset>();

    // make sure any padding in the header is zeroed.
    memset(header, 0, kHeaderSize);

    if (pipeline.readsFragPosition()) {
        header->fFragPosKey =
                GrGLFragmentShaderBuilder::KeyForFragmentPosition(pipeline.getRenderTarget(),
                                                                  gpu->glCaps());
    } else {
        header->fFragPosKey = 0;
    }

    header->fColorEffectCnt = pipeline.numColorFragmentStages();
    header->fCoverageEffectCnt = pipeline.numCoverageFragmentStages();
    glDesc->finalize();
    return true;
}
コード例 #10
0
bool GrGLProgramDescBuilder::Build(GrProgramDesc* desc,
                                   const GrPrimitiveProcessor& primProc,
                                   const GrPipeline& pipeline,
                                   const GrGLSLCaps& glslCaps) {
    // The descriptor is used as a cache key. Thus when a field of the
    // descriptor will not affect program generation (because of the attribute
    // bindings in use or other descriptor field settings) it should be set
    // to a canonical value to avoid duplicate programs with different keys.

    GrGLProgramDesc* glDesc = (GrGLProgramDesc*) desc;

    GR_STATIC_ASSERT(0 == kProcessorKeysOffset % sizeof(uint32_t));
    // Make room for everything up to the effect keys.
    glDesc->key().reset();
    glDesc->key().push_back_n(kProcessorKeysOffset);

    GrProcessorKeyBuilder b(&glDesc->key());

    primProc.getGLSLProcessorKey(glslCaps, &b);
    if (!gen_meta_key(primProc, glslCaps, 0, &b)) {
        glDesc->key().reset();
        return false;
    }
    GrProcessor::RequiredFeatures requiredFeatures = primProc.requiredFeatures();

    for (int i = 0; i < pipeline.numFragmentProcessors(); ++i) {
        const GrFragmentProcessor& fp = pipeline.getFragmentProcessor(i);
        if (!gen_frag_proc_and_meta_keys(primProc, fp, glslCaps, &b)) {
            glDesc->key().reset();
            return false;
        }
        requiredFeatures |= fp.requiredFeatures();
    }

    const GrXferProcessor& xp = pipeline.getXferProcessor();
    xp.getGLSLProcessorKey(glslCaps, &b);
    if (!gen_meta_key(xp, glslCaps, 0, &b)) {
        glDesc->key().reset();
        return false;
    }
    requiredFeatures |= xp.requiredFeatures();

    // --------DO NOT MOVE HEADER ABOVE THIS LINE--------------------------------------------------
    // Because header is a pointer into the dynamic array, we can't push any new data into the key
    // below here.
    KeyHeader* header = glDesc->atOffset<KeyHeader, kHeaderOffset>();

    // make sure any padding in the header is zeroed.
    memset(header, 0, kHeaderSize);

    GrRenderTarget* rt = pipeline.getRenderTarget();

    if (requiredFeatures & (GrProcessor::kFragmentPosition_RequiredFeature |
                            GrProcessor::kSampleLocations_RequiredFeature)) {
        header->fSurfaceOriginKey = GrGLSLFragmentShaderBuilder::KeyForSurfaceOrigin(rt->origin());
    } else {
        header->fSurfaceOriginKey = 0;
    }

    if (requiredFeatures & GrProcessor::kSampleLocations_RequiredFeature) {
        SkASSERT(pipeline.isHWAntialiasState());
        header->fSamplePatternKey =
            rt->renderTargetPriv().getMultisampleSpecs(pipeline.getStencil()).fUniqueID;
    } else {
        header->fSamplePatternKey = 0;
    }

    header->fOutputSwizzle = glslCaps.configOutputSwizzle(rt->config()).asKey();

    if (pipeline.ignoresCoverage()) {
        header->fIgnoresCoverage = 1;
    } else {
        header->fIgnoresCoverage = 0;
    }

    header->fSnapVerticesToPixelCenters = pipeline.snapVerticesToPixelCenters();
    header->fColorEffectCnt = pipeline.numColorFragmentProcessors();
    header->fCoverageEffectCnt = pipeline.numCoverageFragmentProcessors();
    glDesc->finalize();
    return true;
}
コード例 #11
0
GrGLShaderBuilder::GrGLShaderBuilder(GrGpuGL* gpu,
                                     GrGLUniformManager& uniformManager,
                                     const GrGLProgramDesc& desc)
    : fGpu(gpu)
    , fUniformManager(uniformManager)
    , fFSFeaturesAddedMask(0)
    , fFSInputs(kVarsPerBlock)
    , fFSOutputs(kMaxFSOutputs)
    , fUniforms(kVarsPerBlock)
    , fSetupFragPosition(false)
    , fHasCustomColorOutput(false)
    , fHasSecondaryOutput(false)
    , fTopLeftFragPosRead(kTopLeftFragPosRead_FragPosKey == desc.getHeader().fFragPosKey) {

    const GrGLProgramDesc::KeyHeader& header = desc.getHeader();

    // Emit code to read the dst copy textue if necessary.
    if (kNoDstRead_DstReadKey != header.fDstReadKey &&
            GrGLCaps::kNone_FBFetchType == fGpu->glCaps().fbFetchType()) {
        bool topDown = SkToBool(kTopLeftOrigin_DstReadKeyBit & header.fDstReadKey);
        const char* dstCopyTopLeftName;
        const char* dstCopyCoordScaleName;
        uint32_t configMask;
        if (SkToBool(kUseAlphaConfig_DstReadKeyBit & header.fDstReadKey)) {
            configMask = kA_GrColorComponentFlag;
        } else {
            configMask = kRGBA_GrColorComponentFlags;
        }
        fDstCopySamplerUniform = this->addUniform(kFragment_Visibility,
                                 kSampler2D_GrSLType,
                                 "DstCopySampler");
        fDstCopyTopLeftUniform = this->addUniform(kFragment_Visibility,
                                 kVec2f_GrSLType,
                                 "DstCopyUpperLeft",
                                 &dstCopyTopLeftName);
        fDstCopyScaleUniform     = this->addUniform(kFragment_Visibility,
                                   kVec2f_GrSLType,
                                   "DstCopyCoordScale",
                                   &dstCopyCoordScaleName);
        const char* fragPos = this->fragmentPosition();
        this->fsCodeAppend("\t// Read color from copy of the destination.\n");
        this->fsCodeAppendf("\tvec2 _dstTexCoord = (%s.xy - %s) * %s;\n",
                            fragPos, dstCopyTopLeftName, dstCopyCoordScaleName);
        if (!topDown) {
            this->fsCodeAppend("\t_dstTexCoord.y = 1.0 - _dstTexCoord.y;\n");
        }
        this->fsCodeAppendf("\tvec4 %s = ", kDstCopyColorName);
        append_texture_lookup(&fFSCode,
                              fGpu,
                              this->getUniformCStr(fDstCopySamplerUniform),
                              "_dstTexCoord",
                              configMask,
                              "rgba");
        this->fsCodeAppend(";\n\n");
    }

    if (GrGLProgramDesc::kUniform_ColorInput == header.fColorInput) {
        const char* name;
        fColorUniform = this->addUniform(GrGLShaderBuilder::kFragment_Visibility,
                                         kVec4f_GrSLType, "Color", &name);
        fInputColor = GrGLSLExpr4(name);
    } else if (GrGLProgramDesc::kSolidWhite_ColorInput == header.fColorInput) {
        fInputColor = GrGLSLExpr4(1);
    } else if (GrGLProgramDesc::kTransBlack_ColorInput == header.fColorInput) {
        fInputColor = GrGLSLExpr4(0);
    }

    if (GrGLProgramDesc::kUniform_ColorInput == header.fCoverageInput) {
        const char* name;
        fCoverageUniform = this->addUniform(GrGLShaderBuilder::kFragment_Visibility,
                                            kVec4f_GrSLType, "Coverage", &name);
        fInputCoverage = GrGLSLExpr4(name);
    } else if (GrGLProgramDesc::kSolidWhite_ColorInput == header.fCoverageInput) {
        fInputCoverage = GrGLSLExpr4(1);
    } else if (GrGLProgramDesc::kTransBlack_ColorInput == header.fCoverageInput) {
        fInputCoverage = GrGLSLExpr4(0);
    }

    if (k110_GrGLSLGeneration != fGpu->glslGeneration()) {
        fFSOutputs.push_back().set(kVec4f_GrSLType,
                                   GrGLShaderVar::kOut_TypeModifier,
                                   declared_color_output_name());
        fHasCustomColorOutput = true;
    }
}