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 "GrResourceProvider.h"
11 #include "GrShaderCaps.h"
12 #include "GrSimpleTextureEffect.h"
13 #include "GrSurfaceProxyPriv.h"
14 #include "SkFloatingPoint.h"
15 #include "glsl/GrGLSLColorSpaceXformHelper.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 
can_ignore_rect(GrTextureProxy * proxy,const SkRect & domain)22 static bool can_ignore_rect(GrTextureProxy* proxy, const SkRect& domain) {
23     if (GrResourceProvider::IsFunctionallyExact(proxy)) {
24         const SkIRect kFullRect = SkIRect::MakeWH(proxy->width(), proxy->height());
25 
26         return domain.contains(kFullRect);
27     }
28 
29     return false;
30 }
31 
can_ignore_rect(GrTexture * tex,const SkRect & domain)32 static bool can_ignore_rect(GrTexture* tex, const SkRect& domain) {
33     // This logic is relying on the instantiated size of 'tex'. In the deferred world it
34     // will have to change so this logic only fires for kExact texture proxies. This shouldn't
35     // change the actual behavior of Ganesh since shaders shouldn't be accessing pixels outside
36     // of the content rectangle.
37     const SkIRect kFullRect = SkIRect::MakeWH(tex->width(), tex->height());
38 
39     return domain.contains(kFullRect);
40 }
41 
GrTextureDomain(GrTexture * tex,const SkRect & domain,Mode mode,int index)42 GrTextureDomain::GrTextureDomain(GrTexture* tex, const SkRect& domain, Mode mode, int index)
43     : fMode(mode)
44     , fIndex(index) {
45 
46     if (kIgnore_Mode == fMode) {
47         return;
48     }
49 
50     if (kClamp_Mode == mode && can_ignore_rect(tex, domain)) {
51         fMode = kIgnore_Mode;
52         return;
53     }
54 
55     const SkRect kFullRect = SkRect::MakeIWH(tex->width(), tex->height());
56 
57     // We don't currently handle domains that are empty or don't intersect the texture.
58     // It is OK if the domain rect is a line or point, but it should not be inverted. We do not
59     // handle rects that do not intersect the [0..1]x[0..1] rect.
60     SkASSERT(domain.fLeft <= domain.fRight);
61     SkASSERT(domain.fTop <= domain.fBottom);
62     fDomain.fLeft = SkScalarPin(domain.fLeft, 0.0f, kFullRect.fRight);
63     fDomain.fRight = SkScalarPin(domain.fRight, fDomain.fLeft, kFullRect.fRight);
64     fDomain.fTop = SkScalarPin(domain.fTop, 0.0f, kFullRect.fBottom);
65     fDomain.fBottom = SkScalarPin(domain.fBottom, fDomain.fTop, kFullRect.fBottom);
66     SkASSERT(fDomain.fLeft <= fDomain.fRight);
67     SkASSERT(fDomain.fTop <= fDomain.fBottom);
68 }
69 
GrTextureDomain(GrTextureProxy * proxy,const SkRect & domain,Mode mode,int index)70 GrTextureDomain::GrTextureDomain(GrTextureProxy* proxy, const SkRect& domain, Mode mode, int index)
71     : fMode(mode)
72     , fIndex(index) {
73 
74     if (kIgnore_Mode == fMode) {
75         return;
76     }
77 
78     if (kClamp_Mode == mode && can_ignore_rect(proxy, domain)) {
79         fMode = kIgnore_Mode;
80         return;
81     }
82 
83     const SkRect kFullRect = SkRect::MakeIWH(proxy->width(), proxy->height());
84 
85     // We don't currently handle domains that are empty or don't intersect the texture.
86     // It is OK if the domain rect is a line or point, but it should not be inverted. We do not
87     // handle rects that do not intersect the [0..1]x[0..1] rect.
88     SkASSERT(domain.fLeft <= domain.fRight);
89     SkASSERT(domain.fTop <= domain.fBottom);
90     fDomain.fLeft = SkScalarPin(domain.fLeft, 0.0f, kFullRect.fRight);
91     fDomain.fRight = SkScalarPin(domain.fRight, fDomain.fLeft, kFullRect.fRight);
92     fDomain.fTop = SkScalarPin(domain.fTop, 0.0f, kFullRect.fBottom);
93     fDomain.fBottom = SkScalarPin(domain.fBottom, fDomain.fTop, kFullRect.fBottom);
94     SkASSERT(fDomain.fLeft <= fDomain.fRight);
95     SkASSERT(fDomain.fTop <= fDomain.fBottom);
96 }
97 
98 //////////////////////////////////////////////////////////////////////////////
99 
sampleTexture(GrGLSLShaderBuilder * builder,GrGLSLUniformHandler * uniformHandler,const GrShaderCaps * shaderCaps,const GrTextureDomain & textureDomain,const char * outColor,const SkString & inCoords,GrGLSLFragmentProcessor::SamplerHandle sampler,const char * inModulateColor,GrGLSLColorSpaceXformHelper * colorXformHelper)100 void GrTextureDomain::GLDomain::sampleTexture(GrGLSLShaderBuilder* builder,
101                                               GrGLSLUniformHandler* uniformHandler,
102                                               const GrShaderCaps* shaderCaps,
103                                               const GrTextureDomain& textureDomain,
104                                               const char* outColor,
105                                               const SkString& inCoords,
106                                               GrGLSLFragmentProcessor::SamplerHandle sampler,
107                                               const char* inModulateColor,
108                                               GrGLSLColorSpaceXformHelper* colorXformHelper) {
109     SkASSERT((Mode)-1 == fMode || textureDomain.mode() == fMode);
110     SkDEBUGCODE(fMode = textureDomain.mode();)
111 
112     if (textureDomain.mode() != kIgnore_Mode && !fDomainUni.isValid()) {
113         const char* name;
114         SkString uniName("TexDom");
115         if (textureDomain.fIndex >= 0) {
116             uniName.appendS32(textureDomain.fIndex);
117         }
118         fDomainUni = uniformHandler->addUniform(kFragment_GrShaderFlag,
119                                                 kVec4f_GrSLType, kDefault_GrSLPrecision,
120                                                 uniName.c_str(), &name);
121         fDomainName = name;
122     }
123 
124     switch (textureDomain.mode()) {
125         case kIgnore_Mode: {
126             builder->codeAppendf("%s = ", outColor);
127             builder->appendTextureLookupAndModulate(inModulateColor, sampler, inCoords.c_str(),
128                                                     kVec2f_GrSLType, colorXformHelper);
129             builder->codeAppend(";");
130             break;
131         }
132         case kClamp_Mode: {
133             SkString clampedCoords;
134             clampedCoords.appendf("clamp(%s, %s.xy, %s.zw)",
135                                   inCoords.c_str(), fDomainName.c_str(), fDomainName.c_str());
136 
137             builder->codeAppendf("%s = ", outColor);
138             builder->appendTextureLookupAndModulate(inModulateColor, sampler, clampedCoords.c_str(),
139                                                     kVec2f_GrSLType, colorXformHelper);
140             builder->codeAppend(";");
141             break;
142         }
143         case kDecal_Mode: {
144             // Add a block since we're going to declare variables.
145             GrGLSLShaderBuilder::ShaderBlock block(builder);
146 
147             const char* domain = fDomainName.c_str();
148             if (!shaderCaps->canUseAnyFunctionInShader()) {
149                 // On the NexusS and GalaxyNexus, the other path (with the 'any'
150                 // call) causes the compilation error "Calls to any function that
151                 // may require a gradient calculation inside a conditional block
152                 // may return undefined results". This appears to be an issue with
153                 // the 'any' call since even the simple "result=black; if (any())
154                 // result=white;" code fails to compile.
155                 builder->codeAppend("vec4 outside = vec4(0.0, 0.0, 0.0, 0.0);");
156                 builder->codeAppend("vec4 inside = ");
157                 builder->appendTextureLookupAndModulate(inModulateColor, sampler, inCoords.c_str(),
158                                                         kVec2f_GrSLType, colorXformHelper);
159                 builder->codeAppend(";");
160 
161                 builder->codeAppendf("highp float x = (%s).x;", inCoords.c_str());
162                 builder->codeAppendf("highp float y = (%s).y;", inCoords.c_str());
163 
164                 builder->codeAppendf("x = abs(2.0*(x - %s.x)/(%s.z - %s.x) - 1.0);",
165                                      domain, domain, domain);
166                 builder->codeAppendf("y = abs(2.0*(y - %s.y)/(%s.w - %s.y) - 1.0);",
167                                      domain, domain, domain);
168                 builder->codeAppend("float blend = step(1.0, max(x, y));");
169                 builder->codeAppendf("%s = mix(inside, outside, blend);", outColor);
170             } else {
171                 builder->codeAppend("bvec4 outside;\n");
172                 builder->codeAppendf("outside.xy = lessThan(%s, %s.xy);", inCoords.c_str(),
173                                        domain);
174                 builder->codeAppendf("outside.zw = greaterThan(%s, %s.zw);", inCoords.c_str(),
175                                        domain);
176                 builder->codeAppendf("%s = any(outside) ? vec4(0.0, 0.0, 0.0, 0.0) : ",
177                                        outColor);
178                 builder->appendTextureLookupAndModulate(inModulateColor, sampler, inCoords.c_str(),
179                                                         kVec2f_GrSLType, colorXformHelper);
180                 builder->codeAppend(";");
181             }
182             break;
183         }
184         case kRepeat_Mode: {
185             SkString clampedCoords;
186             clampedCoords.printf("mod(%s - %s.xy, %s.zw - %s.xy) + %s.xy",
187                                  inCoords.c_str(), fDomainName.c_str(), fDomainName.c_str(),
188                                  fDomainName.c_str(), fDomainName.c_str());
189 
190             builder->codeAppendf("%s = ", outColor);
191             builder->appendTextureLookupAndModulate(inModulateColor, sampler, clampedCoords.c_str(),
192                                                     kVec2f_GrSLType, colorXformHelper);
193             builder->codeAppend(";");
194             break;
195         }
196     }
197 }
198 
setData(const GrGLSLProgramDataManager & pdman,const GrTextureDomain & textureDomain,GrTexture * tex)199 void GrTextureDomain::GLDomain::setData(const GrGLSLProgramDataManager& pdman,
200                                         const GrTextureDomain& textureDomain,
201                                         GrTexture* tex) {
202     SkASSERT(textureDomain.mode() == fMode);
203     if (kIgnore_Mode != textureDomain.mode()) {
204         SkScalar wInv = SK_Scalar1 / tex->width();
205         SkScalar hInv = SK_Scalar1 / tex->height();
206 
207         float values[kPrevDomainCount] = {
208             SkScalarToFloat(textureDomain.domain().fLeft * wInv),
209             SkScalarToFloat(textureDomain.domain().fTop * hInv),
210             SkScalarToFloat(textureDomain.domain().fRight * wInv),
211             SkScalarToFloat(textureDomain.domain().fBottom * hInv)
212         };
213 
214         SkASSERT(values[0] >= 0.0f && values[0] <= 1.0f);
215         SkASSERT(values[1] >= 0.0f && values[1] <= 1.0f);
216         SkASSERT(values[2] >= 0.0f && values[2] <= 1.0f);
217         SkASSERT(values[3] >= 0.0f && values[3] <= 1.0f);
218 
219         // vertical flip if necessary
220         if (kBottomLeft_GrSurfaceOrigin == tex->origin()) {
221             values[1] = 1.0f - values[1];
222             values[3] = 1.0f - values[3];
223             // The top and bottom were just flipped, so correct the ordering
224             // of elements so that values = (l, t, r, b).
225             SkTSwap(values[1], values[3]);
226         }
227         if (0 != memcmp(values, fPrevDomain, kPrevDomainCount * sizeof(float))) {
228             pdman.set4fv(fDomainUni, 1, values);
229             memcpy(fPrevDomain, values, kPrevDomainCount * sizeof(float));
230         }
231     }
232 }
233 
234 ///////////////////////////////////////////////////////////////////////////////
OptFlags(GrPixelConfig config,GrTextureDomain::Mode mode)235 inline GrFragmentProcessor::OptimizationFlags GrTextureDomainEffect::OptFlags(
236         GrPixelConfig config, GrTextureDomain::Mode mode) {
237     if (mode == GrTextureDomain::kDecal_Mode || !GrPixelConfigIsOpaque(config)) {
238         return GrFragmentProcessor::kCompatibleWithCoverageAsAlpha_OptimizationFlag;
239     } else {
240         return GrFragmentProcessor::kCompatibleWithCoverageAsAlpha_OptimizationFlag |
241                GrFragmentProcessor::kPreservesOpaqueInput_OptimizationFlag;
242     }
243 }
244 
Make(GrResourceProvider * resourceProvider,sk_sp<GrTextureProxy> proxy,sk_sp<GrColorSpaceXform> colorSpaceXform,const SkMatrix & matrix,const SkRect & domain,GrTextureDomain::Mode mode,GrSamplerParams::FilterMode filterMode)245 sk_sp<GrFragmentProcessor> GrTextureDomainEffect::Make(GrResourceProvider* resourceProvider,
246                                                        sk_sp<GrTextureProxy> proxy,
247                                                        sk_sp<GrColorSpaceXform> colorSpaceXform,
248                                                        const SkMatrix& matrix,
249                                                        const SkRect& domain,
250                                                        GrTextureDomain::Mode mode,
251                                                        GrSamplerParams::FilterMode filterMode) {
252     if (GrTextureDomain::kIgnore_Mode == mode ||
253         (GrTextureDomain::kClamp_Mode == mode && can_ignore_rect(proxy.get(), domain))) {
254         return GrSimpleTextureEffect::Make(resourceProvider, std::move(proxy),
255                                            std::move(colorSpaceXform), matrix, filterMode);
256     } else {
257         return sk_sp<GrFragmentProcessor>(
258             new GrTextureDomainEffect(resourceProvider, std::move(proxy),
259                                       std::move(colorSpaceXform),
260                                       matrix, domain, mode, filterMode));
261     }
262 }
263 
GrTextureDomainEffect(GrResourceProvider * resourceProvider,sk_sp<GrTextureProxy> proxy,sk_sp<GrColorSpaceXform> colorSpaceXform,const SkMatrix & matrix,const SkRect & domain,GrTextureDomain::Mode mode,GrSamplerParams::FilterMode filterMode)264 GrTextureDomainEffect::GrTextureDomainEffect(GrResourceProvider* resourceProvider,
265                                              sk_sp<GrTextureProxy> proxy,
266                                              sk_sp<GrColorSpaceXform> colorSpaceXform,
267                                              const SkMatrix& matrix,
268                                              const SkRect& domain,
269                                              GrTextureDomain::Mode mode,
270                                              GrSamplerParams::FilterMode filterMode)
271     : GrSingleTextureEffect(resourceProvider, OptFlags(proxy->config(), mode), proxy,
272                             std::move(colorSpaceXform), matrix, filterMode)
273     , fTextureDomain(proxy.get(), domain, mode) {
274     SkASSERT(mode != GrTextureDomain::kRepeat_Mode ||
275              filterMode == GrSamplerParams::kNone_FilterMode);
276     this->initClassID<GrTextureDomainEffect>();
277 }
278 
onGetGLSLProcessorKey(const GrShaderCaps & caps,GrProcessorKeyBuilder * b) const279 void GrTextureDomainEffect::onGetGLSLProcessorKey(const GrShaderCaps& caps,
280                                                   GrProcessorKeyBuilder* b) const {
281     b->add32(GrTextureDomain::GLDomain::DomainKey(fTextureDomain));
282     b->add32(GrColorSpaceXform::XformKey(this->colorSpaceXform()));
283 }
284 
onCreateGLSLInstance() const285 GrGLSLFragmentProcessor* GrTextureDomainEffect::onCreateGLSLInstance() const  {
286     class GLSLProcessor : public GrGLSLFragmentProcessor {
287     public:
288         void emitCode(EmitArgs& args) override {
289             const GrTextureDomainEffect& tde = args.fFp.cast<GrTextureDomainEffect>();
290             const GrTextureDomain& domain = tde.fTextureDomain;
291 
292             GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
293             SkString coords2D = fragBuilder->ensureCoords2D(args.fTransformedCoords[0]);
294 
295             fColorSpaceHelper.emitCode(args.fUniformHandler, tde.colorSpaceXform());
296             fGLDomain.sampleTexture(fragBuilder,
297                                     args.fUniformHandler,
298                                     args.fShaderCaps,
299                                     domain,
300                                     args.fOutputColor,
301                                     coords2D,
302                                     args.fTexSamplers[0],
303                                     args.fInputColor,
304                                     &fColorSpaceHelper);
305         }
306 
307     protected:
308         void onSetData(const GrGLSLProgramDataManager& pdman, const GrProcessor& fp) override {
309             const GrTextureDomainEffect& tde = fp.cast<GrTextureDomainEffect>();
310             const GrTextureDomain& domain = tde.fTextureDomain;
311             fGLDomain.setData(pdman, domain, tde.textureSampler(0).texture());
312             if (SkToBool(tde.colorSpaceXform())) {
313                 fColorSpaceHelper.setData(pdman, tde.colorSpaceXform());
314             }
315         }
316 
317     private:
318         GrTextureDomain::GLDomain         fGLDomain;
319         GrGLSLColorSpaceXformHelper       fColorSpaceHelper;
320     };
321 
322     return new GLSLProcessor;
323 }
324 
onIsEqual(const GrFragmentProcessor & sBase) const325 bool GrTextureDomainEffect::onIsEqual(const GrFragmentProcessor& sBase) const {
326     const GrTextureDomainEffect& s = sBase.cast<GrTextureDomainEffect>();
327     return this->fTextureDomain == s.fTextureDomain;
328 }
329 
330 ///////////////////////////////////////////////////////////////////////////////
331 
332 GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrTextureDomainEffect);
333 
334 #if GR_TEST_UTILS
TestCreate(GrProcessorTestData * d)335 sk_sp<GrFragmentProcessor> GrTextureDomainEffect::TestCreate(GrProcessorTestData* d) {
336     int texIdx = d->fRandom->nextBool() ? GrProcessorUnitTest::kSkiaPMTextureIdx
337                                         : GrProcessorUnitTest::kAlphaTextureIdx;
338     sk_sp<GrTextureProxy> proxy = d->textureProxy(texIdx);
339     SkRect domain;
340     domain.fLeft = d->fRandom->nextRangeScalar(0, proxy->width());
341     domain.fRight = d->fRandom->nextRangeScalar(domain.fLeft, proxy->width());
342     domain.fTop = d->fRandom->nextRangeScalar(0, proxy->height());
343     domain.fBottom = d->fRandom->nextRangeScalar(domain.fTop, proxy->height());
344     GrTextureDomain::Mode mode =
345         (GrTextureDomain::Mode) d->fRandom->nextULessThan(GrTextureDomain::kModeCount);
346     const SkMatrix& matrix = GrTest::TestMatrix(d->fRandom);
347     bool bilerp = mode != GrTextureDomain::kRepeat_Mode ? d->fRandom->nextBool() : false;
348     sk_sp<GrColorSpaceXform> colorSpaceXform = GrTest::TestColorXform(d->fRandom);
349     return GrTextureDomainEffect::Make(d->resourceProvider(),
350                                        std::move(proxy),
351                                        std::move(colorSpaceXform),
352                                        matrix,
353                                        domain,
354                                        mode,
355                                        bilerp ? GrSamplerParams::kBilerp_FilterMode
356                                               : GrSamplerParams::kNone_FilterMode);
357 }
358 #endif
359 
360 ///////////////////////////////////////////////////////////////////////////////
Make(GrResourceProvider * resourceProvider,sk_sp<GrTextureProxy> proxy,const SkIRect & subset,const SkIPoint & deviceSpaceOffset)361 sk_sp<GrFragmentProcessor> GrDeviceSpaceTextureDecalFragmentProcessor::Make(
362         GrResourceProvider* resourceProvider,
363         sk_sp<GrTextureProxy> proxy,
364         const SkIRect& subset,
365         const SkIPoint& deviceSpaceOffset) {
366     return sk_sp<GrFragmentProcessor>(new GrDeviceSpaceTextureDecalFragmentProcessor(
367             resourceProvider, std::move(proxy), subset, deviceSpaceOffset));
368 }
369 
GrDeviceSpaceTextureDecalFragmentProcessor(GrResourceProvider * resourceProvider,sk_sp<GrTextureProxy> proxy,const SkIRect & subset,const SkIPoint & deviceSpaceOffset)370 GrDeviceSpaceTextureDecalFragmentProcessor::GrDeviceSpaceTextureDecalFragmentProcessor(
371         GrResourceProvider* resourceProvider,
372         sk_sp<GrTextureProxy> proxy,
373         const SkIRect& subset,
374         const SkIPoint& deviceSpaceOffset)
375         : INHERITED(kCompatibleWithCoverageAsAlpha_OptimizationFlag)
376         , fTextureSampler(resourceProvider, proxy, GrSamplerParams::ClampNoFilter())
377         , fTextureDomain(proxy.get(), GrTextureDomain::MakeTexelDomain(subset),
378                          GrTextureDomain::kDecal_Mode) {
379     this->addTextureSampler(&fTextureSampler);
380     fDeviceSpaceOffset.fX = deviceSpaceOffset.fX - subset.fLeft;
381     fDeviceSpaceOffset.fY = deviceSpaceOffset.fY - subset.fTop;
382     this->initClassID<GrDeviceSpaceTextureDecalFragmentProcessor>();
383 }
384 
onCreateGLSLInstance() const385 GrGLSLFragmentProcessor* GrDeviceSpaceTextureDecalFragmentProcessor::onCreateGLSLInstance() const  {
386     class GLSLProcessor : public GrGLSLFragmentProcessor {
387     public:
388         void emitCode(EmitArgs& args) override {
389             const GrDeviceSpaceTextureDecalFragmentProcessor& dstdfp =
390                     args.fFp.cast<GrDeviceSpaceTextureDecalFragmentProcessor>();
391             const char* scaleAndTranslateName;
392             fScaleAndTranslateUni = args.fUniformHandler->addUniform(kFragment_GrShaderFlag,
393                                                                      kVec4f_GrSLType,
394                                                                      kDefault_GrSLPrecision,
395                                                                      "scaleAndTranslate",
396                                                                      &scaleAndTranslateName);
397             args.fFragBuilder->codeAppendf("vec2 coords = sk_FragCoord.xy * %s.xy + %s.zw;",
398                                            scaleAndTranslateName, scaleAndTranslateName);
399             fGLDomain.sampleTexture(args.fFragBuilder,
400                                     args.fUniformHandler,
401                                     args.fShaderCaps,
402                                     dstdfp.fTextureDomain,
403                                     args.fOutputColor,
404                                     SkString("coords"),
405                                     args.fTexSamplers[0],
406                                     args.fInputColor);
407         }
408 
409     protected:
410         void onSetData(const GrGLSLProgramDataManager& pdman, const GrProcessor& fp) override {
411             const GrDeviceSpaceTextureDecalFragmentProcessor& dstdfp =
412                     fp.cast<GrDeviceSpaceTextureDecalFragmentProcessor>();
413             GrTexture* texture = dstdfp.textureSampler(0).texture();
414             fGLDomain.setData(pdman, dstdfp.fTextureDomain, texture);
415             float iw = 1.f / texture->width();
416             float ih = 1.f / texture->height();
417             float scaleAndTransData[4] = {
418                 iw, ih,
419                 -dstdfp.fDeviceSpaceOffset.fX * iw, -dstdfp.fDeviceSpaceOffset.fY * ih
420             };
421             if (texture->origin() == kBottomLeft_GrSurfaceOrigin) {
422                 scaleAndTransData[1] = -scaleAndTransData[1];
423                 scaleAndTransData[3] = 1 - scaleAndTransData[3];
424             }
425             pdman.set4fv(fScaleAndTranslateUni, 1, scaleAndTransData);
426         }
427 
428     private:
429         GrTextureDomain::GLDomain   fGLDomain;
430         UniformHandle               fScaleAndTranslateUni;
431     };
432 
433     return new GLSLProcessor;
434 }
435 
onIsEqual(const GrFragmentProcessor & fp) const436 bool GrDeviceSpaceTextureDecalFragmentProcessor::onIsEqual(const GrFragmentProcessor& fp) const {
437     const GrDeviceSpaceTextureDecalFragmentProcessor& dstdfp =
438             fp.cast<GrDeviceSpaceTextureDecalFragmentProcessor>();
439     return dstdfp.fTextureSampler.texture() == fTextureSampler.texture() &&
440            dstdfp.fDeviceSpaceOffset == fDeviceSpaceOffset &&
441            dstdfp.fTextureDomain == fTextureDomain;
442 }
443 
444 ///////////////////////////////////////////////////////////////////////////////
445 
446 GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrDeviceSpaceTextureDecalFragmentProcessor);
447 
448 #if GR_TEST_UTILS
TestCreate(GrProcessorTestData * d)449 sk_sp<GrFragmentProcessor> GrDeviceSpaceTextureDecalFragmentProcessor::TestCreate(
450         GrProcessorTestData* d) {
451     int texIdx = d->fRandom->nextBool() ? GrProcessorUnitTest::kSkiaPMTextureIdx
452                                         : GrProcessorUnitTest::kAlphaTextureIdx;
453     sk_sp<GrTextureProxy> proxy = d->textureProxy(texIdx);
454     SkIRect subset;
455     subset.fLeft = d->fRandom->nextULessThan(proxy->width() - 1);
456     subset.fRight = d->fRandom->nextRangeU(subset.fLeft, proxy->width());
457     subset.fTop = d->fRandom->nextULessThan(proxy->height() - 1);
458     subset.fBottom = d->fRandom->nextRangeU(subset.fTop, proxy->height());
459     SkIPoint pt;
460     pt.fX = d->fRandom->nextULessThan(2048);
461     pt.fY = d->fRandom->nextULessThan(2048);
462     return GrDeviceSpaceTextureDecalFragmentProcessor::Make(d->resourceProvider(),
463                                                             std::move(proxy), subset, pt);
464 }
465 #endif
466