aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/gpu/batches/GrShadowRRectBatch.cpp
diff options
context:
space:
mode:
authorGravatar Jim Van Verth <jvanverth@google.com>2016-11-17 15:27:09 -0500
committerGravatar Skia Commit-Bot <skia-commit-bot@chromium.org>2016-11-21 21:44:20 +0000
commitc59034145862bf6dc0c503cb1e47eecd321ffa8c (patch)
tree2e9be313f7221dfe66c9777280be809d75c5a025 /src/gpu/batches/GrShadowRRectBatch.cpp
parent71de072f99a0a0e83d4c06420d870af2824a9d2d (diff)
Add shadowrrect geometry processor
GOLD_TRYBOT_URL= https://gold.skia.org/search?issue=4233 Change-Id: I637099709cfe30f7d3c1883e23840a47a7a25c10 Reviewed-on: https://skia-review.googlesource.com/4233 Commit-Queue: Jim Van Verth <jvanverth@google.com> Reviewed-by: Robert Phillips <robertphillips@google.com>
Diffstat (limited to 'src/gpu/batches/GrShadowRRectBatch.cpp')
-rwxr-xr-xsrc/gpu/batches/GrShadowRRectBatch.cpp964
1 files changed, 964 insertions, 0 deletions
diff --git a/src/gpu/batches/GrShadowRRectBatch.cpp b/src/gpu/batches/GrShadowRRectBatch.cpp
new file mode 100755
index 0000000000..08f7e86952
--- /dev/null
+++ b/src/gpu/batches/GrShadowRRectBatch.cpp
@@ -0,0 +1,964 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "GrShadowRRectBatch.h"
+
+#include "GrBatchFlushState.h"
+#include "GrBatchTest.h"
+#include "GrResourceProvider.h"
+#include "GrStyle.h"
+
+#include "effects/GrShadowGeoProc.h"
+
+///////////////////////////////////////////////////////////////////////////////
+
+// We have two possible cases for geometry for a circle:
+
+// In the case of a normal fill, we draw geometry for the circle as an octagon.
+static const uint16_t gFillCircleIndices[] = {
+ // enter the octagon
+ 0, 1, 8, 1, 2, 8,
+ 2, 3, 8, 3, 4, 8,
+ 4, 5, 8, 5, 6, 8,
+ 6, 7, 8, 7, 0, 8,
+};
+
+// For stroked circles, we use two nested octagons.
+static const uint16_t gStrokeCircleIndices[] = {
+ // enter the octagon
+ 0, 1, 9, 0, 9, 8,
+ 1, 2, 10, 1, 10, 9,
+ 2, 3, 11, 2, 11, 10,
+ 3, 4, 12, 3, 12, 11,
+ 4, 5, 13, 4, 13, 12,
+ 5, 6, 14, 5, 14, 13,
+ 6, 7, 15, 6, 15, 14,
+ 7, 0, 8, 7, 8, 15,
+};
+
+static const int kIndicesPerFillCircle = SK_ARRAY_COUNT(gFillCircleIndices);
+static const int kIndicesPerStrokeCircle = SK_ARRAY_COUNT(gStrokeCircleIndices);
+static const int kVertsPerStrokeCircle = 16;
+static const int kVertsPerFillCircle = 9;
+
+static int circle_type_to_vert_count(bool stroked) {
+ return stroked ? kVertsPerStrokeCircle : kVertsPerFillCircle;
+}
+
+static int circle_type_to_index_count(bool stroked) {
+ return stroked ? kIndicesPerStrokeCircle : kIndicesPerFillCircle;
+}
+
+static const uint16_t* circle_type_to_indices(bool stroked) {
+ return stroked ? gStrokeCircleIndices : gFillCircleIndices;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+class ShadowCircleBatch : public GrVertexBatch {
+public:
+ DEFINE_BATCH_CLASS_ID
+
+ static GrDrawBatch* Create(GrColor color, const SkMatrix& viewMatrix, SkPoint center,
+ SkScalar radius, SkScalar blurRadius, const GrStyle& style) {
+ SkASSERT(viewMatrix.isSimilarity());
+ const SkStrokeRec& stroke = style.strokeRec();
+ if (style.hasPathEffect()) {
+ return nullptr;
+ }
+ SkStrokeRec::Style recStyle = stroke.getStyle();
+
+ viewMatrix.mapPoints(&center, 1);
+ radius = viewMatrix.mapRadius(radius);
+ SkScalar strokeWidth = viewMatrix.mapRadius(stroke.getWidth());
+
+ bool isStrokeOnly = SkStrokeRec::kStroke_Style == recStyle ||
+ SkStrokeRec::kHairline_Style == recStyle;
+ bool hasStroke = isStrokeOnly || SkStrokeRec::kStrokeAndFill_Style == recStyle;
+
+ SkScalar innerRadius = -SK_ScalarHalf;
+ SkScalar outerRadius = radius;
+ SkScalar halfWidth = 0;
+ if (hasStroke) {
+ if (SkScalarNearlyZero(strokeWidth)) {
+ halfWidth = SK_ScalarHalf;
+ } else {
+ halfWidth = SkScalarHalf(strokeWidth);
+ }
+
+ outerRadius += halfWidth;
+ if (isStrokeOnly) {
+ innerRadius = radius - halfWidth;
+ }
+ }
+
+ // TODO: still needed?
+ // The radii are outset for two reasons. First, it allows the shader to simply perform
+ // simpler computation because the computed alpha is zero, rather than 50%, at the radius.
+ // Second, the outer radius is used to compute the verts of the bounding box that is
+ // rendered and the outset ensures the box will cover all partially covered by the circle.
+ outerRadius += SK_ScalarHalf;
+ innerRadius -= SK_ScalarHalf;
+ bool stroked = isStrokeOnly && innerRadius > 0.0f;
+ ShadowCircleBatch* batch = new ShadowCircleBatch();
+ batch->fViewMatrixIfUsingLocalCoords = viewMatrix;
+
+ SkRect devBounds = SkRect::MakeLTRB(center.fX - outerRadius, center.fY - outerRadius,
+ center.fX + outerRadius, center.fY + outerRadius);
+
+ batch->fGeoData.emplace_back(Geometry{
+ color,
+ outerRadius,
+ innerRadius,
+ blurRadius,
+ devBounds,
+ stroked
+ });
+
+ // Use the original radius and stroke radius for the bounds so that it does not include the
+ // AA bloat.
+ radius += halfWidth;
+ batch->setBounds({ center.fX - radius, center.fY - radius,
+ center.fX + radius, center.fY + radius },
+ HasAABloat::kNo, IsZeroArea::kNo);
+ batch->fVertCount = circle_type_to_vert_count(stroked);
+ batch->fIndexCount = circle_type_to_index_count(stroked);
+ return batch;
+ }
+
+ const char* name() const override { return "ShadowCircleBatch"; }
+
+ SkString dumpInfo() const override {
+ SkString string;
+ for (int i = 0; i < fGeoData.count(); ++i) {
+ string.appendf("Color: 0x%08x Rect [L: %.2f, T: %.2f, R: %.2f, B: %.2f], "
+ "OuterRad: %.2f, InnerRad: %.2f, BlurRad: %.2f\n",
+ fGeoData[i].fColor,
+ fGeoData[i].fDevBounds.fLeft, fGeoData[i].fDevBounds.fTop,
+ fGeoData[i].fDevBounds.fRight, fGeoData[i].fDevBounds.fBottom,
+ fGeoData[i].fOuterRadius, fGeoData[i].fInnerRadius,
+ fGeoData[i].fBlurRadius);
+ }
+ string.append(INHERITED::dumpInfo());
+ return string;
+ }
+
+ void computePipelineOptimizations(GrInitInvariantOutput* color,
+ GrInitInvariantOutput* coverage,
+ GrBatchToXPOverrides* overrides) const override {
+ // When this is called on a batch, there is only one geometry bundle
+ color->setKnownFourComponents(fGeoData[0].fColor);
+ coverage->setUnknownSingleComponent();
+ }
+
+private:
+ ShadowCircleBatch() : INHERITED(ClassID()) {}
+ void initBatchTracker(const GrXPOverridesForBatch& overrides) override {
+ // Handle any overrides that affect our GP.
+ overrides.getOverrideColorIfSet(&fGeoData[0].fColor);
+ if (!overrides.readsLocalCoords()) {
+ fViewMatrixIfUsingLocalCoords.reset();
+ }
+ }
+
+ void onPrepareDraws(Target* target) const override {
+ SkMatrix localMatrix;
+ if (!fViewMatrixIfUsingLocalCoords.invert(&localMatrix)) {
+ return;
+ }
+
+ // Setup geometry processor
+ sk_sp<GrGeometryProcessor> gp(GrRRectShadowGeoProc::Make(localMatrix));
+
+ struct CircleVertex {
+ SkPoint fPos;
+ GrColor fColor;
+ SkPoint fOffset;
+ SkScalar fOuterRadius;
+ SkScalar fBlurRadius;
+ };
+
+ int instanceCount = fGeoData.count();
+ size_t vertexStride = gp->getVertexStride();
+ SkASSERT(vertexStride == sizeof(CircleVertex));
+
+ const GrBuffer* vertexBuffer;
+ int firstVertex;
+ char* vertices = (char*)target->makeVertexSpace(vertexStride, fVertCount,
+ &vertexBuffer, &firstVertex);
+ if (!vertices) {
+ SkDebugf("Could not allocate vertices\n");
+ return;
+ }
+
+ const GrBuffer* indexBuffer = nullptr;
+ int firstIndex = 0;
+ uint16_t* indices = target->makeIndexSpace(fIndexCount, &indexBuffer, &firstIndex);
+ if (!indices) {
+ SkDebugf("Could not allocate indices\n");
+ return;
+ }
+
+ int currStartVertex = 0;
+ for (int i = 0; i < instanceCount; i++) {
+ const Geometry& geom = fGeoData[i];
+
+ GrColor color = geom.fColor;
+ SkScalar outerRadius = geom.fOuterRadius;
+ SkScalar innerRadius = geom.fInnerRadius;
+ SkScalar blurRadius = geom.fBlurRadius;
+
+ const SkRect& bounds = geom.fDevBounds;
+ CircleVertex* ov0 = reinterpret_cast<CircleVertex*>(vertices + 0 * vertexStride);
+ CircleVertex* ov1 = reinterpret_cast<CircleVertex*>(vertices + 1 * vertexStride);
+ CircleVertex* ov2 = reinterpret_cast<CircleVertex*>(vertices + 2 * vertexStride);
+ CircleVertex* ov3 = reinterpret_cast<CircleVertex*>(vertices + 3 * vertexStride);
+ CircleVertex* ov4 = reinterpret_cast<CircleVertex*>(vertices + 4 * vertexStride);
+ CircleVertex* ov5 = reinterpret_cast<CircleVertex*>(vertices + 5 * vertexStride);
+ CircleVertex* ov6 = reinterpret_cast<CircleVertex*>(vertices + 6 * vertexStride);
+ CircleVertex* ov7 = reinterpret_cast<CircleVertex*>(vertices + 7 * vertexStride);
+
+ // The inner radius in the vertex data must be specified in normalized space.
+ innerRadius = innerRadius / outerRadius;
+
+ SkPoint center = SkPoint::Make(bounds.centerX(), bounds.centerY());
+ SkScalar halfWidth = 0.5f*bounds.width();
+ SkScalar octOffset = 0.41421356237f; // sqrt(2) - 1
+
+ ov0->fPos = center + SkPoint::Make(-octOffset*halfWidth, -halfWidth);
+ ov0->fColor = color;
+ ov0->fOffset = SkPoint::Make(-octOffset, -1);
+ ov0->fOuterRadius = outerRadius;
+ ov0->fBlurRadius = blurRadius;
+
+ ov1->fPos = center + SkPoint::Make(octOffset*halfWidth, -halfWidth);
+ ov1->fColor = color;
+ ov1->fOffset = SkPoint::Make(octOffset, -1);
+ ov1->fOuterRadius = outerRadius;
+ ov1->fBlurRadius = blurRadius;
+
+ ov2->fPos = center + SkPoint::Make(halfWidth, -octOffset*halfWidth);
+ ov2->fColor = color;
+ ov2->fOffset = SkPoint::Make(1, -octOffset);
+ ov2->fOuterRadius = outerRadius;
+ ov2->fBlurRadius = blurRadius;
+
+ ov3->fPos = center + SkPoint::Make(halfWidth, octOffset*halfWidth);
+ ov3->fColor = color;
+ ov3->fOffset = SkPoint::Make(1, octOffset);
+ ov3->fOuterRadius = outerRadius;
+ ov3->fBlurRadius = blurRadius;
+
+ ov4->fPos = center + SkPoint::Make(octOffset*halfWidth, halfWidth);
+ ov4->fColor = color;
+ ov4->fOffset = SkPoint::Make(octOffset, 1);
+ ov4->fOuterRadius = outerRadius;
+ ov4->fBlurRadius = blurRadius;
+
+ ov5->fPos = center + SkPoint::Make(-octOffset*halfWidth, halfWidth);
+ ov5->fColor = color;
+ ov5->fOffset = SkPoint::Make(-octOffset, 1);
+ ov5->fOuterRadius = outerRadius;
+ ov5->fBlurRadius = blurRadius;
+
+ ov6->fPos = center + SkPoint::Make(-halfWidth, octOffset*halfWidth);
+ ov6->fColor = color;
+ ov6->fOffset = SkPoint::Make(-1, octOffset);
+ ov6->fOuterRadius = outerRadius;
+ ov6->fBlurRadius = blurRadius;
+
+ ov7->fPos = center + SkPoint::Make(-halfWidth, -octOffset*halfWidth);
+ ov7->fColor = color;
+ ov7->fOffset = SkPoint::Make(-1, -octOffset);
+ ov7->fOuterRadius = outerRadius;
+ ov7->fBlurRadius = blurRadius;
+
+ if (geom.fStroked) {
+ // compute the inner ring
+ CircleVertex* iv0 = reinterpret_cast<CircleVertex*>(vertices + 8 * vertexStride);
+ CircleVertex* iv1 = reinterpret_cast<CircleVertex*>(vertices + 9 * vertexStride);
+ CircleVertex* iv2 = reinterpret_cast<CircleVertex*>(vertices + 10 * vertexStride);
+ CircleVertex* iv3 = reinterpret_cast<CircleVertex*>(vertices + 11 * vertexStride);
+ CircleVertex* iv4 = reinterpret_cast<CircleVertex*>(vertices + 12 * vertexStride);
+ CircleVertex* iv5 = reinterpret_cast<CircleVertex*>(vertices + 13 * vertexStride);
+ CircleVertex* iv6 = reinterpret_cast<CircleVertex*>(vertices + 14 * vertexStride);
+ CircleVertex* iv7 = reinterpret_cast<CircleVertex*>(vertices + 15 * vertexStride);
+
+ // cosine and sine of pi/8
+ SkScalar c = 0.923579533f;
+ SkScalar s = 0.382683432f;
+ SkScalar r = geom.fInnerRadius;
+
+ iv0->fPos = center + SkPoint::Make(-s*r, -c*r);
+ iv0->fColor = color;
+ iv0->fOffset = SkPoint::Make(-s*innerRadius, -c*innerRadius);
+ iv0->fOuterRadius = outerRadius;
+ iv0->fBlurRadius = blurRadius;
+
+ iv1->fPos = center + SkPoint::Make(s*r, -c*r);
+ iv1->fColor = color;
+ iv1->fOffset = SkPoint::Make(s*innerRadius, -c*innerRadius);
+ iv1->fOuterRadius = outerRadius;
+ iv1->fBlurRadius = blurRadius;
+
+ iv2->fPos = center + SkPoint::Make(c*r, -s*r);
+ iv2->fColor = color;
+ iv2->fOffset = SkPoint::Make(c*innerRadius, -s*innerRadius);
+ iv2->fOuterRadius = outerRadius;
+ iv2->fBlurRadius = blurRadius;
+
+ iv3->fPos = center + SkPoint::Make(c*r, s*r);
+ iv3->fColor = color;
+ iv3->fOffset = SkPoint::Make(c*innerRadius, s*innerRadius);
+ iv3->fOuterRadius = outerRadius;
+ iv3->fBlurRadius = blurRadius;
+
+ iv4->fPos = center + SkPoint::Make(s*r, c*r);
+ iv4->fColor = color;
+ iv4->fOffset = SkPoint::Make(s*innerRadius, c*innerRadius);
+ iv4->fOuterRadius = outerRadius;
+ iv4->fBlurRadius = blurRadius;
+
+ iv5->fPos = center + SkPoint::Make(-s*r, c*r);
+ iv5->fColor = color;
+ iv5->fOffset = SkPoint::Make(-s*innerRadius, c*innerRadius);
+ iv5->fOuterRadius = outerRadius;
+ iv5->fBlurRadius = blurRadius;
+
+ iv6->fPos = center + SkPoint::Make(-c*r, s*r);
+ iv6->fColor = color;
+ iv6->fOffset = SkPoint::Make(-c*innerRadius, s*innerRadius);
+ iv6->fOuterRadius = outerRadius;
+ iv6->fBlurRadius = blurRadius;
+
+ iv7->fPos = center + SkPoint::Make(-c*r, -s*r);
+ iv7->fColor = color;
+ iv7->fOffset = SkPoint::Make(-c*innerRadius, -s*innerRadius);
+ iv7->fOuterRadius = outerRadius;
+ iv7->fBlurRadius = blurRadius;
+ } else {
+ // filled
+ CircleVertex* iv = reinterpret_cast<CircleVertex*>(vertices + 8 * vertexStride);
+ iv->fPos = center;
+ iv->fColor = color;
+ iv->fOffset = SkPoint::Make(0, 0);
+ iv->fOuterRadius = outerRadius;
+ iv->fBlurRadius = blurRadius;
+ }
+
+ const uint16_t* primIndices = circle_type_to_indices(geom.fStroked);
+ const int primIndexCount = circle_type_to_index_count(geom.fStroked);
+ for (int i = 0; i < primIndexCount; ++i) {
+ *indices++ = primIndices[i] + currStartVertex;
+ }
+
+ currStartVertex += circle_type_to_vert_count(geom.fStroked);
+ vertices += circle_type_to_vert_count(geom.fStroked)*vertexStride;
+ }
+
+ GrMesh mesh;
+ mesh.initIndexed(kTriangles_GrPrimitiveType, vertexBuffer, indexBuffer, firstVertex,
+ firstIndex, fVertCount, fIndexCount);
+ target->draw(gp.get(), mesh);
+ }
+
+ bool onCombineIfPossible(GrBatch* t, const GrCaps& caps) override {
+ ShadowCircleBatch* that = t->cast<ShadowCircleBatch>();
+ if (!GrPipeline::CanCombine(*this->pipeline(), this->bounds(), *that->pipeline(),
+ that->bounds(), caps)) {
+ return false;
+ }
+
+ if (!fViewMatrixIfUsingLocalCoords.cheapEqualTo(that->fViewMatrixIfUsingLocalCoords)) {
+ return false;
+ }
+
+ fGeoData.push_back_n(that->fGeoData.count(), that->fGeoData.begin());
+ this->joinBounds(*that);
+ fVertCount += that->fVertCount;
+ fIndexCount += that->fIndexCount;
+ return true;
+ }
+
+ struct Geometry {
+ GrColor fColor;
+ SkScalar fOuterRadius;
+ SkScalar fInnerRadius;
+ SkScalar fBlurRadius;
+ SkRect fDevBounds;
+ bool fStroked;
+ };
+
+ SkSTArray<1, Geometry, true> fGeoData;
+ SkMatrix fViewMatrixIfUsingLocalCoords;
+ int fVertCount;
+ int fIndexCount;
+
+ typedef GrVertexBatch INHERITED;
+};
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+// We have two possible cases for geometry for a shadow roundrect.
+//
+// In the case of a normal stroke, we draw the roundrect as a 9-patch without the center quad.
+// ____________
+// |_|________|_|
+// | | | |
+// | | | |
+// | | | |
+// |_|________|_|
+// |_|________|_|
+//
+// In the case where the stroke width is greater than twice the corner radius (overstroke),
+// we add additional geometry to mark out the rectangle in the center. The shared vertices
+// are duplicated so we can set a different outer radius for the fill calculation.
+// ____________
+// |_|________|_|
+// | |\ ____ /| |
+// | | | | | |
+// | | |____| | |
+// |_|/______\|_|
+// |_|________|_|
+//
+// For filled rrects we reuse the overstroke geometry but make the inner rect degenerate
+// (either a point or a horizontal or vertical line).
+
+static const uint16_t gOverstrokeRRectIndices[] = {
+ // corners
+ 0, 1, 5, 0, 5, 4,
+ 2, 3, 7, 2, 7, 6,
+ 8, 9, 13, 8, 13, 12,
+ 10, 11, 15, 10, 15, 14,
+
+ // edges
+ 1, 2, 6, 1, 6, 5,
+ 4, 5, 9, 4, 9, 8,
+ 6, 7, 11, 6, 11, 10,
+ 9, 10, 14, 9, 14, 13,
+
+ // overstroke quads
+ // we place this at the end so that we can skip these indices when rendering as stroked
+ 16, 17, 19, 16, 19, 18,
+ 19, 17, 23, 19, 23, 21,
+ 21, 23, 22, 21, 22, 20,
+ 22, 16, 18, 22, 18, 20,
+};
+// standard stroke indices start at the same place, but will skip the overstroke "ring"
+static const uint16_t* gStrokeRRectIndices = gOverstrokeRRectIndices;
+
+// overstroke count
+static const int kIndicesPerOverstrokeRRect = SK_ARRAY_COUNT(gOverstrokeRRectIndices);
+// simple stroke count skips overstroke indices
+static const int kIndicesPerStrokeRRect = kIndicesPerOverstrokeRRect - 6 * 4 + 6;
+static const int kVertsPerStrokeRRect = 16;
+static const int kVertsPerOverstrokeRRect = 24;
+
+enum RRectType {
+ kFill_RRectType,
+ kStroke_RRectType,
+ kOverstroke_RRectType,
+};
+
+static int rrect_type_to_vert_count(RRectType type) {
+ static const int kTypeToVertCount[] = {
+ kVertsPerOverstrokeRRect,
+ kVertsPerStrokeRRect,
+ kVertsPerOverstrokeRRect,
+ };
+
+ return kTypeToVertCount[type];
+}
+
+static int rrect_type_to_index_count(RRectType type) {
+ static const int kTypeToIndexCount[] = {
+ kIndicesPerOverstrokeRRect,
+ kIndicesPerStrokeRRect,
+ kIndicesPerOverstrokeRRect,
+ };
+
+ return kTypeToIndexCount[type];
+}
+
+static const uint16_t* rrect_type_to_indices(RRectType type) {
+ static const uint16_t* kTypeToIndices[] = {
+ gOverstrokeRRectIndices,
+ gStrokeRRectIndices,
+ gOverstrokeRRectIndices,
+ };
+
+ return kTypeToIndices[type];
+}
+
+// For distance computations in the interior of filled rrects we:
+//
+// add a interior degenerate (point or line) rect
+// each vertex of that rect gets -outerRad as its radius
+// this makes the computation of the distance to the outer edge be negative
+// negative values are caught and then handled differently in the GP's onEmitCode
+// each vertex is also given the normalized x & y distance from the interior rect's edge
+// the GP takes the min of those depths +1 to get the normalized distance to the outer edge
+
+class ShadowCircularRRectBatch : public GrVertexBatch {
+public:
+ DEFINE_BATCH_CLASS_ID
+
+ // A devStrokeWidth <= 0 indicates a fill only. If devStrokeWidth > 0 then strokeOnly indicates
+ // whether the rrect is only stroked or stroked and filled.
+ ShadowCircularRRectBatch(GrColor color, const SkMatrix& viewMatrix,
+ const SkRect& devRect, float devRadius, float blurRadius,
+ float devStrokeWidth, bool strokeOnly)
+ : INHERITED(ClassID())
+ , fViewMatrixIfUsingLocalCoords(viewMatrix) {
+ SkRect bounds = devRect;
+ SkASSERT(!(devStrokeWidth <= 0 && strokeOnly));
+ SkScalar innerRadius = 0.0f;
+ SkScalar outerRadius = devRadius;
+ SkScalar halfWidth = 0;
+ RRectType type = kFill_RRectType;
+ if (devStrokeWidth > 0) {
+ if (SkScalarNearlyZero(devStrokeWidth)) {
+ halfWidth = SK_ScalarHalf;
+ } else {
+ halfWidth = SkScalarHalf(devStrokeWidth);
+ }
+
+ if (strokeOnly) {
+ // Outset stroke by 1/4 pixel
+ devStrokeWidth += 0.25f;
+ // If stroke is greater than width or height, this is still a fill
+ // Otherwise we compute stroke params
+ if (devStrokeWidth <= devRect.width() &&
+ devStrokeWidth <= devRect.height()) {
+ innerRadius = devRadius - halfWidth;
+ type = (innerRadius >= 0) ? kStroke_RRectType : kOverstroke_RRectType;
+ }
+ }
+ outerRadius += halfWidth;
+ bounds.outset(halfWidth, halfWidth);
+ }
+
+ // TODO: still needed?
+ // The radii are outset for two reasons. First, it allows the shader to simply perform
+ // simpler computation because the computed alpha is zero, rather than 50%, at the radius.
+ // Second, the outer radius is used to compute the verts of the bounding box that is
+ // rendered and the outset ensures the box will cover all partially covered by the rrect
+ // corners.
+ outerRadius += SK_ScalarHalf;
+ innerRadius -= SK_ScalarHalf;
+
+ this->setBounds(bounds, HasAABloat::kYes, IsZeroArea::kNo);
+
+ // Expand the rect for aa to generate correct vertices.
+ bounds.outset(SK_ScalarHalf, SK_ScalarHalf);
+
+ fGeoData.emplace_back(Geometry{color, outerRadius, innerRadius, blurRadius, bounds, type});
+ fVertCount = rrect_type_to_vert_count(type);
+ fIndexCount = rrect_type_to_index_count(type);
+ }
+
+ const char* name() const override { return "ShadowCircularRRectBatch"; }
+
+ SkString dumpInfo() const override {
+ SkString string;
+ for (int i = 0; i < fGeoData.count(); ++i) {
+ string.appendf("Color: 0x%08x Rect [L: %.2f, T: %.2f, R: %.2f, B: %.2f],"
+ "OuterRad: %.2f, InnerRad: %.2f, BlurRad: %.2f\n",
+ fGeoData[i].fColor,
+ fGeoData[i].fDevBounds.fLeft, fGeoData[i].fDevBounds.fTop,
+ fGeoData[i].fDevBounds.fRight, fGeoData[i].fDevBounds.fBottom,
+ fGeoData[i].fOuterRadius, fGeoData[i].fInnerRadius,
+ fGeoData[i].fBlurRadius);
+ }
+ string.append(INHERITED::dumpInfo());
+ return string;
+ }
+
+ void computePipelineOptimizations(GrInitInvariantOutput* color,
+ GrInitInvariantOutput* coverage,
+ GrBatchToXPOverrides* overrides) const override {
+ // When this is called on a batch, there is only one geometry bundle
+ color->setKnownFourComponents(fGeoData[0].fColor);
+ coverage->setUnknownSingleComponent();
+ }
+
+private:
+ void initBatchTracker(const GrXPOverridesForBatch& overrides) override {
+ // Handle any overrides that affect our GP.
+ overrides.getOverrideColorIfSet(&fGeoData[0].fColor);
+ if (!overrides.readsLocalCoords()) {
+ fViewMatrixIfUsingLocalCoords.reset();
+ }
+ }
+
+ struct CircleVertex {
+ SkPoint fPos;
+ GrColor fColor;
+ SkPoint fOffset;
+ SkScalar fOuterRadius;
+ SkScalar fBlurRadius;
+ };
+
+ static void FillInOverstrokeVerts(CircleVertex** verts, const SkRect& bounds,
+ SkScalar smInset, SkScalar bigInset, SkScalar xOffset,
+ SkScalar outerRadius, GrColor color, SkScalar blurRadius) {
+ SkASSERT(smInset < bigInset);
+
+ // TL
+ (*verts)->fPos = SkPoint::Make(bounds.fLeft + smInset, bounds.fTop + smInset);
+ (*verts)->fColor = color;
+ (*verts)->fOffset = SkPoint::Make(xOffset, 0);
+ (*verts)->fOuterRadius = outerRadius;
+ (*verts)->fBlurRadius = blurRadius;
+ (*verts)++;
+
+ // TR
+ (*verts)->fPos = SkPoint::Make(bounds.fRight - smInset, bounds.fTop + smInset);
+ (*verts)->fColor = color;
+ (*verts)->fOffset = SkPoint::Make(xOffset, 0);
+ (*verts)->fOuterRadius = outerRadius;
+ (*verts)->fBlurRadius = blurRadius;
+ (*verts)++;
+
+ (*verts)->fPos = SkPoint::Make(bounds.fLeft + bigInset, bounds.fTop + bigInset);
+ (*verts)->fColor = color;
+ (*verts)->fOffset = SkPoint::Make(0, 0);
+ (*verts)->fOuterRadius = outerRadius;
+ (*verts)->fBlurRadius = blurRadius;
+ (*verts)++;
+
+ (*verts)->fPos = SkPoint::Make(bounds.fRight - bigInset, bounds.fTop + bigInset);
+ (*verts)->fColor = color;
+ (*verts)->fOffset = SkPoint::Make(0, 0);
+ (*verts)->fOuterRadius = outerRadius;
+ (*verts)->fBlurRadius = blurRadius;
+ (*verts)++;
+
+ (*verts)->fPos = SkPoint::Make(bounds.fLeft + bigInset, bounds.fBottom - bigInset);
+ (*verts)->fColor = color;
+ (*verts)->fOffset = SkPoint::Make(0, 0);
+ (*verts)->fOuterRadius = outerRadius;
+ (*verts)->fBlurRadius = blurRadius;
+ (*verts)++;
+
+ (*verts)->fPos = SkPoint::Make(bounds.fRight - bigInset, bounds.fBottom - bigInset);
+ (*verts)->fColor = color;
+ (*verts)->fOffset = SkPoint::Make(0, 0);
+ (*verts)->fOuterRadius = outerRadius;
+ (*verts)->fBlurRadius = blurRadius;
+ (*verts)++;
+
+ // BL
+ (*verts)->fPos = SkPoint::Make(bounds.fLeft + smInset, bounds.fBottom - smInset);
+ (*verts)->fColor = color;
+ (*verts)->fOffset = SkPoint::Make(xOffset, 0);
+ (*verts)->fOuterRadius = outerRadius;
+ (*verts)->fBlurRadius = blurRadius;
+ (*verts)++;
+
+ // BR
+ (*verts)->fPos = SkPoint::Make(bounds.fRight - smInset, bounds.fBottom - smInset);
+ (*verts)->fColor = color;
+ (*verts)->fOffset = SkPoint::Make(xOffset, 0);
+ (*verts)->fOuterRadius = outerRadius;
+ (*verts)->fBlurRadius = blurRadius;
+ (*verts)++;
+ }
+
+ void onPrepareDraws(Target* target) const override {
+ // Invert the view matrix as a local matrix (if any other processors require coords).
+ SkMatrix localMatrix;
+ if (!fViewMatrixIfUsingLocalCoords.invert(&localMatrix)) {
+ return;
+ }
+
+ // Setup geometry processor
+ sk_sp<GrGeometryProcessor> gp(GrRRectShadowGeoProc::Make(localMatrix));
+
+ int instanceCount = fGeoData.count();
+ size_t vertexStride = gp->getVertexStride();
+ SkASSERT(sizeof(CircleVertex) == vertexStride);
+
+ const GrBuffer* vertexBuffer;
+ int firstVertex;
+
+ CircleVertex* verts = (CircleVertex*)target->makeVertexSpace(vertexStride, fVertCount,
+ &vertexBuffer, &firstVertex);
+ if (!verts) {
+ SkDebugf("Could not allocate vertices\n");
+ return;
+ }
+
+ const GrBuffer* indexBuffer = nullptr;
+ int firstIndex = 0;
+ uint16_t* indices = target->makeIndexSpace(fIndexCount, &indexBuffer, &firstIndex);
+ if (!indices) {
+ SkDebugf("Could not allocate indices\n");
+ return;
+ }
+
+ int currStartVertex = 0;
+ for (int i = 0; i < instanceCount; i++) {
+ const Geometry& args = fGeoData[i];
+
+ GrColor color = args.fColor;
+ SkScalar outerRadius = args.fOuterRadius;
+
+ const SkRect& bounds = args.fDevBounds;
+
+ SkScalar yCoords[4] = {
+ bounds.fTop,
+ bounds.fTop + outerRadius,
+ bounds.fBottom - outerRadius,
+ bounds.fBottom
+ };
+
+ SkScalar yOuterRadii[4] = { -1, 0, 0, 1 };
+ // The inner radius in the vertex data must be specified in normalized space.
+ // For fills, specifying -1/outerRadius guarantees an alpha of 1.0 at the inner radius.
+ SkScalar blurRadius = args.fBlurRadius;
+ for (int i = 0; i < 4; ++i) {
+ verts->fPos = SkPoint::Make(bounds.fLeft, yCoords[i]);
+ verts->fColor = color;
+ verts->fOffset = SkPoint::Make(-1, yOuterRadii[i]);
+ verts->fOuterRadius = outerRadius;
+ verts->fBlurRadius = blurRadius;
+ verts++;
+
+ verts->fPos = SkPoint::Make(bounds.fLeft + outerRadius, yCoords[i]);
+ verts->fColor = color;
+ verts->fOffset = SkPoint::Make(0, yOuterRadii[i]);
+ verts->fOuterRadius = outerRadius;
+ verts->fBlurRadius = blurRadius;
+ verts++;
+
+ verts->fPos = SkPoint::Make(bounds.fRight - outerRadius, yCoords[i]);
+ verts->fColor = color;
+ verts->fOffset = SkPoint::Make(0, yOuterRadii[i]);
+ verts->fOuterRadius = outerRadius;
+ verts->fBlurRadius = blurRadius;
+ verts++;
+
+ verts->fPos = SkPoint::Make(bounds.fRight, yCoords[i]);
+ verts->fColor = color;
+ verts->fOffset = SkPoint::Make(1, yOuterRadii[i]);
+ verts->fOuterRadius = outerRadius;
+ verts->fBlurRadius = blurRadius;
+ verts++;
+ }
+ // Add the additional vertices for overstroked rrects.
+ // Effectively this is an additional stroked rrect, with its
+ // outer radius = outerRadius - innerRadius, and inner radius = 0.
+ // This will give us correct AA in the center and the correct
+ // distance to the outer edge.
+ //
+ // Also, the outer offset is a constant vector pointing to the right, which
+ // guarantees that the distance value along the outer rectangle is constant.
+ if (kOverstroke_RRectType == args.fType) {
+ SkASSERT(args.fInnerRadius <= 0.0f);
+
+ SkScalar overstrokeOuterRadius = outerRadius - args.fInnerRadius;
+ // this is the normalized distance from the outer rectangle of this
+ // geometry to the outer edge
+ SkScalar maxOffset = -args.fInnerRadius / overstrokeOuterRadius;
+
+ FillInOverstrokeVerts(&verts, bounds, outerRadius, overstrokeOuterRadius,
+ maxOffset, overstrokeOuterRadius, color, blurRadius);
+ }
+
+ if (kFill_RRectType == args.fType) {
+ SkScalar halfMinDim = 0.5f * SkTMin(bounds.width(), bounds.height());
+
+ SkScalar xOffset = 1.0f - outerRadius / halfMinDim;
+
+ FillInOverstrokeVerts(&verts, bounds, outerRadius, halfMinDim,
+ xOffset, halfMinDim, color, blurRadius);
+ }
+
+ const uint16_t* primIndices = rrect_type_to_indices(args.fType);
+ const int primIndexCount = rrect_type_to_index_count(args.fType);
+ for (int i = 0; i < primIndexCount; ++i) {
+ *indices++ = primIndices[i] + currStartVertex;
+ }
+
+ currStartVertex += rrect_type_to_vert_count(args.fType);
+ }
+
+ GrMesh mesh;
+ mesh.initIndexed(kTriangles_GrPrimitiveType, vertexBuffer, indexBuffer, firstVertex,
+ firstIndex, fVertCount, fIndexCount);
+ target->draw(gp.get(), mesh);
+ }
+
+ bool onCombineIfPossible(GrBatch* t, const GrCaps& caps) override {
+ ShadowCircularRRectBatch* that = t->cast<ShadowCircularRRectBatch>();
+ if (!GrPipeline::CanCombine(*this->pipeline(), this->bounds(), *that->pipeline(),
+ that->bounds(), caps)) {
+ return false;
+ }
+
+ if (!fViewMatrixIfUsingLocalCoords.cheapEqualTo(that->fViewMatrixIfUsingLocalCoords)) {
+ return false;
+ }
+
+ fGeoData.push_back_n(that->fGeoData.count(), that->fGeoData.begin());
+ this->joinBounds(*that);
+ fVertCount += that->fVertCount;
+ fIndexCount += that->fIndexCount;
+ return true;
+ }
+
+ struct Geometry {
+ GrColor fColor;
+ SkScalar fOuterRadius;
+ SkScalar fInnerRadius;
+ SkScalar fBlurRadius;
+ SkRect fDevBounds;
+ RRectType fType;
+ };
+
+ SkSTArray<1, Geometry, true> fGeoData;
+ SkMatrix fViewMatrixIfUsingLocalCoords;
+ int fVertCount;
+ int fIndexCount;
+
+ typedef GrVertexBatch INHERITED;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+static GrDrawBatch* create_shadow_circle_batch(GrColor color,
+ const SkMatrix& viewMatrix,
+ const SkRect& oval,
+ SkScalar blurRadius,
+ const SkStrokeRec& stroke,
+ const GrShaderCaps* shaderCaps) {
+ // we can only draw circles
+ SkScalar width = oval.width();
+ SkASSERT(SkScalarNearlyEqual(width, oval.height()) && viewMatrix.isSimilarity());
+ SkPoint center = { oval.centerX(), oval.centerY() };
+ return ShadowCircleBatch::Create(color, viewMatrix, center, width / 2.f,
+ blurRadius, GrStyle(stroke, nullptr));
+}
+
+static GrDrawBatch* create_shadow_rrect_batch(GrColor color,
+ const SkMatrix& viewMatrix,
+ const SkRRect& rrect,
+ SkScalar blurRadius,
+ const SkStrokeRec& stroke) {
+ SkASSERT(viewMatrix.rectStaysRect());
+ SkASSERT(rrect.isSimple());
+ SkASSERT(!rrect.isOval());
+
+ // Shadow rrect batchs only handle simple circular rrects
+ // do any matrix crunching before we reset the draw state for device coords
+ const SkRect& rrectBounds = rrect.getBounds();
+ SkRect bounds;
+ viewMatrix.mapRect(&bounds, rrectBounds);
+
+ SkVector radii = rrect.getSimpleRadii();
+ SkScalar xRadius = SkScalarAbs(viewMatrix[SkMatrix::kMScaleX] * radii.fX +
+ viewMatrix[SkMatrix::kMSkewY] * radii.fY);
+ SkScalar yRadius = SkScalarAbs(viewMatrix[SkMatrix::kMSkewX] * radii.fX +
+ viewMatrix[SkMatrix::kMScaleY] * radii.fY);
+ SkASSERT(SkScalarNearlyEqual(xRadius, yRadius));
+
+ SkStrokeRec::Style style = stroke.getStyle();
+
+ // Do (potentially) anisotropic mapping of stroke. Use -1s to indicate fill-only draws.
+ SkVector scaledStroke = { -1, -1 };
+ SkScalar strokeWidth = stroke.getWidth();
+
+ bool isStrokeOnly = SkStrokeRec::kStroke_Style == style ||
+ SkStrokeRec::kHairline_Style == style;
+ bool hasStroke = isStrokeOnly || SkStrokeRec::kStrokeAndFill_Style == style;
+
+ if (hasStroke) {
+ if (SkStrokeRec::kHairline_Style == style) {
+ scaledStroke.set(1, 1);
+ } else {
+ scaledStroke.fX = SkScalarAbs(strokeWidth*(viewMatrix[SkMatrix::kMScaleX] +
+ viewMatrix[SkMatrix::kMSkewY]));
+ scaledStroke.fY = SkScalarAbs(strokeWidth*(viewMatrix[SkMatrix::kMSkewX] +
+ viewMatrix[SkMatrix::kMScaleY]));
+ }
+
+ // we don't handle anisotropic strokes
+ if (!SkScalarNearlyEqual(scaledStroke.fX, scaledStroke.fY)) {
+ return nullptr;
+ }
+ }
+
+ // The way the effect interpolates the offset-to-ellipse/circle-center attribute only works on
+ // the interior of the rrect if the radii are >= 0.5. Otherwise, the inner rect of the nine-
+ // patch will have fractional coverage. This only matters when the interior is actually filled.
+ // We could consider falling back to rect rendering here, since a tiny radius is
+ // indistinguishable from a square corner.
+ if (!isStrokeOnly && (SK_ScalarHalf > xRadius || SK_ScalarHalf > yRadius)) {
+ return nullptr;
+ }
+
+ return new ShadowCircularRRectBatch(color, viewMatrix, bounds, xRadius,
+ blurRadius, scaledStroke.fX, isStrokeOnly);
+}
+
+GrDrawBatch* CreateShadowRRectBatch(GrColor color,
+ const SkMatrix& viewMatrix,
+ const SkRRect& rrect,
+ const SkScalar blurRadius,
+ const SkStrokeRec& stroke,
+ const GrShaderCaps* shaderCaps) {
+ if (rrect.isOval()) {
+ return create_shadow_circle_batch(color, viewMatrix, rrect.getBounds(),
+ blurRadius, stroke, shaderCaps);
+ }
+
+ if (!viewMatrix.rectStaysRect() || !rrect.isSimple()) {
+ return nullptr;
+ }
+
+ return create_shadow_rrect_batch(color, viewMatrix, rrect, blurRadius, stroke);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#ifdef GR_TEST_UTILS
+
+DRAW_BATCH_TEST_DEFINE(ShadowCircleBatch) {
+ do {
+ SkScalar rotate = random->nextSScalar1() * 360.f;
+ SkScalar translateX = random->nextSScalar1() * 1000.f;
+ SkScalar translateY = random->nextSScalar1() * 1000.f;
+ SkScalar scale = random->nextSScalar1() * 100.f;
+ SkMatrix viewMatrix;
+ viewMatrix.setRotate(rotate);
+ viewMatrix.postTranslate(translateX, translateY);
+ viewMatrix.postScale(scale, scale);
+ GrColor color = GrRandomColor(random);
+ SkRect circle = GrTest::TestSquare(random);
+ SkPoint center = { circle.centerX(), circle.centerY() };
+ SkScalar radius = circle.width() / 2.f;
+ SkStrokeRec stroke = GrTest::TestStrokeRec(random);
+ SkScalar blurRadius = random->nextSScalar1() * 72.f;
+ GrDrawBatch* batch = ShadowCircleBatch::Create(color, viewMatrix, center, radius,
+ blurRadius, GrStyle(stroke, nullptr));
+ if (batch) {
+ return batch;
+ }
+ } while (true);
+}
+
+DRAW_BATCH_TEST_DEFINE(ShadowRRectBatch) {
+ SkMatrix viewMatrix = GrTest::TestMatrixRectStaysRect(random);
+ GrColor color = GrRandomColor(random);
+ const SkRRect& rrect = GrTest::TestRRectSimple(random);
+ SkScalar blurRadius = random->nextSScalar1() * 72.f;
+ return create_shadow_rrect_batch(color, viewMatrix, rrect,
+ blurRadius, GrTest::TestStrokeRec(random));
+}
+
+#endif