blob: 0032bf61f6159b470a18f4d3c52cf1d677ad19f3 [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
10#include "include/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
21GrTextureEffect::Sampling::Sampling(const GrSurfaceProxy& proxy,
22 GrSamplerState sampler,
23 const SkRect& subset,
Brian Salomonca6b2f42020-01-24 11:31:21 -050024 const SkRect* domain,
25 const GrCaps& caps) {
Brian Salomon53c909e2020-02-13 13:54:24 -050026 struct Span {
27 float fA = 0.f, fB = 0.f;
Brian Salomonca6b2f42020-01-24 11:31:21 -050028
Brian Salomon53c909e2020-02-13 13:54:24 -050029 Span makeInset(float o) const {
30 Span r = {fA + o, fB - o};
31 if (r.fA > r.fB) {
32 r.fA = r.fB = (r.fA + r.fB) / 2;
33 }
Brian Salomonca6b2f42020-01-24 11:31:21 -050034 return r;
35 }
36
Brian Salomon53c909e2020-02-13 13:54:24 -050037 bool contains(Span r) const { return fA <= r.fA && fB >= r.fB; }
38 };
39 struct Result1D {
40 ShaderMode fShaderMode;
41 Span fShaderSubset;
42 Span fShaderClamp;
43 Mode fHWMode;
44 };
45
46 auto type = proxy.asTextureProxy()->textureType();
47 auto filter = sampler.filter();
48
49 auto resolve = [type, &caps, filter](int size, Mode mode, Span subset, Span domain) {
50 Result1D r;
51 bool canDoHW = (mode != Mode::kClampToBorder || caps.clampToBorderSupport()) &&
52 (mode == Mode::kClamp || caps.npotTextureTileSupport() || SkIsPow2(size)) &&
53 (mode == Mode::kClamp || mode == Mode::kClampToBorder ||
54 type == GrTextureType::k2D);
55 if (canDoHW && size > 0 && subset.fA <= 0 && subset.fB >= size) {
56 r.fShaderMode = ShaderMode::kNone;
57 r.fHWMode = mode;
58 r.fShaderSubset = r.fShaderClamp = {0, 0};
59 return r;
60 }
61
62 r.fShaderSubset = subset;
Brian Salomoned729f92020-02-05 12:15:18 -050063 bool domainIsSafe = false;
Brian Salomoned729f92020-02-05 12:15:18 -050064 if (filter == Filter::kNearest) {
65 Span isubset{sk_float_floor(subset.fA), sk_float_ceil(subset.fB)};
66 if (domain.fA > isubset.fA && domain.fB < isubset.fB) {
67 domainIsSafe = true;
68 }
Brian Salomon53c909e2020-02-13 13:54:24 -050069 // This inset prevents sampling neighboring texels that could occur when
70 // texture coords fall exactly at texel boundaries (depending on precision
71 // and GPU-specific snapping at the boundary).
72 r.fShaderClamp = isubset.makeInset(0.5f);
73 } else {
74 r.fShaderClamp = subset.makeInset(0.5f);
75 if (r.fShaderClamp.contains(domain)) {
76 domainIsSafe = true;
Brian Salomoned729f92020-02-05 12:15:18 -050077 }
Brian Salomoned729f92020-02-05 12:15:18 -050078 }
Brian Salomon53c909e2020-02-13 13:54:24 -050079 if (domainIsSafe) {
80 // The domain of coords that will be used won't access texels outside of the subset.
81 // So the wrap mode effectively doesn't matter. We use kClamp since it is always
82 // supported.
Brian Salomonca6b2f42020-01-24 11:31:21 -050083 r.fShaderMode = ShaderMode::kNone;
Brian Salomon53c909e2020-02-13 13:54:24 -050084 r.fHWMode = Mode::kClamp;
85 r.fShaderSubset = r.fShaderClamp = {0, 0};
Brian Salomonca6b2f42020-01-24 11:31:21 -050086 return r;
87 }
Brian Salomonca6b2f42020-01-24 11:31:21 -050088 r.fShaderMode = static_cast<ShaderMode>(mode);
89 r.fHWMode = Mode::kClamp;
90 return r;
91 };
92
93 SkISize dim = proxy.isFullyLazy() ? SkISize{-1, -1} : proxy.backingStoreDimensions();
94
95 Span subsetX{subset.fLeft, subset.fRight};
96 auto domainX = domain ? Span{domain->fLeft, domain->fRight}
97 : Span{SK_FloatNegativeInfinity, SK_FloatInfinity};
98 auto x = resolve(dim.width(), sampler.wrapModeX(), subsetX, domainX);
99
100 Span subsetY{subset.fTop, subset.fBottom};
101 auto domainY = domain ? Span{domain->fTop, domain->fBottom}
102 : Span{SK_FloatNegativeInfinity, SK_FloatInfinity};
103 auto y = resolve(dim.height(), sampler.wrapModeY(), subsetY, domainY);
104
Brian Salomon53c909e2020-02-13 13:54:24 -0500105 fHWSampler = {x.fHWMode, y.fHWMode, filter};
Brian Salomonca6b2f42020-01-24 11:31:21 -0500106 fShaderModes[0] = x.fShaderMode;
107 fShaderModes[1] = y.fShaderMode;
108 fShaderSubset = {x.fShaderSubset.fA, y.fShaderSubset.fA,
109 x.fShaderSubset.fB, y.fShaderSubset.fB};
Brian Salomon53c909e2020-02-13 13:54:24 -0500110 fShaderClamp = {x.fShaderClamp.fA, y.fShaderClamp.fA,
111 x.fShaderClamp.fB, y.fShaderClamp.fB};
Brian Salomonca6b2f42020-01-24 11:31:21 -0500112}
113
114bool GrTextureEffect::Sampling::usesDecal() const {
115 return fShaderModes[0] == ShaderMode::kDecal || fShaderModes[1] == ShaderMode::kDecal ||
116 fHWSampler.wrapModeX() == GrSamplerState::WrapMode::kClampToBorder ||
117 fHWSampler.wrapModeY() == GrSamplerState::WrapMode::kClampToBorder;
118}
119
Greg Danield2ccbb52020-02-05 10:45:39 -0500120std::unique_ptr<GrFragmentProcessor> GrTextureEffect::Make(GrSurfaceProxyView view,
Brian Salomonb8f098d2020-01-07 11:15:44 -0500121 SkAlphaType alphaType,
122 const SkMatrix& matrix,
Brian Salomon53c909e2020-02-13 13:54:24 -0500123 Filter filter) {
Brian Salomonb8f098d2020-01-07 11:15:44 -0500124 return std::unique_ptr<GrFragmentProcessor>(
Greg Danield2ccbb52020-02-05 10:45:39 -0500125 new GrTextureEffect(std::move(view), alphaType, matrix, Sampling(filter)));
Brian Salomonca6b2f42020-01-24 11:31:21 -0500126}
127
Greg Danield2ccbb52020-02-05 10:45:39 -0500128std::unique_ptr<GrFragmentProcessor> GrTextureEffect::Make(GrSurfaceProxyView view,
Brian Salomonca6b2f42020-01-24 11:31:21 -0500129 SkAlphaType alphaType,
130 const SkMatrix& matrix,
131 GrSamplerState sampler,
132 const GrCaps& caps) {
Brian Salomon53c909e2020-02-13 13:54:24 -0500133 Sampling sampling(*view.proxy(), sampler, SkRect::Make(view.proxy()->dimensions()), nullptr,
134 caps);
Brian Salomonca6b2f42020-01-24 11:31:21 -0500135 return std::unique_ptr<GrFragmentProcessor>(
Greg Danield2ccbb52020-02-05 10:45:39 -0500136 new GrTextureEffect(std::move(view), alphaType, matrix, sampling));
Brian Salomonca6b2f42020-01-24 11:31:21 -0500137}
138
Greg Danield2ccbb52020-02-05 10:45:39 -0500139std::unique_ptr<GrFragmentProcessor> GrTextureEffect::MakeSubset(GrSurfaceProxyView view,
Brian Salomonca6b2f42020-01-24 11:31:21 -0500140 SkAlphaType alphaType,
141 const SkMatrix& matrix,
142 GrSamplerState sampler,
143 const SkRect& subset,
144 const GrCaps& caps) {
Brian Salomoned729f92020-02-05 12:15:18 -0500145 Sampling sampling(*view.proxy(), sampler, subset, nullptr, caps);
Brian Salomonca6b2f42020-01-24 11:31:21 -0500146 return std::unique_ptr<GrFragmentProcessor>(
Greg Danield2ccbb52020-02-05 10:45:39 -0500147 new GrTextureEffect(std::move(view), alphaType, matrix, sampling));
Brian Salomonca6b2f42020-01-24 11:31:21 -0500148}
149
Greg Danield2ccbb52020-02-05 10:45:39 -0500150std::unique_ptr<GrFragmentProcessor> GrTextureEffect::MakeSubset(GrSurfaceProxyView view,
Brian Salomonca6b2f42020-01-24 11:31:21 -0500151 SkAlphaType alphaType,
152 const SkMatrix& matrix,
153 GrSamplerState sampler,
154 const SkRect& subset,
155 const SkRect& domain,
156 const GrCaps& caps) {
Brian Salomoned729f92020-02-05 12:15:18 -0500157 Sampling sampling(*view.proxy(), sampler, subset, &domain, caps);
Brian Salomonca6b2f42020-01-24 11:31:21 -0500158 return std::unique_ptr<GrFragmentProcessor>(
Greg Danield2ccbb52020-02-05 10:45:39 -0500159 new GrTextureEffect(std::move(view), alphaType, matrix, sampling));
Brian Salomonb8f098d2020-01-07 11:15:44 -0500160}
161
Brian Salomon53c909e2020-02-13 13:54:24 -0500162GrTextureEffect::FilterLogic GrTextureEffect::GetFilterLogic(ShaderMode mode,
163 GrSamplerState::Filter filter) {
164 switch (mode) {
165 case ShaderMode::kMirrorRepeat:
166 case ShaderMode::kNone:
167 case ShaderMode::kClamp:
168 return FilterLogic::kNone;
169 case ShaderMode::kRepeat:
170 switch (filter) {
171 case GrSamplerState::Filter::kNearest:
172 return FilterLogic::kNone;
173 case GrSamplerState::Filter::kBilerp:
174 return FilterLogic::kRepeatBilerp;
175 case GrSamplerState::Filter::kMipMap:
Brian Salomonf46f19b2020-02-13 14:11:28 -0500176 return FilterLogic::kRepeatMipMap;
Brian Salomon53c909e2020-02-13 13:54:24 -0500177 }
178 SkUNREACHABLE;
179 case ShaderMode::kDecal:
180 return filter > GrSamplerState::Filter::kNearest ? FilterLogic::kDecalFilter
181 : FilterLogic::kDecalNearest;
182 }
183 SkUNREACHABLE;
184}
185
Brian Salomonb8f098d2020-01-07 11:15:44 -0500186GrGLSLFragmentProcessor* GrTextureEffect::onCreateGLSLInstance() const {
187 class Impl : public GrGLSLFragmentProcessor {
Brian Salomonca6b2f42020-01-24 11:31:21 -0500188 UniformHandle fSubsetUni;
Brian Salomon53c909e2020-02-13 13:54:24 -0500189 UniformHandle fClampUni;
190 UniformHandle fNormUni;
Brian Salomonca6b2f42020-01-24 11:31:21 -0500191
Brian Salomonb8f098d2020-01-07 11:15:44 -0500192 public:
193 void emitCode(EmitArgs& args) override {
Brian Salomonca6b2f42020-01-24 11:31:21 -0500194 auto te = args.fFp.cast<GrTextureEffect>();
Brian Salomonb8f098d2020-01-07 11:15:44 -0500195 const char* coords;
Brian Salomonb10a6622020-02-20 13:36:01 -0500196 if (args.fFp.isSampledWithExplicitCoords()) {
Brian Salomonb8f098d2020-01-07 11:15:44 -0500197 coords = "_coords";
Brian Salomonb10a6622020-02-20 13:36:01 -0500198 } else {
199 coords = args.fTransformedCoords[0].fVaryingPoint.c_str();
Brian Salomonb8f098d2020-01-07 11:15:44 -0500200 }
201 auto* fb = args.fFragBuilder;
Brian Salomonca6b2f42020-01-24 11:31:21 -0500202 if (te.fShaderModes[0] == ShaderMode::kNone &&
203 te.fShaderModes[1] == ShaderMode::kNone) {
204 fb->codeAppendf("%s = ", args.fOutputColor);
205 fb->appendTextureLookupAndBlend(args.fInputColor, SkBlendMode::kModulate,
206 args.fTexSamplers[0], coords);
207 fb->codeAppendf(";");
208 } else {
Brian Salomon53c909e2020-02-13 13:54:24 -0500209 // Here is the basic flow of the various ShaderModes are implemented in a series of
210 // steps. Not all the steps apply to all the modes. We try to emit only the steps
211 // that are necessary for the given x/y shader modes.
212 //
213 // 0) Start with interpolated coordinates (unnormalize if doing anything
214 // complicated).
215 // 1) Map the coordinates into the subset range [Repeat and MirrorRepeat], or pass
216 // through output of 0).
217 // 2) Clamp the coordinates to a 0.5 inset of the subset rect [Clamp, Repeat, and
218 // MirrorRepeat always or Decal only when filtering] or pass through output of
219 // 1). The clamp rect collapses to a line or point it if the subset rect is less
220 // than one pixel wide/tall.
221 // 3) Look up texture with output of 2) [All]
222 // 3) Use the difference between 1) and 2) to apply filtering at edge [Repeat or
223 // Decal]. In the Repeat case this requires extra texture lookups on the other
224 // side of the subset (up to 3 more reads). Or if Decal and not filtering
225 // do a hard less than/greater than test with the subset rect.
Brian Salomonca6b2f42020-01-24 11:31:21 -0500226
Brian Salomon53c909e2020-02-13 13:54:24 -0500227 // Convert possible projective texture coordinates into non-homogeneous half2.
Brian Salomon66356c22020-02-11 15:22:18 -0500228 fb->codeAppendf(
229 "float2 inCoord = %s;",
230 fb->ensureCoords2D(args.fTransformedCoords[0].fVaryingPoint).c_str());
Brian Salomon66356c22020-02-11 15:22:18 -0500231
Brian Salomon53c909e2020-02-13 13:54:24 -0500232 const auto& m = te.fShaderModes;
233 const auto* texture = te.fSampler.proxy()->peekTexture();
234 bool normCoords = texture->texturePriv().textureType() != GrTextureType::kRectangle;
235 auto filter = te.fSampler.samplerState().filter();
236 FilterLogic filterLogic[2] = {GetFilterLogic(m[0], filter),
237 GetFilterLogic(m[1], filter)};
238
239 auto modeUsesSubset = [](ShaderMode m) {
240 return m == ShaderMode::kRepeat || m == ShaderMode::kMirrorRepeat ||
241 m == ShaderMode::kDecal;
242 };
243
244 auto modeUsesClamp = [filter](ShaderMode m) {
245 return m != ShaderMode::kNone &&
246 (m != ShaderMode::kDecal || filter > Filter::kNearest);
247 };
248
249 bool useSubset[2] = {modeUsesSubset(m[0]), modeUsesSubset(m[1])};
250 bool useClamp [2] = {modeUsesClamp (m[0]), modeUsesClamp (m[1])};
251
252 const char* subsetName = nullptr;
253 if (useSubset[0] || useSubset[1]) {
254 fSubsetUni = args.fUniformHandler->addUniform(
255 kFragment_GrShaderFlag, kFloat4_GrSLType, "subset", &subsetName);
256 }
257
258 const char* clampName = nullptr;
259 if (useClamp[0] || useClamp[1]) {
260 fClampUni = args.fUniformHandler->addUniform(
261 kFragment_GrShaderFlag, kFloat4_GrSLType, "clamp", &clampName);
262 }
263
264 // To keep things a little simpler, when we have filtering logic in the shader we
265 // operate on unnormalized texture coordinates. We add a uniform that stores
266 // {w, h, 1/w, 1/h} in a float4.
267 const char* norm = nullptr;
268 if (normCoords && (filterLogic[0] != FilterLogic::kNone ||
269 filterLogic[1] != FilterLogic::kNone)) {
270 // TODO: Detect support for textureSize() or polyfill textureSize() in SkSL and
271 // always use?
272 fNormUni = args.fUniformHandler->addUniform(kFragment_GrShaderFlag,
273 kFloat4_GrSLType, "norm", &norm);
274 // TODO: Remove the normalization from the CoordTransform to skip unnormalizing
275 // step here.
276 fb->codeAppendf("inCoord *= %s.xy;", norm);
277 }
278
279 // Generates a string to read at a coordinate, normalizing coords if necessary.
Brian Salomonf46f19b2020-02-13 14:11:28 -0500280 auto read = [&](const char* coord) {
Brian Salomon53c909e2020-02-13 13:54:24 -0500281 SkString result;
282 SkString normCoord;
283 if (norm) {
284 normCoord.printf("(%s) * %s.zw", coord, norm);
285 } else {
286 normCoord = coord;
287 }
Brian Salomonf46f19b2020-02-13 14:11:28 -0500288 fb->appendTextureLookup(&result, args.fTexSamplers[0], normCoord.c_str());
Brian Salomon53c909e2020-02-13 13:54:24 -0500289 return result;
290 };
291
292 // Implements coord wrapping for kRepeat and kMirrorRepeat
Brian Salomonf46f19b2020-02-13 14:11:28 -0500293 auto subsetCoord = [&](ShaderMode mode,
294 const char* coordSwizzle,
295 const char* subsetStartSwizzle,
296 const char* subsetStopSwizzle,
297 const char* extraCoord,
298 const char* coordWeight) {
Brian Salomon66356c22020-02-11 15:22:18 -0500299 switch (mode) {
Brian Salomon53c909e2020-02-13 13:54:24 -0500300 // These modes either don't use the subset rect or don't need to map the
301 // coords to be within the subset.
Brian Salomon66356c22020-02-11 15:22:18 -0500302 case ShaderMode::kNone:
Brian Salomon66356c22020-02-11 15:22:18 -0500303 case ShaderMode::kDecal:
Brian Salomon66356c22020-02-11 15:22:18 -0500304 case ShaderMode::kClamp:
Brian Salomon53c909e2020-02-13 13:54:24 -0500305 fb->codeAppendf("subsetCoord.%s = inCoord.%s;", coordSwizzle,
306 coordSwizzle);
Brian Salomon66356c22020-02-11 15:22:18 -0500307 break;
308 case ShaderMode::kRepeat:
Brian Salomonf46f19b2020-02-13 14:11:28 -0500309 if (filter == Filter::kMipMap) {
310 // The approach here is to generate two sets of texture coords that
311 // are both "moving" at the same speed (if not direction) as
312 // inCoords. We accomplish that by using two out of phase mirror
313 // repeat coords. We will always sample using both coords but the
314 // read from the upward sloping one is selected using a weight
315 // that transitions from one set to the other near the reflection
316 // point. Like the coords, the weight is a saw-tooth function,
317 // phase-shifted, vertically translated, and then clamped to 0..1.
318 // TODO: Skip this and use textureGrad() when available.
319 SkASSERT(extraCoord);
320 SkASSERT(coordWeight);
321 fb->codeAppend("{");
322 fb->codeAppendf("float w = %s.%s - %s.%s;", subsetName,
323 subsetStopSwizzle, subsetName, subsetStartSwizzle);
324 fb->codeAppendf("float w2 = 2 * w;");
325 fb->codeAppendf("float d = inCoord.%s - %s.%s;", coordSwizzle,
326 subsetName, subsetStartSwizzle);
327 fb->codeAppend("float m = mod(d, w2);");
328 fb->codeAppend("float o = mix(m, w2 - m, step(w, m));");
329 fb->codeAppendf("subsetCoord.%s = o + %s.%s;", coordSwizzle,
330 subsetName, subsetStartSwizzle);
331 fb->codeAppendf("%s = w - o + %s.%s;", extraCoord, subsetName,
332 subsetStartSwizzle);
333 // coordWeight is used as the third param of mix() to blend between a
334 // sample taken using subsetCoord and a sample at extraCoord.
335 fb->codeAppend("float hw = w/2;");
336 fb->codeAppend("float n = mod(d - hw, w2);");
337 fb->codeAppendf(
338 "%s = saturate(half(mix(n, w2 - n, step(w, n)) - hw + "
339 "0.5));",
340 coordWeight);
341 fb->codeAppend("}");
342 } else {
343 fb->codeAppendf(
344 "subsetCoord.%s = mod(inCoord.%s - %s.%s, %s.%s - %s.%s) + "
345 "%s.%s;",
346 coordSwizzle, coordSwizzle, subsetName, subsetStartSwizzle,
347 subsetName, subsetStopSwizzle, subsetName,
348 subsetStartSwizzle, subsetName, subsetStartSwizzle);
349 }
Brian Salomon66356c22020-02-11 15:22:18 -0500350 break;
351 case ShaderMode::kMirrorRepeat: {
352 fb->codeAppend("{");
353 fb->codeAppendf("float w = %s.%s - %s.%s;", subsetName,
354 subsetStopSwizzle, subsetName, subsetStartSwizzle);
355 fb->codeAppendf("float w2 = 2 * w;");
356 fb->codeAppendf("float m = mod(inCoord.%s - %s.%s, w2);", coordSwizzle,
357 subsetName, subsetStartSwizzle);
Brian Salomon53c909e2020-02-13 13:54:24 -0500358 fb->codeAppendf("subsetCoord.%s = mix(m, w2 - m, step(w, m)) + %s.%s;",
Brian Salomon66356c22020-02-11 15:22:18 -0500359 coordSwizzle, subsetName, subsetStartSwizzle);
360 fb->codeAppend("}");
361 break;
362 }
363 }
364 };
365
Brian Salomonf46f19b2020-02-13 14:11:28 -0500366 auto clampCoord = [&](bool clamp,
367 const char* coordSwizzle,
368 const char* clampStartSwizzle,
369 const char* clampStopSwizzle) {
Brian Salomon53c909e2020-02-13 13:54:24 -0500370 if (clamp) {
371 fb->codeAppendf("clampedCoord.%s = clamp(subsetCoord.%s, %s.%s, %s.%s);",
372 coordSwizzle, coordSwizzle, clampName, clampStartSwizzle,
373 clampName, clampStopSwizzle);
374 } else {
375 fb->codeAppendf("clampedCoord.%s = subsetCoord.%s;", coordSwizzle,
376 coordSwizzle);
377 }
378 };
379
Brian Salomonf46f19b2020-02-13 14:11:28 -0500380 // Insert vars for extra coords and blending weights for kRepeatMipMap.
381 const char* extraRepeatCoordX = nullptr;
382 const char* repeatCoordWeightX = nullptr;
383 const char* extraRepeatCoordY = nullptr;
384 const char* repeatCoordWeightY = nullptr;
385 if (filterLogic[0] == FilterLogic::kRepeatMipMap) {
386 fb->codeAppend("float extraRepeatCoordX; half repeatCoordWeightX;");
387 extraRepeatCoordX = "extraRepeatCoordX";
388 repeatCoordWeightX = "repeatCoordWeightX";
389 }
390 if (filterLogic[1] == FilterLogic::kRepeatMipMap) {
391 fb->codeAppend("float extraRepeatCoordY; half repeatCoordWeightY;");
392 extraRepeatCoordY = "extraRepeatCoordY";
393 repeatCoordWeightY = "repeatCoordWeightY";
394 }
395
396 // Apply subset rect and clamp rect to coords.
Brian Salomon53c909e2020-02-13 13:54:24 -0500397 fb->codeAppend("float2 subsetCoord;");
Brian Salomonf46f19b2020-02-13 14:11:28 -0500398 subsetCoord(te.fShaderModes[0], "x", "x", "z", extraRepeatCoordX,
399 repeatCoordWeightX);
400 subsetCoord(te.fShaderModes[1], "y", "y", "w", extraRepeatCoordY,
401 repeatCoordWeightY);
Brian Salomon53c909e2020-02-13 13:54:24 -0500402 fb->codeAppend("float2 clampedCoord;");
403 clampCoord(useClamp[0], "x", "x", "z");
404 clampCoord(useClamp[1], "y", "y", "w");
Brian Salomonca6b2f42020-01-24 11:31:21 -0500405
Brian Salomonf46f19b2020-02-13 14:11:28 -0500406 // Additional clamping for the extra coords for kRepeatMipMap.
407 if (filterLogic[0] == FilterLogic::kRepeatMipMap) {
408 fb->codeAppendf("extraRepeatCoordX = clamp(extraRepeatCoordX, %s.x, %s.z);",
409 clampName, clampName);
410 }
411 if (filterLogic[1] == FilterLogic::kRepeatMipMap) {
412 fb->codeAppendf("extraRepeatCoordY = clamp(extraRepeatCoordY, %s.y, %s.w);",
413 clampName, clampName);
414 }
415
416 // Do the 2 or 4 texture reads for kRepeatMipMap and then apply the weight(s)
417 // to blend between them. If neither direction is kRepeatMipMap do a single
418 // read at clampedCoord.
419 if (filterLogic[0] == FilterLogic::kRepeatMipMap &&
420 filterLogic[1] == FilterLogic::kRepeatMipMap) {
421 fb->codeAppendf(
422 "half4 textureColor ="
423 " mix(mix(%s, %s, repeatCoordWeightX),"
424 " mix(%s, %s, repeatCoordWeightX),"
425 " repeatCoordWeightY);",
426 read("clampedCoord").c_str(),
427 read("float2(extraRepeatCoordX, clampedCoord.y)").c_str(),
428 read("float2(clampedCoord.x, extraRepeatCoordY)").c_str(),
429 read("float2(extraRepeatCoordX, extraRepeatCoordY)").c_str());
430
431 } else if (filterLogic[0] == FilterLogic::kRepeatMipMap) {
432 fb->codeAppendf("half4 textureColor = mix(%s, %s, repeatCoordWeightX);",
433 read("clampedCoord").c_str(),
434 read("float2(extraRepeatCoordX, clampedCoord.y)").c_str());
435 } else if (filterLogic[1] == FilterLogic::kRepeatMipMap) {
436 fb->codeAppendf("half4 textureColor = mix(%s, %s, repeatCoordWeightY);",
437 read("clampedCoord").c_str(),
438 read("float2(clampedCoord.x, extraRepeatCoordY)").c_str());
439 } else {
440 fb->codeAppendf("half4 textureColor = %s;", read("clampedCoord").c_str());
441 }
Brian Salomonca6b2f42020-01-24 11:31:21 -0500442
Brian Salomon53c909e2020-02-13 13:54:24 -0500443 // Strings for extra texture reads used only in kRepeatBilerp
444 SkString repeatBilerpReadX;
445 SkString repeatBilerpReadY;
446
447 // Calculate the amount the coord moved for clamping. This will be used
448 // to implement shader-based filtering for kDecal and kRepeat.
449
450 if (filterLogic[0] == FilterLogic::kRepeatBilerp ||
451 filterLogic[0] == FilterLogic::kDecalFilter) {
452 fb->codeAppend("half errX = half(subsetCoord.x - clampedCoord.x);");
453 fb->codeAppendf("float repeatCoordX = errX > 0 ? %s.x : %s.z;", clampName,
454 clampName);
455 repeatBilerpReadX = read("float2(repeatCoordX, clampedCoord.y)");
Brian Salomonca6b2f42020-01-24 11:31:21 -0500456 }
Brian Salomon53c909e2020-02-13 13:54:24 -0500457 if (filterLogic[1] == FilterLogic::kRepeatBilerp ||
458 filterLogic[1] == FilterLogic::kDecalFilter) {
459 fb->codeAppend("half errY = half(subsetCoord.y - clampedCoord.y);");
460 fb->codeAppendf("float repeatCoordY = errY > 0 ? %s.y : %s.w;", clampName,
461 clampName);
462 repeatBilerpReadY = read("float2(clampedCoord.x, repeatCoordY)");
463 }
464
465 // Add logic for kRepeatBilerp. Do 1 or 3 more texture reads depending
466 // on whether both modes are kRepeat and whether we're near a single subset edge
467 // or a corner. Then blend the multiple reads using the err values calculated
468 // above.
469 const char* ifStr = "if";
470 if (filterLogic[0] == FilterLogic::kRepeatBilerp &&
471 filterLogic[1] == FilterLogic::kRepeatBilerp) {
472 auto repeatBilerpReadXY = read("float2(repeatCoordX, repeatCoordY)");
473 fb->codeAppendf(
474 "if (errX != 0 && errY != 0) {"
475 " textureColor = mix(mix(textureColor, %s, errX),"
476 " mix(%s, %s, errX),"
477 " errY);"
478 "}",
479 repeatBilerpReadX.c_str(), repeatBilerpReadY.c_str(),
480 repeatBilerpReadXY.c_str());
481 ifStr = "else if";
482 }
483 if (filterLogic[0] == FilterLogic::kRepeatBilerp) {
484 fb->codeAppendf(
485 "%s (errX != 0) {"
486 " textureColor = mix(textureColor, %s, abs(errX));"
487 "}",
488 ifStr, repeatBilerpReadX.c_str());
489 }
490 if (filterLogic[1] == FilterLogic::kRepeatBilerp) {
491 fb->codeAppendf(
492 "%s (errY != 0) {"
493 " textureColor = mix(textureColor, %s, abs(errY));"
494 "}",
495 ifStr, repeatBilerpReadY.c_str());
496 }
497
498 // Do soft edge shader filtering against transparent black for kDecalFilter using
499 // the err values calculated above.
500 if (filterLogic[0] == FilterLogic::kDecalFilter) {
501 fb->codeAppendf(
502 "textureColor = mix(textureColor, half4(0), min(abs(errX), 1));");
503 }
504 if (filterLogic[1] == FilterLogic::kDecalFilter) {
505 fb->codeAppendf(
506 "textureColor = mix(textureColor, half4(0), min(abs(errY), 1));");
507 }
508
509 // Do hard-edge shader transition to transparent black for kDecalNearest at the
510 // subset boundaries.
511 if (filterLogic[0] == FilterLogic::kDecalNearest) {
512 fb->codeAppendf(
513 "if (inCoord.x < %s.x || inCoord.x > %s.z) {"
514 " textureColor = half4(0);"
515 "}",
516 subsetName, subsetName);
517 }
518 if (filterLogic[1] == FilterLogic::kDecalNearest) {
519 fb->codeAppendf(
520 "if (inCoord.y < %s.y || inCoord.y > %s.w) {"
521 " textureColor = half4(0);"
522 "}",
523 subsetName, subsetName);
524 }
525 fb->codeAppendf("%s = %s * textureColor;", args.fOutputColor, args.fInputColor);
Brian Salomonca6b2f42020-01-24 11:31:21 -0500526 }
527 }
528
529 protected:
530 void onSetData(const GrGLSLProgramDataManager& pdm,
531 const GrFragmentProcessor& fp) override {
532 const auto& te = fp.cast<GrTextureEffect>();
Brian Salomonca6b2f42020-01-24 11:31:21 -0500533
Brian Salomon53c909e2020-02-13 13:54:24 -0500534 const float w = te.fSampler.peekTexture()->width();
535 const float h = te.fSampler.peekTexture()->height();
536 const auto& s = te.fSubset;
537 const auto& c = te.fClamp;
Brian Salomonca6b2f42020-01-24 11:31:21 -0500538
Brian Salomon53c909e2020-02-13 13:54:24 -0500539 auto type = te.fSampler.peekTexture()->texturePriv().textureType();
540
541 float norm[4] = {w, h, 1.f/w, 1.f/h};
542
543 if (fNormUni.isValid()) {
544 pdm.set4fv(fNormUni, 1, norm);
545 SkASSERT(type != GrTextureType::kRectangle);
546 }
547
548 auto pushRect = [&](float rect[4], UniformHandle uni) {
Brian Salomonca6b2f42020-01-24 11:31:21 -0500549 if (te.fSampler.view().origin() == kBottomLeft_GrSurfaceOrigin) {
550 rect[1] = h - rect[1];
551 rect[3] = h - rect[3];
552 std::swap(rect[1], rect[3]);
553 }
Brian Salomon53c909e2020-02-13 13:54:24 -0500554 if (!fNormUni.isValid() && type != GrTextureType::kRectangle) {
555 rect[0] *= norm[2];
556 rect[2] *= norm[2];
557 rect[1] *= norm[3];
558 rect[3] *= norm[3];
Brian Salomonca6b2f42020-01-24 11:31:21 -0500559 }
Brian Salomon53c909e2020-02-13 13:54:24 -0500560 pdm.set4fv(uni, 1, rect);
561 };
Brian Salomonca6b2f42020-01-24 11:31:21 -0500562
Brian Salomon53c909e2020-02-13 13:54:24 -0500563 if (fSubsetUni.isValid()) {
564 float subset[] = {s.fLeft, s.fTop, s.fRight, s.fBottom};
565 pushRect(subset, fSubsetUni);
566 }
567 if (fClampUni.isValid()) {
568 float subset[] = {c.fLeft, c.fTop, c.fRight, c.fBottom};
569 pushRect(subset, fClampUni);
Brian Salomonca6b2f42020-01-24 11:31:21 -0500570 }
Brian Salomonb8f098d2020-01-07 11:15:44 -0500571 }
572 };
573 return new Impl;
574}
575
Brian Salomonca6b2f42020-01-24 11:31:21 -0500576void GrTextureEffect::onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder* b) const {
Brian Salomonca6b2f42020-01-24 11:31:21 -0500577 auto m0 = static_cast<uint32_t>(fShaderModes[0]);
578 auto m1 = static_cast<uint32_t>(fShaderModes[1]);
Brian Salomon53c909e2020-02-13 13:54:24 -0500579 auto filter = fSampler.samplerState().filter();
580 auto l0 = static_cast<uint32_t>(GetFilterLogic(fShaderModes[0], filter));
581 auto l1 = static_cast<uint32_t>(GetFilterLogic(fShaderModes[1], filter));
582 b->add32((l0 << 24) | (l1 << 16) | (m0 << 8) | m1);
Brian Salomonca6b2f42020-01-24 11:31:21 -0500583}
Brian Salomonb8f098d2020-01-07 11:15:44 -0500584
Brian Salomonca6b2f42020-01-24 11:31:21 -0500585bool GrTextureEffect::onIsEqual(const GrFragmentProcessor& other) const {
586 auto that = other.cast<GrTextureEffect>();
Brian Salomon922496f2020-02-14 12:20:14 -0500587 return fShaderModes[0] == that.fShaderModes[0] && fShaderModes[1] == that.fShaderModes[1] &&
Brian Salomonca6b2f42020-01-24 11:31:21 -0500588 fSubset == that.fSubset;
Brian Salomonb8f098d2020-01-07 11:15:44 -0500589}
590
Greg Danield2ccbb52020-02-05 10:45:39 -0500591GrTextureEffect::GrTextureEffect(GrSurfaceProxyView view, SkAlphaType alphaType,
Brian Salomonca6b2f42020-01-24 11:31:21 -0500592 const SkMatrix& matrix, const Sampling& sampling)
Brian Salomonb8f098d2020-01-07 11:15:44 -0500593 : GrFragmentProcessor(kGrTextureEffect_ClassID,
Brian Salomonca6b2f42020-01-24 11:31:21 -0500594 ModulateForSamplerOptFlags(alphaType, sampling.usesDecal()))
Greg Daniel67da6652020-02-12 14:09:34 -0500595 , fCoordTransform(matrix, view.proxy(), view.origin())
Greg Danield2ccbb52020-02-05 10:45:39 -0500596 , fSampler(std::move(view), sampling.fHWSampler)
Brian Salomonca6b2f42020-01-24 11:31:21 -0500597 , fSubset(sampling.fShaderSubset)
Brian Salomon53c909e2020-02-13 13:54:24 -0500598 , fClamp(sampling.fShaderClamp)
Brian Salomonca6b2f42020-01-24 11:31:21 -0500599 , fShaderModes{sampling.fShaderModes[0], sampling.fShaderModes[1]} {
600 // We always compare the range even when it isn't used so assert we have canonical don't care
601 // values.
602 SkASSERT(fShaderModes[0] != ShaderMode::kNone || (fSubset.fLeft == 0 && fSubset.fRight == 0));
603 SkASSERT(fShaderModes[1] != ShaderMode::kNone || (fSubset.fTop == 0 && fSubset.fBottom == 0));
Brian Salomonb8f098d2020-01-07 11:15:44 -0500604 this->setTextureSamplerCnt(1);
605 this->addCoordTransform(&fCoordTransform);
606}
607
608GrTextureEffect::GrTextureEffect(const GrTextureEffect& src)
609 : INHERITED(kGrTextureEffect_ClassID, src.optimizationFlags())
610 , fCoordTransform(src.fCoordTransform)
Brian Salomonca6b2f42020-01-24 11:31:21 -0500611 , fSampler(src.fSampler)
612 , fSubset(src.fSubset)
Brian Salomon922496f2020-02-14 12:20:14 -0500613 , fClamp(src.fClamp)
Brian Salomonca6b2f42020-01-24 11:31:21 -0500614 , fShaderModes{src.fShaderModes[0], src.fShaderModes[1]} {
Brian Salomonb8f098d2020-01-07 11:15:44 -0500615 this->setTextureSamplerCnt(1);
616 this->addCoordTransform(&fCoordTransform);
617}
618
619std::unique_ptr<GrFragmentProcessor> GrTextureEffect::clone() const {
620 return std::unique_ptr<GrFragmentProcessor>(new GrTextureEffect(*this));
621}
622
623const GrFragmentProcessor::TextureSampler& GrTextureEffect::onTextureSampler(int) const {
624 return fSampler;
625}
626
627GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrTextureEffect);
628#if GR_TEST_UTILS
629std::unique_ptr<GrFragmentProcessor> GrTextureEffect::TestCreate(GrProcessorTestData* testData) {
Greg Daniel026a60c2020-02-12 10:53:51 -0500630 auto [view, ct, at] = testData->randomView();
Brian Salomon53c909e2020-02-13 13:54:24 -0500631 Mode wrapModes[2];
Brian Salomonb8f098d2020-01-07 11:15:44 -0500632 GrTest::TestWrapModes(testData->fRandom, wrapModes);
Brian Salomonb8f098d2020-01-07 11:15:44 -0500633
Brian Salomon53c909e2020-02-13 13:54:24 -0500634 Filter filter;
635 if (view.asTextureProxy()->mipMapped() == GrMipMapped::kYes) {
636 switch (testData->fRandom->nextULessThan(3)) {
637 case 0:
638 filter = Filter::kNearest;
639 break;
640 case 1:
641 filter = Filter::kBilerp;
642 break;
643 default:
644 filter = Filter::kMipMap;
645 break;
646 }
647 } else {
648 filter = testData->fRandom->nextBool() ? Filter::kBilerp : Filter::kNearest;
649 }
650 GrSamplerState params(wrapModes, filter);
Brian Salomonb8f098d2020-01-07 11:15:44 -0500651
652 const SkMatrix& matrix = GrTest::TestMatrix(testData->fRandom);
Greg Danield2ccbb52020-02-05 10:45:39 -0500653 return GrTextureEffect::Make(std::move(view), at, matrix, params, *testData->caps());
Brian Salomonb8f098d2020-01-07 11:15:44 -0500654}
655#endif