Analytic dashing of circles with single on/off intervals and butt caps.
Change-Id: If19ac52cb78af57572a102cec0084f5b6c037680 Reviewed-on: https://skia-review.googlesource.com/121882 Auto-Submit: Brian Salomon <bsalomon@google.com> Reviewed-by: Greg Daniel <egdaniel@google.com> Commit-Queue: Brian Salomon <bsalomon@google.com>
This commit is contained in:
parent
2fda63abcd
commit
62e4f3daf7
@ -108,3 +108,120 @@ private:
|
||||
};
|
||||
|
||||
DEF_GM(return new DashCircleGM; )
|
||||
|
||||
class DashCircle2GM : public skiagm::GM {
|
||||
public:
|
||||
DashCircle2GM() {}
|
||||
|
||||
protected:
|
||||
SkString onShortName() override { return SkString("dashcircle2"); }
|
||||
|
||||
SkISize onISize() override { return SkISize::Make(635, 900); }
|
||||
|
||||
void onDraw(SkCanvas* canvas) override {
|
||||
// These intervals are defined relative to tau.
|
||||
static constexpr SkScalar kIntervals[][2]{
|
||||
{0.333f, 0.333f},
|
||||
{0.015f, 0.015f},
|
||||
{0.01f , 0.09f },
|
||||
{0.097f, 0.003f},
|
||||
{0.02f , 0.04f },
|
||||
{0.1f , 0.2f },
|
||||
{0.25f , 0.25f },
|
||||
{0.6f , 0.7f }, // adds to > 1
|
||||
{1.2f , 0.8f }, // on is > 1
|
||||
{0.1f , 1.1f }, // off is > 1*/
|
||||
};
|
||||
|
||||
static constexpr int kN = SK_ARRAY_COUNT(kIntervals);
|
||||
static constexpr SkScalar kRadius = 20.f;
|
||||
static constexpr SkScalar kStrokeWidth = 15.f;
|
||||
static constexpr SkScalar kPad = 5.f;
|
||||
static constexpr SkRect kCircle = {-kRadius, -kRadius, kRadius, kRadius};
|
||||
|
||||
static constexpr SkScalar kThinRadius = kRadius * 1.5;
|
||||
static constexpr SkRect kThinCircle = {-kThinRadius, -kThinRadius,
|
||||
kThinRadius, kThinRadius};
|
||||
static constexpr SkScalar kThinStrokeWidth = 0.4f;
|
||||
|
||||
sk_sp<SkPathEffect> deffects[SK_ARRAY_COUNT(kIntervals)];
|
||||
sk_sp<SkPathEffect> thinDEffects[SK_ARRAY_COUNT(kIntervals)];
|
||||
for (int i = 0; i < kN; ++i) {
|
||||
static constexpr SkScalar kTau = 2 * SK_ScalarPI;
|
||||
static constexpr SkScalar kCircumference = kRadius * kTau;
|
||||
SkScalar scaledIntervals[2] = {kCircumference * kIntervals[i][0],
|
||||
kCircumference * kIntervals[i][1]};
|
||||
deffects[i] = SkDashPathEffect::Make(
|
||||
scaledIntervals, 2, kCircumference * fPhaseDegrees * kTau / 360.f);
|
||||
static constexpr SkScalar kThinCircumference = kThinRadius * kTau;
|
||||
scaledIntervals[0] = kThinCircumference * kIntervals[i][0];
|
||||
scaledIntervals[1] = kThinCircumference * kIntervals[i][1];
|
||||
thinDEffects[i] = SkDashPathEffect::Make(
|
||||
scaledIntervals, 2, kThinCircumference * fPhaseDegrees * kTau / 360.f);
|
||||
}
|
||||
|
||||
SkMatrix rotate;
|
||||
rotate.setRotate(25.f);
|
||||
static const SkMatrix kMatrices[]{
|
||||
SkMatrix::I(),
|
||||
SkMatrix::MakeScale(1.2f),
|
||||
SkMatrix::MakeAll(1, 0, 0, 0, -1, 0, 0, 0, 1), // y flipper
|
||||
SkMatrix::MakeAll(-1, 0, 0, 0, 1, 0, 0, 0, 1), // x flipper
|
||||
SkMatrix::MakeScale(0.7f),
|
||||
rotate,
|
||||
SkMatrix::Concat(
|
||||
SkMatrix::Concat(SkMatrix::MakeAll(-1, 0, 0, 0, 1, 0, 0, 0, 1), rotate),
|
||||
rotate)
|
||||
};
|
||||
|
||||
SkPaint paint;
|
||||
paint.setAntiAlias(true);
|
||||
paint.setStrokeWidth(kStrokeWidth);
|
||||
paint.setStyle(SkPaint::kStroke_Style);
|
||||
|
||||
// Compute the union of bounds of all of our test cases.
|
||||
SkRect bounds = SkRect::MakeEmpty();
|
||||
static const SkRect kBounds = kThinCircle.makeOutset(kThinStrokeWidth / 2.f,
|
||||
kThinStrokeWidth / 2.f);
|
||||
for (const auto& m : kMatrices) {
|
||||
SkRect devBounds;
|
||||
m.mapRect(&devBounds, kBounds);
|
||||
bounds.join(devBounds);
|
||||
}
|
||||
|
||||
canvas->save();
|
||||
canvas->translate(-bounds.fLeft + kPad, -bounds.fTop + kPad);
|
||||
for (size_t i = 0; i < SK_ARRAY_COUNT(deffects); ++i) {
|
||||
canvas->save();
|
||||
for (const auto& m : kMatrices) {
|
||||
canvas->save();
|
||||
canvas->concat(m);
|
||||
|
||||
paint.setPathEffect(deffects[i]);
|
||||
paint.setStrokeWidth(kStrokeWidth);
|
||||
canvas->drawOval(kCircle, paint);
|
||||
|
||||
paint.setPathEffect(thinDEffects[i]);
|
||||
paint.setStrokeWidth(kThinStrokeWidth);
|
||||
canvas->drawOval(kThinCircle, paint);
|
||||
|
||||
canvas->restore();
|
||||
canvas->translate(bounds.width() + kPad, 0);
|
||||
}
|
||||
canvas->restore();
|
||||
canvas->translate(0, bounds.height() + kPad);
|
||||
}
|
||||
canvas->restore();
|
||||
}
|
||||
|
||||
protected:
|
||||
bool onAnimate(const SkAnimTimer& timer) override {
|
||||
fPhaseDegrees = timer.secs();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Init with a non-zero phase for when run as a non-animating GM.
|
||||
SkScalar fPhaseDegrees = 12.f;
|
||||
};
|
||||
|
||||
DEF_GM(return new DashCircle2GM;)
|
||||
|
@ -68,6 +68,7 @@ public:
|
||||
enum ClassID {
|
||||
kBigKeyProcessor_ClassID,
|
||||
kBlockInputFragmentProcessor_ClassID,
|
||||
kButtCapStrokedCircleGeometryProcessor_ClassID,
|
||||
kCircleGeometryProcessor_ClassID,
|
||||
kCircularRRectEffect_ClassID,
|
||||
kColorMatrixEffect_ClassID,
|
||||
|
@ -1162,7 +1162,8 @@ bool GrRenderTargetContext::drawFilledDRRect(const GrClip& clip,
|
||||
SkStrokeRec stroke(SkStrokeRec::kFill_InitStyle);
|
||||
stroke.setStrokeStyle(outerR - innerR);
|
||||
auto op = GrOvalOpFactory::MakeOvalOp(std::move(paint), viewMatrix, circleBounds,
|
||||
stroke, this->caps()->shaderCaps());
|
||||
GrStyle(stroke, nullptr),
|
||||
this->caps()->shaderCaps());
|
||||
if (op) {
|
||||
this->addDrawOp(clip, std::move(op));
|
||||
return true;
|
||||
@ -1294,21 +1295,17 @@ void GrRenderTargetContext::drawOval(const GrClip& clip,
|
||||
SkDEBUGCODE(this->validate();)
|
||||
GR_CREATE_TRACE_MARKER_CONTEXT("GrRenderTargetContext", "drawOval", fContext);
|
||||
|
||||
if (oval.isEmpty()) {
|
||||
return;
|
||||
if (oval.isEmpty() && !style.pathEffect()) {
|
||||
return;
|
||||
}
|
||||
|
||||
SkASSERT(!style.pathEffect()); // this should've been devolved to a path in SkGpuDevice
|
||||
|
||||
AutoCheckFlush acf(this->drawingManager());
|
||||
const SkStrokeRec& stroke = style.strokeRec();
|
||||
|
||||
GrAAType aaType = this->chooseAAType(aa, GrAllowMixedSamples::kNo);
|
||||
if (GrAAType::kCoverage == aaType) {
|
||||
const GrShaderCaps* shaderCaps = fContext->caps()->shaderCaps();
|
||||
std::unique_ptr<GrDrawOp> op =
|
||||
GrOvalOpFactory::MakeOvalOp(std::move(paint), viewMatrix, oval, stroke, shaderCaps);
|
||||
if (op) {
|
||||
if (auto op = GrOvalOpFactory::MakeOvalOp(std::move(paint), viewMatrix, oval, style,
|
||||
shaderCaps)) {
|
||||
this->addDrawOp(clip, std::move(op));
|
||||
return;
|
||||
}
|
||||
|
@ -509,14 +509,6 @@ void SkGpuDevice::drawRegion(const SkRegion& region, const SkPaint& paint) {
|
||||
void SkGpuDevice::drawOval(const SkRect& oval, const SkPaint& paint) {
|
||||
ASSERT_SINGLE_OWNER
|
||||
GR_CREATE_TRACE_MARKER_CONTEXT("SkGpuDevice", "drawOval", fContext.get());
|
||||
// Presumably the path effect warps this to something other than an oval
|
||||
if (paint.getPathEffect()) {
|
||||
SkPath path;
|
||||
path.setIsVolatile(true);
|
||||
path.addOval(oval);
|
||||
this->drawPath(path, paint, nullptr, true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (paint.getMaskFilter()) {
|
||||
// The RRect path can handle special case blurring
|
||||
|
@ -264,6 +264,240 @@ sk_sp<GrGeometryProcessor> CircleGeometryProcessor::TestCreate(GrProcessorTestDa
|
||||
}
|
||||
#endif
|
||||
|
||||
class ButtCapDashedCircleGeometryProcessor : public GrGeometryProcessor {
|
||||
public:
|
||||
ButtCapDashedCircleGeometryProcessor(const SkMatrix& localMatrix)
|
||||
: INHERITED(kButtCapStrokedCircleGeometryProcessor_ClassID), fLocalMatrix(localMatrix) {
|
||||
fInPosition = &this->addVertexAttrib("inPosition", kFloat2_GrVertexAttribType);
|
||||
fInColor = &this->addVertexAttrib("inColor", kUByte4_norm_GrVertexAttribType);
|
||||
fInCircleEdge = &this->addVertexAttrib("inCircleEdge", kFloat4_GrVertexAttribType);
|
||||
fInDashParams = &this->addVertexAttrib("inDashParams", kFloat4_GrVertexAttribType);
|
||||
}
|
||||
|
||||
~ButtCapDashedCircleGeometryProcessor() override {}
|
||||
|
||||
const char* name() const override { return "ButtCapDashedCircleGeometryProcessor"; }
|
||||
|
||||
void getGLSLProcessorKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const override {
|
||||
GLSLProcessor::GenKey(*this, caps, b);
|
||||
}
|
||||
|
||||
GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const override {
|
||||
return new GLSLProcessor();
|
||||
}
|
||||
|
||||
private:
|
||||
class GLSLProcessor : public GrGLSLGeometryProcessor {
|
||||
public:
|
||||
GLSLProcessor() {}
|
||||
|
||||
void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override {
|
||||
const ButtCapDashedCircleGeometryProcessor& bcscgp =
|
||||
args.fGP.cast<ButtCapDashedCircleGeometryProcessor>();
|
||||
GrGLSLVertexBuilder* vertBuilder = args.fVertBuilder;
|
||||
GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler;
|
||||
GrGLSLUniformHandler* uniformHandler = args.fUniformHandler;
|
||||
GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
|
||||
|
||||
// emit attributes
|
||||
varyingHandler->emitAttributes(bcscgp);
|
||||
fragBuilder->codeAppend("float4 circleEdge;");
|
||||
varyingHandler->addPassThroughAttribute(bcscgp.fInCircleEdge, "circleEdge");
|
||||
|
||||
fragBuilder->codeAppend("float4 dashParams;");
|
||||
varyingHandler->addPassThroughAttribute(
|
||||
bcscgp.fInDashParams, "dashParams",
|
||||
GrGLSLVaryingHandler::Interpolation::kCanBeFlat);
|
||||
GrGLSLVarying wrapDashes(kHalf4_GrSLType);
|
||||
varyingHandler->addVarying("wrapDashes", &wrapDashes,
|
||||
GrGLSLVaryingHandler::Interpolation::kCanBeFlat);
|
||||
GrGLSLVarying lastIntervalLength(kHalf_GrSLType);
|
||||
varyingHandler->addVarying("lastIntervalLength", &lastIntervalLength,
|
||||
GrGLSLVaryingHandler::Interpolation::kCanBeFlat);
|
||||
vertBuilder->codeAppendf("float4 dashParams = %s;", bcscgp.fInDashParams->fName);
|
||||
// Our fragment shader works in on/off intervals as specified by dashParams.xy:
|
||||
// x = length of on interval, y = length of on + off.
|
||||
// There are two other parameters in dashParams.zw:
|
||||
// z = start angle in radians, w = phase offset in radians in range -y/2..y/2.
|
||||
// Each interval has a "corresponding" dash which may be shifted partially or
|
||||
// fully out of its interval by the phase. So there may be up to two "visual"
|
||||
// dashes in an interval.
|
||||
// When computing coverage in an interval we look at three dashes. These are the
|
||||
// "corresponding" dashes from the current, previous, and next intervals. Any of these
|
||||
// may be phase shifted into our interval or even when phase=0 they may be within half a
|
||||
// pixel distance of a pixel center in the interval.
|
||||
// When in the first interval we need to check the dash from the last interval. And
|
||||
// similarly when in the last interval we need to check the dash from the first
|
||||
// interval. When 2pi is not perfectly divisible dashParams.y this is a boundary case.
|
||||
// We compute the dash begin/end angles in the vertex shader and apply them in the
|
||||
// fragment shader when we detect we're in the first/last interval.
|
||||
vertBuilder->codeAppend(R"(
|
||||
// The two boundary dash intervals are stored in wrapDashes.xy and .zw and fed
|
||||
// to the fragment shader as a varying.
|
||||
float4 wrapDashes;
|
||||
half lastIntervalLength = mod(6.28318530718, dashParams.y);
|
||||
// We can happen to be perfectly divisible.
|
||||
if (0 == lastIntervalLength) {
|
||||
lastIntervalLength = dashParams.y;
|
||||
}
|
||||
// Let 'l' be the last interval before reaching 2 pi.
|
||||
// Based on the phase determine whether (l-1)th, l-th, or (l+1)th interval's
|
||||
// "corresponding" dash appears in the l-th interval and is closest to the 0-th
|
||||
// interval.
|
||||
half offset = 0;
|
||||
if (-dashParams.w >= lastIntervalLength) {
|
||||
offset = -dashParams.y;
|
||||
} else if (dashParams.w > dashParams.y - lastIntervalLength) {
|
||||
offset = dashParams.y;
|
||||
}
|
||||
wrapDashes.x = -lastIntervalLength + offset - dashParams.w;
|
||||
// The end of this dash may be beyond the 2 pi and therefore clipped. Hence the
|
||||
// min.
|
||||
wrapDashes.y = min(wrapDashes.x + dashParams.x, 0);
|
||||
|
||||
// Based on the phase determine whether the -1st, 0th, or 1st interval's
|
||||
// "corresponding" dash appears in the 0th interval and is closest to l.
|
||||
offset = 0;
|
||||
if (dashParams.w >= dashParams.x) {
|
||||
offset = dashParams.y;
|
||||
} else if (-dashParams.w > dashParams.y - dashParams.x) {
|
||||
offset = -dashParams.y;
|
||||
}
|
||||
wrapDashes.z = lastIntervalLength + offset - dashParams.w;
|
||||
wrapDashes.w = wrapDashes.z + dashParams.x;
|
||||
// The start of the dash we're considering may be clipped by the start of the
|
||||
// circle.
|
||||
wrapDashes.z = max(wrapDashes.z, lastIntervalLength);
|
||||
)");
|
||||
vertBuilder->codeAppendf("%s = wrapDashes;", wrapDashes.vsOut());
|
||||
vertBuilder->codeAppendf("%s = lastIntervalLength;", lastIntervalLength.vsOut());
|
||||
fragBuilder->codeAppendf("half4 wrapDashes = %s;", wrapDashes.fsIn());
|
||||
fragBuilder->codeAppendf("half lastIntervalLength = %s;", lastIntervalLength.fsIn());
|
||||
|
||||
// setup pass through color
|
||||
varyingHandler->addPassThroughAttribute(
|
||||
bcscgp.fInColor, args.fOutputColor,
|
||||
GrGLSLVaryingHandler::Interpolation::kCanBeFlat);
|
||||
|
||||
// Setup position
|
||||
this->writeOutputPosition(vertBuilder, gpArgs, bcscgp.fInPosition->fName);
|
||||
|
||||
// emit transforms
|
||||
this->emitTransforms(vertBuilder,
|
||||
varyingHandler,
|
||||
uniformHandler,
|
||||
bcscgp.fInPosition->asShaderVar(),
|
||||
bcscgp.fLocalMatrix,
|
||||
args.fFPCoordTransformHandler);
|
||||
GrShaderVar fnArgs[] = {
|
||||
GrShaderVar("angleToEdge", kFloat_GrSLType),
|
||||
GrShaderVar("diameter", kFloat_GrSLType),
|
||||
};
|
||||
SkString fnName;
|
||||
fragBuilder->emitFunction(kFloat_GrSLType, "coverage_from_dash_edge",
|
||||
SK_ARRAY_COUNT(fnArgs), fnArgs, R"(
|
||||
float linearDist;
|
||||
angleToEdge = clamp(angleToEdge, -3.1415, 3.1415);
|
||||
linearDist = diameter * sin(angleToEdge / 2);
|
||||
return clamp(linearDist + 0.5, 0, 1);
|
||||
)",
|
||||
&fnName);
|
||||
fragBuilder->codeAppend(R"(
|
||||
float d = length(circleEdge.xy) * circleEdge.z;
|
||||
|
||||
// Compute coverage from outer/inner edges of the stroke.
|
||||
half distanceToOuterEdge = circleEdge.z - d;
|
||||
half edgeAlpha = clamp(distanceToOuterEdge, 0.0, 1.0);
|
||||
half distanceToInnerEdge = d - circleEdge.z * circleEdge.w;
|
||||
half innerAlpha = clamp(distanceToInnerEdge, 0.0, 1.0);
|
||||
edgeAlpha *= innerAlpha;
|
||||
|
||||
half angleFromStart = atan(circleEdge.y, circleEdge.x) - dashParams.z;
|
||||
angleFromStart = mod(angleFromStart, 6.28318530718);
|
||||
float x = mod(angleFromStart, dashParams.y);
|
||||
// Convert the radial distance from center to pixel into a diameter.
|
||||
d *= 2;
|
||||
half2 currDash = half2(-dashParams.w, dashParams.x - dashParams.w);
|
||||
half2 nextDash = half2(dashParams.y - dashParams.w,
|
||||
dashParams.y + dashParams.x - dashParams.w);
|
||||
half2 prevDash = half2(-dashParams.y - dashParams.w,
|
||||
-dashParams.y + dashParams.x - dashParams.w);
|
||||
half dashAlpha = 0;
|
||||
)");
|
||||
fragBuilder->codeAppendf(R"(
|
||||
if (angleFromStart - x + dashParams.y >= 6.28318530718) {
|
||||
dashAlpha += %s(x - wrapDashes.z, d) * %s(wrapDashes.w - x, d);
|
||||
currDash.y = min(currDash.y, lastIntervalLength);
|
||||
if (nextDash.x >= lastIntervalLength) {
|
||||
// The next dash is outside the 0..2pi range, throw it away
|
||||
nextDash.xy = half2(1000);
|
||||
} else {
|
||||
// Clip the end of the next dash to the end of the circle
|
||||
nextDash.y = min(nextDash.y, lastIntervalLength);
|
||||
}
|
||||
}
|
||||
)", fnName.c_str(), fnName.c_str());
|
||||
fragBuilder->codeAppendf(R"(
|
||||
if (angleFromStart - x - dashParams.y < -0.01) {
|
||||
dashAlpha += %s(x - wrapDashes.x, d) * %s(wrapDashes.y - x, d);
|
||||
currDash.x = max(currDash.x, 0);
|
||||
if (prevDash.y <= 0) {
|
||||
// The previous dash is outside the 0..2pi range, throw it away
|
||||
prevDash.xy = half2(1000);
|
||||
} else {
|
||||
// Clip the start previous dash to the start of the circle
|
||||
prevDash.x = max(prevDash.x, 0);
|
||||
}
|
||||
}
|
||||
)", fnName.c_str(), fnName.c_str());
|
||||
fragBuilder->codeAppendf(R"(
|
||||
dashAlpha += %s(x - currDash.x, d) * %s(currDash.y - x, d);
|
||||
dashAlpha += %s(x - nextDash.x, d) * %s(nextDash.y - x, d);
|
||||
dashAlpha += %s(x - prevDash.x, d) * %s(prevDash.y - x, d);
|
||||
dashAlpha = min(dashAlpha, 1);
|
||||
edgeAlpha *= dashAlpha;
|
||||
)", fnName.c_str(), fnName.c_str(), fnName.c_str(), fnName.c_str(), fnName.c_str(),
|
||||
fnName.c_str());
|
||||
fragBuilder->codeAppendf("%s = half4(edgeAlpha);", args.fOutputCoverage);
|
||||
}
|
||||
|
||||
static void GenKey(const GrGeometryProcessor& gp,
|
||||
const GrShaderCaps&,
|
||||
GrProcessorKeyBuilder* b) {
|
||||
const ButtCapDashedCircleGeometryProcessor& bcscgp =
|
||||
gp.cast<ButtCapDashedCircleGeometryProcessor>();
|
||||
b->add32(bcscgp.fLocalMatrix.hasPerspective());
|
||||
}
|
||||
|
||||
void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor& primProc,
|
||||
FPCoordTransformIter&& transformIter) override {
|
||||
this->setTransformDataHelper(
|
||||
primProc.cast<ButtCapDashedCircleGeometryProcessor>().fLocalMatrix, pdman,
|
||||
&transformIter);
|
||||
}
|
||||
|
||||
private:
|
||||
typedef GrGLSLGeometryProcessor INHERITED;
|
||||
};
|
||||
|
||||
SkMatrix fLocalMatrix;
|
||||
const Attribute* fInPosition;
|
||||
const Attribute* fInColor;
|
||||
const Attribute* fInCircleEdge;
|
||||
const Attribute* fInDashParams;
|
||||
|
||||
GR_DECLARE_GEOMETRY_PROCESSOR_TEST
|
||||
|
||||
typedef GrGeometryProcessor INHERITED;
|
||||
};
|
||||
|
||||
#if GR_TEST_UTILS
|
||||
sk_sp<GrGeometryProcessor> ButtCapDashedCircleGeometryProcessor::TestCreate(GrProcessorTestData* d) {
|
||||
const SkMatrix& matrix = GrTest::TestMatrix(d->fRandom);
|
||||
return sk_sp<GrGeometryProcessor>(new ButtCapDashedCircleGeometryProcessor(matrix));
|
||||
}
|
||||
#endif
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
@ -1240,6 +1474,301 @@ private:
|
||||
typedef GrMeshDrawOp INHERITED;
|
||||
};
|
||||
|
||||
class ButtCapDashedCircleOp final : public GrMeshDrawOp {
|
||||
private:
|
||||
using Helper = GrSimpleMeshDrawOpHelper;
|
||||
|
||||
public:
|
||||
DEFINE_OP_CLASS_ID
|
||||
|
||||
static std::unique_ptr<GrDrawOp> Make(GrPaint&& paint, const SkMatrix& viewMatrix,
|
||||
SkPoint center, SkScalar radius, SkScalar strokeWidth,
|
||||
SkScalar startAngle, SkScalar onAngle, SkScalar offAngle,
|
||||
SkScalar phaseAngle) {
|
||||
SkASSERT(circle_stays_circle(viewMatrix));
|
||||
SkASSERT(strokeWidth < 2 * radius);
|
||||
return Helper::FactoryHelper<ButtCapDashedCircleOp>(std::move(paint), viewMatrix, center,
|
||||
radius, strokeWidth, startAngle,
|
||||
onAngle, offAngle, phaseAngle);
|
||||
}
|
||||
|
||||
ButtCapDashedCircleOp(const Helper::MakeArgs& helperArgs, GrColor color,
|
||||
const SkMatrix& viewMatrix, SkPoint center, SkScalar radius,
|
||||
SkScalar strokeWidth, SkScalar startAngle, SkScalar onAngle,
|
||||
SkScalar offAngle, SkScalar phaseAngle)
|
||||
: GrMeshDrawOp(ClassID()), fHelper(helperArgs, GrAAType::kCoverage) {
|
||||
SkASSERT(circle_stays_circle(viewMatrix));
|
||||
viewMatrix.mapPoints(¢er, 1);
|
||||
radius = viewMatrix.mapRadius(radius);
|
||||
strokeWidth = viewMatrix.mapRadius(strokeWidth);
|
||||
|
||||
// Determine the angle where the circle starts in device space and whether its orientation
|
||||
// has been reversed.
|
||||
SkVector start;
|
||||
bool reflection;
|
||||
if (!startAngle) {
|
||||
start = {1, 0};
|
||||
} else {
|
||||
start.fY = SkScalarSinCos(startAngle, &start.fX);
|
||||
}
|
||||
viewMatrix.mapVectors(&start, 1);
|
||||
startAngle = SkScalarATan2(start.fY, start.fX);
|
||||
reflection = (viewMatrix.getScaleX() * viewMatrix.getScaleY() -
|
||||
viewMatrix.getSkewX() * viewMatrix.getSkewY()) < 0;
|
||||
|
||||
auto totalAngle = onAngle + offAngle;
|
||||
phaseAngle = SkScalarMod(phaseAngle + totalAngle / 2, totalAngle) - totalAngle / 2;
|
||||
|
||||
SkScalar halfWidth = 0;
|
||||
if (SkScalarNearlyZero(strokeWidth)) {
|
||||
halfWidth = SK_ScalarHalf;
|
||||
} else {
|
||||
halfWidth = SkScalarHalf(strokeWidth);
|
||||
}
|
||||
|
||||
SkScalar outerRadius = radius + halfWidth;
|
||||
SkScalar innerRadius = radius - halfWidth;
|
||||
|
||||
// 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;
|
||||
fViewMatrixIfUsingLocalCoords = viewMatrix;
|
||||
|
||||
SkRect devBounds = SkRect::MakeLTRB(center.fX - outerRadius, center.fY - outerRadius,
|
||||
center.fX + outerRadius, center.fY + outerRadius);
|
||||
|
||||
// We store whether there is a reflection as a negative total angle.
|
||||
if (reflection) {
|
||||
totalAngle = -totalAngle;
|
||||
}
|
||||
fCircles.push_back(Circle{
|
||||
color,
|
||||
outerRadius,
|
||||
innerRadius,
|
||||
onAngle,
|
||||
totalAngle,
|
||||
startAngle,
|
||||
phaseAngle,
|
||||
devBounds
|
||||
});
|
||||
// Use the original radius and stroke radius for the bounds so that it does not include the
|
||||
// AA bloat.
|
||||
radius += halfWidth;
|
||||
this->setBounds(
|
||||
{center.fX - radius, center.fY - radius, center.fX + radius, center.fY + radius},
|
||||
HasAABloat::kYes, IsZeroArea::kNo);
|
||||
fVertCount = circle_type_to_vert_count(true);
|
||||
fIndexCount = circle_type_to_index_count(true);
|
||||
}
|
||||
|
||||
const char* name() const override { return "ButtCappedDashedCircleOp"; }
|
||||
|
||||
void visitProxies(const VisitProxyFunc& func) const override { fHelper.visitProxies(func); }
|
||||
|
||||
SkString dumpInfo() const override {
|
||||
SkString string;
|
||||
for (int i = 0; i < fCircles.count(); ++i) {
|
||||
string.appendf(
|
||||
"Color: 0x%08x Rect [L: %.2f, T: %.2f, R: %.2f, B: %.2f],"
|
||||
"InnerRad: %.2f, OuterRad: %.2f, OnAngle: %.2f, TotalAngle: %.2f, "
|
||||
"Phase: %.2f\n",
|
||||
fCircles[i].fColor, fCircles[i].fDevBounds.fLeft, fCircles[i].fDevBounds.fTop,
|
||||
fCircles[i].fDevBounds.fRight, fCircles[i].fDevBounds.fBottom,
|
||||
fCircles[i].fInnerRadius, fCircles[i].fOuterRadius, fCircles[i].fOnAngle,
|
||||
fCircles[i].fTotalAngle, fCircles[i].fPhaseAngle);
|
||||
}
|
||||
string += fHelper.dumpInfo();
|
||||
string += INHERITED::dumpInfo();
|
||||
return string;
|
||||
}
|
||||
|
||||
RequiresDstTexture finalize(const GrCaps& caps, const GrAppliedClip* clip,
|
||||
GrPixelConfigIsClamped dstIsClamped) override {
|
||||
GrColor* color = &fCircles.front().fColor;
|
||||
return fHelper.xpRequiresDstTexture(caps, clip, dstIsClamped,
|
||||
GrProcessorAnalysisCoverage::kSingleChannel, color);
|
||||
}
|
||||
|
||||
FixedFunctionFlags fixedFunctionFlags() const override { return fHelper.fixedFunctionFlags(); }
|
||||
|
||||
private:
|
||||
void onPrepareDraws(Target* target) override {
|
||||
SkMatrix localMatrix;
|
||||
if (!fViewMatrixIfUsingLocalCoords.invert(&localMatrix)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Setup geometry processor
|
||||
sk_sp<GrGeometryProcessor> gp(new ButtCapDashedCircleGeometryProcessor(localMatrix));
|
||||
|
||||
struct CircleVertex {
|
||||
SkPoint fPos;
|
||||
GrColor fColor;
|
||||
SkPoint fOffset;
|
||||
SkScalar fOuterRadius;
|
||||
SkScalar fInnerRadius;
|
||||
SkScalar fOnAngle;
|
||||
SkScalar fTotalAngle;
|
||||
SkScalar fStartAngle;
|
||||
SkScalar fPhaseAngle;
|
||||
};
|
||||
|
||||
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 (const auto& circle : fCircles) {
|
||||
// The inner radius in the vertex data must be specified in normalized space so that
|
||||
// length() can be called with smaller values to avoid precision issues with half
|
||||
// floats.
|
||||
auto normInnerRadius = circle.fInnerRadius / circle.fOuterRadius;
|
||||
const SkRect& bounds = circle.fDevBounds;
|
||||
bool reflect = false;
|
||||
SkScalar totalAngle = circle.fTotalAngle;
|
||||
if (totalAngle < 0) {
|
||||
reflect = true;
|
||||
totalAngle = -totalAngle;
|
||||
}
|
||||
|
||||
// The bounding geometry for the circle is composed of an outer bounding octagon and
|
||||
// an inner bounded octagon.
|
||||
|
||||
// Initializes the attributes that are the same at each vertex. Also applies reflection.
|
||||
auto init_const_attrs_and_reflect = [&](CircleVertex* v) {
|
||||
v->fColor = circle.fColor;
|
||||
v->fOuterRadius = circle.fOuterRadius;
|
||||
v->fInnerRadius = normInnerRadius;
|
||||
v->fOnAngle = circle.fOnAngle;
|
||||
v->fTotalAngle = totalAngle;
|
||||
v->fStartAngle = circle.fStartAngle;
|
||||
v->fPhaseAngle = circle.fPhaseAngle;
|
||||
if (reflect) {
|
||||
v->fStartAngle = -v->fStartAngle;
|
||||
v->fOffset.fY = -v->fOffset.fY;
|
||||
}
|
||||
};
|
||||
|
||||
// Compute the vertices of the outer octagon.
|
||||
SkPoint center = SkPoint::Make(bounds.centerX(), bounds.centerY());
|
||||
SkScalar halfWidth = 0.5f * bounds.width();
|
||||
auto init_outer_vertex = [&](int idx, SkScalar x, SkScalar y) {
|
||||
CircleVertex* v = reinterpret_cast<CircleVertex*>(vertices + idx * vertexStride);
|
||||
v->fPos = center + SkPoint{x * halfWidth, y * halfWidth};
|
||||
v->fOffset = {x, y};
|
||||
init_const_attrs_and_reflect(v);
|
||||
};
|
||||
static constexpr SkScalar kOctOffset = 0.41421356237f; // sqrt(2) - 1
|
||||
init_outer_vertex(0, -kOctOffset, -1);
|
||||
init_outer_vertex(1, kOctOffset, -1);
|
||||
init_outer_vertex(2, 1, -kOctOffset);
|
||||
init_outer_vertex(3, 1, kOctOffset);
|
||||
init_outer_vertex(4, kOctOffset, 1);
|
||||
init_outer_vertex(5, -kOctOffset, 1);
|
||||
init_outer_vertex(6, -1, kOctOffset);
|
||||
init_outer_vertex(7, -1, -kOctOffset);
|
||||
|
||||
// Compute the vertices of the inner octagon.
|
||||
auto init_inner_vertex = [&](int idx, SkScalar x, SkScalar y) {
|
||||
CircleVertex* v =
|
||||
reinterpret_cast<CircleVertex*>(vertices + (idx + 8) * vertexStride);
|
||||
v->fPos = center + SkPoint{x * circle.fInnerRadius, y * circle.fInnerRadius};
|
||||
v->fOffset = {x * normInnerRadius, y * normInnerRadius};
|
||||
init_const_attrs_and_reflect(v);
|
||||
};
|
||||
|
||||
// cosine and sine of pi/8
|
||||
static constexpr SkScalar kCos = 0.923579533f;
|
||||
static constexpr SkScalar kSin = 0.382683432f;
|
||||
|
||||
init_inner_vertex(0, -kSin, -kCos);
|
||||
init_inner_vertex(1, kSin, -kCos);
|
||||
init_inner_vertex(2, kCos, -kSin);
|
||||
init_inner_vertex(3, kCos, kSin);
|
||||
init_inner_vertex(4, kSin, kCos);
|
||||
init_inner_vertex(5, -kSin, kCos);
|
||||
init_inner_vertex(6, -kCos, kSin);
|
||||
init_inner_vertex(7, -kCos, -kSin);
|
||||
|
||||
const uint16_t* primIndices = circle_type_to_indices(true);
|
||||
const int primIndexCount = circle_type_to_index_count(true);
|
||||
for (int i = 0; i < primIndexCount; ++i) {
|
||||
*indices++ = primIndices[i] + currStartVertex;
|
||||
}
|
||||
|
||||
currStartVertex += circle_type_to_vert_count(true);
|
||||
vertices += circle_type_to_vert_count(true) * vertexStride;
|
||||
}
|
||||
|
||||
GrMesh mesh(GrPrimitiveType::kTriangles);
|
||||
mesh.setIndexed(indexBuffer, fIndexCount, firstIndex, 0, fVertCount - 1);
|
||||
mesh.setVertexData(vertexBuffer, firstVertex);
|
||||
target->draw(gp.get(), fHelper.makePipeline(target), mesh);
|
||||
}
|
||||
|
||||
bool onCombineIfPossible(GrOp* t, const GrCaps& caps) override {
|
||||
ButtCapDashedCircleOp* that = t->cast<ButtCapDashedCircleOp>();
|
||||
|
||||
// can only represent 65535 unique vertices with 16-bit indices
|
||||
if (fVertCount + that->fVertCount > 65536) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!fHelper.isCompatible(that->fHelper, caps, this->bounds(), that->bounds())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (fHelper.usesLocalCoords() &&
|
||||
!fViewMatrixIfUsingLocalCoords.cheapEqualTo(that->fViewMatrixIfUsingLocalCoords)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
fCircles.push_back_n(that->fCircles.count(), that->fCircles.begin());
|
||||
this->joinBounds(*that);
|
||||
fVertCount += that->fVertCount;
|
||||
fIndexCount += that->fIndexCount;
|
||||
return true;
|
||||
}
|
||||
|
||||
struct Circle {
|
||||
GrColor fColor;
|
||||
SkScalar fOuterRadius;
|
||||
SkScalar fInnerRadius;
|
||||
SkScalar fOnAngle;
|
||||
SkScalar fTotalAngle;
|
||||
SkScalar fStartAngle;
|
||||
SkScalar fPhaseAngle;
|
||||
SkRect fDevBounds;
|
||||
};
|
||||
|
||||
SkMatrix fViewMatrixIfUsingLocalCoords;
|
||||
Helper fHelper;
|
||||
SkSTArray<1, Circle, true> fCircles;
|
||||
int fVertCount;
|
||||
int fIndexCount;
|
||||
|
||||
typedef GrMeshDrawOp INHERITED;
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class EllipseOp : public GrMeshDrawOp {
|
||||
@ -2487,7 +3016,8 @@ std::unique_ptr<GrDrawOp> GrOvalOpFactory::MakeRRectOp(GrPaint&& paint,
|
||||
const SkStrokeRec& stroke,
|
||||
const GrShaderCaps* shaderCaps) {
|
||||
if (rrect.isOval()) {
|
||||
return MakeOvalOp(std::move(paint), viewMatrix, rrect.getBounds(), stroke, shaderCaps);
|
||||
return MakeOvalOp(std::move(paint), viewMatrix, rrect.getBounds(), GrStyle(stroke, nullptr),
|
||||
shaderCaps);
|
||||
}
|
||||
|
||||
if (!viewMatrix.rectStaysRect() || !rrect.isSimple()) {
|
||||
@ -2502,25 +3032,55 @@ std::unique_ptr<GrDrawOp> GrOvalOpFactory::MakeRRectOp(GrPaint&& paint,
|
||||
std::unique_ptr<GrDrawOp> GrOvalOpFactory::MakeOvalOp(GrPaint&& paint,
|
||||
const SkMatrix& viewMatrix,
|
||||
const SkRect& oval,
|
||||
const SkStrokeRec& stroke,
|
||||
const GrStyle& style,
|
||||
const GrShaderCaps* shaderCaps) {
|
||||
// we can draw circles
|
||||
SkScalar width = oval.width();
|
||||
if (width > SK_ScalarNearlyZero && SkScalarNearlyEqual(width, oval.height()) &&
|
||||
circle_stays_circle(viewMatrix)) {
|
||||
auto r = width / 2.f;
|
||||
SkPoint center = {oval.centerX(), oval.centerY()};
|
||||
return CircleOp::Make(std::move(paint), viewMatrix, center, width / 2.f,
|
||||
GrStyle(stroke, nullptr));
|
||||
if (style.hasNonDashPathEffect()) {
|
||||
return nullptr;
|
||||
} else if (style.isDashed()) {
|
||||
if (style.strokeRec().getCap() != SkPaint::kButt_Cap ||
|
||||
style.dashIntervalCnt() != 2 || style.strokeRec().getWidth() >= width) {
|
||||
return nullptr;
|
||||
}
|
||||
auto onInterval = style.dashIntervals()[0];
|
||||
auto offInterval = style.dashIntervals()[1];
|
||||
if (offInterval == 0) {
|
||||
GrStyle strokeStyle(style.strokeRec(), nullptr);
|
||||
return MakeOvalOp(std::move(paint), viewMatrix, oval, strokeStyle, shaderCaps);
|
||||
} else if (onInterval == 0) {
|
||||
// There is nothing to draw but we have no way to indicate that here.
|
||||
return nullptr;
|
||||
}
|
||||
auto angularOnInterval = onInterval / r;
|
||||
auto angularOffInterval = offInterval / r;
|
||||
auto phaseAngle = style.dashPhase() / r;
|
||||
// Currently this function doesn't accept ovals with different start angles, though
|
||||
// it could.
|
||||
static const SkScalar kStartAngle = 0.f;
|
||||
return ButtCapDashedCircleOp::Make(std::move(paint), viewMatrix, center, r,
|
||||
style.strokeRec().getWidth(), kStartAngle,
|
||||
angularOnInterval, angularOffInterval, phaseAngle);
|
||||
}
|
||||
return CircleOp::Make(std::move(paint), viewMatrix, center, r, style);
|
||||
}
|
||||
|
||||
if (style.pathEffect()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// prefer the device space ellipse op for batchability
|
||||
if (viewMatrix.rectStaysRect()) {
|
||||
return EllipseOp::Make(std::move(paint), viewMatrix, oval, stroke);
|
||||
return EllipseOp::Make(std::move(paint), viewMatrix, oval, style.strokeRec());
|
||||
}
|
||||
|
||||
// Otherwise, if we have shader derivative support, render as device-independent
|
||||
if (shaderCaps->shaderDerivativeSupport()) {
|
||||
return DIEllipseOp::Make(std::move(paint), viewMatrix, oval, stroke);
|
||||
return DIEllipseOp::Make(std::move(paint), viewMatrix, oval, style.strokeRec());
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
@ -2582,6 +3142,27 @@ GR_DRAW_OP_TEST_DEFINE(CircleOp) {
|
||||
} while (true);
|
||||
}
|
||||
|
||||
GR_DRAW_OP_TEST_DEFINE(ButtCapDashedCircleOp) {
|
||||
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);
|
||||
SkRect circle = GrTest::TestSquare(random);
|
||||
SkPoint center = {circle.centerX(), circle.centerY()};
|
||||
SkScalar radius = circle.width() / 2.f;
|
||||
SkScalar strokeWidth = random->nextRangeScalar(0.001f * radius, 1.8f * radius);
|
||||
SkScalar onAngle = random->nextRangeScalar(0.01f, 1000.f);
|
||||
SkScalar offAngle = random->nextRangeScalar(0.01f, 1000.f);
|
||||
SkScalar startAngle = random->nextRangeScalar(-1000.f, 1000.f);
|
||||
SkScalar phase = random->nextRangeScalar(-1000.f, 1000.f);
|
||||
return ButtCapDashedCircleOp::Make(std::move(paint), viewMatrix, center, radius, strokeWidth,
|
||||
startAngle, onAngle, offAngle, phase);
|
||||
}
|
||||
|
||||
GR_DRAW_OP_TEST_DEFINE(EllipseOp) {
|
||||
SkMatrix viewMatrix = GrTest::TestMatrixRectStaysRect(random);
|
||||
SkRect ellipse = GrTest::TestSquare(random);
|
||||
|
@ -28,7 +28,7 @@ public:
|
||||
static std::unique_ptr<GrDrawOp> MakeOvalOp(GrPaint&&,
|
||||
const SkMatrix&,
|
||||
const SkRect& oval,
|
||||
const SkStrokeRec&,
|
||||
const GrStyle& style,
|
||||
const GrShaderCaps*);
|
||||
|
||||
static std::unique_ptr<GrDrawOp> MakeRRectOp(GrPaint&&,
|
||||
|
Loading…
Reference in New Issue
Block a user