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