• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2013 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/core/SkDistanceFieldGen.h"
9 #include "src/gpu/GrCaps.h"
10 #include "src/gpu/GrShaderCaps.h"
11 #include "src/gpu/GrTexture.h"
12 #include "src/gpu/KeyBuilder.h"
13 #include "src/gpu/effects/GrAtlasedShaderHelpers.h"
14 #include "src/gpu/effects/GrDistanceFieldGeoProc.h"
15 #include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h"
16 #include "src/gpu/glsl/GrGLSLProgramDataManager.h"
17 #include "src/gpu/glsl/GrGLSLUniformHandler.h"
18 #include "src/gpu/glsl/GrGLSLVarying.h"
19 #include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h"
20 
21 // Assuming a radius of a little less than the diagonal of the fragment
22 #define SK_DistanceFieldAAFactor     "0.65"
23 
24 class GrDistanceFieldA8TextGeoProc::Impl : public ProgramImpl {
25 public:
setData(const GrGLSLProgramDataManager & pdman,const GrShaderCaps & shaderCaps,const GrGeometryProcessor & geomProc)26     void setData(const GrGLSLProgramDataManager& pdman,
27                  const GrShaderCaps& shaderCaps,
28                  const GrGeometryProcessor& geomProc) override {
29         const GrDistanceFieldA8TextGeoProc& dfa8gp = geomProc.cast<GrDistanceFieldA8TextGeoProc>();
30 
31 #ifdef SK_GAMMA_APPLY_TO_A8
32         float distanceAdjust = dfa8gp.fDistanceAdjust;
33         if (distanceAdjust != fDistanceAdjust) {
34             fDistanceAdjust = distanceAdjust;
35             pdman.set1f(fDistanceAdjustUni, distanceAdjust);
36         }
37 #endif
38 
39         const SkISize& atlasDimensions = dfa8gp.fAtlasDimensions;
40         SkASSERT(SkIsPow2(atlasDimensions.fWidth) && SkIsPow2(atlasDimensions.fHeight));
41 
42         if (fAtlasDimensions != atlasDimensions) {
43             pdman.set2f(fAtlasDimensionsInvUniform,
44                         1.0f / atlasDimensions.fWidth,
45                         1.0f / atlasDimensions.fHeight);
46             fAtlasDimensions = atlasDimensions;
47         }
48         SetTransform(pdman, shaderCaps, fLocalMatrixUniform, dfa8gp.fLocalMatrix, &fLocalMatrix);
49     }
50 
51 private:
onEmitCode(EmitArgs & args,GrGPArgs * gpArgs)52     void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override{
53         const GrDistanceFieldA8TextGeoProc& dfTexEffect =
54                 args.fGeomProc.cast<GrDistanceFieldA8TextGeoProc>();
55         GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
56 
57         GrGLSLVertexBuilder* vertBuilder = args.fVertBuilder;
58         GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler;
59         GrGLSLUniformHandler* uniformHandler = args.fUniformHandler;
60 
61         // emit attributes
62         varyingHandler->emitAttributes(dfTexEffect);
63 
64         const char* atlasDimensionsInvName;
65         fAtlasDimensionsInvUniform = uniformHandler->addUniform(nullptr,
66                                                                 kVertex_GrShaderFlag,
67                                                                 SkSLType::kFloat2,
68                                                                 "AtlasDimensionsInv",
69                                                                 &atlasDimensionsInvName);
70 #ifdef SK_GAMMA_APPLY_TO_A8
71         // adjust based on gamma
72         const char* distanceAdjustUniName = nullptr;
73         // width, height, 1/(3*width)
74         fDistanceAdjustUni = uniformHandler->addUniform(nullptr, kFragment_GrShaderFlag,
75                                                         SkSLType::kHalf, "DistanceAdjust",
76                                                         &distanceAdjustUniName);
77 #endif
78 
79         // Setup pass through color
80         fragBuilder->codeAppendf("half4 %s;\n", args.fOutputColor);
81         varyingHandler->addPassThroughAttribute(dfTexEffect.fInColor.asShaderVar(),
82                                                 args.fOutputColor);
83 
84         // Setup position
85         gpArgs->fPositionVar = dfTexEffect.fInPosition.asShaderVar();
86         WriteLocalCoord(vertBuilder,
87                         uniformHandler,
88                         *args.fShaderCaps,
89                         gpArgs,
90                         gpArgs->fPositionVar,
91                         dfTexEffect.fLocalMatrix,
92                         &fLocalMatrixUniform);
93 
94         // add varyings
95         GrGLSLVarying uv, texIdx, st;
96         append_index_uv_varyings(args,
97                                  dfTexEffect.numTextureSamplers(),
98                                  dfTexEffect.fInTextureCoords.name(),
99                                  atlasDimensionsInvName,
100                                  &uv,
101                                  &texIdx,
102                                  &st);
103 
104         bool isUniformScale = (dfTexEffect.fFlags & kUniformScale_DistanceFieldEffectMask) ==
105                                                     kUniformScale_DistanceFieldEffectMask;
106         bool isSimilarity   = SkToBool(dfTexEffect.fFlags & kSimilarity_DistanceFieldEffectFlag  );
107         bool isGammaCorrect = SkToBool(dfTexEffect.fFlags & kGammaCorrect_DistanceFieldEffectFlag);
108         bool isAliased      = SkToBool(dfTexEffect.fFlags & kAliased_DistanceFieldEffectFlag     );
109 
110         // Use highp to work around aliasing issues
111         fragBuilder->codeAppendf("float2 uv = %s;\n", uv.fsIn());
112         fragBuilder->codeAppend("half4 texColor;");
113         append_multitexture_lookup(args, dfTexEffect.numTextureSamplers(),
114                                    texIdx, "uv", "texColor");
115 
116         fragBuilder->codeAppend("half distance = "
117                       SK_DistanceFieldMultiplier "*(texColor.r - " SK_DistanceFieldThreshold ");");
118 #ifdef SK_GAMMA_APPLY_TO_A8
119         // adjust width based on gamma
120         fragBuilder->codeAppendf("distance -= %s;", distanceAdjustUniName);
121 #endif
122 
123         fragBuilder->codeAppend("half afwidth;");
124         if (isUniformScale) {
125             // For uniform scale, we adjust for the effect of the transformation on the distance
126             // by using the length of the gradient of the t coordinate in the y direction.
127             // We use st coordinates to ensure we're mapping 1:1 from texel space to pixel space.
128 
129             // this gives us a smooth step across approximately one fragment
130             if (args.fShaderCaps->avoidDfDxForGradientsWhenPossible()) {
131                 fragBuilder->codeAppendf(
132                         "afwidth = abs(" SK_DistanceFieldAAFactor "*half(dFdy(%s.y)));", st.fsIn());
133             } else {
134                 fragBuilder->codeAppendf(
135                         "afwidth = abs(" SK_DistanceFieldAAFactor "*half(dFdx(%s.x)));", st.fsIn());
136             }
137         } else if (isSimilarity) {
138             // For similarity transform, we adjust the effect of the transformation on the distance
139             // by using the length of the gradient of the texture coordinates. We use st coordinates
140             // to ensure we're mapping 1:1 from texel space to pixel space.
141             // We use the y gradient because there is a bug in the Mali 400 in the x direction.
142 
143             // this gives us a smooth step across approximately one fragment
144             if (args.fShaderCaps->avoidDfDxForGradientsWhenPossible()) {
145                 fragBuilder->codeAppendf("half st_grad_len = length(half2(dFdy(%s)));", st.fsIn());
146             } else {
147                 fragBuilder->codeAppendf("half st_grad_len = length(half2(dFdx(%s)));", st.fsIn());
148             }
149             fragBuilder->codeAppend("afwidth = abs(" SK_DistanceFieldAAFactor "*st_grad_len);");
150         } else {
151             // For general transforms, to determine the amount of correction we multiply a unit
152             // vector pointing along the SDF gradient direction by the Jacobian of the st coords
153             // (which is the inverse transform for this fragment) and take the length of the result.
154             fragBuilder->codeAppend("half2 dist_grad = half2(float2(dFdx(distance), "
155                                                                    "dFdy(distance)));");
156             // the length of the gradient may be 0, so we need to check for this
157             // this also compensates for the Adreno, which likes to drop tiles on division by 0
158             fragBuilder->codeAppend("half dg_len2 = dot(dist_grad, dist_grad);");
159             fragBuilder->codeAppend("if (dg_len2 < 0.0001) {");
160             fragBuilder->codeAppend("dist_grad = half2(0.7071, 0.7071);");
161             fragBuilder->codeAppend("} else {");
162             fragBuilder->codeAppend("dist_grad = dist_grad*half(inversesqrt(dg_len2));");
163             fragBuilder->codeAppend("}");
164 
165             fragBuilder->codeAppendf("half2 Jdx = half2(dFdx(%s));", st.fsIn());
166             fragBuilder->codeAppendf("half2 Jdy = half2(dFdy(%s));", st.fsIn());
167             fragBuilder->codeAppend("half2 grad = half2(dist_grad.x*Jdx.x + dist_grad.y*Jdy.x,");
168             fragBuilder->codeAppend("                 dist_grad.x*Jdx.y + dist_grad.y*Jdy.y);");
169 
170             // this gives us a smooth step across approximately one fragment
171             fragBuilder->codeAppend("afwidth = " SK_DistanceFieldAAFactor "*length(grad);");
172         }
173 
174         if (isAliased) {
175             fragBuilder->codeAppend("half val = distance > 0 ? 1.0 : 0.0;");
176         } else if (isGammaCorrect) {
177             // The smoothstep falloff compensates for the non-linear sRGB response curve. If we are
178             // doing gamma-correct rendering (to an sRGB or F16 buffer), then we actually want
179             // distance mapped linearly to coverage, so use a linear step:
180             fragBuilder->codeAppend(
181                 "half val = saturate((distance + afwidth) / (2.0 * afwidth));");
182         } else {
183             fragBuilder->codeAppend("half val = smoothstep(-afwidth, afwidth, distance);");
184         }
185 
186         fragBuilder->codeAppendf("half4 %s = half4(val);", args.fOutputCoverage);
187     }
188 
189 private:
190 #ifdef SK_GAMMA_APPLY_TO_A8
191     float    fDistanceAdjust  = -1.f;
192 #endif
193     SkISize  fAtlasDimensions = {-1, -1};
194     SkMatrix fLocalMatrix     = SkMatrix::InvalidMatrix();
195 
196     UniformHandle fDistanceAdjustUni;
197     UniformHandle fAtlasDimensionsInvUniform;
198     UniformHandle fLocalMatrixUniform;
199 
200     using INHERITED = ProgramImpl;
201 };
202 
203 ///////////////////////////////////////////////////////////////////////////////
204 
GrDistanceFieldA8TextGeoProc(const GrShaderCaps & caps,const GrSurfaceProxyView * views,int numViews,GrSamplerState params,float distanceAdjust,uint32_t flags,const SkMatrix & localMatrix)205 GrDistanceFieldA8TextGeoProc::GrDistanceFieldA8TextGeoProc(const GrShaderCaps& caps,
206                                                            const GrSurfaceProxyView* views,
207                                                            int numViews,
208                                                            GrSamplerState params,
209 #ifdef SK_GAMMA_APPLY_TO_A8
210                                                            float distanceAdjust,
211 #endif
212                                                            uint32_t flags,
213                                                            const SkMatrix& localMatrix)
214         : INHERITED(kGrDistanceFieldA8TextGeoProc_ClassID)
215         , fLocalMatrix(localMatrix)
216         , fFlags(flags & kNonLCD_DistanceFieldEffectMask)
217 #ifdef SK_GAMMA_APPLY_TO_A8
218         , fDistanceAdjust(distanceAdjust)
219 #endif
220 {
221     SkASSERT(numViews <= kMaxTextures);
222     SkASSERT(!(flags & ~kNonLCD_DistanceFieldEffectMask));
223 
224     if (flags & kPerspective_DistanceFieldEffectFlag) {
225         fInPosition = {"inPosition", kFloat3_GrVertexAttribType, SkSLType::kFloat3};
226     } else {
227         fInPosition = {"inPosition", kFloat2_GrVertexAttribType, SkSLType::kFloat2};
228     }
229     fInColor = {"inColor", kUByte4_norm_GrVertexAttribType, SkSLType::kHalf4 };
230     fInTextureCoords = {"inTextureCoords", kUShort2_GrVertexAttribType,
231                         caps.integerSupport() ? SkSLType::kUShort2 : SkSLType::kFloat2};
232     this->setVertexAttributesWithImplicitOffsets(&fInPosition, 3);
233 
234     if (numViews) {
235         fAtlasDimensions = views[0].proxy()->dimensions();
236     }
237     for (int i = 0; i < numViews; ++i) {
238         const GrSurfaceProxy* proxy = views[i].proxy();
239         SkASSERT(proxy);
240         SkASSERT(proxy->dimensions() == fAtlasDimensions);
241         fTextureSamplers[i].reset(params, proxy->backendFormat(), views[i].swizzle());
242     }
243     this->setTextureSamplerCnt(numViews);
244 }
245 
addNewViews(const GrSurfaceProxyView * views,int numViews,GrSamplerState params)246 void GrDistanceFieldA8TextGeoProc::addNewViews(const GrSurfaceProxyView* views,
247                                                int numViews,
248                                                GrSamplerState params) {
249     SkASSERT(numViews <= kMaxTextures);
250     // Just to make sure we don't try to add too many proxies
251     numViews = std::min(numViews, kMaxTextures);
252 
253     if (!fTextureSamplers[0].isInitialized()) {
254         fAtlasDimensions = views[0].proxy()->dimensions();
255     }
256 
257     for (int i = 0; i < numViews; ++i) {
258         const GrSurfaceProxy* proxy = views[i].proxy();
259         SkASSERT(proxy);
260         SkASSERT(proxy->dimensions() == fAtlasDimensions);
261         if (!fTextureSamplers[i].isInitialized()) {
262             fTextureSamplers[i].reset(params, proxy->backendFormat(), views[i].swizzle());
263         }
264     }
265     this->setTextureSamplerCnt(numViews);
266 }
267 
addToKey(const GrShaderCaps & caps,skgpu::KeyBuilder * b) const268 void GrDistanceFieldA8TextGeoProc::addToKey(const GrShaderCaps& caps,
269                                             skgpu::KeyBuilder* b) const {
270     uint32_t key = 0;
271     key |= fFlags;
272     key |= ProgramImpl::ComputeMatrixKey(caps, fLocalMatrix) << 16;
273     b->add32(key);
274     b->add32(this->numTextureSamplers());
275 }
276 
makeProgramImpl(const GrShaderCaps &) const277 std::unique_ptr<GrGeometryProcessor::ProgramImpl> GrDistanceFieldA8TextGeoProc::makeProgramImpl(
278         const GrShaderCaps&) const {
279     return std::make_unique<Impl>();
280 }
281 
282 ///////////////////////////////////////////////////////////////////////////////
283 
284 GR_DEFINE_GEOMETRY_PROCESSOR_TEST(GrDistanceFieldA8TextGeoProc);
285 
286 #if GR_TEST_UTILS
TestCreate(GrProcessorTestData * d)287 GrGeometryProcessor* GrDistanceFieldA8TextGeoProc::TestCreate(GrProcessorTestData* d) {
288     auto [view, ct, at] = d->randomAlphaOnlyView();
289 
290     GrSamplerState::WrapMode wrapModes[2];
291     GrTest::TestWrapModes(d->fRandom, wrapModes);
292     GrSamplerState samplerState(wrapModes, d->fRandom->nextBool()
293                                                    ? GrSamplerState::Filter::kLinear
294                                                    : GrSamplerState::Filter::kNearest);
295 
296     uint32_t flags = 0;
297     flags |= d->fRandom->nextBool() ? kSimilarity_DistanceFieldEffectFlag : 0;
298     if (flags & kSimilarity_DistanceFieldEffectFlag) {
299         flags |= d->fRandom->nextBool() ? kScaleOnly_DistanceFieldEffectFlag : 0;
300     }
301     SkMatrix localMatrix = GrTest::TestMatrix(d->fRandom);
302 #ifdef SK_GAMMA_APPLY_TO_A8
303     float lum = d->fRandom->nextF();
304 #endif
305     return GrDistanceFieldA8TextGeoProc::Make(d->allocator(), *d->caps()->shaderCaps(),
306                                               &view, 1,
307                                               samplerState,
308 #ifdef SK_GAMMA_APPLY_TO_A8
309                                               lum,
310 #endif
311                                               flags, localMatrix);
312 }
313 #endif
314 
315 ///////////////////////////////////////////////////////////////////////////////
316 
317 class GrDistanceFieldPathGeoProc::Impl : public ProgramImpl {
318 public:
setData(const GrGLSLProgramDataManager & pdman,const GrShaderCaps & shaderCaps,const GrGeometryProcessor & geomProc)319     void setData(const GrGLSLProgramDataManager& pdman,
320                  const GrShaderCaps& shaderCaps,
321                  const GrGeometryProcessor& geomProc) override {
322         const GrDistanceFieldPathGeoProc& dfpgp = geomProc.cast<GrDistanceFieldPathGeoProc>();
323 
324         // We always set the matrix uniform; it's either used to transform from local to device
325         // for the output position, or from device to local for the local coord variable.
326         SetTransform(pdman, shaderCaps, fMatrixUniform, dfpgp.fMatrix, &fMatrix);
327 
328         const SkISize& atlasDimensions = dfpgp.fAtlasDimensions;
329         SkASSERT(SkIsPow2(atlasDimensions.fWidth) && SkIsPow2(atlasDimensions.fHeight));
330         if (fAtlasDimensions != atlasDimensions) {
331             pdman.set2f(fAtlasDimensionsInvUniform,
332                         1.0f / atlasDimensions.fWidth,
333                         1.0f / atlasDimensions.fHeight);
334             fAtlasDimensions = atlasDimensions;
335         }
336     }
337 
338 private:
onEmitCode(EmitArgs & args,GrGPArgs * gpArgs)339     void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override{
340         const GrDistanceFieldPathGeoProc& dfPathEffect =
341                 args.fGeomProc.cast<GrDistanceFieldPathGeoProc>();
342 
343         GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
344 
345         GrGLSLVertexBuilder* vertBuilder = args.fVertBuilder;
346         GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler;
347         GrGLSLUniformHandler* uniformHandler = args.fUniformHandler;
348 
349         // emit attributes
350         varyingHandler->emitAttributes(dfPathEffect);
351 
352         const char* atlasDimensionsInvName;
353         fAtlasDimensionsInvUniform = uniformHandler->addUniform(nullptr,
354                                                                 kVertex_GrShaderFlag,
355                                                                 SkSLType::kFloat2,
356                                                                 "AtlasDimensionsInv",
357                                                                 &atlasDimensionsInvName);
358 
359         GrGLSLVarying uv, texIdx, st;
360         append_index_uv_varyings(args,
361                                  dfPathEffect.numTextureSamplers(),
362                                  dfPathEffect.fInTextureCoords.name(),
363                                  atlasDimensionsInvName,
364                                  &uv,
365                                  &texIdx,
366                                  &st);
367 
368         // setup pass through color
369         fragBuilder->codeAppendf("half4 %s;", args.fOutputColor);
370         varyingHandler->addPassThroughAttribute(dfPathEffect.fInColor.asShaderVar(),
371                                                 args.fOutputColor);
372 
373         if (dfPathEffect.fMatrix.hasPerspective()) {
374             // Setup position (output position is transformed, local coords are pass through)
375             WriteOutputPosition(vertBuilder,
376                                 uniformHandler,
377                                 *args.fShaderCaps,
378                                 gpArgs,
379                                 dfPathEffect.fInPosition.name(),
380                                 dfPathEffect.fMatrix,
381                                 &fMatrixUniform);
382             gpArgs->fLocalCoordVar = dfPathEffect.fInPosition.asShaderVar();
383         } else {
384             // Setup position (output position is pass through, local coords are transformed)
385             WriteOutputPosition(vertBuilder, gpArgs, dfPathEffect.fInPosition.name());
386             WriteLocalCoord(vertBuilder,
387                             uniformHandler,
388                             *args.fShaderCaps,
389                             gpArgs,
390                             dfPathEffect.fInPosition.asShaderVar(),
391                             dfPathEffect.fMatrix,
392                             &fMatrixUniform);
393         }
394 
395         // Use highp to work around aliasing issues
396         fragBuilder->codeAppendf("float2 uv = %s;", uv.fsIn());
397         fragBuilder->codeAppend("half4 texColor;");
398         append_multitexture_lookup(args, dfPathEffect.numTextureSamplers(), texIdx, "uv",
399                                    "texColor");
400 
401         fragBuilder->codeAppend("half distance = "
402             SK_DistanceFieldMultiplier "*(texColor.r - " SK_DistanceFieldThreshold ");");
403 
404         fragBuilder->codeAppend("half afwidth;");
405         bool isUniformScale = (dfPathEffect.fFlags & kUniformScale_DistanceFieldEffectMask) ==
406                                                      kUniformScale_DistanceFieldEffectMask;
407         bool isSimilarity   = SkToBool(dfPathEffect.fFlags & kSimilarity_DistanceFieldEffectFlag  );
408         bool isGammaCorrect = SkToBool(dfPathEffect.fFlags & kGammaCorrect_DistanceFieldEffectFlag);
409         if (isUniformScale) {
410             // For uniform scale, we adjust for the effect of the transformation on the distance
411             // by using the length of the gradient of the t coordinate in the y direction.
412             // We use st coordinates to ensure we're mapping 1:1 from texel space to pixel space.
413 
414             // this gives us a smooth step across approximately one fragment
415             if (args.fShaderCaps->avoidDfDxForGradientsWhenPossible()) {
416                 fragBuilder->codeAppendf(
417                         "afwidth = abs(" SK_DistanceFieldAAFactor "*half(dFdy(%s.y)));", st.fsIn());
418             } else {
419                 fragBuilder->codeAppendf(
420                         "afwidth = abs(" SK_DistanceFieldAAFactor "*half(dFdx(%s.x)));", st.fsIn());
421             }
422         } else if (isSimilarity) {
423             // For similarity transform, we adjust the effect of the transformation on the distance
424             // by using the length of the gradient of the texture coordinates. We use st coordinates
425             // to ensure we're mapping 1:1 from texel space to pixel space.
426 
427             // this gives us a smooth step across approximately one fragment
428             if (args.fShaderCaps->avoidDfDxForGradientsWhenPossible()) {
429                 fragBuilder->codeAppendf("half st_grad_len = half(length(dFdy(%s)));", st.fsIn());
430             } else {
431                 fragBuilder->codeAppendf("half st_grad_len = half(length(dFdx(%s)));", st.fsIn());
432             }
433             fragBuilder->codeAppend("afwidth = abs(" SK_DistanceFieldAAFactor "*st_grad_len);");
434         } else {
435             // For general transforms, to determine the amount of correction we multiply a unit
436             // vector pointing along the SDF gradient direction by the Jacobian of the st coords
437             // (which is the inverse transform for this fragment) and take the length of the result.
438             fragBuilder->codeAppend("half2 dist_grad = half2(dFdx(distance), "
439                                                             "dFdy(distance));");
440             // the length of the gradient may be 0, so we need to check for this
441             // this also compensates for the Adreno, which likes to drop tiles on division by 0
442             fragBuilder->codeAppend("half dg_len2 = dot(dist_grad, dist_grad);");
443             fragBuilder->codeAppend("if (dg_len2 < 0.0001) {");
444             fragBuilder->codeAppend("dist_grad = half2(0.7071, 0.7071);");
445             fragBuilder->codeAppend("} else {");
446             fragBuilder->codeAppend("dist_grad = dist_grad*half(inversesqrt(dg_len2));");
447             fragBuilder->codeAppend("}");
448 
449             fragBuilder->codeAppendf("half2 Jdx = half2(dFdx(%s));", st.fsIn());
450             fragBuilder->codeAppendf("half2 Jdy = half2(dFdy(%s));", st.fsIn());
451             fragBuilder->codeAppend("half2 grad = half2(dist_grad.x*Jdx.x + dist_grad.y*Jdy.x,");
452             fragBuilder->codeAppend("                   dist_grad.x*Jdx.y + dist_grad.y*Jdy.y);");
453 
454             // this gives us a smooth step across approximately one fragment
455             fragBuilder->codeAppend("afwidth = " SK_DistanceFieldAAFactor "*length(grad);");
456         }
457         // The smoothstep falloff compensates for the non-linear sRGB response curve. If we are
458         // doing gamma-correct rendering (to an sRGB or F16 buffer), then we actually want distance
459         // mapped linearly to coverage, so use a linear step:
460         if (isGammaCorrect) {
461             fragBuilder->codeAppend(
462                 "half val = saturate((distance + afwidth) / (2.0 * afwidth));");
463         } else {
464             fragBuilder->codeAppend("half val = smoothstep(-afwidth, afwidth, distance);");
465         }
466 
467         fragBuilder->codeAppendf("half4 %s = half4(val);", args.fOutputCoverage);
468     }
469 
470     SkMatrix      fMatrix;        // view matrix if perspective, local matrix otherwise
471     UniformHandle fMatrixUniform;
472 
473     SkISize       fAtlasDimensions;
474     UniformHandle fAtlasDimensionsInvUniform;
475 
476     using INHERITED = ProgramImpl;
477 };
478 
479 ///////////////////////////////////////////////////////////////////////////////
480 
GrDistanceFieldPathGeoProc(const GrShaderCaps & caps,const SkMatrix & matrix,bool wideColor,const GrSurfaceProxyView * views,int numViews,GrSamplerState params,uint32_t flags)481 GrDistanceFieldPathGeoProc::GrDistanceFieldPathGeoProc(const GrShaderCaps& caps,
482                                                        const SkMatrix& matrix,
483                                                        bool wideColor,
484                                                        const GrSurfaceProxyView* views,
485                                                        int numViews,
486                                                        GrSamplerState params,
487                                                        uint32_t flags)
488         : INHERITED(kGrDistanceFieldPathGeoProc_ClassID)
489         , fMatrix(matrix)
490         , fFlags(flags & kNonLCD_DistanceFieldEffectMask) {
491     SkASSERT(numViews <= kMaxTextures);
492     SkASSERT(!(flags & ~kNonLCD_DistanceFieldEffectMask));
493 
494     fInPosition = {"inPosition", kFloat2_GrVertexAttribType, SkSLType::kFloat2};
495     fInColor = MakeColorAttribute("inColor", wideColor);
496     fInTextureCoords = {"inTextureCoords", kUShort2_GrVertexAttribType,
497                         caps.integerSupport() ? SkSLType::kUShort2 : SkSLType::kFloat2};
498     this->setVertexAttributesWithImplicitOffsets(&fInPosition, 3);
499 
500     if (numViews) {
501         fAtlasDimensions = views[0].proxy()->dimensions();
502     }
503 
504     for (int i = 0; i < numViews; ++i) {
505         const GrSurfaceProxy* proxy = views[i].proxy();
506         SkASSERT(proxy);
507         SkASSERT(proxy->dimensions() == fAtlasDimensions);
508         fTextureSamplers[i].reset(params, proxy->backendFormat(), views[i].swizzle());
509     }
510     this->setTextureSamplerCnt(numViews);
511 }
512 
addNewViews(const GrSurfaceProxyView * views,int numViews,GrSamplerState params)513 void GrDistanceFieldPathGeoProc::addNewViews(const GrSurfaceProxyView* views,
514                                              int numViews,
515                                              GrSamplerState params) {
516     SkASSERT(numViews <= kMaxTextures);
517     // Just to make sure we don't try to add too many proxies
518     numViews = std::min(numViews, kMaxTextures);
519 
520     if (!fTextureSamplers[0].isInitialized()) {
521         fAtlasDimensions = views[0].proxy()->dimensions();
522     }
523 
524     for (int i = 0; i < numViews; ++i) {
525         const GrSurfaceProxy* proxy = views[i].proxy();
526         SkASSERT(proxy);
527         SkASSERT(proxy->dimensions() == fAtlasDimensions);
528         if (!fTextureSamplers[i].isInitialized()) {
529             fTextureSamplers[i].reset(params, proxy->backendFormat(), views[i].swizzle());
530         }
531     }
532     this->setTextureSamplerCnt(numViews);
533 }
534 
addToKey(const GrShaderCaps & caps,skgpu::KeyBuilder * b) const535 void GrDistanceFieldPathGeoProc::addToKey(const GrShaderCaps& caps,
536                                           skgpu::KeyBuilder* b) const {
537     uint32_t key = fFlags;
538     key |= ProgramImpl::ComputeMatrixKey(caps, fMatrix) << 16;
539     key |= fMatrix.hasPerspective() << (16 + ProgramImpl::kMatrixKeyBits);
540     b->add32(key);
541     b->add32(this->numTextureSamplers());
542 }
543 
makeProgramImpl(const GrShaderCaps &) const544 std::unique_ptr<GrGeometryProcessor::ProgramImpl> GrDistanceFieldPathGeoProc::makeProgramImpl(
545         const GrShaderCaps&) const {
546     return std::make_unique<Impl>();
547 }
548 
549 ///////////////////////////////////////////////////////////////////////////////
550 
551 GR_DEFINE_GEOMETRY_PROCESSOR_TEST(GrDistanceFieldPathGeoProc);
552 
553 #if GR_TEST_UTILS
TestCreate(GrProcessorTestData * d)554 GrGeometryProcessor* GrDistanceFieldPathGeoProc::TestCreate(GrProcessorTestData* d) {
555     auto [view, ct, at] = d->randomAlphaOnlyView();
556 
557     GrSamplerState::WrapMode wrapModes[2];
558     GrTest::TestWrapModes(d->fRandom, wrapModes);
559     GrSamplerState samplerState(wrapModes, d->fRandom->nextBool()
560                                                    ? GrSamplerState::Filter::kLinear
561                                                    : GrSamplerState::Filter::kNearest);
562 
563     uint32_t flags = 0;
564     flags |= d->fRandom->nextBool() ? kSimilarity_DistanceFieldEffectFlag : 0;
565     if (flags & kSimilarity_DistanceFieldEffectFlag) {
566         flags |= d->fRandom->nextBool() ? kScaleOnly_DistanceFieldEffectFlag : 0;
567     }
568     SkMatrix localMatrix = GrTest::TestMatrix(d->fRandom);
569     bool wideColor = d->fRandom->nextBool();
570     return GrDistanceFieldPathGeoProc::Make(d->allocator(), *d->caps()->shaderCaps(),
571                                             localMatrix,
572                                             wideColor,
573                                             &view, 1,
574                                             samplerState,
575                                             flags);
576 }
577 #endif
578 
579 ///////////////////////////////////////////////////////////////////////////////
580 
581 class GrDistanceFieldLCDTextGeoProc::Impl : public ProgramImpl {
582 public:
setData(const GrGLSLProgramDataManager & pdman,const GrShaderCaps & shaderCaps,const GrGeometryProcessor & geomProc)583     void setData(const GrGLSLProgramDataManager& pdman,
584                  const GrShaderCaps& shaderCaps,
585                  const GrGeometryProcessor& geomProc) override {
586         SkASSERT(fDistanceAdjustUni.isValid());
587 
588         const GrDistanceFieldLCDTextGeoProc& dflcd = geomProc.cast<GrDistanceFieldLCDTextGeoProc>();
589         GrDistanceFieldLCDTextGeoProc::DistanceAdjust wa = dflcd.fDistanceAdjust;
590         if (wa != fDistanceAdjust) {
591             pdman.set3f(fDistanceAdjustUni, wa.fR, wa.fG, wa.fB);
592             fDistanceAdjust = wa;
593         }
594 
595         const SkISize& atlasDimensions = dflcd.fAtlasDimensions;
596         SkASSERT(SkIsPow2(atlasDimensions.fWidth) && SkIsPow2(atlasDimensions.fHeight));
597         if (fAtlasDimensions != atlasDimensions) {
598             pdman.set2f(fAtlasDimensionsInvUniform,
599                         1.0f / atlasDimensions.fWidth,
600                         1.0f / atlasDimensions.fHeight);
601             fAtlasDimensions = atlasDimensions;
602         }
603         SetTransform(pdman, shaderCaps, fLocalMatrixUniform, dflcd.fLocalMatrix, &fLocalMatrix);
604     }
605 
606 private:
onEmitCode(EmitArgs & args,GrGPArgs * gpArgs)607     void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override {
608         const GrDistanceFieldLCDTextGeoProc& dfTexEffect =
609                 args.fGeomProc.cast<GrDistanceFieldLCDTextGeoProc>();
610 
611         GrGLSLVertexBuilder* vertBuilder = args.fVertBuilder;
612         GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler;
613         GrGLSLUniformHandler* uniformHandler = args.fUniformHandler;
614 
615         // emit attributes
616         varyingHandler->emitAttributes(dfTexEffect);
617 
618         const char* atlasDimensionsInvName;
619         fAtlasDimensionsInvUniform = uniformHandler->addUniform(nullptr,
620                                                                 kVertex_GrShaderFlag,
621                                                                 SkSLType::kFloat2,
622                                                                 "AtlasDimensionsInv",
623                                                                 &atlasDimensionsInvName);
624 
625         GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
626 
627         // setup pass through color
628         fragBuilder->codeAppendf("half4 %s;\n", args.fOutputColor);
629         varyingHandler->addPassThroughAttribute(dfTexEffect.fInColor.asShaderVar(),
630                                                 args.fOutputColor);
631 
632         // Setup position
633         gpArgs->fPositionVar = dfTexEffect.fInPosition.asShaderVar();
634         WriteLocalCoord(vertBuilder,
635                         uniformHandler,
636                         *args.fShaderCaps,
637                         gpArgs,
638                         dfTexEffect.fInPosition.asShaderVar(),
639                         dfTexEffect.fLocalMatrix,
640                         &fLocalMatrixUniform);
641 
642         // set up varyings
643         GrGLSLVarying uv, texIdx, st;
644         append_index_uv_varyings(args,
645                                  dfTexEffect.numTextureSamplers(),
646                                  dfTexEffect.fInTextureCoords.name(),
647                                  atlasDimensionsInvName,
648                                  &uv,
649                                  &texIdx,
650                                  &st);
651 
652         GrGLSLVarying delta(SkSLType::kFloat);
653         varyingHandler->addVarying("Delta", &delta);
654         if (dfTexEffect.fFlags & kBGR_DistanceFieldEffectFlag) {
655             vertBuilder->codeAppendf("%s = -%s.x/3.0;", delta.vsOut(), atlasDimensionsInvName);
656         } else {
657             vertBuilder->codeAppendf("%s = %s.x/3.0;", delta.vsOut(), atlasDimensionsInvName);
658         }
659 
660         // add frag shader code
661         bool isUniformScale = (dfTexEffect.fFlags & kUniformScale_DistanceFieldEffectMask) ==
662                                                     kUniformScale_DistanceFieldEffectMask;
663         bool isSimilarity   = SkToBool(dfTexEffect.fFlags & kSimilarity_DistanceFieldEffectFlag  );
664         bool isGammaCorrect = SkToBool(dfTexEffect.fFlags & kGammaCorrect_DistanceFieldEffectFlag);
665 
666         // create LCD offset adjusted by inverse of transform
667         // Use highp to work around aliasing issues
668         fragBuilder->codeAppendf("float2 uv = %s;\n", uv.fsIn());
669 
670         if (isUniformScale) {
671             if (args.fShaderCaps->avoidDfDxForGradientsWhenPossible()) {
672                 fragBuilder->codeAppendf("half st_grad_len = half(abs(dFdy(%s.y)));", st.fsIn());
673             } else {
674                 fragBuilder->codeAppendf("half st_grad_len = half(abs(dFdx(%s.x)));", st.fsIn());
675             }
676             fragBuilder->codeAppendf("half2 offset = half2(half(st_grad_len*%s), 0.0);",
677                                      delta.fsIn());
678         } else if (isSimilarity) {
679             // For a similarity matrix with rotation, the gradient will not be aligned
680             // with the texel coordinate axes, so we need to calculate it.
681             if (args.fShaderCaps->avoidDfDxForGradientsWhenPossible()) {
682                 // We use dFdy instead and rotate -90 degrees to get the gradient in the x
683                 // direction.
684                 fragBuilder->codeAppendf("half2 st_grad = half2(dFdy(%s));", st.fsIn());
685                 fragBuilder->codeAppendf("half2 offset = half2(%s*float2(st_grad.y, -st_grad.x));",
686                                          delta.fsIn());
687             } else {
688                 fragBuilder->codeAppendf("half2 st_grad = half2(dFdx(%s));", st.fsIn());
689                 fragBuilder->codeAppendf("half2 offset = half(%s)*st_grad;", delta.fsIn());
690             }
691             fragBuilder->codeAppend("half st_grad_len = length(st_grad);");
692         } else {
693             fragBuilder->codeAppendf("half2 st = half2(%s);\n", st.fsIn());
694 
695             fragBuilder->codeAppend("half2 Jdx = half2(dFdx(st));");
696             fragBuilder->codeAppend("half2 Jdy = half2(dFdy(st));");
697             fragBuilder->codeAppendf("half2 offset = half2(half(%s))*Jdx;", delta.fsIn());
698         }
699 
700         // sample the texture by index
701         fragBuilder->codeAppend("half4 texColor;");
702         append_multitexture_lookup(args, dfTexEffect.numTextureSamplers(),
703                                    texIdx, "uv", "texColor");
704 
705         // green is distance to uv center
706         fragBuilder->codeAppend("half3 distance;");
707         fragBuilder->codeAppend("distance.y = texColor.r;");
708         // red is distance to left offset
709         fragBuilder->codeAppend("half2 uv_adjusted = half2(uv) - offset;");
710         append_multitexture_lookup(args, dfTexEffect.numTextureSamplers(),
711                                    texIdx, "uv_adjusted", "texColor");
712         fragBuilder->codeAppend("distance.x = texColor.r;");
713         // blue is distance to right offset
714         fragBuilder->codeAppend("uv_adjusted = half2(uv) + offset;");
715         append_multitexture_lookup(args, dfTexEffect.numTextureSamplers(),
716                                    texIdx, "uv_adjusted", "texColor");
717         fragBuilder->codeAppend("distance.z = texColor.r;");
718 
719         fragBuilder->codeAppend("distance = "
720            "half3(" SK_DistanceFieldMultiplier ")*(distance - half3(" SK_DistanceFieldThreshold"));");
721 
722         // adjust width based on gamma
723         const char* distanceAdjustUniName = nullptr;
724         fDistanceAdjustUni = uniformHandler->addUniform(nullptr, kFragment_GrShaderFlag,
725                                                         SkSLType::kHalf3, "DistanceAdjust",
726                                                         &distanceAdjustUniName);
727         fragBuilder->codeAppendf("distance -= %s;", distanceAdjustUniName);
728 
729         // To be strictly correct, we should compute the anti-aliasing factor separately
730         // for each color component. However, this is only important when using perspective
731         // transformations, and even then using a single factor seems like a reasonable
732         // trade-off between quality and speed.
733         fragBuilder->codeAppend("half afwidth;");
734         if (isSimilarity) {
735             // For similarity transform (uniform scale-only is a subset of this), we adjust for the
736             // effect of the transformation on the distance by using the length of the gradient of
737             // the texture coordinates. We use st coordinates to ensure we're mapping 1:1 from texel
738             // space to pixel space.
739 
740             // this gives us a smooth step across approximately one fragment
741             fragBuilder->codeAppend("afwidth = " SK_DistanceFieldAAFactor "*st_grad_len;");
742         } else {
743             // For general transforms, to determine the amount of correction we multiply a unit
744             // vector pointing along the SDF gradient direction by the Jacobian of the st coords
745             // (which is the inverse transform for this fragment) and take the length of the result.
746             fragBuilder->codeAppend("half2 dist_grad = half2(half(dFdx(distance.r)), "
747                                                             "half(dFdy(distance.r)));");
748             // the length of the gradient may be 0, so we need to check for this
749             // this also compensates for the Adreno, which likes to drop tiles on division by 0
750             fragBuilder->codeAppend("half dg_len2 = dot(dist_grad, dist_grad);");
751             fragBuilder->codeAppend("if (dg_len2 < 0.0001) {");
752             fragBuilder->codeAppend("dist_grad = half2(0.7071, 0.7071);");
753             fragBuilder->codeAppend("} else {");
754             fragBuilder->codeAppend("dist_grad = dist_grad*half(inversesqrt(dg_len2));");
755             fragBuilder->codeAppend("}");
756             fragBuilder->codeAppend("half2 grad = half2(dist_grad.x*Jdx.x + dist_grad.y*Jdy.x,");
757             fragBuilder->codeAppend("                 dist_grad.x*Jdx.y + dist_grad.y*Jdy.y);");
758 
759             // this gives us a smooth step across approximately one fragment
760             fragBuilder->codeAppend("afwidth = " SK_DistanceFieldAAFactor "*length(grad);");
761         }
762 
763         // The smoothstep falloff compensates for the non-linear sRGB response curve. If we are
764         // doing gamma-correct rendering (to an sRGB or F16 buffer), then we actually want distance
765         // mapped linearly to coverage, so use a linear step:
766         if (isGammaCorrect) {
767             fragBuilder->codeAppendf("half4 %s = "
768                     "half4(saturate((distance + half3(afwidth)) / half3(2.0 * afwidth)), 1.0);",
769                     args.fOutputCoverage);
770         } else {
771             fragBuilder->codeAppendf(
772                     "half4 %s = half4(smoothstep(half3(-afwidth), half3(afwidth), distance), 1.0);",
773                     args.fOutputCoverage);
774         }
775     }
776 
777 private:
778     DistanceAdjust fDistanceAdjust  = DistanceAdjust::Make(1.0f, 1.0f, 1.0f);
779     SkISize        fAtlasDimensions = {-1, -1};
780     SkMatrix       fLocalMatrix     = SkMatrix::InvalidMatrix();
781 
782     UniformHandle fDistanceAdjustUni;
783     UniformHandle fAtlasDimensionsInvUniform;
784     UniformHandle fLocalMatrixUniform;
785 };
786 
787 ///////////////////////////////////////////////////////////////////////////////
788 
GrDistanceFieldLCDTextGeoProc(const GrShaderCaps & caps,const GrSurfaceProxyView * views,int numViews,GrSamplerState params,DistanceAdjust distanceAdjust,uint32_t flags,const SkMatrix & localMatrix)789 GrDistanceFieldLCDTextGeoProc::GrDistanceFieldLCDTextGeoProc(const GrShaderCaps& caps,
790                                                              const GrSurfaceProxyView* views,
791                                                              int numViews,
792                                                              GrSamplerState params,
793                                                              DistanceAdjust distanceAdjust,
794                                                              uint32_t flags,
795                                                              const SkMatrix& localMatrix)
796         : INHERITED(kGrDistanceFieldLCDTextGeoProc_ClassID)
797         , fLocalMatrix(localMatrix)
798         , fDistanceAdjust(distanceAdjust)
799         , fFlags(flags & kLCD_DistanceFieldEffectMask) {
800     SkASSERT(numViews <= kMaxTextures);
801     SkASSERT(!(flags & ~kLCD_DistanceFieldEffectMask) && (flags & kUseLCD_DistanceFieldEffectFlag));
802 
803     if (fFlags & kPerspective_DistanceFieldEffectFlag) {
804         fInPosition = {"inPosition", kFloat3_GrVertexAttribType, SkSLType::kFloat3};
805     } else {
806         fInPosition = {"inPosition", kFloat2_GrVertexAttribType, SkSLType::kFloat2};
807     }
808     fInColor = {"inColor", kUByte4_norm_GrVertexAttribType, SkSLType::kHalf4};
809     fInTextureCoords = {"inTextureCoords", kUShort2_GrVertexAttribType,
810                         caps.integerSupport() ? SkSLType::kUShort2 : SkSLType::kFloat2};
811     this->setVertexAttributesWithImplicitOffsets(&fInPosition, 3);
812 
813     if (numViews) {
814         fAtlasDimensions = views[0].proxy()->dimensions();
815     }
816 
817     for (int i = 0; i < numViews; ++i) {
818         const GrSurfaceProxy* proxy = views[i].proxy();
819         SkASSERT(proxy);
820         SkASSERT(proxy->dimensions() == fAtlasDimensions);
821         fTextureSamplers[i].reset(params, proxy->backendFormat(), views[i].swizzle());
822     }
823     this->setTextureSamplerCnt(numViews);
824 }
825 
addNewViews(const GrSurfaceProxyView * views,int numViews,GrSamplerState params)826 void GrDistanceFieldLCDTextGeoProc::addNewViews(const GrSurfaceProxyView* views,
827                                                 int numViews,
828                                                 GrSamplerState params) {
829     SkASSERT(numViews <= kMaxTextures);
830     // Just to make sure we don't try to add too many proxies
831     numViews = std::min(numViews, kMaxTextures);
832 
833     if (!fTextureSamplers[0].isInitialized()) {
834         fAtlasDimensions = views[0].proxy()->dimensions();
835     }
836 
837     for (int i = 0; i < numViews; ++i) {
838         const GrSurfaceProxy* proxy = views[i].proxy();
839         SkASSERT(proxy);
840         SkASSERT(proxy->dimensions() == fAtlasDimensions);
841         if (!fTextureSamplers[i].isInitialized()) {
842             fTextureSamplers[i].reset(params, proxy->backendFormat(), views[i].swizzle());
843         }
844     }
845     this->setTextureSamplerCnt(numViews);
846 }
847 
addToKey(const GrShaderCaps & caps,skgpu::KeyBuilder * b) const848 void GrDistanceFieldLCDTextGeoProc::addToKey(const GrShaderCaps& caps,
849                                              skgpu::KeyBuilder* b) const {
850     uint32_t key = 0;
851     key |= ProgramImpl::ComputeMatrixKey(caps, fLocalMatrix);
852     key |= fFlags << 16;
853     b->add32(key);
854     b->add32(this->numTextureSamplers());
855 }
856 
makeProgramImpl(const GrShaderCaps &) const857 std::unique_ptr<GrGeometryProcessor::ProgramImpl> GrDistanceFieldLCDTextGeoProc::makeProgramImpl(
858         const GrShaderCaps&) const {
859     return std::make_unique<Impl>();
860 }
861 
862 ///////////////////////////////////////////////////////////////////////////////
863 
864 GR_DEFINE_GEOMETRY_PROCESSOR_TEST(GrDistanceFieldLCDTextGeoProc);
865 
866 #if GR_TEST_UTILS
TestCreate(GrProcessorTestData * d)867 GrGeometryProcessor* GrDistanceFieldLCDTextGeoProc::TestCreate(GrProcessorTestData* d) {
868     auto [view, ct, at] = d->randomView();
869 
870     GrSamplerState::WrapMode wrapModes[2];
871     GrTest::TestWrapModes(d->fRandom, wrapModes);
872     GrSamplerState samplerState(wrapModes, d->fRandom->nextBool()
873                                                    ? GrSamplerState::Filter::kLinear
874                                                    : GrSamplerState::Filter::kNearest);
875     DistanceAdjust wa = { 0.0f, 0.1f, -0.1f };
876     uint32_t flags = kUseLCD_DistanceFieldEffectFlag;
877     flags |= d->fRandom->nextBool() ? kSimilarity_DistanceFieldEffectFlag : 0;
878     if (flags & kSimilarity_DistanceFieldEffectFlag) {
879         flags |= d->fRandom->nextBool() ? kScaleOnly_DistanceFieldEffectFlag : 0;
880     }
881     flags |= d->fRandom->nextBool() ? kBGR_DistanceFieldEffectFlag : 0;
882     SkMatrix localMatrix = GrTest::TestMatrix(d->fRandom);
883 
884     return GrDistanceFieldLCDTextGeoProc::Make(d->allocator(), *d->caps()->shaderCaps(), &view,
885                                                1, samplerState, wa, flags, localMatrix);
886 }
887 #endif
888