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