dvonbeck | 09e12a6 | 2016-08-12 12:50:36 -0700 | [diff] [blame] | 1 | /* |
| 2 | * Copyright 2016 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 "GrAnalyticRectBatch.h" |
| 9 | |
| 10 | #include "GrBatchFlushState.h" |
| 11 | #include "GrBatchTest.h" |
| 12 | #include "GrGeometryProcessor.h" |
| 13 | #include "GrInvariantOutput.h" |
| 14 | #include "GrProcessor.h" |
| 15 | #include "GrResourceProvider.h" |
| 16 | #include "SkRRect.h" |
| 17 | #include "SkStrokeRec.h" |
| 18 | #include "batches/GrVertexBatch.h" |
| 19 | #include "glsl/GrGLSLFragmentShaderBuilder.h" |
| 20 | #include "glsl/GrGLSLGeometryProcessor.h" |
bsalomon | a624bf3 | 2016-09-20 09:12:47 -0700 | [diff] [blame] | 21 | #include "glsl/GrGLSLGeometryProcessor.h" |
dvonbeck | 09e12a6 | 2016-08-12 12:50:36 -0700 | [diff] [blame] | 22 | #include "glsl/GrGLSLProgramDataManager.h" |
| 23 | #include "glsl/GrGLSLVarying.h" |
| 24 | #include "glsl/GrGLSLVertexShaderBuilder.h" |
| 25 | #include "glsl/GrGLSLUniformHandler.h" |
| 26 | #include "glsl/GrGLSLUtil.h" |
| 27 | |
| 28 | namespace { |
| 29 | |
| 30 | struct RectVertex { |
| 31 | SkPoint fPos; |
| 32 | GrColor fColor; |
| 33 | SkPoint fCenter; |
| 34 | SkVector fDownDir; |
| 35 | SkScalar fHalfWidth; |
| 36 | SkScalar fHalfHeight; |
| 37 | }; |
| 38 | |
| 39 | } |
| 40 | |
| 41 | /////////////////////////////////////////////////////////////////////////////// |
| 42 | |
| 43 | /** |
| 44 | * The output of this effect is the input color and coverage for an arbitrarily oriented rect. The |
| 45 | * rect is specified as: |
| 46 | * Center of the rect |
| 47 | * Unit vector point down the height of the rect |
| 48 | * Half width + 0.5 |
| 49 | * Half height + 0.5 |
| 50 | * The center and vector are stored in a vec4 varying ("RectEdge") with the |
| 51 | * center in the xy components and the vector in the zw components. |
| 52 | * The munged width and height are stored in a vec2 varying ("WidthHeight") |
| 53 | * with the width in x and the height in y. |
| 54 | */ |
| 55 | class RectGeometryProcessor : public GrGeometryProcessor { |
| 56 | public: |
| 57 | RectGeometryProcessor(const SkMatrix& localMatrix) : fLocalMatrix(localMatrix) { |
| 58 | this->initClassID<RectGeometryProcessor>(); |
bsalomon | 6cb807b | 2016-08-17 11:33:39 -0700 | [diff] [blame] | 59 | fInPosition = &this->addVertexAttrib("inPosition", kVec2f_GrVertexAttribType, |
| 60 | kHigh_GrSLPrecision); |
| 61 | fInColor = &this->addVertexAttrib("inColor", kVec4ub_GrVertexAttribType); |
| 62 | fInRectEdge = &this->addVertexAttrib("inRectEdge", kVec4f_GrVertexAttribType); |
| 63 | fInWidthHeight = &this->addVertexAttrib("inWidthHeight", kVec2f_GrVertexAttribType); |
dvonbeck | 09e12a6 | 2016-08-12 12:50:36 -0700 | [diff] [blame] | 64 | } |
| 65 | |
Mike Klein | fc6c37b | 2016-09-27 09:34:10 -0400 | [diff] [blame] | 66 | bool implementsDistanceVector() const override { return true; } |
dvonbeck | 09e12a6 | 2016-08-12 12:50:36 -0700 | [diff] [blame] | 67 | |
| 68 | const Attribute* inPosition() const { return fInPosition; } |
| 69 | const Attribute* inColor() const { return fInColor; } |
| 70 | const Attribute* inRectEdge() const { return fInRectEdge; } |
| 71 | const Attribute* inWidthHeight() const { return fInWidthHeight; } |
| 72 | |
| 73 | const SkMatrix& localMatrix() const { return fLocalMatrix; } |
| 74 | |
| 75 | virtual ~RectGeometryProcessor() {} |
| 76 | |
| 77 | const char* name() const override { return "RectEdge"; } |
| 78 | |
| 79 | class GLSLProcessor : public GrGLSLGeometryProcessor { |
| 80 | public: |
| 81 | GLSLProcessor() {} |
| 82 | |
| 83 | void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override{ |
| 84 | const RectGeometryProcessor& rgp = args.fGP.cast<RectGeometryProcessor>(); |
| 85 | GrGLSLVertexBuilder* vertBuilder = args.fVertBuilder; |
| 86 | GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler; |
| 87 | GrGLSLUniformHandler* uniformHandler = args.fUniformHandler; |
| 88 | |
| 89 | // emit attributes |
| 90 | varyingHandler->emitAttributes(rgp); |
| 91 | |
| 92 | // setup the varying for the position |
| 93 | GrGLSLVertToFrag positionVary(kVec2f_GrSLType); |
| 94 | varyingHandler->addVarying("Position", &positionVary); |
| 95 | vertBuilder->codeAppendf("%s = %s;", positionVary.vsOut(), rgp.inPosition()->fName); |
| 96 | |
| 97 | // setup the varying for the center point and the unit vector that points down the |
| 98 | // height of the rect |
| 99 | GrGLSLVertToFrag rectEdgeVary(kVec4f_GrSLType); |
| 100 | varyingHandler->addVarying("RectEdge", &rectEdgeVary); |
| 101 | vertBuilder->codeAppendf("%s = %s;", rectEdgeVary.vsOut(), rgp.inRectEdge()->fName); |
| 102 | |
| 103 | // setup the varying for the width/2+.5 and height/2+.5 |
| 104 | GrGLSLVertToFrag widthHeightVary(kVec2f_GrSLType); |
| 105 | varyingHandler->addVarying("WidthHeight", &widthHeightVary); |
| 106 | vertBuilder->codeAppendf("%s = %s;", |
| 107 | widthHeightVary.vsOut(), rgp.inWidthHeight()->fName); |
| 108 | |
| 109 | GrGLSLPPFragmentBuilder* fragBuilder = args.fFragBuilder; |
| 110 | |
| 111 | // setup pass through color |
| 112 | varyingHandler->addPassThroughAttribute(rgp.inColor(), args.fOutputColor); |
| 113 | |
| 114 | // Setup position |
| 115 | this->setupPosition(vertBuilder, gpArgs, rgp.inPosition()->fName); |
| 116 | |
| 117 | // emit transforms |
| 118 | this->emitTransforms(vertBuilder, |
| 119 | varyingHandler, |
| 120 | uniformHandler, |
| 121 | gpArgs->fPositionVar, |
| 122 | rgp.inPosition()->fName, |
| 123 | rgp.localMatrix(), |
bsalomon | a624bf3 | 2016-09-20 09:12:47 -0700 | [diff] [blame] | 124 | args.fFPCoordTransformHandler); |
dvonbeck | 09e12a6 | 2016-08-12 12:50:36 -0700 | [diff] [blame] | 125 | |
| 126 | // TODO: compute all these offsets, spans, and scales in the VS |
| 127 | fragBuilder->codeAppendf("float insetW = min(1.0, %s.x) - 0.5;", |
| 128 | widthHeightVary.fsIn()); |
| 129 | fragBuilder->codeAppendf("float insetH = min(1.0, %s.y) - 0.5;", |
| 130 | widthHeightVary.fsIn()); |
| 131 | fragBuilder->codeAppend("float outset = 0.5;"); |
| 132 | // For rects > 1 pixel wide and tall the span's are noops (i.e., 1.0). For rects |
| 133 | // < 1 pixel wide or tall they serve to normalize the < 1 ramp to a 0 .. 1 range. |
| 134 | fragBuilder->codeAppend("float spanW = insetW + outset;"); |
| 135 | fragBuilder->codeAppend("float spanH = insetH + outset;"); |
| 136 | // For rects < 1 pixel wide or tall, these scale factors are used to cap the maximum |
| 137 | // value of coverage that is used. In other words it is the coverage that is |
| 138 | // used in the interior of the rect after the ramp. |
| 139 | fragBuilder->codeAppend("float scaleW = min(1.0, 2.0*insetW/spanW);"); |
| 140 | fragBuilder->codeAppend("float scaleH = min(1.0, 2.0*insetH/spanH);"); |
| 141 | // Compute the coverage for the rect's width |
| 142 | fragBuilder->codeAppendf("vec2 offset = %s.xy - %s.xy;", |
| 143 | positionVary.fsIn(), rectEdgeVary.fsIn()); |
| 144 | fragBuilder->codeAppendf("float perpDot = abs(offset.x * %s.w - offset.y * %s.z);", |
| 145 | rectEdgeVary.fsIn(), rectEdgeVary.fsIn()); |
| 146 | |
| 147 | if (args.fDistanceVectorName) { |
| 148 | fragBuilder->codeAppendf("float widthDistance = %s.x - perpDot;", |
| 149 | widthHeightVary.fsIn()); |
| 150 | } |
| 151 | |
| 152 | fragBuilder->codeAppendf( |
| 153 | "float coverage = scaleW*clamp((%s.x-perpDot)/spanW, 0.0, 1.0);", |
| 154 | widthHeightVary.fsIn()); |
| 155 | // Compute the coverage for the rect's height and merge with the width |
| 156 | fragBuilder->codeAppendf("perpDot = abs(dot(offset, %s.zw));", |
| 157 | rectEdgeVary.fsIn()); |
| 158 | |
| 159 | if (args.fDistanceVectorName) { |
| 160 | fragBuilder->codeAppendf("float heightDistance = %s.y - perpDot;", |
| 161 | widthHeightVary.fsIn()); |
| 162 | } |
| 163 | |
| 164 | fragBuilder->codeAppendf( |
| 165 | "coverage = coverage*scaleH*clamp((%s.y-perpDot)/spanH, 0.0, 1.0);", |
| 166 | widthHeightVary.fsIn()); |
| 167 | |
| 168 | fragBuilder->codeAppendf("%s = vec4(coverage);", args.fOutputCoverage); |
| 169 | |
| 170 | if (args.fDistanceVectorName) { |
| 171 | fragBuilder->codeAppend( "// Calculating distance vector\n"); |
| 172 | fragBuilder->codeAppend( "vec2 dvAxis;"); |
| 173 | fragBuilder->codeAppend( "float dvLength;"); |
| 174 | |
| 175 | fragBuilder->codeAppend( "if (heightDistance < widthDistance) {"); |
| 176 | fragBuilder->codeAppendf(" dvAxis = %s.zw;", rectEdgeVary.fsIn()); |
| 177 | fragBuilder->codeAppend( " dvLength = heightDistance;"); |
| 178 | fragBuilder->codeAppend( "} else {"); |
| 179 | fragBuilder->codeAppendf(" dvAxis = vec2(-%s.w, %s.z);", |
| 180 | rectEdgeVary.fsIn(), rectEdgeVary.fsIn()); |
| 181 | fragBuilder->codeAppend( " dvLength = widthDistance;"); |
| 182 | fragBuilder->codeAppend( "}"); |
| 183 | |
| 184 | fragBuilder->codeAppend( "float dvSign = sign(dot(offset, dvAxis));"); |
jvanverth | 6c177a1 | 2016-08-17 07:59:41 -0700 | [diff] [blame] | 185 | fragBuilder->codeAppendf("%s = vec4(dvSign * dvAxis, dvLength, 0.0);", |
dvonbeck | 09e12a6 | 2016-08-12 12:50:36 -0700 | [diff] [blame] | 186 | args.fDistanceVectorName); |
| 187 | |
| 188 | } |
| 189 | } |
| 190 | |
| 191 | static void GenKey(const GrGeometryProcessor& gp, |
Brian Salomon | 94efbf5 | 2016-11-29 13:43:05 -0500 | [diff] [blame] | 192 | const GrShaderCaps&, |
dvonbeck | 09e12a6 | 2016-08-12 12:50:36 -0700 | [diff] [blame] | 193 | GrProcessorKeyBuilder* b) { |
| 194 | b->add32(0x0); |
| 195 | } |
| 196 | |
bsalomon | a624bf3 | 2016-09-20 09:12:47 -0700 | [diff] [blame] | 197 | void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor& primProc, |
| 198 | FPCoordTransformIter&& transformIter) override { |
| 199 | const RectGeometryProcessor& rgp = primProc.cast<RectGeometryProcessor>(); |
| 200 | this->setTransformDataHelper(rgp.fLocalMatrix, pdman,&transformIter); |
dvonbeck | 09e12a6 | 2016-08-12 12:50:36 -0700 | [diff] [blame] | 201 | } |
| 202 | |
| 203 | private: |
| 204 | typedef GrGLSLGeometryProcessor INHERITED; |
| 205 | }; |
| 206 | |
Brian Salomon | 94efbf5 | 2016-11-29 13:43:05 -0500 | [diff] [blame] | 207 | void getGLSLProcessorKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const override { |
dvonbeck | 09e12a6 | 2016-08-12 12:50:36 -0700 | [diff] [blame] | 208 | GLSLProcessor::GenKey(*this, caps, b); |
| 209 | } |
| 210 | |
Brian Salomon | 94efbf5 | 2016-11-29 13:43:05 -0500 | [diff] [blame] | 211 | GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const override { |
dvonbeck | 09e12a6 | 2016-08-12 12:50:36 -0700 | [diff] [blame] | 212 | return new GLSLProcessor(); |
| 213 | } |
| 214 | |
| 215 | private: |
| 216 | SkMatrix fLocalMatrix; |
| 217 | |
| 218 | const Attribute* fInPosition; |
| 219 | const Attribute* fInColor; |
| 220 | const Attribute* fInRectEdge; |
| 221 | const Attribute* fInWidthHeight; |
| 222 | |
| 223 | GR_DECLARE_GEOMETRY_PROCESSOR_TEST; |
| 224 | |
| 225 | typedef GrGeometryProcessor INHERITED; |
| 226 | }; |
| 227 | |
| 228 | GR_DEFINE_GEOMETRY_PROCESSOR_TEST(RectGeometryProcessor); |
| 229 | |
| 230 | sk_sp<GrGeometryProcessor> RectGeometryProcessor::TestCreate(GrProcessorTestData* d) { |
| 231 | return sk_sp<GrGeometryProcessor>( |
| 232 | new RectGeometryProcessor(GrTest::TestMatrix(d->fRandom))); |
| 233 | } |
| 234 | |
| 235 | /////////////////////////////////////////////////////////////////////////////// |
| 236 | |
| 237 | class AnalyticRectBatch : public GrVertexBatch { |
| 238 | public: |
Brian Salomon | 25a8809 | 2016-12-01 09:36:50 -0500 | [diff] [blame^] | 239 | DEFINE_OP_CLASS_ID |
dvonbeck | 09e12a6 | 2016-08-12 12:50:36 -0700 | [diff] [blame] | 240 | |
| 241 | AnalyticRectBatch(GrColor color, const SkMatrix& viewMatrix, const SkRect& rect, |
| 242 | const SkRect& croppedRect, const SkRect& bounds) |
| 243 | : INHERITED(ClassID()) |
| 244 | , fViewMatrixIfUsingLocalCoords(viewMatrix) { |
| 245 | SkPoint center = SkPoint::Make(rect.centerX(), rect.centerY()); |
| 246 | viewMatrix.mapPoints(¢er, 1); |
| 247 | SkScalar halfWidth = viewMatrix.mapRadius(SkScalarHalf(rect.width())); |
| 248 | SkScalar halfHeight = viewMatrix.mapRadius(SkScalarHalf(rect.height())); |
| 249 | SkVector downDir = viewMatrix.mapVector(0.0f, 1.0f); |
| 250 | downDir.normalize(); |
| 251 | |
| 252 | SkRect deviceSpaceCroppedRect = croppedRect; |
| 253 | viewMatrix.mapRect(&deviceSpaceCroppedRect); |
| 254 | |
| 255 | fGeoData.emplace_back(Geometry {color, center, downDir, halfWidth, halfHeight, |
| 256 | deviceSpaceCroppedRect}); |
| 257 | |
| 258 | this->setBounds(bounds, HasAABloat::kYes, IsZeroArea::kNo); |
| 259 | } |
| 260 | |
| 261 | const char* name() const override { return "AnalyticRectBatch"; } |
| 262 | |
| 263 | SkString dumpInfo() const override { |
| 264 | SkString string; |
| 265 | for (int i = 0; i < fGeoData.count(); ++i) { |
| 266 | string.appendf("Color: 0x%08x Rect [C:(%.2f, %.2f) D:<%.2f,%.3f> W/2:%.2f H/2:%.2f]\n", |
| 267 | fGeoData[i].fColor, |
| 268 | fGeoData[i].fCenter.x(), fGeoData[i].fCenter.y(), |
| 269 | fGeoData[i].fDownDir.x(), fGeoData[i].fDownDir.y(), |
| 270 | fGeoData[i].fHalfWidth, |
| 271 | fGeoData[i].fHalfHeight); |
| 272 | } |
Brian Salomon | 7c3e718 | 2016-12-01 09:35:30 -0500 | [diff] [blame] | 273 | string.append(DumpPipelineInfo(*this->pipeline())); |
dvonbeck | 09e12a6 | 2016-08-12 12:50:36 -0700 | [diff] [blame] | 274 | string.append(INHERITED::dumpInfo()); |
| 275 | return string; |
| 276 | } |
| 277 | |
| 278 | void computePipelineOptimizations(GrInitInvariantOutput* color, |
| 279 | GrInitInvariantOutput* coverage, |
| 280 | GrBatchToXPOverrides* overrides) const override { |
| 281 | // When this is called on a batch, there is only one geometry bundle |
| 282 | color->setKnownFourComponents(fGeoData[0].fColor); |
| 283 | coverage->setUnknownSingleComponent(); |
| 284 | } |
| 285 | |
| 286 | private: |
| 287 | void initBatchTracker(const GrXPOverridesForBatch& overrides) override { |
| 288 | // Handle any overrides that affect our GP. |
| 289 | overrides.getOverrideColorIfSet(&fGeoData[0].fColor); |
| 290 | if (!overrides.readsLocalCoords()) { |
| 291 | fViewMatrixIfUsingLocalCoords.reset(); |
| 292 | } |
| 293 | } |
| 294 | |
| 295 | void onPrepareDraws(Target* target) const override { |
| 296 | SkMatrix localMatrix; |
| 297 | if (!fViewMatrixIfUsingLocalCoords.invert(&localMatrix)) { |
| 298 | return; |
| 299 | } |
| 300 | |
| 301 | // Setup geometry processor |
Hal Canary | 144caf5 | 2016-11-07 17:57:18 -0500 | [diff] [blame] | 302 | sk_sp<GrGeometryProcessor> gp(new RectGeometryProcessor(localMatrix)); |
dvonbeck | 09e12a6 | 2016-08-12 12:50:36 -0700 | [diff] [blame] | 303 | |
| 304 | int instanceCount = fGeoData.count(); |
| 305 | size_t vertexStride = gp->getVertexStride(); |
| 306 | SkASSERT(vertexStride == sizeof(RectVertex)); |
| 307 | QuadHelper helper; |
| 308 | RectVertex* verts = reinterpret_cast<RectVertex*>(helper.init(target, vertexStride, |
| 309 | instanceCount)); |
| 310 | if (!verts) { |
| 311 | return; |
| 312 | } |
| 313 | |
| 314 | for (int i = 0; i < instanceCount; i++) { |
| 315 | const Geometry& geom = fGeoData[i]; |
| 316 | |
| 317 | GrColor color = geom.fColor; |
| 318 | SkPoint center = geom.fCenter; |
| 319 | SkVector downDir = geom.fDownDir; |
| 320 | SkScalar halfWidth = geom.fHalfWidth; |
| 321 | SkScalar halfHeight = geom.fHalfHeight; |
| 322 | SkRect croppedRect = geom.fCroppedRect; |
| 323 | |
| 324 | SkVector rightDir; |
| 325 | downDir.rotateCCW(&rightDir); |
| 326 | |
| 327 | verts[0].fPos = {croppedRect.fLeft, croppedRect.fTop}; |
| 328 | verts[0].fColor = color; |
| 329 | verts[0].fCenter = center; |
| 330 | verts[0].fDownDir = downDir; |
| 331 | verts[0].fHalfWidth = halfWidth; |
| 332 | verts[0].fHalfHeight = halfHeight; |
| 333 | |
| 334 | verts[1].fPos = {croppedRect.fRight, croppedRect.fTop}; |
| 335 | verts[1].fColor = color; |
| 336 | verts[1].fCenter = center; |
| 337 | verts[1].fDownDir = downDir; |
| 338 | verts[1].fHalfWidth = halfWidth; |
| 339 | verts[1].fHalfHeight = halfHeight; |
| 340 | |
| 341 | verts[2].fPos = {croppedRect.fRight, croppedRect.fBottom}; |
| 342 | verts[2].fColor = color; |
| 343 | verts[2].fCenter = center; |
| 344 | verts[2].fDownDir = downDir; |
| 345 | verts[2].fHalfWidth = halfWidth; |
| 346 | verts[2].fHalfHeight = halfHeight; |
| 347 | |
| 348 | verts[3].fPos = {croppedRect.fLeft, croppedRect.fBottom}; |
| 349 | verts[3].fColor = color; |
| 350 | verts[3].fCenter = center; |
| 351 | verts[3].fDownDir = downDir; |
| 352 | verts[3].fHalfWidth = halfWidth; |
| 353 | verts[3].fHalfHeight = halfHeight; |
| 354 | |
| 355 | verts += kVerticesPerQuad; |
| 356 | } |
Hal Canary | 144caf5 | 2016-11-07 17:57:18 -0500 | [diff] [blame] | 357 | helper.recordDraw(target, gp.get()); |
dvonbeck | 09e12a6 | 2016-08-12 12:50:36 -0700 | [diff] [blame] | 358 | } |
| 359 | |
Brian Salomon | 25a8809 | 2016-12-01 09:36:50 -0500 | [diff] [blame^] | 360 | bool onCombineIfPossible(GrOp* t, const GrCaps& caps) override { |
dvonbeck | 09e12a6 | 2016-08-12 12:50:36 -0700 | [diff] [blame] | 361 | AnalyticRectBatch* that = t->cast<AnalyticRectBatch>(); |
| 362 | if (!GrPipeline::CanCombine(*this->pipeline(), this->bounds(), *that->pipeline(), |
| 363 | that->bounds(), caps)) { |
| 364 | return false; |
| 365 | } |
| 366 | |
| 367 | if (!fViewMatrixIfUsingLocalCoords.cheapEqualTo(that->fViewMatrixIfUsingLocalCoords)) { |
| 368 | return false; |
| 369 | } |
| 370 | |
| 371 | fGeoData.push_back_n(that->fGeoData.count(), that->fGeoData.begin()); |
| 372 | this->joinBounds(*that); |
| 373 | return true; |
| 374 | } |
| 375 | |
| 376 | struct Geometry { |
| 377 | GrColor fColor; |
| 378 | SkPoint fCenter; |
| 379 | SkVector fDownDir; |
| 380 | SkScalar fHalfWidth; |
| 381 | SkScalar fHalfHeight; |
| 382 | SkRect fCroppedRect; |
| 383 | }; |
| 384 | |
| 385 | SkMatrix fViewMatrixIfUsingLocalCoords; |
| 386 | SkSTArray<1, Geometry, true> fGeoData; |
| 387 | |
| 388 | typedef GrVertexBatch INHERITED; |
| 389 | }; |
| 390 | |
| 391 | GrDrawBatch* GrAnalyticRectBatch::CreateAnalyticRectBatch(GrColor color, |
| 392 | const SkMatrix& viewMatrix, |
| 393 | const SkRect& rect, |
| 394 | const SkRect& croppedRect, |
| 395 | const SkRect& bounds) { |
| 396 | return new AnalyticRectBatch(color, viewMatrix, rect, croppedRect, bounds); |
| 397 | } |
| 398 | |
| 399 | #ifdef GR_TEST_UTILS |
| 400 | |
| 401 | DRAW_BATCH_TEST_DEFINE(AnalyticRectBatch) { |
| 402 | SkMatrix viewMatrix = GrTest::TestMatrix(random); |
| 403 | GrColor color = GrRandomColor(random); |
| 404 | SkRect rect = GrTest::TestSquare(random); |
| 405 | SkRect croppedRect = GrTest::TestSquare(random); |
| 406 | SkRect bounds = GrTest::TestSquare(random); |
| 407 | return new AnalyticRectBatch(color, viewMatrix, rect, croppedRect, bounds); |
| 408 | } |
| 409 | |
| 410 | #endif |