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