1 /*
2  * Copyright 2014 Google Inc.
3  *
4  * Use of this source code is governed by a BSD-style license that can be
5  * found in the LICENSE file.
6  */
7 
8 #include "GrGLProgramBuilder.h"
9 
10 #include "gl/GrGLGeometryProcessor.h"
11 #include "gl/GrGLGpu.h"
12 #include "gl/GrGLPathProcessor.h"
13 #include "gl/GrGLProgram.h"
14 #include "gl/GrGLSLPrettyPrint.h"
15 #include "gl/GrGLUniformHandle.h"
16 #include "gl/GrGLXferProcessor.h"
17 #include "GrAutoLocaleSetter.h"
18 #include "GrCoordTransform.h"
19 #include "GrGLProgramBuilder.h"
20 #include "GrTexture.h"
21 #include "SkRTConf.h"
22 #include "SkTraceEvent.h"
23 
24 #define GL_CALL(X) GR_GL_CALL(this->gpu()->glInterface(), X)
25 #define GL_CALL_RET(R, X) GR_GL_CALL_RET(this->gpu()->glInterface(), R, X)
26 
27 ///////////////////////////////////////////////////////////////////////////////////////////////////
28 
29 class GrGLNvprProgramBuilder : public GrGLProgramBuilder {
30 public:
GrGLNvprProgramBuilder(GrGLGpu * gpu,const DrawArgs & args)31     GrGLNvprProgramBuilder(GrGLGpu* gpu, const DrawArgs& args)
32         : INHERITED(gpu, args) {}
33 
createProgram(GrGLuint programID)34     GrGLProgram* createProgram(GrGLuint programID) override {
35         // this is just for nvpr es, which has separable varyings that are plugged in after
36         // building
37         GrGLPathProcessor* pathProc =
38                 static_cast<GrGLPathProcessor*>(fGeometryProcessor->fGLProc.get());
39         pathProc->resolveSeparableVaryings(fGpu, programID);
40         return SkNEW_ARGS(GrGLNvprProgram, (fGpu, this->desc(), fUniformHandles, programID,
41                                             fUniforms,
42                                             fGeometryProcessor,
43                                             fXferProcessor, fFragmentProcessors.get()));
44     }
45 
46 private:
47     typedef GrGLProgramBuilder INHERITED;
48 };
49 
50 
51 
52 //////////////////////////////////////////////////////////////////////////////
53 
54 const int GrGLProgramBuilder::kVarsPerBlock = 8;
55 
CreateProgram(const DrawArgs & args,GrGLGpu * gpu)56 GrGLProgram* GrGLProgramBuilder::CreateProgram(const DrawArgs& args, GrGLGpu* gpu) {
57     GrAutoLocaleSetter als("C");
58 
59     // create a builder.  This will be handed off to effects so they can use it to add
60     // uniforms, varyings, textures, etc
61     SkAutoTDelete<GrGLProgramBuilder> builder(CreateProgramBuilder(args, gpu));
62 
63     GrGLProgramBuilder* pb = builder.get();
64 
65     // TODO: Once all stages can handle taking a float or vec4 and correctly handling them we can
66     // seed correctly here
67     GrGLSLExpr4 inputColor;
68     GrGLSLExpr4 inputCoverage;
69 
70     if (!pb->emitAndInstallProcs(&inputColor, &inputCoverage)) {
71         return NULL;
72     }
73 
74     return pb->finalize();
75 }
76 
CreateProgramBuilder(const DrawArgs & args,GrGLGpu * gpu)77 GrGLProgramBuilder* GrGLProgramBuilder::CreateProgramBuilder(const DrawArgs& args,
78                                                              GrGLGpu* gpu) {
79     if (args.fPrimitiveProcessor->isPathRendering()) {
80         SkASSERT(gpu->glCaps().shaderCaps()->pathRenderingSupport() &&
81                  !args.fPrimitiveProcessor->willUseGeoShader() &&
82                  args.fPrimitiveProcessor->numAttribs() == 0);
83         return SkNEW_ARGS(GrGLNvprProgramBuilder, (gpu, args));
84     } else {
85         return SkNEW_ARGS(GrGLProgramBuilder, (gpu, args));
86     }
87 }
88 
89 /////////////////////////////////////////////////////////////////////////////
90 
GrGLProgramBuilder(GrGLGpu * gpu,const DrawArgs & args)91 GrGLProgramBuilder::GrGLProgramBuilder(GrGLGpu* gpu, const DrawArgs& args)
92     : fVS(this)
93     , fGS(this)
94     , fFS(this, args.fDesc->header().fFragPosKey)
95     , fOutOfStage(true)
96     , fStageIndex(-1)
97     , fGeometryProcessor(NULL)
98     , fXferProcessor(NULL)
99     , fArgs(args)
100     , fGpu(gpu)
101     , fUniforms(kVarsPerBlock) {
102 }
103 
addVarying(const char * name,GrGLVarying * varying,GrSLPrecision fsPrecision)104 void GrGLProgramBuilder::addVarying(const char* name,
105                                     GrGLVarying* varying,
106                                     GrSLPrecision fsPrecision) {
107     SkASSERT(varying);
108     if (varying->vsVarying()) {
109         fVS.addVarying(name, varying);
110     }
111     if (this->primitiveProcessor().willUseGeoShader()) {
112         fGS.addVarying(name, varying);
113     }
114     if (varying->fsVarying()) {
115         fFS.addVarying(varying, fsPrecision);
116     }
117 }
118 
addPassThroughAttribute(const GrPrimitiveProcessor::Attribute * input,const char * output)119 void GrGLProgramBuilder::addPassThroughAttribute(const GrPrimitiveProcessor::Attribute* input,
120                                                  const char* output) {
121     GrSLType type = GrVertexAttribTypeToSLType(input->fType);
122     GrGLVertToFrag v(type);
123     this->addVarying(input->fName, &v);
124     fVS.codeAppendf("%s = %s;", v.vsOut(), input->fName);
125     fFS.codeAppendf("%s = %s;", output, v.fsIn());
126 }
127 
nameVariable(SkString * out,char prefix,const char * name)128 void GrGLProgramBuilder::nameVariable(SkString* out, char prefix, const char* name) {
129     if ('\0' == prefix) {
130         *out = name;
131     } else {
132         out->printf("%c%s", prefix, name);
133     }
134     if (!fOutOfStage) {
135         if (out->endsWith('_')) {
136             // Names containing "__" are reserved.
137             out->append("x");
138         }
139         out->appendf("_Stage%d", fStageIndex);
140     }
141 }
142 
addUniformArray(uint32_t visibility,GrSLType type,GrSLPrecision precision,const char * name,int count,const char ** outName)143 GrGLProgramDataManager::UniformHandle GrGLProgramBuilder::addUniformArray(
144                                                                 uint32_t visibility,
145                                                                 GrSLType type,
146                                                                 GrSLPrecision precision,
147                                                                 const char* name,
148                                                                 int count,
149                                                                 const char** outName) {
150     SkASSERT(name && strlen(name));
151     SkDEBUGCODE(static const uint32_t kVisibilityMask = kVertex_Visibility | kFragment_Visibility);
152     SkASSERT(0 == (~kVisibilityMask & visibility));
153     SkASSERT(0 != visibility);
154     SkASSERT(kDefault_GrSLPrecision == precision || GrSLTypeIsFloatType(type));
155 
156     UniformInfo& uni = fUniforms.push_back();
157     uni.fVariable.setType(type);
158     uni.fVariable.setTypeModifier(GrGLShaderVar::kUniform_TypeModifier);
159     // TODO this is a bit hacky, lets think of a better way.  Basically we need to be able to use
160     // the uniform view matrix name in the GP, and the GP is immutable so it has to tell the PB
161     // exactly what name it wants to use for the uniform view matrix.  If we prefix anythings, then
162     // the names will mismatch.  I think the correct solution is to have all GPs which need the
163     // uniform view matrix, they should upload the view matrix in their setData along with regular
164     // uniforms.
165     char prefix = 'u';
166     if ('u' == name[0]) {
167         prefix = '\0';
168     }
169     this->nameVariable(uni.fVariable.accessName(), prefix, name);
170     uni.fVariable.setArrayCount(count);
171     uni.fVisibility = visibility;
172     uni.fVariable.setPrecision(precision);
173 
174     if (outName) {
175         *outName = uni.fVariable.c_str();
176     }
177     return GrGLProgramDataManager::UniformHandle::CreateFromUniformIndex(fUniforms.count() - 1);
178 }
179 
appendUniformDecls(ShaderVisibility visibility,SkString * out) const180 void GrGLProgramBuilder::appendUniformDecls(ShaderVisibility visibility,
181                                             SkString* out) const {
182     for (int i = 0; i < fUniforms.count(); ++i) {
183         if (fUniforms[i].fVisibility & visibility) {
184             fUniforms[i].fVariable.appendDecl(this->ctxInfo(), out);
185             out->append(";\n");
186         }
187     }
188 }
189 
ctxInfo() const190 const GrGLContextInfo& GrGLProgramBuilder::ctxInfo() const {
191     return fGpu->ctxInfo();
192 }
193 
emitAndInstallProcs(GrGLSLExpr4 * inputColor,GrGLSLExpr4 * inputCoverage)194 bool GrGLProgramBuilder::emitAndInstallProcs(GrGLSLExpr4* inputColor, GrGLSLExpr4* inputCoverage) {
195     // First we loop over all of the installed processors and collect coord transforms.  These will
196     // be sent to the GrGLPrimitiveProcessor in its emitCode function
197     const GrPrimitiveProcessor& primProc = this->primitiveProcessor();
198     int totalTextures = primProc.numTextures();
199     const int maxTextureUnits = fGpu->glCaps().maxFragmentTextureUnits();
200     SkSTArray<8, GrGLProcessor::TransformedCoordsArray> outCoords;
201     for (int i = 0; i < this->pipeline().numFragmentStages(); i++) {
202         const GrFragmentProcessor* processor = this->pipeline().getFragmentStage(i).processor();
203         SkSTArray<2, const GrCoordTransform*, true>& procCoords = fCoordTransforms.push_back();
204         for (int t = 0; t < processor->numTransforms(); t++) {
205             procCoords.push_back(&processor->coordTransform(t));
206         }
207 
208         totalTextures += processor->numTextures();
209         if (totalTextures >= maxTextureUnits) {
210             GrContextDebugf(fGpu->getContext(), "Program would use too many texture units\n");
211             return false;
212         }
213     }
214 
215     this->emitAndInstallProc(primProc, inputColor, inputCoverage);
216 
217     fFragmentProcessors.reset(SkNEW(GrGLInstalledFragProcs));
218     int numProcs = this->pipeline().numFragmentStages();
219     this->emitAndInstallFragProcs(0, this->pipeline().numColorFragmentStages(), inputColor);
220     this->emitAndInstallFragProcs(this->pipeline().numColorFragmentStages(), numProcs,
221                                   inputCoverage);
222     this->emitAndInstallXferProc(*this->pipeline().getXferProcessor(), *inputColor, *inputCoverage);
223     return true;
224 }
225 
emitAndInstallFragProcs(int procOffset,int numProcs,GrGLSLExpr4 * inOut)226 void GrGLProgramBuilder::emitAndInstallFragProcs(int procOffset,
227                                                  int numProcs,
228                                                  GrGLSLExpr4* inOut) {
229     for (int e = procOffset; e < numProcs; ++e) {
230         GrGLSLExpr4 output;
231         const GrPendingFragmentStage& stage = this->pipeline().getFragmentStage(e);
232         this->emitAndInstallProc(stage, e, *inOut, &output);
233         *inOut = output;
234     }
235 }
236 
nameExpression(GrGLSLExpr4 * output,const char * baseName)237 void GrGLProgramBuilder::nameExpression(GrGLSLExpr4* output, const char* baseName) {
238     // create var to hold stage result.  If we already have a valid output name, just use that
239     // otherwise create a new mangled one.  This name is only valid if we are reordering stages
240     // and have to tell stage exactly where to put its output.
241     SkString outName;
242     if (output->isValid()) {
243         outName = output->c_str();
244     } else {
245         this->nameVariable(&outName, '\0', baseName);
246     }
247     fFS.codeAppendf("vec4 %s;", outName.c_str());
248     *output = outName;
249 }
250 
251 // TODO Processors cannot output zeros because an empty string is all 1s
252 // the fix is to allow effects to take the GrGLSLExpr4 directly
emitAndInstallProc(const GrPendingFragmentStage & proc,int index,const GrGLSLExpr4 & input,GrGLSLExpr4 * output)253 void GrGLProgramBuilder::emitAndInstallProc(const GrPendingFragmentStage& proc,
254                                             int index,
255                                             const GrGLSLExpr4& input,
256                                             GrGLSLExpr4* output) {
257     // Program builders have a bit of state we need to clear with each effect
258     AutoStageAdvance adv(this);
259     this->nameExpression(output, "output");
260 
261     // Enclose custom code in a block to avoid namespace conflicts
262     SkString openBrace;
263     openBrace.printf("{ // Stage %d, %s\n", fStageIndex, proc.name());
264     fFS.codeAppend(openBrace.c_str());
265 
266     this->emitAndInstallProc(proc, index, output->c_str(), input.isOnes() ? NULL : input.c_str());
267 
268     fFS.codeAppend("}");
269 }
270 
emitAndInstallProc(const GrPrimitiveProcessor & proc,GrGLSLExpr4 * outputColor,GrGLSLExpr4 * outputCoverage)271 void GrGLProgramBuilder::emitAndInstallProc(const GrPrimitiveProcessor& proc,
272                                             GrGLSLExpr4* outputColor,
273                                             GrGLSLExpr4* outputCoverage) {
274     // Program builders have a bit of state we need to clear with each effect
275     AutoStageAdvance adv(this);
276     this->nameExpression(outputColor, "outputColor");
277     this->nameExpression(outputCoverage, "outputCoverage");
278 
279     // Enclose custom code in a block to avoid namespace conflicts
280     SkString openBrace;
281     openBrace.printf("{ // Stage %d, %s\n", fStageIndex, proc.name());
282     fFS.codeAppend(openBrace.c_str());
283 
284     this->emitAndInstallProc(proc, outputColor->c_str(), outputCoverage->c_str());
285 
286     fFS.codeAppend("}");
287 }
288 
emitAndInstallProc(const GrPendingFragmentStage & fs,int index,const char * outColor,const char * inColor)289 void GrGLProgramBuilder::emitAndInstallProc(const GrPendingFragmentStage& fs,
290                                             int index,
291                                             const char* outColor,
292                                             const char* inColor) {
293     GrGLInstalledFragProc* ifp = SkNEW(GrGLInstalledFragProc);
294 
295     const GrFragmentProcessor& fp = *fs.processor();
296     ifp->fGLProc.reset(fp.createGLInstance());
297 
298     SkSTArray<4, GrGLProcessor::TextureSampler> samplers(fp.numTextures());
299     this->emitSamplers(fp, &samplers, ifp);
300 
301     ifp->fGLProc->emitCode(this, fp, outColor, inColor, fOutCoords[index], samplers);
302 
303     // We have to check that effects and the code they emit are consistent, ie if an effect
304     // asks for dst color, then the emit code needs to follow suit
305     verify(fp);
306     fFragmentProcessors->fProcs.push_back(ifp);
307 }
308 
emitAndInstallProc(const GrPrimitiveProcessor & gp,const char * outColor,const char * outCoverage)309 void GrGLProgramBuilder::emitAndInstallProc(const GrPrimitiveProcessor& gp,
310                                             const char* outColor,
311                                             const char* outCoverage) {
312     SkASSERT(!fGeometryProcessor);
313     fGeometryProcessor = SkNEW(GrGLInstalledGeoProc);
314 
315     const GrBatchTracker& bt = this->batchTracker();
316     fGeometryProcessor->fGLProc.reset(gp.createGLInstance(bt, *fGpu->glCaps().glslCaps()));
317 
318     SkSTArray<4, GrGLProcessor::TextureSampler> samplers(gp.numTextures());
319     this->emitSamplers(gp, &samplers, fGeometryProcessor);
320 
321     GrGLGeometryProcessor::EmitArgs args(this, gp, bt, outColor, outCoverage, samplers,
322                                          fCoordTransforms, &fOutCoords);
323     fGeometryProcessor->fGLProc->emitCode(args);
324 
325     // We have to check that effects and the code they emit are consistent, ie if an effect
326     // asks for dst color, then the emit code needs to follow suit
327     verify(gp);
328 }
329 
emitAndInstallXferProc(const GrXferProcessor & xp,const GrGLSLExpr4 & colorIn,const GrGLSLExpr4 & coverageIn)330 void GrGLProgramBuilder::emitAndInstallXferProc(const GrXferProcessor& xp,
331                                                 const GrGLSLExpr4& colorIn,
332                                                 const GrGLSLExpr4& coverageIn) {
333     // Program builders have a bit of state we need to clear with each effect
334     AutoStageAdvance adv(this);
335 
336     SkASSERT(!fXferProcessor);
337     fXferProcessor = SkNEW(GrGLInstalledXferProc);
338 
339     fXferProcessor->fGLProc.reset(xp.createGLInstance());
340 
341     // Enable dual source secondary output if we have one
342     if (xp.hasSecondaryOutput()) {
343         fFS.enableSecondaryOutput();
344     }
345 
346     // On any post 1.10 GLSL supporting GPU, we declare custom output
347     if (k110_GrGLSLGeneration != fFS.fProgramBuilder->gpu()->glslGeneration()) {
348         fFS.enableCustomOutput();
349     }
350 
351     SkString openBrace;
352     openBrace.printf("{ // Xfer Processor: %s\n", xp.name());
353     fFS.codeAppend(openBrace.c_str());
354 
355     SkSTArray<4, GrGLProcessor::TextureSampler> samplers(xp.numTextures());
356     this->emitSamplers(xp, &samplers, fXferProcessor);
357 
358     GrGLXferProcessor::EmitArgs args(this, xp, colorIn.c_str(), coverageIn.c_str(),
359                                      fFS.getPrimaryColorOutputName(),
360                                      fFS.getSecondaryColorOutputName(), samplers);
361     fXferProcessor->fGLProc->emitCode(args);
362 
363     // We have to check that effects and the code they emit are consistent, ie if an effect
364     // asks for dst color, then the emit code needs to follow suit
365     verify(xp);
366     fFS.codeAppend("}");
367 }
368 
verify(const GrPrimitiveProcessor & gp)369 void GrGLProgramBuilder::verify(const GrPrimitiveProcessor& gp) {
370     SkASSERT(fFS.hasReadFragmentPosition() == gp.willReadFragmentPosition());
371 }
372 
verify(const GrXferProcessor & xp)373 void GrGLProgramBuilder::verify(const GrXferProcessor& xp) {
374     SkASSERT(fFS.hasReadDstColor() == xp.willReadDstColor());
375 }
376 
verify(const GrFragmentProcessor & fp)377 void GrGLProgramBuilder::verify(const GrFragmentProcessor& fp) {
378     SkASSERT(fFS.hasReadFragmentPosition() == fp.willReadFragmentPosition());
379 }
380 
381 template <class Proc>
emitSamplers(const GrProcessor & processor,GrGLProcessor::TextureSamplerArray * outSamplers,GrGLInstalledProc<Proc> * ip)382 void GrGLProgramBuilder::emitSamplers(const GrProcessor& processor,
383                                       GrGLProcessor::TextureSamplerArray* outSamplers,
384                                       GrGLInstalledProc<Proc>* ip) {
385     int numTextures = processor.numTextures();
386     ip->fSamplers.push_back_n(numTextures);
387     SkString name;
388     for (int t = 0; t < numTextures; ++t) {
389         name.printf("Sampler%d", t);
390         ip->fSamplers[t].fUniform = this->addUniform(GrGLProgramBuilder::kFragment_Visibility,
391                                                      kSampler2D_GrSLType, kDefault_GrSLPrecision,
392                                                      name.c_str());
393         SkNEW_APPEND_TO_TARRAY(outSamplers, GrGLProcessor::TextureSampler,
394                                (ip->fSamplers[t].fUniform, processor.textureAccess(t)));
395     }
396 }
397 
finalize()398 GrGLProgram* GrGLProgramBuilder::finalize() {
399     // verify we can get a program id
400     GrGLuint programID;
401     GL_CALL_RET(programID, CreateProgram());
402     if (0 == programID) {
403         return NULL;
404     }
405 
406     // compile shaders and bind attributes / uniforms
407     SkTDArray<GrGLuint> shadersToDelete;
408 
409     if (!fVS.compileAndAttachShaders(programID, &shadersToDelete)) {
410         this->cleanupProgram(programID, shadersToDelete);
411         return NULL;
412     }
413 
414     // NVPR actually requires a vertex shader to compile
415     bool useNvpr = primitiveProcessor().isPathRendering();
416     if (!useNvpr) {
417         fVS.bindVertexAttributes(programID);
418     }
419 
420     if (!fFS.compileAndAttachShaders(programID, &shadersToDelete)) {
421         this->cleanupProgram(programID, shadersToDelete);
422         return NULL;
423     }
424 
425     bool usingBindUniform = fGpu->glInterface()->fFunctions.fBindUniformLocation != NULL;
426     if (usingBindUniform) {
427         this->bindUniformLocations(programID);
428     }
429     fFS.bindFragmentShaderLocations(programID);
430     GL_CALL(LinkProgram(programID));
431 
432     // Calling GetProgramiv is expensive in Chromium. Assume success in release builds.
433     bool checkLinked = !fGpu->ctxInfo().isChromium();
434 #ifdef SK_DEBUG
435     checkLinked = true;
436 #endif
437     if (checkLinked) {
438         checkLinkStatus(programID);
439     }
440     if (!usingBindUniform) {
441         this->resolveUniformLocations(programID);
442     }
443 
444     this->cleanupShaders(shadersToDelete);
445 
446     return this->createProgram(programID);
447 }
448 
bindUniformLocations(GrGLuint programID)449 void GrGLProgramBuilder::bindUniformLocations(GrGLuint programID) {
450     int count = fUniforms.count();
451     for (int i = 0; i < count; ++i) {
452         GL_CALL(BindUniformLocation(programID, i, fUniforms[i].fVariable.c_str()));
453         fUniforms[i].fLocation = i;
454     }
455 }
456 
checkLinkStatus(GrGLuint programID)457 bool GrGLProgramBuilder::checkLinkStatus(GrGLuint programID) {
458     GrGLint linked = GR_GL_INIT_ZERO;
459     GL_CALL(GetProgramiv(programID, GR_GL_LINK_STATUS, &linked));
460     if (!linked) {
461         GrGLint infoLen = GR_GL_INIT_ZERO;
462         GL_CALL(GetProgramiv(programID, GR_GL_INFO_LOG_LENGTH, &infoLen));
463         SkAutoMalloc log(sizeof(char)*(infoLen+1));  // outside if for debugger
464         if (infoLen > 0) {
465             // retrieve length even though we don't need it to workaround
466             // bug in chrome cmd buffer param validation.
467             GrGLsizei length = GR_GL_INIT_ZERO;
468             GL_CALL(GetProgramInfoLog(programID,
469                                       infoLen+1,
470                                       &length,
471                                       (char*)log.get()));
472             SkDebugf("%s", (char*)log.get());
473         }
474         SkDEBUGFAIL("Error linking program");
475         GL_CALL(DeleteProgram(programID));
476         programID = 0;
477     }
478     return SkToBool(linked);
479 }
480 
resolveUniformLocations(GrGLuint programID)481 void GrGLProgramBuilder::resolveUniformLocations(GrGLuint programID) {
482     int count = fUniforms.count();
483     for (int i = 0; i < count; ++i) {
484         GrGLint location;
485         GL_CALL_RET(location, GetUniformLocation(programID, fUniforms[i].fVariable.c_str()));
486         fUniforms[i].fLocation = location;
487     }
488 }
489 
cleanupProgram(GrGLuint programID,const SkTDArray<GrGLuint> & shaderIDs)490 void GrGLProgramBuilder::cleanupProgram(GrGLuint programID, const SkTDArray<GrGLuint>& shaderIDs) {
491     GL_CALL(DeleteProgram(programID));
492     cleanupShaders(shaderIDs);
493 }
cleanupShaders(const SkTDArray<GrGLuint> & shaderIDs)494 void GrGLProgramBuilder::cleanupShaders(const SkTDArray<GrGLuint>& shaderIDs) {
495     for (int i = 0; i < shaderIDs.count(); ++i) {
496       GL_CALL(DeleteShader(shaderIDs[i]));
497     }
498 }
499 
createProgram(GrGLuint programID)500 GrGLProgram* GrGLProgramBuilder::createProgram(GrGLuint programID) {
501     return SkNEW_ARGS(GrGLProgram, (fGpu, this->desc(), fUniformHandles, programID, fUniforms,
502                                     fGeometryProcessor, fXferProcessor, fFragmentProcessors.get()));
503 }
504 
505 ///////////////////////////////////////////////////////////////////////////////////////////////////
506 
~GrGLInstalledFragProcs()507 GrGLInstalledFragProcs::~GrGLInstalledFragProcs() {
508     int numProcs = fProcs.count();
509     for (int e = 0; e < numProcs; ++e) {
510         SkDELETE(fProcs[e]);
511     }
512 }
513