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 | |
24 | GrTextureDomain::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 | |
49 | GrTextureDomain::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 | |
58 | static 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 | |
88 | void 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 | |
103 | void 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 | |
123 | void 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 | |
226 | void 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 | |
235 | void 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 | |
242 | void 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 | |