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