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