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