diff options
author | 2016-11-17 15:27:09 -0500 | |
---|---|---|
committer | 2016-11-21 21:44:20 +0000 | |
commit | c59034145862bf6dc0c503cb1e47eecd321ffa8c (patch) | |
tree | 2e9be313f7221dfe66c9777280be809d75c5a025 /src/gpu/batches/GrShadowRRectBatch.cpp | |
parent | 71de072f99a0a0e83d4c06420d870af2824a9d2d (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-x | src/gpu/batches/GrShadowRRectBatch.cpp | 964 |
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(¢er, 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 |