• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 "src/gpu/GrGeometryProcessor.h"
9 
10 #include "src/core/SkMatrixPriv.h"
11 #include "src/gpu/GrPipeline.h"
12 #include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h"
13 #include "src/gpu/glsl/GrGLSLProgramBuilder.h"
14 #include "src/gpu/glsl/GrGLSLUniformHandler.h"
15 #include "src/gpu/glsl/GrGLSLVarying.h"
16 
17 #include <queue>
18 
GrGeometryProcessor(ClassID classID)19 GrGeometryProcessor::GrGeometryProcessor(ClassID classID) : GrProcessor(classID) {}
20 
textureSampler(int i) const21 const GrGeometryProcessor::TextureSampler& GrGeometryProcessor::textureSampler(int i) const {
22     SkASSERT(i >= 0 && i < this->numTextureSamplers());
23     return this->onTextureSampler(i);
24 }
25 
ComputeCoordTransformsKey(const GrFragmentProcessor & fp)26 uint32_t GrGeometryProcessor::ComputeCoordTransformsKey(const GrFragmentProcessor& fp) {
27     // This is highly coupled with the code in ProgramImpl::collectTransforms().
28     uint32_t key = static_cast<uint32_t>(fp.sampleUsage().kind()) << 1;
29     // This needs to be updated if GP starts specializing varyings on additional matrix types.
30     if (fp.sampleUsage().hasPerspective()) {
31         key |= 0b1;
32     }
33     return key;
34 }
35 
36 ///////////////////////////////////////////////////////////////////////////////////////////////////
37 
clamp_filter(GrTextureType type,GrSamplerState::Filter requestedFilter)38 static inline GrSamplerState::Filter clamp_filter(GrTextureType type,
39                                                   GrSamplerState::Filter requestedFilter) {
40     if (GrTextureTypeHasRestrictedSampling(type)) {
41         return std::min(requestedFilter, GrSamplerState::Filter::kLinear);
42     }
43     return requestedFilter;
44 }
45 
TextureSampler(GrSamplerState samplerState,const GrBackendFormat & backendFormat,const GrSwizzle & swizzle)46 GrGeometryProcessor::TextureSampler::TextureSampler(GrSamplerState samplerState,
47                                                     const GrBackendFormat& backendFormat,
48                                                     const GrSwizzle& swizzle) {
49     this->reset(samplerState, backendFormat, swizzle);
50 }
51 
reset(GrSamplerState samplerState,const GrBackendFormat & backendFormat,const GrSwizzle & swizzle)52 void GrGeometryProcessor::TextureSampler::reset(GrSamplerState samplerState,
53                                                 const GrBackendFormat& backendFormat,
54                                                 const GrSwizzle& swizzle) {
55     fSamplerState = samplerState;
56     fSamplerState.setFilterMode(clamp_filter(backendFormat.textureType(), samplerState.filter()));
57     fBackendFormat = backendFormat;
58     fSwizzle = swizzle;
59     fIsInitialized = true;
60 }
61 
62 //////////////////////////////////////////////////////////////////////////////
63 
64 using ProgramImpl = GrGeometryProcessor::ProgramImpl;
65 
emitCode(EmitArgs & args,const GrPipeline & pipeline)66 ProgramImpl::FPCoordsMap ProgramImpl::emitCode(EmitArgs& args, const GrPipeline& pipeline) {
67     GrGPArgs gpArgs;
68     this->onEmitCode(args, &gpArgs);
69 
70     GrShaderVar positionVar = gpArgs.fPositionVar;
71     // skia:12198
72     if (args.fGeomProc.willUseTessellationShaders()) {
73         positionVar = {};
74     }
75     FPCoordsMap transformMap = this->collectTransforms(args.fVertBuilder,
76                                                        args.fVaryingHandler,
77                                                        args.fUniformHandler,
78                                                        gpArgs.fLocalCoordVar,
79                                                        positionVar,
80                                                        pipeline);
81 
82     if (args.fGeomProc.willUseTessellationShaders()) {
83         // Tessellation shaders are temporarily responsible for integrating their own code strings
84         // while we work out full support.
85         return transformMap;
86     }
87 
88     GrGLSLVertexBuilder* vBuilder = args.fVertBuilder;
89     // Emit the vertex position to the hardware in the normalized window coordinates it expects.
90     SkASSERT(kFloat2_GrSLType == gpArgs.fPositionVar.getType() ||
91                 kFloat3_GrSLType == gpArgs.fPositionVar.getType());
92     vBuilder->emitNormalizedSkPosition(gpArgs.fPositionVar.c_str(),
93                                         gpArgs.fPositionVar.getType());
94     if (kFloat2_GrSLType == gpArgs.fPositionVar.getType()) {
95         args.fVaryingHandler->setNoPerspective();
96     }
97     return transformMap;
98 }
99 
collectTransforms(GrGLSLVertexBuilder * vb,GrGLSLVaryingHandler * varyingHandler,GrGLSLUniformHandler * uniformHandler,const GrShaderVar & localCoordsVar,const GrShaderVar & positionVar,const GrPipeline & pipeline)100 ProgramImpl::FPCoordsMap ProgramImpl::collectTransforms(GrGLSLVertexBuilder* vb,
101                                                         GrGLSLVaryingHandler* varyingHandler,
102                                                         GrGLSLUniformHandler* uniformHandler,
103                                                         const GrShaderVar& localCoordsVar,
104                                                         const GrShaderVar& positionVar,
105                                                         const GrPipeline& pipeline) {
106     SkASSERT(localCoordsVar.getType() == kFloat2_GrSLType ||
107              localCoordsVar.getType() == kFloat3_GrSLType ||
108              localCoordsVar.getType() == kVoid_GrSLType);
109     SkASSERT(positionVar.getType() == kFloat2_GrSLType ||
110              positionVar.getType() == kFloat3_GrSLType ||
111              positionVar.getType() == kVoid_GrSLType);
112 
113     enum class BaseCoord { kNone, kLocal, kPosition };
114 
115     auto baseLocalCoordFSVar = [&, baseLocalCoord = GrGLSLVarying()]() mutable {
116         SkASSERT(GrSLTypeIsFloatType(localCoordsVar.getType()));
117         if (baseLocalCoord.type() == kVoid_GrSLType) {
118             // Initialize to the GP provided coordinate
119             baseLocalCoord = GrGLSLVarying(localCoordsVar.getType());
120             varyingHandler->addVarying("LocalCoord", &baseLocalCoord);
121             vb->codeAppendf("%s = %s;\n", baseLocalCoord.vsOut(), localCoordsVar.getName().c_str());
122         }
123         return baseLocalCoord.fsInVar();
124     };
125 
126     bool canUsePosition = positionVar.getType() != kVoid_GrSLType;
127 
128     FPCoordsMap result;
129     // Performs a pre-order traversal of FP hierarchy rooted at fp and identifies FPs that are
130     // sampled with a series of matrices applied to local coords. For each such FP a varying is
131     // added to the varying handler and added to 'result'.
132     auto liftTransforms = [&, traversalIndex = 0](
133                                   auto& self,
134                                   const GrFragmentProcessor& fp,
135                                   bool hasPerspective,
136                                   const GrFragmentProcessor* lastMatrixFP = nullptr,
137                                   int lastMatrixTraversalIndex = -1,
138                                   BaseCoord baseCoord = BaseCoord::kLocal) mutable -> void {
139         ++traversalIndex;
140         switch (fp.sampleUsage().kind()) {
141             case SkSL::SampleUsage::Kind::kNone:
142                 // This should only happen at the root. Otherwise how did this FP get added?
143                 SkASSERT(!fp.parent());
144                 break;
145             case SkSL::SampleUsage::Kind::kPassThrough:
146                 break;
147             case SkSL::SampleUsage::Kind::kUniformMatrix:
148                 // Update tracking of last matrix and matrix props.
149                 hasPerspective |= fp.sampleUsage().hasPerspective();
150                 lastMatrixFP = &fp;
151                 lastMatrixTraversalIndex = traversalIndex;
152                 break;
153             case SkSL::SampleUsage::Kind::kFragCoord:
154                 hasPerspective = positionVar.getType() == kFloat3_GrSLType;
155                 lastMatrixFP = nullptr;
156                 lastMatrixTraversalIndex = -1;
157                 baseCoord = BaseCoord::kPosition;
158                 break;
159             case SkSL::SampleUsage::Kind::kExplicit:
160                 baseCoord = BaseCoord::kNone;
161                 break;
162         }
163 
164         auto& [varyingFSVar, hasCoordsParam] = result[&fp];
165         hasCoordsParam = fp.usesSampleCoordsDirectly();
166 
167         // We add a varying if we're in a chain of matrices multiplied by local or device coords.
168         // If the coord is the untransformed local coord we add a varying. We don't if it is
169         // untransformed device coords since it doesn't save us anything over "sk_FragCoord.xy". Of
170         // course, if the FP doesn't directly use its coords then we don't add a varying.
171         if (fp.usesSampleCoordsDirectly() &&
172             (baseCoord == BaseCoord::kLocal ||
173              (baseCoord == BaseCoord::kPosition && lastMatrixFP && canUsePosition))) {
174             // Associate the varying with the highest possible node in the FP tree that shares the
175             // same coordinates so that multiple FPs in a subtree can share. If there are no matrix
176             // sample nodes on the way up the tree then directly use the local coord.
177             if (!lastMatrixFP) {
178                 varyingFSVar = baseLocalCoordFSVar();
179             } else {
180                 // If there is an already a varying that incorporates all matrices from the root to
181                 // lastMatrixFP just use it. Otherwise, we add it.
182                 auto& [varying, inputCoords, varyingIdx] = fTransformVaryingsMap[lastMatrixFP];
183                 if (varying.type() == kVoid_GrSLType) {
184                     varying = GrGLSLVarying(hasPerspective ? kFloat3_GrSLType : kFloat2_GrSLType);
185                     SkString strVaryingName = SkStringPrintf("TransformedCoords_%d",
186                                                              lastMatrixTraversalIndex);
187                     varyingHandler->addVarying(strVaryingName.c_str(), &varying);
188                     inputCoords = baseCoord == BaseCoord::kLocal ? localCoordsVar : positionVar;
189                     varyingIdx = lastMatrixTraversalIndex;
190                 }
191                 SkASSERT(varyingIdx == lastMatrixTraversalIndex);
192                 // The FP will use the varying in the fragment shader as its coords.
193                 varyingFSVar = varying.fsInVar();
194             }
195             hasCoordsParam = false;
196         }
197 
198         for (int c = 0; c < fp.numChildProcessors(); ++c) {
199             if (auto* child = fp.childProcessor(c)) {
200                 self(self,
201                      *child,
202                      hasPerspective,
203                      lastMatrixFP,
204                      lastMatrixTraversalIndex,
205                      baseCoord);
206                 // If we have a varying then we never need a param. Otherwise, if one of our
207                 // children takes a non-explicit coord then we'll need our coord.
208                 hasCoordsParam |= varyingFSVar.getType() == kVoid_GrSLType &&
209                                   !child->sampleUsage().isExplicit()       &&
210                                   !child->sampleUsage().isFragCoord()      &&
211                                   result[child].hasCoordsParam;
212             }
213         }
214     };
215 
216     bool hasPerspective = GrSLTypeVecLength(localCoordsVar.getType()) == 3;
217     for (int i = 0; i < pipeline.numFragmentProcessors(); ++i) {
218         liftTransforms(liftTransforms, pipeline.getFragmentProcessor(i), hasPerspective);
219     }
220     return result;
221 }
222 
emitTransformCode(GrGLSLVertexBuilder * vb,GrGLSLUniformHandler * uniformHandler)223 void ProgramImpl::emitTransformCode(GrGLSLVertexBuilder* vb, GrGLSLUniformHandler* uniformHandler) {
224     // Because descendant varyings may be computed using the varyings of ancestor FPs we make
225     // sure to visit the varyings according to FP pre-order traversal by dumping them into a
226     // priority queue.
227     using FPAndInfo = std::tuple<const GrFragmentProcessor*, TransformInfo>;
228     auto compare = [](const FPAndInfo& a, const FPAndInfo& b) {
229         return std::get<1>(a).traversalOrder > std::get<1>(b).traversalOrder;
230     };
231     std::priority_queue<FPAndInfo, std::vector<FPAndInfo>, decltype(compare)> pq(compare);
232     std::for_each(fTransformVaryingsMap.begin(), fTransformVaryingsMap.end(), [&pq](auto entry) {
233         pq.push(entry);
234     });
235     for (; !pq.empty(); pq.pop()) {
236         const auto& [fp, info] = pq.top();
237         // If we recorded a transform info, its sample matrix must be uniform
238         SkASSERT(fp->sampleUsage().isUniformMatrix());
239         GrShaderVar uniform = uniformHandler->liftUniformToVertexShader(
240                 *fp->parent(), SkString(SkSL::SampleUsage::MatrixUniformName()));
241         // Start with this matrix and accumulate additional matrices as we walk up the FP tree
242         // to either the base coords or an ancestor FP that has an associated varying.
243         SkString transformExpression = uniform.getName();
244 
245         // If we hit an ancestor with a varying on our walk up then save off the varying as the
246         // input to our accumulated transformExpression. Start off assuming we'll reach the root.
247         GrShaderVar inputCoords = info.inputCoords;
248 
249         for (const auto* base = fp->parent(); base; base = base->parent()) {
250             if (auto iter = fTransformVaryingsMap.find(base); iter != fTransformVaryingsMap.end()) {
251                 // Can stop here, as this varying already holds all transforms from higher FPs
252                 // We'll apply the residual transformExpression we've accumulated up from our
253                 // starting FP to this varying.
254                 inputCoords = iter->second.varying.vsOutVar();
255                 break;
256             } else if (base->sampleUsage().isUniformMatrix()) {
257                 // Accumulate any matrices along the path to either the original local/device coords
258                 // or a parent varying. Getting here means this FP was sampled with a uniform matrix
259                 // but all uses of coords below here in the FP hierarchy are beneath additional
260                 // matrix samples and thus this node wasn't assigned a varying.
261                 GrShaderVar parentUniform = uniformHandler->liftUniformToVertexShader(
262                         *base->parent(), SkString(SkSL::SampleUsage::MatrixUniformName()));
263                 transformExpression.appendf(" * %s", parentUniform.getName().c_str());
264             } else if (base->sampleUsage().isFragCoord()) {
265                 // Our chain of matrices starts here and is based on the device space position.
266                 break;
267             } else {
268                 // This intermediate FP is just a pass through and doesn't need to be built
269                 // in to the expression, but we must visit its parents in case they add transforms.
270                 SkASSERT(base->sampleUsage().isPassThrough() || !base->sampleUsage().isSampled());
271             }
272         }
273 
274         SkString inputStr;
275         if (inputCoords.getType() == kFloat2_GrSLType) {
276             inputStr = SkStringPrintf("%s.xy1", inputCoords.getName().c_str());
277         } else {
278             SkASSERT(inputCoords.getType() == kFloat3_GrSLType);
279             inputStr = inputCoords.getName();
280         }
281 
282         vb->codeAppend("{\n");
283         if (info.varying.type() == kFloat2_GrSLType) {
284             if (vb->getProgramBuilder()->shaderCaps()->nonsquareMatrixSupport()) {
285                 vb->codeAppendf("%s = float3x2(%s) * %s",
286                                 info.varying.vsOut(),
287                                 transformExpression.c_str(),
288                                 inputStr.c_str());
289             } else {
290                 vb->codeAppendf("%s = (%s * %s).xy",
291                                 info.varying.vsOut(),
292                                 transformExpression.c_str(),
293                                 inputStr.c_str());
294             }
295         } else {
296             SkASSERT(info.varying.type() == kFloat3_GrSLType);
297             vb->codeAppendf("%s = %s * %s",
298                             info.varying.vsOut(),
299                             transformExpression.c_str(),
300                             inputStr.c_str());
301         }
302         vb->codeAppend(";\n");
303         vb->codeAppend("}\n");
304     }
305     // We don't need this map anymore.
306     fTransformVaryingsMap.clear();
307 }
308 
setupUniformColor(GrGLSLFPFragmentBuilder * fragBuilder,GrGLSLUniformHandler * uniformHandler,const char * outputName,UniformHandle * colorUniform)309 void ProgramImpl::setupUniformColor(GrGLSLFPFragmentBuilder* fragBuilder,
310                                     GrGLSLUniformHandler* uniformHandler,
311                                     const char* outputName,
312                                     UniformHandle* colorUniform) {
313     SkASSERT(colorUniform);
314     const char* stagedLocalVarName;
315     *colorUniform = uniformHandler->addUniform(nullptr,
316                                                kFragment_GrShaderFlag,
317                                                kHalf4_GrSLType,
318                                                "Color",
319                                                &stagedLocalVarName);
320     fragBuilder->codeAppendf("%s = %s;", outputName, stagedLocalVarName);
321     if (fragBuilder->getProgramBuilder()->shaderCaps()->mustObfuscateUniformColor()) {
322         fragBuilder->codeAppendf("%s = max(%s, half4(0));", outputName, outputName);
323     }
324 }
325 
SetTransform(const GrGLSLProgramDataManager & pdman,const GrShaderCaps & shaderCaps,const UniformHandle & uniform,const SkMatrix & matrix,SkMatrix * state)326 void ProgramImpl::SetTransform(const GrGLSLProgramDataManager& pdman,
327                                const GrShaderCaps& shaderCaps,
328                                const UniformHandle& uniform,
329                                const SkMatrix& matrix,
330                                SkMatrix* state) {
331     if (!uniform.isValid() || (state && SkMatrixPriv::CheapEqual(*state, matrix))) {
332         // No update needed
333         return;
334     }
335     if (state) {
336         *state = matrix;
337     }
338     if (matrix.isScaleTranslate() && !shaderCaps.reducedShaderMode()) {
339         // ComputeMatrixKey and writeX() assume the uniform is a float4 (can't assert since nothing
340         // is exposed on a handle, but should be caught lower down).
341         float values[4] = {matrix.getScaleX(), matrix.getTranslateX(),
342                            matrix.getScaleY(), matrix.getTranslateY()};
343         pdman.set4fv(uniform, 1, values);
344     } else {
345         pdman.setSkMatrix(uniform, matrix);
346     }
347 }
348 
write_passthrough_vertex_position(GrGLSLVertexBuilder * vertBuilder,const GrShaderVar & inPos,GrShaderVar * outPos)349 static void write_passthrough_vertex_position(GrGLSLVertexBuilder* vertBuilder,
350                                               const GrShaderVar& inPos,
351                                               GrShaderVar* outPos) {
352     SkASSERT(inPos.getType() == kFloat3_GrSLType || inPos.getType() == kFloat2_GrSLType);
353     SkString outName = vertBuilder->newTmpVarName(inPos.getName().c_str());
354     outPos->set(inPos.getType(), outName.c_str());
355     vertBuilder->codeAppendf("float%d %s = %s;",
356                              GrSLTypeVecLength(inPos.getType()),
357                              outName.c_str(),
358                              inPos.getName().c_str());
359 }
360 
write_vertex_position(GrGLSLVertexBuilder * vertBuilder,GrGLSLUniformHandler * uniformHandler,const GrShaderCaps & shaderCaps,const GrShaderVar & inPos,const SkMatrix & matrix,const char * matrixName,GrShaderVar * outPos,ProgramImpl::UniformHandle * matrixUniform)361 static void write_vertex_position(GrGLSLVertexBuilder* vertBuilder,
362                                   GrGLSLUniformHandler* uniformHandler,
363                                   const GrShaderCaps& shaderCaps,
364                                   const GrShaderVar& inPos,
365                                   const SkMatrix& matrix,
366                                   const char* matrixName,
367                                   GrShaderVar* outPos,
368                                   ProgramImpl::UniformHandle* matrixUniform) {
369     SkASSERT(inPos.getType() == kFloat3_GrSLType || inPos.getType() == kFloat2_GrSLType);
370     SkString outName = vertBuilder->newTmpVarName(inPos.getName().c_str());
371 
372     if (matrix.isIdentity() && !shaderCaps.reducedShaderMode()) {
373         write_passthrough_vertex_position(vertBuilder, inPos, outPos);
374         return;
375     }
376     SkASSERT(matrixUniform);
377 
378     bool useCompactTransform = matrix.isScaleTranslate() && !shaderCaps.reducedShaderMode();
379     const char* mangledMatrixName;
380     *matrixUniform = uniformHandler->addUniform(nullptr,
381                                                 kVertex_GrShaderFlag,
382                                                 useCompactTransform ? kFloat4_GrSLType
383                                                                     : kFloat3x3_GrSLType,
384                                                 matrixName,
385                                                 &mangledMatrixName);
386 
387     if (inPos.getType() == kFloat3_GrSLType) {
388         // A float3 stays a float3 whether or not the matrix adds perspective
389         if (useCompactTransform) {
390             vertBuilder->codeAppendf("float3 %s = %s.xz1 * %s + %s.yw0;\n",
391                                      outName.c_str(),
392                                      mangledMatrixName,
393                                      inPos.getName().c_str(),
394                                      mangledMatrixName);
395         } else {
396             vertBuilder->codeAppendf("float3 %s = %s * %s;\n",
397                                      outName.c_str(),
398                                      mangledMatrixName,
399                                      inPos.getName().c_str());
400         }
401         outPos->set(kFloat3_GrSLType, outName.c_str());
402         return;
403     }
404     if (matrix.hasPerspective()) {
405         // A float2 is promoted to a float3 if we add perspective via the matrix
406         SkASSERT(!useCompactTransform);
407         vertBuilder->codeAppendf("float3 %s = (%s * %s.xy1);",
408                                  outName.c_str(),
409                                  mangledMatrixName,
410                                  inPos.getName().c_str());
411         outPos->set(kFloat3_GrSLType, outName.c_str());
412         return;
413     }
414     if (useCompactTransform) {
415         vertBuilder->codeAppendf("float2 %s = %s.xz * %s + %s.yw;\n",
416                                  outName.c_str(),
417                                  mangledMatrixName,
418                                  inPos.getName().c_str(),
419                                  mangledMatrixName);
420     } else if (shaderCaps.nonsquareMatrixSupport()) {
421         vertBuilder->codeAppendf("float2 %s = float3x2(%s) * %s.xy1;\n",
422                                  outName.c_str(),
423                                  mangledMatrixName,
424                                  inPos.getName().c_str());
425     } else {
426         vertBuilder->codeAppendf("float2 %s = (%s * %s.xy1).xy;\n",
427                                  outName.c_str(),
428                                  mangledMatrixName,
429                                  inPos.getName().c_str());
430     }
431     outPos->set(kFloat2_GrSLType, outName.c_str());
432 }
433 
WriteOutputPosition(GrGLSLVertexBuilder * vertBuilder,GrGPArgs * gpArgs,const char * posName)434 void ProgramImpl::WriteOutputPosition(GrGLSLVertexBuilder* vertBuilder,
435                                       GrGPArgs* gpArgs,
436                                       const char* posName) {
437     // writeOutputPosition assumes the incoming pos name points to a float2 variable
438     GrShaderVar inPos(posName, kFloat2_GrSLType);
439     write_passthrough_vertex_position(vertBuilder, inPos, &gpArgs->fPositionVar);
440 }
441 
WriteOutputPosition(GrGLSLVertexBuilder * vertBuilder,GrGLSLUniformHandler * uniformHandler,const GrShaderCaps & shaderCaps,GrGPArgs * gpArgs,const char * posName,const SkMatrix & mat,UniformHandle * viewMatrixUniform)442 void ProgramImpl::WriteOutputPosition(GrGLSLVertexBuilder* vertBuilder,
443                                       GrGLSLUniformHandler* uniformHandler,
444                                       const GrShaderCaps& shaderCaps,
445                                       GrGPArgs* gpArgs,
446                                       const char* posName,
447                                       const SkMatrix& mat,
448                                       UniformHandle* viewMatrixUniform) {
449     GrShaderVar inPos(posName, kFloat2_GrSLType);
450     write_vertex_position(vertBuilder,
451                           uniformHandler,
452                           shaderCaps,
453                           inPos,
454                           mat,
455                           "viewMatrix",
456                           &gpArgs->fPositionVar,
457                           viewMatrixUniform);
458 }
459 
WriteLocalCoord(GrGLSLVertexBuilder * vertBuilder,GrGLSLUniformHandler * uniformHandler,const GrShaderCaps & shaderCaps,GrGPArgs * gpArgs,GrShaderVar localVar,const SkMatrix & localMatrix,UniformHandle * localMatrixUniform)460 void ProgramImpl::WriteLocalCoord(GrGLSLVertexBuilder* vertBuilder,
461                                   GrGLSLUniformHandler* uniformHandler,
462                                   const GrShaderCaps& shaderCaps,
463                                   GrGPArgs* gpArgs,
464                                   GrShaderVar localVar,
465                                   const SkMatrix& localMatrix,
466                                   UniformHandle* localMatrixUniform) {
467     write_vertex_position(vertBuilder,
468                           uniformHandler,
469                           shaderCaps,
470                           localVar,
471                           localMatrix,
472                           "localMatrix",
473                           &gpArgs->fLocalCoordVar,
474                           localMatrixUniform);
475 }
476