• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2017 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/GrTextureEffect.h"
9 
10 #include "src/core/SkMatrixPriv.h"
11 #include "src/gpu/KeyBuilder.h"
12 #include "src/gpu/ganesh/GrTexture.h"
13 #include "src/gpu/ganesh/effects/GrMatrixEffect.h"
14 #include "src/gpu/ganesh/glsl/GrGLSLProgramBuilder.h"
15 #include "src/sksl/SkSLUtil.h"
16 
17 using Wrap = GrSamplerState::WrapMode;
18 using Filter = GrSamplerState::Filter;
19 using MipmapMode = GrSamplerState::MipmapMode;
20 
21 struct GrTextureEffect::Sampling {
22     GrSamplerState fHWSampler;
23     ShaderMode fShaderModes[2] = {ShaderMode::kNone, ShaderMode::kNone};
24     SkRect fShaderSubset = {0, 0, 0, 0};
25     SkRect fShaderClamp  = {0, 0, 0, 0};
26     float fBorder[4] = {0, 0, 0, 0};
SamplingGrTextureEffect::Sampling27     Sampling(Filter filter, MipmapMode mm) : fHWSampler(filter, mm) {}
28     Sampling(const GrSurfaceProxy& proxy,
29              GrSamplerState wrap,
30              const SkRect&,
31              const SkRect*,
32              const float border[4],
33              bool alwaysUseShaderTileMode,
34              const GrCaps&,
35              SkVector linearFilterInset = {0.5f, 0.5f});
36     inline bool hasBorderAlpha() const;
37 };
38 
Sampling(const GrSurfaceProxy & proxy,GrSamplerState sampler,const SkRect & subset,const SkRect * domain,const float border[4],bool alwaysUseShaderTileMode,const GrCaps & caps,SkVector linearFilterInset)39 GrTextureEffect::Sampling::Sampling(const GrSurfaceProxy& proxy,
40                                     GrSamplerState sampler,
41                                     const SkRect& subset,
42                                     const SkRect* domain,
43                                     const float border[4],
44                                     bool alwaysUseShaderTileMode,
45                                     const GrCaps& caps,
46                                     SkVector linearFilterInset) {
47     struct Span {
48         float fA = 0.f, fB = 0.f;
49 
50         Span makeInset(float o) const {
51             Span r = {fA + o, fB - o};
52             if (r.fA > r.fB) {
53                 r.fA = r.fB = (r.fA + r.fB) / 2;
54             }
55             return r;
56         }
57 
58         bool contains(Span r) const { return fA <= r.fA && fB >= r.fB; }
59     };
60     struct Result1D {
61         ShaderMode fShaderMode = ShaderMode::kNone;
62         Span fShaderSubset     = {};
63         Span fShaderClamp      = {};
64         Wrap fHWWrap           = Wrap::kClamp;
65     };
66 
67     auto type   = proxy.asTextureProxy()->textureType();
68     auto filter = sampler.filter();
69     auto mm     = sampler.mipmapMode();
70 
71     auto canDoWrapInHW = [&](int size, Wrap wrap) {
72         if (alwaysUseShaderTileMode) {
73             return false;
74         }
75         // TODO: Use HW border color when available.
76         if (wrap == Wrap::kClampToBorder &&
77             (!caps.clampToBorderSupport() || border[0] || border[1] || border[2] || border[3])) {
78             return false;
79         }
80         if (wrap != Wrap::kClamp && !caps.npotTextureTileSupport() && !SkIsPow2(size)) {
81             return false;
82         }
83         if (type != GrTextureType::k2D &&
84                    !(wrap == Wrap::kClamp || wrap == Wrap::kClampToBorder)) {
85             return false;
86         }
87         return true;
88     };
89 
90     SkISize dim = proxy.isFullyLazy() ? SkISize{-1, -1} : proxy.backingStoreDimensions();
91 
92     // TODO: Right now if we use shader based subsetting for any reason we just completely drop
93     // aniso. Longer term allow shader subsetting, reusing the special repeat mode LOD selection
94     // logic for mip maps, and simply don't attempt to restrict ansiso's computed samples to the
95     // subset. That is use "subsetting" but not "clamping"/insetting in terms of the shader gen
96     // logic.
97     bool aniso = sampler.isAniso();
98     SkASSERT(!aniso || caps.anisoSupport());
99     if (aniso) {
100         bool anisoSubset = !proxy.backingStoreBoundsRect().contains(subset) &&
101                            (!domain || !subset.contains(*domain));
102         bool needsShaderWrap = !canDoWrapInHW(dim.width(),  sampler.wrapModeX()) ||
103                                !canDoWrapInHW(dim.height(), sampler.wrapModeY());
104         if (needsShaderWrap || anisoSubset) {
105             MipmapMode newMM = proxy.asTextureProxy()->mipmapped() == GrMipmapped::kYes
106                                     ? MipmapMode::kLinear
107                                     : MipmapMode::kNone;
108             sampler = GrSamplerState(sampler.wrapModeX(),
109                                      sampler.wrapModeY(),
110                                      SkFilterMode::kLinear,
111                                      newMM);
112             aniso = false;
113         }
114     }
115 
116     auto resolve = [&](int size, Wrap wrap, Span subset, Span domain, float linearFilterInset) {
117         Result1D r;
118         bool canDoModeInHW = canDoWrapInHW(size, wrap);
119         if (canDoModeInHW && size > 0 && subset.fA <= 0 && subset.fB >= size) {
120             r.fShaderMode = ShaderMode::kNone;
121             r.fHWWrap = wrap;
122             r.fShaderSubset = r.fShaderClamp = {0, 0};
123             return r;
124         }
125 
126         r.fShaderSubset = subset;
127         bool domainIsSafe = false;
128         if (filter == Filter::kNearest) {
129             Span isubset{sk_float_floor(subset.fA), sk_float_ceil(subset.fB)};
130             if (domain.fA > isubset.fA && domain.fB < isubset.fB) {
131                 domainIsSafe = true;
132             }
133             // This inset prevents sampling neighboring texels that could occur when
134             // texture coords fall exactly at texel boundaries (depending on precision
135             // and GPU-specific snapping at the boundary).
136             r.fShaderClamp = isubset.makeInset(0.5f);
137         } else {
138             r.fShaderClamp = subset.makeInset(linearFilterInset);
139             if (r.fShaderClamp.contains(domain)) {
140                 domainIsSafe = true;
141             }
142         }
143         if (!alwaysUseShaderTileMode && domainIsSafe) {
144             // The domain of coords that will be used won't access texels outside of the subset.
145             // So the wrap mode effectively doesn't matter. We use kClamp since it is always
146             // supported.
147             r.fShaderMode = ShaderMode::kNone;
148             r.fHWWrap = Wrap::kClamp;
149             r.fShaderSubset = r.fShaderClamp = {0, 0};
150             return r;
151         }
152         r.fShaderMode = GetShaderMode(wrap, filter, mm);
153         r.fHWWrap = Wrap::kClamp;
154         return r;
155     };
156 
157     Result1D x, y;
158     if (!aniso) {
159         Span subsetX{subset.fLeft, subset.fRight};
160         auto domainX = domain ? Span{domain->fLeft, domain->fRight}
161                               : Span{SK_FloatNegativeInfinity, SK_FloatInfinity};
162         x = resolve(dim.width(), sampler.wrapModeX(), subsetX, domainX, linearFilterInset.fX);
163 
164         Span subsetY{subset.fTop, subset.fBottom};
165         auto domainY = domain ? Span{domain->fTop, domain->fBottom}
166                               : Span{SK_FloatNegativeInfinity, SK_FloatInfinity};
167         y = resolve(dim.height(), sampler.wrapModeY(), subsetY, domainY, linearFilterInset.fY);
168     } else {
169         x.fHWWrap = sampler.wrapModeX();
170         y.fHWWrap = sampler.wrapModeY();
171     }
172 
173     fHWSampler = aniso ? GrSamplerState::Aniso(x.fHWWrap,
174                                                y.fHWWrap,
175                                                sampler.maxAniso(),
176                                                proxy.asTextureProxy()->mipmapped())
177                        : GrSamplerState{x.fHWWrap, y.fHWWrap, filter, mm};
178     fShaderModes[0] = x.fShaderMode;
179     fShaderModes[1] = y.fShaderMode;
180     fShaderSubset = {x.fShaderSubset.fA, y.fShaderSubset.fA,
181                      x.fShaderSubset.fB, y.fShaderSubset.fB};
182     fShaderClamp = {x.fShaderClamp.fA, y.fShaderClamp.fA,
183                     x.fShaderClamp.fB, y.fShaderClamp.fB};
184     std::copy_n(border, 4, fBorder);
185 }
186 
hasBorderAlpha() const187 bool GrTextureEffect::Sampling::hasBorderAlpha() const {
188     if (fHWSampler.wrapModeX() == Wrap::kClampToBorder ||
189         fHWSampler.wrapModeY() == Wrap::kClampToBorder) {
190         return true;
191     }
192     if (ShaderModeIsClampToBorder(fShaderModes[0]) || ShaderModeIsClampToBorder(fShaderModes[1])) {
193         return fBorder[3] < 1.f;
194     }
195     return false;
196 }
197 
Make(GrSurfaceProxyView view,SkAlphaType alphaType,const SkMatrix & matrix,Filter filter,MipmapMode mm)198 std::unique_ptr<GrFragmentProcessor> GrTextureEffect::Make(GrSurfaceProxyView view,
199                                                            SkAlphaType alphaType,
200                                                            const SkMatrix& matrix,
201                                                            Filter filter,
202                                                            MipmapMode mm) {
203     Sampling sampling = Sampling(filter, mm);
204     std::unique_ptr<GrFragmentProcessor> te(new GrTextureEffect(std::move(view),
205                                                                 alphaType,
206                                                                 sampling));
207     return GrMatrixEffect::Make(matrix, std::move(te));
208 }
209 
Make(GrSurfaceProxyView view,SkAlphaType alphaType,const SkMatrix & matrix,GrSamplerState sampler,const GrCaps & caps,const float border[4])210 std::unique_ptr<GrFragmentProcessor> GrTextureEffect::Make(GrSurfaceProxyView view,
211                                                            SkAlphaType alphaType,
212                                                            const SkMatrix& matrix,
213                                                            GrSamplerState sampler,
214                                                            const GrCaps& caps,
215                                                            const float border[4]) {
216     Sampling sampling(*view.proxy(),
217                       sampler,
218                       SkRect::Make(view.proxy()->dimensions()),
219                       nullptr,
220                       border,
221                       false,
222                       caps);
223     std::unique_ptr<GrFragmentProcessor> te(new GrTextureEffect(std::move(view),
224                                                                 alphaType,
225                                                                 sampling));
226     return GrMatrixEffect::Make(matrix, std::move(te));
227 }
228 
MakeSubset(GrSurfaceProxyView view,SkAlphaType alphaType,const SkMatrix & matrix,GrSamplerState sampler,const SkRect & subset,const GrCaps & caps,const float border[4],bool alwaysUseShaderTileMode)229 std::unique_ptr<GrFragmentProcessor> GrTextureEffect::MakeSubset(GrSurfaceProxyView view,
230                                                                  SkAlphaType alphaType,
231                                                                  const SkMatrix& matrix,
232                                                                  GrSamplerState sampler,
233                                                                  const SkRect& subset,
234                                                                  const GrCaps& caps,
235                                                                  const float border[4],
236                                                                  bool alwaysUseShaderTileMode) {
237     Sampling sampling(*view.proxy(),
238                       sampler,
239                       subset,
240                       nullptr,
241                       border,
242                       alwaysUseShaderTileMode,
243                       caps);
244     std::unique_ptr<GrFragmentProcessor> te(new GrTextureEffect(std::move(view),
245                                                                 alphaType,
246                                                                 sampling));
247     return GrMatrixEffect::Make(matrix, std::move(te));
248 }
249 
MakeSubset(GrSurfaceProxyView view,SkAlphaType alphaType,const SkMatrix & matrix,GrSamplerState sampler,const SkRect & subset,const SkRect & domain,const GrCaps & caps,const float border[4])250 std::unique_ptr<GrFragmentProcessor> GrTextureEffect::MakeSubset(GrSurfaceProxyView view,
251                                                                  SkAlphaType alphaType,
252                                                                  const SkMatrix& matrix,
253                                                                  GrSamplerState sampler,
254                                                                  const SkRect& subset,
255                                                                  const SkRect& domain,
256                                                                  const GrCaps& caps,
257                                                                  const float border[4]) {
258     Sampling sampling(*view.proxy(), sampler, subset, &domain, border, false, caps);
259     std::unique_ptr<GrFragmentProcessor> te(new GrTextureEffect(std::move(view),
260                                                                 alphaType,
261                                                                 sampling));
262     return GrMatrixEffect::Make(matrix, std::move(te));
263 }
264 
MakeCustomLinearFilterInset(GrSurfaceProxyView view,SkAlphaType alphaType,const SkMatrix & matrix,Wrap wx,Wrap wy,const SkRect & subset,const SkRect * domain,SkVector inset,const GrCaps & caps,const float border[4])265 std::unique_ptr<GrFragmentProcessor> GrTextureEffect::MakeCustomLinearFilterInset(
266         GrSurfaceProxyView view,
267         SkAlphaType alphaType,
268         const SkMatrix& matrix,
269         Wrap wx,
270         Wrap wy,
271         const SkRect& subset,
272         const SkRect* domain,
273         SkVector inset,
274         const GrCaps& caps,
275         const float border[4]) {
276     GrSamplerState sampler(wx, wy, Filter::kLinear);
277     Sampling sampling(*view.proxy(), sampler, subset, domain, border, false, caps, inset);
278     std::unique_ptr<GrFragmentProcessor> te(new GrTextureEffect(std::move(view),
279                                                                 alphaType,
280                                                                 sampling));
281     return GrMatrixEffect::Make(matrix, std::move(te));
282 }
283 
coordAdjustmentMatrix() const284 SkMatrix GrTextureEffect::coordAdjustmentMatrix() const {
285     SkMatrix m;
286     GrTexture* texture = this->texture();
287     SkISize d = texture->dimensions();
288     if (this->matrixEffectShouldNormalize()) {
289         if (fView.origin() == kBottomLeft_GrSurfaceOrigin) {
290             m.setScaleTranslate(1.f / d.width(), -1.f / d.height(), 0, 1);
291         } else {
292             m.setScale(1.f / d.width(), 1.f / d.height());
293         }
294     } else {
295         if (fView.origin() == kBottomLeft_GrSurfaceOrigin) {
296             m.setScaleTranslate(1.f, -1.f, 0, d.height());
297         }
298     }
299     return m;
300 }
301 
GetShaderMode(Wrap wrap,Filter filter,MipmapMode mm)302 GrTextureEffect::ShaderMode GrTextureEffect::GetShaderMode(Wrap wrap,
303                                                            Filter filter,
304                                                            MipmapMode mm) {
305     switch (wrap) {
306         case Wrap::kMirrorRepeat:
307             return ShaderMode::kMirrorRepeat;
308         case Wrap::kClamp:
309             return ShaderMode::kClamp;
310         case Wrap::kRepeat:
311             switch (mm) {
312                 case MipmapMode::kNone:
313                     switch (filter) {
314                         case Filter::kNearest: return ShaderMode::kRepeat_Nearest_None;
315                         case Filter::kLinear:  return ShaderMode::kRepeat_Linear_None;
316                     }
317                     SkUNREACHABLE;
318                 case MipmapMode::kNearest:
319                 case MipmapMode::kLinear:
320                     switch (filter) {
321                         case Filter::kNearest: return ShaderMode::kRepeat_Nearest_Mipmap;
322                         case Filter::kLinear:  return ShaderMode::kRepeat_Linear_Mipmap;
323                     }
324                     SkUNREACHABLE;
325             }
326             SkUNREACHABLE;
327         case Wrap::kClampToBorder:
328             return filter == Filter::kNearest ? ShaderMode::kClampToBorder_Nearest
329                                               : ShaderMode::kClampToBorder_Filter;
330     }
331     SkUNREACHABLE;
332 }
333 
ShaderModeIsClampToBorder(ShaderMode m)334 inline bool GrTextureEffect::ShaderModeIsClampToBorder(ShaderMode m) {
335     return m == ShaderMode::kClampToBorder_Nearest || m == ShaderMode::kClampToBorder_Filter;
336 }
337 
ShaderModeRequiresUnormCoord(ShaderMode m)338 bool GrTextureEffect::ShaderModeRequiresUnormCoord(ShaderMode m) {
339     switch (m) {
340         case ShaderMode::kNone:                     return false;
341         case ShaderMode::kClamp:                    return false;
342         case ShaderMode::kRepeat_Nearest_None:      return false;
343         case ShaderMode::kRepeat_Linear_None:       return true;
344         case ShaderMode::kRepeat_Nearest_Mipmap:    return true;
345         case ShaderMode::kRepeat_Linear_Mipmap:     return true;
346         case ShaderMode::kMirrorRepeat:             return false;
347         case ShaderMode::kClampToBorder_Nearest:    return true;
348         case ShaderMode::kClampToBorder_Filter:     return true;
349     }
350     SkUNREACHABLE;
351 }
352 
emitCode(EmitArgs & args)353 void GrTextureEffect::Impl::emitCode(EmitArgs& args) {
354     using ShaderMode = GrTextureEffect::ShaderMode;
355 
356     auto& te = args.fFp.cast<GrTextureEffect>();
357     auto* fb = args.fFragBuilder;
358 
359     if (te.fShaderModes[0] == ShaderMode::kNone &&
360         te.fShaderModes[1] == ShaderMode::kNone) {
361         fb->codeAppendf("return ");
362         fb->appendTextureLookup(fSamplerHandle, args.fSampleCoord);
363         fb->codeAppendf(";");
364     } else {
365         // Here is the basic flow of the various ShaderModes are implemented in a series of
366         // steps. Not all the steps apply to all the modes. We try to emit only the steps
367         // that are necessary for the given x/y shader modes.
368         //
369         // 0) Start with interpolated coordinates (unnormalize if doing anything
370         //    complicated).
371         // 1) Map the coordinates into the subset range [Repeat and MirrorRepeat], or pass
372         //    through output of 0).
373         // 2) Clamp the coordinates to a 0.5 inset of the subset rect [Clamp, Repeat, and
374         //    MirrorRepeat always or ClampToBorder only when filtering] or pass through
375         //    output of 1). The clamp rect collapses to a line or point it if the subset
376         //    rect is less than one pixel wide/tall.
377         // 3) Look up texture with output of 2) [All]
378         // 3) Use the difference between 1) and 2) to apply filtering at edge [Repeat or
379         //    ClampToBorder]. In the Repeat case this requires extra texture lookups on the
380         //    other side of the subset (up to 3 more reads). Or if ClampToBorder and not
381         //    filtering do a hard less than/greater than test with the subset rect.
382 
383         // Convert possible projective texture coordinates into non-homogeneous half2.
384         fb->codeAppendf("float2 inCoord = %s;", args.fSampleCoord);
385 
386         const auto& m = te.fShaderModes;
387 
388         const char* borderName = nullptr;
389         if (te.hasClampToBorderShaderMode()) {
390             fBorderUni = args.fUniformHandler->addUniform(
391                     &te, kFragment_GrShaderFlag, SkSLType::kHalf4, "border", &borderName);
392         }
393         auto modeUsesSubset = [](ShaderMode m) {
394           switch (m) {
395               case ShaderMode::kNone:                     return false;
396               case ShaderMode::kClamp:                    return false;
397               case ShaderMode::kRepeat_Nearest_None:      return true;
398               case ShaderMode::kRepeat_Linear_None:       return true;
399               case ShaderMode::kRepeat_Nearest_Mipmap:    return true;
400               case ShaderMode::kRepeat_Linear_Mipmap:     return true;
401               case ShaderMode::kMirrorRepeat:             return true;
402               case ShaderMode::kClampToBorder_Nearest:    return true;
403               case ShaderMode::kClampToBorder_Filter:     return true;
404           }
405           SkUNREACHABLE;
406         };
407 
408         auto modeUsesClamp = [](ShaderMode m) {
409           switch (m) {
410               case ShaderMode::kNone:                     return false;
411               case ShaderMode::kClamp:                    return true;
412               case ShaderMode::kRepeat_Nearest_None:      return true;
413               case ShaderMode::kRepeat_Linear_None:       return true;
414               case ShaderMode::kRepeat_Nearest_Mipmap:    return true;
415               case ShaderMode::kRepeat_Linear_Mipmap:     return true;
416               case ShaderMode::kMirrorRepeat:             return true;
417               case ShaderMode::kClampToBorder_Nearest:    return false;
418               case ShaderMode::kClampToBorder_Filter:     return true;
419           }
420           SkUNREACHABLE;
421         };
422 
423         bool useSubset[2] = {modeUsesSubset(m[0]), modeUsesSubset(m[1])};
424         bool useClamp [2] = {modeUsesClamp (m[0]), modeUsesClamp (m[1])};
425 
426         const char* subsetName = nullptr;
427         if (useSubset[0] || useSubset[1]) {
428             fSubsetUni = args.fUniformHandler->addUniform(
429                     &te, kFragment_GrShaderFlag, SkSLType::kFloat4, "subset", &subsetName);
430         }
431 
432         const char* clampName = nullptr;
433         if (useClamp[0] || useClamp[1]) {
434             fClampUni = args.fUniformHandler->addUniform(
435                     &te, kFragment_GrShaderFlag, SkSLType::kFloat4, "clamp", &clampName);
436         }
437 
438         bool unormCoordsRequiredForShaderMode = ShaderModeRequiresUnormCoord(m[0]) ||
439                                                 ShaderModeRequiresUnormCoord(m[1]);
440         // We should not pre-normalize the input coords with GrMatrixEffect if we're going to
441         // operate on unnormalized coords and then normalize after the shader mode.
442         SkASSERT(!(unormCoordsRequiredForShaderMode && te.matrixEffectShouldNormalize()));
443         bool sampleCoordsMustBeNormalized =
444                 te.fView.asTextureProxy()->textureType() != GrTextureType::kRectangle;
445 
446         const char* idims = nullptr;
447         if (unormCoordsRequiredForShaderMode && sampleCoordsMustBeNormalized) {
448             // TODO: Detect support for textureSize() or polyfill textureSize() in SkSL and
449             // always use?
450             fIDimsUni = args.fUniformHandler->addUniform(&te, kFragment_GrShaderFlag,
451                                                          SkSLType::kFloat2, "idims", &idims);
452         }
453 
454         // Generates a string to read at a coordinate, normalizing coords if necessary.
455         auto read = [&](const char* coord) {
456             SkString result;
457             SkString normCoord;
458             if (idims) {
459                 normCoord.printf("(%s) * %s", coord, idims);
460             } else {
461                 normCoord = coord;
462             }
463             fb->appendTextureLookup(&result, fSamplerHandle, normCoord.c_str());
464             return result;
465         };
466 
467         // Implements coord wrapping for kRepeat and kMirrorRepeat
468         auto subsetCoord = [&](ShaderMode mode,
469                                const char* coordSwizzle,
470                                const char* subsetStartSwizzle,
471                                const char* subsetStopSwizzle,
472                                const char* extraCoord,
473                                const char* coordWeight) {
474             switch (mode) {
475                 // These modes either don't use the subset rect or don't need to map the
476                 // coords to be within the subset.
477                 case ShaderMode::kNone:
478                 case ShaderMode::kClampToBorder_Nearest:
479                 case ShaderMode::kClampToBorder_Filter:
480                 case ShaderMode::kClamp:
481                     fb->codeAppendf("subsetCoord.%s = inCoord.%s;", coordSwizzle, coordSwizzle);
482                     break;
483                 case ShaderMode::kRepeat_Nearest_None:
484                 case ShaderMode::kRepeat_Linear_None:
485                     fb->codeAppendf(
486                             "subsetCoord.%s = mod(inCoord.%s - %s.%s, %s.%s - %s.%s) + %s.%s;",
487                             coordSwizzle, coordSwizzle, subsetName, subsetStartSwizzle, subsetName,
488                             subsetStopSwizzle, subsetName, subsetStartSwizzle, subsetName,
489                             subsetStartSwizzle);
490                     break;
491                 case ShaderMode::kRepeat_Nearest_Mipmap:
492                 case ShaderMode::kRepeat_Linear_Mipmap:
493                     // The approach here is to generate two sets of texture coords that
494                     // are both "moving" at the same speed (if not direction) as
495                     // inCoords. We accomplish that by using two out of phase mirror
496                     // repeat coords. We will always sample using both coords but the
497                     // read from the upward sloping one is selected using a weight
498                     // that transitions from one set to the other near the reflection
499                     // point. Like the coords, the weight is a saw-tooth function,
500                     // phase-shifted, vertically translated, and then clamped to 0..1.
501                     // TODO: Skip this and use textureGrad() when available.
502                     SkASSERT(extraCoord);
503                     SkASSERT(coordWeight);
504                     fb->codeAppend("{");
505                     fb->codeAppendf("float w = %s.%s - %s.%s;", subsetName, subsetStopSwizzle,
506                                     subsetName, subsetStartSwizzle);
507                     fb->codeAppendf("float w2 = 2 * w;");
508                     fb->codeAppendf("float d = inCoord.%s - %s.%s;", coordSwizzle, subsetName,
509                                     subsetStartSwizzle);
510                     fb->codeAppend("float m = mod(d, w2);");
511                     fb->codeAppend("float o = mix(m, w2 - m, step(w, m));");
512                     fb->codeAppendf("subsetCoord.%s = o + %s.%s;", coordSwizzle, subsetName,
513                                     subsetStartSwizzle);
514                     fb->codeAppendf("%s = w - o + %s.%s;", extraCoord, subsetName,
515                                     subsetStartSwizzle);
516                     // coordWeight is used as the third param of mix() to blend between a
517                     // sample taken using subsetCoord and a sample at extraCoord.
518                     fb->codeAppend("float hw = w/2;");
519                     fb->codeAppend("float n = mod(d - hw, w2);");
520                     fb->codeAppendf("%s = saturate(half(mix(n, w2 - n, step(w, n)) - hw + 0.5));",
521                                     coordWeight);
522                     fb->codeAppend("}");
523                     break;
524                 case ShaderMode::kMirrorRepeat:
525                     fb->codeAppend("{");
526                     fb->codeAppendf("float w = %s.%s - %s.%s;", subsetName, subsetStopSwizzle,
527                                     subsetName, subsetStartSwizzle);
528                     fb->codeAppendf("float w2 = 2 * w;");
529                     fb->codeAppendf("float m = mod(inCoord.%s - %s.%s, w2);", coordSwizzle,
530                                     subsetName, subsetStartSwizzle);
531                     fb->codeAppendf("subsetCoord.%s = mix(m, w2 - m, step(w, m)) + %s.%s;",
532                                     coordSwizzle, subsetName, subsetStartSwizzle);
533                     fb->codeAppend("}");
534                     break;
535             }
536         };
537 
538         auto clampCoord = [&](bool clamp,
539                               const char* coordSwizzle,
540                               const char* clampStartSwizzle,
541                               const char* clampStopSwizzle) {
542             if (clamp) {
543                 fb->codeAppendf("clampedCoord%s = clamp(subsetCoord%s, %s%s, %s%s);",
544                                 coordSwizzle, coordSwizzle,
545                                 clampName, clampStartSwizzle,
546                                 clampName, clampStopSwizzle);
547             } else {
548                 fb->codeAppendf("clampedCoord%s = subsetCoord%s;", coordSwizzle, coordSwizzle);
549             }
550         };
551 
552         // Insert vars for extra coords and blending weights for repeat + mip map.
553         const char* extraRepeatCoordX  = nullptr;
554         const char* repeatCoordWeightX = nullptr;
555         const char* extraRepeatCoordY  = nullptr;
556         const char* repeatCoordWeightY = nullptr;
557 
558         bool mipmapRepeatX = m[0] == ShaderMode::kRepeat_Nearest_Mipmap ||
559                              m[0] == ShaderMode::kRepeat_Linear_Mipmap;
560         bool mipmapRepeatY = m[1] == ShaderMode::kRepeat_Nearest_Mipmap ||
561                              m[1] == ShaderMode::kRepeat_Linear_Mipmap;
562 
563         if (mipmapRepeatX || mipmapRepeatY) {
564             fb->codeAppend("float2 extraRepeatCoord;");
565         }
566         if (mipmapRepeatX) {
567             fb->codeAppend("half repeatCoordWeightX;");
568             extraRepeatCoordX   = "extraRepeatCoord.x";
569             repeatCoordWeightX  = "repeatCoordWeightX";
570         }
571         if (mipmapRepeatY) {
572             fb->codeAppend("half repeatCoordWeightY;");
573             extraRepeatCoordY   = "extraRepeatCoord.y";
574             repeatCoordWeightY  = "repeatCoordWeightY";
575         }
576 
577         // Apply subset rect and clamp rect to coords.
578         fb->codeAppend("float2 subsetCoord;");
579         subsetCoord(te.fShaderModes[0], "x", "x", "z", extraRepeatCoordX, repeatCoordWeightX);
580         subsetCoord(te.fShaderModes[1], "y", "y", "w", extraRepeatCoordY, repeatCoordWeightY);
581         fb->codeAppend("float2 clampedCoord;");
582         if (useClamp[0] == useClamp[1]) {
583             clampCoord(useClamp[0], "", ".xy", ".zw");
584         } else {
585             clampCoord(useClamp[0], ".x", ".x", ".z");
586             clampCoord(useClamp[1], ".y", ".y", ".w");
587         }
588         // Additional clamping for the extra coords for kRepeat with mip maps.
589         if (mipmapRepeatX && mipmapRepeatY) {
590             fb->codeAppendf("extraRepeatCoord = clamp(extraRepeatCoord, %s.xy, %s.zw);",
591                             clampName, clampName);
592         } else if (mipmapRepeatX) {
593             fb->codeAppendf("extraRepeatCoord.x = clamp(extraRepeatCoord.x, %s.x, %s.z);",
594                             clampName, clampName);
595         } else if (mipmapRepeatY) {
596             fb->codeAppendf("extraRepeatCoord.y = clamp(extraRepeatCoord.y, %s.y, %s.w);",
597                             clampName, clampName);
598         }
599 
600         // Do the 2 or 4 texture reads for kRepeatMipMap and then apply the weight(s)
601         // to blend between them. If neither direction is repeat or not using mip maps do a single
602         // read at clampedCoord.
603         if (mipmapRepeatX && mipmapRepeatY) {
604             fb->codeAppendf(
605                     "half4 textureColor ="
606                     "   mix(mix(%s, %s, repeatCoordWeightX),"
607                     "       mix(%s, %s, repeatCoordWeightX),"
608                     "       repeatCoordWeightY);",
609                     read("clampedCoord").c_str(),
610                     read("float2(extraRepeatCoord.x, clampedCoord.y)").c_str(),
611                     read("float2(clampedCoord.x, extraRepeatCoord.y)").c_str(),
612                     read("float2(extraRepeatCoord.x, extraRepeatCoord.y)").c_str());
613 
614         } else if (mipmapRepeatX) {
615             fb->codeAppendf("half4 textureColor = mix(%s, %s, repeatCoordWeightX);",
616                             read("clampedCoord").c_str(),
617                             read("float2(extraRepeatCoord.x, clampedCoord.y)").c_str());
618         } else if (mipmapRepeatY) {
619             fb->codeAppendf("half4 textureColor = mix(%s, %s, repeatCoordWeightY);",
620                             read("clampedCoord").c_str(),
621                             read("float2(clampedCoord.x, extraRepeatCoord.y)").c_str());
622         } else {
623             fb->codeAppendf("half4 textureColor = %s;", read("clampedCoord").c_str());
624         }
625 
626         // Strings for extra texture reads used only in kRepeatLinear
627         SkString repeatLinearReadX;
628         SkString repeatLinearReadY;
629 
630         // Calculate the amount the coord moved for clamping. This will be used
631         // to implement shader-based filtering for kClampToBorder and kRepeat.
632         bool repeatLinearFilterX = m[0] == ShaderMode::kRepeat_Linear_None ||
633                                    m[0] == ShaderMode::kRepeat_Linear_Mipmap;
634         bool repeatLinearFilterY = m[1] == ShaderMode::kRepeat_Linear_None ||
635                                    m[1] == ShaderMode::kRepeat_Linear_Mipmap;
636         if (repeatLinearFilterX || m[0] == ShaderMode::kClampToBorder_Filter) {
637             fb->codeAppend("half errX = half(subsetCoord.x - clampedCoord.x);");
638             if (repeatLinearFilterX) {
639                 fb->codeAppendf("float repeatCoordX = errX > 0 ? %s.x : %s.z;",
640                                 clampName, clampName);
641                 repeatLinearReadX = read("float2(repeatCoordX, clampedCoord.y)");
642             }
643         }
644         if (repeatLinearFilterY || m[1] == ShaderMode::kClampToBorder_Filter) {
645             fb->codeAppend("half errY = half(subsetCoord.y - clampedCoord.y);");
646             if (repeatLinearFilterY) {
647                 fb->codeAppendf("float repeatCoordY = errY > 0 ? %s.y : %s.w;",
648                                 clampName, clampName);
649                 repeatLinearReadY = read("float2(clampedCoord.x, repeatCoordY)");
650             }
651         }
652 
653         // Add logic for kRepeat + linear filter. Do 1 or 3 more texture reads depending
654         // on whether both modes are kRepeat and whether we're near a single subset edge
655         // or a corner. Then blend the multiple reads using the err values calculated
656         // above.
657         const char* ifStr = "if";
658         if (repeatLinearFilterX && repeatLinearFilterY) {
659             auto repeatLinearReadXY = read("float2(repeatCoordX, repeatCoordY)");
660             fb->codeAppendf(
661                     "if (errX != 0 && errY != 0) {"
662                     "    errX = abs(errX);"
663                     "    textureColor = mix(mix(textureColor, %s, errX),"
664                     "                       mix(%s, %s, errX),"
665                     "                       abs(errY));"
666                     "}",
667                     repeatLinearReadX.c_str(), repeatLinearReadY.c_str(),
668                     repeatLinearReadXY.c_str());
669             ifStr = "else if";
670         }
671         if (repeatLinearFilterX) {
672             fb->codeAppendf(
673                     "%s (errX != 0) {"
674                     "    textureColor = mix(textureColor, %s, abs(errX));"
675                     "}",
676                     ifStr, repeatLinearReadX.c_str());
677         }
678         if (repeatLinearFilterY) {
679             fb->codeAppendf(
680                     "%s (errY != 0) {"
681                     "    textureColor = mix(textureColor, %s, abs(errY));"
682                     "}",
683                     ifStr, repeatLinearReadY.c_str());
684         }
685 
686         // Do soft edge shader filtering against border color for kClampToBorderFilter using
687         // the err values calculated above.
688         if (m[0] == ShaderMode::kClampToBorder_Filter) {
689             fb->codeAppendf("textureColor = mix(textureColor, %s, min(abs(errX), 1));", borderName);
690         }
691         if (m[1] == ShaderMode::kClampToBorder_Filter) {
692             fb->codeAppendf("textureColor = mix(textureColor, %s, min(abs(errY), 1));", borderName);
693         }
694 
695         // Do hard-edge shader transition to border color for kClampToBorderNearest at the
696         // subset boundaries. Snap the input coordinates to nearest neighbor (with an
697         // epsilon) before comparing to the subset rect to avoid GPU interpolation errors
698         if (m[0] == ShaderMode::kClampToBorder_Nearest) {
699             fb->codeAppendf(
700                     "float snappedX = floor(inCoord.x + 0.001) + 0.5;"
701                     "if (snappedX < %s.x || snappedX > %s.z) {"
702                     "    textureColor = %s;"
703                     "}",
704                     subsetName, subsetName, borderName);
705         }
706         if (m[1] == ShaderMode::kClampToBorder_Nearest) {
707             fb->codeAppendf(
708                     "float snappedY = floor(inCoord.y + 0.001) + 0.5;"
709                     "if (snappedY < %s.y || snappedY > %s.w) {"
710                     "    textureColor = %s;"
711                     "}",
712                     subsetName, subsetName, borderName);
713         }
714         fb->codeAppendf("return textureColor;");
715     }
716 }
717 
onSetData(const GrGLSLProgramDataManager & pdm,const GrFragmentProcessor & fp)718 void GrTextureEffect::Impl::onSetData(const GrGLSLProgramDataManager& pdm,
719                                       const GrFragmentProcessor& fp) {
720     const auto& te = fp.cast<GrTextureEffect>();
721 
722     const float w = te.texture()->width();
723     const float h = te.texture()->height();
724     const auto& s = te.fSubset;
725     const auto& c = te.fClamp;
726 
727     auto type = te.texture()->textureType();
728 
729     float idims[2] = {1.f/w, 1.f/h};
730 
731     if (fIDimsUni.isValid()) {
732         pdm.set2fv(fIDimsUni, 1, idims);
733         SkASSERT(type != GrTextureType::kRectangle);
734     }
735 
736     auto pushRect = [&](float rect[4], UniformHandle uni) {
737         if (te.view().origin() == kBottomLeft_GrSurfaceOrigin) {
738             rect[1] = h - rect[1];
739             rect[3] = h - rect[3];
740             std::swap(rect[1], rect[3]);
741         }
742         if (!fIDimsUni.isValid() && type != GrTextureType::kRectangle) {
743             rect[0] *= idims[0];
744             rect[2] *= idims[0];
745             rect[1] *= idims[1];
746             rect[3] *= idims[1];
747         }
748         pdm.set4fv(uni, 1, rect);
749     };
750 
751     if (fSubsetUni.isValid()) {
752         float subset[] = {s.fLeft, s.fTop, s.fRight, s.fBottom};
753         pushRect(subset, fSubsetUni);
754     }
755     if (fClampUni.isValid()) {
756         float subset[] = {c.fLeft, c.fTop, c.fRight, c.fBottom};
757         pushRect(subset, fClampUni);
758     }
759     if (fBorderUni.isValid()) {
760         pdm.set4fv(fBorderUni, 1, te.fBorder);
761     }
762 }
763 
onMakeProgramImpl() const764 std::unique_ptr<GrFragmentProcessor::ProgramImpl> GrTextureEffect::onMakeProgramImpl() const {
765     return std::make_unique<Impl>();
766 }
767 
onAddToKey(const GrShaderCaps &,skgpu::KeyBuilder * b) const768 void GrTextureEffect::onAddToKey(const GrShaderCaps&, skgpu::KeyBuilder* b) const {
769     auto m0 = static_cast<uint32_t>(fShaderModes[0]);
770     b->addBits(8, m0, "shaderMode0");
771 
772     auto m1 = static_cast<uint32_t>(fShaderModes[1]);
773     b->addBits(8, m1, "shaderMode1");
774 }
775 
onIsEqual(const GrFragmentProcessor & other) const776 bool GrTextureEffect::onIsEqual(const GrFragmentProcessor& other) const {
777     auto& that = other.cast<GrTextureEffect>();
778     if (fView != that.fView) {
779         return false;
780     }
781     if (fSamplerState != that.fSamplerState) {
782         return false;
783     }
784     if (fShaderModes[0] != that.fShaderModes[0] || fShaderModes[1] != that.fShaderModes[1]) {
785         return false;
786     }
787     if (fSubset != that.fSubset) {
788         return false;
789     }
790     if (this->hasClampToBorderShaderMode() && !std::equal(fBorder, fBorder + 4, that.fBorder)) {
791         return false;
792     }
793     return true;
794 }
795 
matrixEffectShouldNormalize() const796 bool GrTextureEffect::matrixEffectShouldNormalize() const {
797     return fView.asTextureProxy()->textureType() != GrTextureType::kRectangle &&
798            !ShaderModeRequiresUnormCoord(fShaderModes[0])                     &&
799            !ShaderModeRequiresUnormCoord(fShaderModes[1]);
800 }
801 
GrTextureEffect(GrSurfaceProxyView view,SkAlphaType alphaType,const Sampling & sampling)802 GrTextureEffect::GrTextureEffect(GrSurfaceProxyView view,
803                                  SkAlphaType alphaType,
804                                  const Sampling& sampling)
805         : GrFragmentProcessor(kGrTextureEffect_ClassID,
806                               ModulateForSamplerOptFlags(alphaType, sampling.hasBorderAlpha()))
807         , fView(std::move(view))
808         , fSamplerState(sampling.fHWSampler)
809         , fSubset(sampling.fShaderSubset)
810         , fClamp(sampling.fShaderClamp)
811         , fShaderModes{sampling.fShaderModes[0], sampling.fShaderModes[1]} {
812     // We always compare the range even when it isn't used so assert we have canonical don't care
813     // values.
814     SkASSERT(fShaderModes[0] != ShaderMode::kNone || (fSubset.fLeft == 0 && fSubset.fRight == 0));
815     SkASSERT(fShaderModes[1] != ShaderMode::kNone || (fSubset.fTop == 0 && fSubset.fBottom == 0));
816     this->setUsesSampleCoordsDirectly();
817     std::copy_n(sampling.fBorder, 4, fBorder);
818 }
819 
GrTextureEffect(const GrTextureEffect & src)820 GrTextureEffect::GrTextureEffect(const GrTextureEffect& src)
821         : INHERITED(kGrTextureEffect_ClassID, src.optimizationFlags())
822         , fView(src.fView)
823         , fSamplerState(src.fSamplerState)
824         , fSubset(src.fSubset)
825         , fClamp(src.fClamp)
826         , fShaderModes{src.fShaderModes[0], src.fShaderModes[1]} {
827     std::copy_n(src.fBorder, 4, fBorder);
828     this->setUsesSampleCoordsDirectly();
829 }
830 
clone() const831 std::unique_ptr<GrFragmentProcessor> GrTextureEffect::clone() const {
832     return std::unique_ptr<GrFragmentProcessor>(new GrTextureEffect(*this));
833 }
834 
GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrTextureEffect)835 GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrTextureEffect)
836 #if GR_TEST_UTILS
837 std::unique_ptr<GrFragmentProcessor> GrTextureEffect::TestCreate(GrProcessorTestData* testData) {
838     auto [view, ct, at] = testData->randomView();
839     Wrap wrapModes[2];
840     GrTest::TestWrapModes(testData->fRandom, wrapModes);
841 
842     Filter filter = testData->fRandom->nextBool() ? Filter::kLinear : Filter::kNearest;
843     MipmapMode mm = MipmapMode::kNone;
844     if (view.asTextureProxy()->mipmapped() == GrMipmapped::kYes) {
845         mm = testData->fRandom->nextBool() ? MipmapMode::kLinear : MipmapMode::kNone;
846     }
847     GrSamplerState params(wrapModes, filter, mm);
848 
849     const SkMatrix& matrix = GrTest::TestMatrix(testData->fRandom);
850     return GrTextureEffect::Make(std::move(view), at, matrix, params, *testData->caps());
851 }
852 #endif
853