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