• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/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"
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 
GrTextureDomain(GrSurfaceProxy * proxy,const SkRect & domain,Mode modeX,Mode modeY,int index)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 
GrTextureDomain(const SkRect & domain,Mode modeX,Mode modeY,int index)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 
append_wrap(GrGLSLShaderBuilder * builder,GrTextureDomain::Mode mode,const char * inCoord,const char * domainStart,const char * domainEnd,bool is2D,const char * out)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 
sampleProcessor(const GrTextureDomain & textureDomain,const char * inColor,const char * outColor,const SkString & inCoords,GrGLSLFragmentProcessor * parent,GrGLSLFragmentProcessor::EmitArgs & args,int childIndex)88 void 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) {
96         return parent->invokeChild(childIndex, inColor, args, coord);
97     };
98     this->sample(args.fFragBuilder, args.fUniformHandler, textureDomain, outColor, inCoords,
99                  appendProcessorSample);
100 }
101 
sampleTexture(GrGLSLShaderBuilder * builder,GrGLSLUniformHandler * uniformHandler,const GrShaderCaps * shaderCaps,const GrTextureDomain & textureDomain,const char * outColor,const SkString & inCoords,GrGLSLFragmentProcessor::SamplerHandle sampler,const char * inModulateColor)102 void GrTextureDomain::GLDomain::sampleTexture(GrGLSLShaderBuilder* builder,
103                                               GrGLSLUniformHandler* uniformHandler,
104                                               const GrShaderCaps* shaderCaps,
105                                               const GrTextureDomain& textureDomain,
106                                               const char* outColor,
107                                               const SkString& inCoords,
108                                               GrGLSLFragmentProcessor::SamplerHandle sampler,
109                                               const char* inModulateColor) {
110     auto appendTextureSample = [&sampler, inModulateColor, builder](const char* coord) {
111         builder->codeAppend("half4 textureColor = ");
112         builder->appendTextureLookupAndBlend(inModulateColor, SkBlendMode::kModulate, sampler,
113                                              coord);
114         builder->codeAppend(";");
115         return SkString("textureColor");
116     };
117     this->sample(builder, uniformHandler, textureDomain, outColor, inCoords, appendTextureSample);
118 }
119 
sample(GrGLSLShaderBuilder * builder,GrGLSLUniformHandler * uniformHandler,const GrTextureDomain & textureDomain,const char * outColor,const SkString & inCoords,const std::function<AppendSample> & appendSample)120 void 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) {
126     SkASSERT(!fHasMode || (textureDomain.modeX() == fModeX && textureDomain.modeY() == fModeY));
127     SkDEBUGCODE(fModeX = textureDomain.modeX();)
128     SkDEBUGCODE(fModeY = textureDomain.modeY();)
129     SkDEBUGCODE(fHasMode = true;)
130 
131     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
134         const char* name;
135         SkString uniName("TexDom");
136         if (textureDomain.fIndex >= 0) {
137             uniName.appendS32(textureDomain.fIndex);
138         }
139         fDomainUni = uniformHandler->addUniform(kFragment_GrShaderFlag, kHalf4_GrSLType,
140                                                 uniName.c_str(), &name);
141         fDomainName = name;
142     }
143 
144     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);
151         }
152         // 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     }
157 
158     // 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());
163     builder->codeAppend("float2 clampedCoord;");
164     SkString start;
165     SkString end;
166     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     }
187     // Sample 'appendSample' at the clamped coordinate location.
188     SkString color = appendSample("clampedCoord");
189 
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) {
196             builder->codeAppendf("half err = max(half(abs(clampedCoord.x - origCoord.x) * %s.x), "
197                                                 "half(abs(clampedCoord.y - origCoord.y) * %s.y));",
198                                  fDecalName.c_str(), fDecalName.c_str());
199         } else if (decalX) {
200             builder->codeAppendf("half err = half(abs(clampedCoord.x - origCoord.x) * %s.x);",
201                                  fDecalName.c_str());
202         } else {
203             SkASSERT(decalY);
204             builder->codeAppendf("half err = half(abs(clampedCoord.y - origCoord.y) * %s.y);",
205                                  fDecalName.c_str());
206         }
207 
208         // 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());
215         builder->codeAppendf("%s = mix(%s, half4(0, 0, 0, 0), err);", outColor, color.c_str());
216     } else {
217         // A simple look up
218         builder->codeAppendf("%s = %s;", outColor, color.c_str());
219     }
220 }
221 
setData(const GrGLSLProgramDataManager & pdman,const GrTextureDomain & textureDomain,const GrSurfaceProxyView & view,GrSamplerState state)222 void GrTextureDomain::GLDomain::setData(const GrGLSLProgramDataManager& pdman,
223                                         const GrTextureDomain& textureDomain,
224                                         const GrSurfaceProxyView& view,
225                                         GrSamplerState state) {
226     // We want a hard transition from texture content to trans-black in nearest mode.
227     bool filterDecal = state.filter() != GrSamplerState::Filter::kNearest;
228     this->setData(pdman, textureDomain, view.proxy(), view.origin(), filterDecal);
229 }
230 
setData(const GrGLSLProgramDataManager & pdman,const GrTextureDomain & textureDomain,bool filterIfDecal)231 void GrTextureDomain::GLDomain::setData(const GrGLSLProgramDataManager& pdman,
232                                         const GrTextureDomain& textureDomain,
233                                         bool filterIfDecal) {
234     // The origin we pass here doesn't matter
235     this->setData(pdman, textureDomain, nullptr, kTopLeft_GrSurfaceOrigin, filterIfDecal);
236 }
237 
setData(const GrGLSLProgramDataManager & pdman,const GrTextureDomain & textureDomain,const GrSurfaceProxy * proxy,GrSurfaceOrigin origin,bool filterIfDecal)238 void GrTextureDomain::GLDomain::setData(const GrGLSLProgramDataManager& pdman,
239                                         const GrTextureDomain& textureDomain,
240                                         const GrSurfaceProxy* proxy,
241                                         GrSurfaceOrigin origin,
242                                         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) {
259         SkScalar wInv, hInv, h;
260         GrTexture* tex = proxy->peekTexture();
261         if (proxy->backendFormat().textureType() == GrTextureType::kRectangle) {
262             wInv = hInv = 1.f;
263             h = tex->height();
264             // Don't do any scaling by texture size for decal filter rate, it's already in
265             // pixels
266         } else {
267             wInv = SK_Scalar1 / tex->width();
268             hInv = SK_Scalar1 / tex->height();
269             h = 1.f;
270 
271             // Account for texture coord normalization in decal filter weights.
272             decalFilterWeights[0] = tex->width();
273             decalFilterWeights[1] = tex->height();
274         }
275 
276         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);
280 
281         if (proxy->backendFormat().textureType() == GrTextureType::kRectangle) {
282             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());
286         } else {
287             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);
291         }
292 
293         // vertical flip if necessary
294         if (kBottomLeft_GrSurfaceOrigin == origin) {
295             tempDomainValues[1] = h - tempDomainValues[1];
296             tempDomainValues[3] = h - tempDomainValues[3];
297 
298             // The top and bottom were just flipped, so correct the ordering
299             // of elements so that values = (l, t, r, b).
300             using std::swap;
301             swap(tempDomainValues[1], tempDomainValues[3]);
302         }
303         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);
315     }
316 }
317 
318 ///////////////////////////////////////////////////////////////////////////////
319 
Make(GrSurfaceProxyView view,const SkIRect & subset,const SkIPoint & deviceSpaceOffset)320 std::unique_ptr<GrFragmentProcessor> GrDeviceSpaceTextureDecalFragmentProcessor::Make(
321         GrSurfaceProxyView view, const SkIRect& subset, const SkIPoint& deviceSpaceOffset) {
322     return std::unique_ptr<GrFragmentProcessor>(new GrDeviceSpaceTextureDecalFragmentProcessor(
323             std::move(view), subset, deviceSpaceOffset));
324 }
325 
GrDeviceSpaceTextureDecalFragmentProcessor(GrSurfaceProxyView view,const SkIRect & subset,const SkIPoint & deviceSpaceOffset)326 GrDeviceSpaceTextureDecalFragmentProcessor::GrDeviceSpaceTextureDecalFragmentProcessor(
327         GrSurfaceProxyView view, const SkIRect& subset, const SkIPoint& deviceSpaceOffset)
328         : INHERITED(kGrDeviceSpaceTextureDecalFragmentProcessor_ClassID,
329                     kCompatibleWithCoverageAsAlpha_OptimizationFlag)
330         , fTextureDomain(view.proxy(),
331                          GrTextureDomain::MakeTexelDomain(subset, GrTextureDomain::kDecal_Mode),
332                          GrTextureDomain::kDecal_Mode, GrTextureDomain::kDecal_Mode)
333         , fTextureSampler(std::move(view), GrSamplerState::Filter::kNearest) {
334     this->setTextureSamplerCnt(1);
335     fDeviceSpaceOffset.fX = deviceSpaceOffset.fX - subset.fLeft;
336     fDeviceSpaceOffset.fY = deviceSpaceOffset.fY - subset.fTop;
337 }
338 
GrDeviceSpaceTextureDecalFragmentProcessor(const GrDeviceSpaceTextureDecalFragmentProcessor & that)339 GrDeviceSpaceTextureDecalFragmentProcessor::GrDeviceSpaceTextureDecalFragmentProcessor(
340         const GrDeviceSpaceTextureDecalFragmentProcessor& that)
341         : INHERITED(kGrDeviceSpaceTextureDecalFragmentProcessor_ClassID,
342                     kCompatibleWithCoverageAsAlpha_OptimizationFlag)
343         , fTextureDomain(that.fTextureDomain)
344         , fTextureSampler(that.fTextureSampler)
345         , fDeviceSpaceOffset(that.fDeviceSpaceOffset) {
346     this->setTextureSamplerCnt(1);
347 }
348 
clone() const349 std::unique_ptr<GrFragmentProcessor> GrDeviceSpaceTextureDecalFragmentProcessor::clone() const {
350     return std::unique_ptr<GrFragmentProcessor>(
351             new GrDeviceSpaceTextureDecalFragmentProcessor(*this));
352 }
353 
onCreateGLSLInstance() const354 GrGLSLFragmentProcessor* GrDeviceSpaceTextureDecalFragmentProcessor::onCreateGLSLInstance() const  {
355     class GLSLProcessor : public GrGLSLFragmentProcessor {
356     public:
357         void emitCode(EmitArgs& args) override {
358             const GrDeviceSpaceTextureDecalFragmentProcessor& dstdfp =
359                     args.fFp.cast<GrDeviceSpaceTextureDecalFragmentProcessor>();
360             const char* scaleAndTranslateName;
361             fScaleAndTranslateUni = args.fUniformHandler->addUniform(kFragment_GrShaderFlag,
362                                                                      kHalf4_GrSLType,
363                                                                      "scaleAndTranslate",
364                                                                      &scaleAndTranslateName);
365             args.fFragBuilder->codeAppendf("half2 coords = half2(sk_FragCoord.xy * %s.xy + %s.zw);",
366                                            scaleAndTranslateName, scaleAndTranslateName);
367             fGLDomain.sampleTexture(args.fFragBuilder,
368                                     args.fUniformHandler,
369                                     args.fShaderCaps,
370                                     dstdfp.fTextureDomain,
371                                     args.fOutputColor,
372                                     SkString("coords"),
373                                     args.fTexSamplers[0],
374                                     args.fInputColor);
375         }
376 
377     protected:
378         void onSetData(const GrGLSLProgramDataManager& pdman,
379                        const GrFragmentProcessor& fp) override {
380             const GrDeviceSpaceTextureDecalFragmentProcessor& dstdfp =
381                     fp.cast<GrDeviceSpaceTextureDecalFragmentProcessor>();
382             const auto& view = dstdfp.textureSampler(0).view();
383             SkISize textureDims = view.proxy()->backingStoreDimensions();
384 
385             fGLDomain.setData(pdman, dstdfp.fTextureDomain, view,
386                               dstdfp.textureSampler(0).samplerState());
387             float iw = 1.f / textureDims.width();
388             float ih = 1.f / textureDims.height();
389             float scaleAndTransData[4] = {
390                 iw, ih,
391                 -dstdfp.fDeviceSpaceOffset.fX * iw, -dstdfp.fDeviceSpaceOffset.fY * ih
392             };
393             if (view.origin() == kBottomLeft_GrSurfaceOrigin) {
394                 scaleAndTransData[1] = -scaleAndTransData[1];
395                 scaleAndTransData[3] = 1 - scaleAndTransData[3];
396             }
397             pdman.set4fv(fScaleAndTranslateUni, 1, scaleAndTransData);
398         }
399 
400     private:
401         GrTextureDomain::GLDomain   fGLDomain;
402         UniformHandle               fScaleAndTranslateUni;
403     };
404 
405     return new GLSLProcessor;
406 }
407 
onIsEqual(const GrFragmentProcessor & fp) const408 bool GrDeviceSpaceTextureDecalFragmentProcessor::onIsEqual(const GrFragmentProcessor& fp) const {
409     const GrDeviceSpaceTextureDecalFragmentProcessor& dstdfp =
410             fp.cast<GrDeviceSpaceTextureDecalFragmentProcessor>();
411     return dstdfp.fTextureSampler.view().proxy()->underlyingUniqueID() ==
412                    fTextureSampler.view().proxy()->underlyingUniqueID() &&
413            dstdfp.fDeviceSpaceOffset == fDeviceSpaceOffset &&
414            dstdfp.fTextureDomain == fTextureDomain;
415 }
416 
417 ///////////////////////////////////////////////////////////////////////////////
418 
419 GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrDeviceSpaceTextureDecalFragmentProcessor);
420 
421 #if GR_TEST_UTILS
TestCreate(GrProcessorTestData * d)422 std::unique_ptr<GrFragmentProcessor> GrDeviceSpaceTextureDecalFragmentProcessor::TestCreate(
423         GrProcessorTestData* d) {
424     auto [view, at, ct] = d->randomView();
425     SkIRect subset;
426     subset.fLeft = d->fRandom->nextULessThan(view.width() - 1);
427     subset.fRight = d->fRandom->nextRangeU(subset.fLeft, view.width());
428     subset.fTop = d->fRandom->nextULessThan(view.height() - 1);
429     subset.fBottom = d->fRandom->nextRangeU(subset.fTop, view.height());
430     SkIPoint pt;
431     pt.fX = d->fRandom->nextULessThan(2048);
432     pt.fY = d->fRandom->nextULessThan(2048);
433 
434     return GrDeviceSpaceTextureDecalFragmentProcessor::Make(std::move(view), subset, pt);
435 }
436 #endif
437