OLD | NEW |
1 /* | 1 /* |
2 * Copyright 2013 Google Inc. | 2 * Copyright 2013 Google Inc. |
3 * | 3 * |
4 * Use of this source code is governed by a BSD-style license that can be | 4 * Use of this source code is governed by a BSD-style license that can be |
5 * found in the LICENSE file. | 5 * found in the LICENSE file. |
6 */ | 6 */ |
7 | 7 |
8 #include "GrOvalRenderer.h" | 8 #include "GrOvalRenderer.h" |
9 | 9 |
10 #include "GrBatchFlushState.h" | 10 #include "GrBatchFlushState.h" |
(...skipping 506 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
517 GR_DEFINE_GEOMETRY_PROCESSOR_TEST(DIEllipseGeometryProcessor); | 517 GR_DEFINE_GEOMETRY_PROCESSOR_TEST(DIEllipseGeometryProcessor); |
518 | 518 |
519 sk_sp<GrGeometryProcessor> DIEllipseGeometryProcessor::TestCreate(GrProcessorTes
tData* d) { | 519 sk_sp<GrGeometryProcessor> DIEllipseGeometryProcessor::TestCreate(GrProcessorTes
tData* d) { |
520 return sk_sp<GrGeometryProcessor>( | 520 return sk_sp<GrGeometryProcessor>( |
521 new DIEllipseGeometryProcessor(GrTest::TestMatrix(d->fRandom), | 521 new DIEllipseGeometryProcessor(GrTest::TestMatrix(d->fRandom), |
522 (DIEllipseStyle)(d->fRandom->nextRangeU(0
,2)))); | 522 (DIEllipseStyle)(d->fRandom->nextRangeU(0
,2)))); |
523 } | 523 } |
524 | 524 |
525 /////////////////////////////////////////////////////////////////////////////// | 525 /////////////////////////////////////////////////////////////////////////////// |
526 | 526 |
527 GrDrawBatch* GrOvalRenderer::CreateOvalBatch(GrColor color, | |
528 const SkMatrix& viewMatrix, | |
529 const SkRect& oval, | |
530 const SkStrokeRec& stroke, | |
531 GrShaderCaps* shaderCaps) { | |
532 // we can draw circles | |
533 if (SkScalarNearlyEqual(oval.width(), oval.height()) && circle_stays_circle(
viewMatrix)) { | |
534 return CreateCircleBatch(color, viewMatrix, oval, stroke); | |
535 } | |
536 | |
537 // if we have shader derivative support, render as device-independent | |
538 if (shaderCaps->shaderDerivativeSupport()) { | |
539 return CreateDIEllipseBatch(color, viewMatrix, oval, stroke); | |
540 } | |
541 | |
542 // otherwise axis-aligned ellipses only | |
543 if (viewMatrix.rectStaysRect()) { | |
544 return CreateEllipseBatch(color, viewMatrix, oval, stroke); | |
545 } | |
546 | |
547 return nullptr; | |
548 } | |
549 | |
550 /////////////////////////////////////////////////////////////////////////////// | |
551 | |
552 class CircleBatch : public GrVertexBatch { | 527 class CircleBatch : public GrVertexBatch { |
553 public: | 528 public: |
554 DEFINE_BATCH_CLASS_ID | 529 DEFINE_BATCH_CLASS_ID |
555 | 530 |
556 struct Geometry { | 531 CircleBatch(GrColor color, const SkMatrix& viewMatrix, const SkRect& circle, |
557 SkRect fDevBounds; | 532 const SkStrokeRec& stroke) |
558 SkScalar fInnerRadius; | 533 : INHERITED(ClassID()) |
559 SkScalar fOuterRadius; | 534 , fViewMatrixIfUsingLocalCoords(viewMatrix) { |
560 GrColor fColor; | 535 SkPoint center = SkPoint::Make(circle.centerX(), circle.centerY()); |
561 }; | 536 viewMatrix.mapPoints(¢er, 1); |
| 537 SkScalar radius = viewMatrix.mapRadius(SkScalarHalf(circle.width())); |
| 538 SkScalar strokeWidth = viewMatrix.mapRadius(stroke.getWidth()); |
562 | 539 |
563 CircleBatch(const Geometry& geometry, const SkMatrix& viewMatrix, bool strok
ed) | 540 SkStrokeRec::Style style = stroke.getStyle(); |
564 : INHERITED(ClassID()) | 541 bool isStrokeOnly = SkStrokeRec::kStroke_Style == style || |
565 , fStroked(stroked) | 542 SkStrokeRec::kHairline_Style == style; |
566 , fViewMatrixIfUsingLocalCoords(viewMatrix) { | 543 bool hasStroke = isStrokeOnly || SkStrokeRec::kStrokeAndFill_Style == st
yle; |
567 fGeoData.push_back(geometry); | 544 |
568 this->setBounds(geometry.fDevBounds); | 545 SkScalar innerRadius = 0.0f; |
| 546 SkScalar outerRadius = radius; |
| 547 SkScalar halfWidth = 0; |
| 548 if (hasStroke) { |
| 549 if (SkScalarNearlyZero(strokeWidth)) { |
| 550 halfWidth = SK_ScalarHalf; |
| 551 } else { |
| 552 halfWidth = SkScalarHalf(strokeWidth); |
| 553 } |
| 554 |
| 555 outerRadius += halfWidth; |
| 556 if (isStrokeOnly) { |
| 557 innerRadius = radius - halfWidth; |
| 558 } |
| 559 } |
| 560 |
| 561 // The radii are outset for two reasons. First, it allows the shader to
simply perform |
| 562 // simpler computation because the computed alpha is zero, rather than 5
0%, at the radius. |
| 563 // Second, the outer radius is used to compute the verts of the bounding
box that is |
| 564 // rendered and the outset ensures the box will cover all partially cove
red by the circle. |
| 565 outerRadius += SK_ScalarHalf; |
| 566 innerRadius -= SK_ScalarHalf; |
| 567 |
| 568 fGeoData.emplace_back(Geometry { |
| 569 color, |
| 570 innerRadius, |
| 571 outerRadius, |
| 572 SkRect::MakeLTRB(center.fX - outerRadius, center.fY - outerRadius, |
| 573 center.fX + outerRadius, center.fY + outerRadius) |
| 574 }); |
| 575 this->setBounds(fGeoData.back().fDevBounds); |
| 576 fStroked = isStrokeOnly && innerRadius > 0; |
569 } | 577 } |
| 578 |
570 const char* name() const override { return "CircleBatch"; } | 579 const char* name() const override { return "CircleBatch"; } |
571 | 580 |
572 SkString dumpInfo() const override { | 581 SkString dumpInfo() const override { |
573 SkString string; | 582 SkString string; |
574 for (int i = 0; i < fGeoData.count(); ++i) { | 583 for (int i = 0; i < fGeoData.count(); ++i) { |
575 string.appendf("Color: 0x%08x Rect [L: %.2f, T: %.2f, R: %.2f, B: %.
2f]," | 584 string.appendf("Color: 0x%08x Rect [L: %.2f, T: %.2f, R: %.2f, B: %.
2f]," |
576 "InnerRad: %.2f, OuterRad: %.2f\n", | 585 "InnerRad: %.2f, OuterRad: %.2f\n", |
577 fGeoData[i].fColor, | 586 fGeoData[i].fColor, |
578 fGeoData[i].fDevBounds.fLeft, fGeoData[i].fDevBounds.
fTop, | 587 fGeoData[i].fDevBounds.fLeft, fGeoData[i].fDevBounds.
fTop, |
579 fGeoData[i].fDevBounds.fRight, fGeoData[i].fDevBounds
.fBottom, | 588 fGeoData[i].fDevBounds.fRight, fGeoData[i].fDevBounds
.fBottom, |
(...skipping 93 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
673 | 682 |
674 if (!fViewMatrixIfUsingLocalCoords.cheapEqualTo(that->fViewMatrixIfUsing
LocalCoords)) { | 683 if (!fViewMatrixIfUsingLocalCoords.cheapEqualTo(that->fViewMatrixIfUsing
LocalCoords)) { |
675 return false; | 684 return false; |
676 } | 685 } |
677 | 686 |
678 fGeoData.push_back_n(that->fGeoData.count(), that->fGeoData.begin()); | 687 fGeoData.push_back_n(that->fGeoData.count(), that->fGeoData.begin()); |
679 this->joinBounds(that->bounds()); | 688 this->joinBounds(that->bounds()); |
680 return true; | 689 return true; |
681 } | 690 } |
682 | 691 |
| 692 struct Geometry { |
| 693 GrColor fColor; |
| 694 SkScalar fInnerRadius; |
| 695 SkScalar fOuterRadius; |
| 696 SkRect fDevBounds; |
| 697 }; |
| 698 |
683 bool fStroked; | 699 bool fStroked; |
684 SkMatrix fViewMatrixIfUsingLocalCoords; | 700 SkMatrix fViewMatrixIfUsingLocalCoords; |
685 SkSTArray<1, Geometry, true> fGeoData; | 701 SkSTArray<1, Geometry, true> fGeoData; |
686 | 702 |
687 typedef GrVertexBatch INHERITED; | 703 typedef GrVertexBatch INHERITED; |
688 }; | 704 }; |
689 | 705 |
690 static GrDrawBatch* create_circle_batch(GrColor color, | |
691 const SkMatrix& viewMatrix, | |
692 const SkRect& circle, | |
693 const SkStrokeRec& stroke) { | |
694 SkPoint center = SkPoint::Make(circle.centerX(), circle.centerY()); | |
695 viewMatrix.mapPoints(¢er, 1); | |
696 SkScalar radius = viewMatrix.mapRadius(SkScalarHalf(circle.width())); | |
697 SkScalar strokeWidth = viewMatrix.mapRadius(stroke.getWidth()); | |
698 | |
699 SkStrokeRec::Style style = stroke.getStyle(); | |
700 bool isStrokeOnly = SkStrokeRec::kStroke_Style == style || | |
701 SkStrokeRec::kHairline_Style == style; | |
702 bool hasStroke = isStrokeOnly || SkStrokeRec::kStrokeAndFill_Style == style; | |
703 | |
704 SkScalar innerRadius = 0.0f; | |
705 SkScalar outerRadius = radius; | |
706 SkScalar halfWidth = 0; | |
707 if (hasStroke) { | |
708 if (SkScalarNearlyZero(strokeWidth)) { | |
709 halfWidth = SK_ScalarHalf; | |
710 } else { | |
711 halfWidth = SkScalarHalf(strokeWidth); | |
712 } | |
713 | |
714 outerRadius += halfWidth; | |
715 if (isStrokeOnly) { | |
716 innerRadius = radius - halfWidth; | |
717 } | |
718 } | |
719 | |
720 // The radii are outset for two reasons. First, it allows the shader to simp
ly perform simpler | |
721 // computation because the computed alpha is zero, rather than 50%, at the r
adius. | |
722 // Second, the outer radius is used to compute the verts of the bounding box
that is rendered | |
723 // and the outset ensures the box will cover all partially covered by the ci
rcle. | |
724 outerRadius += SK_ScalarHalf; | |
725 innerRadius -= SK_ScalarHalf; | |
726 | |
727 CircleBatch::Geometry geometry; | |
728 geometry.fColor = color; | |
729 geometry.fInnerRadius = innerRadius; | |
730 geometry.fOuterRadius = outerRadius; | |
731 geometry.fDevBounds = SkRect::MakeLTRB(center.fX - outerRadius, center.fY -
outerRadius, | |
732 center.fX + outerRadius, center.fY +
outerRadius); | |
733 | |
734 return new CircleBatch(geometry, viewMatrix, isStrokeOnly && innerRadius > 0
); | |
735 } | |
736 | |
737 GrDrawBatch* GrOvalRenderer::CreateCircleBatch(GrColor color, | |
738 const SkMatrix& viewMatrix, | |
739 const SkRect& circle, | |
740 const SkStrokeRec& stroke) { | |
741 return create_circle_batch(color, viewMatrix, circle, stroke); | |
742 } | |
743 | |
744 /////////////////////////////////////////////////////////////////////////////// | 706 /////////////////////////////////////////////////////////////////////////////// |
745 | 707 |
746 class EllipseBatch : public GrVertexBatch { | 708 class EllipseBatch : public GrVertexBatch { |
747 public: | 709 public: |
748 DEFINE_BATCH_CLASS_ID | 710 DEFINE_BATCH_CLASS_ID |
| 711 static GrDrawBatch* Create(GrColor color, const SkMatrix& viewMatrix, const
SkRect& ellipse, |
| 712 const SkStrokeRec& stroke) { |
| 713 SkASSERT(viewMatrix.rectStaysRect()); |
749 | 714 |
750 struct Geometry { | 715 // do any matrix crunching before we reset the draw state for device coo
rds |
751 SkRect fDevBounds; | 716 SkPoint center = SkPoint::Make(ellipse.centerX(), ellipse.centerY()); |
752 SkScalar fXRadius; | 717 viewMatrix.mapPoints(¢er, 1); |
753 SkScalar fYRadius; | 718 SkScalar ellipseXRadius = SkScalarHalf(ellipse.width()); |
754 SkScalar fInnerXRadius; | 719 SkScalar ellipseYRadius = SkScalarHalf(ellipse.height()); |
755 SkScalar fInnerYRadius; | 720 SkScalar xRadius = SkScalarAbs(viewMatrix[SkMatrix::kMScaleX]*ellipseXRa
dius + |
756 GrColor fColor; | 721 viewMatrix[SkMatrix::kMSkewY]*ellipseYRad
ius); |
757 }; | 722 SkScalar yRadius = SkScalarAbs(viewMatrix[SkMatrix::kMSkewX]*ellipseXRad
ius + |
| 723 viewMatrix[SkMatrix::kMScaleY]*ellipseYRa
dius); |
758 | 724 |
759 EllipseBatch(const Geometry& geometry, const SkMatrix& viewMatrix, bool stro
ked) | 725 // do (potentially) anisotropic mapping of stroke |
760 : INHERITED(ClassID()) | 726 SkVector scaledStroke; |
761 , fStroked(stroked) | 727 SkScalar strokeWidth = stroke.getWidth(); |
762 , fViewMatrixIfUsingLocalCoords(viewMatrix) { | 728 scaledStroke.fX = SkScalarAbs(strokeWidth*(viewMatrix[SkMatrix::kMScaleX
] + |
763 fGeoData.push_back(geometry); | 729 viewMatrix[SkMatrix::kMSkewY]
)); |
764 this->setBounds(geometry.fDevBounds); | 730 scaledStroke.fY = SkScalarAbs(strokeWidth*(viewMatrix[SkMatrix::kMSkewX]
+ |
| 731 viewMatrix[SkMatrix::kMScaleY
])); |
| 732 |
| 733 SkStrokeRec::Style style = stroke.getStyle(); |
| 734 bool isStrokeOnly = SkStrokeRec::kStroke_Style == style || |
| 735 SkStrokeRec::kHairline_Style == style; |
| 736 bool hasStroke = isStrokeOnly || SkStrokeRec::kStrokeAndFill_Style == st
yle; |
| 737 |
| 738 SkScalar innerXRadius = 0; |
| 739 SkScalar innerYRadius = 0; |
| 740 if (hasStroke) { |
| 741 if (SkScalarNearlyZero(scaledStroke.length())) { |
| 742 scaledStroke.set(SK_ScalarHalf, SK_ScalarHalf); |
| 743 } else { |
| 744 scaledStroke.scale(SK_ScalarHalf); |
| 745 } |
| 746 |
| 747 // we only handle thick strokes for near-circular ellipses |
| 748 if (scaledStroke.length() > SK_ScalarHalf && |
| 749 (SK_ScalarHalf*xRadius > yRadius || SK_ScalarHalf*yRadius > xRad
ius)) { |
| 750 return nullptr; |
| 751 } |
| 752 |
| 753 // we don't handle it if curvature of the stroke is less than curvat
ure of the ellipse |
| 754 if (scaledStroke.fX*(yRadius*yRadius) < (scaledStroke.fY*scaledStrok
e.fY)*xRadius || |
| 755 scaledStroke.fY*(xRadius*xRadius) < (scaledStroke.fX*scaledStrok
e.fX)*yRadius) { |
| 756 return nullptr; |
| 757 } |
| 758 |
| 759 // this is legit only if scale & translation (which should be the ca
se at the moment) |
| 760 if (isStrokeOnly) { |
| 761 innerXRadius = xRadius - scaledStroke.fX; |
| 762 innerYRadius = yRadius - scaledStroke.fY; |
| 763 } |
| 764 |
| 765 xRadius += scaledStroke.fX; |
| 766 yRadius += scaledStroke.fY; |
| 767 } |
| 768 |
| 769 EllipseBatch* batch = new EllipseBatch(); |
| 770 batch->fGeoData.emplace_back(Geometry { |
| 771 color, |
| 772 xRadius, |
| 773 yRadius, |
| 774 innerXRadius, |
| 775 innerYRadius, |
| 776 SkRect::MakeLTRB(center.fX - xRadius, center.fY - yRadius, |
| 777 center.fX + xRadius, center.fY + yRadius) |
| 778 }); |
| 779 |
| 780 // Outset bounds to include half-pixel width antialiasing. |
| 781 batch->fGeoData[0].fDevBounds.outset(SK_ScalarHalf, SK_ScalarHalf); |
| 782 |
| 783 batch->fStroked = isStrokeOnly && innerXRadius > 0 && innerYRadius > 0; |
| 784 batch->fViewMatrixIfUsingLocalCoords = viewMatrix; |
| 785 batch->setBounds(batch->fGeoData.back().fDevBounds); |
| 786 return batch; |
765 } | 787 } |
766 | 788 |
767 const char* name() const override { return "EllipseBatch"; } | 789 const char* name() const override { return "EllipseBatch"; } |
768 | 790 |
769 void computePipelineOptimizations(GrInitInvariantOutput* color, | 791 void computePipelineOptimizations(GrInitInvariantOutput* color, |
770 GrInitInvariantOutput* coverage, | 792 GrInitInvariantOutput* coverage, |
771 GrBatchToXPOverrides* overrides) const ove
rride { | 793 GrBatchToXPOverrides* overrides) const ove
rride { |
772 // When this is called on a batch, there is only one geometry bundle | 794 // When this is called on a batch, there is only one geometry bundle |
773 color->setKnownFourComponents(fGeoData[0].fColor); | 795 color->setKnownFourComponents(fGeoData[0].fColor); |
774 coverage->setUnknownSingleComponent(); | 796 coverage->setUnknownSingleComponent(); |
775 } | 797 } |
776 | 798 |
777 private: | 799 private: |
| 800 EllipseBatch() : INHERITED(ClassID()) {} |
| 801 |
778 void initBatchTracker(const GrXPOverridesForBatch& overrides) override { | 802 void initBatchTracker(const GrXPOverridesForBatch& overrides) override { |
779 // Handle any overrides that affect our GP. | 803 // Handle any overrides that affect our GP. |
780 if (!overrides.readsCoverage()) { | 804 if (!overrides.readsCoverage()) { |
781 fGeoData[0].fColor = GrColor_ILLEGAL; | 805 fGeoData[0].fColor = GrColor_ILLEGAL; |
782 } | 806 } |
783 if (!overrides.readsLocalCoords()) { | 807 if (!overrides.readsLocalCoords()) { |
784 fViewMatrixIfUsingLocalCoords.reset(); | 808 fViewMatrixIfUsingLocalCoords.reset(); |
785 } | 809 } |
786 } | 810 } |
787 | 811 |
(...skipping 79 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
867 | 891 |
868 if (!fViewMatrixIfUsingLocalCoords.cheapEqualTo(that->fViewMatrixIfUsing
LocalCoords)) { | 892 if (!fViewMatrixIfUsingLocalCoords.cheapEqualTo(that->fViewMatrixIfUsing
LocalCoords)) { |
869 return false; | 893 return false; |
870 } | 894 } |
871 | 895 |
872 fGeoData.push_back_n(that->fGeoData.count(), that->fGeoData.begin()); | 896 fGeoData.push_back_n(that->fGeoData.count(), that->fGeoData.begin()); |
873 this->joinBounds(that->bounds()); | 897 this->joinBounds(that->bounds()); |
874 return true; | 898 return true; |
875 } | 899 } |
876 | 900 |
| 901 struct Geometry { |
| 902 GrColor fColor; |
| 903 SkScalar fXRadius; |
| 904 SkScalar fYRadius; |
| 905 SkScalar fInnerXRadius; |
| 906 SkScalar fInnerYRadius; |
| 907 SkRect fDevBounds; |
| 908 }; |
877 | 909 |
878 bool fStroked; | 910 bool fStroked; |
879 SkMatrix fViewMatrixIfUsingLocalCoords; | 911 SkMatrix fViewMatrixIfUsingLocalCoords; |
880 SkSTArray<1, Geometry, true> fGeoData; | 912 SkSTArray<1, Geometry, true> fGeoData; |
881 | 913 |
882 typedef GrVertexBatch INHERITED; | 914 typedef GrVertexBatch INHERITED; |
883 }; | 915 }; |
884 | 916 |
885 static GrDrawBatch* create_ellipse_batch(GrColor color, | |
886 const SkMatrix& viewMatrix, | |
887 const SkRect& ellipse, | |
888 const SkStrokeRec& stroke) { | |
889 SkASSERT(viewMatrix.rectStaysRect()); | |
890 | |
891 // do any matrix crunching before we reset the draw state for device coords | |
892 SkPoint center = SkPoint::Make(ellipse.centerX(), ellipse.centerY()); | |
893 viewMatrix.mapPoints(¢er, 1); | |
894 SkScalar ellipseXRadius = SkScalarHalf(ellipse.width()); | |
895 SkScalar ellipseYRadius = SkScalarHalf(ellipse.height()); | |
896 SkScalar xRadius = SkScalarAbs(viewMatrix[SkMatrix::kMScaleX]*ellipseXRadius
+ | |
897 viewMatrix[SkMatrix::kMSkewY]*ellipseYRadius)
; | |
898 SkScalar yRadius = SkScalarAbs(viewMatrix[SkMatrix::kMSkewX]*ellipseXRadius
+ | |
899 viewMatrix[SkMatrix::kMScaleY]*ellipseYRadius
); | |
900 | |
901 // do (potentially) anisotropic mapping of stroke | |
902 SkVector scaledStroke; | |
903 SkScalar strokeWidth = stroke.getWidth(); | |
904 scaledStroke.fX = SkScalarAbs(strokeWidth*(viewMatrix[SkMatrix::kMScaleX] + | |
905 viewMatrix[SkMatrix::kMSkewY])); | |
906 scaledStroke.fY = SkScalarAbs(strokeWidth*(viewMatrix[SkMatrix::kMSkewX] + | |
907 viewMatrix[SkMatrix::kMScaleY])); | |
908 | |
909 SkStrokeRec::Style style = stroke.getStyle(); | |
910 bool isStrokeOnly = SkStrokeRec::kStroke_Style == style || | |
911 SkStrokeRec::kHairline_Style == style; | |
912 bool hasStroke = isStrokeOnly || SkStrokeRec::kStrokeAndFill_Style == style; | |
913 | |
914 SkScalar innerXRadius = 0; | |
915 SkScalar innerYRadius = 0; | |
916 if (hasStroke) { | |
917 if (SkScalarNearlyZero(scaledStroke.length())) { | |
918 scaledStroke.set(SK_ScalarHalf, SK_ScalarHalf); | |
919 } else { | |
920 scaledStroke.scale(SK_ScalarHalf); | |
921 } | |
922 | |
923 // we only handle thick strokes for near-circular ellipses | |
924 if (scaledStroke.length() > SK_ScalarHalf && | |
925 (SK_ScalarHalf*xRadius > yRadius || SK_ScalarHalf*yRadius > xRadius)
) { | |
926 return nullptr; | |
927 } | |
928 | |
929 // we don't handle it if curvature of the stroke is less than curvature
of the ellipse | |
930 if (scaledStroke.fX*(yRadius*yRadius) < (scaledStroke.fY*scaledStroke.fY
)*xRadius || | |
931 scaledStroke.fY*(xRadius*xRadius) < (scaledStroke.fX*scaledStroke.fX
)*yRadius) { | |
932 return nullptr; | |
933 } | |
934 | |
935 // this is legit only if scale & translation (which should be the case a
t the moment) | |
936 if (isStrokeOnly) { | |
937 innerXRadius = xRadius - scaledStroke.fX; | |
938 innerYRadius = yRadius - scaledStroke.fY; | |
939 } | |
940 | |
941 xRadius += scaledStroke.fX; | |
942 yRadius += scaledStroke.fY; | |
943 } | |
944 | |
945 EllipseBatch::Geometry geometry; | |
946 geometry.fColor = color; | |
947 geometry.fXRadius = xRadius; | |
948 geometry.fYRadius = yRadius; | |
949 geometry.fInnerXRadius = innerXRadius; | |
950 geometry.fInnerYRadius = innerYRadius; | |
951 geometry.fDevBounds = SkRect::MakeLTRB(center.fX - xRadius, center.fY - yRad
ius, | |
952 center.fX + xRadius, center.fY + yRad
ius); | |
953 | |
954 // outset bounds to include half-pixel width antialiasing. | |
955 geometry.fDevBounds.outset(SK_ScalarHalf, SK_ScalarHalf); | |
956 | |
957 return new EllipseBatch(geometry, viewMatrix, | |
958 isStrokeOnly && innerXRadius > 0 && innerYRadius > 0
); | |
959 } | |
960 | |
961 GrDrawBatch* GrOvalRenderer::CreateEllipseBatch(GrColor color, | |
962 const SkMatrix& viewMatrix, | |
963 const SkRect& ellipse, | |
964 const SkStrokeRec& stroke) { | |
965 return create_ellipse_batch(color, viewMatrix, ellipse, stroke); | |
966 } | |
967 | |
968 ////////////////////////////////////////////////////////////////////////////////
///////////////// | 917 ////////////////////////////////////////////////////////////////////////////////
///////////////// |
969 | 918 |
970 class DIEllipseBatch : public GrVertexBatch { | 919 class DIEllipseBatch : public GrVertexBatch { |
971 public: | 920 public: |
972 DEFINE_BATCH_CLASS_ID | 921 DEFINE_BATCH_CLASS_ID |
973 | 922 |
974 struct Geometry { | 923 static GrDrawBatch* Create(GrColor color, |
975 SkMatrix fViewMatrix; | 924 const SkMatrix& viewMatrix, |
976 SkRect fBounds; | 925 const SkRect& ellipse, |
977 SkScalar fXRadius; | 926 const SkStrokeRec& stroke) { |
978 SkScalar fYRadius; | 927 SkPoint center = SkPoint::Make(ellipse.centerX(), ellipse.centerY()); |
979 SkScalar fInnerXRadius; | 928 SkScalar xRadius = SkScalarHalf(ellipse.width()); |
980 SkScalar fInnerYRadius; | 929 SkScalar yRadius = SkScalarHalf(ellipse.height()); |
981 SkScalar fGeoDx; | |
982 SkScalar fGeoDy; | |
983 GrColor fColor; | |
984 DIEllipseStyle fStyle; | |
985 }; | |
986 | 930 |
987 static GrDrawBatch* Create(const Geometry& geometry, const SkRect& bounds) { | 931 SkStrokeRec::Style style = stroke.getStyle(); |
988 return new DIEllipseBatch(geometry, bounds); | 932 DIEllipseStyle dieStyle = (SkStrokeRec::kStroke_Style == style) ? |
| 933 DIEllipseStyle::kStroke : |
| 934 (SkStrokeRec::kHairline_Style == style) ? |
| 935 DIEllipseStyle::kHairline : DIEllipseStyle::kF
ill; |
| 936 |
| 937 SkScalar innerXRadius = 0; |
| 938 SkScalar innerYRadius = 0; |
| 939 if (SkStrokeRec::kFill_Style != style && SkStrokeRec::kHairline_Style !=
style) { |
| 940 SkScalar strokeWidth = stroke.getWidth(); |
| 941 |
| 942 if (SkScalarNearlyZero(strokeWidth)) { |
| 943 strokeWidth = SK_ScalarHalf; |
| 944 } else { |
| 945 strokeWidth *= SK_ScalarHalf; |
| 946 } |
| 947 |
| 948 // we only handle thick strokes for near-circular ellipses |
| 949 if (strokeWidth > SK_ScalarHalf && |
| 950 (SK_ScalarHalf*xRadius > yRadius || SK_ScalarHalf*yRadius > xRad
ius)) { |
| 951 return nullptr; |
| 952 } |
| 953 |
| 954 // we don't handle it if curvature of the stroke is less than curvat
ure of the ellipse |
| 955 if (strokeWidth*(yRadius*yRadius) < (strokeWidth*strokeWidth)*xRadiu
s || |
| 956 strokeWidth*(xRadius*xRadius) < (strokeWidth*strokeWidth)*yRadiu
s) { |
| 957 return nullptr; |
| 958 } |
| 959 |
| 960 // set inner radius (if needed) |
| 961 if (SkStrokeRec::kStroke_Style == style) { |
| 962 innerXRadius = xRadius - strokeWidth; |
| 963 innerYRadius = yRadius - strokeWidth; |
| 964 } |
| 965 |
| 966 xRadius += strokeWidth; |
| 967 yRadius += strokeWidth; |
| 968 } |
| 969 if (DIEllipseStyle::kStroke == dieStyle) { |
| 970 dieStyle = (innerXRadius > 0 && innerYRadius > 0) ? DIEllipseStyle :
:kStroke : |
| 971 DIEllipseStyle ::kFill; |
| 972 } |
| 973 |
| 974 // This expands the outer rect so that after CTM we end up with a half-p
ixel border |
| 975 SkScalar a = viewMatrix[SkMatrix::kMScaleX]; |
| 976 SkScalar b = viewMatrix[SkMatrix::kMSkewX]; |
| 977 SkScalar c = viewMatrix[SkMatrix::kMSkewY]; |
| 978 SkScalar d = viewMatrix[SkMatrix::kMScaleY]; |
| 979 SkScalar geoDx = SK_ScalarHalf / SkScalarSqrt(a*a + c*c); |
| 980 SkScalar geoDy = SK_ScalarHalf / SkScalarSqrt(b*b + d*d); |
| 981 |
| 982 DIEllipseBatch* batch = new DIEllipseBatch(); |
| 983 batch->fGeoData.emplace_back(Geometry { |
| 984 viewMatrix, |
| 985 color, |
| 986 xRadius, |
| 987 yRadius, |
| 988 innerXRadius, |
| 989 innerYRadius, |
| 990 geoDx, |
| 991 geoDy, |
| 992 dieStyle, |
| 993 SkRect::MakeLTRB(center.fX - xRadius - geoDx, center.fY - yRadius -
geoDy, |
| 994 center.fX + xRadius + geoDx, center.fY + yRadius +
geoDy) |
| 995 }); |
| 996 SkRect devBounds = batch->fGeoData.back().fBounds; |
| 997 viewMatrix.mapRect(&devBounds); |
| 998 batch->setBounds(devBounds); |
| 999 return batch; |
989 } | 1000 } |
990 | 1001 |
991 const char* name() const override { return "DIEllipseBatch"; } | 1002 const char* name() const override { return "DIEllipseBatch"; } |
992 | 1003 |
993 void computePipelineOptimizations(GrInitInvariantOutput* color, | 1004 void computePipelineOptimizations(GrInitInvariantOutput* color, |
994 GrInitInvariantOutput* coverage, | 1005 GrInitInvariantOutput* coverage, |
995 GrBatchToXPOverrides* overrides) const ove
rride { | 1006 GrBatchToXPOverrides* overrides) const ove
rride { |
996 // When this is called on a batch, there is only one geometry bundle | 1007 // When this is called on a batch, there is only one geometry bundle |
997 color->setKnownFourComponents(fGeoData[0].fColor); | 1008 color->setKnownFourComponents(fGeoData[0].fColor); |
998 coverage->setUnknownSingleComponent(); | 1009 coverage->setUnknownSingleComponent(); |
999 } | 1010 } |
1000 | 1011 |
1001 private: | 1012 private: |
1002 | 1013 |
| 1014 DIEllipseBatch() : INHERITED(ClassID()) {} |
| 1015 |
1003 void initBatchTracker(const GrXPOverridesForBatch& overrides) override { | 1016 void initBatchTracker(const GrXPOverridesForBatch& overrides) override { |
1004 // Handle any overrides that affect our GP. | 1017 // Handle any overrides that affect our GP. |
1005 overrides.getOverrideColorIfSet(&fGeoData[0].fColor); | 1018 overrides.getOverrideColorIfSet(&fGeoData[0].fColor); |
1006 fUsesLocalCoords = overrides.readsLocalCoords(); | 1019 fUsesLocalCoords = overrides.readsLocalCoords(); |
1007 } | 1020 } |
1008 | 1021 |
1009 void onPrepareDraws(Target* target) const override { | 1022 void onPrepareDraws(Target* target) const override { |
1010 // Setup geometry processor | 1023 // Setup geometry processor |
1011 SkAutoTUnref<GrGeometryProcessor> gp(new DIEllipseGeometryProcessor(this
->viewMatrix(), | 1024 SkAutoTUnref<GrGeometryProcessor> gp(new DIEllipseGeometryProcessor(this
->viewMatrix(), |
1012 this
->style())); | 1025 this
->style())); |
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1055 verts[3].fPos = SkPoint::Make(bounds.fRight, bounds.fTop); | 1068 verts[3].fPos = SkPoint::Make(bounds.fRight, bounds.fTop); |
1056 verts[3].fColor = color; | 1069 verts[3].fColor = color; |
1057 verts[3].fOuterOffset = SkPoint::Make(1.0f + offsetDx, -1.0f - offse
tDy); | 1070 verts[3].fOuterOffset = SkPoint::Make(1.0f + offsetDx, -1.0f - offse
tDy); |
1058 verts[3].fInnerOffset = SkPoint::Make(innerRatioX + offsetDx, -inner
RatioY - offsetDy); | 1071 verts[3].fInnerOffset = SkPoint::Make(innerRatioX + offsetDx, -inner
RatioY - offsetDy); |
1059 | 1072 |
1060 verts += kVerticesPerQuad; | 1073 verts += kVerticesPerQuad; |
1061 } | 1074 } |
1062 helper.recordDraw(target, gp); | 1075 helper.recordDraw(target, gp); |
1063 } | 1076 } |
1064 | 1077 |
1065 DIEllipseBatch(const Geometry& geometry, const SkRect& bounds) : INHERITED(C
lassID()) { | |
1066 fGeoData.push_back(geometry); | |
1067 | |
1068 this->setBounds(bounds); | |
1069 } | |
1070 | |
1071 bool onCombineIfPossible(GrBatch* t, const GrCaps& caps) override { | 1078 bool onCombineIfPossible(GrBatch* t, const GrCaps& caps) override { |
1072 DIEllipseBatch* that = t->cast<DIEllipseBatch>(); | 1079 DIEllipseBatch* that = t->cast<DIEllipseBatch>(); |
1073 if (!GrPipeline::CanCombine(*this->pipeline(), this->bounds(), *that->pi
peline(), | 1080 if (!GrPipeline::CanCombine(*this->pipeline(), this->bounds(), *that->pi
peline(), |
1074 that->bounds(), caps)) { | 1081 that->bounds(), caps)) { |
1075 return false; | 1082 return false; |
1076 } | 1083 } |
1077 | 1084 |
1078 if (this->style() != that->style()) { | 1085 if (this->style() != that->style()) { |
1079 return false; | 1086 return false; |
1080 } | 1087 } |
1081 | 1088 |
1082 // TODO rewrite to allow positioning on CPU | 1089 // TODO rewrite to allow positioning on CPU |
1083 if (!this->viewMatrix().cheapEqualTo(that->viewMatrix())) { | 1090 if (!this->viewMatrix().cheapEqualTo(that->viewMatrix())) { |
1084 return false; | 1091 return false; |
1085 } | 1092 } |
1086 | 1093 |
1087 fGeoData.push_back_n(that->fGeoData.count(), that->fGeoData.begin()); | 1094 fGeoData.push_back_n(that->fGeoData.count(), that->fGeoData.begin()); |
1088 this->joinBounds(that->bounds()); | 1095 this->joinBounds(that->bounds()); |
1089 return true; | 1096 return true; |
1090 } | 1097 } |
1091 | 1098 |
1092 const SkMatrix& viewMatrix() const { return fGeoData[0].fViewMatrix; } | 1099 const SkMatrix& viewMatrix() const { return fGeoData[0].fViewMatrix; } |
1093 DIEllipseStyle style() const { return fGeoData[0].fStyle; } | 1100 DIEllipseStyle style() const { return fGeoData[0].fStyle; } |
1094 | 1101 |
| 1102 struct Geometry { |
| 1103 SkMatrix fViewMatrix; |
| 1104 GrColor fColor; |
| 1105 SkScalar fXRadius; |
| 1106 SkScalar fYRadius; |
| 1107 SkScalar fInnerXRadius; |
| 1108 SkScalar fInnerYRadius; |
| 1109 SkScalar fGeoDx; |
| 1110 SkScalar fGeoDy; |
| 1111 DIEllipseStyle fStyle; |
| 1112 SkRect fBounds; |
| 1113 }; |
| 1114 |
1095 bool fUsesLocalCoords; | 1115 bool fUsesLocalCoords; |
1096 SkSTArray<1, Geometry, true> fGeoData; | 1116 SkSTArray<1, Geometry, true> fGeoData; |
1097 | 1117 |
1098 typedef GrVertexBatch INHERITED; | 1118 typedef GrVertexBatch INHERITED; |
1099 }; | 1119 }; |
1100 | 1120 |
1101 static GrDrawBatch* create_diellipse_batch(GrColor color, | |
1102 const SkMatrix& viewMatrix, | |
1103 const SkRect& ellipse, | |
1104 const SkStrokeRec& stroke) { | |
1105 SkPoint center = SkPoint::Make(ellipse.centerX(), ellipse.centerY()); | |
1106 SkScalar xRadius = SkScalarHalf(ellipse.width()); | |
1107 SkScalar yRadius = SkScalarHalf(ellipse.height()); | |
1108 | |
1109 SkStrokeRec::Style style = stroke.getStyle(); | |
1110 DIEllipseStyle dieStyle = (SkStrokeRec::kStroke_Style == style) ? | |
1111 DIEllipseStyle::kStroke : | |
1112 (SkStrokeRec::kHairline_Style == style) ? | |
1113 DIEllipseStyle::kHairline : DIEllipseSty
le::kFill; | |
1114 | |
1115 SkScalar innerXRadius = 0; | |
1116 SkScalar innerYRadius = 0; | |
1117 if (SkStrokeRec::kFill_Style != style && SkStrokeRec::kHairline_Style != sty
le) { | |
1118 SkScalar strokeWidth = stroke.getWidth(); | |
1119 | |
1120 if (SkScalarNearlyZero(strokeWidth)) { | |
1121 strokeWidth = SK_ScalarHalf; | |
1122 } else { | |
1123 strokeWidth *= SK_ScalarHalf; | |
1124 } | |
1125 | |
1126 // we only handle thick strokes for near-circular ellipses | |
1127 if (strokeWidth > SK_ScalarHalf && | |
1128 (SK_ScalarHalf*xRadius > yRadius || SK_ScalarHalf*yRadius > xRadius)
) { | |
1129 return nullptr; | |
1130 } | |
1131 | |
1132 // we don't handle it if curvature of the stroke is less than curvature
of the ellipse | |
1133 if (strokeWidth*(yRadius*yRadius) < (strokeWidth*strokeWidth)*xRadius || | |
1134 strokeWidth*(xRadius*xRadius) < (strokeWidth*strokeWidth)*yRadius) { | |
1135 return nullptr; | |
1136 } | |
1137 | |
1138 // set inner radius (if needed) | |
1139 if (SkStrokeRec::kStroke_Style == style) { | |
1140 innerXRadius = xRadius - strokeWidth; | |
1141 innerYRadius = yRadius - strokeWidth; | |
1142 } | |
1143 | |
1144 xRadius += strokeWidth; | |
1145 yRadius += strokeWidth; | |
1146 } | |
1147 if (DIEllipseStyle::kStroke == dieStyle) { | |
1148 dieStyle = (innerXRadius > 0 && innerYRadius > 0) ? DIEllipseStyle ::kSt
roke : | |
1149 DIEllipseStyle ::kFi
ll; | |
1150 } | |
1151 | |
1152 // This expands the outer rect so that after CTM we end up with a half-pixel
border | |
1153 SkScalar a = viewMatrix[SkMatrix::kMScaleX]; | |
1154 SkScalar b = viewMatrix[SkMatrix::kMSkewX]; | |
1155 SkScalar c = viewMatrix[SkMatrix::kMSkewY]; | |
1156 SkScalar d = viewMatrix[SkMatrix::kMScaleY]; | |
1157 SkScalar geoDx = SK_ScalarHalf / SkScalarSqrt(a*a + c*c); | |
1158 SkScalar geoDy = SK_ScalarHalf / SkScalarSqrt(b*b + d*d); | |
1159 | |
1160 DIEllipseBatch::Geometry geometry; | |
1161 geometry.fViewMatrix = viewMatrix; | |
1162 geometry.fColor = color; | |
1163 geometry.fXRadius = xRadius; | |
1164 geometry.fYRadius = yRadius; | |
1165 geometry.fInnerXRadius = innerXRadius; | |
1166 geometry.fInnerYRadius = innerYRadius; | |
1167 geometry.fGeoDx = geoDx; | |
1168 geometry.fGeoDy = geoDy; | |
1169 geometry.fStyle = dieStyle; | |
1170 geometry.fBounds = SkRect::MakeLTRB(center.fX - xRadius - geoDx, center.fY -
yRadius - geoDy, | |
1171 center.fX + xRadius + geoDx, center.fY +
yRadius + geoDy); | |
1172 | |
1173 SkRect devBounds = geometry.fBounds; | |
1174 viewMatrix.mapRect(&devBounds); | |
1175 return DIEllipseBatch::Create(geometry, devBounds); | |
1176 } | |
1177 | |
1178 GrDrawBatch* GrOvalRenderer::CreateDIEllipseBatch(GrColor color, | |
1179 const SkMatrix& viewMatrix, | |
1180 const SkRect& ellipse, | |
1181 const SkStrokeRec& stroke) { | |
1182 return create_diellipse_batch(color, viewMatrix, ellipse, stroke); | |
1183 } | |
1184 | |
1185 /////////////////////////////////////////////////////////////////////////////// | 1121 /////////////////////////////////////////////////////////////////////////////// |
1186 | 1122 |
1187 static const uint16_t gRRectIndices[] = { | 1123 static const uint16_t gRRectIndices[] = { |
1188 // corners | 1124 // corners |
1189 0, 1, 5, 0, 5, 4, | 1125 0, 1, 5, 0, 5, 4, |
1190 2, 3, 7, 2, 7, 6, | 1126 2, 3, 7, 2, 7, 6, |
1191 8, 9, 13, 8, 13, 12, | 1127 8, 9, 13, 8, 13, 12, |
1192 10, 11, 15, 10, 15, 14, | 1128 10, 11, 15, 10, 15, 14, |
1193 | 1129 |
1194 // edges | 1130 // edges |
(...skipping 29 matching lines...) Expand all Loading... |
1224 | 1160 |
1225 } | 1161 } |
1226 } | 1162 } |
1227 | 1163 |
1228 ////////////////////////////////////////////////////////////////////////////////
/////////////////// | 1164 ////////////////////////////////////////////////////////////////////////////////
/////////////////// |
1229 | 1165 |
1230 class RRectCircleRendererBatch : public GrVertexBatch { | 1166 class RRectCircleRendererBatch : public GrVertexBatch { |
1231 public: | 1167 public: |
1232 DEFINE_BATCH_CLASS_ID | 1168 DEFINE_BATCH_CLASS_ID |
1233 | 1169 |
1234 struct Geometry { | 1170 // A devStrokeWidth <= 0 indicates a fill only. If devStrokeWidth > 0 then s
trokeOnly indicates |
1235 SkRect fDevBounds; | 1171 // whether the rrect is only stroked or stroked and filled. |
1236 SkScalar fInnerRadius; | 1172 RRectCircleRendererBatch(GrColor color, const SkMatrix& viewMatrix, const Sk
Rect& devRect, |
1237 SkScalar fOuterRadius; | 1173 float devRadius, float devStrokeWidth, bool strokeO
nly) |
1238 GrColor fColor; | 1174 : INHERITED(ClassID()) |
1239 }; | 1175 , fViewMatrixIfUsingLocalCoords(viewMatrix) { |
| 1176 SkRect bounds = devRect; |
| 1177 SkASSERT(!(devStrokeWidth <= 0 && strokeOnly)); |
| 1178 SkScalar innerRadius = 0.0f; |
| 1179 SkScalar outerRadius = devRadius; |
| 1180 SkScalar halfWidth = 0; |
| 1181 fStroked = false; |
| 1182 if (devStrokeWidth > 0) { |
| 1183 if (SkScalarNearlyZero(devStrokeWidth)) { |
| 1184 halfWidth = SK_ScalarHalf; |
| 1185 } else { |
| 1186 halfWidth = SkScalarHalf(devStrokeWidth); |
| 1187 } |
1240 | 1188 |
1241 RRectCircleRendererBatch(const Geometry& geometry, const SkMatrix& viewMatri
x, bool stroked) | 1189 if (strokeOnly) { |
1242 : INHERITED(ClassID()) | 1190 innerRadius = devRadius - halfWidth; |
1243 , fStroked(stroked) | 1191 fStroked = innerRadius >= 0; |
1244 , fViewMatrixIfUsingLocalCoords(viewMatrix) { | 1192 } |
1245 fGeoData.push_back(geometry); | 1193 outerRadius += halfWidth; |
| 1194 bounds.outset(halfWidth, halfWidth); |
| 1195 } |
1246 | 1196 |
1247 this->setBounds(geometry.fDevBounds); | 1197 // The radii are outset for two reasons. First, it allows the shader to
simply perform |
| 1198 // simpler computation because the computed alpha is zero, rather than 5
0%, at the radius. |
| 1199 // Second, the outer radius is used to compute the verts of the bounding
box that is |
| 1200 // rendered and the outset ensures the box will cover all partially cove
red by the rrect |
| 1201 // corners. |
| 1202 outerRadius += SK_ScalarHalf; |
| 1203 innerRadius -= SK_ScalarHalf; |
| 1204 |
| 1205 // Expand the rect so all the pixels will be captured. |
| 1206 bounds.outset(SK_ScalarHalf, SK_ScalarHalf); |
| 1207 |
| 1208 fGeoData.emplace_back(Geometry { color, innerRadius, outerRadius, bounds
}); |
| 1209 this->setBounds(bounds); |
1248 } | 1210 } |
1249 | 1211 |
1250 const char* name() const override { return "RRectCircleBatch"; } | 1212 const char* name() const override { return "RRectCircleBatch"; } |
1251 | 1213 |
1252 void computePipelineOptimizations(GrInitInvariantOutput* color, | 1214 void computePipelineOptimizations(GrInitInvariantOutput* color, |
1253 GrInitInvariantOutput* coverage, | 1215 GrInitInvariantOutput* coverage, |
1254 GrBatchToXPOverrides* overrides) const ove
rride { | 1216 GrBatchToXPOverrides* overrides) const ove
rride { |
1255 // When this is called on a batch, there is only one geometry bundle | 1217 // When this is called on a batch, there is only one geometry bundle |
1256 color->setKnownFourComponents(fGeoData[0].fColor); | 1218 color->setKnownFourComponents(fGeoData[0].fColor); |
1257 coverage->setUnknownSingleComponent(); | 1219 coverage->setUnknownSingleComponent(); |
(...skipping 101 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1359 | 1321 |
1360 if (!fViewMatrixIfUsingLocalCoords.cheapEqualTo(that->fViewMatrixIfUsing
LocalCoords)) { | 1322 if (!fViewMatrixIfUsingLocalCoords.cheapEqualTo(that->fViewMatrixIfUsing
LocalCoords)) { |
1361 return false; | 1323 return false; |
1362 } | 1324 } |
1363 | 1325 |
1364 fGeoData.push_back_n(that->fGeoData.count(), that->fGeoData.begin()); | 1326 fGeoData.push_back_n(that->fGeoData.count(), that->fGeoData.begin()); |
1365 this->joinBounds(that->bounds()); | 1327 this->joinBounds(that->bounds()); |
1366 return true; | 1328 return true; |
1367 } | 1329 } |
1368 | 1330 |
| 1331 struct Geometry { |
| 1332 GrColor fColor; |
| 1333 SkScalar fInnerRadius; |
| 1334 SkScalar fOuterRadius; |
| 1335 SkRect fDevBounds; |
| 1336 }; |
| 1337 |
1369 bool fStroked; | 1338 bool fStroked; |
1370 SkMatrix fViewMatrixIfUsingLocalCoords; | 1339 SkMatrix fViewMatrixIfUsingLocalCoords; |
1371 SkSTArray<1, Geometry, true> fGeoData; | 1340 SkSTArray<1, Geometry, true> fGeoData; |
1372 | 1341 |
1373 typedef GrVertexBatch INHERITED; | 1342 typedef GrVertexBatch INHERITED; |
1374 }; | 1343 }; |
1375 | 1344 |
1376 class RRectEllipseRendererBatch : public GrVertexBatch { | 1345 class RRectEllipseRendererBatch : public GrVertexBatch { |
1377 public: | 1346 public: |
1378 DEFINE_BATCH_CLASS_ID | 1347 DEFINE_BATCH_CLASS_ID |
1379 | 1348 |
1380 struct Geometry { | 1349 // If devStrokeWidths values are <= 0 indicates then fill only. Otherwise, s
trokeOnly indicates |
1381 SkRect fDevBounds; | 1350 // whether the rrect is only stroked or stroked and filled. |
1382 SkScalar fXRadius; | 1351 static GrDrawBatch* Create(GrColor color, const SkMatrix& viewMatrix, const
SkRect& devRect, |
1383 SkScalar fYRadius; | 1352 float devXRadius, float devYRadius, SkVector devS
trokeWidths, |
1384 SkScalar fInnerXRadius; | 1353 bool strokeOnly) { |
1385 SkScalar fInnerYRadius; | 1354 SkASSERT(devXRadius > 0.5); |
1386 GrColor fColor; | 1355 SkASSERT(devYRadius > 0.5); |
1387 }; | 1356 SkASSERT((devStrokeWidths.fX > 0) == (devStrokeWidths.fY > 0)); |
| 1357 SkASSERT(!(strokeOnly && devStrokeWidths.fX <= 0)); |
| 1358 SkScalar innerXRadius = 0.0f; |
| 1359 SkScalar innerYRadius = 0.0f; |
| 1360 SkRect bounds = devRect; |
| 1361 bool stroked = false; |
| 1362 if (devStrokeWidths.fX > 0) { |
| 1363 if (SkScalarNearlyZero(devStrokeWidths.length())) { |
| 1364 devStrokeWidths.set(SK_ScalarHalf, SK_ScalarHalf); |
| 1365 } else { |
| 1366 devStrokeWidths.scale(SK_ScalarHalf); |
| 1367 } |
1388 | 1368 |
1389 RRectEllipseRendererBatch(const Geometry& geometry, const SkMatrix& viewMatr
ix, bool stroked) | 1369 // we only handle thick strokes for near-circular ellipses |
1390 : INHERITED(ClassID()) | 1370 if (devStrokeWidths.length() > SK_ScalarHalf && |
1391 , fStroked(stroked) | 1371 (SK_ScalarHalf*devXRadius > devYRadius || SK_ScalarHalf*devYRadi
us > devXRadius)) { |
1392 , fViewMatrixIfUsingLocalCoords(viewMatrix) { | 1372 return nullptr; |
1393 fGeoData.push_back(geometry); | 1373 } |
1394 this->setBounds(geometry.fDevBounds); | 1374 |
| 1375 // we don't handle it if curvature of the stroke is less than curvat
ure of the ellipse |
| 1376 if (devStrokeWidths.fX*(devYRadius*devYRadius) < |
| 1377 (devStrokeWidths.fY*devStrokeWidths.fY)*devXRadius) { |
| 1378 return nullptr; |
| 1379 } |
| 1380 if (devStrokeWidths.fY*(devXRadius*devXRadius) < |
| 1381 (devStrokeWidths.fX*devStrokeWidths.fX)*devYRadius) { |
| 1382 return nullptr; |
| 1383 } |
| 1384 |
| 1385 // this is legit only if scale & translation (which should be the ca
se at the moment) |
| 1386 if (strokeOnly) { |
| 1387 innerXRadius = devXRadius - devStrokeWidths.fX; |
| 1388 innerYRadius = devYRadius - devStrokeWidths.fY; |
| 1389 stroked = (innerXRadius >= 0 && innerYRadius >= 0); |
| 1390 } |
| 1391 |
| 1392 devXRadius += devStrokeWidths.fX; |
| 1393 devYRadius += devStrokeWidths.fY; |
| 1394 bounds.outset(devStrokeWidths.fX, devStrokeWidths.fY); |
| 1395 } |
| 1396 |
| 1397 // Expand the rect so all the pixels will be captured. |
| 1398 bounds.outset(SK_ScalarHalf, SK_ScalarHalf); |
| 1399 |
| 1400 RRectEllipseRendererBatch* batch = new RRectEllipseRendererBatch(); |
| 1401 batch->fStroked = stroked; |
| 1402 batch->fViewMatrixIfUsingLocalCoords = viewMatrix; |
| 1403 batch->fGeoData.emplace_back( |
| 1404 Geometry {color, devXRadius, devYRadius, innerXRadius, innerYRadius,
bounds}); |
| 1405 batch->setBounds(bounds); |
| 1406 return batch; |
1395 } | 1407 } |
1396 | 1408 |
1397 const char* name() const override { return "RRectEllipseRendererBatch"; } | 1409 const char* name() const override { return "RRectEllipseRendererBatch"; } |
1398 | 1410 |
1399 void computePipelineOptimizations(GrInitInvariantOutput* color, | 1411 void computePipelineOptimizations(GrInitInvariantOutput* color, |
1400 GrInitInvariantOutput* coverage, | 1412 GrInitInvariantOutput* coverage, |
1401 GrBatchToXPOverrides* overrides) const ove
rride { | 1413 GrBatchToXPOverrides* overrides) const ove
rride { |
1402 // When this is called on a batch, there is only one geometry bundle | 1414 // When this is called on a batch, there is only one geometry bundle |
1403 color->setKnownFourComponents(fGeoData[0].fColor); | 1415 color->setKnownFourComponents(fGeoData[0].fColor); |
1404 coverage->setUnknownSingleComponent(); | 1416 coverage->setUnknownSingleComponent(); |
1405 } | 1417 } |
1406 | 1418 |
1407 private: | 1419 private: |
| 1420 RRectEllipseRendererBatch() : INHERITED(ClassID()) {} |
| 1421 |
1408 void initBatchTracker(const GrXPOverridesForBatch& overrides) override { | 1422 void initBatchTracker(const GrXPOverridesForBatch& overrides) override { |
1409 // Handle overrides that affect our GP. | 1423 // Handle overrides that affect our GP. |
1410 overrides.getOverrideColorIfSet(&fGeoData[0].fColor); | 1424 overrides.getOverrideColorIfSet(&fGeoData[0].fColor); |
1411 if (!overrides.readsLocalCoords()) { | 1425 if (!overrides.readsLocalCoords()) { |
1412 fViewMatrixIfUsingLocalCoords.reset(); | 1426 fViewMatrixIfUsingLocalCoords.reset(); |
1413 } | 1427 } |
1414 } | 1428 } |
1415 | 1429 |
1416 void onPrepareDraws(Target* target) const override { | 1430 void onPrepareDraws(Target* target) const override { |
1417 SkMatrix localMatrix; | 1431 SkMatrix localMatrix; |
(...skipping 99 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1517 | 1531 |
1518 if (!fViewMatrixIfUsingLocalCoords.cheapEqualTo(that->fViewMatrixIfUsing
LocalCoords)) { | 1532 if (!fViewMatrixIfUsingLocalCoords.cheapEqualTo(that->fViewMatrixIfUsing
LocalCoords)) { |
1519 return false; | 1533 return false; |
1520 } | 1534 } |
1521 | 1535 |
1522 fGeoData.push_back_n(that->fGeoData.count(), that->fGeoData.begin()); | 1536 fGeoData.push_back_n(that->fGeoData.count(), that->fGeoData.begin()); |
1523 this->joinBounds(that->bounds()); | 1537 this->joinBounds(that->bounds()); |
1524 return true; | 1538 return true; |
1525 } | 1539 } |
1526 | 1540 |
| 1541 struct Geometry { |
| 1542 GrColor fColor; |
| 1543 SkScalar fXRadius; |
| 1544 SkScalar fYRadius; |
| 1545 SkScalar fInnerXRadius; |
| 1546 SkScalar fInnerYRadius; |
| 1547 SkRect fDevBounds; |
| 1548 }; |
| 1549 |
1527 bool fStroked; | 1550 bool fStroked; |
1528 SkMatrix fViewMatrixIfUsingLocalCoords; | 1551 SkMatrix fViewMatrixIfUsingLocalCoords; |
1529 SkSTArray<1, Geometry, true> fGeoData; | 1552 SkSTArray<1, Geometry, true> fGeoData; |
1530 | 1553 |
1531 typedef GrVertexBatch INHERITED; | 1554 typedef GrVertexBatch INHERITED; |
1532 }; | 1555 }; |
1533 | 1556 |
1534 static GrDrawBatch* create_rrect_batch(GrColor color, | 1557 static GrDrawBatch* create_rrect_batch(GrColor color, |
1535 const SkMatrix& viewMatrix, | 1558 const SkMatrix& viewMatrix, |
1536 const SkRRect& rrect, | 1559 const SkRRect& rrect, |
1537 const SkStrokeRec& stroke) { | 1560 const SkStrokeRec& stroke) { |
1538 SkASSERT(viewMatrix.rectStaysRect()); | 1561 SkASSERT(viewMatrix.rectStaysRect()); |
1539 SkASSERT(rrect.isSimple()); | 1562 SkASSERT(rrect.isSimple()); |
1540 SkASSERT(!rrect.isOval()); | 1563 SkASSERT(!rrect.isOval()); |
1541 | 1564 |
1542 // RRect batchs only handle simple, but not too simple, rrects | 1565 // RRect batchs only handle simple, but not too simple, rrects |
1543 // do any matrix crunching before we reset the draw state for device coords | 1566 // do any matrix crunching before we reset the draw state for device coords |
1544 const SkRect& rrectBounds = rrect.getBounds(); | 1567 const SkRect& rrectBounds = rrect.getBounds(); |
1545 SkRect bounds; | 1568 SkRect bounds; |
1546 viewMatrix.mapRect(&bounds, rrectBounds); | 1569 viewMatrix.mapRect(&bounds, rrectBounds); |
1547 | 1570 |
1548 SkVector radii = rrect.getSimpleRadii(); | 1571 SkVector radii = rrect.getSimpleRadii(); |
1549 SkScalar xRadius = SkScalarAbs(viewMatrix[SkMatrix::kMScaleX]*radii.fX + | 1572 SkScalar xRadius = SkScalarAbs(viewMatrix[SkMatrix::kMScaleX]*radii.fX + |
1550 viewMatrix[SkMatrix::kMSkewY]*radii.fY); | 1573 viewMatrix[SkMatrix::kMSkewY]*radii.fY); |
1551 SkScalar yRadius = SkScalarAbs(viewMatrix[SkMatrix::kMSkewX]*radii.fX + | 1574 SkScalar yRadius = SkScalarAbs(viewMatrix[SkMatrix::kMSkewX]*radii.fX + |
1552 viewMatrix[SkMatrix::kMScaleY]*radii.fY); | 1575 viewMatrix[SkMatrix::kMScaleY]*radii.fY); |
1553 | 1576 |
1554 SkStrokeRec::Style style = stroke.getStyle(); | 1577 SkStrokeRec::Style style = stroke.getStyle(); |
1555 | 1578 |
1556 // do (potentially) anisotropic mapping of stroke | 1579 // Do (potentially) anisotropic mapping of stroke. Use -1s to indicate fill-
only draws. |
1557 SkVector scaledStroke; | 1580 SkVector scaledStroke = {-1, -1}; |
1558 SkScalar strokeWidth = stroke.getWidth(); | 1581 SkScalar strokeWidth = stroke.getWidth(); |
1559 | 1582 |
1560 bool isStrokeOnly = SkStrokeRec::kStroke_Style == style || | 1583 bool isStrokeOnly = SkStrokeRec::kStroke_Style == style || |
1561 SkStrokeRec::kHairline_Style == style; | 1584 SkStrokeRec::kHairline_Style == style; |
1562 bool hasStroke = isStrokeOnly || SkStrokeRec::kStrokeAndFill_Style == style; | 1585 bool hasStroke = isStrokeOnly || SkStrokeRec::kStrokeAndFill_Style == style; |
1563 | 1586 |
1564 if (hasStroke) { | 1587 if (hasStroke) { |
1565 if (SkStrokeRec::kHairline_Style == style) { | 1588 if (SkStrokeRec::kHairline_Style == style) { |
1566 scaledStroke.set(1, 1); | 1589 scaledStroke.set(1, 1); |
1567 } else { | 1590 } else { |
(...skipping 13 matching lines...) Expand all Loading... |
1581 // the interior of the rrect if the radii are >= 0.5. Otherwise, the inner r
ect of the nine- | 1604 // the interior of the rrect if the radii are >= 0.5. Otherwise, the inner r
ect of the nine- |
1582 // patch will have fractional coverage. This only matters when the interior
is actually filled. | 1605 // patch will have fractional coverage. This only matters when the interior
is actually filled. |
1583 // We could consider falling back to rect rendering here, since a tiny radiu
s is | 1606 // We could consider falling back to rect rendering here, since a tiny radiu
s is |
1584 // indistinguishable from a square corner. | 1607 // indistinguishable from a square corner. |
1585 if (!isStrokeOnly && (SK_ScalarHalf > xRadius || SK_ScalarHalf > yRadius)) { | 1608 if (!isStrokeOnly && (SK_ScalarHalf > xRadius || SK_ScalarHalf > yRadius)) { |
1586 return nullptr; | 1609 return nullptr; |
1587 } | 1610 } |
1588 | 1611 |
1589 // if the corners are circles, use the circle renderer | 1612 // if the corners are circles, use the circle renderer |
1590 if ((!hasStroke || scaledStroke.fX == scaledStroke.fY) && xRadius == yRadius
) { | 1613 if ((!hasStroke || scaledStroke.fX == scaledStroke.fY) && xRadius == yRadius
) { |
1591 SkScalar innerRadius = 0.0f; | 1614 return new RRectCircleRendererBatch(color, viewMatrix, bounds, xRadius,
scaledStroke.fX, |
1592 SkScalar outerRadius = xRadius; | 1615 isStrokeOnly); |
1593 SkScalar halfWidth = 0; | |
1594 if (hasStroke) { | |
1595 if (SkScalarNearlyZero(scaledStroke.fX)) { | |
1596 halfWidth = SK_ScalarHalf; | |
1597 } else { | |
1598 halfWidth = SkScalarHalf(scaledStroke.fX); | |
1599 } | |
1600 | |
1601 if (isStrokeOnly) { | |
1602 innerRadius = xRadius - halfWidth; | |
1603 } | |
1604 outerRadius += halfWidth; | |
1605 bounds.outset(halfWidth, halfWidth); | |
1606 } | |
1607 | |
1608 isStrokeOnly = (isStrokeOnly && innerRadius >= 0); | |
1609 | |
1610 // The radii are outset for two reasons. First, it allows the shader to
simply perform | |
1611 // simpler computation because the computed alpha is zero, rather than 5
0%, at the radius. | |
1612 // Second, the outer radius is used to compute the verts of the bounding
box that is | |
1613 // rendered and the outset ensures the box will cover all partially cove
red by the rrect | |
1614 // corners. | |
1615 outerRadius += SK_ScalarHalf; | |
1616 innerRadius -= SK_ScalarHalf; | |
1617 | |
1618 // Expand the rect so all the pixels will be captured. | |
1619 bounds.outset(SK_ScalarHalf, SK_ScalarHalf); | |
1620 | |
1621 RRectCircleRendererBatch::Geometry geometry; | |
1622 geometry.fColor = color; | |
1623 geometry.fInnerRadius = innerRadius; | |
1624 geometry.fOuterRadius = outerRadius; | |
1625 geometry.fDevBounds = bounds; | |
1626 | |
1627 return new RRectCircleRendererBatch(geometry, viewMatrix, isStrokeOnly); | |
1628 // otherwise we use the ellipse renderer | 1616 // otherwise we use the ellipse renderer |
1629 } else { | 1617 } else { |
1630 SkScalar innerXRadius = 0.0f; | 1618 return RRectEllipseRendererBatch::Create(color, viewMatrix, bounds, xRad
ius, yRadius, |
1631 SkScalar innerYRadius = 0.0f; | 1619 scaledStroke, isStrokeOnly); |
1632 if (hasStroke) { | |
1633 if (SkScalarNearlyZero(scaledStroke.length())) { | |
1634 scaledStroke.set(SK_ScalarHalf, SK_ScalarHalf); | |
1635 } else { | |
1636 scaledStroke.scale(SK_ScalarHalf); | |
1637 } | |
1638 | 1620 |
1639 // we only handle thick strokes for near-circular ellipses | |
1640 if (scaledStroke.length() > SK_ScalarHalf && | |
1641 (SK_ScalarHalf*xRadius > yRadius || SK_ScalarHalf*yRadius > xRad
ius)) { | |
1642 return nullptr; | |
1643 } | |
1644 | |
1645 // we don't handle it if curvature of the stroke is less than curvat
ure of the ellipse | |
1646 if (scaledStroke.fX*(yRadius*yRadius) < (scaledStroke.fY*scaledStrok
e.fY)*xRadius || | |
1647 scaledStroke.fY*(xRadius*xRadius) < (scaledStroke.fX*scaledStrok
e.fX)*yRadius) { | |
1648 return nullptr; | |
1649 } | |
1650 | |
1651 // this is legit only if scale & translation (which should be the ca
se at the moment) | |
1652 if (isStrokeOnly) { | |
1653 innerXRadius = xRadius - scaledStroke.fX; | |
1654 innerYRadius = yRadius - scaledStroke.fY; | |
1655 } | |
1656 | |
1657 xRadius += scaledStroke.fX; | |
1658 yRadius += scaledStroke.fY; | |
1659 bounds.outset(scaledStroke.fX, scaledStroke.fY); | |
1660 } | |
1661 | |
1662 isStrokeOnly = (isStrokeOnly && innerXRadius >= 0 && innerYRadius >= 0); | |
1663 | |
1664 // Expand the rect so all the pixels will be captured. | |
1665 bounds.outset(SK_ScalarHalf, SK_ScalarHalf); | |
1666 | |
1667 RRectEllipseRendererBatch::Geometry geometry; | |
1668 geometry.fColor = color; | |
1669 geometry.fXRadius = xRadius; | |
1670 geometry.fYRadius = yRadius; | |
1671 geometry.fInnerXRadius = innerXRadius; | |
1672 geometry.fInnerYRadius = innerYRadius; | |
1673 geometry.fDevBounds = bounds; | |
1674 | |
1675 return new RRectEllipseRendererBatch(geometry, viewMatrix, isStrokeOnly)
; | |
1676 } | 1621 } |
1677 } | 1622 } |
1678 | 1623 |
1679 GrDrawBatch* GrOvalRenderer::CreateRRectBatch(GrColor color, | 1624 GrDrawBatch* GrOvalRenderer::CreateRRectBatch(GrColor color, |
1680 const SkMatrix& viewMatrix, | 1625 const SkMatrix& viewMatrix, |
1681 const SkRRect& rrect, | 1626 const SkRRect& rrect, |
1682 const SkStrokeRec& stroke, | 1627 const SkStrokeRec& stroke, |
1683 GrShaderCaps* shaderCaps) { | 1628 GrShaderCaps* shaderCaps) { |
1684 if (rrect.isOval()) { | 1629 if (rrect.isOval()) { |
1685 return CreateOvalBatch(color, viewMatrix, rrect.getBounds(), stroke, sha
derCaps); | 1630 return CreateOvalBatch(color, viewMatrix, rrect.getBounds(), stroke, sha
derCaps); |
1686 } | 1631 } |
1687 | 1632 |
1688 if (!viewMatrix.rectStaysRect() || !rrect.isSimple()) { | 1633 if (!viewMatrix.rectStaysRect() || !rrect.isSimple()) { |
1689 return nullptr; | 1634 return nullptr; |
1690 } | 1635 } |
1691 | 1636 |
1692 return create_rrect_batch(color, viewMatrix, rrect, stroke); | 1637 return create_rrect_batch(color, viewMatrix, rrect, stroke); |
1693 } | 1638 } |
1694 | 1639 |
1695 ////////////////////////////////////////////////////////////////////////////////
/////////////////// | 1640 /////////////////////////////////////////////////////////////////////////////// |
| 1641 |
| 1642 GrDrawBatch* GrOvalRenderer::CreateOvalBatch(GrColor color, |
| 1643 const SkMatrix& viewMatrix, |
| 1644 const SkRect& oval, |
| 1645 const SkStrokeRec& stroke, |
| 1646 GrShaderCaps* shaderCaps) { |
| 1647 // we can draw circles |
| 1648 if (SkScalarNearlyEqual(oval.width(), oval.height()) && circle_stays_circle(
viewMatrix)) { |
| 1649 return new CircleBatch(color, viewMatrix, oval, stroke); |
| 1650 } |
| 1651 |
| 1652 // if we have shader derivative support, render as device-independent |
| 1653 if (shaderCaps->shaderDerivativeSupport()) { |
| 1654 return DIEllipseBatch::Create(color, viewMatrix, oval, stroke); |
| 1655 } |
| 1656 |
| 1657 // otherwise axis-aligned ellipses only |
| 1658 if (viewMatrix.rectStaysRect()) { |
| 1659 return EllipseBatch::Create(color, viewMatrix, oval, stroke); |
| 1660 } |
| 1661 |
| 1662 return nullptr; |
| 1663 } |
| 1664 |
| 1665 /////////////////////////////////////////////////////////////////////////////// |
1696 | 1666 |
1697 #ifdef GR_TEST_UTILS | 1667 #ifdef GR_TEST_UTILS |
1698 | 1668 |
1699 DRAW_BATCH_TEST_DEFINE(CircleBatch) { | 1669 DRAW_BATCH_TEST_DEFINE(CircleBatch) { |
1700 SkMatrix viewMatrix = GrTest::TestMatrix(random); | 1670 SkMatrix viewMatrix = GrTest::TestMatrix(random); |
1701 GrColor color = GrRandomColor(random); | 1671 GrColor color = GrRandomColor(random); |
1702 SkRect circle = GrTest::TestSquare(random); | 1672 SkRect circle = GrTest::TestSquare(random); |
1703 return create_circle_batch(color, viewMatrix, circle, GrTest::TestStrokeRec(
random)); | 1673 return new CircleBatch(color, viewMatrix, circle, GrTest::TestStrokeRec(rand
om)); |
1704 } | 1674 } |
1705 | 1675 |
1706 DRAW_BATCH_TEST_DEFINE(EllipseBatch) { | 1676 DRAW_BATCH_TEST_DEFINE(EllipseBatch) { |
1707 SkMatrix viewMatrix = GrTest::TestMatrixRectStaysRect(random); | 1677 SkMatrix viewMatrix = GrTest::TestMatrixRectStaysRect(random); |
1708 GrColor color = GrRandomColor(random); | 1678 GrColor color = GrRandomColor(random); |
1709 SkRect ellipse = GrTest::TestSquare(random); | 1679 SkRect ellipse = GrTest::TestSquare(random); |
1710 return create_ellipse_batch(color, viewMatrix, ellipse, GrTest::TestStrokeRe
c(random)); | 1680 return EllipseBatch::Create(color, viewMatrix, ellipse, GrTest::TestStrokeRe
c(random)); |
1711 } | 1681 } |
1712 | 1682 |
1713 DRAW_BATCH_TEST_DEFINE(DIEllipseBatch) { | 1683 DRAW_BATCH_TEST_DEFINE(DIEllipseBatch) { |
1714 SkMatrix viewMatrix = GrTest::TestMatrix(random); | 1684 SkMatrix viewMatrix = GrTest::TestMatrix(random); |
1715 GrColor color = GrRandomColor(random); | 1685 GrColor color = GrRandomColor(random); |
1716 SkRect ellipse = GrTest::TestSquare(random); | 1686 SkRect ellipse = GrTest::TestSquare(random); |
1717 return create_diellipse_batch(color, viewMatrix, ellipse, GrTest::TestStroke
Rec(random)); | 1687 return DIEllipseBatch::Create(color, viewMatrix, ellipse, GrTest::TestStroke
Rec(random)); |
1718 } | 1688 } |
1719 | 1689 |
1720 DRAW_BATCH_TEST_DEFINE(RRectBatch) { | 1690 DRAW_BATCH_TEST_DEFINE(RRectBatch) { |
1721 SkMatrix viewMatrix = GrTest::TestMatrixRectStaysRect(random); | 1691 SkMatrix viewMatrix = GrTest::TestMatrixRectStaysRect(random); |
1722 GrColor color = GrRandomColor(random); | 1692 GrColor color = GrRandomColor(random); |
1723 const SkRRect& rrect = GrTest::TestRRectSimple(random); | 1693 const SkRRect& rrect = GrTest::TestRRectSimple(random); |
1724 return create_rrect_batch(color, viewMatrix, rrect, GrTest::TestStrokeRec(ra
ndom)); | 1694 return create_rrect_batch(color, viewMatrix, rrect, GrTest::TestStrokeRec(ra
ndom)); |
1725 } | 1695 } |
1726 | 1696 |
1727 #endif | 1697 #endif |
OLD | NEW |