1/*
2 * Copyright 2012 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/GrTextureDomain.h"
9
10#include "include/private/SkFloatingPoint.h"
11#include "src/gpu/GrProxyProvider.h"
12#include "src/gpu/GrShaderCaps.h"
13#include "src/gpu/GrSurfaceProxyPriv.h"
14#include "src/gpu/GrTexture.h"
15#include "src/gpu/effects/GrTextureEffect.h"
16#include "src/gpu/glsl/GrGLSLFragmentProcessor.h"
17#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h"
18#include "src/gpu/glsl/GrGLSLProgramDataManager.h"
19#include "src/gpu/glsl/GrGLSLShaderBuilder.h"
20#include "src/gpu/glsl/GrGLSLUniformHandler.h"
21
22#include <utility>
23
24GrTextureDomain::GrTextureDomain(GrSurfaceProxy* proxy, const SkRect& domain, Mode modeX,
25 Mode modeY, int index)
26 : fModeX(modeX)
27 , fModeY(modeY)
28 , fIndex(index) {
29
30 if (!proxy) {
31 SkASSERT(modeX == kIgnore_Mode && modeY == kIgnore_Mode);
32 return;
33 }
34
35 const SkRect kFullRect = proxy->getBoundsRect();
36
37 // We don't currently handle domains that are empty or don't intersect the texture.
38 // It is OK if the domain rect is a line or point, but it should not be inverted. We do not
39 // handle rects that do not intersect the [0..1]x[0..1] rect.
40 SkASSERT(domain.isSorted());
41 fDomain.fLeft = SkTPin(domain.fLeft, 0.0f, kFullRect.fRight);
42 fDomain.fRight = SkTPin(domain.fRight, fDomain.fLeft, kFullRect.fRight);
43 fDomain.fTop = SkTPin(domain.fTop, 0.0f, kFullRect.fBottom);
44 fDomain.fBottom = SkTPin(domain.fBottom, fDomain.fTop, kFullRect.fBottom);
45 SkASSERT(fDomain.fLeft <= fDomain.fRight);
46 SkASSERT(fDomain.fTop <= fDomain.fBottom);
47}
48
49GrTextureDomain::GrTextureDomain(const SkRect& domain, Mode modeX, Mode modeY, int index)
50 : fDomain(domain), fModeX(modeX), fModeY(modeY), fIndex(index) {
51 // We don't currently handle domains that are empty or don't intersect the texture.
52 // It is OK if the domain rect is a line or point, but it should not be inverted.
53 SkASSERT(domain.isSorted());
54}
55
56//////////////////////////////////////////////////////////////////////////////
57
58static void append_wrap(GrGLSLShaderBuilder* builder, GrTextureDomain::Mode mode,
59 const char* inCoord, const char* domainStart, const char* domainEnd,
60 bool is2D, const char* out) {
61 switch(mode) {
62 case GrTextureDomain::kIgnore_Mode:
63 builder->codeAppendf("%s = %s;\n", out, inCoord);
64 break;
65 case GrTextureDomain::kDecal_Mode:
66 // The lookup coordinate to use for decal will be clamped just like kClamp_Mode,
67 // it's just that the post-processing will be different, so fall through
68 case GrTextureDomain::kClamp_Mode:
69 builder->codeAppendf("%s = clamp(%s, %s, %s);", out, inCoord, domainStart, domainEnd);
70 break;
71 case GrTextureDomain::kRepeat_Mode:
72 builder->codeAppendf("%s = mod(%s - %s, %s - %s) + %s;", out, inCoord, domainStart,
73 domainEnd, domainStart, domainStart);
74 break;
75 case GrTextureDomain::kMirrorRepeat_Mode: {
76 const char* type = is2D ? "float2" : "float";
77 builder->codeAppend("{");
78 builder->codeAppendf("%s w = %s - %s;", type, domainEnd, domainStart);
79 builder->codeAppendf("%s w2 = 2 * w;", type);
80 builder->codeAppendf("%s m = mod(%s - %s, w2);", type, inCoord, domainStart);
81 builder->codeAppendf("%s = mix(m, w2 - m, step(w, m)) + %s;", out, domainStart);
82 builder->codeAppend("}");
83 break;
84 }
85 }
86}
87
88void GrTextureDomain::GLDomain::sampleProcessor(const GrFragmentProcessor* owner,
89 const GrTextureDomain& textureDomain,
90 const char* inColor,
91 const char* outColor,
92 const SkString& inCoords,
93 GrGLSLFragmentProcessor* parent,
94 GrGLSLFragmentProcessor::EmitArgs& args,
95 int childIndex) {
96 auto appendProcessorSample = [parent, &args, childIndex, inColor](const char* coord) {
97 return parent->invokeChild(childIndex, inColor, args, coord);
98 };
99 this->sample(owner, args.fFragBuilder, args.fUniformHandler, textureDomain, outColor, inCoords,
100 appendProcessorSample);
101}
102
103void GrTextureDomain::GLDomain::sampleTexture(const GrFragmentProcessor* owner,
104 GrGLSLShaderBuilder* builder,
105 GrGLSLUniformHandler* uniformHandler,
106 const GrShaderCaps* shaderCaps,
107 const GrTextureDomain& textureDomain,
108 const char* outColor,
109 const SkString& inCoords,
110 GrGLSLFragmentProcessor::SamplerHandle sampler,
111 const char* inModulateColor) {
112 auto appendTextureSample = [&sampler, inModulateColor, builder](const char* coord) {
113 builder->codeAppend("half4 textureColor = ");
114 builder->appendTextureLookupAndBlend(inModulateColor, SkBlendMode::kModulate, sampler,
115 coord);
116 builder->codeAppend(";");
117 return SkString("textureColor");
118 };
119 this->sample(owner, builder, uniformHandler, textureDomain, outColor, inCoords,
120 appendTextureSample);
121}
122
123void GrTextureDomain::GLDomain::sample(const GrFragmentProcessor* owner,
124 GrGLSLShaderBuilder* builder,
125 GrGLSLUniformHandler* uniformHandler,
126 const GrTextureDomain& textureDomain,
127 const char* outColor,
128 const SkString& inCoords,
129 const std::function<AppendSample>& appendSample) {
130 SkASSERT(!fHasMode || (textureDomain.modeX() == fModeX && textureDomain.modeY() == fModeY));
131 SkDEBUGCODE(fModeX = textureDomain.modeX();)
132 SkDEBUGCODE(fModeY = textureDomain.modeY();)
133 SkDEBUGCODE(fHasMode = true;)
134
135 if ((textureDomain.modeX() != kIgnore_Mode || textureDomain.modeY() != kIgnore_Mode) &&
136 !fDomainUni.isValid()) {
137 // Must include the domain uniform since at least one axis uses it
138 const char* name;
139 SkString uniName("TexDom");
140 if (textureDomain.fIndex >= 0) {
141 uniName.appendS32(textureDomain.fIndex);
142 }
143 fDomainUni = uniformHandler->addUniform(owner, kFragment_GrShaderFlag, kHalf4_GrSLType,
144 uniName.c_str(), &name);
145 fDomainName = name;
146 }
147
148 bool decalX = textureDomain.modeX() == kDecal_Mode;
149 bool decalY = textureDomain.modeY() == kDecal_Mode;
150 if ((decalX || decalY) && !fDecalUni.isValid()) {
151 const char* name;
152 SkString uniName("DecalParams");
153 if (textureDomain.fIndex >= 0) {
154 uniName.appendS32(textureDomain.fIndex);
155 }
156 // Half3 since this will hold texture width, height, and then a step function control param
157 fDecalUni = uniformHandler->addUniform(owner, kFragment_GrShaderFlag, kHalf3_GrSLType,
158 uniName.c_str(), &name);
159 fDecalName = name;
160 }
161
162 // Add a block so that we can declare variables
163 GrGLSLShaderBuilder::ShaderBlock block(builder);
164 // Always use a local variable for the input coordinates; often callers pass in an expression
165 // and we want to cache it across all of its references in the code below
166 builder->codeAppendf("float2 origCoord = %s;", inCoords.c_str());
167 builder->codeAppend("float2 clampedCoord;");
168 SkString start;
169 SkString end;
170 bool is2D = textureDomain.modeX() == textureDomain.modeY();
171 if (is2D) {
172 // Doing the domain setup using vectors seems to avoid shader compilation issues on
173 // Chromecast, possibly due to reducing shader length.
174 start.printf("%s.xy", fDomainName.c_str());
175 end.printf("%s.zw", fDomainName.c_str());
176 append_wrap(builder, textureDomain.modeX(), "origCoord", start.c_str(), end.c_str(),
177 true, "clampedCoord");
178 } else {
179 // Apply x mode to the x coordinate using the left and right edges of the domain rect
180 // (stored as the x and z components of the domain uniform).
181 start.printf("%s.x", fDomainName.c_str());
182 end.printf("%s.z", fDomainName.c_str());
183 append_wrap(builder, textureDomain.modeX(), "origCoord.x", start.c_str(), end.c_str(),
184 false, "clampedCoord.x");
185 // Repeat the same logic for y.
186 start.printf("%s.y", fDomainName.c_str());
187 end.printf("%s.w", fDomainName.c_str());
188 append_wrap(builder, textureDomain.modeY(), "origCoord.y", start.c_str(), end.c_str(),
189 false, "clampedCoord.y");
190 }
191 // Sample 'appendSample' at the clamped coordinate location.
192 SkString color = appendSample("clampedCoord");
193
194 // Apply decal mode's transparency interpolation if needed
195 if (decalX || decalY) {
196 // The decal err is the max absoluate value between the clamped coordinate and the original
197 // pixel coordinate. This will then be clamped to 1.f if it's greater than the control
198 // parameter, which simulates kNearest and kBilerp behavior depending on if it's 0 or 1.
199 if (decalX && decalY) {
200 builder->codeAppendf("half err = max(half(abs(clampedCoord.x - origCoord.x) * %s.x), "
201 "half(abs(clampedCoord.y - origCoord.y) * %s.y));",
202 fDecalName.c_str(), fDecalName.c_str());
203 } else if (decalX) {
204 builder->codeAppendf("half err = half(abs(clampedCoord.x - origCoord.x) * %s.x);",
205 fDecalName.c_str());
206 } else {
207 SkASSERT(decalY);
208 builder->codeAppendf("half err = half(abs(clampedCoord.y - origCoord.y) * %s.y);",
209 fDecalName.c_str());
210 }
211
212 // Apply a transform to the error rate, which let's us simulate nearest or bilerp filtering
213 // in the same shader. When the texture is nearest filtered, fSizeName.z is set to 1/2 so
214 // this becomes a step function centered at .5 away from the clamped coordinate (but the
215 // domain for decal is inset by .5 so the edge lines up properly). When bilerp, fSizeName.z
216 // is set to 1 and it becomes a simple linear blend between texture and transparent.
217 builder->codeAppendf("if (err > %s.z) { err = 1.0; } else if (%s.z < 1) { err = 0.0; }",
218 fDecalName.c_str(), fDecalName.c_str());
219 builder->codeAppendf("%s = mix(%s, half4(0, 0, 0, 0), err);", outColor, color.c_str());
220 } else {
221 // A simple look up
222 builder->codeAppendf("%s = %s;", outColor, color.c_str());
223 }
224}
225
226void GrTextureDomain::GLDomain::setData(const GrGLSLProgramDataManager& pdman,
227 const GrTextureDomain& textureDomain,
228 const GrSurfaceProxyView& view,
229 GrSamplerState state) {
230 // We want a hard transition from texture content to trans-black in nearest mode.
231 bool filterDecal = state.filter() != GrSamplerState::Filter::kNearest;
232 this->setData(pdman, textureDomain, view.proxy(), view.origin(), filterDecal);
233}
234
235void GrTextureDomain::GLDomain::setData(const GrGLSLProgramDataManager& pdman,
236 const GrTextureDomain& textureDomain,
237 bool filterIfDecal) {
238 // The origin we pass here doesn't matter
239 this->setData(pdman, textureDomain, nullptr, kTopLeft_GrSurfaceOrigin, filterIfDecal);
240}
241
242void GrTextureDomain::GLDomain::setData(const GrGLSLProgramDataManager& pdman,
243 const GrTextureDomain& textureDomain,
244 const GrSurfaceProxy* proxy,
245 GrSurfaceOrigin origin,
246 bool filterIfDecal) {
247 SkASSERT(fHasMode && textureDomain.modeX() == fModeX && textureDomain.modeY() == fModeY);
248 if (kIgnore_Mode == textureDomain.modeX() && kIgnore_Mode == textureDomain.modeY()) {
249 return;
250 }
251 // If the texture is using nearest filtering, then the decal filter weight should step from
252 // 0 (texture) to 1 (transparent) one half pixel away from the domain. When doing any other
253 // form of filtering, the weight should be 1.0 so that it smoothly interpolates between the
254 // texture and transparent.
255 // Start off assuming we're in pixel units and later adjust if we have to deal with normalized
256 // texture coords.
257 float decalFilterWeights[3] = {1.f, 1.f, filterIfDecal ? 1.f : 0.5f};
258 bool sendDecalData = textureDomain.modeX() == kDecal_Mode ||
259 textureDomain.modeY() == kDecal_Mode;
260 float tempDomainValues[4];
261 const float* values;
262 if (proxy) {
263 SkScalar wInv, hInv, h;
264 GrTexture* tex = proxy->peekTexture();
265 if (proxy->backendFormat().textureType() == GrTextureType::kRectangle) {
266 wInv = hInv = 1.f;
267 h = tex->height();
268 // Don't do any scaling by texture size for decal filter rate, it's already in
269 // pixels
270 } else {
271 wInv = SK_Scalar1 / tex->width();
272 hInv = SK_Scalar1 / tex->height();
273 h = 1.f;
274
275 // Account for texture coord normalization in decal filter weights.
276 decalFilterWeights[0] = tex->width();
277 decalFilterWeights[1] = tex->height();
278 }
279
280 tempDomainValues[0] = SkScalarToFloat(textureDomain.domain().fLeft * wInv);
281 tempDomainValues[1] = SkScalarToFloat(textureDomain.domain().fTop * hInv);
282 tempDomainValues[2] = SkScalarToFloat(textureDomain.domain().fRight * wInv);
283 tempDomainValues[3] = SkScalarToFloat(textureDomain.domain().fBottom * hInv);
284
285 if (proxy->backendFormat().textureType() == GrTextureType::kRectangle) {
286 SkASSERT(tempDomainValues[0] >= 0.0f && tempDomainValues[0] <= proxy->width());
287 SkASSERT(tempDomainValues[1] >= 0.0f && tempDomainValues[1] <= proxy->height());
288 SkASSERT(tempDomainValues[2] >= 0.0f && tempDomainValues[2] <= proxy->width());
289 SkASSERT(tempDomainValues[3] >= 0.0f && tempDomainValues[3] <= proxy->height());
290 } else {
291 SkASSERT(tempDomainValues[0] >= 0.0f && tempDomainValues[0] <= 1.0f);
292 SkASSERT(tempDomainValues[1] >= 0.0f && tempDomainValues[1] <= 1.0f);
293 SkASSERT(tempDomainValues[2] >= 0.0f && tempDomainValues[2] <= 1.0f);
294 SkASSERT(tempDomainValues[3] >= 0.0f && tempDomainValues[3] <= 1.0f);
295 }
296
297 // vertical flip if necessary
298 if (kBottomLeft_GrSurfaceOrigin == origin) {
299 tempDomainValues[1] = h - tempDomainValues[1];
300 tempDomainValues[3] = h - tempDomainValues[3];
301
302 // The top and bottom were just flipped, so correct the ordering
303 // of elements so that values = (l, t, r, b).
304 using std::swap;
305 swap(tempDomainValues[1], tempDomainValues[3]);
306 }
307 values = tempDomainValues;
308 } else {
309 values = textureDomain.domain().asScalars();
310 }
311 if (!std::equal(values, values + 4, fPrevDomain)) {
312 pdman.set4fv(fDomainUni, 1, values);
313 std::copy_n(values, 4, fPrevDomain);
314 }
315 if (sendDecalData &&
316 !std::equal(decalFilterWeights, decalFilterWeights + 3, fPrevDeclFilterWeights)) {
317 pdman.set3fv(fDecalUni, 1, decalFilterWeights);
318 std::copy_n(decalFilterWeights, 3, fPrevDeclFilterWeights);
319 }
320}
321