blob: 66af222f41f1f0a0444468c979eaa13c97bb5077 [file] [log] [blame]
Brian Salomonb8f098d2020-01-07 11:15:44 -05001/*
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
Greg Daniel456f9b52020-03-05 19:14:18 +000010#include "src/gpu/GrTexture.h"
Brian Salomonca6b2f42020-01-24 11:31:21 -050011#include "src/gpu/GrTexturePriv.h"
Brian Salomonb8f098d2020-01-07 11:15:44 -050012#include "src/gpu/glsl/GrGLSLFragmentProcessor.h"
13#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h"
14#include "src/gpu/glsl/GrGLSLProgramBuilder.h"
15#include "src/sksl/SkSLCPP.h"
16#include "src/sksl/SkSLUtil.h"
17
Brian Salomon53c909e2020-02-13 13:54:24 -050018using Mode = GrSamplerState::WrapMode;
19using Filter = GrSamplerState::Filter;
Brian Salomonca6b2f42020-01-24 11:31:21 -050020
Brian Salomond71548a2020-02-29 19:43:30 -050021struct 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(GrSamplerState::Filter filter) : fHWSampler(filter) {}
28 Sampling(const GrSurfaceProxy& proxy,
29 GrSamplerState sampler,
30 const SkRect&,
31 const SkRect*,
32 const float border[4],
33 const GrCaps&);
34 inline bool hasBorderAlpha() const;
35};
36
Brian Salomonca6b2f42020-01-24 11:31:21 -050037GrTextureEffect::Sampling::Sampling(const GrSurfaceProxy& proxy,
38 GrSamplerState sampler,
39 const SkRect& subset,
Brian Salomonca6b2f42020-01-24 11:31:21 -050040 const SkRect* domain,
Brian Salomond71548a2020-02-29 19:43:30 -050041 const float border[4],
Brian Salomonca6b2f42020-01-24 11:31:21 -050042 const GrCaps& caps) {
Brian Salomon53c909e2020-02-13 13:54:24 -050043 struct Span {
44 float fA = 0.f, fB = 0.f;
Brian Salomonca6b2f42020-01-24 11:31:21 -050045
Brian Salomon53c909e2020-02-13 13:54:24 -050046 Span makeInset(float o) const {
47 Span r = {fA + o, fB - o};
48 if (r.fA > r.fB) {
49 r.fA = r.fB = (r.fA + r.fB) / 2;
50 }
Brian Salomonca6b2f42020-01-24 11:31:21 -050051 return r;
52 }
53
Brian Salomon53c909e2020-02-13 13:54:24 -050054 bool contains(Span r) const { return fA <= r.fA && fB >= r.fB; }
55 };
56 struct Result1D {
57 ShaderMode fShaderMode;
58 Span fShaderSubset;
59 Span fShaderClamp;
60 Mode fHWMode;
61 };
62
63 auto type = proxy.asTextureProxy()->textureType();
64 auto filter = sampler.filter();
65
Brian Salomond71548a2020-02-29 19:43:30 -050066 auto resolve = [type, &caps, filter, &border](int size, Mode mode, Span subset, Span domain) {
Brian Salomon53c909e2020-02-13 13:54:24 -050067 Result1D r;
Brian Salomond71548a2020-02-29 19:43:30 -050068 bool canDoModeInHW = true;
69 // TODO: Use HW border color when available.
70 if (mode == Mode::kClampToBorder &&
71 (!caps.clampToBorderSupport() || border[0] || border[1] || border[2] || border[3])) {
72 canDoModeInHW = false;
73 } else if (mode != Mode::kClamp && !caps.npotTextureTileSupport() && !SkIsPow2(size)) {
74 canDoModeInHW = false;
75 } else if (type != GrTextureType::k2D &&
76 !(mode == Mode::kClamp || mode == Mode::kClampToBorder)) {
77 canDoModeInHW = false;
78 }
79 if (canDoModeInHW && size > 0 && subset.fA <= 0 && subset.fB >= size) {
Brian Salomon53c909e2020-02-13 13:54:24 -050080 r.fShaderMode = ShaderMode::kNone;
81 r.fHWMode = mode;
82 r.fShaderSubset = r.fShaderClamp = {0, 0};
83 return r;
84 }
85
86 r.fShaderSubset = subset;
Brian Salomoned729f92020-02-05 12:15:18 -050087 bool domainIsSafe = false;
Brian Salomoned729f92020-02-05 12:15:18 -050088 if (filter == Filter::kNearest) {
89 Span isubset{sk_float_floor(subset.fA), sk_float_ceil(subset.fB)};
90 if (domain.fA > isubset.fA && domain.fB < isubset.fB) {
91 domainIsSafe = true;
92 }
Brian Salomon53c909e2020-02-13 13:54:24 -050093 // This inset prevents sampling neighboring texels that could occur when
94 // texture coords fall exactly at texel boundaries (depending on precision
95 // and GPU-specific snapping at the boundary).
96 r.fShaderClamp = isubset.makeInset(0.5f);
97 } else {
98 r.fShaderClamp = subset.makeInset(0.5f);
99 if (r.fShaderClamp.contains(domain)) {
100 domainIsSafe = true;
Brian Salomoned729f92020-02-05 12:15:18 -0500101 }
Brian Salomoned729f92020-02-05 12:15:18 -0500102 }
Brian Salomon53c909e2020-02-13 13:54:24 -0500103 if (domainIsSafe) {
104 // The domain of coords that will be used won't access texels outside of the subset.
105 // So the wrap mode effectively doesn't matter. We use kClamp since it is always
106 // supported.
Brian Salomonca6b2f42020-01-24 11:31:21 -0500107 r.fShaderMode = ShaderMode::kNone;
Brian Salomon53c909e2020-02-13 13:54:24 -0500108 r.fHWMode = Mode::kClamp;
109 r.fShaderSubset = r.fShaderClamp = {0, 0};
Brian Salomonca6b2f42020-01-24 11:31:21 -0500110 return r;
111 }
Brian Salomonca6b2f42020-01-24 11:31:21 -0500112 r.fShaderMode = static_cast<ShaderMode>(mode);
113 r.fHWMode = Mode::kClamp;
114 return r;
115 };
116
117 SkISize dim = proxy.isFullyLazy() ? SkISize{-1, -1} : proxy.backingStoreDimensions();
118
119 Span subsetX{subset.fLeft, subset.fRight};
120 auto domainX = domain ? Span{domain->fLeft, domain->fRight}
121 : Span{SK_FloatNegativeInfinity, SK_FloatInfinity};
122 auto x = resolve(dim.width(), sampler.wrapModeX(), subsetX, domainX);
123
124 Span subsetY{subset.fTop, subset.fBottom};
125 auto domainY = domain ? Span{domain->fTop, domain->fBottom}
126 : Span{SK_FloatNegativeInfinity, SK_FloatInfinity};
127 auto y = resolve(dim.height(), sampler.wrapModeY(), subsetY, domainY);
128
Brian Salomon53c909e2020-02-13 13:54:24 -0500129 fHWSampler = {x.fHWMode, y.fHWMode, filter};
Brian Salomonca6b2f42020-01-24 11:31:21 -0500130 fShaderModes[0] = x.fShaderMode;
131 fShaderModes[1] = y.fShaderMode;
132 fShaderSubset = {x.fShaderSubset.fA, y.fShaderSubset.fA,
133 x.fShaderSubset.fB, y.fShaderSubset.fB};
Brian Salomon53c909e2020-02-13 13:54:24 -0500134 fShaderClamp = {x.fShaderClamp.fA, y.fShaderClamp.fA,
135 x.fShaderClamp.fB, y.fShaderClamp.fB};
Brian Salomond71548a2020-02-29 19:43:30 -0500136 std::copy_n(border, 4, fBorder);
Brian Salomonca6b2f42020-01-24 11:31:21 -0500137}
138
Brian Salomond71548a2020-02-29 19:43:30 -0500139bool GrTextureEffect::Sampling::hasBorderAlpha() const {
140 if (fHWSampler.wrapModeX() == GrSamplerState::WrapMode::kClampToBorder ||
141 fHWSampler.wrapModeY() == GrSamplerState::WrapMode::kClampToBorder) {
142 return true;
143 }
144 if (fShaderModes[0] == ShaderMode::kClampToBorder ||
145 fShaderModes[1] == ShaderMode::kClampToBorder) {
146 return fBorder[3] < 1.f;
147 }
148 return false;
Brian Salomonca6b2f42020-01-24 11:31:21 -0500149}
150
Greg Danield2ccbb52020-02-05 10:45:39 -0500151std::unique_ptr<GrFragmentProcessor> GrTextureEffect::Make(GrSurfaceProxyView view,
Brian Salomonb8f098d2020-01-07 11:15:44 -0500152 SkAlphaType alphaType,
153 const SkMatrix& matrix,
Brian Salomon53c909e2020-02-13 13:54:24 -0500154 Filter filter) {
Brian Salomonb8f098d2020-01-07 11:15:44 -0500155 return std::unique_ptr<GrFragmentProcessor>(
Greg Danield2ccbb52020-02-05 10:45:39 -0500156 new GrTextureEffect(std::move(view), alphaType, matrix, Sampling(filter)));
Brian Salomonca6b2f42020-01-24 11:31:21 -0500157}
158
Greg Danield2ccbb52020-02-05 10:45:39 -0500159std::unique_ptr<GrFragmentProcessor> GrTextureEffect::Make(GrSurfaceProxyView view,
Brian Salomonca6b2f42020-01-24 11:31:21 -0500160 SkAlphaType alphaType,
161 const SkMatrix& matrix,
162 GrSamplerState sampler,
Brian Salomond71548a2020-02-29 19:43:30 -0500163 const GrCaps& caps,
164 const float border[4]) {
Brian Salomon53c909e2020-02-13 13:54:24 -0500165 Sampling sampling(*view.proxy(), sampler, SkRect::Make(view.proxy()->dimensions()), nullptr,
Brian Salomond71548a2020-02-29 19:43:30 -0500166 border, caps);
Brian Salomonca6b2f42020-01-24 11:31:21 -0500167 return std::unique_ptr<GrFragmentProcessor>(
Greg Danield2ccbb52020-02-05 10:45:39 -0500168 new GrTextureEffect(std::move(view), alphaType, matrix, sampling));
Brian Salomonca6b2f42020-01-24 11:31:21 -0500169}
170
Greg Danield2ccbb52020-02-05 10:45:39 -0500171std::unique_ptr<GrFragmentProcessor> GrTextureEffect::MakeSubset(GrSurfaceProxyView view,
Brian Salomonca6b2f42020-01-24 11:31:21 -0500172 SkAlphaType alphaType,
173 const SkMatrix& matrix,
174 GrSamplerState sampler,
175 const SkRect& subset,
Brian Salomond71548a2020-02-29 19:43:30 -0500176 const GrCaps& caps,
177 const float border[4]) {
178 Sampling sampling(*view.proxy(), sampler, subset, nullptr, border, caps);
Brian Salomonca6b2f42020-01-24 11:31:21 -0500179 return std::unique_ptr<GrFragmentProcessor>(
Greg Danield2ccbb52020-02-05 10:45:39 -0500180 new GrTextureEffect(std::move(view), alphaType, matrix, sampling));
Brian Salomonca6b2f42020-01-24 11:31:21 -0500181}
182
Greg Danield2ccbb52020-02-05 10:45:39 -0500183std::unique_ptr<GrFragmentProcessor> GrTextureEffect::MakeSubset(GrSurfaceProxyView view,
Brian Salomonca6b2f42020-01-24 11:31:21 -0500184 SkAlphaType alphaType,
185 const SkMatrix& matrix,
186 GrSamplerState sampler,
187 const SkRect& subset,
188 const SkRect& domain,
Brian Salomond71548a2020-02-29 19:43:30 -0500189 const GrCaps& caps,
190 const float border[4]) {
191 Sampling sampling(*view.proxy(), sampler, subset, &domain, border, caps);
Brian Salomonca6b2f42020-01-24 11:31:21 -0500192 return std::unique_ptr<GrFragmentProcessor>(
Greg Danield2ccbb52020-02-05 10:45:39 -0500193 new GrTextureEffect(std::move(view), alphaType, matrix, sampling));
Brian Salomonb8f098d2020-01-07 11:15:44 -0500194}
195
Brian Salomon53c909e2020-02-13 13:54:24 -0500196GrTextureEffect::FilterLogic GrTextureEffect::GetFilterLogic(ShaderMode mode,
197 GrSamplerState::Filter filter) {
198 switch (mode) {
199 case ShaderMode::kMirrorRepeat:
200 case ShaderMode::kNone:
201 case ShaderMode::kClamp:
202 return FilterLogic::kNone;
203 case ShaderMode::kRepeat:
204 switch (filter) {
205 case GrSamplerState::Filter::kNearest:
206 return FilterLogic::kNone;
207 case GrSamplerState::Filter::kBilerp:
208 return FilterLogic::kRepeatBilerp;
209 case GrSamplerState::Filter::kMipMap:
Brian Salomonf46f19b2020-02-13 14:11:28 -0500210 return FilterLogic::kRepeatMipMap;
Brian Salomon53c909e2020-02-13 13:54:24 -0500211 }
212 SkUNREACHABLE;
Brian Salomond71548a2020-02-29 19:43:30 -0500213 case ShaderMode::kClampToBorder:
214 return filter > GrSamplerState::Filter::kNearest ? FilterLogic::kClampToBorderFilter
215 : FilterLogic::kClampToBorderNearest;
Brian Salomon53c909e2020-02-13 13:54:24 -0500216 }
217 SkUNREACHABLE;
218}
219
Brian Salomonb8f098d2020-01-07 11:15:44 -0500220GrGLSLFragmentProcessor* GrTextureEffect::onCreateGLSLInstance() const {
221 class Impl : public GrGLSLFragmentProcessor {
Brian Salomonca6b2f42020-01-24 11:31:21 -0500222 UniformHandle fSubsetUni;
Brian Salomon53c909e2020-02-13 13:54:24 -0500223 UniformHandle fClampUni;
224 UniformHandle fNormUni;
Brian Salomond71548a2020-02-29 19:43:30 -0500225 UniformHandle fBorderUni;
Brian Salomonca6b2f42020-01-24 11:31:21 -0500226
Brian Salomonb8f098d2020-01-07 11:15:44 -0500227 public:
228 void emitCode(EmitArgs& args) override {
Ethan Nicholas94996eb2020-04-01 13:46:10 -0400229 auto& te = args.fFp.cast<GrTextureEffect>();
Brian Salomonb8f098d2020-01-07 11:15:44 -0500230 auto* fb = args.fFragBuilder;
Brian Salomonca6b2f42020-01-24 11:31:21 -0500231 if (te.fShaderModes[0] == ShaderMode::kNone &&
232 te.fShaderModes[1] == ShaderMode::kNone) {
Ethan Nicholas58430122020-04-14 09:54:02 -0400233 SkString coords;
234 if (args.fFp.isSampledWithExplicitCoords()) {
235 coords = "_coords";
236 } else {
237 coords = args.fTransformedCoords[0].fVaryingPoint.c_str();
238 }
239 switch (te.sampleMatrix().fKind) {
240 case SkSL::SampleMatrix::Kind::kMixed:
241 case SkSL::SampleMatrix::Kind::kVariable:
242 coords = SkStringPrintf("(_matrix * float3(%s, 1)).xy", coords.c_str());
243 break;
244 default:
245 break;
246 }
Brian Salomonca6b2f42020-01-24 11:31:21 -0500247 fb->codeAppendf("%s = ", args.fOutputColor);
248 fb->appendTextureLookupAndBlend(args.fInputColor, SkBlendMode::kModulate,
Ethan Nicholas58430122020-04-14 09:54:02 -0400249 args.fTexSamplers[0], coords.c_str());
Brian Salomonca6b2f42020-01-24 11:31:21 -0500250 fb->codeAppendf(";");
251 } else {
Brian Salomon53c909e2020-02-13 13:54:24 -0500252 // Here is the basic flow of the various ShaderModes are implemented in a series of
253 // steps. Not all the steps apply to all the modes. We try to emit only the steps
254 // that are necessary for the given x/y shader modes.
255 //
256 // 0) Start with interpolated coordinates (unnormalize if doing anything
257 // complicated).
258 // 1) Map the coordinates into the subset range [Repeat and MirrorRepeat], or pass
259 // through output of 0).
260 // 2) Clamp the coordinates to a 0.5 inset of the subset rect [Clamp, Repeat, and
Brian Salomond71548a2020-02-29 19:43:30 -0500261 // MirrorRepeat always or ClampToBorder only when filtering] or pass through
262 // output of 1). The clamp rect collapses to a line or point it if the subset
263 // rect is less than one pixel wide/tall.
Brian Salomon53c909e2020-02-13 13:54:24 -0500264 // 3) Look up texture with output of 2) [All]
265 // 3) Use the difference between 1) and 2) to apply filtering at edge [Repeat or
Brian Salomond71548a2020-02-29 19:43:30 -0500266 // ClampToBorder]. In the Repeat case this requires extra texture lookups on the
267 // other side of the subset (up to 3 more reads). Or if ClampToBorder and not
268 // filtering do a hard less than/greater than test with the subset rect.
Brian Salomonca6b2f42020-01-24 11:31:21 -0500269
Brian Salomon53c909e2020-02-13 13:54:24 -0500270 // Convert possible projective texture coordinates into non-homogeneous half2.
Brian Salomon66356c22020-02-11 15:22:18 -0500271 fb->codeAppendf(
272 "float2 inCoord = %s;",
Ethan Nicholas58430122020-04-14 09:54:02 -0400273 fb->ensureCoords2D(args.fTransformedCoords[0].fVaryingPoint,
274 te.sampleMatrix()).c_str());
Brian Salomon66356c22020-02-11 15:22:18 -0500275
Brian Salomon53c909e2020-02-13 13:54:24 -0500276 const auto& m = te.fShaderModes;
Robert Phillips6eb5cb92020-03-05 12:52:45 -0500277 GrTextureType textureType = te.fSampler.proxy()->backendFormat().textureType();
278 bool normCoords = textureType != GrTextureType::kRectangle;
Brian Salomon53c909e2020-02-13 13:54:24 -0500279 auto filter = te.fSampler.samplerState().filter();
280 FilterLogic filterLogic[2] = {GetFilterLogic(m[0], filter),
281 GetFilterLogic(m[1], filter)};
282
Brian Salomond71548a2020-02-29 19:43:30 -0500283 const char* borderName = nullptr;
284 if (te.fShaderModes[0] == ShaderMode::kClampToBorder ||
285 te.fShaderModes[1] == ShaderMode::kClampToBorder) {
286 fBorderUni = args.fUniformHandler->addUniform(
Ethan Nicholas16464c32020-04-06 13:53:05 -0400287 &te, kFragment_GrShaderFlag, kHalf4_GrSLType, "border", &borderName);
Brian Salomond71548a2020-02-29 19:43:30 -0500288 }
Brian Salomon53c909e2020-02-13 13:54:24 -0500289 auto modeUsesSubset = [](ShaderMode m) {
290 return m == ShaderMode::kRepeat || m == ShaderMode::kMirrorRepeat ||
Brian Salomond71548a2020-02-29 19:43:30 -0500291 m == ShaderMode::kClampToBorder;
Brian Salomon53c909e2020-02-13 13:54:24 -0500292 };
293
294 auto modeUsesClamp = [filter](ShaderMode m) {
295 return m != ShaderMode::kNone &&
Brian Salomond71548a2020-02-29 19:43:30 -0500296 (m != ShaderMode::kClampToBorder || filter > Filter::kNearest);
Brian Salomon53c909e2020-02-13 13:54:24 -0500297 };
298
299 bool useSubset[2] = {modeUsesSubset(m[0]), modeUsesSubset(m[1])};
300 bool useClamp [2] = {modeUsesClamp (m[0]), modeUsesClamp (m[1])};
301
302 const char* subsetName = nullptr;
303 if (useSubset[0] || useSubset[1]) {
304 fSubsetUni = args.fUniformHandler->addUniform(
Ethan Nicholas16464c32020-04-06 13:53:05 -0400305 &te, kFragment_GrShaderFlag, kFloat4_GrSLType, "subset", &subsetName);
Brian Salomon53c909e2020-02-13 13:54:24 -0500306 }
307
308 const char* clampName = nullptr;
309 if (useClamp[0] || useClamp[1]) {
310 fClampUni = args.fUniformHandler->addUniform(
Ethan Nicholas16464c32020-04-06 13:53:05 -0400311 &te, kFragment_GrShaderFlag, kFloat4_GrSLType, "clamp", &clampName);
Brian Salomon53c909e2020-02-13 13:54:24 -0500312 }
313
314 // To keep things a little simpler, when we have filtering logic in the shader we
315 // operate on unnormalized texture coordinates. We add a uniform that stores
316 // {w, h, 1/w, 1/h} in a float4.
317 const char* norm = nullptr;
318 if (normCoords && (filterLogic[0] != FilterLogic::kNone ||
319 filterLogic[1] != FilterLogic::kNone)) {
320 // TODO: Detect support for textureSize() or polyfill textureSize() in SkSL and
321 // always use?
Ethan Nicholas16464c32020-04-06 13:53:05 -0400322 fNormUni = args.fUniformHandler->addUniform(&te, kFragment_GrShaderFlag,
Brian Salomon53c909e2020-02-13 13:54:24 -0500323 kFloat4_GrSLType, "norm", &norm);
324 // TODO: Remove the normalization from the CoordTransform to skip unnormalizing
325 // step here.
326 fb->codeAppendf("inCoord *= %s.xy;", norm);
327 }
328
329 // Generates a string to read at a coordinate, normalizing coords if necessary.
Brian Salomonf46f19b2020-02-13 14:11:28 -0500330 auto read = [&](const char* coord) {
Brian Salomon53c909e2020-02-13 13:54:24 -0500331 SkString result;
332 SkString normCoord;
333 if (norm) {
334 normCoord.printf("(%s) * %s.zw", coord, norm);
335 } else {
336 normCoord = coord;
337 }
Brian Salomonf46f19b2020-02-13 14:11:28 -0500338 fb->appendTextureLookup(&result, args.fTexSamplers[0], normCoord.c_str());
Brian Salomon53c909e2020-02-13 13:54:24 -0500339 return result;
340 };
341
342 // Implements coord wrapping for kRepeat and kMirrorRepeat
Brian Salomonf46f19b2020-02-13 14:11:28 -0500343 auto subsetCoord = [&](ShaderMode mode,
344 const char* coordSwizzle,
345 const char* subsetStartSwizzle,
346 const char* subsetStopSwizzle,
347 const char* extraCoord,
348 const char* coordWeight) {
Brian Salomon66356c22020-02-11 15:22:18 -0500349 switch (mode) {
Brian Salomon53c909e2020-02-13 13:54:24 -0500350 // These modes either don't use the subset rect or don't need to map the
351 // coords to be within the subset.
Brian Salomon66356c22020-02-11 15:22:18 -0500352 case ShaderMode::kNone:
Brian Salomond71548a2020-02-29 19:43:30 -0500353 case ShaderMode::kClampToBorder:
Brian Salomon66356c22020-02-11 15:22:18 -0500354 case ShaderMode::kClamp:
Brian Salomon53c909e2020-02-13 13:54:24 -0500355 fb->codeAppendf("subsetCoord.%s = inCoord.%s;", coordSwizzle,
356 coordSwizzle);
Brian Salomon66356c22020-02-11 15:22:18 -0500357 break;
358 case ShaderMode::kRepeat:
Brian Salomonf46f19b2020-02-13 14:11:28 -0500359 if (filter == Filter::kMipMap) {
360 // The approach here is to generate two sets of texture coords that
361 // are both "moving" at the same speed (if not direction) as
362 // inCoords. We accomplish that by using two out of phase mirror
363 // repeat coords. We will always sample using both coords but the
364 // read from the upward sloping one is selected using a weight
365 // that transitions from one set to the other near the reflection
366 // point. Like the coords, the weight is a saw-tooth function,
367 // phase-shifted, vertically translated, and then clamped to 0..1.
368 // TODO: Skip this and use textureGrad() when available.
369 SkASSERT(extraCoord);
370 SkASSERT(coordWeight);
371 fb->codeAppend("{");
372 fb->codeAppendf("float w = %s.%s - %s.%s;", subsetName,
373 subsetStopSwizzle, subsetName, subsetStartSwizzle);
374 fb->codeAppendf("float w2 = 2 * w;");
375 fb->codeAppendf("float d = inCoord.%s - %s.%s;", coordSwizzle,
376 subsetName, subsetStartSwizzle);
377 fb->codeAppend("float m = mod(d, w2);");
378 fb->codeAppend("float o = mix(m, w2 - m, step(w, m));");
379 fb->codeAppendf("subsetCoord.%s = o + %s.%s;", coordSwizzle,
380 subsetName, subsetStartSwizzle);
381 fb->codeAppendf("%s = w - o + %s.%s;", extraCoord, subsetName,
382 subsetStartSwizzle);
383 // coordWeight is used as the third param of mix() to blend between a
384 // sample taken using subsetCoord and a sample at extraCoord.
385 fb->codeAppend("float hw = w/2;");
386 fb->codeAppend("float n = mod(d - hw, w2);");
387 fb->codeAppendf(
388 "%s = saturate(half(mix(n, w2 - n, step(w, n)) - hw + "
389 "0.5));",
390 coordWeight);
391 fb->codeAppend("}");
392 } else {
393 fb->codeAppendf(
394 "subsetCoord.%s = mod(inCoord.%s - %s.%s, %s.%s - %s.%s) + "
395 "%s.%s;",
396 coordSwizzle, coordSwizzle, subsetName, subsetStartSwizzle,
397 subsetName, subsetStopSwizzle, subsetName,
398 subsetStartSwizzle, subsetName, subsetStartSwizzle);
399 }
Brian Salomon66356c22020-02-11 15:22:18 -0500400 break;
401 case ShaderMode::kMirrorRepeat: {
402 fb->codeAppend("{");
403 fb->codeAppendf("float w = %s.%s - %s.%s;", subsetName,
404 subsetStopSwizzle, subsetName, subsetStartSwizzle);
405 fb->codeAppendf("float w2 = 2 * w;");
406 fb->codeAppendf("float m = mod(inCoord.%s - %s.%s, w2);", coordSwizzle,
407 subsetName, subsetStartSwizzle);
Brian Salomon53c909e2020-02-13 13:54:24 -0500408 fb->codeAppendf("subsetCoord.%s = mix(m, w2 - m, step(w, m)) + %s.%s;",
Brian Salomon66356c22020-02-11 15:22:18 -0500409 coordSwizzle, subsetName, subsetStartSwizzle);
410 fb->codeAppend("}");
411 break;
412 }
413 }
414 };
415
Brian Salomonf46f19b2020-02-13 14:11:28 -0500416 auto clampCoord = [&](bool clamp,
417 const char* coordSwizzle,
418 const char* clampStartSwizzle,
419 const char* clampStopSwizzle) {
Brian Salomon53c909e2020-02-13 13:54:24 -0500420 if (clamp) {
421 fb->codeAppendf("clampedCoord.%s = clamp(subsetCoord.%s, %s.%s, %s.%s);",
422 coordSwizzle, coordSwizzle, clampName, clampStartSwizzle,
423 clampName, clampStopSwizzle);
424 } else {
425 fb->codeAppendf("clampedCoord.%s = subsetCoord.%s;", coordSwizzle,
426 coordSwizzle);
427 }
428 };
429
Brian Salomonf46f19b2020-02-13 14:11:28 -0500430 // Insert vars for extra coords and blending weights for kRepeatMipMap.
431 const char* extraRepeatCoordX = nullptr;
432 const char* repeatCoordWeightX = nullptr;
433 const char* extraRepeatCoordY = nullptr;
434 const char* repeatCoordWeightY = nullptr;
435 if (filterLogic[0] == FilterLogic::kRepeatMipMap) {
436 fb->codeAppend("float extraRepeatCoordX; half repeatCoordWeightX;");
437 extraRepeatCoordX = "extraRepeatCoordX";
438 repeatCoordWeightX = "repeatCoordWeightX";
439 }
440 if (filterLogic[1] == FilterLogic::kRepeatMipMap) {
441 fb->codeAppend("float extraRepeatCoordY; half repeatCoordWeightY;");
442 extraRepeatCoordY = "extraRepeatCoordY";
443 repeatCoordWeightY = "repeatCoordWeightY";
444 }
445
446 // Apply subset rect and clamp rect to coords.
Brian Salomon53c909e2020-02-13 13:54:24 -0500447 fb->codeAppend("float2 subsetCoord;");
Brian Salomonf46f19b2020-02-13 14:11:28 -0500448 subsetCoord(te.fShaderModes[0], "x", "x", "z", extraRepeatCoordX,
449 repeatCoordWeightX);
450 subsetCoord(te.fShaderModes[1], "y", "y", "w", extraRepeatCoordY,
451 repeatCoordWeightY);
Brian Salomon53c909e2020-02-13 13:54:24 -0500452 fb->codeAppend("float2 clampedCoord;");
453 clampCoord(useClamp[0], "x", "x", "z");
454 clampCoord(useClamp[1], "y", "y", "w");
Brian Salomonca6b2f42020-01-24 11:31:21 -0500455
Brian Salomonf46f19b2020-02-13 14:11:28 -0500456 // Additional clamping for the extra coords for kRepeatMipMap.
457 if (filterLogic[0] == FilterLogic::kRepeatMipMap) {
458 fb->codeAppendf("extraRepeatCoordX = clamp(extraRepeatCoordX, %s.x, %s.z);",
459 clampName, clampName);
460 }
461 if (filterLogic[1] == FilterLogic::kRepeatMipMap) {
462 fb->codeAppendf("extraRepeatCoordY = clamp(extraRepeatCoordY, %s.y, %s.w);",
463 clampName, clampName);
464 }
465
466 // Do the 2 or 4 texture reads for kRepeatMipMap and then apply the weight(s)
467 // to blend between them. If neither direction is kRepeatMipMap do a single
468 // read at clampedCoord.
469 if (filterLogic[0] == FilterLogic::kRepeatMipMap &&
470 filterLogic[1] == FilterLogic::kRepeatMipMap) {
471 fb->codeAppendf(
472 "half4 textureColor ="
473 " mix(mix(%s, %s, repeatCoordWeightX),"
474 " mix(%s, %s, repeatCoordWeightX),"
475 " repeatCoordWeightY);",
476 read("clampedCoord").c_str(),
477 read("float2(extraRepeatCoordX, clampedCoord.y)").c_str(),
478 read("float2(clampedCoord.x, extraRepeatCoordY)").c_str(),
479 read("float2(extraRepeatCoordX, extraRepeatCoordY)").c_str());
480
481 } else if (filterLogic[0] == FilterLogic::kRepeatMipMap) {
482 fb->codeAppendf("half4 textureColor = mix(%s, %s, repeatCoordWeightX);",
483 read("clampedCoord").c_str(),
484 read("float2(extraRepeatCoordX, clampedCoord.y)").c_str());
485 } else if (filterLogic[1] == FilterLogic::kRepeatMipMap) {
486 fb->codeAppendf("half4 textureColor = mix(%s, %s, repeatCoordWeightY);",
487 read("clampedCoord").c_str(),
488 read("float2(clampedCoord.x, extraRepeatCoordY)").c_str());
489 } else {
490 fb->codeAppendf("half4 textureColor = %s;", read("clampedCoord").c_str());
491 }
Brian Salomonca6b2f42020-01-24 11:31:21 -0500492
Brian Salomon53c909e2020-02-13 13:54:24 -0500493 // Strings for extra texture reads used only in kRepeatBilerp
494 SkString repeatBilerpReadX;
495 SkString repeatBilerpReadY;
496
497 // Calculate the amount the coord moved for clamping. This will be used
Brian Salomond71548a2020-02-29 19:43:30 -0500498 // to implement shader-based filtering for kClampToBorder and kRepeat.
Brian Salomon53c909e2020-02-13 13:54:24 -0500499
500 if (filterLogic[0] == FilterLogic::kRepeatBilerp ||
Brian Salomond71548a2020-02-29 19:43:30 -0500501 filterLogic[0] == FilterLogic::kClampToBorderFilter) {
Brian Salomon53c909e2020-02-13 13:54:24 -0500502 fb->codeAppend("half errX = half(subsetCoord.x - clampedCoord.x);");
503 fb->codeAppendf("float repeatCoordX = errX > 0 ? %s.x : %s.z;", clampName,
504 clampName);
505 repeatBilerpReadX = read("float2(repeatCoordX, clampedCoord.y)");
Brian Salomonca6b2f42020-01-24 11:31:21 -0500506 }
Brian Salomon53c909e2020-02-13 13:54:24 -0500507 if (filterLogic[1] == FilterLogic::kRepeatBilerp ||
Brian Salomond71548a2020-02-29 19:43:30 -0500508 filterLogic[1] == FilterLogic::kClampToBorderFilter) {
Brian Salomon53c909e2020-02-13 13:54:24 -0500509 fb->codeAppend("half errY = half(subsetCoord.y - clampedCoord.y);");
510 fb->codeAppendf("float repeatCoordY = errY > 0 ? %s.y : %s.w;", clampName,
511 clampName);
512 repeatBilerpReadY = read("float2(clampedCoord.x, repeatCoordY)");
513 }
514
515 // Add logic for kRepeatBilerp. Do 1 or 3 more texture reads depending
516 // on whether both modes are kRepeat and whether we're near a single subset edge
517 // or a corner. Then blend the multiple reads using the err values calculated
518 // above.
519 const char* ifStr = "if";
520 if (filterLogic[0] == FilterLogic::kRepeatBilerp &&
521 filterLogic[1] == FilterLogic::kRepeatBilerp) {
522 auto repeatBilerpReadXY = read("float2(repeatCoordX, repeatCoordY)");
523 fb->codeAppendf(
524 "if (errX != 0 && errY != 0) {"
Brian Salomon3fcf83a2020-02-23 21:29:01 -0500525 " errX = abs(errX);"
Brian Salomon53c909e2020-02-13 13:54:24 -0500526 " textureColor = mix(mix(textureColor, %s, errX),"
527 " mix(%s, %s, errX),"
Brian Salomon3fcf83a2020-02-23 21:29:01 -0500528 " abs(errY));"
Brian Salomon53c909e2020-02-13 13:54:24 -0500529 "}",
530 repeatBilerpReadX.c_str(), repeatBilerpReadY.c_str(),
531 repeatBilerpReadXY.c_str());
532 ifStr = "else if";
533 }
534 if (filterLogic[0] == FilterLogic::kRepeatBilerp) {
535 fb->codeAppendf(
536 "%s (errX != 0) {"
537 " textureColor = mix(textureColor, %s, abs(errX));"
538 "}",
539 ifStr, repeatBilerpReadX.c_str());
540 }
541 if (filterLogic[1] == FilterLogic::kRepeatBilerp) {
542 fb->codeAppendf(
543 "%s (errY != 0) {"
544 " textureColor = mix(textureColor, %s, abs(errY));"
545 "}",
546 ifStr, repeatBilerpReadY.c_str());
547 }
548
Brian Salomond71548a2020-02-29 19:43:30 -0500549 // Do soft edge shader filtering against border color for kClampToBorderFilter using
Brian Salomon53c909e2020-02-13 13:54:24 -0500550 // the err values calculated above.
Brian Salomond71548a2020-02-29 19:43:30 -0500551 if (filterLogic[0] == FilterLogic::kClampToBorderFilter) {
552 fb->codeAppendf("textureColor = mix(textureColor, %s, min(abs(errX), 1));",
553 borderName);
Brian Salomon53c909e2020-02-13 13:54:24 -0500554 }
Brian Salomond71548a2020-02-29 19:43:30 -0500555 if (filterLogic[1] == FilterLogic::kClampToBorderFilter) {
556 fb->codeAppendf("textureColor = mix(textureColor, %s, min(abs(errY), 1));",
557 borderName);
Brian Salomon53c909e2020-02-13 13:54:24 -0500558 }
559
Brian Salomond71548a2020-02-29 19:43:30 -0500560 // Do hard-edge shader transition to border color for kClampToBorderNearest at the
Brian Salomon53c909e2020-02-13 13:54:24 -0500561 // subset boundaries.
Brian Salomond71548a2020-02-29 19:43:30 -0500562 if (filterLogic[0] == FilterLogic::kClampToBorderNearest) {
Brian Salomon53c909e2020-02-13 13:54:24 -0500563 fb->codeAppendf(
564 "if (inCoord.x < %s.x || inCoord.x > %s.z) {"
Brian Salomond71548a2020-02-29 19:43:30 -0500565 " textureColor = %s;"
Brian Salomon53c909e2020-02-13 13:54:24 -0500566 "}",
Brian Salomond71548a2020-02-29 19:43:30 -0500567 subsetName, subsetName, borderName);
Brian Salomon53c909e2020-02-13 13:54:24 -0500568 }
Brian Salomond71548a2020-02-29 19:43:30 -0500569 if (filterLogic[1] == FilterLogic::kClampToBorderNearest) {
Brian Salomon53c909e2020-02-13 13:54:24 -0500570 fb->codeAppendf(
571 "if (inCoord.y < %s.y || inCoord.y > %s.w) {"
Brian Salomond71548a2020-02-29 19:43:30 -0500572 " textureColor = %s;"
Brian Salomon53c909e2020-02-13 13:54:24 -0500573 "}",
Brian Salomond71548a2020-02-29 19:43:30 -0500574 subsetName, subsetName, borderName);
Brian Salomon53c909e2020-02-13 13:54:24 -0500575 }
576 fb->codeAppendf("%s = %s * textureColor;", args.fOutputColor, args.fInputColor);
Brian Salomonca6b2f42020-01-24 11:31:21 -0500577 }
578 }
579
580 protected:
581 void onSetData(const GrGLSLProgramDataManager& pdm,
582 const GrFragmentProcessor& fp) override {
583 const auto& te = fp.cast<GrTextureEffect>();
Brian Salomonca6b2f42020-01-24 11:31:21 -0500584
Brian Salomon53c909e2020-02-13 13:54:24 -0500585 const float w = te.fSampler.peekTexture()->width();
586 const float h = te.fSampler.peekTexture()->height();
587 const auto& s = te.fSubset;
588 const auto& c = te.fClamp;
Brian Salomonca6b2f42020-01-24 11:31:21 -0500589
Brian Salomon53c909e2020-02-13 13:54:24 -0500590 auto type = te.fSampler.peekTexture()->texturePriv().textureType();
591
592 float norm[4] = {w, h, 1.f/w, 1.f/h};
593
594 if (fNormUni.isValid()) {
595 pdm.set4fv(fNormUni, 1, norm);
596 SkASSERT(type != GrTextureType::kRectangle);
597 }
598
599 auto pushRect = [&](float rect[4], UniformHandle uni) {
Brian Salomonca6b2f42020-01-24 11:31:21 -0500600 if (te.fSampler.view().origin() == kBottomLeft_GrSurfaceOrigin) {
601 rect[1] = h - rect[1];
602 rect[3] = h - rect[3];
603 std::swap(rect[1], rect[3]);
604 }
Brian Salomon53c909e2020-02-13 13:54:24 -0500605 if (!fNormUni.isValid() && type != GrTextureType::kRectangle) {
606 rect[0] *= norm[2];
607 rect[2] *= norm[2];
608 rect[1] *= norm[3];
609 rect[3] *= norm[3];
Brian Salomonca6b2f42020-01-24 11:31:21 -0500610 }
Brian Salomon53c909e2020-02-13 13:54:24 -0500611 pdm.set4fv(uni, 1, rect);
612 };
Brian Salomonca6b2f42020-01-24 11:31:21 -0500613
Brian Salomon53c909e2020-02-13 13:54:24 -0500614 if (fSubsetUni.isValid()) {
615 float subset[] = {s.fLeft, s.fTop, s.fRight, s.fBottom};
616 pushRect(subset, fSubsetUni);
617 }
618 if (fClampUni.isValid()) {
619 float subset[] = {c.fLeft, c.fTop, c.fRight, c.fBottom};
620 pushRect(subset, fClampUni);
Brian Salomonca6b2f42020-01-24 11:31:21 -0500621 }
Brian Salomond71548a2020-02-29 19:43:30 -0500622 if (fBorderUni.isValid()) {
623 pdm.set4fv(fBorderUni, 1, te.fBorder);
624 }
Brian Salomonb8f098d2020-01-07 11:15:44 -0500625 }
626 };
627 return new Impl;
628}
629
Brian Salomonca6b2f42020-01-24 11:31:21 -0500630void GrTextureEffect::onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder* b) const {
Brian Salomonca6b2f42020-01-24 11:31:21 -0500631 auto m0 = static_cast<uint32_t>(fShaderModes[0]);
632 auto m1 = static_cast<uint32_t>(fShaderModes[1]);
Brian Salomon53c909e2020-02-13 13:54:24 -0500633 auto filter = fSampler.samplerState().filter();
634 auto l0 = static_cast<uint32_t>(GetFilterLogic(fShaderModes[0], filter));
635 auto l1 = static_cast<uint32_t>(GetFilterLogic(fShaderModes[1], filter));
636 b->add32((l0 << 24) | (l1 << 16) | (m0 << 8) | m1);
Brian Salomonca6b2f42020-01-24 11:31:21 -0500637}
Brian Salomonb8f098d2020-01-07 11:15:44 -0500638
Brian Salomonca6b2f42020-01-24 11:31:21 -0500639bool GrTextureEffect::onIsEqual(const GrFragmentProcessor& other) const {
Ethan Nicholas94996eb2020-04-01 13:46:10 -0400640 auto& that = other.cast<GrTextureEffect>();
Brian Salomond71548a2020-02-29 19:43:30 -0500641 if (fShaderModes[0] != that.fShaderModes[0] || fShaderModes[1] != that.fShaderModes[1]) {
642 return false;
643 }
644 if (fSubset != that.fSubset) {
645 return false;
646 }
647 if ((fShaderModes[0] == ShaderMode::kClampToBorder ||
648 fShaderModes[1] == ShaderMode::kClampToBorder) &&
649 !std::equal(fBorder, fBorder + 4, that.fBorder)) {
650 return false;
651 }
652 return true;
Brian Salomonb8f098d2020-01-07 11:15:44 -0500653}
654
Greg Danield2ccbb52020-02-05 10:45:39 -0500655GrTextureEffect::GrTextureEffect(GrSurfaceProxyView view, SkAlphaType alphaType,
Brian Salomonca6b2f42020-01-24 11:31:21 -0500656 const SkMatrix& matrix, const Sampling& sampling)
Brian Salomonb8f098d2020-01-07 11:15:44 -0500657 : GrFragmentProcessor(kGrTextureEffect_ClassID,
Brian Salomond71548a2020-02-29 19:43:30 -0500658 ModulateForSamplerOptFlags(alphaType, sampling.hasBorderAlpha()))
Greg Daniel67da6652020-02-12 14:09:34 -0500659 , fCoordTransform(matrix, view.proxy(), view.origin())
Greg Danield2ccbb52020-02-05 10:45:39 -0500660 , fSampler(std::move(view), sampling.fHWSampler)
Brian Salomonca6b2f42020-01-24 11:31:21 -0500661 , fSubset(sampling.fShaderSubset)
Brian Salomon53c909e2020-02-13 13:54:24 -0500662 , fClamp(sampling.fShaderClamp)
Brian Salomonca6b2f42020-01-24 11:31:21 -0500663 , fShaderModes{sampling.fShaderModes[0], sampling.fShaderModes[1]} {
664 // We always compare the range even when it isn't used so assert we have canonical don't care
665 // values.
666 SkASSERT(fShaderModes[0] != ShaderMode::kNone || (fSubset.fLeft == 0 && fSubset.fRight == 0));
667 SkASSERT(fShaderModes[1] != ShaderMode::kNone || (fSubset.fTop == 0 && fSubset.fBottom == 0));
Brian Salomonb8f098d2020-01-07 11:15:44 -0500668 this->setTextureSamplerCnt(1);
669 this->addCoordTransform(&fCoordTransform);
Brian Salomond71548a2020-02-29 19:43:30 -0500670 std::copy_n(sampling.fBorder, 4, fBorder);
Brian Salomonb8f098d2020-01-07 11:15:44 -0500671}
672
673GrTextureEffect::GrTextureEffect(const GrTextureEffect& src)
674 : INHERITED(kGrTextureEffect_ClassID, src.optimizationFlags())
675 , fCoordTransform(src.fCoordTransform)
Brian Salomonca6b2f42020-01-24 11:31:21 -0500676 , fSampler(src.fSampler)
677 , fSubset(src.fSubset)
Brian Salomon922496f2020-02-14 12:20:14 -0500678 , fClamp(src.fClamp)
Brian Salomonca6b2f42020-01-24 11:31:21 -0500679 , fShaderModes{src.fShaderModes[0], src.fShaderModes[1]} {
Brian Salomonba90ce92020-03-04 12:38:04 -0500680 std::copy_n(src.fBorder, 4, fBorder);
Brian Salomonb8f098d2020-01-07 11:15:44 -0500681 this->setTextureSamplerCnt(1);
682 this->addCoordTransform(&fCoordTransform);
683}
684
685std::unique_ptr<GrFragmentProcessor> GrTextureEffect::clone() const {
686 return std::unique_ptr<GrFragmentProcessor>(new GrTextureEffect(*this));
687}
688
689const GrFragmentProcessor::TextureSampler& GrTextureEffect::onTextureSampler(int) const {
690 return fSampler;
691}
692
693GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrTextureEffect);
694#if GR_TEST_UTILS
695std::unique_ptr<GrFragmentProcessor> GrTextureEffect::TestCreate(GrProcessorTestData* testData) {
Greg Daniel026a60c2020-02-12 10:53:51 -0500696 auto [view, ct, at] = testData->randomView();
Brian Salomon53c909e2020-02-13 13:54:24 -0500697 Mode wrapModes[2];
Brian Salomonb8f098d2020-01-07 11:15:44 -0500698 GrTest::TestWrapModes(testData->fRandom, wrapModes);
Brian Salomonb8f098d2020-01-07 11:15:44 -0500699
Brian Salomon53c909e2020-02-13 13:54:24 -0500700 Filter filter;
701 if (view.asTextureProxy()->mipMapped() == GrMipMapped::kYes) {
702 switch (testData->fRandom->nextULessThan(3)) {
703 case 0:
704 filter = Filter::kNearest;
705 break;
706 case 1:
707 filter = Filter::kBilerp;
708 break;
709 default:
710 filter = Filter::kMipMap;
711 break;
712 }
713 } else {
714 filter = testData->fRandom->nextBool() ? Filter::kBilerp : Filter::kNearest;
715 }
716 GrSamplerState params(wrapModes, filter);
Brian Salomonb8f098d2020-01-07 11:15:44 -0500717
718 const SkMatrix& matrix = GrTest::TestMatrix(testData->fRandom);
Greg Danield2ccbb52020-02-05 10:45:39 -0500719 return GrTextureEffect::Make(std::move(view), at, matrix, params, *testData->caps());
Brian Salomonb8f098d2020-01-07 11:15:44 -0500720}
721#endif