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