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