Flutter Engine
The Flutter Engine
GrGLSLProgramBuilder.cpp
Go to the documentation of this file.
1/*
2 * Copyright 2015 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 */
8
11#include "include/gpu/GrTypes.h"
27#include "src/sksl/SkSLString.h"
28
29#include <functional>
30#include <memory>
31#include <tuple>
32#include <unordered_map>
33#include <utility>
34
35
36using namespace skia_private;
37
39
41 const GrProgramInfo& programInfo)
42 : fVS(this)
43 , fFS(this)
44 , fDesc(desc)
45 , fProgramInfo(programInfo)
46 , fNumFragmentSamplers(0) {}
47
49
51 uint32_t featureBit,
52 const char* extensionName) {
53 if (shaders & kVertex_GrShaderFlag) {
54 fVS.addFeature(featureBit, extensionName);
55 }
56 if (shaders & kFragment_GrShaderFlag) {
57 fFS.addFeature(featureBit, extensionName);
58 }
59}
60
62 // First we loop over all of the installed processors and collect coord transforms. These will
63 // be sent to the ProgramImpl in its emitCode function
64 SkString inputColor;
65 SkString inputCoverage;
66 if (!this->emitAndInstallPrimProc(&inputColor, &inputCoverage)) {
67 return false;
68 }
69 if (!this->emitAndInstallDstTexture()) {
70 return false;
71 }
72 if (!this->emitAndInstallFragProcs(&inputColor, &inputCoverage)) {
73 return false;
74 }
75 if (!this->emitAndInstallXferProc(inputColor, inputCoverage)) {
76 return false;
77 }
78 fGPImpl->emitTransformCode(&fVS, this->uniformHandler());
79
80 return this->checkSamplerCounts();
81}
82
83bool GrGLSLProgramBuilder::emitAndInstallPrimProc(SkString* outputColor, SkString* outputCoverage) {
84 const GrGeometryProcessor& geomProc = this->geometryProcessor();
85
86 // Program builders have a bit of state we need to clear with each effect
87 this->advanceStage();
88 this->nameExpression(outputColor, "outputColor");
89 this->nameExpression(outputCoverage, "outputCoverage");
90
94
95 fFS.codeAppendf("// Stage %d, %s\n", fStageIndex, geomProc.name());
96 fVS.codeAppendf("// Primitive Processor %s\n", geomProc.name());
97
99 fGPImpl = geomProc.makeProgramImpl(*this->shaderCaps());
100
102 for (int i = 0; i < geomProc.numTextureSamplers(); ++i) {
104 name.printf("TextureSampler_%d", i);
105 const auto& sampler = geomProc.textureSampler(i);
106 texSamplers[i] = this->emitSampler(sampler.backendFormat(),
107 sampler.samplerState(),
108 sampler.swizzle(),
109 name.c_str());
110 if (!texSamplers[i].isValid()) {
111 return false;
112 }
113 }
114
116 &fFS,
117 this->varyingHandler(),
118 this->uniformHandler(),
119 this->shaderCaps(),
120 geomProc,
121 outputColor->c_str(),
122 outputCoverage->c_str(),
123 texSamplers.get());
124 std::tie(fFPCoordsMap, fLocalCoordsVar) = fGPImpl->emitCode(args, this->pipeline());
125
126 // We have to check that effects and the code they emit are consistent, ie if an effect
127 // asks for dst color, then the emit code needs to follow suit
128 SkDEBUGCODE(verify(geomProc);)
129
130 return true;
131}
132
133bool GrGLSLProgramBuilder::emitAndInstallFragProcs(SkString* color, SkString* coverage) {
134 int fpCount = this->pipeline().numFragmentProcessors();
135 SkASSERT(fFPImpls.empty());
136 fFPImpls.reserve(fpCount);
137 for (int i = 0; i < fpCount; ++i) {
141 fFPImpls.push_back(fp.makeProgramImpl());
142 output = this->emitRootFragProc(fp, *fFPImpls.back(), *inOut, output);
143 if (output.isEmpty()) {
144 return false;
145 }
146 *inOut = std::move(output);
147 }
148 return true;
149}
150
153 int* samplerIndex) {
154 bool ok = true;
155 fp.visitWithImpls([&](const GrFragmentProcessor& fp, GrFragmentProcessor::ProgramImpl& impl) {
156 if (const GrTextureEffect* te = fp.asTextureEffect()) {
157 SkString name = SkStringPrintf("TextureSampler_%d", *samplerIndex);
158 *samplerIndex += 1;
159
160 GrSamplerState samplerState = te->samplerState();
161 const GrBackendFormat& format = te->view().proxy()->backendFormat();
162 skgpu::Swizzle swizzle = te->view().swizzle();
163 SamplerHandle handle = this->emitSampler(format, samplerState, swizzle, name.c_str());
164 if (!handle.isValid()) {
165 ok = false;
166 return;
167 }
168 static_cast<GrTextureEffect::Impl&>(impl).setSamplerHandle(handle);
169 }
170 }, impl);
171
172 return ok;
173}
174
177 const char* inputColor,
178 const char* destColor,
179 const char* coords) const {
180 if (fp.isBlendFunction()) {
181 if (this->fragmentProcessorHasCoordsParam(&fp)) {
182 return SkSL::String::printf("%s(%s, %s, %s)", impl.functionName(), inputColor,
183 destColor, coords);
184 } else {
185 return SkSL::String::printf("%s(%s, %s)", impl.functionName(), inputColor, destColor);
186 }
187 }
188
189 if (this->fragmentProcessorHasCoordsParam(&fp)) {
190 return SkSL::String::printf("%s(%s, %s)", impl.functionName(), inputColor, coords);
191 } else {
192 return SkSL::String::printf("%s(%s)", impl.functionName(), inputColor);
193 }
194}
195
196SkString GrGLSLProgramBuilder::emitRootFragProc(const GrFragmentProcessor& fp,
198 const SkString& input,
200 SkASSERT(input.size());
201
202 // Program builders have a bit of state we need to clear with each effect
203 this->advanceStage();
204 this->nameExpression(&output, "output");
205 fFS.codeAppendf("half4 %s;", output.c_str());
206 int samplerIndex = 0;
207 if (!this->emitTextureSamplersForFPs(fp, impl, &samplerIndex)) {
208 return {};
209 }
210
211 this->writeFPFunction(fp, impl);
212
214 "%s = %s;",
215 output.c_str(),
216 this->invokeFP(fp, impl, input.c_str(), "half4(1)", fLocalCoordsVar.c_str()).c_str());
217
218 // We have to check that effects and the code they emit are consistent, ie if an effect asks
219 // for dst color, then the emit code needs to follow suit
220 SkDEBUGCODE(verify(fp);)
221
222 return output;
223}
224
225void GrGLSLProgramBuilder::writeChildFPFunctions(const GrFragmentProcessor& fp,
227 fSubstageIndices.push_back(0);
228 for (int i = 0; i < impl.numChildProcessors(); ++i) {
230 if (!childImpl) {
231 continue;
232 }
233
234 const GrFragmentProcessor* childFP = fp.childProcessor(i);
235 SkASSERT(childFP);
236
237 this->writeFPFunction(*childFP, *childImpl);
238 ++fSubstageIndices.back();
239 }
240 fSubstageIndices.pop_back();
241}
242
245 constexpr const char* kDstColor = "_dst";
246 const char* const inputColor = fp.isBlendFunction() ? "_src" : "_input";
247 const char* sampleCoords = "_coords";
248 fFS.nextStage();
249 // Conceptually, an FP is always sampled at a particular coordinate. However, if it is only
250 // sampled by a chain of uniform matrix expressions (or legacy coord transforms), the value that
251 // would have been passed to _coords is lifted to the vertex shader and
252 // varying. In that case it uses that variable and we do not pass a second argument for _coords.
254 int numParams = 0;
255
256 params[numParams++] = GrShaderVar(inputColor, SkSLType::kHalf4);
257
258 if (fp.isBlendFunction()) {
259 // Blend functions take a dest color as input.
260 params[numParams++] = GrShaderVar(kDstColor, SkSLType::kHalf4);
261 }
262
263 auto fpCoordsIter = fFPCoordsMap.find(&fp);
264 if (fpCoordsIter == fFPCoordsMap.end()) {
265 // This FP isn't in our coords map at all, so its coords (if any) couldn't have been lifted
266 // to a varying.
267 if (fp.usesSampleCoords()) {
268 params[numParams++] = GrShaderVar(sampleCoords, SkSLType::kFloat2);
269 }
270 } else if (fpCoordsIter->second.hasCoordsParam) {
271 // This FP is in our map, and it takes an explicit coords param.
272 params[numParams++] = GrShaderVar(sampleCoords, SkSLType::kFloat2);
273 } else {
274 // Either doesn't use coords at all or sampled through a chain of passthrough/matrix
275 // samples usages. In the latter case the coords are emitted in the vertex shader as a
276 // varying, so this only has to access it. Add a float2 _coords variable that maps to the
277 // associated varying and replaces the absent 2nd argument to the fp's function.
278 GrShaderVar varying = fpCoordsIter->second.coordsVarying;
279
280 switch (varying.getType()) {
281 case SkSLType::kVoid:
282 SkASSERT(!fp.usesSampleCoordsDirectly());
283 break;
285 // Just point the local coords to the varying
286 sampleCoords = varying.getName().c_str();
287 break;
289 // Must perform the perspective divide in the frag shader based on the
290 // varying, and since we won't actually have a function parameter for local
291 // coords, add it as a local variable.
292 fFS.codeAppendf("float2 %s = %s.xy / %s.z;\n",
293 sampleCoords,
294 varying.getName().c_str(),
295 varying.getName().c_str());
296 break;
297 default:
298 SkDEBUGFAILF("Unexpected varying type for coord: %s %d\n",
299 varying.getName().c_str(),
300 (int)varying.getType());
301 break;
302 }
303 }
304
305 SkASSERT(numParams <= (int)std::size(params));
306
307 // First, emit every child's function. This needs to happen (even for children that aren't
308 // sampled), so that all of the expected uniforms are registered.
309 this->writeChildFPFunctions(fp, impl);
311 this->uniformHandler(),
312 this->shaderCaps(),
313 fp,
314 inputColor,
315 kDstColor,
316 sampleCoords);
317
318 impl.emitCode(args);
320
322 impl.functionName(),
323 SkSpan(params, numParams),
324 fFS.code().c_str());
326}
327
328bool GrGLSLProgramBuilder::emitAndInstallDstTexture() {
330
331 const GrSurfaceProxyView& dstView = this->pipeline().dstProxyView();
332 if (this->pipeline().usesDstTexture()) {
333 // Set up a sampler handle for the destination texture.
334 GrTextureProxy* dstTextureProxy = dstView.asTextureProxy();
335 SkASSERT(dstTextureProxy);
336 const skgpu::Swizzle& swizzle = dstView.swizzle();
337 fDstTextureSamplerHandle = this->emitSampler(dstTextureProxy->backendFormat(),
338 GrSamplerState(), swizzle, "DstTextureSampler");
339 if (!fDstTextureSamplerHandle.isValid()) {
340 return false;
341 }
342 fDstTextureOrigin = dstView.origin();
343 SkASSERT(dstTextureProxy->textureType() != GrTextureType::kExternal);
344
345 // Declare a _dstColor global variable which samples from the dest-texture sampler at the
346 // top of the fragment shader.
347 const char* dstTextureCoordsName;
349 /*owner=*/nullptr,
352 "DstTextureCoords",
353 &dstTextureCoordsName);
354 fFS.codeAppend("// Read color from copy of the destination\n");
355 if (dstTextureProxy->textureType() == GrTextureType::k2D) {
356 fFS.codeAppendf("float2 _dstTexCoord = (sk_FragCoord.xy - %s.xy) * %s.zw;\n",
357 dstTextureCoordsName, dstTextureCoordsName);
359 fFS.codeAppend("_dstTexCoord.y = 1.0 - _dstTexCoord.y;\n");
360 }
361 } else {
362 SkASSERT(dstTextureProxy->textureType() == GrTextureType::kRectangle);
363 fFS.codeAppendf("float2 _dstTexCoord = sk_FragCoord.xy - %s.xy;\n",
364 dstTextureCoordsName);
366 // When the texture type is kRectangle, instead of a scale stored in the zw of the
367 // uniform, we store the height in z so we can flip the coord here.
368 fFS.codeAppendf("_dstTexCoord.y = %s.z - _dstTexCoord.y;\n", dstTextureCoordsName);
369 }
370 }
371 const char* dstColor = fFS.dstColor();
372 SkString dstColorDecl = SkStringPrintf("half4 %s;", dstColor);
373 fFS.definitionAppend(dstColorDecl.c_str());
374 fFS.codeAppendf("%s = ", dstColor);
376 fFS.codeAppend(";\n");
377 } else if (this->pipeline().usesDstInputAttachment()) {
378 // Set up an input attachment for the destination texture.
379 const skgpu::Swizzle& swizzle = dstView.swizzle();
380 fDstTextureSamplerHandle = this->emitInputSampler(swizzle, "DstTextureInput");
381 if (!fDstTextureSamplerHandle.isValid()) {
382 return false;
383 }
384
385 // Populate the _dstColor variable by loading from the input attachment at the top of the
386 // fragment shader.
387 fFS.codeAppend("// Read color from input attachment\n");
388 const char* dstColor = fFS.dstColor();
389 SkString dstColorDecl = SkStringPrintf("half4 %s;", dstColor);
390 fFS.definitionAppend(dstColorDecl.c_str());
391 fFS.codeAppendf("%s = ", dstColor);
393 fFS.codeAppend(";\n");
394 }
395
396 return true;
397}
398
399bool GrGLSLProgramBuilder::emitAndInstallXferProc(const SkString& colorIn,
400 const SkString& coverageIn) {
401 // Program builders have a bit of state we need to clear with each effect
402 this->advanceStage();
403
405 const GrXferProcessor& xp = this->pipeline().getXferProcessor();
407
408 // Enable dual source secondary output if we have one
409 if (xp.hasSecondaryOutput()) {
410 fFS.enableSecondaryOutput();
411 }
412
413 SkString openBrace;
414 openBrace.printf("{ // Xfer Processor: %s\n", xp.name());
415 fFS.codeAppend(openBrace.c_str());
416
417 SkString finalInColor = colorIn.size() ? colorIn : SkString("float4(1)");
418
420 &fFS,
421 this->uniformHandler(),
422 this->shaderCaps(),
423 xp,
424 finalInColor.c_str(),
425 coverageIn.size() ? coverageIn.c_str() : "float4(1)",
426 fFS.getPrimaryColorOutputName(),
427 fFS.getSecondaryColorOutputName(),
430 this->pipeline().writeSwizzle());
431 fXPImpl->emitCode(args);
432
433 // We have to check that effects and the code they emit are consistent, ie if an effect
434 // asks for dst color, then the emit code needs to follow suit
435 SkDEBUGCODE(verify(xp);)
436 fFS.codeAppend("}");
437 return true;
438}
439
440GrGLSLProgramBuilder::SamplerHandle GrGLSLProgramBuilder::emitSampler(
441 const GrBackendFormat& backendFormat, GrSamplerState state, const skgpu::Swizzle& swizzle,
442 const char* name) {
443 ++fNumFragmentSamplers;
444 return this->uniformHandler()->addSampler(backendFormat, state, swizzle, name,
445 this->shaderCaps());
446}
447
448GrGLSLProgramBuilder::SamplerHandle GrGLSLProgramBuilder::emitInputSampler(
449 const skgpu::Swizzle& swizzle, const char* name) {
450 return this->uniformHandler()->addInputSampler(swizzle, name);
451}
452
453bool GrGLSLProgramBuilder::checkSamplerCounts() {
454 const GrShaderCaps& shaderCaps = *this->shaderCaps();
455 if (fNumFragmentSamplers > shaderCaps.fMaxFragmentSamplers) {
456 GrCapsDebugf(this->caps(), "Program would use too many fragment samplers\n");
457 return false;
458 }
459 return true;
460}
461
462#ifdef SK_DEBUG
463void GrGLSLProgramBuilder::verify(const GrGeometryProcessor& geomProc) {
464 SkASSERT(!fFS.fHasReadDstColorThisStage_DebugOnly);
465}
466
467void GrGLSLProgramBuilder::verify(const GrFragmentProcessor& fp) {
468 SkASSERT(fp.willReadDstColor() == fFS.fHasReadDstColorThisStage_DebugOnly);
469}
470
471void GrGLSLProgramBuilder::verify(const GrXferProcessor& xp) {
472 SkASSERT(xp.willReadDstColor() == fFS.fHasReadDstColorThisStage_DebugOnly);
473}
474#endif
475
476SkString GrGLSLProgramBuilder::getMangleSuffix() const {
477 SkASSERT(fStageIndex >= 0);
479 suffix.printf("_S%d", fStageIndex);
480 for (auto c : fSubstageIndices) {
481 suffix.appendf("_c%d", c);
482 }
483 return suffix;
484}
485
488 if ('\0' == prefix) {
489 out = name;
490 } else {
491 out.printf("%c%s", prefix, name);
492 }
493 if (mangle) {
494 SkString suffix = this->getMangleSuffix();
495 // Names containing "__" are reserved; add "x" if needed to avoid consecutive underscores.
496 const char *underscoreSplitter = out.endsWith('_') ? "x" : "";
497 out.appendf("%s%s", underscoreSplitter, suffix.c_str());
498 }
499 return out;
500}
501
502void GrGLSLProgramBuilder::nameExpression(SkString* output, const char* baseName) {
503 // Name a variable to hold stage result. If we already have a valid output name, use that as-is;
504 // otherwise, create a new mangled one.
505 if (output->isEmpty()) {
506 *output = this->nameVariable(/*prefix=*/'\0', baseName);
507 }
508}
509
511 this->uniformHandler()->appendUniformDecls(visibility, out);
512}
513
518 uniformHandler->internalAddUniformArray(nullptr,
521 name,
522 false,
523 0,
524 nullptr);
525}
526
528 auto iter = fFPCoordsMap.find(fp);
529 return (iter != fFPCoordsMap.end()) ? iter->second.hasCoordsParam
530 : fp->usesSampleCoords();
531}
532
534 this->varyingHandler()->finalize();
537}
#define GrCapsDebugf(caps,...)
Definition: GrTypesPriv.h:490
GrShaderFlags
Definition: GrTypesPriv.h:284
@ kVertex_GrShaderFlag
Definition: GrTypesPriv.h:286
@ kFragment_GrShaderFlag
Definition: GrTypesPriv.h:287
@ kBottomLeft_GrSurfaceOrigin
Definition: GrTypes.h:149
@ kTopLeft_GrSurfaceOrigin
Definition: GrTypes.h:148
#define SkDEBUGFAILF(fmt,...)
Definition: SkAssert.h:119
#define SkASSERT(cond)
Definition: SkAssert.h:116
static bool ok(int result)
SK_API SkString SkStringPrintf(const char *format,...) SK_PRINTF_LIKE(1
Creates a new string and writes into it using a printf()-style format.
SkDEBUGCODE(SK_SPI) SkThreadID SkGetThreadID()
virtual void emitCode(EmitArgs &)=0
ProgramImpl * childProcessor(int index) const
std::unique_ptr< ProgramImpl > makeProgramImpl() const
SkString nameVariable(char prefix, const char *name, bool mangle=true)
virtual GrGLSLUniformHandler * uniformHandler()=0
GrSurfaceOrigin fDstTextureOrigin
bool fragmentProcessorHasCoordsParam(const GrFragmentProcessor *) const
std::vector< std::unique_ptr< GrFragmentProcessor::ProgramImpl > > fFPImpls
virtual const GrCaps * caps() const =0
std::unique_ptr< GrGeometryProcessor::ProgramImpl > fGPImpl
const GrShaderCaps * shaderCaps() const
SamplerHandle fDstTextureSamplerHandle
void addFeature(GrShaderFlags shaders, uint32_t featureBit, const char *extensionName)
GrGLSLVertexBuilder fVS
const GrGeometryProcessor & geometryProcessor() const
GrGLSLUniformHandler::SamplerHandle SamplerHandle
bool emitTextureSamplersForFPs(const GrFragmentProcessor &fp, GrFragmentProcessor::ProgramImpl &impl, int *samplerIndex)
void appendUniformDecls(GrShaderFlags visibility, SkString *) const
virtual GrGLSLVaryingHandler * varyingHandler()=0
std::string invokeFP(const GrFragmentProcessor &fp, const GrFragmentProcessor::ProgramImpl &impl, const char *inputColor, const char *destColor, const char *coords) const
std::unique_ptr< GrXferProcessor::ProgramImpl > fXPImpl
virtual ~GrGLSLProgramBuilder()
static const int kVarsPerBlock
GrGLSLBuiltinUniformHandles fUniformHandles
GrGLSLFragmentShaderBuilder fFS
void addRTFlipUniform(const char *name)
GrGLSLProgramBuilder(const GrProgramDesc &, const GrProgramInfo &)
void writeFPFunction(const GrFragmentProcessor &fp, GrFragmentProcessor::ProgramImpl &impl)
const GrPipeline & pipeline() const
void emitFunction(SkSLType returnType, const char *mangledName, SkSpan< const GrShaderVar > args, const char *body)
this code().appendVAList(format
void definitionAppend(const char *str)
bool addFeature(uint32_t featureBit, const char *extensionName)
void finalize(uint32_t visibility)
void codeAppend(const char *str)
void appendTextureLookup(SkString *out, SamplerHandle, const char *coordName) const
SkString getMangledFunctionName(const char *baseName)
void codeAppendf(const char format[],...) SK_PRINTF_LIKE(2
void appendInputLoad(SamplerHandle)
virtual void appendUniformDecls(GrShaderFlags visibility, SkString *) const =0
virtual UniformHandle internalAddUniformArray(const GrProcessor *owner, uint32_t visibility, SkSLType type, const char *name, bool mangleName, int arrayCount, const char **outName)=0
UniformHandle addUniform(const GrProcessor *owner, uint32_t visibility, SkSLType type, const char *name, const char **outName=nullptr)
virtual SamplerHandle addSampler(const GrBackendFormat &, GrSamplerState, const skgpu::Swizzle &, const char *name, const GrShaderCaps *)=0
virtual SamplerHandle addInputSampler(const skgpu::Swizzle &swizzle, const char *name)
virtual std::unique_ptr< ProgramImpl > makeProgramImpl(const GrShaderCaps &) const =0
const TextureSampler & textureSampler(int index) const
int numFragmentProcessors() const
Definition: GrPipeline.h:99
bool isColorFragmentProcessor(int idx) const
Definition: GrPipeline.h:101
const GrFragmentProcessor & getFragmentProcessor(int idx) const
Definition: GrPipeline.h:157
const GrSurfaceProxyView & dstProxyView() const
Definition: GrPipeline.h:138
const GrXferProcessor & getXferProcessor() const
Definition: GrPipeline.h:116
virtual const char * name() const =0
SkSLType getType() const
Definition: GrShaderVar.h:97
const char * c_str() const
Definition: GrShaderVar.h:94
const SkString & getName() const
Definition: GrShaderVar.h:91
skgpu::Swizzle swizzle() const
GrTextureProxy * asTextureProxy() const
GrSurfaceOrigin origin() const
const GrBackendFormat & backendFormat() const
void setSamplerHandle(GrGLSLShaderBuilder::SamplerHandle handle)
GrTextureType textureType() const
bool hasSecondaryOutput() const
virtual std::unique_ptr< ProgramImpl > makeProgramImpl() const =0
bool willReadDstColor() const
static constexpr const char RTADJUST_NAME[]
Definition: SkSLCompiler.h:72
void printf(const char format[],...) SK_PRINTF_LIKE(2
Definition: SkString.cpp:534
size_t size() const
Definition: SkString.h:131
const char * c_str() const
Definition: SkString.h:133
DlColor color
const EmbeddedViewParams * params
AtkStateType state
G_BEGIN_DECLS G_MODULE_EXPORT FlValue * args
std::string printf(const char *fmt,...) SK_PRINTF_LIKE(1
Definition: SkSLString.cpp:83
const uint32_t fp
DEF_SWITCHES_START aot vmservice shared library name
Definition: switches.h:32
it will be possible to load the file into Perfetto s trace viewer disable asset Prevents usage of any non test fonts unless they were explicitly Loaded via prefetched default font Indicates whether the embedding started a prefetch of the default font manager before creating the engine run In non interactive keep the shell running after the Dart script has completed enable serial On low power devices with low core running concurrent GC tasks on threads can cause them to contend with the UI thread which could potentially lead to jank This option turns off all concurrent GC activities domain network JSON encoded network policy per domain This overrides the DisallowInsecureConnections switch Embedder can specify whether to allow or disallow insecure connections at a domain level old gen heap size
Definition: switches.h:259
GrGLSLProgramDataManager::UniformHandle fRTFlipUni
GrGLSLProgramDataManager::UniformHandle fDstTextureCoordsUni
GrGLSLProgramDataManager::UniformHandle fRTAdjustmentUni
int fMaxFragmentSamplers
Definition: GrShaderCaps.h:71