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