blob: 9d8202c41d3ad76bf5a32e9cc0b7e134e78059bc [file] [log] [blame]
commit-bot@chromium.org907fbd52013-12-09 17:03:02 +00001/*
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
Mike Kleinc0bd9f92019-04-23 12:05:21 -05008#include "src/gpu/effects/GrTextureDomain.h"
Robert Phillips40fd7c92017-01-30 08:06:27 -05009
Mike Kleinc0bd9f92019-04-23 12:05:21 -050010#include "include/gpu/GrTexture.h"
11#include "include/private/SkFloatingPoint.h"
12#include "src/gpu/GrProxyProvider.h"
13#include "src/gpu/GrShaderCaps.h"
14#include "src/gpu/GrSurfaceProxyPriv.h"
Brian Salomonb8f098d2020-01-07 11:15:44 -050015#include "src/gpu/effects/GrTextureEffect.h"
Mike Kleinc0bd9f92019-04-23 12:05:21 -050016#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"
commit-bot@chromium.org907fbd52013-12-09 17:03:02 +000021
Ben Wagnerf08d1d02018-06-18 15:11:00 -040022#include <utility>
23
Michael Ludwig8fa469d2019-11-25 16:08:44 -050024GrTextureDomain::GrTextureDomain(GrSurfaceProxy* proxy, const SkRect& domain, Mode modeX,
Michael Ludwigbe315a22018-12-17 09:50:51 -050025 Mode modeY, int index)
26 : fModeX(modeX)
27 , fModeY(modeY)
Robert Phillips40fd7c92017-01-30 08:06:27 -050028 , fIndex(index) {
29
Michael Ludwigbe315a22018-12-17 09:50:51 -050030 if (!proxy) {
31 SkASSERT(modeX == kIgnore_Mode && modeY == kIgnore_Mode);
Robert Phillips40fd7c92017-01-30 08:06:27 -050032 return;
33 }
34
Brian Salomon9f2b86c2019-10-22 10:37:46 -040035 const SkRect kFullRect = proxy->getBoundsRect();
Robert Phillips40fd7c92017-01-30 08:06:27 -050036
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.
Brian Salomon7eabfe82019-12-02 14:20:20 -050040 SkASSERT(domain.isSorted());
Brian Osmanaba642c2020-02-06 12:52:25 -050041 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);
Robert Phillips40fd7c92017-01-30 08:06:27 -050045 SkASSERT(fDomain.fLeft <= fDomain.fRight);
46 SkASSERT(fDomain.fTop <= fDomain.fBottom);
47}
48
Brian Salomon7eabfe82019-12-02 14:20:20 -050049GrTextureDomain::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
commit-bot@chromium.org907fbd52013-12-09 17:03:02 +000056//////////////////////////////////////////////////////////////////////////////
57
Brian Salomon7caa1372019-12-09 10:40:56 -050058static void append_wrap(GrGLSLShaderBuilder* builder, GrTextureDomain::Mode mode,
59 const char* inCoord, const char* domainStart, const char* domainEnd,
Brian Salomon095d2462019-12-09 15:22:33 -050060 bool is2D, const char* out) {
Michael Ludwigbe315a22018-12-17 09:50:51 -050061 switch(mode) {
62 case GrTextureDomain::kIgnore_Mode:
Brian Salomon7caa1372019-12-09 10:40:56 -050063 builder->codeAppendf("%s = %s;\n", out, inCoord);
Michael Ludwigbe315a22018-12-17 09:50:51 -050064 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:
Brian Salomon7caa1372019-12-09 10:40:56 -050069 builder->codeAppendf("%s = clamp(%s, %s, %s);", out, inCoord, domainStart, domainEnd);
Michael Ludwigbe315a22018-12-17 09:50:51 -050070 break;
71 case GrTextureDomain::kRepeat_Mode:
Brian Salomon7caa1372019-12-09 10:40:56 -050072 builder->codeAppendf("%s = mod(%s - %s, %s - %s) + %s;", out, inCoord, domainStart,
73 domainEnd, domainStart, domainStart);
Michael Ludwigbe315a22018-12-17 09:50:51 -050074 break;
Brian Salomon7caa1372019-12-09 10:40:56 -050075 case GrTextureDomain::kMirrorRepeat_Mode: {
Brian Salomon095d2462019-12-09 15:22:33 -050076 const char* type = is2D ? "float2" : "float";
Brian Salomon7caa1372019-12-09 10:40:56 -050077 builder->codeAppend("{");
Brian Salomon095d2462019-12-09 15:22:33 -050078 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);
Brian Salomon7caa1372019-12-09 10:40:56 -050081 builder->codeAppendf("%s = mix(m, w2 - m, step(w, m)) + %s;", out, domainStart);
82 builder->codeAppend("}");
Michael Ludwigbe315a22018-12-17 09:50:51 -050083 break;
Brian Salomon7caa1372019-12-09 10:40:56 -050084 }
Michael Ludwigbe315a22018-12-17 09:50:51 -050085 }
Michael Ludwigbe315a22018-12-17 09:50:51 -050086}
87
Brian Salomon7eabfe82019-12-02 14:20:20 -050088void GrTextureDomain::GLDomain::sampleProcessor(const GrTextureDomain& textureDomain,
89 const char* inColor,
90 const char* outColor,
91 const SkString& inCoords,
92 GrGLSLFragmentProcessor* parent,
93 GrGLSLFragmentProcessor::EmitArgs& args,
94 int childIndex) {
95 auto appendProcessorSample = [parent, &args, childIndex, inColor](const char* coord) {
Brian Osman978693c2020-01-24 14:52:10 -050096 return parent->invokeChild(childIndex, inColor, args, coord);
Brian Salomon7eabfe82019-12-02 14:20:20 -050097 };
98 this->sample(args.fFragBuilder, args.fUniformHandler, textureDomain, outColor, inCoords,
99 appendProcessorSample);
100}
101
egdaniel2d721d32015-11-11 13:06:05 -0800102void GrTextureDomain::GLDomain::sampleTexture(GrGLSLShaderBuilder* builder,
egdaniel7ea439b2015-12-03 09:20:44 -0800103 GrGLSLUniformHandler* uniformHandler,
Brian Salomon1edc5b92016-11-29 13:43:46 -0500104 const GrShaderCaps* shaderCaps,
commit-bot@chromium.org907fbd52013-12-09 17:03:02 +0000105 const GrTextureDomain& textureDomain,
106 const char* outColor,
107 const SkString& inCoords,
egdaniel09aa1fc2016-04-20 07:09:46 -0700108 GrGLSLFragmentProcessor::SamplerHandle sampler,
Brian Osman2240be92017-10-18 13:15:13 -0400109 const char* inModulateColor) {
Brian Salomon7eabfe82019-12-02 14:20:20 -0500110 auto appendTextureSample = [&sampler, inModulateColor, builder](const char* coord) {
111 builder->codeAppend("half4 textureColor = ");
Brian Salomon87e9ddb2019-12-19 14:50:22 -0500112 builder->appendTextureLookupAndBlend(inModulateColor, SkBlendMode::kModulate, sampler,
113 coord);
Brian Salomon7eabfe82019-12-02 14:20:20 -0500114 builder->codeAppend(";");
115 return SkString("textureColor");
116 };
117 this->sample(builder, uniformHandler, textureDomain, outColor, inCoords, appendTextureSample);
118}
119
120void GrTextureDomain::GLDomain::sample(GrGLSLShaderBuilder* builder,
121 GrGLSLUniformHandler* uniformHandler,
122 const GrTextureDomain& textureDomain,
123 const char* outColor,
124 const SkString& inCoords,
125 const std::function<AppendSample>& appendSample) {
Michael Ludwigbe315a22018-12-17 09:50:51 -0500126 SkASSERT(!fHasMode || (textureDomain.modeX() == fModeX && textureDomain.modeY() == fModeY));
127 SkDEBUGCODE(fModeX = textureDomain.modeX();)
128 SkDEBUGCODE(fModeY = textureDomain.modeY();)
Hans Wennborgc63ec5c2017-12-08 18:56:23 -0800129 SkDEBUGCODE(fHasMode = true;)
commit-bot@chromium.org907fbd52013-12-09 17:03:02 +0000130
Michael Ludwigbe315a22018-12-17 09:50:51 -0500131 if ((textureDomain.modeX() != kIgnore_Mode || textureDomain.modeY() != kIgnore_Mode) &&
132 !fDomainUni.isValid()) {
133 // Must include the domain uniform since at least one axis uses it
commit-bot@chromium.org907fbd52013-12-09 17:03:02 +0000134 const char* name;
135 SkString uniName("TexDom");
136 if (textureDomain.fIndex >= 0) {
137 uniName.appendS32(textureDomain.fIndex);
138 }
Ethan Nicholasf7b88202017-09-18 14:10:39 -0400139 fDomainUni = uniformHandler->addUniform(kFragment_GrShaderFlag, kHalf4_GrSLType,
egdaniel7ea439b2015-12-03 09:20:44 -0800140 uniName.c_str(), &name);
commit-bot@chromium.org907fbd52013-12-09 17:03:02 +0000141 fDomainName = name;
142 }
commit-bot@chromium.org907fbd52013-12-09 17:03:02 +0000143
Michael Ludwigbe315a22018-12-17 09:50:51 -0500144 bool decalX = textureDomain.modeX() == kDecal_Mode;
145 bool decalY = textureDomain.modeY() == kDecal_Mode;
146 if ((decalX || decalY) && !fDecalUni.isValid()) {
147 const char* name;
148 SkString uniName("DecalParams");
149 if (textureDomain.fIndex >= 0) {
150 uniName.appendS32(textureDomain.fIndex);
joshualitt5ae5fc52014-07-29 12:59:27 -0700151 }
Michael Ludwigbe315a22018-12-17 09:50:51 -0500152 // Half3 since this will hold texture width, height, and then a step function control param
153 fDecalUni = uniformHandler->addUniform(kFragment_GrShaderFlag, kHalf3_GrSLType,
154 uniName.c_str(), &name);
155 fDecalName = name;
156 }
commit-bot@chromium.org907fbd52013-12-09 17:03:02 +0000157
Michael Ludwigbe315a22018-12-17 09:50:51 -0500158 // Add a block so that we can declare variables
159 GrGLSLShaderBuilder::ShaderBlock block(builder);
160 // Always use a local variable for the input coordinates; often callers pass in an expression
161 // and we want to cache it across all of its references in the code below
162 builder->codeAppendf("float2 origCoord = %s;", inCoords.c_str());
Brian Salomon7caa1372019-12-09 10:40:56 -0500163 builder->codeAppend("float2 clampedCoord;");
164 SkString start;
165 SkString end;
Brian Salomon095d2462019-12-09 15:22:33 -0500166 bool is2D = textureDomain.modeX() == textureDomain.modeY();
167 if (is2D) {
168 // Doing the domain setup using vectors seems to avoid shader compilation issues on
169 // Chromecast, possibly due to reducing shader length.
170 start.printf("%s.xy", fDomainName.c_str());
171 end.printf("%s.zw", fDomainName.c_str());
172 append_wrap(builder, textureDomain.modeX(), "origCoord", start.c_str(), end.c_str(),
173 true, "clampedCoord");
174 } else {
175 // Apply x mode to the x coordinate using the left and right edges of the domain rect
176 // (stored as the x and z components of the domain uniform).
177 start.printf("%s.x", fDomainName.c_str());
178 end.printf("%s.z", fDomainName.c_str());
179 append_wrap(builder, textureDomain.modeX(), "origCoord.x", start.c_str(), end.c_str(),
180 false, "clampedCoord.x");
181 // Repeat the same logic for y.
182 start.printf("%s.y", fDomainName.c_str());
183 end.printf("%s.w", fDomainName.c_str());
184 append_wrap(builder, textureDomain.modeY(), "origCoord.y", start.c_str(), end.c_str(),
185 false, "clampedCoord.y");
186 }
Brian Salomon7eabfe82019-12-02 14:20:20 -0500187 // Sample 'appendSample' at the clamped coordinate location.
188 SkString color = appendSample("clampedCoord");
Michael Ludwigbe315a22018-12-17 09:50:51 -0500189
190 // Apply decal mode's transparency interpolation if needed
191 if (decalX || decalY) {
192 // The decal err is the max absoluate value between the clamped coordinate and the original
193 // pixel coordinate. This will then be clamped to 1.f if it's greater than the control
194 // parameter, which simulates kNearest and kBilerp behavior depending on if it's 0 or 1.
195 if (decalX && decalY) {
Ethan Nicholase1f55022019-02-05 17:17:40 -0500196 builder->codeAppendf("half err = max(half(abs(clampedCoord.x - origCoord.x) * %s.x), "
197 "half(abs(clampedCoord.y - origCoord.y) * %s.y));",
Michael Ludwigbe315a22018-12-17 09:50:51 -0500198 fDecalName.c_str(), fDecalName.c_str());
199 } else if (decalX) {
Ethan Nicholase1f55022019-02-05 17:17:40 -0500200 builder->codeAppendf("half err = half(abs(clampedCoord.x - origCoord.x) * %s.x);",
Michael Ludwigbe315a22018-12-17 09:50:51 -0500201 fDecalName.c_str());
202 } else {
203 SkASSERT(decalY);
Ethan Nicholase1f55022019-02-05 17:17:40 -0500204 builder->codeAppendf("half err = half(abs(clampedCoord.y - origCoord.y) * %s.y);",
Michael Ludwigbe315a22018-12-17 09:50:51 -0500205 fDecalName.c_str());
joshualitt5ae5fc52014-07-29 12:59:27 -0700206 }
joshualitt5ae5fc52014-07-29 12:59:27 -0700207
Michael Ludwigbe315a22018-12-17 09:50:51 -0500208 // Apply a transform to the error rate, which let's us simulate nearest or bilerp filtering
209 // in the same shader. When the texture is nearest filtered, fSizeName.z is set to 1/2 so
210 // this becomes a step function centered at .5 away from the clamped coordinate (but the
211 // domain for decal is inset by .5 so the edge lines up properly). When bilerp, fSizeName.z
212 // is set to 1 and it becomes a simple linear blend between texture and transparent.
213 builder->codeAppendf("if (err > %s.z) { err = 1.0; } else if (%s.z < 1) { err = 0.0; }",
214 fDecalName.c_str(), fDecalName.c_str());
Brian Salomon7eabfe82019-12-02 14:20:20 -0500215 builder->codeAppendf("%s = mix(%s, half4(0, 0, 0, 0), err);", outColor, color.c_str());
Michael Ludwigbe315a22018-12-17 09:50:51 -0500216 } else {
217 // A simple look up
Brian Salomon7eabfe82019-12-02 14:20:20 -0500218 builder->codeAppendf("%s = %s;", outColor, color.c_str());
commit-bot@chromium.org907fbd52013-12-09 17:03:02 +0000219 }
220}
221
egdaniel018fb622015-10-28 07:26:40 -0700222void GrTextureDomain::GLDomain::setData(const GrGLSLProgramDataManager& pdman,
commit-bot@chromium.org907fbd52013-12-09 17:03:02 +0000223 const GrTextureDomain& textureDomain,
Robert Phillipsbd99c0c2019-12-12 13:26:58 +0000224 const GrSurfaceProxyView& view,
Brian Salomonccb61422020-01-09 10:46:36 -0500225 GrSamplerState state) {
Brian Salomon7eabfe82019-12-02 14:20:20 -0500226 // We want a hard transition from texture content to trans-black in nearest mode.
227 bool filterDecal = state.filter() != GrSamplerState::Filter::kNearest;
Robert Phillipsbd99c0c2019-12-12 13:26:58 +0000228 this->setData(pdman, textureDomain, view.proxy(), view.origin(), filterDecal);
Brian Salomon7eabfe82019-12-02 14:20:20 -0500229}
Michael Ludwig170de012019-11-15 21:55:18 +0000230
Brian Salomon7eabfe82019-12-02 14:20:20 -0500231void GrTextureDomain::GLDomain::setData(const GrGLSLProgramDataManager& pdman,
232 const GrTextureDomain& textureDomain,
233 bool filterIfDecal) {
Robert Phillipsbd99c0c2019-12-12 13:26:58 +0000234 // The origin we pass here doesn't matter
235 this->setData(pdman, textureDomain, nullptr, kTopLeft_GrSurfaceOrigin, filterIfDecal);
Brian Salomon7eabfe82019-12-02 14:20:20 -0500236}
237
238void GrTextureDomain::GLDomain::setData(const GrGLSLProgramDataManager& pdman,
239 const GrTextureDomain& textureDomain,
240 const GrSurfaceProxy* proxy,
Robert Phillipsbd99c0c2019-12-12 13:26:58 +0000241 GrSurfaceOrigin origin,
Brian Salomon7eabfe82019-12-02 14:20:20 -0500242 bool filterIfDecal) {
243 SkASSERT(fHasMode && textureDomain.modeX() == fModeX && textureDomain.modeY() == fModeY);
244 if (kIgnore_Mode == textureDomain.modeX() && kIgnore_Mode == textureDomain.modeY()) {
245 return;
246 }
247 // If the texture is using nearest filtering, then the decal filter weight should step from
248 // 0 (texture) to 1 (transparent) one half pixel away from the domain. When doing any other
249 // form of filtering, the weight should be 1.0 so that it smoothly interpolates between the
250 // texture and transparent.
251 // Start off assuming we're in pixel units and later adjust if we have to deal with normalized
252 // texture coords.
253 float decalFilterWeights[3] = {1.f, 1.f, filterIfDecal ? 1.f : 0.5f};
254 bool sendDecalData = textureDomain.modeX() == kDecal_Mode ||
255 textureDomain.modeY() == kDecal_Mode;
256 float tempDomainValues[4];
257 const float* values;
258 if (proxy) {
Brian Salomon246bc3d2018-12-06 15:33:02 -0500259 SkScalar wInv, hInv, h;
Brian Salomon7eabfe82019-12-02 14:20:20 -0500260 GrTexture* tex = proxy->peekTexture();
Michael Ludwig8fa469d2019-11-25 16:08:44 -0500261 if (proxy->backendFormat().textureType() == GrTextureType::kRectangle) {
Brian Salomon246bc3d2018-12-06 15:33:02 -0500262 wInv = hInv = 1.f;
263 h = tex->height();
Brian Salomon7eabfe82019-12-02 14:20:20 -0500264 // Don't do any scaling by texture size for decal filter rate, it's already in
265 // pixels
Brian Salomon246bc3d2018-12-06 15:33:02 -0500266 } else {
267 wInv = SK_Scalar1 / tex->width();
268 hInv = SK_Scalar1 / tex->height();
269 h = 1.f;
Michael Ludwigbe315a22018-12-17 09:50:51 -0500270
Brian Salomon7eabfe82019-12-02 14:20:20 -0500271 // Account for texture coord normalization in decal filter weights.
272 decalFilterWeights[0] = tex->width();
273 decalFilterWeights[1] = tex->height();
Brian Salomon246bc3d2018-12-06 15:33:02 -0500274 }
Robert Phillipse98234f2017-01-09 14:23:59 -0500275
Brian Salomon7eabfe82019-12-02 14:20:20 -0500276 tempDomainValues[0] = SkScalarToFloat(textureDomain.domain().fLeft * wInv);
277 tempDomainValues[1] = SkScalarToFloat(textureDomain.domain().fTop * hInv);
278 tempDomainValues[2] = SkScalarToFloat(textureDomain.domain().fRight * wInv);
279 tempDomainValues[3] = SkScalarToFloat(textureDomain.domain().fBottom * hInv);
Robert Phillipse98234f2017-01-09 14:23:59 -0500280
Michael Ludwig8fa469d2019-11-25 16:08:44 -0500281 if (proxy->backendFormat().textureType() == GrTextureType::kRectangle) {
Brian Salomon7eabfe82019-12-02 14:20:20 -0500282 SkASSERT(tempDomainValues[0] >= 0.0f && tempDomainValues[0] <= proxy->width());
283 SkASSERT(tempDomainValues[1] >= 0.0f && tempDomainValues[1] <= proxy->height());
284 SkASSERT(tempDomainValues[2] >= 0.0f && tempDomainValues[2] <= proxy->width());
285 SkASSERT(tempDomainValues[3] >= 0.0f && tempDomainValues[3] <= proxy->height());
Brian Salomon246bc3d2018-12-06 15:33:02 -0500286 } else {
Brian Salomon7eabfe82019-12-02 14:20:20 -0500287 SkASSERT(tempDomainValues[0] >= 0.0f && tempDomainValues[0] <= 1.0f);
288 SkASSERT(tempDomainValues[1] >= 0.0f && tempDomainValues[1] <= 1.0f);
289 SkASSERT(tempDomainValues[2] >= 0.0f && tempDomainValues[2] <= 1.0f);
290 SkASSERT(tempDomainValues[3] >= 0.0f && tempDomainValues[3] <= 1.0f);
Brian Salomon246bc3d2018-12-06 15:33:02 -0500291 }
Robert Phillipse98234f2017-01-09 14:23:59 -0500292
commit-bot@chromium.org907fbd52013-12-09 17:03:02 +0000293 // vertical flip if necessary
Robert Phillipsbd99c0c2019-12-12 13:26:58 +0000294 if (kBottomLeft_GrSurfaceOrigin == origin) {
Brian Salomon7eabfe82019-12-02 14:20:20 -0500295 tempDomainValues[1] = h - tempDomainValues[1];
296 tempDomainValues[3] = h - tempDomainValues[3];
Brian Salomon246bc3d2018-12-06 15:33:02 -0500297
commit-bot@chromium.org907fbd52013-12-09 17:03:02 +0000298 // The top and bottom were just flipped, so correct the ordering
299 // of elements so that values = (l, t, r, b).
Ben Wagnerf08d1d02018-06-18 15:11:00 -0400300 using std::swap;
Brian Salomon7eabfe82019-12-02 14:20:20 -0500301 swap(tempDomainValues[1], tempDomainValues[3]);
commit-bot@chromium.org907fbd52013-12-09 17:03:02 +0000302 }
Brian Salomon7eabfe82019-12-02 14:20:20 -0500303 values = tempDomainValues;
304 } else {
305 values = textureDomain.domain().asScalars();
306 }
307 if (!std::equal(values, values + 4, fPrevDomain)) {
308 pdman.set4fv(fDomainUni, 1, values);
309 std::copy_n(values, 4, fPrevDomain);
310 }
311 if (sendDecalData &&
312 !std::equal(decalFilterWeights, decalFilterWeights + 3, fPrevDeclFilterWeights)) {
313 pdman.set3fv(fDecalUni, 1, decalFilterWeights);
314 std::copy_n(decalFilterWeights, 3, fPrevDeclFilterWeights);
commit-bot@chromium.org907fbd52013-12-09 17:03:02 +0000315 }
316}