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