beziereffects.cpp revision 88c23fc6e8ed0243159fd17d815b4f813805647b
1 2/* 3 * Copyright 2013 Google Inc. 4 * 5 * Use of this source code is governed by a BSD-style license that can be 6 * found in the LICENSE file. 7 */ 8 9// This test only works with the GPU backend. 10 11#include "gm.h" 12 13#if SK_SUPPORT_GPU 14 15#include "GrBatchTarget.h" 16#include "GrContext.h" 17#include "GrPathUtils.h" 18#include "GrTest.h" 19#include "GrTestBatch.h" 20#include "SkColorPriv.h" 21#include "SkDevice.h" 22#include "SkGeometry.h" 23 24#include "effects/GrBezierEffect.h" 25 26static inline SkScalar eval_line(const SkPoint& p, const SkScalar lineEq[3], SkScalar sign) { 27 return sign * (lineEq[0] * p.fX + lineEq[1] * p.fY + lineEq[2]); 28} 29 30namespace skiagm { 31 32class BezierCubicOrConicTestBatch : public GrTestBatch { 33public: 34 struct Geometry : public GrTestBatch::Geometry { 35 SkRect fBounds; 36 }; 37 38 const char* name() const override { return "BezierCubicOrConicTestBatch"; } 39 40 static GrBatch* Create(const GrGeometryProcessor* gp, const Geometry& geo, 41 const SkScalar klmEqs[9], SkScalar sign) { 42 return SkNEW_ARGS(BezierCubicOrConicTestBatch, (gp, geo, klmEqs, sign)); 43 } 44 45private: 46 BezierCubicOrConicTestBatch(const GrGeometryProcessor* gp, const Geometry& geo, 47 const SkScalar klmEqs[9], SkScalar sign) 48 : INHERITED(gp, geo.fBounds) { 49 for (int i = 0; i < 9; i++) { 50 fKlmEqs[i] = klmEqs[i]; 51 } 52 53 fGeometry = geo; 54 fSign = sign; 55 } 56 57 struct Vertex { 58 SkPoint fPosition; 59 float fKLM[4]; // The last value is ignored. The effect expects a vec4f. 60 }; 61 62 Geometry* geoData(int index) override { 63 SkASSERT(0 == index); 64 return &fGeometry; 65 } 66 67 const Geometry* geoData(int index) const override { 68 SkASSERT(0 == index); 69 return &fGeometry; 70 } 71 72 void onGenerateGeometry(GrBatchTarget* batchTarget, const GrPipeline* pipeline) override { 73 QuadHelper helper; 74 size_t vertexStride = this->geometryProcessor()->getVertexStride(); 75 SkASSERT(vertexStride == sizeof(Vertex)); 76 Vertex* verts = reinterpret_cast<Vertex*>(helper.init(batchTarget, vertexStride, 1)); 77 if (!verts) { 78 return; 79 } 80 81 verts[0].fPosition.setRectFan(fGeometry.fBounds.fLeft, fGeometry.fBounds.fTop, 82 fGeometry.fBounds.fRight, fGeometry.fBounds.fBottom, 83 sizeof(Vertex)); 84 for (int v = 0; v < 4; ++v) { 85 verts[v].fKLM[0] = eval_line(verts[v].fPosition, fKlmEqs + 0, fSign); 86 verts[v].fKLM[1] = eval_line(verts[v].fPosition, fKlmEqs + 3, fSign); 87 verts[v].fKLM[2] = eval_line(verts[v].fPosition, fKlmEqs + 6, 1.f); 88 } 89 helper.issueDraw(batchTarget); 90 } 91 92 Geometry fGeometry; 93 SkScalar fKlmEqs[9]; 94 SkScalar fSign; 95 96 static const int kVertsPerCubic = 4; 97 static const int kIndicesPerCubic = 6; 98 99 typedef GrTestBatch INHERITED; 100}; 101 102/** 103 * This GM directly exercises effects that draw Bezier curves in the GPU backend. 104 */ 105class BezierCubicEffects : public GM { 106public: 107 BezierCubicEffects() { 108 this->setBGColor(0xFFFFFFFF); 109 } 110 111protected: 112 SkString onShortName() override { 113 return SkString("bezier_cubic_effects"); 114 } 115 116 SkISize onISize() override { 117 return SkISize::Make(800, 800); 118 } 119 120 void onDraw(SkCanvas* canvas) override { 121 GrRenderTarget* rt = canvas->internal_private_accessTopLayerRenderTarget(); 122 if (NULL == rt) { 123 this->drawGpuOnlyMessage(canvas); 124 return; 125 } 126 GrContext* context = rt->getContext(); 127 if (NULL == context) { 128 return; 129 } 130 131 struct Vertex { 132 SkPoint fPosition; 133 float fKLM[4]; // The last value is ignored. The effect expects a vec4f. 134 }; 135 136 static const int kNumCubics = 15; 137 SkRandom rand; 138 139 // Mult by 3 for each edge effect type 140 int numCols = SkScalarCeilToInt(SkScalarSqrt(SkIntToScalar(kNumCubics*3))); 141 int numRows = SkScalarCeilToInt(SkIntToScalar(kNumCubics*3) / numCols); 142 SkScalar w = SkIntToScalar(rt->width()) / numCols; 143 SkScalar h = SkIntToScalar(rt->height()) / numRows; 144 int row = 0; 145 int col = 0; 146 static const GrColor color = 0xff000000; 147 148 for (int i = 0; i < kNumCubics; ++i) { 149 SkPoint baseControlPts[] = { 150 {rand.nextRangeF(0.f, w), rand.nextRangeF(0.f, h)}, 151 {rand.nextRangeF(0.f, w), rand.nextRangeF(0.f, h)}, 152 {rand.nextRangeF(0.f, w), rand.nextRangeF(0.f, h)}, 153 {rand.nextRangeF(0.f, w), rand.nextRangeF(0.f, h)} 154 }; 155 for(int edgeType = 0; edgeType < kGrProcessorEdgeTypeCnt; ++edgeType) { 156 SkAutoTUnref<GrGeometryProcessor> gp; 157 { // scope to contain GrTestTarget 158 GrTestTarget tt; 159 context->getTestTarget(&tt); 160 if (NULL == tt.target()) { 161 continue; 162 } 163 GrPrimitiveEdgeType et = (GrPrimitiveEdgeType)edgeType; 164 gp.reset(GrCubicEffect::Create(color, SkMatrix::I(), et, 165 *tt.target()->caps())); 166 if (!gp) { 167 continue; 168 } 169 } 170 171 SkScalar x = SkScalarMul(col, w); 172 SkScalar y = SkScalarMul(row, h); 173 SkPoint controlPts[] = { 174 {x + baseControlPts[0].fX, y + baseControlPts[0].fY}, 175 {x + baseControlPts[1].fX, y + baseControlPts[1].fY}, 176 {x + baseControlPts[2].fX, y + baseControlPts[2].fY}, 177 {x + baseControlPts[3].fX, y + baseControlPts[3].fY} 178 }; 179 SkPoint chopped[10]; 180 SkScalar klmEqs[9]; 181 SkScalar klmSigns[3]; 182 int cnt = GrPathUtils::chopCubicAtLoopIntersection(controlPts, 183 chopped, 184 klmEqs, 185 klmSigns); 186 187 SkPaint ctrlPtPaint; 188 ctrlPtPaint.setColor(rand.nextU() | 0xFF000000); 189 for (int i = 0; i < 4; ++i) { 190 canvas->drawCircle(controlPts[i].fX, controlPts[i].fY, 6.f, ctrlPtPaint); 191 } 192 193 SkPaint polyPaint; 194 polyPaint.setColor(0xffA0A0A0); 195 polyPaint.setStrokeWidth(0); 196 polyPaint.setStyle(SkPaint::kStroke_Style); 197 canvas->drawPoints(SkCanvas::kPolygon_PointMode, 4, controlPts, polyPaint); 198 199 SkPaint choppedPtPaint; 200 choppedPtPaint.setColor(~ctrlPtPaint.getColor() | 0xFF000000); 201 202 for (int c = 0; c < cnt; ++c) { 203 SkPoint* pts = chopped + 3 * c; 204 205 for (int i = 0; i < 4; ++i) { 206 canvas->drawCircle(pts[i].fX, pts[i].fY, 3.f, choppedPtPaint); 207 } 208 209 SkRect bounds; 210 bounds.set(pts, 4); 211 212 SkPaint boundsPaint; 213 boundsPaint.setColor(0xff808080); 214 boundsPaint.setStrokeWidth(0); 215 boundsPaint.setStyle(SkPaint::kStroke_Style); 216 canvas->drawRect(bounds, boundsPaint); 217 218 GrTestTarget tt; 219 context->getTestTarget(&tt); 220 SkASSERT(tt.target()); 221 222 GrPipelineBuilder pipelineBuilder; 223 pipelineBuilder.setRenderTarget(rt); 224 225 BezierCubicOrConicTestBatch::Geometry geometry; 226 geometry.fColor = color; 227 geometry.fBounds = bounds; 228 229 SkAutoTUnref<GrBatch> batch( 230 BezierCubicOrConicTestBatch::Create(gp, geometry, klmEqs, klmSigns[c])); 231 232 tt.target()->drawBatch(&pipelineBuilder, batch); 233 } 234 ++col; 235 if (numCols == col) { 236 col = 0; 237 ++row; 238 } 239 } 240 } 241 } 242 243private: 244 typedef GM INHERITED; 245}; 246 247////////////////////////////////////////////////////////////////////////////// 248 249/** 250 * This GM directly exercises effects that draw Bezier curves in the GPU backend. 251 */ 252class BezierConicEffects : public GM { 253public: 254 BezierConicEffects() { 255 this->setBGColor(0xFFFFFFFF); 256 } 257 258protected: 259 SkString onShortName() override { 260 return SkString("bezier_conic_effects"); 261 } 262 263 SkISize onISize() override { 264 return SkISize::Make(800, 800); 265 } 266 267 268 void onDraw(SkCanvas* canvas) override { 269 GrRenderTarget* rt = canvas->internal_private_accessTopLayerRenderTarget(); 270 if (NULL == rt) { 271 this->drawGpuOnlyMessage(canvas); 272 return; 273 } 274 GrContext* context = rt->getContext(); 275 if (NULL == context) { 276 return; 277 } 278 279 struct Vertex { 280 SkPoint fPosition; 281 float fKLM[4]; // The last value is ignored. The effect expects a vec4f. 282 }; 283 284 static const int kNumConics = 10; 285 SkRandom rand; 286 287 // Mult by 3 for each edge effect type 288 int numCols = SkScalarCeilToInt(SkScalarSqrt(SkIntToScalar(kNumConics*3))); 289 int numRows = SkScalarCeilToInt(SkIntToScalar(kNumConics*3) / numCols); 290 SkScalar w = SkIntToScalar(rt->width()) / numCols; 291 SkScalar h = SkIntToScalar(rt->height()) / numRows; 292 int row = 0; 293 int col = 0; 294 static const GrColor color = 0xff000000; 295 296 for (int i = 0; i < kNumConics; ++i) { 297 SkPoint baseControlPts[] = { 298 {rand.nextRangeF(0.f, w), rand.nextRangeF(0.f, h)}, 299 {rand.nextRangeF(0.f, w), rand.nextRangeF(0.f, h)}, 300 {rand.nextRangeF(0.f, w), rand.nextRangeF(0.f, h)} 301 }; 302 SkScalar weight = rand.nextRangeF(0.f, 2.f); 303 for(int edgeType = 0; edgeType < kGrProcessorEdgeTypeCnt; ++edgeType) { 304 SkAutoTUnref<GrGeometryProcessor> gp; 305 { // scope to contain GrTestTarget 306 GrTestTarget tt; 307 context->getTestTarget(&tt); 308 if (NULL == tt.target()) { 309 continue; 310 } 311 GrPrimitiveEdgeType et = (GrPrimitiveEdgeType)edgeType; 312 gp.reset(GrConicEffect::Create(color, SkMatrix::I(), et, 313 *tt.target()->caps(), SkMatrix::I())); 314 if (!gp) { 315 continue; 316 } 317 } 318 319 SkScalar x = SkScalarMul(col, w); 320 SkScalar y = SkScalarMul(row, h); 321 SkPoint controlPts[] = { 322 {x + baseControlPts[0].fX, y + baseControlPts[0].fY}, 323 {x + baseControlPts[1].fX, y + baseControlPts[1].fY}, 324 {x + baseControlPts[2].fX, y + baseControlPts[2].fY} 325 }; 326 SkConic dst[4]; 327 SkScalar klmEqs[9]; 328 int cnt = chop_conic(controlPts, dst, weight); 329 GrPathUtils::getConicKLM(controlPts, weight, klmEqs); 330 331 SkPaint ctrlPtPaint; 332 ctrlPtPaint.setColor(rand.nextU() | 0xFF000000); 333 for (int i = 0; i < 3; ++i) { 334 canvas->drawCircle(controlPts[i].fX, controlPts[i].fY, 6.f, ctrlPtPaint); 335 } 336 337 SkPaint polyPaint; 338 polyPaint.setColor(0xffA0A0A0); 339 polyPaint.setStrokeWidth(0); 340 polyPaint.setStyle(SkPaint::kStroke_Style); 341 canvas->drawPoints(SkCanvas::kPolygon_PointMode, 3, controlPts, polyPaint); 342 343 SkPaint choppedPtPaint; 344 choppedPtPaint.setColor(~ctrlPtPaint.getColor() | 0xFF000000); 345 346 for (int c = 0; c < cnt; ++c) { 347 SkPoint* pts = dst[c].fPts; 348 for (int i = 0; i < 3; ++i) { 349 canvas->drawCircle(pts[i].fX, pts[i].fY, 3.f, choppedPtPaint); 350 } 351 352 SkRect bounds; 353 //SkPoint bPts[] = {{0.f, 0.f}, {800.f, 800.f}}; 354 //bounds.set(bPts, 2); 355 bounds.set(pts, 3); 356 357 SkPaint boundsPaint; 358 boundsPaint.setColor(0xff808080); 359 boundsPaint.setStrokeWidth(0); 360 boundsPaint.setStyle(SkPaint::kStroke_Style); 361 canvas->drawRect(bounds, boundsPaint); 362 363 GrTestTarget tt; 364 context->getTestTarget(&tt); 365 SkASSERT(tt.target()); 366 367 GrPipelineBuilder pipelineBuilder; 368 pipelineBuilder.setRenderTarget(rt); 369 370 BezierCubicOrConicTestBatch::Geometry geometry; 371 geometry.fColor = color; 372 geometry.fBounds = bounds; 373 374 SkAutoTUnref<GrBatch> batch( 375 BezierCubicOrConicTestBatch::Create(gp, geometry, klmEqs, 1.f)); 376 377 tt.target()->drawBatch(&pipelineBuilder, batch); 378 } 379 ++col; 380 if (numCols == col) { 381 col = 0; 382 ++row; 383 } 384 } 385 } 386 } 387 388private: 389 // Uses the max curvature function for quads to estimate 390 // where to chop the conic. If the max curvature is not 391 // found along the curve segment it will return 1 and 392 // dst[0] is the original conic. If it returns 2 the dst[0] 393 // and dst[1] are the two new conics. 394 int split_conic(const SkPoint src[3], SkConic dst[2], const SkScalar weight) { 395 SkScalar t = SkFindQuadMaxCurvature(src); 396 if (t == 0) { 397 if (dst) { 398 dst[0].set(src, weight); 399 } 400 return 1; 401 } else { 402 if (dst) { 403 SkConic conic; 404 conic.set(src, weight); 405 conic.chopAt(t, dst); 406 } 407 return 2; 408 } 409 } 410 411 // Calls split_conic on the entire conic and then once more on each subsection. 412 // Most cases will result in either 1 conic (chop point is not within t range) 413 // or 3 points (split once and then one subsection is split again). 414 int chop_conic(const SkPoint src[3], SkConic dst[4], const SkScalar weight) { 415 SkConic dstTemp[2]; 416 int conicCnt = split_conic(src, dstTemp, weight); 417 if (2 == conicCnt) { 418 int conicCnt2 = split_conic(dstTemp[0].fPts, dst, dstTemp[0].fW); 419 conicCnt = conicCnt2 + split_conic(dstTemp[1].fPts, &dst[conicCnt2], dstTemp[1].fW); 420 } else { 421 dst[0] = dstTemp[0]; 422 } 423 return conicCnt; 424 } 425 426 typedef GM INHERITED; 427}; 428 429////////////////////////////////////////////////////////////////////////////// 430 431class BezierQuadTestBatch : public GrTestBatch { 432public: 433 struct Geometry : public GrTestBatch::Geometry { 434 SkRect fBounds; 435 }; 436 437 const char* name() const override { return "BezierQuadTestBatch"; } 438 439 static GrBatch* Create(const GrGeometryProcessor* gp, const Geometry& geo, 440 const GrPathUtils::QuadUVMatrix& devToUV) { 441 return SkNEW_ARGS(BezierQuadTestBatch, (gp, geo, devToUV)); 442 } 443 444private: 445 BezierQuadTestBatch(const GrGeometryProcessor* gp, const Geometry& geo, 446 const GrPathUtils::QuadUVMatrix& devToUV) 447 : INHERITED(gp, geo.fBounds) 448 , fGeometry(geo) 449 , fDevToUV(devToUV) { 450 } 451 452 struct Vertex { 453 SkPoint fPosition; 454 float fKLM[4]; // The last value is ignored. The effect expects a vec4f. 455 }; 456 457 Geometry* geoData(int index) override { 458 SkASSERT(0 == index); 459 return &fGeometry; 460 } 461 462 const Geometry* geoData(int index) const override { 463 SkASSERT(0 == index); 464 return &fGeometry; 465 } 466 467 void onGenerateGeometry(GrBatchTarget* batchTarget, const GrPipeline* pipeline) override { 468 QuadHelper helper; 469 size_t vertexStride = this->geometryProcessor()->getVertexStride(); 470 SkASSERT(vertexStride == sizeof(Vertex)); 471 Vertex* verts = reinterpret_cast<Vertex*>(helper.init(batchTarget, vertexStride, 1)); 472 if (!verts) { 473 return; 474 } 475 verts[0].fPosition.setRectFan(fGeometry.fBounds.fLeft, fGeometry.fBounds.fTop, 476 fGeometry.fBounds.fRight, fGeometry.fBounds.fBottom, 477 sizeof(Vertex)); 478 fDevToUV.apply<4, sizeof(Vertex), sizeof(SkPoint)>(verts); 479 helper.issueDraw(batchTarget); 480 } 481 482 Geometry fGeometry; 483 GrPathUtils::QuadUVMatrix fDevToUV; 484 485 static const int kVertsPerCubic = 4; 486 static const int kIndicesPerCubic = 6; 487 488 typedef GrTestBatch INHERITED; 489}; 490 491/** 492 * This GM directly exercises effects that draw Bezier quad curves in the GPU backend. 493 */ 494class BezierQuadEffects : public GM { 495public: 496 BezierQuadEffects() { 497 this->setBGColor(0xFFFFFFFF); 498 } 499 500protected: 501 SkString onShortName() override { 502 return SkString("bezier_quad_effects"); 503 } 504 505 SkISize onISize() override { 506 return SkISize::Make(800, 800); 507 } 508 509 510 void onDraw(SkCanvas* canvas) override { 511 GrRenderTarget* rt = canvas->internal_private_accessTopLayerRenderTarget(); 512 if (NULL == rt) { 513 this->drawGpuOnlyMessage(canvas); 514 return; 515 } 516 GrContext* context = rt->getContext(); 517 if (NULL == context) { 518 return; 519 } 520 521 struct Vertex { 522 SkPoint fPosition; 523 float fUV[4]; // The last two values are ignored. The effect expects a vec4f. 524 }; 525 526 static const int kNumQuads = 5; 527 SkRandom rand; 528 529 int numCols = SkScalarCeilToInt(SkScalarSqrt(SkIntToScalar(kNumQuads*3))); 530 int numRows = SkScalarCeilToInt(SkIntToScalar(kNumQuads*3) / numCols); 531 SkScalar w = SkIntToScalar(rt->width()) / numCols; 532 SkScalar h = SkIntToScalar(rt->height()) / numRows; 533 int row = 0; 534 int col = 0; 535 static const GrColor color = 0xff000000; 536 537 for (int i = 0; i < kNumQuads; ++i) { 538 SkPoint baseControlPts[] = { 539 {rand.nextRangeF(0.f, w), rand.nextRangeF(0.f, h)}, 540 {rand.nextRangeF(0.f, w), rand.nextRangeF(0.f, h)}, 541 {rand.nextRangeF(0.f, w), rand.nextRangeF(0.f, h)} 542 }; 543 for(int edgeType = 0; edgeType < kGrProcessorEdgeTypeCnt; ++edgeType) { 544 SkAutoTUnref<GrGeometryProcessor> gp; 545 { // scope to contain GrTestTarget 546 GrTestTarget tt; 547 context->getTestTarget(&tt); 548 if (NULL == tt.target()) { 549 continue; 550 } 551 GrPrimitiveEdgeType et = (GrPrimitiveEdgeType)edgeType; 552 gp.reset(GrQuadEffect::Create(color, SkMatrix::I(), et, 553 *tt.target()->caps(), SkMatrix::I())); 554 if (!gp) { 555 continue; 556 } 557 } 558 559 SkScalar x = SkScalarMul(col, w); 560 SkScalar y = SkScalarMul(row, h); 561 SkPoint controlPts[] = { 562 {x + baseControlPts[0].fX, y + baseControlPts[0].fY}, 563 {x + baseControlPts[1].fX, y + baseControlPts[1].fY}, 564 {x + baseControlPts[2].fX, y + baseControlPts[2].fY} 565 }; 566 SkPoint chopped[5]; 567 int cnt = SkChopQuadAtMaxCurvature(controlPts, chopped); 568 569 SkPaint ctrlPtPaint; 570 ctrlPtPaint.setColor(rand.nextU() | 0xFF000000); 571 for (int i = 0; i < 3; ++i) { 572 canvas->drawCircle(controlPts[i].fX, controlPts[i].fY, 6.f, ctrlPtPaint); 573 } 574 575 SkPaint polyPaint; 576 polyPaint.setColor(0xffA0A0A0); 577 polyPaint.setStrokeWidth(0); 578 polyPaint.setStyle(SkPaint::kStroke_Style); 579 canvas->drawPoints(SkCanvas::kPolygon_PointMode, 3, controlPts, polyPaint); 580 581 SkPaint choppedPtPaint; 582 choppedPtPaint.setColor(~ctrlPtPaint.getColor() | 0xFF000000); 583 584 for (int c = 0; c < cnt; ++c) { 585 SkPoint* pts = chopped + 2 * c; 586 587 for (int i = 0; i < 3; ++i) { 588 canvas->drawCircle(pts[i].fX, pts[i].fY, 3.f, choppedPtPaint); 589 } 590 591 SkRect bounds; 592 bounds.set(pts, 3); 593 594 SkPaint boundsPaint; 595 boundsPaint.setColor(0xff808080); 596 boundsPaint.setStrokeWidth(0); 597 boundsPaint.setStyle(SkPaint::kStroke_Style); 598 canvas->drawRect(bounds, boundsPaint); 599 600 GrTestTarget tt; 601 context->getTestTarget(&tt); 602 SkASSERT(tt.target()); 603 604 GrPipelineBuilder pipelineBuilder; 605 pipelineBuilder.setRenderTarget(rt); 606 607 GrPathUtils::QuadUVMatrix DevToUV(pts); 608 609 BezierQuadTestBatch::Geometry geometry; 610 geometry.fColor = color; 611 geometry.fBounds = bounds; 612 613 SkAutoTUnref<GrBatch> batch(BezierQuadTestBatch::Create(gp, geometry, DevToUV)); 614 615 tt.target()->drawBatch(&pipelineBuilder, batch); 616 } 617 ++col; 618 if (numCols == col) { 619 col = 0; 620 ++row; 621 } 622 } 623 } 624 } 625 626private: 627 typedef GM INHERITED; 628}; 629 630DEF_GM( return SkNEW(BezierCubicEffects); ) 631DEF_GM( return SkNEW(BezierConicEffects); ) 632DEF_GM( return SkNEW(BezierQuadEffects); ) 633 634} 635 636#endif 637