Flutter Engine
The Flutter Engine
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) {
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]) {
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) {
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) {
326 return ShaderMode::kMirrorRepeat;
327 case Wrap::kClamp:
328 return ShaderMode::kClamp;
329 case Wrap::kRepeat:
330 switch (mm) {
332 switch (filter) {
333 case Filter::kNearest: return ShaderMode::kRepeat_Nearest_None;
334 case Filter::kLinear: return ShaderMode::kRepeat_Linear_None;
335 }
339 switch (filter) {
340 case Filter::kNearest: return ShaderMode::kRepeat_Nearest_Mipmap;
341 case Filter::kLinear: return ShaderMode::kRepeat_Linear_Mipmap;
342 }
344 }
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
737void GrTextureEffect::Impl::onSetData(const GrGLSLProgramDataManager& pdm,
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
787void GrTextureEffect::onAddToKey(const GrShaderCaps&, skgpu::KeyBuilder* b) const {
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
795bool GrTextureEffect::onIsEqual(const GrFragmentProcessor& other) const {
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)
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;
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
Definition: GrTypesPriv.h:287
@ kBottomLeft_GrSurfaceOrigin
Definition: GrTypes.h:149
static bool equal(const SkBitmap &a, const SkBitmap &b)
Definition: ImageTest.cpp:1395
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
void swap(sk_sp< T > &a, sk_sp< T > &b)
Definition: SkRefCnt.h:341
SkFilterMode
SkMipmapMode
GLenum type
Definition: GrCaps.h:57
bool anisoSupport() const
Definition: GrCaps.h:74
bool clampToBorderSupport() const
Definition: GrCaps.h:484
bool npotTextureTileSupport() const
Definition: GrCaps.h:69
OptimizationFlags optimizationFlags() const
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
Definition: GrProcessor.h:127
@ 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
void emitCode(EmitArgs &) 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
GrTexture * texture() const
std::unique_ptr< GrFragmentProcessor > clone() 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)
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
sk_sp< SkBlender > blender SkRect rect
Definition: SkRecords.h:350
SkSamplingOptions sampling
Definition: SkRecords.h:337
const uint32_t fp
it will be possible to load the file into Perfetto s trace viewer disable asset Prevents usage of any non test fonts unless they were explicitly Loaded via prefetched default font Indicates whether the embedding started a prefetch of the default font manager before creating the engine run In non interactive mode
Definition: switches.h:228
it will be possible to load the file into Perfetto s trace viewer disable asset Prevents usage of any non test fonts unless they were explicitly Loaded via prefetched default font Indicates whether the embedding started a prefetch of the default font manager before creating the engine run In non interactive keep the shell running after the Dart script has completed enable serial On low power devices with low core running concurrent GC tasks on threads can cause them to contend with the UI thread which could potentially lead to jank This option turns off all concurrent GC activities domain network JSON encoded network policy per domain This overrides the DisallowInsecureConnections switch Embedder can specify whether to allow or disallow insecure connections at a domain level old gen heap size
Definition: switches.h:259
constexpr bool contains(std::string_view str, std::string_view needle)
Definition: SkStringView.h:41
SIN Vec< N, float > floor(const Vec< N, float > &x)
Definition: SkVx.h:703
SIN Vec< N, float > ceil(const Vec< N, float > &x)
Definition: SkVx.h:702
Definition: ref_ptr.h:256
static SkRect inset(const SkRect &r)
SkScalar w
SkScalar h
Sampling(Filter filter, MipmapMode mm)
Definition: SkSize.h:16
constexpr int32_t width() const
Definition: SkSize.h:36
constexpr int32_t height() const
Definition: SkSize.h:37
float fX
x-axis value
Definition: SkPoint_impl.h:164
float fY
y-axis value
Definition: SkPoint_impl.h:165
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