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