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 "GrTextureDomain.h" 9 10 #include "GrProxyProvider.h" 11 #include "GrShaderCaps.h" 12 #include "GrSimpleTextureEffect.h" 13 #include "GrSurfaceProxyPriv.h" 14 #include "GrTexture.h" 15 #include "SkFloatingPoint.h" 16 #include "glsl/GrGLSLFragmentProcessor.h" 17 #include "glsl/GrGLSLFragmentShaderBuilder.h" 18 #include "glsl/GrGLSLProgramDataManager.h" 19 #include "glsl/GrGLSLShaderBuilder.h" 20 #include "glsl/GrGLSLUniformHandler.h" 21 22 #include <utility> 23 24 GrTextureDomain::GrTextureDomain(GrTextureProxy* 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 = SkRect::MakeIWH(proxy->width(), proxy->height()); 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.fLeft <= domain.fRight); 41 SkASSERT(domain.fTop <= domain.fBottom); 42 fDomain.fLeft = SkScalarPin(domain.fLeft, 0.0f, kFullRect.fRight); 43 fDomain.fRight = SkScalarPin(domain.fRight, fDomain.fLeft, kFullRect.fRight); 44 fDomain.fTop = SkScalarPin(domain.fTop, 0.0f, kFullRect.fBottom); 45 fDomain.fBottom = SkScalarPin(domain.fBottom, fDomain.fTop, kFullRect.fBottom); 46 SkASSERT(fDomain.fLeft <= fDomain.fRight); 47 SkASSERT(fDomain.fTop <= fDomain.fBottom); 48 } 49 50 ////////////////////////////////////////////////////////////////////////////// 51 52 static SkString clamp_expression(GrTextureDomain::Mode mode, const char* inCoord, 53 const char* coordSwizzle, const char* domain, 54 const char* minSwizzle, const char* maxSwizzle) { 55 SkString clampedExpr; 56 switch(mode) { 57 case GrTextureDomain::kIgnore_Mode: 58 clampedExpr.printf("%s.%s\n", inCoord, coordSwizzle); 59 break; 60 case GrTextureDomain::kDecal_Mode: 61 // The lookup coordinate to use for decal will be clamped just like kClamp_Mode, 62 // it's just that the post-processing will be different, so fall through 63 case GrTextureDomain::kClamp_Mode: 64 clampedExpr.printf("clamp(%s.%s, %s.%s, %s.%s)", 65 inCoord, coordSwizzle, domain, minSwizzle, domain, maxSwizzle); 66 break; 67 case GrTextureDomain::kRepeat_Mode: 68 clampedExpr.printf("mod(%s.%s - %s.%s, %s.%s - %s.%s) + %s.%s", 69 inCoord, coordSwizzle, domain, minSwizzle, domain, maxSwizzle, 70 domain, minSwizzle, domain, minSwizzle); 71 break; 72 default: 73 SkASSERTF(false, "Unknown texture domain mode: %u\n", (uint32_t) mode); 74 break; 75 } 76 return clampedExpr; 77 } 78 79 void GrTextureDomain::GLDomain::sampleTexture(GrGLSLShaderBuilder* builder, 80 GrGLSLUniformHandler* uniformHandler, 81 const GrShaderCaps* shaderCaps, 82 const GrTextureDomain& textureDomain, 83 const char* outColor, 84 const SkString& inCoords, 85 GrGLSLFragmentProcessor::SamplerHandle sampler, 86 const char* inModulateColor) { 87 SkASSERT(!fHasMode || (textureDomain.modeX() == fModeX && textureDomain.modeY() == fModeY)); 88 SkDEBUGCODE(fModeX = textureDomain.modeX();) 89 SkDEBUGCODE(fModeY = textureDomain.modeY();) 90 SkDEBUGCODE(fHasMode = true;) 91 92 if ((textureDomain.modeX() != kIgnore_Mode || textureDomain.modeY() != kIgnore_Mode) && 93 !fDomainUni.isValid()) { 94 // Must include the domain uniform since at least one axis uses it 95 const char* name; 96 SkString uniName("TexDom"); 97 if (textureDomain.fIndex >= 0) { 98 uniName.appendS32(textureDomain.fIndex); 99 } 100 fDomainUni = uniformHandler->addUniform(kFragment_GrShaderFlag, kHalf4_GrSLType, 101 uniName.c_str(), &name); 102 fDomainName = name; 103 } 104 105 bool decalX = textureDomain.modeX() == kDecal_Mode; 106 bool decalY = textureDomain.modeY() == kDecal_Mode; 107 if ((decalX || decalY) && !fDecalUni.isValid()) { 108 const char* name; 109 SkString uniName("DecalParams"); 110 if (textureDomain.fIndex >= 0) { 111 uniName.appendS32(textureDomain.fIndex); 112 } 113 // Half3 since this will hold texture width, height, and then a step function control param 114 fDecalUni = uniformHandler->addUniform(kFragment_GrShaderFlag, kHalf3_GrSLType, 115 uniName.c_str(), &name); 116 fDecalName = name; 117 } 118 119 // Add a block so that we can declare variables 120 GrGLSLShaderBuilder::ShaderBlock block(builder); 121 // Always use a local variable for the input coordinates; often callers pass in an expression 122 // and we want to cache it across all of its references in the code below 123 builder->codeAppendf("float2 origCoord = %s;", inCoords.c_str()); 124 builder->codeAppend("float2 clampedCoord = "); 125 if (textureDomain.modeX() != textureDomain.modeY()) { 126 // The wrap modes differ on the two axes, so build up a coordinate that respects each axis' 127 // domain rule independently before sampling the texture. 128 SkString tcX = clamp_expression(textureDomain.modeX(), "origCoord", "x", 129 fDomainName.c_str(), "x", "z"); 130 SkString tcY = clamp_expression(textureDomain.modeY(), "origCoord", "y", 131 fDomainName.c_str(), "y", "w"); 132 builder->codeAppendf("float2(%s, %s)", tcX.c_str(), tcY.c_str()); 133 } else { 134 // Since the x and y axis wrap modes are the same, they can be calculated together using 135 // more efficient vector operations 136 SkString tc = clamp_expression(textureDomain.modeX(), "origCoord", "xy", 137 fDomainName.c_str(), "xy", "zw"); 138 builder->codeAppend(tc.c_str()); 139 } 140 builder->codeAppend(";"); 141 142 // Look up the texture sample at the clamped coordinate location 143 builder->codeAppend("half4 inside = "); 144 builder->appendTextureLookupAndModulate(inModulateColor, sampler, "clampedCoord", 145 kFloat2_GrSLType); 146 builder->codeAppend(";"); 147 148 // Apply decal mode's transparency interpolation if needed 149 if (decalX || decalY) { 150 // The decal err is the max absoluate value between the clamped coordinate and the original 151 // pixel coordinate. This will then be clamped to 1.f if it's greater than the control 152 // parameter, which simulates kNearest and kBilerp behavior depending on if it's 0 or 1. 153 if (decalX && decalY) { 154 builder->codeAppendf("half err = max(half(abs(clampedCoord.x - origCoord.x) * %s.x), " 155 "half(abs(clampedCoord.y - origCoord.y) * %s.y));", 156 fDecalName.c_str(), fDecalName.c_str()); 157 } else if (decalX) { 158 builder->codeAppendf("half err = half(abs(clampedCoord.x - origCoord.x) * %s.x);", 159 fDecalName.c_str()); 160 } else { 161 SkASSERT(decalY); 162 builder->codeAppendf("half err = half(abs(clampedCoord.y - origCoord.y) * %s.y);", 163 fDecalName.c_str()); 164 } 165 166 // Apply a transform to the error rate, which let's us simulate nearest or bilerp filtering 167 // in the same shader. When the texture is nearest filtered, fSizeName.z is set to 1/2 so 168 // this becomes a step function centered at .5 away from the clamped coordinate (but the 169 // domain for decal is inset by .5 so the edge lines up properly). When bilerp, fSizeName.z 170 // is set to 1 and it becomes a simple linear blend between texture and transparent. 171 builder->codeAppendf("if (err > %s.z) { err = 1.0; } else if (%s.z < 1) { err = 0.0; }", 172 fDecalName.c_str(), fDecalName.c_str()); 173 builder->codeAppendf("%s = mix(inside, half4(0, 0, 0, 0), err);", outColor); 174 } else { 175 // A simple look up 176 builder->codeAppendf("%s = inside;", outColor); 177 } 178 } 179 180 void GrTextureDomain::GLDomain::setData(const GrGLSLProgramDataManager& pdman, 181 const GrTextureDomain& textureDomain, 182 GrTextureProxy* proxy, 183 const GrSamplerState& sampler) { 184 GrTexture* tex = proxy->peekTexture(); 185 SkASSERT(fHasMode && textureDomain.modeX() == fModeX && textureDomain.modeY() == fModeY); 186 if (kIgnore_Mode != textureDomain.modeX() || kIgnore_Mode != textureDomain.modeY()) { 187 bool sendDecalData = textureDomain.modeX() == kDecal_Mode || 188 textureDomain.modeY() == kDecal_Mode; 189 190 // If the texture is using nearest filtering, then the decal filter weight should step from 191 // 0 (texture) to 1 (transparent) one half pixel away from the domain. When doing any other 192 // form of filtering, the weight should be 1.0 so that it smoothly interpolates between the 193 // texture and transparent. 194 SkScalar decalFilterWeight = sampler.filter() == GrSamplerState::Filter::kNearest ? 195 SK_ScalarHalf : 1.0f; 196 SkScalar wInv, hInv, h; 197 if (proxy->textureType() == GrTextureType::kRectangle) { 198 wInv = hInv = 1.f; 199 h = tex->height(); 200 201 // Don't do any scaling by texture size for decal filter rate, it's already in pixels 202 if (sendDecalData) { 203 pdman.set3f(fDecalUni, 1.f, 1.f, decalFilterWeight); 204 } 205 } else { 206 wInv = SK_Scalar1 / tex->width(); 207 hInv = SK_Scalar1 / tex->height(); 208 h = 1.f; 209 210 if (sendDecalData) { 211 pdman.set3f(fDecalUni, tex->width(), tex->height(), decalFilterWeight); 212 } 213 } 214 215 float values[kPrevDomainCount] = { 216 SkScalarToFloat(textureDomain.domain().fLeft * wInv), 217 SkScalarToFloat(textureDomain.domain().fTop * hInv), 218 SkScalarToFloat(textureDomain.domain().fRight * wInv), 219 SkScalarToFloat(textureDomain.domain().fBottom * hInv) 220 }; 221 222 if (proxy->textureType() == GrTextureType::kRectangle) { 223 SkASSERT(values[0] >= 0.0f && values[0] <= proxy->height()); 224 SkASSERT(values[1] >= 0.0f && values[1] <= proxy->height()); 225 SkASSERT(values[2] >= 0.0f && values[2] <= proxy->height()); 226 SkASSERT(values[3] >= 0.0f && values[3] <= proxy->height()); 227 } else { 228 SkASSERT(values[0] >= 0.0f && values[0] <= 1.0f); 229 SkASSERT(values[1] >= 0.0f && values[1] <= 1.0f); 230 SkASSERT(values[2] >= 0.0f && values[2] <= 1.0f); 231 SkASSERT(values[3] >= 0.0f && values[3] <= 1.0f); 232 } 233 234 // vertical flip if necessary 235 if (kBottomLeft_GrSurfaceOrigin == proxy->origin()) { 236 values[1] = h - values[1]; 237 values[3] = h - values[3]; 238 239 // The top and bottom were just flipped, so correct the ordering 240 // of elements so that values = (l, t, r, b). 241 using std::swap; 242 swap(values[1], values[3]); 243 } 244 if (0 != memcmp(values, fPrevDomain, kPrevDomainCount * sizeof(float))) { 245 pdman.set4fv(fDomainUni, 1, values); 246 memcpy(fPrevDomain, values, kPrevDomainCount * sizeof(float)); 247 } 248 } 249 } 250 251 /////////////////////////////////////////////////////////////////////////////// 252 253 std::unique_ptr<GrFragmentProcessor> GrTextureDomainEffect::Make( 254 sk_sp<GrTextureProxy> proxy, 255 const SkMatrix& matrix, 256 const SkRect& domain, 257 GrTextureDomain::Mode mode, 258 GrSamplerState::Filter filterMode) { 259 return Make(std::move(proxy), matrix, domain, mode, mode, 260 GrSamplerState(GrSamplerState::WrapMode::kClamp, filterMode)); 261 } 262 263 std::unique_ptr<GrFragmentProcessor> GrTextureDomainEffect::Make( 264 sk_sp<GrTextureProxy> proxy, 265 const SkMatrix& matrix, 266 const SkRect& domain, 267 GrTextureDomain::Mode modeX, 268 GrTextureDomain::Mode modeY, 269 const GrSamplerState& sampler) { 270 // If both domain modes happen to be ignore, it would be faster to just drop the domain logic 271 // entirely Technically, we could also use the simple texture effect if the domain modes agree 272 // with the sampler modes and the proxy is the same size as the domain. It's a lot easier for 273 // calling code to detect these cases and handle it themselves. 274 return std::unique_ptr<GrFragmentProcessor>(new GrTextureDomainEffect( 275 std::move(proxy), matrix, domain, modeX, modeY, sampler)); 276 } 277 278 GrTextureDomainEffect::GrTextureDomainEffect(sk_sp<GrTextureProxy> proxy, 279 const SkMatrix& matrix, 280 const SkRect& domain, 281 GrTextureDomain::Mode modeX, 282 GrTextureDomain::Mode modeY, 283 const GrSamplerState& sampler) 284 : INHERITED(kGrTextureDomainEffect_ClassID, 285 ModulateForSamplerOptFlags(proxy->config(), 286 GrTextureDomain::IsDecalSampled(sampler, modeX, modeY))) 287 , fCoordTransform(matrix, proxy.get()) 288 , fTextureDomain(proxy.get(), domain, modeX, modeY) 289 , fTextureSampler(std::move(proxy), sampler) { 290 SkASSERT((modeX != GrTextureDomain::kRepeat_Mode && modeY != GrTextureDomain::kRepeat_Mode) || 291 sampler.filter() == GrSamplerState::Filter::kNearest); 292 this->addCoordTransform(&fCoordTransform); 293 this->setTextureSamplerCnt(1); 294 } 295 296 GrTextureDomainEffect::GrTextureDomainEffect(const GrTextureDomainEffect& that) 297 : INHERITED(kGrTextureDomainEffect_ClassID, that.optimizationFlags()) 298 , fCoordTransform(that.fCoordTransform) 299 , fTextureDomain(that.fTextureDomain) 300 , fTextureSampler(that.fTextureSampler) { 301 this->addCoordTransform(&fCoordTransform); 302 this->setTextureSamplerCnt(1); 303 } 304 305 void GrTextureDomainEffect::onGetGLSLProcessorKey(const GrShaderCaps& caps, 306 GrProcessorKeyBuilder* b) const { 307 b->add32(GrTextureDomain::GLDomain::DomainKey(fTextureDomain)); 308 } 309 310 GrGLSLFragmentProcessor* GrTextureDomainEffect::onCreateGLSLInstance() const { 311 class GLSLProcessor : public GrGLSLFragmentProcessor { 312 public: 313 void emitCode(EmitArgs& args) override { 314 const GrTextureDomainEffect& tde = args.fFp.cast<GrTextureDomainEffect>(); 315 const GrTextureDomain& domain = tde.fTextureDomain; 316 317 GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; 318 SkString coords2D = fragBuilder->ensureCoords2D(args.fTransformedCoords[0]); 319 320 fGLDomain.sampleTexture(fragBuilder, 321 args.fUniformHandler, 322 args.fShaderCaps, 323 domain, 324 args.fOutputColor, 325 coords2D, 326 args.fTexSamplers[0], 327 args.fInputColor); 328 } 329 330 protected: 331 void onSetData(const GrGLSLProgramDataManager& pdman, 332 const GrFragmentProcessor& fp) override { 333 const GrTextureDomainEffect& tde = fp.cast<GrTextureDomainEffect>(); 334 const GrTextureDomain& domain = tde.fTextureDomain; 335 GrTextureProxy* proxy = tde.textureSampler(0).proxy(); 336 337 fGLDomain.setData(pdman, domain, proxy, tde.textureSampler(0).samplerState()); 338 } 339 340 private: 341 GrTextureDomain::GLDomain fGLDomain; 342 }; 343 344 return new GLSLProcessor; 345 } 346 347 bool GrTextureDomainEffect::onIsEqual(const GrFragmentProcessor& sBase) const { 348 const GrTextureDomainEffect& s = sBase.cast<GrTextureDomainEffect>(); 349 return this->fTextureDomain == s.fTextureDomain; 350 } 351 352 /////////////////////////////////////////////////////////////////////////////// 353 354 GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrTextureDomainEffect); 355 356 #if GR_TEST_UTILS 357 std::unique_ptr<GrFragmentProcessor> GrTextureDomainEffect::TestCreate(GrProcessorTestData* d) { 358 int texIdx = d->fRandom->nextBool() ? GrProcessorUnitTest::kSkiaPMTextureIdx 359 : GrProcessorUnitTest::kAlphaTextureIdx; 360 sk_sp<GrTextureProxy> proxy = d->textureProxy(texIdx); 361 SkRect domain; 362 domain.fLeft = d->fRandom->nextRangeScalar(0, proxy->width()); 363 domain.fRight = d->fRandom->nextRangeScalar(domain.fLeft, proxy->width()); 364 domain.fTop = d->fRandom->nextRangeScalar(0, proxy->height()); 365 domain.fBottom = d->fRandom->nextRangeScalar(domain.fTop, proxy->height()); 366 GrTextureDomain::Mode modeX = 367 (GrTextureDomain::Mode) d->fRandom->nextULessThan(GrTextureDomain::kModeCount); 368 GrTextureDomain::Mode modeY = 369 (GrTextureDomain::Mode) d->fRandom->nextULessThan(GrTextureDomain::kModeCount); 370 const SkMatrix& matrix = GrTest::TestMatrix(d->fRandom); 371 bool bilerp = modeX != GrTextureDomain::kRepeat_Mode && modeY != GrTextureDomain::kRepeat_Mode ? 372 d->fRandom->nextBool() : false; 373 return GrTextureDomainEffect::Make( 374 std::move(proxy), 375 matrix, 376 domain, 377 modeX, 378 modeY, 379 GrSamplerState(GrSamplerState::WrapMode::kClamp, bilerp ? 380 GrSamplerState::Filter::kBilerp : GrSamplerState::Filter::kNearest)); 381 } 382 #endif 383 384 /////////////////////////////////////////////////////////////////////////////// 385 std::unique_ptr<GrFragmentProcessor> GrDeviceSpaceTextureDecalFragmentProcessor::Make( 386 sk_sp<GrTextureProxy> proxy, const SkIRect& subset, const SkIPoint& deviceSpaceOffset) { 387 return std::unique_ptr<GrFragmentProcessor>(new GrDeviceSpaceTextureDecalFragmentProcessor( 388 std::move(proxy), subset, deviceSpaceOffset)); 389 } 390 391 GrDeviceSpaceTextureDecalFragmentProcessor::GrDeviceSpaceTextureDecalFragmentProcessor( 392 sk_sp<GrTextureProxy> proxy, const SkIRect& subset, const SkIPoint& deviceSpaceOffset) 393 : INHERITED(kGrDeviceSpaceTextureDecalFragmentProcessor_ClassID, 394 kCompatibleWithCoverageAsAlpha_OptimizationFlag) 395 , fTextureSampler(proxy, GrSamplerState::ClampNearest()) 396 , fTextureDomain(proxy.get(), 397 GrTextureDomain::MakeTexelDomain(subset, GrTextureDomain::kDecal_Mode), 398 GrTextureDomain::kDecal_Mode, GrTextureDomain::kDecal_Mode) { 399 this->setTextureSamplerCnt(1); 400 fDeviceSpaceOffset.fX = deviceSpaceOffset.fX - subset.fLeft; 401 fDeviceSpaceOffset.fY = deviceSpaceOffset.fY - subset.fTop; 402 } 403 404 GrDeviceSpaceTextureDecalFragmentProcessor::GrDeviceSpaceTextureDecalFragmentProcessor( 405 const GrDeviceSpaceTextureDecalFragmentProcessor& that) 406 : INHERITED(kGrDeviceSpaceTextureDecalFragmentProcessor_ClassID, 407 kCompatibleWithCoverageAsAlpha_OptimizationFlag) 408 , fTextureSampler(that.fTextureSampler) 409 , fTextureDomain(that.fTextureDomain) 410 , fDeviceSpaceOffset(that.fDeviceSpaceOffset) { 411 this->setTextureSamplerCnt(1); 412 } 413 414 std::unique_ptr<GrFragmentProcessor> GrDeviceSpaceTextureDecalFragmentProcessor::clone() const { 415 return std::unique_ptr<GrFragmentProcessor>( 416 new GrDeviceSpaceTextureDecalFragmentProcessor(*this)); 417 } 418 419 GrGLSLFragmentProcessor* GrDeviceSpaceTextureDecalFragmentProcessor::onCreateGLSLInstance() const { 420 class GLSLProcessor : public GrGLSLFragmentProcessor { 421 public: 422 void emitCode(EmitArgs& args) override { 423 const GrDeviceSpaceTextureDecalFragmentProcessor& dstdfp = 424 args.fFp.cast<GrDeviceSpaceTextureDecalFragmentProcessor>(); 425 const char* scaleAndTranslateName; 426 fScaleAndTranslateUni = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, 427 kHalf4_GrSLType, 428 "scaleAndTranslate", 429 &scaleAndTranslateName); 430 args.fFragBuilder->codeAppendf("half2 coords = half2(sk_FragCoord.xy * %s.xy + %s.zw);", 431 scaleAndTranslateName, scaleAndTranslateName); 432 fGLDomain.sampleTexture(args.fFragBuilder, 433 args.fUniformHandler, 434 args.fShaderCaps, 435 dstdfp.fTextureDomain, 436 args.fOutputColor, 437 SkString("coords"), 438 args.fTexSamplers[0], 439 args.fInputColor); 440 } 441 442 protected: 443 void onSetData(const GrGLSLProgramDataManager& pdman, 444 const GrFragmentProcessor& fp) override { 445 const GrDeviceSpaceTextureDecalFragmentProcessor& dstdfp = 446 fp.cast<GrDeviceSpaceTextureDecalFragmentProcessor>(); 447 GrTextureProxy* proxy = dstdfp.textureSampler(0).proxy(); 448 GrTexture* texture = proxy->peekTexture(); 449 450 fGLDomain.setData(pdman, dstdfp.fTextureDomain, proxy, 451 dstdfp.textureSampler(0).samplerState()); 452 float iw = 1.f / texture->width(); 453 float ih = 1.f / texture->height(); 454 float scaleAndTransData[4] = { 455 iw, ih, 456 -dstdfp.fDeviceSpaceOffset.fX * iw, -dstdfp.fDeviceSpaceOffset.fY * ih 457 }; 458 if (proxy->origin() == kBottomLeft_GrSurfaceOrigin) { 459 scaleAndTransData[1] = -scaleAndTransData[1]; 460 scaleAndTransData[3] = 1 - scaleAndTransData[3]; 461 } 462 pdman.set4fv(fScaleAndTranslateUni, 1, scaleAndTransData); 463 } 464 465 private: 466 GrTextureDomain::GLDomain fGLDomain; 467 UniformHandle fScaleAndTranslateUni; 468 }; 469 470 return new GLSLProcessor; 471 } 472 473 bool GrDeviceSpaceTextureDecalFragmentProcessor::onIsEqual(const GrFragmentProcessor& fp) const { 474 const GrDeviceSpaceTextureDecalFragmentProcessor& dstdfp = 475 fp.cast<GrDeviceSpaceTextureDecalFragmentProcessor>(); 476 return dstdfp.fTextureSampler.proxy()->underlyingUniqueID() == 477 fTextureSampler.proxy()->underlyingUniqueID() && 478 dstdfp.fDeviceSpaceOffset == fDeviceSpaceOffset && 479 dstdfp.fTextureDomain == fTextureDomain; 480 } 481 482 /////////////////////////////////////////////////////////////////////////////// 483 484 GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrDeviceSpaceTextureDecalFragmentProcessor); 485 486 #if GR_TEST_UTILS 487 std::unique_ptr<GrFragmentProcessor> GrDeviceSpaceTextureDecalFragmentProcessor::TestCreate( 488 GrProcessorTestData* d) { 489 int texIdx = d->fRandom->nextBool() ? GrProcessorUnitTest::kSkiaPMTextureIdx 490 : GrProcessorUnitTest::kAlphaTextureIdx; 491 sk_sp<GrTextureProxy> proxy = d->textureProxy(texIdx); 492 SkIRect subset; 493 subset.fLeft = d->fRandom->nextULessThan(proxy->width() - 1); 494 subset.fRight = d->fRandom->nextRangeU(subset.fLeft, proxy->width()); 495 subset.fTop = d->fRandom->nextULessThan(proxy->height() - 1); 496 subset.fBottom = d->fRandom->nextRangeU(subset.fTop, proxy->height()); 497 SkIPoint pt; 498 pt.fX = d->fRandom->nextULessThan(2048); 499 pt.fY = d->fRandom->nextULessThan(2048); 500 return GrDeviceSpaceTextureDecalFragmentProcessor::Make(std::move(proxy), subset, pt); 501 } 502 #endif 503