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