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