Move three Op-derived classes to the skgpu::v1 namespace
GrRegionOp
GrShadowRRectOp
GrStrokeRectOp
Bug: skia:11837
Change-Id: I0b573031acb13138e1549712c555337247e0a08e
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/442836
Reviewed-by: Michael Ludwig <michaelludwig@google.com>
Commit-Queue: Robert Phillips <robertphillips@google.com>
diff --git a/src/gpu/ops/ShadowRRectOp.cpp b/src/gpu/ops/ShadowRRectOp.cpp
new file mode 100644
index 0000000..fbaf2f3
--- /dev/null
+++ b/src/gpu/ops/ShadowRRectOp.cpp
@@ -0,0 +1,792 @@
+/*
+ * 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 "src/gpu/ops/ShadowRRectOp.h"
+
+#include "include/gpu/GrRecordingContext.h"
+#include "src/core/SkRRectPriv.h"
+#include "src/gpu/GrMemoryPool.h"
+#include "src/gpu/GrOpFlushState.h"
+#include "src/gpu/GrProgramInfo.h"
+#include "src/gpu/GrProxyProvider.h"
+#include "src/gpu/GrRecordingContextPriv.h"
+#include "src/gpu/GrThreadSafeCache.h"
+#include "src/gpu/SkGr.h"
+#include "src/gpu/effects/GrShadowGeoProc.h"
+#include "src/gpu/ops/GrSimpleMeshDrawOpHelper.h"
+
+namespace {
+
+///////////////////////////////////////////////////////////////////////////////
+// Circle Data
+//
+// 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
+ // clang-format off
+ 0, 1, 8, 1, 2, 8,
+ 2, 3, 8, 3, 4, 8,
+ 4, 5, 8, 5, 6, 8,
+ 6, 7, 8, 7, 0, 8,
+ // clang-format on
+};
+
+// For stroked circles, we use two nested octagons.
+static const uint16_t gStrokeCircleIndices[] = {
+ // enter the octagon
+ // clang-format off
+ 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,
+ // clang-format on
+};
+
+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;
+
+int circle_type_to_vert_count(bool stroked) {
+ return stroked ? kVertsPerStrokeCircle : kVertsPerFillCircle;
+}
+
+int circle_type_to_index_count(bool stroked) {
+ return stroked ? kIndicesPerStrokeCircle : kIndicesPerFillCircle;
+}
+
+const uint16_t* circle_type_to_indices(bool stroked) {
+ return stroked ? gStrokeCircleIndices : gFillCircleIndices;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// RoundRect Data
+//
+// The geometry for a shadow roundrect is similar to a 9-patch:
+// ____________
+// |_|________|_|
+// | | | |
+// | | | |
+// | | | |
+// |_|________|_|
+// |_|________|_|
+//
+// However, each corner is rendered as a fan rather than a simple quad, as below. (The diagram
+// shows the upper part of the upper left corner. The bottom triangle would similarly be split
+// into two triangles.)
+// ________
+// |\ \ |
+// | \ \ |
+// | \\ |
+// | \|
+// --------
+//
+// The center of the fan handles the curve of the corner. For roundrects where the stroke width
+// is greater than the corner radius, the outer triangles blend from the curve to the straight
+// sides. Otherwise these triangles will be degenerate.
+//
+// In the case where the stroke width is greater than the corner radius and the
+// blur radius (overstroke), we add additional geometry to mark out the rectangle in the center.
+// This rectangle extends the coverage values of the center edges of the 9-patch.
+// ____________
+// |_|________|_|
+// | |\ ____ /| |
+// | | | | | |
+// | | |____| | |
+// |_|/______\|_|
+// |_|________|_|
+//
+// For filled rrects we reuse the stroke geometry but add an additional quad to the center.
+
+static const uint16_t gRRectIndices[] = {
+ // clang-format off
+ // overstroke quads
+ // we place this at the beginning so that we can skip these indices when rendering as filled
+ 0, 6, 25, 0, 25, 24,
+ 6, 18, 27, 6, 27, 25,
+ 18, 12, 26, 18, 26, 27,
+ 12, 0, 24, 12, 24, 26,
+
+ // corners
+ 0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 4, 5,
+ 6, 11, 10, 6, 10, 9, 6, 9, 8, 6, 8, 7,
+ 12, 17, 16, 12, 16, 15, 12, 15, 14, 12, 14, 13,
+ 18, 19, 20, 18, 20, 21, 18, 21, 22, 18, 22, 23,
+
+ // edges
+ 0, 5, 11, 0, 11, 6,
+ 6, 7, 19, 6, 19, 18,
+ 18, 23, 17, 18, 17, 12,
+ 12, 13, 1, 12, 1, 0,
+
+ // fill quad
+ // we place this at the end so that we can skip these indices when rendering as stroked
+ 0, 6, 18, 0, 18, 12,
+ // clang-format on
+};
+
+// overstroke count
+static const int kIndicesPerOverstrokeRRect = SK_ARRAY_COUNT(gRRectIndices) - 6;
+// simple stroke count skips overstroke indices
+static const int kIndicesPerStrokeRRect = kIndicesPerOverstrokeRRect - 6*4;
+// fill count adds final quad to stroke count
+static const int kIndicesPerFillRRect = kIndicesPerStrokeRRect + 6;
+static const int kVertsPerStrokeRRect = 24;
+static const int kVertsPerOverstrokeRRect = 28;
+static const int kVertsPerFillRRect = 24;
+
+enum RRectType {
+ kFill_RRectType,
+ kStroke_RRectType,
+ kOverstroke_RRectType,
+};
+
+int rrect_type_to_vert_count(RRectType type) {
+ switch (type) {
+ case kFill_RRectType:
+ return kVertsPerFillRRect;
+ case kStroke_RRectType:
+ return kVertsPerStrokeRRect;
+ case kOverstroke_RRectType:
+ return kVertsPerOverstrokeRRect;
+ }
+ SK_ABORT("Invalid type");
+}
+
+int rrect_type_to_index_count(RRectType type) {
+ switch (type) {
+ case kFill_RRectType:
+ return kIndicesPerFillRRect;
+ case kStroke_RRectType:
+ return kIndicesPerStrokeRRect;
+ case kOverstroke_RRectType:
+ return kIndicesPerOverstrokeRRect;
+ }
+ SK_ABORT("Invalid type");
+}
+
+const uint16_t* rrect_type_to_indices(RRectType type) {
+ switch (type) {
+ case kFill_RRectType:
+ case kStroke_RRectType:
+ return gRRectIndices + 6*4;
+ case kOverstroke_RRectType:
+ return gRRectIndices;
+ }
+ SK_ABORT("Invalid type");
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+class ShadowCircularRRectOp final : public GrMeshDrawOp {
+public:
+ DEFINE_OP_CLASS_ID
+
+ // An insetWidth > 1/2 rect width or height indicates a simple fill.
+ ShadowCircularRRectOp(GrColor color, const SkRect& devRect,
+ float devRadius, bool isCircle, float blurRadius, float insetWidth,
+ GrSurfaceProxyView falloffView)
+ : INHERITED(ClassID())
+ , fFalloffView(std::move(falloffView)) {
+ SkRect bounds = devRect;
+ SkASSERT(insetWidth > 0);
+ SkScalar innerRadius = 0.0f;
+ SkScalar outerRadius = devRadius;
+ SkScalar umbraInset;
+
+ RRectType type = kFill_RRectType;
+ if (isCircle) {
+ umbraInset = 0;
+ } else {
+ umbraInset = std::max(outerRadius, blurRadius);
+ }
+
+ // If stroke is greater than width or height, this is still a fill,
+ // otherwise we compute stroke params.
+ if (isCircle) {
+ innerRadius = devRadius - insetWidth;
+ type = innerRadius > 0 ? kStroke_RRectType : kFill_RRectType;
+ } else {
+ if (insetWidth <= 0.5f*std::min(devRect.width(), devRect.height())) {
+ // We don't worry about a real inner radius, we just need to know if we
+ // need to create overstroke vertices.
+ innerRadius = std::max(insetWidth - umbraInset, 0.0f);
+ type = innerRadius > 0 ? kOverstroke_RRectType : kStroke_RRectType;
+ }
+ }
+
+ this->setBounds(bounds, HasAABloat::kNo, IsHairline::kNo);
+
+ fGeoData.emplace_back(Geometry{color, outerRadius, umbraInset, innerRadius,
+ blurRadius, bounds, type, isCircle});
+ if (isCircle) {
+ fVertCount = circle_type_to_vert_count(kStroke_RRectType == type);
+ fIndexCount = circle_type_to_index_count(kStroke_RRectType == type);
+ } else {
+ fVertCount = rrect_type_to_vert_count(type);
+ fIndexCount = rrect_type_to_index_count(type);
+ }
+ }
+
+ const char* name() const override { return "ShadowCircularRRectOp"; }
+
+ FixedFunctionFlags fixedFunctionFlags() const override { return FixedFunctionFlags::kNone; }
+
+ GrProcessorSet::Analysis finalize(const GrCaps&, const GrAppliedClip*, GrClampType) override {
+ return GrProcessorSet::EmptySetAnalysis();
+ }
+
+private:
+ struct Geometry {
+ GrColor fColor;
+ SkScalar fOuterRadius;
+ SkScalar fUmbraInset;
+ SkScalar fInnerRadius;
+ SkScalar fBlurRadius;
+ SkRect fDevBounds;
+ RRectType fType;
+ bool fIsCircle;
+ };
+
+ struct CircleVertex {
+ SkPoint fPos;
+ GrColor fColor;
+ SkPoint fOffset;
+ SkScalar fDistanceCorrection;
+ };
+
+ void fillInCircleVerts(const Geometry& args, bool isStroked, CircleVertex** verts) const {
+
+ GrColor color = args.fColor;
+ SkScalar outerRadius = args.fOuterRadius;
+ SkScalar innerRadius = args.fInnerRadius;
+ SkScalar blurRadius = args.fBlurRadius;
+ SkScalar distanceCorrection = outerRadius / blurRadius;
+
+ const SkRect& bounds = args.fDevBounds;
+
+ // 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
+
+ (*verts)->fPos = center + SkPoint::Make(-octOffset * halfWidth, -halfWidth);
+ (*verts)->fColor = color;
+ (*verts)->fOffset = SkPoint::Make(-octOffset, -1);
+ (*verts)->fDistanceCorrection = distanceCorrection;
+ (*verts)++;
+
+ (*verts)->fPos = center + SkPoint::Make(octOffset * halfWidth, -halfWidth);
+ (*verts)->fColor = color;
+ (*verts)->fOffset = SkPoint::Make(octOffset, -1);
+ (*verts)->fDistanceCorrection = distanceCorrection;
+ (*verts)++;
+
+ (*verts)->fPos = center + SkPoint::Make(halfWidth, -octOffset * halfWidth);
+ (*verts)->fColor = color;
+ (*verts)->fOffset = SkPoint::Make(1, -octOffset);
+ (*verts)->fDistanceCorrection = distanceCorrection;
+ (*verts)++;
+
+ (*verts)->fPos = center + SkPoint::Make(halfWidth, octOffset * halfWidth);
+ (*verts)->fColor = color;
+ (*verts)->fOffset = SkPoint::Make(1, octOffset);
+ (*verts)->fDistanceCorrection = distanceCorrection;
+ (*verts)++;
+
+ (*verts)->fPos = center + SkPoint::Make(octOffset * halfWidth, halfWidth);
+ (*verts)->fColor = color;
+ (*verts)->fOffset = SkPoint::Make(octOffset, 1);
+ (*verts)->fDistanceCorrection = distanceCorrection;
+ (*verts)++;
+
+ (*verts)->fPos = center + SkPoint::Make(-octOffset * halfWidth, halfWidth);
+ (*verts)->fColor = color;
+ (*verts)->fOffset = SkPoint::Make(-octOffset, 1);
+ (*verts)->fDistanceCorrection = distanceCorrection;
+ (*verts)++;
+
+ (*verts)->fPos = center + SkPoint::Make(-halfWidth, octOffset * halfWidth);
+ (*verts)->fColor = color;
+ (*verts)->fOffset = SkPoint::Make(-1, octOffset);
+ (*verts)->fDistanceCorrection = distanceCorrection;
+ (*verts)++;
+
+ (*verts)->fPos = center + SkPoint::Make(-halfWidth, -octOffset * halfWidth);
+ (*verts)->fColor = color;
+ (*verts)->fOffset = SkPoint::Make(-1, -octOffset);
+ (*verts)->fDistanceCorrection = distanceCorrection;
+ (*verts)++;
+
+ if (isStroked) {
+ // compute the inner ring
+
+ // cosine and sine of pi/8
+ SkScalar c = 0.923579533f;
+ SkScalar s = 0.382683432f;
+ SkScalar r = args.fInnerRadius;
+
+ (*verts)->fPos = center + SkPoint::Make(-s * r, -c * r);
+ (*verts)->fColor = color;
+ (*verts)->fOffset = SkPoint::Make(-s * innerRadius, -c * innerRadius);
+ (*verts)->fDistanceCorrection = distanceCorrection;
+ (*verts)++;
+
+ (*verts)->fPos = center + SkPoint::Make(s * r, -c * r);
+ (*verts)->fColor = color;
+ (*verts)->fOffset = SkPoint::Make(s * innerRadius, -c * innerRadius);
+ (*verts)->fDistanceCorrection = distanceCorrection;
+ (*verts)++;
+
+ (*verts)->fPos = center + SkPoint::Make(c * r, -s * r);
+ (*verts)->fColor = color;
+ (*verts)->fOffset = SkPoint::Make(c * innerRadius, -s * innerRadius);
+ (*verts)->fDistanceCorrection = distanceCorrection;
+ (*verts)++;
+
+ (*verts)->fPos = center + SkPoint::Make(c * r, s * r);
+ (*verts)->fColor = color;
+ (*verts)->fOffset = SkPoint::Make(c * innerRadius, s * innerRadius);
+ (*verts)->fDistanceCorrection = distanceCorrection;
+ (*verts)++;
+
+ (*verts)->fPos = center + SkPoint::Make(s * r, c * r);
+ (*verts)->fColor = color;
+ (*verts)->fOffset = SkPoint::Make(s * innerRadius, c * innerRadius);
+ (*verts)->fDistanceCorrection = distanceCorrection;
+ (*verts)++;
+
+ (*verts)->fPos = center + SkPoint::Make(-s * r, c * r);
+ (*verts)->fColor = color;
+ (*verts)->fOffset = SkPoint::Make(-s * innerRadius, c * innerRadius);
+ (*verts)->fDistanceCorrection = distanceCorrection;
+ (*verts)++;
+
+ (*verts)->fPos = center + SkPoint::Make(-c * r, s * r);
+ (*verts)->fColor = color;
+ (*verts)->fOffset = SkPoint::Make(-c * innerRadius, s * innerRadius);
+ (*verts)->fDistanceCorrection = distanceCorrection;
+ (*verts)++;
+
+ (*verts)->fPos = center + SkPoint::Make(-c * r, -s * r);
+ (*verts)->fColor = color;
+ (*verts)->fOffset = SkPoint::Make(-c * innerRadius, -s * innerRadius);
+ (*verts)->fDistanceCorrection = distanceCorrection;
+ (*verts)++;
+ } else {
+ // filled
+ (*verts)->fPos = center;
+ (*verts)->fColor = color;
+ (*verts)->fOffset = SkPoint::Make(0, 0);
+ (*verts)->fDistanceCorrection = distanceCorrection;
+ (*verts)++;
+ }
+ }
+
+ void fillInRRectVerts(const Geometry& args, CircleVertex** verts) const {
+ GrColor color = args.fColor;
+ SkScalar outerRadius = args.fOuterRadius;
+
+ const SkRect& bounds = args.fDevBounds;
+
+ SkScalar umbraInset = args.fUmbraInset;
+ SkScalar minDim = 0.5f*std::min(bounds.width(), bounds.height());
+ if (umbraInset > minDim) {
+ umbraInset = minDim;
+ }
+
+ SkScalar xInner[4] = { bounds.fLeft + umbraInset, bounds.fRight - umbraInset,
+ bounds.fLeft + umbraInset, bounds.fRight - umbraInset };
+ SkScalar xMid[4] = { bounds.fLeft + outerRadius, bounds.fRight - outerRadius,
+ bounds.fLeft + outerRadius, bounds.fRight - outerRadius };
+ SkScalar xOuter[4] = { bounds.fLeft, bounds.fRight,
+ bounds.fLeft, bounds.fRight };
+ SkScalar yInner[4] = { bounds.fTop + umbraInset, bounds.fTop + umbraInset,
+ bounds.fBottom - umbraInset, bounds.fBottom - umbraInset };
+ SkScalar yMid[4] = { bounds.fTop + outerRadius, bounds.fTop + outerRadius,
+ bounds.fBottom - outerRadius, bounds.fBottom - outerRadius };
+ SkScalar yOuter[4] = { bounds.fTop, bounds.fTop,
+ bounds.fBottom, bounds.fBottom };
+
+ SkScalar blurRadius = args.fBlurRadius;
+
+ // In the case where we have to inset more for the umbra, our two triangles in the
+ // corner get skewed to a diamond rather than a square. To correct for that,
+ // we also skew the vectors we send to the shader that help define the circle.
+ // By doing so, we end up with a quarter circle in the corner rather than the
+ // elliptical curve.
+
+ // This is a bit magical, but it gives us the correct results at extrema:
+ // a) umbraInset == outerRadius produces an orthogonal vector
+ // b) outerRadius == 0 produces a diagonal vector
+ // And visually the corner looks correct.
+ SkVector outerVec = SkVector::Make(outerRadius - umbraInset, -outerRadius - umbraInset);
+ outerVec.normalize();
+ // We want the circle edge to fall fractionally along the diagonal at
+ // (sqrt(2)*(umbraInset - outerRadius) + outerRadius)/sqrt(2)*umbraInset
+ //
+ // Setting the components of the diagonal offset to the following value will give us that.
+ SkScalar diagVal = umbraInset / (SK_ScalarSqrt2*(outerRadius - umbraInset) - outerRadius);
+ SkVector diagVec = SkVector::Make(diagVal, diagVal);
+ SkScalar distanceCorrection = umbraInset / blurRadius;
+
+ // build corner by corner
+ for (int i = 0; i < 4; ++i) {
+ // inner point
+ (*verts)->fPos = SkPoint::Make(xInner[i], yInner[i]);
+ (*verts)->fColor = color;
+ (*verts)->fOffset = SkVector::Make(0, 0);
+ (*verts)->fDistanceCorrection = distanceCorrection;
+ (*verts)++;
+
+ // outer points
+ (*verts)->fPos = SkPoint::Make(xOuter[i], yInner[i]);
+ (*verts)->fColor = color;
+ (*verts)->fOffset = SkVector::Make(0, -1);
+ (*verts)->fDistanceCorrection = distanceCorrection;
+ (*verts)++;
+
+ (*verts)->fPos = SkPoint::Make(xOuter[i], yMid[i]);
+ (*verts)->fColor = color;
+ (*verts)->fOffset = outerVec;
+ (*verts)->fDistanceCorrection = distanceCorrection;
+ (*verts)++;
+
+ (*verts)->fPos = SkPoint::Make(xOuter[i], yOuter[i]);
+ (*verts)->fColor = color;
+ (*verts)->fOffset = diagVec;
+ (*verts)->fDistanceCorrection = distanceCorrection;
+ (*verts)++;
+
+ (*verts)->fPos = SkPoint::Make(xMid[i], yOuter[i]);
+ (*verts)->fColor = color;
+ (*verts)->fOffset = outerVec;
+ (*verts)->fDistanceCorrection = distanceCorrection;
+ (*verts)++;
+
+ (*verts)->fPos = SkPoint::Make(xInner[i], yOuter[i]);
+ (*verts)->fColor = color;
+ (*verts)->fOffset = SkVector::Make(0, -1);
+ (*verts)->fDistanceCorrection = distanceCorrection;
+ (*verts)++;
+ }
+
+ // Add the additional vertices for overstroked rrects.
+ // Effectively this is an additional stroked rrect, with its
+ // parameters equal to those in the center of the 9-patch. This will
+ // give constant values across this inner ring.
+ if (kOverstroke_RRectType == args.fType) {
+ SkASSERT(args.fInnerRadius > 0.0f);
+
+ SkScalar inset = umbraInset + args.fInnerRadius;
+
+ // TL
+ (*verts)->fPos = SkPoint::Make(bounds.fLeft + inset, bounds.fTop + inset);
+ (*verts)->fColor = color;
+ (*verts)->fOffset = SkPoint::Make(0, 0);
+ (*verts)->fDistanceCorrection = distanceCorrection;
+ (*verts)++;
+
+ // TR
+ (*verts)->fPos = SkPoint::Make(bounds.fRight - inset, bounds.fTop + inset);
+ (*verts)->fColor = color;
+ (*verts)->fOffset = SkPoint::Make(0, 0);
+ (*verts)->fDistanceCorrection = distanceCorrection;
+ (*verts)++;
+
+ // BL
+ (*verts)->fPos = SkPoint::Make(bounds.fLeft + inset, bounds.fBottom - inset);
+ (*verts)->fColor = color;
+ (*verts)->fOffset = SkPoint::Make(0, 0);
+ (*verts)->fDistanceCorrection = distanceCorrection;
+ (*verts)++;
+
+ // BR
+ (*verts)->fPos = SkPoint::Make(bounds.fRight - inset, bounds.fBottom - inset);
+ (*verts)->fColor = color;
+ (*verts)->fOffset = SkPoint::Make(0, 0);
+ (*verts)->fDistanceCorrection = distanceCorrection;
+ (*verts)++;
+ }
+
+ }
+
+ GrProgramInfo* programInfo() override { return fProgramInfo; }
+
+ void onCreateProgramInfo(const GrCaps* caps,
+ SkArenaAlloc* arena,
+ const GrSurfaceProxyView& writeView,
+ bool usesMSAASurface,
+ GrAppliedClip&& appliedClip,
+ const GrDstProxyView& dstProxyView,
+ GrXferBarrierFlags renderPassXferBarriers,
+ GrLoadOp colorLoadOp) override {
+ GrGeometryProcessor* gp = GrRRectShadowGeoProc::Make(arena, fFalloffView);
+ SkASSERT(sizeof(CircleVertex) == gp->vertexStride());
+
+ fProgramInfo = GrSimpleMeshDrawOpHelper::CreateProgramInfo(caps, arena, writeView,
+ usesMSAASurface,
+ std::move(appliedClip),
+ dstProxyView, gp,
+ GrProcessorSet::MakeEmptySet(),
+ GrPrimitiveType::kTriangles,
+ renderPassXferBarriers,
+ colorLoadOp,
+ GrPipeline::InputFlags::kNone,
+ &GrUserStencilSettings::kUnused);
+ }
+
+ void onPrepareDraws(GrMeshDrawTarget* target) override {
+ int instanceCount = fGeoData.count();
+
+ sk_sp<const GrBuffer> vertexBuffer;
+ int firstVertex;
+ CircleVertex* verts = (CircleVertex*)target->makeVertexSpace(
+ sizeof(CircleVertex), fVertCount, &vertexBuffer, &firstVertex);
+ if (!verts) {
+ SkDebugf("Could not allocate vertices\n");
+ return;
+ }
+
+ sk_sp<const GrBuffer> indexBuffer;
+ 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];
+
+ if (args.fIsCircle) {
+ bool isStroked = SkToBool(kStroke_RRectType == args.fType);
+ this->fillInCircleVerts(args, isStroked, &verts);
+
+ const uint16_t* primIndices = circle_type_to_indices(isStroked);
+ const int primIndexCount = circle_type_to_index_count(isStroked);
+ for (int j = 0; j < primIndexCount; ++j) {
+ *indices++ = primIndices[j] + currStartVertex;
+ }
+
+ currStartVertex += circle_type_to_vert_count(isStroked);
+
+ } else {
+ this->fillInRRectVerts(args, &verts);
+
+ const uint16_t* primIndices = rrect_type_to_indices(args.fType);
+ const int primIndexCount = rrect_type_to_index_count(args.fType);
+ for (int j = 0; j < primIndexCount; ++j) {
+ *indices++ = primIndices[j] + currStartVertex;
+ }
+
+ currStartVertex += rrect_type_to_vert_count(args.fType);
+ }
+ }
+
+ fMesh = target->allocMesh();
+ fMesh->setIndexed(std::move(indexBuffer), fIndexCount, firstIndex, 0, fVertCount - 1,
+ GrPrimitiveRestart::kNo, std::move(vertexBuffer), firstVertex);
+ }
+
+ void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override {
+ if (!fProgramInfo) {
+ this->createProgramInfo(flushState);
+ }
+
+ if (!fProgramInfo || !fMesh) {
+ return;
+ }
+
+ flushState->bindPipelineAndScissorClip(*fProgramInfo, chainBounds);
+ flushState->bindTextures(fProgramInfo->geomProc(), *fFalloffView.proxy(),
+ fProgramInfo->pipeline());
+ flushState->drawMesh(*fMesh);
+ }
+
+ CombineResult onCombineIfPossible(GrOp* t, SkArenaAlloc*, const GrCaps& caps) override {
+ ShadowCircularRRectOp* that = t->cast<ShadowCircularRRectOp>();
+ fGeoData.push_back_n(that->fGeoData.count(), that->fGeoData.begin());
+ fVertCount += that->fVertCount;
+ fIndexCount += that->fIndexCount;
+ return CombineResult::kMerged;
+ }
+
+#if GR_TEST_UTILS
+ SkString onDumpInfo() 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, Umbra: %.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].fUmbraInset,
+ fGeoData[i].fInnerRadius, fGeoData[i].fBlurRadius);
+ }
+ return string;
+ }
+#endif
+
+ void visitProxies(const GrVisitProxyFunc& func) const override {
+ func(fFalloffView.proxy(), GrMipmapped(false));
+ if (fProgramInfo) {
+ fProgramInfo->visitFPProxies(func);
+ }
+ }
+
+ SkSTArray<1, Geometry, true> fGeoData;
+ int fVertCount;
+ int fIndexCount;
+ GrSurfaceProxyView fFalloffView;
+
+ GrSimpleMesh* fMesh = nullptr;
+ GrProgramInfo* fProgramInfo = nullptr;
+
+ using INHERITED = GrMeshDrawOp;
+};
+
+} // anonymous namespace
+
+///////////////////////////////////////////////////////////////////////////////
+
+namespace skgpu::v1::ShadowRRectOp {
+
+static GrSurfaceProxyView create_falloff_texture(GrRecordingContext* rContext) {
+ static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain();
+ GrUniqueKey key;
+ GrUniqueKey::Builder builder(&key, kDomain, 0, "Shadow Gaussian Falloff");
+ builder.finish();
+
+ auto threadSafeCache = rContext->priv().threadSafeCache();
+
+ GrSurfaceProxyView view = threadSafeCache->find(key);
+ if (view) {
+ SkASSERT(view.origin() == kTopLeft_GrSurfaceOrigin);
+ return view;
+ }
+
+ static const int kWidth = 128;
+ static const size_t kRowBytes = kWidth * GrColorTypeBytesPerPixel(GrColorType::kAlpha_8);
+ SkImageInfo ii = SkImageInfo::MakeA8(kWidth, 1);
+
+ SkBitmap bitmap;
+ bitmap.allocPixels(ii, kRowBytes);
+
+ unsigned char* values = (unsigned char*)bitmap.getPixels();
+ for (int i = 0; i < 128; ++i) {
+ SkScalar d = SK_Scalar1 - i / SkIntToScalar(127);
+ values[i] = SkScalarRoundToInt((SkScalarExp(-4 * d * d) - 0.018f) * 255);
+ }
+ bitmap.setImmutable();
+
+ view = std::get<0>(GrMakeUncachedBitmapProxyView(rContext, bitmap));
+ if (!view) {
+ return {};
+ }
+
+ view = threadSafeCache->add(key, view);
+ SkASSERT(view.origin() == kTopLeft_GrSurfaceOrigin);
+ return view;
+}
+
+GrOp::Owner Make(GrRecordingContext* context,
+ GrColor color,
+ const SkMatrix& viewMatrix,
+ const SkRRect& rrect,
+ SkScalar blurWidth,
+ SkScalar insetWidth) {
+ // Shadow rrect ops only handle simple circular rrects.
+ SkASSERT(viewMatrix.isSimilarity() && SkRRectPriv::EqualRadii(rrect));
+
+ GrSurfaceProxyView falloffView = create_falloff_texture(context);
+ if (!falloffView) {
+ return nullptr;
+ }
+
+ // Do any matrix crunching before we reset the draw state for device coords.
+ const SkRect& rrectBounds = rrect.getBounds();
+ SkRect bounds;
+ viewMatrix.mapRect(&bounds, rrectBounds);
+
+ // Map radius and inset. As the matrix is a similarity matrix, this should be isotropic.
+ SkScalar radius = SkRRectPriv::GetSimpleRadii(rrect).fX;
+ SkScalar matrixFactor = viewMatrix[SkMatrix::kMScaleX] + viewMatrix[SkMatrix::kMSkewX];
+ SkScalar scaledRadius = SkScalarAbs(radius*matrixFactor);
+ SkScalar scaledInsetWidth = SkScalarAbs(insetWidth*matrixFactor);
+
+ if (scaledInsetWidth <= 0) {
+ return nullptr;
+ }
+
+ return GrOp::Make<ShadowCircularRRectOp>(context,
+ color,
+ bounds,
+ scaledRadius,
+ rrect.isOval(),
+ blurWidth,
+ scaledInsetWidth,
+ std::move(falloffView));
+}
+
+} // namespace skgpu::v1::ShadowRRectOp
+
+///////////////////////////////////////////////////////////////////////////////
+
+#if GR_TEST_UTILS
+
+#include "src/gpu/GrDrawOpTest.h"
+
+GR_DRAW_OP_TEST_DEFINE(ShadowRRectOp) {
+ // We may choose matrix and inset values that cause the factory to fail. We loop until we find
+ // an acceptable combination.
+ do {
+ // create a similarity matrix
+ 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);
+ SkScalar insetWidth = random->nextSScalar1() * 72.f;
+ SkScalar blurWidth = random->nextSScalar1() * 72.f;
+ bool isCircle = random->nextBool();
+ // This op doesn't use a full GrPaint, just a color.
+ GrColor color = paint.getColor4f().toBytes_RGBA();
+ if (isCircle) {
+ SkRect circle = GrTest::TestSquare(random);
+ SkRRect rrect = SkRRect::MakeOval(circle);
+ if (auto op = skgpu::v1::ShadowRRectOp::Make(
+ context, color, viewMatrix, rrect, blurWidth, insetWidth)) {
+ return op;
+ }
+ } else {
+ SkRRect rrect;
+ do {
+ // This may return a rrect with elliptical corners, which will cause an assert.
+ rrect = GrTest::TestRRectSimple(random);
+ } while (!SkRRectPriv::IsSimpleCircular(rrect));
+ if (auto op = skgpu::v1::ShadowRRectOp::Make(
+ context, color, viewMatrix, rrect, blurWidth, insetWidth)) {
+ return op;
+ }
+ }
+ } while (true);
+}
+
+#endif // GR_TEST_UTILS