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