* 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/GrShadowRRectOp.h"
#include "include/private/GrRecordingContext.h"
#include "src/core/SkRRectPriv.h"
#include "src/gpu/GrDrawOpTest.h"
#include "src/gpu/GrMemoryPool.h"
#include "src/gpu/GrOpFlushState.h"
#include "src/gpu/GrRecordingContextPriv.h"
#include "src/gpu/effects/GrShadowGeoProc.h"
static const uint16_t gFillCircleIndices[] = {
0, 1, 8, 1, 2, 8,
2, 3, 8, 3, 4, 8,
4, 5, 8, 5, 6, 8,
6, 7, 8, 7, 0, 8,
};
static const uint16_t gStrokeCircleIndices[] = {
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;
}
static const uint16_t gRRectIndices[] = {
0, 6, 25, 0, 25, 24,
6, 18, 27, 6, 27, 25,
18, 12, 26, 18, 26, 27,
12, 0, 24, 12, 24, 26,
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,
0, 5, 11, 0, 11, 6,
6, 7, 19, 6, 19, 18,
18, 23, 17, 18, 17, 12,
12, 13, 1, 12, 1, 0,
0, 6, 18, 0, 18, 12,
};
static const int kIndicesPerOverstrokeRRect = SK_ARRAY_COUNT(gRRectIndices) - 6;
static const int kIndicesPerStrokeRRect = kIndicesPerOverstrokeRRect - 6*4;
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,
};
static 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");
}
static 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");
}
static 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");
}
namespace {
class ShadowCircularRRectOp final : public GrMeshDrawOp {
public:
DEFINE_OP_CLASS_ID
ShadowCircularRRectOp(GrColor color, const SkRect& devRect,
float devRadius, bool isCircle, float blurRadius, float insetWidth)
: INHERITED(ClassID()) {
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 = SkTMax(outerRadius, blurRadius);
}
if (isCircle) {
innerRadius = devRadius - insetWidth;
type = innerRadius > 0 ? kStroke_RRectType : kFill_RRectType;
} else {
if (insetWidth <= 0.5f*SkTMin(devRect.width(), devRect.height())) {
innerRadius = SkTMax(insetWidth - umbraInset, 0.0f);
type = innerRadius > 0 ? kOverstroke_RRectType : kStroke_RRectType;
}
}
this->setBounds(bounds, HasAABloat::kNo, IsZeroArea::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"; }
#ifdef SK_DEBUG
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, 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);
}
string.append(INHERITED::dumpInfo());
return string;
}
#endif
FixedFunctionFlags fixedFunctionFlags() const override { return FixedFunctionFlags::kNone; }
GrProcessorSet::Analysis finalize(const GrCaps&, const GrAppliedClip*,
bool hasMixedSampledCoverage, 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;
innerRadius = innerRadius / outerRadius;
SkPoint center = SkPoint::Make(bounds.centerX(), bounds.centerY());
SkScalar halfWidth = 0.5f * bounds.width();
SkScalar octOffset = 0.41421356237f;
(*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) {
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 {
(*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*SkTMin(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;
SkVector outerVec = SkVector::Make(outerRadius - umbraInset, -outerRadius - umbraInset);
outerVec.normalize();
SkScalar diagVal = umbraInset / (SK_ScalarSqrt2*(outerRadius - umbraInset) - outerRadius);
SkVector diagVec = SkVector::Make(diagVal, diagVal);
SkScalar distanceCorrection = umbraInset / blurRadius;
for (int i = 0; i < 4; ++i) {
(*verts)->fPos = SkPoint::Make(xInner[i], yInner[i]);
(*verts)->fColor = color;
(*verts)->fOffset = SkVector::Make(0, 0);
(*verts)->fDistanceCorrection = distanceCorrection;
(*verts)++;
(*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)++;
}
if (kOverstroke_RRectType == args.fType) {
SkASSERT(args.fInnerRadius > 0.0f);
SkScalar inset = umbraInset + args.fInnerRadius;
(*verts)->fPos = SkPoint::Make(bounds.fLeft + inset, bounds.fTop + inset);
(*verts)->fColor = color;
(*verts)->fOffset = SkPoint::Make(0, 0);
(*verts)->fDistanceCorrection = distanceCorrection;
(*verts)++;
(*verts)->fPos = SkPoint::Make(bounds.fRight - inset, bounds.fTop + inset);
(*verts)->fColor = color;
(*verts)->fOffset = SkPoint::Make(0, 0);
(*verts)->fDistanceCorrection = distanceCorrection;
(*verts)++;
(*verts)->fPos = SkPoint::Make(bounds.fLeft + inset, bounds.fBottom - inset);
(*verts)->fColor = color;
(*verts)->fOffset = SkPoint::Make(0, 0);
(*verts)->fDistanceCorrection = distanceCorrection;
(*verts)++;
(*verts)->fPos = SkPoint::Make(bounds.fRight - inset, bounds.fBottom - inset);
(*verts)->fColor = color;
(*verts)->fOffset = SkPoint::Make(0, 0);
(*verts)->fDistanceCorrection = distanceCorrection;
(*verts)++;
}
}
void onPrepareDraws(Target* target) override {
sk_sp<GrGeometryProcessor> gp = GrRRectShadowGeoProc::Make();
int instanceCount = fGeoData.count();
SkASSERT(sizeof(CircleVertex) == gp->vertexStride());
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 i = 0; i < primIndexCount; ++i) {
*indices++ = primIndices[i] + 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 i = 0; i < primIndexCount; ++i) {
*indices++ = primIndices[i] + currStartVertex;
}
currStartVertex += rrect_type_to_vert_count(args.fType);
}
}
GrMesh* mesh = target->allocMesh(GrPrimitiveType::kTriangles);
mesh->setIndexed(std::move(indexBuffer), fIndexCount, firstIndex, 0, fVertCount - 1,
GrPrimitiveRestart::kNo);
mesh->setVertexData(std::move(vertexBuffer), firstVertex);
target->recordDraw(std::move(gp), mesh);
}
void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override {
flushState->executeDrawsAndUploadsForMeshDrawOp(
this, chainBounds, GrProcessorSet::MakeEmptySet());
}
CombineResult onCombineIfPossible(GrOp* t, 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;
}
SkSTArray<1, Geometry, true> fGeoData;
int fVertCount;
int fIndexCount;
typedef GrMeshDrawOp INHERITED;
};
}
namespace GrShadowRRectOp {
std::unique_ptr<GrDrawOp> Make(GrRecordingContext* context,
GrColor color,
const SkMatrix& viewMatrix,
const SkRRect& rrect,
SkScalar blurWidth,
SkScalar insetWidth) {
SkASSERT(viewMatrix.isSimilarity() && SkRRectPriv::EqualRadii(rrect));
const SkRect& rrectBounds = rrect.getBounds();
SkRect bounds;
viewMatrix.mapRect(&bounds, rrectBounds);
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;
}
GrOpMemoryPool* pool = context->priv().opMemoryPool();
return pool->allocate<ShadowCircularRRectOp>(color, bounds,
scaledRadius,
rrect.isOval(),
blurWidth,
scaledInsetWidth);
}
}
#if GR_TEST_UTILS
GR_DRAW_OP_TEST_DEFINE(ShadowRRectOp) {
SkScalar rotate = random->nextSScalar1() * 360.f;
SkScalar translateX = random->nextSScalar1() * 1000.f;
SkScalar translateY = random->nextSScalar1() * 1000.f;
SkScalar scale;
do {
scale = random->nextSScalar1() * 100.f;
} while (scale == 0);
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();
GrColor color = paint.getColor4f().toBytes_RGBA();
if (isCircle) {
SkRect circle = GrTest::TestSquare(random);
SkRRect rrect = SkRRect::MakeOval(circle);
return GrShadowRRectOp::Make(context, color, viewMatrix, rrect, blurWidth, insetWidth);
} else {
SkRRect rrect;
do {
rrect = GrTest::TestRRectSimple(random);
} while (!SkRRectPriv::IsSimpleCircular(rrect));
return GrShadowRRectOp::Make(context, color, viewMatrix, rrect, blurWidth, insetWidth);
}
}
#endif