beziereffects.cpp revision 9d524f22bfde5dc3dc8f48e1be39bdebd3bb0304
1/* 2 * Copyright 2013 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// This test only works with the GPU backend. 9 10#include "gm.h" 11 12#if SK_SUPPORT_GPU 13 14#include "GrDrawContextPriv.h" 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) const 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 SkAutoTUnref<GrDrawContext> drawContext(context->drawContext(rt)); 133 if (!drawContext) { 134 return; 135 } 136 137 struct Vertex { 138 SkPoint fPosition; 139 float fKLM[4]; // The last value is ignored. The effect expects a vec4f. 140 }; 141 142 static const int kNumCubics = 15; 143 SkRandom rand; 144 145 // Mult by 3 for each edge effect type 146 int numCols = SkScalarCeilToInt(SkScalarSqrt(SkIntToScalar(kNumCubics*3))); 147 int numRows = SkScalarCeilToInt(SkIntToScalar(kNumCubics*3) / numCols); 148 SkScalar w = SkIntToScalar(rt->width()) / numCols; 149 SkScalar h = SkIntToScalar(rt->height()) / numRows; 150 int row = 0; 151 int col = 0; 152 static const GrColor color = 0xff000000; 153 154 for (int i = 0; i < kNumCubics; ++i) { 155 SkPoint baseControlPts[] = { 156 {rand.nextRangeF(0.f, w), rand.nextRangeF(0.f, h)}, 157 {rand.nextRangeF(0.f, w), rand.nextRangeF(0.f, h)}, 158 {rand.nextRangeF(0.f, w), rand.nextRangeF(0.f, h)}, 159 {rand.nextRangeF(0.f, w), rand.nextRangeF(0.f, h)} 160 }; 161 for(int edgeType = 0; edgeType < kGrProcessorEdgeTypeCnt; ++edgeType) { 162 SkAutoTUnref<GrGeometryProcessor> gp; 163 GrPrimitiveEdgeType et = (GrPrimitiveEdgeType)edgeType; 164 gp.reset(GrCubicEffect::Create(color, SkMatrix::I(), et, 165 *context->caps())); 166 if (!gp) { 167 continue; 168 } 169 SkScalar x = SkScalarMul(col, w); 170 SkScalar y = SkScalarMul(row, h); 171 SkPoint controlPts[] = { 172 {x + baseControlPts[0].fX, y + baseControlPts[0].fY}, 173 {x + baseControlPts[1].fX, y + baseControlPts[1].fY}, 174 {x + baseControlPts[2].fX, y + baseControlPts[2].fY}, 175 {x + baseControlPts[3].fX, y + baseControlPts[3].fY} 176 }; 177 SkPoint chopped[10]; 178 SkScalar klmEqs[9]; 179 SkScalar klmSigns[3]; 180 int cnt = GrPathUtils::chopCubicAtLoopIntersection(controlPts, 181 chopped, 182 klmEqs, 183 klmSigns); 184 185 SkPaint ctrlPtPaint; 186 ctrlPtPaint.setColor(rand.nextU() | 0xFF000000); 187 for (int i = 0; i < 4; ++i) { 188 canvas->drawCircle(controlPts[i].fX, controlPts[i].fY, 6.f, ctrlPtPaint); 189 } 190 191 SkPaint polyPaint; 192 polyPaint.setColor(0xffA0A0A0); 193 polyPaint.setStrokeWidth(0); 194 polyPaint.setStyle(SkPaint::kStroke_Style); 195 canvas->drawPoints(SkCanvas::kPolygon_PointMode, 4, controlPts, polyPaint); 196 197 SkPaint choppedPtPaint; 198 choppedPtPaint.setColor(~ctrlPtPaint.getColor() | 0xFF000000); 199 200 for (int c = 0; c < cnt; ++c) { 201 SkPoint* pts = chopped + 3 * c; 202 203 for (int i = 0; i < 4; ++i) { 204 canvas->drawCircle(pts[i].fX, pts[i].fY, 3.f, choppedPtPaint); 205 } 206 207 SkRect bounds; 208 bounds.set(pts, 4); 209 210 SkPaint boundsPaint; 211 boundsPaint.setColor(0xff808080); 212 boundsPaint.setStrokeWidth(0); 213 boundsPaint.setStyle(SkPaint::kStroke_Style); 214 canvas->drawRect(bounds, boundsPaint); 215 216 GrPipelineBuilder pipelineBuilder; 217 pipelineBuilder.setXPFactory( 218 GrPorterDuffXPFactory::Create(SkXfermode::kSrc_Mode))->unref(); 219 pipelineBuilder.setRenderTarget(rt); 220 221 BezierCubicOrConicTestBatch::Geometry geometry; 222 geometry.fColor = color; 223 geometry.fBounds = bounds; 224 225 SkAutoTUnref<GrDrawBatch> batch( 226 BezierCubicOrConicTestBatch::Create(gp, geometry, klmEqs, klmSigns[c])); 227 228 drawContext->drawContextPriv().testingOnly_drawBatch(pipelineBuilder, batch); 229 } 230 ++col; 231 if (numCols == col) { 232 col = 0; 233 ++row; 234 } 235 } 236 } 237 } 238 239private: 240 typedef GM INHERITED; 241}; 242 243////////////////////////////////////////////////////////////////////////////// 244 245/** 246 * This GM directly exercises effects that draw Bezier curves in the GPU backend. 247 */ 248class BezierConicEffects : public GM { 249public: 250 BezierConicEffects() { 251 this->setBGColor(0xFFFFFFFF); 252 } 253 254protected: 255 SkString onShortName() override { 256 return SkString("bezier_conic_effects"); 257 } 258 259 SkISize onISize() override { 260 return SkISize::Make(800, 800); 261 } 262 263 264 void onDraw(SkCanvas* canvas) override { 265 GrRenderTarget* rt = canvas->internal_private_accessTopLayerRenderTarget(); 266 if (nullptr == rt) { 267 skiagm::GM::DrawGpuOnlyMessage(canvas); 268 return; 269 } 270 GrContext* context = rt->getContext(); 271 if (nullptr == context) { 272 return; 273 } 274 275 SkAutoTUnref<GrDrawContext> drawContext(context->drawContext(rt)); 276 if (!drawContext) { 277 return; 278 } 279 280 struct Vertex { 281 SkPoint fPosition; 282 float fKLM[4]; // The last value is ignored. The effect expects a vec4f. 283 }; 284 285 static const int kNumConics = 10; 286 SkRandom rand; 287 288 // Mult by 3 for each edge effect type 289 int numCols = SkScalarCeilToInt(SkScalarSqrt(SkIntToScalar(kNumConics*3))); 290 int numRows = SkScalarCeilToInt(SkIntToScalar(kNumConics*3) / numCols); 291 SkScalar w = SkIntToScalar(rt->width()) / numCols; 292 SkScalar h = SkIntToScalar(rt->height()) / numRows; 293 int row = 0; 294 int col = 0; 295 static const GrColor color = 0xff000000; 296 297 for (int i = 0; i < kNumConics; ++i) { 298 SkPoint baseControlPts[] = { 299 {rand.nextRangeF(0.f, w), rand.nextRangeF(0.f, h)}, 300 {rand.nextRangeF(0.f, w), rand.nextRangeF(0.f, h)}, 301 {rand.nextRangeF(0.f, w), rand.nextRangeF(0.f, h)} 302 }; 303 SkScalar weight = rand.nextRangeF(0.f, 2.f); 304 for(int edgeType = 0; edgeType < kGrProcessorEdgeTypeCnt; ++edgeType) { 305 SkAutoTUnref<GrGeometryProcessor> gp; 306 GrPrimitiveEdgeType et = (GrPrimitiveEdgeType)edgeType; 307 gp.reset(GrConicEffect::Create(color, SkMatrix::I(), et, 308 *context->caps(), SkMatrix::I(), false)); 309 if (!gp) { 310 continue; 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 GrPipelineBuilder pipelineBuilder; 358 pipelineBuilder.setXPFactory( 359 GrPorterDuffXPFactory::Create(SkXfermode::kSrc_Mode))->unref(); 360 pipelineBuilder.setRenderTarget(rt); 361 362 BezierCubicOrConicTestBatch::Geometry geometry; 363 geometry.fColor = color; 364 geometry.fBounds = bounds; 365 366 SkAutoTUnref<GrDrawBatch> batch( 367 BezierCubicOrConicTestBatch::Create(gp, geometry, klmEqs, 1.f)); 368 369 drawContext->drawContextPriv().testingOnly_drawBatch(pipelineBuilder, batch); 370 } 371 ++col; 372 if (numCols == col) { 373 col = 0; 374 ++row; 375 } 376 } 377 } 378 } 379 380private: 381 // Uses the max curvature function for quads to estimate 382 // where to chop the conic. If the max curvature is not 383 // found along the curve segment it will return 1 and 384 // dst[0] is the original conic. If it returns 2 the dst[0] 385 // and dst[1] are the two new conics. 386 int split_conic(const SkPoint src[3], SkConic dst[2], const SkScalar weight) { 387 SkScalar t = SkFindQuadMaxCurvature(src); 388 if (t == 0) { 389 if (dst) { 390 dst[0].set(src, weight); 391 } 392 return 1; 393 } else { 394 if (dst) { 395 SkConic conic; 396 conic.set(src, weight); 397 conic.chopAt(t, dst); 398 } 399 return 2; 400 } 401 } 402 403 // Calls split_conic on the entire conic and then once more on each subsection. 404 // Most cases will result in either 1 conic (chop point is not within t range) 405 // or 3 points (split once and then one subsection is split again). 406 int chop_conic(const SkPoint src[3], SkConic dst[4], const SkScalar weight) { 407 SkConic dstTemp[2]; 408 int conicCnt = split_conic(src, dstTemp, weight); 409 if (2 == conicCnt) { 410 int conicCnt2 = split_conic(dstTemp[0].fPts, dst, dstTemp[0].fW); 411 conicCnt = conicCnt2 + split_conic(dstTemp[1].fPts, &dst[conicCnt2], dstTemp[1].fW); 412 } else { 413 dst[0] = dstTemp[0]; 414 } 415 return conicCnt; 416 } 417 418 typedef GM INHERITED; 419}; 420 421////////////////////////////////////////////////////////////////////////////// 422 423class BezierQuadTestBatch : public GrTestBatch { 424public: 425 DEFINE_BATCH_CLASS_ID 426 struct Geometry : public GrTestBatch::Geometry { 427 SkRect fBounds; 428 }; 429 430 const char* name() const override { return "BezierQuadTestBatch"; } 431 432 static GrDrawBatch* Create(const GrGeometryProcessor* gp, const Geometry& geo, 433 const GrPathUtils::QuadUVMatrix& devToUV) { 434 return new BezierQuadTestBatch(gp, geo, devToUV); 435 } 436 437private: 438 BezierQuadTestBatch(const GrGeometryProcessor* gp, const Geometry& geo, 439 const GrPathUtils::QuadUVMatrix& devToUV) 440 : INHERITED(ClassID(), gp, geo.fBounds) 441 , fGeometry(geo) 442 , fDevToUV(devToUV) { 443 } 444 445 struct Vertex { 446 SkPoint fPosition; 447 float fKLM[4]; // The last value is ignored. The effect expects a vec4f. 448 }; 449 450 Geometry* geoData(int index) override { 451 SkASSERT(0 == index); 452 return &fGeometry; 453 } 454 455 const Geometry* geoData(int index) const override { 456 SkASSERT(0 == index); 457 return &fGeometry; 458 } 459 460 void generateGeometry(Target* target) const override { 461 QuadHelper helper; 462 size_t vertexStride = this->geometryProcessor()->getVertexStride(); 463 SkASSERT(vertexStride == sizeof(Vertex)); 464 Vertex* verts = reinterpret_cast<Vertex*>(helper.init(target, vertexStride, 1)); 465 if (!verts) { 466 return; 467 } 468 verts[0].fPosition.setRectFan(fGeometry.fBounds.fLeft, fGeometry.fBounds.fTop, 469 fGeometry.fBounds.fRight, fGeometry.fBounds.fBottom, 470 sizeof(Vertex)); 471 fDevToUV.apply<4, sizeof(Vertex), sizeof(SkPoint)>(verts); 472 helper.recordDraw(target); 473 } 474 475 Geometry fGeometry; 476 GrPathUtils::QuadUVMatrix fDevToUV; 477 478 static const int kVertsPerCubic = 4; 479 static const int kIndicesPerCubic = 6; 480 481 typedef GrTestBatch INHERITED; 482}; 483 484/** 485 * This GM directly exercises effects that draw Bezier quad curves in the GPU backend. 486 */ 487class BezierQuadEffects : public GM { 488public: 489 BezierQuadEffects() { 490 this->setBGColor(0xFFFFFFFF); 491 } 492 493protected: 494 SkString onShortName() override { 495 return SkString("bezier_quad_effects"); 496 } 497 498 SkISize onISize() override { 499 return SkISize::Make(800, 800); 500 } 501 502 503 void onDraw(SkCanvas* canvas) override { 504 GrRenderTarget* rt = canvas->internal_private_accessTopLayerRenderTarget(); 505 if (nullptr == rt) { 506 skiagm::GM::DrawGpuOnlyMessage(canvas); 507 return; 508 } 509 GrContext* context = rt->getContext(); 510 if (nullptr == context) { 511 return; 512 } 513 514 SkAutoTUnref<GrDrawContext> drawContext(context->drawContext(rt)); 515 if (!drawContext) { 516 return; 517 } 518 519 struct Vertex { 520 SkPoint fPosition; 521 float fUV[4]; // The last two values are ignored. The effect expects a vec4f. 522 }; 523 524 static const int kNumQuads = 5; 525 SkRandom rand; 526 527 int numCols = SkScalarCeilToInt(SkScalarSqrt(SkIntToScalar(kNumQuads*3))); 528 int numRows = SkScalarCeilToInt(SkIntToScalar(kNumQuads*3) / numCols); 529 SkScalar w = SkIntToScalar(rt->width()) / numCols; 530 SkScalar h = SkIntToScalar(rt->height()) / numRows; 531 int row = 0; 532 int col = 0; 533 static const GrColor color = 0xff000000; 534 535 for (int i = 0; i < kNumQuads; ++i) { 536 SkPoint baseControlPts[] = { 537 {rand.nextRangeF(0.f, w), rand.nextRangeF(0.f, h)}, 538 {rand.nextRangeF(0.f, w), rand.nextRangeF(0.f, h)}, 539 {rand.nextRangeF(0.f, w), rand.nextRangeF(0.f, h)} 540 }; 541 for(int edgeType = 0; edgeType < kGrProcessorEdgeTypeCnt; ++edgeType) { 542 SkAutoTUnref<GrGeometryProcessor> gp; 543 GrPrimitiveEdgeType et = (GrPrimitiveEdgeType)edgeType; 544 gp.reset(GrQuadEffect::Create(color, SkMatrix::I(), et, 545 *context->caps(), SkMatrix::I(), false)); 546 if (!gp) { 547 continue; 548 } 549 550 SkScalar x = SkScalarMul(col, w); 551 SkScalar y = SkScalarMul(row, h); 552 SkPoint controlPts[] = { 553 {x + baseControlPts[0].fX, y + baseControlPts[0].fY}, 554 {x + baseControlPts[1].fX, y + baseControlPts[1].fY}, 555 {x + baseControlPts[2].fX, y + baseControlPts[2].fY} 556 }; 557 SkPoint chopped[5]; 558 int cnt = SkChopQuadAtMaxCurvature(controlPts, chopped); 559 560 SkPaint ctrlPtPaint; 561 ctrlPtPaint.setColor(rand.nextU() | 0xFF000000); 562 for (int i = 0; i < 3; ++i) { 563 canvas->drawCircle(controlPts[i].fX, controlPts[i].fY, 6.f, ctrlPtPaint); 564 } 565 566 SkPaint polyPaint; 567 polyPaint.setColor(0xffA0A0A0); 568 polyPaint.setStrokeWidth(0); 569 polyPaint.setStyle(SkPaint::kStroke_Style); 570 canvas->drawPoints(SkCanvas::kPolygon_PointMode, 3, controlPts, polyPaint); 571 572 SkPaint choppedPtPaint; 573 choppedPtPaint.setColor(~ctrlPtPaint.getColor() | 0xFF000000); 574 575 for (int c = 0; c < cnt; ++c) { 576 SkPoint* pts = chopped + 2 * c; 577 578 for (int i = 0; i < 3; ++i) { 579 canvas->drawCircle(pts[i].fX, pts[i].fY, 3.f, choppedPtPaint); 580 } 581 582 SkRect bounds; 583 bounds.set(pts, 3); 584 585 SkPaint boundsPaint; 586 boundsPaint.setColor(0xff808080); 587 boundsPaint.setStrokeWidth(0); 588 boundsPaint.setStyle(SkPaint::kStroke_Style); 589 canvas->drawRect(bounds, boundsPaint); 590 591 GrPipelineBuilder pipelineBuilder; 592 pipelineBuilder.setXPFactory( 593 GrPorterDuffXPFactory::Create(SkXfermode::kSrc_Mode))->unref(); 594 pipelineBuilder.setRenderTarget(rt); 595 596 GrPathUtils::QuadUVMatrix DevToUV(pts); 597 598 BezierQuadTestBatch::Geometry geometry; 599 geometry.fColor = color; 600 geometry.fBounds = bounds; 601 602 SkAutoTUnref<GrDrawBatch> batch(BezierQuadTestBatch::Create(gp, geometry, 603 DevToUV)); 604 605 drawContext->drawContextPriv().testingOnly_drawBatch(pipelineBuilder, batch); 606 } 607 ++col; 608 if (numCols == col) { 609 col = 0; 610 ++row; 611 } 612 } 613 } 614 } 615 616private: 617 typedef GM INHERITED; 618}; 619 620DEF_GM(return new BezierCubicEffects;) 621DEF_GM(return new BezierConicEffects;) 622DEF_GM(return new BezierQuadEffects;) 623} 624 625#endif 626