| 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 |  | 
|---|