xref: /aosp_15_r20/external/skia/src/gpu/ganesh/effects/GrDistanceFieldGeoProc.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
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("float2x2 jacobian = float2x2(dFdx(%s), dFdy(%s));",
180                                      st.fsIn(), st.fsIn());
181             fragBuilder->codeAppend("half2 grad = half2(jacobian * dist_grad);");
182 
183             // this gives us a smooth step across approximately one fragment
184             fragBuilder->codeAppend("afwidth = " SK_DistanceFieldAAFactor "*length(grad);");
185         }
186 
187         if (isAliased) {
188             fragBuilder->codeAppend("half val = distance > 0 ? 1.0 : 0.0;");
189         } else if (isGammaCorrect) {
190             // The smoothstep falloff compensates for the non-linear sRGB response curve. If we are
191             // doing gamma-correct rendering (to an sRGB or F16 buffer), then we actually want
192             // distance mapped linearly to coverage, so use a linear step:
193             fragBuilder->codeAppend(
194                 "half val = saturate((distance + afwidth) / (2.0 * afwidth));");
195         } else {
196             fragBuilder->codeAppend("half val = smoothstep(-afwidth, afwidth, distance);");
197         }
198 
199         fragBuilder->codeAppendf("half4 %s = half4(val);", args.fOutputCoverage);
200     }
201 
202 private:
203 #ifdef SK_GAMMA_APPLY_TO_A8
204     float    fDistanceAdjust  = -1.f;
205 #endif
206     SkISize  fAtlasDimensions = {-1, -1};
207     SkMatrix fLocalMatrix     = SkMatrix::InvalidMatrix();
208 
209     UniformHandle fDistanceAdjustUni;
210     UniformHandle fAtlasDimensionsInvUniform;
211     UniformHandle fLocalMatrixUniform;
212 
213     using INHERITED = ProgramImpl;
214 };
215 
216 ///////////////////////////////////////////////////////////////////////////////
217 
GrDistanceFieldA8TextGeoProc(const GrShaderCaps & caps,const GrSurfaceProxyView * views,int numViews,GrSamplerState params,float distanceAdjust,uint32_t flags,const SkMatrix & localMatrix)218 GrDistanceFieldA8TextGeoProc::GrDistanceFieldA8TextGeoProc(const GrShaderCaps& caps,
219                                                            const GrSurfaceProxyView* views,
220                                                            int numViews,
221                                                            GrSamplerState params,
222 #ifdef SK_GAMMA_APPLY_TO_A8
223                                                            float distanceAdjust,
224 #endif
225                                                            uint32_t flags,
226                                                            const SkMatrix& localMatrix)
227         : INHERITED(kGrDistanceFieldA8TextGeoProc_ClassID)
228         , fLocalMatrix(localMatrix)
229         , fFlags(flags & kNonLCD_DistanceFieldEffectMask)
230 #ifdef SK_GAMMA_APPLY_TO_A8
231         , fDistanceAdjust(distanceAdjust)
232 #endif
233 {
234     SkASSERT(numViews <= kMaxTextures);
235     SkASSERT(!(flags & ~kNonLCD_DistanceFieldEffectMask));
236 
237     if (flags & kPerspective_DistanceFieldEffectFlag) {
238         fInPosition = {"inPosition", kFloat3_GrVertexAttribType, SkSLType::kFloat3};
239     } else {
240         fInPosition = {"inPosition", kFloat2_GrVertexAttribType, SkSLType::kFloat2};
241     }
242     fInColor = {"inColor", kUByte4_norm_GrVertexAttribType, SkSLType::kHalf4 };
243     fInTextureCoords = {"inTextureCoords", kUShort2_GrVertexAttribType,
244                         caps.fIntegerSupport ? SkSLType::kUShort2 : SkSLType::kFloat2};
245     this->setVertexAttributesWithImplicitOffsets(&fInPosition, 3);
246 
247     if (numViews) {
248         fAtlasDimensions = views[0].proxy()->dimensions();
249     }
250     for (int i = 0; i < numViews; ++i) {
251         const GrSurfaceProxy* proxy = views[i].proxy();
252         SkASSERT(proxy);
253         SkASSERT(proxy->dimensions() == fAtlasDimensions);
254         fTextureSamplers[i].reset(params, proxy->backendFormat(), views[i].swizzle());
255     }
256     this->setTextureSamplerCnt(numViews);
257 }
258 
addNewViews(const GrSurfaceProxyView * views,int numViews,GrSamplerState params)259 void GrDistanceFieldA8TextGeoProc::addNewViews(const GrSurfaceProxyView* views,
260                                                int numViews,
261                                                GrSamplerState params) {
262     SkASSERT(numViews <= kMaxTextures);
263     // Just to make sure we don't try to add too many proxies
264     numViews = std::min(numViews, kMaxTextures);
265 
266     if (!fTextureSamplers[0].isInitialized()) {
267         fAtlasDimensions = views[0].proxy()->dimensions();
268     }
269 
270     for (int i = 0; i < numViews; ++i) {
271         const GrSurfaceProxy* proxy = views[i].proxy();
272         SkASSERT(proxy);
273         SkASSERT(proxy->dimensions() == fAtlasDimensions);
274         if (!fTextureSamplers[i].isInitialized()) {
275             fTextureSamplers[i].reset(params, proxy->backendFormat(), views[i].swizzle());
276         }
277     }
278     this->setTextureSamplerCnt(numViews);
279 }
280 
addToKey(const GrShaderCaps & caps,skgpu::KeyBuilder * b) const281 void GrDistanceFieldA8TextGeoProc::addToKey(const GrShaderCaps& caps,
282                                             skgpu::KeyBuilder* b) const {
283     uint32_t key = 0;
284     key |= fFlags;
285     key |= ProgramImpl::ComputeMatrixKey(caps, fLocalMatrix) << 16;
286     b->add32(key);
287     b->add32(this->numTextureSamplers());
288 }
289 
makeProgramImpl(const GrShaderCaps &) const290 std::unique_ptr<GrGeometryProcessor::ProgramImpl> GrDistanceFieldA8TextGeoProc::makeProgramImpl(
291         const GrShaderCaps&) const {
292     return std::make_unique<Impl>();
293 }
294 
295 ///////////////////////////////////////////////////////////////////////////////
296 
GR_DEFINE_GEOMETRY_PROCESSOR_TEST(GrDistanceFieldA8TextGeoProc)297 GR_DEFINE_GEOMETRY_PROCESSOR_TEST(GrDistanceFieldA8TextGeoProc)
298 
299 #if defined(GPU_TEST_UTILS)
300 GrGeometryProcessor* GrDistanceFieldA8TextGeoProc::TestCreate(GrProcessorTestData* d) {
301     auto [view, ct, at] = d->randomAlphaOnlyView();
302 
303     GrSamplerState::WrapMode wrapModes[2];
304     GrTest::TestWrapModes(d->fRandom, wrapModes);
305     GrSamplerState samplerState(wrapModes, d->fRandom->nextBool()
306                                                    ? GrSamplerState::Filter::kLinear
307                                                    : GrSamplerState::Filter::kNearest);
308 
309     uint32_t flags = 0;
310     flags |= d->fRandom->nextBool() ? kSimilarity_DistanceFieldEffectFlag : 0;
311     if (flags & kSimilarity_DistanceFieldEffectFlag) {
312         flags |= d->fRandom->nextBool() ? kScaleOnly_DistanceFieldEffectFlag : 0;
313     }
314     SkMatrix localMatrix = GrTest::TestMatrix(d->fRandom);
315 #ifdef SK_GAMMA_APPLY_TO_A8
316     float lum = d->fRandom->nextF();
317 #endif
318     return GrDistanceFieldA8TextGeoProc::Make(d->allocator(), *d->caps()->shaderCaps(),
319                                               &view, 1,
320                                               samplerState,
321 #ifdef SK_GAMMA_APPLY_TO_A8
322                                               lum,
323 #endif
324                                               flags, localMatrix);
325 }
326 #endif
327 
328 ///////////////////////////////////////////////////////////////////////////////
329 
330 class GrDistanceFieldPathGeoProc::Impl : public ProgramImpl {
331 public:
setData(const GrGLSLProgramDataManager & pdman,const GrShaderCaps & shaderCaps,const GrGeometryProcessor & geomProc)332     void setData(const GrGLSLProgramDataManager& pdman,
333                  const GrShaderCaps& shaderCaps,
334                  const GrGeometryProcessor& geomProc) override {
335         const GrDistanceFieldPathGeoProc& dfpgp = geomProc.cast<GrDistanceFieldPathGeoProc>();
336 
337         // We always set the matrix uniform. It's used to transform from from device to local
338         // for the local coord variable.
339         SetTransform(pdman, shaderCaps, fLocalMatrixUniform, dfpgp.fLocalMatrix, &fLocalMatrix);
340 
341         const SkISize& atlasDimensions = dfpgp.fAtlasDimensions;
342         SkASSERT(SkIsPow2(atlasDimensions.fWidth) && SkIsPow2(atlasDimensions.fHeight));
343         if (fAtlasDimensions != atlasDimensions) {
344             pdman.set2f(fAtlasDimensionsInvUniform,
345                         1.0f / atlasDimensions.fWidth,
346                         1.0f / atlasDimensions.fHeight);
347             fAtlasDimensions = atlasDimensions;
348         }
349     }
350 
351 private:
onEmitCode(EmitArgs & args,GrGPArgs * gpArgs)352     void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override{
353         const GrDistanceFieldPathGeoProc& dfPathEffect =
354                 args.fGeomProc.cast<GrDistanceFieldPathGeoProc>();
355 
356         GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
357 
358         GrGLSLVertexBuilder* vertBuilder = args.fVertBuilder;
359         GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler;
360         GrGLSLUniformHandler* uniformHandler = args.fUniformHandler;
361 
362         // emit attributes
363         varyingHandler->emitAttributes(dfPathEffect);
364 
365         const char* atlasDimensionsInvName;
366         fAtlasDimensionsInvUniform = uniformHandler->addUniform(nullptr,
367                                                                 kVertex_GrShaderFlag,
368                                                                 SkSLType::kFloat2,
369                                                                 "AtlasDimensionsInv",
370                                                                 &atlasDimensionsInvName);
371 
372         GrGLSLVarying uv, texIdx, st;
373         append_index_uv_varyings(args,
374                                  dfPathEffect.numTextureSamplers(),
375                                  dfPathEffect.fInTextureCoords.name(),
376                                  atlasDimensionsInvName,
377                                  &uv,
378                                  &texIdx,
379                                  &st);
380 
381         // setup pass through color
382         fragBuilder->codeAppendf("half4 %s;", args.fOutputColor);
383         varyingHandler->addPassThroughAttribute(dfPathEffect.fInColor.asShaderVar(),
384                                                 args.fOutputColor);
385 
386         // Setup position (output position is pass through, local coords are transformed)
387         gpArgs->fPositionVar = dfPathEffect.fInPosition.asShaderVar();
388         WriteLocalCoord(vertBuilder,
389                         uniformHandler,
390                         *args.fShaderCaps,
391                         gpArgs,
392                         gpArgs->fPositionVar,
393                         dfPathEffect.fLocalMatrix,
394                         &fLocalMatrixUniform);
395 
396         // Use highp to work around aliasing issues
397         fragBuilder->codeAppendf("float2 uv = %s;", uv.fsIn());
398         fragBuilder->codeAppend("half4 texColor;");
399         append_multitexture_lookup(args, dfPathEffect.numTextureSamplers(), texIdx, "uv",
400                                    "texColor");
401 
402         fragBuilder->codeAppend("half distance = "
403             SK_DistanceFieldMultiplier "*(texColor.r - " SK_DistanceFieldThreshold ");");
404 
405         fragBuilder->codeAppend("half afwidth;");
406         bool isUniformScale = (dfPathEffect.fFlags & kUniformScale_DistanceFieldEffectMask) ==
407                                                      kUniformScale_DistanceFieldEffectMask;
408         bool isSimilarity   = SkToBool(dfPathEffect.fFlags & kSimilarity_DistanceFieldEffectFlag  );
409         bool isGammaCorrect = SkToBool(dfPathEffect.fFlags & kGammaCorrect_DistanceFieldEffectFlag);
410         if (isUniformScale) {
411             // For uniform scale, we adjust for the effect of the transformation on the distance
412             // by using the length of the gradient of the t coordinate in the y direction.
413             // We use st coordinates to ensure we're mapping 1:1 from texel space to pixel space.
414 
415             // this gives us a smooth step across approximately one fragment
416             if (args.fShaderCaps->fAvoidDfDxForGradientsWhenPossible) {
417                 fragBuilder->codeAppendf(
418                         "afwidth = abs(" SK_DistanceFieldAAFactor "*half(dFdy(%s.y)));", st.fsIn());
419             } else {
420                 fragBuilder->codeAppendf(
421                         "afwidth = abs(" SK_DistanceFieldAAFactor "*half(dFdx(%s.x)));", st.fsIn());
422             }
423         } else if (isSimilarity) {
424             // For similarity transform, we adjust the effect of the transformation on the distance
425             // by using the length of the gradient of the texture coordinates. We use st coordinates
426             // to ensure we're mapping 1:1 from texel space to pixel space.
427 
428             // this gives us a smooth step across approximately one fragment
429             if (args.fShaderCaps->fAvoidDfDxForGradientsWhenPossible) {
430                 fragBuilder->codeAppendf("half st_grad_len = half(length(dFdy(%s)));", st.fsIn());
431             } else {
432                 fragBuilder->codeAppendf("half st_grad_len = half(length(dFdx(%s)));", st.fsIn());
433             }
434             fragBuilder->codeAppend("afwidth = abs(" SK_DistanceFieldAAFactor "*st_grad_len);");
435         } else {
436             // For general transforms, to determine the amount of correction we multiply a unit
437             // vector pointing along the SDF gradient direction by the Jacobian of the st coords
438             // (which is the inverse transform for this fragment) and take the length of the result.
439             fragBuilder->codeAppend("half2 dist_grad = half2(dFdx(distance), "
440                                                             "dFdy(distance));");
441             // the length of the gradient may be 0, so we need to check for this
442             // this also compensates for the Adreno, which likes to drop tiles on division by 0
443             fragBuilder->codeAppend("half dg_len2 = dot(dist_grad, dist_grad);"
444                                     "if (dg_len2 < 0.0001) {"
445                                         "dist_grad = half2(0.7071, 0.7071);"
446                                     "} else {"
447                                         "dist_grad = dist_grad*half(inversesqrt(dg_len2));"
448                                     "}");
449 
450             fragBuilder->codeAppendf("float2x2 jacobian = float2x2(dFdx(%s), dFdy(%s));",
451                                      st.fsIn(), st.fsIn());
452             fragBuilder->codeAppend("half2 grad = half2(jacobian * dist_grad);");
453 
454             // this gives us a smooth step across approximately one fragment
455             fragBuilder->codeAppend("afwidth = " SK_DistanceFieldAAFactor "*length(grad);");
456         }
457         // The smoothstep falloff compensates for the non-linear sRGB response curve. If we are
458         // doing gamma-correct rendering (to an sRGB or F16 buffer), then we actually want distance
459         // mapped linearly to coverage, so use a linear step:
460         if (isGammaCorrect) {
461             fragBuilder->codeAppend(
462                 "half val = saturate((distance + afwidth) / (2.0 * afwidth));");
463         } else {
464             fragBuilder->codeAppend("half val = smoothstep(-afwidth, afwidth, distance);");
465         }
466 
467         fragBuilder->codeAppendf("half4 %s = half4(val);", args.fOutputCoverage);
468     }
469 
470     SkMatrix      fLocalMatrix;
471     UniformHandle fLocalMatrixUniform;
472 
473     SkISize       fAtlasDimensions;
474     UniformHandle fAtlasDimensionsInvUniform;
475 
476     using INHERITED = ProgramImpl;
477 };
478 
479 ///////////////////////////////////////////////////////////////////////////////
480 
GrDistanceFieldPathGeoProc(const GrShaderCaps & caps,const GrSurfaceProxyView * views,int numViews,GrSamplerState params,const SkMatrix & localMatrix,uint32_t flags)481 GrDistanceFieldPathGeoProc::GrDistanceFieldPathGeoProc(const GrShaderCaps& caps,
482                                                        const GrSurfaceProxyView* views,
483                                                        int numViews,
484                                                        GrSamplerState params,
485                                                        const SkMatrix& localMatrix,
486                                                        uint32_t flags)
487         : INHERITED(kGrDistanceFieldPathGeoProc_ClassID)
488         , fLocalMatrix(localMatrix)
489         , fFlags(flags & kPath_DistanceFieldEffectMask) {
490     SkASSERT(numViews <= kMaxTextures);
491     SkASSERT(!(flags & ~kPath_DistanceFieldEffectMask));
492 
493     fInPosition = {"inPosition", kFloat3_GrVertexAttribType, SkSLType::kFloat3};
494     fInColor = MakeColorAttribute("inColor", SkToBool(flags & kWideColor_DistanceFieldEffectFlag));
495     fInTextureCoords = {"inTextureCoords", kUShort2_GrVertexAttribType,
496                         caps.fIntegerSupport ? SkSLType::kUShort2 : SkSLType::kFloat2};
497     this->setVertexAttributesWithImplicitOffsets(&fInPosition, 3);
498 
499     if (numViews) {
500         fAtlasDimensions = views[0].proxy()->dimensions();
501     }
502 
503     for (int i = 0; i < numViews; ++i) {
504         const GrSurfaceProxy* proxy = views[i].proxy();
505         SkASSERT(proxy);
506         SkASSERT(proxy->dimensions() == fAtlasDimensions);
507         fTextureSamplers[i].reset(params, proxy->backendFormat(), views[i].swizzle());
508     }
509     this->setTextureSamplerCnt(numViews);
510 }
511 
addNewViews(const GrSurfaceProxyView * views,int numViews,GrSamplerState params)512 void GrDistanceFieldPathGeoProc::addNewViews(const GrSurfaceProxyView* views,
513                                              int numViews,
514                                              GrSamplerState params) {
515     SkASSERT(numViews <= kMaxTextures);
516     // Just to make sure we don't try to add too many proxies
517     numViews = std::min(numViews, kMaxTextures);
518 
519     if (!fTextureSamplers[0].isInitialized()) {
520         fAtlasDimensions = views[0].proxy()->dimensions();
521     }
522 
523     for (int i = 0; i < numViews; ++i) {
524         const GrSurfaceProxy* proxy = views[i].proxy();
525         SkASSERT(proxy);
526         SkASSERT(proxy->dimensions() == fAtlasDimensions);
527         if (!fTextureSamplers[i].isInitialized()) {
528             fTextureSamplers[i].reset(params, proxy->backendFormat(), views[i].swizzle());
529         }
530     }
531     this->setTextureSamplerCnt(numViews);
532 }
533 
addToKey(const GrShaderCaps & caps,skgpu::KeyBuilder * b) const534 void GrDistanceFieldPathGeoProc::addToKey(const GrShaderCaps& caps,
535                                           skgpu::KeyBuilder* b) const {
536     uint32_t key = fFlags;
537     key |= ProgramImpl::ComputeMatrixKey(caps, fLocalMatrix) << 16;
538     key |= fLocalMatrix.hasPerspective() << (16 + ProgramImpl::kMatrixKeyBits);
539     b->add32(key);
540     b->add32(this->numTextureSamplers());
541 }
542 
makeProgramImpl(const GrShaderCaps &) const543 std::unique_ptr<GrGeometryProcessor::ProgramImpl> GrDistanceFieldPathGeoProc::makeProgramImpl(
544         const GrShaderCaps&) const {
545     return std::make_unique<Impl>();
546 }
547 
548 ///////////////////////////////////////////////////////////////////////////////
549 
GR_DEFINE_GEOMETRY_PROCESSOR_TEST(GrDistanceFieldPathGeoProc)550 GR_DEFINE_GEOMETRY_PROCESSOR_TEST(GrDistanceFieldPathGeoProc)
551 
552 #if defined(GPU_TEST_UTILS)
553 GrGeometryProcessor* GrDistanceFieldPathGeoProc::TestCreate(GrProcessorTestData* d) {
554     auto [view, ct, at] = d->randomAlphaOnlyView();
555 
556     GrSamplerState::WrapMode wrapModes[2];
557     GrTest::TestWrapModes(d->fRandom, wrapModes);
558     GrSamplerState samplerState(wrapModes, d->fRandom->nextBool()
559                                                    ? GrSamplerState::Filter::kLinear
560                                                    : GrSamplerState::Filter::kNearest);
561 
562     uint32_t flags = 0;
563     flags |= d->fRandom->nextBool() ? kSimilarity_DistanceFieldEffectFlag : 0;
564     if (flags & kSimilarity_DistanceFieldEffectFlag) {
565         flags |= d->fRandom->nextBool() ? kScaleOnly_DistanceFieldEffectFlag : 0;
566     }
567     flags |= d->fRandom->nextBool() ? kWideColor_DistanceFieldEffectFlag : 0;
568     SkMatrix localMatrix = GrTest::TestMatrix(d->fRandom);
569     return GrDistanceFieldPathGeoProc::Make(d->allocator(), *d->caps()->shaderCaps(),
570                                             &view, 1,
571                                             samplerState,
572                                             localMatrix,
573                                             flags);
574 }
575 #endif
576 
577 
578 ///////////////////////////////////////////////////////////////////////////////
579 
580 class GrDistanceFieldLCDTextGeoProc::Impl : public ProgramImpl {
581 public:
setData(const GrGLSLProgramDataManager & pdman,const GrShaderCaps & shaderCaps,const GrGeometryProcessor & geomProc)582     void setData(const GrGLSLProgramDataManager& pdman,
583                  const GrShaderCaps& shaderCaps,
584                  const GrGeometryProcessor& geomProc) override {
585         SkASSERT(fDistanceAdjustUni.isValid());
586 
587         const GrDistanceFieldLCDTextGeoProc& dflcd = geomProc.cast<GrDistanceFieldLCDTextGeoProc>();
588         GrDistanceFieldLCDTextGeoProc::DistanceAdjust wa = dflcd.fDistanceAdjust;
589         if (wa != fDistanceAdjust) {
590             pdman.set3f(fDistanceAdjustUni, wa.fR, wa.fG, wa.fB);
591             fDistanceAdjust = wa;
592         }
593 
594         const SkISize& atlasDimensions = dflcd.fAtlasDimensions;
595         SkASSERT(SkIsPow2(atlasDimensions.fWidth) && SkIsPow2(atlasDimensions.fHeight));
596         if (fAtlasDimensions != atlasDimensions) {
597             pdman.set2f(fAtlasDimensionsInvUniform,
598                         1.0f / atlasDimensions.fWidth,
599                         1.0f / atlasDimensions.fHeight);
600             fAtlasDimensions = atlasDimensions;
601         }
602         SetTransform(pdman, shaderCaps, fLocalMatrixUniform, dflcd.fLocalMatrix, &fLocalMatrix);
603     }
604 
605 private:
onEmitCode(EmitArgs & args,GrGPArgs * gpArgs)606     void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override {
607         const GrDistanceFieldLCDTextGeoProc& dfTexEffect =
608                 args.fGeomProc.cast<GrDistanceFieldLCDTextGeoProc>();
609 
610         GrGLSLVertexBuilder* vertBuilder = args.fVertBuilder;
611         GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler;
612         GrGLSLUniformHandler* uniformHandler = args.fUniformHandler;
613 
614         // emit attributes
615         varyingHandler->emitAttributes(dfTexEffect);
616 
617         const char* atlasDimensionsInvName;
618         fAtlasDimensionsInvUniform = uniformHandler->addUniform(nullptr,
619                                                                 kVertex_GrShaderFlag,
620                                                                 SkSLType::kFloat2,
621                                                                 "AtlasDimensionsInv",
622                                                                 &atlasDimensionsInvName);
623 
624         GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
625 
626         // setup pass through color
627         fragBuilder->codeAppendf("half4 %s;\n", args.fOutputColor);
628         varyingHandler->addPassThroughAttribute(dfTexEffect.fInColor.asShaderVar(),
629                                                 args.fOutputColor);
630 
631         // Setup position
632         gpArgs->fPositionVar = dfTexEffect.fInPosition.asShaderVar();
633         WriteLocalCoord(vertBuilder,
634                         uniformHandler,
635                         *args.fShaderCaps,
636                         gpArgs,
637                         dfTexEffect.fInPosition.asShaderVar(),
638                         dfTexEffect.fLocalMatrix,
639                         &fLocalMatrixUniform);
640 
641         // set up varyings
642         GrGLSLVarying uv, texIdx, st;
643         append_index_uv_varyings(args,
644                                  dfTexEffect.numTextureSamplers(),
645                                  dfTexEffect.fInTextureCoords.name(),
646                                  atlasDimensionsInvName,
647                                  &uv,
648                                  &texIdx,
649                                  &st);
650 
651         GrGLSLVarying delta(SkSLType::kFloat);
652         varyingHandler->addVarying("Delta", &delta);
653         if (dfTexEffect.fFlags & kPortrait_DistanceFieldEffectFlag) {
654             if (dfTexEffect.fFlags & kBGR_DistanceFieldEffectFlag) {
655                 vertBuilder->codeAppendf("%s = -%s.y/3.0;", delta.vsOut(), atlasDimensionsInvName);
656             } else {
657                 vertBuilder->codeAppendf("%s = %s.y/3.0;", delta.vsOut(), atlasDimensionsInvName);
658             }
659         } else {
660             if (dfTexEffect.fFlags & kBGR_DistanceFieldEffectFlag) {
661                 vertBuilder->codeAppendf("%s = -%s.x/3.0;", delta.vsOut(), atlasDimensionsInvName);
662             } else {
663                 vertBuilder->codeAppendf("%s = %s.x/3.0;", delta.vsOut(), atlasDimensionsInvName);
664             }
665         }
666 
667         // add frag shader code
668         bool isUniformScale = (dfTexEffect.fFlags & kUniformScale_DistanceFieldEffectMask) ==
669                                                     kUniformScale_DistanceFieldEffectMask;
670         bool isSimilarity   = SkToBool(dfTexEffect.fFlags & kSimilarity_DistanceFieldEffectFlag  );
671         bool isGammaCorrect = SkToBool(dfTexEffect.fFlags & kGammaCorrect_DistanceFieldEffectFlag);
672 
673         // create LCD offset adjusted by inverse of transform
674         // Use highp to work around aliasing issues
675         fragBuilder->codeAppendf("float2 uv = %s;\n", uv.fsIn());
676 
677         if (isUniformScale) {
678             if (args.fShaderCaps->fAvoidDfDxForGradientsWhenPossible) {
679                 fragBuilder->codeAppendf("half st_grad_len = half(abs(dFdy(%s.y)));", st.fsIn());
680             } else {
681                 fragBuilder->codeAppendf("half st_grad_len = half(abs(dFdx(%s.x)));", st.fsIn());
682             }
683             if (dfTexEffect.fFlags & kPortrait_DistanceFieldEffectFlag) {
684                 fragBuilder->codeAppendf("half2 offset = half2(0.0, half(st_grad_len*%s));",
685                                          delta.fsIn());
686             } else {
687                 fragBuilder->codeAppendf("half2 offset = half2(half(st_grad_len*%s), 0.0);",
688                                          delta.fsIn());
689             }
690         } else if (isSimilarity) {
691             // For a similarity matrix with rotation, the gradient will not be aligned
692             // with the texel coordinate axes, so we need to calculate it.
693             if (args.fShaderCaps->fAvoidDfDxForGradientsWhenPossible) {
694                 // We use dFdy instead and rotate -90 degrees to get the gradient in the x
695                 // direction.
696                 fragBuilder->codeAppendf("half2 st_grad = half2(dFdy(%s));", st.fsIn());
697                 if (dfTexEffect.fFlags & kPortrait_DistanceFieldEffectFlag) {
698                     fragBuilder->codeAppendf("half2 offset = half2(%s)*st_grad;", delta.fsIn());
699                 } else {
700                     fragBuilder->codeAppendf(
701                             "half2 offset = half2(%s*float2(st_grad.y,-st_grad.x));", delta.fsIn());
702                 }
703             } else {
704                 fragBuilder->codeAppendf("half2 st_grad = half2(dFdx(%s));", st.fsIn());
705                 if (dfTexEffect.fFlags & kPortrait_DistanceFieldEffectFlag) {
706                     fragBuilder->codeAppendf(
707                             "half2 offset = half2(%s*float2(-st_grad.y,st_grad.x));", delta.fsIn());
708                 } else {
709                     fragBuilder->codeAppendf("half2 offset = half(%s)*st_grad;", delta.fsIn());
710                 }
711             }
712             fragBuilder->codeAppend("half st_grad_len = length(st_grad);");
713         } else {
714             fragBuilder->codeAppendf("half2 st = half2(%s);\n", st.fsIn());
715 
716             fragBuilder->codeAppend("float2x2 jacobian = float2x2(dFdx(st), dFdy(st));");
717             if (dfTexEffect.fFlags & kPortrait_DistanceFieldEffectFlag) {
718                 fragBuilder->codeAppendf("half2 offset = half2(jacobian * half2(0, %s));",
719                                          delta.fsIn());
720             } else {
721                 fragBuilder->codeAppendf("half2 offset = half2(jacobian * half2(%s, 0));",
722                                          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(jacobian * dist_grad);");
767 
768             // this gives us a smooth step across approximately one fragment
769             fragBuilder->codeAppend("afwidth = " SK_DistanceFieldAAFactor "*length(grad);");
770         }
771 
772         // The smoothstep falloff compensates for the non-linear sRGB response curve. If we are
773         // doing gamma-correct rendering (to an sRGB or F16 buffer), then we actually want distance
774         // mapped linearly to coverage, so use a linear step:
775         if (isGammaCorrect) {
776             fragBuilder->codeAppendf("half4 %s = "
777                     "half4(saturate((distance + half3(afwidth)) / half3(2.0 * afwidth)), 1.0);",
778                     args.fOutputCoverage);
779         } else {
780             fragBuilder->codeAppendf(
781                     "half4 %s = half4(smoothstep(half3(-afwidth), half3(afwidth), distance), 1.0);",
782                     args.fOutputCoverage);
783         }
784     }
785 
786 private:
787     DistanceAdjust fDistanceAdjust  = DistanceAdjust::Make(1.0f, 1.0f, 1.0f);
788     SkISize        fAtlasDimensions = {-1, -1};
789     SkMatrix       fLocalMatrix     = SkMatrix::InvalidMatrix();
790 
791     UniformHandle fDistanceAdjustUni;
792     UniformHandle fAtlasDimensionsInvUniform;
793     UniformHandle fLocalMatrixUniform;
794 };
795 
796 ///////////////////////////////////////////////////////////////////////////////
797 
GrDistanceFieldLCDTextGeoProc(const GrShaderCaps & caps,const GrSurfaceProxyView * views,int numViews,GrSamplerState params,DistanceAdjust distanceAdjust,uint32_t flags,const SkMatrix & localMatrix)798 GrDistanceFieldLCDTextGeoProc::GrDistanceFieldLCDTextGeoProc(const GrShaderCaps& caps,
799                                                              const GrSurfaceProxyView* views,
800                                                              int numViews,
801                                                              GrSamplerState params,
802                                                              DistanceAdjust distanceAdjust,
803                                                              uint32_t flags,
804                                                              const SkMatrix& localMatrix)
805         : INHERITED(kGrDistanceFieldLCDTextGeoProc_ClassID)
806         , fLocalMatrix(localMatrix)
807         , fDistanceAdjust(distanceAdjust)
808         , fFlags(flags & kLCD_DistanceFieldEffectMask) {
809     SkASSERT(numViews <= kMaxTextures);
810     SkASSERT(!(flags & ~kLCD_DistanceFieldEffectMask) && (flags & kUseLCD_DistanceFieldEffectFlag));
811 
812     if (fFlags & kPerspective_DistanceFieldEffectFlag) {
813         fInPosition = {"inPosition", kFloat3_GrVertexAttribType, SkSLType::kFloat3};
814     } else {
815         fInPosition = {"inPosition", kFloat2_GrVertexAttribType, SkSLType::kFloat2};
816     }
817     fInColor = {"inColor", kUByte4_norm_GrVertexAttribType, SkSLType::kHalf4};
818     fInTextureCoords = {"inTextureCoords", kUShort2_GrVertexAttribType,
819                         caps.fIntegerSupport ? SkSLType::kUShort2 : SkSLType::kFloat2};
820     this->setVertexAttributesWithImplicitOffsets(&fInPosition, 3);
821 
822     if (numViews) {
823         fAtlasDimensions = views[0].proxy()->dimensions();
824     }
825 
826     for (int i = 0; i < numViews; ++i) {
827         const GrSurfaceProxy* proxy = views[i].proxy();
828         SkASSERT(proxy);
829         SkASSERT(proxy->dimensions() == fAtlasDimensions);
830         fTextureSamplers[i].reset(params, proxy->backendFormat(), views[i].swizzle());
831     }
832     this->setTextureSamplerCnt(numViews);
833 }
834 
addNewViews(const GrSurfaceProxyView * views,int numViews,GrSamplerState params)835 void GrDistanceFieldLCDTextGeoProc::addNewViews(const GrSurfaceProxyView* views,
836                                                 int numViews,
837                                                 GrSamplerState params) {
838     SkASSERT(numViews <= kMaxTextures);
839     // Just to make sure we don't try to add too many proxies
840     numViews = std::min(numViews, kMaxTextures);
841 
842     if (!fTextureSamplers[0].isInitialized()) {
843         fAtlasDimensions = views[0].proxy()->dimensions();
844     }
845 
846     for (int i = 0; i < numViews; ++i) {
847         const GrSurfaceProxy* proxy = views[i].proxy();
848         SkASSERT(proxy);
849         SkASSERT(proxy->dimensions() == fAtlasDimensions);
850         if (!fTextureSamplers[i].isInitialized()) {
851             fTextureSamplers[i].reset(params, proxy->backendFormat(), views[i].swizzle());
852         }
853     }
854     this->setTextureSamplerCnt(numViews);
855 }
856 
addToKey(const GrShaderCaps & caps,skgpu::KeyBuilder * b) const857 void GrDistanceFieldLCDTextGeoProc::addToKey(const GrShaderCaps& caps,
858                                              skgpu::KeyBuilder* b) const {
859     uint32_t key = 0;
860     key |= ProgramImpl::ComputeMatrixKey(caps, fLocalMatrix);
861     key |= fFlags << 16;
862     b->add32(key);
863     b->add32(this->numTextureSamplers());
864 }
865 
makeProgramImpl(const GrShaderCaps &) const866 std::unique_ptr<GrGeometryProcessor::ProgramImpl> GrDistanceFieldLCDTextGeoProc::makeProgramImpl(
867         const GrShaderCaps&) const {
868     return std::make_unique<Impl>();
869 }
870 
871 ///////////////////////////////////////////////////////////////////////////////
872 
GR_DEFINE_GEOMETRY_PROCESSOR_TEST(GrDistanceFieldLCDTextGeoProc)873 GR_DEFINE_GEOMETRY_PROCESSOR_TEST(GrDistanceFieldLCDTextGeoProc)
874 
875 #if defined(GPU_TEST_UTILS)
876 GrGeometryProcessor* GrDistanceFieldLCDTextGeoProc::TestCreate(GrProcessorTestData* d) {
877     auto [view, ct, at] = d->randomView();
878 
879     GrSamplerState::WrapMode wrapModes[2];
880     GrTest::TestWrapModes(d->fRandom, wrapModes);
881     GrSamplerState samplerState(wrapModes, d->fRandom->nextBool()
882                                                    ? GrSamplerState::Filter::kLinear
883                                                    : GrSamplerState::Filter::kNearest);
884     DistanceAdjust wa = { 0.0f, 0.1f, -0.1f };
885     uint32_t flags = kUseLCD_DistanceFieldEffectFlag;
886     flags |= d->fRandom->nextBool() ? kSimilarity_DistanceFieldEffectFlag : 0;
887     if (flags & kSimilarity_DistanceFieldEffectFlag) {
888         flags |= d->fRandom->nextBool() ? kScaleOnly_DistanceFieldEffectFlag : 0;
889     }
890     flags |= d->fRandom->nextBool() ? kBGR_DistanceFieldEffectFlag : 0;
891     SkMatrix localMatrix = GrTest::TestMatrix(d->fRandom);
892 
893     return GrDistanceFieldLCDTextGeoProc::Make(d->allocator(), *d->caps()->shaderCaps(), &view,
894                                                1, samplerState, wa, flags, localMatrix);
895 }
896 #endif
897 
898 #endif // !defined(SK_DISABLE_SDF_TEXT)
899